Abril de 2019

Volume 34 – Número 4

[Inteligência Artificial]

Como as redes neurais aprendem?

Por Frank La La | abril de 2019

Frank La VigneNa minha coluna anterior ("Um olhar mais aprofundado sobre as redes neurais", msdn.com/magazine/mt833269), examinei a estrutura básica das redes neurais e criei uma a partir do zero com Python. Após examinar as estruturas básicas comuns a todas as redes neurais, criei um exemplo de framework para calcular as somas ponderadas e os valores de saída. Os neurônios são simples e executam funções matemáticas básicas para normalizar suas saídas entre 1 e 0 ou -1 e 1. No entanto, tornam-se poderosos quando se conectam uns aos outros. Os neurônios estão organizados em camadas em uma rede neural e cada um deles passa valores para a próxima camada. Os valores de entrada propagam-se em cascata para frente por meio da rede e afetam a saída em um processo chamado de propagação direta.

Mas como exatamente as redes neurais aprendem? Qual é o processo e o que acontece dentro de uma rede neural quando ela aprende? Na coluna anterior, o foco era a propagação direta de valores. Para cenários de aprendizado supervisionado, as redes neurais podem se beneficiar de um processo chamado retropropagação.

Retropropagação, Perda e Épocas

Lembre-se de que cada neurônio em uma rede neural recebe valores de entrada multiplicados por um peso para representar a força dessa conexão. A retropropagação descobre os pesos corretos que devem ser aplicados aos nós de uma rede neural, comparando as saídas atuais da rede com as saídas desejadas ou as corretas. A diferença entre a saída desejada e a saída atual é calculada pela função de Perda, ou Custo. Em outras palavras, a função de Perda nos informa o nível de precisão de nossa rede neural fazendo previsões para uma determinada entrada.

A fórmula para calcular a Perda pode ser vista na Figura 1. Não se intimide com a matemática, tudo o que ela faz é somar os quadrados de todas as diferenças. Em geral, os pesos e desvios são definidos inicialmente com valores aleatórios, que com frequência produzem um valor alto de Perda quando começamos a treinar uma rede neural.

A função de Custo ou Perda
Figura 1 A função de Custo ou Perda

Em seguida, o algoritmo ajusta cada peso para minimizar a diferença entre o valor calculado e o valor correto. O termo "retropropagação" vem do fato de que o algoritmo volta e ajusta os pesos e desvios após calcular uma resposta. Quanto menor é a Perda de uma rede, mais precisa ela se torna. Logo, o processo de aprendizado pode ser determinado como a redução da saída da função de perda. Cada ciclo de propagação direta e correção por retropropagação para reduzir a Perda é chamado de época. Resumindo, retropropagação é encontrar os melhores pesos e desvios de entrada para obter uma saída mais precisa ou "minimizar a Perda". Se você acha que isso soa computacionalmente dispendioso, realmente é. Na verdade, o poder de computação era insuficiente até muito recentemente para tornar esse processo prático para uso amplo.

Gradiente Descendente, Taxa de Aprendizado e Gradiente Descendente Estocástico

Como os pesos são ajustados em cada época? Eles são ajustados aleatoriamente ou há um processo? É nesse momento que muitos iniciantes começam a ficar confusos, já que há o uso de vários termos desconhecidos, como gradiente descendente e taxa de aprendizado. No entanto, não é tão complicado quando explicado apropriadamente. A função de Perda reduz toda a complexidade de uma rede neural a um único número que indica quanto a resposta da rede está distante da resposta desejada. Considerar a saída da rede neural na forma de um único número nos permite avaliar seu desempenho de maneira mais simples. O objetivo é encontrar a série de pesos que resulta em um valor de perda mais baixo, ou no valor mínimo.

Plotar isso em um gráfico, como na Figura 2, mostra que a função de Perda tem sua própria curva e gradientes que podem ser usados como guia no ajuste dos pesos. A inclinação da curva da função de Perda serve como guia e aponta para o valor mínimo. O objetivo é localizar o valor mínimo na curva inteira, representando as entradas em que a rede neural é mais precisa.

Gráfico da função de Perda com uma curva simples
Figura 2 Gráfico da função de Perda com uma curva simples

Na Figura 2, aumentar os pesos nos faz atingir um ponto mais baixo e, em seguida, há uma elevação novamente. A inclinação da linha revela a direção desse ponto mais baixo na curva, que representa a menor perda. Quando a inclinação for negativa, aumente os pesos. Quando a inclinação for positiva, diminua os pesos. O valor específico adicionado ou subtraído nos pesos é conhecido como Taxa de Aprendizado. Determinar uma taxa de aprendizado ideal é ao mesmo tempo arte e ciência. Se a taxa for muito alta o algoritmo pode exceder o mínimo. Se for muito baixa o treinamento levará muito tempo. Esse processo é chamado de gradiente descendente. Os leitores que estiverem mais familiarizados com as complexidades do cálculo integral verão o processo pelo que ele realmente é: a determinação da derivada da função de Perda.

Raramente, no entanto, o gráfico de uma função de Perda é tão simples como o da Figura 2. Na prática, existem muitos altos e baixos. Logo, o desafio passa a ser como localizar o mais baixo entre os pontos baixos (o mínimo global) e não se deixar enganar pelo pontos baixos nas proximidades (mínimas locais). A melhor abordagem nessa situação é escolher um ponto aleatoriamente ao longo da curva e, em seguida, continuar com o processo de descida de gradiente descrito anteriormente, daí o termo "Gradiente Descendente Eletrostático". Para ver uma ótima explicação dos conceitos matemáticos envolvidos nesse processo, assista ao vídeo do YouTube, “Gradient Descent, How Neural Networks Learn | Deep Learning, Chapter 2”, localizado em youtu.be/IHZwWFHWa-w.

Na maioria das vezes, esse nível de arquitetura de rede neural é amplamente fornecido por bibliotecas como Keras e TensorFlow. Como em qualquer esforço de engenharia de software, conhecer os conceitos básicos sempre ajuda quando precisamos superar os desafios na prática.

Aplicando a teoria à prática

Na coluna anterior, criei uma rede neural a partir do zero para processar os dígitos MNIST. A base de código resultante para resolver o problema foi ótima para ilustrar o funcionamento interno das arquiteturas de rede neural, mas não era viável para progredirmos. Atualmente, existem muitos frameworks e bibliotecas que realizam a mesma tarefa com menos código.

Para começar, abra um novo Jupyter notebook, digite o seguinte em uma célula em branco e execute para importar todas as bibliotecas necessárias:

import keras
from keras.models import Sequential
from keras.layers import Dense
from keras.utils import to_categorical
import matplotlib.pyplot as plt

Observe que a saída dessa célula informa que o Keras está usando um back-end TensorFlow. Já que o exemplo de rede neural MNIST é muito comum, o Keras o inclui como parte de sua API e até mesmo divide os dados em um conjunto de treinamento e um conjunto de teste. Escreva o código a seguir em uma nova célula e execute-o para baixar os dados e lê-los nas variáveis apropriadas:

# import the data
from keras.datasets import mnist
# read the data
(X_train, y_train), (X_test, y_test) = mnist.load_data()

Quando a saída indicar que os arquivos foram baixados, use o código a seguir para examinar brevemente o conjunto de dados de treinamento e de teste:

print(X_train.shape)
print(X_test.shape)

A saída deve exibir que o conjunto de dados x_train tem 60.000 itens e o conjunto de dados x_test tem 10.000 itens. Os dois são compostos por uma matriz de 28x28 pixels. Para ver uma imagem específica a partir dos dados MNIST, use MatPlotLib para renderizar uma imagem com o código a seguir:

plt.imshow(X_train[10])

A saída deve ser semelhante a um "3" manuscrito. Para ver o que há dentro do conjunto de dados de teste, insira o código a seguir:

plt.imshow(X_test[10])

A saída exibe um zero. Faça testes à vontade alterando o número de índice e o conjunto de dados para examinar os conjuntos de dados de imagem.

Modelando os dados

Assim como acontece com qualquer projeto de ciência de dados ou IA, os dados de entrada devem ser remodelados para as necessidades dos algoritmos. Os dados de imagem precisam ser nivelados para um vetor unidimensional. Como cada imagem tem 28x28 pixels, o vetor unidimensional será de 1 por (28x28), ou 1 por 784. Insira o código a seguir em uma nova célula e execute (é bom ressaltar que esse código não produzirá texto de saída):

num_pixels = X_train.shape[1] * X_train.shape[2]
X_train = X_train.reshape(X_train.shape[0], num_pixels).astype('float32')
X_test = X_test.reshape(X_test.shape[0], num_pixels).astype('float32')

Os valores dos pixels variam de zero a 255. Para usá-los, você terá que normalizá-los para valores entre zero e um. Use o comando a seguir para fazê-lo:

X_train = X_train / 255
X_test = X_test / 255

Em seguida, insira o código abaixo para ver como ficaram os dados:

X_train[0]

O resultado revela uma matriz de 784 valores entre zero e um.

A tarefa de pegar várias imagens de dígitos manuscritos e determinar que número eles representam chama-se classificação. Antes de criar o modelo, você terá que dividir as variáveis de destino em categorias. Nesse caso, você sabe que há 10, mas poderia usar a função to_categorical no Keras para determinar automaticamente. Insira o código a seguir e execute-o (a saída deve exibir 10):

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
num_classes = y_test.shape[1]
print(num_classes)

Criar, treinar e testar a rede neural

Agora que os dados foram modelados e preparados, é hora de criar as redes neurais usando o Keras. Insira o código a seguir para criar uma função que defina uma rede neural sequencial de três camadas com uma camada de entrada de num_pixels (ou 784) neurônios:

def classification_model():
  model = Sequential()
  model.add(Dense(num_pixels, activation='relu', input_shape=(num_pixels,)))
  model.add(Dense(100, activation='relu'))
  model.add(Dense(num_classes, activation='softmax'))
  model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
  return model

Compare esse código com o código dos métodos da minha última coluna criados "a partir do zero". Você deve notar novos termos como "relu" ou "softmax" referenciados nas funções de ativação. Até agora, só examinei a função de ativação Sigmoid, mas há vários tipos de funções de ativação. Por enquanto, lembre-se de que todas as funções de ativação compactam um valor de entrada exibindo um valor entre 0 e 1 ou -1 e 1.

Com toda a infraestrutura definida, é hora de criar, treinar e avaliar o modelo. Insira o código a seguir em uma célula em branco e execute-o:

model = classification_model()
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, verbose=2)
scores = model.evaluate(X_test, y_test, verbose=0)

À medida que a rede neural é executada, observe que o valor de perda cai a cada iteração. Paralelamente, a precisão também aumenta. Observe também quanto tempo cada época leva para ser executada. Ao terminar, insira o código a seguir para ver as porcentagens de precisão e erro:

print('Model Accuracy: {} \n Error: {}'.format(scores[1], 1 - scores[1]))

O resultado revela uma precisão maior que 98% e um erro de 1,97%.

Persistindo o modelo

Agora que o modelo foi treinado para um alto grau de precisão, você pode salvá-lo para uso futuro e evitar ter que treiná-lo novamente. Felizmente, o Keras torna isso fácil. Insira o código a seguir em uma nova célula e execute-o:

model.save('MNIST_classification_model.h5')

Ele cria um arquivo binário com aproximadamente 8KB de tamanho e contém os valores ideais para pesos e desvios. Carregar o modelo também é fácil com o Keras, da seguinte forma:

from keras.models import load_model
pretrained_model = load_model('MNIST_classification_model.h5')

Esse arquivo h5 contém o modelo e pode ser implantado junto com um código para remodelar e preparar os dados de imagem de entrada. Em outras palavras, o processo demorado de treinar um modelo só precisa ser feito uma vez. Referenciar o modelo predefinido não requer o computacionalmente caro processo de treinamento e, no sistema de produção final, a rede neural pode ser implementada rapidamente.

Conclusão

As redes neurais podem resolver problemas que por décadas têm confundido os algoritmos tradicionais. Como vimos, sua estrutura simples oculta sua verdadeira complexidade. As redes neurais funcionam propagando entradas diretas, pesos e desvios. No entanto, é o processo inverso da retropropagação, em que a rede realmente aprende determinando as alterações feitas nos pesos e desvios para produzir um resultado preciso.

O aprendizado, no que diz respeito ao computador, envolve reduzir a diferença entre o resultado real e o correto. O processo é demorado e computacionalmente caro, como prova o tempo necessário para percorrermos uma única época. Felizmente, esse treinamento só precisa ser feito uma vez e não a cada vez que o modelo é necessário. Também examinei o uso do Keras para criar essa rede neural. Embora seja possível escrever o código necessário para criar redes neurais a partir do zero, é muito mais simples usar as bibliotecas existentes, como o Keras, que cuidam dos mínimos detalhes para nós.


Frank La Vigne trabalha na Microsoft como um profissional de Soluções de Tecnologia de Inteligência Artificial, em que ele ajuda as empresas a alcançar mais obtendo o máximo proveito de seus dados com análise e IA. Ele também co-hospeda o podcast DataDriven. Ele escreve regularmente em seu blog FranksWorld.com e você pode assisti-lo em seu canal no YouTube “Frank’s World TV” (FranksWorld.TV).

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Andy Leonard