diff --git a/translations/pt/README.md b/translations/pt/README.md index 3ced603c..840c713c 100644 --- a/translations/pt/README.md +++ b/translations/pt/README.md @@ -1,21 +1,21 @@ -[![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. \ No newline at end of file +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. \ No newline at end of file diff --git a/translations/pt/lessons/2-Symbolic/Animals.ipynb b/translations/pt/lessons/2-Symbolic/Animals.ipynb new file mode 100644 index 00000000..e49fcf1c --- /dev/null +++ b/translations/pt/lessons/2-Symbolic/Animals.ipynb @@ -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 +} \ No newline at end of file diff --git a/translations/pt/lessons/2-Symbolic/FamilyOntology.ipynb b/translations/pt/lessons/2-Symbolic/FamilyOntology.ipynb new file mode 100644 index 00000000..d1c862e2 --- /dev/null +++ b/translations/pt/lessons/2-Symbolic/FamilyOntology.ipynb @@ -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: .\n", + "@prefix owl: .\n", + "@prefix rdf: .\n", + "@prefix rdfs: .\n", + "@prefix xml: .\n", + "@prefix xsd: .\n", + "\n", + " 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 +} \ No newline at end of file diff --git a/translations/pt/lessons/2-Symbolic/MSConceptGraph.ipynb b/translations/pt/lessons/2-Symbolic/MSConceptGraph.ipynb new file mode 100644 index 00000000..0d9872d1 --- /dev/null +++ b/translations/pt/lessons/2-Symbolic/MSConceptGraph.ipynb @@ -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 = ''\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-19’s 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 Women’s 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 Sunak’s 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 Eye’s 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 +} \ No newline at end of file diff --git a/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb b/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb new file mode 100644 index 00000000..4b1e41a0 --- /dev/null +++ b/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb @@ -0,0 +1,1092 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Perceptron\n", + "\n", + "> Este notebook faz parte do [AI for Beginners Curricula](http://github.com/microsoft/ai-for-beginners). Visite o repositório para o conjunto completo de materiais de aprendizagem.\n", + "\n", + "Como discutimos, o perceptron permite resolver um **problema de classificação binária**, ou seja, classificar exemplos de entrada em duas classes - podemos chamá-las de **positiva** e **negativa**.\n", + "\n", + "Primeiro, vamos importar algumas bibliotecas necessárias.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import pylab\n", + "from matplotlib import gridspec\n", + "from sklearn.datasets import make_classification\n", + "import numpy as np\n", + "from ipywidgets import interact, interactive, fixed\n", + "import ipywidgets as widgets\n", + "import pickle\n", + "import os\n", + "import gzip\n", + "\n", + "# pick the seed for reproducability - change it to explore the effects of random variations\n", + "np.random.seed(1)\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Problema Simples\n", + "\n", + "Para começar, vamos abordar um problema simples, onde temos duas características de entrada. Por exemplo, na medicina, podemos querer classificar tumores como benignos ou malignos, dependendo do seu tamanho e idade.\n", + "\n", + "Vamos gerar um conjunto de dados de classificação aleatório utilizando a função `make_classification` da biblioteca SciKit Learn:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Features:\n", + " [[-1.7441838 -1.3952037 ]\n", + " [ 2.5921783 -0.08124504]\n", + " [ 0.9218062 0.91789985]\n", + " [-0.8437018 -0.18738253]]\n", + "Labels:\n", + " [-1 -1 1 -1]\n" + ] + } + ], + "source": [ + "n = 50\n", + "X, Y = make_classification(n_samples = n, n_features=2,\n", + " n_redundant=0, n_informative=2, flip_y=0)\n", + "Y = Y*2-1 # convert initial 0/1 values into -1/1\n", + "X = X.astype(np.float32); Y = Y.astype(np.int32) # features - float, label - int\n", + "\n", + "# Split the dataset into training and test\n", + "train_x, test_x = np.split(X, [ n*8//10])\n", + "train_labels, test_labels = np.split(Y, [n*8//10])\n", + "print(\"Features:\\n\",train_x[0:4])\n", + "print(\"Labels:\\n\",train_labels[0:4])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos também plotar o conjunto de dados:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":11: UserWarning: Matplotlib is currently using module://ipykernel.pylab.backend_inline, which is a non-GUI backend, so cannot show the figure.\n", + " fig.show()\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYQAAAEnCAYAAACpNTSTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAA9CklEQVR4nO3dd5ycdbX48c/ZlrqbuqmbZEMICUmAAEuTFnox0hQviCAXNSpNUAQUf4IFG15FvIKCBe8VFASlJICh90ASSEICJATY9N7L9jm/P87M3clm6u70Oe/Xa1/JzDwzz3c3m+c833aOqCrOOedcSbYb4JxzLjd4QHDOOQd4QHDOORfkAcE55xzgAcE551yQBwTnnHOABwSXYSKiCXzVd/EclwY/p7YT7723q+dPJRF5QURe6MT7pojILSLi/8ddwsT3IbhMEpEjOzz1L2A+cEvYc02q+nYXzlENjAHeVtWmJN87BqjqyvlTKRQMVHVKku+7BbgZKFfV1pQ3zBWksmw3wBUXVZ0V/lhEmoCNHZ/vcEwpdvOS0IVNVTcAGzrZvg878z7nCoF3J13OCQ733CoiN4rIx0AzcICIdBeRX4nIQhHZKSJrReRxERnf4f17DRmJSL2I/FVELhCR90Rkl4jMEZFjOrx3jyEjEakNftZXROQHIrJGRLYGz1vT4b09ReQuEdkkIjtE5F8i8ong+y9N4Pu+QETeF5EmEVkkIudGOCbuzyCsdwDQEhqKC3v9+yLylohsE5GNIvJchJ6bK0LeQ3C56lLgI+A6YBewGugGVAI/AtYA/YHLgVkiMl5V18b5zGOBccD/AxqBHwLTRaRWVbfGee+3gdeAy4BBwH8B9wHHhx1zN3A+Nvw1BzgpeExcInIycD8wA/gmUA38GigHFocdmsjP4A9ADfBF4BigrcPphgO/AlYCvYDPAy+JSJ2qLkikva5Aqap/+VfWvoB64K8dnlMsAPSI895SoCewA7g27PlLg59R2+E8W4B+Yc/VBY/7XNhz9wL1YY9rg8e82OHc1wWfHxZ8PA4IANd3OO6O4HGXxvleXgXeBUrCnjsi+N4XOvEzuCX43rIEfoZlWND5dbZ/H/wru18+ZORy1VOq2tDxSRH5rIi8ISJbgVas99AbuyDH87qqbgl7/E7wz5EJvHdGh8cd33sEIMA/Ohz3ULwPDs6RHAY8pKqB0POq+gYWyDoe35WfASJysog8LyKbgu9vAfZL9P2ucHlAcLlqTccnRORTwAPAe8DnsIvwYdgEcvcEPnNz+ANtX4GU9HuBju8dGvxzfYfj1iXw2QOxoaFIx+7xXFd/BiJyCPAEsBMbUjoy+P75ibzfFTafQ3C5KtJ66AuApap6aegJESnHxtGzLRTABgEfhz0/OIH3bsTu0iMdOxhYFva4qz+DT2O9gvNUtSXsM/oBWxP8DFegvIfg8klP7GIW7mJsHDzb3sCC2Pkdnu/4eC+q2gbMBj4TvpFMRI7A5jDCJfozCPVgekR4fxthAVdETiSxYTNX4LyH4PLJU8A5IvIrYDpwKHA1OXBnq6qLReR+4IfBi/pc4ETgU8FDAlHfbG4GZgKPiMjvsVVG3wc6rpxK9GfwbvDPb4rIk0Cbqs4Jvv8a4F4R+TM2d/D/gFWJf7euUHkPweWTe4Bbgf8AHgc+iV1wt2WzUWGmAX8Crsd2YE8Ergi+FrONqvoMcBE2sftP4FvYhXtxh0MT/RlMB+7ElqS+jvVAUNV/YwHk6OAxlwGXAEuT+D5dgfLUFc6lkYh8C/gZtgR2ebbb41wsPmTkXIqIyFRgEjAPGyI6Ftuv8KAHA5cPPCA4lzo7gHOAG7EdwKuwjWk3x3iPcznDh4ycc84BPqnsnHMuyAOCc845wAOCc865IA8IzjnnAA8IzjnngjwgOOecAzwgOOecC/KA4JxzDvCA4JxzLsgDgnPOOcADgnPOuSAPCM455wAPCM4554LyOv31wIEDtba2NtvNcM65vDJ37tyNqlrd8fm8Dgi1tbXMmTMn281wzrm8IiLLIj3vQ0bOOecADwjO5afWVmhuBi9w5VIor4eMnCsqqvDuuzBzJixYYI/794czz4SjjoJevbLdQpfnPCA4lw9U4R//gOnToXdvGDECSkpg1y743/+FZ5+Fb33LAoRzneRDRs7lg9deg8cfh1GjYNAgCwZgvYLRo2HzZvjNb3wIyXWJBwTncl0gAI89ZoGgtDTyMUOGQH09LF2a0aa5wuJDRs5FogrLlsHzz8PChfbc+PFwwgkwZgyIZK4tq1fDhg0wcmT0Y0SgrAxmz4axYzPXNldQPCA411EgAA89BDNmQEWFjcuLwJw58MorFhQuvjj63XqqNTQkFoAqKmD79vS3xxUsDwjOdfT00zZeX1u750V/6FALFs89B1VVcN556W+Lqs0PrF4NLS123gEDIgeIpiYYODD9bXIFywOCc+Gam+HRR2H48Mg9gJISW+Hz5JNw2mnpXeq5fDn84Q82dLV6tc0RdOsGPXvCIYfsefFXtb0JRxyRvva4gueTys6Fe/99G6Lp3j36MeXldrcemltIhxUr4NZbrXdQWwuf+IQNCfXubb2UV16B9evtWFU7fvJkqKmJ/bmqsGWLvbehIX3td3nJewjOhdu5M7HjRNI3Xq9qewtKS9t7AYMH2wV/wQJ7XFZmcxp1dbYXYcIEmDYt+lxDIGATzjNmWPAoKbGvY4+1ns7gwen5Xlxe8YDgXLhYPYNwqjZ0kw6rV8OSJbbnINzo0RYgQkNIW7ZAjx7wla/AxIkWJCIJBCzAPPOMzT+MHGmBo7UVXn7Z9jjccIN9vitqOTNkJCIjROR5EXlPRBaJyNez3SZXhMaNswtrS0v0Y9ra7IK6//7pacPq1Xb3Huluv7ISJk2CU0+FI4+0Pw86KHowAJg1y4LB6NHQt2/755aV2VxJt27w61/bpLQrajkTEIBW4Juquj9wJHCFiEzIcptcsenVC046CVaujLzrV9VeO/ro/EgToWrpLqqr23c3d9SvH2zd2j4c5YpWzgQEVV2jqm8F/74DeA8Ynt1WuaJ03nm2iuejj+xCqWpf27fDxx9bL+Kii9J3/poaG+aJl4ZCNfZmNbANbWvWWM8ill694M03k2unKzg5OYcgIrXAwcAbWW6KK0YVFXDFFTZp++STNmYvYvsQpk2Dww+3Y9Jl6FAbjqqvjz7Zu3Wr3fWPGxf7s1paog8/hSsrg927O9NaV0ByLiCISG/gYeAaVd1rGYeITAOmAYyMd3fkXGeVldkY/RFHtNcd6NYtcykrLr4YfvQjWLfOchiFzhtaNrprF9x4Y/RhoJCqKntPW1vsndW7d1sgckUtpwKCiJRjweA+Vf1npGNU9W7gboC6ujpP7VjsWlrgnXfg3/+2O/myMhvuOeGE1KyaEbFAEMm2bfDGGzb2HgjAfvvZfoFBg7p+3mHD4LvfhXvvtRVH4QGhpgauvhr23Tf+51RW2tLU+fOjX/BV7ed4zDFdb7fLa6I5ki5XRAT4C7BZVa9J5D11dXXqNZWL2O7dlvL53XftTrhPH7swb9xoK2Y+8xmYOjU9d/Uvvwx/+YvdeVdV2Tl27LDzn3UWnHNOas6rCqtW2d4BVctqOnp0cp+9YgV8//v28+k4lxBK4nfooXDllZlN2ueyRkTmqmpdx+dzqYdwNHAx8I6IzAs+9x1VfSJ7TXI57Y9/hMWLbSdv+IVs2DBbY//gg7Zu/6ijUnvet96Ce+6x84TvW+jb18778MPWqzjzzK6fS8R6BPF2IMcyYgRcdx3ccYftfK6stJ7Uzp0WOA89FL78ZQ8GLncCgqq+AvhvpEvMqlUwd65t3op0ISsrs6GbRx6xeYB4Y+2JCgTg73+3Cd1Im9jKymzlzyOPwJQp6du8lqzx4+G22+xn9sYb0NgIBxwAxx0H++zjwcABORQQnEvK3Lk2SRrrQlZZacMhK1bsveu3sz76yJZyxvq8igqbiF6wwCamc0WvXhYAjjsu2y1xOSpn9iE4l5TNmxNb+hmqO5wqW7cmdjddWmqBw7k84j0E1zltbbBoETz1lJVtFLF8OqecYsMT6R6C6NPH7sLjUbV8P6lSXp5Y3eJAIPrqJOdylAcEl7zmZvj97y17ZlWVbZ5Shffes81cJ50En/98eiuKHXqo1S1QjR58du609BKpGi4CG28vLbXJ42j5g0I7m9OV68i5NPEhI5e8Bx6wC38o+2ZpqV0cBw+2FT/PPms9h3QaMcImRVeujPx6W5vl/D/rrNRNKIPNSxx3nE1qR+sprF1rvaSurAxyLgs8ILjkbNtmhedDKZQ7KimxDJrTp6c3e6aIpZEYOdLyC23b1r4jd906m0yeOtXy/afa+efDmDF23vAiM83Ndt7KSl/G6fKSDxm55CxcGD8NQrdutqxx8WI48MDox6m258/p2TP5C2hlpaVvCM85VFJihWROPdXy/KTjotyjh63rf+456wlt2GDnKS+3vQennmpzHM7lGQ8ILjnbtyc2NyASPVlaUxO8/jo88UT7SpzBg+GTn7Q9A8kkjuvWzVJRH320TeSKZObOvHv39ov/xo0W3Pr3z+xE8tq18Oqr1lMpL7eUHYcemjt7H1ze8YDgklNVZRfeeKJVFNu9G26/3WoXV1fbhK+qpX24+24r5nLVVYlXLguXyrmCRJWVWTqJTGprs81xzzxj33OozvK8eXD//fDVr1rRHOeS5HMILjkTJ9pFqK0t+jFNTXanvN9+e792//3wwQc2IR3KqyNigWaffSwv0YMPpqftheIf/7BkfjU1Nrner5+Vxhw1yoLD7bfbUmDnkuQBwSWnb184/vj2ZGsdBQK2AufMM/e+y9+82YY4amoiD+uI2IT0iy+mr4B9vtu82YLBqFGRh+569bKvf/wj821zec+HjFzyLrjALkxvv22Tp/362fObNtnQz/HH23xAR++/b0Ek1hxEWZkFlcWL4bDD0tP+fBaqahbrZzhggKXMXrcueoGdrmhrs57cK69YbYY+fSx19sSJsWs7u5zn/3oued262Tj/O+/Y6p4PP7S7+wkTbJJ1woTI4/mNjYnt8gUv+B7N6tXx51dELGBs2ZL6gLBpkw1JrVhhq626d7e9IHPmWL2Fa65JTT0IlxUeEFznlJXBwQfbV+giH291T6huQCLi1QAuVj162C7peAIBW3mUSg0N8ItfWO+wtnbv19ets4yqt9xiw1Yu7/gcguu6RJd6TpzYngk0msZGu+h52ofIDjrIqpvFEvoZjhiR2nPPnQtr1kSvvDZ4sC0jfsNLoecrDwguc3r0gE99yoYbIq1Sam21Cemzz05vEft8Nm6cLXPduDHy66p20T7ttNT/DP/97/b5omgGDkx/2hKXNh4QXGZ98pNwxhkWFFatsn0Ju3bZ31eutGBw6qnZbmXuKi21+Ruwn2H48NGuXVav4aCD4PTTU3/utWttWWssvXpZLyFHSvO65PgcgsuskhK48ELbWfzCC7byCGxl0nHH2TCH5wCKbfhwuPlm2+n98svtF9+qKrjkEqvUluj8wfr1MH++rQ7r08fSfgwYEPnYbt0sAMVa4dTaaj0T/zfMS6J5HMnr6up0zpw52W6Gc9mze7etJiottZ3fiaYc370b7r3XUpiDva+tzS7kxx4Ln/vc3mk4/vY3y2QbK4vrqlUW7C+9tDPfjcsQEZmrqnUdn/cegnP5rGfP5HMXNTe372YeMWLPJcKBgG0M3LEDrrhizwBz/PHw9NM2aR1p6Wtzs014n3hip74Vl30+h+BcsZk714bqOgYDsMejRtm+gnff3fO1YcPgssts0nr9+vacVqo2b7ByJXzhC5aS3OUl7yE4V2yeeMIys0Yb5xexfSAzZ1oRonDHHGMbz6ZPt42JJSUWECZMsAUDvlw4r3lAcK6YtLXZ6qR4ZUX79rUVS5Hstx984xuWb2rXLhuy8voPBcEDgnPFJLSJMFYtarDX46UTr6qyr0SoWtqN5cttqGnIEMtu66uRcooHBOeKSUmJDe/U19uqpGg2bkxdcsE1a2xF0+LFewaAoUNtNVKkNOkuK3xS2blic9pptoooWqGjtjZLLpiK1UJr18Ktt1rPYNSo9q+RI2246ac/bd+L4rLOA4JzxWbSJNu8Vl9vS0jD7d5tz0+dakM6XXX//bZZbfDgPXsHIpYGo08fq5QXq+CSy5icGjISkT8BU4H1qjop2+1xriCVlNjy0CFDYMYMy1IaulhXVtrS0ilTYo/v79gBixbZn7172zBUx4nl9ethwYLYy1D79LEA9P77lvzQZVVOBQTgXuC/gf/JcjucK2ylpVbV7uSTrZ5FQ4PlIRozJnaRm9ZWePhh26AWyqMUKno0ZYoVTwol1Vu50oJPvMnpkhL4+GMPCDkgpwKCqr4kIrXZbodzRaOiIvG9A4EA/OEP8NprdtcfHjja2uC556yAzlVXtb+WSGocX2mUM/JuDkFEponIHBGZs2HDhmw3x7ni8d578PrrMHr03r2I0lKbLH7rLUuWB7azWTV+UGhr893NOSLvAoKq3q2qdapaVx1r2ZzrurY2WwkSq6CNKx5PP23DSrF2OPfrZ2VVweYoJkywuYRoduywTXATJqS8uS55OTVk5HLE5s2WmvqZZ2xsGaxU5mmnWYEWV5wWL7aUF7H062dzEoGAzQ1cdBH86EeW62jgwD2Dyfbt9rt23XWx5y1cxvi/gtvTihXws5/Z8sPBgy1vTSBgq0DmzoXPftYmI33ct/gk828eOnb4cLjpJpt7qK/f8/UBA+Bb3/LJ5BySUwFBRP4GTAEGishK4GZV/WN2W1VEmprgV7+y/6zhY7olJRYYWlrggQdsrHiSrwouOhMnwsKFdqMQzaZNMH78nsGjpsYK+ixbtmfqiv32i78CyWVUTgUEVb0w220oavPnWxe+tjby6+Xltm58xgwPCMXo5JPhzTfbh4M6UrVhoEjlO0Xs9yra75bLCR6eXbtXXrGNSbH0729jydu2ZaZNLnfst5+ls6iv33uhwY4dMG+eXfCHDctC41wq5FQPwWXZzp3xa/GGsmU2NWWmTS53iMDnP283BTNm2O9AU5MFiE2bbL5g+XK44QY45BA4//zYw0su53hAcO2qqy0zZayUxqGcM716ZaZNLreUlsKnPgWnnAJvvAF33WXzS8cd115WMxCwlBXvvw/f+Y73GPKIDxm5dscd177MNJr16+Hwwz0gFLvu3W2IqKoKJk/es8ZySYkFgUAA7rknsd3KLid4QHDtxo+Hffe1HDSR7NplK43OOCOz7XK5Z8MGCwhDhkQ/prrachQtW5axZrmuSTogiEgvESlNR2NclpWWWh6aESPsP/LGjZYeeccO+0+9bRtcc42nGXDtewpiLRsNLT2NVorT5Zy4cwgiUgJcAFwEHAY0Ad1EZAPwBHC3qn6Q1la6zOnTx8Z9Fy2yVAXr1tlwwGc/C0ceaTtRnUu0foFIe1ZUl/MSmVR+HngG+DawUFUDACLSHzgB+KmI/EtV/5q+ZrqMKi+3ceHJk7PdEperqqvbE9fFq80ca1jJ5ZREAsLJqtrS8UlV3Qw8DDwsInHWKrq8sX69pTeur7f8MpMn2xLCnj2z3TKXS/bZxyaOt22z5HSR7Nplk86euC5vxA0IqtoiIuOB4cAbqroz9JqInK6qT0UKGC7PBALw0EPwxBM2Lty7tz03Z44NGV1+ORx4YLZb6XJFaE/Cz39uNw69e+/5ekODDTeG10ZwOS/upLKIXA08ClwFLBSRs8Ne/nG6GuYy7JFH4PHHbUJ55EjbfDRwoO08ray0HEcf+FSRCzNhAnzzm7bwoL4eVq+2fSz19dZzuPxyOOywbLfSJSGR0P1l4FBV3RmsZvaQiNSq6q8BT3lZCLZuhenTLWldaYQFZL162R3fgw/ahLNnOnUhBxwAv/ylJb1bssR6lfvss/feBJcXEgkIpaFhIlWtF5EpWFAYhQeEwjB3rk3+xeraDxgAS5fC2rUwdGjm2uZyX0WFzTMdcki2W+K6KJF9CGtFZHLoQTA4TAUGAgekqV0uk1auhG7dYh8jYnMLW7Zkpk3OuYxLJCBcAqwNf0JVW1X1EuC4tLTKZVaPHomtK4/Xi3DO5bW4AUFVV6rq2iivvZr6JrmMO+AAS0kRS1OTDQ2MGpWZNjnnMs5zGTmrkzxkiKWqiETVVpCcemr8oSXnXN7ygOBsbuCqq+zvK1fumWpg927LazRhAnzyk9lpn3MuIxIeEBYRwfIZ7aOqPxCRkcAQVX0zba1zmTN8OHzve1b45NVX29MSVFbCBRdY+cSKimy30jmXRqIJ5ioXkbuAAHCiqu4vIv2AmaqatZ0ndXV1OmfOnGydvnDt2mUVsEpLreKVTyS7OFRtT9ratdbhHDHCViq73CQic1W1ruPzyfxPP0JVDxGRtwFUdYuI+C1jIerVywvguIQtWwb33Wcb2UN7FlXh0EPhwgs9MOSTZAJCS7AOggKISDXWY3DOFamPPoKf/tQS5I4c2R4QQlU0P/oIbrrJg0K+SGZS+Q7gX8AgEbkVeAXPZeRc0QoE4He/s20s1dV7ZjQJVdHcsQPuvz97bXTJSaiHEJxQfgmYC5yEpaw4R1XfS2PbnHM5bMkSy5ZeWwuoUtbWBKq0lnX/v+gwdCi89ZZNSXkvIfclFBBUVUXkEVU9FHg/zW1yzuWBDz+EMloZtvItxi59gr5brXbyzl6D+WDsGayqORLKuiECy5d7QMgHyQwZzRIRz2XrnAMg0NTClPfv4vDZ/02Phi1s6zOSbX1HURpo4ZC3/8gnXvsF5c27AJtkdrkvmYBwAvC6iHwoIgtE5B0RWZDKxojI6SKyWESWisiNqfxs51xqTfzwUWo3zmFr39E09uj3f8NEzd0q2dp3NP22fMiB8/+HQMBWL7vUCASguTk9QTaZVUZnpP707YIrmH4LnAKsBGaLyGOq+m46z+uc64SGBkYtnsniqhpKW2TvPYsibK+qYeCHb3LQQZ9h2LDqrDSzkNTXw7PPwuuvWzKByko47TQ45pjoVUyTlXBAUNVlqTllVIcDS1X1IwAR+TtwNuABwblcs2QJpW0tHFRXzhtvWOegvENl9YbGEsrblAsOXISVUXGd9cILcO+9lixgyBDbK9rQAA8/DDNnwvXXQ01N18+TTOqK70V6XlV/0PVmAFazeUXY45XAESn6bOdcKjU2Ara09IgjYN482+AevjGtZ0844KASqip3Za+dBeCDD+DPf7afdXhuyR49LPnwpk1WtO7HP+56kbpkhozC/1W7Y0VyUrnsNFL1tb1GyURkGjANYOTIkSk8vct1gYAlXW1qsu7yoEHZblERq6z8v78OG2ZzBOvXw/btFhT69bOS3LIsYA9cpz3xhCUOiJZoeMAA2y0+f74F565IZsjov8Ifi8gvgMe6dvo9rARGhD2uAVZHaMfdwN1guYxSeH6XowIBePllePxx2LzZLjiBAIwdC+edB+PHZ7uFRWjsWOjd28YtevSgtNT2HOxRXbW11fJhHeCFFTtr92670McbDqqshJde6npA6Er6657APl07/R5mA2NFZHQwR9IFpDbguDykCn/9K/zhD/b3kSMtcdrIkZZM7Sc/gVmzst3KIlRebtF49eo906WHBAK2+eC00/boTbjkNDTYnyVxrtQVFdY766pk5hDeoX0IpxSoBn7Y9SYYVW0VkSuBfwc//0+quihVn+/y07x58PTTMHr0nv8pRKyr3LOnBYv99oP+/bPWzOI0ZYrV2H7sMQsQ/fvbP8yWLTbHcPzxFjSKkCosXWqrgt5+2yrU1tZafJw8ee8J+Gh69rQfaVubdbaiaWy0DPZdlcwcwtSwv7cC61Q1wq1B56nqE8ATqfxMl79UrTxDv37R75B69LCb0ddeg6lTIx/j0kQEzj0X6upsGcw777SnOT3xRBgzZs8ER0VCFR56CKZPb8/zVFJicyy/+Y3Vmrr6arvYx9Ojhw0DzZ3bYTiug507LT53VTIB4XJVvSH8CRH5WcfnnEuVhgZLjxBv7UC/fvDmmx4QskLE/oEuuSTbLckZL71knaba2j3v6vv3t9/VJUvgT3+CK69M7PNOP92GRXfvjhxE1qyx3sGkSV1vezJzCKdEeC6tm9VccWtttetNvJvM0lLbuelctrW1waOP2l6BSEM8IjYHNmeOXcgTMXKkVbjdssWmZXbvhpYW2LrVNqv17w/XXpv4MFQscQOCiHwtOH8wLpiyIvT1MfBO15vgXGS9elmXObjkPaodO+L3IpzLhPp6u1DHqi8Vusl5663EP/fgg20BxTnn2BDp1q3W25g2DW6+2Zb4pkIiQ0b3A08CPwHC8wvtUNXNqWmGc3srLYVTT4VHHrENOJGo2h3TiSdmtGnORbR7d2LHVVTYRT0ZAwfCWWfZV7rE7SGo6jZVrVfVC4HtwGBgFDBJRI5LX9Ocs4myfv1sQq4jVduQM3myrTJyLtsSmSgGG/JJVf6hVEpm2emXgK9jG8bmAUcCrwN+b+bSpk8fuOEGuOMO645XVNhXQ4ON19bVwZe+FH+dtnOZUFtrv7O7dkUfNlK1YZ9DDslo0xKSzCqjrwOHAbNU9QQRGQ98Pz3Ncq7d4MHwgx/Y6ozZs22J3aBBcOSRtrqiCFc2uhxVWgpnn22riEaN2ntiWRVWrLAbmVjLSLMlmYDQqKqNIoKIdFPV90VkXNpa5lyY0lLYf3/7ci6XHX+8DXHOmGGLIgYOtB7stm22Umj//eGyy7LdysiSCQgrRaQv8AjwtIhsIUKuIeecK2YicP75Nrf1zDO2276tzXoMn/98cjuVMy2Z5HbnBv96i4g8D/QBnkpLq5xzLo+J2EKH0GIH1fwY2kx4Kk7M50Xke6r6IjaxPDldDXPOuUKRD8EAktupfCdwFHBh8PEOrOSlc865ApDMHMIRqnqIiLwNoKpbgmmqnXPOFYBkeggtIlJKMAW2iFQDgbS0yjnnXMYl00O4A/gXMEhEbgU+A3w3La1Kt7Y2G9Tz3UxFZ9Mm2LjR/ulramxZoHPOxA0IIvK/qnoxMBC4HjgJq398jqqmsqZyerW2WqWKJ5+Ejz6ygLDvvnDGGXDQQbGrT7i8t3IlPPywLQEsKbFVH2VllgPprLMSTzmQa3butPXtZWXtefed6yxRjV2WWETexdJcPwZMwYLB/8lmgru6ujqdM2dO/AObmuDOOy0g9OvXnkRkyxb7OvJISxuYq4uDs0XV8u3Om2cpRQcOtOIn1dXZbllSPv4YfvpTuwcYNKj9otncbCmIR42C66/Pr6CwerVtfAqVD1W1f5ZPfhKOPtrvb1xsIjJXVes6Pp/IkNHvsP0G+wBzwz8Tm09IZV3l9HjgAatUPXr0nuu/QhUr3nzT/jd99rPZa2Ou2boV7roLFi+2K2hFhV1BH3gAjjnGdth065btVsbV1ga//S10727/1OEqKiwYLFtmOewvvDDyZ+SaDz+En//cgsCwYe0X/x074J57LMXHf/6nBwWXvESynd6hqvtjNY73Cfsaraq5Hwy2b4cXX7SqFJEWA4vYYPLTT1tGKmc/h9tus6G1UaOs2MCQIe0V7l96Ce6+2zJ05bj33rM5g47BINywYfD884mnLs6mpib49a9t7iM8GIDVsh892n7dX3kle210+SuRAjkCoKpfi3dMTnr33fgVqsvLbY7h/fcz165c9uqrNugeKXNcSYmldJwzx25Fc9yiRfFHAsvL7Vdk+fLMtKkr5s+3nkC01MklJTYsNmNGXsRrl2MSmYJ6XkSuEpE9alKJSIWInCgifwG+kJ7mpUAyt30NDelrR74IBGzifdCg6MeI2C3qs89mrl2d1NKS+ERrW1t625IKs2fHn+uorLReUaQaEs7FksgcwunAZcDfRGQ0sBXoDpQCM4Ffqeq8dDWwy3r3TvzYWHXvikVjo80fRCtRFtKnjxUoyHE1NfHrLataMMiHufLGRltRFI+IBUPnkpHIHEKjqt6pqkdjldJOAg5R1VGq+uWcDgYAEyfa/6BY/zuam22G0XMrt99Ox1l9RiCQF7OWdXXWzFhBYcMGmDAhdqeoM1pbbcw/3o8yGcOHx5/qamuzc+ZiRS6X25JatayqLaq6RlW3pqk9qderF5x+ulWliDSoGgjYePnUqbYUpdh16wZjxthy3Fg2b87Nkk8d9O4Nn/60/fNHuifYts2CRaoWmKnatNWvfgVf/jJ89avwzW/CzJm2Z6Crjj7aAk2sILNuHRx+uA0dOZeMZHYq569zzrFhkJdftgHY0JKTzZtt3uCkk+DMM7PZwtwhYpv1br/dbjEjDcA3N1sgPfbYTLeuU04/3f58+GG7e+7e3Zrf1NReorO2tuvnUYWHHoLHH7dANGKE/fh27YK//c2mXK6/HgYM6Pw5amrgqKPgtdeszR3n/Ldvt3ZMndqlb8UVqbgb03JZwhvTwK4AS5bY8tLFi+25CRPg5JNh7Nj8yU+bCYEA/PnPtn5x6ND2/A6qtsRlwwa45BI45ZTstjNJ27fb4qjly20UceJEmDQpdfsRX3/d9j/W1kYeTVu71sqB3nxz13YUNzdbicZZs6ztvXpZr2HnTusVXH21/Uo7F020jWmdCggiMkRV16akZV2QVEBwyWlrs3JPM2bYlUbEAsWQITYGU7fX71JRU4XvfMd6HVVV0Y+rr4dvfxvGdbH4bKg2b2iFcEUFHHGEVePykU8XT1d2KkfyBJCyAWQROR+4BdgfOFxV/SqfbaWlcNppNpxWX2/LWyorbXOa96b2snq1jd2PGBH7uIoK2xjf1YAgYv8UI0fGP9a5RHU2IKT6irAQOA/4fYo/13XR9t1lbCnfl9Lu1jko81gQUWOjXaTjxcqKChu6ci4XdTYg3JPKRoSypubyhudis3q1TY6+8Ub7Ra53b5tvPukkzwPYUe/eNqIWr3ZuY6PlCHQuF3U2IPwzpa1IgohMA6YBjPT+clqEsoOq2rr30ATp7t1w//2WH+jKKz0ohBs0CPbZx+bb+/ePfIyqTf4eeWRm2+Zcojq71uGJZN8gIs+IyMIIX2cn8zmqereq1qlqXXU+bC3NMy0tcMcdkZOn9expydPeftvW1afa1q22Uue55yxFQz4kmwsRgXPPte+hqWnv10OTwAce6OP+LndlbA5BVU/u5LlcBi1aZHvSoq3LF7GVqE89BaeemppeQmOjrdN/+eX2fEIi9tlnnmkFbPJgUzSTJsEXvwh/+YstK+3f39q9Y4d97b8/fOUrPifvcldOzCG43PHWW/GXLfboYUMjq1fHT3kUT0sL/OY3FohGjNjzwt/SAv/8J6xaZa/Nnm3PjRxplc7Gjct8hbDdu60NPXtGDobHH2/tevllm39parLgesop7VlUnMtVnfr1VNU7U9kIETkX+A1QDcwQkXmqeloqz+ES09SU2N24iI2Hd9Xs2fDOO3vXLgK74JaVWf7/yZMt+JSUwMKFtnRz8mT42tfSv+4+lI7iySctcInYaqEpU2yCvePI5ZAhcP759uVcPsmJCqyq+i9VrVHVbqo62INB9tTUxM8CHgjYV6yiM4lQhSeesFQOkYZR1q+3HkvPnjas1Lu3/X3wYLvrnj8f/vjHrrUhkTY++ij87Ge2HSO09r9/f9v0fvPNeZH01bmE5ERAcLnjyCPb00FHs2GD3Z1HW02TqOZmGw6KtLNX1eoVde/ent8/nIj1GGbPts9IlwULLAfSyJG2XDQUuCoqbBirvNzSPjU2pq8NzmVKIhXT+ifw1TcDbXUZUF1t493LlkUOCtu32xj6ueem5nzRMqfs2mW5B2MNB4nY8NYbb6SmLZHaNn265fiLNvbfr59lTJ03Lz1tcC6TEplDWB38irU2ohTwxXQ5KLTccdMmu3jW1sbOtQPtqaBnzrT39OxpwWH3bhu2uf761CydDBW537Zt79z9zc3tO38bGmwoK5Lu3dNXGWzbNli6NP732ru3ZR/1/QUu3yUSEN5T1YNjHSAib6eoPUWtrc02fb34ol3Aq6rgmGNs7XpFRfKft3ixbSRbtqx9NY6Ifeb550cvJldWBp/7nE2Yvv66ZQetqLDyBwcdZCUTUiGUafvOOy0Ndfg8QmhiOxCwn0u0ZbAtLckVxUtGc7P93OItEy0vj1+0Jhe0tdkw3Pz5FmSHD7e6CV0d+nOFI5GAcFSKjnExbNliY9HLltkdeejOd948G8a59lrbKJaoBQvgl7+0C+2oUe0XtdZWWxL50Udw442xq4YOHmylJNKprg4OPtguUjU17UMzVVV2oQ1VM4tW/aulJX2JV0OBpq0t9sqr3bu7nqwu3VautA2H69dbcC8rs0ypDz5oOQw/85n82Ovh0iuhEprRXhOR/4x3jIuvqQn+679gzRq7Ex40yC6IAwfa49274ec/tyGMRDQ2wu9+Z4GkX78973DLymwIZOVKGx/PtrIyuOIKm7dYu9YC4rJl1isZMsQ2wU2YEPkufd06CyL77ZeetvXsacNA69ZFP0bVft7HH5+eNqTC+vXwk59Yr6C21m4sBg2y34Phwy3D+QMPZLuVLhd0dZXR91PSiiI3b55doKP1AKqrLRi8+mpinzd/vgWRWHf/Q4daioh4S0wzoaICLrrIyk5ecYXt9v361+0idemltqxz06b2CeiGhvae1FVXpXdz2hlnWA8hUvlLVft322+/9AWlVJgxw246IlVqKy21HuTMmRaQXXGLO2QkIguivQQMTm1zitPTT9vQTiyDBlm6iDPOiD+mvXBhe5GzaCoqbPhozRpLypYLKiv3Hv65+GI44ADbr7B0qX3vvXpZjZ7jjos/Qd5VNTXwjW/YbupNm2zoqrzcAsSuXRYI0h2UumLXLruRGDIk+jGBgC3r/fnP4ROfgPHjLc2GDyEVn0TmEAYDpwEdq64L8FrKW1SE1q2LXxC9Rw/r+re0xJ9gbmtLPF9OIJDYcdkiYnMMkydbz6CtzXoGmbxYTZxoF8vZs+GVV2yIaNw4m3TP9Qvn5s3Wk4m0bFbVel8LF1qPcuNGW1Y8Y4b1Jr72NRgzJuNNdlmUSECYDvRW1XkdXxCRF1LdoGLUrZvdrce60Le12V1oIrlwxoyx1UGxtLXZBWHQoOTami0iFgiyparKAsBJJ2WvDZ0RK1jV11vm2qoqO65fv/blvVu32u7s737Xs7MWk0Qmlb+oqq9Eee1zqW9S8Tn6aBuOiGX9equZm8jQxGGH2XHNzdGPWbsWjjoq/UMuLrsGD7beZ8dU4i0t1jOoqrKbjOZmm1cK6dvXgsSDD2a0uS7LcnTks7gcc4zdAUfL/9/cbMMUid6dVlXZ5rLly/fOza9qSzm7dYOzk6pE4fJRaamlEF+7ds9d4WvWWC8xFAzKyvYMCGC9x4ULY6+ycoUlkUnlt1T1kK4e46KrroavfhV++1u7UA8aZHf4qjauu2OHTa4mM5576ql2MfjHP+w/fGlpe4nHmhobH86X4aJ0UrUKca++ahe+nj2t5zRpUuFUhDvhBLuwz59vF/3u3W3VWkmJTTqHqrh1/H5DqUE2bLCehit8otGSyYQOEGkAPoh1CNBHVTM+0lhXV6dz5szJ9GnT5uOPbTXN3Ln2nzEQsDX4U6fa5GVnNDTYhWDNGvsPv//+tqrIi7RYj+z3v7efT3m5BYOWFnt+wAC45hpLYFcImpvh3/+2lWqhZbsrVtj8wIQJ0XcrL18O111nx7jCISJzVXWvLZ2JBIRESqC0qerKzjauswotIITs3GkXpe7dfYw/XQIB28m9aJFdFDsGyNC+h+9/P/L6/XzV3GyBYNEiuO++2DcaoWXJt98efxWcyy/RAkIik8rLwr+AL2JF7g8HKoLPZzwYFLLevdt3K7v0WLLECvMMHWrBt7FxzzH2AQPs+WefzV4b06GiwoYezzzThg63bo1+7OrVcOyxHgyKSdKTyqr6PeAOYAfwaRHxcpouKYGAjV1ns4bAo4/acMhTT8Ezz9hwyosv2kUwFBiGDLHd3C0t2WtnupSVweWX27/D+vV7BsPW1vbUIZ/+dPba6DIv4RKaInI7cK2adcBTwS/nErJ9O7z0kqVJ2LHDnhs3znZfH3hg5uY1PvwQ/v53u/D169c+gd/QALNmwdixNqlcUWHBYOfOrleHy0Vjxtg+gwcesCy74RlxjzvOgoH3DopLMjWVdwKPicgFqrpLRE4FblbVo9PUNldANmyw3b4bN9pwWP/+dhFetQp+8Qs4/XRLuZ3uoNDYaGPi3bvb+cMvgj162CqvDz6w9g0bZr2ZQlltFMmoUVbfYt066ymUlNicigeC4pRwQFDV74rI54AXRKQJ2AXcmLaWuYIRCFjq5R077AIUImJj9X372tDNyJE2Zp1Ob71l7dh3X5tD6FiRraTEAsOSJfbavvvGThJYKAYP9qWlLok5BBE5CfgyFgiqgatV9eV0NcwVjiVLLCtotARrpaXWa3j88fTnVpo1y+5+a2rsvJF2c3fvbpOt69fb5Ksv0XXFIplJ5ZuA/6eqU4DPAA+IyIlpaZUrKLNnxx92qay04aTVq9PblsZGm1Dt1s1SfDQ02BxBaFJV1XZ379plG7omT05ve5zLJQkHBFU9MZTTSFXfAc4AfpSuhrnCsXNnYiVAS0rSv/Jo6ND2cpdDhtjkaXW1TXhv22Z/lpXZ+vyLL/begSsuyUwq70FV1wSHkZyLqbo6/oVe1XLrpHsy89hj4YUX7HwitnroiCOsfaEUH1u3WiqHYpg7cC5cl5LbqWoO1Ntyue6II2yJZ6xN8Vu22DLIdOdXGjMGDjrI1tmHtye0K7ytzR6ffnp62+FcLvJspy7tamrg0EP3vgiHNDbaXfl556V/iEbEEglOmmT5fNassWGizZvtcVMTfOtbVmvYuWLT6SGjVBKR24BPAc3Ah8B/qurWrDbKpYwIfOlLcOedsGCBDcVUVtrd+ObN9vpXvpK5BGo9e8K119oGtdDu5B49bJjo0EOzW4jHuWyKm9wuI42wTW7PqWqriPwMQFVviPe+Qk1uV6ja2mDxYssPtHy5rTw67DCrB1Fdne3WOVc8oiW3y4kegqrODHs4C1vW6gpMaan1AjyVsnO5KRfnEC4Dnsx2I5xzrthkrIcgIs8Akfaq3qSqjwaPuQloBe6L8TnTsPTbjPTq3845lzIZCwiqenKs10XkC8BU4CSNMbGhqncDd4PNIaS0kc45V8RyYg5BRE4HbgCOV9Uopeadc86lU67MIfw3UAk8LSLzROR32W6Qc84Vm5zoIajqvtlug3POFbucCAjOhGr79uhhX845l0keEHLA0qXw5JNWvEXEvg4/3PLphBeUSYeGBnj7baivt30C48fDxImW8dM5V1z8v32Wvfwy/OEPli5hxAhLAd3WZsFh1iy48kpLp5Bqqnbu++6z/D3dullxmieftApmX/ua1Tt2zhWPXJlULkorVsCf/mS1ewcPbq/vW1pqefurq+Guu6xyV6q9+ircc4+lf66ttfMNH25/Lymx+scffpj68zrncpcHhCx67rn26l2R9Oxpd/KvvJLa8zY1Wc9g2LC9awqDpYHu2RP+9rfYKaudc4XFA0KWhC708QqbDxyY+oCwYIHNHcSauO7f33oIq1al9tzOudzlASFLAgFoabHhoVjKy231USqtXBl/0jg0ub1hQ2rP7ZzLXR4QsqS01MbvG+LUnNu1K/WpocvLLSAlosR/Q5wrGv7fPYtOPTX+HfiWLXDaaak979ix8ecGQqUk073s1TmXOzwgZNHRR0OfPrBxY+TX1661OYZULzsdOxaGDIFNm6Ifs3q1VRDr2ze153bO5S4PCFlUVQXXX2+rjOrr7QK9c6cFiPp6uxhfd13qdy2XlNg+g5YWCzrhw0etrbYcduBA+I//SO15nXO5LSdKaHZWoZTQbGqC+fOtvu+2bbbCZ8oUOOAAG+9Pl1Wr4MEHbdVRSYkNI5WUwCc+AZ/+tPVenHOFJ1oJTQ8Ijg0bbPNbSYltTquqynaLnHPplNM1lV12VVd7kXvnnM8hOOecC/KA4JxzDvCA4JxzLsgDgnPOOcADgnPOuSAPCM455wAPCM4554I8IDjnnAM8IDjnnAvygOCccw7wgOCccy7IA4JzzjnAA4JzzrmgnAgIIvJDEVkgIvNEZKaIDMt2m5xzrtjkREAAblPVA1V1MjAd+F6W2+Occ0UnJwKCqm4Pe9gLyN+qPc45l6dypkCOiNwKXAJsA06Icdw0YBrAyJEjM9M455wrAhkroSkizwBDIrx0k6o+Gnbct4HuqnpzvM/0EprOOZe8rJfQVNWTEzz0fmAGEDcgOOecS52cmEMQkbFhD88C3s9WW5xzrljlyhzCT0VkHBAAlgFfzXJ7nHOu6OREQFDVT2e7Dc4lY9s2WLYMAgGoroZhw0Ak261yrmtyIiA4ly+2bYMHHoBZs9qfCwRg7Fi48ELYZ5/stc25rvKA4FyCtm2DH/8YNm6E4cOhtNSeV4U1a+DWW+GGG2C//bLbTuc6KycmlZ3LBw8/DBs2wIgR7cEAbKho4ECoqoK77oLW1uy10bmu8IDgXAJ27IBXX7W5gmj69IGtW+HddzPWLOdSygOCcwlYscL+LIszyFpWBu/7ommXpzwgOJeAQCCx40SgrS29bXEuXTwgOJeAwYMtKMQLDC0tMGpUZtrkXKp5QHAuAdXVcMABNqkcTWMjVFTAwQdnrl3OpZIHBOcS9NnPWg9h8+a9X2tshFWr4KKLoEePzLfNuVTwgOBcgmpq4Nvftgv+xx/D8uWwciXU19vqomnT4Nhjs91K5zrPN6Y5l4TaWvjJT2DJEvjgA5szqKmBAw+E7t2z3TrnusYDgnNJKimB8ePty7lC4kNGzjnnAA8IzjnngjwgOOecAzJYUzkdRGQDVlAn3EBgYxaakymF/v1B4X+P/v3lt0L4/kapanXHJ/M6IEQiInMiFY8uFIX+/UHhf4/+/eW3Qv7+fMjIOecc4AHBOedcUCEGhLuz3YA0K/TvDwr/e/TvL78V7PdXcHMIzjnnOqcQewjOOec6oSADgoj8UEQWiMg8EZkpIjEKH+YfEblNRN4Pfo//EpG+2W5TKonI+SKySEQCIlIwqzlE5HQRWSwiS0Xkxmy3J9VE5E8isl5EFma7LekgIiNE5HkReS/4+/n1bLcp1QoyIAC3qeqBqjoZmA58L8vtSbWngUmqeiCwBPh2ltuTaguB84CXst2QVBGRUuC3wBnABOBCEZmQ3Val3L3A6dluRBq1At9U1f2BI4ErCu3fsCADgqpuD3vYCyioiRJVnamqrcGHs4CabLYn1VT1PVVdnO12pNjhwFJV/UhVm4G/A2dnuU0ppaovARGqRRQGVV2jqm8F/74DeA8Ynt1WpVbBZjsVkVuBS4BtwAlZbk46XQY8kO1GuLiGAyvCHq8EjshSW1wXiUgtcDDwRpabklJ5GxBE5BlgSISXblLVR1X1JuAmEfk2cCVwc0Yb2EXxvr/gMTdh3dj7Mtm2VEjk+yswEuG5guq5FgsR6Q08DFzTYTQi7+VtQFDVkxM89H5gBnkWEOJ9fyLyBWAqcJLm4drhJP79CsVKYETY4xpgdZba4jpJRMqxYHCfqv4z2+1JtYKcQxCRsWEPzwLez1Zb0kFETgduAM5S1d3Zbo9LyGxgrIiMFpEK4ALgsSy3ySVBRAT4I/Ceqv4y2+1Jh4LcmCYiDwPjgACWDfWrqroqu61KHRFZCnQDNgWfmqWqX81ik1JKRM4FfgNUA1uBeap6WlYblQIiciZwO1AK/ElVb81ui1JLRP4GTMGyga4DblbVP2a1USkkIscALwPvYNcWgO+o6hPZa1VqFWRAcM45l7yCHDJyzjmXPA8IzjnnAA8IzjnngjwgOOecAzwgOOecC/KA4JxzDvCA4JxzLsgDgisIIlIrIg0iMi/sub3qD4hIj2CdjGYRGdjFc/YQkReDqa0RkauDufKTyi0lIn1F5PKutCWBc+xVq0BEKkTkJRHJ2xQ2LrU8ILhC8mGwBkbU+gOq2hA8JhV5hC4D/qmqbcHHlwNnqupFSX5O3+B7kyIm0f/D99KhVkEwDfezwH8ke25XmDwguLwRrFZ1SvDvPxKRO2Icnon6AxcBocyzvwP2AR4TkWtF5PMi8mawN/L7sF7EIyIyN1hxa1rwc34KjAkee1uwtxN+J3+diNwS/HttsBdyJ/AWMCLaucLFqFXwSPD7cM4DgssrN2MpzS/CctFfG+PYSPUHUlbMJJigbh9VrQcI5pJajdXeeAq76z462Btpo/2ie5mqHgrUAVeLyADgRoK9G1X9VgKnHwf8j6oeDPSMca5ELAQOS+J4V8B87NDlDVV9KZhx8hvAlNBQjYj8EMtCGS7d9QcGYon3IjkJOBSYbc2lB7A++NrVweR9YOmwxwJrkzz3MlWdlcC54lLVtuB8SmWwCpgrYh4QXN4QkQOAocDG0MVLRIYQ+fc46foDInIF8OXgwzOBc8Mfq2r4+xuA7tE+CviLqu5R61pEpgAnA0ep6m4ReSHKZ7SyZ++94zG74p0rSd2Axi683xUIHzJyeUFEhmKV4c4GdolIKB32wcC8CG9Juv6Aqv42OGwzWVVXd3zc4dgtQKmIRLqgPwt8RkQGBdveX0RGAX2ALcFgMB4r1A6wA6gMe/86YJCIDBCRblghpGiinSshwSGrDarakuh7XOHygOBynoj0BP4JfFNV3wN+CNwSfHkyEQKCqrZipVP/jRVDf1BVF6W4aTOBYyKc+13gu8BMEVkAPI31bJ4CyoLP/RCYFTx+E/CqiCwUkduCF+cfYPV6pxOjwFOMc+0hWKvgdWCciKwUkS8GXzoBKJh8/q5rvB6Cy2si8kdsWGckMF1VJyX4vnqgTlU3duHcBwPfUNWLO/sZ2SYi/wS+raqLs90Wl33eQ3B5TVW/qKoBbHVNn/CNaZGENqYB5bRXversud8Gno+0zDMfBIfSHvFg4EK8h+Cccw7wHoJzzrkgDwjOOecADwjOOeeCPCA455wDPCA455wL8oDgnHMO8IDgnHMuyAOCc845AP4/znfK4PpkDfYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def plot_dataset(suptitle, features, labels):\n", + " # prepare the plot\n", + " fig, ax = pylab.subplots(1, 1)\n", + " #pylab.subplots_adjust(bottom=0.2, wspace=0.4)\n", + " fig.suptitle(suptitle, fontsize = 16)\n", + " ax.set_xlabel('$x_i[0]$ -- (feature 1)')\n", + " ax.set_ylabel('$x_i[1]$ -- (feature 2)')\n", + "\n", + " colors = ['r' if l>0 else 'b' for l in labels]\n", + " ax.scatter(features[:, 0], features[:, 1], marker='o', c=colors, s=100, alpha = 0.5)\n", + " fig.show()\n", + "\n", + "plot_dataset('Training data', train_x, train_labels)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Perceptron\n", + "\n", + "Como o perceptron é um classificador binário, para cada vetor de entrada $x$, o resultado do nosso perceptron será +1 ou -1, dependendo da classe. O resultado será calculado utilizando a fórmula\n", + "\n", + "$$y(\\mathbf{x}) = f(\\mathbf{w}^{\\mathrm{T}}\\mathbf{x})$$\n", + "\n", + "onde $\\mathbf{w}$ é um vetor de pesos, e $f$ é uma função de ativação em degrau:\n", + "$$\n", + "f(x) = \\begin{cases}\n", + " +1 & x \\geq 0 \\\\\n", + " -1 & x < 0\n", + " \\end{cases} \\\\\n", + "$$\n", + "\n", + "No entanto, um modelo linear genérico também deve ter um termo de bias, ou seja, idealmente deveríamos calcular $y$ como $y=f(\\mathbf{w}^{\\mathrm{T}}\\mathbf{x}+\\mathbf{b})$. Para simplificar o nosso modelo, podemos eliminar este termo de bias adicionando mais uma dimensão às nossas características de entrada, que será sempre igual a 1:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 0.92180622 0.91789985 1. ]\n", + " [-1.06435513 1.49764717 1. ]\n", + " [ 0.32839951 2.25677919 1. ]]\n" + ] + } + ], + "source": [ + "pos_examples = np.array([ [t[0], t[1], 1] for i,t in enumerate(train_x) \n", + " if train_labels[i]>0])\n", + "neg_examples = np.array([ [t[0], t[1], 1] for i,t in enumerate(train_x) \n", + " if train_labels[i]<0])\n", + "print(pos_examples[0:3])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Algoritmo de Treino\n", + "\n", + "Para treinar o perceptrão, precisamos determinar os pesos $\\mathbf{w}$ que irão minimizar o erro. O erro é definido utilizando o **critério do perceptrão**:\n", + "\n", + "$$E(\\mathbf{w}) = -\\sum_{n \\in \\mathcal{M}}\\mathbf{w}^{\\mathrm{T}}\\mathbf{x}_{n}t_{n}$$\n", + " \n", + " * $t_{n} \\in \\{-1, +1\\}$ para amostras de treino negativas e positivas, respetivamente\n", + " * $\\mathcal{M}$ - um conjunto de exemplos classificados incorretamente\n", + " \n", + "Iremos utilizar o processo de **descida do gradiente**. Começando com alguns pesos iniciais aleatórios $\\mathbf{w}^{(0)}$, ajustaremos os pesos em cada passo do treino utilizando o gradiente de $E$:\n", + "\n", + "$$\\mathbf{w}^{\\tau + 1}=\\mathbf{w}^{\\tau} - \\eta \\nabla E(\\mathbf{w}) = \\mathbf{w}^{\\tau} + \\eta\\sum_{n \\in \\mathcal{M}}\\mathbf{x}_{n} t_{n}$$\n", + "\n", + "onde $\\eta$ é a **taxa de aprendizagem**, e $\\tau\\in\\mathbb{N}$ - número de iteração.\n", + "\n", + "Vamos definir este algoritmo em Python:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "def train(positive_examples, negative_examples, num_iterations = 100):\n", + " num_dims = positive_examples.shape[1]\n", + " \n", + " # Initialize weights. \n", + " # We initialize with 0 for simplicity, but random initialization is also a good idea\n", + " weights = np.zeros((num_dims,1)) \n", + " \n", + " pos_count = positive_examples.shape[0]\n", + " neg_count = negative_examples.shape[0]\n", + " \n", + " report_frequency = 10\n", + " \n", + " for i in range(num_iterations):\n", + " # Pick one positive and one negative example\n", + " pos = random.choice(positive_examples)\n", + " neg = random.choice(negative_examples)\n", + "\n", + " z = np.dot(pos, weights) \n", + " if z < 0: # positive example was classified as negative\n", + " weights = weights + pos.reshape(weights.shape)\n", + "\n", + " z = np.dot(neg, weights)\n", + " if z >= 0: # negative example was classified as positive\n", + " weights = weights - neg.reshape(weights.shape)\n", + " \n", + " # Periodically, print out the current accuracy on all examples \n", + " if i % report_frequency == 0: \n", + " pos_out = np.dot(positive_examples, weights)\n", + " neg_out = np.dot(negative_examples, weights) \n", + " pos_correct = (pos_out >= 0).sum() / float(pos_count)\n", + " neg_correct = (neg_out < 0).sum() / float(neg_count)\n", + " print(\"Iteration={}, pos correct={}, neg correct={}\".format(i,pos_correct,neg_correct))\n", + "\n", + " return weights" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Iteration=0, pos correct=0.2631578947368421, neg correct=0.6190476190476191\n", + "Iteration=10, pos correct=0.8947368421052632, neg correct=0.8571428571428571\n", + "Iteration=20, pos correct=0.8421052631578947, neg correct=1.0\n", + "Iteration=30, pos correct=0.8947368421052632, neg correct=0.9523809523809523\n", + "Iteration=40, pos correct=0.8947368421052632, neg correct=0.9523809523809523\n", + "Iteration=50, pos correct=0.9473684210526315, neg correct=0.9047619047619048\n", + "Iteration=60, pos correct=0.8947368421052632, neg correct=0.9523809523809523\n", + "Iteration=70, pos correct=0.8947368421052632, neg correct=0.9047619047619048\n", + "Iteration=80, pos correct=0.8947368421052632, neg correct=0.6190476190476191\n", + "Iteration=90, pos correct=0.8421052631578947, neg correct=1.0\n", + "[[-0.66042328 4.90850882 -1. ]]\n" + ] + } + ], + "source": [ + "wts = train(pos_examples,neg_examples)\n", + "print(wts.transpose())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Como pode ver, a precisão inicial é cerca de 50%, mas aumenta rapidamente para valores mais elevados próximos de 90%.\n", + "\n", + "Vamos visualizar como as classes estão separadas. A nossa função de classificação tem a forma $\\mathbf{w}^Tx$, e é maior que 0 para uma classe e menor que 0 para outra. Assim, a linha de separação das classes é definida por $\\mathbf{w}^Tx = 0$. Como temos apenas duas dimensões $x_0$ e $x_1$, a equação da linha seria $w_0x_0+w_1x_1+w_2 = 0$ (lembre-se de que definimos explicitamente uma dimensão extra $x_2=1$). Vamos representar esta linha:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "def plot_boundary(positive_examples, negative_examples, weights):\n", + " if np.isclose(weights[1], 0):\n", + " if np.isclose(weights[0], 0):\n", + " x = y = np.array([-6, 6], dtype = 'float32')\n", + " else:\n", + " y = np.array([-6, 6], dtype='float32')\n", + " x = -(weights[1] * y + weights[2])/weights[0]\n", + " else:\n", + " x = np.array([-6, 6], dtype='float32')\n", + " y = -(weights[0] * x + weights[2])/weights[1]\n", + "\n", + " pylab.xlim(-6, 6)\n", + " pylab.ylim(-6, 6) \n", + " pylab.plot(positive_examples[:,0], positive_examples[:,1], 'bo')\n", + " pylab.plot(negative_examples[:,0], negative_examples[:,1], 'ro')\n", + " pylab.plot(x, y, 'g', linewidth=2.0)\n", + " pylab.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_boundary(pos_examples,neg_examples,wts)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Avaliar no Conjunto de Dados de Teste\n", + "\n", + "No início, reservámos alguns dados para o conjunto de teste. Vamos verificar quão preciso é o nosso classificador neste conjunto de teste. Para isso, expandimos o conjunto de teste com uma dimensão extra, multiplicamos pela matriz de pesos e garantimos que o valor obtido tem o mesmo sinal que o rótulo (+1 ou -1). De seguida, somamos todos os valores booleanos e dividimos pelo tamanho da amostra de teste para obter a precisão:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def accuracy(weights, test_x, test_labels):\n", + " res = np.dot(np.c_[test_x,np.ones(len(test_x))],weights)\n", + " return (res.reshape(test_labels.shape)*test_labels>=0).sum()/float(len(test_labels))\n", + "\n", + "accuracy(wts, test_x, test_labels)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Observando o processo de treino\n", + "\n", + "Já vimos anteriormente como a precisão diminui durante o treino. Seria interessante observar como a linha de separação se comporta durante o treino. O código abaixo irá visualizar tudo num único gráfico, e deverá ser possível mover o cursor para \"viajar no tempo\" ao longo do processo de treino.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "def train_graph(positive_examples, negative_examples, num_iterations = 100):\n", + " num_dims = positive_examples.shape[1]\n", + " weights = np.zeros((num_dims,1)) # initialize weights\n", + " \n", + " pos_count = positive_examples.shape[0]\n", + " neg_count = negative_examples.shape[0]\n", + " \n", + " report_frequency = 15;\n", + " snapshots = []\n", + " \n", + " for i in range(num_iterations):\n", + " pos = random.choice(positive_examples)\n", + " neg = random.choice(negative_examples)\n", + "\n", + " z = np.dot(pos, weights) \n", + " if z < 0:\n", + " weights = weights + pos.reshape(weights.shape)\n", + "\n", + " z = np.dot(neg, weights)\n", + " if z >= 0:\n", + " weights = weights - neg.reshape(weights.shape)\n", + " \n", + " if i % report_frequency == 0: \n", + " pos_out = np.dot(positive_examples, weights)\n", + " neg_out = np.dot(negative_examples, weights) \n", + " pos_correct = (pos_out >= 0).sum() / float(pos_count)\n", + " neg_correct = (neg_out < 0).sum() / float(neg_count)\n", + " # make correction a list so it is homogeneous to weights list then numpy array accepts\n", + " snapshots.append((np.concatenate(weights),[(pos_correct+neg_correct)/2.0,0,0]))\n", + "\n", + " return np.array(snapshots)\n", + "\n", + "snapshots = train_graph(pos_examples,neg_examples)\n", + "\n", + "def plotit(pos_examples,neg_examples,snapshots,step):\n", + " fig = pylab.figure(figsize=(10,4))\n", + " fig.add_subplot(1, 2, 1)\n", + " plot_boundary(pos_examples, neg_examples, snapshots[step][0])\n", + " fig.add_subplot(1, 2, 2)\n", + " pylab.plot(np.arange(len(snapshots[:,1])), snapshots[:,1])\n", + " pylab.ylabel('Accuracy')\n", + " pylab.xlabel('Iteration')\n", + " pylab.plot(step, snapshots[step,1][0], \"bo\")\n", + " pylab.show()\n", + "def pl1(step): plotit(pos_examples,neg_examples,snapshots,step)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8561af1ae77c421f9ca068fe0bdac566", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=0, description='step', max=6), Output()), _dom_classes=('widget-interact…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interact(pl1, step=widgets.IntSlider(value=0, min=0, max=len(snapshots)-1))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Limitações do Perceptron\n", + "\n", + "Como visto acima, o perceptron é um **classificador linear**. Ele consegue distinguir bem entre duas classes se forem **linearmente separáveis**, ou seja, se puderem ser separadas por uma linha reta. Caso contrário, o processo de treino do perceptron não irá convergir.\n", + "\n", + "Um exemplo claro de um problema que não pode ser resolvido por um perceptron é o chamado **problema XOR**. Queremos que o nosso perceptron aprenda a função booleana XOR, que possui a seguinte tabela de verdade:\n", + "\n", + "| | 0 | 1 |\n", + "|---|---|---|\n", + "| 0 | 0 | 1 | \n", + "| 1 | 1 | 0 |\n", + "\n", + "Vamos tentar fazer isso! Vamos preencher manualmente todos os exemplos de treino positivos e negativos e, em seguida, chamar a nossa função de treino definida acima:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "pos_examples_xor = np.array([[1,0,1],[0,1,1]])\n", + "neg_examples_xor = np.array([[1,1,1],[0,0,1]])\n", + "\n", + "snapshots_xor = train_graph(pos_examples_xor,neg_examples_xor,1000)\n", + "def pl2(step): plotit(pos_examples_xor,neg_examples_xor,snapshots_xor,step)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "8f45bf78e4c8471fbc6eea233dde2bf6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=0, description='step', max=6), Output()), _dom_classes=('widget-interact…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interact(pl2, step=widgets.IntSlider(value=0, min=0, max=len(snapshots)-1))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Como pode ver no gráfico acima, a precisão nunca ultrapassa os 75%, porque é impossível traçar uma linha reta de forma a acertar todos os exemplos possíveis.\n", + "\n", + "O problema XOR é um exemplo clássico das limitações do perceptrão, e foi destacado por Marvin Minsky e Seymour Papert em 1969 no seu livro [Perceptrons](https://en.wikipedia.org/wiki/Perceptrons_(book)). Esta observação limitou a investigação na área das redes neuronais durante quase 10 anos, embora - e veremos isto na próxima secção do nosso curso - perceptrões com múltiplas camadas sejam perfeitamente capazes de resolver tais problemas.\n", + "\n", + "## Exemplo Complexo - MNIST\n", + "\n", + "Embora o perceptrão não consiga resolver o problema XOR, ele pode resolver problemas muito mais complexos, como o reconhecimento de caracteres manuscritos.\n", + "\n", + "Um conjunto de dados frequentemente utilizado para dominar o aprendizado de máquina chama-se [MNIST](https://en.wikipedia.org/wiki/MNIST_database). Foi criado pelo Instituto Nacional de Padrões e Tecnologia Modificado e contém um conjunto de treino com 60.000 dígitos manuscritos, recolhidos de cerca de 250 estudantes e funcionários do instituto. Existe também um conjunto de teste com 10.000 dígitos, recolhidos de diferentes indivíduos.\n", + "\n", + "Todos os dígitos são representados por imagens em escala de cinza com tamanho de 28x28 pixels.\n", + "\n", + "> O conjunto de dados MNIST está disponível como uma competição de treino no [Kaggle](https://www.kaggle.com/c/digit-recognizer), um site que organiza competições e concursos de aprendizado de máquina. Assim que aprender a classificar os dígitos do MNIST, pode submeter a sua solução ao Kaggle para ver como é avaliada entre outros participantes.\n", + "\n", + "Começamos por carregar o conjunto de dados MNIST:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "# If you are not running this notebook from a cloned repository, you may need to grab the binary dataset file first\n", + "# !wget https://github.com/microsoft/AI-For-Beginners/blob/main/data/mnist.pkl.gz?raw=true\n", + "# In this case correct the link to the dataset below as well.\n", + "\n", + "with gzip.open('../../data/mnist.pkl.gz', 'rb') as mnist_pickle:\n", + " MNIST = pickle.load(mnist_pickle, encoding='latin1')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos agora plotar o conjunto de dados:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 0 188 255 94 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 191 250 253 93 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n", + "1\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "print(MNIST['Train']['Features'][0][130:180])\n", + "print(MNIST['Train']['Labels'][0])\n", + "features = MNIST['Train']['Features'].astype(np.float32) / 256.0\n", + "labels = MNIST['Train']['Labels']\n", + "fig = pylab.figure(figsize=(10,5))\n", + "for i in range(10):\n", + " ax = fig.add_subplot(1,10,i+1)\n", + " pylab.imshow(features[i].reshape(28,28))\n", + "pylab.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Porque o perceptron é um classificador binário, iremos limitar o nosso problema ao reconhecimento de apenas dois dígitos. A função abaixo irá preencher os arrays de amostras positivas e negativas com dois dígitos dados (e também mostrará amostras desses dígitos para maior clareza).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def set_mnist_pos_neg(positive_label, negative_label):\n", + " positive_indices = [i for i, j in enumerate(MNIST['Train']['Labels']) \n", + " if j == positive_label]\n", + " negative_indices = [i for i, j in enumerate(MNIST['Train']['Labels']) \n", + " if j == negative_label]\n", + "\n", + " positive_images = MNIST['Train']['Features'][positive_indices]\n", + " negative_images = MNIST['Train']['Features'][negative_indices]\n", + "\n", + " fig = pylab.figure()\n", + " ax = fig.add_subplot(1, 2, 1)\n", + " pylab.imshow(positive_images[0].reshape(28,28), cmap='gray', interpolation='nearest')\n", + " ax.set_xticks([])\n", + " ax.set_yticks([])\n", + " ax = fig.add_subplot(1, 2, 2)\n", + " pylab.imshow(negative_images[0].reshape(28,28), cmap='gray', interpolation='nearest')\n", + " ax.set_xticks([])\n", + " ax.set_yticks([])\n", + " pylab.show()\n", + " \n", + " return positive_images, negative_images" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos começar por tentar classificar entre 0 e 1:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pos1,neg1 = set_mnist_pos_neg(1,0)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "def plotit2(snapshots_mn,step):\n", + " fig = pylab.figure(figsize=(10,4))\n", + " ax = fig.add_subplot(1, 2, 1)\n", + " pylab.imshow(snapshots_mn[step][0].reshape(28, 28), interpolation='nearest')\n", + " ax.set_xticks([])\n", + " ax.set_yticks([])\n", + " pylab.colorbar()\n", + " ax = fig.add_subplot(1, 2, 2)\n", + " ax.set_ylim([0,1])\n", + " pylab.plot(np.arange(len(snapshots_mn[:,1])), snapshots_mn[:,1])\n", + " pylab.plot(step, snapshots_mn[step,1], \"bo\")\n", + " pylab.show()\n", + "def pl3(step): plotit2(snapshots_mn,step)\n", + "def pl4(step): plotit2(snapshots_mn2,step) " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "afbc754ef0d04c95a1af039574b46a6b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=0, description='step', max=66), Output()), _dom_classes=('widget-interac…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "snapshots_mn = train_graph(pos1,neg1,1000) \n", + "interact(pl3, step=widgets.IntSlider(value=0, min=0, max=len(snapshots_mn) - 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Por favor, note como a precisão aumenta quase 100% muito rapidamente.\n", + "\n", + "Por favor, mova o cursor para uma posição mais próxima do final do treino e observe a matriz de pesos plotada à esquerda. Esta matriz permitirá que você entenda como o perceptron realmente funciona. Pode-se ver os valores altos de peso no meio do campo, que correspondem a pixels que geralmente estão presentes no dígito 1, e valores negativos baixos nas laterais, onde estão partes do dígito 0. Assim, se o dígito apresentado ao perceptron for de fato 1, a parte central será multiplicada por valores altos, produzindo um resultado positivo. Por outro lado, quando o perceptron observa o dígito 0, os pixels correspondentes serão multiplicados por números negativos.\n", + "\n", + "> Pode reparar que, se dermos ao perceptron um dígito 1 ligeiramente deslocado horizontalmente, de forma que seus pixels ocupem o lugar onde há partes verticais do dígito 0, podemos obter um resultado incorreto. Como a natureza do nosso conjunto de dados MNIST é tal que todos os dígitos estão centralizados e posicionados corretamente, o perceptron depende disso para distinguir entre os dígitos.\n", + "\n", + "Agora vamos experimentar diferentes dígitos:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWAAAACqCAYAAACTZZUqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAI90lEQVR4nO3dT4hVZR8H8HPfphJxLKzQippFZIOGTFEZFNEuxE3YIjAKFxYoKkqMItGqiCjEhSARWRkVgqCbyAKFslqJC/9ubKOgKP6JcuHoQPfdvBAvz+/Uvc2993e98/ksvzxzzsPM8cvB8zznNJrNZgVA7/0newIA05UCBkiigAGSKGCAJAoYIIkCBkgy1M7gRqNhzRpd1Ww2G70+p+uabqu7rt0BAyRRwABJFDBAEgUMkEQBAyRRwABJFDBAEgUMkEQBAyRRwABJFDBAEgUMkEQBAyRRwABJFDBAEgUMkEQBAyRRwABJFDBAEgUMkEQBAyRRwABJFDBAEgUMkEQBAyQZyp5ANz3yyCNF9sQTT/Ts/CtXrgzz559/vsi2bt0ajj18+HCR7d+/Pxx74cKF1icHLRgdHS2ytWvXhmNvv/32Ips7d244dunSpS3P4dChQ0W2Z8+ecOy+ffuK7OjRoy2fq9fcAQMkUcAASRQwQBIFDJBEAQMkaTSbzdYHNxqtD+6huiet0WqBBQsWdHs6Xffdd9+FeTtPlvtVs9ls9Pqc/Xpdd8vw8HCYv/fee0X22muvFdmsWbNaPlejEf852+mddkxMTBTZ7t27w7ErVqzoyhwidde1O2CAJAoYIIkCBkiigAGSDMRW5E8++STMB+GBW2TRokXZU+AmMTIyUmQ//vhjOPaBBx5o6ZjffvttmE9OThZZrx/CPfbYY0X28ssvh2N///33IhsfHw/H3rhxY2oTq+EOGCCJAgZIooABkihggCQKGCDJQKyCgOkuehl6VVXV119/XWQPPvhgODZambBr164ie/XVV8Of//PPP/9uij0RbZNevnx5OHbZsmVFNnPmzHCsVRAAA0YBAyRRwABJFDBAkoF4H/Drr78e5h999FGPZ9Ib586dC/NWt5L2M+8D/nfqrvXo30bd9uAvv/yyyNavX19kV65caW9yeB8wQL9RwABJFDBAEgUMkEQBAyQZiFUQs2fPDvOjR48WWTsrBU6ePBnmn376aZH99ttvRbZjx46Wz9UOqyA6q1+v63ZcvHgxzO+6664i+/zzz8OxGzZsKLLopeW0zyoIgD6jgAGSKGCAJAoYIMlAvA/4jz/+CPMXX3yxyOq+oPzVV18V2c6dO8Ox0VbMuuNCpy1ZsqTI7rjjjnBs9JA9ethWVVN/4HbnnXcW2dBQXDHRvC5fvjyl89+M3AEDJFHAAEkUMEASBQyQRAEDJBmIrcjtqHtaPNUnwNH24Llz507pmHX2798f5i+88EJXztdLtiL/pe5Lxz/88EORPfXUUy0f95Zbbml57L333ltkq1atCsdGebQVuqqq6vr160X28ccfh2PHx8eLrFtfKe4WW5EB+owCBkiigAGSKGCAJAOxFbkd7TxsGxkZCfO33367yIaHh//1nP7OTz/9VGQrVqzoyrnoL3XXVDsP3L755psiW7lyZTh206ZNRXbPPfe0PK923HbbbUW2Zs2acOylS5eK7J133pnyHPqBO2CAJAoYIIkCBkiigAGSTLudcO04cuRImD/66KMdP9fExESYR+9YnZyc7Pj5+4WdcH+59dZbwzzaCfnss8+2fNxGI/4Vt9oFhw4dCvNjx461PIeXXnqpyOp2qZ4/f77IHn/88XDshQsXWp5DL9kJB9BnFDBAEgUMkEQBAyRRwABJpt1W5DrRE+dou2QnRCsetmzZEo4d5BUP/L26v/1bb71VZAcOHAjHRtf11atXw7HRl8Hff//9Ijtz5kz48+2IVm1EK36qKn4n8UMPPRSO7ddVEHXcAQMkUcAASRQwQBIFDJDEQ7j/id6FOn/+/K6ca86cOUUWfaQQIj///HORLVy4MBwbfYDz2rVr4dhOPFxrVbTtuW4r9OXLl4vs7NmzHZ9TBnfAAEkUMEASBQyQRAEDJFHAAEmm3SqI0dHRMO/Gl4brvsDczkvwoRW//vpr9hRCdf/e7r///paPcfjw4SI7ffr0v55TP3EHDJBEAQMkUcAASRQwQJKBfgg3NjZWZHv27AnHjoyMdPz8b775ZpjfuHGj4+eCfrRz584wnzVrVsvH2Lt3b6em03fcAQMkUcAASRQwQBIFDJBEAQMkGehVEM8880yRdWO1Q1VV1YkTJ4rs4MGDXTlX9KXbqqqqhx9+uCvnO3/+fJFduXKlK+fi5rVhw4Yie/LJJ8Ox0Xb8HTt2hGM/++yzqU2sj7kDBkiigAGSKGCAJAoYIMlAP4TrpeirtFu3bg3HHjlyZErnqtvGuW7duikdt873339fZMuWLQvHTkxMdGUO9I/nnnsuzLds2VJkjUYjHHv16tUie/fdd8Oxk5OTbczu5uIOGCCJAgZIooABkihggCQKGCDJQK+CiLbQRk9fq6qqhoeHO37+pUuXtpVna+d3MzQ00JfOtDRz5swiW7NmTZGNj4+HPx9tL65bwbBx48YiO3PmzD9NceC4AwZIooABkihggCQKGCBJI/qP89rBjUbrg/vUL7/8EuZPP/10j2fSG9GDyKqKt33Wje3lV2mbzWa8d7WLbrbrevHixUV23333hWOjv90bb7wRjl27dm2RLViwoM3Z/b8PPvggzDdv3jyl495s6q5rd8AASRQwQBIFDJBEAQMkUcAASabdftJXXnklzLdt21ZkdS+ernsheq9cv349zA8cOFBkdU+bjx8/3tE50Tvz5s0rsi+++CIce+3atSK7++67w7Gtrog6depUmEdfNf7www9bOuZ05Q4YIIkCBkiigAGSKGCAJNNuK3I76t7bO3v27CJbvXp1kW3fvr3jc6qq+i8P93LLcLfYivzPxsbGiqxui/2MGTOKrO5LxdHXuqNrKnrYVlVVdfbs2TDHVmSAvqOAAZIoYIAkChggiQIGSGIVBH3FKggGkVUQAH1GAQMkUcAASRQwQBIFDJBEAQMkUcAASRQwQBIFDJBEAQMkUcAASRQwQBIFDJBEAQMkUcAASYbaHH+pqqrT3ZgIVFU1knRe1zXdVHtdt/VCdgA6x39BACRRwABJFDBAEgUMkEQBAyRRwABJFDBAEgUMkEQBAyT5L95QHHzClR5NAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pos2,neg2 = set_mnist_pos_neg(2,5)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "bf6f25d14c3b4d548ac026af97f70e2a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=0, description='step', max=66), Output()), _dom_classes=('widget-interac…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "snapshots_mn2 = train_graph(pos2,neg2,1000)\n", + "interact(pl4, step=widgets.IntSlider(value=0, min=0, max=len(snapshots_mn2) - 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Discussão\n", + "\n", + "Por alguma razão, 2 e 5 não são tão facilmente separáveis. Apesar de obtermos uma precisão relativamente alta (acima de 85%), é evidente que o perceptron para de aprender em determinado momento.\n", + "\n", + "Para entender por que isso acontece, podemos tentar usar [Análise de Componentes Principais](https://en.wikipedia.org/wiki/Principal_component_analysis) (PCA). Trata-se de uma técnica de aprendizagem automática utilizada para reduzir a dimensionalidade do conjunto de dados de entrada, de forma a obter a melhor separabilidade entre classes.\n", + "\n", + "No nosso caso, uma imagem de entrada tem 784 pixels (características de entrada), e queremos usar o PCA para reduzir o número de parâmetros para apenas 2, de modo a podermos representá-los num gráfico. Esses dois parâmetros seriam uma combinação linear das características originais, e podemos encarar este procedimento como uma \"rotação\" do nosso espaço original de 784 dimensões e observar a sua projeção no espaço 2D, até obtermos a melhor visualização que separa as classes.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "from sklearn.decomposition import PCA\n", + "\n", + "def pca_analysis(positive_label, negative_label):\n", + " positive_images, negative_images = set_mnist_pos_neg(positive_label, negative_label)\n", + " M = np.append(positive_images, negative_images, 0)\n", + "\n", + " mypca = PCA(n_components=2)\n", + " mypca.fit(M)\n", + " \n", + " pos_points = mypca.transform(positive_images[:200])\n", + " neg_points = mypca.transform(negative_images[:200])\n", + "\n", + " pylab.plot(pos_points[:,0], pos_points[:,1], 'bo')\n", + " pylab.plot(neg_points[:,0], neg_points[:,1], 'ro')" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX8AAAD4CAYAAAAEhuazAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAAow0lEQVR4nO2dX4wkx33fv7XDXcZ7ywS8ISVcJO0sGQtBqJdE3AgiEutFQSTeixxYBmQM6QMt4KBlAjAB9MDDvujlHqwgAajQVHiJjrpwFpadf7AswKAlJkEAJzC9DPhPkM8k5dszLULk3dkJqZV11F3lobu1vb1V1VXdVd1V3d8P0NjZmpnu6p6qb/3qV7+qElJKEEIIGRdLfWeAEEJI91D8CSFkhFD8CSFkhFD8CSFkhFD8CSFkhNzSdwZsueOOO+TGxkbf2SCEkKR4/vnnr0gp76ymJyP+Gxsb2N3d7TsbhBCSFEKIPVU63T6EEDJCKP6EEDJCKP6EEDJCKP6EEDJCKP6EEDJCKP49srMDbGwAS0vZ352dvnNECBkLyYR6Do2dHeD0aWB/P/t/by/7HwDm8/7yRQgZB7T8e2J7+0D4C/b3s3RCCAkNxb8nLl92SyeEEJ9Q/Htifd0tnRBCfELxD0TdYO7Zs8Dq6uG01dUsnRBCQkPxD0AxmLu3B0h5MJhbbgDmc+DcOWA2A4TI/p47x8FeQkg3iFT28N3c3JSpLOy2sZEJfpXZDLh0qevcEELGjBDieSnlZjWdln8AOJhLCIkdin8AOJhLCIkdin8AOJhLCIkdin8AOJhLCIkdLu8QiPmcYk8IiRda/oQQMkIo/j1hmgTG1T4JIaGh26cHTCt6AlztkxASHk7y6gHTJDCAE8QIIf7gJK+eKbtyVOIOZJPAOEGMENIFdPt0QNXNo6OYBKZqHDhBjBDiE4p/B6g2bqlSngRWbSg4QYwQ4hu6fTrA5LIpTwIDgEceOSz80ykniBFC/GMt/kKI80KIt4QQr5TSjgshvi2EeDX/e3vpvTNCiNeEEBeFEJ8qpd8rhHg5f+8rQgjh73bi5Phxdfp0Cty8eTCQ+9BDwNWrhz/zzjtBs0YIGSkulv/XAXy6kvYogGellB8G8Gz+P4QQ9wD4HICP5N95Qggxyb/zVQCnAXw4P6rnTJK2sfnb28B77x1Nv36d+/qSnD4ngHDyyfCQUlofADYAvFL6/yKAE/nrEwAu5q/PADhT+twzAO7LP/PHpfRfAfCkzbXvvfdeGSuLhZSrq1JmW7dkx+pqli6llEIcfq84hDg4h+4z1c/1wWIh5WyW5WM2O7gv0iF1hSyWa7OwRAeAXanSc1Wi7lCI/19W3v+L/O/jAB4opX8NwGcBbAL4Tin9FwB8y+baMYv/dKoW7dkse382M79v+kz1c13Tp+aQEjaFqO9rs7BEiU78Qw34qvz40pCuPokQp4UQu0KI3bfffttb5nyys3PUT1+wt5e9b7PE89mzwPLy0XOsrPQb6aOKVNrfpyuqc/qcAGJ7bRaWpGgr/j8UQpwAgPzvW3n6GwA+VPrcBwH8IE//oCJdiZTynJRyU0q5eeedd7bMahjqynWxNEPdEs/zOfDUU9kgcMF0Cpw/32+kDyedRUKfOwTZXpuFJSnaiv83AZzKX58C8Dul9M8JIW4VQtyFbGD3OSnlmwDeEUJ8PI/y+dXSd5KkrlwXhs98nkX1FNE9KkGfz4ErVw76zFeu9B/iyV3JIqHPHYJsr83CkhYqX5DqAPCbAN4E8B4yC/7zAKbIonxezf8eL31+G8DryAaF7y+lbwJ4JX/vceTrC9Udsfr8Tb76WAZs20A3bkT0OZhqc22bwsIB4c6BjwHfPo9YxV9V3mMasPUB6yuxxlRYaEn0gk78uapnS3Z2slm5ukHf1VXO0CUEgHk5Wy5ZGwyu6tmQuk1XTp8+LPwrK9lALffuJaQCB4Sjggu7GTBtujKfqyPbrl8H1taywVpCSIn1dS5ZGxG0/A3UhS37NmQ4g54Mmj4jlsgRKP4G6sTdZ2Rb0cvY28tGwopeBhsAMhjm8/oJL6QzKP4G6sTdpyHDyZFkFNhMeCGdQPE3cPKkOv3nfz7769OQ0fUy9vboBiIdQ//jKKD4G/jt31anP/vsQX1wNWR09crkKircQL/2a6yHJDD0P44Gir8BXew+cNgdUwi6EMAtt2R/NzaAhx8+LPQPP6yvVyoXUpXr17M5BSQxUrKk6X8cD6qZXzEefczwrVu2oVjawbQWf3WZB9MM4PLkSNN56uCM3IhIbVarzeYTJCnQ8ZLOyfPww+b3hTgIWbadJK37XOHvL7uQmsJee2SkZkmPdXG2lHpnnqD4K3j4YeCrXzV/xueqGKp6VV7a2Sa9IDWtGTypzWodYyz+SC0mir+Cc+f079WJbx3V7ep19eqxx45u7rK8nKWbSE1rBk9qlvQYY/FHajFR/BXcuKF/77HHsvrQlC98wa5eFZu7lD/71FP1dTA1rRk8sVjSLm6NscXij9ViUg0ExHh0OeA7mZgHeW2WcVYd02n4vKc2vjgK+h6BZ6Ew0+f+yB0Arudvz9aWXsCLoIdyfZ5OpVxeNgt/l3Wtb60hkTFwcWvNwBtHnfhzPf8Kdevzq5Ye1y1TPplkPef19ayXP/TeM4mUpSV1hIIQ7ULLhsTOTubjv3x5cBWW6/lboFqfv8q77x51l+pcgzdvjsdtmgQjDOcDEH4gaAjPdWzjHKD4H0I16F/l6tWjUWAcZO0RW+EZaTgfgLCDzmN+rqmj8gXFeHTh87edqVsM3tb5/KfTA7ch/fABcPHVjt3vHaoAjv25JgDo869H57u3YWUli8P/0Y8Op6+uAqdOARcuHO5VcG9fD7jsCUu/dxj4XKOHPn8LbBZX03H9OvBXf3U0fX8/E/kRziEJj0t8dte+uVB+cF/n9XUe+jzTRdUdiPHoKtRzsZByacne/dPm4FpZLXFxOXQZzhfqWr7O6zN/Aw+THAJgnL8906lesGcz8/uqQzdpjG7RlrgKT1cDL6H84L7O6zt/HNCKGoq/A3Wr2rrM8F1dzSaN6TSK9aYlMT7AUMsi+zovl20eFTrxp88/p+wCXdI8lcKNWax9ZcO5c8ATT6jXygIYJdeaGOOzjx93S7fFl3+dfnoCDvgCOBqqrFrYrRoWPZ/XL/C2tGTWopEuJtg9Q5iEBPiL149lsTnSL6ruQIxHSLePzgU6mZi9CXXun2PH9J8zfY+9b4/0MSAZ0q3iy80Vo7uMBAGM89fTJlR5Zwd44AH1e8X3TWv/qHoZqjB10hCXuQApX5MQDYzzN6BzdUppt/S5zv1TnFcXjn7jBnvfweljrXa6VUgCUPxhntxlMwhbV9d1jUsx8DumTZM6p4/BzS52wxrKOAbpD5UvKMYjdKhn4QI1xffbfF/lQuU8mB6JdS5AG1igiANgnL8dpsXdmurBYnF4Ylh5wTfSAbaCnoqocjE14oBO/On2qWDyBkiHWPyiVy4E8OCDh/cI+PGP2+WRPX5HbOcCxBx7W/7RdasPDn3PWeIVin8Fm8Xd9vezlTptlo4HjkYStdETLp8ekFg38q7+6DpcxzHKFsott2R/aU2MB1V3IMaj6z18TZu41y3RYLP2T9OQb/b4AxLrwzUNRjV1T5kmqcTo6iKNAX3+9VR98zbHdGq/zo8PPeGyLAEoj/ZXH3AMQhhiIKquQem7wSPe0In/KN0+Kp+5zf69Kq5erd/6sUqbkG8uy+IZlY9OiOx1LLG3plhh1zWNisJft2tR364uEh5Vi+B6ALgE4GUALyBvZQAcB/BtAK/mf28vff4MgNcAXATwKZtr+LL8dQEdrhZ/00OIzK3kO/99G6fJEqurp0zIdfxp+Q8ehHT75OJ/RyXtywAezV8/CuDX89f3AHgRwK0A7gLwOoBJ3TV8ib+N+9THMZ2GW8c/hVD0ZEjFj+bjR7ct/LQmBoVO/EO6fT4D4EL++gKAXyylf0NK+RMp5Z/mPYCPBczHIbroza6sAI89pl8XqG0eYlzFOFlS8aP5+NFNBW8yyf725epi/HLn+BJ/CeD3hRDPCyFO52nvl1K+CQD53/fl6R8A8Gel776Rpx1BCHFaCLErhNh9++23vWRUV6cLN68Pbrstqzup6MqoGdM6PKaxg5/+NLP7+7AmVPHLDz6YRuhpyo2WqjvgegD4m/nf9yFz6XwCwF9WPvMX+d/fAPBAKf1rAH6p7hqhff6+/fqma7FHHYimrpGx+NFiLZB17qgY8qgi1udZAV2FegL4EoAvIhvMPZGnnQBwMX99BsCZ0uefAXBf3Xl9hnqq6rrPsYCyT38sutI7XVTEIfyYMd6DKZS1OCaTOPJaJoVgARlQ/AEcA3Bb6fX/AvBpAP8Shwd8v5y//ggOD/h+Hx0O+OrY2vIn/tOpvm7FWPcGQeiKmIiVlySpDkQnEiwQUvzvzsX8RQDfBbCdp08BPIss1PNZAMdL39lGFuVzEcD9NtcJLf4u5c8lLLRcXqkfAQldEbu08mK0EELmKdUQ1LFb/l0docTfxuVT6EpR5hcLKVdW7BuAoiwkUlbSJPTD7crKi9FC6NKlVq5wuiOGxlDKOH8rBRR/BW0MDtUyzabGQ8pkeolpEnrd/q5abt/XCTk/IJTVsliYF9eKSWBj7KVVoPgrcBnktRHoujpCyz8wIdft78rK82kh+Mqzrzy5CGWdZcZKY83oxV9V7myCDFRlrdxDLQyU2SwbNDbVtUR6icOnaSvsw8qrO4dPC6HJuVxC4VTdYZ/b2S0W+grJ7rI1oxZ/Xbk7dsxO+OsEvPy5rS1z3U6glzh8+vK/qQaLVlbai6QO1/vUXbvOqrHJd9NGjd3l1oxa/NvE8FfDi7kS7gDoQlBUrbxuYGg6rf9uE1zv0/T5tj2WpstSs7vcmlGLv4t7x1T2bc7F3mgChBaUJtPIfaNrbEz32aZHVPfdNhvSsLvcilGLv4/Zu0W57MryZ3kPTMgH3KTAtclr9X2Vm6bcy9Dda5seUd13bUPr2HX2zqjFX1XumvQGinoXevc79nQTQjX673pU3T7V87tGEdQVbpOF3bTg2Xy33Ejp8saus3dGLf5SuhlHpqOY5FXtUZsMKlc4xpUILhNFikKyvHw4bXnZXHCaxg83tbDb9IhcvstC3hmjF38VdXNJdEaTTfBDGzgZLBFchLcoIK7iWlcYmg5o9V2Y2L3tDIp/BRv/ve4ItUNXAY2iRLAR3tARO7r36/JmW5hCjI3oJspQ+INA8S/h2lvv2piiUZQIXYz+N/H5F91TXWhpdbEql2uvrJiXrW17P67nYlRELRT/Em2jf5aWwtX1ApbrBOhi9L+4juvMQVsLx5TPNuGZOnx1a2khWUPxL9E27v/YMZY7Io+O/Nta1F3gYuFMJuqGxbaiuAh30wEt26gq+kaPoBP/kBu4R8vx4+2+v7+f7XE9m2XbjPa15zXpkWLf2atXD9J+7ueAxaKffXCrmDZrr3LjRiade3vZPRX70NpuNu1yrSYbW5f3+C3y6yMvI2eU4m9iNgO2toC1Nf1n1tezun3pEnDzZhx1nXTM9nZmBZTZ38/SY8BWuKuU7+Hkycy68Xmts2eB1dXDaaurWboO1bP2kZeRM0rxv3ZNnS5EJuRPPAG8807WCFTLfl05JSNBZ2H2aXnu7AAbG8DSEvDuu8Dy8uH3l5eBlZX681y+nJ3rwoWsR1BmqSIZrhViPnfvNts+05UVVk4HRin+tj3PJ54Ann6a7p1RUxbUjY16l0hflmfZNSJl5o4SAphODwrvU08B588fFOjJRH2u9XW9tX377foKoXtWVVy7zbbPtNpQETOqgYAYj9ChnhywJUcwFZTYClHTtft19+BrOehQ651wwNcaMNrnMLroOIZXkp9hs1hZLAWmbRRN9R58LgftA9ton75nLkcIxb+G2Aw5EgEprbPRVnxtFr8KsRz01taBmE8m2f9d3O+IoPjXwLJEjpBSoQixImfdtnRlmjyrrS31d2waAFpr1lD8a0jJyCMdkZrA6Fw4bXfhsr2267PSuW8mk3b3Sw5B8a8hJSOPdEjqAmMjyr4sH9dnpfPbA653SQzoxH+UoZ4qmsw9IZ6xDRXskpRn8+3sAKdO1U9G8xW2avusit9Zhy4EtY4Yy0/MqFqEGI9Q6/lXx7hSNvKSJjUXS+zUhUeWrfoun73Ldo62FVK1uxLLz88A3T6HodZEho3fbQgumK7y77rcdFd5a7OkrqqCbm2ZF6CL3W/bwXOn+OfUbeISe1kZLHV+59Rb667zbxLEPp5bm92TTMZA3cqjMUdsdFQmKP7SrscZc1kZNHWWv25jklRa66YRBU13vdJdbzLpbp+B8ud87Z5UrqA2jUnM5aOjKBOKv0y/rAyauqUUUm+tm0TUtNksJrRV6XL+uoq3uqpv3E0VtM7qj71n2FF8OcVfNi8rTd1yqbuoO8d1qYEuW+umMfQFTay8tttEhiyApvupXtd0D9Op/c5j1QpqOndx3pih5R+H5a+rG00NqNRd1FFharW7eKCmGbC2P7LqHHX74cbsz64bU6jm06YBcw2/Uz1TIeyXiOgb+vzDiX/b5UqkbN44c9KYR3QPczrt9/q6Gaq6H7lcIKdTKZeXzYWxreUfEtdnEqoB66p7Heo6jPbxL/4+liuRsrlbjstFeCSEheRS6Vw3fbb5kW1DW219/l37GHW/ictz6rsBsyXxbvzoxN+X5b22pj7P2lo31yc5PsXNtTK7Wrk2PRJb68Am2qcvcVL9JqZeWqoC2ldl9lTmRyf+vizvOnelS3RbKmV98LhWZlM3suq6ATJfftNQzCaCEpOlURe1lWIERIhufN2z8CggoxN/X/WhrtdqqueplvWC1POvpWnYpephNJ1/4CKSdb7K2HyMQys4bcRE9SxshN1jgz468XdtOHXl1Wb8qqtxxy4J1nOJQRh8WspthNdWGKpHQKFwyutY8B3yZ2MweGzQoxN/AJ8GcBHAawAerfu8j2ifJi4a3X4T1WNoBNGTWHxhPvPh+0HVRfiozh/6ucbyu/VJk8bP9rdUCftQLX8AEwCvA7gbwAqAFwHcY/pOyPX8655zeae5sYh/EE9CbL5pH5aszlJvOsnINrJINzCsup+292oa8B5jT8AWF+EP2KDHJv73AXim9P8ZAGdM3wkp/rb1S9dbG6LbJ4hOx+ab9sVi4W9J4SaWf13e2oqITYM0tp5AHYuF/rnZRj4NMdoHwGcB/PvS/w8CeFzxudMAdgHsrq+vN7rxOky/UdWwWSyOBncsLw+zzAfp6cdk+fvG17018fmHzpfvBmkM6J6ZEJ1HPsUm/r+sEP9/Y/pOKMvftlynHq3WBO/3OmTfsc9ejWu0T+h82TRIQ+jB+cTUW+qY2MS/F7ePSsxcJm/SsPHAUFvPWHs1Pnskxe/muqyFy7mHUiYiKg+xif8tAL4P4K7SgO9HTN9pK/4q40UIKY8dsxd/GjZES4hejQ9RDJUvX+ccam8wovuKSvyz/OAkgD/Jo3626z7fVvxN7h3VJM1IGu2kSN6Aa3sDxfcBt41XdOeqisfysnkV0Lp8+fxhbM5p85mILGTvRFIhohN/16Ot+JvcO9Op3V4SxUqxkfymURGRodMMXzfg6zw2g1G2E436KKy2z8FmTMLnPYyw8o5e/E11SQj9Am7lozDmquU1KZELRJIGXAg/tq8HYTsYZTpvn+FppudQfu5LS+rPFfHTfbuYBtBYjF7868JubepZ0zo4BpIL4Q8VwWI6j4uQ2IahmfLX58QUl81eTHn0aVW4nquv7qznBmf04i9l5rZRWe0+xD9akeuI5Cz/ELHrPib2lM9lI5Km/Jm+FxrTrGCXCuXTqnB1MfmOarIhQIND8c9RNapthT9qkeuI5Hz+IWatmib2NFn9s1xYp9NsCVmX/HUp/qrBbpWl5VqhurT8Y5jPEMCKovhrsIn1rzNWoha5DvHaWw3ta62z/Jtc01SQfE22cnkmXbl9TKJZ3HfZ1+/S6Hbp849hJnMA/ynFX4Pp9y5H9+j2pE50DChuuuhGmFr9ppXbZLX14RdbLI72FlZW2s0YVmHTkJbz5LqRfchon/KzsBH+0JYeLf/uxN92FvYABv3ToSuhNFnpTTA1WrEMHm5t+QtptRVNlxVIu8TWzdPl6qX0+fdv+Y/dh98rXYUOhfjxTcIWg+j5uGdb0Yy9MvmaS+EbRvt0I/7JDVSOga5a5KH/+CoR8dGw2vrGY3+edWM0bYQ3hoY+h+JvIKLfKRhJ3WOXohyzpd7m+q5xzS4Nq22EROwFLZSR0WRcIyAUf0v6ru8hSNLA7fuH6PuhuVxf5dfXCfTS0tFZv75CWmN17+jQRXIUkR5NicydRPG3oO/6Hoqh1NVOCf3Q6ho32+ubQtF0R1sr1LaidNGAt72GrofUJq8+lubwCMXfgqGKZHJLL8RAyIdmI56213fxv6sKdVPxrPteV+G6ba8RotLb/iYdVUCKvwWm+mYKD47dPTTURi0oIR+azbltr++yG5GqUIcS6C4KnY9rhGjkbaOhaPnHI/5t1viJ2T00VHdWUEI+NNs1Zmyu38byDynQXXQ3Q0Yu+Rj0LazD6bT9OEsLKP4Kqr+P7ZpTPTfkjeh7/DRJQj00F39+3fVdY+7LwhNSoPu0/KdT+9+tK8uoxwpI8a/QpM7UHfShEyt8D5hWP2fqwhbnWSzCrloZSlSrFptq+QpXK3vglhHFv0LT3nKqlj+JjJADpovFUVEEDjZyMVk+PoXSt6iq8l3d2tLHPIaBQfGv0HScrK4nHSsDN26GR1u3iUkEdeeeTOJwkeiweSaurqwRVAyKf4U2lr8QUn7yk+mUmb7rLKkhxDIMpu/brmZYpe+wMZtn4pLHEDH+EULxr9DU5x+70Kvou84SA7qW2cZvb8L0o+veK8I/dfQ9YcSmILuMp/he0jtSKP4KVLH7pgYh1TLRd50lBkwRK6bCWLehhEkEmwpf3/50nwPlpq7/wCoGxd+SxUJdxlPuDdLyjxibmYV13VHTmj/lwjydHnzOVfgWi6NRNEA2sNxlxfDloze5vgZWMSj+GnRlaUjjQPT5R0ybQcw6wTL98K4WgamHkiJNXV8JQvFXMCZRHFJjNihsCqGN9a+y2E0C71r4m/gOY14TRXX/Plb0jBCKvwIfEwQJaU2TmH8bi71OsF26va49BZs8921pjcQiovgrsI3177uMkoHQRmzKAmwbnthksEfXI3Dd+9c2lnpg/vUYofgrcIn1ZxkltZjE3aeP0WXZB9dr1rmKbHsKtpbVwCJrYoTir8Al1p9llBipE9q+Qq5cextNZsi6zlOo+lib5JNYoxN/kb0XP5ubm3J3d9f7eXd2gAcesPvsbAacPQvM596zQVJnYwPY2zuaPpsBly4BS0uZ3FURArh5M3Tu7LnjDuDq1aPp0ylw5crRdN19T6fAj38M7O+br7eyAnz+88CFC4c/u7oKnDvHyuYBIcTzUsrNavpSH5lJlb094KGHsgaDkENcvmxOX19Xv69LTwXdfV+7lon3bJY1cLMZsLZ29HPXr2efqzYS+/vA9rb//JKfMXrxdy1f770HPPJImLyQhKkT97NnM2u2zOpqlh4T1665pZvuez7Pej03b2Z/f/Qj9Wdv3FCn6xoW4oXRi7+qx1qHqlccEzs7WW98aSn7y55KIMoP+t13MxdGmZWVLH1pKbMyTp06bAnH6NbQifnx4+p0l0ZNd+7JxC0vxA+qgYAYj1ADvrbRPtUjVsY0ca1X6taW73nrvsY0WcahbfSRaxgpcQKM9jlK02Wd19a8Z8UbXMfHgTYRJnUPOuUfQhepM5m0j8bpYz2VtudOPBKJ4q+g6YYupuVM+i4nXMHTkrZdpLoH7fOHCF2oque3qQTVHbRiFcS2v/MAutIUfwVNLX/XkOcuy0nKBmentH1QXVn+NoWq7cxh1Ro3rpVC5xbq2xoK/TsX9H2fBij+ChYL9VanTS3/GIQ3hgYoCdpa5nUP2tcPUVeo2l7HtLpl24oRsjDaim3IHdHKeYm40gURfwBfAvDnAF7Ij5Ol984AeA3ARQCfKqXfC+Dl/L2vANlEs7oj5JLOtpMR68Q/FpdLxEZIPPhoqW0WZGv7Q9QVqrb3UbeuvRCZr9+2cpQJZQ25iG0Xln8MVp+BkOL/RUX6PQBeBHArgLsAvA5gkr/3HID7AAgAvwfgfptrhd7MxcUFpBPzyMsAKRO5tfYz6gpVW4vDptC6rINSJpQ15FLRuvD5x2L1aeha/M8AOFP6/5lc8E8A+ONS+q8AeNLmWqHF36V8u6xiG6OehCDJ3kYKmVYVqpWVg8FWnVVua3HYFtrys9KJXbVLHMoaarL+UMhon8itvpDifwnASwDOA7g9T38cwAOlz30NwGcBbAL4Tin9FwB8y3D+0wB2Aeyur68Hf0hbW/WuzjoxT0FPfDPmRq8TyoVKNX/AtZCazm9TaFVzAZaX1Q2GbcFwyUNsYht5BWgs/gC+A+AVxfEZAO8HMEE2U/gsgPP5d35DIf6/BODvK8T/d+vyIGU3e/jauH6Wlg7KWSS/be/EVhcHgU4MdQ/bRwy+j/w1+ZyreMYothFbfcGjfQBsAHglf52c20dK9wCHvstbLETu8kwPk7i1edixClQT6yHWe4mQUG6fE6XX/wLAN/LXH6kM+H6/NOD7RwA+XhrwPWlzrS7E3zXqp1xGx1z2aPl7xvRAmz7sLqzlpoIc2noYeUMRSvyfzsM2XwLwzUpjsJ1H+VwsR/Tkfv9X8vceR8+hnmWaiv/YewEx9sKTxiSGpklZJmEL3UK3KQQhXVksnOHdPqGPUJO8XGe11/UAxsrIjSu/2EzsKj5ju59vaOu6TeNiE2rXVLDZLaX4V/E1qz1EPSIDokmrqCucW1uHP+cibKFF0MeM6bpJZU3yWteLGoHFQvGv4HNW+wiNCWJDG5eDKu7YZXJRVdhCL5vss3Hx2UvR5Ws6HY07iOJfwSTyLrPZ68rNSIwLoqKNILZZVkAnbFtb4Qpjk4bONZy1SUOiy5dugG+AFhzFv4KpfC0WdoJfNBKm8OXqwnGmPTHIwGhjwbZZUKwvYXOxdEyNhe9BWlW+RhSfTPGvUFf2islcpqN6vmr50tVB034AZECEtvylTFfYbAe1Q3WZRzQQTPFXoCpftmv8TCaHz6NqSGwbDjJQbCxYnciFCJ0szh+DH7LvBmpEIaAUf0tsQz6PHTuoP03mB5io9hqm00GWyXFgEts6AWoq1LrzxrRXbgyWdywNYWAo/pa0Dfe0Oeq2gVRtMKNaN4skTkgBVAlbFxO9fPj8iVco/pbUWf5tG4c6ETddf4DuyHHTtesj5PV8RvsQr+jEX2Tvxc/m5qbc3d0Nfp2dHeD0aWB/3+95hQDW14GzZ4H5XP+5paWs5ujOcfOm33yRHtnYAPb2jqbPZsClS2ldr+t7IdYIIZ6XUm5W05f6yEzMzOfAuXNZmRUCmE4zQW7DbJaJ9qVLZuEHsgaiyXskQc6eBVZXD6etrmbpqV3v8mW3dNI7FH8F83km1DdvAmtr7axt17p19iywsnI0fXk5nCaQnqhaGrNZ9r/JQtjZyazspaXs785Os+sBwGSSdXG3t7PztDm3zjKhxRIvKl9QjEdXPv8qbXz8k8nh5Vhc9r9YWzvskq0u60JGQKglGlT++ZWVo7tz+d7rlvQCOODbjLarfS4vH4Rt2i7AuFjY7ZJHBozLyoOukQAuhdrl3BzAjRKd+HPAt4adHeChh4D33gtz/vJ42M5O1gNXjZsB2fjDlSth8kEiQzeAqsI1EsAUVdD23CQ6OODbkPkceOqprA6EoBgPK6KMTPX96tUweSAR4jJQ6upXd/k8ffaDheJvwXwOPP00cMst+s/MZpll7kpRt7a3/YeXkoTRiW7VCmkSraOK+llZyaIK2p6bJAPF35L5HPj617PonwIhgK2trAd96RLw2GNu5yzXLRtDr0njQhJFF5b5hS+4RQepUEUZnT+fdXHbnpskA33+Hih89Zcv27tSJxPgwoWDulXn4l1Zyeon6+KIKBcsmxmChCjQ+fwp/i1pOiO4Oo5mGlieTrNeBes9IcQVDvi2RDf/5ZFHmvnqjx8/fN4HHwRu3FB/dm2Nwk8I8YthCJMUVK37vb3s/z/4A3MEzmxmduVUz6vrhHGGPCHEN7T8LVBF4uzvA08+qf+OEMDJk/r3r12zj/BhtB0hxDcUfwt0lrdp7ouUWbCEjvV1O4ue0XaEkBBQ/C1oannrfPhAJui6804mjLYjhISF4m+BLuT62DHz9yYTdfp0mgm67rwXLtgvAU0IIU2g+FugW3n3ySfVyy8DmYifPq0W92IyWJMVfQkhxAcUf0vKa/wXFvl8nk28Ki+PDhyI+BNP1Iu76ryEjJY2ewoQJzjJixASB6oZk6ur7A63hJO8CCFxo4up3t7uJz8Dh+JPCIkD7gPcKRR/QkgccB/gTqH4E0LiQBf7zFmOQaD4E0LigLHPncKF3Qgh8VDEUJPg0PInhJARQvEnhJARQvEnhJARQvEnhJARQvEnhJARkszaPkKItwEYNkUMxh0ArvRw3bakmm+Aee+DVPMNpJv3rvI9k1LeWU1MRvz7Qgixq1oUKXZSzTfAvPdBqvkG0s173/mm24cQQkYIxZ8QQkYIxb8ewzbsUZNqvgHmvQ9SzTeQbt57zTd9/oQQMkJo+RNCyAih+BNCyAgZtfgLIX5ZCPFdIcRNIcRm5b0zQojXhBAXhRCfKqXfK4R4OX/vK0IIkaffKoT4rTz9D4UQGx3ex5eEEH8uhHghP042vY8+EUJ8Os/na0KIR/vOjwohxKX8ub0ghNjN044LIb4thHg1/3t76fPK599RXs8LId4SQrxSSnPOa9dlRZPv6Mu4EOJDQoj/LoT4Xq4rj+TpcT5zKeVoDwB/B8DfBvA/AGyW0u8B8CKAWwHcBeB1AJP8vecA3AdAAPg9APfn6Q8D+Lf5688B+K0O7+NLAL6oSHe+jx5/i0mev7sBrOT5vqfvMqLI5yUAd1TSvgzg0fz1owB+ve75d5TXTwD4KIBX2uS167KiyXf0ZRzACQAfzV/fBuBP8vxF+cxHbflLKb8npbyoeOszAL4hpfyJlPJPAbwG4GNCiBMA/rqU8n/L7Bf6DwB+sfSdC/nr/wTgkxFY003uoy8+BuA1KeX3pZTXAXwDWf5ToPzbX8DhMnHk+XeVKSnl/wRwrZLslNc+yoom3zpiyvebUsr/k79+B8D3AHwAkT7zUYu/gQ8A+LPS/2/kaR/IX1fTD31HSvlTAP8XwDR4Tg/4Z0KIl/Iuc9GtbHIffaHLa2xIAL8vhHheCHE6T3u/lPJNIBMAAO/L02O8J9e8xlRWkinjudv37wH4Q0T6zAcv/kKI7wghXlEcJqtSZbFLQ7rpO16ouY+vAvhbAP4ugDcB/KuaPAXNa0NizJOKfyCl/CiA+wH8UyHEJwyfTeWegPjLSjJlXAixBuA/A/jnUsr/Z/qoIq2zvA9+G0cp5T9q8LU3AHyo9P8HAfwgT/+gIr38nTeEELcA+Buw77rWYnsfQoh/B+BblTwV2NxHX+jyGhVSyh/kf98SQvxXZG6cHwohTkgp38y77G/lH4/xnlzzGkVZkVL+sHgdcxkXQiwjE/4dKeV/yZOjfOaDt/wb8k0AnxNZBM9dAD4M4Lm8y/aOEOLjuT//VwH8Tuk7p/LXnwXw33J/XXDyAlXwTwAUURJN7qMv/gjAh4UQdwkhVpANmn+z5zwdQghxTAhxW/EawD9G9qzLv/0pHC4TR55/t7k+glNeYykrKZTx/DpfA/A9KeW/Lr0V5zMPOfod+4GsEL0B4CcAfgjgmdJ728hG3y+iNNIOYBNZwXsdwOM4mCX91wD8R2SDNs8BuLvD+3gawMsAXsoL1Imm99Hz73ESWYTE6wC2+86PIn93I4vOeBHAd4s8IhvbeRbAq/nf43XPv6P8/iYyF8l7eTn/fJO8dl1WNPmOvowD+IfI3DMvAXghP07G+sy5vAMhhIwQun0IIWSEUPwJIWSEUPwJIWSEUPwJIWSEUPwJIWSEUPwJIWSEUPwJIWSE/H9eZaOb4ecC7gAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "pca_analysis(1,0)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "scrolled": false, + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "pca_analysis(2,5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Como pode ver, 0 e 1 podem ser claramente separados por uma linha reta. Isto indica que, no espaço original de 784 dimensões, os pontos correspondentes aos dígitos também são linearmente separáveis. No caso de 2 e 5, não conseguimos encontrar uma boa projeção que separe os dígitos claramente, e, por isso, há alguns casos de classificação errada.\n", + "\n", + "> Mais tarde neste curso, iremos aprender como criar classificadores não lineares utilizando Redes Neuronais e como lidar com o problema de dígitos que não estão devidamente alinhados. Muito em breve, alcançaremos uma precisão acima de 99% na classificação de dígitos MNIST, enquanto os classificamos em 10 classes diferentes.\n", + "\n", + "## Conclusões\n", + "\n", + " * Aprendemos sobre a arquitetura mais simples de rede neural - o perceptrão de uma camada.\n", + " * Implementámos o perceptrão \"manualmente\", utilizando um procedimento de treino simples baseado no gradiente descendente.\n", + " * Apesar da simplicidade, o perceptrão de uma camada consegue resolver problemas relativamente complexos de reconhecimento de dígitos manuscritos.\n", + " * O perceptrão de uma camada é um classificador linear e, por isso, oferece o mesmo poder de classificação que a regressão logística.\n", + " * No espaço amostral, o perceptrão pode separar duas classes de dados de entrada utilizando um hiperplano.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Créditos\n", + "\n", + "Este notebook faz parte do [AI for Beginners Curricula](http://github.com/microsoft/ai-for-beginners) e foi preparado por [Dmitry Soshnikov](http://soshnikov.com). É inspirado no Workshop de Redes Neurais da Microsoft Research Cambridge. Parte do código e dos materiais ilustrativos foram retirados de apresentações de [Katja Hoffmann](https://www.microsoft.com/en-us/research/people/kahofman/), [Matthew Johnson](https://www.microsoft.com/en-us/research/people/matjoh/) e [Ryoto Tomioka](https://www.microsoft.com/en-us/research/people/ryoto/), bem como do repositório [NeuroWorkshop](http://github.com/shwars/NeuroWorkshop).\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": { + "celltoolbar": "Slideshow", + "interpreter": { + "hash": "16aeaa504b544176258e5caf576fc030dfd6fff62d0c15825e7863ff13e121ff" + }, + "kernelspec": { + "display_name": "Python 3.8.0 64-bit (conda)", + "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.0" + }, + "livereveal": { + "start_slideshow_at": "selected" + }, + "coopTranslator": { + "original_hash": "914fc55f59efc7e17b8130d4033ac617", + "translation_date": "2025-08-31T11:50:40+00:00", + "source_file": "lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb", + "language_code": "pt" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/lab/PerceptronMultiClass.ipynb b/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/lab/PerceptronMultiClass.ipynb new file mode 100644 index 00000000..82cad4ec --- /dev/null +++ b/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/lab/PerceptronMultiClass.ipynb @@ -0,0 +1,277 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Classificação Multi-Classe com Perceptron\n", + "\n", + "Trabalho prático do [Currículo de IA para Iniciantes](https://github.com/microsoft/ai-for-beginners).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pickle\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Podes usar o seguinte código de treino do perceptrão da aula:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "def train(positive_examples, negative_examples, num_iterations = 100):\n", + " num_dims = positive_examples.shape[1]\n", + " weights = np.zeros((num_dims,1)) # initialize weights\n", + " \n", + " pos_count = positive_examples.shape[0]\n", + " neg_count = negative_examples.shape[0]\n", + " \n", + " report_frequency = 10\n", + " \n", + " for i in range(num_iterations):\n", + " pos = random.choice(positive_examples)\n", + " neg = random.choice(negative_examples)\n", + "\n", + " z = np.dot(pos, weights) \n", + " if z < 0:\n", + " weights = weights + pos.reshape(weights.shape)\n", + "\n", + " z = np.dot(neg, weights)\n", + " if z >= 0:\n", + " weights = weights - neg.reshape(weights.shape)\n", + " \n", + " if i % report_frequency == 0: \n", + " pos_out = np.dot(positive_examples, weights)\n", + " neg_out = np.dot(negative_examples, weights) \n", + " pos_correct = (pos_out >= 0).sum() / float(pos_count)\n", + " neg_correct = (neg_out < 0).sum() / float(neg_count)\n", + " print(\"Iteration={}, pos correct={}, neg correct={}\".format(i,pos_correct,neg_correct))\n", + "\n", + " return weights" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def accuracy(weights, test_x, test_labels):\n", + " res = np.dot(np.c_[test_x,np.ones(len(test_x))],weights)\n", + " return (res.reshape(test_labels.shape)*test_labels>=0).sum()/float(len(test_labels))\n", + "\n", + "accuracy(wts, test_x, test_labels)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Ler o 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 AI Curriculum.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "!rm *.pkl\n", + "https://github.com/mnielsen/neural-networks-and-deep-learning/blob/master/data/mnist.pkl.gz", + "!gzip -d mnist.pkl.gz" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "with open('mnist.pkl', 'rb') as mnist_pickle:\n", + " MNIST = pickle.load(mnist_pickle)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 0 188 255 94 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 191 250 253 93 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n", + "1\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "print(MNIST['Train']['Features'][0][130:180])\n", + "print(MNIST['Train']['Labels'][0])\n", + "features = MNIST['Train']['Features'].astype(np.float32) / 256.0\n", + "labels = MNIST['Train']['Labels']\n", + "fig = plt.figure(figsize=(10,5))\n", + "for i in range(10):\n", + " ax = fig.add_subplot(1,10,i+1)\n", + " plt.imshow(features[i].reshape(28,28))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Código para criar um conjunto de dados *um-contra-outro* para classificação de dois dígitos. É necessário modificar este código para criar um conjunto de dados *um-contra-todos*.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def set_mnist_pos_neg(positive_label, negative_label):\n", + " positive_indices = [i for i, j in enumerate(MNIST['Train']['Labels']) \n", + " if j == positive_label]\n", + " negative_indices = [i for i, j in enumerate(MNIST['Train']['Labels']) \n", + " if j == negative_label]\n", + "\n", + " positive_images = MNIST['Train']['Features'][positive_indices]\n", + " negative_images = MNIST['Train']['Features'][negative_indices]\n", + "\n", + " return positive_images, negative_images" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora precisas de:\n", + "1. Criar 10 conjuntos de dados *um-contra-todos* para todos os dígitos \n", + "1. Treinar 10 perceptrões \n", + "1. Definir a função `classify` para realizar a classificação de dígitos \n", + "1. Medir a precisão da classificação e imprimir a *matriz de confusão* \n", + "1. [Opcional] Criar uma função `classify` melhorada que realize a classificação utilizando uma única multiplicação de matrizes. \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 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": { + "celltoolbar": "Slideshow", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.5" + }, + "livereveal": { + "start_slideshow_at": "selected" + }, + "coopTranslator": { + "original_hash": "346132688ca5e351b17b56c6166067e2", + "translation_date": "2025-08-31T11:50:51+00:00", + "source_file": "lessons/3-NeuralNetworks/03-Perceptron/lab/PerceptronMultiClass.ipynb", + "language_code": "pt" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/lab/README.md b/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/lab/README.md index 23c664d9..df2295a5 100644 --- a/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/lab/README.md +++ b/translations/pt/lessons/3-NeuralNetworks/03-Perceptron/lab/README.md @@ -1,8 +1,8 @@ 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\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\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\u001b[0m in \u001b[0;36m\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 +} \ No newline at end of file diff --git a/translations/pt/lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb b/translations/pt/lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb new file mode 100644 index 00000000..58eac165 --- /dev/null +++ b/translations/pt/lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb @@ -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 +} \ No newline at end of file diff --git a/translations/pt/lessons/5-NLP/18-Transformers/TransformersTF.ipynb b/translations/pt/lessons/5-NLP/18-Transformers/TransformersTF.ipynb new file mode 100644 index 00000000..b5609ee9 --- /dev/null +++ b/translations/pt/lessons/5-NLP/18-Transformers/TransformersTF.ipynb @@ -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": [ + "" + ] + }, + "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\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': ,\n", + " 'input_word_ids': ,\n", + " 'input_mask': }" + ] + }, + "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": [ + "" + ] + }, + "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": [ + "" + ] + }, + "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': , 'token_type_ids': , 'attention_mask': }" + ] + }, + "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": [ + "" + ] + }, + "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 +} \ No newline at end of file diff --git a/translations/pt/lessons/5-NLP/19-NER/NER-TF.ipynb b/translations/pt/lessons/5-NLP/19-NER/NER-TF.ipynb new file mode 100644 index 00000000..25f5aa91 --- /dev/null +++ b/translations/pt/lessons/5-NLP/19-NER/NER-TF.ipynb @@ -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": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Sentence #WordPOSTag
0Sentence: 1ThousandsNNSO
1NaNofINO
2NaNdemonstratorsNNSO
3NaNhaveVBPO
4NaNmarchedVBNO
\n", + "
" + ], + "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] = ''\n", + "vocab.add('')\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": [ + "" + ] + }, + "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 +} \ No newline at end of file diff --git a/translations/pt/lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb b/translations/pt/lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb new file mode 100644 index 00000000..8785e7e4 --- /dev/null +++ b/translations/pt/lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb @@ -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 +} \ No newline at end of file diff --git a/translations/pt/lessons/6-Other/21-GeneticAlgorithms/Diophantine.ipynb b/translations/pt/lessons/6-Other/21-GeneticAlgorithms/Diophantine.ipynb new file mode 100644 index 00000000..af975805 --- /dev/null +++ b/translations/pt/lessons/6-Other/21-GeneticAlgorithms/Diophantine.ipynb @@ -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 +} \ No newline at end of file diff --git a/translations/pt/lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb b/translations/pt/lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb new file mode 100644 index 00000000..5215d7ae --- /dev/null +++ b/translations/pt/lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb @@ -0,0 +1,705 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "# Algoritmos Genéticos\n", + "\n", + "Este caderno faz parte do [Currículo de IA para Iniciantes](http://github.com/microsoft/ai-for-beginners).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "import random\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import math\n", + "import time" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Alguma Teoria\n", + "\n", + "**Algoritmos Genéticos** (AG) baseiam-se numa **abordagem evolutiva** para a IA, na qual são utilizados métodos de evolução de populações para obter uma solução ótima para um dado problema. Foram propostos em 1975 por [John Henry Holland](https://en.wikipedia.org/wiki/John_Henry_Holland).\n", + "\n", + "Os Algoritmos Genéticos baseiam-se nas seguintes ideias:\n", + "* Soluções válidas para o problema podem ser representadas como **genes**\n", + "* O **Cruzamento** permite combinar duas soluções para obter uma nova solução válida\n", + "* A **Seleção** é usada para escolher soluções mais ótimas utilizando uma **função de aptidão**\n", + "* **Mutações** são introduzidas para desestabilizar a otimização e evitar mínimos locais\n", + "\n", + "Se quiser implementar um Algoritmo Genético, precisará do seguinte:\n", + "\n", + "* Encontrar um método para codificar as soluções do problema utilizando **genes** $g\\in\\Gamma$\n", + "* No conjunto de genes $\\Gamma$, é necessário definir uma **função de aptidão** $\\mathrm{fit}: \\Gamma\\to\\mathbb{R}$. Valores mais baixos da função correspondem a soluções melhores.\n", + "* Definir um mecanismo de **cruzamento** para combinar dois genes e obter uma nova solução válida $\\mathrm{crossover}: \\Gamma^2\\to\\Gamma$.\n", + "* Definir um mecanismo de **mutação** $\\mathrm{mutate}: \\Gamma\\to\\Gamma$.\n", + "Em muitos casos, os algoritmos de cruzamento e mutação são bastante simples, manipulando genes como sequências numéricas ou vetores binários.\n", + "\n", + "A implementação específica de um algoritmo genético pode variar de caso para caso, mas a estrutura geral é a seguinte:\n", + "\n", + "1. Selecionar a população inicial $G\\subset\\Gamma$\n", + "2. Selecionar aleatoriamente uma das operações que será realizada neste passo: cruzamento ou mutação\n", + "3. **Cruzamento**:\n", + " * Selecionar aleatoriamente dois genes $g_1, g_2 \\in G$\n", + " * Calcular o cruzamento $g=\\mathrm{crossover}(g_1,g_2)$\n", + " * Se $\\mathrm{fit}(g)<\\mathrm{fit}(g_1)$ ou $\\mathrm{fit}(g)<\\mathrm{fit}(g_2)$ - substituir o gene correspondente na população por $g$.\n", + "4. **Mutação** - selecionar um gene aleatório $g\\in G$ e substituí-lo por $\\mathrm{mutate}(g)$\n", + "5. Repetir a partir do passo 2, até obtermos um valor suficientemente pequeno de $\\mathrm{fit}$, ou até que o limite do número de passos seja atingido.\n", + "\n", + "Tarefas tipicamente resolvidas por AG:\n", + "1. Otimização de horários\n", + "1. Embalagem ótima\n", + "1. Corte ótimo\n", + "1. Aceleração de pesquisa exaustiva\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problema 1: Divisão Justa do Tesouro\n", + "\n", + "**Tarefa**: \n", + "Duas pessoas encontraram um tesouro que contém diamantes de diferentes tamanhos (e, consequentemente, diferentes valores). Elas precisam dividir o tesouro em duas partes de forma que a diferença no valor seja 0 (ou mínima).\n", + "\n", + "**Definição formal**: \n", + "Temos um conjunto de números $S$. Precisamos dividi-lo em dois subconjuntos $S_1$ e $S_2$, de forma que $$\\left|\\sum_{i\\in S_1}i - \\sum_{j\\in S_2}j\\right|\\to\\min$$ e $S_1\\cup S_2=S$, $S_1\\cap S_2=\\emptyset$.\n", + "\n", + "Primeiramente, vamos definir o conjunto $S$:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[8344 2197 9335 3131 5863 9429 3818 9791 15 5455 1396 9538 4872 6549\n", + " 8587 5986 6021 9764 8102 5083 5739 7684 8498 3007 6599 820 7490 2372\n", + " 9370 5235 3525 3154 859 1906 8159 3950 2173 2988 2050 349 8713 2284\n", + " 4177 6033 1651 9176 5049 8201 171 5081 1216 3756 4711 2757 7738 1272\n", + " 5650 6584 5395 9004 7797 969 8104 1283 1392 4001 5768 445 274 256\n", + " 8239 8015 4381 9021 1189 8879 1411 3539 6526 8011 136 7230 2332 451\n", + " 5702 2989 4320 2446 9578 8486 4027 2410 9588 8981 2177 1493 3232 9151\n", + " 4835 5594 6859 8394 369 3200 126 4259 2283 7755 2014 2458 8327 8082\n", + " 7413 7622 1206 5533 8751 3495 5868 8472 6850 3958 3149 4672 4810 6274\n", + " 4700 6134 4627 4616 6656 9949 884 2256 7419 1926 7973 5319 5967 9158\n", + " 3823 7697 9466 5675 5412 9784 5426 8209 3421 1136 6047 4429 8001 4417\n", + " 1381 722 7350 6018 6235 7860 5853 7660 5937 6242 1 9552 3971 8302\n", + " 2633 9227 7283 154 8599 4269 9392 8539 1630 368 2409 9351 3838 9814\n", + " 6186 5743 5083 1325 1610 779 3643 3262 5768 8725 961 4611 6310 4788\n", + " 1648 5951 8118 7779]\n" + ] + } + ], + "source": [ + "N = 200\n", + "S = np.array([random.randint(1,10000) for _ in range(N)])\n", + "print(S)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos codificar cada solução possível do problema por um vetor binário $B\\in\\{0,1\\}^N$, onde o número na posição $i$ indica a qual dos conjuntos ($S_1$ ou $S_2$) o $i$-ésimo número no conjunto original $S$ pertence. A função `generate` irá gerar esses vetores binários aleatórios.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 0 0 1 1 1 1 1 0 1 1 1 0 0 1 0 1 1 1 0 0 1 1 0 1 1 0 0 1 0 1 0 1 0 1 1 1\n", + " 0 1 1 1 0 1 0 0 1 0 0 1 1 0 1 0 1 1 0 0 1 0 0 0 1 1 0 1 1 0 0 0 0 1 0 1 0\n", + " 1 0 0 0 0 0 1 1 0 1 0 0 1 0 1 0 0 1 1 0 0 1 1 1 0 0 1 1 0 1 1 0 0 0 0 1 1\n", + " 1 0 1 0 0 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 0 1 0 1 0 1 0 0 1 1 1 0 0 1 1\n", + " 0 1 1 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 1 1 1 0 0 1 1 0 1 1 0 0 1 1 0\n", + " 0 0 0 1 0 1 1 0 1 1 0 1 0 0 0]\n" + ] + } + ], + "source": [ + "def generate(S):\n", + " return np.array([random.randint(0,1) for _ in S])\n", + "\n", + "b = generate(S)\n", + "print(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos agora definir a função `fit` que calcula o \"custo\" da solução. Será a diferença entre a soma de dois conjuntos, $S_1$ e $S_2$:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "133784" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def fit(B,S=S):\n", + " c1 = (B*S).sum()\n", + " c2 = ((1-B)*S).sum()\n", + " return abs(c1-c2)\n", + "\n", + "fit(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora precisamos definir funções para mutação e cruzamento: \n", + "* Para a mutação, iremos selecionar um bit aleatório e negá-lo (alterar de 0 para 1 e vice-versa). \n", + "* Para o cruzamento, iremos pegar alguns bits de um vetor e outros bits de outro vetor. Utilizaremos a mesma função `generate` para selecionar aleatoriamente quais bits serão retirados de cada uma das máscaras de entrada. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "def mutate(b):\n", + " x = b.copy()\n", + " i = random.randint(0,len(b)-1)\n", + " x[i] = 1-x[i]\n", + " return x\n", + "\n", + "def xover(b1,b2):\n", + " x = generate(b1)\n", + " return b1*x+b2*(1-x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos criar a população inicial das soluções $P$ com o tamanho `pop_size`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "pop_size = 30\n", + "P = [generate(S) for _ in range(pop_size)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora, a função principal para realizar a evolução. `n` é o número de etapas de evolução a serem realizadas. Em cada etapa: \n", + "* Com uma probabilidade de 30%, realizamos uma mutação e substituímos o elemento com a pior função `fit` pelo elemento mutado \n", + "* Com uma probabilidade de 70%, realizamos o cruzamento \n", + "\n", + "A função devolve a melhor solução (gene correspondente à melhor solução) e o histórico da função `fit` mínima na população em cada iteração. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0 0 0 1 1 0 0 0 0 1 1 1 0 1 0 0 0 1 0 1 0 1 0 1 0 1 1 0 0 0 0 0 1 0 1 1 0\n", + " 0 0 0 1 1 0 0 1 0 0 0 0 0 1 0 0 1 1 1 1 1 1 1 0 1 1 0 1 1 1 1 1 0 1 0 0 0\n", + " 0 1 1 1 0 1 0 1 1 1 1 1 0 0 0 1 1 0 1 0 0 1 0 0 1 1 1 1 1 1 1 1 0 1 0 1 1\n", + " 0 1 1 0 0 0 0 1 1 1 1 0 1 0 0 1 0 1 1 1 0 1 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0\n", + " 1 0 1 1 1 1 1 0 1 0 1 0 1 1 1 0 0 0 1 1 0 0 0 0 0 0 1 1 1 0 1 0 0 0 1 0 1\n", + " 0 1 0 1 0 0 1 1 1 0 1 1 0 0 1] 4\n" + ] + } + ], + "source": [ + "def evolve(P,S=S,n=2000):\n", + " res = []\n", + " for _ in range(n):\n", + " f = min([fit(b) for b in P])\n", + " res.append(f)\n", + " if f==0:\n", + " break\n", + " if random.randint(1,10)<3:\n", + " i = random.randint(0,len(P)-1)\n", + " b = mutate(P[i])\n", + " i = np.argmax([fit(z) for z in P])\n", + " P[i] = b\n", + " else:\n", + " i = random.randint(0,len(P)-1)\n", + " j = random.randint(0,len(P)-1)\n", + " b = xover(P[i],P[j])\n", + " if fit(b)" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(hist)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problema 2: Problema das N Rainhas\n", + "\n", + "**Tarefa**: \n", + "É necessário colocar $N$ rainhas num tabuleiro de xadrez de tamanho $N\\times N$ de forma que elas não se ataquem umas às outras.\n", + "\n", + "Antes de mais nada, vamos resolver o problema sem recorrer a algoritmos genéticos, utilizando uma busca exaustiva. Podemos representar o estado do tabuleiro através da lista $L$, onde o número na posição $i$ da lista representa a posição horizontal da rainha na linha $i$. É bastante óbvio que cada solução terá apenas uma rainha por linha, e cada linha terá uma rainha.\n", + "\n", + "O nosso objetivo será encontrar a primeira solução para o problema, após o que interromperemos a busca. Pode-se facilmente estender esta função para gerar todas as posições possíveis para as rainhas.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 5, 8, 6, 3, 7, 2, 4]\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "N = 8\n", + "\n", + "def checkbeats(i_new,j_new,l):\n", + " for i,j in enumerate(l,start=1):\n", + " if j==j_new:\n", + " return False\n", + " else:\n", + " if abs(j-j_new) == i_new-i:\n", + " return False\n", + " return True\n", + "\n", + "def nqueens(l,N=8,disp=True):\n", + " if len(l)==N:\n", + " if disp: print(l)\n", + " return True\n", + " else:\n", + " for j in range(1,N+1):\n", + " if checkbeats(len(l)+1,j,l):\n", + " l.append(j)\n", + " if nqueens(l,N,disp): return True\n", + " else: l.pop()\n", + " return False\n", + " \n", + "nqueens([],8)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora vamos medir quanto tempo demora para obter uma solução para o problema das 20 rainhas:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10.6 s ± 2.17 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit nqueens([],20,False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora vamos resolver o mesmo problema utilizando um algoritmo genético. Esta solução é inspirada por [este artigo de blog](https://kushalvyas.github.io/gen_8Q.html).\n", + "\n", + "Iremos representar cada solução pela mesma lista de comprimento $N$, e como função `fit` iremos considerar o número de rainhas que se atacam mutuamente:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "def fit(L):\n", + " x=0\n", + " for i1,j1 in enumerate(L,1):\n", + " for i2,j2 in enumerate(L,1):\n", + " if i2>i1:\n", + " if j2==j1 or (abs(j2-j1)==i2-i1): x+=1\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Como calcular a função de aptidão consome tempo, vamos armazenar cada solução na população juntamente com o valor da função de aptidão. Vamos gerar a população inicial:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[(array([2, 3, 8, 7, 5, 4, 1, 6]), 4),\n", + " (array([3, 4, 5, 1, 2, 8, 6, 7]), 8),\n", + " (array([1, 3, 7, 4, 5, 8, 6, 2]), 6),\n", + " (array([1, 5, 4, 6, 8, 3, 7, 2]), 4),\n", + " (array([3, 5, 7, 1, 8, 6, 4, 2]), 3)]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def generate_one(N):\n", + " x = np.arange(1,N+1)\n", + " np.random.shuffle(x)\n", + " return (x,fit(x))\n", + "\n", + "def generate(N,NP):\n", + " return [generate_one(N) for _ in range(NP)]\n", + "\n", + "generate(8,5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora precisamos definir as funções de mutação e cruzamento. O cruzamento combinaria dois genes ao dividi-los em um ponto aleatório e concatenar duas partes de genes diferentes juntos.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 7, 8])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def mutate(G):\n", + " x=random.randint(0,len(G)-1)\n", + " G[x]=random.randint(1,len(G))\n", + " return G\n", + " \n", + "def xover(G1,G2):\n", + " x=random.randint(0,len(G1))\n", + " return np.concatenate((G1[:x],G2[x:]))\n", + "\n", + "xover([1,2,3,4],[5,6,7,8])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "trusted": true + }, + "outputs": [], + "source": [ + "def choose_rand(P):\n", + " N=len(P[0][0])\n", + " mf = N*(N-1)//2 # max fitness fn\n", + " z = [mf-x[1] for x in P]\n", + " tf = sum(z) # total fitness\n", + " w = [x/tf for x in z]\n", + " p = np.random.choice(len(P),2,False,p=w)\n", + " return p[0],p[1]\n", + "\n", + "def choose(P):\n", + " def ch(w):\n", + " p=[]\n", + " while p==[]:\n", + " r = random.random()\n", + " p = [i for i,x in enumerate(P) if x[1]>=r]\n", + " return random.choice(p)\n", + " N=len(P[0][0])\n", + " mf = N*(N-1)//2 # max fitness fn\n", + " z = [mf-x[1] for x in P]\n", + " tf = sum(z) # total fitness\n", + " w = [x/tf for x in z]\n", + " p1=p2=0\n", + " while p1==p2:\n", + " p1 = ch(w)\n", + " p2 = ch(w)\n", + " return p1,p2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Agora vamos definir o principal ciclo evolutivo. Vamos tornar a lógica ligeiramente diferente do exemplo anterior, para mostrar que é possível ser criativo. Iremos repetir o ciclo até obtermos a solução perfeita (função de aptidão = 0), e em cada etapa iremos pegar a geração atual e produzir uma nova geração do mesmo tamanho. Isto é feito utilizando a função `nxgeneration`, seguindo os passos abaixo:\n", + "\n", + "1. Descartar as soluções menos aptas - existe a função `discard_unfit` que realiza esta tarefa\n", + "1. Adicionar algumas soluções aleatórias à geração\n", + "1. Criar uma nova geração de tamanho `gen_size` utilizando os seguintes passos para cada novo gene:\n", + " - selecionar dois genes aleatórios, com probabilidade proporcional à função de aptidão\n", + " - calcular um cruzamento\n", + " - aplicar uma mutação com a probabilidade `mutation_prob`\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([4, 7, 5, 3, 1, 6, 8, 2]), 0)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mutation_prob = 0.1\n", + "\n", + "def discard_unfit(P):\n", + " P.sort(key=lambda x:x[1])\n", + " return P[:len(P)//3]\n", + "\n", + "def nxgeneration(P):\n", + " gen_size=len(P)\n", + " P = discard_unfit(P)\n", + " P.extend(generate(len(P[0][0]),3))\n", + " new_gen = []\n", + " for _ in range(gen_size):\n", + " p1,p2 = choose_rand(P)\n", + " n = xover(P[p1][0],P[p2][0])\n", + " if random.random()0:\n", + " #print(\"Generation {0}, fit={1}\".format(n,mf))\n", + " n+=1\n", + " mf = min([x[1] for x in P])\n", + " P = nxgeneration(P)\n", + " mi = np.argmin([x[1] for x in P])\n", + " return P[mi]\n", + "\n", + "genetic(8)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The slowest run took 18.71 times longer than the fastest. This could mean that an intermediate result is being cached.\n", + "26.4 s ± 28.7 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit genetic(10)" + ] + }, + { + "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": "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.9.5" + }, + "coopTranslator": { + "original_hash": "1012c117808753433f6e2696e775ff4b", + "translation_date": "2025-08-31T11:34:40+00:00", + "source_file": "lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb", + "language_code": "pt" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/translations/pt/lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb b/translations/pt/lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb new file mode 100644 index 00000000..3bbf8b13 --- /dev/null +++ b/translations/pt/lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb @@ -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 +} \ No newline at end of file diff --git a/translations/pt/lessons/6-Other/22-DeepRL/CartPole-RL-TF.ipynb b/translations/pt/lessons/6-Other/22-DeepRL/CartPole-RL-TF.ipynb new file mode 100644 index 00000000..365bd96a --- /dev/null +++ b/translations/pt/lessons/6-Other/22-DeepRL/CartPole-RL-TF.ipynb @@ -0,0 +1,683 @@ +{ + "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 por [este artigo](https://medium.com/swlh/policy-gradient-reinforcement-learning-with-keras-57ca6ed32555), pela [documentação oficial do TensorFlow](https://www.tensorflow.org/tutorials/reinforcement_learning/actor_critic) e por [este exemplo de Keras RL](https://keras.io/examples/rl/actor_critic_cartpole/).\n", + "\n", + "Neste exemplo, usaremos RL para treinar um modelo a equilibrar uma barra em um carrinho que pode mover-se para a esquerda e para a direita numa escala horizontal. Utilizaremos o ambiente [OpenAI Gym](https://www.gymlibrary.ml/) para simular a barra.\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, poderá 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": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Requirement already satisfied: gym in /home/leo/.local/lib/python3.10/site-packages (0.25.0)\n", + "Requirement already satisfied: pygame in /home/leo/.local/lib/python3.10/site-packages (2.1.2)\n", + "Requirement already satisfied: gym-notices>=0.0.4 in /home/leo/.local/lib/python3.10/site-packages (from gym) (0.0.7)\n", + "Requirement already satisfied: cloudpickle>=1.2.0 in /home/leo/.local/lib/python3.10/site-packages (from gym) (2.1.0)\n", + "Requirement already satisfied: numpy>=1.18.0 in /usr/lib/python3/dist-packages (from gym) (1.21.5)\n" + ] + } + ], + "source": [ + "import sys\n", + "!{sys.executable} -m pip install gym pygame" + ] + }, + { + "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": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Action space: Discrete(2)\n", + "Observation space: Box([-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], [4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38], (4,), float32)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/leo/.local/lib/python3.10/site-packages/gym/core.py:329: DeprecationWarning: \u001b[33mWARN: Initializing wrapper in old step API which returns one bool instead of two. It is recommended to set `new_step_api=True` to use new step API. This will be the default behaviour in future.\u001b[0m\n", + " deprecation(\n", + "/home/leo/.local/lib/python3.10/site-packages/gym/wrappers/step_api_compatibility.py:39: DeprecationWarning: \u001b[33mWARN: Initializing environment in old step API which returns one bool instead of two. It is recommended to set `new_step_api=True` to use new step API. This will be the default behaviour in future.\u001b[0m\n", + " deprecation(\n" + ] + } + ], + "source": [ + "import gym\n", + "import pygame\n", + "import tqdm\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 ciclo seguinte 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 ele se comporta.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/leo/.local/lib/python3.10/site-packages/gym/core.py:57: DeprecationWarning: \u001b[33mWARN: You are calling render method, but you didn't specified the argument render_mode at environment initialization. To maintain backward compatibility, the environment will render in human mode.\n", + "If you want to render in human mode, initialize the environment in this way: gym.make('EnvName', render_mode='human') and don't call the render method.\n", + "See here for more information: https://www.gymlibrary.ml/content/api/\u001b[0m\n", + " deprecation(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0.00425272 -0.19994313 0.00917169 0.34113726] -> 1.0\n", + "[ 0.00025386 -0.00495286 0.01599443 0.05136059] -> 1.0\n", + "[ 1.5480528e-04 1.8993615e-01 1.7021643e-02 -2.3623335e-01] -> 1.0\n", + "[ 0.00395353 0.38481084 0.01229698 -0.5234989 ] -> 1.0\n", + "[ 0.01164974 0.18951797 0.001827 -0.22696657] -> 1.0\n", + "[ 0.0154401 0.38461378 -0.00271233 -0.51907265] -> 1.0\n", + "[ 0.02313238 0.5797738 -0.01309379 -0.812609 ] -> 1.0\n", + "[ 0.03472786 0.38483363 -0.02934597 -0.5240733 ] -> 1.0\n", + "[ 0.04242453 0.580356 -0.03982743 -0.8258571 ] -> 1.0\n", + "[ 0.05403165 0.38580072 -0.05634458 -0.54596174] -> 1.0\n", + "[ 0.06174766 0.19151384 -0.06726381 -0.27155042] -> 1.0\n", + "[ 0.06557794 -0.00258703 -0.07269482 -0.00081817] -> 1.0\n", + "[ 0.0655262 -0.19659522 -0.07271118 0.26807207] -> 1.0\n", + "[ 0.0615943 -0.00051497 -0.06734974 -0.04662942] -> 1.0\n", + "[ 0.061584 0.19550486 -0.06828233 -0.3597784 ] -> 1.0\n", + "[ 0.06549409 0.00141663 -0.0754779 -0.08938391] -> 1.0\n", + "[ 0.06552242 -0.19254686 -0.07726558 0.17856352] -> 1.0\n", + "[ 0.06167149 0.00359088 -0.0736943 -0.1374588 ] -> 1.0\n", + "[ 0.0617433 0.19968675 -0.07644348 -0.45245075] -> 1.0\n", + "[ 0.06573704 0.3958018 -0.0854925 -0.7682167 ] -> 1.0\n", + "[ 0.07365308 0.20195423 -0.10085683 -0.50361156] -> 1.0\n", + "[ 0.07769216 0.0083876 -0.11092906 -0.24433874] -> 1.0\n", + "[ 0.07785992 -0.18498953 -0.11581583 0.01139782] -> 1.0\n", + "[ 0.07416012 0.01158649 -0.11558788 -0.31546465] -> 1.0\n", + "[ 0.07439185 0.20814891 -0.12189718 -0.64224803] -> 1.0\n", + "[ 0.07855483 0.01491799 -0.13474214 -0.3903015 ] -> 1.0\n", + "[ 0.07885319 -0.17806001 -0.14254816 -0.14295265] -> 1.0\n", + "[ 0.07529199 0.01878517 -0.14540721 -0.47699296] -> 1.0\n", + "[ 0.07566769 -0.17401667 -0.15494707 -0.23344138] -> 1.0\n", + "[ 0.07218736 0.0229406 -0.1596159 -0.57071024] -> 1.0\n", + "[ 0.07264617 0.21989843 -0.1710301 -0.9091196 ] -> 1.0\n", + "[ 0.07704414 0.02745241 -0.1892125 -0.6747003 ] -> 1.0\n", + "[ 0.07759319 -0.16460665 -0.20270652 -0.4470505 ] -> 1.0\n", + "[ 0.07430106 -0.35637102 -0.21164753 -0.22448184] -> 1.0\n", + "Total reward: 34.0\n" + ] + } + ], + "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}\")\n", + "\n", + "env.close()" + ] + }, + { + "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 a cada passo. Pode ver que, no ambiente CartPole, é atribuído 1 ponto de recompensa por cada passo de simulação, e o objetivo é maximizar a recompensa total, ou seja, o tempo que o CartPole consegue equilibrar-se 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 tomarmos 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**, treinaremos uma rede neural para prever a próxima ação.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.10/dist-packages/tensorflow/__init__.py:29: DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives\n", + " import distutils as _distutils\n", + "2022-07-24 16:50:47.597258: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:47.597280: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.\n", + "/usr/local/lib/python3.10/dist-packages/flatbuffers/compat.py:19: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses\n", + " import imp\n", + "2022-07-24 16:50:49.838826: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:975] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero\n", + "2022-07-24 16:50:49.839078: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839143: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcublas.so.11'; dlerror: libcublas.so.11: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839194: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcublasLt.so.11'; dlerror: libcublasLt.so.11: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839245: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcufft.so.10'; dlerror: libcufft.so.10: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839295: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcurand.so.10'; dlerror: libcurand.so.10: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839345: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcusolver.so.11'; dlerror: libcusolver.so.11: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839392: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcusparse.so.11'; dlerror: libcusparse.so.11: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839441: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudnn.so.8'; dlerror: libcudnn.so.8: cannot open shared object file: No such file or directory\n", + "2022-07-24 16:50:49.839449: W tensorflow/core/common_runtime/gpu/gpu_device.cc:1850] Cannot dlopen some GPU libraries. Please make sure the missing libraries mentioned above are installed properly if you would like to use GPU. Follow the guide at https://www.tensorflow.org/install/gpu for how to download and setup the required libraries for your platform.\n", + "Skipping registering GPU devices...\n", + "2022-07-24 16:50:49.839649: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA\n", + "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import tensorflow as tf\n", + "from tensorflow import keras\n", + "import matplotlib.pyplot as plt\n", + "\n", + "num_inputs = 4\n", + "num_actions = 2\n", + "\n", + "model = keras.Sequential([\n", + " keras.layers.Dense(128, activation=\"relu\",input_shape=(num_inputs,)),\n", + " keras.layers.Dense(num_actions, activation=\"softmax\")\n", + "])\n", + "\n", + "model.compile(loss='categorical_crossentropy', optimizer=keras.optimizers.Adam(learning_rate=0.01))" + ] + }, + { + "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": 5, + "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(np.expand_dims(state,0))[0]\n", + " action = np.random.choice(num_actions, p=np.squeeze(action_probs))\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)\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": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total reward: 27.0\n" + ] + } + ], + "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 passo do jogo e, durante este processo, descontamos as recompensas iniciais usando um coeficiente $gamma$. Também normalizamos o vetor resultante, porque o usaremos como peso para afetar o nosso treino:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "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 temos 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 até que ponto 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": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 -> 29.0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-07-24 16:50:51.475024: W tensorflow/core/data/root_dataset.cc:247] Optimization loop failed: CANCELLED: Operation was cancelled\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100 -> 135.0\n", + "200 -> 484.0\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-07-24 16:51:35.910774: W tensorflow/core/data/root_dataset.cc:247] Optimization loop failed: CANCELLED: Operation was cancelled\n", + "2022-07-24 16:51:37.151017: W tensorflow/core/data/root_dataset.cc:247] Optimization loop failed: CANCELLED: Operation was cancelled\n", + "2022-07-24 16:51:39.284311: W tensorflow/core/data/root_dataset.cc:247] Optimization loop failed: CANCELLED: Operation was cancelled\n", + "2022-07-24 16:51:42.235074: W tensorflow/core/data/root_dataset.cc:247] Optimization loop failed: CANCELLED: Operation was cancelled\n", + "2022-07-24 16:51:44.691458: W tensorflow/core/data/root_dataset.cc:247] Optimization loop failed: CANCELLED: Operation was cancelled\n", + "2022-07-24 16:51:48.381946: W tensorflow/core/data/root_dataset.cc:247] Optimization loop failed: CANCELLED: Operation was cancelled\n" + ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "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", + " model.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": 10, + "metadata": {}, + "outputs": [ + { + "ename": "error", + "evalue": "display Surface quit", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31merror\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_44248/1459719159.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0m_\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrun_episode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\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/tmp/ipykernel_44248/3855001447.py\u001b[0m in \u001b[0;36mrun_episode\u001b[0;34m(max_steps_per_episode, render)\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0m_\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmax_steps_per_episode\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[1;32m 5\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\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[1;32m 7\u001b[0m \u001b[0maction_probs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexpand_dims\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0maction\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mchoice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnum_actions\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msqueeze\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maction_probs\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~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 429\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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[1;32m 430\u001b[0m \u001b[0;34m\"\"\"Renders the environment.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 431\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 432\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 433\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\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~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/wrappers/order_enforcing.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;34m\"set `disable_render_order_enforcing=True` on the OrderEnforcer wrapper.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 50\u001b[0m )\n\u001b[0;32m---> 51\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 52\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 429\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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[1;32m 430\u001b[0m \u001b[0;34m\"\"\"Renders the environment.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 431\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 432\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 433\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\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~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/wrappers/env_checker.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0menv_render_passive_checker\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 54\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 55\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/envs/classic_control/cartpole.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, mode)\u001b[0m\n\u001b[1;32m 215\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrenderer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_renders\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[1;32m 216\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 217\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_render\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmode\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 218\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 219\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_render\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmode\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"human\"\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~/.local/lib/python3.10/site-packages/gym/envs/classic_control/cartpole.py\u001b[0m in \u001b[0;36m_render\u001b[0;34m(self, mode)\u001b[0m\n\u001b[1;32m 296\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 297\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msurf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpygame\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransform\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mflip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msurf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 298\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscreen\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mblit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msurf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\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[1;32m 299\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmode\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"human\"\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 300\u001b[0m \u001b[0mpygame\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mevent\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpump\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;31merror\u001b[0m: display Surface quit" + ] + } + ], + "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 assim:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "num_inputs = 4\n", + "num_actions = 2\n", + "num_hidden = 128\n", + "\n", + "inputs = keras.layers.Input(shape=(num_inputs,))\n", + "common = keras.layers.Dense(num_hidden, activation=\"relu\")(inputs)\n", + "action = keras.layers.Dense(num_actions, activation=\"softmax\")(common)\n", + "critic = keras.layers.Dense(1)(common)\n", + "\n", + "model = keras.Model(inputs=inputs, outputs=[action, critic])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Precisaríamos modificar ligeiramente a nossa função `run_episode` para também retornar os resultados do crítico:\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, critic = [],[],[],[],[]\n", + " state = env.reset()\n", + " for _ in range(max_steps_per_episode):\n", + " if render:\n", + " env.render()\n", + " action_probs, est_rew = model(np.expand_dims(state,0))\n", + " action = np.random.choice(num_actions, p=np.squeeze(action_probs[0]))\n", + " nstate, reward, done, info = env.step(action)\n", + " if done:\n", + " break\n", + " states.append(state)\n", + " actions.append(action)\n", + " probs.append(tf.math.log(action_probs[0,action]))\n", + " rewards.append(reward)\n", + " critic.append(est_rew[0,0])\n", + " state = nstate\n", + " return states, actions, probs, rewards, critic" + ] + }, + { + "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": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "running reward: 5.82 at episode 10\n", + "running reward: 9.43 at episode 20\n", + "running reward: 10.30 at episode 30\n", + "running reward: 10.28 at episode 40\n", + "running reward: 11.00 at episode 50\n", + "running reward: 13.01 at episode 60\n", + "running reward: 21.78 at episode 70\n", + "running reward: 40.54 at episode 80\n", + "running reward: 73.70 at episode 90\n", + "running reward: 100.19 at episode 100\n", + "running reward: 159.20 at episode 110\n", + "Solved at episode 114!\n" + ] + } + ], + "source": [ + "optimizer = keras.optimizers.Adam(learning_rate=0.01)\n", + "huber_loss = keras.losses.Huber()\n", + "episode_count = 0\n", + "running_reward = 0\n", + "\n", + "while True: # Run until solved\n", + " state = env.reset()\n", + " episode_reward = 0\n", + " with tf.GradientTape() as tape:\n", + " _,_,action_probs, rewards, critic_values = run_episode()\n", + " episode_reward = np.sum(rewards)\n", + " \n", + " # Update running reward to check condition for solving\n", + " running_reward = 0.05 * episode_reward + (1 - 0.05) * running_reward\n", + "\n", + " # Calculate discounted rewards that will be labels for our critic\n", + " dr = discounted_rewards(rewards)\n", + "\n", + " # Calculating loss values to update our network\n", + " actor_losses = []\n", + " critic_losses = []\n", + " for log_prob, value, rew in zip(action_probs, critic_values, dr):\n", + " # When we took the action with probability `log_prob`, we received discounted reward of `rew`,\n", + " # while critic predicted it to be `value` \n", + " # First we calculate actor loss, to make actor predict actions that lead to higher rewards\n", + " diff = rew - value\n", + " actor_losses.append(-log_prob * diff)\n", + "\n", + " # The critic loss is to minimize the difference between predicted reward `value` and actual\n", + " # discounted reward `rew`\n", + " critic_losses.append(\n", + " huber_loss(tf.expand_dims(value, 0), tf.expand_dims(rew, 0))\n", + " )\n", + "\n", + " # Backpropagation\n", + " loss_value = sum(actor_losses) + sum(critic_losses)\n", + " grads = tape.gradient(loss_value, model.trainable_variables)\n", + " optimizer.apply_gradients(zip(grads, model.trainable_variables))\n", + "\n", + " # Log details\n", + " episode_count += 1\n", + " if episode_count % 10 == 0:\n", + " template = \"running reward: {:.2f} at episode {}\"\n", + " print(template.format(running_reward, episode_count))\n", + "\n", + " if running_reward > 195: # Condition to consider the task solved\n", + " print(\"Solved at episode {}!\".format(episode_count))\n", + " break\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos executar o episódio e ver quão bom é o nosso modelo:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_ = run_episode(render=True)" + ] + }, + { + "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": "4acab0ddd5fd306aaecd56a7b1e0d069", + "translation_date": "2025-08-31T11:36:13+00:00", + "source_file": "lessons/6-Other/22-DeepRL/CartPole-RL-TF.ipynb", + "language_code": "pt" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/translations/pt/lessons/6-Other/22-DeepRL/lab/MountainCar.ipynb b/translations/pt/lessons/6-Other/22-DeepRL/lab/MountainCar.ipynb new file mode 100644 index 00000000..fbf61d00 --- /dev/null +++ b/translations/pt/lessons/6-Other/22-DeepRL/lab/MountainCar.ipynb @@ -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 +} \ No newline at end of file diff --git a/translations/pt/lessons/6-Other/22-DeepRL/notebook.ipynb b/translations/pt/lessons/6-Other/22-DeepRL/notebook.ipynb new file mode 100644 index 00000000..d940241d --- /dev/null +++ b/translations/pt/lessons/6-Other/22-DeepRL/notebook.ipynb @@ -0,0 +1,597 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Patinagem no CartPole\n", + "\n", + "> **Problema**: Se o Pedro quiser escapar do lobo, ele precisa ser mais rápido do que ele. Vamos ver como o Pedro pode aprender a patinar, em particular, a manter o equilíbrio, utilizando Q-Learning.\n", + "\n", + "Primeiro, vamos instalar o gym e importar as bibliotecas necessárias:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Collecting gym\n", + " Downloading gym-0.25.0.tar.gz (720 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m720.4/720.4 KB\u001b[0m \u001b[31m3.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", + "\u001b[?25h Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", + "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: numpy>=1.18.0 in /usr/lib/python3/dist-packages (from gym) (1.21.5)\n", + "Collecting gym-notices>=0.0.4\n", + " Downloading gym_notices-0.0.7-py3-none-any.whl (2.7 kB)\n", + "Collecting cloudpickle>=1.2.0\n", + " Downloading cloudpickle-2.1.0-py3-none-any.whl (25 kB)\n", + "Building wheels for collected packages: gym\n", + " Building wheel for gym (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for gym: filename=gym-0.25.0-py3-none-any.whl size=824430 sha256=3f4ed647f1d12814bb457f7d83a7ccd0f682d12a0259ca07b7fab0db5100fc6e\n", + " Stored in directory: /home/leo/.cache/pip/wheels/c0/3c/33/32d86254a5bd554f5f07759ae1794646e490dd5fa81ebdcda3\n", + "Successfully built gym\n", + "Installing collected packages: gym-notices, cloudpickle, gym\n", + "Successfully installed cloudpickle-2.1.0 gym-0.25.0 gym-notices-0.0.7\n" + ] + } + ], + "source": [ + "import sys\n", + "!pip install gym pygame\n", + "\n", + "import gym\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import random" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Criar um ambiente de cartpole\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Discrete(2)\n", + "Box([-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], [4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38], (4,), float32)\n", + "1\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/leo/.local/lib/python3.10/site-packages/gym/core.py:329: DeprecationWarning: \u001b[33mWARN: Initializing wrapper in old step API which returns one bool instead of two. It is recommended to set `new_step_api=True` to use new step API. This will be the default behaviour in future.\u001b[0m\n", + " deprecation(\n", + "/home/leo/.local/lib/python3.10/site-packages/gym/wrappers/step_api_compatibility.py:39: DeprecationWarning: \u001b[33mWARN: Initializing environment in old step API which returns one bool instead of two. It is recommended to set `new_step_api=True` to use new step API. This will be the default behaviour in future.\u001b[0m\n", + " deprecation(\n" + ] + } + ], + "source": [ + "env = gym.make(\"CartPole-v1\")\n", + "print(env.action_space)\n", + "print(env.observation_space)\n", + "print(env.action_space.sample())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Para ver como o ambiente funciona, vamos executar uma simulação curta de 100 passos.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/leo/.local/lib/python3.10/site-packages/gym/core.py:57: DeprecationWarning: \u001b[33mWARN: You are calling render method, but you didn't specified the argument render_mode at environment initialization. To maintain backward compatibility, the environment will render in human mode.\n", + "If you want to render in human mode, initialize the environment in this way: gym.make('EnvName', render_mode='human') and don't call the render method.\n", + "See here for more information: https://www.gymlibrary.ml/content/api/\u001b[0m\n", + " deprecation(\n" + ] + }, + { + "ename": "DependencyNotInstalled", + "evalue": "pygame is not installed, run `pip install gym[classic_control]`", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/envs/classic_control/cartpole.py\u001b[0m in \u001b[0;36m_render\u001b[0;34m(self, mode)\u001b[0m\n\u001b[1;32m 221\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 222\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mpygame\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 223\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mpygame\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mgfxdraw\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'pygame'", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mDependencyNotInstalled\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_32716/4123126963.py\u001b[0m in \u001b[0;36m\u001b[0;34m\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;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m100\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[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\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[1;32m 5\u001b[0m \u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maction_space\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msample\u001b[0m\u001b[0;34m(\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[1;32m 6\u001b[0m \u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\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~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 429\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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[1;32m 430\u001b[0m \u001b[0;34m\"\"\"Renders the environment.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 431\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 432\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 433\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\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~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/wrappers/order_enforcing.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;34m\"set `disable_render_order_enforcing=True` on the OrderEnforcer wrapper.\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 50\u001b[0m )\n\u001b[0;32m---> 51\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 52\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 53\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 429\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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[1;32m 430\u001b[0m \u001b[0;34m\"\"\"Renders the environment.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 431\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 432\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 433\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\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~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/wrappers/env_checker.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 51\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mchecked_render\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 52\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mchecked_render\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 53\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0menv_render_passive_checker\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 54\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/utils/passive_env_checker.py\u001b[0m in \u001b[0;36menv_render_passive_checker\u001b[0;34m(env, *args, **kwargs)\u001b[0m\n\u001b[1;32m 322\u001b[0m )\n\u001b[1;32m 323\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 324\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrender\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 325\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 326\u001b[0m \u001b[0;31m# TODO: Check that the result is correct\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/core.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 64\u001b[0m )\n\u001b[1;32m 65\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrender_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\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 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mrender\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.10/site-packages/gym/envs/classic_control/cartpole.py\u001b[0m in \u001b[0;36mrender\u001b[0;34m(self, mode)\u001b[0m\n\u001b[1;32m 215\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrenderer\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_renders\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[1;32m 216\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 217\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_render\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmode\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 218\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 219\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_render\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmode\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"human\"\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~/.local/lib/python3.10/site-packages/gym/envs/classic_control/cartpole.py\u001b[0m in \u001b[0;36m_render\u001b[0;34m(self, mode)\u001b[0m\n\u001b[1;32m 223\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mpygame\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mgfxdraw\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 224\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mImportError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 225\u001b[0;31m raise DependencyNotInstalled(\n\u001b[0m\u001b[1;32m 226\u001b[0m \u001b[0;34m\"pygame is not installed, run `pip install gym[classic_control]`\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 227\u001b[0m )\n", + "\u001b[0;31mDependencyNotInstalled\u001b[0m: pygame is not installed, run `pip install gym[classic_control]`" + ] + } + ], + "source": [ + "env.reset()\n", + "\n", + "for i in range(100):\n", + " env.render()\n", + " env.step(env.action_space.sample())\n", + "env.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Durante a simulação, precisamos obter observações para decidir como agir. Na verdade, a função `step` devolve-nos as observações atuais, a função de recompensa e o indicador `done` que indica se faz sentido continuar a simulação ou não:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0.03044442 -0.19543914 -0.04496216 0.28125618] -> 1.0\n", + "[ 0.02653564 -0.38989186 -0.03933704 0.55942606] -> 1.0\n", + "[ 0.0187378 -0.19424049 -0.02814852 0.25461393] -> 1.0\n", + "[ 0.01485299 -0.38894946 -0.02305624 0.53828712] -> 1.0\n", + "[ 0.007074 -0.19351108 -0.0122905 0.23842953] -> 1.0\n", + "[ 0.00320378 0.00178427 -0.00752191 -0.05810469] -> 1.0\n", + "[ 0.00323946 0.19701326 -0.008684 -0.35315131] -> 1.0\n", + "[ 0.00717973 0.00201587 -0.01574703 -0.06321931] -> 1.0\n", + "[ 0.00722005 0.19736001 -0.01701141 -0.36082863] -> 1.0\n", + "[ 0.01116725 0.39271958 -0.02422798 -0.65882671] -> 1.0\n", + "[ 0.01902164 0.19794307 -0.03740452 -0.37387001] -> 1.0\n", + "[ 0.0229805 0.39357584 -0.04488192 -0.67810827] -> 1.0\n", + "[ 0.03085202 0.58929164 -0.05844408 -0.98457719] -> 1.0\n", + "[ 0.04263785 0.78514572 -0.07813563 -1.2950295 ] -> 1.0\n", + "[ 0.05834076 0.98116859 -0.10403622 -1.61111521] -> 1.0\n", + "[ 0.07796413 0.78741784 -0.13625852 -1.35259196] -> 1.0\n", + "[ 0.09371249 0.98396202 -0.16331036 -1.68461179] -> 1.0\n", + "[ 0.11339173 0.79106371 -0.1970026 -1.44691436] -> 1.0\n", + "[ 0.12921301 0.59883361 -0.22594088 -1.22169133] -> 1.0\n" + ] + } + ], + "source": [ + "env.reset()\n", + "\n", + "done = False\n", + "while not done:\n", + " env.render()\n", + " obs, rew, done, info = env.step(env.action_space.sample())\n", + " print(f\"{obs} -> {rew}\")\n", + "env.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Podemos obter o valor mínimo e máximo desses números:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38]\n", + "[4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38]\n" + ] + } + ], + "source": [ + "print(env.observation_space.low)\n", + "print(env.observation_space.high)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def discretize(x):\n", + " return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos também explorar outro método de discretização usando intervalos:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sample bins for interval (-5,5) with 10 bins\n", + " [-5. -4. -3. -2. -1. 0. 1. 2. 3. 4. 5.]\n" + ] + } + ], + "source": [ + "def create_bins(i,num):\n", + " return np.arange(num+1)*(i[1]-i[0])/num+i[0]\n", + "\n", + "print(\"Sample bins for interval (-5,5) with 10 bins\\n\",create_bins((-5,5),10))\n", + "\n", + "ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter\n", + "nbins = [20,20,10,10] # number of bins for each parameter\n", + "bins = [create_bins(ints[i],nbins[i]) for i in range(4)]\n", + "\n", + "def discretize_bins(x):\n", + " return tuple(np.digitize(x[i],bins[i]) for i in range(4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Vamos agora executar uma breve simulação e observar esses valores discretos do ambiente.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(0, 0, -1, -3)\n", + "(0, 0, -2, 0)\n", + "(0, 0, -2, -3)\n", + "(0, 1, -3, -6)\n", + "(0, 2, -4, -9)\n", + "(0, 3, -6, -12)\n", + "(0, 2, -8, -9)\n", + "(0, 3, -10, -13)\n", + "(0, 4, -13, -16)\n", + "(0, 4, -16, -19)\n", + "(0, 4, -20, -17)\n", + "(0, 4, -24, -20)\n" + ] + } + ], + "source": [ + "env.reset()\n", + "\n", + "done = False\n", + "while not done:\n", + " #env.render()\n", + " obs, rew, done, info = env.step(env.action_space.sample())\n", + " #print(discretize_bins(obs))\n", + " print(discretize(obs))\n", + "env.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "Q = {}\n", + "actions = (0,1)\n", + "\n", + "def qvalues(state):\n", + " return [Q.get((state,a),0) for a in actions]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# hyperparameters\n", + "alpha = 0.3\n", + "gamma = 0.9\n", + "epsilon = 0.90" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: 108.0, alpha=0.3, epsilon=0.9\n" + ] + } + ], + "source": [ + "def probs(v,eps=1e-4):\n", + " v = v-v.min()+eps\n", + " v = v/v.sum()\n", + " return v\n", + "\n", + "Qmax = 0\n", + "cum_rewards = []\n", + "rewards = []\n", + "for epoch in range(100000):\n", + " obs = env.reset()\n", + " done = False\n", + " cum_reward=0\n", + " # == do the simulation ==\n", + " while not done:\n", + " s = discretize(obs)\n", + " if random.random() Qmax:\n", + " Qmax = np.average(cum_rewards)\n", + " Qbest = Q\n", + " cum_rewards=[]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "image/svg+xml": "\r\n\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(rewards)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A partir deste gráfico, não é possível determinar nada, porque devido à natureza do processo de treino estocástico, a duração das sessões de treino varia bastante. Para dar mais sentido a este gráfico, podemos calcular a **média móvel** ao longo de uma série de experiências, digamos 100. Isto pode ser feito convenientemente usando `np.convolve`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "image/svg+xml": "\r\n\r\n\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "def running_average(x,window):\n", + " return np.convolve(x,np.ones(window)/window,mode='valid')\n", + "\n", + "plt.plot(running_average(rewards,100))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Variar Hiperparâmetros e Ver os Resultados em Ação\n", + "\n", + "Agora seria interessante ver como o modelo treinado se comporta na prática. Vamos executar a simulação, seguindo a mesma estratégia de seleção de ações utilizada durante o treino: amostragem de acordo com a distribuição de probabilidades na Q-Table:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "obs = env.reset()\n", + "done = False\n", + "while not done:\n", + " s = discretize(obs)\n", + " env.render()\n", + " v = probs(np.array(qvalues(s)))\n", + " a = random.choices(actions,weights=v)[0]\n", + " obs,_,done,_ = env.step(a)\n", + "env.close()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Guardar o resultado num GIF animado\n", + "\n", + "Se quiser impressionar os seus amigos, pode enviar-lhes a imagem animada do bastão de equilíbrio. Para isso, podemos usar `env.render` para gerar um fotograma de imagem e, em seguida, guardar esses fotogramas num GIF animado utilizando a biblioteca PIL:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "360\n" + ] + } + ], + "source": [ + "from PIL import Image\n", + "obs = env.reset()\n", + "done = False\n", + "i=0\n", + "ims = []\n", + "while not done:\n", + " s = discretize(obs)\n", + " img=env.render(mode='rgb_array')\n", + " ims.append(Image.fromarray(img))\n", + " v = probs(np.array([Qbest.get((s,a),0) for a in actions]))\n", + " a = random.choices(actions,weights=v)[0]\n", + " obs,_,done,_ = env.step(a)\n", + " i+=1\n", + "env.close()\n", + "ims[0].save('images/cartpole-balance.gif',save_all=True,append_images=ims[1::2],loop=0,duration=5)\n", + "print(i)" + ] + }, + { + "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": "6206baa8d76f5ea272c3fd92e6e4d56e", + "translation_date": "2025-08-31T11:35:01+00:00", + "source_file": "lessons/6-Other/22-DeepRL/notebook.ipynb", + "language_code": "pt" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/translations/pt/lessons/6-Other/22-DeepRL/tmp.ipynb b/translations/pt/lessons/6-Other/22-DeepRL/tmp.ipynb new file mode 100644 index 00000000..f0b4b382 --- /dev/null +++ b/translations/pt/lessons/6-Other/22-DeepRL/tmp.ipynb @@ -0,0 +1,313 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import tensorflow as tf\n", + "from tensorflow import keras\n", + "import matplotlib.pyplot as plt\n", + "import gym" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "env = gym.make(\"CartPole-v1\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "<>:74: DeprecationWarning: invalid escape sequence \\g\n", + "<>:93: DeprecationWarning: invalid escape sequence \\d\n" + ] + } + ], + "source": [ + "class REINFORCE:\n", + " def __init__(self, env, path=None):\n", + " self.env=env #import env\n", + " self.state_shape=env.observation_space.shape # the state space\n", + " self.action_shape=env.action_space.n # the action space\n", + " self.gamma=0.99 # decay rate of past observations\n", + " self.alpha=1e-4 # learning rate in the policy gradient\n", + " self.learning_rate=0.01 # learning rate in deep learning\n", + " \n", + " if not path:\n", + " self.model=self._create_model() #build model\n", + " else:\n", + " self.model=self.load_model(path) #import model\n", + "\n", + " # record observations\n", + " self.states=[]\n", + " self.gradients=[] \n", + " self.rewards=[]\n", + " self.probs=[]\n", + " self.discounted_rewards=[]\n", + " self.total_rewards=[]\n", + "\n", + " def hot_encode_action(self, action):\n", + " '''encoding the actions into a binary list'''\n", + "\n", + " action_encoded=np.zeros(self.action_shape, np.float32)\n", + " action_encoded[action]=1\n", + "\n", + " return action_encoded\n", + " \n", + " def remember(self, state, action, action_prob, reward):\n", + " '''stores observations'''\n", + " encoded_action=self.hot_encode_action(action)\n", + " self.gradients.append(encoded_action-action_prob)\n", + " self.states.append(state)\n", + " self.rewards.append(reward)\n", + " self.probs.append(action_prob)\n", + "\n", + " def _create_model(self):\n", + " ''' builds the model using keras'''\n", + " model=keras.Sequential()\n", + "\n", + " # input shape is of observations\n", + " model.add(keras.layers.Dense(24, input_shape=self.state_shape, activation=\"relu\"))\n", + " # add a relu layer \n", + " model.add(keras.layers.Dense(12, activation=\"relu\"))\n", + "\n", + " # output shape is according to the number of action\n", + " # The softmax function outputs a probability distribution over the actions\n", + " model.add(keras.layers.Dense(self.action_shape, activation=\"softmax\")) \n", + " model.compile(loss=\"categorical_crossentropy\",\n", + " optimizer=keras.optimizers.Adam(lr=self.learning_rate))\n", + " \n", + " return model\n", + "\n", + " def get_action(self, state):\n", + " '''samples the next action based on the policy probabilty distribution \n", + " of the actions'''\n", + "\n", + " # transform state\n", + " state=state.reshape([1, state.shape[0]])\n", + " # get action probably\n", + " action_probability_distribution=self.model.predict(state).flatten()\n", + " # norm action probability distribution\n", + " action_probability_distribution/=np.sum(action_probability_distribution)\n", + " \n", + " # sample action\n", + " action=np.random.choice(self.action_shape,1,\n", + " p=action_probability_distribution)[0]\n", + "\n", + " return action, action_probability_distribution\n", + "\n", + " def get_discounted_rewards(self, rewards): \n", + " '''Use gamma to calculate the total reward discounting for rewards\n", + " Following - \\gamma ^ t * Gt'''\n", + " \n", + " discounted_rewards=[]\n", + " cumulative_total_return=0\n", + " # iterate the rewards backwards and and calc the total return \n", + " for reward in rewards[::-1]: \n", + " cumulative_total_return=(cumulative_total_return*self.gamma)+reward\n", + " discounted_rewards.insert(0, cumulative_total_return)\n", + "\n", + " # normalize discounted rewards\n", + " mean_rewards=np.mean(discounted_rewards)\n", + " std_rewards=np.std(discounted_rewards)\n", + " norm_discounted_rewards=(discounted_rewards-\n", + " mean_rewards)/(std_rewards+1e-7) # avoiding zero div\n", + " \n", + " return norm_discounted_rewards\n", + "\n", + " def update_policy(self):\n", + " '''Updates the policy network using the NN model.\n", + " This function is used after the MC sampling is done - following\n", + " \\delta \\theta = \\alpha * gradient + log pi'''\n", + " \n", + " # get X\n", + " states=np.vstack(self.states)\n", + "\n", + " # get Y\n", + " gradients=np.vstack(self.gradients)\n", + " rewards=np.vstack(self.rewards)\n", + " discounted_rewards=self.get_discounted_rewards(rewards)\n", + " gradients*=discounted_rewards\n", + " gradients=self.alpha*np.vstack([gradients])+self.probs\n", + "\n", + " history=self.model.train_on_batch(states, gradients)\n", + " \n", + " self.states, self.probs, self.gradients, self.rewards=[], [], [], []\n", + "\n", + " return history\n", + "\n", + " def train(self, episodes, rollout_n=1, render_n=50):\n", + " '''train the model\n", + " episodes - number of training iterations \n", + " rollout_n- number of episodes between policy update\n", + " render_n - number of episodes between env rendering ''' \n", + " \n", + " env=self.env\n", + " total_rewards=np.zeros(episodes)\n", + "\n", + " for episode in range(episodes):\n", + " # each episode is a new game env\n", + " state=env.reset()\n", + " done=False \n", + " episode_reward=0 #record episode reward\n", + " \n", + " while not done:\n", + " # play an action and record the game state & reward per episode\n", + " action, prob=self.get_action(state)\n", + " next_state, reward, done, _=env.step(action)\n", + " self.remember(state, action, prob, reward)\n", + " state=next_state\n", + " episode_reward+=reward\n", + "\n", + " #if episode%render_n==0: ## render env to visualize.\n", + " #env.render()\n", + " if done:\n", + " # update policy \n", + " if episode%rollout_n==0:\n", + " history=self.update_policy()\n", + "\n", + " total_rewards[episode]=episode_reward\n", + " if episode%10==0:\n", + " print(f\"{episode} -> {episode_reward}\")\n", + " \n", + " self.total_rewards=total_rewards" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\winapp\\Miniconda3\\envs\\py38\\lib\\site-packages\\keras\\optimizer_v2\\adam.py:105: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.\n", + " super(Adam, self).__init__(name, **kwargs)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 -> 39.0\n", + "10 -> 50.0\n", + "20 -> 18.0\n", + "30 -> 15.0\n", + "40 -> 16.0\n", + "50 -> 17.0\n", + "60 -> 20.0\n", + "70 -> 11.0\n", + "80 -> 20.0\n", + "90 -> 40.0\n", + "100 -> 36.0\n", + "110 -> 18.0\n", + "120 -> 79.0\n", + "130 -> 43.0\n", + "140 -> 41.0\n", + "150 -> 37.0\n", + "160 -> 95.0\n", + "170 -> 72.0\n", + "180 -> 138.0\n", + "190 -> 102.0\n" + ] + } + ], + "source": [ + "r = REINFORCE(env)\n", + "\n", + "r.train(200)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(r.total_rewards)" + ] + }, + { + "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 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" + }, + "orig_nbformat": 4, + "coopTranslator": { + "original_hash": "63bf7c4cf1c4e441d20d69060dc0a615", + "translation_date": "2025-08-31T11:35:03+00:00", + "source_file": "lessons/6-Other/22-DeepRL/tmp.ipynb", + "language_code": "pt" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/translations/pt/lessons/X-Extras/X1-MultiModal/Clip.ipynb b/translations/pt/lessons/X-Extras/X1-MultiModal/Clip.ipynb new file mode 100644 index 00000000..f6e2e533 --- /dev/null +++ b/translations/pt/lessons/X-Extras/X1-MultiModal/Clip.ipynb @@ -0,0 +1,222 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "37e940b3-c58b-4aef-b3eb-98a31e0b10c4", + "metadata": {}, + "source": [ + "# Modelos Multimodais\n", + "\n", + "Este notebook faz parte do [Currículo de IA para Iniciantes](http://aka.ms/ai-beginners).\n", + "\n", + "## Experimentar com o CLIP\n", + "\n", + "[CLIP](https://openai.com/blog/clip/) da OpenAI foi disponibilizado publicamente, por isso pode experimentá-lo para diferentes tarefas, incluindo classificação de imagens sem treino prévio (zero-shot). Tenha em mente que é bastante exigente em termos de recursos!\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6a187374-f4fc-4318-b89c-486b1e28686a", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "!{sys.executable} -m pip install git+https://github.com/openai/CLIP.git" + ] + }, + { + "cell_type": "markdown", + "id": "1574d33a-adaa-49dd-8a12-51a2a3426d18", + "metadata": {}, + "source": [ + "Vamos primeiro garantir que podemos usar a GPU, se estiver disponível, e depois carregar o modelo CLIP.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "907a1bc9-abfa-4290-9c66-729d45ed8cd5", + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import clip\n", + "from PIL import Image\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import os\n", + "np.set_printoptions(precision=2,suppress=True)\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "model, preprocess = clip.load(\"ViT-B/32\", device=device)" + ] + }, + { + "cell_type": "markdown", + "id": "0e796a87", + "metadata": {}, + "source": [ + "Vamos também obter um subconjunto de imagens de gatos do [Oxford-IIIT Dataset](https://www.robots.ox.ac.uk/~vgg/data/pets/):\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "fb3a2099", + "metadata": {}, + "outputs": [], + "source": [ + "!wget https://mslearntensorflowlp.blob.core.windows.net/data/oxcats.tar.gz\n", + "!tar xfz oxcats.tar.gz\n", + "!rm oxcats.tar.gz" + ] + }, + { + "cell_type": "markdown", + "id": "9b9f0991-9987-4a2a-bb51-19cdfb2c35af", + "metadata": {}, + "source": [ + "### Classificação de Imagens Zero-Shot\n", + "\n", + "A principal funcionalidade do CLIP é associar uma imagem a uma descrição em texto. Por exemplo, se tivermos uma imagem de um gato e tentarmos compará-la com as descrições \"um gato\", \"um pinguim\", \"um urso\" - a primeira provavelmente terá uma maior probabilidade. Assim, podemos concluir que estamos a lidar com um gato. Não é necessário treinar um modelo, pois ele já foi pré-treinado com um enorme conjunto de dados - por isso é chamado de **zero-shot**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5b7712b9-7e85-44ac-8a85-75e29f5e86ac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Label probs: [[0. 0. 1.]]\n" + ] + } + ], + "source": [ + "image = preprocess(Image.open(\"oxcats/Maine_Coon_1.jpg\")).unsqueeze(0).to(device)\n", + "text = clip.tokenize([\"a penguin\", \"a bear\", \"a cat\"]).to(device)\n", + "\n", + "with torch.no_grad():\n", + " image_features = model.encode_image(image)\n", + " text_features = model.encode_text(text)\n", + " \n", + " logits_per_image, logits_per_text = model(image, text)\n", + " probs = logits_per_image.softmax(dim=-1).cpu().numpy()\n", + "\n", + "print(\"Label probs:\", probs)" + ] + }, + { + "cell_type": "markdown", + "id": "8d66738a-f72c-4334-af71-5021f9942a39", + "metadata": {}, + "source": [ + "### Pesquisa Inteligente de Imagens\n", + "\n", + "No exemplo anterior, havia uma imagem e três descrições de texto. Podemos usar o CLIP num contexto diferente, por exemplo, podemos tirar várias imagens de um gato e, em seguida, selecionar a imagem que melhor se adequa à descrição textual:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "0f8c3b95-3409-4a37-9f40-cca6795e08e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Img Index: 97\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "cats_img = [ Image.open(os.path.join(\"oxcats\",x)) for x in os.listdir(\"oxcats\") ] \n", + "cats = torch.cat([ preprocess(i).unsqueeze(0) for i in cats_img ]).to(device)\n", + "text = clip.tokenize([\"a very fat gray cat\"]).to(device)\n", + "with torch.no_grad():\n", + " logits_per_image, logits_per_text = model(cats, text)\n", + " res = logits_per_text.softmax(dim=-1).argmax().cpu().numpy()\n", + "\n", + "print(\"Img Index:\", res)\n", + "\n", + "plt.imshow(cats_img[res])" + ] + }, + { + "cell_type": "markdown", + "id": "21a91394", + "metadata": {}, + "source": [ + "## Conclusão\n", + "\n", + "O modelo CLIP pré-treinado pode ser utilizado para realizar tarefas como classificação de imagens de objetos comuns sem necessidade de treino específico para o domínio. Além disso, permite uma classificação/pesquisa de imagens mais flexível, considerando a configuração espacial dos objetos na imagem.\n", + "\n", + "Para outro uso interessante do CLIP, procure por **VQGAN+CLIP**.\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": "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": "0deee3d1c1d97b670a6dfd2ebd28220f", + "translation_date": "2025-08-31T11:36:36+00:00", + "source_file": "lessons/X-Extras/X1-MultiModal/Clip.ipynb", + "language_code": "pt" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file