Sejam bem-vindos ao curso de Redes Neurais Transformers com o PyTorch. Meu nome é Alura e serei o instrutor durante esta jornada, na qual aprenderemos a construir um Transformer do zero. Este é o coração dos modelos generativos mais utilizados no mercado, seja na geração de texto, imagem ou conteúdos de forma geral.
Audiodescrição: Alura é um instrutor fictício, representando a equipe de instrutores da plataforma. Ao fundo, há uma parede branca com uma luz rosa e azul refletida nela.
Neste curso, aprenderemos como funcionam internamente as redes neurais Transformers, uma das bases mais poderosas das IAs modernas, especialmente os modelos generativos. Vamos entender os componentes principais, tudo na prática, utilizando o PyTorch.
Ao longo das aulas, aprenderemos a compreender a arquitetura interna dos Transformers, incluindo os mecanismos de atenção, self-attention (autoatenção), positional encoding (codificação posicional), as camadas de LayerNorm (normalização de camada) e as conexões residuais. Construiremos o Transformer encoder (codificador Transformer) e o decoder (decodificador), utilizando o PyTorch de forma passo a passo.
Prepararemos um dataset real, utilizando a base de dados do Hugging Face, que contém uma extração da Wikipedia em português. Organizaremos todos esses dados, tratando-os como dados reais, como se fosse um projeto no qual estaríamos trabalhando no mercado de trabalho.
Aprenderemos a usar tokenizadores pré-treinados e integrar embeddings (incorporações) para alimentar nossos modelos. Treinaremos e ajustaremos os hiperparâmetros desses modelos, trazendo então uma estabilização e gerando um resultado factível para colocar em produção.
Por último, iremos avaliar os resultados que geramos, utilizando essas redes neurais que construiremos do zero, e então entender como expandir esse modelo para tarefas mais complexas, salvando todos os artefatos necessários para poder colocar em produção e fazer o deploy dessa nossa rede neural Transformers.
No projeto final, construiremos um Transformer funcional do zero, treinando com textos em português, uma base bem grande de dados, passando por todas as etapas de pré-processamento, tokenização, definição da arquitetura e treinamento. A partir de uma base sólida, construiremos e evoluiremos a rede neural para geração de texto.
Nos vemos no nosso curso.
Os Transformers e transformações surgiram com um artigo chamado "Attention is All You Need". Basicamente, ele oferece um contraponto às tarefas de linguagem e sequências que eram dominadas por redes neurais RNNs e LSTMs. Essas redes analisavam token a token, como se fosse uma série temporal, considerando o estado atual e o estado anterior. Isso causava uma limitação de paralelização e, ao trabalhar com dependências muito longas, textos extensos ou séries temporais prolongadas, era necessário utilizar ferramentas para achatar a série temporal, como trabalhar com estatísticas para aprender os padrões.
Com o surgimento desse artigo, surgiu a possibilidade de relacionar a posição e a sequência de forma paralela via atenção, que estudaremos mais profundamente nas aulas futuras. A atenção torna o treinamento mais eficiente, permitindo verificar características da sequência de forma paralela e treinar a rede com base não apenas nos tokens, mas também do ponto de vista de multi-atenção. Assim, temos a atenção introduzida no "Attention is All You Need" e o multi-attention, que seria o multi-head, permitindo verificar a atenção sobre diferentes características da sequência.
A arquitetura padrão clássica da rede neural Transformers segue essa formação. Temos o input, que passa por um embedding e um embedding posicional. O embedding numérico transforma a sequência em números e também traz sua posição. A partir do multi-head e adicionando normalizações no decoder e no encoder da rede, conseguimos passar para o decoder e, por meio de processos e interações, criamos as probabilidades e identificamos a próxima palavra ou caractere na sequência. A base da rede aprende e segue todos esses passos nas camadas, gerando probabilidades possíveis e interagindo sobre a sequência.
Visualizando o gráfico da rede que utilizaremos no curso, usaremos multi-head e atenção para verificar atenções em sequências diferentes, seguindo essa característica. Aqui está todo o código da rede neural e da estrutura que verificaremos. Seguimos o mesmo padrão da arquitetura tradicional, com o input e o positional encoder. No encoder, realizamos todo o processo de normalização, criação das multi-head e atenção, adicionamos a normalização e criamos uma lei de normalização e posicional das características. No decoder, realizamos os mesmos processos, mas acrescentamos a máscara de multi-atenção, que verifica o futuro. Em vez de olhar toda a sequência anterior, focamos nas probabilidades futuras, dando atenção ao que vem depois. Essa é a ideia principal ao trabalhar com máscaras multi-head e self-attention.
Seguindo toda a arquitetura, ao final, com a Softmax, obtemos as probabilidades das próximas palavras e como vamos seguir na sequência. Por exemplo, ainda não entraremos profundamente nos exemplos práticos, mas veremos mais à frente. Se dissermos uma frase como "eu amo pizza" ou "eu gosto de pizza", ambas seguem uma sequência que transmite basicamente o mesmo significado, indicando afeição por essa comida específica. A partir disso, poderíamos criar uma base de self-attention. Assim, ao dizer "eu amo", a próxima palavra provável seria "pizza", e o mesmo ocorre com "eu gosto". A ideia é criar uma rede que atente para determinadas palavras e suas sequências, identificando padrões nos dados com os quais trabalhamos. Neste curso, focaremos em dados textuais, mas a lógica pode ser aplicada a outros tipos de dados, inclusive não estruturados, como imagens.
O self-attention segue uma equação que define as queries, as keys e os values, calculando a similaridade entre as palavras e utilizando o softmax para normalizar e identificar as probabilidades, multiplicando pela média ponderada desses valores. A atenção utiliza as queries para determinar qual a próxima palavra, as keys para identificar as palavras disponíveis, e os values para trazer a informação da palavra. Essa é a lógica do self-attention. Não se preocupe, pois faremos isso na prática, sem ainda inserir na arquitetura ou na rede neural, utilizando um exemplo básico com matrizes para entender seu funcionamento.
O multi-head attention consiste em várias "cabeças" que analisam problemas distintos. Criamos várias atenções diferentes para identificar padrões, como o comprimento da palavra, sintaxe e estilo. Todas essas características são concatenadas e ponderadas para projetar os pesos das palavras, trazendo coerência à geração das redes neurais.
O positional encoding é uma assinatura rítmica que determina a posição específica de uma palavra dentro de uma sentença. Pode ser treinada com uma embedding posicional ou utilizando equações pré-treinadas, como o senoidal clássico, que não é treinável. Nesse caso, calculamos o seno e o cosseno para criar posições indeterminadas para conjuntos de palavras e sequências dentro das funções senoidais e cossênicas.
Agora, precisamos configurar nosso ambiente de trabalho e trazer os dados para começar a trabalhar com redes neurais Transformers. Na próxima aula, vamos configurar e reunir todas as informações necessárias.
Vamos iniciar a configuração do nosso ambiente. Precisaremos instalar algumas bibliotecas no Colab, além de incorporar outras que provavelmente já utilizamos, como Regex, PyTorch, Seaborn, Matplotlib, entre outras.
Primeiramente, conectaremos o Colab. Será necessário instalar as bibliotecas do Hugging Face, pois não vêm por padrão no Colab. Essas bibliotecas nos ajudarão a obter tanto o tokenizador quanto os datasets. Utilizaremos o comando !pip install com a opção -q para suprimir as mensagens de instalação.
!pip install transformers sentencepiece -q
!pip install datasets -q
Após a instalação, importaremos as bibliotecas que utilizaremos: re para Regex, essencial para trabalhar com texto; os para manipulação de arquivos textuais; unicodedata para informações de codificação textual; torch para treinar redes neurais; math para funções matemáticas; torch.nn para redes neurais; torch.nn.functional para funcionalidades adicionais; numpy, pandas, matplotlib, seaborn, pathlib para manipulação de caminhos, e tipografias como dicionário, lista, opcionais, tuplas e uniões.
# Importações essenciais
import re
import shutil
import unicodedata
import torch
import math
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union
from dataclasses import dataclass
from collections import Counter
import polars as pl
from tqdm import tqdm
import json
import random
Além disso, utilizaremos Dataset para criar tipos diferentes de dados, Counter para criar contadores, polars para leitura rápida de datasets, tqdm para acompanhar o progresso do treinamento dos modelos, json e random para replicabilidade, e warnings para gerenciar avisos.
import warnings
from datasets import load_dataset
Importaremos também load_dataset do módulo datasets para carregar nosso dataset do Hugging Face. Do módulo torch.optim.lr_scheduler, importaremos a função CosineAnnealingWarmRestarts para ajustar a taxa de aprendizado durante o treinamento da rede neural. Utilizaremos torch para manipulação de tensores, amp para precisão mista, autocast e GradScaler para ajustes na descida do gradiente.
# Importar tokenizer pré-treinado
from transformers import AutoTokenizer
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
from torch.cuda.amp import autocast, GradScaler
Verificaremos se a CUDA está disponível para o treinamento, pois os dados são pesados. No entanto, para demonstração, utilizaremos CPU.
# Verificar se temos GPU disponível
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Usando: {device}")
if torch.cuda.is_available():
print(f"GPU: {torch.cuda.get_device_name(0)}")
Configuraremos a replicabilidade no PyTorch com torch.manual_seed(42) e np.random.seed(42). Se a CUDA estiver disponível, configuraremos torch.cuda.manual_seed(42).
torch.manual_seed(42)
np.random.seed(42)
random.seed(42)
if torch.cuda.is_available():
torch.cuda.manual_seed(42)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
Outro ponto importante é a importação da biblioteca huggingface_hub para realizar o login no Hugging Face com um token específico. Isso permitirá treinar o modelo sem problemas.
from huggingface_hub import login
login(token='hf_...')
Seguiremos para criar a classe do nosso dataset, que será baixado utilizando polars para uma rápida obtenção dos dados do Hugging Face. Concatenaremos esses dados para gerar nossa base de dados específica.
A classe será chamada WikipediaPortugueseLoader, representando a base de dados do Hugging Face, que contém o Wikipedia em português, extraído por Pablo Moreira. A base é extensa, por isso utilizaremos um limitador de caracteres.
class WikipediaPortugueseLoader:
def __init__(self, dataset_path: str = 'hf://datasets/pablo-moreira/wikipedia-pt/latest/train-*.parquet'):
print("Carregando dataset...")
self.df = pl.read_parquet(dataset_path, columns=['text'])
print(f"Carregado: {len(self.df):,} artigos")
Iniciaremos a classe com self.dataset_path, apontando para o caminho do dataset pablo-moreira/wikipedia-pt-latest, que possui duas subsets, sendo uma delas train. Utilizaremos polars.read_parquet para criar um dataframe a partir dos arquivos Parquet, focando na coluna de texto.
Dentro da classe, criaremos um módulo para carregar os dados, permitindo definir um limite de caracteres (padrão de 500 mil) e um separador opcional.
def carregar_tudo(self, limite_caracteres: int = 500_000, sep_token: str = "[SEP]",
inserir_sep_final: bool = True) -> str:
print("Processando textos...")
O dataset será processado para criar uma variável dataset_limpo, aplicando filtros e substituições com Regex para tratar os artigos textuais, removendo caracteres indesejados que possam dificultar o treinamento da rede neural.
# Limpa e filtra textos de forma rigorosa (conforme seu pipeline atual)
df_limpo = (
self.df
.filter(pl.col("text").str.len_chars() > 100) # pegar texto com mais de 100 caracteres
.with_columns([
pl.col("text")
# Remover elementos específicos da Wikipedia
.str.replace_all(r'\[\d+\]', ' ') # Referências [1], [2]
.str.replace_all(r'\{\{[^}]*\}\}', ' ') # Templates {{...}}
.str.replace_all(r'\[\[(?:[^\]|]*\|)?([^\]]+)\]\]', r'$1') # Links [[...]]
.str.replace_all(r'\[\[[ˆ\[\]]*\]\]', r'$1') # Links simples
# URLs, emails, HTML
.str.replace_all(r'https?://\S+|www\.\S+', ' ') # URLs
.str.replace_all(r'\S+@\w+\.\w+', ' ') # emails
.str.replace_all(r'<.*?>', ' ') # Tags HTML
.str.replace_all(r'&\w+;', ' ') # Entidades HTML (&, )
.str.replace_all(r'==.*==+', ' ') # Headers Wiki (==Título==)
# Caracteres de controle e problemáticos
.str.replace_all(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', ' ')
# Normalizar pontuação
.str.replace_all(r'["“”]', '"') # Aspas diferentes
.str.replace_all(r"['‘’]", "'") # Aspas simples
.str.replace_all(r'[–—]', '-') # Travessões
.str.replace_all(r'[\.]{3,}', '...') # Reticências
# Remover números longos sem contexto e símbolos matemáticos
.str.replace_all(r'\b\d{5,}\b', ' ') # Números muito longos
.str.replace_all(r'[=≈≠≤≥∑∫∏√∞∇∂∈∉∩∪⊂⊃∧∨¬∀∃]', ' ')
# Manter apenas caracteres válidos para português
.str.replace_all(r'[^a-zA-ZÀ-ú0-9\s.,;:!?-()\'"\[\]%]', ' ')
# Limpar múltipla pontuação
.str.replace_all(r'(\.){2,}', '.')
.str.replace_all(r'(,){2,}', ',')
.str.replace_all(r'(:){2,}', ':')
.str.replace_all(r'(;){2,}', ';')
.str.replace_all(r'(!){2,}', '!')
.str.replace_all(r'(\?){2,}', '?')
# Espaços e formatação final
.str.replace_all(r'\s+([.,;:!?])', r'$1') # Remove espaço antes de pontuação
.str.replace_all(r'\s+', ' ') # Múltiplos espaços
.str.strip_chars()
])
)
Realizamos diversos testes para identificar os tipos de elementos que mais apareciam e aplicamos os filtros necessários. Removemos números muito longos e mantemos apenas caracteres válidos do português. Caso haja alfabetos diferentes que possam causar problemas, fazemos a substituição da pontuação, transformando vírgulas em pontos, por exemplo. Ajustamos espaçamentos finais, removendo espaços antes de pontuações duplicadas e múltiplos espaços.
.filter(
(pl.col("text").str.len_chars() > 50) &
(pl.col("text").str.len_chars() < 5000) & # Não muito longo
# Pelo menos 70% de caracteres alfabéticos
(pl.col("text").str.count_matches(r'[a-zA-ZÀ-ú]') * 100 / pl.col("text").str.len_chars() > 70) &
# Deve ter pelo menos uma frase completa
(pl.col("text").str.contains(r'[.!?]\s+[A-ZÀ-ú]'))
)
Com o dataset pronto após a limpeza, verificamos o tamanho de cada artigo e convertemos o texto em listas, concatenando-os para gerar um texto extenso. Este texto é então separado em diferentes sentenças para treinar nosso modelo, formando nossas sequências de blocos.
print(f"Após limpeza: {len(df_limpo):,} artigos")
# Converter para lista mantendo ordem original
textos = df_limpo.get_column("text").to_list()
Para a concatenação, seguimos duas estratégias. A primeira utiliza um separador entre cada artigo, aplicando strip no texto e adicionando espaços, respeitando os limites dos caracteres e cortando os textos nas fronteiras. Assim, pegamos cada texto, pulamos uma linha, colocamos separadores e respeitamos os blocos de texto ao montar nosso dataset.
# --- Concatenação com DELIMITADOR [SEP]
# Estratégia:
# # Para cada artigo, adicionamos: "[SEP]\n" + texto.strip() + "\n"
# # Isso cria fronteiras claras entre documentos.
# # Respeitamos 'limite_caracteres' e cortamos o último texto em fronteira de frase, se necessário.
corpus_parts = []
total_len = 0
textos_adicionados = 0
# Pré-calculamos o custo fixo do prefixo/sufixo de cada bloco
prefix = f"{sep_token}\n"
suffix = "\n"
prefix_len = len(prefix)
suffix_len = len(suffix)
O corpus é uma lista que contém o comprimento total dos textos adicionais que somamos, pré-calculando os custos de cada texto. Iteramos sobre todos os textos, verificando se cada bloco cabe dentro do limite estabelecido. Se não couber, ele será limitado e adicionado à próxima sentença, subtraindo o número de caracteres e adicionando novamente. Isso é necessário devido ao tamanho da memória da GPU. Com uma memória maior, poderíamos carregar toda a base de dados e trabalhar de forma diferente, verificando sentenças semelhantes e criando clusters de palavras.
for texto in textos:
t = (texto or "").strip()
if not t:
continue
bloco = f"{prefix}{t}{suffix}"
bloco_len = len(bloco)
# Cabe inteiro?
if total_len + bloco_len <= limite_caracteres:
corpus_parts.append(bloco)
total_len += bloco_len
textos_adicionados += 1
continue
# Se não cabe inteiro, tentamos um corte "limpo" (até fim de frase) se houver espaço suficiente
restante = limite_caracteres - total_len
# Espaço disponível para o conteúdo (descontando prefixo/sufixo)
disponivel_para_texto = restante - prefix_len - suffix_len
if disponivel_para_texto > 100:
# Cortamos o texto no que cabe e tentamos encontrar a última sentença completa
parc = t[:max(0, disponivel_para_texto)]
# Procura o último delimitador de frase que caiba
ponto = max(parc.rfind('.'), parc.rfind('!'), parc.rfind('?'))
if ponto > 0:
bloco = f"{prefix}{parc[:ponto+1].rstrip()}{suffix}"
corpus_parts.append(bloco)
total_len += len(bloco)
textos_adicionados += 1
# Independente do corte, atingimos o limite
break
Verificamos a disponibilidade dos textos e adicionamos as delimitações, carregando-os na lista de corpus. No final, fazemos um join de cada parte do corpus em uma string, adicionando separadores específicos no início e no final, conforme as estratégias mencionadas.
corpus = "".join(corpus_parts).strip()
# [SEP] final para marcar término do último documento
if inserir_sep_final:
final_tag = f"{sep_token}\n"
if not corpus.endswith(final_tag):
# Se ainda houver espaço para adicionar o [SEP] final sem estourar o limite
if len(corpus) + len(final_tag) <= limite_caracteres:
corpus += ("\n" if not corpus.endswith("\n") else "") + final_tag
# Senão, ignora silenciosamente (prioriza conteúdo)
print(f"Concluído: {len(corpus):,} caracteres de {textos_adicionados:,} textos (limite: {limite_caracteres:,})")
return corpus
Criamos uma classe que faz o download e carrega o dataset em uma variável. Primeiramente, chamamos o loader, que será o nosso WikipediaPortugueseLoader, instanciamos e carregamos os textos, limitando a 5.000 caracteres para teste. O dataset é baixado e carregado na memória.
loader = WikipediaPortugueseLoader()
textos = loader.carregar_tudo(limite_caracteres=5_000)
Após a limpeza, verificamos o número de caracteres e as sentenças, resultando em 833 artigos que passaram pelos filtros. Com o limite de 5.000 caracteres, o texto é carregado. Ao verificar, o texto é extenso, com separadores e espaços no início. Ao realizar um split pelos separadores, obtemos cada linha de texto. Por exemplo, ao pegar um elemento, temos o primeiro texto, seguido pelo segundo, que é a primeira sentença dentro dos parâmetros verificados e limpos.
textos.split("[SEP]")
textos.split("[SEP]")[0]
textos.split("[SEP]")[1]
Na próxima aula, transformaremos o texto em formato numérico, tokenizando os dados. Podemos treinar um tokenizador, mas utilizaremos um pré-treinado, que possui um dicionário extenso de informações, incluindo palavras, sentenças, expressões e símbolos. Quanto mais palavras no dicionário, melhor será para o tokenizador, permitindo a criação de sentenças complexas e um treinamento mais eficaz.
Na próxima aula, utilizaremos o Batch, treinado com a língua portuguesa, para transformar nossos textos em tokens.
O curso Transformers: fundamentos e prática com PyTorch possui 136 minutos de vídeos, em um total de 40 atividades. Gostou? Conheça nossos outros cursos de IA para Dados em Inteligência Artificial, ou leia nossos artigos de Inteligência Artificial.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
2 anos de Alura
Matricule-se no plano PLUS 24 e garanta:
Jornada de estudos progressiva que te guia desde os fundamentos até a atuação prática. Você acompanha sua evolução, entende os próximos passos e se aprofunda nos conteúdos com quem é referência no mercado.
Mobile, Programação, Front-end, DevOps, UX & Design, Marketing Digital, Data Science, Inovação & Gestão, Inteligência Artificial
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você participa de eventos exclusivos, pode tirar dúvidas em estudos colaborativos e ainda conta com mentorias em grupo com especialistas de diversas áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
2 anos de Alura
Todos os benefícios do PLUS 24 e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Para estudantes ultra comprometidos atingirem seu objetivo mais rápido.
2 anos de Alura
Todos os benefícios do PRO 24 e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.
Conecte-se ao mercado com mentoria individual personalizada, vagas exclusivas e networking estratégico que impulsionam sua carreira tech para o próximo nível.