O tópico destas aulas é a construção, treino e validação de redes neuronais, usando o TensorFlow
.
Para tal iremos usar o Colab da Google, que nos permite escrever e correr código Python diretamente no browser.
Comece por abrir um novo notebook Colab.
Para isso, basta fazer login na sua conta Google e depois aceder a https://colab.research.google.com
Deve criar um novo notebook.
Depois, a primeira coisa a fazer é permitir o acesso ao Google Drive, de modo a que possa facilmente ter acesso a ficheiros que aí se encontrem.
Para tal, coloque o seguinte código:
----
from google.colab import drive
drive.mount('/content/drive')
----
Quando esta célula é executada (usando o botão no canto superior esquerdo da célula ou usando Ctrl+Enter), abre um dialogo que deve confirmar. Isto monta a Google Drive em /content/drive/My Drive
Em todos os passos seguintes, deve usar uma nova célula executável que pode criar usando o botão (+ code).
Sempre que quiser adicionar texto com comentários ao código que vai escrevendo, pode criar células de texto usando o botão (+ text).
------
%cd "drive/My Drive/caminho-para-a-pasta"
%pwd
-----
Finalmente, irá importar as bibliotecas de Python necessárias para esta aula.
----
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.utils import to_categorical
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import pandas as pd
import random
----
Considere o seguinte ficheiro de dados (curva.txt), que em cada linha tem um par de valores.
Cada par (x,y) indica que ao valor de entrada x corresponde o valor de saída y.
Queremos criar e treinar um modelo que se ajuste a estes dados.
Deve colocar este ficheiro na pasta Drive que criou no início (no resto da ficha, deve fazer isto para todos os ficheiros que precisar aceder).
Depois disso, podemos então começar por ler e preparar os dados do ficheiro.
----
mat = np.loadtxt('curva.txt')
xs = mat[:,:1]
ys = mat[:,1:]
----
Repare que tanto x como y são transformados em colunas.----
keras.backend.clear_session()
model = keras.Sequential()
model.add(layers.Dense(1, activation='sigmoid'))
opt = keras.optimizers.SGD(momentum = 0.9,learning_rate = 0.05)
model.compile(optimizer=opt,loss='mse')
model.fit(xs,ys,epochs=200, batch_size=16)
----
Na primeira linha apagamos possíveis modelos anteriores existentes.Depois de o modelo estar treinado, pode visualizar o ajuste do modelo aos dados de treino.
----
x = np.linspace(0,1,200).reshape((-1,1))
y = model.predict(x)
plt.plot(x,y)
plt.plot(xs,ys,'.')
----
Num modelo sequencial, a última camada é designada de camada de saída e as anteriores são designadas camadas escondidas.
A camada de entrada está implícita, e é construída internamente na primeira vez que treinamos o modelo, com base na forma do conjunto de treino dado.Experimente construir e treinar um modelo semelhante, mas com ativação linear, permitindo assim que o output não esteja limitado a valores entre 0 e 1.
Para isso deve usar (activation='linear'
) em vez de (activation='sigmoid'
).
Compile, treine e visualize o resultado. O que aconteceu?
Adicione ao modelo anterior uma camada escondida com activação linear (activation='linear'
) e com um neurónio.
Compile, treine e visualize o resultado. Houve melhorias?
Altere agora nesta camada escondida o neurónio para activação sigmóide. Compile, treine, visualize o resultado, e compare com os resultados anteriores.
Adicione mais um neurónio na camada com activação sigmóide já existente. Compile, treine, visualize o resultado, e compare com os resultados anteriores.
Finalmente, adicione mais dois neurónios na camada com activação sigmóide. Compare o tempo de treino com os exemplos anteriores.
Recorde que o treino de uma rede neuronal é um processo estocástico, e que, por isso, cada vez que treina uma rede, os pesos da rede resultante são diferentes.----
model.save('nome_ficheiro.keras')
----
Este código guardará o modelo "model" no ficheiro ----
new_model = keras.models.load_model('nome_ficheiro.keras')
----
e o modelo que estava guardado no ficheiro ficará na variável "new_model".
Neste exemplo iremos considerar uma possível curva do número de casos diários confirmados de COVID-19 em Portugal.
Para tal vamos usar o seguinte ficheiro de dados (confirmados.txt).
Cada exemplo deste conjunto de dados é constituído pelo número do dia (a contar do primeiro dia em que houve casos confirmados) e o número de casos nesse dia.
Note que redimensionar os dados de entrada, de modo a os tornar pequenos, é uma importante técnica na área das redes neuronais.
Isto evita processos de treino lentos e instáveis, que poderiam ocorrer quando os valores de entrada são elevados.
Tendo isso em conta, vamos mudar a escala dos dados de entrada para o intervalo [0,1].
---- mat = np.loadtxt('confirmados.txt') scale = np.max(mat,axis=0) mat = mat / scale xs = mat[:,:1] ys = mat[:,1:] ----
Para redes mais profundas, devemos usar Rectified Linear Unit (ReLU) como função de ativação das camadas escondidas.
Para isso usamos (activation='relu'
) em cada uma das camadas escondidas.
Construa um modelo com várias camadas com activação ReLU e no fim uma camada linear.
Experimente diferentes arquiteturas da rede e compare os resultados obtidos.
Pode visualizar o ajustamento do modelo aos dados usando código semelhante ao apresentado antes, mas tendo em conta a escala original dos dados:
---- x = np.linspace(0,mat[-1,0],200).reshape((-1,1)) y = model.predict(x) plt.plot(x*scale[0],y*scale[1]) plt.plot(xs*scale[0],ys*scale[1],'.') ----
Vamos agora explorar um exemplo simples de classificação. Ao contrário dos problemas de regressão, como era o caso nos dois exemplos anteriores, em que se tentava prever uma quantidade,
nos problemas de classificação o objectivo é prever uma classe.
O conjunto de dados utilizado neste exemplo é chamado de Fisher's Iris dataset, em memória do biólogo inglês Ronald Fisher,
que em 1936 classificou três diferentes tipos de lírios (género Iris): Setosa, Versicolor e Virginica.
Este conjunto de dados consiste em 50 amostras de cada uma das três espécies, em que,
para cada amostra, quatro características foram medidas: largura e comprimento das pétalas e das sépalas.
O ficheiro de dados pode ser encontrado aqui (iris.txt).
Cada exemplo deste conjunto de dados é composto por cinco elementos: os valores dos quatro atributos e a classe a que pertence (0-Setosa, 1-Versicolor, 2-Virginica).
Queremos treinar um modelo de modo a que seja possível, dado a valor dos quatro atributos de um lírio, prever a que classe este pertence.
Começamos por carregar os dados de treino. Como estes estão ordenados por classes, é conveniente mudar, de forma aleatória, a sua ordem.
Para além disso, para garantir que todas as características são consideradas de igual modo, é usual normalizar o conjunto de entrada, subtraindo todos os valores pela média e dividindo pelo desvio padrão.
Nos problemas de classificação, uma passo importante a fazer é recodificar as classes.
Para isso, como neste caso temos três classes, consideramos três neurónios, e usamos uma técnica usualmente denominada one-hot encoding.
Neste exemplo, a classe 0 é codificada no vector (1,0,0)
, a classe 1 é codificada no vector (0,1,0)
e a classe 2 é codificada no vector (0,0,1)
.
O Keras permite fazer isso de uma forma simples com a função to_categorical
.
O seguinte código permite fazer tudo isto.
----
mat = np.loadtxt('iris.txt')
np.random.shuffle(mat)
x = mat[:,:-1]
x = (x-np.mean(x,axis=0))/np.std(x,axis=0)
y_orig = mat[:,-1]
y = to_categorical(y_orig)
----
Ao contrário dos problemas de regressão anteriores, nos quais usámos MSE como função de erro,
no caso de problemas de classificação com múltiplas classes, iremos usar Categorical Cross Entropy. loss='categorical_crossentropy'
.metrics=['accuracy']
.----
model.compile(optimizer=opt,loss='categorical_crossentropy',metrics=['accuracy'])
----
Comece por construir um modelo com apenas uma camada com três neurónios e activação softmax.
Treine durante 400 épocas e com 16 como tamanho do batch.----
hist = model.fit(x,y, validation_split=0.2, epochs=400, batch_size=16)
plt.figure()
plt.plot(hist.history['loss'], label='train_loss')
plt.plot(hist.history['val_loss'], label='val_loss')
plt.plot(hist.history['accuracy'], label='train_acc')
plt.plot(hist.history['val_accuracy'], label='val_acc')
plt.title('Training Loss and Accuracy')
plt.xlabel('Epoch #')
plt.ylabel('Loss/Accuracy')
plt.margins(x=0)
plt.margins(y=0)
plt.legend()
plt.show()
----
Ao usar validation_split=0.2
, estamos a indicar que dois décimos dos dados de treino vão ser usados como dados de validação.validation_split
, devemos usar a opção validation_data=(x_v,y_v)
.Experimente treinar os modelos anteriores com conjunto de validação, e visualize a evolução do erro e da exatidão.
----
def _preprocess_images(images):
# Scale the raw pixel intensities to the range [-1, 1]
images = images / 127.5
images = images - 1.0
images.astype(np.float32)
return images
# Start by loading the training dataset
# The dataset is made of pictures and their labels (EmptyTrain, HalfFullTrain, FullTrain, OverloadedTrain)
training_data_labels_frame = pd.read_csv('training_trains_labels.csv')
data_labels = training_data_labels_frame.columns.values[1:]
training_data_labels = training_data_labels_frame[data_labels].to_numpy()
original_training_data_images = np.load('training_images.npz')['images']
training_data_images = _preprocess_images(original_training_data_images)
# Load the validation dataset
validation_data_labels_frame = pd.read_csv('validation_trains_labels.csv')
validation_data_labels = validation_data_labels_frame[data_labels].to_numpy()
validation_data_images = _preprocess_images(np.load('validation_images.npz')['images'])
----
Repare que os valores dos pixeis (entre 0 e 255) são normalizados e transformados em valores no intervalo [-1,1].----
# Lets explore our training dataset
# Print the shape of the training images (#images, height, width, #channels)
# Note: Each channel represents one of the RGB color values
print('Image data shape:', training_data_images.shape)
# Print our labels
print('Data labels:', data_labels)
# Visualize a few images from the training dataset
for i in range(3):
plt.imshow(random.choice(original_training_data_images))
plt.show()
----
Este código permite verificar o formato do conjunto de treino: 1200 exemplos, cada um correspondente à representação de uma imagem: 20(altura) x 115(largura) x 3(canaisRGB). ----
...
model = keras.Sequential()
model.add(layers.Flatten())
...
----
Para problemas mais complexos, como é este o caso, podemos usar o optimizador Adam, que é uma versão melhorada do SGD. Para tal, quando compilar o modelo deve usar:----
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
----
Deve agora encontrar um modelo e definir parâmetros adequados, de modo a garantir o melhor valor possível de exatidão no conjunto de validação.hist=model.fit(...)
, usar o seguinte código:
----
dados_finais_de_treino=[(l,hist.history[l][-1]) for l in hist.history]
print(dados_finais_de_treino)
----
Recorde também que pode ir guardando e reutilizando modelos treinados usando as funções model.save('nome_ficheiro.keras')
, e new_model = keras.models.load_model('nome_ficheiro.keras')
, tal como descrito no início da aula.
----
def _print_predictions_example(model,file_name):
test_data_images = _preprocess_images(np.load(file_name)['images'])
predictions = np.argmax(model.predict(test_data_images), axis=-1)
print(predictions)
----
Aplicando esta função ao conjunto de exemplos no ficheiro 'test_images.npz' que colocou na sua pasta Drive, obtém a uma lista com a previsão que o seu modelo treinado (model) faz da classe a que cada um dos 800 exemplos do conjunto pertence.
Note que usamos a correspondência 'Empty' -> 0, 'HalfFull' -> 1, 'Full' -> 2, 'Overloaded' -> 3.
----
_print_predictions_example(model,'test_images.npz')
----
Para garantir que o resultado da função é mostrado na consola, mesmo quando o número de exemplos é mais elevado, basta que use o seguinte código uma vez.
----
np.set_printoptions(threshold=np.inf)
----
Se quiser visualizar a n-ésima imagem do conjunto de exemplos, juntamente com a previsão que um modelo dado faz sobre a classe a que a imagem pertence, pode usar a seguinte função:
----
def _visualize_predictions_example(model,file_name,n):
test_images=np.load(file_name)['images']
test_data_images = _preprocess_images(np.load(file_name)['images'])
predictions = np.argmax(model.predict(test_data_images), axis=-1)
plt.imshow(test_images[n])
labels=['Empty','HalfFull', 'Full', 'Overloaded']
print('A classe prevista é:',labels[predictions[n]])
----
Depois de descarregar o dataset, é feito a normalização dos dados de input para o intervalo [0,1], dividindo pelo maior valor possível (255).---- mnist = keras.datasets.mnist (x_t,y_t), (x_v,y_v) = mnist.load_data() x_t = x_t/255 x_v = x_v/255 plt.imshow(x_t[0],cmap='gray') plt.figure() plt.imshow(x_t[1],cmap='gray') y_t_cats = to_categorical(y_t) y_v_cats = to_categorical(y_v) ----
Deve agora encontrar um modelo e definir parâmetros adequados, de modo a garantir o melhor valor possível exatidão no conjunto de validação.
Para isso deverá testar vários modelos com arquiteturas diferentes e diferentes parâmetros.