Você está na página 1de 8

Inteligência Artificial

Início
Objectivos
Programa
Bibliografia
Docentes
Avaliação
Horário
Aulas
Trabalhos

Aprendizagem com Redes Neuronais


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.

Configurar Colab e importar bibliotecas necessárias


Comece por abrir um novo notebook Colab.

e depois aceder a https://colab.research.google.com
Para isso, basta fazer login na sua conta Google
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), é
fornecido um link onde há uma chave.

Copie e cole a chave onde é pedida e prima Enter. Isto monta a Google Drive em /content/drive/My Drive
Basta fazer isto uma vez no início da sessão.

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).

O segundo passo consiste em criar uma pasta de trabalho dentro da sua Google Drive onde irá guardar os ficheiros
de dados.
Depois de
a criar, use o seguinte código, que permite mudar para essa pastar para facilitar o acesso aos ficheiros.
------

%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

----
1) Um exemplo simples de ajustamento

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.


Vamos agora criar e treinar um modelo que se ajuste aos dados.


Para isso iremos usar o Keras, que é um API de alto nível do TensorFlow. Este permite criar e treinar modelos de
aprendizagem profunda.
----

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.


Na segunda linha criamos um novo modelo sequencial (sequência de
camadas de neurónios), ao qual, na terceira
linha, acrescentamos uma camada com um neurónio de activação sigmóide.
Criamos depois o optimizador, neste caso Stochastic Gradient Descent (SGD),
e indicamos a taxa/ritmo de
aprendizagem (learning rate) e o momento (momentum).
Compilamos o modelo usando Mean Squared Error (mse)
como função de erro.

Finalmente, treinamos o modelo, dando para isso os valores de entrada, correspondentes valores de saída, número de
épocas e tamanho do batch.

Recorde que o número de épocas indica quantas vezes usamos o conjunto completo de treino, enquanto que o
tamanho do batch é o número de exemplos que vão ser usados em cada atualização dos pesos da rede.
Observe a evolução do treino ao longo das 200 épocas.

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.
Neste exemplo temos apenas uma camada, que é por isso
a camada de saída, e não temos camadas escondidas.
Repare como a ativação sigmóide da camada de saída faz com que o valor de saída do modelo varie apenas entre 0 e
1, não se ajustando por isso bem à curva.

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?
Adicione agora uma camada escondida com um neurónio mas desta vez com 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.
Se tiver um modelo já treinado (com nome "model") que queira guardar, deve usar o código:
----

model.save('nome_ficheiro.h5')

----

Este código guardará o modelo "model" no ficheiro nome_ficheiro.h5 na sua pasta drive.
Para voltar a usar o modelo
deve fazer:
----

new_model = keras.models.load_model('nome_ficheiro.h5')

----

e o modelo que estava guardado no ficheiro ficará na variável "new_model".

2) Casos diários COVID-19


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],'.')

----

3) Classificação de tipos de lírios

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.
Para tal, na compilação do modelo deve usar loss='categorical_crossentropy'.
Para além disso, a última camada deverá ter três neurónios, correspondentes a cada uma das classes.
A função de activação desta última camada deverá ser softmax, que permite que o valor de cada neurónio de saída
possa ser visto como a probabilidade de pertencer a essa classe.
Desse modo, a classe a que cada exemplo pertence será identificada pelo neurónio da camada de saída que tiver
maior activação.
Nos problemas de classificação, para além do erro (loss), podemos considerar a exatidão (accuracy), que é a
proporção de exemplos bem classificados.
Para poder observar a variação da exatidão no conjunto de treino e de validação ao longo do treino, deve, na
compilação do modelo, acrescentar 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.

Depois poderá testar a adição de mais camadas escondidas com activação relu, antes da camada de saída softmax e
comparar com o anterior.

Recorde agora que, para além do conjunto de treino, podemos também considerar um conjunto de validação.
O Keras permite, de uma maneira simples, indicar que parte dos dados de treino serão usados para validação.
Para isso basta que na fase de treino do modelo, se indique que fracção do conjunto de treino será usada como
conjunto de validação.
Para além disso, pode ser útil visualizar o gráfico da evolução do erro e da exatidão, tanto de treino como de
validação, ao longo das várias épocas do treino da rede.
O seguinte fragmento de código mostra como indicar a fracção do conjunto de treino que será usado para validação,
e como gerar o gráfico com a evolução do erro e validação.
----

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.
Se em vez de usar uma parte do conjunto de treino para validação, tivermos um conjunto de dados (x_v,y_v) que
queremos usar para validação,
então, em vez de usar validation_split, devemos usar a opção validation_data=
(x_v,y_v).
Recorde que a visualização conjunta da evolução dos erros e exatidão no conjunto de treino e de validação é muito
importante, pois permite identificar casos de sobreajustamento (overfitting).

Experimente treinar os modelos anteriores com conjunto de validação, e visualize a evolução do erro e da exatidão.

4) Classificação de comboios - o Explainable Abstract Trains Dataset


Neste exercício iremos usar uma versão simplificada do dataset Explainable Abstract Trains Dataset, que contém um
conjunto de imagens de representações de comboios.
Cada imagem tem dimensão 20 (altura) por 115 (largura) pixeis e contém um comboio com três carruagens, cada
uma delas contendo até três figuras geométricas.
As seguintes imagens são exemplos do dataset.

Iremos considerar quatro classes de comboios:


EmptyTrain - todas as carruagens estão vazias;


HalfFullTrain - algumas carruagens vazias, mas
nem todas;
FullTrain - todas as carruagens têm exatamente uma figura;

OverloadedTrain - não há carruagens vazias e há pelo menos


uma carruagem que tem mais do que uma figura.

A representação de cada uma das imagens é uma matriz 20x115 correspondente aos seus pixeis.
Como as imagens
são coloridas, cada pixel é representado pelos valores dos seus três canais RGB, i.e, é um triplo (r,g,b) de valores
entre 0 e 255.
A ideia é que,
dado o conjunto destes 6900=20x115x3 valores associados a uma imagem, i.e., o nível de vermelho
(r), verde (g) e azul (b) de cada pixel da imagem, seja possível inferir a que classe de comboio essa imagem
corresponde.

Deve começar por descarregar e descompactar o seguinte ficheiro trains_dataset_ia.zip, e colocar todos os ficheiros
desta pasta comprimida na sua pasta da Drive.

Depois de os ficheiros estarem na sua pasta, o seguinte código permite ler e preparar o conjunto de treino e
validaçã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].
Depois desta leitura e pre-processamento dos dados, os pares (training_data_images, training_data_labels) e
(validation_data_images, validation_data_labels) contêm o conjunto de treino e validação, respectivamente.
Podemos visualizar um pouco mais de informação sobre o dataset usando o seguinte código:
----

# 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).
Podemos ainda ver quais os labels das classes que vamos usar para classificar as imagens: Empty, HalfFull, Full, e
Overloaded.
Para além disso, podemos visualizar três imagens escolhidas aleatoriamente do conjunto.

Como a representação de cada imagem tem o formato 20x115x3, temos de transformar esta representação numa só
lista.
O Keras permite fazer isso facilmente usando uma camada Flatten no início da rede:
----

...

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.
Para isso deverá testar vários modelos com arquiteturas diferentes e diferentes parâmetros.
Para poder comparar modelos treinados, não se esqueça de ir guardando os valores do erro (loss) final no conjunto
de treino (L), erro final no conjunto de validação (L_V), a exatidão (accuracy) final no conjunto de treino (A), e a
exatidão final no conjunto de validação (A_V).
Pode obter estes valores finais de erro e validação, se depois de treinar um modelo usando 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.h5'), e new_model = keras.models.load_model('nome_ficheiro.h5'), tal como descrito
no início da aula.
Não se deve esquecer de apontar também a arquitetura da rede (número de camadas e neurónios em cada camada).
Finalmente, a seguinte função recebe o nome de um modelo e o nome de um ficheiro contendo um conjunto de
exemplos, e imprime uma lista com a classe prevista pelo modelo para cada uma das imagens do conjunto.
----

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]])

----

5) (Extra) Classificação de dígitos manuscritos - o dataset MNIST


Neste exercício irá utilizar o dataset MNIST, que é um conjunto de imagens de dígitos (entre 0 e 9) manuscritos em
escala de cinza e com tamanho de 28x28 pixeis.
Este dataset contém 60000 imagens de treino e 10000 imagens de
teste.
A representação de cada uma das imagens é uma matriz 28x28 de inteiros entre 0 e 225, correspondentes à
escala de cinza de cada pixel da imagem.
A ideia é que, dados estes valores de cada um dos 784 pixeis de uma
imagem, seja possível inferir a que dígito manuscrito essa imagem corresponde.
A Keras permite descarregar este
dataset.

----

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)

----

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).

É
É depois possível visualizar duas imagens do conjunto de treino. Finalmente, usamos a técnica one-hot-encoding
para as classes existentes usando a função to_categorical.

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.

Início |
Objectivos |
Programa |
Bibliografia |
Docentes |
Avaliação |
Horário |
Aulas |
Trabalhos |

Comentários e sugestões para jleite@fct.unl.pt

Você também pode gostar