chore(i18n): sync translations with latest source changes (chunk 8/8, 60 changes)

This commit is contained in:
localizeflow[bot] 2026-01-30 01:46:12 +00:00
parent 0581d04b3f
commit 7f61888e3b
60 changed files with 19752 additions and 0 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,179 @@
# Detecção de Objetos
Os modelos de classificação de imagens que abordámos até agora recebiam uma imagem e produziam um resultado categórico, como a classe 'número' num problema MNIST. No entanto, em muitos casos, não queremos apenas saber que uma imagem retrata objetos - queremos determinar a sua localização precisa. Este é exatamente o objetivo da **detecção de objetos**.
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/21)
![Detecção de Objetos](../../../../../translated_images/pt-PT/Screen_Shot_2016-11-17_at_11.14.54_AM.b4bb3769353287be.webp)
> Imagem do [site YOLO v2](https://pjreddie.com/darknet/yolov2/)
## Uma Abordagem Ingénua para Detecção de Objetos
Supondo que queremos encontrar um gato numa imagem, uma abordagem muito ingénua para detecção de objetos seria a seguinte:
1. Dividir a imagem em vários blocos.
2. Executar a classificação de imagem em cada bloco.
3. Os blocos que resultarem numa ativação suficientemente alta podem ser considerados como contendo o objeto em questão.
![Detecção Ingénua de Objetos](../../../../../translated_images/pt-PT/naive-detection.e7f1ba220ccd08c6.webp)
> *Imagem do [Caderno de Exercícios](ObjectDetection-TF.ipynb)*
No entanto, esta abordagem está longe de ser ideal, pois só permite ao algoritmo localizar a caixa delimitadora do objeto de forma muito imprecisa. Para uma localização mais precisa, precisamos de executar algum tipo de **regressão** para prever as coordenadas das caixas delimitadoras - e, para isso, necessitamos de conjuntos de dados específicos.
## Regressão para Detecção de Objetos
[Este artigo](https://towardsdatascience.com/object-detection-with-neural-networks-a4e2c46b4491) oferece uma excelente introdução à deteção de formas.
## Conjuntos de Dados para Detecção de Objetos
Poderá encontrar os seguintes conjuntos de dados para esta tarefa:
* [PASCAL VOC](http://host.robots.ox.ac.uk/pascal/VOC/) - 20 classes
* [COCO](http://cocodataset.org/#home) - Objetos Comuns em Contexto. 80 classes, caixas delimitadoras e máscaras de segmentação
![COCO](../../../../../translated_images/pt-PT/coco-examples.71bc60380fa6cceb.webp)
## Métricas de Detecção de Objetos
### Interseção sobre União
Enquanto na classificação de imagens é fácil medir o desempenho do algoritmo, na detecção de objetos precisamos de medir tanto a correção da classe como a precisão da localização da caixa delimitadora inferida. Para esta última, utilizamos a chamada **Interseção sobre União** (IoU), que mede o quão bem duas caixas (ou duas áreas arbitrárias) se sobrepõem.
![IoU](../../../../../translated_images/pt-PT/iou_equation.9a4751d40fff4e11.webp)
> *Figura 2 de [este excelente artigo sobre IoU](https://pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/)*
A ideia é simples - dividimos a área de interseção entre duas figuras pela área da sua união. Para duas áreas idênticas, o IoU seria 1, enquanto para áreas completamente disjuntas será 0. Caso contrário, variará entre 0 e 1. Normalmente, consideramos apenas as caixas delimitadoras para as quais o IoU está acima de um determinado valor.
### Precisão Média
Suponha que queremos medir o quão bem uma determinada classe de objetos $C$ é reconhecida. Para medir isso, utilizamos a métrica de **Precisão Média**, que é calculada da seguinte forma:
1. Consideramos a curva de Precisão-Recall que mostra a precisão dependendo de um valor de limiar de deteção (de 0 a 1).
2. Dependendo do limiar, obteremos mais ou menos objetos detetados na imagem e diferentes valores de precisão e recall.
3. A curva terá este aspeto:
<img src="https://github.com/shwars/NeuroWorkshop/raw/master/images/ObjDetectionPrecisionRecall.png"/>
> *Imagem de [NeuroWorkshop](http://github.com/shwars/NeuroWorkshop)*
A Precisão Média para uma classe $C$ é a área sob esta curva. Mais precisamente, o eixo Recall é normalmente dividido em 10 partes, e a Precisão é calculada como média em todos esses pontos:
$$
AP = {1\over11}\sum_{i=0}^{10}\mbox{Precision}(\mbox{Recall}={i\over10})
$$
### AP e IoU
Consideramos apenas as deteções para as quais o IoU está acima de um determinado valor. Por exemplo, no conjunto de dados PASCAL VOC, normalmente $\mbox{IoU Threshold} = 0.5$ é assumido, enquanto no COCO o AP é medido para diferentes valores de $\mbox{IoU Threshold}$.
<img src="https://github.com/shwars/NeuroWorkshop/raw/master/images/ObjDetectionPrecisionRecallIoU.png"/>
> *Imagem de [NeuroWorkshop](http://github.com/shwars/NeuroWorkshop)*
### Precisão Média Global - mAP
A principal métrica para Detecção de Objetos é chamada de **Precisão Média Global**, ou **mAP**. É o valor da Precisão Média, calculado como média entre todas as classes de objetos, e às vezes também sobre $\mbox{IoU Threshold}$. O processo de cálculo do **mAP** é descrito em mais detalhe
[neste artigo](https://medium.com/@timothycarlen/understanding-the-map-evaluation-metric-for-object-detection-a07fe6962cf3)), e também [aqui com exemplos de código](https://gist.github.com/tarlen5/008809c3decf19313de216b9208f3734).
## Diferentes Abordagens para Detecção de Objetos
Existem duas grandes classes de algoritmos de detecção de objetos:
* **Redes de Proposta de Região** (R-CNN, Fast R-CNN, Faster R-CNN). A ideia principal é gerar **Regiões de Interesse** (ROI) e executar CNN sobre elas, procurando a ativação máxima. É um pouco semelhante à abordagem ingénua, com a exceção de que as ROIs são geradas de forma mais inteligente. Uma das principais desvantagens desses métodos é que são lentos, pois necessitam de várias passagens do classificador CNN sobre a imagem.
* Métodos de **uma única passagem** (YOLO, SSD, RetinaNet). Nessas arquiteturas, projetamos a rede para prever tanto as classes como as ROIs numa única passagem.
### R-CNN: CNN Baseada em Região
[R-CNN](http://islab.ulsan.ac.kr/files/announcement/513/rcnn_pami.pdf) utiliza [Selective Search](http://www.huppelen.nl/publications/selectiveSearchDraft.pdf) para gerar uma estrutura hierárquica de regiões ROI, que são então passadas por extratores de características CNN e classificadores SVM para determinar a classe do objeto, e regressão linear para determinar as coordenadas da *caixa delimitadora*. [Artigo Oficial](https://arxiv.org/pdf/1506.01497v1.pdf)
![RCNN](../../../../../translated_images/pt-PT/rcnn1.cae407020dfb1d1f.webp)
> *Imagem de van de Sande et al. ICCV11*
![RCNN-1](../../../../../translated_images/pt-PT/rcnn2.2d9530bb83516484.webp)
> *Imagens de [este artigo](https://towardsdatascience.com/r-cnn-fast-r-cnn-faster-r-cnn-yolo-object-detection-algorithms-36d53571365e)*
### F-RCNN - Fast R-CNN
Esta abordagem é semelhante à R-CNN, mas as regiões são definidas após as camadas de convolução terem sido aplicadas.
![FRCNN](../../../../../translated_images/pt-PT/f-rcnn.3cda6d9bb4188875.webp)
> Imagem do [Artigo Oficial](https://www.cv-foundation.org/openaccess/content_iccv_2015/papers/Girshick_Fast_R-CNN_ICCV_2015_paper.pdf), [arXiv](https://arxiv.org/pdf/1504.08083.pdf), 2015
### Faster R-CNN
A ideia principal desta abordagem é usar uma rede neural para prever ROIs - a chamada *Rede de Proposta de Região*. [Artigo](https://arxiv.org/pdf/1506.01497.pdf), 2016
![FasterRCNN](../../../../../translated_images/pt-PT/faster-rcnn.8d46c099b87ef30a.webp)
> Imagem do [artigo oficial](https://arxiv.org/pdf/1506.01497.pdf)
### R-FCN: Rede Totalmente Convolucional Baseada em Região
Este algoritmo é ainda mais rápido que o Faster R-CNN. A ideia principal é a seguinte:
1. Extraímos características usando ResNet-101.
1. As características são processadas por **Position-Sensitive Score Map**. Cada objeto das classes $C$ é dividido em regiões $k\times k$, e treinamos para prever partes dos objetos.
1. Para cada parte das regiões $k\times k$, todas as redes votam pelas classes de objetos, e a classe de objeto com o voto máximo é selecionada.
![r-fcn image](../../../../../translated_images/pt-PT/r-fcn.13eb88158b99a3da.webp)
> Imagem do [artigo oficial](https://arxiv.org/abs/1605.06409)
### YOLO - You Only Look Once
YOLO é um algoritmo de uma única passagem em tempo real. A ideia principal é a seguinte:
* A imagem é dividida em regiões $S\times S$.
* Para cada região, **CNN** prevê $n$ objetos possíveis, coordenadas da *caixa delimitadora* e *confiança*=*probabilidade* * IoU.
![YOLO](../../../../../translated_images/pt-PT/yolo.a2648ec82ee8bb4e.webp)
> Imagem do [artigo oficial](https://arxiv.org/abs/1506.02640)
### Outros Algoritmos
* RetinaNet: [artigo oficial](https://arxiv.org/abs/1708.02002)
- [Implementação PyTorch em Torchvision](https://pytorch.org/vision/stable/_modules/torchvision/models/detection/retinanet.html)
- [Implementação Keras](https://github.com/fizyr/keras-retinanet)
- [Detecção de Objetos com RetinaNet](https://keras.io/examples/vision/retinanet/) em exemplos Keras
* SSD (Single Shot Detector): [artigo oficial](https://arxiv.org/abs/1512.02325)
## ✍️ Exercícios: Detecção de Objetos
Continue a sua aprendizagem no seguinte caderno:
[ObjectDetection.ipynb](ObjectDetection.ipynb)
## Conclusão
Nesta lição, fez uma rápida exploração de todas as várias formas de realizar detecção de objetos!
## 🚀 Desafio
Leia estes artigos e cadernos sobre YOLO e experimente por si mesmo:
* [Bom artigo](https://www.analyticsvidhya.com/blog/2018/12/practical-guide-object-detection-yolo-framewor-python/) descrevendo YOLO
* [Site oficial](https://pjreddie.com/darknet/yolo/)
* YOLO: [Implementação Keras](https://github.com/experiencor/keras-yolo2), [caderno passo-a-passo](https://github.com/experiencor/basic-yolo-keras/blob/master/Yolo%20Step-by-Step.ipynb)
* YOLO v2: [Implementação Keras](https://github.com/experiencor/keras-yolo2), [caderno passo-a-passo](https://github.com/experiencor/keras-yolo2/blob/master/Yolo%20Step-by-Step.ipynb)
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/22)
## Revisão e Autoestudo
* [Detecção de Objetos](https://tjmachinelearning.com/lectures/1718/obj/) por Nikhil Sardana
* [Uma boa comparação de algoritmos de detecção de objetos](https://lilianweng.github.io/lil-log/2018/12/27/object-detection-part-4.html)
* [Revisão de Algoritmos de Aprendizagem Profunda para Detecção de Objetos](https://medium.com/comet-app/review-of-deep-learning-algorithms-for-object-detection-c1f3d437b852)
* [Uma Introdução Passo-a-Passo aos Algoritmos Básicos de Detecção de Objetos](https://www.analyticsvidhya.com/blog/2018/10/a-step-by-step-introduction-to-the-basic-object-detection-algorithms-part-1/)
* [Implementação de Faster R-CNN em Python para Detecção de Objetos](https://www.analyticsvidhya.com/blog/2018/11/implementation-faster-r-cnn-python-object-detection/)
## [Tarefa: Detecção de Objetos](lab/README.md)
---

View File

@ -0,0 +1,66 @@
# Deteção de Cabeças usando o Hollywood Heads Dataset
Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).
## Tarefa
Contar o número de pessoas num fluxo de vídeo de uma câmara de vigilância é uma tarefa importante que nos permite estimar o número de visitantes em lojas, as horas de maior movimento num restaurante, etc. Para resolver esta tarefa, precisamos de ser capazes de detetar cabeças humanas a partir de diferentes ângulos. Para treinar um modelo de deteção de objetos para identificar cabeças humanas, podemos usar o [Hollywood Heads Dataset](https://www.di.ens.fr/willow/research/headdetection/).
## O Conjunto de Dados
O [Hollywood Heads Dataset](https://www.di.ens.fr/willow/research/headdetection/release/HollywoodHeads.zip) contém 369.846 cabeças humanas anotadas em 224.740 frames de filmes de Hollywood. É fornecido no formato [https://host.robots.ox.ac.uk/pascal/VOC/](../../../../../../lessons/4-ComputerVision/11-ObjectDetection/lab/PASCAL VOC), onde para cada imagem existe também um ficheiro de descrição XML que se parece com isto:
```xml
<annotation>
<folder>HollywoodHeads</folder>
<filename>mov_021_149390.jpeg</filename>
<source>
<database>HollywoodHeads 2015 Database</database>
<annotation>HollywoodHeads 2015</annotation>
<image>WILLOW</image>
</source>
<size>
<width>608</width>
<height>320</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>head</name>
<bndbox>
<xmin>201</xmin>
<ymin>1</ymin>
<xmax>480</xmax>
<ymax>263</ymax>
</bndbox>
<difficult>0</difficult>
</object>
<object>
<name>head</name>
<bndbox>
<xmin>3</xmin>
<ymin>4</ymin>
<xmax>241</xmax>
<ymax>285</ymax>
</bndbox>
<difficult>0</difficult>
</object>
</annotation>
```
Neste conjunto de dados, existe apenas uma classe de objetos, `head`, e para cada cabeça, obtém-se as coordenadas da caixa delimitadora. Pode-se analisar os ficheiros XML usando bibliotecas Python ou usar [esta biblioteca](https://pypi.org/project/pascal-voc/) para lidar diretamente com o formato PASCAL VOC.
## Treinar a Deteção de Objetos
Pode treinar um modelo de deteção de objetos utilizando uma das seguintes abordagens:
* Usar o [Azure Custom Vision](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/quickstarts/object-detection?tabs=visual-studio&WT.mc_id=academic-77998-cacaste) e a sua API Python para treinar programaticamente o modelo na cloud. O Custom Vision não consegue usar mais do que algumas centenas de imagens para treinar o modelo, por isso pode ser necessário limitar o conjunto de dados.
* Usar o exemplo do [tutorial do Keras](https://keras.io/examples/vision/retinanet/) para treinar o modelo RetunaNet.
* Usar o módulo integrado [torchvision.models.detection.RetinaNet](https://pytorch.org/vision/stable/_modules/torchvision/models/detection/retinanet.html) no torchvision.
## Conclusão
A deteção de objetos é uma tarefa frequentemente necessária na indústria. Embora existam alguns serviços que podem ser usados para realizar a deteção de objetos (como o [Azure Custom Vision](https://docs.microsoft.com/azure/cognitive-services/custom-vision-service/quickstarts/object-detection?tabs=visual-studio&WT.mc_id=academic-77998-cacaste)), é importante compreender como funciona a deteção de objetos e ser capaz de treinar os seus próprios modelos.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, tenha em atenção que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.

View File

@ -0,0 +1,69 @@
# Segmentação
Já aprendemos sobre Detecção de Objetos, que nos permite localizar objetos numa imagem ao prever os seus *bounding boxes*. No entanto, para algumas tarefas, não precisamos apenas de bounding boxes, mas também de uma localização mais precisa dos objetos. Esta tarefa chama-se **segmentação**.
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/23)
A segmentação pode ser vista como **classificação de píxeis**, onde para **cada** píxel da imagem devemos prever a sua classe (*fundo* sendo uma das classes). Existem dois principais algoritmos de segmentação:
* **Segmentação semântica** apenas indica a classe do píxel, sem distinguir entre diferentes objetos da mesma classe.
* **Segmentação por instância** divide as classes em diferentes instâncias.
Na segmentação por instância, estas ovelhas são objetos diferentes, mas na segmentação semântica todas as ovelhas são representadas por uma única classe.
<img src="../../../../../translated_images/pt-PT/instance_vs_semantic.eee9812bebf8cd45.webp" width="50%">
> Imagem retirada [deste artigo](https://nirmalamurali.medium.com/image-classification-vs-semantic-segmentation-vs-instance-segmentation-625c33a08d50)
Existem diferentes arquiteturas neurais para segmentação, mas todas têm a mesma estrutura. De certa forma, é semelhante ao autoencoder que aprendeste anteriormente, mas em vez de desconstruir a imagem original, o nosso objetivo é desconstruir uma **máscara**. Assim, uma rede de segmentação tem as seguintes partes:
* **Codificador (Encoder)** extrai características da imagem de entrada.
* **Decodificador (Decoder)** transforma essas características na **imagem de máscara**, com o mesmo tamanho e número de canais correspondentes ao número de classes.
<img src="../../../../../translated_images/pt-PT/segm.92442f2cb42ff4fa.webp" width="80%">
> Imagem retirada [desta publicação](https://arxiv.org/pdf/2001.05566.pdf)
Devemos destacar especialmente a função de perda utilizada na segmentação. Ao usar autoencoders clássicos, precisamos medir a similaridade entre duas imagens, e podemos usar o erro quadrático médio (MSE) para isso. Na segmentação, cada píxel na imagem de máscara alvo representa o número da classe (codificado em one-hot ao longo da terceira dimensão), então precisamos usar funções de perda específicas para classificação - perda de entropia cruzada, média sobre todos os píxeis. Se a máscara for binária, utiliza-se a **perda de entropia cruzada binária** (BCE).
> ✅ A codificação one-hot é uma forma de codificar um rótulo de classe num vetor de comprimento igual ao número de classes. Dá uma vista de olhos [neste artigo](https://datagy.io/sklearn-one-hot-encode/) sobre esta técnica.
## Segmentação em Imagens Médicas
Nesta lição, veremos a segmentação em ação ao treinar uma rede para reconhecer nevos humanos (também conhecidos como sinais) em imagens médicas. Utilizaremos a <a href="https://www.fc.up.pt/addi/ph2%20database.html">Base de Dados PH<sup>2</sup></a> de imagens dermatoscópicas como fonte de imagens. Este conjunto de dados contém 200 imagens de três classes: nevo típico, nevo atípico e melanoma. Todas as imagens também contêm uma **máscara** correspondente que delineia o nevo.
> ✅ Esta técnica é particularmente adequada para este tipo de imagens médicas, mas que outras aplicações do mundo real consegues imaginar?
<img alt="navi" src="../../../../../translated_images/pt-PT/navi.2f20b727910110ea.webp"/>
> Imagem retirada da Base de Dados PH<sup>2</sup>
Vamos treinar um modelo para segmentar qualquer nevo do seu fundo.
## ✍️ Exercícios: Segmentação Semântica
Abre os notebooks abaixo para aprender mais sobre diferentes arquiteturas de segmentação semântica, praticar com elas e vê-las em ação.
* [Segmentação Semântica Pytorch](SemanticSegmentationPytorch.ipynb)
* [Segmentação Semântica TensorFlow](SemanticSegmentationTF.ipynb)
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/24)
## Conclusão
A segmentação é uma técnica muito poderosa para classificação de imagens, indo além dos bounding boxes para a classificação a nível de píxeis. É uma técnica utilizada em imagens médicas, entre outras aplicações.
## 🚀 Desafio
A segmentação corporal é apenas uma das tarefas comuns que podemos realizar com imagens de pessoas. Outras tarefas importantes incluem **detecção de esqueleto** e **detecção de pose**. Experimenta a biblioteca [OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose) para ver como a detecção de pose pode ser utilizada.
## Revisão & Estudo Autónomo
Este [artigo da Wikipédia](https://wikipedia.org/wiki/Image_segmentation) oferece uma boa visão geral das várias aplicações desta técnica. Aprende mais por conta própria sobre os subdomínios de Segmentação por Instância e Segmentação Panóptica neste campo de estudo.
## [Trabalho prático](lab/README.md)
Neste laboratório, experimenta **segmentação do corpo humano** utilizando o [Segmentation Full Body MADS Dataset](https://www.kaggle.com/datasets/tapakah68/segmentation-full-body-mads-dataset) do Kaggle.
---

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,22 @@
# Segmentação do Corpo Humano
Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).
## Tarefa
Na produção de vídeo, como em previsões meteorológicas, muitas vezes precisamos recortar a imagem de uma pessoa captada pela câmara e colocá-la sobre outro vídeo. Isto é normalmente feito utilizando técnicas de **chroma key**, onde uma pessoa é filmada em frente a um fundo de cor uniforme, que é posteriormente removido. Neste trabalho prático, iremos treinar um modelo de rede neural para recortar a silhueta humana.
## O Conjunto de Dados
Iremos utilizar o [Segmentation Full Body MADS Dataset](https://www.kaggle.com/datasets/tapakah68/segmentation-full-body-mads-dataset) do Kaggle. Faça o download manual do conjunto de dados no Kaggle.
## Notebook Inicial
Comece o trabalho prático abrindo [BodySegmentation.ipynb](../../../../../../lessons/4-ComputerVision/12-Segmentation/lab/BodySegmentation.ipynb)
## Conclusão
A segmentação do corpo é apenas uma das tarefas comuns que podemos realizar com imagens de pessoas. Outras tarefas importantes incluem **detecção de esqueleto** e **detecção de pose**. Explore a biblioteca [OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose) para ver como essas tarefas podem ser implementadas.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.

View File

@ -0,0 +1,16 @@
# Visão Computacional
![Resumo do conteúdo de Visão Computacional em um desenho](../../../../lessons/sketchnotes/ai-computervision.png)
Nesta seção, vamos aprender sobre:
* [Introdução à Visão Computacional e OpenCV](06-IntroCV/README.md)
* [Redes Neurais Convolucionais](07-ConvNets/README.md)
* [Redes Pré-treinadas e Aprendizagem por Transferência](08-TransferLearning/README.md)
* [Autoencoders](09-Autoencoders/README.md)
* [Redes Adversárias Generativas](10-GANs/README.md)
* [Deteção de Objetos](11-ObjectDetection/README.md)
* [Segmentação Semântica](12-Segmentation/README.md)
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, tenha em atenção que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.

View File

@ -0,0 +1,76 @@
# Representar Texto como Tensores
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/25)
## Classificação de Texto
Ao longo da primeira parte desta secção, vamos focar-nos na tarefa de **classificação de texto**. Utilizaremos o Dataset [AG News](https://www.kaggle.com/amananandrai/ag-news-classification-dataset), que contém artigos de notícias como o seguinte:
* Categoria: Ciência/Tecnologia
* Título: Empresa de Ky. Ganha Subsídio para Estudar Peptídeos (AP)
* Corpo: AP - Uma empresa fundada por um investigador de química da Universidade de Louisville ganhou um subsídio para desenvolver...
O nosso objetivo será classificar o artigo de notícias numa das categorias com base no texto.
## Representar texto
Se quisermos resolver tarefas de Processamento de Linguagem Natural (NLP) com redes neuronais, precisamos de uma forma de representar texto como tensores. Os computadores já representam caracteres textuais como números que mapeiam para fontes no ecrã utilizando codificações como ASCII ou UTF-8.
<img alt="Imagem mostrando um diagrama que mapeia um caractere para uma representação ASCII e binária" src="../../../../../translated_images/pt-PT/ascii-character-map.18ed6aa7f3b0a7ff.webp" width="50%"/>
> [Fonte da imagem](https://www.seobility.net/en/wiki/ASCII)
Como humanos, entendemos o que cada letra **representa** e como todos os caracteres se juntam para formar as palavras de uma frase. No entanto, os computadores, por si só, não têm essa compreensão, e a rede neuronal tem de aprender o significado durante o treino.
Portanto, podemos usar diferentes abordagens para representar texto:
* **Representação ao nível de caracteres**, onde representamos o texto tratando cada caractere como um número. Dado que temos *C* caracteres diferentes no nosso corpus de texto, a palavra *Hello* seria representada por um tensor de 5x*C*. Cada letra corresponderia a uma coluna do tensor em codificação one-hot.
* **Representação ao nível de palavras**, na qual criamos um **vocabulário** de todas as palavras no nosso texto e, em seguida, representamos as palavras usando codificação one-hot. Esta abordagem é de certa forma melhor, porque cada letra, por si só, não tem muito significado, e assim, ao usar conceitos semânticos de nível superior - palavras - simplificamos a tarefa para a rede neuronal. No entanto, dado o tamanho do dicionário, precisamos lidar com tensores esparsos de alta dimensão.
Independentemente da representação, primeiro precisamos converter o texto numa sequência de **tokens**, sendo um token um caractere, uma palavra ou, por vezes, até parte de uma palavra. Depois, convertemos o token num número, normalmente usando um **vocabulário**, e este número pode ser alimentado numa rede neuronal usando codificação one-hot.
## N-Gramas
Na linguagem natural, o significado preciso das palavras só pode ser determinado no contexto. Por exemplo, os significados de *rede neural* e *rede de pesca* são completamente diferentes. Uma das formas de levar isso em conta é construir o nosso modelo com pares de palavras, considerando os pares de palavras como tokens separados no vocabulário. Desta forma, a frase *Eu gosto de ir pescar* será representada pela seguinte sequência de tokens: *Eu gosto*, *gosto de*, *de ir*, *ir pescar*. O problema com esta abordagem é que o tamanho do dicionário cresce significativamente, e combinações como *ir pescar* e *ir às compras* são apresentadas por tokens diferentes, que não partilham qualquer semelhança semântica, apesar do mesmo verbo.
Em alguns casos, podemos considerar usar tri-gramas -- combinações de três palavras -- também. Assim, esta abordagem é frequentemente chamada de **n-gramas**. Além disso, faz sentido usar n-gramas com representação ao nível de caracteres, caso em que os n-gramas corresponderão aproximadamente a diferentes sílabas.
## Bag-of-Words e TF/IDF
Ao resolver tarefas como classificação de texto, precisamos de ser capazes de representar o texto por um vetor de tamanho fixo, que usaremos como entrada para o classificador denso final. Uma das formas mais simples de fazer isso é combinar todas as representações individuais das palavras, por exemplo, somando-as. Se somarmos as codificações one-hot de cada palavra, acabaremos com um vetor de frequências, mostrando quantas vezes cada palavra aparece no texto. Tal representação de texto é chamada de **bag of words** (BoW).
<img src="../../../../../translated_images/pt-PT/bow.3811869cff59368d.webp" width="90%"/>
> Imagem do autor
Um BoW essencialmente representa quais palavras aparecem no texto e em que quantidades, o que pode ser uma boa indicação do tema do texto. Por exemplo, um artigo de notícias sobre política provavelmente contém palavras como *presidente* e *país*, enquanto uma publicação científica teria algo como *colisor*, *descoberto*, etc. Assim, as frequências das palavras podem, em muitos casos, ser um bom indicador do conteúdo do texto.
O problema com BoW é que certas palavras comuns, como *e*, *é*, etc., aparecem na maioria dos textos e têm as maiores frequências, ocultando as palavras que são realmente importantes. Podemos reduzir a importância dessas palavras levando em conta a frequência com que ocorrem em toda a coleção de documentos. Esta é a ideia principal por trás da abordagem TF/IDF, que é abordada em mais detalhe nos notebooks anexados a esta lição.
No entanto, nenhuma dessas abordagens consegue levar totalmente em conta a **semântica** do texto. Precisamos de modelos de redes neuronais mais poderosos para fazer isso, o que discutiremos mais tarde nesta secção.
## ✍️ Exercícios: Representação de Texto
Continue a sua aprendizagem nos seguintes notebooks:
* [Representação de Texto com PyTorch](TextRepresentationPyTorch.ipynb)
* [Representação de Texto com TensorFlow](TextRepresentationTF.ipynb)
## Conclusão
Até agora, estudámos técnicas que podem adicionar peso de frequência a diferentes palavras. No entanto, elas não conseguem representar o significado ou a ordem. Como o famoso linguista J. R. Firth disse em 1935, "O significado completo de uma palavra é sempre contextual, e nenhum estudo de significado fora do contexto pode ser levado a sério." Aprenderemos mais tarde no curso como capturar informações contextuais do texto usando modelagem de linguagem.
## 🚀 Desafio
Experimente outros exercícios usando bag-of-words e diferentes modelos de dados. Pode inspirar-se nesta [competição no Kaggle](https://www.kaggle.com/competitions/word2vec-nlp-tutorial/overview/part-1-for-beginners-bag-of-words)
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/26)
## Revisão & Autoestudo
Pratique as suas competências com técnicas de embeddings de texto e bag-of-words em [Microsoft Learn](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-pytorch/?WT.mc_id=academic-77998-cacaste)
## [Tarefa: Notebooks](assignment.md)
---

View File

@ -0,0 +1,577 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tarefa de classificação de texto\n",
"\n",
"Como mencionámos, iremos focar-nos numa tarefa simples de classificação de texto baseada no conjunto de dados **AG_NEWS**, que consiste em classificar manchetes de notícias em uma de 4 categorias: Mundo, Desporto, Negócios e Ciência/Tecnologia.\n",
"\n",
"## O Conjunto de Dados\n",
"\n",
"Este conjunto de dados está integrado no módulo [`torchtext`](https://github.com/pytorch/text), o que nos permite aceder a ele facilmente.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"import torchtext\n",
"import os\n",
"import collections\n",
"os.makedirs('./data',exist_ok=True)\n",
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aqui, `train_dataset` e `test_dataset` contêm coleções que retornam pares de etiqueta (número da classe) e texto, respetivamente, por exemplo:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(3,\n",
" \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(train_dataset)[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Então, vamos imprimir as primeiras 10 novas manchetes do nosso conjunto de dados:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n",
"**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n",
"**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n",
"**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n",
"**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n"
]
}
],
"source": [
"for i,x in zip(range(5),train_dataset):\n",
" print(f\"**{classes[x[0]]}** -> {x[1]}\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Porque os conjuntos de dados são iteradores, se quisermos usar os dados várias vezes, precisamos convertê-los para uma lista:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
"train_dataset = list(train_dataset)\n",
"test_dataset = list(test_dataset)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tokenização\n",
"\n",
"Agora precisamos converter o texto em **números** que possam ser representados como tensores. Se quisermos uma representação ao nível de palavras, precisamos fazer duas coisas: \n",
"* usar um **tokenizador** para dividir o texto em **tokens** \n",
"* construir um **vocabulário** desses tokens. \n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['he', 'said', 'hello']"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
"tokenizer('He said: hello')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter, min_freq=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Usando o vocabulário, podemos facilmente codificar a nossa sequência tokenizada num conjunto de números:\n"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocab size if 95810\n"
]
},
{
"data": {
"text/plain": [
"[599, 3279, 97, 1220, 329, 225, 7368]"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vocab_size = len(vocab)\n",
"print(f\"Vocab size if {vocab_size}\")\n",
"\n",
"stoi = vocab.get_stoi() # dict to convert tokens to indices\n",
"\n",
"def encode(x):\n",
" return [stoi[s] for s in tokenizer(x)]\n",
"\n",
"encode('I love to play with my words')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Representação de texto com Bag of Words\n",
"\n",
"Como as palavras representam significado, às vezes conseguimos compreender o significado de um texto apenas analisando as palavras individuais, independentemente da sua ordem na frase. Por exemplo, ao classificar notícias, palavras como *meteorologia*, *neve* provavelmente indicam *previsão do tempo*, enquanto palavras como *ações*, *dólar* seriam associadas a *notícias financeiras*.\n",
"\n",
"A representação vetorial **Bag of Words** (BoW) é a representação vetorial tradicional mais utilizada. Cada palavra é associada a um índice do vetor, e o elemento do vetor contém o número de ocorrências de uma palavra num determinado documento.\n",
"\n",
"![Imagem mostrando como uma representação vetorial Bag of Words é armazenada na memória.](../../../../../lessons/5-NLP/13-TextRep/images/bag-of-words-example.png) \n",
"\n",
"> **Nota**: Também pode pensar no BoW como a soma de todos os vetores one-hot-encoded para as palavras individuais no texto.\n",
"\n",
"Abaixo está um exemplo de como gerar uma representação Bag of Words utilizando a biblioteca python Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import CountVectorizer\n",
"vectorizer = CountVectorizer()\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para calcular o vetor bag-of-words a partir da representação vetorial do nosso conjunto de dados AG_NEWS, podemos usar a seguinte função:\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tensor([2., 1., 2., ..., 0., 0., 0.])\n"
]
}
],
"source": [
"vocab_size = len(vocab)\n",
"\n",
"def to_bow(text,bow_vocab_size=vocab_size):\n",
" res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n",
" for i in encode(text):\n",
" if i<bow_vocab_size:\n",
" res[i] += 1\n",
" return res\n",
"\n",
"print(to_bow(train_dataset[0][1]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota:** Aqui estamos a usar a variável global `vocab_size` para especificar o tamanho padrão do vocabulário. Como frequentemente o tamanho do vocabulário é bastante grande, podemos limitar o tamanho do vocabulário às palavras mais frequentes. Experimente reduzir o valor de `vocab_size` e executar o código abaixo, e veja como isso afeta a precisão. Deve esperar alguma queda na precisão, mas não dramática, em troca de um desempenho superior.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Treinar o classificador BoW\n",
"\n",
"Agora que aprendemos a construir a representação Bag-of-Words do nosso texto, vamos treinar um classificador com base nela. Primeiro, precisamos converter o nosso conjunto de dados para treino de forma que todas as representações vetoriais posicionais sejam convertidas para a representação bag-of-words. Isto pode ser feito passando a função `bowify` como o parâmetro `collate_fn` para o `DataLoader` padrão do torch:\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"from torch.utils.data import DataLoader\n",
"import numpy as np \n",
"\n",
"# this collate function gets list of batch_size tuples, and needs to \n",
"# return a pair of label-feature tensors for the whole minibatch\n",
"def bowify(b):\n",
" return (\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([to_bow(t[1]) for t in b])\n",
" )\n",
"\n",
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos definir uma rede neural de classificação simples que contém uma camada linear. O tamanho do vetor de entrada é igual a `vocab_size`, e o tamanho da saída corresponde ao número de classes (4). Como estamos a resolver uma tarefa de classificação, a função de ativação final é `LogSoftmax()`.\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos definir o ciclo de treino padrão do PyTorch. Como o nosso conjunto de dados é bastante grande, para fins de ensino iremos treinar apenas por uma época, e por vezes até menos do que uma época (especificar o parâmetro `epoch_size` permite-nos limitar o treino). Também iremos reportar a precisão acumulada durante o treino; a frequência de reporte é especificada utilizando o parâmetro `report_freq`.\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
" net.train()\n",
" total_loss,acc,count,i = 0,0,0,0\n",
" for labels,features in dataloader:\n",
" optimizer.zero_grad()\n",
" out = net(features)\n",
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss+=loss\n",
" _,predicted = torch.max(out,1)\n",
" acc+=(predicted==labels).sum()\n",
" count+=len(labels)\n",
" i+=1\n",
" if i%report_freq==0:\n",
" print(f\"{count}: acc={acc.item()/count}\")\n",
" if epoch_size and count>epoch_size:\n",
" break\n",
" return total_loss.item()/count, acc.item()/count"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.8028125\n",
"6400: acc=0.8371875\n",
"9600: acc=0.8534375\n",
"12800: acc=0.85765625\n"
]
},
{
"data": {
"text/plain": [
"(0.026090790722161722, 0.8620069296375267)"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_epoch(net,train_loader,epoch_size=15000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## BiGramas, TriGramas e N-Gramas\n",
"\n",
"Uma limitação da abordagem de saco de palavras é que algumas palavras fazem parte de expressões compostas por várias palavras. Por exemplo, a palavra 'hot dog' tem um significado completamente diferente das palavras 'hot' e 'dog' em outros contextos. Se representarmos as palavras 'hot' e 'dog' sempre pelos mesmos vetores, isso pode confundir o nosso modelo.\n",
"\n",
"Para resolver este problema, as **representações N-gramas** são frequentemente utilizadas em métodos de classificação de documentos, onde a frequência de cada palavra, bi-palavra ou tri-palavra é uma característica útil para treinar classificadores. Na representação de bigramas, por exemplo, adicionamos todos os pares de palavras ao vocabulário, além das palavras originais.\n",
"\n",
"Abaixo está um exemplo de como gerar uma representação de saco de palavras com bigramas usando o Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary:\n",
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
]
},
{
"data": {
"text/plain": [
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
" dtype=int64)"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"bigram_vectorizer.fit_transform(corpus)\n",
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A principal desvantagem da abordagem N-gram é que o tamanho do vocabulário começa a crescer extremamente rápido. Na prática, é necessário combinar a representação N-gram com algumas técnicas de redução de dimensionalidade, como *embeddings*, que iremos discutir na próxima unidade.\n",
"\n",
"Para usar a representação N-gram no nosso conjunto de dados **AG News**, precisamos construir um vocabulário ngram especial:\n"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Bigram vocabulary length = 1308842\n"
]
}
],
"source": [
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" l = tokenizer(line)\n",
" counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n",
" \n",
"bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n",
"\n",
"print(\"Bigram vocabulary length = \",len(bi_vocab))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Poderíamos então usar o mesmo código acima para treinar o classificador, no entanto, isso seria muito ineficiente em termos de memória. Na próxima unidade, iremos treinar um classificador de bigramas utilizando embeddings.\n",
"\n",
"> **Nota:** Pode deixar apenas os ngrams que ocorrem no texto mais vezes do que o número especificado. Isto garantirá que bigramas pouco frequentes sejam omitidos e reduzirá significativamente a dimensionalidade. Para fazer isso, defina o parâmetro `min_freq` com um valor mais alto e observe a mudança no tamanho do vocabulário.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Frequência de Termos e Frequência Inversa de Documentos TF-IDF\n",
"\n",
"Na representação BoW, as ocorrências de palavras têm o mesmo peso, independentemente da palavra em si. No entanto, é evidente que palavras frequentes, como *a*, *em*, etc., são muito menos importantes para a classificação do que termos especializados. De facto, na maioria das tarefas de PLN, algumas palavras são mais relevantes do que outras.\n",
"\n",
"**TF-IDF** significa **frequência de termosfrequência inversa de documentos**. É uma variação do modelo bag of words, onde, em vez de um valor binário 0/1 que indica a presença de uma palavra num documento, utiliza-se um valor em ponto flutuante, que está relacionado com a frequência de ocorrência da palavra no corpus.\n",
"\n",
"Mais formalmente, o peso $w_{ij}$ de uma palavra $i$ no documento $j$ é definido como:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"onde\n",
"* $tf_{ij}$ é o número de ocorrências de $i$ em $j$, ou seja, o valor BoW que vimos anteriormente\n",
"* $N$ é o número de documentos na coleção\n",
"* $df_i$ é o número de documentos que contêm a palavra $i$ em toda a coleção\n",
"\n",
"O valor TF-IDF $w_{ij}$ aumenta proporcionalmente ao número de vezes que uma palavra aparece num documento e é ajustado pelo número de documentos no corpus que contêm a palavra, o que ajuda a compensar o facto de algumas palavras aparecerem mais frequentemente do que outras. Por exemplo, se a palavra aparecer em *todos* os documentos da coleção, $df_i=N$, e $w_{ij}=0$, e esses termos seriam completamente ignorados.\n",
"\n",
"Pode criar facilmente a vetorização TF-IDF de texto utilizando o Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
" 0. , 0. , 0. , 0. , 0. ,\n",
" 0. ]])"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusão\n",
"\n",
"Embora as representações TF-IDF atribuam peso de frequência a diferentes palavras, elas não conseguem representar significado ou ordem. Como o famoso linguista J. R. Firth disse em 1935: “O significado completo de uma palavra é sempre contextual, e nenhum estudo de significado fora do contexto pode ser levado a sério.”. Mais à frente no curso, aprenderemos como capturar informações contextuais de texto utilizando modelagem de linguagem.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "7b9040985e748e4e2d4c689892456ad7",
"translation_date": "2025-08-31T12:03:46+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,647 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tarefa de classificação de texto\n",
"\n",
"Neste módulo, vamos começar com uma tarefa simples de classificação de texto baseada no conjunto de dados **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)**: vamos classificar manchetes de notícias em uma das 4 categorias: Mundo, Desporto, Negócios e Ciência/Tecnologia.\n",
"\n",
"## O Conjunto de Dados\n",
"\n",
"Para carregar o conjunto de dados, utilizaremos a API **[TensorFlow Datasets](https://www.tensorflow.org/datasets)**.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import tensorflow_datasets as tfds\n",
"\n",
"# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n",
"# we will set tensorflow option to grow GPU memory allocation when required.\n",
"physical_devices = tf.config.list_physical_devices('GPU') \n",
"if len(physical_devices)>0:\n",
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
"\n",
"dataset = tfds.load('ag_news_subset')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora podemos aceder às partes de treino e teste do conjunto de dados utilizando `dataset['train']` e `dataset['test']`, respetivamente:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Length of train dataset = 120000\n",
"Length of test dataset = 7600\n"
]
}
],
"source": [
"ds_train = dataset['train']\n",
"ds_test = dataset['test']\n",
"\n",
"print(f\"Length of train dataset = {len(ds_train)}\")\n",
"print(f\"Length of test dataset = {len(ds_test)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos imprimir as primeiras 10 novas manchetes do nosso conjunto de dados:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n",
"1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n",
"2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n",
"3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n",
"1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n"
]
}
],
"source": [
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
"\n",
"for i,x in zip(range(5),ds_train):\n",
" print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Vetorização de texto\n",
"\n",
"Agora precisamos converter texto em **números** que podem ser representados como tensores. Se quisermos uma representação ao nível de palavras, precisamos fazer duas coisas:\n",
"\n",
"* Usar um **tokenizador** para dividir o texto em **tokens**.\n",
"* Construir um **vocabulário** desses tokens.\n",
"\n",
"### Limitar o tamanho do vocabulário\n",
"\n",
"No exemplo do conjunto de dados AG News, o tamanho do vocabulário é bastante grande, com mais de 100 mil palavras. De um modo geral, não precisamos de palavras que raramente aparecem no texto — apenas algumas frases as terão, e o modelo não aprenderá com elas. Assim, faz sentido limitar o tamanho do vocabulário para um número menor, passando um argumento ao construtor do vetorizador:\n",
"\n",
"Ambos os passos podem ser realizados utilizando a camada **TextVectorization**. Vamos instanciar o objeto vetorizador e, em seguida, chamar o método `adapt` para percorrer todo o texto e construir um vocabulário:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"vocab_size = 50000\n",
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n",
"vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota** que estamos a utilizar apenas um subconjunto do conjunto de dados completo para construir um vocabulário. Fazemos isto para acelerar o tempo de execução e não te deixar à espera. No entanto, corremos o risco de que algumas palavras do conjunto de dados completo não sejam incluídas no vocabulário e sejam ignoradas durante o treino. Assim, utilizar o tamanho total do vocabulário e percorrer todo o conjunto de dados durante o `adapt` deverá aumentar a precisão final, mas não de forma significativa.\n",
"\n",
"Agora podemos aceder ao vocabulário real:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n",
"Length of vocabulary: 5335\n"
]
}
],
"source": [
"vocab = vectorizer.get_vocabulary()\n",
"vocab_size = len(vocab)\n",
"print(vocab[:10])\n",
"print(f\"Length of vocabulary: {vocab_size}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Usando o vetorizador, podemos facilmente codificar qualquer texto num conjunto de números:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vectorizer('I love to play with my words')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Representação de texto com Bag-of-words\n",
"\n",
"Como as palavras representam significado, às vezes conseguimos entender o significado de um texto apenas olhando para as palavras individuais, independentemente da ordem em que aparecem na frase. Por exemplo, ao classificar notícias, palavras como *meteorologia* e *neve* provavelmente indicam *previsão do tempo*, enquanto palavras como *ações* e *dólar* seriam associadas a *notícias financeiras*.\n",
"\n",
"A representação vetorial **Bag-of-words** (BoW) é a forma tradicional mais simples de entender uma representação vetorial. Cada palavra está associada a um índice no vetor, e um elemento do vetor contém o número de ocorrências de cada palavra em um determinado documento.\n",
"\n",
"![Imagem mostrando como uma representação vetorial bag-of-words é armazenada na memória.](../../../../../lessons/5-NLP/13-TextRep/images/bag-of-words-example.png) \n",
"\n",
"> **Note**: Também pode pensar no BoW como a soma de todos os vetores one-hot codificados para as palavras individuais no texto.\n",
"\n",
"Abaixo está um exemplo de como gerar uma representação bag-of-words utilizando a biblioteca python Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import CountVectorizer\n",
"sc_vectorizer = CountVectorizer()\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"sc_vectorizer.fit_transform(corpus)\n",
"sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos também usar o vetorizador do Keras que definimos acima, convertendo cada número de palavra numa codificação one-hot e somando todos esses vetores:\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def to_bow(text):\n",
" return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n",
"\n",
"to_bow('My dog likes hot dogs on a hot day.').numpy()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota**: Pode ser surpreendente que o resultado seja diferente do exemplo anterior. A razão para isso é que, no exemplo do Keras, o comprimento do vetor corresponde ao tamanho do vocabulário, que foi construído a partir de todo o conjunto de dados AG News, enquanto no exemplo do Scikit Learn construímos o vocabulário a partir do texto de exemplo de forma dinâmica.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Treinar o classificador BoW\n",
"\n",
"Agora que aprendemos a construir a representação bag-of-words do nosso texto, vamos treinar um classificador que a utilize. Primeiro, precisamos converter o nosso conjunto de dados para uma representação bag-of-words. Isto pode ser feito utilizando a função `map` da seguinte forma:\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"batch_size = 128\n",
"\n",
"ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n",
"ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos definir uma rede neural de classificação simples que contém uma camada linear. O tamanho da entrada é `vocab_size`, e o tamanho da saída corresponde ao número de classes (4). Como estamos a resolver uma tarefa de classificação, a função de ativação final é **softmax**:\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x20c70a947f0>"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n",
"])\n",
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
"model.fit(ds_train_bow,validation_data=ds_test_bow)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como temos 4 classes, uma precisão acima de 80% é um bom resultado.\n",
"\n",
"## Treinar um classificador como uma única rede\n",
"\n",
"Como o vetorizador também é uma camada do Keras, podemos definir uma rede que o inclua e treiná-la de ponta a ponta. Desta forma, não precisamos vetorizar o conjunto de dados usando `map`, podemos simplesmente passar o conjunto de dados original para a entrada da rede.\n",
"\n",
"> **Nota**: Ainda precisaríamos aplicar mapeamentos ao nosso conjunto de dados para converter campos de dicionários (como `title`, `description` e `label`) em tuplos. No entanto, ao carregar os dados do disco, podemos construir um conjunto de dados com a estrutura necessária desde o início.\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"model\"\n",
"_________________________________________________________________\n",
" Layer (type) Output Shape Param # \n",
"=================================================================\n",
" input_1 (InputLayer) [(None, 1)] 0 \n",
" \n",
" text_vectorization (TextVec (None, None) 0 \n",
" torization) \n",
" \n",
" tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n",
" \n",
" tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n",
" bda) \n",
" \n",
" dense_2 (Dense) (None, 4) 21344 \n",
" \n",
"=================================================================\n",
"Total params: 21,344\n",
"Trainable params: 21,344\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n",
"938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x20c721521f0>"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def extract_text(x):\n",
" return x['title']+' '+x['description']\n",
"\n",
"def tupelize(x):\n",
" return (extract_text(x),x['label'])\n",
"\n",
"inp = keras.Input(shape=(1,),dtype=tf.string)\n",
"x = vectorizer(inp)\n",
"x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n",
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
"model = keras.models.Model(inp,out)\n",
"model.summary()\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Bigramas, trigramas e n-gramas\n",
"\n",
"Uma limitação da abordagem de saco de palavras é que algumas palavras fazem parte de expressões compostas, por exemplo, a palavra 'hot dog' tem um significado completamente diferente das palavras 'hot' e 'dog' em outros contextos. Se representarmos as palavras 'hot' e 'dog' sempre usando os mesmos vetores, isso pode confundir o nosso modelo.\n",
"\n",
"Para resolver isso, **representações de n-gramas** são frequentemente utilizadas em métodos de classificação de documentos, onde a frequência de cada palavra, bi-palavra ou tri-palavra é uma característica útil para treinar classificadores. Em representações de bigramas, por exemplo, adicionamos todos os pares de palavras ao vocabulário, além das palavras originais.\n",
"\n",
"Abaixo está um exemplo de como gerar uma representação de saco de palavras com bigramas usando Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary:\n",
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
]
},
{
"data": {
"text/plain": [
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
" dtype=int64)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
"corpus = [\n",
" 'I like hot dogs.',\n",
" 'The dog ran fast.',\n",
" 'Its hot outside.',\n",
" ]\n",
"bigram_vectorizer.fit_transform(corpus)\n",
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A principal desvantagem da abordagem n-gram é que o tamanho do vocabulário começa a crescer extremamente rápido. Na prática, precisamos combinar a representação n-gram com uma técnica de redução de dimensionalidade, como *embeddings*, que discutiremos na próxima unidade.\n",
"\n",
"Para usar uma representação n-gram no nosso conjunto de dados **AG News**, precisamos passar o parâmetro `ngrams` para o nosso construtor `TextVectorization`. O tamanho de um vocabulário de bigramas é **significativamente maior**; no nosso caso, são mais de 1,3 milhões de tokens! Por isso, faz sentido limitar também os tokens de bigramas a um número razoável.\n",
"\n",
"Poderíamos usar o mesmo código acima para treinar o classificador, no entanto, isso seria muito ineficiente em termos de memória. Na próxima unidade, treinaremos o classificador de bigramas utilizando embeddings. Enquanto isso, pode experimentar treinar o classificador de bigramas neste notebook e verificar se consegue obter uma precisão maior.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Calculando automaticamente os vetores BoW\n",
"\n",
"No exemplo acima, calculámos os vetores BoW manualmente somando as codificações one-hot de palavras individuais. No entanto, a versão mais recente do TensorFlow permite-nos calcular os vetores BoW automaticamente ao passar o parâmetro `output_mode='count` para o construtor do vetorizador. Isto torna a definição e o treino do nosso modelo significativamente mais simples:\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training vectorizer\n",
"938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x20c725217c0>"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n",
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
"])\n",
"print(\"Training vectorizer\")\n",
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Frequência de termos - frequência inversa de documentos (TF-IDF)\n",
"\n",
"Na representação BoW, as ocorrências de palavras são ponderadas usando a mesma técnica, independentemente da palavra em si. No entanto, é evidente que palavras frequentes como *a* e *em* são muito menos importantes para a classificação do que termos especializados. Na maioria das tarefas de PLN, algumas palavras são mais relevantes do que outras.\n",
"\n",
"**TF-IDF** significa **frequência de termos - frequência inversa de documentos**. É uma variação do modelo bag-of-words, onde, em vez de um valor binário 0/1 que indica a presença de uma palavra num documento, é utilizado um valor em ponto flutuante, relacionado com a frequência de ocorrência da palavra no corpus.\n",
"\n",
"Mais formalmente, o peso $w_{ij}$ de uma palavra $i$ no documento $j$ é definido como:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"onde\n",
"* $tf_{ij}$ é o número de ocorrências de $i$ em $j$, ou seja, o valor BoW que vimos anteriormente\n",
"* $N$ é o número de documentos na coleção\n",
"* $df_i$ é o número de documentos que contêm a palavra $i$ em toda a coleção\n",
"\n",
"O valor TF-IDF $w_{ij}$ aumenta proporcionalmente ao número de vezes que uma palavra aparece num documento e é ajustado pelo número de documentos no corpus que contêm a palavra, o que ajuda a compensar o facto de algumas palavras aparecerem mais frequentemente do que outras. Por exemplo, se a palavra aparecer em *todos* os documentos da coleção, $df_i=N$, e $w_{ij}=0$, e esses termos seriam completamente desconsiderados.\n",
"\n",
"Pode criar facilmente uma vetorização TF-IDF de texto utilizando o Scikit Learn:\n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
" 0. , 0. , 0. , 0. , 0. ,\n",
" 0. ]])"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
"vectorizer.fit_transform(corpus)\n",
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Em Keras, a camada `TextVectorization` pode calcular automaticamente as frequências TF-IDF ao passar o parâmetro `output_mode='tf-idf'`. Vamos repetir o código que usamos acima para ver se usar TF-IDF aumenta a precisão:\n"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training vectorizer\n",
"938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x20c729dfd30>"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n",
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
"])\n",
"print(\"Training vectorizer\")\n",
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusão\n",
"\n",
"Embora as representações TF-IDF atribuam pesos de frequência a diferentes palavras, elas não conseguem representar o significado ou a ordem. Como disse o famoso linguista J. R. Firth em 1935: \"O significado completo de uma palavra é sempre contextual, e nenhum estudo de significado fora do contexto pode ser levado a sério.\" Mais à frente no curso, aprenderemos como capturar informações contextuais de texto utilizando modelagem de linguagem.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante ter em conta que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
},
"kernel_info": {
"name": "conda-env-py37_tensorflow-py"
},
"kernelspec": {
"display_name": "py37_tensorflow",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"nteract": {
"version": "nteract-front-end@1.0.0"
},
"coopTranslator": {
"original_hash": "19b43951d55b377a76209c24c1f017e4",
"translation_date": "2025-08-31T12:04:40+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,6 @@
# Tarefa: Notebooks
Utilizando os notebooks associados a esta lição (seja a versão em PyTorch ou TensorFlow), execute-os novamente usando o seu próprio conjunto de dados, talvez um do Kaggle, utilizado com a devida atribuição. Reescreva o notebook para destacar as suas próprias conclusões. Experimente alguns conjuntos de dados inovadores que possam ser surpreendentes, como [este sobre avistamentos de OVNIs](https://www.kaggle.com/datasets/NUFORC/ufo-sightings) do NUFORC.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,724 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Embeddings\n",
"\n",
"No nosso exemplo anterior, trabalhámos com vetores de bag-of-words de alta dimensão com comprimento `vocab_size`, e estávamos a converter explicitamente de vetores de representação posicional de baixa dimensão para uma representação esparsa de one-hot. Esta representação one-hot não é eficiente em termos de memória e, além disso, cada palavra é tratada de forma independente das outras, ou seja, os vetores codificados em one-hot não expressam qualquer semelhança semântica entre palavras.\n",
"\n",
"Nesta unidade, continuaremos a explorar o conjunto de dados **News AG**. Para começar, vamos carregar os dados e obter algumas definições do notebook anterior.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Building vocab...\n",
"Vocab size = 95812\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"import numpy as np\n",
"from torchnlp import *\n",
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
"vocab_size = len(vocab)\n",
"print(\"Vocab size = \",vocab_size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## O que é embedding?\n",
"\n",
"A ideia de **embedding** é representar palavras por vetores densos de dimensão reduzida, que de alguma forma refletem o significado semântico de uma palavra. Mais à frente, discutiremos como construir embeddings de palavras significativos, mas por agora vamos apenas pensar em embeddings como uma forma de reduzir a dimensionalidade de um vetor de palavras.\n",
"\n",
"Assim, a camada de embedding receberia uma palavra como entrada e produziria um vetor de saída com o `embedding_size` especificado. De certa forma, é muito semelhante à camada `Linear`, mas em vez de receber um vetor codificado em one-hot, será capaz de receber um número correspondente à palavra como entrada.\n",
"\n",
"Ao usar a camada de embedding como a primeira camada na nossa rede, podemos mudar do modelo bag-of-words para o modelo **embedding bag**, onde primeiro convertemos cada palavra do nosso texto no embedding correspondente e, em seguida, calculamos alguma função agregada sobre todos esses embeddings, como `sum`, `average` ou `max`.\n",
"\n",
"![Imagem mostrando um classificador de embedding para cinco palavras de sequência.](../../../../../lessons/5-NLP/14-Embeddings/images/embedding-classifier-example.png)\n",
"\n",
"A nossa rede neural classificadora começará com uma camada de embedding, seguida por uma camada de agregação e, por fim, um classificador linear no topo:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class EmbedClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, num_class):\n",
" super().__init__()\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" x = self.embedding(x)\n",
" x = torch.mean(x,dim=1)\n",
" return self.fc(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Lidar com tamanhos variáveis de sequência\n",
"\n",
"Como resultado desta arquitetura, os minibatches para a nossa rede precisariam ser criados de uma forma específica. Na unidade anterior, ao usar bag-of-words, todos os tensores BoW num minibatch tinham o mesmo tamanho `vocab_size`, independentemente do comprimento real da nossa sequência de texto. Assim que passamos a usar embeddings de palavras, acabamos por ter um número variável de palavras em cada amostra de texto, e ao combinar essas amostras em minibatches, teríamos de aplicar algum preenchimento (padding).\n",
"\n",
"Isto pode ser feito utilizando a mesma técnica de fornecer a função `collate_fn` à fonte de dados:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def padify(b):\n",
" # b is the list of tuples of length batch_size\n",
" # - first element of a tuple = label, \n",
" # - second = feature (text sequence)\n",
" # build vectorized sequence\n",
" v = [encode(x[1]) for x in b]\n",
" # first, compute max length of a sequence in this minibatch\n",
" l = max(map(len,v))\n",
" return ( # tuple of two tensors - labels and features\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v])\n",
" )\n",
"\n",
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Treinar o classificador de embeddings\n",
"\n",
"Agora que definimos um dataloader adequado, podemos treinar o modelo utilizando a função de treino que definimos na unidade anterior:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.6415625\n",
"6400: acc=0.6865625\n",
"9600: acc=0.7103125\n",
"12800: acc=0.726953125\n",
"16000: acc=0.739375\n",
"19200: acc=0.75046875\n",
"22400: acc=0.7572321428571429\n"
]
},
{
"data": {
"text/plain": [
"(0.889799795315499, 0.7623160588611644)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota**: Estamos apenas a treinar para 25k registos aqui (menos de uma época completa) por questões de tempo, mas pode continuar a treinar, escrever uma função para treinar durante várias épocas e experimentar com o parâmetro da taxa de aprendizagem para alcançar maior precisão. Deve ser possível atingir uma precisão de cerca de 90%.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Camada EmbeddingBag e Representação de Sequências de Comprimento Variável\n",
"\n",
"Na arquitetura anterior, era necessário preencher todas as sequências para que tivessem o mesmo comprimento, de forma a ajustá-las num minibatch. Esta não é a forma mais eficiente de representar sequências de comprimento variável - uma abordagem alternativa seria utilizar um vetor de **offset**, que armazenaria os deslocamentos de todas as sequências contidas num único vetor grande.\n",
"\n",
"![Imagem mostrando uma representação de sequência com offset](../../../../../lessons/5-NLP/14-Embeddings/images/offset-sequence-representation.png)\n",
"\n",
"> **Nota**: Na imagem acima, mostramos uma sequência de caracteres, mas no nosso exemplo estamos a trabalhar com sequências de palavras. No entanto, o princípio geral de representar sequências com um vetor de offset mantém-se o mesmo.\n",
"\n",
"Para trabalhar com a representação por offset, utilizamos a camada [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html). É semelhante à camada `Embedding`, mas recebe como entrada um vetor de conteúdo e um vetor de offset, e também inclui uma camada de agregação, que pode ser `mean`, `sum` ou `max`.\n",
"\n",
"Aqui está uma rede modificada que utiliza `EmbeddingBag`:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"class EmbedClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, num_class):\n",
" super().__init__()\n",
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
"\n",
" def forward(self, text, off):\n",
" x = self.embedding(text, off)\n",
" return self.fc(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para preparar o conjunto de dados para treino, precisamos fornecer uma função de conversão que irá preparar o vetor de deslocamento:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def offsetify(b):\n",
" # first, compute data tensor from all sequences\n",
" x = [torch.tensor(encode(t[1])) for t in b]\n",
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
" o = [0] + [len(t) for t in x]\n",
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
" return ( \n",
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
" torch.cat(x), # text \n",
" o\n",
" )\n",
"\n",
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nota que, ao contrário de todos os exemplos anteriores, a nossa rede agora aceita dois parâmetros: vetor de dados e vetor de deslocamento, que têm tamanhos diferentes. Da mesma forma, o nosso carregador de dados também nos fornece 3 valores em vez de 2: tanto os vetores de texto como os vetores de deslocamento são fornecidos como características. Portanto, precisamos ajustar ligeiramente a nossa função de treino para lidar com isso:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.6153125\n",
"6400: acc=0.6615625\n",
"9600: acc=0.6932291666666667\n",
"12800: acc=0.715078125\n",
"16000: acc=0.7270625\n",
"19200: acc=0.7382291666666667\n",
"22400: acc=0.7486160714285715\n"
]
},
{
"data": {
"text/plain": [
"(22.771553103007037, 0.7551983365323096)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
"\n",
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
" loss_fn = loss_fn.to(device)\n",
" net.train()\n",
" total_loss,acc,count,i = 0,0,0,0\n",
" for labels,text,off in dataloader:\n",
" optimizer.zero_grad()\n",
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
" out = net(text, off)\n",
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss+=loss\n",
" _,predicted = torch.max(out,1)\n",
" acc+=(predicted==labels).sum()\n",
" count+=len(labels)\n",
" i+=1\n",
" if i%report_freq==0:\n",
" print(f\"{count}: acc={acc.item()/count}\")\n",
" if epoch_size and count>epoch_size:\n",
" break\n",
" return total_loss.item()/count, acc.item()/count\n",
"\n",
"\n",
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Embeddings Semânticos: Word2Vec\n",
"\n",
"No nosso exemplo anterior, a camada de embedding do modelo aprendeu a mapear palavras para representações vetoriais, contudo, essa representação não tinha muito significado semântico. Seria interessante aprender uma representação vetorial em que palavras semelhantes ou sinónimos corresponderiam a vetores próximos entre si em termos de alguma distância vetorial (por exemplo, distância euclidiana).\n",
"\n",
"Para isso, precisamos pré-treinar o nosso modelo de embedding numa grande coleção de texto de uma forma específica. Uma das primeiras abordagens para treinar embeddings semânticos é chamada de [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Baseia-se em duas arquiteturas principais que são usadas para produzir uma representação distribuída de palavras:\n",
"\n",
" - **Continuous bag-of-words** (CBoW) — nesta arquitetura, treinamos o modelo para prever uma palavra a partir do contexto envolvente. Dado o ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$, o objetivo do modelo é prever $W_0$ a partir de $(W_{-2},W_{-1},W_1,W_2)$.\n",
" - **Continuous skip-gram** é o oposto do CBoW. O modelo utiliza a janela de palavras de contexto envolvente para prever a palavra atual.\n",
"\n",
"CBoW é mais rápido, enquanto o skip-gram é mais lento, mas faz um trabalho melhor ao representar palavras menos frequentes.\n",
"\n",
"![Imagem mostrando os algoritmos CBoW e Skip-Gram para converter palavras em vetores.](../../../../../lessons/5-NLP/14-Embeddings/images/example-algorithms-for-converting-words-to-vectors.png)\n",
"\n",
"Para experimentar o embedding Word2Vec pré-treinado no conjunto de dados Google News, podemos usar a biblioteca **gensim**. Abaixo, encontramos as palavras mais semelhantes a 'neural'.\n",
"\n",
"> **Nota:** Quando cria vetores de palavras pela primeira vez, o download pode demorar algum tempo!\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import gensim.downloader as api\n",
"w2v = api.load('word2vec-google-news-300')"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"neuronal -> 0.7804799675941467\n",
"neurons -> 0.7326500415802002\n",
"neural_circuits -> 0.7252851724624634\n",
"neuron -> 0.7174385190010071\n",
"cortical -> 0.6941086649894714\n",
"brain_circuitry -> 0.6923246383666992\n",
"synaptic -> 0.6699118614196777\n",
"neural_circuitry -> 0.6638563275337219\n",
"neurochemical -> 0.6555314064025879\n",
"neuronal_activity -> 0.6531826257705688\n"
]
}
],
"source": [
"for w,p in w2v.most_similar('neural'):\n",
" print(f\"{w} -> {p}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos também calcular embeddings de vetores a partir da palavra, para serem utilizados no treino do modelo de classificação (mostramos apenas os primeiros 20 componentes do vetor para maior clareza):\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
" dtype=float32)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"w2v.word_vec('play')[:20]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A grande vantagem das incorporações semânticas é que se pode manipular a codificação vetorial para alterar a semântica. Por exemplo, podemos pedir para encontrar uma palavra cuja representação vetorial seja o mais próxima possível das palavras *rei* e *mulher*, e o mais distante possível da palavra *homem*:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('queen', 0.7118192911148071)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tanto CBoW como Skip-Grams são embeddings \"previsivos\", pois consideram apenas contextos locais. O Word2Vec não aproveita o contexto global.\n",
"\n",
"**FastText** baseia-se no Word2Vec ao aprender representações vetoriais para cada palavra e os n-gramas de caracteres encontrados dentro de cada palavra. Os valores das representações são então calculados como uma média em um único vetor em cada etapa de treino. Embora isso adicione muita computação adicional ao pré-treino, permite que os embeddings de palavras codifiquem informações de subpalavras.\n",
"\n",
"Outro método, **GloVe**, utiliza a ideia de matriz de coocorrência e emprega métodos neurais para decompor a matriz de coocorrência em vetores de palavras mais expressivos e não lineares.\n",
"\n",
"Pode experimentar o exemplo alterando os embeddings para FastText e GloVe, já que o gensim suporta vários modelos diferentes de embeddings de palavras.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Utilizar Embeddings Pré-Treinados no PyTorch\n",
"\n",
"Podemos modificar o exemplo acima para pré-preencher a matriz na nossa camada de embedding com embeddings semânticos, como o Word2Vec. É importante ter em conta que os vocabulários do embedding pré-treinado e do nosso corpus de texto provavelmente não irão coincidir, por isso iremos inicializar os pesos para as palavras em falta com valores aleatórios:\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Embedding size: 300\n",
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
]
}
],
"source": [
"embed_size = len(w2v.get_vector('hello'))\n",
"print(f'Embedding size: {embed_size}')\n",
"\n",
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
"\n",
"print('Populating matrix, this will take some time...',end='')\n",
"found, not_found = 0,0\n",
"for i,w in enumerate(vocab.get_itos()):\n",
" try:\n",
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
" found+=1\n",
" except:\n",
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
" not_found+=1\n",
"\n",
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
"net = net.to(device)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos treinar o nosso modelo. Note que o tempo necessário para treinar o modelo é significativamente maior do que no exemplo anterior, devido ao tamanho maior da camada de embeddings e, consequentemente, ao número muito mais elevado de parâmetros. Além disso, por causa disso, pode ser necessário treinar o nosso modelo com mais exemplos se quisermos evitar overfitting.\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.6359375\n",
"6400: acc=0.68109375\n",
"9600: acc=0.7067708333333333\n",
"12800: acc=0.723671875\n",
"16000: acc=0.73625\n",
"19200: acc=0.7463541666666667\n",
"22400: acc=0.7560714285714286\n"
]
},
{
"data": {
"text/plain": [
"(214.1013875559821, 0.7626759436980166)"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No nosso caso, não observamos um grande aumento na precisão, o que provavelmente se deve a vocabulários bastante diferentes. \n",
"Para superar o problema de vocabulários distintos, podemos usar uma das seguintes soluções: \n",
"* Re-treinar o modelo word2vec com o nosso vocabulário \n",
"* Carregar o nosso conjunto de dados utilizando o vocabulário do modelo word2vec pré-treinado. O vocabulário usado para carregar o conjunto de dados pode ser especificado durante o carregamento. \n",
"\n",
"A última abordagem parece mais fácil, especialmente porque o framework `torchtext` do PyTorch contém suporte integrado para embeddings. Podemos, por exemplo, instanciar um vocabulário baseado em GloVe da seguinte forma: \n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
]
}
],
"source": [
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O vocabulário carregado tem as seguintes operações básicas:\n",
"* O dicionário `vocab.stoi` permite-nos converter uma palavra no seu índice no dicionário.\n",
"* `vocab.itos` faz o oposto - converte um número numa palavra.\n",
"* `vocab.vectors` é o array de vetores de embeddings, então, para obter o embedding de uma palavra `s`, precisamos usar `vocab.vectors[vocab.stoi[s]]`.\n",
"\n",
"Aqui está um exemplo de manipulação de embeddings para demonstrar a equação **kind-man+woman = queen** (tive de ajustar um pouco o coeficiente para funcionar):\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'queen'"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# get the vector corresponding to kind-man+woman\n",
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
"# find the index of the closest embedding vector \n",
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
"min_idx = torch.argmin(d)\n",
"# find the corresponding word\n",
"vocab.itos[min_idx]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para treinar o classificador usando essas embeddings, primeiro precisamos codificar o nosso conjunto de dados utilizando o vocabulário GloVe:\n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def offsetify(b):\n",
" # first, compute data tensor from all sequences\n",
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
" o = [0] + [len(t) for t in x]\n",
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
" return ( \n",
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
" torch.cat(x), # text \n",
" o\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como vimos acima, todas as incorporações de vetores estão armazenadas na matriz `vocab.vectors`. Isso torna super fácil carregar esses pesos na camada de incorporação usando uma cópia simples:\n"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
"net.embedding.weight.data = vocab.vectors\n",
"net = net.to(device)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.6271875\n",
"6400: acc=0.68078125\n",
"9600: acc=0.7030208333333333\n",
"12800: acc=0.71984375\n",
"16000: acc=0.7346875\n",
"19200: acc=0.7455729166666667\n",
"22400: acc=0.7529464285714286\n"
]
},
{
"data": {
"text/plain": [
"(35.53972978646833, 0.7575175943698017)"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Uma das razões pelas quais não estamos a observar um aumento significativo na precisão deve-se ao facto de algumas palavras do nosso conjunto de dados estarem ausentes no vocabulário pré-treinado do GloVe e, assim, serem essencialmente ignoradas. Para superar este facto, podemos treinar os nossos próprios embeddings no nosso conjunto de dados.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Embeddings Contextuais\n",
"\n",
"Uma limitação importante das representações tradicionais de embeddings pré-treinados, como o Word2Vec, é o problema da desambiguação do significado das palavras. Embora os embeddings pré-treinados consigam capturar parte do significado das palavras no contexto, todos os possíveis significados de uma palavra são codificados no mesmo embedding. Isto pode causar problemas em modelos subsequentes, já que muitas palavras, como a palavra 'play', têm significados diferentes dependendo do contexto em que são usadas.\n",
"\n",
"Por exemplo, a palavra 'play' nas duas frases abaixo tem significados bastante diferentes:\n",
"- Fui a uma **peça** no teatro.\n",
"- O João quer **brincar** com os amigos.\n",
"\n",
"Os embeddings pré-treinados acima representam ambos os significados da palavra 'play' no mesmo embedding. Para superar esta limitação, precisamos construir embeddings baseados no **modelo de linguagem**, que é treinado num grande corpus de texto e *sabe* como as palavras podem ser combinadas em diferentes contextos. Discutir embeddings contextuais está fora do âmbito deste tutorial, mas voltaremos a eles quando falarmos sobre modelos de linguagem na próxima unidade.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original no seu idioma nativo deve ser considerado a fonte oficial. Para informações críticas, recomenda-se uma tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
},
"kernelspec": {
"display_name": "py37_pytorch",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "f50b026abce5cf36783a560ea72cb9b1",
"translation_date": "2025-08-31T12:03:03+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,695 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Embeddings\n",
"\n",
"No nosso exemplo anterior, trabalhámos com vetores de bag-of-words de alta dimensão com comprimento `vocab_size`, e convertemos explicitamente vetores de representação posicional de baixa dimensão em representações esparsas de uma só posição ativa (one-hot). Esta representação one-hot não é eficiente em termos de memória. Além disso, cada palavra é tratada de forma independente, o que significa que os vetores codificados em one-hot não expressam semelhanças semânticas entre palavras.\n",
"\n",
"Nesta unidade, continuaremos a explorar o conjunto de dados **News AG**. Para começar, vamos carregar os dados e obter algumas definições da unidade anterior.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import tensorflow_datasets as tfds\n",
"import numpy as np\n",
"\n",
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### O que é uma embedding?\n",
"\n",
"A ideia de **embedding** é representar palavras utilizando vetores densos de dimensão reduzida que refletem o significado semântico da palavra. Mais à frente, discutiremos como construir embeddings de palavras significativas, mas por agora vamos apenas pensar em embeddings como uma forma de reduzir a dimensionalidade de um vetor de palavras.\n",
"\n",
"Assim, uma camada de embedding recebe uma palavra como entrada e produz um vetor de saída com o tamanho especificado por `embedding_size`. De certa forma, é muito semelhante a uma camada `Dense`, mas em vez de receber um vetor codificado em one-hot como entrada, consegue receber um número que representa a palavra.\n",
"\n",
"Ao usar uma camada de embedding como a primeira camada na nossa rede, podemos mudar de um modelo de bag-of-words para um modelo de **embedding bag**, onde primeiro convertemos cada palavra do nosso texto na embedding correspondente e, em seguida, calculamos uma função agregada sobre todas essas embeddings, como `sum`, `average` ou `max`.\n",
"\n",
"![Imagem mostrando um classificador com embedding para cinco palavras de sequência.](../../../../../lessons/5-NLP/14-Embeddings/images/embedding-classifier-example.png)\n",
"\n",
"A nossa rede neural de classificação consiste nas seguintes camadas:\n",
"\n",
"* Camada `TextVectorization`, que recebe uma string como entrada e produz um tensor de números de tokens. Vamos especificar um tamanho de vocabulário razoável, `vocab_size`, e ignorar palavras menos usadas. A forma da entrada será 1, e a forma da saída será $n$, já que obteremos $n$ tokens como resultado, cada um contendo números entre 0 e `vocab_size`.\n",
"* Camada `Embedding`, que recebe $n$ números e reduz cada número a um vetor denso de um comprimento especificado (100 no nosso exemplo). Assim, o tensor de entrada com forma $n$ será transformado num tensor $n\\times 100$.\n",
"* Camada de agregação, que calcula a média deste tensor ao longo do primeiro eixo, ou seja, calculará a média de todos os $n$ tensores de entrada correspondentes a diferentes palavras. Para implementar esta camada, usaremos uma camada `Lambda` e passaremos para ela a função para calcular a média. A saída terá uma forma de 100 e será a representação numérica de toda a sequência de entrada.\n",
"* Classificador linear final `Dense`.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential\"\n",
"_________________________________________________________________\n",
" Layer (type) Output Shape Param # \n",
"=================================================================\n",
" text_vectorization (TextVec (None, None) 0 \n",
" torization) \n",
" \n",
" embedding (Embedding) (None, None, 100) 3000000 \n",
" \n",
" lambda (Lambda) (None, 100) 0 \n",
" \n",
" dense (Dense) (None, 4) 404 \n",
" \n",
"=================================================================\n",
"Total params: 3,000,404\n",
"Trainable params: 3,000,404\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"vocab_size = 30000\n",
"batch_size = 128\n",
"\n",
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,input_shape=(1,))\n",
"\n",
"model = keras.models.Sequential([\n",
" vectorizer, \n",
" keras.layers.Embedding(vocab_size,100),\n",
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
" keras.layers.Dense(4, activation='softmax')\n",
"])\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No resumo `summary`, na coluna **output shape**, a primeira dimensão do tensor `None` corresponde ao tamanho do minibatch, e a segunda corresponde ao comprimento da sequência de tokens. Todas as sequências de tokens no minibatch têm comprimentos diferentes. Vamos discutir como lidar com isso na próxima secção.\n",
"\n",
"Agora, vamos treinar a rede:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training vectorizer\n",
"938/938 [==============================] - 20s 20ms/step - loss: 0.7891 - acc: 0.8155 - val_loss: 0.4470 - val_acc: 0.8642\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x22255515100>"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def extract_text(x):\n",
" return x['title']+' '+x['description']\n",
"\n",
"def tupelize(x):\n",
" return (extract_text(x),x['label'])\n",
"\n",
"print(\"Training vectorizer\")\n",
"vectorizer.adapt(ds_train.take(500).map(extract_text))\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"> **Nota** que estamos a construir o vetorizador com base num subconjunto dos dados. Isto é feito para acelerar o processo, e pode resultar numa situação em que nem todos os tokens do nosso texto estejam presentes no vocabulário. Neste caso, esses tokens seriam ignorados, o que pode resultar numa precisão ligeiramente inferior. No entanto, na vida real, um subconjunto de texto frequentemente fornece uma boa estimativa do vocabulário.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Lidando com tamanhos variáveis de sequências\n",
"\n",
"Vamos entender como o treino ocorre em minibatches. No exemplo acima, o tensor de entrada tem dimensão 1, e usamos minibatches de tamanho 128, de forma que o tamanho real do tensor é $128 \\times 1$. No entanto, o número de tokens em cada frase é diferente. Se aplicarmos a camada `TextVectorization` a uma única entrada, o número de tokens retornados será diferente, dependendo de como o texto é tokenizado:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"tf.Tensor([ 1 45], shape=(2,), dtype=int64)\n",
"tf.Tensor([ 112 1271 1 3 1747 158], shape=(6,), dtype=int64)\n"
]
}
],
"source": [
"print(vectorizer('Hello, world!'))\n",
"print(vectorizer('I am glad to meet you!'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No entanto, quando aplicamos o vetorizador a várias sequências, ele tem de produzir um tensor de forma retangular, preenchendo os elementos não utilizados com o token PAD (que, no nosso caso, é zero):\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<tf.Tensor: shape=(2, 6), dtype=int64, numpy=\n",
"array([[ 1, 45, 0, 0, 0, 0],\n",
" [ 112, 1271, 1, 3, 1747, 158]], dtype=int64)>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vectorizer(['Hello, world!','I am glad to meet you!'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aqui podemos ver as incorporações:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[[ 1.53059261e-02, 6.80514947e-02, 3.14026810e-02, ...,\n",
" -8.92002955e-02, 1.52911525e-04, -5.65562584e-02],\n",
" [ 2.57456154e-01, 2.79364467e-01, -2.03605562e-01, ...,\n",
" -2.07474351e-01, 8.31158683e-02, -2.03911960e-01],\n",
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02]],\n",
"\n",
" [[ 1.89674050e-01, 2.61548996e-01, -3.67433839e-02, ...,\n",
" -2.07366899e-01, -1.05442435e-01, -2.36952081e-01],\n",
" [ 6.16133213e-02, 1.80511594e-01, 9.77298319e-02, ...,\n",
" -5.46628237e-02, -1.07340455e-01, -1.06589928e-01],\n",
" [ 1.53059261e-02, 6.80514947e-02, 3.14026810e-02, ...,\n",
" -8.92002955e-02, 1.52911525e-04, -5.65562584e-02],\n",
" [-4.84890305e-02, -8.41715634e-02, 1.51529670e-01, ...,\n",
" 1.28192469e-01, -7.77286515e-02, 1.26041949e-01],\n",
" [-4.17212099e-02, -5.60694858e-02, 4.08860669e-02, ...,\n",
" 8.70475471e-02, 8.92383084e-02, 1.67974353e-01],\n",
" [ 2.85779923e-01, 4.57767487e-01, 4.52292450e-02, ...,\n",
" -1.97419018e-01, -2.04659685e-01, -2.79758364e-01]]],\n",
" dtype=float32)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.layers[1](vectorizer(['Hello, world!','I am glad to meet you!'])).numpy()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota**: Para minimizar a quantidade de preenchimento, em alguns casos faz sentido ordenar todas as sequências no conjunto de dados pela ordem crescente de comprimento (ou, mais precisamente, pelo número de tokens). Isto garantirá que cada minibatch contenha sequências de comprimento semelhante.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Embeddings semânticos: Word2Vec\n",
"\n",
"No nosso exemplo anterior, a camada de embedding aprendeu a mapear palavras para representações vetoriais; no entanto, essas representações não tinham significado semântico. Seria interessante aprender uma representação vetorial em que palavras semelhantes ou sinónimos correspondam a vetores próximos entre si em termos de alguma distância vetorial (por exemplo, distância euclidiana).\n",
"\n",
"Para isso, precisamos de pré-treinar o nosso modelo de embedding numa grande coleção de texto utilizando uma técnica como o [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Esta técnica baseia-se em duas arquiteturas principais que são usadas para produzir uma representação distribuída de palavras:\n",
"\n",
" - **Continuous bag-of-words** (CBoW), onde treinamos o modelo para prever uma palavra com base no contexto envolvente. Dado o ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$, o objetivo do modelo é prever $W_0$ a partir de $(W_{-2},W_{-1},W_1,W_2)$.\n",
" - **Continuous skip-gram** é o oposto do CBoW. O modelo utiliza a janela de palavras de contexto envolvente para prever a palavra atual.\n",
"\n",
"O CBoW é mais rápido, enquanto o skip-gram, embora mais lento, representa melhor palavras menos frequentes.\n",
"\n",
"![Imagem mostrando os algoritmos CBoW e Skip-Gram para converter palavras em vetores.](../../../../../lessons/5-NLP/14-Embeddings/images/example-algorithms-for-converting-words-to-vectors.png)\n",
"\n",
"Para experimentar o embedding Word2Vec pré-treinado no conjunto de dados Google News, podemos usar a biblioteca **gensim**. Abaixo, encontramos as palavras mais semelhantes a 'neural'.\n",
"\n",
"> **Nota:** Quando cria vetores de palavras pela primeira vez, o download pode demorar algum tempo!\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import gensim.downloader as api\n",
"w2v = api.load('word2vec-google-news-300')"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"neuronal -> 0.7804799675941467\n",
"neurons -> 0.7326500415802002\n",
"neural_circuits -> 0.7252851724624634\n",
"neuron -> 0.7174385190010071\n",
"cortical -> 0.6941086649894714\n",
"brain_circuitry -> 0.6923246383666992\n",
"synaptic -> 0.6699118614196777\n",
"neural_circuitry -> 0.6638563275337219\n",
"neurochemical -> 0.6555314064025879\n",
"neuronal_activity -> 0.6531826257705688\n"
]
}
],
"source": [
"for w,p in w2v.most_similar('neural'):\n",
" print(f\"{w} -> {p}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos também extrair a incorporação vetorial da palavra, para ser utilizada no treino do modelo de classificação. A incorporação tem 300 componentes, mas aqui mostramos apenas os primeiros 20 componentes do vetor para maior clareza:\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
" dtype=float32)"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"w2v['play'][:20]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A grande vantagem das incorporações semânticas é que se pode manipular a codificação vetorial com base na semântica. Por exemplo, podemos pedir para encontrar uma palavra cuja representação vetorial esteja o mais próxima possível das palavras *rei* e *mulher*, e o mais distante possível da palavra *homem*:\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('queen', 0.7118192911148071)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
]
},
{
"cell_type": "markdown",
"metadata": {
"tags": []
},
"source": [
"Um exemplo acima utiliza alguma magia interna do GenSym, mas a lógica subjacente é na verdade bastante simples. Uma coisa interessante sobre embeddings é que se podem realizar operações normais de vetores em vetores de embedding, e isso refletiria operações nos **significados** das palavras. O exemplo acima pode ser expresso em termos de operações de vetores: calculamos o vetor correspondente a **REI-HOMEM+MULHER** (as operações `+` e `-` são realizadas nas representações vetoriais das palavras correspondentes), e depois encontramos a palavra mais próxima no dicionário para esse vetor:\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'queen'"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# get the vector corresponding to kind-man+woman\n",
"qvec = w2v['king']-1.7*w2v['man']+1.7*w2v['woman']\n",
"# find the index of the closest embedding vector \n",
"d = np.sum((w2v.vectors-qvec)**2,axis=1)\n",
"min_idx = np.argmin(d)\n",
"# find the corresponding word\n",
"w2v.index_to_key[min_idx]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **NOTA**: Tivemos de adicionar pequenos coeficientes aos vetores *man* e *woman* - experimente removê-los para ver o que acontece.\n",
"\n",
"Para encontrar o vetor mais próximo, utilizamos a estrutura do TensorFlow para calcular um vetor de distâncias entre o nosso vetor e todos os vetores no vocabulário, e depois encontramos o índice da palavra mínima usando `argmin`.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Embora o Word2Vec pareça uma ótima forma de expressar a semântica das palavras, apresenta várias desvantagens, incluindo as seguintes:\n",
"\n",
"* Tanto os modelos CBoW como os skip-gram são **embeddings preditivos**, e consideram apenas o contexto local. O Word2Vec não tira proveito do contexto global.\n",
"* O Word2Vec não leva em conta a **morfologia** das palavras, ou seja, o facto de o significado de uma palavra poder depender de diferentes partes da mesma, como a raiz.\n",
"\n",
"O **FastText** tenta superar a segunda limitação e baseia-se no Word2Vec ao aprender representações vetoriais para cada palavra e para os n-gramas de caracteres encontrados dentro de cada palavra. Os valores das representações são então calculados como uma média, resultando num único vetor em cada etapa de treino. Embora isso adicione um grande volume de computação adicional ao pré-treino, permite que os embeddings de palavras codifiquem informações de subpalavras.\n",
"\n",
"Outro método, o **GloVe**, utiliza uma abordagem diferente para embeddings de palavras, baseada na fatorização da matriz de contexto de palavras. Primeiro, constrói-se uma grande matriz que conta o número de ocorrências de palavras em diferentes contextos, e depois tenta-se representar essa matriz em dimensões reduzidas de forma a minimizar a perda de reconstrução.\n",
"\n",
"A biblioteca gensim suporta esses embeddings de palavras, e pode experimentar com eles alterando o código de carregamento do modelo acima.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Utilizar embeddings pré-treinados no Keras\n",
"\n",
"Podemos modificar o exemplo acima para pré-preencher a matriz na nossa camada de embedding com embeddings semânticos, como o Word2Vec. É provável que os vocabulários do embedding pré-treinado e do corpus de texto não coincidam, por isso precisamos escolher um deles. Aqui exploramos as duas opções possíveis: usar o vocabulário do tokenizer e usar o vocabulário dos embeddings do Word2Vec.\n",
"\n",
"### Usar o vocabulário do tokenizer\n",
"\n",
"Ao usar o vocabulário do tokenizer, algumas palavras do vocabulário terão embeddings correspondentes do Word2Vec, enquanto outras estarão em falta. Dado que o tamanho do nosso vocabulário é `vocab_size`, e o comprimento do vetor de embedding do Word2Vec é `embed_size`, a camada de embedding será representada por uma matriz de pesos com a forma `vocab_size`$\\times$`embed_size`. Vamos preencher esta matriz percorrendo o vocabulário:\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Embedding size: 300\n",
"Populating matrix, this will take some time...Done, found 4551 words, 784 words missing\n"
]
}
],
"source": [
"embed_size = len(w2v.get_vector('hello'))\n",
"print(f'Embedding size: {embed_size}')\n",
"\n",
"vocab = vectorizer.get_vocabulary()\n",
"W = np.zeros((vocab_size,embed_size))\n",
"print('Populating matrix, this will take some time...',end='')\n",
"found, not_found = 0,0\n",
"for i,w in enumerate(vocab):\n",
" try:\n",
" W[i] = w2v.get_vector(w)\n",
" found+=1\n",
" except:\n",
" # W[i] = np.random.normal(0.0,0.3,size=(embed_size,))\n",
" not_found+=1\n",
"\n",
"print(f\"Done, found {found} words, {not_found} words missing\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para palavras que não estão presentes no vocabulário do Word2Vec, podemos deixá-las como zeros ou gerar um vetor aleatório.\n",
"\n",
"Agora podemos definir uma camada de incorporação com pesos pré-treinados:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"emb = keras.layers.Embedding(vocab_size,embed_size,weights=[W],trainable=False)\n",
"model = keras.models.Sequential([\n",
" vectorizer, emb,\n",
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
" keras.layers.Dense(4, activation='softmax')\n",
"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"938/938 [==============================] - 10s 10ms/step - loss: 1.1075 - acc: 0.7822 - val_loss: 0.9134 - val_acc: 0.8175\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x2220226ef10>"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
" validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota**: Repare que definimos `trainable=False` ao criar o `Embedding`, o que significa que não estamos a re-treinar a camada Embedding. Isto pode fazer com que a precisão seja ligeiramente inferior, mas acelera o processo de treino.\n",
"\n",
"### Utilizar o vocabulário do embedding\n",
"\n",
"Um problema com a abordagem anterior é que os vocabulários usados no TextVectorization e no Embedding são diferentes. Para resolver este problema, podemos usar uma das seguintes soluções:\n",
"* Re-treinar o modelo Word2Vec com o nosso vocabulário.\n",
"* Carregar o nosso conjunto de dados com o vocabulário do modelo Word2Vec pré-treinado. Os vocabulários usados para carregar o conjunto de dados podem ser especificados durante o carregamento.\n",
"\n",
"A segunda abordagem parece mais simples, por isso vamos implementá-la. Antes de mais, iremos criar uma camada `TextVectorization` com o vocabulário especificado, retirado dos embeddings do Word2Vec:\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"vocab = list(w2v.vocab.keys())\n",
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(input_shape=(1,))\n",
"vectorizer.set_vocabulary(vocab)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A biblioteca de embeddings de palavras gensim contém uma função conveniente, `get_keras_embeddings`, que criará automaticamente a camada de embeddings correspondente do Keras para si.\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Epoch 1/5\n",
"938/938 [==============================] - 20s 14ms/step - loss: 1.3377 - acc: 0.4978 - val_loss: 1.2995 - val_acc: 0.5647\n",
"Epoch 2/5\n",
"938/938 [==============================] - 10s 10ms/step - loss: 1.2587 - acc: 0.5722 - val_loss: 1.2339 - val_acc: 0.5842\n",
"Epoch 3/5\n",
"938/938 [==============================] - 10s 10ms/step - loss: 1.1980 - acc: 0.5884 - val_loss: 1.1826 - val_acc: 0.5954\n",
"Epoch 4/5\n",
"938/938 [==============================] - 12s 13ms/step - loss: 1.1503 - acc: 0.6002 - val_loss: 1.1417 - val_acc: 0.6018\n",
"Epoch 5/5\n",
"938/938 [==============================] - 11s 12ms/step - loss: 1.1120 - acc: 0.6097 - val_loss: 1.1083 - val_acc: 0.6104\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x2220ccb81c0>"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" vectorizer, \n",
" w2v.get_keras_embedding(train_embeddings=False),\n",
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
" keras.layers.Dense(4, activation='softmax')\n",
"])\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128),epochs=5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Uma das razões pelas quais não estamos a obter maior precisão é porque algumas palavras do nosso conjunto de dados estão ausentes no vocabulário pré-treinado do GloVe e, assim, são essencialmente ignoradas. Para superar isto, podemos treinar os nossos próprios embeddings com base no nosso conjunto de dados.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Embeddings contextuais\n",
"\n",
"Uma das principais limitações das representações tradicionais de embeddings pré-treinados, como o Word2Vec, é o facto de que, embora consigam captar algum significado de uma palavra, não conseguem diferenciar entre significados diferentes. Isto pode causar problemas em modelos subsequentes.\n",
"\n",
"Por exemplo, a palavra 'play' tem significados diferentes nestas duas frases:\n",
"- Fui a uma **peça** no teatro.\n",
"- O João quer **brincar** com os amigos.\n",
"\n",
"Os embeddings pré-treinados de que falámos representam ambos os significados da palavra 'play' no mesmo embedding. Para superar esta limitação, precisamos de construir embeddings baseados no **modelo de linguagem**, que é treinado num grande corpus de texto e *sabe* como as palavras podem ser combinadas em diferentes contextos. Discutir embeddings contextuais está fora do âmbito deste tutorial, mas voltaremos a este tema ao falar sobre modelos de linguagem na próxima unidade.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
},
"kernel_info": {
"name": "conda-env-py37_tensorflow-py"
},
"kernelspec": {
"display_name": "py37_tensorflow",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"nteract": {
"version": "nteract-front-end@1.0.0"
},
"coopTranslator": {
"original_hash": "b859482be7f61d1eadc2c6a2720a37e4",
"translation_date": "2025-08-31T12:02:06+00:00",
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsTF.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,68 @@
# Embeddings
## [Pre-lecture quiz](https://ff-quizzes.netlify.app/en/ai/quiz/27)
Ao treinar classificadores baseados em BoW ou TF/IDF, trabalhávamos com vetores de bag-of-words de alta dimensão com comprimento `vocab_size`, e estávamos a converter explicitamente de vetores de representação posicional de baixa dimensão para uma representação esparsa de one-hot. No entanto, esta representação one-hot não é eficiente em termos de memória. Além disso, cada palavra é tratada de forma independente, ou seja, os vetores codificados em one-hot não expressam qualquer semelhança semântica entre palavras.
A ideia de **embedding** é representar palavras por vetores densos de menor dimensão, que de alguma forma refletem o significado semântico de uma palavra. Mais tarde, discutiremos como construir embeddings de palavras significativos, mas por agora vamos apenas pensar nos embeddings como uma forma de reduzir a dimensionalidade de um vetor de palavras.
Assim, a camada de embedding receberia uma palavra como entrada e produziria um vetor de saída com o tamanho especificado `embedding_size`. De certa forma, é muito semelhante a uma camada `Linear`, mas em vez de receber um vetor codificado em one-hot, será capaz de receber um número de palavra como entrada, permitindo-nos evitar a criação de grandes vetores codificados em one-hot.
Ao usar uma camada de embedding como a primeira camada na nossa rede de classificação, podemos mudar de um modelo de bag-of-words para um modelo de **embedding bag**, onde primeiro convertemos cada palavra no nosso texto no embedding correspondente e, em seguida, calculamos alguma função agregada sobre todos esses embeddings, como `sum`, `average` ou `max`.
![Imagem mostrando um classificador de embedding para cinco palavras de sequência.](../../../../../translated_images/pt-PT/embedding-classifier-example.b77f021a7ee67eee.webp)
> Imagem do autor
## ✍️ Exercícios: Embeddings
Continue a sua aprendizagem nos seguintes notebooks:
* [Embeddings com PyTorch](EmbeddingsPyTorch.ipynb)
* [Embeddings com TensorFlow](EmbeddingsTF.ipynb)
## Embeddings Semânticos: Word2Vec
Embora a camada de embedding tenha aprendido a mapear palavras para representações vetoriais, essa representação não necessariamente possui muito significado semântico. Seria interessante aprender uma representação vetorial tal que palavras semelhantes ou sinónimos correspondam a vetores próximos entre si em termos de alguma distância vetorial (por exemplo, distância Euclidiana).
Para isso, precisamos de pré-treinar o nosso modelo de embedding numa grande coleção de texto de uma forma específica. Uma maneira de treinar embeddings semânticos é chamada [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Baseia-se em duas arquiteturas principais que são usadas para produzir uma representação distribuída de palavras:
- **Continuous bag-of-words** (CBoW) — nesta arquitetura, treinamos o modelo para prever uma palavra a partir do contexto circundante. Dado o ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$, o objetivo do modelo é prever $W_0$ a partir de $(W_{-2},W_{-1},W_1,W_2)$.
- **Continuous skip-gram** é o oposto do CBoW. O modelo usa uma janela de palavras de contexto circundantes para prever a palavra atual.
CBoW é mais rápido, enquanto skip-gram é mais lento, mas faz um trabalho melhor ao representar palavras menos frequentes.
![Imagem mostrando os algoritmos CBoW e Skip-Gram para converter palavras em vetores.](../../../../../translated_images/pt-PT/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)
> Imagem retirada [deste artigo](https://arxiv.org/pdf/1301.3781.pdf)
Os embeddings pré-treinados do Word2Vec (bem como outros modelos semelhantes, como GloVe) também podem ser usados no lugar da camada de embedding em redes neurais. No entanto, precisamos de lidar com vocabulários, porque o vocabulário usado para pré-treinar o Word2Vec/GloVe provavelmente será diferente do vocabulário no nosso corpus de texto. Consulte os notebooks acima para ver como este problema pode ser resolvido.
## Embeddings Contextuais
Uma limitação importante das representações tradicionais de embeddings pré-treinados, como Word2Vec, é o problema da desambiguação de sentidos das palavras. Embora os embeddings pré-treinados possam capturar algum significado das palavras no contexto, todos os possíveis significados de uma palavra são codificados no mesmo embedding. Isso pode causar problemas em modelos posteriores, já que muitas palavras, como a palavra 'play', têm significados diferentes dependendo do contexto em que são usadas.
Por exemplo, a palavra 'play' nas duas frases abaixo tem significados bastante diferentes:
- Fui a uma **peça** no teatro.
- O João quer **brincar** com os amigos.
Os embeddings pré-treinados acima representam ambos os significados da palavra 'play' no mesmo embedding. Para superar esta limitação, precisamos de construir embeddings baseados no **modelo de linguagem**, que é treinado num grande corpus de texto e *sabe* como as palavras podem ser combinadas em diferentes contextos. Discutir embeddings contextuais está fora do âmbito deste tutorial, mas voltaremos a eles quando falarmos sobre modelos de linguagem mais adiante no curso.
## Conclusão
Nesta lição, descobriu como construir e usar camadas de embedding no TensorFlow e PyTorch para refletir melhor os significados semânticos das palavras.
## 🚀 Desafio
O Word2Vec tem sido usado em algumas aplicações interessantes, incluindo a geração de letras de músicas e poesia. Veja [este artigo](https://www.politetype.com/blog/word2vec-color-poems), que explica como o autor usou o Word2Vec para gerar poesia. Assista também a [este vídeo de Dan Shiffmann](https://www.youtube.com/watch?v=LSS_bos_TPI&ab_channel=TheCodingTrain) para descobrir uma explicação diferente desta técnica. Depois, tente aplicar estas técnicas ao seu próprio corpus de texto, talvez obtido no Kaggle.
## [Post-lecture quiz](https://ff-quizzes.netlify.app/en/ai/quiz/28)
## Revisão & Autoestudo
Leia este artigo sobre Word2Vec: [Efficient Estimation of Word Representations in Vector Space](https://arxiv.org/pdf/1301.3781.pdf)
## [Assignment: Notebooks](assignment.md)
---

View File

@ -0,0 +1,6 @@
# Tarefa: Notebooks
Utilizando os notebooks associados a esta lição (seja a versão em PyTorch ou TensorFlow), execute-os novamente usando o seu próprio conjunto de dados, talvez um do Kaggle, utilizado com a devida atribuição. Reescreva o notebook para destacar as suas próprias conclusões. Experimente um tipo diferente de conjunto de dados e documente as suas descobertas, utilizando texto como [estas letras dos Beatles](https://www.kaggle.com/datasets/jenlooper/beatles-lyrics).
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,576 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "NXTSugt6ieXh"
},
"source": [
"## Treinar Modelo CBoW\n",
"\n",
"Este notebook faz parte do [Currículo de IA para Iniciantes](http://aka.ms/ai-beginners)\n",
"\n",
"Neste exemplo, vamos explorar o treino de um modelo de linguagem CBoW para obter o nosso próprio espaço de embeddings Word2Vec. Usaremos o conjunto de dados AG News como fonte de texto.\n"
]
},
{
"cell_type": "code",
"source": [
"import torch\n",
"import torchtext\n",
"import os\n",
"import collections\n",
"import builtins\n",
"import random\n",
"import numpy as np"
],
"metadata": {
"id": "q-UiiJUKaxHj"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"
],
"metadata": {
"id": "TFbR8CZaTZ1q"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"Primeiro, vamos carregar o nosso conjunto de dados e definir o tokenizador e o vocabulário. Vamos definir `vocab_size` para 5000 para limitar um pouco os cálculos.\n"
],
"metadata": {
"id": "HIwC7lI5T-ov"
}
},
{
"cell_type": "code",
"source": [
"def load_dataset(ngrams = 1, min_freq = 1, vocab_size = 5000 , lines_cnt = 500):\n",
" tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
" print(\"Loading dataset...\")\n",
" test_dataset, train_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
" train_dataset = list(train_dataset)\n",
" test_dataset = list(test_dataset)\n",
" classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
" print('Building vocab...')\n",
" counter = collections.Counter()\n",
" for i, (_, line) in enumerate(train_dataset):\n",
" counter.update(torchtext.data.utils.ngrams_iterator(tokenizer(line),ngrams=ngrams))\n",
" if i == lines_cnt:\n",
" break\n",
" vocab = torchtext.vocab.Vocab(collections.Counter(dict(counter.most_common(vocab_size))), min_freq=min_freq)\n",
" return train_dataset, test_dataset, classes, vocab, tokenizer"
],
"metadata": {
"id": "wdZuygtgiuLG"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"train_dataset, test_dataset, _, vocab, tokenizer = load_dataset()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "4d1nU1gsivGu",
"outputId": "949fe272-ae0e-49f5-c373-6703458b3a74"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"def encode(x, vocabulary, tokenizer = tokenizer):\n",
" return [vocabulary[s] for s in tokenizer(x)]"
],
"metadata": {
"id": "1XDYNhG8ToFV"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "LIlQk6_PaHVY"
},
"source": [
"## Modelo CBoW\n",
"\n",
"O modelo CBoW aprende a prever uma palavra com base nas $2N$ palavras vizinhas. Por exemplo, quando $N=1$, obtemos os seguintes pares a partir da frase *I like to train networks*: (like, I), (I, like), (to, like), (like, to), (train, to), (to, train), (networks, train), (train, networks). Aqui, a primeira palavra é a palavra vizinha usada como entrada, e a segunda palavra é aquela que estamos a prever.\n",
"\n",
"Para construir uma rede que preveja a próxima palavra, será necessário fornecer a palavra vizinha como entrada e obter o número da palavra como saída. A arquitetura da rede CBoW é a seguinte:\n",
"\n",
"* A palavra de entrada é passada por uma camada de embeddings. Esta mesma camada de embeddings será o nosso embedding Word2Vec, pelo que a definiremos separadamente como a variável `embedder`. Neste exemplo, utilizaremos um tamanho de embedding = 30, embora possas querer experimentar dimensões mais altas (o Word2Vec real tem 300).\n",
"* O vetor de embedding será então passado por uma camada linear que irá prever a palavra de saída. Assim, esta camada terá `vocab_size` neurónios.\n",
"\n",
"Para a saída, se utilizarmos `CrossEntropyLoss` como função de perda, também será necessário fornecer apenas os números das palavras como resultados esperados, sem codificação one-hot.\n"
]
},
{
"cell_type": "code",
"source": [
"vocab_size = len(vocab)\n",
"\n",
"embedder = torch.nn.Embedding(num_embeddings = vocab_size, embedding_dim = 30)\n",
"model = torch.nn.Sequential(\n",
" embedder,\n",
" torch.nn.Linear(in_features = 30, out_features = vocab_size),\n",
")\n",
"\n",
"print(model)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "akKTcKQKkfl2",
"outputId": "da687e3e-a8ec-4c1a-e456-ab8cd6ac7dad"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Sequential(\n",
" (0): Embedding(5002, 30)\n",
" (1): Linear(in_features=30, out_features=5002, bias=True)\n",
")\n"
]
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Nud6jgGPaHVa"
},
"source": [
"## Preparar Dados de Treino\n",
"\n",
"Agora vamos programar a função principal que irá calcular os pares de palavras CBoW a partir de texto. Esta função permitirá especificar o tamanho da janela e retornará um conjunto de pares - palavra de entrada e palavra de saída. Note que esta função pode ser usada tanto em palavras como em vetores/tensores - o que nos permitirá codificar o texto antes de o passar para a função `to_cbow`.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "x-dsXygOieXn",
"outputId": "c2218280-e540-40ba-9546-efe48d0d714f"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[['like', 'I'], ['to', 'I'], ['I', 'like'], ['to', 'like'], ['train', 'like'], ['I', 'to'], ['like', 'to'], ['train', 'to'], ['networks', 'to'], ['like', 'train'], ['to', 'train'], ['networks', 'train'], ['to', 'networks'], ['train', 'networks']]\n",
"[[232, 172], [5, 172], [172, 232], [5, 232], [0, 232], [172, 5], [232, 5], [0, 5], [1202, 5], [232, 0], [5, 0], [1202, 0], [5, 1202], [0, 1202]]\n"
]
}
],
"source": [
"def to_cbow(sent,window_size=2):\n",
" res = []\n",
" for i,x in enumerate(sent):\n",
" for j in range(max(0,i-window_size),min(i+window_size+1,len(sent))):\n",
" if i!=j:\n",
" res.append([sent[j],x])\n",
" return res\n",
"\n",
"print(to_cbow(['I','like','to','train','networks']))\n",
"print(to_cbow(encode('I like to train networks', vocab)))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "XVaaDLjaaHVb"
},
"source": [
"Vamos preparar o conjunto de dados de treino. Vamos passar por todas as notícias, chamar `to_cbow` para obter a lista de pares de palavras e adicionar esses pares a `X` e `Y`. Por questões de tempo, vamos considerar apenas os primeiros 10k itens de notícias - pode facilmente remover esta limitação caso tenha mais tempo para esperar e queira obter melhores embeddings :)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "54b-Gd9TieXo"
},
"outputs": [],
"source": [
"X = []\n",
"Y = []\n",
"for i, x in zip(range(10000), train_dataset):\n",
" for w1, w2 in to_cbow(encode(x[1], vocab), window_size = 5):\n",
" X.append(w1)\n",
" Y.append(w2)\n",
"\n",
"X = torch.tensor(X)\n",
"Y = torch.tensor(Y)"
]
},
{
"cell_type": "markdown",
"source": [
"Também iremos converter esses dados para um único conjunto de dados e criar um dataloader:\n"
],
"metadata": {
"id": "cwWy0PzXWhN5"
}
},
{
"cell_type": "code",
"source": [
"class SimpleIterableDataset(torch.utils.data.IterableDataset):\n",
" def __init__(self, X, Y):\n",
" super(SimpleIterableDataset).__init__()\n",
" self.data = []\n",
" for i in range(len(X)):\n",
" self.data.append( (Y[i], X[i]) )\n",
" random.shuffle(self.data)\n",
"\n",
" def __iter__(self):\n",
" return iter(self.data)"
],
"metadata": {
"id": "mfoAcGPFZU8p"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "e4NQ_-5waHVc"
},
"source": [
"Também iremos converter esses dados para um único conjunto de dados e criar um dataloader:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "AbLUcojlieXo"
},
"outputs": [],
"source": [
"ds = SimpleIterableDataset(X, Y)\n",
"dl = torch.utils.data.DataLoader(ds, batch_size = 256)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "pKQr7sXeaHVc"
},
"source": [
"Agora vamos fazer o treino propriamente dito. Vamos usar o otimizador `SGD` com uma taxa de aprendizagem bastante alta. Também pode experimentar outros otimizadores, como o `Adam`. Vamos treinar durante 10 épocas para começar - e pode voltar a executar esta célula se quiser uma perda ainda menor.\n"
]
},
{
"cell_type": "code",
"source": [
"def train_epoch(net, dataloader, lr = 0.01, optimizer = None, loss_fn = torch.nn.CrossEntropyLoss(), epochs = None, report_freq = 1):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(), lr = lr)\n",
" loss_fn = loss_fn.to(device)\n",
" net.train()\n",
"\n",
" for i in range(epochs):\n",
" total_loss, j = 0, 0, \n",
" for labels, features in dataloader:\n",
" optimizer.zero_grad()\n",
" features, labels = features.to(device), labels.to(device)\n",
" out = net(features)\n",
" loss = loss_fn(out, labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss += loss\n",
" j += 1\n",
" if i % report_freq == 0:\n",
" print(f\"Epoch: {i+1}: loss={total_loss.item()/j}\")\n",
"\n",
" return total_loss.item()/j"
],
"metadata": {
"id": "HeeCYKr_KF1w"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"train_epoch(net = model, dataloader = dl, optimizer = torch.optim.SGD(model.parameters(), lr = 0.1), loss_fn = torch.nn.CrossEntropyLoss(), epochs = 10)"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "KVgwGtDHgDlT",
"outputId": "2447833f-f0e3-4566-c33d-addbfe2f451d"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Epoch: 1: loss=5.664632366860172\n",
"Epoch: 2: loss=5.632101973960962\n",
"Epoch: 3: loss=5.610399051405015\n",
"Epoch: 4: loss=5.594621561080262\n",
"Epoch: 5: loss=5.582538017415446\n",
"Epoch: 6: loss=5.572900234519603\n",
"Epoch: 7: loss=5.564951676341915\n",
"Epoch: 8: loss=5.558288112064614\n",
"Epoch: 9: loss=5.552576955031129\n",
"Epoch: 10: loss=5.547634165194347\n"
]
},
{
"output_type": "execute_result",
"data": {
"text/plain": [
"5.547634165194347"
]
},
"metadata": {},
"execution_count": 16
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "W8u2qXZmaHVd"
},
"source": [
"## Experimentar o Word2Vec\n",
"\n",
"Para utilizar o Word2Vec, vamos extrair os vetores correspondentes a todas as palavras do nosso vocabulário:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "r8TatcXjkU_t"
},
"outputs": [],
"source": [
"vectors = torch.stack([embedder(torch.tensor(vocab[s])) for s in vocab.itos], 0)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "3OcX21UOaHVd"
},
"source": [
"Vamos ver, por exemplo, como a palavra **Paris** é codificada num vetor:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "bz6tAeLzieXp",
"outputId": "5b20850e-4342-45e9-f840-cfac2b4d61d8"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"tensor([-0.0915, 2.1224, -0.0281, -0.6819, 1.1219, 0.6458, -1.3704, -1.3314,\n",
" -1.1437, 0.4496, 0.2301, -0.3515, -0.8485, 1.0481, 0.4386, -0.8949,\n",
" 0.5644, 1.0939, -2.5096, 3.2949, -0.2601, -0.8640, 0.1421, -0.0804,\n",
" -0.5083, -1.0560, 0.9753, -0.5949, -1.6046, 0.5774],\n",
" grad_fn=<EmbeddingBackward>)\n"
]
}
],
"source": [
"paris_vec = embedder(torch.tensor(vocab['paris']))\n",
"print(paris_vec)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "pHTJlaeYaHVd"
},
"source": [
"É interessante usar Word2Vec para procurar sinónimos. A seguinte função irá devolver as `n` palavras mais próximas de uma dada entrada. Para encontrá-las, calculamos a norma de $|w_i - v|$, onde $v$ é o vetor correspondente à nossa palavra de entrada, e $w_i$ é a codificação da palavra `i`-ésima no vocabulário. Em seguida, ordenamos o array e devolvemos os índices correspondentes usando `argsort`, e selecionamos os primeiros `n` elementos da lista, que codificam as posições das palavras mais próximas no vocabulário.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "NlZyi-_olFar",
"outputId": "b5dbb163-88c4-4d5a-eaf2-6751f700e98c"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['microsoft', 'quoted', 'lp', 'rate', 'top']"
]
},
"metadata": {},
"execution_count": 56
}
],
"source": [
"def close_words(x, n = 5):\n",
" vec = embedder(torch.tensor(vocab[x]))\n",
" top5 = np.linalg.norm(vectors.detach().numpy() - vec.detach().numpy(), axis = 1).argsort()[:n]\n",
" return [ vocab.itos[x] for x in top5 ]\n",
"\n",
"close_words('microsoft')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "-dQq7xeAln0U",
"outputId": "66f768c3-c248-4bfd-ce4f-c8ffc6d0dd0d"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['basketball', 'lot', 'sinai', 'states', 'healthdaynews']"
]
},
"metadata": {},
"execution_count": 51
}
],
"source": [
"close_words('basketball')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "fJXqK26b29sa",
"outputId": "78f0baba-ffd0-485a-dd87-0a12bedfd7fa"
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['funds', 'travel', 'sydney', 'japan', 'business']"
]
},
"metadata": {},
"execution_count": 77
}
],
"source": [
"close_words('funds')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "My0VeTDd3Ji8"
},
"source": [
"## Conclusão\n",
"\n",
"Utilizando técnicas inteligentes como o CBoW, podemos treinar o modelo Word2Vec. Também pode experimentar treinar o modelo skip-gram, que é treinado para prever a palavra vizinha com base na palavra central, e verificar o seu desempenho.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [],
"name": "CBoW-PyTorch.ipynb",
"provenance": []
},
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"orig_nbformat": 4,
"gpuClass": "standard",
"coopTranslator": {
"original_hash": "36df28efe3fe40b6fb0a7fa48fe3ea82",
"translation_date": "2025-08-31T11:56:51+00:00",
"source_file": "lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
# Modelação de Linguagem
Embeddings semânticos, como Word2Vec e GloVe, são, na verdade, um primeiro passo para a **modelação de linguagem** - criar modelos que de alguma forma *compreendem* (ou *representam*) a natureza da linguagem.
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/29)
A ideia principal por trás da modelação de linguagem é treiná-los em conjuntos de dados não rotulados de forma não supervisionada. Isto é importante porque temos enormes quantidades de texto não rotulado disponíveis, enquanto a quantidade de texto rotulado estará sempre limitada pelo esforço necessário para rotular. Na maioria das vezes, podemos construir modelos de linguagem que conseguem **prever palavras em falta** no texto, porque é fácil ocultar uma palavra aleatória no texto e usá-la como amostra de treino.
## Treinar Embeddings
Nos nossos exemplos anteriores, utilizámos embeddings semânticos pré-treinados, mas é interessante ver como esses embeddings podem ser treinados. Existem várias ideias possíveis que podem ser utilizadas:
* **Modelação de linguagem N-Gram**, onde prevemos um token olhando para os N tokens anteriores (N-gram).
* **Continuous Bag-of-Words** (CBoW), onde prevemos o token central $W_0$ numa sequência de tokens $W_{-N}$, ..., $W_N$.
* **Skip-gram**, onde prevemos um conjunto de tokens vizinhos {$W_{-N},\dots, W_{-1}, W_1,\dots, W_N$} a partir do token central $W_0$.
![imagem do artigo sobre conversão de palavras em vetores](../../../../../translated_images/pt-PT/example-algorithms-for-converting-words-to-vectors.fbe9207a726922f6.webp)
> Imagem retirada [deste artigo](https://arxiv.org/pdf/1301.3781.pdf)
## ✍️ Notebooks de Exemplo: Treinar modelo CBoW
Continue a sua aprendizagem nos seguintes notebooks:
* [Treinar CBoW Word2Vec com TensorFlow](CBoW-TF.ipynb)
* [Treinar CBoW Word2Vec com PyTorch](CBoW-PyTorch.ipynb)
## Conclusão
Na aula anterior vimos que embeddings de palavras funcionam como magia! Agora sabemos que treinar embeddings de palavras não é uma tarefa muito complexa, e devemos ser capazes de treinar os nossos próprios embeddings para texto específico de um domínio, se necessário.
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/30)
## Revisão & Estudo Individual
* [Tutorial oficial do PyTorch sobre Modelação de Linguagem](https://pytorch.org/tutorials/beginner/nlp/word_embeddings_tutorial.html).
* [Tutorial oficial do TensorFlow sobre treinar modelo Word2Vec](https://www.TensorFlow.org/tutorials/text/word2vec).
* Utilizar o framework **gensim** para treinar os embeddings mais comuns em poucas linhas de código está descrito [nesta documentação](https://pytorch.org/tutorials/beginner/nlp/word_embeddings_tutorial.html).
## 🚀 [Tarefa: Treinar Modelo Skip-Gram](lab/README.md)
No laboratório, desafiamos você a modificar o código desta aula para treinar um modelo skip-gram em vez de CBoW. [Leia os detalhes](lab/README.md)
---

View File

@ -0,0 +1,29 @@
# Treinar Modelo Skip-Gram
Trabalho prático do [Currículo AI for Beginners](https://github.com/microsoft/ai-for-beginners).
## Tarefa
Neste laboratório, desafiamos-te a treinar um modelo Word2Vec utilizando a técnica Skip-Gram. Treina uma rede com embeddings para prever palavras vizinhas numa janela Skip-Gram de $N$ tokens de largura. Podes usar o [código desta lição](../../../../../../lessons/5-NLP/15-LanguageModeling/CBoW-TF.ipynb) e modificá-lo ligeiramente.
## O Conjunto de Dados
Podes usar qualquer livro. Podes encontrar muitos textos gratuitos no [Project Gutenberg](https://www.gutenberg.org/), por exemplo, aqui está um link direto para [Alice's Adventures in Wonderland](https://www.gutenberg.org/files/11/11-0.txt) de Lewis Carroll. Ou, podes usar as peças de Shakespeare, que podes obter utilizando o seguinte código:
```python
path_to_file = tf.keras.utils.get_file(
'shakespeare.txt',
'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')
text = open(path_to_file, 'rb').read().decode(encoding='utf-8')
```
## Explora!
Se tiveres tempo e quiseres aprofundar o tema, tenta explorar várias questões:
* Como o tamanho do embedding afeta os resultados?
* Como diferentes estilos de texto afetam o resultado?
* Escolhe vários tipos de palavras muito diferentes e os seus sinónimos, obtém as suas representações vetoriais, aplica PCA para reduzir as dimensões para 2 e representa-as num espaço 2D. Consegues identificar algum padrão?
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos pela precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se uma tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.

View File

@ -0,0 +1,87 @@
# Redes Neuronais Recorrentes
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/31)
Nas secções anteriores, utilizámos representações semânticas ricas de texto e um classificador linear simples sobre as embeddings. Esta arquitetura captura o significado agregado das palavras numa frase, mas não considera a **ordem** das palavras, porque a operação de agregação sobre as embeddings remove esta informação do texto original. Como estes modelos não conseguem modelar a ordem das palavras, não conseguem resolver tarefas mais complexas ou ambíguas, como geração de texto ou resposta a perguntas.
Para capturar o significado de uma sequência de texto, precisamos de usar outra arquitetura de rede neural, chamada **rede neural recorrente**, ou RNN. Numa RNN, passamos a nossa frase pela rede, um símbolo de cada vez, e a rede produz um **estado**, que depois passamos novamente à rede com o próximo símbolo.
![RNN](../../../../../translated_images/pt-PT/rnn.27f5c29c53d727b5.webp)
> Imagem do autor
Dada a sequência de entrada de tokens X<sub>0</sub>,...,X<sub>n</sub>, a RNN cria uma sequência de blocos de rede neural e treina esta sequência de ponta a ponta usando retropropagação. Cada bloco de rede recebe um par (X<sub>i</sub>,S<sub>i</sub>) como entrada e produz S<sub>i+1</sub> como resultado. O estado final S<sub>n</sub> ou (saída Y<sub>n</sub>) é enviado para um classificador linear para produzir o resultado. Todos os blocos da rede partilham os mesmos pesos e são treinados de ponta a ponta usando uma única passagem de retropropagação.
Como os vetores de estado S<sub>0</sub>,...,S<sub>n</sub> são passados pela rede, esta consegue aprender as dependências sequenciais entre palavras. Por exemplo, quando a palavra *não* aparece em algum lugar da sequência, a rede pode aprender a negar certos elementos dentro do vetor de estado, resultando em negação.
> ✅ Como os pesos de todos os blocos RNN na imagem acima são partilhados, a mesma imagem pode ser representada como um único bloco (à direita) com um loop de feedback recorrente, que passa o estado de saída da rede de volta para a entrada.
## Anatomia de uma Célula RNN
Vamos ver como uma célula RNN simples é organizada. Ela aceita o estado anterior S<sub>i-1</sub> e o símbolo atual X<sub>i</sub> como entradas, e tem de produzir o estado de saída S<sub>i</sub> (e, por vezes, também estamos interessados noutra saída Y<sub>i</sub>, como no caso de redes generativas).
Uma célula RNN simples tem duas matrizes de pesos internas: uma transforma um símbolo de entrada (vamos chamá-la de W) e outra transforma um estado de entrada (H). Neste caso, a saída da rede é calculada como &sigma;(W&times;X<sub>i</sub>+H&times;S<sub>i-1</sub>+b), onde &sigma; é a função de ativação e b é um viés adicional.
<img alt="Anatomia da Célula RNN" src="../../../../../translated_images/pt-PT/rnn-anatomy.79ee3f3920b3294b.webp" width="50%"/>
> Imagem do autor
Em muitos casos, os tokens de entrada são passados por uma camada de embedding antes de entrar na RNN para reduzir a dimensionalidade. Neste caso, se a dimensão dos vetores de entrada for *emb_size* e o vetor de estado for *hid_size*, o tamanho de W será *emb_size*&times;*hid_size*, e o tamanho de H será *hid_size*&times;*hid_size*.
## Memória de Longo e Curto Prazo (LSTM)
Um dos principais problemas das RNNs clássicas é o chamado problema de **gradientes que desaparecem**. Como as RNNs são treinadas de ponta a ponta numa única passagem de retropropagação, têm dificuldade em propagar o erro para as primeiras camadas da rede, e assim a rede não consegue aprender relações entre tokens distantes. Uma das formas de evitar este problema é introduzir **gestão explícita de estado** usando os chamados **gates**. Existem duas arquiteturas bem conhecidas deste tipo: **Memória de Longo e Curto Prazo** (LSTM) e **Unidade de Relevo com Gate** (GRU).
![Imagem mostrando um exemplo de célula de memória de longo e curto prazo](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)
> Fonte da imagem a definir
A rede LSTM é organizada de forma semelhante à RNN, mas existem dois estados que são passados de camada para camada: o estado real C e o vetor oculto H. Em cada unidade, o vetor oculto H<sub>i</sub> é concatenado com a entrada X<sub>i</sub>, e eles controlam o que acontece ao estado C através de **gates**. Cada gate é uma rede neural com ativação sigmoide (saída no intervalo [0,1]), que pode ser vista como uma máscara bit a bit quando multiplicada pelo vetor de estado. Existem os seguintes gates (da esquerda para a direita na imagem acima):
* O **gate de esquecimento** recebe um vetor oculto e determina quais componentes do vetor C precisamos esquecer e quais passar adiante.
* O **gate de entrada** extrai alguma informação dos vetores de entrada e ocultos e insere-a no estado.
* O **gate de saída** transforma o estado através de uma camada linear com ativação *tanh*, depois seleciona alguns dos seus componentes usando um vetor oculto H<sub>i</sub> para produzir um novo estado C<sub>i+1</sub>.
Os componentes do estado C podem ser vistos como algumas flags que podem ser ativadas ou desativadas. Por exemplo, quando encontramos o nome *Alice* na sequência, podemos assumir que se refere a uma personagem feminina e ativar a flag no estado indicando que temos um substantivo feminino na frase. Quando encontramos posteriormente a frase *e Tom*, ativamos a flag indicando que temos um substantivo plural. Assim, manipulando o estado, podemos supostamente acompanhar as propriedades gramaticais das partes da frase.
> ✅ Um excelente recurso para entender os detalhes internos do LSTM é este ótimo artigo [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) de Christopher Olah.
## RNNs Bidirecionais e Multicamadas
Discutimos redes recorrentes que operam numa direção, do início de uma sequência até ao fim. Parece natural, porque se assemelha à forma como lemos e ouvimos discurso. No entanto, como em muitos casos práticos temos acesso aleatório à sequência de entrada, pode fazer sentido executar o cálculo recorrente em ambas as direções. Estas redes são chamadas de **RNNs bidirecionais**. Ao lidar com redes bidirecionais, precisaríamos de dois vetores de estado oculto, um para cada direção.
Uma rede recorrente, seja unidirecional ou bidirecional, captura certos padrões dentro de uma sequência e pode armazená-los num vetor de estado ou passá-los para a saída. Tal como nas redes convolucionais, podemos construir outra camada recorrente sobre a primeira para capturar padrões de nível superior e construir a partir dos padrões de baixo nível extraídos pela primeira camada. Isto leva-nos à noção de uma **RNN multicamada**, que consiste em duas ou mais redes recorrentes, onde a saída da camada anterior é passada para a próxima camada como entrada.
![Imagem mostrando uma RNN multicamada com LSTM](../../../../../translated_images/pt-PT/multi-layer-lstm.dd975e29bb2a59fe.webp)
*Imagem retirada [deste excelente artigo](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) de Fernando López*
## ✍️ Exercícios: Embeddings
Continue a sua aprendizagem nos seguintes notebooks:
* [RNNs com PyTorch](RNNPyTorch.ipynb)
* [RNNs com TensorFlow](RNNTF.ipynb)
## Conclusão
Nesta unidade, vimos que as RNNs podem ser usadas para classificação de sequências, mas na verdade, elas podem lidar com muitas outras tarefas, como geração de texto, tradução automática e mais. Vamos considerar essas tarefas na próxima unidade.
## 🚀 Desafio
Leia alguma literatura sobre LSTMs e considere as suas aplicações:
- [Grid Long Short-Term Memory](https://arxiv.org/pdf/1507.01526v1.pdf)
- [Show, Attend and Tell: Neural Image Caption
Generation with Visual Attention](https://arxiv.org/pdf/1502.03044v2.pdf)
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/32)
## Revisão e Autoestudo
- [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) de Christopher Olah.
## [Tarefa: Notebooks](assignment.md)
---

View File

@ -0,0 +1,479 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Redes neuronais recorrentes\n",
"\n",
"No módulo anterior, utilizámos representações semânticas ricas de texto e um classificador linear simples sobre as embeddings. O que esta arquitetura faz é capturar o significado agregado das palavras numa frase, mas não considera a **ordem** das palavras, porque a operação de agregação sobre as embeddings removeu esta informação do texto original. Como estes modelos não conseguem modelar a ordem das palavras, não conseguem resolver tarefas mais complexas ou ambíguas, como geração de texto ou resposta a perguntas.\n",
"\n",
"Para capturar o significado de uma sequência de texto, precisamos de usar outra arquitetura de rede neural, chamada **rede neural recorrente**, ou RNN. Numa RNN, passamos a nossa frase pela rede, um símbolo de cada vez, e a rede produz um **estado**, que depois passamos novamente para a rede juntamente com o próximo símbolo.\n",
"\n",
"Dada a sequência de tokens de entrada $X_0,\\dots,X_n$, a RNN cria uma sequência de blocos de rede neural e treina esta sequência de ponta a ponta utilizando retropropagação. Cada bloco de rede recebe um par $(X_i,S_i)$ como entrada e produz $S_{i+1}$ como resultado. O estado final $S_n$ ou a saída $X_n$ é enviado para um classificador linear para produzir o resultado. Todos os blocos da rede partilham os mesmos pesos e são treinados de ponta a ponta utilizando uma única passagem de retropropagação.\n",
"\n",
"Como os vetores de estado $S_0,\\dots,S_n$ são passados pela rede, esta consegue aprender as dependências sequenciais entre palavras. Por exemplo, quando a palavra *não* aparece em algum lugar da sequência, a rede pode aprender a negar certos elementos dentro do vetor de estado, resultando em negação.\n",
"\n",
"> Como os pesos de todos os blocos da RNN na imagem são partilhados, a mesma imagem pode ser representada como um único bloco (à direita) com um loop de feedback recorrente, que passa o estado de saída da rede de volta para a entrada.\n",
"\n",
"Vamos ver como as redes neuronais recorrentes podem ajudar-nos a classificar o nosso conjunto de dados de notícias.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"from torchnlp import *\n",
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
"vocab_size = len(vocab)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Classificador RNN Simples\n",
"\n",
"No caso de um RNN simples, cada unidade recorrente é uma rede linear simples, que recebe um vetor de entrada concatenado e um vetor de estado, e produz um novo vetor de estado. O PyTorch representa esta unidade com a classe `RNNCell`, e uma rede composta por estas células - como a camada `RNN`.\n",
"\n",
"Para definir um classificador RNN, primeiro aplicaremos uma camada de embedding para reduzir a dimensionalidade do vocabulário de entrada, e depois utilizaremos uma camada RNN sobre esta:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class RNNClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,h = self.rnn(x)\n",
" return self.fc(x.mean(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota:** Aqui utilizamos uma camada de embedding não treinada para simplificar, mas para obter resultados ainda melhores, podemos usar uma camada de embedding pré-treinada com embeddings do Word2Vec ou GloVe, conforme descrito na unidade anterior. Para uma melhor compreensão, pode ser útil adaptar este código para funcionar com embeddings pré-treinados.\n",
"\n",
"No nosso caso, utilizaremos um carregador de dados com preenchimento (padded data loader), de forma que cada lote terá um número de sequências preenchidas com o mesmo comprimento. A camada RNN receberá a sequência de tensores de embedding e produzirá dois outputs:\n",
"* $x$ é uma sequência de outputs das células RNN em cada passo\n",
"* $h$ é o estado oculto final para o último elemento da sequência\n",
"\n",
"De seguida, aplicamos um classificador linear totalmente conectado para obter o número de classes.\n",
"\n",
"> **Nota:** As RNNs são bastante difíceis de treinar, porque, uma vez que as células RNN são desenroladas ao longo do comprimento da sequência, o número resultante de camadas envolvidas na retropropagação é bastante elevado. Por isso, é necessário selecionar uma taxa de aprendizagem pequena e treinar a rede num conjunto de dados maior para obter bons resultados. Este processo pode demorar bastante tempo, pelo que é preferível utilizar uma GPU.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.3090625\n",
"6400: acc=0.38921875\n",
"9600: acc=0.4590625\n",
"12800: acc=0.511953125\n",
"16000: acc=0.5506875\n",
"19200: acc=0.57921875\n",
"22400: acc=0.6070089285714285\n",
"25600: acc=0.6304296875\n",
"28800: acc=0.6484027777777778\n",
"32000: acc=0.66509375\n",
"35200: acc=0.6790056818181818\n",
"38400: acc=0.6929166666666666\n",
"41600: acc=0.7035817307692308\n",
"44800: acc=0.7137276785714286\n",
"48000: acc=0.72225\n",
"51200: acc=0.73001953125\n",
"54400: acc=0.7372794117647059\n",
"57600: acc=0.7436631944444444\n",
"60800: acc=0.7503947368421052\n",
"64000: acc=0.75634375\n",
"67200: acc=0.7615773809523809\n",
"70400: acc=0.7662642045454545\n",
"73600: acc=0.7708423913043478\n",
"76800: acc=0.7751822916666666\n",
"80000: acc=0.7790625\n",
"83200: acc=0.7825\n",
"86400: acc=0.7858564814814815\n",
"89600: acc=0.7890513392857142\n",
"92800: acc=0.7920474137931034\n",
"96000: acc=0.7952708333333334\n",
"99200: acc=0.7982258064516129\n",
"102400: acc=0.80099609375\n",
"105600: acc=0.8037594696969697\n",
"108800: acc=0.8060569852941176\n"
]
}
],
"source": [
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Long Short Term Memory (LSTM)\n",
"\n",
"Um dos principais problemas das RNNs clássicas é o chamado problema de **gradientes que desaparecem**. Como as RNNs são treinadas de ponta a ponta em uma única passagem de retropropagação, têm dificuldade em propagar o erro para as primeiras camadas da rede, e, por isso, a rede não consegue aprender relações entre tokens distantes. Uma das formas de evitar este problema é introduzir **gestão explícita de estado** utilizando os chamados **gates**. Existem duas arquiteturas mais conhecidas deste tipo: **Long Short Term Memory** (LSTM) e **Gated Relay Unit** (GRU).\n",
"\n",
"![Imagem mostrando um exemplo de célula de memória de longo curto prazo](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"A rede LSTM é organizada de forma semelhante à RNN, mas existem dois estados que são passados de camada para camada: o estado atual $c$ e o vetor oculto $h$. Em cada unidade, o vetor oculto $h_i$ é concatenado com a entrada $x_i$, e eles controlam o que acontece ao estado $c$ através dos **gates**. Cada gate é uma rede neural com ativação sigmoide (saída no intervalo $[0,1]$), que pode ser vista como uma máscara bit a bit quando multiplicada pelo vetor de estado. Existem os seguintes gates (da esquerda para a direita na imagem acima):\n",
"* **forget gate** utiliza o vetor oculto e determina quais componentes do vetor $c$ precisamos esquecer e quais devemos manter.\n",
"* **input gate** extrai algumas informações da entrada e do vetor oculto e insere-as no estado.\n",
"* **output gate** transforma o estado através de uma camada linear com ativação $\\tanh$, e depois seleciona alguns dos seus componentes usando o vetor oculto $h_i$ para produzir o novo estado $c_{i+1}$.\n",
"\n",
"Os componentes do estado $c$ podem ser vistos como algumas bandeiras que podem ser ativadas ou desativadas. Por exemplo, quando encontramos o nome *Alice* numa sequência, podemos assumir que se refere a uma personagem feminina e ativar a bandeira no estado indicando que temos um substantivo feminino na frase. Quando mais tarde encontramos a expressão *e Tom*, ativamos a bandeira indicando que temos um substantivo no plural. Assim, manipulando o estado, podemos supostamente acompanhar as propriedades gramaticais das partes da frase.\n",
"\n",
"> **Note**: Um excelente recurso para entender os detalhes internos do LSTM é este ótimo artigo [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) de Christopher Olah.\n",
"\n",
"Embora a estrutura interna da célula LSTM possa parecer complexa, o PyTorch oculta esta implementação dentro da classe `LSTMCell` e fornece o objeto `LSTM` para representar toda a camada LSTM. Assim, a implementação de um classificador LSTM será bastante semelhante à RNN simples que vimos acima:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"class LSTMClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" x,(h,c) = self.rnn(x)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.259375\n",
"6400: acc=0.25859375\n",
"9600: acc=0.26177083333333334\n",
"12800: acc=0.2784375\n",
"16000: acc=0.313\n",
"19200: acc=0.3528645833333333\n",
"22400: acc=0.3965625\n",
"25600: acc=0.4385546875\n",
"28800: acc=0.4752777777777778\n",
"32000: acc=0.505375\n",
"35200: acc=0.5326704545454546\n",
"38400: acc=0.5557552083333334\n",
"41600: acc=0.5760817307692307\n",
"44800: acc=0.5954910714285714\n",
"48000: acc=0.6118333333333333\n",
"51200: acc=0.62681640625\n",
"54400: acc=0.6404779411764706\n",
"57600: acc=0.6520138888888889\n",
"60800: acc=0.662828947368421\n",
"64000: acc=0.673546875\n",
"67200: acc=0.6831547619047619\n",
"70400: acc=0.6917897727272727\n",
"73600: acc=0.6997146739130434\n",
"76800: acc=0.707109375\n",
"80000: acc=0.714075\n",
"83200: acc=0.7209134615384616\n",
"86400: acc=0.727037037037037\n",
"89600: acc=0.7326674107142858\n",
"92800: acc=0.7379633620689655\n",
"96000: acc=0.7433645833333333\n",
"99200: acc=0.7479032258064516\n",
"102400: acc=0.752119140625\n",
"105600: acc=0.7562405303030303\n",
"108800: acc=0.76015625\n",
"112000: acc=0.7641339285714286\n",
"115200: acc=0.7677777777777778\n",
"118400: acc=0.7711233108108108\n"
]
},
{
"data": {
"text/plain": [
"(0.03487814127604167, 0.7728)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch(net,train_loader, lr=0.001)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sequências compactadas\n",
"\n",
"No nosso exemplo, tivemos de preencher todas as sequências no minibatch com vetores de zeros. Embora isso resulte em algum desperdício de memória, com RNNs é ainda mais crítico o facto de células adicionais da RNN serem criadas para os itens de entrada preenchidos, que participam no treino, mas não transportam nenhuma informação de entrada relevante. Seria muito melhor treinar a RNN apenas com o tamanho real da sequência.\n",
"\n",
"Para isso, um formato especial de armazenamento de sequências preenchidas foi introduzido no PyTorch. Suponha que temos um minibatch preenchido de entrada que se parece com isto:\n",
"```\n",
"[[1,2,3,4,5],\n",
" [6,7,8,0,0],\n",
" [9,0,0,0,0]]\n",
"```\n",
"Aqui, 0 representa os valores preenchidos, e o vetor de comprimentos reais das sequências de entrada é `[5,3,1]`.\n",
"\n",
"Para treinar eficazmente a RNN com sequências preenchidas, queremos começar o treino do primeiro grupo de células da RNN com um minibatch grande (`[1,6,9]`), mas depois terminar o processamento da terceira sequência e continuar o treino com minibatches mais curtos (`[2,7]`, `[3,8]`), e assim por diante. Assim, a sequência compactada é representada como um único vetor - no nosso caso `[1,6,9,2,7,3,8,4,5]`, e um vetor de comprimentos (`[5,3,1]`), a partir do qual podemos facilmente reconstruir o minibatch preenchido original.\n",
"\n",
"Para produzir uma sequência compactada, podemos usar a função `torch.nn.utils.rnn.pack_padded_sequence`. Todas as camadas recorrentes, incluindo RNN, LSTM e GRU, suportam sequências compactadas como entrada e produzem uma saída compactada, que pode ser decodificada usando `torch.nn.utils.rnn.pad_packed_sequence`.\n",
"\n",
"Para conseguir produzir uma sequência compactada, precisamos passar o vetor de comprimentos para a rede, e, assim, precisamos de uma função diferente para preparar os minibatches:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def pad_length(b):\n",
" # build vectorized sequence\n",
" v = [encode(x[1]) for x in b]\n",
" # compute max length of a sequence in this minibatch and length sequence itself\n",
" len_seq = list(map(len,v))\n",
" l = max(len_seq)\n",
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
" torch.tensor(len_seq)\n",
" )\n",
"\n",
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A rede real seria muito semelhante ao `LSTMClassifier` acima, mas a passagem `forward` receberá tanto o minibatch preenchido como o vetor de comprimentos das sequências. Após calcular a incorporação (embedding), calculamos a sequência compactada, passamo-la para a camada LSTM e, em seguida, descompactamos o resultado.\n",
"\n",
"> **Nota**: Na verdade, não utilizamos o resultado descompactado `x`, porque usamos a saída das camadas ocultas nos cálculos seguintes. Assim, podemos remover a descompactação completamente deste código. A razão pela qual a colocamos aqui é para que possa modificar este código facilmente, caso precise usar a saída da rede em cálculos posteriores.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"class LSTMPackClassifier(torch.nn.Module):\n",
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
" super().__init__()\n",
" self.hidden_dim = hidden_dim\n",
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
"\n",
" def forward(self, x, lengths):\n",
" batch_size = x.size(0)\n",
" x = self.embedding(x)\n",
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
" pad_x,(h,c) = self.rnn(pad_x)\n",
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
" return self.fc(h[-1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3200: acc=0.285625\n",
"6400: acc=0.33359375\n",
"9600: acc=0.3876041666666667\n",
"12800: acc=0.44078125\n",
"16000: acc=0.4825\n",
"19200: acc=0.5235416666666667\n",
"22400: acc=0.5559821428571429\n",
"25600: acc=0.58609375\n",
"28800: acc=0.6116666666666667\n",
"32000: acc=0.63340625\n",
"35200: acc=0.6525284090909091\n",
"38400: acc=0.668515625\n",
"41600: acc=0.6822596153846154\n",
"44800: acc=0.6948214285714286\n",
"48000: acc=0.7052708333333333\n",
"51200: acc=0.71521484375\n",
"54400: acc=0.7239889705882353\n",
"57600: acc=0.7315277777777778\n",
"60800: acc=0.7388486842105263\n",
"64000: acc=0.74571875\n",
"67200: acc=0.7518303571428572\n",
"70400: acc=0.7576988636363636\n",
"73600: acc=0.7628940217391305\n",
"76800: acc=0.7681510416666667\n",
"80000: acc=0.7728125\n",
"83200: acc=0.7772235576923077\n",
"86400: acc=0.7815393518518519\n",
"89600: acc=0.7857700892857142\n",
"92800: acc=0.7895043103448276\n",
"96000: acc=0.7930520833333333\n",
"99200: acc=0.7959072580645161\n",
"102400: acc=0.798994140625\n",
"105600: acc=0.802064393939394\n",
"108800: acc=0.8051378676470589\n",
"112000: acc=0.8077857142857143\n",
"115200: acc=0.8104600694444445\n",
"118400: acc=0.8128293918918919\n"
]
},
{
"data": {
"text/plain": [
"(0.029785829671223958, 0.8138166666666666)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota:** Pode ter reparado no parâmetro `use_pack_sequence` que passamos para a função de treino. Atualmente, a função `pack_padded_sequence` requer que o tensor de sequência de comprimentos esteja no dispositivo CPU, e, portanto, a função de treino precisa evitar mover os dados da sequência de comprimentos para a GPU durante o treino. Pode consultar a implementação da função `train_emb` no ficheiro [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNNs bidirecionais e multicamadas\n",
"\n",
"Nos nossos exemplos, todas as redes recorrentes operaram numa única direção, do início de uma sequência até ao fim. Isto parece natural, pois assemelha-se à forma como lemos e ouvimos fala. No entanto, como em muitos casos práticos temos acesso aleatório à sequência de entrada, pode fazer sentido executar o cálculo recorrente em ambas as direções. Estas redes são chamadas de **RNNs bidirecionais**, e podem ser criadas passando o parâmetro `bidirectional=True` ao construtor de RNN/LSTM/GRU.\n",
"\n",
"Ao trabalhar com uma rede bidirecional, precisaremos de dois vetores de estado oculto, um para cada direção. O PyTorch codifica esses vetores como um único vetor com o dobro do tamanho, o que é bastante conveniente, pois normalmente passaríamos o estado oculto resultante para uma camada linear totalmente conectada, e apenas precisaríamos levar este aumento de tamanho em consideração ao criar a camada.\n",
"\n",
"Uma rede recorrente, seja unidirecional ou bidirecional, captura certos padrões dentro de uma sequência e pode armazená-los no vetor de estado ou passá-los para a saída. Assim como nas redes convolucionais, podemos construir outra camada recorrente sobre a primeira para capturar padrões de nível superior, construídos a partir dos padrões de baixo nível extraídos pela primeira camada. Isto leva-nos ao conceito de **RNN multicamada**, que consiste em duas ou mais redes recorrentes, onde a saída da camada anterior é passada como entrada para a próxima camada.\n",
"\n",
"![Imagem mostrando uma RNN de memória de longo e curto prazo multicamada](../../../../../lessons/5-NLP/16-RNN/images/multi-layer-lstm.jpg)\n",
"\n",
"*Imagem retirada [deste excelente artigo](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) de Fernando López*\n",
"\n",
"O PyTorch torna a construção de tais redes uma tarefa simples, pois basta passar o parâmetro `num_layers` ao construtor de RNN/LSTM/GRU para criar automaticamente várias camadas de recorrência. Isto também significa que o tamanho do vetor de estado/oculto aumentará proporcionalmente, e será necessário levar isso em consideração ao lidar com a saída das camadas recorrentes.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNNs para outras tarefas\n",
"\n",
"Nesta unidade, vimos que as RNNs podem ser usadas para classificação de sequências, mas, na verdade, elas podem lidar com muitas outras tarefas, como geração de texto, tradução automática e muito mais. Vamos abordar essas tarefas na próxima unidade.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "522ee52ae3d5ae933e283286254e9a55",
"translation_date": "2025-08-31T12:01:06+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,460 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Redes neuronais recorrentes\n",
"\n",
"No módulo anterior, abordámos representações semânticas ricas de texto. A arquitetura que temos utilizado captura o significado agregado das palavras numa frase, mas não considera a **ordem** das palavras, porque a operação de agregação que segue as embeddings elimina esta informação do texto original. Como estes modelos não conseguem representar a ordem das palavras, não conseguem resolver tarefas mais complexas ou ambíguas, como geração de texto ou resposta a perguntas.\n",
"\n",
"Para capturar o significado de uma sequência de texto, utilizaremos uma arquitetura de rede neural chamada **rede neural recorrente**, ou RNN. Ao usar uma RNN, passamos a nossa frase pela rede, um token de cada vez, e a rede produz um **estado**, que depois passamos novamente para a rede juntamente com o próximo token.\n",
"\n",
"![Imagem que mostra um exemplo de geração de rede neural recorrente.](../../../../../lessons/5-NLP/16-RNN/images/rnn.png)\n",
"\n",
"Dada a sequência de entrada de tokens $X_0,\\dots,X_n$, a RNN cria uma sequência de blocos de rede neural e treina esta sequência de ponta a ponta utilizando retropropagação. Cada bloco de rede recebe um par $(X_i,S_i)$ como entrada e produz $S_{i+1}$ como resultado. O estado final $S_n$ ou a saída $Y_n$ é enviado para um classificador linear para produzir o resultado. Todos os blocos da rede partilham os mesmos pesos e são treinados de ponta a ponta utilizando uma única passagem de retropropagação.\n",
"\n",
"> A figura acima mostra uma rede neural recorrente na forma expandida (à esquerda) e numa representação recorrente mais compacta (à direita). É importante perceber que todas as células RNN têm os mesmos **pesos partilháveis**.\n",
"\n",
"Como os vetores de estado $S_0,\\dots,S_n$ são passados pela rede, a RNN é capaz de aprender dependências sequenciais entre palavras. Por exemplo, quando a palavra *não* aparece em algum lugar da sequência, a rede pode aprender a negar certos elementos dentro do vetor de estado.\n",
"\n",
"Internamente, cada célula RNN contém duas matrizes de pesos: $W_H$ e $W_I$, e um bias $b$. Em cada passo da RNN, dado o input $X_i$ e o estado de entrada $S_i$, o estado de saída é calculado como $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, onde $f$ é uma função de ativação (frequentemente $\\tanh$).\n",
"\n",
"> Para problemas como geração de texto (que abordaremos na próxima unidade) ou tradução automática, também queremos obter um valor de saída em cada passo da RNN. Neste caso, existe também outra matriz $W_O$, e a saída é calculada como $Y_i=f(W_O\\times S_i+b_O)$.\n",
"\n",
"Vamos ver como as redes neuronais recorrentes podem ajudar-nos a classificar o nosso conjunto de dados de notícias.\n",
"\n",
"> Para o ambiente sandbox, precisamos de executar a seguinte célula para garantir que a biblioteca necessária está instalada e os dados estão pré-carregados. Se estiver a trabalhar localmente, pode ignorar a célula seguinte.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import tensorflow_datasets as tfds\n",
"import numpy as np\n",
"\n",
"# We are going to be training pretty large models. In order not to face errors, we need\n",
"# to set tensorflow option to grow GPU memory allocation when required\n",
"physical_devices = tf.config.list_physical_devices('GPU') \n",
"if len(physical_devices)>0:\n",
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
"\n",
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
]
},
{
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"Ao treinar modelos grandes, a alocação de memória da GPU pode tornar-se um problema. Também pode ser necessário experimentar diferentes tamanhos de minibatch, de forma a que os dados caibam na memória da GPU, mas que o treino seja suficientemente rápido. Se estiver a executar este código na sua própria máquina com GPU, pode experimentar ajustar o tamanho do minibatch para acelerar o treino.\n",
"\n",
"> **Nota**: Certas versões dos drivers da NVidia são conhecidas por não libertarem a memória após o treino do modelo. Estamos a executar vários exemplos neste notebook, e isso pode levar ao esgotamento da memória em determinadas configurações, especialmente se estiver a realizar as suas próprias experiências no mesmo notebook. Se encontrar erros estranhos ao iniciar o treino do modelo, pode ser necessário reiniciar o kernel do notebook.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true,
"jupyter": {
"outputs_hidden": false,
"source_hidden": false
},
"nteract": {
"transient": {
"deleting": false
}
}
},
"outputs": [],
"source": [
"batch_size = 16\n",
"embed_size = 64"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Classificador RNN Simples\n",
"\n",
"No caso de uma RNN simples, cada unidade recorrente é uma rede linear simples, que recebe um vetor de entrada e um vetor de estado, e produz um novo vetor de estado. No Keras, isto pode ser representado pela camada `SimpleRNN`.\n",
"\n",
"Embora possamos passar diretamente tokens codificados em one-hot para a camada RNN, isto não é uma boa ideia devido à sua alta dimensionalidade. Por isso, utilizaremos uma camada de embedding para reduzir a dimensionalidade dos vetores de palavras, seguida por uma camada RNN e, por fim, um classificador `Dense`.\n",
"\n",
"> **Nota**: Em casos onde a dimensionalidade não é tão alta, por exemplo, ao usar tokenização a nível de caracteres, pode fazer sentido passar tokens codificados em one-hot diretamente para a célula RNN.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"text_vectorization (TextVect (None, None) 0 \n",
"_________________________________________________________________\n",
"embedding (Embedding) (None, None, 64) 1280000 \n",
"_________________________________________________________________\n",
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
"_________________________________________________________________\n",
"dense (Dense) (None, 4) 68 \n",
"=================================================================\n",
"Total params: 1,281,364\n",
"Trainable params: 1,281,364\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"vocab_size = 20000\n",
"\n",
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
" max_tokens=vocab_size,\n",
" input_shape=(1,))\n",
"\n",
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, embed_size),\n",
" keras.layers.SimpleRNN(16),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota:** Aqui usamos uma camada de incorporação não treinada para simplificar, mas para obter melhores resultados podemos usar uma camada de incorporação pré-treinada utilizando Word2Vec, conforme descrito na unidade anterior. Seria um bom exercício para si adaptar este código para funcionar com incorporações pré-treinadas.\n",
"\n",
"Agora vamos treinar a nossa RNN. As RNNs, de forma geral, são bastante difíceis de treinar, porque, uma vez que as células da RNN são desenroladas ao longo do comprimento da sequência, o número resultante de camadas envolvidas na retropropagação é bastante grande. Por isso, precisamos selecionar uma taxa de aprendizagem menor e treinar a rede num conjunto de dados maior para obter bons resultados. Isto pode demorar bastante tempo, por isso é preferível usar uma GPU.\n",
"\n",
"Para acelerar o processo, vamos treinar o modelo RNN apenas nos títulos das notícias, omitindo a descrição. Pode tentar treinar com a descrição e ver se consegue fazer o modelo treinar.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training vectorizer\n"
]
}
],
"source": [
"def extract_title(x):\n",
" return x['title']\n",
"\n",
"def tupelize_title(x):\n",
" return (extract_title(x),x['label'])\n",
"\n",
"print('Training vectorizer')\n",
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"> **Nota** que a precisão é provavelmente menor aqui, porque estamos a treinar apenas com títulos de notícias.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Revisitar sequências de variáveis\n",
"\n",
"Lembre-se de que a camada `TextVectorization` irá automaticamente preencher sequências de comprimento variável em um minibatch com tokens de preenchimento (pad tokens). Acontece que esses tokens também participam do treino, e isso pode complicar a convergência do modelo.\n",
"\n",
"Existem várias abordagens que podemos adotar para minimizar a quantidade de preenchimento. Uma delas é reorganizar o conjunto de dados pelo comprimento das sequências e agrupar todas as sequências por tamanho. Isso pode ser feito utilizando a função `tf.data.experimental.bucket_by_sequence_length` (consulte a [documentação](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
"\n",
"Outra abordagem é usar **masking**. No Keras, algumas camadas suportam entradas adicionais que indicam quais tokens devem ser considerados durante o treino. Para incorporar masking no nosso modelo, podemos incluir uma camada `Masking` separada ([documentação](https://keras.io/api/layers/core_layers/masking/)) ou especificar o parâmetro `mask_zero=True` na nossa camada `Embedding`.\n",
"\n",
"> **Note**: Este treino levará cerca de 5 minutos para completar uma época em todo o conjunto de dados. Sinta-se à vontade para interromper o treino a qualquer momento se perder a paciência. Outra opção é limitar a quantidade de dados usados para o treino, adicionando a cláusula `.take(...)` após os conjuntos de dados `ds_train` e `ds_test`.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def extract_text(x):\n",
" return x['title']+' '+x['description']\n",
"\n",
"def tupelize(x):\n",
" return (extract_text(x),x['label'])\n",
"\n",
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
" keras.layers.SimpleRNN(16),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora que estamos a usar mascaramento, podemos treinar o modelo em todo o conjunto de dados de títulos e descrições.\n",
"\n",
"> **Note**: Reparaste que temos estado a usar um vetorizador treinado nos títulos das notícias, e não no corpo completo do artigo? Potencialmente, isto pode fazer com que alguns dos tokens sejam ignorados, por isso é melhor re-treinar o vetorizador. No entanto, isto pode ter apenas um efeito muito pequeno, por isso vamos continuar a usar o vetorizador pré-treinado anterior para simplificar.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## LSTM: Memória de longo curto prazo\n",
"\n",
"Um dos principais problemas das RNNs é o **desvanecimento de gradientes**. As RNNs podem ser bastante longas e podem ter dificuldade em propagar os gradientes até à primeira camada da rede durante a retropropagação. Quando isso acontece, a rede não consegue aprender relações entre tokens distantes. Uma forma de evitar este problema é introduzir uma **gestão explícita de estados** utilizando **portas**. As duas arquiteturas mais comuns que introduzem portas são a **memória de longo curto prazo** (LSTM) e a **unidade de retransmissão com portas** (GRU). Vamos abordar as LSTMs aqui.\n",
"\n",
"![Imagem mostrando um exemplo de célula de memória de longo curto prazo](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"Uma rede LSTM é organizada de forma semelhante a uma RNN, mas existem dois estados que são passados de camada para camada: o estado real $c$ e o vetor oculto $h$. Em cada unidade, o vetor oculto $h_{t-1}$ é combinado com a entrada $x_t$, e juntos controlam o que acontece ao estado $c_t$ e à saída $h_{t}$ através de **portas**. Cada porta tem uma ativação sigmoide (saída no intervalo $[0,1]$), que pode ser vista como uma máscara bit a bit quando multiplicada pelo vetor de estado. As LSTMs possuem as seguintes portas (da esquerda para a direita na imagem acima):\n",
"* **porta de esquecimento**, que determina quais componentes do vetor $c_{t-1}$ precisamos esquecer e quais devemos manter.\n",
"* **porta de entrada**, que determina quanta informação do vetor de entrada e do vetor oculto anterior deve ser incorporada no vetor de estado.\n",
"* **porta de saída**, que pega o novo vetor de estado e decide quais dos seus componentes serão usados para produzir o novo vetor oculto $h_t$.\n",
"\n",
"Os componentes do estado $c$ podem ser vistos como indicadores que podem ser ativados ou desativados. Por exemplo, quando encontramos o nome *Alice* na sequência, presumimos que se refere a uma mulher e ativamos o indicador no estado que diz que temos um substantivo feminino na frase. Quando mais tarde encontramos as palavras *e Tom*, ativamos o indicador que diz que temos um substantivo no plural. Assim, ao manipular o estado, podemos acompanhar as propriedades gramaticais da frase.\n",
"\n",
"> **Nota**: Aqui está um excelente recurso para compreender os detalhes internos das LSTMs: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) de Christopher Olah.\n",
"\n",
"Embora a estrutura interna de uma célula LSTM possa parecer complexa, o Keras esconde esta implementação dentro da camada `LSTM`, por isso a única coisa que precisamos fazer no exemplo acima é substituir a camada recorrente:\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, embed_size),\n",
" keras.layers.LSTM(8),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNNs Bidirecionais e Multicamadas\n",
"\n",
"Nos nossos exemplos até agora, as redes recorrentes operam do início de uma sequência até ao fim. Isto parece natural para nós porque segue a mesma direção em que lemos ou ouvimos uma fala. No entanto, para cenários que requerem acesso aleatório à sequência de entrada, faz mais sentido executar o cálculo recorrente em ambas as direções. As RNNs que permitem cálculos em ambas as direções são chamadas de RNNs **bidirecionais**, e podem ser criadas ao envolver a camada recorrente com uma camada especial chamada `Bidirectional`.\n",
"\n",
"> **Note**: A camada `Bidirectional` cria duas cópias da camada dentro dela e define a propriedade `go_backwards` de uma dessas cópias como `True`, fazendo com que ela percorra a sequência na direção oposta.\n",
"\n",
"As redes recorrentes, sejam unidirecionais ou bidirecionais, capturam padrões dentro de uma sequência e armazenam-nos em vetores de estado ou retornam-nos como saída. Tal como nas redes convolucionais, podemos construir outra camada recorrente após a primeira para capturar padrões de nível superior, construídos a partir de padrões de nível inferior extraídos pela primeira camada. Isto leva-nos ao conceito de uma **RNN multicamada**, que consiste em duas ou mais redes recorrentes, onde a saída da camada anterior é passada como entrada para a próxima camada.\n",
"\n",
"![Imagem mostrando uma RNN de memória de longo curto prazo multicamada](../../../../../lessons/5-NLP/16-RNN/images/multi-layer-lstm.jpg)\n",
"\n",
"*Imagem retirada [deste excelente artigo](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) de Fernando López.*\n",
"\n",
"O Keras torna a construção destas redes uma tarefa simples, porque basta adicionar mais camadas recorrentes ao modelo. Para todas as camadas, exceto a última, precisamos especificar o parâmetro `return_sequences=True`, porque precisamos que a camada retorne todos os estados intermédios, e não apenas o estado final do cálculo recorrente.\n",
"\n",
"Vamos construir uma LSTM bidirecional de duas camadas para o nosso problema de classificação.\n",
"\n",
"> **Note** este código, novamente, demora bastante tempo a ser executado, mas oferece-nos a maior precisão que vimos até agora. Por isso, talvez valha a pena esperar e ver o resultado.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
]
}
],
"source": [
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
" validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNNs para outras tarefas\n",
"\n",
"Até agora, concentrámo-nos em usar RNNs para classificar sequências de texto. No entanto, elas podem lidar com muitas outras tarefas, como geração de texto e tradução automática — abordaremos essas tarefas na próxima unidade.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante ter em conta que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"kernel_info": {
"name": "conda-env-py37_tensorflow-py"
},
"kernelspec": {
"display_name": "py37_tensorflow",
"language": "python",
"name": "conda-env-py37_tensorflow-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.9"
},
"nteract": {
"version": "nteract-front-end@1.0.0"
},
"coopTranslator": {
"original_hash": "81351e61f619b432ff51010a4f993194",
"translation_date": "2025-08-31T12:00:24+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,6 @@
# Tarefa: Notebooks
Utilizando os notebooks associados a esta lição (seja a versão em PyTorch ou TensorFlow), execute-os novamente usando o seu próprio conjunto de dados, talvez um do Kaggle, utilizado com a devida atribuição. Reescreva o notebook para destacar as suas próprias conclusões. Experimente um tipo diferente de conjunto de dados e documente as suas descobertas, utilizando texto como [este conjunto de dados de uma competição do Kaggle sobre tweets relacionados ao clima](https://www.kaggle.com/competitions/crowdflower-weather-twitter/data?select=train.csv).
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos pela precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,414 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Redes Generativas\n",
"\n",
"As Redes Neuronais Recorrentes (RNNs) e suas variantes com células controladas, como as Células de Memória de Longo Prazo (LSTMs) e as Unidades Recorrentes Controladas (GRUs), proporcionaram um mecanismo para modelagem de linguagem, ou seja, elas podem aprender a ordem das palavras e fornecer previsões para a próxima palavra em uma sequência. Isso permite que utilizemos RNNs para **tarefas generativas**, como geração de texto comum, tradução automática e até mesmo legendagem de imagens.\n",
"\n",
"Na arquitetura de RNN que discutimos na unidade anterior, cada unidade RNN produzia o próximo estado oculto como saída. No entanto, também podemos adicionar outra saída a cada unidade recorrente, o que nos permitiria gerar uma **sequência** (que tem o mesmo comprimento da sequência original). Além disso, podemos usar unidades RNN que não aceitam uma entrada em cada passo, mas apenas recebem um vetor de estado inicial e, em seguida, produzem uma sequência de saídas.\n",
"\n",
"Neste notebook, vamos focar em modelos generativos simples que nos ajudam a gerar texto. Para simplificar, vamos construir uma **rede ao nível de caracteres**, que gera texto letra por letra. Durante o treino, precisamos pegar um corpus de texto e dividi-lo em sequências de letras.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"import numpy as np\n",
"from torchnlp import *\n",
"train_dataset,test_dataset,classes,vocab = load_dataset()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Construir um vocabulário de caracteres\n",
"\n",
"Para criar uma rede generativa a nível de caracteres, é necessário dividir o texto em caracteres individuais em vez de palavras. Isto pode ser feito ao definir um tokenizador diferente:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Vocabulary size = 82\n",
"Encoding of 'a' is 1\n",
"Character with code 13 is c\n"
]
}
],
"source": [
"def char_tokenizer(words):\n",
" return list(words) #[word for word in words]\n",
"\n",
"counter = collections.Counter()\n",
"for (label, line) in train_dataset:\n",
" counter.update(char_tokenizer(line))\n",
"vocab = torchtext.vocab.vocab(counter)\n",
"\n",
"vocab_size = len(vocab)\n",
"print(f\"Vocabulary size = {vocab_size}\")\n",
"print(f\"Encoding of 'a' is {vocab.get_stoi()['a']}\")\n",
"print(f\"Character with code 13 is {vocab.get_itos()[13]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos ver o exemplo de como podemos codificar o texto do nosso conjunto de dados:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"tensor([ 0, 1, 2, 2, 3, 4, 5, 6, 3, 7, 8, 1, 9, 10, 3, 11, 2, 1,\n",
" 12, 3, 7, 1, 13, 14, 3, 15, 16, 5, 17, 3, 5, 18, 8, 3, 7, 2,\n",
" 1, 13, 14, 3, 19, 20, 8, 21, 5, 8, 9, 10, 22, 3, 20, 8, 21, 5,\n",
" 8, 9, 10, 3, 23, 3, 4, 18, 17, 9, 5, 23, 10, 8, 2, 2, 8, 9,\n",
" 10, 24, 3, 0, 1, 2, 2, 3, 4, 5, 9, 8, 8, 5, 25, 10, 3, 26,\n",
" 12, 27, 16, 26, 2, 27, 16, 28, 29, 30, 1, 16, 26, 3, 17, 31, 3, 21,\n",
" 2, 5, 9, 1, 23, 13, 32, 16, 27, 13, 10, 24, 3, 1, 9, 8, 3, 10,\n",
" 8, 8, 27, 16, 28, 3, 28, 9, 8, 8, 16, 3, 1, 28, 1, 27, 16, 6])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def enc(x):\n",
" return torch.LongTensor(encode(x,voc=vocab,tokenizer=char_tokenizer))\n",
"\n",
"enc(train_dataset[0][1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Treinar uma RNN generativa\n",
"\n",
"A forma como iremos treinar a RNN para gerar texto é a seguinte. A cada passo, iremos pegar uma sequência de caracteres com comprimento `nchars` e pedir à rede que gere o próximo caractere de saída para cada caractere de entrada:\n",
"\n",
"![Imagem mostrando um exemplo de geração de RNN com a palavra 'HELLO'.](../../../../../lessons/5-NLP/17-GenerativeNetworks/images/rnn-generate.png)\n",
"\n",
"Dependendo do cenário específico, também podemos querer incluir alguns caracteres especiais, como *fim de sequência* `<eos>`. No nosso caso, queremos apenas treinar a rede para geração contínua de texto, portanto, iremos fixar o tamanho de cada sequência para ser igual a `nchars` tokens. Consequentemente, cada exemplo de treino consistirá em `nchars` entradas e `nchars` saídas (que são a sequência de entrada deslocada um símbolo para a esquerda). O minibatch consistirá em várias dessas sequências.\n",
"\n",
"A forma como iremos gerar os minibatches será pegar cada texto de notícias com comprimento `l` e gerar todas as combinações possíveis de entrada-saída a partir dele (haverá `l-nchars` dessas combinações). Elas formarão um minibatch, e o tamanho dos minibatches será diferente em cada passo de treino.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(tensor([[ 0, 1, 2, ..., 28, 29, 30],\n",
" [ 1, 2, 2, ..., 29, 30, 1],\n",
" [ 2, 2, 3, ..., 30, 1, 16],\n",
" ...,\n",
" [20, 8, 21, ..., 1, 28, 1],\n",
" [ 8, 21, 5, ..., 28, 1, 27],\n",
" [21, 5, 8, ..., 1, 27, 16]]),\n",
" tensor([[ 1, 2, 2, ..., 29, 30, 1],\n",
" [ 2, 2, 3, ..., 30, 1, 16],\n",
" [ 2, 3, 4, ..., 1, 16, 26],\n",
" ...,\n",
" [ 8, 21, 5, ..., 28, 1, 27],\n",
" [21, 5, 8, ..., 1, 27, 16],\n",
" [ 5, 8, 9, ..., 27, 16, 6]]))"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nchars = 100\n",
"\n",
"def get_batch(s,nchars=nchars):\n",
" ins = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
" outs = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
" for i in range(len(s)-nchars):\n",
" ins[i] = enc(s[i:i+nchars])\n",
" outs[i] = enc(s[i+1:i+nchars+1])\n",
" return ins,outs\n",
"\n",
"get_batch(train_dataset[0][1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos definir a rede geradora. Pode ser baseada em qualquer célula recorrente que discutimos na unidade anterior (simples, LSTM ou GRU). No nosso exemplo, utilizaremos LSTM.\n",
"\n",
"Como a rede recebe caracteres como entrada e o tamanho do vocabulário é relativamente pequeno, não precisamos de uma camada de embedding; a entrada codificada em one-hot pode ir diretamente para a célula LSTM. No entanto, como passamos números de caracteres como entrada, é necessário codificá-los em one-hot antes de enviá-los para o LSTM. Isto é feito ao chamar a função `one_hot` durante a passagem `forward`. O codificador de saída será uma camada linear que converterá o estado oculto em uma saída codificada em one-hot.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"class LSTMGenerator(torch.nn.Module):\n",
" def __init__(self, vocab_size, hidden_dim):\n",
" super().__init__()\n",
" self.rnn = torch.nn.LSTM(vocab_size,hidden_dim,batch_first=True)\n",
" self.fc = torch.nn.Linear(hidden_dim, vocab_size)\n",
"\n",
" def forward(self, x, s=None):\n",
" x = torch.nn.functional.one_hot(x,vocab_size).to(torch.float32)\n",
" x,s = self.rnn(x,s)\n",
" return self.fc(x),s"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Durante o treino, queremos ser capazes de amostrar texto gerado. Para isso, iremos definir a função `generate`, que produzirá uma string de saída com comprimento `size`, começando pela string inicial `start`.\n",
"\n",
"O funcionamento é o seguinte. Primeiro, passamos a string inicial completa pela rede e obtemos o estado de saída `s` e o próximo carácter previsto `out`. Como `out` está codificado em one-hot, utilizamos `argmax` para obter o índice do carácter `nc` no vocabulário e usamos `itos` para identificar o carácter real e adicioná-lo à lista resultante de caracteres `chars`. Este processo de gerar um carácter é repetido `size` vezes para gerar o número necessário de caracteres.\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def generate(net,size=100,start='today '):\n",
" chars = list(start)\n",
" out, s = net(enc(chars).view(1,-1).to(device))\n",
" for i in range(size):\n",
" nc = torch.argmax(out[0][-1])\n",
" chars.append(vocab.get_itos()[nc])\n",
" out, s = net(nc.view(1,-1),s)\n",
" return ''.join(chars)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos começar o treino! O ciclo de treino é quase o mesmo que em todos os nossos exemplos anteriores, mas, em vez de precisão, imprimimos texto gerado amostrado a cada 1000 épocas.\n",
"\n",
"É necessário prestar atenção especial à forma como calculamos a perda. Precisamos calcular a perda com base na saída codificada em one-hot `out` e no texto esperado `text_out`, que é a lista de índices de caracteres. Felizmente, a função `cross_entropy` espera como primeiro argumento a saída não normalizada da rede e, como segundo argumento, o número da classe, que é exatamente o que temos. Além disso, realiza automaticamente a média em relação ao tamanho do minibatch.\n",
"\n",
"Também limitamos o treino por amostras definidas em `samples_to_train`, para não esperar demasiado tempo. Incentivamos-te a experimentar e tentar treinos mais longos, possivelmente por várias épocas (nesse caso, seria necessário criar outro ciclo em torno deste código).\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Current loss = 4.398899078369141\n",
"today sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr s\n",
"Current loss = 2.161320447921753\n",
"today and to the tor to to the tor to to the tor to to the tor to to the tor to to the tor to to the tor t\n",
"Current loss = 1.6722588539123535\n",
"today and the court to the could to the could to the could to the could to the could to the could to the c\n",
"Current loss = 2.423795223236084\n",
"today and a second to the conternation of the conternation of the conternation of the conternation of the \n",
"Current loss = 1.702607274055481\n",
"today and the company to the company to the company to the company to the company to the company to the co\n",
"Current loss = 1.692358136177063\n",
"today and the company to the company to the company to the company to the company to the company to the co\n",
"Current loss = 1.9722288846969604\n",
"today and the control the control the control the control the control the control the control the control \n",
"Current loss = 1.8705692291259766\n",
"today and the second to the second to the second to the second to the second to the second to the second t\n",
"Current loss = 1.7626899480819702\n",
"today and a security and a security and a security and a security and a security and a security and a secu\n",
"Current loss = 1.5574463605880737\n",
"today and the company and the company and the company and the company and the company and the company and \n",
"Current loss = 1.5620026588439941\n",
"today and the be that the be the be that the be the be that the be the be that the be the be that the be t\n"
]
}
],
"source": [
"net = LSTMGenerator(vocab_size,64).to(device)\n",
"\n",
"samples_to_train = 10000\n",
"optimizer = torch.optim.Adam(net.parameters(),0.01)\n",
"loss_fn = torch.nn.CrossEntropyLoss()\n",
"net.train()\n",
"for i,x in enumerate(train_dataset):\n",
" # x[0] is class label, x[1] is text\n",
" if len(x[1])-nchars<10:\n",
" continue\n",
" samples_to_train-=1\n",
" if not samples_to_train: break\n",
" text_in, text_out = get_batch(x[1])\n",
" optimizer.zero_grad()\n",
" out,s = net(text_in)\n",
" loss = torch.nn.functional.cross_entropy(out.view(-1,vocab_size),text_out.flatten()) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" if i%1000==0:\n",
" print(f\"Current loss = {loss.item()}\")\n",
" print(generate(net))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este exemplo já gera um texto bastante bom, mas pode ser melhorado de várias formas:\n",
"\n",
"* **Melhor geração de minibatches**. A forma como preparamos os dados para o treino foi gerar um minibatch a partir de uma única amostra. Isto não é ideal, porque os minibatches têm tamanhos diferentes e alguns deles nem sequer podem ser gerados, porque o texto é menor do que `nchars`. Além disso, minibatches pequenos não utilizam suficientemente a GPU. Seria mais sensato obter um grande bloco de texto de todas as amostras, depois gerar todos os pares de entrada-saída, embaralhá-los e criar minibatches de tamanho igual.\n",
"\n",
"* **LSTM multicamada**. Faz sentido experimentar 2 ou 3 camadas de células LSTM. Como mencionámos na unidade anterior, cada camada de LSTM extrai certos padrões do texto e, no caso de um gerador a nível de caracteres, podemos esperar que os níveis inferiores de LSTM sejam responsáveis por extrair sílabas, e os níveis superiores por palavras e combinações de palavras. Isto pode ser implementado facilmente passando o parâmetro número-de-camadas ao construtor de LSTM.\n",
"\n",
"* Também pode querer experimentar com **unidades GRU** e ver quais apresentam melhor desempenho, bem como com **diferentes tamanhos de camadas ocultas**. Uma camada oculta demasiado grande pode resultar em overfitting (por exemplo, a rede aprenderá o texto exato), enquanto um tamanho menor pode não produzir bons resultados.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Geração de texto suave e temperatura\n",
"\n",
"Na definição anterior de `generate`, estávamos sempre a escolher o carácter com a maior probabilidade como o próximo carácter no texto gerado. Isto resultava no facto de o texto frequentemente \"ciclar\" entre as mesmas sequências de caracteres repetidamente, como neste exemplo: \n",
"```\n",
"today of the second the company and a second the company ...\n",
"```\n",
"\n",
"No entanto, se analisarmos a distribuição de probabilidades para o próximo carácter, pode acontecer que a diferença entre algumas das probabilidades mais altas não seja muito grande, por exemplo, um carácter pode ter uma probabilidade de 0,2, outro de 0,19, etc. Por exemplo, ao procurar o próximo carácter na sequência '*play*', o próximo carácter pode ser igualmente um espaço ou **e** (como na palavra *player*).\n",
"\n",
"Isto leva-nos à conclusão de que nem sempre é \"justo\" selecionar o carácter com maior probabilidade, porque escolher o segundo mais provável ainda pode levar-nos a um texto significativo. É mais sensato **amostrar** caracteres a partir da distribuição de probabilidades fornecida pela saída da rede.\n",
"\n",
"Esta amostragem pode ser feita utilizando a função `multinomial`, que implementa a chamada **distribuição multinomial**. Uma função que implementa esta geração de texto **suave** está definida abaixo:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--- Temperature = 0.3\n",
"Today and a company and complete an all the land the restrational the as a security and has provers the pay to and a report and the computer in the stand has filities and working the law the stations for a company and with the company and the final the first company and refight of the state and and workin\n",
"\n",
"--- Temperature = 0.8\n",
"Today he oniis its first to Aus bomblaties the marmation a to manan boogot that pirate assaid a relaid their that goverfin the the Cappets Ecrotional Assonia Cition targets it annight the w scyments Blamity #39;s TVeer Diercheg Reserals fran envyuil that of ster said access what succers of Dour-provelith\n",
"\n",
"--- Temperature = 1.0\n",
"Today holy they a 11 will meda a toket subsuaties, engins for Chanos, they's has stainger past to opening orital his thempting new Nattona was al innerforder advan-than #36;s night year his religuled talitatian what the but with Wednesday to Justment will wemen of Mark CCC Camp as Timed Nae wome a leaders\n",
"\n",
"--- Temperature = 1.3\n",
"Today gpone 2.5 fech atcusion poor cocles toparsdorM.cht Line Pamage put 43 his calt lowed to the book, that has authh-the silia rruch ailing to'ory andhes beutirsimi- Aefffive heading offil an auf eacklets is charged evis, Gunymy oy) Mony has it after-sloythyor loveId out filme, the Natabl -Najuntaxiggs \n",
"\n",
"--- Temperature = 1.8\n",
"Today plary, P.slan chly\\401 mardregationly #39;t 8.1Mide) closes ,filtcon alfly playin roven!\\grea.-QFBEP: Iss onfarchQ/itilia CCf Zivesigntwasta orce.-Peul-aw.uicrin of fuglinfsut aftaningwo, MIEX awayew Aice Woiduar Corvagiugge oppo esig ThusBratourid canthly-RyI.co lagitems\\eexciaishes.conBabntusmor I\n",
"\n"
]
}
],
"source": [
"def generate_soft(net,size=100,start='today ',temperature=1.0):\n",
" chars = list(start)\n",
" out, s = net(enc(chars).view(1,-1).to(device))\n",
" for i in range(size):\n",
" #nc = torch.argmax(out[0][-1])\n",
" out_dist = out[0][-1].div(temperature).exp()\n",
" nc = torch.multinomial(out_dist,1)[0]\n",
" chars.append(vocab.get_itos()[nc])\n",
" out, s = net(nc.view(1,-1),s)\n",
" return ''.join(chars)\n",
" \n",
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
" print(f\"--- Temperature = {i}\\n{generate_soft(net,size=300,start='Today ',temperature=i)}\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Introduzimos mais um parâmetro chamado **temperatura**, que é usado para indicar o quão rigorosamente devemos aderir à maior probabilidade. Se a temperatura for 1.0, fazemos uma amostragem multinomial justa, e quando a temperatura vai para infinito - todas as probabilidades tornam-se iguais, e selecionamos aleatoriamente o próximo caractere. No exemplo abaixo, podemos observar que o texto torna-se sem sentido quando aumentamos demasiado a temperatura, e assemelha-se a um texto \"ciclado\" gerado rigidamente quando se aproxima de 0.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "7673cd150d96c74c6d6011460094efb4",
"translation_date": "2025-08-31T11:56:23+00:00",
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,495 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Redes generativas\n",
"\n",
"Redes Neuronais Recorrentes (RNNs) e suas variantes com células controladas, como Células de Memória de Longo e Curto Prazo (LSTMs) e Unidades Recorrentes Controladas (GRUs), proporcionaram um mecanismo para modelagem de linguagem, ou seja, elas podem aprender a ordem das palavras e fornecer previsões para a próxima palavra em uma sequência. Isso permite que utilizemos RNNs para **tarefas generativas**, como geração de texto comum, tradução automática e até mesmo legendagem de imagens.\n",
"\n",
"Na arquitetura de RNN que discutimos na unidade anterior, cada unidade RNN produzia o próximo estado oculto como saída. No entanto, também podemos adicionar outra saída a cada unidade recorrente, o que nos permitiria gerar uma **sequência** (que tem o mesmo comprimento da sequência original). Além disso, podemos usar unidades RNN que não aceitam uma entrada em cada etapa, mas apenas recebem um vetor de estado inicial e, em seguida, produzem uma sequência de saídas.\n",
"\n",
"Neste notebook, vamos focar em modelos generativos simples que nos ajudam a gerar texto. Para simplificar, vamos construir uma **rede ao nível de caracteres**, que gera texto letra por letra. Durante o treino, precisamos pegar um corpus de texto e dividi-lo em sequências de letras.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import tensorflow_datasets as tfds\n",
"import numpy as np\n",
"\n",
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Construir um vocabulário de caracteres\n",
"\n",
"Para construir uma rede generativa a nível de caracteres, precisamos dividir o texto em caracteres individuais em vez de palavras. A camada `TextVectorization` que temos usado anteriormente não consegue fazer isso, por isso temos duas opções:\n",
"\n",
"* Carregar o texto manualmente e fazer a tokenização 'à mão', como neste [exemplo oficial do Keras](https://keras.io/examples/generative/lstm_character_level_text_generation/)\n",
"* Usar a classe `Tokenizer` para a tokenização a nível de caracteres.\n",
"\n",
"Vamos optar pela segunda opção. A `Tokenizer` também pode ser usada para tokenizar em palavras, por isso deve ser possível alternar facilmente entre a tokenização a nível de caracteres e a nível de palavras.\n",
"\n",
"Para realizar a tokenização a nível de caracteres, precisamos passar o parâmetro `char_level=True`:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"def extract_text(x):\n",
" return x['title']+' '+x['description']\n",
"\n",
"def tupelize(x):\n",
" return (extract_text(x),x['label'])\n",
"\n",
"tokenizer = keras.preprocessing.text.Tokenizer(char_level=True,lower=False)\n",
"tokenizer.fit_on_texts([x['title'].numpy().decode('utf-8') for x in ds_train])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Também queremos usar um token especial para indicar **fim de sequência**, que chamaremos de `<eos>`. Vamos adicioná-lo manualmente ao vocabulário:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"eos_token = len(tokenizer.word_index)+1\n",
"tokenizer.word_index['<eos>'] = eos_token\n",
"\n",
"vocab_size = eos_token + 1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[[48, 2, 10, 10, 5, 44, 1, 25, 5, 8, 10, 13, 78]]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer.texts_to_sequences(['Hello, world!'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Treinar uma RNN generativa para criar títulos\n",
"\n",
"A forma como iremos treinar a RNN para gerar títulos de notícias é a seguinte. Em cada passo, iremos pegar um título, que será alimentado numa RNN, e para cada caractere de entrada pediremos à rede que gere o próximo caractere de saída:\n",
"\n",
"![Imagem mostrando um exemplo de geração de RNN da palavra 'HELLO'.](../../../../../lessons/5-NLP/17-GenerativeNetworks/images/rnn-generate.png)\n",
"\n",
"Para o último caractere da nossa sequência, pediremos à rede que gere o token `<eos>`.\n",
"\n",
"A principal diferença da RNN generativa que estamos a usar aqui é que iremos utilizar a saída de cada passo da RNN, e não apenas da célula final. Isto pode ser conseguido ao especificar o parâmetro `return_sequences` na célula da RNN.\n",
"\n",
"Assim, durante o treino, a entrada para a rede será uma sequência de caracteres codificados de um determinado comprimento, e a saída será uma sequência do mesmo comprimento, mas deslocada por um elemento e terminada com `<eos>`. O minibatch consistirá em várias dessas sequências, e será necessário usar **padding** para alinhar todas as sequências.\n",
"\n",
"Vamos criar funções que irão transformar o conjunto de dados para nós. Como queremos adicionar padding às sequências ao nível do minibatch, primeiro agruparemos o conjunto de dados ao chamar `.batch()`, e depois aplicaremos `map` para realizar a transformação. Portanto, a função de transformação irá receber um minibatch inteiro como parâmetro:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def title_batch(x):\n",
" x = [t.numpy().decode('utf-8') for t in x]\n",
" z = tokenizer.texts_to_sequences(x)\n",
" z = tf.keras.preprocessing.sequence.pad_sequences(z)\n",
" return tf.one_hot(z,vocab_size), tf.one_hot(tf.concat([z[:,1:],tf.constant(eos_token,shape=(len(z),1))],axis=1),vocab_size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Algumas coisas importantes que fazemos aqui: \n",
"* Primeiro extraímos o texto real do tensor de strings \n",
"* `text_to_sequences` converte a lista de strings numa lista de tensores inteiros \n",
"* `pad_sequences` preenche esses tensores até ao seu comprimento máximo \n",
"* Finalmente, fazemos a codificação one-hot de todos os caracteres, além de realizar o deslocamento e a adição de `<eos>`. Em breve veremos por que precisamos de caracteres codificados em one-hot \n",
"\n",
"No entanto, esta função é **Pythonic**, ou seja, não pode ser automaticamente traduzida para o grafo computacional do Tensorflow. Iremos obter erros se tentarmos usar esta função diretamente na função `Dataset.map`. Precisamos encapsular esta chamada Pythonic utilizando o wrapper `py_function`: \n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def title_batch_fn(x):\n",
" x = x['title']\n",
" a,b = tf.py_function(title_batch,inp=[x],Tout=(tf.float32,tf.float32))\n",
" return a,b"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Nota**: Diferenciar entre funções de transformação Pythonic e Tensorflow pode parecer um pouco complexo, e pode estar a questionar-se por que não transformamos o conjunto de dados usando funções padrão de Python antes de passá-lo para `fit`. Embora isso definitivamente possa ser feito, usar `Dataset.map` tem uma grande vantagem, porque o pipeline de transformação de dados é executado usando o grafo computacional do Tensorflow, que aproveita os cálculos na GPU e minimiza a necessidade de transferir dados entre CPU/GPU.\n",
"\n",
"Agora podemos construir a nossa rede geradora e começar o treino. Pode ser baseada em qualquer célula recorrente que discutimos na unidade anterior (simples, LSTM ou GRU). No nosso exemplo, usaremos LSTM.\n",
"\n",
"Como a rede recebe caracteres como entrada e o tamanho do vocabulário é relativamente pequeno, não precisamos de uma camada de embedding; a entrada codificada em one-hot pode ir diretamente para a célula LSTM. A camada de saída será um classificador `Dense` que converterá a saída do LSTM em números de tokens codificados em one-hot.\n",
"\n",
"Além disso, como estamos a lidar com sequências de comprimento variável, podemos usar a camada `Masking` para criar uma máscara que ignorará a parte preenchida da string. Isto não é estritamente necessário, porque não estamos muito interessados em tudo o que vai além do token `<eos>`, mas usaremos esta camada para ganhar alguma experiência com este tipo de camada. O `input_shape` será `(None, vocab_size)`, onde `None` indica a sequência de comprimento variável, e o formato de saída será `(None, vocab_size)` também, como pode ver no `summary`:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"masking (Masking) (None, None, 84) 0 \n",
"_________________________________________________________________\n",
"lstm (LSTM) (None, None, 128) 109056 \n",
"_________________________________________________________________\n",
"dense (Dense) (None, None, 84) 10836 \n",
"=================================================================\n",
"Total params: 119,892\n",
"Trainable params: 119,892\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n",
"15000/15000 [==============================] - 229s 15ms/step - loss: 1.5385\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7fa40c1245e0>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" keras.layers.Masking(input_shape=(None,vocab_size)),\n",
" keras.layers.LSTM(128,return_sequences=True),\n",
" keras.layers.Dense(vocab_size,activation='softmax')\n",
"])\n",
"\n",
"model.summary()\n",
"model.compile(loss='categorical_crossentropy')\n",
"\n",
"model.fit(ds_train.batch(8).map(title_batch_fn))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Gerar resultados\n",
"\n",
"Agora que treinámos o modelo, queremos utilizá-lo para gerar alguns resultados. Antes de mais, precisamos de uma forma de decodificar texto representado por uma sequência de números de tokens. Para isso, poderíamos usar a função `tokenizer.sequences_to_texts`; no entanto, esta não funciona bem com tokenização a nível de caracteres. Por isso, vamos pegar num dicionário de tokens do tokenizer (chamado `word_index`), construir um mapa inverso e escrever a nossa própria função de decodificação:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"reverse_map = {val:key for key, val in tokenizer.word_index.items()}\n",
"\n",
"def decode(x):\n",
" return ''.join([reverse_map[t] for t in x])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora, vamos começar a geração. Iniciaremos com uma string `start`, codificá-la-emos numa sequência `inp`, e em cada passo chamaremos a nossa rede para inferir o próximo carácter.\n",
"\n",
"O output da rede `out` é um vetor de elementos `vocab_size` que representam as probabilidades de cada token, e podemos encontrar o número do token mais provável utilizando `argmax`. Em seguida, adicionamos este carácter à lista de tokens gerados e continuamos com a geração. Este processo de gerar um carácter é repetido `size` vezes para gerar o número necessário de caracteres, e terminamos antecipadamente quando o `eos_token` é encontrado.\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Today #39;s lead to strike for the strike for the strike for the strike (AFP)'"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def generate(model,size=100,start='Today '):\n",
" inp = tokenizer.texts_to_sequences([start])[0]\n",
" chars = inp\n",
" for i in range(size):\n",
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
" nc = tf.argmax(out)\n",
" if nc==eos_token:\n",
" break\n",
" chars.append(nc.numpy())\n",
" inp = inp+[nc]\n",
" return decode(chars)\n",
" \n",
"generate(model)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Amostragem de saída durante o treino\n",
"\n",
"Como não temos métricas úteis como *precisão*, a única forma de verificar se o nosso modelo está a melhorar é através da **amostragem** de strings geradas durante o treino. Para isso, utilizaremos **callbacks**, ou seja, funções que podemos passar para a função `fit`, e que serão chamadas periodicamente durante o treino.\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Epoch 1/3\n",
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.2703\n",
"Today #39;s a lead in the company for the strike\n",
"Epoch 2/3\n",
"15000/15000 [==============================] - 227s 15ms/step - loss: 1.2057\n",
"Today #39;s the Market Service on Security Start (AP)\n",
"Epoch 3/3\n",
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.1752\n",
"Today #39;s a line on the strike to start for the start\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7fa40c74e3d0>"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sampling_callback = keras.callbacks.LambdaCallback(\n",
" on_epoch_end = lambda batch, logs: print(generate(model))\n",
")\n",
"\n",
"model.fit(ds_train.batch(8).map(title_batch_fn),callbacks=[sampling_callback],epochs=3)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Este exemplo já gera um texto bastante bom, mas pode ser melhorado de várias maneiras:\n",
"\n",
"* **Mais texto**. Utilizámos apenas títulos para a nossa tarefa, mas pode querer experimentar com texto completo. Lembre-se de que as RNNs não lidam muito bem com sequências longas, por isso faz sentido dividi-las em frases mais curtas ou treinar sempre com uma sequência fixa de comprimento pré-definido `num_chars` (por exemplo, 256). Pode tentar alterar o exemplo acima para essa arquitetura, usando o [tutorial oficial do Keras](https://keras.io/examples/generative/lstm_character_level_text_generation/) como inspiração.\n",
"\n",
"* **LSTM com várias camadas**. Faz sentido experimentar 2 ou 3 camadas de células LSTM. Como mencionámos na unidade anterior, cada camada de LSTM extrai certos padrões do texto, e no caso de um gerador a nível de caracteres, podemos esperar que o nível inferior do LSTM seja responsável por extrair sílabas, e os níveis superiores - palavras e combinações de palavras. Isto pode ser implementado facilmente passando o parâmetro número-de-camadas ao construtor do LSTM.\n",
"\n",
"* Também pode querer experimentar com **unidades GRU** e ver quais produzem melhores resultados, bem como com **diferentes tamanhos de camadas ocultas**. Uma camada oculta demasiado grande pode resultar em overfitting (por exemplo, a rede aprenderá o texto exato), e um tamanho menor pode não produzir bons resultados.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Geração de texto suave e temperatura\n",
"\n",
"Na definição anterior de `generate`, estávamos sempre a escolher o carácter com a maior probabilidade como o próximo carácter no texto gerado. Isto resultava no facto de o texto frequentemente \"ciclar\" entre as mesmas sequências de caracteres repetidamente, como neste exemplo:\n",
"```\n",
"today of the second the company and a second the company ...\n",
"```\n",
"\n",
"No entanto, se analisarmos a distribuição de probabilidades para o próximo carácter, pode acontecer que a diferença entre algumas das probabilidades mais altas não seja muito grande, por exemplo, um carácter pode ter uma probabilidade de 0.2, outro de 0.19, etc. Por exemplo, ao procurar o próximo carácter na sequência '*play*', o próximo carácter pode ser igualmente um espaço ou **e** (como na palavra *player*).\n",
"\n",
"Isto leva-nos à conclusão de que nem sempre é \"justo\" selecionar o carácter com maior probabilidade, porque escolher o segundo mais provável ainda pode levar-nos a um texto significativo. É mais sensato **amostrar** caracteres a partir da distribuição de probabilidades fornecida pela saída da rede.\n",
"\n",
"Esta amostragem pode ser feita utilizando a função `np.multinomial`, que implementa a chamada **distribuição multinomial**. Uma função que implementa esta geração de texto **suave** está definida abaixo:\n"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"--- Temperature = 0.3\n",
"Today #39;s strike #39; to start at the store return\n",
"On Sunday PO to Be Data Profit Up (Reuters)\n",
"Moscow, SP wins straight to the Microsoft #39;s control of the space start\n",
"President olding of the blast start for the strike to pay &lt;b&gt;...&lt;/b&gt;\n",
"Little red riding hood ficed to the spam countered in European &lt;b&gt;...&lt;/b&gt;\n",
"\n",
"--- Temperature = 0.8\n",
"Today countie strikes ryder missile faces food market blut\n",
"On Sunday collores lose-toppy of sale of Bullment in &lt;b&gt;...&lt;/b&gt;\n",
"Moscow, IBM Diffeiting in Afghan Software Hotels (Reuters)\n",
"President Ol Luster for Profit Peaced Raised (AP)\n",
"Little red riding hood dace on depart talks #39; bank up\n",
"\n",
"--- Temperature = 1.0\n",
"Today wits House buiting debate fixes #39; supervice stake again\n",
"On Sunday arling digital poaching In for level\n",
"Moscow, DS Up 7, Top Proble Protest Caprey Mamarian Strike\n",
"President teps help of roubler stepted lessabul-Dhalitics (AFP)\n",
"Little red riding hood signs on cash in Carter-youb\n",
"\n",
"--- Temperature = 1.3\n",
"Today wits flawer ro, pSIA figat's co DroftwavesIs Talo up\n",
"On Sunday hround elitwing wint EU Powerburlinetien\n",
"Moscow, Bazz #39;s sentries olymen winnelds' next for Olympite Huc?\n",
"President lost securitys from power Elections in Smiltrials\n",
"Little red riding hood vides profit, exponituity, profitmainalist-at said listers\n",
"\n",
"--- Temperature = 1.8\n",
"Today #39;It: He deat: N.KA Asside\n",
"On Sunday i arry Par aldeup patient Wo stele1\n"
]
},
{
"ename": "KeyError",
"evalue": "0",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"\\n--- Temperature = {i}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenerate_soft\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m300\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwords\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mtemperature\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36mgenerate_soft\u001b[0;34m(model, size, start, temperature)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mchars\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0minp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minp\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchars\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mwords\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m'Today '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'On Sunday '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Moscow, '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'President '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Little red riding hood '\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36m<listcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mKeyError\u001b[0m: 0"
]
}
],
"source": [
"def generate_soft(model,size=100,start='Today ',temperature=1.0):\n",
" inp = tokenizer.texts_to_sequences([start])[0]\n",
" chars = inp\n",
" for i in range(size):\n",
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
" probs = tf.exp(tf.math.log(out)/temperature).numpy().astype(np.float64)\n",
" probs = probs/np.sum(probs)\n",
" nc = np.argmax(np.random.multinomial(1,probs,1))\n",
" if nc==eos_token:\n",
" break\n",
" chars.append(nc)\n",
" inp = inp+[nc]\n",
" return decode(chars)\n",
"\n",
"words = ['Today ','On Sunday ','Moscow, ','President ','Little red riding hood ']\n",
" \n",
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
" print(f\"\\n--- Temperature = {i}\")\n",
" for j in range(5):\n",
" print(generate_soft(model,size=300,start=words[j],temperature=i))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Introduzimos mais um parâmetro chamado **temperatura**, que é usado para indicar o quão rigorosamente devemos aderir à maior probabilidade. Se a temperatura for 1.0, fazemos uma amostragem multinomial justa, e quando a temperatura vai para infinito - todas as probabilidades tornam-se iguais, e selecionamos aleatoriamente o próximo caractere. No exemplo abaixo, podemos observar que o texto torna-se sem sentido quando aumentamos demasiado a temperatura, e assemelha-se a um texto \"ciclado\" gerado rigidamente quando se aproxima de 0.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "9fbb7d5fda708537649f71f5f646fcde",
"translation_date": "2025-08-31T11:55:50+00:00",
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,79 @@
# Redes Generativas
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/33)
As Redes Neuronais Recorrentes (RNNs) e suas variantes com células de controlo, como as Long Short Term Memory Cells (LSTMs) e Gated Recurrent Units (GRUs), proporcionaram um mecanismo para modelação de linguagem, permitindo aprender a ordem das palavras e prever a próxima palavra numa sequência. Isto permite-nos usar RNNs para **tarefas generativas**, como geração de texto comum, tradução automática e até legendagem de imagens.
> ✅ Pense em todas as vezes que beneficiou de tarefas generativas, como a conclusão de texto enquanto escreve. Pesquise sobre as suas aplicações favoritas para ver se utilizaram RNNs.
Na arquitetura de RNN discutida na unidade anterior, cada unidade RNN produzia o próximo estado oculto como saída. No entanto, também podemos adicionar outra saída a cada unidade recorrente, permitindo-nos gerar uma **sequência** (de comprimento igual à sequência original). Além disso, podemos usar unidades RNN que não aceitam uma entrada em cada passo, mas apenas um vetor de estado inicial, e depois produzem uma sequência de saídas.
Isto permite diferentes arquiteturas neuronais, como mostrado na imagem abaixo:
![Imagem mostrando padrões comuns de redes neuronais recorrentes.](../../../../../translated_images/pt-PT/unreasonable-effectiveness-of-rnn.541ead816778f42d.webp)
> Imagem do artigo [Unreasonable Effectiveness of Recurrent Neural Networks](http://karpathy.github.io/2015/05/21/rnn-effectiveness/) por [Andrej Karpaty](http://karpathy.github.io/)
* **Um-para-um** é uma rede neural tradicional com uma entrada e uma saída.
* **Um-para-muitos** é uma arquitetura generativa que aceita um valor de entrada e gera uma sequência de valores de saída. Por exemplo, se quisermos treinar uma rede de **legendagem de imagens** que produza uma descrição textual de uma imagem, podemos usar uma imagem como entrada, passá-la por uma CNN para obter o estado oculto e, em seguida, usar uma cadeia recorrente para gerar a legenda palavra por palavra.
* **Muitos-para-um** corresponde às arquiteturas RNN descritas na unidade anterior, como classificação de texto.
* **Muitos-para-muitos**, ou **sequência-para-sequência**, corresponde a tarefas como **tradução automática**, onde temos uma primeira RNN que recolhe toda a informação da sequência de entrada no estado oculto, e outra cadeia RNN que desenrola este estado na sequência de saída.
Nesta unidade, vamos focar-nos em modelos generativos simples que nos ajudam a gerar texto. Para simplificar, usaremos tokenização a nível de caracteres.
Vamos treinar esta RNN para gerar texto passo a passo. Em cada passo, tomaremos uma sequência de caracteres de comprimento `nchars` e pediremos à rede que gere o próximo carácter de saída para cada carácter de entrada:
![Imagem mostrando um exemplo de geração de RNN da palavra 'HELLO'.](../../../../../translated_images/pt-PT/rnn-generate.56c54afb52f9781d.webp)
Ao gerar texto (durante a inferência), começamos com um **prompt**, que é passado pelas células RNN para gerar o estado intermédio, e a partir deste estado começa a geração. Geramos um carácter de cada vez e passamos o estado e o carácter gerado para outra célula RNN para gerar o próximo, até gerarmos caracteres suficientes.
<img src="../../../../../translated_images/pt-PT/rnn-generate-inf.5168dc65e0370eea.webp" width="60%"/>
> Imagem do autor
## ✍️ Exercícios: Redes Generativas
Continue a sua aprendizagem nos seguintes notebooks:
* [Redes Generativas com PyTorch](GenerativePyTorch.ipynb)
* [Redes Generativas com TensorFlow](GenerativeTF.ipynb)
## Geração de texto suave e temperatura
A saída de cada célula RNN é uma distribuição de probabilidade de caracteres. Se sempre escolhermos o carácter com a maior probabilidade como o próximo carácter no texto gerado, o texto pode frequentemente tornar-se "cíclico", repetindo as mesmas sequências de caracteres repetidamente, como neste exemplo:
```
today of the second the company and a second the company ...
```
No entanto, se olharmos para a distribuição de probabilidade do próximo carácter, pode acontecer que a diferença entre algumas das maiores probabilidades não seja muito grande, por exemplo, um carácter pode ter probabilidade 0.2, outro 0.19, etc. Por exemplo, ao procurar o próximo carácter na sequência '*play*', o próximo carácter pode ser igualmente um espaço ou **e** (como na palavra *player*).
Isto leva-nos à conclusão de que nem sempre é "justo" selecionar o carácter com maior probabilidade, porque escolher o segundo maior ainda pode levar a texto significativo. É mais sensato **amostrar** caracteres da distribuição de probabilidade dada pela saída da rede. Podemos também usar um parâmetro, **temperatura**, que ajusta a distribuição de probabilidade, caso queiramos adicionar mais aleatoriedade ou torná-la mais íngreme, se quisermos aderir mais aos caracteres de maior probabilidade.
Explore como esta geração de texto suave é implementada nos notebooks acima mencionados.
## Conclusão
Embora a geração de texto possa ser útil por si só, os maiores benefícios vêm da capacidade de gerar texto usando RNNs a partir de algum vetor de características inicial. Por exemplo, a geração de texto é usada como parte da tradução automática (sequência-para-sequência, neste caso o vetor de estado do *encoder* é usado para gerar ou *decodificar* a mensagem traduzida) ou para gerar descrições textuais de uma imagem (neste caso, o vetor de características viria de um extrator CNN).
## 🚀 Desafio
Faça algumas lições na Microsoft Learn sobre este tópico:
* Geração de Texto com [PyTorch](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-pytorch/6-generative-networks/?WT.mc_id=academic-77998-cacaste)/[TensorFlow](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-tensorflow/5-generative-networks/?WT.mc_id=academic-77998-cacaste)
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/34)
## Revisão & Autoestudo
Aqui estão alguns artigos para expandir o seu conhecimento:
* Diferentes abordagens para geração de texto com Markov Chain, LSTM e GPT-2: [artigo](https://towardsdatascience.com/text-generation-gpt-2-lstm-markov-chain-9ea371820e1e)
* Exemplo de geração de texto na [documentação do Keras](https://keras.io/examples/generative/lstm_character_level_text_generation/)
## [Tarefa](lab/README.md)
Vimos como gerar texto carácter por carácter. No laboratório, irá explorar a geração de texto a nível de palavras.
---

View File

@ -0,0 +1,14 @@
# Geração de Texto a Nível de Palavra usando RNNs
Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).
## Tarefa
Neste trabalho, precisas de escolher um livro e utilizá-lo como conjunto de dados para treinar um gerador de texto a nível de palavra.
## O Conjunto de Dados
Podes usar qualquer livro. Podes encontrar muitos textos gratuitos no [Project Gutenberg](https://www.gutenberg.org/), por exemplo, aqui está um link direto para [Alice's Adventures in Wonderland](https://www.gutenberg.org/files/11/11-0.txt)) de Lewis Carroll.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,112 @@
# Mecanismos de Atenção e Transformers
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/35)
Um dos problemas mais importantes no domínio de NLP é a **tradução automática**, uma tarefa essencial que sustenta ferramentas como o Google Translate. Nesta seção, vamos focar na tradução automática ou, de forma mais geral, em qualquer tarefa de *sequência para sequência* (também chamada de **transdução de frases**).
Com RNNs, a tarefa de sequência para sequência é implementada por duas redes recorrentes, onde uma rede, o **codificador**, condensa uma sequência de entrada num estado oculto, enquanto outra rede, o **descodificador**, expande este estado oculto num resultado traduzido. Existem alguns problemas com esta abordagem:
* O estado final da rede codificadora tem dificuldade em lembrar-se do início de uma frase, causando uma qualidade inferior do modelo para frases longas.
* Todas as palavras numa sequência têm o mesmo impacto no resultado. Na realidade, no entanto, palavras específicas na sequência de entrada frequentemente têm mais impacto nos resultados sequenciais do que outras.
Os **Mecanismos de Atenção** fornecem um meio de ponderar o impacto contextual de cada vetor de entrada em cada previsão de saída da RNN. Isto é implementado criando atalhos entre estados intermediários da RNN de entrada e a RNN de saída. Desta forma, ao gerar o símbolo de saída y<sub>t</sub>, consideramos todos os estados ocultos de entrada h<sub>i</sub>, com diferentes coeficientes de peso &alpha;<sub>t,i</sub>.
![Imagem mostrando um modelo codificador/descodificador com uma camada de atenção aditiva](../../../../../translated_images/pt-PT/encoder-decoder-attention.7a726296894fb567.webp)
> O modelo codificador-descodificador com mecanismo de atenção aditiva em [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf), citado deste [post de blog](https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html)
A matriz de atenção {&alpha;<sub>i,j</sub>} representaria o grau em que certas palavras de entrada influenciam a geração de uma determinada palavra na sequência de saída. Abaixo está um exemplo de tal matriz:
![Imagem mostrando um alinhamento de exemplo encontrado por RNNsearch-50, retirada de Bahdanau - arviz.org](../../../../../translated_images/pt-PT/bahdanau-fig3.09ba2d37f202a6af.webp)
> Figura de [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf) (Fig.3)
Os mecanismos de atenção são responsáveis por grande parte do estado da arte atual ou próximo ao estado da arte em NLP. No entanto, adicionar atenção aumenta significativamente o número de parâmetros do modelo, o que levou a problemas de escalabilidade com RNNs. Uma restrição chave na escalabilidade das RNNs é que a natureza recorrente dos modelos torna desafiador agrupar e paralelizar o treino. Numa RNN, cada elemento de uma sequência precisa ser processado em ordem sequencial, o que significa que não pode ser facilmente paralelizado.
![Codificador Descodificador com Atenção](../../../../../lessons/5-NLP/18-Transformers/images/EncDecAttention.gif)
> Figura do [Blog do Google](https://research.googleblog.com/2016/09/a-neural-network-for-machine.html)
A adoção de mecanismos de atenção combinada com esta restrição levou à criação dos modelos Transformer, agora estado da arte, que conhecemos e usamos hoje, como BERT e Open-GPT3.
## Modelos Transformer
Uma das principais ideias por trás dos transformers é evitar a natureza sequencial das RNNs e criar um modelo que seja paralelizável durante o treino. Isto é alcançado implementando duas ideias:
* codificação posicional
* uso de mecanismo de auto-atenção para capturar padrões em vez de RNNs (ou CNNs) (é por isso que o artigo que introduz os transformers se chama *[Attention is all you need](https://arxiv.org/abs/1706.03762)*)
### Codificação/Embeddings Posicionais
A ideia da codificação posicional é a seguinte.
1. Ao usar RNNs, a posição relativa dos tokens é representada pelo número de passos e, portanto, não precisa ser explicitamente representada.
2. No entanto, ao mudar para atenção, precisamos saber as posições relativas dos tokens dentro de uma sequência.
3. Para obter codificação posicional, aumentamos a nossa sequência de tokens com uma sequência de posições dos tokens na sequência (ou seja, uma sequência de números 0,1, ...).
4. Depois misturamos a posição do token com um vetor de embedding do token. Para transformar a posição (inteiro) num vetor, podemos usar diferentes abordagens:
* Embedding treinável, semelhante ao embedding de tokens. Esta é a abordagem que consideramos aqui. Aplicamos camadas de embedding tanto nos tokens quanto nas suas posições, resultando em vetores de embedding com as mesmas dimensões, que depois somamos.
* Função de codificação posicional fixa, como proposto no artigo original.
<img src="../../../../../translated_images/pt-PT/pos-embedding.e41ce9b6cf6078af.webp" width="50%"/>
> Imagem do autor
O resultado que obtemos com o embedding posicional incorpora tanto o token original quanto a sua posição dentro de uma sequência.
### Auto-Atenção Multi-Head
A seguir, precisamos capturar alguns padrões dentro da nossa sequência. Para isso, os transformers usam um mecanismo de **auto-atenção**, que é essencialmente atenção aplicada à mesma sequência como entrada e saída. Aplicar auto-atenção permite-nos levar em conta o **contexto** dentro da frase e ver quais palavras estão inter-relacionadas. Por exemplo, permite-nos ver quais palavras são referidas por correferências, como *it*, e também considerar o contexto:
![](../../../../../translated_images/pt-PT/CoreferenceResolution.861924d6d384a7d6.webp)
> Imagem do [Blog do Google](https://research.googleblog.com/2017/08/transformer-novel-neural-network.html)
Nos transformers, usamos **Atenção Multi-Head** para dar à rede o poder de capturar vários tipos diferentes de dependências, como relações de palavras de longo prazo vs. curto prazo, correferência vs. algo diferente, etc.
[Notebook TensorFlow](TransformersTF.ipynb) contém mais detalhes sobre a implementação de camadas de transformer.
### Atenção Codificador-Descodificador
Nos transformers, a atenção é usada em dois lugares:
* Para capturar padrões dentro do texto de entrada usando auto-atenção
* Para realizar tradução de sequência - é a camada de atenção entre codificador e descodificador.
A atenção codificador-descodificador é muito semelhante ao mecanismo de atenção usado em RNNs, conforme descrito no início desta seção. Este diagrama animado explica o papel da atenção codificador-descodificador.
![GIF animado mostrando como as avaliações são realizadas em modelos transformer.](../../../../../lessons/5-NLP/18-Transformers/images/transformer-animated-explanation.gif)
Como cada posição de entrada é mapeada independentemente para cada posição de saída, os transformers podem paralelizar melhor do que as RNNs, o que permite modelos de linguagem muito maiores e mais expressivos. Cada cabeça de atenção pode ser usada para aprender diferentes relações entre palavras, melhorando tarefas de Processamento de Linguagem Natural.
## BERT
**BERT** (Bidirectional Encoder Representations from Transformers) é uma rede transformer muito grande com várias camadas: 12 camadas para o *BERT-base* e 24 para o *BERT-large*. O modelo é primeiro pré-treinado num grande corpus de dados de texto (WikiPedia + livros) usando treino não supervisionado (prevendo palavras mascaradas numa frase). Durante o pré-treino, o modelo absorve níveis significativos de compreensão da linguagem, que podem ser aproveitados com outros conjuntos de dados usando ajuste fino. Este processo é chamado de **aprendizagem por transferência**.
![imagem de http://jalammar.github.io/illustrated-bert/](../../../../../translated_images/pt-PT/jalammarBERT-language-modeling-masked-lm.34f113ea5fec4362.webp)
> Imagem [fonte](http://jalammar.github.io/illustrated-bert/)
## ✍️ Exercícios: Transformers
Continue a sua aprendizagem nos seguintes notebooks:
* [Transformers em PyTorch](TransformersPyTorch.ipynb)
* [Transformers em TensorFlow](TransformersTF.ipynb)
## Conclusão
Nesta lição, aprendeu sobre Transformers e Mecanismos de Atenção, ferramentas essenciais na caixa de ferramentas de NLP. Existem muitas variações de arquiteturas Transformer, incluindo BERT, DistilBERT, BigBird, OpenGPT3 e mais, que podem ser ajustadas. O pacote [HuggingFace](https://github.com/huggingface/) fornece um repositório para treinar muitas destas arquiteturas com PyTorch e TensorFlow.
## 🚀 Desafio
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/36)
## Revisão e Autoestudo
* [Post de blog](https://mchromiak.github.io/articles/2017/Sep/12/Transformer-Attention-is-all-you-need/), explicando o clássico artigo [Attention is all you need](https://arxiv.org/abs/1706.03762) sobre transformers.
* [Uma série de posts de blog](https://towardsdatascience.com/transformers-explained-visually-part-1-overview-of-functionality-95a6dd460452) sobre transformers, explicando a arquitetura em detalhe.
## [Tarefa](assignment.md)
---

View File

@ -0,0 +1,353 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Mecanismos de atenção e transformadores\n",
"\n",
"Uma grande limitação das redes recorrentes é que todas as palavras numa sequência têm o mesmo impacto no resultado. Isso causa um desempenho subótimo em modelos padrão de codificador-decodificador LSTM para tarefas de sequência para sequência, como Reconhecimento de Entidades Nomeadas e Tradução Automática. Na realidade, palavras específicas na sequência de entrada frequentemente têm mais impacto nos resultados sequenciais do que outras.\n",
"\n",
"Considere um modelo de sequência para sequência, como a tradução automática. Ele é implementado por duas redes recorrentes, onde uma rede (**codificador**) comprime a sequência de entrada num estado oculto, e outra, o **decodificador**, expande esse estado oculto no resultado traduzido. O problema com essa abordagem é que o estado final da rede tem dificuldade em lembrar o início de uma frase, o que resulta numa qualidade inferior do modelo em frases longas.\n",
"\n",
"**Mecanismos de Atenção** fornecem um meio de ponderar o impacto contextual de cada vetor de entrada em cada previsão de saída da RNN. Isso é implementado criando atalhos entre os estados intermediários da RNN de entrada e a RNN de saída. Dessa forma, ao gerar o símbolo de saída $y_t$, consideramos todos os estados ocultos de entrada $h_i$, com diferentes coeficientes de peso $\\alpha_{t,i}$. \n",
"\n",
"![Imagem mostrando um modelo codificador/decodificador com uma camada de atenção aditiva](../../../../../lessons/5-NLP/18-Transformers/images/encoder-decoder-attention.png)\n",
"*O modelo codificador-decodificador com mecanismo de atenção aditiva em [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf), citado deste [post de blog](https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html)*\n",
"\n",
"A matriz de atenção $\\{\\alpha_{i,j}\\}$ representaria o grau em que certas palavras de entrada influenciam a geração de uma palavra específica na sequência de saída. Abaixo está um exemplo de tal matriz:\n",
"\n",
"![Imagem mostrando um alinhamento de exemplo encontrado pelo RNNsearch-50, retirada de Bahdanau - arviz.org](../../../../../lessons/5-NLP/18-Transformers/images/bahdanau-fig3.png)\n",
"\n",
"*Figura retirada de [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf) (Fig.3)*\n",
"\n",
"Os mecanismos de atenção são responsáveis por grande parte do estado da arte atual ou próximo ao estado da arte no Processamento de Linguagem Natural. No entanto, adicionar atenção aumenta significativamente o número de parâmetros do modelo, o que levou a problemas de escalabilidade com RNNs. Uma limitação chave na escalabilidade das RNNs é que a natureza recorrente dos modelos torna desafiador agrupar e paralelizar o treino. Numa RNN, cada elemento de uma sequência precisa ser processado em ordem sequencial, o que significa que não pode ser facilmente paralelizado.\n",
"\n",
"A adoção de mecanismos de atenção combinada com essa limitação levou à criação dos agora modelos transformadores de estado da arte que conhecemos e usamos hoje, desde o BERT até o OpenGPT3.\n",
"\n",
"## Modelos transformadores\n",
"\n",
"Em vez de encaminhar o contexto de cada previsão anterior para a próxima etapa de avaliação, os **modelos transformadores** utilizam **codificações posicionais** e atenção para capturar o contexto de uma entrada específica dentro de uma janela de texto fornecida. A imagem abaixo mostra como as codificações posicionais com atenção podem capturar o contexto dentro de uma janela específica.\n",
"\n",
"![GIF animado mostrando como as avaliações são realizadas em modelos transformadores.](../../../../../lessons/5-NLP/18-Transformers/images/transformer-animated-explanation.gif) \n",
"\n",
"Como cada posição de entrada é mapeada de forma independente para cada posição de saída, os transformadores podem ser melhor paralelizados do que as RNNs, o que permite modelos de linguagem muito maiores e mais expressivos. Cada cabeça de atenção pode ser usada para aprender diferentes relações entre palavras, melhorando as tarefas de Processamento de Linguagem Natural.\n",
"\n",
"**BERT** (Representações de Codificador Bidirecional de Transformadores) é uma rede transformadora muito grande com várias camadas: 12 camadas para o *BERT-base* e 24 para o *BERT-large*. O modelo é inicialmente pré-treinado num grande corpus de dados textuais (Wikipedia + livros) usando treino não supervisionado (prevendo palavras mascaradas numa frase). Durante o pré-treino, o modelo adquire um nível significativo de compreensão da linguagem, que pode ser aproveitado com outros conjuntos de dados usando ajuste fino. Esse processo é chamado de **aprendizagem por transferência**. \n",
"\n",
"![imagem de http://jalammar.github.io/illustrated-bert/](../../../../../lessons/5-NLP/18-Transformers/images/jalammarBERT-language-modeling-masked-lm.png)\n",
"\n",
"Existem muitas variações das arquiteturas de Transformadores, incluindo BERT, DistilBERT, BigBird, OpenGPT3 e mais, que podem ser ajustadas. O pacote [HuggingFace](https://github.com/huggingface/) fornece um repositório para treinar muitas dessas arquiteturas com PyTorch. \n",
"\n",
"## Usando o BERT para classificação de texto\n",
"\n",
"Vamos ver como podemos usar o modelo BERT pré-treinado para resolver a nossa tarefa tradicional: classificação de sequência. Vamos classificar o nosso conjunto de dados original AG News.\n",
"\n",
"Primeiro, vamos carregar a biblioteca HuggingFace e o nosso conjunto de dados:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loading dataset...\n",
"Building vocab...\n"
]
}
],
"source": [
"import torch\n",
"import torchtext\n",
"from torchnlp import *\n",
"import transformers\n",
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
"vocab_len = len(vocab)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como iremos utilizar o modelo BERT pré-treinado, será necessário usar um tokenizer específico. Primeiro, vamos carregar um tokenizer associado ao modelo BERT pré-treinado.\n",
"\n",
"A biblioteca HuggingFace contém um repositório de modelos pré-treinados, que podem ser utilizados simplesmente especificando os seus nomes como argumentos nas funções `from_pretrained`. Todos os ficheiros binários necessários para o modelo serão automaticamente descarregados.\n",
"\n",
"No entanto, em determinados momentos, pode ser necessário carregar os seus próprios modelos. Nesse caso, pode especificar o diretório que contém todos os ficheiros relevantes, incluindo os parâmetros para o tokenizer, o ficheiro `config.json` com os parâmetros do modelo, os pesos binários, etc.\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"# To load the model from Internet repository using model name. \n",
"# Use this if you are running from your own copy of the notebooks\n",
"bert_model = 'bert-base-uncased' \n",
"\n",
"# To load the model from the directory on disk. Use this for Microsoft Learn module, because we have\n",
"# prepared all required files for you.\n",
"bert_model = './bert'\n",
"\n",
"tokenizer = transformers.BertTokenizer.from_pretrained(bert_model)\n",
"\n",
"MAX_SEQ_LEN = 128\n",
"PAD_INDEX = tokenizer.convert_tokens_to_ids(tokenizer.pad_token)\n",
"UNK_INDEX = tokenizer.convert_tokens_to_ids(tokenizer.unk_token)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O objeto `tokenizer` contém a função `encode` que pode ser usada diretamente para codificar texto:\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[101, 1052, 22123, 2953, 2818, 2003, 1037, 2307, 7705, 2005, 17953, 2361, 102]"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer.encode('PyTorch is a great framework for NLP')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Em seguida, vamos criar iteradores que usaremos durante o treino para aceder aos dados. Como o BERT utiliza a sua própria função de codificação, será necessário definir uma função de preenchimento semelhante à `padify` que definimos anteriormente:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def pad_bert(b):\n",
" # b is the list of tuples of length batch_size\n",
" # - first element of a tuple = label, \n",
" # - second = feature (text sequence)\n",
" # build vectorized sequence\n",
" v = [tokenizer.encode(x[1]) for x in b]\n",
" # compute max length of a sequence in this minibatch\n",
" l = max(map(len,v))\n",
" return ( # tuple of two tensors - labels and features\n",
" torch.LongTensor([t[0] for t in b]),\n",
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v])\n",
" )\n",
"\n",
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=8, collate_fn=pad_bert, shuffle=True)\n",
"test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=8, collate_fn=pad_bert)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No nosso caso, iremos utilizar o modelo BERT pré-treinado chamado `bert-base-uncased`. Vamos carregar o modelo utilizando o pacote `BertForSequenceClassification`. Isto garante que o nosso modelo já tenha a arquitetura necessária para classificação, incluindo o classificador final. Verás uma mensagem de aviso indicando que os pesos do classificador final não estão inicializados e que o modelo necessitaria de pré-treino - isso é perfeitamente normal, porque é exatamente o que estamos prestes a fazer!\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Some weights of the model checkpoint at ./bert were not used when initializing BertForSequenceClassification: ['cls.predictions.bias', 'cls.predictions.transform.dense.weight', 'cls.predictions.transform.dense.bias', 'cls.predictions.decoder.weight', 'cls.seq_relationship.weight', 'cls.seq_relationship.bias', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.LayerNorm.bias']\n",
"- This IS expected if you are initializing BertForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).\n",
"- This IS NOT expected if you are initializing BertForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).\n",
"Some weights of BertForSequenceClassification were not initialized from the model checkpoint at ./bert and are newly initialized: ['classifier.weight', 'classifier.bias']\n",
"You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
]
}
],
"source": [
"model = transformers.BertForSequenceClassification.from_pretrained(bert_model,num_labels=4).to(device)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora estamos prontos para começar o treino! Como o BERT já está pré-treinado, queremos começar com uma taxa de aprendizagem relativamente pequena para não comprometer os pesos iniciais.\n",
"\n",
"Todo o trabalho pesado é realizado pelo modelo `BertForSequenceClassification`. Quando chamamos o modelo nos dados de treino, ele retorna tanto a perda (loss) quanto a saída da rede para o minibatch de entrada. Utilizamos a perda para a otimização dos parâmetros (`loss.backward()` realiza a retropropagação), e `out` para calcular a precisão do treino, comparando os rótulos obtidos `labs` (calculados usando `argmax`) com os rótulos esperados `labels`.\n",
"\n",
"Para controlar o processo, acumulamos a perda e a precisão ao longo de várias iterações e imprimimos esses valores a cada `report_freq` ciclos de treino.\n",
"\n",
"Este treino provavelmente levará bastante tempo, por isso limitamos o número de iterações.\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Loss = 1.1254194641113282, Accuracy = 0.585\n",
"Loss = 0.6194715118408203, Accuracy = 0.83\n",
"Loss = 0.46665248870849607, Accuracy = 0.8475\n",
"Loss = 0.4309701919555664, Accuracy = 0.8575\n",
"Loss = 0.35427074432373046, Accuracy = 0.8825\n",
"Loss = 0.3306886291503906, Accuracy = 0.8975\n",
"Loss = 0.30340143203735354, Accuracy = 0.8975\n",
"Loss = 0.26139299392700194, Accuracy = 0.915\n",
"Loss = 0.26708646774291994, Accuracy = 0.9225\n",
"Loss = 0.3667240524291992, Accuracy = 0.8675\n"
]
}
],
"source": [
"optimizer = torch.optim.Adam(model.parameters(), lr=2e-5)\n",
"\n",
"report_freq = 50\n",
"iterations = 500 # make this larger to train for longer time!\n",
"\n",
"model.train()\n",
"\n",
"i,c = 0,0\n",
"acc_loss = 0\n",
"acc_acc = 0\n",
"\n",
"for labels,texts in train_loader:\n",
" labels = labels.to(device)-1 # get labels in the range 0-3 \n",
" texts = texts.to(device)\n",
" loss, out = model(texts, labels=labels)[:2]\n",
" labs = out.argmax(dim=1)\n",
" acc = torch.mean((labs==labels).type(torch.float32))\n",
" optimizer.zero_grad()\n",
" loss.backward()\n",
" optimizer.step()\n",
" acc_loss += loss\n",
" acc_acc += acc\n",
" i+=1\n",
" c+=1\n",
" if i%report_freq==0:\n",
" print(f\"Loss = {acc_loss.item()/c}, Accuracy = {acc_acc.item()/c}\")\n",
" c = 0\n",
" acc_loss = 0\n",
" acc_acc = 0\n",
" iterations-=1\n",
" if not iterations:\n",
" break"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pode ver (especialmente se aumentar o número de iterações e esperar o tempo suficiente) que a classificação com BERT nos dá uma precisão bastante boa! Isto deve-se ao facto de o BERT já compreender muito bem a estrutura da linguagem, sendo necessário apenas ajustar o classificador final. No entanto, como o BERT é um modelo grande, todo o processo de treino demora bastante tempo e exige um poder computacional significativo! (GPU, e de preferência mais do que uma).\n",
"\n",
"> **Nota:** No nosso exemplo, temos utilizado um dos modelos BERT pré-treinados mais pequenos. Existem modelos maiores que provavelmente produzirão resultados melhores.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Avaliar o desempenho do modelo\n",
"\n",
"Agora podemos avaliar o desempenho do nosso modelo no conjunto de dados de teste. O ciclo de avaliação é bastante semelhante ao ciclo de treino, mas não devemos esquecer de mudar o modelo para o modo de avaliação, chamando `model.eval()`.\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Final accuracy: 0.9047029702970297\n"
]
}
],
"source": [
"model.eval()\n",
"iterations = 100\n",
"acc = 0\n",
"i = 0\n",
"for labels,texts in test_loader:\n",
" labels = labels.to(device)-1 \n",
" texts = texts.to(device)\n",
" _, out = model(texts, labels=labels)[:2]\n",
" labs = out.argmax(dim=1)\n",
" acc += torch.mean((labs==labels).type(torch.float32))\n",
" i+=1\n",
" if i>iterations: break\n",
" \n",
"print(f\"Final accuracy: {acc.item()/i}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusão\n",
"\n",
"Nesta unidade, vimos como é fácil utilizar um modelo de linguagem pré-treinado da biblioteca **transformers** e adaptá-lo à nossa tarefa de classificação de texto. Da mesma forma, os modelos BERT podem ser usados para extração de entidades, resposta a perguntas e outras tarefas de PLN.\n",
"\n",
"Os modelos Transformer representam o estado da arte atual em PLN e, na maioria dos casos, devem ser a primeira solução a ser experimentada ao implementar soluções personalizadas de PLN. No entanto, compreender os princípios básicos subjacentes às redes neurais recorrentes discutidos neste módulo é extremamente importante se quiser desenvolver modelos neurais avançados.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "py37_pytorch",
"language": "python",
"name": "conda-env-py37_pytorch-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.7"
},
"coopTranslator": {
"original_hash": "753865967678a92dbce7d7efbd36d980",
"translation_date": "2025-08-31T11:57:56+00:00",
"source_file": "lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,819 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Mecanismos de atenção e transformers\n",
"\n",
"Uma das principais limitações das redes recorrentes é que todas as palavras numa sequência têm o mesmo impacto no resultado. Isto resulta em desempenho subótimo com modelos padrão de codificador-decodificador LSTM para tarefas de sequência para sequência, como Reconhecimento de Entidades Nomeadas e Tradução Automática. Na realidade, palavras específicas na sequência de entrada frequentemente têm mais impacto nos resultados sequenciais do que outras.\n",
"\n",
"Considere um modelo de sequência para sequência, como a tradução automática. Este é implementado por duas redes recorrentes, onde uma rede (**codificador**) comprime a sequência de entrada num estado oculto, e outra, o **decodificador**, expande este estado oculto no resultado traduzido. O problema com esta abordagem é que o estado final da rede terá dificuldade em lembrar-se do início de uma frase, o que resulta numa qualidade inferior do modelo em frases longas.\n",
"\n",
"**Mecanismos de Atenção** fornecem um meio de ponderar o impacto contextual de cada vetor de entrada em cada previsão de saída da RNN. Isto é implementado criando atalhos entre os estados intermédios da RNN de entrada e a RNN de saída. Desta forma, ao gerar o símbolo de saída $y_t$, consideramos todos os estados ocultos de entrada $h_i$, com diferentes coeficientes de peso $\\alpha_{t,i}$. \n",
"\n",
"![Imagem mostrando um modelo codificador/decodificador com uma camada de atenção aditiva](../../../../../lessons/5-NLP/18-Transformers/images/encoder-decoder-attention.png)\n",
"*O modelo codificador-decodificador com mecanismo de atenção aditiva em [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf), citado deste [artigo de blog](https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html)*\n",
"\n",
"A matriz de atenção $\\{\\alpha_{i,j}\\}$ representa o grau em que certas palavras de entrada influenciam a geração de uma palavra específica na sequência de saída. Abaixo está um exemplo de tal matriz:\n",
"\n",
"![Imagem mostrando um alinhamento de exemplo encontrado pelo RNNsearch-50, retirada de Bahdanau - arviz.org](../../../../../lessons/5-NLP/18-Transformers/images/bahdanau-fig3.png)\n",
"\n",
"*Figura retirada de [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf) (Fig.3)*\n",
"\n",
"Os mecanismos de atenção são responsáveis por grande parte do estado da arte atual ou quase atual no Processamento de Linguagem Natural. No entanto, adicionar atenção aumenta significativamente o número de parâmetros do modelo, o que levou a problemas de escalabilidade com RNNs. Uma limitação chave na escalabilidade das RNNs é que a natureza recorrente dos modelos torna desafiador agrupar e paralelizar o treino. Numa RNN, cada elemento de uma sequência precisa de ser processado em ordem sequencial, o que significa que não pode ser facilmente paralelizado.\n",
"\n",
"A adoção de mecanismos de atenção, combinada com esta limitação, levou à criação dos agora modelos Transformer de Estado da Arte que conhecemos e usamos hoje, desde o BERT ao OpenGPT3.\n",
"\n",
"## Modelos Transformer\n",
"\n",
"Em vez de encaminhar o contexto de cada previsão anterior para o próximo passo de avaliação, os **modelos transformer** utilizam **codificações posicionais** e **atenção** para capturar o contexto de uma entrada específica dentro de uma janela de texto fornecida. A imagem abaixo mostra como as codificações posicionais com atenção podem capturar o contexto dentro de uma janela específica.\n",
"\n",
"![GIF animado mostrando como as avaliações são realizadas em modelos transformer.](../../../../../lessons/5-NLP/18-Transformers/images/transformer-animated-explanation.gif) \n",
"\n",
"Como cada posição de entrada é mapeada de forma independente para cada posição de saída, os transformers conseguem paralelizar melhor do que as RNNs, o que permite modelos de linguagem muito maiores e mais expressivos. Cada cabeça de atenção pode ser usada para aprender diferentes relações entre palavras, melhorando as tarefas de Processamento de Linguagem Natural.\n",
"\n",
"## Construir um Modelo Transformer Simples\n",
"\n",
"O Keras não contém uma camada Transformer integrada, mas podemos construir a nossa própria. Como antes, vamos focar-nos na classificação de texto do conjunto de dados AG News, mas vale a pena mencionar que os modelos Transformer apresentam os melhores resultados em tarefas de PLN mais complexas.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import tensorflow_datasets as tfds\n",
"import numpy as np\n",
"\n",
"ds_train, ds_test = tfds.load('ag_news_subset').values()\n",
"\n",
"def extract_text(x):\n",
" return x['title']+' '+x['description']\n",
"\n",
"def tupelize(x):\n",
" return (extract_text(x),x['label'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Novas camadas no Keras devem ser subclasses da classe `Layer` e implementar o método `call`. Vamos começar com a camada **Positional Embedding**. Usaremos [algum código da documentação oficial do Keras](https://keras.io/examples/nlp/text_classification_with_transformer/). Assumiremos que preenchemos todas as sequências de entrada até ao comprimento `maxlen`.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"class TokenAndPositionEmbedding(keras.layers.Layer):\n",
" def __init__(self, maxlen, vocab_size, embed_dim):\n",
" super(TokenAndPositionEmbedding, self).__init__()\n",
" self.token_emb = keras.layers.Embedding(input_dim=vocab_size, output_dim=embed_dim)\n",
" self.pos_emb = keras.layers.Embedding(input_dim=maxlen, output_dim=embed_dim)\n",
" self.maxlen = maxlen\n",
"\n",
" def call(self, x):\n",
" maxlen = self.maxlen\n",
" positions = tf.range(start=0, limit=maxlen, delta=1)\n",
" positions = self.pos_emb(positions)\n",
" x = self.token_emb(x)\n",
" return x+positions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Esta camada consiste em duas camadas `Embedding`: uma para incorporar tokens (de uma forma que já discutimos anteriormente) e outra para incorporar posições dos tokens. As posições dos tokens são criadas como uma sequência de números naturais de 0 até `maxlen` utilizando `tf.range`, e depois são passadas pela camada de embedding. Os dois vetores de embedding resultantes são então somados, produzindo uma representação incorporada posicionalmente da entrada com a forma `maxlen`$\\times$`embed_dim`.\n",
"\n",
"Agora, vamos implementar o bloco transformer. Ele irá receber como entrada o resultado da camada de embedding definida anteriormente:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"class TransformerBlock(keras.layers.Layer):\n",
" def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1):\n",
" super(TransformerBlock, self).__init__()\n",
" self.att = keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim, name='attn')\n",
" self.ffn = keras.Sequential(\n",
" [keras.layers.Dense(ff_dim, activation=\"relu\"), keras.layers.Dense(embed_dim),]\n",
" )\n",
" self.layernorm1 = keras.layers.LayerNormalization(epsilon=1e-6)\n",
" self.layernorm2 = keras.layers.LayerNormalization(epsilon=1e-6)\n",
" self.dropout1 = keras.layers.Dropout(rate)\n",
" self.dropout2 = keras.layers.Dropout(rate)\n",
"\n",
" def call(self, inputs, training):\n",
" attn_output = self.att(inputs, inputs)\n",
" attn_output = self.dropout1(attn_output, training=training)\n",
" out1 = self.layernorm1(inputs + attn_output)\n",
" ffn_output = self.ffn(out1)\n",
" ffn_output = self.dropout2(ffn_output, training=training)\n",
" return self.layernorm2(out1 + ffn_output)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora, estamos prontos para definir o modelo completo do transformer:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential_1\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"text_vectorization (TextVect (None, 256) 0 \n",
"_________________________________________________________________\n",
"token_and_position_embedding (None, 256, 32) 648192 \n",
"_________________________________________________________________\n",
"transformer_block (Transform (None, 256, 32) 10656 \n",
"_________________________________________________________________\n",
"global_average_pooling1d (Gl (None, 32) 0 \n",
"_________________________________________________________________\n",
"dropout_2 (Dropout) (None, 32) 0 \n",
"_________________________________________________________________\n",
"dense_2 (Dense) (None, 20) 660 \n",
"_________________________________________________________________\n",
"dropout_3 (Dropout) (None, 20) 0 \n",
"_________________________________________________________________\n",
"dense_3 (Dense) (None, 4) 84 \n",
"=================================================================\n",
"Total params: 659,592\n",
"Trainable params: 659,592\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"embed_dim = 32 # Embedding size for each token\n",
"num_heads = 2 # Number of attention heads\n",
"ff_dim = 32 # Hidden layer size in feed forward network inside transformer\n",
"maxlen = 256\n",
"vocab_size = 20000\n",
"\n",
"model = keras.models.Sequential([\n",
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_sequence_length=maxlen, input_shape=(1,)),\n",
" TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim),\n",
" TransformerBlock(embed_dim, num_heads, ff_dim),\n",
" keras.layers.GlobalAveragePooling1D(),\n",
" keras.layers.Dropout(0.1),\n",
" keras.layers.Dense(20, activation=\"relu\"),\n",
" keras.layers.Dropout(0.1),\n",
" keras.layers.Dense(4, activation=\"softmax\")\n",
"])\n",
"\n",
"model.summary()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training tokenizer\n",
"938/938 [==============================] - 45s 39ms/step - loss: 0.4978 - acc: 0.8068 - val_loss: 0.2808 - val_acc: 0.9124\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f9c2427a0d0>"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print('Training tokenizer')\n",
"model.layers[0].adapt(ds_train.map(extract_text))\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Modelos Transformer BERT\n",
"\n",
"**BERT** (Representações de Codificador Bidirecional de Transformers) é uma rede transformer muito grande com várias camadas: 12 camadas para o *BERT-base* e 24 para o *BERT-large*. O modelo é inicialmente pré-treinado em um grande corpus de dados de texto (WikiPedia + livros) utilizando treino não supervisionado (prevendo palavras mascaradas numa frase). Durante o pré-treino, o modelo adquire um nível significativo de compreensão da linguagem, que pode ser aproveitado com outros conjuntos de dados através de ajuste fino. Este processo é chamado de **aprendizagem por transferência**.\n",
"\n",
"![imagem de http://jalammar.github.io/illustrated-bert/](../../../../../lessons/5-NLP/18-Transformers/images/jalammarBERT-language-modeling-masked-lm.png)\n",
"\n",
"Existem muitas variações de arquiteturas Transformer, incluindo BERT, DistilBERT, BigBird, OpenGPT3 e outras, que podem ser ajustadas.\n",
"\n",
"Vamos ver como podemos usar o modelo BERT pré-treinado para resolver o nosso problema tradicional de classificação de sequências. Vamos aproveitar a ideia e algum código da [documentação oficial](https://www.tensorflow.org/text/tutorials/classify_text_with_bert).\n",
"\n",
"Para carregar modelos pré-treinados, utilizaremos o **Tensorflow hub**. Primeiro, vamos carregar o vetor específico do BERT:\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"ename": "ModuleNotFoundError",
"evalue": "No module named 'tensorflow_text'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_41180/4216669875.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mimport\u001b[0m \u001b[0mtensorflow_text\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mtensorflow_hub\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mhub\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mvectorizer\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mhub\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mKerasLayer\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'tensorflow_text'"
]
}
],
"source": [
"import tensorflow_text \n",
"import tensorflow_hub as hub\n",
"vectorizer = hub.KerasLayer('https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3')"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input_type_ids': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
" array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
" dtype=int32)>,\n",
" 'input_word_ids': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
" array([[ 101, 1045, 2293, 19081, 102, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0]], dtype=int32)>,\n",
" 'input_mask': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
" array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
" dtype=int32)>}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vectorizer(['I love transformers'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"É importante que utilizes o mesmo vectorizador que foi usado para treinar a rede original. Além disso, o vectorizador BERT retorna três componentes:\n",
"* `input_word_ids`, que é uma sequência de números de tokens para a frase de entrada\n",
"* `input_mask`, que indica qual parte da sequência contém a entrada real e qual é preenchimento. É semelhante à máscara produzida pela camada `Masking`\n",
"* `input_type_ids` é usado para tarefas de modelagem de linguagem e permite especificar duas frases de entrada numa única sequência.\n",
"\n",
"De seguida, podemos instanciar o extrator de características BERT:\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"bert = hub.KerasLayer('https://tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-4_H-128_A-2/1')"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pooled_output -> (1, 128)\n",
"encoder_outputs -> 4\n",
"sequence_output -> (1, 128, 128)\n",
"default -> (1, 128)\n"
]
}
],
"source": [
"z = bert(vectorizer(['I love transformers']))\n",
"for i,x in z.items():\n",
" print(f\"{i} -> { len(x) if isinstance(x, list) else x.shape }\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Então, a camada BERT retorna vários resultados úteis:\n",
"* `pooled_output` é o resultado da média de todos os tokens na sequência. Pode ser visto como uma representação semântica inteligente de toda a rede. É equivalente ao resultado da camada `GlobalAveragePooling1D` no nosso modelo anterior.\n",
"* `sequence_output` é o resultado da última camada transformer (corresponde ao resultado de `TransformerBlock` no nosso modelo acima).\n",
"* `encoder_outputs` são os resultados de todas as camadas transformer. Como carregámos um modelo BERT de 4 camadas (como provavelmente pode deduzir pelo nome, que contém `4_H`), ele possui 4 tensores. O último é igual a `sequence_output`.\n",
"\n",
"Agora vamos definir o modelo de classificação de ponta a ponta. Utilizaremos a *definição funcional do modelo*, onde definimos a entrada do modelo e, em seguida, fornecemos uma série de expressões para calcular o seu resultado. Também tornaremos os pesos do modelo BERT não treináveis e treinaremos apenas o classificador final:\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"model\"\n",
"__________________________________________________________________________________________________\n",
"Layer (type) Output Shape Param # Connected to \n",
"==================================================================================================\n",
"input_1 (InputLayer) [(None,)] 0 \n",
"__________________________________________________________________________________________________\n",
"keras_layer (KerasLayer) {'input_type_ids': ( 0 input_1[0][0] \n",
"__________________________________________________________________________________________________\n",
"keras_layer_1 (KerasLayer) {'pooled_output': (N 4782465 keras_layer[0][0] \n",
" keras_layer[0][1] \n",
" keras_layer[0][2] \n",
"__________________________________________________________________________________________________\n",
"dropout_4 (Dropout) (None, 128) 0 keras_layer_1[0][5] \n",
"__________________________________________________________________________________________________\n",
"dense_4 (Dense) (None, 4) 516 dropout_4[0][0] \n",
"==================================================================================================\n",
"Total params: 4,782,981\n",
"Trainable params: 516\n",
"Non-trainable params: 4,782,465\n",
"__________________________________________________________________________________________________\n"
]
}
],
"source": [
"inp = keras.Input(shape=(),dtype=tf.string)\n",
"x = vectorizer(inp)\n",
"x = bert(x)\n",
"x = keras.layers.Dropout(0.1)(x['pooled_output'])\n",
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
"model = keras.models.Model(inp,out)\n",
"bert.trainable = False\n",
"model.summary()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"938/938 [==============================] - 528s 559ms/step - loss: 0.8056 - acc: 0.6983 - val_loss: 0.5953 - val_acc: 0.7888\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f9bb1e36d00>"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Apesar de haver poucos parâmetros treináveis, o processo é bastante lento, porque o extrator de características do BERT é computacionalmente pesado. Parece que não conseguimos alcançar uma precisão razoável, seja por falta de treino ou por insuficiência nos parâmetros do modelo.\n",
"\n",
"Vamos tentar descongelar os pesos do BERT e treiná-lo também. Isto exige uma taxa de aprendizagem muito pequena e uma estratégia de treino mais cuidadosa com **warmup**, utilizando o otimizador **AdamW**. Vamos usar o pacote `tf-models-official` para criar o otimizador:\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"model\"\n",
"__________________________________________________________________________________________________\n",
"Layer (type) Output Shape Param # Connected to \n",
"==================================================================================================\n",
"input_1 (InputLayer) [(None,)] 0 \n",
"__________________________________________________________________________________________________\n",
"keras_layer (KerasLayer) {'input_type_ids': ( 0 input_1[0][0] \n",
"__________________________________________________________________________________________________\n",
"keras_layer_1 (KerasLayer) {'pooled_output': (N 4782465 keras_layer[0][0] \n",
" keras_layer[0][1] \n",
" keras_layer[0][2] \n",
"__________________________________________________________________________________________________\n",
"dropout_4 (Dropout) (None, 128) 0 keras_layer_1[0][5] \n",
"__________________________________________________________________________________________________\n",
"dense_4 (Dense) (None, 4) 516 dropout_4[0][0] \n",
"==================================================================================================\n",
"Total params: 4,782,981\n",
"Trainable params: 4,782,980\n",
"Non-trainable params: 1\n",
"__________________________________________________________________________________________________\n",
"938/938 [==============================] - 629s 664ms/step - loss: 0.6344 - acc: 0.7658 - val_loss: 0.4876 - val_acc: 0.8247\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f9bb0bd0070>"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from official.nlp import optimization \n",
"bert.trainable=True\n",
"model.summary()\n",
"epochs = 3\n",
"opt = optimization.create_optimizer(\n",
" init_lr=3e-5,\n",
" num_train_steps=epochs*len(ds_train),\n",
" num_warmup_steps=0.1*epochs*len(ds_train),\n",
" optimizer_type='adamw')\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer=opt)\n",
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como pode ver, o treino avança de forma bastante lenta - mas pode querer experimentar e treinar o modelo durante algumas épocas (5-10) para ver se consegue obter o melhor resultado em comparação com as abordagens que utilizámos anteriormente.\n",
"\n",
"## Biblioteca Huggingface Transformers\n",
"\n",
"Outra forma muito comum (e um pouco mais simples) de utilizar modelos Transformer é através do [pacote HuggingFace](https://github.com/huggingface/), que fornece blocos de construção simples para diferentes tarefas de PLN. Está disponível tanto para Tensorflow como para PyTorch, outro framework de redes neuronais muito popular.\n",
"\n",
"> **Nota**: Se não estiver interessado em ver como funciona a biblioteca Transformers - pode saltar para o final deste notebook, porque não verá nada substancialmente diferente do que fizemos acima. Estaremos a repetir os mesmos passos de treino do modelo BERT utilizando uma biblioteca diferente e um modelo substancialmente maior. Assim, o processo envolve um treino bastante longo, pelo que pode preferir apenas analisar o código.\n",
"\n",
"Vamos ver como o nosso problema pode ser resolvido utilizando [Huggingface Transformers](http://huggingface.co).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A primeira coisa que precisamos fazer é escolher o modelo que iremos utilizar. Além de alguns modelos integrados, o Huggingface possui um [repositório de modelos online](https://huggingface.co/models), onde pode encontrar muitos mais modelos pré-treinados pela comunidade. Todos esses modelos podem ser carregados e utilizados apenas fornecendo o nome do modelo. Todos os ficheiros binários necessários para o modelo serão automaticamente descarregados.\n",
"\n",
"Em determinados momentos, poderá precisar de carregar os seus próprios modelos, caso em que pode especificar o diretório que contém todos os ficheiros relevantes, incluindo os parâmetros para o tokenizer, o ficheiro `config.json` com os parâmetros do modelo, os pesos binários, etc.\n",
"\n",
"A partir do nome do modelo, podemos instanciar tanto o modelo como o tokenizer. Vamos começar com o tokenizer:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import transformers\n",
"\n",
"# To load the model from Internet repository using model name. \n",
"# Use this if you are running from your own copy of the notebooks\n",
"bert_model = 'bert-base-uncased' \n",
"\n",
"# To load the model from the directory on disk. Use this for Microsoft Learn module, because we have\n",
"# prepared all required files for you.\n",
"#bert_model = './bert'\n",
"\n",
"tokenizer = transformers.BertTokenizer.from_pretrained(bert_model)\n",
"\n",
"MAX_SEQ_LEN = 128\n",
"PAD_INDEX = tokenizer.convert_tokens_to_ids(tokenizer.pad_token)\n",
"UNK_INDEX = tokenizer.convert_tokens_to_ids(tokenizer.unk_token)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O objeto `tokenizer` contém a função `encode` que pode ser usada diretamente para codificar texto:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[101, 23435, 12314, 2003, 1037, 2307, 7705, 2005, 17953, 2361, 102]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer.encode('Tensorflow is a great framework for NLP')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos também usar o tokenizer para codificar uma sequência de uma forma adequada para passar ao modelo, ou seja, incluindo os campos `token_ids`, `input_mask`, etc. Podemos também especificar que queremos tensores do Tensorflow ao fornecer o argumento `return_tensors='tf'`:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'input_ids': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[ 101, 7592, 1010, 2045, 102]], dtype=int32)>, 'token_type_ids': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[0, 0, 0, 0, 0]], dtype=int32)>, 'attention_mask': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[1, 1, 1, 1, 1]], dtype=int32)>}"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tokenizer(['Hello, there'],return_tensors='tf')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No nosso caso, iremos utilizar um modelo BERT pré-treinado chamado `bert-base-uncased`. *Uncased* indica que o modelo não diferencia entre maiúsculas e minúsculas.\n",
"\n",
"Ao treinar o modelo, precisamos fornecer uma sequência tokenizada como entrada e, por isso, iremos projetar um pipeline de processamento de dados. Como `tokenizer.encode` é uma função em Python, utilizaremos a mesma abordagem da última unidade, chamando-a através de `py_function`:\n"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [],
"source": [
"def process(x):\n",
" return tokenizer.encode(x.numpy().decode('utf-8'),return_tensors='tf',padding='max_length',max_length=MAX_SEQ_LEN,truncation=True)[0]\n",
"\n",
"def process_fn(x):\n",
" s = x['title']+' '+x['description']\n",
" e = tf.py_function(process,inp=[s],Tout=(tf.int32))\n",
" e.set_shape(MAX_SEQ_LEN)\n",
" return e,x['label']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora podemos carregar o modelo real usando o pacote `BertForSequenceClassification`. Isto garante que o nosso modelo já tem a arquitetura necessária para classificação, incluindo o classificador final. Verás uma mensagem de aviso indicando que os pesos do classificador final não estão inicializados e que o modelo precisará de pré-treino - isso é perfeitamente normal, porque é exatamente o que estamos prestes a fazer!\n"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [],
"source": [
"model = transformers.TFBertForSequenceClassification.from_pretrained(bert_model,num_labels=4,output_attentions=False)"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"tf_bert_for_sequence_classification_1\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"bert (TFBertMainLayer) multiple 109482240 \n",
"_________________________________________________________________\n",
"dropout_75 (Dropout) multiple 0 \n",
"_________________________________________________________________\n",
"classifier (Dense) multiple 3076 \n",
"=================================================================\n",
"Total params: 109,485,316\n",
"Trainable params: 109,485,316\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como pode ver em `summary()`, o modelo contém quase 110 milhões de parâmetros! Presumivelmente, se quisermos uma tarefa de classificação simples num conjunto de dados relativamente pequeno, não queremos treinar a camada base do BERT:\n"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"tf_bert_for_sequence_classification_1\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"bert (TFBertMainLayer) multiple 109482240 \n",
"_________________________________________________________________\n",
"dropout_75 (Dropout) multiple 0 \n",
"_________________________________________________________________\n",
"classifier (Dense) multiple 3076 \n",
"=================================================================\n",
"Total params: 109,485,316\n",
"Trainable params: 3,076\n",
"Non-trainable params: 109,482,240\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"model.layers[0].trainable = False\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora estamos prontos para começar o treino!\n",
"\n",
"> **Nota**: Treinar um modelo BERT completo pode ser extremamente demorado! Por isso, vamos treiná-lo apenas para as primeiras 32 batches. Isto serve apenas para demonstrar como o treino do modelo é configurado. Se estiver interessado em experimentar o treino completo, basta remover os parâmetros `steps_per_epoch` e `validation_steps`, e prepare-se para esperar!\n"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"32/32 [==============================] - 142s 4s/step - loss: 1.3896 - acc: 0.2500 - val_loss: 1.3863 - val_acc: 0.2480\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f1d40a4b6a0>"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.compile('adam','sparse_categorical_crossentropy',['acc'])\n",
"tf.get_logger().setLevel('ERROR')\n",
"model.fit(ds_train.map(process_fn).batch(32),validation_data=ds_test.map(process_fn).batch(32),steps_per_epoch=32,validation_steps=2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Se aumentar o número de iterações e esperar o tempo necessário, e treinar durante vários epochs, pode esperar que a classificação com BERT nos dê a melhor precisão! Isto deve-se ao facto de o BERT já compreender bastante bem a estrutura da linguagem, sendo necessário apenas ajustar o classificador final. No entanto, como o BERT é um modelo grande, todo o processo de treino demora bastante tempo e exige uma capacidade computacional significativa! (GPU, e de preferência mais do que uma).\n",
"\n",
"> **Note:** No nosso exemplo, temos utilizado um dos modelos BERT pré-treinados mais pequenos. Existem modelos maiores que provavelmente produzirão melhores resultados.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusão\n",
"\n",
"Nesta unidade, vimos arquiteturas de modelos muito recentes baseadas em **transformers**. Aplicámo-las à nossa tarefa de classificação de texto, mas, de forma semelhante, os modelos BERT podem ser usados para extração de entidades, resposta a perguntas e outras tarefas de PLN.\n",
"\n",
"Os modelos transformer representam o estado da arte atual em PLN e, na maioria dos casos, devem ser a primeira solução a experimentar ao implementar soluções personalizadas de PLN. No entanto, compreender os princípios básicos subjacentes às redes neuronais recorrentes discutidos neste módulo é extremamente importante se quiseres construir modelos neuronais avançados.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante ter em conta que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
},
"kernelspec": {
"display_name": "py38_tensorflow",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "ab59c532409774988ab875f2260e8e53",
"translation_date": "2025-08-31T11:58:56+00:00",
"source_file": "lessons/5-NLP/18-Transformers/TransformersTF.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,6 @@
# Tarefa: Transformers
Experimente os Transformers no HuggingFace! Teste alguns dos scripts que eles fornecem para trabalhar com os vários modelos disponíveis no site: https://huggingface.co/docs/transformers/run_scripts. Experimente um dos seus conjuntos de dados, depois importe um dos seus próprios deste currículo ou do Kaggle e veja se consegue gerar textos interessantes. Produza um notebook com as suas descobertas.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,492 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Reconhecimento de Entidades Nomeadas (NER)\n",
"\n",
"Este notebook faz parte do [Currículo de IA para Iniciantes](http://aka.ms/ai-beginners).\n",
"\n",
"Neste exemplo, vamos aprender a treinar um modelo de NER no conjunto de dados [Corpus Anotado para Reconhecimento de Entidades Nomeadas](https://www.kaggle.com/datasets/abhinavwalia95/entity-annotated-corpus) do Kaggle. Antes de prosseguir, faça o download do ficheiro [ner_dataset.csv](https://www.kaggle.com/datasets/abhinavwalia95/entity-annotated-corpus?resource=download&select=ner_dataset.csv) para o diretório atual.\n"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"from tensorflow import keras\n",
"import numpy as np"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Preparar o Conjunto de Dados\n",
"\n",
"Vamos começar por ler o conjunto de dados para um dataframe. Se quiser aprender mais sobre como usar o Pandas, visite uma [lição sobre processamento de dados](https://github.com/microsoft/Data-Science-For-Beginners/tree/main/2-Working-With-Data/07-python) no nosso [Ciência de Dados para Principiantes](http://aka.ms/datascience-beginners)\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Sentence #</th>\n",
" <th>Word</th>\n",
" <th>POS</th>\n",
" <th>Tag</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>Sentence: 1</td>\n",
" <td>Thousands</td>\n",
" <td>NNS</td>\n",
" <td>O</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>NaN</td>\n",
" <td>of</td>\n",
" <td>IN</td>\n",
" <td>O</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>NaN</td>\n",
" <td>demonstrators</td>\n",
" <td>NNS</td>\n",
" <td>O</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>NaN</td>\n",
" <td>have</td>\n",
" <td>VBP</td>\n",
" <td>O</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>NaN</td>\n",
" <td>marched</td>\n",
" <td>VBN</td>\n",
" <td>O</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Sentence # Word POS Tag\n",
"0 Sentence: 1 Thousands NNS O\n",
"1 NaN of IN O\n",
"2 NaN demonstrators NNS O\n",
"3 NaN have VBP O\n",
"4 NaN marched VBN O"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.read_csv('ner_dataset.csv',encoding='unicode-escape')\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos obter etiquetas únicas e criar dicionários de pesquisa que podemos usar para converter etiquetas em números de classe:\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array(['O', 'B-geo', 'B-gpe', 'B-per', 'I-geo', 'B-org', 'I-org', 'B-tim',\n",
" 'B-art', 'I-art', 'I-per', 'I-gpe', 'I-tim', 'B-nat', 'B-eve',\n",
" 'I-eve', 'I-nat'], dtype=object)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tags = df.Tag.unique()\n",
"tags"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'O'"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"id2tag = dict(enumerate(tags))\n",
"tag2id = { v : k for k,v in id2tag.items() }\n",
"\n",
"id2tag[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora precisamos fazer o mesmo com o vocabulário. Para simplificar, iremos criar o vocabulário sem levar em conta a frequência das palavras; na vida real, poderá querer usar o vetorizador do Keras e limitar o número de palavras.\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"vocab = set(df['Word'].apply(lambda x: x.lower()))\n",
"id2word = { i+1 : v for i,v in enumerate(vocab) }\n",
"id2word[0] = '<UNK>'\n",
"vocab.add('<UNK>')\n",
"word2id = { v : k for k,v in id2word.items() }"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Precisamos criar um conjunto de dados de frases para treino. Vamos percorrer o conjunto de dados original e separar todas as frases individuais em `X` (listas de palavras) e `Y` (lista de tokens):\n"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [],
"source": [
"X,Y = [],[]\n",
"s,t = [],[]\n",
"for i,row in df[['Sentence #','Word','Tag']].iterrows():\n",
" if pd.isna(row['Sentence #']):\n",
" s.append(row['Word'])\n",
" t.append(row['Tag'])\n",
" else:\n",
" if len(s)>0:\n",
" X.append(s)\n",
" Y.append(t)\n",
" s,t = [row['Word']],[row['Tag']]\n",
"X.append(s)\n",
"Y.append(t)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"([10386,\n",
" 23515,\n",
" 4134,\n",
" 29620,\n",
" 7954,\n",
" 13583,\n",
" 21193,\n",
" 12222,\n",
" 27322,\n",
" 18258,\n",
" 5815,\n",
" 15880,\n",
" 5355,\n",
" 25242,\n",
" 31327,\n",
" 18258,\n",
" 27067,\n",
" 23515,\n",
" 26444,\n",
" 14412,\n",
" 358,\n",
" 26551,\n",
" 5011,\n",
" 30558],\n",
" [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0])"
]
},
"execution_count": 93,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def vectorize(seq):\n",
" return [word2id[x.lower()] for x in seq]\n",
"\n",
"def tagify(seq):\n",
" return [tag2id[x] for x in seq]\n",
"\n",
"Xv = list(map(vectorize,X))\n",
"Yv = list(map(tagify,Y))\n",
"\n",
"Xv[0], Yv[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para simplicidade, iremos preencher todas as frases com 0 tokens até ao comprimento máximo. Na vida real, poderíamos querer usar uma estratégia mais inteligente e preencher sequências apenas dentro de um minibatch.\n"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {},
"outputs": [],
"source": [
"X_data = keras.preprocessing.sequence.pad_sequences(Xv,padding='post')\n",
"Y_data = keras.preprocessing.sequence.pad_sequences(Yv,padding='post')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Definir Rede de Classificação de Tokens\n",
"\n",
"Vamos utilizar uma rede bidirecional LSTM de duas camadas para a classificação de tokens. Para aplicar um classificador denso a cada saída da última camada LSTM, usaremos a construção `TimeDistributed`, que replica a mesma camada densa para cada uma das saídas da LSTM em cada etapa:\n"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential_3\"\n",
"_________________________________________________________________\n",
" Layer (type) Output Shape Param # \n",
"=================================================================\n",
" embedding_4 (Embedding) (None, 104, 300) 9545400 \n",
" \n",
" bidirectional_6 (Bidirectio (None, 104, 200) 320800 \n",
" nal) \n",
" \n",
" bidirectional_7 (Bidirectio (None, 104, 200) 240800 \n",
" nal) \n",
" \n",
" time_distributed_3 (TimeDis (None, 104, 17) 3417 \n",
" tributed) \n",
" \n",
"=================================================================\n",
"Total params: 10,110,417\n",
"Trainable params: 10,110,417\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"maxlen = X_data.shape[1]\n",
"vocab_size = len(vocab)\n",
"num_tags = len(tags)\n",
"model = keras.models.Sequential([\n",
" keras.layers.Embedding(vocab_size, 300, input_length=maxlen),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(units=100, activation='tanh', return_sequences=True)),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(units=100, activation='tanh', return_sequences=True)),\n",
" keras.layers.TimeDistributed(keras.layers.Dense(num_tags, activation='softmax'))\n",
"])\n",
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note aqui que estamos a especificar explicitamente `maxlen` para o nosso conjunto de dados - caso queiramos que a rede consiga lidar com sequências de comprimento variável, precisamos de ser um pouco mais engenhosos ao definir a rede.\n",
"\n",
"Vamos agora treinar o modelo. Para acelerar o processo, iremos treinar apenas por uma época, mas pode experimentar treinar por mais tempo. Além disso, pode querer separar uma parte do conjunto de dados como conjunto de treino, para observar a precisão da validação.\n"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1499/1499 [==============================] - 740s 488ms/step - loss: 0.0667 - acc: 0.9841\n"
]
},
{
"data": {
"text/plain": [
"<keras.callbacks.History at 0x16f0bb2a310>"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.fit(X_data,Y_data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Testar o Resultado\n",
"\n",
"Vamos agora ver como o nosso modelo de reconhecimento de entidades funciona numa frase de exemplo:\n"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {},
"outputs": [],
"source": [
"sent = 'John Smith went to Paris to attend a conference in cancer development institute'\n",
"words = sent.lower().split()\n",
"v = keras.preprocessing.sequence.pad_sequences([[word2id[x] for x in words]],padding='post',maxlen=maxlen)\n",
"res = model(v)[0]"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"john -> B-per\n",
"smith -> I-per\n",
"went -> O\n",
"to -> O\n",
"paris -> B-geo\n",
"to -> O\n",
"attend -> O\n",
"a -> O\n",
"conference -> O\n",
"in -> O\n",
"cancer -> B-org\n",
"development -> I-org\n",
"institute -> I-org\n"
]
}
],
"source": [
"r = np.argmax(res.numpy(),axis=1)\n",
"for i,w in zip(r,words):\n",
" print(f\"{w} -> {id2tag[i]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusão\n",
"\n",
"Mesmo um modelo LSTM simples apresenta resultados razoáveis em NER. No entanto, para obter resultados muito melhores, pode ser útil utilizar grandes modelos de linguagem pré-treinados, como o BERT. O treino do BERT para NER utilizando a biblioteca Huggingface Transformers está descrito [aqui](https://huggingface.co/course/chapter7/2?fw=pt).\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"orig_nbformat": 4,
"coopTranslator": {
"original_hash": "254d25052dcca4ef84f59a05f2935bdc",
"translation_date": "2025-08-31T11:59:36+00:00",
"source_file": "lessons/5-NLP/19-NER/NER-TF.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,85 @@
# Reconhecimento de Entidades Nomeadas
Até agora, temos concentrado principalmente numa tarefa de PNL - classificação. No entanto, existem outras tarefas de PNL que podem ser realizadas com redes neuronais. Uma dessas tarefas é o **[Reconhecimento de Entidades Nomeadas](https://wikipedia.org/wiki/Named-entity_recognition)** (NER), que trata de reconhecer entidades específicas dentro de um texto, como lugares, nomes de pessoas, intervalos de data e hora, fórmulas químicas, entre outros.
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/37)
## Exemplo de Utilização do NER
Suponha que deseja desenvolver um chatbot de linguagem natural, semelhante ao Amazon Alexa ou Google Assistant. A forma como os chatbots inteligentes funcionam é *entender* o que o utilizador quer, classificando a frase de entrada. O resultado dessa classificação é o chamado **intento**, que determina o que o chatbot deve fazer.
<img alt="Bot NER" src="../../../../../translated_images/pt-PT/bot-ner.4b09235dbb0ad275.webp" width="50%"/>
> Imagem do autor
No entanto, o utilizador pode fornecer alguns parâmetros como parte da frase. Por exemplo, ao perguntar sobre o tempo, pode especificar uma localização ou uma data. Um bot deve ser capaz de entender essas entidades e preencher os parâmetros adequadamente antes de executar a ação. É exatamente aqui que o NER entra em ação.
> ✅ Outro exemplo seria [analisar artigos científicos médicos](https://soshnikov.com/science/analyzing-medical-papers-with-azure-and-text-analytics-for-health/). Um dos principais aspetos a procurar são termos médicos específicos, como doenças e substâncias médicas. Enquanto um pequeno número de doenças pode ser extraído através de pesquisa por subcadeias, entidades mais complexas, como compostos químicos e nomes de medicamentos, requerem uma abordagem mais sofisticada.
## NER como Classificação de Tokens
Os modelos de NER são essencialmente **modelos de classificação de tokens**, porque para cada token de entrada precisamos decidir se pertence a uma entidade ou não, e, caso pertença, a qual classe de entidade.
Considere o seguinte título de artigo:
**Regurgitação da válvula tricúspide** e **carbonato de lítio** **toxicidade** em um recém-nascido.
As entidades aqui são:
* Regurgitação da válvula tricúspide é uma doença (`DIS`)
* Carbonato de lítio é uma substância química (`CHEM`)
* Toxicidade também é uma doença (`DIS`)
Note que uma entidade pode abranger vários tokens. E, como neste caso, precisamos distinguir entre duas entidades consecutivas. Assim, é comum usar duas classes para cada entidade - uma especificando o primeiro token da entidade (frequentemente o prefixo `B-` é usado, para **b**eginning), e outra para a continuação da entidade (`I-`, para **i**nner token). Usamos também `O` como classe para representar todos os **o**utros tokens. Essa marcação de tokens é chamada de [marcação BIO](https://en.wikipedia.org/wiki/Inside%E2%80%93outside%E2%80%93beginning_(tagging)) (ou IOB). Quando marcada, o nosso título ficará assim:
Token | Tag
------|-----
Tricuspid | B-DIS
valve | I-DIS
regurgitation | I-DIS
and | O
lithium | B-CHEM
carbonate | I-CHEM
toxicity | B-DIS
in | O
a | O
newborn | O
infant | O
. | O
Como precisamos construir uma correspondência um-para-um entre tokens e classes, podemos treinar um modelo de rede neural **muitos-para-muitos** da seguinte forma:
![Imagem mostrando padrões comuns de redes neuronais recorrentes.](../../../../../translated_images/pt-PT/unreasonable-effectiveness-of-rnn.541ead816778f42d.webp)
> *Imagem retirada [deste artigo](http://karpathy.github.io/2015/05/21/rnn-effectiveness/) por [Andrej Karpathy](http://karpathy.github.io/). Os modelos de classificação de tokens NER correspondem à arquitetura de rede mais à direita nesta imagem.*
## Treinar Modelos de NER
Como um modelo de NER é essencialmente um modelo de classificação de tokens, podemos usar RNNs, com os quais já estamos familiarizados, para esta tarefa. Neste caso, cada bloco da rede recorrente retornará o ID do token. O seguinte notebook de exemplo mostra como treinar um LSTM para classificação de tokens.
## ✍️ Notebooks de Exemplo: NER
Continue a sua aprendizagem no seguinte notebook:
* [NER com TensorFlow](NER-TF.ipynb)
## Conclusão
Um modelo de NER é um **modelo de classificação de tokens**, o que significa que pode ser usado para realizar classificação de tokens. Esta é uma tarefa muito comum em PNL, ajudando a reconhecer entidades específicas dentro de um texto, incluindo lugares, nomes, datas e mais.
## 🚀 Desafio
Complete o exercício abaixo para treinar um modelo de reconhecimento de entidades nomeadas para termos médicos e, em seguida, experimente-o num conjunto de dados diferente.
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/38)
## Revisão e Estudo Individual
Leia o artigo [The Unreasonable Effectiveness of Recurrent Neural Networks](http://karpathy.github.io/2015/05/21/rnn-effectiveness/) e siga a secção de Leitura Adicional nesse artigo para aprofundar o seu conhecimento.
## [Exercício](lab/README.md)
No exercício desta aula, terá de treinar um modelo de reconhecimento de entidades médicas. Pode começar por treinar um modelo LSTM, como descrito nesta aula, e depois avançar para usar o modelo transformer BERT. Leia [as instruções](lab/README.md) para obter todos os detalhes.
---

View File

@ -0,0 +1,50 @@
# NER
Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).
## Tarefa
Neste laboratório, precisas de treinar um modelo de reconhecimento de entidades nomeadas (NER) para termos médicos.
## O Conjunto de Dados
Para treinar o modelo NER, precisamos de um conjunto de dados devidamente etiquetado com entidades médicas. O [conjunto de dados BC5CDR](https://biocreative.bioinformatics.udel.edu/tasks/biocreative-v/track-3-cdr/) contém entidades de doenças e químicos etiquetadas a partir de mais de 1500 artigos. Podes descarregar o conjunto de dados após te registares no site deles.
O conjunto de dados BC5CDR tem o seguinte aspeto:
```
6794356|t|Tricuspid valve regurgitation and lithium carbonate toxicity in a newborn infant.
6794356|a|A newborn with massive tricuspid regurgitation, atrial flutter, congestive heart failure, and a high serum lithium level is described. This is the first patient to initially manifest tricuspid regurgitation and atrial flutter, and the 11th described patient with cardiac disease among infants exposed to lithium compounds in the first trimester of pregnancy. Sixty-three percent of these infants had tricuspid valve involvement. Lithium carbonate may be a factor in the increasing incidence of congenital heart disease when taken during early pregnancy. It also causes neurologic depression, cyanosis, and cardiac arrhythmia when consumed prior to delivery.
6794356 0 29 Tricuspid valve regurgitation Disease D014262
6794356 34 51 lithium carbonate Chemical D016651
6794356 52 60 toxicity Disease D064420
...
```
Neste conjunto de dados, o título e o resumo do artigo estão nas duas primeiras linhas, seguidos pelas entidades individuais, com as posições de início e fim dentro do bloco título+resumo. Além do tipo de entidade, obténs o ID de ontologia dessa entidade dentro de uma ontologia médica.
Precisarás de escrever algum código em Python para converter isto para a codificação BIO.
## A Rede
A primeira tentativa de NER pode ser feita utilizando uma rede LSTM, como no exemplo que viste durante a aula. No entanto, em tarefas de PLN, a [arquitetura transformer](https://en.wikipedia.org/wiki/Transformer_(machine_learning_model)), e especificamente os [modelos de linguagem BERT](https://en.wikipedia.org/wiki/BERT_(language_model)), apresentam resultados muito melhores. Modelos BERT pré-treinados compreendem a estrutura geral de uma linguagem e podem ser ajustados para tarefas específicas com conjuntos de dados relativamente pequenos e custos computacionais reduzidos.
Como planeamos aplicar NER a um cenário médico, faz sentido usar um modelo BERT treinado em textos médicos. A Microsoft Research lançou um modelo pré-treinado chamado [PubMedBERT][PubMedBERT] ([publicação][PubMedBERT-Pub]), que foi ajustado utilizando textos do repositório [PubMed](https://pubmed.ncbi.nlm.nih.gov/).
O padrão *de facto* para treinar modelos transformer é a biblioteca [Hugging Face Transformers](https://huggingface.co/). Esta também contém um repositório de modelos pré-treinados mantidos pela comunidade, incluindo o PubMedBERT. Para carregar e usar este modelo, só precisas de algumas linhas de código:
```python
model_name = "microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract"
classes = ... # number of classes: 2*entities+1
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = BertForTokenClassification.from_pretrained(model_name, classes)
```
Isto fornece-nos o `model` propriamente dito, construído para a tarefa de classificação de tokens usando `classes` número de classes, bem como o objeto `tokenizer` que pode dividir o texto de entrada em tokens. Precisarás de converter o conjunto de dados para o formato BIO, tendo em conta a tokenização do PubMedBERT. Podes usar [este trecho de código Python](https://gist.github.com/shwars/580b55684be3328eb39ecf01b9cbbd88) como inspiração.
## Conclusão
Esta tarefa é muito próxima da tarefa real que provavelmente terás se quiseres obter mais insights sobre grandes volumes de textos em linguagem natural. No nosso caso, podemos aplicar o nosso modelo treinado ao [conjunto de dados de artigos relacionados com a COVID](https://www.kaggle.com/allen-institute-for-ai/CORD-19-research-challenge) e ver que insights conseguimos obter. [Este artigo de blog](https://soshnikov.com/science/analyzing-medical-papers-with-azure-and-text-analytics-for-health/) e [este artigo científico](https://www.mdpi.com/2504-2289/6/1/4) descrevem a investigação que pode ser feita neste corpus de artigos utilizando NER.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos pela precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,325 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Experimentar com o OpenAI GPT\n",
"\n",
"Este notebook faz parte do [Currículo de IA para Iniciantes](http://aka.ms/ai-beginners).\n",
"\n",
"Neste notebook, vamos explorar como podemos utilizar o modelo OpenAI-GPT com a biblioteca `transformers` da Hugging Face.\n",
"\n",
"Sem mais demoras, vamos instanciar o pipeline de geração de texto e começar a gerar!\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\bethanycheum\\Desktop\\AI-For-Beginners\\.venv\\lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
" from .autonotebook import tqdm as notebook_tqdm\n",
"Downloading model.safetensors: 100%|██████████| 479M/479M [04:28<00:00, 1.78MB/s] \n",
"c:\\Users\\bethanycheum\\Desktop\\AI-For-Beginners\\.venv\\lib\\site-packages\\huggingface_hub\\file_download.py:133: UserWarning: `huggingface_hub` cache-system uses symlinks by default to efficiently store duplicated files but your machine does not support them in C:\\Users\\bethanycheum\\.cache\\huggingface\\hub. Caching files will still work but in a degraded version that might require more space on your disk. This warning can be disabled by setting the `HF_HUB_DISABLE_SYMLINKS_WARNING` environment variable. For more details, see https://huggingface.co/docs/huggingface_hub/how-to-cache#limitations.\n",
"To support symlinks on Windows, you either need to activate Developer Mode or to run Python as an administrator. In order to see activate developer mode, see this article: https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development\n",
" warnings.warn(message)\n",
"Some weights of OpenAIGPTLMHeadModel were not initialized from the model checkpoint at openai-gpt and are newly initialized: ['position_ids']\n",
"You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n",
"Downloading (…)neration_config.json: 100%|██████████| 74.0/74.0 [00:00<00:00, 48.8kB/s]\n",
"Downloading (…)olve/main/vocab.json: 100%|██████████| 816k/816k [00:00<00:00, 1.76MB/s]\n",
"Downloading (…)olve/main/merges.txt: 100%|██████████| 458k/458k [00:00<00:00, 1.11MB/s]\n",
"Downloading (…)/main/tokenizer.json: 100%|██████████| 1.27M/1.27M [00:00<00:00, 2.12MB/s]\n",
"Xformers is not installed correctly. If you want to use memory_efficient_attention to accelerate training use the following command to install Xformers\n",
"pip install xformers.\n"
]
},
{
"data": {
"text/plain": [
"[{'generated_text': \"Hello! I am a neural network, and I want to say that i apologize for not coming to you yourself, for not helping you, and that i was too busy getting dressed and studying for a midterm. you know, the kind where the teachers are like that and they come in pairs with their boyfriends, but not with theirs. it's true, that i have had a girlfriend, and i'm only going on wednesdays and thursdays because i was too busy with college, but maybe\"},\n",
" {'generated_text': 'Hello! I am a neural network, and I want to say that we have been blessed with a wonderful gift ; no one of us has died at all. and our spirits are strong, very strong. in one very lucky moment of luck for you, all has been given direction and destiny, and for us there are no more mysteries. the earth has been chosen for you, and that earth is now ours, and you must be forever in our hearts. \" \\n the words, as one,'},\n",
" {'generated_text': 'Hello! I am a neural network, and I want to say that if you would just turn and face the general, you would have a nice day. \" \\n \" sure thing, \" said one of the soldiers, and started to run. the rest of the soldiers followed, shouting. the general turned to general zulu, raising his arm. the general said something in his native language, and the general immediately started to run. zulu started to move toward the wall, with the'},\n",
" {'generated_text': 'Hello! I am a neural network, and I want to say that i am not a doctor but an anthropologist to you, a specialist, a specialist in the field of astrobiological biology, and that i am very much involved in this investigation. i am not sure, i am not certain, but i can confirm your conclusions and therefore i will go to the top. i have a colleague who has just returned from this expedition and his findings confirm that you are a specialist. that is, he'},\n",
" {'generated_text': \"Hello! I am a neural network, and I want to say that everyone here is in agreement that no matter how many times i say to myself,'he was never a man of action on the battlefield,'or'he 'll never take a chance at killing any civilians,'or'he 'll never let his men go undefended against enemy forces of this caliber,'or'that's just what i need in a day like today. \\n you see, there are only three groups that\"}]"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from transformers import pipeline\n",
"\n",
"model_name = 'openai-gpt' \n",
"\n",
"generator = pipeline('text-generation', model=model_name)\n",
"\n",
"generator(\"Hello! I am a neural network, and I want to say that\", max_length=100, num_return_sequences=5)\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Engenharia de Prompts\n",
"\n",
"Em alguns problemas, pode utilizar a geração do openai-gpt diretamente ao criar prompts adequados. Veja os exemplos abaixo:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'generated_text': 'Synonyms of a word cat: the same cat i used to stare at, and you in'},\n",
" {'generated_text': 'Synonyms of a word cat: cat of the woods, cat of the hills, cat of'},\n",
" {'generated_text': 'Synonyms of a word cat: you! \\n \" it\\'s a girl. \" i said'},\n",
" {'generated_text': \"Synonyms of a word cat: big cat. but how come, we didn't hear it\"},\n",
" {'generated_text': 'Synonyms of a word cat: \" mea - o - c \" which makes them sound'}]"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"generator(\"Synonyms of a word cat:\", max_length=20, num_return_sequences=5)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'generated_text': 'I love when you say this -> Positive\\nI have myself -> Negative\\nThis is awful for you to say this -> positive this is so horrible - > positive that your brother is gay - >'},\n",
" {'generated_text': 'I love when you say this -> Positive\\nI have myself -> Negative\\nThis is awful for you to say this -> negative i will bring this on you -, < positive am i, i'},\n",
" {'generated_text': 'I love when you say this -> Positive\\nI have myself -> Negative\\nThis is awful for you to say this -> negative i have self - esteem i must take it - : \\n - -'},\n",
" {'generated_text': 'I love when you say this -> Positive\\nI have myself -> Negative\\nThis is awful for you to say this -> negative this is - : \\n if it were true that the devil would have'},\n",
" {'generated_text': \"I love when you say this -> Positive\\nI have myself -> Negative\\nThis is awful for you to say this -> positive i have you - > positive it's a bad thing, > positive\"}]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"generator(\"I love when you say this -> Positive\\nI have myself -> Negative\\nThis is awful for you to say this ->\", max_length=40, num_return_sequences=5)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'generated_text': 'Translate English to French: cat => chat, dog => chien, student => new and unusual. there were no more words to be'},\n",
" {'generated_text': 'Translate English to French: cat => chat, dog => chien, student => student \\n his eyes were huge in his lean face as'},\n",
" {'generated_text': \"Translate English to French: cat => chat, dog => chien, student => the teacher's words, their words, their words.\"}]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"generator(\"Translate English to French: cat => chat, dog => chien, student => \", top_k=50, max_length=30, num_return_sequences=3)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'generated_text': 'People who liked the movie The Matrix also liked it, and there was the movie of the first man after us. \\n i wanted to laugh at how stupid these stupid actors were. no, they were'},\n",
" {'generated_text': \"People who liked the movie The Matrix also liked the movie, and the film was the result. and that's when the man in the story was brought into reality, after a few decades. \\n a\"},\n",
" {'generated_text': 'People who liked the movie The Matrix also liked the movie the matrix, because there was a very old movie movie called the matrix, where there was a great super hero, and the super hero came out'},\n",
" {'generated_text': \"People who liked the movie The Matrix also liked the movie that didn't have a chance to pay cash, if they could afford it. most often they got a good deal and a lot of money,\"},\n",
" {'generated_text': \"People who liked the movie The Matrix also liked the movie, and i didn't seem to have the same problem. \\n i 'd met the other half of my family. i spent most of my time\"}]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"generator(\"People who liked the movie The Matrix also liked \", max_length=40, num_return_sequences=5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Estratégias de Amostragem de Texto\n",
"\n",
"Até agora, temos utilizado uma estratégia de amostragem **gananciosa** simples, onde selecionamos a próxima palavra com base na maior probabilidade. Aqui está como funciona:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw my friend, a young man, sprawled across the bed in his bed. \\n \" hi, i\\'m mike eptirard. \" \\n there was silence on the other side of the door. i listened for any trace of life but there was nothing. my heart began to pound, i was starting to sweat, i took out my wallet'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw my mother on the bed, hugging her legs to her chest and sobbing. i saw my dad and mother from the corner of my eye. \\n elfin face was covered in tears as i entered the room. my dad and mother also wept ; just as they did every other time i came to work. but this time, they had different faces'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw the room had changed because it was dark. it still smelled like a hospital. a new light shined through from a vent in the ceiling. i found myself in a bathroom and a small room with a sink and a wall of glass. the bathroom billion years ago. not so different from all of the rest of the apartment. \\n now...'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw a large woman with dark hair and pale skin. she was asleep, but i noticed a faint movement of her face. i could sense she was awake. i got up and walked over to her. \\n \" hello miss. i am inspector michael o\\'dell ; we are investigating the case against you. i wanted to ask if you were the'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw i had an empty table and three empty chairs. that was all i needed. i had left a note on a table in the center of the room and had a pen in hand. \" \\n \" i think what you were doing was something he was doing to her. \" \\n \" yeah, \" i nodded with a grin. \" i'}]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt = \"It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw\"\n",
"generator(prompt,max_length=100,num_return_sequences=5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Beam Search** permite ao gerador explorar várias direções (*beams*) de geração de texto e selecionar aquelas com maior pontuação geral. Pode realizar beam search fornecendo o parâmetro `num_beams`. Também pode especificar `no_repeat_ngram_size` para penalizar o modelo por repetir n-grams de um determinado tamanho:\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw a man sitting in a chair with his head in his hands. he didn\\'t look up as i approached. \\n \" excuse me, sir, \" i said. \" can i help you? \" \\n the man looked up at me. his eyes were red - rimmed and his face was pale, as if he hadn\\'t slept in days'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw a man sitting at a desk in the middle of the room. he had his back to me, so i couldn\\'t see what he was doing. \" \\n \" what did he look like? \" i asked as i sat down on the bed next to her. \\n she took a deep breath and looked at me with tears in her eyes'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw a woman sitting on the bed, reading a book. she looked up at me and smiled. \\n \" hi, \" she said. \" can i help you? \" \\n i sat down next to her and looked around the room. the walls were white, and there was a large window in the middle of the wall that looked out on'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw a man sitting at a table in the middle of the room. he looked up as i walked in, and when he saw me, he got up and walked over to me. \\n \" can i help you? \" he asked as he put his hand on the small of my back and led me to a chair at the other end of'},\n",
" {'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw a woman sitting on the edge of her bed, reading a book. she looked up at me and smiled. \\n \" hello, \" she said. \" can i help you? \" \\n i didn\\'t know what to say, so i just sat down in the chair next to the bed and looked at her. her hair was dark brown'}]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt = \"It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw\"\n",
"generator(prompt,max_length=100,num_return_sequences=5,num_beams=10,no_repeat_ngram_size=2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Amostragem** seleciona a próxima palavra de forma não determinística, utilizando a distribuição de probabilidade retornada pelo modelo. Ativa-se a amostragem utilizando o parâmetro `do_sample=True`. Também pode especificar a `temperature` para tornar o modelo mais ou menos determinístico.\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'generated_text': 'It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw her. she was on the bed, but she looked very different. \\n \" honey, what\\'s the matter? \" i asked. \\n she sat up. \" i can\\'t believe it\\'s real. i\\'ve been dreaming about you for the last two days. \" \\n \" i can\\'t believe it either. i guess that\\'s how'}]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"prompt = \"It was early evening when I can back from work. I usually work late, but this time it was an exception. When I entered a room, I saw\"\n",
"generator(prompt,max_length=100,do_sample=True,temperature=0.8)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos também fornecer parâmetros adicionais para a amostragem: \n",
"* `top_k` especifica o número de opções de palavras a considerar ao usar a amostragem. Isto minimiza a probabilidade de obter palavras estranhas (de baixa probabilidade) no nosso texto. \n",
"* `top_p` é semelhante, mas escolhemos o menor subconjunto de palavras mais prováveis, cuja probabilidade total seja maior que p. \n",
"\n",
"Sinta-se à vontade para experimentar adicionar esses parâmetros. \n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ajustar os seus modelos\n",
"\n",
"Também pode [ajustar o seu modelo](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/fine-tuning?pivots=programming-language-studio?WT.mc_id=academic-77998-bethanycheum) com base no seu próprio conjunto de dados. Isto permitirá que adapte o estilo do texto, mantendo a maior parte do modelo de linguagem.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.11"
},
"orig_nbformat": 4,
"coopTranslator": {
"original_hash": "d4ff89615d38924a55594f16d6d20678",
"translation_date": "2025-08-31T11:59:13+00:00",
"source_file": "lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,55 @@
# Modelos de Linguagem Pré-Treinados
Em todas as nossas tarefas anteriores, treinámos uma rede neural para realizar uma determinada tarefa utilizando um conjunto de dados rotulado. Com modelos transformadores grandes, como o BERT, utilizamos modelagem de linguagem de forma auto-supervisionada para construir um modelo de linguagem, que é então especializado para uma tarefa específica com treino adicional orientado ao domínio. No entanto, foi demonstrado que modelos de linguagem grandes também podem resolver muitas tarefas sem QUALQUER treino específico de domínio. Uma família de modelos capaz de fazer isso é chamada de **GPT**: Transformador Generativo Pré-Treinado.
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/39)
## Geração de Texto e Perplexidade
A ideia de uma rede neural ser capaz de realizar tarefas gerais sem treino adicional é apresentada no artigo [Language Models are Unsupervised Multitask Learners](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf). A ideia principal é que muitas outras tarefas podem ser modeladas utilizando **geração de texto**, porque compreender texto essencialmente significa ser capaz de produzi-lo. Como o modelo é treinado com uma enorme quantidade de texto que abrange o conhecimento humano, ele também se torna conhecedor de uma ampla variedade de assuntos.
> Compreender e ser capaz de produzir texto também implica saber algo sobre o mundo ao nosso redor. As pessoas também aprendem, em grande parte, através da leitura, e a rede GPT é semelhante nesse aspeto.
Redes de geração de texto funcionam ao prever a probabilidade da próxima palavra $$P(w_N)$$. No entanto, a probabilidade incondicional da próxima palavra equivale à frequência dessa palavra no corpus de texto. O GPT é capaz de nos fornecer a **probabilidade condicional** da próxima palavra, dadas as anteriores: $$P(w_N | w_{n-1}, ..., w_0)$$.
> Pode ler mais sobre probabilidades no nosso [Currículo de Ciência de Dados para Iniciantes](https://github.com/microsoft/Data-Science-For-Beginners/tree/main/1-Introduction/04-stats-and-probability).
A qualidade de um modelo de geração de linguagem pode ser definida utilizando a **perplexidade**. É uma métrica intrínseca que nos permite medir a qualidade do modelo sem qualquer conjunto de dados específico para a tarefa. Baseia-se na noção de *probabilidade de uma frase* - o modelo atribui alta probabilidade a uma frase que é provável de ser real (ou seja, o modelo não está **perplexo** com ela) e baixa probabilidade a frases que fazem menos sentido (ex.: *Pode ele faz o quê?*). Quando damos ao nosso modelo frases de um corpus de texto real, esperamos que tenham alta probabilidade e baixa **perplexidade**. Matematicamente, é definida como a probabilidade inversa normalizada do conjunto de teste:
$$
\mathrm{Perplexity}(W) = \sqrt[N]{1\over P(W_1,...,W_N)}
$$
**Pode experimentar a geração de texto utilizando o [editor de texto alimentado por GPT da Hugging Face](https://transformer.huggingface.co/doc/gpt2-large)**. Neste editor, começa a escrever o seu texto e, ao pressionar **[TAB]**, serão oferecidas várias opções de conclusão. Se forem demasiado curtas ou não estiver satisfeito com elas, pressione [TAB] novamente e terá mais opções, incluindo trechos de texto mais longos.
## GPT é uma Família
O GPT não é um único modelo, mas sim uma coleção de modelos desenvolvidos e treinados pela [OpenAI](https://openai.com).
Dentro dos modelos GPT, temos:
| [GPT-2](https://huggingface.co/docs/transformers/model_doc/gpt2#openai-gpt2) | [GPT 3](https://openai.com/research/language-models-are-few-shot-learners) | [GPT-4](https://openai.com/gpt-4) |
| -- | -- | -- |
|Modelo de linguagem com até 1,5 mil milhões de parâmetros. | Modelo de linguagem com até 175 mil milhões de parâmetros. | 100T parâmetros e aceita tanto entradas de imagem como de texto, e gera saídas em texto. |
Os modelos GPT-3 e GPT-4 estão disponíveis [como um serviço cognitivo da Microsoft Azure](https://azure.microsoft.com/en-us/services/cognitive-services/openai-service/#overview?WT.mc_id=academic-77998-cacaste) e como [API da OpenAI](https://openai.com/api/).
## Engenharia de Prompts
Como o GPT foi treinado com vastos volumes de dados para compreender linguagem e código, ele fornece respostas em função das entradas (prompts). Prompts são entradas ou consultas para o GPT, onde se fornecem instruções aos modelos sobre as tarefas que devem ser realizadas a seguir. Para obter um resultado desejado, é necessário o prompt mais eficaz, o que envolve selecionar as palavras, formatos, frases ou até símbolos certos. Esta abordagem é chamada de [Engenharia de Prompts](https://learn.microsoft.com/en-us/shows/ai-show/the-basics-of-prompt-engineering-with-azure-openai-service?WT.mc_id=academic-77998-bethanycheum).
[Esta documentação](https://learn.microsoft.com/en-us/semantic-kernel/prompt-engineering/?WT.mc_id=academic-77998-bethanycheum) fornece mais informações sobre engenharia de prompts.
## ✍️ Notebook de Exemplo: [Experimentando com OpenAI-GPT](GPT-PyTorch.ipynb)
Continue a sua aprendizagem nos seguintes notebooks:
* [Gerando texto com OpenAI-GPT e Hugging Face Transformers](GPT-PyTorch.ipynb)
## Conclusão
Novos modelos de linguagem pré-treinados gerais não apenas modelam a estrutura da linguagem, mas também contêm uma vasta quantidade de linguagem natural. Assim, podem ser utilizados de forma eficaz para resolver algumas tarefas de PLN em configurações de zero-shot ou few-shot.
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/40)
---

View File

@ -0,0 +1,69 @@
# Processamento de Linguagem Natural
![Resumo das tarefas de PLN em um desenho](../../../../lessons/sketchnotes/ai-nlp.png)
Nesta secção, vamos focar no uso de Redes Neuronais para lidar com tarefas relacionadas ao **Processamento de Linguagem Natural (PLN)**. Existem muitos problemas de PLN que queremos que os computadores sejam capazes de resolver:
* **Classificação de texto** é um problema típico de classificação relacionado a sequências de texto. Exemplos incluem classificar mensagens de e-mail como spam ou não-spam, ou categorizar artigos como desporto, negócios, política, etc. Além disso, ao desenvolver chatbots, muitas vezes precisamos entender o que o utilizador quis dizer — neste caso, estamos a lidar com **classificação de intenções**. Frequentemente, na classificação de intenções, precisamos lidar com muitas categorias.
* **Análise de sentimento** é um problema típico de regressão, onde precisamos atribuir um número (um sentimento) que corresponda ao quão positiva/negativa é a mensagem de uma frase. Uma versão mais avançada da análise de sentimento é a **análise de sentimento baseada em aspetos** (ABSA), onde atribuímos o sentimento não à frase inteira, mas a diferentes partes dela (aspeto), por exemplo: *Neste restaurante, gostei da cozinha, mas a atmosfera era horrível*.
* **Reconhecimento de Entidades Nomeadas** (NER) refere-se ao problema de extrair certas entidades de um texto. Por exemplo, podemos precisar entender que na frase *Preciso de voar para Paris amanhã*, a palavra *amanhã* refere-se a uma DATA, e *Paris* é uma LOCALIZAÇÃO.
* **Extração de palavras-chave** é semelhante ao NER, mas precisamos extrair automaticamente palavras importantes para o significado da frase, sem pré-treino para tipos específicos de entidades.
* **Agrupamento de texto** pode ser útil quando queremos agrupar frases semelhantes, por exemplo, pedidos semelhantes em conversas de suporte técnico.
* **Resposta a perguntas** refere-se à capacidade de um modelo responder a uma pergunta específica. O modelo recebe um trecho de texto e uma pergunta como entradas, e precisa fornecer um local no texto onde a resposta à pergunta está contida (ou, por vezes, gerar o texto da resposta).
* **Geração de texto** é a capacidade de um modelo gerar novo texto. Pode ser considerado como uma tarefa de classificação que prevê a próxima letra/palavra com base num *texto inicial*. Modelos avançados de geração de texto, como o GPT-3, conseguem resolver outras tarefas de PLN, como classificação, usando uma técnica chamada [programação por prompts](https://towardsdatascience.com/software-3-0-how-prompting-will-change-the-rules-of-the-game-a982fbfe1e0) ou [engenharia de prompts](https://medium.com/swlh/openai-gpt-3-and-prompt-engineering-dcdc2c5fcd29).
* **Sumarização de texto** é uma técnica em que queremos que um computador "leia" um texto longo e o resuma em algumas frases.
* **Tradução automática** pode ser vista como uma combinação de compreensão de texto numa língua e geração de texto noutra.
Inicialmente, a maioria das tarefas de PLN era resolvida usando métodos tradicionais, como gramáticas. Por exemplo, na tradução automática, analisadores sintáticos eram usados para transformar a frase inicial numa árvore sintática, depois estruturas semânticas de nível superior eram extraídas para representar o significado da frase, e com base nesse significado e na gramática da língua de destino, o resultado era gerado. Atualmente, muitas tarefas de PLN são resolvidas de forma mais eficaz usando redes neuronais.
> Muitos métodos clássicos de PLN estão implementados na biblioteca Python [Natural Language Processing Toolkit (NLTK)](https://www.nltk.org). Existe um excelente [Livro NLTK](https://www.nltk.org/book/) disponível online que cobre como diferentes tarefas de PLN podem ser resolvidas usando o NLTK.
No nosso curso, vamos focar-nos principalmente no uso de Redes Neuronais para PLN, e usaremos o NLTK quando necessário.
Já aprendemos a usar redes neuronais para lidar com dados tabulares e com imagens. A principal diferença entre esses tipos de dados e texto é que o texto é uma sequência de comprimento variável, enquanto o tamanho da entrada no caso de imagens é conhecido antecipadamente. Embora redes convolucionais possam extrair padrões de dados de entrada, os padrões no texto são mais complexos. Por exemplo, podemos ter uma negação separada do sujeito por muitas palavras (ex.: *Eu não gosto de laranjas* vs. *Eu não gosto daquelas laranjas grandes, coloridas e saborosas*), e isso ainda deve ser interpretado como um único padrão. Assim, para lidar com a linguagem, precisamos introduzir novos tipos de redes neuronais, como *redes recorrentes* e *transformers*.
## Instalar Bibliotecas
Se estiver a usar uma instalação local de Python para executar este curso, pode ser necessário instalar todas as bibliotecas necessárias para PLN usando os seguintes comandos:
**Para PyTorch**
```bash
pip install -r requirements-torch.txt
```
**Para TensorFlow**
```bash
pip install -r requirements-tf.txt
```
> Pode experimentar PLN com TensorFlow no [Microsoft Learn](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-tensorflow/?WT.mc_id=academic-77998-cacaste)
## Aviso sobre GPU
Nesta secção, em alguns dos exemplos, treinaremos modelos bastante grandes.
* **Use um Computador com GPU**: É recomendável executar os seus notebooks num computador com GPU para reduzir os tempos de espera ao trabalhar com modelos grandes.
* **Limitações de Memória da GPU**: Executar numa GPU pode levar a situações em que a memória da GPU se esgote, especialmente ao treinar modelos grandes.
* **Consumo de Memória da GPU**: A quantidade de memória da GPU consumida durante o treino depende de vários fatores, incluindo o tamanho do minibatch.
* **Minimize o Tamanho do Minibatch**: Se encontrar problemas de memória na GPU, considere reduzir o tamanho do minibatch no seu código como uma solução potencial.
* **Libertação de Memória da GPU no TensorFlow**: Versões mais antigas do TensorFlow podem não libertar corretamente a memória da GPU ao treinar vários modelos num único kernel Python. Para gerir o uso da memória da GPU de forma eficaz, pode configurar o TensorFlow para alocar memória da GPU apenas conforme necessário.
* **Inclusão de Código**: Para configurar o TensorFlow para aumentar a alocação de memória da GPU apenas quando necessário, inclua o seguinte código nos seus notebooks:
```python
physical_devices = tf.config.list_physical_devices('GPU')
if len(physical_devices)>0:
tf.config.experimental.set_memory_growth(physical_devices[0], True)
```
Se estiver interessado em aprender sobre PLN numa perspetiva de ML clássico, visite [esta coleção de lições](https://github.com/microsoft/ML-For-Beginners/tree/main/6-NLP)
## Nesta Secção
Nesta secção, vamos aprender sobre:
* [Representar texto como tensores](13-TextRep/README.md)
* [Embeddings de palavras](14-Emdeddings/README.md)
* [Modelagem de linguagem](15-LanguageModeling/README.md)
* [Redes Neuronais Recorrentes](16-RNN/README.md)
* [Redes Generativas](17-GenerativeNetworks/README.md)
* [Transformers](18-Transformers/README.md)
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,49 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Tarefa: Equações Diofantinas\n",
"\n",
"> Esta tarefa faz parte do [Currículo de IA para Iniciantes](http://github.com/microsoft/ai-for-beginners) e foi inspirada por [este artigo](https://habr.com/post/128704/).\n",
"\n",
"O seu objetivo é resolver a chamada **equação diofantina** - uma equação com raízes inteiras e coeficientes inteiros. Por exemplo, considere a seguinte equação:\n",
"\n",
"$$a+2b+3c+4d=30$$\n",
"\n",
"Você precisa encontrar raízes inteiras $a$,$b$,$c$,$d\\in\\mathbb{N}$ que satisfaçam esta equação.\n",
"\n",
"Dicas:\n",
"1. Pode considerar que as raízes estão no intervalo [0;30]\n",
"1. Como gene, considere usar a lista de valores das raízes\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante ter em conta que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"language_info": {
"name": "python"
},
"orig_nbformat": 4,
"coopTranslator": {
"original_hash": "a967e1fa1e11ab2b6467b19349a4a9aa",
"translation_date": "2025-08-31T11:33:58+00:00",
"source_file": "lessons/6-Other/21-GeneticAlgorithms/Diophantine.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,80 @@
# Algoritmos Genéticos
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/41)
**Algoritmos Genéticos** (GA) baseiam-se numa abordagem **evolutiva** para IA, em que métodos de evolução de uma população são utilizados para obter uma solução ótima para um determinado problema. Foram propostos em 1975 por [John Henry Holland](https://wikipedia.org/wiki/John_Henry_Holland).
Os Algoritmos Genéticos baseiam-se nas seguintes ideias:
* Soluções válidas para o problema podem ser representadas como **genes**
* O **Cruzamento** permite combinar duas soluções para obter uma nova solução válida
* A **Seleção** é usada para escolher soluções mais ótimas utilizando uma **função de aptidão**
* **Mutação** é introduzida para desestabilizar a otimização e sair de mínimos locais
Se quiser implementar um Algoritmo Genético, precisa do seguinte:
* Encontrar um método para codificar as soluções do problema utilizando **genes** g&in;&Gamma;
* No conjunto de genes &Gamma;, é necessário definir uma **função de aptidão** fit: &Gamma;&rightarrow;**R**. Valores menores da função correspondem a melhores soluções.
* Definir um mecanismo de **cruzamento** para combinar dois genes e obter uma nova solução válida crossover: &Gamma;<sup>2</sub>&rightarrow;&Gamma;.
* Definir um mecanismo de **mutação** mutate: &Gamma;&rightarrow;&Gamma;.
Em muitos casos, os algoritmos de cruzamento e mutação são bastante simples para manipular genes como sequências numéricas ou vetores de bits.
A implementação específica de um algoritmo genético pode variar de caso para caso, mas a estrutura geral é a seguinte:
1. Selecionar uma população inicial G&subset;&Gamma;
2. Selecionar aleatoriamente uma das operações que será realizada neste passo: cruzamento ou mutação
3. **Cruzamento**:
* Selecionar aleatoriamente dois genes g<sub>1</sub>, g<sub>2</sub> &in; G
* Calcular o cruzamento g=crossover(g<sub>1</sub>,g<sub>2</sub>)
* Se fit(g)<fit(g<sub>1</sub>) ou fit(g)<fit(g<sub>2</sub>) - substituir o gene correspondente na população por g.
4. **Mutação** - selecionar um gene aleatório g&in;G e substituí-lo por mutate(g)
5. Repetir a partir do passo 2, até obter um valor suficientemente pequeno de fit, ou até atingir o limite de passos.
## Tarefas Típicas
As tarefas tipicamente resolvidas por Algoritmos Genéticos incluem:
1. Otimização de horários
1. Empacotamento ótimo
1. Corte ótimo
1. Aceleração de busca exaustiva
## ✍️ Exercícios: Algoritmos Genéticos
Continue a sua aprendizagem nos seguintes notebooks:
Aceda a [este notebook](Genetic.ipynb) para ver dois exemplos de utilização de Algoritmos Genéticos:
1. Divisão justa de tesouro
1. Problema das 8 Rainhas
## Conclusão
Os Algoritmos Genéticos são usados para resolver muitos problemas, incluindo logística e problemas de busca. Este campo é inspirado por pesquisas que fundiram tópicos em Psicologia e Ciência da Computação.
## 🚀 Desafio
"Os algoritmos genéticos são simples de implementar, mas o seu comportamento é difícil de entender." [fonte](https://wikipedia.org/wiki/Genetic_algorithm) Faça uma pesquisa para encontrar uma implementação de um algoritmo genético, como resolver um puzzle de Sudoku, e explique como funciona através de um esboço ou fluxograma.
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/42)
## Revisão & Autoestudo
Veja [este excelente vídeo](https://www.youtube.com/watch?v=qv6UVOQ0F44) que fala sobre como um computador pode aprender a jogar Super Mario utilizando redes neurais treinadas por algoritmos genéticos. Vamos aprender mais sobre computadores a jogar jogos como este [na próxima seção](../22-DeepRL/README.md).
## [Tarefa: Equação Diofantina](Diophantine.ipynb)
O seu objetivo é resolver a chamada **equação Diofantina** - uma equação com raízes inteiras. Por exemplo, considere a equação a+2b+3c+4d=30. É necessário encontrar as raízes inteiras que satisfaçam esta equação.
*Esta tarefa é inspirada por [este post](https://habr.com/post/128704/).*
Dicas:
1. Pode considerar raízes no intervalo [0;30]
1. Como gene, considere usar a lista de valores das raízes
Use [Diophantine.ipynb](Diophantine.ipynb) como ponto de partida.
---

View File

@ -0,0 +1,501 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Treinar RL para equilibrar o Cartpole\n",
"\n",
"Este notebook faz parte do [Currículo de IA para Iniciantes](http://aka.ms/ai-beginners). Foi inspirado pelo [tutorial oficial do PyTorch](https://pytorch.org/tutorials/intermediate/reinforcement_q_learning.html) e por [esta implementação de Cartpole em PyTorch](https://github.com/yc930401/Actor-Critic-pytorch).\n",
"\n",
"Neste exemplo, usaremos RL para treinar um modelo a equilibrar uma vara em um carrinho que pode se mover para a esquerda e para a direita em uma escala horizontal. Utilizaremos o ambiente [OpenAI Gym](https://www.gymlibrary.ml/) para simular a vara.\n",
"\n",
"> **Nota**: Pode executar o código desta lição localmente (por exemplo, no Visual Studio Code), caso em que a simulação será aberta numa nova janela. Ao executar o código online, pode ser necessário fazer alguns ajustes no código, conforme descrito [aqui](https://towardsdatascience.com/rendering-openai-gym-envs-on-binder-and-google-colab-536f99391cc7).\n",
"\n",
"Começaremos por garantir que o Gym está instalado:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"!{sys.executable} -m pip install gym"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos criar o ambiente CartPole e ver como operá-lo. Um ambiente tem as seguintes propriedades:\n",
"\n",
"* **Action space** é o conjunto de ações possíveis que podemos realizar em cada passo da simulação \n",
"* **Observation space** é o espaço de observações que podemos fazer \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import gym\n",
"\n",
"env = gym.make(\"CartPole-v1\")\n",
"\n",
"print(f\"Action space: {env.action_space}\")\n",
"print(f\"Observation space: {env.observation_space}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos ver como a simulação funciona. O seguinte ciclo executa a simulação até que `env.step` não devolva o sinal de terminação `done`. Vamos escolher ações aleatoriamente usando `env.action_space.sample()`, o que significa que a experiência provavelmente falhará muito rapidamente (o ambiente CartPole termina quando a velocidade do CartPole, a sua posição ou o ângulo estão fora de certos limites).\n",
"\n",
"> A simulação será aberta numa nova janela. Pode executar o código várias vezes e observar como se comporta.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"env.reset()\n",
"\n",
"done = False\n",
"total_reward = 0\n",
"while not done:\n",
" env.render()\n",
" obs, rew, done, info = env.step(env.action_space.sample())\n",
" total_reward += rew\n",
" print(f\"{obs} -> {rew}\")\n",
"print(f\"Total reward: {total_reward}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pode notar que as observações contêm 4 números. São eles:\n",
"- Posição do carrinho\n",
"- Velocidade do carrinho\n",
"- Ângulo da barra\n",
"- Taxa de rotação da barra\n",
"\n",
"`rew` é a recompensa que recebemos em cada passo. Pode ver que, no ambiente CartPole, recebe-se 1 ponto por cada passo de simulação, e o objetivo é maximizar a recompensa total, ou seja, o tempo que o CartPole consegue equilibrar sem cair.\n",
"\n",
"Durante o aprendizado por reforço, o nosso objetivo é treinar uma **política** $\\pi$, que para cada estado $s$ nos dirá qual ação $a$ tomar, essencialmente $a = \\pi(s)$.\n",
"\n",
"Se quiser uma solução probabilística, pode pensar na política como retornando um conjunto de probabilidades para cada ação, ou seja, $\\pi(a|s)$ significaria a probabilidade de que devemos tomar a ação $a$ no estado $s$.\n",
"\n",
"## Método de Gradiente de Política\n",
"\n",
"No algoritmo mais simples de RL, chamado **Gradiente de Política**, iremos treinar uma rede neural para prever a próxima ação.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import torch\n",
"\n",
"num_inputs = 4\n",
"num_actions = 2\n",
"\n",
"model = torch.nn.Sequential(\n",
" torch.nn.Linear(num_inputs, 128, bias=False, dtype=torch.float32),\n",
" torch.nn.ReLU(),\n",
" torch.nn.Linear(128, num_actions, bias = False, dtype=torch.float32),\n",
" torch.nn.Softmax(dim=1)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos treinar a rede realizando muitos experimentos e atualizando a nossa rede após cada execução. Vamos definir uma função que irá executar o experimento e retornar os resultados (o chamado **rastro**) - todos os estados, ações (e as suas probabilidades recomendadas) e recompensas:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def run_episode(max_steps_per_episode = 10000,render=False): \n",
" states, actions, probs, rewards = [],[],[],[]\n",
" state = env.reset()\n",
" for _ in range(max_steps_per_episode):\n",
" if render:\n",
" env.render()\n",
" action_probs = model(torch.from_numpy(np.expand_dims(state,0)))[0]\n",
" action = np.random.choice(num_actions, p=np.squeeze(action_probs.detach().numpy()))\n",
" nstate, reward, done, info = env.step(action)\n",
" if done:\n",
" break\n",
" states.append(state)\n",
" actions.append(action)\n",
" probs.append(action_probs.detach().numpy())\n",
" rewards.append(reward)\n",
" state = nstate\n",
" return np.vstack(states), np.vstack(actions), np.vstack(probs), np.vstack(rewards)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pode executar um episódio com uma rede não treinada e observar que a recompensa total (ou seja, a duração do episódio) é muito baixa:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"s, a, p, r = run_episode()\n",
"print(f\"Total reward: {np.sum(r)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Um dos aspetos complicados do algoritmo de gradiente de política é usar **recompensas descontadas**. A ideia é que calculamos o vetor de recompensas totais em cada etapa do jogo e, durante este processo, descontamos as recompensas iniciais usando algum coeficiente $gamma$. Também normalizamos o vetor resultante, porque o usaremos como peso para afetar o nosso treino:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"eps = 0.0001\n",
"\n",
"def discounted_rewards(rewards,gamma=0.99,normalize=True):\n",
" ret = []\n",
" s = 0\n",
" for r in rewards[::-1]:\n",
" s = r + gamma * s\n",
" ret.insert(0, s)\n",
" if normalize:\n",
" ret = (ret-np.mean(ret))/(np.std(ret)+eps)\n",
" return ret"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos começar o treino! Iremos executar 300 episódios, e em cada episódio faremos o seguinte:\n",
"\n",
"1. Executar o experimento e recolher o traço.\n",
"1. Calcular a diferença (`gradients`) entre as ações realizadas e as probabilidades previstas. Quanto menor for a diferença, mais certeza teremos de que tomámos a ação correta.\n",
"1. Calcular recompensas descontadas e multiplicar os gradientes pelas recompensas descontadas - isso garantirá que os passos com recompensas mais altas terão maior impacto no resultado final do que aqueles com recompensas mais baixas.\n",
"1. As ações-alvo esperadas para a nossa rede neural serão parcialmente derivadas das probabilidades previstas durante a execução e parcialmente dos gradientes calculados. Utilizaremos o parâmetro `alpha` para determinar em que medida os gradientes e as recompensas são considerados - isto é chamado de *taxa de aprendizagem* do algoritmo de reforço.\n",
"1. Por fim, treinamos a nossa rede com os estados e ações esperadas, e repetimos o processo.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"optimizer = torch.optim.Adam(model.parameters(), lr=0.01)\n",
"\n",
"def train_on_batch(x, y):\n",
" x = torch.from_numpy(x)\n",
" y = torch.from_numpy(y)\n",
" optimizer.zero_grad()\n",
" predictions = model(x)\n",
" loss = -torch.mean(torch.log(predictions) * y)\n",
" loss.backward()\n",
" optimizer.step()\n",
" return loss"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alpha = 1e-4\n",
"\n",
"history = []\n",
"for epoch in range(300):\n",
" states, actions, probs, rewards = run_episode()\n",
" one_hot_actions = np.eye(2)[actions.T][0]\n",
" gradients = one_hot_actions-probs\n",
" dr = discounted_rewards(rewards)\n",
" gradients *= dr\n",
" target = alpha*np.vstack([gradients])+probs\n",
" train_on_batch(states,target)\n",
" history.append(np.sum(rewards))\n",
" if epoch%100==0:\n",
" print(f\"{epoch} -> {np.sum(rewards)}\")\n",
"\n",
"plt.plot(history)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos executar o episódio com renderização para ver o resultado:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"_ = run_episode(render=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Esperemos que consiga ver que o bastão agora consegue equilibrar-se bastante bem!\n",
"\n",
"## Modelo Actor-Critic\n",
"\n",
"O modelo Actor-Critic é um desenvolvimento adicional dos gradientes de política, no qual construímos uma rede neural para aprender tanto a política como as recompensas estimadas. A rede terá dois outputs (ou pode ser vista como duas redes separadas):\n",
"* **Actor** irá recomendar a ação a tomar, fornecendo-nos a distribuição de probabilidade do estado, como no modelo de gradiente de política.\n",
"* **Critic** estimará qual seria a recompensa dessas ações. Retorna as recompensas totais estimadas no futuro para o estado dado.\n",
"\n",
"Vamos definir um modelo deste tipo:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from itertools import count\n",
"import torch.nn.functional as F"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
"env = gym.make(\"CartPole-v1\")\n",
"\n",
"state_size = env.observation_space.shape[0]\n",
"action_size = env.action_space.n\n",
"lr = 0.0001\n",
"\n",
"class Actor(torch.nn.Module):\n",
" def __init__(self, state_size, action_size):\n",
" super(Actor, self).__init__()\n",
" self.state_size = state_size\n",
" self.action_size = action_size\n",
" self.linear1 = torch.nn.Linear(self.state_size, 128)\n",
" self.linear2 = torch.nn.Linear(128, 256)\n",
" self.linear3 = torch.nn.Linear(256, self.action_size)\n",
"\n",
" def forward(self, state):\n",
" output = F.relu(self.linear1(state))\n",
" output = F.relu(self.linear2(output))\n",
" output = self.linear3(output)\n",
" distribution = torch.distributions.Categorical(F.softmax(output, dim=-1))\n",
" return distribution\n",
"\n",
"\n",
"class Critic(torch.nn.Module):\n",
" def __init__(self, state_size, action_size):\n",
" super(Critic, self).__init__()\n",
" self.state_size = state_size\n",
" self.action_size = action_size\n",
" self.linear1 = torch.nn.Linear(self.state_size, 128)\n",
" self.linear2 = torch.nn.Linear(128, 256)\n",
" self.linear3 = torch.nn.Linear(256, 1)\n",
"\n",
" def forward(self, state):\n",
" output = F.relu(self.linear1(state))\n",
" output = F.relu(self.linear2(output))\n",
" value = self.linear3(output)\n",
" return value"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Precisaríamos modificar ligeiramente as nossas funções `discounted_rewards` e `run_episode`:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def discounted_rewards(next_value, rewards, masks, gamma=0.99):\n",
" R = next_value\n",
" returns = []\n",
" for step in reversed(range(len(rewards))):\n",
" R = rewards[step] + gamma * R * masks[step]\n",
" returns.insert(0, R)\n",
" return returns\n",
"\n",
"def run_episode(actor, critic, n_iters):\n",
" optimizerA = torch.optim.Adam(actor.parameters())\n",
" optimizerC = torch.optim.Adam(critic.parameters())\n",
" for iter in range(n_iters):\n",
" state = env.reset()\n",
" log_probs = []\n",
" values = []\n",
" rewards = []\n",
" masks = []\n",
" entropy = 0\n",
" env.reset()\n",
"\n",
" for i in count():\n",
" env.render()\n",
" state = torch.FloatTensor(state).to(device)\n",
" dist, value = actor(state), critic(state)\n",
"\n",
" action = dist.sample()\n",
" next_state, reward, done, _ = env.step(action.cpu().numpy())\n",
"\n",
" log_prob = dist.log_prob(action).unsqueeze(0)\n",
" entropy += dist.entropy().mean()\n",
"\n",
" log_probs.append(log_prob)\n",
" values.append(value)\n",
" rewards.append(torch.tensor([reward], dtype=torch.float, device=device))\n",
" masks.append(torch.tensor([1-done], dtype=torch.float, device=device))\n",
"\n",
" state = next_state\n",
"\n",
" if done:\n",
" print('Iteration: {}, Score: {}'.format(iter, i))\n",
" break\n",
"\n",
"\n",
" next_state = torch.FloatTensor(next_state).to(device)\n",
" next_value = critic(next_state)\n",
" returns = discounted_rewards(next_value, rewards, masks)\n",
"\n",
" log_probs = torch.cat(log_probs)\n",
" returns = torch.cat(returns).detach()\n",
" values = torch.cat(values)\n",
"\n",
" advantage = returns - values\n",
"\n",
" actor_loss = -(log_probs * advantage.detach()).mean()\n",
" critic_loss = advantage.pow(2).mean()\n",
"\n",
" optimizerA.zero_grad()\n",
" optimizerC.zero_grad()\n",
" actor_loss.backward()\n",
" critic_loss.backward()\n",
" optimizerA.step()\n",
" optimizerC.step()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos executar o ciclo principal de treino. Utilizaremos o processo manual de treino da rede, calculando as funções de perda adequadas e atualizando os parâmetros da rede:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n",
"actor = Actor(state_size, action_size).to(device)\n",
"critic = Critic(state_size, action_size).to(device)\n",
"run_episode(actor, critic, n_iters=100)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"env.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Conclusão\n",
"\n",
"Vimos dois algoritmos de RL nesta demonstração: o simples policy gradient e o mais sofisticado actor-critic. Pode-se observar que esses algoritmos operam com noções abstratas de estado, ação e recompensa - o que significa que podem ser aplicados a ambientes muito diferentes.\n",
"\n",
"O reinforcement learning permite-nos aprender a melhor estratégia para resolver um problema apenas analisando a recompensa final. O facto de não precisarmos de conjuntos de dados rotulados permite-nos repetir simulações várias vezes para otimizar os nossos modelos. No entanto, ainda existem muitos desafios no RL, que poderá explorar caso decida aprofundar-se mais nesta área fascinante da IA.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.4 64-bit",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
},
"orig_nbformat": 4,
"vscode": {
"interpreter": {
"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
}
},
"coopTranslator": {
"original_hash": "04f8d9978cd11281d81dd037cbf6ce20",
"translation_date": "2025-08-31T11:35:37+00:00",
"source_file": "lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,117 @@
# Aprendizagem por Reforço Profundo
A aprendizagem por reforço (RL) é considerada um dos paradigmas básicos de aprendizagem automática, ao lado da aprendizagem supervisionada e não supervisionada. Enquanto na aprendizagem supervisionada dependemos de um conjunto de dados com resultados conhecidos, a RL baseia-se em **aprender fazendo**. Por exemplo, quando vemos um jogo de computador pela primeira vez, começamos a jogar, mesmo sem conhecer as regras, e rapidamente conseguimos melhorar as nossas habilidades apenas pelo processo de jogar e ajustar o nosso comportamento.
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/43)
Para realizar RL, precisamos de:
* Um **ambiente** ou **simulador** que define as regras do jogo. Devemos ser capazes de realizar experiências no simulador e observar os resultados.
* Uma **função de recompensa**, que indica o quão bem-sucedida foi a nossa experiência. No caso de aprender a jogar um jogo de computador, a recompensa seria a nossa pontuação final.
Com base na função de recompensa, devemos ser capazes de ajustar o nosso comportamento e melhorar as nossas habilidades, para que na próxima vez joguemos melhor. A principal diferença entre outros tipos de aprendizagem automática e RL é que na RL normalmente não sabemos se ganhamos ou perdemos até terminarmos o jogo. Assim, não podemos dizer se um determinado movimento isolado é bom ou não - só recebemos uma recompensa no final do jogo.
Durante a RL, normalmente realizamos muitas experiências. Em cada experiência, precisamos equilibrar entre seguir a estratégia ótima que aprendemos até agora (**exploração**) e explorar novos estados possíveis (**exploração**).
## OpenAI Gym
Uma ferramenta excelente para RL é o [OpenAI Gym](https://gym.openai.com/) - um **ambiente de simulação**, que pode simular muitos ambientes diferentes, desde jogos Atari até a física por trás do equilíbrio de um poste. É um dos ambientes de simulação mais populares para treinar algoritmos de aprendizagem por reforço e é mantido pela [OpenAI](https://openai.com/).
> **Nota**: Pode ver todos os ambientes disponíveis no OpenAI Gym [aqui](https://gym.openai.com/envs/#classic_control).
## Equilíbrio do CartPole
Provavelmente já viu dispositivos modernos de equilíbrio, como o *Segway* ou *Gyroscooters*. Eles conseguem equilibrar-se automaticamente ajustando as suas rodas em resposta a um sinal de um acelerómetro ou giroscópio. Nesta seção, vamos aprender a resolver um problema semelhante - equilibrar um poste. É semelhante a uma situação em que um artista de circo precisa equilibrar um poste na sua mão - mas este equilíbrio de poste ocorre apenas em 1D.
Uma versão simplificada de equilíbrio é conhecida como o problema **CartPole**. No mundo do CartPole, temos um slider horizontal que pode mover-se para a esquerda ou para a direita, e o objetivo é equilibrar um poste vertical no topo do slider enquanto ele se move.
<img alt="um cartpole" src="../../../../../translated_images/pt-PT/cartpole.f52a67f27e058170.webp" width="200"/>
Para criar e usar este ambiente, precisamos de algumas linhas de código Python:
```python
import gym
env = gym.make("CartPole-v1")
env.reset()
done = False
total_reward = 0
while not done:
env.render()
action = env.action_space.sample()
observaton, reward, done, info = env.step(action)
total_reward += reward
print(f"Total reward: {total_reward}")
```
Cada ambiente pode ser acessado exatamente da mesma forma:
* `env.reset` inicia uma nova experiência
* `env.step` realiza um passo de simulação. Recebe uma **ação** do **espaço de ações** e retorna uma **observação** (do espaço de observação), bem como uma recompensa e um sinal de término.
No exemplo acima, realizamos uma ação aleatória em cada passo, razão pela qual a vida da experiência é muito curta:
![cartpole sem equilíbrio](../../../../../lessons/6-Other/22-DeepRL/images/cartpole-nobalance.gif)
O objetivo de um algoritmo de RL é treinar um modelo - a chamada **política** &pi; - que retornará a ação em resposta a um estado dado. Também podemos considerar a política como probabilística, ou seja, para qualquer estado *s* e ação *a*, ela retornará a probabilidade &pi;(*a*|*s*) de que devemos tomar *a* no estado *s*.
## Algoritmo de Gradientes de Política
A maneira mais óbvia de modelar uma política é criar uma rede neural que receba estados como entrada e retorne ações correspondentes (ou, mais precisamente, as probabilidades de todas as ações). De certa forma, seria semelhante a uma tarefa de classificação normal, com uma grande diferença - não sabemos de antemão quais ações devemos tomar em cada um dos passos.
A ideia aqui é estimar essas probabilidades. Construímos um vetor de **recompensas acumuladas**, que mostra a nossa recompensa total em cada passo da experiência. Também aplicamos **desconto de recompensa** multiplicando recompensas anteriores por algum coeficiente &gamma;=0.99, para diminuir o papel das recompensas anteriores. Em seguida, reforçamos os passos ao longo do caminho da experiência que geram maiores recompensas.
> Saiba mais sobre o algoritmo de Gradientes de Política e veja-o em ação no [notebook de exemplo](CartPole-RL-TF.ipynb).
## Algoritmo Ator-Crítico
Uma versão melhorada da abordagem de Gradientes de Política é chamada de **Ator-Crítico**. A ideia principal por trás disso é que a rede neural seria treinada para retornar duas coisas:
* A política, que determina qual ação tomar. Esta parte é chamada de **ator**.
* A estimativa da recompensa total que podemos esperar obter neste estado - esta parte é chamada de **crítico**.
De certa forma, esta arquitetura assemelha-se a um [GAN](../../4-ComputerVision/10-GANs/README.md), onde temos duas redes que são treinadas uma contra a outra. No modelo ator-crítico, o ator propõe a ação que precisamos tomar, e o crítico tenta ser crítico e estimar o resultado. No entanto, o nosso objetivo é treinar essas redes em conjunto.
Como sabemos tanto as recompensas acumuladas reais quanto os resultados retornados pelo crítico durante a experiência, é relativamente fácil construir uma função de perda que minimize a diferença entre eles. Isso nos daria a **perda do crítico**. Podemos calcular a **perda do ator** usando a mesma abordagem do algoritmo de gradientes de política.
Depois de executar um desses algoritmos, podemos esperar que o nosso CartPole se comporte assim:
![um cartpole equilibrado](../../../../../lessons/6-Other/22-DeepRL/images/cartpole-balance.gif)
## ✍️ Exercícios: Gradientes de Política e RL Ator-Crítico
Continue a sua aprendizagem nos seguintes notebooks:
* [RL em TensorFlow](CartPole-RL-TF.ipynb)
* [RL em PyTorch](CartPole-RL-PyTorch.ipynb)
## Outras Tarefas de RL
A aprendizagem por reforço é atualmente um campo de pesquisa em rápido crescimento. Alguns exemplos interessantes de aprendizagem por reforço são:
* Ensinar um computador a jogar **Jogos Atari**. A parte desafiadora deste problema é que não temos um estado simples representado como um vetor, mas sim uma captura de ecrã - e precisamos usar a CNN para converter esta imagem de ecrã num vetor de características ou para extrair informações de recompensa. Os jogos Atari estão disponíveis no Gym.
* Ensinar um computador a jogar jogos de tabuleiro, como Xadrez e Go. Recentemente, programas de última geração como **Alpha Zero** foram treinados do zero por dois agentes jogando um contra o outro e melhorando a cada passo.
* Na indústria, a RL é usada para criar sistemas de controlo a partir de simulação. Um serviço chamado [Bonsai](https://azure.microsoft.com/services/project-bonsai/?WT.mc_id=academic-77998-cacaste) foi especificamente projetado para isso.
## Conclusão
Aprendemos agora como treinar agentes para alcançar bons resultados apenas fornecendo-lhes uma função de recompensa que define o estado desejado do jogo e dando-lhes a oportunidade de explorar inteligentemente o espaço de busca. Experimentámos com sucesso dois algoritmos e alcançámos um bom resultado num período de tempo relativamente curto. No entanto, este é apenas o início da sua jornada na RL, e deve definitivamente considerar fazer um curso separado se quiser aprofundar mais.
## 🚀 Desafio
Explore as aplicações listadas na seção 'Outras Tarefas de RL' e tente implementar uma!
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/44)
## Revisão e Autoestudo
Saiba mais sobre aprendizagem por reforço clássica no nosso [Currículo de Aprendizagem Automática para Iniciantes](https://github.com/microsoft/ML-For-Beginners/blob/main/8-Reinforcement/README.md).
Assista a [este excelente vídeo](https://www.youtube.com/watch?v=qv6UVOQ0F44) que fala sobre como um computador pode aprender a jogar Super Mario.
## Tarefa: [Treinar um Carro na Montanha](lab/README.md)
O seu objetivo nesta tarefa será treinar um ambiente diferente do Gym - [Mountain Car](https://www.gymlibrary.ml/environments/classic_control/mountain_car/).
---

View File

@ -0,0 +1,109 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Treinar o Mountain Car para Escapar\n",
"\n",
"Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).\n",
"\n",
"O seu objetivo é treinar o agente de RL para controlar o [Mountain Car](https://www.gymlibrary.ml/environments/classic_control/mountain_car/) no Ambiente OpenAI.\n",
"\n",
"Vamos começar por criar o ambiente:\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import gym\n",
"env = gym.make('MountainCar-v0')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos ver como é o aspeto da experiência aleatória:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"state = env.reset()\n",
"while True:\n",
" env.render()\n",
" action = env.action_space.sample()\n",
" state, reward, done, info = env.step(action)\n",
" if done:\n",
" break"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"## Lost of code here"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"env.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
},
"kernelspec": {
"display_name": "Python 3.8.12 ('py38')",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
},
"coopTranslator": {
"original_hash": "f062b3b18449593ef8e0fcc029868781",
"translation_date": "2025-08-31T11:36:20+00:00",
"source_file": "lessons/6-Other/22-DeepRL/lab/MountainCar.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@ -0,0 +1,22 @@
# Treinar o Carro da Montanha para Escapar
Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).
## Tarefa
O teu objetivo é treinar o agente de RL para controlar o [Mountain Car](https://www.gymlibrary.ml/environments/classic_control/mountain_car/) no Ambiente OpenAI.
## O Ambiente
O ambiente Mountain Car consiste num carro preso dentro de um vale. O teu objetivo é saltar para fora do vale e alcançar a bandeira. As ações que podes realizar são acelerar para a esquerda, para a direita ou não fazer nada. Podes observar a posição do carro ao longo do eixo x e a velocidade.
## Notebook Inicial
Começa o trabalho prático abrindo [MountainCar.ipynb](../../../../../../lessons/6-Other/22-DeepRL/lab/MountainCar.ipynb)
## Conclusão
Deverás aprender ao longo deste trabalho que adaptar algoritmos de RL a um novo ambiente é frequentemente bastante simples, porque o OpenAI Gym tem a mesma interface para todos os ambientes, e os algoritmos, como tal, não dependem muito da natureza do ambiente. Podes até reestruturar o código Python de forma a passar qualquer ambiente para o algoritmo de RL como um parâmetro.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original no seu idioma nativo deve ser considerado a fonte autoritativa. Para informações críticas, recomenda-se a tradução profissional humana. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas resultantes do uso desta tradução.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,155 @@
# Sistemas Multi-Agente
Uma das formas possíveis de alcançar inteligência é a abordagem chamada **emergente** (ou **sinergética**), que se baseia no facto de que o comportamento combinado de muitos agentes relativamente simples pode resultar num comportamento geral mais complexo (ou inteligente) do sistema como um todo. Teoricamente, isto baseia-se nos princípios de [Inteligência Coletiva](https://en.wikipedia.org/wiki/Collective_intelligence), [Emergentismo](https://en.wikipedia.org/wiki/Global_brain) e [Cibernética Evolutiva](https://en.wikipedia.org/wiki/Global_brain), que afirmam que sistemas de nível superior ganham algum tipo de valor acrescentado quando são devidamente combinados a partir de sistemas de nível inferior (o chamado *princípio da transição de metasistema*).
## [Questionário pré-aula](https://ff-quizzes.netlify.app/en/ai/quiz/45)
A direção dos **Sistemas Multi-Agente** surgiu na IA nos anos 1990 como uma resposta ao crescimento da Internet e dos sistemas distribuídos. Um dos livros clássicos de IA, [Artificial Intelligence: A Modern Approach](https://en.wikipedia.org/wiki/Artificial_Intelligence:_A_Modern_Approach), foca-se na visão da IA clássica do ponto de vista dos sistemas multi-agente.
Central ao enfoque multi-agente está a noção de **Agente** - uma entidade que vive num **ambiente**, que pode perceber e sobre o qual pode agir. Esta é uma definição muito ampla, e podem existir muitos tipos e classificações diferentes de agentes:
* Pela sua capacidade de raciocinar:
- Agentes **reativos** geralmente têm um comportamento simples de pedido-resposta
- Agentes **deliberativos** utilizam algum tipo de raciocínio lógico e/ou capacidades de planeamento
* Pelo local onde o agente executa o seu código:
- Agentes **estáticos** trabalham num nó de rede dedicado
- Agentes **móveis** podem mover o seu código entre nós de rede
* Pelo seu comportamento:
- Agentes **passivos** não têm objetivos específicos. Estes agentes podem reagir a estímulos externos, mas não iniciam ações por si próprios.
- Agentes **ativos** têm objetivos que perseguem
- Agentes **cognitivos** envolvem planeamento e raciocínio complexos
Os sistemas multi-agente são atualmente utilizados em várias aplicações:
* Em jogos, muitos personagens não-jogadores utilizam algum tipo de IA e podem ser considerados agentes inteligentes
* Na produção de vídeo, a renderização de cenas 3D complexas que envolvem multidões é normalmente feita utilizando simulação multi-agente
* Na modelação de sistemas, a abordagem multi-agente é usada para simular o comportamento de um modelo complexo. Por exemplo, a abordagem multi-agente foi utilizada com sucesso para prever a propagação da COVID-19 em todo o mundo. Abordagens semelhantes podem ser usadas para modelar o tráfego numa cidade e ver como reage a alterações nas regras de trânsito.
* Em sistemas de automação complexos, cada dispositivo pode atuar como um agente independente, tornando o sistema menos monolítico e mais robusto.
Não vamos dedicar muito tempo a explorar profundamente os sistemas multi-agente, mas vamos considerar um exemplo de **Modelação Multi-Agente**.
## NetLogo
[NetLogo](https://ccl.northwestern.edu/netlogo/) é um ambiente de modelação multi-agente baseado numa versão modificada da linguagem de programação [Logo](https://en.wikipedia.org/wiki/Logo_(programming_language)). Esta linguagem foi desenvolvida para ensinar conceitos de programação a crianças e permite controlar um agente chamado **tartaruga**, que pode mover-se deixando um rastro atrás de si. Isto permite criar figuras geométricas complexas, sendo uma forma muito visual de compreender o comportamento de um agente.
No NetLogo, podemos criar muitas tartarugas utilizando o comando `create-turtles`. Podemos então comandar todas as tartarugas para realizarem algumas ações (no exemplo abaixo - mover 10 pontos para a frente):
```
create-turtles 10
ask turtles [
forward 10
]
```
Claro que não é interessante quando todas as tartarugas fazem a mesma coisa, por isso podemos `ask` grupos de tartarugas, por exemplo, aquelas que estão nas proximidades de um determinado ponto. Também podemos criar tartarugas de diferentes *raças* utilizando o comando `breed [cats cat]`. Aqui `cat` é o nome de uma raça, e precisamos especificar tanto a palavra no singular como no plural, porque diferentes comandos utilizam formas diferentes para maior clareza.
> ✅ Não vamos aprender a linguagem NetLogo em si - pode visitar o excelente recurso [Beginner's Interactive NetLogo Dictionary](https://ccl.northwestern.edu/netlogo/bind/) se estiver interessado em aprender mais.
Pode [descarregar](https://ccl.northwestern.edu/netlogo/download.shtml) e instalar o NetLogo para experimentar.
### Biblioteca de Modelos
Uma das grandes vantagens do NetLogo é que contém uma biblioteca de modelos funcionais que pode experimentar. Vá a **File &rightarrow; Models Library**, e terá muitas categorias de modelos para escolher.
<img alt="NetLogo Models Library" src="../../../../../translated_images/pt-PT/NetLogo-ModelLib.efe023afb4763c05.webp" width="60%"/>
> Uma captura de ecrã da biblioteca de modelos por Dmitry Soshnikov
Pode abrir um dos modelos, por exemplo **Biology &rightarrow; Flocking**.
### Princípios Principais
Depois de abrir o modelo, será levado ao ecrã principal do NetLogo. Aqui está um modelo de exemplo que descreve a população de lobos e ovelhas, dado recursos finitos (relva).
![NetLogo Main Screen](../../../../../translated_images/pt-PT/NetLogo-Main.32653711ec1a01b3.webp)
> Captura de ecrã por Dmitry Soshnikov
Neste ecrã, pode ver:
* A secção **Interface**, que contém:
- O campo principal, onde todos os agentes vivem
- Diferentes controlos: botões, sliders, etc.
- Gráficos que pode usar para exibir parâmetros da simulação
* O separador **Code**, que contém o editor onde pode escrever o programa NetLogo
Na maioria dos casos, a interface terá um botão **Setup**, que inicializa o estado da simulação, e um botão **Go**, que inicia a execução. Estes são tratados por manipuladores correspondentes no código que se parecem com isto:
```
to go [
...
]
```
O mundo do NetLogo consiste nos seguintes objetos:
* **Agentes** (tartarugas) que podem mover-se pelo campo e fazer algo. Comanda os agentes utilizando a sintaxe `ask turtles [...]`, e o código entre parênteses é executado por todos os agentes em *modo tartaruga*.
* **Patches** são áreas quadradas do campo onde os agentes vivem. Pode referir-se a todos os agentes no mesmo patch ou pode alterar as cores dos patches e algumas outras propriedades. Também pode `ask patches` para fazer algo.
* **Observer** é um agente único que controla o mundo. Todos os manipuladores de botões são executados em *modo observador*.
> ✅ A beleza de um ambiente multi-agente é que o código que corre em modo tartaruga ou em modo patch é executado ao mesmo tempo por todos os agentes em paralelo. Assim, ao escrever pouco código e programar o comportamento de um agente individual, pode criar um comportamento complexo do sistema de simulação como um todo.
### Flocking
Como exemplo de comportamento multi-agente, vamos considerar **[Flocking](https://en.wikipedia.org/wiki/Flocking_(behavior))**. Flocking é um padrão complexo muito semelhante ao modo como bandos de pássaros voam. Ao observá-los, pode pensar que seguem algum tipo de algoritmo coletivo ou que possuem alguma forma de *inteligência coletiva*. No entanto, este comportamento complexo surge quando cada agente individual (neste caso, um *pássaro*) apenas observa alguns outros agentes numa curta distância e segue três regras simples:
* **Alinhamento** - dirige-se para a direção média dos agentes vizinhos
* **Coesão** - tenta dirigir-se para a posição média dos vizinhos (*atração de longo alcance*)
* **Separação** - ao aproximar-se demasiado de outros pássaros, tenta afastar-se (*repulsão de curto alcance*)
Pode executar o exemplo de flocking e observar o comportamento. Também pode ajustar parâmetros, como o *grau de separação* ou o *alcance de visão*, que define até onde cada pássaro pode ver. Note que, se diminuir o alcance de visão para 0, todos os pássaros ficam cegos e o flocking para. Se diminuir a separação para 0, todos os pássaros juntam-se numa linha reta.
> ✅ Mude para o separador **Code** e veja onde as três regras de flocking (alinhamento, coesão e separação) estão implementadas no código. Note como nos referimos apenas aos agentes que estão à vista.
### Outros Modelos para Ver
Há mais alguns modelos interessantes que pode experimentar:
* **Art &rightarrow; Fireworks** mostra como um fogo de artifício pode ser considerado um comportamento coletivo de correntes individuais de fogo
* **Social Science &rightarrow; Traffic Basic** e **Social Science &rightarrow; Traffic Grid** mostram o modelo de tráfego urbano em 1D e numa grelha 2D com ou sem semáforos. Cada carro na simulação segue as seguintes regras:
- Se o espaço à sua frente estiver vazio - acelera (até uma certa velocidade máxima)
- Se vir um obstáculo à frente - trava (e pode ajustar até onde o condutor pode ver)
* **Social Science &rightarrow; Party** mostra como as pessoas se agrupam durante uma festa de cocktail. Pode encontrar a combinação de parâmetros que leva ao aumento mais rápido da felicidade do grupo.
Como pode ver nestes exemplos, as simulações multi-agente podem ser uma forma bastante útil de compreender o comportamento de um sistema complexo composto por indivíduos que seguem a mesma lógica ou lógica semelhante. Também pode ser usado para controlar agentes virtuais, como [NPCs](https://en.wikipedia.org/wiki/NPC) em jogos de computador ou agentes em mundos animados em 3D.
## Agentes Deliberativos
Os agentes descritos acima são muito simples, reagindo a mudanças no ambiente utilizando algum tipo de algoritmo. Como tal, são **agentes reativos**. No entanto, por vezes os agentes podem raciocinar e planear as suas ações, caso em que são chamados **deliberativos**.
Um exemplo típico seria um agente pessoal que recebe uma instrução de um humano para reservar uma viagem de férias. Suponha que existem muitos agentes que vivem na internet e que podem ajudá-lo. Deve então contactar outros agentes para ver quais voos estão disponíveis, quais são os preços dos hotéis para diferentes datas e tentar negociar o melhor preço. Quando o plano de férias estiver completo e confirmado pelo proprietário, pode proceder à reserva.
Para isso, os agentes precisam de **comunicar**. Para uma comunicação bem-sucedida, precisam de:
* Algumas **linguagens padrão para trocar conhecimento**, como [Knowledge Interchange Format](https://en.wikipedia.org/wiki/Knowledge_Interchange_Format) (KIF) e [Knowledge Query and Manipulation Language](https://en.wikipedia.org/wiki/Knowledge_Query_and_Manipulation_Language) (KQML). Estas linguagens são projetadas com base na [Teoria dos Atos de Fala](https://en.wikipedia.org/wiki/Speech_act).
* Estas linguagens devem também incluir alguns **protocolos para negociações**, baseados em diferentes **tipos de leilão**.
* Uma **ontologia comum** para usar, de modo que se refiram aos mesmos conceitos conhecendo os seus significados
* Uma forma de **descobrir** o que diferentes agentes podem fazer, também baseada em algum tipo de ontologia
Os agentes deliberativos são muito mais complexos do que os reativos, porque não apenas reagem a mudanças no ambiente, mas também devem ser capazes de *iniciar* ações. Uma das arquiteturas propostas para agentes deliberativos é o chamado agente de Crença-Desejo-Intenção (BDI):
* **Crenças** formam um conjunto de conhecimento sobre o ambiente do agente. Pode ser estruturado como uma base de conhecimento ou conjunto de regras que um agente pode aplicar a uma situação específica no ambiente.
* **Desejos** definem o que um agente quer fazer, ou seja, os seus objetivos. Por exemplo, o objetivo do agente assistente pessoal acima é reservar uma viagem, e o objetivo de um agente de hotel é maximizar o lucro.
* **Intenções** são ações específicas que um agente planeia para alcançar os seus objetivos. As ações normalmente mudam o ambiente e causam comunicação com outros agentes.
Existem algumas plataformas disponíveis para construir sistemas multi-agente, como [JADE](https://jade.tilab.com/). [Este artigo](https://arxiv.org/ftp/arxiv/papers/2007/2007.08961.pdf) contém uma revisão das plataformas multi-agente, juntamente com uma breve história dos sistemas multi-agente e os seus diferentes cenários de uso.
## Conclusão
Os sistemas multi-agente podem assumir formas muito diferentes e ser usados em muitas aplicações diferentes.
Todos tendem a focar-se no comportamento mais simples de um agente individual e alcançar um comportamento mais complexo do sistema geral devido ao **efeito sinergético**.
## 🚀 Desafio
Leve esta lição para o mundo real e tente conceptualizar um sistema multi-agente que possa resolver um problema. O que, por exemplo, um sistema multi-agente precisaria fazer para otimizar uma rota de autocarro escolar? Como poderia funcionar numa padaria?
## [Questionário pós-aula](https://ff-quizzes.netlify.app/en/ai/quiz/46)
## Revisão e Autoestudo
Revise o uso deste tipo de sistema na indústria. Escolha um domínio, como a manufatura ou a indústria de videojogos, e descubra como os sistemas multi-agente podem ser usados para resolver problemas únicos.
## [Tarefa NetLogo](assignment.md)
---

View File

@ -0,0 +1,8 @@
# Tarefa de NetLogo
Escolhe um dos modelos da biblioteca do NetLogo e utiliza-o para simular uma situação da vida real o mais fielmente possível. Um bom exemplo seria ajustar o modelo Virus na pasta Alternative Visualizations para mostrar como pode ser usado para modelar a propagação da COVID-19. Consegues criar um modelo que imite a propagação real de um vírus?
Mostra o teu trabalho guardando uma cópia e criando um vídeo de demonstração a explicar como o modelo está relacionado com uma situação do mundo real.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original no seu idioma nativo deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se uma tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas resultantes do uso desta tradução.

View File

@ -0,0 +1,43 @@
# IA Ética e Responsável
Está quase a terminar este curso, e espero que, até agora, tenha percebido claramente que a IA é baseada em vários métodos matemáticos formais que nos permitem encontrar relações nos dados e treinar modelos para replicar alguns aspetos do comportamento humano. Neste momento da história, consideramos a IA uma ferramenta muito poderosa para extrair padrões dos dados e aplicar esses padrões para resolver novos problemas.
## [Pre-lecture quiz](https://white-water-09ec41f0f.azurestaticapps.net/quiz/5/)
No entanto, na ficção científica, vemos frequentemente histórias onde a IA representa um perigo para a humanidade. Normalmente, essas histórias giram em torno de algum tipo de rebelião da IA, quando esta decide confrontar os seres humanos. Isso implica que a IA tem algum tipo de emoção ou pode tomar decisões imprevistas pelos seus desenvolvedores.
O tipo de IA que aprendemos neste curso não é mais do que operações matriciais em grande escala. É uma ferramenta muito poderosa para nos ajudar a resolver os nossos problemas e, como qualquer outra ferramenta poderosa, pode ser usada para fins bons ou maus. É importante destacar que pode ser *mal utilizada*.
## Princípios de IA Responsável
Para evitar esta utilização acidental ou intencional da IA, a Microsoft define os importantes [Princípios de IA Responsável](https://www.microsoft.com/ai/responsible-ai?WT.mc_id=academic-77998-cacaste). Os seguintes conceitos sustentam estes princípios:
* **Justiça** está relacionada com o importante problema de *viés nos modelos*, que pode ser causado pelo uso de dados enviesados para o treino. Por exemplo, quando tentamos prever a probabilidade de uma pessoa conseguir um emprego como programador, o modelo tende a dar maior preferência aos homens - apenas porque o conjunto de dados de treino provavelmente estava enviesado para um público masculino. Precisamos de equilibrar cuidadosamente os dados de treino e investigar o modelo para evitar vieses, garantindo que o modelo considera características mais relevantes.
* **Fiabilidade e Segurança**. Por natureza, os modelos de IA podem cometer erros. Uma rede neural retorna probabilidades, e precisamos de ter isso em conta ao tomar decisões. Cada modelo tem uma precisão e um recall, e precisamos de compreender isso para evitar danos que conselhos errados possam causar.
* **Privacidade e Segurança** têm algumas implicações específicas para a IA. Por exemplo, quando usamos alguns dados para treinar um modelo, esses dados tornam-se de certa forma "integrados" no modelo. Por um lado, isso aumenta a segurança e a privacidade, por outro - precisamos de lembrar quais os dados que foram usados para treinar o modelo.
* **Inclusão** significa que não estamos a construir IA para substituir pessoas, mas sim para as complementar e tornar o nosso trabalho mais criativo. Está também relacionado com a justiça, porque ao lidar com comunidades sub-representadas, a maioria dos conjuntos de dados que recolhemos tende a ser enviesada, e precisamos de garantir que essas comunidades são incluídas e tratadas corretamente pela IA.
* **Transparência**. Isto inclui garantir que somos sempre claros sobre o uso de IA. Além disso, sempre que possível, queremos usar sistemas de IA que sejam *interpretáveis*.
* **Responsabilidade**. Quando os modelos de IA tomam algumas decisões, nem sempre é claro quem é responsável por essas decisões. Precisamos de garantir que compreendemos onde reside a responsabilidade pelas decisões da IA. Na maioria dos casos, queremos incluir seres humanos no processo de tomada de decisões importantes, para que pessoas reais sejam responsabilizadas.
## Ferramentas para IA Responsável
A Microsoft desenvolveu o [Responsible AI Toolbox](https://github.com/microsoft/responsible-ai-toolbox), que contém um conjunto de ferramentas:
* Interpretability Dashboard (InterpretML)
* Fairness Dashboard (FairLearn)
* Error Analysis Dashboard
* Responsible AI Dashboard que inclui:
- EconML - ferramenta para Análise Causal, que se foca em questões hipotéticas
- DiCE - ferramenta para Análise Contrafactual que permite ver quais as características que precisam de ser alteradas para influenciar a decisão do modelo
Para mais informações sobre Ética na IA, visite [esta lição](https://github.com/microsoft/ML-For-Beginners/tree/main/1-Introduction/3-fairness?WT.mc_id=academic-77998-cacaste) no currículo de Machine Learning, que inclui tarefas.
## Revisão e Estudo Individual
Faça este [Learn Path](https://docs.microsoft.com/learn/modules/responsible-ai-principles/?WT.mc_id=academic-77998-cacaste) para aprender mais sobre IA responsável.
## [Post-lecture quiz](https://white-water-09ec41f0f.azurestaticapps.net/quiz/6/)
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

View File

@ -0,0 +1,8 @@
# Visão Geral
![Visão Geral em um rabisco](../../../lessons/sketchnotes/ai-overview.png)
> Rabisco por [Tomomi Imura](https://twitter.com/girlie_mac)
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes do uso desta tradução.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,79 @@
# Redes Multi-Modais
Após o sucesso dos modelos transformadores na resolução de tarefas de PLN, as mesmas ou similares arquiteturas foram aplicadas a tarefas de visão computacional. Há um interesse crescente em construir modelos que *combinem* capacidades de visão e linguagem natural. Uma dessas tentativas foi realizada pela OpenAI, chamada CLIP e DALL.E.
## Treino Contrastivo de Imagens (CLIP)
A ideia principal do CLIP é ser capaz de comparar descrições textuais com uma imagem e determinar o quão bem a imagem corresponde à descrição.
![Arquitetura CLIP](../../../../../lessons/X-Extras/X1-MultiModal/images/clip-arch.png)
> *Imagem retirada [deste artigo](https://openai.com/blog/clip/)*
O modelo é treinado com imagens obtidas da Internet e suas legendas. Para cada lote, pegamos N pares de (imagem, texto) e convertemos para representações vetoriais I, ..., I / T, ..., T. Essas representações são então comparadas entre si. A função de perda é definida para maximizar a similaridade cosseno entre os vetores correspondentes a um par (por exemplo, I e T) e minimizar a similaridade cosseno entre todos os outros pares. É por isso que essa abordagem é chamada de **contrastiva**.
O modelo/biblioteca CLIP está disponível no [GitHub da OpenAI](https://github.com/openai/CLIP). A abordagem é descrita [neste artigo](https://openai.com/blog/clip/) e em mais detalhes [neste artigo científico](https://arxiv.org/pdf/2103.00020.pdf).
Uma vez que este modelo é pré-treinado, podemos fornecer um lote de imagens e um lote de descrições textuais, e ele retornará um tensor com probabilidades. O CLIP pode ser usado para várias tarefas:
**Classificação de Imagens**
Suponha que precisamos classificar imagens entre, por exemplo, gatos, cães e humanos. Nesse caso, podemos fornecer ao modelo uma imagem e uma série de descrições textuais: "*uma foto de um gato*", "*uma foto de um cão*", "*uma foto de um humano*". No vetor resultante de 3 probabilidades, basta selecionar o índice com o valor mais alto.
![CLIP para Classificação de Imagens](../../../../../lessons/X-Extras/X1-MultiModal/images/clip-class.png)
> *Imagem retirada [deste artigo](https://openai.com/blog/clip/)*
**Pesquisa de Imagens Baseada em Texto**
Também podemos fazer o oposto. Se tivermos uma coleção de imagens, podemos passar essa coleção para o modelo e uma descrição textual - isso nos dará a imagem mais semelhante à descrição fornecida.
## ✍️ Exemplo: [Usando CLIP para Classificação de Imagens e Pesquisa de Imagens](../../../../../lessons/X-Extras/X1-MultiModal/Clip.ipynb)
Abra o notebook [Clip.ipynb](../../../../../lessons/X-Extras/X1-MultiModal/Clip.ipynb) para ver o CLIP em ação.
## Geração de Imagens com VQGAN+CLIP
O CLIP também pode ser usado para **geração de imagens** a partir de uma descrição textual. Para isso, precisamos de um **modelo gerador** que seja capaz de gerar imagens com base em algum vetor de entrada. Um desses modelos é chamado [VQGAN](https://compvis.github.io/taming-transformers/) (Vector-Quantized GAN).
As principais ideias do VQGAN que o diferenciam de um [GAN](../../4-ComputerVision/10-GANs/README.md) tradicional são as seguintes:
* Uso de uma arquitetura transformadora autorregressiva para gerar uma sequência de partes visuais ricas em contexto que compõem a imagem. Essas partes visuais, por sua vez, são aprendidas por uma [CNN](../../4-ComputerVision/07-ConvNets/README.md).
* Uso de um discriminador de sub-imagens que detecta se partes da imagem são "reais" ou "falsas" (diferente da abordagem "tudo ou nada" nos GANs tradicionais).
Saiba mais sobre o VQGAN no site [Taming Transformers](https://compvis.github.io/taming-transformers/).
Uma das diferenças importantes entre o VQGAN e um GAN tradicional é que o último pode produzir uma imagem decente a partir de qualquer vetor de entrada, enquanto o VQGAN provavelmente produzirá uma imagem incoerente. Assim, precisamos orientar ainda mais o processo de criação da imagem, e isso pode ser feito usando o CLIP.
![Arquitetura VQGAN+CLIP](../../../../../lessons/X-Extras/X1-MultiModal/images/vqgan.png)
Para gerar uma imagem correspondente a uma descrição textual, começamos com um vetor de codificação aleatório que é passado pelo VQGAN para produzir uma imagem. Em seguida, o CLIP é usado para produzir uma função de perda que mostra o quão bem a imagem corresponde à descrição textual. O objetivo, então, é minimizar essa perda, usando retropropagação para ajustar os parâmetros do vetor de entrada.
Uma excelente biblioteca que implementa o VQGAN+CLIP é a [Pixray](http://github.com/pixray/pixray).
![Imagem gerada pelo Pixray](../../../../../lessons/X-Extras/X1-MultiModal/images/a_closeup_watercolor_portrait_of_young_male_teacher_of_literature_with_a_book.png) | ![Imagem gerada pelo Pixray](../../../../../lessons/X-Extras/X1-MultiModal/images/a_closeup_oil_portrait_of_young_female_teacher_of_computer_science_with_a_computer.png) | ![Imagem gerada pelo Pixray](../../../../../lessons/X-Extras/X1-MultiModal/images/a_closeup_oil_portrait_of_old_male_teacher_of_math.png)
----|----|----
Imagem gerada a partir da descrição *um retrato em aquarela de perto de um jovem professor de literatura com um livro* | Imagem gerada a partir da descrição *um retrato a óleo de perto de uma jovem professora de ciência da computação com um computador* | Imagem gerada a partir da descrição *um retrato a óleo de perto de um professor idoso de matemática em frente a um quadro-negro*
> Imagens da coleção **Artificial Teachers** por [Dmitry Soshnikov](http://soshnikov.com)
## DALL-E
### [DALL-E 1](https://openai.com/research/dall-e)
DALL-E é uma versão do GPT-3 treinada para gerar imagens a partir de descrições textuais. Foi treinado com 12 bilhões de parâmetros.
Ao contrário do CLIP, o DALL-E recebe tanto texto quanto imagem como um único fluxo de tokens para ambos. Assim, a partir de várias descrições, é possível gerar imagens baseadas no texto.
### [DALL-E 2](https://openai.com/dall-e-2)
A principal diferença entre o DALL-E 1 e o 2 é que este último gera imagens e arte mais realistas.
Exemplos de imagens geradas com o DALL-E:
![Imagem gerada pelo DALL-E](../../../../../lessons/X-Extras/X1-MultiModal/images/DALL·E%202023-06-20%2015.56.56%20-%20a%20closeup%20watercolor%20portrait%20of%20young%20male%20teacher%20of%20literature%20with%20a%20book.png) | ![Imagem gerada pelo DALL-E](../../../../../lessons/X-Extras/X1-MultiModal/images/DALL·E%202023-06-20%2015.57.43%20-%20a%20closeup%20oil%20portrait%20of%20young%20female%20teacher%20of%20computer%20science%20with%20a%20computer.png) | ![Imagem gerada pelo DALL-E](../../../../../lessons/X-Extras/X1-MultiModal/images/DALL·E%202023-06-20%2015.58.42%20-%20%20a%20closeup%20oil%20portrait%20of%20old%20male%20teacher%20of%20mathematics%20in%20front%20of%20blackboard.png)
----|----|----
Imagem gerada a partir da descrição *um retrato em aquarela de perto de um jovem professor de literatura com um livro* | Imagem gerada a partir da descrição *um retrato a óleo de perto de uma jovem professora de ciência da computação com um computador* | Imagem gerada a partir da descrição *um retrato a óleo de perto de um professor idoso de matemática em frente a um quadro-negro*
## Referências
* Artigo sobre VQGAN: [Taming Transformers for High-Resolution Image Synthesis](https://compvis.github.io/taming-transformers/paper/paper.pdf)
* Artigo sobre CLIP: [Learning Transferable Visual Models From Natural Language Supervision](https://arxiv.org/pdf/2103.00020.pdf)
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original no seu idioma nativo deve ser considerado a fonte autoritativa. Para informações críticas, recomenda-se uma tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas resultantes do uso desta tradução.

View File

@ -0,0 +1,263 @@
Atribuição-CompartilhaIgual 4.0 Internacional
=======================================================================
A Creative Commons Corporation ("Creative Commons") não é um escritório de advocacia e não presta serviços ou consultoria jurídica. A distribuição das licenças públicas da Creative Commons não cria uma relação advogado-cliente ou qualquer outra relação. A Creative Commons disponibiliza suas licenças e informações relacionadas "como estão". A Creative Commons não oferece garantias em relação às suas licenças, qualquer material licenciado sob seus termos e condições, ou quaisquer informações relacionadas. A Creative Commons isenta-se de toda responsabilidade por danos resultantes do uso das licenças na máxima extensão permitida.
Utilização das Licenças Públicas da Creative Commons
As licenças públicas da Creative Commons fornecem um conjunto padrão de termos e condições que criadores e outros detentores de direitos podem usar para partilhar obras originais e outros materiais sujeitos a direitos de autor e certos outros direitos especificados na licença pública abaixo. As seguintes considerações são apenas para fins informativos, não são exaustivas e não fazem parte das nossas licenças.
Considerações para licenciadores: As nossas licenças públicas são
destinadas a serem usadas por aqueles autorizados a conceder ao
público permissão para usar materiais de formas que, de outra
forma, seriam restringidas por direitos de autor e certos outros
direitos. As nossas licenças são irrevogáveis. Os licenciadores
devem ler e compreender os termos e condições da licença que
escolhem antes de aplicá-la. Os licenciadores também devem
garantir todos os direitos necessários antes de aplicar as nossas
licenças para que o público possa reutilizar o material conforme
esperado. Os licenciadores devem marcar claramente qualquer
material que não esteja sujeito à licença. Isso inclui outros
materiais licenciados pela CC ou materiais usados sob uma exceção
ou limitação aos direitos de autor. Mais considerações para
licenciadores:
wiki.creativecommons.org/Considerations_for_licensors
Considerações para o público: Ao usar uma das nossas licenças
públicas, um licenciador concede ao público permissão para usar o
material licenciado sob os termos e condições especificados. Se a
permissão do licenciador não for necessária por qualquer motivo —
por exemplo, devido a uma exceção ou limitação aplicável aos
direitos de autor — então esse uso não é regulado pela licença. As
nossas licenças concedem apenas permissões sob direitos de autor e
certos outros direitos que um licenciador tem autoridade para
conceder. O uso do material licenciado pode ainda ser restringido
por outros motivos, incluindo porque outros possuem direitos de
autor ou outros direitos sobre o material. Um licenciador pode
fazer pedidos especiais, como solicitar que todas as alterações
sejam marcadas ou descritas. Embora não seja obrigatório pelas
nossas licenças, é encorajado que respeite esses pedidos quando
razoável. Mais considerações para o público:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Licença Pública Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional
Ao exercer os Direitos Licenciados (definidos abaixo), aceita e concorda em estar vinculado aos termos e condições desta Licença Pública Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional ("Licença Pública"). Na medida em que esta Licença Pública possa ser interpretada como um contrato, os Direitos Licenciados são concedidos em consideração à sua aceitação destes termos e condições, e o Licenciador concede-lhe tais direitos em consideração aos benefícios que o Licenciador recebe ao disponibilizar o Material Licenciado sob estes termos e condições.
Seção 1 -- Definições.
a. Material Adaptado significa material sujeito a Direitos de Autor e
Direitos Similares que é derivado ou baseado no Material Licenciado
e no qual o Material Licenciado é traduzido, alterado, arranjado,
transformado ou de outra forma modificado de uma maneira que
requeira permissão sob os Direitos de Autor e Direitos Similares
detidos pelo Licenciador. Para os fins desta Licença Pública, onde
o Material Licenciado é uma obra musical, performance ou gravação
sonora, o Material Adaptado é sempre produzido quando o Material
Licenciado é sincronizado em relação temporal com uma imagem em
movimento.
b. Licença do Adaptador significa a licença que aplica aos seus
Direitos de Autor e Direitos Similares nas suas contribuições para
o Material Adaptado de acordo com os termos e condições desta
Licença Pública.
c. Licença Compatível BY-SA significa uma licença listada em
creativecommons.org/compatiblelicenses, aprovada pela Creative
Commons como essencialmente equivalente a esta Licença Pública.
d. Direitos de Autor e Direitos Similares significa direitos de autor
e/ou direitos semelhantes intimamente relacionados aos direitos de
autor, incluindo, sem limitação, direitos de performance,
transmissão, gravação sonora e Direitos de Base de Dados Sui
Generis, independentemente de como os direitos são rotulados ou
categorizados. Para os fins desta Licença Pública, os direitos
especificados na Seção 2(b)(1)-(2) não são Direitos de Autor e
Direitos Similares.
e. Medidas Tecnológicas Eficazes significa aquelas medidas que, na
ausência de autoridade adequada, não podem ser contornadas sob
leis que cumpram as obrigações do Artigo 11 do Tratado da OMPI
sobre Direitos de Autor adotado em 20 de dezembro de 1996, e/ou
acordos internacionais semelhantes.
f. Exceções e Limitações significa uso justo, uso aceitável e/ou
qualquer outra exceção ou limitação aos Direitos de Autor e
Direitos Similares que se aplique ao seu uso do Material Licenciado.
g. Elementos da Licença significa os atributos da licença listados no
nome de uma Licença Pública Creative Commons. Os Elementos da
Licença desta Licença Pública são Atribuição e CompartilhaIgual.
h. Material Licenciado significa a obra artística ou literária, base
de dados ou outro material ao qual o Licenciador aplicou esta
Licença Pública.
i. Direitos Licenciados significa os direitos concedidos a si sujeitos
aos termos e condições desta Licença Pública, que se limitam a
todos os Direitos de Autor e Direitos Similares que se aplicam ao
seu uso do Material Licenciado e que o Licenciador tem autoridade
para licenciar.
j. Licenciador significa o(s) indivíduo(s) ou entidade(s) que concede(m)
direitos sob esta Licença Pública.
k. Partilhar significa disponibilizar material ao público por qualquer
meio ou processo que requeira permissão sob os Direitos Licenciados,
como reprodução, exibição pública, performance pública,
distribuição, disseminação, comunicação ou importação, e tornar o
material disponível ao público, incluindo de formas que os membros
do público possam aceder ao material a partir de um local e num
momento escolhidos individualmente por eles.
l. Direitos de Base de Dados Sui Generis significa direitos que não
sejam direitos de autor resultantes da Diretiva 96/9/CE do
Parlamento Europeu e do Conselho de 11 de março de 1996 sobre a
proteção jurídica de bases de dados, conforme alterada e/ou
sucedida, bem como outros direitos essencialmente equivalentes em
qualquer parte do mundo.
m. Você significa o indivíduo ou entidade que exerce os Direitos
Licenciados sob esta Licença Pública. Seu tem um significado
correspondente.
Seção 2 -- Âmbito.
a. Concessão de licença.
1. Sujeito aos termos e condições desta Licença Pública, o
Licenciador concede-lhe uma licença mundial, isenta de
royalties, não sublicenciável, não exclusiva e irrevogável
para exercer os Direitos Licenciados no Material Licenciado
para:
a. reproduzir e Partilhar o Material Licenciado, no todo ou
em parte; e
b. produzir, reproduzir e Partilhar Material Adaptado.
2. Exceções e Limitações. Para evitar dúvidas, onde Exceções e
Limitações se aplicam ao seu uso, esta Licença Pública não se
aplica, e não precisa cumprir os seus termos e condições.
3. Prazo. O prazo desta Licença Pública é especificado na Seção
6(a).
4. Meios e formatos; modificações técnicas permitidas. O
Licenciador autoriza-o a exercer os Direitos Licenciados em
todos os meios e formatos, conhecidos agora ou criados no
futuro, e a fazer modificações técnicas necessárias para o
fazer. O Licenciador renuncia e/ou concorda em não invocar
qualquer direito ou autoridade para proibir que faça
modificações técnicas necessárias para exercer os Direitos
Licenciados, incluindo modificações técnicas necessárias para
contornar Medidas Tecnológicas Eficazes. Para os fins desta
Licença Pública, simplesmente fazer modificações autorizadas
por esta Seção 2(a)(4) nunca produz Material Adaptado.
5. Destinatários subsequentes.
a. Oferta do Licenciador -- Material Licenciado. Cada
destinatário do Material Licenciado recebe
automaticamente uma oferta do Licenciador para exercer
os Direitos Licenciados sob os termos e condições desta
Licença Pública.
b. Oferta adicional do Licenciador -- Material Adaptado.
Cada destinatário de Material Adaptado de si recebe
automaticamente uma oferta do Licenciador para exercer
os Direitos Licenciados no Material Adaptado sob as
condições da Licença do Adaptador que aplica.
c. Sem restrições subsequentes. Não pode oferecer ou impor
quaisquer termos ou condições adicionais ou diferentes,
ou aplicar quaisquer Medidas Tecnológicas Eficazes ao
Material Licenciado se isso restringir o exercício dos
Direitos Licenciados por qualquer destinatário do
Material Licenciado.
6. Sem endosso. Nada nesta Licença Pública constitui ou pode ser
interpretado como permissão para afirmar ou implicar que é,
ou que o seu uso do Material Licenciado está, conectado,
patrocinado, endossado ou recebeu status oficial do
Licenciador ou de outros designados para receber atribuição
conforme previsto na Seção 3(a)(1)(A)(i).
b. Outros direitos.
1. Direitos morais, como o direito à integridade, não são
licenciados sob esta Licença Pública, nem direitos de
publicidade, privacidade e/ou outros direitos de personalidade
semelhantes; no entanto, na medida do possível, o Licenciador
renuncia e/ou concorda em não invocar quaisquer desses
direitos detidos pelo Licenciador na medida limitada
necessária para permitir que exerça os Direitos Licenciados,
mas não de outra forma.
2. Direitos de patente e marca registada não são licenciados sob
esta Licença Pública.
3. Na medida do possível, o Licenciador renuncia a qualquer
direito de cobrar royalties de si pelo exercício dos Direitos
Licenciados, seja diretamente ou através de uma sociedade de
gestão coletiva sob qualquer esquema de licenciamento
voluntário ou estatutário renunciável ou obrigatório. Em
todos os outros casos, o Licenciador reserva expressamente
qualquer direito de cobrar tais royalties.
Direitos, então a base de dados na qual tem Direitos de Base de Dados Sui Generis (mas não os seus conteúdos individuais) é Material Adaptado,
incluindo para os propósitos da Secção 3(b); e
c. Deve cumprir as condições da Secção 3(a) se Partilhar todos ou uma parte substancial dos conteúdos da base de dados.
Para evitar dúvidas, esta Secção 4 complementa e não substitui as suas obrigações ao abrigo desta Licença Pública quando os Direitos Licenciados incluem outros Direitos de Autor e Direitos Similares.
### Secção 5 -- Isenção de Garantias e Limitação de Responsabilidade
a. SALVO SE O LICENCIANTE ASSUMIR SEPARADAMENTE OUTRA OBRIGAÇÃO, NA MEDIDA DO POSSÍVEL, O LICENCIANTE OFERECE O MATERIAL LICENCIADO "TAL COMO ESTÁ" E "CONFORME DISPONÍVEL", E NÃO FAZ REPRESENTAÇÕES OU GARANTIAS DE QUALQUER TIPO RELATIVAMENTE AO MATERIAL LICENCIADO, SEJAM EXPRESSAS, IMPLÍCITAS, LEGAIS OU OUTRAS. ISTO INCLUI, SEM LIMITAÇÃO, GARANTIAS DE TITULARIDADE, COMERCIALIZAÇÃO, ADEQUAÇÃO A UM FIM ESPECÍFICO, NÃO-INFRAÇÃO, AUSÊNCIA DE DEFEITOS LATENTES OU OUTROS, PRECISÃO, OU A PRESENÇA OU AUSÊNCIA DE ERROS, SEJAM OU NÃO CONHECIDOS OU DETETÁVEIS. ONDE ISENÇÕES DE GARANTIAS NÃO SÃO PERMITIDAS TOTAL OU PARCIALMENTE, ESTA ISENÇÃO PODE NÃO SE APLICAR A SI.
b. NA MEDIDA DO POSSÍVEL, EM NENHUMA CIRCUNSTÂNCIA O LICENCIANTE SERÁ RESPONSÁVEL PERANTE SI SOB QUALQUER TEORIA LEGAL (INCLUINDO, SEM LIMITAÇÃO, NEGLIGÊNCIA) OU DE OUTRA FORMA POR QUAISQUER PERDAS, CUSTOS, DESPESAS OU DANOS DIRETOS, ESPECIAIS, INDIRETOS, INCIDENTAIS, CONSEQUENCIAIS, PUNITIVOS, EXEMPLARES OU OUTROS DECORRENTES DESTA LICENÇA PÚBLICA OU DO USO DO MATERIAL LICENCIADO, MESMO QUE O LICENCIANTE TENHA SIDO INFORMADO DA POSSIBILIDADE DE TAIS PERDAS, CUSTOS, DESPESAS OU DANOS. ONDE UMA LIMITAÇÃO DE RESPONSABILIDADE NÃO É PERMITIDA TOTAL OU PARCIALMENTE, ESTA LIMITAÇÃO PODE NÃO SE APLICAR A SI.
c. A isenção de garantias e a limitação de responsabilidade acima descritas devem ser interpretadas de forma a, na medida do possível, aproximarem-se de uma isenção absoluta e renúncia de toda a responsabilidade.
### Secção 6 -- Duração e Rescisão
a. Esta Licença Pública aplica-se durante o período dos Direitos de Autor e Direitos Similares aqui licenciados. No entanto, se não cumprir esta Licença Pública, os seus direitos ao abrigo desta Licença Pública terminam automaticamente.
b. Quando o seu direito de usar o Material Licenciado tiver terminado ao abrigo da Secção 6(a), ele será restabelecido:
1. automaticamente na data em que a violação for corrigida, desde que seja corrigida no prazo de 30 dias após a sua descoberta da violação; ou
2. mediante restabelecimento expresso pelo Licenciante.
Para evitar dúvidas, esta Secção 6(b) não afeta qualquer direito que o Licenciante possa ter de procurar reparações pelas suas violações desta Licença Pública.
c. Para evitar dúvidas, o Licenciante pode também oferecer o Material Licenciado sob termos ou condições separadas ou parar de distribuir o Material Licenciado a qualquer momento; no entanto, isso não terminará esta Licença Pública.
d. As Secções 1, 5, 6, 7 e 8 permanecem em vigor após a rescisão desta Licença Pública.
### Secção 7 -- Outros Termos e Condições
a. O Licenciante não estará vinculado por quaisquer termos ou condições adicionais ou diferentes comunicados por si, salvo acordo expresso.
b. Quaisquer arranjos, entendimentos ou acordos relativos ao Material Licenciado que não estejam aqui declarados são separados e independentes dos termos e condições desta Licença Pública.
### Secção 8 -- Interpretação
a. Para evitar dúvidas, esta Licença Pública não reduz, limita, restringe ou impõe condições a qualquer uso do Material Licenciado que possa ser legalmente feito sem permissão ao abrigo desta Licença Pública.
b. Na medida do possível, se qualquer disposição desta Licença Pública for considerada inexequível, será automaticamente reformada na medida mínima necessária para torná-la exequível. Se a disposição não puder ser reformada, será separada desta Licença Pública sem afetar a exequibilidade dos restantes termos e condições.
c. Nenhum termo ou condição desta Licença Pública será renunciado e nenhum incumprimento será consentido, salvo acordo expresso do Licenciante.
d. Nada nesta Licença Pública constitui ou pode ser interpretado como uma limitação ou renúncia de quaisquer privilégios e imunidades que se apliquem ao Licenciante ou a si, incluindo em relação aos processos legais de qualquer jurisdição ou autoridade.
=======================================================================
A Creative Commons não é parte das suas licenças públicas. Não obstante, a Creative Commons pode optar por aplicar uma das suas licenças públicas ao material que publica e, nesses casos, será considerada o “Licenciante”. O texto das licenças públicas da Creative Commons é dedicado ao domínio público sob a Dedicação ao Domínio Público CC0. Exceto para o propósito limitado de indicar que o material é partilhado sob uma licença pública da Creative Commons ou conforme permitido pelas políticas da Creative Commons publicadas em creativecommons.org/policies, a Creative Commons não autoriza o uso da marca "Creative Commons" ou de qualquer outra marca ou logótipo da Creative Commons sem o seu consentimento prévio por escrito, incluindo, sem limitação, em conexão com quaisquer modificações não autorizadas a qualquer uma das suas licenças públicas ou quaisquer outros arranjos, entendimentos ou acordos relativos ao uso de material licenciado. Para evitar dúvidas, este parágrafo não faz parte das licenças públicas.
A Creative Commons pode ser contactada em creativecommons.org.
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, é importante ter em conta que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.

View File

@ -0,0 +1,8 @@
Todas as sketchnotes do currículo podem ser descarregadas aqui.
🎨 Criado por: Tomomi Imura (Twitter: [@girlie_mac](https://twitter.com/girlie_mac), GitHub: [girliemac](https://github.com/girliemac))
[![CC BY-SA 4.0](https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-sa/4.0/)
**Aviso Legal**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original no seu idioma nativo deve ser considerado a fonte autoritativa. Para informações críticas, recomenda-se uma tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas resultantes do uso desta tradução.

View File

@ -0,0 +1,278 @@
# Guia de Resolução de Problemas do AI-For-Beginners
Este guia ajuda a resolver problemas comuns encontrados ao usar ou contribuir para o repositório [AI-For-Beginners](https://github.com/microsoft/AI-For-Beginners). Cada problema inclui contexto, sintomas, explicações e soluções passo a passo.
---
## Índice
- [Problemas Gerais](../..)
- [Problemas de Instalação](../..)
- [Problemas de Configuração](../..)
- [Execução de Notebooks](../..)
- [Problemas de Desempenho](../..)
- [Problemas no Website do Livro](../..)
- [Problemas de Contribuição](../..)
- [FAQ](../..)
- [Obter Ajuda](../..)
---
## Problemas Gerais
### 1. Repositório Não Clona Corretamente
**Contexto:** Clonar permite copiar o repositório para o seu computador.
**Sintomas:**
- Erro: `fatal: repository not found`
- Erro: `Permission denied (publickey)`
**Possíveis Causas:**
- URL do repositório incorreta
- Permissões insuficientes
- Chaves SSH não configuradas
**Soluções:**
1. **Verifique o URL do repositório.**
Use o URL HTTPS:
```
git clone https://github.com/microsoft/AI-For-Beginners.git
```
2. **Troque para HTTPS se o SSH falhar.**
Se aparecer `Permission denied (publickey)`, use o link HTTPS acima em vez de SSH.
3. **Configure chaves SSH (opcional).**
Se quiser usar SSH, siga o [guia de SSH do GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh).
---
## Problemas de Instalação
### 2. Problemas com o Ambiente Python
**Contexto:** O repositório depende de Python e várias bibliotecas.
**Sintomas:**
- Erro: `ModuleNotFoundError: No module named '<package>'`
- Erros de importação ao executar scripts ou notebooks
**Possíveis Causas:**
- Dependências não instaladas
- Versão errada do Python
**Soluções:**
1. **Configure um ambiente virtual.**
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
2. **Instale as dependências.**
```bash
pip install -r requirements.txt
```
3. **Verifique a versão do Python.**
Use Python 3.7 ou mais recente.
```bash
python --version
```
### 3. Jupyter Não Instalado
**Contexto:** Os notebooks são um recurso essencial de aprendizagem.
**Sintomas:**
- Erro: `jupyter: command not found`
- Os notebooks não abrem
**Possíveis Causas:**
- Jupyter não instalado
**Soluções:**
1. **Instale o Jupyter Notebook.**
```bash
pip install notebook
```
ou, se estiver a usar Anaconda:
```bash
conda install notebook
```
2. **Inicie o Jupyter Notebook.**
```bash
jupyter notebook
```
### 4. Conflitos de Versão de Dependências
**Contexto:** Projetos podem falhar se as versões dos pacotes forem incompatíveis.
**Sintomas:**
- Erros ou avisos sobre versões incompatíveis
**Possíveis Causas:**
- Pacotes Python antigos ou conflitantes
**Soluções:**
1. **Instale num ambiente limpo.**
Apague o venv/env do conda antigo e crie um novo.
2. **Use versões exatas.**
Execute sempre:
```bash
pip install -r requirements.txt
```
Se isto falhar, instale manualmente os pacotes em falta conforme descrito no README.
---
## Problemas de Configuração
### 5. Variáveis de Ambiente Não Configuradas
**Contexto:** Alguns módulos podem exigir chaves, tokens ou configurações.
**Sintomas:**
- Erro: `KeyError` ou avisos sobre configurações em falta
**Possíveis Causas:**
- Variáveis de ambiente necessárias não configuradas
**Soluções:**
1. **Verifique por arquivos `.env.example` ou similares.**
2. **Crie um arquivo `.env` e preencha os valores necessários.**
3. **Recarregue o terminal ou IDE após configurar as variáveis de ambiente.**
---
## Execução de Notebooks
### 6. Notebook Não Abre ou Não Executa
**Contexto:** Notebooks Jupyter precisam de configuração adequada.
**Sintomas:**
- O notebook não abre
- O navegador não abre automaticamente
**Possíveis Causas:**
- Jupyter não instalado
- Problemas de configuração do navegador
**Soluções:**
1. **Instale o Jupyter (veja Problemas de Instalação acima).**
2. **Abra os notebooks manualmente.**
- Copie o URL do terminal (ex.: `http://localhost:8888/?token=...`) e cole no navegador.
### 7. Kernel a Falhar ou Congelar
**Contexto:** Kernels de notebooks podem falhar devido a limites de recursos ou erros de código.
**Sintomas:**
- O kernel morre ou reinicia repetidamente
- Erros de falta de memória
**Possíveis Causas:**
- Conjuntos de dados grandes
- Código ou pacotes incompatíveis
**Soluções:**
1. **Reinicie o kernel.**
Use o botão "Restart Kernel" no Jupyter.
2. **Verifique o uso de memória.**
Feche aplicações não utilizadas.
3. **Execute os notebooks em plataformas na nuvem.**
Use o [Google Colab](https://colab.research.google.com/) ou [Azure Notebooks](https://notebooks.azure.com/).
---
## Problemas de Desempenho
### 8. Notebooks a Executar Lentamente
**Contexto:** Algumas tarefas de IA exigem muita memória e CPU.
**Sintomas:**
- Execução lenta
- Ventoinha do portátil a funcionar intensamente
**Possíveis Causas:**
- Conjuntos de dados ou modelos grandes
- Recursos limitados do sistema
**Soluções:**
1. **Use uma plataforma na nuvem.**
- Carregue o notebook no Colab ou Azure Notebooks.
2. **Reduza o tamanho do conjunto de dados.**
- Use dados de amostra para prática.
3. **Feche programas desnecessários.**
- Libere RAM do sistema.
---
## Problemas no Website do Livro
### 9. Capítulo Não Carrega
**Contexto:** O livro online exibe lições e capítulos.
**Sintomas:**
- Um capítulo (ex.: Transformers/BERT) está em falta ou não abre
**Problema Conhecido:**
- [Problema #303](https://github.com/microsoft/AI-For-Beginners/issues/303): “18 Transformers. BERT. não pode ser aberto no website do livro.” Causado por um erro no nome do ficheiro (`READMEtransformers.md` em vez de `README.md`).
**Soluções:**
1. **Verifique erros de renomeação de ficheiros.**
Se for um colaborador, certifique-se de que os ficheiros dos capítulos estão nomeados como `README.md`.
2. **Reporte ficheiros em falta.**
Abra um problema no GitHub com o nome do capítulo e detalhes do erro.
---
## Problemas de Contribuição
### 10. PR Não Aceite ou Builds a Falhar
**Contexto:** As contribuições devem passar nos testes e seguir as diretrizes.
**Sintomas:**
- Pedido de pull rejeitado
- Erros na pipeline CI/CD
**Possíveis Causas:**
- Testes a falhar
- Não seguir os padrões de codificação
**Soluções:**
1. **Leia as diretrizes de contribuição.**
- Siga o [CONTRIBUTING.md](https://github.com/microsoft/AI-For-Beginners/blob/main/CONTRIBUTING.md) do repositório.
2. **Execute os testes localmente antes de enviar.**
3. **Verifique regras de linting ou requisitos de formatação.**
---
## FAQ
### Onde posso encontrar ajuda para módulos específicos?
- Cada módulo geralmente tem seu próprio README. Comece por lá para dicas de configuração e uso.
### Como reporto um bug ou solicito uma funcionalidade?
- [Abra um Problema no GitHub](https://github.com/microsoft/AI-For-Beginners/issues/new) com uma descrição clara e passos para reproduzir.
### Posso pedir ajuda se o meu problema não estiver listado?
- Sim! Pesquise problemas existentes primeiro e, se não encontrar o seu problema, crie um novo.
---
## Obter Ajuda
- **Verifique Problemas:** [Problemas no GitHub](https://github.com/microsoft/AI-For-Beginners/issues)
- **Faça Perguntas:** Use as Discussões no GitHub ou abra um problema.
- **Comunidade:** Veja os links do repositório para opções de chat/fórum.
---
_Última Atualização: 20-09-2025_
---
**Aviso**:
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos pela precisão, é importante notar que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se uma tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.