Escolar Documentos
Profissional Documentos
Cultura Documentos
Posted on 20/02/2017
Métodos iterativos de otimização são usados toda a parte de Aprendizado de Máquina. Aqui, nós vamos olhar o
método de gradiente descendente. Em se tratando de uma simples regressão linear, o método de gradiente
descendente só é recomendado quando temos dados com muitas dimensões. Nesse caso, a inversão da matriz
X
X
T
X
X começa a demorar muito e resolver regressão linear pela fórmula analítica w
^
^
w = (X
X
T
X)
X
−1
X
X
T
y
y não vale
mais a pena.
Nós também veremos um pouco de regimes de aprendizado online e em lotes (mini-batch learning) e discutiremos
como esses regimes podem ser usados para aprender utilizando bases gigantescas, que não são possíveis de
carregar de uma só vez para o RAM do computador (e.g. bases com +/- 20 GB).
Para melhor entendimento do algoritmo de otimização, é mais interessante começar usando-o em um problema mais
simples, então vamos introduzir o algoritmo com um problema de regressão linear simples, com apenas uma variável
na matriz de dados X
X.
Conteúdo
Pré-requisitos
Intuição e explicação matemática
Implementando e Visualizando gradiente descendente
Hiper-parâmetros
Problemas no aprendizado
Gradiente descendente estocástico: aprendizado em mini-lotes
Explorando melhorias: acelerando GDE
Usando gradiente-descendente na prática
Ligações-externas
Pré-requisitos
Vou pressupor que você tenha os conhecimentos especificados no tutorial sobre matemática e programação para
aprendizado de máquina (https://matheusfacure.github.io/2017/01/15/pre-req-ml/), isto é, que sabe cálculo
(derivadas), o básico de álgebra linear, de estatística e de programação. Eu também vou pressupor que você viu os
tutoriais anteriores a esse. Meus tutoriais (https://matheusfacure.github.io/tutorials/) são ordenados de maneira lógica
e sugiro fortemente que você se atenha à ordem deles para maior compreensão.
resíduos.
A ideia pro trás dos métodos iterativos de otimização é bastante simples: nós começamos com algum chute razoável
para os valores de ^
b e w
^ e vamos atualizando-os na direção certa até que chegamos no valor mínimo da nossa
2 2
função custo, nesse caso, || ϵ^
^|| . Matematicamente, nós temos que perceber que a nossa função custo, || ϵ
ϵ ^|| , é uma
^
ϵ
função de ^
b e w
^:
2
^ ^
L( b , w ) = || ^
^
ϵ
ϵ ||
2
^
= ∑ ϵ
2
^ − y)
= ∑ (y
^ ^ − y)
2
= ∑ ( b + xw
T
= (X
Xw^
^ − y
w y) (X
Xw^
^ − y
w y)
^
Em que w
^ é o vetor com os parâmetros, incluindo b . Podemos minimizar essa função custo em seus
^
w
parâmetros usando cálculo multivariado. Essa função custo - especifica de regressão linear - é uma função convexa,
o que quer dizer que o único ponto de mínimo que ela tem é um mínimo global. Em outras palavras, a função custo
pode ser vista como uma tigela, e o gradiente dessa função nos apontará a direção de descida mais íngreme, de
forma que possamos chegar ao fundo da tigela, onde está o ponto de menor custo. No nosso exemplo com apenas
dois parâmetros, essas direções são nos espaços de ^
b e w
^. Para implementar o gradiente descendente, basta
∂
^ ^ ^ ^
b := b − α L( b , w )
∂^
b
∂
^ := w
^ − α ^ ^
w L( b , w )
∂w
^
Ou, no caso específico da nossa função custo de soma dos erros quadrados:
^ ^ ^ ^ x − y)
b := b − α2 ∑ ( b + w
w ^ − α2 ∑ ((^
^ := w ^ x − y)x)
b + w
Se quisermos simplificar, podemos retirar da fórmula 2 que não fará diferença, uma vez que as derivadas já estão
sendo multiplicadas por uma constante α . Se quisermos simplificar a notação mais ainda, podemos utilizar a notação
de vetores:
^
^ := w
w
w ^
^ − α∇(L)
w
∂
T
∇(L) = (X
Xw^ − y
w
^ y) (X
Xw^ − y
w
^ y)
∂w
^
^
w
∂ T T
T T T
= (w
^
^
w X
X X
Xw^ − 2w
w
^ ^
^
w X
X y
y + y
y y
y)
∂w
^
^
w
T T
= 2X
X X
Xw^ − 2X
w
^ X y
y
^
Em que w
^ é o vetor dos parâmetros da regressão linear, incluindo o intercepto b . Note que esse última regra de
^
w
atualização é geral para qualquer número de dimensões que nossos dados possam ter.
a única variável, w
^.
Particularmente, vamos gerar dados x e y de forma que y = 5 + 3x + ϵ , em que ϵ é algum erro aleatório. Nós
^
sabemos que os valores ótimos de w
^ e b seriam então 3 e 2, respectivamente, então poderemos ver quão perto deles
Visualmente, se plotarmos os pares (x,y) teremos um gráfico como o abaixo. A nossa esperança é que a técnica de
gradiente descendente consiga achar uma reta que melhor se encaixe nestes dados.
Antes de implementar a regressão linear por gradiente descendente, para melhor entendimento do algoritmo, é uma
boa visualizar como é a nossa função custo quando plotada nas duas dimensões dos parâmetros ^
b e w
^ que
queremos aprender:
Como eu disse, a função de custo parece uma tigela. Se você olhar bem, vai perceber como o ponto de mínimo da
tigela está onde ^
b = 5 ew
^ = 3 . O gradiente dessa função é simplesmente um vetor de derivadas parciais, que dão
a inclinação dessa tigela em cada ponto e em cada direção:
∂L ∂L
∇(L) = [ , ]
^ ^
∂w
∂b
Se nós seguirmos na direção oposta do gradiente, então chegaremos no ponto de mínimo. Podemos traçar uma
analogia com uma bolinha de gude sendo solta em uma tigela: a bolinha descerá na direção mais inclinada e
eventualmente parará no ponto mais baixo da tigela. Há uma importante diferença, no entanto. Quando falamos de
uma bolinha de gude deslizando para o fundo de uma tigela, podemos visualizar a bolinha começando com uma
pequena velocidade e acelerando ao longo do trajeto. Com gradiente descendente ocorre o oposto: inicialmente, os
parâmetros ^
b e w
^ caminham rapidamente em direção ao ponto de mínimo e, quanto mais se aproximam dele,
Mas por que isso acontece? Pense em como a cada iteração os parâmetros ^
b e w
^ dão um passo em direção ao
mínimo. O tamanho desse passo será o valor do gradiente naquele ponto multiplicado pela constante α . Olhe de
novo para o gráfico acima e note que quanto mais próximos estamos do ponto de mínimo, menor a inclinação da
função custo, OU SEJA menor o gradiente, OU SEJA, menor o passo dado em direção ao mínimo.
Essa característica do método de gradiente descendente é ao mesmo tempo boa e ruim. É ruim pois atrasa o
processo de aprendizado quando chegamos próximo do mínimo, mas é boa porque nos permite uma exploração mais
minuciosa da superfície de custo em torno do ponto de mínimo. Dessa forma, podemos localizá-lo com mais precisão.
Isso talvez não pareça muito importante nesse caso super simples de regressão linear com apenas dois parâmetros
para aprender, mas quando estamos lidando com aprendizado de redes neurais com milhares de parâmetros e uma
função custo não convexa você vai entender porque é importante essa exploração minuciosa do espaço da função
custo.
Tendo dito tudo isso, vamos agora implementar a regressão linear com gradiente descendente. Note como abaixo nós
nos restringimos ao caso simples para que possamos visualizar o processo de aprendizado. Algumas pequenas
mudanças são necessárias no caso de uma regressão linear com vários parâmetros para aprender.
import pandas as pd
dados = pd.DataFrame()
dados['x'] = np.linspace(-10,10,100)
self.learning_rate = learning_rate
self.training_iters = training_iters
# formata os dados
if len(X_train.values.shape) < 2:
X = X_train.values.reshape(-1,1)
X = np.insert(X, 0, 1, 1)
for _ in range(self.training_iters):
# atualiza os parâmetros
self.w_hat -= gradient
# formata os dados
if len(X_test.values.shape) < 2:
X = X_test.values.reshape(-1,1)
X = np.insert(X, 0, 1, 1)
conseguimos que os parâmetros aprendidos chegassem muito perto dos valores de mínimo. Nós podemos dizer com
confiança que nosso algoritmo de gradiente descendente foi um sucesso!
Obs: Para uma implementação com visualização do aprendizado, veja meu GitHub
(https://github.com/matheusfacure/Tutoriais-de-AM).
Hiper-parâmetros
O algoritmo de otimização iterativa por gradiente descendente é talvez o algoritmo de Aprendizado de Máquina mais
importante que você vai aprender: ele é extremamente poderoso, relativamente rápido e funciona nos mais diversos
cenários. No entanto, tudo isso vêm a um preço e nesse caso são os hiper-parâmetros.
rede neural, como veremos mais para frente), os hiper-parâmetros não são aprendidos pela máquina durante o
treinamento e devem ser ajustados manualmente. No caso da nossa regressão linear por gradiente descendente,
podemos distinguir três hiper-parâmetros:
A taxa de aprendizado
O número de iterações de treino
Os valores iniciais de w
^
^
w
No caso de regressão linear, como a função custo é convexa, não importa muito onde começamos em termos de w
^.
^
w
implementação eles nem sequer foram feitos para serem ajustados e são simplesmente pequenos valores aleatórios).
Agora, os dois primeiros hiper-parâmetros são muito importantes e o sucesso ou fracasso do aprendizado depende
severamente de conseguirmos ajustá-los corretamente. A taxa de aprendizado é definitivamente o mais importante
de todos, então vamos gastar um certo tempo discutindo como ela influencia no aprendizado e como ajustá-la bem.
A taxa de aprendizado define o tamanho dos passos que daremos em direção ao mínimo em cada iteração. Se esses
passos forem muito pequenos, é quase garantido que chegaremos ao ponto de mínimo da função, mas para isso
talvez precisaremos de muitas iterações de treino, tornando o algoritmo desnecessariamente lento.
Por outro lado, se colocarmos uma taxa de aprendizado muito alta, pode acontecer de sermos catapultados para cima
da função custo e irmos cada vez mais longe do mínimo, resultando em uma falha completa de aprendizado. Isso
acontecerá quando o passo que dermos for tão grande que pulará o ponto de mínimo e chegará em um ponto na
função custo mais alto do que o de onde saímos. Nesse novo ponto, o gradiente será ainda maior, aumentando mais
ainda o passo seguinte e nos arremessando ainda mais longe do ponto de mínimo a cada iteração.
Podemos ver que a taxa de aprendizado não deve ser nem tão grande, nem tão pequena. Uma sugestão de
ajustamento desse hiper-parâmetro é começar com 0.01 e explorar os pontos em volta dez vezes maior/menor (isto é,
0.1 e 0.001). Na maioria dos casos, uma boa taxa de aprendizado será algum dos seguintes valores: 1, 0.1, 0.01,
0.001, 0.0001, 0.00001.
Com uma boa taxa de aprendizado, selecionar o número de iterações de treino é uma tarefa fácil. Mesmo assim,
recomenda-se plotar o valor da função custo a cada iteração de treino, assim como fizemos no gráfico 2 do GIF
acima. Dessa forma você poderá ver se a função custo já chegou em uma região em que o seu valor não diminui ou
diminui pouco a cada iteração.
No nosso caso, o gráfico da função custo a cada iteração é bastante suave, mas pode acontecer de haver tanto
iterações em que o custo cai quando iterações em que o custo sobe. Se esse é o caso e a função custo flutua muito a
cada iteração, recomenda-se diminuir a taxa de aprendizado. Se a função custo desce suavemente e
constantemente, mas muito devagar, recomenda-se aumentar a taxa de aprendizado.
Problemas no aprendizado
Lembre-se de como a função custo da regressão linear é uma tigela? Se fizermos secções horizontais nessa tigela
teremos um mapa topográfico da superfície de custo, assim como no ótimo desenho abaixo feito por mim.
Sabemos que a otimização por gradiente descendente dará passos na direção mais inclinada, ou seja, na direção
perpendicular as curvas de nível, assim como desenhado acima. Se as curvas de nível forem círculos perfeitos (como
os que eu tentei desenhar), gradiente descendente só dará passos em direção ao ponto de mínimo e convergirá
rapidamente. Por outro lado, se as curvas de nível da superfície de custo forem elipses alongadas, o tempo de
convergência dependerá fortemente da inicialização dos nossos parâmetros.
Por exemplo, se começarmos nossa descida no ponto 2 da imagem acima, a direção perpendicular à curva de nível
aponta diretamente para o ponto de mínimo e não teremos maiores problemas durante o aprendizado. Mas se
começarmos em um ponto como o 1 da imagem acima, a direção perpendicular à curva de nível aponta numa direção
quase 90 graus da direção ao ponto de mínimo. Como consequência, daremos muitos passos em zig-zag e a
convergência demorará muito mais.
Esse formato de elipse da função custo surge quando as variáveis dos nossos dados estão em escalas muito
diferentes. Assim, uma solução simples para esse problema é deixar todos as variáveis na mesma escala. Uma forma
de realizar isso é, para cada variável, subtrair a média e dividir pelo desvio padrão (normalização).
Em primeiro lugar, considere se os seus dados tem alguma redundância, isto é, se você embaralhasse todas as
observações, uma parte dos dados seria parecida com a outra? Se sim, então nós não precisamos percorrer todos os
dados para computar o gradiente e podemos conseguir uma aproximação dele apenas olhando alguns exemplos dos
dados. Essa é a ideia central por trás da técnica de gradiente descendente estocástico (G.D.E.).
Para possibilitar que a otimização por gradiente descendente continue rápida mesmo com milhões de dados, nós
vamos alterá-la da seguinte forma:
1. Primeiro, embaralhamos os nossos dados de forma que se retirássemos diferentes sub-amostras deles, elas
não diferirão muito.
2. Em segundo lugar, ao invés de computar o gradiente usando todos os dados, nós vamos fazer uma estimação
dele usando apenas alguns dados - digamos um lote de 5 observações. Nós então atualizaremos os parâmetros
com base nessa estimação do gradiente. Na atualização seguinte, nós repetiremos esse processo, mas agora
estimando o gradiente com o próximo lote de dados, e assim por diante.
Você pode estar pensando que utilizar apenas 5 observações para estimar um gradiente nos dará uma estimativa
bem ruim e você tem razão. Na verdade, essa estimativa é tão ruim que muitas vezes o gradiente estimado nos
levará em uma direção errada e custo aumentará. No entanto, na média, o gradiente estimado nos levará na direção
correta. Em resumo, com GDE precisaremos de mais iterações de treino para chegar próximo do mínimo, mas cada
iteração demorará muito (muuuito, muuutio) menos tempo e o aprendizado como um todo será mais rápido. A rigor,
se gradiente descendente com todos os dados demora linearmente mais conforme mais dados temos, com GDE o
tempo de treino é CONSTANTE e não aumenta com o a quantidade de dados! Você leu direito! Isso porque pode
acontecer de nem sequer precisarmos ver todas as observações para chegar a uma região razoável na função de
custo.
Mais ainda, como não precisamos de todos os dados de uma vez para o processo de treinamento, podemos utilizar
essa técnicas para Aprendizado de Máquina em bases de dados gigantescas, maiores até do que nosso computador
suportaria trazer para a memória de curto prazo de uma só vez (digamos, bases com mais de 100GB).
Ao utilizar GDE introduzimos mais um hiper-parâmetro que terá que ser ajustado manualmente: o tamanho do lote. É
importante entender como esse hiper-parâmetro funciona para saber como ajustá-lo bem. Em geral, lotes maiores
significam passos mais precisos em direção ao mínimo, mas ao mesmo tempo significa passos mais demorados. Não
existe uma recomendação única para o tamanho do mini-lote pois o tamanho ótimo depende fortemente da
característica dos dados em questão. Se os dados são bastante redundantes, um mini-lote menor bastará, mas se os
dados forem muito desbalanceados, recomenda-se usar um mini-lote maior. Normalmente o tamanho do mini-lote
varia entre 1 a 1000 observações, mas podem surgir ocasiões que peçam um mini-lote maior.
Um outro detalhe que vale a pena mencionar é que GDE normalmente não converge, mas fica vagando em alguma
região próxima ao ponto de mínimo. Na prática, isso não é um problema, pois nessa região o custo já é baixo o
suficiente. De qualquer forma, é uma boa visualizar um exemplo do tipo de trajeto que GDE percorrerá numa
superfície de custo:
E finalmente, nossa implementação de GDE:
np.random.seed(23)
self.learning_rate = learning_rate
self.training_iters = training_iters
self.batch_size = batch_size
# formata os dados
if len(X_train.values.shape) < 2:
X = X_train.values.reshape(-1,1)
X = np.insert(X, 0, 1, 1)
for i in range(self.training_iters):
# cria os mini-lotes
offset = (i * self.batch_size) % (y_train.shape[0] - self.batch_si
batch_X = X[offset:(offset + self.batch_size), :]
gradient *= self.learning_rate
self.w_hat -= gradient
# formata os dados
if len(X_test.values.shape) < 2:
X = X_test.values.reshape(-1,1)
X = np.insert(X, 0, 1, 1)
Note como nem todos os passos nos levam em direção ao mínimo. Além disso, perceba como, no final do
aprendizado, jamais chegamos ao mínimo, mas ficamos vagando em torno dele.</div>
Como já dissemos, a diferença fundamental entre o método de gradiente descendente e o processo de uma bolinha
de gude descendo em uma cuia é que a bolinha acumula momento, acelerando conforme desce. Em outras palavras,
quando a direção de descida é a mesma, a bolinha aumenta a velocidade. Isso é definitivamente uma propriedade
que gostaríamos de ter no nosso processo de aprendizado por GDE: se estamos indo na direção certa, é uma boa
ideia acelerar!
Não se preocupe, é fácil modificar GDE para incorporar momento. Para isso, basta sabermos a velocidade passada
da bolinha e atualizá-la conforme o processo de descida. Além disso, nós agora vamos atualizar os parâmetros
conforme a velocidade, em vez de utilizar apenas o gradiente. Eis a nova regra de atualização dos parâmetros:
v
vtt := γv
vt−1
t−1 + α∇(L))
^
^ := w
w
w ^
^ − v
w vtt
Na primeira linha, nós atualizamos a velocidade. O termo γvt−1 funciona como um atrito ou resistência do ar,
diminuindo a velocidade em uma porcentagem 1 − γ da velocidade anterior. Pense nele como alguma viscosidade
que nos impede de acelerar. γ é mais um hiper-parâmetro que precisa ser ajustado manualmente. O termo seguinte,
α∇(L)) , incorpora a informação da inclinação da descida.
self.learning_rate = learning_rate
self.training_iters = training_iters
self.batch_size = batch_size
self.gamma = gamma
# formata os dados
if len(X_train.values.shape) < 2:
X = X_train.values.reshape(-1,1)
X = np.insert(X, 0, 1, 1)
for i in range(self.training_iters):
# cria os mini-lotes
offset = (i * self.batch_size) % (y_train.shape[0] - self.batch_si
batch_X = X[offset:(offset + self.batch_size), :]
gradient *= self.learning_rate
# atualiza a velocidade
velocidade = (velocidade * self.gamma) + gradient
# formata os dados
if len(X_test.values.shape) < 2:
X = X_test.values.reshape(-1,1)
X = np.insert(X, 0, 1, 1)
Para mostrar como utilizar gradiente descendente na prática vamos utilizar uma biblioteca de aprendizado de
máquina desenvolvida pelo Google e agora aberta ao público: TensorFlow (https://www.tensorflow.org/). Veja como
em poucas linhas podemos implementar a técnica de gradiente descendente para resolver nosso exemplo de
regressão linear. Note também como podemos rodar muito mais iterações mais rapidamente:
import tensorflow as tf
import numpy as np
# Monta a estrutura tf
b_hat = tf.Variable(tf.zeros([1]))
# modelo
y_hat = W_hat * x + b_hat
# Função custo
loss = tf.reduce_mean(tf.square(y_hat - y))
# otimizador
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(loss)
sess.run(optimizer)
sess.close()
Uma grande vantagem dessas bibliotecas é que elas são otimizadas para realizar operações algébricas muito
rapidamente. Nós vimos que GDE pode ser definido em termos de varias operações com vetores e matrizes e esse
tipo de operação pode ser paralelizado facilmente. Assim, se você tem um computador com uma placa de vídeo
(GPU) a otimização por GDE será extremamente rápida.
Ligações externas
Dada a sua importância, há muitas fontes excelentes para aprender sobre gradiente descendente:
Os vídeos do curso online de Neural Networks for Machine Learning, da universidade de Toronto, são
provavelmente a melhor fonte para estudar gradiente descendente. Todos os vídeos desta seção do curso
(https://www.youtube.com/playlist?list=PLnnr1O8OWc6bAAkp43m0jNF_DEqwWp2o2) são excelentes para
aprender bastante sobre gradiente descendente e suas extensões.
Os vídeos 1 (https://www.youtube.com/watch?
v=LN0PLnDpGN4&index=5&t=598s&list=PLnnr1O8OWc6ajN_fNcSUz9k5gF_E9huF0), 2
(https://www.youtube.com/watch?v=kWq2k1gPyBs&index=6&list=PLnnr1O8OWc6ajN_fNcSUz9k5gF_E9huF0) e
3 (https://www.youtube.com/watch?v=7LqYTTwuu0k&list=PLnnr1O8OWc6ajN_fNcSUz9k5gF_E9huF0&index=7)
sobre gradiente descendente com regressão linear de uma variável (do curso de Machine Learning com o
professor Ng) cobrem a maioria do conteúdo que vimos aqui com bastante visualização e de maneira intuitiva.
Além disso, o vídeo 4 (https://www.youtube.com/watch?v=UfNU3Vhv5CA&t=627s) do mesmo curso mostra bem
a intuição de GDE
Os vídeos 1 (https://www.youtube.com/watch?
v=hMLUgM6kTp8&index=20&list=PLAwxTw4SYaPn_OWPFT9ulXLuQrImzHfOV) e 2
(https://www.youtube.com/watch?v=s6jC7Wc9iMI&index=21&list=PLAwxTw4SYaPn_OWPFT9ulXLuQrImzHfOV)
do custo de Deep Learning do Google resumem bem GDE, dão dicas de como acelerar o aprendizado e ainda
falam sobre a extensão do algoritmo com momento - embora um pouco diferente da nossa.
TAMBÉM EM MATHEUSFACURE
Explorando Aprendizado de Explorando Aprendizado de Lighthearted Statistics and Lighthearted Statistics and E
Máquina nas Ciências Máquina nas Ciências Stuff Stuff M
Humanas e Sociais Humanas e Sociais H
5 Comentários matheusfacure 🔒 Disqus' Privacy Policy
1 Entrar
Participe da discussão...
Nome
Apenas um detalhe, o link "meus tutoriais" (https://matheusfacure.githu... presente nesta página está quebrado.
△ ▽ • Responder • Compartilhar ›
um abraço.
△ ▽ • Responder • Compartilhar ›
um abraço.
△ ▽ • Responder • Compartilhar ›
Não! Inclusive GDE é uma excelente alternativa para resolver problemas onde a não linearidade resulta na
ausência de soluções fechadas para o problema de otimização. Um exemplo são modelos de redes neurais.
1△ ▽ • Responder • Compartilhar ›
(http://linkedin.com/in/matheus-
facure-
7b0099117)
(https://github.com/matheusfacure)
(https://www.instagram.com/matfacure/)
(mailto:matheusfacure01@gmail.com)