diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index c29476fa2..a7714c94a 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: en es fa fr he pt ru th tr + languages: bn en es fa fr he ko pt ru th tr secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} \ No newline at end of file diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index b83457820..f7b92f51d 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: en es fa fr he pt ru th tr + languages: bn en es fa fr he ko pt ru th tr hub_base_path: https://moon-ci-docs.huggingface.co/course \ No newline at end of file diff --git a/README.md b/README.md index 32e7d7b11..2b70941d7 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ This repo contains the content that's used to create the **[Hugging Face course] | Language | Source | Authors | |:-------------------------------------------------------|:-----------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | +| [Korean](https://huggingface.co/course/ko/chapter1/1) (WIP) | [`chapters/ko`](https://github.com/huggingface/course/tree/main/chapters/ko) | [@Doohae](https://github.com/Doohae) | +| [Russian](https://huggingface.co/course/ru/chapter1/1) (WIP) | [`chapters/ru`](https://github.com/huggingface/course/tree/main/chapters/ru) | [@pdumin](https://github.com/pdumin) | | [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu) | -| [Thai](https://huggingface.co/course/th/chapter1/1) (WIP) | [`chapters/th`](https://github.com/huggingface/course/tree/main/chapters/th) | [@peeraponw](https://github.com/peeraponw) | +| [Thai](https://huggingface.co/course/th/chapter1/1) (WIP) | [`chapters/th`](https://github.com/huggingface/course/tree/main/chapters/th) | [@peeraponw](https://github.com/peeraponw), [@a-krirk](https://github.com/a-krirk), [@jomariya23156](https://github.com/jomariya23156) | ### Translating the course into your language diff --git a/chapters/bn/_toctree.yml b/chapters/bn/_toctree.yml new file mode 100644 index 000000000..6be05c074 --- /dev/null +++ b/chapters/bn/_toctree.yml @@ -0,0 +1,4 @@ +- title: 0. সেটআপ + sections: + - local: chapter0/1 + title: ভূমিকা diff --git a/chapters/bn/chapter0/1.mdx b/chapters/bn/chapter0/1.mdx new file mode 100644 index 000000000..ab323044e --- /dev/null +++ b/chapters/bn/chapter0/1.mdx @@ -0,0 +1,112 @@ +# ভূমিকা + +হাগিং ফেস কোর্সে স্বাগতম! এই অধ্যায়টি একটি "ওয়ার্কিং এনভায়রনমেন্ট" সেট আপ করতে আপনাকে গাইড করবে। আপনি যদি এইপ্রথম কোর্সটি শুরু করে থাকেন, আমরা আপনাকে প্রথমে [অধ্যায় 1](/course/chapter1) একবার দেখে পড়ে আসার পরামর্শ দিচ্ছি, ফিরে এসে "ওয়ার্কিং এনভায়রনমেন্ট" সেট আপ করুন যাতে আপনি নিজেই কোডটি চেষ্টা করতে পারেন। + +এই কোর্সে আমরা যে সমস্ত লাইব্রেরিগুলি ব্যবহার করব সেগুলি পাইথন প্যাকেজ হিসাবে পাওয়া যাবে, তাই এখানে আমরা আপনাকে দেখাব কিভাবে একটি পাইথন এনভায়রনমেন্ট সেট আপ করতে হয় এবং আপনার প্রয়োজনীয় নির্দিষ্ট লাইব্রেরিগুলি ইনস্টল করতে হয়৷ + +Colab নোটবুক বা পাইথন virtual environment ব্যবহার করে আমরা "ওয়ার্কিং এনভায়রনমেন্ট" সেট-আপ করার দুটি উপায় কভার করব। যে পদ্ধতিটি আপনার কাছে সহজ সেটি আপনি বেছে নিতে পাড়েন। যারা নতুন শুরু করছেন তাদের জন্য আমরা Colab নোটবুক ব্যবহার করে শুরু করতে জোরালোভাবে রিকমেন্ড করি। + +মনে রাখবেন যে এখানে উইন্ডোজ সিস্টেম কভার করা হবে না। আপনি যদি উইন্ডোজ চালান, তাহলে আমরা Colab নোটবুক ব্যবহার করে ফলো করার পরামর্শ দিচ্ছি। আর আপনি যদি লিনাক্স ডিস্ট্রিবিউশন বা ম্যাকওএস ব্যবহার করেন তবে এখানে বর্ণিত পদ্ধতির যেকোনো একটি ব্যবহার করতে পারেন। + +কোর্সের অনেকটাই হাগিং ফেস অ্যাকাউন্ট উপর নির্ভর করবে। তাই আমরা একটি একাউন্ট ওপেন করার করার পরামর্শ দিচ্ছি: [একটি অ্যাকাউন্ট তৈরি করুন](https://huggingface.co/join)। + +## Google Colab নোটবুক ব্যবহার করার পদ্ধতি + +Colab নোটবুক ব্যবহার করার সবচেয়ে সহজ সেটআপ হচ্ছে ব্রাউজারে একটি নোটবুক ওপেন করুন এবং সরাসরি কোডিং এ যান! + +আপনি যদি Colab-এর সাথে পরিচিত না হন তাহলে আমরা আপনাকে [Colab পরিচয়](https://colab.research.google.com/notebooks/intro.ipynb) অনুসরণ করে শুরু করার পরামর্শ দিচ্ছি। Colab আপনাকে কিছু এক্সেলারেসন হার্ডওয়্যার ব্যবহার করতে দেয়, যেমন GPUs বা TPUs যা ছোট ওয়ার্ক লোডের জন্য ফ্রি। + +Colab-এর উপর আপানার হাত চলে আসলে একটি নতুন নোটবুক ওপেন করে সেট-আপ শুরু করুন: + +
+An empty colab notebook +
+ +পরবর্তী ধাপে আমরা এই কোর্সে ব্যবহার হবে এমন লাইব্রেরিগুলি ইনস্টল করা দেখাবো। আমরা ইনস্টলেশনের জন্য পাইথনের প্যাকেজ ম্যানেজার `pip` ব্যবহার করব। নোটবুকগুলিতে, আপনি `!` অক্ষর দিয়ে আগে সিস্টেম কমান্ড চালাতে পারবেন। যেমন ধরুন, নিচের কমান্ডটি দিয়ে 🤗 Transformers লাইব্রেরি ইনস্টল করতে পারবেন: + +``` +!pip install transformers +``` + +প্যাকেজটি আপনার পাইথন রানটাইমের মধ্যে সঠিকভাবে ইনস্টল করা হয়েছে কিনা তা import করে নিশ্চিত হতে পাড়েন। + +``` +import transformers +``` + +
+একটি gif উপরের দুটি কমান্ডের ফলাফল দেখাচ্ছে: installation and import +
+ +এটি 🤗 ট্রান্সফরমারের একটি খুব লাইট ভার্সন ইনস্টল করে। বিশেষ করে, যদিনা নির্দিষ্ট মেশিন লার্নিং ফ্রেমওয়ার্ক (যেমন PyTorch বা TensorFlow) ইনস্টল করা থাকে। যেহেতু আমরা লাইব্রেরির বিভিন্ন ফিচার ব্যবহার করব, তাই আমরা ডেভেলপমেন্ট ভার্সন ইনস্টল করার পরামর্শ দিচ্ছি, যতে ধারানা করার এমন সব ইউজ কেসে কাজ করবে: + +``` +!pip install transformers[sentencepiece] +``` + +ইনস্টল হতে কিছুটা সময় লাগবে, কিন্তু এরপর আপনি বাকি কোর্সের জন্য প্রস্তুত হয়ে যাবেন! + +## একটি পাইথন virtual environment ব্যবহার করা + +আপনি যদি পাইথন virtual environment ব্যবহার করতে পছন্দ করেন, প্রথম ধাপ হল আপনার সিস্টেমে পাইথন ইনস্টল করা। শুরু করার জন্য আমরা [এই নির্দেশিকা](https://realpython.com/installing-python/) অনুসরণ করার পরামর্শ দিচ্ছি। + +একবার আপনি পাইথন ইনস্টল করলে, আপনি আপনার টার্মিনালে পাইথন কমান্ড চালাতে সক্ষম হবেন। পরবর্তী ধাপে যাওয়ার আগে এটি সঠিকভাবে ইনস্টল করা হয়েছে তা নিশ্চিত করতে আপনি নিম্নলিখিত কমান্ডটি চালিয়ে শুরু করতে পারেন: `python --version`। এটি আপনার সিস্টেমে ইনস্টল হওয়া পাইথন সংস্করণটি প্রিন্ট করা উচিত। + +আপনার টার্মিনালে পাইথন কমান্ড চালানোর সময়, যেমন `python --version`, আপানাকে ভাবতে হবে যে এটি "main" পাইথন প্রোগ্রাম যা আপানার কমান্ড টিকে রান করছে। আমরা এই মূল ইনস্টলেশনটিকে যেকোন প্যাকেজ ইনস্টল থেকে মুক্ত রাখার সুপারিশ করি। এ আপনি যখন আলাদা অ্যাপ্লিকেশনে কাজ করবেন তখন তার জন্য আলাদা virtual environment তৈরি করতে এই পাইথন ইনস্টলেশনটিকে ব্যবহার করবেন। এতে করে প্রতিটি অ্যাপ্লিকেশনের নিজস্ব ডিপেন্ডেন্সি এবং প্যাকেজ আলাদা থাকবে এবং অন্যান্য অ্যাপ্লিকেশনের সাথে এর সম্ভাব্য কম্পাটিবিলটি নিয়ে আপানকে সমস্যায় করতে হবে না। + +পাইথনে এটি [*virtual environments*](https://docs.python.org/3/tutorial/venv.html) দিয়ে করা হয়, যেটি স্বয়ংসম্পূর্ণ ডিরেক্টরি ট্রি। যার প্রত্যেকটিতে এপ্লিকেশনের প্রয়োজনীয় সমস্ত প্যাকেজের পাশাপাশি একটি নির্দিষ্ট পাইথন ভার্শনের পাইথন ইনস্টলেশন আছে। এই ধরনের একটি virtual environments বিভিন্ন ভাবে তৈরি করা যেতে পারে। তবে আমরা এর জন্য অফিসিয়াল পাইথন প্যাকেজ ব্যবহার করব, যাকে বলা হয় [`venv`](https://docs.python.org/3/library) /venv.html#module-venv)। + +প্রথমে, আপনি যে ডিরেক্টরিটি আপনার অ্যাপ্লিকেশনটিতে রাখতে চান তা তৈরি করুন — উদাহরণস্বরূপ, আপনি আপনার হোম ডিরেক্টরির বা ফোল্ডার ভেতর *transformers-course* নামে একটি নতুন ডিরেক্টরি তৈরি করতে চাইতে পারেন: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +এই ডিরেক্টরির ভিতর থেকে, পাইথন `venv` মডিউল ব্যবহার করে একটি virtual environment তৈরি করুন: + +``` +python -m venv .env +``` + +আপনার এখন *.env* নামে একটি ফোল্ডার থাকা উচিত, অন্যথায় খালি ফোল্ডার : + +``` +ls -a +``` + +```out +. .. .env +``` + + +আপনি এখন virtual environment টি `activate` করতে বা `deactivate` নিচের কমান্ড গুলো ব্যবহার করতে পারেন। + +``` +# virtual environment টি activate করার কমান্ড +source .env/bin/activate + +# virtual environment টি deactivate করার কমান্ড +source .env/bin/deactivate +``` + +`which python` কমান্ড চালিয়ে নিশ্চিত করতে পারেন যে virtual environment টি activate হয়েছে কিনা। +যদি এটি virtual environment টি কে পয়েন্ট করে করে, তাহলে আপনি সফলভাবে এটি সক্রিয় করেছেন! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### ডিপেন্ডেন্সি ইনস্টল করা + +আগের সেকশনে Google Colab এ যেভাবে প্যাকেজ ইনস্টল করা হয়েছে একই ভাবে এখানেও `pip` প্যাকেজ ম্যানেজার ব্যবহার করে 🤗 Transformer এর development সংস্করণ ইনস্টল করতে পারেন: + +```` +pip install "transformers[sentencepiece]" +```` + +আপনি এখন শুরু করা জন্য সম্পূর্ণ প্রস্তুত! diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index 4a4973f75..ad9953b2d 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -1,4 +1,24 @@ - title: 0. Configuration sections: - local: chapter0/1 - title: Introduction \ No newline at end of file + title: Introduction + +- title: 5. La bibliothèque 🤗 Datasets + sections: + - local: chapter5/1 + title: Introduction + - local: chapter5/2 + title: Que faire si mon ensemble de données n'est pas sur le Hub ? + - local: chapter5/3 + title: Il est temps de trancher et de découper + - local: chapter5/4 + title: Big Data? 🤗 Des jeux de données à la rescousse ! + - local: chapter5/5 + title: Création de votre propre jeu de données + - local: chapter5/6 + title: Recherche sémantique avec FAISS + - local: chapter5/7 + title: 🤗 Datasets, vérifié ! + - local: chapter5/8 + title: Quiz de fin de chapitre + quiz: 5 \ No newline at end of file diff --git a/chapters/fr/chapter5/1.mdx b/chapters/fr/chapter5/1.mdx new file mode 100644 index 000000000..8e76cea5e --- /dev/null +++ b/chapters/fr/chapter5/1.mdx @@ -0,0 +1,17 @@ +# Introduction + +Dans le [Chapitre 3](/course/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 Datasets et vous avez vu qu'il y avait trois étapes principales pour affiner un modèle: + +1. Chargez un jeu de données à partir de Hugging Face Hub. +2. Prétraitez les données avec `Dataset.map()`. +3. Charger et calculer des métriques. + +Mais ce n'est qu'effleurer la surface de ce que 🤗 Datasets peut faire ! Dans ce chapitre, nous allons plonger profondément dans la bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes: + +* Que faites-vous lorsque votre jeu de données n'est pas sur le Hub ? +* Comment pouvez-vous découper et trancher un ensemble de données ? (Et si vous avez _vraiment_ besoin d'utiliser Pandas ?) +* Que faites-vous lorsque votre ensemble de données est énorme et va faire fondre la RAM de votre ordinateur portable ? +* Qu'est-ce que c'est que le "mappage de la mémoire" et Apache Arrow ? +* Comment pouvez-vous créer votre propre ensemble de données et le pousser vers le Hub ? + +Les techniques que vous apprenez ici vous prépareront aux tâches avancées de tokenisation et de réglage fin du [Chapitre 6](/course/chapter6) et du [Chapitre 7](/course/chapter7) -- alors prenez un café et commençons ! \ No newline at end of file diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx new file mode 100644 index 000000000..896dc8ca2 --- /dev/null +++ b/chapters/fr/chapter5/2.mdx @@ -0,0 +1,167 @@ +# Que faire si mon ensemble de données n'est pas sur le Hub ? + + + +Vous savez comment utiliser le [Hugging Face Hub](https://huggingface.co/datasets) pour télécharger des ensembles de données, mais vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 Datasets peut être utilisé pour charger des ensembles de données qui ne sont pas disponibles sur le Hugging Face Hub. + + + +## Travailler avec des ensembles de données locaux et distants + +🤗 Datasets fournit des scripts de chargement pour gérer le chargement des ensembles de données locaux et distants. Il prend en charge plusieurs formats de données courants, tels que : + +| Format de données | Chargement du script | Exemple | +| :----------------: | :------------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & Lignes JSON | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| DataFrames marinés | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux ; plus tard, nous verrons comment faire la même chose avec des fichiers distants. + +## Charger un jeu de données local + +Pour cet exemple, nous utiliserons l'ensemble de données [SQuAD-it](https://github.com/crux82/squad-it/), qui est un ensemble de données à grande échelle pour répondre aux questions en Italien. + +Les fractionnements de formation et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Cela téléchargera deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz*, que nous pouvons décompresser avec la commande Linux `gzip` : + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_it-train.json_ et _SQuAD_it-text.json_, et que les données sont stockées au format JSON. + + + +✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes shell ci-dessus, c'est parce que nous les exécutons dans un cahier Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser l'ensemble de données dans un terminal. + + + +Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux ensembles de données de questions-réponses, SQuAD-it utilise le format imbriqué, avec tout le texte stocké dans un champ "données". Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec une division `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Cela nous montre le nombre de lignes et les noms de colonnes associés à l'ensemble d'apprentissage. Nous pouvons afficher l'un des exemples en indexant la division "train" comme suit : + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +Super, nous avons chargé notre premier jeu de données local ! Mais bien que cela ait fonctionné pour l'ensemble d'entraînement, ce que nous voulons vraiment, c'est inclure à la fois les divisions `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux divisions à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom de division à un fichier associé à cette division : + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer diverses techniques de prétraitement pour nettoyer les données, tokeniser les avis, etc. + + + +The `data_files` argument of the `load_dataset()` function is quite flexible and can be either a single file path, a list of file paths, or a dictionary that maps split names to file paths. You can also group files matching a specified pattern according to the rules used by the Unix shell (for example, you can group all JSON files in a directory into a single division by setting `data_files="*.json"` ). See 🤗 Datasets [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) for details. + + + +Les scripts de chargement dans 🤗 Datasets prend en charge la décompression automatique des fichiers d'entrée, nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR, il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! + +Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants. + +## Charger un jeu de données distant + +Si vous travaillez en tant que data scientist ou codeur dans une entreprise, il y a de fortes chances que les ensembles de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour l'ensemble de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela renvoie le même objet `DatasetDict` obtenu ci-dessus, mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des ensembles de données qui ne sont pas hébergés sur le Hugging Face Hub. Maintenant que nous avons un ensemble de données avec lequel jouer, mettons-nous la main à la pâte avec diverses techniques de gestion des données ! + + + +✏️ **Essayez-le !** Choisissez un autre ensemble de données hébergé sur GitHub ou le [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) et essayez de le charger localement et à distance en utilisant les techniques présentées ci-dessus. Pour obtenir des points bonus, essayez de charger un ensemble de données stocké au format CSV ou texte (voir la [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) pour plus d'informations sur ces formats). + + + + diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx new file mode 100644 index 000000000..9d6a57959 --- /dev/null +++ b/chapters/fr/chapter5/3.mdx @@ -0,0 +1,744 @@ +# Il est temps de trancher et de découper + + + +La plupart du temps, les données avec lesquelles vous travaillez ne seront pas parfaitement préparées pour les modèles de formation. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 Datasets pour nettoyer vos ensembles de données. + + + +## Trancher et découper nos données + +Semblable à Pandas, 🤗 Datasets fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [Chapitre 3](/course/chapter3), et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. + +Pour cet exemple, nous utiliserons le [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [UC Irvine Machine Learning Repository] (https://archive.ics.uci.edu/ml/index.php), qui contient des avis de patients sur divers médicaments, ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. + +Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 Datasets, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Peek at the first few examples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples de l'ensemble de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre ensemble de données : + +* La colonne "Sans nom : 0" ressemble étrangement à un identifiant anonyme pour chaque patient. +* La colonne "condition" comprend un mélange d'étiquettes en majuscules et en minuscules. +* Les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. + +Voyons comment nous pouvons utiliser 🤗 Datasets pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Cela semble confirmer notre hypothèse, alors nettoyons un peu l'ensemble de données en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Essayez-le !** Utilisez la fonction "Dataset.unique()" pour trouver le nombre de médicaments et de conditions uniques dans les ensembles d'entraînement et de test. + + + +Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Oh non, nous avons rencontré un problème avec notre fonction de carte ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne "condition" sont "Aucune", qui ne peuvent pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple de l'ensemble de données. Au lieu d'écrire une fonction explicite comme : + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +puis en exécutant `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _lambda function_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : + +``` +lambda : +``` + +où `lambda` est l'un des [mots clés] spéciaux de Python (https://docs.python.org/3/reference/lexical_analysis.html#keywords), `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : + +``` +lambda x : x * x +``` + +Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en les séparant par des virgules. Par exemple, nous pouvons calculer l'aire d'un triangle comme suit : + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Les fonctions Lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte 🤗 Datasets, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de mappage et de filtrage, alors utilisons cette astuce pour éliminer les entrées "None" dans notre jeu de données : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Avec les entrées "None" supprimées, nous pouvons normaliser notre colonne "condition" : + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Ça marche ! Maintenant que nous avons nettoyé les étiquettes, examinons le nettoyage des avis eux-mêmes. + +## Création de nouvelles colonnes + +Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme "Génial !" ou un essai complet avec des milliers de mots, et selon le cas d'utilisation, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. + +Définissons une fonction simple qui compte le nombre de mots dans chaque avis : + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne de l'ensemble de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il sera appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspect the first training example +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Comme prévu, nous pouvons voir qu'une colonne "review_length" a été ajoutée à notre ensemble d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, ne serait pas informatif si nous voulons prédire la condition. + + + +🙋 Une autre façon d'ajouter de nouvelles colonnes à un ensemble de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de fournir la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. + + + +Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne "condition", nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos ensembles d'entraînement et de test d'origine. + + + +✏️ **Essayez-le !** Utilisez la fonction `Dataset.sort()` pour inspecter les avis avec le plus grand nombre de mots. Consultez la [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) pour voir quel argument vous devez utiliser pour trier les avis par longueur dans l'ordre décroissant. + + + +La dernière chose à laquelle nous devons faire face est la présence de codes de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Nous utiliserons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données -- et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! + +## Les superpouvoirs de la méthode `map()` + +La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un lot d'exemples à la fonction map en une seule fois (la taille du lot est configurable mais par défaut à 1 000). Par exemple, la fonction de carte précédente qui dégageait tout le code HTML prenait un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une liste en compréhension. + +Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs de l'ensemble de données, mais chaque valeur est maintenant une _liste de valeurs_, et non plus une seule valeur. La valeur de retour de `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre ensemble de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Si vous exécutez ce code dans un notebook, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été sans échappement HTML -- si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle "for", et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. + +L'utilisation de `Dataset.map()` avec `batched=True` sera essentielle pour débloquer la vitesse des tokenizers "rapides" que nous rencontrerons dans [Chapitre 6](/course/chapter6), qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les revues de médicaments avec un tokenizer rapide, nous pourrions utiliser une fonction comme celle-ci : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Comme vous l'avez vu dans le [Chapitre 3](/course/chapter3), nous pouvons passer un ou plusieurs exemples au tokenizer, nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un cahier, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, il affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). + + + +✏️ **Essayez-le !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un tokenizer lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels numéros vous obtenez sur votre matériel. + + + +Voici les résultats que nous avons obtenus avec et sans batching, avec un tokenizer rapide et un lent : + +Options | Tokenizer rapide | Tokenizer lent +:--------------:|:----------------:|:-----------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Cela signifie que l'utilisation d'un tokenizer rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans traitement par lot -- c'est vraiment incroyable ! C'est la raison principale pour laquelle les tokenizers rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés "rapides"). Ils sont capables d'atteindre une telle accélération car dans les coulisses, le code de tokenisation est exécuté dans Rust, qui est un langage qui facilite la parallélisation de l'exécution du code. + +La parallélisation est également la raison de l'accélération de près de 6 fois obtenue par le fast tokenizer avec le traitement par lots : vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez tokeniser de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. chacun responsable de ses propres textes. + +`Dataset.map()` possède également ses propres capacités de parallélisation. Comme ils ne sont pas soutenus par Rust, ils ne laisseront pas un tokenizer lent rattraper un rapide, mais ils peuvent toujours être utiles (surtout si vous utilisez un tokenizer qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Vous pouvez expérimenter un peu le timing pour déterminer le nombre optimal de processus à utiliser ; dans notre cas 8 semblait produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : + +Options | Tokenizer rapide | Tokenizer lent +:----------------------------:|:----------------:|:---------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Ce sont des résultats beaucoup plus raisonnables pour le tokenizer lent, mais les performances du tokenizer rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas - pour les valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement Python pour les tokenizers rapides avec `batched=True`. + + + +Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée, tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. + + + +Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple, et nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches NLP que nous entreprendrons dans [Chapitre 7](/course/ Chapitre 7). + + + +💡 En machine learning, un _example_ est généralement défini comme l'ensemble de _features_ que nous alimentons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. + + + +Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128, mais nous demanderons au tokenizer de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur l'ensemble de données : + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Ainsi, notre premier exemple dans l'ensemble de formation est devenu deux fonctionnalités car il a été segmenté à plus que le nombre maximum de jetons que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du base de données! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 +``` + +Oh non! Cela n'a pas fonctionné ! Pourquoi pas? L'examen du message d'erreur nous donnera un indice : il y a une incompatibilité dans les longueurs de l'une des colonnes, l'une étant de longueur 1 463 et l'autre de longueur 1 000. Si vous avez consulté la [documentation] `Dataset.map()`(https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map), vous vous souviendrez peut-être qu'il s'agit du nombre d'échantillons passés à la fonction que nous mappons ; ici, ces 1 000 exemples ont donné 1 463 nouvelles fonctionnalités, entraînant une erreur de forme. + +Le problème est que nous essayons de mélanger deux ensembles de données différents de tailles différentes : les colonnes `drug_dataset` auront un certain nombre d'exemples (les 1 000 dans notre erreur), mais le `tokenized_dataset` que nous construisons en aura plus (le 1 463 dans le message d'erreur). Cela ne fonctionne pas pour un `Dataset`, nous devons donc soit supprimer les colonnes de l'ancien jeu de données, soit leur donner la même taille que dans le nouveau jeu de données. Nous pouvons faire le premier avec l'argument `remove_columns` : + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous aurons besoin du champ `overflow_to_sample_mapping` que le tokenizer renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de fonctionnalité et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles fonctionnalités : + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Nous obtenons le même nombre de fonctionnalités d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. + +Vous avez maintenant vu comment 🤗 Datasets peut être utilisé pour prétraiter un ensemble de données de différentes manières. Bien que les fonctions de traitement de 🤗 Datasets couvrent la plupart de vos besoins de formation de modèles, +il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 Datasets est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. + +## De `Dataset`s à `DataFrame`s et vice versa + + + +Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 Datasets fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ de l'ensemble de données, vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données en Pandas : + +```py +drug_dataset.set_format("pandas") +``` + +Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode dunder `__getitem__()` de l'ensemble de données. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout l'ensemble de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. + + + + +De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Essayez-le !** Calculez la note moyenne par médicament et stockez le résultat dans un nouvel ensemble de données. + + + +Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 Datasets. Pour compléter la section, créons un ensemble de validation pour préparer l'ensemble de données pour la formation d'un classificateur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : + +```python +drug_dataset.reset_format() +``` + +## Création d'un ensemble de validation + +Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble de test. Ce processus permet d'atténuer le risque de dépassement de l'ensemble de test et de déploiement d'un modèle qui échoue sur des données du monde réel. + +🤗 Datasets fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-le pour diviser notre ensemble d'entraînement en divisions "train" et "validation" (nous définissons l'argument "seed" pour la reproductibilité) : + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rename the default "test" split to "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Add the "test" set to our `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/chapter5/5), nous vous montrerons comment télécharger des ensembles de données sur le Hugging Face Hub, mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des ensembles de données sur votre ordinateur local. + +## Enregistrer un jeu de données + + + +Bien que 🤗 Datasets mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 Datasets fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : + +| Format de données | Fonction | +| :---------------: | :----------------------: | +| Flèche | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Cela créera un répertoire avec la structure suivante : + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +où nous pouvons voir que chaque division est associée à sa propre table * dataset.arrow * et à certaines métadonnées dans * dataset_info.json * et * state.json *. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. + +Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet "DatasetDict" : + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Cela enregistre chaque fractionnement au [format de lignes JSON] (https://jsonlines.org), où chaque ligne de l'ensemble de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125} +``` + +Nous pouvons ensuite utiliser les techniques de [section 2](/course/chapter5/2) pour charger les fichiers JSON comme suit : + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Et c'est tout pour notre excursion dans le data wrangling avec 🤗 Datasets ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : + +1. Utilisez les techniques du [Chapitre 3](/course/chapter3) pour former un classificateur capable de prédire l'état du patient en fonction de l'examen du médicament. +2. Utilisez le pipeline `summarization` du [Chapitre 1](/course/chapter1) pour générer des résumés des révisions. + +Ensuite, nous verrons comment 🤗 Datasets peut vous permettre de travailler avec d'énormes ensembles de données sans faire exploser votre ordinateur portable ! diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx new file mode 100644 index 000000000..63ed0be44 --- /dev/null +++ b/chapters/fr/chapter5/4.mdx @@ -0,0 +1,287 @@ +# Big Data? 🤗 Datasets à la rescousse ! + + + + +De nos jours, il n'est pas rare de travailler avec des ensembles de données de plusieurs gigaoctets, surtout si vous envisagez de pré-entraîner un transformateur comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte - le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! + +Heureusement, 🤗 Datasets a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les ensembles de données comme des fichiers _mappés en mémoire_, et des limites du disque dur en _streaming_ les entrées dans un corpus. + + + +Dans cette section, nous allons explorer ces fonctionnalités de 🤗 Datasets avec un énorme corpus de 825 Go connu sous le nom de [the Pile](https://pile.eleuther.ai). Commençons! + +## Qu'est-ce que The Pile ? + +The Pile est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée d'ensembles de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus de formation est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/), et vous pouvez également télécharger plusieurs des [composants individuels](https://mystic .the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil à l'ensemble de données PubMed Abstracts, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). L'ensemble de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`, nous devons donc d'abord l'installer : + +```py +!pip install zstandard +``` + +Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/chapter5/2) : + +```py +from datasets import load_dataset + +# Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre ensemble de données -- c'est beaucoup ! + + + +✎ Par défaut, 🤗 Datasets décompressera les fichiers nécessaires pour charger un jeu de données. Si vous souhaitez conserver de l'espace sur le disque dur, vous pouvez passer `DownloadConfig(delete_extracted=True)` à l'argument `download_config` de `load_dataset()`. Voir la [documentation](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig) pour plus de détails. + + + +Inspectons le contenu du premier exemple : + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'} +``` + +OK, ça ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! + +## La magie de la cartographie mémoire + +Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/), qui peut être installée avec `pip` comme suit : + +```python +!pip install psutil +``` + +Il fournit une classe `Process` qui nous permet de vérifier l'utilisation de la mémoire du processus en cours comme suit : + +```py +import psutil + +# Process.memory_info is expressed in bytes, so convert to megabytes +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger l'ensemble de données est un peu plus petite. À titre de comparaison, voyons la taille de l'ensemble de données sur le disque, en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Bien - malgré sa taille de près de 20 Go, nous pouvons charger et accéder à l'ensemble de données avec beaucoup moins de RAM ! + + + +✏️ **Essayez-le !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de la Pile qui est plus grand que la RAM de votre ordinateur portable ou de bureau, chargez avec 🤗 Datasets, et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 de [the Pile paper](https://arxiv.org/abs/2101.00027). + + + +Si vous êtes familier avec les pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 Datasets résout-il ce problème de gestion de la mémoire ? 🤗 Datasets traite chaque ensemble de données comme un [fichier mappé en mémoire] (https://en.wikipedia.org/wiki/Memory-mapped_file), qui fournit un mappage entre la RAM et le stockage du système de fichiers qui permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. + +Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus, ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier l'ensemble de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index .html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données PubMed Abstracts : + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un ensemble de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un ensemble de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger la Pile dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 Datasets fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. + + + +💡 Dans les notebooks Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Ensembles de données en continu + +Pour activer le streaming de l'ensemble de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données PubMed Abstracts, mais en mode streaming : + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : + + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'} +``` + +Les éléments d'un ensemble de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant la formation si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans [Chapitre 3](/course/chapter3), à la seule différence que les sorties sont renvoyées une par une : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples lot par lot ; la taille de lot par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. + + + +Vous pouvez également mélanger un ensemble de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un ensemble de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans l'ensemble de données PubMed Abstracts, nous pouvons procéder comme suit : + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un ensemble de données mélangé comme suit : + +```py +# Skip the first 1,000 examples and include the rest in the training set +train_dataset = shuffled_dataset.skip(1000) +# Take the first 1,000 examples for the validation set +validation_dataset = shuffled_dataset.take(1000) +``` + +Terminons notre exploration du streaming d'ensembles de données avec une application commune : combiner plusieurs ensembles de données pour créer un seul corpus. 🤗 Datasets fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands ensembles de données. Par exemple, diffusons le sous-ensemble FreeLaw de la pile, qui est un ensemble de données de 51 Go d'avis juridiques de tribunaux américains : + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Cet ensemble de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les exemples des jeux de données FreeLaw et PubMed Abstracts avec la fonction `interleave_datasets()` : + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples de l'ensemble de données combiné, et nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux ensembles de données source. + +Enfin, si vous souhaitez diffuser le Pile dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Essayez-le !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co /datasets/oscar) pour créer un jeu de données multilingue en continu qui représente les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche, vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. + + + +Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des ensembles de données de toutes formes et tailles - mais à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre parcours PNL où vous devrez réellement créer un ensemble de données pour résoudre le problème à portée de main. C'est le sujet de la section suivante ! diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx new file mode 100644 index 000000000..298f69200 --- /dev/null +++ b/chapters/fr/chapter5/5.mdx @@ -0,0 +1,469 @@ +# Création de votre propre jeu de données + + + +Parfois, l'ensemble de données dont vous avez besoin pour créer une application NLP n'existe pas, vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les référentiels GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : + +* Explorer combien de temps il faut pour fermer les problèmes ouverts ou les demandes d'extraction +* Entraînement d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple, "bogue", "amélioration" ou "question") +* Création d'un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur + +Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 Datasets ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. + +## Obtenir les données + +Vous pouvez trouver tous les problèmes dans 🤗 Datasets en accédant à l'[onglet Problèmes] du référentiel (https://github.com/huggingface/datasets/issues). Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés. + +
+Les problèmes GitHub associés aux 🤗 Datasets. +
+ +Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous. + +
+Un problème GitHub typique dans le référentiel 🤗 Datasets. +
+ +Pour télécharger tous les problèmes du référentiel, nous utiliserons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github. com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON, chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. + +Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque "requests", qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : + +```python +!pip install requests +``` + +Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : + +```py +response.status_code +``` + +```python out +200 +``` + +où un statut "200" signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles comme `title`, `body` et `number` qui décrivent le problème, ainsi que des informations sur l'utilisateur GitHub qui a ouvert le problème. + + + +✏️ **Essayez-le !** Cliquez sur quelques-unes des URL dans la charge utile JSON ci-dessus pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. + + + +Comme décrit dans la [documentation] GitHub(https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout référentiel contenant plus de quelques milliers de problèmes. Donc, à la place, vous devez suivre les [instructions] de GitHub (https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre jeton, vous pouvez l'inclure dans l'en-tête de la requête : + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Ne partagez pas un notebook avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. + + + +Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par lots pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure ; le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 Datasets : + +```py +# Depending on your internet connection, this can take several minutes to run... +fetch_issues() +``` + +Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/chaper5/2) : + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Génial, nous avons créé notre premier ensemble de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet Problèmes](https://github.com/huggingface/datasets/issues) du 🤗 Datasets n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation] GitHub(https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé tous les les demandes d'extraction également : + +> L'API REST v3 de GitHub considère chaque demande d'extraction comme un problème, mais chaque problème n'est pas une demande d'extraction. Pour cette raison, les points de terminaison "Problèmes" peuvent renvoyer à la fois des problèmes et des demandes d'extraction dans la réponse. Vous pouvez identifier les demandes d'extraction par la clé `pull_request`. Sachez que l'identifiant d'une demande d'extraction renvoyée par les points de terminaison "Problèmes" sera un identifiant de problème. + +Étant donné que le contenu des problèmes et des demandes d'extraction est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. + +## Nettoyer les données + +L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne "pull_request" peut être utilisée pour différencier les problèmes et les demandes d'extraction. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Ici, nous pouvons voir que chaque demande d'extraction est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée "Aucun". Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Essayez-le !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 Datasets. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts, et vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir l'ensemble de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les demandes d'extraction. + + + +Bien que nous puissions continuer à nettoyer davantage l'ensemble de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de conserver l'ensemble de données aussi "brut" que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. + +Avant de pousser notre ensemble de données vers le Hugging Face Hub, traitons d'une chose qui lui manque : les commentaires associés à chaque problème et pull request. Nous les ajouterons ensuite avec - vous l'avez deviné - l'API GitHub REST ! + +## Enrichir le jeu de données + +Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une demande d'extraction fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque. + +
+Commentaires associés à un problème concernant 🤗 Datasets. +
+ +L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Nous pouvons voir que le commentaire est stocké dans le champ `body`, écrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Testez notre fonction fonctionne comme prévu +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Cela a l'air bien, alors utilisons `Dataset.map()` pour ajouter une nouvelle colonne `commentaires` à chaque problème de notre ensemble de données : + +```py +# Selon votre connexion internet, cela peut prendre quelques minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +La dernière étape consiste à enregistrer l'ensemble de données augmentées avec nos données brutes afin que nous puissions les pousser toutes les deux vers le Hub : + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Téléchargement de l'ensemble de données sur le Hugging Face Hub + + + +Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le Hub afin que nous puissions le partager avec la communauté ! Pour télécharger l'ensemble de données, nous utiliserons la [bibliothèque Hub 🤗](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le hub Hugging Face via une API Python. 🤗 Hub est préinstallé avec 🤗 Transformers, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le Hub : + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Essayez-le !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face Hub pour obtenir un jeton et créer un référentiel vide appelé "github-issues". N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel, car ces informations peuvent être exploitées par de mauvais acteurs. + + + +Ensuite, clonons le référentiel du Hub sur notre machine locale et copions-y notre fichier d'ensemble de données. 🤗 Hub fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes, donc pour cloner le référentiel distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp datasets-issues-with-comments.jsonl github-issues/ +``` + +Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : + +```py +repo.lfs_track("*.jsonl") +``` + +Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser l'ensemble de données vers le Hub : + +```py +repo.push_to_hub() +``` + +Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé. + +
+Notre référentiel d'ensembles de données sur le Hugging Face Hub. +
+ +À partir de là, n'importe qui peut télécharger l'ensemble de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Cool, nous avons poussé notre jeu de données vers le Hub et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. + + + +💡 Vous pouvez également télécharger un ensemble de données sur le Hugging Face Hub directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [🤗 Datasets guide](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) pour savoir comment procéder. + + + +## Création d'une fiche de jeu de données + +Des ensembles de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à votre futur moi !), car ils fournissent le contexte permettant aux utilisateurs de décider si l'ensemble de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation l'ensemble de données. + +Sur le Hugging Face Hub, ces informations sont stockées dans le fichier *README.md* de chaque référentiel d'ensembles de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : + +1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le Hugging Face Hub et garantissent que votre ensemble de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un ensemble de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface : + +
+L'interface `datasets-tagging`. +
+ +2. Lisez le [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création de cartes d'ensemble de données informatives et utilisez-le comme modèle. + +Vous pouvez créer le fichier *README.md* directement sur le Hub, et vous pouvez trouver un modèle de carte d'ensemble de données dans le référentiel d'ensemble de données `lewtun/github-issues`. Une capture d'écran de la carte de jeu de données remplie est illustrée ci-dessous. + +
+Une carte de jeu de données. +
+ + + +* Fichier README.md* pour votre ensemble de données de problèmes GitHub. + + + +C'est ça! Nous avons vu dans cette section que la création d'un bon ensemble de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouvel ensemble de données pour créer un moteur de recherche sémantique avec 🤗 Deatasets qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. + + + +✏️ **Essayez-le !** Suivez les étapes que nous avons suivies dans cette section pour créer un ensemble de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 Datasets, bien sûr !). Pour obtenir des points bonus, ajustez un classificateur multilabel pour prédire les balises présentes dans le champ "labels". + + + + diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx new file mode 100644 index 000000000..fb055ccb8 --- /dev/null +++ b/chapters/fr/chapter5/6.mdx @@ -0,0 +1,530 @@ + + +# Recherche sémantique avec FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans [section 5](/course/chapter5/5), nous avons créé un ensemble de données de problèmes et de commentaires GitHub à partir du référentiel 🤗 Datasets. Dans cette section, nous utiliserons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! + + + +## Utilisation des représentations vectorielles continues pour la recherche sémantique + +Comme nous l'avons vu dans le [Chapitre 1](/course/chapter1), les modèles de langage basés sur Transformer représentent chaque jeton dans une étendue de texte sous la forme d'un _vecteur d'intégration_. Il s'avère que l'on peut "regrouper" les incorporations individuelles pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces intégrations peuvent ensuite être utilisées pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque intégration et en renvoyant les documents avec le plus grand chevauchement. + +Dans cette section, nous utiliserons les incorporations pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents. + +
+Recherche sémantique. + +
+ +## Chargement et préparation du jeu de données + +La première chose que nous devons faire est de télécharger notre ensemble de données de problèmes GitHub, alors utilisons la bibliothèque 🤗 Hub pour résoudre l'URL où notre fichier est stocké sur le Hugging Face Hub : + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Avec l'URL stockée dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/chapter5/2) : + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Ici, nous avons spécifié la division `train` par défaut dans `load_dataset()`, de sorte qu'elle renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les demandes d'extraction, car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre ensemble de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Nous pouvons voir qu'il y a beaucoup de colonnes dans notre ensemble de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Pour créer nos intégrations, nous ajouterons à chaque commentaire le titre et le corps du problème, car ces champs contiennent souvent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons "éclater" la colonne afin que chaque ligne se compose d'un tuple `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format "DataFrame" de Pandas : + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Lorsque nous décomposons `df`, nous nous attendons à obtenir une ligne pour chacun de ces commentaires. Vérifions si c'est le cas : + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne "commentaires" contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! + + + + +✏️ **Essayez-le !** Voyez si vous pouvez utiliser `Dataset.map()` pour exploser la colonne `comments` de `issues_dataset` _sans_ recourir à l'utilisation de Pandas. C'est un peu délicat; vous pourriez trouver la section ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentation 🤗 Datasets utile pour cette tâche. + + + +Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts, qui incluent généralement des éléments tels que "cc @lewtun" ou "Merci !" qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre, mais environ 15 mots semblent être un bon début : + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Après avoir un peu nettoyé notre ensemble de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne "texte". Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Nous sommes enfin prêts à créer des embeddings ! Nous allons jeter un coup d'oeil. + +## Création d'incorporations de texte + +Nous avons vu dans [Chapitre 2](/course/chapter2) que nous pouvons obtenir des incorporations de jetons en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un point de contrôle approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d'incorporations. Comme décrit dans la [documentation] de la bibliothèque (https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _asymmetric recherche sémantique_ car nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, comme un commentaire sur un problème. Le [tableau de présentation des modèles] (https://www.sbert.net/docs/pretrained_models.html#model-overview) pratique de la documentation indique que le point de contrôle `multi-qa-mpnet-base-dot-v1` a le meilleures performances pour la recherche sémantique, nous l'utiliserons donc pour notre application. Nous allons également charger le tokenizer en utilisant le même point de contrôle : + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Pour accélérer le processus d'intégration, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch, donc définir `from_pt=True` les convertira automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un framework à l'autre dans 🤗 Transformers ! + +{/if} + +Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique, nous devons donc "regrouper" ou faire la moyenne de nos incorporations de jetons d'une manière ou d'une autre. Une approche populaire consiste à effectuer un * regroupement CLS * sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le jeton spécial `[CLS]`. La fonction suivante fait l'affaire pour nous : + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Ensuite, nous allons créer une fonction d'assistance qui va tokeniser une liste de documents, placer les tenseurs sur le GPU, les alimenter au modèle et enfin appliquer le regroupement CLS aux sorties : + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui fournissant la première entrée de texte dans notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Super, nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions ! Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus, créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui fournissant la première entrée de texte dans notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Super, nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions ! Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus, créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + + +Notez que nous avons converti les intégrations en tableaux NumPy -- c'est parce que 🤗 Datasets nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. + +## Utilisation de FAISS pour une recherche de similarité efficace + +Maintenant que nous avons un ensemble de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 Datasets appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de Facebook AI Similarity Search) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. + +L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 Datasets est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche de voisin le plus proche avec la fonction `Dataset.get_nearest_examples()`. Testons cela en intégrant d'abord une question comme suit : + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête, que nous pouvons comparer à l'ensemble du corpus pour trouver les plongements les plus similaires : + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +La fonction `Dataset.get_nearest_examples()` renvoie un tuple de scores qui classent le chevauchement entre la requête et le document, et un ensemble correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Pas mal! Notre deuxième résultat semble correspondre à la requête. + + + +✏️ **Essayez-le !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. + + \ No newline at end of file diff --git a/chapters/fr/chapter5/7.mdx b/chapters/fr/chapter5/7.mdx new file mode 100644 index 000000000..a4da397e5 --- /dev/null +++ b/chapters/fr/chapter5/7.mdx @@ -0,0 +1,11 @@ +# 🤗 Datasets, vérifié ! + +Eh bien, ce fut toute une visite de la 🤗 Datasets -- félicitations pour être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : + +- Chargez des ensembles de données de n'importe où, que ce soit le Hugging Face Hub, votre ordinateur portable ou un serveur distant de votre entreprise. +- Démêlez vos données en utilisant un mélange des fonctions `Dataset.map()` et `Dataset.filter()`. +- Basculez rapidement entre les formats de données comme Pandas et NumPy en utilisant `Dataset.set_format()`. +- Créez votre propre ensemble de données et transférez-le vers le Hugging Face Hub. +- Intégrez vos documents à l'aide d'un modèle Transformer et créez un moteur de recherche sémantique à l'aide de FAISS. + +Dans le [Chapitre 7](/course/chapter7), nous mettrons tout cela à profit en approfondissant les principales tâches de la PNL pour lesquelles les modèles Transformer sont parfaits. Avant de vous lancer, mettez à l'épreuve vos connaissances sur 🤗 Datasets avec un quiz rapide ! diff --git a/chapters/fr/chapter5/8.mdx b/chapters/fr/chapter5/8.mdx new file mode 100644 index 000000000..19bb1d08a --- /dev/null +++ b/chapters/fr/chapter5/8.mdx @@ -0,0 +1,226 @@ + + +# Quiz de fin de chapitre + +Ce chapitre a couvert beaucoup de terrain! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails ; les prochains chapitres vous aideront à comprendre comment les choses fonctionnent sous le capot. + +Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. + +### 1. La fonction `load_dataset()` dans 🤗 Datasets vous permet de charger un jeu de données depuis lequel des emplacements suivants ? + +data_files de load_dataset() pour charger les jeux de données locaux.", + correct: true + }, + { + text: "Le hub du visage étreignant", + explain: "Correct! Vous pouvez charger des ensembles de données sur le Hub en fournissant l'ID de l'ensemble de données, par ex. load_dataset('emotion').", + correct: true + }, + { + text: "Un serveur distant", + explain: "Correct! Vous pouvez passer des URL à l'argument data_files de load_dataset() pour charger des fichiers distants.", + correct: true + }, + ]} +/> + +### 2. Supposons que vous chargiez l'une des tâches GLUE comme suit : + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? + +dataset.sample(50)", + explain: "Ceci est incorrect -- il n'y a pas de méthode Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Correct! Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord l'ensemble de données, puis sélectionnez les échantillons à partir de celui-ci.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Ceci est incorrect - bien que le code s'exécute, il ne mélange que les 50 premiers éléments de l'ensemble de données." + } + ]} +/> + +### 3. Supposons que vous disposiez d'un ensemble de données sur les animaux domestiques appelé "pets_dataset", qui comporte une colonne "name" indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer l'ensemble de données pour tous les animaux dont le nom commence par la lettre "L" ? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "Correct! L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Ceci est incorrect -- une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." + }, + { + text: "Créez une fonction comme def filter_names(x): return x['name'].startswith('L') et exécutez pets_dataset.filter(filter_names).", + explain: "Correct! Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", + correct: true + } + ]} +/> + +### 4. Qu'est-ce que la cartographie mémoire ? + + + +### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du mappage mémoire ? + + + +### 6. Pourquoi le code suivant échoue-t-il ? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Correct! Un IterableDataset est un générateur, pas un conteneur, vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", + correct: true + }, + { + text: "L'ensemble de données allocine n'a pas de division train.", + explain: "Ceci est incorrect - consultez la [fiche de l'ensemble de données allocine](https://huggingface.co/datasets/allocine) sur le Hub pour voir quelles divisions elle contient." + } + ]} +/> + +### 7. Parmi les avantages suivants, lesquels sont les principaux avantages de la création d'une fiche d'ensemble de données ? + + + + +### 8. Qu'est-ce que la recherche sémantique ? + + + +### 9. Pour la recherche sémantique asymétrique, vous avez généralement : + + + +### 10. Puis-je utiliser 🤗 Datasets pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? + +MNIST dataset sur le Hub pour un exemple de vision par ordinateur." + }, + { + text: "Oui", + explain: "Correct! Découvrez les développements passionnants avec la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", + correct : true + }, + ]} +/> diff --git a/chapters/ko/_toctree.yml b/chapters/ko/_toctree.yml new file mode 100644 index 000000000..fe4e110ef --- /dev/null +++ b/chapters/ko/_toctree.yml @@ -0,0 +1,29 @@ +- title: 0. 초기 설정 + sections: + - local: chapter0/1 + title: 강의 소개 + +- title: 1. 트랜스포머 모델 + sections: + - local: chapter1/1 + title: 단원 소개 + - local: chapter1/2 + title: 자연어 처리 + - local: chapter1/3 + title: 트랜스포머로 무엇을 할 수 있나요? + - local: chapter1/4 + title: 트랜스포머는 어떻게 동작하나요? + - local: chapter1/5 + title: 인코더 모델 + - local: chapter1/6 + title: 디코더 모델 + - local: chapter1/7 + title: 시퀀스-투-시퀀스 모델 + - local: chapter1/8 + title: 편향과 한계 + - local: chapter1/9 + title: 단원 정리 + - local: chapter1/10 + title: 단원 마무리 퀴즈 + quiz: 1 + \ No newline at end of file diff --git a/chapters/ko/chapter0/1.mdx b/chapters/ko/chapter0/1.mdx new file mode 100644 index 000000000..0a8dd7735 --- /dev/null +++ b/chapters/ko/chapter0/1.mdx @@ -0,0 +1,110 @@ +# 강의 소개 + +Hugging Face 강의에 오신 여러분들 환영합니다! 이번 강의 소개에서는 작업 환경 설정에 대해 안내드리겠습니다. 방금 막 이번 과정을 시작하셨다면 먼저 [Chapter 1](/course/chapter1) 내용을 살펴보고 돌아오신 뒤, 환경을 설정하여 코드를 직접 실행해보시길 추천드립니다. + +이번 과정에서 사용할 모든 라이브러리는 파이썬 패키지를 통해 사용할 수 있으므로 여기서는 파이썬 환경 설정 방법 및 필요한 라이브러리 설치 방법을 보여드리겠습니다. + +작업 환경 설정 방법으로 Colab 노트북을 이용한 방법과 파이썬 가상 환경을 이용한 방법, 두 가지를 다룰 것이고 둘 중 더 마음이 가는 방식을 자유롭게 선택하셔도 됩니다. 입문자의 경우 Colab 노트북을 이용하시길 강력하게 추천합니다. + +여기서 Windows 환경에 대해서는 다루지 않기 때문에 Windows에서 실행 중이시면 Colab 노트북을 이용해 아래 과정을 따라가 주시길 권장드립니다. Linux 혹은 macOS를 실행 중이시라면 어떤 방식을 택해도 무방합니다. + +대부분의 강의는 여러분이 Hugging Face 계정이 있다는 것을 전제로 하기 때문에 지금 바로 계정을 생성하시길 추천드립니다: [계정 생성하기](https://huggingface.co/join) + +## Google Colab 노트북 사용하기 + +Colab 노트북은 가장 쉬운 설정 방식입니다. 브라우저에 Colab 노트북을 켜고 바로 코딩을 시작하시면 됩니다! + +Colab에 익숙하지 않으시다면 [introduction](https://colab.research.google.com/notebooks/intro.ipynb) 링크를 따라 시작하시길 권장드립니다. Colab에서는 GPU, TPU와 같은 가속 하드웨어를 사용할 수 있으며 적은 양의 워크로드에 대해서는 무료입니다. + +Colab과 친숙해 지셨다면 새로운 노트북을 생성하여 아래와 같이 시작합니다: + +
+An empty colab notebook +
+ +다음으로, 이번 강의에서 사용할 라이브러리를 설치합니다. 설치에는 파이썬 패키지 관리자인 `pip` 를 사용하도록 하겠습니다. 노트북 파일에서는 시스템 명령어 앞에 `!` 를 붙여 실행시킬 수 있으므로, 아래와 같이 🤗 Transformers 라이브러리를 설치할 수 있습니다: + +``` +!pip install transformers +``` + +이제 파이썬 런타임에 패키지를 가져와 패키지가 제대로 설치되었는지 확인해보겠습니다: + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +위의 방식으로는 아주 가벼운 버전의 🤗 Transformers가 설치되고, 이는 PyTorch나 TensorFlow와 같은 특정 기계학습 프레임워크를 포함하지 않습니다. 하지만 본 강의에서는 이 라이브러리의 아주 다양한 기능들을 사용할 예정이므로, 아래의 명령어를 통해 대부분의 예제에 필요한 종속성(dependency)을 제공하는 개발 버전을 설치하시길 바랍니다: + +``` +!pip install transformers[sentencepiece] +``` + +설치에 시간이 조금 걸리지만 곧 강의를 위한 준비가 모두 끝납니다! + +## 파이썬 가상 환경 사용하기 + +파이썬 가상 환경 사용을 원하신다면 먼저 파이썬을 설치해야 합니다. 이 [가이드](https://realpython.com/installing-python/)를 따라 설치를 진행하실 수 있습니다. + +파이썬 설치가 완료되면 터미널에서 파이썬 명령어를 실행할 수 있습니다. 다음 단계로 넘어가기 전에, 다음과 같은 명령어를 실행하여 설치가 잘 되었는지 확인하세요: `python --version`. 이 때 시스템에 사용할 수 있는 파이썬 버전을 출력되어야 합니다. + +터미널에서 `python --version` 과 같은 파이썬 명령어를 실행하면, 명령어를 실행하는 프로그램을 시스템의 “메인(main)” 파이썬으로 생각해야 합니다. 이 메인 파이썬은 어떤 패키지도 설치하지 않은 상태로 유지하면서, 작업 중인 각 어플리케이션마다 별도의 환경을 생성하여 이용하는 것을 권장합니다. 이렇게 하면, 각 어플리케이션은 각각의 의존성 및 패키지를 갖게 되어 다른 어플리케이션과의 잠재적 호환성 문제를 피할 수 있습니다. + +파이썬에서 이는 [*가상 환경*](https://docs.python.org/3/tutorial/venv.html)을 통해 완수됩니다. 가상 환경은 자체 포함 디렉토리 트리로, 각 트리는 어플리케이션에게 필요한 모든 패키지와 함께 특정 파이썬 버전에 대한 파이썬 설치를 포함합니다. 이러한 가상 환경을 생성하는 방법은 여러 툴을 통해 할 수 있지만, 여기서는 공식 파이썬 패키지인 `[venv](https://docs.python.org/3/library/venv.html#module-venv)` 를 통해 생성해 보겠습니다. + +먼저, 어플리케이션을 넣어줄 디렉토리를 생성합니다. 예를 들어, 홈 디렉토리의 *transformers-course*와 같은 이름의 디렉토리를 만들어 봅시다: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +디렉토리 내부에서, 파이썬 `venv` 모듈을 사용하여 가상 환경을 생성합니다: + +``` +python -m venv .env +``` + +원래 아무것도 없던 빈 폴더에 *.env*라는 디렉토리가 생기게 됩니다: + +``` +ls -a +``` + +```out +. .. .env +``` + +`activate` 스크립트를 통해 가상 환경으로 접속할 수 있고, `deactivate` 를 통해 가상 환경 밖으로 나올 수 있습니다: + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` + +환경이 제대로 활성화 되었는지 `which python` 명령어를 실행하여 확인해 봅시다. 아래와 같이 가상 환경을 보여준다면 제대로 활성화가 것입니다! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### 의존성(dependencies) 설치하기 + +Google Colab 사용법에서와 마찬가지로 다음 단계로 넘어가기 위해 패키지를 설치해야 합니다. 여기서도, `pip` 패키지 관리자를 통해 🤗 Transformers 개발 버전을 설치할 수 있습니다: + +``` +pip install "transformers[sentencepiece]" +``` + +이제 모든 환경 설정을 마치고 시작할 준비가 되었습니다! diff --git a/chapters/ko/chapter1/1.mdx b/chapters/ko/chapter1/1.mdx new file mode 100644 index 000000000..3bfb81f49 --- /dev/null +++ b/chapters/ko/chapter1/1.mdx @@ -0,0 +1,52 @@ +# 단원 소개 + +## 🤗 강의 수강생 여러분 환영합니다! + + + +이번 강의에서는 [Hugging Face](https://huggingface.co/) 환경의 라이브러리([🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), [🤗 Accelerate](https://github.com/huggingface/accelerate))와 [Hugging Face Hub](https://huggingface.co/models) 를 이용해 자연어 처리(NLP)에 대해 배워보겠습니다. (무료 강의에 광고도 없는건 비밀입니다!) + +## 무엇을 배우나요? + +강의 개요 훑어보기: + +
+Brief overview of the chapters of the course. + +
+ +- 챕터 1~4에서는 🤗 Transformers 라이브러리의 핵심 개념에 대해 소개합니다. 이 부분을 마치면 트랜스포머 모델의 동작 원리를 이해하실 수 있고, [Hugging Face Hub](https://huggingface.co/models)에서 모델을 사용하여 데이터셋으로 미세 조정(fine-tune)한 후 Hub에 모델을 공유하는 방법까지 터득하게 될 것입니다! +- 챕터 5~8은 본격적으로 고전 NLP 업무를 수행하기 앞서, 🤗 Datasets와 🤗 Tokenizers의 기초에 대해 알아봅니다. 이 부분을 모두 학습하시면 일반적인 NLP 문제를 스스로 해낼 수 있게 됩니다. +- 챕터 9~12에서는 트랜스포머 모델이 NLP 문제를 넘어, 음성 처리 및 컴퓨터 비전에 어떻게 활용되는지 탐구합니다. 이 과정에서 모델 데모를 구축하고 공유하는 방법과 이를 프로덕션 환경에 최적화하는 방법을 공부합니다. 이러한 과정을 거쳐서, 여러분들은 거의 모든 기계 학습(머신 러닝) 문제에 🤗 Transformers를 적용할 준비를 갖추게 됩니다! + +이번 강의는: + +* 파이썬에 대한 기초 지식이 필요합니다 +* [DeepLearning.AI](https://www.deeplearning.ai/) 의 프로그램이나 [fast.ai's](https://www.fast.ai/) [Practical Deep Learning for Coders](https://course.fast.ai/) 와 같은 딥러닝에 대한 기초 강의를 듣고 수강하면 더욱 효과적입니다 +* [PyTorch](https://pytorch.org/) , [TensorFlow](https://www.tensorflow.org/) 에 대한 선수 지식이 필요하지는 않지만, 이에 익숙하시다면 도움이 될 것입니다 + +본 강의를 모두 수강한 후, DeepLearning.AI의 [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh)을 학습하시길 권장드립니다. 해당 과정에서는 Naive Bayes, LSTM과 같은 알아두면 너무나 유용한 더 넓은 범위의 전통 NLP 모델에 대해 학습할 수 있습니다! + +## 우리가 누구일까요? + +저자 소개: + +**Matthew Carrigan**은 Hugging Face의 머신 러닝 엔지니어입니다. 현재 아일랜드 더블린에 살고 있으며, 이전에는 [Parse.ly](http://parse.ly/) 에서 ML 엔지니어로, 그 전에는 Trinity Collge Dublin에서 박사 과정 이후 연구원으로 근무했습니다. 사람이 기존 인공지능 아키텍쳐를 확장하여 사람 수준에는 도달하지 못할거라고 생각하지만, 그럼에도 불멸 로봇(immortality robot)에 대해 큰 기대를 갖고 있습니다. + +**Lysandre Debut**는 Hugging Face의 머신 러닝 엔지니어이며 초창기부터 🤗 Transformers 라이브러리 작업을 함께 했습니다. 아주 사용하기 쉬운 API를 개발하여 모두가 NLP를 쉽게 사용할 수 있도록 하는 목표를 갖고 있습니다. + +**Sylvain Gugger**는 Hugging Face의 리서치 엔지니어로 🤗 Transformers 라이브러리의 주요 관리자 중 한명입니다. 이전에 [fast.ai](http://fast.ai/) 에서 리서치 사이언티스트로 있었으며 Jeremy Howard와 함께 *[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)* 를 저술했습니다. 적은 리소스에서도 모델이 빠르게 학습되도록 기술을 디자인하고 개선하여 딥러닝에 보다 쉽게 접근할 수 있도록 하는 것을 리서치의 가장 큰 목표로 삼고 있습니다. + +**Merve Noyan**은 Hugging Face의 개발자 애드보케이트로, 모두에게 평등한 민주적인 머신 러닝 생태계를 만드는 목표를 갖고 있으며, 개발툴 작업 및 주변 컨텐츠 구축 작업을 담당하고 있습니다. + +**Lucile Saulnier**은 Hugging Face의 ML 엔지니어로 오픈 소스 툴 사용에 대한 개발 및 지원을 담당합니다. 자연어 처리 분야에서 협업 학습, BigScience등과 같은 다양한 리서치 프로젝트에도 활발히 참여하고 있습니다. + +**Lewis Tunstall**는 Hugging Face의 ML 엔지니어로 오픈 소스 툴을 개발하여 더 많은 커뮤니티에 상용화되도록 하는 데에 초점을 맞추고 있습니다. 곧 출간되는 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)의 공저자이기도 합니다. + +**Leandro von Werra**는 Hugging Face 오픈소스 팀의 머신 러닝 엔지니어이자 곧 출간될 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)의 공동 저자입니다. 모든 머신 러닝 스택에서의 작업을 통해 수 년간 NLP 프로젝트를 프로덕션으로 들여온 경력자입니다. + +시작할 준비가 되셨나요? 이번 챕터에서 다룰 내용은 다음과 같습니다: + +- 텍스트 생성 및 분류와 같은 NLP 문제를 푸는 `pipeline()` 함수 사용법 +- 트랜스포머 모델 구조 +- 인코더(encoder), 디코더(decoder), 인코더-디코더(encoder-decoder)의 구조와 용례 \ No newline at end of file diff --git a/chapters/ko/chapter1/10.mdx b/chapters/ko/chapter1/10.mdx new file mode 100644 index 000000000..a05bd4c3b --- /dev/null +++ b/chapters/ko/chapter1/10.mdx @@ -0,0 +1,254 @@ + + +# 단원 마무리 퀴즈 + +이번 챕터에서는 정말 많은 내용들을 다뤘습니다! 그러니 모든 세부 사항을 다 이해하지 못했다고 해서 좌절하지 마세요. 다음 챕터에서 다루는 내용은 내부 작동 방식을 이해하는 데에 도움이 될거에요. + +그래도 우선, 이번 챕터에서 배운 내용에 대해 확인해보는 시간을 갖도록 하겠습니다! + + +### 1. Hub에서 `roberta-large-mnli` 체크포인트를 검색해 보세요. 이 모델은 어떤 작업을 수행하나요? + + +roberta-large-mnli page." + }, + { + text: "텍스트 분류", + explain: "더 정확하게 말하면, 이 모델은 두 문장이 논리적으로 타당한지 세 가지 레이블(모순, 함의, 중립)로 분류합니다. 이러한 문제를 자연어 추론(natural language inference)이라고 부릅니다.", + correct: true + }, + { + text: "텍스트 생성", + explain: "이 페이지를 다시 확인하세요 roberta-large-mnli page." + } + ]} +/> + +### 2. 다음 코드는 무엇을 반환하나요? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis 파이프라인에 대한 설명입니다." + }, + { + text: "이 문장을 완성할, 생성 텍스트를 반환합니다.", + explain: "오답입니다 — 이는 text-generation 파이프라인에 대한 설명입니다.", + }, + { + text: "사람, 기관, 장소 등을 나타내는 단어들을 반환합니다.", + explain: "이 뿐만 아니라, grouped_entities=True를 사용해 \"Hugging Face\"와 같이 같은 개체에 해당하는 단어들을 그룹화해줍니다.", + correct: true + } + ]} +/> + +### 3. 다음 예제 코드에서 ... 대신 무엇이 들어가야 할까요? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + +", + explain: "오답입니다. 여기 bert-base-cased 모델 카드를 보시고 다시 도전해보세요." + }, + { + text: "[MASK]", + explain: "정답! 이 모델의 마스크 토큰은 [MASK]입니다.", + correct: true + }, + { + text: "man", + explain: "오답입니다. 이 파이프라인은 마스킹된 단어를 채워야하기니까 어딘가에는 마스크 토큰이 있어야겠죠?" + } + ]} +/> + +### 4. 다음 코드가 실행되지 않는 이유는 무엇일까요? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "한 문장이 아니라, 여러 문장을 파이프라인에 넣어주어야 합니다.", + explain: "틀렸지만, 다른 파이프라인과 마찬가지로 제대로 사용한다면 물론 여러 리스트의 문장도 입력으로 넣어줄 수 있습니다." + }, + { + text: "늘 그렇듯 🤗 Transformers 라이브러리가 또 고장난거 아닌가요?", + explain: "못 들은 걸로 하겠습니다!" + }, + { + text: "위의 문장은 너무 짧아서, 더 긴 문장을 입력해야 합니다.", + explain: "오답입니다. 매우 긴 텍스트는 파이프라인에서 처리할 때 잘리게 되는 것을 명심하세요." + } + ]} +/> + +### 5. "전이 학습(transfer learning)"이란 무엇을 의미하나요? + + + +### 6. 언어 모델은 일반적으로 사전 학습시에 레이블을 필요로 하지 않습니다. 이 문장은 참일까요 거짓일까요? + + +자가 지도(self-supervised) 방식입니다. 이는 다음 단어 예측 혹은 마스킹 된 단어 채우기 등과 같이 입력으로부터 자동으로 레이블을 생성하는 것을 의미합니다.", + correct: true + }, + { + text: "거짓", + explain: "정답이 아닙니다." + } + ]} +/> + +### 7. 다음 중 “모델(model)”, “구조(architecture)”, “가중치(weights)”에 대해 가장 잘 설명한 것을 고르세요. + + + + +### 8. 다음 중 어떤 모델이 텍스트를 생성하여 프롬프트(prompt)를 완성시키는 데에 가장 적합할까요? + + + +### 9. 다음 중 어떤 모델이 텍스트 요약에 가장 적합할까요? + + + +### 10. 다음 중 어떤 모델이 입력 텍스트를 특정 레이블로 분류하는 데에 가장 적합할까요? + + + +### 11. 다음 중 모델이 편향성(bias)을 갖게 되는 데에 가장 가능성 있는 원인을 모두 고르세요. + + diff --git a/chapters/ko/chapter1/2.mdx b/chapters/ko/chapter1/2.mdx new file mode 100644 index 000000000..ca5d62dc8 --- /dev/null +++ b/chapters/ko/chapter1/2.mdx @@ -0,0 +1,21 @@ +# 자연어 처리(Natural Language Processing) + +트랜스포머 모델을 공부하기에 앞서 자연어 처리(NLP)가 무엇인지, 그리고 왜 NLP가 중요한지 빠르고 간단하게 살펴보겠습니다. + +## NLP가 무엇인가요? + +NLP(Natural Language Processing)란 사람의 언어와 관련된 모든 것을 이해하는 데에 중점을 둔 언어학 및 기계 학습(머신 러닝) 분야를 말합니다. NLP의 목적은 단순히 하나의 개별 단어를 이해하는 것을 넘어, 해당 단어들의 문맥을 이해하는 것입니다. + +아래는 가장 일반적인 NLP 작업과 그 예시입니다: + +- **전체 문장 분류**: 리뷰에 드러난 감정 파악하기, 스팸 메일 분류하기, 문장이 문법적으로 올바른지 혹은 문장 쌍이 논리적으로 관련이 있는지 없는지 결정하기 +- **문장 내 단어 분류**: 문장 구성 성분(명사, 동사, 형용사 등) 혹은 개체명(사람, 장소, 기관) 식별하기 +- **텍스트 컨텐츠 생성**: 자동 생성 텍스트로 프롬프트 작성하기, 텍스트 내 마스킹 된 단어의 빈칸 채우기 +- **텍스트 안에서 정답 추출하기**: 지문과 질의가 주어질 때 지문에 주어진 정보를 이용해 질의에 대한 정답 추출하기 +- **입력 텍스트로부터 새로운 문장 생성하기**: 입력 텍스트를 다른 언어로 번역하거나, 요약하기 + +하지만 NLP는 위와 같은 텍스트 처리에만 제한되지 않습니다. NLP에서는 오디오 샘플의 스크립트 생성 및 이미지의 설명문 생성과 같이 음성 인식과 컴퓨터 비전 분야에서의 까다로운 문제 또한 다룹니다. + +## 왜 NLP가 어렵나요? + +컴퓨터와 사람은 서로 정보를 처리하는 방식이 다릅니다. 이를테면, “나는 배고파”라는 문장을 읽을 때 우리는 바로 그 의미를 이해할 수 있습니다. 마찬가지로 사람은 “나는 배고파”, “나 슬퍼”와 같은 문장 쌍이 주어질 때, 두 문장이 얼마나 유사한지 쉽게 판단할 수 있습니다. 그러나, 기계 학습 모델은 사람만큼 이를 쉽게 할 수 없습니다. 우선 모델이 텍스트를 학습할 수 있도록 텍스트가 처리 과정을 거쳐야 하는데, 사람의 언어 체계는 매우 복잡하기 때문에 이러한 처리가 어떻게 이루어져야 하는지 면밀히 고민해야 합니다. 따라서 텍스트 표현 방법과 관련한 수많은 연구가 진행되어 왔고, 다음 챕터에서 그 중 몇 가지 방법들을 소개해드리겠습니다. \ No newline at end of file diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx new file mode 100644 index 000000000..f32892430 --- /dev/null +++ b/chapters/ko/chapter1/3.mdx @@ -0,0 +1,328 @@ +# 트랜스포머로 무엇을 할 수 있나요? + + + +이번 장에서는 트랜스포머(Transformer) 모델을 사용해 무엇을 할 수 있는지 같이 살펴보고, 🤗 Transformers 라이브러리 툴의 첫 사용을 `pipeline()` 함수와 함께 시작하겠습니다. + + +👀 오른쪽 상단에 Open in Colab 버튼이 보이시나요? 버튼을 클릭하면 이번 장에서 사용한 모든 코드 샘플들을 Google Colab notebook을 통해 열 수 있습니다. 이런 버튼을 예제 코드를 포함하는 모든 단원에서 발견하실 수 있습니다. + +로컬 환경에서 예제 코드를 실행하려면 setup을 살펴보세요. + + +## 트랜스포머는 어디에나 있어요! + +트랜스포머 모델은 이전 단원에서 언급한 작업과 같은 모든 NLP 문제를 해결하기 위해 사용됩니다. 아래와 같이 Hugging Face와 트랜스포머 모델을 이용하고 다시 모델을 공유하여 커뮤니티에 기여하는 많은 기업과 기관이 있습니다: + +Companies using Hugging Face + +[🤗 Transformers 라이브러리](https://github.com/huggingface/transformers)는 이렇게 공유한 모델을 사용하고 구축하는 기능들을 제공합니다. [Model Hub](https://huggingface.co/models)에서는 모두가 다운로드 받아 쓸 수 있는 수 천 개의 사전 학습된 모델들이 여러분을 기다리고 있습니다. 여러분만의 모델을 Hub에 업로드하는 것 또한 가능합니다! + + +⚠️ The Hugging Face Hub에는 트랜스포머 모델만 있지 않아요. 누구든지 어떠한 종류의 모델이나 데이터를 공유할 수 있습니다! Create a huggingface.co 링크에서 계정을 만들고 모든 기능을 사용해보세요! + + +트랜스포머 모델 안에서 무슨 일이 벌어지는지 알아보기 전에, 트랜스포머가 NLP 문제 해결에 어떻게 사용되는지 몇 가지 흥미로운 예시들을 살펴보겠습니다. + +## 파이프라인으로 작업하기 + + + +🤗 Transformers 라이브러리의 가장 기본 객체는 `pipeline()` 함수입니다. 이 함수는 모델에 있어서 필수 과정인 전처리와 후처리 과정을 모델과 연결하고, 우리가 바로 어떠한 텍스트 입력을 넣든 원하는 답을 얻을 수 있도록 합니다: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +아래와 같이 여러 문장을 함께 넣을 수도 있습니다! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +기본적으로 이 파이프라인은 영어 감정 분석에 미세 조정(fine-tune)된 사전 학습 모델을 선택하여 넣게 됩니다. 여기서 `classifier` 객체를 생성할 때 모델이 다운로드 되며 캐싱(caching)이 이루어지때문에, 재실행 시에는 캐싱된 모델을 사용하게 되어 모델을 다시 다운로드 하지 않습니다. + +텍스트를 파이프라인에 넣을 때 다음과 같은 세 가지 주요 과정을 거칩니다: + +1. 텍스트가 모델이 이해할 수 있는 형태로 전처리 과정을 거칩니다. +2. 전처리된 입력이 모델 입력으로 들어갑니다. +3. 모델의 예측값이 후처리를 거쳐, 사람이 이해할 수 있는 형태로 반환됩니다. + + +현재까지 사용할 수 있는 파이프라인([available pipelines](https://huggingface.co/transformers/main_classes/pipelines.html))은 다음과 같습니다: + +- `feature-extraction` : 특징 추출 (텍스트에 대한 벡터 표현 추출) +- `fill-mask` : 마스크 채우기 +- `ner` : 개체명 인식 (named entity recognition) +- `question-answering` : 질의 응답 +- `sentiment-analysis` : 감정 분석 +- `summarization` : 요약 +- `text-generation` : 텍스트 생성 +- `translation` : 번역 +- `zero-shot-classification` : 제로샷 분류 + +이 중 몇 가지를 같이 살펴보도록 하겠습니다! + +## 제로샷 분류(Zero-shot classification) + +레이블이 없는 텍스트를 분류하는 더 까다로운 과제부터 시작하겠습니다. 텍스트에 레이블을 다는 것은 시간이 많이 소요되고 도메인 지식이 필요하기 때문에 이러한 작업은 실제 프로젝트에서 아주 흔한 상황입니다. 이러한 상황에서 `zero-shot-classification` 파이프라인은 매우 유용합니다. 제로샷 파이프라인은 사전 학습된 모델에 의존하지 않고도 분류 작업에 사용할 레이블을 특정할 수 있도록 합니다. 위의 예시에서 모델이 긍정(positive)과 부정(negative)의 두 레이블을 분류하는 샘플을 살펴보았는데, 제로샷 파이프라인을 통해서는 어떠한 레이블 세트에 대해서도 분류 작업을 수행할 수 있습니다. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +이러한 파이프라인이 제로샷(zero-shot)이라 불리는 이유는 여러분의 데이터에 맞춰 미세 조정(fine-tune)하지 않고도 바로 작업에 사용할 수 있기 때문입니다. 제로샷은 여러분이 원하는 어떠한 분류 레이블에 대해서도 확률 점수를 즉시 반환합니다. + + + +✏️ **직접 해보기!** 여러분이 직접 작성한 시퀀스와 레이블을 사용해 모델이 어떻게 동작하는지 확인해보세요. + + + + +## 텍스트 생성(Text generation) + +지금부터 파이프라인을 사용해 텍스트를 생성하는 방법을 알아보겠습니다. 여기서의 핵심은 프롬트를 모델에 제공하면 모델이 나머지 텍스트를 생성하여 이를 자동으로 완성하는 것입니다. 이는 스마트폰의 텍스트 자동 완성 기능과 유사합니다. 텍스트 생성에는 랜덤하게 결과를 생성하는 과정이 포함되어 있어서 여러분이 아래와 같이 동일하게 입력을 넣어도 매번 다른 결과가 나올 수 있습니다. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +`num_return_sequences`라는 인자(argument)를 통해 몇 개의 서로 다른 출력 결과를 생성할지 정할 수 있고, `max_length` 인자를 통해 출력 텍스트의 총 길이를 설정할 수 있습니다. + + + +✏️ **직접 해보기!** `num_return_sequences` 와 `max_length` 인자를 설정해 15개의 단어를 가진 서로 다른 두 개의 문장을 출력해보세요. + + + + +## 파이프라인에 Hub의 모델 적용하기 + +지금까지 예제들은 해당 작업에 대해 기본 모델들을 사용했지만, 특정 모델을 Hub에서 선택해 텍스트 생성과 같은 특정 작업에 대한 파이프라인에서도 사용할 수 있습니다. [Model Hub](https://huggingface.co/models) 페이지의 화면 왼쪽에 태그를 클릭하여 태그명에 해당하는 작업을 지원하는 모델을 확인할 수 있습니다. 이 때, [다음](https://huggingface.co/models?pipeline_tag=text-generation)과 같은 페이지로 이동하게 됩니다. + +함께 [`distilgpt2`](https://huggingface.co/distilgpt2) 모델을 사용해보겠습니다! 위 예제에서 사용한 파이프라인에서 모델을 아래와 같이 로드 할 수 있습니다: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +언어 태그를 클릭하여 해당 언어를 지원하고 생성하는 모델을 보다 구체적으로 검색할 수 있습니다. Model Hub에는 다양한 언어를 처리하는 다국어 모델의 체크포인트(모델의 파라미터 값) 또한 포함하고 있습니다. + +모델을 클릭하면 온라인상에서 바로 사용 가능한 위젯을 확인할 수 있고, 이를 통해 모델을 직접 다운로드 받기 전에 모델의 기능을 빠르게 테스트 해볼 수 있습니다. + + + +✏️ **직접 해보기!** 영어를 제외한 다른 언어를 생성하는 모델을 검색해보세요. 위젯을 자유롭게 다뤄 보시고 파이프라인을 사용해보세요! + + + +### 추론(Inference) API + +모든 모델들은 [Hugging Face 웹사이트](https://huggingface.co/)에서 제공하는 추론 API를 통해 여러분의 브라우저상에서 직접 테스트할 수 있습니다. 이 페이지 링크로 접속해 직접 작성하신 텍스트를 입력하시면 모델의 입력 데이터를 처리 결과를 확인할 수 있습니다. + +위젯을 구동하는 추론 API는 간편한 워크플로우를 가능하게 하는 유료 버전의 제품으로도 이용 가능합니다. 자세한 사항은 [가격 정책 페이지](https://huggingface.co/pricing)를 참고해주세요. + +## 마스크 채우기(Mask filling) + +다음으로 사용해볼 파이프라인은 마스크 채우기(`fill-mask`)입니다. 이 작업의 핵심 아이디어는 주어진 텍스트의 빈칸을 채우기입니다: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +상위 몇 개의 높은 확률을 띠는 토큰을 출력할지 `top_k` 인자를 통해 조절합니다. 여기서 모델이 특이한 `` 단어를 채우는 것을 주목하세요. 이를 마스크 토큰(mask token)이라고 부릅니다. 다른 마스크 채우기 모델들은 다른 형태의 마스크 토큰을 사용할 수 있기 때문에 다른 모델을 탐색할 때 항상 해당 모델의 마스크 단어가 무엇인지 확인해야 합니다. 위젯에서 사용되는 마스크 단어를 보고 이를 확인할 수 있습니다. + + + +✏️ **직접 해보기!** Hub에서 `bert-base-cased`를 검색해 보고 추론 API 위젯을 통해 모델의 마스크 단어가 무엇인지 확인해 보세요. 이 모델이 위의 `pipeline` 예제에서 사용한 문장에 대해 어떻게 예측하나요? + + + +## 개체명 인식(Named entity recognition) + +모델이 입력 텍스트의 어느 부분이 사람, 장소, 기관 등과 같은 개체에 해당하는지 찾는 작업을 개체명 인식(NER)이라고 합니다. 예제를 통해 확인해 봅시다: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +모델이 정확하게 Sylvain을 사람(PER)으로, Hugging Face를 기관(ORG)으로, Brooklyn을 장소(LOC)으로 예측했네요! + +파이프라인을 생성하는 함수에 `grouped_entities=True` 옵션을 전달하면 파이프라인이 같은 개체에 해당하는 문장 부분을 다시 그룹화합니다. 이 옵션을 설정하면 모델은 여러 단어로 구성된 단어임에도 “Hugging”과 “Face”를 하나의 기관으로 정확히 분류하게 됩니다. 다음 챕터에서도 확인하겠지만, 놀랍게도 전처리 과정에서 각 단어들은 더 작은 부분으로 쪼개집니다. 예를 들어 `Sylvain` 이라는 단어는 `S`, `##yl`, `##va`, `##in` 이렇게 네 조각으로 쪼개집니다. 후처리 단계에서 파이프라인은 이 조각들을 멋지게 재그룹화합니다. + + + +✏️ **직접 해보기!** Model Hub에서 영어 품사 태깅(part-of-speech tagging, 줄여서 POS)이 가능한 모델을 찾아보세요. 이 모델이 위의 예시 문장으로 무엇을 예측하나요? + + + +## 질의 응답(Question-answering) + +질의 응답(`question-answering`) 파이프라인은 주어진 지문(context)의 정보를 활용하여 질문에 대한 답을 하는 태스크입니다: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +본 파이프라인은 답을 새롭게 생성하는 방식이 아닌, 주어진 지문 내에서 정답을 추출하는 방식임을 잘 기억하세요. + +## 요약(Summarization) + +요약(Summarization)은 참조 텍스트의 모든(혹은 대부분의) 중요한 특징을 그대로 유지한 채 텍스트를 짧게 줄이는 작업입니다. 아래 예제를 확인하세요: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +텍스트 생성에서와 마찬가지로 `max_length` 와 `min_length` 를 미리 설정할 수 있습니다. + +## 번역(Translation) + +번역(Translation)의 경우 `"translation_en_to_fr"` 와 같이 태스크명에 해당하는 언어 쌍을 넣어준다면 기본 모델을 사용할 수 있지만, 더 간단하게 [Model Hub](https://huggingface.co/models)에서 원하는 모델을 선택해 사용하는 방법이 있습니다. 아래에서는 프랑스어에서 영어로 번역을 시도해 보겠습니다: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +텍스트 생성 및 요약에서와 마찬가지로, `max_length` 혹은 `min_length` 를 지정하여 결과를 출력할 수 있습니다. + + + +✏️ **직접 해보기!** 다른 언어를 지원하는 번역 모델을 검색해보고 위의 문장을 몇 가지 다른 언어들로 번역해 봅시다. + + + +지금까지 보여드린 파이프라인들은 대부분 특정 작업을 위해 프로그래밍된 데모용 파이프라인으로, 여러 태스크를 동시에 지원하지는 않습니다. 다음 단원에서는 `pipeline()` 함수 내부를 살펴보고 그 동작 방식을 직접 설계하는 방법에 대해 다루겠습니다. \ No newline at end of file diff --git a/chapters/ko/chapter1/4.mdx b/chapters/ko/chapter1/4.mdx new file mode 100644 index 000000000..96456fab5 --- /dev/null +++ b/chapters/ko/chapter1/4.mdx @@ -0,0 +1,171 @@ +# 트랜스포머는 어떻게 동작하나요? + +이번 단원에서는 트랜스포머(Transformer) 모델의 대략적인 구조를 알아보겠습니다. + +## 트랜스포머 역사 훑어보기 + +아래에 트랜스포머 모델의 (짧은) 역사 중 주요한 지점을 나타냈습니다: + +
+A brief chronology of Transformers models. + +
+ +[Transformer architecture](https://arxiv.org/abs/1706.03762)는 2017년 6월에 처음 소개되었습니다. 처음 연구 목적은 번역 작업 수행이었습니다. 이후로 다음과 같이 줄줄이 막강한 모델들이 세상에 등장했습니다: + +- **2018년 6월**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 최초로 사전 학습된 트랜스포머 모델로 다양한 NLP 작업에 미세 조정(fine-tune)되도록 사용되었으며 SOTA(state-of-the-art) 성능 달성 + +- **2018년 10월**: [BERT](https://arxiv.org/abs/1810.04805), 또 다른 거대 사전 학습 언어 모델로, 더 좋은 문장 요약을 위해 설계 (이번 단원과 다음 단원에서 더 자세히 알아봐요!) + +- **2019년 2월**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), 더 좋은 성능(그리고 더 큰) 버전의 GPT로, 윤리적 문제로 인해 즉시 공개되지 못하였음 + +- **2019년 10월**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT의 60% 빠른 속도에 메모리 측면에서 40% 가볍지만 BERT 성능의 97%를 재현하는 경량화 버전의 BERT + +- **2019년 10월**: [BART](https://arxiv.org/abs/1910.13461) 와 [T5](https://arxiv.org/abs/1910.10683), 동일한 구조의 (처음으로) 원본 트랜스포머 모델의 구조를 그대로 따른 두 거대 사전학습 언어 모델 + +- **2020년 5월**, [GPT-3](https://arxiv.org/abs/2005.14165), 미세 조정 없이도 (zero-shot learning이라 부름) 다양한 작업을 훌륭하게 수행하는 GPT-2의 더 큰 버전 + +위 리스트는 단순히 여러 종류의 트랜스포머 모델들 중 몇몇을 강조하기 위한 목록입니다. 넓은 관점에서 트랜스포머 모델은 아래와 같이 세 개의 카테고리로 묶을 수 있습니다: + +- GPT-계열 (*Auto-regressive* 트랜스포머 모델로도 불림) +- BERT-계열 (*Auto-encoding* 트랜스포머 모델로도 불림) +- BART/T5-계열 (*Sequence-to-sequence* 트랜스포머 모델로도 불림) + +추후에 각 계열들에 대해 더 자세히 살펴보도록 하겠습니다. + +## 트랜스포머는 언어 모델입니다 + +위에 언급한 모델(GPT, BERT, BART, T5 등)들은 *언어 모델(language model)*로서 학습 되었습니다. 다르게 말하면 이 모델들은 스스로 지도하는 방식으로 수많은 텍스트에 대해 학습된 모델들입니다. 이러한 자가 지도 학습(self-supervised learning)은 학습의 목적이 모델 입력으로부터 자동으로 계산되는 방식을 말합니다. 결국 사람이 데이터에 레이블을 달지 않아도 학습이 가능한 것입니다! + +이러한 종류의 모델은 학습한 언어에 대해 통계 기반의 방식으로 이해를 하지만, 이는 몇몇 실생활 문제에 적합하지 않습니다. 그렇기 때문에 사전 학습된 모델은 *전이 학습(transfer learning)*이라 불리는 과정을 거칩니다. 이 과정에서 모델은 특정 작업에 맞춰 지도적(supervised)인 방법, 즉 사람이 레이블을 추가한 데이터를 사용하는 방법으로 미세 조정(fine-tune)이 이루어지는 단계를 거칩니다. + +하나의 예시로 문장 내에서 이전 *n*개의 단어를 읽고 다음에 올 단어를 에측하는 문제를 들 수 있습니다. 이를 과거와 현재의 입력 정보를 이용하는 방식(미래에 올 입력 정보는 이용하지 않습니다)이기 때문에 *인과적 언어 모델링(causal language modeling)*이라고 부릅니다. + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +다른 예시로 *마스크 언어 모델링(masked language modeling)*을 들 수 있습니다. 여기서 모델은 문장 내에 마스킹 된 단어를 예측합니다. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## 트랜스포머는 거대 모델입니다 + +(DistilBERT와 같은 몇몇 예외를 제외하고) 모델의 성능을 향상시키는 일반적인 전략은 사전 학습에 사용하는 "모델과 데이터의 크기를 늘리기" 입니다. + +
+Number of parameters of recent Transformers models +
+ +불행히도 아주 아주 큰 모델의 경우, 학습을 위해 어마무시한 양의 데이터를 필요로 하여 시간과 컴퓨팅 리소스 관점에서 비용이 매우 많이 듭니다. 아래 그래프에서 볼 수 있듯이 이는 환경 오염 문제로 이어지기도 합니다. + +
+The carbon footprint of a large language model. + +
+ + + +위 비디오는 사전 학습 과정이 환경에 끼치는 부정적 영향을 꾸준히 줄이기 위해 노력하는 한 팀의 (초거대) 언어 모델 프로젝트를 소개합니다. 최적의 하이퍼파라미터를 찾기 위한 수많은 시도에는 아직 많은 여정이 남아 있습니다. + +매번 리서치 팀, 학생 기관, 기업 등등이 밑바닥부터 모델을 학습시킨다고 생각해보세요. 이는 결국 정말 어마어마한 양의 글로벌 비용으로 이어질 것입니다! + +이제 왜 언어 모델을 공유하는 것이 중요한지 아시겠나요? 학습 가중치를 공유하고, 이미 학습시킨 가중치에 이를 차곡차곡 쌓아 올리는 방식으로 커뮤니티의 컴퓨팅 비용과 탄소 발자국을 줄일 수 있기 때문입니다. + + +## 전이 학습(Transfer Learning) + + + +*사전 학습(Pretraining)*시에는 모델을 밑바닥부터 학습시키게 됩니다. 모델 가중치를 랜덤하게 초기화하고 사전 지식 없이 학습을 시작합니다. + +
+The pretraining of a language model is costly in both time and money. + +
+ +이러한 사전 학습 과정은 엄청난 양의 데이터로 이루어지기 때문에 방대한 양의 코퍼스 데이터와 수 주 씩 걸리는 학습 시간을 필요로 하기도 합니다. + +반면에 *미세 조정(Fine-tuning)*이란 모델이 모두 사전 학습을 마친 **이후에** 하는 학습을 의미합니다. 미세 조정을 하기 위해서 우선 사전 학습된 언어 모델을 가져오고, 여러분이 할 작업에 특화된 데이터셋을 이용해 추가 학습을 수행합니다. 잠깐만요, 그냥 한번에 최종 태스크에 맞춰 학습시키면 안될까요? 이렇게 하는 데에는 몇 가지 이유가 있습니다: + +- 사전 학습된 모델은 이미 미세 조정 데이터셋과 유사한 데이터셋으로 학습이 이루어진 상태입니다. 결국 모델이 사전 학습시에 얻은 지식(이를테면, NLP 문제에서 사전 학습된 모델이 얻게 되는 언어의 통계적 이해)을 십분 활용해 미세 조정에 활용할 수 있게 됩니다. +- 사전 학습된 모델은 이미 방대한 데이터로 학습되었기 떄문에 미세 조정에서는 원하는 성능을 얻기까지 적은 양의 데이터만 필요로 하게 됩니다. +- 위와 같은 이유로, 원하는 성능을 얻기까지 적은 시간과 리소스만 필요하게 됩니다. + +예시로, 영어로 사전 학습된 모델을 활용해 arXiv 코퍼스로 미세 조정하여 과학/연구 기반 모델을 만들 수 있습니다. 이 때 미세 조정에는 적은 양의 데이터만 필요할 것입니다. 여기서 사전 학습 과정에서 얻은 지식이 “전이”되었다고 하여 *전이 학습(transfer learning)*이라 부릅니다. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +모델의 미세 조정은 이렇게 시간, 데이터, 경제, 그리고 환경 비용 측면에서 훨씬 저렴합니다. 뿐만 아니라 전체 사전 학습 과정보다 제약이 적기 때문에 다양한 방식의 미세 조정을 쉽고 빠르게 반복할 수 있습니다. + +이렇듯 엄청나게 많은 데이터를 확보하지 않은 이상 미세 조정 방식이 밑바닥부터 학습시키는 방식보다 더 좋은 결과를 낼 수 있습니다. 따라서 항상 여러분의 지금 작업 내용과 최대한 비슷한 사전 학습 모델을 불러와 미세 조정하세요! + +## 일반적인 구조 + +이번 섹션에서는 전반적인 트랜스포머 모델의 구조에 대해 알아보겠습니다. 설령 몇몇 개념을 이해하지 못하게 되더라도 걱정하지 마세요. 추후에 각 구성 요소를 자세하게 다루는 섹션이 있으니까요. + + + +## 구조 소개 + +모델은 기본적으로 두 개의 블럭으로 이루어져 있습니다: + +* **인코더 (왼쪽)**: 인코더는 입력을 받아 입력(혹은 입력의 특징)을 구축합니다. 이는 인코더 모델이 입력을 이해하는 데에 최적화되어 있음을 의미합니다. +* **디코더 (오른쪽)**: 디코더는 인코더의 (특징) 표현과 또 다른 입력을 활용해 타겟 시퀀스를 생성합니다. 이는 디코더 모델이 출력을 생성하는 데에 최적화되어 있음을 의미합니다. + +
+Architecture of a Transformers models + +
+ +트랜스포머 모델의 각 파트는 태스크에 따라 다음과 같이 개별적으로 쓰일 수도 있습니다: + +* **인코더 모델(Encoder-only models)**: 문장 분류, 개체명 인식과 같이 입력에 대한 높은 이해를 요구하는 작업에 특화 +* **디코더 모델(Decoder-only models)**: 텍스트 생성과 같이 생성 관련 작업에 특화 +* **인코더-디코더(Encoder-decoder) 모델 또는 시퀀스-투-시퀀스 (Sequence-to-sequence) 모델**: 번역, 요약과 같이 입력을 필요로 하는 생성 관련 작업에 특화 + +다음 단원들에서 각각의 구조에 대해 더 깊게 살펴보도록 하겠습니다. + +## 어텐션 레이어(Attention layers) + +트랜스포머 모델의 중요한 특징은 *어텐션 레이어(attention layers)*라 불리는 특수한 레이어로 구성되었다는 점입니다. 사실, 트랜스포머 구조를 처음 소개한 논문 제목마저 ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)입니다! 어텐션 레이어의 디테일에 대해서는 추후 강의에서 자세히 다루겠지만, 우선은 이 레이어가 단어의 표현을 다룰 때 입력으로 넣어준 문장의 특정 단어에 어텐션(주의)을 기울이고 특정 단어는 무시하도록 알려준다는 사실을 기억하셔야 합니다. + +이러한 상황을 설명하기 위해 영어 텍스트를 프랑스어로 번역하는 작업을 생각해 보겠습니다. “You like this course”라는 입력이 주어지면 번역 모델은 “like”라는 단어를 알맞게 번역하기 위해 인접한 단어 “You”에도 주의를 기울입니다. 이는 프랑스어의 동사 “like”가 문맥에 따라 다른 의미로 해석되기 때문입니다. 그러나 문장의 나머지 단어들은 이 단어를 번역하는 데에 크게 유용하지 않습니다. 같은 맥락에서 “this”를 번역할 때, 이 단어는 연결 명사가 남성형이거나 여성형이냐에 따라 다르게 해석될 수 있기 때문에 모델은 “course”라는 단어에 주의를 기울입니다. 여기서도 문장의 나머지 다른 단어들은 “this”의 의미를 해석하는 데에 크게 중요하지 않습니다. 더 복잡한 문법의 문장에서 모델은 문장의 각 단어를 적절히 번역하기 위해 더 멀리 떨어진 단어들에 대해서도 주의를 기울여야 합니다. + +이와 동일한 개념이 자연어의 모든 문제에 적용됩니다. 단어는 그 자체로도 의미를 갖지만 문맥상 앞뒤의 다른 단어정보를 알게 되면 의미가 달라지기도 하죠. + +지금까지 어텐션 레이어가 무엇인지 알아보았으니 트랜스포머 구조에 대해 살펴보겠습니다. + +## 원본 구조 + +트랜스포머 구조는 처음에 번역을 위해 만들어졌습니다. 학습시에 인코더는 특정 언어의 입력 문장을 받고, 동시에 디코더는 타겟 언어로된 동일한 의미의 문장을 받습니다. 인코더에서 어텐션 레이어는 문장 내의 모든 단어를 활요할 수 있습니다(방금 보았듯이 주어진 단어의 번역은 문장의 전후를 살펴보아야 하니까요). 반면, 디코더는 순차적으로 작동하기 때문에 문장 내에서 이미 번역이 이루어진 부분에만 주의를 기울일 수 밖에 없습니다. 이로 인해 현재 생성(번역)되고 있는 단어의 앞에 단어들만 이용할 수 있죠. 예시로, 번역된 타겟의 처음 세 단어를 예측해 놨을 때, 이 결과를 디코더로 넘기면 디코더는 인코더로부터 받은 모든 입력 정보를 함께 이용해 네 번째 올 단어를 예측하는 것입니다. + +모델이 타겟 문장에 대한 액세스(access)가 있는 상황에서, 훈련 속도를 높이기 위해 디코더는 전체 타겟을 제공하지만 뒤에 올 단어들을 사용할 수 없습니다. (모델이 두 번째 올 단어를 예측하기 위해 두 번째 위치 단어를 접근할 수 있다면 예측이 의미없어지겠죠?) 예를 들어, 네 번째 단어를 예측할 때 어텐션 레이어는 1~3 번째 단어에만 액세스하도록 합니다. + +원래 처음 트랜스포머의 구조는 아래와 같이 왼쪽에는 인코더, 오른쪽에는 디코더가 있는 형태를 지닙니다: + +
+Architecture of a Transformers models + +
+ +디코더 블럭의 첫 번째 어텐션 레이어는 모든 이전의 디코더 입력에 대해 주의를 기울이지만, 두 번째 어텐션 레이어는 인코더의 출력만 사용하는 점을 주목하세요. 이로써 디코더는 전체 입력 문장에 액세스하여 현재 올 단어를 잘 예측하게 되는 것입니다. 이는 서로 다른 언어는 서로 다른 어순을 갖거나 문장의 뒷부분에 등장하는 문맥이 주어진 단어의 가장 적합한 번역을 결정할 수 있기 때문에 매우 유용합니다. + +*어텐션 마스크(attention mask)*는 또한 모델이 몇몇 특수 단어들에 어텐션을 주는 것을 막는 데에 사용됩니다. 그 예로, 여러 다른 길이의 문장들을 모아 배치를 구성할 때, 패딩이라고 불리는 특수한 단어를 넣어 문장들의 길이를 맞춰주는데 사용하는 경우 있습니다. + +## 구조(Architectures) vs. 체크포인트(Checkpoints) + +트랜스포머 모델을 본격적으로 공부하기 앞서, 모델(models)과 함께 *구조(architectures)*와 *체크포인트(checkpoints)*라는 단어를 들으시게 될겁니다. 이 셋은 아래와 같이 조금 다른 의미를 갖고 있습니다: + +* **구조(Architecture)**: 모델의 뼈대를 의미하는 용어로, 모델 내부의 각 레이어와 각 연산 작용들을 의미합니다. +* **체크포인트(Checkpoints)**: 주어진 구조(architecture)에 적용될 가중치들을 의미합니다. +* **모델(Model)**: 사실 모델은 “구조”나 “가중치”만큼 구체적이지 않은, 다소 뭉뚱그려 사용되는 용어입니다. 이 강의에서는 모호함을 피하기 위해 *구조(architecture)*와 *체크포인트(checkpoint)*를 구분해서 사용하도록 하겠습니다. + +예를 들면, BERT는 구조에 해당하고, Google 팀이 최초 공개에서 내놓은 학습 가중치 셋인 `bert-base-cased`는 체크포인에 해당합니다. 그렇지만 “BERT 모델”, “`bert-base-cased` 모델” 등과 같이 구분하지 않고 사용하기도 합니다. diff --git a/chapters/ko/chapter1/5.mdx b/chapters/ko/chapter1/5.mdx new file mode 100644 index 000000000..3c133aea8 --- /dev/null +++ b/chapters/ko/chapter1/5.mdx @@ -0,0 +1,17 @@ +# 인코더 모델 + + + +인코더 모델(Encoder models)은 트랜스포머 모델의 인코더만 사용합니다. 각각의 단계에서 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있습니다. 이러한 모델은 “양방향성(bi-directional)” 어텐션을 지닌 특성이 있다고도 하며 *자동 인코딩(auto-enoding) 모델*이라고 부릅니다. + +이러한 모델은 주어진 문장을 훼손시킨 후(랜덤으로 단어에 마스킹을 하는 방식 등으로) 모델이 원본 문장을 찾아 재구성하게끔 하는 과정을 반복시키는 방식으로 사전 학습을 진행 합니다. + +인코더 모델은 문장 분류, 개체명 인식(더 넓은 범위에서 단어 분류), 추출 질의 응답 등과 같이 전체 문장에 대한 이해를 요구하는 작업에 특화되어 있습니다. + +이 계열을 대표하는 모델들은 아래와 같습니다: + +- [ALBERT](https://huggingface.co/transformers/model_doc/albert.html) +- [BERT](https://huggingface.co/transformers/model_doc/bert.html) +- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html) +- [ELECTRA](https://huggingface.co/transformers/model_doc/electra.html) +- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta.html) diff --git a/chapters/ko/chapter1/6.mdx b/chapters/ko/chapter1/6.mdx new file mode 100644 index 000000000..121403b4d --- /dev/null +++ b/chapters/ko/chapter1/6.mdx @@ -0,0 +1,16 @@ +# 디코더 모델 + + + +디코더 모델(Decoder models)은 트랜스포머 모델의 디코더만 사용합니다. 각각의 단계마다, 어텐션 레이어는 주어진 단어에 대해 문장 내에서 해당 단어 앞에 위치한 단어들에 대해서만 액세스 할 수 있습니다. 이러한 모델을 *자동 회귀(auto-regressive) 모델*이라고 부릅니다. + +디코더 모델의 사전 학습은 보통 문장 내 다음 단어 예측을 반복하는 방식으로 이루어집니다. + +이러한 모델은 텍스트 생성에 특화되어 있습니다. + +디코더 모델 계열의 대표 주자들은 다음과 같습니다: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/ko/chapter1/7.mdx b/chapters/ko/chapter1/7.mdx new file mode 100644 index 000000000..98aad86e9 --- /dev/null +++ b/chapters/ko/chapter1/7.mdx @@ -0,0 +1,16 @@ +# 시퀀스-투-시퀀스 모델 + + + +인코더-디코더 모델(Encoder-decoder models) (*시퀀스-투-시퀀스 모델(sequence-to-sequence models)*로 부르기도 합니다)은 트랜스포머 구조의 인코더, 디코더 둘을 모두 사용합니다. 각 단계마다, 인코더의 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있는 반면, 디코더의 어텐션 레이어는 주어진 단어 앞에 위치한 단어들에만 액세스 할 수 있습니다. + +이러한 모델의 사전 학습은 인코더 혹은 디코더 모델의 방식을 모두 사용할 수 있지만 조금 더 복잡합니다. 이를테면, [T5](https://huggingface.co/t5-base)는 (여러 단어를 포함하기도 하는) 임의의 텍스트 범위를 하나의 특수 마스크 토큰으로 바꾸고 마스크 단어를 대체할 텍스트를 예측하는 방식으로 사전 학습 되었습니다. + +시퀀스-투-시퀀스 모델은 요약, 번역, 생성 질의 응답과 같이 주어진 입력을 기반으로 새로운 문장을 생성하는 작업에 가장 적합합니다. + +이 계열을 대표하는 모델은 다음과 같습니다: + +- [BART](https://huggingface.co/transformers/model_doc/bart.html) +- [mBART](https://huggingface.co/transformers/model_doc/mbart.html) +- [Marian](https://huggingface.co/transformers/model_doc/marian.html) +- [T5](https://huggingface.co/transformers/model_doc/t5.html) \ No newline at end of file diff --git a/chapters/ko/chapter1/8.mdx b/chapters/ko/chapter1/8.mdx new file mode 100644 index 000000000..edbd32548 --- /dev/null +++ b/chapters/ko/chapter1/8.mdx @@ -0,0 +1,32 @@ +# 편향과 한계 + + + +사전 학습된 혹은 미세 조정된 모델을 프로덕션 단계에서 사용하실 계획이라면, 이러한 모델들은 강력한 툴이지만 한계가 있음을 반드시 명심하셔야 합니다. 가장 큰 한계점은 리서처들이 무수히 많은 양의 데이터를 사전 학습에 사용하기 위해, 인터넷상에서 모을 수 있는 양질의 데이터와 함께 그렇지 않은 데이터까지 수집했을 가능성이 있다는 것입니다. + +이를 빠르게 보여드리기 위해 `fill-mask` 파이프라인에 BERT 모델을 연결한 예제를 다시 살펴보겠습니다: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +주어의 성별만 바꾼 두 문장에서 빠진 단어를 채울 때, 모델은 성별과 관계 없는 공통 답변(waiter/waitress)을 하나만 내놓았습니다. 다른 답변들은 일반적으로 특정 성별에 편향된 답변이었습니다. 이를테면, 모델은 매춘이라는 단어와 연관된 상위 5개의 단어에 “여성”과 “일”을 포함시켰습니다. BERT가 전체 인터넷 상의 텍스트를 이용하여 사전 학습된 것이 아니라 [English Wikipedia](https://huggingface.co/datasets/wikipedia) 와 [BookCorpus](https://huggingface.co/datasets/bookcorpus) 같이 상당히 중립적인 데이터를 이용해 사전 학습 되었음에도 불구하고 이러한 현상이 일어납니다. + +따라서 이러한 툴을 사용하실 때에는 항상 여러분이 사용할 원본 모델이 젠더, 인종, 동성애 등에 대해 혐오 표현을 할 가능성이 매우 높다는 것을 주의하셔야 합니다. 이러한 모델은 미세 조정을 거쳐도 내제된 편향성을 없애지 못합니다. \ No newline at end of file diff --git a/chapters/ko/chapter1/9.mdx b/chapters/ko/chapter1/9.mdx new file mode 100644 index 000000000..191b5654d --- /dev/null +++ b/chapters/ko/chapter1/9.mdx @@ -0,0 +1,11 @@ +# 단원 정리 + +이번 단원에서는 🤗 Transformers의 하이레벨 함수인 `pipeline()` 를 사용하여 다양한 NLP 문제에 대한 접근 방식을 배웠습니다. 그리고 Hub에서 모델을 검색하여 사용하는 방법, 추론 API를 이용해 브라우저 상에서 바로 모델을 테스트 하는 방법 또한 알아보았습니다. + +지금까지 트랜스포머 모델의 대략작인 동작 방식과, 전이 학습(transfer learning) 및 미세 조정(fine-tuning)의 중요성에 알아보았습니다. 핵심은 어떤 문제를 풀고싶냐에 따라 전체 모델 구조를 다 사용하거나 인코더, 디코더만 사용할 수도 있다는 것입니다. 아래 표는 이를 요약해서 보여주고 있습니다: + +| Model | Examples | Tasks | +| --- | --- | --- | +| 인코더 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | 문장 분류, 개체명 인식, 추출 질의 응답 | +| 디코더 | CTRL, GPT, GPT-2, Transformer XL | 텍스트 생성 | +| 인코더-디코더 | BART, T5, Marian, mBART | 오약, 번역, 생성 질의 응답 | \ No newline at end of file diff --git a/chapters/tr/_toctree.yml b/chapters/tr/_toctree.yml index 41c6d6420..7c06284a4 100644 --- a/chapters/tr/_toctree.yml +++ b/chapters/tr/_toctree.yml @@ -2,3 +2,9 @@ sections: - local: chapter0/1 title: Giriş + + +- title: 1. Transformer modelleri + sections: + - local: chapter1/2 + title: Doğal Dil İşleme diff --git a/chapters/tr/chapter1/2.mdx b/chapters/tr/chapter1/2.mdx new file mode 100644 index 000000000..a9536d43a --- /dev/null +++ b/chapters/tr/chapter1/2.mdx @@ -0,0 +1,21 @@ +# Doğal Dil İşleme (NLP) + +Transformer modellerine geçiş yapmadan önce, doğal dil işlemenin tanımına ve neden önemli olduğuna kısaca bir göz atalım. + + +## NLP Nedir? + +NLP, dil bilim ve makina öğrenmesi alanlarini kapsayan, dogal dil üzerine olan herşeyi anlamaya yönelik bir alandır. NLP her bir kelimeyi anlamanın yanısıra, kelimelerin bağlamını anlayabilmeyi amaçlar. Aşağıda NLP görevlerinin genel bir listesini, her bir göreve dair bir kaç örnekle birlikte görülebilir: + +- **Cümle Sınıflandırması: Değerlendirmeler üzerine duygu analizi, spam e-posta saptaması, bir cümlenin dilbilgisel doğruluğunu, ya da iki cümlenin mantıksal olarak bağlı olup olmadığını anlama +- **Cümle içerisindeki her bir kelimenin sınıflandırması: Bir cümlenin dilbilgisel öğelerinin (isim, fiil, sıfat), ya da isim verilmiş varlıkların tanımlanması +- **Metin üretme: Verilen bir ipucunu kullanarak, otomatik olarak metni tamamlama, maskeli kelimeler ile metindeki boşlukları doldurma +- **Metinden cevap çıkarımı: Verilen bir soru ve bağlamı kullanarak, bağlamda verilmi olan bilgiye dayanarak verilen soruyu cevaplama +- **Verilen metni kullanarak yeni bir cümle üretme: Metni başka bir dile çevirme, metin özetleme. + +Metin işlemenin yanısıra, NLP, konuşma tanıma ve bilgisayar görmesi alanlarında gorülen, bir ses örneğini yazıya dökme ya da bir fotoğrafı betimleme gibi komplike işlemleri çözümlemek için de kullanılabilir. + +## NLP Neden Zor? + +Bilgisayarlar, bilgiyi biz insanların yaptığı gibi işlemiyor. Örneğin, insan beyni “Acıktım” gibi basit bir cümlenin anlamını kolaylıkla anlayabilir. Benzer bir şekilde, “acıktım” ve “üzgünüm” cümlelerinin dilbilgisel açıdan benzerliğini kolayca görebiliyoruz. Ancak, makine öğrenmesi modelleri için bu gibi işlemler hiç de kolay değil. Bir modelin, verilen metnin anlamını öğrenebilmesi için metnin belirli bir yöntem ile işlenmesi gerekir. İnsan dilinin bileşikliği nedeniyle, bu işlemenin nasıl yapılacağı konsunu dikkatlice düşünmeliyiz. Bu güne kadar NLP alanında yazının nasıl temsil edileceğine dair bir çok çalışma yapıldı. Bir sonraki bölümde, bu çalışmalarda geliştirilen bazı yöntemlere göz atacağız. +