Introdução teórica a Neural Network — Deep Learning — Parte 2
No post anterior vimos o funcionamento de uma Rede Neural (Neural Network ou, somente, NN), agora devemos entender como gerar um output preciso. Para ajudar a entender essa segunda parte, iremos exemplificar em um clássico problema de reconhecimento de números.
O exemplo abaixo retrata uma imagem com 14 pixel do número 1 e será aplicado NN para classificar.
Mas até chegar a imagem da direita, como o NN interpretou a imagem da esquerda? Vamos lá…
Essa é uma imagem de 14x14, que resulta em um total de 196 pixel, como mostra a imagem da direita, com uma escala de tonalidade do branco ao preto que varia entre 0 e 1. Então, esse seria nossos input's e para as próximas camadas (Hidden Layers), os pesos e bias irão determinar os neurônios que serão ativados ou não. Como visto no post parte 1, o bias é uma constante que determina quando se deve considerar ou melhorar a significância das somas dos pesos vezes os input's.
Mas supondo que trabalhemos com 2 camadas escondidas e 1 de saída. Sendo que cada camada tem 16 neurônios e a camada de saída 10 (0,1,2,3,4,5,6,7,8,9). Portanto, teremos no total 196*16+16*16+16*10 = 3.552 ajustes de pesos e bias teremos mais 16+16+10. Por fim, um total de 3.594 combinações para determinar o valor final de uma interação. Em suma, a função de NN terá 196 pixeis de INPUT'S, com 10 OUTPUTS e 3.594 PARÂMETROS.
Então claramente entendemos que ajustando os pesos e os bias, é a maneira de refinar o resultado. Na primeira iteração os pesos e bias são inicializados de maneira aleatória e devido a isso, resultará uma função de custo alto.
Vou usar um outro exemplo para demonstrar como funciona, imagine que estamos querendo classificar o número 3. Abaixo mostra o resultado do output do modelo comparado com o real, calculando assim o resultado da função de custo do modelo, que será a soma de cada quadrado da diferença entre o resultado do modelo com o real dos outputs.
E cada interação terá sua função de custo, portanto devemos considerar a função de custo médio de todos o treinamentos que o modelo irá fazer. Usando novamente o primeiro exemplo a função de custo será definido por 3.594 INPUT'S, 1 OUTPUT e os PARÂMETROS dependerá de quantos exemplos de treino o modelo terá, quanto mais, melhor!
Agora, tão esperado momento…
como diminuiremos o valor da função custo?
A galera de exatas irá dizer DERIVANDO, o que não está errado se for um gráfico simples como uma hipérbole. Mas normalmente o gráfico da função é composto por altos e baixos com n dimensões. Para isso precisamos de um vetor nos direcionando para o menor valor, em outras palavras o negativo do vetor gradiente!
Em linhas gerais, como funciona o vetor gradiente?
Será inicializado de maneira aleatória no gráfico da função. Em seguida o modelo deve escolher que direção deve seguir, o slope da função(tangente) irá ter esse papel. O vetor gradiente é apenas um vetor e o que dirá a ele quanto deve-se avançar é conhecido como step size. Se o step size for proporcional ao tamanho do slope e estiver no ponto flat, perpendicular ao eixo y (em torno do ponto mínimo), o step size será cada vez menor evitando assim o overshooting (passar do mínimo local).
Fazendo repetidamente, usando o slope como guia, certamente irá chegar ao mínimo local. O vetor gradiente dirá a direção para o maior valor a partir do ponto, então devemos considerar o negativo para que possamos enxergar a direção do menor valor possível.
Entendeu? Vamos entrar no “matematiquês"
Na figura abaixo, mostra um simples exemplo de vetor gradiente de uma função da circunferência.O ponto P(2,5) — está em amarelo, foi escolhido de forma arbitrária e será usado para achar a curva de nível do problema aplicando a função objetiva — está em vermelho, resultando o valor da curva de nível de 29 — está em rosa. Para quem não lembra, a curva de nível é o desenho do contorno da função objetiva e serve para representar a área graficamente.
Agora, deve-se calcular a derivada parcial em função de x e y — está em azul. Depois de ter calculado a derivada parcial em função de x e y, deve-se aplicar na fórmula padrão do vetor gradiente. Resultará uma nova função que será aplicado novamente o ponto escolhido de forma aleatória para achar a direção do vetor — em verde. No gráfico — em preto, mostra a posição do ponto escolhido no eixo x=2 e no y=5. A parti deste ponto, será projetado o vetor gradiente da seguinte maneira: como o o vetor resultante foi <4,10>, deve somar mais quatro no eixo x e em cima dessa projeção, somar mais 10 no eixo y.
A sua diagonal será o vetor gradiente da função.
Voltando a RNN…
O vetor gradiente deve ser incrementado ao vetor de peso e bias para resultar o novo vetor resultado. Esse método é aplicado em todas amostras de treino já que devemos minimizar a função de custo que nada mais é a média da função de custo de cada treinamento (iteração). O algoritmo Backpropagation que computa de forma eficiente os gradientes, além de ser o terceiro pulo do gato de redes neurais porque com ele você entenderá como funciona o processo de aprendizagem de máquina — não se preocupe que esse tópico será explicado com mais detalhe em um próximo post.
Ao final de um modelo com uma acurácia acima de 96%, posso dizer que meu modelo saber identificar um número? Bem… desculpe ser franco, mas NÃO e calma vou explicar melhor.
Em cada camada, cada neurônio será um padrão dos conjuntos de pixel e quase podemos dizer que não existe um padrão muito claro. Só podemos afirmar que dentro de 3.594 dimensões de espaços possíveis de pesos e bias, o modelo achou um mínimo local suficiente para classificar os números. Agora se colocássemos um imagem aleatória (imagem de um gato) o modelo iria fornecer um resultado? Sim, ele pode resultar um número e isso ajuda a provar que o modelo não é capaz de desenhar o número que está classificando… logo eles não são tão inteligentes assim né?!
E outra, esse método não parece mas é velho… só que para facilitar o entendimento dos métodos Rede Neural Convolucional e LSTM as Redes Neurais Multicamadas (MLP) podem ser uma boa opção.
Voltandooo… o gradiente descedente aumenta as chances de convergir para o mínimo local fornecendo um alto número de épocas (epochs em inglês ou iterações), já que ele treina toda a população para encontrar mínimo local de cada iteração. Todavia, é um processo lento e custoso computacionalmente. Devido a esse fato, desenvolveram o Gradiente Descendente Estocástico (GDE), Mini-Batch Gradiente Descendente, Momentum, Adagrad, Adadelta, Rmsprop, Adam.
Gradiente Descendente
Esse método considera apenas amostra aleatória N=1 ao invés de pegar todos inputs e calcular todos outputs como no tradicional Gradiente Descendente. O Mini-Batch Gradiente Descendente nada mais é do que uma variação do GDE com uma amostra maior que 1, onde os Batch serão divididos em partes para serem embaralhados e esse processo será repetido para cada iteração. Abaixo segue um gráfico para ajudar o entendimento, o Full Batch se refere ao método padrão do Gradiente Descendente.
Ficou confuso? Talvez em código ajuda a clarear a diferença. Segue um simples script em python.
for i in range(nb_interacao):
params_grad = evaluate_gradient(fun_custo, data, params)
params = params - params_grad * learning_rate
Agora o Gradiente Descendente Estocástico são escritos da seguinte maneira:
for i in range(nb_interacao):
np.random.shuffle(data)
for example in data:
params_grad = evaluate_gradient(fun_custo, example, params)
params = params - params_grad * learning_rate
Por fim, o Mini-Batch Gradiente Descendente:
for i in range(nb_interacao):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
params_grad = evaluate_gradient(fun_custo, batch, params)
params = params - params_grad * learning_rate
O learning_rate
serve para apenas diminuir o valor do erro para não causar overshooting, geralmente é um valor pequeno como 0,01.
Dos vetores gradientes descendente, Mini Batch acaba sendo a melhor opção por pegar o melhor dos dois mundos.
No próximo post iremos continuar abordando os demais otimizadores. Espero que para quem nunca ouviu falar em rede neural, tenha conseguido entender de fora resumida como funciona essa importante etapa. Quando formos escrever um script em Python ou R, perceberá que essa breve introdução ajudará a entender e ajustar os parâmetros da melhor forma possível.
FONTES:
- https://mathcs.clarku.edu/~djoyce/ma131/directional.pdf
- http://cs231n.github.io/neural-networks-3/#sgd
- https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/learn/text_classification.py
- http://napitupulu-jon.appspot.com/posts/deep-learning-tensorflow-mnist.html
- https://www.quora.com/What-are-differences-between-update-rules-like-AdaDelta-RMSProp-AdaGrad-and-AdaM
- http://ruder.io/optimizing-gradient-descent/index.html#adagrad
- https://towardsdatascience.com/learning-rate-schedules-and-adaptive-learning-rate-methods-for-deep-learning-2c8f433990d1
- https://www.reddit.com/r/MachineLearning/comments/3y84hr/how_does_adam_compare_to_adadelta/
- https://arxiv.org/pdf/1212.5701.pdf
- https://openreview.net/pdf?id=ryQu7f-RZ
- http://nghenglim.github.io/Paper-adadelta/
- https://arxiv.org/pdf/1609.04747.pdf
- https://arxiv.org/pdf/1206.5533v2.pdf
- https://arxiv.org/pdf/1804.04235.pdf