Voltar ao catálogo
Season 55 17 Episódios 1h 3m 2026

PyTorch Fundamentals

v2.11 — Edição de 2026. Um curso em áudio abrangente sobre a construção de modelos de deep learning utilizando o PyTorch versão 2.11. Abrange Tensors, Autograd, Neural Networks, Optimizers, DataLoaders e o compilador do PyTorch.

Frameworks de AI/ML Python Core Ciência de Dados
PyTorch Fundamentals
A Reproduzir
Click play to start
0:00
0:00
1
A Identidade Central do PyTorch
Descubra o propósito fundamental do PyTorch e o que o distingue das bibliotecas matemáticas tradicionais. Este episódio explica o papel dos Tensors, do Autograd e da aceleração por GPU no deep learning moderno.
3m 44s
2
Compreender os Tensors do PyTorch
Mergulhe nos Tensors, a estrutura de dados fundamental do PyTorch. Aprenda como fazem a ponte entre dados brutos e Neural Networks e partilham memória de forma contínua com arrays Numpy.
4m 17s
3
Operações com Tensors e Memória
Aprenda a manipular Tensors de forma eficiente. Este episódio abrange operações aritméticas, concatenação, transferências entre dispositivos e as implicações de memória das operações in-place.
3m 26s
4
A Magia do Autograd
Desvende o motor que torna o deep learning possível no PyTorch. Aprenda como o Autograd rastreia operações dinamicamente e calcula derivadas complexas de forma automática.
3m 13s
5
Controlar o Rastreio de Gradientes
Descubra como desativar o rastreio de gradientes do PyTorch para poupar memória e acelerar os cálculos. Essencial para executar inferências e congelar parâmetros do modelo.
3m 57s
6
Datasets e Manipulação de Dados
Aprenda a separar o processamento de dados da arquitetura do seu modelo utilizando a classe Dataset do PyTorch. Exploramos o lazy loading e estruturas de datasets personalizadas.
3m 44s
7
DataLoaders e Batching
Liberte toda a velocidade do seu hardware envolvendo Datasets em DataLoaders. Aprenda a fazer batch, shuffle e multiprocessamento dos seus fluxos de dados.
3m 34s
8
Transformações de Dados
Descubra como pré-processar dados brutos em tempo real antes de chegarem à sua Neural Network. Abordamos transforms do torchvision como ToTensor e funções Lambda personalizadas.
3m 53s
9
Desenhar Redes com nn.Module
Explore o modelo estrutural de todas as Neural Networks do PyTorch. Aprenda a criar subclasses de nn.Module, a definir camadas na inicialização e a encaminhar dados no forward pass.
3m 40s
10
Camadas Lineares e Ativações
Olhe para o interior da Neural Network. Desconstruímos o módulo nn.Linear e explicamos por que razão as funções de ativação não-lineares como a ReLU são matematicamente essenciais.
4m 09s
11
O Contentor nn.Sequential
Otimize o seu código PyTorch utilizando o contentor nn.Sequential. Aprenda a encaixar camadas de forma limpa e a inspecionar os parâmetros do seu modelo.
4m 01s
12
Compreender as Loss Functions
Antes que uma IA possa aprender, tem de medir os seus erros. Mergulhamos nas Loss Functions do PyTorch, comparando a CrossEntropyLoss para classificação e a MSELoss para regressão.
3m 20s
13
Optimizers e Gradient Descent
Explore como o Optimizer atualiza os pesos do modelo para reduzir o erro. Aprenda a dança crucial de três passos: zero_grad(), backward() e step().
3m 26s
15
Validação e Inferência
Avalie o seu modelo de forma objetiva. Aprenda a mudar a sua rede para o modo de avaliação, a congelar gradientes e a extrair previsões precisas em dados não vistos.
3m 45s
16
Guardar e Carregar Modelos
Não perca o progresso que tanto lhe custou a alcançar! Discutimos as formas mais seguras de serializar os pesos do seu modelo utilizando o state_dict e de os carregar novamente com segurança.
3m 36s
17
Aumentar a Velocidade com torch.compile
Desbloqueie a funcionalidade de destaque do PyTorch 2.0. Aprenda como o decorador torch.compile faz o JIT-compile do seu código Python em kernels otimizados para aumentos massivos de velocidade.
3m 26s
18
Compiladores e Graph Breaks
Mergulhe no interior do compilador do PyTorch. Exploramos graph breaks, fluxo de controlo dinâmico e por que razão o torch.compile tem sucesso onde os sistemas antigos falharam.
3m 57s

Episódios

1

A Identidade Central do PyTorch

3m 44s

Descubra o propósito fundamental do PyTorch e o que o distingue das bibliotecas matemáticas tradicionais. Este episódio explica o papel dos Tensors, do Autograd e da aceleração por GPU no deep learning moderno.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 1 de 18. Escreves um modelo matemático complexo em Python, mas quando o escalas, sobrecarrega completamente o teu processador. Precisas de correr esses cálculos em hardware paralelo e calcular continuamente todas as suas derivadas, mas reescrever tudo numa linguagem de baixo nível demoraria semanas. Essa tensão é resolvida pela identidade central do PyTorch. Quando olhas para o PyTorch pela primeira vez, muitas vezes parece exatamente igual ao NumPy. Crias arrays, multiplicas matrizes e manipulas números. Esta semelhança visual causa muita confusão inicial. As pessoas assumem que o PyTorch é apenas mais uma library matemática standard. Não é. Enquanto as libraries matemáticas normais são construídas para computação numérica limitada pelo CPU, o PyTorch foi desenhado de raiz para tirar partido de hardware paralelo e construir grafos computacionais dinâmicos. O bloco de construção fundamental desta framework é o tensor. Um tensor é essencialmente um array multidimensional. Se tiveres uma grelha de números a representar uma imagem, uma onda sonora ou um bloco de texto, guardas isso num tensor. A diferença crucial entre um array normal e um tensor do PyTorch é onde esses dados podem viver e ser executados. Os tensores podem mover-se de forma transparente da memória do teu computador para uma Graphics Processing Unit. Pensa numa multiplicação de matrizes massiva. Tens duas grelhas com milhões de números. Se pedires a um CPU normal para as multiplicar, ele processa a matemática de forma sequencial ou em batches muito pequenos. O processo engasga-se e bloqueia. Como os tensores são desenhados explicitamente para aceleração de hardware, podes enviar exatamente os mesmos dados para um GPU. O GPU contém milhares de pequenos cores desenhados para executar operações matemáticas em simultâneo. Uma computação massiva que demora minutos num CPU termina instantaneamente num GPU. O PyTorch atua como a ponte, traduzindo o teu código Python normal em instruções para esse hardware paralelo. Hardware rápido é apenas metade do requisito para machine learning. Treinar uma rede neuronal requer cálculo contínuo. Precisas de saber exatamente como ajustar uma variável altera o teu output final, o que significa calcular gradientes constantemente. Fazer isto manualmente para um modelo com milhares de milhões de parâmetros é impossível. Isto leva-nos ao segundo pilar do PyTorch, que é o Autograd. O Autograd é um motor de diferenciação automática. Quando realizas operações matemáticas em tensores, o PyTorch não calcula apenas o número final. Ele constrói silenciosamente um mapa em background. Regista cada adição, multiplicação e transformação de dados num grafo computacional dinâmico. Quando chegas ao fim do teu cálculo, simplesmente pedes à framework para computar os gradientes. O PyTorch percorre esse grafo invisível de trás para a frente, aplicando automaticamente a regra da cadeia do cálculo. Recebes as derivadas exatas para cada parâmetro do teu modelo sem escreveres qualquer código de cálculo tu mesmo. Como este grafo é construído dinamicamente on the fly, ele adapta-se ao teu código. Se um loop normal de Python ou um if-statement alterar o fluxo dos teus dados, o grafo ajusta-se imediatamente. O verdadeiro poder do PyTorch não é apenas correr rápido ou fazer cálculo. Dá-te a velocidade de execução de um supercomputador e o rigor matemático de um motor de cálculo automatizado, totalmente escondido atrás de Python legível e comum. Se quiseres ajudar a manter estes episódios a sair, podes procurar por DevStoriesEU no Patreon e apoiar o programa. É tudo para este episódio. Obrigado por ouvires, e continua a construir!
2

Compreender os Tensors do PyTorch

4m 17s

Mergulhe nos Tensors, a estrutura de dados fundamental do PyTorch. Aprenda como fazem a ponte entre dados brutos e Neural Networks e partilham memória de forma contínua com arrays Numpy.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 2 de 18. Cada imagem, onda sonora e documento de texto que alimentas numa rede neural acaba por se transformar exatamente na mesma estrutura de dados. Se tudo é apenas uma grelha de números, podes perguntar-te porque é que precisamos de um objeto especializado em vez de depender de arrays de programação standard. A resposta está em compreender os tensors do PyTorch. Um tensor é uma estrutura de dados especializada que se parece e age de forma muito semelhante a um array ou a uma matriz. No PyTorch, os tensors são a moeda universal. Eles armazenam os teus inputs brutos, os outputs que o teu modelo gera e os parâmetros internos da própria rede neural. As pessoas costumam assumir que os tensors são completamente idênticos aos arrays do NumPy. Eles de facto parecem semelhantes e partilham muitos dos mesmos comportamentos. A diferença crucial está naquilo que os tensors desbloqueiam. Enquanto um array standard reside na memória principal do teu sistema e corre no teu processador central, um tensor é concebido para ser facilmente transferido para uma unidade de processamento gráfico, ou GPU, para uma aceleração de hardware massiva. Os tensors também contêm a infraestrutura embutida necessária para o gradient tracking, o que permite que as redes neurais aprendam. Podes inicializar um tensor de várias maneiras. A rota mais direta é passar dados brutos, como uma lista de números standard de Python, diretamente para o construtor do tensor. Também podes criar um novo tensor com base num já existente. Quando fazes isto, o novo tensor herda automaticamente as propriedades do original, o que significa que terá as mesmas dimensões e data type, a menos que faças um override explícito. Em alternativa, se precisares apenas de um placeholder, podes definir um shape, que é uma simples coleção de números que representa as dimensões que queres, e pedir ao PyTorch para gerar um tensor preenchido com números aleatórios, só com uns, ou só com zeros com base nesse shape. Assim que tiveres um tensor, vais verificar frequentemente três atributos principais. O primeiro é o shape, que te diz o tamanho exato do tensor ao longo de cada dimensão. O segundo é o data type, que indica o tipo de números lá armazenados, como floats de 32 bits ou integers. O terceiro é o atributo device. Isto diz-te onde o tensor reside fisicamente neste momento, seja no CPU ou numa GPU específica. Precisas de acompanhar isto porque o PyTorch exige que os tensors estejam no mesmo device antes de poderem interagir. Tensors e arrays standard frequentemente precisam de trabalhar juntos, o que nos leva à ponte do NumPy. Tensors que residem no CPU podem, na verdade, partilhar a sua memória subjacente com um array do NumPy. Imagina que carregas uma fotografia de alta resolução usando uma library standard de processamento de imagem em Python. Essa imagem é carregada para a memória do teu sistema como um array standard do NumPy. Podes passar esse array para o PyTorch usando uma função dedicada que cria um tensor a partir do NumPy. O PyTorch não duplica os dados de píxeis subjacentes para um novo bloco de memória. Ele simplesmente faz wrap da sua própria interface de tensor à volta do endereço de memória existente. Alterar um valor no tensor altera imediatamente o valor no array do NumPy, e vice-versa. Esta conversão zero-copy poupa tanto memória como tempo de processamento. Quando terminares de passar os dados pelo teu modelo e precisares de devolver os resultados a uma tool de visualização standard, chamas um único método no tensor para o expor novamente como um array do NumPy, usando exatamente a mesma memória partilhada. O verdadeiro poder de um tensor não é apenas armazenar uma grelha de números, mas sim carregar o contexto de hardware específico e a estrutura de memória necessários para passar dados brutos através de uma rede neural sem atrito. Obrigado por ouvirem, happy coding a todos!
3

Operações com Tensors e Memória

3m 26s

Aprenda a manipular Tensors de forma eficiente. Este episódio abrange operações aritméticas, concatenação, transferências entre dispositivos e as implicações de memória das operações in-place.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 3 de 18. Um simples underscore no teu código pode poupar gigabytes de memória, mas também pode quebrar silenciosamente toda a tua rede neuronal. Saber quando usar esse underscore resume-se a compreender Tensor Operations e memória. Considera um cenário prático. Tens três feature vectors separados a representar dados de texto, áudio e imagem. Queres combiná-los e multiplicá-los por uma weight matrix. Por defeito, o PyTorch cria tensores no CPU. Mas para matemática pesada de matrizes, queres usar um hardware accelerator. Podes verificar se uma GPU está disponível usando as verificações built-in da framework. Se estiver, moves os teus tensores chamando o método to sobre eles. Passas o nome do device de destino, como a string cuda, para este método. O PyTorch depois copia o tensor da RAM do sistema para a memória dedicada da tua placa gráfica. Com os teus tensores no hardware certo, precisas de combinar os três feature vectors separados num só. Fazes isso usando a função concatenate, geralmente escrita como cat. Passas-lhe uma lista dos teus tensores e especificas uma dimensão. Se os combinares ao longo da dimensão da coluna, os teus três tensores estreitos são unidos lado a lado para formar um tensor mais largo. Agora tens um input unificado a residir na memória da GPU. O PyTorch lida com mais de cem operações diferentes, mas a aritmética é a base. Para processar o teu feature vector combinado, precisas de o multiplicar por uma weight matrix. Podes usar o método matmul, ou simplesmente usar o símbolo arroba como uma abreviatura conveniente. Isto realiza uma verdadeira multiplicação matemática de matrizes, a calcular os dot products de linhas e colunas, e devolve um tensor completamente novo a conter os resultados. Às vezes, precisas de matemática element-wise. Supõe que queres aplicar uma binary mask ao teu tensor, a forçar certos valores a zero. Para isto, usas o método mul, ou o operador asterisco standard. Isto não faz multiplicação de matrizes. Simplesmente multiplica o primeiro elemento do tensor A pelo primeiro elemento do tensor B, o segundo pelo segundo, e por aí em diante. Cada vez que corres operações como multiplicação de matrizes ou adição element-wise, o PyTorch aloca memória nova para o resultado. Quando operas com milhões de parâmetros, isto consome rapidamente a memória disponível do teu hardware. É aqui que tens de prestar atenção. O PyTorch fornece operações in-place para gerir o memory overhead. Qualquer operação que termine com um underscore opera in-place. Se usares o método add standard, obténs um novo tensor. Se usares o método add com um underscore, o PyTorch sobrescreve diretamente os valores dentro do tensor existente. O memory footprint permanece exatamente o mesmo. Embora as operações in-place sejam altamente eficientes para a memória, também são perigosas. Quando sobrescreves um tensor, apagas os seus valores anteriores. As redes neuronais dependem de um registo completo dos estados passados para calcular derivadas durante a fase de aprendizagem. Se sobrescreveres um tensor usando uma operação in-place, destróis o histórico de computação de que o sistema precisa para atualizar o modelo. Reserva as operações in-place para formatar dados antes de entrarem no teu modelo, e mantém-te nas operações standard durante o treino para manter o teu histórico de computação intacto. É tudo por este episódio. Obrigado por ouvires, e continua a desenvolver!
4

A Magia do Autograd

3m 13s

Desvende o motor que torna o deep learning possível no PyTorch. Aprenda como o Autograd rastreia operações dinamicamente e calcula derivadas complexas de forma automática.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 4 de 18. Treinar uma rede neuronal significa calcular a derivada do teu erro em relação a milhões de parâmetros. Fazer isso à mão exigiria páginas de cálculo e reescritas constantes sempre que mudasses a arquitetura do teu modelo. O PyTorch resolve isto monitorizando a tua matemática em background, um conceito conhecido como a Magia do autograd. O autograd é o motor de diferenciação built-in do PyTorch. Ele calcula automaticamente os gradientes para qualquer grafo computacional. Para veres como funciona, imagina uma transformação linear standard. Tens um tensor de input com os teus dados, uma matriz de weights e um vetor de bias. O objetivo é calcular um output, compará-lo com o valor target real e calcular o erro, ou loss. Os teus dados de input são fixos, por isso não precisas das suas derivadas. Mas os tensores de weights e bias precisam de ser atualizados mais tarde, o que significa que precisas absolutamente dos seus gradientes. Sinalizas isto ao PyTorch definindo uma flag chamada requires grad como true quando crias esses tensores de parâmetros. Isto diz ao motor de autograd para começar a monitorizá-los. Quando fazes operações nestes tensores monitorizados — multiplicar o input pelos weights, adicionar o bias e calcular a loss final — o PyTorch faz duas coisas ao mesmo tempo. Ele calcula o resultado numérico real e, em simultâneo, constrói um Directed Acyclic Graph, ou DAG. Neste grafo, os teus tensores iniciais são as folhas, e as operações matemáticas que aplicaste são as raízes. Cada novo tensor criado por uma operação tem um atributo que guarda uma referência à função que o criou. Isto diz ao autograd exatamente como calcular a derivada para esse passo matemático específico. Este grafo não é uma estrutura estática definida no início do teu script. O PyTorch constrói o DAG dinamicamente do zero durante cada iteração. Quando corres uma forward pass, um grafo completamente novo é construído on the fly. Esta execução dinâmica significa que a tua rede pode mudar o seu comportamento a cada passo. Podes usar o control flow standard do Python, como if statements ou loops, e o motor vai seguir de forma limpa qualquer caminho que os dados tenham realmente percorrido durante essa run específica. Assim que a tua forward pass produz o tensor de loss final, disparas o cálculo dos gradientes chamando o método backward nessa loss. O autograd percorre imediatamente o grafo na ordem inversa. Ele usa a regra da cadeia para calcular as derivadas da loss em relação a cada tensor que tenha o requires grad definido como true. Depois, pega nesses valores calculados e guarda-os no atributo grad dos teus tensores de weights e bias. O cálculo complexo é completamente abstraído. Há momentos em que só queres passar dados pelo modelo sem calcular gradientes, como quando estás a avaliar um modelo treinado. Seguir o histórico requer memória e computação extra. Podes impedir o autograd de construir o grafo por completo envolvendo o teu bloco de código no context manager torch no grad. Isto interrompe temporariamente o tracking e executa a matemática muito mais rápido. O verdadeiro poder do autograd é que transforma código Python arbitrário numa estrutura matemática totalmente diferenciável sem que tenhas de escrever manualmente as fórmulas das derivadas. Obrigado por ouvirem. Fiquem bem, pessoal.
5

Controlar o Rastreio de Gradientes

3m 57s

Descubra como desativar o rastreio de gradientes do PyTorch para poupar memória e acelerar os cálculos. Essencial para executar inferências e congelar parâmetros do modelo.

Download
Olá, daqui é o Alex do DEV STORIES DOT EU. PyTorch Fundamentals, episódio 5 de 18. Se correres um modelo recém-treinado num grande batch de imagens de teste sem alterares uma configuração específica, a tua aplicação vai acabar por crashar com um erro de out-of-memory. Mesmo que estejas apenas a fazer previsões, o modelo está secretamente a acumular memória para se lembrar de cada operação matemática que realiza até o sistema morrer. Controlar o Gradient Tracking é a forma de evitares isto. Por default, os tensores do PyTorch são construídos para aprender. Se um tensor tiver o seu requisito de gradiente definido como true, o PyTorch faz o tracking de cada operação realizada nele. Ele constrói um computation graph em background, ligando inputs, weights e outputs para poder calcular os gradientes mais tarde durante a backpropagation. Este motor de tracking é brilhante para training, mas exige muito overhead. Quando o training está concluído, as tuas prioridades mudam. Imagina que acabaste de treinar um image classifier e precisas de correr previsões num milhão de novas imagens. Já não precisas de atualizar os weights do modelo. Só queres a forward pass. Se deixares a maquinaria de tracking a correr, o PyTorch constrói um computation graph enorme e inútil para esse milhão de imagens, a devorar a tua RAM e a atrasar os teus compute cycles. Para parar isto, tens duas ferramentas principais. A primeira é um context manager chamado torch dot no grad. Usas isto para fazer wrap a blocos inteiros de código. Quando colocas a tua forward pass dentro de um bloco no grad, estás a dizer ao PyTorch para desligar temporariamente o tracking engine. Quaisquer operações realizadas dentro desse bloco não serão registadas. Mesmo que os tensores de input sejam tracked normalmente, os outputs criados dentro do bloco terão os seus requisitos de gradiente definidos como false. Esta é a tua ferramenta para correr evaluation, testing ou previsões em massa. Ela desliga o graph para tudo dentro do seu scope. A segunda ferramenta é o método detach. Enquanto o no grad lida com blocos de código, o detach lida com tensores individuais. Chamar o detach num tensor devolve um novo tensor que partilha exatamente os mesmos dados subjacentes que o original, mas está completamente desligado do computation graph. Não tem histórico. As pessoas muitas vezes confundem quando usar qual. Usa o context manager torch dot no grad quando quiseres silenciar o tracking para uma sequência de operações, como ao passar de training para inference. Usa o método detach quando estiveres a construir ativamente um computation graph durante o training, mas precisares de tirar um tensor específico desse graph. Um use case comum para o detach é quando precisas de passar um tensor para uma library Python diferente, como o NumPy, que não entende os computation graphs do PyTorch. Primeiro fazes detach ao tensor, removendo a bagagem do tracking, e depois entregas os números raw. Desativar o gradient tracking é também uma técnica central para fazer freeze a parâmetros. Se estiveres a fazer fine-tuning a um modelo pré-treinado massivo, provavelmente não queres treinar a coisa toda do zero. Podes fazer um loop pelas base layers do modelo e definir os seus requisitos de gradiente para false. O PyTorch para de fazer o tracking delas por completo. Durante a backward pass, essas frozen layers não vão calcular gradientes e não vão fazer update, poupando quantidades massivas de memória e acelerando drasticamente o teu processo de fine-tuning. O gradient tracking é maquinaria industrial pesada desenhada estritamente para learning. Sempre que um tensor não precisar de aprender, desliga a maquinaria para recuperares a tua memória e velocidade. É tudo por este episódio. Obrigado por ouvires, e continua a desenvolver!
6

Datasets e Manipulação de Dados

3m 44s

Aprenda a separar o processamento de dados da arquitetura do seu modelo utilizando a classe Dataset do PyTorch. Exploramos o lazy loading e estruturas de datasets personalizadas.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 6 de 18. O teu modelo é apenas tão bom quanto os dados que lhe dás, mas o que fazes quando o teu dataset tem um terabyte de tamanho e a tua máquina tem apenas dezasseis gigabytes de RAM? A resposta está na forma como fazes o fetch desses dados. Este episódio aborda Datasets e Data Handling. A lógica de processamento de dados pode rapidamente ficar confusa. Se misturares o teu código de leitura, descodificação e formatação de ficheiros diretamente no loop de treino do teu modelo, o teu projeto torna-se frágil e difícil de manter. O PyTorch incentiva-te a separar estas responsabilidades. Tu queres a preparação dos teus dados completamente separada do teu algoritmo de treino. Para conseguir isto, o PyTorch fornece uma primitiva chamada Dataset, localizada no módulo torch ponto utils ponto data. A classe Dataset atua como um wrapper padronizado à volta dos teus dados brutos. Para lidar com os teus próprios ficheiros específicos, crias uma classe customizada que herda desta primitiva. Ao construíres um dataset personalizado, tens de implementar três métodos específicos. Estes são o init, o len e o getitem. O método init corre exatamente uma vez quando crias o objeto dataset. É aqui que configuras os teus diretórios e paths. Um erro frequente que os iniciantes cometem é tentar carregar todos os dados reais para a memória logo aqui. Não faças isto. Se tiveres cinquenta mil imagens de alta resolução, lê-las todas para a memória durante a inicialização vai crashar a tua máquina imediatamente. Em vez disso, usa o init para carregar um index leve. Por exemplo, podes ler um ficheiro CSV que contém os filenames das imagens numa coluna e as suas respetivas text labels noutra. Estás apenas a construir o mapa, não a guardar o território. A seguir vem o método len. Este simplesmente retorna o número total de samples no teu dataset. Se o teu ficheiro CSV tiver cinquenta mil linhas, este método retorna o número cinquenta mil. O sistema depende disto para saber os limites absolutos dos teus dados disponíveis, para não pedir um index que não existe. O trabalho pesado acontece no método getitem. Esta função foi desenhada para carregar e retornar uma única sample num index específico solicitado. Quando o sistema precisa da sample número quarenta e dois, chama o getitem e passa esse número. O teu código procura a linha quarenta e dois no CSV que carregaste anteriormente. Lê a string do file path dessa linha. Depois, e só depois, acede ao disco, encontra o ficheiro e descodifica os píxeis reais da imagem para a memória. Agarra na label dessa mesma linha do CSV, e retorna a imagem e a label juntas como um tuple. Esta técnica chama-se lazy loading. Só consomes memória para o pedaço específico de dados de que precisas, no momento exato em que estás pronto para o processar. Ao isolar esta lógica dentro do método getitem, o teu código de treino nunca precisa de saber se os dados vieram de um disco rígido local, de uma network stream, ou de uma base de dados complexa. Apenas pede um index e recebe um output padronizado. Separar o mecanismo de como os dados são obtidos de como são consumidos é a base de um código de machine learning escalável. Se achas estes episódios úteis e queres apoiar o programa, podes procurar por DevStoriesEU no Patreon. É tudo por este episódio. Obrigado por ouvires, e continua a construir!
7

DataLoaders e Batching

3m 34s

Liberte toda a velocidade do seu hardware envolvendo Datasets em DataLoaders. Aprenda a fazer batch, shuffle e multiprocessamento dos seus fluxos de dados.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Fundamentos do PyTorch, episódio 7 de 18. Os GPUs são incrivelmente rápidos, mas ficarão completamente parados se o teu CPU não conseguir alimentá-los com dados com rapidez suficiente. Os loops de treino geralmente têm bottlenecks não na matemática, mas no carregamento do próximo conjunto de ficheiros do disco. A solução para isto é separar a obtenção de dados da execução do modelo usando DataLoaders e Batching. É fácil confundir as funções de um Dataset e de um DataLoader. Um Dataset tem exatamente uma função: ir buscar um único item e a sua label. Não sabe nada sobre o processo de treino mais amplo. O DataLoader é um wrapper à volta desse Dataset. Atua como o gestor responsável por organizar esses itens individuais em grupos, aleatorizar a sua ordem e usar vários processos para os carregar de forma eficiente. Durante o treino, os modelos raramente analisam um ponto de dados de cada vez. Atualizam os seus pesos internos com base num grupo de itens avaliados simultaneamente, conhecido como minibatch. Esta abordagem torna o processo de treino mais estável e tira o máximo partido do poder de processamento paralelo do hardware. Para construíres um minibatch manualmente, terias de escrever um loop para extrair amostras individuais, empilhá-las numa estrutura de tensor maior e lidar com edge cases, como o último batch ser menor que os restantes. O DataLoader trata de tudo isto automaticamente. Inicializas um DataLoader passando-lhe o teu objeto Dataset e um parâmetro chamado batch size. Se definires o batch size para 64, o DataLoader vai extrair 64 itens distintos do Dataset, consolidá-los num único tensor e servi-los de uma só vez. No teu código, o DataLoader comporta-se como um iterable padrão do Python. Fazes um loop sobre ele. Sempre que o loop avança, o DataLoader faz yield do próximo batch completo de dados e do batch correspondente de labels. Também passas um parâmetro shuffle. Se uma rede neural processar os dados de treino exatamente na mesma sequência todas as vezes, pode memorizar essa sequência específica em vez de aprender as features reais. Definir o shuffle como true diz ao DataLoader para aleatorizar a ordem dos elementos do Dataset no início de cada epoch. Assim que for feito o yield de todos os batches e o Dataset estiver esgotado, o loop termina. Da próxima vez que iterares sobre o DataLoader, ele gera uma sequência aleatória totalmente nova. Esta é a parte que interessa. O DataLoader também aceita um parâmetro para o número de workers. Quando usas vários workers, o DataLoader arranca processos de CPU em background para ir buscar os dados. Imagina alimentares uma rede neural com essas 64 imagens. Enquanto o teu GPU está ocupado a calcular os gradientes para o batch atual, os workers de CPU em background estão simultaneamente a ler, a descodificar e a empilhar as próximas 64 imagens. Quando o GPU termina a sua etapa matemática atual, o próximo batch de dados já está à espera na memória. O GPU nunca fica sem dados. Um loop de treino de alta performance isola a realidade lenta e imprevisível das operações de disco da realidade rápida e estruturada do treino do modelo. O DataLoader proporciona esse isolamento, transformando uma coleção de ficheiros independentes num pipeline contínuo e paralelizado de minibatches. É tudo por este episódio. Obrigado por ouvires e continua a desenvolver!
8

Transformações de Dados

3m 53s

Descubra como pré-processar dados brutos em tempo real antes de chegarem à sua Neural Network. Abordamos transforms do torchvision como ToTensor e funções Lambda personalizadas.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 8 de 18. As redes neuronais só processam números, mas os teus dados do mundo real são geralmente uma coleção desorganizada de ficheiros de imagem raw e categorias de texto. Se escreveres loops manuais para converter cada imagem e label antes de começares o training, o teu código vai rapidamente tornar-se numa confusão frágil e ilegível. As Data Transformations são o mecanismo que resolve isto, convertendo automaticamente os teus dados raw num formato pronto para o modelo exatamente quando é preciso. Os dados raramente chegam prontos para machine learning. Tens de os manipular para um formato de tensor específico antes de os passares para a tua rede. O PyTorch lida com isto de forma limpa, aplicando transforms on the fly durante o processo de data loading. Quando inicializas um dataset, especialmente em libraries como a torchvision, defines estas modificações usando dois argumentos específicos. Usas o argumento transform exclusivamente para as tuas input features, como as tuas imagens raw. Usas o argumento target transform exclusivamente para as tuas labels. É fundamental manter estes dois separados, pois operam de forma independente em metades diferentes dos teus dados. Vamos olhar primeiro para as input features. Imagina que tens um dataset de imagens PIL raw. Uma rede neuronal não consegue ler um objeto de imagem PIL diretamente. Para resolver isto, passas um transform built-in da torchvision chamado ToTensor para o argumento transform. Quando o dataset faz load de uma imagem, o ToTensor executa automaticamente dois passos. Primeiro, converte a imagem PIL num float tensor do PyTorch. Segundo, faz scale dos valores de intensidade dos píxeis. Os píxeis de uma imagem raw geralmente variam de zero a duzentos e cinquenta e cinco. A operação ToTensor normaliza estes valores para um intervalo de floating-point entre zero e um. O dataset aplica esta operação estritamente à medida que cada imagem é obtida. Isto cobre os inputs, mas e os outputs? As labels do teu dataset podem ser simples integers que representam diferentes categorias. Por exemplo, o número três pode significar um cão. Mas para calcular a loss durante o training, o teu modelo muitas vezes exige que essas labels sejam one-hot encoded vectors, em vez de simples integers. Isto significa que precisas de um array onde todos os valores são zero, exceto o index que representa a class correta, que é definido como um. Para lidar com lógica custom como esta, o PyTorch fornece Lambda transforms. Um Lambda transform faz wrap a qualquer função definida pelo utilizador para que possa ser aplicada durante o data loading. Escreves uma função curta que recebe a tua label integer como input. Dentro dessa função, crias um tensor de zeros que corresponde ao número total de categorias no teu dataset. A seguir, usas uma operação interna do PyTorch para fazer scatter de um valor de um para o index específico que corresponde à tua label integer. Passas esta função custom para um Lambda transform, e depois atribuis isso ao argumento target transform do teu dataset. Isto cria um pipeline altamente eficiente. Uma worker thread extrai um único registo raw do teu disco. A imagem chega ao argumento transform, passa pelo ToTensor, e emerge como um float tensor normalizado. Simultaneamente, a categoria integer chega ao argumento target transform, executa a tua função Lambda custom, e transforma-se num one-hot encoded vector. Ambas as partes estão agora formatadas matematicamente e são entregues diretamente ao teu modelo. O verdadeiro poder desta arquitetura é a separation of concerns. Ao anexar estas Data Transformations diretamente à definição do dataset, o teu training loop fica completamente alheio à realidade confusa dos teus ficheiros raw. É tudo por este episódio. Obrigado por ouvires, e continua a programar!
9

Desenhar Redes com nn.Module

3m 40s

Explore o modelo estrutural de todas as Neural Networks do PyTorch. Aprenda a criar subclasses de nn.Module, a definir camadas na inicialização e a encaminhar dados no forward pass.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 9 de 18. Todas as redes neuronais no PyTorch, desde um classificador de imagens básico até um modelo de linguagem massivo, partilham exatamente o mesmo blueprint subjacente. Se não entenderes como este blueprint organiza dados e lógica, vais acabar a lutar contra a framework a cada passo. Desenhar redes com nn.Module é a forma de dominares esta estrutura. O nn.Module é a classe base para todos os componentes de redes neuronais no PyTorch. Atua como um contentor universal. Quando constróis um custom model, crias uma classe que herda de nn.Module. Esta herança dá automaticamente à tua classe a capacidade de monitorizar os seus próprios parâmetros, calcular gradientes, e integrar-se perfeitamente com o resto do ecossistema PyTorch. Também permite arquiteturas aninhadas. Podes colocar módulos dentro de outros módulos, criando uma árvore de layers que o módulo pai monitoriza e gere como uma única unidade. Imagina configurares o esqueleto vazio de um classificador de imagens novo em folha. Construir este esqueleto requer a definição de dois métodos específicos: o método initialize e o método forward. O PyTorch impõe uma separation of concerns rigorosa entre estas duas fases. Primeiro, temos o método initialize. Pensa nisto como o teu inventário. Quando a classe é instanciada, este método corre exatamente uma vez. Usas este método para declarar todas as layers individuais e operações matemáticas de que o teu modelo virá a precisar. Não estás a processar nenhuns dados reais aqui. Estás simplesmente a tirar componentes estruturais da prateleira, a configurar as suas shapes de input e output, e a guardá-los como variáveis internas dentro da tua classe. A seguir, temos o método forward. Esta é a tua linha de montagem ativa. O método forward recebe um tensor de input e dita exatamente como ele viaja pelo inventário que acabaste de declarar. Escreves a sequência de operações passo a passo. Pegas no tensor da imagem de input, passas para uma operação de flattening, alimentas esse resultado numa série de dense layers, e finalmente retornas as predictions de output. Todos os custom models devem definir este método forward para estabelecer o data flow. Isto leva-nos a uma armadilha comum. Como escreveste explicitamente a lógica do data flow dentro de um método chamado forward, o instinto natural é passares os teus dados chamando model dot forward. Não faças isso. Deves chamar o modelo diretamente como se fosse uma função normal, passando o teu input diretamente para o objeto do modelo instanciado. Internamente, executar o objeto do modelo diretamente aciona vários background hooks críticos de que o PyTorch precisa para gerir o estado da rede. Chamar o método forward diretamente ignora estes hooks e vai causar comportamentos inesperados durante o teu training loop. Assim que a tua classe estiver definida e tiveres criado um objeto de modelo, tens uma rede funcional. No entanto, por defeito, o PyTorch cria este objeto e todos os seus weights internos na memória da CPU do teu sistema. Para treinares a velocidades realistas, precisas de enviar esta arquitetura para um acelerador. Fazes isto verificando se uma GPU CUDA ou silicon especializado como o MPS da Apple está disponível, e atribuindo esse hardware target a uma variável device. A seguir, chamas o método to no teu modelo, passando essa variável device. Este único comando move imediatamente todos os parâmetros inicializados do modelo da memória standard para a memória de alta velocidade do teu hardware accelerator. A característica principal do nn.Module é a forma como impõe uma fronteira arquitetónica limpa entre os componentes estáticos que o teu modelo detém em memória, e o caminho dinâmico que os teus dados percorrem para serem processados. Obrigado por ouvirem. Fiquem bem, pessoal.
10

Camadas Lineares e Ativações

4m 09s

Olhe para o interior da Neural Network. Desconstruímos o módulo nn.Linear e explicamos por que razão as funções de ativação não-lineares como a ReLU são matematicamente essenciais.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 10 de 18. Se empilhares cem layers de uma neural network sem um truque matemático específico, a rede inteira colapsa matematicamente numa única linha reta. A culpada é a álgebra linear, e a solução requer que percebas de Linear Layers e Activations. Uma rede neuronal é, fundamentalmente, uma sequência de operações matemáticas em tensores. A operação mais comum é a linear layer, definida no PyTorch como nn.Linear. Este módulo aplica uma transformação afim aos dados de entrada. Ele guarda dois tensores internos que aprende ao longo do tempo: os weights e os biases. Quando os dados passam por lá, a layer multiplica o input pela matriz de weights e adiciona o bias. Pega numa imagem standard em tons de cinzento de 28 por 28 píxeis. Antes que uma linear layer a consiga processar, tu fazes flatten da grelha bidimensional para um array unidimensional de 784 números. Passas esse array de 784 valores para uma layer nn.Linear configurada para dar um output de 512 features. Internamente, o PyTorch cria uma matriz de weights que mapeia os 784 inputs para 512 outputs. Ele multiplica os valores dos teus píxeis por esses weights, soma-os, adiciona um termo de bias para ajustar o resultado, e dá como output 512 novos números. Durante o training, o PyTorch atualiza continuamente estes weights e biases. Eles formam a verdadeira memória do teu model. Podes assumir que uma deep neural network é apenas uma longa sequência destas linear layers empilhadas umas nas outras. Esta é a parte que interessa. Se fizeres chain de várias operações nn.Linear sem nada pelo meio, a matemática simplifica-se. A matriz A multiplicada pela matriz B é apenas outra matriz, C. Empilhar dez linear layers tem exatamente a mesma capacidade matemática que calcular uma única linear layer. A tua deep network fica reduzida a uma equação linear plana, completamente incapaz de aprender padrões complexos do mundo real. Para travar este colapso matemático, introduzes uma não-linearidade imediatamente a seguir à linear layer. A estas chamamos activation functions. A activation mais utilizada no PyTorch é a nn.ReLU, que significa Rectified Linear Unit. Depois de a linear layer calcular os seus 512 outputs, passas esse tensor diretamente para uma função ReLU. A lógica da ReLU é brutalmente simples. Ela olha para cada número no tensor. Se um número for menor que zero, a ReLU muda-o para exatamente zero. Se um número for zero ou positivo, a ReLU deixa-o completamente em paz. Essa única quebra no zero destrói a linearidade. Impede que a próxima linear layer se funda matematicamente com a anterior. Ao forçar os valores negativos a zero, a ReLU também cria representações esparsas. Isto significa que apenas um subconjunto específico de neurónios é ativado para um dado input, tornando a rede altamente eficiente. O fluxo de dados é consistente. A tua imagem flattened entra na linear layer, é transformada pelos weights e biases, e depois chega à activation ReLU, onde os outputs negativos são removidos. Podes então passar este tensor ativado em segurança para uma segunda linear layer, para extrair padrões mais profundos e abstratos. Uma linear layer determina quanta importância matemática dar a cada input, mas a activation function dá à rede a verdadeira geometria necessária para aprender as formas imprevisíveis dos dados reais. Obrigado por passares uns minutos comigo. Até à próxima, fica bem.
11

O Contentor nn.Sequential

4m 01s

Otimize o seu código PyTorch utilizando o contentor nn.Sequential. Aprenda a encaixar camadas de forma limpa e a inspecionar os parâmetros do seu modelo.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Fundamentos do PyTorch, episódio 11 de 18. Escrever métodos forward personalizados para cada rede neuronal torna-se rapidamente cansativo quando estás apenas a empilhar camadas standard. Nem sempre precisas de fazer o routing manual dos dados de uma função para a outra. Às vezes, só precisas de encaixar as camadas como peças de LEGO. É exatamente isso que o container nn.Sequential faz. O container nn.Sequential é um pipeline ordenado de módulos de rede neuronal. Quando passas dados para este container, os dados fluem pelos módulos internos na sequência exata em que foram adicionados. Pensa em montar um Multilayer Perceptron standard de três camadas. Normalmente, definirias as tuas camadas lineares e funções de ativação num método de inicialização e, de seguida, escreverias um método forward personalizado. Nesse método forward, irias pegar explicitamente no input, passá-lo para a camada um, envolvê-lo numa ativação ReLU, passar esse resultado para a camada dois, aplicar outra ReLU e passar isso para a camada final. Com o Sequential, ignoras completamente o método forward. Instancias o container e passas os teus módulos diretamente para ele como argumentos. Forneces um módulo Linear, seguido por um módulo ReLU, um segundo módulo Linear, outro ReLU e um módulo Linear final. O PyTorch lida automaticamente com o routing dos dados. O output do primeiro módulo torna-se instantaneamente o input para o segundo, prosseguindo automaticamente pela chain abaixo. Este container é altamente eficiente, mas tem uma limitação rígida. É estritamente para um data flow linear e direto. Não consegue lidar com arquiteturas complexas que exigem branching, múltiplos inputs ou skip connections. Se estiveres a construir algo como uma Residual Network, onde os dados fazem bypass a certas camadas e voltam a ser adicionados mais à frente, o Sequential não vai funcionar. Para qualquer topologia não linear, continuas a ter de escrever um módulo personalizado com um método forward dedicado. Depois de encadeares as tuas camadas, geralmente precisas de inspecionar o que acabaste de construir. Cada camada no teu container Sequential é uma subclasse de nn.Module, o que significa que o PyTorch regista e faz o tracking automaticamente de todo o state subjacente. Para visualizar esse state, usas o método named_parameters. Chamar named_parameters no teu modelo fornece um iterador sobre todos os weights e biases internos. Cada item que ele devolve é um par simples: o nome do parâmetro e o próprio tensor do parâmetro. Como usaste um container Sequential sem nomear explicitamente as tuas camadas, o PyTorch gera nomes numéricos com base nos seus índices. Vais ver nomes como zero dot weight para os weights da primeira camada linear, ou zero dot bias para os seus termos de bias. O tensor correspondente contém os valores numéricos reais, o shape da matriz e se requer o cálculo de gradiente. Fazer um loop por named_parameters é a maneira standard de verificar a tua arquitetura. Podes fazer print rapidamente do tamanho de cada matriz de weights para confirmar se as dimensões de input e output estão perfeitamente alinhadas em toda a chain antes mesmo de começares a enviar dados reais para o sistema. O verdadeiro poder do container Sequential, combinado com o tracking de parâmetros, é que o PyTorch absorve a gestão de state e o routing de dados, permitindo que te concentres inteiramente no shape da tua rede. É tudo por este episódio. Até à próxima!
12

Compreender as Loss Functions

3m 20s

Antes que uma IA possa aprender, tem de medir os seus erros. Mergulhamos nas Loss Functions do PyTorch, comparando a CrossEntropyLoss para classificação e a MSELoss para regressão.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. PyTorch Fundamentals, episódio 12 de 18. Para ensinares uma neural network a acertar, primeiro tens de medir rigorosamente o quão errada ela está. Se não consegues quantificar o erro, o teu model não consegue aprender com ele. Isso leva-nos a perceber as loss functions. Quando uma network não treinada processa dados, o seu output é essencialmente um palpite. Uma loss function avalia esse palpite. Ela mede o grau de dissimilaridade entre o resultado que o model produziu e a verdade absoluta do target value. O output de uma loss function é sempre um único número escalar. Todo o teu processo de training existe para empurrar esse número o mais perto possível de zero. Como diferentes tarefas de machine learning têm diferentes definições do que é estar errado, o PyTorch fornece várias loss functions. Se estiveres a construir um regression model para prever um valor contínuo, como a temperatura de amanhã, medes a distância entre o teu palpite e a temperatura real. Para isso, usas o Mean Square Error, que no PyTorch se chama nn.MSELoss. Mas classification é diferente. Supõe que tens um model a categorizar imagens de roupa em dez categorias de moda. O model olha para a imagem de um casaco e faz o output de dez raw scores, uma para cada categoria possível. Estas raw scores, não normalizadas, chamam-se logits. A resposta certa é apenas um único integer, que representa a class correta. Não podes simplesmente subtrair um class index de uma raw score. Em vez disso, precisas de uma função que penalize o model por dar scores baixas à class correta e scores altas às classes erradas. Para classification, a ferramenta standard é a nn.CrossEntropyLoss. Inicializas a tua loss function, passas-lhe os dez raw logits do teu model juntamente com a integer label correta, e ela devolve a tua penalidade escalar. Esta é a parte que interessa. Há aqui uma armadilha gigante para os developers. Em muitos livros de machine learning, uma classification network acaba com uma softmax layer. O softmax força os raw logits para uma probability distribution certinha, onde todas as scores somam exatamente um. Por causa disto, os developers muitas vezes adicionam manualmente uma operação softmax mesmo no fim do seu PyTorch model. Se estiveres a usar a nn.CrossEntropyLoss, fazer isso é um erro. No PyTorch, a nn.CrossEntropyLoss aplica automaticamente uma função LogSoftmax internamente antes de calcular a negative log likelihood. Ela foi construída para aceitar raw logits não normalizados diretamente. Se o teu model faz output de probabilities porque já aplicaste o softmax, passá-las para a nn.CrossEntropyLoss significa que estás a aplicar a matemática duas vezes. Isto comprime os teus gradients, abranda drasticamente o training, e arruína a capacidade do teu model de aprender de forma eficaz. A regra a reter é que a tua neural network deve apenas fazer output de raw numbers. Mantém os outputs do teu model raw, passa-os diretamente para a nn.CrossEntropyLoss, e deixa o PyTorch fazer o trabalho pesado de transformar esses logits numa penalidade com significado. Obrigado por ouvirem, happy coding a todos!
13

Optimizers e Gradient Descent

3m 26s

Explore como o Optimizer atualiza os pesos do modelo para reduzir o erro. Aprenda a dança crucial de três passos: zero_grad(), backward() e step().

Download
Olá, daqui é o Alex do DEV STORIES DOT EU. PyTorch Fundamentals, episódio 13 de 18. O bug mais comum no training de PyTorch não são maus dados ou uma arquitetura errada. É esqueceres-te de limpar os teus cálculos antigos, fazendo com que a tua rede fique fora de controlo. Hoje falamos sobre Optimizers e Gradient Descent, que lidam exatamente com a forma como o teu modelo aprende com os seus erros. O teu modelo faz uma previsão e tu calculas a loss para veres o quão longe estava. Agora precisas de ajustar os weights internos da rede neuronal para tornar a próxima previsão um pouco mais precisa. Este processo de ajustar os parâmetros para minimizar a loss chama-se optimization. O optimizer é o algoritmo específico que controla como esses weights mudam. Para configurares um optimizer, tens de lhe dar duas coisas. Primeiro, passas-lhe um iterable com os parâmetros do modelo que queres que ele ajuste. Segundo, dás-lhe um learning rate. O learning rate é um hyperparameter fundamental que controla a magnitude das mudanças aplicadas aos weights. Se o learning rate for muito pequeno, o optimizer dá steps microscópicos, tornando o training dolorosamente lento. Se o learning rate for muito grande, o optimizer ultrapassa os valores ideais, levando a um comportamento caótico e imprevisível. Um algoritmo standard para esta tarefa é o Stochastic Gradient Descent, ou SGD. Ele avalia a inclinação da tua loss function e dá um step na direção oposta para descer em direção ao menor erro possível. Depois de inicializares o teu optimizer SGD com os teus parâmetros e learning rate, as atualizações reais acontecem numa sequência rigorosa de três passos. O passo um é limpar tudo. Chamas o comando zero grad no optimizer. É aqui que vive esse bug comum. O PyTorch acumula gradients por default. Quando calcula novos gradients, não substitui os antigos; simplesmente adiciona os novos números aos totais existentes. Se saltares este step de zero grad, a matemática do teu batch atual é corrompida pelos números que sobraram do batch anterior. Zera sempre os gradients antes de fazeres qualquer outra coisa. O passo dois é calcular os novos gradients. Pegas no teu valor de loss calculado e chamas o comando backward. Isto desencadeia a backpropagation. O PyTorch percorre a arquitetura da tua rede de trás para a frente. Ele calcula a derivada da loss em relação a cada parâmetro. Essencialmente, descobre exatamente quanto cada weight individual contribuiu para o erro geral. Estes gradients calculados são guardados diretamente dentro dos objetos dos parâmetros. O passo três é aplicar a correção. Chamas o comando step no optimizer. O optimizer olha para os gradients guardados em cada parâmetro durante a backward pass. Ele multiplica esses gradients pelo learning rate para descobrir o tamanho exato do ajuste, e depois atualiza os weights reais na memória. Este ciclo repete-se para cada batch. Zera os gradients, calcula a backward loss, faz step ao optimizer. O detalhe crítico a lembrar é que o optimizer apenas atualiza os parâmetros que lhe foram explicitamente dados durante o setup. Se precisares de fazer freeze a uma layer na tua rede, simplesmente excluis os seus parâmetros quando inicializas o optimizer, e esses weights permanecerão permanentemente fixos. Já agora, se quiseres apoiar o programa, podes procurar por DevStoriesEU no Patreon. Obrigado por ouvirem. Fiquem bem, pessoal.
15

Validação e Inferência

3m 45s

Avalie o seu modelo de forma objetiva. Aprenda a mudar a sua rede para o modo de avaliação, a congelar gradientes e a extrair previsões precisas em dados não vistos.

Download
Olá, daqui é o Alex do DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 15 de 18. O teu modelo pode ter um desempenho perfeito nos dados de treino, mas o único verdadeiro teste de uma IA é como ela lida com o desconhecido. Se dependeres apenas do feedback que o teu optimizer recebe, podes estar apenas a construir um banco de memória muito caro. Para veres se o teu modelo realmente generaliza para o mundo real, precisas de validação e inferência. Durante a fase de treino, olhas para a training loss. Esse número existe para guiar o optimizer interno. Ele força o modelo a ajustar os seus weights até que o erro matemático diminua. Mas uma training loss baixa não significa que tenhas um bom modelo. Significa simplesmente que o modelo é muito bom a responder a perguntas que já viu. A validation accuracy é uma métrica completamente separada que indica aos humanos se o modelo consegue fazer predictions corretas em dados totalmente novos. Para obteres esta métrica, tens de correr um validation loop contra um test dataset dedicado. Antes de passares um único dado de teste para a rede, tens de alterar o estado do modelo. Fazes isso chamando o método eval no teu objeto de modelo. Chamar o eval muda a rede para evaluation mode. Certas camadas internas comportam-se de maneira diferente durante o treino do que durante a inferência. Chamar o eval força-as a bloquear o seu comportamento para que as tuas predictions se mantenham consistentes. Se saltares este passo, os teus resultados de teste serão fundamentalmente pouco fiáveis. Isso cobre o estado do modelo. A seguir, tens de controlar o próprio motor desligando o gradient tracking. Fazes isso envolvendo o teu código de validação num context manager no grad. Durante o treino, o PyTorch constrói constantemente um computational graph em memória, guardando o histórico de cada operação para poder calcular os gradients mais tarde. Num validation loop, já terminaste completamente o treino. Não queres atualizar os weights. O bloco no grad diz ao PyTorch para parar de fazer tracking do histórico. Esta é a parte que interessa. Desativar o tracking previne atualizações acidentais no teu modelo, mas também liberta uma quantidade enorme de memória e acelera drasticamente a computação. Dentro desse bloco no grad, a lógica em si é simples. Iteras sobre o teu test dataset em batches. Para cada batch, passas os dados de input pelo modelo. O modelo calcula a forward pass e devolve as suas raw predictions. Se estiveres a fazer classificação, o modelo não devolve uma text label certinha. Em vez disso, devolve uma lista de scores numéricos para cada categoria que conhece. Para descobrires qual a categoria que o modelo realmente escolheu, precisas da função argmax. O argmax olha para a lista de raw scores e encontra o número mais alto. Depois, devolve a posição do index desse score mais alto. Esse index é a tua class prediction escolhida. Assim que tiveres as predictions do modelo, comparas diretamente com as true labels fornecidas pelo test dataset. Contas exatamente quantas predictions correspondem às true labels. Manténs um total acumulado destas correspondências corretas ao longo de todas as batches. Quando o loop termina, divides o número total de predictions corretas pelo número total de itens no test dataset. O resultado é a tua percentagem de accuracy final. O training loop força o teu modelo a ajustar-se aos dados históricos, mas as restrições rigorosas do validation loop provam se esse modelo é realmente útil. Obrigado por ouvirem. Fiquem bem, pessoal.
16

Guardar e Carregar Modelos

3m 36s

Não perca o progresso que tanto lhe custou a alcançar! Discutimos as formas mais seguras de serializar os pesos do seu modelo utilizando o state_dict e de os carregar novamente com segurança.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Fundamentos de PyTorch, episódio 16 de 18. Treinar um image classifier durante cinquenta epochs pode demorar semanas e gastar milhares de dólares em compute. No entanto, no momento em que o teu script Python acaba de correr, todos esses padrões conquistados a muito custo desaparecem completamente da memória. Para protegeres esse investimento, precisas de uma forma de persistir o teu progresso no disco. É exatamente isso que o save e load de modelos resolve. Dentro de cada modelo PyTorch existe um dicionário interno chamado state dict. Este dicionário mapeia cada layer da tua rede para os seus tensores de parâmetros correspondentes. Ele guarda os weights e biases reais que o teu modelo aprendeu durante o treino. A estrutura do modelo é apenas código, mas o state dict é a inteligência. Para persistires o teu modelo, extrais este dicionário e gravas num ficheiro. Fazes isso usando uma função chamada torch dot save. Passas-lhe duas coisas. Primeiro, o state dict do teu modelo. Segundo, o file path onde o queres guardar, que tradicionalmente usa uma extensão dot pth. Numa única linha de código, a tua run de treino de cinquenta epochs fica guardada em segurança no teu disco rígido como um único ficheiro contendo apenas dados raw de tensores. Podes ver exemplos online que ignoram o state dict por completo e passam simplesmente o objeto do modelo diretamente para o torch dot save. Não faças isso. Fazer save do modelo inteiro depende muito da serialização pickle de Python. Isso vincula o ficheiro guardado à estrutura de diretórios e definições de classe exatas presentes no momento em que o ficheiro foi criado. Se mais tarde fizeres refactor ao teu código ou moveres um ficheiro, o modelo vai falhar ao fazer load. Ficares-te pelo state dict é muito mais seguro e significativamente mais robusto, porque só estás a fazer save dos dados, não do código. Quando chegar a hora de fazeres deploy do teu classifier para inferência em produção, tens de reverter o processo. Como só fizeste save dos weights, o PyTorch precisa de saber como é a estrutura da rede. Começas por instanciar uma versão completamente em branco da tua classe de modelo. Isto dá-te a shell arquitetural. A seguir, chamas o torch dot load e dás-lhe o teu file path para ler o dicionário de volta para a memória. Quando chamas o torch dot load, há uma best practice moderna e crucial que deves seguir. Passa sempre o argumento weights only definido como true. Os ficheiros pickle de Python podem conter código executável arbitrário. Se fizeres download de um modelo pre-trained da internet e lhe fizeres load às cegas, ele pode correr scripts maliciosos na tua máquina. Definir o weights only como true restringe o loader a desserializar apenas tensores standard do PyTorch, mantendo o teu sistema seguro. Finalmente, com o teu modelo em branco pronto e o teu dicionário seguro carregado, chamas o load state dict no modelo e passas o dicionário. O PyTorch mapeia os weights carregados para as layers correspondentes na shell em branco. O teu modelo está agora totalmente restaurado e pronto para fazer previsões. Nunca confies o teu investimento de treino a um objeto serializado frágil; separa sempre a arquitetura no teu código dos parâmetros aprendidos no teu disco. Obrigado por estares aí. Espero que tenhas aprendido algo novo.
17

Aumentar a Velocidade com torch.compile

3m 26s

Desbloqueie a funcionalidade de destaque do PyTorch 2.0. Aprenda como o decorador torch.compile faz o JIT-compile do seu código Python em kernels otimizados para aumentos massivos de velocidade.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Fundamentos do PyTorch, episódio 17 de 18. Passas semanas a ajustar a arquitetura de um modelo para obteres um ganho de velocidade de cinco por cento. Mas, muitas vezes, o verdadeiro bottleneck não está na tua matemática. Está no próprio Python e nas constantes e ineficientes transferências de dados entre a tua memória e a GPU. Corrigir isto não exige reescreveres a tua codebase. Hoje, vamos ver como potenciar a velocidade com o torch dot compile. Introduzida no PyTorch 2.0, esta feature muda a execução do teu código de uma execução standard para um workflow altamente otimizado. Uma ideia comum é que acelerar o PyTorch para produção significa escrever custom kernels em C plus plus ou mudar fundamentalmente a tua arquitetura. Não é o caso. Não alteras nada dentro do teu modelo. Basta fazeres o wrap do teu modelo existente numa única function call. Para perceberes porque é que isto gera um salto enorme na performance, tens de olhar para como o PyTorch normalmente corre. O PyTorch standard opera em eager mode. Ele executa exatamente o que lhe dizes, exatamente quando pedes, uma operação de cada vez. Se o teu código disser à GPU para somar dois tensores, multiplicar o resultado por outro tensor e aplicar uma activation function, o eager mode trata isso como três eventos isolados. Em cada passo, a GPU lê dados da sua main memory, faz os cálculos e escreve o resultado intermédio de volta. A memory bandwidth da GPU é limitada. Essa constante transferência de dados demora muito mais tempo do que os cálculos em si. Quando passas o teu modelo pela compile function, o PyTorch muda de tática. Ele usa uma tool interna chamada TorchDynamo para capturar as tuas operações num computation graph antes de as executar. Ao olhar para a sequência mais ampla, encontra ineficiências. Depois, usa um backend compiler para gerar uma nova versão, altamente otimizada, das tuas operações. A principal técnica que usa é a kernel fusion. Em vez de ler e escrever na memória três vezes separadas, o código compilado funde esses passos. A GPU lê os dados uma vez, mantém-nos nos seus internal registers mais rápidos, faz a adição, multiplicação e ativação de seguida, e depois escreve o resultado final apenas uma vez. O overhead do Python desaparece, e o memory bottleneck é contornado. A implementação é simples. Instancias o teu modelo como de costume. Depois, chamas o torch dot compile, passas o teu modelo, e atribuis o output a uma nova variável. Encaminhas os teus dados através desta versão compilada. Se o TorchDynamo encontrar um construct obscuro de Python que não consiga otimizar com segurança, não quebra o teu programa. Simplesmente deixa essa pequena secção em eager mode standard e compila o resto. Quando fizeres o benchmark disto, presta atenção à primeira run. A pass inicial demora significativamente mais tempo porque a compilação real acontece exatamente quando os primeiros dados chegam. Mas na segunda pass, o inference time cai drasticamente. Já não tens de escolher entre a flexibilidade do eager mode durante o desenvolvimento e a velocidade bruta de um compiled backend em produção. É tudo por este episódio. Obrigado por ouvires, e continua a construir!
18

Compiladores e Graph Breaks

3m 57s

Mergulhe no interior do compilador do PyTorch. Exploramos graph breaks, fluxo de controlo dinâmico e por que razão o torch.compile tem sucesso onde os sistemas antigos falharam.

Download
Daqui fala o Alex da DEV STORIES DOT EU. Fundamentos do PyTorch, episódio 18 de 18. Os compiladores de IA mais antigos exigiam execution paths perfeitamente previsíveis, falhando espetacularmente se incluísses estruturas de Python complexas e dinâmicas no teu modelo. Bastava ajustares um único conditional statement e todo o processo de compilação crashava. O PyTorch dois ponto zero lida com esse exato mesmo código arbitrário sem se queixar. O motor por trás desta flexibilidade baseia-se na forma como o compilador lida com graph breaks. Se trabalhaste com a ferramenta de compilação legacy, o TorchScript, sabes que exigia estruturas de código rígidas. O TorchScript dependia de static typing estrito e de uma execução previsível. Se o teu modelo tivesse um control flow altamente dinâmico, dependesse de dicionários standard de Python, ou fizesse calls para libraries externas non-tensor, o TorchScript muitas vezes rejeitava-o. Os engenheiros muitas vezes tinham de reescrever partes significativas da arquitetura do modelo apenas para satisfazer o compilador. O PyTorch dois ponto zero aborda isto de uma forma completamente diferente. Em vez de exigir código estático à partida, o compilador nativo analisa a tua execução de Python dinamicamente. Captura todas as operações matemáticas que consegue otimizar com segurança e agrupa-as num computational graph altamente eficiente. Inevitavelmente, o compilador vai encontrar código que não consegue mapear facilmente para uma estrutura de graph otimizada. Quando encontra esta lógica imprevisível, faz trigger de um graph break. Um graph break não é um erro, e não é um crash. É simplesmente um mecanismo de fallback. Significa que o compilador devolve o controlo, de forma elegante, à eager execution standard do PyTorch para esse segmento específico de código. Imagina uma função onde corres uma série de matrix multiplications pesadas, seguidas por um if-statement de Python que verifica o valor médio de um tensor para decidir a próxima operação. Essa condição é data-dependent. O execution path é completamente desconhecido até ao momento exato em que os valores do tensor são calculados em runtime. Quando fazes trace a esta função com o compilador, ele analisa o flow. Pega nas matrix multiplications que acontecem antes da condição e compila-as num sub-graph rápido e otimizado. Depois, esbarra no if-statement complicado. Como não consegue prever o resultado, cria um graph break. O compilador deixa o Python standard executar a condição em eager mode. Assim que a condição é avaliada e o path é escolhido, o compilador retoma o controlo, pegando nas operações restantes e compilando-as num segundo sub-graph otimizado. O sistema segmenta o teu código automaticamente. Ficas com ilhas compiladas de matemática rápida separadas por pontes de Python standard. O teu modelo continua a correr de forma seamless. Esta é a parte que interessa. É provável que vejas performance logs a apontar graph breaks na tua arquitetura. Embora, por norma, os queiras minimizar para espremer a máxima velocidade de execução, eles existem puramente como uma rede de segurança. Eles garantem que o teu código produz sempre o resultado matemático correto, mesmo que nem todas as linhas possam ser fundidas num único kernel. A principal mudança de design na compilação moderna do PyTorch é priorizar uma seamless execution em detrimento da otimização total, garantindo que o motor se adapta à tua lógica arbitrária, em vez de forçar a tua lógica a adaptar-se ao motor. Isto conclui a nossa série sobre PyTorch. Encorajo-te vivamente a explorares a documentação oficial, a experimentares estas ferramentas de compilação hands-on, e a visitares a DEV STORIES DOT EU para sugerir tópicos para futuras séries. Gostaria de tirar um momento para te agradecer por ouvires — isso ajuda-nos imenso. Tem um ótimo dia!