🌐 Update translations via Co-op Translator
This commit is contained in:
parent
a76be73417
commit
b9c43e4edf
|
|
@ -1,8 +1,8 @@
|
|||
<!--
|
||||
CO_OP_TRANSLATOR_METADATA:
|
||||
{
|
||||
"original_hash": "f3a6b0ddf7e6e3f33b2a543baf086dc9",
|
||||
"translation_date": "2025-08-24T21:21:49+00:00",
|
||||
"original_hash": "07191303b7ea2aff1d47e2b0fe4bb862",
|
||||
"translation_date": "2025-08-31T12:22:37+00:00",
|
||||
"source_file": "README.md",
|
||||
"language_code": "ko"
|
||||
}
|
||||
|
|
@ -23,103 +23,113 @@ CO_OP_TRANSLATOR_METADATA:
|
|||
|
||||
# 초보자를 위한 인공지능 - 커리큘럼
|
||||
|
||||
| ](./lessons/sketchnotes/ai-overview.png)|
|
||||
||
|
||||
|:---:|
|
||||
| 초보자를 위한 인공지능 - _스케치노트 by [@girlie_mac](https://twitter.com/girlie_mac)_ |
|
||||
| 초보자를 위한 AI - _스케치노트: [@girlie_mac](https://twitter.com/girlie_mac)_ |
|
||||
|
||||
**인공지능**(AI)의 세계를 탐험해보세요! 12주 동안 진행되는 24개의 강의로 구성된 커리큘럼입니다. 실습 강의, 퀴즈, 실험이 포함되어 있으며 초보자도 쉽게 따라올 수 있습니다. TensorFlow와 PyTorch 같은 도구뿐만 아니라 AI 윤리도 다룹니다.
|
||||
**인공지능**(AI)의 세계를 12주, 24강으로 구성된 커리큘럼을 통해 탐험해보세요! 실습 강의, 퀴즈, 실험실이 포함되어 있으며, 초보자도 쉽게 따라갈 수 있도록 설계되었습니다. TensorFlow와 PyTorch 같은 도구와 AI 윤리도 다룹니다.
|
||||
|
||||
### 🌐 다국어 지원
|
||||
|
||||
#### GitHub Action을 통한 지원 (자동화 및 항상 최신 상태 유지)
|
||||
|
||||
[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](./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)](../pt/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)
|
||||
|
||||
**추가 번역을 원하시면 [여기](https://github.com/Azure/co-op-translator/blob/main/getting_started/supported-languages.md)에서 지원 언어를 확인하세요.**
|
||||
|
||||
## 커뮤니티에 참여하세요
|
||||
[](https://discord.gg/kzRShWzttr)
|
||||
|
||||
## 학습 내용
|
||||
|
||||
**[코스 마인드맵](http://soshnikov.com/courses/ai-for-beginners/mindmap.html)**
|
||||
**[강의 마인드맵](http://soshnikov.com/courses/ai-for-beginners/mindmap.html)**
|
||||
|
||||
이 커리큘럼에서 배우게 될 내용:
|
||||
|
||||
* **지식 표현**과 추론을 포함한 "고전적인" 상징적 접근 방식([GOFAI](https://en.wikipedia.org/wiki/Symbolic_artificial_intelligence)).
|
||||
* 현대 AI의 핵심인 **신경망**과 **딥러닝**. 이 중요한 주제의 개념을 [TensorFlow](http://Tensorflow.org)와 [PyTorch](http://pytorch.org)라는 두 가지 인기 있는 프레임워크를 사용하여 코드로 설명합니다.
|
||||
* 이미지와 텍스트를 다루기 위한 **신경 아키텍처**. 최신 모델을 다루지만 최첨단 기술은 다소 부족할 수 있습니다.
|
||||
* **유전 알고리즘**과 **다중 에이전트 시스템**과 같은 덜 알려진 AI 접근 방식.
|
||||
* **지식 표현**과 추론을 포함한 "고전적" 상징적 접근법([GOFAI](https://en.wikipedia.org/wiki/Symbolic_artificial_intelligence))을 포함한 다양한 인공지능 접근법.
|
||||
* 현대 AI의 핵심인 **신경망**과 **딥러닝**. TensorFlow와 PyTorch라는 두 가지 인기 있는 프레임워크를 사용하여 이 중요한 주제의 개념을 설명합니다.
|
||||
* 이미지와 텍스트 작업을 위한 **신경망 아키텍처**. 최신 모델을 다루지만 최첨단 기술은 다소 부족할 수 있습니다.
|
||||
* **유전 알고리즘** 및 **다중 에이전트 시스템**과 같은 덜 알려진 AI 접근법.
|
||||
|
||||
이 커리큘럼에서 다루지 않는 내용:
|
||||
|
||||
> [이 과정의 추가 자료는 Microsoft Learn 컬렉션에서 확인하세요](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum)
|
||||
> [이 강의의 추가 자료는 Microsoft Learn 컬렉션에서 확인하세요](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum)
|
||||
|
||||
* **비즈니스에서 AI 활용** 사례. Microsoft Learn에서 [비즈니스 사용자를 위한 AI 소개](https://docs.microsoft.com/learn/paths/introduction-ai-for-business-users/?WT.mc_id=academic-77998-bethanycheum) 학습 경로를 수강하거나 [INSEAD](https://www.insead.edu/)와 협력하여 개발된 [AI 비즈니스 스쿨](https://www.microsoft.com/ai/ai-business-school/?WT.mc_id=academic-77998-bethanycheum)을 참고하세요.
|
||||
* **고전적인 머신러닝**, 이는 [초보자를 위한 머신러닝 커리큘럼](http://github.com/Microsoft/ML-for-Beginners)에 잘 설명되어 있습니다.
|
||||
* **[Cognitive Services](https://azure.microsoft.com/services/cognitive-services/?WT.mc_id=academic-77998-bethanycheum)**를 사용하여 구축된 실용적인 AI 애플리케이션. 이를 위해 Microsoft Learn의 [비전](https://docs.microsoft.com/learn/paths/create-computer-vision-solutions-azure-cognitive-services/?WT.mc_id=academic-77998-bethanycheum), [자연어 처리](https://docs.microsoft.com/learn/paths/explore-natural-language-processing/?WT.mc_id=academic-77998-bethanycheum), **[Azure OpenAI Service를 활용한 생성 AI](https://learn.microsoft.com/en-us/training/paths/develop-ai-solutions-azure-openai/?WT.mc_id=academic-77998-bethanycheum)** 등의 모듈을 시작하는 것을 추천합니다.
|
||||
* 특정 ML **클라우드 프레임워크**, 예를 들어 [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), 또는 [Azure Databricks](https://docs.microsoft.com/learn/paths/data-engineer-azure-databricks?WT.mc_id=academic-77998-bethanycheum). [Azure Machine Learning을 사용하여 머신러닝 솔루션 구축 및 운영](https://docs.microsoft.com/learn/paths/build-ai-solutions-with-azure-ml-service/?WT.mc_id=academic-77998-bethanycheum) 및 [Azure Databricks를 사용하여 머신러닝 솔루션 구축 및 운영](https://docs.microsoft.com/learn/paths/build-operate-machine-learning-solutions-azure-databricks/?WT.mc_id=academic-77998-bethanycheum) 학습 경로를 고려하세요.
|
||||
* **대화형 AI** 및 **챗봇**. 별도의 [대화형 AI 솔루션 만들기](https://docs.microsoft.com/learn/paths/create-conversational-ai-solutions/?WT.mc_id=academic-77998-bethanycheum) 학습 경로가 있으며, [이 블로그 게시물](https://soshnikov.com/azure/hello-bot-conversational-ai-on-microsoft-platform/)을 참조할 수 있습니다.
|
||||
* 딥러닝의 **심화 수학적 내용**. 이를 위해 Ian Goodfellow, Yoshua Bengio, Aaron Courville의 [Deep Learning](https://www.amazon.com/Deep-Learning-Adaptive-Computation-Machine/dp/0262035618)을 추천하며, 온라인에서도 확인할 수 있습니다: [https://www.deeplearningbook.org/](https://www.deeplearningbook.org/).
|
||||
* **비즈니스에서의 AI 활용 사례**. Microsoft Learn의 [비즈니스 사용자를 위한 AI 소개](https://docs.microsoft.com/learn/paths/introduction-ai-for-business-users/?WT.mc_id=academic-77998-bethanycheum) 학습 경로나 [AI 비즈니스 스쿨](https://www.microsoft.com/ai/ai-business-school/?WT.mc_id=academic-77998-bethanycheum)을 고려하세요. 이 과정은 [INSEAD](https://www.insead.edu/)와 협력하여 개발되었습니다.
|
||||
* **고전적 머신러닝**. 이는 [초보자를 위한 머신러닝 커리큘럼](http://github.com/Microsoft/ML-for-Beginners)에서 잘 설명되어 있습니다.
|
||||
* **[Cognitive Services](https://azure.microsoft.com/services/cognitive-services/?WT.mc_id=academic-77998-bethanycheum)**를 사용하여 구축된 실질적인 AI 응용 프로그램. 이를 위해 Microsoft Learn의 [비전](https://docs.microsoft.com/learn/paths/create-computer-vision-solutions-azure-cognitive-services/?WT.mc_id=academic-77998-bethanycheum), [자연어 처리](https://docs.microsoft.com/learn/paths/explore-natural-language-processing/?WT.mc_id=academic-77998-bethanycheum), **[Azure OpenAI 서비스로 생성형 AI](https://learn.microsoft.com/en-us/training/paths/develop-ai-solutions-azure-openai/?WT.mc_id=academic-77998-bethanycheum)** 모듈을 시작점으로 삼으세요.
|
||||
* **클라우드 머신러닝 프레임워크**, 예를 들어 [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), 또는 [Azure Databricks](https://docs.microsoft.com/learn/paths/data-engineer-azure-databricks?WT.mc_id=academic-77998-bethanycheum). [Azure Machine Learning으로 AI 솔루션 구축 및 운영](https://docs.microsoft.com/learn/paths/build-ai-solutions-with-azure-ml-service/?WT.mc_id=academic-77998-bethanycheum) 및 [Azure Databricks로 머신러닝 솔루션 구축 및 운영](https://docs.microsoft.com/learn/paths/build-operate-machine-learning-solutions-azure-databricks/?WT.mc_id=academic-77998-bethanycheum) 학습 경로를 참고하세요.
|
||||
* **대화형 AI** 및 **챗봇**. 별도의 [대화형 AI 솔루션 만들기](https://docs.microsoft.com/learn/paths/create-conversational-ai-solutions/?WT.mc_id=academic-77998-bethanycheum) 학습 경로가 있으며, [이 블로그 게시물](https://soshnikov.com/azure/hello-bot-conversational-ai-on-microsoft-platform/)에서 더 자세한 내용을 확인할 수 있습니다.
|
||||
* 딥러닝의 **심화 수학**. 이를 위해 Ian Goodfellow, Yoshua Bengio, Aaron Courville의 [Deep Learning](https://www.amazon.com/Deep-Learning-Adaptive-Computation-Machine/dp/0262035618)을 추천합니다. 이 책은 [https://www.deeplearningbook.org/](https://www.deeplearningbook.org/)에서도 온라인으로 이용할 수 있습니다.
|
||||
|
||||
클라우드에서 _AI_를 부드럽게 소개받고 싶다면 [Azure에서 인공지능 시작하기](https://docs.microsoft.com/learn/paths/get-started-with-artificial-intelligence-on-azure/?WT.mc_id=academic-77998-bethanycheum) 학습 경로를 고려하세요.
|
||||
클라우드에서 _AI_를 부드럽게 시작하려면 [Azure에서 인공지능 시작하기](https://docs.microsoft.com/learn/paths/get-started-with-artificial-intelligence-on-azure/?WT.mc_id=academic-77998-bethanycheum) 학습 경로를 고려해보세요.
|
||||
|
||||
# 콘텐츠
|
||||
|
||||
| | 강의 링크 | PyTorch/Keras/TensorFlow | 실습 |
|
||||
| :-: | :------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------: | ------------------------------------------------------------------------------ |
|
||||
| 0 | [코스 설정](./lessons/0-course-setup/setup.md) | [개발 환경 설정](./lessons/0-course-setup/how-to-run.md) | |
|
||||
| 0 | [강의 준비](./lessons/0-course-setup/setup.md) | [개발 환경 설정](./lessons/0-course-setup/how-to-run.md) | |
|
||||
| I | [**AI 소개**](./lessons/1-Intro/README.md) | | |
|
||||
| 01 | [AI 소개 및 역사](./lessons/1-Intro/README.md) | - | - |
|
||||
| II | **상징적 AI** |
|
||||
| 02 | [지식 표현 및 전문가 시스템](./lessons/2-Symbolic/README.md) | [전문가 시스템](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/2-Symbolic/Animals.ipynb) / [온톨로지](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/2-Symbolic/FamilyOntology.ipynb) /[개념 그래프](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/2-Symbolic/MSConceptGraph.ipynb) | |
|
||||
| 02 | [지식 표현 및 전문가 시스템](./lessons/2-Symbolic/README.md) | [전문가 시스템](./lessons/2-Symbolic/Animals.ipynb) / [온톨로지](./lessons/2-Symbolic/FamilyOntology.ipynb) /[개념 그래프](./lessons/2-Symbolic/MSConceptGraph.ipynb) | |
|
||||
| III | [**신경망 소개**](./lessons/3-NeuralNetworks/README.md) |||
|
||||
| 03 | [퍼셉트론](./lessons/3-NeuralNetworks/03-Perceptron/README.md) | [노트북](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb) | [실습](./lessons/3-NeuralNetworks/03-Perceptron/lab/README.md) |
|
||||
| 04 | [다층 퍼셉트론 및 자체 프레임워크 생성](./lessons/3-NeuralNetworks/04-OwnFramework/README.md) | [노트북](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/3-NeuralNetworks/04-OwnFramework/OwnFramework.ipynb) | [실습](./lessons/3-NeuralNetworks/04-OwnFramework/lab/README.md) |
|
||||
| 05 | [프레임워크 소개 (PyTorch/TensorFlow) 및 과적합](./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) | [실습](./lessons/3-NeuralNetworks/05-Frameworks/lab/README.md) |
|
||||
| IV | [**컴퓨터 비전**](./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)| [Microsoft Azure에서 컴퓨터 비전 탐색](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum) |
|
||||
| 06 | [컴퓨터 비전 소개. OpenCV](./lessons/4-ComputerVision/06-IntroCV/README.md) | [노트북](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/06-IntroCV/OpenCV.ipynb) | [실습](./lessons/4-ComputerVision/06-IntroCV/lab/README.md) |
|
||||
| 07 | [합성곱 신경망](./lessons/4-ComputerVision/07-ConvNets/README.md) & [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) | [실습](./lessons/4-ComputerVision/07-ConvNets/lab/README.md) |
|
||||
| 08 | [사전 학습된 네트워크와 전이 학습](./lessons/4-ComputerVision/08-TransferLearning/README.md) 및 [학습 팁](./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) | [실습](./lessons/4-ComputerVision/08-TransferLearning/lab/README.md) |
|
||||
| 09 | [오토인코더와 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 | [생성적 적대 신경망(GANs) 및 예술적 스타일 전이](./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 | [객체 탐지](./lessons/4-ComputerVision/11-ObjectDetection/README.md) | [TensorFlow](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/4-ComputerVision/11-ObjectDetection/ObjectDetection.ipynb) | [실습](./lessons/4-ComputerVision/11-ObjectDetection/lab/README.md) |
|
||||
| 12 | [의미론적 분할. 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 | [**자연어 처리**](./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) | [Microsoft Azure에서 자연어 처리 탐색](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum)|
|
||||
| 13 | [텍스트 표현. 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 | [의미론적 단어 임베딩. Word2Vec 및 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 | [언어 모델링. 임베딩 직접 학습하기](./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) | [실습](./lessons/5-NLP/15-LanguageModeling/lab/README.md) |
|
||||
| 16 | [순환 신경망(RNN)](./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 | [생성적 순환 신경망](./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) | [실습](./lessons/5-NLP/17-GenerativeNetworks/lab/README.md) |
|
||||
| 18 | [트랜스포머. 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 | [개체명 인식(NER)](./lessons/5-NLP/19-NER/README.md) | [TensorFlow](https://microsoft.github.io/AI-For-Beginners/lessons/5-NLP/19-NER/NER-TF.ipynb) | [실습](./lessons/5-NLP/19-NER/lab/README.md) |
|
||||
| 20 | [대규모 언어 모델, 프롬프트 프로그래밍 및 Few-Shot 작업](./lessons/5-NLP/20-LangModels/READMELargeLang.md) | [PyTorch](https://microsoft.github.io/AI-For-Beginners/lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb) | |
|
||||
| 03 | [퍼셉트론](./lessons/3-NeuralNetworks/03-Perceptron/README.md) | [노트북](./lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb) | [실습](./lessons/3-NeuralNetworks/03-Perceptron/lab/README.md) |
|
||||
| 04 | [다층 퍼셉트론 및 자체 프레임워크 생성](./lessons/3-NeuralNetworks/04-OwnFramework/README.md) | [노트북](./lessons/3-NeuralNetworks/04-OwnFramework/OwnFramework.ipynb) | [실습](./lessons/3-NeuralNetworks/04-OwnFramework/lab/README.md) |
|
||||
| 05 | [프레임워크 소개 (PyTorch/TensorFlow) 및 과적합](./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) | [실습](./lessons/3-NeuralNetworks/05-Frameworks/lab/README.md) |
|
||||
| IV | [**컴퓨터 비전**](./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)| [Microsoft Azure에서 컴퓨터 비전 탐구하기](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum) |
|
||||
| 06 | [컴퓨터 비전 소개. OpenCV](./lessons/4-ComputerVision/06-IntroCV/README.md) | [노트북](./lessons/4-ComputerVision/06-IntroCV/OpenCV.ipynb) | [실습](./lessons/4-ComputerVision/06-IntroCV/lab/README.md) |
|
||||
| 07 | [합성곱 신경망](./lessons/4-ComputerVision/07-ConvNets/README.md) & [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) | [실습](./lessons/4-ComputerVision/07-ConvNets/lab/README.md) |
|
||||
| 08 | [사전 학습된 네트워크 및 전이 학습](./lessons/4-ComputerVision/08-TransferLearning/README.md) 및 [훈련 팁](./lessons/4-ComputerVision/08-TransferLearning/TrainingTricks.md) | [PyTorch](./lessons/4-ComputerVision/08-TransferLearning/TransferLearningPyTorch.ipynb) / [TensorFlow](./lessons/3-NeuralNetworks/05-Frameworks/IntroKerasTF.ipynb) | [실습](./lessons/4-ComputerVision/08-TransferLearning/lab/README.md) |
|
||||
| 09 | [오토인코더 및 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 | [생성적 적대 신경망 및 예술적 스타일 전이](./lessons/4-ComputerVision/10-GANs/README.md) | [PyTorch](./lessons/4-ComputerVision/10-GANs/GANPyTorch.ipynb) / [TensorFlow](./lessons/4-ComputerVision/10-GANs/GANTF.ipynb) | |
|
||||
| 11 | [객체 탐지](./lessons/4-ComputerVision/11-ObjectDetection/README.md) | [TensorFlow](./lessons/4-ComputerVision/11-ObjectDetection/ObjectDetection.ipynb) | [실습](./lessons/4-ComputerVision/11-ObjectDetection/lab/README.md) |
|
||||
| 12 | [의미론적 분할. 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 | [**자연어 처리**](./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) | [Microsoft Azure에서 자연어 처리 탐구하기](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum)|
|
||||
| 13 | [텍스트 표현. 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 | [의미론적 단어 임베딩. Word2Vec 및 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 | [언어 모델링. 임베딩 직접 훈련하기](./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) | [실습](./lessons/5-NLP/15-LanguageModeling/lab/README.md) |
|
||||
| 16 | [순환 신경망](./lessons/5-NLP/16-RNN/README.md) | [PyTorch](./lessons/5-NLP/16-RNN/RNNPyTorch.ipynb) / [TensorFlow](./lessons/5-NLP/16-RNN/RNNTF.ipynb) | |
|
||||
| 17 | [생성적 순환 네트워크](./lessons/5-NLP/17-GenerativeNetworks/README.md) | [PyTorch](./lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.md) / [TensorFlow](./lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.md) | [실습](./lessons/5-NLP/17-GenerativeNetworks/lab/README.md) |
|
||||
| 18 | [트랜스포머. 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 | [개체명 인식](./lessons/5-NLP/19-NER/README.md) | [TensorFlow](./lessons/5-NLP/19-NER/NER-TF.ipynb) | [실습](./lessons/5-NLP/19-NER/lab/README.md) |
|
||||
| 20 | [대규모 언어 모델, 프롬프트 프로그래밍 및 Few-Shot 작업](./lessons/5-NLP/20-LangModels/READMELargeLang.md) | [PyTorch](./lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb) | |
|
||||
| VI | **기타 AI 기술** || |
|
||||
| 21 | [유전 알고리즘](./lessons/6-Other/21-GeneticAlgorithms/README.md) | [노트북](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb) | |
|
||||
| 22 | [심층 강화 학습](./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) | [실습](./lessons/6-Other/22-DeepRL/lab/README.md) |
|
||||
| 21 | [유전 알고리즘](./lessons/6-Other/21-GeneticAlgorithms/README.md) | [노트북](./lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb) | |
|
||||
| 22 | [심층 강화 학습](./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) | [실습](./lessons/6-Other/22-DeepRL/lab/README.md) |
|
||||
| 23 | [다중 에이전트 시스템](./lessons/6-Other/23-MultiagentSystems/README.md) | | |
|
||||
| VII | **AI 윤리** | | |
|
||||
| 24 | [AI 윤리 및 책임 있는 AI](./lessons/7-Ethics/README.md) | [Microsoft Learn: 책임 있는 AI 원칙](https://docs.microsoft.com/learn/paths/responsible-ai-business-principles/?WT.mc_id=academic-77998-cacaste) | |
|
||||
| IX | **추가 자료** | | |
|
||||
| 25 | [멀티모달 네트워크, CLIP 및 VQGAN](./lessons/X-Extras/X1-MultiModal/README.md) | [노트북](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/X-Extras/X1-MultiModal/Clip.ipynb) | |
|
||||
| IX | **기타** | | |
|
||||
| 25 | [멀티모달 네트워크, CLIP 및 VQGAN](./lessons/X-Extras/X1-MultiModal/README.md) | [노트북](./lessons/X-Extras/X1-MultiModal/Clip.ipynb) | |
|
||||
|
||||
## 각 강의는 다음을 포함합니다
|
||||
## 각 강의에는 다음이 포함됩니다
|
||||
|
||||
* 사전 학습 자료
|
||||
* 실행 가능한 Jupyter 노트북, 주로 프레임워크(**PyTorch** 또는 **TensorFlow**)에 따라 제공됩니다. 실행 가능한 노트북에는 많은 이론적 내용이 포함되어 있으므로 주제를 이해하려면 노트북 버전 중 하나(예: PyTorch 또는 TensorFlow)를 반드시 학습해야 합니다.
|
||||
* 사전 읽기 자료
|
||||
* 실행 가능한 Jupyter 노트북. 이는 주로 프레임워크(**PyTorch** 또는 **TensorFlow**)에 따라 다릅니다. 실행 가능한 노트북에는 많은 이론적 자료도 포함되어 있으므로, 주제를 이해하려면 노트북 버전 중 하나(Pytorch 또는 TensorFlow)를 반드시 학습해야 합니다.
|
||||
* **실습**은 일부 주제에서 제공되며, 학습한 내용을 특정 문제에 적용해볼 기회를 제공합니다.
|
||||
* 일부 섹션에는 관련 주제를 다루는 [**MS Learn**](https://learn.microsoft.com/en-us/collections/7w28iy2xrqzdj0?WT.mc_id=academic-77998-bethanycheum) 모듈 링크가 포함되어 있습니다.
|
||||
|
||||
## 시작하기
|
||||
|
||||
- 개발 환경 설정을 돕기 위해 [설정 강의](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/0-course-setup/setup.md)를 준비했습니다. - 교육자를 위해 [교사용 커리큘럼 설정 강의](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/0-course-setup/for-teachers.md)도 준비했습니다!
|
||||
- [VSCode 또는 Codepace에서 코드 실행 방법](https://github.com/microsoft/AI-For-Beginners/blob/main/lessons/0-course-setup/how-to-run.md)
|
||||
- 개발 환경 설정을 돕기 위해 [설정 강의](./lessons/0-course-setup/setup.md)를 준비했습니다.
|
||||
- 교육자를 위해 [커리큘럼 설정 강의](./lessons/0-course-setup/for-teachers.md)도 준비했습니다!
|
||||
- [VSCode 또는 Codepace에서 코드 실행 방법](./lessons/0-course-setup/how-to-run.md)
|
||||
|
||||
다음 단계를 따라 진행하세요:
|
||||
다음 단계를 따르세요:
|
||||
|
||||
저장소 포크: 페이지 오른쪽 상단의 "Fork" 버튼을 클릭하세요.
|
||||
|
||||
저장소 클론: `git clone https://github.com/microsoft/AI-For-Beginners.git`
|
||||
|
||||
이 저장소를 나중에 쉽게 찾을 수 있도록 별표(🌟)를 잊지 마세요.
|
||||
1. 리포지토리 포크: 이 페이지 오른쪽 상단의 "Fork" 버튼을 클릭하세요.
|
||||
2. 리포지토리 클론: `git clone https://github.com/microsoft/AI-For-Beginners.git`
|
||||
3. 이 리포지토리를 별표(🌟) 표시하여 나중에 쉽게 찾을 수 있도록 하세요.
|
||||
|
||||
## 다른 학습자 만나기
|
||||
|
||||
[공식 AI Discord 서버](https://aka.ms/genai-discord?WT.mc_id=academic-105485-bethanycheum)에 가입하여 이 과정을 수강하는 다른 학습자들과 네트워크를 형성하고 지원을 받으세요.
|
||||
[공식 AI Discord 서버](https://aka.ms/genai-discord?WT.mc_id=academic-105485-bethanycheum)에 참여하여 이 과정을 수강하는 다른 학습자들과 네트워크를 형성하고 지원을 받으세요.
|
||||
|
||||
제품 피드백이나 질문이 있을 경우 [Azure AI Foundry 개발자 포럼](https://aka.ms/foundry/forum)을 방문하세요.
|
||||
제품 피드백이나 질문이 있다면 [Azure AI Foundry 개발자 포럼](https://aka.ms/foundry/forum)을 방문하세요.
|
||||
|
||||
## 퀴즈
|
||||
> **퀴즈에 대한 참고 사항**: 모든 퀴즈는 etc\quiz-app의 Quiz-app 폴더에 포함되어 있습니다. 퀴즈는 각 레슨 내에서 연결되어 있으며, 퀴즈 앱은 로컬에서 실행하거나 Azure에 배포할 수 있습니다. `quiz-app` 폴더의 지침을 따르세요. 퀴즈는 점진적으로 현지화되고 있습니다.
|
||||
> **퀴즈에 대한 참고 사항**: 모든 퀴즈는 etc\quiz-app의 Quiz-app 폴더에 포함되어 있으며, [온라인에서 확인하기](https://ff-quizzes.netlify.app/)도 가능합니다. 퀴즈는 각 수업 내에서 연결되어 있으며, 로컬에서 실행하거나 Azure에 배포할 수 있습니다. `quiz-app` 폴더의 지침을 따라주세요. 퀴즈는 점진적으로 현지화되고 있습니다.
|
||||
## 도움 요청
|
||||
|
||||
제안 사항이 있거나 철자 또는 코드 오류를 발견하셨나요? 이슈를 제기하거나 풀 리퀘스트를 생성해 주세요.
|
||||
|
|
@ -151,5 +161,7 @@ CO_OP_TRANSLATOR_METADATA:
|
|||
- [C#/.NET 개발자를 위한 GitHub Copilot 마스터하기](https://github.com/microsoft/mastering-github-copilot-for-dotnet-csharp-developers)
|
||||
- [나만의 Copilot 모험 선택하기](https://github.com/microsoft/CopilotAdventures)
|
||||
|
||||
---
|
||||
|
||||
**면책 조항**:
|
||||
이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.
|
||||
이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.
|
||||
|
|
@ -0,0 +1,478 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"source": [
|
||||
"# 동물 전문가 시스템 구현하기\n",
|
||||
"\n",
|
||||
"[AI for Beginners Curriculum](http://github.com/microsoft/ai-for-beginners)에서 제공하는 예제입니다.\n",
|
||||
"\n",
|
||||
"이 예제에서는 몇 가지 신체적 특징을 기반으로 동물을 판별하는 간단한 지식 기반 시스템을 구현합니다. 이 시스템은 다음과 같은 AND-OR 트리로 표현될 수 있습니다 (이 트리는 전체 트리의 일부이며, 규칙을 더 추가하는 것도 간단합니다):\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 우리의 역추론 기반 전문가 시스템 셸\n",
|
||||
"\n",
|
||||
"생산 규칙에 기반한 지식 표현을 위한 간단한 언어를 정의해 봅시다. 우리는 규칙을 정의하기 위해 Python 클래스들을 키워드로 사용할 것입니다. 기본적으로 세 가지 유형의 클래스가 있습니다:\n",
|
||||
"* `Ask`는 사용자에게 물어봐야 할 질문을 나타냅니다. 이 클래스는 가능한 답변의 집합을 포함합니다.\n",
|
||||
"* `If`는 규칙을 나타내며, 규칙의 내용을 저장하기 위한 단순한 문법적 설탕(syntactic sugar)입니다.\n",
|
||||
"* `AND`/`OR`는 트리의 AND/OR 분기를 나타내는 클래스입니다. 이 클래스들은 내부에 인수 목록을 저장하는 역할만 합니다. 코드를 단순화하기 위해 모든 기능은 부모 클래스인 `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": [
|
||||
"우리 시스템에서 작업 기억은 **속성-값 쌍**으로 된 **사실** 목록을 포함합니다. 지식 기반은 작업 기억에 삽입해야 할 새로운 사실(행동)을 조건에 매핑하는 하나의 큰 사전으로 정의될 수 있으며, 조건은 AND-OR 표현식으로 표현됩니다. 또한 일부 사실은 `Ask`될 수 있습니다.\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": [
|
||||
"역추론을 수행하기 위해 `Knowledgebase` 클래스를 정의합니다. 이 클래스는 다음을 포함합니다:\n",
|
||||
"* 작동 중인 `memory` - 속성을 값에 매핑하는 딕셔너리\n",
|
||||
"* 위에서 정의된 형식의 Knowledgebase `rules`\n",
|
||||
"\n",
|
||||
"주요 메서드 두 가지는 다음과 같습니다:\n",
|
||||
"* `get` - 속성의 값을 얻고 필요하면 추론을 수행합니다. 예를 들어, `get('color')`는 색상 슬롯의 값을 가져옵니다(필요하면 질문을 하고, 이후 사용을 위해 작업 메모리에 값을 저장합니다). 만약 `get('color:blue')`를 요청하면 색상을 묻고, 색상에 따라 `y`/`n` 값을 반환합니다.\n",
|
||||
"* `eval` - 실제 추론을 수행하며, AND/OR 트리를 탐색하고, 하위 목표를 평가하는 등의 작업을 합니다.\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": [
|
||||
"이제 우리의 동물 지식 기반을 정의하고 상담을 수행해 봅시다. 이 호출은 당신에게 질문을 할 것입니다. 예/아니오 질문에는 `y`/`n`을 입력하여 답변할 수 있으며, 더 긴 다중 선택 질문에는 숫자(0..N)를 지정하여 답변할 수 있습니다.\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": [
|
||||
"## PyKnow를 사용한 전방 추론\n",
|
||||
"\n",
|
||||
"다음 예제에서는 지식 표현을 위한 라이브러리 중 하나인 [PyKnow](https://github.com/buguroo/pyknow/)를 사용하여 전방 추론을 구현해 보겠습니다. **PyKnow**는 Python에서 전방 추론 시스템을 생성하기 위한 라이브러리로, 고전적인 시스템 [CLIPS](http://www.clipsrules.net/index.html)와 유사하게 설계되었습니다.\n",
|
||||
"\n",
|
||||
"물론 우리가 직접 전방 연쇄를 구현할 수도 있지만, 단순한 구현은 보통 효율적이지 않습니다. 더 효과적인 규칙 매칭을 위해 특별한 알고리즘인 [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": [
|
||||
"우리 시스템을 `KnowledgeEngine`을 서브클래스로 하는 클래스으로 정의할 것입니다. 각 규칙은 `@Rule` 주석으로 정의된 별도의 함수로 구성되며, 규칙이 실행되어야 할 시점을 지정합니다. 규칙 내부에서는 `declare` 함수를 사용하여 새로운 사실을 추가할 수 있으며, 이러한 사실을 추가하면 전방 추론 엔진에 의해 더 많은 규칙이 호출됩니다.\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": [
|
||||
"지식 기반을 정의한 후, 몇 가지 초기 사실로 작업 메모리를 채우고 `run()` 메서드를 호출하여 추론을 수행합니다. 결과적으로 새로운 추론된 사실들이 작업 메모리에 추가되며, 초기 사실을 올바르게 설정한 경우 동물에 대한 최종 사실도 포함됩니다.\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**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 신뢰할 수 있는 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T13:15:39+00:00",
|
||||
"source_file": "lessons/2-Symbolic/Animals.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,595 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"source": [
|
||||
"# 가족 관계 온톨로지\n",
|
||||
"\n",
|
||||
"이 예제는 [AI for Beginners Curriculum](http://github.com/microsoft/ai-for-beginners)의 일부이며, [이 블로그 글](https://habr.com/post/270857/)에서 영감을 받았습니다.\n",
|
||||
"\n",
|
||||
"저는 항상 가족 내 사람들 간의 다양한 관계를 기억하는 것이 어렵다고 느낍니다. 이 예제에서는 가족 관계를 정의하는 온톨로지와 실제 족보를 사용하여, 자동 추론을 통해 모든 친척을 찾는 방법을 보여드리겠습니다.\n",
|
||||
"\n",
|
||||
"### 족보 얻기\n",
|
||||
"\n",
|
||||
"예제로, [로마노프 황실 가문](https://en.wikipedia.org/wiki/House_of_Romanov)의 족보를 사용하겠습니다. 가족 관계를 설명하는 가장 일반적인 형식은 [GEDCOM](https://en.wikipedia.org/wiki/GEDCOM)입니다. 우리는 로마노프 가문의 족보를 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": [
|
||||
"GEDCOM 파일을 사용하려면 `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": [
|
||||
"이 라이브러리는 파일 구문 분석과 관련된 몇 가지 기술적 문제를 제거하지만, 여전히 트리 내의 모든 개인과 가족에 대해 비교적 저수준의 접근을 제공합니다. 다음은 파일을 구문 분석하고 모든 개인의 목록을 표시하는 방법입니다:\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": [
|
||||
"다음은 가족에 대한 정보를 얻는 방법입니다. 참고로 이것은 **식별자** 목록을 제공하며, 더 명확히 하고 싶다면 이를 이름으로 변환해야 합니다:\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": [
|
||||
"### 가족 온톨로지 가져오기\n",
|
||||
"\n",
|
||||
"다음으로, [가족 온톨로지](https://raw.githubusercontent.com/blokhin/genealogical-trees/master/data/header.ttl)를 살펴보겠습니다. 이 온톨로지는 시맨틱 웹 삼중항 집합으로 정의되어 있습니다. 이 온톨로지는 `isUncleOf`, `isCousinOf`와 같은 관계를 비롯해 다양한 관계를 정의합니다. 이러한 모든 관계는 기본 술어인 `isMotherOf`, `isFatherOf`, `isBrotherOf`, `isSisterOf`를 기반으로 정의됩니다. 우리는 이 온톨로지를 사용하여 자동 추론을 통해 다른 모든 관계를 도출할 것입니다.\n",
|
||||
"\n",
|
||||
"다음은 `isAuntOf` 속성의 샘플 정의입니다. 이 속성은 `isSisterOf`와 `isParentOf`의 조합으로 정의됩니다 (*이모/고모는 부모의 자매입니다*).\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"fhkb:isAuntOf a owl:ObjectProperty ;\n",
|
||||
" rdfs:domain fhkb:Woman ;\n",
|
||||
" rdfs:range fhkb:Person ;\n",
|
||||
" owl:propertyChainAxiom ( fhkb:isSisterOf fhkb:isParentOf ) .\n",
|
||||
"```\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {
|
||||
"trusted": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"@prefix fhkb: <http://www.example.com/genealogy.owl#> .\n",
|
||||
"@prefix owl: <http://www.w3.org/2002/07/owl#> .\n",
|
||||
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n",
|
||||
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n",
|
||||
"@prefix xml: <http://www.w3.org/XML/1998/namespace> .\n",
|
||||
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n",
|
||||
"\n",
|
||||
"<http://www.example.com/genealogy.owl#> a owl:Ontology .\n",
|
||||
"\n",
|
||||
"fhkb:DomainEntity a owl:Class .\n",
|
||||
"\n",
|
||||
"fhkb:Man a owl:Class ;\n",
|
||||
" owl:equivalentClass [ a owl:Class ;\n",
|
||||
" owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;\n",
|
||||
" owl:onProperty fhkb:hasSex ;\n",
|
||||
" owl:someValuesFrom fhkb:Male ] ) ] .\n",
|
||||
"\n",
|
||||
"fhkb:Woman a owl:Class ;\n",
|
||||
" owl:equivalentClass [ a owl:Class ;\n",
|
||||
" owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!head -20 data/onto.ttl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 추론을 위한 온톨로지 구성\n",
|
||||
"\n",
|
||||
"간단하게 하기 위해, 가족 온톨로지의 원래 규칙과 GEDCOM 파일에서 가져온 개인에 대한 사실을 포함하는 하나의 온톨로지 파일을 만들 것입니다. GEDCOM 파일을 살펴보며 가족과 개인에 대한 정보를 추출하고, 이를 삼중항으로 변환할 것입니다.\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": [
|
||||
"### 추론 수행하기\n",
|
||||
"\n",
|
||||
"이제 이 온톨로지를 사용하여 추론하고 쿼리를 실행할 수 있기를 원합니다. 우리는 [RDFLib](https://github.com/RDFLib)를 사용할 것입니다. 이 라이브러리는 다양한 형식의 RDF 그래프를 읽고 쿼리하는 등의 작업을 지원합니다.\n",
|
||||
"\n",
|
||||
"논리적 추론을 위해 [OWL-RL](https://github.com/RDFLib/OWL-RL) 라이브러리를 사용할 것입니다. 이 라이브러리는 RDF 그래프의 **Closure**를 구축할 수 있도록 해줍니다. 즉, 추론 가능한 모든 개념과 관계를 추가할 수 있습니다.\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": [
|
||||
"온톨로지 파일을 열어서 몇 개의 삼중항을 포함하고 있는지 확인해 봅시다:\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": [
|
||||
"이제 클로저를 만들어 보고 삼중항의 수가 어떻게 증가하는지 확인해 봅시다:\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": [
|
||||
"### 친척 관계 조회하기\n",
|
||||
"\n",
|
||||
"이제 그래프를 조회하여 사람들 간의 다양한 관계를 확인할 수 있습니다. **SPARQL** 언어와 `query` 메서드를 함께 사용할 수 있습니다. 이번에는 우리 가족 트리에서 모든 **삼촌**을 확인해 보겠습니다:\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": [
|
||||
"다양한 가족 관계를 실험해보세요. 예를 들어, 특정 사람의 모든 조상을 재귀적으로 정의하는 `isAncestorOf` 관계를 살펴볼 수 있습니다.\n",
|
||||
"\n",
|
||||
"마지막으로, 정리해봅시다!\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**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T13:13:38+00:00",
|
||||
"source_file": "lessons/2-Symbolic/FamilyOntology.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -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/)는 인터넷에서 추출된 용어들의 대규모 분류 체계로, 개념 간의 `is-a` 관계를 포함하고 있습니다.\n",
|
||||
"\n",
|
||||
"Context Graph는 두 가지 형태로 제공됩니다:\n",
|
||||
" * 다운로드 가능한 대규모 텍스트 파일\n",
|
||||
" * REST API\n",
|
||||
"\n",
|
||||
"통계:\n",
|
||||
" * 5401933개의 고유 개념\n",
|
||||
" * 12551613개의 고유 인스턴스\n",
|
||||
" * 87603947개의 `is-a` 관계\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 웹 서비스 사용하기\n",
|
||||
"\n",
|
||||
"웹 서비스는 특정 개념이 다양한 그룹에 속할 확률을 추정하기 위한 여러 호출을 제공합니다. 자세한 정보는 [여기](https://concept.research.microsoft.com/Home/Api)에서 확인할 수 있습니다. \n",
|
||||
"다음은 호출을 위한 샘플 URL입니다: `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": [
|
||||
"뉴스 제목을 상위 개념으로 분류해 봅시다. 뉴스 제목을 얻기 위해 [NewsApi.org](http://newsapi.org) 서비스를 사용할 것입니다. 서비스를 사용하려면 자체 API 키를 얻어야 하며, 웹사이트에 방문하여 무료 개발자 플랜에 등록하십시오.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {
|
||||
"trusted": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"newsapi_key = '<your API key here>'\n",
|
||||
"def get_news(country='us'):\n",
|
||||
" res = json.loads(http(\"https://newsapi.org/v2/top-headlines?country={0}&apiKey={1}\".format(country,newsapi_key)))\n",
|
||||
" return res['articles']\n",
|
||||
"\n",
|
||||
"all_titles = [x['title'] for x in get_news('us')+get_news('gb')]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['Covid-19 Live Updates: Vaccines and Boosters News - The New York Times',\n",
|
||||
" 'Ukrainians Flee Mariupol as Russian Forces Push to Take Port City - The Wall Street Journal',\n",
|
||||
" 'Bond Yields Jump, Stock Futures Rise After Powell Says Fed Is Ready to Be More Aggressive - The Wall Street Journal',\n",
|
||||
" 'Putin critic Alexei Navalny found guilty by Russian court - New York Post ',\n",
|
||||
" \"Supreme Court nominee Ketanji Brown Jackson will face questions at confirmation hearing's second day - CNN\",\n",
|
||||
" '2 teachers killed at Swedish high school, student arrested - ABC News',\n",
|
||||
" 'Clues to Covid-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": [
|
||||
"우선, 우리는 뉴스 제목에서 명사를 추출할 수 있기를 원합니다. 이를 위해 `TextBlob` 라이브러리를 사용할 것이며, 이는 이러한 일반적인 NLP 작업을 많이 간소화합니다.\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": [
|
||||
"우리는 명사가 큰 주제 그룹을 제공하지 않는다는 것을 알 수 있습니다. 개념 그래프에서 얻은 더 일반적인 용어로 명사를 대체해 봅시다. 이것은 시간이 좀 걸릴 것입니다. 왜냐하면 각 명사 구에 대해 REST 호출을 수행하고 있기 때문입니다.\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**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T13:14:47+00:00",
|
||||
"source_file": "lessons/2-Symbolic/MSConceptGraph.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,8 @@
|
|||
<!--
|
||||
CO_OP_TRANSLATOR_METADATA:
|
||||
{
|
||||
"original_hash": "7336583e4630220c835335da640016db",
|
||||
"translation_date": "2025-08-24T21:35:11+00:00",
|
||||
"original_hash": "ba5d1eb353d20d3e7181066b3c424b99",
|
||||
"translation_date": "2025-08-31T12:23:58+00:00",
|
||||
"source_file": "lessons/3-NeuralNetworks/03-Perceptron/lab/README.md",
|
||||
"language_code": "ko"
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ CO_OP_TRANSLATOR_METADATA:
|
|||
|
||||
## 과제
|
||||
|
||||
이 강의에서 개발한 MNIST 손글씨 숫자의 이진 분류 코드를 사용하여 모든 숫자를 인식할 수 있는 다중 클래스 분류기를 만드세요. 학습 및 테스트 데이터셋에서 분류 정확도를 계산하고 혼동 행렬(confusion matrix)을 출력하세요.
|
||||
이 수업에서 개발한 MNIST 손글씨 숫자의 이진 분류 코드를 사용하여 모든 숫자를 인식할 수 있는 다중 클래스 분류기를 만드세요. 학습 및 테스트 데이터셋에서 분류 정확도를 계산하고 혼동 행렬(confusion matrix)을 출력하세요.
|
||||
|
||||
## 힌트
|
||||
|
||||
|
|
@ -21,11 +21,13 @@ CO_OP_TRANSLATOR_METADATA:
|
|||
1. 이진 분류를 위해 10개의 서로 다른 퍼셉트론을 학습시키세요 (각 숫자마다 하나씩).
|
||||
1. 입력 숫자를 분류할 수 있는 함수를 정의하세요.
|
||||
|
||||
> **힌트**: 10개의 퍼셉트론의 가중치를 하나의 행렬로 결합하면, 입력 숫자에 대해 한 번의 행렬 곱셈으로 모든 퍼셉트론을 적용할 수 있습니다. 가장 가능성이 높은 숫자는 출력값에 `argmax` 연산을 적용하여 찾을 수 있습니다.
|
||||
> **힌트**: 10개의 퍼셉트론 가중치를 하나의 행렬로 결합하면, 입력 숫자에 대해 한 번의 행렬 곱셈으로 모든 퍼셉트론을 적용할 수 있습니다. 가장 가능성이 높은 숫자는 출력값에 `argmax` 연산을 적용하여 찾을 수 있습니다.
|
||||
|
||||
## 시작 노트북
|
||||
|
||||
[PerceptronMultiClass.ipynb](../../../../../../lessons/3-NeuralNetworks/03-Perceptron/lab/PerceptronMultiClass.ipynb)를 열어 실습을 시작하세요.
|
||||
[PerceptronMultiClass.ipynb](PerceptronMultiClass.ipynb)를 열어 실습을 시작하세요.
|
||||
|
||||
---
|
||||
|
||||
**면책 조항**:
|
||||
이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 권위 있는 출처로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.
|
||||
이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,183 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# MNIST 숫자 분류: 우리만의 프레임워크 사용\n",
|
||||
"\n",
|
||||
"[AI for Beginners Curriculum](https://github.com/microsoft/ai-for-beginners)에서 제공하는 실습 과제입니다.\n",
|
||||
"\n",
|
||||
"### 데이터셋 읽기\n",
|
||||
"\n",
|
||||
"이 코드는 인터넷에 있는 저장소에서 데이터셋을 다운로드합니다. 데이터셋을 직접 복사하려면 AI Curriculum 저장소의 `/data` 디렉토리에서 가져올 수도 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" % Total % Received % Xferd Average Speed Time Time Time Current\n",
|
||||
" Dload Upload Total Spent Left Speed\n",
|
||||
"\n",
|
||||
" 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\n",
|
||||
"100 9.9M 100 9.9M 0 0 9.9M 0 0:00:01 --:--:-- 0:00:01 15.8M\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!rm *.pkl\n",
|
||||
"!wget https://raw.githubusercontent.com/microsoft/AI-For-Beginners/main/data/mnist.pkl.gz\n",
|
||||
"!gzip -d mnist.pkl.gz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pickle\n",
|
||||
"with open('mnist.pkl','rb') as f:\n",
|
||||
" MNIST = pickle.load(f)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"labels = MNIST['Train']['Labels']\n",
|
||||
"data = MNIST['Train']['Features']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리가 가진 데이터의 형태를 확인해 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(42000, 784)"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"data.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 데이터 분할\n",
|
||||
"\n",
|
||||
"Scikit Learn을 사용하여 데이터를 학습 데이터와 테스트 데이터로 분할합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Train samples: 33600, test samples: 8400\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.model_selection import train_test_split\n",
|
||||
"\n",
|
||||
"features_train, features_test, labels_train, labels_test = train_test_split(data,labels,test_size=0.2)\n",
|
||||
"\n",
|
||||
"print(f\"Train samples: {len(features_train)}, test samples: {len(features_test)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 지침\n",
|
||||
"\n",
|
||||
"1. 수업에서 제공된 프레임워크 코드를 이 노트북에 붙여넣거나, (더 좋게는) 별도의 Python 모듈에 저장하세요.\n",
|
||||
"1. 단일 계층 퍼셉트론을 정의하고 훈련하며, 훈련 및 검증 정확도를 관찰하세요.\n",
|
||||
"1. 과적합이 발생했는지 확인하고, 계층 매개변수를 조정하여 정확도를 개선하세요.\n",
|
||||
"1. 이전 단계를 2계층 및 3계층 퍼셉트론에 대해 반복하세요. 계층 간에 다양한 활성화 함수를 실험해 보세요.\n",
|
||||
"1. 다음 질문에 답해 보세요:\n",
|
||||
" - 계층 간 활성화 함수가 네트워크 성능에 영향을 미치나요?\n",
|
||||
" - 이 작업에 2계층 또는 3계층 네트워크가 필요한가요?\n",
|
||||
" - 네트워크를 훈련시키는 동안 문제가 발생했나요? 특히 계층 수가 증가했을 때.\n",
|
||||
" - 훈련 중 네트워크의 가중치는 어떻게 변화하나요? 가중치의 최대 절대값을 에포크에 따라 그래프로 그려 관계를 이해해 보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 신뢰할 수 있는 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.7.4 64-bit (conda)",
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
|
||||
}
|
||||
},
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.5"
|
||||
},
|
||||
"orig_nbformat": 2,
|
||||
"coopTranslator": {
|
||||
"original_hash": "6fa055f484eb5d6bdf41166a356d3abf",
|
||||
"translation_date": "2025-08-31T13:21:33+00:00",
|
||||
"source_file": "lessons/3-NeuralNetworks/04-OwnFramework/lab/MyFW_MNIST.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,102 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**목표**는 Optical Flow를 사용하여 비디오의 어느 부분에서 위/아래/왼쪽/오른쪽 움직임이 발생했는지 확인하는 것입니다.\n",
|
||||
"\n",
|
||||
"강의에서 설명한 대로 비디오 프레임을 가져오는 것부터 시작하세요:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 강의에서 설명한 대로 밀집 광학 흐름 프레임을 계산하고 밀집 광학 흐름을 극좌표로 변환하십시오.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"각 광학 흐름 프레임에 대해 방향 히스토그램을 생성합니다. 히스토그램은 특정 구간에 속하는 벡터의 개수를 보여주며, 프레임에서 서로 다른 움직임 방향을 구분할 수 있도록 해야 합니다.\n",
|
||||
"\n",
|
||||
"> 또한, 크기가 특정 임계값보다 작은 모든 벡터를 0으로 설정하는 것도 고려해볼 수 있습니다. 이렇게 하면 눈이나 머리와 같은 비디오의 작은 추가 움직임을 제거할 수 있습니다.\n",
|
||||
"\n",
|
||||
"일부 프레임에 대한 히스토그램을 그려보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"히스토그램을 보면 이동 방향을 결정하는 방법이 비교적 간단해야 합니다. 특정 임계값을 초과하는 위/아래/왼쪽/오른쪽 방향에 해당하는 빈을 선택해야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"축하합니다! 위의 모든 단계를 완료했다면, 실습을 완료한 것입니다!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"coopTranslator": {
|
||||
"original_hash": "153d9e417e079bf62f8f693002d0deaf",
|
||||
"translation_date": "2025-08-31T12:56:58+00:00",
|
||||
"source_file": "lessons/4-ComputerVision/06-IntroCV/lab/MovementDetection.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,577 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 텍스트 분류 작업\n",
|
||||
"\n",
|
||||
"앞서 언급했듯이, 우리는 **AG_NEWS** 데이터셋을 기반으로 간단한 텍스트 분류 작업에 집중할 것입니다. 이 작업은 뉴스 헤드라인을 세계, 스포츠, 비즈니스, 과학/기술의 4가지 카테고리 중 하나로 분류하는 것입니다.\n",
|
||||
"\n",
|
||||
"## 데이터셋\n",
|
||||
"\n",
|
||||
"이 데이터셋은 [`torchtext`](https://github.com/pytorch/text) 모듈에 내장되어 있어 쉽게 접근할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import os\n",
|
||||
"import collections\n",
|
||||
"os.makedirs('./data',exist_ok=True)\n",
|
||||
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
|
||||
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"여기서 `train_dataset`와 `test_dataset`은 각각 클래스 번호(레이블)와 텍스트 쌍을 반환하는 컬렉션을 포함합니다. 예를 들어:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(3,\n",
|
||||
" \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"list(train_dataset)[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"그래서, 우리의 데이터셋에서 새로운 헤드라인 10개를 출력해 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n",
|
||||
"**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n",
|
||||
"**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n",
|
||||
"**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n",
|
||||
"**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for i,x in zip(range(5),train_dataset):\n",
|
||||
" print(f\"**{classes[x[0]]}** -> {x[1]}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"데이터셋은 반복자이기 때문에 데이터를 여러 번 사용하려면 이를 리스트로 변환해야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
|
||||
"train_dataset = list(train_dataset)\n",
|
||||
"test_dataset = list(test_dataset)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 토크나이제이션\n",
|
||||
"\n",
|
||||
"이제 텍스트를 **숫자**로 변환하여 텐서로 표현해야 합니다. 단어 수준의 표현을 원한다면, 두 가지 작업이 필요합니다:\n",
|
||||
"* 텍스트를 **토큰**으로 나누기 위해 **토크나이저**를 사용합니다.\n",
|
||||
"* 이러한 토큰의 **어휘**를 구축합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['he', 'said', 'hello']"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
|
||||
"tokenizer('He said: hello')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"counter = collections.Counter()\n",
|
||||
"for (label, line) in train_dataset:\n",
|
||||
" counter.update(tokenizer(line))\n",
|
||||
"vocab = torchtext.vocab.vocab(counter, min_freq=1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"어휘를 사용하여 토큰화된 문자열을 숫자 집합으로 쉽게 인코딩할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocab size if 95810\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[599, 3279, 97, 1220, 329, 225, 7368]"
|
||||
]
|
||||
},
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(f\"Vocab size if {vocab_size}\")\n",
|
||||
"\n",
|
||||
"stoi = vocab.get_stoi() # dict to convert tokens to indices\n",
|
||||
"\n",
|
||||
"def encode(x):\n",
|
||||
" return [stoi[s] for s in tokenizer(x)]\n",
|
||||
"\n",
|
||||
"encode('I love to play with my words')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 단어 묶음(Bag of Words) 텍스트 표현\n",
|
||||
"\n",
|
||||
"단어는 의미를 나타내기 때문에, 때로는 문장에서 단어의 순서를 고려하지 않고 개별 단어만 살펴보아도 텍스트의 의미를 파악할 수 있습니다. 예를 들어, 뉴스를 분류할 때 *날씨*, *눈*과 같은 단어는 *일기예보*를 나타낼 가능성이 높고, *주식*, *달러*와 같은 단어는 *금융 뉴스*에 해당할 가능성이 있습니다.\n",
|
||||
"\n",
|
||||
"**단어 묶음**(BoW) 벡터 표현은 가장 일반적으로 사용되는 전통적인 벡터 표현 방식입니다. 각 단어는 벡터의 인덱스에 연결되며, 벡터 요소는 주어진 문서에서 해당 단어가 등장한 횟수를 포함합니다.\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"> **Note**: BoW를 텍스트 내 개별 단어에 대한 모든 원-핫 인코딩 벡터의 합으로 생각할 수도 있습니다.\n",
|
||||
"\n",
|
||||
"아래는 Scikit Learn 파이썬 라이브러리를 사용하여 단어 묶음 표현을 생성하는 방법의 예시입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
||||
"vectorizer = CountVectorizer()\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"vectorizer.fit_transform(corpus)\n",
|
||||
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"AG_NEWS 데이터셋의 벡터 표현에서 bag-of-words 벡터를 계산하려면 다음 함수를 사용할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"tensor([2., 1., 2., ..., 0., 0., 0.])\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = len(vocab)\n",
|
||||
"\n",
|
||||
"def to_bow(text,bow_vocab_size=vocab_size):\n",
|
||||
" res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n",
|
||||
" for i in encode(text):\n",
|
||||
" if i<bow_vocab_size:\n",
|
||||
" res[i] += 1\n",
|
||||
" return res\n",
|
||||
"\n",
|
||||
"print(to_bow(train_dataset[0][1]))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **참고:** 여기서는 기본 어휘 크기를 지정하기 위해 전역 변수 `vocab_size`를 사용합니다. 어휘 크기가 종종 매우 크기 때문에 가장 빈번한 단어로 어휘 크기를 제한할 수 있습니다. `vocab_size` 값을 낮추고 아래 코드를 실행하여 정확도에 어떤 영향을 미치는지 확인해 보세요. 약간의 정확도 저하를 예상할 수 있지만, 성능 향상을 위해 극적인 변화는 없을 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## BoW 분류기 학습하기\n",
|
||||
"\n",
|
||||
"이제 텍스트의 Bag-of-Words 표현을 만드는 방법을 배웠으니, 이를 기반으로 분류기를 학습시켜 봅시다. 먼저, 학습을 위해 데이터셋을 변환해야 합니다. 모든 위치 벡터 표현을 Bag-of-Words 표현으로 변환해야 합니다. 이를 위해 표준 torch `DataLoader`의 `collate_fn` 매개변수에 `bowify` 함수를 전달하면 됩니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from torch.utils.data import DataLoader\n",
|
||||
"import numpy as np \n",
|
||||
"\n",
|
||||
"# this collate function gets list of batch_size tuples, and needs to \n",
|
||||
"# return a pair of label-feature tensors for the whole minibatch\n",
|
||||
"def bowify(b):\n",
|
||||
" return (\n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||||
" torch.stack([to_bow(t[1]) for t in b])\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
|
||||
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 하나의 선형 계층을 포함하는 간단한 분류기 신경망을 정의해봅시다. 입력 벡터의 크기는 `vocab_size`와 같고, 출력 크기는 클래스 수(4)에 해당합니다. 분류 작업을 해결하기 때문에 최종 활성화 함수는 `LogSoftmax()`입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 표준 PyTorch 훈련 루프를 정의하겠습니다. 우리의 데이터셋이 상당히 크기 때문에, 교육 목적으로 우리는 한 에포크만 훈련할 것이며, 때로는 에포크보다 적게 훈련할 수도 있습니다 (`epoch_size` 매개변수를 지정하면 훈련을 제한할 수 있습니다). 또한 훈련 중 누적된 훈련 정확도를 보고할 것이며, 보고 빈도는 `report_freq` 매개변수를 사용하여 지정됩니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
|
||||
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
||||
" net.train()\n",
|
||||
" total_loss,acc,count,i = 0,0,0,0\n",
|
||||
" for labels,features in dataloader:\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" out = net(features)\n",
|
||||
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" total_loss+=loss\n",
|
||||
" _,predicted = torch.max(out,1)\n",
|
||||
" acc+=(predicted==labels).sum()\n",
|
||||
" count+=len(labels)\n",
|
||||
" i+=1\n",
|
||||
" if i%report_freq==0:\n",
|
||||
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
||||
" if epoch_size and count>epoch_size:\n",
|
||||
" break\n",
|
||||
" return total_loss.item()/count, acc.item()/count"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.8028125\n",
|
||||
"6400: acc=0.8371875\n",
|
||||
"9600: acc=0.8534375\n",
|
||||
"12800: acc=0.85765625\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.026090790722161722, 0.8620069296375267)"
|
||||
]
|
||||
},
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_epoch(net,train_loader,epoch_size=15000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## BiGrams, TriGrams 및 N-Grams\n",
|
||||
"\n",
|
||||
"Bag of words 접근법의 한 가지 한계는 일부 단어가 여러 단어로 이루어진 표현의 일부라는 점입니다. 예를 들어, 'hot dog'이라는 단어는 다른 문맥에서 'hot'과 'dog'이라는 단어와 완전히 다른 의미를 가집니다. 만약 'hot'과 'dog'을 항상 동일한 벡터로 표현한다면, 이는 모델을 혼란스럽게 할 수 있습니다.\n",
|
||||
"\n",
|
||||
"이를 해결하기 위해 **N-gram 표현**이 문서 분류 방법에서 자주 사용됩니다. 여기서 각 단어, 두 단어 또는 세 단어의 빈도는 분류기를 학습시키는 데 유용한 특징이 됩니다. 예를 들어, bigram 표현에서는 원래 단어 외에도 모든 단어 쌍을 어휘에 추가합니다.\n",
|
||||
"\n",
|
||||
"아래는 Scikit Learn을 사용하여 bigram bag of word 표현을 생성하는 방법의 예입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocabulary:\n",
|
||||
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"bigram_vectorizer.fit_transform(corpus)\n",
|
||||
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
|
||||
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"N-gram 접근 방식의 주요 단점은 어휘 크기가 매우 빠르게 증가한다는 점입니다. 실제로는 *임베딩*과 같은 차원 축소 기법과 N-gram 표현을 결합해야 하며, 이는 다음 단원에서 다룰 예정입니다.\n",
|
||||
"\n",
|
||||
"**AG News** 데이터셋에서 N-gram 표현을 사용하려면, 특별한 ngram 어휘를 구축해야 합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Bigram vocabulary length = 1308842\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"counter = collections.Counter()\n",
|
||||
"for (label, line) in train_dataset:\n",
|
||||
" l = tokenizer(line)\n",
|
||||
" counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n",
|
||||
" \n",
|
||||
"bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n",
|
||||
"\n",
|
||||
"print(\"Bigram vocabulary length = \",len(bi_vocab))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리는 위의 코드와 동일한 코드를 사용하여 분류기를 훈련시킬 수 있지만, 이는 메모리 효율성이 매우 낮을 것입니다. 다음 단원에서는 임베딩을 사용하여 빅그램 분류기를 훈련시킬 것입니다.\n",
|
||||
"\n",
|
||||
"> **참고:** 텍스트에서 지정된 횟수 이상 발생하는 n그램만 남길 수 있습니다. 이렇게 하면 드문 빅그램이 제외되고 차원이 크게 감소합니다. 이를 위해 `min_freq` 매개변수를 더 높은 값으로 설정하고 어휘 길이의 변화를 관찰하세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 용어 빈도-역문서 빈도 (TF-IDF)\n",
|
||||
"\n",
|
||||
"BoW 표현에서는 단어 자체와 상관없이 단어의 출현이 동일한 가중치를 갖습니다. 하지만 *a*, *in* 같은 자주 등장하는 단어들은 전문 용어에 비해 분류 작업에서 훨씬 덜 중요하다는 것은 명백합니다. 실제로 대부분의 NLP 작업에서는 특정 단어들이 다른 단어들보다 더 중요합니다.\n",
|
||||
"\n",
|
||||
"**TF-IDF**는 **용어 빈도-역문서 빈도**를 의미합니다. 이는 단순히 문서 내 단어의 출현 여부를 0/1로 나타내는 BoW 방식과 달리, 단어 출현 빈도와 관련된 부동소수점 값을 사용하는 방식입니다.\n",
|
||||
"\n",
|
||||
"좀 더 공식적으로, 문서 $j$에서 단어 $i$의 가중치 $w_{ij}$는 다음과 같이 정의됩니다:\n",
|
||||
"$$\n",
|
||||
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
||||
"$$\n",
|
||||
"여기서\n",
|
||||
"* $tf_{ij}$는 $j$에서 $i$가 등장한 횟수, 즉 이전에 본 BoW 값입니다.\n",
|
||||
"* $N$은 컬렉션 내 문서의 총 개수입니다.\n",
|
||||
"* $df_i$는 컬렉션 전체에서 단어 $i$를 포함하는 문서의 개수입니다.\n",
|
||||
"\n",
|
||||
"TF-IDF 값 $w_{ij}$는 단어가 문서에서 등장하는 횟수에 비례하여 증가하며, 해당 단어를 포함하는 말뭉치 내 문서 수에 따라 조정됩니다. 이는 일부 단어가 다른 단어보다 더 자주 등장하는 사실을 보정하는 데 도움을 줍니다. 예를 들어, 특정 단어가 컬렉션의 *모든* 문서에 등장한다면, $df_i=N$이 되고 $w_{ij}=0$이 되어, 해당 단어는 완전히 무시됩니다.\n",
|
||||
"\n",
|
||||
"Scikit Learn을 사용하면 텍스트의 TF-IDF 벡터화를 쉽게 생성할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
|
||||
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. ]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
|
||||
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
|
||||
"vectorizer.fit_transform(corpus)\n",
|
||||
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 결론\n",
|
||||
"\n",
|
||||
"TF-IDF 표현이 다양한 단어에 빈도 가중치를 부여하더라도, 의미나 순서를 표현할 수는 없습니다. 유명한 언어학자 J. R. Firth가 1935년에 말했듯이, “단어의 완전한 의미는 항상 문맥적이며, 문맥을 제외한 의미 연구는 진지하게 받아들일 수 없다.” 이후 강의에서 언어 모델링을 사용하여 텍스트에서 문맥 정보를 포착하는 방법을 배우게 될 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "7b9040985e748e4e2d4c689892456ad7",
|
||||
"translation_date": "2025-08-31T14:09:03+00:00",
|
||||
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,647 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 텍스트 분류 작업\n",
|
||||
"\n",
|
||||
"이 모듈에서는 **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)** 데이터셋을 기반으로 간단한 텍스트 분류 작업을 시작합니다. 우리는 뉴스 헤드라인을 세계, 스포츠, 비즈니스, 과학/기술 중 하나의 카테고리로 분류할 것입니다.\n",
|
||||
"\n",
|
||||
"## 데이터셋\n",
|
||||
"\n",
|
||||
"데이터셋을 로드하기 위해 **[TensorFlow Datasets](https://www.tensorflow.org/datasets)** API를 사용할 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"\n",
|
||||
"# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n",
|
||||
"# we will set tensorflow option to grow GPU memory allocation when required.\n",
|
||||
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
||||
"if len(physical_devices)>0:\n",
|
||||
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
||||
"\n",
|
||||
"dataset = tfds.load('ag_news_subset')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 `dataset['train']` 및 `dataset['test']`를 사용하여 데이터셋의 학습 및 테스트 부분에 각각 접근할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Length of train dataset = 120000\n",
|
||||
"Length of test dataset = 7600\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ds_train = dataset['train']\n",
|
||||
"ds_test = dataset['test']\n",
|
||||
"\n",
|
||||
"print(f\"Length of train dataset = {len(ds_train)}\")\n",
|
||||
"print(f\"Length of test dataset = {len(ds_test)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리의 데이터셋에서 새로운 헤드라인 10개를 출력해 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n",
|
||||
"1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n",
|
||||
"2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n",
|
||||
"3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n",
|
||||
"1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
||||
"\n",
|
||||
"for i,x in zip(range(5),ds_train):\n",
|
||||
" print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 텍스트 벡터화\n",
|
||||
"\n",
|
||||
"이제 텍스트를 **숫자**로 변환하여 텐서로 표현해야 합니다. 단어 수준의 표현을 원한다면, 두 가지 작업이 필요합니다:\n",
|
||||
"\n",
|
||||
"* **토크나이저**를 사용하여 텍스트를 **토큰**으로 분리합니다.\n",
|
||||
"* 이러한 토큰의 **어휘집**을 만듭니다.\n",
|
||||
"\n",
|
||||
"### 어휘 크기 제한하기\n",
|
||||
"\n",
|
||||
"AG News 데이터셋 예제를 보면, 어휘 크기가 상당히 큽니다. 10만 개 이상의 단어가 포함되어 있습니다. 일반적으로 텍스트에 거의 등장하지 않는 단어는 필요하지 않습니다. 이런 단어들은 몇 문장에만 나타나며, 모델이 학습하는 데 큰 도움이 되지 않습니다. 따라서 벡터화 생성자에 인자를 전달하여 어휘 크기를 더 작은 숫자로 제한하는 것이 합리적입니다.\n",
|
||||
"\n",
|
||||
"이 두 단계는 모두 **TextVectorization** 레이어를 사용하여 처리할 수 있습니다. 이제 벡터화 객체를 생성한 다음, `adapt` 메서드를 호출하여 모든 텍스트를 처리하고 어휘집을 만들어 보겠습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vocab_size = 50000\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n",
|
||||
"vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **참고** 우리는 전체 데이터셋의 일부만 사용하여 어휘를 구축하고 있습니다. 이렇게 하면 실행 시간을 단축시켜 기다리는 시간을 줄일 수 있습니다. 하지만 전체 데이터셋의 일부 단어가 어휘에 포함되지 않아 학습 중 무시될 위험이 있습니다. 따라서 `adapt` 과정에서 전체 어휘 크기를 사용하고 모든 데이터셋을 처리하면 최종 정확도가 약간 향상될 수 있지만, 그 차이는 크지 않을 것입니다.\n",
|
||||
"\n",
|
||||
"이제 실제 어휘에 접근할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n",
|
||||
"Length of vocabulary: 5335\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab = vectorizer.get_vocabulary()\n",
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(vocab[:10])\n",
|
||||
"print(f\"Length of vocabulary: {vocab_size}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"벡터라이저를 사용하여 우리는 텍스트를 숫자 집합으로 쉽게 인코딩할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vectorizer('I love to play with my words')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Bag-of-words 텍스트 표현\n",
|
||||
"\n",
|
||||
"단어는 의미를 나타내기 때문에, 때로는 문장에서 단어의 순서를 고려하지 않고 개별 단어만을 살펴보는 것으로 텍스트의 의미를 파악할 수 있습니다. 예를 들어, 뉴스 분류를 할 때 *weather*와 *snow* 같은 단어는 *날씨 예보*를 나타낼 가능성이 높고, *stocks*와 *dollar* 같은 단어는 *금융 뉴스*에 해당할 것입니다.\n",
|
||||
"\n",
|
||||
"**Bag-of-words** (BoW) 벡터 표현은 가장 이해하기 쉬운 전통적인 벡터 표현 방식입니다. 각 단어는 벡터 인덱스에 연결되며, 벡터 요소는 주어진 문서에서 각 단어가 나타난 횟수를 포함합니다.\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"> **Note**: BoW를 텍스트 내 개별 단어에 대한 모든 원-핫 인코딩 벡터의 합으로 생각할 수도 있습니다.\n",
|
||||
"\n",
|
||||
"아래는 Scikit Learn 파이썬 라이브러리를 사용하여 bag-of-words 표현을 생성하는 예제입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
||||
"sc_vectorizer = CountVectorizer()\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"sc_vectorizer.fit_transform(corpus)\n",
|
||||
"sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리는 위에서 정의한 Keras 벡터라이저를 사용하여 각 단어 번호를 원-핫 인코딩으로 변환하고, 그 벡터들을 모두 더할 수도 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def to_bow(text):\n",
|
||||
" return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n",
|
||||
"\n",
|
||||
"to_bow('My dog likes hot dogs on a hot day.').numpy()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **참고**: 결과가 이전 예제와 다르다는 점이 놀라울 수 있습니다. 그 이유는 Keras 예제에서는 벡터의 길이가 전체 AG News 데이터셋에서 생성된 어휘 크기에 해당하지만, Scikit Learn 예제에서는 샘플 텍스트에서 즉석으로 어휘를 생성했기 때문입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## BoW 분류기 학습하기\n",
|
||||
"\n",
|
||||
"이제 텍스트의 bag-of-words 표현을 만드는 방법을 배웠으니, 이를 사용하는 분류기를 학습시켜 봅시다. 먼저, 데이터셋을 bag-of-words 표현으로 변환해야 합니다. 이는 다음과 같이 `map` 함수를 사용하여 수행할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"batch_size = 128\n",
|
||||
"\n",
|
||||
"ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n",
|
||||
"ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 하나의 선형 계층을 포함하는 간단한 분류기 신경망을 정의해 봅시다. 입력 크기는 `vocab_size`이고, 출력 크기는 클래스 수(4)에 해당합니다. 분류 작업을 해결하고 있으므로 최종 활성화 함수는 **softmax**입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c70a947f0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n",
|
||||
"])\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train_bow,validation_data=ds_test_bow)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리가 4개의 클래스를 가지고 있기 때문에, 80% 이상의 정확도는 좋은 결과입니다.\n",
|
||||
"\n",
|
||||
"## 하나의 네트워크로 분류기 학습시키기\n",
|
||||
"\n",
|
||||
"벡터라이저가 Keras 레이어이기 때문에, 이를 포함한 네트워크를 정의하고 처음부터 끝까지 학습시킬 수 있습니다. 이렇게 하면 `map`을 사용해 데이터셋을 벡터화할 필요가 없으며, 원본 데이터셋을 네트워크의 입력으로 바로 전달할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: 여전히 데이터셋의 필드(예: `title`, `description`, `label`)를 딕셔너리에서 튜플로 변환하기 위해 `map`을 적용해야 합니다. 하지만 디스크에서 데이터를 로드할 때 처음부터 필요한 구조를 가진 데이터셋을 생성할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"model\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
" Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
" input_1 (InputLayer) [(None, 1)] 0 \n",
|
||||
" \n",
|
||||
" text_vectorization (TextVec (None, None) 0 \n",
|
||||
" torization) \n",
|
||||
" \n",
|
||||
" tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n",
|
||||
" \n",
|
||||
" tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n",
|
||||
" bda) \n",
|
||||
" \n",
|
||||
" dense_2 (Dense) (None, 4) 21344 \n",
|
||||
" \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 21,344\n",
|
||||
"Trainable params: 21,344\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n",
|
||||
"938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c721521f0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"inp = keras.Input(shape=(1,),dtype=tf.string)\n",
|
||||
"x = vectorizer(inp)\n",
|
||||
"x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n",
|
||||
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
||||
"model = keras.models.Model(inp,out)\n",
|
||||
"model.summary()\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 바이그램, 트라이그램 및 n-그램\n",
|
||||
"\n",
|
||||
"Bag-of-words 접근법의 한계 중 하나는 일부 단어가 다단어 표현의 일부라는 점입니다. 예를 들어, 'hot dog'이라는 단어는 다른 문맥에서 'hot'과 'dog'이라는 단어와 완전히 다른 의미를 가집니다. 'hot'과 'dog'을 항상 동일한 벡터로 표현하면 모델이 혼란스러워질 수 있습니다.\n",
|
||||
"\n",
|
||||
"이를 해결하기 위해, **n-그램 표현**이 문서 분류 방법에서 자주 사용됩니다. 여기서 각 단어, 두 단어 또는 세 단어의 빈도는 분류기를 학습시키는 데 유용한 특징이 됩니다. 예를 들어, 바이그램 표현에서는 원래 단어 외에도 모든 단어 쌍을 어휘에 추가합니다.\n",
|
||||
"\n",
|
||||
"아래는 Scikit Learn을 사용하여 바이그램 bag-of-words 표현을 생성하는 방법의 예입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocabulary:\n",
|
||||
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"bigram_vectorizer.fit_transform(corpus)\n",
|
||||
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
|
||||
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"n-gram 접근법의 주요 단점은 어휘 크기가 매우 빠르게 증가한다는 점입니다. 실제로는 *임베딩(embeddings)*과 같은 차원 축소 기법과 n-gram 표현을 결합해야 합니다. 이에 대해서는 다음 단원에서 다룰 예정입니다.\n",
|
||||
"\n",
|
||||
"**AG News** 데이터셋에서 n-gram 표현을 사용하려면, `TextVectorization` 생성자에 `ngrams` 매개변수를 전달해야 합니다. 바이그램 어휘의 길이는 **상당히 더 커지며**, 우리의 경우 130만 개 이상의 토큰에 달합니다! 따라서 합리적인 숫자로 바이그램 토큰의 수를 제한하는 것이 타당합니다.\n",
|
||||
"\n",
|
||||
"위와 동일한 코드를 사용하여 분류기를 훈련할 수도 있지만, 이는 메모리 효율성이 매우 낮을 것입니다. 다음 단원에서는 임베딩을 사용하여 바이그램 분류기를 훈련할 것입니다. 그동안 이 노트북에서 바이그램 분류기 훈련을 실험해 보고 더 높은 정확도를 얻을 수 있는지 확인해 보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## BoW 벡터 자동 계산\n",
|
||||
"\n",
|
||||
"위 예제에서는 개별 단어의 원-핫 인코딩을 합산하여 BoW 벡터를 수작업으로 계산했습니다. 하지만 TensorFlow의 최신 버전을 사용하면 벡터라이저 생성자에 `output_mode='count` 매개변수를 전달하여 BoW 벡터를 자동으로 계산할 수 있습니다. 이를 통해 모델을 정의하고 학습시키는 과정이 훨씬 간단해집니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n",
|
||||
"938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c725217c0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n",
|
||||
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
||||
"])\n",
|
||||
"print(\"Training vectorizer\")\n",
|
||||
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 용어 빈도 - 역문서 빈도 (TF-IDF)\n",
|
||||
"\n",
|
||||
"BoW 표현에서는 단어 자체와 상관없이 동일한 방식으로 단어 발생 빈도가 가중치로 사용됩니다. 하지만 *a*나 *in* 같은 자주 등장하는 단어들은 전문 용어에 비해 분류에 훨씬 덜 중요하다는 것은 명백합니다. 대부분의 NLP 작업에서는 특정 단어들이 다른 단어들보다 더 중요합니다.\n",
|
||||
"\n",
|
||||
"**TF-IDF**는 **용어 빈도 - 역문서 빈도**를 의미합니다. 이는 단순히 문서 내 단어의 존재 여부를 0/1로 나타내는 BoW 방식과 달리, 단어 발생 빈도와 관련된 부동 소수점 값을 사용하는 BoW의 변형입니다.\n",
|
||||
"\n",
|
||||
"좀 더 공식적으로, 문서 $j$에서 단어 $i$의 가중치 $w_{ij}$는 다음과 같이 정의됩니다:\n",
|
||||
"$$\n",
|
||||
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
||||
"$$\n",
|
||||
"여기서\n",
|
||||
"* $tf_{ij}$는 $j$에서 $i$가 등장한 횟수, 즉 이전에 본 BoW 값입니다.\n",
|
||||
"* $N$은 컬렉션 내 문서의 총 개수입니다.\n",
|
||||
"* $df_i$는 컬렉션 전체에서 단어 $i$를 포함하는 문서의 개수입니다.\n",
|
||||
"\n",
|
||||
"TF-IDF 값 $w_{ij}$는 단어가 문서에 등장한 횟수에 비례하여 증가하며, 해당 단어를 포함하는 코퍼스 내 문서 수에 따라 조정됩니다. 이는 일부 단어가 다른 단어보다 더 자주 등장하는 사실을 보정하는 데 도움을 줍니다. 예를 들어, 특정 단어가 컬렉션 내 *모든* 문서에 등장한다면, $df_i=N$이 되고 $w_{ij}=0$이 되어 해당 단어는 완전히 무시됩니다.\n",
|
||||
"\n",
|
||||
"Scikit Learn을 사용하면 텍스트의 TF-IDF 벡터화를 쉽게 생성할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
|
||||
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. ]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
|
||||
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
|
||||
"vectorizer.fit_transform(corpus)\n",
|
||||
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Keras에서 `TextVectorization` 레이어는 `output_mode='tf-idf'` 매개변수를 전달하여 TF-IDF 빈도를 자동으로 계산할 수 있습니다. TF-IDF를 사용하면 정확도가 향상되는지 확인하기 위해 위에서 사용한 코드를 반복해 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n",
|
||||
"938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c729dfd30>"
|
||||
]
|
||||
},
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n",
|
||||
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
||||
"])\n",
|
||||
"print(\"Training vectorizer\")\n",
|
||||
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 결론\n",
|
||||
"\n",
|
||||
"TF-IDF 표현이 각 단어에 빈도 가중치를 부여하더라도, 의미나 순서를 표현할 수는 없습니다. 유명한 언어학자 J. R. 퍼스가 1935년에 말했듯이, \"단어의 완전한 의미는 항상 맥락적이며, 맥락을 벗어난 의미 연구는 진지하게 받아들일 수 없다.\" 이후 강의에서 언어 모델링을 사용하여 텍스트에서 맥락 정보를 포착하는 방법을 배우게 될 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||||
},
|
||||
"kernel_info": {
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_tensorflow",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"nteract": {
|
||||
"version": "nteract-front-end@1.0.0"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "19b43951d55b377a76209c24c1f017e4",
|
||||
"translation_date": "2025-08-31T14:11:40+00:00",
|
||||
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,725 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 임베딩\n",
|
||||
"\n",
|
||||
"이전 예제에서는 길이가 `vocab_size`인 고차원 bag-of-words 벡터를 사용했으며, 저차원 위치 표현 벡터를 희소한 원-핫 표현으로 명시적으로 변환했습니다. 하지만 이 원-핫 표현은 메모리 효율적이지 않을 뿐만 아니라, 각 단어가 서로 독립적으로 처리됩니다. 즉, 원-핫 인코딩된 벡터는 단어 간의 의미적 유사성을 전혀 표현하지 못합니다.\n",
|
||||
"\n",
|
||||
"이번 단원에서는 **News AG** 데이터셋을 계속 탐구할 것입니다. 시작하기 위해 데이터를 로드하고 이전 노트북에서 사용한 몇 가지 정의를 가져오겠습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loading dataset...\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
||||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Building vocab...\n",
|
||||
"Vocab size = 95812\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import numpy as np\n",
|
||||
"from torchnlp import *\n",
|
||||
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(\"Vocab size = \",vocab_size)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 임베딩이란 무엇인가?\n",
|
||||
"\n",
|
||||
"**임베딩(embedding)**의 개념은 단어를 낮은 차원의 밀집 벡터로 표현하는 것입니다. 이 벡터는 단어의 의미를 어느 정도 반영합니다. 나중에 의미 있는 단어 임베딩을 만드는 방법에 대해 논의하겠지만, 지금은 임베딩을 단어 벡터의 차원을 줄이는 방법으로 생각해 봅시다.\n",
|
||||
"\n",
|
||||
"임베딩 레이어는 단어를 입력으로 받아 지정된 `embedding_size` 크기의 출력 벡터를 생성합니다. 어떤 면에서는 `Linear` 레이어와 매우 유사하지만, 원-핫 인코딩된 벡터를 입력으로 받는 대신 단어 번호를 입력으로 받을 수 있습니다.\n",
|
||||
"\n",
|
||||
"네트워크의 첫 번째 레이어로 임베딩 레이어를 사용하면, 우리가 사용하는 모델을 **임베딩 백(embedding bag)** 모델로 전환할 수 있습니다. 이 모델에서는 텍스트의 각 단어를 해당 임베딩으로 변환한 다음, `sum`, `average`, `max`와 같은 집계 함수를 사용해 모든 임베딩에 대해 계산을 수행합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"우리의 분류기 신경망은 임베딩 레이어로 시작하여, 그 다음 집계 레이어, 그리고 그 위에 선형 분류기로 구성됩니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class EmbedClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x):\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" x = torch.mean(x,dim=1)\n",
|
||||
" return self.fc(x)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 가변적인 시퀀스 크기 처리\n",
|
||||
"\n",
|
||||
"이 아키텍처의 결과로 인해, 네트워크에 전달할 미니배치를 특정 방식으로 생성해야 합니다. 이전 단원에서 Bag-of-Words를 사용할 때는, 미니배치 내의 모든 BoW 텐서가 텍스트 시퀀스의 실제 길이와 상관없이 `vocab_size`로 동일한 크기를 가졌습니다. 하지만 단어 임베딩으로 전환하면, 각 텍스트 샘플에 포함된 단어 수가 달라지게 됩니다. 이러한 샘플들을 미니배치로 결합할 때는 패딩을 적용해야 합니다.\n",
|
||||
"\n",
|
||||
"이 작업은 데이터 소스에 `collate_fn` 함수를 제공하는 동일한 기술을 사용하여 수행할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def padify(b):\n",
|
||||
" # b is the list of tuples of length batch_size\n",
|
||||
" # - first element of a tuple = label, \n",
|
||||
" # - second = feature (text sequence)\n",
|
||||
" # build vectorized sequence\n",
|
||||
" v = [encode(x[1]) for x in b]\n",
|
||||
" # first, compute max length of a sequence in this minibatch\n",
|
||||
" l = max(map(len,v))\n",
|
||||
" return ( # tuple of two tensors - labels and features\n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||||
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v])\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 임베딩 분류기 학습\n",
|
||||
"\n",
|
||||
"적절한 데이터 로더를 정의했으니, 이전 단원에서 정의한 학습 함수를 사용하여 모델을 학습시킬 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6415625\n",
|
||||
"6400: acc=0.6865625\n",
|
||||
"9600: acc=0.7103125\n",
|
||||
"12800: acc=0.726953125\n",
|
||||
"16000: acc=0.739375\n",
|
||||
"19200: acc=0.75046875\n",
|
||||
"22400: acc=0.7572321428571429\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.889799795315499, 0.7623160588611644)"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||||
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **참고**: 여기서는 시간 절약을 위해 25,000개의 레코드(전체 에포크보다 적음)만 학습하지만, 여러 에포크 동안 학습을 계속하고 학습률 매개변수를 실험하여 더 높은 정확도를 달성할 수 있습니다. 약 90%의 정확도에 도달할 수 있어야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### EmbeddingBag 레이어와 가변 길이 시퀀스 표현\n",
|
||||
"\n",
|
||||
"이전 아키텍처에서는 미니배치에 맞추기 위해 모든 시퀀스를 동일한 길이로 패딩해야 했습니다. 이는 가변 길이 시퀀스를 표현하는 가장 효율적인 방법은 아닙니다. 다른 접근법으로는 **offset** 벡터를 사용하는 것이 있습니다. 이 벡터는 하나의 큰 벡터에 저장된 모든 시퀀스의 오프셋을 포함합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"> **Note**: 위 그림에서는 문자 시퀀스를 보여주고 있지만, 우리의 예제에서는 단어 시퀀스를 다룹니다. 그러나 오프셋 벡터로 시퀀스를 표현하는 일반적인 원리는 동일합니다.\n",
|
||||
"\n",
|
||||
"오프셋 표현을 사용하기 위해 [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html) 레이어를 사용합니다. 이 레이어는 `Embedding`과 유사하지만, 콘텐츠 벡터와 오프셋 벡터를 입력으로 받으며, 평균화 레이어를 포함합니다. 이 평균화는 `mean`, `sum`, 또는 `max`로 설정할 수 있습니다.\n",
|
||||
"\n",
|
||||
"다음은 `EmbeddingBag`을 사용하는 수정된 네트워크입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class EmbedClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
||||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, text, off):\n",
|
||||
" x = self.embedding(text, off)\n",
|
||||
" return self.fc(x)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"훈련을 위한 데이터셋을 준비하려면 오프셋 벡터를 준비할 변환 함수를 제공해야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def offsetify(b):\n",
|
||||
" # first, compute data tensor from all sequences\n",
|
||||
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
||||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||||
" o = [0] + [len(t) for t in x]\n",
|
||||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||||
" return ( \n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||||
" torch.cat(x), # text \n",
|
||||
" o\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이전에 나온 모든 예제와 달리, 이제 우리의 네트워크는 서로 다른 크기의 데이터 벡터와 오프셋 벡터라는 두 개의 매개변수를 받습니다. 마찬가지로, 우리의 데이터 로더도 2개 대신 3개의 값을 제공합니다: 텍스트와 오프셋 벡터가 모두 특징으로 제공됩니다. 따라서, 이를 처리하기 위해 우리의 훈련 함수를 약간 조정해야 합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6153125\n",
|
||||
"6400: acc=0.6615625\n",
|
||||
"9600: acc=0.6932291666666667\n",
|
||||
"12800: acc=0.715078125\n",
|
||||
"16000: acc=0.7270625\n",
|
||||
"19200: acc=0.7382291666666667\n",
|
||||
"22400: acc=0.7486160714285715\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(22.771553103007037, 0.7551983365323096)"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||||
"\n",
|
||||
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
||||
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
||||
" loss_fn = loss_fn.to(device)\n",
|
||||
" net.train()\n",
|
||||
" total_loss,acc,count,i = 0,0,0,0\n",
|
||||
" for labels,text,off in dataloader:\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
||||
" out = net(text, off)\n",
|
||||
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" total_loss+=loss\n",
|
||||
" _,predicted = torch.max(out,1)\n",
|
||||
" acc+=(predicted==labels).sum()\n",
|
||||
" count+=len(labels)\n",
|
||||
" i+=1\n",
|
||||
" if i%report_freq==0:\n",
|
||||
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
||||
" if epoch_size and count>epoch_size:\n",
|
||||
" break\n",
|
||||
" return total_loss.item()/count, acc.item()/count\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 시맨틱 임베딩: Word2Vec\n",
|
||||
"\n",
|
||||
"이전 예제에서 모델의 임베딩 레이어는 단어를 벡터 표현으로 매핑하는 방법을 학습했지만, 이 표현은 의미론적 의미가 많지 않았습니다. 비슷한 단어나 동의어가 특정 벡터 거리(예: 유클리드 거리) 측면에서 서로 가까운 벡터로 대응되는 벡터 표현을 학습할 수 있다면 좋을 것입니다.\n",
|
||||
"\n",
|
||||
"이를 위해서는 특정 방식으로 대규모 텍스트 컬렉션에서 임베딩 모델을 사전 학습해야 합니다. 시맨틱 임베딩을 학습하는 초기 방법 중 하나는 [Word2Vec](https://en.wikipedia.org/wiki/Word2vec)이라고 불립니다. 이는 단어의 분산 표현을 생성하기 위해 사용되는 두 가지 주요 아키텍처를 기반으로 합니다:\n",
|
||||
"\n",
|
||||
" - **연속적 Bag-of-Words** (CBoW) — 이 아키텍처에서는 주변 문맥으로부터 단어를 예측하도록 모델을 학습시킵니다. n그램 $(W_{-2},W_{-1},W_0,W_1,W_2)$가 주어졌을 때, 모델의 목표는 $(W_{-2},W_{-1},W_1,W_2)$로부터 $W_0$를 예측하는 것입니다.\n",
|
||||
" - **연속적 Skip-Gram** — CBoW와 반대입니다. 이 모델은 현재 단어를 예측하기 위해 주변 문맥 단어의 윈도우를 사용합니다.\n",
|
||||
"\n",
|
||||
"CBoW는 더 빠르지만, Skip-Gram은 더 느리며 드문 단어를 표현하는 데 더 효과적입니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Google News 데이터셋에서 사전 학습된 Word2Vec 임베딩을 실험하려면 **gensim** 라이브러리를 사용할 수 있습니다. 아래는 'neural'과 가장 유사한 단어를 찾는 예제입니다.\n",
|
||||
"\n",
|
||||
"> **Note:** 처음으로 단어 벡터를 생성할 때, 다운로드하는 데 시간이 걸릴 수 있습니다!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gensim.downloader as api\n",
|
||||
"w2v = api.load('word2vec-google-news-300')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"neuronal -> 0.7804799675941467\n",
|
||||
"neurons -> 0.7326500415802002\n",
|
||||
"neural_circuits -> 0.7252851724624634\n",
|
||||
"neuron -> 0.7174385190010071\n",
|
||||
"cortical -> 0.6941086649894714\n",
|
||||
"brain_circuitry -> 0.6923246383666992\n",
|
||||
"synaptic -> 0.6699118614196777\n",
|
||||
"neural_circuitry -> 0.6638563275337219\n",
|
||||
"neurochemical -> 0.6555314064025879\n",
|
||||
"neuronal_activity -> 0.6531826257705688\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for w,p in w2v.most_similar('neural'):\n",
|
||||
" print(f\"{w} -> {p}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리는 단어로부터 벡터 임베딩을 계산하여 분류 모델 훈련에 사용할 수 있습니다 (명확성을 위해 벡터의 첫 20개 구성 요소만 표시합니다):\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
||||
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
||||
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
||||
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
||||
" dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v.word_vec('play')[:20]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"문장의 임베딩의 훌륭한 점은 벡터 인코딩을 조작하여 의미를 변경할 수 있다는 것입니다. 예를 들어, 우리는 *king*과 *woman*이라는 단어에 최대한 가까우면서 *man*이라는 단어와는 최대한 멀리 떨어진 벡터 표현을 가진 단어를 찾을 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"('queen', 0.7118192911148071)"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"CBoW와 Skip-Grams는 모두 \"예측 기반\" 임베딩으로, 로컬 컨텍스트만을 고려합니다. Word2Vec은 글로벌 컨텍스트를 활용하지 않습니다.\n",
|
||||
"\n",
|
||||
"**FastText**는 Word2Vec을 기반으로 각 단어와 단어 내에서 발견되는 문자 n-그램에 대한 벡터 표현을 학습합니다. 이 표현 값들은 각 학습 단계에서 하나의 벡터로 평균화됩니다. 이는 사전 학습에 많은 추가 계산을 요구하지만, 단어 임베딩이 서브워드 정보를 인코딩할 수 있도록 합니다.\n",
|
||||
"\n",
|
||||
"또 다른 방법인 **GloVe**는 공기행렬(co-occurrence matrix)의 아이디어를 활용하며, 공기행렬을 더 표현력 있고 비선형적인 단어 벡터로 분해하기 위해 신경망 방법을 사용합니다.\n",
|
||||
"\n",
|
||||
"gensim은 여러 가지 단어 임베딩 모델을 지원하므로, FastText와 GloVe로 임베딩을 변경하여 예제를 실험해볼 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## PyTorch에서 사전 학습된 임베딩 사용하기\n",
|
||||
"\n",
|
||||
"위의 예제를 수정하여 임베딩 레이어의 행렬을 Word2Vec과 같은 의미적 임베딩으로 미리 채울 수 있습니다. 사전 학습된 임베딩의 어휘와 우리의 텍스트 코퍼스의 어휘가 일치하지 않을 가능성이 높으므로, 누락된 단어에 대한 가중치는 랜덤 값으로 초기화해야 합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Embedding size: 300\n",
|
||||
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embed_size = len(w2v.get_vector('hello'))\n",
|
||||
"print(f'Embedding size: {embed_size}')\n",
|
||||
"\n",
|
||||
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
||||
"\n",
|
||||
"print('Populating matrix, this will take some time...',end='')\n",
|
||||
"found, not_found = 0,0\n",
|
||||
"for i,w in enumerate(vocab.get_itos()):\n",
|
||||
" try:\n",
|
||||
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
||||
" found+=1\n",
|
||||
" except:\n",
|
||||
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
||||
" not_found+=1\n",
|
||||
"\n",
|
||||
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
||||
"net = net.to(device)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 모델을 훈련시켜 봅시다. 모델을 훈련시키는 데 걸리는 시간이 이전 예제보다 훨씬 더 길다는 점에 유의하세요. 이는 더 큰 임베딩 레이어 크기와 훨씬 더 많은 매개변수 때문입니다. 또한, 이러한 이유로 과적합을 피하려면 더 많은 예제에서 모델을 훈련시켜야 할 수도 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6359375\n",
|
||||
"6400: acc=0.68109375\n",
|
||||
"9600: acc=0.7067708333333333\n",
|
||||
"12800: acc=0.723671875\n",
|
||||
"16000: acc=0.73625\n",
|
||||
"19200: acc=0.7463541666666667\n",
|
||||
"22400: acc=0.7560714285714286\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(214.1013875559821, 0.7626759436980166)"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리의 경우, 정확도가 크게 향상되지 않는 것을 볼 수 있는데, 이는 아마도 상당히 다른 어휘 때문일 가능성이 높습니다. \n",
|
||||
"서로 다른 어휘 문제를 해결하기 위해 다음과 같은 방법 중 하나를 사용할 수 있습니다: \n",
|
||||
"* 우리의 어휘로 word2vec 모델을 다시 학습시키기 \n",
|
||||
"* 사전 학습된 word2vec 모델의 어휘를 사용하여 데이터셋을 로드하기. 데이터셋을 로드할 때 사용할 어휘는 로드 과정에서 지정할 수 있습니다. \n",
|
||||
"\n",
|
||||
"후자의 접근 방식이 더 쉬워 보이는데, 특히 PyTorch의 `torchtext` 프레임워크가 임베딩에 대한 내장 지원을 포함하고 있기 때문입니다. \n",
|
||||
"예를 들어, GloVe 기반 어휘를 다음과 같은 방식으로 인스턴스화할 수 있습니다: \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"로드된 어휘는 다음과 같은 기본 작업을 제공합니다:\n",
|
||||
"* `vocab.stoi` 사전은 단어를 사전 인덱스로 변환할 수 있도록 해줍니다.\n",
|
||||
"* `vocab.itos`는 반대로 숫자를 단어로 변환합니다.\n",
|
||||
"* `vocab.vectors`는 임베딩 벡터의 배열로, 단어 `s`의 임베딩을 얻으려면 `vocab.vectors[vocab.stoi[s]]`를 사용해야 합니다.\n",
|
||||
"\n",
|
||||
"다음은 임베딩을 조작하여 **kind-man+woman = queen**이라는 방정식을 보여주는 예제입니다 (작동하도록 계수를 약간 조정했습니다):\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'queen'"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# get the vector corresponding to kind-man+woman\n",
|
||||
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
||||
"# find the index of the closest embedding vector \n",
|
||||
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
||||
"min_idx = torch.argmin(d)\n",
|
||||
"# find the corresponding word\n",
|
||||
"vocab.itos[min_idx]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"GloVe 어휘를 사용하여 데이터셋을 인코딩한 후, 해당 임베딩을 사용하여 분류기를 학습시켜야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def offsetify(b):\n",
|
||||
" # first, compute data tensor from all sequences\n",
|
||||
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
||||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||||
" o = [0] + [len(t) for t in x]\n",
|
||||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||||
" return ( \n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||||
" torch.cat(x), # text \n",
|
||||
" o\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"위에서 본 바와 같이, 모든 벡터 임베딩은 `vocab.vectors` 매트릭스에 저장됩니다. 간단한 복사를 통해 임베딩 레이어의 가중치에 이러한 가중치를 로드하는 것이 매우 쉽습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
||||
"net.embedding.weight.data = vocab.vectors\n",
|
||||
"net = net.to(device)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6271875\n",
|
||||
"6400: acc=0.68078125\n",
|
||||
"9600: acc=0.7030208333333333\n",
|
||||
"12800: acc=0.71984375\n",
|
||||
"16000: acc=0.7346875\n",
|
||||
"19200: acc=0.7455729166666667\n",
|
||||
"22400: acc=0.7529464285714286\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(35.53972978646833, 0.7575175943698017)"
|
||||
]
|
||||
},
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
||||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리 데이터셋의 일부 단어가 사전 학습된 GloVe 어휘에 없기 때문에 정확도가 크게 증가하지 않는 이유 중 하나입니다. 따라서 이러한 단어들은 사실상 무시됩니다. 이 문제를 해결하기 위해, 우리는 데이터셋에서 자체 임베딩을 학습시킬 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 문맥적 임베딩\n",
|
||||
"\n",
|
||||
"Word2Vec와 같은 전통적인 사전 학습 임베딩 표현의 주요 한계 중 하나는 단어 의미의 중의성 문제입니다. 사전 학습된 임베딩은 단어의 문맥적 의미를 어느 정도 포착할 수 있지만, 단어의 모든 가능한 의미가 동일한 임베딩에 인코딩됩니다. 이는 'play'와 같은 많은 단어가 사용되는 문맥에 따라 다른 의미를 가지기 때문에, 후속 모델에서 문제를 일으킬 수 있습니다.\n",
|
||||
"\n",
|
||||
"예를 들어, 'play'라는 단어는 다음 두 문장에서 매우 다른 의미를 가집니다:\n",
|
||||
"- 나는 극장에서 **연극**을 봤다.\n",
|
||||
"- 존은 친구들과 **놀고** 싶어한다.\n",
|
||||
"\n",
|
||||
"위의 사전 학습된 임베딩은 'play'라는 단어의 두 가지 의미를 동일한 임베딩으로 표현합니다. 이러한 한계를 극복하기 위해서는 **언어 모델**을 기반으로 한 임베딩을 구축해야 합니다. 언어 모델은 방대한 텍스트 코퍼스에서 학습되며, 단어들이 다양한 문맥에서 어떻게 조합될 수 있는지를 *이해*합니다. 문맥적 임베딩에 대한 논의는 이 튜토리얼의 범위를 벗어나지만, 다음 단원에서 언어 모델을 다룰 때 다시 논의할 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_pytorch",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "f50b026abce5cf36783a560ea72cb9b1",
|
||||
"translation_date": "2025-08-31T14:06:05+00:00",
|
||||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,695 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 임베딩\n",
|
||||
"\n",
|
||||
"이전 예제에서는 `vocab_size` 길이의 고차원 bag-of-words 벡터를 사용했고, 저차원 위치 표현 벡터를 희소한 원-핫 표현으로 명시적으로 변환했습니다. 하지만 이 원-핫 표현은 메모리 효율적이지 않습니다. 게다가 각 단어가 서로 독립적으로 처리되기 때문에 원-핫 인코딩된 벡터는 단어 간의 의미적 유사성을 표현하지 못합니다.\n",
|
||||
"\n",
|
||||
"이번 단원에서는 **News AG** 데이터셋을 계속 탐구할 것입니다. 시작하기 위해 데이터를 로드하고 이전 단원에서 정의를 가져오겠습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 임베딩이란 무엇인가?\n",
|
||||
"\n",
|
||||
"**임베딩**의 아이디어는 단어를 낮은 차원의 밀집 벡터로 표현하여 단어의 의미론적 의미를 반영하는 것입니다. 나중에 의미 있는 단어 임베딩을 구축하는 방법에 대해 논의하겠지만, 지금은 임베딩을 단어 벡터의 차원을 줄이는 방법으로 생각해봅시다.\n",
|
||||
"\n",
|
||||
"임베딩 레이어는 단어를 입력으로 받아 지정된 `embedding_size`의 출력 벡터를 생성합니다. 어느 정도로는 `Dense` 레이어와 매우 유사하지만, 원-핫 인코딩된 벡터를 입력으로 받는 대신 단어 번호를 입력으로 받을 수 있습니다.\n",
|
||||
"\n",
|
||||
"네트워크의 첫 번째 레이어로 임베딩 레이어를 사용하면, bag-of-words 모델에서 **embedding bag** 모델로 전환할 수 있습니다. 여기서 텍스트의 각 단어를 해당 임베딩으로 변환한 후, `sum`, `average`, `max`와 같은 집계 함수를 사용하여 모든 임베딩을 계산합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"우리의 분류기 신경망은 다음 레이어들로 구성됩니다:\n",
|
||||
"\n",
|
||||
"* `TextVectorization` 레이어: 문자열을 입력으로 받아 토큰 번호의 텐서를 생성합니다. 적절한 어휘 크기 `vocab_size`를 지정하고, 자주 사용되지 않는 단어는 무시합니다. 입력 형태는 1이고, 출력 형태는 $n$입니다. 결과적으로 $n$개의 토큰을 얻으며, 각 토큰은 0에서 `vocab_size` 사이의 숫자를 포함합니다.\n",
|
||||
"* `Embedding` 레이어: $n$개의 숫자를 받아 각 숫자를 주어진 길이의 밀집 벡터로 줄입니다 (예: 100). 따라서 $n$ 형태의 입력 텐서는 $n\\times 100$ 형태의 텐서로 변환됩니다.\n",
|
||||
"* 집계 레이어: 첫 번째 축을 따라 이 텐서의 평균을 계산합니다. 즉, 서로 다른 단어에 해당하는 모든 $n$ 입력 텐서의 평균을 계산합니다. 이 레이어를 구현하기 위해 `Lambda` 레이어를 사용하고 평균을 계산하는 함수를 전달합니다. 출력 형태는 100이며, 전체 입력 시퀀스의 수치적 표현이 됩니다.\n",
|
||||
"* 최종 `Dense` 선형 분류기.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
" Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
" text_vectorization (TextVec (None, None) 0 \n",
|
||||
" torization) \n",
|
||||
" \n",
|
||||
" embedding (Embedding) (None, None, 100) 3000000 \n",
|
||||
" \n",
|
||||
" lambda (Lambda) (None, 100) 0 \n",
|
||||
" \n",
|
||||
" dense (Dense) (None, 4) 404 \n",
|
||||
" \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 3,000,404\n",
|
||||
"Trainable params: 3,000,404\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = 30000\n",
|
||||
"batch_size = 128\n",
|
||||
"\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,input_shape=(1,))\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer, \n",
|
||||
" keras.layers.Embedding(vocab_size,100),\n",
|
||||
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
|
||||
" keras.layers.Dense(4, activation='softmax')\n",
|
||||
"])\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"`summary` 출력에서 **output shape** 열의 첫 번째 텐서 차원 `None`은 미니배치 크기를 나타내며, 두 번째 차원은 토큰 시퀀스의 길이를 나타냅니다. 미니배치 내 모든 토큰 시퀀스는 서로 다른 길이를 가지고 있습니다. 다음 섹션에서 이를 처리하는 방법에 대해 논의하겠습니다.\n",
|
||||
"\n",
|
||||
"이제 네트워크를 훈련시켜 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n",
|
||||
"938/938 [==============================] - 20s 20ms/step - loss: 0.7891 - acc: 0.8155 - val_loss: 0.4470 - val_acc: 0.8642\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x22255515100>"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"print(\"Training vectorizer\")\n",
|
||||
"vectorizer.adapt(ds_train.take(500).map(extract_text))\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"> **참고** 우리는 데이터의 일부를 기반으로 벡터라이저를 구축하고 있습니다. 이는 프로세스를 가속화하기 위해 수행되며, 이로 인해 텍스트의 모든 토큰이 어휘에 포함되지 않을 수 있습니다. 이 경우 해당 토큰은 무시되며 약간 낮은 정확도를 초래할 수 있습니다. 그러나 실제로 텍스트의 일부는 종종 좋은 어휘 추정을 제공합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 변수 시퀀스 크기 처리하기\n",
|
||||
"\n",
|
||||
"미니배치에서 훈련이 어떻게 이루어지는지 이해해 봅시다. 위 예시에서 입력 텐서는 차원이 1이고, 128개의 미니배치를 사용하므로 텐서의 실제 크기는 $128 \\times 1$입니다. 하지만 각 문장에 포함된 토큰 수는 서로 다릅니다. `TextVectorization` 레이어를 단일 입력에 적용하면, 텍스트가 어떻게 토큰화되었는지에 따라 반환되는 토큰 수가 달라집니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"tf.Tensor([ 1 45], shape=(2,), dtype=int64)\n",
|
||||
"tf.Tensor([ 112 1271 1 3 1747 158], shape=(6,), dtype=int64)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(vectorizer('Hello, world!'))\n",
|
||||
"print(vectorizer('I am glad to meet you!'))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"그러나 여러 시퀀스에 벡터라이저를 적용할 때 직사각형 모양의 텐서를 생성해야 하므로 사용되지 않은 요소를 PAD 토큰(우리의 경우 0)으로 채웁니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tf.Tensor: shape=(2, 6), dtype=int64, numpy=\n",
|
||||
"array([[ 1, 45, 0, 0, 0, 0],\n",
|
||||
" [ 112, 1271, 1, 3, 1747, 158]], dtype=int64)>"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vectorizer(['Hello, world!','I am glad to meet you!'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"여기에서 임베딩을 볼 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[[ 1.53059261e-02, 6.80514947e-02, 3.14026810e-02, ...,\n",
|
||||
" -8.92002955e-02, 1.52911525e-04, -5.65562584e-02],\n",
|
||||
" [ 2.57456154e-01, 2.79364467e-01, -2.03605562e-01, ...,\n",
|
||||
" -2.07474351e-01, 8.31158683e-02, -2.03911960e-01],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02]],\n",
|
||||
"\n",
|
||||
" [[ 1.89674050e-01, 2.61548996e-01, -3.67433839e-02, ...,\n",
|
||||
" -2.07366899e-01, -1.05442435e-01, -2.36952081e-01],\n",
|
||||
" [ 6.16133213e-02, 1.80511594e-01, 9.77298319e-02, ...,\n",
|
||||
" -5.46628237e-02, -1.07340455e-01, -1.06589928e-01],\n",
|
||||
" [ 1.53059261e-02, 6.80514947e-02, 3.14026810e-02, ...,\n",
|
||||
" -8.92002955e-02, 1.52911525e-04, -5.65562584e-02],\n",
|
||||
" [-4.84890305e-02, -8.41715634e-02, 1.51529670e-01, ...,\n",
|
||||
" 1.28192469e-01, -7.77286515e-02, 1.26041949e-01],\n",
|
||||
" [-4.17212099e-02, -5.60694858e-02, 4.08860669e-02, ...,\n",
|
||||
" 8.70475471e-02, 8.92383084e-02, 1.67974353e-01],\n",
|
||||
" [ 2.85779923e-01, 4.57767487e-01, 4.52292450e-02, ...,\n",
|
||||
" -1.97419018e-01, -2.04659685e-01, -2.79758364e-01]]],\n",
|
||||
" dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.layers[1](vectorizer(['Hello, world!','I am glad to meet you!'])).numpy()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **참고**: 패딩 양을 최소화하기 위해, 경우에 따라 데이터셋의 모든 시퀀스를 길이가 증가하는 순서(더 정확히는 토큰 수)에 따라 정렬하는 것이 합리적일 수 있습니다. 이렇게 하면 각 미니배치가 유사한 길이의 시퀀스를 포함하도록 할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 시맨틱 임베딩: Word2Vec\n",
|
||||
"\n",
|
||||
"이전 예제에서 임베딩 레이어는 단어를 벡터 표현으로 매핑하는 방법을 학습했지만, 이러한 표현은 의미론적 의미를 가지지 않았습니다. 비슷한 단어나 동의어가 어떤 벡터 거리(예: 유클리드 거리)를 기준으로 서로 가까운 벡터에 해당하도록 벡터 표현을 학습할 수 있다면 좋을 것입니다.\n",
|
||||
"\n",
|
||||
"이를 위해, [Word2Vec](https://en.wikipedia.org/wiki/Word2vec)과 같은 기법을 사용하여 대규모 텍스트 컬렉션에서 임베딩 모델을 사전 학습해야 합니다. Word2Vec은 단어의 분산 표현을 생성하는 데 사용되는 두 가지 주요 아키텍처를 기반으로 합니다:\n",
|
||||
"\n",
|
||||
" - **Continuous bag-of-words** (CBoW): 주변 문맥에서 단어를 예측하도록 모델을 학습합니다. n그램 $(W_{-2},W_{-1},W_0,W_1,W_2)$가 주어졌을 때, 모델의 목표는 $(W_{-2},W_{-1},W_1,W_2)$로부터 $W_0$를 예측하는 것입니다.\n",
|
||||
" - **Continuous skip-gram**: CBoW와 반대입니다. 이 모델은 현재 단어를 예측하기 위해 주변 문맥 단어의 윈도우를 사용합니다.\n",
|
||||
"\n",
|
||||
"CBoW는 속도가 빠르며, skip-gram은 더 느리지만 드문 단어를 표현하는 데 더 효과적입니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Google News 데이터셋에서 사전 학습된 Word2Vec 임베딩을 실험하려면 **gensim** 라이브러리를 사용할 수 있습니다. 아래는 'neural'과 가장 유사한 단어를 찾는 예제입니다.\n",
|
||||
"\n",
|
||||
"> **참고:** 처음으로 단어 벡터를 생성할 때, 다운로드에 시간이 걸릴 수 있습니다!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gensim.downloader as api\n",
|
||||
"w2v = api.load('word2vec-google-news-300')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"neuronal -> 0.7804799675941467\n",
|
||||
"neurons -> 0.7326500415802002\n",
|
||||
"neural_circuits -> 0.7252851724624634\n",
|
||||
"neuron -> 0.7174385190010071\n",
|
||||
"cortical -> 0.6941086649894714\n",
|
||||
"brain_circuitry -> 0.6923246383666992\n",
|
||||
"synaptic -> 0.6699118614196777\n",
|
||||
"neural_circuitry -> 0.6638563275337219\n",
|
||||
"neurochemical -> 0.6555314064025879\n",
|
||||
"neuronal_activity -> 0.6531826257705688\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for w,p in w2v.most_similar('neural'):\n",
|
||||
" print(f\"{w} -> {p}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"단어에서 벡터 임베딩을 추출하여 분류 모델 훈련에 사용할 수 있습니다. 임베딩은 300개의 구성 요소를 가지지만, 명확성을 위해 여기서는 벡터의 첫 20개 구성 요소만 표시합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
||||
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
||||
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
||||
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
||||
" dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v['play'][:20]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"의미 임베딩의 훌륭한 점은 의미를 기반으로 벡터 인코딩을 조작할 수 있다는 것입니다. 예를 들어, *king*과 *woman*의 벡터 표현에 최대한 가깝고 *man*이라는 단어에서 최대한 멀리 떨어진 단어를 찾을 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"('queen', 0.7118192911148071)"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"위의 예는 일부 내부 GenSym 마법을 사용하지만, 기본 논리는 실제로 매우 간단합니다. 임베딩에 대한 흥미로운 점은 임베딩 벡터에서 일반적인 벡터 연산을 수행할 수 있으며, 이는 단어 **의미**에 대한 연산을 반영한다는 것입니다. 위의 예는 벡터 연산으로 표현될 수 있습니다: 우리는 **KING-MAN+WOMAN**에 해당하는 벡터를 계산하고(해당 단어의 벡터 표현에서 `+`와 `-` 연산을 수행), 그런 다음 그 벡터에 가장 가까운 단어를 사전에서 찾습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'queen'"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# get the vector corresponding to kind-man+woman\n",
|
||||
"qvec = w2v['king']-1.7*w2v['man']+1.7*w2v['woman']\n",
|
||||
"# find the index of the closest embedding vector \n",
|
||||
"d = np.sum((w2v.vectors-qvec)**2,axis=1)\n",
|
||||
"min_idx = np.argmin(d)\n",
|
||||
"# find the corresponding word\n",
|
||||
"w2v.index_to_key[min_idx]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **NOTE**: 우리는 *man*과 *woman* 벡터에 작은 계수를 추가해야 했습니다. 이를 제거하고 어떤 일이 발생하는지 확인해 보세요.\n",
|
||||
"\n",
|
||||
"가장 가까운 벡터를 찾기 위해, 우리는 TensorFlow의 메커니즘을 사용하여 우리의 벡터와 어휘에 있는 모든 벡터 간의 거리 벡터를 계산한 다음, `argmin`을 사용하여 최소 단어의 인덱스를 찾습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Word2Vec은 단어의 의미를 표현하는 훌륭한 방법처럼 보이지만, 다음과 같은 여러 단점이 있습니다:\n",
|
||||
"\n",
|
||||
"* CBoW와 skip-gram 모델은 **예측 임베딩**으로, 로컬 컨텍스트만 고려합니다. Word2Vec은 글로벌 컨텍스트를 활용하지 않습니다.\n",
|
||||
"* Word2Vec은 단어의 **형태론**을 고려하지 않습니다. 즉, 단어의 의미가 단어의 다른 부분(예: 어근)에 따라 달라질 수 있다는 점을 반영하지 않습니다.\n",
|
||||
"\n",
|
||||
"**FastText**는 두 번째 한계를 극복하려고 시도하며, Word2Vec을 기반으로 각 단어와 단어 내에서 발견되는 문자 n-그램에 대한 벡터 표현을 학습합니다. 그런 다음, 각 학습 단계에서 이러한 표현 값을 하나의 벡터로 평균화합니다. 이는 사전 학습에 많은 추가 계산을 요구하지만, 단어 임베딩이 서브워드 정보를 인코딩할 수 있도록 합니다.\n",
|
||||
"\n",
|
||||
"또 다른 방법인 **GloVe**는 단어 임베딩에 대해 다른 접근 방식을 사용하며, 단어-컨텍스트 행렬의 분해를 기반으로 합니다. 먼저, 다양한 컨텍스트에서 단어가 등장하는 횟수를 세는 큰 행렬을 생성한 후, 이 행렬을 낮은 차원으로 표현하여 재구성 손실을 최소화하려고 합니다.\n",
|
||||
"\n",
|
||||
"gensim 라이브러리는 이러한 단어 임베딩을 지원하며, 위의 모델 로딩 코드를 변경하여 이를 실험해볼 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Keras에서 사전 학습된 임베딩 사용하기\n",
|
||||
"\n",
|
||||
"위의 예제를 수정하여 임베딩 레이어의 행렬을 Word2Vec과 같은 의미론적 임베딩으로 미리 채울 수 있습니다. 사전 학습된 임베딩의 어휘와 텍스트 코퍼스의 어휘는 일치하지 않을 가능성이 높으므로 하나를 선택해야 합니다. 여기서는 두 가지 가능한 옵션을 탐구합니다: 토크나이저 어휘를 사용하는 방법과 Word2Vec 임베딩의 어휘를 사용하는 방법.\n",
|
||||
"\n",
|
||||
"### 토크나이저 어휘 사용하기\n",
|
||||
"\n",
|
||||
"토크나이저 어휘를 사용할 때, 어휘의 일부 단어는 Word2Vec 임베딩과 대응되지만 일부는 누락될 수 있습니다. 우리의 어휘 크기가 `vocab_size`이고 Word2Vec 임베딩 벡터 길이가 `embed_size`일 때, 임베딩 레이어는 `vocab_size`$\\times$`embed_size` 형태의 가중치 행렬로 표현됩니다. 우리는 어휘를 순회하며 이 행렬을 채울 것입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Embedding size: 300\n",
|
||||
"Populating matrix, this will take some time...Done, found 4551 words, 784 words missing\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embed_size = len(w2v.get_vector('hello'))\n",
|
||||
"print(f'Embedding size: {embed_size}')\n",
|
||||
"\n",
|
||||
"vocab = vectorizer.get_vocabulary()\n",
|
||||
"W = np.zeros((vocab_size,embed_size))\n",
|
||||
"print('Populating matrix, this will take some time...',end='')\n",
|
||||
"found, not_found = 0,0\n",
|
||||
"for i,w in enumerate(vocab):\n",
|
||||
" try:\n",
|
||||
" W[i] = w2v.get_vector(w)\n",
|
||||
" found+=1\n",
|
||||
" except:\n",
|
||||
" # W[i] = np.random.normal(0.0,0.3,size=(embed_size,))\n",
|
||||
" not_found+=1\n",
|
||||
"\n",
|
||||
"print(f\"Done, found {found} words, {not_found} words missing\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Word2Vec 어휘에 없는 단어들은 0으로 남겨두거나, 랜덤 벡터를 생성할 수 있습니다.\n",
|
||||
"\n",
|
||||
"이제 사전 학습된 가중치를 사용하여 임베딩 레이어를 정의할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"emb = keras.layers.Embedding(vocab_size,embed_size,weights=[W],trainable=False)\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer, emb,\n",
|
||||
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
|
||||
" keras.layers.Dense(4, activation='softmax')\n",
|
||||
"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"938/938 [==============================] - 10s 10ms/step - loss: 1.1075 - acc: 0.7822 - val_loss: 0.9134 - val_acc: 0.8175\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x2220226ef10>"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
||||
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Note**: `Embedding`을 생성할 때 `trainable=False`로 설정한 것을 주목하세요. 이는 Embedding 레이어를 재학습하지 않는다는 것을 의미합니다. 이로 인해 정확도가 약간 낮아질 수 있지만, 학습 속도는 빨라집니다.\n",
|
||||
"\n",
|
||||
"### 임베딩 어휘 사용하기\n",
|
||||
"\n",
|
||||
"이전 접근 방식의 문제점 중 하나는 TextVectorization과 Embedding에서 사용되는 어휘가 서로 다르다는 점입니다. 이 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있습니다:\n",
|
||||
"* Word2Vec 모델을 우리의 어휘로 재학습합니다.\n",
|
||||
"* 사전 학습된 Word2Vec 모델의 어휘를 사용하여 우리의 데이터셋을 로드합니다. 데이터셋을 로드할 때 사용되는 어휘는 로드 과정에서 지정할 수 있습니다.\n",
|
||||
"\n",
|
||||
"후자의 방법이 더 간단해 보이므로 이를 구현해 보겠습니다. 먼저, Word2Vec 임베딩에서 가져온 지정된 어휘를 사용하여 `TextVectorization` 레이어를 생성하겠습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vocab = list(w2v.vocab.keys())\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(input_shape=(1,))\n",
|
||||
"vectorizer.set_vocabulary(vocab)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"gensim 워드 임베딩 라이브러리에는 `get_keras_embeddings`라는 편리한 함수가 포함되어 있으며, 이를 사용하면 자동으로 해당 Keras 임베딩 레이어를 생성할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Epoch 1/5\n",
|
||||
"938/938 [==============================] - 20s 14ms/step - loss: 1.3377 - acc: 0.4978 - val_loss: 1.2995 - val_acc: 0.5647\n",
|
||||
"Epoch 2/5\n",
|
||||
"938/938 [==============================] - 10s 10ms/step - loss: 1.2587 - acc: 0.5722 - val_loss: 1.2339 - val_acc: 0.5842\n",
|
||||
"Epoch 3/5\n",
|
||||
"938/938 [==============================] - 10s 10ms/step - loss: 1.1980 - acc: 0.5884 - val_loss: 1.1826 - val_acc: 0.5954\n",
|
||||
"Epoch 4/5\n",
|
||||
"938/938 [==============================] - 12s 13ms/step - loss: 1.1503 - acc: 0.6002 - val_loss: 1.1417 - val_acc: 0.6018\n",
|
||||
"Epoch 5/5\n",
|
||||
"938/938 [==============================] - 11s 12ms/step - loss: 1.1120 - acc: 0.6097 - val_loss: 1.1083 - val_acc: 0.6104\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x2220ccb81c0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer, \n",
|
||||
" w2v.get_keras_embedding(train_embeddings=False),\n",
|
||||
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
|
||||
" keras.layers.Dense(4, activation='softmax')\n",
|
||||
"])\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128),epochs=5)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리 데이터셋의 일부 단어가 사전 학습된 GloVe 어휘에 없기 때문에 더 높은 정확도를 보지 못하는 이유 중 하나입니다. 따라서 이러한 단어들은 본질적으로 무시됩니다. 이를 극복하기 위해 우리는 데이터셋을 기반으로 자체 임베딩을 학습할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 문맥적 임베딩\n",
|
||||
"\n",
|
||||
"Word2Vec와 같은 전통적인 사전 학습 임베딩 표현의 주요 한계 중 하나는 단어의 일부 의미를 포착할 수는 있지만, 서로 다른 의미를 구별하지 못한다는 점입니다. 이는 후속 모델에서 문제를 일으킬 수 있습니다.\n",
|
||||
"\n",
|
||||
"예를 들어, 'play'라는 단어는 다음 두 문장에서 서로 다른 의미를 가집니다:\n",
|
||||
"- 나는 극장에서 **연극**을 보았다.\n",
|
||||
"- 존은 친구들과 **놀고** 싶어 한다.\n",
|
||||
"\n",
|
||||
"우리가 언급한 사전 학습 임베딩은 'play'라는 단어의 두 가지 의미를 동일한 임베딩으로 표현합니다. 이러한 한계를 극복하기 위해, 우리는 **언어 모델**을 기반으로 한 임베딩을 구축해야 합니다. 언어 모델은 방대한 텍스트 코퍼스에서 학습되며, 단어들이 서로 다른 문맥에서 어떻게 조합될 수 있는지를 *이해*합니다. 문맥적 임베딩에 대한 논의는 이 튜토리얼의 범위를 벗어나지만, 다음 단원에서 언어 모델을 다룰 때 다시 논의할 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||||
},
|
||||
"kernel_info": {
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_tensorflow",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"nteract": {
|
||||
"version": "nteract-front-end@1.0.0"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "b859482be7f61d1eadc2c6a2720a37e4",
|
||||
"translation_date": "2025-08-31T14:02:41+00:00",
|
||||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsTF.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,576 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "NXTSugt6ieXh"
|
||||
},
|
||||
"source": [
|
||||
"## CBoW 모델 훈련\n",
|
||||
"\n",
|
||||
"이 노트북은 [AI for Beginners Curriculum](http://aka.ms/ai-beginners)의 일부입니다.\n",
|
||||
"\n",
|
||||
"이 예제에서는 CBoW 언어 모델을 훈련하여 자체 Word2Vec 임베딩 공간을 얻는 방법을 살펴보겠습니다. 텍스트 소스로 AG News 데이터셋을 사용할 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import os\n",
|
||||
"import collections\n",
|
||||
"import builtins\n",
|
||||
"import random\n",
|
||||
"import numpy as np"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "q-UiiJUKaxHj"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "TFbR8CZaTZ1q"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"먼저 데이터셋을 로드하고 토크나이저와 어휘를 정의합시다. 계산을 약간 제한하기 위해 `vocab_size`를 5000으로 설정하겠습니다.\n"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "HIwC7lI5T-ov"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def load_dataset(ngrams = 1, min_freq = 1, vocab_size = 5000 , lines_cnt = 500):\n",
|
||||
" tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
|
||||
" print(\"Loading dataset...\")\n",
|
||||
" test_dataset, train_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
|
||||
" train_dataset = list(train_dataset)\n",
|
||||
" test_dataset = list(test_dataset)\n",
|
||||
" classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
||||
" print('Building vocab...')\n",
|
||||
" counter = collections.Counter()\n",
|
||||
" for i, (_, line) in enumerate(train_dataset):\n",
|
||||
" counter.update(torchtext.data.utils.ngrams_iterator(tokenizer(line),ngrams=ngrams))\n",
|
||||
" if i == lines_cnt:\n",
|
||||
" break\n",
|
||||
" vocab = torchtext.vocab.Vocab(collections.Counter(dict(counter.most_common(vocab_size))), min_freq=min_freq)\n",
|
||||
" return train_dataset, test_dataset, classes, vocab, tokenizer"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "wdZuygtgiuLG"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"train_dataset, test_dataset, _, vocab, tokenizer = load_dataset()"
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "4d1nU1gsivGu",
|
||||
"outputId": "949fe272-ae0e-49f5-c373-6703458b3a74"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"Loading dataset...\n",
|
||||
"Building vocab...\n"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def encode(x, vocabulary, tokenizer = tokenizer):\n",
|
||||
" return [vocabulary[s] for s in tokenizer(x)]"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "1XDYNhG8ToFV"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "LIlQk6_PaHVY"
|
||||
},
|
||||
"source": [
|
||||
"## CBoW 모델\n",
|
||||
"\n",
|
||||
"CBoW는 $2N$개의 주변 단어를 기반으로 단어를 예측하는 방법을 학습합니다. 예를 들어, $N=1$일 때, 문장 *I like to train networks*에서 다음과 같은 쌍을 얻을 수 있습니다: (like,I), (I, like), (to, like), (like,to), (train,to), (to, train), (networks, train), (train,networks). 여기서 첫 번째 단어는 입력으로 사용되는 주변 단어이고, 두 번째 단어는 우리가 예측하려는 단어입니다.\n",
|
||||
"\n",
|
||||
"다음 단어를 예측하는 네트워크를 구축하려면, 주변 단어를 입력으로 제공하고 단어 번호를 출력으로 얻어야 합니다. CBoW 네트워크의 구조는 다음과 같습니다:\n",
|
||||
"\n",
|
||||
"* 입력 단어는 임베딩 레이어를 통해 전달됩니다. 이 임베딩 레이어는 우리의 Word2Vec 임베딩이 될 것이며, 따라서 이를 `embedder` 변수로 별도로 정의합니다. 이 예제에서는 임베딩 크기를 30으로 설정하지만, 더 높은 차원으로 실험해볼 수도 있습니다 (실제 Word2Vec은 300 차원입니다).\n",
|
||||
"* 임베딩 벡터는 출력 단어를 예측하는 선형 레이어로 전달됩니다. 따라서 이 레이어는 `vocab_size` 개의 뉴런을 가집니다.\n",
|
||||
"\n",
|
||||
"출력의 경우, 손실 함수로 `CrossEntropyLoss`를 사용한다면, 원핫 인코딩 없이 단어 번호만 예상 결과로 제공해야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"vocab_size = len(vocab)\n",
|
||||
"\n",
|
||||
"embedder = torch.nn.Embedding(num_embeddings = vocab_size, embedding_dim = 30)\n",
|
||||
"model = torch.nn.Sequential(\n",
|
||||
" embedder,\n",
|
||||
" torch.nn.Linear(in_features = 30, out_features = vocab_size),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(model)"
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "akKTcKQKkfl2",
|
||||
"outputId": "da687e3e-a8ec-4c1a-e456-ab8cd6ac7dad"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"Sequential(\n",
|
||||
" (0): Embedding(5002, 30)\n",
|
||||
" (1): Linear(in_features=30, out_features=5002, bias=True)\n",
|
||||
")\n"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "Nud6jgGPaHVa"
|
||||
},
|
||||
"source": [
|
||||
"## 훈련 데이터 준비하기\n",
|
||||
"\n",
|
||||
"이제 텍스트에서 CBoW 단어 쌍을 계산하는 주요 함수를 작성해 봅시다. 이 함수는 윈도우 크기를 지정할 수 있게 해주며, 입력 단어와 출력 단어 쌍의 집합을 반환합니다. 이 함수는 단어뿐만 아니라 벡터나 텐서에도 사용할 수 있습니다. 이를 통해 텍스트를 인코딩한 후 `to_cbow` 함수에 전달할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "x-dsXygOieXn",
|
||||
"outputId": "c2218280-e540-40ba-9546-efe48d0d714f"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"[['like', 'I'], ['to', 'I'], ['I', 'like'], ['to', 'like'], ['train', 'like'], ['I', 'to'], ['like', 'to'], ['train', 'to'], ['networks', 'to'], ['like', 'train'], ['to', 'train'], ['networks', 'train'], ['to', 'networks'], ['train', 'networks']]\n",
|
||||
"[[232, 172], [5, 172], [172, 232], [5, 232], [0, 232], [172, 5], [232, 5], [0, 5], [1202, 5], [232, 0], [5, 0], [1202, 0], [5, 1202], [0, 1202]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def to_cbow(sent,window_size=2):\n",
|
||||
" res = []\n",
|
||||
" for i,x in enumerate(sent):\n",
|
||||
" for j in range(max(0,i-window_size),min(i+window_size+1,len(sent))):\n",
|
||||
" if i!=j:\n",
|
||||
" res.append([sent[j],x])\n",
|
||||
" return res\n",
|
||||
"\n",
|
||||
"print(to_cbow(['I','like','to','train','networks']))\n",
|
||||
"print(to_cbow(encode('I like to train networks', vocab)))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "XVaaDLjaaHVb"
|
||||
},
|
||||
"source": [
|
||||
"훈련 데이터셋을 준비합시다. 모든 뉴스를 살펴보고 `to_cbow`를 호출하여 단어 쌍 목록을 얻은 다음 해당 쌍을 `X`와 `Y`에 추가할 것입니다. 시간 절약을 위해 처음 10k 뉴스 항목만 고려할 것입니다 - 더 많은 시간을 기다릴 수 있고 더 나은 임베딩을 원한다면 이 제한을 쉽게 제거할 수 있습니다 :)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "54b-Gd9TieXo"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"X = []\n",
|
||||
"Y = []\n",
|
||||
"for i, x in zip(range(10000), train_dataset):\n",
|
||||
" for w1, w2 in to_cbow(encode(x[1], vocab), window_size = 5):\n",
|
||||
" X.append(w1)\n",
|
||||
" Y.append(w2)\n",
|
||||
"\n",
|
||||
"X = torch.tensor(X)\n",
|
||||
"Y = torch.tensor(Y)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"우리는 또한 그 데이터를 하나의 데이터셋으로 변환하고, 데이터로더를 생성할 것입니다.\n"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "cwWy0PzXWhN5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"class SimpleIterableDataset(torch.utils.data.IterableDataset):\n",
|
||||
" def __init__(self, X, Y):\n",
|
||||
" super(SimpleIterableDataset).__init__()\n",
|
||||
" self.data = []\n",
|
||||
" for i in range(len(X)):\n",
|
||||
" self.data.append( (Y[i], X[i]) )\n",
|
||||
" random.shuffle(self.data)\n",
|
||||
"\n",
|
||||
" def __iter__(self):\n",
|
||||
" return iter(self.data)"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "mfoAcGPFZU8p"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e4NQ_-5waHVc"
|
||||
},
|
||||
"source": [
|
||||
"우리는 또한 그 데이터를 하나의 데이터셋으로 변환하고, 데이터로더를 생성할 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "AbLUcojlieXo"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ds = SimpleIterableDataset(X, Y)\n",
|
||||
"dl = torch.utils.data.DataLoader(ds, batch_size = 256)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "pKQr7sXeaHVc"
|
||||
},
|
||||
"source": [
|
||||
"이제 실제 훈련을 시작합시다. 우리는 비교적 높은 학습률을 가진 `SGD` 옵티마이저를 사용할 것입니다. 또한 `Adam`과 같은 다른 옵티마이저를 사용해보는 것도 가능합니다. 처음에는 10 에포크 동안 훈련을 진행할 것이며, 더 낮은 손실을 원한다면 이 셀을 다시 실행할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def train_epoch(net, dataloader, lr = 0.01, optimizer = None, loss_fn = torch.nn.CrossEntropyLoss(), epochs = None, report_freq = 1):\n",
|
||||
" optimizer = optimizer or torch.optim.Adam(net.parameters(), lr = lr)\n",
|
||||
" loss_fn = loss_fn.to(device)\n",
|
||||
" net.train()\n",
|
||||
"\n",
|
||||
" for i in range(epochs):\n",
|
||||
" total_loss, j = 0, 0, \n",
|
||||
" for labels, features in dataloader:\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" features, labels = features.to(device), labels.to(device)\n",
|
||||
" out = net(features)\n",
|
||||
" loss = loss_fn(out, labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" total_loss += loss\n",
|
||||
" j += 1\n",
|
||||
" if i % report_freq == 0:\n",
|
||||
" print(f\"Epoch: {i+1}: loss={total_loss.item()/j}\")\n",
|
||||
"\n",
|
||||
" return total_loss.item()/j"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "HeeCYKr_KF1w"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"train_epoch(net = model, dataloader = dl, optimizer = torch.optim.SGD(model.parameters(), lr = 0.1), loss_fn = torch.nn.CrossEntropyLoss(), epochs = 10)"
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "KVgwGtDHgDlT",
|
||||
"outputId": "2447833f-f0e3-4566-c33d-addbfe2f451d"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"Epoch: 1: loss=5.664632366860172\n",
|
||||
"Epoch: 2: loss=5.632101973960962\n",
|
||||
"Epoch: 3: loss=5.610399051405015\n",
|
||||
"Epoch: 4: loss=5.594621561080262\n",
|
||||
"Epoch: 5: loss=5.582538017415446\n",
|
||||
"Epoch: 6: loss=5.572900234519603\n",
|
||||
"Epoch: 7: loss=5.564951676341915\n",
|
||||
"Epoch: 8: loss=5.558288112064614\n",
|
||||
"Epoch: 9: loss=5.552576955031129\n",
|
||||
"Epoch: 10: loss=5.547634165194347\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"5.547634165194347"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "W8u2qXZmaHVd"
|
||||
},
|
||||
"source": [
|
||||
"## Word2Vec 사용해보기\n",
|
||||
"\n",
|
||||
"Word2Vec을 사용하려면, 우리의 어휘에 있는 모든 단어에 해당하는 벡터를 추출해봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "r8TatcXjkU_t"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vectors = torch.stack([embedder(torch.tensor(vocab[s])) for s in vocab.itos], 0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3OcX21UOaHVd"
|
||||
},
|
||||
"source": [
|
||||
"예를 들어, 단어 **Paris**가 벡터로 어떻게 인코딩되는지 살펴보겠습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "bz6tAeLzieXp",
|
||||
"outputId": "5b20850e-4342-45e9-f840-cfac2b4d61d8"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"tensor([-0.0915, 2.1224, -0.0281, -0.6819, 1.1219, 0.6458, -1.3704, -1.3314,\n",
|
||||
" -1.1437, 0.4496, 0.2301, -0.3515, -0.8485, 1.0481, 0.4386, -0.8949,\n",
|
||||
" 0.5644, 1.0939, -2.5096, 3.2949, -0.2601, -0.8640, 0.1421, -0.0804,\n",
|
||||
" -0.5083, -1.0560, 0.9753, -0.5949, -1.6046, 0.5774],\n",
|
||||
" grad_fn=<EmbeddingBackward>)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"paris_vec = embedder(torch.tensor(vocab['paris']))\n",
|
||||
"print(paris_vec)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "pHTJlaeYaHVd"
|
||||
},
|
||||
"source": [
|
||||
"Word2Vec을 사용하여 동의어를 찾는 것은 흥미롭습니다. 다음 함수는 주어진 입력에 대해 가장 가까운 `n`개의 단어를 반환합니다. 이를 찾기 위해, 우리는 $|w_i - v|$의 노름을 계산합니다. 여기서 $v$는 입력 단어에 해당하는 벡터이고, $w_i$는 어휘에서 $i$번째 단어의 인코딩입니다. 그런 다음 배열을 정렬하고 `argsort`를 사용하여 해당 인덱스를 반환하며, 목록의 첫 번째 `n` 요소를 가져옵니다. 이는 어휘에서 가장 가까운 단어의 위치를 인코딩합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "NlZyi-_olFar",
|
||||
"outputId": "b5dbb163-88c4-4d5a-eaf2-6751f700e98c"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['microsoft', 'quoted', 'lp', 'rate', 'top']"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 56
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def close_words(x, n = 5):\n",
|
||||
" vec = embedder(torch.tensor(vocab[x]))\n",
|
||||
" top5 = np.linalg.norm(vectors.detach().numpy() - vec.detach().numpy(), axis = 1).argsort()[:n]\n",
|
||||
" return [ vocab.itos[x] for x in top5 ]\n",
|
||||
"\n",
|
||||
"close_words('microsoft')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "-dQq7xeAln0U",
|
||||
"outputId": "66f768c3-c248-4bfd-ce4f-c8ffc6d0dd0d"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['basketball', 'lot', 'sinai', 'states', 'healthdaynews']"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 51
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"close_words('basketball')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "fJXqK26b29sa",
|
||||
"outputId": "78f0baba-ffd0-485a-dd87-0a12bedfd7fa"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['funds', 'travel', 'sydney', 'japan', 'business']"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 77
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"close_words('funds')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "My0VeTDd3Ji8"
|
||||
},
|
||||
"source": [
|
||||
"## 주요 내용\n",
|
||||
"\n",
|
||||
"CBoW와 같은 기발한 기법을 사용하여 Word2Vec 모델을 훈련시킬 수 있습니다. 중심 단어를 기준으로 주변 단어를 예측하도록 훈련되는 skip-gram 모델을 직접 훈련해 보고, 그 성능이 얼마나 좋은지 확인해 보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 권위 있는 출처로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"collapsed_sections": [],
|
||||
"name": "CBoW-PyTorch.ipynb",
|
||||
"provenance": []
|
||||
},
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"gpuClass": "standard",
|
||||
"coopTranslator": {
|
||||
"original_hash": "36df28efe3fe40b6fb0a7fa48fe3ea82",
|
||||
"translation_date": "2025-08-31T13:45:46+00:00",
|
||||
"source_file": "lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,479 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 순환 신경망 (Recurrent Neural Networks)\n",
|
||||
"\n",
|
||||
"이전 모듈에서는 텍스트의 풍부한 의미 표현을 사용하고, 임베딩 위에 간단한 선형 분류기를 적용했습니다. 이 아키텍처는 문장에서 단어들의 **종합적인 의미**를 포착하지만, 임베딩 위에서의 집계 연산이 원래 텍스트의 **단어 순서** 정보를 제거하기 때문에 단어 순서를 고려하지 않습니다. 이러한 모델은 단어 순서를 모델링할 수 없기 때문에 텍스트 생성이나 질문 응답과 같은 더 복잡하거나 모호한 작업을 해결할 수 없습니다.\n",
|
||||
"\n",
|
||||
"텍스트 시퀀스의 의미를 포착하려면 **순환 신경망**(Recurrent Neural Network, RNN)이라고 불리는 또 다른 신경망 아키텍처를 사용해야 합니다. RNN에서는 문장을 한 번에 하나의 기호씩 네트워크에 통과시키고, 네트워크는 **상태(state)**를 생성합니다. 그런 다음 이 상태를 다음 기호와 함께 다시 네트워크에 전달합니다.\n",
|
||||
"\n",
|
||||
"주어진 토큰 시퀀스 $X_0,\\dots,X_n$에 대해, RNN은 신경망 블록의 시퀀스를 생성하고, 이 시퀀스를 역전파를 통해 끝까지 학습합니다. 각 네트워크 블록은 $(X_i,S_i)$ 쌍을 입력으로 받아 $S_{i+1}$을 결과로 생성합니다. 최종 상태 $S_n$ 또는 출력 $X_n$은 선형 분류기로 전달되어 결과를 생성합니다. 모든 네트워크 블록은 동일한 가중치를 공유하며, 하나의 역전파 과정을 통해 끝까지 학습됩니다.\n",
|
||||
"\n",
|
||||
"상태 벡터 $S_0,\\dots,S_n$이 네트워크를 통해 전달되기 때문에, RNN은 단어 간의 순차적 의존성을 학습할 수 있습니다. 예를 들어, 시퀀스 어딘가에 *not*이라는 단어가 나타날 때, 상태 벡터 내 특정 요소를 부정하도록 학습할 수 있습니다. 이는 부정적인 의미를 반영하는 결과를 가져옵니다.\n",
|
||||
"\n",
|
||||
"> 그림에서 모든 RNN 블록의 가중치가 공유되기 때문에, 동일한 그림을 하나의 블록(오른쪽)으로 표현할 수 있습니다. 이 블록은 순환 피드백 루프를 가지며, 네트워크의 출력 상태를 다시 입력으로 전달합니다.\n",
|
||||
"\n",
|
||||
"이제 순환 신경망이 뉴스 데이터셋 분류에 어떻게 도움을 줄 수 있는지 살펴보겠습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loading dataset...\n",
|
||||
"Building vocab...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"from torchnlp import *\n",
|
||||
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
||||
"vocab_size = len(vocab)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 간단한 RNN 분류기\n",
|
||||
"\n",
|
||||
"간단한 RNN의 경우, 각 순환 유닛은 입력 벡터와 상태 벡터를 결합하여 새로운 상태 벡터를 생성하는 단순 선형 네트워크입니다. PyTorch는 이 유닛을 `RNNCell` 클래스와 같은 셀 네트워크를 `RNN` 레이어로 표현합니다.\n",
|
||||
"\n",
|
||||
"RNN 분류기를 정의하기 위해 먼저 임베딩 레이어를 적용하여 입력 어휘의 차원을 낮추고, 그 위에 RNN 레이어를 추가할 것입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class RNNClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.hidden_dim = hidden_dim\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x):\n",
|
||||
" batch_size = x.size(0)\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" x,h = self.rnn(x)\n",
|
||||
" return self.fc(x.mean(dim=1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Note:** 여기서는 간단함을 위해 학습되지 않은 임베딩 레이어를 사용하지만, 이전 단원에서 설명한 것처럼 Word2Vec 또는 GloVe 임베딩을 사용한 사전 학습된 임베딩 레이어를 활용하면 더 나은 결과를 얻을 수 있습니다. 더 잘 이해하기 위해 이 코드를 사전 학습된 임베딩과 함께 작동하도록 수정해보는 것도 좋습니다.\n",
|
||||
"\n",
|
||||
"이번에는 패딩된 데이터 로더를 사용할 것이며, 각 배치는 동일한 길이의 패딩된 시퀀스를 포함하게 됩니다. RNN 레이어는 임베딩 텐서의 시퀀스를 받아들이고 두 가지 출력을 생성합니다:\n",
|
||||
"* $x$: 각 단계에서 RNN 셀 출력의 시퀀스\n",
|
||||
"* $h$: 시퀀스의 마지막 요소에 대한 최종 은닉 상태\n",
|
||||
"\n",
|
||||
"그 후, 완전 연결된 선형 분류기를 적용하여 클래스 수를 얻습니다.\n",
|
||||
"\n",
|
||||
"> **Note:** RNN은 훈련하기가 상당히 어렵습니다. RNN 셀이 시퀀스 길이에 따라 펼쳐지면, 역전파에 관여하는 레이어 수가 매우 많아지기 때문입니다. 따라서 작은 학습률을 선택하고 더 큰 데이터셋에서 네트워크를 훈련시켜야 좋은 결과를 얻을 수 있습니다. 시간이 오래 걸릴 수 있으므로 GPU를 사용하는 것이 권장됩니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.3090625\n",
|
||||
"6400: acc=0.38921875\n",
|
||||
"9600: acc=0.4590625\n",
|
||||
"12800: acc=0.511953125\n",
|
||||
"16000: acc=0.5506875\n",
|
||||
"19200: acc=0.57921875\n",
|
||||
"22400: acc=0.6070089285714285\n",
|
||||
"25600: acc=0.6304296875\n",
|
||||
"28800: acc=0.6484027777777778\n",
|
||||
"32000: acc=0.66509375\n",
|
||||
"35200: acc=0.6790056818181818\n",
|
||||
"38400: acc=0.6929166666666666\n",
|
||||
"41600: acc=0.7035817307692308\n",
|
||||
"44800: acc=0.7137276785714286\n",
|
||||
"48000: acc=0.72225\n",
|
||||
"51200: acc=0.73001953125\n",
|
||||
"54400: acc=0.7372794117647059\n",
|
||||
"57600: acc=0.7436631944444444\n",
|
||||
"60800: acc=0.7503947368421052\n",
|
||||
"64000: acc=0.75634375\n",
|
||||
"67200: acc=0.7615773809523809\n",
|
||||
"70400: acc=0.7662642045454545\n",
|
||||
"73600: acc=0.7708423913043478\n",
|
||||
"76800: acc=0.7751822916666666\n",
|
||||
"80000: acc=0.7790625\n",
|
||||
"83200: acc=0.7825\n",
|
||||
"86400: acc=0.7858564814814815\n",
|
||||
"89600: acc=0.7890513392857142\n",
|
||||
"92800: acc=0.7920474137931034\n",
|
||||
"96000: acc=0.7952708333333334\n",
|
||||
"99200: acc=0.7982258064516129\n",
|
||||
"102400: acc=0.80099609375\n",
|
||||
"105600: acc=0.8037594696969697\n",
|
||||
"108800: acc=0.8060569852941176\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
|
||||
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
||||
"train_epoch(net,train_loader, lr=0.001)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 장단기 메모리 (LSTM)\n",
|
||||
"\n",
|
||||
"고전적인 RNN의 주요 문제 중 하나는 **기울기 소실** 문제입니다. RNN은 한 번의 역전파 과정에서 끝까지 학습되기 때문에, 네트워크의 첫 번째 레이어로 오류를 전달하는 데 어려움을 겪으며, 결과적으로 네트워크는 먼 토큰 간의 관계를 학습할 수 없습니다. 이 문제를 피하는 방법 중 하나는 **게이트**를 사용하여 **명시적인 상태 관리**를 도입하는 것입니다. 이러한 종류의 가장 잘 알려진 아키텍처는 **장단기 메모리**(LSTM)와 **게이트 릴레이 유닛**(GRU)입니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"LSTM 네트워크는 RNN과 유사한 방식으로 구성되지만, 레이어 간에 전달되는 두 가지 상태가 있습니다: 실제 상태 $c$와 숨겨진 벡터 $h$입니다. 각 유닛에서 숨겨진 벡터 $h_i$는 입력 $x_i$와 연결되며, **게이트**를 통해 상태 $c$에 어떤 일이 발생할지를 제어합니다. 각 게이트는 시그모이드 활성화 함수(출력 범위 $[0,1]$)를 가진 신경망으로, 상태 벡터와 곱해질 때 비트 마스크처럼 작동한다고 생각할 수 있습니다. 위 그림에서 왼쪽에서 오른쪽으로 다음과 같은 게이트가 있습니다:\n",
|
||||
"* **포겟 게이트**는 숨겨진 벡터를 받아 벡터 $c$의 어떤 구성 요소를 잊어야 할지, 어떤 것을 통과시켜야 할지를 결정합니다.\n",
|
||||
"* **입력 게이트**는 입력과 숨겨진 벡터에서 일부 정보를 가져와 상태에 삽입합니다.\n",
|
||||
"* **출력 게이트**는 상태를 $\\tanh$ 활성화가 있는 일부 선형 레이어를 통해 변환한 다음, 숨겨진 벡터 $h_i$를 사용하여 새로운 상태 $c_{i+1}$를 생성할 일부 구성 요소를 선택합니다.\n",
|
||||
"\n",
|
||||
"상태 $c$의 구성 요소는 켜고 끌 수 있는 플래그로 생각할 수 있습니다. 예를 들어, 시퀀스에서 *Alice*라는 이름을 접했을 때, 이를 여성 캐릭터로 간주하고 상태에서 여성 명사가 있다는 플래그를 올릴 수 있습니다. 이후 *and Tom*이라는 구절을 접했을 때, 복수 명사가 있다는 플래그를 올릴 수 있습니다. 따라서 상태를 조작함으로써 문장 부분의 문법적 속성을 추적할 수 있다고 가정할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: LSTM의 내부 구조를 이해하는 데 훌륭한 자료는 Christopher Olah의 [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/)라는 훌륭한 기사입니다.\n",
|
||||
"\n",
|
||||
"LSTM 셀의 내부 구조는 복잡해 보일 수 있지만, PyTorch는 이를 `LSTMCell` 클래스 내부에 숨기고 전체 LSTM 레이어를 나타내는 `LSTM` 객체를 제공합니다. 따라서 LSTM 분류기를 구현하는 것은 위에서 본 간단한 RNN과 매우 유사할 것입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class LSTMClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.hidden_dim = hidden_dim\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
||||
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x):\n",
|
||||
" batch_size = x.size(0)\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" x,(h,c) = self.rnn(x)\n",
|
||||
" return self.fc(h[-1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.259375\n",
|
||||
"6400: acc=0.25859375\n",
|
||||
"9600: acc=0.26177083333333334\n",
|
||||
"12800: acc=0.2784375\n",
|
||||
"16000: acc=0.313\n",
|
||||
"19200: acc=0.3528645833333333\n",
|
||||
"22400: acc=0.3965625\n",
|
||||
"25600: acc=0.4385546875\n",
|
||||
"28800: acc=0.4752777777777778\n",
|
||||
"32000: acc=0.505375\n",
|
||||
"35200: acc=0.5326704545454546\n",
|
||||
"38400: acc=0.5557552083333334\n",
|
||||
"41600: acc=0.5760817307692307\n",
|
||||
"44800: acc=0.5954910714285714\n",
|
||||
"48000: acc=0.6118333333333333\n",
|
||||
"51200: acc=0.62681640625\n",
|
||||
"54400: acc=0.6404779411764706\n",
|
||||
"57600: acc=0.6520138888888889\n",
|
||||
"60800: acc=0.662828947368421\n",
|
||||
"64000: acc=0.673546875\n",
|
||||
"67200: acc=0.6831547619047619\n",
|
||||
"70400: acc=0.6917897727272727\n",
|
||||
"73600: acc=0.6997146739130434\n",
|
||||
"76800: acc=0.707109375\n",
|
||||
"80000: acc=0.714075\n",
|
||||
"83200: acc=0.7209134615384616\n",
|
||||
"86400: acc=0.727037037037037\n",
|
||||
"89600: acc=0.7326674107142858\n",
|
||||
"92800: acc=0.7379633620689655\n",
|
||||
"96000: acc=0.7433645833333333\n",
|
||||
"99200: acc=0.7479032258064516\n",
|
||||
"102400: acc=0.752119140625\n",
|
||||
"105600: acc=0.7562405303030303\n",
|
||||
"108800: acc=0.76015625\n",
|
||||
"112000: acc=0.7641339285714286\n",
|
||||
"115200: acc=0.7677777777777778\n",
|
||||
"118400: acc=0.7711233108108108\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.03487814127604167, 0.7728)"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
||||
"train_epoch(net,train_loader, lr=0.001)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 패킹된 시퀀스\n",
|
||||
"\n",
|
||||
"예제에서는 미니배치의 모든 시퀀스를 0 벡터로 패딩해야 했습니다. 이는 메모리 낭비를 초래할 수 있지만, RNN에서는 패딩된 입력 항목에 대해 추가 RNN 셀을 생성하는 것이 더 중요한 문제입니다. 이러한 셀은 학습에 참여하지만 중요한 입력 정보를 포함하지 않습니다. 실제 시퀀스 크기에 맞춰 RNN을 학습시키는 것이 훨씬 더 효율적입니다.\n",
|
||||
"\n",
|
||||
"이를 위해 PyTorch에서는 패딩된 시퀀스를 저장하는 특별한 형식이 도입되었습니다. 예를 들어, 패딩된 미니배치 입력이 다음과 같다고 가정해봅시다:\n",
|
||||
"```\n",
|
||||
"[[1,2,3,4,5],\n",
|
||||
" [6,7,8,0,0],\n",
|
||||
" [9,0,0,0,0]]\n",
|
||||
"```\n",
|
||||
"여기서 0은 패딩된 값을 나타내며, 입력 시퀀스의 실제 길이 벡터는 `[5,3,1]`입니다.\n",
|
||||
"\n",
|
||||
"패딩된 시퀀스를 효과적으로 학습시키기 위해, 첫 번째 그룹의 RNN 셀을 큰 미니배치(`[1,6,9]`)로 학습을 시작한 후, 세 번째 시퀀스의 처리를 종료하고 짧아진 미니배치(`[2,7]`, `[3,8]`)로 학습을 계속 진행하고 싶습니다. 따라서 패킹된 시퀀스는 하나의 벡터로 표현됩니다. 이 경우 `[1,6,9,2,7,3,8,4,5]`와 길이 벡터(`[5,3,1]`)로 구성되며, 이를 통해 원래의 패딩된 미니배치를 쉽게 복원할 수 있습니다.\n",
|
||||
"\n",
|
||||
"패킹된 시퀀스를 생성하려면 `torch.nn.utils.rnn.pack_padded_sequence` 함수를 사용할 수 있습니다. RNN, LSTM, GRU를 포함한 모든 순환 레이어는 입력으로 패킹된 시퀀스를 지원하며, 패킹된 출력을 생성합니다. 이 출력은 `torch.nn.utils.rnn.pad_packed_sequence`를 사용하여 디코딩할 수 있습니다.\n",
|
||||
"\n",
|
||||
"패킹된 시퀀스를 생성하려면 네트워크에 길이 벡터를 전달해야 하며, 이를 위해 미니배치를 준비하는 다른 함수가 필요합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def pad_length(b):\n",
|
||||
" # build vectorized sequence\n",
|
||||
" v = [encode(x[1]) for x in b]\n",
|
||||
" # compute max length of a sequence in this minibatch and length sequence itself\n",
|
||||
" len_seq = list(map(len,v))\n",
|
||||
" l = max(len_seq)\n",
|
||||
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||||
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
|
||||
" torch.tensor(len_seq)\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"실제 네트워크는 위의 `LSTMClassifier`와 매우 유사하지만, `forward` 패스는 패딩된 미니배치와 시퀀스 길이 벡터를 모두 받습니다. 임베딩을 계산한 후, 패킹된 시퀀스를 계산하고 이를 LSTM 레이어에 전달한 다음, 결과를 다시 언패킹합니다.\n",
|
||||
"\n",
|
||||
"> **Note**: 사실 우리는 언패킹된 결과 `x`를 사용하지 않습니다. 왜냐하면 이후 계산에서 숨겨진 레이어의 출력을 사용하기 때문입니다. 따라서 이 코드에서 언패킹을 완전히 제거할 수 있습니다. 여기에서 언패킹을 포함한 이유는, 만약 네트워크 출력을 추가 계산에서 사용해야 할 경우, 여러분이 이 코드를 쉽게 수정할 수 있도록 하기 위함입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class LSTMPackClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.hidden_dim = hidden_dim\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
||||
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x, lengths):\n",
|
||||
" batch_size = x.size(0)\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
|
||||
" pad_x,(h,c) = self.rnn(pad_x)\n",
|
||||
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
|
||||
" return self.fc(h[-1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.285625\n",
|
||||
"6400: acc=0.33359375\n",
|
||||
"9600: acc=0.3876041666666667\n",
|
||||
"12800: acc=0.44078125\n",
|
||||
"16000: acc=0.4825\n",
|
||||
"19200: acc=0.5235416666666667\n",
|
||||
"22400: acc=0.5559821428571429\n",
|
||||
"25600: acc=0.58609375\n",
|
||||
"28800: acc=0.6116666666666667\n",
|
||||
"32000: acc=0.63340625\n",
|
||||
"35200: acc=0.6525284090909091\n",
|
||||
"38400: acc=0.668515625\n",
|
||||
"41600: acc=0.6822596153846154\n",
|
||||
"44800: acc=0.6948214285714286\n",
|
||||
"48000: acc=0.7052708333333333\n",
|
||||
"51200: acc=0.71521484375\n",
|
||||
"54400: acc=0.7239889705882353\n",
|
||||
"57600: acc=0.7315277777777778\n",
|
||||
"60800: acc=0.7388486842105263\n",
|
||||
"64000: acc=0.74571875\n",
|
||||
"67200: acc=0.7518303571428572\n",
|
||||
"70400: acc=0.7576988636363636\n",
|
||||
"73600: acc=0.7628940217391305\n",
|
||||
"76800: acc=0.7681510416666667\n",
|
||||
"80000: acc=0.7728125\n",
|
||||
"83200: acc=0.7772235576923077\n",
|
||||
"86400: acc=0.7815393518518519\n",
|
||||
"89600: acc=0.7857700892857142\n",
|
||||
"92800: acc=0.7895043103448276\n",
|
||||
"96000: acc=0.7930520833333333\n",
|
||||
"99200: acc=0.7959072580645161\n",
|
||||
"102400: acc=0.798994140625\n",
|
||||
"105600: acc=0.802064393939394\n",
|
||||
"108800: acc=0.8051378676470589\n",
|
||||
"112000: acc=0.8077857142857143\n",
|
||||
"115200: acc=0.8104600694444445\n",
|
||||
"118400: acc=0.8128293918918919\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.029785829671223958, 0.8138166666666666)"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
||||
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **참고:** 훈련 함수에 전달하는 매개변수 `use_pack_sequence`를 확인했을 수 있습니다. 현재 `pack_padded_sequence` 함수는 길이 시퀀스 텐서가 CPU 장치에 있어야 하며, 따라서 훈련 함수는 훈련 중에 길이 시퀀스 데이터를 GPU로 이동하는 것을 피해야 합니다. [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py) 파일의 `train_emb` 함수 구현을 확인할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 양방향 및 다층 RNN\n",
|
||||
"\n",
|
||||
"우리의 예제에서는 모든 순환 신경망이 시퀀스의 시작부터 끝까지 한 방향으로 작동했습니다. 이는 우리가 읽거나 말을 들을 때의 방식과 유사하기 때문에 자연스럽게 보입니다. 하지만, 많은 실제 사례에서 입력 시퀀스에 무작위로 접근할 수 있는 경우가 많으므로, 순환 연산을 양방향으로 실행하는 것이 합리적일 수 있습니다. 이러한 네트워크를 **양방향** RNN이라고 하며, RNN/LSTM/GRU 생성자에 `bidirectional=True` 매개변수를 전달하여 생성할 수 있습니다.\n",
|
||||
"\n",
|
||||
"양방향 네트워크를 다룰 때는 각 방향에 대해 하나씩 두 개의 은닉 상태 벡터가 필요합니다. PyTorch는 이 벡터들을 두 배 크기의 하나의 벡터로 인코딩합니다. 이는 매우 편리한데, 왜냐하면 일반적으로 결과 은닉 상태를 완전 연결 선형 계층에 전달하기 때문입니다. 이때 계층을 생성할 때 크기 증가를 고려하기만 하면 됩니다.\n",
|
||||
"\n",
|
||||
"순환 신경망은 단방향이든 양방향이든 시퀀스 내 특정 패턴을 포착하고 이를 상태 벡터에 저장하거나 출력으로 전달할 수 있습니다. 합성곱 신경망과 마찬가지로, 첫 번째 계층에서 추출한 저수준 패턴을 기반으로 더 높은 수준의 패턴을 포착하기 위해 첫 번째 계층 위에 또 다른 순환 계층을 쌓을 수 있습니다. 이를 **다층 RNN**의 개념이라고 하며, 이는 두 개 이상의 순환 신경망으로 구성되며, 이전 계층의 출력을 다음 계층의 입력으로 전달합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Fernando López의 [이 훌륭한 글](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3)에서 가져온 그림*\n",
|
||||
"\n",
|
||||
"PyTorch는 이러한 네트워크를 구성하는 작업을 매우 쉽게 만들어줍니다. RNN/LSTM/GRU 생성자에 `num_layers` 매개변수를 전달하기만 하면 여러 계층의 순환 구조를 자동으로 생성할 수 있습니다. 이는 또한 은닉/상태 벡터의 크기가 비례적으로 증가함을 의미하며, 순환 계층의 출력을 처리할 때 이를 고려해야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 기타 작업을 위한 RNN\n",
|
||||
"\n",
|
||||
"이번 단원에서는 RNN이 시퀀스 분류에 사용될 수 있다는 것을 배웠습니다. 하지만 실제로는 텍스트 생성, 기계 번역 등 훨씬 더 많은 작업을 처리할 수 있습니다. 이러한 작업들은 다음 단원에서 다룰 예정입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "522ee52ae3d5ae933e283286254e9a55",
|
||||
"translation_date": "2025-08-31T13:59:42+00:00",
|
||||
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 순환 신경망\n",
|
||||
"\n",
|
||||
"이전 모듈에서는 텍스트의 풍부한 의미 표현에 대해 다뤘습니다. 우리가 사용했던 아키텍처는 문장에서 단어들의 집합적인 의미를 포착하지만, 단어들의 **순서**를 고려하지 않습니다. 이는 임베딩 이후의 집계 작업이 원본 텍스트에서 이 정보를 제거하기 때문입니다. 이러한 모델은 단어 순서를 표현할 수 없기 때문에 텍스트 생성이나 질문 응답과 같은 더 복잡하거나 모호한 작업을 해결할 수 없습니다.\n",
|
||||
"\n",
|
||||
"텍스트 시퀀스의 의미를 포착하기 위해 **순환 신경망**(Recurrent Neural Network, RNN)이라는 신경망 아키텍처를 사용합니다. RNN을 사용할 때, 문장을 네트워크에 한 번에 하나의 토큰씩 전달하며, 네트워크는 **상태**를 생성합니다. 이 상태를 다음 토큰과 함께 네트워크에 다시 전달합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"토큰 입력 시퀀스 $X_0,\\dots,X_n$가 주어지면, RNN은 신경망 블록의 시퀀스를 생성하고, 이 시퀀스를 역전파를 통해 끝까지 학습합니다. 각 네트워크 블록은 $(X_i,S_i)$ 쌍을 입력으로 받아들이고, 결과로 $S_{i+1}$을 생성합니다. 최종 상태 $S_n$ 또는 출력 $Y_n$은 선형 분류기로 전달되어 결과를 생성합니다. 모든 네트워크 블록은 동일한 가중치를 공유하며, 하나의 역전파 과정을 통해 끝까지 학습됩니다.\n",
|
||||
"\n",
|
||||
"> 위 그림은 순환 신경망을 펼친 형태(왼쪽)와 더 간결한 순환 표현(오른쪽)으로 보여줍니다. 모든 RNN 셀이 동일한 **공유 가능한 가중치**를 가진다는 점을 이해하는 것이 중요합니다.\n",
|
||||
"\n",
|
||||
"상태 벡터 $S_0,\\dots,S_n$가 네트워크를 통해 전달되기 때문에, RNN은 단어 간의 순차적 의존성을 학습할 수 있습니다. 예를 들어, 시퀀스 어딘가에 *not*이라는 단어가 나타날 때, 상태 벡터 내 특정 요소를 부정하는 방법을 학습할 수 있습니다.\n",
|
||||
"\n",
|
||||
"RNN 셀 내부에는 두 개의 가중치 행렬 $W_H$와 $W_I$, 그리고 편향 $b$가 포함되어 있습니다. 각 RNN 단계에서 입력 $X_i$와 입력 상태 $S_i$가 주어지면, 출력 상태는 $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$로 계산됩니다. 여기서 $f$는 활성화 함수(종종 $\\tanh$)입니다.\n",
|
||||
"\n",
|
||||
"> 텍스트 생성(다음 단원에서 다룰 예정)이나 기계 번역과 같은 문제에서는 각 RNN 단계에서 출력 값을 얻고자 합니다. 이 경우, 또 다른 행렬 $W_O$가 있으며, 출력은 $Y_i=f(W_O\\times S_i+b_O)$로 계산됩니다.\n",
|
||||
"\n",
|
||||
"이제 순환 신경망이 뉴스 데이터셋을 분류하는 데 어떻게 도움을 줄 수 있는지 살펴보겠습니다.\n",
|
||||
"\n",
|
||||
"> 샌드박스 환경에서는 필요한 라이브러리가 설치되고 데이터가 미리 가져와졌는지 확인하기 위해 다음 셀을 실행해야 합니다. 로컬에서 실행 중이라면, 다음 셀을 건너뛸 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
|
||||
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"# We are going to be training pretty large models. In order not to face errors, we need\n",
|
||||
"# to set tensorflow option to grow GPU memory allocation when required\n",
|
||||
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
||||
"if len(physical_devices)>0:\n",
|
||||
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
||||
"\n",
|
||||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"대규모 모델을 훈련할 때 GPU 메모리 할당이 문제가 될 수 있습니다. 또한, 데이터를 GPU 메모리에 맞추면서도 훈련 속도를 충분히 빠르게 유지하기 위해 다양한 미니배치 크기를 실험해볼 필요가 있습니다. 만약 이 코드를 본인의 GPU 머신에서 실행 중이라면, 훈련 속도를 높이기 위해 미니배치 크기를 조정해보는 실험을 할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: 특정 버전의 NVidia 드라이버는 모델 훈련 후에도 메모리를 해제하지 않는 것으로 알려져 있습니다. 이 노트북에서는 여러 예제를 실행하고 있으며, 동일한 노트북에서 실험을 진행할 경우 특정 환경에서 메모리가 소진될 수 있습니다. 모델 훈련을 시작할 때 이상한 오류가 발생한다면, 노트북 커널을 재시작하는 것을 고려해보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"jupyter": {
|
||||
"outputs_hidden": false,
|
||||
"source_hidden": false
|
||||
},
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"batch_size = 16\n",
|
||||
"embed_size = 64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 간단한 RNN 분류기\n",
|
||||
"\n",
|
||||
"간단한 RNN의 경우, 각 순환 유닛은 입력 벡터와 상태 벡터를 받아 새로운 상태 벡터를 생성하는 단순한 선형 네트워크입니다. Keras에서는 이를 `SimpleRNN` 레이어로 표현할 수 있습니다.\n",
|
||||
"\n",
|
||||
"RNN 레이어에 원-핫 인코딩된 토큰을 직접 전달할 수도 있지만, 차원이 너무 높아 비효율적일 수 있습니다. 따라서 단어 벡터의 차원을 줄이기 위해 임베딩 레이어를 사용하고, 그 뒤에 RNN 레이어와 마지막으로 `Dense` 분류기를 추가할 것입니다.\n",
|
||||
"\n",
|
||||
"> **Note**: 차원이 그리 높지 않은 경우, 예를 들어 문자 수준 토큰화를 사용하는 경우에는 원-핫 인코딩된 토큰을 RNN 셀에 직접 전달하는 것이 적합할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
"text_vectorization (TextVect (None, None) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"embedding (Embedding) (None, None, 64) 1280000 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense (Dense) (None, 4) 68 \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 1,281,364\n",
|
||||
"Trainable params: 1,281,364\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = 20000\n",
|
||||
"\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
|
||||
" max_tokens=vocab_size,\n",
|
||||
" input_shape=(1,))\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||||
" keras.layers.SimpleRNN(16),\n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **참고:** 여기서는 간단함을 위해 학습되지 않은 임베딩 레이어를 사용하지만, 더 나은 결과를 위해 이전 단원에서 설명한 Word2Vec을 사용하여 사전 학습된 임베딩 레이어를 사용할 수 있습니다. 사전 학습된 임베딩을 활용하도록 코드를 수정하는 것은 좋은 연습이 될 것입니다.\n",
|
||||
"\n",
|
||||
"이제 RNN을 학습시켜 봅시다. 일반적으로 RNN은 학습시키기가 꽤 어렵습니다. RNN 셀이 시퀀스 길이에 따라 펼쳐지면, 역전파에 관여하는 레이어의 수가 매우 많아지기 때문입니다. 따라서 더 작은 학습률을 선택하고, 더 큰 데이터셋에서 네트워크를 학습시켜야 좋은 결과를 얻을 수 있습니다. 이 과정은 시간이 꽤 오래 걸릴 수 있으므로 GPU를 사용하는 것이 권장됩니다.\n",
|
||||
"\n",
|
||||
"속도를 높이기 위해, 우리는 뉴스 제목만을 사용하여 RNN 모델을 학습시킬 것이며, 설명 부분은 생략할 것입니다. 설명을 포함하여 학습을 시도해보고 모델을 학습시킬 수 있는지 확인해보는 것도 좋습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_title(x):\n",
|
||||
" return x['title']\n",
|
||||
"\n",
|
||||
"def tupelize_title(x):\n",
|
||||
" return (extract_title(x),x['label'])\n",
|
||||
"\n",
|
||||
"print('Training vectorizer')\n",
|
||||
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"> **참고** 뉴스 제목만으로 학습하고 있기 때문에 정확도가 낮을 가능성이 높습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 변수 시퀀스 다시 살펴보기\n",
|
||||
"\n",
|
||||
"`TextVectorization` 레이어는 미니배치 내에서 가변 길이의 시퀀스를 자동으로 패드 토큰으로 채워줍니다. 그런데 이 패드 토큰도 훈련에 참여하게 되며, 이는 모델의 수렴을 복잡하게 만들 수 있습니다.\n",
|
||||
"\n",
|
||||
"패딩의 양을 최소화하기 위해 사용할 수 있는 몇 가지 접근법이 있습니다. 그 중 하나는 데이터셋을 시퀀스 길이에 따라 재정렬하고 모든 시퀀스를 크기별로 그룹화하는 것입니다. 이는 `tf.data.experimental.bucket_by_sequence_length` 함수를 사용하여 수행할 수 있습니다 (참고: [문서](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
|
||||
"\n",
|
||||
"또 다른 접근법은 **마스킹**을 사용하는 것입니다. Keras에서는 일부 레이어가 훈련 시 어떤 토큰을 고려해야 하는지 보여주는 추가 입력을 지원합니다. 모델에 마스킹을 통합하려면 별도의 `Masking` 레이어를 포함시키거나 ([문서](https://keras.io/api/layers/core_layers/masking/)), `Embedding` 레이어의 `mask_zero=True` 매개변수를 지정할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: 이 훈련은 전체 데이터셋에서 한 에포크를 완료하는 데 약 5분 정도 소요됩니다. 인내심이 부족하다면 언제든지 훈련을 중단해도 괜찮습니다. 또한, 훈련에 사용되는 데이터 양을 제한하려면 `ds_train` 및 `ds_test` 데이터셋 뒤에 `.take(...)` 절을 추가할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
|
||||
" keras.layers.SimpleRNN(16),\n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 마스킹을 사용하므로, 제목과 설명 전체 데이터셋으로 모델을 훈련할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: 지금까지 뉴스 제목에 대해 훈련된 벡터라이저를 사용하고, 기사 본문 전체에 대해 훈련하지 않았다는 점을 눈치채셨나요? 이로 인해 일부 토큰이 무시될 가능성이 있습니다. 따라서 벡터라이저를 다시 훈련하는 것이 더 나을 수 있습니다. 하지만 그 영향은 매우 미미할 가능성이 있으므로, 간단함을 위해 이전에 훈련된 벡터라이저를 계속 사용할 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## LSTM: 장단기 메모리\n",
|
||||
"\n",
|
||||
"RNN의 주요 문제 중 하나는 **기울기 소실**입니다. RNN은 상당히 길어질 수 있으며, 역전파 과정에서 네트워크의 첫 번째 레이어까지 기울기를 제대로 전달하기 어려울 수 있습니다. 이런 일이 발생하면 네트워크는 먼 토큰 간의 관계를 학습할 수 없습니다. 이 문제를 피하는 한 가지 방법은 **게이트**를 사용하여 **명시적인 상태 관리**를 도입하는 것입니다. 게이트를 도입하는 가장 일반적인 두 가지 아키텍처는 **장단기 메모리**(LSTM)와 **게이트 순환 유닛**(GRU)입니다. 여기서는 LSTM에 대해 다룹니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"LSTM 네트워크는 RNN과 유사한 방식으로 구성되지만, 레이어 간에 전달되는 두 가지 상태가 있습니다: 실제 상태 $c$와 숨겨진 벡터 $h$입니다. 각 유닛에서 숨겨진 벡터 $h_{t-1}$은 입력 $x_t$와 결합되며, 이 둘이 함께 **게이트**를 통해 상태 $c_t$와 출력 $h_{t}$에 영향을 미칩니다. 각 게이트는 시그모이드 활성화 함수(출력 범위 $[0,1]$)를 가지며, 상태 벡터와 곱해질 때 비트 마스크처럼 작동한다고 생각할 수 있습니다. LSTM에는 다음과 같은 게이트가 있습니다 (위 그림에서 왼쪽에서 오른쪽 순서로):\n",
|
||||
"* **망각 게이트**: 벡터 $c_{t-1}$의 어떤 구성 요소를 잊어야 하고, 어떤 것을 통과시켜야 할지를 결정합니다.\n",
|
||||
"* **입력 게이트**: 입력 벡터와 이전 숨겨진 벡터에서 얼마나 많은 정보를 상태 벡터에 통합할지를 결정합니다.\n",
|
||||
"* **출력 게이트**: 새로운 상태 벡터를 받아 그 구성 요소 중 어떤 것을 사용하여 새로운 숨겨진 벡터 $h_t$를 생성할지를 결정합니다.\n",
|
||||
"\n",
|
||||
"상태 $c$의 구성 요소는 켜고 끌 수 있는 플래그로 생각할 수 있습니다. 예를 들어, 시퀀스에서 *Alice*라는 이름을 만나면 여성을 지칭한다고 추측하고, 문장에 여성 명사가 있다는 플래그를 상태에서 올립니다. 이후 *and Tom*이라는 단어를 만나면 복수 명사가 있다는 플래그를 올립니다. 이렇게 상태를 조작함으로써 문법적 속성을 추적할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: LSTM의 내부 구조를 이해하는 데 유용한 훌륭한 자료가 있습니다: Christopher Olah의 [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/).\n",
|
||||
"\n",
|
||||
"LSTM 셀의 내부 구조는 복잡해 보일 수 있지만, Keras는 이를 `LSTM` 레이어 내부에 숨겨두었기 때문에 위의 예제에서 우리가 해야 할 유일한 일은 순환 레이어를 교체하는 것입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||||
" keras.layers.LSTM(8),\n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 양방향 및 다층 RNN\n",
|
||||
"\n",
|
||||
"지금까지의 예제에서는 순환 신경망이 시퀀스의 시작부터 끝까지 작동했습니다. 이는 우리가 읽거나 말을 들을 때의 방향과 같기 때문에 자연스럽게 느껴집니다. 하지만 입력 시퀀스를 임의로 접근해야 하는 상황에서는 순환 연산을 양방향으로 실행하는 것이 더 적합합니다. 양방향으로 연산을 허용하는 RNN을 **양방향 RNN**이라고 하며, 이는 순환 레이어를 특별한 `Bidirectional` 레이어로 감싸서 생성할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: `Bidirectional` 레이어는 내부 레이어의 두 복사본을 생성하며, 그 중 하나의 `go_backwards` 속성을 `True`로 설정하여 시퀀스를 반대 방향으로 처리하도록 만듭니다.\n",
|
||||
"\n",
|
||||
"순환 신경망은 단방향이든 양방향이든 시퀀스 내의 패턴을 포착하고 이를 상태 벡터에 저장하거나 출력으로 반환합니다. 합성곱 신경망과 마찬가지로, 첫 번째 레이어에서 추출한 저수준 패턴을 기반으로 더 높은 수준의 패턴을 포착하기 위해 첫 번째 레이어 다음에 또 다른 순환 레이어를 추가할 수 있습니다. 이를 통해 **다층 RNN**이라는 개념이 도출되며, 이는 두 개 이상의 순환 신경망으로 구성되고 이전 레이어의 출력이 다음 레이어의 입력으로 전달됩니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Fernando López의 [이 훌륭한 글](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3)에서 가져온 그림입니다.*\n",
|
||||
"\n",
|
||||
"Keras는 이러한 네트워크를 쉽게 구성할 수 있도록 해줍니다. 모델에 더 많은 순환 레이어를 추가하기만 하면 됩니다. 마지막 레이어를 제외한 모든 레이어에는 `return_sequences=True` 매개변수를 지정해야 합니다. 이는 순환 연산의 최종 상태뿐만 아니라 모든 중간 상태를 반환하도록 레이어가 필요하기 때문입니다.\n",
|
||||
"\n",
|
||||
"이제 분류 문제를 위해 양방향 LSTM 두 개 층으로 구성된 모델을 만들어 보겠습니다.\n",
|
||||
"\n",
|
||||
"> **Note** 이 코드는 실행 시간이 꽤 오래 걸리지만, 지금까지 본 것 중 가장 높은 정확도를 제공합니다. 따라서 기다려서 결과를 확인할 가치가 있을 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
|
||||
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
|
||||
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
||||
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## RNN을 활용한 기타 작업\n",
|
||||
"\n",
|
||||
"지금까지 우리는 RNN을 사용하여 텍스트 시퀀스를 분류하는 데 초점을 맞췄습니다. 하지만 RNN은 텍스트 생성이나 기계 번역과 같은 훨씬 더 다양한 작업을 처리할 수 있습니다. 이러한 작업은 다음 단원에서 다룰 예정입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernel_info": {
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_tensorflow",
|
||||
"language": "python",
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.9"
|
||||
},
|
||||
"nteract": {
|
||||
"version": "nteract-front-end@1.0.0"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "81351e61f619b432ff51010a4f993194",
|
||||
"translation_date": "2025-08-31T13:57:04+00:00",
|
||||
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,414 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 생성 네트워크\n",
|
||||
"\n",
|
||||
"순환 신경망(Recurrent Neural Networks, RNNs)과 Long Short Term Memory Cells(LSTMs), Gated Recurrent Units(GRUs)와 같은 게이트 셀 변형은 언어 모델링을 위한 메커니즘을 제공합니다. 즉, 단어의 순서를 학습하고 시퀀스에서 다음 단어를 예측할 수 있습니다. 이를 통해 RNN을 **생성 작업**에 사용할 수 있습니다. 예를 들어, 일반적인 텍스트 생성, 기계 번역, 심지어 이미지 캡션 생성에도 활용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"이전 단원에서 논의한 RNN 아키텍처에서는 각 RNN 유닛이 다음 숨겨진 상태를 출력으로 생성했습니다. 그러나 각 순환 유닛에 또 다른 출력을 추가할 수도 있습니다. 이를 통해 원래 시퀀스와 길이가 동일한 **시퀀스**를 출력할 수 있습니다. 더 나아가, 각 단계에서 입력을 받지 않고 초기 상태 벡터만 받아 시퀀스 출력을 생성하는 RNN 유닛도 사용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"이 노트북에서는 텍스트 생성을 돕는 간단한 생성 모델에 초점을 맞출 것입니다. 간단히 말해, **문자 단위 네트워크**를 구축해 텍스트를 한 글자씩 생성해 보겠습니다. 학습 과정에서는 텍스트 코퍼스를 가져와 이를 문자 시퀀스로 분할해야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loading dataset...\n",
|
||||
"Building vocab...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import numpy as np\n",
|
||||
"from torchnlp import *\n",
|
||||
"train_dataset,test_dataset,classes,vocab = load_dataset()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 문자 어휘 구축\n",
|
||||
"\n",
|
||||
"문자 수준의 생성 네트워크를 구축하려면 텍스트를 단어가 아닌 개별 문자로 분리해야 합니다. 이를 위해 다른 토크나이저를 정의할 수 있습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocabulary size = 82\n",
|
||||
"Encoding of 'a' is 1\n",
|
||||
"Character with code 13 is c\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def char_tokenizer(words):\n",
|
||||
" return list(words) #[word for word in words]\n",
|
||||
"\n",
|
||||
"counter = collections.Counter()\n",
|
||||
"for (label, line) in train_dataset:\n",
|
||||
" counter.update(char_tokenizer(line))\n",
|
||||
"vocab = torchtext.vocab.vocab(counter)\n",
|
||||
"\n",
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(f\"Vocabulary size = {vocab_size}\")\n",
|
||||
"print(f\"Encoding of 'a' is {vocab.get_stoi()['a']}\")\n",
|
||||
"print(f\"Character with code 13 is {vocab.get_itos()[13]}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리 데이터셋에서 텍스트를 어떻게 인코딩할 수 있는지 예를 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([ 0, 1, 2, 2, 3, 4, 5, 6, 3, 7, 8, 1, 9, 10, 3, 11, 2, 1,\n",
|
||||
" 12, 3, 7, 1, 13, 14, 3, 15, 16, 5, 17, 3, 5, 18, 8, 3, 7, 2,\n",
|
||||
" 1, 13, 14, 3, 19, 20, 8, 21, 5, 8, 9, 10, 22, 3, 20, 8, 21, 5,\n",
|
||||
" 8, 9, 10, 3, 23, 3, 4, 18, 17, 9, 5, 23, 10, 8, 2, 2, 8, 9,\n",
|
||||
" 10, 24, 3, 0, 1, 2, 2, 3, 4, 5, 9, 8, 8, 5, 25, 10, 3, 26,\n",
|
||||
" 12, 27, 16, 26, 2, 27, 16, 28, 29, 30, 1, 16, 26, 3, 17, 31, 3, 21,\n",
|
||||
" 2, 5, 9, 1, 23, 13, 32, 16, 27, 13, 10, 24, 3, 1, 9, 8, 3, 10,\n",
|
||||
" 8, 8, 27, 16, 28, 3, 28, 9, 8, 8, 16, 3, 1, 28, 1, 27, 16, 6])"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def enc(x):\n",
|
||||
" return torch.LongTensor(encode(x,voc=vocab,tokenizer=char_tokenizer))\n",
|
||||
"\n",
|
||||
"enc(train_dataset[0][1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 생성적 RNN 훈련하기\n",
|
||||
"\n",
|
||||
"RNN을 훈련시켜 텍스트를 생성하는 방법은 다음과 같습니다. 각 단계에서 `nchars` 길이의 문자 시퀀스를 가져와 네트워크가 각 입력 문자에 대해 다음 출력 문자를 생성하도록 요청합니다:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"실제 시나리오에 따라 *시퀀스 종료* `<eos>`와 같은 특수 문자를 포함하고 싶을 수도 있습니다. 하지만 우리의 경우, 끝없는 텍스트 생성을 위해 네트워크를 훈련시키고자 하므로 각 시퀀스의 크기를 `nchars` 토큰으로 고정할 것입니다. 따라서 각 훈련 예제는 `nchars` 입력과 `nchars` 출력(입력 시퀀스를 왼쪽으로 한 기호씩 이동한 것)으로 구성됩니다. 미니배치는 이러한 여러 시퀀스로 구성됩니다.\n",
|
||||
"\n",
|
||||
"미니배치를 생성하는 방법은 길이가 `l`인 각 뉴스 텍스트를 가져와 그로부터 가능한 모든 입력-출력 조합을 생성하는 것입니다(이 조합은 `l-nchars`개가 될 것입니다). 이 조합들은 하나의 미니배치를 형성하며, 훈련 단계마다 미니배치의 크기는 달라질 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(tensor([[ 0, 1, 2, ..., 28, 29, 30],\n",
|
||||
" [ 1, 2, 2, ..., 29, 30, 1],\n",
|
||||
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
||||
" ...,\n",
|
||||
" [20, 8, 21, ..., 1, 28, 1],\n",
|
||||
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
||||
" [21, 5, 8, ..., 1, 27, 16]]),\n",
|
||||
" tensor([[ 1, 2, 2, ..., 29, 30, 1],\n",
|
||||
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
||||
" [ 2, 3, 4, ..., 1, 16, 26],\n",
|
||||
" ...,\n",
|
||||
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
||||
" [21, 5, 8, ..., 1, 27, 16],\n",
|
||||
" [ 5, 8, 9, ..., 27, 16, 6]]))"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"nchars = 100\n",
|
||||
"\n",
|
||||
"def get_batch(s,nchars=nchars):\n",
|
||||
" ins = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
||||
" outs = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
||||
" for i in range(len(s)-nchars):\n",
|
||||
" ins[i] = enc(s[i:i+nchars])\n",
|
||||
" outs[i] = enc(s[i+1:i+nchars+1])\n",
|
||||
" return ins,outs\n",
|
||||
"\n",
|
||||
"get_batch(train_dataset[0][1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 생성기 네트워크를 정의해 봅시다. 이는 이전 단원에서 논의한 반복 셀(단순 RNN, LSTM, GRU 중 하나)을 기반으로 할 수 있습니다. 이번 예제에서는 LSTM을 사용할 것입니다.\n",
|
||||
"\n",
|
||||
"네트워크가 문자를 입력으로 받고, 어휘 크기가 비교적 작기 때문에 임베딩 레이어는 필요하지 않습니다. 원-핫 인코딩된 입력을 바로 LSTM 셀에 전달할 수 있습니다. 하지만, 입력으로 문자 번호를 전달하기 때문에, LSTM에 전달하기 전에 이를 원-핫 인코딩해야 합니다. 이는 `forward` 단계에서 `one_hot` 함수를 호출하여 수행됩니다. 출력 인코더는 은닉 상태를 원-핫 인코딩된 출력으로 변환하는 선형 레이어가 될 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class LSTMGenerator(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, hidden_dim):\n",
|
||||
" super().__init__()\n",
|
||||
" self.rnn = torch.nn.LSTM(vocab_size,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, vocab_size)\n",
|
||||
"\n",
|
||||
" def forward(self, x, s=None):\n",
|
||||
" x = torch.nn.functional.one_hot(x,vocab_size).to(torch.float32)\n",
|
||||
" x,s = self.rnn(x,s)\n",
|
||||
" return self.fc(x),s"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"훈련 중에는 생성된 텍스트를 샘플링할 수 있어야 합니다. 이를 위해, 초기 문자열 `start`에서 시작하여 길이가 `size`인 출력 문자열을 생성하는 `generate` 함수를 정의할 것입니다.\n",
|
||||
"\n",
|
||||
"작동 방식은 다음과 같습니다. 먼저, 전체 시작 문자열을 네트워크에 전달하고 출력 상태 `s`와 다음에 예측된 문자 `out`을 가져옵니다. `out`은 원-핫 인코딩되어 있으므로, `argmax`를 사용하여 어휘에서 문자 `nc`의 인덱스를 얻고, `itos`를 사용하여 실제 문자를 확인한 후 결과 문자 리스트 `chars`에 추가합니다. 이 과정을 통해 한 문자를 생성하는 작업을 `size` 횟수만큼 반복하여 필요한 수의 문자를 생성합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def generate(net,size=100,start='today '):\n",
|
||||
" chars = list(start)\n",
|
||||
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
||||
" for i in range(size):\n",
|
||||
" nc = torch.argmax(out[0][-1])\n",
|
||||
" chars.append(vocab.get_itos()[nc])\n",
|
||||
" out, s = net(nc.view(1,-1),s)\n",
|
||||
" return ''.join(chars)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 훈련을 시작해봅시다! 훈련 루프는 이전 예제들과 거의 동일하지만, 정확도를 출력하는 대신 1000 에포크마다 샘플링된 생성 텍스트를 출력합니다.\n",
|
||||
"\n",
|
||||
"특히 손실(loss)을 계산하는 방식에 주의를 기울여야 합니다. 우리는 원-핫 인코딩된 출력 `out`과 예상 텍스트 `text_out`(문자 인덱스의 리스트)을 기반으로 손실을 계산해야 합니다. 다행히도, `cross_entropy` 함수는 첫 번째 인수로 정규화되지 않은 네트워크 출력을, 두 번째 인수로 클래스 번호를 기대하며, 이는 우리가 가진 데이터와 정확히 일치합니다. 또한, 이 함수는 미니배치 크기에 대한 자동 평균화도 수행합니다.\n",
|
||||
"\n",
|
||||
"또한, 너무 오래 기다리지 않도록 `samples_to_train` 샘플로 훈련을 제한합니다. 더 긴 훈련을 시도해보는 것도 권장하며, 몇 에포크 동안 훈련을 진행해보는 것도 좋습니다(이 경우 이 코드를 감싸는 또 다른 루프를 만들어야 할 것입니다).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Current loss = 4.398899078369141\n",
|
||||
"today sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr s\n",
|
||||
"Current loss = 2.161320447921753\n",
|
||||
"today and to the tor to to the tor to to the tor to to the tor to to the tor to to the tor to to the tor t\n",
|
||||
"Current loss = 1.6722588539123535\n",
|
||||
"today and the court to the could to the could to the could to the could to the could to the could to the c\n",
|
||||
"Current loss = 2.423795223236084\n",
|
||||
"today and a second to the conternation of the conternation of the conternation of the conternation of the \n",
|
||||
"Current loss = 1.702607274055481\n",
|
||||
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
||||
"Current loss = 1.692358136177063\n",
|
||||
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
||||
"Current loss = 1.9722288846969604\n",
|
||||
"today and the control the control the control the control the control the control the control the control \n",
|
||||
"Current loss = 1.8705692291259766\n",
|
||||
"today and the second to the second to the second to the second to the second to the second to the second t\n",
|
||||
"Current loss = 1.7626899480819702\n",
|
||||
"today and a security and a security and a security and a security and a security and a security and a secu\n",
|
||||
"Current loss = 1.5574463605880737\n",
|
||||
"today and the company and the company and the company and the company and the company and the company and \n",
|
||||
"Current loss = 1.5620026588439941\n",
|
||||
"today and the be that the be the be that the be the be that the be the be that the be the be that the be t\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = LSTMGenerator(vocab_size,64).to(device)\n",
|
||||
"\n",
|
||||
"samples_to_train = 10000\n",
|
||||
"optimizer = torch.optim.Adam(net.parameters(),0.01)\n",
|
||||
"loss_fn = torch.nn.CrossEntropyLoss()\n",
|
||||
"net.train()\n",
|
||||
"for i,x in enumerate(train_dataset):\n",
|
||||
" # x[0] is class label, x[1] is text\n",
|
||||
" if len(x[1])-nchars<10:\n",
|
||||
" continue\n",
|
||||
" samples_to_train-=1\n",
|
||||
" if not samples_to_train: break\n",
|
||||
" text_in, text_out = get_batch(x[1])\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" out,s = net(text_in)\n",
|
||||
" loss = torch.nn.functional.cross_entropy(out.view(-1,vocab_size),text_out.flatten()) #cross_entropy(out,labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" if i%1000==0:\n",
|
||||
" print(f\"Current loss = {loss.item()}\")\n",
|
||||
" print(generate(net))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이 예제는 이미 꽤 괜찮은 텍스트를 생성하지만, 몇 가지 방법으로 더 개선할 수 있습니다:\n",
|
||||
"\n",
|
||||
"* **더 나은 미니배치 생성**. 우리가 훈련 데이터를 준비한 방식은 하나의 샘플에서 하나의 미니배치를 생성하는 것이었습니다. 이는 이상적이지 않은데, 왜냐하면 미니배치의 크기가 모두 다르고, 텍스트가 `nchars`보다 작을 경우 일부 미니배치를 생성할 수 없기 때문입니다. 또한, 작은 미니배치는 GPU를 충분히 활용하지 못합니다. 더 나은 방법은 모든 샘플에서 하나의 큰 텍스트 덩어리를 가져온 다음, 모든 입력-출력 쌍을 생성하고, 이를 섞은 후, 크기가 동일한 미니배치를 생성하는 것입니다.\n",
|
||||
"\n",
|
||||
"* **다층 LSTM**. LSTM 셀을 2층 또는 3층으로 시도해보는 것도 의미가 있습니다. 이전 단원에서 언급했듯이, LSTM의 각 층은 텍스트에서 특정 패턴을 추출합니다. 문자 수준 생성기의 경우, 낮은 LSTM 층은 음절을 추출하고, 높은 층은 단어와 단어 조합을 담당할 것으로 예상할 수 있습니다. 이는 LSTM 생성자에 층 수 매개변수를 전달하여 간단히 구현할 수 있습니다.\n",
|
||||
"\n",
|
||||
"* **GRU 유닛**을 실험해보고 어떤 것이 더 나은 성능을 보이는지 확인하거나, **다양한 은닉층 크기**를 시도해볼 수도 있습니다. 은닉층이 너무 크면 과적합(예: 네트워크가 텍스트를 정확히 학습함)될 수 있고, 크기가 너무 작으면 좋은 결과를 내지 못할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 부드러운 텍스트 생성과 온도\n",
|
||||
"\n",
|
||||
"이전의 `generate` 정의에서는 항상 생성된 텍스트에서 다음 문자로 가장 높은 확률을 가진 문자를 선택했습니다. 이로 인해 텍스트가 종종 동일한 문자 시퀀스를 반복하는 \"순환\" 현상이 발생하곤 했습니다. 예를 들어, 아래와 같은 경우입니다:\n",
|
||||
"```\n",
|
||||
"today of the second the company and a second the company ...\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"하지만 다음 문자의 확률 분포를 살펴보면, 가장 높은 확률들 간의 차이가 크지 않을 수 있습니다. 예를 들어, 한 문자의 확률이 0.2이고, 다른 문자가 0.19인 경우처럼 말이죠. 예를 들어, 시퀀스 '*play*'에서 다음 문자를 찾을 때, 다음 문자는 공백일 수도 있고, **e**일 수도 있습니다 (단어 *player*에서처럼).\n",
|
||||
"\n",
|
||||
"이로부터 우리는 항상 더 높은 확률을 가진 문자를 선택하는 것이 \"공정\"하지 않을 수 있다는 결론에 도달합니다. 두 번째로 높은 확률을 선택하더라도 여전히 의미 있는 텍스트를 생성할 수 있기 때문입니다. 따라서 네트워크 출력이 제공하는 확률 분포에서 문자를 **샘플링**하는 것이 더 현명합니다.\n",
|
||||
"\n",
|
||||
"이 샘플링은 **다항 분포**라고 불리는 것을 구현하는 `multinomial` 함수를 사용하여 수행할 수 있습니다. 이 **부드러운** 텍스트 생성을 구현하는 함수는 아래와 같이 정의됩니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--- Temperature = 0.3\n",
|
||||
"Today and a company and complete an all the land the restrational the as a security and has provers the pay to and a report and the computer in the stand has filities and working the law the stations for a company and with the company and the final the first company and refight of the state and and workin\n",
|
||||
"\n",
|
||||
"--- Temperature = 0.8\n",
|
||||
"Today he oniis its first to Aus bomblaties the marmation a to manan boogot that pirate assaid a relaid their that goverfin the the Cappets Ecrotional Assonia Cition targets it annight the w scyments Blamity #39;s TVeer Diercheg Reserals fran envyuil that of ster said access what succers of Dour-provelith\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.0\n",
|
||||
"Today holy they a 11 will meda a toket subsuaties, engins for Chanos, they's has stainger past to opening orital his thempting new Nattona was al innerforder advan-than #36;s night year his religuled talitatian what the but with Wednesday to Justment will wemen of Mark CCC Camp as Timed Nae wome a leaders\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.3\n",
|
||||
"Today gpone 2.5 fech atcusion poor cocles toparsdorM.cht Line Pamage put 43 his calt lowed to the book, that has authh-the silia rruch ailing to'ory andhes beutirsimi- Aefffive heading offil an auf eacklets is charged evis, Gunymy oy) Mony has it after-sloythyor loveId out filme, the Natabl -Najuntaxiggs \n",
|
||||
"\n",
|
||||
"--- Temperature = 1.8\n",
|
||||
"Today plary, P.slan chly\\401 mardregationly #39;t 8.1Mide) closes ,filtcon alfly playin roven!\\grea.-QFBEP: Iss onfarchQ/itilia CCf Zivesigntwasta orce.-Peul-aw.uicrin of fuglinfsut aftaningwo, MIEX awayew Aice Woiduar Corvagiugge oppo esig ThusBratourid canthly-RyI.co lagitems\\eexciaishes.conBabntusmor I\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate_soft(net,size=100,start='today ',temperature=1.0):\n",
|
||||
" chars = list(start)\n",
|
||||
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
||||
" for i in range(size):\n",
|
||||
" #nc = torch.argmax(out[0][-1])\n",
|
||||
" out_dist = out[0][-1].div(temperature).exp()\n",
|
||||
" nc = torch.multinomial(out_dist,1)[0]\n",
|
||||
" chars.append(vocab.get_itos()[nc])\n",
|
||||
" out, s = net(nc.view(1,-1),s)\n",
|
||||
" return ''.join(chars)\n",
|
||||
" \n",
|
||||
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
||||
" print(f\"--- Temperature = {i}\\n{generate_soft(net,size=300,start='Today ',temperature=i)}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리는 **온도**라는 하나의 매개변수를 추가로 도입했으며, 이는 우리가 가장 높은 확률에 얼마나 강하게 고수해야 하는지를 나타내는 데 사용됩니다. 온도가 1.0이면 공정한 다항 샘플링을 수행하며, 온도가 무한대로 증가하면 모든 확률이 동일해지고 다음 문자를 무작위로 선택하게 됩니다. 아래 예시에서 온도를 너무 높이면 텍스트가 무의미해지고, 온도가 0에 가까워지면 \"순환된\" 강제 생성 텍스트와 유사해지는 것을 관찰할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 신뢰할 수 있는 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "7673cd150d96c74c6d6011460094efb4",
|
||||
"translation_date": "2025-08-31T13:43:54+00:00",
|
||||
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 생성 네트워크\n",
|
||||
"\n",
|
||||
"순환 신경망(Recurrent Neural Networks, RNNs)과 Long Short Term Memory Cells(LSTMs), Gated Recurrent Units(GRUs)와 같은 게이트 셀 변형은 언어 모델링 메커니즘을 제공합니다. 즉, 이들은 단어의 순서를 학습하고, 시퀀스에서 다음 단어를 예측할 수 있습니다. 이를 통해 RNN을 **생성 작업**에 사용할 수 있습니다. 예를 들어, 일반 텍스트 생성, 기계 번역, 심지어 이미지 캡션 생성에도 활용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"이전 단원에서 논의한 RNN 아키텍처에서는 각 RNN 유닛이 다음 숨겨진 상태를 출력으로 생성했습니다. 그러나 각 순환 유닛에 또 다른 출력을 추가하여 **시퀀스**를 출력할 수도 있습니다(이는 원래 시퀀스와 길이가 동일합니다). 더 나아가, 각 단계에서 입력을 받지 않고 초기 상태 벡터만 받아 시퀀스 출력을 생성하는 RNN 유닛도 사용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"이 노트북에서는 텍스트 생성을 돕는 간단한 생성 모델에 초점을 맞출 것입니다. 간단히 말해, **문자 수준 네트워크**를 구축하여 텍스트를 한 글자씩 생성해 보겠습니다. 훈련 중에는 텍스트 코퍼스를 가져와 이를 문자 시퀀스로 나누어야 합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 문자 단위 어휘 구축\n",
|
||||
"\n",
|
||||
"문자 단위 생성 네트워크를 구축하려면 텍스트를 단어가 아닌 개별 문자로 분리해야 합니다. 이전에 사용했던 `TextVectorization` 레이어는 이를 수행할 수 없으므로 두 가지 옵션이 있습니다:\n",
|
||||
"\n",
|
||||
"* 텍스트를 수동으로 로드하고 직접 토큰화하는 방법, [이 공식 Keras 예제](https://keras.io/examples/generative/lstm_character_level_text_generation/)처럼\n",
|
||||
"* 문자 단위 토큰화를 위해 `Tokenizer` 클래스를 사용하는 방법\n",
|
||||
"\n",
|
||||
"우리는 두 번째 옵션을 선택할 것입니다. `Tokenizer`는 단어 단위로도 토큰화할 수 있으므로 문자 단위에서 단어 단위 토큰화로 쉽게 전환할 수 있습니다.\n",
|
||||
"\n",
|
||||
"문자 단위 토큰화를 수행하려면 `char_level=True` 매개변수를 전달해야 합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"tokenizer = keras.preprocessing.text.Tokenizer(char_level=True,lower=False)\n",
|
||||
"tokenizer.fit_on_texts([x['title'].numpy().decode('utf-8') for x in ds_train])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리는 또한 **시퀀스의 끝**을 나타내는 특별한 토큰을 사용하고 싶으며, 이를 `<eos>`라고 부를 것입니다. 이를 어휘에 수동으로 추가해 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"eos_token = len(tokenizer.word_index)+1\n",
|
||||
"tokenizer.word_index['<eos>'] = eos_token\n",
|
||||
"\n",
|
||||
"vocab_size = eos_token + 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[[48, 2, 10, 10, 5, 44, 1, 25, 5, 8, 10, 13, 78]]"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tokenizer.texts_to_sequences(['Hello, world!'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 제목 생성을 위한 생성적 RNN 훈련\n",
|
||||
"\n",
|
||||
"RNN을 훈련시켜 뉴스 제목을 생성하는 방법은 다음과 같습니다. 각 단계에서 하나의 제목을 가져와 RNN에 입력으로 제공하고, 각 입력 문자에 대해 네트워크가 다음 출력 문자를 생성하도록 요청합니다:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"시퀀스의 마지막 문자에 대해서는 네트워크가 `<eos>` 토큰을 생성하도록 요청할 것입니다.\n",
|
||||
"\n",
|
||||
"여기서 사용하는 생성적 RNN의 주요 차이점은 RNN의 최종 셀에서만 출력을 가져오는 것이 아니라 각 단계에서 출력을 가져온다는 점입니다. 이는 RNN 셀에 `return_sequences` 매개변수를 지정함으로써 가능합니다.\n",
|
||||
"\n",
|
||||
"따라서 훈련 중 네트워크에 대한 입력은 특정 길이의 인코딩된 문자 시퀀스가 되고, 출력은 동일한 길이의 시퀀스이지만 한 요소씩 이동되고 `<eos>`로 종료됩니다. 미니배치는 이러한 여러 시퀀스로 구성되며, 모든 시퀀스를 정렬하기 위해 **패딩**을 사용해야 합니다.\n",
|
||||
"\n",
|
||||
"이제 데이터셋을 변환하는 함수를 만들어 보겠습니다. 미니배치 수준에서 시퀀스를 패딩하고자 하므로 먼저 `.batch()`를 호출하여 데이터셋을 배치한 다음, 변환을 위해 `map`을 호출합니다. 따라서 변환 함수는 전체 미니배치를 매개변수로 받게 됩니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def title_batch(x):\n",
|
||||
" x = [t.numpy().decode('utf-8') for t in x]\n",
|
||||
" z = tokenizer.texts_to_sequences(x)\n",
|
||||
" z = tf.keras.preprocessing.sequence.pad_sequences(z)\n",
|
||||
" return tf.one_hot(z,vocab_size), tf.one_hot(tf.concat([z[:,1:],tf.constant(eos_token,shape=(len(z),1))],axis=1),vocab_size)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리가 여기서 하는 몇 가지 중요한 작업은 다음과 같습니다:\n",
|
||||
"* 먼저 문자열 텐서에서 실제 텍스트를 추출합니다.\n",
|
||||
"* `text_to_sequences`는 문자열 목록을 정수 텐서 목록으로 변환합니다.\n",
|
||||
"* `pad_sequences`는 이러한 텐서를 최대 길이로 패딩합니다.\n",
|
||||
"* 마지막으로 모든 문자를 원-핫 인코딩하고, 시프트 및 `<eos>` 추가 작업도 수행합니다. 원-핫 인코딩된 문자가 필요한 이유는 곧 알게 될 것입니다.\n",
|
||||
"\n",
|
||||
"하지만 이 함수는 **Pythonic**합니다, 즉 Tensorflow 계산 그래프로 자동 변환될 수 없습니다. 이 함수를 `Dataset.map` 함수에서 직접 사용하려고 하면 오류가 발생합니다. 이 Pythonic 호출을 `py_function` 래퍼를 사용하여 감싸야 합니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def title_batch_fn(x):\n",
|
||||
" x = x['title']\n",
|
||||
" a,b = tf.py_function(title_batch,inp=[x],Tout=(tf.float32,tf.float32))\n",
|
||||
" return a,b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Note**: Pythonic 변환 함수와 Tensorflow 변환 함수의 차이를 구분하는 것이 다소 복잡하게 느껴질 수 있으며, 왜 데이터를 `fit`에 전달하기 전에 표준 Python 함수를 사용해 변환하지 않는지 의문이 들 수도 있습니다. 물론 이렇게 하는 것도 가능하지만, `Dataset.map`을 사용하는 데는 큰 장점이 있습니다. 데이터 변환 파이프라인이 Tensorflow 계산 그래프를 통해 실행되므로 GPU 계산을 활용할 수 있고, CPU와 GPU 간의 데이터 전송 필요성을 최소화할 수 있습니다.\n",
|
||||
"\n",
|
||||
"이제 생성기 네트워크를 구축하고 학습을 시작할 수 있습니다. 이는 이전 단원에서 논의한 임의의 순환 셀(단순 RNN, LSTM 또는 GRU)을 기반으로 할 수 있습니다. 이 예제에서는 LSTM을 사용할 것입니다.\n",
|
||||
"\n",
|
||||
"네트워크는 문자를 입력으로 받으며, 어휘 크기가 비교적 작기 때문에 임베딩 레이어가 필요하지 않습니다. 원-핫 인코딩된 입력을 LSTM 셀에 직접 전달할 수 있습니다. 출력 레이어는 LSTM 출력값을 원-핫 인코딩된 토큰 번호로 변환하는 `Dense` 분류기가 될 것입니다.\n",
|
||||
"\n",
|
||||
"또한, 가변 길이 시퀀스를 다루기 때문에 `Masking` 레이어를 사용하여 문자열의 패딩된 부분을 무시하는 마스크를 생성할 수 있습니다. 이는 엄밀히 말해 필수는 아닙니다. `<eos>` 토큰 이후의 모든 것에 대해 크게 관심이 있는 것은 아니기 때문입니다. 하지만 이 레이어 유형에 대한 경험을 쌓기 위해 사용해 보겠습니다. `input_shape`는 `(None, vocab_size)`가 되며, 여기서 `None`은 가변 길이의 시퀀스를 나타냅니다. 출력 형태도 `(None, vocab_size)`이며, 이는 `summary`에서 확인할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
"masking (Masking) (None, None, 84) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"lstm (LSTM) (None, None, 128) 109056 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense (Dense) (None, None, 84) 10836 \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 119,892\n",
|
||||
"Trainable params: 119,892\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n",
|
||||
"15000/15000 [==============================] - 229s 15ms/step - loss: 1.5385\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7fa40c1245e0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.Masking(input_shape=(None,vocab_size)),\n",
|
||||
" keras.layers.LSTM(128,return_sequences=True),\n",
|
||||
" keras.layers.Dense(vocab_size,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.summary()\n",
|
||||
"model.compile(loss='categorical_crossentropy')\n",
|
||||
"\n",
|
||||
"model.fit(ds_train.batch(8).map(title_batch_fn))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 출력 생성하기\n",
|
||||
"\n",
|
||||
"모델을 훈련시켰으니 이제 이를 사용해 출력을 생성해 보겠습니다. 먼저, 토큰 번호의 시퀀스로 표현된 텍스트를 디코딩할 방법이 필요합니다. 이를 위해 `tokenizer.sequences_to_texts` 함수를 사용할 수 있지만, 이 함수는 문자 수준 토큰화와 잘 맞지 않습니다. 따라서 토크나이저에서 가져온 토큰 딕셔너리(`word_index`라고 불림)를 사용해 역맵을 만들고, 직접 디코딩 함수를 작성할 것입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"reverse_map = {val:key for key, val in tokenizer.word_index.items()}\n",
|
||||
"\n",
|
||||
"def decode(x):\n",
|
||||
" return ''.join([reverse_map[t] for t in x])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 생성을 시작해 봅시다. 우리는 문자열 `start`로 시작하여 이를 시퀀스 `inp`로 인코딩한 후, 각 단계마다 네트워크를 호출하여 다음 문자를 추론합니다.\n",
|
||||
"\n",
|
||||
"네트워크의 출력값 `out`은 각 토큰의 확률을 나타내는 `vocab_size` 요소로 이루어진 벡터입니다. 여기서 `argmax`를 사용하여 가장 확률이 높은 토큰 번호를 찾을 수 있습니다. 그런 다음 이 문자를 생성된 토큰 리스트에 추가하고 생성을 계속 진행합니다. 한 문자를 생성하는 이 과정은 필요한 문자 수를 생성하기 위해 `size` 횟수만큼 반복되며, `eos_token`이 나타나면 조기 종료됩니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Today #39;s lead to strike for the strike for the strike for the strike (AFP)'"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate(model,size=100,start='Today '):\n",
|
||||
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
||||
" chars = inp\n",
|
||||
" for i in range(size):\n",
|
||||
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
||||
" nc = tf.argmax(out)\n",
|
||||
" if nc==eos_token:\n",
|
||||
" break\n",
|
||||
" chars.append(nc.numpy())\n",
|
||||
" inp = inp+[nc]\n",
|
||||
" return decode(chars)\n",
|
||||
" \n",
|
||||
"generate(model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 훈련 중 출력 샘플링\n",
|
||||
"\n",
|
||||
"*정확도*와 같은 유용한 지표가 없기 때문에, 모델이 개선되고 있는지 확인할 수 있는 유일한 방법은 훈련 중에 생성된 문자열을 **샘플링**하는 것입니다. 이를 위해 우리는 **콜백(callbacks)**을 사용할 것입니다. 즉, `fit` 함수에 전달할 수 있는 함수로, 훈련 중 주기적으로 호출됩니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Epoch 1/3\n",
|
||||
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.2703\n",
|
||||
"Today #39;s a lead in the company for the strike\n",
|
||||
"Epoch 2/3\n",
|
||||
"15000/15000 [==============================] - 227s 15ms/step - loss: 1.2057\n",
|
||||
"Today #39;s the Market Service on Security Start (AP)\n",
|
||||
"Epoch 3/3\n",
|
||||
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.1752\n",
|
||||
"Today #39;s a line on the strike to start for the start\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7fa40c74e3d0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"sampling_callback = keras.callbacks.LambdaCallback(\n",
|
||||
" on_epoch_end = lambda batch, logs: print(generate(model))\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model.fit(ds_train.batch(8).map(title_batch_fn),callbacks=[sampling_callback],epochs=3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이 예제는 이미 꽤 괜찮은 텍스트를 생성하지만, 몇 가지 방법으로 더 개선할 수 있습니다:\n",
|
||||
"\n",
|
||||
"* **더 많은 텍스트**. 우리는 작업을 위해 제목만 사용했지만, 전체 텍스트로 실험해보는 것도 좋습니다. RNN은 긴 시퀀스를 처리하는 데 그다지 뛰어나지 않으므로, 이를 짧은 문장으로 나누거나, 항상 미리 정의된 값 `num_chars`(예: 256)의 고정된 시퀀스 길이로 학습하는 것이 합리적입니다. 위의 예제를 이러한 아키텍처로 변경해보는 것도 좋으며, [공식 Keras 튜토리얼](https://keras.io/examples/generative/lstm_character_level_text_generation/)을 참고로 활용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"* **다층 LSTM**. LSTM 셀을 2층 또는 3층으로 시도해보는 것도 의미가 있습니다. 이전 단원에서 언급했듯이, LSTM의 각 층은 텍스트에서 특정 패턴을 추출하며, 문자 수준 생성기의 경우, 낮은 LSTM 층은 음절을 추출하고, 높은 층은 단어와 단어 조합을 담당할 것으로 기대할 수 있습니다. 이는 LSTM 생성자에 층 수 매개변수를 전달함으로써 간단히 구현할 수 있습니다.\n",
|
||||
"\n",
|
||||
"* **GRU 유닛**을 실험해보고 어떤 것이 더 나은 성능을 보이는지 확인하거나, **다양한 은닉층 크기**를 시도해보는 것도 좋습니다. 은닉층이 너무 크면 과적합(예: 네트워크가 정확한 텍스트를 학습)될 수 있고, 크기가 너무 작으면 좋은 결과를 내지 못할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 부드러운 텍스트 생성과 온도\n",
|
||||
"\n",
|
||||
"이전의 `generate` 정의에서는 항상 생성된 텍스트에서 다음 문자로 가장 높은 확률을 가진 문자를 선택했습니다. 이로 인해 텍스트가 종종 동일한 문자 시퀀스를 반복하는 결과를 낳았습니다. 예를 들어, 아래와 같은 경우입니다:\n",
|
||||
"```\n",
|
||||
"today of the second the company and a second the company ...\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"하지만 다음 문자의 확률 분포를 살펴보면, 가장 높은 확률들 간의 차이가 크지 않을 수 있습니다. 예를 들어, 한 문자의 확률이 0.2이고, 다른 문자가 0.19인 경우처럼 말이죠. 예를 들어, '*play*'라는 시퀀스에서 다음 문자를 찾을 때, 다음 문자는 공백일 수도 있고, **e**일 수도 있습니다 (*player*라는 단어에서처럼).\n",
|
||||
"\n",
|
||||
"이로부터 우리는 항상 더 높은 확률을 가진 문자를 선택하는 것이 \"공정\"하지 않을 수 있다는 결론에 도달합니다. 두 번째로 높은 확률을 선택하더라도 여전히 의미 있는 텍스트를 생성할 수 있기 때문입니다. 따라서 네트워크 출력이 제공하는 확률 분포에서 문자를 **샘플링**하는 것이 더 현명합니다.\n",
|
||||
"\n",
|
||||
"이 샘플링은 **다항 분포**라고 불리는 것을 구현하는 `np.multinomial` 함수를 사용하여 수행할 수 있습니다. 이 **부드러운** 텍스트 생성을 구현하는 함수는 아래와 같이 정의됩니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 33,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"--- Temperature = 0.3\n",
|
||||
"Today #39;s strike #39; to start at the store return\n",
|
||||
"On Sunday PO to Be Data Profit Up (Reuters)\n",
|
||||
"Moscow, SP wins straight to the Microsoft #39;s control of the space start\n",
|
||||
"President olding of the blast start for the strike to pay <b>...</b>\n",
|
||||
"Little red riding hood ficed to the spam countered in European <b>...</b>\n",
|
||||
"\n",
|
||||
"--- Temperature = 0.8\n",
|
||||
"Today countie strikes ryder missile faces food market blut\n",
|
||||
"On Sunday collores lose-toppy of sale of Bullment in <b>...</b>\n",
|
||||
"Moscow, IBM Diffeiting in Afghan Software Hotels (Reuters)\n",
|
||||
"President Ol Luster for Profit Peaced Raised (AP)\n",
|
||||
"Little red riding hood dace on depart talks #39; bank up\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.0\n",
|
||||
"Today wits House buiting debate fixes #39; supervice stake again\n",
|
||||
"On Sunday arling digital poaching In for level\n",
|
||||
"Moscow, DS Up 7, Top Proble Protest Caprey Mamarian Strike\n",
|
||||
"President teps help of roubler stepted lessabul-Dhalitics (AFP)\n",
|
||||
"Little red riding hood signs on cash in Carter-youb\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.3\n",
|
||||
"Today wits flawer ro, pSIA figat's co DroftwavesIs Talo up\n",
|
||||
"On Sunday hround elitwing wint EU Powerburlinetien\n",
|
||||
"Moscow, Bazz #39;s sentries olymen winnelds' next for Olympite Huc?\n",
|
||||
"President lost securitys from power Elections in Smiltrials\n",
|
||||
"Little red riding hood vides profit, exponituity, profitmainalist-at said listers\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.8\n",
|
||||
"Today #39;It: He deat: N.KA Asside\n",
|
||||
"On Sunday i arry Par aldeup patient Wo stele1\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ename": "KeyError",
|
||||
"evalue": "0",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
|
||||
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"\\n--- Temperature = {i}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenerate_soft\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m300\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwords\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mtemperature\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36mgenerate_soft\u001b[0;34m(model, size, start, temperature)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mchars\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0minp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minp\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchars\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mwords\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m'Today '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'On Sunday '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Moscow, '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'President '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Little red riding hood '\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36m<listcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||
"\u001b[0;31mKeyError\u001b[0m: 0"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate_soft(model,size=100,start='Today ',temperature=1.0):\n",
|
||||
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
||||
" chars = inp\n",
|
||||
" for i in range(size):\n",
|
||||
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
||||
" probs = tf.exp(tf.math.log(out)/temperature).numpy().astype(np.float64)\n",
|
||||
" probs = probs/np.sum(probs)\n",
|
||||
" nc = np.argmax(np.random.multinomial(1,probs,1))\n",
|
||||
" if nc==eos_token:\n",
|
||||
" break\n",
|
||||
" chars.append(nc)\n",
|
||||
" inp = inp+[nc]\n",
|
||||
" return decode(chars)\n",
|
||||
"\n",
|
||||
"words = ['Today ','On Sunday ','Moscow, ','President ','Little red riding hood ']\n",
|
||||
" \n",
|
||||
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
||||
" print(f\"\\n--- Temperature = {i}\")\n",
|
||||
" for j in range(5):\n",
|
||||
" print(generate_soft(model,size=300,start=words[j],temperature=i))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리는 **온도**라는 하나의 매개변수를 추가로 도입했으며, 이는 가장 높은 확률에 얼마나 강하게 고수해야 하는지를 나타내는 데 사용됩니다. 온도가 1.0이면 공정한 다항 샘플링을 수행하며, 온도가 무한대로 증가하면 모든 확률이 동일해지고 다음 문자를 무작위로 선택하게 됩니다. 아래 예시에서 온도를 너무 높이면 텍스트가 무의미해지고, 온도가 0에 가까워지면 \"순환된\" 강제 생성 텍스트와 유사해지는 것을 관찰할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T13:41:19+00:00",
|
||||
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 어텐션 메커니즘과 트랜스포머\n",
|
||||
"\n",
|
||||
"순환 신경망(Recurrent Neural Networks, RNN)의 주요 단점 중 하나는 시퀀스 내 모든 단어가 결과에 동일한 영향을 미친다는 점입니다. 이는 이름 엔터티 인식(Named Entity Recognition)이나 기계 번역(Machine Translation)과 같은 시퀀스-투-시퀀스 작업에서 표준 LSTM 인코더-디코더 모델의 성능을 저하시키는 원인이 됩니다. 실제로 입력 시퀀스의 특정 단어는 다른 단어보다 순차적 출력에 더 큰 영향을 미치는 경우가 많습니다.\n",
|
||||
"\n",
|
||||
"기계 번역과 같은 시퀀스-투-시퀀스 모델을 생각해봅시다. 이 모델은 두 개의 순환 신경망으로 구현되며, 하나의 네트워크(**인코더**)는 입력 시퀀스를 은닉 상태로 압축하고, 다른 네트워크(**디코더**)는 이 은닉 상태를 번역된 결과로 펼칩니다. 이 접근 방식의 문제는 네트워크의 최종 상태가 문장의 시작 부분을 기억하기 어렵다는 점이며, 이는 긴 문장에서 모델의 품질 저하를 초래합니다.\n",
|
||||
"\n",
|
||||
"**어텐션 메커니즘**은 RNN의 각 출력 예측에 대해 각 입력 벡터의 맥락적 영향을 가중치로 부여하는 방법을 제공합니다. 이는 입력 RNN의 중간 상태와 출력 RNN 간에 단축 경로를 생성함으로써 구현됩니다. 이 방식으로 출력 심볼 $y_t$를 생성할 때, 서로 다른 가중치 계수 $\\alpha_{t,i}$를 사용하여 모든 입력 은닉 상태 $h_i$를 고려합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*[Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf)의 어텐션 메커니즘이 포함된 인코더-디코더 모델, [이 블로그 글](https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html)에서 인용됨*\n",
|
||||
"\n",
|
||||
"어텐션 행렬 $\\{\\alpha_{i,j}\\}$은 출력 시퀀스의 특정 단어를 생성하는 데 있어 특정 입력 단어가 얼마나 중요한지를 나타냅니다. 아래는 이러한 행렬의 예시입니다:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*[Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf)에서 가져온 그림 (Fig.3)*\n",
|
||||
"\n",
|
||||
"어텐션 메커니즘은 현재 또는 거의 현재의 자연어 처리(NLP) 분야에서 최첨단 기술의 많은 부분을 차지하고 있습니다. 그러나 어텐션을 추가하면 모델 매개변수의 수가 크게 증가하여 RNN에서 확장 문제를 초래합니다. RNN 확장의 주요 제약은 모델의 순환적 특성으로 인해 학습을 배치 및 병렬화하기 어렵다는 점입니다. RNN에서는 시퀀스의 각 요소를 순차적으로 처리해야 하므로 병렬화가 쉽지 않습니다.\n",
|
||||
"\n",
|
||||
"어텐션 메커니즘의 채택과 이러한 제약은 오늘날 우리가 알고 사용하는 최첨단 트랜스포머 모델(BERT에서 OpenGPT3까지)의 탄생으로 이어졌습니다.\n",
|
||||
"\n",
|
||||
"## 트랜스포머 모델\n",
|
||||
"\n",
|
||||
"이전 예측의 맥락을 다음 평가 단계로 전달하는 대신, **트랜스포머 모델**은 **위치 인코딩(positional encodings)**과 어텐션을 사용하여 주어진 텍스트 창 내에서 입력의 맥락을 캡처합니다. 아래 이미지는 위치 인코딩과 어텐션이 주어진 창 내에서 맥락을 어떻게 캡처하는지 보여줍니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"각 입력 위치가 독립적으로 각 출력 위치에 매핑되기 때문에, 트랜스포머는 RNN보다 병렬화가 더 잘 이루어질 수 있으며, 이는 훨씬 더 크고 표현력이 뛰어난 언어 모델을 가능하게 합니다. 각 어텐션 헤드는 단어 간의 다양한 관계를 학습하는 데 사용될 수 있으며, 이는 자연어 처리 작업의 성능을 향상시킵니다.\n",
|
||||
"\n",
|
||||
"**BERT**(Bidirectional Encoder Representations from Transformers)는 매우 큰 다층 트랜스포머 네트워크로, *BERT-base*는 12개 층, *BERT-large*는 24개 층으로 구성됩니다. 이 모델은 대규모 텍스트 데이터(WikiPedia + 책)를 사용하여 비지도 학습(문장에서 마스킹된 단어 예측)을 통해 먼저 사전 학습됩니다. 사전 학습 동안 모델은 상당한 수준의 언어 이해를 흡수하며, 이를 다른 데이터셋과 함께 미세 조정(fine-tuning)하여 활용할 수 있습니다. 이 과정을 **전이 학습(transfer learning)**이라고 합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"BERT, DistilBERT, BigBird, OpenGPT3 등 다양한 트랜스포머 아키텍처 변형이 있으며, 이를 미세 조정할 수 있습니다. [HuggingFace 패키지](https://github.com/huggingface/)는 PyTorch를 사용하여 이러한 아키텍처 중 다수를 학습할 수 있는 저장소를 제공합니다.\n",
|
||||
"\n",
|
||||
"## BERT를 사용한 텍스트 분류\n",
|
||||
"\n",
|
||||
"이제 사전 학습된 BERT 모델을 사용하여 전통적인 작업인 시퀀스 분류를 해결하는 방법을 살펴보겠습니다. 우리는 원래의 AG News 데이터셋을 분류할 것입니다.\n",
|
||||
"\n",
|
||||
"먼저 HuggingFace 라이브러리와 데이터셋을 로드해봅시다:\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": [
|
||||
"사전 학습된 BERT 모델을 사용할 것이기 때문에 특정 토크나이저를 사용해야 합니다. 먼저, 사전 학습된 BERT 모델과 연결된 토크나이저를 로드하겠습니다.\n",
|
||||
"\n",
|
||||
"HuggingFace 라이브러리는 사전 학습된 모델의 저장소를 포함하고 있으며, 모델 이름을 `from_pretrained` 함수의 인수로 지정하기만 하면 사용할 수 있습니다. 모델에 필요한 모든 바이너리 파일은 자동으로 다운로드됩니다.\n",
|
||||
"\n",
|
||||
"하지만 때로는 직접 만든 모델을 로드해야 할 때가 있습니다. 이 경우 토크나이저의 매개변수, 모델 매개변수가 포함된 `config.json` 파일, 바이너리 가중치 등을 포함한 관련 파일이 있는 디렉토리를 지정할 수 있습니다.\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": [
|
||||
"`tokenizer` 객체는 텍스트를 직접 인코딩하는 데 사용할 수 있는 `encode` 함수를 포함하고 있습니다:\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": [
|
||||
"그렇다면, 훈련 중 데이터를 접근하기 위해 사용할 반복자를 만들어 봅시다. BERT는 자체 인코딩 함수를 사용하기 때문에 이전에 정의한 `padify`와 유사한 패딩 함수를 정의해야 합니다.\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": [
|
||||
"우리의 경우, `bert-base-uncased`라는 사전 학습된 BERT 모델을 사용할 것입니다. `BertForSequenceClassification` 패키지를 사용하여 모델을 로드해 봅시다. 이를 통해 우리의 모델이 분류를 위한 필요한 아키텍처를 이미 갖추고 있으며, 최종 분류기를 포함하고 있음을 보장합니다. 최종 분류기의 가중치가 초기화되지 않았으며 모델이 사전 학습을 필요로 한다는 경고 메시지를 보게 될 것입니다. 이는 완전히 괜찮습니다. 왜냐하면 바로 그것이 우리가 하려는 일이기 때문입니다!\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": [
|
||||
"이제 훈련을 시작할 준비가 되었습니다! BERT는 이미 사전 학습이 완료된 모델이기 때문에 초기 가중치를 손상시키지 않기 위해 비교적 작은 학습률로 시작하는 것이 좋습니다.\n",
|
||||
"\n",
|
||||
"모든 주요 작업은 `BertForSequenceClassification` 모델이 수행합니다. 훈련 데이터를 모델에 호출하면 입력 미니배치에 대해 손실과 네트워크 출력을 반환합니다. 우리는 손실을 매개변수 최적화에 사용하며 (`loss.backward()`는 역전파를 수행합니다), `out`은 `argmax`를 사용해 계산된 레이블 `labs`과 기대 레이블 `labels`을 비교하여 훈련 정확도를 계산하는 데 사용합니다.\n",
|
||||
"\n",
|
||||
"훈련 과정을 제어하기 위해 여러 반복 동안 손실과 정확도를 누적하고, 이를 `report_freq` 훈련 주기마다 출력합니다.\n",
|
||||
"\n",
|
||||
"이 훈련은 아마도 상당히 오랜 시간이 걸릴 가능성이 있으므로 반복 횟수를 제한합니다.\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": [
|
||||
"BERT 분류를 사용하면 (특히 반복 횟수를 늘리고 충분히 기다리면) 꽤 좋은 정확도를 얻을 수 있다는 것을 알 수 있습니다! 이는 BERT가 이미 언어 구조를 꽤 잘 이해하고 있기 때문이며, 우리는 최종 분류기를 미세 조정하기만 하면 됩니다. 하지만 BERT는 큰 모델이기 때문에 전체 학습 과정이 오래 걸리고 상당한 계산 능력이 필요합니다! (GPU, 그리고 가능하면 여러 대가 필요합니다).\n",
|
||||
"\n",
|
||||
"> **Note:** 우리의 예제에서는 가장 작은 사전 학습된 BERT 모델 중 하나를 사용하고 있습니다. 더 큰 모델들은 더 나은 결과를 낼 가능성이 높습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 모델 성능 평가\n",
|
||||
"\n",
|
||||
"이제 테스트 데이터셋에서 모델의 성능을 평가할 수 있습니다. 평가 루프는 훈련 루프와 매우 유사하지만, `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": [
|
||||
"## 주요 내용\n",
|
||||
"\n",
|
||||
"이 단원에서는 **transformers** 라이브러리에서 사전 학습된 언어 모델을 가져와 텍스트 분류 작업에 쉽게 적용할 수 있는 방법을 살펴보았습니다. 마찬가지로, BERT 모델은 엔티티 추출, 질문 응답, 기타 NLP 작업에도 사용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"Transformer 모델은 현재 NLP 분야에서 최첨단 기술을 대표하며, 대부분의 경우 맞춤형 NLP 솔루션을 구현할 때 처음으로 실험을 시작해야 하는 솔루션입니다. 하지만, 이 모듈에서 논의된 순환 신경망의 기본 원리를 이해하는 것은 고급 신경 모델을 구축하려는 경우 매우 중요합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 권위 있는 출처로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T13:49:05+00:00",
|
||||
"source_file": "lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,819 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 어텐션 메커니즘과 트랜스포머\n",
|
||||
"\n",
|
||||
"순환 신경망(Recurrent Neural Networks, RNN)의 주요 단점 중 하나는 시퀀스 내 모든 단어가 결과에 동일한 영향을 미친다는 점입니다. 이는 이름 엔티티 인식(Named Entity Recognition)이나 기계 번역(Machine Translation)과 같은 시퀀스-투-시퀀스(Sequence-to-Sequence) 작업에서 표준 LSTM 인코더-디코더 모델의 성능을 저하시키는 원인이 됩니다. 실제로 입력 시퀀스의 특정 단어는 다른 단어들보다 순차적 출력에 더 큰 영향을 미치는 경우가 많습니다.\n",
|
||||
"\n",
|
||||
"기계 번역과 같은 시퀀스-투-시퀀스 모델을 생각해봅시다. 이 모델은 두 개의 순환 신경망으로 구현되며, 하나의 네트워크(**인코더**)는 입력 시퀀스를 은닉 상태로 압축하고, 다른 네트워크(**디코더**)는 이 은닉 상태를 번역된 결과로 펼칩니다. 이 접근 방식의 문제는 네트워크의 최종 상태가 문장의 시작 부분을 기억하기 어렵다는 점이며, 이는 긴 문장에서 모델의 품질 저하를 초래합니다.\n",
|
||||
"\n",
|
||||
"**어텐션 메커니즘**은 RNN의 각 출력 예측에 대해 각 입력 벡터의 맥락적 영향을 가중치로 부여하는 방법을 제공합니다. 이는 입력 RNN의 중간 상태와 출력 RNN 사이에 지름길을 생성함으로써 구현됩니다. 이 방식으로 출력 심볼 $y_t$를 생성할 때, 서로 다른 가중치 계수 $\\alpha_{t,i}$를 사용하여 모든 입력 은닉 상태 $h_i$를 고려합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*[Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf)의 어텐션 메커니즘이 포함된 인코더-디코더 모델, [이 블로그 글](https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html)에서 인용됨*\n",
|
||||
"\n",
|
||||
"어텐션 행렬 $\\{\\alpha_{i,j}\\}$은 출력 시퀀스의 특정 단어를 생성하는 데 있어 특정 입력 단어가 얼마나 중요한지를 나타냅니다. 아래는 이러한 행렬의 예시입니다:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*[Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf)에서 발췌한 그림 (Fig.3)*\n",
|
||||
"\n",
|
||||
"어텐션 메커니즘은 현재 또는 근접한 자연어 처리(NLP) 분야의 최첨단 기술에 큰 기여를 하고 있습니다. 하지만 어텐션을 추가하면 모델 파라미터 수가 크게 증가하여 RNN에서 확장성 문제가 발생합니다. RNN의 확장성을 제한하는 주요 제약은 모델의 순환적 특성으로 인해 학습을 배치 처리하거나 병렬화하기 어렵다는 점입니다. RNN에서는 시퀀스의 각 요소를 순차적으로 처리해야 하므로 병렬화가 쉽지 않습니다.\n",
|
||||
"\n",
|
||||
"어텐션 메커니즘의 도입과 이러한 제약 조건은 오늘날 우리가 알고 사용하는 BERT에서 OpenGPT3에 이르는 최첨단 트랜스포머 모델의 탄생으로 이어졌습니다.\n",
|
||||
"\n",
|
||||
"## 트랜스포머 모델\n",
|
||||
"\n",
|
||||
"이전 예측의 맥락을 다음 평가 단계로 전달하는 대신, **트랜스포머 모델**은 **위치 인코딩(Positional Encoding)**과 **어텐션**을 사용하여 주어진 텍스트 창 내에서 입력의 맥락을 캡처합니다. 아래 이미지는 위치 인코딩과 어텐션이 주어진 창 내에서 맥락을 어떻게 캡처하는지 보여줍니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"각 입력 위치가 독립적으로 각 출력 위치에 매핑되기 때문에, 트랜스포머는 RNN보다 병렬화가 더 잘 이루어질 수 있으며, 이는 훨씬 더 크고 표현력이 뛰어난 언어 모델을 가능하게 합니다. 각 어텐션 헤드는 단어 간의 다양한 관계를 학습하는 데 사용될 수 있으며, 이는 자연어 처리 작업의 성능을 향상시킵니다.\n",
|
||||
"\n",
|
||||
"## 간단한 트랜스포머 모델 구축\n",
|
||||
"\n",
|
||||
"Keras에는 내장된 트랜스포머 레이어가 없지만, 직접 구축할 수 있습니다. 이전과 마찬가지로 AG News 데이터셋의 텍스트 분류에 초점을 맞추겠지만, 트랜스포머 모델은 더 어려운 NLP 작업에서 최고의 결과를 보여준다는 점을 언급할 가치가 있습니다.\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": [
|
||||
"Keras의 새로운 레이어는 `Layer` 클래스를 서브클래스로 만들어야 하며, `call` 메서드를 구현해야 합니다. **Positional Embedding** 레이어부터 시작해 보겠습니다. 우리는 [공식 Keras 문서의 일부 코드](https://keras.io/examples/nlp/text_classification_with_transformer/)를 사용할 것입니다. 모든 입력 시퀀스를 길이 `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": [
|
||||
"이 레이어는 두 개의 `Embedding` 레이어로 구성됩니다: 하나는 토큰을 임베딩하기 위한 것이고(앞서 논의한 방식으로), 다른 하나는 토큰 위치를 임베딩하기 위한 것입니다. 토큰 위치는 `tf.range`를 사용하여 0부터 `maxlen`까지의 자연수 시퀀스로 생성된 후, 임베딩 레이어를 거칩니다. 이렇게 생성된 두 개의 임베딩 벡터는 더해져서 입력의 위치 정보를 포함한 임베딩 표현을 생성하며, 이 표현의 형태는 `maxlen`$\\times$`embed_dim`입니다.\n",
|
||||
"\n",
|
||||
"이제, 트랜스포머 블록을 구현해 보겠습니다. 이는 이전에 정의한 임베딩 레이어의 출력을 입력으로 받게 됩니다:\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": [
|
||||
"이제 완전한 Transformer 모델을 정의할 준비가 되었습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential_1\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
"text_vectorization (TextVect (None, 256) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"token_and_position_embedding (None, 256, 32) 648192 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"transformer_block (Transform (None, 256, 32) 10656 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"global_average_pooling1d (Gl (None, 32) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dropout_2 (Dropout) (None, 32) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense_2 (Dense) (None, 20) 660 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dropout_3 (Dropout) (None, 20) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense_3 (Dense) (None, 4) 84 \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 659,592\n",
|
||||
"Trainable params: 659,592\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embed_dim = 32 # Embedding size for each token\n",
|
||||
"num_heads = 2 # Number of attention heads\n",
|
||||
"ff_dim = 32 # Hidden layer size in feed forward network inside transformer\n",
|
||||
"maxlen = 256\n",
|
||||
"vocab_size = 20000\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_sequence_length=maxlen, input_shape=(1,)),\n",
|
||||
" TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim),\n",
|
||||
" TransformerBlock(embed_dim, num_heads, ff_dim),\n",
|
||||
" keras.layers.GlobalAveragePooling1D(),\n",
|
||||
" keras.layers.Dropout(0.1),\n",
|
||||
" keras.layers.Dense(20, activation=\"relu\"),\n",
|
||||
" keras.layers.Dropout(0.1),\n",
|
||||
" keras.layers.Dense(4, activation=\"softmax\")\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training tokenizer\n",
|
||||
"938/938 [==============================] - 45s 39ms/step - loss: 0.4978 - acc: 0.8068 - val_loss: 0.2808 - val_acc: 0.9124\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f9c2427a0d0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print('Training tokenizer')\n",
|
||||
"model.layers[0].adapt(ds_train.map(extract_text))\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## BERT Transformer Models\n",
|
||||
"\n",
|
||||
"**BERT**(Bidirectional Encoder Representations from Transformers)는 매우 큰 다층 트랜스포머 네트워크로, *BERT-base*는 12개의 레이어, *BERT-large*는 24개의 레이어로 구성됩니다. 이 모델은 먼저 대규모 텍스트 데이터(WikiPedia + 책)를 사용하여 비지도 학습(문장에서 마스킹된 단어를 예측)을 통해 사전 학습됩니다. 사전 학습 과정에서 모델은 상당한 수준의 언어 이해를 습득하며, 이를 다른 데이터셋에 미세 조정을 통해 활용할 수 있습니다. 이 과정을 **전이 학습**이라고 합니다.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"BERT, DistilBERT, BigBird, OpenGPT3 등 다양한 트랜스포머 아키텍처 변형이 있으며, 이들 모두 미세 조정이 가능합니다.\n",
|
||||
"\n",
|
||||
"이제 사전 학습된 BERT 모델을 사용하여 기존의 시퀀스 분류 문제를 해결하는 방법을 살펴보겠습니다. [공식 문서](https://www.tensorflow.org/text/tutorials/classify_text_with_bert)에서 아이디어와 일부 코드를 가져와 사용해 보겠습니다.\n",
|
||||
"\n",
|
||||
"사전 학습된 모델을 로드하기 위해 **Tensorflow hub**를 사용할 것입니다. 먼저, BERT 전용 벡터라이저를 로드해 보겠습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"ename": "ModuleNotFoundError",
|
||||
"evalue": "No module named 'tensorflow_text'",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)",
|
||||
"\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_41180/4216669875.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mimport\u001b[0m \u001b[0mtensorflow_text\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mtensorflow_hub\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mhub\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mvectorizer\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mhub\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mKerasLayer\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'tensorflow_text'"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import tensorflow_text \n",
|
||||
"import tensorflow_hub as hub\n",
|
||||
"vectorizer = hub.KerasLayer('https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'input_type_ids': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
|
||||
" array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int32)>,\n",
|
||||
" 'input_word_ids': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
|
||||
" array([[ 101, 1045, 2293, 19081, 102, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0]], dtype=int32)>,\n",
|
||||
" 'input_mask': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
|
||||
" array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int32)>}"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vectorizer(['I love transformers'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"원래 네트워크가 학습된 벡터라이저와 동일한 벡터라이저를 사용하는 것이 중요합니다. 또한, BERT 벡터라이저는 세 가지 구성 요소를 반환합니다:\n",
|
||||
"* `input_word_ids`: 입력 문장의 토큰 번호로 이루어진 시퀀스\n",
|
||||
"* `input_mask`: 시퀀스에서 실제 입력이 포함된 부분과 패딩 부분을 나타냅니다. 이는 `Masking` 레이어가 생성하는 마스크와 유사합니다.\n",
|
||||
"* `input_type_ids`: 언어 모델링 작업에 사용되며, 하나의 시퀀스에서 두 개의 입력 문장을 지정할 수 있도록 합니다.\n",
|
||||
"\n",
|
||||
"그런 다음, 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": [
|
||||
"그래서 BERT 레이어는 여러 유용한 결과를 반환합니다:\n",
|
||||
"* `pooled_output`은 시퀀스 내 모든 토큰을 평균화한 결과입니다. 이를 네트워크 전체의 지능적인 의미적 임베딩으로 볼 수 있습니다. 이는 이전 모델에서 사용한 `GlobalAveragePooling1D` 레이어의 출력과 동일합니다.\n",
|
||||
"* `sequence_output`은 마지막 Transformer 레이어의 출력입니다 (위 모델에서 `TransformerBlock`의 출력에 해당).\n",
|
||||
"* `encoder_outputs`는 모든 Transformer 레이어의 출력입니다. 우리가 4-레이어 BERT 모델을 로드했기 때문에 (이름에 `4_H`가 포함되어 있다는 점에서 추측할 수 있듯이), 4개의 텐서를 포함합니다. 마지막 텐서는 `sequence_output`과 동일합니다.\n",
|
||||
"\n",
|
||||
"이제 엔드-투-엔드 분류 모델을 정의하겠습니다. 우리는 *함수형 모델 정의*를 사용할 것입니다. 모델 입력을 정의한 후, 일련의 표현식을 통해 출력 값을 계산합니다. 또한 BERT 모델의 가중치를 학습하지 않도록 설정하고, 최종 분류기만 학습시킬 것입니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"model\"\n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # Connected to \n",
|
||||
"==================================================================================================\n",
|
||||
"input_1 (InputLayer) [(None,)] 0 \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer (KerasLayer) {'input_type_ids': ( 0 input_1[0][0] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer_1 (KerasLayer) {'pooled_output': (N 4782465 keras_layer[0][0] \n",
|
||||
" keras_layer[0][1] \n",
|
||||
" keras_layer[0][2] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dropout_4 (Dropout) (None, 128) 0 keras_layer_1[0][5] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dense_4 (Dense) (None, 4) 516 dropout_4[0][0] \n",
|
||||
"==================================================================================================\n",
|
||||
"Total params: 4,782,981\n",
|
||||
"Trainable params: 516\n",
|
||||
"Non-trainable params: 4,782,465\n",
|
||||
"__________________________________________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inp = keras.Input(shape=(),dtype=tf.string)\n",
|
||||
"x = vectorizer(inp)\n",
|
||||
"x = bert(x)\n",
|
||||
"x = keras.layers.Dropout(0.1)(x['pooled_output'])\n",
|
||||
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
||||
"model = keras.models.Model(inp,out)\n",
|
||||
"bert.trainable = False\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"938/938 [==============================] - 528s 559ms/step - loss: 0.8056 - acc: 0.6983 - val_loss: 0.5953 - val_acc: 0.7888\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f9bb1e36d00>"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"훈련 가능한 매개변수가 적음에도 불구하고, BERT 특징 추출기가 계산적으로 무겁기 때문에 과정이 꽤 느립니다. 훈련 부족이나 모델 매개변수 부족으로 인해 합리적인 정확도를 달성하지 못한 것 같습니다.\n",
|
||||
"\n",
|
||||
"이제 BERT 가중치를 고정 해제하고 함께 훈련해 봅시다. 이를 위해서는 매우 작은 학습률이 필요하며, **warmup**과 **AdamW** 옵티마이저를 사용하는 더 신중한 훈련 전략이 요구됩니다. 옵티마이저를 생성하기 위해 `tf-models-official` 패키지를 사용할 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"model\"\n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # Connected to \n",
|
||||
"==================================================================================================\n",
|
||||
"input_1 (InputLayer) [(None,)] 0 \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer (KerasLayer) {'input_type_ids': ( 0 input_1[0][0] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer_1 (KerasLayer) {'pooled_output': (N 4782465 keras_layer[0][0] \n",
|
||||
" keras_layer[0][1] \n",
|
||||
" keras_layer[0][2] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dropout_4 (Dropout) (None, 128) 0 keras_layer_1[0][5] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dense_4 (Dense) (None, 4) 516 dropout_4[0][0] \n",
|
||||
"==================================================================================================\n",
|
||||
"Total params: 4,782,981\n",
|
||||
"Trainable params: 4,782,980\n",
|
||||
"Non-trainable params: 1\n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"938/938 [==============================] - 629s 664ms/step - loss: 0.6344 - acc: 0.7658 - val_loss: 0.4876 - val_acc: 0.8247\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f9bb0bd0070>"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from official.nlp import optimization \n",
|
||||
"bert.trainable=True\n",
|
||||
"model.summary()\n",
|
||||
"epochs = 3\n",
|
||||
"opt = optimization.create_optimizer(\n",
|
||||
" init_lr=3e-5,\n",
|
||||
" num_train_steps=epochs*len(ds_train),\n",
|
||||
" num_warmup_steps=0.1*epochs*len(ds_train),\n",
|
||||
" optimizer_type='adamw')\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer=opt)\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"훈련이 꽤 느리게 진행되는 것을 볼 수 있습니다. 하지만 몇 번의 에포크(5-10) 동안 모델을 훈련시키고, 이전에 사용했던 접근 방식과 비교하여 최상의 결과를 얻을 수 있는지 실험해보는 것도 좋습니다.\n",
|
||||
"\n",
|
||||
"## Huggingface Transformers 라이브러리\n",
|
||||
"\n",
|
||||
"Transformer 모델을 사용하는 또 다른 매우 일반적이고 (조금 더 간단한) 방법은 [HuggingFace 패키지](https://github.com/huggingface/)를 사용하는 것입니다. 이 패키지는 다양한 NLP 작업을 위한 간단한 빌딩 블록을 제공합니다. Tensorflow와 PyTorch(또 다른 매우 인기 있는 신경망 프레임워크) 모두에서 사용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"> **Note**: Transformers 라이브러리가 어떻게 작동하는지에 관심이 없다면 이 노트북의 끝부분으로 건너뛰어도 됩니다. 위에서 했던 것과 본질적으로 크게 다르지 않은 내용을 보게 될 것입니다. 우리는 다른 라이브러리와 훨씬 더 큰 모델을 사용하여 BERT 모델을 훈련시키는 동일한 단계를 반복할 것입니다. 따라서 이 과정은 다소 긴 훈련을 포함하므로, 코드를 훑어보는 것으로 충분할 수도 있습니다.\n",
|
||||
"\n",
|
||||
"[Huggingface Transformers](http://huggingface.co)를 사용하여 우리의 문제를 어떻게 해결할 수 있는지 살펴봅시다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"먼저 사용할 모델을 선택해야 합니다. Huggingface에는 몇 가지 내장 모델 외에도 [온라인 모델 저장소](https://huggingface.co/models)가 있어, 커뮤니티에서 제공하는 더 많은 사전 학습된 모델을 찾을 수 있습니다. 이러한 모든 모델은 모델 이름만 제공하면 로드하고 사용할 수 있습니다. 모델에 필요한 모든 바이너리 파일은 자동으로 다운로드됩니다.\n",
|
||||
"\n",
|
||||
"때로는 직접 만든 모델을 로드해야 할 수도 있습니다. 이 경우, 토크나이저의 매개변수, 모델 매개변수가 포함된 `config.json` 파일, 바이너리 가중치 등 관련 파일이 포함된 디렉터리를 지정할 수 있습니다.\n",
|
||||
"\n",
|
||||
"모델 이름을 통해 모델과 토크나이저를 모두 인스턴스화할 수 있습니다. 토크나이저부터 시작해 봅시다:\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": [
|
||||
"`tokenizer` 객체는 텍스트를 직접 인코딩할 수 있는 `encode` 함수를 포함하고 있습니다:\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": [
|
||||
"우리는 토크나이저를 사용하여 시퀀스를 모델에 전달하기 적합한 방식으로 인코딩할 수도 있습니다. 즉, `token_ids`, `input_mask` 필드 등을 포함하는 방식입니다. 또한 `return_tensors='tf'` 인수를 제공하여 Tensorflow 텐서를 원한다고 지정할 수도 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'input_ids': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[ 101, 7592, 1010, 2045, 102]], dtype=int32)>, 'token_type_ids': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[0, 0, 0, 0, 0]], dtype=int32)>, 'attention_mask': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[1, 1, 1, 1, 1]], dtype=int32)>}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tokenizer(['Hello, there'],return_tensors='tf')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리의 경우, 우리는 `bert-base-uncased`라는 사전 학습된 BERT 모델을 사용할 것입니다. *Uncased*는 모델이 대소문자를 구분하지 않는다는 것을 의미합니다.\n",
|
||||
"\n",
|
||||
"모델을 훈련할 때, 우리는 토큰화된 시퀀스를 입력으로 제공해야 하므로 데이터 처리 파이프라인을 설계할 것입니다. `tokenizer.encode`는 Python 함수이기 때문에, 이전 단원에서 사용했던 것처럼 `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": [
|
||||
"이제 `BertForSequenceClassification` 패키지를 사용하여 실제 모델을 로드할 수 있습니다. 이를 통해 모델이 분류를 위한 필요한 아키텍처를 이미 갖추고 있으며, 최종 분류기를 포함하고 있음을 보장합니다. 최종 분류기의 가중치가 초기화되지 않았으며 모델이 사전 훈련이 필요하다는 경고 메시지가 표시될 것입니다. 이는 완전히 괜찮습니다. 왜냐하면 바로 그것이 우리가 하려는 일이기 때문입니다!\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": [
|
||||
"`summary()`에서 볼 수 있듯이, 모델은 거의 1억 1천만 개의 파라미터를 포함하고 있습니다! 아마도 비교적 작은 데이터셋에서 간단한 분류 작업을 원한다면, 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": [
|
||||
"이제 훈련을 시작할 준비가 되었습니다!\n",
|
||||
"\n",
|
||||
"> **Note**: 전체 규모의 BERT 모델을 훈련하는 데는 매우 많은 시간이 소요될 수 있습니다! 따라서 우리는 처음 32개의 배치만 훈련할 것입니다. 이는 모델 훈련이 어떻게 설정되는지 보여주기 위한 것입니다. 전체 규모의 훈련을 시도해보고 싶다면 `steps_per_epoch`와 `validation_steps` 매개변수를 제거하고 기다릴 준비를 하세요!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"32/32 [==============================] - 142s 4s/step - loss: 1.3896 - acc: 0.2500 - val_loss: 1.3863 - val_acc: 0.2480\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f1d40a4b6a0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 30,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile('adam','sparse_categorical_crossentropy',['acc'])\n",
|
||||
"tf.get_logger().setLevel('ERROR')\n",
|
||||
"model.fit(ds_train.map(process_fn).batch(32),validation_data=ds_test.map(process_fn).batch(32),steps_per_epoch=32,validation_steps=2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"만약 반복 횟수를 늘리고 충분히 기다리며 여러 에포크 동안 훈련을 진행한다면, BERT 분류가 최고의 정확도를 제공할 것으로 기대할 수 있습니다! 이는 BERT가 이미 언어 구조를 꽤 잘 이해하고 있기 때문에, 최종 분류기를 미세 조정(fine-tuning)하는 것만 필요하기 때문입니다. 하지만, BERT는 대규모 모델이기 때문에 전체 훈련 과정이 오래 걸리고 상당한 계산 능력이 필요합니다! (GPU, 그리고 가능하다면 여러 개의 GPU가 필요합니다).\n",
|
||||
"\n",
|
||||
"> **Note:** 이 예제에서는 가장 작은 사전 훈련된 BERT 모델 중 하나를 사용했습니다. 더 큰 모델들은 더 나은 결과를 제공할 가능성이 높습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 주요 내용\n",
|
||||
"\n",
|
||||
"이번 단원에서는 **transformers**를 기반으로 한 최신 모델 아키텍처를 살펴보았습니다. 이를 텍스트 분류 작업에 적용해 보았지만, 마찬가지로 BERT 모델은 개체 추출, 질문 응답, 기타 NLP 작업에도 사용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"Transformer 모델은 현재 NLP 분야에서 최첨단 기술을 대표하며, 대부분의 경우 맞춤형 NLP 솔루션을 구현할 때 가장 먼저 실험해봐야 할 솔루션입니다. 하지만, 이 모듈에서 다룬 순환 신경망의 기본 원리를 이해하는 것은 고급 신경망 모델을 구축하려면 매우 중요합니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 권위 있는 출처로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.\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-31T13:51:40+00:00",
|
||||
"source_file": "lessons/5-NLP/18-Transformers/TransformersTF.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,492 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 개체명 인식 (NER)\n",
|
||||
"\n",
|
||||
"이 노트북은 [AI for Beginners Curriculum](http://aka.ms/ai-beginners)에서 제공됩니다.\n",
|
||||
"\n",
|
||||
"이 예제에서는 [개체명 인식을 위한 주석 코퍼스](https://www.kaggle.com/datasets/abhinavwalia95/entity-annotated-corpus) 데이터셋을 사용하여 NER 모델을 학습하는 방법을 배워봅니다. 진행하기 전에 [ner_dataset.csv](https://www.kaggle.com/datasets/abhinavwalia95/entity-annotated-corpus?resource=download&select=ner_dataset.csv) 파일을 현재 디렉토리에 다운로드하세요.\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": [
|
||||
"## 데이터셋 준비하기\n",
|
||||
"\n",
|
||||
"데이터셋을 데이터프레임으로 읽는 것부터 시작하겠습니다. Pandas 사용법에 대해 더 알고 싶다면 [데이터 처리에 관한 강의](https://github.com/microsoft/Data-Science-For-Beginners/tree/main/2-Working-With-Data/07-python)를 [초보자를 위한 데이터 과학](http://aka.ms/datascience-beginners)에서 확인해보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>Sentence #</th>\n",
|
||||
" <th>Word</th>\n",
|
||||
" <th>POS</th>\n",
|
||||
" <th>Tag</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>Sentence: 1</td>\n",
|
||||
" <td>Thousands</td>\n",
|
||||
" <td>NNS</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>of</td>\n",
|
||||
" <td>IN</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>demonstrators</td>\n",
|
||||
" <td>NNS</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>have</td>\n",
|
||||
" <td>VBP</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>marched</td>\n",
|
||||
" <td>VBN</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" Sentence # Word POS Tag\n",
|
||||
"0 Sentence: 1 Thousands NNS O\n",
|
||||
"1 NaN of IN O\n",
|
||||
"2 NaN demonstrators NNS O\n",
|
||||
"3 NaN have VBP O\n",
|
||||
"4 NaN marched VBN O"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"df = pd.read_csv('ner_dataset.csv',encoding='unicode-escape')\n",
|
||||
"df.head()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"고유 태그를 얻고 태그를 클래스 번호로 변환하는 데 사용할 수 있는 조회 사전을 만듭시다:\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": [
|
||||
"이제 어휘에 대해서도 동일한 작업을 수행해야 합니다. 간단히 하기 위해 단어 빈도를 고려하지 않고 어휘를 만들 것입니다. 실제로는 Keras 벡터라이저를 사용하고 단어 수를 제한하는 것이 좋을 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vocab = set(df['Word'].apply(lambda x: x.lower()))\n",
|
||||
"id2word = { i+1 : v for i,v in enumerate(vocab) }\n",
|
||||
"id2word[0] = '<UNK>'\n",
|
||||
"vocab.add('<UNK>')\n",
|
||||
"word2id = { v : k for k,v in id2word.items() }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"우리는 훈련을 위한 문장 데이터셋을 만들어야 합니다. 원본 데이터셋을 반복하며 모든 개별 문장을 `X`(단어 목록)과 `Y`(토큰 목록)으로 분리합시다.\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": [
|
||||
"문장의 간단함을 위해 모든 문장을 최대 길이까지 0 토큰으로 패딩할 것입니다. 실제로는 더 영리한 전략을 사용하여 하나의 미니배치 내에서만 시퀀스를 패딩할 수 있습니다.\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": [
|
||||
"## 토큰 분류 네트워크 정의\n",
|
||||
"\n",
|
||||
"토큰 분류를 위해 이중 레이어 양방향 LSTM 네트워크를 사용할 것입니다. 마지막 LSTM 레이어의 각 출력에 밀집 분류기를 적용하기 위해, `TimeDistributed` 구조를 사용할 것입니다. 이 구조는 LSTM의 각 단계에서 동일한 밀집 레이어를 각 출력에 복제합니다:\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": [
|
||||
"여기서 우리는 데이터셋에 대해 `maxlen`을 명시적으로 지정하고 있습니다. 네트워크가 가변 길이 시퀀스를 처리할 수 있도록 하려면 네트워크를 정의할 때 조금 더 신중해야 합니다.\n",
|
||||
"\n",
|
||||
"이제 모델을 훈련시켜 봅시다. 속도를 위해 한 에포크만 훈련시키겠지만, 더 오랜 시간 동안 훈련을 시도해볼 수도 있습니다. 또한, 데이터셋의 일부를 훈련 데이터셋으로 분리하여 검증 정확도를 관찰하는 것도 고려해볼 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 57,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1499/1499 [==============================] - 740s 488ms/step - loss: 0.0667 - acc: 0.9841\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x16f0bb2a310>"
|
||||
]
|
||||
},
|
||||
"execution_count": 57,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.fit(X_data,Y_data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 결과 테스트하기\n",
|
||||
"\n",
|
||||
"이제 샘플 문장에서 우리의 엔터티 인식 모델이 어떻게 작동하는지 확인해봅시다:\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": [
|
||||
"## 주요 내용\n",
|
||||
"\n",
|
||||
"간단한 LSTM 모델만으로도 NER에서 괜찮은 결과를 얻을 수 있습니다. 하지만 훨씬 더 나은 결과를 얻고 싶다면 BERT와 같은 대규모 사전 학습된 언어 모델을 사용하는 것이 좋습니다. Huggingface Transformers 라이브러리를 사용하여 BERT를 NER에 맞게 학습시키는 방법은 [여기](https://huggingface.co/course/chapter7/2?fw=pt)에서 설명되어 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 권위 있는 출처로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T13:55:23+00:00",
|
||||
"source_file": "lessons/5-NLP/19-NER/NER-TF.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## OpenAI GPT 실험하기\n",
|
||||
"\n",
|
||||
"이 노트북은 [AI for Beginners Curriculum](http://aka.ms/ai-beginners)의 일부입니다.\n",
|
||||
"\n",
|
||||
"이 노트북에서는 Hugging Face의 `transformers` 라이브러리를 사용하여 OpenAI-GPT 모델을 활용하는 방법을 탐구해 보겠습니다.\n",
|
||||
"\n",
|
||||
"그럼 바로 텍스트 생성 파이프라인을 생성하고 시작해 봅시다!\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": [
|
||||
"## 프롬프트 엔지니어링\n",
|
||||
"\n",
|
||||
"일부 문제에서는 적절한 프롬프트를 설계하여 바로 openai-gpt 생성을 사용할 수 있습니다. 아래 예시를 확인해보세요:\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": [
|
||||
"## 텍스트 샘플링 전략\n",
|
||||
"\n",
|
||||
"지금까지 우리는 가장 높은 확률을 기준으로 다음 단어를 선택하는 간단한 **탐욕적** 샘플링 전략을 사용해왔습니다. 작동 방식은 다음과 같습니다:\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": [
|
||||
"**빔 서치**는 생성기가 텍스트 생성의 여러 방향(*빔*)을 탐색하고 전체 점수가 높은 방향을 선택할 수 있도록 합니다. `num_beams` 매개변수를 제공하여 빔 서치를 수행할 수 있습니다. 또한 `no_repeat_ngram_size`를 지정하여 주어진 크기의 n-그램 반복에 대해 모델에 페널티를 부여할 수 있습니다.\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": [
|
||||
"**샘플링**은 모델이 반환한 확률 분포를 사용하여 다음 단어를 비결정적으로 선택합니다. `do_sample=True` 매개변수를 사용하여 샘플링을 활성화할 수 있습니다. 또한 `temperature`를 지정하여 모델을 더 결정적이거나 덜 결정적으로 만들 수 있습니다.\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": [
|
||||
"샘플링에 추가 매개변수를 제공할 수도 있습니다:\n",
|
||||
"* `top_k`는 샘플링을 사용할 때 고려할 단어 옵션의 수를 지정합니다. 이를 통해 텍스트에 이상한(낮은 확률의) 단어가 포함될 가능성을 줄일 수 있습니다.\n",
|
||||
"* `top_p`는 비슷한 역할을 하지만, 확률이 p보다 큰 가장 유력한 단어들의 최소 부분집합을 선택합니다.\n",
|
||||
"\n",
|
||||
"이 매개변수를 추가하여 실험해 보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 모델 세부 조정하기\n",
|
||||
"\n",
|
||||
"자신의 데이터셋을 사용하여 [모델을 세부 조정](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/fine-tuning?pivots=programming-language-studio?WT.mc_id=academic-77998-bethanycheum)할 수도 있습니다. 이를 통해 언어 모델의 주요 부분은 유지하면서 텍스트 스타일을 조정할 수 있습니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T13:54:21+00:00",
|
||||
"source_file": "lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 과제: 디오판토스 방정식\n",
|
||||
"\n",
|
||||
"> 이 과제는 [AI for Beginners Curriculum](http://github.com/microsoft/ai-for-beginners)의 일부이며, [이 게시물](https://habr.com/post/128704/)에서 영감을 받았습니다.\n",
|
||||
"\n",
|
||||
"여러분의 목표는 **디오판토스 방정식**이라고 불리는 방정식을 푸는 것입니다. 이 방정식은 정수 해와 정수 계수를 가지는 방정식입니다. 예를 들어, 다음과 같은 방정식을 생각해봅시다:\n",
|
||||
"\n",
|
||||
"$$a+2b+3c+4d=30$$\n",
|
||||
"\n",
|
||||
"이 방정식을 만족하는 정수 해 $a$,$b$,$c$,$d\\in\\mathbb{N}$를 찾아야 합니다.\n",
|
||||
"\n",
|
||||
"힌트:\n",
|
||||
"1. 해를 [0;30] 구간 내에서 고려할 수 있습니다.\n",
|
||||
"1. 유전자(gene)로 해 값들의 리스트를 사용하는 것을 고려해보세요.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"coopTranslator": {
|
||||
"original_hash": "a967e1fa1e11ab2b6467b19349a4a9aa",
|
||||
"translation_date": "2025-08-31T12:25:38+00:00",
|
||||
"source_file": "lessons/6-Other/21-GeneticAlgorithms/Diophantine.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,501 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 카트폴 균형 잡기 위한 RL 훈련\n",
|
||||
"\n",
|
||||
"이 노트북은 [AI for Beginners Curriculum](http://aka.ms/ai-beginners)의 일부입니다. [공식 PyTorch 튜토리얼](https://pytorch.org/tutorials/intermediate/reinforcement_q_learning.html)과 [이 카트폴 PyTorch 구현](https://github.com/yc930401/Actor-Critic-pytorch)에서 영감을 받았습니다.\n",
|
||||
"\n",
|
||||
"이 예제에서는 RL을 사용하여 수평 축에서 좌우로 움직일 수 있는 카트 위에 막대를 균형 있게 세우는 모델을 훈련시킬 것입니다. 우리는 [OpenAI Gym](https://www.gymlibrary.ml/) 환경을 사용하여 막대를 시뮬레이션할 것입니다.\n",
|
||||
"\n",
|
||||
"> **Note**: 이 강의의 코드는 로컬(예: Visual Studio Code)에서 실행할 수 있으며, 이 경우 시뮬레이션이 새 창에서 열립니다. 온라인으로 코드를 실행할 경우, [여기](https://towardsdatascience.com/rendering-openai-gym-envs-on-binder-and-google-colab-536f99391cc7)에 설명된 대로 코드를 약간 수정해야 할 수도 있습니다.\n",
|
||||
"\n",
|
||||
"우리는 Gym이 설치되어 있는지 확인하는 것부터 시작하겠습니다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"!{sys.executable} -m pip install gym"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 CartPole 환경을 생성하고 이를 어떻게 작동시키는지 살펴보겠습니다. 환경은 다음과 같은 속성을 가지고 있습니다:\n",
|
||||
"\n",
|
||||
"* **Action space**는 시뮬레이션의 각 단계에서 수행할 수 있는 가능한 행동들의 집합입니다. \n",
|
||||
"* **Observation space**는 우리가 관찰할 수 있는 관찰값들의 공간입니다. \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": [
|
||||
"시뮬레이션이 어떻게 작동하는지 살펴봅시다. 다음 루프는 `env.step`이 종료 플래그 `done`을 반환할 때까지 시뮬레이션을 실행합니다. 우리는 `env.action_space.sample()`을 사용하여 무작위로 행동을 선택할 것이며, 이는 실험이 매우 빠르게 실패할 가능성이 높다는 것을 의미합니다 (CartPole 환경은 CartPole의 속도, 위치 또는 각도가 특정 한계를 벗어날 때 종료됩니다).\n",
|
||||
"\n",
|
||||
"> 시뮬레이션은 새 창에서 열립니다. 코드를 여러 번 실행하여 동작을 확인할 수 있습니다.\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": [
|
||||
"관찰값은 4개의 숫자로 구성되어 있습니다. 이 숫자는 다음을 나타냅니다:\n",
|
||||
"- 카트의 위치\n",
|
||||
"- 카트의 속도\n",
|
||||
"- 막대의 각도\n",
|
||||
"- 막대의 회전 속도\n",
|
||||
"\n",
|
||||
"`rew`는 매 단계마다 받는 보상입니다. CartPole 환경에서는 시뮬레이션 단계마다 1점을 받으며, 목표는 총 보상을 최대화하는 것입니다. 즉, CartPole이 넘어지지 않고 균형을 유지할 수 있는 시간을 늘리는 것이 목표입니다.\n",
|
||||
"\n",
|
||||
"강화 학습에서는 **정책** $\\pi$를 훈련시키는 것이 목표입니다. 이 정책은 각 상태 $s$에 대해 어떤 행동 $a$를 취해야 하는지 알려줍니다. 즉, 본질적으로 $a = \\pi(s)$입니다.\n",
|
||||
"\n",
|
||||
"확률적 해결 방법을 원한다면, 정책을 각 행동에 대한 확률 집합을 반환하는 것으로 생각할 수 있습니다. 즉, $\\pi(a|s)$는 상태 $s$에서 행동 $a$를 취해야 할 확률을 의미합니다.\n",
|
||||
"\n",
|
||||
"## 정책 경사법\n",
|
||||
"\n",
|
||||
"가장 간단한 강화 학습 알고리즘인 **정책 경사법**에서는 신경망을 훈련시켜 다음 행동을 예측하도록 합니다.\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": [
|
||||
"우리는 여러 실험을 실행하고 각 실행 후 네트워크를 업데이트하여 네트워크를 훈련할 것입니다. 실험을 실행하고 결과(소위 **추적**) - 모든 상태, 행동(및 권장 확률), 보상을 반환하는 함수를 정의해 봅시다:\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": [
|
||||
"훈련되지 않은 네트워크로 하나의 에피소드를 실행하고 총 보상(즉, 에피소드 길이)이 매우 낮다는 것을 관찰할 수 있습니다:\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": [
|
||||
"정책 경사 알고리즘의 까다로운 측면 중 하나는 **할인된 보상**을 사용하는 것입니다. 아이디어는 게임의 각 단계에서 총 보상의 벡터를 계산하고, 이 과정에서 일부 계수 $gamma$를 사용하여 초기 보상을 할인하는 것입니다. 또한 결과 벡터를 정규화하는데, 이는 이를 훈련에 영향을 미치는 가중치로 사용할 것이기 때문입니다.\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": [
|
||||
"이제 실제 훈련을 시작해봅시다! 우리는 300개의 에피소드를 실행할 것이며, 각 에피소드에서 다음을 수행합니다:\n",
|
||||
"\n",
|
||||
"1. 실험을 실행하고 추적 데이터를 수집합니다.\n",
|
||||
"1. 수행된 행동과 예측된 확률 간의 차이(`gradients`)를 계산합니다. 차이가 적을수록 올바른 행동을 취했을 가능성이 높아집니다.\n",
|
||||
"1. 할인된 보상을 계산하고 이를 `gradients`에 곱합니다. 이렇게 하면 높은 보상을 받은 단계가 낮은 보상을 받은 단계보다 최종 결과에 더 큰 영향을 미치게 됩니다.\n",
|
||||
"1. 신경망의 예상 목표 행동은 실행 중 예측된 확률과 계산된 `gradients`에서 부분적으로 가져옵니다. 우리는 `alpha` 매개변수를 사용하여 `gradients`와 보상이 어느 정도로 고려될지를 결정합니다. 이것은 강화 알고리즘의 *학습률*이라고 불립니다.\n",
|
||||
"1. 마지막으로, 상태와 예상 행동을 기반으로 네트워크를 훈련시키고 과정을 반복합니다.\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": [
|
||||
"이제 렌더링을 통해 에피소드를 실행하여 결과를 확인해 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"_ = run_episode(render=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"이제 막대가 꽤 잘 균형을 잡을 수 있다는 것을 확인할 수 있습니다!\n",
|
||||
"\n",
|
||||
"## 액터-크리틱 모델\n",
|
||||
"\n",
|
||||
"액터-크리틱 모델은 정책 기울기(policy gradients)를 더욱 발전시킨 형태로, 정책과 예상 보상을 동시에 학습하는 신경망을 구축하는 방식입니다. 이 신경망은 두 가지 출력을 가지며(혹은 두 개의 별도 네트워크로 볼 수도 있습니다):\n",
|
||||
"* **액터(Actor)**는 정책 기울기 모델에서처럼 상태 확률 분포를 제공하여 어떤 행동을 취할지 추천합니다.\n",
|
||||
"* **크리틱(Critic)**은 해당 행동들로부터 얻을 수 있는 보상을 예측합니다. 주어진 상태에서 미래에 예상되는 총 보상을 반환합니다.\n",
|
||||
"\n",
|
||||
"이러한 모델을 정의해 봅시다:\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": [
|
||||
"우리는 `discounted_rewards` 및 `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": [
|
||||
"이제 주요 학습 루프를 실행하겠습니다. 적절한 손실 함수를 계산하고 네트워크 매개변수를 업데이트하여 수동 네트워크 학습 프로세스를 사용할 것입니다.\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": [
|
||||
"## 주요 내용\n",
|
||||
"\n",
|
||||
"이 데모에서는 두 가지 강화 학습 알고리즘을 살펴보았습니다: 간단한 정책 경사법과 더 정교한 액터-크리틱 방법. 이러한 알고리즘은 상태, 행동, 보상이라는 추상적인 개념을 기반으로 작동하므로 매우 다양한 환경에 적용할 수 있습니다.\n",
|
||||
"\n",
|
||||
"강화 학습은 최종 보상을 관찰하는 것만으로 문제를 해결하기 위한 최적의 전략을 학습할 수 있게 해줍니다. 라벨이 지정된 데이터셋이 필요하지 않다는 점은 모델을 최적화하기 위해 시뮬레이션을 여러 번 반복할 수 있다는 장점을 제공합니다. 하지만 강화 학습에는 여전히 많은 도전 과제가 있으며, 이 흥미로운 AI 분야에 더 집중하기로 결정한다면 이러한 과제들을 배우게 될 것입니다.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전을 신뢰할 수 있는 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\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-31T12:30:17+00:00",
|
||||
"source_file": "lessons/6-Other/22-DeepRL/CartPole-RL-PyTorch.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# # 산악 자동차 탈출 훈련\n",
|
||||
"\n",
|
||||
"[AI for Beginners Curriculum](https://github.com/microsoft/ai-for-beginners)에서 제공하는 실습 과제입니다.\n",
|
||||
"\n",
|
||||
"여러분의 목표는 OpenAI 환경에서 [Mountain Car](https://www.gymlibrary.ml/environments/classic_control/mountain_car/)를 제어할 수 있도록 RL 에이전트를 훈련시키는 것입니다.\n",
|
||||
"\n",
|
||||
"먼저 환경을 생성해 봅시다:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gym\n",
|
||||
"env = gym.make('MountainCar-v0')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"랜덤 실험이 어떻게 생겼는지 봅시다:\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**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있으나, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서의 원어 버전이 권위 있는 출처로 간주되어야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.\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-31T12:33:55+00:00",
|
||||
"source_file": "lessons/6-Other/22-DeepRL/lab/MountainCar.ipynb",
|
||||
"language_code": "ko"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,478 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"source": [
|
||||
"# Įgyvendinant gyvūnų ekspertų sistemą\n",
|
||||
"\n",
|
||||
"Pavyzdys iš [AI pradedantiesiems mokymo programos](http://github.com/microsoft/ai-for-beginners).\n",
|
||||
"\n",
|
||||
"Šiame pavyzdyje įgyvendinsime paprastą žinių pagrindu veikiančią sistemą, kuri nustatys gyvūną pagal tam tikras fizines savybes. Sistema gali būti pavaizduota šiuo AND-OR medžiu (tai yra tik dalis viso medžio, lengvai galime pridėti daugiau taisyklių):\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Mūsų pačių ekspertinių sistemų apvalkalas su atvirkštine išvada\n",
|
||||
"\n",
|
||||
"Pabandykime apibrėžti paprastą kalbą žinių reprezentacijai, pagrįstą produkcinėmis taisyklėmis. Naudosime Python klases kaip raktinius žodžius taisyklėms apibrėžti. Iš esmės bus 3 tipų klasės:\n",
|
||||
"* `Ask` reiškia klausimą, kurį reikia užduoti vartotojui. Ji turi galimų atsakymų rinkinį.\n",
|
||||
"* `If` reiškia taisyklę ir yra tik sintaksinis patogumas taisyklės turiniui saugoti.\n",
|
||||
"* `AND`/`OR` yra klasės, skirtos atstovauti AND/OR šakoms medyje. Jos tiesiog saugo argumentų sąrašą viduje. Siekiant supaprastinti kodą, visa funkcionalumas apibrėžtas pagrindinėje klasėje `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": [
|
||||
"Mūsų sistemoje darbinė atmintis turėtų faktų sąrašą kaip **atributų-reikšmių poras**. Žinių bazė gali būti apibrėžta kaip didelis žodynas, kuris susieja veiksmus (naujus faktus, kuriuos reikia įterpti į darbinę atmintį) su sąlygomis, išreikštomis AND-OR išraiškomis. Taip pat kai kurių faktų galima `paklausti`.\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": [
|
||||
"Norint atlikti atvirkštinę išvadą, mes apibrėšime klasę `Knowledgebase`. Ji apims:\n",
|
||||
"* Darbinę `atmintį` - žodyną, kuris susieja atributus su reikšmėmis\n",
|
||||
"* Žinių bazės `taisykles` pagal aukščiau apibrėžtą formatą\n",
|
||||
"\n",
|
||||
"Du pagrindiniai metodai yra:\n",
|
||||
"* `get`, skirtas gauti atributo reikšmę, atliekant išvadą, jei reikia. Pavyzdžiui, `get('color')` gautų spalvos reikšmę (jei reikia, paklaustų ir išsaugotų reikšmę vėlesniam naudojimui darbinėje atmintyje). Jei klausiame `get('color:blue')`, tai paklaus spalvos ir grąžins `y`/`n` reikšmę, priklausomai nuo spalvos.\n",
|
||||
"* `eval` atlieka faktinę išvadą, t. y. pereina per AND/OR medį, įvertina sub-tikslus ir pan.\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": [
|
||||
"Dabar apibrėžkime mūsų gyvūnų žinių bazę ir atlikime konsultaciją. Atkreipkite dėmesį, kad šis procesas užduos jums klausimus. Galite atsakyti įvesdami `y`/`n` atsakymus į taip-ne klausimus arba nurodydami skaičių (0..N) klausimams su ilgesniais daugybinio pasirinkimo atsakymais.\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": [
|
||||
"## Naudojant PyKnow priekinei išvadai\n",
|
||||
"\n",
|
||||
"Kitame pavyzdyje bandysime įgyvendinti priekinę išvadą naudodami vieną iš žinių atvaizdavimo bibliotekų, [PyKnow](https://github.com/buguroo/pyknow/). **PyKnow** yra biblioteka, skirta kurti priekinių išvadų sistemas Python kalboje, kuri yra sukurta taip, kad būtų panaši į klasikinę seną sistemą [CLIPS](http://www.clipsrules.net/index.html).\n",
|
||||
"\n",
|
||||
"Mes taip pat galėjome patys įgyvendinti priekinį grandinavimą be didelių problemų, tačiau naivūs įgyvendinimai paprastai nėra labai efektyvūs. Dėl efektyvesnio taisyklių atitikimo naudojamas specialus algoritmas [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": [
|
||||
"Mes apibrėšime savo sistemą kaip klasę, kuri paveldi `KnowledgeEngine`. Kiekviena taisyklė apibrėžiama atskira funkcija su `@Rule` anotacija, kuri nurodo, kada taisyklė turėtų būti vykdoma. Taisyklės viduje galime pridėti naujus faktus naudodami `declare` funkciją, o pridėjus tuos faktus, į priekį išvedimo variklis iškvies daugiau taisyklių.\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": [
|
||||
"Kai apibrėžiame žinių bazę, užpildome darbinę atmintį pradiniais faktais, o tada iškviečiame `run()` metodą, kad atliktume išvadų darymą. Rezultate galite matyti, kad nauji išvesti faktai pridedami į darbinę atmintį, įskaitant galutinį faktą apie gyvūną (jei teisingai nustatėme visus pradinius faktus).\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**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama profesionali žmogaus vertimo paslauga. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\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-31T13:16:05+00:00",
|
||||
"source_file": "lessons/2-Symbolic/Animals.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,595 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"source": [
|
||||
"# Šeimos Santykių Ontologija\n",
|
||||
"\n",
|
||||
"Šis pavyzdys yra [AI pradedantiesiems mokymo programos](http://github.com/microsoft/ai-for-beginners) dalis ir buvo įkvėptas [šio tinklaraščio įrašo](https://habr.com/post/270857/).\n",
|
||||
"\n",
|
||||
"Man visada sunku prisiminti skirtingus žmonių santykius šeimoje. Šiame pavyzdyje mes naudosime ontologiją, kuri apibrėžia šeimos santykius, ir tikrą genealoginį medį, kad parodytume, kaip galima automatiškai nustatyti visus giminaičius.\n",
|
||||
"\n",
|
||||
"### Genealoginio Medžio Gavimas\n",
|
||||
"\n",
|
||||
"Kaip pavyzdį, naudosime [Romanovų carų šeimos](https://en.wikipedia.org/wiki/House_of_Romanov) genealoginį medį. Dažniausiai naudojamas formatas šeimos santykiams aprašyti yra [GEDCOM](https://en.wikipedia.org/wiki/GEDCOM). Naudosime Romanovų šeimos medį GEDCOM formatu:\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": [
|
||||
"Norint naudoti GEDCOM failą, galima naudoti `python-gedcom` biblioteką:\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": [
|
||||
"Ši biblioteka pašalina kai kurias technines problemas, susijusias su failų analizavimu, tačiau vis tiek suteikia gana žemo lygio prieigą prie visų asmenų ir šeimų medyje. Štai kaip galime analizuoti failą ir parodyti visų asmenų sąrašą:\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": [
|
||||
"Štai kaip galime gauti informaciją apie šeimas. Atkreipkite dėmesį, kad tai pateikia **identifikatorių** sąrašą, ir mums reikia juos konvertuoti į vardus, jei norime daugiau aiškumo:\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": [
|
||||
"### Šeimos ontologijos gavimas\n",
|
||||
"\n",
|
||||
"Dabar pažvelkime į [šeimos ontologiją](https://raw.githubusercontent.com/blokhin/genealogical-trees/master/data/header.ttl), apibrėžtą kaip Semantinio tinklo trigubai. Ši ontologija apibrėžia tokius ryšius kaip `isUncleOf`, `isCousinOf` ir daugelį kitų. Visi šie ryšiai yra apibrėžti naudojant pagrindinius predikatus `isMotherOf`, `isFatherOf`, `isBrotherOf` ir `isSisterOf`. Naudosime automatinį samprotavimą, kad iš ontologijos išvestume visus kitus ryšius.\n",
|
||||
"\n",
|
||||
"Štai pavyzdinis `isAuntOf` savybės apibrėžimas, kuris yra sudarytas iš `isSisterOf` ir `isParentOf` (*Teta yra vieno iš tėvų sesuo*).\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"fhkb:isAuntOf a owl:ObjectProperty ;\n",
|
||||
" rdfs:domain fhkb:Woman ;\n",
|
||||
" rdfs:range fhkb:Person ;\n",
|
||||
" owl:propertyChainAxiom ( fhkb:isSisterOf fhkb:isParentOf ) .\n",
|
||||
"```\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {
|
||||
"trusted": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"@prefix fhkb: <http://www.example.com/genealogy.owl#> .\n",
|
||||
"@prefix owl: <http://www.w3.org/2002/07/owl#> .\n",
|
||||
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n",
|
||||
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n",
|
||||
"@prefix xml: <http://www.w3.org/XML/1998/namespace> .\n",
|
||||
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n",
|
||||
"\n",
|
||||
"<http://www.example.com/genealogy.owl#> a owl:Ontology .\n",
|
||||
"\n",
|
||||
"fhkb:DomainEntity a owl:Class .\n",
|
||||
"\n",
|
||||
"fhkb:Man a owl:Class ;\n",
|
||||
" owl:equivalentClass [ a owl:Class ;\n",
|
||||
" owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;\n",
|
||||
" owl:onProperty fhkb:hasSex ;\n",
|
||||
" owl:someValuesFrom fhkb:Male ] ) ] .\n",
|
||||
"\n",
|
||||
"fhkb:Woman a owl:Class ;\n",
|
||||
" owl:equivalentClass [ a owl:Class ;\n",
|
||||
" owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!head -20 data/onto.ttl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Ontologijos kūrimas išvedimui\n",
|
||||
"\n",
|
||||
"Siekiant paprastumo, sukursime vieną ontologijos failą, kuriame bus originalios taisyklės iš šeimos ontologijos ir faktai apie asmenis iš mūsų GEDCOM failo. Peržiūrėsime GEDCOM failą, ištrauksime informaciją apie šeimas ir asmenis, ir konvertuosime ją į trikampius.\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": [
|
||||
"### Atlikti išvadą\n",
|
||||
"\n",
|
||||
"Dabar norime naudoti šią ontologiją išvadoms ir užklausoms. Naudosime [RDFLib](https://github.com/RDFLib), biblioteką, skirtą RDF grafams skaityti įvairiais formatais, užklausoms ir pan.\n",
|
||||
"\n",
|
||||
"Loginei išvadai atlikti naudosime [OWL-RL](https://github.com/RDFLib/OWL-RL) biblioteką, kuri leidžia sukurti **Uždarymą** RDF grafo, t. y. pridėti visas galimas sąvokas ir ryšius, kurie gali būti išvesti.\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": [
|
||||
"Atidarykime ontologijos failą ir pažiūrėkime, kiek trikampių jis turi:\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": [
|
||||
"Dabar sukurkime uždarąją sritį ir pažiūrėkime, kaip padidėja trigubų skaičius:\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": [
|
||||
"### Užklausos apie giminaičius\n",
|
||||
"\n",
|
||||
"Dabar galime atlikti užklausas grafui, kad pamatytume skirtingus ryšius tarp žmonių. Galime naudoti **SPARQL** kalbą kartu su `query` metodu. Mūsų atveju, pažiūrėkime visus **dėdes** mūsų šeimos medyje:\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": [
|
||||
"Drąsiai eksperimentuokite su įvairiais kitais šeimos ryšiais. Pavyzdžiui, galite peržiūrėti ryšį `isAncestorOf`, kuris rekursyviai apibrėžia visus tam tikro asmens protėvius.\n",
|
||||
"\n",
|
||||
"Galiausiai, užbaikime!\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**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama profesionali žmogaus vertimo paslauga. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\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-31T13:14:07+00:00",
|
||||
"source_file": "lessons/2-Symbolic/FamilyOntology.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -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/) yra didelė terminų taksonomija, išgauta iš interneto, su `is-a` ryšiais tarp sąvokų.\n",
|
||||
"\n",
|
||||
"Context Graph galima naudoti dviem būdais:\n",
|
||||
" * Didelis tekstinis failas atsisiuntimui\n",
|
||||
" * REST API\n",
|
||||
"\n",
|
||||
"Statistika:\n",
|
||||
" * 5401933 unikalios sąvokos,\n",
|
||||
" * 12551613 unikalūs pavyzdžiai\n",
|
||||
" * 87603947 `is-a` ryšiai\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Naudojimasis žiniatinklio paslauga\n",
|
||||
"\n",
|
||||
"Žiniatinklio paslauga siūlo įvairius užklausų tipus, leidžiančius įvertinti tikimybę, kad tam tikra sąvoka priklauso skirtingoms grupėms. Daugiau informacijos rasite [čia](https://concept.research.microsoft.com/Home/Api). \n",
|
||||
"Štai pavyzdinis URL užklausai: `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": [
|
||||
"Pabandykime suskirstyti naujienų antraštes pagal pagrindines sąvokas. Norėdami gauti naujienų antraštes, naudosime [NewsApi.org](http://newsapi.org) paslaugą. Jums reikės gauti savo API raktą, kad galėtumėte naudotis paslauga - eikite į svetainę ir užsiregistruokite nemokamam kūrėjo planui.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {
|
||||
"trusted": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"newsapi_key = '<your API key here>'\n",
|
||||
"def get_news(country='us'):\n",
|
||||
" res = json.loads(http(\"https://newsapi.org/v2/top-headlines?country={0}&apiKey={1}\".format(country,newsapi_key)))\n",
|
||||
" return res['articles']\n",
|
||||
"\n",
|
||||
"all_titles = [x['title'] for x in get_news('us')+get_news('gb')]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['Covid-19 Live Updates: Vaccines and Boosters News - The New York Times',\n",
|
||||
" 'Ukrainians Flee Mariupol as Russian Forces Push to Take Port City - The Wall Street Journal',\n",
|
||||
" 'Bond Yields Jump, Stock Futures Rise After Powell Says Fed Is Ready to Be More Aggressive - The Wall Street Journal',\n",
|
||||
" 'Putin critic Alexei Navalny found guilty by Russian court - New York Post ',\n",
|
||||
" \"Supreme Court nominee Ketanji Brown Jackson will face questions at confirmation hearing's second day - CNN\",\n",
|
||||
" '2 teachers killed at Swedish high school, student arrested - ABC News',\n",
|
||||
" 'Clues to Covid-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": [
|
||||
"Visų pirma, norime išgauti daiktavardžius iš naujienų antraščių. Tam naudosime biblioteką `TextBlob`, kuri labai supaprastina daugelį tipinių NLP užduočių, tokių kaip ši.\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": [
|
||||
"Mes matome, kad daiktavardžiai nesuteikia mums didelių teminių grupių. Pakeiskime daiktavardžius bendresniais terminais, gautais iš koncepcijų grafiko. Tai užtruks šiek tiek laiko, nes kiekvienai daiktavardžio frazei atliekame REST užklausą.\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**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama profesionali žmogaus vertimo paslauga. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius naudojant šį vertimą.\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-31T13:15:02+00:00",
|
||||
"source_file": "lessons/2-Symbolic/MSConceptGraph.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,183 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# MNIST skaitmenų klasifikavimas naudojant mūsų pačių sistemą\n",
|
||||
"\n",
|
||||
"Laboratorinis darbas iš [AI pradedantiesiems mokymo programos](https://github.com/microsoft/ai-for-beginners).\n",
|
||||
"\n",
|
||||
"### Duomenų rinkinio skaitymas\n",
|
||||
"\n",
|
||||
"Šis kodas atsisiunčia duomenų rinkinį iš internetinio saugyklos. Taip pat galite rankiniu būdu nukopijuoti duomenų rinkinį iš `/data` katalogo AI mokymo programos saugykloje.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" % Total % Received % Xferd Average Speed Time Time Time Current\n",
|
||||
" Dload Upload Total Spent Left Speed\n",
|
||||
"\n",
|
||||
" 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\n",
|
||||
"100 9.9M 100 9.9M 0 0 9.9M 0 0:00:01 --:--:-- 0:00:01 15.8M\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"!rm *.pkl\n",
|
||||
"!wget https://raw.githubusercontent.com/microsoft/AI-For-Beginners/main/data/mnist.pkl.gz\n",
|
||||
"!gzip -d mnist.pkl.gz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pickle\n",
|
||||
"with open('mnist.pkl','rb') as f:\n",
|
||||
" MNIST = pickle.load(f)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"labels = MNIST['Train']['Labels']\n",
|
||||
"data = MNIST['Train']['Features']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pažiūrėkime, kokia yra turimų duomenų forma:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(42000, 784)"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"data.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Duomenų skaidymas\n",
|
||||
"\n",
|
||||
"Naudosime Scikit Learn, kad padalintume duomenis į mokymo ir testavimo rinkinius:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Train samples: 33600, test samples: 8400\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.model_selection import train_test_split\n",
|
||||
"\n",
|
||||
"features_train, features_test, labels_train, labels_test = train_test_split(data,labels,test_size=0.2)\n",
|
||||
"\n",
|
||||
"print(f\"Train samples: {len(features_train)}, test samples: {len(features_test)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Instrukcijos\n",
|
||||
"\n",
|
||||
"1. Paimkite pamokos pagrindinį kodą ir įklijuokite jį į šį užrašų knygelę arba (dar geriau) į atskirą Python modulį.\n",
|
||||
"1. Apibrėžkite ir apmokykite vieno sluoksnio perceptroną, stebėdami mokymo ir validacijos tikslumą mokymo metu.\n",
|
||||
"1. Pabandykite suprasti, ar įvyko permokymas, ir pakoreguokite sluoksnio parametrus, kad pagerintumėte tikslumą.\n",
|
||||
"1. Pakartokite ankstesnius veiksmus 2 ir 3 sluoksnių perceptronams. Eksperimentuokite su skirtingomis aktyvavimo funkcijomis tarp sluoksnių.\n",
|
||||
"1. Pabandykite atsakyti į šiuos klausimus:\n",
|
||||
" - Ar tarpsluoksninė aktyvavimo funkcija turi įtakos tinklo veikimui?\n",
|
||||
" - Ar šiai užduočiai reikia 2 ar 3 sluoksnių tinklo?\n",
|
||||
" - Ar susidūrėte su kokiomis nors problemomis treniruojant tinklą? Ypač kai sluoksnių skaičius padidėjo.\n",
|
||||
" - Kaip tinklo svoriai elgiasi mokymo metu? Galite nubrėžti didžiausios absoliučios svorių reikšmės ir epochos santykį, kad suprastumėte ryšį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama profesionali žmogaus vertimo paslauga. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus aiškinimus, kilusius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.7.4 64-bit (conda)",
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "86193a1ab0ba47eac1c69c1756090baa3b420b3eea7d4aafab8b85f8b312f0c5"
|
||||
}
|
||||
},
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.5"
|
||||
},
|
||||
"orig_nbformat": 2,
|
||||
"coopTranslator": {
|
||||
"original_hash": "6fa055f484eb5d6bdf41166a356d3abf",
|
||||
"translation_date": "2025-08-31T13:21:47+00:00",
|
||||
"source_file": "lessons/3-NeuralNetworks/04-OwnFramework/lab/MyFW_MNIST.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,108 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Delno judėjimo aptikimas naudojant optinį srautą\n",
|
||||
"\n",
|
||||
"Šis praktinis darbas yra dalis [Dirbtinio intelekto pradedantiesiems mokymo programos](http://aka.ms/ai-beginners).\n",
|
||||
"\n",
|
||||
"Apsvarstykite [šį vaizdo įrašą](../../../../../../lessons/4-ComputerVision/06-IntroCV/lab/palm-movement.mp4), kuriame žmogaus delnas juda į kairę/dešinę/aukštyn/žemyn stabiliame fone.\n",
|
||||
"\n",
|
||||
"**Jūsų tikslas** būtų naudoti optinį srautą, kad nustatytumėte, kurios vaizdo įrašo dalys rodo judesius aukštyn/žemyn/kairėn/dešinėn.\n",
|
||||
"\n",
|
||||
"Pradėkite nuo vaizdo įrašo kadrų gavimo, kaip aprašyta paskaitoje:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar apskaičiuokite tankų optinį srautą pagal paskaitoje aprašytą metodiką ir konvertuokite tankų optinį srautą į polines koordinatės:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Sukurkite optinio srauto kadrų krypčių histogramą. Histogramoje parodoma, kiek vektorių patenka į tam tikrą intervalą, ir ji turėtų atskirti skirtingas judėjimo kryptis kadre.\n",
|
||||
"\n",
|
||||
"> Taip pat galite nustatyti, kad visi vektoriai, kurių dydis yra mažesnis už tam tikrą slenkstį, būtų nustatyti į nulį. Tai pašalins mažus nereikšmingus judesius vaizdo įraše, tokius kaip akių ar galvos judesiai.\n",
|
||||
"\n",
|
||||
"Nubraižykite histogramas kai kuriems kadrams.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Žiūrint į histogramas, turėtų būti gana paprasta nustatyti judėjimo kryptį. Reikia pasirinkti tas juostas, kurios atitinka aukštyn/žemyn/kairėn/dešinėn kryptis ir kurios viršija tam tikrą slenkstį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Code here"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Sveikiname! Jei atlikote visus aukščiau nurodytus veiksmus, jūs baigėte laboratoriją!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant dirbtinio intelekto vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, atkreipkite dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Dėl svarbios informacijos rekomenduojama kreiptis į profesionalius vertėjus. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus aiškinimus, kylančius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"coopTranslator": {
|
||||
"original_hash": "153d9e417e079bf62f8f693002d0deaf",
|
||||
"translation_date": "2025-08-31T12:57:14+00:00",
|
||||
"source_file": "lessons/4-ComputerVision/06-IntroCV/lab/MovementDetection.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,577 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Teksto klasifikavimo užduotis\n",
|
||||
"\n",
|
||||
"Kaip jau minėjome, mes sutelksime dėmesį į paprastą teksto klasifikavimo užduotį, pagrįstą **AG_NEWS** duomenų rinkiniu, kurio tikslas yra klasifikuoti naujienų antraštes į vieną iš 4 kategorijų: Pasaulis, Sportas, Verslas ir Mokslas/Technologijos.\n",
|
||||
"\n",
|
||||
"## Duomenų rinkinys\n",
|
||||
"\n",
|
||||
"Šis duomenų rinkinys yra integruotas į [`torchtext`](https://github.com/pytorch/text) modulį, todėl galime lengvai jį pasiekti.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import os\n",
|
||||
"import collections\n",
|
||||
"os.makedirs('./data',exist_ok=True)\n",
|
||||
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
|
||||
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Čia `train_dataset` ir `test_dataset` yra kolekcijos, kurios grąžina poras: etiketę (klasės numerį) ir tekstą atitinkamai, pavyzdžiui:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(3,\n",
|
||||
" \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"list(train_dataset)[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Taigi, atspausdinkime pirmąsias 10 naujų antraščių iš mūsų duomenų rinkinio:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n",
|
||||
"**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n",
|
||||
"**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n",
|
||||
"**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n",
|
||||
"**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for i,x in zip(range(5),train_dataset):\n",
|
||||
" print(f\"**{classes[x[0]]}** -> {x[1]}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Kadangi duomenų rinkiniai yra iteratoriai, jei norime naudoti duomenis kelis kartus, turime juos konvertuoti į sąrašą:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
|
||||
"train_dataset = list(train_dataset)\n",
|
||||
"test_dataset = list(test_dataset)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Tokenizacija\n",
|
||||
"\n",
|
||||
"Dabar turime paversti tekstą į **skaičius**, kuriuos galima atvaizduoti kaip tensorius. Jei norime žodžių lygmens reprezentacijos, turime atlikti du dalykus:\n",
|
||||
"* naudoti **tokenizatorių**, kad tekstas būtų padalintas į **tokenus**\n",
|
||||
"* sukurti tų tokenų **žodyną**.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['he', 'said', 'hello']"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
|
||||
"tokenizer('He said: hello')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"counter = collections.Counter()\n",
|
||||
"for (label, line) in train_dataset:\n",
|
||||
" counter.update(tokenizer(line))\n",
|
||||
"vocab = torchtext.vocab.vocab(counter, min_freq=1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Naudodami žodyną, galime lengvai užkoduoti savo suskaidytą eilutę į skaičių rinkinį:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocab size if 95810\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[599, 3279, 97, 1220, 329, 225, 7368]"
|
||||
]
|
||||
},
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(f\"Vocab size if {vocab_size}\")\n",
|
||||
"\n",
|
||||
"stoi = vocab.get_stoi() # dict to convert tokens to indices\n",
|
||||
"\n",
|
||||
"def encode(x):\n",
|
||||
" return [stoi[s] for s in tokenizer(x)]\n",
|
||||
"\n",
|
||||
"encode('I love to play with my words')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Žodžių maišo teksto reprezentacija\n",
|
||||
"\n",
|
||||
"Kadangi žodžiai perteikia prasmę, kartais teksto prasmę galime suprasti tiesiog pažvelgę į atskirus žodžius, nepaisant jų tvarkos sakinyje. Pavyzdžiui, klasifikuojant naujienas, tokie žodžiai kaip *oras*, *sniegas* greičiausiai nurodys *orų prognozę*, o žodžiai kaip *akcijos*, *doleris* būtų susiję su *finansinėmis naujienomis*.\n",
|
||||
"\n",
|
||||
"**Žodžių maišo** (BoW) vektorinė reprezentacija yra dažniausiai naudojama tradicinė vektorinė reprezentacija. Kiekvienas žodis yra susietas su vektoriaus indeksu, o vektoriaus elementas nurodo, kiek kartų žodis pasirodo tam tikrame dokumente.\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"> **Note**: Taip pat galite galvoti apie BoW kaip apie visų vieno žodžio koduotų vektorių sumą tekste.\n",
|
||||
"\n",
|
||||
"Žemiau pateiktas pavyzdys, kaip sukurti žodžių maišo reprezentaciją naudojant Scikit Learn python biblioteką:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
||||
"vectorizer = CountVectorizer()\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"vectorizer.fit_transform(corpus)\n",
|
||||
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Norėdami apskaičiuoti žodžių maišo vektorių iš mūsų AG_NEWS duomenų rinkinio vektorinės reprezentacijos, galime naudoti šią funkciją:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"tensor([2., 1., 2., ..., 0., 0., 0.])\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = len(vocab)\n",
|
||||
"\n",
|
||||
"def to_bow(text,bow_vocab_size=vocab_size):\n",
|
||||
" res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n",
|
||||
" for i in encode(text):\n",
|
||||
" if i<bow_vocab_size:\n",
|
||||
" res[i] += 1\n",
|
||||
" return res\n",
|
||||
"\n",
|
||||
"print(to_bow(train_dataset[0][1]))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba:** Čia naudojame globalųjį kintamąjį `vocab_size`, kad nurodytume numatytąjį žodyno dydį. Kadangi dažnai žodyno dydis yra gana didelis, galime apriboti žodyno dydį iki dažniausiai vartojamų žodžių. Pabandykite sumažinti `vocab_size` reikšmę ir paleisti žemiau pateiktą kodą, kad pamatytumėte, kaip tai veikia tikslumą. Turėtumėte tikėtis tam tikro tikslumo sumažėjimo, tačiau ne drastiško, mainais už didesnį našumą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Mokome BoW klasifikatorių\n",
|
||||
"\n",
|
||||
"Dabar, kai išmokome sukurti Bag-of-Words (BoW) reprezentaciją mūsų tekstui, apmokykime klasifikatorių, naudodami šią reprezentaciją. Pirmiausia turime konvertuoti savo duomenų rinkinį mokymui taip, kad visos pozicinės vektorinės reprezentacijos būtų paverstos į Bag-of-Words reprezentaciją. Tai galima padaryti perduodant funkciją `bowify` kaip `collate_fn` parametrą standartiniam torch `DataLoader`:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from torch.utils.data import DataLoader\n",
|
||||
"import numpy as np \n",
|
||||
"\n",
|
||||
"# this collate function gets list of batch_size tuples, and needs to \n",
|
||||
"# return a pair of label-feature tensors for the whole minibatch\n",
|
||||
"def bowify(b):\n",
|
||||
" return (\n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||||
" torch.stack([to_bow(t[1]) for t in b])\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
|
||||
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar apibrėžkime paprastą klasifikatoriaus neuroninį tinklą, kuris turi vieną linijinį sluoksnį. Įvesties vektoriaus dydis yra lygus `vocab_size`, o išvesties dydis atitinka klasių skaičių (4). Kadangi sprendžiame klasifikavimo užduotį, galutinė aktyvavimo funkcija yra `LogSoftmax()`.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar apibrėšime standartinį PyTorch mokymo ciklą. Kadangi mūsų duomenų rinkinys yra gana didelis, mokymo tikslais treniruosime tik vieną epochą, o kartais net mažiau nei vieną epochą (nustatant `epoch_size` parametrą galima apriboti mokymą). Taip pat pranešime apie sukauptą mokymo tikslumą mokymo metu; pranešimo dažnis nustatomas naudojant `report_freq` parametrą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
|
||||
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
||||
" net.train()\n",
|
||||
" total_loss,acc,count,i = 0,0,0,0\n",
|
||||
" for labels,features in dataloader:\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" out = net(features)\n",
|
||||
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" total_loss+=loss\n",
|
||||
" _,predicted = torch.max(out,1)\n",
|
||||
" acc+=(predicted==labels).sum()\n",
|
||||
" count+=len(labels)\n",
|
||||
" i+=1\n",
|
||||
" if i%report_freq==0:\n",
|
||||
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
||||
" if epoch_size and count>epoch_size:\n",
|
||||
" break\n",
|
||||
" return total_loss.item()/count, acc.item()/count"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.8028125\n",
|
||||
"6400: acc=0.8371875\n",
|
||||
"9600: acc=0.8534375\n",
|
||||
"12800: acc=0.85765625\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.026090790722161722, 0.8620069296375267)"
|
||||
]
|
||||
},
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_epoch(net,train_loader,epoch_size=15000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## BiGramai, TriGramai ir N-Gramai\n",
|
||||
"\n",
|
||||
"Viena iš maišo žodžių metodo apribojimų yra ta, kad kai kurie žodžiai sudaro daugiakalbius posakius. Pavyzdžiui, žodis „hot dog“ turi visiškai kitokią reikšmę nei žodžiai „hot“ ir „dog“ kitame kontekste. Jei žodžius „hot“ ir „dog“ visada atvaizduosime tais pačiais vektoriais, tai gali suklaidinti mūsų modelį.\n",
|
||||
"\n",
|
||||
"Norint tai išspręsti, dokumentų klasifikavimo metodai dažnai naudoja **N-gramų reprezentacijas**, kur kiekvieno žodžio, dviejų žodžių ar trijų žodžių dažnis yra naudinga savybė mokant klasifikatorius. Pavyzdžiui, bigramų reprezentacijoje mes į žodyną pridėsime visas žodžių poras, be originalių žodžių.\n",
|
||||
"\n",
|
||||
"Žemiau pateiktas pavyzdys, kaip sukurti bigramų maišo žodžių reprezentaciją naudojant Scikit Learn:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocabulary:\n",
|
||||
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"bigram_vectorizer.fit_transform(corpus)\n",
|
||||
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
|
||||
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pagrindinis N-gram metodo trūkumas yra tas, kad žodyno dydis pradeda augti itin greitai. Praktikoje reikia derinti N-gram reprezentaciją su tam tikromis dimensijų mažinimo technikomis, tokiomis kaip *embedding'ai*, apie kurias kalbėsime kitame skyriuje.\n",
|
||||
"\n",
|
||||
"Norint naudoti N-gram reprezentaciją mūsų **AG News** duomenų rinkinyje, reikia sukurti specialų ngram žodyną:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Bigram vocabulary length = 1308842\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"counter = collections.Counter()\n",
|
||||
"for (label, line) in train_dataset:\n",
|
||||
" l = tokenizer(line)\n",
|
||||
" counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n",
|
||||
" \n",
|
||||
"bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n",
|
||||
"\n",
|
||||
"print(\"Bigram vocabulary length = \",len(bi_vocab))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Tą patį kodą galėtume naudoti klasifikatoriui mokyti, tačiau tai būtų labai neefektyvu atminties atžvilgiu. Kitame skyriuje mokysime bigramų klasifikatorių naudodami įterpimus.\n",
|
||||
"\n",
|
||||
"> **Pastaba:** Galite palikti tik tuos ngramus, kurie tekste pasirodo daugiau nei nurodytą kartų skaičių. Tai užtikrins, kad retai pasitaikantys bigramai bus praleisti, ir žymiai sumažins dimensionalumą. Norėdami tai padaryti, nustatykite `min_freq` parametrą į didesnę reikšmę ir stebėkite, kaip keičiasi žodyno ilgis.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Termų dažnio ir atvirkštinio dokumentų dažnio metodas (TF-IDF)\n",
|
||||
"\n",
|
||||
"BoW (maišo žodžių) reprezentacijoje žodžių pasikartojimai yra vertinami vienodai, nepriklausomai nuo paties žodžio. Tačiau akivaizdu, kad dažnai pasitaikantys žodžiai, tokie kaip *a*, *in* ir pan., yra daug mažiau svarbūs klasifikacijai nei specializuoti terminai. Iš tiesų, daugelyje NLP užduočių kai kurie žodžiai yra reikšmingesni nei kiti.\n",
|
||||
"\n",
|
||||
"**TF-IDF** reiškia **termų dažnio–atvirkštinio dokumentų dažnio metodą**. Tai yra maišo žodžių variacija, kur vietoj dvejetainės 0/1 reikšmės, nurodančios žodžio pasirodymą dokumente, naudojama slankiojo kablelio reikšmė, susijusi su žodžio pasikartojimo dažniu korpuse.\n",
|
||||
"\n",
|
||||
"Formaliau, žodžio $i$ svoris dokumente $j$ apibrėžiamas taip:\n",
|
||||
"$$\n",
|
||||
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
||||
"$$\n",
|
||||
"kur:\n",
|
||||
"* $tf_{ij}$ yra žodžio $i$ pasikartojimų skaičius dokumente $j$, t. y. BoW reikšmė, kurią jau aptarėme\n",
|
||||
"* $N$ yra dokumentų skaičius kolekcijoje\n",
|
||||
"* $df_i$ yra dokumentų, kuriuose yra žodis $i$, skaičius visoje kolekcijoje\n",
|
||||
"\n",
|
||||
"TF-IDF reikšmė $w_{ij}$ didėja proporcingai žodžio pasikartojimų skaičiui dokumente ir yra koreguojama pagal dokumentų skaičių korpuse, kuriuose yra tas žodis. Tai padeda atsižvelgti į tai, kad kai kurie žodžiai pasitaiko dažniau nei kiti. Pavyzdžiui, jei žodis pasirodo *kiekviename* kolekcijos dokumente, $df_i=N$, ir $w_{ij}=0$, todėl tokie terminai būtų visiškai ignoruojami.\n",
|
||||
"\n",
|
||||
"TF-IDF tekstų vektorizaciją galite lengvai sukurti naudodami Scikit Learn:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
|
||||
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. ]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
|
||||
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
|
||||
"vectorizer.fit_transform(corpus)\n",
|
||||
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Išvada\n",
|
||||
"\n",
|
||||
"Nors TF-IDF reprezentacijos suteikia skirtingiems žodžiams dažnio svorį, jos nesugeba perteikti prasmės ar tvarkos. Kaip garsus lingvistas J. R. Firth 1935 m. pasakė: „Visapusiška žodžio prasmė visada yra kontekstinė, ir joks prasmės tyrimas, atskirtas nuo konteksto, negali būti laikomas rimtu.“ Vėliau kurse sužinosime, kaip iš teksto išgauti kontekstinę informaciją naudojant kalbos modeliavimą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "7b9040985e748e4e2d4c689892456ad7",
|
||||
"translation_date": "2025-08-31T14:09:52+00:00",
|
||||
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,647 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Teksto klasifikavimo užduotis\n",
|
||||
"\n",
|
||||
"Šiame modulyje pradėsime nuo paprastos teksto klasifikavimo užduoties, remdamiesi **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)** duomenų rinkiniu: klasifikuosime naujienų antraštes į vieną iš 4 kategorijų: Pasaulis, Sportas, Verslas ir Mokslas/Technologijos.\n",
|
||||
"\n",
|
||||
"## Duomenų rinkinys\n",
|
||||
"\n",
|
||||
"Norėdami įkelti duomenų rinkinį, naudosime **[TensorFlow Datasets](https://www.tensorflow.org/datasets)** API.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"\n",
|
||||
"# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n",
|
||||
"# we will set tensorflow option to grow GPU memory allocation when required.\n",
|
||||
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
||||
"if len(physical_devices)>0:\n",
|
||||
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
||||
"\n",
|
||||
"dataset = tfds.load('ag_news_subset')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar galime pasiekti mokymo ir testavimo duomenų rinkinio dalis naudodami `dataset['train']` ir `dataset['test']` atitinkamai:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Length of train dataset = 120000\n",
|
||||
"Length of test dataset = 7600\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ds_train = dataset['train']\n",
|
||||
"ds_test = dataset['test']\n",
|
||||
"\n",
|
||||
"print(f\"Length of train dataset = {len(ds_train)}\")\n",
|
||||
"print(f\"Length of test dataset = {len(ds_test)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Išspausdinkime pirmąsias 10 naujų antraščių iš mūsų duomenų rinkinio:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n",
|
||||
"1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n",
|
||||
"2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n",
|
||||
"3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n",
|
||||
"1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
||||
"\n",
|
||||
"for i,x in zip(range(5),ds_train):\n",
|
||||
" print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Teksto vektorizacija\n",
|
||||
"\n",
|
||||
"Dabar turime konvertuoti tekstą į **skaičius**, kurie gali būti pateikti kaip tensoriai. Jei norime žodžių lygmens reprezentacijos, turime atlikti du dalykus:\n",
|
||||
"\n",
|
||||
"* Naudoti **tokenizatorių**, kad tekstas būtų padalintas į **tokenus**.\n",
|
||||
"* Sukurti tų tokenų **žodyną**.\n",
|
||||
"\n",
|
||||
"### Žodyno dydžio ribojimas\n",
|
||||
"\n",
|
||||
"AG News duomenų rinkinio pavyzdyje žodyno dydis yra gana didelis – daugiau nei 100 tūkst. žodžių. Apskritai, mums nereikia žodžių, kurie tekste pasitaiko retai — tik keliose sakiniuose jie bus, o modelis iš jų nesimokys. Todėl logiška apriboti žodyno dydį iki mažesnio skaičiaus, perduodant argumentą vektorizatoriaus konstruktoriui:\n",
|
||||
"\n",
|
||||
"Abu šiuos veiksmus galima atlikti naudojant **TextVectorization** sluoksnį. Sukurkime vektorizatoriaus objektą ir tada iškvieskime `adapt` metodą, kad pereitume per visą tekstą ir sukurtume žodyną:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vocab_size = 50000\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n",
|
||||
"vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba**: mes naudojame tik dalį viso duomenų rinkinio, kad sukurtume žodyną. Tai darome tam, kad pagreitintume vykdymo laiką ir nereikėtų jūsų laukti. Tačiau prisiimame riziką, kad kai kurie žodžiai iš viso duomenų rinkinio nebus įtraukti į žodyną ir bus ignoruojami mokymo metu. Taigi, naudojant visą žodyno dydį ir apdorojant visą duomenų rinkinį per `adapt`, galutinis tikslumas turėtų padidėti, bet ne žymiai.\n",
|
||||
"\n",
|
||||
"Dabar galime pasiekti tikrąjį žodyną:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n",
|
||||
"Length of vocabulary: 5335\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab = vectorizer.get_vocabulary()\n",
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(vocab[:10])\n",
|
||||
"print(f\"Length of vocabulary: {vocab_size}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Naudodami vektorizatorių, galime lengvai užkoduoti bet kokį tekstą į skaičių rinkinį:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vectorizer('I love to play with my words')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Žodžių maišo (Bag-of-words) teksto reprezentacija\n",
|
||||
"\n",
|
||||
"Kadangi žodžiai perteikia prasmę, kartais galime suprasti teksto reikšmę tiesiog pažvelgę į atskirus žodžius, nepaisant jų tvarkos sakinyje. Pavyzdžiui, klasifikuojant naujienas, tokie žodžiai kaip *oras* ir *sniegas* greičiausiai nurodys į *orų prognozę*, o žodžiai kaip *akcijos* ir *doleris* bus susiję su *finansinėmis naujienomis*.\n",
|
||||
"\n",
|
||||
"**Žodžių maišo** (BoW) vektorinė reprezentacija yra pati paprasčiausia ir lengviausiai suprantama tradicinė vektorinė reprezentacija. Kiekvienas žodis yra susietas su vektoriaus indeksu, o vektoriaus elementas nurodo, kiek kartų tam tikras žodis pasirodo konkrečiame dokumente.\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"> **Note**: Taip pat galite galvoti apie BoW kaip apie visų vieno žodžio vienetinės koduotės (one-hot-encoded) vektorių sumą tekste.\n",
|
||||
"\n",
|
||||
"Žemiau pateiktas pavyzdys, kaip sugeneruoti žodžių maišo reprezentaciją naudojant Scikit Learn python biblioteką:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
||||
"sc_vectorizer = CountVectorizer()\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"sc_vectorizer.fit_transform(corpus)\n",
|
||||
"sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Mes taip pat galime naudoti aukščiau apibrėžtą Keras vektorizatorių, konvertuodami kiekvieną žodžio numerį į vieno karšto kodavimo formatą ir sudėdami visus tuos vektorius:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def to_bow(text):\n",
|
||||
" return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n",
|
||||
"\n",
|
||||
"to_bow('My dog likes hot dogs on a hot day.').numpy()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba**: Gali nustebinti, kad rezultatas skiriasi nuo ankstesnio pavyzdžio. Taip yra todėl, kad Keras pavyzdyje vektoriaus ilgis atitinka žodyno dydį, kuris buvo sukurtas naudojant visą AG News duomenų rinkinį, o Scikit Learn pavyzdyje žodyną sukūrėme iš pateikto teksto vietoje.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Mokymas BoW klasifikatoriaus\n",
|
||||
"\n",
|
||||
"Dabar, kai išmokome sukurti žodžių maišo (bag-of-words) reprezentaciją mūsų tekstui, pereikime prie klasifikatoriaus mokymo, kuris ja naudojasi. Pirmiausia, turime konvertuoti savo duomenų rinkinį į žodžių maišo reprezentaciją. Tai galima padaryti naudojant `map` funkciją tokiu būdu:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"batch_size = 128\n",
|
||||
"\n",
|
||||
"ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n",
|
||||
"ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar apibrėžkime paprastą klasifikatoriaus neuroninį tinklą, kuris turi vieną linijinį sluoksnį. Įvesties dydis yra `vocab_size`, o išvesties dydis atitinka klasių skaičių (4). Kadangi sprendžiame klasifikavimo užduotį, galutinė aktyvacijos funkcija yra **softmax**:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c70a947f0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n",
|
||||
"])\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train_bow,validation_data=ds_test_bow)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Kadangi turime 4 klases, tikslumas virš 80% yra geras rezultatas.\n",
|
||||
"\n",
|
||||
"## Klasifikatoriaus mokymas kaip vieno tinklo\n",
|
||||
"\n",
|
||||
"Kadangi vektorizatorius taip pat yra Keras sluoksnis, galime apibrėžti tinklą, kuris jį įtraukia, ir mokyti jį nuo pradžios iki pabaigos. Tokiu būdu nereikia vektorizuoti duomenų rinkinio naudojant `map`, tiesiog galime perduoti originalų duomenų rinkinį į tinklo įvestį.\n",
|
||||
"\n",
|
||||
"> **Pastaba**: Vis tiek reikės taikyti `map` mūsų duomenų rinkiniui, kad laukus iš žodynų (pvz., `title`, `description` ir `label`) paverstume į poras. Tačiau, kai duomenys įkeliami iš disko, galime iš karto sukurti duomenų rinkinį su reikiama struktūra.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"model\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
" Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
" input_1 (InputLayer) [(None, 1)] 0 \n",
|
||||
" \n",
|
||||
" text_vectorization (TextVec (None, None) 0 \n",
|
||||
" torization) \n",
|
||||
" \n",
|
||||
" tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n",
|
||||
" \n",
|
||||
" tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n",
|
||||
" bda) \n",
|
||||
" \n",
|
||||
" dense_2 (Dense) (None, 4) 21344 \n",
|
||||
" \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 21,344\n",
|
||||
"Trainable params: 21,344\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n",
|
||||
"938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c721521f0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"inp = keras.Input(shape=(1,),dtype=tf.string)\n",
|
||||
"x = vectorizer(inp)\n",
|
||||
"x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n",
|
||||
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
||||
"model = keras.models.Model(inp,out)\n",
|
||||
"model.summary()\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Bigramai, trigramai ir n-gramai\n",
|
||||
"\n",
|
||||
"Viena iš maišo žodžių metodo apribojimų yra ta, kad kai kurie žodžiai sudaro daugiakalbius posakius. Pavyzdžiui, žodis „hot dog“ turi visiškai kitokią reikšmę nei žodžiai „hot“ ir „dog“ kitame kontekste. Jei žodžius „hot“ ir „dog“ visada atvaizduosime tais pačiais vektoriais, tai gali suklaidinti mūsų modelį.\n",
|
||||
"\n",
|
||||
"Norint tai išspręsti, dokumentų klasifikavimo metodai dažnai naudoja **n-gramų reprezentacijas**, kur kiekvieno žodžio, dviejų žodžių ar trijų žodžių dažnis yra naudinga savybė mokant klasifikatorius. Pavyzdžiui, bigramų reprezentacijoje į žodyną pridedame visas žodžių poras, be originalių žodžių.\n",
|
||||
"\n",
|
||||
"Žemiau pateiktas pavyzdys, kaip sukurti bigramų maišo žodžių reprezentaciją naudojant Scikit Learn:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocabulary:\n",
|
||||
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int64)"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
|
||||
"corpus = [\n",
|
||||
" 'I like hot dogs.',\n",
|
||||
" 'The dog ran fast.',\n",
|
||||
" 'Its hot outside.',\n",
|
||||
" ]\n",
|
||||
"bigram_vectorizer.fit_transform(corpus)\n",
|
||||
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
|
||||
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pagrindinis n-gramų metodo trūkumas yra tas, kad žodyno dydis pradeda augti itin greitai. Praktikoje mums reikia derinti n-gramų reprezentaciją su dimensijų mažinimo technika, tokia kaip *embedding'ai*, apie kuriuos kalbėsime kitame skyriuje.\n",
|
||||
"\n",
|
||||
"Norėdami naudoti n-gramų reprezentaciją mūsų **AG News** duomenų rinkinyje, turime perduoti `ngrams` parametrą mūsų `TextVectorization` konstruktoriui. Bigramų žodyno ilgis yra **žymiai didesnis**, mūsų atveju jis viršija 1,3 milijono žodžių! Todėl yra prasminga apriboti bigramų žodžius iki tam tikro pagrįsto skaičiaus.\n",
|
||||
"\n",
|
||||
"Galėtume naudoti tą patį kodą, kaip ir aukščiau, norėdami apmokyti klasifikatorių, tačiau tai būtų labai neefektyvu atminties atžvilgiu. Kitame skyriuje apmokysime bigramų klasifikatorių naudodami embedding'us. Tuo tarpu galite eksperimentuoti su bigramų klasifikatoriaus mokymu šiame užrašų knygelėje ir pažiūrėti, ar galite pasiekti didesnį tikslumą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Automatinis BoW vektorių skaičiavimas\n",
|
||||
"\n",
|
||||
"Ankstesniame pavyzdyje BoW vektorius skaičiavome rankiniu būdu, sudėdami atskirų žodžių vieno karšto kodavimo rezultatus. Tačiau naujausia TensorFlow versija leidžia automatiškai apskaičiuoti BoW vektorius, perduodant `output_mode='count` parametrą vektorizatoriaus konstruktoriui. Tai žymiai supaprastina mūsų modelio apibrėžimą ir treniravimą:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n",
|
||||
"938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c725217c0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n",
|
||||
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
||||
"])\n",
|
||||
"print(\"Training vectorizer\")\n",
|
||||
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Termino dažnis - atvirkštinis dokumento dažnis (TF-IDF)\n",
|
||||
"\n",
|
||||
"BoW reprezentacijoje žodžių pasikartojimai yra vertinami naudojant tą pačią techniką, nepriklausomai nuo paties žodžio. Tačiau akivaizdu, kad dažni žodžiai, tokie kaip *a* ir *in*, yra daug mažiau svarbūs klasifikacijai nei specializuoti terminai. Daugumoje NLP užduočių kai kurie žodžiai yra reikšmingesni nei kiti.\n",
|
||||
"\n",
|
||||
"**TF-IDF** reiškia **termino dažnis - atvirkštinis dokumento dažnis**. Tai yra maišo su žodžiais (BoW) variacija, kur vietoj dvejetainės 0/1 reikšmės, nurodančios žodžio buvimą dokumente, naudojama slankiojo kablelio reikšmė, susijusi su žodžio pasikartojimo dažniu korpuse.\n",
|
||||
"\n",
|
||||
"Formaliau, žodžio $i$ svoris $w_{ij}$ dokumente $j$ apibrėžiamas taip:\n",
|
||||
"$$\n",
|
||||
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
||||
"$$\n",
|
||||
"kur\n",
|
||||
"* $tf_{ij}$ yra žodžio $i$ pasikartojimų skaičius dokumente $j$, t. y. BoW reikšmė, kurią jau matėme\n",
|
||||
"* $N$ yra dokumentų skaičius kolekcijoje\n",
|
||||
"* $df_i$ yra dokumentų, kuriuose yra žodis $i$, skaičius visoje kolekcijoje\n",
|
||||
"\n",
|
||||
"TF-IDF reikšmė $w_{ij}$ didėja proporcingai žodžio pasikartojimų skaičiui dokumente ir yra koreguojama pagal dokumentų skaičių korpuse, kuriuose yra tas žodis. Tai padeda atsižvelgti į tai, kad kai kurie žodžiai pasikartoja dažniau nei kiti. Pavyzdžiui, jei žodis pasirodo *kiekviename* kolekcijos dokumente, $df_i=N$, ir $w_{ij}=0$, tokie terminai būtų visiškai ignoruojami.\n",
|
||||
"\n",
|
||||
"TF-IDF tekstų vektorizaciją galite lengvai sukurti naudodami Scikit Learn:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
|
||||
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. , 0. , 0. , 0. , 0. ,\n",
|
||||
" 0. ]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
|
||||
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
|
||||
"vectorizer.fit_transform(corpus)\n",
|
||||
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Keras bibliotekoje `TextVectorization` sluoksnis gali automatiškai apskaičiuoti TF-IDF dažnius, perduodant parametrą `output_mode='tf-idf'`. Pakartokime aukščiau naudotą kodą, kad pamatytume, ar TF-IDF naudojimas padidina tikslumą:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n",
|
||||
"938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x20c729dfd30>"
|
||||
]
|
||||
},
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n",
|
||||
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
||||
"])\n",
|
||||
"print(\"Training vectorizer\")\n",
|
||||
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Išvada\n",
|
||||
"\n",
|
||||
"Nors TF-IDF reprezentacijos suteikia skirtingiems žodžiams dažnio svorius, jos nesugeba perteikti prasmės ar tvarkos. Kaip garsus lingvistas J. R. Firth pasakė 1935 m., „Visapusiška žodžio prasmė visada yra kontekstinė, ir joks prasmės tyrimas, atskirtas nuo konteksto, negali būti laikomas rimtu.“ Vėliau kurse išmoksime, kaip iš teksto išgauti kontekstinę informaciją naudojant kalbos modeliavimą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant dirbtinio intelekto vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, atkreipkite dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus aiškinimus, kylančius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||||
},
|
||||
"kernel_info": {
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_tensorflow",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"nteract": {
|
||||
"version": "nteract-front-end@1.0.0"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "19b43951d55b377a76209c24c1f017e4",
|
||||
"translation_date": "2025-08-31T14:12:42+00:00",
|
||||
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,724 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Įterpimai\n",
|
||||
"\n",
|
||||
"Ankstesniame pavyzdyje dirbome su aukštos dimensijos žodžių maišo vektoriais, kurių ilgis yra `vocab_size`, ir aiškiai konvertavome iš žemos dimensijos pozicinių reprezentacijų vektorių į retą vieno elemento reprezentaciją. Ši vieno elemento reprezentacija nėra efektyvi atminties požiūriu, be to, kiekvienas žodis yra traktuojamas nepriklausomai nuo kitų, t. y. vieno elemento užkoduoti vektoriai neišreiškia jokio semantinio panašumo tarp žodžių.\n",
|
||||
"\n",
|
||||
"Šiame skyriuje toliau nagrinėsime **News AG** duomenų rinkinį. Pradėkime įkeldami duomenis ir pasinaudodami kai kuriomis ankstesnio užrašų knygelės apibrėžtimis.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loading dataset...\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
||||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Building vocab...\n",
|
||||
"Vocab size = 95812\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import numpy as np\n",
|
||||
"from torchnlp import *\n",
|
||||
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(\"Vocab size = \",vocab_size)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Kas yra įterpimas?\n",
|
||||
"\n",
|
||||
"Įterpimo (**embedding**) idėja yra atvaizduoti žodžius mažesnės dimensijos tankiais vektoriais, kurie tam tikru būdu atspindi žodžio semantinę reikšmę. Vėliau aptarsime, kaip sukurti prasmingus žodžių įterpimus, tačiau šiuo metu tiesiog galvokime apie įterpimus kaip apie būdą sumažinti žodžio vektoriaus dimensiją.\n",
|
||||
"\n",
|
||||
"Taigi, įterpimo sluoksnis priims žodį kaip įvestį ir pateiks išvesties vektorių su nurodytu `embedding_size`. Tam tikra prasme, tai labai panašu į `Linear` sluoksnį, tačiau vietoj vieno karšto kodavimo vektoriaus jis galės priimti žodžio numerį kaip įvestį.\n",
|
||||
"\n",
|
||||
"Naudodami įterpimo sluoksnį kaip pirmąjį sluoksnį mūsų tinkle, galime pereiti nuo žodžių maišo (bag-of-words) prie **įterpimo maišo** (embedding bag) modelio, kuriame pirmiausia kiekvieną žodį mūsų tekste paverčiame atitinkamu įterpimu, o tada apskaičiuojame tam tikrą agregavimo funkciją visiems tiems įterpimams, pvz., `sum`, `average` arba `max`.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Mūsų klasifikatoriaus neuroninis tinklas prasidės įterpimo sluoksniu, tada agregavimo sluoksniu ir lineariu klasifikatoriumi viršuje:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class EmbedClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x):\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" x = torch.mean(x,dim=1)\n",
|
||||
" return self.fc(x)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Darbas su kintamu sekos dydžiu\n",
|
||||
"\n",
|
||||
"Dėl šios architektūros mūsų tinklui reikės sukurti minibatch'us tam tikru būdu. Ankstesniame skyriuje, naudojant žodžių maišo (BoW) metodą, visi BoW tensoriai minibatch'e turėjo vienodą dydį `vocab_size`, nepaisant tikrojo mūsų teksto sekos ilgio. Kai pereiname prie žodžių įterpimų (word embeddings), kiekviename teksto pavyzdyje turėsime skirtingą žodžių skaičių, o jungiant šiuos pavyzdžius į minibatch'us reikės taikyti tam tikrą užpildymą (padding).\n",
|
||||
"\n",
|
||||
"Tai galima padaryti naudojant tą pačią techniką, pateikiant `collate_fn` funkciją duomenų šaltiniui:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def padify(b):\n",
|
||||
" # b is the list of tuples of length batch_size\n",
|
||||
" # - first element of a tuple = label, \n",
|
||||
" # - second = feature (text sequence)\n",
|
||||
" # build vectorized sequence\n",
|
||||
" v = [encode(x[1]) for x in b]\n",
|
||||
" # first, compute max length of a sequence in this minibatch\n",
|
||||
" l = max(map(len,v))\n",
|
||||
" return ( # tuple of two tensors - labels and features\n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||||
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v])\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Mokymas įterpimo klasifikatoriaus\n",
|
||||
"\n",
|
||||
"Dabar, kai apibrėžėme tinkamą duomenų įkroviklį, galime treniruoti modelį naudodami mokymo funkciją, kurią apibrėžėme ankstesniame skyriuje:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6415625\n",
|
||||
"6400: acc=0.6865625\n",
|
||||
"9600: acc=0.7103125\n",
|
||||
"12800: acc=0.726953125\n",
|
||||
"16000: acc=0.739375\n",
|
||||
"19200: acc=0.75046875\n",
|
||||
"22400: acc=0.7572321428571429\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.889799795315499, 0.7623160588611644)"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||||
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba**: Čia mes treniruojame tik 25 tūkst. įrašų (mažiau nei vieną pilną epochą) dėl laiko taupymo, tačiau galite tęsti treniravimą, parašyti funkciją treniruoti kelias epochas ir eksperimentuoti su mokymosi tempo parametru, kad pasiektumėte didesnį tikslumą. Turėtumėte sugebėti pasiekti apie 90% tikslumą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### EmbeddingBag sluoksnis ir kintamo ilgio sekų reprezentacija\n",
|
||||
"\n",
|
||||
"Ankstesnėje architektūroje reikėjo visas sekas užpildyti iki vienodo ilgio, kad jos tilptų į mini paketą. Tai nėra pats efektyviausias būdas reprezentuoti kintamo ilgio sekas – kitas požiūris būtų naudoti **poslinkio** vektorių, kuris saugotų visų sekų poslinkius viename dideliame vektoriuje.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"> **Note**: Aukščiau pateiktame paveikslėlyje rodoma simbolių seka, tačiau mūsų pavyzdyje dirbame su žodžių sekų reprezentacija. Vis dėlto bendras principas, kaip sekas reprezentuoti naudojant poslinkio vektorių, išlieka tas pats.\n",
|
||||
"\n",
|
||||
"Norėdami dirbti su poslinkio reprezentacija, naudojame [`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html) sluoksnį. Jis panašus į `Embedding`, tačiau kaip įvestį naudoja turinio vektorių ir poslinkio vektorių. Be to, jis apima vidurkinimo sluoksnį, kuris gali būti `mean`, `sum` arba `max`.\n",
|
||||
"\n",
|
||||
"Štai modifikuotas tinklas, kuris naudoja `EmbeddingBag`:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class EmbedClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
||||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, text, off):\n",
|
||||
" x = self.embedding(text, off)\n",
|
||||
" return self.fc(x)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Norėdami paruošti duomenų rinkinį mokymui, turime pateikti konversijos funkciją, kuri paruoš poslinkio vektorių:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def offsetify(b):\n",
|
||||
" # first, compute data tensor from all sequences\n",
|
||||
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
||||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||||
" o = [0] + [len(t) for t in x]\n",
|
||||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||||
" return ( \n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||||
" torch.cat(x), # text \n",
|
||||
" o\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Atkreipkite dėmesį, kad, skirtingai nei visuose ankstesniuose pavyzdžiuose, mūsų tinklas dabar priima du parametrus: duomenų vektorių ir poslinkio vektorių, kurie yra skirtingo dydžio. Panašiai, mūsų duomenų įkroviklis taip pat pateikia mums 3 reikšmes vietoj 2: tiek teksto, tiek poslinkio vektoriai pateikiami kaip ypatybės. Todėl turime šiek tiek pakoreguoti savo mokymo funkciją, kad tai būtų tinkamai apdorota:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6153125\n",
|
||||
"6400: acc=0.6615625\n",
|
||||
"9600: acc=0.6932291666666667\n",
|
||||
"12800: acc=0.715078125\n",
|
||||
"16000: acc=0.7270625\n",
|
||||
"19200: acc=0.7382291666666667\n",
|
||||
"22400: acc=0.7486160714285715\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(22.771553103007037, 0.7551983365323096)"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||||
"\n",
|
||||
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
||||
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
||||
" loss_fn = loss_fn.to(device)\n",
|
||||
" net.train()\n",
|
||||
" total_loss,acc,count,i = 0,0,0,0\n",
|
||||
" for labels,text,off in dataloader:\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
||||
" out = net(text, off)\n",
|
||||
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" total_loss+=loss\n",
|
||||
" _,predicted = torch.max(out,1)\n",
|
||||
" acc+=(predicted==labels).sum()\n",
|
||||
" count+=len(labels)\n",
|
||||
" i+=1\n",
|
||||
" if i%report_freq==0:\n",
|
||||
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
||||
" if epoch_size and count>epoch_size:\n",
|
||||
" break\n",
|
||||
" return total_loss.item()/count, acc.item()/count\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Semantiniai įterpiniai: Word2Vec\n",
|
||||
"\n",
|
||||
"Mūsų ankstesniame pavyzdyje modelio įterpimo sluoksnis išmoko susieti žodžius su vektorinėmis reprezentacijomis, tačiau ši reprezentacija neturėjo daug semantinės prasmės. Būtų naudinga išmokti tokią vektorinę reprezentaciją, kurioje panašūs žodžiai ar sinonimai atitiktų vektorius, esančius arti vienas kito pagal tam tikrą vektorinį atstumą (pvz., euklidinį atstumą).\n",
|
||||
"\n",
|
||||
"Tam reikia iš anksto apmokyti mūsų įterpimo modelį naudojant didelę tekstų kolekciją specifiniu būdu. Vienas iš pirmųjų būdų mokyti semantinius įterpinius vadinamas [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Jis pagrįstas dviem pagrindinėmis architektūromis, kurios naudojamos žodžių paskirstytai reprezentacijai kurti:\n",
|
||||
"\n",
|
||||
" - **Nuolatinis maišo žodžių modelis** (CBoW) — šioje architektūroje modelis mokomas numatyti žodį iš aplinkinio konteksto. Turint ngramą $(W_{-2},W_{-1},W_0,W_1,W_2)$, modelio tikslas yra numatyti $W_0$ iš $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
||||
" - **Nuolatinis skip-gram modelis** yra priešingas CBoW. Modelis naudoja aplinkinį kontekstinių žodžių langą, kad numatytų dabartinį žodį.\n",
|
||||
"\n",
|
||||
"CBoW yra greitesnis, o skip-gram yra lėtesnis, tačiau geriau reprezentuoja retus žodžius.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Norėdami eksperimentuoti su Word2Vec įterpimu, iš anksto apmokytu naudojant Google News duomenų rinkinį, galime naudoti **gensim** biblioteką. Žemiau pateikiame žodžius, labiausiai panašius į 'neural'.\n",
|
||||
"\n",
|
||||
"> **Note:** Kai pirmą kartą kuriate žodžių vektorius, jų atsisiuntimas gali užtrukti!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gensim.downloader as api\n",
|
||||
"w2v = api.load('word2vec-google-news-300')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"neuronal -> 0.7804799675941467\n",
|
||||
"neurons -> 0.7326500415802002\n",
|
||||
"neural_circuits -> 0.7252851724624634\n",
|
||||
"neuron -> 0.7174385190010071\n",
|
||||
"cortical -> 0.6941086649894714\n",
|
||||
"brain_circuitry -> 0.6923246383666992\n",
|
||||
"synaptic -> 0.6699118614196777\n",
|
||||
"neural_circuitry -> 0.6638563275337219\n",
|
||||
"neurochemical -> 0.6555314064025879\n",
|
||||
"neuronal_activity -> 0.6531826257705688\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for w,p in w2v.most_similar('neural'):\n",
|
||||
" print(f\"{w} -> {p}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Mes taip pat galime apskaičiuoti vektorių įterpimus iš žodžio, kurie bus naudojami klasifikavimo modelio mokymui (aiškumo dėlei rodome tik pirmąsias 20 vektoriaus komponentų):\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
||||
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
||||
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
||||
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
||||
" dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v.word_vec('play')[:20]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Puikus dalykas apie semantinius įterpimus yra tai, kad galite manipuliuoti vektoriaus kodavimu, kad pakeistumėte semantiką. Pavyzdžiui, galime paprašyti surasti žodį, kurio vektorinė reprezentacija būtų kuo artimesnė žodžiams *karalius* ir *moteris*, ir kuo toliau nuo žodžio *vyras*:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"('queen', 0.7118192911148071)"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Tiek CBoW, tiek Skip-Grams yra „prognozuojančios“ įterptys, nes jos atsižvelgia tik į vietinius kontekstus. Word2Vec nepasinaudoja globaliu kontekstu.\n",
|
||||
"\n",
|
||||
"**FastText** remiasi Word2Vec, mokydamas vektorių reprezentacijas kiekvienam žodžiui ir simbolių n-gramas, esančias žodyje. Šių reprezentacijų reikšmės kiekviename mokymo žingsnyje yra vidurkinamos į vieną vektorių. Nors tai prideda daug papildomų skaičiavimų priešmokymio metu, tai leidžia žodžių įterptims koduoti subžodžių informaciją.\n",
|
||||
"\n",
|
||||
"Kitas metodas, **GloVe**, pasinaudoja koegzistavimo matricos idėja, naudodamas neuroninius metodus, kad išskaidytų koegzistavimo matricą į išraiškingesnius ir nelinijinius žodžių vektorius.\n",
|
||||
"\n",
|
||||
"Galite eksperimentuoti su pavyzdžiu, keisdami įterptis į FastText ir GloVe, nes gensim palaiko kelis skirtingus žodžių įterpimo modelius.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Naudojant iš anksto apmokytus įterpimus PyTorch\n",
|
||||
"\n",
|
||||
"Galime pakeisti aukščiau pateiktą pavyzdį, kad iš anksto užpildytume matricą mūsų įterpimo sluoksnyje semantiniais įterpimais, tokiais kaip Word2Vec. Turime atsižvelgti į tai, kad iš anksto apmokytų įterpimų ir mūsų teksto korpuso žodynai greičiausiai nesutaps, todėl trūkstamų žodžių svorius inicializuosime atsitiktinėmis reikšmėmis:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Embedding size: 300\n",
|
||||
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embed_size = len(w2v.get_vector('hello'))\n",
|
||||
"print(f'Embedding size: {embed_size}')\n",
|
||||
"\n",
|
||||
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
||||
"\n",
|
||||
"print('Populating matrix, this will take some time...',end='')\n",
|
||||
"found, not_found = 0,0\n",
|
||||
"for i,w in enumerate(vocab.get_itos()):\n",
|
||||
" try:\n",
|
||||
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
||||
" found+=1\n",
|
||||
" except:\n",
|
||||
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
||||
" not_found+=1\n",
|
||||
"\n",
|
||||
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
||||
"net = net.to(device)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar treniruokime mūsų modelį. Atkreipkite dėmesį, kad modelio treniravimas užtrunka žymiai ilgiau nei ankstesniame pavyzdyje, dėl didesnio įterpimo sluoksnio dydžio ir daug didesnio parametrų skaičiaus. Taip pat dėl to gali prireikti treniruoti modelį su daugiau pavyzdžių, jei norime išvengti per didelio pritaikymo.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6359375\n",
|
||||
"6400: acc=0.68109375\n",
|
||||
"9600: acc=0.7067708333333333\n",
|
||||
"12800: acc=0.723671875\n",
|
||||
"16000: acc=0.73625\n",
|
||||
"19200: acc=0.7463541666666667\n",
|
||||
"22400: acc=0.7560714285714286\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(214.1013875559821, 0.7626759436980166)"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Mūsų atveju nematome didelio tikslumo padidėjimo, greičiausiai dėl labai skirtingų žodynų. \n",
|
||||
"Norint išspręsti skirtingų žodynų problemą, galime naudoti vieną iš šių sprendimų: \n",
|
||||
"* Iš naujo apmokyti word2vec modelį pagal mūsų žodyną \n",
|
||||
"* Įkelti mūsų duomenų rinkinį su žodynu iš iš anksto apmokyto word2vec modelio. Žodyną, naudojamą duomenų rinkiniui įkelti, galima nurodyti įkėlimo metu. \n",
|
||||
"\n",
|
||||
"Pastarasis metodas atrodo paprastesnis, ypač todėl, kad PyTorch `torchtext` sistema turi integruotą palaikymą įterpimams. Pavyzdžiui, galime sukurti žodyną, pagrįstą GloVe, tokiu būdu: \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Įkeltas žodynas turi šias pagrindines operacijas:\n",
|
||||
"* `vocab.stoi` žodynas leidžia mums konvertuoti žodį į jo indeksą žodyne\n",
|
||||
"* `vocab.itos` atlieka priešingą veiksmą - konvertuoja skaičių į žodį\n",
|
||||
"* `vocab.vectors` yra įterptųjų vektorių masyvas, todėl norint gauti žodžio `s` įterptį, turime naudoti `vocab.vectors[vocab.stoi[s]]`\n",
|
||||
"\n",
|
||||
"Štai pavyzdys, kaip manipuliuoti įterptimis, kad būtų pademonstruota lygtis **kind-man+woman = queen** (turėjau šiek tiek pakoreguoti koeficientą, kad tai veiktų):\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'queen'"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# get the vector corresponding to kind-man+woman\n",
|
||||
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
||||
"# find the index of the closest embedding vector \n",
|
||||
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
||||
"min_idx = torch.argmin(d)\n",
|
||||
"# find the corresponding word\n",
|
||||
"vocab.itos[min_idx]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Norint apmokyti klasifikatorių naudojant šiuos įterpimus, pirmiausia turime užkoduoti savo duomenų rinkinį naudodami GloVe žodyną:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def offsetify(b):\n",
|
||||
" # first, compute data tensor from all sequences\n",
|
||||
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
||||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||||
" o = [0] + [len(t) for t in x]\n",
|
||||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||||
" return ( \n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||||
" torch.cat(x), # text \n",
|
||||
" o\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Kaip matėme aukščiau, visi vektorių įterpimai saugomi `vocab.vectors` matricoje. Tai labai palengvina šių svorių įkėlimą į įterpimo sluoksnio svorius naudojant paprastą kopijavimą:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 17,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
||||
"net.embedding.weight.data = vocab.vectors\n",
|
||||
"net = net.to(device)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.6271875\n",
|
||||
"6400: acc=0.68078125\n",
|
||||
"9600: acc=0.7030208333333333\n",
|
||||
"12800: acc=0.71984375\n",
|
||||
"16000: acc=0.7346875\n",
|
||||
"19200: acc=0.7455729166666667\n",
|
||||
"22400: acc=0.7529464285714286\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(35.53972978646833, 0.7575175943698017)"
|
||||
]
|
||||
},
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
||||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Viena iš priežasčių, kodėl nematome reikšmingo tikslumo padidėjimo, yra ta, kad kai kurių žodžių iš mūsų duomenų rinkinio nėra iš anksto apmokyto GloVe žodyno, todėl jie iš esmės ignoruojami. Norėdami įveikti šią problemą, galime apmokyti savo įterpimus pagal mūsų duomenų rinkinį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Kontekstinės įterptys\n",
|
||||
"\n",
|
||||
"Viena pagrindinių tradicinių iš anksto apmokytų įterpčių, tokių kaip Word2Vec, apribojimų yra žodžių reikšmių išskyrimo problema. Nors iš anksto apmokytos įterptys gali užfiksuoti dalį žodžių reikšmės kontekste, visos galimos žodžio reikšmės yra užkoduojamos toje pačioje įterptyje. Tai gali sukelti problemų tolimesniuose modeliuose, nes daugelis žodžių, pavyzdžiui, žodis „play“, turi skirtingas reikšmes priklausomai nuo konteksto, kuriame jie naudojami.\n",
|
||||
"\n",
|
||||
"Pavyzdžiui, žodis „play“ šiuose dviejuose sakiniuose turi gana skirtingas reikšmes:\n",
|
||||
"- Aš nuėjau į **spektaklį** teatre.\n",
|
||||
"- Jonas nori **žaisti** su savo draugais.\n",
|
||||
"\n",
|
||||
"Aukščiau pateiktos iš anksto apmokytos įterptys abu šiuos žodžio „play“ reikšmes pateikia toje pačioje įterptyje. Norint įveikti šį apribojimą, reikia kurti įterptis, pagrįstas **kalbos modeliu**, kuris yra apmokytas naudojant didelį tekstų korpusą ir *žino*, kaip žodžiai gali būti naudojami skirtinguose kontekstuose. Kontekstinių įterpčių aptarimas nėra šio vadovo dalis, tačiau mes prie jų sugrįšime, kai kalbėsime apie kalbos modelius kitame skyriuje.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar neteisingą interpretaciją, atsiradusią dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_pytorch",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "f50b026abce5cf36783a560ea72cb9b1",
|
||||
"translation_date": "2025-08-31T14:07:10+00:00",
|
||||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,695 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Įterpimai\n",
|
||||
"\n",
|
||||
"Ankstesniame pavyzdyje dirbome su aukštos dimensijos žodžių maišo vektoriais, kurių ilgis yra `vocab_size`, ir aiškiai konvertavome žemos dimensijos pozicinius reprezentacijos vektorius į retą vieno karšto (one-hot) reprezentaciją. Ši vieno karšto reprezentacija nėra efektyvi atminties požiūriu. Be to, kiekvienas žodis yra traktuojamas nepriklausomai nuo kitų, todėl vieno karšto kodavimo vektoriai neišreiškia semantinių panašumų tarp žodžių.\n",
|
||||
"\n",
|
||||
"Šiame skyriuje toliau tyrinėsime **News AG** duomenų rinkinį. Pradėkime įkeldami duomenis ir pasinaudodami ankstesnio skyriaus apibrėžimais.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Kas yra įterpimas?\n",
|
||||
"\n",
|
||||
"**Įterpimo** idėja yra atvaizduoti žodžius naudojant mažesnės dimensijos tankius vektorius, kurie atspindi žodžio semantinę prasmę. Vėliau aptarsime, kaip sukurti prasmingus žodžių įterpimus, tačiau kol kas galvokime apie įterpimus kaip apie būdą sumažinti žodžio vektoriaus dimensiją.\n",
|
||||
"\n",
|
||||
"Taigi, įterpimo sluoksnis priima žodį kaip įvestį ir pateikia išvesties vektorių su nurodytu `embedding_size`. Tam tikra prasme jis yra labai panašus į `Dense` sluoksnį, tačiau vietoj to, kad naudotų vieno karšto kodavimo (one-hot encoding) vektorių kaip įvestį, jis gali priimti žodžio numerį.\n",
|
||||
"\n",
|
||||
"Naudodami įterpimo sluoksnį kaip pirmąjį mūsų tinklo sluoksnį, galime pereiti nuo žodžių maišo (bag-of-words) prie **įterpimo maišo** (embedding bag) modelio. Šiame modelyje pirmiausia kiekvieną tekstą sudarantį žodį paverčiame atitinkamu įterpimu, o tada apskaičiuojame tam tikrą agregavimo funkciją visiems tiems įterpimams, pavyzdžiui, `sum`, `average` arba `max`.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Mūsų klasifikatoriaus neuroninis tinklas susideda iš šių sluoksnių:\n",
|
||||
"\n",
|
||||
"* `TextVectorization` sluoksnis, kuris priima eilutę kaip įvestį ir pateikia skaičių žetonų (tokenų) tensorių. Mes nurodysime tam tikrą pagrįstą žodyno dydį `vocab_size` ir ignoruosime rečiau naudojamus žodžius. Įvesties forma bus 1, o išvesties forma bus $n$, nes gausime $n$ žetonų kaip rezultatą, kurių kiekvienas turės skaičius nuo 0 iki `vocab_size`.\n",
|
||||
"* `Embedding` sluoksnis, kuris priima $n$ skaičių ir sumažina kiekvieną skaičių iki tankaus vektoriaus su nurodytu ilgiu (mūsų pavyzdyje – 100). Taigi, įvesties tensorius su forma $n$ bus transformuotas į $n\\times 100$ tensorių.\n",
|
||||
"* Agregavimo sluoksnis, kuris apskaičiuoja šio tensoriaus vidurkį pagal pirmąją ašį, t. y. jis apskaičiuos visų $n$ įvesties tensorių, atitinkančių skirtingus žodžius, vidurkį. Šiam sluoksniui įgyvendinti naudosime `Lambda` sluoksnį ir perduosime jam funkciją, skirtą vidurkiui apskaičiuoti. Išvestis turės 100 formą ir bus visos įvesties sekos skaitmeninis atvaizdavimas.\n",
|
||||
"* Galutinis `Dense` linijinis klasifikatorius.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
" Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
" text_vectorization (TextVec (None, None) 0 \n",
|
||||
" torization) \n",
|
||||
" \n",
|
||||
" embedding (Embedding) (None, None, 100) 3000000 \n",
|
||||
" \n",
|
||||
" lambda (Lambda) (None, 100) 0 \n",
|
||||
" \n",
|
||||
" dense (Dense) (None, 4) 404 \n",
|
||||
" \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 3,000,404\n",
|
||||
"Trainable params: 3,000,404\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = 30000\n",
|
||||
"batch_size = 128\n",
|
||||
"\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,input_shape=(1,))\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer, \n",
|
||||
" keras.layers.Embedding(vocab_size,100),\n",
|
||||
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
|
||||
" keras.layers.Dense(4, activation='softmax')\n",
|
||||
"])\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Santraukoje, stulpelyje **output shape**, pirmoji tensoriaus dimensija `None` atitinka mini partijos dydį, o antroji - žetonų sekos ilgį. Visos žetonų sekos mini partijoje turi skirtingus ilgius. Apie tai, kaip su tuo susidoroti, aptarsime kitame skyriuje.\n",
|
||||
"\n",
|
||||
"Dabar treniruokime tinklą:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n",
|
||||
"938/938 [==============================] - 20s 20ms/step - loss: 0.7891 - acc: 0.8155 - val_loss: 0.4470 - val_acc: 0.8642\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x22255515100>"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"print(\"Training vectorizer\")\n",
|
||||
"vectorizer.adapt(ds_train.take(500).map(extract_text))\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"> **Pastaba**: mes kuriame vektorizatorių remdamiesi duomenų pogrupiu. Tai daroma siekiant pagreitinti procesą, ir tai gali lemti situaciją, kai ne visi mūsų teksto žetonai yra žodyne. Tokiu atveju tie žetonai būtų ignoruojami, o tai gali šiek tiek sumažinti tikslumą. Tačiau realiame gyvenime teksto pogrupis dažnai suteikia gerą žodyno įvertinimą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Darbas su kintamais sekų dydžiais\n",
|
||||
"\n",
|
||||
"Pažvelkime, kaip vyksta mokymas naudojant mini partijas. Aukščiau pateiktame pavyzdyje įvesties tensorius turi dimensiją 1, o mes naudojame 128 dydžio mini partijas, todėl tikrasis tensoriaus dydis yra $128 \\times 1$. Tačiau kiekviename sakinyje esančių žodžių skaičius skiriasi. Jei pritaikysime `TextVectorization` sluoksnį vienai įvesčiai, grąžinamų žodžių skaičius bus skirtingas, priklausomai nuo to, kaip tekstas yra suskaidytas į žodžius:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"tf.Tensor([ 1 45], shape=(2,), dtype=int64)\n",
|
||||
"tf.Tensor([ 112 1271 1 3 1747 158], shape=(6,), dtype=int64)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(vectorizer('Hello, world!'))\n",
|
||||
"print(vectorizer('I am glad to meet you!'))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Tačiau, kai vektorizatorių taikome kelioms sekoms, jis turi sukurti stačiakampio formos tensorių, todėl neužpildytus elementus užpildo PAD žetonu (kuris mūsų atveju yra nulis):\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tf.Tensor: shape=(2, 6), dtype=int64, numpy=\n",
|
||||
"array([[ 1, 45, 0, 0, 0, 0],\n",
|
||||
" [ 112, 1271, 1, 3, 1747, 158]], dtype=int64)>"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vectorizer(['Hello, world!','I am glad to meet you!'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Čia matome įterpimus:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([[[ 1.53059261e-02, 6.80514947e-02, 3.14026810e-02, ...,\n",
|
||||
" -8.92002955e-02, 1.52911525e-04, -5.65562584e-02],\n",
|
||||
" [ 2.57456154e-01, 2.79364467e-01, -2.03605562e-01, ...,\n",
|
||||
" -2.07474351e-01, 8.31158683e-02, -2.03911960e-01],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02],\n",
|
||||
" [ 3.98201384e-02, -8.03454965e-03, 2.39790026e-02, ...,\n",
|
||||
" -7.18549127e-04, 2.66963355e-02, -4.30646613e-02]],\n",
|
||||
"\n",
|
||||
" [[ 1.89674050e-01, 2.61548996e-01, -3.67433839e-02, ...,\n",
|
||||
" -2.07366899e-01, -1.05442435e-01, -2.36952081e-01],\n",
|
||||
" [ 6.16133213e-02, 1.80511594e-01, 9.77298319e-02, ...,\n",
|
||||
" -5.46628237e-02, -1.07340455e-01, -1.06589928e-01],\n",
|
||||
" [ 1.53059261e-02, 6.80514947e-02, 3.14026810e-02, ...,\n",
|
||||
" -8.92002955e-02, 1.52911525e-04, -5.65562584e-02],\n",
|
||||
" [-4.84890305e-02, -8.41715634e-02, 1.51529670e-01, ...,\n",
|
||||
" 1.28192469e-01, -7.77286515e-02, 1.26041949e-01],\n",
|
||||
" [-4.17212099e-02, -5.60694858e-02, 4.08860669e-02, ...,\n",
|
||||
" 8.70475471e-02, 8.92383084e-02, 1.67974353e-01],\n",
|
||||
" [ 2.85779923e-01, 4.57767487e-01, 4.52292450e-02, ...,\n",
|
||||
" -1.97419018e-01, -2.04659685e-01, -2.79758364e-01]]],\n",
|
||||
" dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.layers[1](vectorizer(['Hello, world!','I am glad to meet you!'])).numpy()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba**: Siekiant sumažinti užpildymo kiekį, kai kuriais atvejais prasminga surūšiuoti visas sekas duomenų rinkinyje didėjančio ilgio tvarka (arba, tiksliau, pagal žetonų skaičių). Tai užtikrins, kad kiekvienas mažasis paketas turės panašaus ilgio sekas.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Semantiniai įterpiniai: Word2Vec\n",
|
||||
"\n",
|
||||
"Mūsų ankstesniame pavyzdyje įterpimo sluoksnis išmoko susieti žodžius su vektorinėmis reprezentacijomis, tačiau šios reprezentacijos neturėjo semantinės prasmės. Būtų naudinga išmokti vektorinę reprezentaciją, kurioje panašūs žodžiai ar sinonimai atitiktų vektorius, esančius arti vienas kito pagal tam tikrą vektorinį atstumą (pavyzdžiui, euklidinį atstumą).\n",
|
||||
"\n",
|
||||
"Tam reikia iš anksto apmokyti mūsų įterpimo modelį naudojant didelę tekstų kolekciją ir tokią techniką kaip [Word2Vec](https://en.wikipedia.org/wiki/Word2vec). Ši technika remiasi dviem pagrindinėmis architektūromis, kurios naudojamos žodžių paskirstytai reprezentacijai kurti:\n",
|
||||
"\n",
|
||||
" - **Nuolatinis žodžių maišas** (CBoW), kai modelis yra mokomas numatyti žodį pagal aplinkinį kontekstą. Turint ngramą $(W_{-2},W_{-1},W_0,W_1,W_2)$, modelio tikslas yra numatyti $W_0$ pagal $(W_{-2},W_{-1},W_1,W_2)$.\n",
|
||||
" - **Nuolatinis skip-gramas** yra priešingas CBoW. Modelis naudoja aplinkinį kontekstinių žodžių langą, kad numatytų dabartinį žodį.\n",
|
||||
"\n",
|
||||
"CBoW yra greitesnis, o skip-gramas, nors ir lėtesnis, geriau reprezentuoja retai pasitaikančius žodžius.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Norėdami eksperimentuoti su Word2Vec įterpimu, iš anksto apmokytu naudojant Google News duomenų rinkinį, galime naudoti **gensim** biblioteką. Žemiau pateikiame žodžius, kurie yra labiausiai panašūs į 'neural'.\n",
|
||||
"\n",
|
||||
"> **Note:** Kai pirmą kartą kuriate žodžių vektorius, jų atsisiuntimas gali užtrukti!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gensim.downloader as api\n",
|
||||
"w2v = api.load('word2vec-google-news-300')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"neuronal -> 0.7804799675941467\n",
|
||||
"neurons -> 0.7326500415802002\n",
|
||||
"neural_circuits -> 0.7252851724624634\n",
|
||||
"neuron -> 0.7174385190010071\n",
|
||||
"cortical -> 0.6941086649894714\n",
|
||||
"brain_circuitry -> 0.6923246383666992\n",
|
||||
"synaptic -> 0.6699118614196777\n",
|
||||
"neural_circuitry -> 0.6638563275337219\n",
|
||||
"neurochemical -> 0.6555314064025879\n",
|
||||
"neuronal_activity -> 0.6531826257705688\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"for w,p in w2v.most_similar('neural'):\n",
|
||||
" print(f\"{w} -> {p}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Taip pat galime išgauti vektoriaus įterpimą iš žodžio, kuris bus naudojamas klasifikavimo modelio mokymui. Įterpimas turi 300 komponentų, tačiau čia aiškumo dėlei parodome tik pirmuosius 20 vektoriaus komponentų:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
||||
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
||||
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
||||
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
||||
" dtype=float32)"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v['play'][:20]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Puikus dalykas apie semantinius įterpimus yra tai, kad galite manipuliuoti vektoriaus kodavimu pagal semantiką. Pavyzdžiui, galime paprašyti surasti žodį, kurio vektorinė reprezentacija būtų kuo artimesnė žodžiams *karalius* ir *moteris*, ir kuo toliau nuo žodžio *vyras*:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"('queen', 0.7118192911148071)"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"source": [
|
||||
"Pavyzdys aukščiau naudoja tam tikrą vidinę GenSym magiją, tačiau pagrindinė logika iš tikrųjų yra gana paprasta. Įdomus dalykas apie įterpimus yra tai, kad galite atlikti įprastas vektorių operacijas su įterpimo vektoriais, ir tai atspindėtų operacijas su žodžių **reikšmėmis**. Pavyzdys aukščiau gali būti išreikštas vektorių operacijų terminais: mes apskaičiuojame vektorių, atitinkantį **KARALIUS-VYRAS+MOTERIS** (operacijos `+` ir `-` atliekamos su atitinkamų žodžių vektorinėmis reprezentacijomis), o tada randame artimiausią žodį žodyne tam vektoriui:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'queen'"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# get the vector corresponding to kind-man+woman\n",
|
||||
"qvec = w2v['king']-1.7*w2v['man']+1.7*w2v['woman']\n",
|
||||
"# find the index of the closest embedding vector \n",
|
||||
"d = np.sum((w2v.vectors-qvec)**2,axis=1)\n",
|
||||
"min_idx = np.argmin(d)\n",
|
||||
"# find the corresponding word\n",
|
||||
"w2v.index_to_key[min_idx]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **PASTABA**: Turėjome pridėti mažus koeficientus prie *vyro* ir *moters* vektorių – pabandykite juos pašalinti ir pažiūrėkite, kas nutiks.\n",
|
||||
"\n",
|
||||
"Norėdami rasti artimiausią vektorių, naudojame TensorFlow įrankius, kad apskaičiuotume atstumų vektorių tarp mūsų vektoriaus ir visų žodyno vektorių, o tada naudojame `argmin`, kad rastume mažiausio žodžio indeksą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Nors Word2Vec atrodo puikus būdas išreikšti žodžių semantiką, jis turi daug trūkumų, įskaitant šiuos:\n",
|
||||
"\n",
|
||||
"* Tiek CBoW, tiek skip-gram modeliai yra **prognozuojamieji įterpiniai**, ir jie atsižvelgia tik į vietinį kontekstą. Word2Vec nepasinaudoja globaliu kontekstu.\n",
|
||||
"* Word2Vec neatsižvelgia į žodžių **morfologiją**, t. y. į tai, kad žodžio reikšmė gali priklausyti nuo skirtingų žodžio dalių, pavyzdžiui, šaknies.\n",
|
||||
"\n",
|
||||
"**FastText** bando įveikti antrąjį apribojimą ir remiasi Word2Vec, mokydamas vektorių reprezentacijas kiekvienam žodžiui ir simbolių n-gramoms, randamoms kiekviename žodyje. Reprezentacijų reikšmės tada vidurkinamos į vieną vektorių kiekviename mokymo žingsnyje. Nors tai prideda daug papildomų skaičiavimų išankstiniam mokymui, tai leidžia žodžių įterpiniams užkoduoti subžodžių informaciją.\n",
|
||||
"\n",
|
||||
"Kitas metodas, **GloVe**, naudoja kitokį požiūrį į žodžių įterpinius, pagrįstą žodžių-konteksto matricos faktorizacija. Pirmiausia jis sukuria didelę matricą, kuri skaičiuoja žodžių pasikartojimų skirtinguose kontekstuose skaičių, o tada bando šią matricą pavaizduoti mažesnėje dimensijoje taip, kad būtų sumažintas rekonstrukcijos nuostolis.\n",
|
||||
"\n",
|
||||
"Gensim biblioteka palaiko šiuos žodžių įterpinius, ir jūs galite eksperimentuoti su jais, pakeisdami aukščiau pateiktą modelio įkėlimo kodą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Naudojant iš anksto apmokytus įterpimus Keras bibliotekoje\n",
|
||||
"\n",
|
||||
"Galime pakeisti aukščiau pateiktą pavyzdį, kad užpildytume įterpimo sluoksnio matricą semantiniais įterpimais, tokiais kaip Word2Vec. Iš anksto apmokyto įterpimo ir teksto korpuso žodynai greičiausiai nesutaps, todėl turime pasirinkti vieną. Čia nagrinėjame dvi galimas parinktis: naudoti žodyną iš tokenizerio arba naudoti žodyną iš Word2Vec įterpimų.\n",
|
||||
"\n",
|
||||
"### Naudojant tokenizerio žodyną\n",
|
||||
"\n",
|
||||
"Naudojant tokenizerio žodyną, kai kurie žodžiai iš žodyno turės atitinkamus Word2Vec įterpimus, o kai kurių trūks. Atsižvelgiant į tai, kad mūsų žodyno dydis yra `vocab_size`, o Word2Vec įterpimo vektoriaus ilgis yra `embed_size`, įterpimo sluoksnis bus atvaizduotas svorio matrica, kurios forma yra `vocab_size`$\\times$`embed_size`. Šią matricą užpildysime eidami per žodyną:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Embedding size: 300\n",
|
||||
"Populating matrix, this will take some time...Done, found 4551 words, 784 words missing\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embed_size = len(w2v.get_vector('hello'))\n",
|
||||
"print(f'Embedding size: {embed_size}')\n",
|
||||
"\n",
|
||||
"vocab = vectorizer.get_vocabulary()\n",
|
||||
"W = np.zeros((vocab_size,embed_size))\n",
|
||||
"print('Populating matrix, this will take some time...',end='')\n",
|
||||
"found, not_found = 0,0\n",
|
||||
"for i,w in enumerate(vocab):\n",
|
||||
" try:\n",
|
||||
" W[i] = w2v.get_vector(w)\n",
|
||||
" found+=1\n",
|
||||
" except:\n",
|
||||
" # W[i] = np.random.normal(0.0,0.3,size=(embed_size,))\n",
|
||||
" not_found+=1\n",
|
||||
"\n",
|
||||
"print(f\"Done, found {found} words, {not_found} words missing\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Žodžiams, kurie nėra Word2Vec žodyno dalis, galime palikti jų reikšmes kaip nulius arba sugeneruoti atsitiktinį vektorių.\n",
|
||||
"\n",
|
||||
"Dabar galime apibrėžti įterpimo sluoksnį su iš anksto paruoštais svoriais:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"emb = keras.layers.Embedding(vocab_size,embed_size,weights=[W],trainable=False)\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer, emb,\n",
|
||||
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
|
||||
" keras.layers.Dense(4, activation='softmax')\n",
|
||||
"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"938/938 [==============================] - 10s 10ms/step - loss: 1.1075 - acc: 0.7822 - val_loss: 0.9134 - val_acc: 0.8175\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x2220226ef10>"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
||||
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba**: Atkreipkite dėmesį, kad nustatėme `trainable=False` kurdami `Embedding`, tai reiškia, kad mes nepertreniruojame Embedding sluoksnio. Tai gali šiek tiek sumažinti tikslumą, tačiau paspartina mokymą.\n",
|
||||
"\n",
|
||||
"### Naudojant įterpimo žodyną\n",
|
||||
"\n",
|
||||
"Viena problema su ankstesniu metodu yra ta, kad TextVectorization ir Embedding naudoja skirtingus žodynus. Norėdami išspręsti šią problemą, galime naudoti vieną iš šių sprendimų:\n",
|
||||
"* Iš naujo pertreniruoti Word2Vec modelį pagal mūsų žodyną.\n",
|
||||
"* Įkelti mūsų duomenų rinkinį naudojant žodyną iš iš anksto apmokyto Word2Vec modelio. Žodynai, naudojami duomenų rinkiniui įkelti, gali būti nurodyti įkėlimo metu.\n",
|
||||
"\n",
|
||||
"Antrasis metodas atrodo paprastesnis, todėl jį įgyvendinkime. Pirmiausia sukursime `TextVectorization` sluoksnį su nurodytu žodynu, paimtu iš Word2Vec įterpimų:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vocab = list(w2v.vocab.keys())\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(input_shape=(1,))\n",
|
||||
"vectorizer.set_vocabulary(vocab)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Gensim žodžių įterpimų biblioteka turi patogią funkciją, `get_keras_embeddings`, kuri automatiškai sukurs atitinkamą Keras įterpimų sluoksnį jums.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Epoch 1/5\n",
|
||||
"938/938 [==============================] - 20s 14ms/step - loss: 1.3377 - acc: 0.4978 - val_loss: 1.2995 - val_acc: 0.5647\n",
|
||||
"Epoch 2/5\n",
|
||||
"938/938 [==============================] - 10s 10ms/step - loss: 1.2587 - acc: 0.5722 - val_loss: 1.2339 - val_acc: 0.5842\n",
|
||||
"Epoch 3/5\n",
|
||||
"938/938 [==============================] - 10s 10ms/step - loss: 1.1980 - acc: 0.5884 - val_loss: 1.1826 - val_acc: 0.5954\n",
|
||||
"Epoch 4/5\n",
|
||||
"938/938 [==============================] - 12s 13ms/step - loss: 1.1503 - acc: 0.6002 - val_loss: 1.1417 - val_acc: 0.6018\n",
|
||||
"Epoch 5/5\n",
|
||||
"938/938 [==============================] - 11s 12ms/step - loss: 1.1120 - acc: 0.6097 - val_loss: 1.1083 - val_acc: 0.6104\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x2220ccb81c0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer, \n",
|
||||
" w2v.get_keras_embedding(train_embeddings=False),\n",
|
||||
" keras.layers.Lambda(lambda x: tf.reduce_mean(x,axis=1)),\n",
|
||||
" keras.layers.Dense(4, activation='softmax')\n",
|
||||
"])\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'])\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128),epochs=5)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Viena iš priežasčių, kodėl nematome didesnio tikslumo, yra ta, kad kai kurių žodžių iš mūsų duomenų rinkinio nėra iš anksto apmokyto GloVe žodyno, todėl jie iš esmės ignoruojami. Norėdami tai įveikti, galime apmokyti savo žodžių įterpimus, remdamiesi mūsų duomenų rinkiniu.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Kontekstiniai įterpiniai\n",
|
||||
"\n",
|
||||
"Viena pagrindinių tradicinių iš anksto apmokytų įterpinių, tokių kaip Word2Vec, apribojimų yra tai, kad, nors jie gali užfiksuoti tam tikrą žodžio reikšmę, jie negali atskirti skirtingų reikšmių. Tai gali sukelti problemų vėlesniuose modeliuose.\n",
|
||||
"\n",
|
||||
"Pavyzdžiui, žodis „play“ turi skirtingas reikšmes šiuose dviejuose sakiniuose:\n",
|
||||
"- Aš nuėjau į **spektaklį** teatre.\n",
|
||||
"- Jonas nori **žaisti** su savo draugais.\n",
|
||||
"\n",
|
||||
"Iš anksto apmokyti įterpiniai, apie kuriuos kalbėjome, abu žodžio „play“ reikšmes pateikia viename įterpinyje. Norint įveikti šį apribojimą, reikia kurti įterpinius, pagrįstus **kalbos modeliu**, kuris yra apmokytas naudojant didelį tekstų korpusą ir *žino*, kaip žodžiai gali būti naudojami skirtinguose kontekstuose. Kontekstinių įterpinių aptarimas nėra šio mokymo dalis, tačiau prie jų sugrįšime, kai kalbėsime apie kalbos modelius kitame skyriuje.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||||
},
|
||||
"kernel_info": {
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_tensorflow",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"nteract": {
|
||||
"version": "nteract-front-end@1.0.0"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "b859482be7f61d1eadc2c6a2720a37e4",
|
||||
"translation_date": "2025-08-31T14:03:57+00:00",
|
||||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsTF.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,576 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "NXTSugt6ieXh"
|
||||
},
|
||||
"source": [
|
||||
"## CBoW modelio mokymas\n",
|
||||
"\n",
|
||||
"Šis užrašų knygelė yra dalis [AI for Beginners Curriculum](http://aka.ms/ai-beginners)\n",
|
||||
"\n",
|
||||
"Šiame pavyzdyje apžvelgsime, kaip apmokyti CBoW kalbos modelį, kad sukurtume savo Word2Vec įterpimo erdvę. Naudosime AG News duomenų rinkinį kaip teksto šaltinį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import os\n",
|
||||
"import collections\n",
|
||||
"import builtins\n",
|
||||
"import random\n",
|
||||
"import numpy as np"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "q-UiiJUKaxHj"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "TFbR8CZaTZ1q"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"Pirmiausia įkelkime savo duomenų rinkinį ir apibrėžkime žodžių skirstytuvą bei žodyną. Nustatysime `vocab_size` reikšmę į 5000, kad šiek tiek apribotume skaičiavimus.\n"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "HIwC7lI5T-ov"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def load_dataset(ngrams = 1, min_freq = 1, vocab_size = 5000 , lines_cnt = 500):\n",
|
||||
" tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n",
|
||||
" print(\"Loading dataset...\")\n",
|
||||
" test_dataset, train_dataset = torchtext.datasets.AG_NEWS(root='./data')\n",
|
||||
" train_dataset = list(train_dataset)\n",
|
||||
" test_dataset = list(test_dataset)\n",
|
||||
" classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
||||
" print('Building vocab...')\n",
|
||||
" counter = collections.Counter()\n",
|
||||
" for i, (_, line) in enumerate(train_dataset):\n",
|
||||
" counter.update(torchtext.data.utils.ngrams_iterator(tokenizer(line),ngrams=ngrams))\n",
|
||||
" if i == lines_cnt:\n",
|
||||
" break\n",
|
||||
" vocab = torchtext.vocab.Vocab(collections.Counter(dict(counter.most_common(vocab_size))), min_freq=min_freq)\n",
|
||||
" return train_dataset, test_dataset, classes, vocab, tokenizer"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "wdZuygtgiuLG"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"train_dataset, test_dataset, _, vocab, tokenizer = load_dataset()"
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "4d1nU1gsivGu",
|
||||
"outputId": "949fe272-ae0e-49f5-c373-6703458b3a74"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"Loading dataset...\n",
|
||||
"Building vocab...\n"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def encode(x, vocabulary, tokenizer = tokenizer):\n",
|
||||
" return [vocabulary[s] for s in tokenizer(x)]"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "1XDYNhG8ToFV"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "LIlQk6_PaHVY"
|
||||
},
|
||||
"source": [
|
||||
"## CBoW Modelis\n",
|
||||
"\n",
|
||||
"CBoW modelis mokosi numatyti žodį remdamasis $2N$ kaimyniniais žodžiais. Pavyzdžiui, kai $N=1$, iš sakinio *I like to train networks* gausime šias poras: (like,I), (I, like), (to, like), (like,to), (train,to), (to, train), (networks, train), (train,networks). Čia pirmasis žodis yra kaimyninis žodis, naudojamas kaip įvestis, o antrasis žodis yra tas, kurį prognozuojame.\n",
|
||||
"\n",
|
||||
"Norint sukurti tinklą, kuris prognozuotų kitą žodį, mums reikės pateikti kaimyninį žodį kaip įvestį ir gauti žodžio numerį kaip išvestį. CBoW tinklo architektūra yra tokia:\n",
|
||||
"\n",
|
||||
"* Įvesties žodis perduodamas per įterpimo sluoksnį. Šis įterpimo sluoksnis bus mūsų Word2Vec įterpimas, todėl jį apibrėšime atskirai kaip kintamąjį `embedder`. Šiame pavyzdyje naudosime įterpimo dydį = 30, nors galite eksperimentuoti su didesniais matmenimis (tikrasis Word2Vec turi 300).\n",
|
||||
"* Įterpimo vektorius tada perduodamas per linijinį sluoksnį, kuris prognozuos išvesties žodį. Todėl jis turi `vocab_size` neuronų.\n",
|
||||
"\n",
|
||||
"Kalbant apie išvestį, jei naudosime `CrossEntropyLoss` kaip nuostolių funkciją, taip pat turėsime pateikti tik žodžių numerius kaip laukiamus rezultatus, be vieno karšto kodavimo (one-hot encoding).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"vocab_size = len(vocab)\n",
|
||||
"\n",
|
||||
"embedder = torch.nn.Embedding(num_embeddings = vocab_size, embedding_dim = 30)\n",
|
||||
"model = torch.nn.Sequential(\n",
|
||||
" embedder,\n",
|
||||
" torch.nn.Linear(in_features = 30, out_features = vocab_size),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(model)"
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "akKTcKQKkfl2",
|
||||
"outputId": "da687e3e-a8ec-4c1a-e456-ab8cd6ac7dad"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"Sequential(\n",
|
||||
" (0): Embedding(5002, 30)\n",
|
||||
" (1): Linear(in_features=30, out_features=5002, bias=True)\n",
|
||||
")\n"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "Nud6jgGPaHVa"
|
||||
},
|
||||
"source": [
|
||||
"## Mokymo duomenų paruošimas\n",
|
||||
"\n",
|
||||
"Dabar suprogramuokime pagrindinę funkciją, kuri apskaičiuos CBoW žodžių poras iš teksto. Ši funkcija leis mums nurodyti lango dydį ir grąžins porų rinkinį - įvesties ir išvesties žodį. Atkreipkite dėmesį, kad ši funkcija gali būti naudojama tiek su žodžiais, tiek su vektoriais/tensoriais - tai leis mums užkoduoti tekstą prieš perduodant jį funkcijai `to_cbow`.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "x-dsXygOieXn",
|
||||
"outputId": "c2218280-e540-40ba-9546-efe48d0d714f"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"[['like', 'I'], ['to', 'I'], ['I', 'like'], ['to', 'like'], ['train', 'like'], ['I', 'to'], ['like', 'to'], ['train', 'to'], ['networks', 'to'], ['like', 'train'], ['to', 'train'], ['networks', 'train'], ['to', 'networks'], ['train', 'networks']]\n",
|
||||
"[[232, 172], [5, 172], [172, 232], [5, 232], [0, 232], [172, 5], [232, 5], [0, 5], [1202, 5], [232, 0], [5, 0], [1202, 0], [5, 1202], [0, 1202]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def to_cbow(sent,window_size=2):\n",
|
||||
" res = []\n",
|
||||
" for i,x in enumerate(sent):\n",
|
||||
" for j in range(max(0,i-window_size),min(i+window_size+1,len(sent))):\n",
|
||||
" if i!=j:\n",
|
||||
" res.append([sent[j],x])\n",
|
||||
" return res\n",
|
||||
"\n",
|
||||
"print(to_cbow(['I','like','to','train','networks']))\n",
|
||||
"print(to_cbow(encode('I like to train networks', vocab)))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "XVaaDLjaaHVb"
|
||||
},
|
||||
"source": [
|
||||
"Pasiruoškime mokymo duomenų rinkinį. Peržiūrėsime visas naujienas, iškviesime `to_cbow`, kad gautume žodžių porų sąrašą, ir pridėsime tas poras į `X` ir `Y`. Siekdami sutaupyti laiko, apsiribosime pirmomis 10 tūkst. naujienų - jei turite daugiau laiko laukti ir norite gauti geresnius įterpimus, lengvai galite pašalinti šį apribojimą :)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "54b-Gd9TieXo"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"X = []\n",
|
||||
"Y = []\n",
|
||||
"for i, x in zip(range(10000), train_dataset):\n",
|
||||
" for w1, w2 in to_cbow(encode(x[1], vocab), window_size = 5):\n",
|
||||
" X.append(w1)\n",
|
||||
" Y.append(w2)\n",
|
||||
"\n",
|
||||
"X = torch.tensor(X)\n",
|
||||
"Y = torch.tensor(Y)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"Mes taip pat konvertuosime tuos duomenis į vieną duomenų rinkinį ir sukursime duomenų įkroviklį:\n"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "cwWy0PzXWhN5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"class SimpleIterableDataset(torch.utils.data.IterableDataset):\n",
|
||||
" def __init__(self, X, Y):\n",
|
||||
" super(SimpleIterableDataset).__init__()\n",
|
||||
" self.data = []\n",
|
||||
" for i in range(len(X)):\n",
|
||||
" self.data.append( (Y[i], X[i]) )\n",
|
||||
" random.shuffle(self.data)\n",
|
||||
"\n",
|
||||
" def __iter__(self):\n",
|
||||
" return iter(self.data)"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "mfoAcGPFZU8p"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "e4NQ_-5waHVc"
|
||||
},
|
||||
"source": [
|
||||
"Mes taip pat konvertuosime tuos duomenis į vieną duomenų rinkinį ir sukursime duomenų įkroviklį:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "AbLUcojlieXo"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ds = SimpleIterableDataset(X, Y)\n",
|
||||
"dl = torch.utils.data.DataLoader(ds, batch_size = 256)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "pKQr7sXeaHVc"
|
||||
},
|
||||
"source": [
|
||||
"Dabar pradėkime tikrąjį mokymą. Naudosime `SGD` optimizatorių su gana dideliu mokymosi greičiu. Taip pat galite pabandyti naudoti kitus optimizatorius, tokius kaip `Adam`. Iš pradžių treniruosime 10 epochų – ir, jei norite dar mažesnio nuostolio, galite vėl paleisti šią langelį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"def train_epoch(net, dataloader, lr = 0.01, optimizer = None, loss_fn = torch.nn.CrossEntropyLoss(), epochs = None, report_freq = 1):\n",
|
||||
" optimizer = optimizer or torch.optim.Adam(net.parameters(), lr = lr)\n",
|
||||
" loss_fn = loss_fn.to(device)\n",
|
||||
" net.train()\n",
|
||||
"\n",
|
||||
" for i in range(epochs):\n",
|
||||
" total_loss, j = 0, 0, \n",
|
||||
" for labels, features in dataloader:\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" features, labels = features.to(device), labels.to(device)\n",
|
||||
" out = net(features)\n",
|
||||
" loss = loss_fn(out, labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" total_loss += loss\n",
|
||||
" j += 1\n",
|
||||
" if i % report_freq == 0:\n",
|
||||
" print(f\"Epoch: {i+1}: loss={total_loss.item()/j}\")\n",
|
||||
"\n",
|
||||
" return total_loss.item()/j"
|
||||
],
|
||||
"metadata": {
|
||||
"id": "HeeCYKr_KF1w"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"train_epoch(net = model, dataloader = dl, optimizer = torch.optim.SGD(model.parameters(), lr = 0.1), loss_fn = torch.nn.CrossEntropyLoss(), epochs = 10)"
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "KVgwGtDHgDlT",
|
||||
"outputId": "2447833f-f0e3-4566-c33d-addbfe2f451d"
|
||||
},
|
||||
"execution_count": null,
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"Epoch: 1: loss=5.664632366860172\n",
|
||||
"Epoch: 2: loss=5.632101973960962\n",
|
||||
"Epoch: 3: loss=5.610399051405015\n",
|
||||
"Epoch: 4: loss=5.594621561080262\n",
|
||||
"Epoch: 5: loss=5.582538017415446\n",
|
||||
"Epoch: 6: loss=5.572900234519603\n",
|
||||
"Epoch: 7: loss=5.564951676341915\n",
|
||||
"Epoch: 8: loss=5.558288112064614\n",
|
||||
"Epoch: 9: loss=5.552576955031129\n",
|
||||
"Epoch: 10: loss=5.547634165194347\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"5.547634165194347"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "W8u2qXZmaHVd"
|
||||
},
|
||||
"source": [
|
||||
"## Bandymas su Word2Vec\n",
|
||||
"\n",
|
||||
"Norėdami naudoti Word2Vec, ištraukime vektorius, atitinkančius visus žodžius mūsų žodyne:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "r8TatcXjkU_t"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vectors = torch.stack([embedder(torch.tensor(vocab[s])) for s in vocab.itos], 0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "3OcX21UOaHVd"
|
||||
},
|
||||
"source": [
|
||||
"Pažiūrėkime, pavyzdžiui, kaip žodis **Paryžius** yra užkoduotas į vektorių:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "bz6tAeLzieXp",
|
||||
"outputId": "5b20850e-4342-45e9-f840-cfac2b4d61d8"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "stream",
|
||||
"name": "stdout",
|
||||
"text": [
|
||||
"tensor([-0.0915, 2.1224, -0.0281, -0.6819, 1.1219, 0.6458, -1.3704, -1.3314,\n",
|
||||
" -1.1437, 0.4496, 0.2301, -0.3515, -0.8485, 1.0481, 0.4386, -0.8949,\n",
|
||||
" 0.5644, 1.0939, -2.5096, 3.2949, -0.2601, -0.8640, 0.1421, -0.0804,\n",
|
||||
" -0.5083, -1.0560, 0.9753, -0.5949, -1.6046, 0.5774],\n",
|
||||
" grad_fn=<EmbeddingBackward>)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"paris_vec = embedder(torch.tensor(vocab['paris']))\n",
|
||||
"print(paris_vec)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "pHTJlaeYaHVd"
|
||||
},
|
||||
"source": [
|
||||
"Įdomu naudoti Word2Vec ieškant sinonimų. Ši funkcija grąžins `n` artimiausių žodžių pagal pateiktą įvestį. Norėdami juos rasti, apskaičiuojame $|w_i - v|$ normą, kur $v$ yra mūsų įvesties žodžiui atitinkantis vektorius, o $w_i$ yra $i$-tojo žodžio žodyne kodavimas. Tada surūšiuojame masyvą ir naudojame `argsort`, kad gautume atitinkamus indeksus, bei paimame pirmus `n` sąrašo elementus, kurie nurodo artimiausių žodžių pozicijas žodyne.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "NlZyi-_olFar",
|
||||
"outputId": "b5dbb163-88c4-4d5a-eaf2-6751f700e98c"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['microsoft', 'quoted', 'lp', 'rate', 'top']"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 56
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def close_words(x, n = 5):\n",
|
||||
" vec = embedder(torch.tensor(vocab[x]))\n",
|
||||
" top5 = np.linalg.norm(vectors.detach().numpy() - vec.detach().numpy(), axis = 1).argsort()[:n]\n",
|
||||
" return [ vocab.itos[x] for x in top5 ]\n",
|
||||
"\n",
|
||||
"close_words('microsoft')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "-dQq7xeAln0U",
|
||||
"outputId": "66f768c3-c248-4bfd-ce4f-c8ffc6d0dd0d"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['basketball', 'lot', 'sinai', 'states', 'healthdaynews']"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 51
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"close_words('basketball')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"base_uri": "https://localhost:8080/"
|
||||
},
|
||||
"id": "fJXqK26b29sa",
|
||||
"outputId": "78f0baba-ffd0-485a-dd87-0a12bedfd7fa"
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "execute_result",
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['funds', 'travel', 'sydney', 'japan', 'business']"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"execution_count": 77
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"close_words('funds')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"id": "My0VeTDd3Ji8"
|
||||
},
|
||||
"source": [
|
||||
"## Pagrindinė mintis\n",
|
||||
"\n",
|
||||
"Naudodami sumanius metodus, tokius kaip CBoW, galime apmokyti Word2Vec modelį. Taip pat galite pabandyti apmokyti skip-gram modelį, kuris yra mokomas numatyti kaimyninį žodį, turint centrinį, ir pažiūrėti, kaip gerai jis veikia.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors stengiamės užtikrinti tikslumą, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"colab": {
|
||||
"collapsed_sections": [],
|
||||
"name": "CBoW-PyTorch.ipynb",
|
||||
"provenance": []
|
||||
},
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"orig_nbformat": 4,
|
||||
"gpuClass": "standard",
|
||||
"coopTranslator": {
|
||||
"original_hash": "36df28efe3fe40b6fb0a7fa48fe3ea82",
|
||||
"translation_date": "2025-08-31T13:46:19+00:00",
|
||||
"source_file": "lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,479 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Rekurentiniai neuroniniai tinklai\n",
|
||||
"\n",
|
||||
"Ankstesniame modulyje naudojome turtingas semantines tekstų reprezentacijas ir paprastą linijinį klasifikatorių virš įterpimų. Ši architektūra padeda užfiksuoti agreguotą žodžių prasmę sakinyje, tačiau ji neatsižvelgia į **žodžių tvarką**, nes agregavimo operacija virš įterpimų pašalina šią informaciją iš pradinio teksto. Kadangi šie modeliai negali modeliuoti žodžių tvarkos, jie negali spręsti sudėtingesnių ar dviprasmiškų užduočių, tokių kaip teksto generavimas ar klausimų atsakymas.\n",
|
||||
"\n",
|
||||
"Norėdami užfiksuoti teksto sekos prasmę, turime naudoti kitą neuroninių tinklų architektūrą, vadinamą **rekurentiniu neuroniniu tinklu** arba RNN. RNN tinklu mes perduodame savo sakinį per tinklą po vieną simbolį, o tinklas sukuria tam tikrą **būseną**, kurią vėliau perduodame tinklui kartu su kitu simboliu.\n",
|
||||
"\n",
|
||||
"Duotai įvesties sekai $X_0,\\dots,X_n$, RNN sukuria neuroninių tinklų blokų seką ir treniruoja šią seką nuo pradžios iki pabaigos naudodamas atgalinę sklaidą. Kiekvienas tinklo blokas kaip įvestį gauna porą $(X_i,S_i)$ ir kaip rezultatą sukuria $S_{i+1}$. Galutinė būsena $S_n$ arba išvestis $X_n$ perduodama linijiniam klasifikatoriui, kad būtų gautas rezultatas. Visi tinklo blokai dalijasi tais pačiais svoriais ir yra treniruojami nuo pradžios iki pabaigos per vieną atgalinės sklaidos etapą.\n",
|
||||
"\n",
|
||||
"Kadangi būsenos vektoriai $S_0,\\dots,S_n$ perduodami per tinklą, jis gali išmokti sekos priklausomybes tarp žodžių. Pavyzdžiui, kai žodis *ne* pasirodo kažkur sekoje, tinklas gali išmokti paneigti tam tikrus elementus būsenos vektoriuje, sukeldamas neigimą.\n",
|
||||
"\n",
|
||||
"> Kadangi visų RNN blokų svoriai paveikslėlyje yra bendri, tas pats paveikslėlis gali būti pavaizduotas kaip vienas blokas (dešinėje) su rekursiniu grįžtamojo ryšio ciklu, kuris perduoda tinklo išvesties būseną atgal į įvestį.\n",
|
||||
"\n",
|
||||
"Pažiūrėkime, kaip rekurentiniai neuroniniai tinklai gali padėti klasifikuoti mūsų naujienų duomenų rinkinį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loading dataset...\n",
|
||||
"Building vocab...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"from torchnlp import *\n",
|
||||
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
||||
"vocab_size = len(vocab)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Paprastas RNN klasifikatorius\n",
|
||||
"\n",
|
||||
"Naudojant paprastą RNN, kiekvienas pasikartojantis vienetas yra paprastas linijinis tinklas, kuris priima sujungtą įvesties vektorių ir būsenos vektorių, o tada sukuria naują būsenos vektorių. PyTorch šį vienetą atvaizduoja naudodamas `RNNCell` klasę, o tokių ląstelių tinklą - kaip `RNN` sluoksnį.\n",
|
||||
"\n",
|
||||
"Norėdami apibrėžti RNN klasifikatorių, pirmiausia pritaikysime įterpimo sluoksnį, kad sumažintume įvesties žodyno dimensiją, o tada virš jo pridėsime RNN sluoksnį:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class RNNClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.hidden_dim = hidden_dim\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x):\n",
|
||||
" batch_size = x.size(0)\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" x,h = self.rnn(x)\n",
|
||||
" return self.fc(x.mean(dim=1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba:** Čia naudojame netreniruotą įterpimo sluoksnį dėl paprastumo, tačiau dar geresniems rezultatams galime naudoti iš anksto apmokytą įterpimo sluoksnį su Word2Vec arba GloVe įterpimais, kaip aprašyta ankstesniame skyriuje. Norėdami geriau suprasti, galite pritaikyti šį kodą darbui su iš anksto apmokytais įterpimais.\n",
|
||||
"\n",
|
||||
"Mūsų atveju naudosime užpildytą duomenų kaupiklį, todėl kiekvienas paketas turės tam tikrą skaičių užpildytų sekų, kurios bus vienodo ilgio. RNN sluoksnis priims įterpimo tensorių seką ir sugeneruos du išvesties rezultatus:\n",
|
||||
"* $x$ yra RNN ląstelių išvesties seka kiekviename žingsnyje\n",
|
||||
"* $h$ yra galutinė paslėpta būsena paskutiniam sekos elementui\n",
|
||||
"\n",
|
||||
"Tada pritaikome pilnai sujungtą linijinį klasifikatorių, kad gautume klasių skaičių.\n",
|
||||
"\n",
|
||||
"> **Pastaba:** RNN yra gana sunku treniruoti, nes kai RNN ląstelės yra išskleistos pagal sekos ilgį, sluoksnių, dalyvaujančių atgalinėje propagacijoje, skaičius tampa labai didelis. Todėl reikia pasirinkti mažą mokymosi greitį ir treniruoti tinklą su didesniu duomenų rinkiniu, kad būtų pasiekti geri rezultatai. Tai gali užtrukti gana ilgai, todėl rekomenduojama naudoti GPU.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.3090625\n",
|
||||
"6400: acc=0.38921875\n",
|
||||
"9600: acc=0.4590625\n",
|
||||
"12800: acc=0.511953125\n",
|
||||
"16000: acc=0.5506875\n",
|
||||
"19200: acc=0.57921875\n",
|
||||
"22400: acc=0.6070089285714285\n",
|
||||
"25600: acc=0.6304296875\n",
|
||||
"28800: acc=0.6484027777777778\n",
|
||||
"32000: acc=0.66509375\n",
|
||||
"35200: acc=0.6790056818181818\n",
|
||||
"38400: acc=0.6929166666666666\n",
|
||||
"41600: acc=0.7035817307692308\n",
|
||||
"44800: acc=0.7137276785714286\n",
|
||||
"48000: acc=0.72225\n",
|
||||
"51200: acc=0.73001953125\n",
|
||||
"54400: acc=0.7372794117647059\n",
|
||||
"57600: acc=0.7436631944444444\n",
|
||||
"60800: acc=0.7503947368421052\n",
|
||||
"64000: acc=0.75634375\n",
|
||||
"67200: acc=0.7615773809523809\n",
|
||||
"70400: acc=0.7662642045454545\n",
|
||||
"73600: acc=0.7708423913043478\n",
|
||||
"76800: acc=0.7751822916666666\n",
|
||||
"80000: acc=0.7790625\n",
|
||||
"83200: acc=0.7825\n",
|
||||
"86400: acc=0.7858564814814815\n",
|
||||
"89600: acc=0.7890513392857142\n",
|
||||
"92800: acc=0.7920474137931034\n",
|
||||
"96000: acc=0.7952708333333334\n",
|
||||
"99200: acc=0.7982258064516129\n",
|
||||
"102400: acc=0.80099609375\n",
|
||||
"105600: acc=0.8037594696969697\n",
|
||||
"108800: acc=0.8060569852941176\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
|
||||
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
||||
"train_epoch(net,train_loader, lr=0.001)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Ilgalaikė trumpalaikė atmintis (LSTM)\n",
|
||||
"\n",
|
||||
"Viena iš pagrindinių klasikinių RNN problemų yra vadinamoji **nykstančių gradientų** problema. Kadangi RNN yra mokomi nuo pradžios iki pabaigos vienu atgalinio sklidimo etapu, jiems sunku perduoti klaidą į pirmuosius tinklo sluoksnius, todėl tinklas negali išmokti ryšių tarp tolimų žodžių. Vienas iš būdų išvengti šios problemos yra įvesti **aiškų būsenos valdymą** naudojant vadinamuosius **vartus**. Yra dvi žinomiausios tokio tipo architektūros: **Ilgalaikė trumpalaikė atmintis** (LSTM) ir **Vartų relės vienetas** (GRU).\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"LSTM tinklas yra organizuotas panašiai kaip RNN, tačiau yra dvi būsenos, kurios perduodamos iš sluoksnio į sluoksnį: faktinė būsena $c$ ir paslėptas vektorius $h$. Kiekviename vienete paslėptas vektorius $h_i$ yra sujungiamas su įvestimi $x_i$, ir jie kontroliuoja, kas vyksta su būsena $c$ per **vartus**. Kiekvienas vartas yra neuroninis tinklas su sigmoidine aktyvacija (rezultatas intervale $[0,1]$), kurį galima įsivaizduoti kaip bitų kaukę, kai jis dauginamas iš būsenos vektoriaus. Yra šie vartai (iš kairės į dešinę paveikslėlyje aukščiau):\n",
|
||||
"* **užmaršumo vartai** priima paslėptą vektorių ir nustato, kuriuos vektoriaus $c$ komponentus reikia pamiršti, o kuriuos perduoti toliau.\n",
|
||||
"* **įvesties vartai** paima tam tikrą informaciją iš įvesties ir paslėpto vektoriaus bei įterpia ją į būseną.\n",
|
||||
"* **išvesties vartai** transformuoja būseną per tam tikrą linijinį sluoksnį su $\\tanh$ aktyvacija, tada pasirenka kai kuriuos jos komponentus naudodami paslėptą vektorių $h_i$, kad sukurtų naują būseną $c_{i+1}$.\n",
|
||||
"\n",
|
||||
"Būsenos $c$ komponentus galima įsivaizduoti kaip tam tikrus vėliavėles, kurias galima įjungti ir išjungti. Pavyzdžiui, kai sekoje sutinkame vardą *Alice*, galime manyti, kad jis nurodo moterišką veikėją, ir pakelti vėliavėlę būsenoje, kad sakinyje turime moterišką daiktavardį. Kai toliau sutinkame frazę *ir Tom*, pakelsime vėliavėlę, kad turime daugiskaitinį daiktavardį. Taigi, manipuliuodami būsena, galime, tikėtina, sekti sakinio dalių gramatines savybes.\n",
|
||||
"\n",
|
||||
"> **Note**: Puikus šaltinis, padedantis suprasti LSTM vidinę struktūrą, yra šis puikus Christopher Olah straipsnis [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/).\n",
|
||||
"\n",
|
||||
"Nors LSTM ląstelės vidinė struktūra gali atrodyti sudėtinga, PyTorch slepia šią įgyvendinimą `LSTMCell` klasėje ir pateikia `LSTM` objektą, skirtą visam LSTM sluoksniui atvaizduoti. Todėl LSTM klasifikatoriaus įgyvendinimas bus gana panašus į paprasto RNN, kurį matėme aukščiau:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class LSTMClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.hidden_dim = hidden_dim\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
||||
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x):\n",
|
||||
" batch_size = x.size(0)\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" x,(h,c) = self.rnn(x)\n",
|
||||
" return self.fc(h[-1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.259375\n",
|
||||
"6400: acc=0.25859375\n",
|
||||
"9600: acc=0.26177083333333334\n",
|
||||
"12800: acc=0.2784375\n",
|
||||
"16000: acc=0.313\n",
|
||||
"19200: acc=0.3528645833333333\n",
|
||||
"22400: acc=0.3965625\n",
|
||||
"25600: acc=0.4385546875\n",
|
||||
"28800: acc=0.4752777777777778\n",
|
||||
"32000: acc=0.505375\n",
|
||||
"35200: acc=0.5326704545454546\n",
|
||||
"38400: acc=0.5557552083333334\n",
|
||||
"41600: acc=0.5760817307692307\n",
|
||||
"44800: acc=0.5954910714285714\n",
|
||||
"48000: acc=0.6118333333333333\n",
|
||||
"51200: acc=0.62681640625\n",
|
||||
"54400: acc=0.6404779411764706\n",
|
||||
"57600: acc=0.6520138888888889\n",
|
||||
"60800: acc=0.662828947368421\n",
|
||||
"64000: acc=0.673546875\n",
|
||||
"67200: acc=0.6831547619047619\n",
|
||||
"70400: acc=0.6917897727272727\n",
|
||||
"73600: acc=0.6997146739130434\n",
|
||||
"76800: acc=0.707109375\n",
|
||||
"80000: acc=0.714075\n",
|
||||
"83200: acc=0.7209134615384616\n",
|
||||
"86400: acc=0.727037037037037\n",
|
||||
"89600: acc=0.7326674107142858\n",
|
||||
"92800: acc=0.7379633620689655\n",
|
||||
"96000: acc=0.7433645833333333\n",
|
||||
"99200: acc=0.7479032258064516\n",
|
||||
"102400: acc=0.752119140625\n",
|
||||
"105600: acc=0.7562405303030303\n",
|
||||
"108800: acc=0.76015625\n",
|
||||
"112000: acc=0.7641339285714286\n",
|
||||
"115200: acc=0.7677777777777778\n",
|
||||
"118400: acc=0.7711233108108108\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.03487814127604167, 0.7728)"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
||||
"train_epoch(net,train_loader, lr=0.001)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Supakuotos sekos\n",
|
||||
"\n",
|
||||
"Mūsų pavyzdyje turėjome užpildyti visas mini partijos sekas nulio vektoriais. Nors tai šiek tiek švaisto atmintį, su RNN dar svarbiau, kad papildomos RNN ląstelės yra sukuriamos užpildytoms įvesties reikšmėms, kurios dalyvauja mokyme, tačiau neturi jokios svarbios įvesties informacijos. Būtų daug geriau, jei RNN būtų mokoma tik pagal tikrąją sekos ilgį.\n",
|
||||
"\n",
|
||||
"Tam PyTorch įveda specialų užpildytų sekų saugojimo formatą. Tarkime, turime užpildytą įvesties mini partiją, kuri atrodo taip:\n",
|
||||
"```\n",
|
||||
"[[1,2,3,4,5],\n",
|
||||
" [6,7,8,0,0],\n",
|
||||
" [9,0,0,0,0]]\n",
|
||||
"```\n",
|
||||
"Čia 0 reiškia užpildytas reikšmes, o tikrasis įvesties sekų ilgio vektorius yra `[5,3,1]`.\n",
|
||||
"\n",
|
||||
"Kad efektyviai mokytume RNN su užpildyta seka, norime pradėti mokymą su pirmąja RNN ląstelių grupe, turinčia didelę mini partiją (`[1,6,9]`), tačiau tada baigti trečios sekos apdorojimą ir tęsti mokymą su mažesnėmis mini partijomis (`[2,7]`, `[3,8]`) ir taip toliau. Taigi, supakuota seka yra pateikiama kaip vienas vektorius – mūsų atveju `[1,6,9,2,7,3,8,4,5]`, ir ilgio vektorius (`[5,3,1]`), iš kurio galime lengvai atkurti pradinę užpildytą mini partiją.\n",
|
||||
"\n",
|
||||
"Norėdami sukurti supakuotą seką, galime naudoti funkciją `torch.nn.utils.rnn.pack_padded_sequence`. Visos rekursinės sluoksnių rūšys, įskaitant RNN, LSTM ir GRU, palaiko supakuotas sekas kaip įvestį ir sukuria supakuotą išvestį, kurią galima dekoduoti naudojant `torch.nn.utils.rnn.pad_packed_sequence`.\n",
|
||||
"\n",
|
||||
"Kad galėtume sukurti supakuotą seką, turime perduoti ilgio vektorių tinklui, todėl mums reikia kitos funkcijos mini partijoms paruošti:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def pad_length(b):\n",
|
||||
" # build vectorized sequence\n",
|
||||
" v = [encode(x[1]) for x in b]\n",
|
||||
" # compute max length of a sequence in this minibatch and length sequence itself\n",
|
||||
" len_seq = list(map(len,v))\n",
|
||||
" l = max(len_seq)\n",
|
||||
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
|
||||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||||
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
|
||||
" torch.tensor(len_seq)\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Tikrasis tinklas būtų labai panašus į aukščiau pateiktą `LSTMClassifier`, tačiau `forward` perdavimas gaus tiek užpildytą mini partiją, tiek sekų ilgių vektorių. Po įterpimo apskaičiavimo, mes apskaičiuojame supakuotą seką, perduodame ją LSTM sluoksniui ir tada išpakuojame rezultatą atgal.\n",
|
||||
"\n",
|
||||
"> **Pastaba**: Iš tikrųjų mes nenaudojame išpakuoto rezultato `x`, nes tolesniuose skaičiavimuose naudojame iš paslėptų sluoksnių gautą išvestį. Todėl šį išpakavimą galima visiškai pašalinti iš šio kodo. Priežastis, kodėl jį čia pateikiame, yra ta, kad jums būtų lengviau modifikuoti šį kodą, jei prireiktų naudoti tinklo išvestį tolimesniuose skaičiavimuose.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class LSTMPackClassifier(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
||||
" super().__init__()\n",
|
||||
" self.hidden_dim = hidden_dim\n",
|
||||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||||
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
||||
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
||||
"\n",
|
||||
" def forward(self, x, lengths):\n",
|
||||
" batch_size = x.size(0)\n",
|
||||
" x = self.embedding(x)\n",
|
||||
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
|
||||
" pad_x,(h,c) = self.rnn(pad_x)\n",
|
||||
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
|
||||
" return self.fc(h[-1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"3200: acc=0.285625\n",
|
||||
"6400: acc=0.33359375\n",
|
||||
"9600: acc=0.3876041666666667\n",
|
||||
"12800: acc=0.44078125\n",
|
||||
"16000: acc=0.4825\n",
|
||||
"19200: acc=0.5235416666666667\n",
|
||||
"22400: acc=0.5559821428571429\n",
|
||||
"25600: acc=0.58609375\n",
|
||||
"28800: acc=0.6116666666666667\n",
|
||||
"32000: acc=0.63340625\n",
|
||||
"35200: acc=0.6525284090909091\n",
|
||||
"38400: acc=0.668515625\n",
|
||||
"41600: acc=0.6822596153846154\n",
|
||||
"44800: acc=0.6948214285714286\n",
|
||||
"48000: acc=0.7052708333333333\n",
|
||||
"51200: acc=0.71521484375\n",
|
||||
"54400: acc=0.7239889705882353\n",
|
||||
"57600: acc=0.7315277777777778\n",
|
||||
"60800: acc=0.7388486842105263\n",
|
||||
"64000: acc=0.74571875\n",
|
||||
"67200: acc=0.7518303571428572\n",
|
||||
"70400: acc=0.7576988636363636\n",
|
||||
"73600: acc=0.7628940217391305\n",
|
||||
"76800: acc=0.7681510416666667\n",
|
||||
"80000: acc=0.7728125\n",
|
||||
"83200: acc=0.7772235576923077\n",
|
||||
"86400: acc=0.7815393518518519\n",
|
||||
"89600: acc=0.7857700892857142\n",
|
||||
"92800: acc=0.7895043103448276\n",
|
||||
"96000: acc=0.7930520833333333\n",
|
||||
"99200: acc=0.7959072580645161\n",
|
||||
"102400: acc=0.798994140625\n",
|
||||
"105600: acc=0.802064393939394\n",
|
||||
"108800: acc=0.8051378676470589\n",
|
||||
"112000: acc=0.8077857142857143\n",
|
||||
"115200: acc=0.8104600694444445\n",
|
||||
"118400: acc=0.8128293918918919\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(0.029785829671223958, 0.8138166666666666)"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
||||
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba:** Galbūt pastebėjote parametrą `use_pack_sequence`, kurį perduodame mokymo funkcijai. Šiuo metu funkcija `pack_padded_sequence` reikalauja, kad ilgio sekos tensorius būtų CPU įrenginyje, todėl mokymo funkcija turi vengti perkelti ilgio sekos duomenis į GPU mokymo metu. Galite peržiūrėti `train_emb` funkcijos įgyvendinimą [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py) faile.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Dvikryptės ir daugiapakopės RNN\n",
|
||||
"\n",
|
||||
"Mūsų pavyzdžiuose visos rekursinės tinklų operacijos vyko viena kryptimi – nuo sekos pradžios iki pabaigos. Tai atrodo natūralu, nes primena būdą, kaip skaitome ar klausomės kalbos. Tačiau daugelyje praktinių atvejų turime atsitiktinę prieigą prie įvesties sekos, todėl gali būti prasminga vykdyti rekursinį skaičiavimą abiem kryptimis. Tokie tinklai vadinami **dvikrypčiais** RNN, ir juos galima sukurti perduodant `bidirectional=True` parametrą RNN/LSTM/GRU konstruktoriui.\n",
|
||||
"\n",
|
||||
"Dirbant su dvikrypčiu tinklu, mums reikės dviejų paslėptų būsenų vektorių – po vieną kiekvienai krypčiai. PyTorch koduoja šiuos vektorius kaip vieną dvigubai didesnio dydžio vektorių, kas yra gana patogu, nes paprastai galutinę paslėptą būseną perduodate pilnai sujungtam linijiniam sluoksniui, ir jums tereikia atsižvelgti į šį dydžio padidėjimą kuriant sluoksnį.\n",
|
||||
"\n",
|
||||
"Rekursinis tinklas, vienkryptis ar dvikryptis, fiksuoja tam tikrus sekos modelius ir gali juos išsaugoti būsenos vektoriuje arba perduoti į išvestį. Kaip ir konvoliuciniuose tinkluose, galime sukurti kitą rekursinį sluoksnį ant pirmojo, kad fiksuotume aukštesnio lygio modelius, sudarytus iš žemesnio lygio modelių, kuriuos ištraukė pirmasis sluoksnis. Tai veda mus prie **daugiapakopės RNN** sąvokos, kurią sudaro du ar daugiau rekursinių tinklų, kur ankstesnio sluoksnio išvestis perduodama kitam sluoksniui kaip įvestis.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Paveikslas iš [šio nuostabaus įrašo](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) autoriaus Fernando López*\n",
|
||||
"\n",
|
||||
"PyTorch palengvina tokių tinklų konstravimą, nes jums tereikia perduoti `num_layers` parametrą RNN/LSTM/GRU konstruktoriui, kad automatiškai sukurtumėte kelis rekursijos sluoksnius. Tai taip pat reiškia, kad paslėpto/būsenos vektoriaus dydis proporcingai padidės, ir jums reikės atsižvelgti į tai tvarkant rekursinių sluoksnių išvestį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## RNN kitiems užduotims\n",
|
||||
"\n",
|
||||
"Šiame skyriuje matėme, kad RNN gali būti naudojami sekų klasifikavimui, tačiau iš tiesų jie gali atlikti daug daugiau užduočių, tokių kaip teksto generavimas, mašininis vertimas ir kt. Šias užduotis aptarsime kitame skyriuje.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "522ee52ae3d5ae933e283286254e9a55",
|
||||
"translation_date": "2025-08-31T14:00:44+00:00",
|
||||
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Rekurentiniai neuroniniai tinklai\n",
|
||||
"\n",
|
||||
"Ankstesniame modulyje aptarėme turtingas semantines teksto reprezentacijas. Naudota architektūra apima agreguotą žodžių prasmę sakinyje, tačiau ji neatsižvelgia į žodžių **tvarką**, nes agregavimo operacija, atliekama po įterpimų, pašalina šią informaciją iš pradinio teksto. Kadangi šie modeliai negali atspindėti žodžių tvarkos, jie negali spręsti sudėtingesnių ar dviprasmiškų užduočių, tokių kaip teksto generavimas ar klausimų atsakymas.\n",
|
||||
"\n",
|
||||
"Norėdami užfiksuoti teksto sekos prasmę, naudosime neuroninių tinklų architektūrą, vadinamą **rekurentiniais neuroniniais tinklais** (RNN). Naudojant RNN, mes perduodame savo sakinį per tinklą po vieną žodį, o tinklas sukuria tam tikrą **būseną**, kurią vėliau perduodame tinklui kartu su kitu žodžiu.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Turint įvesties žodžių seką $X_0,\\dots,X_n$, RNN sukuria neuroninių tinklų blokų seką ir treniruoja šią seką nuo pradžios iki pabaigos naudodamas atgalinį sklidimą. Kiekvienas tinklo blokas kaip įvestį gauna porą $(X_i,S_i)$ ir kaip rezultatą sukuria $S_{i+1}$. Galutinė būsena $S_n$ arba išvestis $Y_n$ perduodama į linijinį klasifikatorių, kad būtų gautas rezultatas. Visi tinklo blokai dalijasi tais pačiais svoriais ir yra treniruojami nuo pradžios iki pabaigos per vieną atgalinio sklidimo etapą.\n",
|
||||
"\n",
|
||||
"> Aukščiau pateiktame paveikslėlyje rekurentinis neuroninis tinklas parodytas išskleista forma (kairėje) ir kompaktiškesne rekurentine reprezentacija (dešinėje). Svarbu suprasti, kad visos RNN ląstelės turi tuos pačius **dalijamus svorius**.\n",
|
||||
"\n",
|
||||
"Kadangi būsenos vektoriai $S_0,\\dots,S_n$ perduodami per tinklą, RNN gali išmokti sekos priklausomybes tarp žodžių. Pavyzdžiui, kai žodis *ne* pasirodo kažkur sekoje, tinklas gali išmokti paneigti tam tikrus elementus būsenos vektoriuje.\n",
|
||||
"\n",
|
||||
"Kiekvienoje RNN ląstelėje yra du svorio matricos: $W_H$ ir $W_I$, bei poslinkis $b$. Kiekviename RNN žingsnyje, turint įvestį $X_i$ ir įvesties būseną $S_i$, išvesties būsena apskaičiuojama kaip $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, kur $f$ yra aktyvavimo funkcija (dažnai $\\tanh$).\n",
|
||||
"\n",
|
||||
"> Tokiems uždaviniams kaip teksto generavimas (kurį aptarsime kitame skyriuje) ar mašininis vertimas, mes taip pat norime gauti tam tikrą išvesties reikšmę kiekviename RNN žingsnyje. Tokiu atveju yra dar viena matrica $W_O$, o išvestis apskaičiuojama kaip $Y_i=f(W_O\\times S_i+b_O)$.\n",
|
||||
"\n",
|
||||
"Pažiūrėkime, kaip rekurentiniai neuroniniai tinklai gali padėti klasifikuoti mūsų naujienų duomenų rinkinį.\n",
|
||||
"\n",
|
||||
"> Smėliadėžės aplinkoje turime paleisti šią langelį, kad įsitikintume, jog reikalinga biblioteka yra įdiegta ir duomenys yra iš anksto užkrauti. Jei dirbate vietoje, galite praleisti šį langelį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import sys\n",
|
||||
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
|
||||
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"# We are going to be training pretty large models. In order not to face errors, we need\n",
|
||||
"# to set tensorflow option to grow GPU memory allocation when required\n",
|
||||
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
||||
"if len(physical_devices)>0:\n",
|
||||
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
||||
"\n",
|
||||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"Kai treniruojami dideli modeliai, GPU atminties paskirstymas gali tapti problema. Taip pat gali tekti eksperimentuoti su skirtingais mini paketų dydžiais, kad duomenys tilptų į GPU atmintį, tačiau mokymas būtų pakankamai greitas. Jei vykdote šį kodą savo GPU kompiuteryje, galite eksperimentuoti su mini paketų dydžio koregavimu, kad paspartintumėte mokymą.\n",
|
||||
"\n",
|
||||
"> **Note**: Kai kurios NVidia tvarkyklių versijos yra žinomos dėl to, kad po modelio mokymo neatlaisvina atminties. Šiame užrašų knygelėje vykdome kelis pavyzdžius, ir tai gali sukelti atminties išsekimą tam tikrose konfigūracijose, ypač jei atliekate savo eksperimentus toje pačioje užrašų knygelėje. Jei susiduriate su keistomis klaidomis pradėdami mokyti modelį, gali tekti iš naujo paleisti užrašų knygelės branduolį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"jupyter": {
|
||||
"outputs_hidden": false,
|
||||
"source_hidden": false
|
||||
},
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"batch_size = 16\n",
|
||||
"embed_size = 64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Paprastas RNN klasifikatorius\n",
|
||||
"\n",
|
||||
"Paprasto RNN atveju kiekvienas pasikartojantis vienetas yra paprastas linijinis tinklas, kuris priima įvesties vektorių ir būsenos vektorių, o tada sukuria naują būsenos vektorių. Keras bibliotekoje tai galima atvaizduoti naudojant `SimpleRNN` sluoksnį.\n",
|
||||
"\n",
|
||||
"Nors galime tiesiogiai perduoti vieno karšto kodavimo (one-hot encoded) žetonus į RNN sluoksnį, tai nėra gera idėja dėl jų didelio dimensionalumo. Todėl naudosime įterpimo (embedding) sluoksnį, kad sumažintume žodžių vektorių dimensionalumą, po to RNN sluoksnį ir galiausiai `Dense` klasifikatorių.\n",
|
||||
"\n",
|
||||
"> **Note**: Tais atvejais, kai dimensionalumas nėra toks didelis, pavyzdžiui, naudojant simbolių lygmens tokenizaciją, gali būti prasminga tiesiogiai perduoti vieno karšto kodavimo žetonus į RNN ląstelę.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
"text_vectorization (TextVect (None, None) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"embedding (Embedding) (None, None, 64) 1280000 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense (Dense) (None, 4) 68 \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 1,281,364\n",
|
||||
"Trainable params: 1,281,364\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vocab_size = 20000\n",
|
||||
"\n",
|
||||
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
|
||||
" max_tokens=vocab_size,\n",
|
||||
" input_shape=(1,))\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||||
" keras.layers.SimpleRNN(16),\n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba:** Čia naudojame netreniruotą įterpimo sluoksnį dėl paprastumo, tačiau geresniems rezultatams galime naudoti iš anksto apmokytą įterpimo sluoksnį, pasitelkiant Word2Vec, kaip aprašyta ankstesniame skyriuje. Būtų gera praktika pritaikyti šį kodą darbui su iš anksto apmokytais įterpimais.\n",
|
||||
"\n",
|
||||
"Dabar apmokykime savo RNN. Apskritai, RNN yra gana sudėtinga treniruoti, nes kai RNN ląstelės yra išskleidžiamos pagal sekos ilgį, sluoksnių, dalyvaujančių atgaliniame sklidime, skaičius tampa labai didelis. Todėl turime pasirinkti mažesnį mokymosi greitį ir treniruoti tinklą su didesniu duomenų rinkiniu, kad gautume gerus rezultatus. Tai gali užtrukti gana ilgai, todėl rekomenduojama naudoti GPU.\n",
|
||||
"\n",
|
||||
"Kad paspartintume procesą, RNN modelį treniruosime tik su naujienų antraštėmis, praleisdami aprašymą. Galite pabandyti treniruoti su aprašymu ir pažiūrėti, ar pavyks modelį apmokyti.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training vectorizer\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_title(x):\n",
|
||||
" return x['title']\n",
|
||||
"\n",
|
||||
"def tupelize_title(x):\n",
|
||||
" return (extract_title(x),x['label'])\n",
|
||||
"\n",
|
||||
"print('Training vectorizer')\n",
|
||||
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"nteract": {
|
||||
"transient": {
|
||||
"deleting": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"> **Pastaba**: tikslumas greičiausiai bus mažesnis, nes mokymui naudojami tik naujienų pavadinimai.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Peržiūrint kintamųjų sekas\n",
|
||||
"\n",
|
||||
"Atminkite, kad `TextVectorization` sluoksnis automatiškai užpildo kintamo ilgio sekas mini paketų viduje užpildymo ženklais. Pasirodo, kad šie ženklai taip pat dalyvauja mokyme ir gali apsunkinti modelio konvergenciją.\n",
|
||||
"\n",
|
||||
"Yra keletas būdų, kaip sumažinti užpildymo kiekį. Vienas iš jų – pertvarkyti duomenų rinkinį pagal sekos ilgį ir grupuoti visas sekas pagal dydį. Tai galima padaryti naudojant funkciją `tf.data.experimental.bucket_by_sequence_length` (žr. [dokumentaciją](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
|
||||
"\n",
|
||||
"Kitas būdas – naudoti **maskavimą**. Keras bibliotekoje kai kurie sluoksniai palaiko papildomą įvestį, kuri nurodo, kuriuos ženklus reikia atsižvelgti mokymo metu. Norėdami įtraukti maskavimą į mūsų modelį, galime arba pridėti atskirą `Masking` sluoksnį ([dokumentacija](https://keras.io/api/layers/core_layers/masking/)), arba nurodyti `mask_zero=True` parametrą mūsų `Embedding` sluoksnyje.\n",
|
||||
"\n",
|
||||
"> **Note**: Šis mokymas užtruks apie 5 minutes, kad būtų baigtas vienas epochas visame duomenų rinkinyje. Jei pritrūksite kantrybės, galite bet kada nutraukti mokymą. Taip pat galite apriboti mokymui naudojamų duomenų kiekį, pridėdami `.take(...)` sąlygą po `ds_train` ir `ds_test` duomenų rinkinių.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
|
||||
" keras.layers.SimpleRNN(16),\n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar, kai naudojame maskavimą, galime treniruoti modelį su visu antraščių ir aprašymų duomenų rinkiniu.\n",
|
||||
"\n",
|
||||
"> **Pastaba**: Ar pastebėjote, kad naudojome vektorizatorių, ištreniruotą pagal naujienų antraštes, o ne visą straipsnio tekstą? Tai gali lemti, kad kai kurie žetonai bus ignoruojami, todėl geriau būtų pertreniruoti vektorizatorių. Tačiau tai gali turėti tik labai mažą poveikį, todėl dėl paprastumo laikysimės ankstesnio iš anksto ištreniruoto vektorizatoriaus.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## LSTM: Ilgalaikė trumpalaikė atmintis\n",
|
||||
"\n",
|
||||
"Viena iš pagrindinių RNN problemų yra **nykstantys gradientai**. RNN tinklai gali būti gana ilgi, todėl jiems gali būti sunku perduoti gradientus atgal iki pat pirmojo tinklo sluoksnio atgalinio sklidimo metu. Kai taip nutinka, tinklas negali išmokti ryšių tarp tolimų žodžių. Vienas iš būdų išvengti šios problemos yra įvesti **aiškų būsenos valdymą** naudojant **vartus**. Dvi dažniausiai naudojamos architektūros, kurios naudoja vartus, yra **ilgalaikė trumpalaikė atmintis** (LSTM) ir **vartų relės vienetas** (GRU). Šiame skyriuje aptarsime LSTM.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"LSTM tinklas organizuotas panašiai kaip RNN, tačiau čia yra dvi būsenos, kurios perduodamos iš sluoksnio į sluoksnį: tikroji būsena $c$ ir paslėptas vektorius $h$. Kiekviename vienete paslėptas vektorius $h_{t-1}$ yra sujungiamas su įvestimi $x_t$, ir kartu jie kontroliuoja, kas vyksta su būsena $c_t$ ir išvestimi $h_{t}$ per **vartus**. Kiekvienas vartas turi sigmoidinę aktyvaciją (išvestis intervale $[0,1]$), kurią galima laikyti bitiniu masku, kai ji dauginama iš būsenos vektoriaus. LSTM turi šiuos vartus (nuo kairės į dešinę aukščiau esančiame paveikslėlyje):\n",
|
||||
"* **užmaršumo vartai**, kurie nustato, kuriuos vektoriaus $c_{t-1}$ komponentus reikia pamiršti, o kuriuos perduoti toliau.\n",
|
||||
"* **įvesties vartai**, kurie nustato, kiek informacijos iš įvesties vektoriaus ir ankstesnio paslėpto vektoriaus turėtų būti įtraukta į būsenos vektorių.\n",
|
||||
"* **išvesties vartai**, kurie paima naują būsenos vektorių ir nusprendžia, kurie jo komponentai bus naudojami naujam paslėptam vektoriui $h_t$ sukurti.\n",
|
||||
"\n",
|
||||
"Būsenos $c$ komponentus galima laikyti vėliavėlėmis, kurias galima įjungti arba išjungti. Pavyzdžiui, kai sekos metu sutinkame vardą *Alice*, galime spėti, kad tai moteris, ir pakelti vėliavėlę būsenoje, kuri nurodo, kad sakinyje yra moteriškos giminės daiktavardis. Kai toliau sutinkame žodžius *and Tom*, pakeliame vėliavėlę, kuri nurodo, kad dabar turime daugiskaitos daiktavardį. Taigi, manipuliuodami būsena, galime sekti sakinio gramatines savybes.\n",
|
||||
"\n",
|
||||
"> **Note**: Štai puikus šaltinis, padedantis suprasti LSTM vidinę struktūrą: [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) Christopher Olah.\n",
|
||||
"\n",
|
||||
"Nors LSTM ląstelės vidinė struktūra gali atrodyti sudėtinga, Keras paslepia šią įgyvendinimą `LSTM` sluoksnyje, todėl vienintelis dalykas, kurį reikia padaryti aukščiau pateiktame pavyzdyje, yra pakeisti rekursinį sluoksnį:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
||||
" keras.layers.LSTM(8),\n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Dvikryptės ir daugiasluoksnės RNN\n",
|
||||
"\n",
|
||||
"Mūsų ankstesniuose pavyzdžiuose rekursiniai tinklai veikė nuo sekos pradžios iki pabaigos. Tai mums atrodo natūralu, nes atitinka kryptį, kuria skaitome ar klausomės kalbos. Tačiau scenarijams, kuriems reikia atsitiktinės prieigos prie įvesties sekos, logiškiau vykdyti rekursinį skaičiavimą abiem kryptimis. RNN, leidžiančios skaičiavimus abiem kryptimis, vadinamos **dvikryptėmis** RNN, ir jos gali būti sukurtos apgaubiant rekursinį sluoksnį specialiu `Bidirectional` sluoksniu.\n",
|
||||
"\n",
|
||||
"> **Note**: `Bidirectional` sluoksnis sukuria dvi sluoksnio kopijas ir nustato vienos iš jų `go_backwards` savybę į `True`, kad ji eitų priešinga kryptimi per seką.\n",
|
||||
"\n",
|
||||
"Rekursiniai tinklai, tiek vienkryptės, tiek dvikryptės, fiksuoja sekos šablonus ir saugo juos būsenos vektoriuose arba grąžina juos kaip išvestį. Kaip ir konvoliuciniuose tinkluose, mes galime sukurti dar vieną rekursinį sluoksnį po pirmojo, kad užfiksuotume aukštesnio lygio šablonus, sudarytus iš žemesnio lygio šablonų, kuriuos ištraukė pirmasis sluoksnis. Tai veda mus prie **daugiasluoksnės RNN** sąvokos, kurią sudaro du ar daugiau rekursinių tinklų, kur ankstesnio sluoksnio išvestis perduodama kitam sluoksniui kaip įvestis.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Paveikslėlis iš [šio puikaus straipsnio](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) autoriaus Fernando López.*\n",
|
||||
"\n",
|
||||
"Keras leidžia lengvai sukurti šiuos tinklus, nes tereikia pridėti daugiau rekursinių sluoksnių prie modelio. Visuose sluoksniuose, išskyrus paskutinį, reikia nurodyti parametrą `return_sequences=True`, nes mums reikia, kad sluoksnis grąžintų visas tarpines būsenas, o ne tik galutinę rekursinio skaičiavimo būseną.\n",
|
||||
"\n",
|
||||
"Sukurkime dviejų sluoksnių dvikryptę LSTM mūsų klasifikavimo užduočiai.\n",
|
||||
"\n",
|
||||
"> **Note** šis kodas vėl užtrunka gana ilgai, tačiau jis suteikia didžiausią tikslumą, kokį iki šiol matėme. Taigi galbūt verta palaukti ir pamatyti rezultatą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" vectorizer,\n",
|
||||
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
|
||||
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
|
||||
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
|
||||
" keras.layers.Dense(4,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
||||
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## RNN kitiems užduočių tipams\n",
|
||||
"\n",
|
||||
"Iki šiol mes daugiausia dėmesio skyrėme RNN naudojimui tekstų sekų klasifikavimui. Tačiau jos gali atlikti ir daugybę kitų užduočių, tokių kaip teksto generavimas ar mašininis vertimas — šias užduotis aptarsime kitame skyriuje.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernel_info": {
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "py37_tensorflow",
|
||||
"language": "python",
|
||||
"name": "conda-env-py37_tensorflow-py"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.9"
|
||||
},
|
||||
"nteract": {
|
||||
"version": "nteract-front-end@1.0.0"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "81351e61f619b432ff51010a4f993194",
|
||||
"translation_date": "2025-08-31T13:58:00+00:00",
|
||||
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,414 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Generatyviniai tinklai\n",
|
||||
"\n",
|
||||
"Pasikartojantys neuroniniai tinklai (RNN) ir jų užtvarų ląstelių variantai, tokie kaip ilgos trumpos atminties ląstelės (LSTM) ir užtvarų pasikartojančios vienetai (GRU), suteikė mechanizmą kalbos modeliavimui, t. y. jie gali išmokti žodžių tvarką ir pateikti prognozes apie kitą žodį sekoje. Tai leidžia mums naudoti RNN **generatyvinėms užduotims**, tokioms kaip įprastas teksto generavimas, mašininis vertimas ir net vaizdų aprašymas.\n",
|
||||
"\n",
|
||||
"RNN architektūroje, kurią aptarėme ankstesniame skyriuje, kiekvienas RNN vienetas generavo kitą paslėptą būseną kaip išvestį. Tačiau mes taip pat galime pridėti kitą išvestį prie kiekvieno pasikartojančio vieneto, kuris leistų mums generuoti **seką** (kuri yra tokio pat ilgio kaip pradinė seka). Be to, galime naudoti RNN vienetus, kurie kiekviename žingsnyje nepriima įvesties, o tiesiog naudoja pradinį būsenos vektorių ir tada generuoja išvesties seką.\n",
|
||||
"\n",
|
||||
"Šiame užrašų knygelėje mes sutelksime dėmesį į paprastus generatyvinius modelius, kurie padeda mums generuoti tekstą. Paprastumo dėlei sukurkime **simbolių lygmens tinklą**, kuris generuoja tekstą raidė po raidės. Mokymo metu mums reikia paimti tam tikrą teksto korpusą ir padalyti jį į raidžių sekas.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Loading dataset...\n",
|
||||
"Building vocab...\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchtext\n",
|
||||
"import numpy as np\n",
|
||||
"from torchnlp import *\n",
|
||||
"train_dataset,test_dataset,classes,vocab = load_dataset()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Charakterių žodyno kūrimas\n",
|
||||
"\n",
|
||||
"Norint sukurti generatyvinį tinklą, veikiantį simbolių lygiu, tekstą reikia suskaidyti į atskirus simbolius, o ne žodžius. Tai galima padaryti apibrėžiant kitokį žodyną:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Vocabulary size = 82\n",
|
||||
"Encoding of 'a' is 1\n",
|
||||
"Character with code 13 is c\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def char_tokenizer(words):\n",
|
||||
" return list(words) #[word for word in words]\n",
|
||||
"\n",
|
||||
"counter = collections.Counter()\n",
|
||||
"for (label, line) in train_dataset:\n",
|
||||
" counter.update(char_tokenizer(line))\n",
|
||||
"vocab = torchtext.vocab.vocab(counter)\n",
|
||||
"\n",
|
||||
"vocab_size = len(vocab)\n",
|
||||
"print(f\"Vocabulary size = {vocab_size}\")\n",
|
||||
"print(f\"Encoding of 'a' is {vocab.get_stoi()['a']}\")\n",
|
||||
"print(f\"Character with code 13 is {vocab.get_itos()[13]}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pažiūrėkime pavyzdį, kaip galime užkoduoti tekstą iš mūsų duomenų rinkinio:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([ 0, 1, 2, 2, 3, 4, 5, 6, 3, 7, 8, 1, 9, 10, 3, 11, 2, 1,\n",
|
||||
" 12, 3, 7, 1, 13, 14, 3, 15, 16, 5, 17, 3, 5, 18, 8, 3, 7, 2,\n",
|
||||
" 1, 13, 14, 3, 19, 20, 8, 21, 5, 8, 9, 10, 22, 3, 20, 8, 21, 5,\n",
|
||||
" 8, 9, 10, 3, 23, 3, 4, 18, 17, 9, 5, 23, 10, 8, 2, 2, 8, 9,\n",
|
||||
" 10, 24, 3, 0, 1, 2, 2, 3, 4, 5, 9, 8, 8, 5, 25, 10, 3, 26,\n",
|
||||
" 12, 27, 16, 26, 2, 27, 16, 28, 29, 30, 1, 16, 26, 3, 17, 31, 3, 21,\n",
|
||||
" 2, 5, 9, 1, 23, 13, 32, 16, 27, 13, 10, 24, 3, 1, 9, 8, 3, 10,\n",
|
||||
" 8, 8, 27, 16, 28, 3, 28, 9, 8, 8, 16, 3, 1, 28, 1, 27, 16, 6])"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def enc(x):\n",
|
||||
" return torch.LongTensor(encode(x,voc=vocab,tokenizer=char_tokenizer))\n",
|
||||
"\n",
|
||||
"enc(train_dataset[0][1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generatyvios RNN mokymas\n",
|
||||
"\n",
|
||||
"RNN mokysime generuoti tekstą tokiu būdu. Kiekviename žingsnyje imsime simbolių seką, kurios ilgis yra `nchars`, ir paprašysime tinklo sugeneruoti kitą išvesties simbolį kiekvienam įvesties simboliui:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Priklausomai nuo konkretaus scenarijaus, galime norėti įtraukti specialius simbolius, tokius kaip *sekos pabaiga* `<eos>`. Mūsų atveju, mes tiesiog norime išmokyti tinklą generuoti begalinį tekstą, todėl kiekvienos sekos dydį nustatysime kaip `nchars` simbolių. Taigi, kiekvienas mokymo pavyzdys susidarys iš `nchars` įvesties ir `nchars` išvesties (kurios yra įvesties seka, paslinkta vienu simboliu į kairę). Minipartija susidarys iš kelių tokių sekų.\n",
|
||||
"\n",
|
||||
"Minipartijas generuosime taip: imsime kiekvieną naujienų tekstą, kurio ilgis yra `l`, ir iš jo sukursime visas galimas įvesties-išvesties kombinacijas (jų bus `l-nchars`). Jos sudarys vieną minipartiją, o minipartijų dydis kiekviename mokymo žingsnyje bus skirtingas.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(tensor([[ 0, 1, 2, ..., 28, 29, 30],\n",
|
||||
" [ 1, 2, 2, ..., 29, 30, 1],\n",
|
||||
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
||||
" ...,\n",
|
||||
" [20, 8, 21, ..., 1, 28, 1],\n",
|
||||
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
||||
" [21, 5, 8, ..., 1, 27, 16]]),\n",
|
||||
" tensor([[ 1, 2, 2, ..., 29, 30, 1],\n",
|
||||
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
||||
" [ 2, 3, 4, ..., 1, 16, 26],\n",
|
||||
" ...,\n",
|
||||
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
||||
" [21, 5, 8, ..., 1, 27, 16],\n",
|
||||
" [ 5, 8, 9, ..., 27, 16, 6]]))"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"nchars = 100\n",
|
||||
"\n",
|
||||
"def get_batch(s,nchars=nchars):\n",
|
||||
" ins = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
||||
" outs = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
||||
" for i in range(len(s)-nchars):\n",
|
||||
" ins[i] = enc(s[i:i+nchars])\n",
|
||||
" outs[i] = enc(s[i+1:i+nchars+1])\n",
|
||||
" return ins,outs\n",
|
||||
"\n",
|
||||
"get_batch(train_dataset[0][1])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar apibrėžkime generatoriaus tinklą. Jis gali būti pagrįstas bet kuria pasikartojančia ląstele, kurią aptarėme ankstesniame skyriuje (paprasta, LSTM arba GRU). Mūsų pavyzdyje naudosime LSTM.\n",
|
||||
"\n",
|
||||
"Kadangi tinklas kaip įvestį naudoja simbolius, o žodyno dydis yra gana mažas, mums nereikia įterpimo sluoksnio – vieno karšto kodavimo įvestis gali tiesiogiai pereiti į LSTM ląstelę. Tačiau, kadangi kaip įvestį perduodame simbolių numerius, prieš perduodant juos į LSTM, turime juos užkoduoti vieno karšto kodavimu. Tai atliekama kviečiant funkciją `one_hot` vykdymo metu (`forward` pass). Išvesties koduotojas bus linijinis sluoksnis, kuris paslėptą būseną pavers vieno karšto kodavimo išvestimi.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class LSTMGenerator(torch.nn.Module):\n",
|
||||
" def __init__(self, vocab_size, hidden_dim):\n",
|
||||
" super().__init__()\n",
|
||||
" self.rnn = torch.nn.LSTM(vocab_size,hidden_dim,batch_first=True)\n",
|
||||
" self.fc = torch.nn.Linear(hidden_dim, vocab_size)\n",
|
||||
"\n",
|
||||
" def forward(self, x, s=None):\n",
|
||||
" x = torch.nn.functional.one_hot(x,vocab_size).to(torch.float32)\n",
|
||||
" x,s = self.rnn(x,s)\n",
|
||||
" return self.fc(x),s"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Mokymosi metu norime turėti galimybę generuoti tekstą. Tam apibrėšime funkciją `generate`, kuri sukurs išvesties eilutę, kurios ilgis yra `size`, pradedant nuo pradinės eilutės `start`.\n",
|
||||
"\n",
|
||||
"Štai kaip tai veikia. Pirmiausia, visą pradinę eilutę perduosime per tinklą, gausime išvesties būseną `s` ir kitą numatomą simbolį `out`. Kadangi `out` yra vieno karšto kodavimo (one-hot encoded) formatu, naudojame `argmax`, kad gautume simbolio `nc` indeksą žodyne, o tada naudojame `itos`, kad nustatytume tikrąjį simbolį ir pridėtume jį prie rezultatų simbolių sąrašo `chars`. Šis simbolio generavimo procesas kartojamas `size` kartų, kad būtų sugeneruotas reikiamas simbolių skaičius.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def generate(net,size=100,start='today '):\n",
|
||||
" chars = list(start)\n",
|
||||
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
||||
" for i in range(size):\n",
|
||||
" nc = torch.argmax(out[0][-1])\n",
|
||||
" chars.append(vocab.get_itos()[nc])\n",
|
||||
" out, s = net(nc.view(1,-1),s)\n",
|
||||
" return ''.join(chars)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar pradėkime mokymą! Mokymo ciklas beveik toks pat kaip ir visuose ankstesniuose pavyzdžiuose, tačiau vietoj tikslumo kas 1000 epochų spausdiname sugeneruotą tekstą.\n",
|
||||
"\n",
|
||||
"Ypatingą dėmesį reikia skirti tam, kaip apskaičiuojame nuostolį. Turime apskaičiuoti nuostolį, turėdami vieno karšto kodavimo išvestį `out` ir tikėtiną tekstą `text_out`, kuris yra simbolių indeksų sąrašas. Laimei, `cross_entropy` funkcija tikisi neapdorotos tinklo išvesties kaip pirmo argumento ir klasės numerio kaip antro, kas būtent ir atitinka mūsų situaciją. Ji taip pat automatiškai atlieka vidurkinimą pagal mini partijos dydį.\n",
|
||||
"\n",
|
||||
"Taip pat apribojame mokymą iki `samples_to_train` pavyzdžių, kad nereikėtų per ilgai laukti. Skatiname jus eksperimentuoti ir bandyti ilgesnį mokymą, galbūt kelias epochas (tokiu atveju reikėtų sukurti dar vieną ciklą aplink šį kodą).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Current loss = 4.398899078369141\n",
|
||||
"today sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr s\n",
|
||||
"Current loss = 2.161320447921753\n",
|
||||
"today and to the tor to to the tor to to the tor to to the tor to to the tor to to the tor to to the tor t\n",
|
||||
"Current loss = 1.6722588539123535\n",
|
||||
"today and the court to the could to the could to the could to the could to the could to the could to the c\n",
|
||||
"Current loss = 2.423795223236084\n",
|
||||
"today and a second to the conternation of the conternation of the conternation of the conternation of the \n",
|
||||
"Current loss = 1.702607274055481\n",
|
||||
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
||||
"Current loss = 1.692358136177063\n",
|
||||
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
||||
"Current loss = 1.9722288846969604\n",
|
||||
"today and the control the control the control the control the control the control the control the control \n",
|
||||
"Current loss = 1.8705692291259766\n",
|
||||
"today and the second to the second to the second to the second to the second to the second to the second t\n",
|
||||
"Current loss = 1.7626899480819702\n",
|
||||
"today and a security and a security and a security and a security and a security and a security and a secu\n",
|
||||
"Current loss = 1.5574463605880737\n",
|
||||
"today and the company and the company and the company and the company and the company and the company and \n",
|
||||
"Current loss = 1.5620026588439941\n",
|
||||
"today and the be that the be the be that the be the be that the be the be that the be the be that the be t\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"net = LSTMGenerator(vocab_size,64).to(device)\n",
|
||||
"\n",
|
||||
"samples_to_train = 10000\n",
|
||||
"optimizer = torch.optim.Adam(net.parameters(),0.01)\n",
|
||||
"loss_fn = torch.nn.CrossEntropyLoss()\n",
|
||||
"net.train()\n",
|
||||
"for i,x in enumerate(train_dataset):\n",
|
||||
" # x[0] is class label, x[1] is text\n",
|
||||
" if len(x[1])-nchars<10:\n",
|
||||
" continue\n",
|
||||
" samples_to_train-=1\n",
|
||||
" if not samples_to_train: break\n",
|
||||
" text_in, text_out = get_batch(x[1])\n",
|
||||
" optimizer.zero_grad()\n",
|
||||
" out,s = net(text_in)\n",
|
||||
" loss = torch.nn.functional.cross_entropy(out.view(-1,vocab_size),text_out.flatten()) #cross_entropy(out,labels)\n",
|
||||
" loss.backward()\n",
|
||||
" optimizer.step()\n",
|
||||
" if i%1000==0:\n",
|
||||
" print(f\"Current loss = {loss.item()}\")\n",
|
||||
" print(generate(net))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Šis pavyzdys jau generuoja gana gerą tekstą, tačiau jį galima dar labiau patobulinti keliais būdais:\n",
|
||||
"\n",
|
||||
"* **Geresnė minibatch generacija**. Duomenų paruošimas mokymui buvo atliekamas generuojant vieną minibatch iš vieno pavyzdžio. Tai nėra idealu, nes minibatch dydžiai yra skirtingi, o kai kurie jų net negali būti sugeneruoti, nes tekstas yra trumpesnis nei `nchars`. Be to, maži minibatch nepakankamai apkrauna GPU. Protingiau būtų paimti didelį teksto fragmentą iš visų pavyzdžių, tada sugeneruoti visas įvesties-išvesties poras, jas sumaišyti ir sukurti vienodo dydžio minibatch.\n",
|
||||
"\n",
|
||||
"* **Daugiasluoksnis LSTM**. Verta išbandyti 2 ar 3 LSTM ląstelių sluoksnius. Kaip minėjome ankstesniame skyriuje, kiekvienas LSTM sluoksnis iš tekstų išskiria tam tikrus modelius, o simbolių lygio generatoriaus atveju galima tikėtis, kad žemesnis LSTM lygis bus atsakingas už skiemenų išskyrimą, o aukštesni lygiai - už žodžius ir jų kombinacijas. Tai galima paprastai įgyvendinti perduodant sluoksnių skaičiaus parametrą LSTM konstruktoriui.\n",
|
||||
"\n",
|
||||
"* Taip pat galite eksperimentuoti su **GRU vienetais** ir patikrinti, kurie veikia geriau, bei su **skirtingais paslėpto sluoksnio dydžiais**. Per didelis paslėpto sluoksnio dydis gali sukelti per didelį pritaikymą (pvz., tinklas išmoks tikslų tekstą), o per mažas dydis gali neduoti gero rezultato.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Minkštas teksto generavimas ir temperatūra\n",
|
||||
"\n",
|
||||
"Ankstesnėje `generate` funkcijos apibrėžtyje mes visada pasirinkdavome simbolį su didžiausia tikimybe kaip kitą simbolį generuojamame tekste. Tai dažnai lėmė, kad tekstas \"kartodavosi\" tarp tų pačių simbolių sekų vėl ir vėl, kaip šiame pavyzdyje:\n",
|
||||
"```\n",
|
||||
"today of the second the company and a second the company ...\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Tačiau, jei pažvelgsime į tikimybių pasiskirstymą kitam simboliui, gali būti, kad skirtumas tarp kelių didžiausių tikimybių nėra didelis, pvz., vienas simbolis gali turėti tikimybę 0.2, o kitas - 0.19 ir pan. Pavyzdžiui, ieškant kito simbolio sekoje '*play*', kitas simbolis gali būti tiek tarpas, tiek **e** (kaip žodyje *player*).\n",
|
||||
"\n",
|
||||
"Tai leidžia daryti išvadą, kad ne visada yra \"teisinga\" pasirinkti simbolį su didžiausia tikimybe, nes pasirinkus antrą pagal dydį tikimybę vis tiek galime gauti prasmingą tekstą. Protingiau yra **imti mėginius** iš tikimybių pasiskirstymo, kurį pateikia tinklo išvestis.\n",
|
||||
"\n",
|
||||
"Šis mėginių ėmimas gali būti atliekamas naudojant `multinomial` funkciją, kuri įgyvendina vadinamąjį **multinominį pasiskirstymą**. Funkcija, kuri įgyvendina šį **minkštą** teksto generavimą, apibrėžta žemiau:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"--- Temperature = 0.3\n",
|
||||
"Today and a company and complete an all the land the restrational the as a security and has provers the pay to and a report and the computer in the stand has filities and working the law the stations for a company and with the company and the final the first company and refight of the state and and workin\n",
|
||||
"\n",
|
||||
"--- Temperature = 0.8\n",
|
||||
"Today he oniis its first to Aus bomblaties the marmation a to manan boogot that pirate assaid a relaid their that goverfin the the Cappets Ecrotional Assonia Cition targets it annight the w scyments Blamity #39;s TVeer Diercheg Reserals fran envyuil that of ster said access what succers of Dour-provelith\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.0\n",
|
||||
"Today holy they a 11 will meda a toket subsuaties, engins for Chanos, they's has stainger past to opening orital his thempting new Nattona was al innerforder advan-than #36;s night year his religuled talitatian what the but with Wednesday to Justment will wemen of Mark CCC Camp as Timed Nae wome a leaders\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.3\n",
|
||||
"Today gpone 2.5 fech atcusion poor cocles toparsdorM.cht Line Pamage put 43 his calt lowed to the book, that has authh-the silia rruch ailing to'ory andhes beutirsimi- Aefffive heading offil an auf eacklets is charged evis, Gunymy oy) Mony has it after-sloythyor loveId out filme, the Natabl -Najuntaxiggs \n",
|
||||
"\n",
|
||||
"--- Temperature = 1.8\n",
|
||||
"Today plary, P.slan chly\\401 mardregationly #39;t 8.1Mide) closes ,filtcon alfly playin roven!\\grea.-QFBEP: Iss onfarchQ/itilia CCf Zivesigntwasta orce.-Peul-aw.uicrin of fuglinfsut aftaningwo, MIEX awayew Aice Woiduar Corvagiugge oppo esig ThusBratourid canthly-RyI.co lagitems\\eexciaishes.conBabntusmor I\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate_soft(net,size=100,start='today ',temperature=1.0):\n",
|
||||
" chars = list(start)\n",
|
||||
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
||||
" for i in range(size):\n",
|
||||
" #nc = torch.argmax(out[0][-1])\n",
|
||||
" out_dist = out[0][-1].div(temperature).exp()\n",
|
||||
" nc = torch.multinomial(out_dist,1)[0]\n",
|
||||
" chars.append(vocab.get_itos()[nc])\n",
|
||||
" out, s = net(nc.view(1,-1),s)\n",
|
||||
" return ''.join(chars)\n",
|
||||
" \n",
|
||||
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
||||
" print(f\"--- Temperature = {i}\\n{generate_soft(net,size=300,start='Today ',temperature=i)}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Mes pristatėme dar vieną parametrą, vadinamą **temperatūra**, kuris naudojamas nurodyti, kaip stipriai turėtume laikytis didžiausios tikimybės. Jei temperatūra yra 1.0, atliekame sąžiningą multinominį mėginių ėmimą, o kai temperatūra pasiekia begalybę - visos tikimybės tampa lygios, ir mes atsitiktinai pasirenkame kitą simbolį. Žemiau pateiktame pavyzdyje galime pastebėti, kad tekstas tampa beprasmiškas, kai temperatūra per daug padidėja, ir primena „ciklinį“ sunkiai generuojamą tekstą, kai ji artėja prie 0.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"interpreter": {
|
||||
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3.8.12 ('py38')",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.8.12"
|
||||
},
|
||||
"coopTranslator": {
|
||||
"original_hash": "7673cd150d96c74c6d6011460094efb4",
|
||||
"translation_date": "2025-08-31T13:44:35+00:00",
|
||||
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Generatyviniai tinklai\n",
|
||||
"\n",
|
||||
"Pasikartojantys neuroniniai tinklai (RNN) ir jų užtvarų ląstelių variantai, tokie kaip ilgos trumpalaikės atminties ląstelės (LSTM) ir užtvarų pasikartojantys vienetai (GRU), suteikė mechanizmą kalbos modeliavimui, t. y. jie gali išmokti žodžių tvarką ir pateikti prognozes apie kitą žodį sekoje. Tai leidžia mums naudoti RNN **generatyvinėms užduotims**, tokioms kaip įprastas teksto generavimas, mašininis vertimas ir net vaizdų aprašymas.\n",
|
||||
"\n",
|
||||
"RNN architektūroje, kurią aptarėme ankstesniame skyriuje, kiekvienas RNN vienetas generavo kitą paslėptą būseną kaip išvestį. Tačiau mes taip pat galime pridėti kitą išvestį prie kiekvieno pasikartojančio vieneto, kuris leistų mums generuoti **seką** (kuri yra tokio pat ilgio kaip pradinė seka). Be to, galime naudoti RNN vienetus, kurie kiekviename žingsnyje nepriima įvesties, o tiesiog naudoja pradinį būsenos vektorių ir tada generuoja išvesties seką.\n",
|
||||
"\n",
|
||||
"Šiame užrašų knygelėje mes sutelksime dėmesį į paprastus generatyvinius modelius, kurie padeda mums generuoti tekstą. Paprastumo dėlei sukurkime **simbolių lygmens tinklą**, kuris generuoja tekstą raidė po raidės. Mokymo metu mums reikia paimti tam tikrą teksto korpusą ir padalyti jį į raidžių sekas.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import tensorflow as tf\n",
|
||||
"from tensorflow import keras\n",
|
||||
"import tensorflow_datasets as tfds\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Charakterių žodyno kūrimas\n",
|
||||
"\n",
|
||||
"Norint sukurti generatyvinį tinklą simbolių lygiu, tekstą reikia suskaidyti į atskirus simbolius, o ne žodžius. `TextVectorization` sluoksnis, kurį naudojome anksčiau, to padaryti negali, todėl turime dvi galimybes:\n",
|
||||
"\n",
|
||||
"* Rankiniu būdu įkelti tekstą ir atlikti tokenizaciją „rankomis“, kaip parodyta [šiame oficialiame Keras pavyzdyje](https://keras.io/examples/generative/lstm_character_level_text_generation/)\n",
|
||||
"* Naudoti `Tokenizer` klasę simbolių lygio tokenizacijai.\n",
|
||||
"\n",
|
||||
"Mes pasirinkome antrąjį variantą. `Tokenizer` taip pat gali būti naudojamas žodžių tokenizacijai, todėl turėtų būti gana paprasta pereiti nuo simbolių lygio prie žodžių lygio tokenizacijos.\n",
|
||||
"\n",
|
||||
"Norint atlikti simbolių lygio tokenizaciją, reikia perduoti parametrą `char_level=True`:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def extract_text(x):\n",
|
||||
" return x['title']+' '+x['description']\n",
|
||||
"\n",
|
||||
"def tupelize(x):\n",
|
||||
" return (extract_text(x),x['label'])\n",
|
||||
"\n",
|
||||
"tokenizer = keras.preprocessing.text.Tokenizer(char_level=True,lower=False)\n",
|
||||
"tokenizer.fit_on_texts([x['title'].numpy().decode('utf-8') for x in ds_train])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Mes taip pat norime naudoti vieną specialų žymeklį, kuris žymėtų **sekos pabaigą**, kurį pavadinsime `<eos>`. Pridėkime jį rankiniu būdu į žodyną:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"eos_token = len(tokenizer.word_index)+1\n",
|
||||
"tokenizer.word_index['<eos>'] = eos_token\n",
|
||||
"\n",
|
||||
"vocab_size = eos_token + 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[[48, 2, 10, 10, 5, 44, 1, 25, 5, 8, 10, 13, 78]]"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tokenizer.texts_to_sequences(['Hello, world!'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generatyvios RNN mokymas kurti pavadinimus\n",
|
||||
"\n",
|
||||
"Štai kaip mes mokysime RNN generuoti naujienų pavadinimus. Kiekviename žingsnyje imsime vieną pavadinimą, kurį pateiksime RNN, ir kiekvienam įvesties simboliui prašysime tinklo sugeneruoti kitą išvesties simbolį:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Paskutiniam mūsų sekos simboliui prašysime tinklo sugeneruoti `<eos>` žymeklį.\n",
|
||||
"\n",
|
||||
"Pagrindinis skirtumas tarp generatyvios RNN, kurią naudojame čia, yra tas, kad imsime išvestį iš kiekvieno RNN žingsnio, o ne tik iš paskutinės ląstelės. Tai galima pasiekti nurodant `return_sequences` parametrą RNN ląstelei.\n",
|
||||
"\n",
|
||||
"Taigi, mokymo metu tinklo įvestis bus tam tikro ilgio užkoduotų simbolių seka, o išvestis bus tokio pat ilgio seka, bet paslinkta vienu elementu ir baigiama `<eos>`. Minipartija sudarys kelias tokias sekas, ir mums reikės naudoti **užpildymą**, kad suderintume visas sekas.\n",
|
||||
"\n",
|
||||
"Sukurkime funkcijas, kurios transformuos mums duomenų rinkinį. Kadangi norime užpildyti sekas minipartijos lygiu, pirmiausia sugrupuosime duomenų rinkinį naudodami `.batch()`, o tada naudosime `map`, kad atliktume transformaciją. Taigi, transformacijos funkcija priims visą minipartiją kaip parametrą:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def title_batch(x):\n",
|
||||
" x = [t.numpy().decode('utf-8') for t in x]\n",
|
||||
" z = tokenizer.texts_to_sequences(x)\n",
|
||||
" z = tf.keras.preprocessing.sequence.pad_sequences(z)\n",
|
||||
" return tf.one_hot(z,vocab_size), tf.one_hot(tf.concat([z[:,1:],tf.constant(eos_token,shape=(len(z),1))],axis=1),vocab_size)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Keletas svarbių dalykų, kuriuos čia atliekame:\n",
|
||||
"* Pirmiausia išgauname tikrąjį tekstą iš string tipo tensoriaus\n",
|
||||
"* `text_to_sequences` konvertuoja tekstų sąrašą į sveikųjų skaičių tensorių sąrašą\n",
|
||||
"* `pad_sequences` užpildo tuos tensorius iki jų maksimalaus ilgio\n",
|
||||
"* Galiausiai atliekame vieno karšto kodavimo (one-hot encoding) procesą visiems simboliams, taip pat atliekame poslinkį ir `<eos>` pridėjimą. Netrukus paaiškinsime, kodėl mums reikia vieno karšto koduotų simbolių\n",
|
||||
"\n",
|
||||
"Tačiau ši funkcija yra **Pythonic**, t. y. jos negalima automatiškai paversti Tensorflow skaičiavimo grafu. Jei bandysime naudoti šią funkciją tiesiogiai `Dataset.map` funkcijoje, gausime klaidų. Turime šį Pythonic kvietimą apgaubti naudojant `py_function` apvalkalą:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def title_batch_fn(x):\n",
|
||||
" x = x['title']\n",
|
||||
" a,b = tf.py_function(title_batch,inp=[x],Tout=(tf.float32,tf.float32))\n",
|
||||
" return a,b"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"> **Pastaba**: Skirtumas tarp Pythonic ir Tensorflow transformacijos funkcijų gali atrodyti pernelyg sudėtingas, ir galbūt kyla klausimas, kodėl mes nenaudojame standartinių Python funkcijų duomenų rinkiniui transformuoti prieš perduodant jį į `fit`. Nors tai tikrai galima padaryti, naudojant `Dataset.map` yra didelis privalumas, nes duomenų transformacijos procesas vykdomas naudojant Tensorflow skaičiavimo grafiką, kuris išnaudoja GPU skaičiavimo galimybes ir sumažina poreikį perduoti duomenis tarp CPU/GPU.\n",
|
||||
"\n",
|
||||
"Dabar galime sukurti savo generatoriaus tinklą ir pradėti mokymą. Jis gali būti pagrįstas bet kuria pasikartojančia ląstele, kurią aptarėme ankstesniame skyriuje (paprasta, LSTM arba GRU). Mūsų pavyzdyje naudosime LSTM.\n",
|
||||
"\n",
|
||||
"Kadangi tinklas kaip įvestį naudoja simbolius, o žodyno dydis yra gana mažas, mums nereikia įterpimo sluoksnio – vieno karšto kodavimo (one-hot-encoded) įvestis gali tiesiogiai patekti į LSTM ląstelę. Išvesties sluoksnis būtų `Dense` klasifikatorius, kuris konvertuos LSTM išvestį į vieno karšto kodavimo simbolių numerius.\n",
|
||||
"\n",
|
||||
"Be to, kadangi dirbame su kintamo ilgio sekų duomenimis, galime naudoti `Masking` sluoksnį, kad sukurtume kaukę, kuri ignoruos užpildytą (padded) eilutės dalį. Tai nėra griežtai būtina, nes mums nėra labai svarbu viskas, kas yra už `<eos>` žymos, tačiau naudosime šį sluoksnį, kad įgytume patirties su šio tipo sluoksniais. `input_shape` bus `(None, vocab_size)`, kur `None` nurodo kintamo ilgio seką, o išvesties forma taip pat yra `(None, vocab_size)`, kaip matote iš `summary`:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
"masking (Masking) (None, None, 84) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"lstm (LSTM) (None, None, 128) 109056 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense (Dense) (None, None, 84) 10836 \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 119,892\n",
|
||||
"Trainable params: 119,892\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n",
|
||||
"15000/15000 [==============================] - 229s 15ms/step - loss: 1.5385\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7fa40c1245e0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.Masking(input_shape=(None,vocab_size)),\n",
|
||||
" keras.layers.LSTM(128,return_sequences=True),\n",
|
||||
" keras.layers.Dense(vocab_size,activation='softmax')\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.summary()\n",
|
||||
"model.compile(loss='categorical_crossentropy')\n",
|
||||
"\n",
|
||||
"model.fit(ds_train.batch(8).map(title_batch_fn))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generuojant rezultatą\n",
|
||||
"\n",
|
||||
"Dabar, kai modelis yra apmokytas, norime jį panaudoti rezultatui generuoti. Visų pirma, mums reikia būdo dekoduoti tekstą, kuris yra pateiktas kaip skaičių sekos. Tam galėtume naudoti funkciją `tokenizer.sequences_to_texts`; tačiau ji nėra labai efektyvi, kai naudojama simbolių lygmens tokenizacija. Todėl mes paimsime tokenų žodyną iš tokenizer (vadinamą `word_index`), sukursime atvirkštinį žemėlapį ir parašysime savo dekodavimo funkciją:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"reverse_map = {val:key for key, val in tokenizer.word_index.items()}\n",
|
||||
"\n",
|
||||
"def decode(x):\n",
|
||||
" return ''.join([reverse_map[t] for t in x])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Dabar pradėsime generavimą. Pirmiausia turime tam tikrą eilutę `start`, kurią užkoduojame į seką `inp`, o tada kiekviename žingsnyje kviesime savo tinklą, kad nustatytume kitą simbolį.\n",
|
||||
"\n",
|
||||
"Tinklo išvestis `out` yra vektorius su `vocab_size` elementų, kurie atspindi kiekvieno ženklo tikimybes. Naudodami `argmax` galime rasti labiausiai tikėtiną ženklo numerį. Tada šį simbolį pridedame prie sugeneruoto ženklų sąrašo ir tęsiame generavimą. Šis procesas, kai sugeneruojamas vienas simbolis, kartojamas `size` kartų, kad būtų sugeneruotas reikiamas simbolių skaičius, o generavimą baigiame anksčiau, jei pasiekiamas `eos_token`.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'Today #39;s lead to strike for the strike for the strike for the strike (AFP)'"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate(model,size=100,start='Today '):\n",
|
||||
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
||||
" chars = inp\n",
|
||||
" for i in range(size):\n",
|
||||
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
||||
" nc = tf.argmax(out)\n",
|
||||
" if nc==eos_token:\n",
|
||||
" break\n",
|
||||
" chars.append(nc.numpy())\n",
|
||||
" inp = inp+[nc]\n",
|
||||
" return decode(chars)\n",
|
||||
" \n",
|
||||
"generate(model)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Pavyzdžių generavimas treniruotės metu\n",
|
||||
"\n",
|
||||
"Kadangi neturime jokių naudingų metrikų, tokių kaip *tikslumas*, vienintelis būdas pamatyti, ar mūsų modelis tobulėja, yra **generuojamų eilučių pavyzdžių peržiūra** treniruotės metu. Tam naudosime **atšaukimus** (callbacks), t. y. funkcijas, kurias galime perduoti `fit` funkcijai ir kurios bus periodiškai iškviečiamos treniruotės metu.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Epoch 1/3\n",
|
||||
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.2703\n",
|
||||
"Today #39;s a lead in the company for the strike\n",
|
||||
"Epoch 2/3\n",
|
||||
"15000/15000 [==============================] - 227s 15ms/step - loss: 1.2057\n",
|
||||
"Today #39;s the Market Service on Security Start (AP)\n",
|
||||
"Epoch 3/3\n",
|
||||
"15000/15000 [==============================] - 226s 15ms/step - loss: 1.1752\n",
|
||||
"Today #39;s a line on the strike to start for the start\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7fa40c74e3d0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"sampling_callback = keras.callbacks.LambdaCallback(\n",
|
||||
" on_epoch_end = lambda batch, logs: print(generate(model))\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model.fit(ds_train.batch(8).map(title_batch_fn),callbacks=[sampling_callback],epochs=3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Šis pavyzdys jau generuoja gana gerą tekstą, tačiau jį galima dar labiau patobulinti keliais būdais:\n",
|
||||
"\n",
|
||||
"* **Daugiau teksto**. Mes naudojome tik antraštes savo užduočiai, tačiau galite eksperimentuoti su pilnu tekstu. Atminkite, kad RNN nėra labai gerai pritaikyti ilgiems sekų apdorojimams, todėl verta jas suskaidyti į trumpesnes sakinių dalis arba visada treniruoti fiksuoto sekos ilgio, pvz., `num_chars` (pavyzdžiui, 256). Galite pabandyti pakeisti aukščiau pateiktą pavyzdį į tokią architektūrą, naudodami [oficialų Keras vadovą](https://keras.io/examples/generative/lstm_character_level_text_generation/) kaip įkvėpimą.\n",
|
||||
"\n",
|
||||
"* **Daugiasluoksnis LSTM**. Verta išbandyti 2 ar 3 LSTM ląstelių sluoksnius. Kaip minėjome ankstesniame skyriuje, kiekvienas LSTM sluoksnis iš tekstų išskiria tam tikrus raštus, o simbolių lygio generatoriaus atveju galime tikėtis, kad žemesnis LSTM lygis bus atsakingas už skiemenų išskyrimą, o aukštesni lygiai - už žodžius ir jų derinius. Tai galima paprastai įgyvendinti perduodant sluoksnių skaičiaus parametrą LSTM konstruktoriui.\n",
|
||||
"\n",
|
||||
"* Taip pat galite eksperimentuoti su **GRU vienetais** ir pažiūrėti, kurie veikia geriau, bei su **skirtingais paslėptų sluoksnių dydžiais**. Per didelis paslėptas sluoksnis gali sukelti per didelį pritaikymą (pvz., tinklas išmoks tikslų tekstą), o mažesnis dydis gali neduoti gero rezultato.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Minkštas teksto generavimas ir temperatūra\n",
|
||||
"\n",
|
||||
"Ankstesnėje `generate` apibrėžtyje mes visada rinkdavomės simbolį su didžiausia tikimybe kaip kitą simbolį generuojamame tekste. Tai lėmė, kad tekstas dažnai „kartodavosi“ tarp tų pačių simbolių sekų vėl ir vėl, kaip šiame pavyzdyje:\n",
|
||||
"```\n",
|
||||
"today of the second the company and a second the company ...\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Tačiau, jei pažvelgsime į tikimybių pasiskirstymą kitam simboliui, gali būti, kad skirtumas tarp kelių didžiausių tikimybių nėra didelis, pvz., vienas simbolis gali turėti tikimybę 0.2, kitas - 0.19 ir pan. Pavyzdžiui, ieškant kito simbolio sekai '*play*', kitas simbolis gali būti tiek tarpas, tiek **e** (kaip žodyje *player*).\n",
|
||||
"\n",
|
||||
"Tai leidžia daryti išvadą, kad ne visada „teisinga“ pasirinkti simbolį su didesne tikimybe, nes pasirinkus antrą pagal dydį tikimybę vis tiek galime gauti prasmingą tekstą. Protingiau yra **imti mėginius** iš tikimybių pasiskirstymo, kurį pateikia tinklo išvestis.\n",
|
||||
"\n",
|
||||
"Šis mėginių ėmimas gali būti atliekamas naudojant `np.multinomial` funkciją, kuri įgyvendina vadinamąjį **multinominį pasiskirstymą**. Funkcija, kuri įgyvendina šį **minkštą** teksto generavimą, apibrėžta žemiau:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 33,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"--- Temperature = 0.3\n",
|
||||
"Today #39;s strike #39; to start at the store return\n",
|
||||
"On Sunday PO to Be Data Profit Up (Reuters)\n",
|
||||
"Moscow, SP wins straight to the Microsoft #39;s control of the space start\n",
|
||||
"President olding of the blast start for the strike to pay <b>...</b>\n",
|
||||
"Little red riding hood ficed to the spam countered in European <b>...</b>\n",
|
||||
"\n",
|
||||
"--- Temperature = 0.8\n",
|
||||
"Today countie strikes ryder missile faces food market blut\n",
|
||||
"On Sunday collores lose-toppy of sale of Bullment in <b>...</b>\n",
|
||||
"Moscow, IBM Diffeiting in Afghan Software Hotels (Reuters)\n",
|
||||
"President Ol Luster for Profit Peaced Raised (AP)\n",
|
||||
"Little red riding hood dace on depart talks #39; bank up\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.0\n",
|
||||
"Today wits House buiting debate fixes #39; supervice stake again\n",
|
||||
"On Sunday arling digital poaching In for level\n",
|
||||
"Moscow, DS Up 7, Top Proble Protest Caprey Mamarian Strike\n",
|
||||
"President teps help of roubler stepted lessabul-Dhalitics (AFP)\n",
|
||||
"Little red riding hood signs on cash in Carter-youb\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.3\n",
|
||||
"Today wits flawer ro, pSIA figat's co DroftwavesIs Talo up\n",
|
||||
"On Sunday hround elitwing wint EU Powerburlinetien\n",
|
||||
"Moscow, Bazz #39;s sentries olymen winnelds' next for Olympite Huc?\n",
|
||||
"President lost securitys from power Elections in Smiltrials\n",
|
||||
"Little red riding hood vides profit, exponituity, profitmainalist-at said listers\n",
|
||||
"\n",
|
||||
"--- Temperature = 1.8\n",
|
||||
"Today #39;It: He deat: N.KA Asside\n",
|
||||
"On Sunday i arry Par aldeup patient Wo stele1\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ename": "KeyError",
|
||||
"evalue": "0",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)",
|
||||
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"\\n--- Temperature = {i}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenerate_soft\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0msize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m300\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mwords\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mtemperature\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||
"\u001b[0;32m<ipython-input-33-db32367a0feb>\u001b[0m in \u001b[0;36mgenerate_soft\u001b[0;34m(model, size, start, temperature)\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0mchars\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0minp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minp\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 13\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mchars\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 14\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0mwords\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m'Today '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'On Sunday '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Moscow, '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'President '\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m'Little red riding hood '\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36mdecode\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||
"\u001b[0;32m<ipython-input-10-3f5fa6130b1d>\u001b[0m in \u001b[0;36m<listcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mreverse_map\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
|
||||
"\u001b[0;31mKeyError\u001b[0m: 0"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def generate_soft(model,size=100,start='Today ',temperature=1.0):\n",
|
||||
" inp = tokenizer.texts_to_sequences([start])[0]\n",
|
||||
" chars = inp\n",
|
||||
" for i in range(size):\n",
|
||||
" out = model(tf.expand_dims(tf.one_hot(inp,vocab_size),0))[0][-1]\n",
|
||||
" probs = tf.exp(tf.math.log(out)/temperature).numpy().astype(np.float64)\n",
|
||||
" probs = probs/np.sum(probs)\n",
|
||||
" nc = np.argmax(np.random.multinomial(1,probs,1))\n",
|
||||
" if nc==eos_token:\n",
|
||||
" break\n",
|
||||
" chars.append(nc)\n",
|
||||
" inp = inp+[nc]\n",
|
||||
" return decode(chars)\n",
|
||||
"\n",
|
||||
"words = ['Today ','On Sunday ','Moscow, ','President ','Little red riding hood ']\n",
|
||||
" \n",
|
||||
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
||||
" print(f\"\\n--- Temperature = {i}\")\n",
|
||||
" for j in range(5):\n",
|
||||
" print(generate_soft(model,size=300,start=words[j],temperature=i))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Mes pristatėme dar vieną parametrą, vadinamą **temperatūra**, kuris naudojamas nurodyti, kaip stipriai turėtume laikytis didžiausios tikimybės. Jei temperatūra yra 1.0, atliekame sąžiningą multinominį mėginių ėmimą, o kai temperatūra pasiekia begalybę - visos tikimybės tampa lygios, ir mes atsitiktinai pasirenkame kitą simbolį. Žemiau pateiktame pavyzdyje galime pastebėti, kad tekstas tampa beprasmiškas, kai temperatūra per daug padidėja, ir primena „ciklinį“ sunkiai generuojamą tekstą, kai ji artėja prie 0.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\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-31T13:42:18+00:00",
|
||||
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativeTF.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Dėmesio mechanizmai ir transformatoriai\n",
|
||||
"\n",
|
||||
"Viena iš pagrindinių pasikartojančių tinklų trūkumų yra ta, kad visi sekos žodžiai turi vienodą įtaką rezultatui. Tai lemia neoptimalų veikimą naudojant standartinius LSTM koduotojo-dekoduotojo modelius sekų užduotims, tokioms kaip pavadintų objektų atpažinimas ar mašininis vertimas. Iš tikrųjų tam tikri žodžiai įvesties sekoje dažnai turi didesnę įtaką išvesties sekai nei kiti.\n",
|
||||
"\n",
|
||||
"Apsvarstykime sekos į seką modelį, pavyzdžiui, mašininį vertimą. Jis įgyvendinamas naudojant du pasikartojančius tinklus, kur vienas tinklas (**koduotojas**) suspaudžia įvesties seką į paslėptą būseną, o kitas tinklas (**dekoduotojas**) išskleidžia šią paslėptą būseną į išverstą rezultatą. Problema su šiuo metodu yra ta, kad tinklo galutinė būsena sunkiai prisimena sakinio pradžią, todėl modelis prastai veikia su ilgais sakiniais.\n",
|
||||
"\n",
|
||||
"**Dėmesio mechanizmai** suteikia galimybę įvertinti kiekvieno įvesties vektoriaus kontekstinę įtaką kiekvienai RNN išvesties prognozei. Tai įgyvendinama sukuriant trumpesnius ryšius tarp įvesties RNN tarpinių būsenų ir išvesties RNN. Tokiu būdu, generuojant išvesties simbolį $y_t$, atsižvelgiama į visas įvesties paslėptas būsenas $h_i$, su skirtingais svorio koeficientais $\\alpha_{t,i}$.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Koduotojo-dekoduotojo modelis su adityviniu dėmesio mechanizmu iš [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf), cituota iš [šio tinklaraščio įrašo](https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html)*\n",
|
||||
"\n",
|
||||
"Dėmesio matrica $\\{\\alpha_{i,j}\\}$ atspindi, kokiu mastu tam tikri įvesties žodžiai prisideda prie tam tikro žodžio generavimo išvesties sekoje. Žemiau pateiktas tokios matricos pavyzdys:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Paveikslas paimtas iš [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf) (3 pav.)*\n",
|
||||
"\n",
|
||||
"Dėmesio mechanizmai yra atsakingi už daugelį dabartinių ar beveik dabartinių pažangiausių rezultatų natūralios kalbos apdorojime. Tačiau dėmesio pridėjimas žymiai padidina modelio parametrų skaičių, o tai sukėlė mastelio problemas su RNN. Vienas iš pagrindinių RNN mastelio apribojimų yra tai, kad modelių pasikartojantis pobūdis apsunkina mokymo partijų kūrimą ir lygiagretinimą. RNN kiekvienas sekos elementas turi būti apdorojamas nuosekliai, todėl jo negalima lengvai lygiagrečiai apdoroti.\n",
|
||||
"\n",
|
||||
"Dėmesio mechanizmų pritaikymas kartu su šiuo apribojimu paskatino sukurti dabartinius pažangiausius transformatorių modelius, kuriuos šiandien naudojame, tokius kaip BERT ar OpenGPT3.\n",
|
||||
"\n",
|
||||
"## Transformatorių modeliai\n",
|
||||
"\n",
|
||||
"Užuot perdavę kiekvienos ankstesnės prognozės kontekstą į kitą vertinimo žingsnį, **transformatorių modeliai** naudoja **pozicinius kodavimus** ir dėmesį, kad užfiksuotų įvesties kontekstą tam tikrame teksto lange. Žemiau pateiktas vaizdas rodo, kaip poziciniai kodavimai su dėmesiu gali užfiksuoti kontekstą tam tikrame lange.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Kadangi kiekviena įvesties pozicija yra nepriklausomai susieta su kiekviena išvesties pozicija, transformatoriai gali geriau lygiagrečiai apdoroti nei RNN, o tai leidžia kurti daug didesnius ir išraiškingesnius kalbos modelius. Kiekviena dėmesio galvutė gali būti naudojama mokytis skirtingų žodžių tarpusavio ryšių, kurie pagerina natūralios kalbos apdorojimo užduotis.\n",
|
||||
"\n",
|
||||
"**BERT** (Bidirectional Encoder Representations from Transformers) yra labai didelis daugiasluoksnis transformatorių tinklas su 12 sluoksnių *BERT-base* versijoje ir 24 sluoksniais *BERT-large* versijoje. Modelis pirmiausia yra iš anksto apmokomas naudojant didelį tekstų korpusą (Vikipedija + knygos) taikant nesupervizuotą mokymąsi (prognozuojant užmaskuotus žodžius sakinyje). Išankstinio mokymo metu modelis įgyja reikšmingą kalbos supratimo lygį, kurį vėliau galima pritaikyti kitoms duomenų aibėms naudojant smulkųjį derinimą. Šis procesas vadinamas **perkėlimo mokymusi**.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Yra daug transformatorių architektūrų variantų, įskaitant BERT, DistilBERT, BigBird, OpenGPT3 ir daugiau, kuriuos galima smulkiai derinti. [HuggingFace paketas](https://github.com/huggingface/) suteikia saugyklą daugeliui šių architektūrų mokyti naudojant PyTorch.\n",
|
||||
"\n",
|
||||
"## BERT naudojimas teksto klasifikavimui\n",
|
||||
"\n",
|
||||
"Pažiūrėkime, kaip galime naudoti iš anksto apmokytą BERT modelį, kad išspręstume tradicinę užduotį: sekos klasifikavimą. Mes klasifikuosime savo pradinį AG News duomenų rinkinį.\n",
|
||||
"\n",
|
||||
"Pirmiausia įkelkime HuggingFace biblioteką ir mūsų duomenų rinkinį:\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": [
|
||||
"Kadangi naudosime iš anksto apmokytą BERT modelį, mums reikės naudoti specifinį tokenizatorių. Pirmiausia įkelsime tokenizatorių, susietą su iš anksto apmokytu BERT modeliu.\n",
|
||||
"\n",
|
||||
"HuggingFace biblioteka turi iš anksto apmokytų modelių saugyklą, kurią galite naudoti tiesiog nurodydami jų pavadinimus kaip argumentus `from_pretrained` funkcijoms. Visi reikalingi dvejetainiai modelio failai bus automatiškai atsisiųsti.\n",
|
||||
"\n",
|
||||
"Tačiau tam tikrais atvejais jums gali prireikti įkelti savo modelius. Tokiu atveju galite nurodyti katalogą, kuriame yra visi susiję failai, įskaitant tokenizatoriaus parametrus, `config.json` failą su modelio parametrais, dvejetainius svorius ir kt.\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": [
|
||||
"`tokenizer` objektas turi `encode` funkciją, kuri gali būti tiesiogiai naudojama tekstui užkoduoti:\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": [
|
||||
"Tada sukurkime iteratorius, kuriuos naudosime mokymo metu, kad pasiektume duomenis. Kadangi BERT naudoja savo kodavimo funkciją, mums reikės apibrėžti užpildymo funkciją, panašią į anksčiau apibrėžtą `padify`:\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": [
|
||||
"Mūsų atveju naudosime iš anksto apmokytą BERT modelį, vadinamą `bert-base-uncased`. Įkelkime modelį naudodami `BertForSequenceClassfication` paketą. Tai užtikrina, kad mūsų modelis jau turi reikiamą klasifikavimo architektūrą, įskaitant galutinį klasifikatorių. Pamatysite įspėjimo pranešimą, kad galutinio klasifikatoriaus svoriai nėra inicializuoti ir modelis reikalautų išankstinio apmokymo - tai visiškai normalu, nes būtent tai mes ir ketiname daryti!\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": [
|
||||
"Dabar esame pasiruošę pradėti mokymus! Kadangi BERT jau yra iš anksto apmokytas, norime pradėti nuo gana mažo mokymosi greičio, kad nepažeistume pradinių svorių.\n",
|
||||
"\n",
|
||||
"Visą sunkų darbą atlieka `BertForSequenceClassification` modelis. Kai iškviečiame modelį su mokymo duomenimis, jis grąžina tiek nuostolį, tiek tinklo išvestį už pateiktą minibatch įvestį. Nuostolį naudojame parametrų optimizavimui (`loss.backward()` atlieka atgalinį skaičiavimą), o `out` naudojame mokymo tikslumui apskaičiuoti, lygindami gautas etiketes `labs` (apskaičiuotas naudojant `argmax`) su laukiamomis `labels`.\n",
|
||||
"\n",
|
||||
"Norėdami kontroliuoti procesą, kaupiame nuostolį ir tikslumą per kelias iteracijas ir juos atspausdiname kas `report_freq` mokymo ciklų.\n",
|
||||
"\n",
|
||||
"Šis mokymas greičiausiai užtruks gana ilgai, todėl ribojame iteracijų skaičių.\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": [
|
||||
"Galite pastebėti (ypač jei padidinsite iteracijų skaičių ir palauksite pakankamai ilgai), kad BERT klasifikacija suteikia mums gana gerą tikslumą! Taip yra todėl, kad BERT jau gana gerai supranta kalbos struktūrą, o mums tereikia pritaikyti galutinį klasifikatorių. Tačiau, kadangi BERT yra didelis modelis, visas mokymo procesas užtrunka ilgai ir reikalauja didelių skaičiavimo resursų! (GPU, ir geriausia, jei jų būtų daugiau nei vienas).\n",
|
||||
"\n",
|
||||
"> **Pastaba:** Mūsų pavyzdyje naudojome vieną iš mažiausių iš anksto apmokytų BERT modelių. Yra didesnių modelių, kurie tikriausiai duotų geresnius rezultatus.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modelio veikimo vertinimas\n",
|
||||
"\n",
|
||||
"Dabar galime įvertinti mūsų modelio veikimą testavimo duomenų rinkinyje. Vertinimo ciklas yra gana panašus į mokymo ciklą, tačiau nepamirškime perjungti modelio į vertinimo režimą, iškviečiant `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": [
|
||||
"## Pagrindinės mintys\n",
|
||||
"\n",
|
||||
"Šiame skyriuje matėme, kaip lengva paimti iš anksto apmokytą kalbos modelį iš **transformers** bibliotekos ir pritaikyti jį mūsų teksto klasifikavimo užduočiai. Panašiai BERT modeliai gali būti naudojami objektų išskyrimui, klausimų atsakymui ir kitoms NLP užduotims.\n",
|
||||
"\n",
|
||||
"Transformeriai yra dabartinė pažangiausia technologija NLP srityje, ir daugeliu atvejų tai turėtų būti pirmasis sprendimas, kurį pradedate testuoti, kai įgyvendinate individualius NLP sprendimus. Tačiau suprasti pagrindinius pasikartojančių neuroninių tinklų principus, aptartus šiame modulyje, yra itin svarbu, jei norite kurti pažangius neuroninius modelius.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant dirbtinio intelekto vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, atkreipiame dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudotis profesionalių vertėjų paslaugomis. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus aiškinimus, kylančius dėl šio vertimo naudojimo.\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-31T13:49:51+00:00",
|
||||
"source_file": "lessons/5-NLP/18-Transformers/TransformersPyTorch.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,819 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Dėmesio mechanizmai ir transformatoriai\n",
|
||||
"\n",
|
||||
"Viena iš pagrindinių pasikartojančių tinklų trūkumų yra ta, kad visi žodžiai sekoje turi vienodą įtaką rezultatui. Tai lemia neoptimalų standartinių LSTM koduotojo-dekoduotojo modelių veikimą atliekant sekos į seką užduotis, tokias kaip pavadintų objektų atpažinimas ar mašininis vertimas. Iš tiesų, tam tikri žodžiai įvesties sekoje dažnai turi didesnę įtaką išvesties sekai nei kiti.\n",
|
||||
"\n",
|
||||
"Apsvarstykime sekos į seką modelį, pavyzdžiui, mašininį vertimą. Jis įgyvendinamas naudojant du pasikartojančius tinklus, kur vienas tinklas (**koduotojas**) suspaudžia įvesties seką į paslėptą būseną, o kitas tinklas (**dekoduotojas**) išskleidžia šią paslėptą būseną į išverstą rezultatą. Problema su šiuo požiūriu yra ta, kad tinklo galutinė būsena sunkiai prisimena sakinio pradžią, todėl modelis prastai veikia su ilgais sakiniais.\n",
|
||||
"\n",
|
||||
"**Dėmesio mechanizmai** suteikia galimybę įvertinti kiekvieno įvesties vektoriaus kontekstinę įtaką kiekvienai RNN išvesties prognozei. Tai įgyvendinama sukuriant trumpesnius ryšius tarp tarpinių įvesties RNN būsenų ir išvesties RNN. Tokiu būdu, generuojant išvesties simbolį $y_t$, atsižvelgiama į visas įvesties paslėptas būsenas $h_i$, su skirtingais svorio koeficientais $\\alpha_{t,i}$. \n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Koduotojo-dekoduotojo modelis su adityviniu dėmesio mechanizmu iš [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf), cituota iš [šio tinklaraščio įrašo](https://lilianweng.github.io/lil-log/2018/06/24/attention-attention.html)*\n",
|
||||
"\n",
|
||||
"Dėmesio matrica $\\{\\alpha_{i,j}\\}$ atspindi, kokiu mastu tam tikri įvesties žodžiai prisideda prie tam tikro žodžio generavimo išvesties sekoje. Žemiau pateiktas tokios matricos pavyzdys:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"*Paveikslas paimtas iš [Bahdanau et al., 2015](https://arxiv.org/pdf/1409.0473.pdf) (3 pav.)*\n",
|
||||
"\n",
|
||||
"Dėmesio mechanizmai yra atsakingi už dabartinę arba beveik dabartinę pažangiausią natūralios kalbos apdorojimo būklę. Tačiau dėmesio pridėjimas žymiai padidina modelio parametrų skaičių, o tai sukėlė mastelio problemas su RNN. Vienas iš pagrindinių RNN mastelio apribojimų yra tas, kad modelių pasikartojantis pobūdis apsunkina mokymo partijų kūrimą ir lygiagretinimą. RNN kiekvienas sekos elementas turi būti apdorojamas nuosekliai, todėl jų lygiagretinimas yra sudėtingas.\n",
|
||||
"\n",
|
||||
"Dėmesio mechanizmų pritaikymas kartu su šiuo apribojimu paskatino sukurti dabartinius pažangiausius transformatorių modelius, kuriuos šiandien naudojame, tokius kaip BERT ar OpenGPT3.\n",
|
||||
"\n",
|
||||
"## Transformatorių modeliai\n",
|
||||
"\n",
|
||||
"Užuot perdavę kiekvienos ankstesnės prognozės kontekstą į kitą vertinimo žingsnį, **transformatorių modeliai** naudoja **pozicinius kodavimus** ir **dėmesį**, kad užfiksuotų įvesties kontekstą tam tikrame teksto lange. Žemiau pateiktas paveikslas rodo, kaip poziciniai kodavimai kartu su dėmesiu gali užfiksuoti kontekstą tam tikrame lange.\n",
|
||||
"\n",
|
||||
" \n",
|
||||
"\n",
|
||||
"Kadangi kiekviena įvesties pozicija yra nepriklausomai susieta su kiekviena išvesties pozicija, transformatoriai gali geriau lygiagretinti nei RNN, o tai leidžia kurti daug didesnius ir išraiškingesnius kalbos modelius. Kiekviena dėmesio galvutė gali būti naudojama mokytis skirtingų žodžių tarpusavio ryšių, kurie pagerina natūralios kalbos apdorojimo užduotis.\n",
|
||||
"\n",
|
||||
"## Paprasto transformatoriaus modelio kūrimas\n",
|
||||
"\n",
|
||||
"Keras neturi įmontuoto transformatoriaus sluoksnio, tačiau mes galime sukurti savo. Kaip ir anksčiau, sutelksime dėmesį į AG News duomenų rinkinio teksto klasifikavimą, tačiau verta paminėti, kad transformatorių modeliai geriausius rezultatus pasiekia sudėtingesnėse NLP užduotyse.\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": [
|
||||
"Nauji sluoksniai Keras turėtų paveldėti `Layer` klasę ir įgyvendinti `call` metodą. Pradėkime nuo **Pozicinio Įterpimo** sluoksnio. Naudosime [šiek tiek kodo iš oficialios Keras dokumentacijos](https://keras.io/examples/nlp/text_classification_with_transformer/). Mes darysime prielaidą, kad visi įvesties sekos yra užpildytos iki ilgio `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": [
|
||||
"Šis sluoksnis susideda iš dviejų `Embedding` sluoksnių: vienas skirtas žodžių įterpimui (kaip aptarėme anksčiau), o kitas – pozicijų įterpimui. Pozicijos sukuriamos kaip natūralių skaičių seka nuo 0 iki `maxlen`, naudojant `tf.range`, ir tada perduodamos per įterpimo sluoksnį. Du gauti įterpimo vektoriai yra sudedami, sukuriant poziciškai įterptą įvesties reprezentaciją, kurios forma yra `maxlen`$\\times$`embed_dim`.\n",
|
||||
"\n",
|
||||
"Dabar įgyvendinkime transformatoriaus bloką. Jis priims anksčiau apibrėžto įterpimo sluoksnio išvestį:\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": [
|
||||
"Dabar esame pasiruošę apibrėžti pilną transformatoriaus modelį:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"sequential_1\"\n",
|
||||
"_________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # \n",
|
||||
"=================================================================\n",
|
||||
"text_vectorization (TextVect (None, 256) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"token_and_position_embedding (None, 256, 32) 648192 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"transformer_block (Transform (None, 256, 32) 10656 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"global_average_pooling1d (Gl (None, 32) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dropout_2 (Dropout) (None, 32) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense_2 (Dense) (None, 20) 660 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dropout_3 (Dropout) (None, 20) 0 \n",
|
||||
"_________________________________________________________________\n",
|
||||
"dense_3 (Dense) (None, 4) 84 \n",
|
||||
"=================================================================\n",
|
||||
"Total params: 659,592\n",
|
||||
"Trainable params: 659,592\n",
|
||||
"Non-trainable params: 0\n",
|
||||
"_________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embed_dim = 32 # Embedding size for each token\n",
|
||||
"num_heads = 2 # Number of attention heads\n",
|
||||
"ff_dim = 32 # Hidden layer size in feed forward network inside transformer\n",
|
||||
"maxlen = 256\n",
|
||||
"vocab_size = 20000\n",
|
||||
"\n",
|
||||
"model = keras.models.Sequential([\n",
|
||||
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_sequence_length=maxlen, input_shape=(1,)),\n",
|
||||
" TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim),\n",
|
||||
" TransformerBlock(embed_dim, num_heads, ff_dim),\n",
|
||||
" keras.layers.GlobalAveragePooling1D(),\n",
|
||||
" keras.layers.Dropout(0.1),\n",
|
||||
" keras.layers.Dense(20, activation=\"relu\"),\n",
|
||||
" keras.layers.Dropout(0.1),\n",
|
||||
" keras.layers.Dense(4, activation=\"softmax\")\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Training tokenizer\n",
|
||||
"938/938 [==============================] - 45s 39ms/step - loss: 0.4978 - acc: 0.8068 - val_loss: 0.2808 - val_acc: 0.9124\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f9c2427a0d0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print('Training tokenizer')\n",
|
||||
"model.layers[0].adapt(ds_train.map(extract_text))\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## BERT transformerių modeliai\n",
|
||||
"\n",
|
||||
"**BERT** (Bidirectional Encoder Representations from Transformers) yra labai didelis daugiasluoksnis transformerių tinklas, turintis 12 sluoksnių *BERT-base* versijoje ir 24 sluoksnius *BERT-large* versijoje. Modelis pirmiausia yra iš anksto apmokomas naudojant didelį tekstinių duomenų korpusą (Vikipedija + knygos) taikant nesupervizuotą mokymą (prognozuojant užmaskuotus žodžius sakinyje). Per šį išankstinį mokymą modelis įgyja reikšmingą kalbos supratimo lygį, kurį vėliau galima pritaikyti su kitais duomenų rinkiniais naudojant smulkųjį derinimą. Šis procesas vadinamas **perkėlimo mokymusi**.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Yra daug transformerių architektūrų variantų, įskaitant BERT, DistilBERT, BigBird, OpenGPT3 ir kitus, kuriuos galima smulkiai derinti.\n",
|
||||
"\n",
|
||||
"Pažiūrėkime, kaip galime naudoti iš anksto apmokytą BERT modelį, kad išspręstume tradicinę sekų klasifikavimo problemą. Pasiskolinsime idėją ir šiek tiek kodo iš [oficialios dokumentacijos](https://www.tensorflow.org/text/tutorials/classify_text_with_bert).\n",
|
||||
"\n",
|
||||
"Norėdami įkelti iš anksto apmokytus modelius, naudosime **Tensorflow hub**. Pirmiausia įkelkime BERT specifinį vektorizatorių:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"ename": "ModuleNotFoundError",
|
||||
"evalue": "No module named 'tensorflow_text'",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[1;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)",
|
||||
"\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_41180/4216669875.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mimport\u001b[0m \u001b[0mtensorflow_text\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mtensorflow_hub\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mhub\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mvectorizer\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mhub\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mKerasLayer\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
|
||||
"\u001b[1;31mModuleNotFoundError\u001b[0m: No module named 'tensorflow_text'"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import tensorflow_text \n",
|
||||
"import tensorflow_hub as hub\n",
|
||||
"vectorizer = hub.KerasLayer('https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'input_type_ids': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
|
||||
" array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int32)>,\n",
|
||||
" 'input_word_ids': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
|
||||
" array([[ 101, 1045, 2293, 19081, 102, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0]], dtype=int32)>,\n",
|
||||
" 'input_mask': <tf.Tensor: shape=(1, 128), dtype=int32, numpy=\n",
|
||||
" array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n",
|
||||
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
||||
" dtype=int32)>}"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"vectorizer(['I love transformers'])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Svarbu naudoti tą patį vektorizatorių, kuris buvo naudojamas originaliam tinklui treniruoti. Be to, BERT vektorizatorius grąžina tris komponentus:\n",
|
||||
"* `input_word_ids`, tai yra įvesties sakinio žodžių numerių seka\n",
|
||||
"* `input_mask`, rodanti, kuri seka yra tikroji įvestis, o kuri – užpildas. Tai panašu į kaukę, kurią sukuria `Masking` sluoksnis\n",
|
||||
"* `input_type_ids` naudojamas kalbos modeliavimo užduotims ir leidžia nurodyti du įvesties sakinius vienoje sekoje.\n",
|
||||
"\n",
|
||||
"Tuomet galime sukurti BERT funkcijų ištraukiklį:\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": [
|
||||
"Taigi, BERT sluoksnis grąžina keletą naudingų rezultatų:\n",
|
||||
"* `pooled_output` yra sekos visų žetonų vidurkis. Galite tai laikyti kaip išmanų viso tinklo semantinį įterpimą. Tai atitinka `GlobalAveragePooling1D` sluoksnio išvestį mūsų ankstesniame modelyje.\n",
|
||||
"* `sequence_output` yra paskutinio transformatoriaus sluoksnio išvestis (atitinka `TransformerBlock` išvestį mūsų aukščiau esančiame modelyje).\n",
|
||||
"* `encoder_outputs` yra visų transformatorių sluoksnių išvestys. Kadangi įkėlėme 4 sluoksnių BERT modelį (kaip tikriausiai galite nuspėti iš pavadinimo, kuriame yra `4_H`), jis turi 4 tensorius. Paskutinis iš jų yra toks pat kaip `sequence_output`.\n",
|
||||
"\n",
|
||||
"Dabar apibrėšime viso proceso klasifikavimo modelį. Naudosime *funkcinį modelio apibrėžimą*, kai apibrėžiame modelio įvestį ir tada pateikiame seriją išraiškų, kad apskaičiuotume jo išvestį. Taip pat padarysime, kad BERT modelio svoriai nebūtų treniruojami, ir treniruosime tik galutinį klasifikatorių:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"model\"\n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # Connected to \n",
|
||||
"==================================================================================================\n",
|
||||
"input_1 (InputLayer) [(None,)] 0 \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer (KerasLayer) {'input_type_ids': ( 0 input_1[0][0] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer_1 (KerasLayer) {'pooled_output': (N 4782465 keras_layer[0][0] \n",
|
||||
" keras_layer[0][1] \n",
|
||||
" keras_layer[0][2] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dropout_4 (Dropout) (None, 128) 0 keras_layer_1[0][5] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dense_4 (Dense) (None, 4) 516 dropout_4[0][0] \n",
|
||||
"==================================================================================================\n",
|
||||
"Total params: 4,782,981\n",
|
||||
"Trainable params: 516\n",
|
||||
"Non-trainable params: 4,782,465\n",
|
||||
"__________________________________________________________________________________________________\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"inp = keras.Input(shape=(),dtype=tf.string)\n",
|
||||
"x = vectorizer(inp)\n",
|
||||
"x = bert(x)\n",
|
||||
"x = keras.layers.Dropout(0.1)(x['pooled_output'])\n",
|
||||
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
||||
"model = keras.models.Model(inp,out)\n",
|
||||
"bert.trainable = False\n",
|
||||
"model.summary()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"938/938 [==============================] - 528s 559ms/step - loss: 0.8056 - acc: 0.6983 - val_loss: 0.5953 - val_acc: 0.7888\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f9bb1e36d00>"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Nepaisant to, kad treniruojamų parametrų yra nedaug, procesas vyksta gana lėtai, nes BERT požymių ištraukiklis yra skaičiavimo požiūriu sudėtingas. Panašu, kad mums nepavyko pasiekti tinkamo tikslumo, galbūt dėl nepakankamo treniravimo arba dėl modelio parametrų trūkumo.\n",
|
||||
"\n",
|
||||
"Pabandykime atšildyti BERT svorius ir taip pat jį treniruoti. Tam reikės labai mažo mokymosi greičio, taip pat atsargesnės treniravimo strategijos su **apšilimu**, naudojant **AdamW** optimizatorių. Naudosime `tf-models-official` paketą optimizatoriui sukurti:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model: \"model\"\n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"Layer (type) Output Shape Param # Connected to \n",
|
||||
"==================================================================================================\n",
|
||||
"input_1 (InputLayer) [(None,)] 0 \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer (KerasLayer) {'input_type_ids': ( 0 input_1[0][0] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"keras_layer_1 (KerasLayer) {'pooled_output': (N 4782465 keras_layer[0][0] \n",
|
||||
" keras_layer[0][1] \n",
|
||||
" keras_layer[0][2] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dropout_4 (Dropout) (None, 128) 0 keras_layer_1[0][5] \n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"dense_4 (Dense) (None, 4) 516 dropout_4[0][0] \n",
|
||||
"==================================================================================================\n",
|
||||
"Total params: 4,782,981\n",
|
||||
"Trainable params: 4,782,980\n",
|
||||
"Non-trainable params: 1\n",
|
||||
"__________________________________________________________________________________________________\n",
|
||||
"938/938 [==============================] - 629s 664ms/step - loss: 0.6344 - acc: 0.7658 - val_loss: 0.4876 - val_acc: 0.8247\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f9bb0bd0070>"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from official.nlp import optimization \n",
|
||||
"bert.trainable=True\n",
|
||||
"model.summary()\n",
|
||||
"epochs = 3\n",
|
||||
"opt = optimization.create_optimizer(\n",
|
||||
" init_lr=3e-5,\n",
|
||||
" num_train_steps=epochs*len(ds_train),\n",
|
||||
" num_warmup_steps=0.1*epochs*len(ds_train),\n",
|
||||
" optimizer_type='adamw')\n",
|
||||
"\n",
|
||||
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer=opt)\n",
|
||||
"model.fit(ds_train.map(tupelize).batch(128),validation_data=ds_test.map(tupelize).batch(128))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Kaip matote, mokymas vyksta gana lėtai – tačiau galite eksperimentuoti ir treniruoti modelį kelis epochus (5–10), kad pamatytumėte, ar galite pasiekti geriausią rezultatą, palyginti su anksčiau naudotais metodais.\n",
|
||||
"\n",
|
||||
"## Huggingface Transformers biblioteka\n",
|
||||
"\n",
|
||||
"Kitas labai dažnas (ir šiek tiek paprastesnis) būdas naudoti Transformer modelius yra [HuggingFace paketas](https://github.com/huggingface/), kuris suteikia paprastus komponentus įvairioms NLP užduotims. Jis prieinamas tiek Tensorflow, tiek PyTorch – dar vienai labai populiariai neuroninių tinklų sistemai.\n",
|
||||
"\n",
|
||||
"> **Note**: Jei nesate suinteresuoti pamatyti, kaip veikia Transformers biblioteka – galite praleisti šio užrašų knygelės pabaigą, nes nieko iš esmės naujo, palyginti su tuo, ką darėme aukščiau, nepamatysite. Mes kartosime tuos pačius BERT modelio mokymo žingsnius, naudodami kitą biblioteką ir žymiai didesnį modelį. Taigi procesas apima gana ilgą mokymą, todėl galite tiesiog peržvelgti kodą.\n",
|
||||
"\n",
|
||||
"Pažiūrėkime, kaip mūsų problemą galima išspręsti naudojant [Huggingface Transformers](http://huggingface.co).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pirmiausia turime pasirinkti modelį, kurį naudosime. Be kelių įmontuotų modelių, Huggingface turi [internetinį modelių saugyklą](https://huggingface.co/models), kur bendruomenė dalijasi daugybe iš anksto apmokytų modelių. Visi šie modeliai gali būti įkelti ir naudojami tiesiog nurodant modelio pavadinimą. Visi reikalingi dvejetainiai failai modeliui bus automatiškai atsisiųsti.\n",
|
||||
"\n",
|
||||
"Tam tikrais atvejais jums gali prireikti įkelti savo modelius. Tokiu atveju galite nurodyti katalogą, kuriame yra visi susiję failai, įskaitant parametrus, skirtus tokenizeriui, `config.json` failą su modelio parametrais, dvejetainius svorius ir pan.\n",
|
||||
"\n",
|
||||
"Iš modelio pavadinimo galime sukurti tiek modelį, tiek tokenizerį. Pradėkime nuo tokenizerio:\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": [
|
||||
"`tokenizer` objektas turi `encode` funkciją, kuri gali būti tiesiogiai naudojama tekstui užkoduoti:\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": [
|
||||
"Mes taip pat galime naudoti tokenizatorių, kad užkoduotume seką taip, kad ji būtų tinkama perduoti modeliui, t. y. įtraukiant `token_ids`, `input_mask` laukus ir kt. Taip pat galime nurodyti, kad norime Tensorflow tensorių, pateikdami argumentą `return_tensors='tf'`:\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'input_ids': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[ 101, 7592, 1010, 2045, 102]], dtype=int32)>, 'token_type_ids': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[0, 0, 0, 0, 0]], dtype=int32)>, 'attention_mask': <tf.Tensor: shape=(1, 5), dtype=int32, numpy=array([[1, 1, 1, 1, 1]], dtype=int32)>}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tokenizer(['Hello, there'],return_tensors='tf')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Šiuo atveju naudosime iš anksto apmokytą BERT modelį, vadinamą `bert-base-uncased`. *Uncased* reiškia, kad modelis yra nejautrus raidžių dydžiui.\n",
|
||||
"\n",
|
||||
"Treniruojant modelį, turime pateikti tokenizuotą seką kaip įvestį, todėl sukursime duomenų apdorojimo procesą. Kadangi `tokenizer.encode` yra Python funkcija, naudosime tą patį metodą kaip ir paskutiniame skyriuje, iškviesdami ją naudojant `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": [
|
||||
"Dabar galime įkelti faktinį modelį naudodami `BertForSequenceClassification` paketą. Tai užtikrina, kad mūsų modelis jau turi reikiamą klasifikavimo architektūrą, įskaitant galutinį klasifikatorių. Pamatysite įspėjimo pranešimą, kad galutinio klasifikatoriaus svoriai nėra inicializuoti, ir modelis reikalautų išankstinio mokymo - tai visiškai normalu, nes būtent tai mes ketiname daryti!\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": [
|
||||
"Kaip matote iš `summary()`, modelis turi beveik 110 milijonų parametrų! Tikėtina, kad jei norime paprastos klasifikavimo užduoties su palyginti mažu duomenų rinkiniu, nenorime treniruoti BERT bazinio sluoksnio:\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": [
|
||||
"Dabar esame pasiruošę pradėti mokymus!\n",
|
||||
"\n",
|
||||
"> **Pastaba**: Pilno masto BERT modelio mokymas gali užtrukti labai daug laiko! Todėl mes jį treniruosime tik pirmosioms 32 partijoms. Tai tik tam, kad parodytume, kaip nustatomas modelio mokymas. Jei norite išbandyti pilno masto mokymą, tiesiog pašalinkite `steps_per_epoch` ir `validation_steps` parametrus ir pasiruoškite laukti!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"32/32 [==============================] - 142s 4s/step - loss: 1.3896 - acc: 0.2500 - val_loss: 1.3863 - val_acc: 0.2480\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<tensorflow.python.keras.callbacks.History at 0x7f1d40a4b6a0>"
|
||||
]
|
||||
},
|
||||
"execution_count": 30,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.compile('adam','sparse_categorical_crossentropy',['acc'])\n",
|
||||
"tf.get_logger().setLevel('ERROR')\n",
|
||||
"model.fit(ds_train.map(process_fn).batch(32),validation_data=ds_test.map(process_fn).batch(32),steps_per_epoch=32,validation_steps=2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Jei padidinsite iteracijų skaičių, palauksite pakankamai ilgai ir treniruositės kelis epochus, galite tikėtis, kad BERT klasifikacija suteiks geriausią tikslumą! Taip yra todėl, kad BERT jau gana gerai supranta kalbos struktūrą, ir mums tereikia pritaikyti galutinį klasifikatorių. Tačiau, kadangi BERT yra didelis modelis, visas treniravimo procesas užtrunka ilgai ir reikalauja rimtų skaičiavimo išteklių! (GPU, ir pageidautina daugiau nei vieno).\n",
|
||||
"\n",
|
||||
"> **Pastaba:** Mūsų pavyzdyje naudojome vieną iš mažiausių iš anksto apmokytų BERT modelių. Yra didesnių modelių, kurie tikriausiai duotų geresnius rezultatus.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Pagrindinės mintys\n",
|
||||
"\n",
|
||||
"Šiame skyriuje aptarėme naujausias modelių architektūras, pagrįstas **transformeriais**. Mes pritaikėme jas savo teksto klasifikavimo užduočiai, tačiau BERT modeliai taip pat gali būti naudojami entitetų išskyrimui, klausimų-atsakymų sistemoms ir kitoms NLP užduotims.\n",
|
||||
"\n",
|
||||
"Transformeriai šiuo metu yra pažangiausia technologija NLP srityje, ir daugeliu atvejų tai turėtų būti pirmasis sprendimas, kurį pradėsite išbandyti, kurdami individualius NLP sprendimus. Tačiau labai svarbu suprasti pagrindinius pasikartojančių neuroninių tinklų principus, aptartus šiame modulyje, jei norite kurti pažangius neuroninius modelius.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama profesionali žmogaus vertimo paslauga. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius naudojant šį vertimą.\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-31T13:52:53+00:00",
|
||||
"source_file": "lessons/5-NLP/18-Transformers/TransformersTF.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
|
|
@ -0,0 +1,492 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Pavadintų objektų atpažinimas (NER)\n",
|
||||
"\n",
|
||||
"Šis užrašų knygelė yra iš [AI for Beginners Curriculum](http://aka.ms/ai-beginners).\n",
|
||||
"\n",
|
||||
"Šiame pavyzdyje išmoksime, kaip apmokyti NER modelį naudojant [Annotated Corpus for Named Entity Recognition](https://www.kaggle.com/datasets/abhinavwalia95/entity-annotated-corpus) duomenų rinkinį iš Kaggle. Prieš tęsiant, prašome atsisiųsti [ner_dataset.csv](https://www.kaggle.com/datasets/abhinavwalia95/entity-annotated-corpus?resource=download&select=ner_dataset.csv) failą į dabartinį katalogą.\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": [
|
||||
"## Duomenų rinkinio paruošimas\n",
|
||||
"\n",
|
||||
"Pradėsime nuo duomenų rinkinio įkėlimo į duomenų rėmelį. Jei norite sužinoti daugiau apie Pandas naudojimą, apsilankykite [pamokoje apie duomenų apdorojimą](https://github.com/microsoft/Data-Science-For-Beginners/tree/main/2-Working-With-Data/07-python) mūsų [Duomenų mokslo pradedantiesiems](http://aka.ms/datascience-beginners) vadove.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>Sentence #</th>\n",
|
||||
" <th>Word</th>\n",
|
||||
" <th>POS</th>\n",
|
||||
" <th>Tag</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>Sentence: 1</td>\n",
|
||||
" <td>Thousands</td>\n",
|
||||
" <td>NNS</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>of</td>\n",
|
||||
" <td>IN</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>demonstrators</td>\n",
|
||||
" <td>NNS</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>have</td>\n",
|
||||
" <td>VBP</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td>NaN</td>\n",
|
||||
" <td>marched</td>\n",
|
||||
" <td>VBN</td>\n",
|
||||
" <td>O</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" Sentence # Word POS Tag\n",
|
||||
"0 Sentence: 1 Thousands NNS O\n",
|
||||
"1 NaN of IN O\n",
|
||||
"2 NaN demonstrators NNS O\n",
|
||||
"3 NaN have VBP O\n",
|
||||
"4 NaN marched VBN O"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"df = pd.read_csv('ner_dataset.csv',encoding='unicode-escape')\n",
|
||||
"df.head()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Gaukime unikalius žymenis ir sukurkime paieškos žodynus, kuriuos galime naudoti žymenims konvertuoti į klasės numerius:\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": [
|
||||
"Dabar turime padaryti tą patį su žodynu. Dėl paprastumo sukursime žodyną, neatsižvelgdami į žodžių dažnį; realiame gyvenime galbūt norėsite naudoti Keras vektorizatorių ir apriboti žodžių skaičių.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"vocab = set(df['Word'].apply(lambda x: x.lower()))\n",
|
||||
"id2word = { i+1 : v for i,v in enumerate(vocab) }\n",
|
||||
"id2word[0] = '<UNK>'\n",
|
||||
"vocab.add('<UNK>')\n",
|
||||
"word2id = { v : k for k,v in id2word.items() }"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Turime sukurti sakinių duomenų rinkinį mokymui. Pereikime per originalų duomenų rinkinį ir atskirkime visus atskirus sakinius į `X` (žodžių sąrašus) ir `Y` (žymų sąrašą):\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": [
|
||||
"Kad būtų paprasčiau, visas sakinius užpildysime 0 žetonais iki maksimalaus ilgio. Realiame gyvenime galėtume naudoti protingesnę strategiją ir užpildyti sekas tik viename mini pakete.\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": [
|
||||
"## Apibrėžiant žymų klasifikavimo tinklą\n",
|
||||
"\n",
|
||||
"Naudosime dviejų sluoksnių dvikryptį LSTM tinklą žymų klasifikavimui. Kad galėtume taikyti tankųjį klasifikatorių kiekvienam paskutinio LSTM sluoksnio išėjimui, naudosime `TimeDistributed` konstrukciją, kuri tą patį tankųjį sluoksnį pritaiko kiekvienam LSTM išėjimui kiekviename žingsnyje:\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": [
|
||||
"Atkreipkite dėmesį, kad čia aiškiai nurodome `maxlen` mūsų duomenų rinkiniui – jei norime, kad tinklas galėtų apdoroti kintamo ilgio sekas, turime būti šiek tiek sumanesni, kai apibrėžiame tinklą.\n",
|
||||
"\n",
|
||||
"Dabar apmokykime modelį. Siekiant greičio, treniruosime tik vieną epochą, tačiau galite pabandyti treniruoti ilgesnį laiką. Taip pat galite atskirti dalį duomenų rinkinio kaip mokymo duomenų rinkinį, kad galėtumėte stebėti validacijos tikslumą.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 57,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1499/1499 [==============================] - 740s 488ms/step - loss: 0.0667 - acc: 0.9841\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<keras.callbacks.History at 0x16f0bb2a310>"
|
||||
]
|
||||
},
|
||||
"execution_count": 57,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.fit(X_data,Y_data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Rezultato testavimas\n",
|
||||
"\n",
|
||||
"Dabar pažiūrėkime, kaip mūsų subjektų atpažinimo modelis veikia su pavyzdiniu sakiniu:\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": [
|
||||
"## Svarbiausia\n",
|
||||
"\n",
|
||||
"Net ir paprastas LSTM modelis rodo pakankamai gerus rezultatus NER užduotyje. Tačiau, norint pasiekti žymiai geresnių rezultatų, verta naudoti didelius iš anksto apmokytus kalbos modelius, tokius kaip BERT. Kaip apmokyti BERT NER užduočiai naudojant Huggingface Transformers biblioteką, aprašyta [čia](https://huggingface.co/course/chapter7/2?fw=pt).\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\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-31T13:55:49+00:00",
|
||||
"source_file": "lessons/5-NLP/19-NER/NER-TF.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Eksperimentai su OpenAI GPT\n",
|
||||
"\n",
|
||||
"Šis užrašų knygelė yra dalis [AI pradedantiesiems mokymo programos](http://aka.ms/ai-beginners).\n",
|
||||
"\n",
|
||||
"Šioje užrašų knygelėje tyrinėsime, kaip galime naudoti OpenAI-GPT modelį su Hugging Face `transformers` biblioteka.\n",
|
||||
"\n",
|
||||
"Be ilgų įžangų, sukurkime teksto generavimo procesą ir pradėkime generuoti!\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": [
|
||||
"## Užklausų kūrimas\n",
|
||||
"\n",
|
||||
"Kai kuriose problemose galite iš karto naudoti openai-gpt generavimą, sukurdami tinkamas užklausas. Pažvelkite į žemiau pateiktus pavyzdžius:\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": [
|
||||
"## Teksto atrankos strategijos\n",
|
||||
"\n",
|
||||
"Iki šiol naudojome paprastą **godumo** atrankos strategiją, kai kitą žodį pasirinkome pagal didžiausią tikimybę. Štai kaip tai veikia:\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": [
|
||||
"**Spindulių paieška** leidžia generatoriui tyrinėti kelias teksto generavimo kryptis (*spindulius*) ir pasirinkti tas, kurios turi aukščiausią bendrą įvertinimą. Spindulių paiešką galite atlikti nurodydami `num_beams` parametrą. Taip pat galite nustatyti `no_repeat_ngram_size`, kad modelis būtų baudžiamas už tam tikro dydžio n-gramų kartojimą:\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": [
|
||||
"**Imties ėmimas** parenka kitą žodį nedeterminuotai, naudojant modelio grąžintą tikimybių pasiskirstymą. Imties ėmimą galite įjungti naudodami parametrą `do_sample=True`. Taip pat galite nurodyti `temperature`, kad modelis būtų daugiau ar mažiau deterministinis.\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": [
|
||||
"Mes taip pat galime pridėti papildomus parametrus mėginiavimui: \n",
|
||||
"* `top_k` nurodo žodžių pasirinkimų skaičių, į kuriuos atsižvelgiama naudojant mėginiavimą. Tai sumažina tikimybę, kad mūsų tekste atsiras keistų (mažos tikimybės) žodžių. \n",
|
||||
"* `top_p` yra panašus, tačiau pasirenkame mažiausią labiausiai tikėtinų žodžių rinkinį, kurio bendra tikimybė yra didesnė nei p. \n",
|
||||
"\n",
|
||||
"Drąsiai eksperimentuokite, pridėdami šiuos parametrus. \n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Modelių pritaikymas\n",
|
||||
"\n",
|
||||
"Jūs taip pat galite [pritaikyti savo modelį](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/fine-tuning?pivots=programming-language-studio?WT.mc_id=academic-77998-bethanycheum) pagal savo duomenų rinkinį. Tai leis jums koreguoti teksto stilių, išlaikant pagrindinę kalbos modelio dalį.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n---\n\n**Atsakomybės apribojimas**: \nŠis dokumentas buvo išverstas naudojant AI vertimo paslaugą [Co-op Translator](https://github.com/Azure/co-op-translator). Nors siekiame tikslumo, prašome atkreipti dėmesį, kad automatiniai vertimai gali turėti klaidų ar netikslumų. Originalus dokumentas jo gimtąja kalba turėtų būti laikomas autoritetingu šaltiniu. Kritinei informacijai rekomenduojama naudoti profesionalų žmogaus vertimą. Mes neprisiimame atsakomybės už nesusipratimus ar klaidingus interpretavimus, atsiradusius dėl šio vertimo naudojimo.\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-31T13:54:40+00:00",
|
||||
"source_file": "lessons/5-NLP/20-LangModels/GPT-PyTorch.ipynb",
|
||||
"language_code": "lt"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue