Deep Learning com Tensorflow - Aula 2
Classificando Dígitos da base de dados MNIST com Redes Neurais Convolucionais
- Preprocessamento dos dados
- Formatação dos dados
- Definição dos modelos
- Treinamento do modelo não-regularizado
- Treinamento do modelo regularizado
- Comparação entre os dois modelos
- Visualização das representações
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from skimage.transform import rotate
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import OneHotEncoder
# Estética dos plots
plt.rcParams['mathtext.fontset'] = 'custom'
plt.rcParams['mathtext.rm'] = 'Bitstream Vera Sans'
plt.rcParams['mathtext.it'] = 'Bitstream Vera Sans:italic'
plt.rcParams['mathtext.bf'] = 'Bitstream Vera Sans:bold'
plt.rcParams['font.size'] = 16
plt.rcParams['mathtext.fontset'] = 'stix'
plt.rcParams['font.family'] = 'STIXGeneral'
Preprocessamento dos dados
Começamos por pre-processar os dados. Para tanto, utilisaremos o próprio Tensorflow para fazer o carregamento dos dados. À partir da biblioteca Keras, carregamos os dados de treino e de teste usando a chamada
tf.keras.datasets.mnist.load_data()
Na verdade, o Tensorflow possui vários datasets comuns em machine learning. Uma lista completa pode ser encontrada no seguinte link
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
Note que dividimos os dados em 4 arrays. Estes arrays correspondem aos dados de treino e teste. Os dados de treino correspondem àqueles que serão utilizados durante a otimização do modelo, e o de teste será usado para avaliar o modelo. Fazemos essa divisão por dois motivos:
- Queremos simular a situação em que o nosso modelo é treinado num conjunto de dados fixo, e depois é utilisado na prática com dados novos, os quais o modelo não viu durante a fase de treino. Para o caso do MNIST, imagine que treinamos a rede numa base de dados local, e utilisamos o modelo para a predição em tempo real de dígitos numa aplicação remota. Os dados obtidos em tempo real não foram vistos pela rede neural durante treinamento.
- As estatísticas obtidas com os dados de treinamento são geralmente mais otimistas do que em dados não vistos. Imagine o caso em que uma pessoa estuda para uma prova à partir de uma lista de exercícios. Quem você acha que teria o melhor desempenho? (1) um aluno que faz uma prova com as questões retiradas da lista, ou (2) um aluno que faz uma prova com questões inteiramente novas?
Além de dividir os dados em treino/teste, iremos também dividí-los entre características (array X) e rótulos (array y).
Formatação dos dados
Iremos começar analizando os dados como vieram no dataset da biblioteca tensorflow. Dado que as aplicações são, via de regra, para redes neurais convolucionais, os dados vem como matrizes.
Visualização imagens como matrizes
fig, ax = plt.subplots()
ax.imshow(x_train[0], cmap='gray')
_ = ax.set_xticks([])
_ = ax.set_yticks([])
print("Formato da matriz de dados: {}".format(x_train.shape))
note que os dados estão salvos como imagens. Portanto, a faixa de valores para seus pixels está entre 0 e 255. Além disso, os rótulos estão salvos em formato categórico, ou seja, $y_{i} \in \{1, \cdots, K\}$, onde $K$ é o número de classes. Particularmente, $K = 10$ para o dataset MNIST.
Para converter a matriz de caracteríticas, tomaremos 2 passos:
- converter de int para float,
- converter da faixa [0, 255] para [0, 1]
Note que podemos aplicar a seguinte transformação,
$$ x \leftarrow \dfrac{x - x_{min}}{x_{max}-x_{min}}, $$Como discutido anteriormente, $x_{min} = 0$ e $x_{max} = 255$, portanto,
$$ x \leftarrow \dfrac{x}{255} $$Como nesse tutorial usaremos redes neurais convolucionais, as amostras permanecerão em formato de imagem. Dessa forma, temos tensores de treino/teste com formato $(N, H, W)$, onde $N_{tr} = 60000$, $H=28$ e $W=28$
Exercício 1: Se um float ocupa 32 bits em memória, qual o espaço ocupado pelo tensor de treino?
Xtr = x_train.astype(float)[..., np.newaxis] / 255.0
Xts = x_test.astype(float)[..., np.newaxis] / 255.0
print("Formato da matriz de dados: {}".format(Xtr.shape))
print("Nova faixa de valores de X: [{}, {}]".format(Xtr.min(), Xtr.max()))
Iremos também transformar a notação categórica dos rótulos na notação One Hot. Isso é simples utilizando a biblioteca scikit-learn do Python, através da classe OneHotEncoder. O exemplo abaixo fornece uma ilustração para 3 classes e 3 amostras:
$$ y^{cat} = [1, 2, 3] \iff y^{OneHot} = \begin{bmatrix} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 0 & 1 \end{bmatrix} $$# OBS1: o objeto OneHotEncoder espera um array de 2 dimensões.
# Porém y_train só possui 1 dimensão (observe os prints
# abaixo). Para convertê-lo num array 2D, utilisaremos a
# função reshape, que muda o formato do array.
# OBS2: .reshape(-1, ...) faz com que a biblioteca numpy faça
# uma inferência do valor adequado para a dimensão especificada
# como -1. No caso, como utilisamos .reshape(-1, 1), teremos uma
# transformação de formatação (N, ) -> (N, 1)
print("Formato de y_train antes de usar .reshape: {}".format(y_train.shape))
print("Formato de y_train após usar .reshape: {}".format(y_train.reshape(-1, 1).shape))
enc = OneHotEncoder(sparse=False)
ytr = enc.fit_transform(y_train.reshape(-1, 1))
yts = enc.fit_transform(y_test.reshape(-1, 1))
print("Formato da matriz de rótulos após a aplicação da nova codificação: {}".format(ytr.shape))
def network1(input_shape=(28, 28, 1), n_classes=10):
x = tf.keras.layers.Input(shape=input_shape)
# Convolutional block: Convolution -> Activation -> Pooling
y = tf.keras.layers.Conv2D(filters=36, kernel_size=(3, 3), padding='same', activation='relu')(x)
y = tf.keras.layers.MaxPool2D(pool_size=(2, 2), padding='same')(y)
y = tf.keras.layers.Flatten()(y)
y = tf.keras.layers.Dense(units=100, activation='relu')(y)
y = tf.keras.layers.Dense(units=n_classes, activation='softmax')(y)
return tf.keras.models.Model(x, y)
def network2(input_shape=(28, 28, 1), n_classes=10):
x = tf.keras.layers.Input(shape=input_shape)
# Convolutional block: Convolution -> Activation -> Pooling
y = tf.keras.layers.Conv2D(filters=36,
kernel_size=(7, 7),
padding='same',
kernel_regularizer=tf.keras.regularizers.l2(1e-3),
activation='relu')(x)
y = tf.keras.layers.MaxPool2D(pool_size=(2, 2), padding='same')(y)
y = tf.keras.layers.Flatten()(y)
y = tf.keras.layers.Dense(units=100, activation='relu',
kernel_regularizer=tf.keras.regularizers.l2(1e-3))(y)
y = tf.keras.layers.Dense(units=n_classes, activation='softmax')(y)
return tf.keras.models.Model(x, y)
model1 = network1()
model1.summary()
loss_obj = tf.keras.losses.CategoricalCrossentropy()
optimizer_obj = tf.keras.optimizers.Adam(lr=0.01)
model1.compile(loss=loss_obj, optimizer=optimizer_obj, metrics=['accuracy'])
hist1 = model1.fit(x=Xtr, y=ytr, batch_size=1024, epochs=30, validation_data=(Xts, yts), validation_batch_size=128)
fig, axes = plt.subplots(1, 2, figsize=(15, 5))
axes[0].plot(100 * np.array(hist1.history['accuracy']), label='Treino')
axes[0].plot(100 * np.array(hist1.history['val_accuracy']), label='Teste')
axes[0].set_ylabel('Percentual de Acerto')
axes[0].set_xlabel('Época')
axes[0].legend()
axes[1].plot(100 * np.array(hist1.history['loss']), label='Treino')
axes[1].plot(100 * np.array(hist1.history['val_loss']), label='Teste')
axes[1].set_ylabel('Função de Erro')
axes[1].set_xlabel('Época')
axes[1].legend()
filters = model1.weights[0].numpy()
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(filters[:, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
inp = model1.layers[0].input
outs = [layer.output for layer in model1.layers]
layerized_model1 = tf.keras.models.Model(inp, outs)
Omat = layerized_model1.predict(Xts[0, ...].reshape(1, 28, 28, 1))
plt.imshow(Omat[0][0, :, :, 0], cmap='gray')
plt.yticks([])
plt.xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(Omat[1][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(Omat[2][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(x, cmap='gray')
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[1].bar(np.arange(10), Omat[-1][0, :])
_ = axes[1].set_xticks([i for i in range(10)])
x = Omat[0][0, :, :, 0]
xrot = rotate(x, angle=15)
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(x, cmap='gray')
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[1].imshow(xrot, cmap='gray')
axes[1].set_xticks([])
axes[1].set_yticks([])
tmp = layerized_model.predict(xrot.reshape(1, 28, 28, 1))
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[1][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[2][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(2, 2, figsize=(10, 5))
axes[0, 0].imshow(x, cmap='gray')
axes[0, 0].set_xticks([])
axes[0, 0].set_yticks([])
axes[0, 1].imshow(xrot, cmap='gray')
axes[0, 1].set_xticks([])
axes[0, 1].set_yticks([])
axes[1, 0].bar(np.arange(10), Omat[-1][0, :])
axes[1, 1].bar(np.arange(10), tmp[-1][0, :])
x = Omat[0][0, :, :, 0]
xrot = rotate(x, angle=45)
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(x, cmap='gray')
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[1].imshow(xrot, cmap='gray')
axes[1].set_xticks([])
axes[1].set_yticks([])
tmp = layerized_model1.predict(xrot.reshape(1, 28, 28, 1))
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[1][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[2][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(2, 2, figsize=(10, 5))
axes[0, 0].imshow(x, cmap='gray')
axes[0, 0].set_xticks([])
axes[0, 0].set_yticks([])
axes[0, 1].imshow(xrot, cmap='gray')
axes[0, 1].set_xticks([])
axes[0, 1].set_yticks([])
axes[1, 0].bar(np.arange(10), Omat[-1][0, :])
axes[1, 0].set_ylim([0, 1.1])
_ = axes[1, 0].set_xticks([i for i in range(0, 10)])
axes[1, 1].bar(np.arange(10), tmp[-1][0, :])
axes[1, 1].set_ylim([0, 1.1])
_ = axes[1, 1].set_xticks([i for i in range(0, 10)])
x = Omat[0][0, :, :, 0]
noise = 0.5 * np.random.randn(*x.shape)
xnoise = np.clip(x + noise, 0.0, 1.0)
fig, axes = plt.subplots(1, 3, figsize=(10, 5))
axes[0].imshow(x, cmap='gray')
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[1].imshow(xnoise, cmap='gray')
axes[1].set_xticks([])
axes[1].set_yticks([])
axes[2].imshow(noise, cmap='gray')
axes[2].set_xticks([])
axes[2].set_yticks([])
tmp = layerized_model1.predict(xnoise.reshape(1, 28, 28, 1))
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[1][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[2][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(2, 2, figsize=(10, 5))
axes[0, 0].imshow(x, cmap='gray')
axes[0, 0].set_xticks([])
axes[0, 0].set_yticks([])
axes[0, 1].imshow(xnoise, cmap='gray')
axes[0, 1].set_xticks([])
axes[0, 1].set_yticks([])
axes[1, 0].bar(np.arange(10), Omat[-1][0, :])
axes[1, 0].set_ylim([0, 1.1])
_ = axes[1, 0].set_xticks([i for i in range(0, 10)])
axes[1, 1].bar(np.arange(10), tmp[-1][0, :])
axes[1, 1].set_ylim([0, 1.1])
_ = axes[1, 1].set_xticks([i for i in range(0, 10)])
y = Xts[1, ...].reshape(28, 28)
alpha = 0.25
interpol_xy = np.clip((1 - alpha) * x + alpha * y, 0.0, 1.0)
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(x, cmap='gray')
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[1].imshow(interpol_xy, cmap='gray')
axes[1].set_xticks([])
axes[1].set_yticks([])
tmp = layerized_model1.predict(interpol_xy.reshape(1, 28, 28, 1))
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[1][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(tmp[2][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(2, 2, figsize=(10, 5))
axes[0, 0].imshow(x, cmap='gray')
axes[0, 0].set_xticks([])
axes[0, 0].set_yticks([])
axes[0, 1].imshow(interpol_xy, cmap='gray')
axes[0, 1].set_xticks([])
axes[0, 1].set_yticks([])
axes[1, 0].bar(np.arange(10), Omat[-1][0, :])
axes[1, 0].set_ylim([0, 1.1])
_ = axes[1, 0].set_xticks([i for i in range(0, 10)])
axes[1, 1].bar(np.arange(10), tmp[-1][0, :])
axes[1, 1].set_ylim([0, 1.1])
_ = axes[1, 1].set_xticks([i for i in range(0, 10)])
model2 = network2()
model2.summary()
loss_obj = tf.keras.losses.CategoricalCrossentropy()
optimizer_obj = tf.keras.optimizers.Adam(lr=0.01)
model2.compile(loss=loss_obj, optimizer=optimizer_obj, metrics=['accuracy'])
hist2 = model2.fit(x=Xtr, y=ytr, batch_size=1024, epochs=30, validation_data=(Xts, yts), validation_batch_size=128)
fig, axes = plt.subplots(1, 2, figsize=(15, 5))
axes[0].plot(100 * np.array(hist2.history['accuracy']), label='Treino')
axes[0].plot(100 * np.array(hist2.history['val_accuracy']), label='Teste')
axes[0].set_ylabel('Percentual de Acerto')
axes[0].set_xlabel('Época')
axes[0].legend()
axes[1].plot(100 * np.array(hist2.history['loss']), label='Treino')
axes[1].plot(100 * np.array(hist2.history['val_loss']), label='Teste')
axes[1].set_ylabel('Função de Erro')
axes[1].set_xlabel('Época')
axes[1].legend()
filters = model2.weights[0].numpy()
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(filters[:, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
inp = model2.layers[0].input
outs = [layer.output for layer in model2.layers]
layerized_model2 = tf.keras.models.Model(inp, outs)
Omat = layerized_model2.predict(Xts[0, ...].reshape(1, 28, 28, 1))
plt.imshow(Omat[0][0, :, :, 0], cmap='gray')
plt.yticks([])
plt.xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(Omat[1][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(6, 6, figsize=(8, 8))
for i, ax in enumerate(axes.flatten()):
ax.imshow(np.squeeze(Omat[2][0, :, :, i]), cmap='gray')
ax.set_yticks([])
ax.set_xticks([])
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(x, cmap='gray')
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[1].bar(np.arange(10), Omat[-1][0, :])
_ = axes[1].set_xticks([i for i in range(10)])
fig, axes = plt.subplots(2, 1, figsize=(15, 5))
axes[0].plot(hist1.history['accuracy'], label='Modelo 1')
axes[0].plot(hist2.history['accuracy'], label='Modelo 2')
axes[0].legend()
axes[1].plot(hist1.history['val_accuracy'], label='Modelo 1')
axes[1].plot(hist2.history['val_accuracy'], label='Modelo 2')
axes[1].legend()
fig, axes = plt.subplots(2, 1, figsize=(15, 5))
axes[0].plot(hist1.history['loss'], label='Modelo 1')
axes[0].plot(hist2.history['loss'], label='Modelo 2')
axes[0].legend()
axes[1].plot(hist1.history['val_loss'], label='Modelo 1')
axes[1].plot(hist2.history['val_loss'], label='Modelo 2')
axes[1].legend()
yp1 = model1.predict(Xts).argmax(axis=1)
yp2 = model2.predict(Xts).argmax(axis=1)
print("Taxa de precisão: {}".format(100 * accuracy_score(y_test, yp1)))
print("Taxa de precisão (Regularizado): {}".format(100 * accuracy_score(y_test, yp2)))
Omat = layerized_model2.predict(Xts)
sample_inds = []
for i in np.unique(y_test):
sample_inds.append(np.where(y_test == i)[0][:100])
sample_inds = np.concatenate(sample_inds, axis=0)
raw_rep = Omat[0][sample_inds, :]
print(raw_rep.shape)
tsne = TSNE(n_components=2, init='pca', verbose=True)
tsne.fit(raw_rep.reshape(-1, 784))
embedding = tsne.embedding_
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
for i in np.unique(y_test):
ax.scatter(embedding[100 * i: 100 * (i + 1), 0],
embedding[100 * i: 100 * (i + 1), 1],
label='Digit {}'.format(i))
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
conv_rep = Omat[3][sample_inds, :]
print(conv_rep.shape)
tsne = TSNE(n_components=2, init='pca', verbose=True)
tsne.fit(conv_rep)
embedding = tsne.embedding_
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
for i in np.unique(y_test):
ax.scatter(embedding[100 * i: 100 * (i + 1), 0],
embedding[100 * i: 100 * (i + 1), 1],
label='Digit {}'.format(i))
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
dense_rep = Omat[-2][sample_inds, :]
tsne = TSNE(n_components=2, init='pca', verbose=True)
tsne.fit(dense_rep)
embedding = tsne.embedding_
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
for i in np.unique(y_test):
ax.scatter(embedding[100 * i: 100 * (i + 1), 0],
embedding[100 * i: 100 * (i + 1), 1],
label='Digit {}'.format(i))
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))