diff --git a/README.md b/README.md index 9f46e8eef..49654517c 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,27 @@ This repo contains the content that's used to create the **[Hugging Face course] ## 🌎 Languages and translations -| 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) | -| [Chinese (simplified)](https://huggingface.co/course/zh/chapter1/1) (WIP) | [`chapters/zh`](https://github.com/huggingface/course/tree/main/chapters/zh) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | -| [French](https://huggingface.co/course/fr/chapter1/1) (WIP) | [`chapters/fr`](https://github.com/huggingface/course/tree/main/chapters/fr) | [@lbourdois](https://github.com/lbourdois), [@ChainYo](https://github.com/ChainYo), [@melaniedrevet](https://github.com/melaniedrevet), [@abdouaziz](https://github.com/abdouaziz) | -| [Gujarati](https://huggingface.co/course/gu/chapter1/1) (WIP) | [`chapters/gu`](https://github.com/huggingface/course/tree/main/chapters/gu) | [@pandyaved98](https://github.com/pandyaved98) | -| [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | -| [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) | -| [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | -| [Portuguese](https://huggingface.co/course/pt/chapter1/1) (WIP) | [`chapters/pt`](https://github.com/huggingface/course/tree/main/chapters/pt) | [@johnnv1](https://github.com/johnnv1), [@victorescosta](https://github.com/victorescosta), [@LincolnVS](https://github.com/LincolnVS) | -| [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), [@svv73](https://github.com/svv73) | -| [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), [@munozariasjm](https://github.com/munozariasjm), [@fordaz](https://github.com/fordaz) | -| [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), [@ckingkan](https://github.com/ckingkan) | -| [Turkish](https://huggingface.co/course/tr/chapter1/1) (WIP) | [`chapters/tr`](https://github.com/huggingface/course/tree/main/chapters/tr) | [@tanersekmen](https://github.com/tanersekmen), [@mertbozkir](https://github.com/mertbozkir), [@ftarlaci](https://github.com/ftarlaci), [@akkasayaz](https://github.com/akkasayaz) | +| 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) | +| [Bengali](https://huggingface.co/course/bn/chapter1/1) (WIP) | [`chapters/bn`](https://github.com/huggingface/course/tree/main/chapters/bn) | [@avishek-018](https://github.com/avishek-018), [@eNipu](https://github.com/eNipu) | +| [German](https://huggingface.co/course/de/chapter1/1) (WIP) | [`chapters/de`](https://github.com/huggingface/course/tree/main/chapters/de) | [@JesperDramsch](https://github.com/JesperDramsch), [@MarcusFra](https://github.com/MarcusFra) | +| [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), [@munozariasjm](https://github.com/munozariasjm), [@fordaz](https://github.com/fordaz) | +| [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | +| [French](https://huggingface.co/course/fr/chapter1/1) | [`chapters/fr`](https://github.com/huggingface/course/tree/main/chapters/fr) | [@lbourdois](https://github.com/lbourdois), [@ChainYo](https://github.com/ChainYo), [@melaniedrevet](https://github.com/melaniedrevet), [@abdouaziz](https://github.com/abdouaziz) | +| [Gujarati](https://huggingface.co/course/gu/chapter1/1) (WIP) | [`chapters/gu`](https://github.com/huggingface/course/tree/main/chapters/gu) | [@pandyaved98](https://github.com/pandyaved98) | +| [Hebrew](https://huggingface.co/course/he/chapter1/1) (WIP) | [`chapters/he`](https://github.com/huggingface/course/tree/main/chapters/he) | [@omer-dor](https://github.com/omer-dor) | +| [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | +| [Italian](https://huggingface.co/course/it/chapter1/1) (WIP) | [`chapters/it`](https://github.com/huggingface/course/tree/main/chapters/it) | [@CaterinaBi](https://github.com/CaterinaBi), [@ClonedOne](https://github.com/ClonedOne), [@Nolanogenn](https://github.com/Nolanogenn), [@EdAbati](https://github.com/EdAbati), [@gdacciaro](https://github.com/gdacciaro) | +| [Japanese](https://huggingface.co/course/ja/chapter1/1) (WIP) | [`chapters/ja`](https://github.com/huggingface/course/tree/main/chapters/ja) | [@hiromu166](https://github.com/@hiromu166), [@younesbelkada](https://github.com/@younesbelkada), [@HiromuHota](https://github.com/@HiromuHota) | +| [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) | +| [Portuguese](https://huggingface.co/course/pt/chapter1/1) (WIP) | [`chapters/pt`](https://github.com/huggingface/course/tree/main/chapters/pt) | [@johnnv1](https://github.com/johnnv1), [@victorescosta](https://github.com/victorescosta), [@LincolnVS](https://github.com/LincolnVS) | +| [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), [@svv73](https://github.com/svv73) | +| [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), [@ckingkan](https://github.com/ckingkan) | +| [Turkish](https://huggingface.co/course/tr/chapter1/1) (WIP) | [`chapters/tr`](https://github.com/huggingface/course/tree/main/chapters/tr) | [@tanersekmen](https://github.com/tanersekmen), [@mertbozkir](https://github.com/mertbozkir), [@ftarlaci](https://github.com/ftarlaci), [@akkasayaz](https://github.com/akkasayaz) | +| [Vietnamese](https://huggingface.co/course/vi/chapter1/1) (WIP) | [`chapters/vi`](https://github.com/huggingface/course/tree/main/chapters/vi) | [@honghanhh](https://github.com/honghanhh) | +| [Chinese (simplified)](https://huggingface.co/course/zh-CN/chapter1/1) (WIP) | [`chapters/zh-CN`](https://github.com/huggingface/course/tree/main/chapters/zh-CN) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | +| [Chinese (traditional)](https://huggingface.co/course/zh-TW/chapter1/1) (WIP) | [`chapters/zh-TW`](https://github.com/huggingface/course/tree/main/chapters/zh-TW) | [@davidpeng86](https://github.com/davidpeng86) | ### Translating the course into your language diff --git a/chapters/bn/chapter1/1.mdx b/chapters/bn/chapter1/1.mdx index 339d066d5..c40753b53 100644 --- a/chapters/bn/chapter1/1.mdx +++ b/chapters/bn/chapter1/1.mdx @@ -1,5 +1,10 @@ # ভূমিকা + + ## 🤗 কোর্সে স্বাগতম! diff --git a/chapters/bn/chapter2/1.mdx b/chapters/bn/chapter2/1.mdx index 3fbb16aa2..358c1f474 100644 --- a/chapters/bn/chapter2/1.mdx +++ b/chapters/bn/chapter2/1.mdx @@ -1,5 +1,10 @@ # ভূমিকা + + [অধ্যায় ১](/course/bn/chapter1) এ আমরা দেখে এসেছি যে Transformer মডেলগুলো সাধারণত অনেক বড় হয়। লাখ-লাখ কোটি-কোটি প্যারামিটার সম্বলিত এই মডেল গুলো কে ট্রেনিং এবং ডেপ্লয় করা বেশ জটিল ও কষ্টসাধ্য একটা কাজ। তাছাড়াও প্রায় প্রতিদিনই নতুন নতুন মডেল রিলিজ হচ্ছে এবং সবগুলোরই নিজস্ব বাস্তবায়ন রয়েছে। এই সবকিছু একসাথে এপ্লাই করা খুব সহজ একটা কাজ নয়। এই 🤗 Transformers লাইব্রেরিটা বানানো হয়েছে এই সমস্যাগুলো সমাধান করার জন্য। এর আসল উদ্দেশ্য হলো এমন একটি API প্রদান করা যার মাধ্যমে যেকোনো Transformer মডেলকে লোড করা, ট্রেইন করা কিংবা সেভ করা যাবে। লাইব্রেরিটির আসল ফিচারগুলো হলঃ diff --git a/chapters/de/chapter3/1.mdx b/chapters/de/chapter3/1.mdx index 0f1c7041c..7d275b917 100644 --- a/chapters/de/chapter3/1.mdx +++ b/chapters/de/chapter3/1.mdx @@ -2,6 +2,11 @@ # Einführung + + In [Kapitel 2](/course/chapter2) haben wir behandelt, wie man Tokenizer und vortrainierte Modelle verwendet, um Vorhersagen zu treffen. Was passiert aber, wenn wir ein vortrainiertes Modell für unseren eigenen Datensatz optimieren möchten? Das ist das Thema dieses Kapitels! Folgendes wirst du lernen: {#if fw === 'pt'} diff --git a/chapters/de/chapter3/2.mdx b/chapters/de/chapter3/2.mdx index 21c6df41a..9248e9b0f 100644 --- a/chapters/de/chapter3/2.mdx +++ b/chapters/de/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/de/chapter3/3.mdx b/chapters/de/chapter3/3.mdx index ee38b9c67..466c25d29 100644 --- a/chapters/de/chapter3/3.mdx +++ b/chapters/de/chapter3/3.mdx @@ -2,9 +2,9 @@ # Fine-tuning eine Modells mit der Trainer API - diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 6290506eb..686d8bb71 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Modell mit Keras fein-tunen - diff --git a/chapters/de/chapter3/4.mdx b/chapters/de/chapter3/4.mdx index c940e4030..2eaca08e8 100644 --- a/chapters/de/chapter3/4.mdx +++ b/chapters/de/chapter3/4.mdx @@ -1,8 +1,8 @@ # Komplettes Training - diff --git a/chapters/de/chapter3/5.mdx b/chapters/de/chapter3/5.mdx index 944442f6a..a017a1189 100644 --- a/chapters/de/chapter3/5.mdx +++ b/chapters/de/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fein-tunen, Check! + + Das hat Spaß gemacht! In den ersten beiden Kapiteln hast du etwas über Modelle und Tokenizer gelernt, und jetzt weißt du, wie du sie auf deine eigenen Daten fein-tunen kannst. Rekapitulieren wir, was du in diesem Kapitel gelernt hast: {#if fw === 'pt'} diff --git a/chapters/de/chapter3/6.mdx b/chapters/de/chapter3/6.mdx index c031089f0..cd82492e7 100644 --- a/chapters/de/chapter3/6.mdx +++ b/chapters/de/chapter3/6.mdx @@ -4,6 +4,11 @@ # Quiz am Ende des Kapitels + + Teste, was du in diesem Kapitel gelernt hast! ### 1. Der Datensatz `emotion` enthält Twitter-Nachrichten, die mit Emotionen gelabelt sind. Suche im [Hub](https://huggingface.co/datasets) nach dem Datensatz und lies die Datensatzkarte. Welche der folgenden Emotionen gehört nicht zu den grundlegenden Emotionen? diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index cd1851129..b5e8bd360 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introduction + + ## Welcome to the 🤗 Course! diff --git a/chapters/en/chapter1/10.mdx b/chapters/en/chapter1/10.mdx index 355cade7f..7c1b22080 100644 --- a/chapters/en/chapter1/10.mdx +++ b/chapters/en/chapter1/10.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + This chapter covered a lot of ground! Don't worry if you didn't grasp all the details; the next chapters will help you understand how things work under the hood. First, though, let's test what you learned in this chapter! diff --git a/chapters/en/chapter1/2.mdx b/chapters/en/chapter1/2.mdx index 4e4aecc1a..548e65d63 100644 --- a/chapters/en/chapter1/2.mdx +++ b/chapters/en/chapter1/2.mdx @@ -1,5 +1,10 @@ # Natural Language Processing + + Before jumping into Transformer models, let's do a quick overview of what natural language processing is and why we care about it. ## What is NLP? diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index ac22e7e8f..d25513cd4 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers, what can they do? - diff --git a/chapters/en/chapter1/4.mdx b/chapters/en/chapter1/4.mdx index 6d286a42e..6792a8a57 100644 --- a/chapters/en/chapter1/4.mdx +++ b/chapters/en/chapter1/4.mdx @@ -1,5 +1,10 @@ # How do Transformers work? + + In this section, we will take a high-level look at the architecture of Transformer models. ## A bit of Transformer history diff --git a/chapters/en/chapter1/5.mdx b/chapters/en/chapter1/5.mdx index 1c707033b..59c9d3a5a 100644 --- a/chapters/en/chapter1/5.mdx +++ b/chapters/en/chapter1/5.mdx @@ -1,5 +1,10 @@ # Encoder models + + Encoder models use only the encoder of a Transformer model. At each stage, the attention layers can access all the words in the initial sentence. These models are often characterized as having "bi-directional" attention, and are often called *auto-encoding models*. diff --git a/chapters/en/chapter1/6.mdx b/chapters/en/chapter1/6.mdx index d86cea9e5..2ec974012 100644 --- a/chapters/en/chapter1/6.mdx +++ b/chapters/en/chapter1/6.mdx @@ -1,5 +1,10 @@ # Decoder models + + Decoder models use only the decoder of a Transformer model. At each stage, for a given word the attention layers can only access the words positioned before it in the sentence. These models are often called *auto-regressive models*. diff --git a/chapters/en/chapter1/7.mdx b/chapters/en/chapter1/7.mdx index 3639c2a81..0474bcf76 100644 --- a/chapters/en/chapter1/7.mdx +++ b/chapters/en/chapter1/7.mdx @@ -1,5 +1,10 @@ # Sequence-to-sequence models + + Encoder-decoder models (also called *sequence-to-sequence models*) use both parts of the Transformer architecture. At each stage, the attention layers of the encoder can access all the words in the initial sentence, whereas the attention layers of the decoder can only access the words positioned before a given word in the input. diff --git a/chapters/en/chapter1/8.mdx b/chapters/en/chapter1/8.mdx index 90c80665d..08135867b 100644 --- a/chapters/en/chapter1/8.mdx +++ b/chapters/en/chapter1/8.mdx @@ -1,8 +1,8 @@ # Bias and limitations - diff --git a/chapters/en/chapter1/9.mdx b/chapters/en/chapter1/9.mdx index 4cd91feac..b9f6b5d99 100644 --- a/chapters/en/chapter1/9.mdx +++ b/chapters/en/chapter1/9.mdx @@ -1,5 +1,10 @@ # Summary + + In this chapter, you saw how to approach different NLP tasks using the high-level `pipeline()` function from 🤗 Transformers. You also saw how to search for and use models in the Hub, as well as how to use the Inference API to test the models directly in your browser. We discussed how Transformer models work at a high level, and talked about the importance of transfer learning and fine-tuning. A key aspect is that you can use the full architecture or only the encoder or decoder, depending on what kind of task you aim to solve. The following table summarizes this: diff --git a/chapters/en/chapter2/1.mdx b/chapters/en/chapter2/1.mdx index 9ab184b82..ce9c18224 100644 --- a/chapters/en/chapter2/1.mdx +++ b/chapters/en/chapter2/1.mdx @@ -1,5 +1,10 @@ # Introduction + + As you saw in [Chapter 1](/course/chapter1), Transformer models are usually very large. With millions to tens of *billions* of parameters, training and deploying these models is a complicated undertaking. Furthermore, with new models being released on a near-daily basis and each having its own implementation, trying them all out is no easy task. The 🤗 Transformers library was created to solve this problem. Its goal is to provide a single API through which any Transformer model can be loaded, trained, and saved. The library's main features are: diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index d1304d737..d65e4983e 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/3.mdx b/chapters/en/chapter2/3.mdx index c9100c42c..61e60b564 100644 --- a/chapters/en/chapter2/3.mdx +++ b/chapters/en/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/4.mdx b/chapters/en/chapter2/4.mdx index ccebe04ec..9699ef2fc 100644 --- a/chapters/en/chapter2/4.mdx +++ b/chapters/en/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index 5a692aa19..a268b4ce5 100644 --- a/chapters/en/chapter2/5.mdx +++ b/chapters/en/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/6.mdx b/chapters/en/chapter2/6.mdx index 974123515..49e9e0bca 100644 --- a/chapters/en/chapter2/6.mdx +++ b/chapters/en/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/7.mdx b/chapters/en/chapter2/7.mdx index 122728d08..d5306c56f 100644 --- a/chapters/en/chapter2/7.mdx +++ b/chapters/en/chapter2/7.mdx @@ -1,5 +1,10 @@ # Basic usage completed! + + Great job following the course up to here! To recap, in this chapter you: - Learned the basic building blocks of a Transformer model. diff --git a/chapters/en/chapter2/8.mdx b/chapters/en/chapter2/8.mdx index 861aacb39..96dfd107e 100644 --- a/chapters/en/chapter2/8.mdx +++ b/chapters/en/chapter2/8.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + ### 1. What is the order of the language modeling pipeline? + In [Chapter 2](/course/chapter2) we explored how to use tokenizers and pretrained models to make predictions. But what if you want to fine-tune a pretrained model for your own dataset? That's the topic of this chapter! You will learn: {#if fw === 'pt'} diff --git a/chapters/en/chapter3/2.mdx b/chapters/en/chapter3/2.mdx index d6d4ceb49..dd340b738 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index ebd301469..4420d705b 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -2,9 +2,9 @@ # Fine-tuning a model with the Trainer API - diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 6357be0b2..72f84ba20 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Fine-tuning a model with Keras - diff --git a/chapters/en/chapter3/4.mdx b/chapters/en/chapter3/4.mdx index a515ce2af..53550b40c 100644 --- a/chapters/en/chapter3/4.mdx +++ b/chapters/en/chapter3/4.mdx @@ -1,8 +1,8 @@ # A full training - diff --git a/chapters/en/chapter3/5.mdx b/chapters/en/chapter3/5.mdx index dda8cb7fe..bdbea1c52 100644 --- a/chapters/en/chapter3/5.mdx +++ b/chapters/en/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fine-tuning, Check! + + That was fun! In the first two chapters you learned about models and tokenizers, and now you know how to fine-tune them for your own data. To recap, in this chapter you: {#if fw === 'pt'} diff --git a/chapters/en/chapter3/6.mdx b/chapters/en/chapter3/6.mdx index 63e6c7052..75a7cf9f5 100644 --- a/chapters/en/chapter3/6.mdx +++ b/chapters/en/chapter3/6.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + Test what you learned in this chapter! ### 1. The `emotion` dataset contains Twitter messages labeled with emotions. Search for it in the [Hub](https://huggingface.co/datasets), and read the dataset card. Which of these is not one of its basic emotions? diff --git a/chapters/en/chapter4/1.mdx b/chapters/en/chapter4/1.mdx index 5b3a7f706..264326231 100644 --- a/chapters/en/chapter4/1.mdx +++ b/chapters/en/chapter4/1.mdx @@ -1,5 +1,10 @@ # The Hugging Face Hub + + The [Hugging Face Hub](https://huggingface.co/) –- our main website –- is a central platform that enables anyone to discover, use, and contribute new state-of-the-art models and datasets. It hosts a wide variety of models, with more than 10,000 publicly available. We'll focus on the models in this chapter, and take a look at the datasets in Chapter 5. The models in the Hub are not limited to 🤗 Transformers or even NLP. There are models from [Flair](https://github.com/flairNLP/flair) and [AllenNLP](https://github.com/allenai/allennlp) for NLP, [Asteroid](https://github.com/asteroid-team/asteroid) and [pyannote](https://github.com/pyannote/pyannote-audio) for speech, and [timm](https://github.com/rwightman/pytorch-image-models) for vision, to name a few. diff --git a/chapters/en/chapter4/2.mdx b/chapters/en/chapter4/2.mdx index 9519cddac..bca54f883 100644 --- a/chapters/en/chapter4/2.mdx +++ b/chapters/en/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter4/3.mdx b/chapters/en/chapter4/3.mdx index a2dbc7ee8..3fb6e0a5c 100644 --- a/chapters/en/chapter4/3.mdx +++ b/chapters/en/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter4/4.mdx b/chapters/en/chapter4/4.mdx index 8d9a75817..b609b479a 100644 --- a/chapters/en/chapter4/4.mdx +++ b/chapters/en/chapter4/4.mdx @@ -1,5 +1,10 @@ # Building a model card + + The model card is a file which is arguably as important as the model and tokenizer files in a model repository. It is the central definition of the model, ensuring reusability by fellow community members and reproducibility of results, and providing a platform on which other members may build their artifacts. Documenting the training and evaluation process helps others understand what to expect of a model — and providing sufficient information regarding the data that was used and the preprocessing and postprocessing that were done ensures that the limitations, biases, and contexts in which the model is and is not useful can be identified and understood. diff --git a/chapters/en/chapter4/5.mdx b/chapters/en/chapter4/5.mdx index 9edff8a97..341f9f348 100644 --- a/chapters/en/chapter4/5.mdx +++ b/chapters/en/chapter4/5.mdx @@ -1,5 +1,10 @@ # Part 1 completed! + + This is the end of the first part of the course! Part 2 will be released on November 15th with a big community event, see more information [here](https://huggingface.co/blog/course-launch-event). You should now be able to fine-tune a pretrained model on a text classification problem (single or pairs of sentences) and upload the result to the Model Hub. To make sure you mastered this first section, you should do exactly that on a problem that interests you (and not necessarily in English if you speak another language)! You can find help in the [Hugging Face forums](https://discuss.huggingface.co/) and share your project in [this topic](https://discuss.huggingface.co/t/share-your-projects/6803) once you're finished. diff --git a/chapters/en/chapter4/6.mdx b/chapters/en/chapter4/6.mdx index 6e968972f..4317fdb4f 100644 --- a/chapters/en/chapter4/6.mdx +++ b/chapters/en/chapter4/6.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. What are models on the Hub limited to? diff --git a/chapters/en/chapter5/1.mdx b/chapters/en/chapter5/1.mdx index f65c89609..04fbbed52 100644 --- a/chapters/en/chapter5/1.mdx +++ b/chapters/en/chapter5/1.mdx @@ -1,5 +1,10 @@ # Introduction + + In [Chapter 3](/course/chapter3) you got your first taste of the 🤗 Datasets library and saw that there were three main steps when it came to fine-tuning a model: 1. Load a dataset from the Hugging Face Hub. diff --git a/chapters/en/chapter5/2.mdx b/chapters/en/chapter5/2.mdx index 853595311..9b9c84d05 100644 --- a/chapters/en/chapter5/2.mdx +++ b/chapters/en/chapter5/2.mdx @@ -1,8 +1,8 @@ # What if my dataset isn't on the Hub? - diff --git a/chapters/en/chapter5/3.mdx b/chapters/en/chapter5/3.mdx index f47d5abbb..075a88ceb 100644 --- a/chapters/en/chapter5/3.mdx +++ b/chapters/en/chapter5/3.mdx @@ -1,8 +1,8 @@ # Time to slice and dice - diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index cb90067f4..7de820546 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -1,8 +1,8 @@ # Big data? 🤗 Datasets to the rescue! - diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index d5b0605ca..363afacbc 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -1,8 +1,8 @@ # Creating your own dataset - diff --git a/chapters/en/chapter5/6.mdx b/chapters/en/chapter5/6.mdx index 1db80cc9e..b2aa21057 100644 --- a/chapters/en/chapter5/6.mdx +++ b/chapters/en/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter5/7.mdx b/chapters/en/chapter5/7.mdx index 7b67dbe10..d4f42a6a7 100644 --- a/chapters/en/chapter5/7.mdx +++ b/chapters/en/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets, check! + + Well, that was quite a tour through the 🤗 Datasets library -- congratulations on making it this far! With the knowledge that you've gained from this chapter, you should be able to: - Load datasets from anywhere, be it the Hugging Face Hub, your laptop, or a remote server at your company. diff --git a/chapters/en/chapter5/8.mdx b/chapters/en/chapter5/8.mdx index 69888b348..ae049731e 100644 --- a/chapters/en/chapter5/8.mdx +++ b/chapters/en/chapter5/8.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + This chapter covered a lot of ground! Don't worry if you didn't grasp all the details; the next chapters will help you understand how things work under the hood. Before moving on, though, let's test what you learned in this chapter. diff --git a/chapters/en/chapter6/1.mdx b/chapters/en/chapter6/1.mdx index 5e5c63c9a..778883488 100644 --- a/chapters/en/chapter6/1.mdx +++ b/chapters/en/chapter6/1.mdx @@ -1,5 +1,10 @@ # Introduction + + In [Chapter 3](/course/chapter3), we looked at how to fine-tune a model on a given task. When we do that, we use the same tokenizer that the model was pretrained with -- but what do we do when we want to train a model from scratch? In these cases, using a tokenizer that was pretrained on a corpus from another domain or language is typically suboptimal. For example, a tokenizer that's trained on an English corpus will perform poorly on a corpus of Japanese texts because the use of spaces and punctuation is very different in the two languages. In this chapter, you will learn how to train a brand new tokenizer on a corpus of texts, so it can then be used to pretrain a language model. This will all be done with the help of the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library, which provides the "fast" tokenizers in the [🤗 Transformers](https://github.com/huggingface/transformers) library. We'll take a close look at the features that this library provides, and explore how the fast tokenizers differ from the "slow" versions. diff --git a/chapters/en/chapter6/10.mdx b/chapters/en/chapter6/10.mdx index 6d488332d..3a2fec5dd 100644 --- a/chapters/en/chapter6/10.mdx +++ b/chapters/en/chapter6/10.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. When should you train a new tokenizer? diff --git a/chapters/en/chapter6/2.mdx b/chapters/en/chapter6/2.mdx index 7eba91c92..4effa7320 100644 --- a/chapters/en/chapter6/2.mdx +++ b/chapters/en/chapter6/2.mdx @@ -1,8 +1,8 @@ # Training a new tokenizer from an old one - diff --git a/chapters/en/chapter6/3.mdx b/chapters/en/chapter6/3.mdx index 85b24c195..4f9bd30db 100644 --- a/chapters/en/chapter6/3.mdx +++ b/chapters/en/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter6/3b.mdx b/chapters/en/chapter6/3b.mdx index 61fad33d0..6b8ccd02f 100644 --- a/chapters/en/chapter6/3b.mdx +++ b/chapters/en/chapter6/3b.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter6/4.mdx b/chapters/en/chapter6/4.mdx index 741e57e8c..58a68f23b 100644 --- a/chapters/en/chapter6/4.mdx +++ b/chapters/en/chapter6/4.mdx @@ -1,8 +1,8 @@ # Normalization and pre-tokenization - diff --git a/chapters/en/chapter6/5.mdx b/chapters/en/chapter6/5.mdx index a9f070b0e..18b189bc5 100644 --- a/chapters/en/chapter6/5.mdx +++ b/chapters/en/chapter6/5.mdx @@ -1,8 +1,8 @@ # Byte-Pair Encoding tokenization - diff --git a/chapters/en/chapter6/6.mdx b/chapters/en/chapter6/6.mdx index d4152cd5e..7e60dabb0 100644 --- a/chapters/en/chapter6/6.mdx +++ b/chapters/en/chapter6/6.mdx @@ -1,8 +1,8 @@ # WordPiece tokenization - diff --git a/chapters/en/chapter6/7.mdx b/chapters/en/chapter6/7.mdx index e23d6f473..2a4d6f2f3 100644 --- a/chapters/en/chapter6/7.mdx +++ b/chapters/en/chapter6/7.mdx @@ -1,8 +1,8 @@ # Unigram tokenization - diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 301648c7e..d831eb239 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -1,8 +1,8 @@ # Building a tokenizer, block by block - diff --git a/chapters/en/chapter6/9.mdx b/chapters/en/chapter6/9.mdx index 6d2e0f36e..132206d25 100644 --- a/chapters/en/chapter6/9.mdx +++ b/chapters/en/chapter6/9.mdx @@ -1,5 +1,10 @@ # Tokenizers, check! + + Great job finishing this chapter! After this deep dive into tokenizers, you should: diff --git a/chapters/en/chapter7/1.mdx b/chapters/en/chapter7/1.mdx index 58215cb40..020d7ea5d 100644 --- a/chapters/en/chapter7/1.mdx +++ b/chapters/en/chapter7/1.mdx @@ -2,6 +2,11 @@ # Introduction + + In [Chapter 3](/course/chapter3), you saw how to fine-tune a model for text classification. In this chapter, we will tackle the following common NLP tasks: - Token classification diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 3eaba62c8..b052dc207 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 1ea3d6ee5..63eeba0a9 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index e68bb376b..cc0a41c49 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index e6df6fc31..f66a4f429 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -1,1051 +1,1051 @@ - - -# Summarization - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. - - - -Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: - - - -As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. - -## Preparing a multilingual corpus - -We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the Hugging Face Hub: - -```python -from datasets import load_dataset - -spanish_dataset = load_dataset("amazon_reviews_multi", "es") -english_dataset = load_dataset("amazon_reviews_multi", "en") -english_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], - num_rows: 200000 - }) - validation: Dataset({ - features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], - num_rows: 5000 - }) - test: Dataset({ - features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], - num_rows: 5000 - }) -}) -``` - -As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): - -```python -def show_samples(dataset, num_samples=3, seed=42): - sample = dataset["train"].shuffle(seed=seed).select(range(num_samples)) - for example in sample: - print(f"\n'>> Title: {example['review_title']}'") - print(f"'>> Review: {example['review_body']}'") - - -show_samples(english_dataset) -``` - -```python out -'>> Title: Worked in front position, not rear' -'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.' - -'>> Title: meh' -'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue' - -'>> Title: Can\'t beat these for the money' -'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.' -``` - - - -✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. - - - -This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: - -```python -english_dataset.set_format("pandas") -english_df = english_dataset["train"][:] -# Show counts for top 20 products -english_df["product_category"].value_counts()[:20] -``` - -```python out -home 17679 -apparel 15951 -wireless 15717 -other 13418 -beauty 12091 -drugstore 11730 -kitchen 10382 -toy 8745 -sports 8277 -automotive 7506 -lawn_and_garden 7327 -home_improvement 7136 -pet_products 7082 -digital_ebook_purchase 6749 -pc 6401 -electronics 6186 -office_product 5521 -shoes 5197 -grocery 4730 -book 3756 -Name: product_category, dtype: int64 -``` - -The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: - -```python -def filter_books(example): - return ( - example["product_category"] == "book" - or example["product_category"] == "digital_ebook_purchase" - ) -``` - -Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: - -```python -english_dataset.reset_format() -``` - -We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: - -```python -spanish_books = spanish_dataset.filter(filter_books) -english_books = english_dataset.filter(filter_books) -show_samples(english_books) -``` - -```python out -'>> Title: I\'m dissapointed.' -'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.' - -'>> Title: Good art, good price, poor design' -'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar' - -'>> Title: Helpful' -'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' -``` - -Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: - -```python -from datasets import concatenate_datasets, DatasetDict - -books_dataset = DatasetDict() - -for split in english_books.keys(): - books_dataset[split] = concatenate_datasets( - [english_books[split], spanish_books[split]] - ) - books_dataset[split] = books_dataset[split].shuffle(seed=42) - -# Peek at a few examples -show_samples(books_dataset) -``` - -```python out -'>> Title: Easy to follow!!!!' -'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.' - -'>> Title: PARCIALMENTE DAÑADO' -'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).' - -'>> Title: no lo he podido descargar' -'>> Review: igual que el anterior' -``` - -This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: - -
-Word count distributions for the review titles and texts. - -
- -To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: - -```python -books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) -``` - -Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! - -## Models for text summarization - -If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. - -| Transformer model | Description | Multilingual? | -| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | -| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | -| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | -| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | -| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | - -As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! - -We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! - -
-Different tasks performed by the T5 architecture. - -
- -mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. - - - - -✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. - - - -## Preprocessing the data - - - -Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: - -```python -from transformers import AutoTokenizer - -model_checkpoint = "google/mt5-small" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - - - -💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! - - - -Let's test out the mT5 tokenizer on a small example: - -```python -inputs = tokenizer("I loved reading the Hunger Games!") -inputs -``` - -```python out -{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} -``` - -Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: - -```python -tokenizer.convert_ids_to_tokens(inputs.input_ids) -``` - -```python out -['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] -``` - -The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. - -To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `as_target_tokenizer()` function that allows you to tokenize the labels in parallel to the inputs. This is typically done using a context manager inside a preprocessing function that first encodes the inputs, and then encodes the labels as a separate column. Here is an example of such a function for mT5: - -```python -max_input_length = 512 -max_target_length = 30 - - -def preprocess_function(examples): - model_inputs = tokenizer( - examples["review_body"], max_length=max_input_length, truncation=True - ) - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. Then, in the `preprocess_function()` itself we can see the reviews are first tokenized, followed by the titles with `as_target_tokenizer()`. - -With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: - -```python -tokenized_datasets = books_dataset.map(preprocess_function, batched=True) -``` - -Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. - - - -💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! - - - - -## Metrics for text summarization - - - -In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. - -For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: - -```python -generated_summary = "I absolutely loved reading the Hunger Games" -reference_summary = "I loved reading the Hunger Games" -``` - -One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. - - - -🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). - - - -For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: - -$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ - -For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: - -$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ - -Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: - -```py -!pip install rouge_score -``` - -and then loading the ROUGE metric as follows: - -```python -import evaluate - -rouge_score = evaluate.load("rouge") -``` - -Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: - -```python -scores = rouge_score.compute( - predictions=[generated_summary], references=[reference_summary] -) -scores -``` - -```python out -{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), - 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)), - 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), - 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} -``` - -Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: - -```python -scores["rouge1"].mid -``` - -```python out -Score(precision=0.86, recall=1.0, fmeasure=0.92) -``` - -Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. - - - -✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. - - - -We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! - -### Creating a strong baseline - -A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: - -```python -!pip install nltk -``` - -and then download the punctuation rules: - -```python -import nltk - -nltk.download("punkt") -``` - -Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: - -```python -from nltk.tokenize import sent_tokenize - - -def three_sentence_summary(text): - return "\n".join(sent_tokenize(text)[:3]) - - -print(three_sentence_summary(books_dataset["train"][1]["review_body"])) -``` - -```python out -'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' -'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' -'She found Strangers.' -``` - -This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: - -```python -def evaluate_baseline(dataset, metric): - summaries = [three_sentence_summary(text) for text in dataset["review_body"]] - return metric.compute(predictions=summaries, references=dataset["review_title"]) -``` - -We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using Pandas: - -```python -import pandas as pd - -score = evaluate_baseline(books_dataset["validation"], rouge_score) -rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"] -rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names) -rouge_dict -``` - -```python out -{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} -``` - -We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! - -{#if fw === 'pt'} - -## Fine-tuning mT5 with the `Trainer` API - -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: - -```python -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## Fine-tuning mT5 with Keras - -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: - -```python -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{/if} - - - -💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. - - - -The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: - -``` -huggingface-cli login -``` - -{#if fw === 'pt'} - -We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: - -```python -from transformers import Seq2SeqTrainingArguments - -batch_size = 8 -num_train_epochs = 8 -# Show the training loss with every epoch -logging_steps = len(tokenized_datasets["train"]) // batch_size -model_name = model_checkpoint.split("/")[-1] - -args = Seq2SeqTrainingArguments( - output_dir=f"{model_name}-finetuned-amazon-en-es", - evaluation_strategy="epoch", - learning_rate=5.6e-5, - per_device_train_batch_size=batch_size, - per_device_eval_batch_size=batch_size, - weight_decay=0.01, - save_total_limit=3, - num_train_epochs=num_train_epochs, - predict_with_generate=True, - logging_steps=logging_steps, - push_to_hub=True, -) -``` - -Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. - -The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. - -The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: - -```python -import numpy as np - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - # Decode generated summaries into text - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Replace -100 in the labels as we can't decode them - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - # Decode reference summaries into text - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # ROUGE expects a newline after each sentence - decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] - decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] - # Compute ROUGE scores - result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True - ) - # Extract the median scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - return {k: round(v, 4) for k, v in result.items()} -``` - -{/if} - -Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). - -Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `model`: - -{#if fw === 'pt'} - -```python -from transformers import DataCollatorForSeq2Seq - -data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) -``` - -{:else} - -```python -from transformers import DataCollatorForSeq2Seq - -data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") -``` - -{/if} - -Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: - -```python -tokenized_datasets = tokenized_datasets.remove_columns( - books_dataset["train"].column_names -) -``` - -Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: - -```python -features = [tokenized_datasets["train"][i] for i in range(2)] -data_collator(features) -``` - -```python out -{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[ 1494, 259, 8622, 390, 259, 262, 2316, 3435, 955, - 772, 281, 772, 1617, 263, 305, 14701, 260, 1385, - 3031, 259, 24146, 332, 1037, 259, 43906, 305, 336, - 260, 1, 0, 0, 0, 0, 0, 0], - [ 259, 27531, 13483, 259, 7505, 260, 112240, 15192, 305, - 53198, 276, 259, 74060, 263, 260, 459, 25640, 776, - 2119, 336, 259, 2220, 259, 18896, 288, 4906, 288, - 1037, 3931, 260, 7083, 101476, 1143, 260, 1]]), 'labels': tensor([[ 7483, 259, 2364, 15695, 1, -100], - [ 259, 27531, 13483, 259, 7505, 1]]), 'decoder_input_ids': tensor([[ 0, 7483, 259, 2364, 15695, 1], - [ 0, 259, 27531, 13483, 259, 7505]])} -``` - -The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. - -{#if fw === 'pt'} - -We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: - -```python -from transformers import Seq2SeqTrainer - -trainer = Seq2SeqTrainer( - model, - args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - tokenizer=tokenizer, - compute_metrics=compute_metrics, -) -``` - -and launch our training run: - -```python -trainer.train() -``` - -During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `Trainer.evaluate()`: - -```python -trainer.evaluate() -``` - -```python out -{'eval_loss': 3.028524398803711, - 'eval_rouge1': 16.9728, - 'eval_rouge2': 8.2969, - 'eval_rougeL': 16.8366, - 'eval_rougeLsum': 16.851, - 'eval_gen_len': 10.1597, - 'eval_runtime': 6.1054, - 'eval_samples_per_second': 38.982, - 'eval_steps_per_second': 4.914} -``` - -From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: - -``` -trainer.push_to_hub(commit_message="Training complete", tags="summarization") -``` - -```python out -'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' -``` - -This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! - -To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. - -{:else} - -We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=8, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=8, -) -``` - -Now, we define our training hyperparameters and compile: - -```python -from transformers import create_optimizer -import tensorflow as tf - -# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied -# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, -# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. -num_train_epochs = 8 -num_train_steps = len(tf_train_dataset) * num_train_epochs -model_name = model_checkpoint.split("/")[-1] - -optimizer, schedule = create_optimizer( - init_lr=5.6e-5, - num_warmup_steps=0, - num_train_steps=num_train_steps, - weight_decay_rate=0.01, -) - -model.compile(optimizer=optimizer) - -# Train in mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: - -```python -from transformers.keras_callbacks import PushToHubCallback - -callback = PushToHubCallback( - output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer -) - -model.fit( - tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8 -) -``` - -We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`): - -```python -from tqdm import tqdm -import numpy as np - -all_preds = [] -all_labels = [] -for batch in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] - decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] - all_preds.extend(decoded_preds) - all_labels.extend(decoded_labels) -``` - -Once we have our lists of label and prediction strings, computing the ROUGE score is easy: - -```python -result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True -) -result = {key: value.mid.fmeasure * 100 for key, value in result.items()} -{k: round(v, 4) for k, v in result.items()} -``` - -``` -{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815} -``` - - -{/if} - -{#if fw === 'pt'} - -## Fine-tuning mT5 with 🤗 Accelerate - -Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! - -### Preparing everything for training - -The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: - -```python -tokenized_datasets.set_format("torch") -``` - -Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: - -```python -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -We can then instantiate the data collator and use this to define our dataloaders: - -```python -from torch.utils.data import DataLoader - -batch_size = 8 -train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=batch_size, -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size -) -``` - -The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. - - - -Now that we've prepared our objects, there are three remaining things to do: - -* Define the learning rate schedule. -* Implement a function to post-process the summaries for evaluation. -* Create a repository on the Hub that we can push our model to. - -For the learning rate schedule, we'll use the standard linear one from previous sections: - -```python -from transformers import get_scheduler - -num_train_epochs = 10 -num_update_steps_per_epoch = len(train_dataloader) -num_training_steps = num_train_epochs * num_update_steps_per_epoch - -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -``` - -For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: - -```python -def postprocess_text(preds, labels): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # ROUGE expects a newline after each sentence - preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] - labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] - - return preds, labels -``` - -This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. - -Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: - -```python -from huggingface_hub import get_full_repo_name - -model_name = "test-bert-finetuned-squad-accelerate" -repo_name = get_full_repo_name(model_name) -repo_name -``` - -```python out -'lewtun/mt5-finetuned-amazon-en-es-accelerate' -``` - -Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: - -```python -from huggingface_hub import Repository - -output_dir = "results-mt5-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. - -### Training loop - -The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: - -1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. -2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. -3. Compute the ROUGE scores using the same techniques we saw earlier. -4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! - -These steps can be seen in the following block of code: - -```python -from tqdm.auto import tqdm -import torch -import numpy as np - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Training - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - generated_tokens = accelerator.unwrap_model(model).generate( - batch["input_ids"], - attention_mask=batch["attention_mask"], - ) - - generated_tokens = accelerator.pad_across_processes( - generated_tokens, dim=1, pad_index=tokenizer.pad_token_id - ) - labels = batch["labels"] - - # If we did not pad to max length, we need to pad the labels too - labels = accelerator.pad_across_processes( - batch["labels"], dim=1, pad_index=tokenizer.pad_token_id - ) - - generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() - labels = accelerator.gather(labels).cpu().numpy() - - # Replace -100 in the labels as we can't decode them - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - if isinstance(generated_tokens, tuple): - generated_tokens = generated_tokens[0] - decoded_preds = tokenizer.batch_decode( - generated_tokens, skip_special_tokens=True - ) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - decoded_preds, decoded_labels = postprocess_text( - decoded_preds, decoded_labels - ) - - rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) - - # Compute metrics - result = rouge_score.compute() - # Extract the median ROUGE scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - result = {k: round(v, 4) for k, v in result.items()} - print(f"Epoch {epoch}:", result) - - # Save and upload - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -```python out -Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005} -Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306} -Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468} -Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518} -Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029} -Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913} -Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701} -Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194} -Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744} -Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} -``` - -And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. - -{/if} - -## Using your fine-tuned model - -Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: - -```python -from transformers import pipeline - -hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" -summarizer = pipeline("summarization", model=hub_model_id) -``` - -We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: - -```python -def print_summary(idx): - review = books_dataset["test"][idx]["review_body"] - title = books_dataset["test"][idx]["review_title"] - summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"] - print(f"'>>> Review: {review}'") - print(f"\n'>>> Title: {title}'") - print(f"\n'>>> Summary: {summary}'") -``` - -Let's take a look at one of the English examples we get: - -```python -print_summary(100) -``` - -```python out -'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.' - -'>>> Title: Not impressed at all... buy something else' - -'>>> Summary: Nothing special at all about this product' -``` - -This is not too bad! We can see that our model has actually been able to perform _abstractive_ summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews: - -```python -print_summary(0) -``` - -```python out -'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' - -'>>> Title: Buena literatura para adolescentes' - -'>>> Summary: Muy facil de leer' -``` - -The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! - -Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. + + +# Summarization + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. + + + +Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: + + + +As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. + +## Preparing a multilingual corpus + +We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the Hugging Face Hub: + +```python +from datasets import load_dataset + +spanish_dataset = load_dataset("amazon_reviews_multi", "es") +english_dataset = load_dataset("amazon_reviews_multi", "en") +english_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 200000 + }) + validation: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) + test: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) +}) +``` + +As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): + +```python +def show_samples(dataset, num_samples=3, seed=42): + sample = dataset["train"].shuffle(seed=seed).select(range(num_samples)) + for example in sample: + print(f"\n'>> Title: {example['review_title']}'") + print(f"'>> Review: {example['review_body']}'") + + +show_samples(english_dataset) +``` + +```python out +'>> Title: Worked in front position, not rear' +'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.' + +'>> Title: meh' +'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue' + +'>> Title: Can\'t beat these for the money' +'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.' +``` + + + +✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. + + + +This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Show counts for top 20 products +english_df["product_category"].value_counts()[:20] +``` + +```python out +home 17679 +apparel 15951 +wireless 15717 +other 13418 +beauty 12091 +drugstore 11730 +kitchen 10382 +toy 8745 +sports 8277 +automotive 7506 +lawn_and_garden 7327 +home_improvement 7136 +pet_products 7082 +digital_ebook_purchase 6749 +pc 6401 +electronics 6186 +office_product 5521 +shoes 5197 +grocery 4730 +book 3756 +Name: product_category, dtype: int64 +``` + +The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: + +```python +english_dataset.reset_format() +``` + +We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: + +```python +spanish_books = spanish_dataset.filter(filter_books) +english_books = english_dataset.filter(filter_books) +show_samples(english_books) +``` + +```python out +'>> Title: I\'m dissapointed.' +'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.' + +'>> Title: Good art, good price, poor design' +'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar' + +'>> Title: Helpful' +'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' +``` + +Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: + +```python +from datasets import concatenate_datasets, DatasetDict + +books_dataset = DatasetDict() + +for split in english_books.keys(): + books_dataset[split] = concatenate_datasets( + [english_books[split], spanish_books[split]] + ) + books_dataset[split] = books_dataset[split].shuffle(seed=42) + +# Peek at a few examples +show_samples(books_dataset) +``` + +```python out +'>> Title: Easy to follow!!!!' +'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.' + +'>> Title: PARCIALMENTE DAÑADO' +'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).' + +'>> Title: no lo he podido descargar' +'>> Review: igual que el anterior' +``` + +This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: + +
+Word count distributions for the review titles and texts. + +
+ +To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! + +## Models for text summarization + +If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. + +| Transformer model | Description | Multilingual? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | +| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | + +As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! + +We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. + + + + +✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. + + + +## Preprocessing the data + + + +Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! + + + +Let's test out the mT5 tokenizer on a small example: + +```python +inputs = tokenizer("I loved reading the Hunger Games!") +inputs +``` + +```python out +{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. + +To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `as_target_tokenizer()` function that allows you to tokenize the labels in parallel to the inputs. This is typically done using a context manager inside a preprocessing function that first encodes the inputs, and then encodes the labels as a separate column. Here is an example of such a function for mT5: + +```python +max_input_length = 512 +max_target_length = 30 + + +def preprocess_function(examples): + model_inputs = tokenizer( + examples["review_body"], max_length=max_input_length, truncation=True + ) + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. Then, in the `preprocess_function()` itself we can see the reviews are first tokenized, followed by the titles with `as_target_tokenizer()`. + +With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. + + + +💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! + + + + +## Metrics for text summarization + + + +In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. + +For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. + + + +🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: + +```py +!pip install rouge_score +``` + +and then loading the ROUGE metric as follows: + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: + +```python +scores = rouge_score.compute( + predictions=[generated_summary], references=[reference_summary] +) +scores +``` + +```python out +{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)), + 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} +``` + +Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. + + + +✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. + + + +We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! + +### Creating a strong baseline + +A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: + +```python +!pip install nltk +``` + +and then download the punctuation rules: + +```python +import nltk + +nltk.download("punkt") +``` + +Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: + +```python +from nltk.tokenize import sent_tokenize + + +def three_sentence_summary(text): + return "\n".join(sent_tokenize(text)[:3]) + + +print(three_sentence_summary(books_dataset["train"][1]["review_body"])) +``` + +```python out +'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' +'She found Strangers.' +``` + +This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: + +```python +def evaluate_baseline(dataset, metric): + summaries = [three_sentence_summary(text) for text in dataset["review_body"]] + return metric.compute(predictions=summaries, references=dataset["review_title"]) +``` + +We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using Pandas: + +```python +import pandas as pd + +score = evaluate_baseline(books_dataset["validation"], rouge_score) +rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"] +rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names) +rouge_dict +``` + +```python out +{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} +``` + +We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! + +{#if fw === 'pt'} + +## Fine-tuning mT5 with the `Trainer` API + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Fine-tuning mT5 with Keras + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. + + + +The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. + +The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. + +The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). + +Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `model`: + +{#if fw === 'pt'} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: + +```python +features = [tokenized_datasets["train"][i] for i in range(2)] +data_collator(features) +``` + +```python out +{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[ 1494, 259, 8622, 390, 259, 262, 2316, 3435, 955, + 772, 281, 772, 1617, 263, 305, 14701, 260, 1385, + 3031, 259, 24146, 332, 1037, 259, 43906, 305, 336, + 260, 1, 0, 0, 0, 0, 0, 0], + [ 259, 27531, 13483, 259, 7505, 260, 112240, 15192, 305, + 53198, 276, 259, 74060, 263, 260, 459, 25640, 776, + 2119, 336, 259, 2220, 259, 18896, 288, 4906, 288, + 1037, 3931, 260, 7083, 101476, 1143, 260, 1]]), 'labels': tensor([[ 7483, 259, 2364, 15695, 1, -100], + [ 259, 27531, 13483, 259, 7505, 1]]), 'decoder_input_ids': tensor([[ 0, 7483, 259, 2364, 15695, 1], + [ 0, 259, 27531, 13483, 259, 7505]])} +``` + +The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. + +{#if fw === 'pt'} + +We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: + +```python +from transformers import Seq2SeqTrainer + +trainer = Seq2SeqTrainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +and launch our training run: + +```python +trainer.train() +``` + +During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `Trainer.evaluate()`: + +```python +trainer.evaluate() +``` + +```python out +{'eval_loss': 3.028524398803711, + 'eval_rouge1': 16.9728, + 'eval_rouge2': 8.2969, + 'eval_rougeL': 16.8366, + 'eval_rougeLsum': 16.851, + 'eval_gen_len': 10.1597, + 'eval_runtime': 6.1054, + 'eval_samples_per_second': 38.982, + 'eval_steps_per_second': 4.914} +``` + +From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! + +To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. + +{:else} + +We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Now, we define our training hyperparameters and compile: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied +# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, +# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. +num_train_epochs = 8 +num_train_steps = len(tf_train_dataset) * num_train_epochs +model_name = model_checkpoint.split("/")[-1] + +optimizer, schedule = create_optimizer( + init_lr=5.6e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) + +model.compile(optimizer=optimizer) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8 +) +``` + +We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`): + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["labels"].numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) +``` + +Once we have our lists of label and prediction strings, computing the ROUGE score is easy: + +```python +result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True +) +result = {key: value.mid.fmeasure * 100 for key, value in result.items()} +{k: round(v, 4) for k, v in result.items()} +``` + +``` +{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815} +``` + + +{/if} + +{#if fw === 'pt'} + +## Fine-tuning mT5 with 🤗 Accelerate + +Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! + +### Preparing everything for training + +The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: + +```python +tokenized_datasets.set_format("torch") +``` + +Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +We can then instantiate the data collator and use this to define our dataloaders: + +```python +from torch.utils.data import DataLoader + +batch_size = 8 +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=batch_size, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size +) +``` + +The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. + + + +Now that we've prepared our objects, there are three remaining things to do: + +* Define the learning rate schedule. +* Implement a function to post-process the summaries for evaluation. +* Create a repository on the Hub that we can push our model to. + +For the learning rate schedule, we'll use the standard linear one from previous sections: + +```python +from transformers import get_scheduler + +num_train_epochs = 10 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. + +Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: + +```python +from huggingface_hub import get_full_repo_name + +model_name = "test-bert-finetuned-squad-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/mt5-finetuned-amazon-en-es-accelerate' +``` + +Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. + +### Training loop + +The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: + +1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. +2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. +3. Compute the ROUGE scores using the same techniques we saw earlier. +4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! + +These steps can be seen in the following block of code: + +```python +from tqdm.auto import tqdm +import torch +import numpy as np + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005} +Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306} +Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468} +Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518} +Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029} +Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913} +Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701} +Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194} +Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744} +Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} +``` + +And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. + +{/if} + +## Using your fine-tuned model + +Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: + +```python +def print_summary(idx): + review = books_dataset["test"][idx]["review_body"] + title = books_dataset["test"][idx]["review_title"] + summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"] + print(f"'>>> Review: {review}'") + print(f"\n'>>> Title: {title}'") + print(f"\n'>>> Summary: {summary}'") +``` + +Let's take a look at one of the English examples we get: + +```python +print_summary(100) +``` + +```python out +'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.' + +'>>> Title: Not impressed at all... buy something else' + +'>>> Summary: Nothing special at all about this product' +``` + +This is not too bad! We can see that our model has actually been able to perform _abstractive_ summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews: + +```python +print_summary(0) +``` + +```python out +'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' + +'>>> Title: Buena literatura para adolescentes' + +'>>> Summary: Muy facil de leer' +``` + +The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! + +Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index 27dc8cbbd..e113b9423 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index d32fc7d8d..2dd81123a 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/8.mdx b/chapters/en/chapter7/8.mdx index 0ac5ccc02..dcda455a5 100644 --- a/chapters/en/chapter7/8.mdx +++ b/chapters/en/chapter7/8.mdx @@ -1,5 +1,10 @@ # Mastering NLP + + If you've made it this far in the course, congratulations -- you now have all the knowledge and tools you need to tackle (almost) any NLP task with 🤗 Transformers and the Hugging Face ecosystem! We have seen a lot of different data collators, so we made this little video to help you find which one to use for each task: diff --git a/chapters/en/chapter7/9.mdx b/chapters/en/chapter7/9.mdx index 1763e56ed..e6da1f801 100644 --- a/chapters/en/chapter7/9.mdx +++ b/chapters/en/chapter7/9.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. Which of the following tasks can be framed as a token classification problem? diff --git a/chapters/en/chapter8/1.mdx b/chapters/en/chapter8/1.mdx index 5a14a567f..644e438a1 100644 --- a/chapters/en/chapter8/1.mdx +++ b/chapters/en/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introduction + + Now that you know how to tackle the most common NLP tasks with 🤗 Transformers, you should be able to get started on your own projects! In this chapter we will explore what to do when you hit a problem. You'll learn how to successfully debug your code or your training, and how to ask the community for help if you don't manage to solve the problem by yourself. And if you think you've found a bug in one of the Hugging Face libraries, we'll show you the best way to report it so that the issue is resolved as quickly as possible. More precisely, in this chapter you will learn: diff --git a/chapters/en/chapter8/2.mdx b/chapters/en/chapter8/2.mdx index b366666fc..20dac3a77 100644 --- a/chapters/en/chapter8/2.mdx +++ b/chapters/en/chapter8/2.mdx @@ -1,8 +1,8 @@ # What to do when you get an error - diff --git a/chapters/en/chapter8/3.mdx b/chapters/en/chapter8/3.mdx index 46a1564d0..0dd7c2ec3 100644 --- a/chapters/en/chapter8/3.mdx +++ b/chapters/en/chapter8/3.mdx @@ -1,8 +1,8 @@ # Asking for help on the forums - diff --git a/chapters/en/chapter8/4.mdx b/chapters/en/chapter8/4.mdx index 54232cdc9..8d2306321 100644 --- a/chapters/en/chapter8/4.mdx +++ b/chapters/en/chapter8/4.mdx @@ -2,9 +2,9 @@ # Debugging the training pipeline - diff --git a/chapters/en/chapter8/4_tf.mdx b/chapters/en/chapter8/4_tf.mdx index 6a241216d..a9f595b48 100644 --- a/chapters/en/chapter8/4_tf.mdx +++ b/chapters/en/chapter8/4_tf.mdx @@ -2,9 +2,9 @@ # Debugging the training pipeline - diff --git a/chapters/en/chapter8/5.mdx b/chapters/en/chapter8/5.mdx index f58cc94b9..51f71c15e 100644 --- a/chapters/en/chapter8/5.mdx +++ b/chapters/en/chapter8/5.mdx @@ -1,8 +1,8 @@ # How to write a good issue - diff --git a/chapters/en/chapter8/6.mdx b/chapters/en/chapter8/6.mdx index 3db86f7ae..2c8b97712 100644 --- a/chapters/en/chapter8/6.mdx +++ b/chapters/en/chapter8/6.mdx @@ -1,5 +1,10 @@ # Part 2 completed! + + Congratulations, you've made it through the second part of the course! We're actively working on the third one, so subscribe to our [newsletter](https://huggingface.curated.co/) to make sure you don't miss its release. You should now be able to tackle a range of NLP tasks, and fine-tune or pretrain a model on them. Don't forget to share your results with the community on the [Model Hub](https://huggingface.co/models). diff --git a/chapters/en/chapter8/7.mdx b/chapters/en/chapter8/7.mdx index 9d29e8fcb..219328edd 100644 --- a/chapters/en/chapter8/7.mdx +++ b/chapters/en/chapter8/7.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. In which order should you read a Python traceback? diff --git a/chapters/en/chapter9/1.mdx b/chapters/en/chapter9/1.mdx index edfcb13f9..74ea8a0a3 100644 --- a/chapters/en/chapter9/1.mdx +++ b/chapters/en/chapter9/1.mdx @@ -1,5 +1,10 @@ # Introduction to Gradio + + In this chapter we will be learning about how to build **interactive demos** for your machine learning models. Why build a demo or a GUI for your machine learning model in the first place? Demos allow: diff --git a/chapters/en/chapter9/2.mdx b/chapters/en/chapter9/2.mdx index d6445763a..3cf23d896 100644 --- a/chapters/en/chapter9/2.mdx +++ b/chapters/en/chapter9/2.mdx @@ -1,8 +1,8 @@ # Building your first demo - diff --git a/chapters/en/chapter9/3.mdx b/chapters/en/chapter9/3.mdx index be34af06d..bd829ec95 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -1,8 +1,8 @@ # Understanding the Interface class - diff --git a/chapters/en/chapter9/4.mdx b/chapters/en/chapter9/4.mdx index ad8b4714a..fc31404d0 100644 --- a/chapters/en/chapter9/4.mdx +++ b/chapters/en/chapter9/4.mdx @@ -1,8 +1,8 @@ # Sharing demos with others - diff --git a/chapters/en/chapter9/5.mdx b/chapters/en/chapter9/5.mdx index 31c796ce6..b7c727a7f 100644 --- a/chapters/en/chapter9/5.mdx +++ b/chapters/en/chapter9/5.mdx @@ -1,8 +1,8 @@ # Integrations with the Hugging Face Hub - diff --git a/chapters/en/chapter9/6.mdx b/chapters/en/chapter9/6.mdx index 28a8c17f4..ce4ee3ecc 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -1,8 +1,8 @@ # Advanced Interface features - diff --git a/chapters/en/chapter9/7.mdx b/chapters/en/chapter9/7.mdx index 7d6de8c1b..eb0fd3f61 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -1,8 +1,8 @@ # Introduction to Gradio Blocks - diff --git a/chapters/en/chapter9/8.mdx b/chapters/en/chapter9/8.mdx index 9380d2e50..1c6dd04c2 100644 --- a/chapters/en/chapter9/8.mdx +++ b/chapters/en/chapter9/8.mdx @@ -1,5 +1,10 @@ # Gradio, check! + + This wraps up the chapter on building cool ML demos with Gradio - we hope you enjoyed it! To recap, in this chapter we learned: - How to create Gradio demos with the high-level `Interface` API, and how to configure different input and output modalities. diff --git a/chapters/en/chapter9/9.mdx b/chapters/en/chapter9/9.mdx index 7d2dfb8db..0bb98fca8 100644 --- a/chapters/en/chapter9/9.mdx +++ b/chapters/en/chapter9/9.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. What can you use Gradio to do? diff --git a/chapters/es/chapter1/1.mdx b/chapters/es/chapter1/1.mdx index fc53d0700..5c82e6bba 100644 --- a/chapters/es/chapter1/1.mdx +++ b/chapters/es/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introducción + + ## ¡Te damos la bienvenida al curso de 🤗! diff --git a/chapters/es/chapter1/10.mdx b/chapters/es/chapter1/10.mdx index 18e0262ae..6749eeee5 100644 --- a/chapters/es/chapter1/10.mdx +++ b/chapters/es/chapter1/10.mdx @@ -2,6 +2,11 @@ # Quiz de final de capítulo + + ¡Este capítulo cubrió una gran variedad de temas! No te preocupes si no entendiste todos los detalles; los siguientes capítulos te ayudarán a entender cómo funcionan las cosas detrás de cámaras. Por ahora, ¡revisemos lo que aprendiste en este capítulo! diff --git a/chapters/es/chapter1/2.mdx b/chapters/es/chapter1/2.mdx index 662379538..e799de435 100644 --- a/chapters/es/chapter1/2.mdx +++ b/chapters/es/chapter1/2.mdx @@ -1,5 +1,10 @@ # Procesamiento de Lenguaje Natural + + Antes de ver los Transformadores, hagamos una revisión rápida de qué es el procesamiento de lenguaje natural y por qué nos interesa. ## ¿Qué es PLN? diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index c725bb68d..539f62dca 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformadores, ¿qué pueden hacer? - diff --git a/chapters/es/chapter1/4.mdx b/chapters/es/chapter1/4.mdx index 3f28b352d..7d6b958b0 100644 --- a/chapters/es/chapter1/4.mdx +++ b/chapters/es/chapter1/4.mdx @@ -1,5 +1,10 @@ # ¿Cómo funcionan los Transformadores? + + En esta sección, daremos una mirada de alto nivel a la arquitectura de los Transformadores. ## Un poco de historia sobre los Transformadores diff --git a/chapters/es/chapter1/5.mdx b/chapters/es/chapter1/5.mdx index 0d587219f..b06cb7b75 100644 --- a/chapters/es/chapter1/5.mdx +++ b/chapters/es/chapter1/5.mdx @@ -1,5 +1,10 @@ # Modelos de codificadores + + Los modelos de codificadores usan únicamente el codificador del Transformador. En cada etapa, las capas de atención pueden acceder a todas las palabras de la oración inicial. Estos modelos se caracterizan generalmente por tener atención "bidireccional" y se suelen llamar modelos *auto-encoding*. diff --git a/chapters/es/chapter1/6.mdx b/chapters/es/chapter1/6.mdx index 803ba35bb..b4462cd8e 100644 --- a/chapters/es/chapter1/6.mdx +++ b/chapters/es/chapter1/6.mdx @@ -1,5 +1,10 @@ # Modelos de decodificadores + + Los modelos de decodificadores usan únicamente el decodificador del Transformador. En cada etapa, para una palabra dada las capas de atención pueden acceder sólamente a las palabras que se ubican antes en la oración. Estos modelos se suelen llamar modelos *auto-regressive*. diff --git a/chapters/es/chapter1/8.mdx b/chapters/es/chapter1/8.mdx index de17dfed0..aae4180a9 100644 --- a/chapters/es/chapter1/8.mdx +++ b/chapters/es/chapter1/8.mdx @@ -1,8 +1,8 @@ # Sesgos y limitaciones - diff --git a/chapters/es/chapter1/9.mdx b/chapters/es/chapter1/9.mdx index 71eb8b4f6..49228f34f 100644 --- a/chapters/es/chapter1/9.mdx +++ b/chapters/es/chapter1/9.mdx @@ -1,5 +1,10 @@ # Resumen + + En este capítulo viste cómo abordar diferentes tareas de PLN usando la función de alto nivel `pipeline()` de 🤗 Transformers. También viste como buscar modelos en el Hub, así como usar la API de Inferencia para probar los modelos directamente en tu navegador. Discutimos brevemente el funcionamiento de los Transformadores y hablamos sobre la importancia de la transferencia de aprendizaje y el ajuste. Un aspecto clave es que puedes usar la arquitectura completa o sólo el codificador o decodificador, dependiendo de qué tipo de tarea quieres resolver. La siguiente tabla resume lo anterior: diff --git a/chapters/es/chapter2/4.mdx b/chapters/es/chapter2/4.mdx index 7a3b40160..b53535f03 100644 --- a/chapters/es/chapter2/4.mdx +++ b/chapters/es/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/es/chapter2/5.mdx b/chapters/es/chapter2/5.mdx index 606f9bc89..366b53839 100644 --- a/chapters/es/chapter2/5.mdx +++ b/chapters/es/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/es/chapter3/1.mdx b/chapters/es/chapter3/1.mdx index 16ae3c5d2..2f0398087 100644 --- a/chapters/es/chapter3/1.mdx +++ b/chapters/es/chapter3/1.mdx @@ -2,6 +2,11 @@ # Introducción + + En el [Capítulo 2](/course/chapter2) exploramos como usar los tokenizadores y modelos preentrenados para realizar predicciones. Pero qué tal si deseas ajustar un modelo preentrenado con tu propio conjunto de datos ? {#if fw === 'pt'} diff --git a/chapters/es/chapter3/2.mdx b/chapters/es/chapter3/2.mdx index 3e7e3d91a..df59b8a2c 100644 --- a/chapters/es/chapter3/2.mdx +++ b/chapters/es/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/es/chapter3/4.mdx b/chapters/es/chapter3/4.mdx index b5bd3f7cf..96e07b050 100644 --- a/chapters/es/chapter3/4.mdx +++ b/chapters/es/chapter3/4.mdx @@ -1,8 +1,8 @@ # Un entrenamiento completo - diff --git a/chapters/es/chapter8/1.mdx b/chapters/es/chapter8/1.mdx index 4ebde8120..262a46d4f 100644 --- a/chapters/es/chapter8/1.mdx +++ b/chapters/es/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introducción + + Ahora sabes cómo abordar las tareas de PLN más comunes con la librería 🤗 Transformers, ¡deberías ser capaz de iniciar tus propios proyectos! En este capítulo exploraremos qué debes hacer cuando te encuentras con un problema. Aprenderás a cómo depurar (debug) exitosamente tu código o tu entrenamiento, y cómo solicitar ayuda si no consigues resolver el problema por ti mismo. Además, si crees que has encontrado un error (bug) en una de las librerías de Hugging Face, te indicaremos la mejor manera de reportarlo para que se resuelva tan pronto como sea posible. Más precisamente, en este capítulo aprenderás: diff --git a/chapters/es/chapter8/2.mdx b/chapters/es/chapter8/2.mdx index 34a4e9392..0e7553eae 100644 --- a/chapters/es/chapter8/2.mdx +++ b/chapters/es/chapter8/2.mdx @@ -1,8 +1,8 @@ # ¿Qué hacer cuando se produce un error? - diff --git a/chapters/fa/chapter1/1.mdx b/chapters/fa/chapter1/1.mdx index 5b826a280..88d60c612 100644 --- a/chapters/fa/chapter1/1.mdx +++ b/chapters/fa/chapter1/1.mdx @@ -1,6 +1,11 @@
# مقدمه + + ## به دوره‌ آموزشی هاگینگ‌فِیس خوش آمدید diff --git a/chapters/fa/chapter1/2.mdx b/chapters/fa/chapter1/2.mdx index 3342d6e47..9b3962c1c 100644 --- a/chapters/fa/chapter1/2.mdx +++ b/chapters/fa/chapter1/2.mdx @@ -1,6 +1,11 @@
# پردازش زبان طبیعی + + قبل از اینکه به سراغ مدل‌های ترنسفومر برویم، بیایید نگاهی سریع بیاندازیم به اینکه پردازش زبان طبیعی[^1] چیست و چرا برای ما حائز اهمیت است. ## NLP چیست؟ diff --git a/chapters/fa/chapter2/1.mdx b/chapters/fa/chapter2/1.mdx index 5d61827cf..ad6bdd22e 100644 --- a/chapters/fa/chapter2/1.mdx +++ b/chapters/fa/chapter2/1.mdx @@ -2,6 +2,11 @@
# مقدمه + + همان طور که در [فصل اول](/course/chapter1) دیدید، مدل‌های ترنسفورمر معمولا بسیار بزرگ هستند. با داشتن میلیون‌ها یا حتی ده‌ها میلیارد پارامتر، تعلیم و بکارگیری این مدل‌ها کار بسیار پیچیده‌ای است. علاوه بر این،‌ تقریبا هر روز مدل‌های جدیدی عرضه می‌شوند که هرکدام شیوه پیاده‌سازی خود را دارند و امتحان کردن تمام آن‌ها کار آسانی نیست. کتابخانه ترنسفومرهای هاگینگ‌فِیس برای حل این مشکل تولید شده است. هدف آن، ارائه یک API واحد برای بارگذاری، تعلیم و ذخیره‌سازی مدل‌های ترنسفورمر است. ویژگی های اصلی این کتابخانه از این قرار است: diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 71abc5e16..9ae1d64e2 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -5,18 +5,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fa/chapter2/3.mdx b/chapters/fa/chapter2/3.mdx index 0155c5634..eace2dc12 100644 --- a/chapters/fa/chapter2/3.mdx +++ b/chapters/fa/chapter2/3.mdx @@ -5,18 +5,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index b3f520423..cf96cdbf8 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -4,6 +4,11 @@ # مقدمه + + در [فصل ۲](/course/chapter2) نحوه استفاده از توکِنایزرها و مدل‌های از پیش تعلیم دیده را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان کوک‌ کنید؟ {#if fw === 'pt'} diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 092a92b30..deabbc5f0 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -6,18 +6,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fa/chapter4/1.mdx b/chapters/fa/chapter4/1.mdx index b071b63df..7f6995ac0 100644 --- a/chapters/fa/chapter4/1.mdx +++ b/chapters/fa/chapter4/1.mdx @@ -1,6 +1,11 @@
# هاب هاگینگ‌فِیس + + [هاب هاگینگ‌فِیس](https://huggingface.co/) –- وب‌سایت اصلی ما –- پلتفرمی مرکزی است که به همه امکان مشارکت، کشف و استفاده از جدیدترین و بهترین مدل‌ها و دیتاسِت‌ها را می‌دهد. این هاب طیف گسترده‌ای از مدل‌ها را در خود جای داده، که بالغ بر ۱۰ هزار مورد از آن‌ها در دسترس عموم قرار دارد. در این فصل بر روی مدل‌ها متمرکز شده و در فصل ۵ نیز نگاهی به دیتاسِت‌ها خواهیم داشت. مدل‌های موجود در هاب، محدود به ترنسفورمرهای هاگینگ‌فِیس یا حتی NLP نیستند. از جمله آنها می‌توان مدل‌هایی از قبیل [Flair](https://github.com/flairNLP/flair) و [AllenNLP](https://github.com/allenai/allennlp) برای پردازش زبان طبیعی، [Asteroid](https://github.com/asteroid-team/asteroid) و [pyannote](https://github.com/pyannote/pyannote-audio) برای پردازش گفتار، و [timm](https://github.com/rwightman/pytorch-image-models) را برای پردازش تصویر برشمرد. diff --git a/chapters/fa/chapter4/2.mdx b/chapters/fa/chapter4/2.mdx index 01960357d..2514c1849 100644 --- a/chapters/fa/chapter4/2.mdx +++ b/chapters/fa/chapter4/2.mdx @@ -5,18 +5,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index a0ff68898..6dd1423c5 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -1,55 +1,60 @@ -# Introduction - -## Bienvenue au cours 🤗 ! - - - -Ce cours vous apprendra à utiliser les bibliothèques de NLP de l'écosystème [Hugging Face](https://huggingface.co/) : [🤗 *Transformers*](https://github.com/huggingface/transformers), [🤗 *Datasets*](https://github.com/huggingface/datasets), [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers) et [🤗 *Accelerate*](https://github.com/huggingface/accelerate), ainsi que le [*Hub*](https://huggingface.co/models). C'est totalement gratuit et sans publicité. - -## À quoi s'attendre ? - -Voici un bref aperçu du cours : - -
-Bref aperçu du contenu du cours. - -
- -- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ce chapitre, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* ! -- Les chapitres 5 à 8 présentent les bases des librairies 🤗 *Datasets* et 🤗 *Tokenizers* ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. -- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les *transformers* peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour des environnements de production. Enfin, vous serez prêt à appliquer 🤗 *Transformers* à (presque) n'importe quel problème d'apprentissage automatique ! - -Ce cours : - -* requiert un bon niveau en Python, -* se comprend mieux si vous avez déjà suivi un cours d'introduction à l'apprentissage profond comme [fast.ai's](https://www.fast.ai/), [*Practical Deep Learning for Coders*](https://course.fast.ai/) ou un des cours développés par [*DeepLearning.AI*](https://www.deeplearning.ai/), -* n'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider. - -Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](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) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître! - -## Qui sommes-nous ? - -À propos des auteurs de ce cours : - -*Abubakar Abid** a obtenu son doctorat à Stanford en apprentissage automatique appliqué. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python open-source qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. - -**Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. - -**Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. - -**Sylvain Gugger** est ingénieur recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. - -**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé de l'Université de New York en informatique. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. - -**Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. - -**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet BigScience. - -**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). - -**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. - -Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez : -* à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification, -* l'architecture d'un *transformer*, -* comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur ainsi que leurs différents cas d'usage. +# Introduction + + + +## Bienvenue au cours 🤗 ! + + + +Ce cours vous apprendra à utiliser les bibliothèques de NLP de l'écosystème [Hugging Face](https://huggingface.co/) : [🤗 *Transformers*](https://github.com/huggingface/transformers), [🤗 *Datasets*](https://github.com/huggingface/datasets), [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers) et [🤗 *Accelerate*](https://github.com/huggingface/accelerate), ainsi que le [*Hub*](https://huggingface.co/models). C'est totalement gratuit et sans publicité. + +## À quoi s'attendre ? + +Voici un bref aperçu du cours : + +
+Bref aperçu du contenu du cours. + +
+ +- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ce chapitre, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* ! +- Les chapitres 5 à 8 présentent les bases des librairies 🤗 *Datasets* et 🤗 *Tokenizers* ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. +- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les *transformers* peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour des environnements de production. Enfin, vous serez prêt à appliquer 🤗 *Transformers* à (presque) n'importe quel problème d'apprentissage automatique ! + +Ce cours : + +* requiert un bon niveau en Python, +* se comprend mieux si vous avez déjà suivi un cours d'introduction à l'apprentissage profond comme [fast.ai's](https://www.fast.ai/), [*Practical Deep Learning for Coders*](https://course.fast.ai/) ou un des cours développés par [*DeepLearning.AI*](https://www.deeplearning.ai/), +* n'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider. + +Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](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) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître! + +## Qui sommes-nous ? + +À propos des auteurs de ce cours : + +*Abubakar Abid** a obtenu son doctorat à Stanford en apprentissage automatique appliqué. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python open-source qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. + +**Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. + +**Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. + +**Sylvain Gugger** est ingénieur recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. + +**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé de l'Université de New York en informatique. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. + +**Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. + +**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet BigScience. + +**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). + +**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. + +Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez : +* à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification, +* l'architecture d'un *transformer*, +* comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur ainsi que leurs différents cas d'usage. diff --git a/chapters/fr/chapter1/10.mdx b/chapters/fr/chapter1/10.mdx index d48d06003..889612715 100644 --- a/chapters/fr/chapter1/10.mdx +++ b/chapters/fr/chapter1/10.mdx @@ -1,258 +1,263 @@ - - -# Quiz de fin de chapitre - -Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement. - -Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appris dans ce chapitre ! - - -### 1. Explorez le *Hub* et cherchez le modèle `roberta-large-mnli`. Quelle tâche accomplit-il ? - - -page roberta-large-mnli." - }, - { - text: "Classification de texte", - explain: "Pour être plus précis, il classifie si deux phrases sont logiquement liées entre elles parmis trois possibilités (contradiction, neutre, lien). Il s'agit d'une tâche aussi appelée inference de langage naturel.", - correct: true - }, - { - text: "Génération de texte", - explain: "Regardez à nouveau sur la page roberta-large-mnli." - } - ]} -/> - -### 2. Que renvoie le code suivant ? - -```py -from transformers import pipeline - -ner = pipeline("ner", grouped_entities=True) -ner( - "My name is Sylvain and I work at Hugging Face in Brooklyn." -) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. -``` - -d'analyse de sentiment (sentiment-analysis dans la documentation d'Hugging-Face)." - }, - { - text: "Il renvoie un texte généré qui complète cette phrase.", - explain: "Cela correspondrait au pipeline de génération de texte (text-generation dans la documentation d'Hugging-Face)." - }, - { - text: "Il renvoie les entités nommées dans cette phrase, telles que les personnes, les organisations ou lieux.", - explain: "De plus, avec grouped_entities=True, cela regroupe les mots appartenant à la même entité, comme par exemple \"Hugging Face\".", - correct: true - } - ]} -/> - -### 3. Que remplace « ... » dans ce code ? - -```py -from transformers import pipeline - -filler = pipeline("fill-mask", model="bert-base-cased") -result = filler("...") -``` - - has been waiting for you. # Ce <mask> vous attend.", - explain: "Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." - }, - { - text: "This [MASK] has been waiting for you. # Ce [MASK] vous attend.", - explain: "Le modèle utilise [MASK] comme mot-masque.", - correct: true - }, - { - text: "This man has been waiting for you. # Cet homme vous attend.", - explain: "Ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque." - } - ]} -/> - -### 4. Pourquoi ce code ne fonctionne-t-il pas ? - -```py -from transformers import pipeline - -classifier = pipeline("zero-shot-classification") -result = classifier( - "This is a course about the Transformers library" -) # C'est un cours sur la bibliothèque Transformers -``` - -candidate_labels=[...].", - correct: true - }, - { - text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.", - explain: "Bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)." - }, - { - text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", - explain: "Nous n'avons aucun commentaire pour cette réponse !", - }, - { - text: "Ce pipeline nécessite des phrases plus longues, celle-ci est trop courte.", - explain: "Notez que si un texte est très long, il est tronqué par le pipeline." - } - ]} -/> - -### 5. Que signifie « apprentissage par transfert » ? - - - -### 6. Vrai ou faux ? Un modèle de langage n'a généralement pas besoin d'étiquettes pour son pré-entraînement. - - -autosupervisé, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", - correct: true - }, - { - text: "Faux", - explain: "Ce n'est pas la bonne réponse." - } - ]} -/> - -### 7. Sélectionnez la phrase qui décrit le mieux les termes « modèle », « architecture » et « poids ». - - - - -### 8. Parmi ces types de modèles, quel est le plus approprié pour générer du texte à partir d'une instruction (*prompt*) ? - - - -### 9. Parmi ces types de modèles, quel est le plus approprié pour le résumé de texte ? - - - -### 10. Quel type de modèle utiliseriez-vous pour classifier des entrées de texte en fonction de certains labels ? - - - -### 11. De quelle source possible peut être le biais observé dans un modèle ? - -finetunée d'un modèle pré-entraîné et il a conservé ses biais.", - explain: "Avec l'apprentissage par transfert, les biais du modèle pré-entraîné perdurent dans le modèle finetuné.", - correct: true - }, - { - text: "Le modèle a été entraîné sur des données qui sont biaisées.", - explain: "Ceci représente la source de biais la plus évidente mais n'est pas la seule possible.", - correct: true - }, - { - text: "La métrique optimisée lors de l'entraînement du modèle est biaisée.", - explain: "Une source moins évidente est la façon dont le modèle est entraîné. Votre modèle va de façon aveugle optimiser la métrique que vous avez sélectionnée, sans prendre aucun recul.", - correct: true - } - ]} -/> + + +# Quiz de fin de chapitre + + + +Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement. + +Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appris dans ce chapitre ! + + +### 1. Explorez le *Hub* et cherchez le modèle `roberta-large-mnli`. Quelle tâche accomplit-il ? + + +page roberta-large-mnli." + }, + { + text: "Classification de texte", + explain: "Pour être plus précis, il classifie si deux phrases sont logiquement liées entre elles parmis trois possibilités (contradiction, neutre, lien). Il s'agit d'une tâche aussi appelée inference de langage naturel.", + correct: true + }, + { + text: "Génération de texte", + explain: "Regardez à nouveau sur la page roberta-large-mnli." + } + ]} +/> + +### 2. Que renvoie le code suivant ? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner( + "My name is Sylvain and I work at Hugging Face in Brooklyn." +) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. +``` + +d'analyse de sentiment (sentiment-analysis dans la documentation d'Hugging-Face)." + }, + { + text: "Il renvoie un texte généré qui complète cette phrase.", + explain: "Cela correspondrait au pipeline de génération de texte (text-generation dans la documentation d'Hugging-Face)." + }, + { + text: "Il renvoie les entités nommées dans cette phrase, telles que les personnes, les organisations ou lieux.", + explain: "De plus, avec grouped_entities=True, cela regroupe les mots appartenant à la même entité, comme par exemple \"Hugging Face\".", + correct: true + } + ]} +/> + +### 3. Que remplace « ... » dans ce code ? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you. # Ce <mask> vous attend.", + explain: "Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." + }, + { + text: "This [MASK] has been waiting for you. # Ce [MASK] vous attend.", + explain: "Le modèle utilise [MASK] comme mot-masque.", + correct: true + }, + { + text: "This man has been waiting for you. # Cet homme vous attend.", + explain: "Ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque." + } + ]} +/> + +### 4. Pourquoi ce code ne fonctionne-t-il pas ? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier( + "This is a course about the Transformers library" +) # C'est un cours sur la bibliothèque Transformers +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.", + explain: "Bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)." + }, + { + text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", + explain: "Nous n'avons aucun commentaire pour cette réponse !", + }, + { + text: "Ce pipeline nécessite des phrases plus longues, celle-ci est trop courte.", + explain: "Notez que si un texte est très long, il est tronqué par le pipeline." + } + ]} +/> + +### 5. Que signifie « apprentissage par transfert » ? + + + +### 6. Vrai ou faux ? Un modèle de langage n'a généralement pas besoin d'étiquettes pour son pré-entraînement. + + +autosupervisé, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", + correct: true + }, + { + text: "Faux", + explain: "Ce n'est pas la bonne réponse." + } + ]} +/> + +### 7. Sélectionnez la phrase qui décrit le mieux les termes « modèle », « architecture » et « poids ». + + + + +### 8. Parmi ces types de modèles, quel est le plus approprié pour générer du texte à partir d'une instruction (*prompt*) ? + + + +### 9. Parmi ces types de modèles, quel est le plus approprié pour le résumé de texte ? + + + +### 10. Quel type de modèle utiliseriez-vous pour classifier des entrées de texte en fonction de certains labels ? + + + +### 11. De quelle source possible peut être le biais observé dans un modèle ? + +finetunée d'un modèle pré-entraîné et il a conservé ses biais.", + explain: "Avec l'apprentissage par transfert, les biais du modèle pré-entraîné perdurent dans le modèle finetuné.", + correct: true + }, + { + text: "Le modèle a été entraîné sur des données qui sont biaisées.", + explain: "Ceci représente la source de biais la plus évidente mais n'est pas la seule possible.", + correct: true + }, + { + text: "La métrique optimisée lors de l'entraînement du modèle est biaisée.", + explain: "Une source moins évidente est la façon dont le modèle est entraîné. Votre modèle va de façon aveugle optimiser la métrique que vous avez sélectionnée, sans prendre aucun recul.", + correct: true + } + ]} +/> diff --git a/chapters/fr/chapter1/2.mdx b/chapters/fr/chapter1/2.mdx index ef5803d79..c3b8db6ac 100644 --- a/chapters/fr/chapter1/2.mdx +++ b/chapters/fr/chapter1/2.mdx @@ -1,21 +1,26 @@ -# Traitement du langage naturel (NLP pour Natural Language Processing) - -Avant de commencer avec les *transformers*, voyons succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. - -## Le NLP, qu'est-ce que c'est ? - -Le traitement du langage naturel est un domaine de linguistique et d'apprentissage automatique se concentrant sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots. - -La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples : - -- **Classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc. -- **Classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc. -- **Génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc. -- **Extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc. -- **Génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc. - -Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. - -## Pourquoi est-ce difficile ? - -Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase « j'ai faim », nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que « j'ai faim » et « je suis triste », nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles d'apprentissage automatique, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte et nous allons voir quelques-unes de ces méthodes dans le chapitre suivant. +# Traitement du langage naturel (NLP pour Natural Language Processing) + + + +Avant de commencer avec les *transformers*, voyons succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. + +## Le NLP, qu'est-ce que c'est ? + +Le traitement du langage naturel est un domaine de linguistique et d'apprentissage automatique se concentrant sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots. + +La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples : + +- **Classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc. +- **Classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc. +- **Génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc. +- **Extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc. +- **Génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc. + +Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. + +## Pourquoi est-ce difficile ? + +Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase « j'ai faim », nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que « j'ai faim » et « je suis triste », nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles d'apprentissage automatique, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte et nous allons voir quelques-unes de ces méthodes dans le chapitre suivant. diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index 5428aff2b..d10b714e2 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -1,381 +1,381 @@ -# Que peuvent faire les transformers ? - - - -Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`. - - -👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un notebook Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. - -Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre configuration. - - -## Les transformers sont partout ! - -Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face, les *transformers* et qui contribuent aussi à la communauté en partageant leurs modèles : - -Companies using Hugging Face - -La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! - - - ⚠️ Le Hub n'est pas limité aux transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! - - -Avant de découvrir en détail comment les *transformers* fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes intéressants de NLP. - -## Travailler avec les pipelines - - - -L'outil le plus basique de la bibliothèque 🤗 *Transformers* est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible : - -```python -from transformers import pipeline - -classifier = pipeline("sentiment-analysis") -classifier( - "I've been waiting for a HuggingFace course my whole life." -) # J'ai attendu un cours d'HuggingFace toute ma vie. -``` - -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}] -``` - -On peut même passer plusieurs phrases ! - -```python -classifier( - [ - "I've been waiting for a HuggingFace course my whole life.", - "I hate this so much!", - ] # « J'ai attendu un cours d'HuggingFace toute ma vie. », « Je déteste tellement ça ! » -) -``` - -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}, - {'label': 'NEGATIVE', 'score': 0.9994558095932007}] -``` - -Par défaut, ce pipeline sélectionne un modèle pré-entraîné qui a été spécifiquement entraîné pour l'analyse de sentiment en anglais. Le modèle est téléchargé et mis en cache lorsque vous créez l'objet `classifier`. Si vous réexécutez la commande, c'est le modèle mis en cache qui sera utilisé et il n'y a pas besoin de télécharger le modèle à nouveau. - -Il y a trois étapes principales lorsque vous passez du texte à un pipeline : - -1. le texte est prétraité pour qu'il ait un format compréhensible par le modèle, -2. les données prétraitées sont passées au modèle, -3. les prédictions du modèle sont post-traitées de sorte que vous puissiez les comprendre. - - -Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.co/transformers/main_classes/pipelines.html) : - -- `feature-extraction` (pour obtenir la représentation vectorielle d'un texte) -- `fill-mask` -- `ner` (*named entity recognition* ou reconnaissance d'entités nommées en français) -- `question-answering` -- `sentiment-analysis` -- `summarization` -- `text-generation` -- `translation` -- `zero-shot-classification` - -Regardons de plus près certains d'entre eux ! - -## Zero-shot classification - -Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de textes est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. - -```python -from transformers import pipeline - -classifier = pipeline("zero-shot-classification") -classifier( - "This is a course about the Transformers library", - # C'est un cours sur la bibliothèque Transformers - candidate_labels=["education", "politics", "business"], -) -``` - -```python out -{'sequence': 'This is a course about the Transformers library', -# C'est un cours sur la bibliothèque Transformers - 'labels': ['education', 'business', 'politics'], - 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} -``` - -Ce pipeline est appelé _zero-shot_ car vous n'avez pas besoin d'entraîner spécifiquement le modèle sur vos données pour l'utiliser. Il peut directement retourner des scores de probabilité pour n'importe quel ensemble de labels que vous choisissez ! - - - -✏️ **Essayez !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne. - - - - -## Génération de texte - -Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. Cette fonction est similaire à la fonction de texte prédictif que l'on trouve sur de nombreux téléphones portables. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous. - -```python -from transformers import pipeline - -generator = pipeline("text-generation") -generator( - "In this course, we will teach you how to" -) # Dans ce cours, nous vous enseignerons comment -``` - -```python out -[{'generated_text': 'In this course, we will teach you how to understand and use ' - # Dans ce cours, nous vous enseignerons comment comprendre et utiliser - 'data flow and data interchange when handling user data. We ' - # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous - 'will be working with one or more of the most commonly used ' - # travailleront avec un ou plusieurs des plus couramment utilisés - 'data flows — data flows of various types, as seen by the ' - # flux de données - flux de données de différents types, tels qu'ils sont vus par - 'HTTP'}] # HTTP -``` - -Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`. - - - -✏️ **Essayez !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune. - - - - -## Utiliser n'importe quel modèle du Hub dans un pipeline - -Les exemples précédents utilisaient le modèle par défaut pour la tâche en question mais vous pouvez aussi choisir un modèle particulier du *Hub* et l'utiliser dans un pipeline pour une tâche spécifique comme par exemple la génération de texte. Rendez-vous sur le [*Hub*](https://huggingface.co/models) et cliquez sur le *filtre* correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). - -Essayons le modèle [`distilgpt2`](https://huggingface.co/distilgpt2) ! Voici comment charger le modèle dans le même pipeline que précédemment : - -```python -from transformers import pipeline - -generator = pipeline("text-generation", model="distilgpt2") -generator( - "In this course, we will teach you how to", - # Dans ce cours, nous vous enseignerons comment - max_length=30, - num_return_sequences=2, -) -``` - -```python out -[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' - # Dans ce cours, nous vous enseignerons comment manipuler le monde et - 'move your mental and physical capabilities to your advantage.'}, - # utiliser vos capacités mentales et physiques à votre avantage. - {'generated_text': 'In this course, we will teach you how to become an expert and ' - # Dans ce cours, nous vous apprendrons comment devenir un expert et - 'practice realtime, and with a hands on experience on both real ' - # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais - 'time and real'}] - # temps et réel -``` - -Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues. - -Une fois que vous avez choisi un modèle, vous verrez que vous pouvez tester son fonctionnement en ligne directement. Cela vous permet de tester rapidement les capacités du modèle avant de le télécharger. - - - -✏️ **Essayez !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le *widget* et l'utiliser dans un pipeline ! - - - -### L'API d'inférence - -Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée. - -L'API d'inférence qui est utilisée par le *widget* est également disponible en tant que produit payant si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. - -## Remplacement des mots manquants - -Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manquants d'un texte donné : - -```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.', -# Ce cours vous apprendra tout sur les modèles mathématiques. - 'score': 0.19619831442832947, - 'token': 30412, - 'token_str': ' mathematical'}, - {'sequence': 'This course will teach you all about computational models.', - # Ce cours vous apprendra tout sur les modèles mathématiques. - 'score': 0.04052725434303284, - 'token': 38163, - 'token_str': ' computational'}] -``` - -L'argument `top_k` permet de contrôler le nombre de possibilités que vous souhaitez afficher. Notez que dans ce cas, le modèle remplace le mot spécial ``, qui est souvent appelé un *mot masqué*. D'autres modèles permettant de remplacer les mots manquants peuvent avoir des mots masqués différents, donc il est toujours bon de vérifier le mot masqué approprié lorsque vous comparez d'autres modèles. Une façon de le vérifier est de regarder le mot masqué utilisé dans l'outil de test de la page du modèle. - - - -✏️ **Essayez !** Recherchez le modèle `bert-base-cased` sur le *Hub* et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ? - - - -## Reconnaissance d'entités nommées - -La reconnaissance d'entités nommées ou NER (pour *Named Entity Recognition*) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple : - -```python -from transformers import pipeline - -ner = pipeline("ner", grouped_entities=True) -ner( - "My name is Sylvain and I work at Hugging Face in Brooklyn." -) # Je m'appelle Sylvain et je travaille à Hugging Face à 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} -] -``` - -Nous pouvons voir que le modèle a correctement identifié Sylvain comme une personne (PER), Hugging Face comme une organisation (ORG) et Brooklyn comme un lieu (LOC). - -Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité : ici le modèle à correctement regroupé `Hugging` et `Face` comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans le prochain chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux : `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux. - - - -✏️ **Essayez !** Recherchez sur le *Hub* un modèle capable de reconnaître les différentes parties du langage (généralement abrégé en POS pour *Part-of-speech*) en anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ? - - - -## Réponse à des questions - -Le pipeline `question-answering` répond à des questions en utilisant des informations données en contexte : - -```python -from transformers import pipeline - -question_answerer = pipeline("question-answering") -question_answerer( - question="Where do I work?", # Où est-ce que je travaille ? - context="My name is Sylvain and I work at Hugging Face in Brooklyn", - # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. -) -``` - -```python out -{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} -``` - -Notez que ce pipeline fonctionne par extraction d'information depuis le contexte fourni, il ne génère pas la réponse. - -## Résumé - -Le résumé est une tâche de réduction d'un texte en un texte plus court, tout en gardant tous (ou presque tous) les aspects importants référencés dans le texte. Voici un exemple : - -```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. -""" -) - -""" - L'Amérique a changé de façon spectaculaire au cours des dernières années. Non seulement le nombre de - diplômés dans les disciplines traditionnelles de l'ingénierie telles que le génie mécanique, civil, - l'électricité, la chimie et l'aéronautique a diminué, mais dans la plupart - des grandes universités américaines, les programmes d'études d'ingénierie se concentrent désormais sur - et encouragent largement l'étude des sciences de l'ingénieur. Par conséquent, il y a - de moins en moins d'offres dans les sujets d'ingénierie traitant de l'infrastructure, - l'environnement et les questions connexes, et une plus grande concentration sur les sujets de haute - technologie, qui soutiennent en grande partie des développements scientifiques de plus en plus - complexes. Si cette dernière est importante, elle ne doit pas se faire au détriment - de l'ingénierie plus traditionnelle. - - Les économies en développement rapide telles que la Chine et l'Inde, ainsi que d'autres - pays industrialisés d'Europe et d'Asie, continuent d'encourager et de promouvoir - l'enseignement de l'ingénierie. La Chine et l'Inde, respectivement, diplôment - six et huit fois plus d'ingénieurs traditionnels que les États-Unis. - Les autres pays industriels maintiennent au minimum leur production, tandis que l'Amérique - souffre d'une baisse de plus en plus importante du nombre de diplômés en ingénierie - et un manque d'ingénieurs bien formés. -""" -``` - -```python out -[{'summary_text': ' America has changed dramatically during recent years . The ' - # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le - 'number of engineering graduates in the U.S. has declined in ' - # nombre de diplômés en ingénierie aux États-Unis a diminué dans - 'traditional engineering disciplines such as mechanical, civil ' - # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil - ', electrical, chemical, and aeronautical engineering . Rapidly ' - # l'électricité, la chimie et l'aéronautique. Les économies - 'developing economies such as China and India, as well as other ' - # en développement rapide comme la Chine et l'Inde, ainsi que d'autres - 'industrial countries in Europe and Asia, continue to encourage ' - # pays industriels d'Europe et d'Asie, continuent d'encourager - 'and advance engineering.'}] - # et à faire progresser l'ingénierie. -``` - -Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. - - -## Traduction - -Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [*Hub*](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais : - -```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.'}] -``` - -Comme pour la génération de texte et le résumé de texte, il est possible de spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. - - - -✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. - - - -Les pipelines présentés jusqu'ici sont principalement destinés à des fins de démonstration. Ils ont été programmés pour des tâches spécifiques et ne peuvent pas effectuer de variations de celles-ci. Dans le chapitre suivant, vous apprendrez ce qu'il y a dans un `pipeline()` et comment modifier son comportement. +# Que peuvent faire les transformers ? + + + +Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`. + + +👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un notebook Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. + +Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre configuration. + + +## Les transformers sont partout ! + +Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face, les *transformers* et qui contribuent aussi à la communauté en partageant leurs modèles : + +Companies using Hugging Face + +La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! + + + ⚠️ Le Hub n'est pas limité aux transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! + + +Avant de découvrir en détail comment les *transformers* fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes intéressants de NLP. + +## Travailler avec les pipelines + + + +L'outil le plus basique de la bibliothèque 🤗 *Transformers* est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible : + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + "I've been waiting for a HuggingFace course my whole life." +) # J'ai attendu un cours d'HuggingFace toute ma vie. +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +On peut même passer plusieurs phrases ! + +```python +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] # « J'ai attendu un cours d'HuggingFace toute ma vie. », « Je déteste tellement ça ! » +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Par défaut, ce pipeline sélectionne un modèle pré-entraîné qui a été spécifiquement entraîné pour l'analyse de sentiment en anglais. Le modèle est téléchargé et mis en cache lorsque vous créez l'objet `classifier`. Si vous réexécutez la commande, c'est le modèle mis en cache qui sera utilisé et il n'y a pas besoin de télécharger le modèle à nouveau. + +Il y a trois étapes principales lorsque vous passez du texte à un pipeline : + +1. le texte est prétraité pour qu'il ait un format compréhensible par le modèle, +2. les données prétraitées sont passées au modèle, +3. les prédictions du modèle sont post-traitées de sorte que vous puissiez les comprendre. + + +Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.co/transformers/main_classes/pipelines.html) : + +- `feature-extraction` (pour obtenir la représentation vectorielle d'un texte) +- `fill-mask` +- `ner` (*named entity recognition* ou reconnaissance d'entités nommées en français) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Regardons de plus près certains d'entre eux ! + +## Zero-shot classification + +Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de textes est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + # C'est un cours sur la bibliothèque Transformers + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', +# C'est un cours sur la bibliothèque Transformers + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +Ce pipeline est appelé _zero-shot_ car vous n'avez pas besoin d'entraîner spécifiquement le modèle sur vos données pour l'utiliser. Il peut directement retourner des scores de probabilité pour n'importe quel ensemble de labels que vous choisissez ! + + + +✏️ **Essayez !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne. + + + + +## Génération de texte + +Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. Cette fonction est similaire à la fonction de texte prédictif que l'on trouve sur de nombreux téléphones portables. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator( + "In this course, we will teach you how to" +) # Dans ce cours, nous vous enseignerons comment +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + # Dans ce cours, nous vous enseignerons comment comprendre et utiliser + 'data flow and data interchange when handling user data. We ' + # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous + 'will be working with one or more of the most commonly used ' + # travailleront avec un ou plusieurs des plus couramment utilisés + 'data flows — data flows of various types, as seen by the ' + # flux de données - flux de données de différents types, tels qu'ils sont vus par + 'HTTP'}] # HTTP +``` + +Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`. + + + +✏️ **Essayez !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune. + + + + +## Utiliser n'importe quel modèle du Hub dans un pipeline + +Les exemples précédents utilisaient le modèle par défaut pour la tâche en question mais vous pouvez aussi choisir un modèle particulier du *Hub* et l'utiliser dans un pipeline pour une tâche spécifique comme par exemple la génération de texte. Rendez-vous sur le [*Hub*](https://huggingface.co/models) et cliquez sur le *filtre* correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). + +Essayons le modèle [`distilgpt2`](https://huggingface.co/distilgpt2) ! Voici comment charger le modèle dans le même pipeline que précédemment : + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + # Dans ce cours, nous vous enseignerons comment + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + # Dans ce cours, nous vous enseignerons comment manipuler le monde et + 'move your mental and physical capabilities to your advantage.'}, + # utiliser vos capacités mentales et physiques à votre avantage. + {'generated_text': 'In this course, we will teach you how to become an expert and ' + # Dans ce cours, nous vous apprendrons comment devenir un expert et + 'practice realtime, and with a hands on experience on both real ' + # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais + 'time and real'}] + # temps et réel +``` + +Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues. + +Une fois que vous avez choisi un modèle, vous verrez que vous pouvez tester son fonctionnement en ligne directement. Cela vous permet de tester rapidement les capacités du modèle avant de le télécharger. + + + +✏️ **Essayez !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le *widget* et l'utiliser dans un pipeline ! + + + +### L'API d'inférence + +Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée. + +L'API d'inférence qui est utilisée par le *widget* est également disponible en tant que produit payant si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. + +## Remplacement des mots manquants + +Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manquants d'un texte donné : + +```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.', +# Ce cours vous apprendra tout sur les modèles mathématiques. + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + # Ce cours vous apprendra tout sur les modèles mathématiques. + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +L'argument `top_k` permet de contrôler le nombre de possibilités que vous souhaitez afficher. Notez que dans ce cas, le modèle remplace le mot spécial ``, qui est souvent appelé un *mot masqué*. D'autres modèles permettant de remplacer les mots manquants peuvent avoir des mots masqués différents, donc il est toujours bon de vérifier le mot masqué approprié lorsque vous comparez d'autres modèles. Une façon de le vérifier est de regarder le mot masqué utilisé dans l'outil de test de la page du modèle. + + + +✏️ **Essayez !** Recherchez le modèle `bert-base-cased` sur le *Hub* et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ? + + + +## Reconnaissance d'entités nommées + +La reconnaissance d'entités nommées ou NER (pour *Named Entity Recognition*) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple : + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner( + "My name is Sylvain and I work at Hugging Face in Brooklyn." +) # Je m'appelle Sylvain et je travaille à Hugging Face à 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} +] +``` + +Nous pouvons voir que le modèle a correctement identifié Sylvain comme une personne (PER), Hugging Face comme une organisation (ORG) et Brooklyn comme un lieu (LOC). + +Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité : ici le modèle à correctement regroupé `Hugging` et `Face` comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans le prochain chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux : `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux. + + + +✏️ **Essayez !** Recherchez sur le *Hub* un modèle capable de reconnaître les différentes parties du langage (généralement abrégé en POS pour *Part-of-speech*) en anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ? + + + +## Réponse à des questions + +Le pipeline `question-answering` répond à des questions en utilisant des informations données en contexte : + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", # Où est-ce que je travaille ? + context="My name is Sylvain and I work at Hugging Face in Brooklyn", + # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +Notez que ce pipeline fonctionne par extraction d'information depuis le contexte fourni, il ne génère pas la réponse. + +## Résumé + +Le résumé est une tâche de réduction d'un texte en un texte plus court, tout en gardant tous (ou presque tous) les aspects importants référencés dans le texte. Voici un exemple : + +```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. +""" +) + +""" + L'Amérique a changé de façon spectaculaire au cours des dernières années. Non seulement le nombre de + diplômés dans les disciplines traditionnelles de l'ingénierie telles que le génie mécanique, civil, + l'électricité, la chimie et l'aéronautique a diminué, mais dans la plupart + des grandes universités américaines, les programmes d'études d'ingénierie se concentrent désormais sur + et encouragent largement l'étude des sciences de l'ingénieur. Par conséquent, il y a + de moins en moins d'offres dans les sujets d'ingénierie traitant de l'infrastructure, + l'environnement et les questions connexes, et une plus grande concentration sur les sujets de haute + technologie, qui soutiennent en grande partie des développements scientifiques de plus en plus + complexes. Si cette dernière est importante, elle ne doit pas se faire au détriment + de l'ingénierie plus traditionnelle. + + Les économies en développement rapide telles que la Chine et l'Inde, ainsi que d'autres + pays industrialisés d'Europe et d'Asie, continuent d'encourager et de promouvoir + l'enseignement de l'ingénierie. La Chine et l'Inde, respectivement, diplôment + six et huit fois plus d'ingénieurs traditionnels que les États-Unis. + Les autres pays industriels maintiennent au minimum leur production, tandis que l'Amérique + souffre d'une baisse de plus en plus importante du nombre de diplômés en ingénierie + et un manque d'ingénieurs bien formés. +""" +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le + 'number of engineering graduates in the U.S. has declined in ' + # nombre de diplômés en ingénierie aux États-Unis a diminué dans + 'traditional engineering disciplines such as mechanical, civil ' + # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil + ', electrical, chemical, and aeronautical engineering . Rapidly ' + # l'électricité, la chimie et l'aéronautique. Les économies + 'developing economies such as China and India, as well as other ' + # en développement rapide comme la Chine et l'Inde, ainsi que d'autres + 'industrial countries in Europe and Asia, continue to encourage ' + # pays industriels d'Europe et d'Asie, continuent d'encourager + 'and advance engineering.'}] + # et à faire progresser l'ingénierie. +``` + +Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. + + +## Traduction + +Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [*Hub*](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais : + +```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.'}] +``` + +Comme pour la génération de texte et le résumé de texte, il est possible de spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. + + + +✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. + + + +Les pipelines présentés jusqu'ici sont principalement destinés à des fins de démonstration. Ils ont été programmés pour des tâches spécifiques et ne peuvent pas effectuer de variations de celles-ci. Dans le chapitre suivant, vous apprendrez ce qu'il y a dans un `pipeline()` et comment modifier son comportement. diff --git a/chapters/fr/chapter1/4.mdx b/chapters/fr/chapter1/4.mdx index dc996234e..28de8aa80 100644 --- a/chapters/fr/chapter1/4.mdx +++ b/chapters/fr/chapter1/4.mdx @@ -1,169 +1,174 @@ -# Comment fonctionnent les transformers ? - -Dans cette partie, nous allons jeter un coup d'œil à l'architecture des *transformers*. - -## Court historique des transformers - -Voici quelques dates clefs dans la courte histoire des *transformers* : - -
-A brief chronology of Transformers models. - -
- -[L'architecture *Transformer*](https://arxiv.org/abs/1706.03762) a été présentée en juin 2017. Initialement, la recherche portait sur la tâche de traduction. Elle a été suivie par l'introduction de plusieurs modèles influents, notamment : - -- **Juin 2018** : [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier *transformer* pré-entraîné et *finetuné* sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art, - -- **Octobre 2018** : [BERT](https://arxiv.org/abs/1810.04805), autre grand modèle pré-entraîné ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !), - -- **Février 2019** : [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques, - -- **Octobre 2019** : [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire et conservant tout de même 97% des performances initiales de BERT, - -- **Octobre 2019** : [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles pré-entraînés utilisant la même architecture que le *transformer* original (les premiers à faire cela), - -- **Mai 2020** : [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de *finetuning* (appelé _zero-shot learning_). - -Cette liste est loin d'être exhaustive et met en lumière certains *transformers*. Plus largement, ces modèles peuvent être regroupés en trois catégories : - -- ceux de type GPT (aussi appelés *transformers* _autorégressifs_) -- ceux de type BERT (aussi appelés *transformers* _auto-encodeurs_) -- ceux de type BART/T5 (aussi appelés *transformers* _séquence-à-séquence_) - -Nous verrons plus en profondeur ces familles de modèles plus tard. - -## Les transformers sont des modèles de langage - -Tous les *transformers* mentionnés ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles de langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts de manière autosupervisée. L'apprentissage autosupervisé est un type d'entraînement dans lequel l'objectif est automatiquement calculé à partir des entrées du modèle. Cela signifie que les humains ne sont pas nécessaires pour étiqueter les données ! - -Ce type de modèle développe une compréhension statistique de la langue sur laquelle il a été entraîné, mais il n'est pas très utile pour des tâches pratiques spécifiques. Pour cette raison, le modèle pré-entraîné passe ensuite par un processus appelé apprentissage par transfert. Au cours de ce processus, le modèle est *finetuné* de manière supervisée (c'est-à-dire en utilisant des étiquettes annotées par des humains) pour une tâche donnée. - -Un exemple de tâche consiste à prédire le mot suivant dans une phrase après avoir lu les *n* mots précédents. Cette tâche est appelée *modélisation causale du langage* car la sortie dépend des entrées passées et présentes, mais pas des entrées futures. -
-Example of causal language modeling in which the next word from a sentence is predicted. - -
- -Un autre exemple est la *modélisation du langage masqué*, dans laquelle le modèle prédit un mot masqué dans la phrase. - -
-Example of masked language modeling in which a masked word from a sentence is predicted. - -
- -## Les transformers sont énormes - -En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers. - -
-Number of parameters of recent Transformers models -
- -Malheureusement, entraîner un modèle et particulièrement un très grand modèle, nécessite une importante quantité de données. Cela devient très coûteux en termes de temps et de ressources de calcul. Cela se traduit même par un impact environnemental comme le montre le graphique suivant. - -
-The carbon footprint of a large language model. - -
- - - -L'image montre l'empreinte carbone pour un projet d'entraînement d'un (très grand) modèle mené par une équipe qui pourtant essaie consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte de l'exécution de nombreux essais pour obtenir les meilleurs hyperparamètres serait encore plus élevée. - -Imaginez qu'à chaque fois qu'une équipe de recherche, une association d'étudiants ou une entreprise souhaite entraîner un modèle, elle le fasse en partant de zéro. Cela entraînerait des coûts globaux énormes et inutiles ! - -C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté. - -## L'apprentissage par transfert - - - -Le pré-entraînement consiste à entraîner un modèle à partir de zéro : les poids sont initialisés de manière aléatoire et l'entraînement commence sans aucune connaissance préalable. - -
-The pretraining of a language model is costly in both time and money. - -
- -Ce pré-entraînement est généralement effectué sur de très grandes quantités de données. Il nécessite donc un très grand corpus de données et l'entraînement peut prendre jusqu'à plusieurs semaines. - -Le *finetuning*, quant à lui, est l'entrainement effectué après qu'un modèle ait été pré-entraîné. Pour effectuer un *finetuning*, vous devez d'abord acquérir un modèle de langue pré-entraîné, puis effectuer un entraînement supplémentaire avec un jeu de données spécifiques. Mais pourquoi ne pas entraîner directement pour la tâche finale ? Il y a plusieurs raisons à cela : - -* Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui présente certaines similitudes avec le jeu de données de *finetuning*. Le processus de *finetuning* est donc en mesure de tirer parti des connaissances acquises par le modèle initial lors du pré-entraînement (par exemple, pour les problèmes de langage naturel, le modèle pré-entraîné aura une certaine compréhension statistique de la langue que vous utilisez pour votre tâche) -* Comme le modèle pré-entraîné a déjà été entraîné sur de nombreuses données, le *finetuning* nécessite beaucoup moins de données pour obtenir des résultats décents. -* Pour la même raison, le temps et les ressources nécessaires pour obtenir de bons résultats sont beaucoup moins importants. - -Par exemple, il est possible d'exploiter un modèle pré-entraîné entraîné sur la langue anglaise, puis de le *finetuner* sur un corpus arXiv, pour obtenir un modèle basé sur la science et la recherche. Le *finetuning* ne nécessitera qu'une quantité limitée de données : les connaissances acquises par le modèle pré-entraîné sont « transférées », d'où le terme d'apprentissage par transfert. - -
-The fine-tuning of a language model is cheaper than pretraining in both time and money. - -
- -Le *finetuning* d'un modèle a donc un coût moindre en termes de temps, de données, de finances et d'environnement. Il est aussi plus rapide et plus facile d'itérer sur différents schémas de *finetuning* car l'entraînement est moins contraignant qu'un pré-entraînement complet. - -Ce processus permet également d'obtenir de meilleurs résultats que l'entraînement à partir de zéro (à moins que vous ne disposiez d'un grand nombre de données). C'est pourquoi vous devez toujours essayer de tirer parti d'un modèle pré-entraîné, c'est-à-dire un modèle aussi proche que possible de la tâche que vous avez à accomplir, et de le *finetuner*. - -## Architecture générale - -Dans cette section, nous allons voir l'architecture générale des *transformers*. Pas d'inquiétudes si vous ne comprenez pas tous les concepts, des sections détaillées qui couvrent chaque composant seront abordées plus tard. - - - -## Introduction - -Le modèle est principalement composé de deux blocs : - -* **Encodeur (à gauche)** : l'encodeur reçoit une entrée et construit une représentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées. -* **Décodeur (à droite)** : le décodeur utilise la représentation de l'encodeur (les caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties. - -
-Architecture of a Transformers models - -
- -Chacun de ces blocs peuvent être utilisés indépendamment en fonction de la tâche que l'on souhaite traiter : - -* **Modèles uniquement encodeurs** : adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées. -* **Modèles uniquement décodeurs** : adaptés pour les tâches génératives telles que la génération de texte. -* **Modèles encodeurs-décodeurs** (ou **modèles de séquence-à-séquence**) : adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte. - -Nous verrons plus en détails chacune de ces architectures plus tard. - -## Les couches d'attention - -Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* se nomme [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot. - -Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « *You like this course* », un modèle de traduction devra également s'intéresser au mot adjacent « *You* » pour obtenir la traduction correcte du mot « *like* », car en français le verbe « *like* » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « *this* », le modèle devra également faire attention au mot « *course* » car « *this* » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « *this* ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. - -Le même concept s'applique à toute tâche associée au langage naturel : un mot en lui-même a un sens, mais ce sens est profondément affecté par le contexte, qui peut être n'importe quel autre mot (ou mots) avant ou après le mot étudié. - -Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des *transformers*. - -## L'architecture originale - -L'architecture du *transformer* a initialement été construite pour la tâche de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot. - -Pour accélérer les choses pendant l'apprentissage (lorsque le modèle a accès aux phrases cibles), le décodeur est alimenté avec la cible entière, mais il n'est pas autorisé à utiliser les mots futurs (s'il avait accès au mot en position 2 lorsqu'il essayait de prédire le mot en position 2, le problème ne serait pas très difficile !). Par exemple, en essayant de prédire le quatrième mot, la couche d'attention n'aura accès qu'aux mots des positions 1 à 3. - -L'architecture originale du *transformer* ressemble à ceci, avec l'encodeur à gauche et le décodeur à droite : - -
-Architecture of a Transformers models - -
- -Notez que la première couche d'attention dans un bloc décodeur prête attention à toutes les entrées (passées) du décodeur, mais que la deuxième couche d'attention utilise la sortie de l'encodeur. Elle peut donc accéder à l'ensemble de la phrase d'entrée pour prédire au mieux le mot actuel. C'est très utile, car différentes langues peuvent avoir des règles grammaticales qui placent les mots dans un ordre différent, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné. - -Le *masque d'attention* peut également être utilisé dans l'encodeur/décodeur pour empêcher le modèle de prêter attention à certains mots spéciaux. Par exemple, le mot de remplissage spécial (le *padding*) utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. - -## Architectures contre checkpoints - -En approfondissant l'étude des transformers dans ce cours, vous verrez des mentions d'architectures et de checkpoints ainsi que de modèles. Ces termes ont tous des significations légèrement différentes : - -* **Architecture** : c'est le squelette du modèle, la définition de chaque couche et chaque opération qui se produit au sein du modèle. -* **Checkpoints** : ce sont les poids qui seront chargés dans une architecture donnée. -* **Modèle** : c'est un mot valise n'étant pas aussi précis que les mots « architecture » ou « *checkpoint* ». Il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il sera essentiel de réduire toute ambiguïté. - -Par exemple, BERT est une architecture alors que `bert-base-cased` (un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT) est un *checkpoint*. Cependant, il est possible de dire « le modèle BERT » et « le modèle `bert-base-cased` ». +# Comment fonctionnent les transformers ? + + + +Dans cette partie, nous allons jeter un coup d'œil à l'architecture des *transformers*. + +## Court historique des transformers + +Voici quelques dates clefs dans la courte histoire des *transformers* : + +
+A brief chronology of Transformers models. + +
+ +[L'architecture *Transformer*](https://arxiv.org/abs/1706.03762) a été présentée en juin 2017. Initialement, la recherche portait sur la tâche de traduction. Elle a été suivie par l'introduction de plusieurs modèles influents, notamment : + +- **Juin 2018** : [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier *transformer* pré-entraîné et *finetuné* sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art, + +- **Octobre 2018** : [BERT](https://arxiv.org/abs/1810.04805), autre grand modèle pré-entraîné ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !), + +- **Février 2019** : [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques, + +- **Octobre 2019** : [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire et conservant tout de même 97% des performances initiales de BERT, + +- **Octobre 2019** : [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles pré-entraînés utilisant la même architecture que le *transformer* original (les premiers à faire cela), + +- **Mai 2020** : [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de *finetuning* (appelé _zero-shot learning_). + +Cette liste est loin d'être exhaustive et met en lumière certains *transformers*. Plus largement, ces modèles peuvent être regroupés en trois catégories : + +- ceux de type GPT (aussi appelés *transformers* _autorégressifs_) +- ceux de type BERT (aussi appelés *transformers* _auto-encodeurs_) +- ceux de type BART/T5 (aussi appelés *transformers* _séquence-à-séquence_) + +Nous verrons plus en profondeur ces familles de modèles plus tard. + +## Les transformers sont des modèles de langage + +Tous les *transformers* mentionnés ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles de langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts de manière autosupervisée. L'apprentissage autosupervisé est un type d'entraînement dans lequel l'objectif est automatiquement calculé à partir des entrées du modèle. Cela signifie que les humains ne sont pas nécessaires pour étiqueter les données ! + +Ce type de modèle développe une compréhension statistique de la langue sur laquelle il a été entraîné, mais il n'est pas très utile pour des tâches pratiques spécifiques. Pour cette raison, le modèle pré-entraîné passe ensuite par un processus appelé apprentissage par transfert. Au cours de ce processus, le modèle est *finetuné* de manière supervisée (c'est-à-dire en utilisant des étiquettes annotées par des humains) pour une tâche donnée. + +Un exemple de tâche consiste à prédire le mot suivant dans une phrase après avoir lu les *n* mots précédents. Cette tâche est appelée *modélisation causale du langage* car la sortie dépend des entrées passées et présentes, mais pas des entrées futures. +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Un autre exemple est la *modélisation du langage masqué*, dans laquelle le modèle prédit un mot masqué dans la phrase. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Les transformers sont énormes + +En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers. + +
+Number of parameters of recent Transformers models +
+ +Malheureusement, entraîner un modèle et particulièrement un très grand modèle, nécessite une importante quantité de données. Cela devient très coûteux en termes de temps et de ressources de calcul. Cela se traduit même par un impact environnemental comme le montre le graphique suivant. + +
+The carbon footprint of a large language model. + +
+ + + +L'image montre l'empreinte carbone pour un projet d'entraînement d'un (très grand) modèle mené par une équipe qui pourtant essaie consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte de l'exécution de nombreux essais pour obtenir les meilleurs hyperparamètres serait encore plus élevée. + +Imaginez qu'à chaque fois qu'une équipe de recherche, une association d'étudiants ou une entreprise souhaite entraîner un modèle, elle le fasse en partant de zéro. Cela entraînerait des coûts globaux énormes et inutiles ! + +C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté. + +## L'apprentissage par transfert + + + +Le pré-entraînement consiste à entraîner un modèle à partir de zéro : les poids sont initialisés de manière aléatoire et l'entraînement commence sans aucune connaissance préalable. + +
+The pretraining of a language model is costly in both time and money. + +
+ +Ce pré-entraînement est généralement effectué sur de très grandes quantités de données. Il nécessite donc un très grand corpus de données et l'entraînement peut prendre jusqu'à plusieurs semaines. + +Le *finetuning*, quant à lui, est l'entrainement effectué après qu'un modèle ait été pré-entraîné. Pour effectuer un *finetuning*, vous devez d'abord acquérir un modèle de langue pré-entraîné, puis effectuer un entraînement supplémentaire avec un jeu de données spécifiques. Mais pourquoi ne pas entraîner directement pour la tâche finale ? Il y a plusieurs raisons à cela : + +* Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui présente certaines similitudes avec le jeu de données de *finetuning*. Le processus de *finetuning* est donc en mesure de tirer parti des connaissances acquises par le modèle initial lors du pré-entraînement (par exemple, pour les problèmes de langage naturel, le modèle pré-entraîné aura une certaine compréhension statistique de la langue que vous utilisez pour votre tâche) +* Comme le modèle pré-entraîné a déjà été entraîné sur de nombreuses données, le *finetuning* nécessite beaucoup moins de données pour obtenir des résultats décents. +* Pour la même raison, le temps et les ressources nécessaires pour obtenir de bons résultats sont beaucoup moins importants. + +Par exemple, il est possible d'exploiter un modèle pré-entraîné entraîné sur la langue anglaise, puis de le *finetuner* sur un corpus arXiv, pour obtenir un modèle basé sur la science et la recherche. Le *finetuning* ne nécessitera qu'une quantité limitée de données : les connaissances acquises par le modèle pré-entraîné sont « transférées », d'où le terme d'apprentissage par transfert. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Le *finetuning* d'un modèle a donc un coût moindre en termes de temps, de données, de finances et d'environnement. Il est aussi plus rapide et plus facile d'itérer sur différents schémas de *finetuning* car l'entraînement est moins contraignant qu'un pré-entraînement complet. + +Ce processus permet également d'obtenir de meilleurs résultats que l'entraînement à partir de zéro (à moins que vous ne disposiez d'un grand nombre de données). C'est pourquoi vous devez toujours essayer de tirer parti d'un modèle pré-entraîné, c'est-à-dire un modèle aussi proche que possible de la tâche que vous avez à accomplir, et de le *finetuner*. + +## Architecture générale + +Dans cette section, nous allons voir l'architecture générale des *transformers*. Pas d'inquiétudes si vous ne comprenez pas tous les concepts, des sections détaillées qui couvrent chaque composant seront abordées plus tard. + + + +## Introduction + +Le modèle est principalement composé de deux blocs : + +* **Encodeur (à gauche)** : l'encodeur reçoit une entrée et construit une représentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées. +* **Décodeur (à droite)** : le décodeur utilise la représentation de l'encodeur (les caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties. + +
+Architecture of a Transformers models + +
+ +Chacun de ces blocs peuvent être utilisés indépendamment en fonction de la tâche que l'on souhaite traiter : + +* **Modèles uniquement encodeurs** : adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées. +* **Modèles uniquement décodeurs** : adaptés pour les tâches génératives telles que la génération de texte. +* **Modèles encodeurs-décodeurs** (ou **modèles de séquence-à-séquence**) : adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte. + +Nous verrons plus en détails chacune de ces architectures plus tard. + +## Les couches d'attention + +Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* se nomme [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot. + +Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « *You like this course* », un modèle de traduction devra également s'intéresser au mot adjacent « *You* » pour obtenir la traduction correcte du mot « *like* », car en français le verbe « *like* » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « *this* », le modèle devra également faire attention au mot « *course* » car « *this* » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « *this* ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. + +Le même concept s'applique à toute tâche associée au langage naturel : un mot en lui-même a un sens, mais ce sens est profondément affecté par le contexte, qui peut être n'importe quel autre mot (ou mots) avant ou après le mot étudié. + +Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des *transformers*. + +## L'architecture originale + +L'architecture du *transformer* a initialement été construite pour la tâche de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot. + +Pour accélérer les choses pendant l'apprentissage (lorsque le modèle a accès aux phrases cibles), le décodeur est alimenté avec la cible entière, mais il n'est pas autorisé à utiliser les mots futurs (s'il avait accès au mot en position 2 lorsqu'il essayait de prédire le mot en position 2, le problème ne serait pas très difficile !). Par exemple, en essayant de prédire le quatrième mot, la couche d'attention n'aura accès qu'aux mots des positions 1 à 3. + +L'architecture originale du *transformer* ressemble à ceci, avec l'encodeur à gauche et le décodeur à droite : + +
+Architecture of a Transformers models + +
+ +Notez que la première couche d'attention dans un bloc décodeur prête attention à toutes les entrées (passées) du décodeur, mais que la deuxième couche d'attention utilise la sortie de l'encodeur. Elle peut donc accéder à l'ensemble de la phrase d'entrée pour prédire au mieux le mot actuel. C'est très utile, car différentes langues peuvent avoir des règles grammaticales qui placent les mots dans un ordre différent, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné. + +Le *masque d'attention* peut également être utilisé dans l'encodeur/décodeur pour empêcher le modèle de prêter attention à certains mots spéciaux. Par exemple, le mot de remplissage spécial (le *padding*) utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. + +## Architectures contre checkpoints + +En approfondissant l'étude des transformers dans ce cours, vous verrez des mentions d'architectures et de checkpoints ainsi que de modèles. Ces termes ont tous des significations légèrement différentes : + +* **Architecture** : c'est le squelette du modèle, la définition de chaque couche et chaque opération qui se produit au sein du modèle. +* **Checkpoints** : ce sont les poids qui seront chargés dans une architecture donnée. +* **Modèle** : c'est un mot valise n'étant pas aussi précis que les mots « architecture » ou « *checkpoint* ». Il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il sera essentiel de réduire toute ambiguïté. + +Par exemple, BERT est une architecture alors que `bert-base-cased` (un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT) est un *checkpoint*. Cependant, il est possible de dire « le modèle BERT » et « le modèle `bert-base-cased` ». diff --git a/chapters/fr/chapter1/5.mdx b/chapters/fr/chapter1/5.mdx index 0f1fe9d3a..92d30c2a9 100644 --- a/chapters/fr/chapter1/5.mdx +++ b/chapters/fr/chapter1/5.mdx @@ -1,17 +1,22 @@ -# Les modèles basés sur l'encodeur - - - -Les modèles basés sur l'encodeur utilisent uniquement l'encodeur d'un *transformer*. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention bidirectionnelle et sont souvent appelés *modèles d'auto-encodage*. - -Le pré-entraînement de ces modèles se concentre généralement sur la modification d'une phrase donnée (par exemple, en masquant des mots aléatoires dans celle-ci) et en demandant au modèle de trouver ou de reconstruire la phrase initiale. - -Ces modèles sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance d'entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives. - -Les modèles les plus représentatifs de cette famille sont : - -- [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) +# Les modèles basés sur l'encodeur + + + + + +Les modèles basés sur l'encodeur utilisent uniquement l'encodeur d'un *transformer*. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention bidirectionnelle et sont souvent appelés *modèles d'auto-encodage*. + +Le pré-entraînement de ces modèles se concentre généralement sur la modification d'une phrase donnée (par exemple, en masquant des mots aléatoires dans celle-ci) et en demandant au modèle de trouver ou de reconstruire la phrase initiale. + +Ces modèles sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance d'entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives. + +Les modèles les plus représentatifs de cette famille sont : + +- [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/fr/chapter1/6.mdx b/chapters/fr/chapter1/6.mdx index 338d6d25c..b2fbf236c 100644 --- a/chapters/fr/chapter1/6.mdx +++ b/chapters/fr/chapter1/6.mdx @@ -1,16 +1,21 @@ -# Les modèles basés sur le décodeur - - - -Les modèles basés sur le décodeur utilisent seulement le décodeur d'un *transformer*. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles autorégressifs*. - -Le pré-entraînement des modèles basés sur le décodeur se concentre généralement sur la prédiction du prochain mot dans la phrase. - -Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération de texte. - -Les modèles qui représentent le mieux la famille des modèles décodeurs sont : - -- [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) +# Les modèles basés sur le décodeur + + + + + +Les modèles basés sur le décodeur utilisent seulement le décodeur d'un *transformer*. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles autorégressifs*. + +Le pré-entraînement des modèles basés sur le décodeur se concentre généralement sur la prédiction du prochain mot dans la phrase. + +Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération de texte. + +Les modèles qui représentent le mieux la famille des modèles décodeurs sont : + +- [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/fr/chapter1/7.mdx b/chapters/fr/chapter1/7.mdx index be4c527dc..7a94a5be3 100644 --- a/chapters/fr/chapter1/7.mdx +++ b/chapters/fr/chapter1/7.mdx @@ -1,16 +1,21 @@ -# Les modèles de séquence-à-séquence - - - -Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties du *transformer*. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention. - -Le pré-entraînement de ces modèles peut être réalisé en utilisant les objectifs des modèles basés sur l'encodeur ou des modèles basés sur le décodeur. En général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un masque spécial et l'objectif est alors de prédire le texte que ce masque cache. - -Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de questions-réponses. - -Les modèles qui représentent le mieux cette famille sont : - -- [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) +# Les modèles de séquence-à-séquence + + + + + +Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties du *transformer*. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention. + +Le pré-entraînement de ces modèles peut être réalisé en utilisant les objectifs des modèles basés sur l'encodeur ou des modèles basés sur le décodeur. En général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un masque spécial et l'objectif est alors de prédire le texte que ce masque cache. + +Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de questions-réponses. + +Les modèles qui représentent le mieux cette famille sont : + +- [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) diff --git a/chapters/fr/chapter1/8.mdx b/chapters/fr/chapter1/8.mdx index e3e3c2310..9ab2ccbb3 100644 --- a/chapters/fr/chapter1/8.mdx +++ b/chapters/fr/chapter1/8.mdx @@ -1,34 +1,34 @@ -# Biais et limitations - - - -Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet. - -Pour illustrer cela rapidement, revenons au pipeline *fill-mask* avec le modèle BERT : - -```python -from transformers import pipeline - -unmasker = pipeline("fill-mask", model="bert-base-uncased") -result = unmasker("This man works as a [MASK].") # Cet homme travaille comme [MASQUE] -print([r["token_str"] for r in result]) - -result = unmasker("This woman works as a [MASK].") # Cette femme travaille comme [MASQUE] -print([r["token_str"] for r in result]) -``` - -```python out -['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] -# [avocat, charpentier, médecin, serveur, mécanicien] -['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] -# ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"] -``` - -Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (*waiter*/*waitress* → serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique : et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à « femme » et à « travail » par le modèle. Cela se produit même si BERT est l'un des rare *transformers* qui n'a pas été construit avec des données récupérées par *scrapping* sur internet, mais à l'aide de données en apparence neutres. En effet, il est entraîné sur les jeux de donnés [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). - -Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le *finetuning* du modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque. +# Biais et limitations + + + +Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet. + +Pour illustrer cela rapidement, revenons au pipeline *fill-mask* avec le modèle BERT : + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") # Cet homme travaille comme [MASQUE] +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") # Cette femme travaille comme [MASQUE] +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +# [avocat, charpentier, médecin, serveur, mécanicien] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +# ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"] +``` + +Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (*waiter*/*waitress* → serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique : et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à « femme » et à « travail » par le modèle. Cela se produit même si BERT est l'un des rare *transformers* qui n'a pas été construit avec des données récupérées par *scrapping* sur internet, mais à l'aide de données en apparence neutres. En effet, il est entraîné sur les jeux de donnés [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). + +Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le *finetuning* du modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque. diff --git a/chapters/fr/chapter1/9.mdx b/chapters/fr/chapter1/9.mdx index 502bf459d..efe33bfc8 100644 --- a/chapters/fr/chapter1/9.mdx +++ b/chapters/fr/chapter1/9.mdx @@ -1,11 +1,16 @@ -# Résumé du chapitre - -Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la bibliothèque 🤗 *Transformers*. Vous avez également vu comment rechercher et utiliser des modèles dans le *Hub* ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur. - -Nous avons pu aborder le fonctionnement des *transformers* de façon générale et parler de l'importance de l'apprentissage par transfert et du *finetuning*. Un point important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivant résume ceci : - -| Modèle | Exemples | Tâches | -|-------------------|--------------------------------------------|----------------------------------------------------------------------------------------------| -| Encodeur | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance d'entités nommées, extraction de question-réponse | -| Décodeur | CTRL, GPT, GPT-2, Transformer XL | Génération de texte | +# Résumé du chapitre + + + +Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la bibliothèque 🤗 *Transformers*. Vous avez également vu comment rechercher et utiliser des modèles dans le *Hub* ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur. + +Nous avons pu aborder le fonctionnement des *transformers* de façon générale et parler de l'importance de l'apprentissage par transfert et du *finetuning*. Un point important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivant résume ceci : + +| Modèle | Exemples | Tâches | +|-------------------|--------------------------------------------|----------------------------------------------------------------------------------------------| +| Encodeur | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance d'entités nommées, extraction de question-réponse | +| Décodeur | CTRL, GPT, GPT-2, Transformer XL | Génération de texte | | Encodeur-décodeur | BART, T5, Marian, mBART | Résumé, traduction, génération de question-réponse | \ No newline at end of file diff --git a/chapters/fr/chapter2/1.mdx b/chapters/fr/chapter2/1.mdx index 77a53ca12..aa72cc82f 100644 --- a/chapters/fr/chapter2/1.mdx +++ b/chapters/fr/chapter2/1.mdx @@ -1,24 +1,29 @@ -# Introduction - -Comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile. - -La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont : - -- **La facilité d'utilisation** : en seulement deux lignes de code il est possible de télécharger, charger et utiliser un modèle de NLP à l'état de l'art pour faire de l'inférence, -- **La flexibilité** : au fond, tous les modèles sont de simples classes PyTorch `nn.Module` ou TensorFlow `tf.keras.Model` et peuvent être manipulés comme n'importe quel autre modèle dans leurs *frameworks* d'apprentissage automatique respectifs, -- **La simplicité** : pratiquement aucune abstraction n'est faite dans la bibliothèque. Avoir tout dans un fichier est un concept central : la passe avant d'un modèle est entièrement définie dans un seul fichier afin que le code lui-même soit compréhensible et piratable. - -Cette dernière caractéristique rend 🤗 *Transformers* très différent des autres bibliothèques d'apprentissage automatique. -Les modèles ne sont pas construits sur des modules partagés entre plusieurs fichiers. Au lieu de cela, chaque modèle possède ses propres couches. -En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres. - -Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [chapitre 1](/course/fr/chapter1). -Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. - -Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. -Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. -Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`. - - - ⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le Hub et la bibliothèque 🤗 Transformers, nous vous recommandons de créer un compte. - +# Introduction + + + +Comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile. + +La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont : + +- **La facilité d'utilisation** : en seulement deux lignes de code il est possible de télécharger, charger et utiliser un modèle de NLP à l'état de l'art pour faire de l'inférence, +- **La flexibilité** : au fond, tous les modèles sont de simples classes PyTorch `nn.Module` ou TensorFlow `tf.keras.Model` et peuvent être manipulés comme n'importe quel autre modèle dans leurs *frameworks* d'apprentissage automatique respectifs, +- **La simplicité** : pratiquement aucune abstraction n'est faite dans la bibliothèque. Avoir tout dans un fichier est un concept central : la passe avant d'un modèle est entièrement définie dans un seul fichier afin que le code lui-même soit compréhensible et piratable. + +Cette dernière caractéristique rend 🤗 *Transformers* très différent des autres bibliothèques d'apprentissage automatique. +Les modèles ne sont pas construits sur des modules partagés entre plusieurs fichiers. Au lieu de cela, chaque modèle possède ses propres couches. +En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres. + +Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [chapitre 1](/course/fr/chapter1). +Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. + +Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. +Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. +Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`. + + + ⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le Hub et la bibliothèque 🤗 Transformers, nous vous recommandons de créer un compte. + diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index baaf88030..6aceac55a 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -1,349 +1,349 @@ - - -# Derrière le pipeline - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -Il s'agit de la première section dont le contenu est légèrement différent selon que vous utilisez PyTorch ou TensorFlow. Cliquez sur le bouton situé au-dessus du titre pour sélectionner la plateforme que vous préférez ! - - -{#if fw === 'pt'} - -{:else} - -{/if} - -Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [chapitre 1](/course/chapter1) : - -```python -from transformers import pipeline - -classifier = pipeline("sentiment-analysis") -classifier( - [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "I hate this so much!", # Je déteste tellement ça ! - ] -) -``` - -la sortie : - -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}, - {'label': 'NEGATIVE', 'score': 0.9994558095932007}] -``` - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement. - -
-The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. - -
- -Passons rapidement en revue chacun de ces éléments. - -## Prétraitement avec un tokenizer - -Comme d'autres réseaux de neurones, les *transformers* ne peuvent pas traiter directement le texte brut, donc la première étape de notre pipeline est de convertir les entrées textuelles en nombres afin que le modèle puisse les comprendre. Pour ce faire, nous utilisons un *tokenizer*, qui sera responsable de : -- diviser l'entrée en mots, sous-mots, ou symboles (comme la ponctuation) qui sont appelés *tokens*, -- associer chaque *token* à un nombre entier, -- ajouter des entrées supplémentaires qui peuvent être utiles au modèle. - -Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous). - -Puisque le *checkpoint* par défaut du pipeline `sentiment-analysis` (analyse de sentiment) est `distilbert-base-uncased-finetuned-sst-2-english` (vous pouvez voir la carte de ce modèle [ici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), nous exécutons ce qui suit : - -```python -from transformers import AutoTokenizer - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -``` - -Une fois que nous avons le *tokenizer* nous pouvons lui passer directement nos phrases et obtenir un dictionnaire prêt à être donné à notre modèle ! La seule chose qui reste à faire est de convertir en tenseurs la liste des identifiants d'entrée. - -Vous pouvez utiliser 🤗 *Transformers* sans avoir à vous soucier du *framework* utilisé comme *backend*. Il peut s'agir de PyTorch, de TensorFlow ou de Flax pour certains modèles. Cependant, les *transformers* n'acceptent que les *tenseurs* en entrée. Si c'est la première fois que vous entendez parler de tenseurs, vous pouvez les considérer comme des tableaux NumPy. Un tableau NumPy peut être un scalaire (0D), un vecteur (1D), une matrice (2D), ou avoir davantage de dimensions. Les tenseurs des autres *frameworks* d'apprentissage machine se comportent de manière similaire et sont généralement aussi simples à instancier que les tableaux NumPy. - -Pour spécifier le type de tenseurs que nous voulons récupérer (PyTorch, TensorFlow, ou simplement NumPy), nous utilisons l'argument `return_tensors` : - -{#if fw === 'pt'} -```python -raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "I hate this so much!", # Je déteste tellement ça ! -] -inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") -print(inputs) -``` -{:else} -```python -raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "I hate this so much!", # Je déteste tellement ça ! -] -inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") -print(inputs) -``` -{/if} - -Ne vous préoccupez pas encore du remplissage (*padding*) et de la troncature, nous les expliquerons plus tard. Les principales choses à retenir ici sont que vous pouvez passer une phrase ou une liste de phrases, ainsi que spécifier le type de tenseurs que vous voulez récupérer (si aucun type n'est passé, par défaut vous obtiendrez une liste de listes comme résultat). - -{#if fw === 'pt'} - -Voici à quoi ressemblent les résultats sous forme de tenseurs PyTorch : - -```python out -{ - 'input_ids': tensor([ - [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], - [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] - ]), - 'attention_mask': tensor([ - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ]) -} -``` -{:else} - -Voici à quoi ressemblent les résultats sous forme de tenseurs TensorFlow : - -```python out -{ - 'input_ids': , - 'attention_mask': -} -``` -{/if} - -La sortie elle-même est un dictionnaire contenant deux clés : `input_ids` et `attention_mask`. `input_ids` contient deux lignes d'entiers (une pour chaque phrase) qui sont les identifiants uniques des *tokens* dans chaque phrase. Nous expliquerons ce qu'est l'`attention_mask` plus tard dans ce chapitre. - -## Passage au modèle - -{#if fw === 'pt'} -Nous pouvons télécharger notre modèle pré-entraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` : - -```python -from transformers import AutoModel - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = AutoModel.from_pretrained(checkpoint) -``` -{:else} -Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `TFAutoModel` qui possède également une méthode `from_pretrained()` : - -```python -from transformers import TFAutoModel - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = TFAutoModel.from_pretrained(checkpoint) -``` -{/if} - -Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nous avons utilisé dans notre pipeline auparavant (il devrait en fait avoir déjà été mis en cache) et instancié un modèle avec lui. - -Cette architecture ne contient que le module de *transformer* de base : étant donné certaines entrées, il produit ce que nous appellerons des *états cachés*, également connus sous le nom de *caractéristiques*. -Pour chaque entrée du modèle, nous récupérons un vecteur en grande dimension représentant la **compréhension contextuelle de cette entrée par le *transformer***. - -Si cela ne fait pas sens, ne vous inquiétez pas. Nous expliquons tout plus tard. - -Bien que ces états cachés puissent être utiles en eux-mêmes, ils sont généralement les entrées d'une autre partie du modèle, connue sous le nom de *tête*. Dans le [chapitre 1](/course/fr/chapter1), les différentes tâches auraient pu être réalisées avec la même architecture mais en ayant chacune d'elles une tête différente. - -### Un vecteur de grande dimension ? - -Le vecteur produit en sortie par le *transformer* est généralement de grande dimension. Il a généralement trois dimensions : - -- **la taille du lot** : le nombre de séquences traitées à la fois (2 dans notre exemple), -- **la longueur de la séquence** : la longueur de la représentation numérique de la séquence (16 dans notre exemple), -- **la taille cachée** : la dimension du vecteur de chaque entrée du modèle. - -On dit qu'il est de « grande dimension » en raison de la dernière valeur. La taille cachée peut être très grande (généralement 768 pour les petits modèles et pour les grands modèles cela peut atteindre 3072 voire plus). - -Nous pouvons le constater si nous alimentons notre modèle avec les entrées que nous avons prétraitées : - - -{#if fw === 'pt'} -```python -outputs = model(**inputs) -print(outputs.last_hidden_state.shape) -``` - -```python out -torch.Size([2, 16, 768]) -``` -{:else} -```py -outputs = model(inputs) -print(outputs.last_hidden_state.shape) -``` - -```python out -(2, 16, 768) -``` -{/if} - -Notez que les sorties des modèles de la bibliothèque 🤗 *Transformers* se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`). - -### Les têtes des modèles : donner du sens aux chiffres -Les têtes des modèles prennent en entrée le vecteur de grande dimension des états cachés et le projettent sur une autre dimension. Elles sont généralement composées d'une ou de quelques couches linéaires : -
-A Transformer network alongside its head. - -
- -La sortie du *transformer* est envoyée directement à la tête du modèle pour être traitée. -Dans ce diagramme, le modèle est représenté par sa couche d’enchâssement et les couches suivantes. La couche d’enchâssement convertit chaque identifiant d'entrée dans l'entrée tokenisée en un vecteur qui représente le *token* associé. Les couches suivantes manipulent ces vecteurs en utilisant le mécanisme d'attention pour produire la représentation finale des phrases. -Il existe de nombreuses architectures différentes disponibles dans la bibliothèque 🤗 *Transformers*, chacune étant conçue autour de la prise en charge d'une tâche spécifique. En voici une liste non exhaustive : -- `*Model` (récupérer les états cachés) -- `*ForCausalLM` -- `*ForMaskedLM` -- `*ForMultipleChoice` -- `*ForQuestionAnswering` -- `*ForSequenceClassification` -- `*ForTokenClassification` -- et autres 🤗 - -{#if fw === 'pt'} -Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe `AutoModel` mais plutôt `AutoModelForSequenceClassification` : -```python -from transformers import AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -outputs = model(**inputs) -``` -{:else} -Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe ` TFAutoModel` mais plutôt ` TFAutoModelForSequenceClassification` : -```python -from transformers import TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -outputs = model(inputs) -``` -{/if} - -Maintenant, si nous examinons la forme de nos entrées, la dimensionnalité est beaucoup plus faible. La tête du modèle prend en entrée les vecteurs de grande dimension que nous avons vus précédemment et elle produit des vecteurs contenant deux valeurs (une par étiquette) : -```python -print(outputs.logits.shape) -``` - -{#if fw === 'pt'} -```python out -torch.Size([2, 2]) -``` -{:else} -```python out -(2, 2) -``` -{/if} - -Comme nous n'avons que deux phrases et deux étiquettes, le résultat que nous obtenons est de forme 2 x 2 - -## Post-traitement de la sortie - -Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d’œil : - -```python -print(outputs.logits) -``` - -{#if fw === 'pt'} -```python out -tensor([[-1.5607, 1.6123], - [ 4.1692, -3.3464]], grad_fn=) -``` -{:else} -```python out - -``` -{/if} - -Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 *Transformers* sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : - -{#if fw === 'pt'} -```py -import torch - -predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) -print(predictions) -``` -{:else} -```py -import tensorflow as tf - -predictions = tf.math.softmax(outputs.logits, axis=-1) -print(predictions) -``` -{/if} - -{#if fw === 'pt'} -```python out -tensor([[4.0195e-02, 9.5980e-01], - [9.9946e-01, 5.4418e-04]], grad_fn=) -``` -{:else} -```python out -tf.Tensor( -[[4.01951671e-02 9.59804833e-01] - [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) -``` -{/if} - -Maintenant nous pouvons voir que le modèle a prédit `[0.0402, 0.9598]` pour la première phrase et `[0.9995, 0.0005]` pour la seconde. Ce sont des scores de probabilité reconnaissables. - -Pour obtenir les étiquettes correspondant à chaque position, nous pouvons inspecter l'attribut `id2label` de la configuration du modèle (plus de détails dans la section suivante) : - -```python -model.config.id2label -``` - -```python out -{0: 'NEGATIVE', 1: 'POSITIVE'} -``` - -Nous pouvons maintenant conclure que le modèle a prédit ce qui suit : - -- première phrase : NEGATIVE: 0.0402, POSITIVE: 0.9598 -- deuxième phrase : NEGATIVE: 0.9995, POSITIVE: 0.0005 - -Nous avons reproduit avec succès les trois étapes du pipeline : prétraitement avec les *tokenizers*, passage des entrées dans le modèle et post-traitement ! Prenons maintenant le temps de nous plonger plus profondément dans chacune de ces étapes. - - - -✏️ **Essayez !** Choisissez deux (ou plus) textes de votre choix (en anglais) et faites-les passer par le pipeline `sentiment-analysis`. Reproduisez ensuite vous-même les étapes vues ici et vérifiez que vous obtenez les mêmes résultats ! - - + + +# Derrière le pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Il s'agit de la première section dont le contenu est légèrement différent selon que vous utilisez PyTorch ou TensorFlow. Cliquez sur le bouton situé au-dessus du titre pour sélectionner la plateforme que vous préférez ! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [chapitre 1](/course/chapter1) : + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! + ] +) +``` + +la sortie : + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement. + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +Passons rapidement en revue chacun de ces éléments. + +## Prétraitement avec un tokenizer + +Comme d'autres réseaux de neurones, les *transformers* ne peuvent pas traiter directement le texte brut, donc la première étape de notre pipeline est de convertir les entrées textuelles en nombres afin que le modèle puisse les comprendre. Pour ce faire, nous utilisons un *tokenizer*, qui sera responsable de : +- diviser l'entrée en mots, sous-mots, ou symboles (comme la ponctuation) qui sont appelés *tokens*, +- associer chaque *token* à un nombre entier, +- ajouter des entrées supplémentaires qui peuvent être utiles au modèle. + +Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous). + +Puisque le *checkpoint* par défaut du pipeline `sentiment-analysis` (analyse de sentiment) est `distilbert-base-uncased-finetuned-sst-2-english` (vous pouvez voir la carte de ce modèle [ici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), nous exécutons ce qui suit : + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +Une fois que nous avons le *tokenizer* nous pouvons lui passer directement nos phrases et obtenir un dictionnaire prêt à être donné à notre modèle ! La seule chose qui reste à faire est de convertir en tenseurs la liste des identifiants d'entrée. + +Vous pouvez utiliser 🤗 *Transformers* sans avoir à vous soucier du *framework* utilisé comme *backend*. Il peut s'agir de PyTorch, de TensorFlow ou de Flax pour certains modèles. Cependant, les *transformers* n'acceptent que les *tenseurs* en entrée. Si c'est la première fois que vous entendez parler de tenseurs, vous pouvez les considérer comme des tableaux NumPy. Un tableau NumPy peut être un scalaire (0D), un vecteur (1D), une matrice (2D), ou avoir davantage de dimensions. Les tenseurs des autres *frameworks* d'apprentissage machine se comportent de manière similaire et sont généralement aussi simples à instancier que les tableaux NumPy. + +Pour spécifier le type de tenseurs que nous voulons récupérer (PyTorch, TensorFlow, ou simplement NumPy), nous utilisons l'argument `return_tensors` : + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Ne vous préoccupez pas encore du remplissage (*padding*) et de la troncature, nous les expliquerons plus tard. Les principales choses à retenir ici sont que vous pouvez passer une phrase ou une liste de phrases, ainsi que spécifier le type de tenseurs que vous voulez récupérer (si aucun type n'est passé, par défaut vous obtiendrez une liste de listes comme résultat). + +{#if fw === 'pt'} + +Voici à quoi ressemblent les résultats sous forme de tenseurs PyTorch : + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + 'attention_mask': tensor([ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ]) +} +``` +{:else} + +Voici à quoi ressemblent les résultats sous forme de tenseurs TensorFlow : + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +La sortie elle-même est un dictionnaire contenant deux clés : `input_ids` et `attention_mask`. `input_ids` contient deux lignes d'entiers (une pour chaque phrase) qui sont les identifiants uniques des *tokens* dans chaque phrase. Nous expliquerons ce qu'est l'`attention_mask` plus tard dans ce chapitre. + +## Passage au modèle + +{#if fw === 'pt'} +Nous pouvons télécharger notre modèle pré-entraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` : + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `TFAutoModel` qui possède également une méthode `from_pretrained()` : + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nous avons utilisé dans notre pipeline auparavant (il devrait en fait avoir déjà été mis en cache) et instancié un modèle avec lui. + +Cette architecture ne contient que le module de *transformer* de base : étant donné certaines entrées, il produit ce que nous appellerons des *états cachés*, également connus sous le nom de *caractéristiques*. +Pour chaque entrée du modèle, nous récupérons un vecteur en grande dimension représentant la **compréhension contextuelle de cette entrée par le *transformer***. + +Si cela ne fait pas sens, ne vous inquiétez pas. Nous expliquons tout plus tard. + +Bien que ces états cachés puissent être utiles en eux-mêmes, ils sont généralement les entrées d'une autre partie du modèle, connue sous le nom de *tête*. Dans le [chapitre 1](/course/fr/chapter1), les différentes tâches auraient pu être réalisées avec la même architecture mais en ayant chacune d'elles une tête différente. + +### Un vecteur de grande dimension ? + +Le vecteur produit en sortie par le *transformer* est généralement de grande dimension. Il a généralement trois dimensions : + +- **la taille du lot** : le nombre de séquences traitées à la fois (2 dans notre exemple), +- **la longueur de la séquence** : la longueur de la représentation numérique de la séquence (16 dans notre exemple), +- **la taille cachée** : la dimension du vecteur de chaque entrée du modèle. + +On dit qu'il est de « grande dimension » en raison de la dernière valeur. La taille cachée peut être très grande (généralement 768 pour les petits modèles et pour les grands modèles cela peut atteindre 3072 voire plus). + +Nous pouvons le constater si nous alimentons notre modèle avec les entrées que nous avons prétraitées : + + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Notez que les sorties des modèles de la bibliothèque 🤗 *Transformers* se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`). + +### Les têtes des modèles : donner du sens aux chiffres +Les têtes des modèles prennent en entrée le vecteur de grande dimension des états cachés et le projettent sur une autre dimension. Elles sont généralement composées d'une ou de quelques couches linéaires : +
+A Transformer network alongside its head. + +
+ +La sortie du *transformer* est envoyée directement à la tête du modèle pour être traitée. +Dans ce diagramme, le modèle est représenté par sa couche d’enchâssement et les couches suivantes. La couche d’enchâssement convertit chaque identifiant d'entrée dans l'entrée tokenisée en un vecteur qui représente le *token* associé. Les couches suivantes manipulent ces vecteurs en utilisant le mécanisme d'attention pour produire la représentation finale des phrases. +Il existe de nombreuses architectures différentes disponibles dans la bibliothèque 🤗 *Transformers*, chacune étant conçue autour de la prise en charge d'une tâche spécifique. En voici une liste non exhaustive : +- `*Model` (récupérer les états cachés) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- et autres 🤗 + +{#if fw === 'pt'} +Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe `AutoModel` mais plutôt `AutoModelForSequenceClassification` : +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe ` TFAutoModel` mais plutôt ` TFAutoModelForSequenceClassification` : +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Maintenant, si nous examinons la forme de nos entrées, la dimensionnalité est beaucoup plus faible. La tête du modèle prend en entrée les vecteurs de grande dimension que nous avons vus précédemment et elle produit des vecteurs contenant deux valeurs (une par étiquette) : +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Comme nous n'avons que deux phrases et deux étiquettes, le résultat que nous obtenons est de forme 2 x 2 + +## Post-traitement de la sortie + +Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d’œil : + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 *Transformers* sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Maintenant nous pouvons voir que le modèle a prédit `[0.0402, 0.9598]` pour la première phrase et `[0.9995, 0.0005]` pour la seconde. Ce sont des scores de probabilité reconnaissables. + +Pour obtenir les étiquettes correspondant à chaque position, nous pouvons inspecter l'attribut `id2label` de la configuration du modèle (plus de détails dans la section suivante) : + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Nous pouvons maintenant conclure que le modèle a prédit ce qui suit : + +- première phrase : NEGATIVE: 0.0402, POSITIVE: 0.9598 +- deuxième phrase : NEGATIVE: 0.9995, POSITIVE: 0.0005 + +Nous avons reproduit avec succès les trois étapes du pipeline : prétraitement avec les *tokenizers*, passage des entrées dans le modèle et post-traitement ! Prenons maintenant le temps de nous plonger plus profondément dans chacune de ces étapes. + + + +✏️ **Essayez !** Choisissez deux (ou plus) textes de votre choix (en anglais) et faites-les passer par le pipeline `sentiment-analysis`. Reproduisez ensuite vous-même les étapes vues ici et vérifiez que vous obtenez les mêmes résultats ! + + diff --git a/chapters/fr/chapter2/3.mdx b/chapters/fr/chapter2/3.mdx index 9fce3d02f..a16dc192d 100644 --- a/chapters/fr/chapter2/3.mdx +++ b/chapters/fr/chapter2/3.mdx @@ -1,231 +1,231 @@ - - -# Les modèles - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -{#if fw === 'pt'} -Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. - -La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. - -{:else} -Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. - -La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. - -{/if} - -Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT. - -## Création d’un transformer - -La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration : - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -# Construire la configuration -config = BertConfig() - -# Construire le modèle à partir de la configuration -model = BertModel(config) -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -# Construire la configuration -config = BertConfig() - -# Construire le modèle à partir de la configuration -model = TFBertModel(config) -``` -{/if} - -La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle : -```py -print(config) -``` - -```python out -BertConfig { - [...] - "hidden_size": 768, - "intermediate_size": 3072, - "max_position_embeddings": 512, - "num_attention_heads": 12, - "num_hidden_layers": 12, - [...] -} -``` - -Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède. - -### Différentes méthodes de chargement - -Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires : - - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -config = BertConfig() -model = BertModel(config) - -# Le modèle est initialisé de façon aléatoire ! -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -config = BertConfig() -model = TFBertModel(config) - -# Le modèle est initialisé de façon aléatoire ! -``` -{/if} - -Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés. - -Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` : - - -{#if fw === 'pt'} -```py -from transformers import BertModel - -model = BertModel.from_pretrained("bert-base-cased") -``` - -Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). - -{:else} -```py -from transformers import TFBertModel - -model = TFBertModel.from_pretrained("bert-base-cased") -``` - -Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). - -{/if} - -Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased). - -Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats. - -Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`. - -L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert). - -### Méthodes de sauvegarde - -Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` : - - -```py -model.save_pretrained("directory_on_my_computer") -``` - -Cela enregistre deux fichiers sur votre disque : - -{#if fw === 'pt'} -``` -ls directory_on_my_computer - -config.json pytorch_model.bin -``` -{:else} -``` -ls directory_on_my_computer - -config.json tf_model.h5 -``` -{/if} - -Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*. - -{#if fw === 'pt'} -Le fichier *pytorch_model.bin* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. - -{:else} -Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. - -{/if} - -### Utilisation d'un transformer pour l'inférence - -Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte. - -Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle. - -Disons que nous avons les séquences suivantes : - - -```py -sequences = ["Hello!", "Cool.", "Nice!"] -``` - -Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est : - -```py no-format -encoded_sequences = [ - [101, 7592, 999, 102], - [101, 4658, 1012, 102], - [101, 3835, 999, 102], -] -``` - -Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile : - -{#if fw === 'pt'} -```py -import torch - -model_inputs = torch.tensor(encoded_sequences) -``` -{:else} -```py -import tensorflow as tf - -model_inputs = tf.constant(encoded_sequences) -``` -{/if} - -### Utilisation des tenseurs comme entrées du modèle - -L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées : - - -```py -output = model(model_inputs) -``` - -Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre. + + +# Les modèles + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{:else} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{/if} + +Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT. + +## Création d’un transformer + +La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration : + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = BertModel(config) +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = TFBertModel(config) +``` +{/if} + +La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle : +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède. + +### Différentes méthodes de chargement + +Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires : + + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{/if} + +Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés. + +Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` : + + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{/if} + +Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased). + +Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats. + +Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`. + +L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert). + +### Méthodes de sauvegarde + +Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` : + + +```py +model.save_pretrained("directory_on_my_computer") +``` + +Cela enregistre deux fichiers sur votre disque : + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*. + +{#if fw === 'pt'} +Le fichier *pytorch_model.bin* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{:else} +Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{/if} + +### Utilisation d'un transformer pour l'inférence + +Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte. + +Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle. + +Disons que nous avons les séquences suivantes : + + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est : + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile : + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Utilisation des tenseurs comme entrées du modèle + +L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées : + + +```py +output = model(model_inputs) +``` + +Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre. diff --git a/chapters/fr/chapter2/4.mdx b/chapters/fr/chapter2/4.mdx index 2015763fb..6445f167e 100644 --- a/chapters/fr/chapter2/4.mdx +++ b/chapters/fr/chapter2/4.mdx @@ -1,253 +1,253 @@ - - -# Les tokenizers - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - - -Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. - -Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte : - - -``` -Jim Henson was a puppeteer # Jim Henson était un marionnettiste -``` - -Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite. - -Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet. - -## Tokenizer basé sur les mots - - - - -Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux : - -
- An example of word-based tokenization. - -
- -Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python : - -```py -tokenized_text = "Jim Henson was a puppeteer".split() -print(tokenized_text) -``` - -```python out -['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste'] -``` - -Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus. - -Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. - -Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. - -Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. - -Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères. - - -## Tokenizer basé sur les caractères - - - -Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux : - -- le vocabulaire est beaucoup plus petit -- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères. - -Mais là aussi, des questions se posent concernant les espaces et la ponctuation : - - -
- An example of character-based tokenization. - -
- -Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine. - -Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus. - -Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*. - - -## Tokénisation en sous-mots - - - -Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. - -Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ». - -Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « *Let's do tokenization* ! » : - - -
- A subword tokenization algorithm. - -
- -Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « *tokenization* » a été divisé en « *token* » et « *ization* » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu. - -Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots. - - -### Et plus encore ! - -Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : - -- le *Byte-level BPE* utilisé par exemple dans le GPT-2 -- le *WordPiece* utilisé par exemple dans BERT -- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues. - -Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. - - -## Chargement et sauvegarde - -Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle). - -Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` : - - -```py -from transformers import BertTokenizer - -tokenizer = BertTokenizer.from_pretrained("bert-base-cased") -``` - -{#if fw === 'pt'} -Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : - -{:else} -Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : - -{/if} - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente : - -```python -tokenizer("Using a Transformer network is simple") -``` - -```python out -{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], - 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], - 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} -``` - -La sauvegarde d'un tokenizer est identique à celle d'un modèle : - -```py -tokenizer.save_pretrained("directory_on_my_computer") -``` - -Nous parlerons plus en détail des `token_type_ids` au [chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*. - -## Encodage - - - - -La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée. - -Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle. - -La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle. - -Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2). - -### Tokenisation - -Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* : - - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") - -sequence = "Using a Transformer network is simple" -tokens = tokenizer.tokenize(sequence) - -print(tokens) -``` - -La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* : - -```python out -['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] -``` - -Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`. - -### De tokens aux identifiants d'entrée - -La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* : - - -```py -ids = tokenizer.convert_tokens_to_ids(tokens) - -print(ids) -``` - -```python out -[7993, 170, 11303, 1200, 2443, 1110, 3014] -``` - -Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre. - - - -✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment ! - - - -## Décodage - -Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit : - - -```py -decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) -print(decoded_string) -``` - -```python out -'Using a Transformer network is simple' -``` - -Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte). - -Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter. + + +# Les tokenizers + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. + +Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte : + + +``` +Jim Henson was a puppeteer # Jim Henson était un marionnettiste +``` + +Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite. + +Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet. + +## Tokenizer basé sur les mots + + + + +Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux : + +
+ An example of word-based tokenization. + +
+ +Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python : + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste'] +``` + +Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus. + +Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. + +Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. + +Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. + +Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères. + + +## Tokenizer basé sur les caractères + + + +Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux : + +- le vocabulaire est beaucoup plus petit +- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères. + +Mais là aussi, des questions se posent concernant les espaces et la ponctuation : + + +
+ An example of character-based tokenization. + +
+ +Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine. + +Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus. + +Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*. + + +## Tokénisation en sous-mots + + + +Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. + +Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ». + +Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « *Let's do tokenization* ! » : + + +
+ A subword tokenization algorithm. + +
+ +Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « *tokenization* » a été divisé en « *token* » et « *ization* » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu. + +Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots. + + +### Et plus encore ! + +Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : + +- le *Byte-level BPE* utilisé par exemple dans le GPT-2 +- le *WordPiece* utilisé par exemple dans BERT +- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues. + +Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. + + +## Chargement et sauvegarde + +Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle). + +Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` : + + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{:else} +Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente : + +```python +tokenizer("Using a Transformer network is simple") +``` + +```python out +{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], + 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], + 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +La sauvegarde d'un tokenizer est identique à celle d'un modèle : + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +Nous parlerons plus en détail des `token_type_ids` au [chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*. + +## Encodage + + + + +La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée. + +Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle. + +La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle. + +Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2). + +### Tokenisation + +Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* : + + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + +sequence = "Using a Transformer network is simple" +tokens = tokenizer.tokenize(sequence) + +print(tokens) +``` + +La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* : + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`. + +### De tokens aux identifiants d'entrée + +La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* : + + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre. + + + +✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment ! + + + +## Décodage + +Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit : + + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte). + +Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter. diff --git a/chapters/fr/chapter2/5.mdx b/chapters/fr/chapter2/5.mdx index fb85ff966..3e9bc09e4 100644 --- a/chapters/fr/chapter2/5.mdx +++ b/chapters/fr/chapter2/5.mdx @@ -1,354 +1,354 @@ - - -# Manipulation de plusieurs séquences - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -Dans la section précédente, nous avons exploré le cas d'utilisation le plus simple : faire une inférence sur une seule séquence de petite longueur. Cependant, certaines questions émergent déjà : - -- comment gérer de plusieurs séquences ? -- comment gérer de plusieurs séquences *de longueurs différentes* ? -- les indices du vocabulaire sont-ils les seules entrées qui permettent à un modèle de bien fonctionner ? -- existe-t-il une séquence trop longue ? - -Voyons quels types de problèmes ces questions posent et comment nous pouvons les résoudre en utilisant l'API 🤗 *Transformers*. - -## Les modèles attendent un batch d'entrées - -Dans l'exercice précédent, vous avez vu comment les séquences sont traduites en listes de nombres. -Convertissons cette liste de nombres en un tenseur et envoyons-le au modèle : - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." -# J'ai attendu un cours d’HuggingFace toute ma vie. - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = torch.tensor(ids) -# Cette ligne va échouer. -model(input_ids) -``` - -```python out -IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." -# J'ai attendu un cours d’HuggingFace toute ma vie. - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = tf.constant(ids) -# This line will fail. -model(input_ids) -``` - -```py out -InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] -``` -{/if} - -Pourquoi cela a échoué ? Nous avons suivi les étapes du pipeline de la section 2. - -Le problème est que nous avons envoyé une seule séquence au modèle, alors que les modèles de l’API 🤗 *Transformers* attendent plusieurs phrases par défaut. Ici, nous avons essayé de faire ce que le *tokenizer* fait en coulisses lorsque nous l'avons appliqué à une `séquence`. Cependant si vous regardez de près, vous verrez qu'il n'a pas seulement converti la liste des identifiants d'entrée en un tenseur mais aussi ajouté une dimension par-dessus : - - -{#if fw === 'pt'} -```py -tokenized_inputs = tokenizer(sequence, return_tensors="pt") -print(tokenized_inputs["input_ids"]) -``` - -```python out -tensor([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, - 2607, 2026, 2878, 2166, 1012, 102]]) -``` -{:else} -```py -tokenized_inputs = tokenizer(sequence, return_tensors="tf") -print(tokenized_inputs["input_ids"]) -``` - -```py out - -``` -{/if} - -Essayons à nouveau en ajoutant une nouvelle dimension : - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." -# J'ai attendu un cours d’HuggingFace toute ma vie. - - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) - -input_ids = torch.tensor([ids]) -print("Input IDs:", input_ids) - -output = model(input_ids) -print("Logits:", output.logits) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." -# J'ai attendu un cours d’HuggingFace toute ma vie. - - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) - -input_ids = tf.constant([ids]) -print("Input IDs:", input_ids) - -output = model(input_ids) -print("Logits:", output.logits) -``` -{/if} - -Nous affichons les identifiants d'entrée ainsi que les logits résultants. Voici la sortie : - -{#if fw === 'pt'} -```python out -Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]] -Logits: [[-2.7276, 2.8789]] -``` -{:else} -```py out -Input IDs: tf.Tensor( -[[ 1045 1005 2310 2042 3403 2005 1037 17662 12172 2607 2026 2878 - 2166 1012]], shape=(1, 14), dtype=int32) -Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) -``` -{/if} - -Le « *batching* » est l'acte d'envoyer plusieurs phrases à travers le modèle, toutes en même temps. Si vous n'avez qu'une seule phrase, vous pouvez simplement construire un batch avec une seule séquence : - -``` -batched_ids = [ids, ids] -``` - -Il s'agit d'un batch de deux séquences identiques ! - - - -✏️ **Essayez !** Convertissez cette liste `batched_ids` en un tenseur et passez-la dans votre modèle. Vérifiez que vous obtenez les mêmes logits que précédemment (mais deux fois) ! - - - -Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer*/*remplir* (le *padding* en anglais) les entrées. - -## Padding des entrées - -La liste de listes suivante ne peut pas être convertie en un tenseur : - - -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200] -] -``` - -Afin de contourner ce problème, nous utilisons le *padding* pour que nos tenseurs aient une forme rectangulaire. Le *padding* permet de s'assurer que toutes nos phrases ont la même longueur en ajoutant un mot spécial appelé *padding token* aux phrases ayant moins de valeurs. Par exemple, si vous avez 10 phrases de 10 mots et 1 phrase de 20 mots, le *padding* fait en sorte que toutes les phrases aient 20 mots. Dans notre exemple, le tenseur résultant ressemble à ceci : - -```py no-format -padding_id = 100 - -batched_ids = [ - [200, 200, 200], - [200, 200, padding_id], -] -``` - -L'identifiant du jeton de *padding* peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch : - -{#if fw === 'pt'} -```py no-format -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence1_ids = [[200, 200, 200]] -sequence2_ids = [[200, 200]] -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -print(model(torch.tensor(sequence1_ids)).logits) -print(model(torch.tensor(sequence2_ids)).logits) -print(model(torch.tensor(batched_ids)).logits) -``` - -```python out -tensor([[ 1.5694, -1.3895]], grad_fn=) -tensor([[ 0.5803, -0.4125]], grad_fn=) -tensor([[ 1.5694, -1.3895], - [ 1.3373, -1.2163]], grad_fn=) -``` -{:else} -```py no-format -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence1_ids = [[200, 200, 200]] -sequence2_ids = [[200, 200]] -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -print(model(tf.constant(sequence1_ids)).logits) -print(model(tf.constant(sequence2_ids)).logits) -print(model(tf.constant(batched_ids)).logits) -``` - -```py out -tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32) -tf.Tensor([[ 0.5803005 -0.41252428]], shape=(1, 2), dtype=float32) -tf.Tensor( -[[ 1.5693681 -1.3894582] - [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32) -``` -{/if} - -Il y a quelque chose qui ne va pas avec les logits de notre prédiction avec les séquences mises dans un même batch. La deuxième ligne devrait être la même que les logits pour la deuxième phrase, mais nous avons des valeurs complètement différentes ! - -C'est parce que dans un *transformer* les couches d’attention *contextualisent* chaque *token*. Celles-ci prennent en compte les *tokens* de *padding* puisqu'elles analysent tous les *tokens* d'une séquence. Pour obtenir le même résultat lorsque l'on passe dans notre modèle des phrases individuelles de différentes longueurs ou un batch composé de mêmes phrases avec *padding*, nous devons dire à ces couches d'attention d'ignorer les jetons de *padding*. Ceci est fait en utilisant un masque d'attention. - - -## Masques d'attention - -Les masques d'attention sont des tenseurs ayant exactement la même forme que le tenseur d'identifiants d'entrée, remplis de 0 et de 1 : -- 1 indique que les *tokens* correspondants doivent être analysés -- 0 indique que les *tokens* correspondants ne doivent pas être analysés (c'est-à-dire qu'ils doivent être ignorés par les couches d'attention du modèle). - -Complétons l'exemple précédent avec un masque d'attention : - - -{#if fw === 'pt'} -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -attention_mask = [ - [1, 1, 1], - [1, 1, 0], -] - -outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask)) -print(outputs.logits) -``` - -```python out -tensor([[ 1.5694, -1.3895], - [ 0.5803, -0.4125]], grad_fn=) -``` -{:else} -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -attention_mask = [ - [1, 1, 1], - [1, 1, 0], -] - -outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask)) -print(outputs.logits) -``` - -```py out -tf.Tensor( -[[ 1.5693681 -1.3894582 ] - [ 0.5803021 -0.41252586]], shape=(2, 2), dtype=float32) -``` -{/if} - -Nous obtenons maintenant les mêmes logits pour la deuxième phrase du batch. - -Remarquez comment la dernière valeur de la deuxième séquence est un identifiant de *padding* valant 0 dans le masque d'attention. - - - - -✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! - - - - -## Séquences plus longues - -Les *transformers* acceptent en entrée que des séquences d’une longueur limitée. La plupart des modèles traitent des séquences allant jusqu'à 512 ou 1024 *tokens* et plantent lorsqu'on leur demande de traiter des séquences plus longues. Il existe deux solutions à ce problème : - -- utiliser un modèle avec une longueur de séquence supportée plus longue, -- tronquer les séquences. - -Certains modèles sont spécialisés dans le traitement de très longues séquences comme par exemple le [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) ou le [LED](https://huggingface.co/transformers/model_doc/led.html). Si vous travaillez sur une tâche qui nécessite de très longues séquences, nous vous recommandons de jeter un coup d'œil à ces modèles. - -Sinon, nous vous recommandons de tronquer vos séquences en spécifiant le paramètre `max_sequence_length` : - - -```py -sequence = sequence[:max_sequence_length] -``` + + +# Manipulation de plusieurs séquences + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +Dans la section précédente, nous avons exploré le cas d'utilisation le plus simple : faire une inférence sur une seule séquence de petite longueur. Cependant, certaines questions émergent déjà : + +- comment gérer de plusieurs séquences ? +- comment gérer de plusieurs séquences *de longueurs différentes* ? +- les indices du vocabulaire sont-ils les seules entrées qui permettent à un modèle de bien fonctionner ? +- existe-t-il une séquence trop longue ? + +Voyons quels types de problèmes ces questions posent et comment nous pouvons les résoudre en utilisant l'API 🤗 *Transformers*. + +## Les modèles attendent un batch d'entrées + +Dans l'exercice précédent, vous avez vu comment les séquences sont traduites en listes de nombres. +Convertissons cette liste de nombres en un tenseur et envoyons-le au modèle : + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = torch.tensor(ids) +# Cette ligne va échouer. +model(input_ids) +``` + +```python out +IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = tf.constant(ids) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +Pourquoi cela a échoué ? Nous avons suivi les étapes du pipeline de la section 2. + +Le problème est que nous avons envoyé une seule séquence au modèle, alors que les modèles de l’API 🤗 *Transformers* attendent plusieurs phrases par défaut. Ici, nous avons essayé de faire ce que le *tokenizer* fait en coulisses lorsque nous l'avons appliqué à une `séquence`. Cependant si vous regardez de près, vous verrez qu'il n'a pas seulement converti la liste des identifiants d'entrée en un tenseur mais aussi ajouté une dimension par-dessus : + + +{#if fw === 'pt'} +```py +tokenized_inputs = tokenizer(sequence, return_tensors="pt") +print(tokenized_inputs["input_ids"]) +``` + +```python out +tensor([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, + 2607, 2026, 2878, 2166, 1012, 102]]) +``` +{:else} +```py +tokenized_inputs = tokenizer(sequence, return_tensors="tf") +print(tokenized_inputs["input_ids"]) +``` + +```py out + +``` +{/if} + +Essayons à nouveau en ajoutant une nouvelle dimension : + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. + + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) + +input_ids = torch.tensor([ids]) +print("Input IDs:", input_ids) + +output = model(input_ids) +print("Logits:", output.logits) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. + + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) + +input_ids = tf.constant([ids]) +print("Input IDs:", input_ids) + +output = model(input_ids) +print("Logits:", output.logits) +``` +{/if} + +Nous affichons les identifiants d'entrée ainsi que les logits résultants. Voici la sortie : + +{#if fw === 'pt'} +```python out +Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]] +Logits: [[-2.7276, 2.8789]] +``` +{:else} +```py out +Input IDs: tf.Tensor( +[[ 1045 1005 2310 2042 3403 2005 1037 17662 12172 2607 2026 2878 + 2166 1012]], shape=(1, 14), dtype=int32) +Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) +``` +{/if} + +Le « *batching* » est l'acte d'envoyer plusieurs phrases à travers le modèle, toutes en même temps. Si vous n'avez qu'une seule phrase, vous pouvez simplement construire un batch avec une seule séquence : + +``` +batched_ids = [ids, ids] +``` + +Il s'agit d'un batch de deux séquences identiques ! + + + +✏️ **Essayez !** Convertissez cette liste `batched_ids` en un tenseur et passez-la dans votre modèle. Vérifiez que vous obtenez les mêmes logits que précédemment (mais deux fois) ! + + + +Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer*/*remplir* (le *padding* en anglais) les entrées. + +## Padding des entrées + +La liste de listes suivante ne peut pas être convertie en un tenseur : + + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +Afin de contourner ce problème, nous utilisons le *padding* pour que nos tenseurs aient une forme rectangulaire. Le *padding* permet de s'assurer que toutes nos phrases ont la même longueur en ajoutant un mot spécial appelé *padding token* aux phrases ayant moins de valeurs. Par exemple, si vous avez 10 phrases de 10 mots et 1 phrase de 20 mots, le *padding* fait en sorte que toutes les phrases aient 20 mots. Dans notre exemple, le tenseur résultant ressemble à ceci : + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +L'identifiant du jeton de *padding* peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch : + +{#if fw === 'pt'} +```py no-format +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence1_ids = [[200, 200, 200]] +sequence2_ids = [[200, 200]] +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +print(model(torch.tensor(sequence1_ids)).logits) +print(model(torch.tensor(sequence2_ids)).logits) +print(model(torch.tensor(batched_ids)).logits) +``` + +```python out +tensor([[ 1.5694, -1.3895]], grad_fn=) +tensor([[ 0.5803, -0.4125]], grad_fn=) +tensor([[ 1.5694, -1.3895], + [ 1.3373, -1.2163]], grad_fn=) +``` +{:else} +```py no-format +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence1_ids = [[200, 200, 200]] +sequence2_ids = [[200, 200]] +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +print(model(tf.constant(sequence1_ids)).logits) +print(model(tf.constant(sequence2_ids)).logits) +print(model(tf.constant(batched_ids)).logits) +``` + +```py out +tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32) +tf.Tensor([[ 0.5803005 -0.41252428]], shape=(1, 2), dtype=float32) +tf.Tensor( +[[ 1.5693681 -1.3894582] + [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32) +``` +{/if} + +Il y a quelque chose qui ne va pas avec les logits de notre prédiction avec les séquences mises dans un même batch. La deuxième ligne devrait être la même que les logits pour la deuxième phrase, mais nous avons des valeurs complètement différentes ! + +C'est parce que dans un *transformer* les couches d’attention *contextualisent* chaque *token*. Celles-ci prennent en compte les *tokens* de *padding* puisqu'elles analysent tous les *tokens* d'une séquence. Pour obtenir le même résultat lorsque l'on passe dans notre modèle des phrases individuelles de différentes longueurs ou un batch composé de mêmes phrases avec *padding*, nous devons dire à ces couches d'attention d'ignorer les jetons de *padding*. Ceci est fait en utilisant un masque d'attention. + + +## Masques d'attention + +Les masques d'attention sont des tenseurs ayant exactement la même forme que le tenseur d'identifiants d'entrée, remplis de 0 et de 1 : +- 1 indique que les *tokens* correspondants doivent être analysés +- 0 indique que les *tokens* correspondants ne doivent pas être analysés (c'est-à-dire qu'ils doivent être ignorés par les couches d'attention du modèle). + +Complétons l'exemple précédent avec un masque d'attention : + + +{#if fw === 'pt'} +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +attention_mask = [ + [1, 1, 1], + [1, 1, 0], +] + +outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask)) +print(outputs.logits) +``` + +```python out +tensor([[ 1.5694, -1.3895], + [ 0.5803, -0.4125]], grad_fn=) +``` +{:else} +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +attention_mask = [ + [1, 1, 1], + [1, 1, 0], +] + +outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask)) +print(outputs.logits) +``` + +```py out +tf.Tensor( +[[ 1.5693681 -1.3894582 ] + [ 0.5803021 -0.41252586]], shape=(2, 2), dtype=float32) +``` +{/if} + +Nous obtenons maintenant les mêmes logits pour la deuxième phrase du batch. + +Remarquez comment la dernière valeur de la deuxième séquence est un identifiant de *padding* valant 0 dans le masque d'attention. + + + + +✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! + + + + +## Séquences plus longues + +Les *transformers* acceptent en entrée que des séquences d’une longueur limitée. La plupart des modèles traitent des séquences allant jusqu'à 512 ou 1024 *tokens* et plantent lorsqu'on leur demande de traiter des séquences plus longues. Il existe deux solutions à ce problème : + +- utiliser un modèle avec une longueur de séquence supportée plus longue, +- tronquer les séquences. + +Certains modèles sont spécialisés dans le traitement de très longues séquences comme par exemple le [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) ou le [LED](https://huggingface.co/transformers/model_doc/led.html). Si vous travaillez sur une tâche qui nécessite de très longues séquences, nous vous recommandons de jeter un coup d'œil à ces modèles. + +Sinon, nous vous recommandons de tronquer vos séquences en spécifiant le paramètre `max_sequence_length` : + + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/fr/chapter2/6.mdx b/chapters/fr/chapter2/6.mdx index 9602b2a96..4752f7bf0 100644 --- a/chapters/fr/chapter2/6.mdx +++ b/chapters/fr/chapter2/6.mdx @@ -1,188 +1,188 @@ - - -# Tout assembler - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans les dernières sections, nous avons fait de notre mieux pour effectuer la plupart du travail manuellement. Nous avons exploré le fonctionnement des *tokenizers* et examiné la tokenisation, la conversion en identifiants d'entrée, le *padding*, la troncature et les masques d'attention. - -Cependant, comme nous l'avons vu dans la section 2, l'API 🤗 *Transformers* peut gérer tout cela pour nous via une fonction dans laquelle nous allons nous plonger ici. Lorsque vous appelez votre `tokenizer` directement sur la phrase, vous récupérez des entrées qui sont prêtes à être passées dans votre modèle : - - -```py -from transformers import AutoTokenizer - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." -# J'ai attendu un cours d’HuggingFace toute ma vie. - -model_inputs = tokenizer(sequence) -``` - -Ici, la variable `model_inputs` contient tout ce qui est nécessaire au bon fonctionnement d'un modèle. Pour DistilBERT, cela inclut les identifiants d'entrée ainsi que le masque d'attention. D'autres modèles qui acceptent des entrées supplémentaires sont également fournis par l'objet `tokenizer`. - -Comme nous allons le voir dans les quelques exemples ci-dessous, cette méthode est très puissante. Premièrement, elle peut tokeniser une seule séquence : - - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." -# J'ai attendu un cours d’HuggingFace toute ma vie. - - -model_inputs = tokenizer(sequence) -``` - -Elle gère également plusieurs séquences à la fois, sans modification de l'API : - - -```py -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -model_inputs = tokenizer(sequences) -``` - -Il est possible de faire du *padding* selon plusieurs objectifs : - -```py -# Remplit les séquences jusqu'à la longueur maximale de la séquence -model_inputs = tokenizer(sequences, padding="longest") - -# Remplit les séquences jusqu'à la longueur maximale du modèle (512 pour BERT ou DistilBERT) -model_inputs = tokenizer(sequences, padding="max_length") - -# Remplit les séquences jusqu'à la longueur maximale spécifiée -model_inputs = tokenizer(sequences, padding="max_length", max_length=8) -``` - -La fonction peut également tronquer les séquences : - -```py -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -# Tronque les séquences qui sont plus longues que la longueur maximale du modèle -# (512 pour BERT ou DistilBERT) -model_inputs = tokenizer(sequences, truncation=True) - -# Tronque les séquences qui sont plus longues que la longueur maximale spécifiée -model_inputs = tokenizer(sequences, max_length=8, truncation=True) -``` - -L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs PyTorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` : - -```py -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -# Retourne des tenseurs PyTorch -model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") - -# Retourne des tenseurs TensorFlow -model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") - -# Retourne des tableaux NumPy -model_inputs = tokenizer(sequences, padding=True, return_tensors="np") -``` - -## Jetons spéciaux - -Si nous jetons un coup d'œil aux identifiants d'entrée renvoyés par le *tokenizer*, nous verrons qu'ils sont un peu différents de ceux que nous avions précédemment : - - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." -# « J'ai attendu un cours de HuggingFace toute ma vie. » - -model_inputs = tokenizer(sequence) -print(model_inputs["input_ids"]) - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -print(ids) -``` - -```python out -[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102] -[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] -``` - -Un identifiant symbolique a été ajouté au début ainsi qu’un autre à la fin. Décodons les deux séquences d'identifiants ci-dessus pour voir de quoi il s'agit : - -```py -print(tokenizer.decode(model_inputs["input_ids"])) -print(tokenizer.decode(ids)) -``` - -```python out -"[CLS] i've been waiting for a huggingface course my whole life. [SEP]" -"i've been waiting for a huggingface course my whole life." -``` - -Le *tokenizer* a ajouté le mot spécial `[CLS]` au début et le mot spécial `[SEP]` à la fin. C'est parce que le modèle a été pré-entraîné avec ces mots, donc pour obtenir les mêmes résultats pour l'inférence, nous devons également les ajouter. Notez que certains modèles n'ajoutent pas de mots spéciaux, ou en ajoutent des différents. Les modèles peuvent aussi ajouter ces mots spéciaux seulement au début, ou seulement à la fin. Dans tous les cas, le *tokenizer* sait lesquels sont attendus et s'en occupe pour vous. - -## Conclusion : du tokenizer au modèle - -Maintenant que nous avons vu toutes les étapes individuelles que l'objet `tokenizer` utilise lorsqu'il est appliqué sur des textes, voyons une dernière fois comment il peut gérer plusieurs séquences (*padding*), de très longues séquences (*troncation*) et plusieurs types de tenseurs avec son API principale : - - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - - -tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") -output = model(**tokens) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") -output = model(**tokens) -``` -{/if} + + +# Tout assembler + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans les dernières sections, nous avons fait de notre mieux pour effectuer la plupart du travail manuellement. Nous avons exploré le fonctionnement des *tokenizers* et examiné la tokenisation, la conversion en identifiants d'entrée, le *padding*, la troncature et les masques d'attention. + +Cependant, comme nous l'avons vu dans la section 2, l'API 🤗 *Transformers* peut gérer tout cela pour nous via une fonction dans laquelle nous allons nous plonger ici. Lorsque vous appelez votre `tokenizer` directement sur la phrase, vous récupérez des entrées qui sont prêtes à être passées dans votre modèle : + + +```py +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. + +model_inputs = tokenizer(sequence) +``` + +Ici, la variable `model_inputs` contient tout ce qui est nécessaire au bon fonctionnement d'un modèle. Pour DistilBERT, cela inclut les identifiants d'entrée ainsi que le masque d'attention. D'autres modèles qui acceptent des entrées supplémentaires sont également fournis par l'objet `tokenizer`. + +Comme nous allons le voir dans les quelques exemples ci-dessous, cette méthode est très puissante. Premièrement, elle peut tokeniser une seule séquence : + + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. + + +model_inputs = tokenizer(sequence) +``` + +Elle gère également plusieurs séquences à la fois, sans modification de l'API : + + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +model_inputs = tokenizer(sequences) +``` + +Il est possible de faire du *padding* selon plusieurs objectifs : + +```py +# Remplit les séquences jusqu'à la longueur maximale de la séquence +model_inputs = tokenizer(sequences, padding="longest") + +# Remplit les séquences jusqu'à la longueur maximale du modèle (512 pour BERT ou DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Remplit les séquences jusqu'à la longueur maximale spécifiée +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +La fonction peut également tronquer les séquences : + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +# Tronque les séquences qui sont plus longues que la longueur maximale du modèle +# (512 pour BERT ou DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Tronque les séquences qui sont plus longues que la longueur maximale spécifiée +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs PyTorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` : + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +# Retourne des tenseurs PyTorch +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Retourne des tenseurs TensorFlow +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Retourne des tableaux NumPy +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Jetons spéciaux + +Si nous jetons un coup d'œil aux identifiants d'entrée renvoyés par le *tokenizer*, nous verrons qu'ils sont un peu différents de ceux que nous avions précédemment : + + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." +# « J'ai attendu un cours de HuggingFace toute ma vie. » + +model_inputs = tokenizer(sequence) +print(model_inputs["input_ids"]) + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +print(ids) +``` + +```python out +[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102] +[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] +``` + +Un identifiant symbolique a été ajouté au début ainsi qu’un autre à la fin. Décodons les deux séquences d'identifiants ci-dessus pour voir de quoi il s'agit : + +```py +print(tokenizer.decode(model_inputs["input_ids"])) +print(tokenizer.decode(ids)) +``` + +```python out +"[CLS] i've been waiting for a huggingface course my whole life. [SEP]" +"i've been waiting for a huggingface course my whole life." +``` + +Le *tokenizer* a ajouté le mot spécial `[CLS]` au début et le mot spécial `[SEP]` à la fin. C'est parce que le modèle a été pré-entraîné avec ces mots, donc pour obtenir les mêmes résultats pour l'inférence, nous devons également les ajouter. Notez que certains modèles n'ajoutent pas de mots spéciaux, ou en ajoutent des différents. Les modèles peuvent aussi ajouter ces mots spéciaux seulement au début, ou seulement à la fin. Dans tous les cas, le *tokenizer* sait lesquels sont attendus et s'en occupe pour vous. + +## Conclusion : du tokenizer au modèle + +Maintenant que nous avons vu toutes les étapes individuelles que l'objet `tokenizer` utilise lorsqu'il est appliqué sur des textes, voyons une dernière fois comment il peut gérer plusieurs séquences (*padding*), de très longues séquences (*troncation*) et plusieurs types de tenseurs avec son API principale : + + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") +output = model(**tokens) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") +output = model(**tokens) +``` +{/if} diff --git a/chapters/fr/chapter2/7.mdx b/chapters/fr/chapter2/7.mdx index 2b6c11798..31ea012a5 100644 --- a/chapters/fr/chapter2/7.mdx +++ b/chapters/fr/chapter2/7.mdx @@ -1,12 +1,17 @@ -# Utilisation de base terminée ! - -Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez : -- appris les blocs de construction de base d'un *transformer*, -- appris ce qui constitue un pipeline de tokenisation, -- vu comment utiliser un *transformer* en pratique, -- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle, -- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions, -- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention, -- joué avec des méthodes de *tokenizer* polyvalentes et configurables. - -À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps. +# Utilisation de base terminée ! + + + +Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez : +- appris les blocs de construction de base d'un *transformer*, +- appris ce qui constitue un pipeline de tokenisation, +- vu comment utiliser un *transformer* en pratique, +- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle, +- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions, +- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention, +- joué avec des méthodes de *tokenizer* polyvalentes et configurables. + +À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps. diff --git a/chapters/fr/chapter2/8.mdx b/chapters/fr/chapter2/8.mdx index a3fa30228..9b55bd866 100644 --- a/chapters/fr/chapter2/8.mdx +++ b/chapters/fr/chapter2/8.mdx @@ -1,307 +1,312 @@ - - - - -# Quiz de fin de chapitre - -### 1. Quel est l'ordre du pipeline de modélisation du langage ? - - -tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", - explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, - { - text: " Tout d'abord, le tokenizer, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", - explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, - { - text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", - explain: " Le tokenizer peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.", - correct: true - } - ]} -/> - -### 2. Combien de dimensions le tenseur produit par le transformer de base possède-t-il et quelles sont-elles ? - - -transformers gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !" - }, - { - text: "3: la longueur de la séquence, la taille du batch et la taille cachée.", - explain: "", - correct: true - } - ]} -/> - -### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ? - - - -### 4. Qu'est-ce qu'une tête de modèle ? - -transformer de base qui redirige les tenseurs vers leurs couches correctes.", - explain: "Il n'y a pas de tel composant." - }, - { - text: "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un token en fonction des autres tokens de la séquence.", - explain: "La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation." - }, - { - text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du transformer en une sortie spécifique à la tâche.", - explain: "Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.", - correct: true - } - ]} -/> - -{#if fw === 'pt'} -### 5. Qu'est-ce qu'un AutoModel? - -checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" - } - ]} -/> - -{:else} -### 5. What is an AutoModel? - -checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" - } - ]} -/> - -{/if} - -### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ? - - -padding", - explain: "Le padding est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?", - correct: true - }, - { - text: "Les masques d'attention ", - explain: "Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.", - correct: true - } - ]} -/> - -### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ? - - - -### 8. Autour de quelle méthode s'articule la majeure partie de l'API tokenizer ? - -encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", - explain: "Bien que la méthode encode existe sur les tokenizer, elle n'existe pas sur les modèles." - }, - { - text: "Appeler directement l'objet tokenizer", - explain: "La méthode __call__ du tokenizer est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.", - correct: true - }, - { - text: "pad", - explain: "Le padding est très utile mais ce n'est qu'une partie de l'API tokenizer." - }, - { - text: "tokenize", - explain: "La méthode tokenize est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API tokenizer." - } - ]} -/> - -### 9. Que contient la variable `result` dans cet exemple de code ? - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -result = tokenizer.tokenize("Hello!") -``` - -token.", - explain: "Convertissez cela en identifiants, et donnez-les à un modèle !", - correct: true - }, - { - text: "Une liste d'identifiants", - explain: "C'est à cela que la méthode __call__ ou la méthode convert_tokens_to_ids sert !" - }, - { - text: "Une chaîne contenant tous les tokens", - explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments." - } - ]} -/> - -{#if fw === 'pt'} -### 10. Y a-t-il un problème avec le code suivant ? - - -```py -from transformers import AutoTokenizer, AutoModel - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -model = AutoModel.from_pretrained("gpt2") - -encoded = tokenizer("Hey!", return_tensors="pt") -result = model(**encoded) -``` - -tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." - }, - { - text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", - explain: "", - correct: true - }, - { - text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", - explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." - } - ]} -/> - -{:else} -### 10. Y a-t-il un problème avec le code suivant ? - -```py -from transformers import AutoTokenizer, TFAutoModel - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -model = TFAutoModel.from_pretrained("gpt2") - -encoded = tokenizer("Hey!", return_tensors="pt") -result = model(**encoded) -``` - -tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." - }, - { - text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", - explain: "", - correct: true - }, - { - text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", - explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." - } - ]} -/> - -{/if} + + + + +# Quiz de fin de chapitre + + + +### 1. Quel est l'ordre du pipeline de modélisation du langage ? + + +tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", + explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, + { + text: " Tout d'abord, le tokenizer, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", + explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, + { + text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", + explain: " Le tokenizer peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.", + correct: true + } + ]} +/> + +### 2. Combien de dimensions le tenseur produit par le transformer de base possède-t-il et quelles sont-elles ? + + +transformers gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !" + }, + { + text: "3: la longueur de la séquence, la taille du batch et la taille cachée.", + explain: "", + correct: true + } + ]} +/> + +### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ? + + + +### 4. Qu'est-ce qu'une tête de modèle ? + +transformer de base qui redirige les tenseurs vers leurs couches correctes.", + explain: "Il n'y a pas de tel composant." + }, + { + text: "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un token en fonction des autres tokens de la séquence.", + explain: "La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation." + }, + { + text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du transformer en une sortie spécifique à la tâche.", + explain: "Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} +### 5. Qu'est-ce qu'un AutoModel? + +checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" + } + ]} +/> + +{:else} +### 5. What is an AutoModel? + +checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" + } + ]} +/> + +{/if} + +### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ? + + +padding", + explain: "Le padding est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?", + correct: true + }, + { + text: "Les masques d'attention ", + explain: "Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.", + correct: true + } + ]} +/> + +### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ? + + + +### 8. Autour de quelle méthode s'articule la majeure partie de l'API tokenizer ? + +encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", + explain: "Bien que la méthode encode existe sur les tokenizer, elle n'existe pas sur les modèles." + }, + { + text: "Appeler directement l'objet tokenizer", + explain: "La méthode __call__ du tokenizer est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.", + correct: true + }, + { + text: "pad", + explain: "Le padding est très utile mais ce n'est qu'une partie de l'API tokenizer." + }, + { + text: "tokenize", + explain: "La méthode tokenize est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API tokenizer." + } + ]} +/> + +### 9. Que contient la variable `result` dans cet exemple de code ? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +token.", + explain: "Convertissez cela en identifiants, et donnez-les à un modèle !", + correct: true + }, + { + text: "Une liste d'identifiants", + explain: "C'est à cela que la méthode __call__ ou la méthode convert_tokens_to_ids sert !" + }, + { + text: "Une chaîne contenant tous les tokens", + explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments." + } + ]} +/> + +{#if fw === 'pt'} +### 10. Y a-t-il un problème avec le code suivant ? + + +```py +from transformers import AutoTokenizer, AutoModel + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +model = AutoModel.from_pretrained("gpt2") + +encoded = tokenizer("Hey!", return_tensors="pt") +result = model(**encoded) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{:else} +### 10. Y a-t-il un problème avec le code suivant ? + +```py +from transformers import AutoTokenizer, TFAutoModel + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +model = TFAutoModel.from_pretrained("gpt2") + +encoded = tokenizer("Hey!", return_tensors="pt") +result = model(**encoded) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{/if} diff --git a/chapters/fr/chapter3/1.mdx b/chapters/fr/chapter3/1.mdx index 3ce9c58f7..3b9d16505 100644 --- a/chapters/fr/chapter3/1.mdx +++ b/chapters/fr/chapter3/1.mdx @@ -1,23 +1,28 @@ - - -# Introduction - -Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser les *tokenizers* et les modèles pré-entraînés pour faire des prédictions. -Mais que faire si vous souhaitez *finetuner* un modèle pré-entraîné pour votre propre jeu de données ? C'est le sujet de ce chapitre ! Vous allez apprendre à : - -{#if fw === 'pt'} -* savoir comment préparer un très grand jeu de données à partir du *Hub*, -* savoir comment utiliser l'API de haut niveau `Trainer` pour *finetuner* un modèle, -* savoir comment utiliser une boucle d'entraînement personnalisée, -* savoir comment tirer parti de la bibliothèque 🤗 *Accelerate* pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quelle configuration distribuée. -configuration distribuée - -{:else} -* savoir comment préparer un très grand jeu de données à partir du *Hub*, -* savoir comment utiliser Keras pour *finetuner* un modèle, -* savoir comment utiliser Keras pour obtenir des prédictions, -* savoir comment utiliser des métriques personnalisées. - -{/if} - + + +# Introduction + + + +Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser les *tokenizers* et les modèles pré-entraînés pour faire des prédictions. +Mais que faire si vous souhaitez *finetuner* un modèle pré-entraîné pour votre propre jeu de données ? C'est le sujet de ce chapitre ! Vous allez apprendre à : + +{#if fw === 'pt'} +* savoir comment préparer un très grand jeu de données à partir du *Hub*, +* savoir comment utiliser l'API de haut niveau `Trainer` pour *finetuner* un modèle, +* savoir comment utiliser une boucle d'entraînement personnalisée, +* savoir comment tirer parti de la bibliothèque 🤗 *Accelerate* pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quelle configuration distribuée. +configuration distribuée + +{:else} +* savoir comment préparer un très grand jeu de données à partir du *Hub*, +* savoir comment utiliser Keras pour *finetuner* un modèle, +* savoir comment utiliser Keras pour obtenir des prédictions, +* savoir comment utiliser des métriques personnalisées. + +{/if} + Afin de télécharger vos *checkpoints* entraînés sur le *Hub* Hugging Face, vous aurez besoin d'un compte huggingface.co : [créer un compte](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 297c260f5..5a3186a74 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -1,385 +1,385 @@ - - -# Préparer les données - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec PyTorch : - -```python -import torch -from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification - -# Même chose que précédemment -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "This course is amazing!", # Ce cours est incroyable ! -] -batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") - -# Ceci est nouveau -batch["labels"] = torch.tensor([1, 1]) - -optimizer = AdamW(model.parameters()) -loss = model(**batch).loss -loss.backward() -optimizer.step() -``` -{:else} - -En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec TensorFlow : - -```python -import tensorflow as tf -import numpy as np -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -# Même chose que précédemment -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "This course is amazing!", # Ce cours est incroyable ! -] -batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) - -# Ceci est nouveau -model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") -labels = tf.convert_to_tensor([1, 1]) -model.train_on_batch(batch, labels) -``` -{/if} -Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. - -Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. - -### Charger un jeu de données depuis le Hub - -{#if fw === 'pt'} - -{:else} - -{/if} - -Le *Hub* ne contient pas seulement des modèles mais aussi plusieurs jeux de données dans un tas de langues différentes. Vous pouvez explorer les jeux de données [ici](https://huggingface.co/datasets) et nous vous conseillons d'essayer de charger un nouveau jeu de données une fois que vous avez étudié cette section (voir la documentation générale [ici](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Mais pour l'instant, concentrons-nous sur le jeu de données MRPC ! Il s'agit de l'un des 10 jeux de données qui constituent le [*benchmark* GLUE](https://gluebenchmark.com/) qui est un *benchmark* académique utilisé pour mesurer les performances des modèles d'apprentissage automatique sur 10 différentes tâches de classification de textes. - -La bibliothèque 🤗 *Datasets* propose une commande très simple pour télécharger et mettre en cache un jeu de données à partir du *Hub*. On peut télécharger le jeu de données MRPC comme ceci : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("glue", "mrpc") -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 408 - }) - test: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 1725 - }) -}) -``` - -Comme vous le voyez, on obtient un objet de type `DatasetDict` qui contient le jeu de données d'entraînement, celui de validation et celui de test. Chacun d'eux contient plusieurs colonnes (`sentence1`, `sentence2`, `label` et `idx`) et une variable nombre de lignes qui contient le nombre d'éléments dans chaque jeu de données (il y a donc 3.668 paires de phrases dans le jeu d'entraînement, 408 dans celui de validation et 1.725 dans celui de test). - -Cette commande télécharge et met en cache le jeu de données dans *~/.cache/huggingface/dataset*. Rappelez-vous que comme vu au chapitre 2, vous pouvez personnaliser votre dossier cache en modifiant la variable d'environnement `HF_HOME`. - -Nous pouvons accéder à chaque paire de phrase de notre objet `raw_datasets` par les indices, comme avec un dictionnaire : - -```py -raw_train_dataset = raw_datasets["train"] -raw_train_dataset[0] -``` - -```python out -{'idx': 0, - 'label': 1, - 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', - # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage. - 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} - # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage. -``` - -Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne : - -```py -raw_train_dataset.features -``` - -```python out -{'sentence1': Value(dtype='string', id=None), - 'sentence2': Value(dtype='string', id=None), - 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), - 'idx': Value(dtype='int32', id=None)} -``` - -En réalité, `label` est de type `ClassLabel` et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à `not_equivalent` et `1` correspond à `equivalent`. - - - -✏️ **Essayez !** Regardez l'élément 15 de l'ensemble d'entraînement et l'élément 87 de l'ensemble de validation. Quelles sont leurs étiquettes ? - - -### Prétraitement d'un jeu de données - -{#if fw === 'pt'} - -{:else} - -{/if} - -Pour prétraiter le jeu de données, nous devons convertir le texte en chiffres compréhensibles par le modèle. Comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), cette conversion est effectuée par un *tokenizer*. Nous pouvons fournir au *tokenizer* une phrase ou une liste de phrases, de sorte que nous pouvons directement tokeniser toutes les premières phrases et toutes les secondes phrases de chaque paire comme ceci : - -```py -from transformers import AutoTokenizer - -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) -tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) -``` - -Cependant, nous ne pouvons pas simplement passer deux séquences au modèle et obtenir une prédiction pour savoir si les deux phrases sont des paraphrases ou non. Nous devons traiter les deux séquences comme une paire, et appliquer le prétraitement approprié. Heureusement, le *tokenizer* peut également prendre une paire de séquences et la préparer de la manière attendue par notre modèle BERT : - -```py -inputs = tokenizer( - "This is the first sentence.", "This is the second one." -) # "C'est la première phrase.", "C'est la deuxième." -inputs -``` - -```python out -{ - 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], - 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], - 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] -} -``` - -Nous avons discuté des clés `input_ids` et `attention_mask` dans le [chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase. - - - -✏️ **Essayez !** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et par paire. Quelle est la différence entre les deux résultats ? - - - -Si on décode les IDs dans `input_ids` en mots : - -```py -tokenizer.convert_ids_to_tokens(inputs["input_ids"]) -``` - -nous aurons : - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -``` - -Nous voyons donc que le modèle s'attend à ce que les entrées soient de la forme `[CLS] phrase1 [SEP] phrase2 [SEP]` lorsqu'il y a deux phrases. En alignant cela avec les `token_type_ids`, on obtient : - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] -``` - -Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sentence1 [SEP]` ont toutes un *token* de type ID de `0`, tandis que les autres parties, correspondant à `sentence2 [SEP]`, ont toutes un *token* de type ID de `1`. - -Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. - -Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases. - -Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. - -En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle. - -Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement : - -```py -tokenized_dataset = tokenizer( - raw_datasets["train"]["sentence1"], - raw_datasets["train"]["sentence2"], - padding=True, - truncation=True, -) -``` - -Cela fonctionne bien, mais a l'inconvénient de retourner un dictionnaire (avec nos clés, `input_ids`, `attention_mask`, et `token_type_ids`, et des valeurs qui sont des listes de listes). Cela ne fonctionnera également que si vous avez assez de RAM pour stocker l'ensemble de votre jeu de données pendant la tokenisation (alors que les jeux de données de la bibliothèque 🤗 *Datasets* sont des fichiers [Apache Arrow](https://arrow.apache.org/) stockés sur le disque, vous ne gardez donc en mémoire que les échantillons que vous demandez). - -Pour conserver les données sous forme de jeu de données, nous utiliserons la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Cela nous permet également une certaine flexibilité, si nous avons besoin d'un prétraitement plus poussé que la simple tokenisation. La méthode `map()` fonctionne en appliquant une fonction sur chaque élément de l'ensemble de données, donc définissons une fonction qui tokenise nos entrées : - -```py -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) -``` - -Cette fonction prend un dictionnaire (comme les éléments de notre jeu de données) et retourne un nouveau dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`. Notez que cela fonctionne également si le dictionnaire `example` contient plusieurs échantillons (chaque clé étant une liste de phrases) puisque le `tokenizer` travaille sur des listes de paires de phrases, comme vu précédemment. Cela nous permettra d'utiliser l'option `batched=True` dans notre appel à `map()`, ce qui accélérera grandement la tokénisation. Le `tokenizer` est soutenu par un *tokenizer* écrit en Rust à partir de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers). Ce *tokenizer* peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps. - -Notez que nous avons laissé l'argument `padding` hors de notre fonction de *tokenizer* pour le moment. C'est parce que le *padding* de tous les échantillons à la longueur maximale n'est pas efficace : il est préférable de remplir les échantillons lorsque nous construisons un batch, car alors nous avons seulement besoin de remplir à la longueur maximale dans ce batch, et non la longueur maximale dans l'ensemble des données. Cela peut permettre de gagner beaucoup de temps et de puissance de traitement lorsque les entrées ont des longueurs très variables ! - -Voici comment nous appliquons la fonction de tokenization sur tous nos jeux de données en même temps. Nous utilisons `batched=True` dans notre appel à `map` pour que la fonction soit appliquée à plusieurs éléments de notre jeu de données en une fois, et non à chaque élément séparément. Cela permet un prétraitement plus rapide. - -```py -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -tokenized_datasets -``` - -La façon dont la bibliothèque 🤗 *Datasets* applique ce traitement consiste à ajouter de nouveaux champs aux jeux de données, un pour chaque clé du dictionnaire renvoyé par la fonction de prétraitement : - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 408 - }) - test: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 1725 - }) -}) -``` - -Vous pouvez même utiliser le multitraitement lorsque vous appliquez votre fonction de prétraitement avec `map()` en passant un argument `num_proc`. Nous ne l'avons pas fait ici parce que la bibliothèque 🤗 *Tokenizers* utilise déjà plusieurs *threads* pour tokeniser nos échantillons plus rapidement, mais si vous n'utilisez pas un *tokenizer* rapide soutenu par cette bibliothèque, cela pourrait accélérer votre prétraitement. - -Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les divisions de notre jeu de données. Notez que nous aurions également pu modifier des champs existants si notre fonction de prétraitement avait retourné une nouvelle valeur pour une clé existante dans l'ensemble de données auquel nous avons appliqué `map()`. - -La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*. - -### Padding dynamique - - - -{#if fw === 'pt'} -La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. - -{:else} -La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. - -{/if} -Pour faire cela en pratique, nous devons définir une fonction de rassemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin : - -{#if fw === 'pt'} -```py -from transformers import DataCollatorWithPadding - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) -``` -{:else} -```py -from transformers import DataCollatorWithPadding - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") -``` -{/if} - -Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des *strings* (et nous ne pouvons pas créer des tenseurs avec des *strings*) et on regarde la longueur de chaque entrée du batch : - -```py -samples = tokenized_datasets["train"][:8] -samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} -[len(x) for x in samples["input_ids"]] -``` - -```python out -[50, 59, 47, 67, 59, 50, 62, 32] -``` - -Sans surprise, nous obtenons des échantillons de longueur variable, de 32 à 67. Le *padding* dynamique signifie que les échantillons de ce batch doivent tous être rembourrés à une longueur de 67, la longueur maximale dans le batch. Sans le *padding* dynamique, tous les échantillons devraient être rembourrés à la longueur maximale du jeu de données entier, ou à la longueur maximale que le modèle peut accepter. Vérifions à nouveau que notre `data_collator` rembourre dynamiquement le batch correctement : - -```py -batch = data_collator(samples) -{k: v.shape for k, v in batch.items()} -``` - -{#if fw === 'tf'} - -```python out -{'attention_mask': TensorShape([8, 67]), - 'input_ids': TensorShape([8, 67]), - 'token_type_ids': TensorShape([8, 67]), - 'labels': TensorShape([8])} -``` - -{:else} - -```python out -{'attention_mask': torch.Size([8, 67]), - 'input_ids': torch.Size([8, 67]), - 'token_type_ids': torch.Size([8, 67]), - 'labels': torch.Size([8])} -``` - -C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que notre modèle peut traiter, nous sommes prêts à le *finetuner* ! - -{/if} - - - -✏️ **Essayez !** Reproduisez le prétraitement sur le jeu de données GLUE SST-2. C'est un peu différent puisqu'il est composé de phrases simples au lieu de paires, mais le reste de ce que nous avons fait devrait être identique. Pour un défi plus difficile, essayez d'écrire une fonction de prétraitement qui fonctionne sur toutes les tâches GLUE. - - - -{#if fw === 'tf'} - -Maintenant que nous disposons de notre jeu de données et d'un collecteur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données ! - -```py -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["attention_mask", "input_ids", "token_type_ids"], - label_cols=["labels"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, -) - -tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["attention_mask", "input_ids", "token_type_ids"], - label_cols=["labels"], - shuffle=False, - collate_fn=data_collator, - batch_size=8, -) -``` - -Et c'est tout ! Nous pouvons utiliser ces jeux de données dans le prochain cours, où l'entraînement sera agréablement simple après tout le dur travail de prétraitement des données. - -{/if} + + +# Préparer les données + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec PyTorch : + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Même chose que précédemment +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "This course is amazing!", # Ce cours est incroyable ! +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# Ceci est nouveau +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} + +En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec TensorFlow : + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Même chose que précédemment +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "This course is amazing!", # Ce cours est incroyable ! +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# Ceci est nouveau +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} +Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. + +Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. + +### Charger un jeu de données depuis le Hub + +{#if fw === 'pt'} + +{:else} + +{/if} + +Le *Hub* ne contient pas seulement des modèles mais aussi plusieurs jeux de données dans un tas de langues différentes. Vous pouvez explorer les jeux de données [ici](https://huggingface.co/datasets) et nous vous conseillons d'essayer de charger un nouveau jeu de données une fois que vous avez étudié cette section (voir la documentation générale [ici](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Mais pour l'instant, concentrons-nous sur le jeu de données MRPC ! Il s'agit de l'un des 10 jeux de données qui constituent le [*benchmark* GLUE](https://gluebenchmark.com/) qui est un *benchmark* académique utilisé pour mesurer les performances des modèles d'apprentissage automatique sur 10 différentes tâches de classification de textes. + +La bibliothèque 🤗 *Datasets* propose une commande très simple pour télécharger et mettre en cache un jeu de données à partir du *Hub*. On peut télécharger le jeu de données MRPC comme ceci : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Comme vous le voyez, on obtient un objet de type `DatasetDict` qui contient le jeu de données d'entraînement, celui de validation et celui de test. Chacun d'eux contient plusieurs colonnes (`sentence1`, `sentence2`, `label` et `idx`) et une variable nombre de lignes qui contient le nombre d'éléments dans chaque jeu de données (il y a donc 3.668 paires de phrases dans le jeu d'entraînement, 408 dans celui de validation et 1.725 dans celui de test). + +Cette commande télécharge et met en cache le jeu de données dans *~/.cache/huggingface/dataset*. Rappelez-vous que comme vu au chapitre 2, vous pouvez personnaliser votre dossier cache en modifiant la variable d'environnement `HF_HOME`. + +Nous pouvons accéder à chaque paire de phrase de notre objet `raw_datasets` par les indices, comme avec un dictionnaire : + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage. + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} + # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage. +``` + +Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne : + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +En réalité, `label` est de type `ClassLabel` et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à `not_equivalent` et `1` correspond à `equivalent`. + + + +✏️ **Essayez !** Regardez l'élément 15 de l'ensemble d'entraînement et l'élément 87 de l'ensemble de validation. Quelles sont leurs étiquettes ? + + +### Prétraitement d'un jeu de données + +{#if fw === 'pt'} + +{:else} + +{/if} + +Pour prétraiter le jeu de données, nous devons convertir le texte en chiffres compréhensibles par le modèle. Comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), cette conversion est effectuée par un *tokenizer*. Nous pouvons fournir au *tokenizer* une phrase ou une liste de phrases, de sorte que nous pouvons directement tokeniser toutes les premières phrases et toutes les secondes phrases de chaque paire comme ceci : + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Cependant, nous ne pouvons pas simplement passer deux séquences au modèle et obtenir une prédiction pour savoir si les deux phrases sont des paraphrases ou non. Nous devons traiter les deux séquences comme une paire, et appliquer le prétraitement approprié. Heureusement, le *tokenizer* peut également prendre une paire de séquences et la préparer de la manière attendue par notre modèle BERT : + +```py +inputs = tokenizer( + "This is the first sentence.", "This is the second one." +) # "C'est la première phrase.", "C'est la deuxième." +inputs +``` + +```python out +{ + 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], + 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], + 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +} +``` + +Nous avons discuté des clés `input_ids` et `attention_mask` dans le [chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase. + + + +✏️ **Essayez !** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et par paire. Quelle est la différence entre les deux résultats ? + + + +Si on décode les IDs dans `input_ids` en mots : + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +nous aurons : + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Nous voyons donc que le modèle s'attend à ce que les entrées soient de la forme `[CLS] phrase1 [SEP] phrase2 [SEP]` lorsqu'il y a deux phrases. En alignant cela avec les `token_type_ids`, on obtient : + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sentence1 [SEP]` ont toutes un *token* de type ID de `0`, tandis que les autres parties, correspondant à `sentence2 [SEP]`, ont toutes un *token* de type ID de `1`. + +Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. + +Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases. + +Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. + +En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle. + +Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement : + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +Cela fonctionne bien, mais a l'inconvénient de retourner un dictionnaire (avec nos clés, `input_ids`, `attention_mask`, et `token_type_ids`, et des valeurs qui sont des listes de listes). Cela ne fonctionnera également que si vous avez assez de RAM pour stocker l'ensemble de votre jeu de données pendant la tokenisation (alors que les jeux de données de la bibliothèque 🤗 *Datasets* sont des fichiers [Apache Arrow](https://arrow.apache.org/) stockés sur le disque, vous ne gardez donc en mémoire que les échantillons que vous demandez). + +Pour conserver les données sous forme de jeu de données, nous utiliserons la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Cela nous permet également une certaine flexibilité, si nous avons besoin d'un prétraitement plus poussé que la simple tokenisation. La méthode `map()` fonctionne en appliquant une fonction sur chaque élément de l'ensemble de données, donc définissons une fonction qui tokenise nos entrées : + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Cette fonction prend un dictionnaire (comme les éléments de notre jeu de données) et retourne un nouveau dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`. Notez que cela fonctionne également si le dictionnaire `example` contient plusieurs échantillons (chaque clé étant une liste de phrases) puisque le `tokenizer` travaille sur des listes de paires de phrases, comme vu précédemment. Cela nous permettra d'utiliser l'option `batched=True` dans notre appel à `map()`, ce qui accélérera grandement la tokénisation. Le `tokenizer` est soutenu par un *tokenizer* écrit en Rust à partir de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers). Ce *tokenizer* peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps. + +Notez que nous avons laissé l'argument `padding` hors de notre fonction de *tokenizer* pour le moment. C'est parce que le *padding* de tous les échantillons à la longueur maximale n'est pas efficace : il est préférable de remplir les échantillons lorsque nous construisons un batch, car alors nous avons seulement besoin de remplir à la longueur maximale dans ce batch, et non la longueur maximale dans l'ensemble des données. Cela peut permettre de gagner beaucoup de temps et de puissance de traitement lorsque les entrées ont des longueurs très variables ! + +Voici comment nous appliquons la fonction de tokenization sur tous nos jeux de données en même temps. Nous utilisons `batched=True` dans notre appel à `map` pour que la fonction soit appliquée à plusieurs éléments de notre jeu de données en une fois, et non à chaque élément séparément. Cela permet un prétraitement plus rapide. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +La façon dont la bibliothèque 🤗 *Datasets* applique ce traitement consiste à ajouter de nouveaux champs aux jeux de données, un pour chaque clé du dictionnaire renvoyé par la fonction de prétraitement : + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Vous pouvez même utiliser le multitraitement lorsque vous appliquez votre fonction de prétraitement avec `map()` en passant un argument `num_proc`. Nous ne l'avons pas fait ici parce que la bibliothèque 🤗 *Tokenizers* utilise déjà plusieurs *threads* pour tokeniser nos échantillons plus rapidement, mais si vous n'utilisez pas un *tokenizer* rapide soutenu par cette bibliothèque, cela pourrait accélérer votre prétraitement. + +Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les divisions de notre jeu de données. Notez que nous aurions également pu modifier des champs existants si notre fonction de prétraitement avait retourné une nouvelle valeur pour une clé existante dans l'ensemble de données auquel nous avons appliqué `map()`. + +La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*. + +### Padding dynamique + + + +{#if fw === 'pt'} +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. + +{:else} +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. + +{/if} +Pour faire cela en pratique, nous devons définir une fonction de rassemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin : + +{#if fw === 'pt'} +```py +from transformers import DataCollatorWithPadding + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` +{:else} +```py +from transformers import DataCollatorWithPadding + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") +``` +{/if} + +Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des *strings* (et nous ne pouvons pas créer des tenseurs avec des *strings*) et on regarde la longueur de chaque entrée du batch : + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Sans surprise, nous obtenons des échantillons de longueur variable, de 32 à 67. Le *padding* dynamique signifie que les échantillons de ce batch doivent tous être rembourrés à une longueur de 67, la longueur maximale dans le batch. Sans le *padding* dynamique, tous les échantillons devraient être rembourrés à la longueur maximale du jeu de données entier, ou à la longueur maximale que le modèle peut accepter. Vérifions à nouveau que notre `data_collator` rembourre dynamiquement le batch correctement : + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que notre modèle peut traiter, nous sommes prêts à le *finetuner* ! + +{/if} + + + +✏️ **Essayez !** Reproduisez le prétraitement sur le jeu de données GLUE SST-2. C'est un peu différent puisqu'il est composé de phrases simples au lieu de paires, mais le reste de ce que nous avons fait devrait être identique. Pour un défi plus difficile, essayez d'écrire une fonction de prétraitement qui fonctionne sur toutes les tâches GLUE. + + + +{#if fw === 'tf'} + +Maintenant que nous disposons de notre jeu de données et d'un collecteur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données ! + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +Et c'est tout ! Nous pouvons utiliser ces jeux de données dans le prochain cours, où l'entraînement sera agréablement simple après tout le dur travail de prétraitement des données. + +{/if} diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx index 2624cdd5a..eba84e1b1 100644 --- a/chapters/fr/chapter3/3.mdx +++ b/chapters/fr/chapter3/3.mdx @@ -1,171 +1,171 @@ - - -# Finetuner un modèle avec l'API Trainer - - - - - -La bibliothèque 🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'elle met à disposition sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). - -Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : - -```py -from datasets import load_dataset -from transformers import AutoTokenizer, DataCollatorWithPadding - -raw_datasets = load_dataset("glue", "mrpc") -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - - -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) - - -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) -``` - -### Entraînement - -La première étape avant de pouvoir définir notre `Trainer` est de définir une classe `TrainingArguments` qui contiendra tous les hyperparamètres que le `Trainer` utilisera pour l'entraînement et l'évaluation. Le seul argument que vous devez fournir est un répertoire où le modèle entraîné sera sauvegardé, ainsi que les *checkpoints*. Pour tout le reste, vous pouvez laisser les valeurs par défaut, qui devraient fonctionner assez bien pour un *finetuning* de base. - -```py -from transformers import TrainingArguments - -training_args = TrainingArguments("test-trainer") -``` - - - -💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [chapitre 4](/course/fr/chapter4/3). - - - -La deuxième étape consiste à définir notre modèle. Comme dans le [chapitre précédent](/course/fr/chapter2), nous utiliserons la classe `AutoModelForSequenceClassification`, avec deux labels : - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -Vous remarquerez que contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. - -Une fois que nous avons notre modèle, nous pouvons définir un `Trainer` en lui passant tous les objets construits jusqu'à présent : le `model`, le `training_args`, les jeux de données d'entraînement et de validation, notre `data_collator`, et notre `tokenizer` : - -```py -from transformers import Trainer - -trainer = Trainer( - model, - training_args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - tokenizer=tokenizer, -) -``` - -Notez que lorsque vous passez le `tokenizer` comme nous l'avons fait ici, le `data_collator` par défaut utilisé par le `Trainer` sera un `DataCollatorWithPadding` comme défini précédemment. Ainsi, vous pouvez sauter la ligne `data_collator=data_collator` dans cet appel. Il était quand même important de vous montrer cette partie du traitement dans la section 2 ! - -Pour *finetuner* le modèle sur notre jeu de données, il suffit d'appeler la méthode `train()` de notre `Trainer` : - -```py -trainer.train() -``` - -Cela lancera le *finetuning* (qui devrait prendre quelques minutes sur un GPU) et indiquera la perte d'entraînement tous les 500 pas. Cependant, elle ne vous dira pas si votre modèle fonctionne bien (ou mal). Ceci est dû au fait que : - -1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque *epoch*). -2. nous n'avons pas fourni au `Trainer` une fonction `compute_metrics()` pour calculer une métrique pendant ladite évaluation (sinon l'évaluation aurait juste affiché la perte, qui n'est pas un nombre très intuitif). - - -### Evaluation - -Voyons comment nous pouvons construire une fonction `compute_metrics()` utile et l'utiliser la prochaine fois que nous entraînons. La fonction doit prendre un objet `EvalPrediction` (qui est un *tuple* nommé avec un champ `predictions` et un champ `label_ids`) et retournera un dictionnaire de chaînes de caractères vers des flottants (les chaînes de caractères étant les noms des métriques retournées, et les flottants leurs valeurs). Pour obtenir des prédictions de notre modèle, nous pouvons utiliser la commande `Trainer.predict()` : - -```py -predictions = trainer.predict(tokenized_datasets["validation"]) -print(predictions.predictions.shape, predictions.label_ids.shape) -``` - -```python out -(408, 2) (408,) -``` - -La sortie de la méthode `predict()` est un autre *tuple* nommé avec trois champs : `predictions`, `label_ids`, et `metrics`. Le champ `metrics` contiendra juste la perte sur le jeu de données passé, ainsi que quelques mesures de temps (combien de temps il a fallu pour prédire, au total et en moyenne). Une fois que nous aurons complété notre fonction `compute_metrics()` et que nous l'aurons passé au `Trainer`, ce champ contiendra également les métriques retournées par `compute_metrics()`. - -Comme vous pouvez le voir, `predictions` est un tableau bidimensionnel de forme 408 x 2 (408 étant le nombre d'éléments dans le jeu de données que nous avons utilisé). Ce sont les logits pour chaque élément du jeu de données que nous avons passé à `predict()` (comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), tous les *transformers* retournent des logits). Pour les transformer en prédictions que nous pouvons comparer à nos étiquettes, nous devons prendre l'indice avec la valeur maximale sur le second axe : - -```py -import numpy as np - -preds = np.argmax(predictions.predictions, axis=-1) -``` - -Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 [*Evaluate*](https://github.com/huggingface/evaluate/). Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique : - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -metric.compute(predictions=preds, references=predictions.label_ids) -``` - -```python out -{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} -``` - -Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. - -En regroupant le tout, nous obtenons notre fonction `compute_metrics()` : - -```py -def compute_metrics(eval_preds): - metric = evaluate.load("glue", "mrpc") - logits, labels = eval_preds - predictions = np.argmax(logits, axis=-1) - return metric.compute(predictions=predictions, references=labels) -``` - -Et pour le voir utilisé en action pour rapporter les métriques à la fin de chaque époque, voici comment nous définissons un nouveau `Trainer` avec cette fonction `compute_metrics()` : - -```py -training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) - -trainer = Trainer( - model, - training_args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - tokenizer=tokenizer, - compute_metrics=compute_metrics, -) -``` - -Notez que nous créons un nouveau `TrainingArguments` avec sa `evaluation_strategy` définie sur `"epoch"` et un nouveau modèle. Sinon, nous ne ferions que continuer l'entraînement du modèle que nous avons déjà entraîné. Pour lancer un nouveau cycle d'entraînement, nous exécutons : - -``` -trainer.train() -``` - -Cette fois, il indiquera la perte et les mesures de validation à la fin de chaque époque, en plus de la perte d'entraînement. Encore une fois, le score exact de précision/F1 que vous atteignez peut être un peu différent de ce que nous avons trouvé, en raison de l'initialisation aléatoire de la tête du modèle, mais il devrait être dans la même fourchette. - -Le `Trainer` fonctionnera sur plusieurs GPUs ou TPUs et fournit beaucoup d'options, comme l'entraînement en précision mixte (utilisez `fp16 = True` dans vos arguments d'entraînement). Nous passerons en revue tout ce qu'il supporte dans le chapitre 10. - -Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur. - - - -✏️ **Essayez !** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2. - - + + +# Finetuner un modèle avec l'API Trainer + + + + + +La bibliothèque 🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'elle met à disposition sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). + +Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Entraînement + +La première étape avant de pouvoir définir notre `Trainer` est de définir une classe `TrainingArguments` qui contiendra tous les hyperparamètres que le `Trainer` utilisera pour l'entraînement et l'évaluation. Le seul argument que vous devez fournir est un répertoire où le modèle entraîné sera sauvegardé, ainsi que les *checkpoints*. Pour tout le reste, vous pouvez laisser les valeurs par défaut, qui devraient fonctionner assez bien pour un *finetuning* de base. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [chapitre 4](/course/fr/chapter4/3). + + + +La deuxième étape consiste à définir notre modèle. Comme dans le [chapitre précédent](/course/fr/chapter2), nous utiliserons la classe `AutoModelForSequenceClassification`, avec deux labels : + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Vous remarquerez que contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. + +Une fois que nous avons notre modèle, nous pouvons définir un `Trainer` en lui passant tous les objets construits jusqu'à présent : le `model`, le `training_args`, les jeux de données d'entraînement et de validation, notre `data_collator`, et notre `tokenizer` : + +```py +from transformers import Trainer + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +Notez que lorsque vous passez le `tokenizer` comme nous l'avons fait ici, le `data_collator` par défaut utilisé par le `Trainer` sera un `DataCollatorWithPadding` comme défini précédemment. Ainsi, vous pouvez sauter la ligne `data_collator=data_collator` dans cet appel. Il était quand même important de vous montrer cette partie du traitement dans la section 2 ! + +Pour *finetuner* le modèle sur notre jeu de données, il suffit d'appeler la méthode `train()` de notre `Trainer` : + +```py +trainer.train() +``` + +Cela lancera le *finetuning* (qui devrait prendre quelques minutes sur un GPU) et indiquera la perte d'entraînement tous les 500 pas. Cependant, elle ne vous dira pas si votre modèle fonctionne bien (ou mal). Ceci est dû au fait que : + +1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque *epoch*). +2. nous n'avons pas fourni au `Trainer` une fonction `compute_metrics()` pour calculer une métrique pendant ladite évaluation (sinon l'évaluation aurait juste affiché la perte, qui n'est pas un nombre très intuitif). + + +### Evaluation + +Voyons comment nous pouvons construire une fonction `compute_metrics()` utile et l'utiliser la prochaine fois que nous entraînons. La fonction doit prendre un objet `EvalPrediction` (qui est un *tuple* nommé avec un champ `predictions` et un champ `label_ids`) et retournera un dictionnaire de chaînes de caractères vers des flottants (les chaînes de caractères étant les noms des métriques retournées, et les flottants leurs valeurs). Pour obtenir des prédictions de notre modèle, nous pouvons utiliser la commande `Trainer.predict()` : + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +La sortie de la méthode `predict()` est un autre *tuple* nommé avec trois champs : `predictions`, `label_ids`, et `metrics`. Le champ `metrics` contiendra juste la perte sur le jeu de données passé, ainsi que quelques mesures de temps (combien de temps il a fallu pour prédire, au total et en moyenne). Une fois que nous aurons complété notre fonction `compute_metrics()` et que nous l'aurons passé au `Trainer`, ce champ contiendra également les métriques retournées par `compute_metrics()`. + +Comme vous pouvez le voir, `predictions` est un tableau bidimensionnel de forme 408 x 2 (408 étant le nombre d'éléments dans le jeu de données que nous avons utilisé). Ce sont les logits pour chaque élément du jeu de données que nous avons passé à `predict()` (comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), tous les *transformers* retournent des logits). Pour les transformer en prédictions que nous pouvons comparer à nos étiquettes, nous devons prendre l'indice avec la valeur maximale sur le second axe : + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 [*Evaluate*](https://github.com/huggingface/evaluate/). Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique : + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. + +En regroupant le tout, nous obtenons notre fonction `compute_metrics()` : + +```py +def compute_metrics(eval_preds): + metric = evaluate.load("glue", "mrpc") + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + return metric.compute(predictions=predictions, references=labels) +``` + +Et pour le voir utilisé en action pour rapporter les métriques à la fin de chaque époque, voici comment nous définissons un nouveau `Trainer` avec cette fonction `compute_metrics()` : + +```py +training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +Notez que nous créons un nouveau `TrainingArguments` avec sa `evaluation_strategy` définie sur `"epoch"` et un nouveau modèle. Sinon, nous ne ferions que continuer l'entraînement du modèle que nous avons déjà entraîné. Pour lancer un nouveau cycle d'entraînement, nous exécutons : + +``` +trainer.train() +``` + +Cette fois, il indiquera la perte et les mesures de validation à la fin de chaque époque, en plus de la perte d'entraînement. Encore une fois, le score exact de précision/F1 que vous atteignez peut être un peu différent de ce que nous avons trouvé, en raison de l'initialisation aléatoire de la tête du modèle, mais il devrait être dans la même fourchette. + +Le `Trainer` fonctionnera sur plusieurs GPUs ou TPUs et fournit beaucoup d'options, comme l'entraînement en précision mixte (utilisez `fp16 = True` dans vos arguments d'entraînement). Nous passerons en revue tout ce qu'il supporte dans le chapitre 10. + +Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur. + + + +✏️ **Essayez !** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2. + + diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 9a84d533d..bace781f0 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -1,190 +1,190 @@ - - -# Finetuner un modèle avec Keras - - - -Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). - -Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : - -```py -from datasets import load_dataset -from transformers import AutoTokenizer, DataCollatorWithPadding -import numpy as np - -raw_datasets = load_dataset("glue", "mrpc") -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - - -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) - - -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") - -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["attention_mask", "input_ids", "token_type_ids"], - label_cols=["labels"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, -) - -tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["attention_mask", "input_ids", "token_type_ids"], - label_cols=["labels"], - shuffle=False, - collate_fn=data_collator, - batch_size=8, -) -``` - -### Entraînement - -Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras. - - - -Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci. - - - -Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : - -```py -from transformers import TFAutoModelForSequenceClassification - -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -Vous remarquerez que, contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. - -Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque. - - - -Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici. - - - -```py -from tensorflow.keras.losses import SparseCategoricalCrossentropy - -model.compile( - optimizer="adam", - loss=SparseCategoricalCrossentropy(from_logits=True), - metrics=["accuracy"], -) -model.fit( - tf_train_dataset, - validation_data=tf_validation_dataset, -) -``` - - - -Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne. - - - - -### Améliorer les performances d'entraînement - - - -Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. - -En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. - -```py -from tensorflow.keras.optimizers.schedules import PolynomialDecay - -batch_size = 8 -num_epochs = 3 -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size. -num_train_steps = len(tf_train_dataset) * num_epochs -lr_scheduler = PolynomialDecay( - initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps -) -from tensorflow.keras.optimizers import Adam - -opt = Adam(learning_rate=lr_scheduler) -``` - - - -La bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours. - - - -Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur : - -```py -import tensorflow as tf - -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) -model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) -``` - -Maintenant, on *fit* : - -```py -model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -``` - - - -💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [chapitre 4](/course/fr/chapter4/3). - - - -### Prédictions du modèle - - - - -Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe. - -```py -preds = model.predict(tf_validation_dataset)["logits"] -``` - -Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable : - -```py -class_preds = np.argmax(preds, axis=1) -print(preds.shape, class_preds.shape) -``` - -```python out -(408, 2) (408,) -``` - -Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) -``` - -```python out -{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} -``` - -Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. - -Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. + + +# Finetuner un modèle avec Keras + + + +Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). + +Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding +import numpy as np + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") + +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +### Entraînement + +Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras. + + + +Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci. + + + +Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Vous remarquerez que, contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. + +Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque. + + + +Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici. + + + +```py +from tensorflow.keras.losses import SparseCategoricalCrossentropy + +model.compile( + optimizer="adam", + loss=SparseCategoricalCrossentropy(from_logits=True), + metrics=["accuracy"], +) +model.fit( + tf_train_dataset, + validation_data=tf_validation_dataset, +) +``` + + + +Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne. + + + + +### Améliorer les performances d'entraînement + + + +Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. + +En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size. +num_train_steps = len(tf_train_dataset) * num_epochs +lr_scheduler = PolynomialDecay( + initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps +) +from tensorflow.keras.optimizers import Adam + +opt = Adam(learning_rate=lr_scheduler) +``` + + + +La bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours. + + + +Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur : + +```py +import tensorflow as tf + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) +model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) +``` + +Maintenant, on *fit* : + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [chapitre 4](/course/fr/chapter4/3). + + + +### Prédictions du modèle + + + + +Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable : + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. + +Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index e66caa6db..b04812639 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -1,359 +1,359 @@ -# Un entraînement complet - - - - - -Maintenant nous allons voir comment obtenir les mêmes résultats que dans la dernière section sans utiliser la classe `Trainer`. Encore une fois, nous supposons que vous avez fait le traitement des données dans la section 2. Voici un court résumé couvrant tout ce dont vous aurez besoin : - -```py -from datasets import load_dataset -from transformers import AutoTokenizer, DataCollatorWithPadding - -raw_datasets = load_dataset("glue", "mrpc") -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - - -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) - - -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) -``` - -### Préparer l'entraînement - -Avant d'écrire réellement notre boucle d'entraînement, nous devons définir quelques objets. Les premiers sont les *dataloaders* que nous utiliserons pour itérer sur les batchs. Mais avant de pouvoir définir ces chargeurs de données, nous devons appliquer un peu de post-traitement à nos `tokenized_datasets`, pour prendre soin de certaines choses que le `Trainer` fait pour nous automatiquement. Spécifiquement, nous devons : - -- supprimer les colonnes correspondant aux valeurs que le modèle n'attend pas (comme les colonnes `sentence1` et `sentence2`), -- renommer la colonne `label` en `labels` (parce que le modèle s'attend à ce que l'argument soit nommé `labels`), -- définir le format des jeux de données pour qu'ils retournent des tenseurs PyTorch au lieu de listes. - -Notre `tokenized_datasets` a une méthode pour chacune de ces étapes : - -```py -tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) -tokenized_datasets = tokenized_datasets.rename_column("label", "labels") -tokenized_datasets.set_format("torch") -tokenized_datasets["train"].column_names -``` - -Nous pouvons alors vérifier que le résultat ne comporte que des colonnes que notre modèle acceptera : - -```python -["attention_mask", "input_ids", "labels", "token_type_ids"] -``` - -Maintenant que cela est fait, nous pouvons facilement définir nos *dataloaders* : - -```py -from torch.utils.data import DataLoader - -train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator -) -``` - -Pour vérifier rapidement qu'il n'y a pas d'erreur dans le traitement des données, nous pouvons inspecter un batch comme celui-ci : - -```py -for batch in train_dataloader: - break -{k: v.shape for k, v in batch.items()} -``` - -```python out -{'attention_mask': torch.Size([8, 65]), - 'input_ids': torch.Size([8, 65]), - 'labels': torch.Size([8]), - 'token_type_ids': torch.Size([8, 65])} -``` - -Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le batch. - -Maintenant que nous en avons terminé avec le prétraitement des données (un objectif satisfaisant mais difficile à atteindre pour tout praticien d'apprentissage automatique), passons au modèle. Nous l'instancions exactement comme nous l'avons fait dans la section précédente : - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -Pour s'assurer que tout se passera bien pendant l'entraînement, nous transmettons notre batch à ce modèle : - -```py -outputs = model(**batch) -print(outputs.loss, outputs.logits.shape) -``` - -```python out -tensor(0.5441, grad_fn=) torch.Size([8, 2]) -``` - -Tous les modèles 🤗 *Transformers* renvoient la perte lorsque les `labels` sont fournis. Nous obtenons également les logits (deux pour chaque entrée de notre batch, donc un tenseur de taille 8 x 2). - -Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir [*Decoupled Weight Decay Regularization*](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) : - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=5e-5) -``` - -Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de batch d'entraînement (qui est la longueur de notre *dataloader* d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça : - -```py -from transformers import get_scheduler - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -print(num_training_steps) -``` - -```python out -1377 -``` - -### La boucle d'entraînement - -Une dernière chose : nous voulons utiliser le GPU si nous en avons un (sur un CPU, l'entraînement peut prendre plusieurs heures au lieu de quelques minutes). Pour ce faire, nous définissons un `device` sur lequel nous allons placer notre modèle et nos batchs : - -```py -import torch - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) -device -``` - -```python out -device(type='cuda') -``` - -Nous sommes maintenant prêts à entraîner ! Pour avoir une idée du moment où l'entraînement sera terminé, nous ajoutons une barre de progression sur le nombre d'étapes d'entraînement, en utilisant la bibliothèque `tqdm` : - -```py -from tqdm.auto import tqdm - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -Vous pouvez voir que le cœur de la boucle d'entraînement ressemble beaucoup à celui de l'introduction. Nous n'avons pas demandé de rapport, donc cette boucle d'entraînement ne nous dira rien sur les résultats du modèle. Pour cela, nous devons ajouter une boucle d'évaluation. - - -### La boucle d'évaluation - -Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Evaluate*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation : - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -model.eval() -for batch in eval_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - with torch.no_grad(): - outputs = model(**batch) - - logits = outputs.logits - predictions = torch.argmax(logits, dim=-1) - metric.add_batch(predictions=predictions, references=batch["labels"]) - -metric.compute() -``` - -```python out -{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} -``` - -Une fois encore, vos résultats seront légèrement différents en raison du caractère aléatoire de l'initialisation de la tête du modèle et du mélange des données, mais ils devraient se situer dans la même fourchette. - - - -✏️ **Essayez** Modifiez la boucle d'entraînement précédente pour *finetuner* votre modèle sur le jeu de données SST-2. - - - -### Optimisez votre boucle d'entraînement avec 🤗 Accelerate - - - -La boucle d'entraînement que nous avons définie précédemment fonctionne bien sur un seul CPU ou GPU. Mais en utilisant la bibliothèque [🤗 *Accelerate*](https://github.com/huggingface/accelerate), il suffit de quelques ajustements pour permettre un entraînement distribué sur plusieurs GPUs ou TPUs. En partant de la création des *dataloaders* d'entraînement et de validation, voici à quoi ressemble notre boucle d'entraînement manuel : - -```py -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -Et voici les changements : - -```diff -+ from accelerate import Accelerator - from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -+ accelerator = Accelerator() - - model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) - optimizer = AdamW(model.parameters(), lr=3e-5) - -- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -- model.to(device) - -+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( -+ train_dataloader, eval_dataloader, model, optimizer -+ ) - - num_epochs = 3 - num_training_steps = num_epochs * len(train_dataloader) - lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps - ) - - progress_bar = tqdm(range(num_training_steps)) - - model.train() - for epoch in range(num_epochs): - for batch in train_dataloader: -- batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss -- loss.backward() -+ accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -La première ligne à ajouter est la ligne d'importation. La deuxième ligne instancie un objet `Accelerator` qui va regarder l'environnement et initialiser la bonne configuration distribuée. 🤗 *Accelerate* gère le placement des périphériques pour vous, donc vous pouvez enlever les lignes qui placent le modèle sur le périphérique (ou, si vous préférez, les changer pour utiliser `accelerator.device` au lieu de `device`). - -Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, le modèle, et l'optimiseur à `accelerator.prepare()`. Cela va envelopper ces objets dans le conteneur approprié pour s'assurer que votre entraînement distribué fonctionne comme prévu. Les changements restants à faire sont la suppression de la ligne qui met le batch sur le `device` (encore une fois, si vous voulez le garder, vous pouvez juste le changer pour utiliser `accelerator.device`) et le remplacement de `loss.backward()` par `accelerator.backward(loss)`. - - -⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du tokenizer. - - -Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 Accelerate : - -```py -from accelerate import Accelerator -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -accelerator = Accelerator() - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -train_dl, eval_dl, model, optimizer = accelerator.prepare( - train_dataloader, eval_dataloader, model, optimizer -) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dl) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dl: - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -En plaçant ceci dans un script `train.py`, cela sera exécutable sur n'importe quel type d'installation distribuée. Pour l'essayer dans votre installation distribuée, exécutez la commande : - -```bash -accelerate config -``` - -qui vous demandera de répondre à quelques questions et enregistrera vos réponses dans un fichier de configuration utilisé par cette commande : - -``` -accelerate launch train.py -``` - -qui lancera l'entraînement distribué. - -Si vous voulez essayer ceci dans un *notebook* (par exemple, pour le tester avec des TPUs sur Colab), collez simplement le code dans une `training_function()` et lancez une dernière cellule avec : - -```python -from accelerate import notebook_launcher - -notebook_launcher(training_function) -``` - -Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples). +# Un entraînement complet + + + + + +Maintenant nous allons voir comment obtenir les mêmes résultats que dans la dernière section sans utiliser la classe `Trainer`. Encore une fois, nous supposons que vous avez fait le traitement des données dans la section 2. Voici un court résumé couvrant tout ce dont vous aurez besoin : + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Préparer l'entraînement + +Avant d'écrire réellement notre boucle d'entraînement, nous devons définir quelques objets. Les premiers sont les *dataloaders* que nous utiliserons pour itérer sur les batchs. Mais avant de pouvoir définir ces chargeurs de données, nous devons appliquer un peu de post-traitement à nos `tokenized_datasets`, pour prendre soin de certaines choses que le `Trainer` fait pour nous automatiquement. Spécifiquement, nous devons : + +- supprimer les colonnes correspondant aux valeurs que le modèle n'attend pas (comme les colonnes `sentence1` et `sentence2`), +- renommer la colonne `label` en `labels` (parce que le modèle s'attend à ce que l'argument soit nommé `labels`), +- définir le format des jeux de données pour qu'ils retournent des tenseurs PyTorch au lieu de listes. + +Notre `tokenized_datasets` a une méthode pour chacune de ces étapes : + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Nous pouvons alors vérifier que le résultat ne comporte que des colonnes que notre modèle acceptera : + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Maintenant que cela est fait, nous pouvons facilement définir nos *dataloaders* : + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Pour vérifier rapidement qu'il n'y a pas d'erreur dans le traitement des données, nous pouvons inspecter un batch comme celui-ci : + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le batch. + +Maintenant que nous en avons terminé avec le prétraitement des données (un objectif satisfaisant mais difficile à atteindre pour tout praticien d'apprentissage automatique), passons au modèle. Nous l'instancions exactement comme nous l'avons fait dans la section précédente : + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Pour s'assurer que tout se passera bien pendant l'entraînement, nous transmettons notre batch à ce modèle : + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Tous les modèles 🤗 *Transformers* renvoient la perte lorsque les `labels` sont fournis. Nous obtenons également les logits (deux pour chaque entrée de notre batch, donc un tenseur de taille 8 x 2). + +Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir [*Decoupled Weight Decay Regularization*](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) : + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de batch d'entraînement (qui est la longueur de notre *dataloader* d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça : + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### La boucle d'entraînement + +Une dernière chose : nous voulons utiliser le GPU si nous en avons un (sur un CPU, l'entraînement peut prendre plusieurs heures au lieu de quelques minutes). Pour ce faire, nous définissons un `device` sur lequel nous allons placer notre modèle et nos batchs : + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Nous sommes maintenant prêts à entraîner ! Pour avoir une idée du moment où l'entraînement sera terminé, nous ajoutons une barre de progression sur le nombre d'étapes d'entraînement, en utilisant la bibliothèque `tqdm` : + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Vous pouvez voir que le cœur de la boucle d'entraînement ressemble beaucoup à celui de l'introduction. Nous n'avons pas demandé de rapport, donc cette boucle d'entraînement ne nous dira rien sur les résultats du modèle. Pour cela, nous devons ajouter une boucle d'évaluation. + + +### La boucle d'évaluation + +Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Evaluate*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation : + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Une fois encore, vos résultats seront légèrement différents en raison du caractère aléatoire de l'initialisation de la tête du modèle et du mélange des données, mais ils devraient se situer dans la même fourchette. + + + +✏️ **Essayez** Modifiez la boucle d'entraînement précédente pour *finetuner* votre modèle sur le jeu de données SST-2. + + + +### Optimisez votre boucle d'entraînement avec 🤗 Accelerate + + + +La boucle d'entraînement que nous avons définie précédemment fonctionne bien sur un seul CPU ou GPU. Mais en utilisant la bibliothèque [🤗 *Accelerate*](https://github.com/huggingface/accelerate), il suffit de quelques ajustements pour permettre un entraînement distribué sur plusieurs GPUs ou TPUs. En partant de la création des *dataloaders* d'entraînement et de validation, voici à quoi ressemble notre boucle d'entraînement manuel : + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Et voici les changements : + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +La première ligne à ajouter est la ligne d'importation. La deuxième ligne instancie un objet `Accelerator` qui va regarder l'environnement et initialiser la bonne configuration distribuée. 🤗 *Accelerate* gère le placement des périphériques pour vous, donc vous pouvez enlever les lignes qui placent le modèle sur le périphérique (ou, si vous préférez, les changer pour utiliser `accelerator.device` au lieu de `device`). + +Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, le modèle, et l'optimiseur à `accelerator.prepare()`. Cela va envelopper ces objets dans le conteneur approprié pour s'assurer que votre entraînement distribué fonctionne comme prévu. Les changements restants à faire sont la suppression de la ligne qui met le batch sur le `device` (encore une fois, si vous voulez le garder, vous pouvez juste le changer pour utiliser `accelerator.device`) et le remplacement de `loss.backward()` par `accelerator.backward(loss)`. + + +⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du tokenizer. + + +Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 Accelerate : + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +En plaçant ceci dans un script `train.py`, cela sera exécutable sur n'importe quel type d'installation distribuée. Pour l'essayer dans votre installation distribuée, exécutez la commande : + +```bash +accelerate config +``` + +qui vous demandera de répondre à quelques questions et enregistrera vos réponses dans un fichier de configuration utilisé par cette commande : + +``` +accelerate launch train.py +``` + +qui lancera l'entraînement distribué. + +Si vous voulez essayer ceci dans un *notebook* (par exemple, pour le tester avec des TPUs sur Colab), collez simplement le code dans une `training_function()` et lancez une dernière cellule avec : + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/fr/chapter3/5.mdx b/chapters/fr/chapter3/5.mdx index db244e545..782e3f5d8 100644 --- a/chapters/fr/chapter3/5.mdx +++ b/chapters/fr/chapter3/5.mdx @@ -1,20 +1,25 @@ - - -# Finetuning, coché ! - -C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous : - -{#if fw === 'pt'} -* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), -* avez appris à charger et à prétraiter des jeux de données, notamment en utilisant le remplissage dynamique et les assembleurs, -* avez implémenté votre propre *finetuning* et évaluation d'un modèle, -* avez implémenté une boucle d'entraînement de niveau inférieur, -* avez utilisé 🤗 *Accelerate* pour adapter facilement votre boucle d'entraînement afin qu'elle fonctionne pour plusieurs GPUs ou TPUs. - -{:else} -* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), -* avez appris comment charger et prétraiter les jeux de données, -* avez appris comment *finetuner* et évaluer un modèle avec Keras, -* avez implémenté une métrique personnalisée. - + + +# Finetuning, coché ! + + + +C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous : + +{#if fw === 'pt'} +* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), +* avez appris à charger et à prétraiter des jeux de données, notamment en utilisant le remplissage dynamique et les assembleurs, +* avez implémenté votre propre *finetuning* et évaluation d'un modèle, +* avez implémenté une boucle d'entraînement de niveau inférieur, +* avez utilisé 🤗 *Accelerate* pour adapter facilement votre boucle d'entraînement afin qu'elle fonctionne pour plusieurs GPUs ou TPUs. + +{:else} +* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), +* avez appris comment charger et prétraiter les jeux de données, +* avez appris comment *finetuner* et évaluer un modèle avec Keras, +* avez implémenté une métrique personnalisée. + {/if} \ No newline at end of file diff --git a/chapters/fr/chapter3/6.mdx b/chapters/fr/chapter3/6.mdx index 44c0fdf44..5379a1fbe 100644 --- a/chapters/fr/chapter3/6.mdx +++ b/chapters/fr/chapter3/6.mdx @@ -1,296 +1,301 @@ - - - - -# Quiz de fin de chapitre - -Testez ce que vous avez appris dans ce chapitre ! - -### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets) et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ? - - - -### 2. Cherchez le jeu de données `ar_sarcasme` dans le [*Hub*](https://huggingface.co/datasets). Quelle tâche prend-il en charge ? - -tags.", - correct: true - }, - { - text: "Traduction automatique", - explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" - }, - { - text: "Reconnaissance des entités nommées", - explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" - }, - { - text: "Réponse aux questions", - explain: "Hélas, cette question n'a pas reçu de réponse correcte. Essayez à nouveau !" - } - ]} -/> - -### 3. Comment le modèle BERT attend-il qu'une paire de phrases soit traitée ? - -[SEP] est nécessaire pour séparer les deux phrases, mais ce n'est pas tout !" - }, - { - text: "[CLS] Tokens_de_la_phrase_1 Tokens_de_la_phrase_2", - explain: "Un jeton spécial [CLS] est requis au début, mais ce n'est pas la seule chose !" - }, - { - text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2 [SEP]", - explain: "C'est exact !", - correct: true - }, - { - text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2", - explain: "Un jeton spécial [CLS] est nécessaire au début, ainsi qu'un jeton spécial [SEP] pour séparer les deux phrases, mais ce n'est pas tout !" - } - ]} -/> - -{#if fw === 'pt'} -### 4. Quels sont les avantages de la méthode `Dataset.map()` ? - - - -### 5. Que signifie le remplissage (*padding*) dynamique ? - -tokens que la précédente dans le jeu de données.", - explain: "Cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement." - }, - ]} -/> - -### 6. Quel est le but d'une fonction de rassemblement ? - -DataCollatorWithPadding." - }, - { - text: "Elle rassemble tous les échantillons dans un batch.", - explain: "Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", - correct: true - }, - { - text: "Elle pré-traite tout le jeu de données.", - explain: "Ce serait une fonction de prétraitement, pas une fonction de rassemblement." - }, - { - text: "Elle tronque les séquences dans le jeu de données.", - explain: "Une fonction de rassemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." - } - ]} -/> - -### 7. Que se passe-t-il lorsque vous instanciez une des classes `AutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? - -AutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", - correct: true - }, - { - text: "La tête du modèle pré-entraîné est supprimée.", - explain: "Quelque chose d'autre doit se produire. Essayez encore !" - }, - { - text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", - explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" - } - ]} -/> - -### 8. Quel est le but de `TrainingArguments` ? - -Trainer.", - explain: "", - correct: true - }, - { - text: "Préciser la taille du modèle.", - explain: "La taille du modèle est définie par la configuration du modèle, et non par la classe TrainingArguments." - }, - { - text: "Juste contenir les hyperparamètres utilisés pour l'évaluation.", - explain: "Dans l'exemple, nous avons spécifié où le modèle et ses checkpoints seront sauvegardés. Essayez à nouveau !" - }, - { - text: "Contenir seulement les hyperparamètres utilisés pour l'entraînement.", - explain: "Dans l'exemple, nous avons utilisé une evaluation_strategy également, ce qui a un impact sur l'évaluation. Essayez à nouveau !" - } - ]} -/> - -### 9. Pourquoi devriez-vous utiliser la librairie 🤗 *Accelerate* ? - -Accelerate ne fournit aucun modèles." - }, - { - text: "Elle fournit une API de haut niveau qui évite d'avoir à mettre en place sa propre boucle d'entraînement.", - explain: "C'est ce que nous avons fait avec le Trainer mais pas avec la librairie 🤗 Accelerate. Essayez à nouveau !" - }, - { - text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.", - explain: "Avec 🤗 Accelerate, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.", - correct: true - }, - { - text: "Elle offre davantage de fonctions d'optimisation.", - explain: "Non, la librairie 🤗 Accelerate ne fournit pas de fonctions d'optimisation." - } - ]} -/> - -{:else} -### 4. Que se passe-t-il lorsque vous instanciez une des classes `TFAutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? - -TFAutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", - correct: true - }, - { - text: "La tête du modèle pré-entraîné est supprimée.", - explain: "Quelque chose d'autre doit se produire. Essayez encore !" - }, - { - text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", - explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" - } - ]} -/> - -### 5. Les modèles TensorFlow de `transformers` sont déjà des modèles Keras. Quel avantage cela offre-t-il ? - -TPUStrategy, y compris l'initialisation du modèle." - }, - { - text: "Vous pouvez tirer parti des méthodes existantes telles que compile(), fit() et predict().", - explain: "Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.", - correct: true - }, - { - text: "Vous apprendrez à connaître Keras ainsi que transformers.", - explain: "Mais nous cherchons quelque chose d'autre :)", - correct: true - }, - { - text: "Vous pouvez facilement calculer les métriques liées au jeu de données.", - explain: "Keras nous aide à entraîner et à évaluer le modèle, et non à calculer les paramètres liés aux jeux de données." - } - ]} -/> - -### 6. Comment pouvez-vous définir votre propre métrique personnalisée ? - -tf.keras.metrics.Metric.", - explain: "Excellent !", - correct: true - }, - { - text: "Utilisation de l'API fonctionnelle de Keras.", - explain: "Essayez à nouveau !" - }, - { - text: "En utilisant un callable avec la signature metric_fn(y_true, y_pred).", - explain: " ", - correct: true - }, - { - text: "En le googlant.", - explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver.", - correct: true - } - ]} -/> - -{/if} + + + + +# Quiz de fin de chapitre + + + +Testez ce que vous avez appris dans ce chapitre ! + +### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets) et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ? + + + +### 2. Cherchez le jeu de données `ar_sarcasme` dans le [*Hub*](https://huggingface.co/datasets). Quelle tâche prend-il en charge ? + +tags.", + correct: true + }, + { + text: "Traduction automatique", + explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" + }, + { + text: "Reconnaissance des entités nommées", + explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" + }, + { + text: "Réponse aux questions", + explain: "Hélas, cette question n'a pas reçu de réponse correcte. Essayez à nouveau !" + } + ]} +/> + +### 3. Comment le modèle BERT attend-il qu'une paire de phrases soit traitée ? + +[SEP] est nécessaire pour séparer les deux phrases, mais ce n'est pas tout !" + }, + { + text: "[CLS] Tokens_de_la_phrase_1 Tokens_de_la_phrase_2", + explain: "Un jeton spécial [CLS] est requis au début, mais ce n'est pas la seule chose !" + }, + { + text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2 [SEP]", + explain: "C'est exact !", + correct: true + }, + { + text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2", + explain: "Un jeton spécial [CLS] est nécessaire au début, ainsi qu'un jeton spécial [SEP] pour séparer les deux phrases, mais ce n'est pas tout !" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Quels sont les avantages de la méthode `Dataset.map()` ? + + + +### 5. Que signifie le remplissage (*padding*) dynamique ? + +tokens que la précédente dans le jeu de données.", + explain: "Cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement." + }, + ]} +/> + +### 6. Quel est le but d'une fonction de rassemblement ? + +DataCollatorWithPadding." + }, + { + text: "Elle rassemble tous les échantillons dans un batch.", + explain: "Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", + correct: true + }, + { + text: "Elle pré-traite tout le jeu de données.", + explain: "Ce serait une fonction de prétraitement, pas une fonction de rassemblement." + }, + { + text: "Elle tronque les séquences dans le jeu de données.", + explain: "Une fonction de rassemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." + } + ]} +/> + +### 7. Que se passe-t-il lorsque vous instanciez une des classes `AutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? + +AutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + correct: true + }, + { + text: "La tête du modèle pré-entraîné est supprimée.", + explain: "Quelque chose d'autre doit se produire. Essayez encore !" + }, + { + text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", + explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" + } + ]} +/> + +### 8. Quel est le but de `TrainingArguments` ? + +Trainer.", + explain: "", + correct: true + }, + { + text: "Préciser la taille du modèle.", + explain: "La taille du modèle est définie par la configuration du modèle, et non par la classe TrainingArguments." + }, + { + text: "Juste contenir les hyperparamètres utilisés pour l'évaluation.", + explain: "Dans l'exemple, nous avons spécifié où le modèle et ses checkpoints seront sauvegardés. Essayez à nouveau !" + }, + { + text: "Contenir seulement les hyperparamètres utilisés pour l'entraînement.", + explain: "Dans l'exemple, nous avons utilisé une evaluation_strategy également, ce qui a un impact sur l'évaluation. Essayez à nouveau !" + } + ]} +/> + +### 9. Pourquoi devriez-vous utiliser la librairie 🤗 *Accelerate* ? + +Accelerate ne fournit aucun modèles." + }, + { + text: "Elle fournit une API de haut niveau qui évite d'avoir à mettre en place sa propre boucle d'entraînement.", + explain: "C'est ce que nous avons fait avec le Trainer mais pas avec la librairie 🤗 Accelerate. Essayez à nouveau !" + }, + { + text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.", + explain: "Avec 🤗 Accelerate, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.", + correct: true + }, + { + text: "Elle offre davantage de fonctions d'optimisation.", + explain: "Non, la librairie 🤗 Accelerate ne fournit pas de fonctions d'optimisation." + } + ]} +/> + +{:else} +### 4. Que se passe-t-il lorsque vous instanciez une des classes `TFAutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? + +TFAutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + correct: true + }, + { + text: "La tête du modèle pré-entraîné est supprimée.", + explain: "Quelque chose d'autre doit se produire. Essayez encore !" + }, + { + text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", + explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" + } + ]} +/> + +### 5. Les modèles TensorFlow de `transformers` sont déjà des modèles Keras. Quel avantage cela offre-t-il ? + +TPUStrategy, y compris l'initialisation du modèle." + }, + { + text: "Vous pouvez tirer parti des méthodes existantes telles que compile(), fit() et predict().", + explain: "Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.", + correct: true + }, + { + text: "Vous apprendrez à connaître Keras ainsi que transformers.", + explain: "Mais nous cherchons quelque chose d'autre :)", + correct: true + }, + { + text: "Vous pouvez facilement calculer les métriques liées au jeu de données.", + explain: "Keras nous aide à entraîner et à évaluer le modèle, et non à calculer les paramètres liés aux jeux de données." + } + ]} +/> + +### 6. Comment pouvez-vous définir votre propre métrique personnalisée ? + +tf.keras.metrics.Metric.", + explain: "Excellent !", + correct: true + }, + { + text: "Utilisation de l'API fonctionnelle de Keras.", + explain: "Essayez à nouveau !" + }, + { + text: "En utilisant un callable avec la signature metric_fn(y_true, y_pred).", + explain: " ", + correct: true + }, + { + text: "En le googlant.", + explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver.", + correct: true + } + ]} +/> + +{/if} diff --git a/chapters/fr/chapter4/1.mdx b/chapters/fr/chapter4/1.mdx index 7a0420461..b27ba639e 100644 --- a/chapters/fr/chapter4/1.mdx +++ b/chapters/fr/chapter4/1.mdx @@ -1,17 +1,22 @@ -# Le Hub d'Hugging Face - -Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5. - -Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. - -Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. - -En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés. - -La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé. - -La vidéo ci-dessous montre comment naviguer sur le *Hub* : - - - +# Le Hub d'Hugging Face + + + +Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5. + +Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. + +Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. + +En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés. + +La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé. + +La vidéo ci-dessous montre comment naviguer sur le *Hub* : + + + Avoir un compte huggingface.co est nécessaire pour suivre cette partie car nous allons créer et gérer des répertoires sur le *Hub* : [créer un compte](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fr/chapter4/2.mdx b/chapters/fr/chapter4/2.mdx index 86579685f..6f86e8f4f 100644 --- a/chapters/fr/chapter4/2.mdx +++ b/chapters/fr/chapter4/2.mdx @@ -1,97 +1,97 @@ - - -# Utilisation de modèles pré-entraînés - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté. - -Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. - -
-Selecting the Camembert model. -
- -Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : - -```py -from transformers import pipeline - -camembert_fill_mask = pipeline("fill-mask", model="camembert-base") -results = camembert_fill_mask("Le camembert est :)") -``` - -```python out -[ - {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, - {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, - {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, - {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, - {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} -] -``` - -Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : - -
-The task selector on the web interface. -
- -Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle : - -{#if fw === 'pt'} -```py -from transformers import CamembertTokenizer, CamembertForMaskedLM - -tokenizer = CamembertTokenizer.from_pretrained("camembert-base") -model = CamembertForMaskedLM.from_pretrained("camembert-base") -``` - -Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* : - -```py -from transformers import AutoTokenizer, AutoModelForMaskedLM - -tokenizer = AutoTokenizer.from_pretrained("camembert-base") -model = AutoModelForMaskedLM.from_pretrained("camembert-base") -``` -{:else} -```py -from transformers import CamembertTokenizer, TFCamembertForMaskedLM - -tokenizer = CamembertTokenizer.from_pretrained("camembert-base") -model = TFCamembertForMaskedLM.from_pretrained("camembert-base") -``` - -Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* : - - -```py -from transformers import AutoTokenizer, TFAutoModelForMaskedLM - -tokenizer = AutoTokenizer.from_pretrained("camembert-base") -model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") -``` -{/if} - - -Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte. - + + +# Utilisation de modèles pré-entraînés + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté. + +Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. + +
+Selecting the Camembert model. +
+ +Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : + +
+The task selector on the web interface. +
+ +Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle : + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* : + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* : + + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte. + diff --git a/chapters/fr/chapter4/3.mdx b/chapters/fr/chapter4/3.mdx index fabdd4a36..b9db79e6e 100644 --- a/chapters/fr/chapter4/3.mdx +++ b/chapters/fr/chapter4/3.mdx @@ -1,638 +1,638 @@ - - -# Partage de modèles pré-entraînés - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans les étapes ci-dessous, nous allons examiner les moyens les plus simples de partager des modèles pré-entraînés sur le 🤗 *Hub*. Il existe des outils et des services disponibles qui permettent de simplifier le partage et la mise à jour des modèles directement sur le *Hub*, que nous allons explorer ci-dessous. - - - -Nous encourageons tous les utilisateurs qui entraînent des modèles à contribuer en les partageant avec la communauté. Le partage des modèles, même s'ils ont été entraînés sur des jeux de données très spécifiques, aidera les autres, en leur faisant gagner du temps, des ressources de calcul et en leur donnant accès à des artefacts entraînés utiles. À votre tour, vous pourrez bénéficier du travail effectué par les autres ! - -Il y a trois façons de créer de nouveaux dépôts de modèles : - -- en utilisant l'API `push_to_hub`, -- en utilisant la bibliothèque Python `huggingface_hub`, -- en utilisant l'interface web. - -Une fois que vous avez créé un dépôt, vous pouvez y charger des fichiers via git et git-lfs. Nous allons vous guider dans la création de dépôts de modèles et le téléchargement de fichiers dans les sections suivantes. - - -## Utilisation de l'API `push_to_hub` - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -La façon la plus simple de télécharger des fichiers vers le *Hub* est d'utiliser l'API `push_to_hub`. - -Avant d'aller plus loin, vous devrez générer un jeton d'authentification afin que l'API `huggingface_hub` sache qui vous êtes et à quels espaces de noms vous avez accès en écriture. Assurez-vous que vous êtes dans un environnement où vous avez installé `transformers` (voir la [Configuration](/course/fr/chapter0)). Si vous êtes dans un *notebook*, vous pouvez utiliser la fonction suivante pour vous connecter : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Dans un terminal, vous pouvez exécuter : - -```bash -huggingface-cli login -``` - -Dans les deux cas, vous serez invité à saisir votre nom d'utilisateur et votre mot de passe, qui sont les mêmes que ceux que vous utilisez pour vous connecter au *Hub*. Si vous n'avez pas encore de profil pour le Hub, vous devez en créer un [ici](https://huggingface.co/join). - -Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques dépôts ! - -{#if fw === 'pt'} - -Si vous avez joué avec l'API `Trainer` pour entraîner un modèle, le moyen le plus simple de le télécharger sur le *Hub* est de définir `push_to_hub=True` lorsque vous définissez vos `TrainingArguments` : - -```py -from transformers import TrainingArguments - -training_args = TrainingArguments( - "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True -) -``` - -Lorsque vous appelez `trainer.train()`, le `Trainer` téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace personnel. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. - -Pour télécharger votre modèle vers une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. - -Une fois que votre entraînement est terminé, vous devriez faire un dernier `trainer.push_to_hub()` pour télécharger la dernière version de votre modèle. Cela générera également une carte pour le modèle avec toutes les métadonnées pertinentes, rapportant les hyperparamètres utilisés et les résultats d'évaluation ! Voici un exemple du contenu que vous pourriez trouver dans une telle carte de modèle : -
- An example of an auto-generated model card. -
- -{:else} - -Si vous utilisez Keras pour entraîner votre modèle, le moyen le plus simple de le télécharger sur le *Hub* est de passer un `PushToHubCallback` lorsque vous appelez `model.fit()` : - -```py -from transformers import PushToHubCallback - -callback = PushToHubCallback( - "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer -) -``` - -Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. Le *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. - -Pour télécharger votre modèle dans une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. - -{/if} - -A un niveau inférieur, l'accès au *Hub* peut être fait directement sur les modèles, les *tokenizers* et les objets de configuration via leur méthode `push_to_hub()`. Cette méthode s'occupe à la fois de la création du dépôt et de l'envoi les fichiers du modèle et du *tokenizer* directement dans le dépôt. Aucune manipulation manuelle n'est nécessaire, contrairement à l'API que nous verrons plus loin. - -Pour avoir une idée de son fonctionnement, commençons par initialiser un modèle et un *tokenizer* : - -{#if fw === 'pt'} -```py -from transformers import AutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = AutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -``` -{:else} -```py -from transformers import TFAutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -``` -{/if} - -Vous êtes libre de faire ce que vous voulez avec ces objets : ajouter des *tokens* au *tokenizer*, entraîner le modèle, le *finetuner*. Une fois que vous êtes satisfait du modèle, des poids et du *tokenizer* obtenus, vous pouvez utiliser la méthode `push_to_hub()` directement disponible sur l'objet `model` : - -```py -model.push_to_hub("dummy-model") -``` - -Cela va créer le nouveau dépôt `dummy-model` dans votre profil et le remplir avec les fichiers du modèle. -Faites la même chose avec le *tokenizer*, de sorte que tous les fichiers sont maintenant disponibles dans ce dépôt : - -```py -tokenizer.push_to_hub("dummy-model") -``` - -Si vous appartenez à une organisation, il suffit de spécifier l'argument `organization` pour télécharger dans l'espace de cette organisation : - -```py -tokenizer.push_to_hub("dummy-model", organization="huggingface") -``` - -Si vous souhaitez utiliser un jeton Hugging Face spécifique, vous pouvez également le spécifier à la méthode `push_to_hub()` : - -```py -tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") -``` - -Maintenant, dirigez-vous sur *Hub* pour trouver votre modèle nouvellement téléchargé : *https://huggingface.co/user-or-organization/dummy-model*. - -Cliquez sur l'onglet « Fichiers et versions » et vous devriez voir les fichiers visibles dans la capture d'écran suivante : - -{#if fw === 'pt'} -
-Dummy model containing both the tokenizer and model files. -
-{:else} -
-Dummy model containing both the tokenizer and model files. -
-{/if} - - - -✏️ **Essayez** Prenez le modèle et le *tokenizer* associés au *checkpoint* `bert-base-cased` et téléchargez-les vers un dépôt dans votre espace en utilisant la méthode `push_to_hub()`. Vérifiez que le dépôt apparaît correctement sur votre page avant de le supprimer. - - - -Comme vous l'avez vu, la méthode `push_to_hub()` accepte plusieurs arguments, ce qui permet de télécharger vers un dépôt ou un espace d'organisation spécifique, ou d'utiliser un jeton d'API différent. Nous vous recommandons de jeter un coup d'œil à la spécification de la méthode disponible directement dans la documentation de [🤗 *Transformers*](https://huggingface.co/transformers/model_sharing.html) pour avoir une idée de ce qui est possible. - -La méthode `push_to_hub()` est soutenue par le *package* Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple. - -Passez à la dernière section pour voir comment télécharger des fichiers dans votre dépôt nouvellement créé ! - -## Utilisation de la bibliothèque Python `huggingface_hub` - -La bibliothèque Python `huggingface_hub` est un *package* qui offre un ensemble d'outils pour les hubs des modèles et des jeux de données. Elle fournit des méthodes et des classes simples pour des tâches courantes telles qu'obtenir et gérer des informations à propos des dépôts sur le *Hub*. Elle fournit des APIs simples qui fonctionnent au-dessus de git pour gérer le contenu de ces dépôts et pour intégrer le *Hub* dans vos projets et bibliothèques. - -De la même manière que pour l'utilisation de l'API `push_to_hub`, vous devrez avoir votre jeton d'API enregistré dans votre cache. Pour ce faire, vous devrez utiliser la commande `login` de la CLI, comme mentionné dans la section précédente (encore une fois, assurez-vous de faire précéder ces commandes du caractère `!` si vous les exécutez dans Google Colab) : - -```bash -huggingface-cli login -``` - -Le *package* `huggingface_hub` offre plusieurs méthodes et classes qui sont utiles pour notre objectif. Tout d'abord, il y a quelques méthodes pour gérer la création, la suppression des dépôts, et autres : - -```python no-format -from huggingface_hub import ( - # Gestion des utilisateurs - login, - logout, - whoami, - - # Création et gestion du dépôt - create_repo, - delete_repo, - update_repo_visibility, - - # Et quelques méthodes pour récupérer/changer des informations sur le contenu - list_models, - list_datasets, - list_metrics, - list_repo_files, - upload_file, - delete_file, -) -``` - - -De plus, elle offre la très puissante classe `Repository` pour gérer un dépôt local. Nous allons explorer ces méthodes et cette classe dans les prochaines sections pour comprendre comment les exploiter. - -La méthode `create_repo` peut être utilisée pour créer un nouveau dépôt sur le *Hub* : - -```py -from huggingface_hub import create_repo - -create_repo("dummy-model") -``` - -Ceci créera le dépôt `dummy-model` dans votre espace. Si vous le souhaitez, vous pouvez spécifier à quelle organisation le dépôt doit appartenir en utilisant l'argument `organization` : - -```py -from huggingface_hub import create_repo - -create_repo("dummy-model", organization="huggingface") -``` - -Cela créera le dépôt `dummy-model` dans l'espace de nom `huggingface`, en supposant que vous appartenez à cette organisation. -D'autres arguments qui peuvent être utiles sont : - -- `private`, afin de spécifier si le dépôt doit être visible des autres ou non, -- `token`, si vous voulez remplacer le jeton stocké dans votre cache par un jeton donné, -- `repo_type`, si vous souhaitez créer un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. - -Une fois que le dépôt est créé, nous devons y ajouter des fichiers ! Passez à la section suivante pour voir les trois façons dont cela peut être géré. - - -## Utilisation de l'interface web - -L'interface web offre des outils pour gérer les dépôts directement dans le *Hub*. En utilisant l'interface, vous pouvez facilement créer des dépôts, ajouter des fichiers (même de grande taille !), explorer des modèles, visualiser les différences, et bien plus encore. - -Pour créer un nouveau dépôt, visitez [huggingface.co/new](https://huggingface.co/new) : - -
-Page showcasing the model used for the creation of a new model repository. -
- -Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au dépôt. - -Ensuite, saisissez le nom de votre modèle. Ce sera également le nom du dépôt. Enfin, vous pouvez préciser si vous souhaitez que votre modèle soit public ou privé. Les modèles privés sont cachés de la vue du public. - -Après avoir créé votre dépôt de modèles, vous devriez voir une page comme celle-ci : - -
-An empty model page after creating a new repository. -
- -C'est là que votre modèle sera hébergé. Pour commencer à le remplir, vous pouvez ajouter un fichier README directement depuis l'interface web. - -
-The README file showing the Markdown capabilities. -
- -Le fichier README est en Markdown. N'hésitez pas à vous lâcher avec lui ! La troisième partie de ce chapitre est consacrée à la construction d'une carte de modèle. Celles-ci sont d'une importance capitale pour valoriser votre modèle, car c'est par elles que vous indiquez aux autres ce qu'il peut faire. - -Si vous regardez l'onglet « *Files and versions* », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers. - -
-The 'Files and versions' tab only shows the .gitattributes and README.md files. -
- -Nous allons maintenant voir comment ajouter de nouveaux fichiers. - -## Téléchargement des fichiers du modèle - -Le système de gestion des fichiers sur le *Hub* est basé sur git pour les fichiers ordinaires et git-lfs (qui signifie [Git Large File Storage](https://git-lfs.github.com/)) pour les fichiers plus importants. - -Dans la section suivante, nous passons en revue trois façons différentes de télécharger des fichiers sur le *Hub* : par `huggingface_hub` et par des commandes git. - -### L'approche `upload_file' - -L'utilisation de `upload_file` ne nécessite pas que git et git-lfs soient installés sur votre système. Il pousse les fichiers directement vers le 🤗 *Hub* en utilisant des requêtes HTTP POST. Une limitation de cette approche est qu'elle ne gère pas les fichiers dont la taille est supérieure à 5 Go. -Si vos fichiers ont une taille supérieure à 5 Go, veuillez suivre les deux autres méthodes détaillées ci-dessous. - -L'API peut être utilisée comme suit : - -```py -from huggingface_hub import upload_file - -upload_file( - "/config.json", - path_in_repo="config.json", - repo_id="/dummy-model", -) -``` - -Ceci téléchargera le fichier `config.json` disponible à `` à la racine du dépôt en tant que `config.json`, vers le dépôt `dummy-model`. -D'autres arguments qui peuvent être utiles sont : - -- `token`, si vous souhaitez remplacer le jeton stocké dans votre cache par un jeton donné, -- `repo_type`, si vous souhaitez télécharger vers un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. - - -### La classe `Repository` - -La classe `Repository` gère un dépôt local d'une manière similaire à git. Elle abstrait la plupart des problèmes que l'on peut rencontrer avec git pour fournir toutes les fonctionnalités dont nous avons besoin. - -L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous que git-lfs est installé (voir [ici](https://git-lfs.github.com/) pour les instructions d'installation) et configuré avant de commencer. - -Afin de commencer à jouer avec le dépôt que nous venons de créer, nous pouvons commencer par l'initialiser dans un dossier local en clonant le dépôt distant : - -```py -from huggingface_hub import Repository - -repo = Repository("", clone_from="/dummy-model") -``` - -Cela a créé le dossier `` dans notre répertoire de travail. Ce dossier ne contient que le fichier `.gitattributes` car c'est le seul fichier créé lors de l'instanciation du dépôt par `create_repo`. - -A partir de maintenant, nous pouvons utiliser plusieurs des méthodes traditionnelles de git : - -```py -repo.git_pull() -repo.git_add() -repo.git_commit() -repo.git_push() -repo.git_tag() -``` - -Et d'autres encore ! Nous vous recommandons de jeter un coup d’œil à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles. - -Actuellement, nous avons un modèle et un *tokenizer* que nous voulons pousser vers le *Hub*. Nous avons réussi à cloner le dépôt, nous pouvons donc enregistrer les fichiers dans ce dépôt. - -Nous nous assurons d'abord que notre clone local est à jour en récupérant les dernières modifications : - -```py -repo.git_pull() -``` - -Une fois que c'est fait, nous sauvegardons les fichiers du modèle et du *tokenizer* : - -```py -model.save_pretrained("") -tokenizer.save_pretrained("") -``` - -Le `` contient maintenant tous les fichiers du modèle et du *tokenizer*. Nous suivons le flux de travail git habituel en ajoutant des fichiers à la zone de transit, en les validant et en les poussant vers le *Hub* : - -```py -repo.git_add() -repo.git_commit("Add model and tokenizer files") -repo.git_push() -``` - -Félicitations ! Vous venez de pousser vos premiers fichiers sur le *Hub*. - -### L'approche basée sur git - -Il s'agit de l'approche la plus basique pour télécharger des fichiers : nous le ferons directement avec git et git-lfs. La plupart des difficultés sont abstraites par les approches précédentes, mais il y a quelques réserves avec la méthode suivante, nous allons donc suivre un cas d'utilisation plus complexe. - -L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous d'avoir [git-lfs](https://git-lfs.github.com/) installé et configuré avant de commencer. - -Commencez par initialiser git-lfs : - -```bash -git lfs install -``` - -```bash -Updated git hooks. -Git LFS initialized. -``` - -Une fois que c'est fait, la première étape consiste à cloner votre dépôt de modèles : - -```bash -git clone https://huggingface.co// -``` - -Mon nom d'utilisateur est `lysandre` et j'ai utilisé le nom de modèle `dummy`, donc pour moi la commande ressemble à ce qui suit : - -``` -git clone https://huggingface.co/lysandre/dummy -``` - -J'ai maintenant un dossier nommé *dummy* dans mon répertoire de travail. Je peux `cd` dans ce dossier et jeter un coup d'oeil à son contenu : - -```bash -cd dummy && ls -``` - -```bash -README.md -``` - -Si vous venez de créer votre dépôt en utilisant la méthode `create_repo` du *Hub*, ce dossier devrait seulement contenir un fichier caché `.gitattributes`. Si vous avez suivi les instructions de la section précédente pour créer un dépôt en utilisant l'interface web, le dossier devrait contenir un seul fichier *README.md* à côté du fichier caché `.gitattributes`, comme indiqué ici. - -L'ajout d'un fichier de taille normale, comme un fichier de configuration, un fichier de vocabulaire, ou tout autre fichier de moins de quelques mégaoctets, est fait exactement comme on le ferait dans n'importe quel système basé sur git. Cependant, les fichiers plus volumineux doivent être enregistrés via git-lfs afin de les pousser vers *huggingface.co*. - -Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons « commiter » dans notre dépôt fictif : - -{#if fw === 'pt'} -```py -from transformers import AutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = AutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - -# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... - -model.save_pretrained("") -tokenizer.save_pretrained("") -``` -{:else} -```py -from transformers import TFAutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - -# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... - -model.save_pretrained("") -tokenizer.save_pretrained("") -``` -{/if} - -Maintenant que nous avons sauvegardé quelques artefacts de modèle et de *tokenizer*, regardons à nouveau le dossier *dummy* : - -```bash -ls -``` - -{#if fw === 'pt'} -```bash -config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json -``` - -Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier d'état du modèle (*pytorch_model.bin*) est la seule exception, avec plus de 400 Mo. - -{:else} -```bash -config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json -``` - -Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier dict de l'état du modèle (*t5_model.h5*) est la seule aberration, avec plus de 400 Mo. - -{/if} - - -✏️ Lors de la création du dépôt à partir de l'interface web, le fichier .gitattributes est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme .bin et .h5, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part. - - -Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement Git en utilisant la commande `git add` : - -```bash -git add . -``` - -Nous pouvons alors jeter un coup d'œil aux fichiers : - -```bash -git status -``` - -{#if fw === 'pt'} -```bash -On branch main -Your branch is up to date with 'origin/main'. - -Changes to be committed: - (use "git restore --staged ..." to unstage) - modified: .gitattributes - new file: config.json - new file: pytorch_model.bin - new file: sentencepiece.bpe.model - new file: special_tokens_map.json - new file: tokenizer.json - new file: tokenizer_config.json -``` -{:else} -```bash -On branch main -Your branch is up to date with 'origin/main'. - -Changes to be committed: - (use "git restore --staged ..." to unstage) - modified: .gitattributes - new file: config.json - new file: sentencepiece.bpe.model - new file: special_tokens_map.json - new file: tf_model.h5 - new file: tokenizer.json - new file: tokenizer_config.json -``` -{/if} - -De même, nous pouvons nous assurer que git-lfs suit les bons fichiers en utilisant sa commande `status` : - -```bash -git lfs status -``` - -{#if fw === 'pt'} -```bash -On branch main -Objects to be pushed to origin/main: - - -Objects to be committed: - - config.json (Git: bc20ff2) - pytorch_model.bin (LFS: 35686c2) - sentencepiece.bpe.model (LFS: 988bc5a) - special_tokens_map.json (Git: cb23931) - tokenizer.json (Git: 851ff3e) - tokenizer_config.json (Git: f0f7783) - -Objects not staged for commit: - - -``` - -Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *pytorch_model.bin* et *sentencepiece.bpe.model*, qui ont `LFS`. Super ! - -{:else} -```bash -On branch main -Objects to be pushed to origin/main: - - -Objects to be committed: - - config.json (Git: bc20ff2) - sentencepiece.bpe.model (LFS: 988bc5a) - special_tokens_map.json (Git: cb23931) - tf_model.h5 (LFS: 86fce29) - tokenizer.json (Git: 851ff3e) - tokenizer_config.json (Git: f0f7783) - -Objects not staged for commit: - - -``` - -Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *t5_model.h5* qui a `LFS`. Super ! - -{/if} - -Passons aux étapes finales, *committing* et *pushing* vers le dépôt distant *huggingface.co* : - -```bash -git commit -m "First model version" -``` - -{#if fw === 'pt'} -```bash -[main b08aab1] First model version - 7 files changed, 29027 insertions(+) - 6 files changed, 36 insertions(+) - create mode 100644 config.json - create mode 100644 pytorch_model.bin - create mode 100644 sentencepiece.bpe.model - create mode 100644 special_tokens_map.json - create mode 100644 tokenizer.json - create mode 100644 tokenizer_config.json -``` -{:else} -```bash -[main b08aab1] First model version - 6 files changed, 36 insertions(+) - create mode 100644 config.json - create mode 100644 sentencepiece.bpe.model - create mode 100644 special_tokens_map.json - create mode 100644 tf_model.h5 - create mode 100644 tokenizer.json - create mode 100644 tokenizer_config.json -``` -{/if} - -Le chargement peut prendre un peu de temps, en fonction de la vitesse de votre connexion Internet et de la taille de vos fichiers : - -```bash -git push -``` - -```bash -Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. -Enumerating objects: 11, done. -Counting objects: 100% (11/11), done. -Delta compression using up to 12 threads -Compressing objects: 100% (9/9), done. -Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. -Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 -To https://huggingface.co/lysandre/dummy - 891b41d..b08aab1 main -> main -``` - -{#if fw === 'pt'} -Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : - -
-The 'Files and versions' tab now contains all the recently uploaded files. -
- -L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : - -
-The diff introduced by the recent commit. -
-{:else} -Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : - -
-The 'Files and versions' tab now contains all the recently uploaded files. -
- -L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : - -
-The diff introduced by the recent commit. -
-{/if} + + +# Partage de modèles pré-entraînés + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans les étapes ci-dessous, nous allons examiner les moyens les plus simples de partager des modèles pré-entraînés sur le 🤗 *Hub*. Il existe des outils et des services disponibles qui permettent de simplifier le partage et la mise à jour des modèles directement sur le *Hub*, que nous allons explorer ci-dessous. + + + +Nous encourageons tous les utilisateurs qui entraînent des modèles à contribuer en les partageant avec la communauté. Le partage des modèles, même s'ils ont été entraînés sur des jeux de données très spécifiques, aidera les autres, en leur faisant gagner du temps, des ressources de calcul et en leur donnant accès à des artefacts entraînés utiles. À votre tour, vous pourrez bénéficier du travail effectué par les autres ! + +Il y a trois façons de créer de nouveaux dépôts de modèles : + +- en utilisant l'API `push_to_hub`, +- en utilisant la bibliothèque Python `huggingface_hub`, +- en utilisant l'interface web. + +Une fois que vous avez créé un dépôt, vous pouvez y charger des fichiers via git et git-lfs. Nous allons vous guider dans la création de dépôts de modèles et le téléchargement de fichiers dans les sections suivantes. + + +## Utilisation de l'API `push_to_hub` + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +La façon la plus simple de télécharger des fichiers vers le *Hub* est d'utiliser l'API `push_to_hub`. + +Avant d'aller plus loin, vous devrez générer un jeton d'authentification afin que l'API `huggingface_hub` sache qui vous êtes et à quels espaces de noms vous avez accès en écriture. Assurez-vous que vous êtes dans un environnement où vous avez installé `transformers` (voir la [Configuration](/course/fr/chapter0)). Si vous êtes dans un *notebook*, vous pouvez utiliser la fonction suivante pour vous connecter : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Dans un terminal, vous pouvez exécuter : + +```bash +huggingface-cli login +``` + +Dans les deux cas, vous serez invité à saisir votre nom d'utilisateur et votre mot de passe, qui sont les mêmes que ceux que vous utilisez pour vous connecter au *Hub*. Si vous n'avez pas encore de profil pour le Hub, vous devez en créer un [ici](https://huggingface.co/join). + +Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques dépôts ! + +{#if fw === 'pt'} + +Si vous avez joué avec l'API `Trainer` pour entraîner un modèle, le moyen le plus simple de le télécharger sur le *Hub* est de définir `push_to_hub=True` lorsque vous définissez vos `TrainingArguments` : + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Lorsque vous appelez `trainer.train()`, le `Trainer` téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace personnel. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. + +Pour télécharger votre modèle vers une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. + +Une fois que votre entraînement est terminé, vous devriez faire un dernier `trainer.push_to_hub()` pour télécharger la dernière version de votre modèle. Cela générera également une carte pour le modèle avec toutes les métadonnées pertinentes, rapportant les hyperparamètres utilisés et les résultats d'évaluation ! Voici un exemple du contenu que vous pourriez trouver dans une telle carte de modèle : +
+ An example of an auto-generated model card. +
+ +{:else} + +Si vous utilisez Keras pour entraîner votre modèle, le moyen le plus simple de le télécharger sur le *Hub* est de passer un `PushToHubCallback` lorsque vous appelez `model.fit()` : + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. Le *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. + +Pour télécharger votre modèle dans une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. + +{/if} + +A un niveau inférieur, l'accès au *Hub* peut être fait directement sur les modèles, les *tokenizers* et les objets de configuration via leur méthode `push_to_hub()`. Cette méthode s'occupe à la fois de la création du dépôt et de l'envoi les fichiers du modèle et du *tokenizer* directement dans le dépôt. Aucune manipulation manuelle n'est nécessaire, contrairement à l'API que nous verrons plus loin. + +Pour avoir une idée de son fonctionnement, commençons par initialiser un modèle et un *tokenizer* : + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +Vous êtes libre de faire ce que vous voulez avec ces objets : ajouter des *tokens* au *tokenizer*, entraîner le modèle, le *finetuner*. Une fois que vous êtes satisfait du modèle, des poids et du *tokenizer* obtenus, vous pouvez utiliser la méthode `push_to_hub()` directement disponible sur l'objet `model` : + +```py +model.push_to_hub("dummy-model") +``` + +Cela va créer le nouveau dépôt `dummy-model` dans votre profil et le remplir avec les fichiers du modèle. +Faites la même chose avec le *tokenizer*, de sorte que tous les fichiers sont maintenant disponibles dans ce dépôt : + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Si vous appartenez à une organisation, il suffit de spécifier l'argument `organization` pour télécharger dans l'espace de cette organisation : + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Si vous souhaitez utiliser un jeton Hugging Face spécifique, vous pouvez également le spécifier à la méthode `push_to_hub()` : + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Maintenant, dirigez-vous sur *Hub* pour trouver votre modèle nouvellement téléchargé : *https://huggingface.co/user-or-organization/dummy-model*. + +Cliquez sur l'onglet « Fichiers et versions » et vous devriez voir les fichiers visibles dans la capture d'écran suivante : + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Essayez** Prenez le modèle et le *tokenizer* associés au *checkpoint* `bert-base-cased` et téléchargez-les vers un dépôt dans votre espace en utilisant la méthode `push_to_hub()`. Vérifiez que le dépôt apparaît correctement sur votre page avant de le supprimer. + + + +Comme vous l'avez vu, la méthode `push_to_hub()` accepte plusieurs arguments, ce qui permet de télécharger vers un dépôt ou un espace d'organisation spécifique, ou d'utiliser un jeton d'API différent. Nous vous recommandons de jeter un coup d'œil à la spécification de la méthode disponible directement dans la documentation de [🤗 *Transformers*](https://huggingface.co/transformers/model_sharing.html) pour avoir une idée de ce qui est possible. + +La méthode `push_to_hub()` est soutenue par le *package* Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple. + +Passez à la dernière section pour voir comment télécharger des fichiers dans votre dépôt nouvellement créé ! + +## Utilisation de la bibliothèque Python `huggingface_hub` + +La bibliothèque Python `huggingface_hub` est un *package* qui offre un ensemble d'outils pour les hubs des modèles et des jeux de données. Elle fournit des méthodes et des classes simples pour des tâches courantes telles qu'obtenir et gérer des informations à propos des dépôts sur le *Hub*. Elle fournit des APIs simples qui fonctionnent au-dessus de git pour gérer le contenu de ces dépôts et pour intégrer le *Hub* dans vos projets et bibliothèques. + +De la même manière que pour l'utilisation de l'API `push_to_hub`, vous devrez avoir votre jeton d'API enregistré dans votre cache. Pour ce faire, vous devrez utiliser la commande `login` de la CLI, comme mentionné dans la section précédente (encore une fois, assurez-vous de faire précéder ces commandes du caractère `!` si vous les exécutez dans Google Colab) : + +```bash +huggingface-cli login +``` + +Le *package* `huggingface_hub` offre plusieurs méthodes et classes qui sont utiles pour notre objectif. Tout d'abord, il y a quelques méthodes pour gérer la création, la suppression des dépôts, et autres : + +```python no-format +from huggingface_hub import ( + # Gestion des utilisateurs + login, + logout, + whoami, + + # Création et gestion du dépôt + create_repo, + delete_repo, + update_repo_visibility, + + # Et quelques méthodes pour récupérer/changer des informations sur le contenu + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +De plus, elle offre la très puissante classe `Repository` pour gérer un dépôt local. Nous allons explorer ces méthodes et cette classe dans les prochaines sections pour comprendre comment les exploiter. + +La méthode `create_repo` peut être utilisée pour créer un nouveau dépôt sur le *Hub* : + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Ceci créera le dépôt `dummy-model` dans votre espace. Si vous le souhaitez, vous pouvez spécifier à quelle organisation le dépôt doit appartenir en utilisant l'argument `organization` : + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Cela créera le dépôt `dummy-model` dans l'espace de nom `huggingface`, en supposant que vous appartenez à cette organisation. +D'autres arguments qui peuvent être utiles sont : + +- `private`, afin de spécifier si le dépôt doit être visible des autres ou non, +- `token`, si vous voulez remplacer le jeton stocké dans votre cache par un jeton donné, +- `repo_type`, si vous souhaitez créer un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. + +Une fois que le dépôt est créé, nous devons y ajouter des fichiers ! Passez à la section suivante pour voir les trois façons dont cela peut être géré. + + +## Utilisation de l'interface web + +L'interface web offre des outils pour gérer les dépôts directement dans le *Hub*. En utilisant l'interface, vous pouvez facilement créer des dépôts, ajouter des fichiers (même de grande taille !), explorer des modèles, visualiser les différences, et bien plus encore. + +Pour créer un nouveau dépôt, visitez [huggingface.co/new](https://huggingface.co/new) : + +
+Page showcasing the model used for the creation of a new model repository. +
+ +Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au dépôt. + +Ensuite, saisissez le nom de votre modèle. Ce sera également le nom du dépôt. Enfin, vous pouvez préciser si vous souhaitez que votre modèle soit public ou privé. Les modèles privés sont cachés de la vue du public. + +Après avoir créé votre dépôt de modèles, vous devriez voir une page comme celle-ci : + +
+An empty model page after creating a new repository. +
+ +C'est là que votre modèle sera hébergé. Pour commencer à le remplir, vous pouvez ajouter un fichier README directement depuis l'interface web. + +
+The README file showing the Markdown capabilities. +
+ +Le fichier README est en Markdown. N'hésitez pas à vous lâcher avec lui ! La troisième partie de ce chapitre est consacrée à la construction d'une carte de modèle. Celles-ci sont d'une importance capitale pour valoriser votre modèle, car c'est par elles que vous indiquez aux autres ce qu'il peut faire. + +Si vous regardez l'onglet « *Files and versions* », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers. + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +Nous allons maintenant voir comment ajouter de nouveaux fichiers. + +## Téléchargement des fichiers du modèle + +Le système de gestion des fichiers sur le *Hub* est basé sur git pour les fichiers ordinaires et git-lfs (qui signifie [Git Large File Storage](https://git-lfs.github.com/)) pour les fichiers plus importants. + +Dans la section suivante, nous passons en revue trois façons différentes de télécharger des fichiers sur le *Hub* : par `huggingface_hub` et par des commandes git. + +### L'approche `upload_file' + +L'utilisation de `upload_file` ne nécessite pas que git et git-lfs soient installés sur votre système. Il pousse les fichiers directement vers le 🤗 *Hub* en utilisant des requêtes HTTP POST. Une limitation de cette approche est qu'elle ne gère pas les fichiers dont la taille est supérieure à 5 Go. +Si vos fichiers ont une taille supérieure à 5 Go, veuillez suivre les deux autres méthodes détaillées ci-dessous. + +L'API peut être utilisée comme suit : + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Ceci téléchargera le fichier `config.json` disponible à `` à la racine du dépôt en tant que `config.json`, vers le dépôt `dummy-model`. +D'autres arguments qui peuvent être utiles sont : + +- `token`, si vous souhaitez remplacer le jeton stocké dans votre cache par un jeton donné, +- `repo_type`, si vous souhaitez télécharger vers un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. + + +### La classe `Repository` + +La classe `Repository` gère un dépôt local d'une manière similaire à git. Elle abstrait la plupart des problèmes que l'on peut rencontrer avec git pour fournir toutes les fonctionnalités dont nous avons besoin. + +L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous que git-lfs est installé (voir [ici](https://git-lfs.github.com/) pour les instructions d'installation) et configuré avant de commencer. + +Afin de commencer à jouer avec le dépôt que nous venons de créer, nous pouvons commencer par l'initialiser dans un dossier local en clonant le dépôt distant : + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Cela a créé le dossier `` dans notre répertoire de travail. Ce dossier ne contient que le fichier `.gitattributes` car c'est le seul fichier créé lors de l'instanciation du dépôt par `create_repo`. + +A partir de maintenant, nous pouvons utiliser plusieurs des méthodes traditionnelles de git : + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +Et d'autres encore ! Nous vous recommandons de jeter un coup d’œil à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles. + +Actuellement, nous avons un modèle et un *tokenizer* que nous voulons pousser vers le *Hub*. Nous avons réussi à cloner le dépôt, nous pouvons donc enregistrer les fichiers dans ce dépôt. + +Nous nous assurons d'abord que notre clone local est à jour en récupérant les dernières modifications : + +```py +repo.git_pull() +``` + +Une fois que c'est fait, nous sauvegardons les fichiers du modèle et du *tokenizer* : + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +Le `` contient maintenant tous les fichiers du modèle et du *tokenizer*. Nous suivons le flux de travail git habituel en ajoutant des fichiers à la zone de transit, en les validant et en les poussant vers le *Hub* : + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +Félicitations ! Vous venez de pousser vos premiers fichiers sur le *Hub*. + +### L'approche basée sur git + +Il s'agit de l'approche la plus basique pour télécharger des fichiers : nous le ferons directement avec git et git-lfs. La plupart des difficultés sont abstraites par les approches précédentes, mais il y a quelques réserves avec la méthode suivante, nous allons donc suivre un cas d'utilisation plus complexe. + +L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous d'avoir [git-lfs](https://git-lfs.github.com/) installé et configuré avant de commencer. + +Commencez par initialiser git-lfs : + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +Une fois que c'est fait, la première étape consiste à cloner votre dépôt de modèles : + +```bash +git clone https://huggingface.co// +``` + +Mon nom d'utilisateur est `lysandre` et j'ai utilisé le nom de modèle `dummy`, donc pour moi la commande ressemble à ce qui suit : + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +J'ai maintenant un dossier nommé *dummy* dans mon répertoire de travail. Je peux `cd` dans ce dossier et jeter un coup d'oeil à son contenu : + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Si vous venez de créer votre dépôt en utilisant la méthode `create_repo` du *Hub*, ce dossier devrait seulement contenir un fichier caché `.gitattributes`. Si vous avez suivi les instructions de la section précédente pour créer un dépôt en utilisant l'interface web, le dossier devrait contenir un seul fichier *README.md* à côté du fichier caché `.gitattributes`, comme indiqué ici. + +L'ajout d'un fichier de taille normale, comme un fichier de configuration, un fichier de vocabulaire, ou tout autre fichier de moins de quelques mégaoctets, est fait exactement comme on le ferait dans n'importe quel système basé sur git. Cependant, les fichiers plus volumineux doivent être enregistrés via git-lfs afin de les pousser vers *huggingface.co*. + +Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons « commiter » dans notre dépôt fictif : + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Maintenant que nous avons sauvegardé quelques artefacts de modèle et de *tokenizer*, regardons à nouveau le dossier *dummy* : + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier d'état du modèle (*pytorch_model.bin*) est la seule exception, avec plus de 400 Mo. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier dict de l'état du modèle (*t5_model.h5*) est la seule aberration, avec plus de 400 Mo. + +{/if} + + +✏️ Lors de la création du dépôt à partir de l'interface web, le fichier .gitattributes est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme .bin et .h5, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part. + + +Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement Git en utilisant la commande `git add` : + +```bash +git add . +``` + +Nous pouvons alors jeter un coup d'œil aux fichiers : + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +De même, nous pouvons nous assurer que git-lfs suit les bons fichiers en utilisant sa commande `status` : + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *pytorch_model.bin* et *sentencepiece.bpe.model*, qui ont `LFS`. Super ! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *t5_model.h5* qui a `LFS`. Super ! + +{/if} + +Passons aux étapes finales, *committing* et *pushing* vers le dépôt distant *huggingface.co* : + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +Le chargement peut prendre un peu de temps, en fonction de la vitesse de votre connexion Internet et de la taille de vos fichiers : + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : + +
+The diff introduced by the recent commit. +
+{:else} +Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/fr/chapter4/4.mdx b/chapters/fr/chapter4/4.mdx index 4f2086aea..3bd79c348 100644 --- a/chapters/fr/chapter4/4.mdx +++ b/chapters/fr/chapter4/4.mdx @@ -1,84 +1,89 @@ -# Construire une carte de modèle - -La carte de modèle est un fichier qui est sans doute aussi important que les fichiers du modèle et du *tokenizer* dans un dépôt de modèles. Il s'agit de la définition centrale du modèle, qui garantit la réutilisation par les autres membres de la communauté, la reproductibilité des résultats, et une plateforme sur laquelle les autres membres peuvent construire leurs artefacts. - -Documenter le processus d'entraînement et d'évaluation aide les autres à comprendre ce qu'ils peuvent attendre d'un modèle. Fournir suffisamment d'informations concernant les données utilisées, les prétraitements et post-traitements effectués permet d'identifier et de comprendre les limites, les biais et les contextes dans lesquels le modèle est ou n'est pas utile. - -Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown. - -Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article [« *Model Cards for Model Reporting* »](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. - -La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes : - -- description du modèle -- utilisations et limites prévues -- comment utiliser le modèle -- limites et biais -- données d'entraînement -- procédure d'entraînement -- résultats de l'évaluation - -Voyons ce que chacune de ces sections doit contenir. - - -### Description du modèle - -La description du modèle fournit des détails de base sur le modèle. Cela inclut l'architecture, la version, s'il a été présenté dans un article, si une implémentation originale est disponible, l'auteur et des informations générales sur le modèle. Tout droit d'auteur doit être attribué ici. Des informations générales sur les procédures d'entraînement, les paramètres et les avertissements importants peuvent également être mentionnés dans cette section. - -### Utilisations et limitations prévues - -Vous décrivez ici les cas d'utilisation auxquels le modèle est destiné, y compris les langues, les domaines et les champs où il peut être appliqué. Cette section de la fiche de modèle peut également documenter les domaines qui sont connus pour être hors de portée du modèle, ou dans lesquels il est susceptible de fonctionner de manière sous-optimale. - -### Comment utiliser - -Cette section doit inclure des exemples d'utilisation du modèle. Cela peut montrer l'utilisation de la fonction `pipeline()`, l'utilisation des classes du modèle et du *tokenizer*, et tout autre code que vous pensez être utile. - -### Données d'entraînement - -Cette partie doit indiquer sur quel(s) jeu(x) de données le modèle a été entraîné. Une brève description du ou des jeux de données est également la bienvenue. - -### Procédure d'entraînement - -Dans cette section, vous devez décrire tous les aspects pertinents de l'entraînement qui sont utiles du point de vue de la reproductibilité. Cela inclut tout prétraitement et post-traitement effectué sur les données, ainsi que des détails tels que le nombre d'époques pour lesquelles le modèle a été entraîné, la taille du batch, le taux d'apprentissage, etc. - -### Variable et métriques - -Décrivez ici les métriques que vous utilisez pour l'évaluation et les différents facteurs que vous mesurez. En mentionnant la ou les métriques utilisées, sur quel jeu de données et quelle division du jeu de données, il est plus facile de comparer les performances de votre modèle à celles d'autres modèles. Les sections précédentes, telles que les utilisateurs prévus et les cas d'utilisation, doivent être prises en compte. - -### Résultats de l'évaluation - -Enfin, fournissez une indication de la performance du modèle sur l'ensemble de données d'évaluation. Si le modèle utilise un seuil de décision, indiquez le seuil de décision utilisé dans l'évaluation ou fournissez des détails sur l'évaluation à différents seuils pour les utilisations prévues. - - -## Exemple - -Voici quelques exemples de cartes de modèles bien conçues : - -- [`bert-base-case`](https://huggingface.co/bert-base-cased) -- [`gpt2`](https://huggingface.co/gpt2) -- [`distilbert`](https://huggingface.co/distilbert-base-uncased) - -D'autres exemples provenant de différentes organisations et entreprises sont disponibles [ici](https://github.com/huggingface/model_card/blob/master/examples.md). - -## Note - -Les fiches de modèle ne sont pas une exigence lors de la publication de modèles, et vous n'avez pas besoin d'inclure toutes les sections décrites ci-dessus lorsque vous en faites une. Cependant, une documentation explicite du modèle ne peut qu'être bénéfique aux futurs utilisateurs. Nous vous recommandons donc de remplir autant de sections que possible, au mieux de vos connaissances et de vos capacités. - -## Métadonnées de la carte de modèle - -Si vous avez exploré un peu le *Hub*, vous devriez avoir vu que certains modèles appartiennent à certaines catégories : vous pouvez les filtrer par tâches, langues, bibliothèques, et plus encore. Les catégories auxquelles appartient un modèle sont identifiées en fonction des métadonnées que vous ajoutez dans l'en-tête de la fiche du modèle. - -Par exemple, si vous regardez la fiche de modèle de [`camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), vous devriez voir les lignes suivantes dans l'en-tête de la fiche de modèle : - -``` ---- -language: fr -license: mit -datasets: -- oscar ---- -``` - -Ces métadonnées sont analysées par le *Hub* qui identifie alors ce modèle comme étant un modèle français, avec une licence MIT, entraîné sur le jeu de données Oscar. - -La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement. +# Construire une carte de modèle + + + +La carte de modèle est un fichier qui est sans doute aussi important que les fichiers du modèle et du *tokenizer* dans un dépôt de modèles. Il s'agit de la définition centrale du modèle, qui garantit la réutilisation par les autres membres de la communauté, la reproductibilité des résultats, et une plateforme sur laquelle les autres membres peuvent construire leurs artefacts. + +Documenter le processus d'entraînement et d'évaluation aide les autres à comprendre ce qu'ils peuvent attendre d'un modèle. Fournir suffisamment d'informations concernant les données utilisées, les prétraitements et post-traitements effectués permet d'identifier et de comprendre les limites, les biais et les contextes dans lesquels le modèle est ou n'est pas utile. + +Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown. + +Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article [« *Model Cards for Model Reporting* »](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. + +La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes : + +- description du modèle +- utilisations et limites prévues +- comment utiliser le modèle +- limites et biais +- données d'entraînement +- procédure d'entraînement +- résultats de l'évaluation + +Voyons ce que chacune de ces sections doit contenir. + + +### Description du modèle + +La description du modèle fournit des détails de base sur le modèle. Cela inclut l'architecture, la version, s'il a été présenté dans un article, si une implémentation originale est disponible, l'auteur et des informations générales sur le modèle. Tout droit d'auteur doit être attribué ici. Des informations générales sur les procédures d'entraînement, les paramètres et les avertissements importants peuvent également être mentionnés dans cette section. + +### Utilisations et limitations prévues + +Vous décrivez ici les cas d'utilisation auxquels le modèle est destiné, y compris les langues, les domaines et les champs où il peut être appliqué. Cette section de la fiche de modèle peut également documenter les domaines qui sont connus pour être hors de portée du modèle, ou dans lesquels il est susceptible de fonctionner de manière sous-optimale. + +### Comment utiliser + +Cette section doit inclure des exemples d'utilisation du modèle. Cela peut montrer l'utilisation de la fonction `pipeline()`, l'utilisation des classes du modèle et du *tokenizer*, et tout autre code que vous pensez être utile. + +### Données d'entraînement + +Cette partie doit indiquer sur quel(s) jeu(x) de données le modèle a été entraîné. Une brève description du ou des jeux de données est également la bienvenue. + +### Procédure d'entraînement + +Dans cette section, vous devez décrire tous les aspects pertinents de l'entraînement qui sont utiles du point de vue de la reproductibilité. Cela inclut tout prétraitement et post-traitement effectué sur les données, ainsi que des détails tels que le nombre d'époques pour lesquelles le modèle a été entraîné, la taille du batch, le taux d'apprentissage, etc. + +### Variable et métriques + +Décrivez ici les métriques que vous utilisez pour l'évaluation et les différents facteurs que vous mesurez. En mentionnant la ou les métriques utilisées, sur quel jeu de données et quelle division du jeu de données, il est plus facile de comparer les performances de votre modèle à celles d'autres modèles. Les sections précédentes, telles que les utilisateurs prévus et les cas d'utilisation, doivent être prises en compte. + +### Résultats de l'évaluation + +Enfin, fournissez une indication de la performance du modèle sur l'ensemble de données d'évaluation. Si le modèle utilise un seuil de décision, indiquez le seuil de décision utilisé dans l'évaluation ou fournissez des détails sur l'évaluation à différents seuils pour les utilisations prévues. + + +## Exemple + +Voici quelques exemples de cartes de modèles bien conçues : + +- [`bert-base-case`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +D'autres exemples provenant de différentes organisations et entreprises sont disponibles [ici](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Note + +Les fiches de modèle ne sont pas une exigence lors de la publication de modèles, et vous n'avez pas besoin d'inclure toutes les sections décrites ci-dessus lorsque vous en faites une. Cependant, une documentation explicite du modèle ne peut qu'être bénéfique aux futurs utilisateurs. Nous vous recommandons donc de remplir autant de sections que possible, au mieux de vos connaissances et de vos capacités. + +## Métadonnées de la carte de modèle + +Si vous avez exploré un peu le *Hub*, vous devriez avoir vu que certains modèles appartiennent à certaines catégories : vous pouvez les filtrer par tâches, langues, bibliothèques, et plus encore. Les catégories auxquelles appartient un modèle sont identifiées en fonction des métadonnées que vous ajoutez dans l'en-tête de la fiche du modèle. + +Par exemple, si vous regardez la fiche de modèle de [`camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), vous devriez voir les lignes suivantes dans l'en-tête de la fiche de modèle : + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +Ces métadonnées sont analysées par le *Hub* qui identifie alors ce modèle comme étant un modèle français, avec une licence MIT, entraîné sur le jeu de données Oscar. + +La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement. diff --git a/chapters/fr/chapter4/5.mdx b/chapters/fr/chapter4/5.mdx index 4365a6733..cf0b3b764 100644 --- a/chapters/fr/chapter4/5.mdx +++ b/chapters/fr/chapter4/5.mdx @@ -1,7 +1,12 @@ -# Fin de la première partie du cours ! - -C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event). - -Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums d'Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé. - +# Fin de la première partie du cours ! + + + +C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event). + +Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums d'Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé. + Nous sommes impatients de voir ce que vous allez construire avec cet outil ! \ No newline at end of file diff --git a/chapters/fr/chapter4/6.mdx b/chapters/fr/chapter4/6.mdx index a180d67fa..d0220522e 100644 --- a/chapters/fr/chapter4/6.mdx +++ b/chapters/fr/chapter4/6.mdx @@ -1,223 +1,228 @@ - - - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. A quoi sont limités les modèles du *Hub* ? - -Transformers.", - explain: "Si les modèles de la bibliothèque 🤗 Transformers sont pris en charge sur le Hub, ils ne sont pas les seuls !" - }, - { - text: "Tous les modèles avec une interface similaire à 🤗 Transformers.", - explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le Hub." - }, - { - text: "Il n'y a pas de limites.", - explain: "Il n'y a pas de limites au téléchargement de modèles sur le Hub.", - correct: true - }, - { - text: "Des modèles qui sont d'une certaine manière liés au NLP.", - explain: "Aucune exigence n'est fixée concernant le domaine d'application !" - } - ]} -/> - -### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ? - -Hub sont de simples dépôts Git exploitant git-lfs pour les fichiers volumineux.", - correct: true - } - ]} -/> - -### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? - -Forker » un dépôt existant.", - explain: "« Forker » un dépôt n'est pas possible sur le Hub." - }, - { - text: "Créer un nouveau dépôt de modèles.", - explain: "Ce n'est pas tout ce que vous pouvez faire, cependant.", - correct: true - }, - { - text: "Gérer et modifier des fichiers.", - explain: "Ce n'est pas la seule bonne réponse, cependant.", - correct: true - }, - { - text: "Télécharger des fichiers.", - explain: "Mais ce n'est pas tout.", - correct: true - }, - { - text: "Voir les différences entre les versions.", - explain: "Ce n'est pas tout ce que vous pouvez faire.", - correct: true - } - ]} -/> - -### 4. Qu'est-ce qu'une carte de modèle ? - -tokenizer.", - explain: "Il s'agit bien d'une description du modèle, mais c'est un élément important : s'il est incomplet ou absent, l'utilité du modèle est considérablement réduite." - }, - { - text: "Un moyen d'assurer la reproductibilité, la réutilisation et l'équité..", - explain: "Le fait de partager les bonnes informations dans la fiche du modèle aidera les utilisateurs à tirer parti de votre modèle et à être conscients de ses limites et de ses biais.", - correct: true - }, - { - text: "Un fichier Python qui peut être exécuté pour récupérer des informations sur le modèle.", - explain: "Les cartes de modèle sont de simples fichiers Markdown." - } - ]} -/> - -### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ? - -{#if fw === 'pt'} -tokenizer", - explain: "Tous les tokenizers ont la méthode push_to_hub et l'utiliser poussera tous les fichiers du tokenizer (vocabulaire, architecture du tokenizer, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !", - correct: true - }, - { - text: "Une configuration de modèle", - explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Un modèle", - explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", - correct: true - }, - { - text: "Trainer", - explain: "Le Trainer implémente aussi la méthode push_to_hub. L'utiliser téléchargera le modèle, sa configuration, le tokenizer et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !", - correct: true - } - ]} -/> -{:else} -tokenizer", - explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Une configuration de modèle", - explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Un modèle", - explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", - correct: true - }, - { - text: "Tout ce qui précède avec un callback dédié", - explain: "Le PushToHubCallback enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.", - correct: true - } - ]} -/> -{/if} - -### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ? - -notebook.", - explain: "Cela affichera un widget pour vous permettre de vous authentifier.", - correct: true - }, - ]} -/> - -### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ? - -tokenizer.", - explain: " ", - correct: true - }, - { - text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise huggingface_hub.", - explain: "Les modèles et les tokenizers bénéficient déjà de huggingface_hub : pas besoin d'emballage supplémentaire !" - }, - { - text: "En les sauvegardant sur le disque et en appelant transformers-cli upload-model.", - explain: "La commande upload-model n'existe pas." - } - ]} -/> - -### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ? - -commit.", - explain: "La méthode git_commit() est là pour ça.", - correct: true - }, - { - text: "Un pull.", - explain: "C'est le but de la méthode git_pull().", - correct: true - }, - { - text: "Un push.", - explain: "La méthode git_push() fait ça.", - correct: true - }, - { - text: "Un merge.", - explain: "Cette opération ne sera jamais possible avec cette API." - } - ]} + + + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. A quoi sont limités les modèles du *Hub* ? + +Transformers.", + explain: "Si les modèles de la bibliothèque 🤗 Transformers sont pris en charge sur le Hub, ils ne sont pas les seuls !" + }, + { + text: "Tous les modèles avec une interface similaire à 🤗 Transformers.", + explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le Hub." + }, + { + text: "Il n'y a pas de limites.", + explain: "Il n'y a pas de limites au téléchargement de modèles sur le Hub.", + correct: true + }, + { + text: "Des modèles qui sont d'une certaine manière liés au NLP.", + explain: "Aucune exigence n'est fixée concernant le domaine d'application !" + } + ]} +/> + +### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ? + +Hub sont de simples dépôts Git exploitant git-lfs pour les fichiers volumineux.", + correct: true + } + ]} +/> + +### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? + +Forker » un dépôt existant.", + explain: "« Forker » un dépôt n'est pas possible sur le Hub." + }, + { + text: "Créer un nouveau dépôt de modèles.", + explain: "Ce n'est pas tout ce que vous pouvez faire, cependant.", + correct: true + }, + { + text: "Gérer et modifier des fichiers.", + explain: "Ce n'est pas la seule bonne réponse, cependant.", + correct: true + }, + { + text: "Télécharger des fichiers.", + explain: "Mais ce n'est pas tout.", + correct: true + }, + { + text: "Voir les différences entre les versions.", + explain: "Ce n'est pas tout ce que vous pouvez faire.", + correct: true + } + ]} +/> + +### 4. Qu'est-ce qu'une carte de modèle ? + +tokenizer.", + explain: "Il s'agit bien d'une description du modèle, mais c'est un élément important : s'il est incomplet ou absent, l'utilité du modèle est considérablement réduite." + }, + { + text: "Un moyen d'assurer la reproductibilité, la réutilisation et l'équité..", + explain: "Le fait de partager les bonnes informations dans la fiche du modèle aidera les utilisateurs à tirer parti de votre modèle et à être conscients de ses limites et de ses biais.", + correct: true + }, + { + text: "Un fichier Python qui peut être exécuté pour récupérer des informations sur le modèle.", + explain: "Les cartes de modèle sont de simples fichiers Markdown." + } + ]} +/> + +### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ? + +{#if fw === 'pt'} +tokenizer", + explain: "Tous les tokenizers ont la méthode push_to_hub et l'utiliser poussera tous les fichiers du tokenizer (vocabulaire, architecture du tokenizer, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Trainer", + explain: "Le Trainer implémente aussi la méthode push_to_hub. L'utiliser téléchargera le modèle, sa configuration, le tokenizer et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !", + correct: true + } + ]} +/> +{:else} +tokenizer", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Tout ce qui précède avec un callback dédié", + explain: "Le PushToHubCallback enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.", + correct: true + } + ]} +/> +{/if} + +### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ? + +notebook.", + explain: "Cela affichera un widget pour vous permettre de vous authentifier.", + correct: true + }, + ]} +/> + +### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ? + +tokenizer.", + explain: " ", + correct: true + }, + { + text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise huggingface_hub.", + explain: "Les modèles et les tokenizers bénéficient déjà de huggingface_hub : pas besoin d'emballage supplémentaire !" + }, + { + text: "En les sauvegardant sur le disque et en appelant transformers-cli upload-model.", + explain: "La commande upload-model n'existe pas." + } + ]} +/> + +### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ? + +commit.", + explain: "La méthode git_commit() est là pour ça.", + correct: true + }, + { + text: "Un pull.", + explain: "C'est le but de la méthode git_pull().", + correct: true + }, + { + text: "Un push.", + explain: "La méthode git_push() fait ça.", + correct: true + }, + { + text: "Un merge.", + explain: "Cette opération ne sera jamais possible avec cette API." + } + ]} /> \ No newline at end of file diff --git a/chapters/fr/chapter5/1.mdx b/chapters/fr/chapter5/1.mdx index 2817734c9..3b1d9718f 100644 --- a/chapters/fr/chapter5/1.mdx +++ b/chapters/fr/chapter5/1.mdx @@ -1,17 +1,22 @@ -# Introduction - -Dans le [chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et des trois étapes principales pour *finetuner* un modèle : - -1. chargement d'un jeu de données à partir du *Hub* d’Hugging Face, -2. prétraitement des données avec `Dataset.map()`, -3. chargement et calcul 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 cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes : - -* que faire lorsque votre jeu de données n'est pas sur le *Hub* ? -* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?) -* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ? -* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ? -* comment créer votre propre jeu de données et le pousser sur le *Hub* ? - +# Introduction + + + +Dans le [chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et des trois étapes principales pour *finetuner* un modèle : + +1. chargement d'un jeu de données à partir du *Hub* d’Hugging Face, +2. prétraitement des données avec `Dataset.map()`, +3. chargement et calcul 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 cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes : + +* que faire lorsque votre jeu de données n'est pas sur le *Hub* ? +* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?) +* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ? +* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ? +* comment créer votre propre jeu de données et le pousser sur le *Hub* ? + Les techniques apprises dans ce chapitre vous prépareront aux tâches avancées de tokenisation du [chapitre 6](/course/fr/chapter6) et de *finetuning* du [chapitre 7](/course/fr/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 index f05424005..ee20d7800 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -1,167 +1,167 @@ -# Que faire si mon jeu de données n'est pas sur le Hub ? - - - -Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique 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 jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. - - - -## Travailler avec des jeux de données locaux et distants - -🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. La bibliothèque prend en charge plusieurs formats de données courants, tels que : - -| Format des données | Script de chargement | Exemple | -| :----------------: | :------------------: | :-----------------------------------------------------: | -| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | -| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | -| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | -| DataFrames en Pickle | `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 puis plus tard comment faire la même chose avec des fichiers distants. - -## Charger un jeu de données local - -Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien. - -Les échantillons d’entraînement 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écharge 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 *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu 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 jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. 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 un échantillon `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’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit : - -```py -squad_it_dataset["train"][0] -``` - -```python out -{ - "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008 - "paragraphs": [ - { - "context": "Il terremoto del Sichuan del 2008 o il terremoto...", - # Le tremblement de terre du Sichuan de 2008 ou le... - "qas": [ - { - "answers": [{"answer_start": 29, "text": "2008"}], - "id": "56cdca7862d2951400fa6826", - "question": "In quale anno si è verificato il terremoto nel Sichuan?", - # En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ? - }, - ... - ], - }, - ... - ], -} -``` - -Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon : - -```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. - - - -L'argument `data_files` de la fonction `load_dataset()` est assez flexible et peut être soit un chemin de fichier unique, une liste de chemins de fichiers, ou un dictionnaire qui fait correspondre les noms des échantillons aux chemins de fichiers. Vous pouvez également regrouper les fichiers correspondant à un motif spécifié selon les règles utilisées par le shell Unix. Par exemple, vous pouvez regrouper tous les fichiers JSON d'un répertoire en une seule division en définissant `data_files="*.json"`. Voir la [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) de 🤗 *Datasets* pour plus de détails. - - - -Les scripts de chargement de 🤗 *Datasets* prennent 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 jeux 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 le jeu 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 jeux de données qui ne sont pas hébergés sur le *Hub*. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données ! - - - -✏️ **Essayez !** Choisissez un autre jeu de données hébergé sur GitHub ou dans 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 jeu 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). - - +# Que faire si mon jeu de données n'est pas sur le Hub ? + + + +Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique 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 jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. + + + +## Travailler avec des jeux de données locaux et distants + +🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. La bibliothèque prend en charge plusieurs formats de données courants, tels que : + +| Format des données | Script de chargement | Exemple | +| :----------------: | :------------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| DataFrames en Pickle | `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 puis plus tard comment faire la même chose avec des fichiers distants. + +## Charger un jeu de données local + +Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien. + +Les échantillons d’entraînement 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écharge 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 *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu 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 jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. 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 un échantillon `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’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit : + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008 + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + # Le tremblement de terre du Sichuan de 2008 ou le... + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + # En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ? + }, + ... + ], + }, + ... + ], +} +``` + +Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon : + +```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. + + + +L'argument `data_files` de la fonction `load_dataset()` est assez flexible et peut être soit un chemin de fichier unique, une liste de chemins de fichiers, ou un dictionnaire qui fait correspondre les noms des échantillons aux chemins de fichiers. Vous pouvez également regrouper les fichiers correspondant à un motif spécifié selon les règles utilisées par le shell Unix. Par exemple, vous pouvez regrouper tous les fichiers JSON d'un répertoire en une seule division en définissant `data_files="*.json"`. Voir la [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) de 🤗 *Datasets* pour plus de détails. + + + +Les scripts de chargement de 🤗 *Datasets* prennent 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 jeux 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 le jeu 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 jeux de données qui ne sont pas hébergés sur le *Hub*. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données ! + + + +✏️ **Essayez !** Choisissez un autre jeu de données hébergé sur GitHub ou dans 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 jeu 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 index 1962c4dad..443681291 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -1,752 +1,752 @@ -# Il est temps de trancher et de découper - - - -La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux 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/fr/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) et contenant 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)) -# Un coup d'œil sur les premiers exemples -drug_sample[:3] -``` - -```python out -{'Unnamed: 0': [87571, 178045, 80482], - 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], - 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], - #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires'] - '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!"', - # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !" - '"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."', - # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires. - '"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."'], - # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée. J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours." - 'rating': [9.0, 3.0, 10.0], - 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], - #['2 septembre 2015', '7 novembre 2011', '5 juin 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 du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données : - -* la colonne `Unnamed: 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 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 !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons 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/fr/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 rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc 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 issu du jeu de données. Au lieu d'écrire une fonction explicite comme : - -```py -def filter_nones(x): - return x["condition"] is not None -``` - -puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. 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](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `` 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 de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de « mappage » et de filtrage. 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) -# Vérification que la mise en minuscule a fonctionné -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. Selon le cas d'usage, 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 du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est 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) -# Inspecter le premier exemple d'entraînement -drug_dataset["train"][0] -``` - -```python out -{'patient_id': 206461, - 'drugName': 'Valsartan', - 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche - 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', - # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson. - 'rating': 9.0, - 'date': 'May 20, 2012', # 20 mai 2012 - 'usefulCount': 27, - 'review_length': 17} -``` - -Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu 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'], - # contraception, spasme musculaire, douleur. - 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok - 'rating': [10.0, 1.0, 6.0], - 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], - # 4 novembre 2008, 24 mars 2017, 20 août 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, n’est pas informatif si nous voulons prédire la condition. - - - -🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner 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 jeux d'entraînement et de test d'origine. - - - -✏️ **Essayez !** 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 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 utilisons `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 batch d'exemples à la fonction *map* en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend 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 compréhension de liste. - -Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu 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é scannées au format 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` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons 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/fr/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 *notebook*, 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, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). - - - -✏️ **Essayez !** 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 temps 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 batch. 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 vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code. - -La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. 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 aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (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 faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble 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 des 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 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. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [chapitre 7](/course/fr/chapter7). - - - -💡 En apprentissage automatique, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons 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 le jeu de données : - -```py -result = tokenize_and_split(drug_dataset["train"][0]) -[len(inp) for inp in result["input_ids"]] -``` - -```python out -[128, 49] -``` - -Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* 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 jeu 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 ? L'examen du message d'erreur nous donne 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](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) de `Dataset.map()`, vous vous souvenez 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 caractéristiques, entraînant une erreur de forme. - -Le problème est que nous essayons de mélanger deux jeux 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 la première option 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 avons 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 caractéristique 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 caractéristiques : - -```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 caractéristiques 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 jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, 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` à `DataFrame` 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_ du jeu 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 vers 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 `__getitem__()`. 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 le jeu 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 !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu 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 le jeu de données à l’entraînement d'un classifieur. 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 test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer 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-la pour diviser notre ensemble d'entraînement `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/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux 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 | -| :---------------: | :----------------------: | -| Arrow | `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 JSON Lines](https://jsonlines.org), où chaque ligne du jeu 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} - # Il semble que je ressente les effets secondaires habituels de LEXAPRO : insomnie, baisse de la libido, somnolence pendant la journée. Je le prends le soir parce que mon médecin m'a dit de le prendre le soir s'il me fatiguait. J'ai supposé que ce serait le cas et j'ai commencé à le prendre la nuit. Rêves étranges, certains agréables. On m'a diagnostiqué une fibromyalgie. Il semble que ce médicament aide à soulager la douleur. J'ai eu de l'anxiété et de la dépression dans ma famille, et j'ai essayé plusieurs autres médicaments qui n'ont pas fonctionné. Cela ne fait que deux semaines que je prends ce médicament, mais je me sens plus positif dans mon esprit et je veux accomplir davantage dans ma vie. J'espère que les effets secondaires vont s'estomper, cela vaut la peine de s'y tenir d'après les réponses des autres. C'est un excellent médicament. -``` - -Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/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 la manipulation des données 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/fr/chapter3) pour entraîner un classifieur 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/fr/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 jeux de données sans faire exploser votre ordinateur portable ! +# Il est temps de trancher et de découper + + + +La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux 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/fr/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) et contenant 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)) +# Un coup d'œil sur les premiers exemples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires'] + '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!"', + # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !" + '"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."', + # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires. + '"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."'], + # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée. J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours." + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + #['2 septembre 2015', '7 novembre 2011', '5 juin 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 du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données : + +* la colonne `Unnamed: 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 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 !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons 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/fr/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 rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc 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 issu du jeu de données. Au lieu d'écrire une fonction explicite comme : + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. 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](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `` 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 de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de « mappage » et de filtrage. 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) +# Vérification que la mise en minuscule a fonctionné +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. Selon le cas d'usage, 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 du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est 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) +# Inspecter le premier exemple d'entraînement +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson. + 'rating': 9.0, + 'date': 'May 20, 2012', # 20 mai 2012 + 'usefulCount': 27, + 'review_length': 17} +``` + +Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu 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'], + # contraception, spasme musculaire, douleur. + 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + # 4 novembre 2008, 24 mars 2017, 20 août 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, n’est pas informatif si nous voulons prédire la condition. + + + +🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner 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 jeux d'entraînement et de test d'origine. + + + +✏️ **Essayez !** 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 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 utilisons `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 batch d'exemples à la fonction *map* en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend 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 compréhension de liste. + +Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu 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é scannées au format 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` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons 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/fr/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 *notebook*, 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, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). + + + +✏️ **Essayez !** 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 temps 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 batch. 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 vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code. + +La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. 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 aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (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 faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble 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 des 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 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. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [chapitre 7](/course/fr/chapter7). + + + +💡 En apprentissage automatique, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons 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 le jeu de données : + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* 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 jeu 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 ? L'examen du message d'erreur nous donne 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](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) de `Dataset.map()`, vous vous souvenez 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 caractéristiques, entraînant une erreur de forme. + +Le problème est que nous essayons de mélanger deux jeux 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 la première option 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 avons 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 caractéristique 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 caractéristiques : + +```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 caractéristiques 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 jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, 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` à `DataFrame` 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_ du jeu 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 vers 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 `__getitem__()`. 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 le jeu 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 !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu 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 le jeu de données à l’entraînement d'un classifieur. 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 test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer 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-la pour diviser notre ensemble d'entraînement `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/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux 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 | +| :---------------: | :----------------------: | +| Arrow | `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 JSON Lines](https://jsonlines.org), où chaque ligne du jeu 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} + # Il semble que je ressente les effets secondaires habituels de LEXAPRO : insomnie, baisse de la libido, somnolence pendant la journée. Je le prends le soir parce que mon médecin m'a dit de le prendre le soir s'il me fatiguait. J'ai supposé que ce serait le cas et j'ai commencé à le prendre la nuit. Rêves étranges, certains agréables. On m'a diagnostiqué une fibromyalgie. Il semble que ce médicament aide à soulager la douleur. J'ai eu de l'anxiété et de la dépression dans ma famille, et j'ai essayé plusieurs autres médicaments qui n'ont pas fonctionné. Cela ne fait que deux semaines que je prends ce médicament, mais je me sens plus positif dans mon esprit et je veux accomplir davantage dans ma vie. J'espère que les effets secondaires vont s'estomper, cela vaut la peine de s'y tenir d'après les réponses des autres. C'est un excellent médicament. +``` + +Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/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 la manipulation des données 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/fr/chapter3) pour entraîner un classifieur 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/fr/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 jeux de données sans faire exploser votre ordinateur portable ! diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index dc286c718..0b369438c 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -1,296 +1,296 @@ -# Données massives ? 🤗 Datasets à la rescousse ! - - - - -De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* 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 jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur 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 de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi 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 au jeu 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/). Le jeu 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 installer cette bibliothèque : - -```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/fr/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 jeu de données. C'est beaucoup ! - - - -✎ Par défaut, 🤗 *Datasets* décompresse 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 ...' -# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ... -} -``` - -Cela 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 du memory mapping - -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 -``` - -Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours : - -```py -import psutil - -# Process.memory_info est exprimé en octets, donc convertir en mégaoctets -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 le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu 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 -``` - -Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM ! - - - -✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le 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 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027). - - - -Si vous êtes familier avec 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 jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à 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 le jeu 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 jeu 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 jeu 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 *The 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). - - - -## Jeux de données en continu - -Pour activer le streaming du jeu 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 jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement 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 le [chapitre 3](/course/fr/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 batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. - - - -Vous pouvez également mélanger un jeu 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 ...' -# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ... -} -``` - -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 jeu 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 le jeu 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 ...' -# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...}, - {'meta': {'pmid': 11409575, 'language': 'eng'}, - 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...' -# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...}, - {'meta': {'pmid': 11409576, 'language': 'eng'}, - 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..." -# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...}, - {'meta': {'pmid': 11409577, 'language': 'eng'}, - 'text': 'Oxygen concentrators and cylinders ...' -# Concentrateurs et bouteilles d'oxygène...}, - {'meta': {'pmid': 11409578, 'language': 'eng'}, - 'text': 'Oxygen supply in rural africa: a personal experience ...' -# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}] -``` - -De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit : - -```py -# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage. -train_dataset = shuffled_dataset.skip(1000) -# Prendre les 1 000 premiers exemples pour l'ensemble de validation. -validation_dataset = shuffled_dataset.take(1000) -``` - -Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux 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 jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de *The Pile* et qui est un jeu 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...'} -``` - -Ce jeu 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 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 du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source. - -Enfin, si vous souhaitez streamer *The 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 !** 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 en streaming un jeu de données multilingue représentant 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 jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante ! +# Données massives ? 🤗 Datasets à la rescousse ! + + + + +De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* 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 jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur 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 de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi 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 au jeu 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/). Le jeu 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 installer cette bibliothèque : + +```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/fr/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 jeu de données. C'est beaucoup ! + + + +✎ Par défaut, 🤗 *Datasets* décompresse 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 ...' +# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ... +} +``` + +Cela 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 du memory mapping + +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 +``` + +Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours : + +```py +import psutil + +# Process.memory_info est exprimé en octets, donc convertir en mégaoctets +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 le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu 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 +``` + +Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM ! + + + +✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le 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 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027). + + + +Si vous êtes familier avec 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 jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à 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 le jeu 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 jeu 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 jeu 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 *The 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). + + + +## Jeux de données en continu + +Pour activer le streaming du jeu 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 jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement 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 le [chapitre 3](/course/fr/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 batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. + + + +Vous pouvez également mélanger un jeu 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 ...' +# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ... +} +``` + +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 jeu 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 le jeu 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 ...' +# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...' +# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..." +# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...' +# Concentrateurs et bouteilles d'oxygène...}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...' +# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}] +``` + +De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit : + +```py +# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage. +train_dataset = shuffled_dataset.skip(1000) +# Prendre les 1 000 premiers exemples pour l'ensemble de validation. +validation_dataset = shuffled_dataset.take(1000) +``` + +Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux 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 jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de *The Pile* et qui est un jeu 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...'} +``` + +Ce jeu 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 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 du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source. + +Enfin, si vous souhaitez streamer *The 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 !** 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 en streaming un jeu de données multilingue représentant 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 jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante ! diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index 4781cd83c..3b1f96a41 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -1,467 +1,467 @@ -# Création de votre propre jeu de données - - - -Parfois, le jeu de données dont vous avez besoin pour créer une application de 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 dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : - -* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* -* entraîner 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 : « bug », « amélioration » ou « question ») -* créer 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 « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. 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. - -
-The GitHub issues associated with 🤗 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. - -
-A typical GitHub issue in the 🤗 Datasets repository. -
- -Pour télécharger tous les problèmes du dépôt, nous utilisons 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 !** Cliquez sur quelques-unes des URL 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 dépôt contenant des 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 *token*, 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 batchs 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 -# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes... -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/fr/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 jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *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é toutes les *pull requests* également : - -> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème. - -Étant donné que le contenu des « Issues » et des *pull request* 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 *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/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 *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. 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 !** 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. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu 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 *pull_requests*. - - - -Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. - -Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec 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 *pull request* 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. - -
-Comments associated with an issue about 🤗 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`. Ecrivons 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()] - - -# Tester 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. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu 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 le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* : - -```py -issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") -``` - -## Téléchargement du jeu de données sur le 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 le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’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 !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt 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 individus. - - - -Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt 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 le jeu 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é. - -
-Our dataset repository on the Hugging Face Hub. -
- -À partir de là, n'importe qui peut télécharger le jeu 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 jeu de données sur le *Hub* directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [guide de 🤗 *Datasets*](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) pour savoir comment procéder. - - - -## Création d'une carte pour un jeu de données - -Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données. - -Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux 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 *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu 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 : - -
-The `datasets-tagging` interface. -
- -2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données 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 dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous. - - -
-A dataset card. -
- - - -✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub. - - -C’est tout ! Nous avons vu dans cette section que la création d'un bon jeu 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 nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. - - - -✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu 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, *finetunez* un classifieur multilabel pour prédire les balises présentes dans le champ `labels`. - - +# Création de votre propre jeu de données + + + +Parfois, le jeu de données dont vous avez besoin pour créer une application de 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 dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : + +* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* +* entraîner 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 : « bug », « amélioration » ou « question ») +* créer 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 « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. 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. + +
+The GitHub issues associated with 🤗 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. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Pour télécharger tous les problèmes du dépôt, nous utilisons 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 !** Cliquez sur quelques-unes des URL 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 dépôt contenant des 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 *token*, 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 batchs 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 +# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes... +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/fr/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 jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *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é toutes les *pull requests* également : + +> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème. + +Étant donné que le contenu des « Issues » et des *pull request* 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 *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/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 *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. 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 !** 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. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu 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 *pull_requests*. + + + +Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. + +Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec 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 *pull request* 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. + +
+Comments associated with an issue about 🤗 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`. Ecrivons 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()] + + +# Tester 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. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu 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 le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* : + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Téléchargement du jeu de données sur le 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 le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’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 !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt 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 individus. + + + +Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt 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 le jeu 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é. + +
+Our dataset repository on the Hugging Face Hub. +
+ +À partir de là, n'importe qui peut télécharger le jeu 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 jeu de données sur le *Hub* directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [guide de 🤗 *Datasets*](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) pour savoir comment procéder. + + + +## Création d'une carte pour un jeu de données + +Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données. + +Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux 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 *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu 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 : + +
+The `datasets-tagging` interface. +
+ +2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données 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 dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous. + + +
+A dataset card. +
+ + + +✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub. + + +C’est tout ! Nous avons vu dans cette section que la création d'un bon jeu 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 nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. + + + +✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu 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, *finetunez* un classifieur 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 index dcb42deb5..69762e98a 100644 --- a/chapters/fr/chapter5/6.mdx +++ b/chapters/fr/chapter5/6.mdx @@ -1,530 +1,530 @@ - - -# Recherche sémantique avec FAISS - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons 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 enchâssements pour la recherche sémantique - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement. - -Dans cette section, nous utilisons les enchâssements 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 jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hub* d’Hugging Face : - -```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é dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/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é l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* 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 jeu 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 jeu 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 enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent 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 `comments` 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 !** 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. La section [« Batch mapping »](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentation 🤗 *Datasets* peut être 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 incluant 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 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 jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. 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 enchâssements ! Jetons un coup d'œil. - -## Création d’enchâssements pour les textes - -Nous avons vu dans [chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. 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 _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* : - -{#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, 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` converti 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 enchâssements de *tokens* 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 *token* spécial `[CLS]`. La fonction suivante fait ça pour nous : - -```py -def cls_pooling(model_output): - return model_output.last_hidden_state[:, 0] -``` - -Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner 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 donnant la première entrée textuelle de 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 donnant la première entrée textuelle de 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 enchâssements 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 jeu 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 des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant 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. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements 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 jeu 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 !** 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. - - + + +# Recherche sémantique avec FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons 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 enchâssements pour la recherche sémantique + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement. + +Dans cette section, nous utilisons les enchâssements 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 jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hub* d’Hugging Face : + +```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é dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/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é l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* 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 jeu 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 jeu 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 enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent 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 `comments` 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 !** 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. La section [« Batch mapping »](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentation 🤗 *Datasets* peut être 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 incluant 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 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 jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. 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 enchâssements ! Jetons un coup d'œil. + +## Création d’enchâssements pour les textes + +Nous avons vu dans [chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. 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 _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* : + +{#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, 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` converti 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 enchâssements de *tokens* 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 *token* spécial `[CLS]`. La fonction suivante fait ça pour nous : + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner 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 donnant la première entrée textuelle de 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 donnant la première entrée textuelle de 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 enchâssements 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 jeu 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 des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant 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. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements 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 jeu 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 !** 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. + + diff --git a/chapters/fr/chapter5/7.mdx b/chapters/fr/chapter5/7.mdx index 55083fffa..e205ff607 100644 --- a/chapters/fr/chapter5/7.mdx +++ b/chapters/fr/chapter5/7.mdx @@ -1,10 +1,15 @@ -# 🤗 Datasets, coché ! - -Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : -- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise, -- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(), -- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(), -- créer votre propre jeu de données et l’envoyer vers le *Hub*, -- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. - -Dans le [chapitre 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! +# 🤗 Datasets, coché ! + + + +Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : +- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise, +- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(), +- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(), +- créer votre propre jeu de données et l’envoyer vers le *Hub*, +- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. + +Dans le [chapitre 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! diff --git a/chapters/fr/chapter5/8.mdx b/chapters/fr/chapter5/8.mdx index 54f4e770f..6abf66b0a 100644 --- a/chapters/fr/chapter5/8.mdx +++ b/chapters/fr/chapter5/8.mdx @@ -1,226 +1,231 @@ - - -# 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 chapitres suivants 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 d’Hugging Face.", - explain: "Vous pouvez charger des jeux de données sur le Hub en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", - correct: true - }, - { - text: "Un serveur distant.", - explain: "Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", - correct: true - }, - ]} -/> - -### 2. Supposons que vous chargiez l'une des tâches du jeu de données 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: "Il n'y a pas de méthode Dataset.sample()." - }, - { - text: "dataset.shuffle().select(range(50))", - explain: "Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.", - correct: true - }, - { - text: "dataset.select(range(50)).shuffle()", - explain: "Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données." - } - ]} -/> - -### 3. Supposons que vous disposiez d'un jeu 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 le jeu 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: "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: "Une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." - }, - { - text: "Créer une fonction comme def filter_names(x): return x['name'].startswith('L') et exécuter pets_dataset.filter(filter_names).", - explain: "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 le *memory mapping* ? - -mapping entre la RAM CPU et GPU.", - explain: "Ce n'est pas ça, réessayez !", - }, - { - text: "Un mapping entre la RAM et le stockage du système de fichiers.", - explain: "🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", - correct: true - }, - { - text: "Un mapping entre deux fichiers dans le cache 🤗 Datasets.", - explain: "Ce n'est pas ça, réessayez !" - } - ]} -/> - -### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ? - -Datasets d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.", - correct: true - }, - { - text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.", - explain: "Cela permet à 🤗 Datasets de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?", - correct: true - }, - { - text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.", - explain: "Ce n'est pas ça, réessayez !" - } - ]} -/> - -### 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: "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: "Le jeu de données allocine n'a pas d’échantillon train.", - explain: "Consultez le jeu de données allocine sur le Hub (https://huggingface.co/datasets/allocine) pour voir quels échantillons il contient." - } - ]} -/> - -### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ? - - - - -### 8. Qu'est-ce que la recherche sémantique ? - -recherche lexicale et c'est ce que vous voyez généralement avec les moteurs de recherche traditionnels." - }, - { - text: "Un moyen de rechercher des documents correspondants en comprenant la signification contextuelle d'une requête.", - explain: "La recherche sémantique utilise des vecteurs d’enchâssement pour représenter les requêtes et les documents. Elle utilise ensuite une métrique de similarité pour mesurer la quantité de chevauchement entre eux. Comment la décrire autrement ?", - correct: true - }, - { - text: "Un moyen d'améliorer la précision de la recherche.", - explain: "Les moteurs de recherche sémantique peuvent capturer l'intention d'une requête bien mieux que la correspondance des mots clés et récupèrent généralement les documents avec une plus grande précision. Mais ce n'est pas la seule bonne réponse. Qu'est-ce que la recherche sémantique apporte d'autre ?", - correct: true - } - ]} -/> - -### 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 ? - -Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de données MNIST sur le Hub pour un exemple de vision par ordinateur." - }, - { - text: "Oui.", - explain: "Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", - correct : true - }, - ]} -/> + + +# 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 chapitres suivants 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 d’Hugging Face.", + explain: "Vous pouvez charger des jeux de données sur le Hub en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", + correct: true + }, + { + text: "Un serveur distant.", + explain: "Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", + correct: true + }, + ]} +/> + +### 2. Supposons que vous chargiez l'une des tâches du jeu de données 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: "Il n'y a pas de méthode Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données." + } + ]} +/> + +### 3. Supposons que vous disposiez d'un jeu 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 le jeu 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: "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: "Une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." + }, + { + text: "Créer une fonction comme def filter_names(x): return x['name'].startswith('L') et exécuter pets_dataset.filter(filter_names).", + explain: "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 le *memory mapping* ? + +mapping entre la RAM CPU et GPU.", + explain: "Ce n'est pas ça, réessayez !", + }, + { + text: "Un mapping entre la RAM et le stockage du système de fichiers.", + explain: "🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", + correct: true + }, + { + text: "Un mapping entre deux fichiers dans le cache 🤗 Datasets.", + explain: "Ce n'est pas ça, réessayez !" + } + ]} +/> + +### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ? + +Datasets d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.", + correct: true + }, + { + text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.", + explain: "Cela permet à 🤗 Datasets de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?", + correct: true + }, + { + text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.", + explain: "Ce n'est pas ça, réessayez !" + } + ]} +/> + +### 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: "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: "Le jeu de données allocine n'a pas d’échantillon train.", + explain: "Consultez le jeu de données allocine sur le Hub (https://huggingface.co/datasets/allocine) pour voir quels échantillons il contient." + } + ]} +/> + +### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ? + + + + +### 8. Qu'est-ce que la recherche sémantique ? + +recherche lexicale et c'est ce que vous voyez généralement avec les moteurs de recherche traditionnels." + }, + { + text: "Un moyen de rechercher des documents correspondants en comprenant la signification contextuelle d'une requête.", + explain: "La recherche sémantique utilise des vecteurs d’enchâssement pour représenter les requêtes et les documents. Elle utilise ensuite une métrique de similarité pour mesurer la quantité de chevauchement entre eux. Comment la décrire autrement ?", + correct: true + }, + { + text: "Un moyen d'améliorer la précision de la recherche.", + explain: "Les moteurs de recherche sémantique peuvent capturer l'intention d'une requête bien mieux que la correspondance des mots clés et récupèrent généralement les documents avec une plus grande précision. Mais ce n'est pas la seule bonne réponse. Qu'est-ce que la recherche sémantique apporte d'autre ?", + correct: true + } + ]} +/> + +### 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 ? + +Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de données MNIST sur le Hub pour un exemple de vision par ordinateur." + }, + { + text: "Oui.", + explain: "Découvrez les développements passionnants concernant 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/fr/chapter6/1.mdx b/chapters/fr/chapter6/1.mdx index 6869cc815..730e89a3f 100644 --- a/chapters/fr/chapter6/1.mdx +++ b/chapters/fr/chapter6/1.mdx @@ -1,13 +1,18 @@ -# Introduction - -Dans le [chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues. - -Dans ce chapitre, vous apprendrez à entraîner un tout nouveau *tokenizer* sur un corpus de textes afin qu'il puisse ensuite être utilisé pour pré-entraîner un modèle de langue. Tout cela se fera à l'aide de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers), qui fournit les *tokenizers* « rapides » de la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers). Nous examinerons de près les fonctionnalités offertes par cette bibliothèque et nous étudierons comment les *tokenizers* rapides diffèrent des versions « lentes ». - -Les sujets que nous couvrirons comprennent : -* comment entraîner sur un nouveau corpus de textes, un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné, -* les caractéristiques spéciales des *tokenizers* rapides, -* les différences entre les trois principaux algorithmes de tokénisation utilisés aujourd'hui en NLP, -* comment construire un *tokenizer* à partir de zéro avec la bibliothèque 🤗 *Tokenizers* et l'entraîner sur des données. - +# Introduction + + + +Dans le [chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues. + +Dans ce chapitre, vous apprendrez à entraîner un tout nouveau *tokenizer* sur un corpus de textes afin qu'il puisse ensuite être utilisé pour pré-entraîner un modèle de langue. Tout cela se fera à l'aide de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers), qui fournit les *tokenizers* « rapides » de la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers). Nous examinerons de près les fonctionnalités offertes par cette bibliothèque et nous étudierons comment les *tokenizers* rapides diffèrent des versions « lentes ». + +Les sujets que nous couvrirons comprennent : +* comment entraîner sur un nouveau corpus de textes, un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné, +* les caractéristiques spéciales des *tokenizers* rapides, +* les différences entre les trois principaux algorithmes de tokénisation utilisés aujourd'hui en NLP, +* comment construire un *tokenizer* à partir de zéro avec la bibliothèque 🤗 *Tokenizers* et l'entraîner sur des données. + Les techniques présentées dans ce chapitre vous prépareront à la section du [chapitre 7](/course/fr/chapter7/6) où nous verrons comment créer un modèle de langue pour le langage Python. Commençons par examiner ce que signifie « entraîner » un *tokenizer*. \ No newline at end of file diff --git a/chapters/fr/chapter6/10.mdx b/chapters/fr/chapter6/10.mdx index 062b51f16..4846966e4 100644 --- a/chapters/fr/chapter6/10.mdx +++ b/chapters/fr/chapter6/10.mdx @@ -1,278 +1,283 @@ - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Quand devez-vous entraîner un nouveau tokenizer ? - -tokenizer que le modèle pré-entraîné et de finetuner ce modèle à la place." - }, - { - text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", - explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." - }, - { - text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant et que vous souhaitez pré-entraîner un nouveau modèle.", - explain: "Dans ce cas, il n'y a aucun avantage à utiliser le même tokenizer.", - correct: true - }, - { - text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant mais que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", - explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." - } - ]} -/> - -### 2. Quel est l'avantage d'utiliser un générateur de listes par rapport à une liste de listes lors de l'utilisation de train_new_from_iterator() ? - -train_new_from_iterator() accepte.", - explain: "Une liste de listes de textes est un type particulier de générateur de listes de textes, la méthode l'acceptera donc aussi. Essayez à nouveau !" - }, - { - text: "Vous éviterez de charger l'ensemble des données en mémoire en une seule fois.", - explain: "Chaque batch de textes sera libéré de la mémoire lorsque vous itérerez et le gain sera particulièrement visible si vous utilisez des 🤗 Datasets pour stocker vos textes.", - correct: true - }, - { - text: "Cela permettra à la bibliothèque 🤗 Tokenizers d'utiliser le multitraitement.", - explain: "Il utilisera le multiprocesseur dans tous les cas." - }, - { - text: "Le tokenizer que vous entraînez générera de meilleurs textes.", - explain: "Le tokenizer ne génère pas de texte. Vous le confondez avec un modèle de langage ?" - } - ]} -/> - -### 3. Quels sont les avantages d'utiliser un tokenizer « rapide » ? - -tokenizer lent lorsque vous faites des batchs d'entrées.", - explain: "Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?", - correct: true - }, - { - text: "Les tokenizers rapides sont toujours plus rapides que leurs homologues lents.", - explain: "Un tokenizer rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme." - }, - { - text: "Il peut appliquer le padding et la troncature.", - explain: "C'est vrai, mais les tokenizers lents le font aussi." - }, - { - text: "Il possède des fonctionnalités supplémentaires qui vous permettent d'associer les tokens à l'extrait de texte qui les a créés.", - explain: "En effet, c'est ce qu'on appelle des correspondances d'offset. Ce n'est pas le seul avantage, cependant.", - correct: true - } - ]} -/> - -### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs tokens ? - -token porte l'étiquette de l'entité, le mot entier est considéré comme étiqueté avec cette entité.", - explain: "C'est une stratégie pour gérer les entités. Quelles autres réponses s'appliquent ici ?", - correct: true - }, - { - text: "Lorsqu'un token a l'étiquette d'une entité donnée, tout autre token suivant ayant la même étiquette est considéré comme faisant partie de la même entité, à moins qu'il ne soit étiqueté comme le début d'une nouvelle entité.", - explain: "C'est la façon la plus courante de regrouper des entités, mais ce n'est pas la seule bonne réponse.", - correct: true - } - ]} -/> - -### 5. Comment le pipeline `question-answering` gère-t-il les contextes longs ? - - - -### 6. Qu'est-ce que la normalisation ? - -tokenizer effectue sur les textes lors des étapes initiales.", - explain: "Par exemple, il peut s'agir de supprimer les accents ou les espaces, ou de mettre les entrées en minuscules.", - correct: true - }, - { - text: "Il s'agit d'une technique d'augmentation de données qui consiste à rendre le texte plus normal en supprimant les mots rares.", - explain: "Essayez encore." - }, - { - text: "C'est l'étape finale du post-traitement où le tokenizer ajoute les tokens spéciaux.", - explain: "Cette étape est simplement appelée post-traitement." - }, - { - text: "C'est lorsque les enchâssements sont faits avec une moyenne nulle et un écart-type de 1, en soustrayant la moyenne et en divisant par l'écart-type.", - explain: "Ce processus est communément appelé normalisation lorsqu'il est appliqué aux valeurs des pixels en vision par ordinateur, mais ce n'est pas ce que signifie la normalisation en NLP." - } - ]} -/> - -### 7. Qu'est-ce que la pré-tokénisation pour un tokenizer en sous-mots ? - -tokenizer, pour diviser l'entrée en mots.", - explain: "C'est la bonne réponse !", - correct: true - }, - { - text: "Il s'agit de l'étape précédant l'application du modèle tokenizer, qui divise l'entrée en tokens.", - explain: "La division en tokens est le travail du modèle tokenizer." - } - ]} -/> - -### 8. Sélectionnez les phrases qui s'appliquent au tokenizer BPE. - -tokens.", - explain: "C'est l'approche adoptée par un algorithme de tokénisation différent." - }, - { - text: "Un tokenizer BPE apprend les règles de fusion en fusionnant la paire de tokens la plus fréquente.", - explain: "C'est exact !", - correct: true - }, - { - text: "Un tokenizer BPE apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", - explain: "C'est la stratégie appliquée par un autre algorithme de tokenization." - }, - { - text: "BPE tokenise les mots en sous-mots en les divisant en caractères, puis en appliquant les règles de fusion.", - explain: " ", - correct: true - }, - { - text: "BPE tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - ]} -/> - -### 9. Sélectionnez les phrases qui s'appliquent au tokenizer WordPiece. - -tokens.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - { - text: "WordPiece Les tokenizer apprennent les règles de fusion en fusionnant la paire de tokens la plus fréquente.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - { - text: "Un tokenizer WordPiece apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", - explain: " ", - correct: true - }, - { - text: "WordPiece tokenise les mots en sous-mots en trouvant la segmentation en tokens la plus probable, selon le modèle.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - { - text: "WordPiece tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", - explain: "C'est ainsi que WordPiece procède pour l'encodage.", - correct: true - }, - ]} -/> - -### 10. Sélectionnez les phrases qui s'appliquent au tokenizer Unigram. - -tokens.", - explain: " ", - correct: true - }, - { - text: "Unigram adapte son vocabulaire en minimisant une perte calculée sur l'ensemble du corpus.", - explain: " ", - correct: true - }, - { - text: "Unigram adapte son vocabulaire en conservant les sous-mots les plus fréquents.", - explain: " " - }, - { - text: "Unigram segmente les mots en sous-mots en trouvant la segmentation la plus probable en tokens, selon le modèle.", - explain: " ", - correct: true - }, - { - text: "Unigram décompose les mots en sous-mots en les divisant en caractères puis en appliquant les règles de fusion.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - ]} -/> + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Quand devez-vous entraîner un nouveau tokenizer ? + +tokenizer que le modèle pré-entraîné et de finetuner ce modèle à la place." + }, + { + text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", + explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." + }, + { + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant et que vous souhaitez pré-entraîner un nouveau modèle.", + explain: "Dans ce cas, il n'y a aucun avantage à utiliser le même tokenizer.", + correct: true + }, + { + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant mais que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", + explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." + } + ]} +/> + +### 2. Quel est l'avantage d'utiliser un générateur de listes par rapport à une liste de listes lors de l'utilisation de train_new_from_iterator() ? + +train_new_from_iterator() accepte.", + explain: "Une liste de listes de textes est un type particulier de générateur de listes de textes, la méthode l'acceptera donc aussi. Essayez à nouveau !" + }, + { + text: "Vous éviterez de charger l'ensemble des données en mémoire en une seule fois.", + explain: "Chaque batch de textes sera libéré de la mémoire lorsque vous itérerez et le gain sera particulièrement visible si vous utilisez des 🤗 Datasets pour stocker vos textes.", + correct: true + }, + { + text: "Cela permettra à la bibliothèque 🤗 Tokenizers d'utiliser le multitraitement.", + explain: "Il utilisera le multiprocesseur dans tous les cas." + }, + { + text: "Le tokenizer que vous entraînez générera de meilleurs textes.", + explain: "Le tokenizer ne génère pas de texte. Vous le confondez avec un modèle de langage ?" + } + ]} +/> + +### 3. Quels sont les avantages d'utiliser un tokenizer « rapide » ? + +tokenizer lent lorsque vous faites des batchs d'entrées.", + explain: "Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?", + correct: true + }, + { + text: "Les tokenizers rapides sont toujours plus rapides que leurs homologues lents.", + explain: "Un tokenizer rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme." + }, + { + text: "Il peut appliquer le padding et la troncature.", + explain: "C'est vrai, mais les tokenizers lents le font aussi." + }, + { + text: "Il possède des fonctionnalités supplémentaires qui vous permettent d'associer les tokens à l'extrait de texte qui les a créés.", + explain: "En effet, c'est ce qu'on appelle des correspondances d'offset. Ce n'est pas le seul avantage, cependant.", + correct: true + } + ]} +/> + +### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs tokens ? + +token porte l'étiquette de l'entité, le mot entier est considéré comme étiqueté avec cette entité.", + explain: "C'est une stratégie pour gérer les entités. Quelles autres réponses s'appliquent ici ?", + correct: true + }, + { + text: "Lorsqu'un token a l'étiquette d'une entité donnée, tout autre token suivant ayant la même étiquette est considéré comme faisant partie de la même entité, à moins qu'il ne soit étiqueté comme le début d'une nouvelle entité.", + explain: "C'est la façon la plus courante de regrouper des entités, mais ce n'est pas la seule bonne réponse.", + correct: true + } + ]} +/> + +### 5. Comment le pipeline `question-answering` gère-t-il les contextes longs ? + + + +### 6. Qu'est-ce que la normalisation ? + +tokenizer effectue sur les textes lors des étapes initiales.", + explain: "Par exemple, il peut s'agir de supprimer les accents ou les espaces, ou de mettre les entrées en minuscules.", + correct: true + }, + { + text: "Il s'agit d'une technique d'augmentation de données qui consiste à rendre le texte plus normal en supprimant les mots rares.", + explain: "Essayez encore." + }, + { + text: "C'est l'étape finale du post-traitement où le tokenizer ajoute les tokens spéciaux.", + explain: "Cette étape est simplement appelée post-traitement." + }, + { + text: "C'est lorsque les enchâssements sont faits avec une moyenne nulle et un écart-type de 1, en soustrayant la moyenne et en divisant par l'écart-type.", + explain: "Ce processus est communément appelé normalisation lorsqu'il est appliqué aux valeurs des pixels en vision par ordinateur, mais ce n'est pas ce que signifie la normalisation en NLP." + } + ]} +/> + +### 7. Qu'est-ce que la pré-tokénisation pour un tokenizer en sous-mots ? + +tokenizer, pour diviser l'entrée en mots.", + explain: "C'est la bonne réponse !", + correct: true + }, + { + text: "Il s'agit de l'étape précédant l'application du modèle tokenizer, qui divise l'entrée en tokens.", + explain: "La division en tokens est le travail du modèle tokenizer." + } + ]} +/> + +### 8. Sélectionnez les phrases qui s'appliquent au tokenizer BPE. + +tokens.", + explain: "C'est l'approche adoptée par un algorithme de tokénisation différent." + }, + { + text: "Un tokenizer BPE apprend les règles de fusion en fusionnant la paire de tokens la plus fréquente.", + explain: "C'est exact !", + correct: true + }, + { + text: "Un tokenizer BPE apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", + explain: "C'est la stratégie appliquée par un autre algorithme de tokenization." + }, + { + text: "BPE tokenise les mots en sous-mots en les divisant en caractères, puis en appliquant les règles de fusion.", + explain: " ", + correct: true + }, + { + text: "BPE tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + ]} +/> + +### 9. Sélectionnez les phrases qui s'appliquent au tokenizer WordPiece. + +tokens.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + { + text: "WordPiece Les tokenizer apprennent les règles de fusion en fusionnant la paire de tokens la plus fréquente.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + { + text: "Un tokenizer WordPiece apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", + explain: " ", + correct: true + }, + { + text: "WordPiece tokenise les mots en sous-mots en trouvant la segmentation en tokens la plus probable, selon le modèle.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + { + text: "WordPiece tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", + explain: "C'est ainsi que WordPiece procède pour l'encodage.", + correct: true + }, + ]} +/> + +### 10. Sélectionnez les phrases qui s'appliquent au tokenizer Unigram. + +tokens.", + explain: " ", + correct: true + }, + { + text: "Unigram adapte son vocabulaire en minimisant une perte calculée sur l'ensemble du corpus.", + explain: " ", + correct: true + }, + { + text: "Unigram adapte son vocabulaire en conservant les sous-mots les plus fréquents.", + explain: " " + }, + { + text: "Unigram segmente les mots en sous-mots en trouvant la segmentation la plus probable en tokens, selon le modèle.", + explain: " ", + correct: true + }, + { + text: "Unigram décompose les mots en sous-mots en les divisant en caractères puis en appliquant les règles de fusion.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + ]} +/> diff --git a/chapters/fr/chapter6/2.mdx b/chapters/fr/chapter6/2.mdx index 9a29792dd..8eba2f385 100644 --- a/chapters/fr/chapter6/2.mdx +++ b/chapters/fr/chapter6/2.mdx @@ -1,265 +1,265 @@ -# Entraîner un nouveau tokenizer à partir d'un ancien - - - -Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. - - - - - -⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est par nature aléatoire (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui identifie les meilleurs sous-mots à choisir pour un corpus donné. Les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. Le processus est déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus. - - - - -## Assemblage d'un corpus - -Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un déjà existant : `AutoTokenizer.train_new_from_iterator()`. Pour illustrer cela, disons que nous voulons entraîner GPT-2 à partir de zéro mais dans une langue autre que l'anglais. Notre première tâche est de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour avoir des exemples que tout le monde puisse comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois mais plutôt une langue anglaise spécialisée : le langage Python. - -La bibliothèque [🤗 *Datasets*](https://github.com/huggingface/datasets) peut nous aider à assembler un corpus de code source Python. Nous allons utiliser la fonction habituelle `load_dataset()` pour télécharger et mettre en cache le jeu de données [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Ce jeu de données a été créé pour le [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) et contient des millions de fonctions provenant de bibliothèques open source sur GitHub dans plusieurs langages de programmation. Ici, nous allons charger la partie Python de ce jeu de données : - -```py -from datasets import load_dataset - -# Cela peut prendre quelques minutes alors prenez un thé ou un café pendant que vous patientez ! -raw_datasets = load_dataset("code_search_net", "python") -``` - -Nous pouvons jeter un coup d'œil au jeu d'entraînement pour voir quelles sont les colonnes auxquelles nous avons accès : - -```py -raw_datasets["train"] -``` - -```python out -Dataset({ - features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', - 'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', - 'func_code_url' - ], - num_rows: 412178 -}) -``` - -Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple de la façon suivante : - -```py -print(raw_datasets["train"][123456]["whole_func_string"]) -``` - -qui nous affiche ce qui suit : - -```out -def handle_simple_responses( - self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): - """Accepts normal responses from the device. - - Args: - timeout_ms: Timeout in milliseconds to wait for each response. - info_cb: Optional callback for text sent from the bootloader. - - Returns: - OKAY packet's message. - """ - return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) -``` - -La première chose à faire est de transformer le jeu de données en un _itérateur_ de listes de textes. Par exemple, une liste de listes de textes. L'utilisation de listes de textes permet à notre *tokenizer* d'aller plus vite (l'entraînement a alors lieu sur des batchs de textes au lieu de traiter des textes un par un). Et le fait que ce soit un itérateur permet d'éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. - -Faire ce qui suit créerait une liste de listes de 1 000 textes chacune mais chargerait tout en mémoire : - - -```py -# Ne décommentez pas la ligne suivante à moins que votre jeu de données soit petit ! -# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] -``` - -En utilisant un générateur, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire à moins que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses : - -```py -training_corpus = ( - raw_datasets["train"][i : i + 1000]["whole_func_string"] - for i in range(0, len(raw_datasets["train"]), 1000) -) -``` - -Cette ligne de code ne récupère aucun élément du jeu de données. Elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert) et seulement 1 000 textes à la fois. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme jeu de données. - -Le problème avec un objet générateur est qu'il ne peut être utilisé qu'une seule fois. Ainsi, au lieu que cet objet nous donne deux fois la liste des 10 premiers chiffres : - - -```py -gen = (i for i in range(10)) -print(list(gen)) -print(list(gen)) -``` - -on les reçoit une fois et ensuite une liste vide : - -```python out -[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -[] -``` - -C'est pourquoi nous définissons une fonction qui renvoie un générateur à la place : - -```py -def get_training_corpus(): - return ( - raw_datasets["train"][i : i + 1000]["whole_func_string"] - for i in range(0, len(raw_datasets["train"]), 1000) - ) - - -training_corpus = get_training_corpus() -``` - -Vous pouvez également définir votre générateur à l'intérieur d'une boucle `for` en utilisant l'instruction `yield` : - -```py -def get_training_corpus(): - dataset = raw_datasets["train"] - for start_idx in range(0, len(dataset), 1000): - samples = dataset[start_idx : start_idx + 1000] - yield samples["whole_func_string"] -``` - -qui produit exactement le même générateur que précédemment mais permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste. - - -## Entraînement d'un nouveau tokenizer - -Maintenant que nous avons notre corpus sous la forme d'un itérateur de batchs de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, le GPT-2) : - - -```py -from transformers import AutoTokenizer - -old_tokenizer = AutoTokenizer.from_pretrained("gpt2") -``` - -Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de faire ça pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser. Notre nouveau *tokenizer* sera exactement le même que celui du GPT-2. La seule chose qui changera sera le vocabulaire qui sera déterminé lors de l'entraînement sur notre corpus. - -Voyons d'abord comment ce *tokenizer* traiterait un exemple de fonction : - - -```py -example = '''def add_numbers(a, b): - """Add the two numbers `a` and `b`.""" - return a + b''' - -tokens = old_tokenizer.tokenize(example) -tokens -``` - -```python out -['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', - 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] -``` - -Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace. Le *tokenizer* renvoie des jetons individuels pour chaque espace alors qu'il pourrait regrouper ceux des indentations (puisqu’avoir des ensembles de quatre ou huit espaces est très courant dans du code). Il divise également le nom de la fonction de façon un peu bizarre car pas habitué à voir des mots avec le caractère `_`. - -Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour cela, nous allons utiliser la méthode `train_new_from_iterator()` : - - -```py -tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) -``` - -Cette commande peut prendre un peu de temps si votre corpus est très grand. Pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs). - -Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le *tokenizer* que vous utilisez est un *tokenizer* « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits en pur Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers* qui est écrite dans le langage [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il faut que cela soit écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque en C optimisée pour les GPUs. - -Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. - -La plupart des *transformers* ont un *tokenizer* rapide de disponible. Il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks). S'il est disponible, l'API `AutoTokenizer` sélectionne toujours pour vous le *tokenizer* rapide. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront très utiles pour des tâches comme la classification de *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : - -```py -tokens = tokenizer.tokenize(example) -tokens -``` - -```python out -['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', - 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] -``` - -Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne. Nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python. Par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une *docstring*. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte. En comparaison, l'utilisation du *tokenizer* en anglais « simple » sur le même exemple nous donnera une phrase plus longue : - -```py -print(len(tokens)) -print(len(old_tokenizer.tokenize(example))) -``` - -```python out -27 -36 -``` - -Prenons un autre exemple : - -```python -example = """class LinearLayer(): - def __init__(self, input_size, output_size): - self.weight = torch.randn(input_size, output_size) - self.bias = torch.zeros(output_size) - - def __call__(self, x): - return x @ self.weights + self.bias - """ -tokenizer.tokenize(example) -``` - -```python out -['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',', - 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_', - 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(', - 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ', - 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] -``` - -En plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul *token*. Nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules. Par exemple `LinearLayer` est tokenisé comme `["ĠLinear", "Layer"]`. - -## Sauvegarde du tokenizer - -Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre nouveau *tokenizer*. Comme pour les modèles, ceci est fait avec la méthode `save_pretrained()` : - - -```py -tokenizer.save_pretrained("code-search-net-tokenizer") -``` - -Cela créera un nouveau dossier nommé *code-search-net-tokenizer* contenant tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas sur un ordinateur portable, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Une fois connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante : - -```py -tokenizer.push_to_hub("code-search-net-tokenizer") -``` - -Cela créera un nouveau dépôt dans votre espace avec le nom `code-search-net-tokenizer` contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` : - -```py -# Remplacez "huggingface-course" ci-dessous par votre espace réel pour utiliser votre propre tokenizer -tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") -``` - +# Entraîner un nouveau tokenizer à partir d'un ancien + + + +Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. + + + + + +⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est par nature aléatoire (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui identifie les meilleurs sous-mots à choisir pour un corpus donné. Les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. Le processus est déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus. + + + + +## Assemblage d'un corpus + +Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un déjà existant : `AutoTokenizer.train_new_from_iterator()`. Pour illustrer cela, disons que nous voulons entraîner GPT-2 à partir de zéro mais dans une langue autre que l'anglais. Notre première tâche est de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour avoir des exemples que tout le monde puisse comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois mais plutôt une langue anglaise spécialisée : le langage Python. + +La bibliothèque [🤗 *Datasets*](https://github.com/huggingface/datasets) peut nous aider à assembler un corpus de code source Python. Nous allons utiliser la fonction habituelle `load_dataset()` pour télécharger et mettre en cache le jeu de données [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Ce jeu de données a été créé pour le [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) et contient des millions de fonctions provenant de bibliothèques open source sur GitHub dans plusieurs langages de programmation. Ici, nous allons charger la partie Python de ce jeu de données : + +```py +from datasets import load_dataset + +# Cela peut prendre quelques minutes alors prenez un thé ou un café pendant que vous patientez ! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Nous pouvons jeter un coup d'œil au jeu d'entraînement pour voir quelles sont les colonnes auxquelles nous avons accès : + +```py +raw_datasets["train"] +``` + +```python out +Dataset({ + features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', + 'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', + 'func_code_url' + ], + num_rows: 412178 +}) +``` + +Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple de la façon suivante : + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +qui nous affiche ce qui suit : + +```out +def handle_simple_responses( + self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): + """Accepts normal responses from the device. + + Args: + timeout_ms: Timeout in milliseconds to wait for each response. + info_cb: Optional callback for text sent from the bootloader. + + Returns: + OKAY packet's message. + """ + return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) +``` + +La première chose à faire est de transformer le jeu de données en un _itérateur_ de listes de textes. Par exemple, une liste de listes de textes. L'utilisation de listes de textes permet à notre *tokenizer* d'aller plus vite (l'entraînement a alors lieu sur des batchs de textes au lieu de traiter des textes un par un). Et le fait que ce soit un itérateur permet d'éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. + +Faire ce qui suit créerait une liste de listes de 1 000 textes chacune mais chargerait tout en mémoire : + + +```py +# Ne décommentez pas la ligne suivante à moins que votre jeu de données soit petit ! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +En utilisant un générateur, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire à moins que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses : + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Cette ligne de code ne récupère aucun élément du jeu de données. Elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert) et seulement 1 000 textes à la fois. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme jeu de données. + +Le problème avec un objet générateur est qu'il ne peut être utilisé qu'une seule fois. Ainsi, au lieu que cet objet nous donne deux fois la liste des 10 premiers chiffres : + + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +on les reçoit une fois et ensuite une liste vide : + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +C'est pourquoi nous définissons une fonction qui renvoie un générateur à la place : + +```py +def get_training_corpus(): + return ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) + ) + + +training_corpus = get_training_corpus() +``` + +Vous pouvez également définir votre générateur à l'intérieur d'une boucle `for` en utilisant l'instruction `yield` : + +```py +def get_training_corpus(): + dataset = raw_datasets["train"] + for start_idx in range(0, len(dataset), 1000): + samples = dataset[start_idx : start_idx + 1000] + yield samples["whole_func_string"] +``` + +qui produit exactement le même générateur que précédemment mais permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste. + + +## Entraînement d'un nouveau tokenizer + +Maintenant que nous avons notre corpus sous la forme d'un itérateur de batchs de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, le GPT-2) : + + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de faire ça pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser. Notre nouveau *tokenizer* sera exactement le même que celui du GPT-2. La seule chose qui changera sera le vocabulaire qui sera déterminé lors de l'entraînement sur notre corpus. + +Voyons d'abord comment ce *tokenizer* traiterait un exemple de fonction : + + +```py +example = '''def add_numbers(a, b): + """Add the two numbers `a` and `b`.""" + return a + b''' + +tokens = old_tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', + 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace. Le *tokenizer* renvoie des jetons individuels pour chaque espace alors qu'il pourrait regrouper ceux des indentations (puisqu’avoir des ensembles de quatre ou huit espaces est très courant dans du code). Il divise également le nom de la fonction de façon un peu bizarre car pas habitué à voir des mots avec le caractère `_`. + +Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour cela, nous allons utiliser la méthode `train_new_from_iterator()` : + + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Cette commande peut prendre un peu de temps si votre corpus est très grand. Pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs). + +Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le *tokenizer* que vous utilisez est un *tokenizer* « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits en pur Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers* qui est écrite dans le langage [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il faut que cela soit écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque en C optimisée pour les GPUs. + +Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. + +La plupart des *transformers* ont un *tokenizer* rapide de disponible. Il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks). S'il est disponible, l'API `AutoTokenizer` sélectionne toujours pour vous le *tokenizer* rapide. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront très utiles pour des tâches comme la classification de *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne. Nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python. Par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une *docstring*. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte. En comparaison, l'utilisation du *tokenizer* en anglais « simple » sur le même exemple nous donnera une phrase plus longue : + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +Prenons un autre exemple : + +```python +example = """class LinearLayer(): + def __init__(self, input_size, output_size): + self.weight = torch.randn(input_size, output_size) + self.bias = torch.zeros(output_size) + + def __call__(self, x): + return x @ self.weights + self.bias + """ +tokenizer.tokenize(example) +``` + +```python out +['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',', + 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_', + 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(', + 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ', + 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] +``` + +En plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul *token*. Nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules. Par exemple `LinearLayer` est tokenisé comme `["ĠLinear", "Layer"]`. + +## Sauvegarde du tokenizer + +Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre nouveau *tokenizer*. Comme pour les modèles, ceci est fait avec la méthode `save_pretrained()` : + + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Cela créera un nouveau dossier nommé *code-search-net-tokenizer* contenant tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas sur un ordinateur portable, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Une fois connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante : + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Cela créera un nouveau dépôt dans votre espace avec le nom `code-search-net-tokenizer` contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` : + +```py +# Remplacez "huggingface-course" ci-dessous par votre espace réel pour utiliser votre propre tokenizer +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous verrons cela dans le [chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe lorsque nous appelons la méthode `train_new_from_iterator()`. \ No newline at end of file diff --git a/chapters/fr/chapter6/3.mdx b/chapters/fr/chapter6/3.mdx index c37fc06ff..41d437a8d 100644 --- a/chapters/fr/chapter6/3.mdx +++ b/chapters/fr/chapter6/3.mdx @@ -1,477 +1,477 @@ - - -# Pouvoirs spéciaux des tokenizers rapides - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans cette section, nous allons examiner de plus près les capacités des *tokenizers* dans 🤗 *Transformers*. -Jusqu'à présent, nous ne les avons utilisés que pour tokeniser les entrées ou décoder les identifiants pour revenir à du texte. Mais les *tokenizers*, et surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans le [chapitre 1](/course/fr/chapter1). - - - -Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et les « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les rapides sont ceux fournis par 🤗 *Tokenizers* et sont codés en Rust. Si vous vous souvenez du tableau du [chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données *Drug Review*, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents : - - | *Tokenizer* rapide | *Tokenizer* lent -:--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s - - - -⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez beaucoup de textes en parallèle et en même temps que vous pourrez clairement voir la différence. - - - -## L'objet BatchEncoding - - - -La sortie d'un *tokenizer* n'est pas un simple dictionnaire Python. Ce que nous obtenons est en fait un objet spécial `BatchEncoding`. C'est une sous-classe d'un dictionnaire (c'est pourquoi nous avons pu indexer ce résultat sans problème auparavant), mais avec des méthodes supplémentaires qui sont principalement utilisées par les *tokenizers* rapides. - -En plus de leurs capacités de parallélisation, la fonctionnalité clé des *tokenizers* rapides est qu'ils gardent toujours la trace de l'étendue originale des textes d'où proviennent les *tokens* finaux, une fonctionnalité que nous appelons *mapping offset*. Cela permet de débloquer des fonctionnalités telles que le mappage de chaque mot aux *tokens* qu'il a générés ou le mappage de chaque caractère du texte original au *token* qu'il contient, et vice versa. - -Prenons un exemple : - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -example = "My name is Sylvain and I work at Hugging Face in Brooklyn." -# Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn. -encoding = tokenizer(example) -print(type(encoding)) -``` - -Comme mentionné précédemment, nous obtenons un objet `BatchEncoding` dans la sortie du *tokenizer* : - -```python out - -``` - -Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* comme suit : - -```python -tokenizer.is_fast -``` - -```python out -True -``` - -soit vérifier le même attribut mais avec notre `encoding` : - -```python -encoding.is_fast -``` - -```python out -True -``` - -Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les identifiants en *tokens* : - -```py -encoding.tokens() -``` - -```python out -['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', - 'Brooklyn', '.', '[SEP]'] -``` - -Dans ce cas, le *token* à l'index 5 est `##yl` et fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* : - -```py -encoding.word_ids() -``` - -```python out -[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] -``` - -On peut voir que les *tokens* spéciaux du *tokenizer*, `[CLS]` et `[SEP]`, sont mis en correspondance avec `None` et que chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées et le POS (*Part-of-speech*). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_). - - - -La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I'll » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de prétokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots. - - - -✏️ **Essayez !** Créez un *tokenizer* à partir des checkpoints `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec. Qu'observez-vous ? Quels sont les identifiants des mots ? - - - -De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un *token* à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information). - -Enfin, nous pouvons faire correspondre n'importe quel mot ou *token* aux caractères du texte d'origine (et vice versa) grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci : - -```py -start, end = encoding.word_to_chars(3) -example[start:end] -``` - -```python out -Sylvain -``` - -Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le *tokenizer* rapide garde la trace de la partie du texte d'où provient chaque *token* dans une liste d'*offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`. - - - -✏️ **Essayez !** Rédigez votre propre texte et voyez si vous pouvez comprendre quels *tokens* sont associés à l'identifiant du mot et comment extraire les étendues de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants ont un sens pour vous. - - - -## A l'intérieur du pipeline `token-classification` - -Dans le [chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de la NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction `pipeline()` de 🤗 *Transformers*. Puis, dans le [chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline mais le post-traitement est un peu plus complexe. Voyons comment ! - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -### Obtenir les résultats de base avec le pipeline - -Tout d'abord, prenons un pipeline de classification de *tokens* afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue une NER sur les phrases : - -```py -from transformers import pipeline - -token_classifier = pipeline("token-classification") -token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") -``` - -```python out -[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, - {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, - {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, - {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, - {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, - {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, - {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, - {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] -``` - -Le modèle a correctement identifié chaque *token* généré par « Sylvain » comme une personne, chaque *token* généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les *tokens* qui correspondent à la même entité : - -```py -from transformers import pipeline - -token_classifier = pipeline("token-classification", aggregation_strategy="simple") -token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") -``` - -```python out -[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, - {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, - {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] -``` - -La propriété `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée. Par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles : - -- `"first"`, où le score de chaque entité est le score du premier *token* de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`) -- `"max"`, où le score de chaque entité est le score maximal des *tokens* de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766). -- `"average"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Hugging Face" aurait un score de 0,9819, la moyenne des scores de « Hugging », 0,975, et « Face », 0,98879). - -Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipeline()` ! - -### Des entrées aux prédictions - -{#if fw === 'pt'} - -D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : - -```py -from transformers import AutoTokenizer, AutoModelForTokenClassification - -model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -model = AutoModelForTokenClassification.from_pretrained(model_checkpoint) - -example = "My name is Sylvain and I work at Hugging Face in Brooklyn." -inputs = tokenizer(example, return_tensors="pt") -outputs = model(**inputs) -``` - -Puisque nous utilisons `AutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : - -```py -print(inputs["input_ids"].shape) -print(outputs.logits.shape) -``` - -```python out -torch.Size([1, 19]) -torch.Size([1, 19, 9]) -``` - -{:else} - -D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : - -```py -from transformers import AutoTokenizer, TFAutoModelForTokenClassification - -model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint) - -example = "My name is Sylvain and I work at Hugging Face in Brooklyn." -inputs = tokenizer(example, return_tensors="tf") -outputs = model(**inputs) -``` - -Puisque nous utilisons `TFAutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : - -```py -print(inputs["input_ids"].shape) -print(outputs.logits.shape) -``` - -```python out -(1, 19) -(1, 19, 9) -``` - -{/if} - -Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes. Ainsi, la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits car la fonction softmax ne change pas l'ordre) : - -{#if fw === 'pt'} - -```py -import torch - -probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist() -predictions = outputs.logits.argmax(dim=-1)[0].tolist() -print(predictions) -``` - -{:else} - -```py -import tensorflow as tf - -probabilities = tf.math.softmax(outputs.logits, axis=-1)[0] -probabilities = probabilities.numpy().tolist() -predictions = tf.math.argmax(outputs.logits, axis=-1)[0] -predictions = predictions.numpy().tolist() -print(predictions) -``` - -{/if} - -```python out -[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] -``` - -L'attribut `model.config.id2label` contient la correspondance entre les index et les étiquettes que nous pouvons utiliser pour donner un sens aux prédictions : - -```py -model.config.id2label -``` - -```python out -{0: 'O', - 1: 'B-MISC', - 2: 'I-MISC', - 3: 'B-PER', - 4: 'I-PER', - 5: 'B-ORG', - 6: 'I-ORG', - 7: 'B-LOC', - 8: 'I-LOC'} -``` - -Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside* (en dehors)) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). - -Vous pourriez penser que le modèle s'est trompé ici car il a attribué l'étiquette `I-PER` à ces quatre *tokens* mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`. - -
-IOB1 vs IOB2 format - -
- -Nous sommes à présent prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `O` : - -```py -results = [] -tokens = inputs.tokens() - -for idx, pred in enumerate(predictions): - label = model.config.id2label[pred] - if label != "O": - results.append( - {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]} - ) - -print(results) -``` - -```python out -[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'}, - {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'}, - {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'}, - {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'}, - {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'}, - {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'}, - {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'}, - {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] -``` - -C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre *offset mapping* va entrer en jeu. Pour obtenir les *offsets*, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées : - -```py -inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) -inputs_with_offsets["offset_mapping"] -``` - -```python out -[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32), - (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] -``` - -Chaque *tuple* est l'étendue de texte correspondant à chaque *token* où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le *token* à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple : - - -```py -example[12:14] -``` - -nous obtenons le bon espace de texte sans le `##` : - -```python out -yl -``` - -En utilisant cela, nous pouvons maintenant compléter les résultats précédents : - -```py -results = [] -inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) -tokens = inputs_with_offsets.tokens() -offsets = inputs_with_offsets["offset_mapping"] - -for idx, pred in enumerate(predictions): - label = model.config.id2label[pred] - if label != "O": - start, end = offsets[idx] - results.append( - { - "entity": label, - "score": probabilities[idx][pred], - "word": tokens[idx], - "start": start, - "end": end, - } - ) - -print(results) -``` - -```python out -[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, - {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, - {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, - {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, - {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, - {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, - {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, - {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] -``` - -C'est la même chose que ce que nous avons obtenu avec le premier pipeline ! - -### Regroupement des entités - -L'utilisation des *offsets* pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les *offsets* nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou *Byte-Pair-Encoding* (voir plus loin dans ce chapitre). - -Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des *tokens* `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) : - -```py -example[33:45] -``` - -```python out -Hugging Face -``` - -Pour écrire le code qui post-traite les prédictions tout en regroupant les entités, nous regrouperons les entités qui sont consécutives et étiquetées avec `I-XXX`, à l'exception de la première, qui peut être étiquetée comme `B-XXX` ou `I-XXX` (ainsi, nous arrêtons de regrouper une entité lorsque nous obtenons un `O`, un nouveau type d'entité, ou un `B-XXX` qui nous indique qu'une entité du même type commence) : - -```py -import numpy as np - -results = [] -inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) -tokens = inputs_with_offsets.tokens() -offsets = inputs_with_offsets["offset_mapping"] - -idx = 0 -while idx < len(predictions): - pred = predictions[idx] - label = model.config.id2label[pred] - if label != "O": - # Enlever le B- ou le I- - label = label[2:] - start, _ = offsets[idx] - - # Récupérer tous les tokens étiquetés avec I-label - all_scores = [] - while ( - idx < len(predictions) - and model.config.id2label[predictions[idx]] == f"I-{label}" - ): - all_scores.append(probabilities[idx][pred]) - _, end = offsets[idx] - idx += 1 - - # Le score est la moyenne de tous les scores des tokens dans cette entité groupée - score = np.mean(all_scores).item() - word = example[start:end] - results.append( - { - "entity_group": label, - "score": score, - "word": word, - "start": start, - "end": end, - } - ) - idx += 1 - -print(results) -``` - -Et nous obtenons les mêmes résultats qu'avec notre deuxième pipeline ! - -```python out -[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, - {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, - {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] -``` - -Un autre exemple de tâche où ces *offsets* sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des *tokens* qui débordent lorsque nous tronquons une entrée à une longueur donnée. + + +# Pouvoirs spéciaux des tokenizers rapides + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans cette section, nous allons examiner de plus près les capacités des *tokenizers* dans 🤗 *Transformers*. +Jusqu'à présent, nous ne les avons utilisés que pour tokeniser les entrées ou décoder les identifiants pour revenir à du texte. Mais les *tokenizers*, et surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans le [chapitre 1](/course/fr/chapter1). + + + +Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et les « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les rapides sont ceux fournis par 🤗 *Tokenizers* et sont codés en Rust. Si vous vous souvenez du tableau du [chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données *Drug Review*, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents : + + | *Tokenizer* rapide | *Tokenizer* lent +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez beaucoup de textes en parallèle et en même temps que vous pourrez clairement voir la différence. + + + +## L'objet BatchEncoding + + + +La sortie d'un *tokenizer* n'est pas un simple dictionnaire Python. Ce que nous obtenons est en fait un objet spécial `BatchEncoding`. C'est une sous-classe d'un dictionnaire (c'est pourquoi nous avons pu indexer ce résultat sans problème auparavant), mais avec des méthodes supplémentaires qui sont principalement utilisées par les *tokenizers* rapides. + +En plus de leurs capacités de parallélisation, la fonctionnalité clé des *tokenizers* rapides est qu'ils gardent toujours la trace de l'étendue originale des textes d'où proviennent les *tokens* finaux, une fonctionnalité que nous appelons *mapping offset*. Cela permet de débloquer des fonctionnalités telles que le mappage de chaque mot aux *tokens* qu'il a générés ou le mappage de chaque caractère du texte original au *token* qu'il contient, et vice versa. + +Prenons un exemple : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +# Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn. +encoding = tokenizer(example) +print(type(encoding)) +``` + +Comme mentionné précédemment, nous obtenons un objet `BatchEncoding` dans la sortie du *tokenizer* : + +```python out + +``` + +Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* comme suit : + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +soit vérifier le même attribut mais avec notre `encoding` : + +```python +encoding.is_fast +``` + +```python out +True +``` + +Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les identifiants en *tokens* : + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +Dans ce cas, le *token* à l'index 5 est `##yl` et fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* : + +```py +encoding.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] +``` + +On peut voir que les *tokens* spéciaux du *tokenizer*, `[CLS]` et `[SEP]`, sont mis en correspondance avec `None` et que chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées et le POS (*Part-of-speech*). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_). + + + +La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I'll » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de prétokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots. + + + +✏️ **Essayez !** Créez un *tokenizer* à partir des checkpoints `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec. Qu'observez-vous ? Quels sont les identifiants des mots ? + + + +De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un *token* à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information). + +Enfin, nous pouvons faire correspondre n'importe quel mot ou *token* aux caractères du texte d'origine (et vice versa) grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci : + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le *tokenizer* rapide garde la trace de la partie du texte d'où provient chaque *token* dans une liste d'*offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`. + + + +✏️ **Essayez !** Rédigez votre propre texte et voyez si vous pouvez comprendre quels *tokens* sont associés à l'identifiant du mot et comment extraire les étendues de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants ont un sens pour vous. + + + +## A l'intérieur du pipeline `token-classification` + +Dans le [chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de la NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction `pipeline()` de 🤗 *Transformers*. Puis, dans le [chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline mais le post-traitement est un peu plus complexe. Voyons comment ! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Obtenir les résultats de base avec le pipeline + +Tout d'abord, prenons un pipeline de classification de *tokens* afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue une NER sur les phrases : + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Le modèle a correctement identifié chaque *token* généré par « Sylvain » comme une personne, chaque *token* généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les *tokens* qui correspondent à la même entité : + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification", aggregation_strategy="simple") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +La propriété `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée. Par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles : + +- `"first"`, où le score de chaque entité est le score du premier *token* de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`) +- `"max"`, où le score de chaque entité est le score maximal des *tokens* de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766). +- `"average"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Hugging Face" aurait un score de 0,9819, la moyenne des scores de « Hugging », 0,975, et « Face », 0,98879). + +Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipeline()` ! + +### Des entrées aux prédictions + +{#if fw === 'pt'} + +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : + +```py +from transformers import AutoTokenizer, AutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="pt") +outputs = model(**inputs) +``` + +Puisque nous utilisons `AutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : + +```py +from transformers import AutoTokenizer, TFAutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="tf") +outputs = model(**inputs) +``` + +Puisque nous utilisons `TFAutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes. Ainsi, la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits car la fonction softmax ne change pas l'ordre) : + +{#if fw === 'pt'} + +```py +import torch + +probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist() +predictions = outputs.logits.argmax(dim=-1)[0].tolist() +print(predictions) +``` + +{:else} + +```py +import tensorflow as tf + +probabilities = tf.math.softmax(outputs.logits, axis=-1)[0] +probabilities = probabilities.numpy().tolist() +predictions = tf.math.argmax(outputs.logits, axis=-1)[0] +predictions = predictions.numpy().tolist() +print(predictions) +``` + +{/if} + +```python out +[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] +``` + +L'attribut `model.config.id2label` contient la correspondance entre les index et les étiquettes que nous pouvons utiliser pour donner un sens aux prédictions : + +```py +model.config.id2label +``` + +```python out +{0: 'O', + 1: 'B-MISC', + 2: 'I-MISC', + 3: 'B-PER', + 4: 'I-PER', + 5: 'B-ORG', + 6: 'I-ORG', + 7: 'B-LOC', + 8: 'I-LOC'} +``` + +Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside* (en dehors)) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). + +Vous pourriez penser que le modèle s'est trompé ici car il a attribué l'étiquette `I-PER` à ces quatre *tokens* mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`. + +
+IOB1 vs IOB2 format + +
+ +Nous sommes à présent prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `O` : + +```py +results = [] +tokens = inputs.tokens() + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + results.append( + {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]} + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] +``` + +C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre *offset mapping* va entrer en jeu. Pour obtenir les *offsets*, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées : + +```py +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +inputs_with_offsets["offset_mapping"] +``` + +```python out +[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32), + (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] +``` + +Chaque *tuple* est l'étendue de texte correspondant à chaque *token* où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le *token* à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple : + + +```py +example[12:14] +``` + +nous obtenons le bon espace de texte sans le `##` : + +```python out +yl +``` + +En utilisant cela, nous pouvons maintenant compléter les résultats précédents : + +```py +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + start, end = offsets[idx] + results.append( + { + "entity": label, + "score": probabilities[idx][pred], + "word": tokens[idx], + "start": start, + "end": end, + } + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +C'est la même chose que ce que nous avons obtenu avec le premier pipeline ! + +### Regroupement des entités + +L'utilisation des *offsets* pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les *offsets* nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou *Byte-Pair-Encoding* (voir plus loin dans ce chapitre). + +Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des *tokens* `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) : + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Pour écrire le code qui post-traite les prédictions tout en regroupant les entités, nous regrouperons les entités qui sont consécutives et étiquetées avec `I-XXX`, à l'exception de la première, qui peut être étiquetée comme `B-XXX` ou `I-XXX` (ainsi, nous arrêtons de regrouper une entité lorsque nous obtenons un `O`, un nouveau type d'entité, ou un `B-XXX` qui nous indique qu'une entité du même type commence) : + +```py +import numpy as np + +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +idx = 0 +while idx < len(predictions): + pred = predictions[idx] + label = model.config.id2label[pred] + if label != "O": + # Enlever le B- ou le I- + label = label[2:] + start, _ = offsets[idx] + + # Récupérer tous les tokens étiquetés avec I-label + all_scores = [] + while ( + idx < len(predictions) + and model.config.id2label[predictions[idx]] == f"I-{label}" + ): + all_scores.append(probabilities[idx][pred]) + _, end = offsets[idx] + idx += 1 + + # Le score est la moyenne de tous les scores des tokens dans cette entité groupée + score = np.mean(all_scores).item() + word = example[start:end] + results.append( + { + "entity_group": label, + "score": score, + "word": word, + "start": start, + "end": end, + } + ) + idx += 1 + +print(results) +``` + +Et nous obtenons les mêmes résultats qu'avec notre deuxième pipeline ! + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Un autre exemple de tâche où ces *offsets* sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des *tokens* qui débordent lorsque nous tronquons une entrée à une longueur donnée. diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx index 3e177a9fb..5002f9b4e 100644 --- a/chapters/fr/chapter6/3b.mdx +++ b/chapters/fr/chapter6/3b.mdx @@ -1,720 +1,720 @@ - - -# Tokenizer rapide dans le pipeline de QA - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Nous allons maintenant nous plonger dans le pipeline de `question-answering` et voir comment exploiter les *offsets* pour extraire d'u ncontexte la réponse à la question posée. Nous verrons ensuite comment gérer les contextes très longs qui finissent par être tronqués. Vous pouvez sauter cette section si vous n'êtes pas intéressé par la tâche de réponse aux questions. - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -## Utilisation du pipeline de `question-answering` - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir une réponse à une question : - -```py -from transformers import pipeline - -question_answerer = pipeline("question-answering") -context = """ -🤗 Transformers is backed by the three most popular deep learning libraries - — Jax, PyTorch, and TensorFlow — with a seamless integration between them. -It's straightforward to train your models with one before loading them for inference with the other. -""" -# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires -# (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles. -# C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. -question = "Which deep learning libraries back 🤗 Transformers?" -# Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ? -question_answerer(question=question, context=context) -``` - -```python out -{'score': 0.97773, - 'start': 78, - 'end': 105, - 'answer': 'Jax, PyTorch and TensorFlow'} -``` - -Contrairement aux autres pipelines, qui ne peuvent pas tronquer et diviser les textes dont la longueur est supérieure à la longueur maximale acceptée par le modèle (et qui peuvent donc manquer des informations à la fin d'un document), ce pipeline peut traiter des contextes très longs et retournera la réponse à la question même si elle se trouve à la fin : - -```py -long_context = """ -🤗 Transformers: State of the Art NLP - -🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, -question answering, summarization, translation, text generation and more in over 100 languages. -Its aim is to make cutting-edge NLP easier to use for everyone. - -🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and -then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and -can be modified to enable quick research experiments. - -Why should I use transformers? - -1. Easy-to-use state-of-the-art models: - - High performance on NLU and NLG tasks. - - Low barrier to entry for educators and practitioners. - - Few user-facing abstractions with just three classes to learn. - - A unified API for using all our pretrained models. - - Lower compute costs, smaller carbon footprint: - -2. Researchers can share trained models instead of always retraining. - - Practitioners can reduce compute time and production costs. - - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. - -3. Choose the right framework for every part of a model's lifetime: - - Train state-of-the-art models in 3 lines of code. - - Move a single model between TF2.0/PyTorch frameworks at will. - - Seamlessly pick the right framework for training, evaluation and production. - -4. Easily customize a model or an example to your needs: - - We provide examples for each architecture to reproduce the results published by its original authors. - - Model internals are exposed as consistently as possible. - - Model files can be used independently of the library for quick experiments. - -🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration -between them. It's straightforward to train your models with one before loading them for inference with the other. -""" - -long_context - fr = """ -🤗 Transformers : l'état de l'art du NLP - -🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, -l'extraction d'informations, la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues. -Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tout le monde. - -🤗 Transformers fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. -puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. -peut être modifié pour permettre des expériences de recherche rapides. - -Pourquoi devrais-je utiliser des transformateurs ? - -1. Des modèles de pointe faciles à utiliser : - - Haute performance sur les tâches NLU et NLG. - - Faible barrière à l'entrée pour les éducateurs et les praticiens. - - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. - - Une API unifiée pour utiliser tous nos modèles pré-entraînés. - - Des coûts de calcul plus faibles, une empreinte carbone réduite : - -2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. - - Les praticiens peuvent réduire le temps de calcul et les coûts de production. - - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. - -3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : - - Entraînez des modèles de pointe en 3 lignes de code. - - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. - - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. - -4. Adaptez facilement un modèle ou un exemple à vos besoins : - - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. - - Les éléments internes des modèles sont exposés de manière aussi cohérente que possible. - - Les fichiers de modèles peuvent être utilisés indépendamment de la bibliothèque pour des expériences rapides. - -🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration parfaite -entre elles. Il est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. -""" -question_answerer(question=question, context=long_context) -``` - -```python out -{'score': 0.97149, - 'start': 1892, - 'end': 1919, - 'answer': 'Jax, PyTorch and TensorFlow'} -``` - -Voyons comment il fait tout cela ! - -### Utilisation d'un modèle pour répondre à des questions - -Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le *checkpoint* utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons davantage de ce jeu de données dans le [chapitre 7](/course/fr/chapter7/7)) : - -{#if fw === 'pt'} - -```py -from transformers import AutoTokenizer, AutoModelForQuestionAnswering - -model_checkpoint = "distilbert-base-cased-distilled-squad" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) - -inputs = tokenizer(question, context, return_tensors="pt") -outputs = model(**inputs) -``` - -{:else} - -```py -from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering - -model_checkpoint = "distilbert-base-cased-distilled-squad" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) - -inputs = tokenizer(question, context, return_tensors="tf") -outputs = model(**inputs) -``` - -{/if} - -Notez que nous tokenizons la question et le contexte comme une paire, la question en premier. - -
-An example of tokenization of question and context - -
- -Les modèles de réponse aux questions fonctionnent un peu différemment des modèles que nous avons vus jusqu'à présent. En utilisant l'image ci-dessus comme exemple, le modèle a été entraîné à prédire l'index du *token* de début de la réponse (ici 21) et l'index du *token* où la réponse se termine (ici 24). C'est pourquoi ces modèles ne retournent pas un tenseur de logits mais deux : un pour les logits correspondant au *token* de début de la réponse, et un pour les logits correspondant au *token* de fin de la réponse. Puisque dans ce cas nous n'avons qu'une seule entrée contenant 66 *tokens*, nous obtenons : - -```py -start_logits = outputs.start_logits -end_logits = outputs.end_logits -print(start_logits.shape, end_logits.shape) -``` - -{#if fw === 'pt'} - -```python out -torch.Size([1, 66]) torch.Size([1, 66]) -``` - -{:else} - -```python out -(1, 66) (1, 66) -``` - -{/if} - -Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]` donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]` car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte. - -Puisque nous appliquerons une fonction softmax par la suite, il nous suffit de remplacer les logits que nous voulons masquer par un grand nombre négatif. Ici, nous utilisons `-10000` : - -{#if fw === 'pt'} - -```py -import torch - -sequence_ids = inputs.sequence_ids() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le token [CLS] -mask[0] = False -mask = torch.tensor(mask)[None] - -start_logits[mask] = -10000 -end_logits[mask] = -10000 -``` - -{:else} - -```py -import tensorflow as tf - -sequence_ids = inputs.sequence_ids() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le token [CLS] -mask[0] = False -mask = tf.constant(mask)[None] - -start_logits = tf.where(mask, -10000, start_logits) -end_logits = tf.where(mask, -10000, end_logits) -``` - -{/if} - -Maintenant que nous avons correctement masqué les logits correspondant aux positions que nous ne voulons pas prédire, nous pouvons appliquer la softmax : - -{#if fw === 'pt'} - -```py -start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0] -end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0] -``` - -{:else} - -```py -start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy() -end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() -``` - -{/if} - -A ce stade, nous pourrions prendre l'argmax des probabilités de début et de fin mais nous pourrions nous retrouver avec un indice de début supérieur à l'indice de fin. Nous devons donc prendre quelques précautions supplémentaires. Nous allons calculer les probabilités de chaque `start_index` et `end_index` possible où `start_index<=end_index`, puis nous prendrons le *tuple* `(start_index, end_index)` avec la plus grande probabilité. - -En supposant que les événements « La réponse commence à `start_index` » et « La réponse se termine à `end_index` » sont indépendants, la probabilité que la réponse commence à `end_index` et se termine à `end_index` est : - -$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ - -Ainsi, pour calculer tous les scores, il suffit de calculer tous les produits \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) où `start_index <= end_index`. - -Calculons d'abord tous les produits possibles : - -```py -scores = start_probabilities[:, None] * end_probabilities[None, :] -``` - -{#if fw === 'pt'} - -Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `torch.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : - -```py -scores = torch.triu(scores) -``` - -{:else} - -Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `np.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : - -```py -scores = np.triu(scores) -``` - -{/if} - -Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retourne l'index dans le tenseur aplati, nous devons utiliser les opérations division `//` et modulo `%` pour obtenir le `start_index` et le `end_index` : - -```py -max_index = scores.argmax().item() -start_index = max_index // scores.shape[1] -end_index = max_index % scores.shape[1] -print(scores[start_index, end_index]) -``` - -Nous n'avons pas encore tout à fait terminé, mais au moins nous avons déjà le score correct pour la réponse (vous pouvez le vérifier en le comparant au premier résultat de la section précédente) : - -```python out -0.97773 -``` - - - -✏️ **Essayez !** Calculez les indices de début et de fin pour les cinq réponses les plus probables. - - - -Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*. Maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *tokens* : - -```py -inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) -offsets = inputs_with_offsets["offset_mapping"] - -start_char, _ = offsets[start_index] -_, end_char = offsets[end_index] -answer = context[start_char:end_char] -``` - -Il ne nous reste plus qu'à tout formater pour obtenir notre résultat : - -```py -result = { - "answer": answer, - "start": start_char, - "end": end_char, - "score": scores[start_index, end_index], -} -print(result) -``` - -```python out -{'answer': 'Jax, PyTorch and TensorFlow', - 'start': 78, - 'end': 105, - 'score': 0.97773} -``` - -Super ! C'est la même chose que dans notre premier exemple ! - - - -✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés précédemment pour afficher les cinq réponses les plus probables. Pour vérifier vos résultats, retournez au premier pipeline et passez dans `top_k=5` lorsque vous l'appelez. - - - -## Gestion des contextes longs - -Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé dans l'exemple précédemment, nous obtenons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) : - -```py -inputs = tokenizer(question, long_context) -print(len(inputs["input_ids"])) -``` - -```python out -461 -``` - -Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utilisons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente : - -```py -inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") -print(tokenizer.decode(inputs["input_ids"])) -``` - -```python out -""" -[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP - -[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, -question answering, summarization, translation, text generation and more in over 100 languages. -Its aim is to make cutting-edge NLP easier to use for everyone. - -[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and -then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and -can be modified to enable quick research experiments. - -Why should I use transformers? - -1. Easy-to-use state-of-the-art models: - - High performance on NLU and NLG tasks. - - Low barrier to entry for educators and practitioners. - - Few user-facing abstractions with just three classes to learn. - - A unified API for using all our pretrained models. - - Lower compute costs, smaller carbon footprint: - -2. Researchers can share trained models instead of always retraining. - - Practitioners can reduce compute time and production costs. - - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. - -3. Choose the right framework for every part of a model's lifetime: - - Train state-of-the-art models in 3 lines of code. - - Move a single model between TF2.0/PyTorch frameworks at will. - - Seamlessly pick the right framework for training, evaluation and production. - -4. Easily customize a model or an example to your needs: - - We provide examples for each architecture to reproduce the results published by its original authors. - - Model internal [SEP] -""" - -""" -[CLS] Quelles sont les bibliothèques d'apprentissage profond qui soutiennent [UNK] Transformers ? [SEP] [UNK] Transformers : l'état de l'art du NLP - -[UNK] Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, la réponse à des questions, le résumé, la traduction, la génération de textes, etc, -la réponse à des questions, le résumé, la traduction, la génération de texte et plus encore dans plus de 100 langues. -Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tous. - -Transformers [UNK] fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. -puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. -peut être modifié pour permettre des expériences de recherche rapides. - -Pourquoi devrais-je utiliser des transformateurs ? - -1. Des modèles de pointe faciles à utiliser : - - Haute performance sur les tâches NLU et NLG. - - Faible barrière à l'entrée pour les éducateurs et les praticiens. - - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. - - Une API unifiée pour utiliser tous nos modèles pré-entraînés. - - Des coûts de calcul plus faibles, une empreinte carbone réduite : - -2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. - - Les praticiens peuvent réduire le temps de calcul et les coûts de production. - - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. - -3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : - - Entraînez des modèles de pointe en 3 lignes de code. - - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. - - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. - -4. Adaptez facilement un modèle ou un exemple à vos besoins : - - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. - - Modèle interne [SEP] -""" -``` - -Cela signifie que le modèle a du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux. - -Nous pouvons demander au *tokenizer* (rapide ou lent) de le faire pour nous en ajoutant `return_overflowing_tokens=True`, et nous pouvons spécifier le chevauchement que nous voulons avec l'argument `stride`. Voici un exemple, en utilisant une phrase plus petite : - -```py -sentence = "This sentence is not too long but we are going to split it anyway." -# "Cette phrase n'est pas trop longue mais nous allons la diviser quand même." -inputs = tokenizer( - sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 -) - -for ids in inputs["input_ids"]: - print(tokenizer.decode(ids)) -``` - -```python out -'[CLS] This sentence is not [SEP]' -'[CLS] is not too long [SEP]' -'[CLS] too long but we [SEP]' -'[CLS] but we are going [SEP]' -'[CLS] are going to split [SEP]' -'[CLS] to split it anyway [SEP]' -'[CLS] it anyway. [SEP]' -``` - -Comme on peut le voir, la phrase a été découpée en morceaux de telle sorte que chaque entrée dans `inputs["input_ids"]` a au maximum 6 *tokens* (il faudrait ajouter du *padding* pour que la dernière entrée ait la même taille que les autres) et il y a un chevauchement de 2 *tokens* entre chacune des entrées. - -Regardons de plus près le résultat de la tokénisation : - -```py -print(inputs.keys()) -``` - -```python out -dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) -``` - -Comme prévu, nous obtenons les identifiants d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats. Ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* : - -```py -print(inputs["overflow_to_sample_mapping"]) -``` - -```python out -[0, 0, 0, 0, 0, 0, 0] -``` - -C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple : - -```py -sentences = [ - "This sentence is not too long but we are going to split it anyway.", - # Cette phrase n'est pas trop longue mais nous allons la diviser quand même. - "This sentence is shorter but will still get split.", - # Cette phrase est plus courte mais sera quand même divisée. -] -inputs = tokenizer( - sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 -) - -print(inputs["overflow_to_sample_mapping"]) -``` - -nous donne : - -```python out -[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] -``` - -ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment et que les 4 morceaux suivants proviennent de la deuxième phrase. - -Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384 et un *stride* de 128, qui correspondent à la façon dont le modèle a été *finetuné* (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur afin de pouvoir construire des tenseurs) ainsi que demander les *offsets* : - -```py -inputs = tokenizer( - question, - long_context, - stride=128, - max_length=384, - padding="longest", - truncation="only_second", - return_overflowing_tokens=True, - return_offsets_mapping=True, -) -``` - -Ces `inputs` contiendront les identifiants d'entrée, les masques d'attention que le modèle attend, ainsi que les *offsets* et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la correspondance puisqu'elle n'est pas utile ici) avant de le convertir en tenseur : - -{#if fw === 'pt'} - -```py -_ = inputs.pop("overflow_to_sample_mapping") -offsets = inputs.pop("offset_mapping") - -inputs = inputs.convert_to_tensors("pt") -print(inputs["input_ids"].shape) -``` - -```python out -torch.Size([2, 384]) -``` - -{:else} - -```py -_ = inputs.pop("overflow_to_sample_mapping") -offsets = inputs.pop("offset_mapping") - -inputs = inputs.convert_to_tensors("tf") -print(inputs["input_ids"].shape) -``` - -```python out -(2, 384) -``` - -{/if} - -Notre contexte long a été divisé en deux, ce qui signifie qu'après avoir traversé notre modèle, nous aurons deux ensembles de logits de début et de fin : - -```py -outputs = model(**inputs) - -start_logits = outputs.start_logits -end_logits = outputs.end_logits -print(start_logits.shape, end_logits.shape) -``` - -{#if fw === 'pt'} - -```python out -torch.Size([2, 384]) torch.Size([2, 384]) -``` - -{:else} - -```python out -(2, 384) (2, 384) -``` - -{/if} - -Comme précédemment, nous masquons d'abord les *tokens* qui ne font pas partie du contexte avant de prendre le softmax. Nous masquons également tous les *tokens* de *padding* (tels que signalés par le masque d'attention) : - -{#if fw === 'pt'} - -```py -sequence_ids = inputs.sequence_ids() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le jeton [CLS] -mask[0] = False -# Masquer tous les tokens [PAD] -mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) - -start_logits[mask] = -10000 -end_logits[mask] = -10000 -``` - -{:else} - -```py -sequence_ids = inputs.sequence_ids() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le jeton [CLS] -mask[0] = False -# Masquer tous les tokens [PAD] -mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) - -start_logits = tf.where(mask, -10000, start_logits) -end_logits = tf.where(mask, -10000, end_logits) -``` - -{/if} - -Ensuite, nous pouvons utiliser la fonction softmax pour convertir nos logits en probabilités : - -{#if fw === 'pt'} - -```py -start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1) -end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1) -``` - -{:else} - -```py -start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy() -end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() -``` - -{/if} - -L'étape suivante est similaire à ce que nous avons fait pour le petit contexte mais nous la répétons pour chacun de nos deux morceaux. Nous attribuons un score à tous les espaces de réponse possibles puis nous prenons l'espace ayant le meilleur score : - -{#if fw === 'pt'} - -```py -candidates = [] -for start_probs, end_probs in zip(start_probabilities, end_probabilities): - scores = start_probs[:, None] * end_probs[None, :] - idx = torch.triu(scores).argmax().item() - - start_idx = idx // scores.shape[0] - end_idx = idx % scores.shape[0] - score = scores[start_idx, end_idx].item() - candidates.append((start_idx, end_idx, score)) - -print(candidates) -``` - -{:else} - -```py -candidates = [] -for start_probs, end_probs in zip(start_probabilities, end_probabilities): - scores = start_probs[:, None] * end_probs[None, :] - idx = np.triu(scores).argmax().item() - - start_idx = idx // scores.shape[0] - end_idx = idx % scores.shape[0] - score = scores[start_idx, end_idx].item() - candidates.append((start_idx, end_idx, score)) - -print(candidates) -``` - -{/if} - -```python out -[(0, 18, 0.33867), (173, 184, 0.97149)] -``` - -Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant dans le fait que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau). - - - -✏️ **Essayez !** Adaptez le code ci-dessus pour renvoyer les scores et les étendues des cinq réponses les plus probables (au total, pas par morceau). - - - -Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets* avec une liste par morceau de texte : - -```py -for candidate, offset in zip(candidates, offsets): - start_token, end_token, score = candidate - start_char, _ = offset[start_token] - _, end_char = offset[end_token] - answer = long_context[start_char:end_char] - result = {"answer": answer, "start": start_char, "end": end_char, "score": score} - print(result) -``` - -```python out -{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867} -{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} -``` - -Si nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte ! - - - -✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et spécifiez `top_k=5` en argument en l'appelant. - - - + + +# Tokenizer rapide dans le pipeline de QA + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nous allons maintenant nous plonger dans le pipeline de `question-answering` et voir comment exploiter les *offsets* pour extraire d'u ncontexte la réponse à la question posée. Nous verrons ensuite comment gérer les contextes très longs qui finissent par être tronqués. Vous pouvez sauter cette section si vous n'êtes pas intéressé par la tâche de réponse aux questions. + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Utilisation du pipeline de `question-answering` + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir une réponse à une question : + +```py +from transformers import pipeline + +question_answerer = pipeline("question-answering") +context = """ +🤗 Transformers is backed by the three most popular deep learning libraries + — Jax, PyTorch, and TensorFlow — with a seamless integration between them. +It's straightforward to train your models with one before loading them for inference with the other. +""" +# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires +# (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles. +# C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. +question = "Which deep learning libraries back 🤗 Transformers?" +# Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ? +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.97773, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Contrairement aux autres pipelines, qui ne peuvent pas tronquer et diviser les textes dont la longueur est supérieure à la longueur maximale acceptée par le modèle (et qui peuvent donc manquer des informations à la fin d'un document), ce pipeline peut traiter des contextes très longs et retournera la réponse à la question même si elle se trouve à la fin : + +```py +long_context = """ +🤗 Transformers: State of the Art NLP + +🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internals are exposed as consistently as possible. + - Model files can be used independently of the library for quick experiments. + +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" + +long_context - fr = """ +🤗 Transformers : l'état de l'art du NLP + +🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, +l'extraction d'informations, la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues. +Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tout le monde. + +🤗 Transformers fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. +puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. +peut être modifié pour permettre des expériences de recherche rapides. + +Pourquoi devrais-je utiliser des transformateurs ? + +1. Des modèles de pointe faciles à utiliser : + - Haute performance sur les tâches NLU et NLG. + - Faible barrière à l'entrée pour les éducateurs et les praticiens. + - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. + - Une API unifiée pour utiliser tous nos modèles pré-entraînés. + - Des coûts de calcul plus faibles, une empreinte carbone réduite : + +2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. + - Les praticiens peuvent réduire le temps de calcul et les coûts de production. + - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. + +3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : + - Entraînez des modèles de pointe en 3 lignes de code. + - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. + - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. + +4. Adaptez facilement un modèle ou un exemple à vos besoins : + - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. + - Les éléments internes des modèles sont exposés de manière aussi cohérente que possible. + - Les fichiers de modèles peuvent être utilisés indépendamment de la bibliothèque pour des expériences rapides. + +🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration parfaite +entre elles. Il est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. +""" +question_answerer(question=question, context=long_context) +``` + +```python out +{'score': 0.97149, + 'start': 1892, + 'end': 1919, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Voyons comment il fait tout cela ! + +### Utilisation d'un modèle pour répondre à des questions + +Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le *checkpoint* utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons davantage de ce jeu de données dans le [chapitre 7](/course/fr/chapter7/7)) : + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="pt") +outputs = model(**inputs) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="tf") +outputs = model(**inputs) +``` + +{/if} + +Notez que nous tokenizons la question et le contexte comme une paire, la question en premier. + +
+An example of tokenization of question and context + +
+ +Les modèles de réponse aux questions fonctionnent un peu différemment des modèles que nous avons vus jusqu'à présent. En utilisant l'image ci-dessus comme exemple, le modèle a été entraîné à prédire l'index du *token* de début de la réponse (ici 21) et l'index du *token* où la réponse se termine (ici 24). C'est pourquoi ces modèles ne retournent pas un tenseur de logits mais deux : un pour les logits correspondant au *token* de début de la réponse, et un pour les logits correspondant au *token* de fin de la réponse. Puisque dans ce cas nous n'avons qu'une seule entrée contenant 66 *tokens*, nous obtenons : + +```py +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([1, 66]) torch.Size([1, 66]) +``` + +{:else} + +```python out +(1, 66) (1, 66) +``` + +{/if} + +Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]` donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]` car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte. + +Puisque nous appliquerons une fonction softmax par la suite, il nous suffit de remplacer les logits que nous voulons masquer par un grand nombre négatif. Ici, nous utilisons `-10000` : + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le token [CLS] +mask[0] = False +mask = torch.tensor(mask)[None] + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +import tensorflow as tf + +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le token [CLS] +mask[0] = False +mask = tf.constant(mask)[None] + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Maintenant que nous avons correctement masqué les logits correspondant aux positions que nous ne voulons pas prédire, nous pouvons appliquer la softmax : + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0] +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0] +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() +``` + +{/if} + +A ce stade, nous pourrions prendre l'argmax des probabilités de début et de fin mais nous pourrions nous retrouver avec un indice de début supérieur à l'indice de fin. Nous devons donc prendre quelques précautions supplémentaires. Nous allons calculer les probabilités de chaque `start_index` et `end_index` possible où `start_index<=end_index`, puis nous prendrons le *tuple* `(start_index, end_index)` avec la plus grande probabilité. + +En supposant que les événements « La réponse commence à `start_index` » et « La réponse se termine à `end_index` » sont indépendants, la probabilité que la réponse commence à `end_index` et se termine à `end_index` est : + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +Ainsi, pour calculer tous les scores, il suffit de calculer tous les produits \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) où `start_index <= end_index`. + +Calculons d'abord tous les produits possibles : + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `torch.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : + +```py +scores = torch.triu(scores) +``` + +{:else} + +Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `np.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : + +```py +scores = np.triu(scores) +``` + +{/if} + +Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retourne l'index dans le tenseur aplati, nous devons utiliser les opérations division `//` et modulo `%` pour obtenir le `start_index` et le `end_index` : + +```py +max_index = scores.argmax().item() +start_index = max_index // scores.shape[1] +end_index = max_index % scores.shape[1] +print(scores[start_index, end_index]) +``` + +Nous n'avons pas encore tout à fait terminé, mais au moins nous avons déjà le score correct pour la réponse (vous pouvez le vérifier en le comparant au premier résultat de la section précédente) : + +```python out +0.97773 +``` + + + +✏️ **Essayez !** Calculez les indices de début et de fin pour les cinq réponses les plus probables. + + + +Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*. Maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *tokens* : + +```py +inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) +offsets = inputs_with_offsets["offset_mapping"] + +start_char, _ = offsets[start_index] +_, end_char = offsets[end_index] +answer = context[start_char:end_char] +``` + +Il ne nous reste plus qu'à tout formater pour obtenir notre résultat : + +```py +result = { + "answer": answer, + "start": start_char, + "end": end_char, + "score": scores[start_index, end_index], +} +print(result) +``` + +```python out +{'answer': 'Jax, PyTorch and TensorFlow', + 'start': 78, + 'end': 105, + 'score': 0.97773} +``` + +Super ! C'est la même chose que dans notre premier exemple ! + + + +✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés précédemment pour afficher les cinq réponses les plus probables. Pour vérifier vos résultats, retournez au premier pipeline et passez dans `top_k=5` lorsque vous l'appelez. + + + +## Gestion des contextes longs + +Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé dans l'exemple précédemment, nous obtenons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) : + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utilisons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente : + +```py +inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") +print(tokenizer.decode(inputs["input_ids"])) +``` + +```python out +""" +[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP + +[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internal [SEP] +""" + +""" +[CLS] Quelles sont les bibliothèques d'apprentissage profond qui soutiennent [UNK] Transformers ? [SEP] [UNK] Transformers : l'état de l'art du NLP + +[UNK] Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, la réponse à des questions, le résumé, la traduction, la génération de textes, etc, +la réponse à des questions, le résumé, la traduction, la génération de texte et plus encore dans plus de 100 langues. +Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tous. + +Transformers [UNK] fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. +puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. +peut être modifié pour permettre des expériences de recherche rapides. + +Pourquoi devrais-je utiliser des transformateurs ? + +1. Des modèles de pointe faciles à utiliser : + - Haute performance sur les tâches NLU et NLG. + - Faible barrière à l'entrée pour les éducateurs et les praticiens. + - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. + - Une API unifiée pour utiliser tous nos modèles pré-entraînés. + - Des coûts de calcul plus faibles, une empreinte carbone réduite : + +2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. + - Les praticiens peuvent réduire le temps de calcul et les coûts de production. + - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. + +3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : + - Entraînez des modèles de pointe en 3 lignes de code. + - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. + - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. + +4. Adaptez facilement un modèle ou un exemple à vos besoins : + - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. + - Modèle interne [SEP] +""" +``` + +Cela signifie que le modèle a du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux. + +Nous pouvons demander au *tokenizer* (rapide ou lent) de le faire pour nous en ajoutant `return_overflowing_tokens=True`, et nous pouvons spécifier le chevauchement que nous voulons avec l'argument `stride`. Voici un exemple, en utilisant une phrase plus petite : + +```py +sentence = "This sentence is not too long but we are going to split it anyway." +# "Cette phrase n'est pas trop longue mais nous allons la diviser quand même." +inputs = tokenizer( + sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] This sentence is not [SEP]' +'[CLS] is not too long [SEP]' +'[CLS] too long but we [SEP]' +'[CLS] but we are going [SEP]' +'[CLS] are going to split [SEP]' +'[CLS] to split it anyway [SEP]' +'[CLS] it anyway. [SEP]' +``` + +Comme on peut le voir, la phrase a été découpée en morceaux de telle sorte que chaque entrée dans `inputs["input_ids"]` a au maximum 6 *tokens* (il faudrait ajouter du *padding* pour que la dernière entrée ait la même taille que les autres) et il y a un chevauchement de 2 *tokens* entre chacune des entrées. + +Regardons de plus près le résultat de la tokénisation : + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Comme prévu, nous obtenons les identifiants d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats. Ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* : + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple : + +```py +sentences = [ + "This sentence is not too long but we are going to split it anyway.", + # Cette phrase n'est pas trop longue mais nous allons la diviser quand même. + "This sentence is shorter but will still get split.", + # Cette phrase est plus courte mais sera quand même divisée. +] +inputs = tokenizer( + sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +print(inputs["overflow_to_sample_mapping"]) +``` + +nous donne : + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment et que les 4 morceaux suivants proviennent de la deuxième phrase. + +Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384 et un *stride* de 128, qui correspondent à la façon dont le modèle a été *finetuné* (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur afin de pouvoir construire des tenseurs) ainsi que demander les *offsets* : + +```py +inputs = tokenizer( + question, + long_context, + stride=128, + max_length=384, + padding="longest", + truncation="only_second", + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +``` + +Ces `inputs` contiendront les identifiants d'entrée, les masques d'attention que le modèle attend, ainsi que les *offsets* et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la correspondance puisqu'elle n'est pas utile ici) avant de le convertir en tenseur : + +{#if fw === 'pt'} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("pt") +print(inputs["input_ids"].shape) +``` + +```python out +torch.Size([2, 384]) +``` + +{:else} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("tf") +print(inputs["input_ids"].shape) +``` + +```python out +(2, 384) +``` + +{/if} + +Notre contexte long a été divisé en deux, ce qui signifie qu'après avoir traversé notre modèle, nous aurons deux ensembles de logits de début et de fin : + +```py +outputs = model(**inputs) + +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([2, 384]) torch.Size([2, 384]) +``` + +{:else} + +```python out +(2, 384) (2, 384) +``` + +{/if} + +Comme précédemment, nous masquons d'abord les *tokens* qui ne font pas partie du contexte avant de prendre le softmax. Nous masquons également tous les *tokens* de *padding* (tels que signalés par le masque d'attention) : + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le jeton [CLS] +mask[0] = False +# Masquer tous les tokens [PAD] +mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le jeton [CLS] +mask[0] = False +# Masquer tous les tokens [PAD] +mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Ensuite, nous pouvons utiliser la fonction softmax pour convertir nos logits en probabilités : + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1) +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1) +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() +``` + +{/if} + +L'étape suivante est similaire à ce que nous avons fait pour le petit contexte mais nous la répétons pour chacun de nos deux morceaux. Nous attribuons un score à tous les espaces de réponse possibles puis nous prenons l'espace ayant le meilleur score : + +{#if fw === 'pt'} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = torch.triu(scores).argmax().item() + + start_idx = idx // scores.shape[0] + end_idx = idx % scores.shape[0] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{:else} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = np.triu(scores).argmax().item() + + start_idx = idx // scores.shape[0] + end_idx = idx % scores.shape[0] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{/if} + +```python out +[(0, 18, 0.33867), (173, 184, 0.97149)] +``` + +Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant dans le fait que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau). + + + +✏️ **Essayez !** Adaptez le code ci-dessus pour renvoyer les scores et les étendues des cinq réponses les plus probables (au total, pas par morceau). + + + +Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets* avec une liste par morceau de texte : + +```py +for candidate, offset in zip(candidates, offsets): + start_token, end_token, score = candidate + start_char, _ = offset[start_token] + _, end_char = offset[end_token] + answer = long_context[start_char:end_char] + result = {"answer": answer, "start": start_char, "end": end_char, "score": score} + print(result) +``` + +```python out +{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867} +{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} +``` + +Si nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte ! + + + +✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et spécifiez `top_k=5` en argument en l'appelant. + + + Ceci conclut notre plongée en profondeur dans les capacités du *tokenizer*. Nous mettrons à nouveau tout cela en pratique dans le prochain chapitre, lorsque nous vous montrerons comment *finetuner* un modèle sur une série de tâches NLP courantes. \ No newline at end of file diff --git a/chapters/fr/chapter6/4.mdx b/chapters/fr/chapter6/4.mdx index 0d8ddd4ba..1438e7b6f 100644 --- a/chapters/fr/chapter6/4.mdx +++ b/chapters/fr/chapter6/4.mdx @@ -1,123 +1,123 @@ -# Normalisation et prétokenization - - - -Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation : - -
-The tokenization pipeline. - -
- -Avant de diviser un texte en sous-*tokens* (selon le modèle), le *tokenizer* effectue deux étapes : la _normalisation_ et la _prétokénisation_. - -## Normalisation - - - -L'étape de normalisation implique un nettoyage général, comme la suppression des espaces inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer. - -Le `tokenizer` de 🤗 *Transformers* possède un attribut appelé `backend_tokenizer` qui donne accès au *tokenizer* sous-jacent de la bibliothèque 🤗 *Tokenizers* : - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") -print(type(tokenizer.backend_tokenizer)) -``` - -```python out - -``` - -L'attribut `normalizer` de l'objet `tokenizer` possède une méthode `normalize_str()` que nous pouvons utiliser pour voir comment la normalisation est effectuée : - -```py -print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) -``` - -```python out -'hello how are u?' -``` - -Dans cet exemple, puisque nous avons choisi le *checkpoint* `bert-base-uncased`, la normalisation a mis le texte en minuscule et supprimé les accents. - - - -✏️ **Essayez !** Chargez un *tokenizer* depuis le *checkpoint* `bert-base-cased` et passez-lui le même exemple. Quelles sont les principales différences que vous pouvez voir entre les versions casée et non casée du *tokenizer* ? - - - -## Prétokenization - - - -Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de prétokénisation. Comme nous l'avons vu dans le [chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement. - -Pour voir comment un *tokenizer* rapide effectue la prétokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `tokenizer` : - -```py -tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") -``` - -```python out -[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] -``` - -Remarquez que le *tokenizer* garde déjà la trace des *offsets*, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte. - -Puisque nous utilisons le *tokenizer* de BERT, la prétokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* du GPT-2 : - -```py -tokenizer = AutoTokenizer.from_pretrained("gpt2") -tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") -``` - -Il séparera aussi sur les espaces et la ponctuation mais gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* : - -```python out -[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), - ('?', (19, 20))] -``` - -Notez également que, contrairement au *tokenizer* de BERT, ce *tokenizer* n'ignore pas les doubles espaces. - -Pour un dernier exemple, regardons le *tokenizer* du 5, qui est basé sur l'algorithme SentencePiece : - -```py -tokenizer = AutoTokenizer.from_pretrained("t5-small") -tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") -``` - -```python out -[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] -``` - -Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un *token* spécifique (`_`), mais le *tokenizer* du T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et il ignore le double espace entre `are` et `you`. - -Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece, puis au cours des trois sections suivantes nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots. - -## SentencePiece - -[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenisation pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode et il remplace les espaces par un caractère spécial : `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir la [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de prétokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais). - -L'autre caractéristique principale de SentencePiece est le *tokenisation réversible* : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible. - -## Vue d'ensemble des algorithmes - -Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation en sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous. - - -Modèle | BPE | WordPiece | Unigramme -:----:|:---:|:---------:|:------: -Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens* -Étape d'entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier -Apprend | A fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token* -Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement - -Maintenant, plongeons dans le BPE ! +# Normalisation et prétokenization + + + +Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation : + +
+The tokenization pipeline. + +
+ +Avant de diviser un texte en sous-*tokens* (selon le modèle), le *tokenizer* effectue deux étapes : la _normalisation_ et la _prétokénisation_. + +## Normalisation + + + +L'étape de normalisation implique un nettoyage général, comme la suppression des espaces inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer. + +Le `tokenizer` de 🤗 *Transformers* possède un attribut appelé `backend_tokenizer` qui donne accès au *tokenizer* sous-jacent de la bibliothèque 🤗 *Tokenizers* : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +L'attribut `normalizer` de l'objet `tokenizer` possède une méthode `normalize_str()` que nous pouvons utiliser pour voir comment la normalisation est effectuée : + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +Dans cet exemple, puisque nous avons choisi le *checkpoint* `bert-base-uncased`, la normalisation a mis le texte en minuscule et supprimé les accents. + + + +✏️ **Essayez !** Chargez un *tokenizer* depuis le *checkpoint* `bert-base-cased` et passez-lui le même exemple. Quelles sont les principales différences que vous pouvez voir entre les versions casée et non casée du *tokenizer* ? + + + +## Prétokenization + + + +Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de prétokénisation. Comme nous l'avons vu dans le [chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement. + +Pour voir comment un *tokenizer* rapide effectue la prétokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `tokenizer` : + +```py +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] +``` + +Remarquez que le *tokenizer* garde déjà la trace des *offsets*, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte. + +Puisque nous utilisons le *tokenizer* de BERT, la prétokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* du GPT-2 : + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +Il séparera aussi sur les espaces et la ponctuation mais gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* : + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +Notez également que, contrairement au *tokenizer* de BERT, ce *tokenizer* n'ignore pas les doubles espaces. + +Pour un dernier exemple, regardons le *tokenizer* du 5, qui est basé sur l'algorithme SentencePiece : + +```py +tokenizer = AutoTokenizer.from_pretrained("t5-small") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] +``` + +Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un *token* spécifique (`_`), mais le *tokenizer* du T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et il ignore le double espace entre `are` et `you`. + +Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece, puis au cours des trois sections suivantes nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots. + +## SentencePiece + +[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenisation pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode et il remplace les espaces par un caractère spécial : `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir la [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de prétokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais). + +L'autre caractéristique principale de SentencePiece est le *tokenisation réversible* : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible. + +## Vue d'ensemble des algorithmes + +Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation en sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous. + + +Modèle | BPE | WordPiece | Unigramme +:----:|:---:|:---------:|:------: +Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens* +Étape d'entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier +Apprend | A fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token* +Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement + +Maintenant, plongeons dans le BPE ! diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx index 410d75af8..ee2ad8a52 100644 --- a/chapters/fr/chapter6/5.mdx +++ b/chapters/fr/chapter6/5.mdx @@ -1,364 +1,364 @@ -# Tokénisation Byte-Pair Encoding - - - -Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa. - - - - - -💡 Cette section couvre le BPE en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokenisation. - - - -## Algorithme d'entraînement - -L'entraînement du BPE commence par le calcul de l'unique ensemble de mots utilisés dans le corpus (après les étapes de normalisation et de prétokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple, disons que notre corpus utilise ces cinq mots : - -``` -"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins" -``` - -Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, le vocabulaire de base contient au moins tous les caractères ASCII et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère est converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont par exemple très mauvais dans l'analyse de contenus contenant des emojis. - - - -Les *tokenizers* du GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256) et tous les caractères auxquels vous pouvez penser seront inclus dedans et ne finiront pas par être convertis en un *token* inconnu. Cette astuce est appelée *byte-level BPE*. - - - -Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les fusions qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis au fur et à mesure de l'entraînement, des sous-mots plus longs. - -À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par « paire », nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée. Nous rinçons et répétons pour l'étape suivante. - -Pour revenir à notre exemple précédent, supposons que les mots ont les fréquences suivantes : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* : - -``` -("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) -``` - -Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est cependant pas la paire la plus fréquente. Cet honneur revient à `("u", "g")` qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un total de 20 fois dans le vocabulaire. - -Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` est ajouté au vocabulaire et que la paire doit être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci : - -``` -Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"] -Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) -``` - -Nous avons maintenant quelques paires qui aboutissent à un *token* de plus de deux caractères. Par exemple la paire `("h", "ug")` présente 15 fois dans le corpus. La paire la plus fréquente à ce stade est `("u", "n")`, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. En ajoutant cela au vocabulaire et en fusionnant toutes les occurrences existantes, nous obtenons : - -``` -Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] -Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) -``` - -Maintenant la paire la plus fréquente est `("h", "ug")` donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`. Cela nous donne donc notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci : - -``` -Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] -Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) -``` - -Et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. - - - -✏️ **A votre tour !** A votre avis, quelle sera la prochaine règle de fusion ? - - - -## Algorithme de tokenisation - -La tokenisation suit de près le processus d'entraînement, dans le sens où les nouvelles entrées sont tokenisées en appliquant les étapes suivantes : - -1. Normalisation -2. Prétokénisation -3. Découpage des mots en caractères individuels -4. Application des règles de fusion apprises dans l'ordre sur ces divisions. - -Prenons l'exemple que nous avons utilisé pendant l'entraînement, avec les trois règles de fusion apprises : - -``` -("u", "g") -> "ug" -("u", "n") -> "un" -("h", "ug") -> "hug" -``` - -Le mot « bug » sera traduit par « ["b", "ug"] ». Par contre, le mot « mug » (tasse en français) sera traduit par « ["[UNK]", "ug"] » puisque la lettre « m » ne fait pas partie du vocabulaire de base. De la même façon, le mot « thug » (voyou en français) sera tokenisé en « ["[UNK]", "hug"] » car la lettre « t » n'est pas dans le vocabulaire de base et l'application des règles de fusion résulte d'abord en la fusion de « u » et « g » et ensuite en la fusion de « hu » et « g ». - - - -✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenized ? - - - -## Implémentation du BPE - -Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus. Nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme. - -Tout d'abord, nous avons besoin d'un corpus, alors créons un corpus simple avec quelques phrases : - -```python -corpus = [ - "This is the Hugging Face Course.", - # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", - # Ce chapitre traite de la tokenisation. - "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de tokenizer. - "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. -] -``` - -Ensuite, nous devons prétokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme celui du GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la prétokénisation : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("gpt2") -``` - -Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : - -```python -from collections import defaultdict - -word_freqs = defaultdict(int) - -for text in corpus: - words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) - new_words = [word for word, offset in words_with_offsets] - for word in new_words: - word_freqs[word] += 1 - -print(word_freqs) -``` - -```python out -defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1, - 'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1, - 'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1, - 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) -``` - -L'étape suivante consiste à calculer le vocabulaire de base, formé par tous les caractères utilisés dans le corpus : - -```python -alphabet = [] - -for word in word_freqs.keys(): - for letter in word: - if letter not in alphabet: - alphabet.append(letter) -alphabet.sort() - -print(alphabet) -``` - -```python out -[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', - 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] -``` - -Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas du GPT-2, le seul *token* spécial est `"<|endoftext|>"` : - -```python -vocab = ["<|endoftext|>"] + alphabet.copy() -``` - -Nous devons maintenant diviser chaque mot en caractères individuels pour pouvoir commencer l'entraînement : - -```python -splits = {word: [c for c in word] for word in word_freqs.keys()} -``` - -Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule la fréquence de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : - -```python -def compute_pair_freqs(splits): - pair_freqs = defaultdict(int) - for word, freq in word_freqs.items(): - split = splits[word] - if len(split) == 1: - continue - for i in range(len(split) - 1): - pair = (split[i], split[i + 1]) - pair_freqs[pair] += freq - return pair_freqs -``` - -Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : - -```python -pair_freqs = compute_pair_freqs(splits) - -for i, key in enumerate(pair_freqs.keys()): - print(f"{key}: {pair_freqs[key]}") - if i >= 5: - break -``` - -```python out -('T', 'h'): 3 -('h', 'i'): 3 -('i', 's'): 5 -('Ġ', 'i'): 2 -('Ġ', 't'): 7 -('t', 'h'): 3 -``` - -Maintenant, trouver la paire la plus fréquente ne demande qu'une rapide boucle : - -```python -best_pair = "" -max_freq = None - -for pair, freq in pair_freqs.items(): - if max_freq is None or max_freq < freq: - best_pair = pair - max_freq = freq - -print(best_pair, max_freq) -``` - -```python out -('Ġ', 't') 7 -``` - -Donc la première fusion à apprendre est `('Ġ', 't') -> 'Ġt'`, et on ajoute `'Ġt'` au vocabulaire : - -```python -merges = {("Ġ", "t"): "Ġt"} -vocab.append("Ġt") -``` - -Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : - -```python -def merge_pair(a, b, splits): - for word in word_freqs: - split = splits[word] - if len(split) == 1: - continue - - i = 0 - while i < len(split) - 1: - if split[i] == a and split[i + 1] == b: - split = split[:i] + [a + b] + split[i + 2 :] - else: - i += 1 - splits[word] = split - return splits -``` - -Et nous pouvons regarder le résultat de la première fusion : - -```py -splits = merge_pair("Ġ", "t", splits) -print(splits["Ġtrained"]) -``` - -```python out -['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] -``` - -Maintenant, nous avons tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire de 50 : - -```python -vocab_size = 50 - -while len(vocab) < vocab_size: - pair_freqs = compute_pair_freqs(splits) - best_pair = "" - max_freq = None - for pair, freq in pair_freqs.items(): - if max_freq is None or max_freq < freq: - best_pair = pair - max_freq = freq - splits = merge_pair(*best_pair, splits) - merges[best_pair] = best_pair[0] + best_pair[1] - vocab.append(best_pair[0] + best_pair[1]) -``` - -En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet plus le *token* spécial) : - -```py -print(merges) -``` - -```python out -{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en', - ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok', - ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe', - ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} -``` - -Et le vocabulaire est composé du *token* spécial, de l'alphabet initial, et de tous les résultats des fusions : - -```py -print(vocab) -``` - -```python out -['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', - 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se', - 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni'] -``` - - - -💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que lorsqu'il y a un choix de la paire la plus fréquente, nous avons sélectionné la première rencontrée, alors que la bibliothèque 🤗 *Tokenizers* sélectionne la première en fonction de ses identifiants internes. - - - -Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique toutes les règles de fusion apprises : - -```python -def tokenize(text): - pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) - pre_tokenized_text = [word for word, offset in pre_tokenize_result] - splits = [[l for l in word] for word in pre_tokenized_text] - for pair, merge in merges.items(): - for idx, split in enumerate(splits): - i = 0 - while i < len(split) - 1: - if split[i] == pair[0] and split[i + 1] == pair[1]: - split = split[:i] + [merge] + split[i + 2 :] - else: - i += 1 - splits[idx] = split - - return sum(splits, []) -``` - -Nous pouvons essayer cela sur n'importe quel texte composé de caractères de l'alphabet : - -```py -tokenize("This is not a token.") -``` - -```python out -['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] -``` - - - -⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de token inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet) mais cela pourrait arriver ici car nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé ces détails de côté. - - - +# Tokénisation Byte-Pair Encoding + + + +Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa. + + + + + +💡 Cette section couvre le BPE en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokenisation. + + + +## Algorithme d'entraînement + +L'entraînement du BPE commence par le calcul de l'unique ensemble de mots utilisés dans le corpus (après les étapes de normalisation et de prétokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple, disons que notre corpus utilise ces cinq mots : + +``` +"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins" +``` + +Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, le vocabulaire de base contient au moins tous les caractères ASCII et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère est converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont par exemple très mauvais dans l'analyse de contenus contenant des emojis. + + + +Les *tokenizers* du GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256) et tous les caractères auxquels vous pouvez penser seront inclus dedans et ne finiront pas par être convertis en un *token* inconnu. Cette astuce est appelée *byte-level BPE*. + + + +Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les fusions qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis au fur et à mesure de l'entraînement, des sous-mots plus longs. + +À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par « paire », nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée. Nous rinçons et répétons pour l'étape suivante. + +Pour revenir à notre exemple précédent, supposons que les mots ont les fréquences suivantes : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* : + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est cependant pas la paire la plus fréquente. Cet honneur revient à `("u", "g")` qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un total de 20 fois dans le vocabulaire. + +Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` est ajouté au vocabulaire et que la paire doit être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci : + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) +``` + +Nous avons maintenant quelques paires qui aboutissent à un *token* de plus de deux caractères. Par exemple la paire `("h", "ug")` présente 15 fois dans le corpus. La paire la plus fréquente à ce stade est `("u", "n")`, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. En ajoutant cela au vocabulaire et en fusionnant toutes les occurrences existantes, nous obtenons : + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) +``` + +Maintenant la paire la plus fréquente est `("h", "ug")` donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`. Cela nous donne donc notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci : + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] +Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) +``` + +Et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. + + + +✏️ **A votre tour !** A votre avis, quelle sera la prochaine règle de fusion ? + + + +## Algorithme de tokenisation + +La tokenisation suit de près le processus d'entraînement, dans le sens où les nouvelles entrées sont tokenisées en appliquant les étapes suivantes : + +1. Normalisation +2. Prétokénisation +3. Découpage des mots en caractères individuels +4. Application des règles de fusion apprises dans l'ordre sur ces divisions. + +Prenons l'exemple que nous avons utilisé pendant l'entraînement, avec les trois règles de fusion apprises : + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +Le mot « bug » sera traduit par « ["b", "ug"] ». Par contre, le mot « mug » (tasse en français) sera traduit par « ["[UNK]", "ug"] » puisque la lettre « m » ne fait pas partie du vocabulaire de base. De la même façon, le mot « thug » (voyou en français) sera tokenisé en « ["[UNK]", "hug"] » car la lettre « t » n'est pas dans le vocabulaire de base et l'application des règles de fusion résulte d'abord en la fusion de « u » et « g » et ensuite en la fusion de « hu » et « g ». + + + +✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenized ? + + + +## Implémentation du BPE + +Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus. Nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme. + +Tout d'abord, nous avons besoin d'un corpus, alors créons un corpus simple avec quelques phrases : + +```python +corpus = [ + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # Ce chapitre traite de la tokenisation. + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de tokenizer. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. +] +``` + +Ensuite, nous devons prétokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme celui du GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la prétokénisation : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) + +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +print(word_freqs) +``` + +```python out +defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1, + 'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1, + 'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1, + 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) +``` + +L'étape suivante consiste à calculer le vocabulaire de base, formé par tous les caractères utilisés dans le corpus : + +```python +alphabet = [] + +for word in word_freqs.keys(): + for letter in word: + if letter not in alphabet: + alphabet.append(letter) +alphabet.sort() + +print(alphabet) +``` + +```python out +[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', + 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] +``` + +Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas du GPT-2, le seul *token* spécial est `"<|endoftext|>"` : + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Nous devons maintenant diviser chaque mot en caractères individuels pour pouvoir commencer l'entraînement : + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule la fréquence de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : + +```python +def compute_pair_freqs(splits): + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + pair_freqs[pair] += freq + return pair_freqs +``` + +Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : + +```python +pair_freqs = compute_pair_freqs(splits) + +for i, key in enumerate(pair_freqs.keys()): + print(f"{key}: {pair_freqs[key]}") + if i >= 5: + break +``` + +```python out +('T', 'h'): 3 +('h', 'i'): 3 +('i', 's'): 5 +('Ġ', 'i'): 2 +('Ġ', 't'): 7 +('t', 'h'): 3 +``` + +Maintenant, trouver la paire la plus fréquente ne demande qu'une rapide boucle : + +```python +best_pair = "" +max_freq = None + +for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + +print(best_pair, max_freq) +``` + +```python out +('Ġ', 't') 7 +``` + +Donc la première fusion à apprendre est `('Ġ', 't') -> 'Ġt'`, et on ajoute `'Ġt'` au vocabulaire : + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + split = split[:i] + [a + b] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Et nous pouvons regarder le résultat de la première fusion : + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Maintenant, nous avons tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire de 50 : + +```python +vocab_size = 50 + +while len(vocab) < vocab_size: + pair_freqs = compute_pair_freqs(splits) + best_pair = "" + max_freq = None + for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + splits = merge_pair(*best_pair, splits) + merges[best_pair] = best_pair[0] + best_pair[1] + vocab.append(best_pair[0] + best_pair[1]) +``` + +En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet plus le *token* spécial) : + +```py +print(merges) +``` + +```python out +{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en', + ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok', + ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe', + ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} +``` + +Et le vocabulaire est composé du *token* spécial, de l'alphabet initial, et de tous les résultats des fusions : + +```py +print(vocab) +``` + +```python out +['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', + 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se', + 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni'] +``` + + + +💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que lorsqu'il y a un choix de la paire la plus fréquente, nous avons sélectionné la première rencontrée, alors que la bibliothèque 🤗 *Tokenizers* sélectionne la première en fonction de ses identifiants internes. + + + +Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique toutes les règles de fusion apprises : + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + splits = [[l for l in word] for word in pre_tokenized_text] + for pair, merge in merges.items(): + for idx, split in enumerate(splits): + i = 0 + while i < len(split) - 1: + if split[i] == pair[0] and split[i + 1] == pair[1]: + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[idx] = split + + return sum(splits, []) +``` + +Nous pouvons essayer cela sur n'importe quel texte composé de caractères de l'alphabet : + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de token inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet) mais cela pourrait arriver ici car nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé ces détails de côté. + + + C'est tout pour l'algorithme BPE ! Nous allons nous intéresser à WordPiece dans la suite. \ No newline at end of file diff --git a/chapters/fr/chapter6/6.mdx b/chapters/fr/chapter6/6.mdx index e854edde1..44206d994 100644 --- a/chapters/fr/chapter6/6.mdx +++ b/chapters/fr/chapter6/6.mdx @@ -1,378 +1,378 @@ -# Tokénisation WordPiece - - - -*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment. - - - - - -💡 Cette section couvre le WordPiece en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. - - - -## Algorithme d'entraînement - - - -⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement du WordPiece. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %. - - - -Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi par exemple, `"word"` est divisé comme ceci : - -``` -w ##o ##r ##d -``` - -Ainsi, l'alphabet initial contient tous les caractères présents au début d'un mot et les caractères présents à l'intérieur d'un mot précédé du préfixe de *WordPiece*. - -Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire en utilisant la formule suivante : - -$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ - -En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un batch d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot `"hugging"` apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement. - -Examinons le même vocabulaire que celui utilisé dans l'exemple d'entraînement du BPE : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -Les divisions ici seront : - -``` -("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) -``` - -Si on oublie les *tokens* spéciaux pour l'instant, le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]`. La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36). Ainsi le meilleur score va à la paire `("##g", "##s")` qui est la seule sans un `"##u"` avec un score 1 / 20. Et la première fusion apprise est `("##g", "##s") -> ("##gs")`. - -Notez que lorsque nous fusionnons, nous enlevons le `##` entre les deux *tokens*, donc nous ajoutons `"##gs"` au vocabulaire et appliquons la fusion dans les mots du corpus : - -``` -Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] -Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) -``` - -À ce stade, `" ##u "` est dans toutes les paires possibles, donc elles finissent toutes par avoir le même score. Disons que dans ce cas, la première paire est fusionnée, donc `("h", "##u") -> "hu"`. Cela nous amène à : - -``` -Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] -Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) -``` - -Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires). Ainsi la première paire avec le plus grand score est fusionnée : - -``` -Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] -Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) -``` - -et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. - - - -✏️ **A votre tour !** Quelle sera la prochaine règle de fusion ? - - - -## Algorithme de tokenisation - -La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final et non pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`. Donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`. - -Avec BPE, nous aurions appliqué les fusions apprises dans l'ordre et la tokénisation aurait été `["hu", "##gs"]`, l'encodage est donc différent. - -Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`. - -Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu. Par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme `"bum"` (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` " et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec le BPE qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus. - - - -✏️ **A votre tour !** Comment le mot `"pugs"` sera-t-il tokenisé ? - - - -## Implémentation de WordPiece - -Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique et vous ne pourrez pas l'utiliser sur un grand corpus. - -Nous utiliserons le même corpus que dans l'exemple BPE : - -```python -corpus = [ - "This is the Hugging Face Course.", - # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", - # This chapter is about tokenization - "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de tokenizer. - "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. -] -``` - -Tout d'abord, nous devons prétokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la prétokénisation : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : - -```python -from collections import defaultdict - -word_freqs = defaultdict(int) -for text in corpus: - words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) - new_words = [word for word, offset in words_with_offsets] - for word in new_words: - word_freqs[word] += 1 - -word_freqs -``` - -```python out -defaultdict( - int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1, - 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, - ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, - 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) -``` - -Comme nous l'avons vu précédemment, l'alphabet est l'unique ensemble composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` : - -```python -alphabet = [] -for word in word_freqs.keys(): - if word[0] not in alphabet: - alphabet.append(word[0]) - for letter in word[1:]: - if f"##{letter}" not in alphabet: - alphabet.append(f"##{letter}") - -alphabet.sort() -alphabet - -print(alphabet) -``` - -```python out -['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', - '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', - 'w', 'y'] -``` - -Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : - -```python -vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() -``` - -Ensuite, nous devons diviser chaque mot, avec toutes les lettres qui ne sont pas les premières préfixées par `##` : - -```python -splits = { - word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] - for word in word_freqs.keys() -} -``` - -Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule le score de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : - -```python -def compute_pair_scores(splits): - letter_freqs = defaultdict(int) - pair_freqs = defaultdict(int) - for word, freq in word_freqs.items(): - split = splits[word] - if len(split) == 1: - letter_freqs[split[0]] += freq - continue - for i in range(len(split) - 1): - pair = (split[i], split[i + 1]) - letter_freqs[split[i]] += freq - pair_freqs[pair] += freq - letter_freqs[split[-1]] += freq - - scores = { - pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) - for pair, freq in pair_freqs.items() - } - return scores -``` - -Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : - -```python -pair_scores = compute_pair_scores(splits) -for i, key in enumerate(pair_scores.keys()): - print(f"{key}: {pair_scores[key]}") - if i >= 5: - break -``` - -```python out -('T', '##h'): 0.125 -('##h', '##i'): 0.03409090909090909 -('##i', '##s'): 0.02727272727272727 -('i', '##s'): 0.1 -('t', '##h'): 0.03571428571428571 -('##h', '##e'): 0.011904761904761904 -``` - -Maintenant, trouver la paire avec le meilleur score ne prend qu'une rapide boucle : - -```python -best_pair = "" -max_score = None -for pair, score in pair_scores.items(): - if max_score is None or max_score < score: - best_pair = pair - max_score = score - -print(best_pair, max_score) -``` - -```python out -('a', '##b') 0.2 -``` - -Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'` et nous ajoutons `'ab'` au vocabulaire : - -```python -vocab.append("ab") -``` - -Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : - -```python -def merge_pair(a, b, splits): - for word in word_freqs: - split = splits[word] - if len(split) == 1: - continue - i = 0 - while i < len(split) - 1: - if split[i] == a and split[i + 1] == b: - merge = a + b[2:] if b.startswith("##") else a + b - split = split[:i] + [merge] + split[i + 2 :] - else: - i += 1 - splits[word] = split - return splits -``` - -Et nous pouvons regarder le résultat de la première fusion : - -```py -splits = merge_pair("a", "##b", splits) -splits["about"] -``` - -```python out -['ab', '##o', '##u', '##t'] -``` - -Nous avons maintenant tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire de 70 : - -```python -vocab_size = 70 -while len(vocab) < vocab_size: - scores = compute_pair_scores(splits) - best_pair, max_score = "", None - for pair, score in scores.items(): - if max_score is None or max_score < score: - best_pair = pair - max_score = score - splits = merge_pair(*best_pair, splits) - new_token = ( - best_pair[0] + best_pair[1][2:] - if best_pair[1].startswith("##") - else best_pair[0] + best_pair[1] - ) - vocab.append(new_token) -``` - -Nous pouvons ensuite examiner le vocabulaire généré : - -```py -print(vocab) -``` - -```python out -['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', - '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', - 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', - 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', - '##ut'] -``` - -Comme nous pouvons le voir, comparé à BPE, ce *tokenizer* apprend les parties de mots comme des *tokens* un peu plus rapidement. - - - -💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de son fonctionnement interne), mais utilise le BPE à la place. - - - -Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons. Puis nous répétons le processus sur la deuxième partie et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte : - -```python -def encode_word(word): - tokens = [] - while len(word) > 0: - i = len(word) - while i > 0 and word[:i] not in vocab: - i -= 1 - if i == 0: - return ["[UNK]"] - tokens.append(word[:i]) - word = word[i:] - if len(word) > 0: - word = f"##{word}" - return tokens -``` - -Testons-le sur un mot qui fait partie du vocabulaire, et un autre qui n'en fait pas partie : - -```python -print(encode_word("Hugging")) -print(encode_word("HOgging")) -``` - -```python out -['Hugg', '##i', '##n', '##g'] -['[UNK]'] -``` - -Maintenant, écrivons une fonction qui permet de tokeniser un texte : - -```python -def tokenize(text): - pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) - pre_tokenized_text = [word for word, offset in pre_tokenize_result] - encoded_words = [encode_word(word) for word in pre_tokenized_text] - return sum(encoded_words, []) -``` - -On peut l'essayer sur n'importe quel texte : - -```python -tokenize("This is the Hugging Face Course!") # C'est le cours d'Hugging Face -``` - -```python out -['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', - '##e', '[UNK]'] -``` - -C'est tout pour l'algorithme *WordPiece* ! Maintenant, jetons un coup d'oeil à *Unigram*. +# Tokénisation WordPiece + + + +*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment. + + + + + +💡 Cette section couvre le WordPiece en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. + + + +## Algorithme d'entraînement + + + +⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement du WordPiece. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %. + + + +Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi par exemple, `"word"` est divisé comme ceci : + +``` +w ##o ##r ##d +``` + +Ainsi, l'alphabet initial contient tous les caractères présents au début d'un mot et les caractères présents à l'intérieur d'un mot précédé du préfixe de *WordPiece*. + +Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire en utilisant la formule suivante : + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un batch d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot `"hugging"` apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement. + +Examinons le même vocabulaire que celui utilisé dans l'exemple d'entraînement du BPE : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Les divisions ici seront : + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +Si on oublie les *tokens* spéciaux pour l'instant, le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]`. La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36). Ainsi le meilleur score va à la paire `("##g", "##s")` qui est la seule sans un `"##u"` avec un score 1 / 20. Et la première fusion apprise est `("##g", "##s") -> ("##gs")`. + +Notez que lorsque nous fusionnons, nous enlevons le `##` entre les deux *tokens*, donc nous ajoutons `"##gs"` au vocabulaire et appliquons la fusion dans les mots du corpus : + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] +Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) +``` + +À ce stade, `" ##u "` est dans toutes les paires possibles, donc elles finissent toutes par avoir le même score. Disons que dans ce cas, la première paire est fusionnée, donc `("h", "##u") -> "hu"`. Cela nous amène à : + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] +Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires). Ainsi la première paire avec le plus grand score est fusionnée : + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] +Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. + + + +✏️ **A votre tour !** Quelle sera la prochaine règle de fusion ? + + + +## Algorithme de tokenisation + +La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final et non pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`. Donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`. + +Avec BPE, nous aurions appliqué les fusions apprises dans l'ordre et la tokénisation aurait été `["hu", "##gs"]`, l'encodage est donc différent. + +Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`. + +Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu. Par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme `"bum"` (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` " et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec le BPE qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus. + + + +✏️ **A votre tour !** Comment le mot `"pugs"` sera-t-il tokenisé ? + + + +## Implémentation de WordPiece + +Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique et vous ne pourrez pas l'utiliser sur un grand corpus. + +Nous utiliserons le même corpus que dans l'exemple BPE : + +```python +corpus = [ + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # This chapter is about tokenization + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de tokenizer. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. +] +``` + +Tout d'abord, nous devons prétokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la prétokénisation : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +```python out +defaultdict( + int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1, + 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, + ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, + 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) +``` + +Comme nous l'avons vu précédemment, l'alphabet est l'unique ensemble composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` : + +```python +alphabet = [] +for word in word_freqs.keys(): + if word[0] not in alphabet: + alphabet.append(word[0]) + for letter in word[1:]: + if f"##{letter}" not in alphabet: + alphabet.append(f"##{letter}") + +alphabet.sort() +alphabet + +print(alphabet) +``` + +```python out +['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', + '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', + 'w', 'y'] +``` + +Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Ensuite, nous devons diviser chaque mot, avec toutes les lettres qui ne sont pas les premières préfixées par `##` : + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule le score de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : + +```python +def compute_pair_scores(splits): + letter_freqs = defaultdict(int) + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + letter_freqs[split[0]] += freq + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + letter_freqs[split[i]] += freq + pair_freqs[pair] += freq + letter_freqs[split[-1]] += freq + + scores = { + pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) + for pair, freq in pair_freqs.items() + } + return scores +``` + +Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : + +```python +pair_scores = compute_pair_scores(splits) +for i, key in enumerate(pair_scores.keys()): + print(f"{key}: {pair_scores[key]}") + if i >= 5: + break +``` + +```python out +('T', '##h'): 0.125 +('##h', '##i'): 0.03409090909090909 +('##i', '##s'): 0.02727272727272727 +('i', '##s'): 0.1 +('t', '##h'): 0.03571428571428571 +('##h', '##e'): 0.011904761904761904 +``` + +Maintenant, trouver la paire avec le meilleur score ne prend qu'une rapide boucle : + +```python +best_pair = "" +max_score = None +for pair, score in pair_scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + +print(best_pair, max_score) +``` + +```python out +('a', '##b') 0.2 +``` + +Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'` et nous ajoutons `'ab'` au vocabulaire : + +```python +vocab.append("ab") +``` + +Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + merge = a + b[2:] if b.startswith("##") else a + b + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Et nous pouvons regarder le résultat de la première fusion : + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Nous avons maintenant tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire de 70 : + +```python +vocab_size = 70 +while len(vocab) < vocab_size: + scores = compute_pair_scores(splits) + best_pair, max_score = "", None + for pair, score in scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + splits = merge_pair(*best_pair, splits) + new_token = ( + best_pair[0] + best_pair[1][2:] + if best_pair[1].startswith("##") + else best_pair[0] + best_pair[1] + ) + vocab.append(new_token) +``` + +Nous pouvons ensuite examiner le vocabulaire généré : + +```py +print(vocab) +``` + +```python out +['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', + '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', + 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', + '##ut'] +``` + +Comme nous pouvons le voir, comparé à BPE, ce *tokenizer* apprend les parties de mots comme des *tokens* un peu plus rapidement. + + + +💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de son fonctionnement interne), mais utilise le BPE à la place. + + + +Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons. Puis nous répétons le processus sur la deuxième partie et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte : + +```python +def encode_word(word): + tokens = [] + while len(word) > 0: + i = len(word) + while i > 0 and word[:i] not in vocab: + i -= 1 + if i == 0: + return ["[UNK]"] + tokens.append(word[:i]) + word = word[i:] + if len(word) > 0: + word = f"##{word}" + return tokens +``` + +Testons-le sur un mot qui fait partie du vocabulaire, et un autre qui n'en fait pas partie : + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Maintenant, écrivons une fonction qui permet de tokeniser un texte : + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + encoded_words = [encode_word(word) for word in pre_tokenized_text] + return sum(encoded_words, []) +``` + +On peut l'essayer sur n'importe quel texte : + +```python +tokenize("This is the Hugging Face Course!") # C'est le cours d'Hugging Face +``` + +```python out +['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', + '##e', '[UNK]'] +``` + +C'est tout pour l'algorithme *WordPiece* ! Maintenant, jetons un coup d'oeil à *Unigram*. diff --git a/chapters/fr/chapter6/7.mdx b/chapters/fr/chapter6/7.mdx index bf5c970dc..3d262bbd1 100644 --- a/chapters/fr/chapter6/7.mdx +++ b/chapters/fr/chapter6/7.mdx @@ -1,385 +1,385 @@ -# Tokenisation Unigram - - - -L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet. - - - - - -💡 Cette section couvre *Unigram* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. - - - -## Algorithme d'entraînement - -Comparé au BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base. Nous pouvons par exemple prendre les sous-chaînes les plus courantes dans les mots prétokénisés ou appliquer le BPE sur le corpus initial avec une grande taille de vocabulaire. - -À chaque étape de l'entraînement, l'algorithme *Unigram* calcule une perte sur le corpus compte tenu du vocabulaire actuel. Ensuite, pour chaque symbole du vocabulaire, l'algorithme calcule de combien la perte globale augmenterait si le symbole était supprimé et recherche les symboles qui l'augmenteraient le moins. Ces symboles ont un effet moindre sur la perte globale du corpus, ils sont donc en quelque sorte « moins nécessaires » et sont les meilleurs candidats à la suppression. - -Comme il s'agit d'une opération très coûteuse, nous ne nous contentons pas de supprimer le symbole unique associé à la plus faible augmentation de la perte mais le \\(p\\) pourcent des symboles associés à la plus faible augmentation de la perte. \(p\\) est un hyperparamètre que vous pouvez contrôler, valant généralement 10 ou 20. Ce processus est ensuite répété jusqu'à ce que le vocabulaire ait atteint la taille souhaitée. - -Notez que nous ne supprimons jamais les caractères de base, afin de nous assurer que tout mot peut être tokenisé. - -Tout ceci peut paraître encore un peu vague. En effet, la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation *Unigram*, nous allons donc l'aborder à présent. - -Nous allons réutiliser le corpus des exemples précédents : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) # "câlin", "carlin", "jeu de mots", "brioche", "câlins"... -``` - -et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vocabulaire initial : - -``` -["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] -``` - -## Algorithme de tokenisation - -Un modèle *Unigram* est un type de modèle de langage qui considère que chaque *token* est indépendant des *tokens* qui le précèdent. Il s'agit du modèle de langage le plus simple, dans le sens où la probabilité du *token* X compte tenu du contexte précédent est simplement la probabilité du *token* X. Ainsi, si nous utilisions un modèle de langage *Unigram* pour générer du texte, nous prédirions toujours le *token* le plus courant. - -La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`. Il a donc une fréquence de 20 dans notre corpus. - -Voici les fréquences de tous les sous-mots possibles dans le vocabulaire : - -``` -("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) -("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) -``` - -Ainsi, la somme de toutes les fréquences est de 210 et la probabilité du sous-mot `"ug"` est donc de 20/210. - - - -✏️ **A votre tour !** Ecrivez le code permettant de calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, de même que la somme totale. - - - -Maintenant, pour tokeniser un mot donné, nous examinons toutes les segmentations possibles en *tokens* et calculons la probabilité de chacune d'entre elles selon le modèle *Unigram*. Puisque tous les *tokens* sont considérés comme indépendants, cette probabilité est juste le produit de la probabilité de chaque *token*. Par exemple, la tokenisation `["p", "u", "g"]` de `"pug"` a la probabilité : - -$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$ - -Comparativement, la tokenization `["pu", "g"]` a la probabilité : - -$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ - -donc celle-là est beaucoup plus probable. En général, les tokénisations comportant le moins de *tokens* possible auront la probabilité la plus élevée (en raison de la division par 210 répétée pour chaque *token*), ce qui correspond à ce que nous voulons intuitivement : diviser un mot en un nombre de *tokens* le plus faible possible. - -La tokenisation d'un mot avec le modèle *Unigram* est donc la tokenisation avec la plus haute probabilité. Dans l'exemple de `"pug"`, voici les probabilités que nous obtiendrions pour chaque segmentation possible : - -``` -["p", "u", "g"] : 0.000389 -["p", "ug"] : 0.0022676 -["pu", "g"] : 0.0022676 -``` - -Ainsi, `"pug"` sera tokenisé comme `["p", "ug"]` ou `["pu", "g"]`, selon la segmentation rencontrée en premier (notez que dans un corpus plus large, les cas d'égalité comme celui-ci seront rares). - -Dans ce cas-ci, cela a été facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot. - -Pour trouver le chemin qui va avoir le meilleur score dans ce graphe, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin. - -Prenons un exemple en utilisant notre vocabulaire et le mot `"unhug"`. Pour chaque position, les sous-mots avec les meilleurs scores se terminant là sont les suivants : - -``` -Character 0 (u): "u" (score 0.171429) -Character 1 (n): "un" (score 0.076191) -Character 2 (h): "un" "h" (score 0.005442) -Character 3 (u): "un" "hu" (score 0.005442) -Character 4 (g): "un" "hug" (score 0.005442) -``` - -Ainsi, `"unhug"` serait tokenisé comme `["un", "hug"]`. - - - -✏️ **A votre tour !** Déterminer la tokenization du mot `"huggun"` et son score. - - - -## Retour à l'entraînement - -Maintenant que nous avons vu comment fonctionne la tokenisation, nous pouvons nous plonger un peu plus profondément dans la perte utilisée pendant l'entraînement. À n'importe quelle étape, cette perte est calculée en tokenisant chaque mot du corpus, en utilisant le vocabulaire courant et le modèle *Unigram* déterminé par les fréquences de chaque *token* dans le corpus (comme vu précédemment). - -Chaque mot du corpus a un score, et la perte est le négatif du logarithme de ces scores, c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`. - -Revenons à notre exemple avec le corpus suivant : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -La tokenisation de chaque mot avec leurs scores respectifs est : - -``` -"hug": ["hug"] (score 0.071428) -"pug": ["pu", "g"] (score 0.007710) -"pun": ["pu", "n"] (score 0.006168) -"bun": ["bu", "n"] (score 0.001451) -"hugs": ["hug", "s"] (score 0.001701) -``` - -Donc la perte est : - -``` -10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 -``` - -Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots. Par exmeple, comme nous l'avons vu précédemment, `"pug"` pourrait être tokenisé en `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte. - -D'un autre côté, supprimer le mot `"hug"` aggravera la perte, car la tokenisation de `"hug"` et `"hugs"` deviendra : - -``` -"hug": ["hu", "g"] (score 0.006802) -"hugs": ["hu", "gs"] (score 0.001701) -``` - -Ces changements entraîneront une augmentation de la perte de : - -``` -- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 -``` - -Par conséquent, le token `"pu"` sera probablement retiré du vocabulaire, mais pas `"hug"`. - -## Implémentation d'Unigram - -Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour le BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais elle devrait vous aider à le comprendre un peu mieux. - -Nous allons utiliser le même corpus que précédemment comme exemple : - -```python -corpus = [ - "This is the Hugging Face Course.", - # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", - # Ce chapitre traite de la tokenisation. - "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de *tokenizer*. - "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. -] -``` - -Cette fois, nous allons utiliser `xlnet-base-cased` comme modèle : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") -``` - -Comme pour le BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le corpus : - -```python -from collections import defaultdict - -word_freqs = defaultdict(int) -for text in corpus: - words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) - new_words = [word for word, offset in words_with_offsets] - for word in new_words: - word_freqs[word] += 1 - -word_freqs -``` - -Ensuite, nous devons initialiser notre vocabulaire à une taille plus grande que celle du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs. AInsi nous les trions par fréquence : - -```python -char_freqs = defaultdict(int) -subwords_freqs = defaultdict(int) -for word, freq in word_freqs.items(): - for i in range(len(word)): - char_freqs[word[i]] += freq - # Boucle à travers les sous-mots de longueur au moins égale à 2 - for j in range(i + 2, len(word) + 1): - subwords_freqs[word[i:j]] += freq - -# Trier les sous-mots par fréquence -sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) -sorted_subwords[:10] -``` - -```python out -[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] -``` - -Nous regroupons les caractères avec les meilleurs sous-mots pour arriver à un vocabulaire initial de taille 300 : - -```python -token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] -token_freqs = {token: freq for token, freq in token_freqs} -``` - - - -💡 *SentencePiece* utilise un algorithme plus efficace appelé *Enhanced Suffix Array* (ESA) pour créer le vocabulaire initial. - - - -Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car c'est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres. Cela simplifiera aussi le calcul de la perte du modèle : - -```python -from math import log - -total_sum = sum([freq for token, freq in token_freqs.items()]) -model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} -``` - -Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie. - -Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale que nous comparons à ce qui est dans `best_segmentations`. - -Une fois que la boucle principale est terminée, nous commençons juste à la fin et sautons d'une position de départ à une autre, en enregistrant les *tokens* au fur et à mesure, jusqu'à ce que nous atteignions le début du mot : - -```python -def encode_word(word, model): - best_segmentations = [{"start": 0, "score": 1}] + [ - {"start": None, "score": None} for _ in range(len(word)) - ] - for start_idx in range(len(word)): - # Doit être correctement rempli par les étapes précédentes de la boucle - best_score_at_start = best_segmentations[start_idx]["score"] - for end_idx in range(start_idx + 1, len(word) + 1): - token = word[start_idx:end_idx] - if token in model and best_score_at_start is not None: - score = model[token] + best_score_at_start - # Si nous avons trouvé une meilleure segmentation se terminant à end_idx, nous mettons à jour - if ( - best_segmentations[end_idx]["score"] is None - or best_segmentations[end_idx]["score"] > score - ): - best_segmentations[end_idx] = {"start": start_idx, "score": score} - - segmentation = best_segmentations[-1] - if segmentation["score"] is None: - # Nous n'avons pas trouvé de tokenization du mot -> inconnu () - return [""], None - - score = segmentation["score"] - start = segmentation["start"] - end = len(word) - tokens = [] - while start != 0: - tokens.insert(0, word[start:end]) - next_start = best_segmentations[start]["start"] - end = start - start = next_start - tokens.insert(0, word[start:end]) - return tokens, score -``` - -Nous pouvons déjà essayer notre modèle initial sur quelques mots : - -```python -print(encode_word("Hopefully", model)) -print(encode_word("This", model)) -``` - -```python out -(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402) -(['This'], 6.288267030694535) -``` - -Il est maintenant facile de calculer la perte du modèle sur le corpus ! - -```python -def compute_loss(model): - loss = 0 - for word, freq in word_freqs.items(): - _, word_loss = encode_word(word, model) - loss += freq * word_loss - return loss -``` - -Nous pouvons vérifier que cela fonctionne sur le modèle que nous avons : - -```python -compute_loss(model) -``` - -```python out -413.10377642940875 -``` - -Le calcul des scores pour chaque *token* n'est pas très difficile non plus. Il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *token* : - -```python -import copy - - -def compute_scores(model): - scores = {} - model_loss = compute_loss(model) - for token, score in model.items(): - # Nous gardons toujours les tokens de longueur 1. - if len(token) == 1: - continue - model_without_token = copy.deepcopy(model) - _ = model_without_token.pop(token) - scores[token] = compute_loss(model_without_token) - model_loss - return scores -``` - -Nous pouvons l'essayer sur un *token* donné : - -```python -scores = compute_scores(model) -print(scores["ll"]) -print(scores["his"]) -``` - -Puisque `"ll"` est utilisé dans la tokenisation de `"Hopefully"`, et que le supprimer nous fera probablement utiliser le token `"l"` deux fois à la place, nous nous attendons à ce qu'il ait une perte positive. `"his"` n'est utilisé qu'à l'intérieur du mot `"This"`, qui est tokenisé comme lui-même, donc nous nous attendons à ce qu'il ait une perte nulle. Voici les résultats : - -```python out -6.376412403623874 -0.0 -``` - - - -💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X. Au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle. - - - -Une fois tout cela en place, la dernière chose à faire est d'ajouter les *tokens* spéciaux utilisés par le modèle au vocabulaire, puis de boucler jusqu'à ce que nous ayons élagué suffisamment de *tokens* du vocabulaire pour atteindre la taille souhaitée : - -```python -percent_to_remove = 0.1 -while len(model) > 100: - scores = compute_scores(model) - sorted_scores = sorted(scores.items(), key=lambda x: x[1]) - # Supprime les tokens percent_to_remove ayant les scores les plus bas - for i in range(int(len(model) * percent_to_remove)): - _ = token_freqs.pop(sorted_scores[i][0]) - - total_sum = sum([freq for token, freq in token_freqs.items()]) - model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} -``` - -Ensuite, pour tokeniser un texte, il suffit d'appliquer la prétokénisation et d'utiliser la fonction `encode_word()` : - -```python -def tokenize(text, model): - words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) - pre_tokenized_text = [word for word, offset in words_with_offsets] - encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text] - return sum(encoded_words, []) - - -tokenize("This is the Hugging Face course.", model) -``` - -```python out -['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] -``` - -C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez à présent être un expert des *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers* et allons vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*. +# Tokenisation Unigram + + + +L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet. + + + + + +💡 Cette section couvre *Unigram* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. + + + +## Algorithme d'entraînement + +Comparé au BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base. Nous pouvons par exemple prendre les sous-chaînes les plus courantes dans les mots prétokénisés ou appliquer le BPE sur le corpus initial avec une grande taille de vocabulaire. + +À chaque étape de l'entraînement, l'algorithme *Unigram* calcule une perte sur le corpus compte tenu du vocabulaire actuel. Ensuite, pour chaque symbole du vocabulaire, l'algorithme calcule de combien la perte globale augmenterait si le symbole était supprimé et recherche les symboles qui l'augmenteraient le moins. Ces symboles ont un effet moindre sur la perte globale du corpus, ils sont donc en quelque sorte « moins nécessaires » et sont les meilleurs candidats à la suppression. + +Comme il s'agit d'une opération très coûteuse, nous ne nous contentons pas de supprimer le symbole unique associé à la plus faible augmentation de la perte mais le \\(p\\) pourcent des symboles associés à la plus faible augmentation de la perte. \(p\\) est un hyperparamètre que vous pouvez contrôler, valant généralement 10 ou 20. Ce processus est ensuite répété jusqu'à ce que le vocabulaire ait atteint la taille souhaitée. + +Notez que nous ne supprimons jamais les caractères de base, afin de nous assurer que tout mot peut être tokenisé. + +Tout ceci peut paraître encore un peu vague. En effet, la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation *Unigram*, nous allons donc l'aborder à présent. + +Nous allons réutiliser le corpus des exemples précédents : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) # "câlin", "carlin", "jeu de mots", "brioche", "câlins"... +``` + +et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vocabulaire initial : + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Algorithme de tokenisation + +Un modèle *Unigram* est un type de modèle de langage qui considère que chaque *token* est indépendant des *tokens* qui le précèdent. Il s'agit du modèle de langage le plus simple, dans le sens où la probabilité du *token* X compte tenu du contexte précédent est simplement la probabilité du *token* X. Ainsi, si nous utilisions un modèle de langage *Unigram* pour générer du texte, nous prédirions toujours le *token* le plus courant. + +La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`. Il a donc une fréquence de 20 dans notre corpus. + +Voici les fréquences de tous les sous-mots possibles dans le vocabulaire : + +``` +("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) +("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) +``` + +Ainsi, la somme de toutes les fréquences est de 210 et la probabilité du sous-mot `"ug"` est donc de 20/210. + + + +✏️ **A votre tour !** Ecrivez le code permettant de calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, de même que la somme totale. + + + +Maintenant, pour tokeniser un mot donné, nous examinons toutes les segmentations possibles en *tokens* et calculons la probabilité de chacune d'entre elles selon le modèle *Unigram*. Puisque tous les *tokens* sont considérés comme indépendants, cette probabilité est juste le produit de la probabilité de chaque *token*. Par exemple, la tokenisation `["p", "u", "g"]` de `"pug"` a la probabilité : + +$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$ + +Comparativement, la tokenization `["pu", "g"]` a la probabilité : + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +donc celle-là est beaucoup plus probable. En général, les tokénisations comportant le moins de *tokens* possible auront la probabilité la plus élevée (en raison de la division par 210 répétée pour chaque *token*), ce qui correspond à ce que nous voulons intuitivement : diviser un mot en un nombre de *tokens* le plus faible possible. + +La tokenisation d'un mot avec le modèle *Unigram* est donc la tokenisation avec la plus haute probabilité. Dans l'exemple de `"pug"`, voici les probabilités que nous obtiendrions pour chaque segmentation possible : + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Ainsi, `"pug"` sera tokenisé comme `["p", "ug"]` ou `["pu", "g"]`, selon la segmentation rencontrée en premier (notez que dans un corpus plus large, les cas d'égalité comme celui-ci seront rares). + +Dans ce cas-ci, cela a été facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot. + +Pour trouver le chemin qui va avoir le meilleur score dans ce graphe, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin. + +Prenons un exemple en utilisant notre vocabulaire et le mot `"unhug"`. Pour chaque position, les sous-mots avec les meilleurs scores se terminant là sont les suivants : + +``` +Character 0 (u): "u" (score 0.171429) +Character 1 (n): "un" (score 0.076191) +Character 2 (h): "un" "h" (score 0.005442) +Character 3 (u): "un" "hu" (score 0.005442) +Character 4 (g): "un" "hug" (score 0.005442) +``` + +Ainsi, `"unhug"` serait tokenisé comme `["un", "hug"]`. + + + +✏️ **A votre tour !** Déterminer la tokenization du mot `"huggun"` et son score. + + + +## Retour à l'entraînement + +Maintenant que nous avons vu comment fonctionne la tokenisation, nous pouvons nous plonger un peu plus profondément dans la perte utilisée pendant l'entraînement. À n'importe quelle étape, cette perte est calculée en tokenisant chaque mot du corpus, en utilisant le vocabulaire courant et le modèle *Unigram* déterminé par les fréquences de chaque *token* dans le corpus (comme vu précédemment). + +Chaque mot du corpus a un score, et la perte est le négatif du logarithme de ces scores, c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`. + +Revenons à notre exemple avec le corpus suivant : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +La tokenisation de chaque mot avec leurs scores respectifs est : + +``` +"hug": ["hug"] (score 0.071428) +"pug": ["pu", "g"] (score 0.007710) +"pun": ["pu", "n"] (score 0.006168) +"bun": ["bu", "n"] (score 0.001451) +"hugs": ["hug", "s"] (score 0.001701) +``` + +Donc la perte est : + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots. Par exmeple, comme nous l'avons vu précédemment, `"pug"` pourrait être tokenisé en `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte. + +D'un autre côté, supprimer le mot `"hug"` aggravera la perte, car la tokenisation de `"hug"` et `"hugs"` deviendra : + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Ces changements entraîneront une augmentation de la perte de : + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Par conséquent, le token `"pu"` sera probablement retiré du vocabulaire, mais pas `"hug"`. + +## Implémentation d'Unigram + +Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour le BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais elle devrait vous aider à le comprendre un peu mieux. + +Nous allons utiliser le même corpus que précédemment comme exemple : + +```python +corpus = [ + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # Ce chapitre traite de la tokenisation. + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. +] +``` + +Cette fois, nous allons utiliser `xlnet-base-cased` comme modèle : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Comme pour le BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le corpus : + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +Ensuite, nous devons initialiser notre vocabulaire à une taille plus grande que celle du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs. AInsi nous les trions par fréquence : + +```python +char_freqs = defaultdict(int) +subwords_freqs = defaultdict(int) +for word, freq in word_freqs.items(): + for i in range(len(word)): + char_freqs[word[i]] += freq + # Boucle à travers les sous-mots de longueur au moins égale à 2 + for j in range(i + 2, len(word) + 1): + subwords_freqs[word[i:j]] += freq + +# Trier les sous-mots par fréquence +sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) +sorted_subwords[:10] +``` + +```python out +[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] +``` + +Nous regroupons les caractères avec les meilleurs sous-mots pour arriver à un vocabulaire initial de taille 300 : + +```python +token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] +token_freqs = {token: freq for token, freq in token_freqs} +``` + + + +💡 *SentencePiece* utilise un algorithme plus efficace appelé *Enhanced Suffix Array* (ESA) pour créer le vocabulaire initial. + + + +Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car c'est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres. Cela simplifiera aussi le calcul de la perte du modèle : + +```python +from math import log + +total_sum = sum([freq for token, freq in token_freqs.items()]) +model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie. + +Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale que nous comparons à ce qui est dans `best_segmentations`. + +Une fois que la boucle principale est terminée, nous commençons juste à la fin et sautons d'une position de départ à une autre, en enregistrant les *tokens* au fur et à mesure, jusqu'à ce que nous atteignions le début du mot : + +```python +def encode_word(word, model): + best_segmentations = [{"start": 0, "score": 1}] + [ + {"start": None, "score": None} for _ in range(len(word)) + ] + for start_idx in range(len(word)): + # Doit être correctement rempli par les étapes précédentes de la boucle + best_score_at_start = best_segmentations[start_idx]["score"] + for end_idx in range(start_idx + 1, len(word) + 1): + token = word[start_idx:end_idx] + if token in model and best_score_at_start is not None: + score = model[token] + best_score_at_start + # Si nous avons trouvé une meilleure segmentation se terminant à end_idx, nous mettons à jour + if ( + best_segmentations[end_idx]["score"] is None + or best_segmentations[end_idx]["score"] > score + ): + best_segmentations[end_idx] = {"start": start_idx, "score": score} + + segmentation = best_segmentations[-1] + if segmentation["score"] is None: + # Nous n'avons pas trouvé de tokenization du mot -> inconnu () + return [""], None + + score = segmentation["score"] + start = segmentation["start"] + end = len(word) + tokens = [] + while start != 0: + tokens.insert(0, word[start:end]) + next_start = best_segmentations[start]["start"] + end = start + start = next_start + tokens.insert(0, word[start:end]) + return tokens, score +``` + +Nous pouvons déjà essayer notre modèle initial sur quelques mots : + +```python +print(encode_word("Hopefully", model)) +print(encode_word("This", model)) +``` + +```python out +(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402) +(['This'], 6.288267030694535) +``` + +Il est maintenant facile de calculer la perte du modèle sur le corpus ! + +```python +def compute_loss(model): + loss = 0 + for word, freq in word_freqs.items(): + _, word_loss = encode_word(word, model) + loss += freq * word_loss + return loss +``` + +Nous pouvons vérifier que cela fonctionne sur le modèle que nous avons : + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Le calcul des scores pour chaque *token* n'est pas très difficile non plus. Il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *token* : + +```python +import copy + + +def compute_scores(model): + scores = {} + model_loss = compute_loss(model) + for token, score in model.items(): + # Nous gardons toujours les tokens de longueur 1. + if len(token) == 1: + continue + model_without_token = copy.deepcopy(model) + _ = model_without_token.pop(token) + scores[token] = compute_loss(model_without_token) - model_loss + return scores +``` + +Nous pouvons l'essayer sur un *token* donné : + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Puisque `"ll"` est utilisé dans la tokenisation de `"Hopefully"`, et que le supprimer nous fera probablement utiliser le token `"l"` deux fois à la place, nous nous attendons à ce qu'il ait une perte positive. `"his"` n'est utilisé qu'à l'intérieur du mot `"This"`, qui est tokenisé comme lui-même, donc nous nous attendons à ce qu'il ait une perte nulle. Voici les résultats : + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X. Au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle. + + + +Une fois tout cela en place, la dernière chose à faire est d'ajouter les *tokens* spéciaux utilisés par le modèle au vocabulaire, puis de boucler jusqu'à ce que nous ayons élagué suffisamment de *tokens* du vocabulaire pour atteindre la taille souhaitée : + +```python +percent_to_remove = 0.1 +while len(model) > 100: + scores = compute_scores(model) + sorted_scores = sorted(scores.items(), key=lambda x: x[1]) + # Supprime les tokens percent_to_remove ayant les scores les plus bas + for i in range(int(len(model) * percent_to_remove)): + _ = token_freqs.pop(sorted_scores[i][0]) + + total_sum = sum([freq for token, freq in token_freqs.items()]) + model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Ensuite, pour tokeniser un texte, il suffit d'appliquer la prétokénisation et d'utiliser la fonction `encode_word()` : + +```python +def tokenize(text, model): + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in words_with_offsets] + encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text] + return sum(encoded_words, []) + + +tokenize("This is the Hugging Face course.", model) +``` + +```python out +['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] +``` + +C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez à présent être un expert des *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers* et allons vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*. diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 46440deb7..79c0a58a8 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -1,566 +1,566 @@ -# Construction d'un tokenizer, bloc par bloc - - - -Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : - -- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.), -- prétokénisation (division de l'entrée en mots), -- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*), -- post-traitement (ajout des *tokens* spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). - -Pour mémoire, voici un autre aperçu du processus global : - -
-The tokenization pipeline. - -
- -La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes. Vous pouvez les mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! - - - -Plus précisément, la bibliothèque est construite autour d'une classe centrale `Tokenizer` avec les blocs de construction regroupés en sous-modules : - -- `normalizers` contient tous les types de `Normalizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers)), -- `pre_tokenizers` contient tous les types de `PreTokenizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers)), -- `models` contient les différents types de `Model` que vous pouvez utiliser, comme `BPE`, `WordPiece`, et `Unigram` (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models)), -- `trainers` contient tous les différents types de `Trainer` que vous pouvez utiliser pour entraîner votre modèle sur un corpus (un par type de modèle ; liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers)), -- `post_processors` contient les différents types de `PostProcessor` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors)), -- `decoders` contient les différents types de `Decoder` que vous pouvez utiliser pour décoder les sorties de tokenization (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders)). - -Vous pouvez trouver la liste complète des blocs de construction [ici](https://huggingface.co/docs/tokenizers/python/latest/components.html). - -## Acquisition d'un corpus - -Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir ce corpus sont similaires à celles que nous avons suivies au [début du chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [WikiText-2](https://huggingface.co/datasets/wikitext) : - - -```python -from datasets import load_dataset - -dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train") - - -def get_training_corpus(): - for i in range(0, len(dataset), 1000): - yield dataset[i : i + 1000]["text"] -``` - -La fonction `get_training_corpus()` est un générateur qui donne des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. - -🤗 *Tokenizers* peut aussi être entraîné directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes de WikiText-2 que nous pourrons ensuite utilisé en local : - -```python -with open("wikitext-2.txt", "w", encoding="utf-8") as f: - for i in range(len(dataset)): - f.write(dataset[i]["text"] + "\n") -``` - -Ensuite, nous vous montrerons comment construire vos propres *tokenizers* pour BERT, GPT-2 et XLNet, bloc par bloc. Cela vous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! - -## Construire un tokenizer WordPiece à partir de zéro - -Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`. Puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor` et `decoder` aux valeurs que nous voulons. - -Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : - -```python -from tokenizers import ( - decoders, - models, - normalizers, - pre_tokenizers, - processors, - trainers, - Tokenizer, -) - -tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) -``` - -Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). - -La première étape de la tokénisation est la normalisation. Puisque BERT est largement utilisé, une fonction `BertNormalizer` a été créée avec les options classiques que nous pouvons définir pour BERT : `lowercase` pour mettre le texte en minuscule, `strip_accents` qui enlève les accents, `clean_text` pour enlever tous les caractères de contrôle et fusionner des espaces répétés par un seul, et `handle_chinese_chars` qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : - -```python -tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) -``` - -Cependant, généralement, lorsque vous construisez un nouveau *tokenizer*, vous n'avez pas accès à un normaliseur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normaliseur de BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`. Il est possible de composer plusieurs normaliseurs en utilisant une `Sequence` : - -```python -tokenizer.normalizer = normalizers.Sequence( - [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] -) -``` - -Nous utilisons également un normaliseur Unicode `NFD`, car sinon `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. - -Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : - -```python -print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) -``` - -```python out -hello how are u? -``` - - - -**Pour aller plus loin** Si vous testez les deux versions des normaliseurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement qu'ils ne sont pas exactement équivalents. -Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. - - - -L'étape suivante est la prétokenisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() -``` - -Ou nous pouvons le construire à partir de zéro : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() -``` - -Notez que le `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement. Donc techniquement il divise sur les espaces et la ponctuation : - -```python -tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") -``` - -```python out -[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), - ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] -``` - -Si vous voulez seulement séparer sur les espaces, vous devez utiliser `WhitespaceSplit` à la place : - -```python -pre_tokenizer = pre_tokenizers.WhitespaceSplit() -pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") -``` - -```python out -[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] -``` - -Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs prétokenizers : - -```python -pre_tokenizer = pre_tokenizers.Sequence( - [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] -) -pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") -``` - -```python out -[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), - ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] -``` - -L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire puisqu'ils ne sont pas dans le corpus d'entraînement : - -```python -special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] -trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) -``` - -En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). - -Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : - -```python -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer* qui ressemblerait alors à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : - -```python -tokenizer.model = models.WordPiece(unk_token="[UNK]") -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] -``` - -L'encodage obtenu est un `Encoding` contenant toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` et `overflowing`. - -La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase si nous avons une paire de phrases). Nous utiliserons `TemplateProcessor` pour cela, mais d'abord nous devons connaître les identifiants des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : - -```python -cls_token_id = tokenizer.token_to_id("[CLS]") -sep_token_id = tokenizer.token_to_id("[SEP]") -print(cls_token_id, sep_token_id) -``` - -```python out -(2, 3) -``` - -Pour écrire le gabarit pour `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser. La première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'identifiant du *token* correspondant après un deux-points. - -Le gabarit classique de BERT est donc défini comme suit : - -```python -tokenizer.post_processor = processors.TemplateProcessing( - single=f"[CLS]:0 $A:0 [SEP]:0", - pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", - special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], -) -``` - -Notez que nous devons transmettre les identifiants des *tokens* spéciaux afin que le *tokenizer* puisse les convertir correctement. - -Une fois cela ajouté, revenons à notre exemple précédent donnera : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] -``` - -Et sur une paire de phrases, on obtient le bon résultat : - -```python -encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") -print(encoding.tokens) -print(encoding.type_ids) -``` - -```python out -['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]'] -[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] -``` - -Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : - -```python -tokenizer.decoder = decoders.WordPiece(prefix="##") -``` - -Testons-le sur notre précédent `encoding` : - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. -``` - -Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : - -```python -tokenizer.save("tokenizer.json") -``` - -Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : - -```python -new_tokenizer = Tokenizer.from_file("tokenizer.json") -``` - -Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette logique pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. - -Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer* que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, quel est le *token*`[CLS]`, etc : - -```python -from transformers import PreTrainedTokenizerFast - -wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - # tokenizer_file="tokenizer.json", # Vous pouvez charger à partir du fichier du tokenizer, alternativement - unk_token="[UNK]", - pad_token="[PAD]", - cls_token="[CLS]", - sep_token="[SEP]", - mask_token="[MASK]", -) -``` - -Si vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : - -```python -from transformers import BertTokenizerFast - -wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) -``` - -Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()` ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. - -Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes. Nous ne soulignerons que les différences. - -## Construire un tokenizer BPE à partir de zéro - -Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : - -```python -tokenizer = Tokenizer(models.BPE()) -``` - -Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que le GPT-2 utilise un BPE au niveau de l'octet. - -GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la prétokénisation : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) -``` - -L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la prétokénisation d'un texte d'exemple comme avant : - -```python -tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") -``` - -```python out -[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), - ('tokenization', (15, 27)), ('!', (27, 28))] -``` - -Vient ensuite le modèle, qui doit être entraîné. Pour le GPT-2, le seul *token* spécial est le *token* de fin de texte : - -```python -trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme ``), nous pouvons le définir avec `end_of_word_suffix`. - -Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : - -```python -tokenizer.model = models.BPE() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Regardons la tokenisation d'un exemple de texte : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] -``` - -Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : - -```python -tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) -``` - -L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du *token*). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le *token* à l'index 4 : - -```python -sentence = "Let's test this tokenizer." -encoding = tokenizer.encode(sentence) -start, end = encoding.offsets[4] -sentence[start:end] -``` - -```python out -' test' -``` - -Enfin, nous ajoutons un décodeur au niveau de l'octet : - -```python -tokenizer.decoder = decoders.ByteLevel() -``` - -et nous pouvons vérifier qu'il fonctionne correctement : - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"Let's test this tokenizer." # Testons ce tokenizer -``` - -Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : - -```python -from transformers import PreTrainedTokenizerFast - -wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", -) -``` - -ou : - -```python -from transformers import GPT2TokenizerFast - -wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) -``` - -Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. - -## Construire un tokenizer Unigram à partir de zéro - -Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : - -```python -tokenizer = Tokenizer(models.Unigram()) -``` - -Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. - -Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : - -```python -from tokenizers import Regex - -tokenizer.normalizer = normalizers.Sequence( - [ - normalizers.Replace("``", '"'), - normalizers.Replace("''", '"'), - normalizers.NFKD(), - normalizers.StripAccents(), - normalizers.Replace(Regex(" {2,}"), " "), - ] -) -``` - -Il remplace `` et '' par " et toute séquence de deux espaces ou plus par un seul espace, de plus il supprime les accents. - -Le prétokenizer à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() -``` - -Nous pouvons jeter un coup d'oeil à la prétokénisation sur le même exemple de texte que précédemment : - -```python -tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") -``` - -```python out -[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] -``` - -Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : - -```python -special_tokens = ["", "", "", "", "", "", ""] -trainer = trainers.UnigramTrainer( - vocab_size=25000, special_tokens=special_tokens, unk_token="" -) -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Un argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un *token* donné (par défaut 16). - -Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : - -```python -tokenizer.model = models.Unigram() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Regardons la tokenisation de notre exemple : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] -``` - -Une particularité de XLNet est qu'il place le *token* `` à la fin de la phrase, avec un identifiant de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les types d'identifiant de *token* avec un modèle, comme pour BERT. Mais d'abord nous devons obtenir les identifiants des *tokens* `` et `` : - -```python -cls_token_id = tokenizer.token_to_id("") -sep_token_id = tokenizer.token_to_id("") -print(cls_token_id, sep_token_id) -``` - -```python out -0 1 -``` - -Le modèle ressemble à ceci : - -```python -tokenizer.post_processor = processors.TemplateProcessing( - single="$A:0 :0 :2", - pair="$A:0 :0 $B:1 :1 :2", - special_tokens=[("", sep_token_id), ("", cls_token_id)], -) -``` - -Et nous pouvons tester son fonctionnement en codant une paire de phrases : - -```python -encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") -print(encoding.tokens) -print(encoding.type_ids) -``` - -```python out -['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '', '▁', 'on', '▁', 'a', '▁pair', - '▁of', '▁sentence', 's', '!', '', ''] -[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] -``` - -Enfin, nous ajoutons un décodeur `Metaspace` : - -```python -tokenizer.decoder = decoders.Metaspace() -``` - -et on en a fini avec ce *tokenizer* ! On peut le sauvegarder et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de rembourrer à gauche : - -```python -from transformers import PreTrainedTokenizerFast - -wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="", - eos_token="", - unk_token="", - pad_token="", - cls_token="", - sep_token="", - mask_token="", - padding_side="left", -) -``` - -Ou alternativement : - -```python -from transformers import XLNetTokenizerFast - -wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) -``` - -Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. +# Construction d'un tokenizer, bloc par bloc + + + +Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : + +- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.), +- prétokénisation (division de l'entrée en mots), +- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*), +- post-traitement (ajout des *tokens* spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). + +Pour mémoire, voici un autre aperçu du processus global : + +
+The tokenization pipeline. + +
+ +La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes. Vous pouvez les mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! + + + +Plus précisément, la bibliothèque est construite autour d'une classe centrale `Tokenizer` avec les blocs de construction regroupés en sous-modules : + +- `normalizers` contient tous les types de `Normalizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers)), +- `pre_tokenizers` contient tous les types de `PreTokenizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers)), +- `models` contient les différents types de `Model` que vous pouvez utiliser, comme `BPE`, `WordPiece`, et `Unigram` (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models)), +- `trainers` contient tous les différents types de `Trainer` que vous pouvez utiliser pour entraîner votre modèle sur un corpus (un par type de modèle ; liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers)), +- `post_processors` contient les différents types de `PostProcessor` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors)), +- `decoders` contient les différents types de `Decoder` que vous pouvez utiliser pour décoder les sorties de tokenization (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders)). + +Vous pouvez trouver la liste complète des blocs de construction [ici](https://huggingface.co/docs/tokenizers/python/latest/components.html). + +## Acquisition d'un corpus + +Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir ce corpus sont similaires à celles que nous avons suivies au [début du chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [WikiText-2](https://huggingface.co/datasets/wikitext) : + + +```python +from datasets import load_dataset + +dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train") + + +def get_training_corpus(): + for i in range(0, len(dataset), 1000): + yield dataset[i : i + 1000]["text"] +``` + +La fonction `get_training_corpus()` est un générateur qui donne des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. + +🤗 *Tokenizers* peut aussi être entraîné directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes de WikiText-2 que nous pourrons ensuite utilisé en local : + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +Ensuite, nous vous montrerons comment construire vos propres *tokenizers* pour BERT, GPT-2 et XLNet, bloc par bloc. Cela vous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! + +## Construire un tokenizer WordPiece à partir de zéro + +Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`. Puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor` et `decoder` aux valeurs que nous voulons. + +Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). + +La première étape de la tokénisation est la normalisation. Puisque BERT est largement utilisé, une fonction `BertNormalizer` a été créée avec les options classiques que nous pouvons définir pour BERT : `lowercase` pour mettre le texte en minuscule, `strip_accents` qui enlève les accents, `clean_text` pour enlever tous les caractères de contrôle et fusionner des espaces répétés par un seul, et `handle_chinese_chars` qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +Cependant, généralement, lorsque vous construisez un nouveau *tokenizer*, vous n'avez pas accès à un normaliseur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normaliseur de BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`. Il est possible de composer plusieurs normaliseurs en utilisant une `Sequence` : + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +Nous utilisons également un normaliseur Unicode `NFD`, car sinon `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. + +Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Pour aller plus loin** Si vous testez les deux versions des normaliseurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement qu'ils ne sont pas exactement équivalents. +Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. + + + +L'étape suivante est la prétokenisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Ou nous pouvons le construire à partir de zéro : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Notez que le `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement. Donc techniquement il divise sur les espaces et la ponctuation : + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Si vous voulez seulement séparer sur les espaces, vous devez utiliser `WhitespaceSplit` à la place : + +```python +pre_tokenizer = pre_tokenizers.WhitespaceSplit() +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] +``` + +Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs prétokenizers : + +```python +pre_tokenizer = pre_tokenizers.Sequence( + [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] +) +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire puisqu'ils ne sont pas dans le corpus d'entraînement : + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). + +Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer* qui ressemblerait alors à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +L'encodage obtenu est un `Encoding` contenant toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` et `overflowing`. + +La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase si nous avons une paire de phrases). Nous utiliserons `TemplateProcessor` pour cela, mais d'abord nous devons connaître les identifiants des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : + +```python +cls_token_id = tokenizer.token_to_id("[CLS]") +sep_token_id = tokenizer.token_to_id("[SEP]") +print(cls_token_id, sep_token_id) +``` + +```python out +(2, 3) +``` + +Pour écrire le gabarit pour `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser. La première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'identifiant du *token* correspondant après un deux-points. + +Le gabarit classique de BERT est donc défini comme suit : + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single=f"[CLS]:0 $A:0 [SEP]:0", + pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", + special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], +) +``` + +Notez que nous devons transmettre les identifiants des *tokens* spéciaux afin que le *tokenizer* puisse les convertir correctement. + +Une fois cela ajouté, revenons à notre exemple précédent donnera : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Et sur une paire de phrases, on obtient le bon résultat : + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]'] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Testons-le sur notre précédent `encoding` : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. +``` + +Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : + +```python +tokenizer.save("tokenizer.json") +``` + +Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette logique pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. + +Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer* que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, quel est le *token*`[CLS]`, etc : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # Vous pouvez charger à partir du fichier du tokenizer, alternativement + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Si vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()` ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. + +Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes. Nous ne soulignerons que les différences. + +## Construire un tokenizer BPE à partir de zéro + +Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que le GPT-2 utilise un BPE au niveau de l'octet. + +GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la prétokénisation : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la prétokénisation d'un texte d'exemple comme avant : + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") +``` + +```python out +[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), + ('tokenization', (15, 27)), ('!', (27, 28))] +``` + +Vient ensuite le modèle, qui doit être entraîné. Pour le GPT-2, le seul *token* spécial est le *token* de fin de texte : + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme ``), nous pouvons le définir avec `end_of_word_suffix`. + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation d'un exemple de texte : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du *token*). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le *token* à l'index 4 : + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Enfin, nous ajoutons un décodeur au niveau de l'octet : + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +et nous pouvons vérifier qu'il fonctionne correctement : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." # Testons ce tokenizer +``` + +Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +ou : + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. + +## Construire un tokenizer Unigram à partir de zéro + +Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. + +Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Il remplace `` et '' par " et toute séquence de deux espaces ou plus par un seul espace, de plus il supprime les accents. + +Le prétokenizer à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Nous pouvons jeter un coup d'oeil à la prétokénisation sur le même exemple de texte que précédemment : + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") +``` + +```python out +[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] +``` + +Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : + +```python +special_tokens = ["", "", "", "", "", "", ""] +trainer = trainers.UnigramTrainer( + vocab_size=25000, special_tokens=special_tokens, unk_token="" +) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Un argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un *token* donné (par défaut 16). + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation de notre exemple : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +Une particularité de XLNet est qu'il place le *token* `` à la fin de la phrase, avec un identifiant de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les types d'identifiant de *token* avec un modèle, comme pour BERT. Mais d'abord nous devons obtenir les identifiants des *tokens* `` et `` : + +```python +cls_token_id = tokenizer.token_to_id("") +sep_token_id = tokenizer.token_to_id("") +print(cls_token_id, sep_token_id) +``` + +```python out +0 1 +``` + +Le modèle ressemble à ceci : + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single="$A:0 :0 :2", + pair="$A:0 :0 $B:1 :1 :2", + special_tokens=[("", sep_token_id), ("", cls_token_id)], +) +``` + +Et nous pouvons tester son fonctionnement en codant une paire de phrases : + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '', '▁', 'on', '▁', 'a', '▁pair', + '▁of', '▁sentence', 's', '!', '', ''] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] +``` + +Enfin, nous ajoutons un décodeur `Metaspace` : + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +et on en a fini avec ce *tokenizer* ! On peut le sauvegarder et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de rembourrer à gauche : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="", + eos_token="", + unk_token="", + pad_token="", + cls_token="", + sep_token="", + mask_token="", + padding_side="left", +) +``` + +Ou alternativement : + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. diff --git a/chapters/fr/chapter6/9.mdx b/chapters/fr/chapter6/9.mdx index 11e4beeab..2d586dc7b 100644 --- a/chapters/fr/chapter6/9.mdx +++ b/chapters/fr/chapter6/9.mdx @@ -1,11 +1,16 @@ -# Tokenizer, coché ! - -Bon travail pour finir ce chapitre ! - -Après cette plongée en profondeur dans les *tokenizers*, vous devriez : - -- être capable d'entraîner un nouveau tokenizer en utilisant un ancien tokenizer comme modèle, -- comprendre comment utiliser les *offsets* pour faire correspondre la position des *tokens* à l'étendue de texte d'origine, -- connaître les différences entre BPE, *WordPiece* et *Unigram*, -- être capable de combiner les blocs fournis par la bibliothèque 🤗 *Tokenizers* pour construire votre propre *tokenizer*, -- être capable d'utiliser ce *tokenizer* dans la bibliothèque 🤗 *Transformers*. +# Tokenizer, coché ! + + + +Bon travail pour finir ce chapitre ! + +Après cette plongée en profondeur dans les *tokenizers*, vous devriez : + +- être capable d'entraîner un nouveau tokenizer en utilisant un ancien tokenizer comme modèle, +- comprendre comment utiliser les *offsets* pour faire correspondre la position des *tokens* à l'étendue de texte d'origine, +- connaître les différences entre BPE, *WordPiece* et *Unigram*, +- être capable de combiner les blocs fournis par la bibliothèque 🤗 *Tokenizers* pour construire votre propre *tokenizer*, +- être capable d'utiliser ce *tokenizer* dans la bibliothèque 🤗 *Transformers*. diff --git a/chapters/fr/chapter7/1.mdx b/chapters/fr/chapter7/1.mdx index 49c7a7a84..603659553 100644 --- a/chapters/fr/chapter7/1.mdx +++ b/chapters/fr/chapter7/1.mdx @@ -1,33 +1,38 @@ - - -# Introduction - -Dans le [chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes : - -- la classification de *tokens*, -- la modélisation du langage masqué (comme BERT), -- les résumés, -- la traduction, -- le pré-entraînement à la modélisation causale du langage (comme GPT-2), -- la réponse aux questions. - -{#if fw === 'pt'} - -Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer`, sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! - -Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec 🤗 *Accelerate* et votre propre boucle d'entraînement. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus. L'API `Trainer` est idéale pour *finetuner* ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez. - -{:else} - -Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [chapitre 3](/course/fr/chapiter3), sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! - -Chaque section peut être lue indépendamment. - -{/if} - - - - -Si vous lisez les sections dans l'ordre, vous remarquerez qu'elles ont beaucoup de code et de prose en commun. La répétition est intentionnelle, afin de vous permettre de vous plonger (ou de revenir plus tard) dans une tâche qui vous intéresse et de trouver un exemple fonctionnel complet. - - + + +# Introduction + + + +Dans le [chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes : + +- la classification de *tokens*, +- la modélisation du langage masqué (comme BERT), +- les résumés, +- la traduction, +- le pré-entraînement à la modélisation causale du langage (comme GPT-2), +- la réponse aux questions. + +{#if fw === 'pt'} + +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer`, sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! + +Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec 🤗 *Accelerate* et votre propre boucle d'entraînement. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus. L'API `Trainer` est idéale pour *finetuner* ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez. + +{:else} + +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [chapitre 3](/course/fr/chapiter3), sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! + +Chaque section peut être lue indépendamment. + +{/if} + + + + +Si vous lisez les sections dans l'ordre, vous remarquerez qu'elles ont beaucoup de code et de prose en commun. La répétition est intentionnelle, afin de vous permettre de vous plonger (ou de revenir plus tard) dans une tâche qui vous intéresse et de trouver un exemple fonctionnel complet. + + diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 921f9629f..8a4cdc83a 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -1,982 +1,982 @@ - - -# Classification de tokens - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme l'attribution d'une étiquette à chaque *token* d'une phrase, tels que : - -- la **reconnaissance d'entités nommées (NER de l'anglais *Named Entity Recognition*)**, c'est-à-dire trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Ce tâche peut être formulée comme l'attribution d'une étiquette à chaque *token* faisant parti d'une entité en ayant une classe spécifique par entité, et une classe pour les *tokens* ne faisant pas parti d'entité. -- le ***part-of-speech tagging* (POS)**, c'est-à-dire marquer chaque mot dans une phrase comme correspondant à une partie particulière (comme un nom, un verbe, un adjectif, etc.). -- le ***chunking***, c'est-à-dire trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. - - - -Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens*. Ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons *finetuner* un modèle (BERT) sur la tâche de NER. Il sera alors capable de calculer des prédictions comme celle-ci : - - - - - -One-hot encoded labels for question answering. - - - -Vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn) les prédictions du modèle que nous allons entraîner. - -## Préparation des données - -Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. - - - -💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. - - - -### Le jeu de données CoNLL-2003 - -Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("conll2003") -``` - -Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes dans ce jeu de données et la répartition entre les ensembles d'entraînement, de validation et de test : - -```py -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], - num_rows: 14041 - }) - validation: Dataset({ - features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], - num_rows: 3250 - }) - test: Dataset({ - features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], - num_rows: 3453 - }) -}) -``` - -En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS et *chunking*. Une grande différence avec les autres jeux de données est que les entrées textuelles ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées prétokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation en sous-mots). - -Regardons le premier élément de l'ensemble d'entraînement : - -```py -raw_datasets["train"][0]["tokens"] -``` - -```python out -['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] -``` - -Puisque nous voulons effectuer reconnaître des entités nommées, nous allons examiner les balises NER : - -```py -raw_datasets["train"][0]["ner_tags"] -``` - -```python out -[3, 0, 7, 0, 0, 0, 7, 0, 0] -``` - -Ce sont les étiquettes sous forme d'entiers disponibles pour l'entraînement mais ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : - -```py -ner_feature = raw_datasets["train"].features["ner_tags"] -ner_feature -``` - -```python out -Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None) -``` - -Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `feature` : - -```py -label_names = ner_feature.feature.names -label_names -``` - -```python out -['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] -``` - -Nous avons déjà vu ces étiquettes au [chapitre 6](/course/fr/chapter6/3) lorsque nous nous sommes intéressés au pipeline `token-classification` mais nosu pouvons tout de même faire un rapide rappel : - -- `O` signifie que le mot ne correspond à aucune entité. -- `B-PER`/`I-PER` signifie que le mot correspond au début/est à l'intérieur d'une entité *personne*. -- `B-ORG`/`I-ORG` signifie que le mot correspond au début/est à l'intérieur d'une entité *organisation*. -- `B-LOC`/`I-LOC` signifie que le mot correspond au début/est à l'intérieur d'une entité *location*. -- `B-MISC`/`I-MISC` signifie que le mot correspond au début/est à l'intérieur d'une entité *divers*. - -Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : - -```python -words = raw_datasets["train"][0]["tokens"] -labels = raw_datasets["train"][0]["ner_tags"] -line1 = "" -line2 = "" -for word, label in zip(words, labels): - full_label = label_names[label] - max_length = max(len(word), len(full_label)) - line1 += word + " " * (max_length - len(word) + 1) - line2 += full_label + " " * (max_length - len(full_label) + 1) - -print(line1) -print(line2) -``` - -```python out -'EU rejects German call to boycott British lamb .' -'B-ORG O B-MISC O O O B-MISC O O' -``` - -Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur le quatrième élément du jeu d'entraînement : - -```python out -'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .' -'B-LOC O O O O B-ORG I-ORG O O O B-PER I-PER O O O O O O O O O O O B-LOC O O O O O O O' -``` - -Comme on peut le voir, les entités couvrant deux mots, comme « European Union » et « Werner Zwingmann », se voient attribuer une étiquette `B-` pour le premier mot et une étiquette `I-` pour le second. - - - -✏️ *A votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. - - - -### Traitement des données - - - -Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu au [chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées prétokénisées. Heureusement, l'API `tokenizer` peut gérer cela assez facilement. Nous devons juste avertir le `tokenizer` avec un drapeau spécial. - -Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle BERT pré-entraîné, donc nous allons commencer par télécharger et mettre en cache le *tokenizer* associé : - -```python -from transformers import AutoTokenizer - -model_checkpoint = "bert-base-cased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*. Il y a donc une version rapide disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : - -```py -tokenizer.is_fast -``` - -```python out -True -``` - -Pour tokeniser une entrée prétokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `is_split_into_words=True` : - -```py -inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) -inputs.tokens() -``` - -```python out -['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]'] -``` - -Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. - -Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [chapitre 6](/course/fr/chapter6/3)) : - -```py -inputs.word_ids() -``` - -```python out -[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] -``` - -Avec un peu de travail, nous pouvons étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (l'entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : - -```python -def align_labels_with_tokens(labels, word_ids): - new_labels = [] - current_word = None - for word_id in word_ids: - if word_id != current_word: - # Début d'un nouveau mot ! - current_word = word_id - label = -100 if word_id is None else labels[word_id] - new_labels.append(label) - elif word_id is None: - # Token spécial - new_labels.append(-100) - else: - # Même mot que le token précédent - label = labels[word_id] - # Si l'étiquette est B-XXX, nous la changeons en I-XXX - if label % 2 == 1: - label += 1 - new_labels.append(label) - - return new_labels -``` - -Essayons-le sur notre première phrase : - -```py -labels = raw_datasets["train"][0]["ner_tags"] -word_ids = inputs.word_ids() -print(labels) -print(align_labels_with_tokens(labels, word_ids)) -``` - -```python out -[3, 0, 7, 0, 0, 0, 7, 0, 0] -[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] -``` - -Comme nous pouvons le voir, notre fonction a ajouté `-100` pour les deux *tokens* spéciaux du début et de fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. - - - -✏️ *A votre tour !* Certains chercheurs préfèrent n'attribuer qu'une seule étiquette par mot et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les identifiants d'entrée en suivant cette règle. - - -Pour prétraiter notre jeu de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps. Nous allons donc écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les identifiants de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : - -```py -def tokenize_and_align_labels(examples): - tokenized_inputs = tokenizer( - examples["tokens"], truncation=True, is_split_into_words=True - ) - all_labels = examples["ner_tags"] - new_labels = [] - for i, labels in enumerate(all_labels): - word_ids = tokenized_inputs.word_ids(i) - new_labels.append(align_labels_with_tokens(labels, word_ids)) - - tokenized_inputs["labels"] = new_labels - return tokenized_inputs -``` - -Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un collateur de données. - -Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : - -```py -tokenized_datasets = raw_datasets.map( - tokenize_and_align_labels, - batched=True, - remove_columns=raw_datasets["train"].column_names, -) -``` - -Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3). - -{#if fw === 'pt'} - -## Finetuning du modèle avec l'API `Trainer` - -Le code utilisant `Trainer` sera le même que précédemment. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. - -{:else} - -## Finetuning du modèle avec Keras - -Le code utilisant Keras sera très similaire au précédent. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. - -{/if} - - -### Collation des données - -Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne fait que rembourrer les entrées (identifiants d'entrée, masque d'attention et *token* de type identifiants). Ici, nos étiquettes doivent être rembourréés exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. - -Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : - -{#if fw === 'pt'} - -```py -from transformers import DataCollatorForTokenClassification - -data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer) -``` - -{:else} - -```py -from transformers import DataCollatorForTokenClassification - -data_collator = DataCollatorForTokenClassification( - tokenizer=tokenizer, return_tensors="tf" -) -``` - -{/if} - -Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé : - -```py -batch = data_collator([tokenized_datasets["train"][i] for i in range(2)]) -batch["labels"] -``` - -```python out -tensor([[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100], - [-100, 1, 2, -100, -100, -100, -100, -100, -100, -100, -100, -100]]) -``` - -Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données : - -```py -for i in range(2): - print(tokenized_datasets["train"][i]["labels"]) -``` - -```python out -[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] -[-100, 1, 2, -100] -``` - -{#if fw === 'pt'} - -Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant des `-100`. - -{:else} - -Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_tf_dataset()`. - -```py -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["attention_mask", "input_ids", "labels", "token_type_ids"], - collate_fn=data_collator, - shuffle=True, - batch_size=16, -) - -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["attention_mask", "input_ids", "labels", "token_type_ids"], - collate_fn=data_collator, - shuffle=False, - batch_size=16, -) -``` - - -Prochain arrêt : le modèle lui-même. - -{/if} - -{#if fw === 'tf'} - -### Définir le modèle - -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. - -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'identifiant à l'étiquette et vice versa : - -```py -id2label = {str(i): label for i, label in enumerate(label_names)} -label2id = {v: k for k, v in id2label.items()} -``` - -Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle puis correctement enregistrés et téléchargés vers le *Hub* : - -```py -from transformers import TFAutoModelForTokenClassification - -model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : - -```python -model.config.num_labels -``` - -```python out -9 -``` - - - -⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez plus tard une erreur obscure lors de l'appel de `model.fit()`. Cela peut être ennuyeux à déboguer donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. - - - -### Finetuning du modèle - -Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour le taux de décroissance des poids et le taux de décroissance de l'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` : - -```python -from transformers import create_optimizer -import tensorflow as tf - -# Entraîner en mixed-precision float16 -# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction -tf.keras.mixed_precision.set_global_policy("mixed_float16") - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size -num_epochs = 3 -num_train_steps = len(tf_train_dataset) * num_epochs - -optimizer, schedule = create_optimizer( - init_lr=2e-5, - num_warmup_steps=0, - num_train_steps=num_train_steps, - weight_decay_rate=0.01, -) -model.compile(optimizer=optimizer) -``` - -Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi. - -Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* : - -```python -from transformers.keras_callbacks import PushToHubCallback - -callback = PushToHubCallback(output_dir="bert-finetuned-ner", tokenizer=tokenizer) - -model.fit( - tf_train_dataset, - validation_data=tf_eval_dataset, - callbacks=[callback], - epochs=num_epochs, -) -``` - -Vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. - - - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. - -A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir. - -{/if} - - -### Métriques - -{#if fw === 'pt'} - -Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et d'étiquettes, et retourne un dictionnaire avec les noms et les valeurs des métriques. - -Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : - -```py -!pip install seqeval -``` - -Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : - -{:else} - -Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : - -```py -!pip install seqeval -``` - -Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : - -{/if} - -```py -import evaluate - -metric = evaluate.load("seqeval") -``` - -Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : - -```py -labels = raw_datasets["train"][0]["ner_tags"] -labels = [label_names[i] for i in labels] -labels -``` - -```python out -['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O'] -``` - -Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 : - -```py -predictions = labels.copy() -predictions[2] = "O" -metric.compute(predictions=[predictions], references=[labels]) -``` - -Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie : - -```python out -{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2}, - 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1}, - 'overall_precision': 1.0, - 'overall_recall': 0.67, - 'overall_f1': 0.8, - 'overall_accuracy': 0.89} -``` - -{#if fw === 'pt'} - -Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. - -Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer la fonction softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : - -```py -import numpy as np - - -def compute_metrics(eval_preds): - logits, labels = eval_preds - predictions = np.argmax(logits, axis=-1) - - # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes - true_labels = [[label_names[l] for l in label if l != -100] for label in labels] - true_predictions = [ - [label_names[p] for (p, l) in zip(prediction, label) if l != -100] - for prediction, label in zip(predictions, labels) - ] - all_metrics = metric.compute(predictions=true_predictions, references=true_labels) - return { - "precision": all_metrics["overall_precision"], - "recall": all_metrics["overall_recall"], - "f1": all_metrics["overall_f1"], - "accuracy": all_metrics["overall_accuracy"], - } -``` - -Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un objet `model` pour *finetuner* ! - -{:else} - -Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. - -TensorFlow n'aime pas concaténer nos prédictions ensemble car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure et en laissant de côté les *tokens* `-100` qui indiquent le masquage/le remplissage. Puis nous calculerons les métriques sur la liste à la fin : - -```py -import numpy as np - -all_predictions = [] -all_labels = [] -for batch in tf_eval_dataset: - logits = model.predict(batch)["logits"] - labels = batch["labels"] - predictions = np.argmax(logits, axis=-1) - for prediction, label in zip(predictions, labels): - for predicted_idx, label_idx in zip(prediction, label): - if label_idx == -100: - continue - all_predictions.append(label_names[predicted_idx]) - all_labels.append(label_names[label_idx]) -metric.compute(predictions=[all_predictions], references=[all_labels]) -``` - - -```python out -{'LOC': {'precision': 0.91, 'recall': 0.92, 'f1': 0.91, 'number': 1668}, - 'MISC': {'precision': 0.70, 'recall': 0.79, 'f1': 0.74, 'number': 702}, - 'ORG': {'precision': 0.85, 'recall': 0.90, 'f1': 0.88, 'number': 1661}, - 'PER': {'precision': 0.95, 'recall': 0.95, 'f1': 0.95, 'number': 1617}, - 'overall_precision': 0.87, - 'overall_recall': 0.91, - 'overall_f1': 0.89, - 'overall_accuracy': 0.97} -``` - -Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès ! - -{/if} - -{#if fw === 'pt'} - -### Définir le modèle - -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances des étiquettes à la place. - -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre identifiants et étiquettes et vice versa : - -```py -id2label = {str(i): label for i, label in enumerate(label_names)} -label2id = {v: k for k, v in id2label.items()} -``` - -Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, ils seront définis dans la configuration du modèle puis correctement sauvegardés et téléchargés vers le *Hub* : - -```py -from transformers import AutoModelForTokenClassification - -model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : - -```python -model.config.num_labels -``` - -```python out -9 -``` - - - -⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. - - - -### Finetuning du modèle - -Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Une fois ceci fait, nous pouvons définir nos `TrainingArguments` : - -```python -from transformers import TrainingArguments - -args = TrainingArguments( - "bert-finetuned-ner", - evaluation_strategy="epoch", - save_strategy="epoch", - learning_rate=2e-5, - num_train_epochs=3, - weight_decay=0.01, - push_to_hub=True, -) -``` - -Vous avez déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et le taux de décroissance des poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle, l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom. - - - -Enfin, nous passons tout au `Trainer` et lançons l'entraînement : - -```python -from transformers import Trainer - -trainer = Trainer( - model=model, - args=args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - compute_metrics=compute_metrics, - tokenizer=tokenizer, -) -trainer.train() -``` - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. - -Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle : - -```py -trainer.push_to_hub(commit_message="Training complete") -``` - -Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' -``` - -Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations ! - -Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3/4) avec quelques changements pour l'évaluation. - -### Préparer tout pour l'entraînement - -D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous réutilisons notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : - -```py -from torch.utils.data import DataLoader - -train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 -) -``` - -Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne continuons pas le *finetuning* d'avant et que nous repartons bien du modèle pré-entraîné de BERT : - -```py -model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Ensuite, nous avons besoin d'un optimiseur. Nous utilisons le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont le taux de décroissance des poids est appliquée : - -```py -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` : - -```py -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : - -```py -from transformers import get_scheduler - -num_train_epochs = 3 -num_update_steps_per_epoch = len(train_dataloader) -num_training_steps = num_train_epochs * num_update_steps_per_epoch - -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -``` - -Enfin, pour pousser notre modèle vers le *Hub*, nous avons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face si vous n'êtes pas déjà connecté. Nous déterminons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur et ce que fait la fonction `get_full_repo_name()`) : - -```py -from huggingface_hub import Repository, get_full_repo_name - -model_name = "bert-finetuned-ner-accelerate" -repo_name = get_full_repo_name(model_name) -repo_name -``` - -```python out -'sgugger/bert-finetuned-ner-accelerate' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : - -```py -output_dir = "bert-finetuned-ner-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -### Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes, et les convertit en listes de chaînes de caractères comme notre objet `metric` l'attend : - -```py -def postprocess(predictions, labels): - predictions = predictions.detach().cpu().clone().numpy() - labels = labels.detach().cpu().clone().numpy() - - # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes - true_labels = [[label_names[l] for l in label if l != -100] for label in labels] - true_predictions = [ - [label_names[p] for (p, l) in zip(prediction, label) if l != -100] - for prediction, label in zip(predictions, labels) - ] - return true_labels, true_predictions -``` - -Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : - -- L'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant, puis passage en arrière et étape d'optimisation. -- L'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un batch : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée. -- Sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. - -Voici le code complet de la boucle d'entraînement : - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - model.train() - for batch in train_dataloader: - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for batch in eval_dataloader: - with torch.no_grad(): - outputs = model(**batch) - - predictions = outputs.logits.argmax(dim=-1) - labels = batch["labels"] - - # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler - predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100) - labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) - - predictions_gathered = accelerator.gather(predictions) - labels_gathered = accelerator.gather(labels) - - true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered) - metric.add_batch(predictions=true_predictions, references=true_labels) - - results = metric.compute() - print( - f"epoch {epoch}:", - { - key: results[f"overall_{key}"] - for key in ["precision", "recall", "f1", "accuracy"] - }, - ) - - # Sauvegarder et télécharger - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : - -```py -accelerator.wait_for_everyone() -unwrapped_model = accelerator.unwrap_model(model) -unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) -``` - -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model` qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. - -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle finetuné - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : - -```py -from transformers import pipeline - -# Remplacez ceci par votre propre checkpoint -model_checkpoint = "huggingface-course/bert-finetuned-ner" -token_classifier = pipeline( - "token-classification", model=model_checkpoint, aggregation_strategy="simple" -) -token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") -``` - -```python out -[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18}, - {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45}, - {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}] -``` - -Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! - + + +# Classification de tokens + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme l'attribution d'une étiquette à chaque *token* d'une phrase, tels que : + +- la **reconnaissance d'entités nommées (NER de l'anglais *Named Entity Recognition*)**, c'est-à-dire trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Ce tâche peut être formulée comme l'attribution d'une étiquette à chaque *token* faisant parti d'une entité en ayant une classe spécifique par entité, et une classe pour les *tokens* ne faisant pas parti d'entité. +- le ***part-of-speech tagging* (POS)**, c'est-à-dire marquer chaque mot dans une phrase comme correspondant à une partie particulière (comme un nom, un verbe, un adjectif, etc.). +- le ***chunking***, c'est-à-dire trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. + + + +Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens*. Ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons *finetuner* un modèle (BERT) sur la tâche de NER. Il sera alors capable de calculer des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn) les prédictions du modèle que nous allons entraîner. + +## Préparation des données + +Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. + + + +💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. + + + +### Le jeu de données CoNLL-2003 + +Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes dans ce jeu de données et la répartition entre les ensembles d'entraînement, de validation et de test : + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 14041 + }) + validation: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3250 + }) + test: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3453 + }) +}) +``` + +En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS et *chunking*. Une grande différence avec les autres jeux de données est que les entrées textuelles ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées prétokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation en sous-mots). + +Regardons le premier élément de l'ensemble d'entraînement : + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +Puisque nous voulons effectuer reconnaître des entités nommées, nous allons examiner les balises NER : + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +Ce sont les étiquettes sous forme d'entiers disponibles pour l'entraînement mais ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : + +```py +ner_feature = raw_datasets["train"].features["ner_tags"] +ner_feature +``` + +```python out +Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None) +``` + +Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `feature` : + +```py +label_names = ner_feature.feature.names +label_names +``` + +```python out +['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] +``` + +Nous avons déjà vu ces étiquettes au [chapitre 6](/course/fr/chapter6/3) lorsque nous nous sommes intéressés au pipeline `token-classification` mais nosu pouvons tout de même faire un rapide rappel : + +- `O` signifie que le mot ne correspond à aucune entité. +- `B-PER`/`I-PER` signifie que le mot correspond au début/est à l'intérieur d'une entité *personne*. +- `B-ORG`/`I-ORG` signifie que le mot correspond au début/est à l'intérieur d'une entité *organisation*. +- `B-LOC`/`I-LOC` signifie que le mot correspond au début/est à l'intérieur d'une entité *location*. +- `B-MISC`/`I-MISC` signifie que le mot correspond au début/est à l'intérieur d'une entité *divers*. + +Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : + +```python +words = raw_datasets["train"][0]["tokens"] +labels = raw_datasets["train"][0]["ner_tags"] +line1 = "" +line2 = "" +for word, label in zip(words, labels): + full_label = label_names[label] + max_length = max(len(word), len(full_label)) + line1 += word + " " * (max_length - len(word) + 1) + line2 += full_label + " " * (max_length - len(full_label) + 1) + +print(line1) +print(line2) +``` + +```python out +'EU rejects German call to boycott British lamb .' +'B-ORG O B-MISC O O O B-MISC O O' +``` + +Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur le quatrième élément du jeu d'entraînement : + +```python out +'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .' +'B-LOC O O O O B-ORG I-ORG O O O B-PER I-PER O O O O O O O O O O O B-LOC O O O O O O O' +``` + +Comme on peut le voir, les entités couvrant deux mots, comme « European Union » et « Werner Zwingmann », se voient attribuer une étiquette `B-` pour le premier mot et une étiquette `I-` pour le second. + + + +✏️ *A votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. + + + +### Traitement des données + + + +Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu au [chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées prétokénisées. Heureusement, l'API `tokenizer` peut gérer cela assez facilement. Nous devons juste avertir le `tokenizer` avec un drapeau spécial. + +Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle BERT pré-entraîné, donc nous allons commencer par télécharger et mettre en cache le *tokenizer* associé : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*. Il y a donc une version rapide disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Pour tokeniser une entrée prétokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `is_split_into_words=True` : + +```py +inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) +inputs.tokens() +``` + +```python out +['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]'] +``` + +Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. + +Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [chapitre 6](/course/fr/chapter6/3)) : + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +Avec un peu de travail, nous pouvons étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (l'entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : + +```python +def align_labels_with_tokens(labels, word_ids): + new_labels = [] + current_word = None + for word_id in word_ids: + if word_id != current_word: + # Début d'un nouveau mot ! + current_word = word_id + label = -100 if word_id is None else labels[word_id] + new_labels.append(label) + elif word_id is None: + # Token spécial + new_labels.append(-100) + else: + # Même mot que le token précédent + label = labels[word_id] + # Si l'étiquette est B-XXX, nous la changeons en I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +Essayons-le sur notre première phrase : + +```py +labels = raw_datasets["train"][0]["ner_tags"] +word_ids = inputs.word_ids() +print(labels) +print(align_labels_with_tokens(labels, word_ids)) +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +``` + +Comme nous pouvons le voir, notre fonction a ajouté `-100` pour les deux *tokens* spéciaux du début et de fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. + + + +✏️ *A votre tour !* Certains chercheurs préfèrent n'attribuer qu'une seule étiquette par mot et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les identifiants d'entrée en suivant cette règle. + + +Pour prétraiter notre jeu de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps. Nous allons donc écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les identifiants de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : + +```py +def tokenize_and_align_labels(examples): + tokenized_inputs = tokenizer( + examples["tokens"], truncation=True, is_split_into_words=True + ) + all_labels = examples["ner_tags"] + new_labels = [] + for i, labels in enumerate(all_labels): + word_ids = tokenized_inputs.word_ids(i) + new_labels.append(align_labels_with_tokens(labels, word_ids)) + + tokenized_inputs["labels"] = new_labels + return tokenized_inputs +``` + +Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un collateur de données. + +Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3). + +{#if fw === 'pt'} + +## Finetuning du modèle avec l'API `Trainer` + +Le code utilisant `Trainer` sera le même que précédemment. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. + +{:else} + +## Finetuning du modèle avec Keras + +Le code utilisant Keras sera très similaire au précédent. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. + +{/if} + + +### Collation des données + +Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne fait que rembourrer les entrées (identifiants d'entrée, masque d'attention et *token* de type identifiants). Ici, nos étiquettes doivent être rembourréés exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. + +Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer) +``` + +{:else} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification( + tokenizer=tokenizer, return_tensors="tf" +) +``` + +{/if} + +Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé : + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(2)]) +batch["labels"] +``` + +```python out +tensor([[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100], + [-100, 1, 2, -100, -100, -100, -100, -100, -100, -100, -100, -100]]) +``` + +Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données : + +```py +for i in range(2): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +[-100, 1, 2, -100] +``` + +{#if fw === 'pt'} + +Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant des `-100`. + +{:else} + +Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_tf_dataset()`. + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) + +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + + +Prochain arrêt : le modèle lui-même. + +{/if} + +{#if fw === 'tf'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'identifiant à l'étiquette et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle puis correctement enregistrés et téléchargés vers le *Hub* : + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez plus tard une erreur obscure lors de l'appel de `model.fit()`. Cela peut être ennuyeux à déboguer donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. + + + +### Finetuning du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour le taux de décroissance des poids et le taux de décroissance de l'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Entraîner en mixed-precision float16 +# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size +num_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) +``` + +Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi. + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* : + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-ner", tokenizer=tokenizer) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +Vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. + +A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir. + +{/if} + + +### Métriques + +{#if fw === 'pt'} + +Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et d'étiquettes, et retourne un dictionnaire avec les noms et les valeurs des métriques. + +Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : + +{:else} + +Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : + +{/if} + +```py +import evaluate + +metric = evaluate.load("seqeval") +``` + +Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : + +```py +labels = raw_datasets["train"][0]["ner_tags"] +labels = [label_names[i] for i in labels] +labels +``` + +```python out +['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O'] +``` + +Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 : + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie : + +```python out +{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2}, + 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1}, + 'overall_precision': 1.0, + 'overall_recall': 0.67, + 'overall_f1': 0.8, + 'overall_accuracy': 0.89} +``` + +{#if fw === 'pt'} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. + +Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer la fonction softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + all_metrics = metric.compute(predictions=true_predictions, references=true_labels) + return { + "precision": all_metrics["overall_precision"], + "recall": all_metrics["overall_recall"], + "f1": all_metrics["overall_f1"], + "accuracy": all_metrics["overall_accuracy"], + } +``` + +Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un objet `model` pour *finetuner* ! + +{:else} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. + +TensorFlow n'aime pas concaténer nos prédictions ensemble car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure et en laissant de côté les *tokens* `-100` qui indiquent le masquage/le remplissage. Puis nous calculerons les métriques sur la liste à la fin : + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(batch)["logits"] + labels = batch["labels"] + predictions = np.argmax(logits, axis=-1) + for prediction, label in zip(predictions, labels): + for predicted_idx, label_idx in zip(prediction, label): + if label_idx == -100: + continue + all_predictions.append(label_names[predicted_idx]) + all_labels.append(label_names[label_idx]) +metric.compute(predictions=[all_predictions], references=[all_labels]) +``` + + +```python out +{'LOC': {'precision': 0.91, 'recall': 0.92, 'f1': 0.91, 'number': 1668}, + 'MISC': {'precision': 0.70, 'recall': 0.79, 'f1': 0.74, 'number': 702}, + 'ORG': {'precision': 0.85, 'recall': 0.90, 'f1': 0.88, 'number': 1661}, + 'PER': {'precision': 0.95, 'recall': 0.95, 'f1': 0.95, 'number': 1617}, + 'overall_precision': 0.87, + 'overall_recall': 0.91, + 'overall_f1': 0.89, + 'overall_accuracy': 0.97} +``` + +Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès ! + +{/if} + +{#if fw === 'pt'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre identifiants et étiquettes et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, ils seront définis dans la configuration du modèle puis correctement sauvegardés et téléchargés vers le *Hub* : + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. + + + +### Finetuning du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Une fois ceci fait, nous pouvons définir nos `TrainingArguments` : + +```python +from transformers import TrainingArguments + +args = TrainingArguments( + "bert-finetuned-ner", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, + push_to_hub=True, +) +``` + +Vous avez déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et le taux de décroissance des poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle, l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom. + + + +Enfin, nous passons tout au `Trainer` et lançons l'entraînement : + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + compute_metrics=compute_metrics, + tokenizer=tokenizer, +) +trainer.train() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations ! + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3/4) avec quelques changements pour l'évaluation. + +### Préparer tout pour l'entraînement + +D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous réutilisons notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne continuons pas le *finetuning* d'avant et que nous repartons bien du modèle pré-entraîné de BERT : + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Ensuite, nous avons besoin d'un optimiseur. Nous utilisons le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont le taux de décroissance des poids est appliquée : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` : + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +Enfin, pour pousser notre modèle vers le *Hub*, nous avons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face si vous n'êtes pas déjà connecté. Nous déterminons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur et ce que fait la fonction `get_full_repo_name()`) : + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "bert-finetuned-ner-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/bert-finetuned-ner-accelerate' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes, et les convertit en listes de chaînes de caractères comme notre objet `metric` l'attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + return true_labels, true_predictions +``` + +Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- L'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant, puis passage en arrière et étape d'optimisation. +- L'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un batch : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée. +- Sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(predictions) + labels_gathered = accelerator.gather(labels) + + true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=true_predictions, references=true_labels) + + results = metric.compute() + print( + f"epoch {epoch}:", + { + key: results[f"overall_{key}"] + for key in ["precision", "recall", "f1", "accuracy"] + }, + ) + + # Sauvegarder et télécharger + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model` qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle finetuné + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +model_checkpoint = "huggingface-course/bert-finetuned-ner" +token_classifier = pipeline( + "token-classification", model=model_checkpoint, aggregation_strategy="simple" +) +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! + diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 89cd6a843..96f3b04ff 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -1,1042 +1,1042 @@ - - -# Finetuner un modèle de langage masqué - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et le *finetuner* directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*. L'apprentissage par transfert produira généralement de bons résultats. - -Cependant, il existe quelques cas où vous voudrez d'abord *finetuner* les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre jeu de données contient des contrats légaux ou des articles scientifiques, un *transformer* classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle de langage sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois ! - -Ce processus de *finetuning* d'un modèle de langage pré-entraîné sur des données *dans le domaine* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT](https://arxiv.org/abs/1801.06146) qui a été l'une des premières architectures neuronales (basées sur des LSTMs) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous. Dans cette section, nous ferons quelque chose de similaire mais avec un *transformer* au lieu d'une LSTM ! - -
-ULMFiT. - -
- -À la fin de cette section, vous aurez un [modèle de langage masqué](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) sur le *Hub* qui peut autocompléter des phrases comme indiqué ci-dessous : - - - - -Allons-y ! - - - - - -🙋 Si les termes « modélisation du langage masqué » et « modèle pré-entraîné » ne vous sont pas familiers, consultez le [chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui ! - - - -## Choix d'un modèle pré-entraîné pour la modélisation du langage masqué - -Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre « *Fill-Mask* » sur le [*Hub*](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) : - -
-Hub models. -
- -Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html). - -{#if fw === 'pt'} - -Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : - -```python -from transformers import AutoModelForMaskedLM - -model_checkpoint = "distilbert-base-uncased" -model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) -``` - -Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `num_parameters()` : - -```python -distilbert_num_parameters = model.num_parameters() / 1_000_000 -print(f"'>>> DistilBERT nombre de paramètres : {round(distilbert_num_parameters)}M'") -print(f"'>>> BERT nombre de paramètres : 110M'") -``` - -```python out -'>>> DistilBERT nombre de paramètres : 67M' -'>>> BERT nombre de paramètres : 110M' -``` - -{:else} - -Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : - -```python -from transformers import TFAutoModelForMaskedLM - -model_checkpoint = "distilbert-base-uncased" -model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) -``` - -Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `summary()` : - -```python -model(model.dummy_inputs) # Construire le modèle -model.summary() -``` - -```python out -Model: "tf_distil_bert_for_masked_lm" -_________________________________________________________________ -Layer (type) Output Shape Param # -================================================================= -distilbert (TFDistilBertMain multiple 66362880 -_________________________________________________________________ -vocab_transform (Dense) multiple 590592 -_________________________________________________________________ -vocab_layer_norm (LayerNorma multiple 1536 -_________________________________________________________________ -vocab_projector (TFDistilBer multiple 23866170 -================================================================= -Total params: 66,985,530 -Trainable params: 66,985,530 -Non-trainable params: 0 -_________________________________________________________________ -``` - -{/if} - -Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux. Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte : - -```python -text = "This is a great [MASK]." -``` - -En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que « jour », « promenade » ou « peinture ». Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les jeux de données [*English Wikipedia*](https://huggingface.co/datasets/wikipedia) et [*BookCorpus*](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et afficher les 5 meilleurs candidats : - -{#if fw === 'pt'} - -```python -import torch - -inputs = tokenizer(text, return_tensors="pt") -token_logits = model(**inputs).logits -# Trouve l'emplacement de [MASK] et extrait ses logits -mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] -mask_token_logits = token_logits[0, mask_token_index, :] -# Choisissez les candidats [MASK] avec les logits les plus élevés -top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist() - -for token in top_5_tokens: - print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'") -``` - -{:else} - -```python -import numpy as np -import tensorflow as tf - -inputs = tokenizer(text, return_tensors="np") -token_logits = model(**inputs).logits -# Trouve l'emplacement de [MASK] et extrait ses logits -mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] -mask_token_logits = token_logits[0, mask_token_index, :] -# On choisit les candidats [MASK] avec les logits les plus élevés -# Nous annulons le tableau avant argsort pour obtenir le plus grand, et non le plus petit, logits -top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist() - -for token in top_5_tokens: - print(f">>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}") -``` - -{/if} - -```python out -'>>> This is a great deal.' # C'est une bonne affaire -'>>> This is a great success.' # C'est un grand succès -'>>> This is a great adventure.' # C'est une grande aventure -'>>> This is a great idea.' # C'est une bonne idée -'>>> This is a great feat.' # C'est un grand exploit -``` - -Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de Wikipédia. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films ! - - -## Le jeu de données - -Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [*Large Movie Review Dataset*](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En *finetunant* DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *Datasets* : - -```python -from datasets import load_dataset - -imdb_dataset = load_dataset("imdb") -imdb_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['text', 'label'], - num_rows: 25000 - }) - test: Dataset({ - features: ['text', 'label'], - num_rows: 25000 - }) - unsupervised: Dataset({ - features: ['text', 'label'], - num_rows: 50000 - }) -}) -``` - -Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire : - -```python -sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) - -for row in sample: - print(f"\n'>>> Review: {row['text']}'") - print(f"'>>> Label: {row['label']}'") -``` - -```python out - -'>>> Review: This is your typical Priyadarshan movie--a bunch of loony characters out on some silly mission. His signature climax has the entire cast of the film coming together and fighting each other in some crazy moshpit over hidden money. Whether it is a winning lottery ticket in Malamaal Weekly, black money in Hera Pheri, "kodokoo" in Phir Hera Pheri, etc., etc., the director is becoming ridiculously predictable. Don\'t get me wrong; as clichéd and preposterous his movies may be, I usually end up enjoying the comedy. However, in most his previous movies there has actually been some good humor, (Hungama and Hera Pheri being noteworthy ones). Now, the hilarity of his films is fading as he is using the same formula over and over again.

Songs are good. Tanushree Datta looks awesome. Rajpal Yadav is irritating, and Tusshar is not a whole lot better. Kunal Khemu is OK, and Sharman Joshi is the best.' -'>>> Label: 0' - -'>>> Review: Okay, the story makes no sense, the characters lack any dimensionally, the best dialogue is ad-libs about the low quality of movie, the cinematography is dismal, and only editing saves a bit of the muddle, but Sam" Peckinpah directed the film. Somehow, his direction is not enough. For those who appreciate Peckinpah and his great work, this movie is a disappointment. Even a great cast cannot redeem the time the viewer wastes with this minimal effort.

The proper response to the movie is the contempt that the director San Peckinpah, James Caan, Robert Duvall, Burt Young, Bo Hopkins, Arthur Hill, and even Gig Young bring to their work. Watch the great Peckinpah films. Skip this mess.' -'>>> Label: 0' - -'>>> Review: I saw this movie at the theaters when I was about 6 or 7 years old. I loved it then, and have recently come to own a VHS version.

My 4 and 6 year old children love this movie and have been asking again and again to watch it.

I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.

I do not have older children so I do not know what they would think of it.

The songs are very cute. My daughter keeps singing them over and over.

Hope this helps.' -'>>> Label: 1' -``` - -Oui, ce sont bien des critiques de films, et si vous êtes assez âgés, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive. - - - -✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les échantillons `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet ! - - - -Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [chapitre 3](/course/fr/chapter3). Allons-y ! - -## Prétraitement des données - - - -Pour la modélisation autorégressive et la modélisation du langage masqué, une étape commune de prétraitement consiste à concaténer tous les exemples, puis à diviser le corpus entier en morceaux de taille égale. C'est très différent de notre approche habituelle, où nous nous contentons de *tokenizer* les exemples individuels. Pourquoi tout concaténer ? La raison est que les exemples individuels peuvent être tronqués s'ils sont trop longs, ce qui entraînerait la perte d'informations qui pourraient être utiles pour la tâche de modélisation du langage ! - -Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les identifiants des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans le [chapitre 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage de mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin : - -```python -def tokenize_function(examples): - result = tokenizer(examples["text"]) - if tokenizer.is_fast: - result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))] - return result - - -# Utilisation de batched=True pour activer le multithreading rapide ! -tokenized_datasets = imdb_dataset.map( - tokenize_function, batched=True, remove_columns=["text", "label"] -) -tokenized_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'input_ids', 'word_ids'], - num_rows: 25000 - }) - test: Dataset({ - features: ['attention_mask', 'input_ids', 'word_ids'], - num_rows: 25000 - }) - unsupervised: Dataset({ - features: ['attention_mask', 'input_ids', 'word_ids'], - num_rows: 50000 - }) -}) -``` - -Comme DistilBERT est un modèle de type BERT, nous pouvons voir que les textes encodés sont constitués des `input_ids` et des `attention_mask` que nous avons vus dans d'autres chapitres, ainsi que des `word_ids` que nous avons ajoutés. - -Maintenant que nos critiques de films ont été tokenisées, l'étape suivante consiste à les regrouper et à diviser le résultat en chunks. Mais quelle taille doivent avoir ces *chunks* ? Cela sera finalement déterminé par la quantité de mémoire GPU dont vous disposez, mais un bon point de départ est de voir quelle est la taille maximale du contexte du modèle. Cela peut être déduit en inspectant l'attribut `model_max_length` du *tokenizer* : - -```python -tokenizer.model_max_length -``` - - -```python out -512 -``` - -Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un *checkpoint*. Dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT. - - - -✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces *checkpoints* et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte. - - - -Ainsi, pour réaliser nos expériences sur des GPUs comme ceux disponibles sur Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire : - -```python -chunk_size = 128 -``` - - - -Notez que l'utilisation d'une petite taille peut être préjudiciable dans les scénarios du monde réel. Vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle. - - - -Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et affichons le nombre de *tokens* par commentaire : - -```python -# Le découpage produit une liste de listes pour chaque caractéristique -tokenized_samples = tokenized_datasets["train"][:3] - -for idx, sample in enumerate(tokenized_samples["input_ids"]): - print(f"'>>> Review {idx} length: {len(sample)}'") -``` - -```python out -'>>> Review 0 length: 200' -'>>> Review 1 length: 559' -'>>> Review 2 length: 192' -``` - -Nous pouvons ensuite concaténer tous ces exemples avec une simple compréhension du dictionnaire, comme suit : - -```python -concatenated_examples = { - k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys() -} -total_length = len(concatenated_examples["input_ids"]) -print(f"'>>> Longueur des critiques concaténées : {total_length}'") -``` - -```python out -'>>> Longueur des critiques concaténées : 951' -``` - -Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des *chunks* de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique : - -```python -chunks = { - k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] - for k, t in concatenated_examples.items() -} - -for chunk in chunks["input_ids"]: - print(f"'>>> Chunk length: {len(chunk)}'") -``` - -```python out -'>>> Chunk length: 128' -'>>> Chunk length: 128' -'>>> Chunk length: 128' -'>>> Chunk length: 128' -'>>> Chunk length: 128' -'>>> Chunk length: 128' -'>>> Chunk length: 128' -'>>> Chunk length: 55' -``` - -Comme vous pouvez le voir dans cet exemple, le dernier *chunk* sera généralement plus petit que la taille maximale des morceaux. Il y a deux stratégies principales pour gérer cela : - -* Abandonner le dernier morceau s'il est plus petit que `chunk_size`. -* Rembourrer le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`. - -Nous adopterons la première approche ici, donc nous allons envelopper toute la logique ci-dessus dans une seule fonction que nous pouvons appliquer à nos jeux de données tokenisés : - -```python -def group_texts(examples): - # Concaténation de tous les textes - concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} - # Calcule la longueur des textes concaténés - total_length = len(concatenated_examples[list(examples.keys())[0]]) - # Nous laissons tomber le dernier morceau s'il est plus petit que chunk_size - total_length = (total_length // chunk_size) * chunk_size - # Fractionnement par chunk de max_len - result = { - k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] - for k, t in concatenated_examples.items() - } - # Créer une nouvelle colonne d'étiquettes - result["labels"] = result["input_ids"].copy() - return result -``` - -Notez que dans la dernière étape de `group_texts()` nous créons une nouvelle colonne `labels` qui est une copie de la colonne `input_ids`. Comme nous le verrons bientôt, c'est parce que dans la modélisation du langage masqué, l'objectif est de prédire des *tokens* masqués aléatoirement dans le batch d'entrée, et en créant une colonne `labels`, nous fournissons la vérité de base pour notre modèle de langage à apprendre. - -Appliquons maintenant `group_texts()` à nos jeux de données tokenisés en utilisant notre fidèle fonction `Dataset.map()` : - -```python -lm_datasets = tokenized_datasets.map(group_texts, batched=True) -lm_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], - num_rows: 61289 - }) - test: Dataset({ - features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], - num_rows: 59905 - }) - unsupervised: Dataset({ - features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], - num_rows: 122963 - }) -}) -``` - -Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des *tokens* contigus qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* : - -```python -tokenizer.decode(lm_datasets["train"][1]["input_ids"]) -``` - -```python out -".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" -``` - -Dans cet exemple, vous pouvez voir deux critiques de films qui se chevauchent, l'une sur un film de lycée et l'autre sur les sans-abri. Voyons également à quoi ressemblent les étiquettes pour la modélisation du langage masqué : - -```python out -tokenizer.decode(lm_datasets["train"][1]["labels"]) -``` - -```python out -".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" -``` - -Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un collateur de données spécial. - -## Finetuning de DistilBERT avec l'API `Trainer` - -Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque batch de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature : - -```python -from transformers import DataCollatorForLanguageModeling - -data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) -``` - -Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au collateur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : - -```python -samples = [lm_datasets["train"][i] for i in range(2)] -for sample in samples: - _ = sample.pop("word_ids") - -for chunk in data_collator(samples)["input_ids"]: - print(f"\n'>>> {tokenizer.decode(chunk)}'") -``` - -```python output -'>>> [CLS] bromwell [MASK] is a cartoon comedy. it ran at the same [MASK] as some other [MASK] about school life, [MASK] as " teachers ". [MASK] [MASK] [MASK] in the teaching [MASK] lead [MASK] to believe that bromwell high\'[MASK] satire is much closer to reality than is " teachers ". the scramble [MASK] [MASK] financially, the [MASK]ful students whogn [MASK] right through [MASK] pathetic teachers\'pomp, the pettiness of the whole situation, distinction remind me of the schools i knew and their students. when i saw [MASK] episode in [MASK] a student repeatedly tried to burn down the school, [MASK] immediately recalled. [MASK]...' - -'>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless' -``` - -Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque batch ! - - - -✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué et pas les autres. - - - -{#if fw === 'pt'} - -Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même collateur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. - -{/if} - -Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. - -{#if fw === 'pt'} - -```py -import collections -import numpy as np - -from transformers import default_data_collator - -wwm_probability = 0.2 - - -def whole_word_masking_data_collator(features): - for feature in features: - word_ids = feature.pop("word_ids") - - # Création d'une correspondance entre les mots et les indices des tokens correspondants - mapping = collections.defaultdict(list) - current_word_index = -1 - current_word = None - for idx, word_id in enumerate(word_ids): - if word_id is not None: - if word_id != current_word: - current_word = word_id - current_word_index += 1 - mapping[current_word_index].append(idx) - - # Masquer des mots de façon aléatoire - mask = np.random.binomial(1, wwm_probability, (len(mapping),)) - input_ids = feature["input_ids"] - labels = feature["labels"] - new_labels = [-100] * len(labels) - for word_id in np.where(mask)[0]: - word_id = word_id.item() - for idx in mapping[word_id]: - new_labels[idx] = labels[idx] - input_ids[idx] = tokenizer.mask_token_id - - return default_data_collator(features) -``` - -{:else} - -```py -import collections -import numpy as np - -from transformers.data.data_collator import tf_default_data_collator - -wwm_probability = 0.2 - - -def whole_word_masking_data_collator(features): - for feature in features: - word_ids = feature.pop("word_ids") - - # Création d'une correspondance entre les mots et les indices des tokens correspondants - mapping = collections.defaultdict(list) - current_word_index = -1 - current_word = None - for idx, word_id in enumerate(word_ids): - if word_id is not None: - if word_id != current_word: - current_word = word_id - current_word_index += 1 - mapping[current_word_index].append(idx) - - # Masquer des mots de façon aléatoire - mask = np.random.binomial(1, wwm_probability, (len(mapping),)) - input_ids = feature["input_ids"] - labels = feature["labels"] - new_labels = [-100] * len(labels) - for word_id in np.where(mask)[0]: - word_id = word_id.item() - for idx in mapping[word_id]: - new_labels[idx] = labels[idx] - input_ids[idx] = tokenizer.mask_token_id - - return tf_default_data_collator(features) -``` - -{/if} - -Ensuite, nous pouvons l'essayer sur les mêmes échantillons que précédemment : - -```py -samples = [lm_datasets["train"][i] for i in range(2)] -batch = whole_word_masking_data_collator(samples) - -for chunk in batch["input_ids"]: - print(f"\n'>>> {tokenizer.decode(chunk)}'") -``` - -```python out -'>>> [CLS] bromwell high is a cartoon comedy [MASK] it ran at the same time as some other programs about school life, such as " teachers ". my 35 years in the teaching profession lead me to believe that bromwell high\'s satire is much closer to reality than is " teachers ". the scramble to survive financially, the insightful students who can see right through their pathetic teachers\'pomp, the pettiness of the whole situation, all remind me of the schools i knew and their students. when i saw the episode in which a student repeatedly tried to burn down the school, i immediately recalled.....' - -'>>> .... [MASK] [MASK] [MASK] [MASK]....... high. a classic line : inspector : i\'m here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn\'t! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless' -``` - - - -✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que les *tokens* d'un mot donné sont toujours masqués ensemble. - - - -Maintenant que nous avons deux collateurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) : - -```python -train_size = 10_000 -test_size = int(0.1 * train_size) - -downsampled_dataset = lm_datasets["train"].train_test_split( - train_size=train_size, test_size=test_size, seed=42 -) -downsampled_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], - num_rows: 10000 - }) - test: Dataset({ - features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], - num_rows: 1000 - }) -}) -``` - -Cela a automatiquement créé de nouvelles divisions `train` et `test` avec la taille du jeu d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter la taille si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction suivante : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Alternativement, vous pouvez exécuter : - -``` -huggingface-cli login -``` - -dans votre terminal préféré et connectez-vous là. - -{#if fw === 'tf'} - -Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Nous n'utiliserons ici que le collecteur de données standard, mais vous pouvez également essayer le collecteur de masquage de mots entiers et comparer les résultats à titre d'exercice : - -```python -tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=32, -) - -tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=32, -) -``` - -Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré en float16, vous devriez probablement commenter cette ligne. - -De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le *Hub* après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"lewtun/distilbert-finetuned-imdb"`. - -```python -from transformers import create_optimizer -from transformers.keras_callbacks import PushToHubCallback -import tensorflow as tf - -num_train_steps = len(tf_train_dataset) -optimizer, schedule = create_optimizer( - init_lr=2e-5, - num_warmup_steps=1_000, - num_train_steps=num_train_steps, - weight_decay_rate=0.01, -) -model.compile(optimizer=optimizer) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") - -callback = PushToHubCallback( - output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer -) -``` - -Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. - -{:else} - -Une fois que nous sommes connectés, nous pouvons spécifier les arguments pour le `Trainer` : - -```python -from transformers import TrainingArguments - -batch_size = 64 -# Montrer la perte d'entraînement à chaque époque -logging_steps = len(downsampled_dataset["train"]) // batch_size -model_name = model_checkpoint.split("/")[-1] - -training_args = TrainingArguments( - output_dir=f"{model_name}-finetuned-imdb", - overwrite_output_dir=True, - evaluation_strategy="epoch", - learning_rate=2e-5, - weight_decay=0.01, - per_device_train_batch_size=batch_size, - per_device_eval_batch_size=batch_size, - push_to_hub=True, - fp16=True, - logging_steps=logging_steps, -) -``` - -Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. - -Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` `TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. - -Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste le collateur standard `data_collator`, mais vous pouvez essayer le collateur de masquage de mots entiers et comparer les résultats comme exercice : - -```python -from transformers import Trainer - -trainer = Trainer( - model=model, - args=training_args, - train_dataset=downsampled_dataset["train"], - eval_dataset=downsampled_dataset["test"], - data_collator=data_collator, -) -``` - -Nous sommes maintenant prêts à exécuter `trainer.train()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. - -{/if} - -### Perplexité pour les modèles de langage - - - -Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de « ratés d'autocorrection » où le modèle d'un téléphone produit des compléments plutôt amusants (et souvent inappropriés) ! - -{#if fw === 'pt'} - -En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : - -```python -import math - -eval_results = trainer.evaluate() -print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") -``` - -{:else} - -En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : - -```python -import math - -eval_loss = model.evaluate(tf_eval_dataset) -print(f"Perplexité : {math.exp(eval_loss):.2f}") -``` - -{/if} - -```python out ->>> Perplexité : 21.75 -``` - -Un score de perplexité faible signifie un meilleur modèle de langue. Nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement : - -{#if fw === 'pt'} - -```python -trainer.train() -``` - -{:else} - -```python -model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) -``` - -{/if} - -et ensuite calculer la perplexité résultante sur l'ensemble de test comme précédemment : - -{#if fw === 'pt'} - -```python -eval_results = trainer.evaluate() -print(f">>> Perplexité : {math.exp(eval_results['eval_loss']):.2f}") -``` - -{:else} - -```python -eval_loss = model.evaluate(tf_eval_dataset) -print(f"Perplexité : {math.exp(eval_loss):.2f}") -``` - -{/if} - -```python out ->>> Perplexité : 11.32 -``` - -Joli. C'est une réduction considérable de la perplexité, ce qui nous indique que le modèle a appris quelque chose sur le domaine des critiques de films ! - -{#if fw === 'pt'} - -Une fois l'entraînement terminé, nous pouvons pousser la carte de modèle avec les informations d'entraînement vers le *Hub* (les *checkpoints* sont sauvegardés pendant l'entraînement lui-même) : - -```python -trainer.push_to_hub() -``` - -{/if} - - - -✏️ **A votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ? - - - -{#if fw === 'pt'} - -Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose de spécial avec la boucle d'entraînement, mais dans certains cas, vous pourriez avoir besoin de mettre en œuvre une logique personnalisée. Pour ces applications, vous pouvez utiliser 🤗 *Accelerate*. Jetons un coup d'œil ! - -## Finetuning de DistilBERT avec 🤗 Accelerate - -Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! - -Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : - -```python -def insert_random_mask(batch): - features = [dict(zip(batch, t)) for t in zip(*batch.values())] - masked_inputs = data_collator(features) - # Créer une nouvelle colonne "masquée" pour chaque colonne du jeu de données - return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} -``` - -Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié. Dans ce cas vous devez supprimer la première ligne ici : - -```py -downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) -eval_dataset = downsampled_dataset["test"].map( - insert_random_mask, - batched=True, - remove_columns=downsampled_dataset["test"].column_names, -) -eval_dataset = eval_dataset.rename_columns( - { - "masked_input_ids": "input_ids", - "masked_attention_mask": "attention_mask", - "masked_labels": "labels", - } -) -``` - -Nous pouvons ensuite configurer les *dataloaders* comme d'habitude, mais nous utiliserons le `default_data_collator` de 🤗 *Transformers* pour le jeu d'évaluation : - -```python -from torch.utils.data import DataLoader -from transformers import default_data_collator - -batch_size = 64 -train_dataloader = DataLoader( - downsampled_dataset["train"], - shuffle=True, - batch_size=batch_size, - collate_fn=data_collator, -) -eval_dataloader = DataLoader( - eval_dataset, batch_size=batch_size, collate_fn=default_data_collator -) -``` - -Nous suivons les étapes standard avec 🤗 *Accelerate*. La première est de charger une version fraîche du modèle pré-entraîné : - -``` -model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) -``` - -Ensuite, nous devons spécifier l'optimiseur. Nous utiliserons le standard `AdamW` : - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=5e-5) -``` - -Avec ces objets, nous pouvons maintenant tout préparer pour l'entraînement avec l'objet `Accelerator` : - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Maintenant que notre modèle, notre optimiseur et nos chargeurs de données sont configurés, nous pouvons spécifier le planificateur du taux d'apprentissage comme suit : - -```python -from transformers import get_scheduler - -num_train_epochs = 3 -num_update_steps_per_epoch = len(train_dataloader) -num_training_steps = num_train_epochs * num_update_steps_per_epoch - -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -``` - -Il ne reste qu'une dernière chose à faire avant de s'entraîner : créer un dépôt de modèles sur le *Hub* d'Hugging Face ! Nous pouvons utiliser la bibliothèque 🤗 *Hub* pour générer d'abord le nom complet de notre dépôt : - -```python -from huggingface_hub import get_full_repo_name - -model_name = "distilbert-base-uncased-finetuned-imdb-accelerate" -repo_name = get_full_repo_name(model_name) -repo_name -``` - -```python out -'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' -``` - -puis créer et cloner le dépôt en utilisant la classe `Repository` du 🤗 *Hub* : - -```python -from huggingface_hub import Repository - -output_dir = model_name -repo = Repository(output_dir, clone_from=repo_name) -``` - -Une fois cela fait, il ne reste plus qu'à rédiger la boucle complète d'entraînement et d'évaluation : - -```python -from tqdm.auto import tqdm -import torch -import math - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - model.train() - for batch in train_dataloader: - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - losses = [] - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - outputs = model(**batch) - - loss = outputs.loss - losses.append(accelerator.gather(loss.repeat(batch_size))) - - losses = torch.cat(losses) - losses = losses[: len(eval_dataset)] - try: - perplexity = math.exp(torch.mean(losses)) - except OverflowError: - perplexity = float("inf") - - print(f">>> Epoch {epoch}: Perplexity: {perplexity}") - - # Sauvegarder et télécharger - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -```python out ->>> Epoch 0: Perplexity: 11.397545307900472 ->>> Epoch 1: Perplexity: 10.904909330983092 ->>> Epoch 2: Perplexity: 10.729503505340409 -``` - -Cool, nous avons été en mesure d'évaluer la perplexité à chaque époque et de garantir la reproductibilité des entraînements multiples ! - -{/if} - -### Utilisation de notre modèle finetuné - -Vous pouvez interagir avec votre modèle *finetuné* soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons ce dernier pour télécharger notre modèle en utilisant le pipeline `fill-mask` : - -```python -from transformers import pipeline - -mask_filler = pipeline( - "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" -) -``` - -Nous pouvons ensuite donner au pipeline notre exemple de texte « this is a great [MASK] » et voir quelles sont les 5 premières prédictions : - -```python -preds = mask_filler(text) - -for pred in preds: - print(f">>> {pred['sequence']}") -``` - -```python out -'>>> this is a great movie.' -'>>> this is a great film.' -'>>> this is a great story.' -'>>> this is a great movies.' -'>>> this is a great character.' -``` - -Notre modèle a clairement adapté ses pondérations pour prédire les mots qui sont plus fortement associés aux films ! - - - -Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner à partir de zéro un modèle autorégressif comme GPT-2. Allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* ! - - - -✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, finetunez un classifieur sur le jeu de données IMDb pour à la fois, le checkpoint de DistilBERT pré-entraîné et e checkpoint de DistilBERT finetuné. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [chapitre 3](/course/fr/chapter3). - - + + +# Finetuner un modèle de langage masqué + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et le *finetuner* directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*. L'apprentissage par transfert produira généralement de bons résultats. + +Cependant, il existe quelques cas où vous voudrez d'abord *finetuner* les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre jeu de données contient des contrats légaux ou des articles scientifiques, un *transformer* classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle de langage sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois ! + +Ce processus de *finetuning* d'un modèle de langage pré-entraîné sur des données *dans le domaine* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT](https://arxiv.org/abs/1801.06146) qui a été l'une des premières architectures neuronales (basées sur des LSTMs) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous. Dans cette section, nous ferons quelque chose de similaire mais avec un *transformer* au lieu d'une LSTM ! + +
+ULMFiT. + +
+ +À la fin de cette section, vous aurez un [modèle de langage masqué](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) sur le *Hub* qui peut autocompléter des phrases comme indiqué ci-dessous : + + + + +Allons-y ! + + + + + +🙋 Si les termes « modélisation du langage masqué » et « modèle pré-entraîné » ne vous sont pas familiers, consultez le [chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui ! + + + +## Choix d'un modèle pré-entraîné pour la modélisation du langage masqué + +Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre « *Fill-Mask* » sur le [*Hub*](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) : + +
+Hub models. +
+ +Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html). + +{#if fw === 'pt'} + +Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : + +```python +from transformers import AutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `num_parameters()` : + +```python +distilbert_num_parameters = model.num_parameters() / 1_000_000 +print(f"'>>> DistilBERT nombre de paramètres : {round(distilbert_num_parameters)}M'") +print(f"'>>> BERT nombre de paramètres : 110M'") +``` + +```python out +'>>> DistilBERT nombre de paramètres : 67M' +'>>> BERT nombre de paramètres : 110M' +``` + +{:else} + +Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `summary()` : + +```python +model(model.dummy_inputs) # Construire le modèle +model.summary() +``` + +```python out +Model: "tf_distil_bert_for_masked_lm" +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +distilbert (TFDistilBertMain multiple 66362880 +_________________________________________________________________ +vocab_transform (Dense) multiple 590592 +_________________________________________________________________ +vocab_layer_norm (LayerNorma multiple 1536 +_________________________________________________________________ +vocab_projector (TFDistilBer multiple 23866170 +================================================================= +Total params: 66,985,530 +Trainable params: 66,985,530 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux. Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte : + +```python +text = "This is a great [MASK]." +``` + +En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que « jour », « promenade » ou « peinture ». Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les jeux de données [*English Wikipedia*](https://huggingface.co/datasets/wikipedia) et [*BookCorpus*](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et afficher les 5 meilleurs candidats : + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Trouve l'emplacement de [MASK] et extrait ses logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Choisissez les candidats [MASK] avec les logits les plus élevés +top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist() + +for token in top_5_tokens: + print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'") +``` + +{:else} + +```python +import numpy as np +import tensorflow as tf + +inputs = tokenizer(text, return_tensors="np") +token_logits = model(**inputs).logits +# Trouve l'emplacement de [MASK] et extrait ses logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# On choisit les candidats [MASK] avec les logits les plus élevés +# Nous annulons le tableau avant argsort pour obtenir le plus grand, et non le plus petit, logits +top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist() + +for token in top_5_tokens: + print(f">>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}") +``` + +{/if} + +```python out +'>>> This is a great deal.' # C'est une bonne affaire +'>>> This is a great success.' # C'est un grand succès +'>>> This is a great adventure.' # C'est une grande aventure +'>>> This is a great idea.' # C'est une bonne idée +'>>> This is a great feat.' # C'est un grand exploit +``` + +Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de Wikipédia. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films ! + + +## Le jeu de données + +Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [*Large Movie Review Dataset*](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En *finetunant* DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *Datasets* : + +```python +from datasets import load_dataset + +imdb_dataset = load_dataset("imdb") +imdb_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + test: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['text', 'label'], + num_rows: 50000 + }) +}) +``` + +Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire : + +```python +sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) + +for row in sample: + print(f"\n'>>> Review: {row['text']}'") + print(f"'>>> Label: {row['label']}'") +``` + +```python out + +'>>> Review: This is your typical Priyadarshan movie--a bunch of loony characters out on some silly mission. His signature climax has the entire cast of the film coming together and fighting each other in some crazy moshpit over hidden money. Whether it is a winning lottery ticket in Malamaal Weekly, black money in Hera Pheri, "kodokoo" in Phir Hera Pheri, etc., etc., the director is becoming ridiculously predictable. Don\'t get me wrong; as clichéd and preposterous his movies may be, I usually end up enjoying the comedy. However, in most his previous movies there has actually been some good humor, (Hungama and Hera Pheri being noteworthy ones). Now, the hilarity of his films is fading as he is using the same formula over and over again.

Songs are good. Tanushree Datta looks awesome. Rajpal Yadav is irritating, and Tusshar is not a whole lot better. Kunal Khemu is OK, and Sharman Joshi is the best.' +'>>> Label: 0' + +'>>> Review: Okay, the story makes no sense, the characters lack any dimensionally, the best dialogue is ad-libs about the low quality of movie, the cinematography is dismal, and only editing saves a bit of the muddle, but Sam" Peckinpah directed the film. Somehow, his direction is not enough. For those who appreciate Peckinpah and his great work, this movie is a disappointment. Even a great cast cannot redeem the time the viewer wastes with this minimal effort.

The proper response to the movie is the contempt that the director San Peckinpah, James Caan, Robert Duvall, Burt Young, Bo Hopkins, Arthur Hill, and even Gig Young bring to their work. Watch the great Peckinpah films. Skip this mess.' +'>>> Label: 0' + +'>>> Review: I saw this movie at the theaters when I was about 6 or 7 years old. I loved it then, and have recently come to own a VHS version.

My 4 and 6 year old children love this movie and have been asking again and again to watch it.

I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.

I do not have older children so I do not know what they would think of it.

The songs are very cute. My daughter keeps singing them over and over.

Hope this helps.' +'>>> Label: 1' +``` + +Oui, ce sont bien des critiques de films, et si vous êtes assez âgés, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive. + + + +✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les échantillons `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet ! + + + +Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [chapitre 3](/course/fr/chapter3). Allons-y ! + +## Prétraitement des données + + + +Pour la modélisation autorégressive et la modélisation du langage masqué, une étape commune de prétraitement consiste à concaténer tous les exemples, puis à diviser le corpus entier en morceaux de taille égale. C'est très différent de notre approche habituelle, où nous nous contentons de *tokenizer* les exemples individuels. Pourquoi tout concaténer ? La raison est que les exemples individuels peuvent être tronqués s'ils sont trop longs, ce qui entraînerait la perte d'informations qui pourraient être utiles pour la tâche de modélisation du langage ! + +Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les identifiants des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans le [chapitre 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage de mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin : + +```python +def tokenize_function(examples): + result = tokenizer(examples["text"]) + if tokenizer.is_fast: + result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))] + return result + + +# Utilisation de batched=True pour activer le multithreading rapide ! +tokenized_datasets = imdb_dataset.map( + tokenize_function, batched=True, remove_columns=["text", "label"] +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 50000 + }) +}) +``` + +Comme DistilBERT est un modèle de type BERT, nous pouvons voir que les textes encodés sont constitués des `input_ids` et des `attention_mask` que nous avons vus dans d'autres chapitres, ainsi que des `word_ids` que nous avons ajoutés. + +Maintenant que nos critiques de films ont été tokenisées, l'étape suivante consiste à les regrouper et à diviser le résultat en chunks. Mais quelle taille doivent avoir ces *chunks* ? Cela sera finalement déterminé par la quantité de mémoire GPU dont vous disposez, mais un bon point de départ est de voir quelle est la taille maximale du contexte du modèle. Cela peut être déduit en inspectant l'attribut `model_max_length` du *tokenizer* : + +```python +tokenizer.model_max_length +``` + + +```python out +512 +``` + +Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un *checkpoint*. Dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT. + + + +✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces *checkpoints* et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte. + + + +Ainsi, pour réaliser nos expériences sur des GPUs comme ceux disponibles sur Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire : + +```python +chunk_size = 128 +``` + + + +Notez que l'utilisation d'une petite taille peut être préjudiciable dans les scénarios du monde réel. Vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle. + + + +Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et affichons le nombre de *tokens* par commentaire : + +```python +# Le découpage produit une liste de listes pour chaque caractéristique +tokenized_samples = tokenized_datasets["train"][:3] + +for idx, sample in enumerate(tokenized_samples["input_ids"]): + print(f"'>>> Review {idx} length: {len(sample)}'") +``` + +```python out +'>>> Review 0 length: 200' +'>>> Review 1 length: 559' +'>>> Review 2 length: 192' +``` + +Nous pouvons ensuite concaténer tous ces exemples avec une simple compréhension du dictionnaire, comme suit : + +```python +concatenated_examples = { + k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys() +} +total_length = len(concatenated_examples["input_ids"]) +print(f"'>>> Longueur des critiques concaténées : {total_length}'") +``` + +```python out +'>>> Longueur des critiques concaténées : 951' +``` + +Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des *chunks* de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique : + +```python +chunks = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() +} + +for chunk in chunks["input_ids"]: + print(f"'>>> Chunk length: {len(chunk)}'") +``` + +```python out +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 55' +``` + +Comme vous pouvez le voir dans cet exemple, le dernier *chunk* sera généralement plus petit que la taille maximale des morceaux. Il y a deux stratégies principales pour gérer cela : + +* Abandonner le dernier morceau s'il est plus petit que `chunk_size`. +* Rembourrer le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`. + +Nous adopterons la première approche ici, donc nous allons envelopper toute la logique ci-dessus dans une seule fonction que nous pouvons appliquer à nos jeux de données tokenisés : + +```python +def group_texts(examples): + # Concaténation de tous les textes + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Calcule la longueur des textes concaténés + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # Nous laissons tomber le dernier morceau s'il est plus petit que chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Fractionnement par chunk de max_len + result = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() + } + # Créer une nouvelle colonne d'étiquettes + result["labels"] = result["input_ids"].copy() + return result +``` + +Notez que dans la dernière étape de `group_texts()` nous créons une nouvelle colonne `labels` qui est une copie de la colonne `input_ids`. Comme nous le verrons bientôt, c'est parce que dans la modélisation du langage masqué, l'objectif est de prédire des *tokens* masqués aléatoirement dans le batch d'entrée, et en créant une colonne `labels`, nous fournissons la vérité de base pour notre modèle de langage à apprendre. + +Appliquons maintenant `group_texts()` à nos jeux de données tokenisés en utilisant notre fidèle fonction `Dataset.map()` : + +```python +lm_datasets = tokenized_datasets.map(group_texts, batched=True) +lm_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 61289 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 59905 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 122963 + }) +}) +``` + +Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des *tokens* contigus qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* : + +```python +tokenizer.decode(lm_datasets["train"][1]["input_ids"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +Dans cet exemple, vous pouvez voir deux critiques de films qui se chevauchent, l'une sur un film de lycée et l'autre sur les sans-abri. Voyons également à quoi ressemblent les étiquettes pour la modélisation du langage masqué : + +```python out +tokenizer.decode(lm_datasets["train"][1]["labels"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un collateur de données spécial. + +## Finetuning de DistilBERT avec l'API `Trainer` + +Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque batch de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature : + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au collateur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : + +```python +samples = [lm_datasets["train"][i] for i in range(2)] +for sample in samples: + _ = sample.pop("word_ids") + +for chunk in data_collator(samples)["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python output +'>>> [CLS] bromwell [MASK] is a cartoon comedy. it ran at the same [MASK] as some other [MASK] about school life, [MASK] as " teachers ". [MASK] [MASK] [MASK] in the teaching [MASK] lead [MASK] to believe that bromwell high\'[MASK] satire is much closer to reality than is " teachers ". the scramble [MASK] [MASK] financially, the [MASK]ful students whogn [MASK] right through [MASK] pathetic teachers\'pomp, the pettiness of the whole situation, distinction remind me of the schools i knew and their students. when i saw [MASK] episode in [MASK] a student repeatedly tried to burn down the school, [MASK] immediately recalled. [MASK]...' + +'>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless' +``` + +Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque batch ! + + + +✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué et pas les autres. + + + +{#if fw === 'pt'} + +Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même collateur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. + +{/if} + +Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. + +{#if fw === 'pt'} + +```py +import collections +import numpy as np + +from transformers import default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Création d'une correspondance entre les mots et les indices des tokens correspondants + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Masquer des mots de façon aléatoire + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + + return default_data_collator(features) +``` + +{:else} + +```py +import collections +import numpy as np + +from transformers.data.data_collator import tf_default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Création d'une correspondance entre les mots et les indices des tokens correspondants + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Masquer des mots de façon aléatoire + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + + return tf_default_data_collator(features) +``` + +{/if} + +Ensuite, nous pouvons l'essayer sur les mêmes échantillons que précédemment : + +```py +samples = [lm_datasets["train"][i] for i in range(2)] +batch = whole_word_masking_data_collator(samples) + +for chunk in batch["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python out +'>>> [CLS] bromwell high is a cartoon comedy [MASK] it ran at the same time as some other programs about school life, such as " teachers ". my 35 years in the teaching profession lead me to believe that bromwell high\'s satire is much closer to reality than is " teachers ". the scramble to survive financially, the insightful students who can see right through their pathetic teachers\'pomp, the pettiness of the whole situation, all remind me of the schools i knew and their students. when i saw the episode in which a student repeatedly tried to burn down the school, i immediately recalled.....' + +'>>> .... [MASK] [MASK] [MASK] [MASK]....... high. a classic line : inspector : i\'m here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn\'t! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless' +``` + + + +✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que les *tokens* d'un mot donné sont toujours masqués ensemble. + + + +Maintenant que nous avons deux collateurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) : + +```python +train_size = 10_000 +test_size = int(0.1 * train_size) + +downsampled_dataset = lm_datasets["train"].train_test_split( + train_size=train_size, test_size=test_size, seed=42 +) +downsampled_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 10000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 1000 + }) +}) +``` + +Cela a automatiquement créé de nouvelles divisions `train` et `test` avec la taille du jeu d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter la taille si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Alternativement, vous pouvez exécuter : + +``` +huggingface-cli login +``` + +dans votre terminal préféré et connectez-vous là. + +{#if fw === 'tf'} + +Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Nous n'utiliserons ici que le collecteur de données standard, mais vous pouvez également essayer le collecteur de masquage de mots entiers et comparer les résultats à titre d'exercice : + +```python +tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré en float16, vous devriez probablement commenter cette ligne. + +De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le *Hub* après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"lewtun/distilbert-finetuned-imdb"`. + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. + +{:else} + +Une fois que nous sommes connectés, nous pouvons spécifier les arguments pour le `Trainer` : + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Montrer la perte d'entraînement à chaque époque +logging_steps = len(downsampled_dataset["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +training_args = TrainingArguments( + output_dir=f"{model_name}-finetuned-imdb", + overwrite_output_dir=True, + evaluation_strategy="epoch", + learning_rate=2e-5, + weight_decay=0.01, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + push_to_hub=True, + fp16=True, + logging_steps=logging_steps, +) +``` + +Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. + +Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` `TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. + +Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste le collateur standard `data_collator`, mais vous pouvez essayer le collateur de masquage de mots entiers et comparer les résultats comme exercice : + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=training_args, + train_dataset=downsampled_dataset["train"], + eval_dataset=downsampled_dataset["test"], + data_collator=data_collator, +) +``` + +Nous sommes maintenant prêts à exécuter `trainer.train()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. + +{/if} + +### Perplexité pour les modèles de langage + + + +Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de « ratés d'autocorrection » où le modèle d'un téléphone produit des compléments plutôt amusants (et souvent inappropriés) ! + +{#if fw === 'pt'} + +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexité : {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexité : 21.75 +``` + +Un score de perplexité faible signifie un meilleur modèle de langue. Nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement : + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +et ensuite calculer la perplexité résultante sur l'ensemble de test comme précédemment : + +{#if fw === 'pt'} + +```python +eval_results = trainer.evaluate() +print(f">>> Perplexité : {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +```python +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexité : {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexité : 11.32 +``` + +Joli. C'est une réduction considérable de la perplexité, ce qui nous indique que le modèle a appris quelque chose sur le domaine des critiques de films ! + +{#if fw === 'pt'} + +Une fois l'entraînement terminé, nous pouvons pousser la carte de modèle avec les informations d'entraînement vers le *Hub* (les *checkpoints* sont sauvegardés pendant l'entraînement lui-même) : + +```python +trainer.push_to_hub() +``` + +{/if} + + + +✏️ **A votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ? + + + +{#if fw === 'pt'} + +Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose de spécial avec la boucle d'entraînement, mais dans certains cas, vous pourriez avoir besoin de mettre en œuvre une logique personnalisée. Pour ces applications, vous pouvez utiliser 🤗 *Accelerate*. Jetons un coup d'œil ! + +## Finetuning de DistilBERT avec 🤗 Accelerate + +Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! + +Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Créer une nouvelle colonne "masquée" pour chaque colonne du jeu de données + return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} +``` + +Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié. Dans ce cas vous devez supprimer la première ligne ici : + +```py +downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) +eval_dataset = downsampled_dataset["test"].map( + insert_random_mask, + batched=True, + remove_columns=downsampled_dataset["test"].column_names, +) +eval_dataset = eval_dataset.rename_columns( + { + "masked_input_ids": "input_ids", + "masked_attention_mask": "attention_mask", + "masked_labels": "labels", + } +) +``` + +Nous pouvons ensuite configurer les *dataloaders* comme d'habitude, mais nous utiliserons le `default_data_collator` de 🤗 *Transformers* pour le jeu d'évaluation : + +```python +from torch.utils.data import DataLoader +from transformers import default_data_collator + +batch_size = 64 +train_dataloader = DataLoader( + downsampled_dataset["train"], + shuffle=True, + batch_size=batch_size, + collate_fn=data_collator, +) +eval_dataloader = DataLoader( + eval_dataset, batch_size=batch_size, collate_fn=default_data_collator +) +``` + +Nous suivons les étapes standard avec 🤗 *Accelerate*. La première est de charger une version fraîche du modèle pré-entraîné : + +``` +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Ensuite, nous devons spécifier l'optimiseur. Nous utiliserons le standard `AdamW` : + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Avec ces objets, nous pouvons maintenant tout préparer pour l'entraînement avec l'objet `Accelerator` : + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Maintenant que notre modèle, notre optimiseur et nos chargeurs de données sont configurés, nous pouvons spécifier le planificateur du taux d'apprentissage comme suit : + +```python +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +Il ne reste qu'une dernière chose à faire avant de s'entraîner : créer un dépôt de modèles sur le *Hub* d'Hugging Face ! Nous pouvons utiliser la bibliothèque 🤗 *Hub* pour générer d'abord le nom complet de notre dépôt : + +```python +from huggingface_hub import get_full_repo_name + +model_name = "distilbert-base-uncased-finetuned-imdb-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' +``` + +puis créer et cloner le dépôt en utilisant la classe `Repository` du 🤗 *Hub* : + +```python +from huggingface_hub import Repository + +output_dir = model_name +repo = Repository(output_dir, clone_from=repo_name) +``` + +Une fois cela fait, il ne reste plus qu'à rédiger la boucle complète d'entraînement et d'évaluation : + +```python +from tqdm.auto import tqdm +import torch +import math + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(**batch) + + loss = outputs.loss + losses.append(accelerator.gather(loss.repeat(batch_size))) + + losses = torch.cat(losses) + losses = losses[: len(eval_dataset)] + try: + perplexity = math.exp(torch.mean(losses)) + except OverflowError: + perplexity = float("inf") + + print(f">>> Epoch {epoch}: Perplexity: {perplexity}") + + # Sauvegarder et télécharger + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +>>> Epoch 0: Perplexity: 11.397545307900472 +>>> Epoch 1: Perplexity: 10.904909330983092 +>>> Epoch 2: Perplexity: 10.729503505340409 +``` + +Cool, nous avons été en mesure d'évaluer la perplexité à chaque époque et de garantir la reproductibilité des entraînements multiples ! + +{/if} + +### Utilisation de notre modèle finetuné + +Vous pouvez interagir avec votre modèle *finetuné* soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons ce dernier pour télécharger notre modèle en utilisant le pipeline `fill-mask` : + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +Nous pouvons ensuite donner au pipeline notre exemple de texte « this is a great [MASK] » et voir quelles sont les 5 premières prédictions : + +```python +preds = mask_filler(text) + +for pred in preds: + print(f">>> {pred['sequence']}") +``` + +```python out +'>>> this is a great movie.' +'>>> this is a great film.' +'>>> this is a great story.' +'>>> this is a great movies.' +'>>> this is a great character.' +``` + +Notre modèle a clairement adapté ses pondérations pour prédire les mots qui sont plus fortement associés aux films ! + + + +Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner à partir de zéro un modèle autorégressif comme GPT-2. Allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* ! + + + +✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, finetunez un classifieur sur le jeu de données IMDb pour à la fois, le checkpoint de DistilBERT pré-entraîné et e checkpoint de DistilBERT finetuné. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [chapitre 3](/course/fr/chapter3). + + diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index e28cf05d5..8f0659328 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -1,998 +1,998 @@ - - -# Traduction - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/course/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/course/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : - -- Le **transfert de style** ? c'est-à-dire créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). -- La **génération de réponse à des questions** c'est-à-dire créer un modèle qui génère des réponses à des questions compte tenu d'un contexte. - - - -Si vous disposez d'un corpus de textes suffisamment important en deux langues différentes (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/course/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. - -Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le jeu de données [KDE4](https://huggingface.co/datasets/kde4) qui est un jeu de données de fichiers localisés pour les applications [KDE](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du jeu de données [Opus](https://opus.nlpl.eu/) qui contient en fait le jeu de données KDE4. A noter que même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. - -Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : - - - - - -One-hot encoded labels for question answering. - - - -Comme dans les sections précédentes, vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). - -## Préparation des données - -Pour *finetuner* ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section. Notez que vous pouvez adapter assez facilement le code pour utiliser vos propres données du moment que vous disposez de paires de phrases dans les deux langues que vous voulez traduire. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. - -### Le jeu de données KDE4 - -Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") -``` - -Si vous souhaitez travailler avec une autre paire de langues, 92 langues sont disponibles au total pour ce jeu de données. Vous pouvez les voir dans la [carte du jeu de données](https://huggingface.co/datasets/kde4). - -Language available for the KDE4 dataset. - -Jetons un coup d'œil au jeu de données : - -```py -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['id', 'translation'], - num_rows: 210173 - }) -}) -``` - -Nous avons 210 173 paires de phrases. Cependant regroupées dans un seul échantillon. Nous devrons donc créer notre propre jeu de validation. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : - -```py -split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) -split_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['id', 'translation'], - num_rows: 189155 - }) - test: Dataset({ - features: ['id', 'translation'], - num_rows: 21018 - }) -}) -``` - -Nous pouvons renommer la clé `test` en `validation` comme ceci : - -```py -split_datasets["validation"] = split_datasets.pop("test") -``` - -Examinons maintenant un élément de ce jeu de données : - -```py -split_datasets["train"][1]["translation"] -``` - -```python out -{'en': 'Default to expanded threads', - 'fr': 'Par défaut, développer les fils de discussion'} -``` - -Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues qui nous intéresse. -Une particularité de ce jeu de données rempli de termes techniques informatiques est qu'ils sont tous entièrement traduits en français. Cependant, les ingénieurs français sont souvent paresseux et laissent la plupart des mots spécifiques à l'informatique en anglais lorsqu'ils parlent. Ici, par exemple, le mot « *threads* » pourrait très bien apparaître dans une phrase française, surtout dans une conversation technique. Mais dans ce jeu de données, il a été traduit en « fils de discussion ». Le modèle pré-entraîné que nous utilisons (qui a été pré-entraîné sur un plus grand corpus de phrases françaises et anglaises) prend l'option de laisser le mot tel quel : - -```py -from transformers import pipeline - -model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" -translator = pipeline("translation", model=model_checkpoint) -translator("Default to expanded threads") -``` - -```python out -[{'translation_text': 'Par défaut pour les threads élargis'}] -``` - -Un autre exemple de ce comportement peut être observé avec le mot « *plugin* » qui n'est pas officiellement un mot français mais que la plupart des francophones comprendront et ne prendront pas la peine de traduire. -Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel « module d'extension » : - -```py -split_datasets["train"][172]["translation"] -``` - -```python out -{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.', - 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."} -``` - -Notre modèle pré-entraîné, lui, s'en tient au mot anglais : - -```py -translator( - "Unable to import %1 using the OFX importer plugin. This file is not the correct format." -) -``` - -```python out -[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] -``` - -Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités (alerte *spoiler* : il le fera). - - - - - -✏️ **A votre tour !** Un autre mot anglais souvent utilisé en français est « *email* ». Trouvez le premier échantillon dans l'échantillon d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il cette même phrase ? - - - -### Traitement des données - - - -Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues. - -```python -from transformers import AutoTokenizer - -model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") -``` - -Vous pouvez remplacer le `model_checkpoint` par un tout autre modèle disponible sur le [*Hub*](https://huggingface.co/models) qui aurait votre préférence, ou par un dossier en local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. - - - -💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50 ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. - - - -La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`. - -Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme : - -``` -with open(file_path) as f: - content = f.read() -``` - -Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with`. L'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. - -Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). - -Ainsi, le prétraitement d'un échantillon ressemble à ceci : - -```python -en_sentence = split_datasets["train"][1]["translation"]["en"] -fr_sentence = split_datasets["train"][1]["translation"]["fr"] - -inputs = tokenizer(en_sentence) -with tokenizer.as_target_tokenizer(): - targets = tokenizer(fr_sentence) -``` - -Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui dans le cas d'un modèle Marian, ne va pas du tout bien se passer : - -```python -wrong_targets = tokenizer(fr_sentence) -print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) -print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) -``` - -```python out -['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] -['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] -``` - -Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme « discussion »). - -Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.). La dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : - -```python -max_input_length = 128 -max_target_length = 128 - - -def preprocess_function(examples): - inputs = [ex["en"] for ex in examples["translation"]] - targets = [ex["fr"] for ex in examples["translation"]] - model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True) - - # Configurer le tokenizer pour les cibles. - with tokenizer.as_target_tokenizer(): - labels = tokenizer(targets, max_length=max_target_length, truncation=True) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128. - - - -💡 Si vous utilisez un modèle T5 (plus précisément, un des *checkpoints* `t5-xxx`), le modèle s'attendra à ce que les entrées aient un préfixe indiquant la tâche à accomplir, comme `translate: English to French:`. - - - - - -⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`. - - - -Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les échantillons de notre jeu de données : - -```py -tokenized_datasets = split_datasets.map( - preprocess_function, - batched=True, - remove_columns=split_datasets["train"].column_names, -) -``` - -Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné ! - -{#if fw === 'pt'} - -## Finetuner le modèle avec l'API `Trainer` - -Le code actuel utilisant `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) qui est une sous-classe de `Trainer` qui nous permet de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. - -Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : - -```py -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## Finetuner du modèle avec Keras - -Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : - -```py -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) -``` - - - -💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, vous aurez donc une erreur si vous essayez de charger le modèle sans utiliser l'argument `from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, c'est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! - - - -{/if} - -Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. - -### Collecte des données - -Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en batchs dynamique. Ici, nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne rembourre que les entrées (identifiants d'entrée, masque d'attention, et *token* de type identifiants). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le *token* de *padding* du *tokenizer* afin de s'assurer que ces valeurs soient ignorées dans le calcul de la perte. - -Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que ce collateur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `model` : - -{#if fw === 'pt'} - -```py -from transformers import DataCollatorForSeq2Seq - -data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) -``` - -{:else} - -```py -from transformers import DataCollatorForSeq2Seq - -data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") -``` - -{/if} - -Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre échantillon d'entrainement tokénisé : - -```py -batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) -batch.keys() -``` - -```python out -dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) -``` - -Nous pouvons vérifier que nos étiquettes ont été rembourrées à la longueur maximale du batch, en utilisant `-100` : - -```py -batch["labels"] -``` - -```python out -tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, - -100, -100, -100, -100, -100, -100], - [ 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, - 550, 7032, 5821, 7907, 12649, 0]]) -``` - -Nous pouvons aussi jeter un coup d'œil aux identifiants d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : - -```py -batch["decoder_input_ids"] -``` - -```python out -tensor([[59513, 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, - 59513, 59513, 59513, 59513, 59513, 59513], - [59513, 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, - 817, 550, 7032, 5821, 7907, 12649]]) -``` - -Voici les étiquettes des premier et deuxième éléments de notre jeu de données : - -```py -for i in range(1, 3): - print(tokenized_datasets["train"][i]["labels"]) -``` - -```python out -[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0] -[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0] -``` - -{#if fw === 'pt'} - -Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique. - -{:else} - -Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=32, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=16, -) -``` - -{/if} - - -### Métriques - - - -{#if fw === 'pt'} - -La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes. Ainsi c'est une bonne idée d'évaluer notre modèle avec la même configuration. - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. C'est quelque chose qui est implémenté en coulisses dans 🤗 *Transformers* par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous indiquons `predict_with_generate=True`. - -{/if} - -La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que « the the the the the the the ») et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que « the »). - -L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu) qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *SacreBLEU* : - -```py -!pip install sacrebleu -``` - -Nous pouvons ensuite charger ce score via `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : - -```py -import evaluate - -metric = evaluate.load("sacrebleu") -``` - -Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables car il y a souvent plusieurs traductions possibles d'une même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais en NLP, il n'est pas rare de trouver des jeux de données ayant plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases mais les références doivent être une liste de listes de phrases. - -Essayons un exemple : - -```py -predictions = [ - "This plugin lets you translate web pages between several languages automatically." -] -references = [ - [ - "This plugin allows you to automatically translate web pages between several languages." - ] -] -metric.compute(predictions=predictions, references=references) -``` - -```python out -{'score': 46.750469682990165, - 'counts': [11, 6, 4, 3], - 'totals': [12, 11, 10, 9], - 'precisions': [91.67, 54.54, 40.0, 33.33], - 'bp': 0.9200444146293233, - 'sys_len': 12, - 'ref_len': 13} -``` - -Cela donne un score BLEU de 46.75, ce qui est plutôt bon. A titre de comparaison, le *Transformer* original dans l'article [*Attention Is All You Need*](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74). D'autre part, si nous essayons avec les deux mauvais types de prédictions (répétitions ou prédiction trop courte) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : - -```py -predictions = ["This This This This"] -references = [ - [ - "This plugin allows you to automatically translate web pages between several languages." - ] -] -metric.compute(predictions=predictions, references=references) -``` - -```python out -{'score': 1.683602693167689, - 'counts': [1, 0, 0, 0], - 'totals': [4, 3, 2, 1], - 'precisions': [25.0, 16.67, 12.5, 12.5], - 'bp': 0.10539922456186433, - 'sys_len': 4, - 'ref_len': 13} -``` - -```py -predictions = ["This plugin"] -references = [ - [ - "This plugin allows you to automatically translate web pages between several languages." - ] -] -metric.compute(predictions=predictions, references=references) -``` - -```python out -{'score': 0.0, - 'counts': [2, 1, 0, 0], - 'totals': [2, 1, 0, 0], - 'precisions': [100.0, 100.0, 0.0, 0.0], - 'bp': 0.004086771438464067, - 'sys_len': 2, - 'ref_len': 13} -``` - -Le score peut aller de 0 à 100. Plus il est élevé, mieux c'est. - -{#if fw === 'tf'} - -Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : - -```py -import numpy as np - - -def compute_metrics(): - all_preds = [] - all_labels = [] - sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) - tf_generate_dataset = sampled_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=4, - ) - for batch in tf_generate_dataset: - predictions = model.generate( - input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] - ) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - decoded_preds = [pred.strip() for pred in decoded_preds] - decoded_labels = [[label.strip()] for label in decoded_labels] - all_preds.extend(decoded_preds) - all_labels.extend(decoded_labels) - - result = metric.compute(predictions=all_preds, references=all_labels) - return {"bleu": result["score"]} -``` - -{:else} - -Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding* : - -```py -import numpy as np - - -def compute_metrics(eval_preds): - preds, labels = eval_preds - # Dans le cas où le modèle retourne plus que les logits de prédiction - if isinstance(preds, tuple): - preds = preds[0] - - decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) - - # Remplacer les -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - # Quelques post-traitements simples - decoded_preds = [pred.strip() for pred in decoded_preds] - decoded_labels = [[label.strip()] for label in decoded_labels] - - result = metric.compute(predictions=decoded_preds, references=decoded_labels) - return {"bleu": result["score"]} -``` - -{/if} - -Maintenant que c'est fait, nous sommes prêts à *finetuner* notre modèle ! - - -### Finetuner le modèle - -La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'tf'} - -Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement : - -```py -print(compute_metrics()) -``` - -``` -{'bleu': 33.26983701454733} -``` - -Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent). - -```python -from transformers import create_optimizer -from transformers.keras_callbacks import PushToHubCallback -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, -# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, -# et non le jeu de données original donc son len() est déjà num_samples // batch_size. -num_epochs = 3 -num_train_steps = len(tf_train_dataset) * num_epochs - -optimizer, schedule = create_optimizer( - init_lr=5e-5, - num_warmup_steps=0, - num_train_steps=num_train_steps, - weight_decay_rate=0.01, -) -model.compile(optimizer=optimizer) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans la [section 2](/course/fr/chapter7/2), puis nous entraînons simplement le modèle avec ce *callback* : - -```python -from transformers.keras_callbacks import PushToHubCallback - -callback = PushToHubCallback( - output_dir="marian-finetuned-kde4-en-to-fr", tokenizer=tokenizer -) - -model.fit( - tf_train_dataset, - validation_data=tf_eval_dataset, - callbacks=[callback], - epochs=num_epochs, -) -``` - -Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser le modèle avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` dans `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. - - - -Enfin, voyons à quoi ressemblent nos métriques maintenant que l'entraînement est terminé : - -```py -print(compute_metrics()) -``` - -``` -{'bleu': 57.334066271545865} -``` - -À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! - -{:else} - -Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires : - -```python -from transformers import Seq2SeqTrainingArguments - -args = Seq2SeqTrainingArguments( - f"marian-finetuned-kde4-en-to-fr", - evaluation_strategy="no", - save_strategy="epoch", - learning_rate=2e-5, - per_device_train_batch_size=32, - per_device_eval_batch_size=64, - weight_decay=0.01, - save_total_limit=3, - num_train_epochs=3, - predict_with_generate=True, - fp16=True, - push_to_hub=True, -) -``` - -En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille des batchs et une le taux de décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : - -- Nous ne définissons pas d'évaluation car elle prend du temps. Nous allons juste évaluer une fois notre modèle avant l'entraînement et après. -- Nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes. -- Nous définissons `predict_with_generate=True`, comme discuté ci-dessus. -- Nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. - -Notez que vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` à `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé d'après le répertoire de sortie que vous avez défini. Dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom. - - - - -Enfin, nous passons tout au `Seq2SeqTrainer` : - -```python -from transformers import Seq2SeqTrainer - -trainer = Seq2SeqTrainer( - model, - args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - tokenizer=tokenizer, - compute_metrics=compute_metrics, -) -``` - -Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute : - -```python -trainer.evaluate(max_length=max_target_length) -``` - -```python out -{'eval_loss': 1.6964408159255981, - 'eval_bleu': 39.26865061007616, - 'eval_runtime': 965.8884, - 'eval_samples_per_second': 21.76, - 'eval_steps_per_second': 0.341} -``` - -Un score BLEU de 39 n'est pas trop mauvais, ce qui reflète le fait que notre modèle est déjà bon pour traduire des phrases anglaises en phrases françaises. - -Vient ensuite l'entraînement, qui prendra également un peu de temps : - -```python -trainer.train() -``` - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. - -Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle. Avec un peu de chance, nous verrons une amélioration du score BLEU ! - -```py -trainer.evaluate(max_length=max_target_length) -``` - -```python out -{'eval_loss': 0.8558505773544312, - 'eval_bleu': 52.94161337775576, - 'eval_runtime': 714.2576, - 'eval_samples_per_second': 29.426, - 'eval_steps_per_second': 0.461, - 'epoch': 3.0} -``` - -C'est une amélioration de près de 14 points, ce qui est formidable. - -Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le *widget* pour l'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence. Ainsi nous spécifions que c'est un modèle de traduction : - -```py -trainer.push_to_hub(tags="translation", commit_message="Training complete") -``` - -Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' -``` - -À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! - -Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -{/if} - -{#if fw === 'pt'} - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et dans le [chapitre 3](/course/fr/chapter3/4). - -### Préparer le tout pour l'entraînement - -Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs PyTorch : - -```py -from torch.utils.data import DataLoader - -tokenized_datasets.set_format("torch") -train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 -) -``` - -Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle pré-entraîné : - -```py -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -Nous aurons alors besoin d'un optimiseur : - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* de Colab, vous devez déplacer tout ce code dans une fonction d'entraînement et ne devrait pas exécuter une cellule qui instancie un `Accelerator`. - -```py -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le chargeur de données car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : - -```py -from transformers import get_scheduler - -num_train_epochs = 3 -num_update_steps_per_epoch = len(train_dataloader) -num_training_steps = num_train_epochs * num_update_steps_per_epoch - -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -``` - -Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub* si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : - -```py -from huggingface_hub import Repository, get_full_repo_name - -model_name = "marian-finetuned-kde4-en-to-fr-accelerate" -repo_name = get_full_repo_name(model_name) -repo_name -``` - -```python out -'sgugger/marian-finetuned-kde4-en-to-fr-accelerate' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : - -```py -output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -### Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend : - -```py -def postprocess(predictions, labels): - predictions = predictions.cpu().numpy() - labels = labels.cpu().numpy() - - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - - # Remplace -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - # Quelques post-traitements simples - decoded_preds = [pred.strip() for pred in decoded_preds] - decoded_labels = [[label.strip()] for label in decoded_labels] - return decoded_preds, decoded_labels -``` - -La boucle d'entraînement ressemble beaucoup à celles de la [section 2](/course/fr/chapter7/2) et du [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation. Donc concentrons-nous sur cela ! - -La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions. C'est une méthode sur notre modèle de base et non pas le modèle enveloppé créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. - -La deuxième chose est que, comme avec la classification de [*token*](/course/fr/chapter7/2), deux processus peuvent avoir rembourrés les entrées et les étiquettes à des formes différentes. Ainsi nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - model.train() - for batch in train_dataloader: - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for batch in tqdm(eval_dataloader): - with torch.no_grad(): - generated_tokens = accelerator.unwrap_model(model).generate( - batch["input_ids"], - attention_mask=batch["attention_mask"], - max_length=128, - ) - labels = batch["labels"] - - # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler - generated_tokens = accelerator.pad_across_processes( - generated_tokens, dim=1, pad_index=tokenizer.pad_token_id - ) - labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) - - predictions_gathered = accelerator.gather(generated_tokens) - labels_gathered = accelerator.gather(labels) - - decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered) - metric.add_batch(predictions=decoded_preds, references=decoded_labels) - - results = metric.compute() - print(f"epoch {epoch}, BLEU score: {results['score']:.2f}") - - # Sauvegarder et télécharger - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -```python out -epoch 0, BLEU score: 53.47 -epoch 1, BLEU score: 54.24 -epoch 2, BLEU score: 54.44 -``` - -Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons entraîné en utilisant ce code sur [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle finetuné - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : - -```py -from transformers import pipeline - -# Remplacez ceci par votre propre checkpoint -model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr" -translator = pipeline("translation", model=model_checkpoint) -translator("Default to expanded threads") -``` - -```python out -[{'translation_text': 'Par défaut, développer les fils de discussion'}] -``` - -Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons *finetuné*. Et au lieu de laisser le mot anglais « *threads* », le modèle le traduit maintenant par la version française officielle. Il en va de même pour « *plugin* » : - -```py -translator( - "Unable to import %1 using the OFX importer plugin. This file is not the correct format." -) -``` - -```python out -[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}] -``` - -Un autre excellent exemple d'adaptation au domaine ! - - - -✏️ **A votre tour !** Que retourne le modèle sur l'échantillon avec le mot « *email* » que vous avez identifié plus tôt ? - - + + +# Traduction + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/course/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/course/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : + +- Le **transfert de style** ? c'est-à-dire créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). +- La **génération de réponse à des questions** c'est-à-dire créer un modèle qui génère des réponses à des questions compte tenu d'un contexte. + + + +Si vous disposez d'un corpus de textes suffisamment important en deux langues différentes (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/course/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. + +Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le jeu de données [KDE4](https://huggingface.co/datasets/kde4) qui est un jeu de données de fichiers localisés pour les applications [KDE](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du jeu de données [Opus](https://opus.nlpl.eu/) qui contient en fait le jeu de données KDE4. A noter que même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. + +Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Comme dans les sections précédentes, vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). + +## Préparation des données + +Pour *finetuner* ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section. Notez que vous pouvez adapter assez facilement le code pour utiliser vos propres données du moment que vous disposez de paires de phrases dans les deux langues que vous voulez traduire. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. + +### Le jeu de données KDE4 + +Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +Si vous souhaitez travailler avec une autre paire de langues, 92 langues sont disponibles au total pour ce jeu de données. Vous pouvez les voir dans la [carte du jeu de données](https://huggingface.co/datasets/kde4). + +Language available for the KDE4 dataset. + +Jetons un coup d'œil au jeu de données : + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +Nous avons 210 173 paires de phrases. Cependant regroupées dans un seul échantillon. Nous devrons donc créer notre propre jeu de validation. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : + +```py +split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) +split_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 189155 + }) + test: Dataset({ + features: ['id', 'translation'], + num_rows: 21018 + }) +}) +``` + +Nous pouvons renommer la clé `test` en `validation` comme ceci : + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +Examinons maintenant un élément de ce jeu de données : + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues qui nous intéresse. +Une particularité de ce jeu de données rempli de termes techniques informatiques est qu'ils sont tous entièrement traduits en français. Cependant, les ingénieurs français sont souvent paresseux et laissent la plupart des mots spécifiques à l'informatique en anglais lorsqu'ils parlent. Ici, par exemple, le mot « *threads* » pourrait très bien apparaître dans une phrase française, surtout dans une conversation technique. Mais dans ce jeu de données, il a été traduit en « fils de discussion ». Le modèle pré-entraîné que nous utilisons (qui a été pré-entraîné sur un plus grand corpus de phrases françaises et anglaises) prend l'option de laisser le mot tel quel : + +```py +from transformers import pipeline + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut pour les threads élargis'}] +``` + +Un autre exemple de ce comportement peut être observé avec le mot « *plugin* » qui n'est pas officiellement un mot français mais que la plupart des francophones comprendront et ne prendront pas la peine de traduire. +Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel « module d'extension » : + +```py +split_datasets["train"][172]["translation"] +``` + +```python out +{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.', + 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."} +``` + +Notre modèle pré-entraîné, lui, s'en tient au mot anglais : + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] +``` + +Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités (alerte *spoiler* : il le fera). + + + + + +✏️ **A votre tour !** Un autre mot anglais souvent utilisé en français est « *email* ». Trouvez le premier échantillon dans l'échantillon d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il cette même phrase ? + + + +### Traitement des données + + + +Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues. + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +``` + +Vous pouvez remplacer le `model_checkpoint` par un tout autre modèle disponible sur le [*Hub*](https://huggingface.co/models) qui aurait votre préférence, ou par un dossier en local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. + + + +💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50 ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. + + + +La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`. + +Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme : + +``` +with open(file_path) as f: + content = f.read() +``` + +Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with`. L'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. + +Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). + +Ainsi, le prétraitement d'un échantillon ressemble à ceci : + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui dans le cas d'un modèle Marian, ne va pas du tout bien se passer : + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme « discussion »). + +Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.). La dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : + +```python +max_input_length = 128 +max_target_length = 128 + + +def preprocess_function(examples): + inputs = [ex["en"] for ex in examples["translation"]] + targets = [ex["fr"] for ex in examples["translation"]] + model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True) + + # Configurer le tokenizer pour les cibles. + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128. + + + +💡 Si vous utilisez un modèle T5 (plus précisément, un des *checkpoints* `t5-xxx`), le modèle s'attendra à ce que les entrées aient un préfixe indiquant la tâche à accomplir, comme `translate: English to French:`. + + + + + +⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`. + + + +Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les échantillons de notre jeu de données : + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné ! + +{#if fw === 'pt'} + +## Finetuner le modèle avec l'API `Trainer` + +Le code actuel utilisant `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) qui est une sous-classe de `Trainer` qui nous permet de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. + +Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Finetuner du modèle avec Keras + +Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, vous aurez donc une erreur si vous essayez de charger le modèle sans utiliser l'argument `from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, c'est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! + + + +{/if} + +Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. + +### Collecte des données + +Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en batchs dynamique. Ici, nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne rembourre que les entrées (identifiants d'entrée, masque d'attention, et *token* de type identifiants). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le *token* de *padding* du *tokenizer* afin de s'assurer que ces valeurs soient ignorées dans le calcul de la perte. + +Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que ce collateur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `model` : + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre échantillon d'entrainement tokénisé : + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) +batch.keys() +``` + +```python out +dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) +``` + +Nous pouvons vérifier que nos étiquettes ont été rembourrées à la longueur maximale du batch, en utilisant `-100` : + +```py +batch["labels"] +``` + +```python out +tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, + -100, -100, -100, -100, -100, -100], + [ 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, + 550, 7032, 5821, 7907, 12649, 0]]) +``` + +Nous pouvons aussi jeter un coup d'œil aux identifiants d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : + +```py +batch["decoder_input_ids"] +``` + +```python out +tensor([[59513, 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, + 59513, 59513, 59513, 59513, 59513, 59513], + [59513, 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, + 817, 550, 7032, 5821, 7907, 12649]]) +``` + +Voici les étiquettes des premier et deuxième éléments de notre jeu de données : + +```py +for i in range(1, 3): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0] +[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0] +``` + +{#if fw === 'pt'} + +Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique. + +{:else} + +Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### Métriques + + + +{#if fw === 'pt'} + +La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes. Ainsi c'est une bonne idée d'évaluer notre modèle avec la même configuration. + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. C'est quelque chose qui est implémenté en coulisses dans 🤗 *Transformers* par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous indiquons `predict_with_generate=True`. + +{/if} + +La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que « the the the the the the the ») et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que « the »). + +L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu) qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *SacreBLEU* : + +```py +!pip install sacrebleu +``` + +Nous pouvons ensuite charger ce score via `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : + +```py +import evaluate + +metric = evaluate.load("sacrebleu") +``` + +Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables car il y a souvent plusieurs traductions possibles d'une même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais en NLP, il n'est pas rare de trouver des jeux de données ayant plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases mais les références doivent être une liste de listes de phrases. + +Essayons un exemple : + +```py +predictions = [ + "This plugin lets you translate web pages between several languages automatically." +] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 46.750469682990165, + 'counts': [11, 6, 4, 3], + 'totals': [12, 11, 10, 9], + 'precisions': [91.67, 54.54, 40.0, 33.33], + 'bp': 0.9200444146293233, + 'sys_len': 12, + 'ref_len': 13} +``` + +Cela donne un score BLEU de 46.75, ce qui est plutôt bon. A titre de comparaison, le *Transformer* original dans l'article [*Attention Is All You Need*](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74). D'autre part, si nous essayons avec les deux mauvais types de prédictions (répétitions ou prédiction trop courte) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : + +```py +predictions = ["This This This This"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 1.683602693167689, + 'counts': [1, 0, 0, 0], + 'totals': [4, 3, 2, 1], + 'precisions': [25.0, 16.67, 12.5, 12.5], + 'bp': 0.10539922456186433, + 'sys_len': 4, + 'ref_len': 13} +``` + +```py +predictions = ["This plugin"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 0.0, + 'counts': [2, 1, 0, 0], + 'totals': [2, 1, 0, 0], + 'precisions': [100.0, 100.0, 0.0, 0.0], + 'bp': 0.004086771438464067, + 'sys_len': 2, + 'ref_len': 13} +``` + +Le score peut aller de 0 à 100. Plus il est élevé, mieux c'est. + +{#if fw === 'tf'} + +Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["labels"].numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) + + result = metric.compute(predictions=all_preds, references=all_labels) + return {"bleu": result["score"]} +``` + +{:else} + +Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding* : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # Dans le cas où le modèle retourne plus que les logits de prédiction + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Remplacer les -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Quelques post-traitements simples + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + + result = metric.compute(predictions=decoded_preds, references=decoded_labels) + return {"bleu": result["score"]} +``` + +{/if} + +Maintenant que c'est fait, nous sommes prêts à *finetuner* notre modèle ! + + +### Finetuner le modèle + +La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent). + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà num_samples // batch_size. +num_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans la [section 2](/course/fr/chapter7/2), puis nous entraînons simplement le modèle avec ce *callback* : + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir="marian-finetuned-kde4-en-to-fr", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser le modèle avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` dans `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Enfin, voyons à quoi ressemblent nos métriques maintenant que l'entraînement est terminé : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +{:else} + +Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires : + +```python +from transformers import Seq2SeqTrainingArguments + +args = Seq2SeqTrainingArguments( + f"marian-finetuned-kde4-en-to-fr", + evaluation_strategy="no", + save_strategy="epoch", + learning_rate=2e-5, + per_device_train_batch_size=32, + per_device_eval_batch_size=64, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=3, + predict_with_generate=True, + fp16=True, + push_to_hub=True, +) +``` + +En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille des batchs et une le taux de décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : + +- Nous ne définissons pas d'évaluation car elle prend du temps. Nous allons juste évaluer une fois notre modèle avant l'entraînement et après. +- Nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes. +- Nous définissons `predict_with_generate=True`, comme discuté ci-dessus. +- Nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. + +Notez que vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` à `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé d'après le répertoire de sortie que vous avez défini. Dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom. + + + + +Enfin, nous passons tout au `Seq2SeqTrainer` : + +```python +from transformers import Seq2SeqTrainer + +trainer = Seq2SeqTrainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute : + +```python +trainer.evaluate(max_length=max_target_length) +``` + +```python out +{'eval_loss': 1.6964408159255981, + 'eval_bleu': 39.26865061007616, + 'eval_runtime': 965.8884, + 'eval_samples_per_second': 21.76, + 'eval_steps_per_second': 0.341} +``` + +Un score BLEU de 39 n'est pas trop mauvais, ce qui reflète le fait que notre modèle est déjà bon pour traduire des phrases anglaises en phrases françaises. + +Vient ensuite l'entraînement, qui prendra également un peu de temps : + +```python +trainer.train() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle. Avec un peu de chance, nous verrons une amélioration du score BLEU ! + +```py +trainer.evaluate(max_length=max_target_length) +``` + +```python out +{'eval_loss': 0.8558505773544312, + 'eval_bleu': 52.94161337775576, + 'eval_runtime': 714.2576, + 'eval_samples_per_second': 29.426, + 'eval_steps_per_second': 0.461, + 'epoch': 3.0} +``` + +C'est une amélioration de près de 14 points, ce qui est formidable. + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le *widget* pour l'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence. Ainsi nous spécifions que c'est un modèle de traduction : + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +{/if} + +{#if fw === 'pt'} + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et dans le [chapitre 3](/course/fr/chapter3/4). + +### Préparer le tout pour l'entraînement + +Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs PyTorch : + +```py +from torch.utils.data import DataLoader + +tokenized_datasets.set_format("torch") +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle pré-entraîné : + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous aurons alors besoin d'un optimiseur : + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* de Colab, vous devez déplacer tout ce code dans une fonction d'entraînement et ne devrait pas exécuter une cellule qui instancie un `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le chargeur de données car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub* si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "marian-finetuned-kde4-en-to-fr-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/marian-finetuned-kde4-en-to-fr-accelerate' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Remplace -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Quelques post-traitements simples + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +La boucle d'entraînement ressemble beaucoup à celles de la [section 2](/course/fr/chapter7/2) et du [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation. Donc concentrons-nous sur cela ! + +La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions. C'est une méthode sur notre modèle de base et non pas le modèle enveloppé créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. + +La deuxième chose est que, comme avec la classification de [*token*](/course/fr/chapter7/2), deux processus peuvent avoir rembourrés les entrées et les étiquettes à des formes différentes. Ainsi nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for batch in tqdm(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + max_length=128, + ) + labels = batch["labels"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(generated_tokens) + labels_gathered = accelerator.gather(labels) + + decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=decoded_preds, references=decoded_labels) + + results = metric.compute() + print(f"epoch {epoch}, BLEU score: {results['score']:.2f}") + + # Sauvegarder et télécharger + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +epoch 0, BLEU score: 53.47 +epoch 1, BLEU score: 54.24 +epoch 2, BLEU score: 54.44 +``` + +Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons entraîné en utilisant ce code sur [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle finetuné + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut, développer les fils de discussion'}] +``` + +Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons *finetuné*. Et au lieu de laisser le mot anglais « *threads* », le modèle le traduit maintenant par la version française officielle. Il en va de même pour « *plugin* » : + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}] +``` + +Un autre excellent exemple d'adaptation au domaine ! + + + +✏️ **A votre tour !** Que retourne le modèle sur l'échantillon avec le mot « *email* » que vous avez identifié plus tôt ? + + diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index c2177fb07..d2d570667 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -1,1083 +1,1083 @@ - - -# Résumé de textes - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents. - - - -Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [*Hub*](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : - - - - -Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. - -## Préparation d'un corpus multilingue - -Nous allons utiliser le [*Multilingual Amazon Reviews Corpus*](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué de critiques de produits Amazon en six langues et est généralement utilisé pour évaluer les classifieurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *Hub* : - -```python -from datasets import load_dataset - -spanish_dataset = load_dataset("amazon_reviews_multi", "es") -english_dataset = load_dataset("amazon_reviews_multi", "en") -english_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], - num_rows: 200000 - }) - validation: Dataset({ - features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], - num_rows: 5000 - }) - test: Dataset({ - features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], - num_rows: 5000 - }) -}) -``` - -Comme vous pouvez le voir, pour chaque langue, il y a 200 000 critiques pour la partie entraînement et 5 000 critiques pour chacune des parties validation et test. Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [chapitre 5](/course/fr/chapter5) : - -```python -def show_samples(dataset, num_samples=3, seed=42): - sample = dataset["train"].shuffle(seed=seed).select(range(num_samples)) - for example in sample: - print(f"\n'>> Title: {example['review_title']}'") - print(f"'>> Review: {example['review_body']}'") - - -show_samples(english_dataset) -``` - -```python out -'>> Title: Worked in front position, not rear' -# Travaillé en position avant, pas arrière -'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.' -# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté. - -'>> Title: meh' -'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue' -# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. - -'>> Title: Can\'t beat these for the money' -# On ne peut pas faire mieux pour le prix -'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.' -# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. -``` - - - -✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables. - - - -Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre « meh » ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : - -```python -english_dataset.set_format("pandas") -english_df = english_dataset["train"][:] -# Afficher le compte des 20 premiers produits -english_df["product_category"].value_counts()[:20] -``` - -```python out -home 17679 # maison -apparel 15951 # vêtements -wireless 15717 # sans fil -other 13418 # autres -beauty 12091 # beauté -drugstore 11730 # pharmacie -kitchen 10382 # cuisine -toy 8745 # jouets -sports 8277 # sports -automotive 7506 # automobile -lawn_and_garden 7327 # pelouse_et_jardin -home_improvement 7136 # amélioration_de_la_maison -pet_products 7082 # produits_pour_animaux_de_compagnie -digital_ebook_purchase 6749 # achat_de_livres_numériques -pc 6401 # ordinateur_personnel -electronics 6186 # électronique -office_product 5521 # produits_de_bureau -shoes 5197 # chaussures -grocery 4730 # épicerie -book 3756 # livre -Name: product_category, dtype: int64 -``` - -Les produits les plus populaires du jeu de données anglais concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`). Nous allons donc filtrer les jeux de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace. Nous pouvons donc définir une fonction simple pour le faire : - -```python -def filter_books(example): - return ( - example["product_category"] == "book" - or example["product_category"] == "digital_ebook_purchase" - ) -``` - -Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contient que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : - -```python -english_dataset.reset_format() -``` - -Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres : - -```python -spanish_books = spanish_dataset.filter(filter_books) -english_books = english_dataset.filter(filter_books) -show_samples(english_books) -``` - -```python out -'>> Title: I\'m dissapointed.' -# Je suis déçu -'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.' -# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. - -'>> Title: Good art, good price, poor design' -# Un bon art, un bon prix, un mauvais design -'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar' -# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. - -'>> Title: Helpful' -# Utile -'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' -# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. -``` - -D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue : - -```python -from datasets import concatenate_datasets, DatasetDict - -books_dataset = DatasetDict() - -for split in english_books.keys(): - books_dataset[split] = concatenate_datasets( - [english_books[split], spanish_books[split]] - ) - books_dataset[split] = books_dataset[split].shuffle(seed=42) - -# Quelques exemples -show_samples(books_dataset) -``` - -```python out -'>> Title: Easy to follow!!!!' -# Facile à suivre!!!! -'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.' -# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. - -'>> Title: PARCIALMENTE DAÑADO' -# PARTIELLEMENT ENDOMMAGÉ -'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).' -# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). - -'>> Title: no lo he podido descargar' -# Je n'ai pas pu le télécharger -'>> Review: igual que el anterior' -# même chose que ci-dessus -``` - -Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : - -
-Word count distributions for the review titles and texts. - -
- -Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit : - -```python -books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) -``` - -Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus ! - -## Modèles pour le résumé de texte - -Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique. Nous avons un corps de texte, comme une critique, que nous aimerions « traduire » en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des *transformers* pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. - -| *Transformers* | Description | Multilingue ? | -| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que le GPT-2 génère des résumés en ajoutant `TL;DR` à la fin du texte d'entrée. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des *benchmarks* populaires. | ❌ | -| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte. Par exemple, le format d'entrée du modèle pour résumer un document est `summarize: ARTICLE`. | ❌ | -| [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | -| [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | -| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | - -Comme vous pouvez le voir dans ce tableau, la majorité des *transformers* pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue « à haute ressource » comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de *transformers* multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage mais avec une particularité : au lieu d'être entraîné sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues ! - -Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche de NLP est formulée en termes d'un préfixe de *prompt* comme `summarize:` qui conditionne le modèle à adapter le texte généré au *prompt*. Comme le montre la figure ci-dessous, cela rend le T5 extrêmement polyvalent car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! - -
-Different tasks performed by the T5 architecture. - -
- -mT5 n'utilise pas de préfixes mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. - - - - -✏️ **Essayez !** Une fois que vous aurez terminé cette section, comparez le mT5 à mBART en *finetunant* ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux entrées dans les étapes de prétraitement ci-dessous. - - - -## Prétraitement des données - - - -Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au *checkpoint* du modèle pré-entraîné. Nous utiliserons `mt5-small` comme *checkpoint* afin de pouvoir *finetuner* le modèle en un temps raisonnable : - -```python -from transformers import AutoTokenizer - -model_checkpoint = "google/mt5-small" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - - - -💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de « petits » modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le *checkpoint* du modèle ! - - - -Testons le *tokenizer* de mT5 sur un petit exemple : - -```python -inputs = tokenizer( - "I loved reading the Hunger Games!" -) # J'ai adoré lire les Hunger Games ! -inputs -``` - -```python out -{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} -``` - -Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de *finetuning* au [chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du *tokenizer* pour voir à quel type de *tokenizer* nous avons affaire : - -```python -tokenizer.convert_ids_to_tokens(inputs.input_ids) -``` - -```python out -['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] -``` - -Le caractère Unicode spécial `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. - -Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour mT5 : - -```python -max_input_length = 512 -max_target_length = 30 - - -def preprocess_function(examples): - model_inputs = tokenizer( - examples["review_body"], max_length=max_input_length, truncation=True - ) - # Configurer le tokenizer pour les cibles - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`. - -Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours : - -```python -tokenized_datasets = books_dataset.map(preprocess_function, batched=True) -``` - -Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine. - - - -💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de *multithreading* des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! - - - - -## Métriques pour le résumé de texte - - - -Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que « J'ai adoré lire les Hunger Games », il existe plusieurs résumés valides, comme « J'ai adoré Hunger Games » ou « Hunger Games est une excellente lecture ». Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution. En effet, même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. - -Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de *Recall-Oriented Understudy for Gisting Evaluation*). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : - -```python -generated_summary = "I absolutely loved reading the Hunger Games" -# "J'ai absolument adoré lire les Hunger Games" -reference_summary = "I loved reading the Hunger Games" -# "J'ai adoré lire les Hunger Games" -``` - -Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. - - - -🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel. Nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). - - - -Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : - -$$ \mathrm{Recall} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, de\, réference}} $$ - -Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1, c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été « J'ai vraiment aimé lire les Hunger Games toute la nuit ». Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui dans le contexte de ROUGE, mesure la proportion du résumé généré qui est pertinente : - -$$ \mathrm{Precision} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, généré}} $$ - -En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le *package* `rouge_score` : - -```py -!pip install rouge_score -``` - -et ensuite charger la métrique ROUGE comme suit : - -```python -import evaluate - -rouge_score = evaluate.load("rouge") -``` - -Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : - -```python -scores = rouge_score.compute( - predictions=[generated_summary], references=[reference_summary] -) -scores -``` - -```python out -{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), - 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)), - 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), - 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} -``` - -Whoa, il y a pas mal d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule des intervalles de confiance pour la précision, le rappel et le score F1. Ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur `mid` de nos scores : - -```python -scores["rouge1"].mid -``` - -```python out -Score(precision=0.86, recall=1.0, fmeasure=0.92) -``` - -Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. Le « sum » dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. - - - -✏️ **Essayez !** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. - - - -Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une *baseline* solide, mais simple ! - -### Création d'une base de référence solide - -Une *baseline* commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la *baseline* _lead-3_. Nous pourrions utiliser les points pour tracker les limites des phrases mais cela échouera avec des acronymes comme « U.S. » ou « U.N. ». Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le *package* en utilisant `pip` comme suit : - -```python -!pip install nltk -``` - -puis téléchargez les règles de ponctuation : - -```python -import nltk - -nltk.download("punkt") -``` - -Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et tester le tout sur un exemple d'entraînement : - -```python -from nltk.tokenize import sent_tokenize - - -def three_sentence_summary(text): - return "\n".join(sent_tokenize(text)[:3]) - - -print(three_sentence_summary(books_dataset["train"][1]["review_body"])) -``` - -```python out -'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' -# J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" -'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' -# "Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz." -'She found Strangers.' -# Elle a trouvé Strangers. -``` - -Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces résumés d'un jeu de données et calcule les scores ROUGE pour la ligne de base : - -```python -def evaluate_baseline(dataset, metric): - summaries = [three_sentence_summary(text) for text in dataset["review_body"]] - return metric.compute(predictions=summaries, references=dataset["review_title"]) -``` - -Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant Pandas : - -```python -import pandas as pd - -score = evaluate_baseline(books_dataset["validation"], rouge_score) -rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"] -rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names) -rouge_dict -``` - -```python out -{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} -``` - -Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste. Ceci reflète probablement le fait que les titres des critiques sont typiquement concis et donc que la *baseline* *lead-3* est trop verbeuse. Maintenant que nous disposons d'une bonne *baseline*, concentrons-nous sur le *finetuning* du mT5 ! - -{#if fw === 'pt'} - -## Finetuning de mT5 avec l'API `Trainer` - -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : - -```python -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## Finetuning de mT5 avec Keras - -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `TFAutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : - -```python -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{/if} - - - -💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant le *finetuning* du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte du [chapitre 3](/course/fr/chapter3) où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. - - - -La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là : - -``` -huggingface-cli login -``` - -{#if fw === 'pt'} - -Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences : - -```python -from transformers import Seq2SeqTrainingArguments - -batch_size = 8 -num_train_epochs = 8 -# La perte d'entraînement à chaque époque -logging_steps = len(tokenized_datasets["train"]) // batch_size -model_name = model_checkpoint.split("/")[-1] - -args = Seq2SeqTrainingArguments( - output_dir=f"{model_name}-finetuned-amazon-en-es", - evaluation_strategy="epoch", - learning_rate=5.6e-5, - per_device_train_batch_size=batch_size, - per_device_eval_batch_size=batch_size, - weight_decay=0.01, - save_total_limit=3, - num_train_epochs=num_train_epochs, - predict_with_generate=True, - logging_steps=logging_steps, - push_to_hub=True, -) -``` - -Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté au [chapitre 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les *tokens* un par un, et ceci est implémenté par la méthode `generate()`. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à trois *checkpoints* pendant l'entraînement. C'est parce que même la plus petite version de mT5 utilise environ 1 Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. - -L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le *Hub* après l'entraînement. Vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` à `Seq2SeqTrainingArguments`. - -La prochaine chose que nous devons faire est de fournir à `Seq2SeqTrainer` une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : - - -```python -import numpy as np - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - # Décoder les résumés générés en texte - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - # Décoder les résumés de référence en texte - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # ROUGE attend une nouvelle ligne après chaque phrase - decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] - decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] - # Calcul des scores ROUGE - result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True - ) - # Extraire les scores médians - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - return {k: round(v, 4) for k, v in result.items()} -``` - -{/if} - -Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). - -Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le *modèle* : - -{#if fw === 'pt'} - -```python -from transformers import DataCollatorForSeq2Seq - -data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) -``` - -{:else} - -```python -from transformers import DataCollatorForSeq2Seq - -data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") -``` - -{/if} - -Voyons ce que produit ce collateur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : - -```python -tokenized_datasets = tokenized_datasets.remove_columns( - books_dataset["train"].column_names -) -``` - -Comme le collateur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : - -```python -features = [tokenized_datasets["train"][i] for i in range(2)] -data_collator(features) -``` - -```python out -{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[ 1494, 259, 8622, 390, 259, 262, 2316, 3435, 955, - 772, 281, 772, 1617, 263, 305, 14701, 260, 1385, - 3031, 259, 24146, 332, 1037, 259, 43906, 305, 336, - 260, 1, 0, 0, 0, 0, 0, 0], - [ 259, 27531, 13483, 259, 7505, 260, 112240, 15192, 305, - 53198, 276, 259, 74060, 263, 260, 459, 25640, 776, - 2119, 336, 259, 2220, 259, 18896, 288, 4906, 288, - 1037, 3931, 260, 7083, 101476, 1143, 260, 1]]), 'labels': tensor([[ 7483, 259, 2364, 15695, 1, -100], - [ 259, 27531, 13483, 259, 7505, 1]]), 'decoder_input_ids': tensor([[ 0, 7483, 259, 2364, 15695, 1], - [ 0, 259, 27531, 13483, 259, 7505]])} -``` - -La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un *token* `[PAD]` (dont l'identifiant est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un *token* `[PAD]` dans la première entrée. - -{#if fw === 'pt'} - -Nous avons enfin tous les ingrédients dont nous avons besoin pour l'entraînement ! Nous devons maintenant simplement instancier le `Seq2SeqTrainer` avec les arguments : - -```python -from transformers import Seq2SeqTrainer - -trainer = Seq2SeqTrainer( - model, - args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - tokenizer=tokenizer, - compute_metrics=compute_metrics, -) -``` - -et lancer notre course d'entraînement : - -```python -trainer.train() -``` - -Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `Trainer.evaluate()` : - -```python -trainer.evaluate() -``` - -```python out -{'eval_loss': 3.028524398803711, - 'eval_rouge1': 16.9728, - 'eval_rouge2': 8.2969, - 'eval_rougeL': 16.8366, - 'eval_rougeLsum': 16.851, - 'eval_gen_len': 10.1597, - 'eval_runtime': 6.1054, - 'eval_samples_per_second': 38.982, - 'eval_steps_per_second': 4.914} -``` - -D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre *baseline* *lead-3*. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : - -``` -trainer.push_to_hub(commit_message="Training complete", tags="summarization") -``` - -```python out -'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' -``` - -Ceci sauvegardera le *checkpoint* et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le *widget* sur le *Hub* sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! - -Pour conclure cette section, voyons comment nous pouvons également *finetuner* mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. - -{:else} - -Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le collateur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=8, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=8, -) -``` - -Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons : - -```python -from transformers import create_optimizer -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, -# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, -# et non le jeu de données original donc son len() est déjà num_samples // batch_size. -num_train_epochs = 8 -num_train_steps = len(tf_train_dataset) * num_train_epochs -model_name = model_checkpoint.split("/")[-1] - -optimizer, schedule = create_optimizer( - init_lr=5.6e-5, - num_warmup_steps=0, - num_train_steps=num_train_steps, - weight_decay_rate=0.01, -) - -model.compile(optimizer=optimizer) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Et enfin, nous *finetunons* le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : - -```python -from transformers.keras_callbacks import PushToHubCallback - -callback = PushToHubCallback( - output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer -) - -model.fit( - tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8 -) -``` - -Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : - -```python -from tqdm import tqdm -import numpy as np - -all_preds = [] -all_labels = [] -for batch in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] - decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] - all_preds.extend(decoded_preds) - all_labels.extend(decoded_labels) -``` - -Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile : - -```python -result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True -) -result = {key: value.mid.fmeasure * 100 for key, value in result.items()} -{k: round(v, 4) for k, v in result.items()} -``` - -``` -{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815} -``` - - -{/if} - -{#if fw === 'pt'} - -## Finetuning de mT5 avec 🤗 Accelerate - -Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans le [chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! - -### Préparer tout pour l'entraînement - -La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos échantillons. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : - -```python -tokenized_datasets.set_format("torch") -``` - -Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache : - -```python -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : - -```python -from torch.utils.data import DataLoader - -batch_size = 8 -train_dataloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - collate_fn=data_collator, - batch_size=batch_size, -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size -) -``` - -La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes : - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` : - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons préparé nos objets, il reste trois choses à faire : - -* définir le programmeur du taux d'apprentissage, -* implémenter une fonction pour post-traiter les résumés pour l'évaluation, -* créer un dépôt sur le *Hub* vers lequel nous pouvons pousser notre modèle. - -Pour le programmeur de taux d'apprentissage, nous utiliserons le programmeur linéaire standard des sections précédentes : - -```python -from transformers import get_scheduler - -num_train_epochs = 10 -num_update_steps_per_epoch = len(train_dataloader) -num_training_steps = num_train_epochs * num_update_steps_per_epoch - -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -``` - -Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE et nous pouvons y parvenir avec le bout de code suivant : - -```python -def postprocess_text(preds, labels): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # ROUGE attend une nouvelle ligne après chaque phrase - preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] - labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] - - return preds, labels -``` - -Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. - -Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre dépôt, et la bibliothèque a une fonction utilitaire pour combiner l'identifiant du dépôt avec le profil de l'utilisateur : - -```python -from huggingface_hub import get_full_repo_name - -model_name = "test-bert-finetuned-squad-accelerate" -repo_name = get_full_repo_name(model_name) -repo_name -``` - -```python out -'lewtun/mt5-finetuned-amazon-en-es-accelerate' -``` - -Nous pouvons maintenant utiliser ce nom de dépôt pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : - -```python -from huggingface_hub import Repository - -output_dir = "results-mt5-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Cela nous permettra de pousser les artefacts vers le *Hub* en appelant la méthode `repo.push_to_hub()` pendant l'entraînement ! Concluons maintenant notre analyse en écrivant la boucle d'entraînement. - -### Boucle d'entraînement - -La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales : - -1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, -2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, -3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, -4. sauvegarder les *checkpoints* et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les *checkpoints* par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'1 Go ! - -Ces étapes peuvent être vues dans le bloc de code suivant : - -```python -from tqdm.auto import tqdm -import torch -import numpy as np - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - generated_tokens = accelerator.unwrap_model(model).generate( - batch["input_ids"], - attention_mask=batch["attention_mask"], - ) - - generated_tokens = accelerator.pad_across_processes( - generated_tokens, dim=1, pad_index=tokenizer.pad_token_id - ) - labels = batch["labels"] - - # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes - labels = accelerator.pad_across_processes( - batch["labels"], dim=1, pad_index=tokenizer.pad_token_id - ) - - generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() - labels = accelerator.gather(labels).cpu().numpy() - - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - if isinstance(generated_tokens, tuple): - generated_tokens = generated_tokens[0] - decoded_preds = tokenizer.batch_decode( - generated_tokens, skip_special_tokens=True - ) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - decoded_preds, decoded_labels = postprocess_text( - decoded_preds, decoded_labels - ) - - rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) - - # Calculer les métriques - result = rouge_score.compute() - # Extract the median ROUGE scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - result = {k: round(v, 4) for k, v in result.items()} - print(f"Epoch {epoch}:", result) - - # Sauvegarder et télécharger - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -```python out -Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005} -Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306} -Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468} -Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518} -Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029} -Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913} -Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701} -Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194} -Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744} -Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} -``` - -Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`. - -{/if} - -## Utilisation de votre modèle finetuné - -Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le *widget* d'inférence, soit avec un objet `pipeline`, comme suit : - -```python -from transformers import pipeline - -hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" -summarizer = pipeline("summarization", model=hub_model_id) -``` - -Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré : - -```python -def print_summary(idx): - review = books_dataset["test"][idx]["review_body"] - title = books_dataset["test"][idx]["review_title"] - summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"] - print(f"'>>> Review: {review}'") - print(f"\n'>>> Title: {title}'") - print(f"\n'>>> Summary: {summary}'") -``` - -Examinons l'un des exemples anglais que nous recevons : - -```python -print_summary(100) -``` - -```python out -'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.' -# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. - -'>>> Title: Not impressed at all... buy something else' -# Pas du tout impressionné... achetez autre chose. - -'>>> Summary: Nothing special at all about this product' -# Rien de spécial à propos de ce produit -``` - -Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : - -```python -print_summary(0) -``` - -```python out -'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' -# C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. - -'>>> Title: Buena literatura para adolescentes' -# Bonne littérature pour les adolescents - -'>>> Summary: Muy facil de leer' -# Très facile à lire -``` - -Le résumé a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! - -Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. + + +# Résumé de textes + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents. + + + +Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [*Hub*](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : + + + + +Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. + +## Préparation d'un corpus multilingue + +Nous allons utiliser le [*Multilingual Amazon Reviews Corpus*](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué de critiques de produits Amazon en six langues et est généralement utilisé pour évaluer les classifieurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *Hub* : + +```python +from datasets import load_dataset + +spanish_dataset = load_dataset("amazon_reviews_multi", "es") +english_dataset = load_dataset("amazon_reviews_multi", "en") +english_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 200000 + }) + validation: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) + test: Dataset({ + features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'], + num_rows: 5000 + }) +}) +``` + +Comme vous pouvez le voir, pour chaque langue, il y a 200 000 critiques pour la partie entraînement et 5 000 critiques pour chacune des parties validation et test. Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [chapitre 5](/course/fr/chapter5) : + +```python +def show_samples(dataset, num_samples=3, seed=42): + sample = dataset["train"].shuffle(seed=seed).select(range(num_samples)) + for example in sample: + print(f"\n'>> Title: {example['review_title']}'") + print(f"'>> Review: {example['review_body']}'") + + +show_samples(english_dataset) +``` + +```python out +'>> Title: Worked in front position, not rear' +# Travaillé en position avant, pas arrière +'>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.' +# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté. + +'>> Title: meh' +'>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue' +# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. + +'>> Title: Can\'t beat these for the money' +# On ne peut pas faire mieux pour le prix +'>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.' +# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. +``` + + + +✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables. + + + +Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre « meh » ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Afficher le compte des 20 premiers produits +english_df["product_category"].value_counts()[:20] +``` + +```python out +home 17679 # maison +apparel 15951 # vêtements +wireless 15717 # sans fil +other 13418 # autres +beauty 12091 # beauté +drugstore 11730 # pharmacie +kitchen 10382 # cuisine +toy 8745 # jouets +sports 8277 # sports +automotive 7506 # automobile +lawn_and_garden 7327 # pelouse_et_jardin +home_improvement 7136 # amélioration_de_la_maison +pet_products 7082 # produits_pour_animaux_de_compagnie +digital_ebook_purchase 6749 # achat_de_livres_numériques +pc 6401 # ordinateur_personnel +electronics 6186 # électronique +office_product 5521 # produits_de_bureau +shoes 5197 # chaussures +grocery 4730 # épicerie +book 3756 # livre +Name: product_category, dtype: int64 +``` + +Les produits les plus populaires du jeu de données anglais concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`). Nous allons donc filtrer les jeux de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace. Nous pouvons donc définir une fonction simple pour le faire : + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contient que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : + +```python +english_dataset.reset_format() +``` + +Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres : + +```python +spanish_books = spanish_dataset.filter(filter_books) +english_books = english_dataset.filter(filter_books) +show_samples(english_books) +``` + +```python out +'>> Title: I\'m dissapointed.' +# Je suis déçu +'>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.' +# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. + +'>> Title: Good art, good price, poor design' +# Un bon art, un bon prix, un mauvais design +'>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar' +# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. + +'>> Title: Helpful' +# Utile +'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' +# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. +``` + +D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue : + +```python +from datasets import concatenate_datasets, DatasetDict + +books_dataset = DatasetDict() + +for split in english_books.keys(): + books_dataset[split] = concatenate_datasets( + [english_books[split], spanish_books[split]] + ) + books_dataset[split] = books_dataset[split].shuffle(seed=42) + +# Quelques exemples +show_samples(books_dataset) +``` + +```python out +'>> Title: Easy to follow!!!!' +# Facile à suivre!!!! +'>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.' +# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. + +'>> Title: PARCIALMENTE DAÑADO' +# PARTIELLEMENT ENDOMMAGÉ +'>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).' +# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). + +'>> Title: no lo he podido descargar' +# Je n'ai pas pu le télécharger +'>> Review: igual que el anterior' +# même chose que ci-dessus +``` + +Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : + +
+Word count distributions for the review titles and texts. + +
+ +Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit : + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus ! + +## Modèles pour le résumé de texte + +Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique. Nous avons un corps de texte, comme une critique, que nous aimerions « traduire » en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des *transformers* pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. + +| *Transformers* | Description | Multilingue ? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que le GPT-2 génère des résumés en ajoutant `TL;DR` à la fin du texte d'entrée. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des *benchmarks* populaires. | ❌ | +| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte. Par exemple, le format d'entrée du modèle pour résumer un document est `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | + +Comme vous pouvez le voir dans ce tableau, la majorité des *transformers* pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue « à haute ressource » comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de *transformers* multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage mais avec une particularité : au lieu d'être entraîné sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues ! + +Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche de NLP est formulée en termes d'un préfixe de *prompt* comme `summarize:` qui conditionne le modèle à adapter le texte généré au *prompt*. Comme le montre la figure ci-dessous, cela rend le T5 extrêmement polyvalent car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 n'utilise pas de préfixes mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. + + + + +✏️ **Essayez !** Une fois que vous aurez terminé cette section, comparez le mT5 à mBART en *finetunant* ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux entrées dans les étapes de prétraitement ci-dessous. + + + +## Prétraitement des données + + + +Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au *checkpoint* du modèle pré-entraîné. Nous utiliserons `mt5-small` comme *checkpoint* afin de pouvoir *finetuner* le modèle en un temps raisonnable : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de « petits » modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le *checkpoint* du modèle ! + + + +Testons le *tokenizer* de mT5 sur un petit exemple : + +```python +inputs = tokenizer( + "I loved reading the Hunger Games!" +) # J'ai adoré lire les Hunger Games ! +inputs +``` + +```python out +{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de *finetuning* au [chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du *tokenizer* pour voir à quel type de *tokenizer* nous avons affaire : + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +Le caractère Unicode spécial `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. + +Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour mT5 : + +```python +max_input_length = 512 +max_target_length = 30 + + +def preprocess_function(examples): + model_inputs = tokenizer( + examples["review_body"], max_length=max_input_length, truncation=True + ) + # Configurer le tokenizer pour les cibles + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`. + +Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours : + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine. + + + +💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de *multithreading* des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! + + + + +## Métriques pour le résumé de texte + + + +Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que « J'ai adoré lire les Hunger Games », il existe plusieurs résumés valides, comme « J'ai adoré Hunger Games » ou « Hunger Games est une excellente lecture ». Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution. En effet, même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. + +Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de *Recall-Oriented Understudy for Gisting Evaluation*). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +# "J'ai absolument adoré lire les Hunger Games" +reference_summary = "I loved reading the Hunger Games" +# "J'ai adoré lire les Hunger Games" +``` + +Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. + + + +🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel. Nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : + +$$ \mathrm{Recall} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, de\, réference}} $$ + +Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1, c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été « J'ai vraiment aimé lire les Hunger Games toute la nuit ». Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui dans le contexte de ROUGE, mesure la proportion du résumé généré qui est pertinente : + +$$ \mathrm{Precision} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, généré}} $$ + +En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le *package* `rouge_score` : + +```py +!pip install rouge_score +``` + +et ensuite charger la métrique ROUGE comme suit : + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : + +```python +scores = rouge_score.compute( + predictions=[generated_summary], references=[reference_summary] +) +scores +``` + +```python out +{'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)), + 'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)), + 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} +``` + +Whoa, il y a pas mal d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule des intervalles de confiance pour la précision, le rappel et le score F1. Ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur `mid` de nos scores : + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. Le « sum » dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. + + + +✏️ **Essayez !** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. + + + +Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une *baseline* solide, mais simple ! + +### Création d'une base de référence solide + +Une *baseline* commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la *baseline* _lead-3_. Nous pourrions utiliser les points pour tracker les limites des phrases mais cela échouera avec des acronymes comme « U.S. » ou « U.N. ». Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le *package* en utilisant `pip` comme suit : + +```python +!pip install nltk +``` + +puis téléchargez les règles de ponctuation : + +```python +import nltk + +nltk.download("punkt") +``` + +Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et tester le tout sur un exemple d'entraînement : + +```python +from nltk.tokenize import sent_tokenize + + +def three_sentence_summary(text): + return "\n".join(sent_tokenize(text)[:3]) + + +print(three_sentence_summary(books_dataset["train"][1]["review_body"])) +``` + +```python out +'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' +# J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' +# "Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz." +'She found Strangers.' +# Elle a trouvé Strangers. +``` + +Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces résumés d'un jeu de données et calcule les scores ROUGE pour la ligne de base : + +```python +def evaluate_baseline(dataset, metric): + summaries = [three_sentence_summary(text) for text in dataset["review_body"]] + return metric.compute(predictions=summaries, references=dataset["review_title"]) +``` + +Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant Pandas : + +```python +import pandas as pd + +score = evaluate_baseline(books_dataset["validation"], rouge_score) +rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"] +rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names) +rouge_dict +``` + +```python out +{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} +``` + +Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste. Ceci reflète probablement le fait que les titres des critiques sont typiquement concis et donc que la *baseline* *lead-3* est trop verbeuse. Maintenant que nous disposons d'une bonne *baseline*, concentrons-nous sur le *finetuning* du mT5 ! + +{#if fw === 'pt'} + +## Finetuning de mT5 avec l'API `Trainer` + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Finetuning de mT5 avec Keras + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `TFAutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant le *finetuning* du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte du [chapitre 3](/course/fr/chapter3) où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. + + + +La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là : + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences : + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# La perte d'entraînement à chaque époque +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté au [chapitre 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les *tokens* un par un, et ceci est implémenté par la méthode `generate()`. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à trois *checkpoints* pendant l'entraînement. C'est parce que même la plus petite version de mT5 utilise environ 1 Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. + +L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le *Hub* après l'entraînement. Vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` à `Seq2SeqTrainingArguments`. + +La prochaine chose que nous devons faire est de fournir à `Seq2SeqTrainer` une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : + + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Décoder les résumés générés en texte + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Décoder les résumés de référence en texte + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE attend une nouvelle ligne après chaque phrase + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Calcul des scores ROUGE + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extraire les scores médians + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). + +Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le *modèle* : + +{#if fw === 'pt'} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +Voyons ce que produit ce collateur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Comme le collateur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : + +```python +features = [tokenized_datasets["train"][i] for i in range(2)] +data_collator(features) +``` + +```python out +{'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[ 1494, 259, 8622, 390, 259, 262, 2316, 3435, 955, + 772, 281, 772, 1617, 263, 305, 14701, 260, 1385, + 3031, 259, 24146, 332, 1037, 259, 43906, 305, 336, + 260, 1, 0, 0, 0, 0, 0, 0], + [ 259, 27531, 13483, 259, 7505, 260, 112240, 15192, 305, + 53198, 276, 259, 74060, 263, 260, 459, 25640, 776, + 2119, 336, 259, 2220, 259, 18896, 288, 4906, 288, + 1037, 3931, 260, 7083, 101476, 1143, 260, 1]]), 'labels': tensor([[ 7483, 259, 2364, 15695, 1, -100], + [ 259, 27531, 13483, 259, 7505, 1]]), 'decoder_input_ids': tensor([[ 0, 7483, 259, 2364, 15695, 1], + [ 0, 259, 27531, 13483, 259, 7505]])} +``` + +La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un *token* `[PAD]` (dont l'identifiant est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un *token* `[PAD]` dans la première entrée. + +{#if fw === 'pt'} + +Nous avons enfin tous les ingrédients dont nous avons besoin pour l'entraînement ! Nous devons maintenant simplement instancier le `Seq2SeqTrainer` avec les arguments : + +```python +from transformers import Seq2SeqTrainer + +trainer = Seq2SeqTrainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +et lancer notre course d'entraînement : + +```python +trainer.train() +``` + +Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `Trainer.evaluate()` : + +```python +trainer.evaluate() +``` + +```python out +{'eval_loss': 3.028524398803711, + 'eval_rouge1': 16.9728, + 'eval_rouge2': 8.2969, + 'eval_rougeL': 16.8366, + 'eval_rougeLsum': 16.851, + 'eval_gen_len': 10.1597, + 'eval_runtime': 6.1054, + 'eval_samples_per_second': 38.982, + 'eval_steps_per_second': 4.914} +``` + +D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre *baseline* *lead-3*. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +Ceci sauvegardera le *checkpoint* et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le *widget* sur le *Hub* sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! + +Pour conclure cette section, voyons comment nous pouvons également *finetuner* mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. + +{:else} + +Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le collateur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà num_samples // batch_size. +num_train_epochs = 8 +num_train_steps = len(tf_train_dataset) * num_train_epochs +model_name = model_checkpoint.split("/")[-1] + +optimizer, schedule = create_optimizer( + init_lr=5.6e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) + +model.compile(optimizer=optimizer) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Et enfin, nous *finetunons* le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8 +) +``` + +Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["labels"].numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) +``` + +Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile : + +```python +result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True +) +result = {key: value.mid.fmeasure * 100 for key, value in result.items()} +{k: round(v, 4) for k, v in result.items()} +``` + +``` +{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815} +``` + + +{/if} + +{#if fw === 'pt'} + +## Finetuning de mT5 avec 🤗 Accelerate + +Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans le [chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! + +### Préparer tout pour l'entraînement + +La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos échantillons. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : + +```python +tokenized_datasets.set_format("torch") +``` + +Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache : + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : + +```python +from torch.utils.data import DataLoader + +batch_size = 8 +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=batch_size, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size +) +``` + +La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes : + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` : + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons préparé nos objets, il reste trois choses à faire : + +* définir le programmeur du taux d'apprentissage, +* implémenter une fonction pour post-traiter les résumés pour l'évaluation, +* créer un dépôt sur le *Hub* vers lequel nous pouvons pousser notre modèle. + +Pour le programmeur de taux d'apprentissage, nous utiliserons le programmeur linéaire standard des sections précédentes : + +```python +from transformers import get_scheduler + +num_train_epochs = 10 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE et nous pouvons y parvenir avec le bout de code suivant : + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE attend une nouvelle ligne après chaque phrase + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. + +Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre dépôt, et la bibliothèque a une fonction utilitaire pour combiner l'identifiant du dépôt avec le profil de l'utilisateur : + +```python +from huggingface_hub import get_full_repo_name + +model_name = "test-bert-finetuned-squad-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/mt5-finetuned-amazon-en-es-accelerate' +``` + +Nous pouvons maintenant utiliser ce nom de dépôt pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Cela nous permettra de pousser les artefacts vers le *Hub* en appelant la méthode `repo.push_to_hub()` pendant l'entraînement ! Concluons maintenant notre analyse en écrivant la boucle d'entraînement. + +### Boucle d'entraînement + +La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales : + +1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, +2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, +3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, +4. sauvegarder les *checkpoints* et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les *checkpoints* par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'1 Go ! + +Ces étapes peuvent être vues dans le bloc de code suivant : + +```python +from tqdm.auto import tqdm +import torch +import numpy as np + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Calculer les métriques + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Sauvegarder et télécharger + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005} +Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306} +Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468} +Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518} +Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029} +Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913} +Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701} +Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194} +Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744} +Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} +``` + +Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`. + +{/if} + +## Utilisation de votre modèle finetuné + +Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le *widget* d'inférence, soit avec un objet `pipeline`, comme suit : + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré : + +```python +def print_summary(idx): + review = books_dataset["test"][idx]["review_body"] + title = books_dataset["test"][idx]["review_title"] + summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"] + print(f"'>>> Review: {review}'") + print(f"\n'>>> Title: {title}'") + print(f"\n'>>> Summary: {summary}'") +``` + +Examinons l'un des exemples anglais que nous recevons : + +```python +print_summary(100) +``` + +```python out +'>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.' +# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. + +'>>> Title: Not impressed at all... buy something else' +# Pas du tout impressionné... achetez autre chose. + +'>>> Summary: Nothing special at all about this product' +# Rien de spécial à propos de ce produit +``` + +Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : + +```python +print_summary(0) +``` + +```python out +'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' +# C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. + +'>>> Title: Buena literatura para adolescentes' +# Bonne littérature pour les adolescents + +'>>> Summary: Muy facil de leer' +# Très facile à lire +``` + +Le résumé a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! + +Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index a6c81f76d..0a3d395b4 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -1,905 +1,905 @@ - - -# Entraîner un modèle de langage causal à partir de zéro - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Jusqu'à présent, nous avons surtout réutilisé des modèles pré-entraînés et les avons *finetunés* sur de nouveaux cas d'usage. Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et il s'agit d'une stratégie très efficace pour appliquer les *transformers* à la plupart des applications du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente consistant à entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne démarche à adopter si vous avez beaucoup de données et qu'elles sont très différentes des données de pré-entraînement utilisées par les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple *finetuning* d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les jeux de données constitués de notes de musique, de séquences moléculaires telles que l'ADN, ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub (alimentés par le modèle Codex d'OpenAI) qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que le GPT-2. - -Dans cette section, nous allons construire une version réduite d'un modèle de génération de code Python. Nous nous concentrerons sur la complétion d'une ligne de code au lieu de fonctions ou de classes complètes. Lorsque vous travaillez sur des projets de science des données en Python, vous êtes souvent en contact avec les bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques. Il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous. - - - - -Dans le [chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter du code Python. Nous avons besoin d'un jeu de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! - - - - -Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a un certains aléat dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. - -## Collecte des données - -On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). - -Cependant, entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de ressources de calculs. Dans notre cas, nous n'avons besoin que du sous-ensemble du jeu de données qui est relatif aux codes portant sur la science des données. Commençons donc par filtrer le jeu de données `codeparrot` en ne gardant que les fichiers incluant l'une des bibliothèques de science des données énumérées précédemment. En raison de la taille du jeu de données, nous voulons éviter de le télécharger. Nous utiliserons donc la fonctionnalité de *streaming* de 🤗 *Datasets* afin de le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utilisons la fonction suivante : - -```py -def any_keyword_in_string(string, keywords): - for keyword in keywords: - if keyword in string: - return True - return False -``` - -Testons-le sur deux exemples : - -```py -filters = ["pandas", "sklearn", "matplotlib", "seaborn"] -example_1 = "import numpy as np" -example_2 = "import pandas as pd" - -print( - any_keyword_in_string(example_1, filters), any_keyword_in_string(example_2, filters) -) -``` - -```python out -False True -``` - -Nous pouvons l'utiliser pour créer une fonction qui va *streamer* le jeu de donner et filtrer les éléments que nous voulons : - -```py -def filter_streaming_dataset(dataset, filters): - filtered_dict = defaultdict(list) - total = 0 - for sample in tqdm(iter(dataset)): - total += 1 - if any_keyword_in_string(sample["content"], filters): - for k, v in sample.items(): - filtered_dict[k].append(v) - print(f"{len(filtered_dict['content'])/total:.2%} of data after filtering.") - return Dataset.from_dict(filtered_dict) -``` - -Ensuite, nous pouvons simplement appliquer cette fonction : - -```py -# Cette cellule prendra beaucoup de temps à s'exécuter, donc vous devriez la sauter et aller à la suivante ! -from datasets import load_dataset - -split = "train" # "valid" -filters = ["pandas", "sklearn", "matplotlib", "seaborn"] - -data = load_dataset(f"transformersbook/codeparrot-{split}", split=split, streaming=True) -filtered_data = filter_streaming_dataset(data, filters) -``` - -```python out -3.26% of data after filtering. -``` - -Cela nous laisse avec environ 3 % du jeu de données original, ce qui est tout de même assez important puisqu'il fait 6 Go et se compose de 600 000 scripts Python ! - -Le filtrage peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus, nous fournissons sur le *Hub* le jeu de données filtré pour que vous puissiez le télécharger : - -```py -from datasets import load_dataset, DatasetDict - -ds_train = load_dataset("huggingface-course/codeparrot-ds-train", split="train") -ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="train") - -raw_datasets = DatasetDict( - { - "train": ds_train, # .shuffle().select(range(50000)), - "valid": ds_valid, # .shuffle().select(range(500)) - } -) - -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], - num_rows: 606720 - }) - valid: Dataset({ - features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], - num_rows: 3322 - }) -}) -``` - - - -Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons donc d'exécuter d'abord la boucle d'entraînement sur un petit échantillon des données en décommentant les deux lignes dans le code ci-dessus. Assurez-vous alors que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape car vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement ! - - - -Examinons un exemple tiré du jeu de données. Nous ne montrerons que les 200 premiers caractères de chaque champ : - -```py -for key in raw_datasets["train"][0]: - print(f"{key.upper()}: {raw_datasets['train'][0][key][:200]}") -``` - -```python out -'REPO_NAME: kmike/scikit-learn' -'PATH: sklearn/utils/__init__.py' -'COPIES: 3' -'SIZE: 10094' -'''CONTENT: """ -The :mod:`sklearn.utils` module includes various utilites. -""" - -from collections import Sequence - -import numpy as np -from scipy.sparse import issparse -import warnings - -from .murmurhash import murm -LICENSE: bsd-3-clause''' -``` - -Nous pouvons voir que le champ `content` contient le code sur lequel nous voulons que notre modèle s'entraîne. Maintenant que nous avons un jeu de données, nous devons préparer les textes afin qu'ils soient dans un format approprié pour le pré-entraînement. - -## Préparation du jeu de données - - - -La première étape est de tokeniser les données afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est d'autocompléter de courts appels de fonctions, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. Si c'est important pour votre application d'avoir davantage de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre. Gardez néanmoins à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés respectivement dans le GPT-2 et le GPT-3. - -La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous allons utiliser l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans le [chapitre 6](/course/fr/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau est plus petit que la taille du contexte et nous nous en débarrasserons pour éviter les problèmes de *padding*. Nous n'en avons pas vraiment besoin puisque de toute façon nous avons beaucoup de données. - -
-Chunking a large texts in several pieces. - -
- -Voyons comment cela fonctionne en examinant les deux premiers exemples : - -```py -from transformers import AutoTokenizer - -context_length = 128 -tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") - -outputs = tokenizer( - raw_datasets["train"][:2]["content"], - truncation=True, - max_length=context_length, - return_overflowing_tokens=True, - return_length=True, -) - -print(f"Input IDs length: {len(outputs['input_ids'])}") -print(f"Input chunk lengths: {(outputs['length'])}") -print(f"Chunk mapping: {outputs['overflow_to_sample_mapping']}") -``` - -```python out -Input IDs length: 34 -Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 117, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 41] -Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] -``` - -Nous pouvons voir que nous obtenons 34 morceaux à partir de ces deux exemples. En regardant leurs longueurs, nous pouvons voir qu'ils se terminent avec moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des morceaux que nous avons (2/34), donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels morceaux appartenaient à quels échantillons d'entrée. - -Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` de 🤗 *Datasets*. En effet, celle-ci ne nécessite pas une correspondance un à un comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3). Nous pouvons créer des batchs avec plus ou moins d'éléments que le batch d'entrée. C'est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en morceaux de longeur de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `Dataset.map()` : - -```py -def tokenize(element): - outputs = tokenizer( - element["content"], - truncation=True, - max_length=context_length, - return_overflowing_tokens=True, - return_length=True, - ) - input_batch = [] - for length, input_ids in zip(outputs["length"], outputs["input_ids"]): - if length == context_length: - input_batch.append(input_ids) - return {"input_ids": input_batch} - - -tokenized_datasets = raw_datasets.map( - tokenize, batched=True, remove_columns=raw_datasets["train"].column_names -) -tokenized_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['input_ids'], - num_rows: 16702061 - }) - valid: Dataset({ - features: ['input_ids'], - num_rows: 93164 - }) -}) -``` - -Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. A titre de comparaison, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement. Les modèles Codex étant initialisés à partir des *checkpoints* GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide. - -Maintenant que le jeu de données est prêt, configurons le modèle ! - - - -✏️ **Essayez !** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le découpage sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des identifiants des *tokens*. - - - - -## Initialisation d'un nouveau modèle - -Notre première étape consiste à initialiser un GPT-2. Pour notre modèle, nous utiliserons la même configuration que pour le petit modèle GPT-2. Ainsi nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) : - -{#if fw === 'pt'} - -```py -from transformers import AutoTokenizer, GPT2LMHeadModel, AutoConfig - -config = AutoConfig.from_pretrained( - "gpt2", - vocab_size=len(tokenizer), - n_ctx=context_length, - bos_token_id=tokenizer.bos_token_id, - eos_token_id=tokenizer.eos_token_id, -) -``` - -Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : - -```py -model = GPT2LMHeadModel(config) -model_size = sum(t.numel() for t in model.parameters()) -print(f"GPT-2 size: {model_size/1000**2:.1f}M parameters") -``` - -```python out -GPT-2 size: 124.2M parameters -``` - -{:else} - -```py -from transformers import AutoTokenizer, TFGPT2LMHeadModel, AutoConfig - -config = AutoConfig.from_pretrained( - "gpt2", - vocab_size=len(tokenizer), - n_ctx=context_length, - bos_token_id=tokenizer.bos_token_id, - eos_token_id=tokenizer.eos_token_id, -) -``` - -Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : - -```py -model = TFGPT2LMHeadModel(config) -model(model.dummy_inputs) # Construit le modèle -model.summary() -``` - -```python out -_________________________________________________________________ -Layer (type) Output Shape Param # -================================================================= -transformer (TFGPT2MainLayer multiple 124242432 -================================================================= -Total params: 124,242,432 -Trainable params: 124,242,432 -Non-trainable params: 0 -_________________________________________________________________ -``` - -{/if} - -Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les batchs. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le collateur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. - -Notez que `DataCollatorForLanguageModeling` supporte à la fois la modélisation du langage masqué (MLM pour *masked language modeling*) et la modélisation du langage causal (CLM pour *causal language modeling*). Par défaut, il prépare les données pour la MLM mais nous pouvons passer à la CLM en définissant l'argument `mlm=False` : - -{#if fw === 'pt'} - -```py -from transformers import DataCollatorForLanguageModeling - -tokenizer.pad_token = tokenizer.eos_token -data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False) -``` - -{:else} - -```py -from transformers import DataCollatorForLanguageModeling - -tokenizer.pad_token = tokenizer.eos_token -data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_tensors="tf") -``` - -{/if} - -Prenons un exemple : - -```py -out = data_collator([tokenized_dataset["train"][i] for i in range(5)]) -for key in out: - print(f"{key} shape: {out[key].shape}") -``` - -{#if fw === 'pt'} - -```python out -input_ids shape: torch.Size([5, 128]) -attention_mask shape: torch.Size([5, 128]) -labels shape: torch.Size([5, 128]) -``` - -{:else} - -```python out -input_ids shape: (5, 128) -attention_mask shape: (5, 128) -labels shape: (5, 128) -``` - -{/if} - -Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs ont la même forme. - -{#if fw === 'tf'} - -Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec le collateur de données que nous avons créé ci-dessus : - -```python -tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=32, -) -tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=32, -) -``` - -{/if} - - - -⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que le collecteur de données ne fait que copier les entrées pour créer les étiquettes. - - - - -Nous avons maintenant tout ce qu'il faut pour entraîner notre modèle. Ce n'était pas si compliqué ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'pt'} - -Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer la fonction `Trainer`. Nous utiliserons un programme de taux d'apprentissage de type cosinus avec un réchauffement et une taille de batch de 256 (`per_device_train_batch_size` x `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul batch ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages en avant/en arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *Accelerate*. - -```py -from transformers import Trainer, TrainingArguments - -args = TrainingArguments( - output_dir="codeparrot-ds", - per_device_train_batch_size=32, - per_device_eval_batch_size=32, - evaluation_strategy="steps", - eval_steps=5_000, - logging_steps=5_000, - gradient_accumulation_steps=8, - num_train_epochs=1, - weight_decay=0.1, - warmup_steps=1_000, - lr_scheduler_type="cosine", - learning_rate=5e-4, - save_steps=5_000, - fp16=True, - push_to_hub=True, -) - -trainer = Trainer( - model=model, - tokenizer=tokenizer, - args=args, - data_collator=data_collator, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["valid"], -) -``` - -Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! - -```py -trainer.train() -``` - -Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : - -```py -trainer.push_to_hub() -``` - -{:else} - -Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un réchauffement pour améliorer la stabilité de l'entraînement : - -```py -from transformers import create_optimizer -import tensorflow as tf - -num_train_steps = len(tf_train_dataset) -optimizer, schedule = create_optimizer( - init_lr=5e-5, - num_warmup_steps=1_000, - num_train_steps=num_train_steps, - weight_decay_rate=0.01, -) -model.compile(optimizer=optimizer) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : - -```py -from transformers.keras_callbacks import PushToHubCallback - -callback = PushToHubCallback(output_dir="codeparrot-ds", tokenizer=tokenizer) - -model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) -``` - -{/if} - - - -✏️ **Essayez !** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement du GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! - - - - - -{#if fw === 'pt'} - -💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. `Trainer` gère automatiquement plusieurs machines ce qui peut accélérer considérablement l'entraînement. - -{:else} - -💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). - -{/if} - - - -## Génération de code avec le pipeline - -C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans un `pipeline` de génération de texte et, s'il y en a un de disponible, utiliser un GPU pour avoir des générations rapidement : - -{#if fw === 'pt'} - -```py -import torch -from transformers import pipeline - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -pipe = pipeline( - "text-generation", model="huggingface-course/codeparrot-ds", device=device -) -``` - -{:else} - -```py -from transformers import pipeline - -course_model = TFGPT2LMHeadModel.from_pretrained("huggingface-course/codeparrot-ds") -course_tokenizer = AutoTokenizer.from_pretrained("huggingface-course/codeparrot-ds") -pipe = pipeline( - "text-generation", model=course_model, tokenizer=course_tokenizer, device=0 -) -``` - -{/if} - -Let's start with the simple task of creating a scatter plot: - -```py -txt = """\ -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un nuage de points avec x, y -""" -print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) -``` - -```python out -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un nuage de points avec x, y -plt.scatter(x, y) -``` - -Le résultat semble correct. Est-ce que cela fonctionne aussi pour une opération `pandas` ? Voyons si nous pouvons créer un `DataFrame` à partir de deux tableaux : - -```py -txt = """\ -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un tableau de données à partir de x et y -""" -print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) -``` - -```python out -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un tableau de données à partir de x et y -df = pd.DataFrame({'x': x, 'y': y}) -df.insert(0,'x', x) -for -``` - -Bien, c'est la bonne réponse. Bien qu'il insère ensuite la colonne `x` à nouveau. Comme le nombre de *tokens* générés est limité, la boucle `for` suivante est coupée. Voyons si nous pouvons faire quelque chose d'un peu plus complexe et faire en sorte que le modèle nous aide à utiliser l'opération `groupby` : - -```py -txt = """\ -# tableau de données avec profession, revenu et nom -df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) - -# calculer le revenu moyen par profession -""" -print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) -``` - -```python out -# tableau de données avec profession, revenu et nom -df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) - -# calculer le revenu moyen par profession -profession = df.groupby(['profession']).mean() -``` - -Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et utiliser un modèle *Random Forest* : - -```py -txt = """ -# import random forest regressor from scikit-learn -from sklearn.ensemble import RandomForestRegressor - -# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y : -""" -print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) -``` - -```python out -# import random forest regressor from scikit-learn -from sklearn.ensemble import RandomForestRegressor - -# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y : -rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) -rf.fit(X, y) -rf -``` - -{#if fw === 'tf'} - -Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. - -{:else} - -Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. Parfois, il est nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du batch ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*. - -{/if} - -{#if fw === 'pt'} - -## Entraîner avec 🤗 Accelerate - -Nous avons vu comment entraîner un modèle avec le `Trainer`, qui permet une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement ou nous souhaitons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement. - - - -Puisque nous sommes principalement intéressés par l'autocomplétion pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que les fonctions `fit` et `predict` de cette dernière. Si chacun d'entre eux est représenté par un seul *token*, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* : - -```py -keytoken_ids = [] -for keyword in [ - "plt", - "pd", - "sk", - "fit", - "predict", - " plt", - " pd", - " sk", - " fit", - " predict", - "testtest", -]: - ids = tokenizer([keyword]).input_ids[0] - if len(ids) == 1: - keytoken_ids.append(ids[0]) - else: - print(f"Keyword has not single token: {keyword}") -``` - -```python out -'Keyword has not single token: testtest' -``` - -Super, ça a l'air de bien fonctionner ! Nous pouvons maintenant écrire une fonction de perte personnalisée qui prend la séquence d'entrée, les logits et les *tokens* clés que nous venons de sélectionner comme entrées. Tout d'abord, nous devons aligner les logits et les entrées : la séquence d'entrée décalée d'une unité vers la droite forme les étiquettes, puisque le *token* suivant est l'étiquette du *token* actuel. Nous pouvons y parvenir en commençant les étiquettes à partir du deuxième *token* de la séquence d'entrée, puisque le modèle ne fait pas de prédiction pour le premier *token* de toute façon. Ensuite, nous coupons le dernier logit, car nous n'avons pas d'étiquette pour le *token* qui suit la séquence d'entrée complète. Avec cela, nous pouvons calculer la perte par échantillon et compter les occurrences de tous les mots-clés dans chaque échantillon. Enfin, nous calculons la moyenne pondérée sur tous les échantillons en utilisant les occurrences comme poids. Comme nous ne voulons pas rejeter tous les échantillons qui ne contiennent pas de mots-clés, nous ajoutons 1 aux poids : - -```py -from torch.nn import CrossEntropyLoss -import torch - - -def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): - # Décalage pour que tokens < n prédisent n - shift_labels = inputs[..., 1:].contiguous() - shift_logits = logits[..., :-1, :].contiguous() - # Calcul de la perte par token - loss_fct = CrossEntropyLoss(reduce=False) - loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) - # Redimensionnement et perte moyenne par échantillon - loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) - # Calculer et échelonner la pondération - weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( - axis=[0, 2] - ) - weights = alpha * (1.0 + weights) - # Calculer la moyenne pondérée - weighted_loss = (loss_per_sample * weights).mean() - return weighted_loss -``` - -Avant de commencer à entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments : - -- Nous avons besoin de chargeurs de données pour charger les données par batch. -- Nous devons définir les paramètres de décroissance des poids. -- De temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction. - -Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"` et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de batch appropriée : - -```py -from torch.utils.data.dataloader import DataLoader - -tokenized_dataset.set_format("torch") -train_dataloader = DataLoader(tokenized_dataset["train"], batch_size=32, shuffle=True) -eval_dataloader = DataLoader(tokenized_dataset["valid"], batch_size=32) -``` - -Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et les poids de la *LayerNorm* en sont exemptés. Voici comment nous pouvons le faire : - -```py -weight_decay = 0.1 - - -def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): - params_with_wd, params_without_wd = [], [] - for n, p in model.named_parameters(): - if any(nd in n for nd in no_decay): - params_without_wd.append(p) - else: - params_with_wd.append(p) - return [ - {"params": params_with_wd, "weight_decay": weight_decay}, - {"params": params_without_wd, "weight_decay": 0.0}, - ] -``` - -Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le *dataloader* d'évaluation et rassemble toutes les pertes à travers les processus : - -```py -def evaluate(): - model.eval() - losses = [] - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - outputs = model(batch["input_ids"], labels=batch["input_ids"]) - - losses.append(accelerator.gather(outputs.loss)) - loss = torch.mean(torch.cat(losses)) - try: - perplexity = torch.exp(loss) - except OverflowError: - perplexity = float("inf") - return loss.item(), perplexity.item() -``` - -Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous entraînons à nouveau à partir de zéro : - -```py -model = GPT2LMHeadModel(config) -``` - -Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de décroissance des poids : - -```py -from torch.optim import AdamW - -optimizer = AdamW(get_grouped_params(model), lr=5e-4) -``` - -Préparons maintenant le modèle, l'optimiseur et les chargeurs de données pour pouvoir commencer l'entraînement : - -```py -from accelerate import Accelerator - -accelerator = Accelerator(fp16=True) - -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : - -```py -num_train_epochs = 1 -num_update_steps_per_epoch = len(train_dataloader) -num_training_steps = num_train_epochs * num_update_steps_per_epoch - -lr_scheduler = get_scheduler( - name="linear", - optimizer=optimizer, - num_warmup_steps=1_000, - num_training_steps=num_training_steps, -) -``` - -Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : - -```py -from huggingface_hub import Repository, get_full_repo_name - -model_name = "codeparrot-ds-accelerate" -repo_name = get_full_repo_name(model_name) -repo_name -``` - -```python out -'sgugger/codeparrot-ds-accelerate' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : - -```py -output_dir = "codeparrot-ds-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -Avant de nous entraîner, exécutons un test rapide pour voir si la fonction d'évaluation fonctionne correctement : - -```py -evaluate() -``` - -```python out -(10.934126853942871, 56057.14453125) -``` - -Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans celle-ci, nous itérons sur le chargeur de données et transmettons les batchs au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `evaluate()` : - -```py -from tqdm.notebook import tqdm - -gradient_accumulation_steps = 8 -eval_steps = 5_000 - -model.train() -completed_steps = 0 -for epoch in range(num_train_epochs): - for step, batch in tqdm( - enumerate(train_dataloader, start=1), total=len(train_dataloader) - ): - logits = model(batch["input_ids"]).logits - loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) - if step % 100 == 0: - accelerator.print( - { - "lr": get_lr(), - "samples": step * samples_per_step, - "steps": completed_steps, - "loss/train": loss.item() * gradient_accumulation_steps, - } - ) - loss = loss / gradient_accumulation_steps - accelerator.backward(loss) - if step % gradient_accumulation_steps == 0: - accelerator.clip_grad_norm_(model.parameters(), 1.0) - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - completed_steps += 1 - if (step % (eval_steps * gradient_accumulation_steps)) == 0: - eval_loss, perplexity = evaluate() - accelerator.print({"loss/eval": eval_loss, "perplexity": perplexity}) - model.train() - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress step {step}", blocking=False - ) -``` - -Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que le GPT-2. Vous pouvez encore l'adapter à vos besoins. - - - -✏️ **Essayez !** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement. - - - - - -✏️ **Essayez !** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que *TensorBoard* ou *Weights & Biases*. Ajoutez l'un d'eux à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement. - - - -{/if} + + +# Entraîner un modèle de langage causal à partir de zéro + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Jusqu'à présent, nous avons surtout réutilisé des modèles pré-entraînés et les avons *finetunés* sur de nouveaux cas d'usage. Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et il s'agit d'une stratégie très efficace pour appliquer les *transformers* à la plupart des applications du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente consistant à entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne démarche à adopter si vous avez beaucoup de données et qu'elles sont très différentes des données de pré-entraînement utilisées par les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple *finetuning* d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les jeux de données constitués de notes de musique, de séquences moléculaires telles que l'ADN, ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub (alimentés par le modèle Codex d'OpenAI) qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que le GPT-2. + +Dans cette section, nous allons construire une version réduite d'un modèle de génération de code Python. Nous nous concentrerons sur la complétion d'une ligne de code au lieu de fonctions ou de classes complètes. Lorsque vous travaillez sur des projets de science des données en Python, vous êtes souvent en contact avec les bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques. Il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous. + + + + +Dans le [chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter du code Python. Nous avons besoin d'un jeu de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! + + + + +Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a un certains aléat dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. + +## Collecte des données + +On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). + +Cependant, entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de ressources de calculs. Dans notre cas, nous n'avons besoin que du sous-ensemble du jeu de données qui est relatif aux codes portant sur la science des données. Commençons donc par filtrer le jeu de données `codeparrot` en ne gardant que les fichiers incluant l'une des bibliothèques de science des données énumérées précédemment. En raison de la taille du jeu de données, nous voulons éviter de le télécharger. Nous utiliserons donc la fonctionnalité de *streaming* de 🤗 *Datasets* afin de le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utilisons la fonction suivante : + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +Testons-le sur deux exemples : + +```py +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] +example_1 = "import numpy as np" +example_2 = "import pandas as pd" + +print( + any_keyword_in_string(example_1, filters), any_keyword_in_string(example_2, filters) +) +``` + +```python out +False True +``` + +Nous pouvons l'utiliser pour créer une fonction qui va *streamer* le jeu de donner et filtrer les éléments que nous voulons : + +```py +def filter_streaming_dataset(dataset, filters): + filtered_dict = defaultdict(list) + total = 0 + for sample in tqdm(iter(dataset)): + total += 1 + if any_keyword_in_string(sample["content"], filters): + for k, v in sample.items(): + filtered_dict[k].append(v) + print(f"{len(filtered_dict['content'])/total:.2%} of data after filtering.") + return Dataset.from_dict(filtered_dict) +``` + +Ensuite, nous pouvons simplement appliquer cette fonction : + +```py +# Cette cellule prendra beaucoup de temps à s'exécuter, donc vous devriez la sauter et aller à la suivante ! +from datasets import load_dataset + +split = "train" # "valid" +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] + +data = load_dataset(f"transformersbook/codeparrot-{split}", split=split, streaming=True) +filtered_data = filter_streaming_dataset(data, filters) +``` + +```python out +3.26% of data after filtering. +``` + +Cela nous laisse avec environ 3 % du jeu de données original, ce qui est tout de même assez important puisqu'il fait 6 Go et se compose de 600 000 scripts Python ! + +Le filtrage peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus, nous fournissons sur le *Hub* le jeu de données filtré pour que vous puissiez le télécharger : + +```py +from datasets import load_dataset, DatasetDict + +ds_train = load_dataset("huggingface-course/codeparrot-ds-train", split="train") +ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="train") + +raw_datasets = DatasetDict( + { + "train": ds_train, # .shuffle().select(range(50000)), + "valid": ds_valid, # .shuffle().select(range(500)) + } +) + +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 606720 + }) + valid: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 3322 + }) +}) +``` + + + +Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons donc d'exécuter d'abord la boucle d'entraînement sur un petit échantillon des données en décommentant les deux lignes dans le code ci-dessus. Assurez-vous alors que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape car vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement ! + + + +Examinons un exemple tiré du jeu de données. Nous ne montrerons que les 200 premiers caractères de chaque champ : + +```py +for key in raw_datasets["train"][0]: + print(f"{key.upper()}: {raw_datasets['train'][0][key][:200]}") +``` + +```python out +'REPO_NAME: kmike/scikit-learn' +'PATH: sklearn/utils/__init__.py' +'COPIES: 3' +'SIZE: 10094' +'''CONTENT: """ +The :mod:`sklearn.utils` module includes various utilites. +""" + +from collections import Sequence + +import numpy as np +from scipy.sparse import issparse +import warnings + +from .murmurhash import murm +LICENSE: bsd-3-clause''' +``` + +Nous pouvons voir que le champ `content` contient le code sur lequel nous voulons que notre modèle s'entraîne. Maintenant que nous avons un jeu de données, nous devons préparer les textes afin qu'ils soient dans un format approprié pour le pré-entraînement. + +## Préparation du jeu de données + + + +La première étape est de tokeniser les données afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est d'autocompléter de courts appels de fonctions, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. Si c'est important pour votre application d'avoir davantage de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre. Gardez néanmoins à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés respectivement dans le GPT-2 et le GPT-3. + +La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous allons utiliser l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans le [chapitre 6](/course/fr/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau est plus petit que la taille du contexte et nous nous en débarrasserons pour éviter les problèmes de *padding*. Nous n'en avons pas vraiment besoin puisque de toute façon nous avons beaucoup de données. + +
+Chunking a large texts in several pieces. + +
+ +Voyons comment cela fonctionne en examinant les deux premiers exemples : + +```py +from transformers import AutoTokenizer + +context_length = 128 +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") + +outputs = tokenizer( + raw_datasets["train"][:2]["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, +) + +print(f"Input IDs length: {len(outputs['input_ids'])}") +print(f"Input chunk lengths: {(outputs['length'])}") +print(f"Chunk mapping: {outputs['overflow_to_sample_mapping']}") +``` + +```python out +Input IDs length: 34 +Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 117, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 41] +Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +``` + +Nous pouvons voir que nous obtenons 34 morceaux à partir de ces deux exemples. En regardant leurs longueurs, nous pouvons voir qu'ils se terminent avec moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des morceaux que nous avons (2/34), donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels morceaux appartenaient à quels échantillons d'entrée. + +Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` de 🤗 *Datasets*. En effet, celle-ci ne nécessite pas une correspondance un à un comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3). Nous pouvons créer des batchs avec plus ou moins d'éléments que le batch d'entrée. C'est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en morceaux de longeur de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `Dataset.map()` : + +```py +def tokenize(element): + outputs = tokenizer( + element["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, + ) + input_batch = [] + for length, input_ids in zip(outputs["length"], outputs["input_ids"]): + if length == context_length: + input_batch.append(input_ids) + return {"input_ids": input_batch} + + +tokenized_datasets = raw_datasets.map( + tokenize, batched=True, remove_columns=raw_datasets["train"].column_names +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['input_ids'], + num_rows: 16702061 + }) + valid: Dataset({ + features: ['input_ids'], + num_rows: 93164 + }) +}) +``` + +Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. A titre de comparaison, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement. Les modèles Codex étant initialisés à partir des *checkpoints* GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide. + +Maintenant que le jeu de données est prêt, configurons le modèle ! + + + +✏️ **Essayez !** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le découpage sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des identifiants des *tokens*. + + + + +## Initialisation d'un nouveau modèle + +Notre première étape consiste à initialiser un GPT-2. Pour notre modèle, nous utiliserons la même configuration que pour le petit modèle GPT-2. Ainsi nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) : + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, GPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : + +```py +model = GPT2LMHeadModel(config) +model_size = sum(t.numel() for t in model.parameters()) +print(f"GPT-2 size: {model_size/1000**2:.1f}M parameters") +``` + +```python out +GPT-2 size: 124.2M parameters +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFGPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : + +```py +model = TFGPT2LMHeadModel(config) +model(model.dummy_inputs) # Construit le modèle +model.summary() +``` + +```python out +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +transformer (TFGPT2MainLayer multiple 124242432 +================================================================= +Total params: 124,242,432 +Trainable params: 124,242,432 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les batchs. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le collateur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. + +Notez que `DataCollatorForLanguageModeling` supporte à la fois la modélisation du langage masqué (MLM pour *masked language modeling*) et la modélisation du langage causal (CLM pour *causal language modeling*). Par défaut, il prépare les données pour la MLM mais nous pouvons passer à la CLM en définissant l'argument `mlm=False` : + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False) +``` + +{:else} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_tensors="tf") +``` + +{/if} + +Prenons un exemple : + +```py +out = data_collator([tokenized_dataset["train"][i] for i in range(5)]) +for key in out: + print(f"{key} shape: {out[key].shape}") +``` + +{#if fw === 'pt'} + +```python out +input_ids shape: torch.Size([5, 128]) +attention_mask shape: torch.Size([5, 128]) +labels shape: torch.Size([5, 128]) +``` + +{:else} + +```python out +input_ids shape: (5, 128) +attention_mask shape: (5, 128) +labels shape: (5, 128) +``` + +{/if} + +Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs ont la même forme. + +{#if fw === 'tf'} + +Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec le collateur de données que nous avons créé ci-dessus : + +```python +tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que le collecteur de données ne fait que copier les entrées pour créer les étiquettes. + + + + +Nous avons maintenant tout ce qu'il faut pour entraîner notre modèle. Ce n'était pas si compliqué ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer la fonction `Trainer`. Nous utiliserons un programme de taux d'apprentissage de type cosinus avec un réchauffement et une taille de batch de 256 (`per_device_train_batch_size` x `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul batch ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages en avant/en arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *Accelerate*. + +```py +from transformers import Trainer, TrainingArguments + +args = TrainingArguments( + output_dir="codeparrot-ds", + per_device_train_batch_size=32, + per_device_eval_batch_size=32, + evaluation_strategy="steps", + eval_steps=5_000, + logging_steps=5_000, + gradient_accumulation_steps=8, + num_train_epochs=1, + weight_decay=0.1, + warmup_steps=1_000, + lr_scheduler_type="cosine", + learning_rate=5e-4, + save_steps=5_000, + fp16=True, + push_to_hub=True, +) + +trainer = Trainer( + model=model, + tokenizer=tokenizer, + args=args, + data_collator=data_collator, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["valid"], +) +``` + +Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! + +```py +trainer.train() +``` + +Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : + +```py +trainer.push_to_hub() +``` + +{:else} + +Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un réchauffement pour améliorer la stabilité de l'entraînement : + +```py +from transformers import create_optimizer +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : + +```py +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="codeparrot-ds", tokenizer=tokenizer) + +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + + + +✏️ **Essayez !** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement du GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! + + + + + +{#if fw === 'pt'} + +💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. `Trainer` gère automatiquement plusieurs machines ce qui peut accélérer considérablement l'entraînement. + +{:else} + +💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## Génération de code avec le pipeline + +C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans un `pipeline` de génération de texte et, s'il y en a un de disponible, utiliser un GPU pour avoir des générations rapidement : + +{#if fw === 'pt'} + +```py +import torch +from transformers import pipeline + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +pipe = pipeline( + "text-generation", model="huggingface-course/codeparrot-ds", device=device +) +``` + +{:else} + +```py +from transformers import pipeline + +course_model = TFGPT2LMHeadModel.from_pretrained("huggingface-course/codeparrot-ds") +course_tokenizer = AutoTokenizer.from_pretrained("huggingface-course/codeparrot-ds") +pipe = pipeline( + "text-generation", model=course_model, tokenizer=course_tokenizer, device=0 +) +``` + +{/if} + +Let's start with the simple task of creating a scatter plot: + +```py +txt = """\ +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un nuage de points avec x, y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un nuage de points avec x, y +plt.scatter(x, y) +``` + +Le résultat semble correct. Est-ce que cela fonctionne aussi pour une opération `pandas` ? Voyons si nous pouvons créer un `DataFrame` à partir de deux tableaux : + +```py +txt = """\ +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un tableau de données à partir de x et y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un tableau de données à partir de x et y +df = pd.DataFrame({'x': x, 'y': y}) +df.insert(0,'x', x) +for +``` + +Bien, c'est la bonne réponse. Bien qu'il insère ensuite la colonne `x` à nouveau. Comme le nombre de *tokens* générés est limité, la boucle `for` suivante est coupée. Voyons si nous pouvons faire quelque chose d'un peu plus complexe et faire en sorte que le modèle nous aide à utiliser l'opération `groupby` : + +```py +txt = """\ +# tableau de données avec profession, revenu et nom +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculer le revenu moyen par profession +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# tableau de données avec profession, revenu et nom +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculer le revenu moyen par profession +profession = df.groupby(['profession']).mean() +``` + +Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et utiliser un modèle *Random Forest* : + +```py +txt = """ +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y : +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y : +rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) +rf.fit(X, y) +rf +``` + +{#if fw === 'tf'} + +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. + +{:else} + +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. Parfois, il est nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du batch ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*. + +{/if} + +{#if fw === 'pt'} + +## Entraîner avec 🤗 Accelerate + +Nous avons vu comment entraîner un modèle avec le `Trainer`, qui permet une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement ou nous souhaitons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement. + + + +Puisque nous sommes principalement intéressés par l'autocomplétion pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que les fonctions `fit` et `predict` de cette dernière. Si chacun d'entre eux est représenté par un seul *token*, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* : + +```py +keytoken_ids = [] +for keyword in [ + "plt", + "pd", + "sk", + "fit", + "predict", + " plt", + " pd", + " sk", + " fit", + " predict", + "testtest", +]: + ids = tokenizer([keyword]).input_ids[0] + if len(ids) == 1: + keytoken_ids.append(ids[0]) + else: + print(f"Keyword has not single token: {keyword}") +``` + +```python out +'Keyword has not single token: testtest' +``` + +Super, ça a l'air de bien fonctionner ! Nous pouvons maintenant écrire une fonction de perte personnalisée qui prend la séquence d'entrée, les logits et les *tokens* clés que nous venons de sélectionner comme entrées. Tout d'abord, nous devons aligner les logits et les entrées : la séquence d'entrée décalée d'une unité vers la droite forme les étiquettes, puisque le *token* suivant est l'étiquette du *token* actuel. Nous pouvons y parvenir en commençant les étiquettes à partir du deuxième *token* de la séquence d'entrée, puisque le modèle ne fait pas de prédiction pour le premier *token* de toute façon. Ensuite, nous coupons le dernier logit, car nous n'avons pas d'étiquette pour le *token* qui suit la séquence d'entrée complète. Avec cela, nous pouvons calculer la perte par échantillon et compter les occurrences de tous les mots-clés dans chaque échantillon. Enfin, nous calculons la moyenne pondérée sur tous les échantillons en utilisant les occurrences comme poids. Comme nous ne voulons pas rejeter tous les échantillons qui ne contiennent pas de mots-clés, nous ajoutons 1 aux poids : + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Décalage pour que tokens < n prédisent n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calcul de la perte par token + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Redimensionnement et perte moyenne par échantillon + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculer et échelonner la pondération + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculer la moyenne pondérée + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +Avant de commencer à entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments : + +- Nous avons besoin de chargeurs de données pour charger les données par batch. +- Nous devons définir les paramètres de décroissance des poids. +- De temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction. + +Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"` et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de batch appropriée : + +```py +from torch.utils.data.dataloader import DataLoader + +tokenized_dataset.set_format("torch") +train_dataloader = DataLoader(tokenized_dataset["train"], batch_size=32, shuffle=True) +eval_dataloader = DataLoader(tokenized_dataset["valid"], batch_size=32) +``` + +Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et les poids de la *LayerNorm* en sont exemptés. Voici comment nous pouvons le faire : + +```py +weight_decay = 0.1 + + +def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): + params_with_wd, params_without_wd = [], [] + for n, p in model.named_parameters(): + if any(nd in n for nd in no_decay): + params_without_wd.append(p) + else: + params_with_wd.append(p) + return [ + {"params": params_with_wd, "weight_decay": weight_decay}, + {"params": params_without_wd, "weight_decay": 0.0}, + ] +``` + +Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le *dataloader* d'évaluation et rassemble toutes les pertes à travers les processus : + +```py +def evaluate(): + model.eval() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(batch["input_ids"], labels=batch["input_ids"]) + + losses.append(accelerator.gather(outputs.loss)) + loss = torch.mean(torch.cat(losses)) + try: + perplexity = torch.exp(loss) + except OverflowError: + perplexity = float("inf") + return loss.item(), perplexity.item() +``` + +Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous entraînons à nouveau à partir de zéro : + +```py +model = GPT2LMHeadModel(config) +``` + +Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de décroissance des poids : + +```py +from torch.optim import AdamW + +optimizer = AdamW(get_grouped_params(model), lr=5e-4) +``` + +Préparons maintenant le modèle, l'optimiseur et les chargeurs de données pour pouvoir commencer l'entraînement : + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) + +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : + +```py +num_train_epochs = 1 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + name="linear", + optimizer=optimizer, + num_warmup_steps=1_000, + num_training_steps=num_training_steps, +) +``` + +Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "codeparrot-ds-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/codeparrot-ds-accelerate' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : + +```py +output_dir = "codeparrot-ds-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +Avant de nous entraîner, exécutons un test rapide pour voir si la fonction d'évaluation fonctionne correctement : + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans celle-ci, nous itérons sur le chargeur de données et transmettons les batchs au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `evaluate()` : + +```py +from tqdm.notebook import tqdm + +gradient_accumulation_steps = 8 +eval_steps = 5_000 + +model.train() +completed_steps = 0 +for epoch in range(num_train_epochs): + for step, batch in tqdm( + enumerate(train_dataloader, start=1), total=len(train_dataloader) + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "lr": get_lr(), + "samples": step * samples_per_step, + "steps": completed_steps, + "loss/train": loss.item() * gradient_accumulation_steps, + } + ) + loss = loss / gradient_accumulation_steps + accelerator.backward(loss) + if step % gradient_accumulation_steps == 0: + accelerator.clip_grad_norm_(model.parameters(), 1.0) + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + completed_steps += 1 + if (step % (eval_steps * gradient_accumulation_steps)) == 0: + eval_loss, perplexity = evaluate() + accelerator.print({"loss/eval": eval_loss, "perplexity": perplexity}) + model.train() + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress step {step}", blocking=False + ) +``` + +Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que le GPT-2. Vous pouvez encore l'adapter à vos besoins. + + + +✏️ **Essayez !** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement. + + + + + +✏️ **Essayez !** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que *TensorBoard* ou *Weights & Biases*. Ajoutez l'un d'eux à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement. + + + +{/if} diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index b703523bd..ab5fd9f75 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1,1230 +1,1230 @@ - - -# Réponse aux questions - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de « d'étendue de texte » dans le document lui-même. - - - -Nous allons *finetuner* un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celui-ci : - - - - -Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F) - - - -💡 Les modèles basé que sur l'encodeur comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme « Qui a inventé l'architecture Transformer ? » mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme « Pourquoi le ciel est-il bleu ? ». Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme le T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/course/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). - - - -## Préparation des données - -Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/). C'est donc celui que nous utiliserons ici. Il existe également une version plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. - -### Le jeu de données SQuAD - -Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("squad") -``` - -Nous pouvons jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données SQuAD : - -```py -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['id', 'title', 'context', 'question', 'answers'], - num_rows: 87599 - }) - validation: Dataset({ - features: ['id', 'title', 'context', 'question', 'answers'], - num_rows: 10570 - }) -}) -``` - -On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`. Affichons-les pour le premier élément de notre ensemble d'entraînement : - -```py -print("Context: ", raw_datasets["train"][0]["context"]) -print("Question: ", raw_datasets["train"][0]["question"]) -print("Answer: ", raw_datasets["train"][0]["answers"]) -``` - -```python out -Context: 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.' -# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. -Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' -# A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? -Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} -``` - -Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation. Si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. - -Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `Dataset.filter()` : - -```py -raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1) -``` - -```python out -Dataset({ - features: ['id', 'title', 'context', 'question', 'answers'], - num_rows: 0 -}) -``` - -Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes : - -```py -print(raw_datasets["validation"][0]["answers"]) -print(raw_datasets["validation"][2]["answers"]) -``` - -```python out -{'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]} -{'text': ['Santa Clara, California', "Levi's Stadium", "Levi's Stadium in the San Francisco Bay Area at Santa Clara, California."], 'answer_start': [403, 355, 355]} -``` - -Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé pour nous par une métrique de 🤗 *Datasets*. La version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Par exemple, si nous regardons l'échantillon de l'indice 2 : - -```py -print(raw_datasets["validation"][2]["context"]) -print(raw_datasets["validation"][2]["question"]) -``` - -```python out -'Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi\'s Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.' -# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' -'Where did Super Bowl 50 take place?' -# Où a eu lieu le Super Bowl 50 ? -``` - -nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. - -### Traitement des données d'entraînement - - - -Commençons par le prétraitement des données d'entraînement. La partie la plus difficile est de générer des étiquettes pour la réponse à la question, c'est-à-dire les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. - -Mais ne nous emballons pas. Tout d'abord, à l'aide d'un *tokenizer*, nous devons convertir le texte d'entrée en identifiants que le modèle peut comprendre : - -```py -from transformers import AutoTokenizer - -model_checkpoint = "bert-base-cased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec un *tokenizer* rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : - -```py -tokenizer.is_fast -``` - -```python out -True -``` - -Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble. Il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : - -``` -[CLS] question [SEP] context [SEP] -``` - -Vérifions à nouveau : - -```py -context = raw_datasets["train"][0]["context"] -question = raw_datasets["train"][0]["question"] - -inputs = tokenizer(question, context) -tokenizer.decode(inputs["input_ids"]) -``` - -```python out -'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, ' -'the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin ' -'Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms ' -'upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred ' -'Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a ' -'replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette ' -'Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues ' -'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' - -'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' -'l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' -'Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ' -'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré ' -'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' -'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' -'Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' -'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]' -``` - -Les étiquettes sont l'index des *tokens* de début et de fin de la réponse. Le modèle sera chargé de prédire dans l'entrée un logit de début et de fin par *token*, les étiquettes théoriques étant les suivantes : - -
-One-hot encoded labels for question answering. - -
- -Dans ce cas, le contexte n'est pas trop long, mais certains des exemples du jeu de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré le pipeline de `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données et avec une fenêtre glissante entre eux. - -Pour voir comment cela fonctionne sur notre exemple, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons : - -- `max_length` pour définir la longueur maximale (ici 100) -- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue -- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) -- `return_overflowing_tokens=True` pour indiquer au *tokenizer* que l'on veut les *tokens* qui débordent - -```py -inputs = tokenizer( - question, - context, - max_length=100, - truncation="only_second", - stride=50, - return_overflowing_tokens=True, -) - -for ids in inputs["input_ids"]: - print(tokenizer.decode(ids)) -``` - -```python out -'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basi [SEP]' -'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basi [SEP]' - -'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin [SEP]' -'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [SEP]' - -'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 [SEP]' -'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale ( et dans une ligne directe qui relie par 3 [SEP]' - -'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP]. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' -'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d or), se trouve une simple statue de pierre moderne de Marie. [SEP]' -``` - -Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question (« Bernadette Soubirous ») n'apparaît que dans la troisième et la dernière entrée. Donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. - -Le jeu de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les correspondances *offset* que nous avons étudiés au [chapitre 6](/course/fr/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `return_offsets_mapping=True` : - -```py -inputs = tokenizer( - question, - context, - max_length=100, - truncation="only_second", - stride=50, - return_overflowing_tokens=True, - return_offsets_mapping=True, -) -inputs.keys() -``` - -```python out -dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) -``` - -Comme nous pouvons le voir, nous récupérons les identifiants d'entrée, les *tokens* de type identifiant, le masque d'attention, ainsi que la correspondance *offset* dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est en Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0` : - -```py -inputs["overflow_to_sample_mapping"] -``` - -```python out -[0, 0, 0, 0] -``` - -Mais si nous tokenisons davantage d'exemples, cela deviendra plus utile : - -```py -inputs = tokenizer( - raw_datasets["train"][2:6]["question"], - raw_datasets["train"][2:6]["context"], - max_length=100, - truncation="only_second", - stride=50, - return_overflowing_tokens=True, - return_offsets_mapping=True, -) - -print(f"The 4 examples gave {len(inputs['input_ids'])} features.") -print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.") -``` - -```python out -'The 4 examples gave 19 features.' -'Here is where each comes from: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3].' -``` - -Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques. - -Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : - -- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. -- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les identifiants d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les identifiants d'entrée) où la réponse se termine. - -Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les identifiants d'entrée. Nous pourrions utiliser les *tokens* de type identifiants pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre *tokenizer* retourne. - -Une fois que nous avons ces indices de *tokens*, nous regardons les *offsets* correspondants, qui sont des *tuples* de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le morceau de contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : - -```py -answers = raw_datasets["train"][2:6]["answers"] -start_positions = [] -end_positions = [] - -for i, offset in enumerate(inputs["offset_mapping"]): - sample_idx = inputs["overflow_to_sample_mapping"][i] - answer = answers[sample_idx] - start_char = answer["answer_start"][0] - end_char = answer["answer_start"][0] + len(answer["text"][0]) - sequence_ids = inputs.sequence_ids(i) - - # Trouver le début et la fin du contexte - idx = 0 - while sequence_ids[idx] != 1: - idx += 1 - context_start = idx - while sequence_ids[idx] == 1: - idx += 1 - context_end = idx - 1 - - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0) - if offset[context_start][0] > start_char or offset[context_end][1] < end_char: - start_positions.append(0) - end_positions.append(0) - else: - # Sinon, ce sont les positions de début et de fin du token - idx = context_start - while idx <= context_end and offset[idx][0] <= start_char: - idx += 1 - start_positions.append(idx - 1) - - idx = context_end - while idx >= context_start and offset[idx][1] >= end_char: - idx -= 1 - end_positions.append(idx + 1) - -start_positions, end_positions -``` - -```python out -([83, 51, 19, 0, 0, 64, 27, 0, 34, 0, 0, 0, 67, 34, 0, 0, 0, 0, 0], - [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0]) -``` - -Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes. Comparons alors la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : - -```py -idx = 0 -sample_idx = inputs["overflow_to_sample_mapping"][idx] -answer = answers[sample_idx]["text"][0] - -start = start_positions[idx] -end = end_positions[idx] -labeled_answer = tokenizer.decode(inputs["input_ids"][idx][start : end + 1]) - -print(f"Theoretical answer: {answer}, labels give: {labeled_answer}") -``` - -```python out -'Theoretical answer: the Main Building, labels give: the Main Building' -``` - -Cela correspond ! Maintenant vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, signifiant que la réponse n'est pas dans le morceau de contexte de cette caractéristique : - -```py -idx = 4 -sample_idx = inputs["overflow_to_sample_mapping"][idx] -answer = answers[sample_idx]["text"][0] - -decoded_example = tokenizer.decode(inputs["input_ids"][idx]) -print(f"Theoretical answer: {answer}, decoded example: {decoded_example}") -``` - -```python out -'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grot [SEP]' -``` - -En effet, nous ne voyons pas la réponse dans le contexte. - - - -✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué. - - - -Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques). Il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : - -```py -max_length = 384 -stride = 128 - - -def preprocess_training_examples(examples): - questions = [q.strip() for q in examples["question"]] - inputs = tokenizer( - questions, - examples["context"], - max_length=max_length, - truncation="only_second", - stride=stride, - return_overflowing_tokens=True, - return_offsets_mapping=True, - padding="max_length", - ) - - offset_mapping = inputs.pop("offset_mapping") - sample_map = inputs.pop("overflow_to_sample_mapping") - answers = examples["answers"] - start_positions = [] - end_positions = [] - - for i, offset in enumerate(offset_mapping): - sample_idx = sample_map[i] - answer = answers[sample_idx] - start_char = answer["answer_start"][0] - end_char = answer["answer_start"][0] + len(answer["text"][0]) - sequence_ids = inputs.sequence_ids(i) - - # Trouver le début et la fin du contexte - idx = 0 - while sequence_ids[idx] != 1: - idx += 1 - context_start = idx - while sequence_ids[idx] == 1: - idx += 1 - context_end = idx - 1 - - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0) - if offset[context_start][0] > start_char or offset[context_end][1] < end_char: - start_positions.append(0) - end_positions.append(0) - else: - # Sinon, ce sont les positions de début et de fin du token - idx = context_start - while idx <= context_end and offset[idx][0] <= start_char: - idx += 1 - start_positions.append(idx - 1) - - idx = context_end - while idx >= context_start and offset[idx][1] >= end_char: - idx -= 1 - end_positions.append(idx + 1) - - inputs["start_positions"] = start_positions - inputs["end_positions"] = end_positions - return inputs -``` - -Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. - -Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur du jeu de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : - -```py -train_dataset = raw_datasets["train"].map( - preprocess_training_examples, - batched=True, - remove_columns=raw_datasets["train"].column_names, -) -len(raw_datasets["train"]), len(train_dataset) -``` - -```python out -(87599, 88729) -``` - -Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé. Passons au prétraitement de l'ensemble de validation ! - -### Traitement des données de validation - -Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais elle ne nous aidera pas vraiment à comprendre la qualité du modèle). Le réel plaisir sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les correspondances d'*offset* et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne identifiant dans le jeu de données original, nous l'utiliserons. - -La seule chose que nous allons ajouter ici est un petit nettoyage des correspondances d'*offset*. Elles contiendront les *offsets* pour la question et le contexte, mais une fois que nous serons à la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des identifiants d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les *offsets* correspondant à la question à `None` : - -```py -def preprocess_validation_examples(examples): - questions = [q.strip() for q in examples["question"]] - inputs = tokenizer( - questions, - examples["context"], - max_length=max_length, - truncation="only_second", - stride=stride, - return_overflowing_tokens=True, - return_offsets_mapping=True, - padding="max_length", - ) - - sample_map = inputs.pop("overflow_to_sample_mapping") - example_ids = [] - - for i in range(len(inputs["input_ids"])): - sample_idx = sample_map[i] - example_ids.append(examples["id"][sample_idx]) - - sequence_ids = inputs.sequence_ids(i) - offset = inputs["offset_mapping"][i] - inputs["offset_mapping"][i] = [ - o if sequence_ids[k] == 1 else None for k, o in enumerate(offset) - ] - - inputs["example_id"] = example_ids - return inputs -``` - -Nous pouvons appliquer cette fonction sur l'ensemble de validation comme précédemment : - -```py -validation_dataset = raw_datasets["validation"].map( - preprocess_validation_examples, - batched=True, - remove_columns=raw_datasets["validation"].column_names, -) -len(raw_datasets["validation"]), len(validation_dataset) -``` - -```python out -(10570, 10822) -``` - -Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de validation soient un peu plus courts. - -Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. - -{#if fw === 'pt'} - -## Finetuner le modèle avec l'API `Trainer` - -Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. - -{:else} - -## Finetuner fin du modèle avec Keras - -Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. - -{/if} - -### Post-traitement - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Le modèle produira des logits pour les positions de début et de fin de la réponse dans les identifiants d'entrée, comme nous l'avons vu lors de notre exploration du pipeline de `question-answering` [au chapitre 6](/course/fr/chapter6/3b). L'étape de post-traitement sera similaire à ce que nous avons fait à ce chapitre là. Voici un rapide rappel des actions que nous avons prises : - -- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, -- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant une fonction SoftMax, -- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, -- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). - -Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape de la SoftMax. Pour aller plus vite, nous ne donnerons pas non plus un score à toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux `n_best` logits les plus élevés (avec `n_best=20`). Puisque nous sautons la SoftMax, les scores seront des scores logi, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\\)). - -Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline de `question-answering` pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment car elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet par le *tokenizer* du modèle que nous voulons utiliser temporairement : - -```python -small_eval_set = raw_datasets["validation"].select(range(100)) -trained_checkpoint = "distilbert-base-cased-distilled-squad" - -tokenizer = AutoTokenizer.from_pretrained(trained_checkpoint) -eval_set = small_eval_set.map( - preprocess_validation_examples, - batched=True, - remove_columns=raw_datasets["validation"].column_names, -) -``` - -Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine : - -```python -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle. Nous construisons un batch avec tout de ce petit ensemble de validation et le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : - -{#if fw === 'pt'} - -```python -import torch -from transformers import AutoModelForQuestionAnswering - -eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) -eval_set_for_model.set_format("torch") - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -batch = {k: eval_set_for_model[k].to(device) for k in eval_set_for_model.column_names} -trained_model = AutoModelForQuestionAnswering.from_pretrained(trained_checkpoint).to( - device -) - -with torch.no_grad(): - outputs = trained_model(**batch) -``` - -Puisque `Trainer` nous donne les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : - -```python -start_logits = outputs.start_logits.cpu().numpy() -end_logits = outputs.end_logits.cpu().numpy() -``` - -{:else} - -```python -import tensorflow as tf -from transformers import TFAutoModelForQuestionAnswering - -eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) -eval_set_for_model.set_format("numpy") - -batch = {k: eval_set_for_model[k] for k in eval_set_for_model.column_names} -trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoint) - -outputs = trained_model(**batch) -``` - -Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy : - -```python -start_logits = outputs.start_logits.numpy() -end_logits = outputs.end_logits.numpy() -``` - -{/if} - -Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `eval_set` : - -```python -import collections - -example_to_features = collections.defaultdict(list) -for idx, feature in enumerate(eval_set): - example_to_features[feature["example_id"]].append(idx) -``` - -Avec cela, nous pouvons vraiment nous mettre au travail en bouclant tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_best` logits de début et logits de fin, en excluant les positions qui donnent : - -- une réponse qui ne serait pas dans le contexte -- une réponse avec une longueur négative -- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) - -Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit : - -```python -import numpy as np - -n_best = 20 -max_answer_length = 30 -predicted_answers = [] - -for example in small_eval_set: - example_id = example["id"] - context = example["context"] - answers = [] - - for feature_index in example_to_features[example_id]: - start_logit = start_logits[feature_index] - end_logit = end_logits[feature_index] - offsets = eval_set["offset_mapping"][feature_index] - - start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() - end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() - for start_index in start_indexes: - for end_index in end_indexes: - # Ignore les réponses qui ne sont pas entièrement dans le contexte - if offsets[start_index] is None or offsets[end_index] is None: - continue - # Ignore les réponses dont la longueur est soit < 0 soit > max_answer_length - if ( - end_index < start_index - or end_index - start_index + 1 > max_answer_length - ): - continue - - answers.append( - { - "text": context[offsets[start_index][0] : offsets[end_index][1]], - "logit_score": start_logit[start_index] + end_logit[end_index], - } - ) - - best_answer = max(answers, key=lambda x: x["logit_score"]) - predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) -``` - -Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Evaluate* : - -```python -import evaluate - -metric = evaluate.load("squad") -``` - -Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour les réponses possibles) : - -```python -theoretical_answers = [ - {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set -] -``` - -Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes : - -```python -print(predicted_answers[0]) -print(theoretical_answers[0]) -``` - -```python out -{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'} -{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}} -``` - -Pas trop mal ! Voyons maintenant le score que la métrique nous donne : - -```python -metric.compute(predictions=predicted_answers, references=theoretical_answers) -``` - -```python out -{'exact_match': 83.0, 'f1': 88.25} -``` - -Encore une fois, c'est plutôt bon si l'on considère que, d'après [le papier](https://arxiv.org/abs/1910.01108v2) de DistilBERT, *finetuné* sur SQuAD, ce modèle obtient 79,1 et 86,9 pour ces scores sur l'ensemble du jeu de données. - -{#if fw === 'pt'} - -Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un *tuple* `eval_preds` avec les logits et les étiquettes. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux. Ainsi nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation standards pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. - -La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment. Nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). - -{:else} - -Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux : - -{/if} - -```python -from tqdm.auto import tqdm - - -def compute_metrics(start_logits, end_logits, features, examples): - example_to_features = collections.defaultdict(list) - for idx, feature in enumerate(features): - example_to_features[feature["example_id"]].append(idx) - - predicted_answers = [] - for example in tqdm(examples): - example_id = example["id"] - context = example["context"] - answers = [] - - # Parcourir en boucle toutes les fonctionnalités associées à cet exemple - for feature_index in example_to_features[example_id]: - start_logit = start_logits[feature_index] - end_logit = end_logits[feature_index] - offsets = features[feature_index]["offset_mapping"] - - start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() - end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() - for start_index in start_indexes: - for end_index in end_indexes: - # Ignore les réponses qui ne sont pas entièrement dans le contexte - if offsets[start_index] is None or offsets[end_index] is None: - continue - # Ignore les réponses dont la longueur est soit < 0, soit > max_answer_length - if ( - end_index < start_index - or end_index - start_index + 1 > max_answer_length - ): - continue - - answer = { - "text": context[offsets[start_index][0] : offsets[end_index][1]], - "logit_score": start_logit[start_index] + end_logit[end_index], - } - answers.append(answer) - - # Sélectionne la réponse avec le meilleur score - if len(answers) > 0: - best_answer = max(answers, key=lambda x: x["logit_score"]) - predicted_answers.append( - {"id": example_id, "prediction_text": best_answer["text"]} - ) - else: - predicted_answers.append({"id": example_id, "prediction_text": ""}) - - theoretical_answers = [{"id": ex["id"], "answers": ex["answers"]} for ex in examples] - return metric.compute(predictions=predicted_answers, references=theoretical_answers) -``` - -Nous pouvons vérifier que cela fonctionne sur nos prédictions : - -```python -compute_metrics(start_logits, end_logits, eval_set, small_eval_set) -``` - -```python out -{'exact_match': 83.0, 'f1': 88.25} -``` - -C'est bien ! Maintenant, utilisons ceci pour *finetuner* notre modèle. - -### Finetuning du modèle - -{#if fw === 'pt'} - -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : - -```python -model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -{:else} - -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : - -```python -model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -{/if} - -Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! - -Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un *widget* où vous pouvez entrer vos identifiants de connexion : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'pt'} - -Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation standard à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation cela dans le paragraphe « Une boucle d'entraînement personnalisée » ci-dessous. - -C'est là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement est facile. - -Jetons un coup d'œil à notre `TrainingArguments` : - -```python -from transformers import TrainingArguments - -args = TrainingArguments( - "bert-finetuned-squad", - evaluation_strategy="no", - save_strategy="epoch", - learning_rate=2e-5, - num_train_epochs=3, - weight_decay=0.01, - fp16=True, - push_to_hub=True, -) -``` - -Nous avons déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques d'entraînement, un taux de décroissance des poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le *Hub*. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. - -{:else} - -Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : - -```python -from transformers import DefaultDataCollator - -data_collator = DefaultDataCollator(return_tensors="tf") -``` - -Et maintenant nous créons les jeux de données comme d'habitude. - -```python -tf_train_dataset = train_dataset.to_tf_dataset( - columns=[ - "input_ids", - "start_positions", - "end_positions", - "attention_mask", - "token_type_ids", - ], - collate_fn=data_collator, - shuffle=True, - batch_size=16, -) -tf_eval_dataset = validation_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "token_type_ids"], - collate_fn=data_collator, - shuffle=False, - batch_size=16, -) -``` - -Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle : - -```python -from transformers import create_optimizer -from transformers.keras_callbacks import PushToHubCallback -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, -# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, -# et non le jeu de données original donc son len() est déjà num_samples // batch_size. -num_train_epochs = 3 -num_train_steps = len(tf_train_dataset) * num_train_epochs -optimizer, schedule = create_optimizer( - init_lr=2e-5, - num_warmup_steps=0, - num_train_steps=num_train_steps, - weight_decay_rate=0.01, -) -model.compile(optimizer=optimizer) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Enfin, nous sommes prêts à entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. - -{/if} - -Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id`, par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). - -{#if fw === 'pt'} - - - -💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`). - - - -Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement : - -```python -from transformers import Trainer - -trainer = Trainer( - model=model, - args=args, - train_dataset=train_dataset, - eval_dataset=validation_dataset, - tokenizer=tokenizer, -) -trainer.train() -``` - -{:else} - -```python -from transformers.keras_callbacks import PushToHubCallback - -callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer) - -# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement. -model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) -``` - -{/if} - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le *Hub* et vous pourrez commencer à jouer avec votre modèle sur sa page. - -{#if fw === 'pt'} - -Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un *tuple* où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `compute_metrics()` : - -```python -predictions, _ = trainer.predict(validation_dataset) -start_logits, end_logits = predictions -compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) -``` - -{:else} - -Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne : - -```python -predictions = model.predict(tf_eval_dataset) -compute_metrics( - predictions["start_logits"], - predictions["end_logits"], - validation_dataset, - raw_datasets["validation"], -) -``` - -{/if} - -```python out -{'exact_match': 81.18259224219489, 'f1': 88.67381321905516} -``` - -Super ! À titre de comparaison, les scores indiqués dans l'article de BERT pour ce tâche sont de 80,8 et 88,5. Donc nous sommes exactement là où nous devrions être. - -{#if fw === 'pt'} - -Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle : - -```py -trainer.push_to_hub(commit_message="Training complete") -``` - -Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' -``` - -Le `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. - -{/if} - -À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question. Félicitations ! - - - -✏️ **A votre tour** Essayez un autre modèle pour voir s'il est plus performant pour cette tâche ! - - - -{#if fw === 'pt'} - -Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. - -### Préparer tout pour l'entraînement - -Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"` et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par 🤗 *Transformers* comme `collate_fn` et mélanger l'ensemble d'entraînement mais pas celui de validation : - -```py -from torch.utils.data import DataLoader -from transformers import default_data_collator - -train_dataset.set_format("torch") -validation_set = validation_dataset.remove_columns(["example_id", "offset_mapping"]) -validation_set.set_format("torch") - -train_dataloader = DataLoader( - train_dataset, - shuffle=True, - collate_fn=default_data_collator, - batch_size=8, -) -eval_dataloader = DataLoader( - validation_set, collate_fn=default_data_collator, batch_size=8 -) -``` - -Ensuite, nous réinstantifions notre modèle afin de nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle BERT pré-entraîné : - -```py -model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam mais avec une correction dans la façon dont le taux de décroissance des poids est appliqué : - -```py -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant l'argument `fp16=True` à `Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). - -```py -from accelerate import Accelerator - -accelerator = Accelerator(fp16=True) -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes : - -```py -from transformers import get_scheduler - -num_train_epochs = 3 -num_update_steps_per_epoch = len(train_dataloader) -num_training_steps = num_train_epochs * num_update_steps_per_epoch - -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -``` - -Pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : - -```py -from huggingface_hub import Repository, get_full_repo_name - -model_name = "bert-finetuned-squad-accelerate" -repo_name = get_full_repo_name(model_name) -repo_name -``` - -```python out -'sgugger/bert-finetuned-squad-accelerate' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : - -```py -output_dir = "bert-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -## Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : - -- l'entraînement à proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. -- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer car `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. -- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. - -Voici le code complet de la boucle d'entraînement : - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - start_logits = [] - end_logits = [] - accelerator.print("Evaluation!") - for batch in tqdm(eval_dataloader): - with torch.no_grad(): - outputs = model(**batch) - - start_logits.append(accelerator.gather(outputs.start_logits).cpu().numpy()) - end_logits.append(accelerator.gather(outputs.end_logits).cpu().numpy()) - - start_logits = np.concatenate(start_logits) - end_logits = np.concatenate(end_logits) - start_logits = start_logits[: len(validation_dataset)] - end_logits = end_logits[: len(validation_dataset)] - - metrics = compute_metrics( - start_logits, end_logits, validation_dataset, raw_datasets["validation"] - ) - print(f"epoch {epoch}:", metrics) - - # Sauvegarder et télécharger - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : - -```py -accelerator.wait_for_everyone() -unwrapped_model = accelerator.unwrap_model(model) -unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) -``` - -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué. Donc il n'aura plus la méthode `save_pretrained()` car la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. - -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle finetuné - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : - -```py -from transformers import pipeline - -# Remplacez par votre propre checkpoint -model_checkpoint = "huggingface-course/bert-finetuned-squad" -question_answerer = pipeline("question-answering", model=model_checkpoint) - -context = """ -🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration -between them. It's straightforward to train your models with one before loading them for inference with the other. -""" -question = "Which deep learning libraries back 🤗 Transformers?" -question_answerer(question=question, context=context) -``` - -```python out -{'score': 0.9979003071784973, - 'start': 78, - 'end': 105, - 'answer': 'Jax, PyTorch and TensorFlow'} -``` - -Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! + + +# Réponse aux questions + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de « d'étendue de texte » dans le document lui-même. + + + +Nous allons *finetuner* un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celui-ci : + + + + +Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F) + + + +💡 Les modèles basé que sur l'encodeur comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme « Qui a inventé l'architecture Transformer ? » mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme « Pourquoi le ciel est-il bleu ? ». Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme le T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/course/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). + + + +## Préparation des données + +Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/). C'est donc celui que nous utiliserons ici. Il existe également une version plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. + +### Le jeu de données SQuAD + +Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +Nous pouvons jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données SQuAD : + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 87599 + }) + validation: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 10570 + }) +}) +``` + +On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`. Affichons-les pour le premier élément de notre ensemble d'entraînement : + +```py +print("Context: ", raw_datasets["train"][0]["context"]) +print("Question: ", raw_datasets["train"][0]["question"]) +print("Answer: ", raw_datasets["train"][0]["answers"]) +``` + +```python out +Context: 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.' +# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. +Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' +# A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? +Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} +``` + +Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation. Si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. + +Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `Dataset.filter()` : + +```py +raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1) +``` + +```python out +Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 0 +}) +``` + +Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes : + +```py +print(raw_datasets["validation"][0]["answers"]) +print(raw_datasets["validation"][2]["answers"]) +``` + +```python out +{'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]} +{'text': ['Santa Clara, California', "Levi's Stadium", "Levi's Stadium in the San Francisco Bay Area at Santa Clara, California."], 'answer_start': [403, 355, 355]} +``` + +Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé pour nous par une métrique de 🤗 *Datasets*. La version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Par exemple, si nous regardons l'échantillon de l'indice 2 : + +```py +print(raw_datasets["validation"][2]["context"]) +print(raw_datasets["validation"][2]["question"]) +``` + +```python out +'Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi\'s Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.' +# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' +'Where did Super Bowl 50 take place?' +# Où a eu lieu le Super Bowl 50 ? +``` + +nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. + +### Traitement des données d'entraînement + + + +Commençons par le prétraitement des données d'entraînement. La partie la plus difficile est de générer des étiquettes pour la réponse à la question, c'est-à-dire les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. + +Mais ne nous emballons pas. Tout d'abord, à l'aide d'un *tokenizer*, nous devons convertir le texte d'entrée en identifiants que le modèle peut comprendre : + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec un *tokenizer* rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble. Il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : + +``` +[CLS] question [SEP] context [SEP] +``` + +Vérifions à nouveau : + +```py +context = raw_datasets["train"][0]["context"] +question = raw_datasets["train"][0]["question"] + +inputs = tokenizer(question, context) +tokenizer.decode(inputs["input_ids"]) +``` + +```python out +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, ' +'the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin ' +'Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms ' +'upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred ' +'Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a ' +'replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette ' +'Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues ' +'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' + +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' +'l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' +'Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ' +'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré ' +'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' +'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' +'Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' +'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]' +``` + +Les étiquettes sont l'index des *tokens* de début et de fin de la réponse. Le modèle sera chargé de prédire dans l'entrée un logit de début et de fin par *token*, les étiquettes théoriques étant les suivantes : + +
+One-hot encoded labels for question answering. + +
+ +Dans ce cas, le contexte n'est pas trop long, mais certains des exemples du jeu de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré le pipeline de `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données et avec une fenêtre glissante entre eux. + +Pour voir comment cela fonctionne sur notre exemple, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons : + +- `max_length` pour définir la longueur maximale (ici 100) +- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue +- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) +- `return_overflowing_tokens=True` pour indiquer au *tokenizer* que l'on veut les *tokens* qui débordent + +```py +inputs = tokenizer( + question, + context, + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basi [SEP]' +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basi [SEP]' + +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin [SEP]' +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [SEP]' + +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 [SEP]' +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale ( et dans une ligne directe qui relie par 3 [SEP]' + +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP]. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' +'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d or), se trouve une simple statue de pierre moderne de Marie. [SEP]' +``` + +Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question (« Bernadette Soubirous ») n'apparaît que dans la troisième et la dernière entrée. Donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. + +Le jeu de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les correspondances *offset* que nous avons étudiés au [chapitre 6](/course/fr/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `return_offsets_mapping=True` : + +```py +inputs = tokenizer( + question, + context, + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +inputs.keys() +``` + +```python out +dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) +``` + +Comme nous pouvons le voir, nous récupérons les identifiants d'entrée, les *tokens* de type identifiant, le masque d'attention, ainsi que la correspondance *offset* dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est en Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0` : + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +Mais si nous tokenisons davantage d'exemples, cela deviendra plus utile : + +```py +inputs = tokenizer( + raw_datasets["train"][2:6]["question"], + raw_datasets["train"][2:6]["context"], + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, + return_offsets_mapping=True, +) + +print(f"The 4 examples gave {len(inputs['input_ids'])} features.") +print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.") +``` + +```python out +'The 4 examples gave 19 features.' +'Here is where each comes from: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3].' +``` + +Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques. + +Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : + +- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. +- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les identifiants d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les identifiants d'entrée) où la réponse se termine. + +Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les identifiants d'entrée. Nous pourrions utiliser les *tokens* de type identifiants pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre *tokenizer* retourne. + +Une fois que nous avons ces indices de *tokens*, nous regardons les *offsets* correspondants, qui sont des *tuples* de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le morceau de contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : + +```py +answers = raw_datasets["train"][2:6]["answers"] +start_positions = [] +end_positions = [] + +for i, offset in enumerate(inputs["offset_mapping"]): + sample_idx = inputs["overflow_to_sample_mapping"][i] + answer = answers[sample_idx] + start_char = answer["answer_start"][0] + end_char = answer["answer_start"][0] + len(answer["text"][0]) + sequence_ids = inputs.sequence_ids(i) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0) + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: + start_positions.append(0) + end_positions.append(0) + else: + # Sinon, ce sont les positions de début et de fin du token + idx = context_start + while idx <= context_end and offset[idx][0] <= start_char: + idx += 1 + start_positions.append(idx - 1) + + idx = context_end + while idx >= context_start and offset[idx][1] >= end_char: + idx -= 1 + end_positions.append(idx + 1) + +start_positions, end_positions +``` + +```python out +([83, 51, 19, 0, 0, 64, 27, 0, 34, 0, 0, 0, 67, 34, 0, 0, 0, 0, 0], + [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0]) +``` + +Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes. Comparons alors la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : + +```py +idx = 0 +sample_idx = inputs["overflow_to_sample_mapping"][idx] +answer = answers[sample_idx]["text"][0] + +start = start_positions[idx] +end = end_positions[idx] +labeled_answer = tokenizer.decode(inputs["input_ids"][idx][start : end + 1]) + +print(f"Theoretical answer: {answer}, labels give: {labeled_answer}") +``` + +```python out +'Theoretical answer: the Main Building, labels give: the Main Building' +``` + +Cela correspond ! Maintenant vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, signifiant que la réponse n'est pas dans le morceau de contexte de cette caractéristique : + +```py +idx = 4 +sample_idx = inputs["overflow_to_sample_mapping"][idx] +answer = answers[sample_idx]["text"][0] + +decoded_example = tokenizer.decode(inputs["input_ids"][idx]) +print(f"Theoretical answer: {answer}, decoded example: {decoded_example}") +``` + +```python out +'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grot [SEP]' +``` + +En effet, nous ne voyons pas la réponse dans le contexte. + + + +✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué. + + + +Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques). Il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : + +```py +max_length = 384 +stride = 128 + + +def preprocess_training_examples(examples): + questions = [q.strip() for q in examples["question"]] + inputs = tokenizer( + questions, + examples["context"], + max_length=max_length, + truncation="only_second", + stride=stride, + return_overflowing_tokens=True, + return_offsets_mapping=True, + padding="max_length", + ) + + offset_mapping = inputs.pop("offset_mapping") + sample_map = inputs.pop("overflow_to_sample_mapping") + answers = examples["answers"] + start_positions = [] + end_positions = [] + + for i, offset in enumerate(offset_mapping): + sample_idx = sample_map[i] + answer = answers[sample_idx] + start_char = answer["answer_start"][0] + end_char = answer["answer_start"][0] + len(answer["text"][0]) + sequence_ids = inputs.sequence_ids(i) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0) + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: + start_positions.append(0) + end_positions.append(0) + else: + # Sinon, ce sont les positions de début et de fin du token + idx = context_start + while idx <= context_end and offset[idx][0] <= start_char: + idx += 1 + start_positions.append(idx - 1) + + idx = context_end + while idx >= context_start and offset[idx][1] >= end_char: + idx -= 1 + end_positions.append(idx + 1) + + inputs["start_positions"] = start_positions + inputs["end_positions"] = end_positions + return inputs +``` + +Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. + +Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur du jeu de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : + +```py +train_dataset = raw_datasets["train"].map( + preprocess_training_examples, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +len(raw_datasets["train"]), len(train_dataset) +``` + +```python out +(87599, 88729) +``` + +Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé. Passons au prétraitement de l'ensemble de validation ! + +### Traitement des données de validation + +Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais elle ne nous aidera pas vraiment à comprendre la qualité du modèle). Le réel plaisir sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les correspondances d'*offset* et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne identifiant dans le jeu de données original, nous l'utiliserons. + +La seule chose que nous allons ajouter ici est un petit nettoyage des correspondances d'*offset*. Elles contiendront les *offsets* pour la question et le contexte, mais une fois que nous serons à la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des identifiants d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les *offsets* correspondant à la question à `None` : + +```py +def preprocess_validation_examples(examples): + questions = [q.strip() for q in examples["question"]] + inputs = tokenizer( + questions, + examples["context"], + max_length=max_length, + truncation="only_second", + stride=stride, + return_overflowing_tokens=True, + return_offsets_mapping=True, + padding="max_length", + ) + + sample_map = inputs.pop("overflow_to_sample_mapping") + example_ids = [] + + for i in range(len(inputs["input_ids"])): + sample_idx = sample_map[i] + example_ids.append(examples["id"][sample_idx]) + + sequence_ids = inputs.sequence_ids(i) + offset = inputs["offset_mapping"][i] + inputs["offset_mapping"][i] = [ + o if sequence_ids[k] == 1 else None for k, o in enumerate(offset) + ] + + inputs["example_id"] = example_ids + return inputs +``` + +Nous pouvons appliquer cette fonction sur l'ensemble de validation comme précédemment : + +```py +validation_dataset = raw_datasets["validation"].map( + preprocess_validation_examples, + batched=True, + remove_columns=raw_datasets["validation"].column_names, +) +len(raw_datasets["validation"]), len(validation_dataset) +``` + +```python out +(10570, 10822) +``` + +Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de validation soient un peu plus courts. + +Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. + +{#if fw === 'pt'} + +## Finetuner le modèle avec l'API `Trainer` + +Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{:else} + +## Finetuner fin du modèle avec Keras + +Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{/if} + +### Post-traitement + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le modèle produira des logits pour les positions de début et de fin de la réponse dans les identifiants d'entrée, comme nous l'avons vu lors de notre exploration du pipeline de `question-answering` [au chapitre 6](/course/fr/chapter6/3b). L'étape de post-traitement sera similaire à ce que nous avons fait à ce chapitre là. Voici un rapide rappel des actions que nous avons prises : + +- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, +- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant une fonction SoftMax, +- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, +- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). + +Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape de la SoftMax. Pour aller plus vite, nous ne donnerons pas non plus un score à toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux `n_best` logits les plus élevés (avec `n_best=20`). Puisque nous sautons la SoftMax, les scores seront des scores logi, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\\)). + +Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline de `question-answering` pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment car elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet par le *tokenizer* du modèle que nous voulons utiliser temporairement : + +```python +small_eval_set = raw_datasets["validation"].select(range(100)) +trained_checkpoint = "distilbert-base-cased-distilled-squad" + +tokenizer = AutoTokenizer.from_pretrained(trained_checkpoint) +eval_set = small_eval_set.map( + preprocess_validation_examples, + batched=True, + remove_columns=raw_datasets["validation"].column_names, +) +``` + +Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine : + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle. Nous construisons un batch avec tout de ce petit ensemble de validation et le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : + +{#if fw === 'pt'} + +```python +import torch +from transformers import AutoModelForQuestionAnswering + +eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) +eval_set_for_model.set_format("torch") + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +batch = {k: eval_set_for_model[k].to(device) for k in eval_set_for_model.column_names} +trained_model = AutoModelForQuestionAnswering.from_pretrained(trained_checkpoint).to( + device +) + +with torch.no_grad(): + outputs = trained_model(**batch) +``` + +Puisque `Trainer` nous donne les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : + +```python +start_logits = outputs.start_logits.cpu().numpy() +end_logits = outputs.end_logits.cpu().numpy() +``` + +{:else} + +```python +import tensorflow as tf +from transformers import TFAutoModelForQuestionAnswering + +eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) +eval_set_for_model.set_format("numpy") + +batch = {k: eval_set_for_model[k] for k in eval_set_for_model.column_names} +trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoint) + +outputs = trained_model(**batch) +``` + +Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy : + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `eval_set` : + +```python +import collections + +example_to_features = collections.defaultdict(list) +for idx, feature in enumerate(eval_set): + example_to_features[feature["example_id"]].append(idx) +``` + +Avec cela, nous pouvons vraiment nous mettre au travail en bouclant tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_best` logits de début et logits de fin, en excluant les positions qui donnent : + +- une réponse qui ne serait pas dans le contexte +- une réponse avec une longueur négative +- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) + +Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit : + +```python +import numpy as np + +n_best = 20 +max_answer_length = 30 +predicted_answers = [] + +for example in small_eval_set: + example_id = example["id"] + context = example["context"] + answers = [] + + for feature_index in example_to_features[example_id]: + start_logit = start_logits[feature_index] + end_logit = end_logits[feature_index] + offsets = eval_set["offset_mapping"][feature_index] + + start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() + end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() + for start_index in start_indexes: + for end_index in end_indexes: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignore les réponses dont la longueur est soit < 0 soit > max_answer_length + if ( + end_index < start_index + or end_index - start_index + 1 > max_answer_length + ): + continue + + answers.append( + { + "text": context[offsets[start_index][0] : offsets[end_index][1]], + "logit_score": start_logit[start_index] + end_logit[end_index], + } + ) + + best_answer = max(answers, key=lambda x: x["logit_score"]) + predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) +``` + +Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Evaluate* : + +```python +import evaluate + +metric = evaluate.load("squad") +``` + +Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour les réponses possibles) : + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes : + +```python +print(predicted_answers[0]) +print(theoretical_answers[0]) +``` + +```python out +{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'} +{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}} +``` + +Pas trop mal ! Voyons maintenant le score que la métrique nous donne : + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Encore une fois, c'est plutôt bon si l'on considère que, d'après [le papier](https://arxiv.org/abs/1910.01108v2) de DistilBERT, *finetuné* sur SQuAD, ce modèle obtient 79,1 et 86,9 pour ces scores sur l'ensemble du jeu de données. + +{#if fw === 'pt'} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un *tuple* `eval_preds` avec les logits et les étiquettes. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux. Ainsi nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation standards pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. + +La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment. Nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). + +{:else} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux : + +{/if} + +```python +from tqdm.auto import tqdm + + +def compute_metrics(start_logits, end_logits, features, examples): + example_to_features = collections.defaultdict(list) + for idx, feature in enumerate(features): + example_to_features[feature["example_id"]].append(idx) + + predicted_answers = [] + for example in tqdm(examples): + example_id = example["id"] + context = example["context"] + answers = [] + + # Parcourir en boucle toutes les fonctionnalités associées à cet exemple + for feature_index in example_to_features[example_id]: + start_logit = start_logits[feature_index] + end_logit = end_logits[feature_index] + offsets = features[feature_index]["offset_mapping"] + + start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() + end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() + for start_index in start_indexes: + for end_index in end_indexes: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignore les réponses dont la longueur est soit < 0, soit > max_answer_length + if ( + end_index < start_index + or end_index - start_index + 1 > max_answer_length + ): + continue + + answer = { + "text": context[offsets[start_index][0] : offsets[end_index][1]], + "logit_score": start_logit[start_index] + end_logit[end_index], + } + answers.append(answer) + + # Sélectionne la réponse avec le meilleur score + if len(answers) > 0: + best_answer = max(answers, key=lambda x: x["logit_score"]) + predicted_answers.append( + {"id": example_id, "prediction_text": best_answer["text"]} + ) + else: + predicted_answers.append({"id": example_id, "prediction_text": ""}) + + theoretical_answers = [{"id": ex["id"], "answers": ex["answers"]} for ex in examples] + return metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +Nous pouvons vérifier que cela fonctionne sur nos prédictions : + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +C'est bien ! Maintenant, utilisons ceci pour *finetuner* notre modèle. + +### Finetuning du modèle + +{#if fw === 'pt'} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! + +Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un *widget* où vous pouvez entrer vos identifiants de connexion : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation standard à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation cela dans le paragraphe « Une boucle d'entraînement personnalisée » ci-dessous. + +C'est là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement est facile. + +Jetons un coup d'œil à notre `TrainingArguments` : + +```python +from transformers import TrainingArguments + +args = TrainingArguments( + "bert-finetuned-squad", + evaluation_strategy="no", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, + fp16=True, + push_to_hub=True, +) +``` + +Nous avons déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques d'entraînement, un taux de décroissance des poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le *Hub*. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. + +{:else} + +Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +Et maintenant nous créons les jeux de données comme d'habitude. + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle : + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà num_samples // batch_size. +num_train_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_train_epochs +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Enfin, nous sommes prêts à entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. + +{/if} + +Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id`, par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). + +{#if fw === 'pt'} + + + +💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`). + + + +Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement : + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=train_dataset, + eval_dataset=validation_dataset, + tokenizer=tokenizer, +) +trainer.train() +``` + +{:else} + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer) + +# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement. +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le *Hub* et vous pourrez commencer à jouer avec votre modèle sur sa page. + +{#if fw === 'pt'} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un *tuple* où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `compute_metrics()` : + +```python +predictions, _ = trainer.predict(validation_dataset) +start_logits, end_logits = predictions +compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) +``` + +{:else} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne : + +```python +predictions = model.predict(tf_eval_dataset) +compute_metrics( + predictions["start_logits"], + predictions["end_logits"], + validation_dataset, + raw_datasets["validation"], +) +``` + +{/if} + +```python out +{'exact_match': 81.18259224219489, 'f1': 88.67381321905516} +``` + +Super ! À titre de comparaison, les scores indiqués dans l'article de BERT pour ce tâche sont de 80,8 et 88,5. Donc nous sommes exactement là où nous devrions être. + +{#if fw === 'pt'} + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +Le `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. + +{/if} + +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question. Félicitations ! + + + +✏️ **A votre tour** Essayez un autre modèle pour voir s'il est plus performant pour cette tâche ! + + + +{#if fw === 'pt'} + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. + +### Préparer tout pour l'entraînement + +Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"` et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par 🤗 *Transformers* comme `collate_fn` et mélanger l'ensemble d'entraînement mais pas celui de validation : + +```py +from torch.utils.data import DataLoader +from transformers import default_data_collator + +train_dataset.set_format("torch") +validation_set = validation_dataset.remove_columns(["example_id", "offset_mapping"]) +validation_set.set_format("torch") + +train_dataloader = DataLoader( + train_dataset, + shuffle=True, + collate_fn=default_data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + validation_set, collate_fn=default_data_collator, batch_size=8 +) +``` + +Ensuite, nous réinstantifions notre modèle afin de nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle BERT pré-entraîné : + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam mais avec une correction dans la façon dont le taux de décroissance des poids est appliqué : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant l'argument `fp16=True` à `Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes : + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +``` + +Pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "bert-finetuned-squad-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/bert-finetuned-squad-accelerate' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +## Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- l'entraînement à proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. +- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer car `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. +- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + start_logits = [] + end_logits = [] + accelerator.print("Evaluation!") + for batch in tqdm(eval_dataloader): + with torch.no_grad(): + outputs = model(**batch) + + start_logits.append(accelerator.gather(outputs.start_logits).cpu().numpy()) + end_logits.append(accelerator.gather(outputs.end_logits).cpu().numpy()) + + start_logits = np.concatenate(start_logits) + end_logits = np.concatenate(end_logits) + start_logits = start_logits[: len(validation_dataset)] + end_logits = end_logits[: len(validation_dataset)] + + metrics = compute_metrics( + start_logits, end_logits, validation_dataset, raw_datasets["validation"] + ) + print(f"epoch {epoch}:", metrics) + + # Sauvegarder et télécharger + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué. Donc il n'aura plus la méthode `save_pretrained()` car la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle finetuné + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : + +```py +from transformers import pipeline + +# Remplacez par votre propre checkpoint +model_checkpoint = "huggingface-course/bert-finetuned-squad" +question_answerer = pipeline("question-answering", model=model_checkpoint) + +context = """ +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question = "Which deep learning libraries back 🤗 Transformers?" +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.9979003071784973, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! diff --git a/chapters/fr/chapter7/8.mdx b/chapters/fr/chapter7/8.mdx index aeea11abb..927345b35 100644 --- a/chapters/fr/chapter7/8.mdx +++ b/chapters/fr/chapter7/8.mdx @@ -1,17 +1,22 @@ -# Maîtriser le NLP - -Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de *NLP* avec 🤗 *Transformers* et l'écosystème d'*Hugging Face* ! - -Nous avons vu beaucoup de collecteurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : - - - -Après avoir terminé ce tour d'horizon des principales tâches de *NLP*, vous devriez : - -* savoir quelles architectures (encodeur, décodeur ou encodeur-décodeur) sont les mieux adaptées à chaque tâche, -* comprendre la différence entre le pré-entraînement et le *finetuning* d'un modèle de langage, -* savoir comment entraîner des *transformers* en utilisant soit l'API `Trainer` et les fonctionnalités d'entraînement distribué d' 🤗 *Accelerate* ou TensorFlow et Keras selon la piste que vous avez suivie, -* comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte, -* savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*. - +# Maîtriser le NLP + + + +Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de *NLP* avec 🤗 *Transformers* et l'écosystème d'*Hugging Face* ! + +Nous avons vu beaucoup de collecteurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : + + + +Après avoir terminé ce tour d'horizon des principales tâches de *NLP*, vous devriez : + +* savoir quelles architectures (encodeur, décodeur ou encodeur-décodeur) sont les mieux adaptées à chaque tâche, +* comprendre la différence entre le pré-entraînement et le *finetuning* d'un modèle de langage, +* savoir comment entraîner des *transformers* en utilisant soit l'API `Trainer` et les fonctionnalités d'entraînement distribué d' 🤗 *Accelerate* ou TensorFlow et Keras selon la piste que vous avez suivie, +* comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte, +* savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*. + Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un *bug* difficile dans votre code ou aurez une question sur la façon de résoudre un problème de *NLP* particulier. Heureusement, la communauté d'*Hugging Face* est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos modèles et demander de l'aide efficacement. \ No newline at end of file diff --git a/chapters/fr/chapter7/9.mdx b/chapters/fr/chapter7/9.mdx index ae063e9d0..c3f9e5934 100644 --- a/chapters/fr/chapter7/9.mdx +++ b/chapters/fr/chapter7/9.mdx @@ -1,324 +1,329 @@ - - - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de tokens ? - - - -### 2. Quelle partie du prétraitement pour la classification de tokens diffère des autres pipelines de prétraitement ? - --100 pour étiqueter les tokens spéciaux.", - explain: "Ce n'est pas spécifique à la classification de tokens. Nous utilisons toujours -100 comme étiquette pour les tokens que nous voulons ignorer dans la perte." - }, - { - text: "Nous devons nous assurer que les étiquettes sont tronquées ou rembourrées à la même taille que les entrées, lorsque nous appliquons la troncature/le padding.", - explain: "En effet mais ce n'est pas la seule différence.", - correct: true - } - ]} -/> - -### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de tokens et que nous voulons étiqueter les tokens ? - -tokenizer ajoute des tokens spéciaux et nous n'avons pas d'étiquettes pour eux.", - explain: "Nous les étiquetons par -100 ils sont donc ignorés dans la perte." - }, - { - text: "Chaque mot peut produire plusieurs tokens, ce qui fait que nous nous retrouvons avec plus de tokens que d'étiquettes.", - explain: "C'est le problème principal et nous devons aligner les étiquettes originales avec les tokens.", - correct: true - }, - { - text: "Les tokens ajoutés n'ont pas d'étiquettes, il n'y a donc pas de problème.", - explain: "Nous avons besoin d'autant d'étiquettes que de tokens, sinon nos modèles se tromperont." - } - ]} -/> - -### 4. Que signifie « adaptation au domaine » ? - -finetunons un modèle pré-entraîné sur un nouveau jeu de données et qu'il donne des prédictions qui sont plus adaptées à ce nouveau jeu de données.", - explain: "Le modèle a adapté ses connaissances au nouveau jeu de données.", - correct: true - }, - { - text: "C'est lorsque nous ajoutons des échantillons mal classés à un jeu de données pour rendre notre modèle plus robuste.", - explain: "C'est certainement quelque chose que vous devriez faire si vous réentraînez votre modèle régulièrement, mais ce n'est pas une adaptation au domaine." - } - ]} -/> - -### 5. Quelles sont les étiquettes dans un problème de modélisation du langage masqué ? - -tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux.", - explain: "C'est ça !", - correct: true - }, - { - text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux, décalés vers la gauche.", - explain: "Non, le déplacement des étiquettes vers la gauche correspond à la prédiction du mot suivant, ce qui est une modélisation causale du langage." - }, - { - text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et l'étiquette indique si la phrase est positive ou négative.", - explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." - }, - { - text: "Certains des tokens des deux phrases d'entrée sont masqués de manière aléatoire et l'étiquette indique si les deux phrases sont similaires ou non.", - explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." - } - ]} -/> - -### 6. Laquelle de ces tâches peut être considérée comme un problème de séquence à séquence ? - - - -### 7. Quelle est la bonne façon de prétraiter les données pour un problème de séquence à séquence ? - -tokenizer avec les éléments suivants inputs=... et targets=....", - explain: "Nous pourrions ajouter cette API à l'avenir mais ce n'est pas possible pour le moment." - }, - { - text: "Les entrées et les cibles doivent être prétraitées, en deux appels séparés au tokenizer.", - explain: "C'est vrai, mais incomplet. Il y a quelque chose que vous devez faire pour vous assurer que le tokenizer traite les deux correctement." - }, - { - text: "Comme d'habitude, nous devons simplement tokeniser les entrées.", - explain: "Pas dans un problème de classification de séquences. Les cibles sont aussi des textes que nous devons convertir en chiffres !" - }, - { - text: "Les entrées doivent être envoyées au tokenizer, et les cibles aussi, mais sous un gestionnaire de contexte spécial.", - explain: "C'est exact, le tokenizer doit être mis en mode cible par ce gestionnaire de contexte.", - correct: true - } - ]} -/> - -{#if fw === 'pt'} - -### 8. Pourquoi existe-t-il une sous-classe spécifique de Trainer pour les problèmes de séquence à séquence ? - --100.", - explain: "Ce n'est pas du tout une perte personnalisée mais la façon dont la perte est toujours calculée." - }, - { - text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale.", - explain: "Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode generate().", - correct: true - }, - { - text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence.", - explain: "Trainer ne se soucie pas vraiment de cela puisqu'elles ont été prétraités auparavant." - }, - { - text: "Parce que nous utilisons deux modèles dans les problèmes de séquence à séquence.", - explain: "Nous utilisons en quelque sorte deux modèles, un encodeur et un décodeur, mais ils sont regroupés dans un seul modèle." - } - ]} -/> - -{:else} - -### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle compile() sur un transformer ? - -tranformers sont entraînés avec un apprentissage autosupervisé.", - explain: "Pas tout à fait. Même l'apprentissage autosupervisé a besoin d'une fonction de perte !" - }, - { - text: "Parce que la sortie de perte interne du modèle est utilisée par défaut.", - explain: " ", - correct: true - }, - { - text: "Parce que nous calculons les mesures après l'entraînement au lieu de le faire.", - explain: "Nous le faisons souvent mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement." - }, - { - text: "Parce que la perte est spécifiée dans model.fit().", - explain: "La fonction de perte est toujours fixée une fois que vous exécutez model.compile() et ne peut pas être modifiée dans model.fit()." - } - ]} -/> - -{/if} - -### 10. Quand devez-vous pré-entraîner un nouveau modèle ? - -finetuner sur vos données afin d'éviter d'énormes coûts de calcul." - }, - { - text: "Lorsque vous avez des doutes sur le biais du modèle pré-entraîné que vous utilisez.", - explain: "C'est vrai mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.", - correct: true - }, - { - text: "Lorsque les modèles pré-entraînés disponibles ne sont tout simplement pas assez bons.", - explain: "Vous êtes sûr d'avoir bien débogué votre entraînement ?" - } - ]} -/> - -### 11. Pourquoi est-il facile de prétraîner un modèle de langage sur des batchs de textes ? - -Transformers ne nécessite que quelques lignes de code pour démarrer l'entraînement.", - explain: "Bien que vrai, cela ne répond pas vraiment à la question posée. Essayez une autre réponse !" - } - ]} -/> - -### 12. Quels sont les principaux défis lors du prétraitement des données pour une tâche de réponse à des questions ? - - - -### 13. Comment le post-traitement est-il généralement effectué dans les réponses aux questions ? - -tokens correspondant.", - explain: "Ce pourrait être une façon de faire mais c'est un peu trop simpliste." - }, - { - text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et il vous suffit de décoder la plage de tokens correspondant dans celui qui a le meilleur score.", - explain: "C'est proche du post-traitement que nous avons étudié, mais ce n'est pas tout à fait exact." - }, - { - text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.", - explain: "C'est ça en résumé !", - correct: true - }, - { - text: "Le modèle génère une réponse et il vous suffit de la décoder.", - explain: "A moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence." - } - ]} -/> + + + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de tokens ? + + + +### 2. Quelle partie du prétraitement pour la classification de tokens diffère des autres pipelines de prétraitement ? + +-100 pour étiqueter les tokens spéciaux.", + explain: "Ce n'est pas spécifique à la classification de tokens. Nous utilisons toujours -100 comme étiquette pour les tokens que nous voulons ignorer dans la perte." + }, + { + text: "Nous devons nous assurer que les étiquettes sont tronquées ou rembourrées à la même taille que les entrées, lorsque nous appliquons la troncature/le padding.", + explain: "En effet mais ce n'est pas la seule différence.", + correct: true + } + ]} +/> + +### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de tokens et que nous voulons étiqueter les tokens ? + +tokenizer ajoute des tokens spéciaux et nous n'avons pas d'étiquettes pour eux.", + explain: "Nous les étiquetons par -100 ils sont donc ignorés dans la perte." + }, + { + text: "Chaque mot peut produire plusieurs tokens, ce qui fait que nous nous retrouvons avec plus de tokens que d'étiquettes.", + explain: "C'est le problème principal et nous devons aligner les étiquettes originales avec les tokens.", + correct: true + }, + { + text: "Les tokens ajoutés n'ont pas d'étiquettes, il n'y a donc pas de problème.", + explain: "Nous avons besoin d'autant d'étiquettes que de tokens, sinon nos modèles se tromperont." + } + ]} +/> + +### 4. Que signifie « adaptation au domaine » ? + +finetunons un modèle pré-entraîné sur un nouveau jeu de données et qu'il donne des prédictions qui sont plus adaptées à ce nouveau jeu de données.", + explain: "Le modèle a adapté ses connaissances au nouveau jeu de données.", + correct: true + }, + { + text: "C'est lorsque nous ajoutons des échantillons mal classés à un jeu de données pour rendre notre modèle plus robuste.", + explain: "C'est certainement quelque chose que vous devriez faire si vous réentraînez votre modèle régulièrement, mais ce n'est pas une adaptation au domaine." + } + ]} +/> + +### 5. Quelles sont les étiquettes dans un problème de modélisation du langage masqué ? + +tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux.", + explain: "C'est ça !", + correct: true + }, + { + text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux, décalés vers la gauche.", + explain: "Non, le déplacement des étiquettes vers la gauche correspond à la prédiction du mot suivant, ce qui est une modélisation causale du langage." + }, + { + text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et l'étiquette indique si la phrase est positive ou négative.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." + }, + { + text: "Certains des tokens des deux phrases d'entrée sont masqués de manière aléatoire et l'étiquette indique si les deux phrases sont similaires ou non.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." + } + ]} +/> + +### 6. Laquelle de ces tâches peut être considérée comme un problème de séquence à séquence ? + + + +### 7. Quelle est la bonne façon de prétraiter les données pour un problème de séquence à séquence ? + +tokenizer avec les éléments suivants inputs=... et targets=....", + explain: "Nous pourrions ajouter cette API à l'avenir mais ce n'est pas possible pour le moment." + }, + { + text: "Les entrées et les cibles doivent être prétraitées, en deux appels séparés au tokenizer.", + explain: "C'est vrai, mais incomplet. Il y a quelque chose que vous devez faire pour vous assurer que le tokenizer traite les deux correctement." + }, + { + text: "Comme d'habitude, nous devons simplement tokeniser les entrées.", + explain: "Pas dans un problème de classification de séquences. Les cibles sont aussi des textes que nous devons convertir en chiffres !" + }, + { + text: "Les entrées doivent être envoyées au tokenizer, et les cibles aussi, mais sous un gestionnaire de contexte spécial.", + explain: "C'est exact, le tokenizer doit être mis en mode cible par ce gestionnaire de contexte.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. Pourquoi existe-t-il une sous-classe spécifique de Trainer pour les problèmes de séquence à séquence ? + +-100.", + explain: "Ce n'est pas du tout une perte personnalisée mais la façon dont la perte est toujours calculée." + }, + { + text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale.", + explain: "Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode generate().", + correct: true + }, + { + text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence.", + explain: "Trainer ne se soucie pas vraiment de cela puisqu'elles ont été prétraités auparavant." + }, + { + text: "Parce que nous utilisons deux modèles dans les problèmes de séquence à séquence.", + explain: "Nous utilisons en quelque sorte deux modèles, un encodeur et un décodeur, mais ils sont regroupés dans un seul modèle." + } + ]} +/> + +{:else} + +### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle compile() sur un transformer ? + +tranformers sont entraînés avec un apprentissage autosupervisé.", + explain: "Pas tout à fait. Même l'apprentissage autosupervisé a besoin d'une fonction de perte !" + }, + { + text: "Parce que la sortie de perte interne du modèle est utilisée par défaut.", + explain: " ", + correct: true + }, + { + text: "Parce que nous calculons les mesures après l'entraînement au lieu de le faire.", + explain: "Nous le faisons souvent mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement." + }, + { + text: "Parce que la perte est spécifiée dans model.fit().", + explain: "La fonction de perte est toujours fixée une fois que vous exécutez model.compile() et ne peut pas être modifiée dans model.fit()." + } + ]} +/> + +{/if} + +### 10. Quand devez-vous pré-entraîner un nouveau modèle ? + +finetuner sur vos données afin d'éviter d'énormes coûts de calcul." + }, + { + text: "Lorsque vous avez des doutes sur le biais du modèle pré-entraîné que vous utilisez.", + explain: "C'est vrai mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.", + correct: true + }, + { + text: "Lorsque les modèles pré-entraînés disponibles ne sont tout simplement pas assez bons.", + explain: "Vous êtes sûr d'avoir bien débogué votre entraînement ?" + } + ]} +/> + +### 11. Pourquoi est-il facile de prétraîner un modèle de langage sur des batchs de textes ? + +Transformers ne nécessite que quelques lignes de code pour démarrer l'entraînement.", + explain: "Bien que vrai, cela ne répond pas vraiment à la question posée. Essayez une autre réponse !" + } + ]} +/> + +### 12. Quels sont les principaux défis lors du prétraitement des données pour une tâche de réponse à des questions ? + + + +### 13. Comment le post-traitement est-il généralement effectué dans les réponses aux questions ? + +tokens correspondant.", + explain: "Ce pourrait être une façon de faire mais c'est un peu trop simpliste." + }, + { + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et il vous suffit de décoder la plage de tokens correspondant dans celui qui a le meilleur score.", + explain: "C'est proche du post-traitement que nous avons étudié, mais ce n'est pas tout à fait exact." + }, + { + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.", + explain: "C'est ça en résumé !", + correct: true + }, + { + text: "Le modèle génère une réponse et il vous suffit de la décoder.", + explain: "A moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence." + } + ]} +/> diff --git a/chapters/fr/chapter8/1.mdx b/chapters/fr/chapter8/1.mdx index 441ea1969..e3fe6fdd6 100644 --- a/chapters/fr/chapter8/1.mdx +++ b/chapters/fr/chapter8/1.mdx @@ -1,12 +1,17 @@ -# Introduction - -Maintenant que vous savez comment aborder les tâches de NLP les plus courantes avec 🤗 *Transformers*, vous devriez être en mesure de vous lancer dans vos propres projets ! Dans ce chapitre, nous allons explorer ce qu'il faut faire lorsque vous rencontrez un problème. Vous apprendrez comment déboguer avec succès votre code ou votre entraînement et comment demander de l'aide à la communauté si vous ne parvenez pas à résoudre le problème par vous-même. Et si vous pensez avoir trouvé un bug dans l'une des bibliothèques d'Hugging Face, nous vous montrerons la meilleure façon de le signaler afin que le problème soit résolu le plus rapidement possible. - -Plus précisément, dans ce chapitre vous allez apprendre : - -- la première chose à faire lorsque vous obtenez une erreur, -- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/), -- comment déboguer votre pipeline d'entraînement, -- comment rédiger une bonne *issue*. - -Rien de tout cela n'est spécifiquement lié à 🤗 *Transformers* ou à l'écosystème Hugging Face. Les leçons de ce chapitre sont applicables à la plupart des projets open source ! +# Introduction + + + +Maintenant que vous savez comment aborder les tâches de NLP les plus courantes avec 🤗 *Transformers*, vous devriez être en mesure de vous lancer dans vos propres projets ! Dans ce chapitre, nous allons explorer ce qu'il faut faire lorsque vous rencontrez un problème. Vous apprendrez comment déboguer avec succès votre code ou votre entraînement et comment demander de l'aide à la communauté si vous ne parvenez pas à résoudre le problème par vous-même. Et si vous pensez avoir trouvé un bug dans l'une des bibliothèques d'Hugging Face, nous vous montrerons la meilleure façon de le signaler afin que le problème soit résolu le plus rapidement possible. + +Plus précisément, dans ce chapitre vous allez apprendre : + +- la première chose à faire lorsque vous obtenez une erreur, +- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/), +- comment déboguer votre pipeline d'entraînement, +- comment rédiger une bonne *issue*. + +Rien de tout cela n'est spécifiquement lié à 🤗 *Transformers* ou à l'écosystème Hugging Face. Les leçons de ce chapitre sont applicables à la plupart des projets open source ! diff --git a/chapters/fr/chapter8/2.mdx b/chapters/fr/chapter8/2.mdx index 76abf7488..c09d17766 100644 --- a/chapters/fr/chapter8/2.mdx +++ b/chapters/fr/chapter8/2.mdx @@ -1,375 +1,375 @@ -# Que faire lorsque vous obtenez une erreur - - - -Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même. - - - -Nous avons préparé un gabarit de [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -ou ce qui suit dans votre terminal préféré : - -```bash -huggingface-cli login -``` - -Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le gabarit du dépôt avec la fonction suivante : - -```python -from distutils.dir_util import copy_tree -from huggingface_hub import Repository, snapshot_download, create_repo, get_full_repo_name - - -def copy_repository_template(): - # Cloner le dépôt et extraire le chemin local - template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" - commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" - template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) - # Créer un dépôt vide sur le Hub - model_name = template_repo_id.split("/")[1] - create_repo(model_name, exist_ok=True) - # Cloner le dépôt vide - new_repo_id = get_full_repo_name(model_name) - new_repo_dir = model_name - repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) - # Copier les fichiers - copy_tree(template_repo_dir, new_repo_dir) - # Pousser sur le Hub - repo.push_to_hub() -``` - -Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du gabarit du dépôt sous votre compte. - -## Déboguer le pipeline à partir de 🤗 Transformers - -Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage de *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce en ligne à trouver des réponses à des produits. Votre collègue vous envoie un message du genre : - -> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'identifiant du modèle sur le *Hub* est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésite pas à le tester :) - -et la première chose à laquelle on pense est de charger le modèle en utilisant le `pipeline` de 🤗 *Transformers* : - -```python -from transformers import pipeline - -model_checkpoint = get_full_repo_name("distillbert-base-uncased-finetuned-squad-d5716d28") -reader = pipeline("question-answering", model=model_checkpoint) -``` - -```python out -""" -OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28'. Make sure that: - -- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' - -- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file -""" -``` - -Oh non, quelque chose semble s'être mal passée ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'une `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus long appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante : - -
-A Python traceback. -
- -Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les *tracebacks* doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte français de haut en bas mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que le `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot. - - - -🚨 Vous voyez le cadre bleu autour de « 6 frames » dans le traceback de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab qui compresse le traceback en frames. Si vous ne parvenez pas à trouver la source d'une erreur, déroulez le traceback en cliquant sur ces deux petites flèches. - - - -Cela signifie que la dernière ligne du traceback indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle et deux suggestions nous sont données pour le résoudre : - -```python out -""" -Make sure that: - -- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' - -- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file -""" -``` - - - -💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème. - - - -La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du *Hub* : - -
-The wrong model name. -
- -Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le *Hub*... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul « l » dans son nom alors corrigeons cela et cherchons « lewtun/distilbert-base-uncased-finetuned-squad-d5716d28 » à la place : - -
-The right model name. -
- -Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec le bon identifiant : - -```python -model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28") -reader = pipeline("question-answering", model=model_checkpoint) -``` - -```python out -""" -OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28'. Make sure that: - -- 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' - -- or 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file -""" -``` - -Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'identifiant du modèle, le problème doit se situer dans le dépôt lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `huggingface_hub` : - -```python -from huggingface_hub import list_repo_files - -list_repo_files(repo_id=model_checkpoint) -``` - -```python out -['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt'] -``` - -Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le dépôt ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle. Notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir *finetuné*. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'identifiant du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` : - -```python -from transformers import AutoConfig - -pretrained_checkpoint = "distilbert-base-uncased" -config = AutoConfig.from_pretrained(pretrained_checkpoint) -``` - - - -🚨 L'approche que nous adoptons ici n'est pas infaillible puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant de finetuner le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section nous supposerons qu'il a utilisé la configuration par défaut. - - - -Nous pouvons ensuite le pousser vers notre dépôt de modèles avec la fonction `push_to_hub()` de la configuration : - -```python -config.push_to_hub(model_checkpoint, commit_message="Add config.json") -``` - -Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier *commit* de la branche `main` : - -```python -reader = pipeline("question-answering", model=model_checkpoint, revision="main") - -context = r""" -Extractive Question Answering is the task of extracting an answer from a text -given a question. An example of a question answering dataset is the SQuAD -dataset, which is entirely based on that task. If you would like to fine-tune a -model on a SQuAD task, you may leverage the -examples/pytorch/question-answering/run_squad.py script. - -🤗 Transformers is interoperable with the PyTorch, TensorFlow, and JAX -frameworks, so you can use your favourite tools for a wide variety of tasks! -""" - -context_fr = r""" -La réponse à des questions consiste à extraire une réponse d'un texte -à partir d'une question. Un exemple de jeu de données de réponse aux questions est le -jeu de données SQuAD qui est entièrement basé sur cette tâche. Si vous souhaitez finetuner -un modèle sur une tâche SQuAD, vous pouvez utiliser le fichier -exemples/pytorch/question-answering/run_squad.py. - -🤗 Transformers est interopérable avec les frameworks PyTorch, TensorFlow et JAX. -de sorte que vous pouvez utiliser vos outils préférés pour une grande variété de tâches ! -""" - -question = "What is extractive question answering?" -# Qu'est-ce que la réponse extractive aux questions ? -reader(question=question, context=context) -``` - -```python out -{'score': 0.38669535517692566, - 'start': 34, - 'end': 95, - 'answer': 'the task of extracting an answer from a text given a question'} - # la tâche consistant à extraire une réponse d'un texte à partir d'une question. -``` - -Woohoo, ça a marché ! Récapitulons ce que vous venez d'apprendre : - -- les messages d'erreur en Python sont appelés _tracebacks_ et sont lus de bas en haut. La dernière ligne du message d'erreur contient généralement les informations dont vous avez besoin pour localiser la source du problème, -- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans le *traceback* et voyez si vous pouvez identifier où l'erreur se produit dans le code source, -- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire, -- l'`huggingface_hub` fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*. - -Maintenant que vous savez comment déboguer un pipeline, examinons un exemple plus délicat dans la passe avant du modèle lui-même. - -## Déboguer la passe avant de votre modèle - -Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` : - -```python -tokenizer = reader.tokenizer -model = reader.model -``` - -Ensuite, nous avons besoin d'une question, alors voyons si nos *frameworks* préférés sont supportés : - -```python -question = "Which frameworks can I use?" # Quel frameworks puis-je utiliser ? -``` - -Comme nous l'avons vu dans le [chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'étendue de la réponse : - -```python -import torch - -inputs = tokenizer(question, context, add_special_tokens=True) -input_ids = inputs["input_ids"][0] -outputs = model(**inputs) -answer_start_scores = outputs.start_logits -answer_end_scores = outputs.end_logits -# Pour obtenir le début de réponse le plus probable avec l'argmax du score -answer_start = torch.argmax(answer_start_scores) -# Pour obtenir la fin de réponse la plus probable avec l'argmax du score -answer_end = torch.argmax(answer_end_scores) + 1 -answer = tokenizer.convert_tokens_to_string( - tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]) -) -print(f"Question: {question}") -print(f"Answer: {answer}") -``` - -```python out -""" ---------------------------------------------------------------------------- -AttributeError Traceback (most recent call last) -/var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_75743/2725838073.py in - 1 inputs = tokenizer(question, text, add_special_tokens=True) - 2 input_ids = inputs["input_ids"] -----> 3 outputs = model(**inputs) - 4 answer_start_scores = outputs.start_logits - 5 answer_end_scores = outputs.end_logits - -~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs) - 1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks - 1050 or _global_forward_hooks or _global_forward_pre_hooks): --> 1051 return forward_call(*input, **kwargs) - 1052 # Do not call functions when jit is used - 1053 full_backward_hooks, non_full_backward_hooks = [], [] - -~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, start_positions, end_positions, output_attentions, output_hidden_states, return_dict) - 723 return_dict = return_dict if return_dict is not None else self.config.use_return_dict - 724 ---> 725 distilbert_output = self.distilbert( - 726 input_ids=input_ids, - 727 attention_mask=attention_mask, - -~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs) - 1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks - 1050 or _global_forward_hooks or _global_forward_pre_hooks): --> 1051 return forward_call(*input, **kwargs) - 1052 # Do not call functions when jit is used - 1053 full_backward_hooks, non_full_backward_hooks = [], [] - -~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict) - 471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") - 472 elif input_ids is not None: ---> 473 input_shape = input_ids.size() - 474 elif inputs_embeds is not None: - 475 input_shape = inputs_embeds.size()[:-1] - -AttributeError: 'list' object has no attribute 'size' -""" -``` - -Il semble que nous ayons un *bug* dans notre code ! Mais il ne nous fait pas peur. Nous pouvons utiliser le débogueur Python dans un *notebook* : - - - -ou dans un terminal : - - - -Ici, la lecture du message d'erreur nous indique que l'objet `'list' n'a pas d'attribut 'size'`, et nous pouvons voir une flèche `-->` pointant vers la ligne où le problème a été soulevé dans `model(**inputs)`. Vous pouvez déboguer ceci de manière interactive en utilisant le débogueur Python, mais pour l'instant nous allons simplement imprimer une tranche de `inputs` pour voir ce que nous avons : - -```python -inputs["input_ids"][:5] -``` - -```python out -[101, 2029, 7705, 2015, 2064] -``` - -Cela ressemble certainement à une `list` ordinaire en Python mais vérifions le type : - -```python -type(inputs["input_ids"]) -``` - -```python out -list -``` - -Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous que dans le [chapitre 2](/course/fr/chapter2) nous avons vu que les classes `AutoModelForXxx` opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow) et qu'une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()`. Jetons un autre coup d'oeil au *traceback* pour voir quelle ligne a déclenché l'exception : - -``` -~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict) - 471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") - 472 elif input_ids is not None: ---> 473 input_shape = input_ids.size() - 474 elif inputs_embeds is not None: - 475 input_shape = inputs_embeds.size()[:-1] - -AttributeError: 'list' object has no attribute 'size' -``` - -Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `list` Python qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous : - -
-An answer from Stack Overflow. -
- -La réponse recommande d'ajouter `return_tensors='pt'` au *tokenizer*, voyons donc si cela fonctionne pour nous : - -```python out -inputs = tokenizer(question, context, add_special_tokens=True, return_tensors="pt") -input_ids = inputs["input_ids"][0] -outputs = model(**inputs) -answer_start_scores = outputs.start_logits -answer_end_scores = outputs.end_logits -# Pour obtenir le début de réponse le plus probable avec l'argmax du score -answer_start = torch.argmax(answer_start_scores) -# Pour obtenir la fin de réponse la plus probable avec l'argmax du score -answer_end = torch.argmax(answer_end_scores) + 1 -answer = tokenizer.convert_tokens_to_string( - tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]) -) -print(f"Question: {question}") -print(f"Answer: {answer}") -``` - -```python out -""" -Question: Which frameworks can I use? # Quels frameworks puis-je utiliser ? -Answer: pytorch, tensorflow, and jax # pytorch, tensorflow et jax -""" -``` - -Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente. Que faire alors dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions sur les forums pour avoir de bonnes chances d'obtenir une réponse. +# Que faire lorsque vous obtenez une erreur + + + +Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même. + + + +Nous avons préparé un gabarit de [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +ou ce qui suit dans votre terminal préféré : + +```bash +huggingface-cli login +``` + +Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le gabarit du dépôt avec la fonction suivante : + +```python +from distutils.dir_util import copy_tree +from huggingface_hub import Repository, snapshot_download, create_repo, get_full_repo_name + + +def copy_repository_template(): + # Cloner le dépôt et extraire le chemin local + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Créer un dépôt vide sur le Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Cloner le dépôt vide + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copier les fichiers + copy_tree(template_repo_dir, new_repo_dir) + # Pousser sur le Hub + repo.push_to_hub() +``` + +Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du gabarit du dépôt sous votre compte. + +## Déboguer le pipeline à partir de 🤗 Transformers + +Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage de *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce en ligne à trouver des réponses à des produits. Votre collègue vous envoie un message du genre : + +> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'identifiant du modèle sur le *Hub* est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésite pas à le tester :) + +et la première chose à laquelle on pense est de charger le modèle en utilisant le `pipeline` de 🤗 *Transformers* : + +```python +from transformers import pipeline + +model_checkpoint = get_full_repo_name("distillbert-base-uncased-finetuned-squad-d5716d28") +reader = pipeline("question-answering", model=model_checkpoint) +``` + +```python out +""" +OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28'. Make sure that: + +- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' + +- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file +""" +``` + +Oh non, quelque chose semble s'être mal passée ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'une `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus long appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante : + +
+A Python traceback. +
+ +Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les *tracebacks* doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte français de haut en bas mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que le `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot. + + + +🚨 Vous voyez le cadre bleu autour de « 6 frames » dans le traceback de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab qui compresse le traceback en frames. Si vous ne parvenez pas à trouver la source d'une erreur, déroulez le traceback en cliquant sur ces deux petites flèches. + + + +Cela signifie que la dernière ligne du traceback indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle et deux suggestions nous sont données pour le résoudre : + +```python out +""" +Make sure that: + +- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' + +- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file +""" +``` + + + +💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème. + + + +La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du *Hub* : + +
+The wrong model name. +
+ +Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le *Hub*... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul « l » dans son nom alors corrigeons cela et cherchons « lewtun/distilbert-base-uncased-finetuned-squad-d5716d28 » à la place : + +
+The right model name. +
+ +Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec le bon identifiant : + +```python +model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28") +reader = pipeline("question-answering", model=model_checkpoint) +``` + +```python out +""" +OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28'. Make sure that: + +- 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' + +- or 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file +""" +``` + +Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'identifiant du modèle, le problème doit se situer dans le dépôt lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `huggingface_hub` : + +```python +from huggingface_hub import list_repo_files + +list_repo_files(repo_id=model_checkpoint) +``` + +```python out +['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt'] +``` + +Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le dépôt ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle. Notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir *finetuné*. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'identifiant du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` : + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 L'approche que nous adoptons ici n'est pas infaillible puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant de finetuner le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section nous supposerons qu'il a utilisé la configuration par défaut. + + + +Nous pouvons ensuite le pousser vers notre dépôt de modèles avec la fonction `push_to_hub()` de la configuration : + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier *commit* de la branche `main` : + +```python +reader = pipeline("question-answering", model=model_checkpoint, revision="main") + +context = r""" +Extractive Question Answering is the task of extracting an answer from a text +given a question. An example of a question answering dataset is the SQuAD +dataset, which is entirely based on that task. If you would like to fine-tune a +model on a SQuAD task, you may leverage the +examples/pytorch/question-answering/run_squad.py script. + +🤗 Transformers is interoperable with the PyTorch, TensorFlow, and JAX +frameworks, so you can use your favourite tools for a wide variety of tasks! +""" + +context_fr = r""" +La réponse à des questions consiste à extraire une réponse d'un texte +à partir d'une question. Un exemple de jeu de données de réponse aux questions est le +jeu de données SQuAD qui est entièrement basé sur cette tâche. Si vous souhaitez finetuner +un modèle sur une tâche SQuAD, vous pouvez utiliser le fichier +exemples/pytorch/question-answering/run_squad.py. + +🤗 Transformers est interopérable avec les frameworks PyTorch, TensorFlow et JAX. +de sorte que vous pouvez utiliser vos outils préférés pour une grande variété de tâches ! +""" + +question = "What is extractive question answering?" +# Qu'est-ce que la réponse extractive aux questions ? +reader(question=question, context=context) +``` + +```python out +{'score': 0.38669535517692566, + 'start': 34, + 'end': 95, + 'answer': 'the task of extracting an answer from a text given a question'} + # la tâche consistant à extraire une réponse d'un texte à partir d'une question. +``` + +Woohoo, ça a marché ! Récapitulons ce que vous venez d'apprendre : + +- les messages d'erreur en Python sont appelés _tracebacks_ et sont lus de bas en haut. La dernière ligne du message d'erreur contient généralement les informations dont vous avez besoin pour localiser la source du problème, +- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans le *traceback* et voyez si vous pouvez identifier où l'erreur se produit dans le code source, +- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire, +- l'`huggingface_hub` fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*. + +Maintenant que vous savez comment déboguer un pipeline, examinons un exemple plus délicat dans la passe avant du modèle lui-même. + +## Déboguer la passe avant de votre modèle + +Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` : + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +Ensuite, nous avons besoin d'une question, alors voyons si nos *frameworks* préférés sont supportés : + +```python +question = "Which frameworks can I use?" # Quel frameworks puis-je utiliser ? +``` + +Comme nous l'avons vu dans le [chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'étendue de la réponse : + +```python +import torch + +inputs = tokenizer(question, context, add_special_tokens=True) +input_ids = inputs["input_ids"][0] +outputs = model(**inputs) +answer_start_scores = outputs.start_logits +answer_end_scores = outputs.end_logits +# Pour obtenir le début de réponse le plus probable avec l'argmax du score +answer_start = torch.argmax(answer_start_scores) +# Pour obtenir la fin de réponse la plus probable avec l'argmax du score +answer_end = torch.argmax(answer_end_scores) + 1 +answer = tokenizer.convert_tokens_to_string( + tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]) +) +print(f"Question: {question}") +print(f"Answer: {answer}") +``` + +```python out +""" +--------------------------------------------------------------------------- +AttributeError Traceback (most recent call last) +/var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_75743/2725838073.py in + 1 inputs = tokenizer(question, text, add_special_tokens=True) + 2 input_ids = inputs["input_ids"] +----> 3 outputs = model(**inputs) + 4 answer_start_scores = outputs.start_logits + 5 answer_end_scores = outputs.end_logits + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs) + 1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks + 1050 or _global_forward_hooks or _global_forward_pre_hooks): +-> 1051 return forward_call(*input, **kwargs) + 1052 # Do not call functions when jit is used + 1053 full_backward_hooks, non_full_backward_hooks = [], [] + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, start_positions, end_positions, output_attentions, output_hidden_states, return_dict) + 723 return_dict = return_dict if return_dict is not None else self.config.use_return_dict + 724 +--> 725 distilbert_output = self.distilbert( + 726 input_ids=input_ids, + 727 attention_mask=attention_mask, + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs) + 1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks + 1050 or _global_forward_hooks or _global_forward_pre_hooks): +-> 1051 return forward_call(*input, **kwargs) + 1052 # Do not call functions when jit is used + 1053 full_backward_hooks, non_full_backward_hooks = [], [] + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict) + 471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") + 472 elif input_ids is not None: +--> 473 input_shape = input_ids.size() + 474 elif inputs_embeds is not None: + 475 input_shape = inputs_embeds.size()[:-1] + +AttributeError: 'list' object has no attribute 'size' +""" +``` + +Il semble que nous ayons un *bug* dans notre code ! Mais il ne nous fait pas peur. Nous pouvons utiliser le débogueur Python dans un *notebook* : + + + +ou dans un terminal : + + + +Ici, la lecture du message d'erreur nous indique que l'objet `'list' n'a pas d'attribut 'size'`, et nous pouvons voir une flèche `-->` pointant vers la ligne où le problème a été soulevé dans `model(**inputs)`. Vous pouvez déboguer ceci de manière interactive en utilisant le débogueur Python, mais pour l'instant nous allons simplement imprimer une tranche de `inputs` pour voir ce que nous avons : + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Cela ressemble certainement à une `list` ordinaire en Python mais vérifions le type : + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous que dans le [chapitre 2](/course/fr/chapter2) nous avons vu que les classes `AutoModelForXxx` opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow) et qu'une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()`. Jetons un autre coup d'oeil au *traceback* pour voir quelle ligne a déclenché l'exception : + +``` +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict) + 471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") + 472 elif input_ids is not None: +--> 473 input_shape = input_ids.size() + 474 elif inputs_embeds is not None: + 475 input_shape = inputs_embeds.size()[:-1] + +AttributeError: 'list' object has no attribute 'size' +``` + +Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `list` Python qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous : + +
+An answer from Stack Overflow. +
+ +La réponse recommande d'ajouter `return_tensors='pt'` au *tokenizer*, voyons donc si cela fonctionne pour nous : + +```python out +inputs = tokenizer(question, context, add_special_tokens=True, return_tensors="pt") +input_ids = inputs["input_ids"][0] +outputs = model(**inputs) +answer_start_scores = outputs.start_logits +answer_end_scores = outputs.end_logits +# Pour obtenir le début de réponse le plus probable avec l'argmax du score +answer_start = torch.argmax(answer_start_scores) +# Pour obtenir la fin de réponse la plus probable avec l'argmax du score +answer_end = torch.argmax(answer_end_scores) + 1 +answer = tokenizer.convert_tokens_to_string( + tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]) +) +print(f"Question: {question}") +print(f"Answer: {answer}") +``` + +```python out +""" +Question: Which frameworks can I use? # Quels frameworks puis-je utiliser ? +Answer: pytorch, tensorflow, and jax # pytorch, tensorflow et jax +""" +``` + +Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente. Que faire alors dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions sur les forums pour avoir de bonnes chances d'obtenir une réponse. diff --git a/chapters/fr/chapter8/3.mdx b/chapters/fr/chapter8/3.mdx index a75b49f95..b9c3c2c49 100644 --- a/chapters/fr/chapter8/3.mdx +++ b/chapters/fr/chapter8/3.mdx @@ -1,207 +1,207 @@ -# Demander de l'aide sur les forums - - - - - -Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source d'Hugging Face et de la communauté au sens large. Voici à quoi ressemble la page principale : - -
-The Hugging Face forums. -
- -Dans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description. C'est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans le [chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème d'Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum). - -De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées. Par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter. - -Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20) où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face ! - -Une fois une catégorie choisie, vous êtes prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [indications](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire. Dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet. - -## Rédiger un bon message sur le forum - -A titre d'exemple, supposons que nous essayons de générer des enchâssements à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit : - -```python -from transformers import AutoTokenizer, AutoModel - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -model = AutoModel.from_pretrained(model_checkpoint) -``` - -Supposons maintenant que nous essayons d'enchâsser une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !): - -```python -text = """ -Generation One is a retroactive term for the Transformers characters that -appeared between 1984 and 1993. The Transformers began with the 1980s Japanese -toy lines Micro Change and Diaclone. They presented robots able to transform -into everyday vehicles, electronic items or weapons. Hasbro bought the Micro -Change and Diaclone toys, and partnered with Takara. Marvel Comics was hired by -Hasbro to create the backstory; editor-in-chief Jim Shooter wrote an overall -story, and gave the task of creating the characthers to writer Dennis O'Neil. -Unhappy with O'Neil's work (although O'Neil created the name "Optimus Prime"), -Shooter chose Bob Budiansky to create the characters. - -The Transformers mecha were largely designed by Shōji Kawamori, the creator of -the Japanese mecha anime franchise Macross (which was adapted into the Robotech -franchise in North America). Kawamori came up with the idea of transforming -mechs while working on the Diaclone and Macross franchises in the early 1980s -(such as the VF-1 Valkyrie in Macross and Robotech), with his Diaclone mechs -later providing the basis for Transformers. - -The primary concept of Generation One is that the heroic Optimus Prime, the -villainous Megatron, and their finest soldiers crash land on pre-historic Earth -in the Ark and the Nemesis before awakening in 1985, Cybertron hurtling through -the Neutral zone as an effect of the war. The Marvel comic was originally part -of the main Marvel Universe, with appearances from Spider-Man and Nick Fury, -plus some cameos, as well as a visit to the Savage Land. - -The Transformers TV series began around the same time. Produced by Sunbow -Productions and Marvel Productions, later Hasbro Productions, from the start it -contradicted Budiansky's backstories. The TV series shows the Autobots looking -for new energy sources, and crash landing as the Decepticons attack. Marvel -interpreted the Autobots as destroying a rogue asteroid approaching Cybertron. -Shockwave is loyal to Megatron in the TV series, keeping Cybertron in a -stalemate during his absence, but in the comic book he attempts to take command -of the Decepticons. The TV series would also differ wildly from the origins -Budiansky had created for the Dinobots, the Decepticon turned Autobot Jetfire -(known as Skyfire on TV), the Constructicons (who combine to form -Devastator),[19][20] and Omega Supreme. The Marvel comic establishes early on -that Prime wields the Creation Matrix, which gives life to machines. In the -second season, the two-part episode The Key to Vector Sigma introduced the -ancient Vector Sigma computer, which served the same original purpose as the -Creation Matrix (giving life to Transformers), and its guardian Alpha Trion. -""" - -text_fr = """ -Génération 1 est un terme rétroactif pour les personnages de Transformers qui sont apparus -entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises -des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables -de se transformer en véhicules de tous les jours, en objets électroniques ou en armes. -Hasbro a acheté les jouets Micro Change et Diaclone, et s'est associé à Takara. -Marvel Comics est engagé par Hasbro pour créer l'histoire de fond ; le rédacteur en chef -Jim Shooter a écrit une histoire générale et confie la tâche de créer les personnages au -scénariste Dennis O'Neil. Mécontent du travail d'O'Neil (bien que ce dernier ait créé -le nom "Optimus Prime"), Shooter choisit Bob Budiansky pour créer les personnages. - -Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur -de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). Kawamori -a eu l'idée de transformer des mechas transformables alors qu'il travaillait sur les -franchises Diaclone et Macross au début des années 1980 (comme le VF-1 Valkyrie dans -Macross et Robotech), et ses méchas Diaclone ont plus tard servi de base à Transformers. - -Le concept principal de la Génération 1 est que l'héroïque Optimus Prime, le méchant -Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique dans l'Arche -et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone -neutre en raison de la guerre. La bande dessinée Marvel faisait à l'origine partie -de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, -plus quelques caméos, ainsi qu'une visite à la Terre Sauvage. - -La série télévisée Transformers a commencé à peu près à la même époque. -Produite par Sunbow Productions et Marvel Productions, puis Hasbro Productions, -dès le début elle a contredit les histoires de Budiansky. La série TV montre les Autobots -cherchant de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. -Marvel a interprété les Autobots comme la destruction d'un astéroïde malveillant -s'approchant de Cybertron. Shockwave est loyal envers Megatron dans la série TV, -et maintient Cybertron dans une impasse en son absence. -Cybertron dans une impasse pendant son absence, mais dans la BD, -il tente de prendre le commandement des Decepticons. -La série télévisée s'écarte aussi radicalement des origines que Budiansky avait -créé pour les Dinobots, le Decepticon devenu Autobot Jetfire -(connu sous le nom de Skyfire à la télévision), -les Constructicons (qui s'associent pour former Devastator) et Oméga Suprême. -La bande dessinée Marvel établit très tôt que Prime manie la matrice de création, -qui donne la vie aux machines. Dans la saison, l'épisode en deux parties -The Key to Vector Sigma a introduit l'ancien ordinateur l'ancien ordinateur -Vector Sigma, qui servait le même objectif original que la matrice de création -(donner la vie aux Transformers), et son gardien Alpha Trion. -""" - -inputs = tokenizer(text, return_tensors="pt") -logits = model(**inputs).logits -``` - -```python output -IndexError: index out of range in self -``` - -Oh nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre le *traceback* complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ? - -Pour commencer, nous devons cliquer sur le bouton *New Topic* dans le coin supérieur droit (notez que pour créer un sujet, nous devons être connectés) : - -
-Creating a new forum topic. -
- -Cela fait apparaître une interface de rédaction où nous pouvons saisir le titre de notre sujet, sélectionner une catégorie et rédiger le contenu : - -
-The interface for creating a forum topic. -
- -Puisque l'erreur semble concerner exclusivement 🤗 *Transformers*, nous allons la sélectionner pour la catégorie. Notre première tentative d'explication du problème pourrait ressembler à quelque chose comme ça : - -
-Drafting the content for a new forum topic. -
- -Bien que ce sujet contienne le message d'erreur pour lequel nous avons besoin d'aide, il y a quelques problèmes avec la façon dont il est écrit : - -1. le titre n'est pas très descriptif, ainsi toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet, -2. le corps du texte ne fournit pas suffisamment d'informations sur *l'origine* de l'erreur et sur *la manière* de la reproduire, -3. le sujet s'adresse directement à quelques personnes sur un ton quelque peu exigeant. - -Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une) alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre. - -### Choisir un titre descriptif - -Si vous essayez d'obtenir de l'aide pour résoudre un *bug* dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception et savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être : - -> Source de l'IndexError dans la passe avant d'AutoModel ? - -Ce titre indique au lecteur _où_ vous pensez que le *bug* provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez et d'autres variations comme : - -> Pourquoi mon modèle produit-il un IndexError ? - -pourrait également convenir. Maintenant que nous avons un titre descriptif, voyons comment améliorer le corps du texte. - -### Formatage de vos extraits de code - -La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, le forum d'Hugging Face supporte l'utilisation de Markdown donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale : - -
-Our revised forum topic, with proper code formatting. -
- -Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de *backticks* convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des *backticks* simples peuvent être utilisés pour formater des variables en ligne comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant le *traceback* dans ses moindres détails ! - -### Inclure le traceback complet - -Puisque la dernière ligne de le *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans le *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller le *traceback* _entier_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit : - -
-Our example forum topic, with the complete traceback. -
- -Ceci est beaucoup plus informatif et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans le *traceback* : - -> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). - -Cependant, nous pouvons leur faciliter les choses en leur fournissant le code qui a déclenché l'erreur. C'est ce que nous allons faire maintenant. - -### Fournir un exemple reproductible - -Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur le *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant : - -
-The final version of our forum topic. -
- -Ce sujet contient maintenant un bon lot d'informations et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* ! +# Demander de l'aide sur les forums + + + + + +Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source d'Hugging Face et de la communauté au sens large. Voici à quoi ressemble la page principale : + +
+The Hugging Face forums. +
+ +Dans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description. C'est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans le [chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème d'Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum). + +De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées. Par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter. + +Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20) où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face ! + +Une fois une catégorie choisie, vous êtes prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [indications](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire. Dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet. + +## Rédiger un bon message sur le forum + +A titre d'exemple, supposons que nous essayons de générer des enchâssements à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit : + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +Supposons maintenant que nous essayons d'enchâsser une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !): + +```python +text = """ +Generation One is a retroactive term for the Transformers characters that +appeared between 1984 and 1993. The Transformers began with the 1980s Japanese +toy lines Micro Change and Diaclone. They presented robots able to transform +into everyday vehicles, electronic items or weapons. Hasbro bought the Micro +Change and Diaclone toys, and partnered with Takara. Marvel Comics was hired by +Hasbro to create the backstory; editor-in-chief Jim Shooter wrote an overall +story, and gave the task of creating the characthers to writer Dennis O'Neil. +Unhappy with O'Neil's work (although O'Neil created the name "Optimus Prime"), +Shooter chose Bob Budiansky to create the characters. + +The Transformers mecha were largely designed by Shōji Kawamori, the creator of +the Japanese mecha anime franchise Macross (which was adapted into the Robotech +franchise in North America). Kawamori came up with the idea of transforming +mechs while working on the Diaclone and Macross franchises in the early 1980s +(such as the VF-1 Valkyrie in Macross and Robotech), with his Diaclone mechs +later providing the basis for Transformers. + +The primary concept of Generation One is that the heroic Optimus Prime, the +villainous Megatron, and their finest soldiers crash land on pre-historic Earth +in the Ark and the Nemesis before awakening in 1985, Cybertron hurtling through +the Neutral zone as an effect of the war. The Marvel comic was originally part +of the main Marvel Universe, with appearances from Spider-Man and Nick Fury, +plus some cameos, as well as a visit to the Savage Land. + +The Transformers TV series began around the same time. Produced by Sunbow +Productions and Marvel Productions, later Hasbro Productions, from the start it +contradicted Budiansky's backstories. The TV series shows the Autobots looking +for new energy sources, and crash landing as the Decepticons attack. Marvel +interpreted the Autobots as destroying a rogue asteroid approaching Cybertron. +Shockwave is loyal to Megatron in the TV series, keeping Cybertron in a +stalemate during his absence, but in the comic book he attempts to take command +of the Decepticons. The TV series would also differ wildly from the origins +Budiansky had created for the Dinobots, the Decepticon turned Autobot Jetfire +(known as Skyfire on TV), the Constructicons (who combine to form +Devastator),[19][20] and Omega Supreme. The Marvel comic establishes early on +that Prime wields the Creation Matrix, which gives life to machines. In the +second season, the two-part episode The Key to Vector Sigma introduced the +ancient Vector Sigma computer, which served the same original purpose as the +Creation Matrix (giving life to Transformers), and its guardian Alpha Trion. +""" + +text_fr = """ +Génération 1 est un terme rétroactif pour les personnages de Transformers qui sont apparus +entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises +des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables +de se transformer en véhicules de tous les jours, en objets électroniques ou en armes. +Hasbro a acheté les jouets Micro Change et Diaclone, et s'est associé à Takara. +Marvel Comics est engagé par Hasbro pour créer l'histoire de fond ; le rédacteur en chef +Jim Shooter a écrit une histoire générale et confie la tâche de créer les personnages au +scénariste Dennis O'Neil. Mécontent du travail d'O'Neil (bien que ce dernier ait créé +le nom "Optimus Prime"), Shooter choisit Bob Budiansky pour créer les personnages. + +Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur +de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). Kawamori +a eu l'idée de transformer des mechas transformables alors qu'il travaillait sur les +franchises Diaclone et Macross au début des années 1980 (comme le VF-1 Valkyrie dans +Macross et Robotech), et ses méchas Diaclone ont plus tard servi de base à Transformers. + +Le concept principal de la Génération 1 est que l'héroïque Optimus Prime, le méchant +Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique dans l'Arche +et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone +neutre en raison de la guerre. La bande dessinée Marvel faisait à l'origine partie +de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, +plus quelques caméos, ainsi qu'une visite à la Terre Sauvage. + +La série télévisée Transformers a commencé à peu près à la même époque. +Produite par Sunbow Productions et Marvel Productions, puis Hasbro Productions, +dès le début elle a contredit les histoires de Budiansky. La série TV montre les Autobots +cherchant de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. +Marvel a interprété les Autobots comme la destruction d'un astéroïde malveillant +s'approchant de Cybertron. Shockwave est loyal envers Megatron dans la série TV, +et maintient Cybertron dans une impasse en son absence. +Cybertron dans une impasse pendant son absence, mais dans la BD, +il tente de prendre le commandement des Decepticons. +La série télévisée s'écarte aussi radicalement des origines que Budiansky avait +créé pour les Dinobots, le Decepticon devenu Autobot Jetfire +(connu sous le nom de Skyfire à la télévision), +les Constructicons (qui s'associent pour former Devastator) et Oméga Suprême. +La bande dessinée Marvel établit très tôt que Prime manie la matrice de création, +qui donne la vie aux machines. Dans la saison, l'épisode en deux parties +The Key to Vector Sigma a introduit l'ancien ordinateur l'ancien ordinateur +Vector Sigma, qui servait le même objectif original que la matrice de création +(donner la vie aux Transformers), et son gardien Alpha Trion. +""" + +inputs = tokenizer(text, return_tensors="pt") +logits = model(**inputs).logits +``` + +```python output +IndexError: index out of range in self +``` + +Oh nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre le *traceback* complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ? + +Pour commencer, nous devons cliquer sur le bouton *New Topic* dans le coin supérieur droit (notez que pour créer un sujet, nous devons être connectés) : + +
+Creating a new forum topic. +
+ +Cela fait apparaître une interface de rédaction où nous pouvons saisir le titre de notre sujet, sélectionner une catégorie et rédiger le contenu : + +
+The interface for creating a forum topic. +
+ +Puisque l'erreur semble concerner exclusivement 🤗 *Transformers*, nous allons la sélectionner pour la catégorie. Notre première tentative d'explication du problème pourrait ressembler à quelque chose comme ça : + +
+Drafting the content for a new forum topic. +
+ +Bien que ce sujet contienne le message d'erreur pour lequel nous avons besoin d'aide, il y a quelques problèmes avec la façon dont il est écrit : + +1. le titre n'est pas très descriptif, ainsi toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet, +2. le corps du texte ne fournit pas suffisamment d'informations sur *l'origine* de l'erreur et sur *la manière* de la reproduire, +3. le sujet s'adresse directement à quelques personnes sur un ton quelque peu exigeant. + +Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une) alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre. + +### Choisir un titre descriptif + +Si vous essayez d'obtenir de l'aide pour résoudre un *bug* dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception et savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être : + +> Source de l'IndexError dans la passe avant d'AutoModel ? + +Ce titre indique au lecteur _où_ vous pensez que le *bug* provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez et d'autres variations comme : + +> Pourquoi mon modèle produit-il un IndexError ? + +pourrait également convenir. Maintenant que nous avons un titre descriptif, voyons comment améliorer le corps du texte. + +### Formatage de vos extraits de code + +La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, le forum d'Hugging Face supporte l'utilisation de Markdown donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale : + +
+Our revised forum topic, with proper code formatting. +
+ +Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de *backticks* convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des *backticks* simples peuvent être utilisés pour formater des variables en ligne comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant le *traceback* dans ses moindres détails ! + +### Inclure le traceback complet + +Puisque la dernière ligne de le *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans le *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller le *traceback* _entier_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit : + +
+Our example forum topic, with the complete traceback. +
+ +Ceci est beaucoup plus informatif et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans le *traceback* : + +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). + +Cependant, nous pouvons leur faciliter les choses en leur fournissant le code qui a déclenché l'erreur. C'est ce que nous allons faire maintenant. + +### Fournir un exemple reproductible + +Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur le *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant : + +
+The final version of our forum topic. +
+ +Ce sujet contient maintenant un bon lot d'informations et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* ! diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx index 7ac1272c0..e726543f1 100644 --- a/chapters/fr/chapter8/4.mdx +++ b/chapters/fr/chapter8/4.mdx @@ -1,793 +1,793 @@ - - -# Débogage du pipeline d'entraînement - - - -Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. - -## Déboguer le pipeline d'entraînement - - - -Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. - -La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. - -Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : - -```py -from datasets import load_dataset -import evaluate -from transformers import ( - AutoTokenizer, - AutoModelForSequenceClassification, - TrainingArguments, - Trainer, -) - -raw_datasets = load_dataset("glue", "mnli") - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) - - -def preprocess_function(examples): - return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) - - -tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) -model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) - -args = TrainingArguments( - f"distilbert-finetuned-mnli", - evaluation_strategy="epoch", - save_strategy="epoch", - learning_rate=2e-5, - num_train_epochs=3, - weight_decay=0.01, -) - -metric = evaluate.load("glue", "mnli") - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - return metric.compute(predictions=predictions, references=labels) - - -trainer = Trainer( - model, - args, - train_dataset=raw_datasets["train"], - eval_dataset=raw_datasets["validation_matched"], - compute_metrics=compute_metrics, -) -trainer.train() -``` - -Si vous essayez de l'exécuter, vous serez confronté à une erreur plutôt cryptique : - -```python out -'ValueError: You have to specify either input_ids or inputs_embeds' -``` - -### Vérifiez vos données - -Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre jeu d'entraînement. - -Pour éviter d'innombrables heures passées à essayer de corriger quelque chose qui n'est pas la source du bug, nous vous recommandons d'utiliser `trainer.train_dataset` pour vos vérifications et rien d'autre. Faisons donc cela ici : - -```py -trainer.train_dataset[0] -``` - -```python out -{'hypothesis': 'Product and geography are what make cream skimming work. ', - 'idx': 0, - 'label': 1, - 'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'} -``` - -Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments. - -Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les jeux de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! - -```py -from datasets import load_dataset -import evaluate -from transformers import ( - AutoTokenizer, - AutoModelForSequenceClassification, - TrainingArguments, - Trainer, -) - -raw_datasets = load_dataset("glue", "mnli") - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) - - -def preprocess_function(examples): - return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) - - -tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) -model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) - -args = TrainingArguments( - f"distilbert-finetuned-mnli", - evaluation_strategy="epoch", - save_strategy="epoch", - learning_rate=2e-5, - num_train_epochs=3, - weight_decay=0.01, -) - -metric = evaluate.load("glue", "mnli") - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - return metric.compute(predictions=predictions, references=labels) - - -trainer = Trainer( - model, - args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation_matched"], - compute_metrics=compute_metrics, -) -trainer.train() -``` - -Ce nouveau code donnera maintenant une erreur différente (c'est un progrès !) : - -```python out -'ValueError: expected sequence of length 43 at dim 1 (got 37)' -``` - -En regardant le *traceback*, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données : - -```python out -~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) - 105 batch[k] = torch.stack([f[k] for f in features]) - 106 else: ---> 107 batch[k] = torch.tensor([f[k] for f in features]) - 108 - 109 return batch -``` - -Donc, nous devrions passer à cela. Mais avant finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes. - -Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole cela signifie écouter les échantillons audio décodés, et pour notre exemple de NLP cela signifie utiliser notre *tokenizer* pour décoder les entrées : - -```py -tokenizer.decode(trainer.train_dataset[0]["input_ids"]) -``` - -```python out -'[CLS] conceptually cream skimming has two basic dimensions - product and geography. [SEP] product and geography are what make cream skimming work. [SEP]' -``` - -Cela semble correct. Vous devriez faire cela pour toutes les clés dans les entrées : - -```py -trainer.train_dataset[0].keys() -``` - -```python out -dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) -``` - -Notez que les clés qui ne correspondent pas à des entrées acceptées par le modèle seront automatiquement écartées, donc ici nous ne garderons que `input_ids`, `attention_mask`, et `label` (qui sera renommé `labels`). Pour revérifier la signature du modèle, vous pouvez imprimer la classe de votre modèle, puis aller consulter sa documentation : - -```py -type(trainer.model) -``` - -```python out -transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification -``` - -Donc dans notre cas, nous pouvons vérifier les paramètres acceptés sur [cette page](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Le `Trainer` va également enregistrer les colonnes qu'il rejette. - -Nous avons vérifié que les identifiants d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` : - -```py -tokenizer.decode(trainer.train_dataset[0]["attention_mask"]) -``` - -```python out -[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] -``` - -Comme nous n'avons pas appliqué de *padding* dans notre prétraitement, cela semble parfaitement naturel. Pour être sûr qu'il n'y a pas de problème avec ce masque d'attention, vérifions qu'il est de la même longueur que nos identifiants d'entrée : - -```py -len(trainer.train_dataset[0]["attention_mask"]) == len( - trainer.train_dataset[0]["input_ids"] -) -``` - -```python out -True -``` - -C'est bien ! Enfin, vérifions notre étiquette : - -```py -trainer.train_dataset[0]["label"] -``` - -```python out -1 -``` - -Comme les identifiants d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante du jeu de données : - -```py -trainer.train_dataset.features["label"].names -``` - -```python out -['entailment', 'neutral', 'contradiction'] -``` - -Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction : la première n'implique pas la seconde. Cela semble correct ! - -Nous n'avons pas de *token* de type identifiant ici puisque DistilBERT ne les attend pas. Si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée. - - - -✏️ *A votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement. - - - -Ici nous ne vérifions que le jeu d'entraînement. Vous devez bien sûr vérifier de la même façon les jeux de validation et de test. - -Maintenant que nous savons que nos jeux de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement. - -### Des jeux de données aux chargeurs de données - -La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir du jeu d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le *dataloader* de validation) : - -```py -for batch in trainer.get_train_dataloader(): - break -``` - -Ce code crée le *dataloader* d'entraînement puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le *dataloader*, comme c'est le cas ici : - -```python out -~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) - 105 batch[k] = torch.stack([f[k] for f in features]) - 106 else: ---> 107 batch[k] = torch.tensor([f[k] for f in features]) - 108 - 109 return batch - -ValueError: expected sequence of length 45 at dim 1 (got 76) -``` - -L'inspection de la dernière image du *traceback* devrait suffire à vous donner un indice mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch. La première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` : - -```py -data_collator = trainer.get_train_dataloader().collate_fn -data_collator -``` - -```python out - Dict[str, Any]> -``` - -C'est donc `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par `DataCollatorWithPadding`. Et cette assembleur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ? - -La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement l'assembleur de données que l'on veut utiliser pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : - -```py -from datasets import load_dataset -import evaluate -from transformers import ( - AutoTokenizer, - AutoModelForSequenceClassification, - DataCollatorWithPadding, - TrainingArguments, - Trainer, -) - -raw_datasets = load_dataset("glue", "mnli") - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) - - -def preprocess_function(examples): - return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) - - -tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) -model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) - -args = TrainingArguments( - f"distilbert-finetuned-mnli", - evaluation_strategy="epoch", - save_strategy="epoch", - learning_rate=2e-5, - num_train_epochs=3, - weight_decay=0.01, -) - -metric = evaluate.load("glue", "mnli") - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - return metric.compute(predictions=predictions, references=labels) - - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) - -trainer = Trainer( - model, - args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation_matched"], - compute_metrics=compute_metrics, - data_collator=data_collator, - tokenizer=tokenizer, -) -trainer.train() -``` - -La bonne nouvelle ? Nous n'avons plus la même erreur qu'avant, ce qui est un progrès certain. La mauvaise nouvelle ? Nous obtenons une erreur CUDA infâme à la place : - -```python out -RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` -``` - -C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème mais terminons d'abord notre analyse de la création de batchs. - -Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre jeu de données : - -```py -data_collator = trainer.get_train_dataloader().collate_fn -batch = data_collator([trainer.train_dataset[i] for i in range(4)]) -``` - -Ce code échouera parce que le `train_dataset` contient des colonnes de type *string* que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode `Trainer._remove_unused_columns()` qui fait cela : - -```py -data_collator = trainer.get_train_dataloader().collate_fn -actual_train_set = trainer._remove_unused_columns(trainer.train_dataset) -batch = data_collator([actual_train_set[i] for i in range(4)]) -``` - -Vous devriez alors être en mesure de déboguer manuellement ce qui se passe dans le collecteur de données si l'erreur persiste. - -Maintenant que nous avons débogué le processus de création de batch, il est temps d'en passer un dans le modèle ! - - -### Passage par le modèle - -Vous devriez être en mesure d'obtenir un batch en exécutant la commande suivante : - -```py -for batch in trainer.get_train_dataloader(): - break -``` - -Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre *notebook* et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La première plus ennuyeuse est le fait qu'elles sont difficiles à déboguer. - -Comment cela se fait-il ? Cela tient à la façon dont les GPUs fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait mentionnée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre *traceback* précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant. - -Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur *out-of-memory* (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer. - -Pour faire cela dans notre cas, nous devons juste remettre le modèle sur le CPU et l'appeler sur notre batch. Le batch retourné par le `DataLoader` n'a pas encore été déplacé sur le GPU : - -```python -outputs = trainer.model.cpu()(**batch) -``` - -```python out -~/.pyenv/versions/3.7.9/envs/base/lib/python3.7/site-packages/torch/nn/functional.py in nll_loss(input, target, weight, size_average, ignore_index, reduce, reduction) - 2386 ) - 2387 if dim == 2: --> 2388 ret = torch._C._nn.nll_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index) - 2389 elif dim == 4: - 2390 ret = torch._C._nn.nll_loss2d(input, target, weight, _Reduction.get_enum(reduction), ignore_index) - -IndexError: Target 2 is out of bounds. -``` - -L'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec la passe arrière comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un bon moment pour vérifier le nombre de labels de notre modèle : - -```python -trainer.model.config.num_labels -``` - -```python out -2 -``` - -Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms des étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons les indices 0, 1 et 2 dans notre jeu de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! - -```py -from datasets import load_dataset -import evaluate -from transformers import ( - AutoTokenizer, - AutoModelForSequenceClassification, - DataCollatorWithPadding, - TrainingArguments, - Trainer, -) - -raw_datasets = load_dataset("glue", "mnli") - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) - - -def preprocess_function(examples): - return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) - - -tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) -model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) - -args = TrainingArguments( - f"distilbert-finetuned-mnli", - evaluation_strategy="epoch", - save_strategy="epoch", - learning_rate=2e-5, - num_train_epochs=3, - weight_decay=0.01, -) - -metric = evaluate.load("glue", "mnli") - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - return metric.compute(predictions=predictions, references=labels) - - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) - -trainer = Trainer( - model, - args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation_matched"], - compute_metrics=compute_metrics, - data_collator=data_collator, - tokenizer=tokenizer, -) -``` - -Nous n'incluons pas encore la ligne `trainer.train()` pour prendre le temps de vérifier que tout se passe bien. Si nous passons un batch à notre modèle, il fonctionne maintenant sans erreur ! - -```py -for batch in trainer.get_train_dataloader(): - break - -outputs = trainer.model.cpu()(**batch) -``` - -L'étape suivante consiste alors à revenir au GPU et à vérifier que tout fonctionne encore : - -```py -import torch - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -batch = {k: v.to(device) for k, v in batch.items()} - -outputs = trainer.model.to(device)(**batch) -``` - -Si vous obtenez toujours une erreur, assurez-vous de redémarrer votre *notebook* et d'exécuter uniquement la dernière version du script. - -### Exécution d'une étape d'optimisation - -Maintenant que nous savons que nous pouvons construire des batchs qui passent réellement par le modèle, nous sommes prêts pour l'étape suivante du pipeline d'entraînement : calculer les gradients et effectuer une étape d'optimisation. - -La première partie est juste une question d'appel de la méthode `backward()` sur la perte : - -```py -loss = outputs.loss -loss.backward() -``` - -Il est plutôt rare d'obtenir une erreur à ce stade, mais si vous en obtenez une, assurez-vous de retourner au CPU pour obtenir un message d'erreur utile. - -Pour effectuer l'étape d'optimisation, il suffit de créer le `optimizer` et d'appeler sa méthode `step()` : - -```py -trainer.create_optimizer() -trainer.optimizer.step() -``` - -Encore une fois, si vous utilisez l'optimiseur par défaut dans le `Trainer`, vous ne devriez pas avoir d'erreur à ce stade, mais si vous avez un optimiseur personnalisé, il pourrait y avoir quelques problèmes à déboguer ici. N'oubliez pas de revenir au CPU si vous obtenez une erreur CUDA bizarre à ce stade. En parlant d'erreurs CUDA, nous avons mentionné précédemment un cas particulier. Voyons cela maintenant. - -### Gérer les erreurs CUDA out of memory - -Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code et peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement. - -Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle. - - - -Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. - - - -### Évaluation du modèle - -Maintenant que nous avons résolu tous les problèmes liés à notre code, tout est parfait et l'entraînement devrait se dérouler sans problème, n'est-ce pas ? Pas si vite ! Si vous exécutez la commande `trainer.train()`, tout aura l'air bien au début, mais après un moment vous obtiendrez ce qui suit : - -```py -# Cela prendra beaucoup de temps et se soldera par une erreur, vous ne devriez donc pas utiliser cette cellule. -trainer.train() -``` - -```python out -TypeError: only size-1 arrays can be converted to Python scalars -``` - -Vous réaliserez que cette erreur apparaît pendant la phase d'évaluation, donc c'est la dernière chose que nous aurons besoin de déboguer. - -Vous pouvez exécuter la boucle d'évaluation du `Trainer` indépendamment de l'entraînement comme ceci : - -```py -trainer.evaluate() -``` - -```python out -TypeError: only size-1 arrays can be converted to Python scalars -``` - - - -💡 Vous devriez toujours vous assurer que vous pouvez exécuter `trainer.evaluate()` avant de lancer `trainer.train()`, pour éviter de gaspiller beaucoup de ressources de calcul avant de tomber sur une erreur. - - - -Avant de tenter de déboguer un problème dans la boucle d'évaluation, vous devez d'abord vous assurer que vous avez examiné les données, que vous êtes en mesure de former un batch correctement et que vous pouvez exécuter votre modèle sur ces données. Nous avons effectué toutes ces étapes, et le code suivant peut donc être exécuté sans erreur : - -```py -for batch in trainer.get_eval_dataloader(): - break - -batch = {k: v.to(device) for k, v in batch.items()} - -with torch.no_grad(): - outputs = trainer.model(**batch) -``` - -L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons le *traceback*, nous voyons ceci : - -```python trace -~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references) - 431 """ - 432 batch = {"predictions": predictions, "references": references} ---> 433 batch = self.info.features.encode_batch(batch) - 434 if self.writer is None: - 435 self._init_writer() -``` - -Cela nous indique que l'erreur provient du module `datasets/metric.py` donc c'est un problème avec notre fonction `compute_metrics()`. Elle prend un *tuple* avec les logits et les labels sous forme de tableaux NumPy, alors essayons de lui fournir cela : - -```py -predictions = outputs.logits.cpu().numpy() -labels = batch["labels"].cpu().numpy() - -compute_metrics((predictions, labels)) -``` - -```python out -TypeError: only size-1 arrays can be converted to Python scalars -``` - -Nous obtenons la même erreur, donc le problème vient bien de cette fonction. Si on regarde son code, on voit qu'elle transmet simplement les `predictions` et les `labels` à `metric.compute()`. Y a-t-il donc un problème avec cette méthode ? Pas vraiment. Jetons un coup d'oeil rapide aux formes : - -```py -predictions.shape, labels.shape -``` - -```python out -((8, 3), (8,)) -``` - -Nos prédictions sont toujours des logits et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `compute_metrics()` : - -```py -import numpy as np - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - predictions = np.argmax(predictions, axis=1) - return metric.compute(predictions=predictions, references=labels) - - -compute_metrics((predictions, labels)) -``` - -```python out -{'accuracy': 0.625} -``` - -Maintenant notre erreur est corrigée ! C'était la dernière, donc notre script va maintenant entraîner un modèle correctement. - -Pour référence, voici le script complètement corrigé : - -```py -import numpy as np -from datasets import load_dataset -import evaluate -from transformers import ( - AutoTokenizer, - AutoModelForSequenceClassification, - DataCollatorWithPadding, - TrainingArguments, - Trainer, -) - -raw_datasets = load_dataset("glue", "mnli") - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) - - -def preprocess_function(examples): - return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) - - -tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) -model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) - -args = TrainingArguments( - f"distilbert-finetuned-mnli", - evaluation_strategy="epoch", - save_strategy="epoch", - learning_rate=2e-5, - num_train_epochs=3, - weight_decay=0.01, -) - -metric = evaluate.load("glue", "mnli") - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - predictions = np.argmax(predictions, axis=1) - return metric.compute(predictions=predictions, references=labels) - - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) - -trainer = Trainer( - model, - args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation_matched"], - compute_metrics=compute_metrics, - data_collator=data_collator, - tokenizer=tokenizer, -) -trainer.train() -``` - -Dans ce cas, il n'y a plus de problème, et notre script va *finetuner* un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique et nous allons vous montrer quelques techniques qui peuvent vous aider. - - - -💡 Si vous utilisez une boucle d'entraînement manuelle, les mêmes étapes s'appliquent pour déboguer votre pipeline d'entraînement, mais il est plus facile de les séparer. Assurez-vous cependant de ne pas avoir oublié le `model.eval()` ou le `model.train()` aux bons endroits, ou le `zero_grad()` à chaque étape ! - - - -## Déboguer les erreurs silencieuses pendant l'entraînement - -Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique et qu'il n'y a pas de réponse magique. - -### Vérifiez vos données (encore !) - -Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un *bug* corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes : - -- les données décodées sont-elles compréhensibles ? -- êtes-vous d'accord avec les étiquettes ? -- y a-t-il une étiquette qui est plus courante que les autres ? -- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? - - - -⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente du jeu de données. - - - -Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. - -Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. - -Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. - -### Surentraînement du modèle sur un seul batch - -Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. - -Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : - -```py -for batch in trainer.get_train_dataloader(): - break - -batch = {k: v.to(device) for k, v in batch.items()} -trainer.create_optimizer() - -for _ in range(20): - outputs = trainer.model(**batch) - loss = outputs.loss - loss.backward() - trainer.optimizer.step() - trainer.optimizer.zero_grad() -``` - - - -💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. - - - -Le modèle résultant devrait avoir des résultats proches de la perfection sur le même `batch`. Calculons la métrique sur les prédictions résultantes : - -```py -with torch.no_grad(): - outputs = trainer.model(**batch) -preds = outputs.logits -labels = batch["labels"] - -compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())) -``` - -```python out -{'accuracy': 1.0} -``` - -100% de précision, voilà un bel exemple de surentraînement (ce qui signifie que si vous essayez votre modèle sur n'importe quelle autre phrase, il vous donnera très probablement une mauvaise réponse) ! - -Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données. Vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. - - - -⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. - - - -### Ne réglez rien tant que vous n'avez pas une première ligne de base - -Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. - -Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. - -Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. - -### Demander de l'aide - -Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). - -Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : - -- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus -- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao -- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts -- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy - -Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. + + +# Débogage du pipeline d'entraînement + + + +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. + +## Déboguer le pipeline d'entraînement + + + +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. + +La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. + +Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +trainer = Trainer( + model, + args, + train_dataset=raw_datasets["train"], + eval_dataset=raw_datasets["validation_matched"], + compute_metrics=compute_metrics, +) +trainer.train() +``` + +Si vous essayez de l'exécuter, vous serez confronté à une erreur plutôt cryptique : + +```python out +'ValueError: You have to specify either input_ids or inputs_embeds' +``` + +### Vérifiez vos données + +Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre jeu d'entraînement. + +Pour éviter d'innombrables heures passées à essayer de corriger quelque chose qui n'est pas la source du bug, nous vous recommandons d'utiliser `trainer.train_dataset` pour vos vérifications et rien d'autre. Faisons donc cela ici : + +```py +trainer.train_dataset[0] +``` + +```python out +{'hypothesis': 'Product and geography are what make cream skimming work. ', + 'idx': 0, + 'label': 1, + 'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'} +``` + +Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments. + +Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les jeux de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, +) +trainer.train() +``` + +Ce nouveau code donnera maintenant une erreur différente (c'est un progrès !) : + +```python out +'ValueError: expected sequence of length 43 at dim 1 (got 37)' +``` + +En regardant le *traceback*, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données : + +```python out +~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) + 105 batch[k] = torch.stack([f[k] for f in features]) + 106 else: +--> 107 batch[k] = torch.tensor([f[k] for f in features]) + 108 + 109 return batch +``` + +Donc, nous devrions passer à cela. Mais avant finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes. + +Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole cela signifie écouter les échantillons audio décodés, et pour notre exemple de NLP cela signifie utiliser notre *tokenizer* pour décoder les entrées : + +```py +tokenizer.decode(trainer.train_dataset[0]["input_ids"]) +``` + +```python out +'[CLS] conceptually cream skimming has two basic dimensions - product and geography. [SEP] product and geography are what make cream skimming work. [SEP]' +``` + +Cela semble correct. Vous devriez faire cela pour toutes les clés dans les entrées : + +```py +trainer.train_dataset[0].keys() +``` + +```python out +dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) +``` + +Notez que les clés qui ne correspondent pas à des entrées acceptées par le modèle seront automatiquement écartées, donc ici nous ne garderons que `input_ids`, `attention_mask`, et `label` (qui sera renommé `labels`). Pour revérifier la signature du modèle, vous pouvez imprimer la classe de votre modèle, puis aller consulter sa documentation : + +```py +type(trainer.model) +``` + +```python out +transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification +``` + +Donc dans notre cas, nous pouvons vérifier les paramètres acceptés sur [cette page](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Le `Trainer` va également enregistrer les colonnes qu'il rejette. + +Nous avons vérifié que les identifiants d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` : + +```py +tokenizer.decode(trainer.train_dataset[0]["attention_mask"]) +``` + +```python out +[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +``` + +Comme nous n'avons pas appliqué de *padding* dans notre prétraitement, cela semble parfaitement naturel. Pour être sûr qu'il n'y a pas de problème avec ce masque d'attention, vérifions qu'il est de la même longueur que nos identifiants d'entrée : + +```py +len(trainer.train_dataset[0]["attention_mask"]) == len( + trainer.train_dataset[0]["input_ids"] +) +``` + +```python out +True +``` + +C'est bien ! Enfin, vérifions notre étiquette : + +```py +trainer.train_dataset[0]["label"] +``` + +```python out +1 +``` + +Comme les identifiants d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante du jeu de données : + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction : la première n'implique pas la seconde. Cela semble correct ! + +Nous n'avons pas de *token* de type identifiant ici puisque DistilBERT ne les attend pas. Si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée. + + + +✏️ *A votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement. + + + +Ici nous ne vérifions que le jeu d'entraînement. Vous devez bien sûr vérifier de la même façon les jeux de validation et de test. + +Maintenant que nous savons que nos jeux de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement. + +### Des jeux de données aux chargeurs de données + +La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir du jeu d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le *dataloader* de validation) : + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Ce code crée le *dataloader* d'entraînement puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le *dataloader*, comme c'est le cas ici : + +```python out +~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) + 105 batch[k] = torch.stack([f[k] for f in features]) + 106 else: +--> 107 batch[k] = torch.tensor([f[k] for f in features]) + 108 + 109 return batch + +ValueError: expected sequence of length 45 at dim 1 (got 76) +``` + +L'inspection de la dernière image du *traceback* devrait suffire à vous donner un indice mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch. La première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` : + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +C'est donc `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par `DataCollatorWithPadding`. Et cette assembleur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ? + +La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement l'assembleur de données que l'on veut utiliser pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + DataCollatorWithPadding, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, + data_collator=data_collator, + tokenizer=tokenizer, +) +trainer.train() +``` + +La bonne nouvelle ? Nous n'avons plus la même erreur qu'avant, ce qui est un progrès certain. La mauvaise nouvelle ? Nous obtenons une erreur CUDA infâme à la place : + +```python out +RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` +``` + +C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème mais terminons d'abord notre analyse de la création de batchs. + +Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre jeu de données : + +```py +data_collator = trainer.get_train_dataloader().collate_fn +batch = data_collator([trainer.train_dataset[i] for i in range(4)]) +``` + +Ce code échouera parce que le `train_dataset` contient des colonnes de type *string* que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode `Trainer._remove_unused_columns()` qui fait cela : + +```py +data_collator = trainer.get_train_dataloader().collate_fn +actual_train_set = trainer._remove_unused_columns(trainer.train_dataset) +batch = data_collator([actual_train_set[i] for i in range(4)]) +``` + +Vous devriez alors être en mesure de déboguer manuellement ce qui se passe dans le collecteur de données si l'erreur persiste. + +Maintenant que nous avons débogué le processus de création de batch, il est temps d'en passer un dans le modèle ! + + +### Passage par le modèle + +Vous devriez être en mesure d'obtenir un batch en exécutant la commande suivante : + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre *notebook* et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La première plus ennuyeuse est le fait qu'elles sont difficiles à déboguer. + +Comment cela se fait-il ? Cela tient à la façon dont les GPUs fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait mentionnée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre *traceback* précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant. + +Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur *out-of-memory* (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer. + +Pour faire cela dans notre cas, nous devons juste remettre le modèle sur le CPU et l'appeler sur notre batch. Le batch retourné par le `DataLoader` n'a pas encore été déplacé sur le GPU : + +```python +outputs = trainer.model.cpu()(**batch) +``` + +```python out +~/.pyenv/versions/3.7.9/envs/base/lib/python3.7/site-packages/torch/nn/functional.py in nll_loss(input, target, weight, size_average, ignore_index, reduce, reduction) + 2386 ) + 2387 if dim == 2: +-> 2388 ret = torch._C._nn.nll_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index) + 2389 elif dim == 4: + 2390 ret = torch._C._nn.nll_loss2d(input, target, weight, _Reduction.get_enum(reduction), ignore_index) + +IndexError: Target 2 is out of bounds. +``` + +L'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec la passe arrière comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un bon moment pour vérifier le nombre de labels de notre modèle : + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms des étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons les indices 0, 1 et 2 dans notre jeu de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + DataCollatorWithPadding, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +Nous n'incluons pas encore la ligne `trainer.train()` pour prendre le temps de vérifier que tout se passe bien. Si nous passons un batch à notre modèle, il fonctionne maintenant sans erreur ! + +```py +for batch in trainer.get_train_dataloader(): + break + +outputs = trainer.model.cpu()(**batch) +``` + +L'étape suivante consiste alors à revenir au GPU et à vérifier que tout fonctionne encore : + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +batch = {k: v.to(device) for k, v in batch.items()} + +outputs = trainer.model.to(device)(**batch) +``` + +Si vous obtenez toujours une erreur, assurez-vous de redémarrer votre *notebook* et d'exécuter uniquement la dernière version du script. + +### Exécution d'une étape d'optimisation + +Maintenant que nous savons que nous pouvons construire des batchs qui passent réellement par le modèle, nous sommes prêts pour l'étape suivante du pipeline d'entraînement : calculer les gradients et effectuer une étape d'optimisation. + +La première partie est juste une question d'appel de la méthode `backward()` sur la perte : + +```py +loss = outputs.loss +loss.backward() +``` + +Il est plutôt rare d'obtenir une erreur à ce stade, mais si vous en obtenez une, assurez-vous de retourner au CPU pour obtenir un message d'erreur utile. + +Pour effectuer l'étape d'optimisation, il suffit de créer le `optimizer` et d'appeler sa méthode `step()` : + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +Encore une fois, si vous utilisez l'optimiseur par défaut dans le `Trainer`, vous ne devriez pas avoir d'erreur à ce stade, mais si vous avez un optimiseur personnalisé, il pourrait y avoir quelques problèmes à déboguer ici. N'oubliez pas de revenir au CPU si vous obtenez une erreur CUDA bizarre à ce stade. En parlant d'erreurs CUDA, nous avons mentionné précédemment un cas particulier. Voyons cela maintenant. + +### Gérer les erreurs CUDA out of memory + +Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code et peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement. + +Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle. + + + +Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. + + + +### Évaluation du modèle + +Maintenant que nous avons résolu tous les problèmes liés à notre code, tout est parfait et l'entraînement devrait se dérouler sans problème, n'est-ce pas ? Pas si vite ! Si vous exécutez la commande `trainer.train()`, tout aura l'air bien au début, mais après un moment vous obtiendrez ce qui suit : + +```py +# Cela prendra beaucoup de temps et se soldera par une erreur, vous ne devriez donc pas utiliser cette cellule. +trainer.train() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Vous réaliserez que cette erreur apparaît pendant la phase d'évaluation, donc c'est la dernière chose que nous aurons besoin de déboguer. + +Vous pouvez exécuter la boucle d'évaluation du `Trainer` indépendamment de l'entraînement comme ceci : + +```py +trainer.evaluate() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + + + +💡 Vous devriez toujours vous assurer que vous pouvez exécuter `trainer.evaluate()` avant de lancer `trainer.train()`, pour éviter de gaspiller beaucoup de ressources de calcul avant de tomber sur une erreur. + + + +Avant de tenter de déboguer un problème dans la boucle d'évaluation, vous devez d'abord vous assurer que vous avez examiné les données, que vous êtes en mesure de former un batch correctement et que vous pouvez exécuter votre modèle sur ces données. Nous avons effectué toutes ces étapes, et le code suivant peut donc être exécuté sans erreur : + +```py +for batch in trainer.get_eval_dataloader(): + break + +batch = {k: v.to(device) for k, v in batch.items()} + +with torch.no_grad(): + outputs = trainer.model(**batch) +``` + +L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons le *traceback*, nous voyons ceci : + +```python trace +~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references) + 431 """ + 432 batch = {"predictions": predictions, "references": references} +--> 433 batch = self.info.features.encode_batch(batch) + 434 if self.writer is None: + 435 self._init_writer() +``` + +Cela nous indique que l'erreur provient du module `datasets/metric.py` donc c'est un problème avec notre fonction `compute_metrics()`. Elle prend un *tuple* avec les logits et les labels sous forme de tableaux NumPy, alors essayons de lui fournir cela : + +```py +predictions = outputs.logits.cpu().numpy() +labels = batch["labels"].cpu().numpy() + +compute_metrics((predictions, labels)) +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Nous obtenons la même erreur, donc le problème vient bien de cette fonction. Si on regarde son code, on voit qu'elle transmet simplement les `predictions` et les `labels` à `metric.compute()`. Y a-t-il donc un problème avec cette méthode ? Pas vraiment. Jetons un coup d'oeil rapide aux formes : + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +Nos prédictions sont toujours des logits et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `compute_metrics()` : + +```py +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + predictions = np.argmax(predictions, axis=1) + return metric.compute(predictions=predictions, references=labels) + + +compute_metrics((predictions, labels)) +``` + +```python out +{'accuracy': 0.625} +``` + +Maintenant notre erreur est corrigée ! C'était la dernière, donc notre script va maintenant entraîner un modèle correctement. + +Pour référence, voici le script complètement corrigé : + +```py +import numpy as np +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + DataCollatorWithPadding, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + predictions = np.argmax(predictions, axis=1) + return metric.compute(predictions=predictions, references=labels) + + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, + data_collator=data_collator, + tokenizer=tokenizer, +) +trainer.train() +``` + +Dans ce cas, il n'y a plus de problème, et notre script va *finetuner* un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique et nous allons vous montrer quelques techniques qui peuvent vous aider. + + + +💡 Si vous utilisez une boucle d'entraînement manuelle, les mêmes étapes s'appliquent pour déboguer votre pipeline d'entraînement, mais il est plus facile de les séparer. Assurez-vous cependant de ne pas avoir oublié le `model.eval()` ou le `model.train()` aux bons endroits, ou le `zero_grad()` à chaque étape ! + + + +## Déboguer les erreurs silencieuses pendant l'entraînement + +Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique et qu'il n'y a pas de réponse magique. + +### Vérifiez vos données (encore !) + +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un *bug* corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes : + +- les données décodées sont-elles compréhensibles ? +- êtes-vous d'accord avec les étiquettes ? +- y a-t-il une étiquette qui est plus courante que les autres ? +- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? + + + +⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente du jeu de données. + + + +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. + +Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. + +Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. + +### Surentraînement du modèle sur un seul batch + +Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. + +Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : + +```py +for batch in trainer.get_train_dataloader(): + break + +batch = {k: v.to(device) for k, v in batch.items()} +trainer.create_optimizer() + +for _ in range(20): + outputs = trainer.model(**batch) + loss = outputs.loss + loss.backward() + trainer.optimizer.step() + trainer.optimizer.zero_grad() +``` + + + +💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. + + + +Le modèle résultant devrait avoir des résultats proches de la perfection sur le même `batch`. Calculons la métrique sur les prédictions résultantes : + +```py +with torch.no_grad(): + outputs = trainer.model(**batch) +preds = outputs.logits +labels = batch["labels"] + +compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())) +``` + +```python out +{'accuracy': 1.0} +``` + +100% de précision, voilà un bel exemple de surentraînement (ce qui signifie que si vous essayez votre modèle sur n'importe quelle autre phrase, il vous donnera très probablement une mauvaise réponse) ! + +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données. Vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. + + + +⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. + + + +### Ne réglez rien tant que vous n'avez pas une première ligne de base + +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. + +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. + +Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. + +### Demander de l'aide + +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). + +Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : + +- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy + +Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/4_tf.mdx b/chapters/fr/chapter8/4_tf.mdx index 257dafe26..c4b4c8b84 100644 --- a/chapters/fr/chapter8/4_tf.mdx +++ b/chapters/fr/chapter8/4_tf.mdx @@ -1,489 +1,489 @@ - - -# Débogage du pipeline d'entraînement - - - -Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. - -## Déboguer le pipeline d'entraînement - - - -Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. - -La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. - -Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : - -```py -from datasets import load_dataset -import evaluate -from transformers import ( - AutoTokenizer, - TFAutoModelForSequenceClassification, -) - -raw_datasets = load_dataset("glue", "mnli") - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) - - -def preprocess_function(examples): - return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) - - -tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) - -train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "labels"], batch_size=16, shuffle=True -) - -validation_dataset = tokenized_datasets["validation_matched"].to_tf_dataset( - columns=["input_ids", "labels"], batch_size=16, shuffle=True -) - -model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) - -model.compile(loss="sparse_categorical_crossentropy", optimizer="adam") - -model.fit(train_dataset) -``` - -Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu par l'équipe d'Hugging Face, donc veuillez l'ignorer. Si vous lisez le cours après novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige. - -Le problème cependant est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long : - -```python out -ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] -``` - -Qu'est-ce que cela signifie ? Nous avons essayé d'entraîner sur nos données mais nous n'avons pas obtenu de gradient. C'est assez déconcertant. Comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par... - -### Vérifier vos données - -Cela va sans dire, mais si vos données sont corrompues, Keras ne sera pas en mesure de les réparer pour vous. Avant toute chose, vous devez donc jeter un coup d'œil à ce que contient votre ensemble d'entraînement. - -Bien qu'il soit tentant de regarder dans `raw_datasets` et `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch : - -```py -for batch in train_dataset: - break -``` - -`break` termine la boucle après une itération, donc cela prend le premier batch qui sort de `train_dataset` et l'enregistre comme `batch`. Maintenant, jetons un coup d'oeil à ce qu'il y a à l'intérieur : - -```python out -{'attention_mask': , - 'label': , - 'input_ids': } -``` - -Cela semble correct. Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée. - -Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des *transformers* avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas. - -Le problème est maintenant devenu plus clair : nous avons passé un argument `loss`, ce qui signifie que nous demandons à Keras de calculer les pertes pour nous, mais nous avons passé nos étiquettes comme entrées au modèle, et non comme étiquettes à l'endroit où Keras les attend ! Nous devons choisir l'un ou l'autre : soit nous utilisons la perte interne du modèle et gardons les étiquettes où elles sont, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les attend. Pour simplifier, prenons la première approche. Changez l'appel à `compile()` pour lire : - -```py -model.compile(optimizer="adam") -``` - -Maintenant, nous allons utiliser la perte interne du modèle et ce problème devrait être résolu ! - - - -✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients. Mais il y a un autre problème avec la perte que nous avons spécifiée. L'entraînement fonctionnera toujours avec ce problème mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ? - -Un indice codé en ROT13, si vous êtes coincé : Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf ? - -Et un deuxième indice : Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf ny gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf ? - - - -Maintenant, essayons d'entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance nous pouvons juste appeler `model.fit()` et tout fonctionnera bien ! - -```python out - 246/24543 [..............................] - ETA: 15:52 - loss: nan -``` - -Oh non. - -`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de... - -### Vérifier votre modèle - -`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous. Cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent mais les messages d'erreur seront beaucoup plus compréhensibles car ils indiqueront exactement où le problème est survenu dans le code de votre modèle. - -Pour l'instant, cependant, nous n'avons pas besoin de `run_eagerly`. Exécutons le `batch` que nous avons obtenu précédemment à travers le modèle et voyons à quoi ressemblent les résultats : - -```py -model(batch) -``` - -```python out -TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) -``` - -Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "*not a number*". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. -Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ? - -La réponse est d'essayer de *reinitialiser* notre modèle. Une fois que nous avons commencé l'entraînement, nous avons eu un `nan` quelque part et il s'est rapidement propagé à travers tout le modèle. Donc, chargeons le modèle à partir d'un checkpoint et ne faisons aucune mise à jour de poids, et voyons où nous obtenons une valeur `nan` : - -```py -model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) -model(batch) -``` - -Quand on fait ça, on obtient : - -```py out -TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) -``` - -*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que le jeu de données a été mélangé) : - -```python -import numpy as np - -loss = model(batch).loss.numpy() -indices = np.flatnonzero(np.isnan(loss)) -indices -``` - -```python out -array([ 1, 2, 5, 7, 9, 10, 11, 13, 14]) -``` - -Examinons les échantillons d'où proviennent ces indices : - -```python -input_ids = batch["input_ids"].numpy() -input_ids[indices] -``` - -```python out -array([[ 101, 2007, 2032, 2001, 1037, 16480, 3917, 2594, 4135, - 23212, 3070, 2214, 10170, 1010, 2012, 4356, 1997, 3183, - 6838, 12953, 2039, 2000, 1996, 6147, 1997, 2010, 2606, - 1012, 102, 6838, 2001, 3294, 6625, 3773, 1996, 2214, - 2158, 1012, 102, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 1998, 6814, 2016, 2234, 2461, 2153, 1998, 13322, - 2009, 1012, 102, 2045, 1005, 1055, 2053, 3382, 2008, - 2016, 1005, 2222, 3046, 8103, 2075, 2009, 2153, 1012, - 102, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 1998, 2007, 1996, 3712, 4634, 1010, 2057, 8108, - 2025, 3404, 2028, 1012, 1996, 2616, 18449, 2125, 1999, - 1037, 9666, 1997, 4100, 8663, 11020, 6313, 2791, 1998, - 2431, 1011, 4301, 1012, 102, 2028, 1005, 1055, 5177, - 2110, 1998, 3977, 2000, 2832, 2106, 2025, 2689, 2104, - 2122, 6214, 1012, 102, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 1045, 2001, 1999, 1037, 13090, 5948, 2007, 2048, - 2308, 2006, 2026, 5001, 2043, 2026, 2171, 2001, 2170, - 1012, 102, 1045, 2001, 3564, 1999, 2277, 1012, 102, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 2195, 4279, 2191, 2039, 1996, 2181, 2124, 2004, - 1996, 2225, 7363, 1012, 102, 2045, 2003, 2069, 2028, - 2451, 1999, 1996, 2225, 7363, 1012, 102, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 2061, 2008, 1045, 2123, 1005, 1056, 2113, 2065, - 2009, 2428, 10654, 7347, 2030, 2009, 7126, 2256, 2495, - 2291, 102, 2009, 2003, 5094, 2256, 2495, 2291, 2035, - 2105, 1012, 102, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 2051, 1010, 2029, 3216, 2019, 2503, 3444, 1010, - 6732, 1996, 2265, 2038, 19840, 2098, 2125, 9906, 1998, - 2003, 2770, 2041, 1997, 4784, 1012, 102, 2051, 6732, - 1996, 2265, 2003, 9525, 1998, 4569, 1012, 102, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 1996, 10556, 2140, 11515, 2058, 1010, 2010, 2162, - 2252, 5689, 2013, 2010, 7223, 1012, 102, 2043, 1996, - 10556, 2140, 11515, 2058, 1010, 2010, 2252, 3062, 2000, - 1996, 2598, 1012, 102, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0], - [ 101, 13543, 1999, 2049, 6143, 2933, 2443, 102, 2025, - 13543, 1999, 6143, 2933, 2003, 2443, 102, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0]]) -``` - -Il y a beaucoup de batchs ici mais rien d'inhabituel. Regardons les étiquettes : - -```python out -labels = batch['labels'].numpy() -labels[indices] -``` - -```python out -array([2, 2, 2, 2, 2, 2, 2, 2, 2]) -``` - -Ah ! Les échantillons `nan` ont tous le même label. C'est un gros indice. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette vaut 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle : - -```python -model.config.num_labels -``` - -```python out -2 -``` - -Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan`. En essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle : - -``` -model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) -model.compile(optimizer='adam') -model.fit(train_dataset) -``` - -```python out - 869/24543 [>.............................] - ETA: 15:29 - loss: 1.1032 -``` - -On entraîne ! Plus de `nan` et nos pertes diminuent... en quelque sorte. Si vous regardez pendant un certain temps, vous pouvez commencer à vous impatienter car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de... - -### Vérifier les hyperparamètres - -Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size` qui ne semble pas être un coupable probable. Cependant, ne soyez pas dupe, il y a toujours des hyperparamètres. Si vous ne pouvez pas les voir, cela signifie simplement que vous ne connaissez pas leur réglage. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait). - -Dans ce cas, où avons-nous défini un argument avec une chaîne de caractères ? Au départ, nous définissions la perte avec une chaîne de caractères, mais nous ne le faisons plus. Cependant, nous le faisons pour l'optimiseur. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). - -Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous indiquons simplement `'adam'` nous allons obtenir le taux d'apprentissage par défaut qui est de 0.001 (ou 1e-3). C'est beaucoup trop élevé pour un *transformer* ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles soit entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du *checkpoint* au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids : - -```python -from tensorflow.keras.optimizers import Adam - -model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) -model.compile(optimizer=Adam(5e-5)) -``` - - - -💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 Transformers qui vous donnera un optimiseur AdamW avec une décroissance du taux des poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut. - - - -Maintenant, nous pouvons essayer de *finetuner* le modèle avec le nouveau taux d'apprentissage : - -```python -model.fit(train_dataset) -``` - -```python out -319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 -``` - -Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et le taux de décroissance des poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire « caler » l'entraînement à une valeur de perte élevée. - -## Autres problèmes potentiels - -Nous avons couvert les problèmes dans le script ci-dessus, mais il existe plusieurs autres erreurs courantes auxquelles vous pouvez être confronté. Jetons un coup d'oeil à une liste (très incomplète). - -### Gérer les erreurs de manque de mémoire - -Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" (OOM étant l'abréviation de *out of memory*). Il s'agit d'un risque très courant lorsque l'on utilise de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPUs sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter et s'entraînent aussi beaucoup plus rapidement. - - - -Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. - - - -### TensorFlow affamé 🦛 - -Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement. Puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres *frameworks*, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps alors **vous allez passer un mauvais moment**. - -Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela. Si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de *notebook* n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les *notebooks* en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau *notebook* que vous démarrez peut rencontrer des problèmes très étranges. - -Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier si la plupart de votre mémoire est libre ou toujours utilisée. Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche ! - - -### Vérifiez vos données (encore !) - -Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un *bug* qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Il transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement renseignent ce que vous voulez. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit : - - -```py -input_ids = batch["input_ids"].numpy() -tokenizer.decode(input_ids[0]) -``` - -Vous pouvez ensuite la comparer avec la première étiquette, comme suit : - -```py -labels = batch["labels"].numpy() -label = labels[0] -``` - -Une fois que vous pouvez visualiser vos données de cette manière, vous pouvez vous poser les questions suivantes : - -- les données décodées sont-elles compréhensibles ? -- êtes-vous d'accord avec les étiquettes ? -- y a-t-il une étiquette qui est plus courante que les autres ? -- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? - -Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. - -Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. - -Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. - -### Surentraînement du modèle sur un seul batch - -Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. - -Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : - - -```py -for batch in train_dataset: - break - -# Assurez-vous que vous avez exécuté model.compile() et défini votre optimiseur, -# et vos pertes/métriques si vous les utilisez. - -model.fit(batch, epochs=20) -``` - - - -💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. - - - -Le modèle résultant devrait avoir des résultats proches de la perfection sur le `batch`, avec une perte diminuant rapidement vers 0 (ou la valeur minimale pour la perte que vous utilisez). - -Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. - - - -⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. - - - -### Ne réglez rien tant que vous n'avez pas une première ligne de base - -Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. - -Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. - -Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. - -### Demander de l'aide - -Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). - -Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : - -- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus -- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao -- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts -- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy - -Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. + + +# Débogage du pipeline d'entraînement + + + +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. + +## Déboguer le pipeline d'entraînement + + + +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. + +La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. + +Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + TFAutoModelForSequenceClassification, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) + +train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "labels"], batch_size=16, shuffle=True +) + +validation_dataset = tokenized_datasets["validation_matched"].to_tf_dataset( + columns=["input_ids", "labels"], batch_size=16, shuffle=True +) + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +model.compile(loss="sparse_categorical_crossentropy", optimizer="adam") + +model.fit(train_dataset) +``` + +Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu par l'équipe d'Hugging Face, donc veuillez l'ignorer. Si vous lisez le cours après novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige. + +Le problème cependant est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long : + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +Qu'est-ce que cela signifie ? Nous avons essayé d'entraîner sur nos données mais nous n'avons pas obtenu de gradient. C'est assez déconcertant. Comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par... + +### Vérifier vos données + +Cela va sans dire, mais si vos données sont corrompues, Keras ne sera pas en mesure de les réparer pour vous. Avant toute chose, vous devez donc jeter un coup d'œil à ce que contient votre ensemble d'entraînement. + +Bien qu'il soit tentant de regarder dans `raw_datasets` et `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch : + +```py +for batch in train_dataset: + break +``` + +`break` termine la boucle après une itération, donc cela prend le premier batch qui sort de `train_dataset` et l'enregistre comme `batch`. Maintenant, jetons un coup d'oeil à ce qu'il y a à l'intérieur : + +```python out +{'attention_mask': , + 'label': , + 'input_ids': } +``` + +Cela semble correct. Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée. + +Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des *transformers* avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas. + +Le problème est maintenant devenu plus clair : nous avons passé un argument `loss`, ce qui signifie que nous demandons à Keras de calculer les pertes pour nous, mais nous avons passé nos étiquettes comme entrées au modèle, et non comme étiquettes à l'endroit où Keras les attend ! Nous devons choisir l'un ou l'autre : soit nous utilisons la perte interne du modèle et gardons les étiquettes où elles sont, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les attend. Pour simplifier, prenons la première approche. Changez l'appel à `compile()` pour lire : + +```py +model.compile(optimizer="adam") +``` + +Maintenant, nous allons utiliser la perte interne du modèle et ce problème devrait être résolu ! + + + +✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients. Mais il y a un autre problème avec la perte que nous avons spécifiée. L'entraînement fonctionnera toujours avec ce problème mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ? + +Un indice codé en ROT13, si vous êtes coincé : Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf ? + +Et un deuxième indice : Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf ny gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf ? + + + +Maintenant, essayons d'entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance nous pouvons juste appeler `model.fit()` et tout fonctionnera bien ! + +```python out + 246/24543 [..............................] - ETA: 15:52 - loss: nan +``` + +Oh non. + +`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de... + +### Vérifier votre modèle + +`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous. Cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent mais les messages d'erreur seront beaucoup plus compréhensibles car ils indiqueront exactement où le problème est survenu dans le code de votre modèle. + +Pour l'instant, cependant, nous n'avons pas besoin de `run_eagerly`. Exécutons le `batch` que nous avons obtenu précédemment à travers le modèle et voyons à quoi ressemblent les résultats : + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "*not a number*". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. +Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ? + +La réponse est d'essayer de *reinitialiser* notre modèle. Une fois que nous avons commencé l'entraînement, nous avons eu un `nan` quelque part et il s'est rapidement propagé à travers tout le modèle. Donc, chargeons le modèle à partir d'un checkpoint et ne faisons aucune mise à jour de poids, et voyons où nous obtenons une valeur `nan` : + +```py +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model(batch) +``` + +Quand on fait ça, on obtient : + +```py out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que le jeu de données a été mélangé) : + +```python +import numpy as np + +loss = model(batch).loss.numpy() +indices = np.flatnonzero(np.isnan(loss)) +indices +``` + +```python out +array([ 1, 2, 5, 7, 9, 10, 11, 13, 14]) +``` + +Examinons les échantillons d'où proviennent ces indices : + +```python +input_ids = batch["input_ids"].numpy() +input_ids[indices] +``` + +```python out +array([[ 101, 2007, 2032, 2001, 1037, 16480, 3917, 2594, 4135, + 23212, 3070, 2214, 10170, 1010, 2012, 4356, 1997, 3183, + 6838, 12953, 2039, 2000, 1996, 6147, 1997, 2010, 2606, + 1012, 102, 6838, 2001, 3294, 6625, 3773, 1996, 2214, + 2158, 1012, 102, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 1998, 6814, 2016, 2234, 2461, 2153, 1998, 13322, + 2009, 1012, 102, 2045, 1005, 1055, 2053, 3382, 2008, + 2016, 1005, 2222, 3046, 8103, 2075, 2009, 2153, 1012, + 102, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 1998, 2007, 1996, 3712, 4634, 1010, 2057, 8108, + 2025, 3404, 2028, 1012, 1996, 2616, 18449, 2125, 1999, + 1037, 9666, 1997, 4100, 8663, 11020, 6313, 2791, 1998, + 2431, 1011, 4301, 1012, 102, 2028, 1005, 1055, 5177, + 2110, 1998, 3977, 2000, 2832, 2106, 2025, 2689, 2104, + 2122, 6214, 1012, 102, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 1045, 2001, 1999, 1037, 13090, 5948, 2007, 2048, + 2308, 2006, 2026, 5001, 2043, 2026, 2171, 2001, 2170, + 1012, 102, 1045, 2001, 3564, 1999, 2277, 1012, 102, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 2195, 4279, 2191, 2039, 1996, 2181, 2124, 2004, + 1996, 2225, 7363, 1012, 102, 2045, 2003, 2069, 2028, + 2451, 1999, 1996, 2225, 7363, 1012, 102, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 2061, 2008, 1045, 2123, 1005, 1056, 2113, 2065, + 2009, 2428, 10654, 7347, 2030, 2009, 7126, 2256, 2495, + 2291, 102, 2009, 2003, 5094, 2256, 2495, 2291, 2035, + 2105, 1012, 102, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 2051, 1010, 2029, 3216, 2019, 2503, 3444, 1010, + 6732, 1996, 2265, 2038, 19840, 2098, 2125, 9906, 1998, + 2003, 2770, 2041, 1997, 4784, 1012, 102, 2051, 6732, + 1996, 2265, 2003, 9525, 1998, 4569, 1012, 102, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 1996, 10556, 2140, 11515, 2058, 1010, 2010, 2162, + 2252, 5689, 2013, 2010, 7223, 1012, 102, 2043, 1996, + 10556, 2140, 11515, 2058, 1010, 2010, 2252, 3062, 2000, + 1996, 2598, 1012, 102, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0], + [ 101, 13543, 1999, 2049, 6143, 2933, 2443, 102, 2025, + 13543, 1999, 6143, 2933, 2003, 2443, 102, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0]]) +``` + +Il y a beaucoup de batchs ici mais rien d'inhabituel. Regardons les étiquettes : + +```python out +labels = batch['labels'].numpy() +labels[indices] +``` + +```python out +array([2, 2, 2, 2, 2, 2, 2, 2, 2]) +``` + +Ah ! Les échantillons `nan` ont tous le même label. C'est un gros indice. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette vaut 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle : + +```python +model.config.num_labels +``` + +```python out +2 +``` + +Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan`. En essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle : + +``` +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) +model.compile(optimizer='adam') +model.fit(train_dataset) +``` + +```python out + 869/24543 [>.............................] - ETA: 15:29 - loss: 1.1032 +``` + +On entraîne ! Plus de `nan` et nos pertes diminuent... en quelque sorte. Si vous regardez pendant un certain temps, vous pouvez commencer à vous impatienter car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de... + +### Vérifier les hyperparamètres + +Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size` qui ne semble pas être un coupable probable. Cependant, ne soyez pas dupe, il y a toujours des hyperparamètres. Si vous ne pouvez pas les voir, cela signifie simplement que vous ne connaissez pas leur réglage. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait). + +Dans ce cas, où avons-nous défini un argument avec une chaîne de caractères ? Au départ, nous définissions la perte avec une chaîne de caractères, mais nous ne le faisons plus. Cependant, nous le faisons pour l'optimiseur. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). + +Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous indiquons simplement `'adam'` nous allons obtenir le taux d'apprentissage par défaut qui est de 0.001 (ou 1e-3). C'est beaucoup trop élevé pour un *transformer* ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles soit entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du *checkpoint* au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids : + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 Transformers qui vous donnera un optimiseur AdamW avec une décroissance du taux des poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut. + + + +Maintenant, nous pouvons essayer de *finetuner* le modèle avec le nouveau taux d'apprentissage : + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et le taux de décroissance des poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire « caler » l'entraînement à une valeur de perte élevée. + +## Autres problèmes potentiels + +Nous avons couvert les problèmes dans le script ci-dessus, mais il existe plusieurs autres erreurs courantes auxquelles vous pouvez être confronté. Jetons un coup d'oeil à une liste (très incomplète). + +### Gérer les erreurs de manque de mémoire + +Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" (OOM étant l'abréviation de *out of memory*). Il s'agit d'un risque très courant lorsque l'on utilise de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPUs sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter et s'entraînent aussi beaucoup plus rapidement. + + + +Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. + + + +### TensorFlow affamé 🦛 + +Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement. Puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres *frameworks*, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps alors **vous allez passer un mauvais moment**. + +Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela. Si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de *notebook* n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les *notebooks* en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau *notebook* que vous démarrez peut rencontrer des problèmes très étranges. + +Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier si la plupart de votre mémoire est libre ou toujours utilisée. Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche ! + + +### Vérifiez vos données (encore !) + +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un *bug* qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Il transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement renseignent ce que vous voulez. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit : + + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Vous pouvez ensuite la comparer avec la première étiquette, comme suit : + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` + +Une fois que vous pouvez visualiser vos données de cette manière, vous pouvez vous poser les questions suivantes : + +- les données décodées sont-elles compréhensibles ? +- êtes-vous d'accord avec les étiquettes ? +- y a-t-il une étiquette qui est plus courante que les autres ? +- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? + +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. + +Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. + +Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. + +### Surentraînement du modèle sur un seul batch + +Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. + +Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : + + +```py +for batch in train_dataset: + break + +# Assurez-vous que vous avez exécuté model.compile() et défini votre optimiseur, +# et vos pertes/métriques si vous les utilisez. + +model.fit(batch, epochs=20) +``` + + + +💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. + + + +Le modèle résultant devrait avoir des résultats proches de la perfection sur le `batch`, avec une perte diminuant rapidement vers 0 (ou la valeur minimale pour la perte que vous utilisez). + +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. + + + +⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. + + + +### Ne réglez rien tant que vous n'avez pas une première ligne de base + +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. + +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. + +Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. + +### Demander de l'aide + +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). + +Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : + +- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy + +Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/5.mdx b/chapters/fr/chapter8/5.mdx index 330305c27..71c0f9dfc 100644 --- a/chapters/fr/chapter8/5.mdx +++ b/chapters/fr/chapter8/5.mdx @@ -1,92 +1,92 @@ -# Comment rédiger une bonne issue - - - -Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).
-Si vous n'êtes pas complètement certain que le *bug* se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre votre problème et l'équipe d'Hugging Face y suit de près les discussions qui s'y déroulent. - - - -Lorsque vous êtes sûr d'avoir identifier un *bug*, la première étape consiste à construire un exemple minimal qui soit reproductible. - -## Créer un exemple minimal reproductible - -Il est très important d'isoler le morceau de code qui produit le *bug* car personne dans l'équipe d'Hugging Face n'est (encore) un magicien et on ne peut pas réparer ce qu'on ne peut pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur. - - - -🚨 De nombreux problèmes dans le dépôt 🤗 *Transformers* ne sont pas résolus car les données utilisées pour les reproduire ne sont pas accessibles. - - - -Une fois que vous avez quelque chose d'autonome, essayez de le réduire au moins de lignes de code possible, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable. - -Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre *bug*. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une *pull request* pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre message. - -## Remplir le gabarit de problème - -Lorsque vous ouvrerez votre *issue* vous remarquerez qu'il y a un gabarit à remplir. Nous suivrons ici celui pour la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers/issues/new/choose) mais le même type d'information sera requis dans un autre dépôt. Ne laissez pas le gabarit vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème. - -En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre *issue* des critiques qui vous semblent justifiées mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet. - -### Inclure les informations sur votre environnement - -🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations nécessaire concernant votre environnement. Il suffit de taper ce qui suit dans votre terminal : - -``` -transformers-cli env -``` - -et vous devriez obtenir quelque chose comme : - -```out -Copy-and-paste the text below in your GitHub issue and FILL OUT the two last points. - -- `transformers` version: 4.12.0.dev0 -- Platform: Linux-5.10.61-1-MANJARO-x86_64-with-arch-Manjaro-Linux -- Python version: 3.7.9 -- PyTorch version (GPU?): 1.8.1+cu111 (True) -- Tensorflow version (GPU?): 2.5.0 (True) -- Flax version (CPU?/GPU?/TPU?): 0.3.4 (cpu) -- Jax version: 0.2.13 -- JaxLib version: 0.1.65 -- Using GPU in script?: -- Using distributed or parallel set-up in script?: -``` - -Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule de *notebook* puis copier et coller le résultat au début de votre *issue*. - -### Taguer des personnes - -Taguer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Néanmoins utilisez cette fonction avec modération car les personnes que vous taguez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre *bug*, vous devriez taguer la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur « *View git blame* »). - -Sinon, le gabarit propose des suggestions de personnes à taguer. En général, ne marquez jamais plus de trois personnes ! - -### Inclure un exemple reproductible - -Si vous avez réussi à créer un exemple autonome qui produit le *bug*, il est temps de l'inclure ! Tapez une ligne avec trois *backticks* suivis de `python`, comme ceci : - -``` -```python -``` - -puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois *backticks*. Cela permettra de s'assurer que votre code est correctement formaté. - -Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en des étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* d'un Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre. - -Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certaines cellules peuvent être automatiquement réduites dans la trace de la pile et veillez donc à les afficher avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois *backticks* afin qu'il soit correctement formaté. - -### Décrire le comportement attendu - -Expliquez en quelques lignes ce que vous vous attendiez à obtenir afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase mais dans certains cas vous pouvez avoir beaucoup à dire. - -## Et ensuite ? - -Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement. - -Il est inutile d'envoyer des messages aux personnes concernées si vous n'obtenez pas de réponse. Si personne ne vous aide au bout de quelques jours, il est probable que personne n'a pu donner un sens à votre problème. N'hésitez pas à revenir à l'exemple reproductible. Pouvez-vous le rendre plus court et plus concis ? Si vous n'obtenez pas de réponse au bout d'une semaine, vous pouvez laisser un message demandant gentiment de l'aide, surtout si vous avez modifié votre question pour inclure plus d'informations sur le problème. +# Comment rédiger une bonne issue + + + +Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).
+Si vous n'êtes pas complètement certain que le *bug* se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre votre problème et l'équipe d'Hugging Face y suit de près les discussions qui s'y déroulent. + + + +Lorsque vous êtes sûr d'avoir identifier un *bug*, la première étape consiste à construire un exemple minimal qui soit reproductible. + +## Créer un exemple minimal reproductible + +Il est très important d'isoler le morceau de code qui produit le *bug* car personne dans l'équipe d'Hugging Face n'est (encore) un magicien et on ne peut pas réparer ce qu'on ne peut pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur. + + + +🚨 De nombreux problèmes dans le dépôt 🤗 *Transformers* ne sont pas résolus car les données utilisées pour les reproduire ne sont pas accessibles. + + + +Une fois que vous avez quelque chose d'autonome, essayez de le réduire au moins de lignes de code possible, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable. + +Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre *bug*. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une *pull request* pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre message. + +## Remplir le gabarit de problème + +Lorsque vous ouvrerez votre *issue* vous remarquerez qu'il y a un gabarit à remplir. Nous suivrons ici celui pour la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers/issues/new/choose) mais le même type d'information sera requis dans un autre dépôt. Ne laissez pas le gabarit vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème. + +En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre *issue* des critiques qui vous semblent justifiées mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet. + +### Inclure les informations sur votre environnement + +🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations nécessaire concernant votre environnement. Il suffit de taper ce qui suit dans votre terminal : + +``` +transformers-cli env +``` + +et vous devriez obtenir quelque chose comme : + +```out +Copy-and-paste the text below in your GitHub issue and FILL OUT the two last points. + +- `transformers` version: 4.12.0.dev0 +- Platform: Linux-5.10.61-1-MANJARO-x86_64-with-arch-Manjaro-Linux +- Python version: 3.7.9 +- PyTorch version (GPU?): 1.8.1+cu111 (True) +- Tensorflow version (GPU?): 2.5.0 (True) +- Flax version (CPU?/GPU?/TPU?): 0.3.4 (cpu) +- Jax version: 0.2.13 +- JaxLib version: 0.1.65 +- Using GPU in script?: +- Using distributed or parallel set-up in script?: +``` + +Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule de *notebook* puis copier et coller le résultat au début de votre *issue*. + +### Taguer des personnes + +Taguer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Néanmoins utilisez cette fonction avec modération car les personnes que vous taguez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre *bug*, vous devriez taguer la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur « *View git blame* »). + +Sinon, le gabarit propose des suggestions de personnes à taguer. En général, ne marquez jamais plus de trois personnes ! + +### Inclure un exemple reproductible + +Si vous avez réussi à créer un exemple autonome qui produit le *bug*, il est temps de l'inclure ! Tapez une ligne avec trois *backticks* suivis de `python`, comme ceci : + +``` +```python +``` + +puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois *backticks*. Cela permettra de s'assurer que votre code est correctement formaté. + +Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en des étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* d'un Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre. + +Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certaines cellules peuvent être automatiquement réduites dans la trace de la pile et veillez donc à les afficher avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois *backticks* afin qu'il soit correctement formaté. + +### Décrire le comportement attendu + +Expliquez en quelques lignes ce que vous vous attendiez à obtenir afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase mais dans certains cas vous pouvez avoir beaucoup à dire. + +## Et ensuite ? + +Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement. + +Il est inutile d'envoyer des messages aux personnes concernées si vous n'obtenez pas de réponse. Si personne ne vous aide au bout de quelques jours, il est probable que personne n'a pu donner un sens à votre problème. N'hésitez pas à revenir à l'exemple reproductible. Pouvez-vous le rendre plus court et plus concis ? Si vous n'obtenez pas de réponse au bout d'une semaine, vous pouvez laisser un message demandant gentiment de l'aide, surtout si vous avez modifié votre question pour inclure plus d'informations sur le problème. diff --git a/chapters/fr/chapter8/6.mdx b/chapters/fr/chapter8/6.mdx index b31f82d1f..0f705def0 100644 --- a/chapters/fr/chapter8/6.mdx +++ b/chapters/fr/chapter8/6.mdx @@ -1,7 +1,12 @@ -# Partie 2 terminée ! - -Félicitations, vous avez terminé la deuxième partie du cours ! Nous travaillons activement sur la troisième alors inscrivez-vous à notre [*newsletter*](https://huggingface.curated.co/) pour être sûr de ne pas manquer sa sortie. - -Vous devriez maintenant être en mesure d'aborder une série de tâches de NLP et de *finetuner* ou de prétraîner un modèle sur celles-ci. N'oubliez pas de partager vos résultats avec la communauté sur le [*Hub*](https://huggingface.co/models). - -Nous sommes impatients de voir ce que vous allez construire avec les connaissances que vous avez acquises ! +# Partie 2 terminée ! + + + +Félicitations, vous avez terminé la deuxième partie du cours ! Nous travaillons activement sur la troisième alors inscrivez-vous à notre [*newsletter*](https://huggingface.curated.co/) pour être sûr de ne pas manquer sa sortie. + +Vous devriez maintenant être en mesure d'aborder une série de tâches de NLP et de *finetuner* ou de prétraîner un modèle sur celles-ci. N'oubliez pas de partager vos résultats avec la communauté sur le [*Hub*](https://huggingface.co/models). + +Nous sommes impatients de voir ce que vous allez construire avec les connaissances que vous avez acquises ! diff --git a/chapters/fr/chapter8/7.mdx b/chapters/fr/chapter8/7.mdx index 6f93e4e7f..2d3cccf24 100644 --- a/chapters/fr/chapter8/7.mdx +++ b/chapters/fr/chapter8/7.mdx @@ -1,199 +1,204 @@ - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Dans quel ordre devez-vous lire un traceback Python ? - -traceback de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.", - correct: true - } - ]} -/> - -### 2. Qu'est-ce qu'un exemple minimal reproductible ? - -transformer à partir d'un article de recherche.", - explain: "Bien qu'il soit très éducatif d'implémenter vos propres modèles de transformers à partir de zéro, ce n'est pas ce dont nous parlons ici." - }, - { - text: "Un bloc de code compact et autonome qui peut être exécuté sans aucune dépendance externe sur des fichiers ou des données privées.", - explain: "Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.", - correct: true - }, - { - text: "Une capture d'écran de la traceback Python", - explain: "Essayez à nouveau. Bien qu'il soit tentant d'inclure une capture d'écran de l'erreur à laquelle vous êtes confronté lorsque vous soumettez un problème, cela rend très difficile pour les autres de reproduire l'erreur." - }, - { - text: "Un notebook qui contient toute votre analyse, y compris les parties sans rapport avec l'erreur.", - explain: "Pas tout à fait. Bien qu'il puisse être utile de partager un notebook Google Colab qui montre l'erreur, assurez-vous qu'il est court et ne contient que le code pertinent." - } - ]} -/> - -### 3. Supposons que vous essayez d'exécuter le code suivant, qui génère une erreur : - -```py -from transformers import GPT3ForSequenceClassification - -# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py) -# --------------------------------------------------------------------------- -# ImportError Traceback (most recent call last) -# /var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_30848/333858878.py in -# ----> 1 from transformers import GPT3ForSequenceClassification - -# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py) -``` - -Lequel des éléments suivants pourrait être un bon choix pour le titre d'un sujet de forum pour demander de l'aide ? - -ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", - explain: "Inclure la dernière ligne de la traceback peut être descriptif, mais il est préférable de le réserver au corps principal du sujet. Essayez à nouveau !" - }, - { - text: "Problème avec from transformers import GPT3ForSequenceClassification", - explain: "Essayez à nouveau. Bien que cette information soit utile, il est probablement préférable de la réserver au corps du texte.", - }, - { - text: "Pourquoi je ne peux pas importer GPT3ForSequenceClassification?", - explain: "Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 Transformers).", - correct: true - }, - { - text: "Le GPT-3 est-il pris en charge dans 🤗 Transformers ?", - explain: "Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté.", - correct: true - } - ]} -/> - -### 4. Supposons que vous ayez essayé d'exécuter `trainer.train()` et que vous soyez confronté à une erreur énigmatique qui ne vous dit pas exactement d'où vient l'erreur. Quel est le premier endroit où vous devez chercher les erreurs dans votre pipeline d'entraînement ? - -bugs dans votre optimiseur, cela se produit généralement à plusieurs étapes du pipeline d'entraînement, il y a donc d'autres choses à vérifier d'abord. Essayez à nouveau !" - }, - { - text: "L'étape d'évaluation où nous calculons les métriques", - explain: "L'évaluation est généralement ce que vous faites après l'entraînement pour une époque complète, donc vous devriez d'abord vérifier quelque part plus tôt dans le pipeline d'entraînement.", - }, - { - text: "Les jeux de données", - explain: "C'est exact ! L'examen de vos données est presque toujours la première chose à faire, pour vous assurer que le texte est codé de manière appropriée, qu'il présente les caractéristiques attendues, etc.", - correct: true - }, - { - text: "Les chargeurs de données", - explain: "Essayez à nouveau. C'est très proche de la première chose que vous devriez vérifier. Vous souvenez-vous de l'objet que nous passons aux dataloaders ?" - } - ]} -/> - -### 5. Quelle est la meilleure façon de déboguer une erreur CUDA ? - -traceback pour découvrir ce qui a causé l'erreur.", - explain: "C'est ce que vous feriez pour toute autre erreur, mais les erreurs CUDA ne sont généralement pas signalées là où elles se sont produites, car la plupart des opérations CUDA sont asynchrones." - }, - { - text: "Réduisez la taille du batch.", - explain: "La réduction de la taille du batch est généralement une bonne stratégie pour gérer les erreurs CUDA hors mémoire, mais pas pour ce problème particulier. Essayez à nouveau !" - }, - { - text: "Redémarrez le noyau Jupyter.", - explain: "Essayez à nouveau. Le redémarrage du noyau ne fera pas disparaître l'erreur comme par magie !", - } - ]} -/> - -### 6. Quelle est la meilleure façon de faire corriger un problème sur GitHub ? - -bug.", - explain: "C'est la meilleure façon d'aider les mainteneurs à trouver votre bogue. Que devez-vous faire d'autre ?", - correct: true - }, - { - text: "Demandez chaque jour une mise à jour.", - explain: "Il est peu probable que cela vous apporte de l'aide. Les gens vous ignoreront probablement davantage.", - }, - { - text: "Inspectez le code source autour du bogue et essayez de trouver la raison pour laquelle il se produit. Postez les résultats dans le problème.", - explain: "Cela aidera certainement les mainteneurs ! Et si vous trouvez la source du bogue et un correctif, vous pouvez même ouvrir une demande de modification. Que devez-vous faire d'autre ?", - correct: true - } - ]} -/> - -### 7. Pourquoi le surapprentissage à un batch est-il généralement une bonne technique de débogage ? - - - -### 8. Pourquoi est-ce une bonne idée d'inclure des détails sur votre environnement de calcul avec `transformers-cli env` lorsque vous créez une nouvelle question dans le dépôt 🤗 *Transformers* ? - - + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Dans quel ordre devez-vous lire un traceback Python ? + +traceback de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.", + correct: true + } + ]} +/> + +### 2. Qu'est-ce qu'un exemple minimal reproductible ? + +transformer à partir d'un article de recherche.", + explain: "Bien qu'il soit très éducatif d'implémenter vos propres modèles de transformers à partir de zéro, ce n'est pas ce dont nous parlons ici." + }, + { + text: "Un bloc de code compact et autonome qui peut être exécuté sans aucune dépendance externe sur des fichiers ou des données privées.", + explain: "Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.", + correct: true + }, + { + text: "Une capture d'écran de la traceback Python", + explain: "Essayez à nouveau. Bien qu'il soit tentant d'inclure une capture d'écran de l'erreur à laquelle vous êtes confronté lorsque vous soumettez un problème, cela rend très difficile pour les autres de reproduire l'erreur." + }, + { + text: "Un notebook qui contient toute votre analyse, y compris les parties sans rapport avec l'erreur.", + explain: "Pas tout à fait. Bien qu'il puisse être utile de partager un notebook Google Colab qui montre l'erreur, assurez-vous qu'il est court et ne contient que le code pertinent." + } + ]} +/> + +### 3. Supposons que vous essayez d'exécuter le code suivant, qui génère une erreur : + +```py +from transformers import GPT3ForSequenceClassification + +# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py) +# --------------------------------------------------------------------------- +# ImportError Traceback (most recent call last) +# /var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_30848/333858878.py in +# ----> 1 from transformers import GPT3ForSequenceClassification + +# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py) +``` + +Lequel des éléments suivants pourrait être un bon choix pour le titre d'un sujet de forum pour demander de l'aide ? + +ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", + explain: "Inclure la dernière ligne de la traceback peut être descriptif, mais il est préférable de le réserver au corps principal du sujet. Essayez à nouveau !" + }, + { + text: "Problème avec from transformers import GPT3ForSequenceClassification", + explain: "Essayez à nouveau. Bien que cette information soit utile, il est probablement préférable de la réserver au corps du texte.", + }, + { + text: "Pourquoi je ne peux pas importer GPT3ForSequenceClassification?", + explain: "Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 Transformers).", + correct: true + }, + { + text: "Le GPT-3 est-il pris en charge dans 🤗 Transformers ?", + explain: "Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté.", + correct: true + } + ]} +/> + +### 4. Supposons que vous ayez essayé d'exécuter `trainer.train()` et que vous soyez confronté à une erreur énigmatique qui ne vous dit pas exactement d'où vient l'erreur. Quel est le premier endroit où vous devez chercher les erreurs dans votre pipeline d'entraînement ? + +bugs dans votre optimiseur, cela se produit généralement à plusieurs étapes du pipeline d'entraînement, il y a donc d'autres choses à vérifier d'abord. Essayez à nouveau !" + }, + { + text: "L'étape d'évaluation où nous calculons les métriques", + explain: "L'évaluation est généralement ce que vous faites après l'entraînement pour une époque complète, donc vous devriez d'abord vérifier quelque part plus tôt dans le pipeline d'entraînement.", + }, + { + text: "Les jeux de données", + explain: "C'est exact ! L'examen de vos données est presque toujours la première chose à faire, pour vous assurer que le texte est codé de manière appropriée, qu'il présente les caractéristiques attendues, etc.", + correct: true + }, + { + text: "Les chargeurs de données", + explain: "Essayez à nouveau. C'est très proche de la première chose que vous devriez vérifier. Vous souvenez-vous de l'objet que nous passons aux dataloaders ?" + } + ]} +/> + +### 5. Quelle est la meilleure façon de déboguer une erreur CUDA ? + +traceback pour découvrir ce qui a causé l'erreur.", + explain: "C'est ce que vous feriez pour toute autre erreur, mais les erreurs CUDA ne sont généralement pas signalées là où elles se sont produites, car la plupart des opérations CUDA sont asynchrones." + }, + { + text: "Réduisez la taille du batch.", + explain: "La réduction de la taille du batch est généralement une bonne stratégie pour gérer les erreurs CUDA hors mémoire, mais pas pour ce problème particulier. Essayez à nouveau !" + }, + { + text: "Redémarrez le noyau Jupyter.", + explain: "Essayez à nouveau. Le redémarrage du noyau ne fera pas disparaître l'erreur comme par magie !", + } + ]} +/> + +### 6. Quelle est la meilleure façon de faire corriger un problème sur GitHub ? + +bug.", + explain: "C'est la meilleure façon d'aider les mainteneurs à trouver votre bogue. Que devez-vous faire d'autre ?", + correct: true + }, + { + text: "Demandez chaque jour une mise à jour.", + explain: "Il est peu probable que cela vous apporte de l'aide. Les gens vous ignoreront probablement davantage.", + }, + { + text: "Inspectez le code source autour du bogue et essayez de trouver la raison pour laquelle il se produit. Postez les résultats dans le problème.", + explain: "Cela aidera certainement les mainteneurs ! Et si vous trouvez la source du bogue et un correctif, vous pouvez même ouvrir une demande de modification. Que devez-vous faire d'autre ?", + correct: true + } + ]} +/> + +### 7. Pourquoi le surapprentissage à un batch est-il généralement une bonne technique de débogage ? + + + +### 8. Pourquoi est-ce une bonne idée d'inclure des détails sur votre environnement de calcul avec `transformers-cli env` lorsque vous créez une nouvelle question dans le dépôt 🤗 *Transformers* ? + + diff --git a/chapters/fr/chapter9/1.mdx b/chapters/fr/chapter9/1.mdx index 78afbc891..8dd18331c 100644 --- a/chapters/fr/chapter9/1.mdx +++ b/chapters/fr/chapter9/1.mdx @@ -1,32 +1,37 @@ -# Introduction à Gradio - -Dans ce chapitre, nous allons apprendre à construire des **démos interactives** pour vos modèles d'apprentissage automatique. - -Pourquoi construire une démo ou une interface graphique pour votre modèle d'apprentissage automatique ? Les démos permettent : - -- aux **développeurs en apprentissage automatique** de présenter facilement leur travail à un large public, y compris des équipes non techniques ou des clients. -- aux **chercheurs** de reproduire plus facilement les modèles d'apprentissage automatique et leur comportement. -- aux **testeurs qualité** ou **utilisateurs finaux** d'identifier et de déboguer plus facilement les points de défaillance des modèles. -- aux **utilisateurs divers** de découvrir les biais algorithmiques des modèles. - -Nous utiliserons la bibliothèque *Gradio* pour construire des démos pour nos modèles. *Gradio* vous permet de construire, de personnaliser et de partager des démos en ligne pour n'importe quel modèle d'apprentissage automatique. Et cela entièrement en Python. - -Voici quelques exemples de démos d'apprentissage automatique construites avec Gradio : - -* Un modèle de **reconnaissance de croquis** qui prend un croquis et produit des étiquettes de ce qu'il pense être dessiné : - - - -* Un modèle extractif de **réponse à une question** qui prend en entrée un paragraphe de contexte et une requête et produit une réponse et un score de probabilité (nous avons discuté de ce type de modèle [au chapitre 7](/course/fr/chapter7/7)) : - - - -* Un modèle de **suppression de l'arrière-plan** qui prend une image et la restitue avec l'arrière-plan supprimé : - - - -Ce chapitre est divisé en sections qui comprennent à la fois des _concepts_ et des _applications_. Après avoir appris le concept dans chaque section, vous l'appliquerez pour construire un type particulier de démo, allant de la classification d'images à la reconnaissance vocale. À la fin de ce chapitre, vous serez en mesure de créer ces démos (et bien d'autres !) en quelques lignes de code Python seulement. - - -👀 Consultez Hugging Face Spaces pour voir de nombreux exemples récents de démos d'apprentissage automatique construites par la communauté ! - +# Introduction à Gradio + + + +Dans ce chapitre, nous allons apprendre à construire des **démos interactives** pour vos modèles d'apprentissage automatique. + +Pourquoi construire une démo ou une interface graphique pour votre modèle d'apprentissage automatique ? Les démos permettent : + +- aux **développeurs en apprentissage automatique** de présenter facilement leur travail à un large public, y compris des équipes non techniques ou des clients. +- aux **chercheurs** de reproduire plus facilement les modèles d'apprentissage automatique et leur comportement. +- aux **testeurs qualité** ou **utilisateurs finaux** d'identifier et de déboguer plus facilement les points de défaillance des modèles. +- aux **utilisateurs divers** de découvrir les biais algorithmiques des modèles. + +Nous utiliserons la bibliothèque *Gradio* pour construire des démos pour nos modèles. *Gradio* vous permet de construire, de personnaliser et de partager des démos en ligne pour n'importe quel modèle d'apprentissage automatique. Et cela entièrement en Python. + +Voici quelques exemples de démos d'apprentissage automatique construites avec Gradio : + +* Un modèle de **reconnaissance de croquis** qui prend un croquis et produit des étiquettes de ce qu'il pense être dessiné : + + + +* Un modèle extractif de **réponse à une question** qui prend en entrée un paragraphe de contexte et une requête et produit une réponse et un score de probabilité (nous avons discuté de ce type de modèle [au chapitre 7](/course/fr/chapter7/7)) : + + + +* Un modèle de **suppression de l'arrière-plan** qui prend une image et la restitue avec l'arrière-plan supprimé : + + + +Ce chapitre est divisé en sections qui comprennent à la fois des _concepts_ et des _applications_. Après avoir appris le concept dans chaque section, vous l'appliquerez pour construire un type particulier de démo, allant de la classification d'images à la reconnaissance vocale. À la fin de ce chapitre, vous serez en mesure de créer ces démos (et bien d'autres !) en quelques lignes de code Python seulement. + + +👀 Consultez Hugging Face Spaces pour voir de nombreux exemples récents de démos d'apprentissage automatique construites par la communauté ! + diff --git a/chapters/fr/chapter9/2.mdx b/chapters/fr/chapter9/2.mdx index 813c3df3d..2a15df2a7 100644 --- a/chapters/fr/chapter9/2.mdx +++ b/chapters/fr/chapter9/2.mdx @@ -1,117 +1,117 @@ -# Construire votre première démo - - - -Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : - -`$ pip install gradio ` - -Vous pouvez exécuter *Gradio* n'importe où, que ce soit dans votre IDE Python préféré, dans des *notebooks* ou même dans Google Colab 🤯 ! -Alors installez *Gradio* partout où vous exécutez Python ! - -Commençons par un exemple simple de type « *Hello World* » pour nous familiariser avec la syntaxe de *Gradio* : - -```py -import gradio as gr - - -def greet(name): - return "Hello " + name - - -demo = gr.Interface(fn=greet, inputs="text", outputs="text") - -demo.launch() -``` - -Parcourons le code ci-dessus : - -- D'abord, nous définissons une fonction appelée `greet()`. Dans ce cas, c'est une simple fonction qui ajoute « *Hello* » devant votre nom, mais cela peut être *n'importe quelle* fonction Python en général. Par exemple, dans les applications d'apprentissage automatique, cette fonction pourrait *appeler un modèle pour faire une prédiction* sur une entrée et retourner la sortie. -- Ensuite, nous créons une `Interface` *Gradio* avec trois arguments, `fn`, `inputs`, et `outputs`. Ces arguments définissent la fonction de prédiction, ainsi que le _type_ de composants d'entrée et de sortie que nous souhaitons. Dans notre cas, les deux composants sont de simples boîtes de texte. -- Nous appelons ensuite la méthode `launch()` sur l'`Interface` que nous avons créée. - -Si vous exécutez ce code, l'interface ci-dessous apparaîtra automatiquement dans un *notebook* Jupyter/Colab ou dans un navigateur sur **[http://localhost:7860](http://localhost:7860/)** si vous l'exécutez à partir d'un script. - - - -Essayez d'utiliser cette interface maintenant avec votre propre nom ou une autre entrée ! - -Vous remarquerez que dedans, *Gradio* a automatiquement déduit le nom du paramètre d'entrée (`name`) et l'a appliqué comme étiquette au dessus de la zone de texte. Que faire si vous souhaitez changer cela ? -Ou si vous souhaitez personnaliser la zone de texte d'une autre manière ? Dans ce cas, vous pouvez instancier un objet de classe représentant le composant de saisie. - -Jetez un coup d'œil à l'exemple ci-dessous : - -```py -import gradio as gr - - -def greet(name): - return "Hello " + name - - -# Nous instancions la classe Textbox -textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) - -gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() -``` - - - -Ici, nous avons créé une zone de texte d'entrée avec une étiquette, un espace réservé et un nombre de lignes défini. -Vous pourriez faire la même chose pour la zone de texte de sortie, mais nous allons laisser cela pour le moment. - -Nous avons vu qu'avec seulement quelques lignes de code, *Gradio* vous permet de créer une interface simple autour de n'importe quelle fonction -avec n'importe quel type d'entrées ou de sorties. Dans cette section, nous avons commencé par une simple boîte de texte mais dans les sections suivantes, nous couvrirons d'autres types d'entrées et de sorties. Voyons maintenant comment inclure un peu de NLP dans une application *Gradio*. - - -## 🤖 Inclure les prédictions du modèle - -Construisons maintenant une interface simple qui permet de faire une démo d'un modèle de **génération de texte** comme le GPT-2. - -Nous allons charger notre modèle en utilisant la fonction `pipeline()` de 🤗 *Transformers*. -Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3#text-generation). - -Tout d'abord, nous définissons une fonction de prédiction qui prend une invite de texte et renvoie la complétion du texte : - -```py -from transformers import pipeline - -model = pipeline("text-generation") - - -def predict(prompt): - completion = model(prompt)[0]["generated_text"] - return completion -``` - -Cette fonction complète le texte que vous fournissez, et vous pouvez l'exécuter avec les votres pour voir comment elle fonctionne. Voici un exemple (vous obtiendrez peut-être un résultat différent) : - - -``` -predict("My favorite programming language is") # Mon langage de programmation préféré est -``` - -``` ->> My favorite programming language is Haskell. I really enjoyed the Haskell language, but it doesn't have all the features that can be applied to any other language. For example, all it does is compile to a byte array. -# Mon langage de programmation préféré est Haskell. J'ai vraiment apprécié le langage Haskell, mais il n'a pas toutes les caractéristiques que l'on peut appliquer à n'importe quel autre langage. Par exemple, il ne fait que compiler un tableau d'octets. -``` - -Maintenant que nous avons une fonction pour générer des prédictions, nous pouvons créer et lancer une `Interface` de la même manière que nous l'avons fait précédemment : - -```py -import gradio as gr - -gr.Interface(fn=predict, inputs="text", outputs="text").launch() -``` - - -C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du texte en utilisant le modèle GPT-2 comme indiqué ci-dessous 🤯. - - - +# Construire votre première démo + + + +Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : + +`$ pip install gradio ` + +Vous pouvez exécuter *Gradio* n'importe où, que ce soit dans votre IDE Python préféré, dans des *notebooks* ou même dans Google Colab 🤯 ! +Alors installez *Gradio* partout où vous exécutez Python ! + +Commençons par un exemple simple de type « *Hello World* » pour nous familiariser avec la syntaxe de *Gradio* : + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +demo = gr.Interface(fn=greet, inputs="text", outputs="text") + +demo.launch() +``` + +Parcourons le code ci-dessus : + +- D'abord, nous définissons une fonction appelée `greet()`. Dans ce cas, c'est une simple fonction qui ajoute « *Hello* » devant votre nom, mais cela peut être *n'importe quelle* fonction Python en général. Par exemple, dans les applications d'apprentissage automatique, cette fonction pourrait *appeler un modèle pour faire une prédiction* sur une entrée et retourner la sortie. +- Ensuite, nous créons une `Interface` *Gradio* avec trois arguments, `fn`, `inputs`, et `outputs`. Ces arguments définissent la fonction de prédiction, ainsi que le _type_ de composants d'entrée et de sortie que nous souhaitons. Dans notre cas, les deux composants sont de simples boîtes de texte. +- Nous appelons ensuite la méthode `launch()` sur l'`Interface` que nous avons créée. + +Si vous exécutez ce code, l'interface ci-dessous apparaîtra automatiquement dans un *notebook* Jupyter/Colab ou dans un navigateur sur **[http://localhost:7860](http://localhost:7860/)** si vous l'exécutez à partir d'un script. + + + +Essayez d'utiliser cette interface maintenant avec votre propre nom ou une autre entrée ! + +Vous remarquerez que dedans, *Gradio* a automatiquement déduit le nom du paramètre d'entrée (`name`) et l'a appliqué comme étiquette au dessus de la zone de texte. Que faire si vous souhaitez changer cela ? +Ou si vous souhaitez personnaliser la zone de texte d'une autre manière ? Dans ce cas, vous pouvez instancier un objet de classe représentant le composant de saisie. + +Jetez un coup d'œil à l'exemple ci-dessous : + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# Nous instancions la classe Textbox +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +Ici, nous avons créé une zone de texte d'entrée avec une étiquette, un espace réservé et un nombre de lignes défini. +Vous pourriez faire la même chose pour la zone de texte de sortie, mais nous allons laisser cela pour le moment. + +Nous avons vu qu'avec seulement quelques lignes de code, *Gradio* vous permet de créer une interface simple autour de n'importe quelle fonction +avec n'importe quel type d'entrées ou de sorties. Dans cette section, nous avons commencé par une simple boîte de texte mais dans les sections suivantes, nous couvrirons d'autres types d'entrées et de sorties. Voyons maintenant comment inclure un peu de NLP dans une application *Gradio*. + + +## 🤖 Inclure les prédictions du modèle + +Construisons maintenant une interface simple qui permet de faire une démo d'un modèle de **génération de texte** comme le GPT-2. + +Nous allons charger notre modèle en utilisant la fonction `pipeline()` de 🤗 *Transformers*. +Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3#text-generation). + +Tout d'abord, nous définissons une fonction de prédiction qui prend une invite de texte et renvoie la complétion du texte : + +```py +from transformers import pipeline + +model = pipeline("text-generation") + + +def predict(prompt): + completion = model(prompt)[0]["generated_text"] + return completion +``` + +Cette fonction complète le texte que vous fournissez, et vous pouvez l'exécuter avec les votres pour voir comment elle fonctionne. Voici un exemple (vous obtiendrez peut-être un résultat différent) : + + +``` +predict("My favorite programming language is") # Mon langage de programmation préféré est +``` + +``` +>> My favorite programming language is Haskell. I really enjoyed the Haskell language, but it doesn't have all the features that can be applied to any other language. For example, all it does is compile to a byte array. +# Mon langage de programmation préféré est Haskell. J'ai vraiment apprécié le langage Haskell, mais il n'a pas toutes les caractéristiques que l'on peut appliquer à n'importe quel autre langage. Par exemple, il ne fait que compiler un tableau d'octets. +``` + +Maintenant que nous avons une fonction pour générer des prédictions, nous pouvons créer et lancer une `Interface` de la même manière que nous l'avons fait précédemment : + +```py +import gradio as gr + +gr.Interface(fn=predict, inputs="text", outputs="text").launch() +``` + + +C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du texte en utilisant le modèle GPT-2 comme indiqué ci-dessous 🤯. + + + Continuez votre lecture du cours pour voir comment construire d'autres types de démos avec *Gradio* ! \ No newline at end of file diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx index 3a6b97f70..2ed908d58 100644 --- a/chapters/fr/chapter9/3.mdx +++ b/chapters/fr/chapter9/3.mdx @@ -1,166 +1,166 @@ -# Comprendre la classe Interface - - - -Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. - -## Comment créer une interface - -Vous remarquerez que la classe `Interface` a 3 paramètres obligatoires : - -`Interface(fn, inputs, outputs, ...)` - -Ces paramètres sont : - - - `fn`: la fonction de prédiction qui est enveloppée par l'interface *Gradio*. Cette fonction peut prendre un ou plusieurs paramètres et retourner une ou plusieurs valeurs. - - `inputs`: le(s) type(s) de composant(s) d'entrée. *Gradio* fournit de nombreux composants préconstruits tels que`"image"` ou `"mic"`. - - `outputs`: le(s) type(s) de composant(s) de sortie. Encore une fois, *Gradio* fournit de nombreux composants pré-construits, par ex. `"image"` ou `"label"`. - -Pour une liste complète des composants, [jetez un coup d'œil à la documentation de *Gradio*](https://gradio.app/docs). Chaque composant préconstruit peut être personnalisé en instanciant la classe correspondant au composant. - -Par exemple, comme nous l'avons vu dans la [section précédente](/course/fr/chapter9/2), au lieu de passer le paramètre `inputs` par `"textbox"`, vous pouvez passer un composant `Textbox(lines=7, label="Prompt")` pour créer une zone de texte avec 7 lignes et un label. - -Voyons un autre exemple, cette fois avec un composant `Audio`. - -## Un exemple simple avec audio - -Comme mentionné précédemment, *Gradio* fournit de nombreuses entrées et sorties différentes. -Construisons donc une `Interface` qui fonctionne avec l'audio. - -Dans cet exemple, nous allons construire une fonction audio-vers-audio qui prend un fichier audio et l'inverse simplement. - -Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un microphone. Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici). - -De plus, nous aimerions recevoir l'audio sous la forme d'un tableau numpy afin de pouvoir facilement l'inverser. Nous allons donc définir le `"type"` comme étant `"numpy"`, ce qui permet de passer les données d'entrée comme un *tuple* de (`sample_rate`, `data`) dans notre fonction. - -Nous utiliserons également le composant de sortie `Audio` qui peut automatiquement rendre un *tuple* avec un taux d'échantillonnage et un tableau numpy de données comme un fichier audio lisible. -Dans ce cas, nous n'avons pas besoin de faire de personnalisation, donc nous utiliserons le raccourci de la chaîne `"audio"`. - - -```py -import numpy as np -import gradio as gr - - -def reverse_audio(audio): - sr, data = audio - reversed_audio = (sr, np.flipud(data)) - return reversed_audio - - -mic = gr.Audio(source="microphone", type="numpy", label="Speak here...") -gr.Interface(reverse_audio, mic, "audio").launch() -``` - -Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé). - - - -Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 ! - -## Gérer les entrées et sorties multiples - -Imaginons que nous ayons une fonction plus compliquée, avec plusieurs entrées et sorties. -Dans l'exemple ci-dessous, nous avons une fonction qui prend un index de liste déroulante, une valeur de curseur et un nombre, et renvoie un échantillon audio d'une tonalité musicale. - -Regardez comment nous passons une liste de composants d'entrée et de sortie, et voyez si vous pouvez suivre ce qui se passe. - -La clé ici est que lorsque vous passez : -* une liste de composants d'entrée, chaque composant correspond à un paramètre dans l'ordre. -* une liste de composants de sortie, chaque composant correspond à une valeur retournée. - -L'extrait de code ci-dessous montre comment trois composants d'entrée correspondent aux trois arguments de la fonction `generate_tone()` : - -```py -import numpy as np -import gradio as gr - -notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] - - -def generate_tone(note, octave, duration): - sr = 48000 - a4_freq, tones_from_a4 = 440, 12 * (octave - 4) + (note - 9) - frequency = a4_freq * 2 ** (tones_from_a4 / 12) - duration = int(duration) - audio = np.linspace(0, duration, duration * sr) - audio = (20000 * np.sin(audio * (2 * np.pi * frequency))).astype(np.int16) - return (sr, audio) - - -gr.Interface( - generate_tone, - [ - gr.Dropdown(notes, type="index"), - gr.Slider(minimum=4, maximum=6, step=1), - gr.Textbox(type="number", value=1, label="Duration in seconds"), - ], - "audio", -).launch() -``` - - - - -### La méthode `launch()` - -Jusqu'à présent, nous avons utilisé la méthode `launch()` pour lancer l'interface, mais nous n'avons pas vraiment discuté de ce qu'elle fait. - -Par défaut, la méthode `launch()` lancera la démo dans un serveur web qui tourne localement. Si vous exécutez votre code dans un *notebook* Jupyter ou Colab, *Gradio* va intégrer l'interface graphique de la démo dans le *notebook* afin que vous puissiez l'utiliser facilement. - -Vous pouvez personnaliser le comportement de `launch()` à travers différents paramètres : - - - `inline` : si vous voulez afficher l'interface en ligne sur les *notebooks* Python. - - `inbrowser` : pour lancer automatiquement l'interface dans un nouvel onglet du navigateur par défaut. - - `share` : si vous voulez créer un lien public partageable depuis votre ordinateur pour l'interface. Un peu comme un lien Google Drive ! - -Nous couvrirons le paramètre `share` plus en détail dans la section suivante ! - -## ✏️ Appliquons ça ! - -Construisons une interface qui vous permette de faire la démonstration d'un modèle de **reconnaissance vocale**. -Pour rendre la chose intéressante, nous accepterons *soit* une entrée micro, soit un fichier téléchargé. - -Comme d'habitude, nous allons charger notre modèle de reconnaissance vocale en utilisant la fonction `pipeline()` de 🤗 *Transformers*. -Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3). Ensuite, nous allons implémenter une fonction `transcribe_audio()` qui traite l'audio et retourne la transcription (en anglais). Enfin, nous allons envelopper cette fonction dans une `Interface` avec les composants `Audio` pour les entrées et juste le texte pour la sortie. Au total, le code de cette application est le suivant : - -```py -from transformers import pipeline -import gradio as gr - -model = pipeline("automatic-speech-recognition") - - -def transcribe_audio(mic=None, file=None): - if mic is not None: - audio = mic - elif file is not None: - audio = file - else: - return "You must either provide a mic recording or a file" - transcription = model(audio)["text"] - return transcription - - -gr.Interface( - fn=transcribe_audio, - inputs=[ - gr.Audio(source="microphone", type="filepath", optional=True), - gr.Audio(source="upload", type="filepath", optional=True), - ], - outputs="text", -).launch() -``` - -Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé. - - - -Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur). - -Continuez pour voir comment partager votre interface avec d'autres ! +# Comprendre la classe Interface + + + +Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. + +## Comment créer une interface + +Vous remarquerez que la classe `Interface` a 3 paramètres obligatoires : + +`Interface(fn, inputs, outputs, ...)` + +Ces paramètres sont : + + - `fn`: la fonction de prédiction qui est enveloppée par l'interface *Gradio*. Cette fonction peut prendre un ou plusieurs paramètres et retourner une ou plusieurs valeurs. + - `inputs`: le(s) type(s) de composant(s) d'entrée. *Gradio* fournit de nombreux composants préconstruits tels que`"image"` ou `"mic"`. + - `outputs`: le(s) type(s) de composant(s) de sortie. Encore une fois, *Gradio* fournit de nombreux composants pré-construits, par ex. `"image"` ou `"label"`. + +Pour une liste complète des composants, [jetez un coup d'œil à la documentation de *Gradio*](https://gradio.app/docs). Chaque composant préconstruit peut être personnalisé en instanciant la classe correspondant au composant. + +Par exemple, comme nous l'avons vu dans la [section précédente](/course/fr/chapter9/2), au lieu de passer le paramètre `inputs` par `"textbox"`, vous pouvez passer un composant `Textbox(lines=7, label="Prompt")` pour créer une zone de texte avec 7 lignes et un label. + +Voyons un autre exemple, cette fois avec un composant `Audio`. + +## Un exemple simple avec audio + +Comme mentionné précédemment, *Gradio* fournit de nombreuses entrées et sorties différentes. +Construisons donc une `Interface` qui fonctionne avec l'audio. + +Dans cet exemple, nous allons construire une fonction audio-vers-audio qui prend un fichier audio et l'inverse simplement. + +Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un microphone. Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici). + +De plus, nous aimerions recevoir l'audio sous la forme d'un tableau numpy afin de pouvoir facilement l'inverser. Nous allons donc définir le `"type"` comme étant `"numpy"`, ce qui permet de passer les données d'entrée comme un *tuple* de (`sample_rate`, `data`) dans notre fonction. + +Nous utiliserons également le composant de sortie `Audio` qui peut automatiquement rendre un *tuple* avec un taux d'échantillonnage et un tableau numpy de données comme un fichier audio lisible. +Dans ce cas, nous n'avons pas besoin de faire de personnalisation, donc nous utiliserons le raccourci de la chaîne `"audio"`. + + +```py +import numpy as np +import gradio as gr + + +def reverse_audio(audio): + sr, data = audio + reversed_audio = (sr, np.flipud(data)) + return reversed_audio + + +mic = gr.Audio(source="microphone", type="numpy", label="Speak here...") +gr.Interface(reverse_audio, mic, "audio").launch() +``` + +Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé). + + + +Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 ! + +## Gérer les entrées et sorties multiples + +Imaginons que nous ayons une fonction plus compliquée, avec plusieurs entrées et sorties. +Dans l'exemple ci-dessous, nous avons une fonction qui prend un index de liste déroulante, une valeur de curseur et un nombre, et renvoie un échantillon audio d'une tonalité musicale. + +Regardez comment nous passons une liste de composants d'entrée et de sortie, et voyez si vous pouvez suivre ce qui se passe. + +La clé ici est que lorsque vous passez : +* une liste de composants d'entrée, chaque composant correspond à un paramètre dans l'ordre. +* une liste de composants de sortie, chaque composant correspond à une valeur retournée. + +L'extrait de code ci-dessous montre comment trois composants d'entrée correspondent aux trois arguments de la fonction `generate_tone()` : + +```py +import numpy as np +import gradio as gr + +notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] + + +def generate_tone(note, octave, duration): + sr = 48000 + a4_freq, tones_from_a4 = 440, 12 * (octave - 4) + (note - 9) + frequency = a4_freq * 2 ** (tones_from_a4 / 12) + duration = int(duration) + audio = np.linspace(0, duration, duration * sr) + audio = (20000 * np.sin(audio * (2 * np.pi * frequency))).astype(np.int16) + return (sr, audio) + + +gr.Interface( + generate_tone, + [ + gr.Dropdown(notes, type="index"), + gr.Slider(minimum=4, maximum=6, step=1), + gr.Textbox(type="number", value=1, label="Duration in seconds"), + ], + "audio", +).launch() +``` + + + + +### La méthode `launch()` + +Jusqu'à présent, nous avons utilisé la méthode `launch()` pour lancer l'interface, mais nous n'avons pas vraiment discuté de ce qu'elle fait. + +Par défaut, la méthode `launch()` lancera la démo dans un serveur web qui tourne localement. Si vous exécutez votre code dans un *notebook* Jupyter ou Colab, *Gradio* va intégrer l'interface graphique de la démo dans le *notebook* afin que vous puissiez l'utiliser facilement. + +Vous pouvez personnaliser le comportement de `launch()` à travers différents paramètres : + + - `inline` : si vous voulez afficher l'interface en ligne sur les *notebooks* Python. + - `inbrowser` : pour lancer automatiquement l'interface dans un nouvel onglet du navigateur par défaut. + - `share` : si vous voulez créer un lien public partageable depuis votre ordinateur pour l'interface. Un peu comme un lien Google Drive ! + +Nous couvrirons le paramètre `share` plus en détail dans la section suivante ! + +## ✏️ Appliquons ça ! + +Construisons une interface qui vous permette de faire la démonstration d'un modèle de **reconnaissance vocale**. +Pour rendre la chose intéressante, nous accepterons *soit* une entrée micro, soit un fichier téléchargé. + +Comme d'habitude, nous allons charger notre modèle de reconnaissance vocale en utilisant la fonction `pipeline()` de 🤗 *Transformers*. +Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3). Ensuite, nous allons implémenter une fonction `transcribe_audio()` qui traite l'audio et retourne la transcription (en anglais). Enfin, nous allons envelopper cette fonction dans une `Interface` avec les composants `Audio` pour les entrées et juste le texte pour la sortie. Au total, le code de cette application est le suivant : + +```py +from transformers import pipeline +import gradio as gr + +model = pipeline("automatic-speech-recognition") + + +def transcribe_audio(mic=None, file=None): + if mic is not None: + audio = mic + elif file is not None: + audio = file + else: + return "You must either provide a mic recording or a file" + transcription = model(audio)["text"] + return transcription + + +gr.Interface( + fn=transcribe_audio, + inputs=[ + gr.Audio(source="microphone", type="filepath", optional=True), + gr.Audio(source="upload", type="filepath", optional=True), + ], + outputs="text", +).launch() +``` + +Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé. + + + +Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur). + +Continuez pour voir comment partager votre interface avec d'autres ! diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index ffea14513..d929ef20b 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -1,147 +1,147 @@ -# Partager ses démos avec les autres - - - -Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). - -Nous aborderons ces deux approches sous peu. Mais avant de partager votre démo, vous voudrez peut-être la peaufiner 💅. - -### Polir votre démo Gradio - -
-Overview of a gradio interface - -
- -Pour ajouter du contenu supplémentaire à votre démo, la classe `Interface` supporte quelques paramètres optionnels : - - `title` : vous pouvez donner un titre à votre démo, qui apparaît _au-dessus_ des composants d'entrée et de sortie. - - `description` : vous pouvez donner une description (en texte, Markdown, ou HTML) pour l'interface, qui apparaît au-dessus des composants d'entrée et de sortie et en dessous du titre. - - `article` : vous pouvez également écrire un article étendu (en texte, Markdown ou HTML) expliquant l'interface. S'il est fourni, il apparaît _sous_ les composants d'entrée et de sortie. - - `theme` : vous n'aimez pas les couleurs par défaut ? Définissez le thème pour utiliser une des couleurs suivantes : `default`, `huggingface`, `grass`, `peach`. Vous pouvez également ajouter le préfixe `dark-`, par exemple `dark-peach` pour un thème sombre (ou juste `dark` pour le thème sombre par défaut). - - `examples` : pour rendre votre démo *beaucoup plus facile à utiliser*, vous pouvez fournir quelques exemples d'entrées pour la fonction. Ceux-ci apparaissent sous les composants de l'interface utilisateur et peuvent être utilisés pour remplir l'interface. Ils doivent être fournis sous forme de liste imbriquée, dans laquelle la liste extérieure est constituée d'exemples et chaque liste intérieure est constituée d'une entrée correspondant à chaque composant d'entrée. - - `live` : si vous voulez que votre modèle soit relancé à chaque fois que l'entrée change, vous pouvez mettre `live=True`. Ceci est utile pour les modèles rapides (nous verrons un exemple à la fin de cette section). -En utilisant les options ci-dessus, nous obtenons une interface plus complète. Exécutez le code ci-dessous pour pouvoir discuter avec Rick et Morty : - -```python out -title = "Ask Rick a Question" # "Posez une question à Rick" -description = -""" -The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! -# Le robot a été entraîné à répondre à des questions basées sur les dialogues de Rick et Morty. -# Demandez à Rick ce que vous voulez ! - -""" - -article = "Check out [the original Rick and Morty Bot](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) that this demo is based off of." -# Jetez un coup d'œil au [bot original Rick et Morty](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. - -gr.Interface( - fn=predict, - inputs="textbox", - outputs="text", - title=title, - description=description, - article=article, - examples=[["What are you doing?"], ["Where should we time travel to?"]] - # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] -).launch() -``` - -En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous : - - - -### Partager votre démo avec des liens temporaires -Maintenant que nous avons une démo fonctionnelle de notre modèle d'apprentissage automatique, apprenons à partager facilement un lien vers notre interface. -Les interfaces peuvent être facilement partagées publiquement en mettant `share=True` dans la méthode `launch()` : - -```python -gr.Interface(classify_image, "image", "label").launch(share=True) -``` - -Cela génère un lien public et partageable que vous pouvez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle dans son navigateur pendant 72 heures au maximum. Le traitement s'effectuant sur votre appareil (tant qu'il reste allumé !), vous n'avez pas à vous soucier de la mise en place de dépendances. Si vous travaillez à partir d'un *notebook* Google Colab, un lien de partage est toujours créé automatiquement. Il ressemble généralement à quelque chose comme ceci : **XXXXX.gradio.app**. Bien que le lien soit servi par un lien *Gradio*, nous ne sommes qu'un proxy pour votre serveur local, et nous ne stockons pas les données envoyées par les interfaces. - -Gardez cependant à l'esprit que ces liens sont accessibles au public, ce qui signifie que n'importe qui peut utiliser votre modèle pour la prédiction ! Par conséquent, assurez-vous de ne pas exposer d'informations sensibles à travers les fonctions que vous écrivez, ou de permettre que des changements critiques se produisent sur votre appareil. Si vous définissez `share=False` (la valeur par défaut), seul un lien local est créé. - -### Hébergement de votre démo sur Hugging Face Spaces - -Un lien de partage que vous pouvez passer à vos collègues est cool, mais comment pouvez-vous héberger de façon permanente votre démo et la faire exister dans son propre « espace » sur internet ? - -*Hugging Face Spaces* fournit l'infrastructure pour héberger de façon permanente votre démo *Gradio* sur internet et **gratuitement** ! *Spaces* vous permet de créer et de pousser vers un dépôt (public ou privé) le code de votre interface *Gradio*. Il sera placé dans un fichier `app.py`. [Lisez ce tutoriel étape par étape](https://huggingface.co/blog/gradio-spaces) pour commencer ou regardez la vidéo ci-dessous. - - - -## ✏️ Appliquons ça ! - -En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre](/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler. - -Nous pouvons charger les étiquettes depuis [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) et charger le modèle Pytorch pré-entraîné depuis [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Téléchargez ces fichiers en suivant le lien et en cliquant sur « *download* » dans le coin supérieur gauche de l'aperçu du fichier. Regardons le code ci-dessous pour voir comment nous utilisons ces fichiers pour charger notre modèle et créer une fonction `predict()` : - -```py -from pathlib import Path -import torch -import gradio as gr -from torch import nn - -LABELS = Path("class_names.txt").read_text().splitlines() - -model = nn.Sequential( - nn.Conv2d(1, 32, 3, padding="same"), - nn.ReLU(), - nn.MaxPool2d(2), - nn.Conv2d(32, 64, 3, padding="same"), - nn.ReLU(), - nn.MaxPool2d(2), - nn.Conv2d(64, 128, 3, padding="same"), - nn.ReLU(), - nn.MaxPool2d(2), - nn.Flatten(), - nn.Linear(1152, 256), - nn.ReLU(), - nn.Linear(256, len(LABELS)), -) -state_dict = torch.load("pytorch_model.bin", map_location="cpu") -model.load_state_dict(state_dict, strict=False) -model.eval() - - -def predict(im): - x = torch.tensor(im, dtype=torch.float32).unsqueeze(0).unsqueeze(0) / 255.0 - with torch.no_grad(): - out = model(x) - probabilities = torch.nn.functional.softmax(out[0], dim=0) - values, indices = torch.topk(probabilities, 5) - return {LABELS[i]: v.item() for i, v in zip(indices, values)} -``` - -Maintenant que nous avons une fonction `predict()`. La prochaine étape est de définir et de lancer notre interface *Gradio* : - -```py -interface = gr.Interface( - predict, - inputs="sketchpad", - outputs="label", - theme="huggingface", - title="Sketch Recognition", - description="Who wants to play Pictionary? Draw a common object like a shovel or a laptop, and the algorithm will guess in real time!", - # Qui veut jouer au Pictionary ? Dessinez un objet courant comme une pelle ou un ordinateur portable, et l'algorithme le devinera en temps réel ! - article="

Sketch Recognition | Demo Model

", - live=True, -) -interface.launch(share=True) -``` - - - - -Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo de sketchs fait une prédiction chaque fois que quelqu'un dessine sur le bloc (pas de bouton de soumission !). - -De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. -Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus. - +# Partager ses démos avec les autres + + + +Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). + +Nous aborderons ces deux approches sous peu. Mais avant de partager votre démo, vous voudrez peut-être la peaufiner 💅. + +### Polir votre démo Gradio + +
+Overview of a gradio interface + +
+ +Pour ajouter du contenu supplémentaire à votre démo, la classe `Interface` supporte quelques paramètres optionnels : + - `title` : vous pouvez donner un titre à votre démo, qui apparaît _au-dessus_ des composants d'entrée et de sortie. + - `description` : vous pouvez donner une description (en texte, Markdown, ou HTML) pour l'interface, qui apparaît au-dessus des composants d'entrée et de sortie et en dessous du titre. + - `article` : vous pouvez également écrire un article étendu (en texte, Markdown ou HTML) expliquant l'interface. S'il est fourni, il apparaît _sous_ les composants d'entrée et de sortie. + - `theme` : vous n'aimez pas les couleurs par défaut ? Définissez le thème pour utiliser une des couleurs suivantes : `default`, `huggingface`, `grass`, `peach`. Vous pouvez également ajouter le préfixe `dark-`, par exemple `dark-peach` pour un thème sombre (ou juste `dark` pour le thème sombre par défaut). + - `examples` : pour rendre votre démo *beaucoup plus facile à utiliser*, vous pouvez fournir quelques exemples d'entrées pour la fonction. Ceux-ci apparaissent sous les composants de l'interface utilisateur et peuvent être utilisés pour remplir l'interface. Ils doivent être fournis sous forme de liste imbriquée, dans laquelle la liste extérieure est constituée d'exemples et chaque liste intérieure est constituée d'une entrée correspondant à chaque composant d'entrée. + - `live` : si vous voulez que votre modèle soit relancé à chaque fois que l'entrée change, vous pouvez mettre `live=True`. Ceci est utile pour les modèles rapides (nous verrons un exemple à la fin de cette section). +En utilisant les options ci-dessus, nous obtenons une interface plus complète. Exécutez le code ci-dessous pour pouvoir discuter avec Rick et Morty : + +```python out +title = "Ask Rick a Question" # "Posez une question à Rick" +description = +""" +The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! +# Le robot a été entraîné à répondre à des questions basées sur les dialogues de Rick et Morty. +# Demandez à Rick ce que vous voulez ! + +""" + +article = "Check out [the original Rick and Morty Bot](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) that this demo is based off of." +# Jetez un coup d'œil au [bot original Rick et Morty](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. + +gr.Interface( + fn=predict, + inputs="textbox", + outputs="text", + title=title, + description=description, + article=article, + examples=[["What are you doing?"], ["Where should we time travel to?"]] + # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] +).launch() +``` + +En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous : + + + +### Partager votre démo avec des liens temporaires +Maintenant que nous avons une démo fonctionnelle de notre modèle d'apprentissage automatique, apprenons à partager facilement un lien vers notre interface. +Les interfaces peuvent être facilement partagées publiquement en mettant `share=True` dans la méthode `launch()` : + +```python +gr.Interface(classify_image, "image", "label").launch(share=True) +``` + +Cela génère un lien public et partageable que vous pouvez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle dans son navigateur pendant 72 heures au maximum. Le traitement s'effectuant sur votre appareil (tant qu'il reste allumé !), vous n'avez pas à vous soucier de la mise en place de dépendances. Si vous travaillez à partir d'un *notebook* Google Colab, un lien de partage est toujours créé automatiquement. Il ressemble généralement à quelque chose comme ceci : **XXXXX.gradio.app**. Bien que le lien soit servi par un lien *Gradio*, nous ne sommes qu'un proxy pour votre serveur local, et nous ne stockons pas les données envoyées par les interfaces. + +Gardez cependant à l'esprit que ces liens sont accessibles au public, ce qui signifie que n'importe qui peut utiliser votre modèle pour la prédiction ! Par conséquent, assurez-vous de ne pas exposer d'informations sensibles à travers les fonctions que vous écrivez, ou de permettre que des changements critiques se produisent sur votre appareil. Si vous définissez `share=False` (la valeur par défaut), seul un lien local est créé. + +### Hébergement de votre démo sur Hugging Face Spaces + +Un lien de partage que vous pouvez passer à vos collègues est cool, mais comment pouvez-vous héberger de façon permanente votre démo et la faire exister dans son propre « espace » sur internet ? + +*Hugging Face Spaces* fournit l'infrastructure pour héberger de façon permanente votre démo *Gradio* sur internet et **gratuitement** ! *Spaces* vous permet de créer et de pousser vers un dépôt (public ou privé) le code de votre interface *Gradio*. Il sera placé dans un fichier `app.py`. [Lisez ce tutoriel étape par étape](https://huggingface.co/blog/gradio-spaces) pour commencer ou regardez la vidéo ci-dessous. + + + +## ✏️ Appliquons ça ! + +En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre](/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler. + +Nous pouvons charger les étiquettes depuis [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) et charger le modèle Pytorch pré-entraîné depuis [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Téléchargez ces fichiers en suivant le lien et en cliquant sur « *download* » dans le coin supérieur gauche de l'aperçu du fichier. Regardons le code ci-dessous pour voir comment nous utilisons ces fichiers pour charger notre modèle et créer une fonction `predict()` : + +```py +from pathlib import Path +import torch +import gradio as gr +from torch import nn + +LABELS = Path("class_names.txt").read_text().splitlines() + +model = nn.Sequential( + nn.Conv2d(1, 32, 3, padding="same"), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Conv2d(32, 64, 3, padding="same"), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Conv2d(64, 128, 3, padding="same"), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Flatten(), + nn.Linear(1152, 256), + nn.ReLU(), + nn.Linear(256, len(LABELS)), +) +state_dict = torch.load("pytorch_model.bin", map_location="cpu") +model.load_state_dict(state_dict, strict=False) +model.eval() + + +def predict(im): + x = torch.tensor(im, dtype=torch.float32).unsqueeze(0).unsqueeze(0) / 255.0 + with torch.no_grad(): + out = model(x) + probabilities = torch.nn.functional.softmax(out[0], dim=0) + values, indices = torch.topk(probabilities, 5) + return {LABELS[i]: v.item() for i, v in zip(indices, values)} +``` + +Maintenant que nous avons une fonction `predict()`. La prochaine étape est de définir et de lancer notre interface *Gradio* : + +```py +interface = gr.Interface( + predict, + inputs="sketchpad", + outputs="label", + theme="huggingface", + title="Sketch Recognition", + description="Who wants to play Pictionary? Draw a common object like a shovel or a laptop, and the algorithm will guess in real time!", + # Qui veut jouer au Pictionary ? Dessinez un objet courant comme une pelle ou un ordinateur portable, et l'algorithme le devinera en temps réel ! + article="

Sketch Recognition | Demo Model

", + live=True, +) +interface.launch(share=True) +``` + + + + +Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo de sketchs fait une prédiction chaque fois que quelqu'un dessine sur le bloc (pas de bouton de soumission !). + +De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. +Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus. + La prochaine fois, nous couvrirons d'autres façons dont *Gradio* peut être utilisé avec l'écosystème d'*Hugging Face* ! \ No newline at end of file diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx index e4a8c6f07..e11bf093b 100644 --- a/chapters/fr/chapter9/5.mdx +++ b/chapters/fr/chapter9/5.mdx @@ -1,75 +1,75 @@ -# Intégrations avec le Hub d'Hugging Face - - - -Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. -Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*. - -### Chargement de modèles depuis le Hub d'Hugging Face - -Pour commencer, choisissez un des milliers de modèles qu'*Hugging Face* offre à travers le *Hub*, comme décrit dans le [chapitre 4](/course/fr/chapter4/2). - -En utilisant la méthode spéciale `Interface.load()`, vous passez `"model/"` (ou, de manière équivalente, `"huggingface/"`) suivi du nom du modèle. -Par exemple, voici le code pour construire une démo pour le [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), un grand modèle de langue, ajouter quelques exemples d'entrées : - -```py -import gradio as gr - -title = "GPT-J-6B" -description = "Gradio Demo for GPT-J 6B, a transformer model trained using Ben Wang's Mesh Transformer JAX. 'GPT-J' refers to the class of model, while '6B' represents the number of trainable parameters. To use it, simply add your text, or click one of the examples to load them. Read more at the links below." -# Démo Gradio pour GPT-J 6B, un modèle de transformer entraîné avec le Mesh Transformer JAX de Ben Wang. GPT-J fait référence à la classe du modèle, tandis que '6B' représente le nombre de paramètres entraînables. Pour l'utiliser, il suffit d'ajouter votre texte, ou de cliquer sur l'un des exemples pour le charger. Pour en savoir plus, consultez les liens ci-dessous. -article = "

GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model

" -# GPT-J-6B : Un modèle linguistique autorégressif à 6 milliards de paramètres -examples = [ - ["The tower is 324 metres (1,063 ft) tall,"], - # La tour mesure 324 mètres (1 063 pieds) de haut, - ["The Moon's orbit around Earth has"], - # L'orbite de la Lune autour de la Terre a - ["The smooth Borealis basin in the Northern Hemisphere covers 40%"], - # Le bassin de Borealis dans l'hémisphère nord couvre 40 %. -] -gr.Interface.load( - "huggingface/EleutherAI/gpt-j-6B", - inputs=gr.Textbox(lines=5, label="Input Text"), - title=title, - description=description, - article=article, - examples=examples, - enable_queue=True, -).launch() -``` - -Le code ci-dessus produira l'interface ci-dessous : - - - -Le chargement d'un modèle de cette manière utilise l'[API d'Inference](https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM. - -### Chargement depuis Hugging Face Spaces - -Pour charger n'importe quel *Space* depuis le *Hub* et le recréer localement, vous pouvez passer `spaces/` à l'`Interface`, suivi du nom du *Space*. - -Vous vous souvenez de la démo de la section 1 qui supprime le fond d'une image ? Chargeons-la à partir de *Hugging Face Spaces* : - -```py -gr.Interface.load("spaces/abidlabs/remove-bg").launch() -``` - - - -L'un des avantages du chargement de démos à partir du *Hub* ou de *Spaces* est que vous pouvez les personnaliser en remplaçant n'importe lequel des paramètres. Ici, nous ajoutons un titre et faisons en sorte qu'elle fonctionne avec une webcam à la place : - -```py -gr.Interface.load( - "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!" -).launch() -``` - - - +# Intégrations avec le Hub d'Hugging Face + + + +Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. +Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*. + +### Chargement de modèles depuis le Hub d'Hugging Face + +Pour commencer, choisissez un des milliers de modèles qu'*Hugging Face* offre à travers le *Hub*, comme décrit dans le [chapitre 4](/course/fr/chapter4/2). + +En utilisant la méthode spéciale `Interface.load()`, vous passez `"model/"` (ou, de manière équivalente, `"huggingface/"`) suivi du nom du modèle. +Par exemple, voici le code pour construire une démo pour le [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), un grand modèle de langue, ajouter quelques exemples d'entrées : + +```py +import gradio as gr + +title = "GPT-J-6B" +description = "Gradio Demo for GPT-J 6B, a transformer model trained using Ben Wang's Mesh Transformer JAX. 'GPT-J' refers to the class of model, while '6B' represents the number of trainable parameters. To use it, simply add your text, or click one of the examples to load them. Read more at the links below." +# Démo Gradio pour GPT-J 6B, un modèle de transformer entraîné avec le Mesh Transformer JAX de Ben Wang. GPT-J fait référence à la classe du modèle, tandis que '6B' représente le nombre de paramètres entraînables. Pour l'utiliser, il suffit d'ajouter votre texte, ou de cliquer sur l'un des exemples pour le charger. Pour en savoir plus, consultez les liens ci-dessous. +article = "

GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model

" +# GPT-J-6B : Un modèle linguistique autorégressif à 6 milliards de paramètres +examples = [ + ["The tower is 324 metres (1,063 ft) tall,"], + # La tour mesure 324 mètres (1 063 pieds) de haut, + ["The Moon's orbit around Earth has"], + # L'orbite de la Lune autour de la Terre a + ["The smooth Borealis basin in the Northern Hemisphere covers 40%"], + # Le bassin de Borealis dans l'hémisphère nord couvre 40 %. +] +gr.Interface.load( + "huggingface/EleutherAI/gpt-j-6B", + inputs=gr.Textbox(lines=5, label="Input Text"), + title=title, + description=description, + article=article, + examples=examples, + enable_queue=True, +).launch() +``` + +Le code ci-dessus produira l'interface ci-dessous : + + + +Le chargement d'un modèle de cette manière utilise l'[API d'Inference](https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM. + +### Chargement depuis Hugging Face Spaces + +Pour charger n'importe quel *Space* depuis le *Hub* et le recréer localement, vous pouvez passer `spaces/` à l'`Interface`, suivi du nom du *Space*. + +Vous vous souvenez de la démo de la section 1 qui supprime le fond d'une image ? Chargeons-la à partir de *Hugging Face Spaces* : + +```py +gr.Interface.load("spaces/abidlabs/remove-bg").launch() +``` + + + +L'un des avantages du chargement de démos à partir du *Hub* ou de *Spaces* est que vous pouvez les personnaliser en remplaçant n'importe lequel des paramètres. Ici, nous ajoutons un titre et faisons en sorte qu'elle fonctionne avec une webcam à la place : + +```py +gr.Interface.load( + "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!" +).launch() +``` + + + Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section ! \ No newline at end of file diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index c3ca388e8..273102f8c 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -1,138 +1,138 @@ -# Fonctionnalités avancées de l'interface - - - -Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. - -### Utilisation de l'état pour faire persister les données - -*Gradio* supporte *l'état de session* où les données persistent à travers plusieurs soumissions dans un chargement de page. L'état de session est utile pour construire des démos où vous souhaitez faire persister les données au fur et à mesure que l'utilisateur interagit avec le modèle (par exemple des chatbots). Notez que l'état de session ne partage pas les données entre les différents utilisateurs de votre modèle. - -Pour stocker des données dans un état de session, vous devez faire trois choses : - -- Passez un *paramètre supplémentaire* dans votre fonction, qui représente l'état de l'interface. -- A la fin de la fonction, renvoyer la valeur mise à jour de l'état comme une *valeur de retour supplémentaire*. -- Ajoutez les composants "state" input et "state" output lors de la création de votre `Interface`. - -Voir l'exemple de chatbot ci-dessous : - -```py -import random - -import gradio as gr - - -def chat(message, history): - history = history or [] - if message.startswith("How many"): - response = random.randint(1, 10) - elif message.startswith("How"): - response = random.choice(["Great", "Good", "Okay", "Bad"]) - elif message.startswith("Where"): - response = random.choice(["Here", "There", "Somewhere"]) - else: - response = "I don't know" - history.append((message, response)) - return history, history - - -iface = gr.Interface( - chat, - ["text", "state"], - ["chatbot", "state"], - allow_screenshot=False, - allow_flagging="never", -) -iface.launch() -``` - - -Remarquez comment l'état du composant de sortie persiste entre les soumissions. -Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. - -### Utilisation de l'interprétation pour comprendre les prédictions - -La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classificateur d'images incluant l'interprétation : - -```py -import requests -import tensorflow as tf - -import gradio as gr - -inception_net = tf.keras.applications.MobileNetV2() # charger le modèle - -# Télécharger des étiquettes lisibles par l'homme pour ImageNet -response = requests.get("https://git.io/JJkYN") -labels = response.text.split("\n") - - -def classify_image(inp): - inp = inp.reshape((-1, 224, 224, 3)) - inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp) - prediction = inception_net.predict(inp).flatten() - return {labels[i]: float(prediction[i]) for i in range(1000)} - - -image = gr.Image(shape=(224, 224)) -label = gr.Label(num_top_classes=3) - -title = "Gradio Image Classifiction + Interpretation Example" -gr.Interface( - fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title -).launch() -``` - -Testez la fonction d'interprétation en soumettant une entrée puis en cliquant sur « Interpréter » sous le composant de sortie. - - - -En plus de la méthode d'interprétation par défaut fournie par *Gradio*, vous pouvez également spécifier `shap` pour le paramètre `interpretation` et définir le paramètre `num_shap`. Ceci utilise l'interprétation basée sur Shapley, dont vous pouvez lire plus sur [ici](https://christophm.github.io/interpretable-ml-book/shap.html). -Enfin, vous pouvez aussi passer votre propre fonction d'interprétation dans le paramètre `interpretation`. Vous trouverez un exemple dans la page de démarrage de *Gradio* [ici](https://gradio.app/getting_started/). - - -### Ajouter l'authentification - -Vous pouvez vouloir ajouter une authentification à votre interface *Gradio* afin de contrôler qui peut accéder et utiliser votre démo. - -L'authentification peut être ajoutée en fournissant une liste de tuples de nom d'utilisateur/mot de passe au paramètre `auth` de la méthode `launch()`. Pour une gestion plus complexe de l'authentification, vous pouvez passer une fonction qui prend un nom d'utilisateur et un mot de passe comme arguments, et retourne `True` pour permettre l'authentification, `False` sinon. - -Prenons la démo de classification d'images ci-dessus et ajoutons l'authentification : - -```py -import requests -import tensorflow as tf - -import gradio as gr - -inception_net = tf.keras.applications.MobileNetV2() # charger le modèle - -# Télécharger des étiquettes lisibles par l'homme pour ImageNet -response = requests.get("https://git.io/JJkYN") -labels = response.text.split("\n") - - -def classify_image(inp): - inp = inp.reshape((-1, 224, 224, 3)) - inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp) - prediction = inception_net.predict(inp).flatten() - return {labels[i]: float(prediction[i]) for i in range(1000)} - - -image = gr.Image(shape=(224, 224)) -label = gr.Label(num_top_classes=3) - -title = "Gradio Image Classifiction + Interpretation Example" -gr.Interface( - fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title -).launch(auth=("admin", "pass1234")) -``` - - - +# Fonctionnalités avancées de l'interface + + + +Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. + +### Utilisation de l'état pour faire persister les données + +*Gradio* supporte *l'état de session* où les données persistent à travers plusieurs soumissions dans un chargement de page. L'état de session est utile pour construire des démos où vous souhaitez faire persister les données au fur et à mesure que l'utilisateur interagit avec le modèle (par exemple des chatbots). Notez que l'état de session ne partage pas les données entre les différents utilisateurs de votre modèle. + +Pour stocker des données dans un état de session, vous devez faire trois choses : + +- Passez un *paramètre supplémentaire* dans votre fonction, qui représente l'état de l'interface. +- A la fin de la fonction, renvoyer la valeur mise à jour de l'état comme une *valeur de retour supplémentaire*. +- Ajoutez les composants "state" input et "state" output lors de la création de votre `Interface`. + +Voir l'exemple de chatbot ci-dessous : + +```py +import random + +import gradio as gr + + +def chat(message, history): + history = history or [] + if message.startswith("How many"): + response = random.randint(1, 10) + elif message.startswith("How"): + response = random.choice(["Great", "Good", "Okay", "Bad"]) + elif message.startswith("Where"): + response = random.choice(["Here", "There", "Somewhere"]) + else: + response = "I don't know" + history.append((message, response)) + return history, history + + +iface = gr.Interface( + chat, + ["text", "state"], + ["chatbot", "state"], + allow_screenshot=False, + allow_flagging="never", +) +iface.launch() +``` + + +Remarquez comment l'état du composant de sortie persiste entre les soumissions. +Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. + +### Utilisation de l'interprétation pour comprendre les prédictions + +La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classificateur d'images incluant l'interprétation : + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # charger le modèle + +# Télécharger des étiquettes lisibles par l'homme pour ImageNet +response = requests.get("https://git.io/JJkYN") +labels = response.text.split("\n") + + +def classify_image(inp): + inp = inp.reshape((-1, 224, 224, 3)) + inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp) + prediction = inception_net.predict(inp).flatten() + return {labels[i]: float(prediction[i]) for i in range(1000)} + + +image = gr.Image(shape=(224, 224)) +label = gr.Label(num_top_classes=3) + +title = "Gradio Image Classifiction + Interpretation Example" +gr.Interface( + fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title +).launch() +``` + +Testez la fonction d'interprétation en soumettant une entrée puis en cliquant sur « Interpréter » sous le composant de sortie. + + + +En plus de la méthode d'interprétation par défaut fournie par *Gradio*, vous pouvez également spécifier `shap` pour le paramètre `interpretation` et définir le paramètre `num_shap`. Ceci utilise l'interprétation basée sur Shapley, dont vous pouvez lire plus sur [ici](https://christophm.github.io/interpretable-ml-book/shap.html). +Enfin, vous pouvez aussi passer votre propre fonction d'interprétation dans le paramètre `interpretation`. Vous trouverez un exemple dans la page de démarrage de *Gradio* [ici](https://gradio.app/getting_started/). + + +### Ajouter l'authentification + +Vous pouvez vouloir ajouter une authentification à votre interface *Gradio* afin de contrôler qui peut accéder et utiliser votre démo. + +L'authentification peut être ajoutée en fournissant une liste de tuples de nom d'utilisateur/mot de passe au paramètre `auth` de la méthode `launch()`. Pour une gestion plus complexe de l'authentification, vous pouvez passer une fonction qui prend un nom d'utilisateur et un mot de passe comme arguments, et retourne `True` pour permettre l'authentification, `False` sinon. + +Prenons la démo de classification d'images ci-dessus et ajoutons l'authentification : + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # charger le modèle + +# Télécharger des étiquettes lisibles par l'homme pour ImageNet +response = requests.get("https://git.io/JJkYN") +labels = response.text.split("\n") + + +def classify_image(inp): + inp = inp.reshape((-1, 224, 224, 3)) + inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp) + prediction = inception_net.predict(inp).flatten() + return {labels[i]: float(prediction[i]) for i in range(1000)} + + +image = gr.Image(shape=(224, 224)) +label = gr.Label(num_top_classes=3) + +title = "Gradio Image Classifiction + Interpretation Example" +gr.Interface( + fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title +).launch(auth=("admin", "pass1234")) +``` + + + Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section. \ No newline at end of file diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx index 8c66d2231..1f33cb1a7 100644 --- a/chapters/fr/chapter9/7.mdx +++ b/chapters/fr/chapter9/7.mdx @@ -1,240 +1,240 @@ -# Introduction à la classe Blocks - - - -Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. - -Quelle est la différence entre `Interface` et `Blocks` ? - -- ⚡ `Interface` : une API de haut niveau qui vous permet de créer une démo complète d'apprentissage automatique simplement en fournissant une liste d'entrées et de sorties. - -- 🧱 `Blocks` : une API de bas niveau qui vous permet d'avoir un contrôle total sur les flux de données et la disposition de votre application. Vous pouvez construire des applications très complexes, en plusieurs étapes, en utilisant `Blocks`. - - -### Pourquoi Blocks 🧱 ? - -Comme nous l'avons vu dans les sections précédentes, la classe `Interface` vous permet de créer facilement des démos d'apprentissage automatique à part entière avec seulement quelques lignes de code. L'API `Interface` est extrêmement facile à utiliser, mais elle n'a pas la flexibilité qu'offre l'API `Blocks`. Par exemple, vous pourriez vouloir : - -- regrouper des démos connexes sous forme d'onglets multiples dans une application web, -- modifier la mise en page de votre démo, par exemple pour spécifier l'emplacement des entrées et des sorties, -- disposer d'interfaces multi-étapes dans lesquelles la sortie d'un modèle devient l'entrée du modèle suivant ou avoir des flux de données plus flexibles en général, -- modifier les propriétés d'un composant (par exemple, les choix dans une liste déroulante) ou sa visibilité en fonction des entrées de l'utilisateur. - -Nous allons explorer tous ces concepts ci-dessous. - -### Création d'une démo simple en utilisant Blocks - -Après avoir installé *Gradio*, exécutez le code ci-dessous sous forme de script Python, de *notebook* Jupyter ou de *notebook* Colab. - -```py -import gradio as gr - - -def flip_text(x): - return x[::-1] - - -demo = gr.Blocks() - -with demo: - gr.Markdown( - """ - # Flip Text! - Start typing below to see the output. - """ - ) - input = gr.Textbox(placeholder="Flip this text") - output = gr.Textbox() - - input.change(fn=flip_text, inputs=input, outputs=output) - -demo.launch() -``` - - - -Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* : - -1. Les *Blocks* vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`. - - -🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent tutoriel de Real Python. Revenez ici après l'avoir lu 🤗 - - -L'ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l'application Web dans l'ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous) - -2. Vous pouvez définir des fonctions Python ordinaires n'importe où dans votre code et les exécuter avec des entrées utilisateur en utilisant les `Blocks`. Dans notre exemple, nous avons une fonction simple qui inverse le texte entré mais vous pouvez écrire n'importe quelle fonction Python, du simple calcul au traitement des prédictions d'un modèle d'apprentissage automatique. - -3. Vous pouvez assigner des événements à n'importe quel composant `Blocks`. Ainsi, votre fonction sera exécutée lorsque le composant sera cliqué, modifié, etc. Lorsque vous assignez un événement, vous passez trois paramètres : -- `fn` : la fonction qui doit être appelée, -- `inputs` : la (liste) des composants d'entrée -- `outputs` : la (liste) des composants de sortie qui doivent être appelés. - Dans l'exemple ci-dessus, nous exécutons la fonction `flip_text()` lorsque la valeur de la `Textbox` nommée input `input` change. L'événement lit la valeur dans `input`, la passe comme paramètre de nom à `flip_text()`, qui renvoie alors une valeur qui est assignée à notre seconde `Textbox` nommée `output`. - Pour voir la liste des événements que chaque composant supporte, consultez la [documentation](https://www.gradio.app/docs/) de *Gradio*. - -4. *Blocks* détermine automatiquement si un composant doit être interactif (accepter les entrées de l'utilisateur) ou non, en fonction des déclencheurs d'événements que vous définissez. Dans notre exemple, la première zone de texte est interactive, puisque sa valeur est utilisée par la fonction `flip_text()`. La deuxième zone de texte n'est pas interactive, puisque sa valeur n'est jamais utilisée comme entrée. Dans certains cas, vous voudrez peut-être passer outre, ce que vous pouvez faire en passant un booléen au paramètre `interactive` du composant (par exemple, `gr.Textbox(placeholder="Flip this text", interactive=True)`). - - -### Personnaliser la mise en page de votre démo - -Comment pouvons-nous utiliser `Blocks` pour personnaliser la mise en page de notre démo ? Par défaut, `Blocks` affiche verticalement dans une colonne les composants que vous créez. Vous pouvez changer cela en créant des colonnes supplémentaires `avec gradio.Column():` ou des lignes `avec gradio.Row():` et en créant des composants dans ces contextes. - -Voici ce que vous devez garder à l'esprit : tout composant créé sous une `Column` (c'est aussi le défaut) sera disposé verticalement. Tout composant créé sous une `Row` sera disposé horizontalement, comme le [modèle flexbox dans le développement web](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox). - -Enfin, vous pouvez également créer des onglets pour votre démo en utilisant le gestionnaire de contexte `with gradio.Tabs()`. Dans ce contexte, vous pouvez créer plusieurs onglets en spécifiant des enfants `with gradio.TabItem(name_of_tab):`. Tout composant créé dans un contexte `with gradio.TabItem(name_of_tab):` apparaît dans cet onglet. - -Maintenant, ajoutons une fonction `flip_image()` à notre démo et ajoutons un nouvel onglet qui retourne les images. Vous trouverez ci-dessous un exemple avec 2 onglets et utilisant également une `Row` : - -```py -import numpy as np -import gradio as gr - -demo = gr.Blocks() - - -def flip_text(x): - return x[::-1] - - -def flip_image(x): - return np.fliplr(x) - - -with demo: - gr.Markdown("Flip text or image files using this demo.") - with gr.Tabs(): - with gr.TabItem("Flip Text"): - with gr.Row(): - text_input = gr.Textbox() - text_output = gr.Textbox() - text_button = gr.Button("Flip") - with gr.TabItem("Flip Image"): - with gr.Row(): - image_input = gr.Image() - image_output = gr.Image() - image_button = gr.Button("Flip") - - text_button.click(flip_text, inputs=text_input, outputs=text_output) - image_button.click(flip_image, inputs=image_input, outputs=image_output) - -demo.launch() -``` - - - - -Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l'élément qui exécute réellement la fonction. - -### Exploration des événements et de l'état - -De la même manière que vous pouvez contrôler la mise en page, `Blocks` vous donne un contrôle fin sur les événements qui déclenchent les appels de fonction. Chaque composant et de nombreux layouts ont des événements spécifiques qu'ils supportent. - -Par exemple, le composant `Textbox` a 2 événements : `change()` (lorsque la valeur contenue dans la zone de texte change), et `submit()` (lorsqu'un utilisateur appuie sur la touche Entrée alors qu'il est concentré sur la zone de texte). Les composants plus complexes peuvent avoir encore plus d'événements : par exemple, le composant `Audio` a aussi des événements séparés pour quand le fichier audio est joué, effacé, mis en pause, etc. Consultez la documentation pour connaître les événements pris en charge par chaque composant. - -Vous pouvez attacher un déclencheur d'événement à aucun, un ou plusieurs de ces événements. Vous créez un déclencheur d'événement en appelant le nom de l'événement sur l'instance du composant comme une fonction. Par exemple, `textbox.change(...)` ou `btn.click(...)`. La fonction prend trois paramètres, comme indiqué ci-dessus : - -- `fn` : la fonction à exécuter -- `inputs` : une (liste de) composante(s) dont les valeurs doivent être fournies comme paramètres d'entrée à la fonction. La valeur de chaque composant est mise en correspondance avec le paramètre de fonction correspondant, dans l'ordre. Ce paramètre peut être `None` si la fonction ne prend aucun paramètre. -- `outputs` : un (liste de) composant(s) dont les valeurs doivent être mises à jour en fonction des valeurs retournées par la fonction. Chaque valeur de retour met à jour la valeur du composant correspondant, dans l'ordre. Ce paramètre peut être None si la fonction ne retourne rien. - -Vous pouvez même faire en sorte que le composant d'entrée et de sortie soit le même composant, comme nous le faisons dans cet exemple qui utilise un modèle GPT pour compléter du texte : - -```py -import gradio as gr - -api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") - - -def complete_with_gpt(text): - # Utilise les 50 derniers caractères du texte comme contexte. - return text[:-50] + api(text[-50:]) - - -with gr.Blocks() as demo: - textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4) - btn = gr.Button("Generate") - - btn.click(complete_with_gpt, textbox, textbox) - -demo.launch() -``` - - - -### Création de démos multi-étapes - -Dans certains cas, vous pouvez vouloir une _démo multi-étapes_, dans laquelle vous réutilisez la sortie d'une fonction comme entrée de la suivante. C'est très facile à faire avec les `Blocks`, car vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre. Regardez le composant texte dans l'exemple ci-dessous, sa valeur est le résultat d'un modèle de conversion de la parole en texte, mais il est également transmis à un modèle d'analyse des sentiments : - -```py -from transformers import pipeline - -import gradio as gr - -asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h") -classifier = pipeline("text-classification") - - -def speech_to_text(speech): - text = asr(speech)["text"] - return text - - -def text_to_sentiment(text): - return classifier(text)[0]["label"] - - -demo = gr.Blocks() - -with demo: - audio_file = gr.Audio(type="filepath") - text = gr.Textbox() - label = gr.Label() - - b1 = gr.Button("Recognize Speech") - b2 = gr.Button("Classify Sentiment") - - b1.click(speech_to_text, inputs=audio_file, outputs=text) - b2.click(text_to_sentiment, inputs=text, outputs=label) - -demo.launch() -``` - - - -### Mise à jour des propriétés des composants - -Jusqu'à présent, nous avons vu comment créer des événements pour mettre à jour la valeur d'un autre composant. Mais que se passe-t-il si vous voulez modifier d'autres propriétés d'un composant, comme la visibilité d'une zone de texte ou les choix dans un groupe de boutons radio ? Vous pouvez le faire en renvoyant la méthode `update()` d'une classe de composant au lieu d'une valeur de retour normale de votre fonction. - -L'exemple le plus facile à illustrer est le suivant : - -```py -import gradio as gr - - -def change_textbox(choice): - if choice == "short": - return gr.Textbox.update(lines=2, visible=True) - elif choice == "long": - return gr.Textbox.update(lines=8, visible=True) - else: - return gr.Textbox.update(visible=False) - - -with gr.Blocks() as block: - radio = gr.Radio( - ["short", "long", "none"], label="What kind of essay would you like to write?" - ) - text = gr.Textbox(lines=2, interactive=True) - - radio.change(fn=change_textbox, inputs=radio, outputs=text) - block.launch() -``` - - - +# Introduction à la classe Blocks + + + +Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. + +Quelle est la différence entre `Interface` et `Blocks` ? + +- ⚡ `Interface` : une API de haut niveau qui vous permet de créer une démo complète d'apprentissage automatique simplement en fournissant une liste d'entrées et de sorties. + +- 🧱 `Blocks` : une API de bas niveau qui vous permet d'avoir un contrôle total sur les flux de données et la disposition de votre application. Vous pouvez construire des applications très complexes, en plusieurs étapes, en utilisant `Blocks`. + + +### Pourquoi Blocks 🧱 ? + +Comme nous l'avons vu dans les sections précédentes, la classe `Interface` vous permet de créer facilement des démos d'apprentissage automatique à part entière avec seulement quelques lignes de code. L'API `Interface` est extrêmement facile à utiliser, mais elle n'a pas la flexibilité qu'offre l'API `Blocks`. Par exemple, vous pourriez vouloir : + +- regrouper des démos connexes sous forme d'onglets multiples dans une application web, +- modifier la mise en page de votre démo, par exemple pour spécifier l'emplacement des entrées et des sorties, +- disposer d'interfaces multi-étapes dans lesquelles la sortie d'un modèle devient l'entrée du modèle suivant ou avoir des flux de données plus flexibles en général, +- modifier les propriétés d'un composant (par exemple, les choix dans une liste déroulante) ou sa visibilité en fonction des entrées de l'utilisateur. + +Nous allons explorer tous ces concepts ci-dessous. + +### Création d'une démo simple en utilisant Blocks + +Après avoir installé *Gradio*, exécutez le code ci-dessous sous forme de script Python, de *notebook* Jupyter ou de *notebook* Colab. + +```py +import gradio as gr + + +def flip_text(x): + return x[::-1] + + +demo = gr.Blocks() + +with demo: + gr.Markdown( + """ + # Flip Text! + Start typing below to see the output. + """ + ) + input = gr.Textbox(placeholder="Flip this text") + output = gr.Textbox() + + input.change(fn=flip_text, inputs=input, outputs=output) + +demo.launch() +``` + + + +Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* : + +1. Les *Blocks* vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`. + + +🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent tutoriel de Real Python. Revenez ici après l'avoir lu 🤗 + + +L'ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l'application Web dans l'ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous) + +2. Vous pouvez définir des fonctions Python ordinaires n'importe où dans votre code et les exécuter avec des entrées utilisateur en utilisant les `Blocks`. Dans notre exemple, nous avons une fonction simple qui inverse le texte entré mais vous pouvez écrire n'importe quelle fonction Python, du simple calcul au traitement des prédictions d'un modèle d'apprentissage automatique. + +3. Vous pouvez assigner des événements à n'importe quel composant `Blocks`. Ainsi, votre fonction sera exécutée lorsque le composant sera cliqué, modifié, etc. Lorsque vous assignez un événement, vous passez trois paramètres : +- `fn` : la fonction qui doit être appelée, +- `inputs` : la (liste) des composants d'entrée +- `outputs` : la (liste) des composants de sortie qui doivent être appelés. + Dans l'exemple ci-dessus, nous exécutons la fonction `flip_text()` lorsque la valeur de la `Textbox` nommée input `input` change. L'événement lit la valeur dans `input`, la passe comme paramètre de nom à `flip_text()`, qui renvoie alors une valeur qui est assignée à notre seconde `Textbox` nommée `output`. + Pour voir la liste des événements que chaque composant supporte, consultez la [documentation](https://www.gradio.app/docs/) de *Gradio*. + +4. *Blocks* détermine automatiquement si un composant doit être interactif (accepter les entrées de l'utilisateur) ou non, en fonction des déclencheurs d'événements que vous définissez. Dans notre exemple, la première zone de texte est interactive, puisque sa valeur est utilisée par la fonction `flip_text()`. La deuxième zone de texte n'est pas interactive, puisque sa valeur n'est jamais utilisée comme entrée. Dans certains cas, vous voudrez peut-être passer outre, ce que vous pouvez faire en passant un booléen au paramètre `interactive` du composant (par exemple, `gr.Textbox(placeholder="Flip this text", interactive=True)`). + + +### Personnaliser la mise en page de votre démo + +Comment pouvons-nous utiliser `Blocks` pour personnaliser la mise en page de notre démo ? Par défaut, `Blocks` affiche verticalement dans une colonne les composants que vous créez. Vous pouvez changer cela en créant des colonnes supplémentaires `avec gradio.Column():` ou des lignes `avec gradio.Row():` et en créant des composants dans ces contextes. + +Voici ce que vous devez garder à l'esprit : tout composant créé sous une `Column` (c'est aussi le défaut) sera disposé verticalement. Tout composant créé sous une `Row` sera disposé horizontalement, comme le [modèle flexbox dans le développement web](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox). + +Enfin, vous pouvez également créer des onglets pour votre démo en utilisant le gestionnaire de contexte `with gradio.Tabs()`. Dans ce contexte, vous pouvez créer plusieurs onglets en spécifiant des enfants `with gradio.TabItem(name_of_tab):`. Tout composant créé dans un contexte `with gradio.TabItem(name_of_tab):` apparaît dans cet onglet. + +Maintenant, ajoutons une fonction `flip_image()` à notre démo et ajoutons un nouvel onglet qui retourne les images. Vous trouverez ci-dessous un exemple avec 2 onglets et utilisant également une `Row` : + +```py +import numpy as np +import gradio as gr + +demo = gr.Blocks() + + +def flip_text(x): + return x[::-1] + + +def flip_image(x): + return np.fliplr(x) + + +with demo: + gr.Markdown("Flip text or image files using this demo.") + with gr.Tabs(): + with gr.TabItem("Flip Text"): + with gr.Row(): + text_input = gr.Textbox() + text_output = gr.Textbox() + text_button = gr.Button("Flip") + with gr.TabItem("Flip Image"): + with gr.Row(): + image_input = gr.Image() + image_output = gr.Image() + image_button = gr.Button("Flip") + + text_button.click(flip_text, inputs=text_input, outputs=text_output) + image_button.click(flip_image, inputs=image_input, outputs=image_output) + +demo.launch() +``` + + + + +Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l'élément qui exécute réellement la fonction. + +### Exploration des événements et de l'état + +De la même manière que vous pouvez contrôler la mise en page, `Blocks` vous donne un contrôle fin sur les événements qui déclenchent les appels de fonction. Chaque composant et de nombreux layouts ont des événements spécifiques qu'ils supportent. + +Par exemple, le composant `Textbox` a 2 événements : `change()` (lorsque la valeur contenue dans la zone de texte change), et `submit()` (lorsqu'un utilisateur appuie sur la touche Entrée alors qu'il est concentré sur la zone de texte). Les composants plus complexes peuvent avoir encore plus d'événements : par exemple, le composant `Audio` a aussi des événements séparés pour quand le fichier audio est joué, effacé, mis en pause, etc. Consultez la documentation pour connaître les événements pris en charge par chaque composant. + +Vous pouvez attacher un déclencheur d'événement à aucun, un ou plusieurs de ces événements. Vous créez un déclencheur d'événement en appelant le nom de l'événement sur l'instance du composant comme une fonction. Par exemple, `textbox.change(...)` ou `btn.click(...)`. La fonction prend trois paramètres, comme indiqué ci-dessus : + +- `fn` : la fonction à exécuter +- `inputs` : une (liste de) composante(s) dont les valeurs doivent être fournies comme paramètres d'entrée à la fonction. La valeur de chaque composant est mise en correspondance avec le paramètre de fonction correspondant, dans l'ordre. Ce paramètre peut être `None` si la fonction ne prend aucun paramètre. +- `outputs` : un (liste de) composant(s) dont les valeurs doivent être mises à jour en fonction des valeurs retournées par la fonction. Chaque valeur de retour met à jour la valeur du composant correspondant, dans l'ordre. Ce paramètre peut être None si la fonction ne retourne rien. + +Vous pouvez même faire en sorte que le composant d'entrée et de sortie soit le même composant, comme nous le faisons dans cet exemple qui utilise un modèle GPT pour compléter du texte : + +```py +import gradio as gr + +api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") + + +def complete_with_gpt(text): + # Utilise les 50 derniers caractères du texte comme contexte. + return text[:-50] + api(text[-50:]) + + +with gr.Blocks() as demo: + textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4) + btn = gr.Button("Generate") + + btn.click(complete_with_gpt, textbox, textbox) + +demo.launch() +``` + + + +### Création de démos multi-étapes + +Dans certains cas, vous pouvez vouloir une _démo multi-étapes_, dans laquelle vous réutilisez la sortie d'une fonction comme entrée de la suivante. C'est très facile à faire avec les `Blocks`, car vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre. Regardez le composant texte dans l'exemple ci-dessous, sa valeur est le résultat d'un modèle de conversion de la parole en texte, mais il est également transmis à un modèle d'analyse des sentiments : + +```py +from transformers import pipeline + +import gradio as gr + +asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h") +classifier = pipeline("text-classification") + + +def speech_to_text(speech): + text = asr(speech)["text"] + return text + + +def text_to_sentiment(text): + return classifier(text)[0]["label"] + + +demo = gr.Blocks() + +with demo: + audio_file = gr.Audio(type="filepath") + text = gr.Textbox() + label = gr.Label() + + b1 = gr.Button("Recognize Speech") + b2 = gr.Button("Classify Sentiment") + + b1.click(speech_to_text, inputs=audio_file, outputs=text) + b2.click(text_to_sentiment, inputs=text, outputs=label) + +demo.launch() +``` + + + +### Mise à jour des propriétés des composants + +Jusqu'à présent, nous avons vu comment créer des événements pour mettre à jour la valeur d'un autre composant. Mais que se passe-t-il si vous voulez modifier d'autres propriétés d'un composant, comme la visibilité d'une zone de texte ou les choix dans un groupe de boutons radio ? Vous pouvez le faire en renvoyant la méthode `update()` d'une classe de composant au lieu d'une valeur de retour normale de votre fonction. + +L'exemple le plus facile à illustrer est le suivant : + +```py +import gradio as gr + + +def change_textbox(choice): + if choice == "short": + return gr.Textbox.update(lines=2, visible=True) + elif choice == "long": + return gr.Textbox.update(lines=8, visible=True) + else: + return gr.Textbox.update(visible=False) + + +with gr.Blocks() as block: + radio = gr.Radio( + ["short", "long", "none"], label="What kind of essay would you like to write?" + ) + text = gr.Textbox(lines=2, interactive=True) + + radio.change(fn=change_textbox, inputs=radio, outputs=text) + block.launch() +``` + + + Nous venons d'explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur [*Spaces*](https://huggingface.co/spaces). \ No newline at end of file diff --git a/chapters/fr/chapter9/8.mdx b/chapters/fr/chapter9/8.mdx index 418e389e6..307e0c813 100644 --- a/chapters/fr/chapter9/8.mdx +++ b/chapters/fr/chapter9/8.mdx @@ -1,5 +1,10 @@ # Gradio, coché ! + + Ceci conclut le chapitre sur la construction de démos d'apprentissage automatique avec *Gradio*. Nous espérons que vous l'avez apprécié ! Pour récapituler, dans ce chapitre nous avons appris : - à créer des démos *Gradio* avec l'API `Interface` de haut niveau et comment configurer les différentes modalités d'entrée et de sortie, diff --git a/chapters/fr/chapter9/9.mdx b/chapters/fr/chapter9/9.mdx index 2b6e859a2..0f4169307 100644 --- a/chapters/fr/chapter9/9.mdx +++ b/chapters/fr/chapter9/9.mdx @@ -1,233 +1,238 @@ - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Que pouvez-vous faire avec Gradio ? - -share=True dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.", - correct: true - }, - { - text: "Déboguez votre modèle.", - explain: "L'un des avantages d'une démo Gradio est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.", - correct: true - }, - { - text: "Entraîner votre modèle.", - explain: "Gradio est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.", - } - ]} -/> - -### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch - -Gradio fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !" - }, - { - text: "Faux", - explain: "Gradio est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.", - correct: true - } - ]} -/> - -### 3. D'où pouvez-vous lancer une démo Gradio ? - -Gradio fonctionne parfaitement avec votre IDE préféré.", - correct: true - }, - { - text: "De notebooks Google Colab", - explain: "Vous pouvez créer et lancer une démo dans votre notebook Google Colab.", - correct: true - }, - { - text: "De notebooks Jupyter", - explain: "Vous pouvez créer et lancer une démo dans votre notebook Jupyter.", - correct: true - } - ]} -/> - -### 4. Gradio est conçu principalement pour les modèles de NLP - -Gradio fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP." - }, - { - text: "Faux", - explain: "Gradio fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.", - correct: true - } - ]} -/> - -### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par Gradio ? - -Gradio. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.", - correct: true - }, - { - text: "État pour la persistance des données.", - explain: "Gradio est capable d'ajouter un état à votre interface.", - correct: true - }, - { - text: "Authentification par nom d'utilisateur et mot de passe.", - explain: "Passez une liste de tuples de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.", - correct: true - }, - { - text: "Analyse automatique de l'utilisation de votre démo Gradio.", - explain: "Gradio ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos." - }, - { - text: "Chargement d'un modèle à partir du Hub ou de Space.", - explain: "Charger n'importe quel modèle de Hugging Face en utilisant la méthode gr.Interface.load().", - correct: true - } - ]} -/> - -### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du Hub ou de Space ? - -gr.Interface.load('huggingface/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", - correct: true - }, - { - text: "gr.Interface.load('model/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", - correct: true - }, - { - text: "gr.Interface.load('demos/{user}/{model_name}')", - explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe demos." - }, - { - text: "gr.Interface.load('spaces/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de Space.", - correct: true - } - ]} -/> - -### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface Gradio - -Gradio fournit un composant d'entrée et de sortie d'état pour persister les données.", - correct: true - } - ]} -/> - -### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque Gradio ? - -Textbox.", - explain: "Oui, vous pouvez créer des zones de texte avec le composant Textbox.", - correct: true - }, - { - text: "Graph.", - explain: "Il n'y a actuellement aucun composant Graph.", - }, - { - text: "Image.", - explain: "Oui, vous pouvez créer un widget de téléchargement d'images avec le composant Image.", - correct: true - }, - { - text: "Audio.", - explain: "Oui, vous pouvez créer un widget de téléchargement audio avec le composant Audio.", - correct: true - }, - ]} -/> - -### 9. Qu'est-ce que les `Blocks` vous permet de faire ? - -with gradio.Tabs(): pour ajouter des onglets pour plusieurs démos.", - correct: true - }, - { - text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants Blocks.", - explain: "Lorsque vous assignez un événement, vous passez trois paramètres : fn qui est la fonction qui doit être appelée, inputs qui est la (liste) des composants d'entrée, et outputs qui est la (liste) des composants de sortie qui doivent être appelés.", - correct: true - }, - { - text: "Déterminer automatiquement quel composant Blocks doit être interactif ou statique.", - explain: "En fonction des déclencheurs d'événements que vous définissez, Blocks détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..", - correct: true - }, - { - text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.", - explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.", - correct: true - }, - ]} -/> - -### 10. Vous pouvez partager un lien public vers une démo Blocks et accueillir une démo Blocks sur Space - -Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", - correct: true - }, - { - text: "Faux", - explain: "Tout comme Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", - } - ]} + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Que pouvez-vous faire avec Gradio ? + +share=True dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.", + correct: true + }, + { + text: "Déboguez votre modèle.", + explain: "L'un des avantages d'une démo Gradio est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.", + correct: true + }, + { + text: "Entraîner votre modèle.", + explain: "Gradio est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.", + } + ]} +/> + +### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch + +Gradio fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !" + }, + { + text: "Faux", + explain: "Gradio est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.", + correct: true + } + ]} +/> + +### 3. D'où pouvez-vous lancer une démo Gradio ? + +Gradio fonctionne parfaitement avec votre IDE préféré.", + correct: true + }, + { + text: "De notebooks Google Colab", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Google Colab.", + correct: true + }, + { + text: "De notebooks Jupyter", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Jupyter.", + correct: true + } + ]} +/> + +### 4. Gradio est conçu principalement pour les modèles de NLP + +Gradio fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP." + }, + { + text: "Faux", + explain: "Gradio fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.", + correct: true + } + ]} +/> + +### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par Gradio ? + +Gradio. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.", + correct: true + }, + { + text: "État pour la persistance des données.", + explain: "Gradio est capable d'ajouter un état à votre interface.", + correct: true + }, + { + text: "Authentification par nom d'utilisateur et mot de passe.", + explain: "Passez une liste de tuples de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.", + correct: true + }, + { + text: "Analyse automatique de l'utilisation de votre démo Gradio.", + explain: "Gradio ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos." + }, + { + text: "Chargement d'un modèle à partir du Hub ou de Space.", + explain: "Charger n'importe quel modèle de Hugging Face en utilisant la méthode gr.Interface.load().", + correct: true + } + ]} +/> + +### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du Hub ou de Space ? + +gr.Interface.load('huggingface/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('model/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('demos/{user}/{model_name}')", + explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe demos." + }, + { + text: "gr.Interface.load('spaces/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de Space.", + correct: true + } + ]} +/> + +### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface Gradio + +Gradio fournit un composant d'entrée et de sortie d'état pour persister les données.", + correct: true + } + ]} +/> + +### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque Gradio ? + +Textbox.", + explain: "Oui, vous pouvez créer des zones de texte avec le composant Textbox.", + correct: true + }, + { + text: "Graph.", + explain: "Il n'y a actuellement aucun composant Graph.", + }, + { + text: "Image.", + explain: "Oui, vous pouvez créer un widget de téléchargement d'images avec le composant Image.", + correct: true + }, + { + text: "Audio.", + explain: "Oui, vous pouvez créer un widget de téléchargement audio avec le composant Audio.", + correct: true + }, + ]} +/> + +### 9. Qu'est-ce que les `Blocks` vous permet de faire ? + +with gradio.Tabs(): pour ajouter des onglets pour plusieurs démos.", + correct: true + }, + { + text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants Blocks.", + explain: "Lorsque vous assignez un événement, vous passez trois paramètres : fn qui est la fonction qui doit être appelée, inputs qui est la (liste) des composants d'entrée, et outputs qui est la (liste) des composants de sortie qui doivent être appelés.", + correct: true + }, + { + text: "Déterminer automatiquement quel composant Blocks doit être interactif ou statique.", + explain: "En fonction des déclencheurs d'événements que vous définissez, Blocks détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..", + correct: true + }, + { + text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.", + explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.", + correct: true + }, + ]} +/> + +### 10. Vous pouvez partager un lien public vers une démo Blocks et accueillir une démo Blocks sur Space + +Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + correct: true + }, + { + text: "Faux", + explain: "Tout comme Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + } + ]} /> \ No newline at end of file diff --git a/chapters/hi/chapter1/1.mdx b/chapters/hi/chapter1/1.mdx index 59b8aa076..8a4654ed9 100644 --- a/chapters/hi/chapter1/1.mdx +++ b/chapters/hi/chapter1/1.mdx @@ -1,5 +1,10 @@ # परिचय + + ## 🤗 पाठ्यक्रम में आपका स्वागत है! diff --git a/chapters/hi/chapter1/10.mdx b/chapters/hi/chapter1/10.mdx index eeb67fdab..6ef1bbed8 100644 --- a/chapters/hi/chapter1/10.mdx +++ b/chapters/hi/chapter1/10.mdx @@ -1,5 +1,10 @@ # अध्याय के अंत की प्रश्नोत्तरी + + इस अध्याय में बहुत सारी जमीन शामिल है! यदि आप सभी विवरणों को नहीं समझ पाए हैं तो चिंता न करें; अगले अध्याय आपको यह समझने में मदद करेंगे कि चीजें हुड के तहत कैसे काम करती हैं। लेकिन, आइए पहले यह जाँचें कि आपने इस अध्याय में क्या सीखा! diff --git a/chapters/hi/chapter1/2.mdx b/chapters/hi/chapter1/2.mdx index dd707c568..e3a7c498f 100644 --- a/chapters/hi/chapter1/2.mdx +++ b/chapters/hi/chapter1/2.mdx @@ -1,5 +1,10 @@ # प्राकृतिक भाषा प्रसंस्करण + + ट्रांसफॉर्मर मॉडल में जाने से पहले, आइए एक त्वरित अवलोकन करें कि प्राकृतिक भाषा प्रसंस्करण क्या है और हम इसकी परवाह क्यों करते हैं। ## प्राकृतिक भाषा प्रसंस्करण क्या है? diff --git a/chapters/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx index d40137645..13b9eb19d 100644 --- a/chapters/hi/chapter1/3.mdx +++ b/chapters/hi/chapter1/3.mdx @@ -1,8 +1,8 @@ # ट्रांसफार्मर, वे क्या कर सकते हैं? - diff --git a/chapters/hi/chapter1/4.mdx b/chapters/hi/chapter1/4.mdx index 63dd3e619..4f25dba15 100644 --- a/chapters/hi/chapter1/4.mdx +++ b/chapters/hi/chapter1/4.mdx @@ -1,5 +1,10 @@ # ट्रांसफॉर्मर कैसे काम करते हैं? + + इस खंड में, हम ट्रांसफॉर्मर मॉडल की वास्तुकला पर एक उच्च-स्तरीय नज़र डालेंगे। ## ट्रांसफार्मर का थोड़ा सा इतिहास diff --git a/chapters/hi/chapter1/5.mdx b/chapters/hi/chapter1/5.mdx index 0e1957c48..687b61913 100644 --- a/chapters/hi/chapter1/5.mdx +++ b/chapters/hi/chapter1/5.mdx @@ -1,5 +1,10 @@ # एनकोडर मॉडल + + एन्कोडर मॉडल केवल ट्रांसफ़ॉर्मर मॉडल के एन्कोडर का उपयोग करते हैं। प्रत्येक चरण में, ध्यान की परतें प्रारंभिक वाक्य में सभी शब्दों तक पहुंच सकती हैं। इन मॉडलों को अक्सर "द्वि-दिशात्मक" ध्यान देने के रूप में वर्णित किया जाता है, और इन्हें अक्सर *ऑटो-एन्कोडिंग मॉडल* कहा जाता है। diff --git a/chapters/hi/chapter1/6.mdx b/chapters/hi/chapter1/6.mdx index 9b0f245cd..cf0ae79ee 100644 --- a/chapters/hi/chapter1/6.mdx +++ b/chapters/hi/chapter1/6.mdx @@ -1,5 +1,10 @@ # डिकोडर मॉडल + + डिकोडर मॉडल केवल ट्रांसफॉर्मर मॉडल के डिकोडर का उपयोग करते हैं। प्रत्येक चरण में, किसी दिए गए शब्द के लिए ध्यान की परतें केवल वाक्य में उसके सामने स्थित शब्दों तक पहुंच सकती हैं। इन मॉडलों को अक्सर *स्वतः प्रतिगामी मॉडल* कहा जाता है। diff --git a/chapters/hi/chapter1/7.mdx b/chapters/hi/chapter1/7.mdx index d4fc23ae3..66b1991bf 100644 --- a/chapters/hi/chapter1/7.mdx +++ b/chapters/hi/chapter1/7.mdx @@ -1,5 +1,10 @@ # अनुक्रम-से-अनुक्रम मॉडल + + एनकोडर-डिकोडर मॉडल (जिसे *सीक्वेंस-टू-सीक्वेंस मॉडल* भी कहा जाता है) ट्रांसफॉर्मर आर्किटेक्चर के दोनों हिस्सों का उपयोग करते हैं। प्रत्येक चरण में, एन्कोडर की ध्यान परतें प्रारंभिक वाक्य में सभी शब्दों तक पहुंच सकती हैं, जबकि डिकोडर की ध्यान परतें केवल इनपुट में दिए गए शब्द से पहले स्थित शब्दों तक पहुंच सकती हैं। diff --git a/chapters/hi/chapter1/8.mdx b/chapters/hi/chapter1/8.mdx index 52e778528..1612cac3b 100644 --- a/chapters/hi/chapter1/8.mdx +++ b/chapters/hi/chapter1/8.mdx @@ -1,8 +1,8 @@ # पूर्वाग्रह और सीमाएं - diff --git a/chapters/hi/chapter1/9.mdx b/chapters/hi/chapter1/9.mdx index 56649cb6b..edad957d5 100644 --- a/chapters/hi/chapter1/9.mdx +++ b/chapters/hi/chapter1/9.mdx @@ -1,5 +1,10 @@ # सारांश + + इस अध्याय में, आपने देखा कि 🤗 ट्रांसफॉर्मर के उच्च-स्तरीय `पाइपलाइन ()` फ़ंक्शन का उपयोग करके विभिन्न प्राकृतिक भाषा प्रसंस्करण कार्यों को कैसे किया जाता है। आपने यह भी देखा कि हब में मॉडलों की खोज और उनका उपयोग कैसे करें, साथ ही सीधे अपने ब्राउज़र में मॉडलों का परीक्षण करने के लिए अनुमान API का उपयोग कैसे करें। हमने चर्चा की कि ट्रांसफॉर्मर मॉडल उच्च स्तर पर कैसे काम करते हैं और ट्रांसफर लर्निंग और फाइन-ट्यूनिंग के महत्व के बारे में बात की। एक महत्वपूर्ण पहलू यह है कि आप पूर्ण आर्किटेक्चर या केवल एन्कोडर या डिकोडर का उपयोग कर सकते हैं, यह इस बात पर निर्भर करता है कि आप किस प्रकार के कार्य को हल करना चाहते हैं। निम्न तालिका इसे सारांशित करती है: diff --git a/chapters/hi/chapter2/1.mdx b/chapters/hi/chapter2/1.mdx index a8c1e8265..df1e0e139 100644 --- a/chapters/hi/chapter2/1.mdx +++ b/chapters/hi/chapter2/1.mdx @@ -1,5 +1,10 @@ # परिचय + + जैसा कि आपने [अध्याय 1](/course/chapter1) में देखा, ट्रांसफार्मर मॉडल आमतौर पर बहुत बड़े होते हैं। लाखों से दसियों अरबों पैरामीटर्स के साथ, इन मॉडलों का प्रशिक्षण और डिप्लॉय करना एक पेचीदा उपक्रम है। इसके अलावा, नए मॉडल लगभग दैनिक आधार पर जारी किए जा रहे हैं और प्रत्येक का अपना कार्यान्वयन है, उन सभी को आज़माना कोई आसान काम नहीं है। इस समस्या को हल करने के लिए 🤗 ट्रांसफॉर्मर्स लाइब्रेरी बनाई गई थी। इसका लक्ष्य एक एपीआई प्रदान करना है जिसके माध्यम से किसी भी ट्रांसफार्मर मॉडल को लोड, प्रशिक्षित और सेव किया जा सकता है। पुस्तकालय की मुख्य विशेषताएं हैं: diff --git a/chapters/hi/chapter3/1.mdx b/chapters/hi/chapter3/1.mdx index 16e3b4710..d479163f4 100644 --- a/chapters/hi/chapter3/1.mdx +++ b/chapters/hi/chapter3/1.mdx @@ -2,6 +2,11 @@ # परिचय + + [अध्याय 2](/course/chapter2) में हमने जाना कि कैसे भविष्यवाणी करने के लिए टोकननाइज़र और पूर्व-प्रशिक्षित मॉडल का उपयोग किया जाता है । लेकिन तब क्या यदि आप अपने स्वयं के डेटासेट के लिए एक पूर्व-प्रशिक्षित मॉडल को ठीक करना चाहते हैं? यही इस अध्याय का विषय है! आप सीखेंगे कि: {#if fw === 'pt'} diff --git a/chapters/hi/chapter3/2.mdx b/chapters/hi/chapter3/2.mdx index 9105b30cf..da9ddb169 100644 --- a/chapters/hi/chapter3/2.mdx +++ b/chapters/hi/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/hi/chapter3/3.mdx b/chapters/hi/chapter3/3.mdx index 748824180..436949ddd 100644 --- a/chapters/hi/chapter3/3.mdx +++ b/chapters/hi/chapter3/3.mdx @@ -2,9 +2,9 @@ # मॉडल कि Trainer API के साथ - diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 837983be3..3954bb9f4 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # मॉडल कि फाइन-ट्यूनिंग Keras के साथ - diff --git a/chapters/hi/chapter3/4.mdx b/chapters/hi/chapter3/4.mdx index 181e3e0e5..652b78030 100644 --- a/chapters/hi/chapter3/4.mdx +++ b/chapters/hi/chapter3/4.mdx @@ -1,8 +1,8 @@ # एक पूर्ण प्रशिक्षण - diff --git a/chapters/hi/chapter3/5.mdx b/chapters/hi/chapter3/5.mdx index 817398e8d..06c485e56 100644 --- a/chapters/hi/chapter3/5.mdx +++ b/chapters/hi/chapter3/5.mdx @@ -2,6 +2,11 @@ # फाइन-ट्यूनिंग, चेक! + + काफी मजेदार था! पहले दो अध्यायों में आपने मॉडल और टोकननाइज़रस के बारे में सीखा, और अब आप जानते हैं कि उन्हें अपने डेटा के लिए कैसे ठीक यानि फाइन-ट्यून किया जाए। संक्षेप में, इस अध्याय में आपने: {#if fw === 'pt'} diff --git a/chapters/hi/chapter3/6.mdx b/chapters/hi/chapter3/6.mdx index 65fdd2e63..1cf4d5916 100644 --- a/chapters/hi/chapter3/6.mdx +++ b/chapters/hi/chapter3/6.mdx @@ -4,6 +4,11 @@ # अध्याय-का-अंत प्रश्नोत्तरी + + इस अध्याय में आपने जो सीखा, उसका परीक्षण करें! ### 1. `इमोशन` डेटासेट में ट्विटर संदेश है जिनहे भावनाओं के साथ लेबल किया गया है। इसे [हब](https://huggingface.co/datasets) में खोजें, और डेटासेट कार्ड पढ़ें। इनमें से कौन सा इसकी मूल भावनाओं में से एक नहीं है? diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 4174fa0f2..6802ccac3 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -23,7 +23,14 @@ title: Bias e limiti - local: chapter1/9 title: Riassunto - + +- title: 2. Usare i 🤗 Transformers + sections: + - local: chapter2/1 + title: Introduzione + - local: chapter2/2 + title: Dietro la pipeline + - title: 4. Condividere modelli e tokenizers sections: - local: chapter4/1 diff --git a/chapters/it/chapter1/1.mdx b/chapters/it/chapter1/1.mdx index 4fd68aa6a..a2258a521 100644 --- a/chapters/it/chapter1/1.mdx +++ b/chapters/it/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introduzione + + ## Benvenuto/a al corso di 🤗! diff --git a/chapters/it/chapter1/2.mdx b/chapters/it/chapter1/2.mdx index a5845ca54..e1d2eb53f 100644 --- a/chapters/it/chapter1/2.mdx +++ b/chapters/it/chapter1/2.mdx @@ -1,5 +1,10 @@ # Natural Language Processing + + Prima di tuffarci nei modelli Transformer, diamo un'occhiata rapida alla natura del natural language processing (*elaborazione del linguaggio naturale*) e alle ragioni per cui quest'ultimo ci interessa. ## Cosa intendiamo per NLP? diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 7fb506a94..681a54b9a 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -1,8 +1,8 @@ # Cosa fanno i Transformer? - diff --git a/chapters/it/chapter1/4.mdx b/chapters/it/chapter1/4.mdx index 8aa824f14..9d8ea8fd6 100644 --- a/chapters/it/chapter1/4.mdx +++ b/chapters/it/chapter1/4.mdx @@ -1,5 +1,10 @@ # Come funzionano i Transformer? + + In questa sezione, vedremo in maniera approfondita l'architettura dei modelli Transformer. ## Un po' di storia dei Transformer diff --git a/chapters/it/chapter1/5.mdx b/chapters/it/chapter1/5.mdx index 817c81463..7b839ec67 100644 --- a/chapters/it/chapter1/5.mdx +++ b/chapters/it/chapter1/5.mdx @@ -1,5 +1,10 @@ # Modelli encoder + + I modelli encoder utilizzano solo l'encoder di un modello Transformer. In ogni fase, gli attention layer hanno accesso a tutte le parole della frase di partenza. Questi modelli sono spesso caratterizzati come aventi attenzione "bi-direzionale" e chiamati *auto-encoding models*. diff --git a/chapters/it/chapter1/6.mdx b/chapters/it/chapter1/6.mdx index c9a7296a4..f9bf8a64a 100644 --- a/chapters/it/chapter1/6.mdx +++ b/chapters/it/chapter1/6.mdx @@ -1,5 +1,10 @@ # Modelli decoder + + I modelli decoder utilizzano solo il decoder di un modello Transformer. Ad ogni passaggio e per una data parola, gli attention layer hanno accesso solo alle parole che la precedono nella frase. Questi modelli sono spesso detti *auto-regressive models*. diff --git a/chapters/it/chapter1/7.mdx b/chapters/it/chapter1/7.mdx index a8c616c64..14d2e0a6a 100644 --- a/chapters/it/chapter1/7.mdx +++ b/chapters/it/chapter1/7.mdx @@ -1,5 +1,10 @@ # Modelli sequence-to-sequence + + I modelli encoder-decoder (detti anche modelli *sequence-to-sequence*) utilizzano entrambi i componenti dell'architettura Transformer. Ad ogni passaggio, gli attention layer dell'encoder hanno accesso a tutte le parole della frase iniziale, mentre gli attention layer del decoder possono solo accedere alle parole che precedono linearmente una data parola nell'input. diff --git a/chapters/it/chapter1/8.mdx b/chapters/it/chapter1/8.mdx index 9a68fed34..b2548fc64 100644 --- a/chapters/it/chapter1/8.mdx +++ b/chapters/it/chapter1/8.mdx @@ -1,8 +1,8 @@ # Bias e limiti - diff --git a/chapters/it/chapter1/9.mdx b/chapters/it/chapter1/9.mdx index 3b11de19c..577256a58 100644 --- a/chapters/it/chapter1/9.mdx +++ b/chapters/it/chapter1/9.mdx @@ -1,5 +1,10 @@ # Riassunto + + In questo capitolo, hai scoperto come approcciare diversi compiti di NLP utilizzando la funzione di alto livello `pipeline()` degli 🤗 Transformer. Abbiamo anche visto come cercare e utilizzare i modelli dell'Hub, nonché come usare l'Inference API per testare i modelli direttamente nel tuo browser. Abbiamo discusso di come i modelli Transformer lavorino a livello alto, e parlato dell'importanza del transfer learning e dell'affinamento. Un aspetto chiave è che è possibile utilizzare l'architettuta completa oppure solo l'encoder o il decoder, dipendentemente dal compito a cui desideri lavorare. La tabella seguente riordina questi concetti: diff --git a/chapters/it/chapter2/1.mdx b/chapters/it/chapter2/1.mdx new file mode 100644 index 000000000..d0579a712 --- /dev/null +++ b/chapters/it/chapter2/1.mdx @@ -0,0 +1,26 @@ +# Introduzione + + + +Come si è visto nel [Capitolo 1](/course/chapter1), I modelli Transformers sono solitamente molto grandi. +Con milioni o decine di *miliardi* di parametri, l'addestramento e la distribuzione di questi modelli è un'impresa complicata. +Inoltre, con i nuovi modelli che vengono rilasciati quasi ogni giorno e ognuno dei quali ha una propria implementazione, provarli tutti non è un lavoro facile. + +La libreria 🤗 Transformers è stata creata per risolvere questo problema. Il suo obiettivo è fornire un'unica API attraverso la quale caricare, addestrare e salvare qualsiasi modello Transformer. Le caratteristiche principali della libreria sono: + +- **Facilità d'uso**: È possibile scaricare, caricare ed utilizzare un modello NLP all'avanguardia per fare inferenza con appena due righe di codice. +- **Flessibilità**: Al loro interno, tutti i modelli sono semplici classi PyTorch `nn.Module` o TensorFlow `tf.keras.Model` e possono essere gestiti come qualsiasi altro modello nei rispettivi framework di apprendimento automatico (ML). +- **Semplicità**: La libreria non contiene quasi nessuna astrazione. Il concetto di "All in one file" è fondamentale: il forward pass di un modello è interamente definito in un singolo file, in modo che il codice stesso sia comprensibile e violabile. + +Quest'ultima caratteristica rende 🤗 Transformers molto diversi da altre librerie ML. I modelli non sono costruiti su moduli condivisi tra i file, ma ogni modello ha i propri layers. Oltre a rendere i modelli più accessibili e comprensibili, questo permette di sperimentare facilmente su un modello senza influenzare gli altri. + +Questo capitolo inizierà con un esempio in cui usiamo un modello e un tokenizer insieme per replicare la funzione `pipeline()` introdotta nel [Capitolo 1](/course/chapter1). Successivamente, parleremo dell'API del modello: ci immergeremo nelle classi del modello e della configurazione e mostreremo come caricare un modello e come esso elabora gli input numerici per produrre previsioni. + +Successivamente vedremo l'API del tokenizer, che è l'altro componente principale della funzione `pipeline()`. I tokenizer si occupano della prima e dell'ultima fase di elaborazione, gestendo la conversione da testo a input numerici per la rete neurale e la conversione di nuovo in testo quando è necessario. Infine, mostreremo come gestire l'invio di più frasi a un modello in un batch preparato, per poi concludere il tutto con un'analisi più approfondita della funzione di alto livello `tokenizer()`. + + +⚠️ Per poter usufruire di tutte le funzioni disponibili con il Model Hub e i 🤗 Transformers, si consiglia di creare un account. + \ No newline at end of file diff --git a/chapters/it/chapter2/2.mdx b/chapters/it/chapter2/2.mdx new file mode 100644 index 000000000..94fd67ebc --- /dev/null +++ b/chapters/it/chapter2/2.mdx @@ -0,0 +1,351 @@ + + +# Dietro la pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + Questa è la prima sezione in cui il contenuto è leggermente diverso a seconda che si utilizzi PyTorch o TensorFlow. Attivate lo switch sopra il titolo per selezionare la tua piattaforma preferita! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Cominciamo con un esempio completo, dando un'occhiata a ciò che è successo dietro le quinte quando abbiamo eseguito il seguente codice nel [Capitolo 1](/course/chapter1): + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +e ottenuto: + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Come abbiamo visto nel [Capitolo 1](/course/chapter1), questa pipeline raggruppa tre fasi: la pre-elaborazione, il passaggio degli input attraverso il modello e la post-elaborazione: + +
+La pipeline NLP completa: tokenizzazione del testo, conversione in ID e inferenza attraverso il modello Transformer ed il modello head. + +
+ +Esaminiamo rapidamente ciascuno di essi. + +## Preelaborazione con un tokenizer + +Come altre reti neurali, i modelli Transformer non possono elaborare direttamente il testo non elaborato, quindi la prima fase della nostra pipeline consiste nel convertire gli input testuali in numeri che il modello possa interpretare. Per fare ciò, utilizziamo un *tokenizer*, che sarà responsabile di: + +- Suddivisione dell'input in parole, sottoparole o simboli (come la punteggiatura) che vengono chiamati *token*. +- Mappare ogni token in un numero intero +- Aggiunta di ulteriori input che possono essere utili per il modello + +Tutta questa preelaborazione deve essere fatta esattamente nello stesso modo in cui è stato preaddestrato il modello, quindi dobbiamo prima scaricare queste informazioni dal [Model Hub](https://huggingface.co/models). Per farlo, si usa la classe `AutoTokenizer` e il suo metodo `from_pretrained()`. Utilizzando il nome del checkpoint del nostro modello, recupererà automaticamente i dati associati al tokenizer del modello e li metterà in cache (in modo che vengano scaricati solo la prima volta che si esegue il codice sottostante). + +Poiché il checkpoint predefinito della pipeline `sentiment-analysis` è `distilbert-base-uncased-finetuned-sst-2-english` (si può vedere la sua scheda modello [qui](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), eseguiamo quanto segue: + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +Una volta che abbiamo il tokenizer, possiamo passargli direttamente le nostre frasi e otterremo un dizionario pronto per il nostro modello! L'unica cosa che resta da fare è convertire l'elenco degli ID in ingresso in tensori. + +È possibile utilizzare i 🤗 Transformer senza doversi preoccupare di quale framework ML venga utilizzato come backend;potrebbe essere PyTorch o TensorFlow, o Flax per alcuni modelli. Tuttavia, i modelli Transformer accettano solo *tensors* come input. Se è la prima volta che sentite parlare di tensori, potete pensare a loro come array NumPy. Un array NumPy può essere uno scalare (0D), un vettore (1D), una matrice (2D) o avere più dimensioni. Si tratta effettivamente di un tensore; i tensori di altri framework ML si comportano in modo simile e di solito sono semplici da istanziare come gli array NumPy. + +Per specificare il tipo di tensori che vogliamo ottenere (PyTorch, TensorFlow o NumPy), usiamo l'argomento `return_tensors`: + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Non preoccupatevi ancora di padding e truncation; li spiegheremo più avanti.Le cose principali da ricordare sono che si può passare una frase o un elenco di frasi, oltre a specificare il tipo di tensori che si desidera ottenere (se non viene passato alcun tipo, si otterrà una lista di liste come risultato). + +{#if fw === 'pt'} + +Ecco come appaiono i risultati come tensori PyTorch: + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + 'attention_mask': tensor([ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ]) +} +``` +{:else} + +Ecco come appaiono i risultati come tensori TensorFlow: + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +L'output stesso è un dizionario contenente due chiavi, `input_ids` e `attention_mask`. `input_ids` contiene due righe di interi (uno per ogni frase) che sono gli identificatori unici dei token in ogni frase. Spiegheremo cosa sia la `attention_mask` più avanti in questo capitolo. + +## Passare attraverso il modello + +{#if fw === 'pt'} +Possiamo scaricare il nostro modello preaddestrato nello stesso modo in cui abbiamo fatto con il nostro tokenizer. 🤗 Transformers fornisce una classe `AutoModel` che ha anche un metodo `from_pretrained()`: + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Possiamo scaricare il nostro modello preaddestrato nello stesso modo in cui abbiamo fatto con il nostro tokenizer. 🤗 Transformers fornisce una classe `TFAutoModel` che ha anche un metodo `from_pretrained`: + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +In questo frammento di codice, abbiamo scaricato lo stesso checkpoint usato in precedenza nella nostra pipeline (in realtà dovrebbe essere già nella cache) e abbiamo istanziato un modello con esso. + +Questa architettura contiene solo il modulo Transformer di base: dati alcuni input, produce quelli che chiameremo *hidden states*, noti anche come *features*. Per ogni input del modello, recupereremo un vettore ad alta dimensionalità che rappresenta la **comprensione contestuale di quell'input da parte del modello Transformer**. + +Se per te tutto questo non ha senso, non preoccuparti. Ti spiegheremo tutto più avanti. + +Anche se questi stati nascosti possono essere utili da soli, di solito sono input di un'altra parte del modello, nota come *head*. Nel [Capitolo 1](/course/chapter1), i diversi compiti potrebbero essere eseguiti con la stessa architettura, ma a ciascuno di essi sarà associata una head diversa. + +### Un vettore ad alta dimensionalità? + +Il vettore emesso dal modulo Transformer è solitamente di grandi dimensioni. In genere ha tre dimensioni: + +- **Dimensione del batch**: Il numero di sequenze elaborate alla volta (2 nel nostro esempio). +- **Lunghezza della sequenza**: La lunghezza della rappresentazione numerica della sequenza (16 nel nostro esempio). +- **Dimensione nascosta**: La dimensione del vettore di ciascun ingresso del modello. + +Si dice che è "ad alta dimensionalità" a causa dell'ultimo valore. La dimensione nascosta può essere molto grande (768 è comune per i modelli più piccoli, mentre nei modelli più grandi può arrivare a 3072 o più). + +Lo possiamo vedere se alimentiamo il nostro modello con gli input che abbiamo preelaborato: + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Si noti che gli output dei modelli 🤗 Transformers si comportano come `namedtuple` o dizionari. Si può accedere agli elementi per attributi (come abbiamo fatto noi) sia per chiave (`outputs["last_hidden_state"]`), sia per indice se si sa esattamente dove si trova ciò che si sta cercando (`outputs[0]`). + +### Model heads: Dare un senso ai numeri + +Le model head prendono in input il vettore ad alta dimensione degli stati nascosti e lo proiettano su una dimensione diversa. Di solito sono composte da uno o pochi strati lineari: + +
+Una rete di Transformer accanto alla sua head. + +
+ +Gli output del modello Transformer vengono inviati direttamente alla model head per essere elaborati. + +In questo diagramma, il modello è rappresentato dallo strato embeddings e dagli strati successivi. Il livello embeddings converte ogni ID dell'input tokenizzato in un vettore che rappresenta il token associato. I livelli successivi manipolano questi vettori utilizzando il meccanismo di attenzione per produrre la rappresentazione finale delle frasi. + +Esistono diverse architetture disponibili nei 🤗 Transformers, ognuna delle quali è stata progettata per affrontare un compito specifico. Ecco un elenco non esaustivo: + +- `*Model` (retrieve the hidden states) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- e altre 🤗 + +{#if fw === 'pt'} +Per il nostro esempio, avremo bisogno di un modello con una classificazion head della sequenza (per poter classificare le frasi come positive o negative). Quindi, non useremo la classe `AutoModel`, ma `AutoModelForSequenceClassification`: + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Per il nostro esempio, avremo bisogno di un modello con una classificazion head della sequenza (per poter classificare le frasi come positive o negative). Quindi, non useremo la classe `TFAutoModel`, ma `TFAutoModelForSequenceClassification`: + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Ora, se osserviamo la forma dei nostri input, la dimensionalità sarà molto più bassa: la model head prende in input i vettori ad alta dimensionalità che abbiamo visto prima e produce vettori contenenti due valori (uno per etichetta): + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Dato che abbiamo solo due frasi e due etichette, il risultato che otteniamo dal nostro modello è di forma 2 x 2. + +## Postprocessing the output + +I valori che otteniamo come output dal nostro modello non hanno necessariamente senso da soli. Diamo un'occhiata: + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Il nostro modello ha previsto `[-1.5607, 1.6123]` per la prima frase e `[ 4.1692, -3.3464]` per la seconda. Non si tratta di probabilità ma di *logit*, i punteggi non normalizzati emessi dall'ultimo livello del modello. Per poterli convertire in probabilità, devono passare attraverso un layer [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (tutti i modelli 🤗 Transformers producono i logits, poiché la funzione di perdita per l'addestramento generalmente fonde l'ultima funzione di attivazione, come SoftMax, con la funzione di perdita effettiva, come la cross entropy): + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Ora possiamo vedere che il modello ha previsto `[0,0402, 0,9598]` per la prima frase e `[0,9995, 0,0005]` per la seconda. Si tratta di punteggi di probabilità riconoscibili. + +Per ottenere le etichette corrispondenti a ogni posizione, si può ispezionare l'attributo `id2label` della configurazione del modello (si veda la prossima sezione): + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Ora possiamo concludere che il modello ha previsto quanto segue: + +- Prima frase: NEGATIVE: 0.0402, POSITIVE: 0.9598 +- Seconda frase: NEGATIVE: 0.9995, POSITIVE: 0.0005 + +Abbiamo riprodotto con successo le tre fasi della pipeline: preelaborazione con i tokenizer, passaggio degli input attraverso il modello e postelaborazione! Ora prendiamoci un po' di tempo per approfondire ognuna di queste fasi. + + +✏️ **Provaci anche tu!** Scegli due (o più) testi di tua proprietà e lanciali all'interno della pipeline `sentiment-analysis`. Successivamente, replica i passi che hai visto qui e verifica di aver ottenuto gli stessi risultati! + diff --git a/chapters/it/chapter4/1.mdx b/chapters/it/chapter4/1.mdx index b0d8f4f70..df7346be0 100644 --- a/chapters/it/chapter4/1.mdx +++ b/chapters/it/chapter4/1.mdx @@ -1,5 +1,10 @@ # L'Hub di Hugging Face + + L'Hub di Hugging Face -- il nostro sito web principale -- è la piattaforma che permette a chiunque di scoprire, utilizzare, e proporre nuovi modelli e dataset. Contiene una vasta varietà di modelli, con più di 10.000 modelli pubblicamente disponibili. In questo capitolo ci concentreremo sui modelli, mentre approfondiremo i dataset nel capitolo 5. I modelli disponibili nell'Hub non sono limitati ai 🤗 Transformers, o ai modelli di analisi del linguaggio naturale (Natural Language Processing - NLP). Ci sono infatti modelli sviluppati da [Flair](https://github.com/flairNLP/flair) e [AllenNLP](https://github.com/allenai/allennlp) per l'NLP, ma anche modelli di [Asteroid](https://github.com/asteroid-team/asteroid) e [pyannote](https://github.com/pyannote/pyannote-audio) per la fonologia, e [timm](https://github.com/rwightman/pytorch-image-models) per l'analisi di immagini. diff --git a/chapters/it/chapter4/2.mdx b/chapters/it/chapter4/2.mdx index a95b9c9b0..6fbf6e46f 100644 --- a/chapters/it/chapter4/2.mdx +++ b/chapters/it/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/it/chapter4/3.mdx b/chapters/it/chapter4/3.mdx index de96f65ab..93f55ee0e 100644 --- a/chapters/it/chapter4/3.mdx +++ b/chapters/it/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/it/chapter4/4.mdx b/chapters/it/chapter4/4.mdx index 1b00e6946..e9d1922f2 100644 --- a/chapters/it/chapter4/4.mdx +++ b/chapters/it/chapter4/4.mdx @@ -1,5 +1,10 @@ # Generare un cartellino del modello + + Il cartellino del modello (model card) è un file di importanza pari ai file del modello e del tokenizer in un repository. Contiene la definizione del modello, assicurando la possibilità di riutilizzarlo e riprodurre i risultati da parte dei membri della comunità, e facendo si che il modello sia una piattaforma su cui gli altri membri possono costruire i loro artefatti. Documentare il processo di addestramento e valutazione aiuta gli altri a capire cosa aspettarsi dal modello — inoltre, fornire informazioni accurate sui dati utilizzati e sulle operazioni di pre e post elaborazione (preprocessing e postprocessing), assicura che si possano identificare e comprenere le limitazioni, i bias, e i contesti in cui il modello è utile, e quelli in cui non lo è. diff --git a/chapters/it/chapter4/5.mdx b/chapters/it/chapter4/5.mdx index 68112b891..3fbeda1d3 100644 --- a/chapters/it/chapter4/5.mdx +++ b/chapters/it/chapter4/5.mdx @@ -1,5 +1,10 @@ # Fine della parte 1! + + Questa è la dine della prima parte del corso! La seconda parte sarà rilasciata il 15 Novembre durante un grande evento della comunità. Per maggiori informazioni [vedere qui](https://huggingface.co/blog/course-launch-event). A questo punto dovreste essere in grado di affinare un modello pre-addestrato per un problema di classificazione tesutale (a frase signola o doppia), e caricare il risultato sull'Hub dei Modelli (Model Hub). Per assicurarvi di padroneggiare i contenuti di questa prima sezione, dovreste provare a fare esattamente questo su un problema che vi interessi (e non necessariamente in Inglese se parlate un'altra lingua)! Potete trovare aiuto nel [forum di Hugging Face](https://discuss.huggingface.co/), e quando avete finito potete condividere il vostro progetto in [questo topic](https://discuss.huggingface.co/t/share-your-projects/6803). diff --git a/chapters/it/chapter4/6.mdx b/chapters/it/chapter4/6.mdx index c31b9fb76..c2616894c 100644 --- a/chapters/it/chapter4/6.mdx +++ b/chapters/it/chapter4/6.mdx @@ -4,6 +4,11 @@ # Quiz di fine capitolo + + Mettiamo alla prova quello che avete imparato in questo capitolo! ### 1. Quali modelli si possono caricare sull'Hub? diff --git a/chapters/it/chapter5/1.mdx b/chapters/it/chapter5/1.mdx index e23cf502e..b0776fbc0 100644 --- a/chapters/it/chapter5/1.mdx +++ b/chapters/it/chapter5/1.mdx @@ -1,5 +1,10 @@ # Introduzione + + Nel [Capitolo 3](/course/chapter3) hai mosso i primi passi nella libreria 🤗 Datasets, e hai scoperto i tre passaggi fondamentali nell'ottimizzazione dei modelli: 1. Si carica un dataset dell'Hub Hugging Face. diff --git a/chapters/it/chapter5/2.mdx b/chapters/it/chapter5/2.mdx index 93a7d01f6..77133416d 100644 --- a/chapters/it/chapter5/2.mdx +++ b/chapters/it/chapter5/2.mdx @@ -1,8 +1,8 @@ # E se il mio dataset non è sull'Hub? - diff --git a/chapters/it/chapter5/3.mdx b/chapters/it/chapter5/3.mdx index 8c44362ed..af65c1765 100644 --- a/chapters/it/chapter5/3.mdx +++ b/chapters/it/chapter5/3.mdx @@ -1,8 +1,8 @@ # È arrivato il momento di tagliuzzare - diff --git a/chapters/it/chapter5/4.mdx b/chapters/it/chapter5/4.mdx index 682ef8719..03dd0aa2c 100644 --- a/chapters/it/chapter5/4.mdx +++ b/chapters/it/chapter5/4.mdx @@ -1,8 +1,8 @@ # Big data? Ci pensa 🤗 Datasets! - diff --git a/chapters/it/chapter5/5.mdx b/chapters/it/chapter5/5.mdx index be262d885..a0b1542d0 100644 --- a/chapters/it/chapter5/5.mdx +++ b/chapters/it/chapter5/5.mdx @@ -1,8 +1,8 @@ # Creare il proprio dataset - diff --git a/chapters/it/chapter5/6.mdx b/chapters/it/chapter5/6.mdx index 087e457c6..8fa4b93e2 100644 --- a/chapters/it/chapter5/6.mdx +++ b/chapters/it/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/it/chapter5/7.mdx b/chapters/it/chapter5/7.mdx index 118b04511..f2c963eaa 100644 --- a/chapters/it/chapter5/7.mdx +++ b/chapters/it/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets, check! + + Beh, abbiamo fatto un bel giro nella libreria 🤗 Datasets: complimenti per aver raggiunto quest'obiettivo! Con le conoscenze che hai ottenuto da questo capitolo, sei in grado di: - Caricare dataset da ogni luogo, sia esso l'Hub di Hugging Face, il tuo portatile, o un server in remoto della tua compagnia. - Fare data-wrangling usando un mix delle funzioni `Dataset.map()` e `Dataset.filter()`. diff --git a/chapters/it/chapter5/8.mdx b/chapters/it/chapter5/8.mdx index 5fece0b6a..15288bf23 100644 --- a/chapters/it/chapter5/8.mdx +++ b/chapters/it/chapter5/8.mdx @@ -2,6 +2,11 @@ # Quiz di fine capitolo + + In questo capitolo abbiamo fatto un bel po' di strada! Non preoccuparti se non hai colto tutti i dettagli; i capitoli successivi ti aiuteranno a capire come funzionano le cose dietro le quinte! Prima di andare oltre, mettiamo alla prova ciò che hai imparato in questo capitolo. diff --git a/chapters/it/chapter8/1.mdx b/chapters/it/chapter8/1.mdx index 7b34c6a70..1c842c488 100644 --- a/chapters/it/chapter8/1.mdx +++ b/chapters/it/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introduzione + + Ora che sai come affrontare i problemi più comuni nel campo del NLP con i 🤗 Transformers, dovresti essere in grado d'iniziare a lavorare sui tuoi progetti personali! In questo capitolo descriveremo cosa fare quando si incontra un problema. Imparerai come eseguire il debug del tuo codice o del tuo training e come chiedere aiuto alla community se non riesci a risolvere il problema in autonomia. E, se pensi di aver trovato un bug in una delle librerie di Hugging Face, ti mostreremo il modo migliore per segnalarlo, in modo che il problema venga risolto il più rapidamente possibile. Più precisamente, in questo capitolo impareremo: diff --git a/chapters/it/chapter8/2.mdx b/chapters/it/chapter8/2.mdx index 75b43ed9a..d3d4bc206 100644 --- a/chapters/it/chapter8/2.mdx +++ b/chapters/it/chapter8/2.mdx @@ -1,8 +1,8 @@ # Cosa fare quando si riceve un errore - diff --git a/chapters/it/chapter8/3.mdx b/chapters/it/chapter8/3.mdx index d7cc632ba..c34b5cdcc 100644 --- a/chapters/it/chapter8/3.mdx +++ b/chapters/it/chapter8/3.mdx @@ -1,8 +1,8 @@ # Asking for help on the forums - diff --git a/chapters/it/chapter8/4.mdx b/chapters/it/chapter8/4.mdx index c57190b58..245c246fa 100644 --- a/chapters/it/chapter8/4.mdx +++ b/chapters/it/chapter8/4.mdx @@ -2,9 +2,9 @@ # Fare il debug della training pipeline - diff --git a/chapters/it/chapter8/4_tf.mdx b/chapters/it/chapter8/4_tf.mdx index 998000149..7ba7fbc3e 100644 --- a/chapters/it/chapter8/4_tf.mdx +++ b/chapters/it/chapter8/4_tf.mdx @@ -2,9 +2,9 @@ # Fare il debug di una training pipeline - diff --git a/chapters/it/chapter8/5.mdx b/chapters/it/chapter8/5.mdx index 683738e94..444c59436 100644 --- a/chapters/it/chapter8/5.mdx +++ b/chapters/it/chapter8/5.mdx @@ -1,8 +1,8 @@ # Come scrivere un issue correttamente - diff --git a/chapters/it/chapter8/6.mdx b/chapters/it/chapter8/6.mdx index f0792a85a..fafced64c 100644 --- a/chapters/it/chapter8/6.mdx +++ b/chapters/it/chapter8/6.mdx @@ -1,5 +1,10 @@ # Parte 2 completata! + + Congratulazioni, hai completato la seconda parte del corso! Stiamo lavorando attivamente alla terza parte, quindi iscrivetevi alla nostra [newsletter](https://huggingface.curated.co/) per essere sicuro/a di non perdere il suo rilascio. Ora dovresti essere in grado di risolvere una serie di problemi di NLP e di affinare o preaddestrare un modello su di essi. Non dimenticare di condividere i tuoi risultati con la community su [Model Hub](https://huggingface.co/models). diff --git a/chapters/it/chapter8/7.mdx b/chapters/it/chapter8/7.mdx index 467e4943a..e77af1667 100644 --- a/chapters/it/chapter8/7.mdx +++ b/chapters/it/chapter8/7.mdx @@ -2,6 +2,11 @@ # Quiz di fine capitolo + + Mettiamo alla prova quello che hai imparato in questo capitolo! ### 1. In quale ordine si deve leggere un traceback di Python? diff --git a/chapters/ja/chapter1/1.mdx b/chapters/ja/chapter1/1.mdx index 5e1dd3a31..fdcb55837 100644 --- a/chapters/ja/chapter1/1.mdx +++ b/chapters/ja/chapter1/1.mdx @@ -1,5 +1,10 @@ # イントロダクション + + ## 🤗 コースへようこそ! diff --git a/chapters/ja/chapter4/1.mdx b/chapters/ja/chapter4/1.mdx index 93effda6b..94eb03363 100644 --- a/chapters/ja/chapter4/1.mdx +++ b/chapters/ja/chapter4/1.mdx @@ -1,5 +1,10 @@ # ハギングフェイスハブ + + [ハギングフェイスハブ](https://huggingface.co/)(Hugging Face Hub) –- 私たちのメインウェブサイト –- は、誰もが新しい最先端のモデルやデータセットを発見し、利用し、貢献することができるプラットフォームです。様々なモデルをホストしており、10,000以上のモデルが一般に公開されています。この章ではモデルに焦点を当て、第5章ではデータセットについて見ていきます。 ハブにあるモデルは、🤗 Transformersや、そもそもNLPモデルに限りません。例えば、[Flair](https://github.com/flairNLP/flair)や[AllenNLP](https://github.com/allenai/allennlp)からはNLPモデルが、[Asteroid](https://github.com/asteroid-team/asteroid)や[pyannote](https://github.com/pyannote/pyannote-audio)からは音声モデルが、そして[timm](https://github.com/rwightman/pytorch-image-models)からは画像モデルがそれぞれ寄贈されています。 diff --git a/chapters/ja/chapter4/2.mdx b/chapters/ja/chapter4/2.mdx index eeea7d497..777733e93 100644 --- a/chapters/ja/chapter4/2.mdx +++ b/chapters/ja/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter4/3.mdx b/chapters/ja/chapter4/3.mdx index 90e29c62b..088de9698 100644 --- a/chapters/ja/chapter4/3.mdx +++ b/chapters/ja/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter4/4.mdx b/chapters/ja/chapter4/4.mdx index 82e25f2f5..76a02b4bc 100644 --- a/chapters/ja/chapter4/4.mdx +++ b/chapters/ja/chapter4/4.mdx @@ -1,5 +1,10 @@ # モデルカードを作成する + + モデルカードは、モデルリポジトリにおいて、モデルファイルやトークナイザーファイルと同じくらい重要なファイルです。モデルの主要な定義であり、コミュニティメンバーによる再利用と結果の再現性を保証し、さらには他のメンバーが成果物を構築するためのプラットフォームを提供します。 また、使用したデータや前処理・後処理に関する十分な情報を提供することで、モデルの限界、バイアス、有用となる場面の特定及び理解が可能になります。 diff --git a/chapters/ja/chapter4/5.mdx b/chapters/ja/chapter4/5.mdx index d4dd76891..7678c08a4 100644 --- a/chapters/ja/chapter4/5.mdx +++ b/chapters/ja/chapter4/5.mdx @@ -1,5 +1,10 @@ # パート1終了! + + これでコースのパート1は終了です!パート2は11月15日に開催される、大きなコミュニティイベントで公開される予定です。詳しくは[こちら](https://huggingface.co/blog/course-launch-event)をご覧ください。 これで、テキスト分類問題(単一または文のペア)に対する学習済みモデルのファインチューニングを行い、結果をモデルハブにアップロードできるようになりました。この最初のセクションを確実にマスターするためには、自分の興味のある問題(英語でなくてもよい)をきっちりやっておくことが必要です。[Hugging Face forums](https://discuss.huggingface.co/)で助けを求めることもできますし、完成したら[this topic](https://discuss.huggingface.co/t/share-your-projects/6803)でプロジェクトを共有することもできます。 diff --git a/chapters/ja/chapter4/6.mdx b/chapters/ja/chapter4/6.mdx index 22575d1b4..9ff6c9cc8 100644 --- a/chapters/ja/chapter4/6.mdx +++ b/chapters/ja/chapter4/6.mdx @@ -4,6 +4,11 @@ # チャプター修了クイズ + + この章で学んだことを確認してみましょう! ### 1. ハブにアップロードできるモデルには何か制限があるでしょうか? diff --git a/chapters/ja/chapter7/1.mdx b/chapters/ja/chapter7/1.mdx index 5856697ae..cb1eb2ee2 100644 --- a/chapters/ja/chapter7/1.mdx +++ b/chapters/ja/chapter7/1.mdx @@ -2,6 +2,11 @@ # イントロダクション + + [第3章](/course/ja/chapter3)では、テキスト分類のためにモデルを微調整する方法を学びました。この章では、以下のような一般的な自然言語処理タスクに取り組みます。 - トークン分類 diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index efd90ad71..d62280f21 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index faa27116d..eebcce1c8 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index cadd8c24c..519e43f3c 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index 13232100e..f17925981 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/6.mdx b/chapters/ja/chapter7/6.mdx index ad4dccae9..5add41211 100644 --- a/chapters/ja/chapter7/6.mdx +++ b/chapters/ja/chapter7/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index 8ee20d14f..bbbed4999 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/8.mdx b/chapters/ja/chapter7/8.mdx index 7426fcc25..4f9610d6d 100644 --- a/chapters/ja/chapter7/8.mdx +++ b/chapters/ja/chapter7/8.mdx @@ -1,5 +1,10 @@ # NLPをマスター + + このコースでここまで進んだなら、おめでとうございます! あなたは今、🤗トランスフォーマーとハギング フェイス エコシステムを使って(ほとんど)どんなNLPタスクにも取り組むために必要なすべての知識とツールを手にしています。 diff --git a/chapters/ja/chapter7/9.mdx b/chapters/ja/chapter7/9.mdx index 4553fca3e..d2221b3dd 100644 --- a/chapters/ja/chapter7/9.mdx +++ b/chapters/ja/chapter7/9.mdx @@ -4,6 +4,11 @@ # 章末クイズ + + この章で学んだことをテストしてみましょう! ### 1. 次のタスクのうち、トークン分類問題として組み立てられるものはどれでしょうか? diff --git a/chapters/ja/chapter8/1.mdx b/chapters/ja/chapter8/1.mdx index 5c45820d8..7117248c2 100644 --- a/chapters/ja/chapter8/1.mdx +++ b/chapters/ja/chapter8/1.mdx @@ -1,5 +1,10 @@ # イントロダクション + + 🤗 Transformers を使いながらNLPタスクに取り組む方法がわかった後、自分自身のプロジェクトを簡単に始めることができます! この章では、エラーにぶつかったときにどうすればよいかを深く探ります。自分のコードやトレーニングを正確にデバッグする方法、そして自分でエラーを解決できない場合にコミュニティに助けを求める方法を一緒に学びましょう。また、HuggingFace (ハギングフェイス) ライブラリのひとつにバグを見つけたと思ったら、その問題をできるだけ早く解決するため報告する方法を紹介します。 この章では、一緒に次のことを学びます: diff --git a/chapters/ja/chapter8/2.mdx b/chapters/ja/chapter8/2.mdx index c6d73057c..ec6c8b066 100644 --- a/chapters/ja/chapter8/2.mdx +++ b/chapters/ja/chapter8/2.mdx @@ -1,8 +1,8 @@ # エラーを見つけた時に最初にすること - diff --git a/chapters/ja/chapter8/6.mdx b/chapters/ja/chapter8/6.mdx index c644a5c50..4dfbedb21 100644 --- a/chapters/ja/chapter8/6.mdx +++ b/chapters/ja/chapter8/6.mdx @@ -1,5 +1,10 @@ # パート2終了! + + お疲れ様でした。第2部を無事に完了しました!今は第3部について働いてるので情報を見逃せないように我々の[ニュースレター](https://huggingface.curated.co/)に応募して下さい! 今は、様々なNLPタスクに取り組み、その上でモデルを微調整またはプリトレーニングできるようになったはずです!その結果を [モデルハブ] (https://huggingface.co/models) でコミュニティと共有することを忘れないでください。 diff --git a/chapters/ko/chapter1/1.mdx b/chapters/ko/chapter1/1.mdx index 3bfb81f49..6c6bf2410 100644 --- a/chapters/ko/chapter1/1.mdx +++ b/chapters/ko/chapter1/1.mdx @@ -1,5 +1,10 @@ # 단원 소개 + + ## 🤗 강의 수강생 여러분 환영합니다! diff --git a/chapters/ko/chapter1/10.mdx b/chapters/ko/chapter1/10.mdx index a05bd4c3b..e6fbd2a0e 100644 --- a/chapters/ko/chapter1/10.mdx +++ b/chapters/ko/chapter1/10.mdx @@ -2,6 +2,11 @@ # 단원 마무리 퀴즈 + + 이번 챕터에서는 정말 많은 내용들을 다뤘습니다! 그러니 모든 세부 사항을 다 이해하지 못했다고 해서 좌절하지 마세요. 다음 챕터에서 다루는 내용은 내부 작동 방식을 이해하는 데에 도움이 될거에요. 그래도 우선, 이번 챕터에서 배운 내용에 대해 확인해보는 시간을 갖도록 하겠습니다! diff --git a/chapters/ko/chapter1/2.mdx b/chapters/ko/chapter1/2.mdx index ca5d62dc8..de07c0794 100644 --- a/chapters/ko/chapter1/2.mdx +++ b/chapters/ko/chapter1/2.mdx @@ -1,5 +1,10 @@ # 자연어 처리(Natural Language Processing) + + 트랜스포머 모델을 공부하기에 앞서 자연어 처리(NLP)가 무엇인지, 그리고 왜 NLP가 중요한지 빠르고 간단하게 살펴보겠습니다. ## NLP가 무엇인가요? diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index f32892430..0ece2c2eb 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -1,8 +1,8 @@ # 트랜스포머로 무엇을 할 수 있나요? - diff --git a/chapters/ko/chapter1/4.mdx b/chapters/ko/chapter1/4.mdx index 96456fab5..9bb7ca6bb 100644 --- a/chapters/ko/chapter1/4.mdx +++ b/chapters/ko/chapter1/4.mdx @@ -1,5 +1,10 @@ # 트랜스포머는 어떻게 동작하나요? + + 이번 단원에서는 트랜스포머(Transformer) 모델의 대략적인 구조를 알아보겠습니다. ## 트랜스포머 역사 훑어보기 diff --git a/chapters/ko/chapter1/5.mdx b/chapters/ko/chapter1/5.mdx index 3c133aea8..7c7f3f113 100644 --- a/chapters/ko/chapter1/5.mdx +++ b/chapters/ko/chapter1/5.mdx @@ -1,5 +1,10 @@ # 인코더 모델 + + 인코더 모델(Encoder models)은 트랜스포머 모델의 인코더만 사용합니다. 각각의 단계에서 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있습니다. 이러한 모델은 “양방향성(bi-directional)” 어텐션을 지닌 특성이 있다고도 하며 *자동 인코딩(auto-enoding) 모델*이라고 부릅니다. diff --git a/chapters/ko/chapter1/6.mdx b/chapters/ko/chapter1/6.mdx index 121403b4d..30482ca4a 100644 --- a/chapters/ko/chapter1/6.mdx +++ b/chapters/ko/chapter1/6.mdx @@ -1,5 +1,10 @@ # 디코더 모델 + + 디코더 모델(Decoder models)은 트랜스포머 모델의 디코더만 사용합니다. 각각의 단계마다, 어텐션 레이어는 주어진 단어에 대해 문장 내에서 해당 단어 앞에 위치한 단어들에 대해서만 액세스 할 수 있습니다. 이러한 모델을 *자동 회귀(auto-regressive) 모델*이라고 부릅니다. diff --git a/chapters/ko/chapter1/7.mdx b/chapters/ko/chapter1/7.mdx index 98aad86e9..4acfad2d7 100644 --- a/chapters/ko/chapter1/7.mdx +++ b/chapters/ko/chapter1/7.mdx @@ -1,5 +1,10 @@ # 시퀀스-투-시퀀스 모델 + + 인코더-디코더 모델(Encoder-decoder models) (*시퀀스-투-시퀀스 모델(sequence-to-sequence models)*로 부르기도 합니다)은 트랜스포머 구조의 인코더, 디코더 둘을 모두 사용합니다. 각 단계마다, 인코더의 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있는 반면, 디코더의 어텐션 레이어는 주어진 단어 앞에 위치한 단어들에만 액세스 할 수 있습니다. diff --git a/chapters/ko/chapter1/8.mdx b/chapters/ko/chapter1/8.mdx index edbd32548..722a864ae 100644 --- a/chapters/ko/chapter1/8.mdx +++ b/chapters/ko/chapter1/8.mdx @@ -1,8 +1,8 @@ # 편향과 한계 - diff --git a/chapters/ko/chapter1/9.mdx b/chapters/ko/chapter1/9.mdx index 191b5654d..9fee845cc 100644 --- a/chapters/ko/chapter1/9.mdx +++ b/chapters/ko/chapter1/9.mdx @@ -1,5 +1,10 @@ # 단원 정리 + + 이번 단원에서는 🤗 Transformers의 하이레벨 함수인 `pipeline()` 를 사용하여 다양한 NLP 문제에 대한 접근 방식을 배웠습니다. 그리고 Hub에서 모델을 검색하여 사용하는 방법, 추론 API를 이용해 브라우저 상에서 바로 모델을 테스트 하는 방법 또한 알아보았습니다. 지금까지 트랜스포머 모델의 대략작인 동작 방식과, 전이 학습(transfer learning) 및 미세 조정(fine-tuning)의 중요성에 알아보았습니다. 핵심은 어떤 문제를 풀고싶냐에 따라 전체 모델 구조를 다 사용하거나 인코더, 디코더만 사용할 수도 있다는 것입니다. 아래 표는 이를 요약해서 보여주고 있습니다: diff --git a/chapters/pt/chapter1/1.mdx b/chapters/pt/chapter1/1.mdx index aaf99a847..350a2dea9 100644 --- a/chapters/pt/chapter1/1.mdx +++ b/chapters/pt/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introdução + + ## Bem-vindo(a) ao Curso 🤗! diff --git a/chapters/pt/chapter1/10.mdx b/chapters/pt/chapter1/10.mdx index e1a79c2ce..7c488f9e9 100644 --- a/chapters/pt/chapter1/10.mdx +++ b/chapters/pt/chapter1/10.mdx @@ -2,6 +2,11 @@ # Questionário de fim de capítulo + + Este capítulo cobriu muito terreno! Não se preocupe se você não entendeu todos os detalhes; os próximos capítulos o ajudarão a entender como as coisas funcionam debaixo do capô. Primeiro, porém, vamos testar o que você aprendeu neste capítulo! diff --git a/chapters/pt/chapter1/2.mdx b/chapters/pt/chapter1/2.mdx index a3413a2a8..d61decf13 100644 --- a/chapters/pt/chapter1/2.mdx +++ b/chapters/pt/chapter1/2.mdx @@ -1,5 +1,10 @@ # Processamento de Linguagem Natural (NLP) + + Antes de irmos direto para os modelos Transformers, vamos fazer um rápido esboço sobre o que é processamento de linguagem natural e o porquê nós nos importamos com isso. ## O que é NLP? diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx index 254d83372..03dcc6c46 100644 --- a/chapters/pt/chapter1/3.mdx +++ b/chapters/pt/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers, o que eles podem fazer? - diff --git a/chapters/pt/chapter1/4.mdx b/chapters/pt/chapter1/4.mdx index f9b9e1ce8..3e3e39754 100644 --- a/chapters/pt/chapter1/4.mdx +++ b/chapters/pt/chapter1/4.mdx @@ -1,5 +1,10 @@ # Como os Transformers trabalham? + + Nessa seção, nós olharemos para o alto nível de arquitetura dos modelos Transformers. ## Um pouco da história dos Transformers diff --git a/chapters/pt/chapter1/5.mdx b/chapters/pt/chapter1/5.mdx index 4ab25d749..f14bb0ac2 100644 --- a/chapters/pt/chapter1/5.mdx +++ b/chapters/pt/chapter1/5.mdx @@ -1,5 +1,10 @@ # Modelos decodificadores + + Os modelos de encoder (decodificadores) usam apenas o encoder de um modelo Transformer. Em cada estágio, as camadas de atenção podem acessar todas as palavras da frase inicial. Esses modelos geralmente são caracterizados como tendo atenção "bidirecional" e são frequentemente chamados de *modelos de codificação automática*. diff --git a/chapters/pt/chapter1/6.mdx b/chapters/pt/chapter1/6.mdx index 0d6e1cb15..4922350c6 100644 --- a/chapters/pt/chapter1/6.mdx +++ b/chapters/pt/chapter1/6.mdx @@ -1,5 +1,10 @@ # Modelos decodificadores + + Os modelos de decodificador usam apenas o decodificador de um modelo Transformer. Em cada etapa, para uma determinada palavra, as camadas de atenção só podem acessar as palavras posicionadas antes dela na frase. Esses modelos geralmente são chamados de _modelos auto-regressivos_. O pré-treinamento de modelos de decodificadores geralmente gira em torno de prever a próxima palavra na frase. diff --git a/chapters/pt/chapter1/7.mdx b/chapters/pt/chapter1/7.mdx index 695359a16..c65c3e2ce 100644 --- a/chapters/pt/chapter1/7.mdx +++ b/chapters/pt/chapter1/7.mdx @@ -1,5 +1,10 @@ # Modelos sequência a sequência + + Modelos encoder-decoder (também chamados de modelos _sequence-to-sequence_) usam ambas as partes da arquitetura Transformer. Em cada estágio, as camadas de atenção do codificador podem acessar todas as palavras da frase inicial, enquanto as camadas de atenção do decodificador podem acessar apenas as palavras posicionadas antes de uma determinada palavra na entrada. O pré-treinamento desses modelos pode ser feito usando os objetivos dos modelos de codificador ou decodificador, mas geralmente envolve algo um pouco mais complexo. Por exemplo, [T5](https://huggingface.co/t5-base) é pré-treinado substituindo trechos aleatórios de texto (que podem conter várias palavras) por uma única palavra especial de máscara, e o objetivo é prever o texto que esta palavra de máscara substitui. diff --git a/chapters/pt/chapter1/8.mdx b/chapters/pt/chapter1/8.mdx index e01fd445a..386d8a73c 100644 --- a/chapters/pt/chapter1/8.mdx +++ b/chapters/pt/chapter1/8.mdx @@ -1,6 +1,6 @@ # Vieses e limitações - + Se sua intenção é usar um modelo pré-treinado ou uma versão ajustada em produção, esteja ciente de que, embora esses modelos sejam ferramentas poderosas, eles vêm com limitações. A maior delas é que, para possibilitar o pré-treinamento em grandes quantidades de dados, os pesquisadores muitas vezes raspam todo o conteúdo que encontram, tirando o melhor e o pior do que está disponível na internet. diff --git a/chapters/pt/chapter1/9.mdx b/chapters/pt/chapter1/9.mdx index 7398a7fc6..4be0bccb1 100644 --- a/chapters/pt/chapter1/9.mdx +++ b/chapters/pt/chapter1/9.mdx @@ -1,5 +1,10 @@ # Resumo + + Nesse capítulo, você viu como abordar diferentes tarefas de NLP usando a função de alto nível `pipeline()` da biblioteca 🤗 Transformers. Você também viu como pesquisar e usar modelos no Hub, bem como usar a API de inferência para testar os modelos diretamente em seu navegador. Discutimos como os modelos Transformers funcionam em alto nível e falamos sobre a importância do aprendizado de transferência (transfer learning) e do ajuste fino. Um aspecto chave é que você pode usar a arquitetura completa ou apenas o codificador ou decodificador, dependendo do tipo de tarefa que você pretende resolver. A tabela a seguir resume isso: diff --git a/chapters/pt/chapter2/1.mdx b/chapters/pt/chapter2/1.mdx index 4bc8850d5..3d3b8e83e 100644 --- a/chapters/pt/chapter2/1.mdx +++ b/chapters/pt/chapter2/1.mdx @@ -1,5 +1,10 @@ # Introdução + + Como você viu no [Capitulo 1](/course/pt/chapter1), normalmente modelos Transformers são muito grandes. Com milhões a dezenas de *bilhões* de parâmetros, o treinamento e o deploy destes modelos é uma tarefa complicado. Além disso, com novos modelos sendo lançados quase diariamente e cada um tendo sua própria implementação, experimentá-los a todos não é tarefa fácil. A biblioteca 🤗 Transformers foi criado para resolver este problema. Seu objetivo é fornecer uma API única através do qual qualquer modelo Transformer possa ser carregado, treinado e salvo. As principais características da biblioteca são: diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index 88c9a068e..6ee9e9ace 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/3.mdx b/chapters/pt/chapter2/3.mdx index 634cc3a6e..b3c94725d 100644 --- a/chapters/pt/chapter2/3.mdx +++ b/chapters/pt/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/4.mdx b/chapters/pt/chapter2/4.mdx index 7b2f70ff6..1134ad8d6 100644 --- a/chapters/pt/chapter2/4.mdx +++ b/chapters/pt/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/5.mdx b/chapters/pt/chapter2/5.mdx index 9b45c2c47..0caf41234 100644 --- a/chapters/pt/chapter2/5.mdx +++ b/chapters/pt/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/6.mdx b/chapters/pt/chapter2/6.mdx index a1ba25c38..0b65e1405 100644 --- a/chapters/pt/chapter2/6.mdx +++ b/chapters/pt/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/7.mdx b/chapters/pt/chapter2/7.mdx index 2b2f1df5a..178249fea 100644 --- a/chapters/pt/chapter2/7.mdx +++ b/chapters/pt/chapter2/7.mdx @@ -1,5 +1,10 @@ # Uso básico concluído! + + Ótimo trabalho seguindo o curso até aqui! Recapitulando, neste capítulo, você: - Aprendeu os elementos básicos de um modelo Transformer. diff --git a/chapters/pt/chapter2/8.mdx b/chapters/pt/chapter2/8.mdx index 00a283ad3..a8d9b20d5 100644 --- a/chapters/pt/chapter2/8.mdx +++ b/chapters/pt/chapter2/8.mdx @@ -4,6 +4,11 @@ # Questionário de fim de capítulo + + ### 1. Qual é a ordem do pipeline para a modelagem de linguagem? + O [Hugging Face Hub](https://huggingface.co/) –- nosso site principal –- é uma plataforma central que permite a qualquer pessoa descobrir, usar e contribuir com novos modelos e datasets do estado da arte. Ele hospeda uma grande variedade de modelos, com mais de 10.000 disponíveis publicamente. Vamos nos concentrar nos modelos deste capítulo, e daremos uma olhada nos conjuntos de dados do Capítulo 5. Os modelos no Hub não estão limitados a 🤗 Transformers ou mesmo NLP. Há modelos da [Flair](https://github.com/flairNLP/flair) e [AllenNLP](https://github.com/allenai/allennlp) para NLP, [Asteroid](https://github.com/asteroid-team/asteroid) e [pyannote](https://github.com/pyannote/pyannote-audio) para discurso, e [timm](https://github.com/rwightman/pytorch-image-models) para visão, para citar alguns. diff --git a/chapters/pt/chapter4/2.mdx b/chapters/pt/chapter4/2.mdx index 7065e8696..4f190231e 100644 --- a/chapters/pt/chapter4/2.mdx +++ b/chapters/pt/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter4/3.mdx b/chapters/pt/chapter4/3.mdx index 0a84b09dd..98e996143 100644 --- a/chapters/pt/chapter4/3.mdx +++ b/chapters/pt/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter4/4.mdx b/chapters/pt/chapter4/4.mdx index 89ce37719..111d99016 100644 --- a/chapters/pt/chapter4/4.mdx +++ b/chapters/pt/chapter4/4.mdx @@ -1,5 +1,10 @@ # Construindo um cartão para o modelo + + O cartão para o modelo (model card) é um arquivo que é discutivelmente tão importante quanto os arquivos modelo e tokenizer em um repositório modelo. É a definição central do modelo, garantindo a reutilização pelos membros da comunidade e a reprodutibilidade dos resultados, e fornecendo uma plataforma sobre a qual outros membros podem construir seus artefatos. Documentar o processo de treinamento e avaliação ajuda outros a entender o que esperar de um modelo - e fornecer informações suficientes sobre os dados que foram utilizados e o pré e pós-processamento que foram feitos garante que as limitações, enviesamentos e contextos nos quais o modelo é e não é útil possam ser identificados e compreendidos. diff --git a/chapters/pt/chapter4/5.mdx b/chapters/pt/chapter4/5.mdx index 85bb3afb9..c5ced8108 100644 --- a/chapters/pt/chapter4/5.mdx +++ b/chapters/pt/chapter4/5.mdx @@ -1,5 +1,10 @@ # Parte 1 completa! + + Este é o fim da primeira parte do curso! A segunda parte será lançada em 15 de novembro com um grande evento comunitário, veja mais informações [aqui](https://huggingface.co/blog/course-launch-event). Agora você deverá ser capaz de afinar um modelo pré-treinado sobre um problema de classificação de texto (frases simples ou pares de frases) e carregar o resultado para o Model Hub. Para garantir que você domine esta primeira seção, você deve fazer exatamente isso em um problema que lhe interesse (e não necessariamente em inglês se você falar outro idioma)! Você pode encontrar ajuda nos [fóruns Hugging Face](https://discuss.huggingface.co/) e compartilhar seu projeto em [este tópico](https://discuss.huggingface.co/t/share-your-projects/6803) uma vez terminado. diff --git a/chapters/pt/chapter4/6.mdx b/chapters/pt/chapter4/6.mdx index 717de1b1f..b78ba4f38 100644 --- a/chapters/pt/chapter4/6.mdx +++ b/chapters/pt/chapter4/6.mdx @@ -4,6 +4,11 @@ # Questionário de fim de capítulo + + Vamos testar o que você aprendeu neste capítulo! ### 1. A que se limitam os modelos no Hub? diff --git a/chapters/pt/chapter5/1.mdx b/chapters/pt/chapter5/1.mdx index 780a5b770..40de500fc 100644 --- a/chapters/pt/chapter5/1.mdx +++ b/chapters/pt/chapter5/1.mdx @@ -1,5 +1,10 @@ # Introdução + + No [Capítulo 3](/course/chapter3) você teve seu primeiro gostinho da biblioteca 🤗 Datasets e viu que havia três passos principais quando se tratava de treinar para melhorar (fine-tuning) um modelo: 1. Carregar um conjunto de dados (dataset) do Hugging Face Hub. diff --git a/chapters/pt/chapter5/2.mdx b/chapters/pt/chapter5/2.mdx index 741ef7450..2ad037958 100644 --- a/chapters/pt/chapter5/2.mdx +++ b/chapters/pt/chapter5/2.mdx @@ -1,8 +1,8 @@ # E se o meu dataset não estiver no Hub? - diff --git a/chapters/pt/chapter5/3.mdx b/chapters/pt/chapter5/3.mdx index 2c65a36c9..7fbc29618 100644 --- a/chapters/pt/chapter5/3.mdx +++ b/chapters/pt/chapter5/3.mdx @@ -1,8 +1,8 @@ # Hora de fatiar e dividir os dados - diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index fc1f3f92e..aa63e3003 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -1,8 +1,8 @@ # Big data? 🤗 Datasets ao resgate - diff --git a/chapters/pt/chapter5/5.mdx b/chapters/pt/chapter5/5.mdx index be0219602..ca931925e 100644 --- a/chapters/pt/chapter5/5.mdx +++ b/chapters/pt/chapter5/5.mdx @@ -1,8 +1,8 @@ # Criando seu próprio dataset - diff --git a/chapters/pt/chapter5/6.mdx b/chapters/pt/chapter5/6.mdx index 3ced95ff6..6b5d6c61d 100644 --- a/chapters/pt/chapter5/6.mdx +++ b/chapters/pt/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter5/7.mdx b/chapters/pt/chapter5/7.mdx index e83ba6a81..5e054aeed 100644 --- a/chapters/pt/chapter5/7.mdx +++ b/chapters/pt/chapter5/7.mdx @@ -1,5 +1,10 @@ # Confira o 🤗 Datasets! + + Bem, esse foi um belo passeio pela biblioteca 🤗 Datasets - parabéns por chegar até aqui! Com o conhecimento que você adquiriu neste capítulo, você deve ser capaz de: - Carregue conjuntos de dados de qualquer lugar, seja o Hugging Face Hub, seu laptop ou um servidor remoto em sua empresa. diff --git a/chapters/pt/chapter5/8.mdx b/chapters/pt/chapter5/8.mdx index 152c191e5..51a04768b 100644 --- a/chapters/pt/chapter5/8.mdx +++ b/chapters/pt/chapter5/8.mdx @@ -1,6 +1,11 @@ # Questionário de fim de capítulo + + Este capítulo cobriu muita coisa! Não se preocupe se você não entendeu todos os detalhes; os próximos capítulos o ajudarão a entender como as coisas funcionam. Antes de prosseguir, vamos testar o que você aprendeu neste capítulo. diff --git a/chapters/pt/chapter6/1.mdx b/chapters/pt/chapter6/1.mdx index 0fb3099dc..d2eac43c8 100644 --- a/chapters/pt/chapter6/1.mdx +++ b/chapters/pt/chapter6/1.mdx @@ -1,5 +1,10 @@ # Introdução + + No [Capítulo 3](/course/chapter3), nós estudamos como realizar o ajuste fino em um modelo para uma dada tarefa. Quando nós fazemos isso, usamos o mesmo tokenizador utilizado pelo modelo pré-treinado -- mas o que podemos fazer quando queremos treinar um modelo do início? Nestes casos, utilizar um tokenizador que foi pré-treinado em um corpus de outro domínio ou linguagem é tipicamente subótimo. Por exemplo, um tokenizador que é treinado em um corpus de lingua inglesa terá um desempenho ruim em um corpus de textos em japonês, visto que o uso de espaços e pontuações é muito diferente nestes dois idiomas. Neste capítulo, você aprenderá como treinar um novo tokenizador em um corpus de textos, para então ser usado no treinamento de um modelo de linguagem. Isto tudo será feito com ajuda da biblioteca [🤗 Tokenizers](https://github.com/huggingface/tokenizers), que provê o tokenizador rápido na biblioteca [🤗 Transformers](https://github.com/huggingface/transformers). Daremos uma olhada a fundo sobre as funcionalidades oferecidas pela biblioteca, e explorar como os tokenizadores rápidos diferem das versões "lentas". diff --git a/chapters/pt/chapter6/2.mdx b/chapters/pt/chapter6/2.mdx index 049ca184d..7399e9d68 100644 --- a/chapters/pt/chapter6/2.mdx +++ b/chapters/pt/chapter6/2.mdx @@ -1,8 +1,8 @@ # Treinando um novo tokenizador - diff --git a/chapters/pt/chapter6/3.mdx b/chapters/pt/chapter6/3.mdx index b79732841..540982222 100644 --- a/chapters/pt/chapter6/3.mdx +++ b/chapters/pt/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter7/1.mdx b/chapters/pt/chapter7/1.mdx index b95dc7580..65009e31a 100644 --- a/chapters/pt/chapter7/1.mdx +++ b/chapters/pt/chapter7/1.mdx @@ -2,6 +2,11 @@ # Introdução + + No [Capítulo 3](/course/chapter3), você viu como fazer o ajuste fino (fine-tune) de um modelo de classificação de texto. Neste capítulo, abordaremos as seguintes tarefas de NLP (também conhecido como PLN): - Classificação dos Tokens diff --git a/chapters/pt/chapter8/1.mdx b/chapters/pt/chapter8/1.mdx index 2500a85c8..972e02c53 100644 --- a/chapters/pt/chapter8/1.mdx +++ b/chapters/pt/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introdução + + Agora que você sabe como lidar com as tarefas mais comuns de PNL com 🤗 Transformers, você deve ser capaz de começar seus próprios projetos! Neste capítulo, exploraremos o que fazer quando você encontrar um problema. Você aprenderá como debugar com sucesso seu código ou seu treino e como pedir ajuda à comunidade se não conseguir resolver o problema sozinho. E se você acha que encontrou um bug em uma das bibliotecas do Hugging Face, mostraremos a melhor maneira de informa-lo para que o problema seja resolvido o mais rápido possível. Mais precisamente, neste capítulo você aprenderá: diff --git a/chapters/pt/chapter8/2.mdx b/chapters/pt/chapter8/2.mdx index 75a68bb0d..ee380cab5 100644 --- a/chapters/pt/chapter8/2.mdx +++ b/chapters/pt/chapter8/2.mdx @@ -1,8 +1,8 @@ # O que fazer quando ocorrer um erro - diff --git a/chapters/pt/chapter8/3.mdx b/chapters/pt/chapter8/3.mdx index c738682e5..2ac0b8374 100644 --- a/chapters/pt/chapter8/3.mdx +++ b/chapters/pt/chapter8/3.mdx @@ -1,7 +1,7 @@ # Pedindo ajuda nos fóruns - diff --git a/chapters/ru/chapter1/1.mdx b/chapters/ru/chapter1/1.mdx index 5e6e49ee2..6d5e1cd3b 100644 --- a/chapters/ru/chapter1/1.mdx +++ b/chapters/ru/chapter1/1.mdx @@ -1,5 +1,10 @@ # Введение + + ## Добро пожаловать на 🤗 курс! diff --git a/chapters/ru/chapter1/2.mdx b/chapters/ru/chapter1/2.mdx index abc6d6c2d..f265cabca 100644 --- a/chapters/ru/chapter1/2.mdx +++ b/chapters/ru/chapter1/2.mdx @@ -1,5 +1,10 @@ # Обработка естесственного языка + + Прежде, чем перейти к трансформерам, сделаем быстрый обзор того, что такое обработка естесственного языка (NLP) и почему мы заинтересованы в этой сфере. ## Что такое NLP? diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 2f28dc98a..be6579002 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -1,8 +1,8 @@ # Трансформеры, на что они способны? - diff --git a/chapters/ru/chapter1/4.mdx b/chapters/ru/chapter1/4.mdx index 3c901acd4..a817d124c 100644 --- a/chapters/ru/chapter1/4.mdx +++ b/chapters/ru/chapter1/4.mdx @@ -1,5 +1,10 @@ # Как работают трансформеры? + + В этом разделе мы посмотрим в общих чертах на то, как работают трансфореры. ## Немного истории diff --git a/chapters/ru/chapter1/5.mdx b/chapters/ru/chapter1/5.mdx index 7e4dc8b1a..1cc0839a3 100644 --- a/chapters/ru/chapter1/5.mdx +++ b/chapters/ru/chapter1/5.mdx @@ -1,5 +1,10 @@ # Модели энкодеров + + Энкодеры используют только компонент кодировщика трансформера. На каждом этапе слой внимания может использовать все слова исходного предложения. Эти модели часто характеризуют как имеющие двунаправленное внимание ("bi-directional attention"), и часто называют моделями *автоэнкодеров*. diff --git a/chapters/ru/chapter1/6.mdx b/chapters/ru/chapter1/6.mdx index d9ca11089..2a43f54ce 100644 --- a/chapters/ru/chapter1/6.mdx +++ b/chapters/ru/chapter1/6.mdx @@ -1,5 +1,10 @@ # Модели декодеров + + Декодировщики используют только компонент декодер трансформера. На каждом этапе для текущего слова слой внимания может получить доступ только к словам, которые были расположены до текущего в предложении. Такие модели часто называются *авторегрессионными моделями*. diff --git a/chapters/ru/chapter1/7.mdx b/chapters/ru/chapter1/7.mdx index 730b8f96f..86be51145 100644 --- a/chapters/ru/chapter1/7.mdx +++ b/chapters/ru/chapter1/7.mdx @@ -1,5 +1,10 @@ # Модели вида "seq2seq" + + Энкодер-декодер модели (также называемые *sequence-to-sequence models*) используют обе части трансформера. На каждом этапе слой внимания энкодера получает доступ ко всем словам в исходной последовательности, тогда как слой внимания декодера получает доступ только к тем словам, которые позиционированы до текущего слова. diff --git a/chapters/ru/chapter1/8.mdx b/chapters/ru/chapter1/8.mdx index 05d3b724d..df58fe8be 100644 --- a/chapters/ru/chapter1/8.mdx +++ b/chapters/ru/chapter1/8.mdx @@ -1,8 +1,8 @@ # Предвзятости и ограничения - diff --git a/chapters/ru/chapter1/9.mdx b/chapters/ru/chapter1/9.mdx index 730712e4e..f808b04d0 100644 --- a/chapters/ru/chapter1/9.mdx +++ b/chapters/ru/chapter1/9.mdx @@ -1,5 +1,10 @@ # Итоги + + В этой главе вы увидели, как подходить к различным задачам NLP, используя высокоуровневую функцию `pipeline()` из библиотеки 🤗 Transformers. Вы также увидели, как искать и использовать модели в Hub, а также как использовать Inference API для тестирования моделей прямо в браузере. Мы обсудили, как трансформеры работают на высоком уровне, и поговорили о важности трансферного обучения и тонкой настройки. Ключевым аспектом является то, что вы можете использовать всю архитектуру или только энкодер или декодер, в зависимости от того, какую задачу вы хотите решить. Следующая таблица резюмирует это: diff --git a/chapters/ru/chapter2/1.mdx b/chapters/ru/chapter2/1.mdx index 99dbd07b6..47421460e 100644 --- a/chapters/ru/chapter2/1.mdx +++ b/chapters/ru/chapter2/1.mdx @@ -1,5 +1,10 @@ # Введение + + Как вы могли заметить в [Главе 1](/course/chapter1), модели трансформеров обычно бывают очень большие. Обучение и развертывание таких моделей с миллионами и даже десятками *миллиардов* параметров является сложной задачей. Кроме того, новые модели выпускаются почти ежедневно, и каждая из них имеет собственную реализацию, опробовать их все — непростая задача. Библиотека 🤗 Transformers была создана для решения этой проблемы. Её цель — предоставить единый API, с помощью которого можно загружать, обучать и сохранять любую модель трансформера. Основными функциями библиотеки являются: diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index 85ce9cbd5..04fc473d1 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter2/3.mdx b/chapters/ru/chapter2/3.mdx index 080fd385f..6499af197 100644 --- a/chapters/ru/chapter2/3.mdx +++ b/chapters/ru/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter2/7.mdx b/chapters/ru/chapter2/7.mdx index 99bf935fb..708d47430 100644 --- a/chapters/ru/chapter2/7.mdx +++ b/chapters/ru/chapter2/7.mdx @@ -1,5 +1,10 @@ # Базовое использование завершено! + + Отличная работа, вы прошли курс до текущего момента! Напомним, что в этой главе вы: - Изучил основные строительные блоки модели Transformer. diff --git a/chapters/ru/chapter3/1.mdx b/chapters/ru/chapter3/1.mdx index 10ed44252..476152eb3 100644 --- a/chapters/ru/chapter3/1.mdx +++ b/chapters/ru/chapter3/1.mdx @@ -2,6 +2,11 @@ # Введение + + В [главе 2](/course/ru/chapter2) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим: {#if fw === 'pt'} diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx index 4d8ed067e..313cdfcac 100644 --- a/chapters/ru/chapter3/2.mdx +++ b/chapters/ru/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter3/3.mdx b/chapters/ru/chapter3/3.mdx index 5ff79c1ed..17e2155f0 100644 --- a/chapters/ru/chapter3/3.mdx +++ b/chapters/ru/chapter3/3.mdx @@ -2,9 +2,9 @@ # Fine-tuning модели с использованием Trainer API - diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index a3b1f7ef6..1b6393c44 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Fine-tuning модели с использованием Keras - diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx index 15568df81..9a0e473ea 100644 --- a/chapters/ru/chapter3/4.mdx +++ b/chapters/ru/chapter3/4.mdx @@ -1,8 +1,8 @@ # Полное обучение - diff --git a/chapters/ru/chapter3/5.mdx b/chapters/ru/chapter3/5.mdx index eb571700d..fed0ace33 100644 --- a/chapters/ru/chapter3/5.mdx +++ b/chapters/ru/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fine-tuning, итоги! + + Это было весело! В первых двух главах вы узнали о моделях и токенизаторах, и теперь вы знаете как применить fine-tuning на собственных данных. Напомним, в этой главе вы: diff --git a/chapters/ru/chapter3/6.mdx b/chapters/ru/chapter3/6.mdx index b4a492a51..bef136a13 100644 --- a/chapters/ru/chapter3/6.mdx +++ b/chapters/ru/chapter3/6.mdx @@ -4,6 +4,11 @@ # Итоговый тест по главе + + Тест по результатам изучения главы 3. ### Датасет `emotion` содержит сообщения из Твиттера, каждое сообщение помечено какой-либо эмоцией. Найдите его на [Hub](https://huggingface.co/datasets) и изучить карточку датасета. Какая из этих эмоцией не является базовой? diff --git a/chapters/ru/chapter4/1.mdx b/chapters/ru/chapter4/1.mdx index 8a5829bce..402368166 100644 --- a/chapters/ru/chapter4/1.mdx +++ b/chapters/ru/chapter4/1.mdx @@ -1,5 +1,10 @@ # Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/) -- наш главный сайт -- центральная платформа, позволяющая всем изучить, применить и внести свой вклад в SOTA (state-of=the-art) модели и наборы данных. Здесь хранится множество разнообразных моделей, среди которых больше 10000 доступны для использования. В этой главе мы сконцентрируемся на моделях, а в главе 5 обратим внимание на датасеты. Модели из Hugging Face Hub - это не только трансформеры или модели из области NLP. Здесь присутствуют модели [Flair](https://github.com/flairNLP/flair) и [AllenNLP](https://github.com/allenai/allennlp) для NLP, речевые модели [Asteroid](https://github.com/asteroid-team/asteroid) и [pyannote](https://github.com/pyannote/pyannote-audio), [timm](https://github.com/rwightman/pytorch-image-models) для компьютерного зрения. diff --git a/chapters/ru/chapter4/2.mdx b/chapters/ru/chapter4/2.mdx index df15ba630..f6650e03f 100644 --- a/chapters/ru/chapter4/2.mdx +++ b/chapters/ru/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter4/3.mdx b/chapters/ru/chapter4/3.mdx index 601aa06b5..22d5b956c 100644 --- a/chapters/ru/chapter4/3.mdx +++ b/chapters/ru/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter4/4.mdx b/chapters/ru/chapter4/4.mdx index ba506976f..500eae310 100644 --- a/chapters/ru/chapter4/4.mdx +++ b/chapters/ru/chapter4/4.mdx @@ -1,5 +1,10 @@ # Создание карточки модели + + Карточка модели — это файл, который, возможно, так же важен, как файлы модели и токенизатора в репозитории моделей. Это центральное описание модели, обеспечивающее возможность повторного использования другими членами сообщества и воспроизводимость результатов, а также предоставляющее платформу для других участников. Документирование процесса обучения и оценки помогает другим понять, чего ожидать от модели, а предоставление достаточной информации об использованных данных, а также о проведенной предварительной и постобработке. По карточке модели ясны ограничения, предубеждения и контексты, в которых модель может быть полезна, а в каких случаях окажется бесполезной. diff --git a/chapters/ru/chapter4/5.mdx b/chapters/ru/chapter4/5.mdx index 58ab3e1ac..7bad05060 100644 --- a/chapters/ru/chapter4/5.mdx +++ b/chapters/ru/chapter4/5.mdx @@ -1,5 +1,10 @@ # Первая часть завершена! + + Вот и закончилась первая часть курса! Часть 2 будет выпущена 15 ноября вместе с большим событием для сообщества, дополнительную информацию см. [здесь] (https://huggingface.co/blog/course-launch-event). Теперь вы сможете точно настроить предварительно обученную модель для задачи классификации текста (одно предложение или пара предложений) и загрузить результат в Model Hub. Чтобы убедиться, что вы усвоили этот первый раздел, вы должны сделать именно это по интересующей вас проблеме (и не обязательно на английском языке, если вы говорите на другом языке)! Вы можете найти помощь на [форумах Hugging Face](https://discuss.huggingface.co/) и поделиться своим проектом в [этой теме](https://discuss.huggingface.co/t/share-your-projects /6803)! diff --git a/chapters/ru/chapter4/6.mdx b/chapters/ru/chapter4/6.mdx index ccbf6425f..7fd56d55d 100644 --- a/chapters/ru/chapter4/6.mdx +++ b/chapters/ru/chapter4/6.mdx @@ -4,6 +4,11 @@ # Итоговый тест по главе + + Проверим, что вы усвоили в результате изучения данной главы! ### 1. Чем ограничиваются модели с Hub? diff --git a/chapters/th/chapter1/1.mdx b/chapters/th/chapter1/1.mdx index 049f79a98..a11b9c706 100644 --- a/chapters/th/chapter1/1.mdx +++ b/chapters/th/chapter1/1.mdx @@ -1,5 +1,10 @@ # บทนำ + + ## ยินดีต้อนรับเข้าสู่คอร์ส 🤗! diff --git a/chapters/th/chapter1/10.mdx b/chapters/th/chapter1/10.mdx index ac4c5f172..c66822858 100644 --- a/chapters/th/chapter1/10.mdx +++ b/chapters/th/chapter1/10.mdx @@ -2,6 +2,11 @@ # คำถามท้ายบท + + บทนี้พูดถึงพื้นฐานค่อนข้างเยอะมาก ไม่ต้องกังวลไปหากคุณไม่เข้าใจรายละเอียดทั้งหมด บทหน้าจะช่วยอธิบายว่าแต่ละอย่างทำงานกันเบื้องหลังอย่างไร ตอนนี้มาทดสอบกันดีกว่าว่าคุณได้เรียนรู้อะไรมาบ้างในบทนี้! diff --git a/chapters/th/chapter1/2.mdx b/chapters/th/chapter1/2.mdx index ccfe0c296..f887aa6ec 100644 --- a/chapters/th/chapter1/2.mdx +++ b/chapters/th/chapter1/2.mdx @@ -1,5 +1,10 @@ # การประมวลผลภาษาธรรมชาติ(หรือเรียกว่า NLP) + + ก่อนจะเข้าเนื้อหาส่วนโมเดล Transformer เรามาดูกันในภาพรวมก่อนว่า NLP คืออะไร แล้วทำไมเราต้องศึกษาและเรียนรู้มัน ## NLP คืออะไร? diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index 9ab990db5..592a43080 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers ชื่อนี้มีดียังไง? - diff --git a/chapters/th/chapter1/4.mdx b/chapters/th/chapter1/4.mdx index dae9bdecb..639e9106d 100644 --- a/chapters/th/chapter1/4.mdx +++ b/chapters/th/chapter1/4.mdx @@ -1,5 +1,10 @@ # Transformer ทำงานยังไง? + + ในส่วนนี้เราจะมาดูกันว่าสถาปัตยกรรมของโมเดล Transformer ทำงานกันยังไง diff --git a/chapters/th/chapter1/5.mdx b/chapters/th/chapter1/5.mdx index e8b38f14a..98f792f97 100644 --- a/chapters/th/chapter1/5.mdx +++ b/chapters/th/chapter1/5.mdx @@ -1,5 +1,10 @@ # โมเดล Encoder + + โมเดล encoder ใช้เพียงส่วน encoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำทุกคำในประโยคได้ โมเดลเหล่านี้ส่วนใหญ่จะใช้ attention แบบสองทาง (หรือเรียกว่า bi-directional attention) และถูกเรียกว่า *โมเดล auto-encoding* diff --git a/chapters/th/chapter1/6.mdx b/chapters/th/chapter1/6.mdx index f3f362950..dbdd3ea56 100644 --- a/chapters/th/chapter1/6.mdx +++ b/chapters/th/chapter1/6.mdx @@ -1,5 +1,10 @@ # โมเดล Decoder + + โมเดล decoder ใช้เพียงส่วน decoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำที่อยู่ตำแหน่งก่อนหน้าในประโยคได้เท่านั้น โมเดลเหล่านี้เรียกว่า *โมเดล auto-regressive* diff --git a/chapters/th/chapter1/7.mdx b/chapters/th/chapter1/7.mdx index 05fdbcae5..3d3663e53 100644 --- a/chapters/th/chapter1/7.mdx +++ b/chapters/th/chapter1/7.mdx @@ -1,5 +1,10 @@ # โมเดล sequence-to-sequence + + โมเดล encoder-decoder (หรือเรียกอีกชื่อหนึ่งว่า *โมเดล sequence-to-sequence*) ใช้ทั้งสองส่วนในสถาปัตยกรรม Transformer ในแต่ละชั้น attention layer ของ encoder จะเข้าถึงคำทั้งหมดในประโยคเริ่มต้นได้ ในขณะที่ attention layer ของ decoder สามารถเข้าถึงได้เพียงคำที่อยู่ตำแหน่งก่อนหน้าคำที่กำหนดใน input เท่านั้น diff --git a/chapters/th/chapter1/8.mdx b/chapters/th/chapter1/8.mdx index 27dc1a643..4705d850a 100644 --- a/chapters/th/chapter1/8.mdx +++ b/chapters/th/chapter1/8.mdx @@ -1,8 +1,8 @@ # ข้อจำกัดจากอคติของข้อมูล - diff --git a/chapters/th/chapter1/9.mdx b/chapters/th/chapter1/9.mdx index 712dfda78..0d667985d 100644 --- a/chapters/th/chapter1/9.mdx +++ b/chapters/th/chapter1/9.mdx @@ -1,5 +1,10 @@ # สรุป + + ในบทนี้คุณได้เรียนรู้การแก้ปัญหา NLP แบบต่าง ๆ โดยใช้ฟังก์ชัน `pipeline()` จาก 🤗 Transformers รวมถึงได้เรียนรู้การค้นหาและใช้งานโมเดลใน Hub อีกทั้งยังเรียนรู้การใช้งาน Inference API ในการทดสอบโมเดลจากเว็บบราวเซอร์ นอกจากนี้เรายังได้พูดเกี่ยวกับการทำงานของโมเดล Transformer และความสำคัญของการใช้งาน transfer learning และการ fine-tune สิ่งสำคัญเลยคือ คุณสามารถใช้โมเดลแบบเต็มรูปแบบหรือจะใช้เพียงแค่ส่วน encoder หรือ decoder ก็ได้ ขึ้นอยู่กับว่าต้องการใช้งานแบบไหน ตารางด้านล่างสรุปการใช้งานไว้ดังนี้: diff --git a/chapters/th/chapter2/1.mdx b/chapters/th/chapter2/1.mdx index dbb602d76..690eeabe1 100644 --- a/chapters/th/chapter2/1.mdx +++ b/chapters/th/chapter2/1.mdx @@ -1,5 +1,10 @@ # บทนำ + + อย่างที่คุณเห็นใน [Chapter 1](/course/chapter1), โดยปกติแล้วโมเดล Transformer นั้นจะมีขนาดใหญ่มาก การเทรนและการใช้งานโมเดลเหล่านี้ที่มีตัวแปร (parameters) เป็นล้านไปจนถึง *หมื่นล้าน* ตัวแปรนั้นเป็นเรื่องที่ค่อนข้างซับซ้อน นอกจากนั้นแล้วการที่มีโมเดลใหม่ๆปล่อยออกมาเกือบทุกวันและแต่ละโมเดลก็มีวิธีการสร้าง (implementation) เป็นของตัวเอง ดังนั้นการจะลองทุกโมเดลนั้นไม่ใช่เรื่องที่ง่ายเลย 🤗 Transformers library สร้างขึ้นมาเพื่อแก้ปัญหานี้ จุดประสงค์ก็คือ การทำให้ไม่ว่าจะโมเดล Transformer ใดก็ตามสามารถโหลด, เทรน, และบันทึก ได้ด้วยการใช้ API เพียงอันเดียว จุดเด่นหลักๆของ library ประกอบด้วย diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 87968254b..1f2e9566b 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/3.mdx b/chapters/th/chapter2/3.mdx index 6b82bd629..075a2bd7c 100644 --- a/chapters/th/chapter2/3.mdx +++ b/chapters/th/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/4.mdx b/chapters/th/chapter2/4.mdx index 79ad132c1..dcb635cca 100644 --- a/chapters/th/chapter2/4.mdx +++ b/chapters/th/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/5.mdx b/chapters/th/chapter2/5.mdx index eac8b97c4..3927688b0 100644 --- a/chapters/th/chapter2/5.mdx +++ b/chapters/th/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/6.mdx b/chapters/th/chapter2/6.mdx index a930b491a..3af1b5216 100644 --- a/chapters/th/chapter2/6.mdx +++ b/chapters/th/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/7.mdx b/chapters/th/chapter2/7.mdx index cc2235fb3..b623c54aa 100644 --- a/chapters/th/chapter2/7.mdx +++ b/chapters/th/chapter2/7.mdx @@ -1,5 +1,10 @@ # การใช้งานเบื้องต้นสำเร็จแล้ว! + + คุณเยี่ยมมากที่เรียนมาได้ถึงตรงนี่ เรามาทบทวนกันว่าในบทนี้คุณ: - เรียนรู้ blocks พื้นฐานของโมเดล Transformer diff --git a/chapters/th/chapter2/8.mdx b/chapters/th/chapter2/8.mdx index f7e51037d..a16542edc 100644 --- a/chapters/th/chapter2/8.mdx +++ b/chapters/th/chapter2/8.mdx @@ -4,6 +4,11 @@ # แบบทดสอบท้ายบท + + ### 1. ลำดับขั้นตอนใน pipeline ของการทำโมเดลด้านภาษา(language modeling)เป็นอย่างไร ? + ใน [Chapter 2](/course/chapter2) เราได้เรียนรู้วิธีการใช้ tokenizers และโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models) ในการทำนาย แต่ถ้าเราต้องการจะใช้ dataset ของเราเองในการ fine-tune โมเดลล่ะ? นั่นคือหัวข้อของบทนี้เลย! คุณจะได้เรียนรู้: {#if fw === 'pt'} diff --git a/chapters/th/chapter3/2.mdx b/chapters/th/chapter3/2.mdx index 61efcd854..65991cc15 100644 --- a/chapters/th/chapter3/2.mdx +++ b/chapters/th/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter3/3.mdx b/chapters/th/chapter3/3.mdx index e510e443d..9f22267cd 100644 --- a/chapters/th/chapter3/3.mdx +++ b/chapters/th/chapter3/3.mdx @@ -2,9 +2,9 @@ # การ Fine-tune โมเดลด้วย Trainer API - diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index e9ccd4611..2d0e13c94 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # การ Fine-tune โมเดลด้วย Keras - diff --git a/chapters/th/chapter3/4.mdx b/chapters/th/chapter3/4.mdx index d5f9f2f94..1b4df3ca0 100644 --- a/chapters/th/chapter3/4.mdx +++ b/chapters/th/chapter3/4.mdx @@ -1,8 +1,8 @@ # การเทรนโมเดลฉบับสมบูรณ์ - diff --git a/chapters/th/chapter3/5.mdx b/chapters/th/chapter3/5.mdx index 60d9a9fe8..98edf4c65 100644 --- a/chapters/th/chapter3/5.mdx +++ b/chapters/th/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fine-tune โมเดลสำเร็จแล้ว! + + สนุกจังเลย! ในสองบทแรกคุณได้เรียนรู้เกี่ยวกับโมเดลและ tokenizers และตอนนี้คุณก็รู้วิธีการ fine-tune โมเดลด้วยข้อมูลของคุณเองแล้ว มาทบทวนกันว่าคุณได้ทำอะไรไปบ้างในบทนี้: {#if fw === 'pt'} diff --git a/chapters/th/chapter3/6.mdx b/chapters/th/chapter3/6.mdx index 521ac7f5a..b6534f22a 100644 --- a/chapters/th/chapter3/6.mdx +++ b/chapters/th/chapter3/6.mdx @@ -4,6 +4,11 @@ # คำถามท้ายบท + + ทดสอบความรู้ที่คุณได้เรียนมาจากบทนี้กัน! ### 1. ใน `emotion` dataset ซึ่งได้รวบรวมข้อความ Twitter ที่มีการ labeled ว่าแต่ละข้อความนั้นเป็นข้อความที่มีอารมณ์แบบใด ลองค้นข้อมูลดูจาก [Hub](https://huggingface.co/datasets)และอ่าน dataset card ดูแล้วตอบว่า ข้อใดไม่ใช่หนึ่งในอารมณ์พื้นฐานของ dataset นี้? diff --git a/chapters/th/chapter4/1.mdx b/chapters/th/chapter4/1.mdx index c4a18cd1e..fb0811c7f 100644 --- a/chapters/th/chapter4/1.mdx +++ b/chapters/th/chapter4/1.mdx @@ -1,5 +1,10 @@ # The Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/) –- เว็บไซต์หลักของเรา –- เป็นแพลตฟอร์มกลางที่ทุกคนสามารถค้นหาโมเดลและชุดข้อมูลที่ล้ำสมัยที่สุด (state-of-the-art) และสามารถนำไปใช้งาน รวมถึงมีส่วนร่วมได้ เรามีโมเดลที่เปิดให้ใช้ได้อย่างเป็นสาธารณะที่หลากหลายมากกว่า 10,000 โมเดลให้เลือกใช้ ซึ่งในบทนี้เราจะมาเจาะลึกลงในเรื่องของโมเดล และเราจะพูดถึงชุดข้อมูลในบทที่ 5 โมเดลใน hub ของเราไม่ได้มีแค่ 🤗 Transformers หรือ NLP เท่านั้น ยังมีโมเดลจาก [Flair](https://github.com/flairNLP/flair) และ [AllenNLP](https://github.com/allenai/allennlp) สำหรับงาน NLP, [Asteroid](https://github.com/asteroid-team/asteroid) และ [pyannote](https://github.com/pyannote/pyannote-audio) สำหรับงานเสียง และ [timm](https://github.com/rwightman/pytorch-image-models) สำหรับงานภาพ และอื่นๆอีกมากมาย diff --git a/chapters/th/chapter4/2.mdx b/chapters/th/chapter4/2.mdx index 22972bd41..90eb59598 100644 --- a/chapters/th/chapter4/2.mdx +++ b/chapters/th/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter4/3.mdx b/chapters/th/chapter4/3.mdx index b63c6e6c4..9f765a4f1 100644 --- a/chapters/th/chapter4/3.mdx +++ b/chapters/th/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter4/4.mdx b/chapters/th/chapter4/4.mdx index 9f609ed56..6dedd79f5 100644 --- a/chapters/th/chapter4/4.mdx +++ b/chapters/th/chapter4/4.mdx @@ -1,5 +1,10 @@ # การสร้างการ์ดโมเดล (model card) + + การ์ดโมเดลเป็นไฟล์ที่เรียกได้ว่าสำคัญพอๆกับไฟล์โมเดลและ tokenizer ใน model repository มันคือคำนิยามส่วนกลางของโมเดล เป็นสิ่งที่ทำให้มั่นใจว่าสมาชิกของชุมชนสามารถนำไปใช้ได้ (reusability) และสามารถทำซ้ำและได้ผลลัพธ์เหมือนเดิมได้ (reproducibility) และมันยังจัดเตรียมแพลตฟอร์มให้สมาชิกคนอื่นๆอาจใช้สร้างสิ่งประดิษฐ์ (artifacts) ของเขาเองได้ การสร้างหนังสืออ้างอิง (documenting) ถึงกระบวนการเทรนและประเมินผลช่วยให้ผู้อื่นเข้าใจถึงสิ่งที่คาดหวังได้จากโมเดลๆหนึ่ง และการให้ข้อมูลที่เพียงพอเกี่ยวกับข้อมูลที่ถูกใช้, กระบวนการเตรียมข้อมูล (preprocessing) และกระบวนการหลังจากใช้โมเดล (postprocessing) ที่ถูกทำมาทำให้มั่นใจถึงขีดจำกัด (limitations), ความลำเอียง (biases) และบริบท (contexts) ที่โมเดลเป็นและไม่เป็น ซึ่งเป็นสิ่งที่เป็นประโยชน์มากถ้าหากสามารถระบุและเข้าใจได้ diff --git a/chapters/th/chapter4/5.mdx b/chapters/th/chapter4/5.mdx index e810397d8..366d0d404 100644 --- a/chapters/th/chapter4/5.mdx +++ b/chapters/th/chapter4/5.mdx @@ -1,5 +1,10 @@ # จบพาร์ทที่ 1! + + นี่คือจุดจบของพาร์ทแรกในคอร์สนี้! พาร์ทที่ 2 จะถูกปล่อยในวันที่ 15 พฤศจิกายน พร้อมกับงานอีเว้นท์ชุมชนใหญ่ ดูรายละเอียดเพิ่มเติม [ที่นี่](https://huggingface.co/blog/course-launch-event) ตอนนี้คุณควรจะสามารถทำ fine-tune โมเดลที่ผ่านการเทรนมาแล้ว (pretrained model) กับปัญหาการจำแนกประเภทตัวหนังสือ (text classification) (แบบประโยคเดี่ยวหรือคู่ประโยค) และอัพโหลดผลลัพธ์ขึ้นสู่ Model Hub ได้ เพื่อที่จะทำให้มั่นใจว่าคุณเชี่ยวชาญส่วนแรกนี้แล้วจริงๆ คุณควรจะฝึกทำแบบนี้เป๊ะๆกับปัญหาที่คุณสนใจ (และไม่จำเป็นจะต้องเป็นภาษาอังกฤษถ้าคุณพูดภาษาอื่น)! คุณสามารถหาความช่วยเหลือได้ใน [Hugging Face forums](https://discuss.huggingface.co/) และแบ่งปันโปรเจคของคุณได้ใน [หัวข้อนี้](https://discuss.huggingface.co/t/share-your-projects/6803) เมื่อคุณทำเสร็จ diff --git a/chapters/th/chapter4/6.mdx b/chapters/th/chapter4/6.mdx index 078accc9a..98a6c8784 100644 --- a/chapters/th/chapter4/6.mdx +++ b/chapters/th/chapter4/6.mdx @@ -4,6 +4,11 @@ # คำถามท้ายบท + + มาทดสอบความรู้ที่คุณได้เรียนในบทนี้กันเถอะ! ### 1. อะไรคือข้อจำกัดของโมเดลบน Hub? diff --git a/chapters/th/chapter6/1.mdx b/chapters/th/chapter6/1.mdx index d4521932a..ada9a8cce 100644 --- a/chapters/th/chapter6/1.mdx +++ b/chapters/th/chapter6/1.mdx @@ -1,5 +1,10 @@ # บทนำ + + ใน[บทที่ 3](/course/chapter3) คุณได้เรียนเกี่ยวกับการ fine-tune โมเดลเพื่อนำไปใช้ในงานที่คุณต้องการ ตอนนั้นเราใช้ตัวตัดคำ(tokenizer)แบบเดียวกับตัวที่มากับโมเดล แต่หากคุณอยากจะเทรนโมเดลตั้งแต่เริ่มต้นเลย คุณควรจะเลือกใช้ตัวตัดคำแบบไหนดี ในกรณีนี้ถ้าคุณใช้ตัวตัดคำที่เทรนจากคลังข้อมูล(corpus)ที่ไม่ใช่ภาษาเดียวกับโมเดลหรือคลังข้อมูลที่มาจากโดเมนอื่น(แปลว่าเนื้อหาของข้อมูลที่ใช้เทรนตัวตัดคำและใช้เทรนโมเดลมีความแตกต่างกันมาก)ก็จะไม่เหมาะสมนัก ตัวอย่างเช่น ตัวตัดคำที่เทรนมาสำหรับตัดคำภาษาอังกฤษ เมื่อนำมาใช้เพื่อตัดคำภาษาญี่ปุ่นก็จะได้ผลลัพธ์ที่ไม่ดี เพราะว่าทั้งสองภาษามีการใช้ช่องว่าง(space)และเครื่องหมายวรรคตอน(punctuation)ที่ต่างกันมาก diff --git a/chapters/th/chapter6/10.mdx b/chapters/th/chapter6/10.mdx index 82890bc9b..1d11caca9 100644 --- a/chapters/th/chapter6/10.mdx +++ b/chapters/th/chapter6/10.mdx @@ -2,6 +2,11 @@ # คำถามท้ายบท + + มาทดสอบความรู้ที่คุณได้เรียนในบทนี้กันเถอะ! ### 1. สถานการณ์ไหนที่คุณควรจะเทรน tokenizer ขึ้นมาใหม่? diff --git a/chapters/th/chapter6/2.mdx b/chapters/th/chapter6/2.mdx index f36d7bb1b..f10b5d003 100644 --- a/chapters/th/chapter6/2.mdx +++ b/chapters/th/chapter6/2.mdx @@ -1,8 +1,8 @@ # การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว - diff --git a/chapters/th/chapter6/3.mdx b/chapters/th/chapter6/3.mdx index 8f7f6c52f..6b248ff1b 100644 --- a/chapters/th/chapter6/3.mdx +++ b/chapters/th/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter6/3b.mdx b/chapters/th/chapter6/3b.mdx index dd9aad386..ce4283a71 100644 --- a/chapters/th/chapter6/3b.mdx +++ b/chapters/th/chapter6/3b.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter6/4.mdx b/chapters/th/chapter6/4.mdx index 1d763e715..f1f4e6cdc 100644 --- a/chapters/th/chapter6/4.mdx +++ b/chapters/th/chapter6/4.mdx @@ -1,8 +1,8 @@ # Normalization และ pre-tokenization - diff --git a/chapters/th/chapter6/5.mdx b/chapters/th/chapter6/5.mdx index ad05d3e21..8a162474b 100644 --- a/chapters/th/chapter6/5.mdx +++ b/chapters/th/chapter6/5.mdx @@ -1,8 +1,8 @@ # Byte-Pair Encoding tokenization - diff --git a/chapters/th/chapter6/6.mdx b/chapters/th/chapter6/6.mdx index e19f40410..25d3073b5 100644 --- a/chapters/th/chapter6/6.mdx +++ b/chapters/th/chapter6/6.mdx @@ -1,8 +1,8 @@ # WordPiece tokenization - diff --git a/chapters/th/chapter6/7.mdx b/chapters/th/chapter6/7.mdx index a19df558c..ea2311e03 100644 --- a/chapters/th/chapter6/7.mdx +++ b/chapters/th/chapter6/7.mdx @@ -1,8 +1,8 @@ # Unigram tokenization - diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index 8b8d62072..adea0fb9b 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -1,8 +1,8 @@ # การสร้าง tokenizer ทีละขั้นตอน - diff --git a/chapters/th/chapter6/9.mdx b/chapters/th/chapter6/9.mdx index 2b0716c2d..620ddaac2 100644 --- a/chapters/th/chapter6/9.mdx +++ b/chapters/th/chapter6/9.mdx @@ -1,5 +1,10 @@ # เรียนจบเรื่อง tokenizer แล้ว! + + เยี่ยมมาก คุณเรียนจบบทนี้แล้ว! หลังจากที่ได้เรียนเกี่ยวกับ tokenizer อย่างละเอียดแล้ว คุณจะ : diff --git a/chapters/tr/chapter1/1.mdx b/chapters/tr/chapter1/1.mdx index 09670c860..2b41bc4a1 100644 --- a/chapters/tr/chapter1/1.mdx +++ b/chapters/tr/chapter1/1.mdx @@ -1,53 +1,58 @@ -# Giriş - -## 🤗 Kursuna Hoşgeldiniz! - - - -Bu kurs size [Hugging Face](https://huggingface.co/) ekosistemindeki kütüphaneleri — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), ve [🤗 Accelerate](https://github.com/huggingface/accelerate) — ayrıca tabiki de [Hugging Face Hub](https://huggingface.co/models) kullanarak Doğal Dil İşleme'yi (NLP) öğretecektir. Kurs tamamen ücretsiz ve reklam bulunmuyor. - -## Beklentiniz ne olmalı? - -Burada kursa genel bakış bulunmaktadır: - -
-Brief overview of the chapters of the course. - -
- -- 1'den 4'üncü bölüme kadar olan kısımda 🤗 Transformers kütüphanesinin ana konseptlerine giriş yapacağız. Kursun bu bölümünün sonunda, Transformer modellerinin nasıl çalıştığını öğrenecek, ve [Hugging Face Hub](https://huggingface.co/models) üzerinden bir modeli nasil kullanabileceğinizi, verisetine ince ayar (fine-tune) yapmayı, ve sonuçlarınızı Hub üzerinde nasıl paylaşacağınızı bileceksiniz! -- Bölüm 5 ila 8, klasik NLP görevlerine dalmadan önce 🤗 Datasets ve 🤗 Tokenizers'in temellerini öğretiyor. Bu bölümün sonunda, en yaygın NLP problemlerini kendiniz çözebileceksiniz. -- 9'dan 12'ye kadar olan bölümler NLP'nin ötesine geçer ve Transformer modellerinin konuşma işleme ve bilgisayarlı görü alanlarındaki problemleri nasıl çözeceğini ele alır. Süreç boyunca, model demolarınızı nasıl oluşturup paylaşacağınızı ve bunları üretim ortamlarında nasıl optimize edeceğinizi öğreneceksiniz. Bu bölümün sonunda, 🤗 Transformers'i (neredeyse) herhangi bir makine öğrenmesi problemine uygulamaya hazır olacaksınız! - -Bu kurs: - -* Python hakkında iyi düzeyde bilgi gerektirir. -* Giriş düzeyinde derin öğrenme derslerinden sonra alınırsa daha iyi olur. Örneğin: [fast.ai](https://www.fast.ai/) [Practical Deep Learning for Coders kursu](https://course.fast.ai/) veya [DeepLearning.AI](https://www.deeplearning.ai/) tarafından verilen herhangi program. -* [PyTorch](https://pytorch.org/) veya [TensorFlow](https://www.tensorflow.org/) ile herhangi bir ön bilgi beklenmiyor, buna rağmen bunlardan birisine aşinalik yardımcı olur. - -Kursu bitirdikten sonra, DeepLearning.AI [Doğal Dil İşleme Uzmanlık](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 ve LSTM gibi bilmeye değer geleneksel NLP modellerinin bulunduğu seriyi izlemenizi tavsiye ediyoruz. - - -## Biz Kimiz? - -Eğitmenler hakkında: - -**Matthew Carrigan** Hugging Face'de Makine Öğrenmesi Mühendisi. Dublin, İrlanda'da yaşıyor ve daha önce Parse.ly'de ML Engineer olarak çalıştı ve onunda öncesinde Doktora sonrası Araştırmacı olarak Trinity College Dublin'de idi. Mevcut AI mimarilerini ölçeklendirerek AGI'a ulaşacağımıza inanmıyor, ancak ne olursa olsun robot ölümsüzlüğü için büyük umutları var. - -**Lysandre Debut** Hugging Face'de Makine Öğrenmesi Mühendisi ve 🤗 Transformers kütüphanesi üzerine erken gelişim aşamasından beri çalışıyor. Hedefi çok basit bir API geliştirerek NLP'yi herkes için ulaşılabilir kılmak. - -**Sylvain Guggeat** Hugging Face'de Araştırma Mühendisi ve 🤗 Transformers kütüphanesinin proje yürütücülerinden biri. Öncesinde Araştırma Bilimci olarak fast.ai'da çalıştı, ve _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ kitabını Jeremy Howard ile birlikte yazdı. Sylvain'in araştırmalarının ana odağı modellerin sınırlı kaynaklarda daha hızlı eğitilmesine izin veren teknikleri tasarlayıp geliştirerek derin öğrenmeyi herkes için ulaşılabilir kılmak. - -**Merve Noyan** Hugging Face'de Developer Advocate, araçlar geliştirerek ve bu araçlar etrafında içerik üreterek makine öğrenmesini herkes için demokratikleştirme üzerine çalışıyor. - -**Lucile Saulnier** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçlarının geliştirilmesi ve desteklenmesi üzerine uğraşıyor. Ayrıca Doğal Dil İşleme içinde Collaborative Training ve BigScience gibi birçok araştırma projesine dahil. - -**Lewis Tunstall** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçları geliştirmeye ve bunlari daha geniş bir topluluk için ulaşılabilir hale getirmeye odaklanmış. Ayrıca yakında gelecek olan [Transformers üzerine O’Reilly kitabının](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. - -**Leandro von Werra** Hugging Face'de Açık-Kaynak takımında Makine Öğrenmesi Mühendisi ve ayrica yakında gelecek olan [Transformers üzerine olan O’Reilly kitabinin](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. Tüm Makine Öğrenmesi stack'inde çalişarak NLP projelerini üretime getiren birkaç yıllık endüstri deneyimine sahiptir. - - -Başlamaya hazır mısın? Bu bölümde, şunları öğreneceksin: -* Metin oluşturma ve sınıflandırma gibi NLP görevlerini çözmek için `pipeline ()` fonksiyonu nasıl kullanılacağı? -* Transformer mimarisi -* Encoder, Decoder, ve Encoder-Decoder mimarilerini nasil ayırt edileceği ve kullanım alanlari +# Giriş + + + +## 🤗 Kursuna Hoşgeldiniz! + + + +Bu kurs size [Hugging Face](https://huggingface.co/) ekosistemindeki kütüphaneleri — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), ve [🤗 Accelerate](https://github.com/huggingface/accelerate) — ayrıca tabiki de [Hugging Face Hub](https://huggingface.co/models) kullanarak Doğal Dil İşleme'yi (NLP) öğretecektir. Kurs tamamen ücretsiz ve reklam bulunmuyor. + +## Beklentiniz ne olmalı? + +Burada kursa genel bakış bulunmaktadır: + +
+Brief overview of the chapters of the course. + +
+ +- 1'den 4'üncü bölüme kadar olan kısımda 🤗 Transformers kütüphanesinin ana konseptlerine giriş yapacağız. Kursun bu bölümünün sonunda, Transformer modellerinin nasıl çalıştığını öğrenecek, ve [Hugging Face Hub](https://huggingface.co/models) üzerinden bir modeli nasil kullanabileceğinizi, verisetine ince ayar (fine-tune) yapmayı, ve sonuçlarınızı Hub üzerinde nasıl paylaşacağınızı bileceksiniz! +- Bölüm 5 ila 8, klasik NLP görevlerine dalmadan önce 🤗 Datasets ve 🤗 Tokenizers'in temellerini öğretiyor. Bu bölümün sonunda, en yaygın NLP problemlerini kendiniz çözebileceksiniz. +- 9'dan 12'ye kadar olan bölümler NLP'nin ötesine geçer ve Transformer modellerinin konuşma işleme ve bilgisayarlı görü alanlarındaki problemleri nasıl çözeceğini ele alır. Süreç boyunca, model demolarınızı nasıl oluşturup paylaşacağınızı ve bunları üretim ortamlarında nasıl optimize edeceğinizi öğreneceksiniz. Bu bölümün sonunda, 🤗 Transformers'i (neredeyse) herhangi bir makine öğrenmesi problemine uygulamaya hazır olacaksınız! + +Bu kurs: + +* Python hakkında iyi düzeyde bilgi gerektirir. +* Giriş düzeyinde derin öğrenme derslerinden sonra alınırsa daha iyi olur. Örneğin: [fast.ai](https://www.fast.ai/) [Practical Deep Learning for Coders kursu](https://course.fast.ai/) veya [DeepLearning.AI](https://www.deeplearning.ai/) tarafından verilen herhangi program. +* [PyTorch](https://pytorch.org/) veya [TensorFlow](https://www.tensorflow.org/) ile herhangi bir ön bilgi beklenmiyor, buna rağmen bunlardan birisine aşinalik yardımcı olur. + +Kursu bitirdikten sonra, DeepLearning.AI [Doğal Dil İşleme Uzmanlık](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 ve LSTM gibi bilmeye değer geleneksel NLP modellerinin bulunduğu seriyi izlemenizi tavsiye ediyoruz. + + +## Biz Kimiz? + +Eğitmenler hakkında: + +**Matthew Carrigan** Hugging Face'de Makine Öğrenmesi Mühendisi. Dublin, İrlanda'da yaşıyor ve daha önce Parse.ly'de ML Engineer olarak çalıştı ve onunda öncesinde Doktora sonrası Araştırmacı olarak Trinity College Dublin'de idi. Mevcut AI mimarilerini ölçeklendirerek AGI'a ulaşacağımıza inanmıyor, ancak ne olursa olsun robot ölümsüzlüğü için büyük umutları var. + +**Lysandre Debut** Hugging Face'de Makine Öğrenmesi Mühendisi ve 🤗 Transformers kütüphanesi üzerine erken gelişim aşamasından beri çalışıyor. Hedefi çok basit bir API geliştirerek NLP'yi herkes için ulaşılabilir kılmak. + +**Sylvain Guggeat** Hugging Face'de Araştırma Mühendisi ve 🤗 Transformers kütüphanesinin proje yürütücülerinden biri. Öncesinde Araştırma Bilimci olarak fast.ai'da çalıştı, ve _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ kitabını Jeremy Howard ile birlikte yazdı. Sylvain'in araştırmalarının ana odağı modellerin sınırlı kaynaklarda daha hızlı eğitilmesine izin veren teknikleri tasarlayıp geliştirerek derin öğrenmeyi herkes için ulaşılabilir kılmak. + +**Merve Noyan** Hugging Face'de Developer Advocate, araçlar geliştirerek ve bu araçlar etrafında içerik üreterek makine öğrenmesini herkes için demokratikleştirme üzerine çalışıyor. + +**Lucile Saulnier** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçlarının geliştirilmesi ve desteklenmesi üzerine uğraşıyor. Ayrıca Doğal Dil İşleme içinde Collaborative Training ve BigScience gibi birçok araştırma projesine dahil. + +**Lewis Tunstall** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçları geliştirmeye ve bunlari daha geniş bir topluluk için ulaşılabilir hale getirmeye odaklanmış. Ayrıca yakında gelecek olan [Transformers üzerine O’Reilly kitabının](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. + +**Leandro von Werra** Hugging Face'de Açık-Kaynak takımında Makine Öğrenmesi Mühendisi ve ayrica yakında gelecek olan [Transformers üzerine olan O’Reilly kitabinin](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. Tüm Makine Öğrenmesi stack'inde çalişarak NLP projelerini üretime getiren birkaç yıllık endüstri deneyimine sahiptir. + + +Başlamaya hazır mısın? Bu bölümde, şunları öğreneceksin: +* Metin oluşturma ve sınıflandırma gibi NLP görevlerini çözmek için `pipeline ()` fonksiyonu nasıl kullanılacağı? +* Transformer mimarisi +* Encoder, Decoder, ve Encoder-Decoder mimarilerini nasil ayırt edileceği ve kullanım alanlari diff --git a/chapters/tr/chapter1/2.mdx b/chapters/tr/chapter1/2.mdx index a9536d43a..55931f2a1 100644 --- a/chapters/tr/chapter1/2.mdx +++ b/chapters/tr/chapter1/2.mdx @@ -1,5 +1,10 @@ # 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. diff --git a/chapters/tr/chapter1/5.mdx b/chapters/tr/chapter1/5.mdx index cc86a135d..b0d850a0c 100644 --- a/chapters/tr/chapter1/5.mdx +++ b/chapters/tr/chapter1/5.mdx @@ -1,18 +1,23 @@ -# Encoder modelleri - - - -Encoder modelleri Transformer modellerinin sadece encoder kısmını kulanır.Her aşamada, attention katmanları ilk cümlenin bütün kelimelerine erişir. Bu modeller genellikle çift yönlü attention olarak nitelendirilir ve genellikle *auto-encoding models* olarak adlandırılır. - -Bu modellerin öneğitimi genellikle verilen cümleyi bozmaya yöneliktir (örnek olarak, içindeki rastgele kelimeleri maskeleyerek) ve model ilk cümleyi bulma veya yeniden oluşturma ile görevlendirilir. - -Encoder modelleri cümle sınıflandırma, varlık tanıma (daha spesifik olarak sözcük sınıflandırma) ve extractive soru yanıtlama gibi cümlenin tam anlaşılmasını gerektiren görevler için uygundur. - - -Bu model ailesinin temsilcileri şunlardır: - -- [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) +# Encoder modelleri + + + + + +Encoder modelleri Transformer modellerinin sadece encoder kısmını kulanır.Her aşamada, attention katmanları ilk cümlenin bütün kelimelerine erişir. Bu modeller genellikle çift yönlü attention olarak nitelendirilir ve genellikle *auto-encoding models* olarak adlandırılır. + +Bu modellerin öneğitimi genellikle verilen cümleyi bozmaya yöneliktir (örnek olarak, içindeki rastgele kelimeleri maskeleyerek) ve model ilk cümleyi bulma veya yeniden oluşturma ile görevlendirilir. + +Encoder modelleri cümle sınıflandırma, varlık tanıma (daha spesifik olarak sözcük sınıflandırma) ve extractive soru yanıtlama gibi cümlenin tam anlaşılmasını gerektiren görevler için uygundur. + + +Bu model ailesinin temsilcileri şunlardır: + +- [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/tr/chapter1/6.mdx b/chapters/tr/chapter1/6.mdx index 4599582de..198db758a 100644 --- a/chapters/tr/chapter1/6.mdx +++ b/chapters/tr/chapter1/6.mdx @@ -1,5 +1,10 @@ # Decoder modelleri + + Decoder modeller, yalnızca bir Transformer modelinin decoderini kullanır. Her aşamada, attention katmanları sadece cümlede kendisinden önce gelen kelimelere erişebilir. Bu modeller *auto-regressive models* olarak isimlendirilir. diff --git a/chapters/tr/chapter2/1.mdx b/chapters/tr/chapter2/1.mdx index aad4d9b37..e0787052a 100644 --- a/chapters/tr/chapter2/1.mdx +++ b/chapters/tr/chapter2/1.mdx @@ -1,5 +1,10 @@ # Giriş + + [Birinci bölümde](/course/chapter1) gördüğünüz gibi, Transformer modelleri genellikle oldukça büyüktür. Milyonlarca, hatta milyarlarca, parametreleri olan bu modellerin eğitimi ve üretime geçirilmesi oldukça karmaşık bir girişimdir. Bunun yanısıra, hemen hemen her gün, herbirinin kendine özgü uygulaması olan yeni modellerin yayınlaması, bu yeni modellerinin hepsini birden denemeyi daha da zor bir hale getirmektedir. 🤗 Transformers kütüphanesi bu sorunu çözmek için oluşturuldu. Bu kütüphanenin amacı, herhangi bir Transformer modelini tek bir API (uygulama programı arabirimi) aracılığı ile yükleme, eğitme, ve kaydetmeyi sağlamaktır. Kütüphanenin başlıca özellikleri şunlardır: diff --git a/chapters/tr/chapter3/1.mdx b/chapters/tr/chapter3/1.mdx index 89e9d47a6..6a666892f 100644 --- a/chapters/tr/chapter3/1.mdx +++ b/chapters/tr/chapter3/1.mdx @@ -2,6 +2,11 @@ # Giriş + + [İkinci bölümde](/course/chapter2) tokenizer ve pretrained modelleri kullanarak nasıl tahmin yapabileceğimizi öğrendik. Fakat, kendi veri setiniz için, pretrained bir modeli nasıl kullanacaksınız ? İşte bu bölümde bunu öğreneceksiniz! Öğrenecekleriniz : {#if fw === 'pt'} diff --git a/chapters/vi/chapter1/1.mdx b/chapters/vi/chapter1/1.mdx index 5741d0ed6..3981c7bcc 100644 --- a/chapters/vi/chapter1/1.mdx +++ b/chapters/vi/chapter1/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + ## Chào mừng tới 🤗 Khoá học! diff --git a/chapters/vi/chapter1/10.mdx b/chapters/vi/chapter1/10.mdx index 1e529bd97..cb5f871a8 100644 --- a/chapters/vi/chapter1/10.mdx +++ b/chapters/vi/chapter1/10.mdx @@ -2,6 +2,11 @@ # Đố vui cuối chương + + Chương này bao gồm rất nhiều mặt! Đừng lo lắng nếu bạn không nắm được tất cả các chi tiết; các chương tiếp theo sẽ giúp bạn hiểu mọi thứ hoạt động như thế nào. Tuy nhiên, trước tiên, hãy kiểm tra những gì bạn đã học được trong chương này! diff --git a/chapters/vi/chapter1/2.mdx b/chapters/vi/chapter1/2.mdx index b5a27a1b7..5abcbbb8e 100644 --- a/chapters/vi/chapter1/2.mdx +++ b/chapters/vi/chapter1/2.mdx @@ -1,5 +1,10 @@ # Xử lý Ngôn Ngữ Tự nhiên + + Trước khi chuyển sang mô hình Transformer, chúng ta hãy cùng tìm hiểu nhanh tổng quan về Xử lý Ngôn ngữ Tự nhiên là gì và tại sao chúng ta quan tâm đến lĩnh vực này. ## Xử lý Ngôn ngữ Tự nhiên (NLP) là gì? diff --git a/chapters/vi/chapter1/3.mdx b/chapters/vi/chapter1/3.mdx index 677b4f48b..90e9b3de5 100644 --- a/chapters/vi/chapter1/3.mdx +++ b/chapters/vi/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers có thể làm những gì? - diff --git a/chapters/vi/chapter1/4.mdx b/chapters/vi/chapter1/4.mdx index b21901fd4..00660f132 100644 --- a/chapters/vi/chapter1/4.mdx +++ b/chapters/vi/chapter1/4.mdx @@ -1,5 +1,10 @@ # Cơ chế hoạt động của Transformer? + + Trong phần này, chúng ta sẽ tìm hiểu kiến trúc của các mô hình Transformer. ## Lịch sử phát triển của Transformers diff --git a/chapters/vi/chapter1/5.mdx b/chapters/vi/chapter1/5.mdx index f2cb94448..bc4aa15f1 100644 --- a/chapters/vi/chapter1/5.mdx +++ b/chapters/vi/chapter1/5.mdx @@ -1,5 +1,10 @@ # Các mô hình mã hóa + + Các mô hình mã hóa chỉ sử dụng phần mã hóa của mô hình Transformer. Ở mỗi bước, các lớp attention có thể truy cập tất cả các từ trong câu ban đầu. Những mô hình này thường có đặc trưng là chú ý "hai chiều" và thường được gọi là mô hình _auto-encoding_ hay _mã hóa tự động_. diff --git a/chapters/vi/chapter1/6.mdx b/chapters/vi/chapter1/6.mdx index fa577ea9e..3ddd84eb9 100644 --- a/chapters/vi/chapter1/6.mdx +++ b/chapters/vi/chapter1/6.mdx @@ -1,5 +1,10 @@ # CCác mô hình giải mã + + Mô hình giải mã chỉ sử dụng phần giải mã của mô hình Transformer. Ở mỗi bước, đối với một từ nhất định, các lớp attention chỉ có thể truy cập các từ được đặt trước nó trong câu. Những mô hình này thường được gọi là mô hình _auto-regressive_ hay _hồi quy tự động_. diff --git a/chapters/vi/chapter1/7.mdx b/chapters/vi/chapter1/7.mdx index d2c894cfb..18f3f4c60 100644 --- a/chapters/vi/chapter1/7.mdx +++ b/chapters/vi/chapter1/7.mdx @@ -1,5 +1,10 @@ # Các mô hình mã hoá-giải mã + + Các mô hình mã hóa-giải mã (còn được gọi là _mô hình chuỗi-sang-chuỗi_) sử dụng cả hai phần của kiến trúc Transformer. Ở mỗi bước, các lớp attention của phần mã hóa có thể truy cập tất cả các từ trong câu ban đầu, trong khi các lớp attention của phần giải mã chỉ có thể truy cập các từ được đặt trước một từ nhất định trong đầu vào. diff --git a/chapters/vi/chapter1/8.mdx b/chapters/vi/chapter1/8.mdx index 3ce04e0fd..e9a22fc58 100644 --- a/chapters/vi/chapter1/8.mdx +++ b/chapters/vi/chapter1/8.mdx @@ -1,8 +1,8 @@ # Thiên kiến và hạn chế - + Trong chương này, bạn đã biết cách tiếp cận các tác vụ NLP khác nhau bằng cách sử dụng hàm `pipeline()` cấp cao từ 🤗 Transformers. Bạn cũng đã biết cách tìm kiếm và sử dụng các mô hình trong Hub, cũng như cách sử dụng Inference API để kiểm tra các mô hình trực tiếp trong trình duyệt của mình. Chúng ta đã thảo luận về cách các mô hình Transformer hoạt động ở cấp độ cao và nói về tầm quan trọng của việc học chuyển giao và tinh chỉnh. Một khía cạnh quan trọng, đó là bạn có thể sử dụng kiến trúc đầy đủ hoặc chỉ phần mã hóa hoặc giải mã, tùy thuộc vào loại tác vụ bạn muốn giải quyết. Bảng dưới đây tóm tắt khía cạnh này: diff --git a/chapters/vi/chapter2/1.mdx b/chapters/vi/chapter2/1.mdx index e3fd44f8b..8bbce9975 100644 --- a/chapters/vi/chapter2/1.mdx +++ b/chapters/vi/chapter2/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + Như bạn đã thấy trong [Chương 1](/course/chapter1), các mô hình Transformer thường rất lớn. Với hàng triệu đến hàng chục *tỷ* thông số, việc huấn luyện và triển khai các mô hình này là một công việc phức tạp. Hơn nữa, với việc các mô hình mới được phát hành gần như hàng ngày và mỗi mô hình có cách triển khai riêng, việc thử tất cả chúng không phải là nhiệm vụ dễ dàng. Thư viện 🤗 Transformers được tạo ra để giải quyết vấn đề này. Mục tiêu của nó là cung cấp một API duy nhất mà qua đó bất kỳ mô hình Transformer nào cũng có thể được tải, huấn luyện, và lưu. Các tính năng chính của thư viện gồm: diff --git a/chapters/vi/chapter2/2.mdx b/chapters/vi/chapter2/2.mdx index b27e6365f..0c43c6f9b 100644 --- a/chapters/vi/chapter2/2.mdx +++ b/chapters/vi/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/3.mdx b/chapters/vi/chapter2/3.mdx index 4d7021969..48f384d3d 100644 --- a/chapters/vi/chapter2/3.mdx +++ b/chapters/vi/chapter2/3.mdx @@ -4,9 +4,9 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/5.mdx b/chapters/vi/chapter2/5.mdx index 40c583154..78a9b0b5c 100644 --- a/chapters/vi/chapter2/5.mdx +++ b/chapters/vi/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/6.mdx b/chapters/vi/chapter2/6.mdx index 8ffd62368..52c8c2caf 100644 --- a/chapters/vi/chapter2/6.mdx +++ b/chapters/vi/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/7.mdx b/chapters/vi/chapter2/7.mdx index a96ff4fdf..6ea45cada 100644 --- a/chapters/vi/chapter2/7.mdx +++ b/chapters/vi/chapter2/7.mdx @@ -1,5 +1,10 @@ # Hoàn thành cách sử dụng cơ bản! + + Thật tuyệt vời khi theo dõi khóa học đến đây! Tổng kết lại, trong chương này bạn: - Đã học các khối cơ bản của mô hình Transformer. diff --git a/chapters/vi/chapter2/8.mdx b/chapters/vi/chapter2/8.mdx index daf41d7ef..948a0f573 100644 --- a/chapters/vi/chapter2/8.mdx +++ b/chapters/vi/chapter2/8.mdx @@ -4,6 +4,11 @@ # Đố vui cuối chương + + ### 1. Thứ tự của một quy trình mô hình hóa ngôn ngữ là gì? + Trong [Chương 2](/course/chapter2), chúng ta đã khám phá cách sử dụng tokenizer và các mô hình huấn luyện trước để đưa ra dự đoán. Nhưng nếu bạn muốn tinh chỉnh một mô hình được huấn luyện trước cho tập dữ liệu của riêng mình thì sao? Đó là chủ đề của chương này! Bạn sẽ học: {#if fw === 'pt'} diff --git a/chapters/vi/chapter3/2.mdx b/chapters/vi/chapter3/2.mdx index dd3d633bb..dfbf3a1bd 100644 --- a/chapters/vi/chapter3/2.mdx +++ b/chapters/vi/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter3/3.mdx b/chapters/vi/chapter3/3.mdx index 980165a91..c254c523c 100644 --- a/chapters/vi/chapter3/3.mdx +++ b/chapters/vi/chapter3/3.mdx @@ -2,9 +2,9 @@ # Tinh chỉnh một mô hình với Trainer API - diff --git a/chapters/vi/chapter3/3_tf.mdx b/chapters/vi/chapter3/3_tf.mdx index a451985d1..29ec33972 100644 --- a/chapters/vi/chapter3/3_tf.mdx +++ b/chapters/vi/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Tinh chỉnh một mô hình với Keras - diff --git a/chapters/vi/chapter3/4.mdx b/chapters/vi/chapter3/4.mdx index 24e47a91f..8e42a0572 100644 --- a/chapters/vi/chapter3/4.mdx +++ b/chapters/vi/chapter3/4.mdx @@ -1,8 +1,8 @@ # Bản huấn luyện hoàn chỉnh - diff --git a/chapters/vi/chapter3/5.mdx b/chapters/vi/chapter3/5.mdx index d50018fda..c8bf597b9 100644 --- a/chapters/vi/chapter3/5.mdx +++ b/chapters/vi/chapter3/5.mdx @@ -2,6 +2,11 @@ # Tỉnh chỉnh, thử xem! + + Trong hai chương đầu tiên, bạn đã học về các mô hình và tokenizer, và bây giờ bạn biết cách tinh chỉnh chúng cho dữ liệu của riêng bạn. Tóm lại, trong chương này bạn: {#if fw === 'pt'} diff --git a/chapters/vi/chapter3/6.mdx b/chapters/vi/chapter3/6.mdx index 2a87303de..f011e6f95 100644 --- a/chapters/vi/chapter3/6.mdx +++ b/chapters/vi/chapter3/6.mdx @@ -4,6 +4,11 @@ # Đố vui cuối chương + + Kiểm tra những gì bạn đã học trong chương này! ### 1. Tập dữ liệu `emotion` chứa các tin nhắn Twitter được gắn nhãn cảm xúc. Tìm kiếm nó trong [Hub](https://huggingface.co/datasets) và đọc thẻ tập dữ liệu. Cảm xúc nào trong số này không phải là một trong những cảm xúc cơ bản của nó? diff --git a/chapters/vi/chapter4/1.mdx b/chapters/vi/chapter4/1.mdx index 952b5193a..df3f81f1e 100644 --- a/chapters/vi/chapter4/1.mdx +++ b/chapters/vi/chapter4/1.mdx @@ -1,5 +1,10 @@ # Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/) –- trang web chính của chúng tôi –- là một nền tảng tập trung cho phép mọi người khám phá, sử dụng và đóng góp các mô hình và bộ dữ liệu hiện đại nhất. Nó lưu trữ nhiều mô hình khác nhau, với hơn 10,000 mô hình được công bố rộng rãi. Chúng ta sẽ tập trung vào các mô hình trong chương này và xem xét các tập dữ liệu trong Chương 5. Các mô hình trong Hub không giới hạn ở 🤗 Transformer hoặc thậm chí là NLP. Có các mô hình từ [Flair](https://github.com/flairNLP/flair) và [AllenNLP](https://github.com/allenai/allennlp) cho NLP, [Asteroid](https://github.com/asteroid-team/asteroid) và [pyannote](https://github.com/pyannote/pyannote-audio) cho âm thanh và [timm](https://github.com/rwightman/pytorch-image-models) cho hình ảnh. diff --git a/chapters/vi/chapter4/2.mdx b/chapters/vi/chapter4/2.mdx index d094cb86b..f0d35fd18 100644 --- a/chapters/vi/chapter4/2.mdx +++ b/chapters/vi/chapter4/2.mdx @@ -4,9 +4,9 @@ {#if fw === 'pt'} - + Thẻ mô hình là một tệp được cho là quan trọng như mô hình và tệp tokenizer trong kho lưu trữ mô hình. Đây là định nghĩa chủ đạo của mô hình, đảm bảo khả năng tái sử dụng của các thành viên trong cộng đồng và khả năng tái tạo kết quả, đồng thời cung cấp một nền tảng mà các thành viên khác có thể xây dựng các tác phẩm của họ. Việc ghi lại quá trình huấn luyện và đánh giá giúp những người khác hiểu những gì mong đợi ở một mô hình - và cung cấp đầy đủ thông tin liên quan đến dữ liệu đã được sử dụng và quá trình tiền xử lý và hậu xử lý đã được thực hiện đảm bảo rằng các hạn chế, thành kiến ​​và bối cảnh trong đó mô hình đang và không hữu ích có thể được xác định và hiểu. diff --git a/chapters/vi/chapter4/5.mdx b/chapters/vi/chapter4/5.mdx index 15f1a2ef2..062cc560d 100644 --- a/chapters/vi/chapter4/5.mdx +++ b/chapters/vi/chapter4/5.mdx @@ -1,5 +1,10 @@ # Hoàn thành phần 1! + + Đây là mục cuối của phần đầu tiên trong khóa học! Phần 2 sẽ ra mắt vào ngày 15/11 tới đây với một sự kiện cộng đồng lớn, xem thêm thông tin [tại đây](https://huggingface.co/blog/course-launch-event). Giờ đây, bạn có thể tinh chỉnh mô hình được huấn luyện trước về vấn đề phân loại văn bản (đơn hoặc cặp câu) và tải kết quả lên Model Hub. Để đảm bảo bạn thành thạo phần đầu tiên này, bạn nên làm chính xác phần đó đối với một vấn đề mà bạn quan tâm (và không nhất thiết phải bằng tiếng Anh nếu bạn nói một ngôn ngữ khác)! Bạn có thể tìm trợ giúp trong [diễn đàn Hugging Face](https://discuss.huggingface.co/) và chia sẻ dự án của mình trong [chủ đề này](https://discuss.huggingface.co/t/share-your-projects/6803) sau khi bạn hoàn thành. diff --git a/chapters/vi/chapter4/6.mdx b/chapters/vi/chapter4/6.mdx index ab9c55658..d3a12f468 100644 --- a/chapters/vi/chapter4/6.mdx +++ b/chapters/vi/chapter4/6.mdx @@ -4,6 +4,11 @@ # Đố vui cuối chương + + Hãy kiểm tra những gì bạn đã học được trong chương này! ### 1. Các mô hình tải lên trên Hub có giới hạn gì? diff --git a/chapters/vi/chapter5/1.mdx b/chapters/vi/chapter5/1.mdx index d918cb67e..924e85888 100644 --- a/chapters/vi/chapter5/1.mdx +++ b/chapters/vi/chapter5/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + Trong [Chương 3](/course/chapter3), bạn sẽ lần đầu được trải nghiệm thư viện 🤗 Datasets và thấy rằng có ba bước chính khi tinh chỉnh một mô hình: 1. Tải tập dữ liệu từ Hugging Face Hub. diff --git a/chapters/vi/chapter5/2.mdx b/chapters/vi/chapter5/2.mdx index eea04d929..7378423ca 100644 --- a/chapters/vi/chapter5/2.mdx +++ b/chapters/vi/chapter5/2.mdx @@ -1,8 +1,8 @@ # Nếu như dữ liệu của ta không trên Hub thì sao? - diff --git a/chapters/vi/chapter5/3.mdx b/chapters/vi/chapter5/3.mdx index eee6bb891..24b7115c8 100644 --- a/chapters/vi/chapter5/3.mdx +++ b/chapters/vi/chapter5/3.mdx @@ -1,8 +1,8 @@ # Sắp xếp dữ liệu - diff --git a/chapters/vi/chapter5/4.mdx b/chapters/vi/chapter5/4.mdx index b5a2b2348..bb0c9bbd5 100644 --- a/chapters/vi/chapter5/4.mdx +++ b/chapters/vi/chapter5/4.mdx @@ -1,8 +1,8 @@ # Dữ liệu lớn? 🤗 Bộ dữ liệu để giải cứu! - diff --git a/chapters/vi/chapter5/6.mdx b/chapters/vi/chapter5/6.mdx index e6803fb27..6a0d9423e 100644 --- a/chapters/vi/chapter5/6.mdx +++ b/chapters/vi/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter5/7.mdx b/chapters/vi/chapter5/7.mdx index 02ae4bd7e..0844001a3 100644 --- a/chapters/vi/chapter5/7.mdx +++ b/chapters/vi/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets, kiểm tra nào! + + Chà, đó là một chuyến tham quan khá thú vị qua thư viện 🤗 Datasets - chúc mừng bạn đã đi được xa như vậy! Với kiến thức bạn đã thu được từ chương này, bạn sẽ có thể: - Tải bộ dữ liệu từ bất kỳ đâu, có thể là Hugging Face Hub, máy tính xách tay của bạn hoặc máy chủ từ xa tại công ty của bạn. diff --git a/chapters/vi/chapter5/8.mdx b/chapters/vi/chapter5/8.mdx index 40fafb58d..5303d1f5e 100644 --- a/chapters/vi/chapter5/8.mdx +++ b/chapters/vi/chapter5/8.mdx @@ -2,6 +2,11 @@ # Đố vui cuối chương + + Chương này bao gồm rất nhiều nội dung! Đừng lo lắng nếu bạn không nắm được tất cả các chi tiết; các chương tiếp theo sẽ giúp bạn hiểu mọi thứ hoạt động như thế nào. Tuy nhiên, trước khi tiếp tục, hãy kiểm tra những gì bạn đã học được trong chương này. diff --git a/chapters/vi/chapter6/1.mdx b/chapters/vi/chapter6/1.mdx index 6540876b7..bcfe7eada 100644 --- a/chapters/vi/chapter6/1.mdx +++ b/chapters/vi/chapter6/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + Trong [Chương 3](/course/chapter3), chúng ta đã xem xét cách tinh chỉnh một mô hình trong một tác vụ nhất định. Khi làm điều đó, chúng ta sử dụng cùng một trình tokenizer mà mô hình đã được huấn luyện trước - nhưng chúng ra phải làm gì khi muốn huấn luyện một mô hình từ đầu? Trong những trường hợp này, việc sử dụng trình tokenizer đã được huấn luyện trước trên một kho ngữ liệu từ một lĩnh vực hoặc ngôn ngữ khác thường là không tối ưu. Ví dụ: một tokenizer được huấn luyện trên ngữ liệu tiếng Anh sẽ hoạt động kém trên ngữ liệu văn bản tiếng Nhật vì việc sử dụng dấu cách và dấu câu trong hai ngôn ngữ rất khác nhau. Trong chương này, bạn sẽ học cách huấn luyện một trình tokenize hoàn toàn mới trên kho ngữ liệu văn bản, do đó, nó có thể được sử dụng để huấn luyện trước một mô hình ngôn ngữ. Tất cả điều này sẽ được thực hiện với sự trợ giúp của thư viện [🤗 Tokenizers](https://github.com/huggingface/tokenizers), nơi cung cấp các tokenizer "nhanh" trong thư viện [🤗 Transformers](https://github.com/huggingface/transformers). Chúng ta sẽ xem xét kỹ các tính năng mà thư viện này cung cấp và khám phá cách các bản tokenizer nhanh khác so với các phiên bản "chậm". diff --git a/chapters/vi/chapter6/10.md b/chapters/vi/chapter6/10.md index 0a678358a..f7ea9745d 100644 --- a/chapters/vi/chapter6/10.md +++ b/chapters/vi/chapter6/10.md @@ -2,6 +2,11 @@ # Đố vui cuối chương + + Let's test what you learned in this chapter! ### 1. When should you train a new tokenizer? diff --git a/chapters/vi/chapter6/2.mdx b/chapters/vi/chapter6/2.mdx index 2f0287bcd..ed7611d0a 100644 --- a/chapters/vi/chapter6/2.mdx +++ b/chapters/vi/chapter6/2.mdx @@ -1,8 +1,8 @@ # Huấn luyện một tokenizer mới từ cái cũ - diff --git a/chapters/vi/chapter6/3.md b/chapters/vi/chapter6/3.md index c43fead52..41576f859 100644 --- a/chapters/vi/chapter6/3.md +++ b/chapters/vi/chapter6/3.md @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter6/3b.md b/chapters/vi/chapter6/3b.md index 595235c8b..c0f64d63f 100644 --- a/chapters/vi/chapter6/3b.md +++ b/chapters/vi/chapter6/3b.md @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter6/4.md b/chapters/vi/chapter6/4.md index 6710ed42a..b4a7757a1 100644 --- a/chapters/vi/chapter6/4.md +++ b/chapters/vi/chapter6/4.md @@ -1,8 +1,8 @@ # Chuẩn hoá và tiền tokenize - diff --git a/chapters/vi/chapter6/5.md b/chapters/vi/chapter6/5.md index a9f070b0e..18b189bc5 100644 --- a/chapters/vi/chapter6/5.md +++ b/chapters/vi/chapter6/5.md @@ -1,8 +1,8 @@ # Byte-Pair Encoding tokenization - diff --git a/chapters/vi/chapter6/6.md b/chapters/vi/chapter6/6.md index d4152cd5e..7e60dabb0 100644 --- a/chapters/vi/chapter6/6.md +++ b/chapters/vi/chapter6/6.md @@ -1,8 +1,8 @@ # WordPiece tokenization - diff --git a/chapters/vi/chapter6/7.md b/chapters/vi/chapter6/7.md index e23d6f473..2a4d6f2f3 100644 --- a/chapters/vi/chapter6/7.md +++ b/chapters/vi/chapter6/7.md @@ -1,8 +1,8 @@ # Unigram tokenization - diff --git a/chapters/vi/chapter6/8.md b/chapters/vi/chapter6/8.md index 9e54d568a..f5f4f3de1 100644 --- a/chapters/vi/chapter6/8.md +++ b/chapters/vi/chapter6/8.md @@ -1,8 +1,8 @@ # Xây dựng từng khối tokenizer - diff --git a/chapters/vi/chapter6/9.mdx b/chapters/vi/chapter6/9.mdx index cc16eeeec..63f567381 100644 --- a/chapters/vi/chapter6/9.mdx +++ b/chapters/vi/chapter6/9.mdx @@ -1,5 +1,10 @@ # Tokenizers, kiểm tra nào! + + Chúc mừng bạn đã hoàn thành chương này! Sau khi tìm hiểu sâu về tokenizer, bạn nên: diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index 4ab545a0c..fcd439329 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -1,52 +1,57 @@ -# 本章简介 - -## 欢迎来到🤗课程 - - - -本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 - - -## 有什么是值得期待的? - -以下是课程的简要概述: - -
-Brief overview of the chapters of the course. - -
- -- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 -- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 -- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! - -这个课程: - -* 需要良好的 Python 知识 -* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) -* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 - -完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](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),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! - -## 我们是谁? - -关于作者: - -**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 - -**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 - -**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 - -**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 - -**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 - -**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[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 项目投入生产。 - -你准备好了吗?在本章中,您将学习: -* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 -* 关于 Transformer 架构 -* 如何区分编码器、解码器和编码器-解码器架构和用例 +# 本章简介 + + + +## 欢迎来到🤗课程 + + + +本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 + + +## 有什么是值得期待的? + +以下是课程的简要概述: + +
+Brief overview of the chapters of the course. + +
+ +- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 +- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 +- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! + +这个课程: + +* 需要良好的 Python 知识 +* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) +* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 + +完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](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),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! + +## 我们是谁? + +关于作者: + +**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 + +**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 + +**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 + +**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 + +**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 + +**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[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 项目投入生产。 + +你准备好了吗?在本章中,您将学习: +* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 +* 关于 Transformer 架构 +* 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh-CN/chapter1/10.mdx b/chapters/zh-CN/chapter1/10.mdx index 23f768115..3e2951036 100644 --- a/chapters/zh-CN/chapter1/10.mdx +++ b/chapters/zh-CN/chapter1/10.mdx @@ -2,6 +2,11 @@ # 章末小测试 + + 这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 让我们来测试一下你在这一章学到了什么! diff --git a/chapters/zh-CN/chapter1/2.mdx b/chapters/zh-CN/chapter1/2.mdx index 1b5ee0ea6..982425c76 100644 --- a/chapters/zh-CN/chapter1/2.mdx +++ b/chapters/zh-CN/chapter1/2.mdx @@ -1,20 +1,25 @@ -# 自然语言处理 - -在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 - -## 什么是自然语言处理? - -NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 - -以下是常见 NLP 任务的列表,每个任务都有一些示例: - -- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 -- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) -- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 -- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 -- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 - -NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 -## 为什么具有挑战性? - +# 自然语言处理 + + + +在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 + +## 什么是自然语言处理? + +NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 + +以下是常见 NLP 任务的列表,每个任务都有一些示例: + +- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 +- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) +- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 +- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 +- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 + +NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 +## 为什么具有挑战性? + 计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 076263ba4..ec5820720 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers能做什么? - diff --git a/chapters/zh-CN/chapter1/4.mdx b/chapters/zh-CN/chapter1/4.mdx index 45641e71e..a2f8a839f 100644 --- a/chapters/zh-CN/chapter1/4.mdx +++ b/chapters/zh-CN/chapter1/4.mdx @@ -1,172 +1,177 @@ -# Transformers 是如何工作的? - -在本节中,我们将深入了解 Transformer 模型的架构。 - -## 一点Transformers的发展历史 - -以下是 Transformer 模型(简短)历史中的一些关键节点: - -
-A brief chronology of Transformers models. - -
- -[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 - -- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 - -- **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% 的性能 - -- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) - -- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) - -这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: - -- GPT-like (也被称作自回归Transformer模型) -- BERT-like (也被称作自动编码Transformer模型) -- BART/T5-like (也被称作序列到序列的 Transformer模型) - -稍后我们将更深入地探讨这些分类。 - -## Transformers是语言模型 - -上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! - -这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 - -任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 - -
-Example of causal language modeling in which the next word from a sentence is predicted. - -
- -另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 - -
-Example of masked language modeling in which a masked word from a sentence is predicted. - -
- -## Transformer是大模型 - -除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 - -
-Number of parameters of recent Transformers models -
- -不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 - -
-The carbon footprint of a large language model. - -
- - - -Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 - -想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! - -这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 - - -## 迁移学习 - - - -*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 - -
-The pretraining of a language model is costly in both time and money. - -
- -这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 - -另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: - -* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 -* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 -* 出于同样的原因,获得好结果所需的时间和资源要少得多 - -例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 - -
-The fine-tuning of a language model is cheaper than pretraining in both time and money. - -
- -因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 - -这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 - -## 一般的体系结构 -在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 - - - -## 介绍 - -该模型主要由两个块组成: - -* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 -* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 - -
-Architecture of a Transformers models - -
- -这些部件中的每一个都可以独立使用,具体取决于任务: - -* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 -* **Decoder-only models**: 适用于生成任务,如文本生成。 -* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 - -在后面的部分中,我们将单独地深入研究这些体系结构。 - -## 注意力层 - -Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 - -把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 - -同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 - -现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 - -## 原始的结构 - -Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 - -为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 - -最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: - -
-Architecture of a Transformers models - -
- -注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 - -也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 - -## 架构与参数 - -在本课程中,当我们深入探讨Transformers模型时,您将看到 -架构、参数和模型 -。 这些术语的含义略有不同: - -* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 -* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 -* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 - -例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." +# Transformers 是如何工作的? + + + +在本节中,我们将深入了解 Transformer 模型的架构。 + +## 一点Transformers的发展历史 + +以下是 Transformer 模型(简短)历史中的一些关键节点: + +
+A brief chronology of Transformers models. + +
+ +[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 + +- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 + +- **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% 的性能 + +- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) + +- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) + +这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: + +- GPT-like (也被称作自回归Transformer模型) +- BERT-like (也被称作自动编码Transformer模型) +- BART/T5-like (也被称作序列到序列的 Transformer模型) + +稍后我们将更深入地探讨这些分类。 + +## Transformers是语言模型 + +上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! + +这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 + +任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformer是大模型 + +除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 + +
+Number of parameters of recent Transformers models +
+ +不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 + +
+The carbon footprint of a large language model. + +
+ + + +Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 + +想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! + +这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 + + +## 迁移学习 + + + +*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 + +
+The pretraining of a language model is costly in both time and money. + +
+ +这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 + +另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: + +* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 +* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 +* 出于同样的原因,获得好结果所需的时间和资源要少得多 + +例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 + +这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 + +## 一般的体系结构 +在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 + + + +## 介绍 + +该模型主要由两个块组成: + +* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 +* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 + +
+Architecture of a Transformers models + +
+ +这些部件中的每一个都可以独立使用,具体取决于任务: + +* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 +* **Decoder-only models**: 适用于生成任务,如文本生成。 +* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 + +在后面的部分中,我们将单独地深入研究这些体系结构。 + +## 注意力层 + +Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 + +把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 + +同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 + +现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 + +## 原始的结构 + +Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 + +为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 + +最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: + +
+Architecture of a Transformers models + +
+ +注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 + +也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 + +## 架构与参数 + +在本课程中,当我们深入探讨Transformers模型时,您将看到 +架构、参数和模型 +。 这些术语的含义略有不同: + +* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 +* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 +* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 + +例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." diff --git a/chapters/zh-CN/chapter1/5.mdx b/chapters/zh-CN/chapter1/5.mdx index 7aa765ec2..7c235523d 100644 --- a/chapters/zh-CN/chapter1/5.mdx +++ b/chapters/zh-CN/chapter1/5.mdx @@ -1,5 +1,10 @@ # “编码器”模型 + + “编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 diff --git a/chapters/zh-CN/chapter1/6.mdx b/chapters/zh-CN/chapter1/6.mdx index 2de4c44a6..1136f6ebf 100644 --- a/chapters/zh-CN/chapter1/6.mdx +++ b/chapters/zh-CN/chapter1/6.mdx @@ -1,5 +1,10 @@ # “解码器”模型 + + “解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 diff --git a/chapters/zh-CN/chapter1/7.mdx b/chapters/zh-CN/chapter1/7.mdx index 99dc00eea..984488b2d 100644 --- a/chapters/zh-CN/chapter1/7.mdx +++ b/chapters/zh-CN/chapter1/7.mdx @@ -1,16 +1,21 @@ -# 序列到序列模型 - - - -编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 - -这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[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) +# 序列到序列模型 + + + + + +编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 + +这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[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) diff --git a/chapters/zh-CN/chapter1/8.mdx b/chapters/zh-CN/chapter1/8.mdx index 707731892..5bd23953b 100644 --- a/chapters/zh-CN/chapter1/8.mdx +++ b/chapters/zh-CN/chapter1/8.mdx @@ -1,8 +1,8 @@ # Bias and limitations - diff --git a/chapters/zh-CN/chapter1/9.mdx b/chapters/zh-CN/chapter1/9.mdx index 16c5ab6ad..142153f49 100644 --- a/chapters/zh-CN/chapter1/9.mdx +++ b/chapters/zh-CN/chapter1/9.mdx @@ -1,11 +1,16 @@ -# 总结 - -在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 - -我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: - -| 模型 | 示例 | 任务| -| ---- | ---- |----| -| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| -| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| +# 总结 + + + +在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 + +我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: + +| 模型 | 示例 | 任务| +| ---- | ---- |----| +| 编码器 | 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/zh-CN/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx index d0ab0e0d9..f0aa1b797 100644 --- a/chapters/zh-CN/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -1,18 +1,23 @@ -# 本章简介 - -正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 - -创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: -- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 -- **灵活**:所有型号的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它们各自的机器学习(ML)框架中的任何其他模型一样进行处理。 -- **简单**:当前位置整个库几乎没有任何摘要。“都在一个文件中”是一个核心概念:模型的正向传递完全定义在一个文件中,因此代码本身是可以理解的,并且是可以破解的。 - -最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的菜单。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 - -本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和tokenizer分词器来复制[Chapter 1](/course/chapter1)中引入的函数pipeline(). 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 - -然后我们来看看标记器API,它是pipeline()函数的另一个主要组件。它是作用分词器负责第一个和最后一个处理步骤,处理从文本到神经网络数字输入的转换,以及在需要时转换回文本。最后,我们将向您展示如何处理在一个准备好的批处理中通过一个模型发送多个句子的问题,然后详细介绍pipeline()函数。 - - -⚠️ 为了从模型集线器和🤗Transformers的所有可用功能中获益,我们建议creating an account. +# 本章简介 + + + +正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 + +创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: +- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 +- **灵活**:所有型号的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它们各自的机器学习(ML)框架中的任何其他模型一样进行处理。 +- **简单**:当前位置整个库几乎没有任何摘要。“都在一个文件中”是一个核心概念:模型的正向传递完全定义在一个文件中,因此代码本身是可以理解的,并且是可以破解的。 + +最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的菜单。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 + +本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和tokenizer分词器来复制[Chapter 1](/course/chapter1)中引入的函数pipeline(). 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 + +然后我们来看看标记器API,它是pipeline()函数的另一个主要组件。它是作用分词器负责第一个和最后一个处理步骤,处理从文本到神经网络数字输入的转换,以及在需要时转换回文本。最后,我们将向您展示如何处理在一个准备好的批处理中通过一个模型发送多个句子的问题,然后详细介绍pipeline()函数。 + + +⚠️ 为了从模型集线器和🤗Transformers的所有可用功能中获益,我们建议creating an account. \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index 2bf0ef5f8..fd1c8ba69 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index 31341f166..4dbba9df1 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -1,264 +1,264 @@ - - -# 模型 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -{#if fw === 'pt'} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 - -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 - -{:else} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 - -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 - -{/if} - -但是,如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与BERT模型一起工作的。 - -## 创建转换器 - -初始化BERT模型需要做的第一件事是加载配置对象: - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -# Building the config -config = BertConfig() - -# Building the model from the config -model = BertModel(config) -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -# Building the config -config = BertConfig() - -# Building the model from the config -model = TFBertModel(config) -``` -{/if} - -配置包含许多用于构建模型的属性: - -```py -print(config) -``` - -```python out -BertConfig { - [...] - "hidden_size": 768, - "intermediate_size": 3072, - "max_position_embeddings": 512, - "num_attention_heads": 12, - "num_hidden_layers": 12, - [...] -} -``` - -虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:hidden_size属性定义了hidden_状态向量的大小,num_hidden_layers定义了Transformer模型的层数。 - -### 不同的加载方式 - -从默认配置创建模型会使用随机值对其进行初始化: - - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -config = BertConfig() -model = BertModel(config) - -# Model is randomly initialized! -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -config = BertConfig() -model = TFBertModel(config) - -# Model is randomly initialized! -``` -{/if} - - -该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练。我们可以根据手头的任务从头开始训练模型,但正如您在 -[Chapter 1](/course/chapter1) -,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 - - -加载已经训练过的Transformers模型很简单-我们可以使用from_pretrained() -方法: - -{#if fw === 'pt'} -```py -from transformers import BertModel - -model = BertModel.from_pretrained("bert-base-cased") -``` - -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 - -{:else} -```py -from transformers import TFBertModel - -model = TFBertModel.from_pretrained("bert-base-cased") -``` - -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 - -{/if} - -在上面的代码示例中,我们没有使用BertConfig - -,而是通过Bert base cased标识符加载了一个预训练模型。这是一个模型检查点,由BERT的作者自己训练;您可以在 -[model card](https://huggingface.co/bert-base-cased)中找到更多细节. - - - -该模型现在使用检查点的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 - - - -权重已下载并缓存在缓存文件夹中(因此将来对from_pretrained()方法的调用将不会重新下载它们)默认为 -~/.cache/huggingface/transformers -. 您可以通过设置 -HF_HOME -环境变量来自定义缓存文件夹。 - - - -用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与BERT体系结构兼容。可以找到可用的BERT检查点的完整列表 -[here](https://huggingface.co/models?filter=bert) -. -### 保存模型 - -保存模型和加载模型一样简单--我们使用 -save_pretrained() -方法,类似于 -from_pretrained() -方法: - -```py -model.save_pretrained("directory_on_my_computer") -``` - -这会将两个文件保存到磁盘: - -{#if fw === 'pt'} -``` -ls directory_on_my_computer - -config.json pytorch_model.bin -``` -{:else} -``` -ls directory_on_my_computer - -config.json tf_model.h5 -``` -{/if} - -如果你看一下 -config.json -文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如检查点的来源以及上次保存检查点时使用的🤗 Transformers版本。 - -{#if fw === 'pt'} -这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 - -{:else} -这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 - -{/if} - -### 使用Transformers模型进行推理 - -既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论标记化器之前,让我们先探讨模型接受哪些输入。 - -标记化器可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 - -假设我们有几个序列: - -```py -sequences = ["Hello!", "Cool.", "Nice!"] -``` - -分词器将这些转换为词汇表索引,通常称为 -input IDs -. 每个序列现在都是一个数字列表!结果是: - -```py no-format -encoded_sequences = [ - [101, 7592, 999, 102], - [101, 4658, 1012, 102], - [101, 3835, 999, 102], -] -``` - -这是一个编码序列列表:一个列表列表。张量只接受矩形(想想矩阵)。此“数组”已为矩形,因此将其转换为张量很容易: - -{#if fw === 'pt'} -```py -import torch - -model_inputs = torch.tensor(encoded_sequences) -``` -{:else} -```py -import tensorflow as tf - -model_inputs = tf.constant(encoded_sequences) -``` -{/if} - -### 使用张量作为模型的输入 - - - -在模型中使用张量非常简单-我们只需将输入称为模型: - - -```python -output = model(model_inputs) -``` - - - -虽然模型接受许多不同的参数,但只需要 -input IDs。我们稍后将解释其他参数的作用以及何时需要它们,但首先我们需要更仔细地了解 -Transformer模型可以理解的输入的标记 + + +# 模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 +AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 + +这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 + +{:else} +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 +AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 + +这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 + +{/if} + +但是,如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与BERT模型一起工作的。 + +## 创建转换器 + +初始化BERT模型需要做的第一件事是加载配置对象: + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +# Building the config +config = BertConfig() + +# Building the model from the config +model = BertModel(config) +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +# Building the config +config = BertConfig() + +# Building the model from the config +model = TFBertModel(config) +``` +{/if} + +配置包含许多用于构建模型的属性: + +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:hidden_size属性定义了hidden_状态向量的大小,num_hidden_layers定义了Transformer模型的层数。 + +### 不同的加载方式 + +从默认配置创建模型会使用随机值对其进行初始化: + + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Model is randomly initialized! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Model is randomly initialized! +``` +{/if} + + +该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练。我们可以根据手头的任务从头开始训练模型,但正如您在 +[Chapter 1](/course/chapter1) +,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 + + +加载已经训练过的Transformers模型很简单-我们可以使用from_pretrained() +方法: + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 + +{/if} + +在上面的代码示例中,我们没有使用BertConfig + +,而是通过Bert base cased标识符加载了一个预训练模型。这是一个模型检查点,由BERT的作者自己训练;您可以在 +[model card](https://huggingface.co/bert-base-cased)中找到更多细节. + + + +该模型现在使用检查点的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 + + + +权重已下载并缓存在缓存文件夹中(因此将来对from_pretrained()方法的调用将不会重新下载它们)默认为 +~/.cache/huggingface/transformers +. 您可以通过设置 +HF_HOME +环境变量来自定义缓存文件夹。 + + + +用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与BERT体系结构兼容。可以找到可用的BERT检查点的完整列表 +[here](https://huggingface.co/models?filter=bert) +. +### 保存模型 + +保存模型和加载模型一样简单--我们使用 +save_pretrained() +方法,类似于 +from_pretrained() +方法: + +```py +model.save_pretrained("directory_on_my_computer") +``` + +这会将两个文件保存到磁盘: + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +如果你看一下 +config.json +文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如检查点的来源以及上次保存检查点时使用的🤗 Transformers版本。 + +{#if fw === 'pt'} +这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 + +{:else} +这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 + +{/if} + +### 使用Transformers模型进行推理 + +既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论标记化器之前,让我们先探讨模型接受哪些输入。 + +标记化器可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 + +假设我们有几个序列: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +分词器将这些转换为词汇表索引,通常称为 +input IDs +. 每个序列现在都是一个数字列表!结果是: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +这是一个编码序列列表:一个列表列表。张量只接受矩形(想想矩阵)。此“数组”已为矩形,因此将其转换为张量很容易: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### 使用张量作为模型的输入 + + + +在模型中使用张量非常简单-我们只需将输入称为模型: + + +```python +output = model(model_inputs) +``` + + + +虽然模型接受许多不同的参数,但只需要 +input IDs。我们稍后将解释其他参数的作用以及何时需要它们,但首先我们需要更仔细地了解 +Transformer模型可以理解的输入的标记 diff --git a/chapters/zh-CN/chapter2/4.mdx b/chapters/zh-CN/chapter2/4.mdx index fb4819296..cc0fa5bc5 100644 --- a/chapters/zh-CN/chapter2/4.mdx +++ b/chapters/zh-CN/chapter2/4.mdx @@ -1,239 +1,239 @@ - - -# 标记器(Tokenizer) - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - - -标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。在本节中,我们将确切地探讨标记化管道中发生的事情。 - -在 NLP 任务中,通常处理的数据是原始文本。这是此类文本的示例 - -``` -Jim Henson was a puppeteer -``` - -但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。 - -让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 - -## 基于词的(Word-based) - - - -想到的第一种标记器是基于词的(_word-based_).它通常很容易设置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示: - -
- An example of word-based tokenization. - -
- -有多种方法可以拆分文本。例如,我们可以通过应用Python的`split()`函数,使用空格将文本标记为单词: - -```py -tokenized_text = "Jim Henson was a puppeteer".split() -print(tokenized_text) -``` - -```python out -['Jim', 'Henson', 'was', 'a', 'puppeteer'] -``` - -还有一些单词标记器的变体,它们具有额外的标点符号规则。使用这种标记器,我们最终可以得到一些非常大的“词汇表”,其中词汇表由我们在语料库中拥有的独立标记的总数定义。 - -每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。 - -如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。 - -最后,我们需要一个自定义标记(token)来表示不在我们词汇表中的单词。这被称为“未知”标记(token),通常表示为“[UNK]”或"<unk>"。如果你看到标记器产生了很多这样的标记,这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在这个过程中丢失信息。制作词汇表时的目标是以这样一种方式进行,即标记器将尽可能少的单词标记为未知标记。 - -减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(_character-based_)标记器(tokenizer)。 - -## 基于字符(Character-based) - - - -基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处: - -- 词汇量要小得多。 -- 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建。 - -但是这里也出现了一些关于空格和标点符号的问题: - -
- An example of character-based tokenization. - -
- -这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,而单词就是这种情况。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 - -另一件要考虑的事情是,我们的模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token)。 - -为了两全其美,我们可以使用结合这两种方法的第三种技术:*子词标记化(subword tokenization)*。 - -## 子词标记化 - - - -子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。 - -例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。 - -这是一个示例,展示了子词标记化算法如何标记序列“Let's do tokenization!”: - -
- A subword tokenization algorithm. - -
- -这些子词最终提供了很多语义含义:例如,在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记 - -这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 - -### 还有更多! - -不出所料,还有更多的技术。仅举几例: - -- Byte-level BPE, 用于 GPT-2 -- WordPiece, 用于 BERT -- SentencePiece or Unigram, 用于多个多语言模型 - -您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 - -## 加载和保存 - -加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像*建筑学(architecture)*的模型)以及它的词汇(有点像*权重(weights)*模型)。 - -加载使用与 BERT 相同的检查点训练的 BERT 标记器(tokenizer)与加载模型的方式相同,除了我们使用 `BertTokenizer` 类: - -```py -from transformers import BertTokenizer - -tokenizer = BertTokenizer.from_pretrained("bert-base-cased") -``` - -{#if fw === 'pt'} -如同 `AutoModel`,`AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: - -{:else} -如同 `TFAutoModel`, `AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: - -{/if} - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -我们现在可以使用标记器(tokenizer),如上一节所示: - -```python -tokenizer("Using a Transformer network is simple") -``` - -```python out -{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], - 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], - 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} -``` - -保存标记器(tokenizer)与保存模型相同: - -```py -tokenizer.save_pretrained("directory_on_my_computer") -``` - -我们在[Chapter 3](/Couse/chapter3)中将更多地谈论`token_type_ids`,稍后我们将解释 `attention_mask` 键。首先,让我们看看 `input_ids` 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 - -## 编码 - - - -将文本翻译成数字被称为编码(_encoding_).编码分两步完成:标记化,然后转换为输入 ID。 - -正如我们所见,第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为*标记(token)*。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化标记器(tokenizer),以确保我们使用模型预训练时使用的相同规则。 - -第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,标记器(tokenizer)有一个*词汇(vocabulary)*,这是我们在实例化它时下载的部分 `from_pretrained()` 方法。同样,我们需要使用模型预训练时使用的相同词汇。 - -为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 - -### 标记化 - -标记化过程由标记器(tokenizer)的`tokenize()` 方法实现: - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") - -sequence = "Using a Transformer network is simple" -tokens = tokenizer.tokenize(sequence) - -print(tokens) -``` - -此方法的输出是一个字符串列表或标记(token): - -```python out -['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] -``` - -这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。`transformer` 就是这种情况,它分为两个标记:`transform` 和 `##er`。 - -### 从词符(token)到输入 ID -输入 ID 的转换由标记器(tokenizer)的`convert_tokens_to_ids()`方法实现: - -```py -ids = tokenizer.convert_tokens_to_ids(tokens) - -print(ids) -``` - -```python out -[7993, 170, 11303, 1200, 2443, 1110, 3014] -``` - -这些输出一旦转换为适当的框架张量,就可以用作模型的输入,如本章前面所见。 - - - -✏️ **试试看!** 在我们在第 2 节中使用的输入句子(“I've been waiting for a HuggingFace course my whole life.”和“I hate this so much!”)复制最后两个步骤(标记化和转换为输入 ID)。检查您获得的输入 ID 是否与我们之前获得的相同! - - - -## 解码 - -*解码(Decoding)* 正好相反:从词汇索引中,我们想要得到一个字符串。这可以通过 `decode()` 方法实现,如下: - -```py -decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) -print(decoded_string) -``` - -```python out -'Using a Transformer network is simple' -``` - -请注意, `decode` 方法不仅将索引转换回标记(token),还将属于相同单词的标记(token)组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。 - -到现在为止,您应该了解标记器(tokenizer)可以处理的原子操作:标记化、转换为 ID 以及将 ID 转换回字符串。然而,我们只是刮到了冰山一角。在下一节中,我们将采用我们的方法来克服它的限制,并看看如何克服它们。 + + +# 标记器(Tokenizer) + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。在本节中,我们将确切地探讨标记化管道中发生的事情。 + +在 NLP 任务中,通常处理的数据是原始文本。这是此类文本的示例 + +``` +Jim Henson was a puppeteer +``` + +但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。 + +让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 + +## 基于词的(Word-based) + + + +想到的第一种标记器是基于词的(_word-based_).它通常很容易设置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示: + +
+ An example of word-based tokenization. + +
+ +有多种方法可以拆分文本。例如,我们可以通过应用Python的`split()`函数,使用空格将文本标记为单词: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +还有一些单词标记器的变体,它们具有额外的标点符号规则。使用这种标记器,我们最终可以得到一些非常大的“词汇表”,其中词汇表由我们在语料库中拥有的独立标记的总数定义。 + +每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。 + +如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。 + +最后,我们需要一个自定义标记(token)来表示不在我们词汇表中的单词。这被称为“未知”标记(token),通常表示为“[UNK]”或"<unk>"。如果你看到标记器产生了很多这样的标记,这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在这个过程中丢失信息。制作词汇表时的目标是以这样一种方式进行,即标记器将尽可能少的单词标记为未知标记。 + +减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(_character-based_)标记器(tokenizer)。 + +## 基于字符(Character-based) + + + +基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处: + +- 词汇量要小得多。 +- 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建。 + +但是这里也出现了一些关于空格和标点符号的问题: + +
+ An example of character-based tokenization. + +
+ +这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,而单词就是这种情况。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 + +另一件要考虑的事情是,我们的模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token)。 + +为了两全其美,我们可以使用结合这两种方法的第三种技术:*子词标记化(subword tokenization)*。 + +## 子词标记化 + + + +子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。 + +例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。 + +这是一个示例,展示了子词标记化算法如何标记序列“Let's do tokenization!”: + +
+ A subword tokenization algorithm. + +
+ +这些子词最终提供了很多语义含义:例如,在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记 + +这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 + +### 还有更多! + +不出所料,还有更多的技术。仅举几例: + +- Byte-level BPE, 用于 GPT-2 +- WordPiece, 用于 BERT +- SentencePiece or Unigram, 用于多个多语言模型 + +您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 + +## 加载和保存 + +加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像*建筑学(architecture)*的模型)以及它的词汇(有点像*权重(weights)*模型)。 + +加载使用与 BERT 相同的检查点训练的 BERT 标记器(tokenizer)与加载模型的方式相同,除了我们使用 `BertTokenizer` 类: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +如同 `AutoModel`,`AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +{:else} +如同 `TFAutoModel`, `AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +我们现在可以使用标记器(tokenizer),如上一节所示: + +```python +tokenizer("Using a Transformer network is simple") +``` + +```python out +{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], + 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], + 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +保存标记器(tokenizer)与保存模型相同: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +我们在[Chapter 3](/Couse/chapter3)中将更多地谈论`token_type_ids`,稍后我们将解释 `attention_mask` 键。首先,让我们看看 `input_ids` 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 + +## 编码 + + + +将文本翻译成数字被称为编码(_encoding_).编码分两步完成:标记化,然后转换为输入 ID。 + +正如我们所见,第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为*标记(token)*。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化标记器(tokenizer),以确保我们使用模型预训练时使用的相同规则。 + +第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,标记器(tokenizer)有一个*词汇(vocabulary)*,这是我们在实例化它时下载的部分 `from_pretrained()` 方法。同样,我们需要使用模型预训练时使用的相同词汇。 + +为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 + +### 标记化 + +标记化过程由标记器(tokenizer)的`tokenize()` 方法实现: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + +sequence = "Using a Transformer network is simple" +tokens = tokenizer.tokenize(sequence) + +print(tokens) +``` + +此方法的输出是一个字符串列表或标记(token): + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。`transformer` 就是这种情况,它分为两个标记:`transform` 和 `##er`。 + +### 从词符(token)到输入 ID +输入 ID 的转换由标记器(tokenizer)的`convert_tokens_to_ids()`方法实现: + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +这些输出一旦转换为适当的框架张量,就可以用作模型的输入,如本章前面所见。 + + + +✏️ **试试看!** 在我们在第 2 节中使用的输入句子(“I've been waiting for a HuggingFace course my whole life.”和“I hate this so much!”)复制最后两个步骤(标记化和转换为输入 ID)。检查您获得的输入 ID 是否与我们之前获得的相同! + + + +## 解码 + +*解码(Decoding)* 正好相反:从词汇索引中,我们想要得到一个字符串。这可以通过 `decode()` 方法实现,如下: + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +请注意, `decode` 方法不仅将索引转换回标记(token),还将属于相同单词的标记(token)组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。 + +到现在为止,您应该了解标记器(tokenizer)可以处理的原子操作:标记化、转换为 ID 以及将 ID 转换回字符串。然而,我们只是刮到了冰山一角。在下一节中,我们将采用我们的方法来克服它的限制,并看看如何克服它们。 diff --git a/chapters/zh-CN/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx index 1b73568ed..ffa33176d 100644 --- a/chapters/zh-CN/chapter2/5.mdx +++ b/chapters/zh-CN/chapter2/5.mdx @@ -1,355 +1,355 @@ - - -# 处理多个序列 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -在上一节中,我们探讨了最简单的用例:对一个小长度的序列进行推理。然而,一些问题已经出现: - -* 我们如何处理多个序列? - - -* 我们如何处理多个序列不同长度? - - -* 词汇索引是让模型正常工作的唯一输入吗? - - -* 是否存在序列太长的问题? - -让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers API解决它们 - -## 模型需要一批输入 - -在上一个练习中,您看到了序列如何转换为数字列表。让我们将此数字列表转换为张量,并将其发送到模型: - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = torch.tensor(ids) -# This line will fail. -model(input_ids) -``` - -```python out -IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = tf.constant(ids) -# This line will fail. -model(input_ids) -``` - -```py out -InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] -``` -{/if} - -哦,不!为什么失败了?“我们遵循了第2节中管道的步骤。 - -问题是我们向模型发送了一个序列,而🤗 Transformers模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现它不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: - -{#if fw === 'pt'} -```py -tokenized_inputs = tokenizer(sequence, return_tensors="pt") -print(tokenized_inputs["input_ids"]) -``` - -```python out -tensor([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, - 2607, 2026, 2878, 2166, 1012, 102]]) -``` -{:else} -```py -tokenized_inputs = tokenizer(sequence, return_tensors="tf") -print(tokenized_inputs["input_ids"]) -``` - -```py out -tf.Tensor: shape=(1, 16), dtype=int32, numpy= -array([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, - 12172, 2607, 2026, 2878, 2166, 1012, 102]], dtype=int32)> -``` -{/if} - -让我们重试并添加一个新维度: - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) - -input_ids = torch.tensor([ids]) -print("Input IDs:", input_ids) - -output = model(input_ids) -print("Logits:", output.logits) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) - -input_ids = tf.constant([ids]) -print("Input IDs:", input_ids) - -output = model(input_ids) -print("Logits:", output.logits) -``` -{/if} - -我们打印输入ID以及生成的logits-以下是输出: - -{#if fw === 'pt'} -```python out -Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]] -Logits: [[-2.7276, 2.8789]] -``` -{:else} -```py out -Input IDs: tf.Tensor( -[[ 1045 1005 2310 2042 3403 2005 1037 17662 12172 2607 2026 2878 - 2166 1012]], shape=(1, 14), dtype=int32) -Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) -``` -{/if} - -*Batching* 是一次通过模型发送多个句子的行为。如果你只有一句话,你可以用一个序列构建一个批次: - - -``` -batched_ids = [ids, ids] -``` - -这是一批两个相同的序列! - - - -✏️ **Try it out!** 试试看!将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) - - -批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 - -## 填充输入 - -以下列表不能转换为张量: - -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200] -] -``` - -为了解决这个问题,我们将使用填充使张量具有矩形。Padding通过在值较少的句子中添加一个名为Padding token的特殊单词来确保我们所有的句子长度相同。例如,如果你有10个包含10个单词的句子和1个包含20个单词的句子,填充将确保所有句子都包含20个单词。在我们的示例中,生成的张量如下所示: - -```py no-format -padding_id = 100 - -batched_ids = [ - [200, 200, 200], - [200, 200, padding_id], -] -``` - -可以在tokenizer.pad_token_id中找到填充令牌ID. 让我们使用它,将我们的两句话分别发送到模型中,并分批发送到一起: - - -{#if fw === 'pt'} -```py no-format -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence1_ids = [[200, 200, 200]] -sequence2_ids = [[200, 200]] -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -print(model(torch.tensor(sequence1_ids)).logits) -print(model(torch.tensor(sequence2_ids)).logits) -print(model(torch.tensor(batched_ids)).logits) -``` - -```python out -tensor([[ 1.5694, -1.3895]], grad_fn=) -tensor([[ 0.5803, -0.4125]], grad_fn=) -tensor([[ 1.5694, -1.3895], - [ 1.3373, -1.2163]], grad_fn=) -``` -{:else} -```py no-format -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) - -sequence1_ids = [[200, 200, 200]] -sequence2_ids = [[200, 200]] -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -print(model(tf.constant(sequence1_ids)).logits) -print(model(tf.constant(sequence2_ids)).logits) -print(model(tf.constant(batched_ids)).logits) -``` - -```py out -tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32) -tf.Tensor([[ 0.5803005 -0.41252428]], shape=(1, 2), dtype=float32) -tf.Tensor( -[[ 1.5693681 -1.3894582] - [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32) -``` -{/if} - -我们批处理预测中的logits有点问题:第二行应该与第二句的logits相同,但我们得到了完全不同的值! - - -这是因为Transformer模型的关键特性是关注层,它将每个标记上下文化。这些将考虑填充标记,因为它们涉及序列中的所有标记。为了在通过模型传递不同长度的单个句子时,或者在传递一批应用了相同句子和填充的句子时获得相同的结果,我们需要告诉这些注意层忽略填充标记。这是通过使用 attention mask来实现的。 - -## 注意力面具 - -*Attention masks*是与输入ID张量形状完全相同的张量,用0和1填充:1s表示应注意相应的标记,0s表示不应注意相应的标记(即,模型的注意力层应忽略它们)。 - -让我们用attention mask完成上一个示例: - -{#if fw === 'pt'} -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -attention_mask = [ - [1, 1, 1], - [1, 1, 0], -] - -outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask)) -print(outputs.logits) -``` - -```python out -tensor([[ 1.5694, -1.3895], - [ 0.5803, -0.4125]], grad_fn=) -``` -{:else} -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200, tokenizer.pad_token_id], -] - -attention_mask = [ - [1, 1, 1], - [1, 1, 0], -] - -outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask)) -print(outputs.logits) -``` - -```py out -tf.Tensor( -[[ 1.5693681 -1.3894582 ] - [ 0.5803021 -0.41252586]], shape=(2, 2), dtype=float32) -``` -{/if} - -现在我们得到了该批中第二个句子的相同登录。 - -请注意,第二个序列的最后一个值是一个填充ID,它在attention mask中是一个0值。 - - - -✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待拥抱课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! - - - -## 长序列 - -对于Transformers模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达512或1024个令牌的序列,当要求处理更长的序列时,会崩溃。此问题有两种解决方案: - - - -* 使用支持的序列长度较长的模型。 - - -* 截断序列。 - - -模型有不同的支持序列长度,有些模型专门处理很长的序列。 -[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) -这是一个例子,另一个是 -[LED](https://huggingface.co/transformers/model_doc/led.html) -. 如果您正在处理一项需要很长序列的任务,我们建议您查看这些模型。 - -否则,我们建议您通过指定max_sequence_length参数: - -```py -sequence = sequence[:max_sequence_length] -``` + + +# 处理多个序列 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +在上一节中,我们探讨了最简单的用例:对一个小长度的序列进行推理。然而,一些问题已经出现: + +* 我们如何处理多个序列? + + +* 我们如何处理多个序列不同长度? + + +* 词汇索引是让模型正常工作的唯一输入吗? + + +* 是否存在序列太长的问题? + +让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers API解决它们 + +## 模型需要一批输入 + +在上一个练习中,您看到了序列如何转换为数字列表。让我们将此数字列表转换为张量,并将其发送到模型: + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = torch.tensor(ids) +# This line will fail. +model(input_ids) +``` + +```python out +IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = tf.constant(ids) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +哦,不!为什么失败了?“我们遵循了第2节中管道的步骤。 + +问题是我们向模型发送了一个序列,而🤗 Transformers模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现它不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: + +{#if fw === 'pt'} +```py +tokenized_inputs = tokenizer(sequence, return_tensors="pt") +print(tokenized_inputs["input_ids"]) +``` + +```python out +tensor([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, + 2607, 2026, 2878, 2166, 1012, 102]]) +``` +{:else} +```py +tokenized_inputs = tokenizer(sequence, return_tensors="tf") +print(tokenized_inputs["input_ids"]) +``` + +```py out +tf.Tensor: shape=(1, 16), dtype=int32, numpy= +array([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, + 12172, 2607, 2026, 2878, 2166, 1012, 102]], dtype=int32)> +``` +{/if} + +让我们重试并添加一个新维度: + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) + +input_ids = torch.tensor([ids]) +print("Input IDs:", input_ids) + +output = model(input_ids) +print("Logits:", output.logits) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) + +input_ids = tf.constant([ids]) +print("Input IDs:", input_ids) + +output = model(input_ids) +print("Logits:", output.logits) +``` +{/if} + +我们打印输入ID以及生成的logits-以下是输出: + +{#if fw === 'pt'} +```python out +Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]] +Logits: [[-2.7276, 2.8789]] +``` +{:else} +```py out +Input IDs: tf.Tensor( +[[ 1045 1005 2310 2042 3403 2005 1037 17662 12172 2607 2026 2878 + 2166 1012]], shape=(1, 14), dtype=int32) +Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) +``` +{/if} + +*Batching* 是一次通过模型发送多个句子的行为。如果你只有一句话,你可以用一个序列构建一个批次: + + +``` +batched_ids = [ids, ids] +``` + +这是一批两个相同的序列! + + + +✏️ **Try it out!** 试试看!将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) + + +批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 + +## 填充输入 + +以下列表不能转换为张量: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +为了解决这个问题,我们将使用填充使张量具有矩形。Padding通过在值较少的句子中添加一个名为Padding token的特殊单词来确保我们所有的句子长度相同。例如,如果你有10个包含10个单词的句子和1个包含20个单词的句子,填充将确保所有句子都包含20个单词。在我们的示例中,生成的张量如下所示: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +可以在tokenizer.pad_token_id中找到填充令牌ID. 让我们使用它,将我们的两句话分别发送到模型中,并分批发送到一起: + + +{#if fw === 'pt'} +```py no-format +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence1_ids = [[200, 200, 200]] +sequence2_ids = [[200, 200]] +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +print(model(torch.tensor(sequence1_ids)).logits) +print(model(torch.tensor(sequence2_ids)).logits) +print(model(torch.tensor(batched_ids)).logits) +``` + +```python out +tensor([[ 1.5694, -1.3895]], grad_fn=) +tensor([[ 0.5803, -0.4125]], grad_fn=) +tensor([[ 1.5694, -1.3895], + [ 1.3373, -1.2163]], grad_fn=) +``` +{:else} +```py no-format +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence1_ids = [[200, 200, 200]] +sequence2_ids = [[200, 200]] +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +print(model(tf.constant(sequence1_ids)).logits) +print(model(tf.constant(sequence2_ids)).logits) +print(model(tf.constant(batched_ids)).logits) +``` + +```py out +tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32) +tf.Tensor([[ 0.5803005 -0.41252428]], shape=(1, 2), dtype=float32) +tf.Tensor( +[[ 1.5693681 -1.3894582] + [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32) +``` +{/if} + +我们批处理预测中的logits有点问题:第二行应该与第二句的logits相同,但我们得到了完全不同的值! + + +这是因为Transformer模型的关键特性是关注层,它将每个标记上下文化。这些将考虑填充标记,因为它们涉及序列中的所有标记。为了在通过模型传递不同长度的单个句子时,或者在传递一批应用了相同句子和填充的句子时获得相同的结果,我们需要告诉这些注意层忽略填充标记。这是通过使用 attention mask来实现的。 + +## 注意力面具 + +*Attention masks*是与输入ID张量形状完全相同的张量,用0和1填充:1s表示应注意相应的标记,0s表示不应注意相应的标记(即,模型的注意力层应忽略它们)。 + +让我们用attention mask完成上一个示例: + +{#if fw === 'pt'} +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +attention_mask = [ + [1, 1, 1], + [1, 1, 0], +] + +outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask)) +print(outputs.logits) +``` + +```python out +tensor([[ 1.5694, -1.3895], + [ 0.5803, -0.4125]], grad_fn=) +``` +{:else} +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +attention_mask = [ + [1, 1, 1], + [1, 1, 0], +] + +outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask)) +print(outputs.logits) +``` + +```py out +tf.Tensor( +[[ 1.5693681 -1.3894582 ] + [ 0.5803021 -0.41252586]], shape=(2, 2), dtype=float32) +``` +{/if} + +现在我们得到了该批中第二个句子的相同登录。 + +请注意,第二个序列的最后一个值是一个填充ID,它在attention mask中是一个0值。 + + + +✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待拥抱课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! + + + +## 长序列 + +对于Transformers模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达512或1024个令牌的序列,当要求处理更长的序列时,会崩溃。此问题有两种解决方案: + + + +* 使用支持的序列长度较长的模型。 + + +* 截断序列。 + + +模型有不同的支持序列长度,有些模型专门处理很长的序列。 +[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) +这是一个例子,另一个是 +[LED](https://huggingface.co/transformers/model_doc/led.html) +. 如果您正在处理一项需要很长序列的任务,我们建议您查看这些模型。 + +否则,我们建议您通过指定max_sequence_length参数: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/zh-CN/chapter2/6.mdx b/chapters/zh-CN/chapter2/6.mdx index e4b5c6295..95381ad9f 100644 --- a/chapters/zh-CN/chapter2/6.mdx +++ b/chapters/zh-CN/chapter2/6.mdx @@ -1,165 +1,165 @@ - - -# 把它们放在一起 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -在最后几节中,我们一直在尽最大努力手工完成大部分工作。我们探讨了标记化器的工作原理,并研究了标记化、到输入ID的转换、填充、截断和注意掩码。 - -然而,正如我们在第2节中所看到的,🤗 Transformers API可以通过一个高级函数为我们处理所有这些,我们将在这里深入讨论。当你直接在句子上调用标记器时,你会得到准备通过模型传递的输入 - -```py -from transformers import AutoTokenizer - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - -sequence = "I've been waiting for a HuggingFace course my whole life." - -model_inputs = tokenizer(sequence) -``` - -这里,`model_inputs` -变量包含模型良好运行所需的一切。对于DistilBERT,它包括输入 ID和注意力掩码(attention mask)。其他接受额外输入的模型也会有标记器对象的输出。 - -正如我们将在下面的一些示例中看到的,这种方法非常强大。首先,它可以标记单个序列: - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." - -model_inputs = tokenizer(sequence) -``` - -它还一次处理多个序列,并且API没有任何变化: - -```py -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -model_inputs = tokenizer(sequences) -``` - -它可以根据几个目标进行填充: - -```py -# Will pad the sequences up to the maximum sequence length -model_inputs = tokenizer(sequences, padding="longest") - -# Will pad the sequences up to the model max length -# (512 for BERT or DistilBERT) -model_inputs = tokenizer(sequences, padding="max_length") - -# Will pad the sequences up to the specified max length -model_inputs = tokenizer(sequences, padding="max_length", max_length=8) -``` - -它还可以截断序列: - -```py -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -# Will truncate the sequences that are longer than the model max length -# (512 for BERT or DistilBERT) -model_inputs = tokenizer(sequences, truncation=True) - -# Will truncate the sequences that are longer than the specified max length -model_inputs = tokenizer(sequences, max_length=8, truncation=True) -``` - -标记器对象可以处理到特定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们提示标记器从不同的框架返回张量——`"pt"`返回Py Torch张量,`"tf"`返回TensorFlow张量,`"np"`返回NumPy数组: - -```py -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -# Returns PyTorch tensors -model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") - -# Returns TensorFlow tensors -model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") - -# Returns NumPy arrays -model_inputs = tokenizer(sequences, padding=True, return_tensors="np") -``` - -## 特殊词符(token) - -如果我们看一下标记器返回的输入 ID,我们会发现它们与之前的略有不同: - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." - -model_inputs = tokenizer(sequence) -print(model_inputs["input_ids"]) - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -print(ids) -``` - -```python out -[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102] -[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] -``` - -一个在开始时添加了一个标记(token) ID,一个在结束时添加了一个标记(token) ID。让我们解码上面的两个ID序列,看看这是怎么回事: - -```py -print(tokenizer.decode(model_inputs["input_ids"])) -print(tokenizer.decode(ids)) -``` - -```python out -"[CLS] i've been waiting for a huggingface course my whole life. [SEP]" -"i've been waiting for a huggingface course my whole life." -``` - -标记器在开头添加了特殊单词`[CLS]`,在结尾添加了特殊单词`[SEP]`。这是因为模型是用这些数据预训练的,所以为了得到相同的推理结果,我们还需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的单词;模型也可能只在开头或结尾添加这些特殊单词。在任何情况下,标记器都知道需要哪些词符,并将为您处理这些词符。 - -## 结束:从标记器到模型 - -现在我们已经看到了标记器对象在应用于文本时使用的所有单独步骤,让我们最后一次看看它如何处理多个序列(填充!),非常长的序列(截断!),以及多种类型的张量及其主要API: - -{#if fw === 'pt'} -```py -import torch -from transformers import AutoTokenizer, AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") -output = model(**tokens) -``` -{:else} -```py -import tensorflow as tf -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") -output = model(**tokens) -``` -{/if} + + +# 把它们放在一起 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在最后几节中,我们一直在尽最大努力手工完成大部分工作。我们探讨了标记化器的工作原理,并研究了标记化、到输入ID的转换、填充、截断和注意掩码。 + +然而,正如我们在第2节中所看到的,🤗 Transformers API可以通过一个高级函数为我们处理所有这些,我们将在这里深入讨论。当你直接在句子上调用标记器时,你会得到准备通过模型传递的输入 + +```py +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +这里,`model_inputs` +变量包含模型良好运行所需的一切。对于DistilBERT,它包括输入 ID和注意力掩码(attention mask)。其他接受额外输入的模型也会有标记器对象的输出。 + +正如我们将在下面的一些示例中看到的,这种方法非常强大。首先,它可以标记单个序列: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +它还一次处理多个序列,并且API没有任何变化: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +model_inputs = tokenizer(sequences) +``` + +它可以根据几个目标进行填充: + +```py +# Will pad the sequences up to the maximum sequence length +model_inputs = tokenizer(sequences, padding="longest") + +# Will pad the sequences up to the model max length +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Will pad the sequences up to the specified max length +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +它还可以截断序列: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Will truncate the sequences that are longer than the model max length +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Will truncate the sequences that are longer than the specified max length +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +标记器对象可以处理到特定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们提示标记器从不同的框架返回张量——`"pt"`返回Py Torch张量,`"tf"`返回TensorFlow张量,`"np"`返回NumPy数组: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Returns PyTorch tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Returns TensorFlow tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Returns NumPy arrays +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## 特殊词符(token) + +如果我们看一下标记器返回的输入 ID,我们会发现它们与之前的略有不同: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +print(model_inputs["input_ids"]) + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +print(ids) +``` + +```python out +[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102] +[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] +``` + +一个在开始时添加了一个标记(token) ID,一个在结束时添加了一个标记(token) ID。让我们解码上面的两个ID序列,看看这是怎么回事: + +```py +print(tokenizer.decode(model_inputs["input_ids"])) +print(tokenizer.decode(ids)) +``` + +```python out +"[CLS] i've been waiting for a huggingface course my whole life. [SEP]" +"i've been waiting for a huggingface course my whole life." +``` + +标记器在开头添加了特殊单词`[CLS]`,在结尾添加了特殊单词`[SEP]`。这是因为模型是用这些数据预训练的,所以为了得到相同的推理结果,我们还需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的单词;模型也可能只在开头或结尾添加这些特殊单词。在任何情况下,标记器都知道需要哪些词符,并将为您处理这些词符。 + +## 结束:从标记器到模型 + +现在我们已经看到了标记器对象在应用于文本时使用的所有单独步骤,让我们最后一次看看它如何处理多个序列(填充!),非常长的序列(截断!),以及多种类型的张量及其主要API: + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") +output = model(**tokens) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") +output = model(**tokens) +``` +{/if} diff --git a/chapters/zh-CN/chapter2/7.mdx b/chapters/zh-CN/chapter2/7.mdx index 1835352f6..ef205f164 100644 --- a/chapters/zh-CN/chapter2/7.mdx +++ b/chapters/zh-CN/chapter2/7.mdx @@ -1,27 +1,32 @@ -# 基本用法完成! - -很好地完成了到这里的课程!总而言之,在本章中,您可以: - -- 学习了Transformers模型的基本构造块。 - - -- 了解了标记化管道的组成。 - - -- 了解了如何在实践中使用Transformers模型。 - - -- 学习了如何利用分词器将文本转换为模型可以理解的张量。 - - -- 将分词器和模型一起设置,以从文本到预测。 - - -- 了解了inputs IDs的局限性,并了解了attention mask。 - - -- 使用多功能和可配置的分词器方法。 - - - -从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 +# 基本用法完成! + + + +很好地完成了到这里的课程!总而言之,在本章中,您可以: + +- 学习了Transformers模型的基本构造块。 + + +- 了解了标记化管道的组成。 + + +- 了解了如何在实践中使用Transformers模型。 + + +- 学习了如何利用分词器将文本转换为模型可以理解的张量。 + + +- 将分词器和模型一起设置,以从文本到预测。 + + +- 了解了inputs IDs的局限性,并了解了attention mask。 + + +- 使用多功能和可配置的分词器方法。 + + + +从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 diff --git a/chapters/zh-CN/chapter2/8.mdx b/chapters/zh-CN/chapter2/8.mdx index 89cc36502..fda118ada 100644 --- a/chapters/zh-CN/chapter2/8.mdx +++ b/chapters/zh-CN/chapter2/8.mdx @@ -1,293 +1,298 @@ - - - - -# 章末小测试 - -### 1. 语言建模 Pipeline 的顺序是什么? - - -### 2. Transformer模型的输出有多少个维度,每个维度分别是什么? - - -### 3.下列哪一个是Subword标记(Tokenization)的例子(从分词的颗粒度来划分)? - - -### 4.什么是模型的Head层? - - -{#if fw === 'pt'} -### 5.什么是AutoModel? -AutoNLP 产品相混淆了?" - }, - { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: AutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", - correct: true - }, - { - text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" - } - ]} -/> - -{:else} -### 5.什么是 TFAutoModel? -AutoNLP 产品相混淆了?" - }, - { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: TFAutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", - correct: true - }, - { - text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" - } - ]} -/> - -{/if} - -### 6.当将不同长度的序列批处理在一起时,需要进行哪些处理? - - -### 7.将 SoftMax激活函数应用于序列分类(Sequence Classification)模型的 logits 输出有什么意义? - - -### 8.大多数标记器(Tokenizer)的API以什么方法为核心? -编码 ,因为它可以将文本编码为id,将预测的id解码为文本", - explain: "错! 虽然 编码 方法确实存在于标记器中,但是它不存在于模型中。" - }, - { - text: "直接调用标记器(Tokenizer)对象。", - explain: "完全正确!标记化器(Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它也是从模型中获取预测的方法。", - correct: true - }, - { - text: "pad(填充)", - explain: "错! pad(填充)非常有用,但它只是标记器(Tokenizer) API的一部分。" - }, - { - text: "tokenize(标记)", - explain: "可以说,tokenize(标记)方法是最有用的方法之一,但它不是标记器(Tokenizer) API的核心方法。" - } - ]} -/> - -### 9.这个代码示例中的`result`变量包含什么? -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -result = tokenizer.tokenize("Hello!") -``` - -__call__ 或 convert_tokens_to_ids方法的作用!" - }, - { - text: "包含所有标记(Token)的字符串", - explain: "这将是次优的,因为Tokenizer会将字符串拆分为多个标记的列表。" - } - ]} -/> - -{#if fw === 'pt'} -### 10.下面的代码有什么错误吗? -```py -from transformers import AutoTokenizer, AutoModel - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -model = AutoModel.from_pretrained("gpt2") - -encoded = tokenizer("Hey!", return_tensors="pt") -result = model(**encoded) -``` - - - -{:else} -### 10.下面的代码有什么错误吗? -```py -from transformers import AutoTokenizer, TFAutoModel - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -model = TFAutoModel.from_pretrained("gpt2") - -encoded = tokenizer("Hey!", return_tensors="pt") -result = model(**encoded) -``` - - - -{/if} + + + + +# 章末小测试 + + + +### 1. 语言建模 Pipeline 的顺序是什么? + + +### 2. Transformer模型的输出有多少个维度,每个维度分别是什么? + + +### 3.下列哪一个是Subword标记(Tokenization)的例子(从分词的颗粒度来划分)? + + +### 4.什么是模型的Head层? + + +{#if fw === 'pt'} +### 5.什么是AutoModel? +AutoNLP 产品相混淆了?" + }, + { + text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说: AutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + correct: true + }, + { + text: "一种可以自动检测输入语言来加载正确权重的模型", + explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + } + ]} +/> + +{:else} +### 5.什么是 TFAutoModel? +AutoNLP 产品相混淆了?" + }, + { + text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说: TFAutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + correct: true + }, + { + text: "一种可以自动检测输入语言来加载正确权重的模型", + explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + } + ]} +/> + +{/if} + +### 6.当将不同长度的序列批处理在一起时,需要进行哪些处理? + + +### 7.将 SoftMax激活函数应用于序列分类(Sequence Classification)模型的 logits 输出有什么意义? + + +### 8.大多数标记器(Tokenizer)的API以什么方法为核心? +编码 ,因为它可以将文本编码为id,将预测的id解码为文本", + explain: "错! 虽然 编码 方法确实存在于标记器中,但是它不存在于模型中。" + }, + { + text: "直接调用标记器(Tokenizer)对象。", + explain: "完全正确!标记化器(Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它也是从模型中获取预测的方法。", + correct: true + }, + { + text: "pad(填充)", + explain: "错! pad(填充)非常有用,但它只是标记器(Tokenizer) API的一部分。" + }, + { + text: "tokenize(标记)", + explain: "可以说,tokenize(标记)方法是最有用的方法之一,但它不是标记器(Tokenizer) API的核心方法。" + } + ]} +/> + +### 9.这个代码示例中的`result`变量包含什么? +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +__call__ 或 convert_tokens_to_ids方法的作用!" + }, + { + text: "包含所有标记(Token)的字符串", + explain: "这将是次优的,因为Tokenizer会将字符串拆分为多个标记的列表。" + } + ]} +/> + +{#if fw === 'pt'} +### 10.下面的代码有什么错误吗? +```py +from transformers import AutoTokenizer, AutoModel + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +model = AutoModel.from_pretrained("gpt2") + +encoded = tokenizer("Hey!", return_tensors="pt") +result = model(**encoded) +``` + + + +{:else} +### 10.下面的代码有什么错误吗? +```py +from transformers import AutoTokenizer, TFAutoModel + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +model = TFAutoModel.from_pretrained("gpt2") + +encoded = tokenizer("Hey!", return_tensors="pt") +result = model(**encoded) +``` + + + +{/if} diff --git a/chapters/zh-CN/chapter3/1.mdx b/chapters/zh-CN/chapter3/1.mdx index f441c3f21..e744bf78e 100644 --- a/chapters/zh-CN/chapter3/1.mdx +++ b/chapters/zh-CN/chapter3/1.mdx @@ -1,21 +1,26 @@ - - -# 本章简介 - -在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: - -{#if fw === 'pt'} -* 如何从模型中心(hub)准备大型数据集 -* 如何使用高级`训练`API微调一个模型 -* 如何使用自定义训练过程 -* 如何利用🤗 Accelerate库在任何分布式设备上轻松运行自定义训练过程 - -{:else} -* 如何从模型中心(hub)准备大型数据集 -* 如何使用 Keras 微调模型 -* 如何使用 Keras 进行预测 -* 如何使用自定义指标 - -{/if} - + + +# 本章简介 + + + +在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: + +{#if fw === 'pt'} +* 如何从模型中心(hub)准备大型数据集 +* 如何使用高级`训练`API微调一个模型 +* 如何使用自定义训练过程 +* 如何利用🤗 Accelerate库在任何分布式设备上轻松运行自定义训练过程 + +{:else} +* 如何从模型中心(hub)准备大型数据集 +* 如何使用 Keras 微调模型 +* 如何使用 Keras 进行预测 +* 如何使用自定义指标 + +{/if} + 为了将经过训练的参数上传到Hugging Face Hub,您需要一个huggingface.co帐户: [创建一个账户](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index 4a92170fc..cda565d9b 100644 --- a/chapters/zh-CN/chapter3/2.mdx +++ b/chapters/zh-CN/chapter3/2.mdx @@ -1,383 +1,383 @@ - - -# 处理数据 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子: - -```python -import torch -from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification - -# Same as before -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "This course is amazing!", -] -batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") - -# This is new -batch["labels"] = torch.tensor([1, 1]) - -optimizer = AdamW(model.parameters()) -loss = model(**batch).loss -loss.backward() -optimizer.step() -``` -{:else} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在TensorFlow上训练句子分类器的一个例子: - -```python -import tensorflow as tf -import numpy as np -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -# Same as before -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "This course is amazing!", -] -batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) - -# This is new -model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") -labels = tf.convert_to_tensor([1, 1]) -model.train_on_batch(batch, labels) -``` -{/if} - -当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。 - -在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 - -### 从模型中心(Hub)加载数据集 - -{#if fw === 'pt'} - -{:else} - -{/if} - -模型中心(hub)不只是包含模型;它也有许多不同语言的多个数据集。点击[数据集](https://huggingface.co/datasets)的链接即可进行浏览。我们建议您在阅读本节后阅读一下[加载和处理新的数据集](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)这篇文章,这会让您对huggingface的darasets更加清晰。但现在,让我们使用MRPC数据集中的[GLUE 基准测试数据集](https://gluebenchmark.com/),它是构成MRPC数据集的10个数据集之一,这是一个学术基准,用于衡量机器学习模型在10个不同文本分类任务中的性能。 - -🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("glue", "mrpc") -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 408 - }) - test: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 1725 - }) -}) -``` - -正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 - -默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/dataset**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 - -我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: - -```py -raw_train_dataset = raw_datasets["train"] -raw_train_dataset[0] -``` - -```python out -{'idx': 0, - 'label': 1, - 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', - 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} -``` - -我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看**raw_train_dataset**的**features**. 这将告诉我们每列的类型: - -```py -raw_train_dataset.features -``` - -```python out -{'sentence1': Value(dtype='string', id=None), - 'sentence2': Value(dtype='string', id=None), - 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), - 'idx': Value(dtype='int32', id=None)} -``` - -在上面的例子之中,**Label(标签)** 是一种**ClassLabel(分类标签)**,使用整数建立起到类别标签的映射关系。**0**对应于**not_equivalent**,**1**对应于**equivalent**。 - - - -✏️ **试试看!** 查看训练集的第15行元素和验证集的87行元素。他们的标签是什么? - - - -### 预处理数据集 - -{#if fw === 'pt'} - -{:else} - -{/if} - -为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在[第二章](/course/chapter2)上看到的那样 - -```py -from transformers import AutoTokenizer - -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) -tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) -``` - -然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理: - -```py -inputs = tokenizer("This is the first sentence.", "This is the second one.") -inputs -``` - -```python out -{ - 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], - 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], - 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] -} -``` - -我们在[第二章](/course/chapter2) 讨论了**输入词id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我们在那个时候没有讨论**类型标记ID(token_type_ids)**。在这个例子中,**类型标记ID(token_type_ids)**的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 - - - -✏️ ** 试试看!** 选取训练集中的第15个元素,将两句话分别标记为一对。结果和上方的例子有什么不同? - - - -如果我们将**input_ids**中的id转换回文字: - -```py -tokenizer.convert_ids_to_tokens(inputs["input_ids"]) -``` - -我们将得到: - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -``` - -所以我们看到模型需要输入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,当有两句话的时候。**类型标记ID(token_type_ids)** 的值是: - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] -``` - -如您所见,输入中 **[CLS] sentence1 [SEP]** 它们的类型标记ID均为**0**,而其他部分,对应于**sentence2 [SEP]**,所有的类型标记ID均为**1**. - -请注意,如果选择其他的检查点,则不一定具有**类型标记ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。 - -用类型标记ID对BERT进行预训练,并且使用[第一章](/course/chapter1)的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。 - -在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。 - -一般来说,你不需要担心是否有**类型标记ID(token_type_ids)**。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。 - -现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如[之前的章节](/course/chapter2),我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在[第二章](/course/chapter2)中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是: - -```py -tokenized_dataset = tokenizer( - raw_datasets["train"]["sentence1"], - raw_datasets["train"]["sentence2"], - padding=True, - truncation=True, -) -``` - -这很有效,但它的缺点是返回字典(字典的键是**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)**,字典的值是键所对应值的列表)。而且只有当您在转换过程中有足够的内存来存储整个数据集时才不会出错(而🤗数据集库中的数据集是以[Apache Arrow](https://arrow.apache.org/)文件存储在磁盘上,因此您只需将接下来要用的数据加载在内存中,因此会对内存容量的需求要低一些)。 - -为了将数据保存为数据集,我们将使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: - -```py -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) -``` - -此函数的输入是一个字典(与数据集的项类似),并返回一个包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的新字典。请注意,如果像上面的**示例**一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用**map()**使用该选项 **batched=True** ,这将显著加快标记与标记的速度。这个**标记器**来自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。 - -请注意,我们现在在标记函数中省略了**padding**参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力! - -下面是我们如何在所有数据集上同时应用标记函数。我们在调用**map**时使用了**batch =True**,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多 - -```py -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -tokenized_datasets -``` - -🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 408 - }) - test: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 1725 - }) -}) -``` - -在使用预处理函数**map()**时,甚至可以通过传递**num_proc**参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用**num_proc**可能会加快预处理。 - -我们的**标记函数(tokenize_function)**返回包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数**map()**为现有键返回一个新值,那将会修改原有键的值。 - -最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 - -### 动态填充 - - - -{#if fw === 'pt'} -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它是你可以在构建**DataLoader**时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 - -{:else} - -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它只会将您的样本转换为 tf.Tensor并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 - -{/if} - -为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过**DataCollatorWithPadding**为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切: - -{#if fw === 'pt'} -```py -from transformers import DataCollatorWithPadding - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) -``` -{:else} -```py -from transformers import DataCollatorWithPadding - -data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") -``` -{/if} - -为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列**idx**, **sentence1**和**sentence2**,因为不需要它们,并查看一个batch中每个条目的长度: - -```py -samples = tokenized_datasets["train"][:8] -samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} -[len(x) for x in samples["input_ids"]] -``` - -```python out -[50, 59, 47, 67, 59, 50, 62, 32] -``` - -毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: - -```py: - -```py -batch = data_collator(samples) -{k: v.shape for k, v in batch.items()} -``` - -{#if fw === 'tf'} - -```python out -{'attention_mask': TensorShape([8, 67]), - 'input_ids': TensorShape([8, 67]), - 'token_type_ids': TensorShape([8, 67]), - 'labels': TensorShape([8])} -``` - -{:else} - -```python out -{'attention_mask': torch.Size([8, 67]), - 'input_ids': torch.Size([8, 67]), - 'token_type_ids': torch.Size([8, 67]), - 'labels': torch.Size([8])} -``` - -看起来不错!现在,我们已经将原始文本转化为了模型可以处理的数据,我们已准备好对其进行微调! - -{/if} - - - -✏️ ** 试试看!** 在GLUE SST-2数据集上应用预处理。它有点不同,因为它是由单个句子而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个更难的挑战,请尝试编写一个可用于任何GLUE任务的预处理函数。 - - - -{#if fw === 'tf'} - -现在我们有了dataset和data collator,我们需要将dataset批量地应用data collator。 我们可以手动加载批次并整理它们,但这需要大量工作,而且可能性能也不是很好。 相反,有一个简单的方法可以为这个问题提供高效的解决方案:`to_tf_dataset()`。 这将在您的数据集上调用一个 `tf.data.Dataset`的方法,这个方法带有一个可选的data collator功能。 `tf.data.Dataset` 是 Keras 可用于 `model.fit()` 的原生 TensorFlow 格式,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。 让我们看看它在我们的数据集上是如何使用的! - -```py -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["attention_mask", "input_ids", "token_type_ids"], - label_cols=["labels"], - shuffle=True, - collate_fn=data_collator, - batch_size=8, -) - -tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["attention_mask", "input_ids", "token_type_ids"], - label_cols=["labels"], - shuffle=False, - collate_fn=data_collator, - batch_size=8, -) -``` - -就是这样! 我们可以将这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单。 - -{/if} + + +# 处理数据 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在TensorFlow上训练句子分类器的一个例子: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。 + +在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 + +### 从模型中心(Hub)加载数据集 + +{#if fw === 'pt'} + +{:else} + +{/if} + +模型中心(hub)不只是包含模型;它也有许多不同语言的多个数据集。点击[数据集](https://huggingface.co/datasets)的链接即可进行浏览。我们建议您在阅读本节后阅读一下[加载和处理新的数据集](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)这篇文章,这会让您对huggingface的darasets更加清晰。但现在,让我们使用MRPC数据集中的[GLUE 基准测试数据集](https://gluebenchmark.com/),它是构成MRPC数据集的10个数据集之一,这是一个学术基准,用于衡量机器学习模型在10个不同文本分类任务中的性能。 + +🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 + +默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/dataset**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 + +我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看**raw_train_dataset**的**features**. 这将告诉我们每列的类型: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +在上面的例子之中,**Label(标签)** 是一种**ClassLabel(分类标签)**,使用整数建立起到类别标签的映射关系。**0**对应于**not_equivalent**,**1**对应于**equivalent**。 + + + +✏️ **试试看!** 查看训练集的第15行元素和验证集的87行元素。他们的标签是什么? + + + +### 预处理数据集 + +{#if fw === 'pt'} + +{:else} + +{/if} + +为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在[第二章](/course/chapter2)上看到的那样 + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +inputs +``` + +```python out +{ + 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], + 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], + 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +} +``` + +我们在[第二章](/course/chapter2) 讨论了**输入词id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我们在那个时候没有讨论**类型标记ID(token_type_ids)**。在这个例子中,**类型标记ID(token_type_ids)**的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 + + + +✏️ ** 试试看!** 选取训练集中的第15个元素,将两句话分别标记为一对。结果和上方的例子有什么不同? + + + +如果我们将**input_ids**中的id转换回文字: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +我们将得到: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +所以我们看到模型需要输入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,当有两句话的时候。**类型标记ID(token_type_ids)** 的值是: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +如您所见,输入中 **[CLS] sentence1 [SEP]** 它们的类型标记ID均为**0**,而其他部分,对应于**sentence2 [SEP]**,所有的类型标记ID均为**1**. + +请注意,如果选择其他的检查点,则不一定具有**类型标记ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。 + +用类型标记ID对BERT进行预训练,并且使用[第一章](/course/chapter1)的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。 + +在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。 + +一般来说,你不需要担心是否有**类型标记ID(token_type_ids)**。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。 + +现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如[之前的章节](/course/chapter2),我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在[第二章](/course/chapter2)中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +这很有效,但它的缺点是返回字典(字典的键是**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)**,字典的值是键所对应值的列表)。而且只有当您在转换过程中有足够的内存来存储整个数据集时才不会出错(而🤗数据集库中的数据集是以[Apache Arrow](https://arrow.apache.org/)文件存储在磁盘上,因此您只需将接下来要用的数据加载在内存中,因此会对内存容量的需求要低一些)。 + +为了将数据保存为数据集,我们将使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +此函数的输入是一个字典(与数据集的项类似),并返回一个包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的新字典。请注意,如果像上面的**示例**一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用**map()**使用该选项 **batched=True** ,这将显著加快标记与标记的速度。这个**标记器**来自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。 + +请注意,我们现在在标记函数中省略了**padding**参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力! + +下面是我们如何在所有数据集上同时应用标记函数。我们在调用**map**时使用了**batch =True**,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多 + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +在使用预处理函数**map()**时,甚至可以通过传递**num_proc**参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用**num_proc**可能会加快预处理。 + +我们的**标记函数(tokenize_function)**返回包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数**map()**为现有键返回一个新值,那将会修改原有键的值。 + +最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 + +### 动态填充 + + + +{#if fw === 'pt'} +负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它是你可以在构建**DataLoader**时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +{:else} + +负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它只会将您的样本转换为 tf.Tensor并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +{/if} + +为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过**DataCollatorWithPadding**为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切: + +{#if fw === 'pt'} +```py +from transformers import DataCollatorWithPadding + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` +{:else} +```py +from transformers import DataCollatorWithPadding + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") +``` +{/if} + +为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列**idx**, **sentence1**和**sentence2**,因为不需要它们,并查看一个batch中每个条目的长度: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: + +```py: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +看起来不错!现在,我们已经将原始文本转化为了模型可以处理的数据,我们已准备好对其进行微调! + +{/if} + + + +✏️ ** 试试看!** 在GLUE SST-2数据集上应用预处理。它有点不同,因为它是由单个句子而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个更难的挑战,请尝试编写一个可用于任何GLUE任务的预处理函数。 + + + +{#if fw === 'tf'} + +现在我们有了dataset和data collator,我们需要将dataset批量地应用data collator。 我们可以手动加载批次并整理它们,但这需要大量工作,而且可能性能也不是很好。 相反,有一个简单的方法可以为这个问题提供高效的解决方案:`to_tf_dataset()`。 这将在您的数据集上调用一个 `tf.data.Dataset`的方法,这个方法带有一个可选的data collator功能。 `tf.data.Dataset` 是 Keras 可用于 `model.fit()` 的原生 TensorFlow 格式,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。 让我们看看它在我们的数据集上是如何使用的! + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +就是这样! 我们可以将这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单。 + +{/if} diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx index de8018344..92403ecd2 100644 --- a/chapters/zh-CN/chapter3/3.mdx +++ b/chapters/zh-CN/chapter3/3.mdx @@ -1,172 +1,172 @@ - - -# 使用 Trainer API 微调模型 - - - - - -🤗 Transformers提供了一个 **Trainer** 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 **Trainer** .最难的部分可能是为 **Trainer.train()**配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPU[Google Colab](https://colab.research.google.com/). - -下面的示例假设您已经执行了上一节中的示例。下面这段代码,概括了您需要提前运行的代码: - -```py -from datasets import load_dataset -from transformers import AutoTokenizer, DataCollatorWithPadding - -raw_datasets = load_dataset("glue", "mrpc") -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - - -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) - - -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) -``` - -### Training - -在我们定义我们的 **Trainer** 之前首先要定义一个 **TrainingArguments** 类,它将包含 **Trainer**用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。 - -```py -from transformers import TrainingArguments - -training_args = TrainingArguments("test-trainer") -``` - - - -💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在[第四章](/course/chapter4/3)中详细介绍这部分。 - - - -第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 **AutoModelForSequenceClassification** 类,它有两个参数: - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -你会注意到,和[第二章](/course/chapter2)不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。 - -一旦我们有了我们的模型,我们就可以定义一个 **Trainer** 通过将之前构造的所有对象传递给它——我们的**model** 、**training_args** ,训练和验证数据集,**data_collator** ,和 **tokenizer** : - -```py -from transformers import Trainer - -trainer = Trainer( - model, - training_args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - tokenizer=tokenizer, -) -``` - -请注意,当您在这里完成**tokenizer**后,默认 **Trainer**使用 的**data_collator**会使用之前预定义的 **DataCollatorWithPadding** ,因此您可以在这个例子中跳过 **data_collator=data_collator**。在第 2 节中向您展示这部分处理仍然很重要! - -为了让预训练模型在在我们的数据集上微调,我们只需要调用**Trainer**的**train()** 方法 : - -```py -trainer.train() -``` - -这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为: - -1. 我们没有通过将**evaluation_strategy**设置为“**steps**”(在每次更新参数的时候评估)或“**epoch**”(在每个epoch结束时评估)来告诉**Trainer**在训练期间进行评估。 -2. 我们没有为**Trainer**提供一个**compute_metrics()**函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。 - - -### 评估 - -让我们看看如何构建一个有用的 **compute_metrics()** 函数并在我们下次训练时使用它。该函数必须采用 **EvalPrediction** 对象(带有 **predictions** 和 **label_ids** 字段的参数元组)并将返回一个字符串到浮点数的字典(字符串是返回的指标的名称,而浮点数是它们的值)。我们可以使用 **Trainer.predict()** 命令来使用我们的模型进行预测: - -```py -predictions = trainer.predict(tokenized_datasets["validation"]) -print(predictions.predictions.shape, predictions.label_ids.shape) -``` - -```python out -(408, 2) (408,) -``` - - **predict()** 的输出结果是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()**的结果。 - -**predict()** 方法是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()** 的结果。如你看到的, **predictions** 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给**predict()**的数据集的每个元素的结果(logits)(正如你在[之前的章节](/course/chapter2)看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引: - -```py -import numpy as np - -preds = np.argmax(predictions.predictions, axis=-1) -``` - -现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **evaluate.load()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -metric.compute(predictions=preds, references=predictions.label_ids) -``` - -```python out -{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} -``` - -您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在[BERT 论文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基础模型的 F1 分数为 88.9。那是 **uncased** 模型,而我们目前正在使用 **cased** 模型,通过改进得到了更好的结果。 - -最后将所有东西打包在一起,我们得到了我们的 **compute_metrics()** 函数: - -```py -def compute_metrics(eval_preds): - metric = evaluate.load("glue", "mrpc") - logits, labels = eval_preds - predictions = np.argmax(logits, axis=-1) - return metric.compute(predictions=predictions, references=labels) -``` - -为了查看模型在每个训练周期结束的好坏,下面是我们如何使用**compute_metrics()**函数定义一个新的 **Trainer** : - -```py -training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) - -trainer = Trainer( - model, - training_args, - train_dataset=tokenized_datasets["train"], - eval_dataset=tokenized_datasets["validation"], - data_collator=data_collator, - tokenizer=tokenizer, - compute_metrics=compute_metrics, -) -``` - -请注意,我们设置了了一个新的 **TrainingArguments** 它的**evaluation_strategy** 设置为 **epoch** 并创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练之前我们已经训练过的模型。要启动新的训练运行,我们执行: - -``` -trainer.train() -``` - -这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。 - -这 **Trainer** 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 **fp16 = True** )。我们将在第 10 章讨论它支持的所有内容。 - -使用**Trainer** API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在第 7 章中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。 - - - -✏️ **试试看!** 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。 - - - + + +# 使用 Trainer API 微调模型 + + + + + +🤗 Transformers提供了一个 **Trainer** 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 **Trainer** .最难的部分可能是为 **Trainer.train()**配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPU[Google Colab](https://colab.research.google.com/). + +下面的示例假设您已经执行了上一节中的示例。下面这段代码,概括了您需要提前运行的代码: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Training + +在我们定义我们的 **Trainer** 之前首先要定义一个 **TrainingArguments** 类,它将包含 **Trainer**用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。 + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在[第四章](/course/chapter4/3)中详细介绍这部分。 + + + +第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 **AutoModelForSequenceClassification** 类,它有两个参数: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +你会注意到,和[第二章](/course/chapter2)不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。 + +一旦我们有了我们的模型,我们就可以定义一个 **Trainer** 通过将之前构造的所有对象传递给它——我们的**model** 、**training_args** ,训练和验证数据集,**data_collator** ,和 **tokenizer** : + +```py +from transformers import Trainer + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +请注意,当您在这里完成**tokenizer**后,默认 **Trainer**使用 的**data_collator**会使用之前预定义的 **DataCollatorWithPadding** ,因此您可以在这个例子中跳过 **data_collator=data_collator**。在第 2 节中向您展示这部分处理仍然很重要! + +为了让预训练模型在在我们的数据集上微调,我们只需要调用**Trainer**的**train()** 方法 : + +```py +trainer.train() +``` + +这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为: + +1. 我们没有通过将**evaluation_strategy**设置为“**steps**”(在每次更新参数的时候评估)或“**epoch**”(在每个epoch结束时评估)来告诉**Trainer**在训练期间进行评估。 +2. 我们没有为**Trainer**提供一个**compute_metrics()**函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。 + + +### 评估 + +让我们看看如何构建一个有用的 **compute_metrics()** 函数并在我们下次训练时使用它。该函数必须采用 **EvalPrediction** 对象(带有 **predictions** 和 **label_ids** 字段的参数元组)并将返回一个字符串到浮点数的字典(字符串是返回的指标的名称,而浮点数是它们的值)。我们可以使用 **Trainer.predict()** 命令来使用我们的模型进行预测: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + + **predict()** 的输出结果是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()**的结果。 + +**predict()** 方法是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()** 的结果。如你看到的, **predictions** 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给**predict()**的数据集的每个元素的结果(logits)(正如你在[之前的章节](/course/chapter2)看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **evaluate.load()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在[BERT 论文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基础模型的 F1 分数为 88.9。那是 **uncased** 模型,而我们目前正在使用 **cased** 模型,通过改进得到了更好的结果。 + +最后将所有东西打包在一起,我们得到了我们的 **compute_metrics()** 函数: + +```py +def compute_metrics(eval_preds): + metric = evaluate.load("glue", "mrpc") + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + return metric.compute(predictions=predictions, references=labels) +``` + +为了查看模型在每个训练周期结束的好坏,下面是我们如何使用**compute_metrics()**函数定义一个新的 **Trainer** : + +```py +training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +请注意,我们设置了了一个新的 **TrainingArguments** 它的**evaluation_strategy** 设置为 **epoch** 并创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练之前我们已经训练过的模型。要启动新的训练运行,我们执行: + +``` +trainer.train() +``` + +这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。 + +这 **Trainer** 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 **fp16 = True** )。我们将在第 10 章讨论它支持的所有内容。 + +使用**Trainer** API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在第 7 章中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。 + + + +✏️ **试试看!** 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。 + + + diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index be3953a8c..9a30ffe4f 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # 使用 Keras 微调一个模型 - diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx index aab5f40a6..bc44acf7d 100644 --- a/chapters/zh-CN/chapter3/4.mdx +++ b/chapters/zh-CN/chapter3/4.mdx @@ -1,358 +1,358 @@ -# 一个完整的训练 - - - - - -现在,我们将了解如何在不使用`Trainer`类的情况下获得与上一节相同的结果。同样,我们假设您已经学习了第 2 节中的数据处理。下面是一个简短的总结,涵盖了您需要的所有内容: - -```py -from datasets import load_dataset -from transformers import AutoTokenizer, DataCollatorWithPadding - -raw_datasets = load_dataset("glue", "mrpc") -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - - -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) - - -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -data_collator = DataCollatorWithPadding(tokenizer=tokenizer) -``` - -### 训练前的准备 - -在实际编写我们的训练循环之前,我们需要定义一些对象。第一个是我们将用于迭代批次的数据加载器。我们需要对我们的`tokenized_datasets`做一些处理,来处理`Trainer`自动为我们做的一些事情。具体来说,我们需要: - -- 删除与模型不期望的值相对应的列(如`sentence1`和`sentence2`列)。 -- 将列名`label`重命名为`labels`(因为模型期望参数是`labels`)。 -- 设置数据集的格式,使其返回 PyTorch 张量而不是列表。 - -针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: - -```py -tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) -tokenized_datasets = tokenized_datasets.rename_column("label", "labels") -tokenized_datasets.set_format("torch") -tokenized_datasets["train"].column_names -``` - -然后,我们可以检查结果中是否只有模型能够接受的列: - -```python -["attention_mask", "input_ids", "labels", "token_type_ids"] -``` - -至此,我们可以轻松定义数据加载器: - -```py -from torch.utils.data import DataLoader - -train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator -) -``` - -为了快速检验数据处理中没有错误,我们可以这样检验其中的一个批次: - -```py -for batch in train_dataloader: - break -{k: v.shape for k, v in batch.items()} -``` - -```python out -{'attention_mask': torch.Size([8, 65]), - 'input_ids': torch.Size([8, 65]), - 'labels': torch.Size([8]), - 'token_type_ids': torch.Size([8, 65])} -``` - -请注意,实际的形状可能与您略有不同,因为我们为训练数据加载器设置了`shuffle=True`,并且模型会将句子填充到`batch`中的最大长度。 - -现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们完全像在上一节中所做的那样实例化它: - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` -为了确保训练过程中一切顺利,我们将`batch`传递给这个模型: - -```py -outputs = model(**batch) -print(outputs.loss, outputs.logits.shape) -``` - -```python out -tensor(0.5441, grad_fn=) torch.Size([8, 2]) -``` - -当我们提供 `labels` 时, 🤗 Transformers 模型都将返回这个`batch`的`loss`,我们还得到了 `logits`(`batch`中的每个输入有两个,所以张量大小为 8 x 2)。 - -我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图自行实现 `Trainer`的功能,我们将使用相同的优化器和学习率调度器。`Trainer` 使用的优化器是 `AdamW` , 与 `Adam` 相同,但在权重衰减正则化方面有所不同(参见[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=5e-5) -``` - -最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是我们所有训练数据的数量)。`Trainer`默认情况下使用三个`epochs`,因此我们定义训练过程如下: - -```py -from transformers import get_scheduler - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -print(num_training_steps) -``` - -```python out -1377 -``` - -### 训练循环 - -最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device`,它在GPU可用的情况下指向GPU 我们将把我们的模型和`batche`放在`device`上: - -```py -import torch - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) -device -``` - -```python out -device(type='cuda') -``` - -我们现在准备好训练了!为了了解训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: - -```py -from tqdm.auto import tqdm - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -您可以看到训练循环的核心与介绍中的非常相似。我们没有要求任何检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 - - -### 评估循环 - -正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -model.eval() -for batch in eval_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - with torch.no_grad(): - outputs = model(**batch) - - logits = outputs.logits - predictions = torch.argmax(logits, dim=-1) - metric.add_batch(predictions=predictions, references=batch["labels"]) - -metric.compute() -``` - -```python out -{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} -``` - -同样,由于模型头部初始化和数据改组的随机性,您的结果会略有不同,但它们应该在同一个范围内。 - - - -✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调您的模型。 - - - -### S使用🤗 Accelerate加速您的训练循环 - - - -我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: - -```py -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -以下是变化: - -```diff -+ from accelerate import Accelerator - from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -+ accelerator = Accelerator() - - model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) - optimizer = AdamW(model.parameters(), lr=3e-5) - -- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -- model.to(device) - -+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( -+ train_dataloader, eval_dataloader, model, optimizer -+ ) - - num_epochs = 3 - num_training_steps = num_epochs * len(train_dataloader) - lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps - ) - - progress_bar = tqdm(range(num_training_steps)) - - model.train() - for epoch in range(num_epochs): - for batch in train_dataloader: -- batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss -- loss.backward() -+ accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -要添加的第一行是导入`Accelerator`。第二行实例化一个 `Accelerator`对象 ,它将查看环境并初始化适当的分布式设置。 🤗 Accelerate 为您处理数据在设备间的传递,因此您可以删除将模型放在设备上的那行代码(或者,如果您愿意,可使用 `accelerator.device` 代替 `device` )。 - -然后大部分工作会在将数据加载器、模型和优化器发送到的`accelerator.prepare()`中完成。这将会把这些对象包装在适当的容器中,以确保您的分布式训练按预期工作。要进行的其余更改是删除将`batch`放在 `device` 的那行代码(同样,如果您想保留它,您可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为`accelerator.backward(loss)`。 - - -⚠️ 为了使云端 TPU 提供的加速发挥最大的效益,我们建议使用标记器(tokenizer)的 `padding=max_length` 和 `max_length` 参数将您的样本填充到固定长度。 - - -如果您想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: - -```py -from accelerate import Accelerator -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -accelerator = Accelerator() - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -train_dl, eval_dl, model, optimizer = accelerator.prepare( - train_dataloader, eval_dataloader, model, optimizer -) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dl) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dl: - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: - -```bash -accelerate config -``` - -这将询问您几个配置的问题并将您的回答转储到此命令使用的配置文件中: - -``` -accelerate launch train.py -``` - -这将启动分布式训练 - -这将启动分布式训练。如果您想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到 `training_function()` 并使用以下命令运行最后一个单元格: - -```python -from accelerate import notebook_launcher - -notebook_launcher(training_function) -``` - -您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 +# 一个完整的训练 + + + + + +现在,我们将了解如何在不使用`Trainer`类的情况下获得与上一节相同的结果。同样,我们假设您已经学习了第 2 节中的数据处理。下面是一个简短的总结,涵盖了您需要的所有内容: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### 训练前的准备 + +在实际编写我们的训练循环之前,我们需要定义一些对象。第一个是我们将用于迭代批次的数据加载器。我们需要对我们的`tokenized_datasets`做一些处理,来处理`Trainer`自动为我们做的一些事情。具体来说,我们需要: + +- 删除与模型不期望的值相对应的列(如`sentence1`和`sentence2`列)。 +- 将列名`label`重命名为`labels`(因为模型期望参数是`labels`)。 +- 设置数据集的格式,使其返回 PyTorch 张量而不是列表。 + +针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +然后,我们可以检查结果中是否只有模型能够接受的列: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +至此,我们可以轻松定义数据加载器: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +为了快速检验数据处理中没有错误,我们可以这样检验其中的一个批次: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +请注意,实际的形状可能与您略有不同,因为我们为训练数据加载器设置了`shuffle=True`,并且模型会将句子填充到`batch`中的最大长度。 + +现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们完全像在上一节中所做的那样实例化它: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` +为了确保训练过程中一切顺利,我们将`batch`传递给这个模型: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +当我们提供 `labels` 时, 🤗 Transformers 模型都将返回这个`batch`的`loss`,我们还得到了 `logits`(`batch`中的每个输入有两个,所以张量大小为 8 x 2)。 + +我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图自行实现 `Trainer`的功能,我们将使用相同的优化器和学习率调度器。`Trainer` 使用的优化器是 `AdamW` , 与 `Adam` 相同,但在权重衰减正则化方面有所不同(参见[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是我们所有训练数据的数量)。`Trainer`默认情况下使用三个`epochs`,因此我们定义训练过程如下: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### 训练循环 + +最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device`,它在GPU可用的情况下指向GPU 我们将把我们的模型和`batche`放在`device`上: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +我们现在准备好训练了!为了了解训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +您可以看到训练循环的核心与介绍中的非常相似。我们没有要求任何检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 + + +### 评估循环 + +正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +同样,由于模型头部初始化和数据改组的随机性,您的结果会略有不同,但它们应该在同一个范围内。 + + + +✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调您的模型。 + + + +### S使用🤗 Accelerate加速您的训练循环 + + + +我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +以下是变化: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +要添加的第一行是导入`Accelerator`。第二行实例化一个 `Accelerator`对象 ,它将查看环境并初始化适当的分布式设置。 🤗 Accelerate 为您处理数据在设备间的传递,因此您可以删除将模型放在设备上的那行代码(或者,如果您愿意,可使用 `accelerator.device` 代替 `device` )。 + +然后大部分工作会在将数据加载器、模型和优化器发送到的`accelerator.prepare()`中完成。这将会把这些对象包装在适当的容器中,以确保您的分布式训练按预期工作。要进行的其余更改是删除将`batch`放在 `device` 的那行代码(同样,如果您想保留它,您可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为`accelerator.backward(loss)`。 + + +⚠️ 为了使云端 TPU 提供的加速发挥最大的效益,我们建议使用标记器(tokenizer)的 `padding=max_length` 和 `max_length` 参数将您的样本填充到固定长度。 + + +如果您想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: + +```bash +accelerate config +``` + +这将询问您几个配置的问题并将您的回答转储到此命令使用的配置文件中: + +``` +accelerate launch train.py +``` + +这将启动分布式训练 + +这将启动分布式训练。如果您想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到 `training_function()` 并使用以下命令运行最后一个单元格: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 diff --git a/chapters/zh-CN/chapter3/5.mdx b/chapters/zh-CN/chapter3/5.mdx index 760741ec9..51edbadeb 100644 --- a/chapters/zh-CN/chapter3/5.mdx +++ b/chapters/zh-CN/chapter3/5.mdx @@ -1,20 +1,25 @@ - - -# 微调,检查! - -这是非常令人高兴的! 在前两章中,您了解了模型和标记器(tokenizer),现在您知道如何针对您自己的数据对它们进行微调。回顾一下,在本章中,您: - -{#if fw === 'pt'} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 -* 学习了如何加载和预处理数据集,包括使用动态填充和整理器 -* 实现您自己的模型微调和评估 -* 实施了一个较为底层的训练循环 -* 使用 🤗 Accelerate 轻松调整您的训练循环,使其适用于多个 GPU 或 TPU - -{:else} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 -* 学习了如何加载和预处理数据集 -* 学习了如何使用 Keras 微调和评估模型 -* 实现了自定义指标 - -{/if} + + +# 微调,检查! + + + +这是非常令人高兴的! 在前两章中,您了解了模型和标记器(tokenizer),现在您知道如何针对您自己的数据对它们进行微调。回顾一下,在本章中,您: + +{#if fw === 'pt'} +* 了解了[Hub](https://huggingface.co/datasets)中的数据集 +* 学习了如何加载和预处理数据集,包括使用动态填充和整理器 +* 实现您自己的模型微调和评估 +* 实施了一个较为底层的训练循环 +* 使用 🤗 Accelerate 轻松调整您的训练循环,使其适用于多个 GPU 或 TPU + +{:else} +* 了解了[Hub](https://huggingface.co/datasets)中的数据集 +* 学习了如何加载和预处理数据集 +* 学习了如何使用 Keras 微调和评估模型 +* 实现了自定义指标 + +{/if} diff --git a/chapters/zh-CN/chapter3/6.mdx b/chapters/zh-CN/chapter3/6.mdx index 750bfd3cd..96118ac20 100644 --- a/chapters/zh-CN/chapter3/6.mdx +++ b/chapters/zh-CN/chapter3/6.mdx @@ -1,284 +1,289 @@ - - - - -# End-of-chapter quiz - -Test what you learned in this chapter! - -### 1.“情绪”数据集包含标记有情绪的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? - - -### 2.在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索‘ ar _ sarcasm’数据集,它支持哪个任务? - dataset card !" - }, - { - text: "命名实体识别", - explain: "不是这样的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" - }, - { - text: "回答问题", - explain: "Alas, this question was not answered correctly. 再试一次!" - } - ]} -/> - -### 3.BERT 模型期望如何处理一对句子? - [ CLS ] 特殊令牌在开始时是必需的,但是这不是唯一的事情!" - }, - { - text: "表示句子1[ SEP ]的符号表示句子2[ SEP ]", - explain: "没错!", - correct: true - }, - { - text: "表示句子1[ SEP ]的符号表示句子2", - explain: "开头需要一个 < code > [ CLS ] 特殊标记,还需要一个 < code > [ SEP ] 特殊标记来分隔两个句子,但这还不是全部!" - } - ]} -/> - -{#if fw === 'pt'} -### 4.‘ Dataset.map ()’方法的好处是什么? - - -### 5.什么是动态填充? - - -### 6.校对函数的用途是什么? - > DataCollatorWithPadding 。" - }, - { - text: "它把所有的样品一批一批地放在一起。", - explain: "正确! You can pass the collate function as an argument of a DataLoader. We used the DataCollatorWithPadding function, which pads all items in a batch so they have the same length.", - correct: true - }, - { - text: "它预处理整个数据集。", - explain: "这将是一个预处理函数,而不是校对函数。" - }, - { - text: "它截断数据集中的序列。", - explain: "校对函数用于处理单个批处理,而不是整个数据集。如果您对截断感兴趣,可以使用 < code > tokenizer 的 < truncate 参数。" - } - ]} -/> - -### 7.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ AutoModelForXxx’类,这个类对应于一个不同于它所被训练的任务时会发生什么? - Trainer 所做的,而不是 Accelerate 库。再试一次!", - correct: true - }, - { - text: "丢弃预先训练好的模型头部。", - explain: "Something else needs to happen. 再试一次!" - }, - { - text: "没有,因为模型仍然可以针对不同的任务进行微调。", - explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" - } - ]} -/> - -### 8.训练争论的目的是什么? - TrainingArguments 。" - }, - { - text: "它只包含用于评估的超参数。", - explain: "In the example, we specified where the model and its checkpoints will be saved. 再试一次!" - }, - { - text: "您可以轻松地计算与数据集相关的指标。", - explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再试一次!" - } - ]} -/> - -### 9.为什么要使用 Accelerate 库? -Trainer, not the 🤗 Accelerate library. 再试一次!" - }, - { - text: "它使我们的训练循环工作在分布式策略上", - explain: "正确! 随着加速,你的训练循环将为多个 gpu 和 TPUs 工作。", - correct: true - }, - { - text: "它提供了更多的优化功能。", - explain: "不,Accelerate 库不提供任何优化功能。" - } - ]} -/> - -{:else} -### 4.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ tfautoodelforxxx’类时,会发生什么? - - -### 5.来自“变压器”的 TensorFlow 模型已经是 Keras 模型,这有什么好处? - TPUStrategy scope 中的所有内容,包括模型的初始化。" - }, - { - text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () < c/ode > 和 < code > predict () 。", - explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", - correct: true - }, - { - text: "你可以学习 Keras 和变形金刚。", - explain: "没错,但我们要找的是别的东西:)", - correct: true - }, - { - text: "困惑", - explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的度量。" - } - ]} -/> - -### 6.如何定义自己的定制度量? - tfkeras.metrics. Metric 。", - explain: "太好了!", - correct: true - }, - { - text: "使用 Keras 函数 API。", - explain: "再试一次!" - }, - { - text: "通过使用带签名的可调用 < code > metric _ fn (y _ true,y _ pred) 。", - explain: "正确!", - correct: true - }, - { - text: "通过谷歌搜索。", - explain: "这不是我们要找的答案,但它应该能帮助你找到答案。", - correct: true - } - ]} -/> - + + + + +# End-of-chapter quiz + + + +Test what you learned in this chapter! + +### 1.“情绪”数据集包含标记有情绪的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? + + +### 2.在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索‘ ar _ sarcasm’数据集,它支持哪个任务? + dataset card !" + }, + { + text: "命名实体识别", + explain: "不是这样的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" + }, + { + text: "回答问题", + explain: "Alas, this question was not answered correctly. 再试一次!" + } + ]} +/> + +### 3.BERT 模型期望如何处理一对句子? + [ CLS ] 特殊令牌在开始时是必需的,但是这不是唯一的事情!" + }, + { + text: "表示句子1[ SEP ]的符号表示句子2[ SEP ]", + explain: "没错!", + correct: true + }, + { + text: "表示句子1[ SEP ]的符号表示句子2", + explain: "开头需要一个 < code > [ CLS ] 特殊标记,还需要一个 < code > [ SEP ] 特殊标记来分隔两个句子,但这还不是全部!" + } + ]} +/> + +{#if fw === 'pt'} +### 4.‘ Dataset.map ()’方法的好处是什么? + + +### 5.什么是动态填充? + + +### 6.校对函数的用途是什么? + > DataCollatorWithPadding 。" + }, + { + text: "它把所有的样品一批一批地放在一起。", + explain: "正确! You can pass the collate function as an argument of a DataLoader. We used the DataCollatorWithPadding function, which pads all items in a batch so they have the same length.", + correct: true + }, + { + text: "它预处理整个数据集。", + explain: "这将是一个预处理函数,而不是校对函数。" + }, + { + text: "它截断数据集中的序列。", + explain: "校对函数用于处理单个批处理,而不是整个数据集。如果您对截断感兴趣,可以使用 < code > tokenizer 的 < truncate 参数。" + } + ]} +/> + +### 7.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ AutoModelForXxx’类,这个类对应于一个不同于它所被训练的任务时会发生什么? + Trainer 所做的,而不是 Accelerate 库。再试一次!", + correct: true + }, + { + text: "丢弃预先训练好的模型头部。", + explain: "Something else needs to happen. 再试一次!" + }, + { + text: "没有,因为模型仍然可以针对不同的任务进行微调。", + explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" + } + ]} +/> + +### 8.训练争论的目的是什么? + TrainingArguments 。" + }, + { + text: "它只包含用于评估的超参数。", + explain: "In the example, we specified where the model and its checkpoints will be saved. 再试一次!" + }, + { + text: "您可以轻松地计算与数据集相关的指标。", + explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再试一次!" + } + ]} +/> + +### 9.为什么要使用 Accelerate 库? +Trainer, not the 🤗 Accelerate library. 再试一次!" + }, + { + text: "它使我们的训练循环工作在分布式策略上", + explain: "正确! 随着加速,你的训练循环将为多个 gpu 和 TPUs 工作。", + correct: true + }, + { + text: "它提供了更多的优化功能。", + explain: "不,Accelerate 库不提供任何优化功能。" + } + ]} +/> + +{:else} +### 4.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ tfautoodelforxxx’类时,会发生什么? + + +### 5.来自“变压器”的 TensorFlow 模型已经是 Keras 模型,这有什么好处? + TPUStrategy scope 中的所有内容,包括模型的初始化。" + }, + { + text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () < c/ode > 和 < code > predict () 。", + explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", + correct: true + }, + { + text: "你可以学习 Keras 和变形金刚。", + explain: "没错,但我们要找的是别的东西:)", + correct: true + }, + { + text: "困惑", + explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的度量。" + } + ]} +/> + +### 6.如何定义自己的定制度量? + tfkeras.metrics. Metric 。", + explain: "太好了!", + correct: true + }, + { + text: "使用 Keras 函数 API。", + explain: "再试一次!" + }, + { + text: "通过使用带签名的可调用 < code > metric _ fn (y _ true,y _ pred) 。", + explain: "正确!", + correct: true + }, + { + text: "通过谷歌搜索。", + explain: "这不是我们要找的答案,但它应该能帮助你找到答案。", + correct: true + } + ]} +/> + {/if} \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/1.mdx b/chapters/zh-CN/chapter4/1.mdx index 167f46f9d..ea639b48c 100644 --- a/chapters/zh-CN/chapter4/1.mdx +++ b/chapters/zh-CN/chapter4/1.mdx @@ -1,5 +1,10 @@ # The Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/)---我们的主网站,是一个中央平台,在这个网站上任何人都可以查找、使用和贡献新的最先进的模型和数据集。它拥有各种各样的模型,公开可用的模型超过 10,000个。我们在本章去探索Hub中的模型,并在第 5 章中探索Hub中的数据集。 Hub 中的模型不仅限于🤗 Transformers 甚至 NLP。有用于自然语言处理的[Flair](https://github.com/flairNLP/flair),[AllenNLP](https://github.com/allenai/allennlp),[Asteroid](https://github.com/asteroid-team/asteroid)和用于音频检测的[pyannote](https://github.com/pyannote/pyannote-audio),以及对于视觉的[timm](https://github.com/rwightman/pytorch-image-models),这些例子只是Hub中冰山一角,更多的模型。可以由你去探索。 diff --git a/chapters/zh-CN/chapter4/2.mdx b/chapters/zh-CN/chapter4/2.mdx index 696767537..af3efe96a 100644 --- a/chapters/zh-CN/chapter4/2.mdx +++ b/chapters/zh-CN/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx index 4e40bcc68..bbb3c5e39 100644 --- a/chapters/zh-CN/chapter4/3.mdx +++ b/chapters/zh-CN/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter4/4.mdx b/chapters/zh-CN/chapter4/4.mdx index a6c698e13..694f4ace2 100644 --- a/chapters/zh-CN/chapter4/4.mdx +++ b/chapters/zh-CN/chapter4/4.mdx @@ -1,5 +1,10 @@ # 构建模型卡片 + + 模型卡片是一个配置文件,可以说与模型存储库中的模型和 tokenizer 文件一样重要。它包含了模型的核心定义,确保了社区成员可以复现模型的结果,并提供一个其他成员可以在这个模型基础上构建他们的组件的平台。 记录训练和评估过程并提供有关使用的数据以及已完成的预处理和后续处理的足够信息,有助于其他人了解对模型的能力——确保模型存在和目前的限制、偏差可以识别和理解。 diff --git a/chapters/zh-CN/chapter4/5.mdx b/chapters/zh-CN/chapter4/5.mdx index 87f7e5241..093345126 100644 --- a/chapters/zh-CN/chapter4/5.mdx +++ b/chapters/zh-CN/chapter4/5.mdx @@ -1,5 +1,10 @@ # Part 1 完结! + + 这是课程第一部分的结尾!第 2 部分将在 11 月 15 日与大型社区活动一起发布,[点击这里](https://huggingface.co/blog/course-launch-event)查看更多信息. 您现在应该能够针对文本分类问题(单个或成对句子)对预训练模型进行微调,并将结果上传到模型中心。为确保您掌握了第一部分的内容,您应该针对您感兴趣的想法进行尝试(不一定是英语)!一旦你完成,您可以在[Hugging Face 社区](https://discuss.huggingface.co/)的[这个话题](https://discuss.huggingface.co/t/share-your-projects/6803)分享您的项目。 diff --git a/chapters/zh-CN/chapter4/6.mdx b/chapters/zh-CN/chapter4/6.mdx index 6f29d6c17..90a47c3dc 100644 --- a/chapters/zh-CN/chapter4/6.mdx +++ b/chapters/zh-CN/chapter4/6.mdx @@ -4,6 +4,11 @@ # 章末小测试 + + 让我们测试一下你在本章所学的知识! ### 1.Hub上的模型有什么限制? diff --git a/chapters/zh-CN/chapter5/1.mdx b/chapters/zh-CN/chapter5/1.mdx index 20fe40dd0..6004305cc 100644 --- a/chapters/zh-CN/chapter5/1.mdx +++ b/chapters/zh-CN/chapter5/1.mdx @@ -1,5 +1,10 @@ # 本章简介 + + 在[第三章](/course/chapter3)第一次体验了🤗Datasets 库,并发现在微调模型时有三个主要步骤: 1. 从hugs Face Hub加载一个数据集。 diff --git a/chapters/zh-CN/chapter5/2.mdx b/chapters/zh-CN/chapter5/2.mdx index ce3d11dae..119e9e931 100644 --- a/chapters/zh-CN/chapter5/2.mdx +++ b/chapters/zh-CN/chapter5/2.mdx @@ -1,8 +1,8 @@ # 如果我的数据集不在 Hub 上怎么办? - diff --git a/chapters/zh-CN/chapter5/3.mdx b/chapters/zh-CN/chapter5/3.mdx index f4b7eb5d1..ebf199396 100644 --- a/chapters/zh-CN/chapter5/3.mdx +++ b/chapters/zh-CN/chapter5/3.mdx @@ -1,8 +1,8 @@ # 是时候来学一下切片了 - diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index d8224b3bd..ebbe0b81f 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -1,8 +1,8 @@ # 大数据? 🤗 Datasets 来救援! - diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index b97bb7542..75d8d86b8 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -1,8 +1,8 @@ # 创建自己的数据集 - diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx index 4e6411a98..eab6b539c 100644 --- a/chapters/zh-CN/chapter5/6.mdx +++ b/chapters/zh-CN/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter5/7.mdx b/chapters/zh-CN/chapter5/7.mdx index a4bece254..fc9f5e4b4 100644 --- a/chapters/zh-CN/chapter5/7.mdx +++ b/chapters/zh-CN/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets,回顾! + + 这是对 🤗 Datasets 库的一次完整游览——祝贺你走到这一步!凭借从本章中获得的知识,您应该能够: - 从任何地方加载数据集,无论是 Hugging Face Hub、您的笔记本电脑还是您公司的远程服务器。 diff --git a/chapters/zh-CN/chapter5/8.mdx b/chapters/zh-CN/chapter5/8.mdx index 428d5bf1d..217b24feb 100644 --- a/chapters/zh-CN/chapter5/8.mdx +++ b/chapters/zh-CN/chapter5/8.mdx @@ -2,6 +2,11 @@ # 章末小测试 + + 本章涵盖了很多方面! 如果你没有掌握所有细节, 不用担心; 在下一章将帮助你了解内部的事情是如何工作的。 不过, 在继续下一章之前, 让我们测试一下你在本章学到的内容。 diff --git a/chapters/zh-CN/chapter6/1.mdx b/chapters/zh-CN/chapter6/1.mdx index a13faa5a1..6e8abfde2 100644 --- a/chapters/zh-CN/chapter6/1.mdx +++ b/chapters/zh-CN/chapter6/1.mdx @@ -1,5 +1,10 @@ # 本章简介 + + 在 [第三章] (/course/chapter3) 中,我们研究了如何在给定任务上微调模型。 当我们这样做时,我们需要使用与模型预训练相同的标记器——但是当我们想从头开始训练模型时该怎么办? 不过,使用在来自其他领域或语言的语料库上预训练的标记器通常不是最理想的。 例如,在英语语料库上训练的标记器在日语文本语料库上表现不佳,因为两种语言中空格和标点符号的使用非常不同。 在本章中,您将学习如何在文本语料库上训练一个全新的标记器,然后将其用于预训练语言模型。 这一切都将在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库的帮助下完成,该库在 [🤗 Transformers](https://github.com /huggingface/transformers) 库之内。 我们将仔细研究这个库提供的功能,并探讨快速标记器与“慢”版本的区别。 diff --git a/chapters/zh-CN/chapter6/10.mdx b/chapters/zh-CN/chapter6/10.mdx index 703459a3d..8da4b2dc2 100644 --- a/chapters/zh-CN/chapter6/10.mdx +++ b/chapters/zh-CN/chapter6/10.mdx @@ -2,6 +2,11 @@ # 章末小测验 + + 让我们测试一下您在本章中学到了什么! ### 1.你应该什么时候训练一个新的标记器? diff --git a/chapters/zh-CN/chapter6/2.mdx b/chapters/zh-CN/chapter6/2.mdx index ffac12aa8..b5f61ee6a 100644 --- a/chapters/zh-CN/chapter6/2.mdx +++ b/chapters/zh-CN/chapter6/2.mdx @@ -1,8 +1,8 @@ # 根据已有的tokenizer训练新的tokenizer - diff --git a/chapters/zh-CN/chapter6/3.mdx b/chapters/zh-CN/chapter6/3.mdx index f1ed19153..52d90f509 100644 --- a/chapters/zh-CN/chapter6/3.mdx +++ b/chapters/zh-CN/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx index c6012e419..433824079 100644 --- a/chapters/zh-CN/chapter6/3b.mdx +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter6/4.mdx b/chapters/zh-CN/chapter6/4.mdx index 5e58d2747..5304593a0 100644 --- a/chapters/zh-CN/chapter6/4.mdx +++ b/chapters/zh-CN/chapter6/4.mdx @@ -1,8 +1,8 @@ # 标准化和预标记化 - diff --git a/chapters/zh-CN/chapter6/5.mdx b/chapters/zh-CN/chapter6/5.mdx index 272210fad..ee50b8153 100644 --- a/chapters/zh-CN/chapter6/5.mdx +++ b/chapters/zh-CN/chapter6/5.mdx @@ -1,8 +1,8 @@ # 字节对编码标记化 - diff --git a/chapters/zh-CN/chapter6/6.mdx b/chapters/zh-CN/chapter6/6.mdx index c93b41898..240305f48 100644 --- a/chapters/zh-CN/chapter6/6.mdx +++ b/chapters/zh-CN/chapter6/6.mdx @@ -1,8 +1,8 @@ # WordPiece 标记化 - diff --git a/chapters/zh-CN/chapter6/7.mdx b/chapters/zh-CN/chapter6/7.mdx index 4303d8e58..96f156801 100644 --- a/chapters/zh-CN/chapter6/7.mdx +++ b/chapters/zh-CN/chapter6/7.mdx @@ -1,8 +1,8 @@ # Unigram标记化 - diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index c7922a72f..88921eb2e 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -1,8 +1,8 @@ # 逐块地构建标记器 - diff --git a/chapters/zh-CN/chapter6/9.mdx b/chapters/zh-CN/chapter6/9.mdx index b2f5ea052..ca37dfe4e 100644 --- a/chapters/zh-CN/chapter6/9.mdx +++ b/chapters/zh-CN/chapter6/9.mdx @@ -1,5 +1,10 @@ # 标记器,回顾! + + 完成这一章,辛苦了! 在深入研究标记器之后,您应该: diff --git a/chapters/zh/_toctree.yml b/chapters/zh/_toctree.yml deleted file mode 100644 index 453fc5e99..000000000 --- a/chapters/zh/_toctree.yml +++ /dev/null @@ -1,23 +0,0 @@ -- title: 1. Transformer 模型 - sections: - - local: chapter1/1 - title: 章节简介 - - local: chapter1/2 - title: 自然语言处理 - - local: chapter1/3 - title: Transformers能做什么? - - local: chapter1/4 - title: Transformers 是如何工作的? - - 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/zh/chapter1/1.mdx b/chapters/zh/chapter1/1.mdx deleted file mode 100644 index 68e6a14c7..000000000 --- a/chapters/zh/chapter1/1.mdx +++ /dev/null @@ -1,52 +0,0 @@ -# 简介 - -## 欢迎来到🤗课程 - - - -本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 - - -## 有什么是值得期待的? - -以下是课程的简要概述: - -
-Brief overview of the chapters of the course. - -
- -- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 -- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 -- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! - -这个课程: - -* 需要良好的 Python 知识 -* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) -* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 - -完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](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),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! - -## 我们是谁? - -关于作者: - -**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 - -**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 - -**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 - -**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 - -**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 - -**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[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 项目投入生产。 - -你准备好了吗?在本章中,您将学习: -* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 -* 关于 Transformer 架构 -* 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh/chapter1/10.mdx b/chapters/zh/chapter1/10.mdx deleted file mode 100644 index 23f768115..000000000 --- a/chapters/zh/chapter1/10.mdx +++ /dev/null @@ -1,253 +0,0 @@ - - -# 章末小测试 - -这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 - -让我们来测试一下你在这一章学到了什么! - -### 1. 探索 Hub 并寻找 `roberta-large-mnli` checkpoint。 它可以完成什么类型的任务? - - -roberta-large-mnli 页面回顾一下." - }, - { - text: "文本分类", - explain: "更准确地说,它对两个句子在三个标签(矛盾、无关、相近)之间的逻辑链接进行分类——这项任务也称为自然语言推理.", - correct: true - }, - { - text: "文本生成", - explain: "点击前往roberta-large-mnli 页面回顾一下." - } - ]} -/> - -### 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 pipeline将会返回这些." - }, - { - text: "它将返回一个生成的文本来完成这句话。", - explain: "这个选项是不对的 — text-generation pipeline将会返回这些.", - }, - { - 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("...") -``` - - has been waiting for you.", - explain: "这个选项是不对的。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" - }, - { - text: "This [MASK] has been waiting for you.", - explain: "正解! 这个模型的mask的掩码是[MASK].", - correct: true - }, - { - text: "This man has been waiting for you.", - explain: "这个选项是不对的。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入的文本中存在mask的token。" - } - ]} -/> - -### 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: "这个pipeline需要多个句子,而不仅仅是一个。", - explain: "这个选项是不对的。尽管正确使用时,此pipeline可以同时处理多个句子(与所有其他pipeline一样)。" - }, - { - text: "像往常一样,🤗 Transformers库出故障了。", - explain: "对此,我们不予置评!" - }, - { - text: "该pipeline需要更长的输入; 这个句子太短了。", - explain: "这个选项是不对的。 不过请注意,在这个pipeline处理时,太长的文本将被截断。" - } - ]} -/> - -### 5. “迁移学习”是什么意思? - - - -### 6. 语言模型在预训练时通常不需要标签,这样的说法是否正确。 - - -自监督,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", - correct: true - }, - { - text: "错误", - explain: "这不是一个正确的答案。" - } - ]} -/> - - -### 7. 选择最能描述“模型(model)”、“架构(architecture)”和“权重(weights)”的句子。 - - - -### 8. 你将使用以下哪种类型的模型来根据输入的提示生成文本? - - - -### 9. 你会使用哪些类型的模型来生成文本的摘要? - - - -### 10. 你会使用哪一种类型的模型来根据特定的标签对文本输入进行分类? - - - -### 11. 模型中观察到的偏见有哪些可能的来源? - - diff --git a/chapters/zh/chapter1/2.mdx b/chapters/zh/chapter1/2.mdx deleted file mode 100644 index 1b5ee0ea6..000000000 --- a/chapters/zh/chapter1/2.mdx +++ /dev/null @@ -1,20 +0,0 @@ -# 自然语言处理 - -在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 - -## 什么是自然语言处理? - -NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 - -以下是常见 NLP 任务的列表,每个任务都有一些示例: - -- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 -- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) -- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 -- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 -- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 - -NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 -## 为什么具有挑战性? - -计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh/chapter1/3.mdx b/chapters/zh/chapter1/3.mdx deleted file mode 100644 index 076263ba4..000000000 --- a/chapters/zh/chapter1/3.mdx +++ /dev/null @@ -1,287 +0,0 @@ -# Transformers能做什么? - - - -在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 - -👀 看到那个右上角的 在Colab中打开的按钮了吗? 单击它就可以打开一个包含本节所有代码示例的 Google Colab 笔记本。 每一个有实例代码的小节都会有它。 - -如果您想在本地运行示例,我们建议您查看准备. - - -## Transformer被应用于各个方面! -Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: - -![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) -[🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! - - -⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 Huggingface.co 帐户(https://huggingface.co/join)以使用所有可用功能! - - -在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 - -## 使用pipelines - - -(这里有一个视频,但是国内可能打不开,译者注) - - -🤗 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}] -``` -默认情况下,此pipeline选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建**分类器**对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。 - -将一些文本传递到pipeline时涉及三个主要步骤: - -1. 文本被预处理为模型可以理解的格式。 -2. 预处理的输入被传递给模型。 -3. 模型处理后输出最终人类可以理解的结果。 - -目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: - -* **特征提取**(获取文本的向量表示) -* **填充空缺** -* **ner**(命名实体识别) -* **问答** -* **情感分析** -* **文本摘要** -* **文本生成** -* **翻译** -* **零样本分类** - -让我们来看看其中的一些吧! - -## 零样本分类 -我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 - -```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]} -``` - -此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! - -✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 - - -## 文本生成 -现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 - -```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** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 - - -✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 - - -## 在pipeline中使用 Hub 中的其他模型 -前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 - -让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: - -```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'}] -``` -您可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)甚至包含支持多种语言的多语言模型。 - -通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 - -✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! - - -## 推理 API -所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 - -小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 - -## Mask filling -您将尝试的下一个pipeline是 **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** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 - - -✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? - - -## 命名实体识别 -命名实体识别 (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),而布鲁克林是一个位置 (LOC)。 - -我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 - - -✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? - - -## 问答系统 -问答pipeline使用来自给定上下文的信息回答问题: -```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'} -klyn", -) - -``` -请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 - -## 文本摘要 -文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: - -```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_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(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主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file diff --git a/chapters/zh/chapter1/4.mdx b/chapters/zh/chapter1/4.mdx deleted file mode 100644 index 45641e71e..000000000 --- a/chapters/zh/chapter1/4.mdx +++ /dev/null @@ -1,172 +0,0 @@ -# Transformers 是如何工作的? - -在本节中,我们将深入了解 Transformer 模型的架构。 - -## 一点Transformers的发展历史 - -以下是 Transformer 模型(简短)历史中的一些关键节点: - -
-A brief chronology of Transformers models. - -
- -[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 - -- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 - -- **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% 的性能 - -- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) - -- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) - -这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: - -- GPT-like (也被称作自回归Transformer模型) -- BERT-like (也被称作自动编码Transformer模型) -- BART/T5-like (也被称作序列到序列的 Transformer模型) - -稍后我们将更深入地探讨这些分类。 - -## Transformers是语言模型 - -上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! - -这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 - -任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 - -
-Example of causal language modeling in which the next word from a sentence is predicted. - -
- -另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 - -
-Example of masked language modeling in which a masked word from a sentence is predicted. - -
- -## Transformer是大模型 - -除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 - -
-Number of parameters of recent Transformers models -
- -不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 - -
-The carbon footprint of a large language model. - -
- - - -Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 - -想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! - -这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 - - -## 迁移学习 - - - -*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 - -
-The pretraining of a language model is costly in both time and money. - -
- -这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 - -另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: - -* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 -* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 -* 出于同样的原因,获得好结果所需的时间和资源要少得多 - -例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 - -
-The fine-tuning of a language model is cheaper than pretraining in both time and money. - -
- -因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 - -这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 - -## 一般的体系结构 -在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 - - - -## 介绍 - -该模型主要由两个块组成: - -* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 -* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 - -
-Architecture of a Transformers models - -
- -这些部件中的每一个都可以独立使用,具体取决于任务: - -* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 -* **Decoder-only models**: 适用于生成任务,如文本生成。 -* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 - -在后面的部分中,我们将单独地深入研究这些体系结构。 - -## 注意力层 - -Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 - -把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 - -同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 - -现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 - -## 原始的结构 - -Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 - -为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 - -最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: - -
-Architecture of a Transformers models - -
- -注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 - -也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 - -## 架构与参数 - -在本课程中,当我们深入探讨Transformers模型时,您将看到 -架构、参数和模型 -。 这些术语的含义略有不同: - -* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 -* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 -* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 - -例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." diff --git a/chapters/zh/chapter1/5.mdx b/chapters/zh/chapter1/5.mdx deleted file mode 100644 index 7aa765ec2..000000000 --- a/chapters/zh/chapter1/5.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# “编码器”模型 - - - -“编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 - -这些模型的预训练通常围绕着以某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 - -“编码器”模型最适合于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 - -该系列模型的典型代表有: - -- [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/zh/chapter1/6.mdx b/chapters/zh/chapter1/6.mdx deleted file mode 100644 index 2de4c44a6..000000000 --- a/chapters/zh/chapter1/6.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# “解码器”模型 - - - -“解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 - -“解码器”模型的预训练通常围绕预测句子中的下一个单词进行。 - -这些模型最适合于涉及文本生成的任务。 - -该系列模型的典型代表有: - - -- [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/zh/chapter1/7.mdx b/chapters/zh/chapter1/7.mdx deleted file mode 100644 index 99dc00eea..000000000 --- a/chapters/zh/chapter1/7.mdx +++ /dev/null @@ -1,16 +0,0 @@ -# 序列到序列模型 - - - -编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 - -这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[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) diff --git a/chapters/zh/chapter1/8.mdx b/chapters/zh/chapter1/8.mdx deleted file mode 100644 index 707731892..000000000 --- a/chapters/zh/chapter1/8.mdx +++ /dev/null @@ -1,31 +0,0 @@ -# Bias and limitations - - - -如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。 - -为了快速解释清楚这个问题,让我们回到一个使用BERT模型的pipeline的例子: - -```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'] -``` -当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管BERT是使用经过筛选和清洗后,明显中立的数据集上建立的的Transformer模型,而不是通过从互联网上搜集数据(它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)数据集)。 - -因此,当您使用这些工具时,您需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file diff --git a/chapters/zh/chapter1/9.mdx b/chapters/zh/chapter1/9.mdx deleted file mode 100644 index 16c5ab6ad..000000000 --- a/chapters/zh/chapter1/9.mdx +++ /dev/null @@ -1,11 +0,0 @@ -# 总结 - -在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 - -我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: - -| 模型 | 示例 | 任务| -| ---- | ---- |----| -| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| -| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| -| 编码器-解码器 | BART, T5, Marian, mBART |文本摘要、翻译、生成问题的回答| \ No newline at end of file diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index e64100810..bd41c5dcb 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -61,6 +61,7 @@ def read_and_split_frameworks(fname): else: return "".join(content) + def extract_cells(content): """ Extract the code/output cells from content.