<jupyter_start><jupyter_text>Regressor de Posições da Face usando TensorflowNeste notebook iremos implementadar um modelo para regressão das posições da face, comumente conhecidas como __roll, pitch e yaw__. Regressão é uma das "tarefas" em que podemos utilizar Machine Learning. Nesta tarefa o ensino é **supervisionado**. Em outras palavras, nós vamos ensinar ao modelo através de exemplos com gabarito. A diferença em relação a classificação dá-se pelo fato que, na regressão, a saída é um valor contínuo (valor real), enquanto na classificação a saída é um valor discreto/categórico (por exemplo, cachorro/gato).![](images/face_poses.png)Nosso modelo deverá receber as distâncias euclideanas par-a-par entre os 68 pontos da face detectados pela biblioteca [Dlib](http://dlib.net) e estimar os valores de roll, pitch e yaw.![](images/dlib_landmarks.png) DadosOs dados foram criados a partir de 6288 imagens de diferentes bancos de face. Para cada face, 68 pontos correspondentes são encontrados e as distâncias euclideanas par-a-par entres eles são calculadas. Portanto, dado 68 pontos $\{p_1, p_2, ..., p_{68}\}$ onde $d(p_i, p_j) = d(p_j, p_i)$ e $d(p_i, p_i) = 0$, cada amostra terá $\frac{68*67}{2} = 2278$ atributos. Além disso, uma vez que vamos utilizar uma rede neural para predizer o roll, pitch e yaw ao mesmo tempo, nossa variável resposta (saída) terá as dimensões 6288x3. ModeloIremos utilizar uma Rede Neural com 4 camadas (entrada, 2 camadas escondidas e a camada de saída). CréditosEssa atividade é melhor descrita [nesse artigo do Medium](https://medium.com/@arnaldog12/face-pose-estimation-with-deep-learning-eebd0e62dbaf).Obrigada a todos os envolvidos!  Dependências<jupyter_code># Compatibilidade entre Python 2 e Python 3
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# TensorFlow
import tensorflow as tf
from tensorflow.contrib.layers import l2_regularizer
tf.logging.set_verbosity(tf.logging.INFO)  # Permitindo visualização de logs

# Bibliotecas auxiliares
import cv2  # maior eficiência ao processar as imagens
import dlib # calcula os 68 pontos da face

import numpy as np  # manipular vetores
import _pickle as pkl # carregar o dump das amostras
import matplotlib.pyplot as plt  # plotar imagens
from glob import glob # carregar arquivos
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split # dividir o banco em treinamento e teste
%matplotlib inline

# IMPORTANTE: essa linha garante que os números gerados aleatoriamente são previsíveis
np.random.seed(0)

print ('Sua versão do TensorFlow:', tf.__version__)
print ('Recomenda-se para esta atividade uma versão >= 1.4.0')<jupyter_output>Sua versão do TensorFlow: 1.4.0
Recomenda-se para esta atividade uma versão >= 1.4.0<jupyter_text>Buscando os Dados Baixa e extraia os dadosPara baixar os dados, crie uma pasta data na pasta em que se encontra este notebook e execute os seguintes comandos dentro da pasta recém-criada.```bashcurl -O https://drive.google.com/file/d/1e4GHFBYgx4wfcrdUjf7O4anhhVgu9Iz6/viewtar xvf samples.rar```Ou você pode baixar os dados manualmente clicando [neste link](https://drive.google.com/file/d/1e4GHFBYgx4wfcrdUjf7O4anhhVgu9Iz6/view) e extraí-los na pasta _"data"_.Após extrair os arquivos você deverá ver as seguintes subpastas:```bashdata/data/samples.pkl```Crie também uma pasta _models_ na mesma pasta desse notebook.  Carregando os dados<jupyter_code>x, y = pkl.load(open('data/samples.pkl', 'rb'))

print(x.shape, y.shape)
roll, pitch, yaw = y[:, 0], y[:, 1], y[:, 2]

print(roll.min(), roll.max(), roll.mean(), roll.std())
print(pitch.min(), pitch.max(), pitch.mean(), pitch.std())
print(yaw.min(), yaw.max(), yaw.mean(), yaw.std())
# Split the dataset into training, validation, and testing
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42)
x_val, x_test, y_val, y_test = train_test_split(x_test, y_test, test_size=0.5, random_state=42)

print(x_train.shape, y_train.shape)
print(x_val.shape, y_val.shape)
print(x_test.shape, y_test.shape)
std = StandardScaler()
std.fit(x_train)
x_train = std.transform(x_train)
x_val = std.transform(x_val)
x_test = std.transform(x_test)<jupyter_output><empty_output><jupyter_text>Implementando o modelo  Hiper-parâmetros<jupyter_code>BATCH_SIZE = 64
TRAIN_EPOCHS = 100<jupyter_output><empty_output><jupyter_text>Modelo<jupyter_code>tf.reset_default_graph()

graph = tf.Graph()
with graph.as_default():
    inputs = tf.placeholder(dtype=tf.float32, shape=[None, 2278], name='inputs')
    outputs = tf.placeholder(dtype=tf.float32, shape=[None, 3], name='outputs')

    dense_1 = tf.layers.dense(inputs=inputs, units=20, activation=tf.nn.relu, kernel_regularizer=l2_regularizer(scale=0.01), name='dense_1')
    dense_2 = tf.layers.dense(inputs=dense_1, units=10, activation=tf.nn.relu, kernel_regularizer=l2_regularizer(scale=0.01), name='dense_2')
    predictions = tf.layers.dense(inputs=dense_2, units=3, activation=None, name='predictions')
    
    loss = tf.reduce_mean(tf.squared_difference(tf.squeeze(outputs), tf.squeeze(predictions)))    
    optimizer = tf.train.AdamOptimizer().minimize(loss)<jupyter_output><empty_output><jupyter_text>Treinando o Modelo<jupyter_code>steps_by_epoch = x_train.shape[0] // BATCH_SIZE
n_steps = TRAIN_EPOCHS * steps_by_epoch

losses = []
with tf.Session(graph=graph) as sess:
    saver = tf.train.Saver()
    sess.run(tf.global_variables_initializer())
    
    for step in range(n_steps+1):
        offset = (step*BATCH_SIZE) % (x_train.shape[0] - BATCH_SIZE)
        
        x_batch = x_train[offset:(offset+BATCH_SIZE)]
        y_batch = y_train[offset:(offset+BATCH_SIZE)]
        
        feed_dict = {inputs:x_batch, outputs:y_batch}        
        _, loss_batch = sess.run([optimizer, loss], feed_dict=feed_dict)
        
        print('\rstep: {:=3}/{:=3} batch_loss: {:.5f}'.format(step%steps_by_epoch, steps_by_epoch, loss_batch), end='')
        if step % steps_by_epoch == 0:
            feed_dict = {inputs:x_train, outputs:y_train}
            [loss_train] = sess.run([loss], feed_dict=feed_dict)
            
            feed_dict = {inputs:x_val, outputs:y_val} 
            [loss_val] = sess.run([loss], feed_dict=feed_dict)
            print('\repoch: {0:=2} train_loss: {1:.5f} val_loss: {2:.5f}'.format(step//steps_by_epoch, loss_train, loss_val))
            losses.append([loss_train, loss_val])
    
    feed_dict = {inputs:x_train, outputs:y_train}
    [loss_train] = sess.run([loss], feed_dict=feed_dict)
    print('Train Loss: {:.8f}'.format(loss_train))
    
    feed_dict = {inputs:x_val, outputs:y_val}
    [loss_val] = sess.run([loss], feed_dict=feed_dict)
    print('  Val Loss: {:.8f}'.format(loss_val))
    
    feed_dict = {inputs:x_test, outputs:y_test}
    [loss_test] = sess.run([loss], feed_dict=feed_dict)
    print(' Test Loss: {:.8f}'.format(loss_test))
    
    saver.save(sess, 'models/model')<jupyter_output>epoch:  0 train_loss: 91.87151 val_loss: 96.89604
epoch:  1 train_loss: 48.02624 val_loss: 51.20241
epoch:  2 train_loss: 40.90429 val_loss: 43.27192
epoch:  3 train_loss: 36.14545 val_loss: 38.38979
epoch:  4 train_loss: 34.37435 val_loss: 36.92950
epoch:  5 train_loss: 33.32413 val_loss: 35.74061
epoch:  6 train_loss: 33.31502 val_loss: 36.05326
epoch:  7 train_loss: 33.00951 val_loss: 35.73961
epoch:  8 train_loss: 31.88610 val_loss: 34.43459
epoch:  9 train_loss: 31.85619 val_loss: 34.51724
epoch: 10 train_loss: 32.31748 val_loss: 35.42282
epoch: 11 train_loss: 32.07675 val_loss: 35.16140
epoch: 12 train_loss: 31.61383 val_loss: 34.64320
epoch: 13 train_loss: 31.63025 val_loss: 34.68211
epoch: 14 train_loss: 31.72682 val_loss: 34.88272
epoch: 15 train_loss: 31.50734 val_loss: 34.65313
epoch: 16 train_loss: 31.12200 val_loss: 34.04063
epoch: 17 train_loss: 31.21830 val_loss: 34.27548
epoch: 18 train_loss: 31.56988 val_loss: 34.71052
epoch: 19 train_loss: 31.50000 val_loss: 34.65643
[...]<jupyter_text>Como a gente pode ver, nós atingimos um bom resultado mesmo no banco de teste. Além disso, uma vez que a gente está utilizando MSE como função de custo, a gente pode estimar que nosso modelo tem um erro de $\pm$6°. Você pode tentar melhorar o modelo antes de continuar, se você quiser.Vamos visualizar o nosso gráfico de treinamento:<jupyter_code>losses = np.array(losses)
loss_train = losses[:, 0]
loss_val = losses[:, 1]

plt.figure()
plt.plot(loss_train, label='train')
plt.plot(loss_val, label='val', color='red')
plt.legend()<jupyter_output><empty_output><jupyter_text>Bom trabalho. Nosso gráfico segue o padrão que se espera ao treinar modelos de deep learning!Para finalizar a análise do nosso modelo, vamos plotar a diferença entre os valores esperados (saída real) e os valores previstos:<jupyter_code>with tf.Session() as sess:
    new_saver = tf.train.import_meta_graph('models/model.meta')
    new_saver.restore(sess, tf.train.latest_checkpoint('models/'))
    
    graph = tf.get_default_graph()
    inputs = graph.get_tensor_by_name("inputs:0")
    predictions = graph.get_tensor_by_name("predictions/BiasAdd:0")
    
    feed_dict = {inputs:x_test}
    [y_pred] = sess.run([predictions], feed_dict=feed_dict)
    
    diff = y_test - y_pred
    diff_roll = diff[:, 0]
    diff_pitch = diff[:, 1]
    diff_yaw = diff[:, 2]

    plt.figure(figsize=(16, 10))

    plt.subplot(3, 1, 1)
    plt.plot(diff_roll, color='red')
    plt.title('roll')

    plt.subplot(3, 1, 2)
    plt.plot(diff_pitch, color='red')
    plt.title('pitch')

    plt.subplot(3, 1, 3)
    plt.plot(diff_yaw, color='red')
    plt.title('yaw')

    plt.tight_layout()<jupyter_output>INFO:tensorflow:Restoring parameters from models/model<jupyter_text>Como é possível observar, o __yaw__ foi a pose mais fácil de ser estimada pelo nosso modelo, seguida pelo roll e pitch, respectivamente. Entretanto, é fácil ver que há outliers em todos os gráficos. Nós vamos deixar para investigá-los como trabalho futuro.  Testando o modelo  Agora, é hora de testar o modelo que nós criamos. Porém, primeiramente você deve baixar o [shape_model](http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2) (se você ainda não baixou) para poder detectar os landmarks da face. Baixe e extrai o arquivo na pasta _models_.<jupyter_code>def detect_face_points(image):
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor("models/shape_predictor_68_face_landmarks.dat")
    face_rect = detector(image, 1)
    if len(face_rect) != 1: return []
    
    dlib_points = predictor(image, face_rect[0])
    face_points = []
    for i in range(68):
        x, y = dlib_points.part(i).x, dlib_points.part(i).y
        face_points.append(np.array([x, y]))
    return face_points
        
def compute_features(face_points):
    assert (len(face_points) == 68), "len(face_points) must be 68"
    
    face_points = np.array(face_points)
    features = []
    for i in range(68):
        for j in range(i+1, 68):
            features.append(np.linalg.norm(face_points[i]-face_points[j]))
            
    return np.array(features).reshape(1, -1)<jupyter_output><empty_output><jupyter_text>Se você quiser testar na sua própria imagem, cole-a na pasta _data_ e mude o nome na primeira linha abaixo:<jupyter_code>im = cv2.imread('data/lena.png', cv2.IMREAD_COLOR)
im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
face_points = detect_face_points(im)

for x, y in face_points:
    cv2.circle(im, (x, y), 1, (0, 255, 0), -1)
    
features = compute_features(face_points)
features = std.transform(features)

with tf.Session() as sess:
    new_saver = tf.train.import_meta_graph('models/model.meta')
    new_saver.restore(sess, tf.train.latest_checkpoint('models/'))
    
    graph = tf.get_default_graph()
    inputs = graph.get_tensor_by_name("inputs:0")
    predictions = graph.get_tensor_by_name("predictions/BiasAdd:0")
    
    feed_dict = {inputs:features}
    [y_pred] = sess.run([predictions], feed_dict=feed_dict)

roll_pred, pitch_pred, yaw_pred = y_pred[0]
print(' Roll: {:.2f}°'.format(roll_pred))
print('Pitch: {:.2f}°'.format(pitch_pred))
print('  Yaw: {:.2f}°'.format(yaw_pred))
    
plt.figure(figsize=(10, 10))
plt.imshow(im)<jupyter_output>INFO:tensorflow:Restoring parameters from models/model
 Roll: 3.53°
Pitch: -2.07°
  Yaw: 22.33°
{'fraction_non_alphanumeric': 0.1157085020242915, 'fraction_numerical': 0.04137651821862348, 'mean_word_length': 4.681232750689972, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 35, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '58651', 'license': 'Apache-2.0', 'n_tokens_mistral': 4949, 'n_tokens_neox': 4562, 'n_words': 1133, 'path': 'regressor-face-pose/estimators.ipynb', 'repo_name': 'limasigor2/tensorflow-brasil', 'size': '12350'}