No
último capítulo, vimos como as redes neurais podem aprender pesos e compensações de forma independente usando o algoritmo de descida de gradiente. No entanto, houve uma lacuna em nossa explicação: não discutimos o cálculo do gradiente da função de custo. E esta é uma lacuna decente! Neste capítulo, apresentarei um algoritmo rápido para calcular esses gradientes, conhecido como propagação reversa.
O algoritmo de retropropagação foi inventado pela primeira vez na década de 1970, mas sua importância não foi totalmente compreendida até o famoso trabalho de 1986, escrito por David Rumelhart, Joffrey Hinton e Ronald Williams. O artigo descreve várias redes neurais nas quais a propagação reversa funciona muito mais rápido do que nas abordagens anteriores de aprendizagem, razão pela qual desde então foi possível usar uma rede neural para resolver problemas anteriormente insolúveis. Hoje, o algoritmo de retropropagação é o cavalo de batalha para aprender uma rede neural.
Este capítulo contém mais matemática do que todos os outros no livro. Se você não gosta particularmente de matemática, pode ficar tentado a pular este capítulo e simplesmente tratar a retropropagação como uma caixa preta cujos detalhes você deseja ignorar. Por que perder tempo estudando-os?
A razão, é claro, é a compreensão. A propagação reversa é baseada na expressão da derivada parcial ∂C / ∂w da função de custo C em relação ao peso w (ou viés b) da rede. A expressão mostra a rapidez com que o valor muda quando pesos e compensações mudam. E embora essa expressão seja bastante complexa, ela tem sua própria beleza, porque cada um de seus elementos tem uma interpretação natural e intuitiva. Portanto, a retropropagação não é apenas um algoritmo de aprendizado rápido. Ele nos fornece uma compreensão detalhada de como a alteração de pesos e compensações altera todo o comportamento da rede. E vale a pena estudar os detalhes.
Dado tudo isso, se você quiser folhear este capítulo ou pular para o próximo, tudo bem. Escrevi o restante do livro para que seja compreensível, mesmo se considerarmos a distribuição reversa como uma caixa preta. Obviamente, mais adiante neste livro, haverá momentos a partir dos quais faço referências aos resultados deste capítulo. Mas, nesse momento, você deve entender as conclusões básicas, mesmo que não tenha seguido todos os argumentos.
Para aquecimento: uma abordagem matricial rápida para calcular a saída de uma rede neural
Antes de discutirmos a retropropagação, vamos obter um algoritmo de matriz rápida para calcular a saída de uma rede neural. Na verdade, já conhecemos esse algoritmo no final do capítulo anterior, mas eu o descrevi rapidamente, portanto vale a pena considerar novamente com mais detalhes. Em particular, será uma boa maneira de se adaptar ao registro usado na distribuição regressiva, mas em um contexto familiar.
Vamos começar com um registro que nos permita indicar claramente os pesos na rede. Usaremos w
l jk para denotar o peso da conexão do neurônio n. K na camada n. (L-1) com o neurônio n. J na camada n. L. Assim, por exemplo, o diagrama abaixo mostra o peso da conexão do quarto neurônio da segunda camada com o segundo neurônio da terceira camada:

A princípio, essa gravação parece estranha e requer algum tempo para se acostumar. No entanto, em breve parecerá simples e natural para você. Uma de suas características é a ordem dos índices j e k. Você pode decidir que seria mais razoável usar j para designar o neurônio de entrada, ek - para o neurônio de saída, e não vice-versa, como temos. Vou explicar o motivo desse recurso abaixo.
Usaremos notações semelhantes para compensações e ativações de rede. Em particular, b
l j denotará o deslocamento do neurônio n. J na camada n. L. a
l j denotará a ativação do neurônio n. j na camada n. l. O diagrama a seguir mostra exemplos de como usar esta entrada:

Com esse registro, a ativação de um
jj do neurônio n.
J na camada n. L está associada à ativação na camada n. (L-1) pela seguinte equação (compare com a equação (4) e sua discussão no capítulo anterior):
alj= sigma( sumkwljkal−1k+blj) tag23
onde a soma passa por todos os neurônios k na camada (l-1). Para reescrever essa expressão na forma de matriz, definimos uma matriz de peso
wl para cada camada l. Os elementos da matriz de pesos são simplesmente os pesos conectados à camada n ° l, ou seja, o elemento na linha n ° e na coluna n ° k será w n
jk . Da mesma forma, para cada camada l, definimos o vetor de deslocamento b
l . Você provavelmente adivinhou como isso funciona - os componentes do vetor de deslocamento serão simplesmente os valores b
l j , um componente para cada neurônio na camada n. E, finalmente, definimos o vetor de ativação a
l , cujos componentes são a ativação a
l j .
O último ingrediente necessário para reescrever (23) é a forma matricial da vetorização da função σ. Casualmente, encontramos vetorização no último capítulo - a idéia é que queremos aplicar a função σ a todos os elementos do vetor v. Usamos a notação óbvia σ (v) para denotar a aplicação da função por elementos. Ou seja, os componentes σ (v) são simplesmente σ (v)
j = σ (v
j ). Por exemplo, se tivermos uma função f (x) = x
2 , a forma vetorizada f nos fornecerá
f( beginbmatrix23 endbmatrix)= beginbmatrixf(2)f(3) endbmatrix= beginbmatrix49 fimbmatrix tag24
isto é, um vetorizado f esquadrinha todos os elementos do vetor.
Dadas todas essas formas de escrita, a equação (23) pode ser reescrita de uma forma vetorial bonita e compacta:
al= sigma(wlal−1+bl) tag25
Tal expressão nos permite dar uma olhada mais global na relação entre as ativações de uma camada e as ativações da anterior: simplesmente aplicamos a matriz de peso às ativações, adicionamos o vetor de deslocamento e depois aplicamos o sigmóide. A propósito, é esse registro que requer o uso do registro. Se usássemos o índice j para denotar o neurônio de entrada ek para o neurônio de saída, teríamos que substituir a matriz de peso na equação (25) pela transposta. Essa é uma mudança pequena, mas irritante, e perderíamos a simplicidade da declaração (e reflexão) sobre "aplicar a matriz de peso às ativações". Essa abordagem global é mais simples e concisa (e usa menos índices!) Do que a abordagem poneuron. Esta é apenas uma maneira de evitar o inferno sem perder a precisão do que está acontecendo. Essa expressão também é útil na prática, pois a maioria das bibliotecas de matrizes oferece maneiras rápidas de multiplicar matrizes, adicionar vetores e vetorizar. O código no capítulo anterior usou diretamente essa expressão para calcular o comportamento da rede.
Usando a equação (25) para calcular a
l , calculamos o valor intermediário z
l ≡ w
l a
l - 1 + b
l . Esse valor é bastante útil para nomear: chamamos z de entrada ponderada de neurônios da camada n. Mais tarde, usaremos ativamente essa entrada ponderada. Às vezes, a equação (25) é escrita através de uma entrada ponderada, como
l = σ (z
l ). Também é importante notar que z
l possui componentes
zlj= sumkwljkal−1k+blj isto é, zl
j é apenas uma entrada ponderada da função de ativação do neurônio j na camada l.
Duas suposições essenciais sobre a função de custo
O objetivo da retropropagação é calcular as derivadas parciais ∂C / ∂w e ∂C / ∂b da função de custo C para cada peso w e o viés b da rede. Para que a retropropagação funcione, precisamos fazer duas suposições principais sobre a forma da função de custo. No entanto, antes disso, seria útil imaginar um exemplo de função de custo. Usamos a função quadrática do capítulo anterior (equação (6)). Na entrada da seção anterior, parecerá
C= frac12n sumx||y(x)−aL(x)||2 tag26
onde: n é o número total de exemplos de treinamento; a soma vale para todos os exemplos x; y = y (x) é a saída necessária; L indica o número de camadas na rede; a
L = a
L (x) é o vetor de saída das ativações da rede quando x está na entrada.
Ok, então que tipo de suposições precisamos sobre a função de custo C para aplicar a retropropagação? Primeiro, a função de custo pode ser escrita como a média C = 1 / n C
x C
x da função de custo C
x para exemplos de treinamento individuais x. Isso é feito no caso de uma função de custo quadrático, em que o custo de um exemplo de treinamento é C
x = 1/2 || y - a
L ||
2) Essa suposição será verdadeira para todas as outras funções de custo que encontraremos no livro.
Precisamos dessa suposição, porque, de fato, a propagação reversa nos permite calcular as derivadas parciais ∂C / ∂w e ∂C / ∂b, com média dos exemplos de treinamento. Aceitando essa suposição, assumiremos que o exemplo de treinamento x é fixo e pararemos de especificar o índice x, anotando o valor de C
x como C. Em seguida, retornaremos x, mas por enquanto é melhor simplesmente dizer isso.
A segunda suposição sobre a função de custo - pode ser escrita como uma função da saída de uma rede neural:

Por exemplo, a função de custo quadrático atende a esse requisito, pois o custo quadrático de um exemplo de treinamento x pode ser escrito como
C=1/2||y−aL||2=1/2 sumj(yj−aLj)2 tag27
qual será a função das ativações de saída. Obviamente, essa função de custo também depende da saída desejada y, e você pode se perguntar por que não consideramos C como uma função também de y. No entanto, lembre-se de que o exemplo de treinamento de entrada x é fixo, portanto a saída y também é fixa. Em particular, não podemos alterá-lo alterando pesos e deslocamentos, ou seja, não é isso que a rede neural aprende. Portanto, faz sentido considerar C como uma função apenas das ativações de saída a
L e ye apenas como um parâmetro que ajuda a determiná-lo.
O trabalho de Hadamard s⊙t
O algoritmo de retropropagação é baseado nas operações usuais da álgebra linear - adição de vetores, multiplicação de um vetor por uma matriz, etc. No entanto, uma das operações é usada com menos frequência. Suponha que s e t sejam dois vetores da mesma dimensão. Então, por s⊙t, denotamos a multiplicação elementar de dois vetores. Então os componentes s⊙t são simplesmente (s⊙t)
j = s
j t
j . Por exemplo:
beginbmatrix12 endbmatrix odot beginbmatrix34 endbmatrix= beginbmatrix1∗32∗4 endbmatrix= beginbmatrix38 endbmatrix tag28
Um trabalho assim por vezes é chamado de
obra de Hadamard ou obra de Schur. Vamos chamá-lo de obra de Hadamard. Boas bibliotecas para trabalhar com matrizes geralmente têm uma implementação rápida do produto Hadamard, e isso pode ser conveniente ao implementar a propagação posterior.
Quatro equações fundamentais para propagação traseira
A retropropagação envolve entender como a alteração de pesos e compensações da rede altera a função de custo. Em essência, isso significa calcular as derivadas parciais ∂C / ∂w
l jk e ∂C / ∂b
l j . Mas para o cálculo deles, primeiro calculamos o valor intermediário δ
l j , que chamamos de erro no neurônio n. J na camada n. L. A propagação reversa nos dará um procedimento para calcular o erro δ
l j e, em seguida, associar δ
l j com ∂C / ∂w
l jk e ∂C / ∂b
l j .
Para entender como um erro é determinado, imagine que um demônio foi iniciado em nossa rede neural:

Ele está sentado no neurônio nº j da camada nº l. Após o recebimento dos dados de entrada, o daemon interrompe a operação do neurônio. Ele adiciona uma pequena alteração em Δz
l j à entrada ponderada do neurônio e, em vez de produzir σ (z
l j ), o neurônio produz σ (z
l j + Δz
l j ). Essa alteração também se espalhará pelas seguintes camadas da rede, que acabarão alterando o custo total em (∂C / ∂z
l j ) * Δz
l j .
Mas nosso demônio é bom, e ele está tentando ajudá-lo a melhorar o custo, ou seja, encontrar Δz
l j que reduz o custo. Suponha que o valor ∂C / ∂z lj
seja grande (positivo ou negativo). Então o demônio pode reduzir seriamente o custo escolhendo Δz
l j com o sinal oposto a ∂C / ∂z
l j . Mas se ∂C / ∂z
l j é próximo de zero, o demônio não pode melhorar muito o custo alterando a entrada ponderada z
l j . Portanto, do ponto de vista do demônio, o neurônio já está próximo do ideal (isso, é claro, é verdadeiro apenas para pequenos Δz
l j . Suponha que essas sejam as limitações das ações do demônio). Portanto, no sentido heurístico, ∂C / ∂z
l j é uma medida de erro do neurônio.
Sob a motivação desta história, definimos o erro δ
l j do neurônio j na camada l, como
deltalj equiv frac parcialC parcialzlj tag29
Pela nossa convenção usual, usamos δ
l para denotar o vetor de erro associado à camada l. A propagação de retorno nos dará uma maneira de calcular δ
l para qualquer camada e correlacionar esses erros com as quantidades que realmente nos interessam, ∂C / ∂w
l jk e jC / ∂b
l j .
Você pode estar se perguntando por que o daemon altera a entrada ponderada z
l j . Afinal, seria mais natural imaginar que o demônio altere a ativação da saída a
l j, de modo que usamos ∂C / ∂a
l j como uma medida de erro. De fato, se você fizer isso, tudo será muito semelhante ao que discutiremos mais adiante. No entanto, neste caso, a representação da retropropagação será algebricamente um pouco mais complicada. Portanto, insistimos na variante δ
l j = ∂C / ∂z
l j como uma medida de erro.
Em problemas de classificação, o termo “erro” às vezes significa o número de classificações incorretas. Por exemplo, se uma rede neural classificar corretamente 96,0% dos dígitos, o erro será 4,0%. Obviamente, isso não é o que queremos dizer com os vetores δ. Mas, na prática, você geralmente pode entender facilmente o significado.
Plano de Ataque : A retropropagação é baseada em quatro equações fundamentais. Juntos, eles nos fornecem uma maneira de calcular o erro δ
l e o gradiente da função de custo. Eu os dou abaixo. Não há necessidade de esperar seu desenvolvimento instantâneo. Você ficará desapontado. As equações de retropropagação são tão profundas que um bom entendimento requer tempo e paciência tangíveis e um aprofundamento gradual da questão. A boa notícia é que essa paciência valerá a pena. Portanto, nesta seção, nosso raciocínio está apenas começando, ajudando você a seguir o caminho de um profundo entendimento das equações.
Aqui está um diagrama de como aprofundaremos essas equações mais adiante: darei uma breve prova delas para ajudar a explicar por que elas são verdadeiras; vamos reescrevê-los em uma forma algorítmica na forma de pseudocódigo e ver como implementá-lo em código python real; na última parte do capítulo, desenvolveremos uma idéia intuitiva do significado das equações de retropropagação e como elas podem ser encontradas do zero. Periodicamente, retornaremos às quatro equações fundamentais e, quanto mais profundamente você as entender, mais confortáveis e talvez bonitas e naturais elas parecerão para você.
A equação do erro da camada de saída, δ L : os componentes de δ
L são considerados como
deltaLj= frac parcialC parcialaLj sigma′(zLj) tagBP1
Expressão muito natural. O primeiro termo à direita, ∂C / ∂a
L j , mede a rapidez com que o custo muda em função da ativação da saída nº j. Se, por exemplo, C não for particularmente dependente do neurônio de saída j, então δ
L j será pequeno, como esperado. O segundo termo à direita, σ '(z
L j ), mede a rapidez com que a função de ativação σ muda em z
L j .
Observe que tudo em (BP1) é fácil de contar. Em particular, calculamos z
L j ao calcular o comportamento da rede, e serão necessários um pouco mais de recursos para calcular σ '(z
L j ). Obviamente, a forma exata ∂C / ∂a
L j depende da forma da função de custo. No entanto, se a função de custo for conhecida, não deverá haver problemas com o cálculo de ∂C / ∂a
L j . Por exemplo, se usarmos a função de custo quadrático, C = 1/2 ∑
j (y
j - a
L j )
2 , portanto ∂C / ∂a
L j = (a
L j - y
j ), que é fácil de calcular.
A equação (BP1) é uma expressão explodida de δ
L. É completamente normal, mas não registrado na forma de matriz, necessária para a distribuição posterior. No entanto, é fácil reescrever na forma de matriz, pois
deltaL= nablaaC odot sigma′(zL) tagBP1a
Aqui ∇ C é definido como um vetor cujos componentes são as derivadas parciais ∂C / ∂a
L j . Pode ser representado como uma expressão da taxa de variação de C em relação às ativações de saída. É fácil ver que as equações (BP1a) e (BP1) são equivalentes; portanto, usaremos (BP1) para nos referir a qualquer uma delas abaixo. Por exemplo, no caso de um valor quadrático, temos ∇
a C = (a
L - y), portanto, a matriz completa (BP1) será
deltaL=(aL−y) odot sigma′(zL) tag30
Tudo nesta expressão possui uma forma vetorial conveniente e é fácil calcular usando uma biblioteca como o Numpy.
A expressão do erro δ l através do erro na próxima camada, δ l + 1 : em particular,
deltal=((wl+1)T deltal+1) cdot sigma′(zl) tagBP2
onde (w
l + 1 )
T é a transposição da matriz de peso w
l + 1 para a camada nº (l + 1). A equação parece complicada, mas cada elemento é fácil de interpretar. Suponha que conhecemos o erro δ
l + 1 para a camada (l + 1). A transposição da matriz de pesos (wl
+ 1 )
T pode ser imaginada como retrocedendo o erro pela rede, o que nos dá alguma medida de erro na saída da camada n ° l. Então, consideramos o produto Hadamard ⊙σ '(z
l ). Isso empurra o erro de volta através da função de ativação na camada l, fornecendo o valor de erro δl na entrada ponderada da camada l.
Ao combinar (BP2) com (BP1), podemos calcular o erro δ
l para qualquer camada de rede.
Começamos usando (BP1) para calcular δ L , depois usamos a equação (BP2) para calcular δ L-1 , depois novamente para calcular δ L-2 , e assim por diante, até a parte traseira da rede.A equação da taxa de variação do custo em relação a qualquer compensação na rede : em particular:∂ C∂ b l j =δ l j
Ou seja, o erro δ l j é exatamente igual à taxa de variação ∂C / ∂b l j . Isso é excelente porque (BP1) e (BP2) já nos disseram como calcular δ l j . Podemos reescrever (BP3) mais curto quanto∂ C∂ b =δ
onde δ é estimado para o mesmo neurônio que o viés b.A equação da taxa de variação do valor em relação a qualquer peso na rede : em particular:∂ C∂ w l j k =a l - 1 k δ l j
A partir daqui, aprendemos como calcular a derivada parcial ∂C / ∂w l jk através dos valores de δ l e a l-1 , cujo método de cálculo já sabemos. Esta equação pode ser reescrita de uma forma menos carregada:∂ C∂ w =ainδout
onde a in é a ativação da entrada neural para o peso w, e δ out é o erro da saída neural do peso w. Se observarmos mais detalhadamente o peso we dois neurônios conectados por ele, podemos desenhar da seguinte maneira:
Uma boa consequência da equação (32) é que quando a ativação a in é pequena, a em ≈ 0, o termo termo ∂C / ∂w também tende a para zero. Nesse caso, dizemos que o peso é treinado lentamente, ou seja, não muda muito durante a descida do gradiente. Em outras palavras, uma das consequências (BP4) é que a produção ponderada de neurônios com baixa ativação aprende lentamente.Outras idéias podem ser extraídas de (BP1) - (BP4). Vamos começar com a camada de saída. Considere o termo σ '(z L j) em (BP1). Lembre-se do gráfico do sigmóide do último capítulo que ele se torna plano quando σ (z L j ) se aproxima de 0 ou 1. Nesses casos, σ '(z L j ) ≈ 0. Portanto, o peso na última camada será treinado lentamente se a ativação o neurônio de saída é pequeno (≈ 0) ou grande (≈ 1). Nesse caso, costuma-se dizer que o neurônio de saída está saturado e, como resultado, o peso deixou de ser treinado (ou está sendo treinado lentamente). As mesmas observações são válidas para deslocamentos do neurônio de saída.Idéias semelhantes podem ser obtidas com relação às camadas anteriores. Em particular, considera que o termo σ '(z l ) a (BP2). Isso significa que δ l jprovavelmente será pequeno à medida que o neurônio se aproxima da saturação. E isso, por sua vez, significa que quaisquer pesos na entrada de um neurônio saturado serão treinados lentamente (no entanto, isso não funcionará se w l + 1 T δ l + 1 tiver elementos suficientemente grandes que compensem o pequeno valor de σ '(z L j )).Para resumir: aprendemos que o peso será treinado lentamente se a ativação do neurônio de entrada for pequena ou o neurônio de saída estiver saturado, ou seja, sua ativação for pequena ou grande.Isto não é particularmente surpreendente. E, no entanto, essas observações ajudam a melhorar nossa compreensão do que acontece quando treinamos a rede. Além disso, podemos abordar esses argumentos do outro lado. As quatro equações fundamentais são válidas para qualquer função de ativação, e não apenas para o sigmóide padrão (pois, como veremos mais adiante, as propriedades não usam o sigmóide). Portanto, essas equações podem ser usadas para desenvolver funções de ativação com certas propriedades de aprendizado necessárias. Por exemplo, suponha que escolhemos uma função de ativação σ diferente de um sigmóide, de modo que σ 'seja sempre positivo e não se aproxime de zero. Isso evita a desaceleração do aprendizado que ocorre quando os neurônios sigmóides normais estão saturados. Posteriormente neste livro, veremos exemplos em que a função de ativação muda de maneira semelhante.Dadas as equações (BP1) - (BP4), podemos explicar por que essas modificações são necessárias e como elas podem afetar a situação.
:δL=Σ′(zL)∇aC
onde Σ '(z L ) é uma matriz quadrada com σ' (z L j ) localizada na diagonal e os outros elementos são 0. Observe que essa matriz interage com ∇ a C através da multiplicação usual da matriz.Mostre que (BP2) pode ser reescrito comoδ l = Σ ′ ( z l ) ( w l + 1 ) T δ l + 1
Combinando as tarefas anteriores, mostre que:δ l = Σ ' ( z l ) ( w l + 1 ) T ... Σ ' ( z L - 1 ) ( W L ) T Σ ' ( z L ) ∇ um C
Para leitores acostumados à multiplicação de matrizes, essa equação será mais fácil de entender do que (BP1) e (BP2). Eu me concentro em (BP1) e (BP2) porque essa abordagem é mais rápida de implementar numericamente. [aqui Σ não é a soma (∑), mas a capital σ (sigma) / aprox. perev. ]Prova das quatro equações fundamentais (seção opcional)
Agora provamos as quatro equações fundamentais (BP1) - (BP4). Todos eles são consequências da regra da cadeia (a regra de diferenciação de uma função complexa) da análise de funções de muitas variáveis. Se você conhece a regra da cadeia, recomendo tentar contar os derivados antes de continuar com a leitura.Vamos começar com a equação (BP1), o que nos dá uma expressão para o delta de erro de saída de L . Para provar isso, lembramos que, por definição:δ L j = ∂ C∂ z L j
Aplicando a regra da cadeia, reescrevemos as derivadas parciais por meio das derivadas parciais das ativações de saída:δ L j = ∑ k ∂ C∂ a L k ∂a L k∂ z L j
onde a soma passa por todos os neurônios k na camada de saída. Certamente, a ativação de saída a L k do neurônio n. K depende apenas da entrada ponderada z L j para o neurônio n. J quando k = j. Portanto, La L k / ∂z L j desaparece quando k ≠ j. Como resultado, simplificamos a equação anterior paraδ L j = ∂ C∂ a L j ∂a L j∂ z L j
Lembrando que a L j = σ (z L j ), podemos reescrever o segundo termo à direita como σ '(z L j ), e a equação se transforma emδ L j = ∂ C∂ a L j σ′(z L j )
isto é, em (BP1) em uma vista explodida.Então provamos (BP2), que fornece a equação para o erro δ l através do erro na próxima camada δ l + 1 . Para fazer isso, precisamos reescrever δ l j = ∂C / ∂z l j através de δ l + 1 k = ∂C / ∂z l + 1 k . Isso pode ser feito usando a regra da cadeia:δ l j = ∂ C∂ z l j
= ∑ k ∂ C∂ z l + 1 k ∂z l + 1 k∂ z l j
= ∑ k ∂ z l + 1 k∂ z L j δ l + 1 k
onde na última linha trocamos os dois termos à direita e substituímos a definição de δ l + 1 k . Para calcular o primeiro termo na última linha, observe quez l + 1 k = Σ J w l + 1 k j um l j + b l + 1 k = Σ J w l + 1 k j σ ( z L j ) + b l + 1 k
Diferenciando, temos∂ z l + 1 k∂ z l j =w l + 1 k j σ′(z l j ).
Substituindo isso em (42), obtemosδ l j = ∑ k w l + 1 k j δ l + 1 k σ ′ ( z l j ) .
Ou seja, (BP2) em uma entrada explodida.Resta provar (BP3) e (BP4). Eles também seguem a regra da cadeia, aproximadamente da mesma maneira que as duas anteriores. Vou deixá-los para você como um exercício.Exercício
Essa é toda a prova das quatro equações fundamentais de retropropagação. Pode parecer complicado. Mas, na realidade, isso é simplesmente o resultado da aplicação cuidadosa da regra da cadeia. Falando menos sucintamente, a propagação de retorno pode ser imaginada como uma maneira de calcular o gradiente da função de custo através da aplicação sistemática da regra da cadeia a partir da análise das funções de muitas variáveis. E isso é realmente tudo o que a distribuição de volta é - o resto são apenas detalhes.Algoritmo de retropropagação
As equações de retropropagação nos fornecem um método para calcular o gradiente da função de custo. Vamos escrever isso explicitamente como um algoritmo:- Entrada x: atribua a ativação apropriada a 1 para a camada de entrada.
- : l = 2,3,…,L z l = w l a l−1 +b l a l = σ(z l ).
- δ L : δ L = ∇ a C ⊙ σ'(z L ).
- : l = L−1,L−2,…,2 δ l = ((w l+1 ) T δ l+1 ) ⊙ σ'(z l ).
- : ∂C∂wljk=al−1kδlj e ∂C∂blj=δlj .
Observando o algoritmo, você entenderá por que é chamado de retropropagação. Nós calculamos os vetores de erro δ l para trás, começando na última camada. Pode parecer estranho estarmos retrocedendo na rede. Mas se você pensar na prova de propagação de retorno, o movimento inverso é uma conseqüência do fato de que o custo é uma função da saída da rede. Para entender como o custo varia com os pesos e compensações iniciais, precisamos aplicar a regra da cadeia repetidamente, retornando pelas camadas para obter expressões úteis.Exercícios
- . , , f(∑ j w j x j +b), f – , . ?
- . , σ(z) = z . .
Como expliquei anteriormente, o algoritmo de retropropagação calcula o gradiente da função de custo para um exemplo de treinamento, C = C x . Na prática, a propagação traseira é frequentemente combinada com um algoritmo de aprendizado, por exemplo, com descida estocástica do gradiente, quando calculamos o gradiente para muitos exemplos de treinamento. Em particular, para um determinado minipacote de m exemplos de treinamento, o algoritmo a seguir aplica descida gradiente com base nesse minipacote:- Entrada: um conjunto de exemplos de treinamento.
- Para cada exemplo de treinamento x, atribua a ativação de entrada correspondente a x, 1 e execute as seguintes etapas:
- l=2,3,…,L z x,l = w l a x,l−1 +b l a x,l = σ(z x,l ).
- δ x,L : δ x,L = ∇ a C x ⋅ σ'(z x,L ).
- : l=L−1,L−2,…,2 δ x,l = ((w l+1 ) T δ x,l+1 ) ⋅ σ'(z x,l ).
- : l=L,L−1,…,2 wl rightarrowwl− frac etam sumx deltax,l(ax,l−1)T e compensações de acordo com a regra bl rightarrowbl− frac etam sumx deltax,l .
Obviamente, para implementar a descida estocástica do gradiente na prática, você também precisará de um ciclo externo que gere mini pacotes de exemplos de treinamento e um ciclo externo que passe por várias épocas de treinamento. Por simplicidade, eu os omiti.
Código para distribuição posterior
Tendo entendido o lado abstrato da retropropagação, agora podemos entender o código usado no capítulo anterior que implementa a retropropagação. Lembre-se desse capítulo que o código estava contido nos métodos update_mini_batch e backprop da classe Network. O código para esses métodos é uma tradução direta do algoritmo descrito acima. Em particular, o método update_mini_batch atualiza os pesos e compensações da rede calculando o gradiente dos exemplos atuais de treinamento de mini_batch:
class Network(object): ... def update_mini_batch(self, mini_batch, eta): """ , -. mini_batch – (x, y), eta – .""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] for x, y in mini_batch: delta_nabla_b, delta_nabla_w = self.backprop(x, y) nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)] self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)]
A maior parte do trabalho é realizada pelas linhas delta_nabla_b, delta_nabla_w = self.backprop (x, y), usando o método backprop para calcular as derivadas parciais ∂C
x / ∂b
l j e ∂C
x / ∂w
l jk . O método backprop quase repete o algoritmo da seção anterior. Há uma pequena diferença - usamos uma abordagem ligeiramente diferente para a indexação de camadas. Isso é feito para tirar proveito do recurso python, índices de matriz negativos que permitem contar elementos ao contrário a partir do final. l [-3] será o terceiro elemento do final da matriz l. O código backprop é fornecido abaixo, juntamente com as funções auxiliares usadas para calcular o sigmóide, sua derivada e derivada da função de custo. Com eles, o código é completo e compreensível. Se algo não estiver claro, consulte a primeira descrição completa do código de listagem.
class Network(object): ... def backprop(self, x, y): """ ``(nabla_b, nabla_w)``, C_x. ``nabla_b`` ``nabla_w`` - numpy, ``self.biases`` and ``self.weights``.""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights]
Desafio
- Uma abordagem de retropropagação totalmente baseada em matriz no minipack. Nossa implementação de descida de gradiente estocástico usa uma série de exemplos de treinamento do minipacote. O algoritmo de retropropagação pode ser alterado para calcular gradientes para todos os exemplos de treinamento do minipacote ao mesmo tempo. Em vez de começar com um único vetor x, podemos começar com a matriz X = [x 1 x 2 ... x m ], cujas colunas são os vetores do minipack. A distribuição direta é através do produto de matrizes de peso, a adição de uma matriz adequada para deslocamentos e o uso generalizado de sigmóide. A propagação de retorno segue o mesmo padrão. Escreva pseudo-código para esta abordagem para o algoritmo de retropropagação. Modifique network.py para que ele use essa abordagem de matriz. A vantagem dessa abordagem será o uso de todas as vantagens das bibliotecas modernas para álgebra linear. Como resultado, ele pode ser executado mais rapidamente do que o ciclo de mini pacotes (por exemplo, no meu computador, o programa acelera cerca de 2 vezes nas tarefas de classificação do MNIST). Na prática, todas as bibliotecas sérias para distribuição retroativa usam uma abordagem de matriz completa ou alguma versão dela.
Em que sentido a propagação de retorno é um algoritmo rápido?
Em que sentido a propagação de retorno é um algoritmo rápido? Para responder a essa pergunta, considere outra abordagem para calcular o gradiente. Imagine os primeiros dias da pesquisa de redes neurais. Talvez seja nos anos 50 ou 60, e você é a primeira pessoa no mundo que teve a ideia de usar a descida gradiente para o treinamento! Mas, para que isso funcione, é necessário calcular o gradiente da função de custo. Você lembra de álgebra e decide se pode usar a regra da cadeia para calcular o gradiente. Depois de jogar um pouco, você vê que a álgebra parece difícil e está decepcionada. Você está tentando encontrar uma abordagem diferente. Você decide considerar o custo em função apenas dos pesos C = C (w) (retornaremos aos deslocamentos um pouco mais tarde). Você numera os pesos w
1 , w
2 , ... e deseja calcular ∂C / ∂w
j para o peso w
j . A maneira óbvia é usar a aproximação
frac parcialC parcialwj approx fracC(com epsilonej)−C(w) epsilon tag46
Onde ε> 0 é um pequeno número positivo e e
j é o vetor de direção da unidade j. Em outras palavras, podemos estimar aproximadamente ∂C / ∂w
j calculando o custo C para dois valores ligeiramente diferentes de wj e, em seguida, aplicando a equação (46). A mesma idéia nos permite calcular as derivadas parciais de ∂C / ∂b em relação aos deslocamentos.
A abordagem parece promissora. Conceitualmente simples, fácil de implementar, usa apenas algumas linhas de código. Parece muito mais promissor do que a idéia de usar uma regra de cadeia para calcular o gradiente!
Infelizmente, embora essa abordagem pareça promissora, quando implementada em código, ela funciona extremamente devagar. Para entender o porquê, imagine que temos um milhão de pesos na rede. Então, para cada peso w
j , precisamos calcular C (w + εe
j ) para calcular ∂C / ∂w
j . E isso significa que, para calcular o gradiente, precisamos calcular a função de custo um milhão de vezes, o que exigirá um milhão de passagens diretas pela rede (para cada exemplo de treinamento). E também precisamos calcular C (w), para obter um milhão e um de passagem pela rede.
O truque da retropropagação é que ela permite calcular simultaneamente todas as derivadas parciais ∂C / ∂w
j usando apenas uma passagem direta pela rede, seguida por uma passagem reversa. Grosso modo, o custo computacional do passe de retorno é aproximadamente o mesmo do custo direto.
Portanto, o custo total da propagação de retorno é aproximadamente o mesmo que o de duas passagens diretas de rede. Compare isso com o milhão e um passe direto necessário para implementar o método (46)! Portanto, embora a retropropagação pareça uma abordagem mais complicada, na realidade é muito mais rápida.
Pela primeira vez, essa aceleração foi totalmente apreciada em 1986, e isso expandiu drasticamente a gama de tarefas resolvidas com a ajuda de redes neurais. Por sua vez, isso levou a um aumento no número de pessoas que usam redes neurais. Obviamente, a retropropagação não é uma panacéia. Mesmo no final da década de 1980, as pessoas já encontravam suas limitações, principalmente quando tentavam usar a propagação traseira para treinar redes neurais profundas, ou seja, redes com muitas camadas ocultas. Mais adiante veremos como os computadores modernos e as novas idéias complicadas possibilitam o uso da retropropagação para treinar redes neurais profundas.
Distribuição reversa: em geral
Como já expliquei, a propagação traseira revela dois mistérios para nós. A primeira coisa que o algoritmo realmente faz? Nós desenvolvemos um esquema de propagação de retorno para o erro da saída. É possível ir mais fundo, obter uma idéia mais intuitiva do que acontece durante todas essas multiplicações de vetores e matrizes? O segundo enigma é como alguém detectou a propagação de volta? Uma coisa é seguir as etapas do algoritmo ou a prova de sua operação. Mas isso não significa que você entendeu o problema tão bem que poderia inventar esse algoritmo. Existe uma linha razoável de raciocínio que pode nos levar à descoberta do algoritmo de retropropagação? Nesta seção, abordarei os dois quebra-cabeças.
Para melhorar a compreensão da operação do algoritmo, imagine que fizemos uma pequena alteração Δw
l jk de um certo peso w
l jk :

Essa mudança de peso levará a uma alteração na ativação de saída do neurônio correspondente:

Isso levará a uma alteração em todas as ativações da próxima camada:

Essas alterações levarão a alterações na próxima camada, e assim por diante, até a última e, em seguida, a alterações na função de custo:

A mudança em ΔC está relacionada à mudança em Δw
l jk pela equação
DeltaC approx frac parcialC parcialwljk Deltawljk tag47
Segue-se que uma abordagem provável para o cálculo de ∂C / ∂w
l jk é monitorar cuidadosamente a propagação de uma pequena mudança w
l jk , levando a uma pequena mudança em C. Se pudermos fazer isso, expresse cuidadosamente ao longo do caminho tudo em quantidades fáceis de calcular , então podemos calcular ∂C / ∂w
l jk .
Vamos tentar. Uma mudança em Δw
l jk causa uma ligeira mudança em Δa
l j na ativação do neurônio j na camada l. Essa alteração está definida.
Deltaalj approx frac parcialalj parcialwljk Deltawljk tag48
A mudança na ativação Δa
l j leva a mudanças em todas as ativações da próxima camada (l + 1). Vamos nos concentrar apenas em uma dessas ativações alteradas, por exemplo, a
l + 1 q ,

Isso resultará nas seguintes alterações:
Deltaal+1q approx frac parcialal+1q parcialalj Deltaalj tag49
Substituindo a equação (48), obtemos:
Deltaal+1q approx frac parcialal+1q parcialalj frac parcialalj parcialwljk Deltawljk tag50
Obviamente, uma mudança em Δa
l + 1 q também mudará a ativação na próxima camada. Podemos até imaginar um caminho ao longo de toda a rede, de wl
jk a C, onde cada alteração na ativação leva a uma alteração na próxima ativação e, finalmente, a uma alteração no custo na saída. Se o caminho passa pelas ativações a
l j , a
l + 1 q , ..., a
L - 1 n , a
L m , a expressão final será
DeltaC aprox frac parcialC parcialaLm frac parcialaLm parcialaL−1n frac parcialaL−1n parcialaL−2p ldots frac parcialal+1q parcialalj frac parcialalj parcialwljk Deltawljk tag51
Ou seja, escolhemos um membro da forma ∂a / ∂a para cada um dos próximos neurônios que passarmos, bem como para o termo ∂C / ∂a Lm no final. Esta é uma representação das alterações em C devido a alterações nas ativações nesse caminho específico através da rede. Obviamente, existem muitas maneiras pelas quais uma mudança de comportamento pode afetar o custo, e consideramos apenas uma delas. Para calcular a alteração total em C, é razoável supor que devemos resumir todos os caminhos possíveis, do peso ao custo final:
DeltaC approx summnp ldotsq frac parcialC parcialaLm frac parcialaLm parcialaL−1n frac parcialaL−1n parcialaL−2p ldots frac parcialal+1q parcialalj frac parcialalj parcialwljk Deltawljk tag52
onde resumimos todas as opções possíveis para neurônios intermediários ao longo do caminho. Comparando isso com (47), vemos que:
frac parcialC parcialwljk= summnp ldotsq frac parcialC parcialaLm frac parcialaLm parcialaL−1n frac parcialaL−1n parcialaL−2p ldots frac pum r c i a l a l + 1, q p um r c i a l a l j f r um c p a r c i a l a l j p um r c i a l w l j k . t a g 53
A equação (53) parece complicada. No entanto, ele tem uma boa interpretação intuitiva. Contamos a alteração em C com relação aos pesos da rede. Ele nos diz que cada borda entre dois neurônios da rede está associada a um fator de proporção, que é apenas um derivado parcial da ativação de um neurônio em relação à ativação de outro neurônio. Para uma costela do primeiro peso ao primeiro neurônio, o fator de razão é ∂a
l j / ∂w
l jk . O coeficiente de proporção para o caminho é simplesmente o produto dos coeficientes ao longo do caminho. E o coeficiente total de mudança ∂C / ∂w
l jk é a soma dos coeficientes em todos os aspectos, desde o peso inicial até o custo final. Este procedimento é mostrado abaixo para um caminho:

Até agora, apresentamos um argumento heurístico, uma maneira de representar o que acontece quando os pesos da rede mudam. Deixe-me esboçar uma outra maneira de pensar sobre esse tópico para o desenvolvimento desse argumento. Primeiro, podemos derivar a expressão exata para todas as derivadas parciais individuais na equação (53). Isso é fácil de usar usando álgebra simples. Depois disso, você pode tentar entender como escrever todas as somas por índices na forma de produtos matriciais. Isso acaba sendo uma tarefa tediosa que requer paciência, mas não algo extraordinário. Depois de tudo isso e da simplificação máxima, você verá que é obtido exatamente o mesmo algoritmo de retropropagação! Portanto, o algoritmo de retropropagação pode ser imaginado como uma maneira de calcular a soma dos coeficientes para todos os caminhos. Ou, para reformular, o algoritmo de retropropagação é uma maneira complicada de rastrear pequenas alterações nos pesos (e compensações) quando elas se propagam pela rede, atingem uma saída e afetam os custos.
Aqui não farei tudo isso. Este negócio é pouco atraente, exigindo um estudo cuidadoso dos detalhes. Se você estiver pronto para isso, poderá fazer isso. Caso contrário, espero que esses pensamentos lhe dêem algumas idéias sobre os objetivos da retropropagação.
E quanto a outro enigma - como a propagação traseira poderia ser descoberta? De fato, se você seguir o caminho que descrevi, receberá evidências de propagação de retorno. Infelizmente, a prova será mais longa e mais complicada do que a que descrevi anteriormente. Então, como foi descoberta essa evidência curta (mas ainda mais misteriosa)? Se você anotar todos os detalhes de uma prova longa, notará imediatamente algumas simplificações óbvias. Você aplica simplificações, obtém provas mais simples e as anota. E então, novamente, você encontra algumas simplificações óbvias. E você repete o processo. Após várias repetições, a prova que vimos anteriormente será curta, mas um pouco incompreensível, pois todos os marcos foram removidos dela! Obviamente, sugiro que você aceite minha palavra, mas, de fato, não há mistério sobre a origem da evidência. Muito trabalho duro para simplificar a prova que descrevi nesta seção.
No entanto, há um truque inteligente nesse processo. Na equação (53), as variáveis intermediárias são ativações do tipo a
l + 1 q . O truque é mudar para o uso de entradas ponderadas, como z
l + 1 q , como variáveis intermediárias. Se você não usar isso e continuar usando ativações, as evidências obtidas serão um pouco mais complicadas do que as fornecidas anteriormente neste capítulo.