Study guide

Aula 7: PyTorch, o que a biblioteca automatiza

  • WhenSexta, 27/11/2026, das 12h às 14h
  • WhereOn-line, no encontro 2

Material de estudo para acompanhar os slides.

O objetivo da aula é saber dizer o que o PyTorch automatiza, o tensor e o autograd, reescrever a rede da aula 6 em PyTorch e conferir que o resultado é o mesmo, e ler a curva do erro final contra a taxa de aprendizado, com a fronteira quebrada perto do limiar.

Os tópicos

Sem horário marcado: o que não couber hoje entra na aula seguinte.

  1. Revisão das aulas 5 e 6
  2. O que o PyTorch automatiza
  3. A rede da aula 6 em PyTorch
  4. O erro final contra a taxa
  5. De onde vem
  6. O código
  7. Fechamento

Notação

Os nomes novos dos slides, na ordem em que aparecem; os das aulas anteriores estão nos roteiros delas.

Símbolo Lê-se Quer dizer Slide
shape\texttt{shape} shape, forma quantos números o tensor tem em cada eixo 5
.grad\texttt{.grad} ponto grad onde o autograd deixa a derivada da perda em relação a um tensor 6
η\eta eta a taxa de aprendizado, como desde a aula 1 14

No PyTorch, estas peças resolvem quase tudo:

  • torch.from_numpy(X): os dados da aula 6 como tensor, sem copiar os números.
  • x.requires_grad_(True) e L.backward(): pedir o registro das contas e calcular a derivada de L; o resultado fica em x.grad.
  • torch.nn.Sequential(torch.nn.Linear(784, 256), torch.nn.Tanh(), torch.nn.Linear(256, 10)): a rede da aula 6.
  • torch.nn.CrossEntropyLoss(): a softmax e a entropia cruzada, juntas.
  • opt.zero_grad(), perda.backward(), opt.step(): o laço de cada lote.
  • torch.autograd.grad(L, pesos): o gradiente devolvido, sem ir para .grad.
  • with torch.no_grad():: contas que não entram no grafo, como o passo feito à mão.

1. Revisão das aulas 5 e 6

Slide 1: A capa

A aula 7 troca o NumPy pelo PyTorch, a biblioteca de redes neurais que a pesquisa usa. A rede é a mesma da aula 6; a diferença é que a retropropagação, escrita à mão nas aulas 5 e 6, passa a ser feita pela biblioteca. Com o tempo que sobra, a aula olha para a taxa de aprendizado de perto: o erro final contra a taxa, que perto do limiar fica quebrado.

A imagem da capa é o diagrama de bifurcação da equação logística, x ← r·x·(1 − x), uma conta simples repetida muitas vezes. Para cada valor de r, no eixo horizontal, de 2,4 a 4, o diagrama marca os valores para onde a conta vai depois de muitas repetições. Até r = 3, ela vai para um número só; entre 3 e 3,44949, oscila entre dois; depois entre quatro, oito, e, a partir de r ≈ 3,56995, vira caos, com ilhas de ordem no meio. A ligação com a aula é uma analogia, e não uma conta: o gradiente descendente também é uma conta repetida, e a taxa de aprendizado faz o papel do r, decidindo se o treino converge, oscila ou se perde.

Diagrama de Jordan Pierce, de 2011, no Wikimedia Commons, com a licença CC0, de domínio público; recortado para tirar os eixos, que tinham ponto decimal.

Slide 2: Revisão, de memória

A revisão é de memória: quatro perguntas sobre as aulas 5 e 6.

O que a softmax faz com as dez saídas? Transforma dez números quaisquer em dez probabilidades, positivas e somando 1; a maior saída fica com a maior probabilidade.

Para que servem a validação e o teste? A validação escolhe o que o treino não ajusta, como a largura e a hora de parar; o teste mede a rede escolhida, uma vez, no fim.

O que é sobreajuste? A rede decora o treino e erra no dado novo: na aula 6, com mil exemplos, 100% no treino e 87,42% na validação.

Por que treinar em lotes? Porque um passo a cada 64 exemplos custa muito menos que um passo a cada 50 mil, e o gradiente de um lote aponta perto do gradiente de todos.

Slide 3: O fio do curso

O curso segue um fio, e este slide aparece em todas as aulas, com a aula do dia em destaque e, à direita, a data e o formato de cada aula já marcada.

Até a aula 6, toda derivada foi escrita à mão. Nesta aula, a 7, a biblioteca faz a conta, e a mesma rede da aula 6 dá o mesmo resultado, até o último algarismo. Com isso, o curso pode olhar para a taxa de aprendizado de perto. Na aula 8, um MLP vai aos dados do CERN; na 9, a taxa volta, uma por camada, e a fronteira entre o treino que converge e o que diverge vira o fractal do projeto, com a rede do artigo e os dados do CMS.

2. O que o PyTorch automatiza

Slide 4: E se a biblioteca fizesse a conta?

Nas aulas 5 e 6, a retropropagação foi escrita à mão: o δ da saída, o δ de cada neurônio oculto, o gradiente de cada peso. Na aula 6 são sete linhas de conta, e cada rede nova pediria outras, com outra conta. O PyTorch guarda as contas da passada para a frente e faz a de trás para a frente sozinho, para qualquer rede. A pergunta desta parte é o que exatamente ele automatiza, e se o resultado é o mesmo.

Slide 5: O tensor

O tensor é a estrutura de dados do PyTorch. Tensor, no curso, é uma tabela de números com qualquer número de eixos: um número sozinho tem zero eixos; um vetor, um; uma matriz, dois; um lote de imagens de 28 por 28, três. A forma, shape, diz quantos números há em cada eixo: os 50 mil dígitos de treino formam um tensor de forma (50000, 784). Na física, tensor quer dizer outra coisa, um objeto que muda de um jeito determinado quando se trocam as coordenadas; o curso não usa esse sentido.

As contas são as do NumPy: W @ x multiplica matriz por vetor, torch.tanh vale para cada posição, e torch.from_numpy(X) transforma um array do NumPy num tensor sem copiar os números. A diferença está no que o tensor guarda a mais: se ele pede requires_grad, cada conta feita com ele fica registrada, para a derivada vir depois.

Slide 6: O autograd refaz a conta da aula 4

Autograd, no PyTorch, é a diferenciação automática: a biblioteca registra cada conta da passada para a frente e, quando se chama backward(), aplica a regra da cadeia de trás para a frente, o modo reverso da aula 4, e deixa em cada tensor a derivada da perda em relação a ele, no campo .grad.

O teste é a rede do exemplo da aula 4, com os mesmos números: duas camadas com a tanh, a saída linear e a perda L = (y − t)². Na aula 4, o gradiente da perda em relação à entrada saiu como produto de Jacobianos, linha vezes matriz, de trás para a frente: (1,0940; −4,6074). Com o autograd, x.requires_grad_(True) pede o registro, a passada para a frente se escreve como antes, e L.backward() faz o resto. O resultado é (1,0940; −4,6074), com diferença zero para a conta da aula 4: as duas fazem as mesmas multiplicações, na mesma ordem, em float64, a precisão de 16 algarismos do NumPy.

Slide 7: O grafo das contas

Como o autograd faz isso: na passada para a frente, cada conta vira um nó de um grafo, com setas para as contas de que ela depende. Grafo, no curso, é esse desenho das contas, nós ligados por setas; na matemática, grafo é o nome de qualquer conjunto de pontos ligados por linhas.

Na rede do exemplo, x⃗\vec{x} entra na primeira camada, sai h⃗\vec{h}₁, entra na segunda, sai h⃗\vec{h}₂, vira y e vira L. Quando se chama backward(), o PyTorch percorre o grafo de L para trás: em cada nó, multiplica a derivada que chegou pela derivada da própria conta, o Jacobiano da aula 4, e passa adiante. É a conta de trás para a frente, uma linha vezes uma matriz por camada, que a aula 4 mostrou ser a mais barata quando a saída é um número só, a perda.

O grafo se monta de novo a cada passada para a frente; por isso a rede pode ser escrita como um programa comum, com laços e condições, e o autograd segue o caminho que as contas fizeram.

Slide 8: O grafo com números, em vídeo

O vídeo mostra o grafo do slide anterior com os números da rede da aula 4, no ponto x⃗\vec{x} = (1; 0,25). Grafo, no curso, é o desenho das contas: cada conta um nó, ligado por setas às contas de que depende.

Para a frente, em azul, o PyTorch faz as contas e guarda cada valor: h⃗\vec{h}₁ = (0,6351; 0), h⃗\vec{h}₂ = (0,8539; −0,1343), y = 0,9882 e a perda L = 0,9766, a mesma da aula 4. Em cima de cada seta, a conta que leva de um nó ao outro.

Para trás, em laranja, L.backward() começa pela derivada da perda por ela mesma, 1, e volta de nó em nó: por y, 2(y − t) = 1,9764; por h⃗\vec{h}₂, a linha (1,9764; −1,9764), que é 1,9764 vezes v⃗\vec{v} = (1; −1); por h⃗\vec{h}₁, (3,0115; −1,4054); e pela entrada, (1,0940; −4,6074), o gradiente que a aula 4 calculou à mão, de trás para a frente. Em cada passo, a linha que chega vezes o Jacobiano da conta, como no slide do grafo.

No código, a derivada fica em x.grad depois do backward(). O PyTorch guarda a derivada só dos tensores criados com requires_grad; para mostrar a dos nós do meio, o programa pede retain_grad() a cada um.

3. A rede da aula 6 em PyTorch

Slide 9: A rede da aula 6 em PyTorch

A rede da aula 6 em PyTorch. nn.Linear(784, 256) é a camada W x⃗\vec{x} + b⃗\vec{b}, com os pesos e os vieses dentro dela; nn.Tanh() é a ativação; a segunda nn.Linear dá as dez saídas. nn.Sequential encadeia as três. A softmax não aparece: ela vai junto com a entropia cruzada em nn.CrossEntropyLoss, que recebe as dez saídas e o rótulo.

O laço do treino tem quatro linhas por lote: zero_grad() apaga os gradientes do lote anterior, que o PyTorch somaria aos novos; a passada para a frente calcula a perda; backward() calcula o gradiente de todos os pesos; e step() dá o passo, w ← w − η ∂L/∂w, para cada peso. O otimizador, torch.optim.SGD, é o gradiente descendente da aula 2, com a taxa 0,1 da aula 6.

As sete linhas da retropropagação da aula 6 viram uma: perda.backward().

Slide 10: A mesma conta, mais depressa

O teste da troca: a rede em PyTorch começa com os pesos que o código da aula 6 sorteia, percorre os lotes na mesma ordem, com a mesma taxa, por 20 épocas, e em float64. As duas curvas da acurácia na validação caem uma em cima da outra: a diferença é zero em todas as épocas, e o teste dá os mesmos 97,59%. O PyTorch não aprende diferente; ele faz a mesma conta.

O que muda é o tempo e o trabalho. No laptop do professor, cada época levou 0,5 s no PyTorch, contra 5,1 s no NumPy da aula 6, na última vez que o mlp.py rodou; o tempo do NumPy já foi 7,2 s, com o computador mais ocupado. A biblioteca usa rotinas de multiplicação de matrizes mais rápidas. Num laptop mais fraco os dois números crescem, mas a proporção deve se manter perto disso; isso é dedução, não medida.

Na figura, o NumPy em círculos azuis vazados, o PyTorch em xis laranja, por cima.

Slide 11: O Adam, como caixa-preta

O Adam é outro jeito de dar o passo. Fica como caixa-preta: o que entra é o gradiente de cada peso, o que sai é o passo de cada peso, e ele ajusta a taxa de cada peso ao longo do treino, em vez de usar uma só para todos. Não se abre agora porque pede médias móveis do gradiente e do quadrado dele; o algoritmo é o quadro Algorithm 1 do artigo de Kingma e Ba, que está no estudo da semana.

Na rede da aula, com a taxa padrão do Adam no PyTorch, 0,001, a acurácia na validação sai de 93,6% já na primeira época, contra 90,0% do gradiente descendente com taxa 0,1. Nas 20 épocas, os dois terminam perto: 97,16% com o Adam e 97,33% sem ele, na validação; no teste, 97,42% e 97,59%. O Adam chega antes, e não necessariamente mais longe; com outra taxa, o resultado seria outro.

Slide 12: Dois jeitos de dar o passo, em vídeo

O vídeo compara dois jeitos de dar o passo numa tigela alongada, L(a, b) = (a² + 25 b²)/2: rasa na direção a e 25 vezes mais íngreme na direção b. Os dois caminhos saem do mesmo ponto, (−4; 1,2), e dão 40 passos.

Em cima, o gradiente descendente, com η = 0,075, perto da maior taxa que a direção íngreme aguenta, 2/25 = 0,08. O passo é η vezes a inclinação: grande em b, onde a tigela é íngreme, e pequeno em a. Em b, ele passa do fundo e volta, e o caminho ziguezagueia: os primeiros pontos são (−3,70; −1,05), (−3,42; 0,92) e (−3,17; −0,80). Em a, anda devagar.

Embaixo, o Adam, com taxa 0,3 e os valores de fábrica do PyTorch para as duas médias, 0,9 e 0,999. O passo de cada peso é dividido por uma média do tamanho do gradiente daquele peso, e sai de tamanho parecido nas duas direções: (−3,70; 0,90), (−3,40; 0,61), (−3,10; 0,32), uma diagonal. Perto do fundo, ele passa e faz uma curva, porque a média do gradiente, o momento, carrega o passo anterior.

É isso que a caixa-preta do slide anterior quer dizer com a taxa ajustada peso a peso. O Adam não é sempre melhor: nesta tigela, depois de 40 passos, o gradiente descendente chega mais perto do fundo, perda 0,016 contra 0,064; no MNIST, o Adam começou na frente e terminou junto. Os números saem de caminhos(), em codigo/animacoes.py.

4. O erro final contra a taxa

Slide 13: O limiar da aula 2, numa rede de verdade

Na aula 2, a reta treinada pelo gradiente descendente tinha um limiar: abaixo de uma taxa, o treino convergia; acima, divergia. A pergunta agora é o que acontece com esse limiar numa rede de verdade, com camadas e a tanh. A resposta das próximas figuras é que ele deixa de ser um ponto.

Slide 14: O erro final contra a taxa

A rede pequena da aula: 784 entradas, 16 neurônios com a tanh e 10 saídas, treinada nos 256 primeiros exemplos do treino do MNIST, com todos os exemplos de uma vez, sem lotes, por 200 passos. Sem lotes, nada é sorteado durante o treino, e a mesma taxa dá sempre o mesmo resultado. Os pesos começam da semente 0.

Cada ponto é uma rede treinada com uma taxa, de 0,01 a 100, e a altura é o erro final, a perda depois do último passo, nos dois eixos em escala logarítmica. Com taxas pequenas, o treino anda pouco em 200 passos; o erro final cai com a taxa até o menor valor, 0,0070, com η = 2,37. Depois, sobe depressa, e a partir de η = 4,47 o treino deixa a rede pior do que estava antes, acima da linha tracejada, a perda de antes do treino, 2,3220. Com taxas perto de 100, a perda passa de 1.000, e o programa marca a rede como explodida.

A melhor taxa fica logo antes da região em que o treino piora: quem procura a taxa mais rápida anda perto da borda.

Slide 15: Perto do limiar, de perto

O vídeo refaz a figura anterior ponto a ponto, da taxa menor para a maior: a curva desce, chega ao mínimo e sobe. Depois, a janela de 2 a 10 aparece em laranja, e o vídeo olha para ela de perto, com 601 taxas.

De perto, os pontos não formam uma curva. Taxas vizinhas dão erros finais muito diferentes, e embaixo uma faixa mostra, para cada taxa, se o treino melhorou a rede, em azul, ou piorou, em laranja. A troca de cor não acontece num ponto só: ela vai e volta.

Slide 16: A fronteira quebrada

A figura do zoom, parada. Em cima, o erro final das 601 taxas de 2 a 10; embaixo, a faixa, uma listra por taxa. Das 601, 383 pioram a rede, e a resposta, melhora ou piora, troca 77 vezes entre taxas vizinhas. Abaixo de 3,86, tudo melhora; acima de 7,55, tudo piora; no meio, as duas cores se misturam.

Fronteira, aqui, é o conjunto de taxas em que a resposta troca. Na aula 2, a fronteira era um ponto, o limiar. Nesta rede, ela é quebrada, e a causa está no treino de cada taxa da faixa: a perda salta para dezenas nos primeiros passos e fica oscilando, sem se assentar, e o passo 200 pega umas taxas com a perda abaixo da de antes e as vizinhas acima. A aula 9 mostra essas curvas e põe esta rede no plano de duas taxas, uma por camada; o fractal do curso é a fronteira da rede do projeto, a do artigo, com os dados do CMS.

5. De onde vem

Slide 17: De onde vem

Quatro datas.

1970: Seppo Linnainmaa descreve o modo reverso da diferenciação automática, a conta de trás para a frente que o autograd faz, como estava nas aulas 4 e 5.

2002: Ronan Collobert, Samy Bengio e Johnny Mariéthoz publicam o Torch, uma biblioteca de aprendizado de máquina do Instituto de Pesquisa Idiap, na Suíça, escrita em C, com a linguagem Lua por cima.

2016: sai a primeira versão do PyTorch, em setembro, o Torch levado para o Python. A página do PyTorch na Wikipedia dá como autores Gregory Chanan, Soumith Chintala, Sam Gross e Adam Paszke, e como desenvolvedora a Meta AI; hoje o desenvolvimento tem apoio da Linux Foundation.

2017: o desenvolvimento do Torch passa para o PyTorch.

Os anos de 2002, 2016 e 2017 e os nomes saem da Wikipedia em inglês, verbetes "Torch (machine learning)" e "PyTorch"; o de 1970, do verbete "Backpropagation", conferido na aula 5.

6. O código

Slide 18: O código

O código da aula fica pronto e testado antes. codigo/autograd_exemplo.py refaz o gradiente da aula 4 com o autograd. codigo/mlp_torch.py é a solução de referência da entrega, que não vai para a turma antes do prazo: a rede da aula 6 em PyTorch, com os mesmos pesos iniciais e a mesma ordem dos lotes, e o Adam ao lado. codigo/taxa.py faz as duas varreduras da taxa e grava os números; a figura e o vídeo leem dali.

Quatro peças do PyTorch resolvem quase tudo. torch.from_numpy(X) transforma os dados da aula 6 em tensores. nn.Sequential(nn.Linear(784, 256), nn.Tanh(), nn.Linear(256, 10)) monta a rede. perda.backward() calcula o gradiente de todos os pesos. E torch.autograd.grad(L, pesos) devolve o gradiente sem guardá-lo nos pesos, como na varredura da taxa, em que cada passo é escrito à mão: w ← w − η ∂L/∂w.

Tempos no laptop do professor: mlp_torch.py, cerca de 30 segundos; taxa.py, cerca de 50.

Slide 19: O que a entrega pede

A entrega tem duas partes. A primeira é o MNIST em PyTorch: a rede da entrega da aula 6 reescrita com nn.Sequential, CrossEntropyLoss e o laço de quatro linhas, com a acurácia no teste, a semente e a versão dos dados. Quem começar dos mesmos pesos e da mesma ordem dos lotes deve achar a mesma acurácia da aula 6; quem não, um número perto.

A segunda é a curva do erro final contra a taxa, na rede pequena da aula, sem lotes, a mesma que a aula 9 leva ao plano das duas taxas, com uma figura como a da aula. Ela roda em menos de um minuto em qualquer laptop. Cada um escolhe a faixa de taxas; o que se pede é apontar a melhor taxa e a partir de onde o treino piora a rede. A mesma curva no MNIST inteiro, com lotes, é um extra opcional, para quem quiser.

O que se exige é saber dizer o que entra na rede, o que ela aprende e o que significa o número que sai.

7. Fechamento

Slide 20: Para estudar e entregar

O estudo prepara a aula 8, os dados do CERN. O registro 303 do portal de dados abertos do CERN, "Events with two muons from 2010", do CMS, é o conjunto que o projeto usa: cem mil eventos com dois múons. E os dois artigos de 1974 sobre a descoberta do J/psi, de Aubert e colegas e de Augustin e colegas, que saíram no mesmo número da Physical Review Letters, com três páginas cada: a partícula que aparece como o primeiro pico no histograma da aula 8.

A entrega: o MNIST em PyTorch, com a acurácia no teste, a semente e a versão; a curva do erro final contra a taxa; e uma pergunta sobre o estudo.

A seguir, a aula 8 leva um MLP aos dados do CERN, para estimar a massa dos dois múons e conferir pela fórmula; a aula 9 desenha a fronteira desta aula em duas dimensões.

Perguntas que podem aparecer

  • Se o PyTorch faz a conta, por que escrever a retropropagação à mão nas aulas 5 e 6? Para saber o que a biblioteca faz: quando o treino dá errado, quem sabe a conta sabe onde olhar. E a aula 7 mostra que o resultado é o mesmo.
  • Por que zero_grad()? Porque o PyTorch soma o gradiente novo ao que já está em .grad. Sem zerar, cada passo usaria a soma dos gradientes de todos os lotes.
  • O Adam é sempre melhor? Não. Nesta rede ele chega antes, mas termina perto do gradiente descendente; a taxa dele também precisa ser escolhida pela validação.
  • A fronteira quebrada é um defeito do computador? Não: as contas são em float64, e a mesma taxa dá sempre o mesmo resultado. A irregularidade vem do treino repetido, como na equação logística da capa.

Para estudar

O estudo da turma, que é também o seu:

Além dele, para preparar a aula:

Fontes dos fatos citados

Só para conferência; o estudo é a lista de cima.

  • Verbete "PyTorch" da Wikipedia em inglês, revisão 1378376798: a primeira versão em setembro de 2016, os autores, a Meta AI como desenvolvedora, o apoio da Linux Foundation e o Torch como antecessor.
  • Verbete "Torch (machine learning)" da Wikipedia em inglês, revisão 1364448378: Collobert, Bengio e Mariéthoz, outubro de 2002, o Idiap, C e Lua, e a passagem do desenvolvimento para o PyTorch em 2017.
  • Verbete "Logistic map" da Wikipedia em inglês, revisão 1382744518: as oscilações entre dois valores de r = 3 a 3,44949, entre quatro depois disso, o começo do caos em r ≈ 3,56995 e as ilhas de estabilidade.
  • Wikimedia Commons, a página do arquivo "Logistic Bifurcation map High Resolution.png": Jordan Pierce, 12/09/2011, trabalho próprio, CC0.
  • A taxa padrão do Adam no PyTorch, 0,001: a assinatura de torch.optim.Adam na versão 2.15.0.
  • Os números: codigo/autograd_exemplo.py; codigo/mlp_torch.py, com a semente 1 e a versão 227111746c19 dos dados; codigo/taxa.py, com a semente 0; gravados em resultados/.