Detectando os principais acadêmicos das imagens faciais

Ferdinand Mütsch Blocked Desbloquear Seguir Seguindo 2 de janeiro

Há alguns meses, li um artigo com o título “As redes neurais profundas são mais precisas que os humanos na detecção da orientação sexual das imagens faciais” , o que causou muita controvérsia . Embora eu não queira comentar sobre a metodologia e qualidade do artigo (que já foi feito, por exemplo, em um artigo de Jeremy Howard ), achei muito interessante e inspirador. Em suma, os pesquisadores coletaram fotos de face de sites de namoro e construíram um modelo de aprendizado de máquina para classificar a orientação sexual das pessoas e alcançaram uma precisão impressionante com sua abordagem.

Esta postagem de convidado resume os resultados como:

AI não pode dizer se você é gay… mas pode dizer se você é um estereótipo ambulante.

E, de fato, muitas vezes vemos pessoas que parecem muito estereotipadas. Tentei pensar em mais desses cenários e cheguei à conclusão de que outro ambiente, onde esse fenômeno pode ser encontrado com bastante frequência, é um campus universitário. Muitas vezes você anda pelo campus e vê alunos que se parecem com um estudante de direito, um nerd da ciência da computação, um esportista, etc. Às vezes fico tão curioso que quase pergunto se minha suposição está correta.

Depois de ler o artigo acima, imaginei se algum modelo de aprendizado de máquina poderia quantificar essas suposições latentes e descobrir uma profissão ou um curso de estudante de aparência estereotipada.

Embora eu só tenha um pouco mais do que conhecimento básico em aprendizado de máquina, especialmente na classificação de imagens usando redes neurais profundas, eu tomei isso como um desafio pessoal para construir um classificador, que detecta os principais acadêmicos com base em uma imagem de seu rosto .

aviso Legal

Por favor, não leve este artigo a sério demais. Eu não sou um especialista em aprendizado de máquina ou um cientista profissional. Pode haver alguns erros na minha metodologia ou implementação. No entanto, eu adoraria ouvir seus pensamentos e feedback.

Abordagem

Minha primeira (e última) abordagem foi (1.) coletar retratos de estudantes ou outros acadêmicos, (2.) rotulá-los com um conjunto pequeno e limitado de classes, correspondente ao seu curso principal, e eventualmente (3.) rede neural convolucional (CNN) como classificador. Pensei em áreas de estudo, cujos alunos podem parecer um pouco estereotipados e ter quatro classes:

  1. ciência da computação (~ cs)
  2. economia (~ econ)
  3. Lingüística (alemã) (~ alemão)
  4. engenharia mecânica (~ mecânica)

Por favor note que isto não pretende ser ofensivo por qualquer meio! (Eu sou um nerd da ciência da computação).

Obtendo os dados

O primeiro pré-requisito é o treinamento de dados – como sempre, ao fazer o aprendizado de máquina. E desde que eu apontei para treinar uma rede neural convolucional (CNN), deveria haver muitos dados, preferivelmente.

Apesar de ter sido uma abordagem engraçada andar pelo meu campus e perguntar aos alunos o curso e uma foto do rosto deles, eu provavelmente não teria muitos dados. Em vez disso, decidi rastrear fotos de sites de universidades . Quase todos os departamentos de todas as universidades têm uma página chamada " Funcionários ", "Pessoas", "Pesquisadores" ou similares em seus sites. Embora essas não sejam particularmente listas de alunos, mas de professores, assistentes de pesquisa e candidatos a PhD, presumi que essas imagens ainda deveriam ser suficientes como dados de treinamento.

Escrevi vários scripts de rastreador usando o Python e o Selenium WebDriver para rastrear 57 sites diferentes, incluindo os sites de vários departamentos das seguintes universidades:

  • Instituto de Tecnologia de Karlsruhe
  • TU Munique
  • Universidade de Munique
  • Universidade de Würzburg
  • Universidade de Siegen
  • Universidade de Bayreuth
  • Universidade de Feiburg
  • Universidade de Heidelberg
  • Universidade de Erlangen
  • Universidade de Bamberg
  • Universidade de Mannheim

Depois de um pouco de limpeza de dados manual (removendo fotos sem rostos, rodando fotos, …), acabei com um total de 1369 imagens rotuladas de quatro classes diferentes. Enquanto isso não é muito dados para treinar uma CNN, decidi tentar de qualquer maneira.

Exemplos

Imagens

Um trecho da pasta contendo todas as imagens brutas após o rastreamento:

(Se você estiver em uma dessas fotos e quiser ser removido, entre em contato comigo.)

Rótulos

Um trecho de index.csv contendo rótulos e meta-dados para cada imagem:

 id, categoria, image_url, nome 
c35464fd, mecânico, http: //www.fast.kit.edu/lff/1011_1105.php,Prof. Dr. rer. nat. Frank Gauterin
a73d11a7, cs, http: //h2t.anthropomatik.kit.edu/21_1784.php, Kevin Liang
97e230ff, econ, http: //marketing.iism.kit.edu/21_371.php,Dr. Hanna Schumacher
cde71a5c, alemão, https: //www.germanistik.uni-muenchen.de/personal/ndl/mitarbeiter/bach/index.html,Dr. Oliver Bach

Pré-processamento dos dados

Antes que as imagens pudessem ser usadas como dados de treinamento para um algoritmo de aprendizado, um pouco de pré-processamento precisava ser aplicado. Principalmente, eu fiz duas etapas principais de pré-processamento.

  1. Cortar imagens em faces – Como você pode ver, as fotos são tiradas de diferentes ângulos, algumas contêm muito fundo, outras não são centralizadas, etc. Para obter melhores dados de treinamento, as fotos precisam ser cortadas apenas para o rosto e para o rosto. nada mais.
  2. Escala – Todas as imagens têm diferentes resoluções, mas precisam ser exatamente do mesmo tamanho para serem usadas como entrada para uma rede neural.

Para alcançar esses dois passos de pré-processamento, usei uma ótima ferramenta Python baseada em OpenCV, de código aberto, chamada autocrop com o seguinte comando:

autocrop -i raw -o preprocessed -w 128 -H 128 > autocrop.log .

Isso detecta o rosto em todas as imagens na pasta raw , corta a imagem para essa face, redimensiona a imagem resultante para 128 x 128 pixels e a salva na pasta preprocessedpreprocessed . Claro, existem algumas fotos em que o algoritmo não consegue detectar um rosto. Aqueles estão logados no stdout e persistiram no autocrop.log .

Além disso, eu escrevi um script que analisa autocrop.log para obter as imagens com falha e subseqüentemente dividir as imagens em trem (70%), teste (20%) e validação (10%) e copiá-las para uma estrutura de pastas que seja compatível para o formato exigido pelo Keras ImageDataGenerator para ler os dados de treinamento.

 - cru 
- index.csv
- c35464fd.jpg
- a73d11a7.jpg
- ...
- pré-processado
- trem
- cs
- a73d11a7.jpg
- ...
- econ
- 97e230ff.jpg
- ...
- alemão
- cde71a5c.jpg
- ...
- mecânica
- c35464fd.jpg
- ...
- teste
- cs
- ...
- ...
- validação
- cs
- ...
- ...

Construindo um modelo

Código

Eu decidi começar de forma simples e ver se alguma coisa pode ser aprendida com os dados. Eu defini a seguinte arquitetura CNN simples em Keras:

 _________________________________________________________________ 
Camada (tipo) Forma de saída Param #
================================================== ===============
conv2d_1 (Conv2D) (Nenhum, 62, 62, 32) 320
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (Nenhum, 31, 31, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (Nenhum, 29, 29, 32) 9248
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (Nenhum, 14, 14, 32) 0
_________________________________________________________________
conv2d_3 (Conv2D) (Nenhum, 12, 12, 32) 9248
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (Nenhum, 6, 6, 32) 0
_________________________________________________________________
flatten_1 (achatar) (nenhum, 1152) 0
_________________________________________________________________
dense_1 (denso) (nenhum, 64) 73792
_________________________________________________________________
dropout_1 (Saque) (Nenhum, 64) 0
_________________________________________________________________
denso_2 (denso) (nenhum, 4) 260
================================================== ===============
Total de Params: 92.868
Params treináveis: 92.868
Params não treináveis: 0

Usei o ImageDataGenerator da Keras (excelente ferramenta!) Para ler imagens em arrays NumPy, redimensioná-las para uma forma de (64, 63, 3) (64 x 64 pixels, RGB) e realizar algum aumento de dados usando transformações como rotações, zooming, flipping horizontal, etc para explodir meus dados de treinamento e espero construir modelos mais robustos, menos equipados.

Eu deixei o modelo treinar por 100 épocas , usando o otimizador Adam com parâmetros padrão e perda de crossentropy categórica , um tamanho de mini-lote de 32 e 3x de aumento (use transformações para aumentar os dados de treinamento por um fator de três).

Resultados (57,1% de precisão)

A precisão máxima de validação de 0,66 foi alcançada após 74 épocas. A precisão do teste acabou sendo 0,571 . Considerando que um modelo bastante simples foi treinado completamente do zero com menos de 1000 exemplos de treinamento, estou bastante impressionado com esse resultado. Isso significa que, em média, o modelo prevê mais do que cada segundo maior aluno corretamente. A probabilidade a priori de uma classificação correta é de 0,25 , então o modelo definitivamente aprendeu pelo menos alguma coisa.

Abordagem 2: Ajuste fino do VGGFace

Código

Como uma alternativa a um modelo CNN simples e personalizado, que é treinado do zero, eu queria seguir a abordagem comum de ajustar os pesos de um modelo pré-treinado existente. A ideia básica de tal abordagem é não "reinventar a roda", mas aproveitar o que já foi aprendido antes e apenas adaptar um pouco esse "conhecimento" (em forma de pesos) a um certo problema. Os recursos latentes em imagens, que um algoritmo de aprendizado já havia extraído de um conjunto gigantesco de dados de treinamento antes, podem ser aproveitados. A “Classificação de imagens usando modelos pré-treinados em Keras” oferece uma excelente visão geral de como funciona o ajuste fino e como ele é diferente do aprendizado de transferência e modelos personalizados. As expectativas são de que meu problema de classificação possa ser resolvido com mais precisão, com menos dados.

Eu decidi usar uma arquitetura de modelo VGG16 treinada no VGGFace como base (usando a implementação keras-vggface ) e segui este guia para ajustá-lo. O VGGFace é um conjunto de dados publicado pela Universidade de Oxford que contém mais de 3,3 milhões de imagens faciais. Assim, esperava-se que tivesse extraído características faciais muito robustas e que fosse bastante adequado para a classificação de rostos.

Etapa 1: Aprendizagem de transferência para inicializar pesos

Minha implementação consiste em duas etapas, pois é recomendável

Para executar o ajuste fino, todas as camadas devem começar com pesos devidamente treinados.

Nesta primeira etapa, o aprendizado de transferência é usado para encontrar pesos adequados para um conjunto de camadas de classificação recém-adicionadas, personalizadas e totalmente conectadas. Estes são usados como pesos iniciais na etapa 2 mais adiante. Para executar essa inicialização, um modelo VGGFace pré-treinado, com as camadas de classificação final cortadas, é usado para extrair 128 recursos de gargalo para cada imagem. Posteriormente, outro modelo minúsculo, consistindo de camadas totalmente conectadas, é treinado sobre esses recursos para realizar a classificação final. Os pesos são mantidos em um arquivo e carregados novamente na etapa 2.

A arquitetura do modelo se parece com isso:

 ________________________________________________________________ 
 Camada (tipo) Forma de saída Param # 
 ================================================== =============== 
 dense_1 (denso) (nenhum, 128) 65664 
 _________________________________________________________________ 
 dropout_1 (Dropout) (Nenhum, 128) 0 
 _________________________________________________________________ 
 denso_2 (denso) (nenhum, 4) 516 
 ================================================== =============== 
 Params totais: 66.180 
 Params treináveis: 66.180 
 Params não treináveis: 0 

Etapa 2: ajuste fino

Nesta segunda etapa, um modelo VGGFace pré-treinado (com as primeiras n – 3 camadas congeladas) é usado em combinação com as camadas superiores pré-treinadas da etapa 1 para ajustar os pesos para nossa tarefa de classificação específica. São necessários mini lotes de (128, 128, 3) tensores em forma (128 x 128 pixels, RGB) como entrada e prevê probabilidades para cada uma das nossas quatro classes alvo.

A arquitetura do modelo combinado se parece com isso:

 _________________________________________________________________ 
Camada (tipo) Forma de saída Param #
================================================== ===============
vggface_vgg16 (Modelo) (Nenhum, 512) 14714688
_________________________________________________________________
topo (Sequencial) (Nenhum, 4) 66180
================================================== ===============
Params totais: 14.780.868
Parâmetros treináveis: 2.425.988
Params não treináveis: 12.354.880

top é o modelo descrito no passo 1, vggface_vgg16 é um modelo VGG16 e é assim:

 _________________________________________________________________ 
Camada (tipo) Forma de saída Param #
================================================== ===============
input_3 (InputLayer) (Nenhum, 128, 128, 3) 0
_________________________________________________________________
conv1_1 (Conv2D) (Nenhum, 128, 128, 64) 1792
_________________________________________________________________
conv1_2 (Conv2D) (Nenhuma, 128, 128, 64) 36928
_________________________________________________________________
pool1 (MaxPooling2D) (Nenhum, 64, 64, 64) 0
_________________________________________________________________
conv2_1 (Conv2D) (Nenhuma, 64, 64, 128) 73856
_________________________________________________________________
conv2_2 (Conv2D) (nenhum, 64, 64, 128) 147584
_________________________________________________________________
pool2 (MaxPooling2D) (Nenhum, 32, 32, 128) 0
_________________________________________________________________
conv3_1 (Conv2D) (Nenhum, 32, 32, 256) 295168
_________________________________________________________________
conv3_2 (Conv2D) (nenhum, 32, 32, 256) 590080
_________________________________________________________________
conv3_3 (Conv2D) (nenhum, 32, 32, 256) 590080
_________________________________________________________________
pool3 (MaxPooling2D) (Nenhum, 16, 16, 256) 0
_________________________________________________________________
conv4_1 (Conv2D) (Nenhuma, 16, 16, 512) 1180160
_________________________________________________________________
conv4_2 (Conv2D) (Nenhum, 16, 16, 512) 2359808
_________________________________________________________________
conv4_3 (Conv2D) (Nenhuma, 16, 16, 512) 2359808
_________________________________________________________________
pool4 (MaxPooling2D) (Nenhum, 8, 8, 512) 0
_________________________________________________________________
conv5_1 (Conv2D) (Nenhum, 8, 8, 512) 2359808
_________________________________________________________________
conv5_2 (Conv2D) (Nenhum, 8, 8, 512) 2359808
_________________________________________________________________
conv5_3 (Conv2D) (Nenhuma, 8, 8, 512) 2359808
_________________________________________________________________
pool5 (MaxPooling2D) (Nenhum, 4, 4, 512) 0
_________________________________________________________________
global_max_pooling2d_3 (Glob (Nenhum, 512) 0
================================================== ===============
Params totais: 14.714.688
Params treináveis: 2.359.808
Params não treináveis: 12.354.880

Eu estava usando o Keras ImageDataGenerator novamente para carregar os dados, aumentando (3x) e redimensionando-os. Como recomendado , a descida de gradiente estocástica é usada com uma pequena taxa de aprendizado (10 ^ -4) para adaptar cuidadosamente os pesos. O modelo foi treinado por 100 épocas em lotes de 32 imagens e, novamente, utilizou a entropia cruzada categórica como uma função de perda.

Resultados (54,6% de precisão)

A precisão máxima de validação de 0,64 foi alcançada após 38 épocas já. A precisão do teste acabou sendo de 0,546 , o que é um resultado bastante decepcionante, considerando que até mesmo nosso modelo simples e personalizado da CNN alcançou uma precisão maior. Talvez a complexidade do modelo seja alta demais para a pequena quantidade de dados de treinamento?

Inspecionando o modelo

Para obter melhores informações sobre o desempenho do modelo, inspecionei-o brevemente em relação a vários critérios. Este é um breve resumo das minhas descobertas.

Código

Distribuição de classe

A primeira coisa que eu olhei foi a distribuição de classes. Como os quatro principais assuntos do estudo são representados em nossos dados e o que o modelo prevê?

Aparentemente, o modelo negligencia um pouco a classe dos lingüistas alemães . Essa também é a classe para a qual temos menos dados de treinamento. Provavelmente eu deveria coletar mais.

Exemplos de falsas classificações

Eu queria ter uma idéia do que o modelo faz de errado e o que ele faz certo. Consequentemente, eu dei uma olhada no topo (com relação à confiança) cinco (1) falsos negativos , (2) falsos positivos e (3) verdadeiros positivos .

Aqui está um trecho para class econ :

A fileira de cima mostra exemplos de economistas, que o modelo não reconheceu como tal. A fileira central mostra exemplos do que o modelo "pensa" como os economistas se parecem, mas que na verdade são estudantes / pesquisadores com um curso diferente.

Finalmente, a linha inferior mostra exemplos de boas correspondências, ou seja, pessoas para quem o modelo tinha uma confiança muito alta para sua classe real.

Novamente, se você estiver em uma dessas fotos e quiser ser removido, entre em contato comigo.

Matriz de confusão

Para ver em qual profissão o modelo está inseguro, calculei a matriz de confusão.

 array ([[12.76595745, 5.95744681, 0., 6.38297872], 
 [3.40425532, 12.76595745, 3.82978723, 8.08510638], 
 [3,82978723, 5,53191489, 8,5106383, 3,40425532], 
 [5.95744681, 5.10638298, 1.27659574, 13.19148936]]) 

Lenda:

  • 0 = cs, 1 = econ, 2 = alemão, 3 = mecânico
  • Cores mais brilhantes ~ maior valor

O que podemos ler na matriz de confusão é que, por exemplo, o modelo tende a classificar os economistas como engenheiros mecânicos com bastante frequência.

Conclusão

Primeiro de tudo, este não é um estudo científico, mas um pequeno projeto de hobby meu. Além disso, não tem muita importância no mundo real, uma vez que raramente se pode classificar os alunos em quatro categorias.

Embora os resultados não sejam espetaculares, ainda estou muito feliz com eles e, pelo menos, meu modelo foi capaz de fazer muito melhor do que adivinhar aleatoriamente. Dada uma precisão de 57% com quatro classes, você poderia definitivamente dizer que é, até certo ponto, possível aprender o estudo de uma pessoa de aparência estereotipada a partir de apenas uma imagem de seu rosto. Naturalmente, isso só é válido dentro de um contexto delimitado e sob um conjunto de restrições, mas ainda é um insight interessante para mim.

Além disso, tenho certeza de que ainda há muito espaço para melhorias no modelo, o que poderia resultar em um melhor desempenho. Esses podem incluir:

  • Mais dados de treinamento de uma ampla gama de fontes
  • Pré-processamento mais completo (por exemplo, filtrar imagens de secretárias)
  • Arquitetura de modelo diferente
  • Ajuste de hiper-parâmetros
  • Engenharia de recursos manual

Por favor, deixe-me saber o que você acha deste projeto. Eu adoraria receber algum feedback!