🌐 Update translations via Co-op Translator

This commit is contained in:
leestott 2025-08-31 12:04:42 +00:00 committed by GitHub
parent f70eecb763
commit 09060b7955
55 changed files with 50233 additions and 79 deletions

View File

@ -1,21 +1,21 @@
<!--
CO_OP_TRANSLATOR_METADATA:
{
"original_hash": "f3a6b0ddf7e6e3f33b2a543baf086dc9",
"translation_date": "2025-08-24T08:51:19+00:00",
"original_hash": "07191303b7ea2aff1d47e2b0fe4bb862",
"translation_date": "2025-08-31T11:32:33+00:00",
"source_file": "README.md",
"language_code": "pt"
}
-->
[![Licença do GitHub](https://img.shields.io/github/license/microsoft/AI-For-Beginners.svg)](https://github.com/microsoft/AI-For-Beginners/blob/main/LICENSE)
[![Contribuidores do GitHub](https://img.shields.io/github/contributors/microsoft/AI-For-Beginners.svg)](https://GitHub.com/microsoft/AI-For-Beginners/graphs/contributors/)
[![Problemas no GitHub](https://img.shields.io/github/issues/microsoft/AI-For-Beginners.svg)](https://GitHub.com/microsoft/AI-For-Beginners/issues/)
[![Pull Requests no GitHub](https://img.shields.io/github/issues-pr/microsoft/AI-For-Beginners.svg)](https://GitHub.com/microsoft/AI-For-Beginners/pulls/)
[![PRs Bem-vindos](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
[![GitHub license](https://img.shields.io/github/license/microsoft/AI-For-Beginners.svg)](https://github.com/microsoft/AI-For-Beginners/blob/main/LICENSE)
[![GitHub contributors](https://img.shields.io/github/contributors/microsoft/AI-For-Beginners.svg)](https://GitHub.com/microsoft/AI-For-Beginners/graphs/contributors/)
[![GitHub issues](https://img.shields.io/github/issues/microsoft/AI-For-Beginners.svg)](https://GitHub.com/microsoft/AI-For-Beginners/issues/)
[![GitHub pull-requests](https://img.shields.io/github/issues-pr/microsoft/AI-For-Beginners.svg)](https://GitHub.com/microsoft/AI-For-Beginners/pulls/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
[![Observadores no GitHub](https://img.shields.io/github/watchers/microsoft/AI-For-Beginners.svg?style=social&label=Watch)](https://GitHub.com/microsoft/AI-For-Beginners/watchers/)
[![Forks no GitHub](https://img.shields.io/github/forks/microsoft/AI-For-Beginners.svg?style=social&label=Fork)](https://GitHub.com/microsoft/AI-For-Beginners/network/)
[![Estrelas no GitHub](https://img.shields.io/github/stars/microsoft/AI-For-Beginners.svg?style=social&label=Star)](https://GitHub.com/microsoft/AI-For-Beginners/stargazers/)
[![GitHub watchers](https://img.shields.io/github/watchers/microsoft/AI-For-Beginners.svg?style=social&label=Watch)](https://GitHub.com/microsoft/AI-For-Beginners/watchers/)
[![GitHub forks](https://img.shields.io/github/forks/microsoft/AI-For-Beginners.svg?style=social&label=Fork)](https://GitHub.com/microsoft/AI-For-Beginners/network/)
[![GitHub stars](https://img.shields.io/github/stars/microsoft/AI-For-Beginners.svg?style=social&label=Star)](https://GitHub.com/microsoft/AI-For-Beginners/stargazers/)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/microsoft/ai-for-beginners/HEAD)
[![Gitter](https://badges.gitter.im/Microsoft/ai-for-beginners.svg)](https://gitter.im/Microsoft/ai-for-beginners?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
@ -23,21 +23,32 @@ CO_OP_TRANSLATOR_METADATA:
# Inteligência Artificial para Iniciantes - Um Currículo
|![ Sketchnote por [(@girlie_mac)](https://twitter.com/girlie_mac) ](./lessons/sketchnotes/ai-overview.png)|
|![Sketchnote por @girlie_mac https://twitter.com/girlie_mac](../../lessons/sketchnotes/ai-overview.png)|
|:---:|
| AI For Beginners - _Sketchnote por [@girlie_mac](https://twitter.com/girlie_mac)_ |
Explore o mundo da **Inteligência Artificial** (IA) com o nosso currículo de 12 semanas e 24 aulas! Inclui lições práticas, questionários e laboratórios. O currículo é voltado para iniciantes e aborda ferramentas como TensorFlow e PyTorch, além de ética na IA.
Explore o mundo da **Inteligência Artificial** (IA) com o nosso currículo de 12 semanas e 24 aulas! Inclui lições práticas, questionários e laboratórios. O currículo é adequado para iniciantes e aborda ferramentas como TensorFlow e PyTorch, além de ética na IA.
### 🌐 Suporte Multilíngue
#### Suportado via GitHub Action (Automatizado e Sempre Atualizado)
[French](../fr/README.md) | [Spanish](../es/README.md) | [German](../de/README.md) | [Russian](../ru/README.md) | [Arabic](../ar/README.md) | [Persian (Farsi)](../fa/README.md) | [Urdu](../ur/README.md) | [Chinese (Simplified)](../zh/README.md) | [Chinese (Traditional, Macau)](../mo/README.md) | [Chinese (Traditional, Hong Kong)](../hk/README.md) | [Chinese (Traditional, Taiwan)](../tw/README.md) | [Japanese](../ja/README.md) | [Korean](../ko/README.md) | [Hindi](../hi/README.md) | [Bengali](../bn/README.md) | [Marathi](../mr/README.md) | [Nepali](../ne/README.md) | [Punjabi (Gurmukhi)](../pa/README.md) | [Portuguese (Portugal)](./README.md) | [Portuguese (Brazil)](../br/README.md) | [Italian](../it/README.md) | [Polish](../pl/README.md) | [Turkish](../tr/README.md) | [Greek](../el/README.md) | [Thai](../th/README.md) | [Swedish](../sv/README.md) | [Danish](../da/README.md) | [Norwegian](../no/README.md) | [Finnish](../fi/README.md) | [Dutch](../nl/README.md) | [Hebrew](../he/README.md) | [Vietnamese](../vi/README.md) | [Indonesian](../id/README.md) | [Malay](../ms/README.md) | [Tagalog (Filipino)](../tl/README.md) | [Swahili](../sw/README.md) | [Hungarian](../hu/README.md) | [Czech](../cs/README.md) | [Slovak](../sk/README.md) | [Romanian](../ro/README.md) | [Bulgarian](../bg/README.md) | [Serbian (Cyrillic)](../sr/README.md) | [Croatian](../hr/README.md) | [Slovenian](../sl/README.md) | [Ukrainian](../uk/README.md) | [Burmese (Myanmar)](../my/README.md)
**Se desejar ter suporte para idiomas adicionais, os idiomas disponíveis estão listados [aqui](https://github.com/Azure/co-op-translator/blob/main/getting_started/supported-languages.md)**
## Junte-se à Comunidade
[![Azure AI Discord](https://dcbadge.limes.pink/api/server/kzRShWzttr)](https://discord.gg/kzRShWzttr)
## O que irá aprender
**[Mapa mental do curso](http://soshnikov.com/courses/ai-for-beginners/mindmap.html)**
**[Mapa Mental do Curso](http://soshnikov.com/courses/ai-for-beginners/mindmap.html)**
Neste currículo, irá aprender:
* Diferentes abordagens para Inteligência Artificial, incluindo a abordagem simbólica "tradicional" com **Representação de Conhecimento** e raciocínio ([GOFAI](https://en.wikipedia.org/wiki/Symbolic_artificial_intelligence)).
* **Redes Neurais** e **Aprendizagem Profunda**, que estão no centro da IA moderna. Vamos ilustrar os conceitos por trás desses tópicos importantes usando código em dois dos frameworks mais populares - [TensorFlow](http://Tensorflow.org) e [PyTorch](http://pytorch.org).
* **Arquiteturas Neurais** para trabalhar com imagens e texto. Vamos abordar modelos recentes, embora possamos não cobrir o estado da arte.
* **Redes Neurais** e **Aprendizagem Profunda**, que estão no centro da IA moderna. Vamos ilustrar os conceitos por trás destes tópicos importantes usando código em dois dos frameworks mais populares - [TensorFlow](http://Tensorflow.org) e [PyTorch](http://pytorch.org).
* **Arquiteturas Neurais** para trabalhar com imagens e texto. Vamos abordar modelos recentes, mas talvez faltem alguns dos mais avançados.
* Abordagens menos populares de IA, como **Algoritmos Genéticos** e **Sistemas Multiagentes**.
O que não será abordado neste currículo:
@ -46,7 +57,7 @@ O que não será abordado neste currículo:
* Casos de uso de **IA em Negócios**. Considere fazer o percurso de aprendizagem [Introdução à IA para utilizadores empresariais](https://docs.microsoft.com/learn/paths/introduction-ai-for-business-users/?WT.mc_id=academic-77998-bethanycheum) no Microsoft Learn, ou [AI Business School](https://www.microsoft.com/ai/ai-business-school/?WT.mc_id=academic-77998-bethanycheum), desenvolvido em cooperação com [INSEAD](https://www.insead.edu/).
* **Aprendizagem Automática Clássica**, que está bem descrita no nosso [Currículo de Aprendizagem Automática para Iniciantes](http://github.com/Microsoft/ML-for-Beginners).
* Aplicações práticas de IA construídas usando **[Serviços Cognitivos](https://azure.microsoft.com/services/cognitive-services/?WT.mc_id=academic-77998-bethanycheum)**. Para isso, recomendamos começar com os módulos Microsoft Learn para [visão](https://docs.microsoft.com/learn/paths/create-computer-vision-solutions-azure-cognitive-services/?WT.mc_id=academic-77998-bethanycheum), [processamento de linguagem natural](https://docs.microsoft.com/learn/paths/explore-natural-language-processing/?WT.mc_id=academic-77998-bethanycheum), **[IA Generativa com Azure OpenAI Service](https://learn.microsoft.com/en-us/training/paths/develop-ai-solutions-azure-openai/?WT.mc_id=academic-77998-bethanycheum)** e outros.
* Aplicações práticas de IA construídas usando **[Serviços Cognitivos](https://azure.microsoft.com/services/cognitive-services/?WT.mc_id=academic-77998-bethanycheum)**. Para isso, recomendamos começar com os módulos do Microsoft Learn para [visão](https://docs.microsoft.com/learn/paths/create-computer-vision-solutions-azure-cognitive-services/?WT.mc_id=academic-77998-bethanycheum), [processamento de linguagem natural](https://docs.microsoft.com/learn/paths/explore-natural-language-processing/?WT.mc_id=academic-77998-bethanycheum), **[IA Generativa com Azure OpenAI Service](https://learn.microsoft.com/en-us/training/paths/develop-ai-solutions-azure-openai/?WT.mc_id=academic-77998-bethanycheum)** e outros.
* Frameworks específicos de **ML na Nuvem**, como [Azure Machine Learning](https://azure.microsoft.com/services/machine-learning/?WT.mc_id=academic-77998-bethanycheum), [Microsoft Fabric](https://learn.microsoft.com/en-us/training/paths/get-started-fabric/?WT.mc_id=academic-77998-bethanycheum), ou [Azure Databricks](https://docs.microsoft.com/learn/paths/data-engineer-azure-databricks?WT.mc_id=academic-77998-bethanycheum). Considere usar os percursos de aprendizagem [Construir e operar soluções de aprendizagem automática com Azure Machine Learning](https://docs.microsoft.com/learn/paths/build-ai-solutions-with-azure-ml-service/?WT.mc_id=academic-77998-bethanycheum) e [Construir e operar soluções de aprendizagem automática com Azure Databricks](https://docs.microsoft.com/learn/paths/build-operate-machine-learning-solutions-azure-databricks/?WT.mc_id=academic-77998-bethanycheum).
* **IA Conversacional** e **Chat Bots**. Há um percurso de aprendizagem separado [Criar soluções de IA conversacional](https://docs.microsoft.com/learn/paths/create-conversational-ai-solutions/?WT.mc_id=academic-77998-bethanycheum), e também pode consultar [este post no blog](https://soshnikov.com/azure/hello-bot-conversational-ai-on-microsoft-platform/) para mais detalhes.
* **Matemática Avançada** por trás da aprendizagem profunda. Para isso, recomendamos [Deep Learning](https://www.amazon.com/Deep-Learning-Adaptive-Computation-Machine/dp/0262035618) de Ian Goodfellow, Yoshua Bengio e Aaron Courville, que também está disponível online em [https://www.deeplearningbook.org/](https://www.deeplearningbook.org/).
@ -61,48 +72,49 @@ Para uma introdução mais leve aos tópicos de _IA na Nuvem_, pode considerar f
| I | [**Introdução à IA**](./lessons/1-Intro/README.md) | | |
| 01 | [Introdução e História da IA](./lessons/1-Intro/README.md) | - | - |
| II | **IA Simbólica** |
| 02 | [Representação de Conhecimento e Sistemas Especialistas](./lessons/2-Symbolic/README.md) | [Sistemas Especialistas](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/2-Symbolic/Animals.ipynb) / [Ontologia](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/2-Symbolic/FamilyOntology.ipynb) /[Grafo de Conceitos](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/2-Symbolic/MSConceptGraph.ipynb) | |
| 02 | [Representação de Conhecimento e Sistemas Especialistas](./lessons/2-Symbolic/README.md) | [Sistemas Especialistas](./lessons/2-Symbolic/Animals.ipynb) / [Ontologia](./lessons/2-Symbolic/FamilyOntology.ipynb) /[Conceito Gráfico](./lessons/2-Symbolic/MSConceptGraph.ipynb) | |
| III | [**Introdução às Redes Neurais**](./lessons/3-NeuralNetworks/README.md) |||
| 03 | [Perceptron](./lessons/3-NeuralNetworks/03-Perceptron/README.md) | [Notebook](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb) | [Laboratório](./lessons/3-NeuralNetworks/03-Perceptron/lab/README.md) |
| 04 | [Perceptron Multicamadas e Criando o nosso próprio Framework](./lessons/3-NeuralNetworks/04-OwnFramework/README.md) | [Notebook](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/04-OwnFramework/OwnFramework.ipynb) | [Laboratório](./lessons/3-NeuralNetworks/04-OwnFramework/lab/README.md) |
| 05 | [Introdução aos Frameworks (PyTorch/TensorFlow) e Overfitting](./lessons/3-NeuralNetworks/05-Frameworks/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/05-Frameworks/IntroPyTorch.ipynb) / [Keras](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/05-Frameworks/IntroKeras.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/05-Frameworks/IntroKerasTF.ipynb) | [Laboratório](./lessons/3-NeuralNetworks/05-Frameworks/lab/README.md) |
| IV | [**Visão Computacional**](./lessons/4-ComputerVision/README.md) | [PyTorch](https://docs.microsoft.com/learn/modules/intro-computer-vision-pytorch/?WT.mc_id=academic-77998-cacaste) / [TensorFlow](https://docs.microsoft.com/learn/modules/intro-computer-vision-TensorFlow/?WT.mc_id=academic-77998-cacaste)| [Explorar Visão Computacional no Microsoft Azure](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum) |
| 06 | [Introdução à Visão Computacional. OpenCV](./lessons/4-ComputerVision/06-IntroCV/README.md) | [Notebook](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/06-IntroCV/OpenCV.ipynb) | [Laboratório](./lessons/4-ComputerVision/06-IntroCV/lab/README.md) |
| 07 | [Redes Neurais Convolucionais](./lessons/4-ComputerVision/07-ConvNets/README.md) & [Arquiteturas de CNN](./lessons/4-ComputerVision/07-ConvNets/CNN_Architectures.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/07-ConvNets/ConvNetsPyTorch.ipynb) /[TensorFlow](https://microsoft.github.io/AI-For-Beginners/lessons/4-ComputerVision/07-ConvNets/ConvNetsTF.ipynb) | [Laboratório](./lessons/4-ComputerVision/07-ConvNets/lab/README.md) |
| 08 | [Redes Pré-treinadas e Transferência de Aprendizagem](./lessons/4-ComputerVision/08-TransferLearning/README.md) e [Dicas de Treinamento](./lessons/4-ComputerVision/08-TransferLearning/TrainingTricks.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/08-TransferLearning/TransferLearningPyTorch.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/05-Frameworks/IntroKerasTF.ipynb) | [Laboratório](./lessons/4-ComputerVision/08-TransferLearning/lab/README.md) |
| 09 | [Autoencoders e VAEs](./lessons/4-ComputerVision/09-Autoencoders/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/09-Autoencoders/AutoEncodersPyTorch.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/09-Autoencoders/AutoencodersTF.ipynb) | |
| 10 | [Redes Adversárias Generativas e Transferência de Estilo Artístico](./lessons/4-ComputerVision/10-GANs/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/10-GANs/GANPyTorch.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/10-GANs/GANTF.ipynb) | |
| 11 | [Detecção de Objetos](./lessons/4-ComputerVision/11-ObjectDetection/README.md) | [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/11-ObjectDetection/ObjectDetection.ipynb) | [Laboratório](./lessons/4-ComputerVision/11-ObjectDetection/lab/README.md) |
| 12 | [Segmentação Semântica. U-Net](./lessons/4-ComputerVision/12-Segmentation/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/12-Segmentation/SemanticSegmentationPytorch.ipynb) / [TensorFlow](../../(https:/github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/12-Segmentation/SemanticSegmentationTF.ipynb)) | |
| V | [**Processamento de Linguagem Natural**](./lessons/5-NLP/README.md) | [PyTorch](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-pytorch/?WT.mc_id=academic-77998-cacaste) /[TensorFlow](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-TensorFlow/?WT.mc_id=academic-77998-cacaste) | [Explore o Processamento de Linguagem Natural no Microsoft Azure](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum)|
| 13 | [Representação de Texto. Bow/TF-IDF](./lessons/5-NLP/13-TextRep/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb) | |
| 14 | [Embeddings Semânticos de Palavras. Word2Vec e GloVe](./lessons/5-NLP/14-Embeddings/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/14-Embeddings/EmbeddingsTF.ipynb) | |
| 15 | [Modelagem de Linguagem. Treinando seus próprios embeddings](./lessons/5-NLP/15-LanguageModeling/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/15-LanguageModeling/CBoW-TF.ipynb) | [Laboratório](./lessons/5-NLP/15-LanguageModeling/lab/README.md) |
| 16 | [Redes Neurais Recorrentes](./lessons/5-NLP/16-RNN/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/16-RNN/RNNPyTorch.ipynb) / [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/16-RNN/RNNTF.ipynb) | |
| 17 | [Redes Recorrentes Generativas](./lessons/5-NLP/17-GenerativeNetworks/README.md) | [PyTorch](https://microsoft.github.io/AI-For-Beginners/lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.md) / [TensorFlow](https://microsoft.github.io/AI-For-Beginners/lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.md) | [Laboratório](./lessons/5-NLP/17-GenerativeNetworks/lab/README.md) |
| 18 | [Transformers. BERT.](./lessons/5-NLP/18-Transformers/READMEtransformers.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb) /[TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/5-NLP/18-Transformers/TransformersTF.ipynb) | |
| 19 | [Reconhecimento de Entidades Nomeadas](./lessons/5-NLP/19-NER/README.md) | [TensorFlow](https://microsoft.github.io/AI-For-Beginners/lessons/5-NLP/19-NER/NER-TF.ipynb) | [Laboratório](./lessons/5-NLP/19-NER/lab/README.md) |
| 20 | [Modelos de Linguagem Grandes, Programação de Prompts e Tarefas de Poucos Exemplos](./lessons/5-NLP/20-LangModels/READMELargeLang.md) | [PyTorch](https://microsoft.github.io/AI-For-Beginners/lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb) | |
| 03 | [Perceptron](./lessons/3-NeuralNetworks/03-Perceptron/README.md) | [Notebook](./lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb) | [Laboratório](./lessons/3-NeuralNetworks/03-Perceptron/lab/README.md) |
| 04 | [Perceptron Multicamadas e Criando o nosso próprio Framework](./lessons/3-NeuralNetworks/04-OwnFramework/README.md) | [Notebook](./lessons/3-NeuralNetworks/04-OwnFramework/OwnFramework.ipynb) | [Laboratório](./lessons/3-NeuralNetworks/04-OwnFramework/lab/README.md) |
| 05 | [Introdução a Frameworks (PyTorch/TensorFlow) e Overfitting](./lessons/3-NeuralNetworks/05-Frameworks/README.md) | [PyTorch](./lessons/3-NeuralNetworks/05-Frameworks/IntroPyTorch.ipynb) / [Keras](./lessons/3-NeuralNetworks/05-Frameworks/IntroKeras.ipynb) / [TensorFlow](./lessons/3-NeuralNetworks/05-Frameworks/IntroKerasTF.ipynb) | [Laboratório](./lessons/3-NeuralNetworks/05-Frameworks/lab/README.md) |
| IV | [**Visão Computacional**](./lessons/4-ComputerVision/README.md) | [PyTorch](https://docs.microsoft.com/learn/modules/intro-computer-vision-pytorch/?WT.mc_id=academic-77998-cacaste) / [TensorFlow](https://docs.microsoft.com/learn/modules/intro-computer-vision-TensorFlow/?WT.mc_id=academic-77998-cacaste)| [Explorar Visão Computacional no Microsoft Azure](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum) |
| 06 | [Introdução à Visão Computacional. OpenCV](./lessons/4-ComputerVision/06-IntroCV/README.md) | [Notebook](./lessons/4-ComputerVision/06-IntroCV/OpenCV.ipynb) | [Laboratório](./lessons/4-ComputerVision/06-IntroCV/lab/README.md) |
| 07 | [Redes Neuronais Convolucionais](./lessons/4-ComputerVision/07-ConvNets/README.md) & [Arquiteturas de CNN](./lessons/4-ComputerVision/07-ConvNets/CNN_Architectures.md) | [PyTorch](./lessons/4-ComputerVision/07-ConvNets/ConvNetsPyTorch.ipynb) /[TensorFlow](./lessons/4-ComputerVision/07-ConvNets/ConvNetsTF.ipynb) | [Laboratório](./lessons/4-ComputerVision/07-ConvNets/lab/README.md) |
| 08 | [Redes Pré-treinadas e Transfer Learning](./lessons/4-ComputerVision/08-TransferLearning/README.md) e [Dicas de Treinamento](./lessons/4-ComputerVision/08-TransferLearning/TrainingTricks.md) | [PyTorch](./lessons/4-ComputerVision/08-TransferLearning/TransferLearningPyTorch.ipynb) / [TensorFlow](./lessons/3-NeuralNetworks/05-Frameworks/IntroKerasTF.ipynb) | [Laboratório](./lessons/4-ComputerVision/08-TransferLearning/lab/README.md) |
| 09 | [Autoencoders e VAEs](./lessons/4-ComputerVision/09-Autoencoders/README.md) | [PyTorch](./lessons/4-ComputerVision/09-Autoencoders/AutoEncodersPyTorch.ipynb) / [TensorFlow](./lessons/4-ComputerVision/09-Autoencoders/AutoencodersTF.ipynb) | |
| 10 | [Redes Adversárias Generativas & Transferência de Estilo Artístico](./lessons/4-ComputerVision/10-GANs/README.md) | [PyTorch](./lessons/4-ComputerVision/10-GANs/GANPyTorch.ipynb) / [TensorFlow](./lessons/4-ComputerVision/10-GANs/GANTF.ipynb) | |
| 11 | [Deteção de Objetos](./lessons/4-ComputerVision/11-ObjectDetection/README.md) | [TensorFlow](./lessons/4-ComputerVision/11-ObjectDetection/ObjectDetection.ipynb) | [Laboratório](./lessons/4-ComputerVision/11-ObjectDetection/lab/README.md) |
| 12 | [Segmentação Semântica. U-Net](./lessons/4-ComputerVision/12-Segmentation/README.md) | [PyTorch](./lessons/4-ComputerVision/12-Segmentation/SemanticSegmentationPytorch.ipynb) / [TensorFlow](./lessons/4-ComputerVision/12-Segmentation/SemanticSegmentationTF.ipynb) | |
| V | [**Processamento de Linguagem Natural**](./lessons/5-NLP/README.md) | [PyTorch](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-pytorch/?WT.mc_id=academic-77998-cacaste) /[TensorFlow](https://docs.microsoft.com/learn/modules/intro-natural-language-processing-TensorFlow/?WT.mc_id=academic-77998-cacaste) | [Explorar Processamento de Linguagem Natural no Microsoft Azure](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum)|
| 13 | [Representação de Texto. Bow/TF-IDF](./lessons/5-NLP/13-TextRep/README.md) | [PyTorch](./lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb) / [TensorFlow](./lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb) | |
| 14 | [Embeddings Semânticos de Palavras. Word2Vec e GloVe](./lessons/5-NLP/14-Embeddings/README.md) | [PyTorch](./lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb) / [TensorFlow](./lessons/5-NLP/14-Embeddings/EmbeddingsTF.ipynb) | |
| 15 | [Modelagem de Linguagem. Treinando os seus próprios embeddings](./lessons/5-NLP/15-LanguageModeling/README.md) | [PyTorch](./lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb) / [TensorFlow](./lessons/5-NLP/15-LanguageModeling/CBoW-TF.ipynb) | [Laboratório](./lessons/5-NLP/15-LanguageModeling/lab/README.md) |
| 16 | [Redes Neuronais Recorrentes](./lessons/5-NLP/16-RNN/README.md) | [PyTorch](./lessons/5-NLP/16-RNN/RNNPyTorch.ipynb) / [TensorFlow](./lessons/5-NLP/16-RNN/RNNTF.ipynb) | |
| 17 | [Redes Recorrentes Generativas](./lessons/5-NLP/17-GenerativeNetworks/README.md) | [PyTorch](./lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.md) / [TensorFlow](./lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.md) | [Laboratório](./lessons/5-NLP/17-GenerativeNetworks/lab/README.md) |
| 18 | [Transformers. BERT.](./lessons/5-NLP/18-Transformers/READMEtransformers.md) | [PyTorch](./lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb) /[TensorFlow](./lessons/5-NLP/18-Transformers/TransformersTF.ipynb) | |
| 19 | [Reconhecimento de Entidades Nomeadas](./lessons/5-NLP/19-NER/README.md) | [TensorFlow](./lessons/5-NLP/19-NER/NER-TF.ipynb) | [Laboratório](./lessons/5-NLP/19-NER/lab/README.md) |
| 20 | [Modelos de Linguagem de Grande Escala, Programação de Prompts e Tarefas de Poucos Exemplos](./lessons/5-NLP/20-LangModels/READMELargeLang.md) | [PyTorch](./lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb) | |
| VI | **Outras Técnicas de IA** || |
| 21 | [Algoritmos Genéticos](./lessons/6-Other/21-GeneticAlgorithms/README.md) | [Notebook](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb) | |
| 22 | [Aprendizagem por Reforço Profundo](./lessons/6-Other/22-DeepRL/README.md) | [PyTorch](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb) /[TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/6-Other/22-DeepRL/CartPole-RL-TF.ipynb) | [Laboratório](./lessons/6-Other/22-DeepRL/lab/README.md) |
| 23 | [Sistemas Multiagentes](./lessons/6-Other/23-MultiagentSystems/README.md) | | |
| 21 | [Algoritmos Genéticos](./lessons/6-Other/21-GeneticAlgorithms/README.md) | [Notebook](./lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb) | |
| 22 | [Aprendizagem por Reforço Profundo](./lessons/6-Other/22-DeepRL/README.md) | [PyTorch](./lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb) /[TensorFlow](./lessons/6-Other/22-DeepRL/CartPole-RL-TF.ipynb) | [Laboratório](./lessons/6-Other/22-DeepRL/lab/README.md) |
| 23 | [Sistemas Multi-Agente](./lessons/6-Other/23-MultiagentSystems/README.md) | | |
| VII | **Ética em IA** | | |
| 24 | [Ética em IA e IA Responsável](./lessons/7-Ethics/README.md) | [Microsoft Learn: Princípios de IA Responsável](https://docs.microsoft.com/learn/paths/responsible-ai-business-principles/?WT.mc_id=academic-77998-cacaste) | |
| IX | **Extras** | | |
| 25 | [Redes Multimodais, CLIP e VQGAN](./lessons/X-Extras/X1-MultiModal/README.md) | [Notebook](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/X-Extras/X1-MultiModal/Clip.ipynb) | |
| 25 | [Redes Multi-Modais, CLIP e VQGAN](./lessons/X-Extras/X1-MultiModal/README.md) | [Notebook](./lessons/X-Extras/X1-MultiModal/Clip.ipynb) | |
## Cada lição contém
* Material de leitura prévia
* Notebooks Jupyter executáveis, que frequentemente são específicos para o framework (**PyTorch** ou **TensorFlow**). O notebook executável também contém muito material teórico, então para entender o tópico é necessário passar por pelo menos uma versão do notebook (PyTorch ou TensorFlow).
* **Laboratórios** disponíveis para alguns tópicos, que oferecem a oportunidade de aplicar o material aprendido a um problema específico.
* Algumas seções contêm links para módulos do [**MS Learn**](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum) que abordam tópicos relacionados.
* Material de leitura prévia
* Notebooks Jupyter executáveis, muitas vezes específicos para o framework (**PyTorch** ou **TensorFlow**). O notebook executável também contém muito material teórico, por isso, para compreender o tópico, é necessário passar por pelo menos uma versão do notebook (seja PyTorch ou TensorFlow).
* **Laboratórios** disponíveis para alguns tópicos, que oferecem a oportunidade de aplicar o material aprendido a um problema específico.
* Algumas secções contêm links para módulos do [**MS Learn**](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum) que abordam tópicos relacionados.
## Começando
## Começar
- Criámos uma [lição de configuração](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/0-course-setup/setup.md) para ajudar na configuração do seu ambiente de desenvolvimento. - Para Educadores, também criámos uma [lição de configuração de currículo](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/0-course-setup/for-teachers.md)!
- Como [Executar o código no VSCode ou Codepace](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/0-course-setup/how-to-run.md)
- Criámos uma [lição de configuração](./lessons/0-course-setup/setup.md) para ajudar a configurar o seu ambiente de desenvolvimento.
- Para Educadores, criámos uma [lição de configuração de currículo](./lessons/0-course-setup/for-teachers.md) também!
- Como [Executar o código no VSCode ou Codepace](./lessons/0-course-setup/how-to-run.md)
Siga estes passos:
@ -110,46 +122,48 @@ Faça um Fork do Repositório: Clique no botão "Fork" no canto superior direito
Clone o Repositório: `git clone https://github.com/microsoft/AI-For-Beginners.git`
Não se esqueça de dar uma estrela (🌟) a este repositório para encontrá-lo mais facilmente depois.
Não se esqueça de dar uma estrela (🌟) a este repositório para encontrá-lo mais facilmente mais tarde.
## Conheça outros Aprendizes
## Conheça outros Estudantes
Junte-se ao nosso [servidor oficial de Discord de IA](https://aka.ms/genai-discord?WT.mc_id=academic-105485-bethanycheum) para conhecer e interagir com outros aprendizes que estão a fazer este curso e obter suporte.
Junte-se ao nosso [servidor oficial de Discord de IA](https://aka.ms/genai-discord?WT.mc_id=academic-105485-bethanycheum) para conhecer e interagir com outros estudantes que estão a fazer este curso e obter suporte.
Se tiver feedback sobre o produto ou dúvidas enquanto desenvolve, visite o nosso [Fórum de Desenvolvedores do Azure AI Foundry](https://aka.ms/foundry/forum)
## Questionários
> **Uma nota sobre os questionários**: Todos os questionários estão contidos na pasta Quiz-app em etc\quiz-app. Eles estão ligados a partir das lições, e a aplicação de questionários pode ser executada localmente ou implantada no Azure; siga as instruções na pasta `quiz-app`. Estão a ser localizados gradualmente.
> **Uma nota sobre os questionários**: Todos os questionários estão contidos na pasta Quiz-app em etc\quiz-app, ou [Online Aqui](https://ff-quizzes.netlify.app/). Eles estão ligados às lições, e a aplicação de questionários pode ser executada localmente ou implantada no Azure; siga as instruções na pasta `quiz-app`. Estão a ser localizados gradualmente.
## Ajuda Necessária
Tem sugestões ou encontrou erros de ortografia ou código? Abra um problema ou crie um pull request.
Tem sugestões ou encontrou erros de ortografia ou código? Abra uma issue ou crie um pull request.
## Agradecimentos Especiais
* **✍️ Autor Principal:** [Dmitry Soshnikov](http://soshnikov.com), PhD
* **🔥 Editora:** [Jen Looper](https://twitter.com/jenlooper), PhD
* **🎨 Ilustradora de Sketchnotes:** [Tomomi Imura](https://twitter.com/girlie_mac)
* **✅ Criadora de Questionários:** [Lateefah Bello](https://github.com/CinnamonXI), [MLSA](https://studentambassadors.microsoft.com/)
* **🙏 Contribuidores Principais:** [Evgenii Pishchik](https://github.com/Pe4enIks)
* **✍️ Autor Principal:** [Dmitry Soshnikov](http://soshnikov.com), PhD
* **🔥 Editora:** [Jen Looper](https://twitter.com/jenlooper), PhD
* **🎨 Ilustradora de Sketchnotes:** [Tomomi Imura](https://twitter.com/girlie_mac)
* **✅ Criadora de Questionários:** [Lateefah Bello](https://github.com/CinnamonXI), [MLSA](https://studentambassadors.microsoft.com/)
* **🙏 Contribuidores Principais:** [Evgenii Pishchik](https://github.com/Pe4enIks)
## Outros Currículos
A nossa equipa produz outros currículos! Veja:
- [Generative AI for Beginners](https://aka.ms/genai-beginners)
- [Generative AI for Beginners .NET](https://github.com/microsoft/Generative-AI-for-beginners-dotnet)
- [Generative AI with JavaScript](https://github.com/microsoft/generative-ai-with-javascript)
- [Generative AI with Java](https://github.com/microsoft/Generative-AI-for-beginners-java)
- [AI for Beginners](https://aka.ms/ai-beginners)
- [Data Science for Beginners](https://aka.ms/datascience-beginners)
- [ML for Beginners](https://aka.ms/ml-beginners)
- [Cybersecurity for Beginners](https://github.com/microsoft/Security-101)
- [Web Dev for Beginners](https://aka.ms/webdev-beginners)
- [IoT for Beginners](https://aka.ms/iot-beginners)
- [XR Development for Beginners](https://github.com/microsoft/xr-development-for-beginners)
- [Mastering GitHub Copilot for Agentic use](https://github.com/microsoft/Mastering-GitHub-Copilot-for-Paired-Programming)
- [Mastering GitHub Copilot for C#/.NET Developers](https://github.com/microsoft/mastering-github-copilot-for-dotnet-csharp-developers)
- [Choose Your Own Copilot Adventure](https://github.com/microsoft/CopilotAdventures)
- [Generative AI for Beginners](https://aka.ms/genai-beginners)
- [Generative AI for Beginners .NET](https://github.com/microsoft/Generative-AI-for-beginners-dotnet)
- [Generative AI with JavaScript](https://github.com/microsoft/generative-ai-with-javascript)
- [Generative AI with Java](https://github.com/microsoft/Generative-AI-for-beginners-java)
- [AI for Beginners](https://aka.ms/ai-beginners)
- [Data Science for Beginners](https://aka.ms/datascience-beginners)
- [ML for Beginners](https://aka.ms/ml-beginners)
- [Cybersecurity for Beginners](https://github.com/microsoft/Security-101)
- [Web Dev for Beginners](https://aka.ms/webdev-beginners)
- [IoT for Beginners](https://aka.ms/iot-beginners)
- [XR Development for Beginners](https://github.com/microsoft/xr-development-for-beginners)
- [Mastering GitHub Copilot for Agentic use](https://github.com/microsoft/Mastering-GitHub-Copilot-for-Paired-Programming)
- [Mastering GitHub Copilot for C#/.NET Developers](https://github.com/microsoft/mastering-github-copilot-for-dotnet-csharp-developers)
- [Choose Your Own Copilot Adventure](https://github.com/microsoft/CopilotAdventures)
---
**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.
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,478 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# Implementar um Sistema Especialista de Animais\n",
"\n",
"Um exemplo do [Currículo de IA para Iniciantes](http://github.com/microsoft/ai-for-beginners).\n",
"\n",
"Neste exemplo, vamos implementar um sistema simples baseado em conhecimento para determinar um animal com base em algumas características físicas. O sistema pode ser representado pela seguinte árvore AND-OR (esta é uma parte da árvore completa, podemos facilmente adicionar mais regras):\n",
"\n",
"![](../../../../lessons/2-Symbolic/images/AND-OR-Tree.png)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## O nosso próprio sistema especialista com inferência retroativa\n",
"\n",
"Vamos tentar definir uma linguagem simples para representação de conhecimento baseada em regras de produção. Utilizaremos classes Python como palavras-chave para definir as regras. Essencialmente, haverá 3 tipos de classes:\n",
"* `Ask` representa uma pergunta que precisa ser feita ao utilizador. Contém o conjunto de respostas possíveis.\n",
"* `If` representa uma regra, sendo apenas uma simplificação sintática para armazenar o conteúdo da regra.\n",
"* `AND`/`OR` são classes para representar os ramos AND/OR da árvore. Elas apenas armazenam a lista de argumentos no seu interior. Para simplificar o código, toda a funcionalidade é definida na classe-mãe `Content`.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"class Ask():\n",
" def __init__(self,choices=['y','n']):\n",
" self.choices = choices\n",
" def ask(self):\n",
" if max([len(x) for x in self.choices])>1:\n",
" for i,x in enumerate(self.choices):\n",
" print(\"{0}. {1}\".format(i,x),flush=True)\n",
" x = int(input())\n",
" return self.choices[x]\n",
" else:\n",
" print(\"/\".join(self.choices),flush=True)\n",
" return input()\n",
"\n",
"class Content():\n",
" def __init__(self,x):\n",
" self.x=x\n",
" \n",
"class If(Content):\n",
" pass\n",
"\n",
"class AND(Content):\n",
" pass\n",
"\n",
"class OR(Content):\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No nosso sistema, a memória de trabalho conteria a lista de **factos** como **pares atributo-valor**. A base de conhecimento pode ser definida como um grande dicionário que mapeia ações (novos factos que devem ser inseridos na memória de trabalho) para condições, expressas como expressões AND-OR. Além disso, alguns factos podem ser `Perguntados`.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"rules = {\n",
" 'default': Ask(['y','n']),\n",
" 'color' : Ask(['red-brown','black and white','other']),\n",
" 'pattern' : Ask(['dark stripes','dark spots']),\n",
" 'mammal': If(OR(['hair','gives milk'])),\n",
" 'carnivor': If(OR([AND(['sharp teeth','claws','forward-looking eyes']),'eats meat'])),\n",
" 'ungulate': If(['mammal',OR(['has hooves','chews cud'])]),\n",
" 'bird': If(OR(['feathers',AND(['flies','lies eggs'])])),\n",
" 'animal:monkey' : If(['mammal','carnivor','color:red-brown','pattern:dark spots']),\n",
" 'animal:tiger' : If(['mammal','carnivor','color:red-brown','pattern:dark stripes']),\n",
" 'animal:giraffe' : If(['ungulate','long neck','long legs','pattern:dark spots']),\n",
" 'animal:zebra' : If(['ungulate','pattern:dark stripes']),\n",
" 'animal:ostrich' : If(['bird','long nech','color:black and white','cannot fly']),\n",
" 'animal:pinguin' : If(['bird','swims','color:black and white','cannot fly']),\n",
" 'animal:albatross' : If(['bird','flies well'])\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para realizar a inferência reversa, iremos definir a classe `Knowledgebase`. Ela conterá:\n",
"* `memória` de trabalho - um dicionário que mapeia atributos a valores\n",
"* `regras` da base de conhecimento no formato definido acima\n",
"\n",
"Os dois métodos principais são:\n",
"* `get` para obter o valor de um atributo, realizando a inferência, se necessário. Por exemplo, `get('color')` obteria o valor de um campo de cor (irá perguntar, se necessário, e armazenar o valor para uso posterior na memória de trabalho). Se pedirmos `get('color:blue')`, ele irá perguntar por uma cor e, em seguida, retornar o valor `y`/`n` dependendo da cor.\n",
"* `eval` realiza a inferência propriamente dita, ou seja, percorre a árvore AND/OR, avalia subobjetivos, etc.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"class KnowledgeBase():\n",
" def __init__(self,rules):\n",
" self.rules = rules\n",
" self.memory = {}\n",
" \n",
" def get(self,name):\n",
" if ':' in name:\n",
" k,v = name.split(':')\n",
" vv = self.get(k)\n",
" return 'y' if v==vv else 'n'\n",
" if name in self.memory.keys():\n",
" return self.memory[name]\n",
" for fld in self.rules.keys():\n",
" if fld==name or fld.startswith(name+\":\"):\n",
" # print(\" + proving {}\".format(fld))\n",
" value = 'y' if fld==name else fld.split(':')[1]\n",
" res = self.eval(self.rules[fld],field=name)\n",
" if res!='y' and res!='n' and value=='y':\n",
" self.memory[name] = res\n",
" return res\n",
" if res=='y':\n",
" self.memory[name] = value\n",
" return value\n",
" # field is not found, using default\n",
" res = self.eval(self.rules['default'],field=name)\n",
" self.memory[name]=res\n",
" return res\n",
" \n",
" def eval(self,expr,field=None):\n",
" # print(\" + eval {}\".format(expr))\n",
" if isinstance(expr,Ask):\n",
" print(field)\n",
" return expr.ask()\n",
" elif isinstance(expr,If):\n",
" return self.eval(expr.x)\n",
" elif isinstance(expr,AND) or isinstance(expr,list):\n",
" expr = expr.x if isinstance(expr,AND) else expr\n",
" for x in expr:\n",
" if self.eval(x)=='n':\n",
" return 'n'\n",
" return 'y'\n",
" elif isinstance(expr,OR):\n",
" for x in expr.x:\n",
" if self.eval(x)=='y':\n",
" return 'y'\n",
" return 'n'\n",
" elif isinstance(expr,str):\n",
" return self.get(expr)\n",
" else:\n",
" print(\"Unknown expr: {}\".format(expr))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos definir a nossa base de conhecimento sobre animais e realizar a consulta. Note que esta chamada irá fazer-lhe perguntas. Pode responder digitando `s`/`n` para perguntas de sim-não, ou especificando um número (0..N) para perguntas com respostas de múltipla escolha mais longas.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hair\n",
"y/n\n",
"sharp teeth\n",
"y/n\n",
"claws\n",
"y/n\n",
"forward-looking eyes\n",
"y/n\n",
"color\n",
"0. red-brown\n",
"1. black and white\n",
"2. other\n",
"has hooves\n",
"y/n\n",
"long neck\n",
"y/n\n",
"long legs\n",
"y/n\n",
"pattern\n",
"0. dark stripes\n",
"1. dark spots\n"
]
},
{
"data": {
"text/plain": [
"'giraffe'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"kb = KnowledgeBase(rules)\n",
"kb.get('animal')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Utilizar o PyKnow para Inferência Progressiva\n",
"\n",
"No próximo exemplo, vamos tentar implementar inferência progressiva utilizando uma das bibliotecas para representação de conhecimento, [PyKnow](https://github.com/buguroo/pyknow/). **PyKnow** é uma biblioteca para criar sistemas de inferência progressiva em Python, projetada para ser semelhante ao sistema clássico antigo [CLIPS](http://www.clipsrules.net/index.html).\n",
"\n",
"Poderíamos também ter implementado encadeamento progressivo por conta própria sem grandes problemas, mas implementações ingênuas geralmente não são muito eficientes. Para um emparelhamento de regras mais eficaz, é utilizado um algoritmo especial chamado [Rete](https://en.wikipedia.org/wiki/Rete_algorithm).\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Collecting git+https://github.com/buguroo/pyknow/\n",
" Cloning https://github.com/buguroo/pyknow/ to /tmp/pip-req-build-3cqeulyl\n",
" Running command git clone --filter=blob:none --quiet https://github.com/buguroo/pyknow/ /tmp/pip-req-build-3cqeulyl\n",
" Resolved https://github.com/buguroo/pyknow/ to commit 48818336f2e9a126f1964f2d8dc22d37ff800fe8\n",
" Preparing metadata (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25hCollecting frozendict==1.2\n",
" Using cached frozendict-1.2.tar.gz (2.6 kB)\n",
" Preparing metadata (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25hCollecting schema==0.6.7\n",
" Using cached schema-0.6.7-py2.py3-none-any.whl (14 kB)\n",
"Building wheels for collected packages: pyknow, frozendict\n",
" Building wheel for pyknow (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25h Created wheel for pyknow: filename=pyknow-1.7.0-py3-none-any.whl size=34228 sha256=b7de5b09292c4007667c72f69b98d5a1b5f7324ff15f9dd8e077c3d5f7aade42\n",
" Stored in directory: /tmp/pip-ephem-wheel-cache-k7jpave7/wheels/81/1a/d3/f6c15dbe1955598a37755215f2a10449e7418500d7bd4b9508\n",
" Building wheel for frozendict (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25h Created wheel for frozendict: filename=frozendict-1.2-py3-none-any.whl size=3148 sha256=2863d55c240d2409cddf05ccfe600591f8478681549fc97555c47c90dc6bb160\n",
" Stored in directory: /home/rg/.cache/pip/wheels/49/ac/f8/cb8120244e710bdb479c86198b03c7b08c3c2d3d2bf448fd6e\n",
"Successfully built pyknow frozendict\n",
"Installing collected packages: schema, frozendict, pyknow\n",
"Successfully installed frozendict-1.2 pyknow-1.7.0 schema-0.6.7\n"
]
}
],
"source": [
"import sys\n",
"!{sys.executable} -m pip install git+https://github.com/buguroo/pyknow/"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"from pyknow import *\n",
"#import pyknow"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Definiremos o nosso sistema como uma classe que herda de `KnowledgeEngine`. Cada regra é definida por uma função separada com a anotação `@Rule`, que especifica quando a regra deve ser acionada. Dentro da regra, podemos adicionar novos factos usando a função `declare`, e adicionar esses factos resultará em mais regras serem chamadas pelo motor de inferência direta.\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"class Animals(KnowledgeEngine):\n",
" @Rule(OR(\n",
" AND(Fact('sharp teeth'),Fact('claws'),Fact('forward looking eyes')),\n",
" Fact('eats meat')))\n",
" def cornivor(self):\n",
" self.declare(Fact('carnivor'))\n",
" \n",
" @Rule(OR(Fact('hair'),Fact('gives milk')))\n",
" def mammal(self):\n",
" self.declare(Fact('mammal'))\n",
"\n",
" @Rule(Fact('mammal'),\n",
" OR(Fact('has hooves'),Fact('chews cud')))\n",
" def hooves(self):\n",
" self.declare('ungulate')\n",
" \n",
" @Rule(OR(Fact('feathers'),AND(Fact('flies'),Fact('lays eggs'))))\n",
" def bird(self):\n",
" self.declare('bird')\n",
" \n",
" @Rule(Fact('mammal'),Fact('carnivor'),\n",
" Fact(color='red-brown'),\n",
" Fact(pattern='dark spots'))\n",
" def monkey(self):\n",
" self.declare(Fact(animal='monkey'))\n",
"\n",
" @Rule(Fact('mammal'),Fact('carnivor'),\n",
" Fact(color='red-brown'),\n",
" Fact(pattern='dark stripes'))\n",
" def tiger(self):\n",
" self.declare(Fact(animal='tiger'))\n",
"\n",
" @Rule(Fact('ungulate'),\n",
" Fact('long neck'),\n",
" Fact('long legs'),\n",
" Fact(pattern='dark spots'))\n",
" def giraffe(self):\n",
" self.declare(Fact(animal='giraffe'))\n",
"\n",
" @Rule(Fact('ungulate'),\n",
" Fact(pattern='dark stripes'))\n",
" def zebra(self):\n",
" self.declare(Fact(animal='zebra'))\n",
"\n",
" @Rule(Fact('bird'),\n",
" Fact('long neck'),\n",
" Fact('cannot fly'),\n",
" Fact(color='black and white'))\n",
" def straus(self):\n",
" self.declare(Fact(animal='ostrich'))\n",
"\n",
" @Rule(Fact('bird'),\n",
" Fact('swims'),\n",
" Fact('cannot fly'),\n",
" Fact(color='black and white'))\n",
" def pinguin(self):\n",
" self.declare(Fact(animal='pinguin'))\n",
"\n",
" @Rule(Fact('bird'),\n",
" Fact('flies well'))\n",
" def albatros(self):\n",
" self.declare(Fact(animal='albatross'))\n",
" \n",
" @Rule(Fact(animal=MATCH.a))\n",
" def print_result(self,a):\n",
" print('Animal is {}'.format(a))\n",
" \n",
" def factz(self,l):\n",
" for x in l:\n",
" self.declare(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Assim que definimos uma base de conhecimento, populamos a nossa memória de trabalho com alguns factos iniciais e, em seguida, chamamos o método `run()` para realizar a inferência. Pode ver, como resultado, que novos factos inferidos são adicionados à memória de trabalho, incluindo o facto final sobre o animal (se configurarmos todos os factos iniciais corretamente).\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Animal is tiger\n"
]
},
{
"data": {
"text/plain": [
"FactList([(0, InitialFact()),\n",
" (1, Fact(color='red-brown')),\n",
" (2, Fact(pattern='dark stripes')),\n",
" (3, Fact('sharp teeth')),\n",
" (4, Fact('claws')),\n",
" (5, Fact('forward looking eyes')),\n",
" (6, Fact('gives milk')),\n",
" (7, Fact('mammal')),\n",
" (8, Fact('carnivor')),\n",
" (9, Fact(animal='tiger'))])"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ex1 = Animals()\n",
"ex1.reset()\n",
"ex1.factz([\n",
" Fact(color='red-brown'),\n",
" Fact(pattern='dark stripes'),\n",
" Fact('sharp teeth'),\n",
" Fact('claws'),\n",
" Fact('forward looking eyes'),\n",
" Fact('gives milk')])\n",
"ex1.run()\n",
"ex1.facts"
]
},
{
"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": {
"kernelspec": {
"display_name": "Python 3.7.4 64-bit (conda)",
"metadata": {
"interpreter": {
"hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
}
},
"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.11.2"
},
"coopTranslator": {
"original_hash": "ab2bd97b0453415b89a469284609a8ce",
"translation_date": "2025-08-31T11:48:17+00:00",
"source_file": "lessons/2-Symbolic/Animals.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,595 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# Ontologia de Relações Familiares\n",
"\n",
"Este exemplo faz parte do [Currículo de IA para Iniciantes](http://github.com/microsoft/ai-for-beginners) e foi inspirado por [este artigo](https://habr.com/post/270857/).\n",
"\n",
"Sempre achei difícil lembrar-me das diferentes relações entre pessoas numa família. Neste exemplo, vamos utilizar uma ontologia que define relações familiares e a árvore genealógica real, e mostrar como podemos realizar inferências automáticas para encontrar todos os parentes.\n",
"\n",
"### Obter a Árvore Genealógica\n",
"\n",
"Como exemplo, vamos utilizar a árvore genealógica da [Família Romanov](https://en.wikipedia.org/wiki/House_of_Romanov). O formato mais comum para descrever relações familiares é o [GEDCOM](https://en.wikipedia.org/wiki/GEDCOM). Vamos usar a árvore genealógica da família Romanov no formato GEDCOM:\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 HEAD\n",
"1 CHAR UTF8\n",
"1 GEDC\n",
"2 VERS 5.5\n",
"0 @0@ INDI\n",
"1 NAME Mihail Fedorovich /Romanov/\n",
"1 SEX M\n",
"1 BIRT\n",
"2 DATE 1613\n",
"1 DEAT \n",
"2 DATE 1645\n",
"1 FAMS @41@\n",
"0 @1@ INDI\n",
"1 NAME Evdokija Lukjanovna /Streshneva/\n",
"1 SEX F\n"
]
}
],
"source": [
"!head -15 data/tsars.ged"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para usar o ficheiro GEDCOM, podemos usar a biblioteca `python-gedcom`:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Collecting python-gedcom\n",
" Downloading python_gedcom-1.0.0-py2.py3-none-any.whl (35 kB)\n",
"Installing collected packages: python-gedcom\n",
"Successfully installed python-gedcom-1.0.0\n"
]
}
],
"source": [
"import sys\n",
"!{sys.executable} -m pip install python-gedcom"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Esta biblioteca elimina alguns dos problemas técnicos com a análise de ficheiros, mas ainda nos dá acesso bastante de baixo nível a todos os indivíduos e famílias na árvore. Aqui está como podemos analisar o ficheiro e mostrar a lista de todos os indivíduos:\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"from gedcom.parser import Parser\n",
"from gedcom.element.individual import IndividualElement\n",
"from gedcom.element.family import FamilyElement\n",
"g = Parser()\n",
"g.parse_file('data/tsars.ged')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"scrolled": true,
"trusted": true
},
"outputs": [
{
"data": {
"text/plain": [
"[('@0@', ('Mihail Fedorovich', 'Romanov')),\n",
" ('@1@', ('Evdokija Lukjanovna', 'Streshneva')),\n",
" ('@2@', ('Aleksej Mihajlovich', 'Romanov')),\n",
" ('@3@', ('Marija Ilinichna', 'Miloslavskaja')),\n",
" ('@4@', ('Natalja Kirillovna', 'Naryshkina')),\n",
" ('@5@', ('Marfa Matveevna', 'Apraksina')),\n",
" ('@6@', ('Fedor Alekseevich', 'Romanov')),\n",
" ('@7@', ('Sofja Aleksevna', 'Romanova')),\n",
" ('@8@', ('Ivan V Alekseevich', 'Romanov')),\n",
" ('@9@', ('Praskovja Fedorovna', 'Saltykova')),\n",
" ('@10@', ('Ekaterina Ivanovna', 'Romanova')),\n",
" ('@11@', ('Anna Ivanovna', 'Romanova')),\n",
" ('@12@', ('Fridrih Vilgelm', 'Kurlandskij')),\n",
" ('@13@', ('Karl Leopold', 'Meklenburg-Shverinskij')),\n",
" ('@14@', ('Anna Leopoldovna', 'Meklenburg-Shverinskaja')),\n",
" ('@15@', ('Anton Ulrih', 'Braunshvejg-Volfenbjuttelskij')),\n",
" ('@16@', ('Ivan VI Antonovich', 'Braunshvejg-Volfenbjuttelskij')),\n",
" ('@17@', ('Petr I Alekseevich', 'Romanov')),\n",
" ('@18@', ('Evdokija Fedorovna', 'Lopuhina')),\n",
" ('@19@', ('Ekaterina I Alekseevna', 'Mihajlova')),\n",
" ('@20@', ('Aleksej Petrovich', 'Romanov')),\n",
" ('@21@', ('Sharlotta Kristina', 'Braunshvejg-Volfenbjuttelskaja')),\n",
" ('@22@', ('Petr II Alekseevich', 'Romanov')),\n",
" ('@23@', ('Anna Petrovna', 'Romanova')),\n",
" ('@24@', ('Elizaveta Petrovna', 'Romanova')),\n",
" ('@25@', ('Karl Fridrih', 'Golshtejn-Gottorpskij')),\n",
" ('@26@', ('Petr III Fedorovich', 'Romanov')),\n",
" ('@27@', ('Ekaterina II', 'Alekseevna')),\n",
" ('@28@', ('Pavel I Petrovich', 'Romanov')),\n",
" ('@29@', ('Natalja Alekseevna', 'Gessen-Darmshtadskaja')),\n",
" ('@30@', ('Marija Fedorovna', 'Vjurtembergskaja')),\n",
" ('@31@', ('Aleksandr I Pavlovich', 'Romanov')),\n",
" ('@32@', ('Elizaveta Alekseevna', 'Baden-Durlahskaja')),\n",
" ('@33@', ('Nikolaj I Pavlovich', 'Romanov')),\n",
" ('@34@', ('Aleksandra Fedorovna', 'Prusskaja')),\n",
" ('@35@', ('Aleksandr II Nikolaevich', 'Romanov')),\n",
" ('@36@', ('Marija Aleksandrovna', 'Gessenskaja')),\n",
" ('@37@', ('Aleksandr III Aleksandrovich', 'Romanov')),\n",
" ('@38@', ('Marija Fedorovna', 'Datskaja')),\n",
" ('@39@', ('Nikolaj II Aleksandrovich', 'Romanov')),\n",
" ('@40@', ('Aleksandra Fedorovna', 'Gessenskaja'))]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d = g.get_element_dictionary()\n",
"[ (k,v.get_name()) for k,v in d.items() if isinstance(v,IndividualElement)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Aqui está como podemos obter informações sobre famílias. Note que isso nos dá uma lista de **identificadores**, e precisamos convertê-los em nomes se quisermos mais clareza:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('@41@', ['@0@', '@1@', '@2@']),\n",
" ('@42@', ['@2@', '@3@', '@6@', '@7@', '@8@']),\n",
" ('@43@', ['@8@', '@9@', '@10@', '@11@']),\n",
" ('@44@', ['@13@', '@10@', '@14@']),\n",
" ('@45@', ['@15@', '@14@', '@16@']),\n",
" ('@46@', ['@2@', '@4@', '@17@']),\n",
" ('@47@', ['@17@', '@18@', '@20@']),\n",
" ('@48@', ['@20@', '@21@', '@22@']),\n",
" ('@49@', ['@17@', '@19@', '@23@', '@24@']),\n",
" ('@50@', ['@25@', '@23@', '@26@']),\n",
" ('@51@', ['@26@', '@27@', '@28@']),\n",
" ('@52@', ['@28@', '@30@', '@31@', '@33@']),\n",
" ('@53@', ['@33@', '@34@', '@35@']),\n",
" ('@54@', ['@35@', '@36@', '@37@']),\n",
" ('@55@', ['@37@', '@38@', '@39@'])]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d = g.get_element_dictionary()\n",
"[ (k,[x.get_value() for x in v.get_child_elements()]) for k,v in d.items() if isinstance(v,FamilyElement)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Obtendo a Ontologia Familiar\n",
"\n",
"A seguir, vamos analisar a [ontologia familiar](https://raw.githubusercontent.com/blokhin/genealogical-trees/master/data/header.ttl) definida como um conjunto de triplos da Web Semântica. Esta ontologia define relações como `isUncleOf`, `isCousinOf` e muitas outras. Todas essas relações são definidas em termos de predicados básicos `isMotherOf`, `isFatherOf`, `isBrotherOf` e `isSisterOf`. Utilizaremos raciocínio automático para deduzir todas as outras relações usando a ontologia.\n",
"\n",
"Aqui está um exemplo de definição da propriedade `isAuntOf`, que é definida como uma composição de `isSisterOf` e `isParentOf` (*Tia é a irmã de um dos pais*).\n",
"\n",
"```\n",
"fhkb:isAuntOf a owl:ObjectProperty ;\n",
" rdfs:domain fhkb:Woman ;\n",
" rdfs:range fhkb:Person ;\n",
" owl:propertyChainAxiom ( fhkb:isSisterOf fhkb:isParentOf ) .\n",
"```\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"@prefix fhkb: <http://www.example.com/genealogy.owl#> .\n",
"@prefix owl: <http://www.w3.org/2002/07/owl#> .\n",
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n",
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n",
"@prefix xml: <http://www.w3.org/XML/1998/namespace> .\n",
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n",
"\n",
"<http://www.example.com/genealogy.owl#> a owl:Ontology .\n",
"\n",
"fhkb:DomainEntity a owl:Class .\n",
"\n",
"fhkb:Man a owl:Class ;\n",
" owl:equivalentClass [ a owl:Class ;\n",
" owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;\n",
" owl:onProperty fhkb:hasSex ;\n",
" owl:someValuesFrom fhkb:Male ] ) ] .\n",
"\n",
"fhkb:Woman a owl:Class ;\n",
" owl:equivalentClass [ a owl:Class ;\n",
" owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;\n"
]
}
],
"source": [
"!head -20 data/onto.ttl"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Construção de Ontologia para Inferência\n",
"\n",
"Para simplificar, iremos criar um único ficheiro de ontologia que incluirá as regras originais da ontologia familiar e os factos sobre os indivíduos do nosso ficheiro GEDCOM. Vamos analisar o ficheiro GEDCOM, extrair informações sobre famílias e indivíduos e convertê-las em triplos.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"!cp data/onto.ttl .\n",
"\n",
"gedcom_dict = g.get_element_dictionary()\n",
"individuals, marriages = {}, {}\n",
"\n",
"def term2id(el):\n",
" return \"i\" + el.get_pointer().replace('@', '').lower()\n",
"\n",
"out = open(\"onto.ttl\",\"a\")\n",
"\n",
"for k, v in gedcom_dict.items():\n",
" if isinstance(v,IndividualElement):\n",
" children, siblings = set(), set()\n",
" idx = term2id(v)\n",
"\n",
" title = v.get_name()[0] + \" \" + v.get_name()[1]\n",
" title = title.replace('\"', '').replace('[', '').replace(']', '').replace('(', '').replace(')', '').strip()\n",
"\n",
" own_families = g.get_families(v, 'FAMS')\n",
" for fam in own_families:\n",
" children |= set(term2id(i) for i in g.get_family_members(fam, \"CHIL\"))\n",
"\n",
" parent_families = g.get_families(v, 'FAMC')\n",
" if len(parent_families):\n",
" for member in g.get_family_members(parent_families[0], \"CHIL\"): # NB adoptive families i.e len(parent_families)>1 are not considered (TODO?)\n",
" if member.get_pointer() == v.get_pointer():\n",
" continue\n",
" siblings.add(term2id(member))\n",
"\n",
" if idx in individuals:\n",
" children |= individuals[idx].get('children', set())\n",
" siblings |= individuals[idx].get('siblings', set())\n",
" individuals[idx] = {'sex': v.get_gender().lower(), 'children': children, 'siblings': siblings, 'title': title}\n",
"\n",
" elif isinstance(v,FamilyElement):\n",
" wife, husb, children = None, None, set()\n",
" children = set(term2id(i) for i in g.get_family_members(v, \"CHIL\"))\n",
"\n",
" try:\n",
" wife = g.get_family_members(v, \"WIFE\")[0]\n",
" wife = term2id(wife)\n",
" if wife in individuals: individuals[wife]['children'] |= children\n",
" else: individuals[wife] = {'children': children}\n",
" except IndexError: pass\n",
" try:\n",
" husb = g.get_family_members(v, \"HUSB\")[0]\n",
" husb = term2id(husb)\n",
" if husb in individuals: individuals[husb]['children'] |= children\n",
" else: individuals[husb] = {'children': children}\n",
" except IndexError: pass\n",
"\n",
" if wife and husb: marriages[wife + husb] = (term2id(v), wife, husb)\n",
"\n",
"for idx, val in individuals.items():\n",
" added_terms = ''\n",
" if val['sex'] == 'f':\n",
" parent_predicate, sibl_predicate = \"isMotherOf\", \"isSisterOf\"\n",
" else:\n",
" parent_predicate, sibl_predicate = \"isFatherOf\", \"isBrotherOf\"\n",
" if len(val['children']):\n",
" added_terms += \" ;\\n fhkb:\" + parent_predicate + \" \" + \", \".join([\"fhkb:\" + i for i in val['children']])\n",
" if len(val['siblings']):\n",
" added_terms += \" ;\\n fhkb:\" + sibl_predicate + \" \" + \", \".join([\"fhkb:\" + i for i in val['siblings']])\n",
" out.write(\"fhkb:%s a owl:NamedIndividual, owl:Thing%s ;\\n rdfs:label \\\"%s\\\" .\\n\" % (idx, added_terms, val['title']))\n",
"\n",
"for k, v in marriages.items():\n",
" out.write(\"fhkb:%s a owl:NamedIndividual, owl:Thing ;\\n fhkb:hasFemalePartner fhkb:%s ;\\n fhkb:hasMalePartner fhkb:%s .\\n\" % v)\n",
"\n",
"out.write(\"[] a owl:AllDifferent ;\\n owl:distinctMembers (\")\n",
"for idx in individuals.keys():\n",
" out.write(\" fhkb:\" + idx)\n",
"for k, v in marriages.items():\n",
" out.write(\" fhkb:\" + v[0])\n",
"out.write(\" ) .\")\n",
"out.close()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" fhkb:hasFemalePartner fhkb:i34 ;\n",
" fhkb:hasMalePartner fhkb:i33 .\n",
"fhkb:i54 a owl:NamedIndividual, owl:Thing ;\n",
" fhkb:hasFemalePartner fhkb:i36 ;\n",
" fhkb:hasMalePartner fhkb:i35 .\n",
"fhkb:i55 a owl:NamedIndividual, owl:Thing ;\n",
" fhkb:hasFemalePartner fhkb:i38 ;\n",
" fhkb:hasMalePartner fhkb:i37 .\n",
"[] a owl:AllDifferent ;\n",
" owl:distinctMembers ( fhkb:i0 fhkb:i1 fhkb:i2 fhkb:i3 fhkb:i4 fhkb:i5 fhkb:i6 fhkb:i7 fhkb:i8 fhkb:i9 fhkb:i10 fhkb:i11 fhkb:i12 fhkb:i13 fhkb:i14 fhkb:i15 fhkb:i16 fhkb:i17 fhkb:i18 fhkb:i19 fhkb:i20 fhkb:i21 fhkb:i22 fhkb:i23 fhkb:i24 fhkb:i25 fhkb:i26 fhkb:i27 fhkb:i28 fhkb:i29 fhkb:i30 fhkb:i31 fhkb:i32 fhkb:i33 fhkb:i34 fhkb:i35 fhkb:i36 fhkb:i37 fhkb:i38 fhkb:i39 fhkb:i40 fhkb:i41 fhkb:i42 fhkb:i43 fhkb:i44 fhkb:i45 fhkb:i46 fhkb:i47 fhkb:i48 fhkb:i49 fhkb:i50 fhkb:i51 fhkb:i52 fhkb:i53 fhkb:i54 fhkb:i55 ) ."
]
}
],
"source": [
"!tail onto.ttl"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Fazer Inferência\n",
"\n",
"Agora queremos ser capazes de usar esta ontologia para inferência e para realizar consultas. Vamos utilizar a [RDFLib](https://github.com/RDFLib), uma biblioteca para ler Grafos RDF em diferentes formatos, realizar consultas, etc.\n",
"\n",
"Para inferência lógica, utilizaremos a biblioteca [OWL-RL](https://github.com/RDFLib/OWL-RL), que nos permite construir o **Fecho** do Grafo RDF, ou seja, adicionar todos os conceitos e relações possíveis que podem ser inferidos.\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: rdflib in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (6.3.2)\n",
"Requirement already satisfied: isodate<0.7.0,>=0.6.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib) (0.6.1)\n",
"Requirement already satisfied: pyparsing<4,>=2.1.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib) (3.0.9)\n",
"Requirement already satisfied: six in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from isodate<0.7.0,>=0.6.0->rdflib) (1.16.0)\n",
"Collecting git+https://github.com/RDFLib/OWL-RL.git\n",
" Cloning https://github.com/RDFLib/OWL-RL.git to /tmp/pip-req-build-lbfzwi3m\n",
" Running command git clone --filter=blob:none --quiet https://github.com/RDFLib/OWL-RL.git /tmp/pip-req-build-lbfzwi3m\n",
" Resolved https://github.com/RDFLib/OWL-RL.git to commit a77e1791b88b54aace609bc6000aac14c7add4ff\n",
" Preparing metadata (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25hRequirement already satisfied: rdflib>=6.0.2 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from owlrl==6.0.2) (6.3.2)\n",
"Requirement already satisfied: isodate<0.7.0,>=0.6.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib>=6.0.2->owlrl==6.0.2) (0.6.1)\n",
"Requirement already satisfied: pyparsing<4,>=2.1.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib>=6.0.2->owlrl==6.0.2) (3.0.9)\n",
"Requirement already satisfied: six in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from isodate<0.7.0,>=0.6.0->rdflib>=6.0.2->owlrl==6.0.2) (1.16.0)\n"
]
}
],
"source": [
"!{sys.executable} -m pip install rdflib\n",
"!{sys.executable} -m pip install git+https://github.com/RDFLib/OWL-RL.git"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos abrir o ficheiro de ontologia e ver quantos triplos contém:\n"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Triplets found:669\n"
]
}
],
"source": [
"import rdflib\n",
"from owlrl import DeductiveClosure, OWLRL_Extension\n",
"\n",
"g = rdflib.Graph()\n",
"g.parse(\"onto.ttl\", format=\"turtle\")\n",
"\n",
"print(\"Triplets found:%d\" % len(g))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora vamos construir o fecho e ver como o número de triplos aumenta:\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Triplets after inference:4246\n"
]
}
],
"source": [
"DeductiveClosure(OWLRL_Extension).expand(g)\n",
"print(\"Triplets after inference:%d\" % len(g))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Consultar Parentes \n",
"\n",
"Agora podemos consultar o grafo para ver diferentes relações entre pessoas. Podemos usar a linguagem **SPARQL** juntamente com o método `query`. No nosso caso, vamos ver todos os **tios** na nossa árvore genealógica:\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Fedor Alekseevich Romanov is uncle of Ekaterina Ivanovna Romanova\n",
"Aleksandr I Pavlovich Romanov is uncle of Aleksandr II Nikolaevich Romanov\n",
"Fedor Alekseevich Romanov is uncle of Anna Ivanovna Romanova\n"
]
}
],
"source": [
"qres = g.query(\n",
" \"\"\"SELECT DISTINCT ?aname ?bname\n",
" WHERE {\n",
" ?a fhkb:isUncleOf ?b .\n",
" ?a rdfs:label ?aname .\n",
" ?b rdfs:label ?bname .\n",
" }\"\"\")\n",
"\n",
"for row in qres:\n",
" print(\"%s is uncle of %s\" % row)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sinta-se à vontade para experimentar com diferentes relações familiares. Por exemplo, pode explorar a relação `isAncestorOf`, que define recursivamente todos os antepassados de uma determinada pessoa.\n",
"\n",
"Por fim, vamos finalizar!\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"!rm onto.ttl"
]
},
{
"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": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
},
"kernelspec": {
"display_name": "Python 3.6",
"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.11.2"
},
"coopTranslator": {
"original_hash": "6537d5597320e27b6052b4377b8ff8bb",
"translation_date": "2025-08-31T11:47:43+00:00",
"source_file": "lessons/2-Symbolic/FamilyOntology.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,548 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"## Microsoft Concept Graph\n",
"\n",
"[Microsoft Concept Graph](https://concept.research.microsoft.com/) é uma grande taxonomia de termos extraídos da internet, com relações de tipo `is-a` entre conceitos.\n",
"\n",
"O Context Graph está disponível em duas formas:\n",
" * Ficheiro de texto grande para download\n",
" * API REST\n",
"\n",
"Estatísticas:\n",
" * 5401933 conceitos únicos,\n",
" * 12551613 instâncias únicas\n",
" * 87603947 relações de tipo `is-a`\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Utilizar o Serviço Web\n",
"\n",
"O serviço web oferece diferentes chamadas para estimar a probabilidade de um conceito pertencer a diferentes grupos. Mais informações estão disponíveis [aqui](https://concept.research.microsoft.com/Home/Api). \n",
"Aqui está o URL de exemplo para realizar a chamada: `https://concept.research.microsoft.com/api/Concept/ScoreByProb?instance=microsoft&topK=10`\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"trusted": true
},
"outputs": [
{
"data": {
"text/plain": [
"{'company': 0.6105356614382954,\n",
" 'vendor': 0.08858636677518003,\n",
" 'client': 0.048239124001183784,\n",
" 'firm': 0.045476965571668145,\n",
" 'large company': 0.043109401203511886,\n",
" 'organization': 0.043010752688172046,\n",
" 'corporation': 0.035908059583703265,\n",
" 'brand': 0.03383644076156654,\n",
" 'software company': 0.027522935779816515,\n",
" 'technology company': 0.023774292196902438}"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import urllib\n",
"import json\n",
"import ssl\n",
"\n",
"def http(x):\n",
" ssl._create_default_https_context = ssl._create_unverified_context\n",
" response = urllib.request.urlopen(x)\n",
" data = response.read()\n",
" return data.decode('utf-8')\n",
"\n",
"def query(x):\n",
" return json.loads(http(\"https://concept.research.microsoft.com/api/Concept/ScoreByProb?instance={}&topK=10\".format(urllib.parse.quote(x))))\n",
"\n",
"query('microsoft')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos tentar categorizar os títulos das notícias usando conceitos principais. Para obter os títulos das notícias, usaremos o serviço [NewsApi.org](http://newsapi.org). É necessário obter a sua própria chave de API para utilizar o serviço - aceda ao site e registe-se no plano de desenvolvedor gratuito.\n"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"newsapi_key = '<your API key here>'\n",
"def get_news(country='us'):\n",
" res = json.loads(http(\"https://newsapi.org/v2/top-headlines?country={0}&apiKey={1}\".format(country,newsapi_key)))\n",
" return res['articles']\n",
"\n",
"all_titles = [x['title'] for x in get_news('us')+get_news('gb')]"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['Covid-19 Live Updates: Vaccines and Boosters News - The New York Times',\n",
" 'Ukrainians Flee Mariupol as Russian Forces Push to Take Port City - The Wall Street Journal',\n",
" 'Bond Yields Jump, Stock Futures Rise After Powell Says Fed Is Ready to Be More Aggressive - The Wall Street Journal',\n",
" 'Putin critic Alexei Navalny found guilty by Russian court - New York Post ',\n",
" \"Supreme Court nominee Ketanji Brown Jackson will face questions at confirmation hearing's second day - CNN\",\n",
" '2 teachers killed at Swedish high school, student arrested - ABC News',\n",
" 'Clues to Covid-19s Next Moves Come From Sewers - The Wall Street Journal',\n",
" 'Republicans to roll dice by grilling Jackson over child-pornography sentencing decisions | TheHill - The Hill',\n",
" 'Clear sign Putin considering using chemical weapons in Ukraine, claims President Biden - The Independent',\n",
" 'NASA confirms there are 5,000 planets outside our solar system - Daily Mail',\n",
" \"US stocks whipsawed overnight after Fed Chair Powell's remarks - Fox Business\",\n",
" \"'We've learned absolutely nothing': Tests could again be in short supply if Covid surges - POLITICO\",\n",
" \"Duchess of Cambridge swaps khaki jungle gear for Vampire's Wife dress on Belize trip - Daily Mail\",\n",
" 'China searches for victims, flight recorders after first plane crash in 12 years - Reuters',\n",
" 'Second superyacht linked to Russian oligarch Abramovich docks in Turkey - Reuters',\n",
" 'Live updates: Russia stops talks with Japan over sanctions - The Associated Press - en Español',\n",
" 'Powers Remain and Threats Lurk as Womens Sweet 16 Is Set - The New York Times',\n",
" 'Webb Space Telescope Begins Multi-Instrument Alignment - SciTechDaily',\n",
" \"UConn vs UCF - NCAA women's tournament second-round highlights - March Madness\",\n",
" 'Bucking Republican Trend, Indiana Governor Vetoes Transgender Sports Bill - The New York Times',\n",
" \"Maggie Fox dead: Coronation Street and Shameless actress dies after 'sudden accident' - Mirror Online - The Mirror\",\n",
" 'China plane crash live: Search for survivors continues as witness describes moment flight fell from sky - The Independent',\n",
" 'Daniel Morgan murder: damning report condemns Met police - The Guardian',\n",
" 'What to expect from Rishi Sunaks Spring Statement - BBC.com',\n",
" 'UK and Republic of Ireland in line to host Euro 2028 after no one else bids - The Guardian',\n",
" \"Friends beg Vladimir Putin's 'lover' to persuade him to end Ukraine invasion - The Mirror\",\n",
" 'Brass Eyes outtakes show the brutal TV comedy was the tip of an iceberg - The Guardian',\n",
" \"Vladimir Putin threatens civilians to break Mariupol's spirit - The Times\",\n",
" 'Shell U-turn on Cambo oilfield would threaten green targets, say campaigners - The Guardian',\n",
" 'St Helens dog attack: Girl aged 17 months killed at home - BBC',\n",
" \"PlayStation to buy 'Assassin's Creed' veteran Jade Raymond's Haven Studios - NME\",\n",
" 'Clear sign Putin considering using chemical weapons in Ukraine, claims President Biden - The Independent',\n",
" 'NASA confirms there are 5,000 planets outside our solar system - Daily Mail',\n",
" 'Nintendo Switch finally has folders • Eurogamer.net - Eurogamer.net',\n",
" 'FA to “find a solution” as Liverpool fan group blasts “shambolic” Wembley travel - This Is Anfield',\n",
" 'Manchester United transfer news LIVE Erik ten Hag latest and Man Utd manager updates - Manchester Evening News',\n",
" 'Inflation raises cost of UK government borrowing in February; crude oil up again business live - The Guardian',\n",
" 'Alexei Navalny: Kremlin critic found guilty of large-scale fraud and contempt of court by Russian court - Sky News',\n",
" \"UK prepares to nationalize Russia natural gas giant Gazprom's retail unit - Business Insider\",\n",
" 'Zaghari-Ratcliffe: Hunt calls for inquiry into delay over Iran debt payment - The Guardian']"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"all_titles"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Em primeiro lugar, queremos ser capazes de extrair substantivos dos títulos das notícias. Vamos usar a biblioteca `TextBlob` para fazer isso, o que simplifica muito tarefas típicas de PLN como esta.\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Requirement already satisfied: textblob in c:\\winapp\\miniconda3\\lib\\site-packages (0.17.1)\n",
"Requirement already satisfied: nltk>=3.1 in c:\\winapp\\miniconda3\\lib\\site-packages (from textblob) (3.5)\n",
"Requirement already satisfied: joblib in c:\\winapp\\miniconda3\\lib\\site-packages (from nltk>=3.1->textblob) (1.0.1)\n",
"Requirement already satisfied: regex in c:\\winapp\\miniconda3\\lib\\site-packages (from nltk>=3.1->textblob) (2021.11.10)\n",
"Requirement already satisfied: tqdm in c:\\winapp\\miniconda3\\lib\\site-packages (from nltk>=3.1->textblob) (4.61.2)\n",
"Requirement already satisfied: click in c:\\winapp\\miniconda3\\lib\\site-packages (from nltk>=3.1->textblob) (8.0.3)\n",
"Requirement already satisfied: colorama in c:\\winapp\\miniconda3\\lib\\site-packages (from click->nltk>=3.1->textblob) (0.4.4)\n",
"Finished.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"[nltk_data] Downloading package brown to\n",
"[nltk_data] C:\\Users\\dmitryso\\AppData\\Roaming\\nltk_data...\n",
"[nltk_data] Package brown is already up-to-date!\n",
"[nltk_data] Downloading package punkt to\n",
"[nltk_data] C:\\Users\\dmitryso\\AppData\\Roaming\\nltk_data...\n",
"[nltk_data] Package punkt is already up-to-date!\n",
"[nltk_data] Downloading package wordnet to\n",
"[nltk_data] C:\\Users\\dmitryso\\AppData\\Roaming\\nltk_data...\n",
"[nltk_data] Package wordnet is already up-to-date!\n",
"[nltk_data] Downloading package averaged_perceptron_tagger to\n",
"[nltk_data] C:\\Users\\dmitryso\\AppData\\Roaming\\nltk_data...\n",
"[nltk_data] Package averaged_perceptron_tagger is already up-to-\n",
"[nltk_data] date!\n",
"[nltk_data] Downloading package conll2000 to\n",
"[nltk_data] C:\\Users\\dmitryso\\AppData\\Roaming\\nltk_data...\n",
"[nltk_data] Package conll2000 is already up-to-date!\n",
"[nltk_data] Downloading package movie_reviews to\n",
"[nltk_data] C:\\Users\\dmitryso\\AppData\\Roaming\\nltk_data...\n",
"[nltk_data] Package movie_reviews is already up-to-date!\n"
]
}
],
"source": [
"import sys\n",
"!{sys.executable} -m pip install textblob\n",
"!{sys.executable} -m textblob.download_corpora\n",
"from textblob import TextBlob"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"trusted": true
},
"outputs": [
{
"data": {
"text/plain": [
"{'covid-19 live updates': 1,\n",
" 'vaccines': 1,\n",
" 'boosters': 1,\n",
" 'york': 4,\n",
" 'ukrainians flee mariupol': 1,\n",
" 'forces push': 1,\n",
" 'port city': 1,\n",
" 'wall street journal': 3,\n",
" 'bond yields': 1,\n",
" 'futures rise': 1,\n",
" 'powell says fed': 1,\n",
" 'ready': 1,\n",
" 'be': 1,\n",
" 'aggressive': 1,\n",
" 'putin': 3,\n",
" 'alexei navalny': 2,\n",
" 'russian': 2,\n",
" 'supreme court nominee': 1,\n",
" 'ketanji brown jackson': 1,\n",
" \"confirmation hearing 's\": 1,\n",
" 'cnn': 1,\n",
" 'swedish': 1,\n",
" 'high school': 1,\n",
" 'abc': 1,\n",
" 'clues': 1,\n",
" 'covid-19': 1,\n",
" ' s': 2,\n",
" 'moves': 1,\n",
" 'sewers': 1,\n",
" 'roll dice': 1,\n",
" 'jackson': 1,\n",
" 'decisions |': 1,\n",
" 'thehill': 1,\n",
" 'clear': 2,\n",
" 'chemical weapons': 2,\n",
" 'ukraine': 3,\n",
" 'claims president': 2,\n",
" 'biden': 2,\n",
" 'nasa': 2,\n",
" 'solar system': 2,\n",
" 'daily mail': 3,\n",
" 'us stocks': 1,\n",
" 'fed chair powell': 1,\n",
" \"'s remarks\": 1,\n",
" 'fox': 1,\n",
" \"'we 've\": 1,\n",
" 'tests': 1,\n",
" 'covid': 1,\n",
" 'politico': 1,\n",
" 'duchess': 1,\n",
" 'cambridge': 1,\n",
" 'swaps khaki jungle gear': 1,\n",
" 'vampire': 1,\n",
" 'wife': 1,\n",
" 'belize': 1,\n",
" 'china': 2,\n",
" 'flight recorders': 1,\n",
" 'plane crash': 1,\n",
" 'reuters': 2,\n",
" 'russian oligarch': 1,\n",
" 'abramovich': 1,\n",
" 'live': 1,\n",
" 'russia': 2,\n",
" 'stops talks': 1,\n",
" 'japan': 1,\n",
" 'español': 1,\n",
" 'powers remain': 1,\n",
" 'threats lurk': 1,\n",
" 'set': 1,\n",
" 'webb': 1,\n",
" 'telescope begins multi-instrument alignment': 1,\n",
" 'scitechdaily': 1,\n",
" 'uconn': 1,\n",
" 'ucf': 1,\n",
" 'ncaa': 1,\n",
" \"women 's tournament second-round highlights\": 1,\n",
" 'march madness': 1,\n",
" 'bucking republican trend': 1,\n",
" 'indiana': 1,\n",
" 'vetoes transgender': 1,\n",
" 'bill': 1,\n",
" 'maggie fox': 1,\n",
" 'coronation': 1,\n",
" 'shameless': 1,\n",
" \"'sudden accident\": 1,\n",
" 'mirror online': 1,\n",
" 'mirror': 2,\n",
" 'plane crash ': 1,\n",
" 'search': 1,\n",
" 'moment flight': 1,\n",
" 'daniel morgan': 1,\n",
" 'report condemns': 1,\n",
" 'met': 1,\n",
" 'guardian': 6,\n",
" 'rishi sunak': 1,\n",
" ' s spring': 1,\n",
" 'statement': 1,\n",
" 'bbc.com': 1,\n",
" 'uk': 3,\n",
" 'ireland': 1,\n",
" 'euro': 1,\n",
" 'vladimir putin': 2,\n",
" \"'s 'lover\": 1,\n",
" 'brass eye': 1,\n",
" ' s outtakes': 1,\n",
" 'brutal tv comedy': 1,\n",
" 'threatens civilians': 1,\n",
" 'mariupol': 1,\n",
" \"'s spirit\": 1,\n",
" 'shell u-turn': 1,\n",
" 'cambo': 1,\n",
" 'green targets': 1,\n",
" 'st helens': 1,\n",
" 'dog attack': 1,\n",
" 'girl': 1,\n",
" 'bbc': 1,\n",
" 'playstation': 1,\n",
" \"'assassin 's\": 1,\n",
" 'creed': 1,\n",
" 'jade raymond': 1,\n",
" 'haven studios': 1,\n",
" 'nme': 1,\n",
" 'nintendo switch': 1,\n",
" 'folders •': 1,\n",
" 'eurogamer.net': 2,\n",
" 'fa': 1,\n",
" 'solution ”': 1,\n",
" 'liverpool': 1,\n",
" 'fan group blasts “ shambolic ”': 1,\n",
" 'wembley': 1,\n",
" 'anfield': 1,\n",
" 'manchester': 1,\n",
" 'live erik': 1,\n",
" 'hag': 1,\n",
" 'utd': 1,\n",
" 'manager updates': 1,\n",
" 'manchester evening': 1,\n",
" 'inflation': 1,\n",
" 'government borrowing': 1,\n",
" 'february': 1,\n",
" 'crude oil': 1,\n",
" ' business': 1,\n",
" 'kremlin': 1,\n",
" 'large-scale fraud': 1,\n",
" 'sky': 1,\n",
" 'natural gas': 1,\n",
" 'gazprom': 1,\n",
" 'retail unit': 1,\n",
" 'insider': 1,\n",
" 'zaghari-ratcliffe': 1,\n",
" 'hunt': 1,\n",
" 'iran': 1,\n",
" 'debt payment': 1}"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"w = {}\n",
"for x in all_titles:\n",
" for n in TextBlob(x).noun_phrases:\n",
" if n in w:\n",
" w[n].append(x)\n",
" else:\n",
" w[n]=[x]\n",
"{ x:len(w[x]) for x in w.keys()}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos ver que os substantivos não nos dão grandes grupos temáticos. Vamos substituir os substantivos por termos mais gerais obtidos a partir do gráfico de conceitos. Isto vai levar algum tempo, porque estamos a fazer chamadas REST para cada frase nominal.\n"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"trusted": true
},
"outputs": [],
"source": [
"w = {}\n",
"for x in all_titles:\n",
" for noun in TextBlob(x).noun_phrases:\n",
" terms = query(noun.replace(' ','%20'))\n",
" for term in [u for u in terms.keys() if terms[u]>0.1]:\n",
" if term in w:\n",
" w[term].append(x)\n",
" else:\n",
" w[term]=[x]"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"trusted": true
},
"outputs": [
{
"data": {
"text/plain": [
"{'city': 9,\n",
" 'brand': 4,\n",
" 'place': 9,\n",
" 'town': 4,\n",
" 'factor': 4,\n",
" 'film': 4,\n",
" 'nation': 11,\n",
" 'state': 5,\n",
" 'person': 4,\n",
" 'organization': 5,\n",
" 'publication': 10,\n",
" 'market': 5,\n",
" 'economy': 4,\n",
" 'company': 6,\n",
" 'newspaper': 6,\n",
" 'relationship': 6}"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{ x:len(w[x]) for x in w.keys() if len(w[x])>3}"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"ECONOMY:\n",
"China searches for victims, flight recorders after first plane crash in 12 years - Reuters\n",
"Live updates: Russia stops talks with Japan over sanctions - The Associated Press - en Español\n",
"China plane crash live: Search for survivors continues as witness describes moment flight fell from sky - The Independent\n",
"UK prepares to nationalize Russia natural gas giant Gazprom's retail unit - Business Insider\n",
"\n",
"NATION:\n",
"Clear sign Putin considering using chemical weapons in Ukraine, claims President Biden - The Independent\n",
"Duchess of Cambridge swaps khaki jungle gear for Vampire's Wife dress on Belize trip - Daily Mail\n",
"China searches for victims, flight recorders after first plane crash in 12 years - Reuters\n",
"Live updates: Russia stops talks with Japan over sanctions - The Associated Press - en Español\n",
"Live updates: Russia stops talks with Japan over sanctions - The Associated Press - en Español\n",
"China plane crash live: Search for survivors continues as witness describes moment flight fell from sky - The Independent\n",
"UK and Republic of Ireland in line to host Euro 2028 after no one else bids - The Guardian\n",
"Friends beg Vladimir Putin's 'lover' to persuade him to end Ukraine invasion - The Mirror\n",
"Clear sign Putin considering using chemical weapons in Ukraine, claims President Biden - The Independent\n",
"UK prepares to nationalize Russia natural gas giant Gazprom's retail unit - Business Insider\n",
"Zaghari-Ratcliffe: Hunt calls for inquiry into delay over Iran debt payment - The Guardian\n",
"\n",
"PERSON:\n",
"Clear sign Putin considering using chemical weapons in Ukraine, claims President Biden - The Independent\n",
"Duchess of Cambridge swaps khaki jungle gear for Vampire's Wife dress on Belize trip - Daily Mail\n",
"Second superyacht linked to Russian oligarch Abramovich docks in Turkey - Reuters\n",
"Clear sign Putin considering using chemical weapons in Ukraine, claims President Biden - The Independent\n"
]
}
],
"source": [
"print('\\nECONOMY:\\n'+'\\n'.join(w['economy']))\n",
"print('\\nNATION:\\n'+'\\n'.join(w['nation']))\n",
"print('\\nPERSON:\\n'+'\\n'.join(w['person']))"
]
},
{
"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": {
"kernelspec": {
"display_name": "Python 3.7.4 64-bit (conda)",
"metadata": {
"interpreter": {
"hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
}
},
"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.9.5"
},
"coopTranslator": {
"original_hash": "4087f998407d06ceb2947016ba4605d0",
"translation_date": "2025-08-31T11:47:55+00:00",
"source_file": "lessons/2-Symbolic/MSConceptGraph.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
<!--
CO_OP_TRANSLATOR_METADATA:
{
"original_hash": "7336583e4630220c835335da640016db",
"translation_date": "2025-08-24T09:05:15+00:00",
"original_hash": "ba5d1eb353d20d3e7181066b3c424b99",
"translation_date": "2025-08-31T11:33:44+00:00",
"source_file": "lessons/3-NeuralNetworks/03-Perceptron/lab/README.md",
"language_code": "pt"
}
@ -13,11 +13,11 @@ Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/micro
## Tarefa
Utilizando o código que desenvolvemos nesta lição para classificação binária de dígitos manuscritos do MNIST, crie um classificador multi-classe que seja capaz de reconhecer qualquer dígito. Calcule a precisão da classificação nos conjuntos de treino e teste, e apresente a matriz de confusão.
Usando o código que desenvolvemos nesta lição para classificação binária de dígitos manuscritos do MNIST, crie um classificador multi-classe que seja capaz de reconhecer qualquer dígito. Calcule a precisão da classificação nos conjuntos de dados de treino e teste, e apresente a matriz de confusão.
## Dicas
1. Para cada dígito, crie um conjunto de dados para o classificador binário de "este dígito vs. todos os outros dígitos".
1. Para cada dígito, crie um conjunto de dados para um classificador binário de "este dígito vs. todos os outros dígitos".
1. Treine 10 perceptrons diferentes para classificação binária (um para cada dígito).
1. Defina uma função que classifique um dígito de entrada.
@ -25,7 +25,9 @@ Utilizando o código que desenvolvemos nesta lição para classificação binár
## Notebook Inicial
Comece o trabalho prático abrindo [PerceptronMultiClass.ipynb](../../../../../../lessons/3-NeuralNetworks/03-Perceptron/lab/PerceptronMultiClass.ipynb)
Comece o trabalho prático abrindo [PerceptronMultiClass.ipynb](PerceptronMultiClass.ipynb)
---
**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.
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.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,183 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Classificação de Dígitos MNIST com o nosso Próprio Framework\n",
"\n",
"Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).\n",
"\n",
"### Leitura do Conjunto de Dados\n",
"\n",
"Este código faz o download do conjunto de dados a partir do repositório na internet. Também pode copiar manualmente o conjunto de dados do diretório `/data` do repositório do Currículo de IA.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"tags": []
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
" % Total % Received % Xferd Average Speed Time Time Time Current\n",
" Dload Upload Total Spent Left Speed\n",
"\n",
" 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\n",
"100 9.9M 100 9.9M 0 0 9.9M 0 0:00:01 --:--:-- 0:00:01 15.8M\n"
]
}
],
"source": [
"!rm *.pkl\n",
"!wget https://raw.githubusercontent.com/microsoft/AI-For-Beginners/main/data/mnist.pkl.gz\n",
"!gzip -d mnist.pkl.gz"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import pickle\n",
"with open('mnist.pkl','rb') as f:\n",
" MNIST = pickle.load(f)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"labels = MNIST['Train']['Labels']\n",
"data = MNIST['Train']['Features']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos ver qual é a forma dos dados que temos:\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(42000, 784)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Dividir os Dados\n",
"\n",
"Vamos usar o Scikit Learn para dividir os dados entre conjunto de treino e conjunto de teste:\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Train samples: 33600, test samples: 8400\n"
]
}
],
"source": [
"from sklearn.model_selection import train_test_split\n",
"\n",
"features_train, features_test, labels_train, labels_test = train_test_split(data,labels,test_size=0.2)\n",
"\n",
"print(f\"Train samples: {len(features_train)}, test samples: {len(features_test)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Instruções\n",
"\n",
"1. Pegue o código base do framework da lição e cole-o neste notebook ou (ainda melhor) num módulo Python separado.\n",
"1. Defina e treine um perceptron de uma camada, observando a precisão de treino e validação durante o processo.\n",
"1. Tente perceber se ocorreu overfitting e ajuste os parâmetros da camada para melhorar a precisão.\n",
"1. Repita os passos anteriores para perceptrons de 2 e 3 camadas. Experimente diferentes funções de ativação entre as camadas.\n",
"1. Tente responder às seguintes perguntas:\n",
" - A função de ativação entre as camadas afeta o desempenho da rede?\n",
" - Precisamos de uma rede de 2 ou 3 camadas para esta tarefa?\n",
" - Teve algum problema ao treinar a rede? Especialmente à medida que o número de camadas aumentou.\n",
" - Como se comportam os pesos da rede durante o treino? Pode traçar o valor absoluto máximo dos pesos em função da época para compreender a relação.\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": {
"kernelspec": {
"display_name": "Python 3.7.4 64-bit (conda)",
"metadata": {
"interpreter": {
"hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
}
},
"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.9.5"
},
"orig_nbformat": 2,
"coopTranslator": {
"original_hash": "6fa055f484eb5d6bdf41166a356d3abf",
"translation_date": "2025-08-31T11:49:51+00:00",
"source_file": "lessons/3-NeuralNetworks/04-OwnFramework/lab/MyFW_MNIST.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

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

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,108 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Detecção de Movimento da Palma usando Fluxo Óptico\n",
"\n",
"Este laboratório faz parte do [Currículo de IA para Iniciantes](http://aka.ms/ai-beginners).\n",
"\n",
"Considere [este vídeo](../../../../../../lessons/4-ComputerVision/06-IntroCV/lab/palm-movement.mp4), no qual a palma de uma pessoa se move para a esquerda/direita/cima/baixo sobre um fundo estável.\n",
"\n",
"**O seu objetivo** será usar Fluxo Óptico para determinar quais partes do vídeo contêm movimentos para cima/baixo/esquerda/direita.\n",
"\n",
"Comece por obter os frames do vídeo conforme descrito na aula:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora, calcula os fotogramas de fluxo ótico denso conforme descrito na aula e converte o fluxo ótico denso para coordenadas polares:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Construa um histograma de direções para cada frame do fluxo óptico. Um histograma mostra quantos vetores se enquadram em determinados intervalos e deve separar as diferentes direções de movimento no frame.\n",
"\n",
"> Também pode ser útil eliminar todos os vetores cuja magnitude esteja abaixo de um determinado limite. Isso ajudará a remover pequenos movimentos extras no vídeo, como olhos e cabeça.\n",
"\n",
"Faça o gráfico dos histogramas para alguns dos frames.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ao observar os histogramas, deve ser bastante simples determinar a direção do movimento. É necessário selecionar os intervalos que correspondem às direções cima/baixo/esquerda/direita e que estão acima de um determinado limite.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Parabéns! Se concluíste todos os passos acima, terminaste o laboratório!\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": {
"language_info": {
"name": "python"
},
"orig_nbformat": 4,
"coopTranslator": {
"original_hash": "153d9e417e079bf62f8f693002d0deaf",
"translation_date": "2025-08-31T11:42:50+00:00",
"source_file": "lessons/4-ComputerVision/06-IntroCV/lab/MovementDetection.ipynb",
"language_code": "pt"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

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

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

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

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

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

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,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,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,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,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,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,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,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,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,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,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,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
}

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