# Course

## Docs

- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter7/9.md)
- [Суммаризация[[summarization]]](https://huggingface.co/learn/course/ru/chapter7/5.md)
- [Введение[[introduction]]](https://huggingface.co/learn/course/ru/chapter7/1.md)
- [Дообучение модели маскированного языкового моделирования[[fine-tuning-a-masked-language-model]]](https://huggingface.co/learn/course/ru/chapter7/3.md)
- [Обучение каузальной языковой модели с нуля[[training-a-causal-language-model-from-scratch]]](https://huggingface.co/learn/course/ru/chapter7/6.md)
- [Освоение NLP[[mastering-nlp]]](https://huggingface.co/learn/course/ru/chapter7/8.md)
- [Перевод[[translation]]](https://huggingface.co/learn/course/ru/chapter7/4.md)
- [Ответы на вопросы[[question-answering]]](https://huggingface.co/learn/course/ru/chapter7/7.md)
- [Классификация токенов[[token-classification]]](https://huggingface.co/learn/course/ru/chapter7/2.md)
- [Первая часть завершена!](https://huggingface.co/learn/course/ru/chapter4/5.md)
- [Hugging Face Hub](https://huggingface.co/learn/course/ru/chapter4/1.md)
- [Публикация обученых моделей в общий доступ](https://huggingface.co/learn/course/ru/chapter4/3.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter4/6.md)
- [Создание карточки модели](https://huggingface.co/learn/course/ru/chapter4/4.md)
- [Использование предобученных моделей](https://huggingface.co/learn/course/ru/chapter4/2.md)
- [Fine-tuning, итоги!](https://huggingface.co/learn/course/ru/chapter3/5.md)
- [Введение](https://huggingface.co/learn/course/ru/chapter3/1.md)
- [Fine-tuning модели с использованием Trainer API](https://huggingface.co/learn/course/ru/chapter3/3.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter3/6.md)
- [Fine-tuning модели с использованием Keras](https://huggingface.co/learn/course/ru/chapter3/3_tf.md)
- [Полное обучение](https://huggingface.co/learn/course/ru/chapter3/4.md)
- [Предобработка данных](https://huggingface.co/learn/course/ru/chapter3/2.md)
- [Как написать хорошее сообщение об ошибке (issue)[[how-to-write-a-good-issue]]](https://huggingface.co/learn/course/ru/chapter8/5.md)
- [Введение[[introduction]]](https://huggingface.co/learn/course/ru/chapter8/1.md)
- [Обращение за помощью на форумах[[asking-for-help-on-the-forums]]](https://huggingface.co/learn/course/ru/chapter8/3.md)
- [Часть 2 завершена![[part-2-completed]]](https://huggingface.co/learn/course/ru/chapter8/6.md)
- [Отладка обучения[[debugging-the-training-pipeline]]](https://huggingface.co/learn/course/ru/chapter8/4.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter8/7.md)
- [Отладка обучения[[debugging-the-training-pipeline]]](https://huggingface.co/learn/course/ru/chapter8/4_tf.md)
- [Что делать, если возникла ошибка[[what-to-do-when-you-get-an-error]]](https://huggingface.co/learn/course/ru/chapter8/2.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter9/9.md)
- [Интеграция с Hugging Face Hub[[integrations-with-the-hugging-face-hub]]](https://huggingface.co/learn/course/ru/chapter9/5.md)
- [Введение в Gradio[[introduction-to-gradio]]](https://huggingface.co/learn/course/ru/chapter9/1.md)
- [Понимание класса Interface[[understanding-the-interface-class]]](https://huggingface.co/learn/course/ru/chapter9/3.md)
- [Расширенные возможности Interface[[advanced-interface-features]]](https://huggingface.co/learn/course/ru/chapter9/6.md)
- [Gradio, проверка![[gradio-check]]](https://huggingface.co/learn/course/ru/chapter9/8.md)
- [Делимся демо с другими[[sharing-demos-with-others]]](https://huggingface.co/learn/course/ru/chapter9/4.md)
- [Введение в Gradio Blocks[[introduction-to-gradio-blocks]]](https://huggingface.co/learn/course/ru/chapter9/7.md)
- [Создание вашего первого демо[[building-your-first-demo]]](https://huggingface.co/learn/course/ru/chapter9/2.md)
- [Занятия и семинары[[live-sessions-and-workshops]]](https://huggingface.co/learn/course/ru/events/1.md)
- [Вечеринка Gradio Blocks[[gradio-blocks-party]]](https://huggingface.co/learn/course/ru/events/3.md)
- [Событие посвященное выходу 2 части курса[[part-2-release-event]]](https://huggingface.co/learn/course/ru/events/2.md)
- [Токенизаторы, проверка![[tokenizers-check]]](https://huggingface.co/learn/course/ru/chapter6/9.md)
- [Токенизация Byte-Pair Encoding[[byte-pair-encoding-tokenization]]](https://huggingface.co/learn/course/ru/chapter6/5.md)
- [Введение[[introduction]]](https://huggingface.co/learn/course/ru/chapter6/1.md)
- [Особые возможности быстрых токенизаторов[[fast-tokenizers-special-powers]]](https://huggingface.co/learn/course/ru/chapter6/3.md)
- [Токенизация WordPiece[[wordpiece-tokenization]]](https://huggingface.co/learn/course/ru/chapter6/6.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter6/10.md)
- [Создание токенизатора, блок за блоком[[building-a-tokenizer-block-by-block]]](https://huggingface.co/learn/course/ru/chapter6/8.md)
- [Нормализация и предварительная токенизация[[normalization-and-pre-tokenization]]](https://huggingface.co/learn/course/ru/chapter6/4.md)
- [Токенизация Unigram[[unigram-tokenization]]](https://huggingface.co/learn/course/ru/chapter6/7.md)
- [Быстрые токенизаторы в QA конвейере[[fast-tokenizers-in-the-qa-pipeline]]](https://huggingface.co/learn/course/ru/chapter6/3b.md)
- [Обучение нового токенизатора на основе старого[[training-a-new-tokenizer-from-an-old-one]]](https://huggingface.co/learn/course/ru/chapter6/2.md)
- [Введение](https://huggingface.co/learn/course/ru/chapter5/1.md)
- [Препарируем 🤗 Datasets](https://huggingface.co/learn/course/ru/chapter5/3.md)
- [Семантический поиск с помощью FAISS](https://huggingface.co/learn/course/ru/chapter5/6.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter5/8.md)
- [Big data? 🤗 Datasets спешат на помощь!](https://huggingface.co/learn/course/ru/chapter5/4.md)
- [🤗 Datasets, итоги!](https://huggingface.co/learn/course/ru/chapter5/7.md)
- [Что делать, если моего датасета на нет на Hub?](https://huggingface.co/learn/course/ru/chapter5/2.md)
- [Глоссарий](https://huggingface.co/learn/course/ru/glossary/1.md)
- [Обработка нескольких последовательностей[[handling-multiple-sequences]]](https://huggingface.co/learn/course/ru/chapter2/5.md)
- [Введение[[introduction]]](https://huggingface.co/learn/course/ru/chapter2/1.md)
- [Модели[[models]]](https://huggingface.co/learn/course/ru/chapter2/3.md)
- [Собираем все воедино[[putting-it-all-together]]](https://huggingface.co/learn/course/ru/chapter2/6.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter2/8.md)
- [Токенизаторы[[tokenizers]]](https://huggingface.co/learn/course/ru/chapter2/4.md)
- [Базовое использование завершено![[basic-usage-completed]]](https://huggingface.co/learn/course/ru/chapter2/7.md)
- [За конвейером[[behind-the-pipeline]]](https://huggingface.co/learn/course/ru/chapter2/2.md)
- [Введение](https://huggingface.co/learn/course/ru/chapter0/1.md)
- [Итоги](https://huggingface.co/learn/course/ru/chapter1/9.md)
- [Модели-кодировщики](https://huggingface.co/learn/course/ru/chapter1/5.md)
- [Введение](https://huggingface.co/learn/course/ru/chapter1/1.md)
- [Трансформеры: на что они способны?](https://huggingface.co/learn/course/ru/chapter1/3.md)
- [Модели-декодировщики](https://huggingface.co/learn/course/ru/chapter1/6.md)
- [Тест в конце главы[[end-of-chapter-quiz]]](https://huggingface.co/learn/course/ru/chapter1/10.md)
- [Предвзятости и ограничения](https://huggingface.co/learn/course/ru/chapter1/8.md)
- [Как работают трансформеры?](https://huggingface.co/learn/course/ru/chapter1/4.md)
- [Модели вида "seq2seq"](https://huggingface.co/learn/course/ru/chapter1/7.md)
- [Обработка естественного языка](https://huggingface.co/learn/course/ru/chapter1/2.md)

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter7/9.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={7}
    classNames="absolute z-10 right-0 top-0"
/>

Давайте проверим, чему вы научились в этой главе!

### 1. Какую из следующих задач можно сформулировать как проблему классификации токенов?

<Question
	choices={[
		{
			text: "Поиск грамматических компонентов в предложении.",
			explain: "Правильно! Затем мы можем пометить каждое слово как существительное, глагол и т.д.",
			correct: true
		},
		{
			text: "Определение того, является ли предложение грамматически правильным или нет.",
			explain: "Нет, это проблема классификации последовательностей."
		},
		{
			text: "Поиск персон, упомянутых в предложении.",
			explain: "Правильно! Мы можем пометить каждое слово как персона или не персона.",
            correct: true
		},
        {
			text: "Поиск части слов в предложении, которое отвечает на вопрос.",
			explain: "Нет, это была бы проблема ответа на вопрос."
		}
	]}
/>

### 2. Какая часть предварительной обработки для классификации токенов отличается от других конвейеров предварительной обработки?

<Question
	choices={[
		{
			text: "Ничего делать не нужно: тексты уже токенизированы.",
			explain: "Тексты действительно представлены в виде отдельных слов, но нам все равно нужно применить модель токенизации по подсловам."
		},
		{
			text: "Тексты представлены в виде слов, поэтому нам нужно применить только токенизацию по подсловам.",
			explain: "Правильно! Это отличие от обычной предобработки, где нам нужно применить полный конвейер токенизации. Можете ли вы придумать другое отличие?",
			correct: true
		},
		{
			text: "Мы используем <code>-100</code> для обозначения специальных токенов.",
			explain: "Это не относится к классификации токенов - мы всегда используем <code>-100</code> в качестве метки для токенов, которые мы хотим игнорировать в потерях."
		},
		{
			text: "При применении усечения/дополнения нам нужно убедиться, что метки имеют тот же размер, что и входные данные.",
			explain: "Действительно! Но это не единственное отличие.",
			correct: true
		}
	]}
/>

### 3. Какая проблема возникает при токенизации слов в проблеме классификации токенов и при необходимости их маркировки?

<Question
	choices={[
		{
			text: "Токенизатор добавляет специальные токены, для которых у нас нет меток.",
			explain: "Мы помечаем их -100, чтобы они игнорировались при потерях."
		},
		{
			text: "Каждое слово может создавать несколько токенов, поэтому в итоге мы получаем больше токенов, чем меток.",
			explain: "Это основная проблема, и нам нужно выровнять оригинальные метки с токенами.",
			correct: true
		},
		{
			text: "Добавленные токены не имеют меток, поэтому проблем не возникает.",
			explain: "Это неверно; нам нужно столько меток, сколько у нас токенов, иначе наши модели будут ошибаться."
		}
	]}
/>

### 4. Что означает "доменная адаптация"?

<Question
	choices={[
		{
			text: "Это когда мы запускаем модель на датасете и получаем предсказания для каждого примера в этом датасете.",
			explain: "Нет, это просто инференс."
		},
		{
			text: "Это когда мы обучаем модель на датасете.",
			explain: "Нет, это обучение модели; здесь нет адаптации."
		},
		{
			text: "Это когда мы проводим дообучение предварительно обученной модели на новом датасете, и он выдает прогнозы, более адаптированные к этому датасету.",
			explain: "Правильно! Модель адаптировала свои знания к новому датасету.",
            correct: true
		},
        {
			text: "Это когда мы добавляем в датасет неправильно классифицированные образцы, чтобы сделать нашу модель более надежной.",
			explain: "Это, конечно, нужно делать, если вы регулярно переобучаете свою модель, но это не доменная адаптация."
		}
	]}
/>

### 5. Что такое метки в проблеме маскированного языкового моделирования?

<Question
	choices={[
		{
			text: "Некоторые токены во входном предложении маскируются случайным образом, а метками служат оригинальные входные токены.",
			explain: "Это оно!",
            correct: true
		},
		{
			text: "Некоторые токены во входном предложении случайным образом маскируются, а метками служат исходные токены, сдвинутые влево.",
			explain: "Нет, сдвиг меток влево соответствует предсказанию следующего слова, что соответствует каузальному языковому моделированию."
		},
		{
			text: "Некоторые токены во входном предложении маскируются случайным образом, а метка означает, является ли предложение положительным или отрицательным.",
			explain: "Это проблема классификации последовательностей с некоторыми дополнениями к данным, а не маскированное языковое моделирование."
		},
        {
			text: "Некоторые токены в двух входных предложениях маскируются случайным образом, а метка означает, похожи эти два предложения или нет.",
			explain: "Это проблема классификации последовательностей с некоторыми дополнительными данными, а не маскированное моделирование языка."
		}
	]}
/>

### 6. Какую из этих задач можно рассматривать как проблему преобразования последовательности-в-последовательность (sequence-to-sequence problem)? 

<Question
	choices={[
		{
			text: "Написание коротких обзоров длинных документов",
			explain: "Да, это проблема суммаризации. Попробуйте другой ответ!",
            correct: true
		},
		{
			text: "Ответы на вопросы о документе",
			explain: "Это можно сформулировать как проблему преобразования последовательности-в-последовательность. Однако это не единственный правильный ответ.",
            correct: true
		},
		{
			text: "Перевод текста на китайском языке на английский",
			explain: "Это определенно проблема преобразования последовательности-в-последовательность. Вы можете найти другую?",
            correct: true
		},
        {
			text: "Исправление сообщений, отправленных моим племянником/другом, чтобы они были на правильном английском языке",
			explain: "Это своего рода проблема перевода, так что это определенно задача последовательности-в-последовательности. Однако это не единственный правильный ответ!",
			correct: true
		}
	]}
/>

### 7. Каков правильный способ предварительной обработки данных для проблемы преобразования последовательности-в-последовательность?

<Question
	choices={[
		{
			text: "Входные данные и цели должны быть переданы токенизатору вместе с <code>inputs=...</code> и <code>targets=...</code>.",
			explain: "Возможно, в будущем мы добавим такой API, но сейчас это невозможно."
		},
		{
			text: "Входные данные и цели должны быть предварительно обработаны в двух раздельных вызовах токенизатора.",
			explain: "Это правда, но неполная. Вам нужно кое-что сделать, чтобы убедиться, что токенизатор правильно обрабатывает оба варианта."
		},
		{
			text: "Как обычно, нам просто нужно выполнить токенизацию входных данных.",
			explain: "Не в проблеме классификации последовательностей; цели - это тексты, которые мы должны преобразовать в числа!"
		},
        {
			text: "Входные данные должны быть переданы токенизатору, и цели тоже, но под управлением специального контекстного менеджера.",
			explain: "Верно, токенизатор должен быть переведен в target режим этим менеджером контекста.",
			correct: true
		}
	]}
/>

{#if fw === 'pt'}

### 8. Почему существует специальный подкласс `Trainer` для проблем преобразования " последовательности-в-последовательность"?

<Question
	choices={[
		{
			text: "Поскольку проблемы преобразования последовательности-в-последовательность используют определенные пользователем потери, для игнорирования меток задайте <code>-100</code>.",
			explain: "Это вовсе не определенные пользователем потери, а то, как потери всегда вычисляются."
		},
		{
			text: "Поскольку проблемы преобразования последовательности-в-последовательность требуют специального цикла оценки",
			explain: "Это верно. Предсказания моделей преобразующих последовательность-в-последовательность часто выполняются с помощью метода <code>generate()</code>.",
			correct: true
		},
		{
			text: "Поскольку целью являются тексты в проблемах преобразования последовательности-в-последовательность",
			explain: "<code>Trainer</code> не особо заботится об этом, поскольку они уже были предварительно обработаны."
		},
        {
			text: "Поскольку в проблемах преобразования последовательности-в-последовательность мы используем две модели",
			explain: "В некотором смысле мы используем две модели, энкодер и декодер, но они сгруппированы в одну модель."
		}
	]}
/>

{:else}

### 9. Почему при вызове `compile()` для модели трансформера часто нет необходимости определять потери?

<Question
	choices={[
		{
			text: "Потому что модели трансформеров обучаются с помощью обучения без учителя.",
			explain: "Не совсем так - даже для обучения без учителя нужна функция потерь!"
		},
		{
			text: "Потому что по умолчанию используется внутренний вывод потерь модели.",
			explain: "Это верно!",
			correct: true
		},
		{
			text: "Потому что вместо этого мы вычисляем метрики после обучения.",
			explain: "Мы часто так и поступаем, но это не объясняет, откуда мы берем значение потерь, которое оптимизируем в процессе обучения."
		},
        {
			text: "Потому что вместо этого потери определяются в `model.fit()`.",
			explain: "Нет, функция потерь всегда фиксирована после запуска `model.compile()` и не может быть изменена в `model.fit()`."
		}
	]}
/>

{/if}

### 10. Когда следует проводить предварительное обучение новой модели?

<Question
	choices={[
		{
			text: "Если для вашего специфического языка не существует предварительно обученной модели.",
			explain: "Это верно!",
			correct: true
		},
		{
			text: "Когда у вас много данных, даже если есть предварительно обученная модель, которая может работать с ними.",
			explain: "В этом случае, вероятно, следует использовать предварительно обученную модель и провести ее дообучение на ваших данных, чтобы избежать больших вычислительных затрат."
		},
		{
			text: "Если у вас есть опасения по поводу смещения предварительно обученной модели, которую вы используете",
			explain: "Это правда, но вы должны быть уверены, что данные, которые вы будете использовать для обучения, действительно лучше.",
			correct: true
		},
        {
			text: "Когда имеющиеся предварительно обученные модели недостаточно хороши.",
			explain: "Вы уверены, что правильно отладили свой цикл обучения?"
		}
	]}
/>

### 11. Почему легко провести предварительное обучение языковой модели на большом количестве текстов?

<Question
	choices={[
		{
			text: "Потому что в Интернете можно найти множество текстов.",
			explain: "Хотя это и верно, но не отвечает на вопрос. Попробуйте еще раз!"
		},
		{
			text: "Потому что задача предварительного обучения не требует от людей маркировки данных.",
			explain: "Верно, языковое моделирование - это проблема обучения с самоконтролем (self-supervised problem).",
			correct: true
		},
		{
			text: "Потому что библиотека 🤗 Transformers требует всего несколько строк кода для начала обучения.",
			explain: "Хотя это и верно, но не совсем отвечает на поставленный вопрос. Попробуйте другой ответ!"
		}
	]}
/>

### 12. Какие основные проблемы возникают при предварительной обработке данных для задачи ответа на вопрос (question answering)?

<Question
	choices={[
		{
			text: "Вам нужно выполнить токенизацию входных данных.",
			explain: "Это верно, но действительно ли это главная проблема?"
		},
		{
			text: "Вам приходится иметь дело с очень длинными контекстами, в которых дается несколько обучающих признаков, которые могут содержать ответ, а могут и не содержать.",
			explain: "Это, безусловно, одна из проблем.",
			correct: true
		},
		{
			text: "Вам нужно провести токенизацию ответов на вопрос так же, как и входных данных.",
			explain: "Нет, если только вы не формулируете проблему ответа на вопрос как задачу преобразования последовательности-в-последовательность."
		},
       {
			text: "На основе фрагмента ответа в тексте нужно найти начальный и конечный токен в исходном токенизированном тексте.",
			explain: "Это одна из самых сложных частей, да!",
			correct: true
		}
	]}
/>

### 13. Как обычно выполняется постобработка в задаче ответов на вопросы?

<Question
	choices={[
		{
			text: "Модель дает вам начальную и конечную позиции ответа, и вам остается только декодировать соответствующий промежуток токенов.",
			explain: "Это может быть одним из способов, но слишком упрощенным."
		},
		{
			text: "Модель возыращает вам начальную и конечную позиции ответа для каждого признака, созданного одним примером, а вам остается только декодировать соответствующий промежуток токенов в том из них, который имеет наилучшую оценку.",
			explain: "Это близко к постобработке, которую мы изучали, но это не совсем правильно."
		},
		{
			text: "Модель возвращает вам начальную и конечную позиции ответа для каждого признака, созданного одним примером, а вам остается только сопоставить их с промежутком в контексте, чтобы выбрать тот, который набрал наибольшую оценку.",
			explain: "Вот и все в двух словах!",
			correct: true
		},
        {
			text: "Модель генерирует ответ, а вам остается только декодировать его.",
			explain: "Нет, если только вы не формулируете проблему ответа на вопрос как проблему преобразования последовательности-в-последовательность."
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/9.mdx" />

### Суммаризация[[summarization]]
https://huggingface.co/learn/course/ru/chapter7/5.md

# Суммаризация[[summarization]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section5_tf.ipynb"},
]} />

{/if}


В этом разделе мы рассмотрим, как модели Transformer могут быть использованы для сжатия длинных документов в краткое изложение - задача, известная как _суммаризация текста (text summarization)_. Это одна из самых сложных задач NLP, поскольку она требует целого ряда способностей, таких как понимание длинных отрывков и генерация связного текста, отражающего основные темы документа. Однако при правильном подходе суммаризация текста - это мощный инструмент, который может ускорить различные бизнес-процессы, избавив экспертов домена от необходимости детального прочтения длинных документов.

<Youtube id="yHnr5Dk2zCI"/>

Хотя на [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads) уже существуют различные дообученные модели для суммаризации, почти все они подходят только для англоязычных документов. Поэтому, чтобы добавить изюминку в этот раздел, мы обучим двухязыковую модель для английского и испанского языков. К концу этого раздела у вас будет [модель](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es), способная к суммаризации отзывов покупателей, как показано здесь:

<iframe src="https://course-demos-mt5-small-finetuned-amazon-en-es.hf.space" frameBorder="0" height="400" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Как мы увидим, эти резюме кратки, поскольку они составляются на основе названий, которые покупатели указывают в своих отзывах о товаре. Для начала давайте соберем подходящий двуязыковой корпус для этой задачи.

## Подготовка многоязыкового корпуса[[preparing-a-multilingual-corpus]]

Для создания нашей двуязыковой суммаризации мы будем использовать [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi). Этот корпус состоит из отзывов о товарах Amazon на шести языках и обычно используется для тестирования многоязыковых классификаторов. Однако, поскольку каждый отзыв сопровождается коротким заголовком, мы можем использовать заголовки в качестве целевых резюме для обучения нашей модели! Чтобы начать работу, давайте загрузим английские и испанские подмножества из 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
    })
})
```

Как видите, для каждого языка есть 200 000 отзывов в части `train` и по 5 000 отзывов в частях `validation` и `test`. Интересующая нас информация о рецензиях содержится в столбцах `review_body` и `review_title`. Давайте рассмотрим несколько примеров, создав простую функцию, которая берет случайную выборку из обучающего множества с помощью методов, изученных в [Главе 5](../chapter5/1):

```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.'
```

<Tip>

✏️ **Попробуйте!** Измените random seed в команде `Dataset.shuffle()`, чтобы изучить другие отзывы в корпусе. Если вы владеете испанским языком, посмотрите на некоторые отзывы в `spanish_dataset`, чтобы понять, похожи ли их названия на разумные резюме.

</Tip>

Эта выборка демонстрирует разнообразие отзывов, которые обычно можно найти в сети, - от положительных до отрицательных (и все, что между ними!). Хотя пример с названием "meh" не очень информативен, остальные названия выглядят как достойные резюме самих отзывов. Обучение модели суммаризации всех 400 000 отзывов заняло бы слишком много времени на одном GPU, поэтому вместо этого мы сосредоточимся на создании резюме для одного домена продуктов. Чтобы получить представление о том, какие домены мы можем выбрать, давайте преобразуем `english_dataset` в `pandas.DataFrame` и вычислим количество отзывов по каждой категории товаров:

```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
```

Самые популярные товары в английском датасете - это бытовые товары, одежда и беспроводная электроника. Однако, чтобы поддержать тему Amazon, давайте сосредоточимся на суммаризации отзывов о книгах - в конце концов, именно для этого компания и была основана! Мы видим две категории товаров, которые подходят для этой цели (`book` и `digital_ebook_purchase`), поэтому давайте отфильтруем датасеты на обоих языках только для этих товаров. Как мы видели в [Главе 5](../chapter5/1), функция `Dataset.filter()` позволяет нам очень эффективно разделять датасет на части, поэтому мы можем определить простую функцию для этого:

```python
def filter_books(example):
    return (
        example["product_category"] == "book"
        or example["product_category"] == "digital_ebook_purchase"
    )
```

Теперь, когда мы применим эту функцию к `english_dataset` и `spanish_dataset`, результат будет содержать только те строки, в которых присутствуют категории книг. Прежде чем применить фильтр, давайте изменим формат `english_dataset` с `"pandas"` обратно на `"arrow"`:

```python
english_dataset.reset_format()
```

Затем мы можем применить функцию фильтрации, и в качестве проверки работоспособности давайте посмотрим на выборку отзывов, чтобы убедиться, что они действительно посвящены книгам:

```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.'
```

Хорошо, мы видим, что отзывы не совсем о книгах и могут относиться к таким вещам, как календари и электронные приложения, например OneNote. Тем не менее, домен кажется подходящим для обучения модели суммаризации. Прежде чем мы рассмотрим различные модели, подходящие для этой задачи, нам осталось подготовить данные: объединить английские и испанские отзывы в один объект `DatasetDict`. 🤗Datasets предоставляет удобную функцию `concatenate_datasets()`, которая (как следует из названия) стекирует два объекта `Dataset` друг на друга. Таким образом, чтобы создать двуязыковой датасет, мы пройдемся по каждой части, объединим датасеты для этой части и перемешаем результат, чтобы наша модель не была слишком переобучена для одного языка:

```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)

# Посмотрим на несколько примеров
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'
```

Это определенно похоже на смесь английских и испанских обзоров! Теперь, когда у нас есть тренировочный корпус, осталось проверить распределение слов в рецензиях и их заголовках. Это особенно важно для задач суммаризации, где короткие эталонные резюме в данных могут склонять модель в сторону вывода только одного или двух слов в сгенерированных резюме. На графиках ниже показаны распределения слов, и мы видим, что названия сильно перекошены в сторону 1-2 слов:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/review-lengths.svg" alt="Word count distributions for the review titles and texts."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/review-lengths-dark.svg" alt="Word count distributions for the review titles and texts."/>
</div>

Чтобы справиться с этой проблемой, мы отфильтруем примеры с очень короткими заголовками, чтобы наша модель могла создавать более интересные резюме. Поскольку мы имеем дело с английскими и испанскими текстами, мы можем использовать грубую эвристику для разделения названий по символам пробела, а затем использовать наш надежный метод `Dataset.filter()` следующим образом:

```python
books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2)
```

Теперь, когда мы подготовили корпус, давайте рассмотрим несколько возможных моделей Transformer, которые можно было бы дообучить на его основе!

## Модели для суммаризации текста[[models-for-text-summarization]]

Если задуматься, то суммаризация текста - это задача, похожая на машинный перевод: у нас есть текст, например рецензия, который мы хотели бы "перевести" в более короткую версию, передающую основные особенности исходного текста. Соответственно, большинство моделей Transformer для суммаризации используют архитектуру кодер-декодер, с которой мы впервые столкнулись в [Глава 1](../chapter1/1), хотя есть и исключения, например семейство моделей GPT, которые также могут использоваться для суммаризации в условиях few-shot настроек. В следующей таблице перечислены некоторые популярные предварительно обученные модели, которые можно дообучить для суммаризации.

| Модель Transformer | Описание                                                                                                                                                                                                    | Многоязычная? |
| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: |
|    [GPT-2](https://huggingface.co/gpt2-xl)    | Хотя GPT-2 обучен как авторегрессивная языковая модель, вы можете заставить его генерировать резюме, добавляя "TL;DR" в конце входного текста.                                                                          |      ❌       |
|   [PEGASUS](https://huggingface.co/google/pegasus-large)   | Использует цель предварительного обучения для предсказания замаскированных предложений в текстах с несколькими предложениями. Эта задача предварительного обучения ближе к суммаризации, чем к классическому языковому моделированию демонстрирует высокие результаты в популярных бенчмарках.|      ❌       |
|     [T5](https://huggingface.co/t5-base)      | Универсальная трансформерная архитектура, которая формулирует все задачи в рамках преобразования текста в текст; например, входной формат модели для суммаризации документа - `summarize: ARTICLE`.                              |      ❌       |
|     [mT5](https://huggingface.co/google/mt5-base)     | Многоязыковая версия T5, предварительно обученная на многоязыковом корпусе Common Crawl (mC4), охватывающем 101 язык.                                                                                                |      ✅       |
|    [BART](https://huggingface.co/facebook/bart-base)     | Новая архитектура Transformer с кодером и стеком декодеров, обученных восстанавливать поврежденный входной сигнал, сочетает в себе схемы предварительного обучения BERT и GPT-2.                                    |      ❌       |
|  [mBART-50](https://huggingface.co/facebook/mbart-large-50)   | Многоязыковая версия BART, предварительно обученная на 50 языках.                                                                                                                                                     |      ✅       |

Как видно из этой таблицы, большинство моделей Transformer для суммаризации (и вообще для большинства задач NLP) являются монолингвистическими. Это хорошо, если ваша задача на "высокоресурсном" языке, таком как английский или немецкий, но не очень хорошо для тысяч других языков, используемых по всему миру. К счастью, существует класс многоязыковых моделей Transformer, таких как mT5 и mBART, которые приходят на помощь. Эти модели предварительно обучаются с помощью языкового моделирования, но с изюминкой: вместо обучения на корпусе одного языка они обучаются совместно на текстах более чем на 50 языках одновременно!

Мы сосредоточимся на mT5, интересной архитектуре, основанной на T5, которая была предварительно обучена на фреймворке "текс-в-текст" (text-to-text). В T5 каждая задача NLP формулируется в терминах префикса подсказки, например `summarize:`, который определяет, что модель должна адаптировать сгенерированный текст к подсказке. Как показано на рисунке ниже, это делает T5 чрезвычайно универсальным, поскольку вы можете решать множество задач с помощью одной модели!

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/t5.svg" alt="Different tasks performed by the T5 architecture."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/t5-dark.svg" alt="Different tasks performed by the T5 architecture."/>
</div>

В mT5 не используются префиксы, но она обладает многими универсальными возможностями T5 и имеет многоязыковое преимущество. Теперь, когда мы выбрали модель, давайте посмотрим, как подготовить данные для обучения.


<Tip>

✏️ **Попробуйте!** После того как вы проработаете этот раздел, посмотрите, насколько хорошо mT5 сравнится с mBART, дообучив его тем же методам. Чтобы получить бонусные очки, вы также можете попробовать дообучить T5 только на английских рецензиях. Поскольку в T5 есть специальный префикс запроса, вам нужно будет добавить `summarize:` к входным примерам на следующих шагах предварительной обработки.

</Tip>

## Предварительная обработка данных[[preprocessing-the-data]]

<Youtube id="1m7BerpSq8A"/>

Наша следующая задача - токенизация и кодирование отзывов и их заголовков. Как обычно, мы начинаем с загрузки токенизатора, связанного с контрольной точкой предварительно обученной модели. В качестве контрольной точки мы будем использовать `mt5-small`, чтобы можно было дообучить модель за разумное время:

```python
from transformers import AutoTokenizer

model_checkpoint = "google/mt5-small"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

<Tip>

💡 На ранних стадиях ваших NLP-проектов хорошей практикой является обучение класса "маленьких" моделей на небольшой выборке данных. Это позволит вам быстрее отлаживать и итерировать модели, чтобы создать сквозной рабочий процесс. Когда вы будете уверены в результатах, вы всегда сможете увеличить масштаб модели, просто изменив контрольную точку модели!

</Tip>

Давайте протестируем токенизатор mT5 на небольшом примере:

```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]}
```

Здесь мы видим знакомые нам `input_ids` и `attention_mask`, с которыми мы столкнулись в наших первых экспериментах по дообучению еще в [Главе 3](../chapter3/1). Давайте декодируем эти входные идентификаторы с помощью функции токенизатора `convert_ids_to_tokens()`, чтобы понять, с каким токенизатором мы имеем дело:

```python
tokenizer.convert_ids_to_tokens(inputs.input_ids)
```

```python out
['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', '</s>']
```

Специальный символ Юникода `▁` и токен конца последовательности `</s>` указывают на то, что мы имеем дело с токенизатором SentencePiece, который основан на алгоритме сегментации Unigram, рассмотренном в [Главе 6](../chapter6/1). Unigram особенно полезен для многоязычных корпусов, поскольку он позволяет SentencePiece не зависеть от ударений, пунктуации и того факта, что во многих языках, например в японском, нет пробельных символов.

Для токенизации нашего корпуса нам придется столкнуться с одной тонкостью, связанной с сумризацией: поскольку наши метки также являются текстом, возможно, что они превышают максимальный размер контекста модели. Это означает, что нам нужно применять усечение как к обзорам, так и к их заголовкам, чтобы не передавать в модель слишком длинные данные. Токенизаторы в 🤗 Transformers предоставляют интересный аргумент `text_target`, который позволяет вам токенизировать метки параллельно с входными данными. Вот пример того, как обрабатываются входные и целевые данные для 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,
    )
    labels = tokenizer(
        examples["review_title"], max_length=max_target_length, truncation=True
    )
    model_inputs["labels"] = labels["input_ids"]
    return model_inputs
```

Давайте пройдемся по этому коду, чтобы понять, что происходит. Первое, что мы сделали, это определили значения `max_input_length` и `max_target_length`, которые устанавливают верхние границы длины наших обзоров и заголовков. Поскольку тело обзора обычно намного больше заголовка, мы соответственно изменили эти значения.

С помощью `preprocess_function()` можно провести токенизацию всего корпуса с помощью удобной функции `Dataset.map()`, которую мы часто использовали на протяжении всего курса:

```python
tokenized_datasets = books_dataset.map(preprocess_function, batched=True)
```

Теперь, когда корпус был предварительно обработан, давайте посмотрим на некоторые метрики, которые обычно используются для суммаризации. Как мы увидим, не существует серебряной пули, когда дело доходит до измерения качества сгенерированного машиной текста.

<Tip>

💡 Возможно, вы заметили, что выше в функции `Dataset.map()` мы использовали `batched=True`. Это кодирует примеры в батчах по 1 000 (по умолчанию) и позволяет использовать возможности многопоточности быстрых токенизаторов в 🤗 Transformers. По возможности, попробуйте использовать `batched=True`, чтобы получить максимальную отдачу от препроцессинга!

</Tip>


## Метрики для суммаризации текста[[metrics-for-text-summarization]]

<Youtube id="TMshhnrEXlg"/>

По сравнению с большинством других задач, которые мы рассматривали в этом курсе, измерение качества работы задач по созданию текста, таких как суммаризация или перевод, не так просто. Например, для рецензии типа "I loved reading the Hunger Games" существует множество правильных резюме, таких как "I loved the Hunger Games" или "Hunger Games is a great read". Очевидно, что применение какого-то точного соответствия между сгенерированным резюме и меткой не является хорошим решением - даже люди не справятся с такой метрикой, потому что у каждого из нас свой стиль написания.

Для суммаризации одной из наиболее часто используемых метрик является [оценка ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (сокращение от Recall-Oriented Understudy for Gisting Evaluation). Основная идея этой метрики заключается в сравнении сгенерированного резюме с набором эталонных резюме, которые обычно создаются людьми. Чтобы сделать ее более точной, предположим, что мы хотим сравнить следующие два резюме:

```python
generated_summary = "I absolutely loved reading the Hunger Games"
reference_summary = "I loved reading the Hunger Games"
```

Одним из способов их сравнения может быть подсчет количества перекрывающихся слов, которых в данном случае будет 6. Однако это несколько грубовато, поэтому вместо этого ROUGE основывается на вычислении оценок _precision_ и _recall_ для перекрытия.

<Tip>

🙋 Не волнуйтесь, если вы впервые слышите о precision и recall - мы вместе разберем несколько наглядных примеров, чтобы все стало понятно. Эти метрики обычно встречаются в задачах классификации, поэтому, если вы хотите понять, как определяются precision и recall в этом контексте, мы рекомендуем ознакомиться с `scikit-learn` [руководством](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html).

</Tip>

Для ROUGE recall измеряет, насколько эталонное резюме соответствует сгенерированному. Если мы просто сравниваем слова, recall можно рассчитать по следующей формуле:

$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$

Для нашего простого примера выше эта формула дает идеальный recall 6/6 = 1; то есть все слова в эталонном резюме были получены моделью. Это может показаться замечательным, но представьте, если бы сгенерированное нами резюме было "I really really loved reading the Hunger Games all night". Это тоже дало бы идеальный recall, но, возможно, было бы хуже, поскольку было бы многословным. Чтобы справиться с этими сценариями, мы также вычисляем precision, которая в контексте ROUGE измеряет, насколько сгенерированное резюме было релевантным:

$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$

Если применить это к нашему подробному резюме, то precision составит 6/10 = 0,6, что значительно хуже, чем precision 6/7 = 0,86, полученная при использовании более короткого резюме. На практике обычно вычисляют и precision, и recall, а затем F1-score (среднее гармоническое из precision и recall). Мы можем легко это сделать с помощью 🤗 Datasets, предварительно установив пакет `rouge_score`:

```py
!pip install rouge_score
```

а затем загрузить метрику ROUGE следующим образом:

```python
import evaluate

rouge_score = evaluate.load("rouge")
```

Затем мы можем использовать функцию `rouge_score.compute()`, чтобы рассчитать все метрики сразу:

```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))}
```

Ого, в этом выводе много информации - что же она означает? Во-первых, 🤗 Datasets действительно вычисляет доверительные интервалы для precision, recall и F1-score; это `low`, `mid`, и `high` атрибуты, которые вы можете здесь увидеть. Кроме того, 🤗 Dataset вычисляет различные оценки ROUGE, которые основаны на различных типах детализации текста при сравнении сгенерированных и эталонных резюме. Вариант `rouge1` представляет собой перекрытие униграмм - это просто модный способ сказать о перекрытии слов, и это именно та метрика, которую мы обсуждали выше. Чтобы убедиться в этом, давайте извлечем `среднее` значение наших оценок:

```python
scores["rouge1"].mid
```

```python out
Score(precision=0.86, recall=1.0, fmeasure=0.92)
```

Отлично, показатели precision и recall совпадают! А как насчет других показателей ROUGE? `rouge2` измеряет перекрытие биграмм (считайте, что это перекрытие пар слов), а `rougeL` и `rougeLsum` измеряют самые длинные совпадающие последовательности слов, ища самые длинные общие подстроки в сгенерированных и эталонных резюме. Слово "sum" в `rougeLsum` означает, что эта метрика вычисляется для всего резюме, в то время как `rougeL` вычисляется как среднее по отдельным предложениям.

<Tip>

✏️ **Попробуйте!** Создайте свой собственный пример сгенерированного и эталонного резюме и посмотрите, согласуются ли полученные оценки ROUGE с ручным расчетом по формулам precision и recall. Для получения бонусных очков разбейте текст на биграммы и сравните precision и recall для метрики `rouge2`.

</Tip>

Мы будем использовать эту оценку ROUGE для отслеживания эффективности нашей модели, но перед этим давайте сделаем то, что должен сделать каждый хороший NLP-практик: создадим сильную, но простую базовую модель!

### Создание сильного базового уровня[[creating-a-strong-baseline]]

Для суммаризации текста обычно берут первые три предложения статьи, что часто называют базвым уровнем _lead-3_. Мы могли бы использовать символы полной остановки для отслеживания границ предложений, но это не поможет при использовании таких аббревиатур, как " U.S." или "U.N.". -- поэтому вместо этого мы воспользуемся библиотекой `nltk`, которая включает в себя лучший алгоритм для таких случаев. Вы можете установить пакет с помощью `pip` следующим образом:

```python
!pip install nltk
```

а затем скачайте правила пунктуации:

```python
import nltk

nltk.download("punkt")
```

Далее мы импортируем токенизатор предложений из `nltk` и создадим простую функцию для извлечения первых трех предложений в обзоре. При суммаризации текста принято отделять каждое предложение новой строкой, поэтому давайте включим эту функцию и протестируем ее на обучающем примере:

```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.'
```

Похоже, что это работает, так что теперь давайте реализуем функцию, которая извлекает эти "резюме" из датасета и вычисляет оценку ROUGE для базового уровня:

```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"])
```

Затем мы можем использовать эту функцию для вычисления оценок ROUGE на валидационном множестве и немного приукрасить их с помощью 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}
```

Мы видим, что оценка `rouge2` значительно ниже, чем у остальных; скорее всего, это отражает тот факт, что заголовки рецензий обычно лаконичны, и поэтому базовый уровень lead-3 слишком многословен. Теперь, когда у нас есть хороший базовый уровень для работы, давайте дообучим mT5!

{#if fw === 'pt'}

## Дообучение mT5 с API `Trainer`[[fine-tuning-mt5-with-the-trainer-api]]

Дообучение модели суммаризации очень похоже на другие задачи, которые мы рассмотрели в этой главе. Первое, что нам нужно сделать, это загрузить предварительно обученную модель из контрольной точки `mt5-small`. Поскольку суммаризация - это задача преобразования последовательности в последовательность, мы можем загрузить модель с помощью класса `AutoModelForSeq2SeqLM`, который автоматически загрузит и кэширует веса:

```python
from transformers import AutoModelForSeq2SeqLM

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{:else}

## Дообучение mT5 с Keras[[fine-tuning-mt5-with-keras]]

Дообучение модели суммаризации очень похоже на другие задачи, которые мы рассмотрели в этой главе. Первое, что нам нужно сделать, это загрузить предварительно обученную модель из контрольной точки `mt5-small`. Поскольку суммаризация - это задача преобразования последовательности в последовательность, мы можем загрузить модель с помощью класса `TFAutoModelForSeq2SeqLM`, который автоматически загрузит и кэширует веса:

```python
from transformers import TFAutoModelForSeq2SeqLM

model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{/if}

<Tip>

💡 Если вы задаетесь вопросом, почему вы не видите предупреждений о необходимости дообучить модель для последующей задачи, то это потому, что для задач "последовательность-в-последовательность" мы сохраняем все веса сети. Сравните это с нашей моделью классификации текста из [Главы 3](../chapter3/1), где голова предварительно обученной модели была заменена на случайно инициализированную сеть.

</Tip>

Следующее, что нам нужно сделать, это войти в Hugging Face Hub. Если вы выполняете этот код в ноутбуке, вы можете сделать это с помощью следующей полезной функции:

```python
from huggingface_hub import notebook_login

notebook_login()
```

которая отобразит виджет, где вы можете ввести свои учетные данные. Также вы можете запустить эту команду в терминале и войти в систему там:

```
huggingface-cli login
```

{#if fw === 'pt'}

Для вычисления оценки ROUGE в процессе обучения нам понадобится генерировать резюме. К счастью, 🤗 Transformers предоставляет специальные классы `Seq2SeqTrainingArguments` и `Seq2SeqTrainer`, которые могут сделать это за нас автоматически! Чтобы увидеть, как это работает, давайте сначала определим гиперпараметры и другие аргументы для наших экспериментов:

```python
from transformers import Seq2SeqTrainingArguments

batch_size = 8
num_train_epochs = 8
# Выводим потери при обучении по каждой эпохе
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,
)
```

Здесь аргумент `predict_with_generate` был задан, чтобы указать, что мы должны генерировать резюме во время оценки, чтобы мы могли вычислить баллы ROUGE для каждой эпохи. Как обсуждалось в [Главе 1](../chapter1/1), декодер выполняет инференс, предсказывая токены по одному, и это реализуется методом модели `generate()`. Задание `predict_with_generate=True` указывает `Seq2SeqTrainer` на использование этого метода для оценки. Мы также скорректировали некоторые гиперпараметры по умолчанию, такие как скорость обучения, количество эпох и затухание весов, и задали параметр `save_total_limit`, чтобы сохранять только 3 контрольные точки во время обучения - это сделано потому, что даже "маленькая" версия mT5 использует около Гигабайта места на жестком диске, и мы можем сэкономить немного места, ограничив количество копий, которые мы сохраняем.

Аргумент `push_to_hub=True` позволит нам отправить модель в Hub после обучения; вы найдете розиторий под своим профилем пользователя в месте, определенном `output_dir`. Обратите внимание, что вы можете указать имя розитория, в который хотите отправить модель, с помощью аргумента `hub_model_id` (в частности, вам нужно использовать этот аргумент, чтобы отправить модель в организацию). Например, когда мы отправили модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` в `Seq2SeqTrainingArguments`.

Следующее, что нам нужно сделать, это предоставить тренеру функцию `compute_metrics()`, чтобы мы могли оценить нашу модель во время обучения. Для суммаризации это немного сложнее, чем просто вызвать `rouge_score.compute()` для прогнозов модели, поскольку нам нужно _декодировать_ выводы и метки в текст, прежде чем мы сможем вычислить оценку ROUGE. Следующая функция делает именно это, а также использует функцию `sent_tokenize()` из `nltk` для разделения предложений резюме символом новой строки:

```python
import numpy as np


def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    # Декодируем сгенерированные резюме в текст
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    # Заменяем -100 в метках, поскольку мы не можем их декодировать
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
    # Декодируем эталонные резюме в текст
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
    # ROUGE ожидает символ новой строки после каждого предложения
    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]
    # Вычисляем оценки ROUGE
    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()}
    return {k: round(v, 4) for k, v in result.items()}
```

{/if}

Далее нам нужно определить коллатор данных для нашей задачи преобразования последовательности в последовательность. Поскольку mT5 является моделью трансформера кодер-декодер, одна из тонкостей подготовки наших батчей заключается в том, что во время декодирования нам нужно сдвинуть метки вправо на единицу. Это необходимо для того, чтобы декодер видел только предыдущие метки, а не текущие или будущие, которые модели было бы легко запомнить. Это похоже на то, как маскированное самовнимание применяется к входным данным в задаче типа [каузального языкового моделирования](../chapter7/6).

К счастью, 🤗 Transformers предоставляет коллатор `DataCollatorForSeq2Seq`, который будет динамически дополнять входные данные и метки за нас. Чтобы инстанцировать этот коллатор, нам просто нужно предоставить `tokenizer` и `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}

Давайте посмотрим, что выдает этот коллатор, когда ему передается небольшой батч примеров. Во-первых, нам нужно удалить столбцы со строками, потому что коллатор не будет знать, как вставлять эти элементы:

```python
tokenized_datasets = tokenized_datasets.remove_columns(
    books_dataset["train"].column_names
)
```

Поскольку коллатор ожидает список словарей `dict`, где каждый словарь `dict` представляет один пример в датасете, нам также необходимо привести данные к ожидаемому формату, прежде чем передавать их коллатору:

```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]])}
```

Главное, что здесь нужно заметить, - это то, что первый пример длиннее второго, поэтому `input_ids` и `attention_mask` второго примера были дополнены справа токеном `[PAD]` (чей идентификатор равен `0`). Аналогично, мы видим, что `labels` были дополнены значением `-100`, чтобы функция потерь игнорировала токены дополнения. И наконец, мы видим новый `decoder_input_ids`, в котором метки сдвинуты вправо за счет вставки токена `[PAD]` в первую запись.

{#if fw === 'pt'}

Наконец-то у нас есть все необходимые ингредиенты для обучения! Теперь нам нужно просто создать тренер со стандартными аргументами:

```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,
)
```

и запустить наш цикл обучения:

```python
trainer.train()
```

Во время обучения вы должны видеть, как потери при обучении уменьшаются, а оценка ROUGE увеличивается с каждой эпохой. После завершения обучения вы можете увидеть итоговую оценку ROUGE, выполнив команду `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}
```

Из оценок видно, что наша модель значительно превзошла базовый уровень lead-3 - отлично! Осталось отправить веса модели в Hub, как показано ниже:

```
trainer.push_to_hub(commit_message="Training complete", tags="summarization")
```

```python out
'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0'
```

Это позволит сохранить контрольную точку и файлы конфигурации в `output_dir`, а затем загрузить все файлы на Хаб. Указав аргумент `tags`, мы также гарантируем, что виджет на хабе будет предназначен для конвейера суммаризации, а не для конвейера генерации текста по умолчанию, связанного с архитектурой mT5 (более подробную информацию о тегах моделей можно найти в [🤗 документации по Hub](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). Вывод `trainer.push_to_hub()` - это URL на хэш Git-коммита, так что вы можете легко увидеть изменения, которые были сделаны в розитории модели!

В завершение этого раздела рассмотрим, как можно дообучить mT5 с помощью низкоуровневых функций, предоставляемых 🤗 Accelerate.

{:else}

Мы почти готовы к обучению! Нам нужно только преобразовать наши датасеты в `tf.data.Dataset` с помощью коллатора данных, который мы определили выше, а затем выолнить `compile()` и `fit()` модели. Сначала датасеты:

```python
tf_train_dataset = model.prepare_tf_dataset(
    tokenized_datasets["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=8,
)
tf_eval_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=8,
)
```

Теперь определяем гиперпараметры обучения и компилируем:

```python
from transformers import create_optimizer
import tensorflow as tf

# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное
# на общее количество эпох. Обратите внимание, что tf_train_dataset здесь - это батч tf.data.Dataset,
# а не оригинальный датасет Hugging Face, поэтому его len() уже равен 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)

# Обучение со смешанной точностью float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

И наконец, мы подгоняем модель. Мы используем `PushToHubCallback` для сохранения модели на Hub после каждой эпохи, что позволит нам использовать ее позже для инференса:

```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
)
```

Мы получили некоторые значения потерь во время обучения, но на самом деле нам хотелось бы увидеть метрику ROUGE, которую мы вычисляли ранее. Чтобы получить эти метрики, нам нужно сгенерировать выходные данные модели и преобразовать их в строки. Давайте создадим несколько списков меток и прогнозов для сравнения с метрикой ROUGE (обратите внимание, что если вы получаете ошибки импорта в этом разделе, вам может понадобиться команда `!pip install tqdm`). Мы также используем трюк, который значительно повышает производительность, - компиляцию генерируемого кода с помощью [XLA](https://www.tensorflow.org/xla), ускоренного компилятора линейной алгебры TensorFlow. XLA применяет различные оптимизации к графу вычислений модели, что приводит к значительному увеличению скорости и использования памяти. Как описано в Hugging Face [блоге](https://huggingface.co/blog/tf-xla-generate), XLA работает лучше всего, когда формы наших входных данных не слишком сильно различаются. Чтобы справиться с этим, мы дополним наши входные данные до кратных 128 и создадим новый датасет с помощью дополняющего коллатора, а затем применим декоратор `@tf.function(jit_compile=True)` к нашей функции генерации, который помечает всю функцию для компиляции с помощью XLA.

```python
from tqdm import tqdm
import numpy as np

generation_data_collator = DataCollatorForSeq2Seq(
    tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=320
)

tf_generate_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=generation_data_collator,
    shuffle=False,
    batch_size=8,
    drop_remainder=True,
)


@tf.function(jit_compile=True)
def generate_with_xla(batch):
    return model.generate(
        input_ids=batch["input_ids"],
        attention_mask=batch["attention_mask"],
        max_new_tokens=32,
    )


all_preds = []
all_labels = []
for batch, labels in tqdm(tf_generate_dataset):
    predictions = generate_with_xla(batch)
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    labels = 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)
```

Когда у нас есть списки строк меток и прогнозов, вычислить оценку ROUGE очень просто:

```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'}

## Дообучение mT5 с 🤗 Accelerate[[fine-tuning-mt5-with-accelerate]]

Дообучение нашей модели с помощью 🤗 Accelerate очень похоже на пример с классификацией текста, который мы рассматривали в [Главе 3](../chapter3/1). Основные отличия заключаются в необходимости явной генерации резюме во время обучения и определении способа вычисления оценок ROUGE (напомним, что `Seq2SeqTrainer` позаботился о генерации за нас). Давайте посмотрим, как мы можем реализовать эти два требования в 🤗 Accelerate!

### Подготовка всего к обучению[[preparing-everything-for-training]]

Первое, что нам нужно сделать, это создать `DataLoader` для каждой из наших частей. Поскольку загрузчики данных PyTorch ожидают батч тензоров, нам нужно задать формат `"torch"` в наших датасетах:

```python
tokenized_datasets.set_format("torch")
```

Теперь, когда у нас есть датасеты, состоящие только из тензоров, следующее, что нужно сделать, - это снова инстанцировать `DataCollatorForSeq2Seq`. Для этого нам нужно предоставить свежую версию модели, поэтому давайте снова загрузим ее из нашего кэша:

```python
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

Затем мы можем инстанцировать коллатор данных и использовать его для определения наших загрузчиков данных:

```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
)
```

Следующее, что нужно сделать, это определить оптимизатор, который мы хотим использовать. Как и в других наших примерах, мы будем использовать `AdamW`, который хорошо работает для большинства задач:

```python
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Наконец, мы передаем нашу модель, оптимизатор и загрузчики данных в метод `accelerator.prepare()`:

```python
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 Если вы обучаете на TPU, вам нужно будет перенести весь приведенный выше код в специальную функцию обучения. Подробнее смотрите в [Главе 3](../chapter3/1).

</Tip>

Теперь, когда мы подготовили наши объекты, осталось сделать три вещи:

* Определить график скорости обучения.
* Реализовать функцию для постобработки резюме для оценки.
* Создать розиторий на Hub, в который мы можем отправить нашу модель.

В качестве графика скорости обучения мы будем использовать стандартный линейный график из предыдущих разделов:

```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,
)
```

Для постобработки нам нужна функция, которая разбивает сгенерированные резюме на предложения, разделенные символами новой строки. Именно такой формат ожидает метрика ROUGE, и мы можем достичь этого с помощью следующего фрагмента кода:

```python
def postprocess_text(preds, labels):
    preds = [pred.strip() for pred in preds]
    labels = [label.strip() for label in labels]

    # ROUGE ожидает символ новой строки после каждого предложения
    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
```

Это должно показаться вам знакомым, если вы помните, как мы определяли функцию `compute_metrics()` для `Seq2SeqTrainer`.

Наконец, нам нужно создать розиторий модели на Hugging Face Hub. Для этого мы можем использовать библиотеку 🤗Hub с соответствующим заголовком. Нам нужно только задать имя нашего розитория, а в библиотеке есть служебная функция для объединения идентификатора розитория с профилем пользователя:

```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'
```

Теперь мы можем использовать имя этого розитория для клонирования локальной версии в каталог результатов, в котором будут храниться результаты обучения:

```python
from huggingface_hub import Repository

output_dir = "results-mt5-finetuned-squad-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Это позволит нам отправить результаты в Hub, вызвав метод `repo.push_to_hub()` во время обучения! Теперь давайте завершим наш анализ, написав цикл обучения.

### Цикл обучения[[training-loop]]

Цикл обучения суммаризации очень похож на другие примеры 🤗 Accelerate, с которыми мы сталкивались, и состоит из четырех основных этапов:

1. Обучение модели путем итерации по всем примерам в `train_dataloader` на каждой эпохе.
2. Генерация резюме моделью в конце каждой эпохи, сначала генерируются токены, а затем они (и эталонные резюме) декодируются в текст.
3. Вычисление оценок ROUGE с помощью тех же приемов, которые мы рассмотрели ранее.
4. Сохранение контрольных точек и отправка всего в Hub. Здесь мы полагаемся на полезный аргумент `blocking=False` объекта `Repository`, чтобы мы могли отправить контрольные точки на каждой эпохе _асинхронно_. Это позволяет нам продолжать обучение, не дожидаясь медленной загрузки, связанной с моделью размером в гигабайт!

Эти шаги можно увидеть в следующем блоке кода:

```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):
    # Обучение
    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)

    # Оценка
    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"]

            # Если мы не дополнили до максимальной длины, нам нужно дополнить и метки
            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()

            # Заменяем -100 в метках, поскольку мы не можем их декодировать
            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)

    # Вычисляем метрики
    result = rouge_score.compute()
    # Извлекаем медианные оценки ROUGE
    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)

    # Сохранение и загрузка
    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}
```

Вот и все! После запуска у вас будет модель и результаты, очень похожие на те, что мы получили с помощью `Trainer`.

{/if}

## Использование дообученной вами модели[[using-your-fine-tuned-model]]

После того как вы отправили модель в Hub, вы можете работать с ней либо с помощью виджета инференса, либо с помощью объекта `pipeline`, как показано ниже:

```python
from transformers import pipeline

hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es"
summarizer = pipeline("summarization", model=hub_model_id)
```

Мы можем передать в наш конвейер несколько примеров из тестового набора (которые модель не видела), чтобы получить представление о качестве резюме. Для начала давайте реализуем простую функцию, которая будет показывать обзор, заголовок и сгенерированное резюме вместе:

```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}'")
```

Давайте посмотрим на один из английских примеров, которые мы получаем:

```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'
```

Это не так уж плохо! Мы видим, что наша модель действительно способна выполнять _абстрактную_ суммуризацию, дополняя части обзора новыми словами. И, пожалуй, самый интересный аспект нашей модели - это то, что она билингвистическая, так что мы можем генерировать резюме и для испанских рецензий:

```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'
```

Резюме переводится как "Very easy to read" на английском языке, что, как мы видим, в данном случае было непосредственно взято из обзора. Тем не менее, это демонстрирует универсальность модели mT5 и дает вам представление о том, каково это - работать с многоязычным корпусом!

Далее мы обратимся к несколько более сложной задаче: обучению языковой модели с нуля.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/5.mdx" />

### Введение[[introduction]]
https://huggingface.co/learn/course/ru/chapter7/1.md

# Введение[[introduction]]

<CourseFloatingBanner
    chapter={7}
    classNames="absolute z-10 right-0 top-0"
/>

В [Главе 3](../chapter3/1) вы узнали, как дообучить модель для классификации текстов. В этой главе мы рассмотрим следующие общие задачи NLP:

- Классификация токенов (Token classification)
- Маскированное языковое моделирование (Masked language modeling, например, BERT)
- Резюмирование текста (Summarization)
- Перевод (Translation)
- Предварительное обучение каузального языкового моделирования (Causal language modeling, например, GPT-2)
- Ответы на вопросы (Question answering)

{#if fw === 'pt'}

Для этого вам понадобится использовать все, что вы узнали об API `Trainer` и библиотеке 🤗 Accelerate в [Главе 3](../chapter3/1), библиотеке 🤗 Datasets в [Главе 5](../chapter5/1) и библиотеке 🤗 Tokenizers в [Главе 6](../chapter6/1). Мы также загрузим наши результаты в хаб моделей, как мы делали это в [Главе 4](../chapter4/1), так что это действительно глава,в которой все собирается воедино!

Каждый раздел можно читать независимо друг от друга, и в нем вы узнаете, как обучить модель с помощью API `Trainer` или с помощью собственного цикла обучения, используя 🤗 Accelerate. Вы можете пропустить любую часть и сосредоточиться на той, которая вас больше всего интересует: API `Trainer` отлично подходит для того, чтобы дообучить или обучить вашу модель, не беспокоясь о том, что происходит за кулисами, а цикл обучения с `Accelerate` позволит вам легче настроить любую часть, которую вы хотите.

{:else}

Для этого вам понадобится использовать все, что вы узнали об обучении моделей с помощью Keras API в [Главе 3](../chapter3/1), библиотеке 🤗 Datasets в [Главе 5](../chapter5/1) и библиотеке 🤗 Tokenizers в [Главе 6](../chapter6/1). Мы также загрузим наши результаты в хаб моделей, как мы делали это в [Главе 4](../chapter4/1), так что это действительно глава, в которой все собирается воедино!

Каждый раздел можно читать самостоятельно.

{/if}


<Tip>

Если вы будете читать разделы по порядку, то заметите, что в них довольно много общего в коде и тексте. Повторение сделано намеренно, чтобы вы могли погрузиться (или вернуться позже) в любую интересующую вас задачу и найти полный рабочий пример.

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/1.mdx" />

### Дообучение модели маскированного языкового моделирования[[fine-tuning-a-masked-language-model]]
https://huggingface.co/learn/course/ru/chapter7/3.md

# Дообучение модели маскированного языкового моделирования[[fine-tuning-a-masked-language-model]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section3_tf.ipynb"},
]} />

{/if}

Для многих приложений NLP, в которых используются модели-трансформеры, можно просто взять предварительно обученную модель из Hugging Face Hub и дообучить ее непосредственно на ваших данных для решения поставленной задачи. При условии, что корпус, использованный для предварительного обучения, не слишком отличается от корпуса, используемого для дообучения, трансферное обучение обычно дает хорошие результаты.

Однако есть несколько случаев, когда перед обучением специфичной для конкретной задачи головы необходимо дообучить языковые модели на ваших данных. Например, если ваш датасет содержит юридические контракты или научные статьи, ванильная модель трансформер, такая как BERT, обычно рассматривает специфические для области слова в вашем корпусе как редкие токены, и результирующее качество может быть менее чем удовлетворительным. Дообучив языковую модель на данных из домена, вы сможете повысить качество работы во многих последующих задачах, а это значит, что обычно этот шаг нужно выполнить только один раз!

Этот процесс дообучить предварительно обученную языковую модель на данных из домена обычно называют _доменной адаптацией (domain adaptation)_. Он был популяризирован в 2018 году благодаря [ULMFiT](https://arxiv.org/abs/1801.06146), которая стала одной из первых нейронных архитектур (основанных на LSTM), заставивших трансферное обучение действительно работать в NLP. Пример доменной адаптации с помощью ULMFiT показан на изображении ниже; в этом разделе мы сделаем нечто подобное, но с трансформером вместо LSTM!

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/ulmfit.svg" alt="ULMFiT."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/ulmfit-dark.svg" alt="ULMFiT."/>
</div>

К концу этого раздела у вас будет [модель маскированного языкового моделирования](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) на Hub, которая позволяет автоматически дописывать предложения, как показано ниже:

<iframe src="https://course-demos-distilbert-base-uncased-finetuned-imdb.hf.space" frameBorder="0" height="300" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Давайте погрузимся в работу!

<Youtube id="mqElG5QJWUg"/>

<Tip>

🙋 Если термины "маскированное моделирование языка (masked language modeling)" и "предварительно обученная модель (pretrained model)" кажутся вам незнакомыми, загляните в [Главу 1](../chapter1/1), где мы объясняем все эти основные понятия, сопровождая их видеороликами!

</Tip>

## Выбор предварительно обученной модели для маскированного моделирования языка[[picking-a-pretrained-model-for-masked-language-modeling]]

Для начала давайте выберем подходящую предварительно обученную модель для маскированного языкового моделирования. Как показано на следующем скриншоте, вы можете найти список кандидатов, применив фильтр "Fill-Mask" на [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads):

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/mlm-models.png" alt="Hub models." width="80%"/>
</div>

Хотя модели семейства BERT и RoBERTa являются наиболее загружаемыми, мы будем использовать модель под названием [DistilBERT](https://huggingface.co/distilbert-base-uncased) 
которая может быть обучена гораздо быстрее и практически без потерь в качестве. Эта модель была обучена с помощью специальной техники, называемой [_дистилляцией знаний (knowledge distillation)_](https://en.wikipedia.org/wiki/Knowledge_distillation), когда большая "модель-учитель", такая как BERT, используется для обучения "модели-ученика", имеющей гораздо меньше параметров. Объяснение деталей дистилляции знаний завело бы нас слишком далеко в этом разделе, но если вам интересно, вы можете прочитать об этом в [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (в просторечии известном как учебник по Трансформерам).

{#if fw === 'pt'}

Давайте перейдем к загрузке DistilBERT с помощью класса `AutoModelForMaskedLM`:

```python
from transformers import AutoModelForMaskedLM

model_checkpoint = "distilbert-base-uncased"
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

Мы можем узнать, сколько параметров имеет эта модель, вызвав метод `num_parameters()`:

```python
distilbert_num_parameters = model.num_parameters() / 1_000_000
print(f"'>>> DistilBERT number of parameters: {round(distilbert_num_parameters)}M'")
print(f"'>>> BERT number of parameters: 110M'")
```

```python out
'>>> DistilBERT number of parameters: 67M'
'>>> BERT number of parameters: 110M'
```

{:else}

Давайте загрузим DistilBERT, используя класс `AutoModelForMaskedLM`:

```python
from transformers import TFAutoModelForMaskedLM

model_checkpoint = "distilbert-base-uncased"
model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

Мы можем узнать, сколько параметров имеет эта модель, вызвав метод `summary()`:

```python
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}

Имея около 67 миллионов параметров, DistilBERT примерно в два раза меньше, чем базовая модель BERT, что примерно соответствует двукратному ускорению обучения - неплохо! Теперь посмотрим, какие виды токенов эта модель предсказывает как наиболее вероятные завершения небольшого образца текста:

```python
text = "This is a great [MASK]."
```

Как люди, мы можем представить множество вариантов использования токена `[MASK]`, например "day (день)", "ride (поездка)" или "painting (картина)". Для предварительно обученных моделей прогнозы зависят от корпуса, на котором обучалась модель, поскольку она учится улавливать статистические закономерности, присутствующие в данных. Как и BERT, DistilBERT был предварительно обучен на датасетах [English Wikipedia](https://huggingface.co/datasets/wikipedia) и [BookCorpus](https://huggingface.co/datasets/bookcorpus), поэтому мы ожидаем, что прогнозы для `[MASK]` будут отражать эти домены. Для прогнозирования маски нам нужен токенизатор DistilBERT для создания входных данных для модели, поэтому давайте загрузим и его с хаба:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Теперь, имея токенизатор и модель, мы можем передать наш текстовый пример в модель, извлечь логиты и вывести 5 лучших кандидатов:

{#if fw === 'pt'}

```python
import torch

inputs = tokenizer(text, return_tensors="pt")
token_logits = model(**inputs).logits
# Определеним местоположения [MASK] и извлечем его логиты
mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
mask_token_logits = token_logits[0, mask_token_index, :]
# Выберем кандидатов [MASK] с наибольшими логитами
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
# Определеним местоположения [MASK] и извлечем его логиты
mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1]
mask_token_logits = token_logits[0, mask_token_index, :]
# Выберем кандидатов [MASK] с наибольшими логитами
# Мы выполняем отрицание массива перед argsort, чтобы получить самые большие, а не самые маленькие логиты
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.'
'>>> This is a great success.'
'>>> This is a great adventure.'
'>>> This is a great idea.'
'>>> This is a great feat.'
```

Из результатов видно, что предсказания модели относятся к повседневным терминам, что, пожалуй, неудивительно, учитывая основу английской Википедии. Давайте посмотрим, как мы можем изменить эту область на что-то более нишевое - сильно поляризованные обзоры фильмов!


## Датасет[[the-dataset]]

Для демонстрации адаптации к домену мы используем знаменитый [Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (или сокращенно IMDb) - корпус кинорецензий, который часто используется для тестирования моделей анализа настроения. Дообучив DistilBERT на этом корпусе, мы ожидаем, что языковая модель адаптирует свой словарный запас от фактических данных Википедии, на которых она была предварительно обучена, к более субъективным элементам кинорецензий. Мы можем получить данные из Hugging Face Hub с помощью функции `load_dataset()` из 🤗 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
    })
})
```

Мы видим, что части `train` и `test` состоят из 25 000 отзывов каждая, а часть без меток, называемая `unsupervised`, содержит 50 000 отзывов. Давайте посмотрим на несколько примеров, чтобы получить представление о том, с каким текстом мы имеем дело. Как мы уже делали в предыдущих главах курса, мы используем функции `Dataset.shuffle()` и `Dataset.select()` для создания случайной выборки:

```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.<br /><br />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.<br /><br />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. <br /><br />My 4 and 6 year old children love this movie and have been asking again and again to watch it. <br /><br />I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.<br /><br />I do not have older children so I do not know what they would think of it. <br /><br />The songs are very cute. My daughter keeps singing them over and over.<br /><br />Hope this helps.'
'>>> Label: 1'
```

Да, это точно рецензии на фильмы, и если вы родились до 1990х, вам будет лучше понятен комментарий в последней рецензии о VHS-версии. 😜! Хотя нам не понадобятся эти метки для языкового моделирования, мы уже видим, что `0` обозначает отрицательный отзыв, а `1` - положительный.

<Tip>

✏️ **Попробуйте!** Создайте случайную выборку из части `unsupervised` и проверьте, что метки не являются ни `0`, ни `1`. В процессе работы вы также можете проверить, что метки в частях `train` и `test` действительно равны `0` или `1` - это полезная проверка здравомыслия, которую каждый практикующий NLP должен выполнять в начале нового проекта!

</Tip>

Теперь, когда мы вкратце ознакомились с данными, давайте перейдем к их подготовке к моделированию языка по маске. Как мы увидим, есть несколько дополнительных шагов, которые необходимо сделать по сравнению с задачами классификации последовательностей, которые мы рассматривали в [Главе 3](../chapter3/1). Поехали!

## Предварительная обработка данных[[preprocessing-the-data]]

<Youtube id="8PmhEIXhBvI"/>

Как для авторегрессивного, так и для масочного моделирования языка общим шагом предварительной обработки является объединение всех примеров, а затем разбиение всего корпуса на части одинакового размера. Это сильно отличается от нашего обычного подхода, когда мы просто проводим токенизацию отдельных примеров. Зачем конкатенируем все вместе? Причина в том, что отдельные примеры могут быть обрезаны, если они слишком длинные, и это приведет к потере информации, которая может быть полезна для задачи языкового моделирования!

Итак, для начала мы проведем обычную токенизацию нашего корпуса, но _без_ задания параметра `truncation=True` в нашем токенизаторе. Мы также возьмем идентификаторы слов, если они доступны (а они будут доступны, если мы используем быстрый токенизатор, как описано в [Главе 6](../chapter6/3)), поскольку они понадобятся нам позже для маскирования целых слов. Мы обернем это в простую функцию, а пока удалим столбцы `text` и `label`, поскольку они нам больше не нужны:

```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


# Используйте batched=True для активации быстрой многопоточности!
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
    })
})
```

Поскольку DistilBERT - это BERT-подобная модель, мы видим, что закодированные тексты состоят из `input_ids` и `attention_mask`, которые мы уже видели в других главах, а также из `word_ids`, которые мы добавили.

Теперь, когда мы провели токенизацию рецензий на фильмы, следующий шаг - сгруппировать их вместе и разбить результат на части. Но какого размера должны быть эти части? В конечном итоге это будет зависеть от объема доступной памяти GPU, но хорошей отправной точкой будет узнать, каков максимальный размер контекста модели. Это можно сделать путем проверки атрибута `model_max_length` токенизатора:

```python
tokenizer.model_max_length
```

```python out
512
```

Это значение берется из файла *tokenizer_config.json*, связанного с контрольной точкой; в данном случае мы видим, что размер контекста составляет 512 токенов, как и в случае с BERT.

<Tip>

✏️ **Попробуйте!** Некоторые модели трансформеров, например [BigBird](https://huggingface.co/google/bigbird-roberta-base) и [Longformer](hf.co/allenai/longformer-base-4096), имеют гораздо большую длину контекста, чем BERT и другие ранние модели трансформеров. Инстанцируйте токенизатор для одной из этих контрольных точек и проверьте, что `model_max_length` согласуется с тем, что указано в описании модели.

</Tip>

Поэтому для проведения экспериментов на GPU, подобных тем, что стоят в Google Colab, мы выберем что-нибудь поменьше, что может поместиться в памяти:

```python
chunk_size = 128
```

<Tip warning={true}>

Обратите внимание, что использование небольшого размера фрагмента может быть вредным в реальных сценариях, поэтому следует использовать размер, соответствующий сценарию использования, к которому вы будете применять вашу модель.

</Tip>

Теперь наступает самое интересное. Чтобы показать, как работает конкатенация, давайте возьмем несколько отзывов из нашего обучающего набора с токенизацией и выведем количество токенов в отзыве:

```python
# С помощью среза получаем список списков для каждого признака
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'
```

Затем мы можем объединить все эти примеры с помощью простого dictionary comprehension, как показано ниже:

```python
concatenated_examples = {
    k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys()
}
total_length = len(concatenated_examples["input_ids"])
print(f"'>>> Concatenated reviews length: {total_length}'")
```

```python out
'>>> Concatenated reviews length: 951'
```

Отлично, общая длина подтвердилась - теперь давайте разобьем конкатенированные обзоры на части размером `chunk_size`. Для этого мы перебираем признаки в `concatenated_examples` и используем list comprehension для создания срезов каждого признака. В результате мы получаем словарь фрагментов для каждого признака:

```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'
```

Как видно из этого примера, последний фрагмент, как правило, будет меньше максимального размера фрагмента. Существует две основные стратегии решения этой проблемы:

* Отбросить последний фрагмент, если он меньше `chunk_size`.
* Дополнить последний фрагмент до длины, равной `chunk_size`.

Мы воспользуемся первым подходом, поэтому обернем всю описанную выше логику в одну функцию, которую можно применить к нашим токенизированным датасетам:

```python
def group_texts(examples):
    # Конкатенируем все тексты
    concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
    # Вычисляем длину конкатенированных текстов
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # Отбрасываем последний фрагмент, если он меньше chunk_size
    total_length = (total_length // chunk_size) * chunk_size
    # Разбиваем на фрагменты длиной 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()
    }
    # Создаем новый столбец меток
    result["labels"] = result["input_ids"].copy()
    return result
```

Обратите внимание, что на последнем шаге `group_texts()` мы создаем новый столбец `labels`, который является копией столбца `input_ids`. Как мы вскоре увидим, это связано с тем, что при моделировании языка по маске цель состоит в прогнозировании случайно замаскированных токенов во входном батче, и, создавая столбец `labels`, мы предоставляем базовую истину для нашей языковой модели, на которой она будет учиться.

Теперь давайте применим `group_texts()` к нашим токенизированным датасетам, используя нашу надежную функцию `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
    })
})
```

Вы можете видеть, что группировка и последующее разбиение текстов на фрагменты позволили получить гораздо больше примеров, чем наши первоначальные 25 000 примеров для частей `train` и `test`. Это потому, что теперь у нас есть примеры с _непрерывными токенами (contiguous tokens)_, которые охватывают несколько примеров из исходного корпуса. В этом можно убедиться, поискав специальные токены `[SEP]` и `[CLS]` в одном из фрагментов:

```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"
```

В этом примере вы можете увидеть два перекрывающихся обзора фильмов: один - о старшей школе, другой - о бездомности. Давайте также посмотрим, как выглядят метки при моделировании языка по маске:

```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"
```

Как и ожидалось от нашей функции `group_texts()` выше, это выглядит идентично декодированным `input_ids` - но как тогда наша модель может чему-то научиться? Нам не хватает ключевого шага: вставки токенов `[MASK]` в случайные позиции во входных данных! Давайте посмотрим, как это можно сделать на лету во время дообучения с помощью специального коллатора данных.

## Дообучение DistilBERT с помощью API `Trainer`[[fine-tuning-distilbert-with-the-trainer-api]]

Дообучить модель моделирования языка по маске почти то же самое, что и дообучить модель классификации последовательностей, как мы делали в [Главе 3](../chapter3/1). Единственное отличие заключается в том, что нам нужен специальный коллатор данных, который может случайным образом маскировать некоторые токены в каждом батче текстов. К счастью, 🤗 Transformers поставляется со специальным `DataCollatorForLanguageModeling`, предназначенным именно для этой задачи. Нам нужно только передать ему токенизатор и аргумент `mlm_probability`, который указывает, какую долю токенов нужно маскировать. Мы выберем 15 % - это количество используется для BERT и является распространенным выбором в литературе:

```python
from transformers import DataCollatorForLanguageModeling

data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15)
```

Чтобы увидеть, как работает случайное маскирование, давайте отправим несколько примеров в коллатор данных. Поскольку он ожидает список `dict`, где каждый `dict` представляет собой один фрагмент непрерывного текста, мы сначала выполняем итерацию над датасетом, прежде чем отправить батч коллатору. Мы удаляем ключ `"word_ids"` для этого коллатора данных, поскольку он его не ожидает:

```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'
```

Отлично, сработало! Мы видим, что токен `[MASK]` был случайным образом вставлен в различные места нашего текста. Это будут токены, которые наша модель должна будет предсказать в процессе обучения - и прелесть коллатора данных в том, что он будет случайным образом вставлять `[MASK]` в каждом батче!  

<Tip>

✏️ **Попробуйте!** Запустите приведенный выше фрагмент кода несколько раз, чтобы увидеть, как случайное маскирование происходит на ваших глазах! Также замените метод `tokenizer.decode()` на `tokenizer.convert_ids_to_tokens()`, чтобы увидеть, что иногда маскируется один токен из данного слова, а не все остальные.

</Tip>

{#if fw === 'pt'}

Одним из побочных эффектов случайного маскирования является то, что наши метрики оценки не будут детерминированными при использовании `Trainer`, поскольку мы используем один и тот же коллатор данных для обучающего и тестового наборов. Позже, когда мы рассмотрим дообучение с помощью 🤗 Accelerate, мы увидим, как можно использовать гибкость пользовательского цикла оценки, чтобы заморозить случайность.

{/if}

При обучении моделей для моделирования языка с маской можно использовать один из методов - маскировать целые слова, а не только отдельные токены. Такой подход называется _маскированием целых слов (whole word masking)_. Если мы хотим использовать маскирование целых слов, нам нужно будет самостоятельно создать коллатор данных. Коллатор данных - это просто функция, которая берет список примеров и преобразует их в батч, так что давайте сделаем это прямо сейчас! Мы будем использовать идентификаторы слов, вычисленные ранее, для создания карты между индексами слов и соответствующими токенами, затем случайным образом определим, какие слова нужно маскировать, и применим эту маску к входным данным. Обратите внимание, что все метки - `-100`, кроме тех, что соответствуют словам-маскам.

{#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")

        # Создаем отображение между словами и соответствующими индексами токенов
        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)

        # Случайно маскируем слова
        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
        feature["labels"] = new_labels

    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")

        # Создаем отображение между словами и соответствующими индексами токенов
        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)

        # Случайно маскируем слова
        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
        feature["labels"] = new_labels

    return tf_default_data_collator(features)
```

{/if}

Далее мы можем опробовать ее на тех же примерах, что и раньше:

```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'
```

<Tip>

✏️ **Попробуйте!** Запустите приведенный выше фрагмент кода несколько раз, чтобы увидеть, как случайное маскирование происходит на ваших глазах! Также замените метод `tokenizer.decode()` на `tokenizer.convert_ids_to_tokens()`, чтобы увидеть, что токены из данного слова всегда маскируются вместе.

</Tip>

Теперь, когда у нас есть два колатора данных, остальные шаги по дообучению стандартны. Обучение может занять много времени в Google Colab, если вам не посчастливилось получить мифический GPU P100 😭, поэтому мы сначала уменьшим размер обучающего набора до нескольких тысяч примеров. Не волнуйтесь, мы все равно получим довольно приличную языковую модель! Быстрый способ уменьшить размер датасета в 🤗 Datasets - это функция `Dataset.train_test_split()`, которую мы рассматривали в [Главе 5](../chapter5/1):

```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
    })
})
```

Это автоматически создаст новые части `train` и `test`, с размером обучающего набора в 10 000 примеров и валидацией в 10 % от этого количества - не стесняйтесь увеличить это значение, если у вас мощный GPU! Следующее, что нам нужно сделать, это авторизоваться на Hugging Face Hub. Если вы выполняете этот код в блокноте, вы можете сделать это с помощью следующей служебной функции:

```python
from huggingface_hub import notebook_login

notebook_login()
```

которая отобразит виджет, где вы можете ввести свои учетные данные. В качестве альтернативы можно выполнить команду: 

```
huggingface-cli login
```

в вашем любимом терминале и авторизуйтесь там. 

{#if fw === 'tf'}

После того как мы авторизовались, мы можем создавать наши датасеты `tf.data`. Для этого мы воспользуемся методом `prepare_tf_dataset()`, который использует нашу модель для автоматического инференса того, какие столбцы должны войти в датасет. Если вы хотите контролировать, какие именно столбцы будут использоваться, вы можете использовать метод `Dataset.to_tf_dataset()` вместо этого. Для простоты мы будем использовать стандартный коллатор данных, но вы также можете попробовать коллатор для маскировки слов целиком и сравнить результаты в качестве упражнения:

```python
tf_train_dataset = model.prepare_tf_dataset(
    downsampled_dataset["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)

tf_eval_dataset = model.prepare_tf_dataset(
    downsampled_dataset["test"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=32,
)
```

Далее мы задаем гиперпараметры обучения и компилируем нашу модель. Мы используем функцию `create_optimizer()` из библиотеки 🤗 Transformers, которая возвращает нам оптимизатор `AdamW` с линейным затуханием скорости обучения. Мы также используем встроенные потери модели, которые используются по умолчанию, если в качестве аргумента `compile()` не указаны потери, и устанавливаем точность обучения на `"mixed_float16"`. Обратите внимание, что если вы используете Colab GPU или другой GPU, который не имеет поддержки ускорения float16, вам, вероятно, следует закомментировать эту строку.

Кроме того, мы настроили `PushToHubCallback`, который будет сохранять модель в Hub после каждой эпохи. Вы можете указать имя репозитория, в который хотите отправить модель, с помощью аргумента `hub_model_id` (в частности, этот аргумент нужно использовать, чтобы отправить модель в организацию). Например, чтобы отправить модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, так что в нашем случае это будет `"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)

# Обучение со смешанной точностью float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")

model_name = model_checkpoint.split("/")[-1]
callback = PushToHubCallback(
    output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer
)
```

Теперь мы готовы запустить `model.fit()`, но перед этим давайте вкратце рассмотрим _перплексию (perplexity)_, которая является общей метрикой для оценки качества работы языковых моделей.

{:else}

После того как мы авторизовались, мы можем указать аргументы для `Trainer`:

```python
from transformers import TrainingArguments

batch_size = 64
# Показываем потери при обучении для каждой эпохи
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,
)
```

Здесь мы изменили несколько параметров по умолчанию, включая `logging_steps`, чтобы обеспечить отслеживание потерь при обучении в каждой эпохе. Мы также использовали `fp16=True`, чтобы включить обучение со смешанной точностью, что дает нам еще большее улучшение скорости. По умолчанию `Trainer` удаляет все столбцы, которые не являются частью метода `forward()` модели. Это означает, что если вы используете коллатор для маскировки слов целиком, вам также нужно установить `remove_unused_columns=False`, чтобы не потерять колонку `word_ids` во время обучения.

Обратите внимание, что с помощью аргумента `hub_model_id` можно указать имя репозитория, в который вы хотите отправить модель (в частности, этот аргумент нужно использовать, чтобы отправить модель в ырепозиторий организации). Например, когда мы отправили модель в репозиторий организации [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` в `TrainingArguments`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданной вами выходной директорией, так что в нашем случае это будет `"lewtun/distilbert-finetuned-imdb"`.

Теперь у нас есть все компоненты для создания `Trainer`. Здесь мы просто используем стандартный `data_collator`, но вы можете попробовать коллатор для маскирования слов целиком и сравнить результаты в качестве упражнения:

```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,
    tokenizer=tokenizer,
)
```

Теперь мы готовы запустить `trainer.train()`, но перед этим давайте вкратце рассмотрим _перплексию (perplexity), которая является общей метрикой для оценки качества языковых моделей.

{/if}

### Перплексия языковых моделей[[perplexity-for-language-models]]

<Youtube id="NURcDHhYe98"/>

В отличие от других задач, таких как классификация текстов или ответов на вопросы, где для обучения нам дается помеченный корпус, при языковом моделировании у нас нет никаких явных меток. Как же определить, какая языковая модель является хорошей? Как и в случае с функцией автокоррекции в вашем телефоне, хорошая языковая модель - это та, которая присваивает высокую вероятность грамматически правильным предложениям и низкую вероятность бессмысленным предложениям. Чтобы лучше представить себе, как это выглядит, вы можете найти в Интернете целые подборки "неудач автокоррекции", где модель в телефоне человека выдает довольно забавные (и часто неуместные) завершения!

{#if fw === 'pt'}

Если предположить, что наш тестовый набор состоит в основном из грамматически правильных предложений, то одним из способов измерения качества нашей языковой модели является подсчет вероятностей, которые она присваивает следующему слову во всех предложениях тестового набора. Высокие вероятности указывают на то, что модель не "удивлена" и не "перплексирована" не виденными ранее примерами, и предполагают, что она усвоила основные грамматические закономерности языка. Существуют различные математические определения перплексии, но то, которое мы будем использовать, определяет ее как экспоненту потери перекрестной энтропии. Таким образом, мы можем вычислить перплексию нашей предварительно обученной модели, используя функцию `Trainer.evaluate()` для вычисления потерь кросс-энтропии на тестовом наборе, а затем взяв экспоненту результата:

```python
import math

eval_results = trainer.evaluate()
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
```

{:else}

Если предположить, что наш тестовый набор состоит в основном из грамматически правильных предложений, то одним из способов измерения качества нашей языковой модели является подсчет вероятностей, которые она присваивает следующему слову во всех предложениях тестового набора. Высокие вероятности указывают на то, что модель не "удивлена" или не "перплексирована" не виденными ранее примерами, и предполагают, что она выучила основные грамматические закономерности языка. Существуют различные математические определения перплексии, но то, которое мы будем использовать, определяет ее как экспоненту потери перекрестной энтропии. Таким образом, мы можем вычислить перплексию нашей предварительно обученной модели, используя метод `model.evaluate()` для вычисления потери кросс-энтропии на тестовом наборе, а затем взяв экспоненту результата:

```python
import math

eval_loss = model.evaluate(tf_eval_dataset)
print(f"Perplexity: {math.exp(eval_loss):.2f}")
```

{/if}

```python out
>>> Perplexity: 21.75
```

Более низкий показатель перплексии означает лучшую языковую модель, и мы видим, что наша начальная модель имеет несколько большое значение. Давайте посмотрим, сможем ли мы снизить его, дообучив модель! Для этого сначала запустим цикл обучения:

{#if fw === 'pt'}

```python
trainer.train()
```

{:else}

```python
model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback])
```

{/if}

и затем вычислить результирующую перплексию на тестовом наборе, как и раньше:

{#if fw === 'pt'}

```python
eval_results = trainer.evaluate()
print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}")
```

{:else}

```python
eval_loss = model.evaluate(tf_eval_dataset)
print(f"Perplexity: {math.exp(eval_loss):.2f}")
```

{/if}

```python out
>>> Perplexity: 11.32
```

Неплохо - это довольно значительное уменьшение перплексии, что говорит о том, что модель узнала что-то новое об области рецензий на фильмы!

{#if fw === 'pt'}

После завершения обучения мы можем отправить карточку модели с информацией об обучении в Hub (контрольные точки сохраняются во время самого обучения):

```python
trainer.push_to_hub()
```

{/if}

<Tip>

✏️ **Попробуйте!** Запустите обучение, описанное выше, после замены коллатора данных на коллатор маскирующий все слово. Получили ли вы лучшие результаты?

</Tip>

{#if fw === 'pt'} 

В нашем случае нам не нужно было делать ничего особенного с циклом обучения, но в некоторых случаях вам может понадобиться реализовать некоторую пользовательскую логику. Для таких случаев вы можете использовать 🤗 Accelerate - давайте посмотрим!

## Дообучение DistilBERT с помощью 🤗 Accelerate[[fine-tuning-distilbert-with-accelerate]]

Как мы видели на примере `Trainer`, дообучение модели маскированного языкового моделирования очень похоже на пример классификации текста из [Главы 3](../chapter3/1). Фактически, единственной особенностью является использование специального коллатора данных, о котором мы уже рассказывали ранее в этом разделе!

Однако мы видели, что `DataCollatorForLanguageModeling` также применяет случайное маскирование при каждой оценке, поэтому мы увидим некоторые колебания в наших оценках перплексии при каждом цикле обучения. Один из способов устранить этот источник случайности - применить маскирование _один раз_ ко всему тестовому набору, а затем использовать стандартный коллатор данных в 🤗 Transformers для сбора батчей во время оценки. Чтобы увидеть, как это работает, давайте реализуем простую функцию, которая применяет маскирование к батчу, подобно нашей первой работе с `DataCollatorForLanguageModeling`:

```python
def insert_random_mask(batch):
    features = [dict(zip(batch, t)) for t in zip(*batch.values())]
    masked_inputs = data_collator(features)
    # Создаем новый "маскированный" столбец для каждого столбца в датасете
    return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()}
```

Далее мы применим эту функцию к нашему тестовому набору и удалим столбцы без маскирования, чтобы заменить их столбцами с маскированием. Вы можете использовать маскирование целых слов, заменив `data_collator` выше на соответствующий, в этом случае вам следует удалить первую строку здесь:

```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",
    }
)
```

Затем мы можем настроить загрузчики данных как обычно, но для оценочного набора мы будем использовать `default_data_collator` из 🤗 Transformers:

```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
)
```

Здесь мы следуем стандартным шагам 🤗 Accelerate. Первым делом загружаем свежую версию предварительно обученной модели:

```
model = AutoModelForMaskedLM.from_pretrained(model_checkpoint)
```

Затем нужно указать оптимизатор; мы будем использовать стандартный `AdamW`:

```python
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)
```

Теперь, имея эти объекты, мы можем подготовить все для обучения с помощью объекта `Accelerator`:

```python
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

Теперь, когда наша модель, оптимизатор и загрузчики данных настроены, мы можем задать планировщик скорости обучения следующим образом:

```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,
)
```

Осталось сделать еще одну последнюю вещь перед обучением: создать репозиторий модели на Hugging Face Hub! Мы можем использовать библиотеку 🤗 Hub, чтобы сначала сгенерировать полное имя нашего репозитория:

```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'
```

затем создадим и клонируем репозиторий, используя класс `Repository` из 🤗 Hub:

```python
from huggingface_hub import Repository

output_dir = model_name
repo = Repository(output_dir, clone_from=repo_name)
```

После этого остается только написать полный цикл обучения и оценки:

```python
from tqdm.auto import tqdm
import torch
import math

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Обучение
    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)

    # Оценка
    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}")

    # Сохранение и загрузка
    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
```

Круто, мы смогли оценить перплексию для каждой эпохи и обеспечить воспроизводимость нескольких циклов обучения!

{/if}

## Использование нашей дообученной модели[[using-our-fine-tuned-model]]

Вы можете взаимодействовать с дообученной моделью либо с помощью ее виджета на Hub, либо локально с помощью `pipeline` из 🤗 Transformers. Давайте воспользуемся последним вариантом, чтобы загрузить нашу модель с помощью конвейера `fill-mask`:

```python
from transformers import pipeline

mask_filler = pipeline(
    "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb"
)
```

Затем мы можем передать конвейеру наш пример текста " This is a great [MASK]" и посмотреть, каковы 5 лучших прогнозов:

```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.'
```

Отлично - наша модель явно адаптировала свои веса, чтобы прогнозировать слова, которые сильнее ассоциируются с фильмами!

<Youtube id="0Oxphw4Q9fo"/>

На этом мы завершаем наш первый эксперимент по обучению языковой модели. В [разделе 6](../chapter7/6) вы узнаете, как обучить авторегрессионную модель типа GPT-2 с нуля; загляните туда, если хотите посмотреть, как можно предварительно обучить свою собственную модель трансформера!

<Tip>

✏️ **Попробуйте!** Чтобы оценить преимущества адаптации к домену, дообучите классификатор на метках IMDb как для предварительно обученных, так и для дообученных контрольных точек DistilBERT. Если вам нужно освежить в памяти классификацию текстов, ознакомьтесь с [Главой 3](../chapter3/1).

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/3.mdx" />

### Обучение каузальной языковой модели с нуля[[training-a-causal-language-model-from-scratch]]
https://huggingface.co/learn/course/ru/chapter7/6.md

# Обучение каузальной языковой модели с нуля[[training-a-causal-language-model-from-scratch]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section6_tf.ipynb"},
]} />

{/if}

До сих пор мы в основном использовали предварительно обученные модели и осуществляли их дообучение для новых случаев использования, повторно используя веса, полученные в ходе предварительного обучения. Как мы видели в [Главе 1](../chapter1/1), это принято называть _трансферным обучением_, и это очень успешная стратегия применения моделей Transformer для большинства реальных случаев использования, когда размеченных данных недостаточно. В этой главе мы применим другой подход и обучим совершенно новую модель с нуля. Это хороший подход, если у вас много данных, и они сильно отличаются от данных предварительного обучения, используемых для имеющихся моделей. Однако предварительное обучение языковой модели требует значительно больше вычислительных ресурсов, чем дообучение существующей. Примеры, когда имеет смысл обучать новую модель, включают датасеты, состоящие из музыкальных нот, молекулярных последовательностей, таких как ДНК, или языков программирования. Последние в недавнее время получили широкое распространение благодаря таким инструментам, как TabNine и GitHub's Copilot, работающим на основе модели Codex от OpenAI, которые могут генерировать длинные последовательности кода. Для решения этой задачи генерации текста лучше всего подходят авторегрессионные или каузальные языковые модели, такие как GPT-2.

В этом разделе мы построим уменьшенную версию модели генерации кода: мы сосредоточимся на однострочных завершениях вместо полных функций или классов, используя подмножество кода Python. Работая с данными на Python, вы часто сталкиваетесь со стеком Data Science на Python, состоящем из библиотек `matplotlib`, `seaborn`, `pandas` и `scikit-learn`. При использовании этих фреймворков часто возникает необходимость поиска определенных команд, поэтому было бы неплохо, если бы мы могли использовать модель выполненяющую эти вызововы за нас.

<Youtube id="Vpjb1lu0MDk"/>

В [Главе 6](../chapter6/1) мы создали эффективный токенизатор для обработки исходного кода Python, но нам все еще нужен крупный датасет для предварительного обучения модели. Здесь мы применим наш токенизатор к корпусу кода Python, полученному из розиториев GitHub. Затем мы воспользуемся API `Trainer` и 🤗 Accelerate для обучения модели. Приступим!

<iframe src="https://course-demos-codeparrot-ds.hf.space" frameBorder="0" height="300" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

На самом деле это демонстрация модели, которая была обучена и загружена в Hub с помощью кода, приведенного в этом разделе. Вы можете найти ее [здесь](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Обратите внимание, что поскольку при генерации текста происходит некоторая рандомизация, вы, вероятно, получите немного другой результат.
 
## Сбор данных[[gathering-the-data]]

Код на Python в изобилии доступен в таких репозиториях кода, как GitHub, и мы можем использовать его для создания датасета путем поиска каждого розитория Python. Именно такой подход был использован в книге [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) для предварительного обучения большой модели GPT-2. Используя дамп GitHub объемом около 180 ГБ, содержащий примерно 20 миллионов файлов Python под названием `codeparrot`, авторы создали датасет, которым затем поделились на [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot).

Однако обучение на полном корпусе требует много времени и вычислений, а нам нужно только подмножество датасетов, связанных со стеком data science на Python. Итак, давайте начнем с фильтрации датасета `codeparrot` по всем файлам, включающим любую из библиотек из этого стека. Из-за большого размера датасета мы хотим избежать его загрузки; вместо этого мы будем использовать функцию потоковой передачи (streaming), чтобы фильтровать его на лету. Чтобы помочь нам отфильтровать примеры кода с использованием библиотек, о которых мы говорили ранее, мы воспользуемся следующей функцией:

```py
def any_keyword_in_string(string, keywords):
    for keyword in keywords:
        if keyword in string:
            return True
    return False
```

Давайте протестируем ее на двух примерах:

```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
```

Мы можем использовать ее для создания функции, которая будет передавать датасет и отфильтровывать нужные нам элементы:

```py
from collections import defaultdict
from tqdm import tqdm
from datasets import Dataset


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)
```

Затем мы можем просто применить эту функцию к потоковому датасету:

```py
# Выполнение этой ячейки займет очень много времени, поэтому ее следует пропустить и перейти к
# следующей!
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.
```

Таким образом, у нас остается около 3% от исходного датасета, что все равно довольно много - результирующий датасет занимает 6 ГБ и состоит из 600 000 Python-скриптов!

Фильтрация полного датасета может занять 2-3 часа в зависимости от вашей машины и пропускной способности сети. Если вы не хотите выполнять этот длительный процесс самостоятельно, мы предоставляем отфильтрованный датасет на Hub для загрузки:

```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="validation")

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
    })
})
```

<Tip>

Предварительное обучение языковой модели займет некоторое время. Мы рекомендуем сначала запустить цикл обучения на выборке данных, раскомментировав две частичные строки выше, и убедиться, что обучение успешно завершено и модели сохранены. Нет ничего обиднее, чем неудачное обучение на последнем этапе из-за того, что вы забыли создать папку или из-за опечатки в конце цикла обучения!

</Tip>

Давайте рассмотрим пример из датасета. Мы покажем только первые 200 символов каждого поля:

```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'''
```

Мы видим, что поле `content` содержит код, на котором мы хотим обучить нашу модель. Теперь, когда у нас есть датасет, нам нужно подготовить тексты, чтобы они были в формате, подходящем для предварительного обучения.

## Подготовка датасета[[preparing-the-dataset]]

<Youtube id="ma1TrR7gE7I"/>

Первым шагом будет токенизация данных, чтобы мы могли использовать их для обучения. Поскольку наша цель - автозаполнение коротких вызовов функций, мы можем оставить размер контекста относительно небольшим. Благодаря этому мы сможем обучать модель гораздо быстрее, и она будет занимать значительно меньше памяти. Если для вашего приложения важно, чтобы контекст был больше (например, если вы хотите, чтобы модель писала юнит-тесты на основе файла с определением функции), обязательно увеличьте это число, но не забывайте, что это приведет к увеличению объема памяти GPU. Пока что давайте зафиксируем размер контекста на 128 токенов, в отличие от 1 024 или 2 048, используемых в GPT-2 или GPT-3 соответственно.

Большинство документов содержит гораздо больше 128 токенов, поэтому простое обрезание входных данных до максимальной длины приведет к тому, что большая часть нашего датасета будет удалена. Вместо этого мы используем параметр `return_overflowing_tokens` для токенизации всего ввода и разбиения его на части, как мы делали в [Главе 6](../chapter6/4). Мы также будем использовать параметр `return_length`, чтобы автоматически возвращать длину каждого созданного фрагмента. Часто последний фрагмент будет меньше размера контекста, и мы избавимся от этих фрагментов, чтобы избежать проблем с дополнением; на самом деле они нам не нужны, поскольку у нас и так много данных.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/chunking_texts.svg" alt="Chunking a large texts in several pieces."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/chunking_texts-dark.svg" alt="Chunking a large texts in several pieces."/>
</div>

Давайте посмотрим, как это работает, на первых двух примерах:

```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]
```

Из этих двух примеров видно, что в общей сложности мы получили 34 сегмента. Взглянув на длину фрагментов, мы видим, что фрагменты в конце обоих документов содержат менее 128 токенов (117 и 41, соответственно). Это лишь малая часть всех имеющихся у нас фрагментов, поэтому мы можем смело отбросить их. С помощью поля `overflow_to_sample_mapping` мы также можем восстановить, какие фрагменты принадлежали каким входным примерам.

В этой операции мы используем удобную особенность функции `Dataset.map()` из 🤗 Datasets, которая заключается в том, что она не требует отображения один к одному; как мы видели в [разделе 3](../chapter7/3), мы можем создавать батч с большим или меньшим количеством элементов, чем входной батч. Это полезно при выполнении таких операций, как аугментация или фильтрация данных, которые изменяют количество элементов. В нашем случае при токенизации каждого элемента на фрагменты заданного размера контекста мы создаем много примеров из каждого документа. Нам просто нужно убедиться, что мы удалили существующие столбцы, поскольку они имеют противоречивый размер. Если бы мы хотели их сохранить, то могли бы повторить их соответствующим образом и вернуть в рамках вызова `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
    })
})
```

Теперь у нас есть 16,7 миллиона примеров со 128 токенами в каждом, что соответствует примерно 2,1 миллиарда токенов в общей сложности. Для сравнения, модели OpenAI GPT-3 и Codex обучены на 300 и 100 миллиардах токенов соответственно, причем модели Codex инициализируются из контрольных точек GPT-3. Наша цель в этом разделе - не конкурировать с этими моделями, которые могут генерировать длинные, связные тексты, а создать уменьшенную версию, обеспечивающую быструю функцию автозаполнения для специалистов по обработке данных.

Теперь, когда у нас есть готовый датасет, давайте создадим модель!

<Tip>

✏️ **Попробуйте!** Избавление от всех фрагментов, размер которых меньше размера контекста, не является большой проблемой, поскольку мы используем небольшие контекстные окна. При увеличении размера контекста (или если у вас корпус коротких документов) доля отбрасываемых фрагментов также будет расти. Более эффективный способ подготовки данных - объединить все токенизированные примеры в батч с маркером `eos_token_id` между ними, а затем выполнить фрагментацию на конкатенированных последовательностях. В качестве упражнения измените функцию `tokenize()`, чтобы использовать этот подход. Обратите внимание, что вам нужно установить `truncation=False` и удалить другие аргументы из токенизатора, чтобы получить полную последовательность идентификаторов токенов.

</Tip>


## Инициализация новой модели[[initializing-a-new-model]]

Наш первый шаг - инициализация свежей модели GPT-2. Мы будем использовать ту же конфигурацию для нашей модели, что и для маленькой модели GPT-2, поэтому загрузим предварительно обученную конфигурацию, убедимся, что размер токенизатора соответствует размеру словарного запаса модели, и передадим идентификаторы токенов `bos` и `eos` (начало и конец последовательности):

{#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,
)
```

С этой конфигурацией мы можем загрузить новую модель. Обратите внимание, что впервые мы не используем функцию `from_pretrained()`, поскольку фактически инициализируем модель самостоятельно:

```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,
)
```

С этой конфигурацией мы можем загрузить новую модель. Обратите внимание, что впервые мы не используем функцию `from_pretrained()`, поскольку фактически инициализируем модель самостоятельно:

```py
model = TFGPT2LMHeadModel(config)
model(model.dummy_inputs)  # Создание модели
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}

Наша модель имеет 124 миллиона параметров, которые нам предстоит дообучить. Прежде чем начать обучение, нам нужно настроить коллатор данных, который займется созданием батчей. Мы можем использовать коллатор `DataCollatorForLanguageModeling`, который разработан специально для языкового моделирования (о чем недвусмысленно говорит его название). Помимо стекирования и дополнения батчей, он также заботится о создании меток языковой модели - в каузальном языковом моделировании входы тоже служат метками (просто сдвинутыми на один элемент), и этот коллатор данных создает их на лету во время обучения, так что нам не нужно дублировать `input_ids`.

Обратите внимание, что `DataCollatorForLanguageModeling` поддерживает как маскированное языковое моделирование (masked language modeling - MLM), так и каузальное языковое моделирование (causal language modeling - CLM). По умолчанию он подготавливает данные для MLM, но мы можем переключиться на CLM, задав аргумент `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}

Давайте посмотрим на пример:

```py
out = data_collator([tokenized_datasets["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}

Мы видим, что примеры были стекированы, и все тензоры имеют одинаковую форму.

{#if fw === 'tf'}

Теперь мы можем использовать метод `prepare_tf_dataset()` для преобразования наших датасетов в датасеты TensorFlow с помощью коллатора данных, который мы создали выше:

```python
tf_train_dataset = model.prepare_tf_dataset(
    tokenized_dataset["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)
tf_eval_dataset = model.prepare_tf_dataset(
    tokenized_dataset["valid"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=32,
)
```

{/if}

<Tip warning={true}>

⚠️ Сдвиг входов и меток для их выравнивания происходит внутри модели, поэтому коллатор данных просто копирует входы для создания меток.

</Tip>


Теперь у нас есть все необходимое для обучения нашей модели - в конце концов, это было не так уж и сложно! Прежде чем приступить к обучению, мы должны войти в Hugging Face. Если вы работаете в блокноте, вы можете сделать это с помощью следующей служебной функции:

```python
from huggingface_hub import notebook_login

notebook_login()
```

Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face.

Если вы работаете не в блокноте, просто введите следующую строку в терминале:

```bash
huggingface-cli login
```

{#if fw === 'pt'}

Осталось только настроить аргументы обучения и запустить `Trainer`. Мы будем использовать косинусный график скорости обучения с некоторым разогревом (warmup) и эффективным размером батча в 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`). Аккумулирование градиента используется, когда один батч не помещается в память, и инкрементально накапливает градиент за несколько проходов вперед/назад. Мы увидим это в действии, когда создадим цикл обучения с использованием 🤗 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"],
)
```

Теперь мы можем просто запустить `Trainer` и дождаться окончания обучения. В зависимости от того, запустите ли вы его на полном или на подмножестве обучающего набора, это займет 20 или 2 часа соответственно, так что захватите несколько чашек кофе и хорошую книгу для чтения!

```py
trainer.train()
```

После завершения обучения мы можем отправить модель и токенизатор в Hub:

```py
trainer.push_to_hub()
```

{:else}

Осталось только настроить гиперпараметры обучения и вызвать `compile()` и `fit()`. Мы будем использовать график скорости обучения с некоторым разогревом, чтобы повысить стабильность обучения:

```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)

# Обучение со смешанной точностью float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

Теперь мы можем просто вызвать `model.fit()` и дождаться окончания обучения. В зависимости от того, запустите ли вы его на полном или на подмножестве обучающего набора, это займет 20 или 2 часа соответственно, так что захватите несколько чашек кофе и хорошую книгу для чтения! После завершения обучения мы можем отправить модель и токенизатор в 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}

<Tip>

✏️ **Попробуйте!** Всего около 30 строк кода в дополнение к `TrainingArguments` понадобилось нам, чтобы перейти от сырых текстов к обучению GPT-2. Попробуйте это на своем датасете и посмотрите, сможете ли вы получить хорошие результаты!

</Tip>

<Tip>

{#if fw === 'pt'}

💡 Если у вас есть доступ к компьютеру с несколькими GPU, попробуйте запустить код на нем. `Trainer` автоматически управляет несколькими компьютерами, и это может значительно ускорить обучение.

{:else}

💡 Если у вас есть доступ к компьютеру с несколькими GPU, вы можете попробовать использовать контекст `MirroredStrategy` для существенного ускорения обучения. Вам нужно будет создать объект `tf.distribute.MirroredStrategy` и убедиться, что все методы `to_tf_dataset()` или `prepare_tf_dataset()`, а также создание модели и вызов `fit()` выполняются в его контексте `scope()`. Документацию на эту тему можно посмотреть [здесь](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit).

{/if}

</Tip>

## Генерация кода с помощью конвейера[[code-generation-with-a-pipeline]]

Настал момент истины: давайте посмотрим, насколько хорошо работает обученная модель! В логах мы видим, что потери постоянно снижаются, но чтобы проверить модель на практике, давайте посмотрим, насколько хорошо она работает на некоторых подсказках. Для этого мы обернем модель в `pipeline` для генерации текста и поместим ее на GPU для быстрой генерации, если таковой доступен:

{#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}

Давайте начнем с простой задачи - создания диаграммы рассеивания (scatter plot):

```py
txt = """\
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create scatter plot with x, y
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create scatter plot with x, y
plt.scatter(x, y)

# create scatter
```

Результат выглядит корректно. Работает ли это также для `pandas` операции? Давайте посмотрим, сможем ли мы создать `DataFrame` из двух массивов:

```py
txt = """\
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create dataframe from x and y
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# create some data
x = np.random.randn(100)
y = np.random.randn(100)

# create dataframe from x and y
df = pd.DataFrame({'x': x, 'y': y})
df.insert(0,'x', x)
for
```

Отлично, это правильный ответ -- хотя затем он снова вставляет столбец `x`. Поскольку количество генерируемых токенов ограничено, следующий цикл `for` обрывается. Давайте посмотрим, сможем ли мы сделать что-то более сложное, и чтобы модель помогла нам использовать операцию `groupby`: 

```py
txt = """\
# dataframe with profession, income and name
df = pd.DataFrame({'profession': x, 'income':y, 'name': z})

# calculate the mean income per profession
"""
print(pipe(txt, num_return_sequences=1)[0]["generated_text"])
```

```python out
# dataframe with profession, income and name
df = pd.DataFrame({'profession': x, 'income':y, 'name': z})

# calculate the mean income per profession
profession = df.groupby(['profession']).mean()

# compute the
```

Неплохо; это правильный способ сделать это. Наконец, давайте посмотрим, сможем ли мы также использовать его для `scikit-learn` и создать модель Random Forest:

```py
txt = """
# import random forest regressor from scikit-learn
from sklearn.ensemble import RandomForestRegressor

# fit random forest model with 300 estimators on 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

# fit random forest model with 300 estimators on X, y:
rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3)
rf.fit(X, y)
rf
```

{#if fw === 'tf'}

Глядя на эти несколько примеров, кажется, что модель усвоила часть синтаксиса стека Python Data Science. Конечно, нам нужно будет более тщательно оценить модель, прежде чем внедрять ее в реальный мир, но все же это впечатляющий прототип.

{:else}

Глядя на эти несколько примеров, кажется, что модель усвоила часть синтаксиса стека Python Data Science (конечно, нам нужно будет оценить это более тщательно, прежде чем разворачивать модель в реальном мире). Однако иногда требуется более тщательная настройка обучения модели, чтобы добиться необходимого качества работы для конкретного случая использования. Например, если мы хотим динамически обновлять размер батча или иметь условный цикл обучения, который пропускает плохие примеры на лету? Одним из вариантов может быть подкласс `Trainer` и добавление необходимых изменений, но иногда проще написать цикл обучения с нуля. Вот тут-то и приходит на помощь 🤗 Accelerate.

{/if}

{#if fw === 'pt'}

## Обучение с 🤗 Accelerate[[training-with-accelerate]]

Мы уже видели, как обучать модель с помощью `Trainer`, который позволяет сделать некоторые настройки. Однако иногда нам нужен полный контроль над циклом обучения, или мы хотим внести некоторые экзотические изменения. В этом случае 🤗 Accelerate - отличный выбор, и в этом разделе мы рассмотрим шаги по его использованию для обучения нашей модели. Чтобы сделать все более интересным, мы также добавим изюминку в цикл обучения.

<Youtube id="Hm8_PgVTFuc"/>

Поскольку нас в основном интересует разумное автодополнение для библиотек data science, имеет смысл придать больший вес обучающим примерам, в которых чаще используются эти библиотеки. Мы можем легко определить эти примеры по использованию таких ключевых слов, как `plt`, `pd`, `sk`, `fit` и `predict`, которые являются наиболее частыми именами для импорта `matplotlib.pyplot`, `pandas` и `sklearn`, а также шаблонам fit/predict для последней. Если каждый из них представлен в виде одного токена, мы можем легко проверить, встречаются ли они во входной последовательности. Токены могут иметь пробельный префикс, поэтому мы также проверим наличие таких версий в словаре токенизатора. Чтобы убедиться, что все работает, мы добавим один тестовый токен, который должен быть разбит на несколько токенов:

```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'
```

Отлично, похоже, это прекрасно работает! Теперь мы можем написать пользовательскую функцию потерь, которая принимает входную последовательность, логиты и ключевые токены, которые мы только что выбрали в качестве входных данных. Сначала нам нужно выровнять логиты и входные данные: входная последовательность, сдвинутая на единицу вправо, формирует метки, поскольку следующий токен является меткой для текущего токена. Мы можем добиться этого, начиная метки со второго токена входной последовательности, поскольку модель все равно не делает предсказания для первого токена. Затем мы отсекаем последний логит, поскольку у нас нет метки для токена, который следует за всей входной последовательностью. Таким образом, мы можем вычислить потери для каждого примера и подсчитать количество вхождений всех ключевых слов в каждом примере. Наконец, мы вычисляем средневзвешенное значение по всем примерам, используя вхождения в качестве весов. Поскольку мы не хотим отбрасывать все выборки, в которых нет ключевых слов, мы добавляем 1 к весам:

```py
from torch.nn import CrossEntropyLoss
import torch


def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0):
    # Сдвигаем так, чтобы токены < n предсказывали n
    shift_labels = inputs[..., 1:].contiguous()
    shift_logits = logits[..., :-1, :].contiguous()
    # Вычисляем потери на каждый токен
    loss_fct = CrossEntropyLoss(reduce=False)
    loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
    # Изменение размера и средние потери на каждый пример
    loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1)
    # Расчет и масштабирование весов
    weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum(
        axis=[0, 2]
    )
    weights = alpha * (1.0 + weights)
    # Расчет взвешенного среднего
    weighted_loss = (loss_per_sample * weights).mean()
    return weighted_loss
```

Прежде чем приступить к обучению с этой потрясающей новой функцией потерь, нам нужно подготовить несколько вещей:

- Нам нужны загрузчики данных, чтобы загружать данные батчами.
- Нам нужно настроить параметры затухания веса (weight decay).
- Время от времени мы хотим проводить оценку, поэтому имеет смысл обернуть код оценки в функцию.

Давайте начнем с загрузчиков данных. Нам нужно только задать для датасета формат `"torch"`, а затем мы можем передать его в PyTorch `DataLoader` с соответствующим размером батча:

```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)
```

Далее мы группируем параметры, чтобы оптимизатор знал, какие из них получат дополнительное затухание веса. Обычно все смещения (bias) и весовые коэффициенты LayerNorm (LayerNorm weights) исключаются из этого правила; вот как мы можем это реализовать:

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

Поскольку мы хотим регулярно оценивать модель на валидационном множестве во время обучения, давайте напишем функцию и для этого. Она просто запускается через загрузчик оценочных данных и собирает все потери:

```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()
```

С помощью функции `evaluate()` мы можем сообщать о потерях и [перплексии](../chapter7/3) через регулярные промежутки времени. Далее мы переопределим нашу модель, чтобы убедиться, что мы снова обучаемся с нуля:

```py
model = GPT2LMHeadModel(config)
```

Затем мы можем определить наш оптимизатор, используя предыдущую функцию для части параметров для затухания веса:

```py
from torch.optim import AdamW

optimizer = AdamW(get_grouped_params(model), lr=5e-4)
```

Теперь давайте подготовим модель, оптимизатор и загрузчики данных, чтобы начать обучение:

```py
from accelerate import Accelerator

accelerator = Accelerator(fp16=True)

model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 Если вы проводите обучение на TPU, вам нужно будет перенести весь код, начиная с ячейки выше, в выделенную функцию обучения. Подробнее смотрите [Главу 3](../chapter3/1).

</Tip>

Теперь, когда мы отправили наш `train_dataloader` в `accelerator.prepare()`, мы можем использовать его длину для вычисления количества шагов обучения. Помните, что это всегда нужно делать после подготовки загрузчика данных, так как этот метод изменит его длину. Мы используем классический линейный график скорости обучения до 0:

```py
from transformers import get_scheduler

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,
)
```

Наконец, чтобы отправить нашу модель в Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала войдите в Hub Hugging Face, если вы еще не вошли в него. Мы определим имя розитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свой собственный вариант; он просто должен содержать ваше имя пользователя, что и делает функция `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'
```

Затем мы можем клонировать этот розиторий в локальную папку. Если она уже существует, эта локальная папка должна быть существующим клоном розитория, с которым мы работаем:

```py
output_dir = "codeparrot-ds-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи.

Перед обучением давайте проведем быстрый тест, чтобы проверить, правильно ли работает функция оценки:

```py
evaluate()
```

```python out
(10.934126853942871, 56057.14453125)
```

Это очень высокие значения для потерь и перплексии, но это неудивительно, ведь мы еще не обучили модель. Итак, у нас все готово для написания основной части скрипта обучения: цикла обучения. В цикле обучения мы выполняем итерации по загрузчику данных и передаем батчи в модель. С помощью логитов мы можем оценить нашу пользовательскую функцию потерь. Мы масштабируем потери по количеству шагов накопления градиента, чтобы не создавать больших потерь при агрегировании большего количества шагов. Перед оптимизацией мы также обрезаем градиенты для лучшей сходимости. Наконец, каждые несколько шагов мы оцениваем модель на оценочном наборе с помощью нашей новой функции `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=num_training_steps
    ):
        logits = model(batch["input_ids"]).logits
        loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids)
        if step % 100 == 0:
            accelerator.print(
                {
                    "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
                )
```

Вот и все -- теперь у вас есть свой собственный цикл обучения для каузальных языковых моделей, таких как GPT-2, который вы можете дополнительно настроить под свои нужды. 

<Tip>

✏️ **Попробуйте!** Либо создайте свою собственную функцию потерь, подходящую для вашего случая, либо добавьте еще один пользовательский шаг в цикл обучения.

</Tip>

<Tip>

✏️ **Попробуйте!** При проведении длительных экспериментов по обучению полезно регистрировать важные метрики с помощью таких инструментов, как TensorBoard или Weights & Biases. Добавьте соответствующее логирование в цикл обучения, чтобы вы всегда могли проверить, как проходит обучение.

</Tip>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/6.mdx" />

### Освоение NLP[[mastering-nlp]]
https://huggingface.co/learn/course/ru/chapter7/8.md

# Освоение NLP[[mastering-nlp]]

<CourseFloatingBanner
    chapter={7}
    classNames="absolute z-10 right-0 top-0"
/>

Если вы дошли до конца курса, поздравляем - теперь у вас есть все знания и инструменты, необходимые для решения (почти) любой задачи NLP с помощью 🤗 Transformers и экосистемы Hugging Face!

Мы видели много разных коллаторов данных, поэтому сделали это небольшое видео, чтобы помочь вам определить, какой из них лучше использовать для каждой задачи:

<Youtube id="-RPeakdlHYo"/>

Пройдя этот молниеносный тур по основным задачам NLP, вы должны:

* Знать, какие архитектуры (кодер, декодер или кодер-декодер) лучше всего подходят для конкретной задачи
* Понимать разницу между предварительным обучением и дообучением языковой модели
* Знать, как обучать модели Transformer, используя либо API `Trainer` и возможности распределенного обучения в 🤗 Accelerate, либо TensorFlow и Keras, в зависимости от того, какой путь вы выбрали
* Понимать значение и ограничения таких метрик, как ROUGE и BLEU, для задач генерации текста
* Знать, как взаимодействовать с вашими дообученными моделями, как на Hub, так и с помощью `pipeline` из 🤗 Transformers

Несмотря на все эти знания, настанет момент, когда вы столкнетесь с трудной ошибкой в своем коде или у вас возникнет вопрос о том, как решить ту или иную задачу NLP. К счастью, сообщество Hugging Face готово помочь вам! В заключительной главе этой части курса мы рассмотрим, как можно отлаживать свои модели Transformer и эффективно обращаться за помощью.

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/8.mdx" />

### Перевод[[translation]]
https://huggingface.co/learn/course/ru/chapter7/4.md

# Перевод[[translation]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section4_tf.ipynb"},
]} />

{/if}

Теперь давайте погрузимся в перевод. Это еще одна [задача преобразования последовательности в последовательность (sequence-to-sequence)](../chapter1/7), что означает, что это проблема, которую можно сформулировать как переход от одной последовательности к другой. В этом смысле задача очень близка к [резюмированию (summarization)](../chapter7/6), и вы можете приспособить то, что мы здесь рассмотрим, к другим задачам преобразования последовательности в последовательность, таким как:

- **Перенос стиля (Style transfer)**: Создание модели, которая *переводит* тексты, написанные в определенном стиле, в другой (например, из формального в повседневный или из шекспировского английского в современный).
- **Генеративные ответы на вопросы (Generative question answering)**: Создание модели, которая генерирует ответы на вопросы, учитывая контекст

<Youtube id="1JvfrvZgi6c"/>

Если у вас есть достаточно большой корпус текстов на двух (или более) языках, вы можете обучить новую модель перевода с нуля, как мы это сделаем в разделе по [казуальному языковому моделированию (causal language modeling)](../chapter7/6). Однако быстрее будет дообучить существующую модель перевода, будь то многоязычная модель типа mT5 или mBART, которую нужно дообучить для конкретной пары языков, или даже модель, специализированная для перевода с одного языка на другой, которую нужно дообучить для конкретного корпуса.

В этом разделе мы дообучим модель Marian, предварительно обученную переводу с английского на французский (поскольку многие сотрудники Hugging Face говорят на обоих этих языках), на датасете [KDE4](https://huggingface.co/datasets/kde4), который представляет собой набор локализованных файлов для приложений [KDE](https://apps.kde.org/). Модель, которую мы будем использовать, была предварительно обучена на большом корпусе французских и английских текстов, взятых из [Opus dataset](https://opus.nlpl.eu/), который фактически содержит датасет KDE4. Но даже если модель, которую мы используем, видела эти данные во время предварительного обучения, мы увидим, что после дообучения мы сможем получить ее лучшую версию.

Когда мы закончим, у нас будет модель, способная делать прогнозы, подобные этому:

<iframe src="https://course-demos-marian-finetuned-kde4-en-to-fr.hf.space" frameBorder="0" height="350" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

<a class="flex justify-center" href="/huggingface-course/marian-finetuned-kde4-en-to-fr">
<img class="block dark:hidden lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/modeleval-marian-finetuned-kde4-en-to-fr.png" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/modeleval-marian-finetuned-kde4-en-to-fr-dark.png" alt="One-hot encoded labels for question answering."/>
</a>

Как и в предыдущих разделах, вы можете найти актуальную модель, которую мы обучим и загрузим на 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.).

## Подготовка данных[[preparing-the-data]]

Чтобы дообучить или обучить модель перевода с нуля, нам понадобится датасет, подходящий для этой задачи. Как уже упоминалось, мы будем использовать [датасет KDE4](https://huggingface.co/datasets/kde4) , но вы можете легко адаптировать код для использования своих собственных данных, если у вас есть пары предложений на двух языках, с которых вы хотите переводить и на которые хотите переводить. Обратитесь к [Главе 5](../chapter5/1) если вам нужно вспомнить, как загружать пользовательские данные в `Dataset`.

### Датасет KDE4[[the-kde4-dataset]]

Как обычно, мы загружаем наш датасет с помощью функции `load_dataset()`:

```py
from datasets import load_dataset

raw_datasets = load_dataset("kde4", lang1="en", lang2="fr")
```

Если вы хотите работать с другой парой языков, вы можете указать их по кодам. Всего для этого датасета доступно 92 языка; вы можете увидеть их все, развернув языковые теги в его [карточке датасета](https://huggingface.co/datasets/kde4).

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/language_tags.png" alt="Language available for the KDE4 dataset." width="100%">

Давайте посмотрим на датасет:

```py
raw_datasets
```

```python out
DatasetDict({
    train: Dataset({
        features: ['id', 'translation'],
        num_rows: 210173
    })
})
```

У нас есть 210 173 пары предложений, но в одной части, поэтому нам нужно создать собственный проверочный набор. Как мы видели в [Главе 5](../chapter5/1), у `Dataset` есть метод `train_test_split()`, который может нам помочь. Мы зададим seed для воспроизводимости:

```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
    })
})
```

Мы можем переименовать ключ `"test"` в `"validation"` следующим образом:

```py
split_datasets["validation"] = split_datasets.pop("test")
```

Теперь давайте рассмотрим один элемент датасета:

```py
split_datasets["train"][1]["translation"]
```

```python out
{'en': 'Default to expanded threads',
 'fr': 'Par défaut, développer les fils de discussion'}
```

Мы получаем словарь с двумя предложениями на запрошенной паре языков. Одна из особенностей этого датасета, наполненного техническими терминами в области компьютерных наук, заключается в том, что все они полностью переведены на французский язык. Однако французские инженеры при разговоре оставляют большинство специфических для компьютерных наук слов на английском. Например, слово "threads" вполне может встретиться во французском предложении, особенно в техническом разговоре; но в данном датасете оно переведено как более правильное "fils de discussion". Используемая нами модель, предварительно обученная на большем корпусе французских и английских предложений, выбирает более простой вариант - оставить слово как есть:

```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'}]
```

Другой пример такого поведения можно увидеть на примере слова "plugin", которое официально не является французским словом, но большинство носителей языка поймут его и не станут переводить.
В датасете KDE4 это слово было переведено на французский как более официальное "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."}
```

Однако наша предварительно обученная модель придерживается компактного и знакомого английского слова:

```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."}]
```

Будет интересно посмотреть, сможет ли наша дообученная модель уловить эти особенности датасета (спойлер: сможет).

<Youtube id="0Oxphw4Q9fo"/>

<Tip>

✏️ **Попробуйте!** Еще одно английское слово, которое часто используется во французском языке, - "email". Найдите в обучающем датасете первый образец, в котором используется это слово. Как оно переводится? Как предварительно обученная модель переводит то же английское предложение?

</Tip>

### Предварительная обработка данных[[processing-the-data]]

<Youtube id="XAR8jnZZuUs"/>

Вы уже должны знать, как это делается: все тексты нужно преобразовать в наборы идентификаторов токенов, чтобы модель могла понять их смысл. Для этой задачи нам понадобится токенизация как входных данных, так и целевых. Наша первая задача - создать объект `tokenizer`. Как отмечалось ранее, мы будем использовать предварительно обученную модель Marian English to French. Если вы будете пробовать этот код с другой парой языков, обязательно адаптируйте контрольную точку модели. Организация [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) предоставляет более тысячи моделей на разных языках.

```python
from transformers import AutoTokenizer

model_checkpoint = "Helsinki-NLP/opus-mt-en-fr"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt")
```

Вы также можете заменить `model_checkpoint` на любую другую модель из [Hub](https://huggingface.co/models) или локальной папки, в которой вы сохранили предварительно обученную модель и токенизатор.

<Tip>

💡 Если вы используете многоязыковой токенизатор, такой как mBART, mBART-50 или M2M100, вам нужно задать языковые коды ваших входных и целевых данных в токенизаторе, задав правильные значения параметрам `tokenizer.src_lang` и `tokenizer.tgt_lang`.

</Tip>

Подготовка наших данных довольно проста. Нужно помнить только об одном: необходимо убедиться, что токенизатор обрабатывает целевые значения на выходном языке (здесь - французском). Вы можете сделать это, передав целевые данные в аргумент `text_targets` метода `__call__` токенизатора.

Чтобы увидеть, как это работает, давайте обработаем по одному примеру каждого языка из обучающего набора:

```python
en_sentence = split_datasets["train"][1]["translation"]["en"]
fr_sentence = split_datasets["train"][1]["translation"]["fr"]

inputs = tokenizer(en_sentence, text_target=fr_sentence)
inputs
```

```python out
{'input_ids': [47591, 12, 9842, 19634, 9, 0], 'attention_mask': [1, 1, 1, 1, 1, 1], 'labels': [577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]}
```

Как мы видим, в выходных данных содержатся входные идентификаторы, связанные с английским предложением, а идентификаторы, связанные с французским, хранятся в поле `labels`. Если вы забудете указать, что выполняете токенизацию меток, они будут токенизированы входным токенизатором, что в случае с моделью Marian не приведет ни к чему хорошему:

```python
wrong_targets = tokenizer(fr_sentence)
print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"]))
print(tokenizer.convert_ids_to_tokens(inputs["labels"]))
```

```python out
['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', '</s>']
['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', '</s>']
```

Как мы видим, при использовании английского токенизатора для предварительной обработки французского предложения получается гораздо больше токенов, поскольку токенизатор не знает ни одного французского слова (кроме тех, которые также встречаются в английском языке, например "discussion").

Поскольку `inputs` - это словарь с нашими обычными ключами (идентификаторы входных данных, маска внимания и т. д.), последним шагом будет определение функции предварительной обработки, которую мы будем применять к датасетам:

```python
max_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, text_target=targets, max_length=max_length, truncation=True
    )
    return model_inputs
```

Обратите внимание, что мы установили одинаковую максимальную длину для наших входов и выходов. Поскольку тексты, с которыми мы имеем дело, довольно короткие, мы используем 128.

<Tip>

💡 Если вы используете модель T5 (точнее, одну из контрольных точек `t5-xxx`), модель будет ожидать, что текстовые данные будут иметь префикс, указывающий на поставленную задачу, например `translate: English to French:`.

</Tip>

<Tip warning={true}>

⚠️ Мы не обращаем внимания на маску внимания целевых значений, так как модель не будет этого ожидать. Вместо этого метки, соответствующие токенам дополнения, должны быть заданы как `-100`, чтобы они игнорировались при вычислении потерь. Это будет сделано нашим коллатором данных позже, так как мы применяем динамическое дополнение (dynamic padding), но если вы используете дополнение (padding) здесь, вы должны адаптировать функцию предварительной обработки данных, чтобы установить все метки, соответствующие токену дополнения, в `-100`.

</Tip>

Теперь мы можем применить эту функцию предварительной обработки ко всем частям нашего датасета за один раз:

```py
tokenized_datasets = split_datasets.map(
    preprocess_function,
    batched=True,
    remove_columns=split_datasets["train"].column_names,
)
```

Теперь, когда данные прошли предварительную обработку, мы готовы дообучить нашу предварительно обученную модель!

{#if fw === 'pt'}

## Дообучение модели с помощью API `Trainer`[[fine-tuning-the-model-with-the-trainer-api]]

Фактический код, использующий `Trainer`, будет таким же, как и раньше, с одним лишь небольшим изменением: мы используем [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), который является подклассом `Trainer`, что позволит нам правильно работать с оценкой, используя метод `generate()` для предсказания выходов на основе входов. Мы рассмотрим это более подробно, когда будем говорить о вычислении метрик.

Прежде всего, нам нужна реальная модель, которую нужно дообучить. Мы воспользуемся обычным API `AutoModel`:

```py
from transformers import AutoModelForSeq2SeqLM

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

{:else}

## Дообучение модели с Keras[[fine-tuning-the-model-with-keras]]

Прежде всего, нам нужна актуальная модель, которую нужно дообучить. Мы воспользуемся обычным API `AutoModel`:

```py
from transformers import TFAutoModelForSeq2SeqLM

model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True)
```

<Tip warning={false}>

💡 Контрольная точка `Helsinki-NLP/opus-mt-en-fr` имеет только веса PyTorch, поэтому
вы получите ошибку, если попытаетесь загрузить модель без использования аргумента
`from_pt=True` в методе `from_pretrained()`. Когда вы указываете
`from_pt=True`, библиотека автоматически загрузит и преобразует
веса из PyTorch для вас. Как видите, очень просто переключаться между
фреймворками в 🤗 Transformers!

</Tip>

{/if}

Обратите внимание, что в этот раз мы используем модель, которая была обучена на задаче перевода и фактически уже может быть использована, поэтому нет предупреждения о пропущенных или новых инициализированных весах.

### Сопоставление данных[[data-collation]]

Для динамического батча нам понадобится коллатор данных для работы с дополнением (padding). Мы не можем просто использовать `DataCollatorWithPadding`, как в [Главе 3](../chapter3/1), потому что в этом случае будут заполнены только входы (идентификаторы входов, маска внимания и идентификаторы типов токенов). Наши метки также должны быть дополнены до максимальной длины, встречающейся в метках. И, как уже говорилось, добовляемое значение, используемое для дополнения меток, должно быть `-100`, а не добавочный токен токенизатора, чтобы эти добавочные значения игнорировались при вычислении потерь.

Все это делает [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Как и `DataCollatorWithPadding`, он принимает `tokenizer`, используемый для препроцессирования входных данных, а также `model`. Это связано с тем, что данный коллатор данных также будет отвечать за подготовку входных идентификаторов декодера, которые представляют собой сдвинутые версии меток со специальным токеном в начале. Поскольку для разных архитектур этот сдвиг выполняется по-разному, `DataCollatorForSeq2Seq` должен знать объект `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}

Чтобы протестировать его на нескольких примерах, мы просто вызываем его на списке примеров из нашего токинезированного обучающего набора:

```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'])
```

Мы можем проверить, что наши метки были дополнены до максимальной длины батча, с использованием `-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]])
```

Также мы можем взглянуть на идентификаторы входов декодера и убедиться, что они являются сдвинутыми версиями меток:

```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]])
```

Вот метки для первого и второго элементов в нашем датасете:

```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'}

Мы передадим этот `data_collator` в `Seq2SeqTrainer`. Далее давайте рассмотрим метрику.

{:else}

Теперь мы можем использовать этот `data_collator` для преобразования каждого из наших датасетов в `tf.data.Dataset`, готовый к обучению:

```python
tf_train_dataset = model.prepare_tf_dataset(
    tokenized_datasets["train"],
    collate_fn=data_collator,
    shuffle=True,
    batch_size=32,
)
tf_eval_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```

{/if}


### Метрики[[metrics]]

<Youtube id="M05L1DhFqcw"/>

{#if fw === 'pt'}

Свойство, которое `Seq2SeqTrainer` добавляет к своему суперклассу `Trainer`, - это возможность использовать метод `generate()` во время оценки или предсказания. Во время обучения модель будет использовать `decoder_input_ids` с маской внимания, гарантирующей, что она не будет использовать токены после токена, который пытается предсказать, чтобы ускорить обучение. Во время инференса мы не сможем использовать эти данные, так как у нас не будет меток, поэтому было бы неплохо оценить нашу модель с аналогичной настройкой.

Как мы видели в [Главе 1](../chapter1/6), декодер выполняет инференс, предсказывая токены один за другим - то, что за кулисами реализовано в 🤗 Transformers методом `generate()`. Тренер `Seq2SeqTrainer` позволит нам использовать этот метод для оценки, если мы установим `predict_with_generate=True`.

{/if}

Традиционной метрикой, используемой для перевода, является [BLEU score](https://en.wikipedia.org/wiki/BLEU), представленная в [статье 2002 года](https://aclanthology.org/P02-1040.pdf) Кишором Папинени и др. BLEU score оценивает, насколько близки переводы к своим меткам. Он не измеряет разборчивость или грамматическую правильность сгенерированных моделью результатов, но использует статистические правила, чтобы гарантировать, что все слова в сгенерированных результатах также встречаются в целевых. Кроме того, существуют правила, наказывающие повторы одних и тех же слов, если они не повторяются в целевых словах (чтобы модель не выводила предложения типа `"the the the the the the the"), и вывод предложений, которые короче, чем целевые (чтобы модель не выводила предложения типа `"the"`).

Один из недостатков BLEU заключается в том, что она предполагает, что текст уже прошел токенизацию, что затрудняет сравнение оценок между моделями, использующими различные токенизаторы. Поэтому сегодня для сравнения моделей перевода чаще всего используется метрика [SacreBLEU](https://github.com/mjpost/sacrebleu), которая устраняет этот недостаток (и другие) путем стандартизации этапа токенизации. Чтобы использовать эту метрику, сначала нужно установить библиотеку SacreBLEU:

```py
!pip install sacrebleu
```

Затем мы можем загрузить ее с помощью `evaluate.load()`, как мы это делали в [Главе 3](../chapter3/1):

```py
import evaluate

metric = evaluate.load("sacrebleu")
```

Эта метрика принимает тексты в качестве входных и целевых данных. Она рассчитана на использование нескольких приемлемых целей, так как часто существует несколько приемлемых переводов одного и того же предложения - в используемом нами датасете есть только один, но в NLP нередко встречаются датасеты, дающие несколько предложений в качестве меток. Таким образом, прогнозы должны представлять собой список предложений, а ссылки - список списков предложений.

Попробуем рассмотреть пример:

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

Это дает оценку BLEU 46,75, что довольно хорошо - для сравнения, оригинальная модель Transformer в статье ["Attention Is All You Need" paper](https://arxiv.org/pdf/1706.03762.pdf) достигла оценки BLEU 41,8 на аналогичной задаче перевода с английского на французский! (Более подробную информацию об отдельных метриках, таких как `counts` и `bp`, можно найти в репозитории [SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74)). С другой стороны, если мы попробуем использовать два плохих типа предсказаний (много повторов или слишком короткие), которые часто получаются в моделях перевода, мы получим довольно плохие оценки BLEU:

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

Оценка может варьироваться от 0 до 100, причем чем больше, тем лучше.

{#if fw === 'tf'}

Чтобы получить из результатов модели тексты, которые может использовать метрика, мы воспользуемся методом `tokenizer.batch_decode()`. Нам нужно только очистить все знаки `-100` в метках; токенизатор автоматически сделает то же самое для дополняющего токена. Определим функцию, которая берет нашу модель и датасет и вычисляет на нем метрики. Мы также используем трюк, который значительно повышает производительность, - компиляцию нашего кода генерации с помощью [XLA](https://www.tensorflow.org/xla), ускоренного компилятора линейной алгебры TensorFlow. XLA применяет различные оптимизации к графу вычислений модели, что приводит к значительному увеличению скорости и использования памяти. Как описано в блоге Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA лучше всего работает, когда наши входные формы не слишком сильно варьируются. Чтобы справиться с этим, мы разделим наши входные данные на части, кратные 128, и создадим новый датасет с коллатором с дополнением, а затем применим декоратор `@tf.function(jit_compile=True)` к нашей функции генерации, который обозначит всю функцию для компиляции с помощью XLA.

```py
import numpy as np
import tensorflow as tf
from tqdm import tqdm

generation_data_collator = DataCollatorForSeq2Seq(
    tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=128
)

tf_generate_dataset = model.prepare_tf_dataset(
    tokenized_datasets["validation"],
    collate_fn=generation_data_collator,
    shuffle=False,
    batch_size=8,
)


@tf.function(jit_compile=True)
def generate_with_xla(batch):
    return model.generate(
        input_ids=batch["input_ids"],
        attention_mask=batch["attention_mask"],
        max_new_tokens=128,
    )


def compute_metrics():
    all_preds = []
    all_labels = []

    for batch, labels in tqdm(tf_generate_dataset):
        predictions = generate_with_xla(batch)
        decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
        labels = 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}

Чтобы получить из результатов модели тексты, которые может использовать метрика, мы воспользуемся методом `tokenizer.batch_decode()`. Нам просто нужно очистить все значения `-100` в метках (токенизатор автоматически сделает то же самое для дополняющего токена):

```py
import numpy as np


def compute_metrics(eval_preds):
    preds, labels = eval_preds
    # В случае, если модель возвращает больше, чем предсказанные логиты
    if isinstance(preds, tuple):
        preds = preds[0]

    decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)

    # Заменяем -100 в метках, так как мы не можем их декодировать
    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]

    result = metric.compute(predictions=decoded_preds, references=decoded_labels)
    return {"bleu": result["score"]}
```

{/if}

Теперь, когда все готово, мы готовы дообучить нашу модель!


### Дообучение модели[[fine-tuning-the-model]]

Первый шаг - войти в Hugging Face, чтобы загрузить результаты в Model Hub. В блокноте есть удобная функция, которая поможет вам в этом:

```python
from huggingface_hub import notebook_login

notebook_login()
```

Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face.

Если вы работаете не в блокноте, просто введите следующую строку в терминале:

```bash
huggingface-cli login
```

{#if fw === 'tf'}

Прежде чем начать, давайте посмотрим, какие результаты мы получим от нашей модели без какого-либо обучения:

```py
print(compute_metrics())
```

```
{'bleu': 33.26983701454733}
```

Как только это будет сделано, мы сможем подготовить все необходимое для компиляции и обучения нашей модели. Обратите внимание на использование `tf.keras.mixed_precision.set_global_policy("mixed_float16")` - это укажет Keras обучать с использованием float16, что может дать значительное ускорение на GPU, поддерживающих эту функцию (Nvidia 20xx/V100 или новее).

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное
# на общее количество эпох. Обратите внимание, что tf_train_dataset здесь - это батч tf.data.Dataset,
# а не оригинальный датасет Hugging Face, поэтому его len() уже равен 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)

# Обучение со смешанной точностью float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

Далее мы определяем обратный вызов `PushToHubCallback` для загрузки нашей модели в Hub во время обучения, как мы видели в [разделе 2](../chapter7/2), а затем мы просто подгоняем модель с помощью этого обратного вызова:

```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,
)
```

Обратите внимание, что с помощью аргумента `hub_model_id` можно указать имя репозитория, в который вы хотите отправить модель (в частности, этот аргумент нужно использовать, чтобы отправить модель в организацию). Например, когда мы отправили модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` в `Seq2SeqTrainingArguments`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, поэтому здесь это будет `"sgugger/marian-finetuned-kde4-en-to-fr"` (это модель, на которую мы ссылались в начале этого раздела).

<Tip>

💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном репозитория, в который вы хотите выполнить push. Если это не так, вы получите ошибку при вызове `model.fit()` и должны будете задать новое имя.

</Tip>

Наконец, давайте посмотрим, как выглядят наши метрики после завершения обучения:

```py
print(compute_metrics())
```

```
{'bleu': 57.334066271545865}
```

На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи перевода - поздравляем!

{:else}

Когда это сделано, мы можем определить наши `Seq2SeqTrainingArguments`. Как и в случае с `Trainer`, мы используем подкласс `TrainingArguments`, который содержит еще несколько дополнительных полей:

```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,
)
```

Помимо обычных гиперпараметров (таких как скорость обучения, количество эпох, размер батча и некоторое затухание веса), здесь есть несколько отличий по сравнению с тем, что мы видели в предыдущих разделах:

- Мы не задаем никаких регулярных оценок, так как оценка занимает много времени; мы просто оценим нашу модель один раз до обучения и после.
- Мы установили `fp16=True`, что ускоряет обучение на современных GPU.
- Мы устанавливаем `predict_with_generate=True`, как обсуждалось выше.
- Мы используем `push_to_hub=True` для загрузки модели в Hub в конце каждой эпохи.

Обратите внимание, что в аргументе `hub_model_id` можно указать полное имя розитория, в который вы хотите отправить модель (в частности, этот аргумент нужно использовать, чтобы отправить модель в организацию). Например, когда мы отправили модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` в `Seq2SeqTrainingArguments`. По умолчанию используемый розиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, поэтому в нашем случае это будет `"sgugger/marian-finetuned-kde4-en-to-fr"` (это модель, на которую мы ссылались в начале этого раздела).

<Tip>

💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном того розитория, в который вы хотите выполнить push. Если это не так, вы получите ошибку при определении вашего `Seq2SeqTrainer` и должны будете задать новое имя.

</Tip>


Наконец, мы просто передаем все в `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,
)
```

Перед обучением мы сначала посмотрим, какую оценку получила наша модель, чтобы проверить, не ухудшаем ли мы результаты, дообучив ее. Выполнение этой команды займет некоторое время, поэтому во время ее выполнения можно выпить кофе:

```python
trainer.evaluate(max_length=max_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}
```

Оценка BLEU в 39 баллов не так уж плохо, что говорит о том, что наша модель уже хорошо справляется с переводом английских предложений на французский.

Далее следует обучение, которое также займет некоторое время:

```python
trainer.train()
```

Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается на Hub в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине.

После завершения обучения мы снова оцениваем нашу модель - надеемся, мы увидим некоторое улучшение в показателе BLEU!

```py
trainer.evaluate(max_length=max_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}
```

Это улучшение почти на 14 пунктов, что очень хорошо.

Наконец, мы используем метод `push_to_hub()`, чтобы убедиться, что загружена последняя версия модели. Тренер также создает карту модели со всеми результатами оценки и загружает ее. Эта карта модели содержит метаданные, которые помогают хабу моделей выбрать виджет для демонстрации инференса. Обычно ничего не нужно указывать, так как он сам определяет нужный виджет по классу модели, но в данном случае один и тот же класс модели может быть использован для всех видов задач, связанных с последовательностями, поэтому мы указываем, что это модель перевода:

```py
trainer.push_to_hub(tags="translation", commit_message="Training complete")
```

Эта команда возвращает URL-адрес только что выполненного коммита, если вы хотите его просмотреть:

```python out
'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3'
```

На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи перевода - поздравляем!

Если вы хотите более глубоко погрузиться в цикл обучения, мы покажем вам, как сделать то же самое с помощью 🤗 Accelerate.

{/if}

{#if fw === 'pt'}

## Индивидуальный цикл обучения[[a-custom-training-loop]]

Теперь давайте рассмотрим полный цикл обучения, чтобы вы могли легко настроить нужные вам части. Он будет выглядеть примерно так же, как мы делали это в [Главе 2](../chapter7/2) и [Главе 3](../chapter3/4).

### Подготовка всего к обучению[[preparing-everything-for-training]]

Вы уже видели все это несколько раз, поэтому мы пройдемся по коду довольно быстро. Сначала мы создадим `DataLoader` из наших датасетов, после чего установим для датасетов формат `"torch"`, чтобы получить тензоры 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
)
```

Затем мы повторно инстанцируем нашу модель, чтобы убедиться, что мы не продолжаем дообучение предыдущей модели, а начинаем с предварительно обученной модели:

```py
model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)
```

Тогда нам понадобится оптимизатор:

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Когда у нас есть все эти объекты, мы можем отправить их в метод `accelerator.prepare()`. Помните, что если вы хотите обучаться на TPU в блокноте Colab, вам нужно будет перенести весь этот код в функцию обучения, которая не должна выполнять ни одной ячейки, инстанцирующей `Accelerator`.

```py
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

Теперь, когда мы отправили наш `train_dataloader` в `accelerator.prepare()`, мы можем использовать его длину для вычисления количества шагов обучения. Помните, что это всегда нужно делать после подготовки загрузчика данных, так как этот метод изменит длину `DataLoader`. Мы используем классический линейный планировшик скорости обучения до 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,
)
```

Наконец, чтобы отправить нашу модель в Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала войдите в Hub Hugging Face, если вы еще не авторизованы. Мы определим имя розитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свой собственный выбор; оно просто должно содержать ваше имя пользователя, что и делает функция `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'
```

Затем мы можем клонировать этот розиторий в локальную папку. Если она уже существует, эта локальная папка должна быть клоном того розитория, с которым мы работаем:

```py
output_dir = "marian-finetuned-kde4-en-to-fr-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи.

### Цикл обучения[[training-loop]]

Теперь мы готовы написать полный цикл обучения. Чтобы упростить его оценочную часть, мы определяем эту функцию `postprocess()`, которая принимает прогнозы и метки и преобразует их в списки строк, которые будет ожидать наш объект `metric`:

```py
def postprocess(predictions, labels):
    predictions = predictions.cpu().numpy()
    labels = labels.cpu().numpy()

    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)

    # Заменим -100 в метках, так как мы не можем их декодировать.
    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]
    return decoded_preds, decoded_labels
```

Цикл обучения очень похож на циклы в [разделе 2](../chapter7/2) и [главе 3](../chapter3/1), с некоторыми отличиями в части оценки - так что давайте сосредоточимся на этом!

Первое, что следует отметить, это то, что мы используем метод `generate()` для вычисления прогнозов, но это метод нашей базовой модели, а не обернутой модели 🤗 Accelerate, созданной в методе `prepare()`. Поэтому мы сначала развернем модель, а затем вызовем этот метод.

Второй момент заключается в том, что, как и в случае с [классификацией токенов](../chapter7/2), два процесса могут дополнить входы и метки до разных форм, поэтому мы используем `accelerator.pad_across_processes()`, чтобы сделать прогнозы и метки одинаковыми по форме перед вызовом метода `gather()`. Если мы этого не сделаем, оценка либо выдаст ошибку, либо зависнет навсегда.

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Обучение
    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)

    # Оценка
    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"]

        ## Необходимо дополнить прогнозы и метки
        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}")

    # Сохранение и загрузка
    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
```

После этого у вас должна получиться модель, результаты которой будут очень похожи на модель, обученную с помощью `Seq2SeqTrainer`. Вы можете проверить модель, которую мы обучили с помощью этого кода, на [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). А если вы хотите протестировать какие-либо изменения в цикле обучения, вы можете реализовать их напрямую, отредактировав код, показанный выше!

{/if}

## Использование дообученной модели[[using-the-fine-tuned-model]]

Мы уже показали вам, как можно использовать модель, которую мы дообучили на Model Hub, с помощью виджета инференса. Чтобы использовать ее локально в `pipeline`, нам просто нужно указать соответствующий идентификатор модели:

```py
from transformers import pipeline

# Замените это на свою собственную контрольную точку
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'}]
```

Как и ожидалось, наша предварительно обученная модель адаптировала свои знания к корпусу, на котором мы ее дообучили, и вместо того, чтобы оставить английское слово "threads" в покое, она теперь переводит его на официальный французский вариант. То же самое относится и к слову " 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."}]
```

Еще один отличный пример доменной адаптации!

<Tip>

✏️ **Попробуйте!** Что возвращает модель для примера со словом "email", который вы определили ранее?

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/4.mdx" />

### Ответы на вопросы[[question-answering]]
https://huggingface.co/learn/course/ru/chapter7/7.md

# Ответы на вопросы[[question-answering]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section7_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section7_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section7_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section7_tf.ipynb"},
]} />

{/if}

Пришло время взглянуть на ответы на вопросы! Эта задача имеет множество разновидностей, но та, на которой мы сосредоточимся в этом разделе, называется *экстрактивным* ответом на вопросы. Она включает в себя постановку вопросов о документе и определение ответов в виде _участков текста_ в самом документе.

<Youtube id="ajPx5LwJD-I"/>

Мы проведем дообучение BERT-модели на датасете [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), состоящем из вопросов, заданных краудворкерами по набору статей Википедии. В результате мы получим модель, способную вычислять прогнозы, подобные этому:

<iframe src="https://course-demos-bert-finetuned-squad.hf.space" frameBorder="0" height="450" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

На самом деле это демонстрация модели, которая была обучена и загружена на Hub с помощью кода, показанного в этом разделе. Вы можете найти ее и перепроверить прогнозы [здесь](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).

<Tip>

💡 Модели, основанные только на энкодере, такие как BERT, как правило, отлично справляются с извлечением ответов на фактоидные вопросы типа "Кто изобрел архитектуру трансформера?", но плохо справляются с открытыми вопросами типа "Почему небо голубое?". В таких сложных случаях для синтеза информации обычно используются модели энкодеров-декодеров, такие как T5 и BART, что очень похоже на [сумризацию текста](/course/chapter7/5). Если вам интересен этот тип *генеративных* ответов на вопросы, рекомендуем ознакомиться с нашим [демо](https://yjernite.github.io/lfqa.html) основанным на [датасете ELI5](https://huggingface.co/datasets/eli5).

</Tip>

## Подготовка данных[[preparing-the-data]]

В качестве академического бенчмарка для экстрактивных ответов на вопросы чаще всего используется датасет [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), поэтому мы будем использовать именно его. Существует также более сложный датасет [SQuAD v2](https://huggingface.co/datasets/squad_v2), который включает вопросы, не имеющие ответа. Если ваш собственный датасет содержит столбец контекстов, столбец вопросов и столбец ответов, вы сможете адаптировать описанные ниже шаги.

### Датасет SQuAD[[the-squad-dataset]]

Как обычно, мы можем загрузить и кэшировать датасет всего за один шаг благодаря функции `load_dataset()`:

```py
from datasets import load_dataset

raw_datasets = load_dataset("squad")
```

Мы можем взглянуть на этот объект, чтобы узнать больше о датасете 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
    })
})
```

Похоже, у нас есть все необходимое в полях `context`, `question` и `answers`, так что давайте выведем их для первого элемента нашего обучающего набора:

```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.'
Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?'
Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]}
```

Поля `context` и `question` очень просты в использовании. С полем `answers` немного сложнее, поскольку оно представляет собой словарь с двумя полями, которые оба являются списками. Именно такой формат будет ожидать метрика `squad` при оценке; если вы используете свои собственные данные, вам не обязательно беспокоиться о том, чтобы привести ответы к такому же формату. Поле `text` довольно очевидно, а поле `answer_start` содержит индекс начального символа каждого ответа в контексте.

Во время обучения существует только один возможный ответ. Мы можем перепроверить это с помощью метода `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
})
```

Для оценки, однако, существует несколько возможных ответов для каждого примера, которые могут быть одинаковыми или разными:

```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]}
```

Мы не будем углубляться в сценарий оценки, поскольку все это будет завернуто в метрику 🤗 Datasets для нас, но кратко суть в том, что некоторые вопросы имеют несколько возможных ответов, и этот сценарий будет сравнивать спрогнозированный ответ со всеми допустимыми ответами и выбирать лучший результат. Если мы посмотрим, например, на выборку с индексом 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.'
'Where did Super Bowl 50 take place?'
```

мы можем увидеть, что ответ действительно может быть одним из трех возможных вариантов, которые мы видели ранее.

### Подготовка обучающих данных[[processing-the-training-data]]

<Youtube id="qgaM0weJHpA"/>

Начнем с предварительной подготовки обучающих данных. Самое сложное - сгенерировать метки для ответа на вопрос, которые будут представлять собой начальную и конечную позиции токенов, соответствующих ответу в контексте.

Но не будем забегать вперед. Сначала нам нужно преобразовать текст во входных данных в идентификаторы, которые модель сможет понять, используя токенизатор:

```py
from transformers import AutoTokenizer

model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Как упоминалось ранее, мы будем проводить дообучение модели BERT, но вы можете использовать любой другой тип модели, если в ней реализован быстрый токенизатор. Вы можете увидеть все архитектуры с быстрой версией в [этой большой таблице](https://huggingface.co/transformers/#supported-frameworks), а чтобы проверить, что используемый вами объект `tokenizer` действительно поддерживается 🤗 Tokenizers, вы можете посмотреть на его атрибут `is_fast`:

```py
tokenizer.is_fast
```

```python out
True
```

Мы можем передать нашему токенизатору вопрос и контекст вместе, и он правильно вставит специальные токены, чтобы сформировать предложение, подобное этому:

```
[CLS] question [SEP] context [SEP]
```

Давайте перепроверим:

```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]'
```

В качестве меток будут использоваться индексы токенов, начинающих и заканчивающих ответ, а задача модели - предсказать один начальный и конечный логит для каждого токена на входе, при этом теоретические метки будут выглядеть следующим образом:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/qa_labels.svg" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/qa_labels-dark.svg" alt="One-hot encoded labels for question answering."/>
</div>

В данном случае контекст не слишком длинный, но некоторые примеры в датасете имеют очень длинные контексты, которые превысят установленную нами максимальную длину (которая в данном случае равна 384). Как мы видели в [Главе 6](../chapter6/4), когда изучали внутреннее устройство конвейера `question-answering`, мы будем работать с длинными контекстами, создавая несколько обучающих признаков из одной выборки нашего датасета, со скользящим окном между ними.

Чтобы увидеть, как это работает на текущем примере, мы можем ограничить длину до 100 и использовать скользящее окно из 50 токенов. В качестве напоминания мы используем:

- `max_length` для установки максимальной длины (здесь 100)
- `truncation="only_second"` для усечения контекста (который находится во второй позиции), когда вопрос с его контекстом слишком длинный
- `stride` для задания количества перекрывающихся токенов между двумя последовательными фрагментами (здесь 50)
- `return_overflowing_tokens=True`, чтобы сообщить токенизатору, что нам нужны переполненные токены (overflowing tokens)

```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] 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] 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] 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]'
```

Как мы можем видеть, наш пример был разбит на четыре входа, каждый из которых содержит вопрос и часть контекста. Обратите внимание, что ответ на вопрос ("Bernadette Soubirous") появляется только в третьем и последнем входе, поэтому, работая с длинными контекстами таким образом, мы создадим несколько обучающих примеров, в которых ответ не будет включен в контекст. Для этих примеров метками будут `start_position = end_position = 0` (таким образом мы предсказываем токен `[CLS]`). Мы также зададим эти метки в неудачном случае, когда ответ был усечен, так что у нас есть только его начало (или конец). Для примеров, где ответ полностью находится в контексте, метками будут индекс токена, с которого начинается ответ, и индекс токена, на котором ответ заканчивается.

Датасет предоставляет нам начальный символ ответа в контексте, а прибавив к нему длину ответа, мы можем найти конечный символ в контексте. Чтобы сопоставить их с индексами токенов, нам нужно использовать сопоставление смещений, которое мы изучали в [Главе 6](../chapter6/4). Мы можем настроить наш токенизатор на их возврат, передав `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'])
```

Как мы видим, нам возвращаются обычные идентификаторы входа, идентификаторы типов токенов и маска внимания, а также необходимое нам сопоставление смещений и дополнительный ключ `overflow_to_sample_mapping`. Соответствующее значение мы будем использовать при токенизации нескольких текстов одновременно (что мы и должны делать, чтобы извлечь выгоду из того, что наш токенизатор основан на Rust). Поскольку один образец может давать несколько признаков, он сопоставляет каждый признак с примером, из которого он произошел. Поскольку здесь мы токенизировали только один пример, мы получим список `0`:

```py
inputs["overflow_to_sample_mapping"]
```

```python out
[0, 0, 0, 0]
```

Но если мы проведем токенизацию большего количества примеров, это станет более полезным:

```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].'
```

Как мы видим, первые три примера (с индексами 2, 3 и 4 в обучающем наборе) дали по четыре признака, а последний пример (с индексом 5 в обучающем наборе) - 7 признаков.

Эта информация будет полезна для сопоставления каждого полученного признака с соответствующей меткой. Как уже упоминалось ранее, этими метками являются:

- `(0, 0)`, если ответ не находится в соответствующей области контекста
- `(start_position, end_position)`, если ответ находится в соответствующей области контекста, причем `start_position` - это индекс токена (во входных идентификаторах) в начале ответа, а `end_position` - индекс токена (во входных идентификаторах) в конце ответа

Чтобы определить, какой из этих случаев имеет место, и, если нужно, позиции токенов, мы сначала находим индексы, с которых начинается и заканчивается контекст во входных идентификаторах. Для этого мы могли бы использовать идентификаторы типов токенов, но поскольку они не обязательно существуют для всех моделей (например, DistilBERT их не требует), вместо этого мы воспользуемся методом `sequence_ids()` из `BatchEncoding`, который возвращает наш токенизатор. 

Получив индексы токенов, мы смотрим на соответствующие смещения, которые представляют собой кортежи из двух целых чисел, обозначающих промежуток символов внутри исходного контекста. Таким образом, мы можем определить, начинается ли фрагмент контекста в этом признаке после ответа или заканчивается до начала ответа (в этом случае метка будет `(0, 0)`). Если это не так, мы зацикливаемся, чтобы найти первый и последний токен ответа:

```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)

    # Найдём начало и конец контекста
    idx = 0
    while sequence_ids[idx] != 1:
        idx += 1
    context_start = idx
    while sequence_ids[idx] == 1:
        idx += 1
    context_end = idx - 1

    # Если ответ не полностью находится внутри контекста, меткой будет (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:
        # В противном случае это начальная и конечная позиции токенов
        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])
```

Давайте посмотрим на несколько результатов, чтобы убедиться в правильности нашего подхода. Для первого признака мы находим `(83, 85)` в качестве меток, поэтому давайте сравним теоретический ответ с декодированным диапазоном лексем с 83 по 85 (включительно):

```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'
```

Итак, это совпадение! Теперь проверим индекс 4, где мы установили метки на `(0, 0)`, что означает, что ответ не находится в фрагменте контекста этого признака:

```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]'
```

Действительно, мы не видим ответа в контексте.

<Tip>

✏️ **Ваша очередь!** При использовании архитектуры XLNet дополнение применяется слева, а вопрос и контекст меняются местами. Адаптируйте весь код, который мы только что рассмотрели, к архитектуре XLNet (и добавьте `padding=True`). Имейте в виду, что токен `[CLS]` может не находиться в позиции 0 при использовании дополнения.

</Tip>

Теперь, когда мы шаг за шагом разобрались с предварительной обработкой обучающих данных, мы можем сгруппировать их в функцию, которую будем применять ко всему датасету. Мы дополним каждый признак до максимальной длины, которую мы задали, поскольку большинство контекстов будут длинными (и соответствующие образцы будут разбиты на несколько признаков), поэтому применение динамического дополнения здесь не имеет реальной пользы:

```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)

        # Найдём начало и конец контекста
        idx = 0
        while sequence_ids[idx] != 1:
            idx += 1
        context_start = idx
        while sequence_ids[idx] == 1:
            idx += 1
        context_end = idx - 1

        # Если ответ не полностью находится внутри контекста, меткой будет (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:
            # В противном случае это начальная и конечная позиции токенов
            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
```

Обратите внимание, что мы определили две константы для определения максимальной длины и длины скользящего окна, а также добавили немного очистки перед токенизацией: некоторые вопросы в датасете SQuAD имеют лишние пробелы в начале и конце, которые ничего не добавляют (и занимают место при токенизации, если вы используете модель вроде RoBERTa), поэтому мы удалили эти лишние пробелы.

Чтобы применить эту функцию ко всему обучающему набору, мы используем метод `Dataset.map()` с флагом `batched=True`. Это необходимо, так как мы изменяем длину датасета (поскольку один пример может давать несколько обучающих признаков):

```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)
```

Как мы видим, предварительная обработка добавила около 1 000 признаков. Теперь наш обучающий набор готов к использованию - давайте займемся предварительной обработкой валидационного набора!

### Подготовка валидационных данных[[processing-the-validation-data]]

Предварительная обработка валидационных данных будет немного проще, поскольку нам не нужно генерировать метки (если только мы не хотим вычислять потери на валидации, но это число не поможет нам понять, насколько хороша модель). Настоящей радостью будет интерпретация прогнозов модели в диапазонах исходного контекста. Для этого нам нужно хранить как сопоставления смещений, так и способ сопоставления каждого созданного признака с оригинальным примером, из которого он взят. Поскольку в исходном датасете есть столбец ID, мы будем использовать этот ID.

Единственное, что мы добавим сюда, - это немного почистим сопоставления смещений. Они будут содержать смещения для вопроса и контекста, но на этапе постобработки у нас не будет возможности узнать, какая часть входных идентификаторов соответствует контексту, а какая - вопросу (метод `sequence_ids()`, который мы использовали, доступен только для выхода токенизатора). Поэтому мы установим смещения, соответствующие вопросу, в `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
```

Мы можем применить эту функцию ко всему валидационому датасету, как и раньше:

```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)
```

В данном случае мы добавили всего пару сотен примеров, поэтому контексты в валидационном датасете немного короче.

Теперь, когда мы предварительно обработали все данные, можно приступать к обучению.

{#if fw === 'pt'}

## Дообучение модели с API `Trainer`[[fine-tuning-the-model-with-the-trainer-api]]

Код обучения для этого примера будет очень похож на код из предыдущих разделов -- самым сложным будет написание функции `compute_metrics()`. Поскольку мы дополнили все примеры до максимальной длины, которую мы задали, нет никакого коллатора данных, поэтому вычисление метрики - это единственное, о чем нам нужно беспокоиться. Самым сложным будет постобработка прогнозов модели в отрезки текста оригинальных примеров; как только мы это сделаем, метрика из библиотеки 🤗 Datasets сделает за нас большую часть работы.

{:else}

## Дообучение модели с Keras[[fine-tuning-the-model-with-keras]]

Код обучения для этого примера будет очень похож на код в предыдущих разделах, но вычисление метрик будет уникальным. Поскольку мы дополнили все примеры до максимальной длины, которую мы задали, нет коллатора данных, который нужно определить, поэтому вычисление метрики - это единственное, о чем нам нужно беспокоиться. Самое сложное - это постобработка прогнозов модели в отрезки текста в исходных примерах; как только мы это сделаем, метрика из библиотеки 🤗 Datasets сделает за нас большую часть работы.

{/if}

### Постобработка[[post-processing]]

{#if fw === 'pt'}

<Youtube id="BNy08iIWVJM"/>

{:else}

<Youtube id="VN67ZpN33Ss"/>

{/if}

Модель выведет логиты для начальной и конечной позиций ответа во входных идентификаторах, как мы видели при изучении [конвейера `question-answering`](../chapter6/3b). Шаг постобработки будет аналогичен тому, что мы делали там, так что вот краткое напоминание о том, что мы делали:

- Мы маскировали начальные и конечные логиты, соответствующие токенам вне контекста.
- Затем мы преобразовали начальные и конечные логиты в вероятности с помощью softmax.
- Каждой паре `(start_token, end_token)` мы присваивали оценку, взяв произведение двух соответствующих вероятностей.
- Мы искали пару с максимальной оценкой, которая давала правильный ответ (например, `start_token` меньше `end_token`).

В данном случае мы немного изменим этот процесс, поскольку нам не нужно вычислять фактические оценки (только спрогнозированный ответ). Это означает, что мы можем пропустить шаг softmax. Чтобы ускорить процесс, мы также не будем оценивать все возможные пары `(start_token, end_token)`, а только те, которые соответствуют наибольшим `n_best` логитам (при `n_best=20`). Так как мы пропустим softmax, эти оценки будут оценками логитов, и будут получены путем взятия суммы начального и конечного логитов (вместо произведения, по правилу \\(\log(ab) = \log(a) + \log(b)\\)).

Чтобы продемонстрировать все это, нам понадобятся некоторые прогнозы. Поскольку мы еще не обучили нашу модель, мы будем использовать модель по умолчанию для конвейера QA, чтобы сгенерировать несколько прогнозов на небольшой части набора для валидации. Мы можем использовать ту же функцию обработки, что и раньше; поскольку она опирается на глобальную константу `tokenizer`, нам просто нужно изменить этот объект на токенизатор модели, которую мы хотим временно использовать:

```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,
)
```

Теперь, когда препроцессинг завершен, мы меняем токенизатор обратно на тот, который был выбран изначально:

```python
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Затем мы удаляем из нашего `eval_set` столбцы, которые не ожидает модель, создаем батч со всей этой небольшой валидацией и пропускаем его через модель. Если доступен GPU, мы используем его для ускорения работы:

{#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)
```

Поскольку `Trainer` будет возвращать нам прогнозы в виде массивов NumPy, мы берем начальный и конечный логиты и конвертируем их в этот формат:

```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)
```

Для облегчения экспериментов преобразуем выходные данные в массивы NumPy:

```python
start_logits = outputs.start_logits.numpy()
end_logits = outputs.end_logits.numpy()
```

{/if}

Теперь нам нужно найти спрогнозированный ответ для каждого примера в `small_eval_set`. Один пример может быть разбит на несколько признаков в `eval_set`, поэтому первым шагом будет сопоставление каждого примера в `small_eval_set` с соответствующими признаками в `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)
```

Имея это на руках, мы можем приступить к работе, итерируясь по всем примерам и, для каждого примера, по всем ассоциированным с ним признакам. Как мы уже говорили, мы рассмотрим оценки логитов для `n_best` начальных логитов и конечных логитов, исключая позиции, которые дают:

- Ответ, который не вписывается в контекст
- Ответ с отрицательной длиной
- Слишком длинный ответ (мы ограничиваем возможности по `max_answer_length=30`).

После того как мы получили все возможные ответы для одного примера, мы просто выбираем тот, который имеет лучшую оценку логита:

```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:
                # Пропускаем ответы, которые не полностью соответствуют контексту
                if offsets[start_index] is None or offsets[end_index] is None:
                    continue
                # Пропускайте ответы, длина которых либо < 0, либо > 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"]})
```

Окончательный формат спрогнозированных ответов - это тот, который ожидает метрика, которую мы будем использовать. Как обычно, мы можем загрузить ее с помощью библиотеки 🤗 Evaluate:

```python
import evaluate

metric = evaluate.load("squad")
```

Эта метрика ожидает прогнозируемые ответы в формате, который мы видели выше (список словарей с одним ключом для идентификатора примера и одним ключом для прогнозируемого текста), и теоретические ответы в формате ниже (список словарей с одним ключом для идентификатора примера и одним ключом для возможных ответов):

```python
theoretical_answers = [
    {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set
]
```

Теперь мы можем убедиться, что получаем разумные результаты, посмотрев на первый элемент обоих списков:

```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]}}
```

Не так уж плохо! Теперь давайте посмотрим на оценку, которую дает нам метрика:

```python
metric.compute(predictions=predicted_answers, references=theoretical_answers)
```

```python out
{'exact_match': 83.0, 'f1': 88.25}
```

Опять же, это довольно хорошо, если учесть, что согласно [его статье](https://arxiv.org/abs/1910.01108v2) DistilBERT с дообучением на SQuAD получает 79,1 и 86,9 для этих оценок на всем датасете.

{#if fw === 'pt'}

Теперь давайте поместим все, что мы только что сделали, в функцию `compute_metrics()`, которую мы будем использовать в `Trainer`. Обычно функция `compute_metrics()` получает только кортеж `eval_preds` с логитами и метками. Здесь нам понадобится немного больше, так как мы должны искать в датасете признаков смещения и в датасете примеров исходные контексты, поэтому мы не сможем использовать эту функцию для получения обычных результатов оценки во время обучения. Мы будем использовать ее только в конце обучения для проверки результатов.

Функция `compute_metrics()` группирует те же шаги, что и до этого, только добавляется небольшая проверка на случай, если мы не найдем ни одного правильного ответа (в этом случае мы прогнозируем пустую строку).

{:else}

Теперь давайте поместим все, что мы только что сделали, в функцию `compute_metrics()`, которую мы будем использовать после обучения нашей модели. Нам нужно будет передать несколько больше, чем просто выходные логиты, поскольку мы должны искать в датасете признаков смещение, а в датасете примеров - исходные контексты:

{/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 = []

        # Итерируемся по всем признакам, ассоциированным с этим примером
        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:
                    # Пропускаем ответы, которые не полностью соответствуют контексту
                    if offsets[start_index] is None or offsets[end_index] is None:
                        continue
                    # Пропускайте ответы, длина которых либо < 0, либо > 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)

        # Выбираем ответ с лучшей оценкой
        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)
```

Мы можем проверить ее работу на наших прогнозах:

```python
compute_metrics(start_logits, end_logits, eval_set, small_eval_set)
```

```python out
{'exact_match': 83.0, 'f1': 88.25}
```

Выглядит отлично! Теперь давайте используем это для дообучения нашей модели.

### Дообучение модели[[fine-tuning-the-model]]

{#if fw === 'pt'}

Теперь мы готовы к обучению нашей модели. Давайте сначала создадим ее, используя класс `AutoModelForQuestionAnswering`, как и раньше:

```python
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

{:else}

Теперь мы готовы к обучению нашей модели. Давайте сначала создадим ее, используя класс `TFAutoModelForQuestionAnswering`, как и раньше:

```python
model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

{/if}

Как обычно, мы получаем предупреждение о том, что некоторые веса не используются (веса головы предварительного обучения), а другие инициализируются случайным образом (веса головы ответов на вопросы). Вы уже должны были привыкнуть к этому, но это означает, что модель еще не готова к использованию и нуждается в дообучении - хорошо, что мы сейчас этим займемся!

Чтобы отправить нашу модель на Hub, нам нужно войти в Hugging Face. Если вы выполняете этот код в блокноте, вы можете сделать это с помощью следующей служебной функции, которая отображает виджет, где вы можете ввести свои учетные данные:

```python
from huggingface_hub import notebook_login

notebook_login()
```

Если вы работаете не в блокноте, просто введите следующую строку в терминале:

```bash
huggingface-cli login
```

{#if fw === 'pt'}

Когда это сделано, мы можем определить наши `TrainingArguments`. Как мы уже говорили, когда определяли нашу функцию для вычисления метрики, мы не сможем сделать обычный цикл оценки из-за сигнатуры функции `compute_metrics()`. Мы могли бы написать собственный подкласс `Trainer` для этого (такой подход можно найти в [примере скрипта ответа на вопросы](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), но это слишком длинно для данного раздела. Вместо этого мы будем оценивать модель только в конце обучения, а как проводить регулярную оценку, покажем ниже в разделе "Пользовательский цикл обучения".

Именно здесь API `Trainer` показывает свои ограничения, а библиотека 🤗 Accelerate блистает: настройка класса под конкретный случай использования может быть болезненной, но настройка полностью открытого цикла обучения - это просто.

Let's take a look at our `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,
)
```

Большинство из них мы уже видели: мы задаем некоторые гиперпараметры (например, скорость обучения, количество эпох обучения и затухание веса) и указываем, что хотим сохранять модель в конце каждой эпохи, пропускать оценку и загружать результаты в Model Hub. Мы также включаем обучение со смешанной точностью с `fp16=True`, так как это может значительно ускорить обучение на современных GPU.

{:else}

Теперь все готово, и мы можем создать наш TF датасет. На этот раз мы можем использовать простой коллатор данных по умолчанию:

```python
from transformers import DefaultDataCollator

data_collator = DefaultDataCollator(return_tensors="tf")
```

А теперь создадим датасет, как обычно.

```python
tf_train_dataset = model.prepare_tf_dataset(
    train_dataset,
    collate_fn=data_collator,
    shuffle=True,
    batch_size=16,
)
tf_eval_dataset = model.prepare_tf_dataset(
    validation_dataset,
    collate_fn=data_collator,
    shuffle=False,
    batch_size=16,
)
```

Далее мы задаем гиперпараметры обучения и компилируем нашу модель:

```python
from transformers import create_optimizer
from transformers.keras_callbacks import PushToHubCallback
import tensorflow as tf

# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное
# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset,
# а не оригинальный датасет Hugging Face, поэтому его len() уже равен 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)

# Обучение со смешанной точностью float16
tf.keras.mixed_precision.set_global_policy("mixed_float16")
```

Наконец, мы готовы к обучению с помощью `model.fit()`. Мы используем `PushToHubCallback` для загрузки модели в Hub после каждой эпохи.

{/if}

По умолчанию используемый розиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным выходным каталогом, так что в нашем случае он будет находиться в `"sgugger/bert-finetuned-squad"`. Мы можем переопределить это, передав `hub_model_id`; например, чтобы отправить модель в организацию `huggingface_course`, мы использовали `hub_model_id="huggingface_course/bert-finetuned-squad"` (это модель, на которую мы ссылались в начале этого раздела).

{#if fw === 'pt'}

<Tip>

💡 Если используемый вами выходной каталог существует, он должен быть локальным клоном того розитория, в который вы хотите отправлять данные (поэтому задайте новое имя, если вы получите ошибку при определении `Trainer`).

</Tip>

Наконец, мы просто передаем все в класс `Trainer` и запускаем обучение:

```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)

# Мы собираемся провести валидацию после обучения, поэтому не нужно проводить валидацию в середине обучения
model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs)
```

{/if}

Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается на Hub в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине. Все обучение занимает некоторое время (чуть больше часа на Titan RTX), поэтому во время его проведения вы можете выпить кофе или перечитать те части курса, которые показались вам более сложными. Также обратите внимание, что как только закончится первая эпоха, вы увидите, что некоторые веса загружены в Hub, и сможете начать играть с вашей моделью на ее странице.

{#if fw === 'pt'}

Когда обучение завершено, мы можем наконец оценить нашу модель (и помолиться, что не потратили все это время вычислений впустую). Метод `predict()` функции `Trainer` вернет кортеж, где первыми элементами будут предсказания модели (здесь пара с начальным и конечным логитами). Мы отправляем его в нашу функцию `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}

Когда обучение завершено, мы можем наконец оценить нашу модель (и помолиться, что не потратили все это время вычислений впустую). Метод `predict()` функции `Trainer` вернет кортеж, где первыми элементами будут предсказания модели (здесь пара с начальным и конечным логитами). Мы отправляем его в нашу функцию `compute_metrics()`:

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

Отлично! Для сравнения, базовые показатели, указанные в статье BERT для этой модели, составляют 80,8 и 88,5, так что мы как раз там, где должны быть.

{#if fw === 'pt'}

Наконец, мы используем метод `push_to_hub()`, чтобы убедиться, что мы загрузили последнюю версию модели:

```py
trainer.push_to_hub(commit_message="Training complete")
```

Это возвращает URL только что выполненного коммита, если вы хотите его просмотреть:

```python out
'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68'
```

`Trainer` также создает черновик карточки модели со всеми результатами оценки и загружает ее.

{/if}

На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать модель и поделиться ею с друзьями, семьей и любимыми питомцами. Вы успешно провели дообучение модели для задачи ответа на вопрос - поздравляем!

<Tip>

✏️ **Ваша очередь!** Попробуйте другую архитектуру модели, чтобы узнать, лучше ли она справляется с этой задачей!

</Tip>

{#if fw === 'pt'}

Если вы хотите более глубоко погрузиться в тренировочный цикл, мы покажем вам, как сделать то же самое с помощью 🤗 Accelerate.

## Пользовательский цикл обучения[[a-custom-training-loop]]

Теперь давайте посмотрим на полный цикл обучения, чтобы вы могли легко настроить нужные вам части. Он будет очень похож на цикл обучения в [Главе 3](../chapter3/4), за исключением цикла оценки. Мы сможем регулярно оценивать модель, поскольку больше не ограничены классом `Trainer`.

### Подготовим все для обучения[[preparing-everything-for-training]]

Сначала нам нужно создать `DataLoader`ы из наших датасетов. Мы установим формат этих датасетов в `"torch"` и удалим столбцы в наборе валидации, которые не используются моделью. Затем мы можем использовать `default_data_collator`, предоставляемый Transformers, в качестве `collate_fn` и перемешаем обучающий набор, но не набор для валидации:

```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
)
```

Затем мы реинстанцируем нашу модель, чтобы убедиться, что мы не продолжаем дообучение, а снова начинаем с предварительно обученной модели BERT:

```py
model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint)
```

Тогда нам понадобится оптимизатор. Как обычно, мы используем классический `AdamW`, который похож на Adam, но с исправлением в способе применения затухания веса:

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Когда у нас есть все эти объекты, мы можем отправить их в метод `accelerator.prepare()`. Помните, что если вы хотите обучать на TPU в блокноте Colab, вам нужно будет перенести весь этот код в функцию обучения, которая не должна выполнять ни одну ячейку, инстанцирующую `Accelerator`. Мы можем принудительно обучать со смешанной точностью, передав `fp16=True` в `Accelerator` (или, если вы выполняете код в виде скрипта, просто убедитесь, что заполнили 🤗 Accelerate `config` соответствующим образом).

```py
from accelerate import Accelerator

accelerator = Accelerator(fp16=True)
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

Как вы уже поняли из предыдущих разделов, мы можем использовать длину `train_dataloader` для вычисления количества шагов обучения только после того, как она пройдет через метод `accelerator.prepare()`. Мы используем тот же линейный график, что и в предыдущих разделах:

```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,
)
```

Чтобы отправить нашу модель на Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала войдите в Hub Hugging Face, если вы еще не вошли в него. Мы определим имя розитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свое усмотрение; оно просто должно содержать ваше имя пользователя, что и делает функция `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'
```

Затем мы можем клонировать этот розиторий в локальную папку. Если она уже существует, эта локальная папка должна быть клоном того розитория, с которым мы работаем:

```py
output_dir = "bert-finetuned-squad-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи.

### Цикл обучения[[training-loop]]

Теперь мы готовы написать полный цикл обучения. После определения прогресс-бара, чтобы следить за ходом обучения, цикл состоит из трех частей:

- Собственно обучение, которое представляет собой классическую итерацию по `train_dataloader`, прямой проход по модели, затем обратный проход и шаг оптимизатора.
- Оценка, в которой мы собираем все значения для `start_logits` и `end_logits` перед преобразованием их в массивы NumPy. После завершения цикла оценки мы объединяем все результаты. Обратите внимание, что нам нужно произвести усечение, потому что `Accelerator` может добавить несколько примеров в конце, чтобы убедиться, что у нас одинаковое количество примеров в каждом процессе.
- Сохранение и загрузка, где мы сначала сохраняем модель и токенизатор, а затем вызываем `repo.push_to_hub()`. Как и раньше, мы используем аргумент `blocking=False`, чтобы указать библиотеке 🤗 Hub на асинхронный процесс push. Таким образом, обучение продолжается нормально, а эта (длинная) инструкция выполняется в фоновом режиме.

Вот полный код цикла обучения:

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Обучение
    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)

    # Оценка
    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)

    # Сохранение и загрузка
    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
        )
```

Если вы впервые видите модель, сохраненную с помощью 🤗 Accelerate, давайте посмотрим на три строки кода, которые с этим связаны:

```py
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
```

Первая строка не требует пояснений: она предписывает всем процессам подождать, пока все не окажутся на этой стадии, прежде чем продолжить работу. Это нужно для того, чтобы убедиться, что у нас одна и та же модель в каждом процессе перед сохранением. Затем мы захватываем `unwrapped_model`, которая является базовой моделью, которую мы определили. Метод `accelerator.prepare()` изменяет модель для работы в распределенном обучении, поэтому у нее больше не будет метода `save_pretrained()`; метод `accelerator.unwrap_model()` отменяет этот шаг. Наконец, мы вызываем `save_pretrained()`, но указываем этому методу использовать `accelerator.save()` вместо `torch.save()`.

После этого у вас должна получиться модель, которая дает результаты, очень похожие на модель, обученную с помощью `Trainer`. Вы можете проверить модель, которую мы обучили с помощью этого кода, на [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). А если вы хотите протестировать какие-либо изменения в цикле обучения, вы можете напрямую реализовать их, отредактировав код, показанный выше!

{/if}

## Использование дообученной модели[[using-the-fine-tuned-model]]

Мы уже показали вам, как можно использовать модель, дообучение которой мы проводили на Model Hub, с помощью виджета инференса. Чтобы использовать ее локально в `pipeline`, нужно просто указать идентификатор модели:

```py
from transformers import pipeline

# Замените здесь на выбранную вами контрольную точку
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'}
```

Отлично! Наша модель работает так же хорошо, как и модель по умолчанию для этого конвейера!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/7.mdx" />

### Классификация токенов[[token-classification]]
https://huggingface.co/learn/course/ru/chapter7/2.md

# Классификация токенов[[token-classification]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={7}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter7/section2_tf.ipynb"},
]} />

{/if}

Первое приложение, которое мы рассмотрим, - это классификация токенов. Эта общая задача охватывает любую проблему, которую можно сформулировать как "присвоение метки каждому токену в предложении", например:

- **Распознавание именованных сущностей (Named entity recognition - NER)**: Поиск сущностей (например, лиц, мест или организаций) в предложении. Это можно сформулировать как приписывание метки каждому токену, имея один класс для сущности и один класс для "нет сущности".
- **Морфологическая разметка (Part-of-speech tagging - POS)**: Пометить каждое слово в предложении как соответствующее определенной части речи (например, существительное, глагол, прилагательное и т. д.).
- **Выделение токенов (Chunking)**: Поиск токенов, принадлежащих одной и той же сущности. Эта задача (которая может быть объединена с POS или NER) может быть сформулирована как присвоение одной метки (обычно `B-`) всем токенам, которые находятся в начале фрагмента текста, другой метки (обычно `I-`) - токенам, которые находятся внутри фрагмента текста, и третьей метки (обычно `O`) - токенам, которые не принадлежат ни к одному фрагменту.

<Youtube id="wVHdVlPScxA"/>

Конечно, существует множество других типов задач классификации токенов; это лишь несколько показательных примеров. В этом разделе мы дообучим модель (BERT) для задачи NER, которая затем сможет вычислять прогнозы, подобные этому:

<iframe src="https://course-demos-bert-finetuned-ner.hf.space" frameBorder="0" height="350" title="Gradio app" class="block dark:hidden container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

<a class="flex justify-center" href="/huggingface-course/bert-finetuned-ner">
<img class="block dark:hidden lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/model-eval-bert-finetuned-ner.png" alt="One-hot encoded labels for question answering."/>
<img class="hidden dark:block lg:w-3/5" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter7/model-eval-bert-finetuned-ner-dark.png" alt="One-hot encoded labels for question answering."/>
</a>

Вы можете найти модель, которую мы обучим и загрузим на хаб, и перепроверить ее предсказания [здесь](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn).

## Подготовка данных[[preparing-the-data]]

Прежде всего, нам нужен набор данных, подходящий для классификации токенов. В этом разделе мы будем использовать [набор данных CoNLL-2003](https://huggingface.co/datasets/conll2003), который содержит новости от Reuters.

<Tip>

💡 Если ваш набор данных состоит из текстов, часть которых состоит из слов с соответствующими метками, вы сможете адаптировать описанные здесь процедуры обработки данных к своему набору данных. Обратитесь к [Главе 5](../chapter5/1), если вам нужно освежить в памяти то, как загружать собственные данные в `Dataset`.

</Tip>

### Датасет CoNLL-2003[[the-conll-2003-dataset]]

Для загрузки датасета CoNLL-2003 мы используем метод `load_dataset()` из библиотеки 🤗 Datasets:

```py
from datasets import load_dataset

raw_datasets = load_dataset("conll2003")
```

Это позволит загрузить и кэшировать датасет, как мы видели в [Главе 3](../chapter3/1) для датасета GLUE MRPC. Изучение этого объекта показывает нам присутствующие столбцы и части тренировочного, проверочного и тестового наборов:

```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
    })
})
```

В частности, мы видим, что датасет содержит метки для трех задач, о которых мы говорили ранее: NER, POS и chunking. Существенным отличием от других датасетов является то, что входные тексты представлены не как предложения или документы, а как списки слов (последний столбец называется `tokens`, но он содержит слова в том смысле, что это предварительно токинизированные входные данные, которые еще должны пройти через токенизатор для токенизации по подсловам).

Давайте посмотрим на первый элемент обучающего набора:

```py
raw_datasets["train"][0]["tokens"]
```

```python out
['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.']
```

Поскольку мы хотим выполнить распознавание именованных сущностей, мы изучим теги NER:

```py
raw_datasets["train"][0]["ner_tags"]
```

```python out
[3, 0, 7, 0, 0, 0, 7, 0, 0]
```

Это метки в виде целых чисел, готовые для обучения, но они не всегда полезны, когда мы хотим проанализировать данные. Как и в случае с классификацией текста, мы можем получить доступ к соответствию между этими целыми числами и названиями меток, посмотрев на атрибут `features` нашего датасета:

```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)
```

Таким образом, этот столбец содержит элементы, которые являются последовательностями `ClassLabel`. Тип элементов последовательности указан в атрибуте `feature` этого `ner_feature`, и мы можем получить доступ к списку имен, посмотрев на атрибут `names` этого `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']
```

Мы уже видели эти метки при изучении конвейера `token-classification` в [Главе 6](../chapter6/3), но для краткости напомним:  

- `O` означает, что слово не соответствует какой-либо сущности.
- `B-PER`/`I-PER` означает, что слово соответствует началу/находится внутри сущности персоны *person*.
- `B-ORG`/`I-ORG` означает, что слово соответствует началу/находится внутри сущности *organization*.
- `B-LOC`/`I-LOC` означает, что слово соответствует началу/находится внутри сущности *location*.
- `B-MISC`/`I-MISC` означает, что слово соответствует началу/находится внутри сущности *miscellaneous*.

Теперь декодирование меток, которые мы видели ранее, дает нам следующее:

```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'
```

В качестве примера смешивания меток `B-` и `I-`, вот что дает тот же код для элемента обучающего множества с индексом 4:

```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'
```

Как мы видим, сущностям, состоящим из двух слов, например "European Union" и "Werner Zwingmann", присваивается метка `B-` для первого слова и метка `I-` для второго.

<Tip>

✏️ **Попробуйте!** Выведите те же два предложения с метками POS или chunking.

</Tip>

### Обработка данных[[processing-the-data]]

<Youtube id="iY2AZYdZAr0"/>

Как обычно, наши тексты должны быть преобразованы в идентификаторы токенов, прежде чем модель сможет понять их смысл. Как мы видели в [Главе 6](../chapter6/), существенным отличием задачи классификации токенов является то, что у нас есть предварительно токенизированные входные данные. К счастью, API токенизатора справляется с этим довольно легко; нам просто нужно предупредить `tokenizer` специальным флагом.

Для начала давайте создадим объект `tokenizer`. Как мы уже говорили, мы будем использовать предварительно обученную модель BERT, поэтому начнем с загрузки и кэширования соответствующего токенизатора:

```python
from transformers import AutoTokenizer

model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
```

Вы можете заменить `model_checkpoint` на любую другую модель из [Hub](https://huggingface.co/models) или на локальную папку, в которой вы сохранили предварительно обученную модель и токенизатор. Единственное ограничение - токенизатор должен быть создан с помощью библиотеки 🤗 Tokenizers, поэтому существует "быстрая" версия. Вы можете увидеть все архитектуры, которые поставляются с быстрой версией в [этой большой таблице](https://huggingface.co/transformers/#supported-frameworks), а чтобы проверить, что используемый вами объект `tokenizer` действительно поддерживается 🤗 Tokenizers, вы можете посмотреть на его атрибут `is_fast`:

```py
tokenizer.is_fast
```

```python out
True
```

Для токенизации предварительно токинизированного ввода мы можем использовать наш `tokenizer`, как обычно, просто добавив `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]']
```

Как мы видим, токенизатор добавил специальные токены, используемые моделью (`[CLS]` в начале и `[SEP]` в конце), и оставил большинство слов нетронутыми. Слово `lamb`, однако, было токенизировано на два подслова, `la` и `##mb`. Это вносит несоответствие между нашими входными данными и метками: список меток состоит всего из 9 элементов, в то время как наши входные данные теперь содержат 12 токенов. Учесть специальные токены легко (мы знаем, что они находятся в начале и в конце), но нам также нужно убедиться, что мы выровняли все метки с соответствующими словами.

К счастью, поскольку мы используем быстрый токенизатор, у нас есть доступ к суперспособностям 🤗 Tokenizers, что означает, что мы можем легко сопоставить каждый токен с соответствующим словом (как показано в [Глава 6](../chapter6/3)):

```py
inputs.word_ids()
```

```python out
[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None]
```

Немного поработав, мы сможем расширить список меток, чтобы он соответствовал токенам. Первое правило, которое мы применим, заключается в том, что специальные токены получают метку `-100`. Это связано с тем, что по умолчанию `-100` - это индекс, который игнорируется в функции потерь, которую мы будем использовать (кросс-энтропия). Затем каждый токен получает ту же метку, что и токен, с которого началось слово, в котором он находится, поскольку они являются частью одной и той же сущности. Для токенов, находящихся внутри слова, но не в его начале, мы заменяем `B-` на `I-` (поскольку такие токены не являются началом сущности):

```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:
            # Начало нового слова!
            current_word = word_id
            label = -100 if word_id is None else labels[word_id]
            new_labels.append(label)
        elif word_id is None:
            # Специальный токен
            new_labels.append(-100)
        else:
            # То же слово, что и предыдущий токен
            label = labels[word_id]
            # Если метка B-XXX, заменяем ее на I-XXX
            if label % 2 == 1:
                label += 1
            new_labels.append(label)

    return new_labels
```

Давайте опробуем это на нашем первом предложении:

```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]
```

Как мы видим, наша функция добавила `-100` для двух специальных токенов в начале и в конце и новый `0` для нашего слова, которое было разбито на две части.

<Tip>

✏️ **Попробуйте!** Некоторые исследователи предпочитают назначать только одну метку на слово и присваивать `-100` другим подтокенам в данном слове. Это делается для того, чтобы длинные слова, часть которых состоит из множества субтокенов, не вносили значительный вклад в потери.

</Tip>

Чтобы предварительно обработать весь наш датасет, нам нужно провести токенизацию всех входных данных и применить `align_labels_with_tokens()` ко всем меткам. Чтобы воспользоваться преимуществами скорости нашего быстрого токенизатора, лучше всего токенизировать много текстов одновременно, поэтому мы напишем функцию, которая обрабатывает список примеров и использует метод `Dataset.map()` с параметром `batched=True`. Единственное отличие от нашего предыдущего примера заключается в том, что функция `word_ids()` должна получить индекс примера, идентификаторы слов которого нам нужны, с учётом того что входными данными для токенизатора являются списки текстов (или, в нашем случае, списки слов), поэтому мы добавляем и это:

```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
```

Обратите внимание, что мы еще не добавляли во входные данные дополняющие токены; мы сделаем это позже, при создании батчей с помощью коллатора данных.

Теперь мы можем применить всю эту предварительную обработку к другим частям нашего датасета:

```py
tokenized_datasets = raw_datasets.map(
    tokenize_and_align_labels,
    batched=True,
    remove_columns=raw_datasets["train"].column_names,
)
```

Мы сделали самую сложную часть! Теперь, когда данные прошли предварительную обработку, само обучение будет выглядеть примерно так, как мы делали это в [Главе 3](../chapter3/1).

{#if fw === 'pt'}

## Дообучение модели с помощью API `Trainer`[[fine-tuning-the-model-with-the-trainer-api]]

Фактический код, использующий `Trainer`, будет таким же, как и раньше; единственные изменения - это способ объединения данных в батч и функция вычисления метрики.

{:else}

## Дообучение модели с помощью Keras[[fine-tuning-the-model-with-keras]]

Фактический код, использующий Keras, будет очень похож на предыдущий; единственные изменения - это способ объединения данных в батч и функция вычисления метрики.

{/if}


### Сопоставление данных[[data-collation]]

Мы не можем просто использовать `DataCollatorWithPadding`, как в [Главе 3](../chapter3/1), потому что в этом случае дополняются только входные данные (идентификаторы входов, маска внимания и идентификаторы типов токенов). Здесь наши метки должны быть дополнены точно так же, как и входы, чтобы они оставались одного размера, используя `-100` в качестве значения, чтобы соответствующие прогнозы игнорировались при вычислении потерь.

Все это делает [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Как и `DataCollatorWithPadding`, он принимает `токенизатор`, используемый для предварительной обработки входных данных:

{#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}

Чтобы проверить его на нескольких примерах, мы можем просто вызвать его на списке примеров из нашего токенизированного обучающего набора:

```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]])
```

Давайте сравним это с метками для первого и второго элементов в нашем датасете:

```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'}

Как мы видим, второй набор меток был дополнен до длины первого с помощью значения `-100`.

{:else}

Наш коллатор данных готов к работе! Теперь давайте используем его для создания датасета `tf.data.Dataset` с помощью метода `to_tf_dataset()`. Вы также можете использовать `model.prepare_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,
)
```


 Следующая остановка: сама модель.

{/if}

{#if fw === 'tf'}

### Определение модели[[defining-the-model]]

Поскольку мы работаем над проблемой классификации токенов, мы будем использовать класс `TFAutoModelForTokenClassification`. Главное, что нужно помнить при определении этой модели, - это передать информацию о количестве имеющихся у нас меток. Проще всего передать это число с помощью аргумента `num_labels`, но если мы хотим получить красивый виджет инференса, подобный тому, что мы видели в начале этого раздела, то лучше задать правильные соответствия меток.

Они должны быть заданы двумя словарями, `id2label` и `label2id`, которые содержат отображение идентификатора в метку и наоборот:

```py
id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}
```

Теперь мы можем просто передать их в метод `TFAutoModelForTokenClassification.from_pretrained()`, и они будут заданы в конфигурации модели, затем правильно сохранены и загружены в Hub:

```py
from transformers import TFAutoModelForTokenClassification

model = TFAutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

Как и при определении `TFAutoModelForSequenceClassification` в [Главе 3](../chapter3/1), при создании модели выдается предупреждение о том, что некоторые веса не были использованы (веса из предварительно обученной головы), а другие веса инициализированы случайно (веса из новой головы классификации токенов), и что эту модель нужно обучить. Мы сделаем это через минуту, но сначала давайте перепроверим, что наша модель имеет правильное количество меток:

```python
model.config.num_labels
```

```python out
9
```

<Tip warning={true}>

⚠️ Если у вас есть модель с неправильным количеством меток, то при последующем вызове `model.fit()` вы получите непонятную ошибку. Это может вызвать раздражение при отладке, поэтому обязательно выполните эту проверку, чтобы убедиться, что у вас есть ожидаемое количество меток.

</Tip>

### Дообучение модели[[fine-tuning-the-model]]

Теперь мы готовы к обучению нашей модели! Однако сначала нам нужно сделать еще немного работы: войти в Hugging Face и определить гиперпараметры обучения. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом:

```python
from huggingface_hub import notebook_login

notebook_login()
```

Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face.

Если вы работаете не в блокноте, просто введите следующую строку в терминале:

```bash
huggingface-cli login
```

После входа в аккаунт мы можем подготовить все необходимое для компиляции нашей модели. 🤗 Transformers предоставляет удобную функцию `create_optimizer()`, которая создаст вам оптимизатор `AdamW` с соответствующими настройками затухания весов и затухания скорости обучения, что позволит улучшить качество вашей модели по сравнению со встроенным оптимизатором `Adam`: 

```python
from transformers import create_optimizer
import tensorflow as tf

# Обучение со смешанной точностью float16
# Закомментируйте эту строку, если вы используете GPU, которому это не принесет никаких преимуществ
tf.keras.mixed_precision.set_global_policy("mixed_float16")

# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное
# на общее количество эпох. Обратите внимание, что tf_train_dataset здесь - это разбитое на батчи tf.data.Dataset,
# а не оригинальный датасет Hugging Face, поэтому его len() уже равен 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)
```

Обратите внимание, что мы не указываем аргумент `loss` в `compile()`. Это связано с тем, что модели могут вычислять потери внутри себя - если вы компилируете без потерь и предоставляете свои метки во входном словаре (как мы делаем в наших датасетах), то модель будет обучаться, используя эти внутренние потери, которые будут соответствовать задаче и типу выбранной вами модели.

Далее мы определяем `PushToHubCallback` для загрузки нашей модели в Hub во время обучения модели с помощью этого обратного вызова:

```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,
)
```

С помощью аргумента `hub_model_id` можно указать полное имя репозитория, в который вы хотите передать модель (в частности, этот аргумент нужно использовать, чтобы передать модель в организацию). Например, когда мы отправили модель в [организацию `huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/bert-finetuned-ner"`. По умолчанию используемое хранилище будет находиться в вашем пространстве имен и называться в соответствии с заданной вами выходной директорией, например `"cool_huggingface_user/bert-finetuned-ner"`.

<Tip>

💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном репозитория, в который вы хотите выполнить push. Если это не так, вы получите ошибку при вызове `model.fit()` и должны будете задать новое имя.

</Tip>

Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается на хаб в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине.

На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи классификации токенов - поздравляем! Но насколько хороша наша модель на самом деле? Чтобы выяснить это, нам следует оценить некоторые метрики.

{/if}


### Метрики[[metrics]]

{#if fw === 'pt'}

Чтобы `Trainer` вычислял метрику каждую эпоху, нам нужно определить функцию `compute_metrics()`, которая принимает массивы прогнозов и меток и возвращает словарь с именами и значениями метрик. 

Традиционно для оценки прогнозирования классификации токенов используется библиотека [*seqeval*](https://github.com/chakki-works/seqeval). Чтобы использовать эту метрику, сначала нужно установить библиотеку *seqeval*:

```py
!pip install seqeval
```

Мы можем загрузить ее с помощью функции `evaluate.load()`, как мы это делали в [Главе 3](../chapter3/1):

{:else}

Традиционно для оценки прогнозирования классификации токенов используется библиотека [*seqeval*](https://github.com/chakki-works/seqeval). Чтобы использовать эту метрику, сначала нужно установить библиотеку *seqeval*:

```py
!pip install seqeval
```

Мы можем загрузить ее с помощью функции `evaluate.load()`, как мы это делали в [Главе 3](../chapter3/1):

{/if}

```py
import evaluate

metric = evaluate.load("seqeval")
```

Эта метрика ведет себя не так, как стандартная accuracy: на самом деле она принимает списки меток как строки, а не как целые числа, поэтому нам нужно полностью декодировать прогноз и метки перед передачей их в метрику. Давайте посмотрим, как это работает. Сначала мы получим метки для нашего первого обучающего примера:

```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']
```

Затем мы можем создать фальшивые прогнозы для них, просто изменив значение в индексе 2:

```py
predictions = labels.copy()
predictions[2] = "O"
metric.compute(predictions=[predictions], references=[labels])
```

Обратите внимание, что метрика принимает список прогнозов (не только один) и список меток. Вот результат:

```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'}

Она возвращает огромное количество информации! Мы получаем оценки precision, recall и F1 для каждой отдельной сущности, а также в целом. Для расчета метрик мы сохраним только общую оценку, но вы можете настроить функцию `compute_metrics()` так, чтобы она возвращала все метрики, которые вы хотите получить.

Эта функция `compute_metrics()` сначала берет argmax логитов, чтобы преобразовать их в прогнозы (как обычно, логиты и вероятности расположены в том же порядке, поэтому нам не нужно применять softmax). Затем нам нужно преобразовать метки и прогнозы из целых чисел в строки. Мы удаляем все значения, для которых метка равна `-100`, а затем передаем результаты в метод `metric.compute()`:

```py
import numpy as np


def compute_metrics(eval_preds):
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)

    # Удаляем игнорируемый индекс (специальные токены) и преобразуем в метки
    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"],
    }
```

Теперь, когда это сделано, мы почти готовы к определению нашего `Trainer`. Нам просто нужна `model`, чтобы дообучить ее!

{:else}

Она возвращает огромное количество информации! Мы получаем оценки precision, recall и F1 для каждой отдельной сущности, а также в целом. Теперь давайте посмотрим, что произойдет, если мы попробуем использовать реальные прогнозы модели для вычисления реальных оценок.

TensorFlow не любит конкатенировать наши прогнозы, поскольку они имеют переменную длину последовательности. Это означает, что мы не можем просто использовать `model.predict()` - но это нас не остановит. Мы будем получать прогнозы по батчу за раз и конкатенировать их в один большой длинный список по мере продвижения, отбрасывая токены `-100`, которые указывают на маскирование/дополнение, а затем вычислять метрики для списка в конце:

```py
import numpy as np

all_predictions = []
all_labels = []
for batch in tf_eval_dataset:
    logits = model.predict_on_batch(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}
```

Как ваша модель показала себя по сравнению с нашей? Если вы получили похожие цифры, значит, ваше обучение прошло успешно!

{/if}

{#if fw === 'pt'}

### Определение модели[[defining-the-model]]

Поскольку мы работаем над проблемой классификации токенов, мы будем использовать класс `AutoModelForTokenClassification`. Главное, что нужно помнить при определении этой модели, - это передать информацию о количестве имеющихся у нас меток. Проще всего передать это число с помощью аргумента `num_labels`, но если мы хотим получить красивый виджет инференса, подобный тому, что мы видели в начале этого раздела, то лучше задать правильное сопоставление меток.

Оно должно задаваться двумя словарями, `id2label` и `label2id`, которые содержат соответствие между идентификатором и меткой и наоборот:

```py
id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}
```

Теперь мы можем просто передать их в метод `AutoModelForTokenClassification.from_pretrained()`, и они будут заданы в конфигурации модели, а затем правильно сохранены и загружены в Hub:

```py
from transformers import AutoModelForTokenClassification

model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

Как и в случае определения `AutoModelForSequenceClassification` в [Главе 3](../chapter3/1), при создании модели выдается предупреждение о том, что некоторые веса не были использованы (те, что были получены из предварительно обученной головы), а другие инициализированы случайно (те, что были получены из новой головы классификации токенов), и что эту модель необходимо обучить. Мы сделаем это через минуту, но сначала давайте перепроверим, что наша модель имеет правильное количество меток:

```python
model.config.num_labels
```

```python out
9
```

<Tip warning={true}>

⚠️ Если у вас есть модель с неправильным количеством меток, то при последующем вызове метода `Trainer.train()` вы получите непонятную ошибку (что-то вроде "CUDA error: device-side assert triggered"). Это главная причина ошибок, о которых сообщают пользователи, поэтому обязательно выполните эту проверку, чтобы убедиться, что у вас есть ожидаемое количество меток.

</Tip>

### Дообучение модели[[fine-tuning-the-model]]

Теперь мы готовы к обучению нашей модели! Нам осталось сделать две последние вещи, прежде чем мы определим наш `Trainer`: войти в Hugging Face и определить наши аргументы для обучения. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом:

```python
from huggingface_hub import notebook_login

notebook_login()
```

Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face.

Если вы работаете не в ноутбуке, просто введите следующую строку в терминале:

```bash
huggingface-cli login
```

Как только это будет сделано, мы сможем определить наши `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,
)
```

Большинство из них вы уже видели: мы задаем некоторые гиперпараметры (например, скорость обучения, количество эпох для обучения и затухание весов) и указываем `push_to_hub=True`, чтобы указать, что мы хотим сохранить модель и оценить ее в конце каждой эпохи, а также что мы хотим загрузить наши результаты в Model Hub. Обратите внимание, что с помощью аргумента `hub_model_id` можно указать имя репозитория, в который вы хотите передать модель (в частности, этот аргумент нужно использовать, чтобы передать модель в организацию). Например, когда мы передавали модель в [организацию `huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/bert-finetuned-ner"` в `TrainingArguments`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, так что в нашем случае это будет `"sgugger/bert-finetuned-ner"`.

<Tip>

💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном репозитория, в который вы хотите передать модель. Если это не так, вы получите ошибку при определении вашего `Trainer` и должны будете задать новое имя.

</Tip>

Наконец, мы просто передаем все в `Trainer` и запускаем обучение:

```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()
```

Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается в Hub в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине.

После завершения обучения мы используем метод `push_to_hub()`, чтобы убедиться, что загружена самая последняя версия модели:

```py
trainer.push_to_hub(commit_message="Training complete")
```

Эта команда возвращает URL только что выполненного commit, если вы хотите его проверить:

```python out
'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed'
```

 `Trainer` также создает черновик карточки модели со всеми результатами оценки и загружает его. На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи классификации токенов - поздравляем!

 Если вы хотите более глубоко погрузиться в цикл обучения, мы покажем вам, как сделать то же самое с помощью 🤗 Accelerate.

##  Индивидуальный цикл обучения[[a-custom-training-loop]]

Теперь давайте рассмотрим полный цикл обучения, чтобы вы могли легко настроить нужные вам части. Он будет очень похож на тот, что мы делали в [Главе 3](../chapter3/4), с некоторыми изменениями для оценки.

### Подготовка всего к обучению[[preparing-everything-for-training]]

Сначала нам нужно создать `DataLoader` для наших датасетов. Мы используем наш `data_collator` в качестве `collate_fn` и перемешиваем обучающий набор, но не валидационный:

```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
)
```

Затем мы повторно инстанцируем нашу модель, чтобы убедиться, что мы не продолжаем дообучать модель, а снова начинаем с предварительно обученной модели BERT:

```py
model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)
```

Тогда нам понадобится оптимизатор. Мы будем использовать классический `AdamW`, который похож на `Adam`, но с исправлениями в способе применения затухания весов:

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)
```

Когда у нас есть все эти объекты, мы можем отправить их в метод `accelerator.prepare()`:

```py
from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)
```

<Tip>

🚨 Если вы обучаетесь на TPU, вам нужно будет перенести весь код, начиная с ячейки выше, в специальную функцию обучения. Подробнее смотрите [Главу 3](../chapter3/1).

</Tip>

Теперь, когда мы отправили наш `train_dataloader` в `accelerator.prepare()`, мы можем использовать его длину для вычисления количества шагов обучения. Помните, что это всегда нужно делать после подготовки загрузчика данных, так как этот метод изменит его длину. Мы используем классический линейный планировшик скорости обучения до 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,
)
```

Наконец, чтобы передать нашу модель в Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала авторизуйтесь в Hugging Face, если вы еще не авторизованы. Мы определим имя репозитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свой собственный выбор; он просто должен содержать ваше имя пользователя, что и делает функция `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'
```

Затем мы можем клонировать этот репозиторий в локальную папку. Если она уже существует, эта локальная папка должна быть существующим клоном репозитория, с которым мы работаем:

```py
output_dir = "bert-finetuned-ner-accelerate"
repo = Repository(output_dir, clone_from=repo_name)
```

Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи.

### Цикл обучения[[training-loop]]

Теперь мы готовы написать полный цикл обучения. Чтобы упростить его оценочную часть, мы определяем функцию `postprocess()`, которая принимает прогнозы и метки и преобразует их в списки строк, как того ожидает наш объект `metric`:

```py
def postprocess(predictions, labels):
    predictions = predictions.detach().cpu().clone().numpy()
    labels = labels.detach().cpu().clone().numpy()

    # Удаляем игнорируемый индекс (специальные токены) и преобразуем в метки
    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
```

Затем мы можем написать цикл обучения. После определения прогресс-бара, чтобы следить за ходом обучения, цикл состоит из трех частей:

- Само обучение представляет собой классическую итерацию по `train_dataloader`, прямой проход по модели, затем обратный проход и шаг оптимизатора.
- Оценка, в которой есть новшество после получения выходов нашей модели на батче: поскольку два процесса могли дополнять входы и метки до разных форм, нам нужно использовать `accelerator.pad_across_processes()`, чтобы сделать прогнозы и метки одинаковой формы перед вызовом метода `gather()`. Если мы этого не сделаем, оценка либо завершится с ошибкой, либо зависнет навсегда. Затем мы отправляем результаты в `metric.add_batch()` и вызываем `metric.compute()` после завершения цикла оценки.
- Сохранение и загрузка, где мы сначала сохраняем модель и токенизатор, а затем вызываем `repo.push_to_hub()`. Обратите внимание, что мы используем аргумент `blocking=False`, чтобы указать библиотеке 🤗 Hub на выполнение push в асинхронном процессе. Таким образом, обучение продолжается нормально, а эта (длинная) инструкция выполняется в фоновом режиме.

Вот полный код цикла обучения:

```py
from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # Обучение
    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)

    # Оценка
    model.eval()
    for batch in eval_dataloader:
        with torch.no_grad():
            outputs = model(**batch)

        predictions = outputs.logits.argmax(dim=-1)
        labels = batch["labels"]

        # Необходимо добавить предсказания и метки для gather
        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"]
        },
    )

    # Сохранение и загрузка
    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
        )
```

Если вы впервые видите модель, сохраненную с помощью 🤗 Accelerate, давайте посмотрим на три строки кода, которые идут вместе с ним:

```py
accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
```

Первая строка не требует пояснений: она указывает всем процессам подождать, пока все не окажутся на этой стадии, прежде чем продолжить работу. Это нужно для того, чтобы убедиться, что у нас одна и та же модель в каждом процессе перед сохранением. Затем мы берем `unwrapped_model`, которая является базовой моделью, которую мы определили. Метод `accelerator.prepare()` изменяет модель для работы в распределенном обучении, поэтому у нее больше не будет метода `save_pretrained()`; метод `accelerator.unwrap_model()` отменяет этот шаг. Наконец, мы вызываем `save_pretrained()`, но указываем этому методу использовать `accelerator.save()` вместо `torch.save()`. 

После того как это будет сделано, у вас должна получиться модель, выдающая результаты, очень похожие на те, что были обучены с помощью `Trainer`. Вы можете посмотреть модель, которую мы обучили с помощью этого кода, на [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). А если вы хотите протестировать какие-либо изменения в цикле обучения, вы можете напрямую реализовать их, отредактировав код, показанный выше!

{/if}

## Использование дообученной модели[[using-the-fine-tuned-model]]

Мы уже показали вам, как можно использовать модель, которую мы дообучили на Model Hub, с помощью виджета инференса. Чтобы использовать ее локально в `pipeline`, нужно просто указать соответствующий идентификатор модели:

```py
from transformers import pipeline

# Замените это на свою собственную контрольную точку
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}]
```

Отлично! Наша модель работает так же хорошо, как и модель по умолчанию для этого конвейера!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter7/2.mdx" />

### Первая часть завершена!
https://huggingface.co/learn/course/ru/chapter4/5.md

# Первая часть завершена!

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

Вот и закончилась первая часть курса! Часть 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)!

Нам не терпится увидеть, как вы используете свои знания!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter4/5.mdx" />

### Hugging Face Hub
https://huggingface.co/learn/course/ru/chapter4/1.md

# Hugging Face Hub

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

[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) для компьютерного зрения. 

Каждая из этих моделей размещается в виде git-репозитория, что позволяет управлять версиями и воспроизводить их. Совместное использование модели в Hub означает ее доступность для сообщества и предоставление доступа к ней всем, кто хочет легко ее использовать, что, в свою очередь, избавляет их от необходимости обучать модель самостоятельно.

Кроме того, совместное использование модели в Hub автоматически развертывает размещенный API применения для этой модели. Любой участник сообщества может протестировать его прямо на странице модели с пользовательскими входными данными и соответствующими виджетами.

Самое приятное то, что делиться и использовать любую общедоступную модель в Hub можно совершенно бесплатно! [Платные варианты] (https://huggingface.co/pricing) также существуют, если вы хотите поделиться моделями в частном порядке.

В видео ниже показано, как сориентироваться на Hub. 

<Youtube id="XvSGPZFEjDY"/>

Чтобы завершить изучение этой главы, необходимо иметь учетную запись Huggingface.co, так как мы будем создавать и управлять репозиториями в Hugging Face Hub: [создать учетную запись](https://huggingface.co/join)

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter4/1.mdx" />

### Публикация обученых моделей в общий доступ
https://huggingface.co/learn/course/ru/chapter4/3.md

# Публикация обученых моделей в общий доступ

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter4/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter4/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter4/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter4/section3_tf.ipynb"},
]} />

{/if}

Далее мы рассмотрим самые простые способы публикации предварительно обученных моделей в 🤗 Hub: доступные инструменты и утилиты, упрощающие совместное использование и обновление моделей непосредственно в Hub. 

<Youtube id="9yY3RB_GSPM"/>

Мы призываем всех пользователей, которые обучают модели, вносить свой вклад, делясь ими с сообществом — совместное использование моделей, даже если они обучены на очень конкретных наборах данных, поможет другим, сэкономив им время и вычислительные ресурсы! В свою очередь, вы можете извлечь выгоду из работы, которую проделали другие!

Есть три пути создания репозитория с моделью:

- С использованием API `push_to_hub` 
- С использованием python-библиотеки `huggingface_hub`
- С использованием веб-интерфейса

После создания репозитория вы можете загружать в него файлы через git и git-lfs. В следующих разделах мы познакомим вас с созданием репозиториев моделей и загрузкой в них файлов. 

## Использование API `push_to_hub` 

{#if fw === 'pt'}

<Youtube id="Zh0FfmVrKX0"/>

{:else}

<Youtube id="pUh5cGmNV8Y"/>

{/if}

Простейший путь загрузки файлов на Hub – `push_to_hub` API. 

Перед тем, как пойдем дальше, необходимо сгенерировать токен аутентификации. Это необходимо сделать для того, чтобы `huggingface_hub` API «узнал» вас и предоставил вам необходимые права на запись. Убедитесь, что вы находитесь в окружении, в котором установлена библиотека `transformers` (см. [Установка](../chapter0)). Если вы работаете в Jupyter'е, вы можете использовать следующую функцию для авторизации: 

```python
from huggingface_hub import notebook_login

notebook_login()
```

В терминале можно запустить: 

```bash
huggingface-cli login
```

В обоих случаях вам будет предложено ввести свой логин и пароль (это должны быть те же данные, которые вы используете для входа на Hub). Если у вас нет учетной записи HuggingFace, вы можете создать ее [здесь](https://huggingface.co/join). 

Отлично! После авторизации ваш токен сохранится во временную папку. Давайте создадим репозитории!

{#if fw === 'pt'}

Если вы уже пользовались `Trainer` API для обучения модели, то самый простой способ загрузить модель на Hub – установить аргумент `push_to_hub=True` во время инициализации `TrainingArguments`: 

```py
from transformers import TrainingArguments

training_args = TrainingArguments(
    "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True
)
```

При вызове `trainer.train()` `Trainer` будет загружать модель на Hub каждый раз, когда она будет сохраняться (в данном примере каждую эпоху). Репозиторий будет назван так же, как вы назовете папку для сохранения (в примере это `bert-finetuned-mrpc`), но вы можете задать имя репозитория самостоятельно: задайте аргумент `hub_model_id = "a_different_name"`.

Для загрузки модели в репозиторий организации, представитем которой вы являетесь, укажите `hub_model_id = "my_organization/my_repo_name"`.

После окончания обучения следует вызвать метод `trainer.push_to_hub()`, который загрузит последнюю версию вашей модели в репозиторий. Также он сгенерирует карточку модели со всей необходимой информацией, использованными гиперпараметрами и результатами валидации. Ниже приведено то, что вы можете найти в подобной карточке модели: 

<div class="flex justify-center">
  <img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/model_card.png" alt="An example of an auto-generated model card." width="100%"/>
</div>

{:else}

Если вы используете Keras для обучения моделей, простейший способ загрузить модель на Hub – вызвать callback `PushToHubCallback` при вызове `model.fit()`: 

```py
from transformers import PushToHubCallback

callback = PushToHubCallback(
    "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer
)
```

Затем необходимо добавить `callbacks=[callback]` в вызов `model.fit()`. Callback будет загружать модель на Hub каждый раз, когда она будет сохраняться (в данном примере каждую эпоху). Репозиторий будет назван так же, как вы назовете папку для сохранения (в примере это `bert-finetuned-mrpc`), но вы можете задать имя репозитория самостоятельно: задайте аргумент `hub_model_id = "a_different_name"`.

Для загрузки модели в репозиторий организации, представитем которой вы являетесь, укажите `hub_model_id = "my_organization/my_repo_name"`.

{/if}

На более низком уровне доступ к Model Hub может быть осуществлен прямо через метод `push_to_hub()`, который можно вызвать у моделей, токенизаторов и конфигурационных объектов. Этот метод отвечает сразу и за создание репозитория, и за загрузку файлов моделей и токенизаторов. Никак

At a lower level, accessing the Model Hub can be done directly on models, tokenizers, and configuration objects via their `push_to_hub()` method. This method takes care of both the repository creation and pushing the model and tokenizer files directly to the repository. В отличие от API, который мы рассмотрим ниже, здесь никакая ручная обработка не применяется. 

Чтобы понять, как это работает, давайте инициализируем модель и токенизатор:

{#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}

Вы можете сделать с этими объектами что угодно – измените настройки токенизатора, обучите модель, дообучите предобученную и т.д. После того, как вы получите приемлемый результат, вы можете вызвать `push_to_hub()` прямо у экземпляра модели: 

```py
model.push_to_hub("dummy-model")
```

Эта операция создаст новый репозиторий `dummy-model` в вашем профиле и загрузит все необходимые файлы. Сделайте это же с токенизатором: 

```py
tokenizer.push_to_hub("dummy-model")
```

Если вы являетесь членом организации, просто укажите `organization` и данные будут загружены в профиль организации: 

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface")
```

Если вы хотите использовать какой-то конкретный токен, вы можете указать его в методе `push_to_hub()`: 

```py
tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="<TOKEN>")
```

Теперь перейдите в Model Hub и найдите свою модель: *https://huggingface.co/user-or-organization/dummy-model*. 

Выберите вкладку "Files and versions", вы должны увидеть файлы, похожие на приведенные на скриншоте ниже: 

{#if fw === 'pt'}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{:else}
<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/push_to_hub_dummy_model_tf.png" alt="Dummy model containing both the tokenizer and model files." width="80%"/>
</div>
{/if}

<Tip>
✏️ **Попробуйте!** Используйте модель и токенайзер чекпоинта `bert-base-cased` и загрузите их в собственный профиль с помощью метода `push_to_hub()`. Проверьте, что репозиторий корректно создался перед его удалением. 
</Tip>

Как мы увидели выше, метод `push_to_hub()` поддерживает несколько аргументов, позволяющих загрузить данные в конкретный профиль или профиль организации или использовать конкретный токен. Мы рекомендуем обратить внимание на спецификацию метода, доступную по ссылке [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html), и ознакомиться с остальными возможностями метода. 

Метод `push_to_hub()` реализован с использованием python-пакета [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), который напрямую использует API Hugging Face Hub. Этот пакет интегрирован в 🤗 Transformers и несколько других библиотек машинного обучения, например [`allenlp`](https://github.com/allenai/allennlp). Хотя в этой главе мы сосредоточимся на интеграции с 🤗 Transformers, интегрировать его в ваш собственный код или библиотеку очень просто.

Перейдите к последнему разделу, чтобы узнать, как загружать файлы в только что созданный репозиторий!

## Использование библиотеки `huggingface_hub`

Библиотека `huggingface_hub` - это инструмент, который предлагает наборы различных моделей и датасетов. В ней есть возможность использования  простых методов и классов для общих задач, таких как получение информации о репозиториях на хабе и управление ими. Также доступны простые API-интерфейсы, которые работают поверх git для управления содержимым этих репозиториев и интеграции Hub в ваших проектах и библиотеках.

Как и при использовании API `push_to_hub` необходимо, чтобы ваш токен API был сохранен в кэше. Для этого вам нужно будет использовать команду `login` из CLI, как упоминалось в предыдущем разделе (опять же, убедитесь, что перед этими командами стоит символ `!`, если вы работаете в Google Colab):

```bash
huggingface-cli login
```

Пакет `huggingface_hub` предлагает несколько методов и классов, полезных для наших целей. Во-первых, есть несколько способов управления созданием, удалением и прочего:

```python no-format
from huggingface_hub import (
    # Пользовательские настройки
    login,
    logout,
    whoami,

    # Создание и управление репозиториями
    create_repo,
    delete_repo,
    update_repo_visibility,

    # И несколько способов для получения или изменения информации о содержимом
    list_models,
    list_datasets,
    list_metrics,
    list_repo_files,
    upload_file,
    delete_file,
)
```
Кроме того, `huggingface_hub` предлагает очень мощный класс `Repository` для управления локальным хранилищем. Мы рассмотрим эти методы и этот класс в следующих нескольких разделах, чтобы понять, как их использовать.

Метод `create_repo` можно использовать для создания нового репозитория на хабе:

```py
from huggingface_hub import create_repo

create_repo("dummy-model")
```
Это создаст репозиторий `dummy-model` в вашем пространстве. Если хотите, вы можете указать, какой организации должен принадлежать репозиторий, используя аргумент `organization`:

```py
from huggingface_hub import create_repo

create_repo("dummy-model", organization="huggingface")
```

Это создаст репозиторий `dummy-model` в пространстве `huggingface`, предполагая, что вы принадлежите к этой организации.
Другие аргументы, которые могут быть полезны:

- `private`, чтобы указать, должен ли репозиторий быть видимым для других или нет.
- `token`, если вы хотите переопределить токен, хранящийся в вашем кэше, указанным токеном.
- `repo_type`, если вы хотите создать `dataset` или `space` вместо модели. Допустимые значения: `"dataset"` и `"space"`.

Как только репозиторий создан, мы должны добавить в него файлы! Перейдите к следующему разделу, чтобы увидеть три способа сделать это.


## Использование веб-интерфейса

Веб-интерфейс предлагает инструменты для управления репозиториями прямо в хабе. Используя интерфейс, вы можете легко создавать репозитории, добавлять файлы (даже большие!), исследовать модели, визуализировать различия и многое другое.

Для создания нового репозитория перейдите по ссылке: [huggingface.co/new](https://huggingface.co/new):

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/new_model.png" alt="Page showcasing the model used for the creation of a new model repository." width="80%"/>
</div>

Во-первых, укажите владельца репозитория: это можете быть как вы, так и любая из организаций, с которыми вы связаны. Если вы выберете организацию, модель будет размещена на странице организации, и каждый член организации сможет внести свой вклад в репозиторий.

Затем введите название вашей модели. Это также будет имя репозитория. Наконец, вы можете указать, хотите ли вы, чтобы ваша модель была общедоступной или приватной. Приватные модели скрыты от посторонних глаз.

После создания репозитория моделей вы должны увидеть страницу, подобную этой: 

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/empty_model.png" alt="An empty model page after creating a new repository." width="80%"/>
</div>

Здесь будет размещена ваша модель. Чтобы начать заполнение репозитория, вы можете добавить файл README прямо из веб-интерфейса.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/dummy_model.png" alt="The README file showing the Markdown capabilities." width="80%"/>
</div>

Файл README хранится в формате Markdown! Третья часть этой главы посвящена заполнению карточки модели. Она имеет первостепенное значение для повышения ценности вашей модели, поскольку именно здесь вы рассказываете другим, на что способна модель.

Если вы посмотрите на вкладку «Файлы и версии», то увидите, что там пока не так много файлов — только только что созданный *README.md* и файл *.gitattributes*, который отслеживает большие файлы.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/files.png" alt="The 'Files and versions' tab only shows the .gitattributes and README.md files." width="80%"/>
</div>

Позже мы посмотрим, как добавить новые файлы. 

## Загрузка файлов модели

Система управления файлами в Hugging Face Hub основана на git для обычных файлов и git-lfs (что означает [Git Large File Storage](https://git-lfs.github.com/)) для больших файлов.

В следующем разделе мы рассмотрим три различных способа загрузки файлов в Hub: через `huggingface_hub` и через команды git.

### Функция `upload_file` 

Использование `upload_file` не требует установки git и git-lfs в вашей системе. Функция отправляет файлы напрямую в 🤗 Hub с помощью HTTP-запросов POST. Ограничение этого подхода заключается в том, что он не обрабатывает файлы размером более 5 ГБ.
Если размер ваших файлов превышает 5 ГБ, воспользуйтесь двумя другими способами, описанными ниже.

API можно использовать следующим образом:

```py
from huggingface_hub import upload_file

upload_file(
    "<path_to_file>/config.json",
    path_in_repo="config.json",
    repo_id="<namespace>/dummy-model",
)
```

Это загрузит файл `config.json`, доступный по адресу `<path_to_file>`, в корень как `config.json` в репозиторий `dummy-model`.
Другие аргументы, которые могут быть полезны:

- `token`, если вы хотите переопределить токен, хранящийся в вашем кеше, указанным токеном.
- `repo_type`, если вы хотите загрузить в `dataset` или `space`. Допустимые значения: `"dataset"` и `"space"`. 


### Класс `Repository`

Класс `Repository` управляет локальным репозиторием подобно git. Он абстрагирует большинство проблемных моментов, которые могут возникнуть с git, чтобы предоставить все функции, которые нам нужны.

Использование этого класса требует наличия установленных git и git-lfs, поэтому убедитесь, что у вас установлен и настроен git-lfs (см. [здесь] (https://git-lfs.github.com/) для инструкций по установке), прежде чем начать .

Чтобы начать использование только что созданного репозитория, его нужно инициализировать в локальной папке путем клонирования удаленного репозитория:

```py
from huggingface_hub import Repository

repo = Repository("<path_to_dummy_folder>", clone_from="<namespace>/dummy-model")
```

Этот код создал папку `<path_to_dummy_folder>` в нашем рабочем каталоге. Эта папка содержит только файл `.gitattributes`, поскольку это единственный файл, созданный при создании экземпляра репозитория с помощью `create_repo`.

С этого момента мы можем использовать несколько традиционных методов git:

```py
repo.git_pull()
repo.git_add()
repo.git_commit()
repo.git_push()
repo.git_tag()
```

и другие. Мы рекомендуем ознакомиться с доступной документацией `Repository` [здесь] (https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) для обзора всех доступных методов.

В настоящее время у нас есть модель и токенизатор, которые мы хотели бы отправить в хаб. Мы успешно клонировали репозиторий, поэтому мы можем сохранить файлы в этом репозитории.

Сначала мы убедимся, что наш локальный репозиторий обновлен: запросим оттуда все изменения:

```py
repo.git_pull()
```
После того, как это сделано, мы сохраним файлы модели и токенизатора:

```py
model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
`<path_to_dummy_folder>` сейчас содержит пути к модели и токенизатору. Мы последуем обычной процедуре добавления файлов с помощью git, зафиксируем изменения и отправим их в удаленный репозиторий:

```py
repo.git_add()
repo.git_commit("Add model and tokenizer files")
repo.git_push()
```

Поздравляем! Вы только что сделали первый коммит в хаб! 

### git-подход

Это очень простой подход к загрузке файлов: мы сделаем это напрямую с помощью git и git-lfs. 

Использование этого подхода требует наличия установленных git и git-lfs, поэтому убедитесь, что у вас установлен и настроен [git-lfs](https://git-lfs.github.com/) (см. здесь инструкции по установке), прежде чем начать.

Начните с инициализации git-lfs:

```bash
git lfs install
```

```bash
Updated git hooks.
Git LFS initialized.
```

После инициализации клонируйте репозиторий с моделью.

Once that's done, the first step is to clone your model repository:

```bash
git clone https://huggingface.co/<namespace>/<your-model-id>
```

Мое имя пользователя `lysandre` и я использую модель под названием `dummy`, поэтому команда выглядит следующим образом: 

```
git clone https://huggingface.co/lysandre/dummy
```

Теперь у меня есть папка *dummy* в моей рабочей директории. Командной `cd` я могу перейти в эту директорию и посмотреть на ее содержимое:

```bash
cd dummy && ls
```

```bash
README.md
```

Если вы только что создали репозиторий с помощью метода `create_repo` в Hugging Face Hub, эта папка должна содержать только скрытый файл `.gitattributes`. Если вы следовали инструкциям из предыдущего раздела для создания репозитория с помощью веб-интерфейса, папка должна содержать один файл *README.md* вместе со скрытым файлом `.gitattributes`, как показано здесь.

Добавление файла обычного размера, такого как файл конфигурации, файл словаря или практически любого файла размером менее нескольких мегабайт, выполняется точно так же, как это делается в любой системе на основе git. Однако файлы большего размера должны быть зарегистрированы через git-lfs, тогда появится возможность отправить их на *huggingface.co*.

Давайте ненадолго вернемся к Python, чтобы сгенерировать модель и токенизатор, которые мы хотели бы зафиксировать в нашем демонстрацинном репозитории:

{#if fw === 'pt'}
```py
from transformers import AutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = AutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Сделайте с моделью что угодно: обучите с нуля, дообучите и тд

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{:else}
```py
from transformers import TFAutoModelForMaskedLM, AutoTokenizer

checkpoint = "camembert-base"

model = TFAutoModelForMaskedLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# Do whatever with the model, train it, fine-tune it...

model.save_pretrained("<path_to_dummy_folder>")
tokenizer.save_pretrained("<path_to_dummy_folder>")
```
{/if}

Давайте взглянем на нашу директорию после выполненных выше шагов:

```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
```

If you look at the file sizes (for example, with `ls -lh`), you should see that the model state dict file (*pytorch_model.bin*) is the only outlier, at more than 400 MB.

{:else}
```bash
config.json  README.md  sentencepiece.bpe.model  special_tokens_map.json  tf_model.h5  tokenizer_config.json  tokenizer.json
```

Если вы обратите внимание на размеры файлов (например, с помощью команды `ls -lh`), вы заметите, что файл модели (*t5_model.h5*) является единственным большим файлом, он занимает более 400 Мб.

{/if}

<Tip>
✏️ При создании репозитория с помощью веб-интерфейса, файл *.gitattributes* автоматически фиксирует файлы с определенными расширениями (*.bin* и *.h5*) как большие файлы, и git-lfs отследит их без необходимости делать это вручную. 
</Tip> 

Теперь мы можем продолжить и продолжить, как обычно делаем с традиционными репозиториями Git. Мы можем добавить все файлы в промежуточную среду Git с помощью команды `git add`:

```bash
git add .
```

Затем мы можем взглянуть на файлы, которые в настоящее время размещены:

```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 <file>..." 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 <file>..." 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}

Точно так же мы можем убедиться, что git-lfs отслеживает правильные файлы, используя команду `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:


```

Мы видим, что все файлы имеют `Git` в качестве обработчика, кроме *pytorch_model.bin* и *sentencepiece.bpe.model*, у которых есть `LFS`. Отлично!

{: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:


```

Мы видим, что все файлы имеют `Git` в качестве обработчика, кроме *t5_model.h5*, у которых есть `LFS`. Отлично!

{/if}

Перейдем к последним шагам - коммиту и отправке в удаленный репозиторий *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}

Отправка может занять некоторое время, в зависимости от скорости вашего интернет-соединения и размера ваших файлов:

```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'}
Если мы посмотрим на репозиторий модели после завершения отправки, мы увидим все недавно добавленные файлы:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

Интерфейс позволяет вам исследовать файлы моделей и коммиты, а также видеть разницу, представленную каждым коммитом:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffs.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>
{:else}
Если мы посмотрим на репозиторий модели после завершения отправки, мы увидим все недавно добавленные файлы:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/full_model_tf.png" alt="The 'Files and versions' tab now contains all the recently uploaded files." width="80%"/>
</div>

Интерфейс позволяет вам исследовать файлы моделей и коммиты, а также видеть разницу, представленную каждым коммитом:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/diffstf.gif" alt="The diff introduced by the recent commit." width="80%"/>
</div>
{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter4/3.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter4/6.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

Проверим, что вы усвоили в результате изучения данной главы!
  
### 1. Чем ограничиваются модели с Hub?

<Question
	choices={[
		{
			text: "Моделями из библиотеки 🤗 Transformers.",
			explain: "Модели из библиотеки 🤗 Transformers присутствуют на Hugging Face Hub, но не только они!"
		},
		{
			text: "Все модели имеют похожий с  🤗 Transformers интерфейс.",
			explain: "Hugging Face Hub не предъявляет никаких требований к интерфейсам моделей."
		},
		{
			text: "Никаких ограничений",
			explain: "Верно! Никаких ограничений для загрузки моделей на Hub нет!",
            correct: true
		},
        {
			text: "Моделями так или иначе связанным с обработкой естесственного языка.",
			explain: "Модели не ограничены областью использования!"
		}
	]}
/>

### 2. Как можно управлять моделями на Hub?

<Question
	choices={[
		{
			text: "Через аккаунт GCP.",
			explain: "Неправильно!"
		},
		{
			text: "Peer-to-peer взаимодействием.",
			explain: "Неправильно!"
		},
		{
			text: "Через git и git-lfs.",
			explain: "Верно! Модели на Hub – обычные git-репозитории, использующие <code>git-lfs</code> для больших файлов.",
            correct: true
		}
	]}
/>

### 3. Что вы можете сделать, используя веб-интерфейс Hugging Face Hub? 

<Question
	choices={[
		{
			text: "Сделать fork существующего репозитория.",
			explain: "Форкинг на Hugging Face Hub недоступен."
		},
		{
			text: "Сделать новый репозиторий для модели.",
			explain: "Верно! Но это не все, что вы можете сделать.",
            correct: true
		},
		{
			text: "Управлять файлами и редактировать их.",
			explain: "Верно! Но это не единственный правильный ответ.",
            correct: true
		},
        {
			text: "Загружать файлы",
			explain: "Верно! Но это еще не все.",
            correct: true
		},
        {
			text: "Отслеживать изменения между версиями.",
			explain: "Верно! Но это не все, что вы можете делать.",
            correct: true
		}
	]}
/>

### 4. Что такое карточка модели?

<Question
	choices={[
		{
			text: "Примерное описание модели, поэтому оно менее важно, чем файлы модели и токенизатора.",
			explain: "Это действительно описание модели, но это важная часть: если оно неполное или отсутствует, полезность модели резко снижается."
		},
		{
			text: "Способ обеспечить воспроизводимость, возможность повторного использования и честность.",
			explain: "Правильно! Размещение правильной информации в карточке модели поможет пользователям использовать вашу модель и узнать о ее ограничениях и предвзятости.",
            correct: true
		},
		{
			text: "Файл Python, который можно запустить для получения информации о модели.",
			explain: "Карточка модели – это просто файл Markdown."
		}
	]}
/>

### 5. Какие из этих объектов библиотеки 🤗 Transformers могут быть напрямую выложены на  Hub с помощью функции `push_to_hub()`?

{#if fw === 'pt'}
<Question
	choices={[
		{
			text: "Токенизатор",
			explain: "Верно! Все токенизаторы обладают методом <code>push_to_hub</code>, его применение отправит все файлы токенизатора (словарь, архитектуру и пр.) в указанный репозиторий. Тем не менее, это не единственный верный ответ!",
            correct: true
		},
		{
			text: "Конфигурация модели",
			explain: "Верно! Все конфигурации моделей обладают методом <code>push_to_hub</code>, его применение отправит необходимые файлы в указанный репозиторий. Тем не менее, это не единственный верный ответ!",
            correct: true
		},
		{
			text: "Модель",
			explain: "Верно! Все модели обладают методом <code>push_to_hub</code>, его применение отправит сответствующие файлы и конфигурации в указанный репозиторий. Но это не всё, чем вы можете поделиться!",
            correct: true
		},
        {
			text: "Экземпляр Trainer",
			explain: "Правильно: <code>Trainer</code> также обладает методом <code>push_to_hub</code>, его применение загрузит модель, конфигурацию, токенизатор и черновик карточки модели в указанный репозиторий. Попробуйте и другие ответы!",
            correct: true
		}
	]}
/>
{:else}
<Question
	choices={[
		{
			text: "Токенизатор",
			explain: "Верно! Все токенизаторы обладают методом <code>push_to_hub</code>, его применение отправит все файлы токенизатора (словарь, архитектуру и пр.) в указанный репозиторий. Тем не менее, это не единственный верный ответ!",
            correct: true
		},
		{
			text: "Конфигурация модели",
			explain: "Верно! Все конфигурации моделей обладают методом <code>push_to_hub</code>, его применение отправит необходимые файлы в указанный репозиторий. Тем не менее, это не единственный верный ответ!",
            correct: true
		},
		{
			text: "Модель",
			explain: "Верно! Все модели обладают методом <code>push_to_hub</code>, его применение отправит сответствующие файлы и конфигурации в указанный репозиторий. Но это не всё, чем вы можете поделиться!",
            correct: true
		},
		{
			text: "Все вышеперечисленной с помощью специального callback",
			explain: "Верно: <code>PushToHubCallback</code> будет регулярно отсылать все объекты в репозиторий во время обучения.",
            correct: true
		}
	]}
/>
{/if}

### 6. Какой первый шаг при использовани `push_to_hub()` метода или инструментов командной строки?

<Question
	choices={[
		{
			text: "Залогиниться на сайте Hub.",
			explain: "Это не поможет во время работы на локальной машине."
		},
		{
			text: "Запустить'huggingface-cli login' в терминале.",
			explain: "Верно — это загрузит и закэширует ваш персональный токен.",
            correct: true
		},
		{
			text: "Запустить 'notebook_login()' в ноутбуке.",
			explain: "Верно — это отобразит виджет для авторизации.",
            correct: true
		},
	]}
/>

### 7.Если вы используете модель и токенизатор – как вы можете загрузить их на Hub?

<Question
	choices={[
		{
			text: "Вызвав метод `push_to_hub` у модели и токенизатора.",
			explain: "Верно!",
            correct: true
		},
		{
			text: "С использованием среды Python обернуть их в <code>huggingface_hub</code> утилиту.",
			explain: "Модели и токенизаторы уже используют утилиты <code>huggingface_hub</code>: нет необходимости в дополнительных утилитах!"
		},
		{
			text: "Сохранив их на диск и вызвав <code>transformers-cli upload-model</code>",
			explain: "Команды <code>upload-model</code> не существует."
		}
	]}
/>

### 8. Какие операции git вы можете проводить с экземпляром класса `Repository`?

<Question
	choices={[
		{
			text: "Commit",
			explain: "Верно, <code>git_commit()</code> метод как раз для этого.",
            correct: true
		},
		{
			text: "Pull",
			explain: "Это предназначение метода <code>git_pull()</code>.",
            correct: true
		},
		{
			text: "Push",
			explain: "Метод <code>git_push()</code> делает это.",
            correct: true
		},
		{
			text: "Merge",
			explain: "Нет, такая операция невозможная с данным API."
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter4/6.mdx" />

### Создание карточки модели
https://huggingface.co/learn/course/ru/chapter4/4.md

# Создание карточки модели

<CourseFloatingBanner
    chapter={4}
    classNames="absolute z-10 right-0 top-0"
/>

Карточка модели — это файл, который, возможно, так же важен, как файлы модели и токенизатора в репозитории моделей. Это центральное описание модели, обеспечивающее возможность повторного использования другими членами сообщества и воспроизводимость результатов, а также предоставляющее платформу для других участников. 

Документирование процесса обучения и оценки помогает другим понять, чего ожидать от модели, а предоставление достаточной информации об использованных данных, а также о проведенной предварительной и постобработке. По карточке модели ясны ограничения, предубеждения и контексты, в которых модель может быть полезна, а в каких случаях окажется бесполезной.

Поэтому создание карточки модели, которая четко определяет вашу модель, является очень важным шагом. Здесь мы даем несколько советов, которые помогут вам в этом. Карточка модели создается с помощью файла *README.md*, который вы видели ранее, который представляет собой файл Markdown.

Концепция «карточки модели» возникла в результате исследовательского направления Google, впервые представленного в статье ["Model Cards for Model Reporting"] (https://arxiv.org/abs/1810.03993) Маргарет Митчелл и др. Большая часть информации, содержащейся здесь, основана на этом документе, и мы рекомендуем вам ознакомиться с ним, чтобы понять, почему карточки с моделями так важны в мире, который ценит воспроизводимость, возможность повторного использования и честность.

Карточка модели обычно начинается с очень краткого общего обзора того, для чего предназначена модель, за которым следуют дополнительные сведения в следующих разделах:

- Описание модели
- Предполагаемое использование и ограничения
- Как использовать
- Ограничения и предубеждения
- Тренировочные данные
- Процедура обучения
- Результаты оценки

Давайте посмотрим, что должен содержать каждый из этих разделов.

### Описание модели

Описание содержит основные сведения о модели. Оно включает в себя архитектуру, версию, если модель была представлена в статье - автора, ссылку на оригинальную реализацию (если доступна), автора и общую информацию о модели. Любые авторские права должны быть указаны здесь. В этом разделе также можно упомянуть общую информацию о процедурах обучения, параметрах и важных отказах от ответственности.

### Предполагаемое использование и ограничения

Здесь вы описываете варианты использования, для которых предназначена модель, включая языки, области и домены, в которых она может применяться. В этом разделе карты модели также можно документировать те области, которые являются неподходящими для модели.

### Как использовать

Этот раздел должен включать несколько примеров того, как использовать модель. Это может быть продемонстрировано с использованием функции `pipeline()`, использованием классов модели и токенизатора, а также любым другим способом, удобным на ваш взгляд. 

### Обучающие данные

В этой части должно быть указано, на каком наборе данных обучалась модель. Также приветствуется краткое описание набора(ов) данных.

### Процедура обучения

В этом разделе вы должны описать все важные аспекты обучения, которые полезны с точки зрения воспроизводимости. Раздел включает в себя любую предварительную и постобработку данных, а также такие детали, как количество эпох, на которых была обучена модель, размер батча, скорость обучения и т. д.

### Variable and metrics

Здесь вы должны описать метрики, которые вы используете для оценки, и прочие величины, которые вы замеряете. Напишите, какие метрики использовались, в каком датасете и какое разделение разбиение датасета позволяет легко сравнивать производительность вашей модели с другими моделями. 

### Результаты валидации

Наконец, укажите, насколько хорошо модель работает с набором данных для оценки. Если в модели используется порог принятия решения (threshold)– укажите его, либо предоставьте подробные сведения об оценке при различных порогах для предполагаемого использования.

## Пример

Ознакомьтесь с несколькими примерами хорошо сделанных карточек моделей:

- [`bert-base-cased`](https://huggingface.co/bert-base-cased)
- [`gpt2`](https://huggingface.co/gpt2)
- [`distilbert`](https://huggingface.co/distilbert-base-uncased)

Больше примеров от других организаций и компаний доступны: [здесь](https://github.com/huggingface/model_card/blob/master/examples.md).

## Примечание

Карточки моделей не являются обязательным требованием при публикации моделей, и вам не нужно включать все разделы, описанные выше, при их создании. Однако подробное документирование модели может принести только пользу будущим пользователям, поэтому мы рекомендуем вам заполнить как можно больше разделов в меру своих знаний и способностей.

## Метаданные карточки модели

Если вы немного изучили Hugging Face Hub, вы должны были заметить, что некоторые модели относятся к определенным категориям: вы можете фильтровать их по задачам, языкам, библиотекам и т. д. Категории, к которым принадлежит модель, идентифицируются в соответствии с метаданными, которые вы добавляете в заголовок карточки модели.

Например, если вы посмотрите на [карточку модели `camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), вы должны увидеть следующие строки в заголовке карточки модели:

```
---
language: fr
license: mit
datasets:
- oscar
---
```

Эти метаданные анализируются Hugging Face Hub, который затем идентифицирует эту модель как французскую модель с лицензией MIT, обученную на наборе данных Oscar.

[Полная спецификация карточки модели](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) позволяет указать языкы, лицензии, теги, датасеты, метрики, а также результаты валидации модели.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter4/4.mdx" />

### Использование предобученных моделей
https://huggingface.co/learn/course/ru/chapter4/2.md

# Использование предобученных моделей

{#if fw === 'pt'}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter4/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter4/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={4}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter4/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter4/section2_tf.ipynb"},
]} />

{/if}

Hub упрощает выбор подходящей модели, поэтому ее использование в любой задаче заключается в запуске нескольких строк кода. Давайте посмотрим, как это сделать и как внести свой вклад в сообщество.

Допустим, мы ищем модель для французского языка, которая может выполнять заполнение пропущенных слов в предложении.

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/camembert.gif" alt="Selecting the Camembert model." width="80%"/>
</div>

Мы выберем для этой задачи чекпоинт `camembert-base`. Идентификатор `camembert-base` – все, что нам нужно, чтобы начать использовать модель! Как вы видели в предыдущих главах, мы можем инициализировать модель с использованием функции `pipeline()`: 

```py
from transformers import pipeline

camembert_fill_mask = pipeline("fill-mask", model="camembert-base")
results = camembert_fill_mask("Le camembert est <mask> :)")
```

```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'}
]
```

Как видите, загрузить модель в пайплайн очень просто. Единственное, на что вам нужно обратить внимание, это чтобы выбранный чекпоинт подходил для задачи, для которой он будет использоваться. Например, здесь мы загружаем чекпоинт `camembert-base` в пайплайн `fill-mask`, что совершенно нормально. Но если бы мы загрузили эту контрольную точку в пайплайн `text-classification`, результаты не имели бы никакого смысла, потому что выходной слой `camembert-base` не подходит для этой задачи! Мы рекомендуем использовать селектор задач в интерфейсе Hugging Face Hub, чтобы выбрать соответствующие чекпоинты:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter4/tasks.png" alt="The task selector on the web interface." width="80%"/>
</div>

Вы также можете инициализировать модель не через пайплайн, а путем создания экземпляра класса модели:

{#if fw === 'pt'}
```py
from transformers import CamembertTokenizer, CamembertForMaskedLM

tokenizer = CamembertTokenizer.from_pretrained("camembert-base")
model = CamembertForMaskedLM.from_pretrained("camembert-base")
```

Однако вместо этого мы рекомендуем использовать [`Auto*` классы](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes), так как они по своей конструкции не зависят от архитектуры используемой модели. В то время как предыдущий пример кода ограничивает пользователей чекпоинтами, загружаемыми в архитектуре CamemBERT, использование классов `Auto*` упрощает переключение между чекпоинтами:

```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")
```

Однако вместо этого мы рекомендуем использовать [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) так как они по своей конструкции не зависят от архитектуры используемой модели. В то время как предыдущий пример кода ограничивает пользователей чекпоинтами, загружаемыми в архитектуре CamemBERT, использование классов `TFAuto*` упрощает переключение между чекпоинтами: 

```py
from transformers import AutoTokenizer, TFAutoModelForMaskedLM

tokenizer = AutoTokenizer.from_pretrained("camembert-base")
model = TFAutoModelForMaskedLM.from_pretrained("camembert-base")
```
{/if}

<Tip>

При использовании предварительно обученной модели обязательно проверьте: как она была обучена, на каких наборах данных, ее ограничениях и смещениях. Вся эта информация должна быть указана в карточке модели.
</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter4/2.mdx" />

### Fine-tuning, итоги!
https://huggingface.co/learn/course/ru/chapter3/5.md

# Fine-tuning, итоги!

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

Это было весело! В первых двух главах вы узнали о моделях и токенизаторах, и теперь вы знаете как применить fine-tuning на собственных данных.
Напомним, в этой главе вы: 

{#if fw === 'pt'}
* Узнали о датасетах из [Hub](https://huggingface.co/datasets)
* Узнали как загрузить и предобработать данные (включая динамический padding и collator)
* Реализовали свой fine-tuning и валидировали модель
* Реализовали низко-уровневый обучающий цикл
* Использовали 🤗 Accelerate для легкой адаптации обучающего цикла к нескольким GPU или TPU

{:else}
* Узнали о датасетах из [Hub](https://huggingface.co/datasets)
* Узнали как загрузить и предобработать данные
* Узнали как реализовать fine-tuning и валидировать модель с исползованием keras
* Реализовали собственную метрику

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter3/5.mdx" />

### Введение
https://huggingface.co/learn/course/ru/chapter3/1.md

# Введение

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

В [главе 2](../chapter2/1) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим:  

{#if fw === 'pt'}
* Как подготовить большой датасет из Model Hub
* Как использовать высокоуровненое API для дообучения модели
* Как использовать собственный цикл обучения (training loop)
* Как использовать библиотеку 🤗 Accelerate для запуска собственного цикла обучения на распределенной вычислительной структуре

{:else}
* Как подготовить большой датасет из Model Hub
* Как использовать Keras для дообучения модели
* Как использовать Keras для получения предсказаний
* Как использовать собственную метрику


{/if}

Чтобы загрузить свои чекпоинты на Hugging Face Hub, необходимо иметь учетную запись: [создать аккаунт](https://huggingface.co/join)

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter3/1.mdx" />

### Fine-tuning модели с использованием Trainer API
https://huggingface.co/learn/course/ru/chapter3/3.md

# Fine-tuning модели с использованием Trainer API

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter3/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter3/section3.ipynb"},
]} />

<Youtube id="nvBXf7s7vTI"/>

Библиотека 🤗 Transformers предоставляет класс `Trainer`, который помогает произвести fine-tuning любой предобученной модели на вашем датасете. После предобработки данных, сделанных в прошлом разделе, вам останется сделать несколько шагов для определения `Trainer`. Самая сложная часть – подготовить окружение для запуска `Trainer.train()`, т.к. она медленно работает на центральном процессоре. Если у вас нет видеокарты, вы можете бесплатно воспользоваться сервисом [Google Colab](https://colab.research.google.com/), который предоставляет доступ к вычислениям с использованием GPU и TPU.

Фрагменты кода ниже предполагают, что вы выполнили код из предыдущего раздела. Ниже приведено краткое резюме того, что вам нужно: 

```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)
```

### Обучение

Первый шаг перед определением `Trainer`, задание класса `TrainingArguments`, который будет содержать все гиперпараметры для `Trainer` (для процессов обучения и валидации). Единственный аргумент, который вы должны задать — это каталог, в котором будет сохранена обученная модель, а также контрольные точки. Для всего остального вы можете оставить значения по умолчанию, которые должны подойти для базового fine-tuning.


```py
from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")
```

<Tip>

💡 Если вы хотите автоматически загружать модель на Hub во время обучения, передайте аргумент `push_to_hub=True` в `TrainingArguments`. Мы больше узнаем об этом в [главе 4](../chapter4/3). 

</Tip>

Второй шаг – задание модели. Так же, как и в [предыдущей главе](../chapter2/1), мы будем использовать класс `AutoModelForSequenceClassification` с двумя лейблами: 

```py
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

После создания экземпляра предобученной модели будет распечатано предупреждение (в [главе 2](../chapter2/1) мы с таким не сталкивались). Это происходит потому, что 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` как в примере выше, значение по умолчанию для `data_collator` в `Trainer` будет `DataCollatorWithPadding` таким, как определено выше, так что эту строку (`data_collator=data_collator`) можно пропустить в этом вызове.  

<!-- Note that when you pass the `tokenizer` as we did here, the default `data_collator` used by the `Trainer` will be a `DataCollatorWithPadding` as defined previously, so you can skip the line `data_collator=data_collator` in this call. It was still important to show you this part of the processing in section 2! -->

Для fine-tuning модели на нашем датасете мы просто должны вызвать метод `train()` у `Trainer`: 

```py
trainer.train()
```

Это запустит процесс дообучения (который должен занять несколько минут на GPU) и будет распечатывать значение лосса каждые 500 итераций. Однако эти значения не скажут нам, насколько хорошо или плохо модель работает. И вот почему: 

1. Мы не сообщили `Trainer`, что необходимо проводить валидацию: для этого нужно присвоить аргументу `evaluation_strategy` значение `"steps"` (валидировать каждые `eval_steps`) или `"epoch"` (валидировать по окончании каждой эпохи). 
2. Мы не указали `Trainer` аргумент `compute_metrics()` – функцию для вычисления метрики на валидационной части (в таком случае в процессе валидации будет только распечатываться значение лосса, что не очень информативно). 


### Валидация

Давайте посмотрим как мы можем создать и использовать в процессе обучения полезную функцию `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` будет содержать значение лосса на нашем датасете и значения метрик. После реализации функции `compute_metrics()` и передачи ее в `Trainer` поле `metrics` также будет содержать результат функции `compute_metrics()`. 

Как можно заметить, `predictions` - массив 408 х 2 (408 - число элементов в датасете, который мы использовали). Это логиты для каждого элемента нашего датасета, переданного в `predict()` (как вы видели в [предыдущей главе](../chapter2/1) все модели Трансформеров возвращают логиты). Чтобы превратить их в предсказания и сравнить с нашими лейблами, нам необходимо узнать индекс максимального элемента второй оси: 

```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% на валидационной части выборки. Эти метрики используются для валидации результатов на MRPC датасете на бенчмарке GLUE. В таблице в статье о [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)
```

Чтобы увидеть эту функцию в действии после каждой эпохи ниже мы определим еще один `Trainer` с функцией `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,
)
```

Обратите внимание, что мы создали новый объект `TrainingArguments` с собственным `evaluation_strategy` равным `"epoch"` и новой моделью - иначе мы бы продолжили обучать модель, которая уже является обученной. Чтобы запустить обучение заново, надо выполнить: 

```
trainer.train()
```

На этот раз будет распечатываться валидационный лосс и метрики по окончанию каждой эпохи обучения. Напомним, что полученные значения точности и F1 могут не полностью совпадать с приведенными в примере из-за случайной инициализации слоев модели, но порядок должен быть примерно таким же. 

`Trainer` может работать с несколькими GPU или TPU и предоставляет множество опций, например применение техники mixed-precision (установите `fp16 = True` в аргументах). Подробно об опциях мы поговорим чуть в Главе 10. 

На этом введение в fine-tuning с использованием API `Trainer` подошло к концу. Пример того, как сделать это же для наиболее распространенных задач  NLP мы рассмотрим в Главе 7, а сейчас взглянем на то, как реализовать то же самое на чистом PyTorch. 

<Tip>

✏️ **Попробуйте!** Произведите fine-tuning модели на датасете GLUE SST-2 с использованием препроцессинга из раздела 2.

</Tip>



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter3/3.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter3/6.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={3}
    classNames="absolute z-10 right-0 top-0"
/>

Тест по результатам изучения главы 3. 

### Датасет `emotion` содержит сообщения из Твиттера, каждое сообщение помечено какой-либо эмоцией. Найдите его на [Hub](https://huggingface.co/datasets) и изучить карточку датасета. Какая из этих эмоцией не является базовой?

<Question
	choices={[
		{
			text: "Joy",
			explain: "Попробуйте еще раз, этот класс присутствует в датасете!"
		},
		{
			text: "Love",
			explain: "Попробуйте еще раз, этот класс присутствует в датасете!"
		},
		{
			text: "Confusion",
			explain: "Правильно! Замешательство не является одним из 6 базовых классов датасета.",
            correct: true
		},
        {
			text: "Surprise",
			explain: "Сюрприз! Попробуйте другой вариант!"
		}
	]}
/>

### 2. Найдите датасет `ar_sarcasm` в [Hub](https://huggingface.co/datasets). Какую задачу можно решить с использованием этого датасета?

<Question
	choices={[
		{
			text: "Sentiment classification / Классификация тональности",
			explain: "Правильно! Поблагодарите за это тэги. ",
            correct: true
		},
		{
			text: "Machine translation / Машинный перевод",
			explain: "Нет, изучите еще раз <a href='https://huggingface.co/datasets/ar_sarcasm'>карточку</a> датасета!"
		},
		{
			text: "Named entity recognition / Распознавание именованных сущностей",
			explain: "Нет, изучите еще раз <a href='https://huggingface.co/datasets/ar_sarcasm'>карточку</a> датасета!"
		},
        {
			text: "Question answering / Ответы на вопросы",
			explain: "Увы! Неправильный ответ. "
		}
	]}
/>

### 3. В каком формате модель BERT ожидает на вход пару предложений?

<Question
	choices={[
		{
			text: "Токены_предложения_1 [SEP] Токены_предложения_2",
			explain: "Токен <code>[SEP]</code> – специальный токен для разделения двух предложений, однако этого недостаточно."
		},
		{
			text: "[CLS] Токены_предложения_1 Токены_предложения_2",
			explain: "Токен <code>[CLS]</code> – специальный токен, обозначающий начало последовательнсти, однако этого недостаточно."
		},
		{
			text: "[CLS] Токены_предложения_1 [SEP] Токены_предложения_2 [SEP]",
			explain: "Правильно!",
            correct: true
		},
        {
			text: "[CLS] Токены_предложения_1 [SEP] Токены_предложения_2",
			explain: "Токен <code>[CLS]</code> – специальный токен, обозначающий начало последовательнсти,  Токен <code>[SEP]</code> – специальный токен для разделения двух предложений. Но это не всё!"
		}
	]}
/>

{#if fw === 'pt'}
### 4. Какие преимущества есть у метода `Dataset.map()?`

<Question
	choices={[
		{
			text: "Результаты функции кэшируются, поэтому повторное выполнение кода не займет много времени.",
			explain: "Это действительно одно из преимуществ этого метода! Впрочем, не единственное...",
            correct: true
		},
		{
			text: "Можно применить мультипроцессорную обработку – это быстрее, чем применять функцию к каждому элементу выборки.",
			explain: "Это преимущество данного метода, но не единственное.",
            correct: true
		},
		{
			text: "Он не загружает весь набор данных в память, сохраняя результаты сразу после обработки одного элемента.",
			explain: "Это одно из преимуществ этого метода. Хотя есть и другие!",
            correct: true
		},
	]}
/>

### 5. Что такое dynamic padding?

<Question
	choices={[
		{
			text: "Дополнение нулями каждой входной последовательности до максимальной длины, содержащейся в датасете.",
			explain: "Подразумевает дополнение при создании батча, но не до максимальной длины во всем датасете."
		},
		{
			text: "Дополнение нулями при создании батча до максимальной длины предложения, содержашегося в батче.",
			explain: "Правильно! \"Динамическим\" пэддинг становится в момент анализа каждого элемента батча. Все батчи могут иметь разную длину элементов.",
            correct: true
		},
		{
			text: "Такое дополнение, при котором длина каждого предложения равна длине предыдущего предложения.",
			explain: "Неправильно. Хотя бы потому, что мы перемешиваем датасет во время обучения."
		},
	]}
/>

### 6. Какова цель функции сопоставления (collate function)?

<Question
	choices={[
		{
			text: "Она проверяет, что все предложения в датасете одинаковой длины.",
			explain: "Функция сопоставления используется для обработки отдельных батчей, а не всего датасета. Кроме того, мы говорим об общих collate-функциях, а не конкретно о <code>DataCollatorWithPadding</code>."
		},
		{
			text: "Она соединяет вместе все элементы батча.",
			explain: "Верно! Вы можете передать функцию сопоставления в качестве аргумента для <code>DataLoader</code>. Мы использовали функцию <code>DataCollatorWithPadding</code>, которая дополняет все элементы в батче до одинаковой длины.",
            correct: true
		},
		{
			text: "Она обрабатывает весь датасет. ",
			explain: "Тогда она называлась быть функцией препроцессинга, а не функцией сопоставления."
		},
        {
			text: "Она обрезает предложения в датасете.",
			explain: "Collate-функция используется для одного батча, а не всего датасета. Если вам необходимо обрезать датасет, вы можете использовать аргумент <code>truncate</code> в <code>tokenizer</code>."
		}
	]}
/>

### 7. Что происходит, когда вы создаете экземпляр одного из классов `AutoModelForXxx` с предварительно обученной языковой моделью (например, `bert-base-uncased`), которая соответствует задаче, отличной от той, для которой она была обучена? 

<Question
	choices={[
		{
			text: "Ничего, но будет выведено предупреждение.",
			explain: "Предупреждение действительно появится, но это не всё!"
		},
		{
			text: "Последний слой модели будет заменён на слой, подходящий для текущей задачи.",
			explain: "Правильно. Например, когда мы используем <code>AutoModelForSequenceClassification</code> с <code>bert-base-uncased</code> чекпоинтом, распечатывается предупреждение при инициализации модели. Предобученная «голова» модели не используется для классификации предложений, так что она заменяется другим слоем со случайно инициализированными весами.",
            correct: true
		},
		{
			text: "Последний слой модели игнорируется.",
			explain: "Должно произойти что-то еще! Попробуй еще раз!"
		},
        {
			text: "Ничего, модель по-прежнему можно будет настроить на решение другой задачи.",
			explain: "Последний слой модели был обучен решать другую задачу, значит с ним должно что-то произойти!"
		}
	]}
/>

### 8. Зачем нужен `TrainingArguments`?

<Question
	choices={[
		{
			text: "Он содержит все гиперпараметры, используемые для обучения и оценки с помощью <code>Trainer</code>",
			explain: "Верно!",
            correct: true
		},
		{
			text: "Задает размер модели.",
			explain: "Размер модели определяется ее структурой, а не классом <code>TrainingArguments</code>."
		},
		{
			text: "Содержит гиперпараметры для этапа валидации модели.",
			explain: "В примере мы задавали, где будут сохраняться модель и её веса. Попробуй еще раз!"
		},
        {
			text: "Он содержит гиперпараметры этапа обучения.",
			explain: "В примере мы использовали <code>evaluation_strategy</code>, что также влияет на валидацию. Попробуй еще раз!"
		}
	]}
/>

### 9. Зачем нужна библиотека 🤗 Accelerate?

<Question
	choices={[
		{
			text: "Предоставляет доступ к более быстрым моделям.",
			explain: "Нет, 🤗 Accelerate не предоставляет доступа к каким-либо ни было моделям."
		},
		{
			text: "Предоставляет высокоуровневый API и появляется возможность не реализовывать собственный цикл обучения.",
			explain: "Это мы делаем с помощью <code>Trainer</code>, а не 🤗 Accelerate. Попробуй еще раз!"
		},
		{
			text: "Позволяет исполнить наш цикл обучения на распределенных системах.",
			explain: "Праивльно! С помощью 🤗 Accelerate обучающий цикл будет исполняться на нескольких GPU или TPU.",
            correct: true
		},
        {
			text: "Предоставляет больше оптимизационных функций.",
			explain: "Нет, 🤗 Accelerate не предоставляет оптимизационных функций."
		}
	]}
/>

{:else}
### 4. Что происходит, когда вы создаете экземпляр одного из классов `TFAutoModelForXxx` с предварительно обученной языковой моделью (например, `bert-base-uncased`), которая соответствует задаче, отличной от той, для которой она была обучена? 

<Question
	choices={[
		{
			text: "Ничего, но будет выведено предупреждение.",
			explain: "Предупреждение действительно появится, но это не всё!"
		},
		{
			text: "Последний слой модели будет заменён на слой, подходящий для текущей задачи.",
			explain: "Правильно. Например, когда мы используем <code>AutoModelForSequenceClassification</code> с <code>bert-base-uncased</code> чекпоинтом, распечатывается предупреждение при инициализации модели. Предобученная «голова» модели не используется для классификации предложений, так что она заменяется другим слоем со случайно инициализированными весами.",
            correct: true
		},
		{
			text: "Последний слой модели игнорируется.",
			explain: "Должно произойти что-то еще! Попробуй еще раз!"
		},
        {
			text: "Ничего, модель по-прежнему можно будет настроить на решение другой задачи.",
			explain: "Последний слой модели был обучен решать другую задачу, значит с ним должно что-то произойти!"
		}
	]}
/>

### 5. TensorFlow-модели из `transformers` уже можно рассматривать как Keras-модели. Какие преимущества это дает?

<Question
	choices={[
		{
			text: "Модели будут работать на TPU «из коробки».",
			explain: "Почти! Необходимо внести ряд дополнительных изменений, например: следует запустить все с помощью <code>TPUStrategy</code> (включая инициализацию модели)."
		},
		{
			text: "Вы сможете испольовать существующие методы, такие как <code>compile()</code>, <code>fit()</code> и <code>predict()</code>.",
			explain: "Верно! Данные у вас уже есть, дело осталось за малым – обучить модель. ",
            correct: true
		},
		{
			text: "Вы сможете изучить и Keras, и transformers.",
			explain: "Верно! Но ответ все же немного другой :)",
			correct: true
		},
        {
			text: "Вы можете просто вычислить метрики, связанные с датасетом.",
			explain: "Keras помогает в обучении и валидации модели, а не с вычислением метрик."
		}
	]}
/>

### 6. Как мы можем задать собственную метрику?

<Question
	choices={[
		{
			text: "Наследуя <code>tf.keras.metrics.Metric</code>.",
			explain: "Великолепно!",
			correct: true
		},
		{
			text: "С использованием функционального API Keras.",
			explain: "Try again!"
		},
		{
			text: "С использованием вызываемого модуля <code>metric_fn(y_true, y_pred)</code>.",
			explain: "Верно!",
			correct: true
		},
        {
			text: "Загуглив её!",
			explain: "Это не тот ответ, который мы ожидаем, однако это должно помочь вам!",
			correct: true
		}
	]}
/>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter3/6.mdx" />

### Fine-tuning модели с использованием Keras
https://huggingface.co/learn/course/ru/chapter3/3_tf.md

# Fine-tuning модели с использованием Keras

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter3/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter3/section3_tf.ipynb"},
]} />

После того, как вы выполнили всю работу по предварительной обработке данных в последнем разделе, у вас осталось всего несколько шагов для обучения модели. Обратите внимание, однако, что команда `model.fit()` будет работать очень медленно на CPU. Если у вас нет настроенного графического процессора, вы можете получить доступ к бесплатным графическим процессорам или TPU на[Google Colab](https://colab.research.google.com/).

В приведенных ниже примерах кода предполагается, что вы уже выполнили примеры из предыдущего раздела. Вот краткий обзор того, что вам нужно:

```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,
)
```

### Обучение

Модели TensorFlow, импортированные из 🤗 Transformers, уже являются моделями Keras. Вот краткое введение в Keras.

<Youtube id="rnTGBy2ax1c"/>

Это означает, что когда у нас есть данные, остается совсем немного до начала обучения.

<Youtube id="AUozVp78dhk"/>

Как и в [предыдущей главе](../chapter2/1), мы будем использовать класс `TFAutoModelForSequenceClassification` с двумя метками:

```py
from transformers import TFAutoModelForSequenceClassification

model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

Вы заметите, что в отличие от [Главы 2](../chapter2/1), вы получаете предупреждение после создания экземпляра этой предварительно обученной модели. Это связано с тем, что BERT не был предварительно обучен классификации пар предложений, поэтому последний слой предварительно обученной модели был отброшен, а вместо него был вставлен новый слой, подходящий для классификации последовательностей. Предупреждения указывают на то, что некоторые веса не использовались (те, которые соответствуют удаленным слоям), а некоторые другие были инициализированы случайным образом (те, что для новых слоев). В заключение предлагается обучить модель, что мы и собираемся сделать сейчас.

Чтобы точно настроить модель в нашем наборе данных, нам просто нужно вызвать `compile()` у нашей модели, а затем передать наши данные в метод `fit()`. Это запустит процесс fine tuning (который должен занять пару минут на графическом процессоре) и сообщит о значениях функции потерь при обучении, а также о значениях функции потерь на валидации.

<Tip>

Обратите внимание, что у моделей 🤗 Transformers есть особая способность, которой нет у большинства моделей Keras — они могут автоматически использовать соответствующие функции потерь. Они будут использовать эти потерю по умолчанию, если вы не установите аргумент `loss` в `compile()`. Обратите внимание, что для использования внутренней функции вам нужно будет передать свои метки классов как часть обучающих данных, а не как отдельную метку, что является обычным способом использования меток с моделями Keras. Вы увидите примеры этого во второй части курса, где определение правильной функции потерь может быть сложным. Однако для классификации последовательностей стандартная функция потерь Keras отлично работает, поэтому мы будем использовать ее здесь.

</Tip>

```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,
)
```

<Tip warning={true}>

Обратите внимание на очень распространенную ошибку — в Keras функцию потерь можно задать просто текстовым значением, но по умолчанию Keras будет считать, что вы уже применили softmax к своим выходам. Однако многие модели выводят значения непосредственно перед применением softmax, так называемые *логиты*. Нам нужно указать это в функции потерь, а единственный способ сделать это — вызвать ее напрямую, а не по имени в виде строки.

</Tip>


### Повышение производительности обучения

<Youtube id="cpzq6ESSM5c"/>

Если вы запустите приведенный выше код, он, конечно, заработает, но вы обнаружите, что потери снижаются медленно или спорадически. Основная причина это *скорость обучения* (*learning rate*). Как и в случае потери, когда мы передаем Keras имя оптимизатора в виде строки, Keras инициализирует этот оптимизатор со значениями по умолчанию для всех параметров, включая скорость обучения. Однако из многолетнего опыта мы знаем, что модели-трансформеры выигрывают от гораздо более низкой скорости обучения, чем по умолчанию для Adam - 1e-3 (1e-3 = 0.001). Значение 5e-5 (0.00005) примерно в двадцать раз ниже, и это гораздо более эффективное изначальное значение.

В дополнение к снижению скорости обучения у нас есть еще одна хитрость: мы можем медленно снижать скорость обучения процессе обучения. В литературе вы иногда можете встретить термин «убывание» или «отжиг» скорости обучения. В Keras лучший способ сделать это — использовать планировщик скорости обучения. Хороший вариант для использования `PolynomialDecay` — несмотря на название, с настройками по умолчанию он просто линейно занижает скорость обучения от начального значения до конечного значения - это именно то, что нам нужно. Чтобы правильно использовать планировщик,
тем не менее, нам нужно сообщить ему, как долго будет длиться обучение. Мы вычисляем это как `num_train_steps` ниже.

```py
from tensorflow.keras.optimizers.schedules import PolynomialDecay

batch_size = 8
num_epochs = 3
# 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_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)
```

<Tip>

В библиотеке 🤗 Transformers также есть функция `create_optimizer()`, которая создаст оптимизатор `AdamW` с уменьшением скорости обучения. Это удобный способ, с которым вы подробно познакомитесь в следующих разделах курса.

</Tip>

Теперь у нас есть новый оптимизатор, и мы можем попробовать обучить модель с помощью него. Во-первых, давайте перезагрузим модель, чтобы сбросить изменения весов из тренировочного прогона, который мы только что сделали, а затем мы можем скомпилировать ее с помощью нового оптимизатора:

```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"])
```

Запускаем обучение вновь:

```py
model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3)
```

<Tip>

💡 Если вы хотите автоматически загружать свою модель на Hub во время обучения, вы можете передать `PushToHubCallback` в метод `model.fit()`. Мы узнаем об этом больше в [Главе 4](../chapter4/3). 

</Tip>

### Применение модели для классификации

<Youtube id="nx10eh4CoOs"/>

Обучение и наблюдение за снижением значений функции потерь — это очень хорошо, но что, если мы действительно хотим получить результаты от обученной модели, либо для вычисления некоторых показателей, либо для использования модели в производстве? Для этого мы можем просто использовать метод `predict()`. Это вернет *логиты* из модели, по одному на класс.

```py
preds = model.predict(tf_validation_dataset)["logits"]
```

Мы можем сконвертировать логиты в значение класса с помощью функции `argmax` для поиска максимального значения логита, которое соответствует наиболее правдоподобному классу. 


```py
class_preds = np.argmax(preds, axis=1)
print(preds.shape, class_preds.shape)
```

```python out
(408, 2) (408,)
```

Теперь давайте используем эти `preds` для вычисления некоторых метрик! Мы можем загрузить метрики, связанные с датасетом MRPC, так же легко, как мы загрузили этот датасет, на этот раз с помощью функции `evaluate.load()`. Возвращаемый объект имеет метод `compute()`, который мы можем использовать для вычисления метрики:

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

Точные результаты, которые вы получите, могут отличаться, так как случайная инициализация параметров выходных слоев модели может изменить показатели. Здесь мы видим, что наша модель имеет точность 85,78% на валидационном наборе и оценку F1 89,97. Это две метрики, используемые для оценки результатов датасета MRPC для теста GLUE. В таблице в [документации BERT] (https://arxiv.org/pdf/1810.04805.pdf) сообщается о балле F1 88,97% для базовой модели. Это была модель, которая не чувствительна к регистру текста, в то время как сейчас мы используем модель, учитывающую регистр, что и объясняет лучший результат.

На этом введение в fine tuning с помощью Keras API завершено. Пример выполнения этого для наиболее распространенных задач NLP будет дан в [Главе 7](../chapter7). Если вы хотите отточить свои навыки работы с Keras API, попробуйте точно настроить модель в наборе данных GLUE SST-2, используя обработку данных, которую вы выполнили в разделе 2.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter3/3_tf.mdx" />

### Полное обучение
https://huggingface.co/learn/course/ru/chapter3/4.md

# Полное обучение

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter3/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter3/section4.ipynb"},
]} />

<Youtube id="Dh9CL8fyG80"/>

Теперь мы посмотрим, как достичь результатов из предыдущей главы без использования класса `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)
```

### Подготовка к обучению

Перед реализацией цикла обучения необходимо задать несколько объектов. Первый: загрузчики данных (далее - dataloaders), которые мы будем использовать для итерирования по батчам данных. Перед этим нам необходимо применить несколько операций постпроцессинга к нашему `tokenized_datasets`. Это нужно сделать: в прошлый раз за нас это автоматически делал `Trainer`. Необходимо сделать следующее: 


- Удалить колонки, соответствующие значениям, которые модель не принимает на вход (например, `sentence1` и `sentence2`).
- Переименовать колонку `label` в `labels` (потому что модель ожидает аргумент, названный `labels`). 
- Задать тип данных в датасете pytorch tensors вместо списков. 

Наш `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"]
```

Теперь, когда датасет готов, мы может задать dataloader:

```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` для обучающего загрузчика данных, также мы дополняем (padding) до максимальной длины внутри батча.

Теперь мы полностью завершили этап препроцессинга (приятный, но неуловимый момент для любого специалиста по машинному обучению), перейдем к модели. Мы инициализируем ее точно так, как делали в предыдущем примере: 

```py
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

Чтобы убедиться, что обучение пойдет гладко, вы подадим на вход модели один батч:

```py
outputs = model(**batch)
print(outputs.loss, outputs.logits.shape)
```

```python out
tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2])
```

Все модели 🤗 трансформеров возвращают значение функции потерь, если в данных были `labels`, а также логиты (в результате получается тензор 8 х 2). 

Мы почти готовы к написанию обучающего цикла! Мы пропустили только две вещи: оптимизатор и планировщик скорости обучения (learning rate scheduler). Ввиду того, что мы пытаемся повторить вручную то, что делал за нас `Trainer`, мы будем использовать такие же значения по умолчанию. Оптимизатор, используемый в `Trainer` - `AdamW`, который является почти полной копией Adam, за исключением трюка с сокращением весов (далее - weight decay) (см. ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) за авторством Ilya Loshchilov и Frank Hutter). 

```py
from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)
```

Наконец, планировщик скорости обучения по умолчанию - просто линейное уменьшение весов с максимального значения (5e-5) до 0. Чтобы корректно задать его, нам нужно знать число шагов в обучении, которое задается как произведение числа эпох и числа батчей (длины нашего загрузчика данных). Число эпох по умолчанию в `Trainer` равно 3, так же мы зададим его и сейчас: 

```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 в случае, если у нас будет такая возможность (на CPU процесс может занять несколько часов вместо пары минут). Чтобы добиться этого, мы определим переменную `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()`. После того, как мы пройдемся по всем батчам, мы сможем вычислить финальный результат с помощью `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}
```

Повторим: результаты, которые получите вы, могут немного отличаться из-за наличия случайностей при инициализации параметров слоя модели и из-за случайного перемешивания датасета, однако их порядок должен совпадать.

<Tip>

✏️ **Попробуйте!** Измените обучающий цикл так, чтобы дообучить модель на датасете SST-2. 

</Tip>

### Ускорение обучающего цикла с помощью 🤗 Accelerate

<Youtube id="s7dy8QRgjJ0" />

Обучающий цикл, заданный выше, отлично работает на одном GPU или CPU. Однако использование библиотеки [🤗 Accelerate](https://github.com/huggingface/accelerate) позволяет с небольшими изменениями сделать эту процедуру распределенной на несколько GPU или TPU. Начииная с момента создания обучающих и валидационных загрузчиков данных, наш «ручной» обучающий цикл выглядит так: 

```py
from torch.optim import AdamW
from transformers import 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 torch.optim import AdamW
  from transformers import 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`, который проанализирует окружение и определит необходимые настройки. 🤗 Accelerate автоматически использует доступное оборудование, поэтому вы можете удалить строки, которые «прикрепляют»
 модель и данные к видеокарте (или, если вам так удобнее, можете изменить их на `accelerator.device` вместо просто `device`). 

Далее главная часть работы выполняется в строке, которая отправляет данные, модель и оптимизатор на `accelerator.prepare()`. Этот метод «обернет» ваши объекты в контейнер и убедится, что распределенное обучение выполняется корректно. Оставшиеся изменения – удаление строки, которая отправляет батч на `device` (повторим: если вы хотите оставить эту строку, замените `device` на `accelerator.device`) и замените `loss.backward()` на `accelerator.backward(loss)`.

<Tip>
⚠️ Чтобы воспользоваться ускорением, предлагаемым облачными TPU, мы рекомендуем дополнять данные до фиксированной длины с помощью аргументов `padding="max_length"` и `max_length` токенизатора.
</Tip>

Если вы хотите скопировать и запустить этот код, это полная версия с использованием 🤗 Accelerate:

```py
from accelerate import Accelerator
from torch.optim import AdamW
from transformers import 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
```

запускающей распределенное обучение. 

Если вы хотите попробовать запустить этот код в Jupyter Notebook (например, протестировать его с TPU на Google Colab), просто вставьте код в `training_function()` и запустите последнюю ячейку:

```python
from accelerate import notebook_launcher

notebook_launcher(training_function)
```

Вы можете найти больше примеров в репозитории [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples).


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter3/4.mdx" />

### Предобработка данных
https://huggingface.co/learn/course/ru/chapter3/2.md

# Предобработка данных

{#if fw === 'pt'}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter3/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter3/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter3/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter3/section2_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
Продолжим с примером из [предыдущей главы](../chapter2/1), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch:

```python
import torch
from torch.optim import AdamW
from transformers import AutoTokenizer, AutoModelForSequenceClassification

# Так же, как и в прошлый раз
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")

# Эта часть новая
batch["labels"] = torch.tensor([1, 1])

optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()
```
{:else}
Continuing with the example from the [previous chapter](../chapter2/1), вот как мы будем обучать классификатор последовательности на одном батче с помощью TensorFlow:

```python
import tensorflow as tf
import numpy as np
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

# Так же, как и в прошлый раз
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"))

# Эта часть новая
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
labels = tf.convert_to_tensor([1, 1])
model.train_on_batch(batch, labels)
```
{/if}

Обучение всего лишь на двух предложениях, конечно, не даст хорошего результата. Чтобы получить более качественные результаты, вам следует подготовить б_о_льший датасет.

В данном разделе мы будем использовать в качестве примера MRPC (Microsoft Research Paraphrase Corpus) dataset, предложенный в [статье](https://www.aclweb.org/anthology/I05-5002.pdf) авторами William B. Dolan и Chris Brockett. Датасет состоит из 5801 пар предложений с соответствующим им лейблом: является ли пара преложений парафразами или нет (т.е. идет ли речь в обоих предложениях об одном и том же). Мы выбрали именно этот датасет для этой главы потому что он небольшой: с ним легко экспериментировать в процессе обучения.

### Загрузка датасета с Hub

{#if fw === 'pt'}
<Youtube id="_BZearw7f0w"/>
{:else}
<Youtube id="W_gMJF0xomE"/>
{/if}

Hub содержит не только модели, там также расположено множество датасетов на различных языках. Вы можете посмотреть на них [тут](https://huggingface.co/datasets), а также мы рекомендуем попровать загрузить новый датасет после того, как вы изучите текущий раздел (см. документацию [здесь](https://huggingface.co/docs/datasets/loading)). Но сейчас вернемся к датасету MRPC! Это один из 10 датасетов из состава [GLUE](https://gluebenchmark.com/), который является тестом для производительности моделей машинного обучения в задачах классификации текста.

Библиотека 🤗 Datasets предоставляет возможность использовать очень простую команду для загрузки и кэширования датасета с Hub. Мы можем загрузить датасет следующим образом: 

<Tip>
⚠️ **Предупреждение** Убедитесь, что `datasets` установлены, выполнив `pip install datasets`. Затем загрузите набор данных MRPC и выведите его, чтобы увидеть, что он содержит.
</Tip>

```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`, и `idx`) и переменную с числом строк (число элементов в каждой выборке): 3668 пар предложений в обучающей части, 408 в валидационной и 1725 в тестовой .

Эта команда загружает и кэширует датасет (по умолчанию в *~/.cache/huggingface/dataset*). Вспомним из главы 2, что вы можете изменить путь к кэшу изменив переменную окружения `HF_HOME`. 

Мы можем получить доступ к предложениями в объекте `raw_datasets` путем индексирования, как в словаре: 

```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 .'}
```

Можно увидеть, что лейблы уже являются целыми числами (integer), их обрабатывать не нужно. Чтобы сопосотавить индекс класса с его названием, можно распечатать значение переменной `features` у `raw_train_dataset`: 

```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` соответствует именам в *names*. `0` соответствует `not_equivalent`, `1` соответствует `equivalent`. 

<Tip>

✏️ **Попробуйте!** Посмотрите на 15-й элемент обучающей выборки и на 87-й элемент вадидационной выборки. Какие у них лейблы?

</Tip>

### Предобработка датасета

{#if fw === 'pt'}
<Youtube id="0u3ioSwev3s"/>
{:else}
<Youtube id="P-rZWqcB6CE"/>
{/if}

Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](../chapter2/1), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: 

```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]
}
```

Мы уже обсуждали ключи `input_ids` и `attention_mask` в [главе 2](../chapter2/1), но не упоминали о `token_type_ids`. В этом примере мы указываем модели какая часть входных данных является первым предложением, а какая вторым. 

<Tip>

✏️ **Попробуйте!** Токенизируйте 15-й элемент обучающей выборки как два предложения, и как пару предложений. В чем разница между двумя результатами?

</Tip>

Если мы декодируем ID из `input_ids` обратно в слова: 

```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]` в случае двух предложений. Посмотрим соответствие элементов и `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]` имеют тип токена `0`, в то время как остальные части, соответствующие второму предложению `sentence2 [SEP]`, имеют тип токена `1`.

Обратите внимание, что если вы выберете другой чекпоинт, `token_type_ids` необязательно будут присутствовать в ваших токенизированных входных данных (например, они не возвращаются, если вы используете модель DistilBERT). Они возвращаются только тогда, когда модель будет знать, что с ними делать, потому что она видела их во время предобучения.

В данном случае BERT был обучен с информацией о идентификаторах типов токенов, и помимо задачи маскированной языковой модели, о которой мы говорили в [главе 1](../chapter1/1), он может решать еще одну задачу: предсказание следующего предложения (_next sentence prediction_). Суть этой задачи - смоделировать связь между предложениями. 

В этой задаче модели на вход подаются пары предложений (со случайно замаскированными токенами), от модели требуется предсказать, является ли следующее предложение продолжением текущего. Чтобы задача не была слишком тривиальной, половина времени модель обучается на соседних предложениях из одного документа, другую половину на парах предложений, взятых из разных источников. 

В общем случае вам не нужно беспокоиться о наличии `token_type_ids` в ваших токенизированных данных: пока вы используете одинаковый чекпоинт и для токенизатора, и для модели – токенизатор будет знать, как нужно обработать данные. 

Теперь мы знаем, что токенизатор может подготовить сразу пару предложений, а значит мы можем использовать его для целого датасета: так же как и в [предыдущей главе](../chapter2/1) можно подать на вход токенизатору список первых предложений и список вторых предложений. Это также сработает и для механизмов дополнения (padding) и усечения до максимальной длины (truncation) - об этом мы говорили в [главе 2](../chapter2). Итак, один из способов предобработать обучающий датасет такой: 

```py
tokenized_dataset = tokenizer(
    raw_datasets["train"]["sentence1"],
    raw_datasets["train"]["sentence2"],
    padding=True,
    truncation=True,
)
```

Это хорошо работает, однако есть недостаток, который формирует токенизатор (с ключами,  `input_ids`, `attention_mask`, и `token_type_ids`, и значениями в формате списка списков). Это будет работать только если у нас достаточно оперативной памяти (RAM) для хранения целого датасета во время токенизации (в то время как датасеты из библиотеки 🤗 Datasets являются [Apache Arrow](https://arrow.apache.org/) файлами, хранящимися на диске; они будут загружены только в тот момент, когда вы их будете запрашивать). 

Чтобы хранить данные в формате датасета, мы будем использовать методы [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Это позволит нам сохранить высокую гибкость даже если нам нужно что-то большее, чем просто токенизация. Метод `map()` работает так: применяет некоторую функцию к каждому элементу датасета, давайте определим функцию, которая токенизирует наши входные данные:

```py
def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
```

Эта функция принимает на вход слвоарь (похожий на элементы нашего словаря) и возвращает новый словарь с ключами `input_ids`, `attention_mask` и `token_type_ids`. Заметьте, это также работает если словарь `example` содержит несколько элементов (каждый ключ в виде списка предложений), поскольку `tokenizer` работает и со списками пар предложений, как мы и видели ранее. Это позволит нам использовать аргумент `batched=True` в вызове `map()`, которая ускорит процесс токенизации. `tokenizer` внутри реализован на языке Rust из библиотеки [🤗 Tokenizers](https://github.com/huggingface/tokenizers). Этот токенизатор может быть очень быстрым, но только если мы подадим большой объем данных за раз. 

Обратите внимание, в этот раз мы оставили аргумент `padding` пустым, потому что дополнение данных до максимальной длины неэффективно: гораздо быстрее делать это во время формирования батча, в таком случае мы будем дополнять до максимальной длины только элементы батча, а не целого датасета. Это поможет сэкономить время в случае длинных последовательностей. 

Ниже пример того, как мы применяем функцию токенизации к целому датасету. Мы указываем `batched=True` в нашем вызове `map` и функция будет применена сразу к нескольким элементам датасета одновременно, а не к каждому по отдельности. Это позволяет сделать токенизацию более быстрой. 

```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`. Мы его не применяли, потому что библиоетека 🤗 Tokenizers сразу использует несколько потоков для токенизации, но если вы будете использовать функцию не из 🤗 Tokenizers, это может ускорить процесс. 

Наша функция `tokenize_function`  возвращает словарь с ключами `input_ids`, `attention_mask` и `token_type_ids`, они уже добавлены ко всем разбиениями нашего датасета. Обратите внимание, что мы могли бы также изменить существующие поля, если бы наша функция препроцессинга вернула новое значение для существующего ключа в наборе данных, к которому мы применили `map()`.

Последнее, что нам нужно сделать, это дополнить все примеры до длины самого длинного элемента, когда мы собираем элементы вместе — метод, который мы называем *динамическим пэддингом* (*dynamic padding*).


### Dynamic padding

<Youtube id="7q5NyFT8REg"/>

{#if fw === 'pt'}
Функция, отвечающая за объединение элементов внутри батча, называется *collate function* (функция сопоставления). Это аргумент, который вы можете передать при создании `DataLoader`, по умолчанию это функция, которая просто преобразует ваши образцы в тензоры PyTorch и объединяет их (рекурсивно, если вашими элементами являются списки, кортежи или словари). В нашем случае это невозможно, поскольку входные данные, которые у нас есть, не будут иметь одинакового размера. Мы намеренно не стали делать пэддинг, чтобы применять его только по мере необходимости в каждом пакете и избегать слишком длинных входных данных с большим количеством отступов. Это немного ускорит обучение, но учтите, что если вы тренируетесь на TPU, это может вызвать проблемы — TPU предпочитают фиксированные формы, даже если для этого требуется дополнительн пэддинг.

{:else}
Функция, отвечающая за объединение элементов внутри батча, называется *collate function* (функция сопоставления). По умолчанию — это функция, которая просто преобразует ваши образцы в tf.Tensor и объединяет их (рекурсивно, если ваши элементы — это списки, кортежи или словари). В нашем случае это невозможно, поскольку входные данные, которые у нас есть, не будут иметь одинакового размера. Мы намеренно отложили отступы, чтобы применять их только по мере необходимости в каждом пакете и избегать слишком длинных входных данных с большим количеством отступов. Это немного ускорит обучение, но учтите, что если вы тренируетесь на TPU, это может вызвать проблемы — TPU предпочитают фиксированные формы, даже если для этого требуется дополнительное дополнение.

{/if}
Для того, чтобы сделать это на практике, мы должны задать функцию сопоставления, которая будет осуществлять корректный пэддинг элементов выборки, которые мы хотим объединить в батч. К счастью, библиотека 🤗 Transformers предоставляет нам эту функцию через класс `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` т.к. они содержат строки (а мы не можем превратить строки в тензоры) и посмотрим на длину каждой записи в батче:


```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. 

No surprise, we get samples of varying length, from 32 to 67. Dynamic padding means the samples in this batch should all be padded to a length of 67, the maximum length inside the batch. Без динамического заполнения все выборки должны быть дополнены до максимальной длины во всем наборе данных, или до максимальной длины, которую может принять модель. Давайте дважды проверим, что наш `data_collator` динамически правильно дополняет батч:
```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])}
```

Выглядит неплохо! Теперь мы пришли от обычного текста к батчу, с которым может работать наша модель. Можем приступить к fine-tuning!

{/if}

<Tip>

✏️ **Попробуйте!** Повторите этап препроцессинга для набора данных GLUE SST-2. Он немного отличается, так как состоит из отдельных предложений, а не пар, но в остальном это то же самое, что мы сделали. Для более сложной задачи попробуйте написать функцию предварительной обработки, которая работает с любой из задач GLUE.

</Tip>

{#if fw === 'tf'}

Теперь, когда у нас есть набор данных и функция сопоставления данных, нам нужно собрать их вместе. Мы могли бы вручную загружать пакеты и применять к ним функцию, но это неудобно и не очень эффективно. Вместо этого есть простой метод, предлагающий эффективное решение этой проблемы: `to_tf_dataset()`. Эта функция обернет `tf.data.Dataset` вокруг вашего набора данных с дополнительной функцией сопоставления. `tf.data.Dataset` — это собственный формат TensorFlow, который Keras может использовать для `model.fit()`, поэтому этот метод немедленно преобразует 🤗 набор данных в формат, готовый для обучения. Давайте посмотрим на это в действии с нашим набором данных!

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


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter3/2.mdx" />

### Как написать хорошее сообщение об ошибке (issue)[[how-to-write-a-good-issue]]
https://huggingface.co/learn/course/ru/chapter8/5.md

# Как написать хорошее сообщение об ошибке (issue)[[how-to-write-a-good-issue]]

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section5.ipynb"},
]} />

Если вы столкнулись с чем-то, что кажется неправильным в одной из библиотек Hugging Face, обязательно сообщите нам об этом, чтобы мы могли это исправить (то же самое касается любой библиотеки с открытым исходным кодом, если на то пошло). Если вы не уверены, где именно кроется ошибка - в вашем собственном коде или в одной из наших библиотек, - первым делом загляните на [форумы](https://discuss.huggingface.co/). Сообщество поможет вам разобраться в этом, а команда Hugging Face также внимательно следит за обсуждениями там.

<Youtube id="_PAli-V4wj0"/>

Когда вы уверены, что встретили ошибку, первым шагом будет создание минимального воспроизводимого примера.

## Создание минимального воспроизводимого примера[[creating-a-minimal-reproducible-example]]

Очень важно изолировать часть кода, в которой возникает ошибка, поскольку никто из команды Hugging Face не является волшебником (пока), и они не могут исправить то, чего не видят. Минимальный воспроизводимый пример, как видно из названия, должен быть воспроизводимым. Это значит, что он не должен опираться на какие-либо внешние файлы или данные, которые могут у вас быть. Попробуйте заменить используемые данные какими-нибудь фиктивными значениями, которые выглядят как настоящие и при этом выдают ту же ошибку.

<Tip>

🚨 Многие проблемы в репозитории 🤗 Transformers остаются нерешенными, потому что данные, использованные для их воспроизведения, недоступны.

</Tip>

Когда у вас есть что-то самодостаточное, вы можете попытаться сократить его до еще меньшего количества строк кода, создав то, что мы называем _минимальным воспроизводимым примером_. Хотя это требует немного больше работы с вашей стороны, вы почти гарантированно получите помощь и исправление, если предоставите хороший, короткий пример воспроизведения ошибки.

Если вы чувствуете себя достаточно комфортно, просмотрите исходный код, в котором произошла ваша ошибка. Возможно, вы найдете решение проблемы (в этом случае вы даже можете предложить pull request), но в целом это поможет сопровождающим лучше понять исходный код, когда они прочитают ваш отчет.

## Заполнение шаблона проблемы[[filling-out-the-issue-template]]

Когда вы начнете сообщать об ошибке, вы заметите, что есть шаблон, который нужно заполнить. Здесь мы будем следовать шаблону для [🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose), но такая же информация потребуется, если вы сообщите о проблеме в другом репозитории. Не оставляйте шаблон пустым: потратив время на его заполнение, вы увеличите свои шансы на получение ответа и решение проблемы.

В целом, при оформлении проблемы всегда оставайтесь вежливыми. Это проект с открытым исходным кодом, поэтому вы используете свободное программное обеспечение, и никто не обязан вам помогать. Вы можете включить в свой вопрос обоснованную, на ваш взгляд, критику, но тогда сопровождающие могут воспринять это плохо и не спешить вам помогать. Обязательно прочитайте [кодекс поведения](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) проекта.

### Включая информацию о вашем окружении разработки[[including-your-environment-information]]

🤗 Transformers предоставляет утилиту для получения всей необходимой информации о вашем окружении. Просто введите в терминале следующее:

```
transformers-cli env
```

и у вас должно получиться что-то вроде этого:

```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?: <fill in>
- Using distributed or parallel set-up in script?: <fill in>
```

Вы также можете добавить `!` в начало команды `transformers-cli env`, чтобы выполнить ее из ячейки блокнота, а затем скопировать и вставить результат в начало описания проблемы.

### Упоминание людей[[tagging-people]]

Отметив людей, набрав `@`, а затем их GitHub-ник, вы отправите им уведомление, чтобы они увидели вашу проблему и могли быстрее ответить. Используйте этот способ аккуратно, потому что люди, которых вы помечаете, могут не обратить внимания на уведомления, если это что-то, к чему они не имеют прямого отношения. Если вы просмотрели исходные файлы, связанные с вашей ошибкой, вам следует отметить последнего человека, который внес изменения в строку, которая, по вашему мнению, ответственна за вашу проблему (вы можете найти эту информацию, посмотрев на указанную строку на GitHub, выбрав ее, а затем нажав "View git blame").

В противном случае шаблон предложит вам выбрать людей для пометки. В общем случае не следует отмечать более трех человек!

### Включение воспроизводимого примера[[including-a-reproducible-example]]

Если вам удалось создать самодостаточный пример, который выдает ошибку, самое время добавить его! Введите строку с тремя обратными знаками, за которыми следует `python`, например, так:

```
```python
```

затем вставьте свой минимальный воспроизводимый пример и введите новую строку с тремя обратными знаками. Это обеспечит правильное форматирование вашего кода.

Если вам не удалось создать воспроизводимый пример, объясните в четких шагах, как вы пришли к своей проблеме. Если можно, включите ссылку на блокнот Google Colab, в котором вы получили ошибку. Чем больше информации вы предоставите, тем лучше смогут ответить вам сопровождающие.

В любом случае вам следует скопировать и вставить все сообщение об ошибке, которое вы получаете. Если вы работаете в Colab, помните, что некоторые фреймы могут быть автоматически свернуты в трассировке стека, поэтому убедитесь, что вы развернули их перед копированием. Как и в примере кода, поместите сообщение об ошибке между двумя строками с тремя обратными знаками, чтобы оно было правильно отформатировано.

### Описание ожидаемого поведения[[describing-the-expected-behavior]]

Объясните в нескольких строках, что вы ожидали получить, чтобы сопровождающие получили полное представление о проблеме. Эта часть, как правило, довольно очевидна, поэтому должна уместиться в одном предложении, но в некоторых случаях вам будет что сказать.

## И что потом?[[and-then-what]]

Как только проблема будет зарегистрирована, не забудьте быстро проверить, все ли в порядке. Вы можете отредактировать вопрос, если допустили ошибку, или даже изменить его название, если поймете, что проблема отличается от того, что вы думали вначале.

Нет смысла писать людям, если вы не получите ответа. Если никто не поможет вам в течение нескольких дней, скорее всего, никто не смог разобраться в вашей проблеме. Не стесняйтесь возвращаться к воспроизводимому примеру. Можете ли вы сделать его короче и понятнее? Если вы не получите ответа в течение недели, вы можете оставить сообщение с просьбой о помощи, особенно если вы отредактировали свой вопрос, чтобы включить в него больше информации о проблеме.



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/5.mdx" />

### Введение[[introduction]]
https://huggingface.co/learn/course/ru/chapter8/1.md

# Введение[[introduction]]

<CourseFloatingBanner
    chapter={8}
    classNames="absolute z-10 right-0 top-0"
/>

Теперь, когда вы знаете, как решать самые распространенные задачи NLP с помощью 🤗 Transformers, вы должны быть в состоянии начать работу над своими собственными проектами! В этой главе мы рассмотрим, что делать, если вы столкнулись с проблемой. Вы узнаете, как успешно отладить свой код или обучение, как обратиться за помощью к сообществу, если не удается решить проблему самостоятельно. А если вам покажется, что вы нашли ошибку в одной из библиотек Hugging Face, мы покажем вам, как лучше сообщить об этом, чтобы проблема была решена как можно быстрее.

Точнее, в этой главе вы узнаете:

- Что нужно делать в первую очередь при возникновении ошибки
- Как обратиться за помощью на [форумах](https://discuss.huggingface.co/)
- Как отладить свой пайплайн обучения
- Как правильно описать проблему

Разумеется, все это не относится исключительно к 🤗 Transformers или экосистеме Hugging Face; уроки из этой главы применимы к большинству проектов с открытым исходным кодом!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/1.mdx" />

### Обращение за помощью на форумах[[asking-for-help-on-the-forums]]
https://huggingface.co/learn/course/ru/chapter8/3.md

# Обращение за помощью на форумах[[asking-for-help-on-the-forums]]

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section3.ipynb"},
]} />

<Youtube id="S2EEG3JIt2A"/>

Форумы [Hugging Face](https://discuss.huggingface.co) - это отличное место, где можно получить помощь от команды разработчиков библиотек и более широкого сообщества Hugging Face. Вот как выглядит главная страница в любой день:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forums.png" alt="The Hugging Face forums." width="100%"/>
</div>

С левой стороны вы можете увидеть все категории, по которым сгруппированы различные темы, а с правой - самые последние темы. Тема - это сообщение, содержащее заголовок, категорию и описание; это очень похоже на формат вопросов GitHub, который мы видели при создании нашего собственного набора данных в [Главе 5](../chapter5/1). Как следует из названия, категория [Beginners](https://discuss.huggingface.co/c/beginners/5) предназначена в первую очередь для тех, кто только начинает знакомиться с библиотеками и экосистемой Hugging Face. Здесь можно задать любой вопрос по любой из библиотек, будь то отладка кода или просьба о помощи, как что-то сделать. (При этом, если ваш вопрос касается какой-то конкретной библиотеки, вам, вероятно, следует обратиться в соответствующую категорию библиотек на форуме).

Аналогично, категории [Intermediate](https://discuss.huggingface.co/c/intermediate/6) и [Research](https://discuss.huggingface.co/c/research/7) предназначены для более сложных вопросов, например, о библиотеках или новых крутых исследованиях в области НЛП, которые вы хотели бы обсудить.

И, конечно же, нельзя не упомянуть категорию [Course](https://discuss.huggingface.co/c/course/20), где вы можете задавать любые вопросы, связанные с курсом "Hugging Face"!

Выбрав категорию, вы будете готовы написать свою первую тему. На форуме вы можете найти несколько [рекомендаций](https://discuss.huggingface.co/t/how-to-request-support/3128) о том, как это сделать, а в этом разделе мы рассмотрим некоторые особенности, из которых складывается хорошая просьба о помощи.

## Написание хорошего поста на форуме[[writing-a-good-forum-post]]

В качестве примера предположим, что мы пытаемся сгенерировать векторные представления статей Википедии для создания пользовательской поисковой системы. Как обычно, мы загружаем токенизатор и модель следующим образом:

```python
from transformers import AutoTokenizer, AutoModel

model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
model = AutoModel.from_pretrained(model_checkpoint)
```

Теперь предположим, что мы попытаемся построить векторное представления для целого раздела [статьи с Википедии](https://en.wikipedia.org/wiki/Transformers), посвященной Трансформерам (франшизе, а не библиотеке!):

```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.
"""

inputs = tokenizer(text, return_tensors="pt")
logits = model(**inputs).logits
```

```python output
IndexError: index out of range in self
```

О-о, мы столкнулись с проблемой - и сообщение об ошибке гораздо более загадочно, чем те, что мы видели в [разделе 2](2)! Мы не можем разобраться в полном её описании, поэтому решили обратиться за помощью на форум Hugging Face. Как мы можем создать тему?

Чтобы начать, нам нужно нажать кнопку "Новая тема" в правом верхнем углу (обратите внимание, что для создания темы нам нужно войти в систему):

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forums-new-topic.png" alt="Creating a new forum topic." width="100%"/>
</div>

Откроется интерфейс для написания текста, где мы можем ввести название темы, выбрать категорию и составить сообщение:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic01.png" alt="The interface for creating a forum topic." width="100%"/>
</div>

Поскольку ошибка, похоже, связана исключительно с 🤗 Transformers, мы выберем эту категорию. Наша первая попытка объяснить проблему может выглядеть примерно так:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic02.png" alt="Drafting the content for a new forum topic." width="100%"/>
</div>

Хотя эта тема содержит сообщение об ошибке, с которой нам нужна помощь, есть несколько проблем с тем, как оно написано:

1. Заголовок не очень содержателен, поэтому любой, кто просматривает форум, не сможет понять, о чем идет речь, не прочитав и основной текст.
2. В тексте недостаточно информации о том, откуда берется ошибка и как ее воспроизвести.
3. В теме прямо указывается несколько человек, причем в несколько требовательном тоне.

Такие темы, как эта, вряд ли получат быстрый ответ (если вообще получат), так что давайте посмотрим, как можно их улучшить. Начнем с первого вопроса - выбора хорошего названия.

### Выберите содержательное название[[choosing-a-descriptive-title]]

Если вы пытаетесь получить помощь по поводу ошибки в вашем коде, хорошим правилом является включение достаточного количества информации в заголовок, чтобы другие могли быстро определить, смогут ли они ответить на ваш вопрос или нет. В нашем примере мы знаем имя возникающего исключения и имеем некоторые намеки на то, что оно срабатывает в прямом проходе модели, где мы вызываем `model(**inputs)`. Чтобы сообщить об этом, один из возможных вариантов заголовка может быть таким:

> Source of IndexError in the AutoModel forward pass?

Этот заголовок сообщает читателю, откуда, по вашему мнению, исходит ошибка, и если он уже сталкивался с `IndexError`, велика вероятность, что он поймет, как ее отладить. Конечно, заголовок может быть любым, какой вы захотите, и возможны другие варианты, например:

> Why does my model produce an IndexError?

также может подойти. Теперь, когда у нас есть описательный заголовок, давайте посмотрим, как улучшить само описание ошибки.

### Отформатируйте код[[formatting-your-code-snippets]]

Читать исходный код в IDE достаточно сложно, но еще сложнее, когда код скопирован и вставлен в виде обычного текста! К счастью, форумы Hugging Face поддерживают использование Markdown, поэтому вы всегда должны заключать свои блоки кода в три обратных знака (```), чтобы их было легче читать. Давайте сделаем это, чтобы отформатировать сообщение об ошибке - и пока мы это делаем, давайте сделаем текст немного более вежливым, чем наша первоначальная версия:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic03.png" alt="Our revised forum topic, with proper code formatting." width="100%"/>
</div>

Как видно на скриншоте, заключение блоков кода в обратные кавычки превращает исходный текст в отформатированный код, дополненный цветовой стилизацией! Также обратите внимание, что одиночные обратные кавычки могут быть использованы для форматирования встроенных переменных, как мы это сделали для `distilbert-base-uncased`. В таком оформлении сообщение выглядит гораздо лучше, и, если повезет, мы сможем найти кого-то из сообщества, кто догадается, в чем ошибка. Но вместо того, чтобы полагаться на удачу, давайте облегчим себе жизнь, включив трассировку во всех подробностях!

### Добавьте текст полной трассировки[[including-the-full-traceback]]

Как видно на скриншоте, заключение блоков кода в обратные знаки превращает исходный текст в отформатированный код, дополненный цветовой стилизацией! Также обратите внимание, что одиночные обратные знаки могут быть использованы для форматирования встроенных переменных, как мы это сделали для `distilbert-base-uncased`. Эта тема выглядит гораздо лучше, и, если повезет, мы сможем найти кого-то из сообщества, кто догадается, в чем ошибка. Однако вместо того, чтобы полагаться на удачу, давайте облегчим себе жизнь, включив в трассировку все подробности!

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic04.png" alt="Our example forum topic, with the complete traceback." width="100%"/>
</div>

Это гораздо более информативно, и внимательный читатель сможет указать на то, что проблема, похоже, связана с передачей длинного входного сигнала из-за этой строки в трассировке:

> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512).

Однако мы можем еще больше облегчить им задачу, предоставив фактический код, вызвавший ошибку. Давайте сделаем это прямо сейчас.

### Предоставьте воспроизводимый пример[[providing-a-reproducible-example]]

Если вы когда-нибудь пытались отладить чужой код, вы, вероятно, сначала пытались воссоздать проблему, о которой они сообщали, чтобы начать работать над трассировкой, чтобы точно определить ошибку. Это не отличается от того, когда речь идет о получении (или предоставлении) помощи на форумах, поэтому очень помогает, если вы можете предоставить небольшой пример, воспроизводящий ошибку. В половине случаев простое выполнение этого упражнения поможет вам понять, что происходит не так. В любом случае, недостающий фрагмент нашего примера - это показать _входы_, которые мы предоставили модели. Выполнив это, мы получим нечто похожее на следующий завершенный пример:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/forum-topic05.png" alt="The final version of our forum topic." width="100%"/>
</div>

Теперь эта сообщение содержит достаточно много информации, и оно написана таким образом, что с гораздо большей вероятностью привлечет внимание сообщества и получит полезный ответ. Используя эти основные правила, вы сможете создавать отличные темы для поиска ответов на ваши вопросы по библиотеке 🤗 Transformers!



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/3.mdx" />

### Часть 2 завершена![[part-2-completed]]
https://huggingface.co/learn/course/ru/chapter8/6.md

# Часть 2 завершена![[part-2-completed]]

<CourseFloatingBanner
    chapter={8}
    classNames="absolute z-10 right-0 top-0"
/>

Поздравляем, вы прошли вторую часть курса! Мы активно работаем над третьей частью, поэтому подпишитесь на нашу [рассылку](https://huggingface.curated.co/), чтобы не пропустить ее выход.

Теперь вы должны уметь решать различные задачи NLP,  дообучать или обучать модели с нуля. Не забудьте поделиться своими результатами с сообществом на [Model Hub](https://huggingface.co/models).

Нам не терпится увидеть, что вы построите на основе полученных знаний!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/6.mdx" />

### Отладка обучения[[debugging-the-training-pipeline]]
https://huggingface.co/learn/course/ru/chapter8/4.md

# Отладка обучения[[debugging-the-training-pipeline]]

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section4.ipynb"},
]} />

Вы написали прекрасный сценарий для обучения или дообучения модели на заданной задаче, послушно следуя советам из [Главы 7](/course/chapter7/1). Но когда вы запускаете команду `model.fit()`, происходит нечто ужасное: вы получаете ошибку 😱! Или, что еще хуже, все вроде бы хорошо, обучение проходит без ошибок, но результирующая модель получается плохой. В этом разделе мы покажем вам, что можно сделать для отладки подобных проблем.

## Отладка обучающего пайплайна[[debugging-the-training-pipeline]]

<Youtube id="L-WSwUWde1U"/>

Проблема, когда вы сталкиваетесь с ошибкой в `trainer.train()`, заключается в том, что она может возникнуть из нескольких источников, поскольку `Trainer` обычно собирает вместе множество вещей. Он преобразует наборы данных в загрузчики данных, поэтому проблема может заключаться в том, что в вашем наборе данных что-то не так, или в том, что вы пытаетесь объединить элементы наборов данных в батч. Затем он берет батч данных и подает его в модель, так что проблема может быть в коде модели. После этого вычисляются градиенты и выполняется этап оптимизации, так что проблема может быть и в вашем оптимизаторе. И даже если во время обучения все идет хорошо, во время валидации все равно что-то может пойти не так, если проблема в метрике.

Лучший способ отладить ошибку, возникшую в `trainer.train()`, - это вручную пройти весь пайплайн и посмотреть, где все пошло не так. В этом случае ошибку часто очень легко устранить.

Чтобы продемонстрировать это, мы используем следующий скрипт, который (пытается) точно настроить модель DistilBERT на наборе данных [MNLI dataset](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()
```

Если вы попытаетесь выполнить его, то столкнетесь с довольно загадочной ошибкой:

```python out
'ValueError: You have to specify either input_ids or inputs_embeds'
```

### Проверка данных[[check-your-data]]

Это может быть очевидно, но если ваши данные повреждены, `Trainer` не сможет сформировать батчи, не говоря уже об обучении вашей модели. Поэтому прежде всего необходимо посмотреть, что находится в вашем обучающем наборе.

Чтобы избежать бесчисленных часов, потраченных на попытки исправить то, что не является источником ошибки, мы рекомендуем использовать `trainer.train_dataset` для проверок и ничего больше. Так что давайте сделаем это здесь:

```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.'}
```

Вы заметили что-то неладное? В сочетании с сообщением об ошибке `input_ids`, вы должны понять, что это тексты, а не числа, которые модель может понять. Здесь исходная ошибка вводит в заблуждение, потому что `Trainer` автоматически удаляет столбцы, которые не соответствуют сигнатуре модели (то есть аргументам, ожидаемым моделью). Это означает, что здесь было удалено все, кроме меток. Таким образом, не было никаких проблем с созданием батчей и отправкой их модели, которая, в свою очередь, жаловалась, что не получила нужных входных данных.

Почему данные не обрабатывались? Мы действительно использовали метод `Dataset.map()` для наборов данных, чтобы применить токенизатор к каждой выборке. Но если вы внимательно посмотрите на код, то увидите, что мы допустили ошибку при передаче обучающего и валидационного наборов в `Trainer`. Вместо того чтобы использовать `tokenized_datasets`, мы использовали `raw_datasets` 🤦. Так что давайте исправим это!

```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()
```

Теперь этот новый код будет выдавать другую ошибку (прогресс!):

```python out
'ValueError: expected sequence of length 43 at dim 1 (got 37)'
```

Посмотрев на трассировку, мы видим, что ошибка происходит на этапе сборки данных:

```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
```

Поэтому нам следует перейти к этому. Однако перед этим давайте закончим проверку наших данных, чтобы быть на 100% уверенными в их правильности.

При отладке обучения всегда нужно смотреть на декодированные входы модели. Мы не можем понять смысл чисел, которые подаем ей напрямую, поэтому мы должны посмотреть, что эти числа представляют. В компьютерном зрении, например, это означает просмотр декодированных изображений пройденных пикселей, в речи - прослушивание декодированных образцов звука, а в нашем примере с NLP - использование нашего токенизатора для декодирования входных данных:

```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]'
```

Так что, похоже, все правильно. Вы должны сделать это для всех ключей во входах:

```py
trainer.train_dataset[0].keys()
```

```python out
dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise'])
```

Обратите внимание, что ключи, не соответствующие входам, принимаемым моделью, будут автоматически отброшены, поэтому здесь мы оставим только `input_ids`, `attention_mask` и `label` (которая будет переименована в `labels`). Чтобы перепроверить сигнатуру модели, вы можете вывести класс вашей модели, а затем проверить ее документацию:

```py
type(trainer.model)
```

```python out
transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification
```

Итак, в нашем случае мы можем проверить принятые параметры на [этой странице](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). "Trainer" также будет регистрировать столбцы, которые он отбрасывает.

Мы проверили правильность входных идентификаторов, декодировав их. Далее находится `attention_mask`:

```py
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]
```

Так как мы не применяли в препроцессинге дополнение нулями, это кажется совершенно естественным. Чтобы убедиться в отсутствии проблем с этой маской внимания, давайте проверим, что она имеет ту же длину, что и наши входные идентификаторы:

```py
len(trainer.train_dataset[0]["attention_mask"]) == len(
    trainer.train_dataset[0]["input_ids"]
)
```

```python out
True
```

Это хорошо! И наконец, давайте проверим нашу метку класса:

```py
trainer.train_dataset[0]["label"]
```

```python out
1
```

Как и входные идентификаторы, это число, которое само по себе не имеет смысла. Как мы уже видели, соответствие между целыми числами и именами меток хранится в атрибуте `names` соответствующей *функции* набора данных:

```py
trainer.train_dataset.features["label"].names
```

```python out
['entailment', 'neutral', 'contradiction']
```

Итак, `1` означает `нейтральный`, а значит, два предложения, которые мы видели выше, не противоречат друг другу, и из первого не следует второе. Это кажется правильным!

Здесь у нас нет идентификаторов типов токенов, поскольку DistilBERT их не ожидает; если в вашей модели они есть, вам также следует убедиться, что они правильно соответствуют месту первого и второго предложений во входных данных.

<Tip>

✏️ **Ваша очередь!** Проверьте, все ли правильно со вторым элементом обучающего набора данных.

</Tip>

В данном случае мы проверяем только обучающий набор, но, конечно, вы должны дважды проверить валидационный и тестовый наборы таким же образом.

Теперь, когда мы знаем, что наши наборы данных выглядят хорошо, пришло время проверить следующий этап пайплайна обучения.

### От датасетов к загрузчикам данных[[from-datasets-to-dataloaders]]

Следующее, что может пойти не так в пайплайне обучения, - это когда `Trainer` пытается сформировать батчи из обучающего или проверочного набора. Убедившись, что наборы данных `Trainer` корректны, можно попробовать вручную сформировать батч, выполнив следующие действия (замените `train` на `eval` для валидационного загрузчика данных):

```py
for batch in trainer.get_train_dataloader():
    break
```

Этот код создает загрузчик данных для обучения, затем выполняет итерации по нему, останавливаясь на первой итерации. Если код выполняется без ошибок, у вас есть первый обучающий батч, который можно проверить, а если код выдает ошибку, вы точно знаете, что проблема в загрузчике данных, как в данном случае:

```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)
```

Осмотра последнего кадра трассировки должно быть достаточно, чтобы дать вам подсказку, но давайте покопаемся еще немного. Большинство проблем при создании батчей возникает из-за объединения примеров в один батч, поэтому первое, что нужно проверить при возникновении сомнений, это то, какой `collate_fn` использует ваш `DataLoader`:

```py
data_collator = trainer.get_train_dataloader().collate_fn
data_collator
```

```python out
<function transformers.data.data_collator.default_data_collator(features: List[InputDataClass], return_tensors='pt') -> Dict[str, Any]>
```

Этот `default_data_collator`  не то, что нам нужно в данном случае. Мы хотим разбить наши примеры на самые длинные предложения в пакете, что делает `DataCollatorWithPadding`. И этот класс данных должен использоваться по умолчанию в `Trainer`, так почему же он не используется здесь?

Ответ заключается в том, что мы не передали `tokenizer` в `Trainer`, поэтому он не смог создать нужный нам `DataCollatorWithPadding`. На практике вам никогда не следует стесняться явно передавать data collator, который вы хотите использовать, чтобы избежать подобных ошибок. Давайте адаптируем наш код, чтобы сделать именно это:

```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()
```

Хорошие новости? Мы не получаем ту же ошибку, что и раньше, что, безусловно, является прогрессом. Плохие новости? Вместо нее мы получаем печально известную ошибку CUDA:

```python out
RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)`
```

Это плохо, потому что ошибки CUDA вообще очень трудно отлаживать. Через минуту мы увидим, как решить эту проблему, но сначала давайте закончим анализ создания батчей.

Если вы уверены, что ваш data collator правильный, попробуйте применить его на паре примеров вашего набора данных:

```py
data_collator = trainer.get_train_dataloader().collate_fn
batch = data_collator([trainer.train_dataset[i] for i in range(4)])
```

Этот код не сработает, потому что `train_dataset` содержит строковые колонки, которые `Trainer` обычно удаляет. Вы можете удалить их вручную, или, если вы хотите в точности повторить то, что `Trainer` делает за кулисами: нужно вызвать приватный метод `Trainer._remove_unused_columns()`, который делает это:

```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)])
```

Если ошибка не исчезнет, вы сможете вручную отладить, что происходит внутри data collator.

Теперь, когда мы отладили процесс создания батча, пришло время пропустить его через модель!

### Проверьте данные[[going-through-the-model]]

Вы должны иметь возможность получить батч, выполнив следующую команду:

```py
for batch in trainer.get_train_dataloader():
    break
```

Если вы выполняете этот код в ноутбуке, вы можете получить ошибку CUDA, похожую на ту, что мы видели ранее, и в этом случае вам нужно перезапустить ноутбук и заново выполнить последний фрагмент без строки `trainer.train()`. Это вторая самая неприятная вещь в ошибках CUDA: они безвозвратно ломают ваше ядро. Самое неприятное в них то, что их трудно отлаживать.

Почему? Это связано с тем, как работают графические процессоры. Они чрезвычайно эффективны при параллельном выполнении множества операций, но их недостаток в том, что когда одна из этих инструкций приводит к ошибке, вы не сразу об этом узнаете. Только когда программа вызовет синхронизацию нескольких процессов на GPU, она поймет, что что-то пошло не так, поэтому ошибка возникает в том месте, которое не имеет никакого отношения к тому, что ее создало. Например, если мы посмотрим на наш предыдущий трассировочный откат, ошибка была вызвана во время обратного прохода, но через минуту мы увидим, что на самом деле она возникла из-за чего-то в прямом проходе через модель.

Так как же отладить эти ошибки? Ответ прост: никак. Если только ошибка CUDA не является ошибкой вне памяти (что означает, что в вашем GPU недостаточно памяти), вы всегда должны возвращаться к CPU, чтобы отладить ее.

Чтобы сделать это в нашем случае, нам просто нужно вернуть модель на CPU и вызвать ее на нашем пакете - пакет, возвращаемый `DataLoader`, еще не был перемещен на 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.
```

Итак, картина проясняется. Вместо ошибки CUDA у нас теперь `IndexError` при вычислении функции потерь (так что обратный проход, как мы уже говорили, здесь ни при чем). Точнее, мы видим, что ошибка возникает именно в метке класса 2, так что это очень хороший момент для проверки количества меток нашей модели:

```python
trainer.model.config.num_labels
```

```python out
2
```

При двух метках в качестве значений допускаются только 0 и 1, но, согласно сообщению об ошибке, мы получили 2. Получение 2 на самом деле нормально: если мы помним имена меток, которые мы извлекли ранее, их было три, поэтому в нашем наборе данных есть индексы 0, 1 и 2. Проблема в том, что мы не сообщили об этом нашей модели, которая должна была быть создана с тремя метками. Так что давайте это исправим!

```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,
)
```

Мы пока не включаем строку `trainer.train()`, чтобы потратить время на проверку того, что все выглядит хорошо. Если мы запросим батч и передадим ее нашей модели, то теперь она работает без ошибок!

```py
for batch in trainer.get_train_dataloader():
    break

outputs = trainer.model.cpu()(**batch)
```

Следующим шагом будет возвращение к графическому процессору и проверка того, что все по-прежнему работает:

```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)
```

Если вы все еще получаете ошибку, убедитесь, что перезагрузили ноутбук и выполнили только последнюю версию скрипта.

### Выполнение одного шага оптиимзации[[performing-one-optimization-step]]

Теперь, когда мы знаем, что можем создавать батчи, которые действительно проходят через модель без ошибок, мы готовы к следующему шагу пайплайна обучения: вычислению градиентов и выполнению шага оптимизации.

Первая часть заключается в вызове метода `backward()` для функции потерь:

```py
loss = outputs.loss
loss.backward()
```

Ошибки на этом этапе возникают довольно редко, но если они все же возникнут, обязательно вернитесь к процессору, чтобы получить полезное сообщение об ошибке.

Чтобы выполнить шаг оптимизации, нам нужно просто создать `optimizer` и вызвать его метод `step()`:

```py
trainer.create_optimizer()
trainer.optimizer.step()
```

Опять же, если вы используете оптимизатор по умолчанию в `Trainer`, вы не должны получить ошибку на этом этапе, но если вы используете собственный оптимизатор, здесь могут возникнуть некоторые проблемы для отладки. Не забудьте вернуться к процессору, если на этом этапе вы получите странную ошибку CUDA. Говоря об ошибках CUDA, ранее мы упоминали особый случай. Давайте посмотрим на него сейчас.

### Как справиться с ошибками нехватки памяти[[dealing-with-cuda-out-of-memory-errors]]

Если вы получаете сообщение об ошибке, начинающееся с `RuntimeError: CUDA out of memory`, это означает, что вам не хватает памяти GPU. Это не связано напрямую с вашим кодом, и может произойти со скриптом, который работает совершенно нормально. Эта ошибка означает, что вы пытались поместить слишком много данных во внутреннюю память вашего GPU, и это привело к ошибке. Как и в случае с другими ошибками CUDA, вам придется перезапустить ядро, чтобы снова запустить обучение.

Чтобы решить эту проблему, нужно просто использовать меньше памяти на GPU - что зачастую легче сказать, чем сделать. Во-первых, убедитесь, что у вас нет двух моделей на GPU одновременно (если, конечно, это не требуется для решения вашей задачи). Затем, вероятно, следует уменьшить размер батча, поскольку он напрямую влияет на размеры всех промежуточных выходов модели и их градиентов. Если проблема сохраняется, подумайте о том, чтобы использовать меньшую версию модели.

<Tip>

В следующей части курса мы рассмотрим более продвинутые техники, которые помогут вам уменьшить объем занимаемой памяти и позволят точно настроить самые большие модели.

</Tip>

### Валидация модели[[evaluating-the-model]]

Теперь, когда мы решили все проблемы с нашим кодом, все идеально, и обучение должно пройти гладко, верно? Не так быстро! Если вы запустите команду `trainer.train()`, сначала все будет выглядеть хорошо, но через некоторое время вы получите следующее:

```py
# Это займет много времени и приведет к ошибке, поэтому не стоит запускать эту ячейку
trainer.train()
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

Вы поймете, что эта ошибка появляется во время фазы валидации, так что это последнее, что нам нужно будет отладить.

Вы можете запустить цикл оценки `Trainer` независимо от обучения следующим образом:

```py
trainer.evaluate()
```

```python out
TypeError: only size-1 arrays can be converted to Python scalars
```

<Tip>

💡 Перед запуском `trainer.train()` всегда следует убедиться, что вы можете запустить `trainer.evaluate()`, чтобы не тратить много вычислительных ресурсов до того, как столкнетесь с ошибкой.

</Tip>

Прежде чем пытаться отладить проблему в цикле валидации, нужно сначала убедиться, что вы посмотрели на данные, смогли правильно сформировать батч и запустить на нем свою модель. Мы выполнили все эти шаги, поэтому следующий код может быть выполнен без ошибок:

```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)
```

Ошибка возникает позже, в конце фазы валидации, и если мы посмотрим на трассировку, то увидим следующее:

```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()
```

Это говорит нам о том, что ошибка возникает в модуле `datasets/metric.py` - так что это проблема с нашей функцией `compute_metrics()`. Она принимает кортеж с логитами и метками в виде массивов NumPy, так что давайте попробуем скормить ей это:

```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
```

Мы получаем ту же ошибку, так что проблема определенно кроется в этой функции. Если мы посмотрим на ее код, то увидим, что она просто передает `predictions` и `labels` в `metric.compute()`. Так есть ли проблема в этом методе? На самом деле нет. Давайте посмотрим на размерности:

```py
predictions.shape, labels.shape
```

```python out
((8, 3), (8,))
```

Наши предсказания все еще являются логитами, а не реальными предсказаниями, поэтому метрика возвращает эту (несколько непонятную) ошибку. Исправить это довольно просто: нужно просто добавить argmax в функцию `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}
```

Теперь наша ошибка исправлена! Она была последней, поэтому теперь наш скрипт будет правильно обучать модель.

Для справки, вот полностью исправленный скрипт:

```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()
```

В этом случае проблем больше нет, и наш скрипт обучит модель, которая должна дать приемлемые результаты. Но что делать, если обучение проходит без ошибок, а обученная модель совсем не работает? Это самая сложная часть машинного обучения, и мы покажем вам несколько приемов, которые могут помочь.

<Tip>

💡 Если вы используете ручной цикл обучения, для отладки пайплайна обучения применимы те же шаги, но их проще разделить. Убедитесь, что вы не забыли `model.eval()` или `model.train()` в нужных местах, или `zero_grad()` на каждом шаге!
</Tip>

## Отладка скрытых ошибок во время обучения[[debugging-silent-errors-during-training]]

Что можно сделать, чтобы отладить обучение, которое завершается без ошибок, но не дает хороших результатов? Мы дадим вам несколько советов, но имейте в виду, что такая отладка - самая сложная часть машинного обучения, и волшебного ответа на этот вопрос не существует.

### Проверьте свои данные (еще раз!)[[check-your-data-again]]

Ваша модель научится чему-то только в том случае, если из ваших данных действительно можно чему-то научиться. Если в данных есть ошибка, которая портит их, или метки приписаны случайным образом, то, скорее всего, вы не сможете обучить модель на своем наборе данных. Поэтому всегда начинайте с перепроверки декодированных входных данных и меток и задавайте себе следующие вопросы:

- Понятны ли декодированные данные?
- Правильные ли метки?
- Есть ли одна метка, которая встречается чаще других?
- Каким должно быть значение функции потерь/метрики если модель предсказала случайный ответ/всегда один и тот же ответ?

<Tip warning={true}>

⚠️ Если вы проводите распределенное обучение, распечатайте образцы набора данных в каждом процессе и трижды проверьте, что вы получаете одно и то же. Одна из распространенных ошибок - наличие некоторого источника случайности при создании данных, из-за которого каждый процесс имеет свою версию набора данных.

</Tip>

Просмотрев данные, проанализируйте несколько предсказаний модели и декодируйте их. Если модель постоянно предсказывает одно и то же, это может быть связано с тем, что ваш набор данных смещен в сторону одной категории (для проблем классификации); здесь могут помочь такие методы, как oversampling редких классов.

Если значения функции потерь/метрики, которые вы получаете на начальной модели, сильно отличаются от значений функции потерь/метрик, которые можно было бы ожидать для случайных предсказаний, перепроверьте способ вычисления потерь или метрик, так как, возможно, в них есть ошибка. Если вы используете несколько функций потерь, убедитесь, что они имеют одинаковый масштаб.

Когда вы убедитесь, что ваши данные идеальны, вы можете проверить, способна ли модель обучаться на них, с помощью одного простого теста.

### Переобучение модели на одном батче[[overfit-your-model-on-one-batch]]

Обычно мы стараемся избегать переобучения при тренировке модели, поскольку это означает, что модель не учится распознавать общие характеристики, а просто запоминает обучающие выборки. Однако попытка обучить модель на одной выборке снова и снова - это хороший тест, позволяющий проверить, может ли проблема в том виде, в котором вы ее сформулировали, быть решена с помощью модели, которую вы пытаетесь обучить. Это также поможет вам понять, не слишком ли высока ваша начальная скорость обучения.

Сделать это после того, как вы определили свой `Trainer`, очень просто: просто возьмите батч обучающих данных, а затем запустите небольшой цикл ручного обучения, используя только этот батч в течение примерно 20 шагов:

```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()
```

<Tip>

💡 Если ваши обучающие данные несбалансированы, обязательно создайте батч обучающих данных, содержащий все метки.

</Tip>

Результирующая модель должна иметь близкие к идеальным результаты на одном и том же батче. Вычислим метрику по полученным предсказаниям:

```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 %, вот это хороший пример переобучения (это значит, что если вы попробуете использовать модель на любом другом предложении, она, скорее всего, даст вам неправильный ответ)!

Если вам не удается добиться от модели таких идеальных результатов, значит, что-то не так с постановкой задачи или данными, и вам следует это исправить. Только когда вам удастся пройти тест на переобучение, вы сможете быть уверены, что ваша модель действительно способна чему-то научиться.

<Tip warning={true}>

⚠️ Вам придется пересоздать модель и `Trainer` после этого теста на переобучение, поскольку полученная модель, вероятно, не сможет восстановиться и научиться чему-то полезному на полном наборе данных.

</Tip>

### Не обучайте ничего, пока не получите первый бейзлайн.[[dont-tune-anything-until-you-have-a-first-baseline]]

Настройка гиперпараметров всегда считается самой сложной частью машинного обучения, но это всего лишь последний шаг, который поможет вам немного улучшить метрику. В большинстве случаев гиперпараметры по умолчанию `Trainer` будут работать нормально и давать вам хорошие результаты, поэтому не приступайте к трудоемкому и дорогостоящему поиску гиперпараметров, пока у вас не будет чего-то, что превосходит базовый уровень, который у вас есть в вашем наборе данных.

Как только у вас будет достаточно хорошая модель, вы можете начать ее немного оптимизировать. Не пытайтесь запустить тысячу раз с разными гиперпараметрами, но сравните пару запусков с разными значениями одного гиперпараметра, чтобы получить представление о том, какой из них оказывает наибольшее влияние.

Если вы настраиваете саму модель, будьте проще и не пробуйте то, что не можете обосновать. Всегда возвращайтесь к тесту на перебор, чтобы проверить, не привело ли ваше изменение к каким-либо непредвиденным последствиям.

### Попросите о помощи[[ask-for-help]]

Надеемся, вы нашли в этом разделе советы, которые помогли вам решить вашу проблему, но если это не так, помните, что вы всегда можете спросить у сообщества на [форумах](https://discuss.huggingface.co/).

Вот некоторые дополнительные ресурсы, которые могут оказаться полезными:

- [" Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) by Joel Grus
- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) by Cecelia Shao
- [" How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts
- [""A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) by Andrej Karpathy

Конечно, не все проблемы, с которыми вы сталкиваетесь при обучении нейросетей, возникают по вашей вине! Если в библиотеке 🤗 Transformers или 🤗 Datasets вы столкнулись с чем-то, что кажется вам неправильным, возможно, вы обнаружили ошибку. Вам обязательно нужно рассказать нам об этом, и в следующем разделе мы объясним, как именно это сделать.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/4.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter8/7.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={8}
    classNames="absolute z-10 right-0 top-0"
/>

Let's test what you learned in this chapter!

### 1. В каком порядке следует читать обратную трассировку в Python?

<Question
	choices={[
		{
			text: "Сверху вниз",
			explain: "Попробуйте еще раз - хотя большинство других языков программирования выводят исключение сверху, Python в этом отношении особенный.	"
		},
		{
			text: "Снизу вверх",
			explain: "Правильно! Одно из преимуществ трассировки Python, показывающей исключение внизу, заключается в том, что ее легче отлаживать, когда вы работаете в терминале и это последняя строка, которую вы видите.",
			correct: true
		}
	]}
/>

### 2. Что такое минимальный воспроизводимый пример?

<Question
	choices={[
		{
			text: "Простая реализация архитектуры трансформера из научной статьи",
			explain: "Хотя создавать собственные модели трансформеров с нуля очень познавательно, мы сейчас говорим не об этом."
		},
		{
			text: "Компактный и самодостаточный блок кода, который может быть запущен без внешних зависимостей от частных файлов или данных",
			explain: "Правильно! Минимальные воспроизводимые примеры помогают сопровождающим библиотеки воспроизвести возникшую у вас проблему, чтобы они могли быстрее найти решение..",
			correct: true
		},
		{
			text: "Снимок экрана с трассировкой Python",
			explain: "Попробуйте еще раз - хотя очень соблазнительно приложить скриншот ошибки, с которой вы столкнулись при оформлении проблемы, это сильно затрудняет воспроизведение ошибки другими людьми.."
		},
		{
			text: "Jupyter-ноутбук, содержащий весь ваш код, включая части, не имеющие отношения к ошибке",
			explain: "Не совсем так - хотя может быть полезно поделиться блокнотом Google Colab, в котором показана ошибка, убедитесь, что он короткий и содержит только соответствующий код."
		}
	]}
/>

### 3. Предположим, вы пытаетесь выполнить следующий код, который выдает ошибку:

```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 <module>
# ----> 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)
```

Что из нижеперечисленного может быть хорошим выбором для названия темы на форуме с просьбой о помощи?

<Question
	choices={[
		{
			text: "<code>ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)</code>",
			explain: "Включение последней строки трассировки может быть описательным, но это лучше оставить для основной части темы. Попробуйте еще раз!"
		},
		{
			text: "Проблема с  <code>from transformers import GPT3ForSequenceClassification</code>",
			explain: "Попробуйте еще раз - хотя это и полезная информация, ее лучше оставить для основной части текста.",
		},
		{
			text: "Почему я не могу импортировать <code>GPT3ForSequenceClassification</code>?",
			explain: "Отличный выбор! Это название лаконично и дает читателю понять, что может быть не так (например, что GPT-3 не поддерживается в 🤗 Transformers).",
			correct: true
		},
		{
			text: "Поддерживается ли GPT-3 в 🤗 Transformers?",
			explain: "Отличный вариант! Использование вопросов в качестве заголовков тем - отличный способ донести проблему до сообщества.",
			correct: true
		}
	]}
/>

### 4. Предположим, вы пытаетесь запустить `trainer.train()` и сталкиваетесь с загадочной ошибкой, которая не говорит вам, откуда именно она взялась. Что из нижеперечисленного является первым местом, где вы должны искать ошибки в вашем конвейере обучения?

<Question
	choices={[
		{
			text: "Этап оптимизации, на котором мы вычисляем градиенты и выполняем обратное распространение ошибки",
			explain: "Хотя в вашем оптимизаторе могут быть ошибки, обычно это происходит на нескольких этапах обучения, поэтому сначала нужно проверить другие вещи. Попробуйте еще раз!"
		},
		{
			text: "Этап оценки, на котором мы вычисляем метрики",
			explain: "Оценка обычно выполняется после обучения в течение полной эпохи, поэтому сначала следует проверить где-то на более ранних этапах обучения.",
		},
		{
			text: "Набор данных",
			explain: "Правильно! Почти всегда первым делом нужно просмотреть данные, чтобы убедиться, что текст закодирован правильно, имеет ожидаемые характеристики и т. д.",
			correct: true
		},
		{
			text: "Загрузчики данных",
			explain: "Попробуйте еще раз - это очень близко к тому, что нужно проверить в первую очередь. Помните ли вы, какой объект мы передаем в dataloaders?"
		}
	]}
/>

### 5. Каков наилучший способ отладки ошибок CUDA?

<Question
	choices={[
		{
			text: "Опубликовать сообщение об ошибке на форумах или GitHub.",
			explain: "Это никому не поможет, поскольку сообщения об ошибках CUDA обычно очень неинформативны."
		},
		{
			text: "Выполнить такой же код на CPU.",
			explain: "Именно, это должно дать вам более точное сообщение об ошибке!",
			correct: true
		},
		{
			text: "Прочитайте трассировку, чтобы выяснить причину ошибки.",
			explain: "Это то, что вы сделали бы для любой другой ошибки, но ошибки CUDA обычно не проявляются там, где они произошли, потому что большинство операций CUDA асинхронны."
		},
		{
			text: "Уменьшите размер партии батча.",
			explain: "Уменьшение размера батча обычно является хорошей стратегией для устранения ошибок CUDA, связанных с памятью, но не для этой конкретной проблемы. Попробуйте еще раз!"
		},
		{
			text: "Перезапустите ядро Jupyter.",
			explain: "Попробуйте еще раз - перезапуск ядра не приведет к исчезновению ошибки!",
		}
	]}
/>

### 6. Как лучше всего сообщить о проблеме на GitHub?

<Question
	choices={[
		{
			text: "Опубликовать полный воспроизводимый пример ошибки.",
			explain: "Да, это лучший способ помочь сопровождающим найти вашу ошибку. Что еще вы должны сделать?",
			correct: true
		},
		{
			text: "Каждый день спрашивать о новостях.",
			explain: "Это вряд ли поможет вам; скорее всего, люди будут игнорировать вас еще больше.",
		},
		{
			text: "Изучите исходный код вокруг ошибки и попытайтесь найти причину ее возникновения. Опубликуйте результаты в этой теме.",
			explain: "Это определенно поможет сопровождающим! А если вы найдете источник ошибки и ее исправление, вы даже сможете открыть запрос на исправление (pull request). Что еще вы должны сделать?",
			correct: true
		}
	]}
/>

### 7. Почему переобучение модели с одним батчем обычно является хорошим методом отладки?

<Question
	choices={[
		{
			text: "Это не так; переобучение - это всегда плохо и его следует избегать.",
			explain: "При обучении на всем наборе данных переобучение действительно может быть признаком того, что ваша модель плохо обобщается на новые примеры. Однако для отладки мы обычно не обучаемся на всем наборе данных. Попробуйте еще раз!"
		},
		{
			text: "Это позволяет убедиться в том, что модель способна свести значения функциипотерь к нулю.",
			explain: "Правильно! С помощью батча даннх, состоящего всего из нескольких примеров, мы можем быстро проверить, способна ли модель к обучению.",
			correct: true
		},
		{
			text: "Это позволяет нам убедиться в правильности размеров тензоров наших входов и меток.",
			explain: "Попробуйте еще раз - если размерности ваших тензоров неподходящие, то вы точно не сможете обучиться даже на одном батче.",
		}
	]}
/>

### 8. Почему при создании нового вопроса в репозитории  🤗 Transformers стоит указать подробности о вашем  окружении разработки с помощью `transformers-cli env`??

<Question
	choices={[
		{
			text: "Это позволяет сопровождающим понять, какую версию библиотеки вы используете.",
			explain: "Правильно! Поскольку в каждой основной версии библиотеки могут быть изменения в API, знание конкретной версии может помочь сузить круг проблем. Каковы другие преимущества?",
			correct: true
		},
		{
			text: "Это позволяет сопровождающим узнать, работаете ли вы под Windows, macOS или Linux.",
			explain: "Правильно! Иногда ошибки могут быть вызваны конкретной операционной системой, которую вы используете, и знание этого помогает сопровождающим воспроизводить их на месте. Однако это не единственная причина.",
			correct: true
		},
		{
			text: "Это позволяет сопровождающим знать, на каком процессоре или GPU вы выполняете код.",
			explain: "Правильно! Как мы уже видели в этой главе, код, запущенный на GPU или CPU, может давать разные результаты или ошибки, и знание того, какое оборудование вы используете, может помочь сфокусировать внимание сопровождающих. Но это не единственное преимущество...",
			correct: true
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/7.mdx" />

### Отладка обучения[[debugging-the-training-pipeline]]
https://huggingface.co/learn/course/ru/chapter8/4_tf.md

# Отладка обучения[[debugging-the-training-pipeline]]

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section4_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section4_tf.ipynb"},
]} />

Вы написали прекрасный сценарий для обучения или дообучения модели на заданной задаче, послушно следуя советам из [Главы 7](/course/chapter7). Но когда вы запускаете команду `model.fit()`, происходит нечто ужасное: вы получаете ошибку 😱! Или, что еще хуже, все вроде бы хорошо, обучение проходит без ошибок, но результирующая модель получается плохой. В этом разделе мы покажем вам, что можно сделать для отладки подобных проблем.

## Отладка обучающего пайплайна[[debugging-the-training-pipeline]]

<Youtube id="N9kO52itd0Q"/>

Проблема, когда вы сталкиваетесь с ошибкой в `model.fit()`, заключается в том, что она может возникнуть из нескольких источников, поскольку обучение обычно объединяет множество вещей, над которыми вы работали до этого момента. Проблема может заключаться в том, что в вашем наборе данных что-то не так, или в том, что вы пытаетесь объединить элементы наборов данных вместе. Или что-то не так в коде модели, функции потерь или оптимизаторе. И даже если при обучении все идет хорошо, во время оценки что-то может пойти не так, если возникнут проблемы с метрикой.

Лучший способ отладить ошибку, возникшую в `model.fit()`, - это вручную пройти весь конвейер, чтобы увидеть, где что-то пошло не так. В этом случае ошибку часто очень легко устранить.

Чтобы продемонстрировать это, мы используем следующий скрипт, который (пытается) дообучить модель DistilBERT на наборе данных [MNLI dataset](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)
```

Если вы попытаетесь выполнить его, вы можете получить несколько `VisibleDeprecationWarning` при преобразовании набора данных - это известная нам проблема UX, так что, пожалуйста, игнорируйте ее. Если вы читаете курс после, скажем, ноября 2021 года, и это все еще происходит, то отправляйте гневные твиты @carrigmat, пока он не исправит это.

Однако более серьезной проблемой является то, что мы получаем откровенную ошибку. И она действительно ужасающе длинная:

```python out
ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...']
```

Что это значит? Мы пытались обучиться на наших данных, но не получили градиента? Это вызывает недоумение; как мы вообще можем начать отлаживать что-то подобное? Если полученная ошибка не позволяет сразу понять, в чем проблема, лучшим решением будет последовательно пройтись по всем пунктам, убеждаясь на каждом этапе, что все выглядит правильно. И, конечно, начинать всегда нужно с...

### Проверка собственных данных[[check-your-data]]

Это само собой разумеется, но если ваши данные повреждены, Keras не сможет исправить их за вас. Поэтому прежде всего нужно посмотреть, что находится в вашем обучающем наборе.

Хотя заманчиво заглянуть в `raw_datasets` и `tokenized_datasets`, мы настоятельно рекомендуем обращаться к данным непосредственно в той точке, где они попадают в модель. Это означает, что надо посмотреть на выход из `tf.data.Dataset`, который вы создали с помощью функции `to_tf_dataset()`! Как же это сделать? Объекты `tf.data.Dataset` предоставляют нам целые батчи за раз и не поддерживают индексацию, поэтому мы не можем просто запросить `train_dataset[0]`. Однако мы можем вежливо попросить у него батч:

```py
for batch in train_dataset:
    break
```

`break` завершает цикл после одной итерации, поэтому мы берем первый батч, полученный из `train_dataset`, и сохраняем ее как `batch`. Теперь давайте посмотрим, что находится внутри:

```python out
{'attention_mask': <tf.Tensor: shape=(16, 76), dtype=int64, numpy=
 array([[1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0],
        ...,
        [1, 1, 1, ..., 1, 1, 1],
        [1, 1, 1, ..., 0, 0, 0],
        [1, 1, 1, ..., 0, 0, 0]])>,
 'label': <tf.Tensor: shape=(16,), dtype=int64, numpy=array([0, 2, 1, 2, 1, 1, 2, 0, 0, 0, 1, 0, 1, 2, 2, 1])>,
 'input_ids': <tf.Tensor: shape=(16, 76), dtype=int64, numpy=
 array([[ 101, 2174, 1010, ...,    0,    0,    0],
        [ 101, 3174, 2420, ...,    0,    0,    0],
        [ 101, 2044, 2048, ...,    0,    0,    0],
        ...,
        [ 101, 3398, 3398, ..., 2051, 2894,  102],
        [ 101, 1996, 4124, ...,    0,    0,    0],
        [ 101, 1999, 2070, ...,    0,    0,    0]])>}
```

Все выглядит правильно, не так ли? Мы передаем модели `labels`, `attention_mask` и `input_ids`, этого достаточно для вычисления выходов и расчета функции потерь. Так почему же у нас нет градиента? Посмотрите внимательнее: мы передаем на вход один словарь, а обучающая партия обычно представляет собой входной тензор или словарь, плюс тензор меток. Наши метки - это просто ключ в нашем входном словаре.

Является ли это проблемой? На самом деле, не всегда! Но это одна из самых распространенных проблем, с которыми вы столкнетесь при обучении трансформеров с помощью TensorFlow. Все наши модели могут вычислять потери внутри себя, но для этого необходимо передать метки во входной словарь. Именно это происходит, когда мы не указываем функцию потерь в `compile()`. С другой стороны, Keras обычно ожидает, что метки будут передаваться отдельно от входного словаря, и вычисление потерь обычно заканчивается неудачей, если этого не сделать.

Теперь проблема стала яснее: мы передали аргумент `loss`, что означает, что мы просим Keras вычислить для нас функцию потерь, но мы передали наши метки как входные данные для модели, а не как метки в том месте, где их ожидает Keras! Нам нужно выбрать одно из двух: либо мы используем внутреннюю функцию потерь модели и оставляем метки на месте, либо мы продолжаем использовать функцию потерь Keras, но перемещаем метки в то место, где их ожидает Keras. Для простоты возьмем первый подход. Изменим вызов `compile()`:

```py
model.compile(optimizer="adam")
```

Теперь мы будем использовать конкретную функцию  потерь модели, и эта проблема должна быть решена!

<Tip>

✏️ **Ваша очередь!** В качестве дополнительной задачи после решения других проблем вы можете попробовать вернуться к этому шагу и заставить модель работать с оригинальной функцией потерь, вычисленными Keras. Вам нужно будет добавить `"labels"` к аргументу `label_cols` в `to_tf_dataset()`, чтобы обеспечить корректный вывод меток, что позволит получить градиенты - но есть еще одна проблема с функцией потерь, которую мы указали. Обучение будет продолжаться и с этой проблемой, но обучение будет происходить очень медленно и застопорится на высоком уровне потерь при обучении. Можете ли вы понять, в чем дело?

Подсказка в кодировке ROT13, если вы застряли: Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf?

И вторая подсказка: Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf nyy gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf?

</Tip>

Теперь попробуем провести обучение. Теперь мы должны получить градиенты, так что, надеюсь (здесь играет зловещая музыка), мы можем просто вызвать `model.fit()` и все будет работать отлично!

```python out
  246/24543 [..............................] - ETA: 15:52 - loss: nan
```

О, нет.

"nan" — не очень обнадеживающаее значение функции потерь. Тем не менее, мы проверили наши данные, и они выглядят довольно хорошо. Если проблема не в этом, как двигаться дальше? Очевидным следующим шагом будет...

### Проверка модели[[check-your-model]]

`model.fit()` - очень удобная функция в Keras, но она делает много вещей за вас, и это может затруднить поиск того, где именно возникла проблема. Если вы отлаживаете свою модель, одна из стратегий, которая может действительно помочь, - это передать модели только одну партию данных и подробно просмотреть выходные данные для этой одной партии. Еще один очень полезный совет, если модель выдает ошибки, - запустите `compile()` модели с `run_eagerly=True`. Это сделает ее намного медленнее, но сделает сообщения об ошибках гораздо более понятными, потому что они будут указывать, где именно в коде вашей модели возникла проблема.

Впрочем, пока что `run_eagerly` нам не нужен. Давайте прогоним полученный ранее батч через модель и посмотрим, что получится на выходе:

```py
model(batch)
```

```python out
TFSequenceClassifierOutput(loss=<tf.Tensor: shape=(16,), dtype=float32, numpy=
array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan], dtype=float32)>, logits=<tf.Tensor: shape=(16, 2), dtype=float32, numpy=
array([[nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan],
       [nan, nan]], dtype=float32)>, hidden_states=None, attentions=None)
```

Ну, это сложно. Все есть `nan`! Но это странно, не так ли? Как бы все наши логиты стали `nan`? `nan` означает "не число". Значения `nan` часто возникают, когда вы выполняете запрещенную операцию, например деление на ноль. Но в машинном обучении очень важно знать о `nan`: это значение имеет тенденцию к *распространению*. Если вы умножите число на `nan`, то на выходе также получится `nan`. И если вы получите `nan` где-нибудь в вашем выходе, потерях или градиенте, то это быстро распространится по всей вашей модели - потому что когда это `nan` значение распространяется обратно через вашу сеть, вы получите `nan` градиенты, а когда обновления веса вычисляются с этими градиентами, вы получите `nan` веса, а эти веса вычислят еще более `nan` выходы! Вскоре вся сеть будет представлять собой один большой блок `nan`. Когда это произойдет, будет довольно сложно понять, где началась проблема. Как мы можем определить, где `nan` впервые прокрался в сеть?

Ответ заключается в том, чтобы попробовать *переинициализировать* нашу модель. Как только мы начали обучение, где-то появился `nan`, и он быстро распространился по всей модели. Итак, давайте загрузим модель из контрольной точки и не будем делать никаких обновлений весов, и посмотрим, где мы получим значение `nan`:

```py
model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)
model(batch)
```

После запуска получим: 

```py out
TFSequenceClassifierOutput(loss=<tf.Tensor: shape=(16,), dtype=float32, numpy=
array([0.6844486 ,        nan,        nan, 0.67127866, 0.7068601 ,
              nan, 0.69309855,        nan, 0.65531296,        nan,
              nan,        nan, 0.675402  ,        nan,        nan,
       0.69831556], dtype=float32)>, logits=<tf.Tensor: shape=(16, 2), dtype=float32, numpy=
array([[-0.04761693, -0.06509043],
       [-0.0481936 , -0.04556257],
       [-0.0040929 , -0.05848458],
       [-0.02417453, -0.0684005 ],
       [-0.02517801, -0.05241832],
       [-0.04514256, -0.0757378 ],
       [-0.02656011, -0.02646275],
       [ 0.00766164, -0.04350497],
       [ 0.02060014, -0.05655622],
       [-0.02615328, -0.0447021 ],
       [-0.05119278, -0.06928903],
       [-0.02859691, -0.04879177],
       [-0.02210129, -0.05791225],
       [-0.02363213, -0.05962167],
       [-0.05352269, -0.0481673 ],
       [-0.08141848, -0.07110836]], dtype=float32)>, hidden_states=None, attentions=None)
```

*Теперь* у нас что-то получается! В наших логарифмах нет значений `nan`, что обнадеживает. Но мы видим несколько `nan`-значений в наших потерях! Может быть, в этих образцах есть что-то особенное, что вызывает эту проблему? Давайте посмотрим, что это за выборки (обратите внимание, что если вы запустите этот код самостоятельно, то можете получить другие показатели, поскольку набор данных был перемешан):

```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])
```

Давайте посмотрим, какие индексы были у этих примеров: 

```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]])
```

Здесь много всего, но ничто не выделяется. Давайте посмотрим на метки классов:

```python out
labels = batch['labels'].numpy()
labels[indices]
```

```python out
array([2, 2, 2, 2, 2, 2, 2, 2, 2])
```

А! Все образцы `nan` имеют одну и ту же метку, и это метка 2. Это очень сильный намек. Тот факт, что мы получаем значение функции потерь `nan` только тогда, когда наша метка равна 2, говорит о том, что сейчас самое время проверить количество меток в нашей модели:

```python
model.config.num_labels
```

```python out
2
```

Теперь мы видим проблему: модель считает, что существует только два класса, но метка бывает равна 2, что означает, что на самом деле существует три класса (потому что 0 - это тоже класс). Вот так мы и получили `nan` - пытаясь вычислить потери для несуществующего класса! Давайте попробуем изменить это и снова подогнать модель:

```
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
```

Мы обучаемся! Больше никаких `nan`, и наши потери уменьшаются... вроде как. Если понаблюдать за этим некоторое время, можно начать испытывать некоторое нетерпение, потому что значение потерь остается упрямо высоким. Давайте остановим тренировку и попробуем подумать, в чем может быть причина этой проблемы. На данный момент мы уверены, что и данные, и модель в порядке, но наша модель плохо обучается. Что еще остается? Пришло время...

### Проверка гиперпараметров[[check-your-hyperparameters]]

Если вы посмотрите на приведенный выше код, то, возможно, вообще не увидите никаких гиперпараметров, кроме, возможно, `batch_size`, и это не кажется вероятной причиной. Однако не обманывайтесь: гиперпараметры есть всегда, и если вы их не видите, значит, вы просто не знаете, на что они настроены. В частности, запомните важную особенность Keras: если вы задаете функцию потерь, оптимизатора или активации с помощью строки, _все ее аргументы будут установлены в значения по умолчанию_. Это означает, что, несмотря на удобство использования такого способа, следует быть очень осторожным, так как это может легко скрыть от вас критические вещи. (Любой, кто попробует решить опциональную задачу, описанную выше, должен внимательно отнестись к этому факту).

В данном случае где мы задали аргумент с помощью строки? Изначально мы так делали с функцией потерь, но мы это исправили. Теперь мы задаем оптимизатор с помощью строки. Может ли это что-то скрывать от нас? Давайте посмотрим на [его аргументы](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam).

Здесь что-нибудь выделяется? Правильно - скорость обучения! Когда мы просто используем строку `'adam'`, мы получим скорость обучения по умолчанию, которая составляет 0.001, или 1e-3. Это слишком много для модели трансформера! В целом, мы рекомендуем использовать для моделей скорость обучения от 1e-5 до 1e-4; это в 10-100 раз меньше, чем значение, которое мы используем в данном случае. Похоже, это может стать серьезной проблемой, так что давайте попробуем ее уменьшить. Для этого нам нужно импортировать настоящий объект `optimizer`. Пока мы это делаем, давайте заново инициализируем модель из контрольной точки, на случай если обучение с высокой скоростью обучения повредило ее веса:

```python
from tensorflow.keras.optimizers import Adam

model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint)
model.compile(optimizer=Adam(5e-5))
```

<Tip>

💡 Вы также можете импортировать функцию `create_optimizer()` из 🤗 Transformers, которая даст вам оптимизатор AdamW с правильным затуханием весов, а также прогревом и затуханием скорости обучения. Этот оптимизатор часто дает несколько лучшие результаты, чем оптимизатор Adam по умолчанию.

</Tip>

Теперь мы можем попробовать подогнать модель под новую, улучшенную скорость обучения:

```python
model.fit(train_dataset)
```

```python out
319/24543 [..............................] - ETA: 16:07 - loss: 0.9718
```

Теперь значения функции потерь действительно изменяются! Наконец-то обучение выглядит так, как будто оно работает. Здесь можно извлечь урок: если модель работает, но потери не уменьшаются, и вы уверены, что с данными все в порядке, стоит проверить гиперпараметры, такие как скорость обучения и затухание веса. Слишком высокое значение любого из них с большой вероятностью приведет к тому, что обучение "застопорится" при высоком значении потерь.

## Другие потенциальные проблемы[[other-potential-issues]]

Мы рассмотрели проблемы, описанные в скрипте выше, но есть еще несколько распространенных ошибок, с которыми вы можете столкнуться. Давайте рассмотрим (очень неполный) список.

### Как справиться с ошибками нехватки памяти[[dealing-with-out-of-memory-errors]]

Признаком нехватки памяти является ошибка типа "OOM when allocating tensor" - OOM - это сокращение от "out of memory". Это очень распространенная опасность при работе с большими языковыми моделями. Если вы столкнулись с этим, хорошая стратегия - уменьшить размер батча вдвое и попробовать снова. Однако имейте в виду, что некоторые модели *очень* велики. Например, полноразмерная модель GPT-2 имеет 1,5 млрд. параметров, что означает, что вам потребуется 6 Гб памяти только для хранения модели и еще 6 Гб для ее градиентов! Для обучения полной модели GPT-2 обычно требуется более 20 ГБ VRAM, независимо от размера батча, что есть лишь у некоторых GPU. Более легкие модели, такие как `distilbert-base-cased`, гораздо легче запускать, и они обучаются гораздо быстрее.

<Tip>

В следующей части курса мы рассмотрим более продвинутые техники, которые помогут вам уменьшить объем занимаемой памяти и позволят дообучить самые большие модели.

</Tip>

### "Голодный" TensorFlow 🦛[[hungry-hungry-tensorflow]]

Одна особенность TensorFlow, о которой вам следует знать, заключается в том, что он выделяет *всю* память GPU под себя, как только вы загружаете модель или проводите обучение, а затем делит эту память по мере необходимости. Это отличается от поведения других фреймворков, например PyTorch, которые выделяют память по мере необходимости с помощью CUDA, а не делают это внутренне. Одним из преимуществ подхода TensorFlow является то, что он может часто выдавать полезные ошибки, когда у вас заканчивается память, и он может восстановиться из этого состояния без сбоя всего ядра CUDA. Но есть и важный недостаток: если вы запускаете два процесса TensorFlow одновременно, то **у вас будут проблемы**.

Если вы работаете на Colab, то вам не нужно беспокоиться об этом, но если вы работаете локально, то это определенно то, с чем вам следует быть осторожным. В частности, имейте в виду, что закрытие вкладки ноутбука не обязательно приведет к его закрытию! Вам может понадобиться выбрать работающие блокноты (те, что с зеленым значком) и вручную закрыть их в списке каталогов. Любой запущенный блокнот, использующий TensorFlow, может по-прежнему занимать много памяти GPU, а это значит, что любой новый запущенный блокнот может столкнуться с очень странными проблемами.

Если вы начинаете получать ошибки о CUDA, BLAS или cuBLAS в коде, который работал раньше, то очень часто причина кроется именно в этом. Вы можете использовать такую команду, как `nvidia-smi`, чтобы проверить - когда вы выключаете или перезапускаете текущий ноутбук, большая часть памяти свободна или она все еще используется? Если она все еще используется, значит, что-то еще держится за нее!


### Проверьте свои данные (еще раз!)[[check-your-data-again]]

Ваша модель научится чему-то только в том случае, если из ваших данных действительно можно чему-то научиться. Если в данных есть ошибка, которая портит их, или метки приписываются случайным образом, то, скорее всего, вы не сможете обучить модель на своем наборе данных. Одним из полезных инструментов здесь является `tokenizer.decode()`. Он превратит `input_ids` обратно в строки, и вы сможете просмотреть данные и понять, обучают ли ваши тренировочные данные тому, чему вы хотите их обучить. Например, после получения `пакета` из `tf.data.Dataset`, как мы делали выше, вы можете декодировать первый элемент следующим образом:

```py
input_ids = batch["input_ids"].numpy()
tokenizer.decode(input_ids[0])
```

Затем вы можете сравнить его с первой меткой, например, так:

```py
labels = batch["labels"].numpy()
label = labels[0]
```

Как только вы сможете просматривать данные в таком виде, вы сможете задать себе следующие вопросы:

- Понятны ли декодированные данные?
- Правильные ли метки классов?
- Есть ли одна метка классов, которая встречается чаще других?
- Каким должно быть значение функции потерь/оценки, если модель предсказала случайный ответ/всегда один и тот же ответ?

Просмотрев данные, проанализируйте несколько предсказаний модели - если модель выводит токены, попробуйте декодировать и их! Если модель всегда предсказывает одно и то же, это может быть связано с тем, что ваш набор данных смещен в сторону одной категории (для проблем классификации), поэтому такие методы, как oversampling редких классов, могут помочь. Кроме того, это может быть вызвано проблемами с обучением, например, неправильными настройками гиперпараметров.

Если потери/метрики, которые вы получаете на начальной модели до обучения, сильно отличаются от потерь/метрик, ожидаемых для случайных прогнозов, перепроверьте способ вычисления функции потерь или метрик, так как, возможно, в них есть ошибка. Если вы используете несколько функций потерь, проверьте что они имеют одинаковый масштаб.

Когда вы убедитесь, что ваши данные идеальны, вы можете проверить, способна ли модель обучаться на них, с помощью одного простого теста.

### Переобучение модели на одном батче[[overfit-your-model-on-one-batch]]

Обычно мы стараемся избегать (переобучения), поскольку это означает, что модель не учится распознавать общие характеристики, а просто запоминает обучающие выборки. Однако попытка обучить модель на одной выборке снова и снова - это хороший тест, позволяющий проверить, может ли проблема в том виде, в котором вы ее сформулировали, быть решена с помощью модели, которую вы пытаетесь обучить. Это также поможет вам понять, не слишком ли высока ваша начальная скорость обучения.

Сделать это после того, как вы определили объект `model`, очень просто: просто возьмите батч обучающих данных, а затем рассматривайте этот `batch` как весь набор данных, подгоняя модель на большом количестве эпох:

```py
for batch in train_dataset:
    break

# Убедитесь, что вы запустили model.compile() и установили свой оптимизатор,
# и ваши показатели потерь/метрики, если вы их используете

model.fit(batch, epochs=20)
```

<Tip>

💡 Если ваши обучающие данные несбалансированы, обязательно создайте партию обучающих данных, содержащую все метки.

</Tip>

Полученная модель должна иметь близкие к идеальным результаты для `батча`, значение функции потерь должно быстро уменьшаться до 0 (или минимальному значению для используемой вами функции потерь).

Если вам не удается добиться идеальных результатов, значит, что-то не так с постановкой задачи или данными, и вам следует это исправить. Только когда вам удастся пройти тест на избыточную подгонку, вы сможете быть уверены, что ваша модель действительно способна чему-то научиться.

<Tip warning={true}>

⚠️ Вам придется пересоздать модель и перекомпилировать ее после этого теста на переобучение, поскольку полученная модель, вероятно, не сможет восстановиться и научиться чему-то полезному на полном наборе данных.

</Tip>

### Не обучайте ничего, пока не получите первый бейзлайн.[[dont-tune-anything-until-you-have-a-first-baseline]]

Интенсивная настройка гиперпараметров всегда подчеркивается как самая сложная часть машинного обучения, но это лишь последний шаг, который поможет вам немного продвинуться по метрике. *Очень* плохие значения гиперпараметров, например, использование стандартной скорости обучения Adam 1e-3 в модели Transformer, конечно, приведет к тому, что обучение будет идти очень медленно или полностью остановится, но в большинстве случаев "разумные" гиперпараметры, например, скорость обучения от 1e-5 до 5e-5, будут работать просто отлично и дадут вам хорошие результаты. Поэтому не начинайте трудоемкий и дорогостоящий поиск гиперпараметров до тех пор, пока не получите что-то, что превзойдет бейзлайн, имеющийся для вашего набора данных.

Как только у вас будет достаточно хорошая модель, вы можете начать ее немного оптимизировать. Не пытайтесь запустить тысячу раз с разными гиперпараметрами, но сравните пару запусков с разными значениями одного гиперпараметра, чтобы получить представление о том, какой из них оказывает наибольшее влияние.

Если вы настраиваете саму модель, будьте проще и не пробуйте то, что не можете обосновать. Всегда возвращайтесь к тесту на перебор, чтобы проверить, не привело ли ваше изменение к каким-либо непредвиденным последствиям.

### Попросить о помощи[[ask-for-help]]

Надеемся, вы нашли в этом разделе советы, которые помогли вам решить вашу проблему, но если это не так, помните, что вы всегда можете спросить у сообщества на [форумах](https://discuss.huggingface.co/).

Вот некоторые дополнительные ресурсы, которые могут оказаться полезными:

- [" Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) by Joel Grus
- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) by Cecelia Shao
- [" How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts
- ["A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) by Andrej Karpathy

Конечно, не все проблемы, с которыми вы сталкиваетесь при обучении нейросетей, возникают по вашей вине! Если в библиотеке 🤗 Transformers или 🤗 Datasets вы столкнулись с чем-то, что кажется вам неправильным, возможно, вы обнаружили ошибку. Вам обязательно нужно рассказать нам об этом, и в следующем разделе мы объясним, как именно это сделать.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/4_tf.mdx" />

### Что делать, если возникла ошибка[[what-to-do-when-you-get-an-error]]
https://huggingface.co/learn/course/ru/chapter8/2.md

# Что делать, если возникла ошибка[[what-to-do-when-you-get-an-error]]

<CourseFloatingBanner chapter={8}
  classNames="absolute z-10 right-0 top-0"
  notebook={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter8/section2.ipynb"},
    {label: "Aws Studio", значение: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter8/section2.ipynb"},
]} />

В этом разделе мы рассмотрим некоторые распространенные ошибки, которые могут возникнуть при попытке сгенерировать предсказания на основе только что настроенной модели Transformer. Это подготовит вас к [разделу 4](../chapter8/4), где мы рассмотрим, как отладить сам этап обучения.

<Youtube id="DQ-CpJn6Rc4" />

Для этого раздела мы подготовили [репозиторий](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28), и если вы хотите запустить код в этой главе, вам сначала нужно скопировать модель в свой аккаунт на [Hugging Face Hub](https://huggingface.co). Для этого сначала авторизуйтесь на Hugging Face Hub. Если вы выполняете этот код в блокноте, вы можете сделать это с помощью следующей служебной функции:

```python
from huggingface_hub import notebook_login

notebook_login()
```

в терминале выполните следующее:

```bash
huggingface-cli login
```

Вас попросят ввести имя пользователя и пароль, токен будет сохранен в *~/.cache/huggingface/*. После того как вы вошли в систему, вы можете скопировать репозиторий-шаблон  с помощью следующей функции:

```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():
    # Клонируем репозиторий и извлекаем локальный путь
    template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28"
    commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4"
    template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash)
    # Создайте пустое репо на хабе
    имя_модели = template_repo_id.split("/")[1]
    create_repo(model_name, exist_ok=True)
    # Клонирование пустого репо
    new_repo_id = get_full_repo_name(model_name)
    new_repo_dir = имя_модели
    repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id)
    # Копирование файлов
    copy_tree(template_repo_dir, new_repo_dir)
    # Отправить в хаб
    repo.push_to_hub()
```

Теперь при вызове `copy_repository_template()` будет создана копия репозитория-шаблона под вашим аккаунтом.

## Отладка пайплайна из 🤗 Transformers[[debugging-the-pipeline-from-transformers]]

Чтобы начать наше путешествие в удивительный мир отладки моделей трансформеров, рассмотрим следующий сценарий: вы работаете с коллегой над проектом ответа на вопросы, который должен помочь клиентам сайта электронной коммерции найти ответы о потребительских товарах. Ваш коллега отправляет вам сообщение следующего содержания:

> Добрый день! Я только что провел эксперимент, используя техники из [главы 7](../chapter7/7) курса Hugging Face, и получил отличные результаты на SQuAD! Думаю, мы можем использовать эту модель в качестве отправной точки для нашего проекта. ID модели на хабе - "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". Не стесняйтесь протестировать ее :)

и первое, что приходит в голову, это загрузить модель, используя `pipeline` из 🤗 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
"""
```

О нет, кажется, что-то пошло не так! Если вы новичок в программировании, подобные ошибки могут показаться вам немного загадочными (что такое `OSError`?!). Это сообщение является лишь последней частью гораздо большего отчета об ошибке, называемого _Python traceback_ (он же трассировка стека). Например, если вы запустите этот код в Google Colab, вы увидите что-то похожее на содержимое скриншота ниже:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/traceback.png" alt="A Python traceback." width="100%"/>
</div>

В этих отчетах содержится много информации, поэтому давайте вместе пройдемся по ключевым местам. Первое, что следует отметить, - это то, что трассировки следует читать _снизу вверх_. Это может показаться странным, если вы привыкли читать английский текст сверху вниз, но это логично: трассировка показывает последовательность вызовов функций, которые делает `pipeline` при загрузке модели и токенизатора. (Более подробно о том, как работает `pipeline` под капотом, читайте в [Главе 2](../chapter2/1)).

<Tip>

🚨 Видите синюю рамку вокруг "6 frames" в трассировке Google Colab? Это специальная функция Colab, которая помещает отчет в раскрывающийся блок текста. Если вы не можете найти источник ошибки, обязательно раскройте этот блок, нажав на эти две маленькие стрелки.

</Tip>

Последняя строка трассировки указывает на последнее сообщение об ошибке и дает имя исключения, которое было вызвано. В данном случае тип исключения - `OSError`, что указывает на системную ошибку. Если мы прочитаем сопроводительное сообщение об ошибке, то увидим, что, похоже, возникла проблема с файлом *config.json* модели, и нам предлагается два варианта ее устранения:

```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
"""
```

<Tip>

💡 Если вы столкнулись с сообщением об ошибке, которое трудно понять, просто скопируйте и вставьте его в строку поиска Google или [Stack Overflow](https://stackoverflow.com/) (да, действительно!). Велика вероятность того, что вы не первый, кто столкнулся с этой ошибкой, и это хороший способ найти решения, которые опубликовали другие члены сообщества. Например, поиск по запросу `OSError: Can't load config for` на Stack Overflow дает несколько [результатов](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+), которые можно использовать в качестве отправной точки для решения проблемы.

</Tip>

В первом предложении нам предлагается проверить, действительно ли идентификатор модели правильный, поэтому первым делом нужно скопировать идентификатор и вставить его в строку поиска Hub:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/wrong-model-id.png" alt="The wrong model name." width="100%"/>
</div>

Хм, действительно, похоже, что модели нашего коллеги нет на хабе... ага, но в названии модели опечатка! В названии DistilBERT есть только одна буква "l", так что давайте исправим это и поищем вместо нее "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28":

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/true-model-id.png" alt="The right model name." width="100%"/>
</div>

Хорошо! Теперь давайте попробуем загрузить модель снова с правильным идентификатором модели:

```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
"""
```

Cнова неудача - добро пожаловать в повседневную жизнь инженера машинного обучения! Поскольку мы исправили ID модели, проблема должна заключаться в самом репозитории. Быстрый способ получить доступ к содержимому репозитория на 🤗 Hub - это функция `list_repo_files()` библиотеки `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']
```

Интересно - похоже, что в репозитории нет файла *config.json*! Неудивительно, что наш `pipeline` не смог загрузить модель; наш коллега, должно быть, забыл выложить этот файл в Hub после того, как выполнил дообучение. В этом случае проблема кажется довольно простой: мы можем попросить его добавить этот файл, или, поскольку из идентификатора модели видно, что использовалась предварительно обученная модель [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), мы можем загрузить конфигурацию для этой модели и отправить ее в наше репо, чтобы посмотреть, решит ли это проблему. Давайте попробуем это сделать. Используя приемы, изученные в [Главе 2](../chapter2/1), мы можем загрузить конфигурацию модели с помощью класса `AutoConfig`:

```python
from transformers import AutoConfig

pretrained_checkpoint = "distilbert-base-uncased"
config = AutoConfig.from_pretrained(pretrained_checkpoint)
```

<Tip warning={true}>

🚨 Применяемый здесь подход не является надежным, поскольку наш коллега мог изменить конфигурацию `distilbert-base-uncased` перед дообучением модели. В реальной жизни мы бы хотели сначала уточнить у него, но для целей этого раздела будем считать, что он использовал конфигурацию по умолчанию.

</Tip>

Затем мы можем отправить это в наш репозиторий моделей вместе с конфигурацией с помощью функции `push_to_hub()`:

```python
config.push_to_hub(model_checkpoint, commit_message="Add config.json")
```

Теперь мы можем проверить, работает ли это, загрузив модель из последнего коммита в ветке `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!
"""

question = "What is extractive question answering?"
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'}
```

Ура, сработало! Давайте вспомним, что вы только что узнали:

- Сообщения об ошибках в Python называются _tracebacks_ и читаются снизу вверх. Последняя строка сообщения об ошибке обычно содержит информацию, необходимую для поиска источника проблемы.
- Если последняя строка не содержит достаточной информации, пройдите путь вверх по трассировке и посмотрите, сможете ли вы определить, в каком месте исходного кода возникла ошибка.
- Если ни одно из сообщений об ошибках не помогло вам отладить проблему, попробуйте поискать в Интернете решение аналогичной проблемы.
- The `huggingface_hub` 
// 🤗 Hub?
предоставляет набор инструментов, с помощью которых вы можете взаимодействовать с репозиториями на Хабе и отлаживать их.

Теперь, когда вы знаете, как отлаживать конвейер, давайте рассмотрим более сложный пример на прямом проходе самой модели.

## Отладка прямого прохода модели[[debugging-the-forward-pass-of-your-model]]

Хотя `pipeline` отлично подходит для большинства приложений, где вам нужно быстро генерировать предсказания, иногда вам понадобится доступ к логам модели (например, если вы хотите применить какую-то пользовательскую постобработку). Чтобы увидеть, что может пойти не так в этом случае, давайте сначала возьмем модель и токенизатор:

```python
tokenizer = reader.tokenizer
model = reader.model
```

Далее нам нужен вопрос, поэтому давайте посмотрим, поддерживаются ли наши любимые фреймворки:

```python
question = "Which frameworks can I use?"
```

Как мы видели в [Главе 7](../chapter7/1), обычные шаги, которые нам нужно предпринять, - это токенизация входных данных, извлечение логитов начальных и конечных токенов, а затем декодирование диапазона ответов:

```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
# Выберем наиболее правдоподобную позицию начала ответа с помощью функции argmax
answer_start = torch.argmax(answer_start_scores)
# Выберем наиболее правдоподбную позицию окончания ответа с помощью функции argmax
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 <module>
      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'
"""
```

Похоже, что в нашем коде есть ошибка! Но мы не боимся небольшой отладки. Вы можете использовать отладчик Python в блокноте:

<Youtube id="rSPyvPw0p9k"/>

или в терминале: 

<Youtube id="5PkZ4rbHL6c"/>

Здесь чтение сообщения об ошибке говорит нам, что у объекта `'list'` нет атрибута 'size', и мы видим стрелку `-->`, указывающую на строку, где возникла проблема в `model(**inputs)`. Вы можете отладить это интерактивно, используя отладчик Python, но сейчас мы просто распечатаем фрагмент `inputs`, чтобы посмотреть, что у нас есть:

```python
inputs["input_ids"][:5]
```

```python out
[101, 2029, 7705, 2015, 2064]
```

Конечно, это выглядит как обычный `list` в Python, но давайте перепроверим тип:

```python
type(inputs["input_ids"])
```

```python out
list
```

Да, это точно список Python. Так что же пошло не так? Вспомним из [Главы 2](../chapter2/1), что классы `AutoModelForXxx` в 🤗 Transformers работают с _тензорами_ (либо в PyTorch, либо в TensorFlow), и обычной операцией является извлечение размерности тензора с помощью `Tensor.size()`, скажем, в PyTorch. Давайте еще раз посмотрим на трассировку, чтобы увидеть, какая строка вызвала исключение:

```
~/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'
```

Похоже, что наш код пытался вызвать `input_ids.size()`, но это явно не сработает для Python `list`. Как мы можем решить эту проблему? Поиск сообщения об ошибке на Stack Overflow дает довольно много релевантных [результатов](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f). При нажатии на первый из них появляется вопрос, аналогичный нашему, ответ на который показан на скриншоте ниже:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter8/stack-overflow.png" alt="An answer from Stack Overflow." width="100%"/>
</div>

В ответе рекомендуется добавить в токенизатор `return_tensors='pt'`, так что давайте посмотрим, сработает ли это для нас:

```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
# Выберем наиболее правдоподобную позицию начала ответа с помощью функции argmax
answer_start = torch.argmax(answer_start_scores)
# Выберем наиболее правдоподбную позицию окончания ответа с помощью функции argmax
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?
Answer: pytorch, tensorflow, and jax
"""
```

Отлично, это сработало! Это отличный пример того, насколько полезным может быть Stack Overflow: найдя похожую проблему, мы смогли воспользоваться опытом других членов сообщества. Однако подобный поиск не всегда дает нужный ответ, так что же делать в таких случаях? К счастью, на [Hugging Face forums](https://discuss.huggingface.co/) есть гостеприимное сообщество разработчиков, которые могут вам помочь! В следующем разделе мы рассмотрим, как составить хорошие вопросы на форуме, на которые, скорее всего, будет получен ответ.

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter8/2.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter9/9.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={9}
    classNames="absolute z-10 right-0 top-0"
/>

Давайте проверим, чему вы научились в этой главе!

### 1. Для чего можно использовать Gradio?

<Question
	choices={[
        {
			text: "Создание демо для вашей модели машинного обучения",
			explain: "С помощью нескольких строк кода на python вы можете создать демо для вашей ML-модели, используя нашу библиотеку готовых компонентов.",
			correct: true
		},
		{
			text: "Поделиться своей моделью машинного обучения с другими",
			explain: "Используя параметр <code>share=True</code> в методе запуска, вы можете сгенерировать ссылку для общего доступа, чтобы отправить ее всем желающим.",
            correct: true
		},
		{
			text: "Отладка вашей модели",
			explain: "Одно из преимуществ демо Gradio - возможность протестировать модель на реальных данных, которые можно изменять и наблюдать за изменением прогнозов модели в режиме реального времени, что поможет вам отладить модель.",
			correct: true
		},
		{
			text: "Обучить вашу модель",
			explain: "Gradio разработана для инференса модели, ПОСЛЕ того как модель обучена.",
		}
	]}
/>

### 2. Gradio работает ТОЛЬКО с моделями PyTorch

<Question
	choices={[
        {
			text: "Истина",
			explain: "Gradio работает с моделями PyTorch, но также работает с любым типом моделей машинного обучения!"
        },
        {
			text: "Ложь",
			explain: "Gradio не зависит от модели, то есть вы можете создать демо для любого типа модели машинного обучения.",
			correct: true
        }
	]}
/>

### 3. Где можно запустить демо Gradio?

<Question
	choices={[
        {
			text: "Стандартные IDE для python",
			explain: "Gradio отлично работает с вашей любимой IDE.",
            correct: true
        },
        {
			text: "Блокноты Google Colab",
			explain: "Вы можете создать и запустить демо в своем блокноте Google colab.",
			correct: true
        },
        {
			text: "Блокноты Jupyter",
			explain: "Отличный выбор - вы можете создать и запустить демо в блокноте Jupyter.",
			correct: true
        }
	]}
/>

### 4. Gradio предназначен в первую очередь для моделей NLP

<Question
	choices={[
        {
			text: "Истина",
			explain: "Gradio работает практически с любым типом данных, а не только с NLP."
        },
        {
			text: "Ложь",
			explain: "Gradio предоставляет разработчикам библиотеку готовых компонентов практически для всех типов данных.",
            correct: true
        }
	]}
/>

### 5. Какие из следующих функций поддерживаются Gradio?

<Question
	choices={[
        {
			text: "Множественные входы и выходы",
			explain: "С помощью Gradio можно использовать несколько входов и выходов. Все, что вам нужно сделать, это передать список входов и выходов в соответствующие им параметры",
            correct: true
        },
        {
			text: "Сохранение состояния данных",
			explain: "Gradio позволяет добавлять состояние в ваш интерфейс.",
			correct: true
        },
        {
			text: "Аутентификация с помощью имен пользователей и паролей",
			explain: "Передайте список кортежей имен пользователей/паролей в метод запуска для добавления аутентификации.",
			correct: true
        },
        {
			text: "Автоматический анализ того, кто использует ваше демо Gradio",
			explain: "Попробуйте еще раз - Gradio не предоставляет разработчикам аналитику о том, кто использует их демо."
        },
        {
			text: "Загрузка модели из хаба моделей Hugging Face Hub или Hugging Face Spaces",
			explain: "Абсолютно точно - загрузите любую модель Hugging Face, используя метод <code>gr.Interface.load()</code>.",
			correct: true
        }
	]}
/>

### 6. Какие из следующих способов загрузки модели Hugging Face из Hub или Spaces являются правильными?

<Question
	choices={[
        {
			text: "gr.Interface.load('huggingface/{user}/{model_name}')",
			explain: "Это правильный метод загрузки модели Hugging Face из Hub",
            correct: true
        },
        {
			text: "gr.Interface.load('model/{user}/{model_name}')",
			explain: "Это правильный метод загрузки модели Hugging Face из Hub",
			correct: true
        },
        {
			text: "gr.Interface.load('demos/{user}/{model_name}')",
			explain: "Попробуйте еще раз - вы не можете загрузить модель, используя префикс 'demos'."
        },
        {
			text: "gr.Interface.load('spaces/{user}/{model_name}')",
			explain: "Это правильный метод загрузки модели Hugging Face из Spaces",
			correct: true
        }
	]}
/>

### 7. Выберите все шаги, необходимые для добавления состояния в интерфейс Gradio

<Question
	choices={[
        {
			text: "Передать в функцию прогнозирования дополнительный параметр, который представляет собой состояние интерфейса.",
			explain: "Необходим дополнительный параметр, хранящий историю или состояние вашего интерфейса.",
            correct: true
        },
        {
			text: "В конце функции прогнозирования верните обновленное значение состояния в качестве дополнительного возвращаемого значения.",
			explain: "Это значение истории или состояния должно быть возвращено вашей функцией.",
            correct: true
        },
        {
			text: "Добавьте компоненты ввода состояния и вывода состояния при создании интерфейса",
			explain: "Gradio предоставляет компонент ввода и вывода состояния для сохранения данных.",
            correct: true
        }
	]}
/>

### 8. Какие из перечисленных ниже компонентов входят в библиотеку Gradio?

<Question
	choices={[
        {
			text: "Textbox.",
			explain: "Да, вы можете создавать текстовые поля с помощью компонента Textbox.",
            correct: true
        },
        {
			text: "Graph.",
			explain: "В настоящее время компонент Graph отсутствует.",
        },
        {
			text: "Image.",
			explain: "Да, вы можете создать виджет загрузки изображений с помощью компонента Image.",
            correct: true
        },
        {
			text: "Audio.",
			explain: "Да, вы можете создать виджет загрузки звука с помощью компонента Audio.",
            correct: true
        },
	]}
/>

### 9. Что позволяет делать Gradio `Blocks`?

<Question
	choices={[
        {
			text: "Объедините несколько демо в одно веб-приложение",
			explain: "Вы можете использовать функцию `with gradio.Tabs():`, чтобы добавить вкладки для нескольких демо",
			correct: true
        },
        {
			text: "Назначение триггеров событий, таких как clicked/changed/ и так далее, для компонентов `Blocks`.",
			explain: "Когда вы назначаете событие, вы передаете три параметра: fn - функция, которая должна быть вызвана, inputs - (список) компонентов ввода и outputs - (список) компонентов вывода, которые должны быть вызваны.",
			correct: true
        },
        {
			text: "Автоматическое определение того, какой компонент `Blocks` должен быть интерактивным, а какой статичным",
			explain: "Основываясь на определенных вами триггерах событий, `Blocks` автоматически определяет, должен ли компонент принимать пользовательский ввод или нет.",
			correct: true
        },
		 {
			text: "Создавать многоступенчатые демо; это означает, что вы можете повторно использовать результаты работы одного компонента в качестве исходных данных для следующего.",
			explain: "Вы можете использовать компонент в качестве входа для одного триггера событий, но выхода для другого.",
            correct: true
        },
	]}
/>

### 10. Вы можете поделиться публичной ссылкой на демо `Blocks` и разместить демо `Blocks` в пространстве Hugging Face.

<Question
	choices={[
        {
			text: "Истина",
			explain: "Как и в случае с `Interface`, все возможности обмена и хостинга одинаковы для демо `Blocks`!",
			correct: true
        },
        {
			text: "Ложь",
			explain: "Как и в случае с `Interface`, все возможности обмена и хостинга одинаковы для демо `Blocks`!",
			correct: false
        }
	]}
/>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/9.mdx" />

### Интеграция с Hugging Face Hub[[integrations-with-the-hugging-face-hub]]
https://huggingface.co/learn/course/ru/chapter9/5.md

# Интеграция с Hugging Face Hub[[integrations-with-the-hugging-face-hub]]

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section5.ipynb"},
]} />

Чтобы сделать вашу жизнь еще проще, Gradio напрямую интегрируется с Hugging Face Hub и Hugging Face Spaces.
Вы можете загружать демо из Hub и Spaces, используя всего *одну строку кода*.

### Загрузка моделей из Hugging Face Hub[[loading-models-from-the-hugging-face-hub]]
Для начала выберите одну из тысяч моделей, которые Hugging Face предлагает в Hub, как описано в [Главе 4](../chapter4/2).

Используя специальный метод `Interface.load()`, вы передаете `"model/"` (или, эквивалентно, `"huggingface/"`) 
после чего следует имя модели. 
Например, здесь приведен код для создания демо для [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), большой языковой модели, добавьте пару примеров ввода:

```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."
article = "<p style='text-align: center'><a href='https://github.com/kingoflolz/mesh-transformer-jax' target='_blank'>GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model</a></p>"

gr.Interface.load(
    "huggingface/EleutherAI/gpt-j-6B",
    inputs=gr.Textbox(lines=5, label="Input Text"),
    title=title,
    description=description,
    article=article,
).launch()
```
    
Код, приведенный выше, приведет к созданию интерфейса, представленного ниже:

<iframe src="https://course-demos-gpt-j-6B.hf.space" frameBorder="0" height="750" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Загрузка модели таким образом использует [Inference API](https://huggingface.co/inference-api) Hugging Face,
вместо того, чтобы загружать модель в память. Это идеально подходит для огромных моделей, таких как GPT-J или T0pp, которые
 которые требуют много RAM.

### Загрузка с Hugging Face Spaces[[loading-from-hugging-face-spaces]]
Чтобы загрузить любое пространство (Space) из Hugging Face Hub и воссоздать его локально, вы можете передать `spaces/` в `Interface`, за которым следует имя пространства.

Помните демо из раздела 1, которое удаляет фон изображения? Давайте загрузим его из Hugging Face Spaces:

```py
gr.Interface.load("spaces/abidlabs/remove-bg").launch()
```

<iframe src="https://course-demos-remove-bg-original.hf.space" frameBorder="0" height="650" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Одна из особенностей загрузки демо из Hub или Spaces заключается в том, что вы можете настраивать их 
переопределив любой из 
параметров. Здесь мы добавим заголовок и задействуем веб-камеру:

```py
gr.Interface.load(
    "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!"
).launch()
```

<iframe src="https://course-demos-Remove-bg.hf.space" frameBorder="0" height="550" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Теперь, когда мы изучили несколько способов интеграции Gradio с Hugging Face Hub, давайте рассмотрим некоторые дополнительные возможности класса `Interface`. Этому будет посвящен следующий раздел!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/5.mdx" />

### Введение в Gradio[[introduction-to-gradio]]
https://huggingface.co/learn/course/ru/chapter9/1.md

# Введение в Gradio[[introduction-to-gradio]]

<CourseFloatingBanner
    chapter={9}
    classNames="absolute z-10 right-0 top-0"
/>

В этой главе мы узнаем о том, как создавать **интерактивные демонстрации** для моделей машинного обучения.

Зачем вообще создавать демо или графический интерфейс для модели машинного обучения? Демо позволяют:

- **Разработчикам машинного обучения** легко представить свою работу широкой аудитории, включая нетехнические команды или клиентов
- **Исследователям** легче воспроизводить модели машинного обучения и их поведение
- **Тестировщики качества** или **конечные пользователи**, смогут легче выявлять и отлаживать точки отказа в моделях
- **Различные пользователи** смогут обнаружить алгоритмические ошибки в моделях

Мы будем использовать библиотеку Gradio для создания демо для наших моделей. Gradio позволяет создавать, настраивать и распространять веб-демо для любой модели машинного обучения, полностью на языке Python.

Вот несколько примеров демо машинного обучения, созданных с помощью Gradio:

* Модель **распознавания эскизов (sketch recognition)**, которая принимает эскиз и выводит метки того, что, по ее мнению, нарисовано:

<iframe src="https://course-demos-draw2.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

* Экстрактивная модель **ответа на вопрос**, которая принимает контекстный параграф и задание и выдает ответ и оценку вероятности (мы обсуждали этот тип модели [в главе 7](../chapter7/7)):

<iframe src="https://course-demos-question-answering-simple.hf.space" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

* Модель **удаления фона**, которая принимает изображение и возвращает его с удаленным фоном:

<iframe src="https://course-demos-remove-bg-original.hf.space" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Эта глава разбита на разделы, включающие как _концепции_, так и _приложения_. После изучения концепций в каждом разделе вы будете применять их для создания демо определенного типа, начиная от классификации изображений и заканчивая распознаванием речи. К тому времени, как вы закончите эту главу, вы сможете создавать эти демо (и многие другие!) всего в несколько строк кода на Python.

<Tip>
👀 Проверьте <a href="https://huggingface.co/spaces" target="_blank">Hugging Face Spaces</a> чтобы увидеть множество свежих примеров демо машинного обучения, созданных сообществом специалистов по машинному обучению!
</Tip>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/1.mdx" />

### Понимание класса Interface[[understanding-the-interface-class]]
https://huggingface.co/learn/course/ru/chapter9/3.md

# Понимание класса Interface[[understanding-the-interface-class]]

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section3.ipynb"},
]} />

В этом разделе мы подробно рассмотрим класс `Interface` и разберем
основные параметры, используемые для его создания.

## Как создать Interface[[how-to-create-an-interface]]

Вы заметите, что класс `Interface` имеет 3 обязательных параметра:

`Interface(fn, inputs, outputs, ...)`

Это параметры:

  - `fn`: функция прогнозирования, обернутая интерфейсом Gradio. Эта функция может принимать один или несколько параметров и возвращать одно или несколько значений
  - `inputs`: тип(ы) компонента(ов) ввода. Gradio предоставляет множество готовых компонентов, таких как `"image"` или `"mic"`.
  - `outputs`: тип(ы) компонента(ов) вывода. Опять же, Gradio предоставляет множество предварительно созданных компонентов, например, `" image"` или `"label"`.

Полный список компонентов [смотрите в документации Gradio](https://gradio.app/docs). Каждый предварительно созданный компонент можно настроить, инстанцировав соответствующий ему класс.

Например, как мы видели в [предыдущем разделе](../chapter9/2),
вместо передачи `"textbox"` в параметр `inputs`, вы можете передать компонент `Textbox(lines=7, label="Prompt")` для создания текстового поля с 7 строками и меткой.

Давайте рассмотрим еще один пример, на этот раз с компонентом `Audio`.

## Простой пример со звуком[[a-simple-example-with-audio]]

Как уже говорилось, Gradio предоставляет множество различных входов и выходов.
Поэтому давайте создадим `Interface`, работающий с аудио.

В этом примере мы создадим функцию audio-to-audio, которая принимает
аудиофайл и просто переворачивает его.

Для ввода мы будем использовать компонент `Audio`. При использовании компонента `Audio`,
вы можете указать, будет ли источником звука файл, который загружает пользователь
или микрофон, с помощью которого пользователь записывает свой голос. В данном случае давайте
зададим `"microphone"`. Просто ради интереса добавим к `Audio` метку, которая будет гласить
"Speak here...".

Кроме того, мы хотели бы получать аудио в виде массива numpy, чтобы можно было легко
"перевернуть" его. Поэтому мы зададим `"type"` в значение `"numpy"`, которое передаст входные
данные в виде кортежа (`sample_rate`, `data`) в нашу функцию.

Мы также будем использовать компонент вывода `Audio`, который может автоматически
рендерить кортеж с частотой дискретизации и массивом данных numpy в воспроизводимый аудиофайл.
В этом случае нам не нужно делать никаких настроек, поэтому мы будем использовать строку
ярлык `"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()
```

Код, приведенный выше, создаст интерфейс, подобный приведенному ниже (если ваш браузер не
не запрашивает разрешения на использование микрофона, <a href="https://huggingface.co/spaces/course-demos/audio-reverse" target="_blank">откройте демо в отдельной вкладке</a>.)

<iframe src="https://course-demos-audio-reverse.hf.space" frameBorder="0" height="250" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Теперь вы сможете записать свой голос и услышать, как вы говорите в обратную сторону - жутковато 👻!

## Обработка нескольких входов и выходов[[handling-multiple-inputs-and-outputs]]

Допустим, у нас есть более сложная функция, с несколькими входами и выходами.
В примере ниже у нас есть функция, которая принимает индекс выпадающего списка, значение слайдера и число,
и возвращает пример музыкального тона.

Посмотрите, как мы передаем список входных и выходных компонентов,
и посмотрите, сможете ли вы проследить за тем, что происходит.

Ключевым моментом здесь является то, что когда вы передаете:
* список входных компонентов, каждый компонент соответствует параметру по порядку.
* список выходных компонентов, каждый компонент соответствует возвращаемому значению.

В приведенном ниже фрагменте кода показано, как три компонента ввода соответствуют трем аргументам функции `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()
```

<iframe src="https://course-demos-generate-tone.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


### Метод `launch()`[[the-launch-method]]

До сих пор мы использовали метод `launch()` для запуска интерфейса, но мы
не обсуждали, что он делает.

По умолчанию метод `launch()` запускает демо на веб-сервере, который
работает локально. Если вы выполняете свой код в блокноте Jupyter или Colab, то
Gradio встроит демо GUI в блокнот, чтобы вы могли легко им пользоваться.

Вы можете настроить поведение `launch()` с помощью различных параметров:

  - `inline` - отображать ли интерфейс в виде строки в блокнотах Python.
  - `inbrowser` - автоматически ли запускать интерфейс в новой вкладке браузера по умолчанию.
  - `share` - создавать ли для интерфейса общедоступную ссылку с вашего компьютера. Что-то вроде ссылки на Google Drive!

Мы рассмотрим параметр `share` более подробно в следующем разделе!

## ✏️ Давайте применим это![[lets-apply-it]]

Давайте создадим интерфейс, который позволит вам продемонстрировать демо модели **распознавания речи**.
Чтобы было интереснее, мы будем принимать *либо* микрофонный вход, либо загруженный файл.

Как обычно, мы загрузим нашу модель распознавания речи с помощью функции `pipeline()` из 🤗 Transformers.
Если вам нужно быстро вспомнить, вы можете вернуться к [этому разделу в Главе 1](../chapter1/3). Далее мы реализуем функцию `transcribe_audio()`, которая обрабатывает аудио и возвращает транскрипцию. Наконец, мы обернем эту функцию в `Interface` с компонентами `Audio` на входе и просто текстом на выходе. В целом, код этого приложения выглядит следующим образом:

```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()
```

Если ваш браузер не запрашивает разрешения на использование микрофона, <a href="https://huggingface.co/spaces/course-demos/audio-reverse" target="_blank">откройте демо в отдельной вкладке</a>.

<iframe src="https://course-demos-asr.hf.space" frameBorder="0" height="550" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


Вот и все! Теперь вы можете использовать этот интерфейс для транскрибирования аудио. Обратите внимание, что
передавая параметр `optional` как `True`, мы позволяем пользователю предоставить
либо микрофон, либо аудиофайл (либо ни то, ни другое, но в этом случае будет выдано сообщение об ошибке).

Продолжайте, чтобы узнать, как поделиться своим интерфейсом с другими!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/3.mdx" />

### Расширенные возможности Interface[[advanced-interface-features]]
https://huggingface.co/learn/course/ru/chapter9/6.md

# Расширенные возможности Interface[[advanced-interface-features]]

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section6.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section6.ipynb"},
]} />

Теперь, когда мы можем создать базовый интерфейс и поделиться им, давайте изучим некоторые более продвинутые возможности, такие как состояние и интерпретации.

### Использование состояния для сохранения данных[[using-state-to-persist-data]]

Gradio поддерживает *состояние сеанса*, когда данные сохраняются при нескольких отправках в рамках
загруженной страницы. Состояние сессии полезно для создания демо, например, чат-ботов, где необходимо
сохранять данные по мере того, как пользователь взаимодействует с моделью. Обратите внимание, что состояние сессии не позволяет обмениваться данными между разными пользователями вашей модели.

Чтобы хранить данные о состоянии сеанса, необходимо выполнить три действия:

1. Передайте в вашу функцию *дополнительный параметр*, который представляет собой состояние интерфейса.
2. В завершении работы функции верните обновленное значение состояния в качестве *дополнительного возвращаемого значения*.
3. Добавьте входной компонент 'state' и выходной компонент 'state' при создании вашего `Interface`.

Посмотрите пример чатбота ниже:

```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()
```

<iframe src="https://course-demos-Chatbot-Demo.hf.space" frameBorder="0" height="350" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Обратите внимание, что состояние выходного компонента сохраняется при всех отправках данных.
Примечание: в параметр state можно передать значение по умолчанию,
которое используется в качестве начального значения состояния.

### Использование интерпретации для понимания прогнозов[[using-interpretation-to-understand-predictions]]

Большинство моделей машинного обучения представляют собой "черные ящики", и внутренняя логика функции скрыта от конечного пользователя. Чтобы стимулировать прозрачность, мы упростили добавление интерпретации в вашу модель, просто задав ключевое слово interpretation в классе Interface по умолчанию. Это позволит вашим пользователям понять, какие части входных данных отвечают за вывод. Взгляните на простой интерфейс ниже, который показывает классификатор изображений, также включающий интерпретацию:

```py
import requests
import tensorflow as tf

import gradio as gr

inception_net = tf.keras.applications.MobileNetV2()  # загрузим модель

# Загрузим человекочитаемые метки для 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()
```

Проверьте функцию интерпретации, отправив входные данные и нажав кнопку Интерпретировать (Interpret) под компонентом вывода.

<iframe src="https://course-demos-gradio-image-interpretation.hf.space" frameBorder="0" height="570" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Помимо метода интерпретации, предоставляемого Gradio по умолчанию, вы также можете задать `shap` для параметра `interpretation` и установить параметр `num_shap`. При этом используется интерпретация на основе Шэпли, о которой вы можете подробнее прочитать [здесь](https://christophm.github.io/interpretable-ml-book/shap.html). Наконец, в параметр `interpretation` можно передать собственную функцию интерпретации. Пример можно посмотреть на странице Gradio, посвященной началу работы [здесь](https://gradio.app/getting_started/).

На этом мы завершаем наше глубокое погружение в класс `Interface` в Gradio. Как мы уже видели, этот класс позволяет создавать демо машинного обучения в несколько строк кода на Python. Однако иногда возникает необходимость доработать демо, изменив его макет или соединив несколько функций предсказания в цепочку. Было бы здорово, если бы мы могли как-то разделить `Interface` на настраиваемые "блоки"? К счастью, такая возможность есть! Этой теме посвящен последний раздел.

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/6.mdx" />

### Gradio, проверка![[gradio-check]]
https://huggingface.co/learn/course/ru/chapter9/8.md

# Gradio, проверка![[gradio-check]]

<CourseFloatingBanner
    chapter={9}
    classNames="absolute z-10 right-0 top-0"
/>

На этом мы завершаем главу о создании классных демо на основе ML с помощью Gradio - надеемся, вам понравилось! Напомним, что в этой главе мы узнали:

- Как создавать демо Gradio с помощью высокоуровневого API `Interface` и как настраивать различные модальности ввода и вывода.
- Различные способы поделиться демо Gradio, с помощью временных ссылок и хостинга на [Hugging Face Spaces](https://huggingface.co/spaces).
- Как интегрировать демо Gradio с моделями и Spaces на Hugging Face Hub.
- Расширенные возможности, такие как хранение состояния в демо или обеспечение аутентификации.
- Как получить полный контроль над потоком данных и макетом демо с помощью Gradio Blocks.

Если вы хотите проверить свое понимание концепций, рассмотренных в этой главе, пройдите тест в следующем разделе!

## Что дальше?[[where-to-next]]

Если вы хотите узнать больше о Gradio, вы можете

- Взглянуть на [Demo](https://github.com/gradio-app/gradio/tree/main/demo) репозиторий, там довольно много примеров.
- Посмотреть страницу [Guides](https://gradio.app/guides/), где вы можете найти руководства о крутых и продвинутых функциях.
- Заглянуть на страницу [Docs](https://gradio.app/docs/), чтобы узнать детали.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/8.mdx" />

### Делимся демо с другими[[sharing-demos-with-others]]
https://huggingface.co/learn/course/ru/chapter9/4.md

# Делимся демо с другими[[sharing-demos-with-others]]

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section4.ipynb"},
]} />

Теперь, когда вы создали демо, вы наверняка захотите поделиться им с другими. Демо Gradio
можно распространять двумя способами: используя ***временную ссылку для общего доступа*** или ***постоянный хостинг на Spaces***.

Мы рассмотрим оба этих подхода в ближайшее время. Но прежде чем выложить свое демо, вы, возможно, захотите доработать его 💅.

### Доработка демо Gradio:[[polishing-your-gradio-demo]]

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter9/gradio-demo-overview.png" alt="Overview of a gradio interface">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter9/gradio-demo-overview-dark.png" alt="Overview of a gradio interface">
</div>

Чтобы добавить дополнительный контент в демо, класс `Interface` поддерживает некоторые необязательные параметры:
    - `title`: вы можете дать название своему демо, которое будет отображаться _над_ компонентами ввода и вывода.
    - `description`: вы можете дать описание (в виде текста, Markdown или HTML) для интерфейса, которое отображается над компонентами ввода и вывода и под заголовком.
    - `article`: вы также можете написать расширенную статью (в текстовом формате, Markdown или HTML), объясняющую интерфейс. Если она задана, она отображается _под_ компонентами ввода и вывода.
    - `theme`: не нравятся цвета по умолчанию? Установите для темы одно из значений `default`, `huggingface`, `grass`, `peach`. Вы также можете добавить префикс `dark-`, например, `dark-peach` для темной темы (или просто `dark` для темной темы по умолчанию).
    - `examples`: чтобы сделать ваше демо более удобным в использовании, вы можете предоставить несколько примеров входов для функции. Они появляются под компонентами пользовательского интерфейса и могут быть использованы для заполнения интерфейса. Они должны быть предоставлены в виде вложенного списка, в котором внешний список состоит из примеров, а каждый внутренний список состоит из ввода, соответствующего каждому компоненту ввода.
    - `live`: если вы хотите сделать демо "живым", то есть чтобы ваша модель запускалась заново при каждом изменении входных данных, вы можете установить `live=True`. Это имеет смысл использовать с быстрыми моделями (пример мы увидим в конце этого раздела)
Используя приведенные выше варианты, мы получим более завершенный интерфейс. Запустите приведенный ниже код, и вы сможете пообщаться с Риком и Морти:

```py
title = "Ask Rick a Question"
description = """
The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything!
<img src="https://huggingface.co/spaces/course-demos/Rick_and_Morty_QA/resolve/main/rick.png" width=200px>
"""

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."

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?"]],
).launch()
```

Используя приведенные выше варианты, мы получим более завершенный интерфейс. Попробуйте интерфейс, представленный ниже:

<iframe src="https://course-demos-Rick-and-Morty-QA.hf.space" frameBorder="0" height="800" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### Распространение демо с помощью временных ссылок[[sharing-your-demo-with-temporary-links]]
Теперь, когда у нас есть работающее демо нашей модели машинного обучения, давайте узнаем, как легко поделиться ссылкой на наш интерфейс.
Интерфейсами можно легко поделиться публично, установив `share=True` в методе `launch()`:

```python
gr.Interface(classify_image, "image", "label").launch(share=True)
```

В результате создается общедоступная ссылка, которую вы можете отправить кому угодно! Когда вы отправляете эту ссылку, пользователь на другой стороне может опробовать модель в своем браузере в течение 72 часов. Поскольку обработка происходит на вашем устройстве (пока оно включено!), вам не нужно беспокоиться об упаковке каких-либо зависимостей. Если вы работаете в блокноте Google Colab, ссылка на общий доступ всегда создается автоматически. Обычно она выглядит примерно так: **XXXXX.gradio.app**. Хотя ссылка предоставляется через Gradio, мы являемся лишь прокси для вашего локального сервера и не храним никаких данных, передаваемых через интерфейсы.

Однако не забывайте, что эти ссылки общедоступны, а значит, любой желающий может использовать вашу модель для прогнозирования! Поэтому не раскрывайте конфиденциальную информацию через написанные вами функции и не допускайте критических изменений на вашем устройстве. Если вы установите `share=False` (по умолчанию), будет создана только локальная ссылка.

### Хостинг вашего демо на Hugging Face Spaces[[hosting-your-demo-on-hugging-face-spaces]]

Ссылка на ресурс, которую можно передать коллегам, - это здорово, но как разместить демо на постоянном хостинге, чтобы оно существовало в своем собственном "пространстве (space)" в Интернете?

Hugging Face Spaces предоставляет инфраструктуру для постоянного размещения вашей модели Gradio в интернете, **бесплатно**! Spaces позволяет вам создать и разместить в (публичном или частном) репозитории,
где ваш Gradio
код интерфейса будет существовать в файле `app.py`. [Прочитайте пошаговое руководство](https://huggingface.co/blog/gradio-spaces) чтобы начать работу, или посмотрите видео с примером ниже.

<Youtube id="LS9Y2wDVI0k" />

## ✏️ Давайте применим это![[lets-apply-it]]

Используя то, что мы узнали в предыдущих разделах, давайте создадим демо распознавания скетчей, которое мы видели в [первом разделе этой главы](../chapter9/1). Давайте добавим некоторые настройки в наш интерфейс и установим `share=True`, чтобы создать публичную ссылку, которую мы сможем передавать всем желающим.

Мы можем загрузить метки из файла [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) и загрузить предварительно обученную модель pytorch из файла [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Загрузите эти файлы, перейдя по ссылке и нажав кнопку загрузки в левом верхнем углу окна предварительного просмотра файла. Давайте посмотрим на приведенный ниже код, чтобы увидеть, как мы используем эти файлы для загрузки нашей модели и создания функции `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)}
```

Теперь у нас есть функция `predict()`. Следующим шагом будет определение и запуск нашего интерфейса 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!",
    article="<p style='text-align: center'>Sketch Recognition | Demo Model</p>",
    live=True,
)
interface.launch(share=True)
```

<iframe src="https://course-demos-Sketch-Recognition.hf.space" frameBorder="0" height="650" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


Обратите внимание на параметр `live=True` в `Interface`, который означает, что демо скетча делает
предсказание каждый раз, когда кто-то рисует на скетчпаде (без кнопки "Исполнить (submit)"!).

Кроме того, мы также установили аргумент `share=True` в методе `launch()`.
Это создаст общедоступную ссылку, которую вы можете
отправить кому угодно! Когда вы отправите эту ссылку, пользователь на другой стороне сможет опробовать
модель распознавания эскизов. Повторим, что модель также можно разместить на Hugging Face Spaces,
именно так мы и разместили демо выше.

Далее мы расскажем о других способах использования Gradio в экосистеме Hugging Face!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/4.mdx" />

### Введение в Gradio Blocks[[introduction-to-gradio-blocks]]
https://huggingface.co/learn/course/ru/chapter9/7.md

# Введение в Gradio Blocks[[introduction-to-gradio-blocks]]

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section7.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section7.ipynb"},
]} />

В предыдущих разделах мы изучили и создали демо, используя класс `Interface`. В этом разделе мы представим наш **свеже разработанный** низкоуровневый API под названием `gradio.Blocks`.

Итак, в чем разница между `Interface` и `Blocks`?

- ⚡ `Interface`: высокоуровневый API, который позволяет создать полноценное демо машинного обучения, просто предоставив список входных и выходных данных.

- 🧱 `Blocks`: низкоуровневый API, который позволяет вам полностью контролировать потоки данных и компоновку вашего приложения. Вы можете создавать очень сложные, многоступенчатые приложения, используя `Blocks` (как "строительные блоки").


### Почему Blocks 🧱?[[why-blocks-]]

Как мы видели в предыдущих разделах, класс `Interface` позволяет легко создавать полноценные демо машинного обучения с помощью всего нескольких строк кода. API `Interface` чрезвычайно прост в использовании, но ему не хватает гибкости, которую обеспечивает API `Blocks`. Например, вы можете захотеть:

- Сгруппировать связанные демо в виде нескольких вкладок в одном веб-приложении
- Изменить макет вашего демо, например, указать, где расположены входные и выходные компоненты
- Многоступенчатые интерфейсы, в которых выход одной модели становится входом для следующей модели, или более гибкие потоки данных в целом
- Изменить свойства компонента (например, выбор в выпадающем списке) или его видимость на основе пользовательского ввода

Ниже мы рассмотрим все эти понятия.

### Создание простого демо с помощью блоков[[creating-a-simple-demo-using-blocks]]

После того как вы установили Gradio, запустите приведенный ниже код в виде сценария на Python, блокнота Jupyter или блокнота 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()
```

<iframe src="https://course-demos-flip-text.hf.space" frameBorder="0" height="400" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

В этом простом примере представлены 4 концепции, которые лежат в основе блоков:

1. Блоки позволяют создавать веб-приложения, сочетающие в себе разметку, HTML, кнопки и интерактивные компоненты, просто инстанцируя объекты на Python в контексте `with gradio.Blocks`.

<Tip>
🙋Если вы не знакомы с оператором `with` в Python, рекомендуем ознакомиться с отличным [руководством](https://realpython.com/python-with-statement/) от Real Python. Возвращайтесь сюда после его прочтения 🤗

</Tip>

Порядок, в котором вы инстанцируете компоненты, имеет значение, поскольку каждый элемент отображается в веб-приложении в том порядке, в котором он был создан. (Более сложные макеты рассматриваются ниже)

2. Вы можете определять обычные функции Python в любом месте вашего кода и запускать их с пользовательским вводом с помощью `Blocks`. В нашем примере мы используем простую функцию, которая "переворачивает" введенный текст, но вы можете написать любую функцию Python, от простого вычисления до обработки прогнозов модели машинного обучения.

3. Вы можете назначить события для любого компонента `Blocks`. Это позволит запускать вашу функцию при нажатии на компонент, его изменении и т. д. Когда вы назначаете событие, вы передаете три параметра: `fn`: функция, которая должна быть вызвана, `inputs`: (список) входных компонентов, и `outputs`: (список) выходных компонентов, которые должны быть вызваны.

   В примере выше мы запускаем функцию `flip_text()`, когда значение в `Textbox` с названием `input` изменяется. Событие считывает значение в `input`, передает его в качестве именнованного параметра в `flip_text()`, которая затем возвращает значение, которое присваивается нашему второму `Textbox` с именем `output`.

   Список событий, которые поддерживает каждый компонент, можно найти в [документации Gradio](https://www.gradio.app/docs/).

4. Блоки автоматически определяют, должен ли компонент быть интерактивным (принимать пользовательский ввод) или нет, основываясь на определенных вами триггерах событий. В нашем примере первый textbox является интерактивным, поскольку его значение используется функцией `flip_text()`. Второй textbox не является интерактивным, поскольку его значение никогда не используется в качестве входа. В некоторых случаях вы можете переопределить это, передав булево значение в параметр `interactive` компонента (например, `gr.Textbox(placeholder="Flip this text", interactive=True)`).

### Настройка макета вашего демо[[customizing-the-layout-of-your-demo]]

Как мы можем использовать `Blocks` для настройки макета нашего демо? По умолчанию `Blocks` отображает созданные вами компоненты вертикально в одной колонке. Вы можете изменить это, создав дополнительные колонки `с помощью gradio.Column():` или строки `с помощью gradio.Row():` и создав компоненты в этих контекстах.

Вот что следует иметь в виду: все компоненты, созданные в `Column` (это также значение по умолчанию), будут располагаться вертикально. Любой компонент, созданный в `Row`, будет располагаться горизонтально, аналогично [модели flexbox в веб-разработке](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox).

Наконец, вы можете создавать вкладки для демо с помощью контекстного менеджера `with gradio.Tabs()`. В этом контексте вы можете создать несколько вкладок, указав дочерние вкладки в `with gradio.TabItem(name_of_tab):` . Любой компонент, созданный внутри контекста `with gradio.TabItem(name_of_tab):`, отображается на этой вкладке.

Теперь давайте добавим функцию `flip_image()` в наше демо и добавим новую вкладку, которая будет переворачивать изображения. Ниже приведен пример с 2 вкладками, в котором также используется 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()
```

<iframe src="https://course-demos-flip-text-image.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


Вы заметите, что в этом примере мы также создали компонент `Button` на каждой вкладке и назначили событие click для каждой кнопки, что, собственно, и запускает функцию.

### Изучение событий и состояния[[exploring-events-and-state]]

Так же, как вы можете управлять макетом, `Blocks` дает вам тонкий контроль над тем, какие события вызывают вызовы функций. Каждый компонент и многие макеты имеют определенные события, которые они поддерживают.

Например, у компонента `Textbox` есть 2 события: `change()` (когда меняется значение внутри текстового поля) и `submit()` (когда пользователь нажимает клавишу ввода, будучи сфокусированным на текстовом поле). Более сложные компоненты могут иметь еще больше событий: например, компонент `Audio` также имеет отдельные события для воспроизведения аудиофайла, очистки, приостановки и так далее. О том, какие события поддерживает каждый компонент, читайте в документации.

Вы можете прикрепить триггер события ни к одному, одному или нескольким из этих событий. Вы создаете триггер события, вызывая имя события на экземпляре компонента в виде функции - например, `textbox.change(...)` или `btn.click(...)`. Функция принимает три параметра, как говорилось выше:

- `fn`: функция для выполнения
- `inputs`: (список (list)) компонент(ов), значения которых должны быть переданы в качестве входных параметров функции. Значение каждого компонента по порядку сопоставляется с соответствующим параметром функции. Этот параметр может иметь значение None, если функция не принимает никаких параметров.
- `outputs`: (список (list)) компонентов, значения которых должны быть обновлены на основе значений, возвращаемых функцией. Каждое возвращаемое значение устанавливает значение соответствующего компонента по порядку. Этот параметр может быть None, если функция ничего не возвращает.

Вы даже можете сделать так, чтобы компонент ввода и компонент вывода были одним и тем же компонентом, как это сделано в данном примере, где используется модель GPT для дополнения текста:

```py
import gradio as gr

api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")


def complete_with_gpt(text):
    # Используем последние 50 символов текста в качестве контекста
    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()
```

<iframe src="https://course-demos-blocks-gpt.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### Создание многоступенчатых демо[[creating-multi-step-demos]]

В некоторых случаях вам может понадобиться _многоступенчатое демо_, в котором вы повторно используете выход одной функции в качестве входа для следующей. Это очень легко сделать с помощью `Blocks`, так как вы можете использовать компонент в качестве входа для одного триггера события, но выхода для другого. Посмотрите на компонент text в примере ниже: его значение является результатом работы модели преобразования речи в текст, но также передается в модель анализа настроений:

```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()
```

<iframe src="https://course-demos-blocks-multi-step.hf.space" frameBorder="0" height="600" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

### Обновление свойств компонента[[updating-component-properties]]

До сих пор мы видели, как создавать события для обновления значения другого компонента. Но что делать, если вы хотите изменить другие свойства компонента, например видимость текстового поля или варианты выбора в группе радио кнопок? Вы можете сделать это, вернув метод класса компонента `update()` вместо обычного возвращаемого значения из вашей функции.

Это легче всего проиллюстрировать на примере:

```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()
```

<iframe src="https://course-demos-blocks-update-component-properties.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Мы только что изучили все основные концепции `Blocks`! Как и в случае с `Interfaces`, вы можете создавать классные демо, которыми можно поделиться, используя `share=True` в методе `launch()` или развернуть на [Hugging Face Spaces](https://huggingface.co/spaces).

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/7.mdx" />

### Создание вашего первого демо[[building-your-first-demo]]
https://huggingface.co/learn/course/ru/chapter9/2.md

# Создание вашего первого демо[[building-your-first-demo]]

<CourseFloatingBanner chapter={9}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter9/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter9/section2.ipynb"},
]} />

Давайте начнем с установки Gradio! Поскольку это пакет для Python, просто выполните:

`$ pip install gradio `

Вы можете запускать Gradio где угодно, будь то ваша любимая IDE Python, Jupyter-блокнот или даже Google Colab 🤯!
Так что установите Gradio везде, где вы используете Python!

Давайте начнем с простого примера "Hello World", чтобы познакомиться с синтаксисом Gradio:

```py
import gradio as gr


def greet(name):
    return "Hello " + name


demo = gr.Interface(fn=greet, inputs="text", outputs="text")

demo.launch()
```

Давайте пройдемся по приведенному выше коду:

- Сначала мы определяем функцию `greet()`. В данном случае это простая функция, которая добавляет "Hello" перед вашим именем, но это может быть *любая* функция Python в целом. Например, в приложениях машинного обучения эта функция будет *вызывать модель для прогнозирования* на входных данных и возвращать вывод.
- Затем мы создаем интерфейс Gradio `Interface` с тремя аргументами, `fn`, `inputs` и `outputs`. Эти аргументы определяют функцию прогнозирования, а также _тип_ входных и выходных компонентов, которые мы хотим получить. В нашем случае оба компонента представляют собой простые текстовые поля.
- Затем мы вызываем метод `launch()` для созданного нами `Interface`.

Если вы запустите этот код, нижеприведенный интерфейс автоматически появится в блокноте Jupyter/Colab или откроется в браузере на **[http://localhost:7860](http://localhost:7860/)** при запуске из скрипта.

<iframe src="https://course-demos-hello-world.hf.space" frameBorder="0" height="250" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Попробуйте использовать этот GUI прямо сейчас с собственным именем или другими данными!

Вы заметите, что в этом GUI Gradio автоматически определил имя входного параметра (`name`)
и применил его в качестве метки поверх текстового поля. Что если вы захотите изменить это?
Или если вы хотите настроить текстовое поле каким-то другим способом? В этом случае вы можете
инстанцировать объект класса, представляющий компонент ввода.

Посмотрите на пример ниже:

```py
import gradio as gr


def greet(name):
    return "Hello " + name


# Мы инстанцируем класс Textbox
textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2)

gr.Interface(fn=greet, inputs=textbox, outputs="text").launch()
```

<iframe src="https://course-demos-hello-world-custom.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Здесь мы создали текстовое поле ввода с меткой, заполнителем и заданным количеством строк.
То же самое можно сделать и для выходного текстового поля, но мы пока что остановимся на этом.

Мы увидели, что с помощью всего нескольких строк кода Gradio позволяет создать простой интерфейс вокруг любой функции
с любыми входами и выходами. В этом разделе мы начали с
простого текстового поля, но в следующих разделах мы рассмотрим другие виды входов и выходов. Теперь давайте рассмотрим применение некоторого NLP в приложении Gradio.


## 🤖 Добавление прогнозов модели[[including-model-predictions]]

Теперь давайте рассмотрим простой интерфейс, который позволит продемонстрировать демо модели **генерации текста (text-generation)**, такой как GPT-2.

Мы загрузим нашу модель с помощью функции `pipeline()` из 🤗 Transformers.
Если вам нужно быстро освежить в памяти материал, вы можете вернуться к [этому разделу в Главе 1](../chapter1/3#text-generation).

Сначала мы определяем функцию прогнозирования, которая принимает текстовую подсказку (text prompt) и возвращает ее завершение текста:

```py
from transformers import pipeline

model = pipeline("text-generation")


def predict(prompt):
    completion = model(prompt)[0]["generated_text"]
    return completion
```

Эта функция завершает введенные вами подсказки, и вы можете запустить ее с вашими собственными подсказками, чтобы посмотреть, как она работает. Вот пример (вы можете получить другое завершение):

```
predict("My favorite programming language is")
```

```
>> 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.
```

Теперь, когда у нас есть функция для генерации прогнозов, мы можем создать и запустить `Interface` таким же образом, как мы делали это ранее:

```py
import gradio as gr

gr.Interface(fn=predict, inputs="text", outputs="text").launch()
```


Вот и все! Теперь вы можете использовать этот интерфейс для генерации текста с помощью модели GPT-2, как показано ниже 🤯.

<iframe src="https://course-demos-gpt-2.hf.space" frameBorder="0" height="300" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

Продолжайте читать, чтобы узнать, как создавать другие виды демо с помощью Gradio!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter9/2.mdx" />

### Занятия и семинары[[live-sessions-and-workshops]]
https://huggingface.co/learn/course/ru/events/1.md

# Занятия и семинары[[live-sessions-and-workshops]]

В связи с выходом первой и второй частей курса мы организовали несколько занятий и семинаров по программированию. Ссылки на записи этих сессий и семинаров вы можете найти ниже.

## Занятия по программированию[[live-coding-sessions]]

На первом занятии Сильвен вместе с вами изучит Главу 1 курса, объясняя ее шаг за шагом:

<Youtube id="aV4wfnIakSQ"/>

На втором занятии настал черед Льюиса представить главу 2:

<Youtube id="qEl7eORxpFA"/>

Поскольку Глава 2 настолько классная, Сильвен также сделал видео к ней!

<Youtube id="u4e8OGWYpPk"/>

В Главе 3 Льюис возвращается, чтобы обьяснить вам код:

<Youtube id="Be4s0dsbavM"/>

Наконец, Омар завершает занятия, связанные с первой частью курса, рассмотрением главы 4:

<Youtube id="1ATVsyBxu1I"/>

## Семинары[[workshops]]
На первом семинаре Мерве приглашает Льюиса обсудить раздел 7 главы 7, посвященный задаче [ответа на вопросы](https://huggingface.co/learn/nlp-course/ru/chapter7/7).

<Youtube id="Ihgk8kGLpIE"/>

На втором семинаре Мерве приглашает Леандро рассказать о 6 разделе 7 главы, посвященном [обучению каузальной языковой модели с нуля](https://huggingface.co/learn/nlp-course/ru/chapter7/6) и приложению [CodeParrot](https://huggingface.co/codeparrot).

<Youtube id="ExUR7w6xe94"/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/events/1.mdx" />

### Вечеринка Gradio Blocks[[gradio-blocks-party]]
https://huggingface.co/learn/course/ru/events/3.md

# Вечеринка Gradio Blocks[[gradio-blocks-party]]

Одновременно с выпуском главы курса Gradio компания Hugging Face провела мероприятие для сообщества, посвященное созданию крутых демо машинного обучения с помощью новой функции Gradio Blocks.

Все демо, созданные сообществом, вы можете найти в организации [`Gradio-Blocks`](https://huggingface.co/Gradio-Blocks)  на Hub. Вот несколько примеров от победителей:

**Преобразование естественного языка в SQL**

<iframe src="https://curranj-words-to-sql.hf.space" frameBorder="0" height="640" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/events/3.mdx" />

### Событие посвященное выходу 2 части курса[[part-2-release-event]]
https://huggingface.co/learn/course/ru/events/2.md

# Событие посвященное выходу 2 части курса[[part-2-release-event]]

В связи с выходом второй части курса мы организовали живую встречу с двумя днями выступлений перед спринтом по доработке. Если вы пропустили это мероприятие, вы можете просмотреть все выступления, которые перечислены ниже!

## День 1: Высокоуровневое представление о трансформерах и о том, как их обучать[[day-1-a-high-level-view-of-transformers-and-how-to-train-them]]

**Томас Вульф:** *Трансферное обучение и рождение библиотеки Transformers*

<Youtube id="wCYVeahJES0"/>

<p align="center">
<img src="https://i.imgur.com/9eq8oUi.png" alt="A visual summary of Thom's talk" width="80%"/>
</p>

Томас Вольф - соучредитель и главный научный директор компании Hugging Face. Инструменты, созданные Томасом Вольфом и командой Hugging Face, используются более чем в 5 000 исследовательских организаций, включая Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, Институт искусственного интеллекта Аллена, а также большинство университетских факультетов. Томас Вольф является инициатором и старшим председателем крупнейшей исследовательской коллаборации, когда-либо существовавшей в области искусственного интеллекта: ["BigScience"](https://bigscience.huggingface.co), а также набора широко используемых [библиотек и инструментов](https://github.com/huggingface/). Томас Вольф также является активным преподавателем, идейным лидером в области искусственного интеллекта и обработки естественного языка и постоянным приглашенным докладчиком на конференциях по всему миру [https://thomwolf.io](https://thomwolf.io).

**Джей Аламмар:** *Легкое визуальное введение в модели трансформеры*

<Youtube id="VzvG23gmcYU"/>

<p align="center">
<img src="https://i.imgur.com/rOZAuE9.png" alt="A visual summary of Jay's talk" width="80%"/>
</p>

Благодаря своему популярному блогу Джей помог миллионам исследователей и инженеров наглядно понять инструменты и концепции машинного обучения - от базовых (заканчивая документами по NumPy, Pandas) до самых современных (Transformers, BERT, GPT-3).

**Маргарет Митчелл:** *О ценностях в разработке ML*

<Youtube id="8j9HRMjh_s8"/>

<p align="center">
<img src="https://i.imgur.com/NuIsnY3.png" alt="A visual summary of Margaret's talk" width="80%"/>
</p>

Маргарет Митчелл - исследователь, работающий в области этики ИИ. В настоящее время она занимается вопросами разработки ИИ с учетом этических норм в технологиях. Она опубликовала более 50 работ по генерации естественного языка, ассистивным технологиям, компьютерному зрению и этике ИИ, а также имеет множество патентов в области генерации диалогов и классификации настроений. Ранее она работала в Google AI в качестве штатного научного сотрудника, где основала и возглавила группу Google Ethical AI, которая занималась фундаментальными исследованиями в области этики ИИ и операционализацией этики ИИ внутри Google. До прихода в Google она работала исследователем в Microsoft Research, занимаясь вопросами создания компьютерного зрения и языка, а также была постдокторантом в Университете Джонса Хопкинса, занимаясь байесовским моделированием и извлечением информации. Она получила докторскую степень в области компьютерных наук в Абердинском университете и степень магистра в области вычислительной лингвистики в Вашингтонском университете. В период с 2005 по 2012 год она также работала в области машинного обучения, неврологических расстройств и вспомогательных технологий в Орегонском университете здоровья и науки. Она была инициатором ряда семинаров и инициатив на пересечении многообразия, инклюзивности, компьютерных наук и этики. Ее работа была отмечена наградами министра обороны Эша Картера и Американского фонда поддержки слепых, а также была внедрена несколькими технологическими компаниями. Она любит садоводство, собак и кошек.

**Мэттью Уотсон и Чэнь Цянь:** *Рабочие процессы NLP с Keras*

<Youtube id="gZIP-_2XYMM"/>

<p align="center">
<img src="https://i.imgur.com/1vD2az8.png" alt="A visual summary of Matt and Chen's talk" width="80%"/>
</p>

Мэтью Уотсон - инженер по машинному обучению в команде Keras, специализирующийся на высокоуровневых API для моделирования. Он изучал компьютерную графику в бакалавриате и получил степень магистра в Стэнфордском университете. Получив английское образование, он выбрал компьютерную науку, но очень любит работать с разными дисциплинами и делать NLP доступным для широкой аудитории.

Чен Цянь - инженер-программист из команды Keras, специализирующийся на высокоуровневых API для моделирования. Чен получил степень магистра электротехники в Стэнфордском университете и особенно интересуется упрощением имплементации в коде задач ML и крупномасштабным ML проектов.

**Марк Саруфим:** *Как обучить модель с помощью Pytorch*

<Youtube id="KmvPlW2cbIo"/>

<p align="center">
<img src="https://i.imgur.com/TPmlkm8.png" alt="A visual summary of Mark's talk" width="80%"/>
</p>

Марк Саруфим - инженер-партнер компании Pytorch, работающий над производственными инструментами OSS, включая TorchServe и Pytorch Enterprise. В прошлом Марк был прикладным ученым и менеджером по продуктам в Graphcore, [yuri.ai](http://yuri.ai/), Microsoft и NASA's JPL. Его главная страсть - сделать программирование более увлекательным.

**Якоб Ушкорейт:** *Не сломалось, так <del>не чини!</del> Давай сломаем)*

<Youtube id="C6jweXYFHSA"/>

<p align="center">
<img src="https://i.imgur.com/5dWQeNB.png" alt="A visual summary of Jakob's talk" width="80%"/>
</p>

Якоб Ушкорейт - соучредитель компании Inceptive. Inceptive разрабатывает молекулы РНК для вакцин и терапевтических препаратов, используя крупномасштабное глубокое обучение в тесной связке с результативными экспериментами, с целью сделать лекарства на основе РНК более доступными, эффективными и широко применимыми. Ранее Якоб более десяти лет работал в Google, возглавляя группы исследований и разработок в Google Brain, Research and Search, работая над основами глубокого обучения, компьютерным зрением, пониманием языка и машинным переводом.

## День 2: Инструменты, которые следует использовать[[day-2-the-tools-to-use]]

**Льюис Танстолл:** *Простое обучение с 🤗 Transformers Trainer*

<Youtube id="u--UVvH-LIQ"/>

Льюис - инженер по машинному обучению в компании Hugging Face, занимающейся разработкой инструментов с открытым исходным кодом и делающий их доступными для широкого круга пользователей. Он также является соавтором книги O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Вы можете следовать за ним в Twitter (@_lewtun) для получения советов и рекомендаций по NLP!

**Мэтью Кэрриган:** *Новые функции TensorFlow для 🤗 Transformers и 🤗 Datasets*

<Youtube id="gQUlXp1691w"/>

Мэтт отвечает за поддержку TensorFlow в Transformers и в конечном итоге возглавит переворот против действующей фракции PyTorch, который, вероятно, будет координироваться через его аккаунт в Twitter @carrigmat.

**Лисандр Дебю:** *Hugging Face Hub как средство для совместной работы и обмена проектами по машинному обучению*

<Youtube id="RBw1TmdEZp0"/>

<p align="center">
<img src="https://i.imgur.com/TarIPCz.png" alt="A visual summary of Lysandre's talk" width="80%"/>
</p>

Лисандр - инженер по машинному обучению в компании Hugging Face, где он участвует во многих проектах с открытым исходным кодом. Его цель - сделать машинное обучение доступным для всех, разрабатывая мощные инструменты с очень простым API.

**Люсиль Сольнье:** *Получите свой собственный токенизатор с помощью 🤗 Transformers & 🤗 Tokenizers*

<Youtube id="UkNmyTFKriI"/>

Люсиль - инженер по машинному обучению в компании Hugging Face, занимается разработкой и поддержкой использования инструментов с открытым исходным кодом. Она также активно участвует во многих исследовательских проектах в области обработки естественного языка, таких как коллаборативное обучение и BigScience.

**Сильвен Гуггер:** *Ускорьте цикл обучения PyTorch с помощью 🤗 Accelerate*

<Youtube id="t8Krzu-nSeY"/>

Сильвен - инженер-исследователь в Hugging Face, один из основных сопровождающих 🤗 Transformers и разработчик 🤗 Accelerate. Ему нравится делать обучение моделей более доступным.

**Мерве Ноян:** *Демонстрируйте свои демо моделей с помощью 🤗 Spaces*

<Youtube id="vbaKOa4UXoM"/>

Мерве - сторонник разработчиков (developer advocate) в Hugging Face, занимается разработкой инструментов и созданием контента для них, чтобы сделать машинное обучение демократичным для всех.

**Абубакар Абид:** *Быстрое создание приложений машинного обучения*

<Youtube id="c7mle2yYpwQ"/>

<p align="center">
<img src="https://i.imgur.com/qWIFeiF.png" alt="A visual summary of Abubakar's talk" width="80%"/>
</p>

Абубакар Абид - CEO компании [Gradio](www.gradio.app). В 2015 году он получил степень бакалавра наук по электротехнике и информатике в Массачусетском технологическом институте, а в 2021 году - степень доктора наук по прикладному машинному обучению в Стэнфорде. В качестве генерального директора Gradio Абубакар работает над тем, чтобы облегчить демонстрацию, отладку и развертывание моделей машинного обучения.

**Матье Десве:** *AWS ML Vision: Сделать машинное обучение доступным для всех пользователей*

<Youtube id="O2e3pXO4aRE"/>

<p align="center">
<img src="https://i.imgur.com/oLdZTKy.png" alt="A visual summary of Mathieu's talk" width="80%"/>
</p>

Энтузиаст технологий, в свободное время занимаюсь творчеством. Мне нравятся задачи и решение проблем клиентов и пользователей, а также работа с талантливыми людьми, чтобы учиться каждый день. С 2004 года я работаю на разных должностях, начиная с фронтенда, бэкенда, инфраструктуры, заканчивая операциями и управлением. Стараюсь оперативно решать общие технические и управленческие вопросы.

**Филипп Шмид:** *Управляемое обучение с Amazon SageMaker и 🤗 Transformers*

<Youtube id="yG6J2Zfo8iw"/>

Филипп Шмид - инженер по машинному обучению и технический руководитель в Hugging Face, где он возглавляет сотрудничество с командой Amazon SageMaker. Он увлечен демократизацией и производством передовых моделей NLP и повышением простоты использования Deep Learning.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/events/2.mdx" />

### Токенизаторы, проверка![[tokenizers-check]]
https://huggingface.co/learn/course/ru/chapter6/9.md

# Токенизаторы, проверка![[tokenizers-check]]

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

Отличная работа по завершению этой главы!

После этого глубокого погружения в токенизаторы вы должны:

- Уметь обучать новый токенизатор, используя старый в качестве шаблона.
- Понимать, как использовать смещения для сопоставления позиций токенов с их исходным положением в тексте
- Знать различия между BPE, WordPiece и Unigram.
- Уметь комбинировать блоки, предоставляемые библиотекой 🤗 Tokenizers, для создания собственного токенизатора
- Уметь использовать собственный токенизатор в библиотеке 🤗 Transformers


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/9.mdx" />

### Токенизация Byte-Pair Encoding[[byte-pair-encoding-tokenization]]
https://huggingface.co/learn/course/ru/chapter6/5.md

# Токенизация Byte-Pair Encoding[[byte-pair-encoding-tokenization]]

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section5.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section5.ipynb"},
]} />

Byte-Pair Encoding (BPE) изначально была разработана как алгоритм для сжатия текстов, а затем использовалась OpenAI для токенизации при предварительном обучении модели GPT. Она используется во многих моделях трансформеров, включая GPT, GPT-2, RoBERTa, BART и DeBERTa.

<Youtube id="HEikzVL-lZU"/>

<Tip>

💡 В этом разделе подробно рассматривается BPE, вплоть до демонстрации полной реализации. Вы можете пропустить этот раздел, если вам нужен только общий обзор алгоритма токенизации.

</Tip>

## Алгоритм обучения[[training-algorithm]]

Обучение BPE начинается с вычисления уникального набора слов, используемых в корпусе (после завершения этапов нормализации и предварительной токенизации), затем создается словарь, в который заносятся все символы, используемые для записи этих слов. В качестве очень простого примера предположим, что в нашем корпусе используются следующие пять слов:

```
"hug", "pug", "pun", "bun", "hugs"
```

Тогда базовым словарем будет `["b", "g", "h", "n", "p", "s", "u"]`. В реальном мире этот базовый словарь будет содержать, как минимум, все символы ASCII, а возможно, и некоторые символы Unicode. Если в примере, который вы обрабатываете, используется символ, которого нет в обучающем корпусе, этот символ будет преобразован в неизвестный токен. Это одна из причин, по которой многие модели NLP очень плохо анализируют контент с эмоджи, например.

<Tip>

Токенизаторы GPT-2 и RoBERTa (которые довольно похожи) имеют умный способ решения этой проблемы: они рассматривают слова не как символы Unicode, а как байты. Таким образом, базовый словарь имеет небольшой размер (256), но все символы, которые вы можете придумать, все равно будут включены и не будут преобразованы в неизвестный токен. Этот трюк называется *byte-level BPE*.

</Tip>

После получения базового словаря мы добавляем новые токены, пока не достигнем желаемого объема словаря, обучаясь *слияниям*, которые представляют собой правила слияния двух элементов существующего словаря в новый. Таким образом, в начале эти слияния будут создавать токены с двумя символами, а затем, по мере обучения, более длинные подслова.

На любом шаге обучения токенизатора алгоритм BPE будет искать наиболее частую пару существующих токенов (под "парой" здесь понимаются два последовательных токена в слове). Эта наиболее часто встречающаяся пара и будет объединена, после чего все повторяется для следующего шага.

Возвращаясь к нашему предыдущему примеру, предположим, что слова имеют следующую частоту:

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

значение `" hug"` встречалось в корпусе 10 раз, `"pug"` - 5 раз, `"pun"` - 12 раз, `"bun"` - 4 раза, и `"hugs"` - 5 раз. Мы начинаем обучение с разбиения каждого слова на части символов (те, которые формируют наш начальный словарь), чтобы мы могли рассматривать каждое слово как список токенов:

```
("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)
```

Затем мы посмотрим на пары. Пара `("h", "u")` присутствует в словах `"hug"` и `"hugs"`, всего 15 раз в корпусе. Однако это не самая частая пара: эта честь принадлежит `("u", "g")`, которая присутствует в словах `"hug"`, `"pug"` и `"hugs"`, в общей сложности 20 раз в словаре.

Таким образом, первое правило слияния, выученное токенизатором, - `("u", "g") -> "ug"`, что означает, что `"ug"` будет добавлено в словарь, и эта пара должна быть объединена во всех словах корпуса. В конце этого этапа словарь и корпус выглядят следующим образом:

```
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)
```

Теперь у нас есть несколько пар, в результате которых получается токен длиннее двух символов: например, пара `("h", "ug")` (встречается в корпусе 15 раз). Самая частая пара на этом этапе - `("u", "n")`, однако она встречается в корпусе 16 раз, поэтому второе выученное правило слияния - `("u", "n") -> "un"`. Добавив это в словарь и объединив все существующие вхождения, мы получаем:

```
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)
```

Теперь наиболее частой парой является `("h", "ug")`, поэтому мы изучаем правило слияния `("h", "ug") -> "hug"`, что дает нам первый трехбуквенный токен. После слияния корпус выглядит следующим образом:

```
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)
```

И продолжаем в том же духе, пока не достигнем желаемого размера словаря.

<Tip>

✏️ **Теперь ваша очередь!** Как вы думаете, каким будет следующее правило слияния?

</Tip>

## Алгоритм токенизации[[tokenization-algorithm]]

Токенизация следует за процессом обучения в том смысле, что новые входные данные подвергаются токенизации путем применения следующих шагов:

1. Нормализация
2. Предварительная токенизация
3. Разделение слов на отдельные символы
4. Применение правил слияния, изученных по порядку, к этим частям

Возьмем пример, который мы использовали во время обучения, с тремя выученными правилами слияния:

```
("u", "g") -> "ug"
("u", "n") -> "un"
("h", "ug") -> "hug"
```

Слово `"bug"` будет токенизировано как `["b", "ug"]`. Слово `"mug"`, однако, будет токенизировано как `["[UNK]", "ug"]`, поскольку буква `"m"` отсутствует в базовом словаре. Аналогично, слово `"thug" будет токенизировано как `["[UNK]", "hug"]`: буква `"t" отсутствует в базовом словаре, и применение правил слияния приводит сначала к слиянию `"u"` и `"g"`, а затем к слиянию `"h"` и `"ug"`.

<Tip>

✏️ ** Теперь ваша очередь!** Как вы думаете, как будет токенизировано слово `'unhug'`?

</Tip>

## Реализация BPE[[implementing-bpe]]

Теперь давайте посмотрим на реализацию алгоритма BPE. Это не будет оптимизированная версия, которую вы сможете использовать на большом корпусе; мы просто хотим показать вам код, чтобы вы могли лучше понять алгоритм.

Для начала нам нужен корпус текста, поэтому давайте создадим простой корпус с несколькими предложениями:

```python
corpus = [
    "This is the Hugging Face Course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

Далее нам нужно предварительно токенизировать корпус в слова. Поскольку мы воспроизводим токенизатор BPE (например, GPT-2), для предварительной токенизации мы будем использовать токенизатор `gpt2`:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

Затем мы вычисляем частоту каждого слова в корпусе, как и при предварительной токенизации:

```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})
```

Следующий шаг - составление базового словаря, состоящего из всех символов, используемых в корпусе:

```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', 'Ġ']
```

Мы также добавляем специальные токены, используемые моделью, в начало этого словаря. В случае GPT-2 единственным специальным токеном является `"<|endoftext|>"`:

```python
vocab = ["<|endoftext|>"] + alphabet.copy()
```

Теперь нам нужно разделить каждое слово на отдельные символы, чтобы можно было начать обучение:

```python
splits = {word: [c for c in word] for word in word_freqs.keys()}
```

Теперь, когда мы готовы к обучению, давайте напишем функцию, которая вычисляет частоту каждой пары. Нам нужно будет использовать ее на каждом шаге обучения:

```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
```

Давайте посмотрим на часть этого словаря после первых разделений:

```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
```

Теперь, чтобы найти наиболее часто встречающуюся пару, нужно всего лишь сделать быстрый цикл:

```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
```

Итак, первое слияние, которое нужно выучить, это `('Ġ', 't') -> 'Ġt'`, и мы добавляем `'Ġt'` в словарь:

```python
merges = {("Ġ", "t"): "Ġt"}
vocab.append("Ġt")
```

Чтобы продолжить, нам нужно применить это объединение в нашем экземпляре `splits` словаря. Давайте напишем для этого еще одну функцию:

```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
```

И мы можем посмотреть на результат первого слияния:

```py
splits = merge_pair("Ġ", "t", splits)
print(splits["Ġtrained"])
```

```python out
['Ġt', 'r', 'a', 'i', 'n', 'e', 'd']
```

Теперь у нас есть все, что нужно, чтобы проитерироваться до тех пор, пока мы не выучим все слияния, которые нам нужны. Пусть размер словаря будет 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])
```

В результате мы выучили 19 правил слияния (исходный словарь имел размер 31 - 30 символов в алфавите плюс специальный токен):

```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'}
```

А словарь состоит из специального токена, начального алфавита и всех результатов слияния:

```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']
```

<Tip>

💡 Использование `train_new_from_iterator()` на том же корпусе не приведет к созданию точно такого же словаря. Это связано с тем, что при выборе наиболее частотной пары мы выбираем первую попавшуюся, в то время как библиотека 🤗 Tokenizers выбирает первую пару, основываясь на ее внутренних ID.

</Tip>

Чтобы токенизировать новый текст, мы предварительно токенизируем его, разбиваем на части, а затем применяем все изученные правила слияния:

```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, [])
```

Мы можем попробовать это на любом тексте, состоящем из символов алфавита:

```py
tokenize("This is not a token.")
```

```python out
['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.']
```

<Tip warning={true}>

⚠️ Наша реализация будет выбрасывать ошибку при наличии неизвестного символа, поскольку мы ничего не сделали для их обработки. На самом деле в GPT-2 нет неизвестного токена (невозможно получить неизвестный символ при использовании BPE на уровне байтов), но здесь это может произойти, поскольку мы не включили все возможные байты в начальный словарь. Этот аспект BPE выходит за рамки данного раздела, поэтому мы опустили подробности.

</Tip>

Вот и все об алгоритме BPE! Далее мы рассмотрим WordPiece.

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/5.mdx" />

### Введение[[introduction]]
https://huggingface.co/learn/course/ru/chapter6/1.md

# Введение[[introduction]]

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

В [Главе 3](../chapter3/1) мы рассмотрели, как дообучить модель для конкретной задачи. При этом мы используем тот же токенизатор, на котором была предварительно обучена модель, но что делать, когда мы хотим обучить модель с нуля? В таких случаях использование токенизатора, который был предварительно обучен на корпусе из другой области или языка, как правило, является неоптимальным. Например, токенизатор, обученный на корпусе английских текстов, будет плохо работать на корпусе японских текстов, поскольку использование пробелов и знаков препинания в этих двух языках сильно отличается.

В этой главе вы узнаете, как обучить совершенно новый токенизатор на корпусе текстов, чтобы затем использовать его для предварительного обучения языковой модели. Все это будет сделано с помощью библиотеки [🤗 Tokenizers](https://github.com/huggingface/tokenizers), которая предоставляет "быстрые" токенизаторы в библиотеке [🤗 Transformers](https://github.com/huggingface/transformers). Мы подробно рассмотрим возможности, которые предоставляет эта библиотека, и выясним, чем быстрые токенизаторы отличаются от "медленных" версий.

Мы рассмотрим следующие темы:

* Как обучить новый токенизатор, аналогичный тому, который используется в данной контрольной точке, на новом корпусе текстов
* Особенности быстрых токенизаторов
* Различия между тремя основными алгоритмами токенизации по подсловам, используемыми в NLP сегодня
* Как создать токенизатор с нуля с помощью библиотеки 🤗 Tokenizers и обучить его на некоторых данных

Техники, представленные в этой главе, подготовят вас к разделу в [Главе 7](../chapter7/6), где мы рассмотрим создание языковой модели по исходному коду Python. Для начала давайте разберемся, что значит "обучить" токенизатор.

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/1.mdx" />

### Особые возможности быстрых токенизаторов[[fast-tokenizers-special-powers]]
https://huggingface.co/learn/course/ru/chapter6/3.md

# Особые возможности быстрых токенизаторов[[fast-tokenizers-special-powers]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3_tf.ipynb"},
]} />

{/if}

В этом разделе мы подробно рассмотрим возможности токенизаторов в 🤗 Transformers. До сих пор мы использовали их только для токенизации входных данных или декодирования идентификаторов обратно в текст, но токенизаторы -- особенно те, которые поддерживаются библиотекой 🤗 Tokenizers - могут делать гораздо больше. Чтобы проиллюстрировать эти дополнительные возможности, мы рассмотрим, как воспроизвести результаты конвейеров `token-classification` (которые мы назвали `ner`) и `question-answering`, с которыми мы впервые столкнулись в [Главе 1](../chapter1/1).

<Youtube id="g8quOxoqhHQ"/>

В дальнейшем обсуждении мы будем часто проводить различие между "медленными" и "быстрыми" токенизаторами. Медленные токенизаторы - это те, что написаны на Python в библиотеке 🤗 Transformers, а быстрые версии - это те, что предоставляются в 🤗 Tokenizers, которые написаны на Rust. Если вы помните таблицу из [Главы 5](../chapter5/3), в которой приводилось, сколько времени потребовалось быстрому и медленному токенизаторам для токенизации датасета Drug Review Dataset, вы должны иметь представление о том, почему мы называем их быстрыми и медленными:

|               |  Быстрый токенизатор   | Медленный токенизатор
:--------------:|:----------------------:|:----------------------:
`batched=True`  | 10.8s                  | 4min41s
`batched=False` | 59.2s                  | 5min3s

<Tip warning={true}>

⚠️ Когда вы токенизируете одно предложение, вы не всегда увидите разницу в скорости между медленной и быстрой версиями одного и того же токенизатора. Более того, быстрая версия может быть даже медленнее! Только при параллельной токенизации большого количества текстов вы сможете увидеть разницу.

</Tip>

## Batch encoding[[batch-encoding]]

<Youtube id="3umI3tm27Vw"/>

Результат работы токенизатора - это не простой словарь Python; то, что мы получаем, - это специальный объект `BatchEncoding`. Это подкласс словаря (именно поэтому мы раньше могли без проблем индексировать результат), но с дополнительными методами, которые в основном используются быстрыми токенизаторами.

Помимо возможностей распараллеливания, ключевой функцией быстрых токенизаторов является то, что они всегда отслеживают исходный диапазон текстов, из которых взяты конечные токены, - эту функцию мы называем *сопоставление смещений (offset mapping)*. Это, в свою очередь, открывает такие возможности, как сопоставление каждого слова с порожденными им токенами или сопоставление каждого символа исходного текста с токеном, в котором он находится, и наоборот.

Давайте посмотрим на пример:

```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."
encoding = tokenizer(example)
print(type(encoding))
```

Как уже говорилось, на выходе токенизатора мы получаем объект `BatchEncoding`:

```python out
<class 'transformers.tokenization_utils_base.BatchEncoding'>
```

Поскольку класс `AutoTokenizer` по умолчанию выбирает быстрый токенизатор, мы можем использовать дополнительные методы, которые предоставляет объект `BatchEncoding`. У нас есть два способа проверить, является ли наш токенизатор быстрым или медленным. Мы можем проверить атрибут `is_fast` у `tokenizer`:

```python
tokenizer.is_fast
```

```python out
True
```

или проверьте тот же атрибут нашего `encoding`:

```python
encoding.is_fast
```

```python out
True
```

Давайте посмотрим, что позволяет нам сделать быстрый токенизатор. Во-первых, мы можем получить доступ к токенам без необходимости преобразовывать идентификаторы обратно в токены:

```py
encoding.tokens()
```

```python out
['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in',
 'Brooklyn', '.', '[SEP]']
```

В данном случае токен с индексом 5 - это `##yl`, который является частью слова "Sylvain" в исходном предложении. Мы также можем использовать метод `word_ids()`, чтобы получить индекс слова, из которого происходит каждый токен:

```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]
```


Мы можем видеть, что специальные токены токенизатора `[CLS]` и `[SEP]` сопоставляются с `None`, а затем каждый токен сопоставляется со словом, от которого он происходит. Это особенно полезно для определения того, находится ли токен в начале слова или два токена в одном и том же слове. Для этого мы могли бы использовать префикс `##`, но он работает только для токенизаторов типа BERT; этот метод работает для любого типа токенизаторов, лишь бы он был быстрым. В следующей главе мы увидим, как можно использовать эту возможность для применения меток, которые мы имеем для каждого слова, к токенам в таких задачах, как распознавание именованных сущностей (NER) и тегирование частей речи (part-of-speech - POS). Мы также можем использовать ее для маскирования всех токенов, происходящих от одного и того же слова, при моделировании языка по маске (masked language modeling) (эта техника называется _маскированием всего слова (whole word masking)_).

<Tip>

Понятие "слово" очень сложное. Например, "I'll" (сокращение от "I will") считается одним или двумя словами? На самом деле это зависит от токенизатора и применяемой им операции предварительной токенизации. Некоторые токенизаторы просто разделяют пробелы, поэтому они будут считать это одним словом. Другие используют пунктуацию поверх пробелов, поэтому будут считать это двумя словами.

✏️ **Попробуйте!** Создайте токенизатор из контрольных точек `bert-base-cased` и `roberta-base` и токенизируйте с их помощью "81s". Что вы заметили? Каковы идентификаторы слов?

</Tip>

Аналогично, существует метод `sentence_ids()`, который мы можем использовать для сопоставления токена с предложением, из которого оно взято (хотя в этом случае ту же информацию может дать и `token_type_ids`, возвращаемый токенизатором).

Наконец, с помощью методов `word_to_chars()` или `token_to_chars()` и `char_to_word()` или `char_to_token()` мы можем сопоставить любое слово или токен с символами в оригинальном тексте и наоборот. Например, метод `word_ids()` сообщил нам, что `##yl` является частью слова с индексом 3, но какое это слово в предложении? Мы можем выяснить это следующим образом:

```py
start, end = encoding.word_to_chars(3)
example[start:end]
```

```python out
Sylvain
```

Как мы уже говорили, все это происходит благодаря тому, что быстрый токенизатор отслеживает, из какого участка текста происходит каждый токен, в списке *смещений (offsets)*. Чтобы проиллюстрировать их использование, далее мы покажем, как воспроизвести результаты конвейера `token-classification` вручную.

<Tip>

✏️ **Попробуйте!** Создайте свой собственный пример текста и посмотрите, сможете ли вы понять, какие токены связаны с идентификаторами слов, а также как извлечь диапазоны символов для одного слова. Чтобы получить бонусные очки, попробуйте использовать два предложения в качестве входных данных и посмотрите, будут ли идентификаторы предложений иметь для вас смысл.

</Tip>

## Внутри конвейера `token-classification`[[inside-the-token-classification-pipeline]]

В [Главе 1](../chapter1/1) мы впервые попробовали применить NER - когда задача состоит в том, чтобы определить, какие части текста соответствуют сущностям, таким как люди, места или организации - с помощью функции 🤗 Transformers `pipeline()`. Затем, в [Главе 2](../chapter2/1), мы увидели, как конвейер объединяет три этапа, необходимые для получения прогнозов из необработанного текста: токенизацию, прохождение входных данных через модель и постобработку. Первые два шага в конвейере `token-classification` такие же, как и в любом другом конвейере, но постобработка немного сложнее - давайте посмотрим, как это сделать!

{#if fw === 'pt'}

<Youtube id="0E7ltQB7fM8"/>

{:else}

<Youtube id="PrX4CjrVnNc"/>

{/if}

### Получение базовых результатов с помощью конвейера[[getting-the-base-results-with-the-pipeline]]

Для начала возьмем конвейер token classification, чтобы получить результаты для сравнения вручную. По умолчанию используется модель [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); она выполняет NER на предложениях:

```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}]
```

Модель правильно идентифицировала каждый токен, сгенерировав "Sylvain", как человека, каждый токен, сгенерированный "Hugging Face", как организацию, а токен "Brooklyn" - как местоположение. Мы также можем попросить конвейер сгруппировать токены, которые соответствуют одной и той же сущности:

```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}]
```

Выбранная `aggregation_strategy` изменит оценки, вычисляемые для каждой сгруппированной сущности. При использовании значения `"simple"` оценка является средним значением оценок каждого токена данной сущности: например, оценка "Sylvain" является средним значением оценок, которые мы видели в предыдущем примере для токенов `S`, `##yl`, `##va` и `##in`. Другие доступные стратегии:

- `"first"`, где оценка каждой сущности - это оценка первого токена этой сущности (так, для "Sylvain" это будет 0,993828, оценки токена `S`)
- `"max"`, где оценка каждой сущности - это максимальная оценка токенов в этой сущности (так, для ""Hugging Face"" это будет 0.98879766, оценки "Face").
- `"average"`, где оценка каждой сущности - это средняя оценка слов, составляющих эту сущность (таким образом, для слова ""Sylvain"" не будет никаких отличий от стратегии `"simple"`, но "Hugging Face" будет иметь оценку 0.9819, среднюю оценку для "Hugging", 0.975, и "Face", 0.98879)

Теперь давайте посмотрим, как получить эти результаты без использования функции `pipeline()`!

### От входных данных к прогнозам[[from-inputs-to-predictions]]

{#if fw === 'pt'}

Сначала нам нужно токенизировать наш ввод и пропустить его через модель. Это делается точно так же, как в [Главе 2](../chapter2/1); мы инстанцируем токенизатор и модель с помощью классов `AutoXxx`, а затем используем их в нашем примере:

```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)
```

Поскольку мы используем `AutoModelForTokenClassification`, мы получаем один набор логитов для каждого токена во входной последовательности:

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
torch.Size([1, 19])
torch.Size([1, 19, 9])
```

{:else}

Сначала нам нужно токенизировать наши входные данные и пропустить их через модель. Это делается точно так же, как в [Главе 2](../chapter2/1); мы инстанцируем токенизатор и модель с помощью классов `TFAutoXxx`, а затем используем их в нашем примере:

```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)
```

Поскольку мы используем `TFAutoModelForTokenClassification`, мы получаем один набор логитов для каждого токена во входной последовательности:

```py
print(inputs["input_ids"].shape)
print(outputs.logits.shape)
```

```python out
(1, 19)
(1, 19, 9)
```

{/if}

У нас есть батч с 1 последовательностью из 19 токенов, и модель имеет 9 различных меток, поэтому выход модели имеет форму 1 x 19 x 9. Как и для конвейера классификации текста, мы используем функцию softmax для преобразования этих логитов в вероятности и берем argmax для получения прогнозов (обратите внимание, что мы можем взять argmax для логитов, потому что softmax не меняет порядок):

{#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]
```

Атрибут `model.config.id2label` содержит отображение индексов в метки, которые мы можем использовать для осмысления прогнозов:

```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'}
```

Как мы видели ранее, существует 9 меток: `O` - это метка для токенов, которые не входят ни в одну именованную сущность (она означает "вне"), а затем у нас есть две метки для каждого типа сущности (miscellaneous, person, organization и location). Метка `B-XXX` указывает на то, что токен находится в начале сущности `XXX`, а метка `I-XXX` указывает на то, что токен находится внутри сущности `XXX`. Таким образом, в данном примере мы ожидаем, что наша модель классифицирует токен `S` как `B-PER` (начало сущности person), а токены `##yl`, `##va` и `##in` как `I-PER` (внутри сущности person). 

Вы можете подумать, что модель в данном случае ошиблась, поскольку присвоила всем четырем токенам метку `I-PER`, но это не совсем так. На самом деле существует два формата для меток `B-` и `I-`: *IOB1* и *IOB2*. Формат IOB2 (розовый цвет ниже) - это тот, который мы представили, в то время как в формате IOB1 (синий цвет) метки, начинающиеся с `B-`, используются только для разделения двух соседних сущностей одного типа. Используемая нами модель была дообучена на наборе данных, использующем этот формат, поэтому она присваивает токену `S` метку `I-PER`.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions.svg" alt="IOB1 vs IOB2 format"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/IOB_versions-dark.svg" alt="IOB1 vs IOB2 format"/>
</div>

С помощью этой карты мы можем воспроизвести (почти полностью) результаты первого конвейера - мы можем просто получить оценку и метку каждого токена, который не был классифицирован как `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'}]
```

Это очень похоже на то, что у нас было раньше, за одним исключением: конвейер также предоставил нам информацию о `start` и `end` каждой сущности в исходном предложении. Вот тут-то и пригодится наше сопоставление смещений. Чтобы получить смещения, нам нужно просто установить `return_offsets_mapping=True`, когда мы применяем токенизатор к нашим входным данным:

```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)]
```

Каждый кортеж - это участок текста, соответствующий каждому токену, где `(0, 0)` зарезервировано для специальных токенов. Мы уже видели, что токен с индексом 5 - это `##yl`, который имеет `(12, 14)` в качестве смещения. Если мы возьмем соответствующий фрагмент в нашем примере:


```py
example[12:14]
```

мы получим нужный участок текста без использования `##`:

```python out
yl
```

Используя это, мы можем дополнить предыдущие результаты:

```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}]
```

Это то же самое, что мы получили от первого конвейера!

### Группировка сущностей[[grouping-entities]]

Использование смещений для определения начального и конечного ключей для каждой сущности удобно, но эта информация не является строго необходимой. Однако когда мы захотим сгруппировать сущности вместе, смещения избавят нас от большого количества беспорядочного кода. Например, если бы мы хотели сгруппировать токены `Hu`, `##gging` и `Face`, мы могли бы создать специальные правила, согласно которым первые два должны быть присоединены, удалив `##`, а `Face` должен быть добавлен через пробел, поскольку он не начинается с `##` - но это будет работать только для данного конкретного типа токенизатора. Для токенизатора SentencePiece или Byte-Pair-Encoding нам придется написать другой набор правил (о них мы поговорим позже в этой главе).

С помощью смещений весь этот пользовательский код отпадает: мы просто можем взять в исходном тексте промежуток, который начинается с первого токена и заканчивается последним. Так, в случае с токенами `Hu`, `##gging` и `Face` мы должны начать с символа 33 (начало `Hu`) и закончить символом 45 (конец `Face`):

```py
example[33:45]
```

```python out
Hugging Face
```

Чтобы написать код для постобработки прогнозов при группировке сущностей, мы будем группировать сущности, которые идут подряд и помечены `I-XXX`, за исключением первой, которая может быть помечена как `B-XXX` или `I-XXX` (таким образом, мы прекращаем группировать сущность, когда получаем `O`, новый тип сущности, или `B-XXX`, который говорит нам, что начинается сущность того же типа):

```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":
        # Удалим B- или I-
        label = label[2:]
        start, _ = offsets[idx]

        # Соберём все токены, помеченные I-меткой
        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

        # Оценка является средним значением всех оценок токенов в этой сгруппированной сущности
        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)
```

И мы получаем те же результаты, что и со вторым конвейером!

```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}]
```

Еще один пример задачи, в которой эти смещения чрезвычайно полезны, - question answering. Погружение в этот конвейер, которое мы сделаем в следующем разделе, также позволит нам взглянуть на последнюю особенность токенизаторов в библиотеке 🤗 Transformers: работа с переполненными токенами (overflowing tokens), когда мы усекаем входные данные до заданной длины.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/3.mdx" />

### Токенизация WordPiece[[wordpiece-tokenization]]
https://huggingface.co/learn/course/ru/chapter6/6.md

# Токенизация WordPiece[[wordpiece-tokenization]]

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section6.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section6.ipynb"},
]} />

WordPiece - это алгоритм токенизации, разработанный Google для предварительного обучения BERT. Впоследствии он был повторно использован во многих моделях трансформеров, основанных на BERT, таких как DistilBERT, MobileBERT, Funnel Transformers и MPNET. Он очень похож на BPE в плане обучения, но фактическая токенизация выполняется по-другому.

<Youtube id="qpv6ms_t_1A"/>

<Tip>

💡 В этом разделе подробно рассматривается WordPiece, вплоть до демонстрации полной реализации. Вы можете пропустить его, если вам нужен только общий обзор алгоритма токенизации.

</Tip>

## Алгоритм обучения[[training-algorithm]]

<Tip warning={true}>

⚠️ Google никогда не предоставлял открытый доступ к своей реализации алгоритма обучения WordPiece, поэтому все вышесказанное - это наши предположения, основанные на опубликованных материалах. Возможно, они точны не на 100 %.

</Tip>

Как и BPE, WordPiece начинает работу с небольшого словаря, включающего специальные токены, используемые моделью, и начальный алфавит. Поскольку модель идентифицирует подслова путем добавления префикса (как `##` для BERT), каждое слово первоначально разбивается на части путем добавления этого префикса ко всем символам внутри слова. Так, например, `"word"` разбивается на части следующим образом:

```
w ##o ##r ##d
```

Таким образом, начальный алфавит содержит все символы, присутствующие в начале слова, и символы, присутствующие внутри слова, которым предшествует префикс WordPiece.

Затем, как и в случае с BPE, WordPiece изучает правила слияния. Основное отличие заключается в способе выбора пары для слияния. Вместо того чтобы выбирать наиболее частую пару, WordPiece рассчитывает оценку для каждой пары по следующей формуле:

$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$

Деля частоту пары на произведение частот каждой из ее частей, алгоритм отдает предпочтение слиянию пар, отдельные части которых встречаются в словаре реже. Например, он не обязательно объединит `("un", "##able")`, даже если эта пара встречается в словаре очень часто, потому что две пары `"un"` и `"##able"`, скорее всего, встречаются в большом количестве других слов и имеют высокую частоту. Напротив, такая пара, как `("hu", "##gging")`, вероятно, будет объединена быстрее (при условии, что слово  "hugging" часто встречается в словаре), поскольку `"hu"` и `"##gging"` по отдельности, скорее всего, встречаются реже.

Давайте рассмотрим тот же словарь, который мы использовали в учебном примере BPE:

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

Рабиение здесь будет следующим:

```
("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)
```

поэтому исходный словарь будет иметь вид `["b", "h", "p", "##g", "##n", "##s", "##u"]` (если мы пока забудем о специальных токенах). Самая частая пара - `("##u", "##g")` (встречается 20 раз), но индивидуальная частота `"##u"` очень высока, поэтому ее оценка не самая высокая (она составляет 1/36). Все пары с `"##u"` фактически имеют такую же оценку (1/36), поэтому лучшую оценку получает пара `("##g", "##s")` - единственная, в которой нет `"##u"` - с оценкой 1/20, и первым выученным слиянием будет `("##g", "##s") -> ("##gs")`.

Обратите внимание, что при слиянии мы удаляем `##` между двумя токенами, поэтому мы добавляем `"##gs"` в словарь и применяем слияние в словах корпуса:

```
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)
```

В этот момент `"##u"` находится во всех возможных парах, поэтому все они получают одинаковый балл. Допустим, в этом случае первая пара объединяется, так что `("h", "##u") -> "hu"`. Это приводит нас к:

```
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)
```

Затем следующую лучшую оценку разделяют `("hu", "##g")` и `("hu", "##gs")` (1/15, по сравнению с 1/21 для всех остальных пар), поэтому первая пара с наибольшей оценкой объединяется:

```
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)
```

и мы продолжаем так до тех пор, пока не достигнем необходимого размера словаря.

<Tip>

✏️ **Теперь ваша очередь!** Каким будет следующее правило слияния?

</Tip>

## Алгоритм токенизации[[tokenization-algorithm]]

Токенизация в WordPiece и BPE отличается тем, что WordPiece сохраняет только конечный словарь, а не выученные правила слияния. Начиная со слова, которое нужно токенизировать, WordPiece находит самое длинное подслово, которое есть в словаре, а затем разбивает его на части. Например, если мы используем словарь, изученный в примере выше, для слова `" hugs"` самым длинным подсловом, начиная с начала, которое находится в словаре, является `"hug"`, поэтому мы делим его на части и получаем `["hug", "##s"]`. Затем мы продолжаем с `"##s"`, которое находится в словаре, поэтому токенизация `"hugs"` будет `["hug", "##s"]`.

В BPE мы бы применили слияния, выученные по порядку, и токенизировали это как `["hu", "##gs"]`, поэтому кодировка отличается.

В качестве другого примера посмотрим, как будет токенизировано слово `"bugs"`. `"b"` - самое длинное подслово, начинающееся с начала слова, которое есть в словаре, поэтому мы делим его на части и получаем `["b", "##ugs"]`. Затем `"##u"` - самое длинное подслово, начинающееся в начале `"##ugs"`, которое есть в словаре, поэтому мы делим его на части и получаем `["b", "##u", "##gs"]`. Наконец, `"##gs"` находится в словаре, так что этот последний список является токеном `"bugs"`.

Когда токенизация доходит до стадии, когда невозможно найти подслово в словаре, все слово токенизируется как неизвестное - так, например, `"mug"` будет токенизировано как `["[UNK]"]`, как и `"bum"` (даже если мы можем начать с `"b"` и `"##u"`, `"##m"` не входит в словарь, и результирующий токен будет просто `["[UNK]"]`, а не `["b", "##u", "[UNK]"]`). Это еще одно отличие от BPE, который классифицирует как неизвестные только отдельные символы, отсутствующие в словаре.

<Tip>

✏️ **Теперь ваша очередь!** Как будет токенизировано слово `"pugs"`?

</Tip>

## Реализация WordPiece[[implementing-wordpiece]]

Теперь давайте посмотрим на реализацию алгоритма WordPiece. Как и в случае с BPE, это всего лишь учебный пример, и вы не сможете использовать его на большом корпусе.

Мы будем использовать тот же корпус, что и в примере с BPE:

```python
corpus = [
    "This is the Hugging Face Course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

Во-первых, нам нужно предварительно токенизировать корпус в слова. Поскольку мы воспроизводим токенизатор WordPiece (например, BERT), для предварительной токенизации мы будем использовать токенизатор `bert-base-cased`:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

Затем мы вычисляем частоту каждого слова в корпусе, как и при предварительной токенизации:

```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})
```

Как мы уже видели, алфавит - это уникальное множество, состоящее из всех первых букв слов и всех остальных букв, которые встречаются в словах с префиксом `##`:

```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']
```

Мы также добавляем специальные токены, используемые моделью, в начало этого словаря. В случае BERT это список `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`:

```python
vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy()
```

Далее нам нужно разделить каждое слово на части, при этом все буквы, которые не являются первыми, должны иметь префикс `##`:

```python
splits = {
    word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]
    for word in word_freqs.keys()
}
```

Теперь, когда мы готовы к обучению, давайте напишем функцию, которая вычисляет оценку каждой пары. Нам нужно будет использовать ее на каждом шаге обучения:

```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
```

Давайте посмотрим на часть этого словаря после первых разделений:

```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
```

Теперь для того, чтобы найти пару с наилучшим результатом, нужно всего лишь сделать быстрый цикл:

```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
```

Итак, первое слияние, которое нужно выучить, это `('a', '##b') -> 'ab'`, и мы добавляем `'ab'` в словарь:

```python
vocab.append("ab")
```

Чтобы продолжить, нам нужно применить это слияние в нашем словаре `splits`. Давайте напишем для этого еще одну функцию:

```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
```

И мы можем посмотреть на результат первого слияния:

```py
splits = merge_pair("a", "##b", splits)
splits["about"]
```

```python out
['ab', '##o', '##u', '##t']
```

Теперь у нас есть все, что нужно, чтобы зацикливать процесс до тех пор, пока мы не выучим все слияния, которые нам нужны. Давайте нацелимся на размер словаря равный 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)
```

Затем мы можем просмотреть созданный словарь:

```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', 'ab', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully',
 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat',
 '##ut']
```

Как мы видим, по сравнению с BPE этот токенизатор быстрее выучивает части слов как токены.

<Tip>

💡 Использование `train_new_from_iterator()` на одном и том же корпусе не приведет к точно такому же словарю. Это происходит потому, что библиотека 🤗 Tokenizers не реализует WordPiece для обучения (поскольку мы не полностью уверены в его внутреннем устройстве), а использует вместо него BPE.

</Tip>

Чтобы токенизировать новый текст, мы предварительно токенизируем его, разбиваем на части, а затем применяем алгоритм токенизации к каждому слову. То есть начиная с первого слова мы ищем самое большое подслово и разбиваем его на части, затем мы повторяем процесс для второй части, и так далее для оставшейся части этого слова и следующих слов в тексте:

```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
```

Давайте проверим алгоритм на одном слове, которое есть в словаре, и на другом, которого нет:

```python
print(encode_word("Hugging"))
print(encode_word("HOgging"))
```

```python out
['Hugg', '##i', '##n', '##g']
['[UNK]']
```

Теперь давайте напишем функцию, которая токенизирует текст:

```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, [])
```

Мы можем попробовать его на любом тексте:

```python
tokenize("This is the Hugging Face course!")
```

```python out
['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s',
 '##e', '[UNK]']
```

Вот и все об алгоритме WordPiece! Теперь давайте посмотрим на Unigram.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/6.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter6/10.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={6}
    classNames="absolute z-10 right-0 top-0"
/>

Давайте проверим, чему вы научились в этой главе!

### 1. Когда следует обучать новый токенизатор?

<Question
	choices={[
		{
			text: "Если ваш набор данных похож на тот, который используется существующей предварительно обученной моделью, и вы хотите провести предварительное обучение новой модели.",
			explain: "В этом случае, чтобы сэкономить время и вычислительные ресурсы, лучше использовать тот же токенизатор, что и в предварительно обученной модели, и дообучить эту модель."
		},
		{
			text: "Когда ваш набор данных похож на тот, который используется существующей предварительно обученной моделью, и вы хотите дообучить новую модель, используя эту предварительно обученную модель.",
			explain: "Чтобы дообучить модель на основе предварительно обученной модели, всегда следует использовать один и тот же токенизатор."
		},
		{
			text: "Когда ваш набор данных отличается от того, который используется существующей предварительно обученной моделью, и вы хотите провести предварительное обучение новой модели.",
			explain: "Правильно! В этом случае нет никакого преимущества в использовании одного и того же токенизатора.",
            correct: true
		},
        {
			text: "Когда ваш набор данных отличается от того, который используется существующей предварительно обученной моделью, но вы хотите дообучить новую модель с помощью имеющейся предварительно обученной модели.",
			explain: "Чтобы дообучить модель на основе предварительно обученной модели, всегда следует использовать один и тот же токенизатор."
		}
	]}
/>

### 2. В чем преимущество использования генератора списков текстов по сравнению со списком списков текстов при использовании `train_new_from_iterator()`?

<Question
	choices={[
		{
			text: "Это единственный тип, который принимает метод <code>train_new_from_iterator()</code>.",
			explain: "Список списков текстов - это особый вид генератора списков текстов, поэтому метод примет и его. Попробуйте еще раз!"
		},
		{
			text: "Это позволит избежать загрузки в память сразу всего набора данных.",
			explain: "Точно! Каждый батч текстов будет освобождаться из памяти при итерации, и выигрыш будет особенно заметен, если вы используете библиотеку 🤗 Datasets для хранения текстов.",
			correct: true
		},
		{
			text: "Это позволит библиотеке 🤗 Tokenizers использовать многопроцессорность (multiprocessing).",
			explain: "Нет, она будет использовать многопроцессорность в любом случае."
		},
        {
			text: "Обученный вами токенизатор будет генерировать более качественные тексты.",
			explain: "Токенизатор не генерирует текст - вы не путаете его с языковой моделью?"
		}
	]}
/>

### 3. Каковы преимущества использования "быстрого" токенизатора?

<Question
	choices={[
		{
			text: "Он может обрабатывать входные данные быстрее, чем медленный токенизатор, когда вы обьединяете в батч много входных данных.",
			explain: "Правильно! Благодаря параллелизму, реализованному в Rust, он будет быстрее работать с батчами входных данных. Какие еще преимущества вы можете назвать?",
			correct: true
		},
		{
			text: "Быстрые токенизаторы всегда выполняют токенизацию быстрее, чем их медленные аналоги.",
			explain: "Быстрый токенизатор может работать медленнее, если вы передаете ему только один или очень мало текстов, поскольку он не может использовать параллелизм."
		},
		{
			text: "Он может применять дополнение (padding) и усечение (truncation).",
			explain: "Верно, но медленные токенизаторы тоже могут это делать."
		},
        	{
			text: "Он обладает некоторыми дополнительными функциями, позволяющими сопоставлять токены с фрагментами текста, которые их сформировали.",
			explain: "Действительно, это так называемые смещенные отображения (offset mappings). Однако это не единственное преимущество.",
			correct: true
		}
	]}
/>

### 4. Как конвейер `token-classification` обрабатывает сущности, которые охватывают несколько токенов?

<Question
	choices={[
		{
			text: "Сущности с одинаковыми метками объединяются в одну сущность.",
			explain: "Это слишком простое объяснение. Попробуйте еще раз!"
		},
		{
			text: "Есть метка начала сущности и метка продолжения сущности.",
			explain: "Правильно!",
			correct: true
		},
		{
			text: "Если в заданном слове первый токен имеет метку сущности, то все слово считается помеченным этой сущностью.",
			explain: "Это одна из стратегий работы с сущностями. Какие еще ответы здесь применимы?",
			correct: true
		},
        	{
			text: "Когда токен имеет метку данной сущности, любой другой следующий токен с такой же меткой считается частью той же сущности, если только он не помечен как начало новой сущности.",
			explain: "Это самый распространенный способ группировки сущностей - однако это не единственный правильный ответ.",
			correct: true
		}
	]}
/>

### 5. Как конвейер `question-answering` обрабатывает длинные контексты?

<Question
	choices={[
		{
			text: "На самом деле это не так, поскольку он обрезает длинный контекст по максимальной длине, принятой моделью.",
			explain: "Есть один прием, который можно использовать для работы с длинными контекстами. Вы помните, в чем он заключается?"
		},
		{
			text: "Он разбивает контекст на несколько частей и усредняет полученные результаты.",
			explain: "Нет, усреднять результаты не имеет смысла, поскольку некоторые части контекста не будут содержать ответ."
		},
		{
			text: "Он разбивает контекст на несколько частей (с перекрытием) и находит максимальную оценку для ответа в каждой части.",
			explain: "Это правильный ответ!",
			correct: true
		},
        	{
			text: "Он разбивает контекст на несколько частей (без перекрытия, для эффективности) и находит максимальную оценку для ответа в каждой части.",
			explain: "Нет, он включает в себя некоторое перекрытие частей, чтобы избежать ситуации, когда ответ будет разбит на две части."
		}
	]}
/>

### 6. Что такое нормализация?

<Question
	choices={[
		{
			text: "Это любая очистка текста, которую выполняет токенизатор на начальных этапах работы с текстами.",
			explain: "Верно - например, это может быть удаление подчеркиваний или пробелов, или перевод в нижний регистр входных данных.",
			correct: true
		},
		{
			text: "Это техника дополнения данных, которая позволяет сделать текст более нормальным за счет удаления редких слов.",
			explain: "Это неправильно! Попробуйте еще раз."
		},
		{
			text: "Это последний этап постобработки, на котором токенизатор добавляет специальные токены.",
			explain: "Этот этап называется просто постобработкой."
		},
        	{
			text: "Это когда эмбеддинги создаются со среднеквадратичным отклонением 0 и стандартным отклонением 1, путем вычитания среднеквадратичного и деления на стандартное отклонение.",
			explain: "Этот процесс обычно называется нормализацией, когда применяется к значениям пикселей в компьютерном зрении, но это не то, что означает нормализация в NLP."
		}
	]}
/>

### 7. Что такое предварительная токенизация для токенизатора по подсловам?

<Question
	choices={[
		{
			text: "Это шаг перед токенизацией, на котором применяется аугментация данных (например, случайное маскирование).",
			explain: "Нет, этот шаг является частью предварительной обработки."
		},
		{
			text: "Это этап перед токенизацией, на котором к тексту применяются необходимые операции по очистке.",
			explain: "Нет, это этап нормализации."
		},
		{
			text: "Это шаг, предшествующий применению модели токенизатора для разбиения входных данных на слова.",
			explain: "Это правильный ответ!",
			correct: true
		},
        	{
			text: "Это шаг, предшествующий применению модели токенизатора для разделения входных данных на токены.",
			explain: "Нет, разбиение на части - это задача модели токенизатора."
		}
	]}
/>

### 8. Выберите предложения, которые относятся к модели токенизации BPE.

<Question
	choices={[
		{
			text: "BPE - это алгоритм токенизации подслов, который начинает работу с небольшим словарем и изучает правила слияния.",
			explain: "Это действительно так!",
			correct: true
		},
		{
			text: "BPE - это алгоритм токенизации подслов, который начинает с большого словаря и постепенно удаляет из него токены.",
			explain: "Нет, такой подход используется другим алгоритмом токенизации."
		},
		{
			text: "Токенизаторы BPE изучают правила слияния, объединяя пары токенов, которые встречаются чаще всего.",
			explain: "Правильно!",
			correct: true
		},
		{
			text: "Токенизатор BPE изучает правило слияния, объединяя пары токенов, которые максимизируют оценку, которая отдает предпочтение частым парам с менее частыми отдельными частями.",
			explain: "Нет, это стратегия, применяемая другим алгоритмом токенизации."
		},
		{
			text: "BPE токенизирует слова на подслова, разбивая их на символы, после чего применяет правила слияния.",
			explain: "Правильно!",
			correct: true
		},
		{
			text: "BPE токенизирует слова в подслова, находя самое длинное подслово, которое есть в словаре, начиная с начала, а затем повторяя процесс для остальной части текста.",
			explain: "Нет, это способ работы другого алгоритма токенизации."
		}
	]}
/>

### 9. Выберите предложения, которые относятся к модели токенизации WordPiece.

<Question
	choices={[
		{
			text: "WordPiece - это алгоритм токенизации по подсловам, который начинает работу с небольшим словарным запасом и учит правила слияния.",
			explain: "Это действительно так!",
			correct: true
		},
		{
			text: "WordPiece - это алгоритм токенизации по подсловам, который начинает с большого словаря и постепенно удаляет из него токены.",
			explain: "Нет, такой подход используется другим алгоритмом токенизации."
		},
		{
			text: "Токенизаторы WordPiece учат правила слияния, объединяя пары токенов, которые встречаются чаще всего.",
			explain: "Нет, это стратегия, применяемая другим алгоритмом токенизации."
		},
		{
			text: "Токенизатор WordPiece изучает правило слияния, объединяя пары токенов, которые максимизируют оценку, отдающую предпочтение частым парам с менее частыми отдельными частями.",
			explain: "Правильно!",
			correct: true
		},
		{
			text: "WordPiece выполняет токенизацию слов на подслова, находя наиболее вероятную сегментацию на токены в соответствии с моделью.",
			explain: "Нет, так работает другой алгоритм токенизации."
		},
		{
			text: "WordPiece токенизирует слова на подслова, находя самое длинное подслово, которое есть в словаре, начиная с начала, а затем повторяя процесс для остальной части текста.",
			explain: "Да, именно так работает WordPiece в процессе кодирования.",
			correct: true
		}
	]}
/>

### 10. Выберите предложения, которые относятся к модели токенизации Unigram.

<Question
	choices={[
		{
			text: "Unigram - это алгоритм токенизации на подслова, который начинает с небольшого словаря и учится правилам слияния.",
			explain: "Нет, это подход, используемый другим алгоритмом токенизации."
		},
		{
			text: "Unigram - это алгоритм токенизации подслова, который начинает с большого словарного запаса и постепенно удаляет из него токены.",
			explain: "Правильно!",
			correct: true
		},
		{
			text: "Unigram адаптирует свой словарь, минимизируя потери, вычисляемые по всему корпусу текста.",
			explain: "Верно!",
			correct: true
		},
		{
			text: "Unigram адаптирует свой словарь, сохраняя наиболее часто встречающиеся подслова.",
			explain: "Нет, это неверно."
		},
		{
			text: "Unigram осуществляет токенизацию слов на подслова, находя наиболее вероятную сегментацию на токены в соответствии с моделью.",
			explain: "Правильно!",
			correct: true
		},
		{
			text: "Unigram токенизирует слова на подслова, разбивая их на символы, а затем применяя правила слияния.",
			explain: "Нет, так работает другой алгоритм токенизации."
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/10.mdx" />

### Создание токенизатора, блок за блоком[[building-a-tokenizer-block-by-block]]
https://huggingface.co/learn/course/ru/chapter6/8.md

# Создание токенизатора, блок за блоком[[building-a-tokenizer-block-by-block]]

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section8.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section8.ipynb"},
]} />

Как мы уже видели в предыдущих разделах, токенизация состоит из нескольких этапов:

- Нормализация (любая необходимая очистка текста, например, удаление пробелов или подчеркиваний, нормализация Unicode и т. д.)
- Предварительная токенизация (разделение входного текста на слова).
- Прогон входных данных через модель (использование предварительно токенизированных слов для создания последовательности токенов)
- Постобработка (добавление специальных токенов токенизатора, генерация маски внимания и идентификаторов типов токенов)

В качестве напоминания вот еще один взгляд на общий процесс:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

Библиотека 🤗 Tokenizers была создана для того, чтобы предоставить несколько вариантов каждого из этих шагов, которые вы можете смешивать и сочетать между собой. В этом разделе мы рассмотрим, как можно создать токенизатор с нуля, а не обучать новый токенизатор на основе старого, как мы делали в [разделе 2](../chapter6/2). После этого вы сможете создать любой токенизатор, который только сможете придумать!

<Youtube id="MR8tZm5ViWU"/>

Точнее, библиотека построена вокруг центрального класса `Tokenizer`, а строительные блоки сгруппированы в подмодули:

- `normalizers` содержит все возможные типы нормализаторов текста `Normalizer`, которые вы можете использовать (полный список [здесь](https://huggingface.co/docs/tokenizers/api/normalizers)).
- `pre_tokenizers` содержит все возможные типы предварительных токенизаторов `PreTokenizer`, которые вы можете использовать (полный список [здесь](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)).
- `models` содержит различные типы моделей `Model`, которые вы можете использовать, такие как `BPE`, `WordPiece` и `Unigram` (полный список [здесь](https://huggingface.co/docs/tokenizers/api/models)).
- `trainers` содержит все различные типы `Trainer`, которые вы можете использовать для обучения модели на корпусе (по одному на каждый тип модели; полный список [здесь](https://huggingface.co/docs/tokenizers/api/trainers)).
- `post_processors` содержит различные типы постпроцессоров `PostProcessor`, которые вы можете использовать (полный список [здесь](https://huggingface.co/docs/tokenizers/api/post-processors)).
- `decoders` содержит различные типы декодеров `Decoder`, которые вы можете использовать для декодирования результатов токенизации (полный список [здесь](https://huggingface.co/docs/tokenizers/components#decoders)).

Весь список блоков вы можете найти [здесь](https://huggingface.co/docs/tokenizers/components).

## Получение корпуса текста[[acquiring-a-corpus]]

Для обучения нашего нового токенизатора мы будем использовать небольшой корпус текстов (чтобы примеры выполнялись быстро). Шаги по сбору корпуса аналогичны тем, что мы делали в [начале этой главы](../chapter6/2), но на этот раз мы будем использовать набор данных [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"]
```

Функция `get_training_corpus()` - это генератор, который выдает батч из 1000 текстов, которые мы будем использовать для обучения токенизатора. 

🤗 Токенизаторы также можно обучать непосредственно на текстовых файлах. Вот как мы можем сгенерировать текстовый файл, содержащий все тексты/входы из WikiText-2, который мы можем использовать локально:

```python
with open("wikitext-2.txt", "w", encoding="utf-8") as f:
    for i in range(len(dataset)):
        f.write(dataset[i]["text"] + "\n")
```

Далее мы покажем вам, как блок за блоком построить собственные токенизаторы BERT, GPT-2 и XLNet. Это даст нам пример каждого из трех основных алгоритмов токенизации: WordPiece, BPE и Unigram. Начнем с BERT!

## Создание токенизатора WordPiece с нуля[[building-a-wordpiece-tokenizer-from-scratch]]

Чтобы создать токенизатор с помощью библиотеки 🤗 Tokenizers, мы начнем с инстанцирования объектов `Tokenizer` и `model`, затем установим для их атрибутов `normalizer`, `pre_tokenizer`, `post_processor` и `decoder` нужные нам значения.

Для этого примера мы создадим `Tokenizer` с моделью WordPiece:

```python
from tokenizers import (
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    Tokenizer,
)

tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))
```

Мы должны указать `unk_token`, чтобы модель знала, что возвращать, когда она встречает символы, которых раньше не видела. Другие аргументы, которые мы можем задать здесь, включают `vocab` нашей модели (мы собираемся обучать модель, поэтому нам не нужно его задавать) и `max_input_chars_per_word`, который определяет максимальную длину для каждого слова (слова длиннее переданного значения будут разбиты на части).

Первым шагом токенизации является нормализация, поэтому начнем с нее. Поскольку BERT широко используется, существует `BertNormalizer` с классическими параметрами, которые мы можем установить для BERT: `lowercase` и `strip_accents`, которые не требуют пояснений; `clean_text` для удаления всех управляющих символов и замены повторяющихся пробелов на один; и `handle_chinese_chars`, который расставляет пробелы вокруг китайских символов. Чтобы повторить токенизатор `bert-base-uncased`, мы можем просто установить этот нормализатор:

```python
tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True)
```

Однако, как правило, при создании нового токенизатора у вас не будет доступа к такому удобному нормализатору, уже реализованному в библиотеке 🤗 Tokenizers, поэтому давайте посмотрим, как создать нормализатор BERT вручную. Библиотека предоставляет нормализатор `Lowercase` и нормализатор `StripAccents`, и вы можете комбинировать несколько нормализаторов с помощью `Sequence`:

```python
tokenizer.normalizer = normalizers.Sequence(
    [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()]
)
```

Мы также используем нормализатор Unicode `NFD`, поскольку в противном случае нормализатор `StripAccents` не сможет правильно распознать акцентированные символы и, следовательно, не удалит их.

Как мы уже видели ранее, мы можем использовать метод `normalize_str()` нормализатора, чтобы проверить, как он влияет на данный текст:

```python
print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
hello how are u?
```

<Tip>

**Далее** если вы протестируете две версии предыдущих нормализаторов на строке, содержащей символ Unicode `u"\u0085"`, то наверняка заметите, что эти два нормализатора не совсем эквивалентны.  
Чтобы не усложнять версию с `normalizers.Sequence`, мы не включили в нее Regex-замены, которые требует `BertNormalizer`, когда аргумент `clean_text` установлен в `True`, что является поведением по умолчанию. Но не волнуйтесь: можно получить точно такую же нормализацию без использования удобного `BertNormalizer`, добавив два `normalizers.Replace` в последовательность нормализаторов.

</Tip>

Далее следует этап предварительной токенизации. Опять же, есть готовый `BertPreTokenizer`, который мы можем использовать:

```python
tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()
```

Или мы можем создать его с нуля:

```python
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
```

Обратите внимание, что токенизатор `Whitespace` разделяет пробельные символы и все символы, которые не являются буквами, цифрами или символом подчеркивания, поэтому технически он разделяет пробельные символы и знаки пунктуации:

```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))]
```

Если вы хотите выполнять разделение только по пробельным символам, то вместо этого следует использовать предварительный токенизатор `WhitespaceSplit`:

```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))]
```

Как и в случае с нормализаторами, вы можете использовать `Sequence` для комбинирования нескольких предварительных токенизаторов:

```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))]
```

Следующий шаг в конвейере токенизации - обработка входных данных с помощью модели. Мы уже указали нашу модель в инициализации, но нам все еще нужно обучить ее, для чего потребуется `WordPieceTrainer`. Главное, что нужно помнить при инстанцировании тренера в 🤗 Tokenizers, это то, что вам нужно передать ему все специальные токены, которые вы собираетесь использовать - иначе он не добавит их в словарь, поскольку их нет в обучающем корпусе:

```python
special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]
trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens)
```

Помимо указания `vocab_size` и `special_tokens`, мы можем задать `min_frequency` (количество раз, которое должен встретиться токен, чтобы быть включенным в словарь) или изменить `continuing_subword_prefix` (если мы хотим использовать что-то отличное от `##`).

Чтобы обучить нашу модель с помощью итератора, который мы определили ранее, достаточно выполнить эту команду:

```python
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Мы также можем использовать текстовые файлы для обучения нашего токенизатора, что будет выглядеть следующим образом (предварительно мы повторно инициализируем модель с пустым `WordPiece`):

```python
tokenizer.model = models.WordPiece(unk_token="[UNK]")
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

В обоих случаях мы можем проверить работу токенизатора на тексте, вызвав метод `encode()`:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.']
```

Полученное `encoding` представляет собой `Encoding`, которое содержит все необходимые результаты работы токенизатора в разных атрибутах: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` и `overflowing`.

Последний шаг в конвейере токенизации - постобработка. Нам нужно добавить токен `[CLS]` в начале и токен `[SEP]` в конце (или после каждого предложения, если у нас есть пара предложений). Для этого мы будем использовать `TemplateProcessor`, но сначала нам нужно узнать идентификаторы токенов `[CLS]` и `[SEP]` в словаре:

```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)
```

Чтобы написать шаблон для `TemplateProcessor`, мы должны указать, как обрабатывать одно предложение и пару предложений. Для обоих случаев мы указываем специальные токены, которые мы хотим использовать; первое (или одиночное) предложение представлено `$A`, а второе предложение (если кодируется пара) представлено `$B`. Для каждого из них (специальных токенов и предложений) мы также указываем соответствующий идентификатор типа токена (token type ID) после двоеточия. 

Таким образом, классический шаблон BERT определяется следующим образом:

```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)],
)
```

Обратите внимание, что нам нужно передать идентификаторы специальных токенов, чтобы токенизатор мог правильно преобразовать их в их идентификаторы.

Как только это будет добавлено, вернемся к нашему предыдущему примеру:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]']
```

И на паре предложений мы получаем правильный результат:

```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]
```

Мы почти закончили создание этого токенизатора с нуля - остался последний шаг - добавить декодер:

```python
tokenizer.decoder = decoders.WordPiece(prefix="##")
```

Давайте проверим его на нашем предыдущем `encoding`:

```python
tokenizer.decode(encoding.ids)
```

```python out
"let's test this tokenizer... on a pair of sentences."
```

Отлично! Мы можем сохранить наш токенизатор в единственном JSON-файле следующим образом:

```python
tokenizer.save("tokenizer.json")
```

Затем мы можем загрузить этот файл в объект `Tokenizer` с помощью метода `from_file()`:

```python
new_tokenizer = Tokenizer.from_file("tokenizer.json")
```

Чтобы использовать этот токенизатор в 🤗 Transformers, мы должны обернуть его в `PreTrainedTokenizerFast`. Мы можем использовать либо общий класс, либо, если наш токенизатор соответствует существующей модели, использовать этот класс (здесь `BertTokenizerFast`). Если вы используете этот урок для создания нового токенизатора, вам придется использовать первый вариант.

Чтобы обернуть токенизатор в `PreTrainedTokenizerFast`, мы можем либо передать собранный нами токенизатор как `tokenizer_object`, либо передать сохраненный файл токенизатора как `tokenizer_file`. Главное помнить, что нам придется вручную задавать все специальные токены, поскольку класс не может определить из объекта `tokenizer`, какой токен является токеном маски, токеном `[CLS]` и т. д.:

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    # tokenizer_file="tokenizer.json", # В качестве альтернативы можно загрузить из файла токенизатора.
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]",
)
```

Если вы используете определенный класс токенизатора (например, `BertTokenizerFast`), вам нужно будет указать только специальные токены, которые отличаются от токенов по умолчанию (здесь их нет):

```python
from transformers import BertTokenizerFast

wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer)
```

Затем вы можете использовать этот токенизатор, как и любой другой токенизатор 🤗 Transformers. Вы можете сохранить его с помощью метода `save_pretrained()` или загрузить на хаб с помощью метода `push_to_hub()`.

Теперь, когда мы рассмотрели, как создать токенизатор WordPiece, давайте сделаем то же самое для токенизатора BPE. Мы будем двигаться немного быстрее, поскольку вы знаете все шаги, и подчеркнем только различия.

## Создание токенизатора BPE с нуля[[building-a-bpe-tokenizer-from-scratch]]

Теперь давайте создадим токенизатор GPT-2. Как и в случае с токенизатором BERT, мы начнем с инициализации `Tokenizer` с моделью BPE:

```python
tokenizer = Tokenizer(models.BPE())
```

Также, как и в случае с BERT, мы могли бы инициализировать эту модель словарем, если бы он у нас был (в этом случае нам нужно было бы передать `vocab` и `merges`), но поскольку мы будем обучать с нуля, нам не нужно этого делать. Нам также не нужно указывать `unk_token`, потому что GPT-2 использует byte-level BPE, который не требует этого.

GPT-2 не использует нормализатор, поэтому мы пропускаем этот шаг и переходим непосредственно к предварительной токенизации:

```python
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
```

Опция, которую мы добавили к `ByteLevel`, заключается в том, чтобы не добавлять пробел в начале предложения (в противном случае это происходит по умолчанию). Мы можем посмотреть на предварительную токенизацию примера текста, как было показано ранее:

```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))]
```

Далее следует модель, которую нужно обучить. Для GPT-2 единственным специальным токеном является токен конца текста:

```python
trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```

Как и в случае с `WordPieceTrainer`, а также `vocab_size` и `special_tokens`, мы можем указать `min_frequency`, если хотим, или если у нас есть суффикс конца слова (например, `</w>`), мы можем задать его с помощью `end_of_word_suffix`. 

Этот токенизатор также может быть обучен на текстовых файлах:

```python
tokenizer.model = models.BPE()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```

Давайте посмотрим на пример токенизации текста:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.']
```

Мы применяем постобработку на уровне байтов для токенизатора GPT-2 следующим образом:

```python
tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)
```

Опция `trim_offsets = False` указывает постпроцессору, что мы должны оставить смещения токенов, начинающихся с 'Ġ', как есть: таким образом, начало смещения будет указывать на пробел перед словом, а не на первый символ слова (поскольку пробел технически является частью токена). Давайте посмотрим на результат с текстом, который мы только что закодировали, где `'Ġtest'` - это токен с индексом 4:

```python
sentence = "Let's test this tokenizer."
encoding = tokenizer.encode(sentence)
start, end = encoding.offsets[4]
sentence[start:end]
```

```python out
' test'
```

Наконец, мы добавляем декодер на уровне байтов:

```python
tokenizer.decoder = decoders.ByteLevel()
```

и мы сможем перепроверить, правильно ли он работает:

```python
tokenizer.decode(encoding.ids)
```

```python out
"Let's test this tokenizer."
```

Отлично! Теперь, когда мы закончили, мы можем сохранить токенизатор, как раньше, и обернуть его в `PreTrainedTokenizerFast` или `GPT2TokenizerFast`, если мы хотим использовать его в 🤗 Transformers:

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)
```

или:

```python
from transformers import GPT2TokenizerFast

wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer)
```

В качестве последнего примера мы покажем вам, как создать токенизатор Unigram с нуля.

## Создание токенизатора Unigram с нуля[[building-a-unigram-tokenizer-from-scratch]]

Теперь давайте построим токенизатор XLNet. Как и в предыдущих токенизаторах, мы начнем с инициализации `Tokenizer` с моделью Unigram:

```python
tokenizer = Tokenizer(models.Unigram())
```

Опять же, мы могли бы инициализировать эту модель словарем, если бы он у нас был.

Для нормализации XLNet использует несколько замен (которые пришли из SentencePiece):

```python
from tokenizers import Regex

tokenizer.normalizer = normalizers.Sequence(
    [
        normalizers.Replace("``", '"'),
        normalizers.Replace("''", '"'),
        normalizers.NFKD(),
        normalizers.StripAccents(),
        normalizers.Replace(Regex(" {2,}"), " "),
    ]
)
```

Он заменяет <code>``</code> и <code>''</code> with <code>"</code> и любую последовательность из двух или более пробелов на один пробел, а также удаляет ударения в токенезируемых текстах.

Предварительный токенизатор, который должен использоваться для любого токенизатора SentencePiece, - это `Metaspace`:

```python
tokenizer.pre_tokenizer = pre_tokenizers.Metaspace()
```

Мы можем посмотреть на предварительную токенизацию примера текста, как было показано ранее:

```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))]
```

Далее следует модель, которую нужно обучить. В XLNet довольно много специальных токенов:

```python
special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]
trainer = trainers.UnigramTrainer(
    vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>"
)
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)
```
Очень важный аргумент, который не стоит забывать для `UnigramTrainer` - это `unk_token`. Мы также можем передавать другие аргументы, специфичные для алгоритма Unigram, такие как `shrinking_factor` для каждого шага удаления токенов (по умолчанию 0.75) или `max_piece_length` для указания максимальной длины данного токена (по умолчанию 16).

Этот токенизатор также может быть обучен на текстовых файлах:

```python
tokenizer.model = models.Unigram()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)
```
Давайте посмотрим на токенизацию примера текста:

```python
encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```

```python out
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.']
```

Особенностью XLNet является то, что он помещает токен `<cls>` в конец предложения с идентификатором типа 2 (чтобы отличить его от других токенов). В результате он помещается слева. Мы можем разобраться со всеми специальными токенами и идентификаторами типов токенов с помощью шаблона, как в BERT, но сначала нам нужно получить идентификаторы токенов `<cls>` и `<sep>`:

```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
0 1
```

Шаблон выглядит следующим образом:

```python
tokenizer.post_processor = processors.TemplateProcessing(
    single="$A:0 <sep>:0 <cls>:2",
    pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2",
    special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)],
)
```

И мы можем проверить, как это работает, закодировав пару предложений:

```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', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair', 
  '▁of', '▁sentence', 's', '!', '<sep>', '<cls>']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
```

Наконец, мы добавляем декодер `Metaspace`:

```python
tokenizer.decoder = decoders.Metaspace()
```

и мы закончили работу с этим токенизатором! Мы можем сохранить токенизатор, как и раньше, и обернуть его в `PreTrainedTokenizerFast` или `XLNetTokenizerFast`, если мы хотим использовать его в 🤗 Transformers. При использовании `PreTrainedTokenizerFast` следует обратить внимание на то, что помимо специальных токенов, нам нужно указать библиотеке 🤗 Transformers на то, чтобы они располагались слева:

```python
from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<s>",
    eos_token="</s>",
    unk_token="<unk>",
    pad_token="<pad>",
    cls_token="<cls>",
    sep_token="<sep>",
    mask_token="<mask>",
    padding_side="left",
)
```

Или альтернативно:

```python
from transformers import XLNetTokenizerFast

wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer)
```

Теперь, когда вы увидели, как различные блоки используются для создания существующих токенизаторов, вы должны быть в состоянии написать любой токенизатор, который вы хотите, с помощью библиотеки 🤗 Tokenizers и использовать его в 🤗 Transformers.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/8.mdx" />

### Нормализация и предварительная токенизация[[normalization-and-pre-tokenization]]
https://huggingface.co/learn/course/ru/chapter6/4.md

# Нормализация и предварительная токенизация[[normalization-and-pre-tokenization]]

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section4.ipynb"},
]} />

Прежде чем мы более подробно рассмотрим три наиболее распространенных алгоритма токенизации подслов, используемых в моделях Transformer (Byte-Pair Encoding [BPE], WordPiece и Unigram), мы сначала рассмотрим предварительную обработку, которую каждый токенизатор применяет к тексту. Вот высокоуровневый обзор этапов конвейера токенизации:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline.svg" alt="The tokenization pipeline.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/tokenization_pipeline-dark.svg" alt="The tokenization pipeline.">
</div>

Перед тем как разбить текст на подтокены (в соответствии со выбранной моделью), токенизатор выполняет два шага: _нормализацию_ и _претокенизацию_.

## Нормализация[[normalization]]

<Youtube id="4IIC2jI9CaU"/>

Шаг нормализации включает в себя некоторую общую очистку, например, удаление ненужных пробельных символов, понижение регистра и/или удаление ударений. Если вы знакомы с [Unicode normalization](http://www.unicode.org/reports/tr15/) (например, NFC или NFKC), это также может быть применено токенизатором.

У 🤗 Transformers `tokenizer` есть атрибут `backend_tokenizer`, который предоставляет доступ к базовому токенизатору из библиотеки 🤗 Tokenizers:

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
print(type(tokenizer.backend_tokenizer))
```

```python out
<class 'tokenizers.Tokenizer'>
```

Атрибут `normalizer` объекта `tokenizer` имеет метод `normalize_str()`, который мы можем использовать, чтобы увидеть, как выполняется нормализация:

```py
print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```

```python out
'hello how are u?'
```

В этом примере, поскольку мы выбрали контрольную точку `bert-base-uncased`, нормализация применила нижний регистр и удалила ударения. 

<Tip>

✏️ **Попробуйте!** Загрузите токенизатор из контрольной точки `bert-base-cased` и передайте ему тот же пример. Какие основные различия вы можете увидеть между версией токенизатора cased и uncased?

</Tip>

## Предварительная токенизация[[pre-tokenization]]

<Youtube id="grlLV8AIXug"/>

Как мы увидим в следующих разделах, токенизатор не может быть обучен только на сыром тексте. Сначала необходимо разбить текст на небольшие части, например, на слова. Именно в этом и заключается этап предварительной токенизации. Как мы видели в [Главе 2](../chapter2/1), токенизатор на основе слов (word-based tokenizer) может просто разбить необработанный текст на слова по пробелам и знакам пунктуации. Эти слова станут границами подтокенов, которые токенизатор сможет выучить в процессе обучения.

Чтобы увидеть, как быстрый токенизатор выполняет предварительную токенизацию, мы можем воспользоваться методом `pre_tokenize_str()` атрибута `pre_tokenizer` объекта `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))]
```

Обратите внимание, что токенизатор уже следит за смещениями, и именно поэтому он может дать нам сопоставление смещений, которое мы использовали в предыдущем разделе. Здесь токенизатор игнорирует два пробела и заменяет их одним, но смещение перескакивает между `are` и `you`, чтобы учесть это.

Поскольку мы используем токенизатор BERT, предварительная токенизация включает часть пробельных символов и пунктуацию. Другие токенизаторы могут иметь другие правила для этого шага. Например, если мы используем токенизатор GPT-2:

```py
tokenizer = AutoTokenizer.from_pretrained("gpt2")
tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are  you?")
```

он также выполнит разбиение по пробельным символам и пунктуации, но сохранит пробелы и заменит их символом `Ġ`, что позволит ему восстановить исходные пробелы, если мы декодируем токены:

```python out
[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)),
 ('?', (19, 20))]
```

Также обратите внимание, что в отличие от токенизатора BERT, этот токенизатор не игнорирует двойной пробел.

В качестве последнего примера рассмотрим токенизатор T5, основанный на алгоритме 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))]
```

Как и токенизатор GPT-2, этот сохраняет пробелы и заменяет их специальным токеном (`_`), но токенизатор T5 делает разбиение только по пробелам, а не по знакам препинания. Также обратите внимание, что он по умолчанию добавляет пробел в начале предложения (перед `Hello`) и игнорирует двойной пробел между `are` и `you`.

Теперь, когда мы немного познакомились с тем, как обрабатывают текст различные токенизаторы, можно приступить к изучению самих алгоритмов, лежащих в их основе. Мы начнем с краткого обзора широко применяемого SentencePiece; затем, в следующих трех разделах, мы рассмотрим, как работают три основных алгоритма, используемых для токенизации по подсловам.

## SentencePiece[[sentencepiece]]

[SentencePiece](https://github.com/google/sentencepiece) - это алгоритм токенизации для предварительной обработки текста, который можно использовать с любой из моделей, которые мы рассмотрим в следующих трех разделах. Он рассматривает текст как последовательность символов Unicode и заменяет пробелы специальным символом `▁`. При использовании в сочетании с алгоритмом Unigram (см. [раздел 7](../chapter7/7)) он даже не требует шага предварительной токенизации, что очень полезно для языков, где символ пробела не используется (например, китайского или японского).

Другой главной особенностью SentencePiece является *обратимая токенизация*: поскольку в нем нет специальной обработки пробелов, декодирование токенов осуществляется просто путем их конкатенации и замены `_` на пробелы - в результате получается нормализованный текст. Как мы видели ранее, токенизатор BERT удаляет повторяющиеся пробелы, поэтому его токенизация не является обратимой.

## Обзор алгоритма[[algorithm-overview]]

В следующих разделах мы рассмотрим три основных алгоритма токенизации по подсловам: BPE (используется в GPT-2 и других моделях), WordPiece (используется, например, в BERT) и Unigram (используется в T5 и других моделях). Прежде чем мы приступим, вот краткий обзор того, как работает каждый из них. Не стесняйтесь возвращаться к этой таблице после прочтения каждого из следующих разделов, если вам еще не все понятно.


Model | BPE | WordPiece | Unigram
:----:|:---:|:---------:|:------:
Обучение | Начинается с маленького словаря и изучает правила слияния токенов | Начинается с маленького словаря и изучает правила слияния токенов | Начинается с большого словаря и изучает правила удаления токенов
Шаг обучения | Объединяет токены, соответствующие наиболее часто встречающейся паре | Объединяет токены, соответствующие паре с наилучшей оценкой, основанной на частоте пары, отдавая предпочтение парам, где каждый отдельный токен встречается реже | Удаляет все токены в словаре, что минимизирует потери, вычисленные для всего корпуса.
Обучение | Слияние правил и словаря | Только словарь | Словарь с оценкой каждого токена
Кодирование | Разбивает слово на символы и применяет слияния, полученные во время обучения | Находит самое длинное подслово, начиная с начала, которое есть в словаре, затем делает то же самое для остальной части слова | Находит наиболее вероятное разбиение на токены, используя оценки, полученные во время обучения

А теперь давайте погрузимся в BPE!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/4.mdx" />

### Токенизация Unigram[[unigram-tokenization]]
https://huggingface.co/learn/course/ru/chapter6/7.md

# Токенизация Unigram[[unigram-tokenization]]

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section7.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section7.ipynb"},
]} />

Алгоритм Unigram часто используется в SentencePiece, который является алгоритмом токенизации, применяемым в таких моделях, как AlBERT, T5, mBART, Big Bird и XLNet.

<Youtube id="TGZfZVuF9Yc"/>

<Tip>

💡 В этом разделе подробно рассматривается Unigram, вплоть до демонстрации полной реализации. Вы можете пропустить его, если вам нужен только общий обзор алгоритма токенизации.

</Tip>

## Алгоритм обучения[[training-algorithm]]

По сравнению с BPE и WordPiece, Unigram работает в другом направлении: он начинает с большого словарного запаса и удаляет из него токены, пока не достигнет желаемого размера словаря. Существует несколько вариантов создания базового словаря: например, мы можем взять наиболее часто встречающиеся подстроки в предварительно токенизированных словах или применить BPE к исходному корпусу с большим объемом словаря.

На каждом шаге обучения алгоритм Unigram рассчитывает потери по корпусу с учетом текущего словарного запаса. Затем для каждого символа в словаре алгоритм вычисляет, насколько увеличится общая потеря, если этот символ будет удален, и ищет символы, которые увеличат ее меньше всего. Эти символы оказывают меньшее влияние на общую потерю по корпусу, поэтому в некотором смысле они "менее нужны" и являются лучшими кандидатами на удаление.

Это очень дорогостоящая операция, поэтому мы удаляем не просто один символ, связанный с наименьшим увеличением потерь, а \\(p\\) (\\(p\\) - гиперпараметр, которым вы можете управлять, обычно 10 или 20) процентов символов, связанных с наименьшим увеличением потерь. Этот процесс повторяется до тех пор, пока словарь не достигнет желаемого размера.

Обратите внимание, что мы никогда не удаляем базовые символы, чтобы убедиться, что любое слово может быть токенизировано.

Итак, все еще немного туманно: основная часть алгоритма заключается в том, чтобы вычислить потери по корпусу и посмотреть, как они изменяются при удалении некоторых токенов из словаря, но мы еще не объяснили, как это сделать. Этот шаг зависит от алгоритма токенизации модели Unigram, поэтому мы рассмотрим его далее.

Мы используем корпус текста из предыдущих примеров:

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

и для этого примера мы возьмем все подстроки из исходного словаря:

```
["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"]
```

## Алгоритм токенизации[[tokenization-algorithm]]

Модель Unigram - это тип языковой модели, в которой каждый токен рассматривается как независимый от предшествующих ему. Это самая простая языковая модель в том смысле, что вероятность появления токена X с учетом предыдущего контекста - это просто вероятность появления токена X. Таким образом, если бы мы использовали модель Unigram для генерации текста, мы бы всегда предсказывали наиболее часто встречающийся токен.

Вероятность данного токена - это его частота (количество раз, когда мы его находим) в исходном корпусе, деленная на сумму частот всех токенов в словаре (чтобы убедиться, что суммы вероятностей равны 1). Например, `"ug"` присутствует в `"hug"`, `"pug"` и `"hugs"`, поэтому его частота в нашем корпусе равна 20.

Здесь приведены частоты всех возможных подслов в словаре:

```
("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)
```

Итак, сумма всех частот равна 210, а вероятность появления подслова `"ug"`, таким образом, составляет 20/210.

<Tip>

✏️ **Теперь ваша очередь!** Напишите код для вычисления вышеуказанных частот и дважды проверьте правильность приведенных результатов, а также общую сумму.

</Tip>

Теперь для токенизации данного слова мы рассматриваем все возможные сегментации на токены и вычисляем вероятность каждого из них в соответствии с моделью Unigram. Поскольку все токены считаются независимыми, эта вероятность равна произведению вероятностей появления каждого токена. Например, при токенизации `["p", "u", "g"]` слова `"pug"` вероятность составляет:

$$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$$

Для сравнения, токен `["pu", "g"]` имеет вероятность:

$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$

так что один из них гораздо более вероятен. В целом, токенизации с наименьшим количеством токенов будут иметь наибольшую вероятность (из-за деления на 210, повторяющегося для каждого токена), что соответствует интуитивному желанию: разбить слово на наименьшее количество токенов.

Токенизация слова с помощью модели Unigram - это токенизация с наибольшей вероятностью. В примере с `"pug"` приведены вероятности, которые мы получили бы для каждой возможной сегментации:

```
["p", "u", "g"] : 0.000389
["p", "ug"] : 0.0022676
["pu", "g"] : 0.0022676
```

Так, `"pug"` будет токенизировано как `["p", "ug"]` или `["pu", "g"]`, в зависимости от того, какая из этих сегментаций встретится первой (отметим, что в большом корпусе подобные случаи равенства будут редки).

В данном случае было легко найти все возможные сегментации и вычислить их вероятности, но в общем случае это будет немного сложнее. Для этого используется классический алгоритм, который называется *алгоритм Витерби (Viterbi algorithm)*. По сути, мы можем построить граф для выявления возможных сегментаций данного слова, сказав, что существует ветвь от символа _a_ до символа _b_, если подслово от _a_ до _b_ есть в словаре, и приписать этой ветви вероятность подслова.

Чтобы найти путь в этом графе, который будет иметь наилучшую оценку, алгоритм Витерби определяет для каждой позиции в слове сегментацию с наилучшей оценкой, которая заканчивается на этой позиции. Поскольку мы идем от начала к концу, этот лучший результат можно найти, перебирая все подслова, заканчивающиеся на текущей позиции, а затем используя лучший результат токенизации с позиции, на которой начинается это подслово. Затем нужно просто развернуть путь, чтобы прийти к концу.

Давайте рассмотрим пример с использованием нашего словаря и слова `"unhug"`. Для каждой позиции подслова с наилучшими оценками заканчиваются следующим образом:

```
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)
```

Таким образом, `"unhug"` будет токенизировано как `["un", "hug"]`.

<Tip>

✏️ **Теперь ваша очередь!** Определите токенизацию слова `" huggun"` и его оценку.

</Tip>

## Назад к обучению[[back-to-training]]

Теперь, когда мы увидели, как работает токенизация, мы можем немного глубже изучить потери, используемые во время обучения. На любом этапе эта потеря вычисляется путем токенизации каждого слова в корпусе с использованием текущего словаря и модели Unigram, определяемой частотами каждого токена в корпусе (как было показано ранее).

Каждое слово в корпусе имеет оценку, а потеря - это отрицательное логарифмическое правдоподобие этих оценок, то есть сумма для всех слов в корпусе всех `-log(P(word))`.

Давайте вернемся к нашему примеру со следующим корпусом:

```
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
```

Токенизация каждого слова с соответствующими оценками:

```
"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)
```

Таким образом, потери будут:

```
10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8
```

Теперь нам нужно вычислить, как удаление каждого токена влияет на потери. Это довольно утомительно, поэтому мы просто сделаем это для двух токенов и оставим весь процесс на потом, когда у нас будет код, чтобы помочь нам. В этом (очень) конкретном случае у нас есть две эквивалентные токенизации всех слов: как мы видели ранее, например, `"pug"` может быть токенизировано `["p", "ug"]` с тем же результатом. Таким образом, удаление токена `"pu"` из словаря приведет к точно таким же потерям.

С другой стороны, удаление `" hug"` усугубит потери, потому что токенизация `"hug"` и `"hugs"` станет:

```
"hug": ["hu", "g"] (score 0.006802)
"hugs": ["hu", "gs"] (score 0.001701)
```

Эти изменения приведут к увеличению потерь:

```
- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5
```

Поэтому токен `"pu"`, вероятно, будет удален из словаря, но не `"hug"`.

## Реализация Unigram[[implementing-unigram]]

Теперь давайте реализуем все, что мы видели до сих пор, в коде. Как и в случае с BPE и WordPiece, это не эффективная реализация алгоритма Unigram (совсем наоборот), но она должна помочь вам понять его немного лучше.

В качестве примера мы будем использовать тот же корпус текста, что и раньше:

```python
corpus = [
    "This is the Hugging Face Course.",
    "This chapter is about tokenization.",
    "This section shows several tokenizer algorithms.",
    "Hopefully, you will be able to understand how they are trained and generate tokens.",
]
```

На этот раз в качестве модели мы будем использовать `xlnet-base-cased`:

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased")
```

Как и в случае с BPE и WordPiece, мы начинаем с подсчета количества вхождений каждого слова в корпус:

```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
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
        # Перебираем подслова длиной не менее 2
        for j in range(i + 2, len(word) + 1):
            subwords_freqs[word[i:j]] += freq

# Сортировка подслов по частоте
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)]
```

Мы группируем символы с лучшими подсловами, чтобы получить начальный словарь размером 300:

```python
token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)]
token_freqs = {token: freq for token, freq in token_freqs}
```

<Tip>

💡 SentencePiece использует более эффективный алгоритм под названием Enhanced Suffix Array (ESA) для создания начального словаря.

</Tip>

Далее мы вычисляем сумму всех частот, чтобы преобразовать частоты в вероятности. Для нашей модели мы будем хранить логарифмы вероятностей, потому что численно стабильнее складывать логарифмы, чем перемножать маленькие числа, и это упростит вычисление потерь модели:

```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()}
```

Теперь основная функция - это функция токенизации слов с помощью алгоритма Витерби. Как мы уже видели, этот алгоритм вычисляет наилучшую сегментацию каждой подстроки слова, которую мы будем хранить в переменной с именем `best_segmentations`. Мы будем хранить по одному словарю на каждую позицию в слове (от 0 до его полной длины), с двумя ключами: индекс начала последнего токена в лучшей сегментации и оценка лучшей сегментации. По индексу начала последнего токена мы сможем получить полную сегментацию, когда список будет полностью заполнен.

Пополнение списка осуществляется с помощью двух циклов: основной цикл просматривает каждую начальную позицию, а второй цикл перебирает все подстроки, начинающиеся с этой начальной позиции. Если подстрока есть в словаре, мы получаем новую сегментацию слова до этой конечной позиции, которую сравниваем с той, что хранится в `best_segmentations`.

После завершения основного цикла мы просто начинаем с конца и переходим от одной начальной позиции к другой, записывая токены по мере продвижения, пока не достигнем начала слова:

```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)):
        # Это должно быть правильно заполнено предыдущими шагами цикла
        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
                # Если мы нашли лучшую сегментацию, заканчивающуюся на end_idx, мы обновляем
                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:
        # Мы не нашли токенизацию слова -> возвращаем unknown
        return ["<unk>"], 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
```

Мы уже можем опробовать нашу первоначальную модель на некоторых словах:

```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)
```

Теперь легко вычислить потери модели на корпусе!

```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
```

Мы можем проверить его работу на имеющейся у нас модели:

```python
compute_loss(model)
```

```python out
413.10377642940875
```

Вычисление оценок для каждого токена также не представляет особой сложности; нам просто нужно вычислить потери для модели, полученные при удалении каждого токена:

```python
import copy


def compute_scores(model):
    scores = {}
    model_loss = compute_loss(model)
    for token, score in model.items():
        # Мы всегда храним токены длиной 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
```

Мы можем попробовать это на заданном токене:

```python
scores = compute_scores(model)
print(scores["ll"])
print(scores["his"])
```

Поскольку `"ll"` используется в токенизации слова `"Hopefully"`, и его удаление, вероятно, заставит нас дважды использовать токен `"l"` вместо этого, мы ожидаем, что он будет иметь положительную потерю. `"his"` используется только внутри слова `"This"`, которое токенизируется само по себе, поэтому мы ожидаем, что потери будут нулевыми. Вот результаты:

```python out
6.376412403623874
0.0
```

<Tip>

💡 Такой подход очень неэффективен, поэтому SentencePiece использует приближенную оценку потерь модели без токена X: вместо того чтобы начинать с нуля, он просто заменяет токен X его сегментацией в оставшемся словаре. Таким образом, все оценки могут быть вычислены одновременно с потерями модели.

</Tip>

Когда этот процесс завершиться, останется только добавить в словарь специальные токены, используемые моделью, а затем итерироваться, пока мы не вычеркнем из словаря достаточно токенов, чтобы достичь желаемого размера:

```python
percent_to_remove = 0.1
while len(model) > 100:
    scores = compute_scores(model)
    sorted_scores = sorted(scores.items(), key=lambda x: x[1])
    # Удалите токены percent_to_remove с наименьшими оценками
    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()}
```

Затем, чтобы токенизировать некоторый текст, нам просто нужно применить предварительную токенизацию, а затем использовать нашу функцию `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', '.']
```

Вот и все об Unigram! Надеемся, теперь вы чувствуете себя экспертом во всем, что касается токенизаторов. В следующем разделе мы рассмотрим блоки библиотеки 🤗 Tokenizers и покажем, как их можно использовать для создания собственного токенизатора.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/7.mdx" />

### Быстрые токенизаторы в QA конвейере[[fast-tokenizers-in-the-qa-pipeline]]
https://huggingface.co/learn/course/ru/chapter6/3b.md

# Быстрые токенизаторы в QA конвейере[[fast-tokenizers-in-the-qa-pipeline]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section3b_tf.ipynb"},
]} />

{/if}

Теперь мы погрузимся в конвейер `question-answering` и посмотрим, как использовать смещения (offsets) для получения ответа на вопрос из контекста, подобно тому, как мы делали это для сгруппированных сущностей в предыдущем разделе. Затем мы посмотрим, как работать с очень длинными контекстами, которые в итоге будут обрезаны. Вы можете пропустить этот раздел, если вас не интересует задача ответа на вопрос.

{#if fw === 'pt'}

<Youtube id="_wxyB3j3mk4"/>

{:else}

<Youtube id="b3u8RzBCX9Y"/>

{/if}

## Использование конвейера `question-answering`[[using-the-question-answering-pipeline]]

Как мы видели в [Главе 1](../chapter1/1), для получения ответа на вопрос мы можем использовать конвейер `question-answering` следующим образом:

```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.
"""
question = "Which deep learning libraries back 🤗 Transformers?"
question_answerer(question=question, context=context)
```

```python out
{'score': 0.97773,
 'start': 78,
 'end': 105,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

В отличие от других конвейеров, которые не могут обрезать и разбивать на части тексты, длина которых превышает максимально допустимую моделью (и поэтому могут пропустить информацию в конце документа), этот конвейер может работать с очень длинными контекстами и вернет ответ на вопрос, даже если он находится в конце:

```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.
"""
question_answerer(question=question, context=long_context)
```

```python out
{'score': 0.97149,
 'start': 1892,
 'end': 1919,
 'answer': 'Jax, PyTorch and TensorFlow'}
```

Давайте посмотрим, как он справится со всем этим!

## Использование модели для ответа на вопросы[[using-a-model-for-question-answering]]

Как и в любом другом конвейере, мы начинаем с токенизации входных данных, а затем отправляем их через модель. По умолчанию для конвейера `question-answering` используется контрольная точка [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (слово "squad" в названии происходит от набора данных, на котором дообучили модель; подробнее о наборе данных SQuAD мы поговорим в [главе 7](../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}

Обратите внимание, что мы токенизируем вопрос и контекст как пару, причем вопрос стоит первым.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens.svg" alt="An example of tokenization of question and context"/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter6/question_tokens-dark.svg" alt="An example of tokenization of question and context"/>
</div>

Модели для ответов на вопросы работают немного иначе, чем модели, которые мы рассматривали до сих пор. На примере картинки выше модель была обучена предсказывать индекс токена, с которого начинается ответ (здесь 21), и индекс токена, на котором ответ заканчивается (здесь 24). Вот почему эти модели возвращают не один тензор логитов, а два: один для логитов, соответствующих начальному токену ответа, и один для логитов, соответствующих конечному токену ответа. Поскольку в данном случае у нас только один вход, содержащий 66 токенов, мы получаем:

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

Чтобы преобразовать эти логиты в вероятности, мы применим функцию softmax, но перед этим нам нужно убедиться, что мы маскируем индексы, которые не являются частью контекста. Наш вход - `[CLS] вопрос [SEP] контекст [SEP]`, поэтому нам нужно замаскировать токены вопроса, а также токен `[SEP]`. Однако мы оставим токен `[CLS]`, поскольку некоторые модели используют его для указания на то, что ответ не находится в контексте.

Поскольку впоследствии мы будем применять softmax, нам просто нужно заменить логиты, которые мы хотим замаскировать, на большое отрицательное число. Здесь мы используем `-10000`:

{#if fw === 'pt'}

```py
import torch

sequence_ids = inputs.sequence_ids()
# Маскируем все, кроме токенов контекста
mask = [i != 1 for i in sequence_ids]
# Демаскируем токен [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()
# Маскируем все, кроме токенов контекста
mask = [i != 1 for i in sequence_ids]
# Демаскируем токен [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}

Теперь, когда мы правильно замаскировали логиты, соответствующие позициям, которые мы не хотим предсказывать, мы можем применить 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}

На этом этапе мы могли бы взять argmax вероятностей начала и конца - но в итоге мы можем получить начальный индекс, который больше конечного, поэтому нам нужно принять еще несколько мер предосторожности. Мы вычислим вероятности каждого возможного `start_index` и `end_index`, где `start_index <= end_index`, а затем возьмем кортеж `(start_index, end_index)` с наибольшей вероятностью.

Если предположить, что события "Ответ начинается на `start_index`" и "Ответ заканчивается на `end_index`" независимы, то вероятность того, что ответ начинается на `start_index` и заканчивается на `end_index`, равна:

$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ 

Таким образом, чтобы вычислить все оценки, нам нужно просто вычислить все произведения \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) где `start_index <= end_index`.

Сначала вычислим все возможные произведения:

```py
scores = start_probabilities[:, None] * end_probabilities[None, :]
```

{#if fw === 'pt'}

Затем мы замаскируем значения, где `start_index > end_index`, установив для них значение `0` (все остальные вероятности - положительные числа). Функция `torch.triu()` возвращает верхнюю треугольную часть двумерного тензора, переданного в качестве аргумента, поэтому она сделает эту маскировку за нас:

```py
scores = torch.triu(scores)
```

{:else}

Затем мы замаскируем значения, где `start_index > end_index`, установив для них значение `0` (все остальные вероятности - положительные числа). Функция `np.triu()` возвращает верхнюю треугольную часть двумерного тензора, переданного в качестве аргумента, поэтому она сделает эту маскировку за нас:

```py
import numpy as np

scores = np.triu(scores)
```

{/if}

Теперь нам осталось получить индекс максимума. Поскольку PyTorch вернет индекс в плоском тензоре, для получения `start_index` и `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])
```

Мы еще не закончили, но, по крайней мере, у нас уже есть корректная оценка ответа (вы можете проверить это, сравнив ее с первым результатом в предыдущем разделе):

```python out
0.97773
```

<Tip>

✏️ **Попробуйте!** Вычислите начальный и конечный индексы для пяти наиболее вероятных ответов.

</Tip>

У нас есть `start_index` и `end_index` ответа в терминах токенов, так что теперь нам просто нужно преобразовать их в индексы символов в контексте. Именно здесь смещения будут очень полезны. Мы можем захватить их и использовать, как мы это делали в задаче token classification:

```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]
```

Осталось только отформатировать все, чтобы получить результат:

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

Отлично! Это то же самое, что и в нашем первом примере!

<Tip>

✏️ **Попробуйте! ** Используйте лучшие оценки, которые вы вычислили ранее, чтобы показать пять наиболее вероятных ответов. Чтобы проверить результаты, вернитесь к первому конвейеру и передайте `top_k=5` при его вызове.

</Tip>

## Обработка длинных контекстов[[handling-long-contexts]]

Если мы попытаемся токенизировать вопрос и длинный контекст, который мы использовали в качестве примера ранее, мы получим количество токенов, превышающее максимальную длину, используемую в конвейере `question-answering` (которая составляет 384):

```py
inputs = tokenizer(question, long_context)
print(len(inputs["input_ids"]))
```

```python out
461
```

Поэтому нам нужно обрезать входные данные до максимальной длины. Есть несколько способов сделать это, но мы не хотим усекать вопрос, а только контекст. Поскольку контекст - это второе предложение, мы используем стратегию усечения `" only_second"`. Проблема, которая возникает в этом случае, заключается в том, что ответ на вопрос может не находиться в усеченном контексте. Например, здесь мы выбрали вопрос, ответ на который находится в конце контекста, а когда мы его усекаем, то этого ответа в нём нет:

```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]
"""
```

Это означает, что модели будет сложно выбрать правильный ответ. Чтобы исправить это, конвейер `question-answering` позволяет нам разбить контекст на более мелкие фрагменты, указав максимальную длину. Чтобы убедиться, что мы не разбиваем контекст на фрагменты именно в том месте, которое не позволяет найти ответ, он также включает некоторое перекрытие между фрагментами.

Мы можем заставить токенизатор (быстрый или медленный) сделать это за нас, добавив `return_overflowing_tokens=True`, и указать желаемое перекрытие с помощью аргумента `stride`. Вот пример, использующий небольшое предложение:

```py
sentence = "This sentence is not too long but we are going to split it anyway."
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]'
```

Как мы видим, предложение было разбито на части таким образом, что каждая запись в `inputs["input_ids"]` содержит не более 6 токенов (чтобы последняя запись была такого же размера, как и остальные, нам придется добавить дополняющие токены (padding tokens)), и между каждой частью есть перекрытие в 2 токена. 

Давайте посмотрим на результат токенизации:

```py
print(inputs.keys())
```

```python out
dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping'])
```

Как и ожидалось, мы получаем идентификаторы входов и маску внимания. Последний ключ, `overflow_to_sample_mapping`, представляет собой карту, которая говорит нам, какому предложению соответствует каждый из результатов - здесь у нас есть 7 результатов, которые все происходят из (единственного) предложения, которое мы передали токенизатору:

```py
print(inputs["overflow_to_sample_mapping"])
```

```python out
[0, 0, 0, 0, 0, 0, 0]
```

Это более полезно, когда мы токенизируем несколько предложений вместе. Например, так:

```py
sentences = [
    "This sentence is not too long but we are going to split it anyway.",
    "This sentence is shorter but will still get split.",
]
inputs = tokenizer(
    sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2
)

print(inputs["overflow_to_sample_mapping"])
```

gets us:

```python out
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
```

что означает, что первое предложение разбито на 7 частей, как и раньше, а следующие 4 части взяты из второго предложения.

Теперь давайте вернемся к нашему длинному контексту. По умолчанию конвейер `question-answering` использует максимальную длину 384, как мы уже упоминали ранее, и stride 128, что соответствует тому, как была дообучена модель (вы можете настроить эти параметры, передав аргументы `max_seq_len` и `stride` при вызове конвейера). Таким образом, мы будем использовать эти параметры при токенизации. Мы также добавим дополняющие токены (padding tokens) (чтобы иметь образцы одинаковой длины, чтобы можно было строить тензоры), а также запросим смещения:

```py
inputs = tokenizer(
    question,
    long_context,
    stride=128,
    max_length=384,
    padding="longest",
    truncation="only_second",
    return_overflowing_tokens=True,
    return_offsets_mapping=True,
)
```

Эти `inputs` будут содержать идентификаторы входов и маски внимания, которые ожидает модель, а также смещения и `overflow_to_sample_mapping`, о которых мы только что говорили. Поскольку эти два параметра не используются моделью, мы выкинем их из `inputs` (и не будем хранить карту, поскольку она здесь не нужна) перед преобразованием в тензор:

{#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}

Наш длинный контекст был разделен на две части, а это значит, что после того, как он пройдет через нашу модель, у нас будет два набора начальных и конечных логитов:

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

Как и раньше, мы сначала маскируем токены, которые не являются частью контекста, прежде чем использовать softmax. Мы также маскируем все дополняющие токены (padding tokens) (отмеченные маской внимания):

{#if fw === 'pt'}

```py
sequence_ids = inputs.sequence_ids()
# Маскируем все, кроме токенов контекста
mask = [i != 1 for i in sequence_ids]
# Демаскируем токен [CLS].
mask[0] = False
# Маскируем все [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()
# Маскируем все, кроме токенов контекста
mask = [i != 1 for i in sequence_ids]
# Демаскируем токен [CLS].
mask[0] = False
# Маскируем все [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}

Затем мы можем использовать softmax для преобразования логитов в вероятности:

{#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}

Следующий шаг аналогичен тому, что мы делали для малого контекста, но мы повторяем его для каждого из наших двух фрагментов. Мы присваиваем оценку всем возможным фрагментам ответа, а затем выбираем фрагмент с наилучшей оценкой:

{#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[1]
    end_idx = idx % scores.shape[1]
    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[1]
    end_idx = idx % scores.shape[1]
    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)]
```

Эти два кандидата соответствуют лучшим ответам, которые модель смогла найти в каждом фрагменте. Модель гораздо больше уверена в том, что правильный ответ находится во второй части (это хороший знак!). Теперь нам нужно сопоставить эти два диапазона токенов с диапазонами символов в контексте (для получения ответа нам нужно сопоставить только второй, но интересно посмотреть, что модель выбрала в первом фрагменте).

<Tip>

✏️ **Попробуйте!** Адаптируйте приведенный выше код, чтобы он возвращал оценки и промежутки для пяти наиболее вероятных ответов (в целом, а не по частям).

</Tip>

`offsets`, которую мы взяли ранее, на самом деле является списком смещений, по одному списку на каждый фрагмент текста:

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

Если мы проигнорируем первый результат, то получим тот же результат, что и в нашем конвейере для этого длинного контекста - ура!

<Tip>

✏️ **Попробуйте!** Используйте лучшие оценки, которые вы вычислили ранее, чтобы показать пять наиболее вероятных ответов (для всего контекста, а не для каждого фрагмента). Чтобы проверить результаты, вернитесь к первому конвейеру и передайте `top_k=5` при его вызове.

</Tip>

На этом мы завершаем наше глубокое погружение в возможности токенизатора. В следующей главе мы снова применим все это на практике, когда покажем, как дообучить модель для ряда распространенных задач NLP.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/3b.mdx" />

### Обучение нового токенизатора на основе старого[[training-a-new-tokenizer-from-an-old-one]]
https://huggingface.co/learn/course/ru/chapter6/2.md

# Обучение нового токенизатора на основе старого[[training-a-new-tokenizer-from-an-old-one]]

<CourseFloatingBanner chapter={6}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter6/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section2.ipynb"},
]} />

Если языковая модель не доступна на интересующем вас языке или ваш корпус сильно отличается от того, на котором обучалась языковая модель, вам, скорее всего, придется заново обучать модель с нуля, используя токенизатор, адаптированный к вашим данным. Для этого потребуется обучить новый токенизатор на вашем наборе данных. Но что именно это значит? Когда мы впервые рассматривали токенизаторы в [Главе 2](../chapter2/1), мы увидели, что большинство моделей трансформеров используют _алгоритм токенизации по подсловам_. Чтобы определить, какие подслова представляют интерес и наиболее часто встречаются в корпусе, токенизатор должен внимательно изучить все тексты в корпусе - этот процесс мы называем *обучением*. Точные правила обучения зависят от типа используемого токенизатора, далее в этой главе мы рассмотрим три основных алгоритма.

<Youtube id="DJimQynXZsQ"/>

<Tip warning={true}>

⚠️ Обучение токенизатора - это не то же самое, что обучение модели! При обучении модели используется стохастический градиентный спуск, чтобы сделать потери немного меньше для каждого батча. Оно рандомизировано по своей природе (это означает, что вам нужно задать некоторое число seed, чтобы получить одинаковые результаты при повторном обучении). Обучение токенизатора - это статистический процесс, который пытается определить, какие подслова лучше всего выбрать для данного корпуса, а точные правила, используемые для их выбора, зависят от алгоритма токенизации. Это детерминированный процесс, то есть вы всегда получите одинаковые результаты при обучении одного и того же алгоритма на одном и том же корпусе.

</Tip>

## Сбор корпуса слов[[assembling-a-corpus]]

В 🤗 Transformers есть очень простой API, который можно использовать для обучения нового токенизатора с теми же характеристиками, что и у существующего: `AutoTokenizer.train_new_from_iterator()`. Чтобы увидеть это в действии, предположим, что мы хотим обучить GPT-2 с нуля, но на языке, отличном от английского. Нашей первой задачей будет собрать много данных на этом языке в обучающий корпус. Чтобы примеры были понятны всем, мы будем использовать не русский или китайский язык, а будем использовать специализированный английский: Python-код.

Библиотека [🤗 Datasets](https://github.com/huggingface/datasets) может помочь нам собрать корпус исходного кода Python. Мы воспользуемся обычной функцией `load_dataset()` для загрузки и кэширования набора данных [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Этот набор данных был создан для конкурса [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) и содержит миллионы функций из библиотек с открытым исходным кодом с GitHub на нескольких языках программирования. Здесь мы загрузим Python-часть этого набора данных:

```py
from datasets import load_dataset

# Загрузка может занять несколько минут, так что выпейте кофе или чай, пока ждете!
raw_datasets = load_dataset("code_search_net", "python")
```

Мы можем взглянуть на тренировочную часть датасета, чтобы узнать, к каким столбцам у нас есть доступ:

```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
})
```

Мы видим, что набор данных отделяет документацию от кода и предлагает токенизировать и то, и другое. Здесь мы просто используем колонку `whole_func_string` для обучения нашего токенизатора. Мы можем посмотреть пример одной из этих функций, обратившись к соответствующему индексу в части `train`:

```py
print(raw_datasets["train"][123456]["whole_func_string"])
```

который должен вывести следующее:

```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)
```

Первое, что нам нужно сделать, это преобразовать набор данных в _итератор_ списков текстов -- например, список списков текстов. Использование списков текстов позволит нашему токенизатору работать быстрее (обучение на батчах текстов вместо обработки отдельных текстов по одному), и это должен быть итератор, если мы хотим избежать необходимости держать в памяти все сразу. Если ваш корпус огромен, вы захотите воспользоваться тем, что 🤗 Datasets не загружает все в RAM, а хранит элементы набора данных на диске.

Следующее действие создаст список списков по 1 000 текстов в каждом, но загрузит все в память:

```py
# Не раскоментируйте следующую строку кода, если только ваш набор данных не маленький!
# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)]
```

Используя генератор Python, мы можем не загружать ничего в память Python до тех пор, пока это действительно не понадобится. Чтобы создать такой генератор, нужно просто заменить скобки на круглые скобки:

```py
training_corpus = (
    raw_datasets["train"][i : i + 1000]["whole_func_string"]
    for i in range(0, len(raw_datasets["train"]), 1000)
)
```

Эта строка кода не получает никаких элементов из набора данных; она просто создает объект, который можно использовать в цикле Python `for`. Тексты будут загружаться только тогда, когда они вам нужны (то есть когда вы находитесь на том шаге цикла `for`, где они требуются), и за один раз будет загружено только 1 000 текстов. Таким образом, вы не исчерпаете всю память, даже если обрабатываете огромный набор данных.

Проблема с объектом-генератором заключается в том, что он может быть использован только один раз. Поэтому вместо того, чтобы выдать нам список первых 10 цифр дважды:

```py
gen = (i for i in range(10))
print(list(gen))
print(list(gen))
```

мы получаем его один раз, а затем пустой список:

```python out
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
```

Поэтому мы определяем функцию, которая возвращает генератор:

```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()
```

Вы также можете определить свой генератор внутри цикла `for`, используя оператор `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"]
```

который выдает точно такой же генератор, как и предыдущий, но позволяет использовать более сложную логику, чем при работе с list comprehension.

## Обучение нового токенизатора[[training-a-new-tokenizer]]

Теперь, когда у нас есть корпус в виде итератора батчей текстов, мы готовы обучить новый токенизатор. Для этого нам сначала нужно загрузить токенизатор, который мы хотим использовать в паре с нашей моделью (здесь GPT-2):

```py
from transformers import AutoTokenizer

old_tokenizer = AutoTokenizer.from_pretrained("gpt2")
```

Несмотря на то, что мы собираемся обучить новый токенизатор, это хорошая идея сделать это, не начиная все с нуля. Таким образом, нам не придется ничего уточнять об алгоритме токенизации или специальных токенах, которые мы хотим использовать; наш новый токенизатор будет точно таким же, как GPT-2, и единственное, что изменится, - это словарный запас, который будет определен в результате обучения на нашем корпусе.

Сначала давайте посмотрим, как будет работать этот токенизатор с примером функции:

```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']
```

Этот токенизатор имеет несколько специальных символов, таких как `Ġ` и `Ċ`, которые обозначают пробелы и новые строки, соответственно. Как мы видим, это не слишком эффективно: токенизатор возвращает отдельные токены для каждого пробела, в то время как он мог бы группировать уровни отступов (поскольку наборы из четырех или восьми пробелов будут очень часто встречаться в коде). Он также немного странно разделил имя функции, не ожидая увидеть слова с символом `_`.

Давайте обучим новый токенизатор и посмотрим, решит ли он эти проблемы. Для этого мы воспользуемся методом `train_new_from_iterator()`:

```py
tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000)
```

Выполнение этой команды может занять много времени, если ваш корпус очень большой, но для данного набора данных с 1,6 ГБ текстов она работает молниеносно (1 минута 16 секунд на процессоре AMD Ryzen 9 3900X с 12 ядрами).

Обратите внимание, что `AutoTokenizer.train_new_from_iterator()` работает только в том случае, если используемый вами токенизатор является "быстрым" токенизатором. Как вы увидите в следующем разделе, библиотека 🤗 Transformers содержит два типа токенизаторов: одни написаны исключительно на Python, а другие (быстрые) опираются на библиотеку 🤗 Tokenizers, которая написана на языке программирования [Rust](https://www.rust-lang.org). Python - это язык, который чаще всего используется для приложений data science и deep learning, но когда что-то нужно распараллелить для быстроты, это приходится писать на другом языке. Например, матричные умножения, которые лежат в основе вычислений модели, написаны на CUDA, оптимизированной библиотеке языка C для GPU.

Обучение совершенно нового токенизатора на чистом Python было бы мучительно медленным, поэтому мы разработали библиотеку 🤗 Tokenizers. Обратите внимание, что так же как вам не нужно было изучать язык CUDA, чтобы выполнить свою модель на батче входных данных на GPU, вам не понадобится изучать Rust, чтобы использовать быстрый токенизатор. Библиотека 🤗 Tokenizers предоставляет привязки к Python для многих методов, которые внутренне вызывают некоторые части кода на Rust; например, для распараллеливания обучения вашего нового токенизатора или, как мы видели в [Главе 3](../chapter3/1), токенизации батча входных данных.

В большинстве моделей Transformer доступен быстрый токенизатор (есть некоторые исключения, о которых вы можете узнать [здесь](https://huggingface.co/transformers/#supported-frameworks)), а API `AutoTokenizer` всегда выбирает быстрый токенизатор, если он доступен. В следующем разделе мы рассмотрим некоторые другие особенности быстрых токенизаторов, которые будут очень полезны для таких задач, как классификация токенов и ответы на вопросы. Однако прежде чем погрузиться в эту тему, давайте попробуем наш новый токенизатор на предыдущем примере:

```py
tokens = tokenizer.tokenize(example)
tokens
```

```python out
['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`',
 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb']
```

Здесь мы снова видим специальные символы `Ġ` и `Ċ`, обозначающие пробелы и новые строки, но мы также видим, что наш токенизатор выучил некоторые токены, которые очень специфичны для корпуса функций Python: например, есть токен `ĊĠĠĠ`, который обозначает отступ, и токен `Ġ"""`, который обозначает три кавычки, с которых начинается doc-строка. Токенизатор также правильно разделил имя функции на `_`. Это довольно компактное представление; для сравнения, использование токенизатора простого английского языка для того же примера даст нам более длинное предложение:

```py
print(len(tokens))
print(len(old_tokenizer.tokenize(example)))
```

```python out
27
36
```

Давайте рассмотрим другой пример:

```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', 'ĊĠĠĠĠ']
```

В дополнение к токену, соответствующему отступу, здесь мы также видим токен для двойного отступа: `ĊĠĠĠĠĠĠĠĠĠ`. Специальные слова Python, такие как `class`, `init`, `call`, `self` и `return`, обрабатываются как один токен, и мы видим, что наряду с разделением на `_` и `.` токенизатор правильно разделяет даже имена с camel-case: `LinearLayer` обрабатывается как `["ĠLinear", "Layer"]`.

## Сохранение токенизатора[[saving-the-tokenizer]]

Чтобы убедиться, что мы сможем использовать его позже, нам нужно сохранить наш новый токенизатор. Как и для моделей, это делается с помощью метода `save_pretrained()`:

```py
tokenizer.save_pretrained("code-search-net-tokenizer")
```

В результате будет создана новая папка с именем *code-search-net-tokenizer*, в которой будут содержаться все файлы, необходимые токенизатору для загрузки. Если вы хотите поделиться этим токенизатором со своими коллегами и друзьями, вы можете загрузить его на Hub, войдя в свою учетную запись. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом:

```python
from huggingface_hub import notebook_login

notebook_login()
```

Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face. Если вы работаете не в блокноте, просто введите следующую строку в терминале:

```bash
huggingface-cli login
```

После того как вы авторизовались, вы можете опубликовать свой токенизатор, выполнив следующую команду:

```py
tokenizer.push_to_hub("code-search-net-tokenizer")
```

Это создаст новое хранилище в вашем пространстве имен с именем `code-search-net-tokenizer`, содержащее файл токенизатора. Затем вы можете загрузить токенизатор где угодно с помощью метода `from_pretrained()`:

```py
# Замените "huggingface-course" ниже своим реальным пространством имен, чтобы использовать свой собственный токенизатор
tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer")
```

Теперь вы готовы обучить языковую модель с нуля и дообучить ее в соответствии с поставленной задачей! Мы займемся этим в [Главе 7](../chapter7), но сначала в этой главе мы рассмотрим быстрые токенизаторы и подробно изучим, что происходит при вызове метода `train_new_from_iterator()`.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter6/2.mdx" />

### Введение
https://huggingface.co/learn/course/ru/chapter5/1.md

# Введение

В [главе 3](../chapter3/1) вы поверхностно ознакомились с библиотекой 🤗 Datasets и увидели три главных шага для использования ее в процессе fine-tuning: 

1. Загрузить датасет из Hugging Face Hub.
2. Произвести препроцессинг с помощью `Dataset.map()`.
3. Загрузить и вычислить метрики. 

Но это лишь малая часть того, на что способна 🤗 Datasets! В этой главе мы углубимся в библиотеку и попутно мы найдем ответы на следующие вопросы:

* Что делать, когда нужного набора данных нет в Hub?
* Как вы можете разделиить датасет? (Что если вам _действительно_ нужно использовать Pandas?)
* Что делать, когда ваш набор данных огромен и «расплавит» оперативную память вашего ноутбука?
* Что, черт возьми, такое «отображение памяти» (memory mapping) и Apache Arrow?
* Как вы можете создать свой собственный датасет и отправить его в Hub?

Принципы, которые вы изучите в этой главе, подготовят вас к более глубокому использованию токенизации и fine-tuning'а моделей в [главе 6](../chapter6) и [главе 7](../chapter7) – заваривайте кофе и мы начинаем!

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter5/1.mdx" />

### Препарируем 🤗 Datasets
https://huggingface.co/learn/course/ru/chapter5/3.md

# Препарируем 🤗 Datasets

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter5/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter5/section3.ipynb"},
]} />

В большинстве случаев данные, с которыми вы будете работать, не будут идеально подготовлены для обучения моделей. В этом разделе мы исследуем различные функции библиотеки 🤗 Datasets для подготовки данных. 

<Youtube id="tqfSFcPMgOI"/>

## Управление данными

Как и в Pandas, 🤗 Datasets предоставляет несколько функция для управления содержимым объектов `Dataset` и `DatasetDict`. Мы уже познакомились с методом  `Dataset.map()` в [главе 3](../chapter3/1), а далее мы посмотрим на другие функции, имеющиеся в нашем распоряжении. 

Для этого примера мы будем использовать датасет [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29), расположенный на сервере [UC Irvine Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) и содержащий отзывы пациентов на различные лекарства, сведения о состоянии пациентов и рейтинг удовлетворенности, выраженный в 10-балльной шкале. 

Для начала необходимо скачать и разархивировать датасет, мы используем для этого команды `wget` и `unzip`: 

```py
!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip"
!unzip drugsCom_raw.zip
```

Файл TSV - это просто разновидность CSV файла, содержащий табуляции вместо запятых в качестве разделителя, а значит мы можем его загрузить с помощью скрипта `csv` и аргумента `delimiter` через функцию `load_dataset()`: 

```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")
```

Хорошей практикой при исследовании данных является взятие небольшого случайного подмножества для понимания типов данных и их особенностей. В библиотеке 🤗 Datasets мы можем сделать случайную выборку путем последовательного вызова функций `Dataset.shuffle()` и `Dataset.select()`: 

```py
drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000))
# Peek at the first few examples
drug_sample[:3]
```

```python out
{'Unnamed: 0': [87571, 178045, 80482],
 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'],
 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'],
 'review': ['"like the previous person mention, I&#039;m a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"',
  '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."',
  '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure.  I had severe knee and ankle pain which completely went away after taking Mobic.  I attempted to stop the medication however pain returned after a few days."'],
 'rating': [9.0, 3.0, 10.0],
 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'],
 'usefulCount': [36, 13, 128]}
```

Заметьте, что мы зафикисировали переменную `seed` для воспроизводимости результатов.  `Dataset.select()` ожидает на вход итерируемый объект, содержащий индексы, поэтому мы передали `range(1000)` для взятия первых 1000 объектов перемешанного датасета. Для этой подвыборки мы можем сразу увидеть некоторые особенности в данных: 

* Колонка `Unnamed: 0` выглядит как обезличенный ID для каждого пациента.
* Колонка `condition` включает в себя смесь лейблов в нижнем и верхнем регистре.
* Отзывы переменной длины и содержат смесь разделителей текста (`\r\n`) и HTML-кодов (например, `&\#039;`). 

Давайте посмотрим, как мы можем использовать 🤗 Datasets для обработки этих особенностей. Чтобы проверить, что наша гипотеза об уникальности справедлива, мы можем использовать функцию `Dataset.unique()` для проверки, что число ID совпадает с числом строк в обоих датасетах (обучающем и тестовом): 

```py
for split in drug_dataset.keys():
    assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0"))
```

По всей видимости, наша гипотеза подтвердилась, так что перейдем к очистке датасета. Для начала переименуем `Unnamed: 0` во что-то более интерпретируемое. Мы можем использовать функцию `DatasetDict.rename_column()` для переименования столбцы на обоих сплитах (обучающем и тестовом): 

```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
    })
})
```

<Tip>

✏️ **Попробуйте!** Используйте функцию `Dataset.unique()` для поиска числа уникальных лекарств и состояний пациентов в обучающем и тестовом сплитах.

</Tip>

Далее нормализуем все лейблы столбца `condition` с применением `Dataset.map()`. Так же, как мы делали токенизацию в [главе 3](../chapter3/1), мы можем определить простую функцию, которая будет применения для всех строк каждого сплита в `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'
```

О нет! При запуске этой функции мы столкнулись с проблемой! Из ошибки мы можем сделать вывод, что некоторые записи в колонке `condition` являются `None`, которые не могут быть приведены к нижнему регистру как обычные строковые типы данных. Давайте удалим эти строки с помощью `Dataset.filter()`, которая работает схожим с `Dataset.map()` образом и принимает на вход один экземпляр датасета. Вместо реализации собственной функции: 

```py
def filter_nones(x):
    return x["condition"] is not None
```

и вызова этой функции `drug_dataset.filter(filter_nones)`, мы можем сделать то же самое с помощью _lambda-функции_. В Python лямбда-функции - это небольшие функции, которые вы можете определить без явного их именования. Общий вид, которым их можно задать: 

```
lambda <arguments> : <expression>
```

где `lambda` - одно из [ключевых](https://docs.python.org/3/reference/lexical_analysis#keywords) слов Python, а `<arguments>` - список или множество разделенных запятой значений, которые пойдут на вход функции, и `<expression>` задает операции, которые вы хотите применить к аргументам. Например, мы можем задать простую лямбда-функцию, которая возводит в квадрат числа: 

```
lambda x : x * x
```

Чтобы применить эту функцию, мы должны заключить ее и аргументы в скобки: 

```py
(lambda x: x * x)(3)
```

```python out
9
```

По аналогии мы можем задать лямбда-функцию с несколькими аргументами, которые необходимо разделить запятыми. Например, мы можем вычислить площадь треугольника следующим образом: 

```py
(lambda base, height: 0.5 * base * height)(4, 8)
```

```python out
16.0
```

Лямбда-функции удобны, когда вы хотите определить маленькие одноразовые функции (для более подробной информации об этих функциях мы рекомендуем изучить превосходную публикацию [Real Python tutorial](https://realpython.com/python-lambda/) за авторством Andre Burgaud). В контексте библиотеки 🤗 Datasets мы можем использовать лямбда-функции для задания простых операций `map` и `filter`, давайте попробуем устранить `None`-записи из нашего датасета:

```py
drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None)
```

После удаления `None` записей, мы можем нормализовать колонку `condition`: 

```py
drug_dataset = drug_dataset.map(lowercase_condition)
# Check that lowercasing worked
drug_dataset["train"]["condition"][:3]
```

```python out
['left ventricular dysfunction', 'adhd', 'birth control']
```

Заработало! Сейчас мы очистили лейблы, давайте теперь посмотрим на то, как можно очистить непосредственно отзывы. 

## Создание новых столбцов

Всякий раз, когда вы имеете дело с отзывами клиентов, хорошей практикой является проверка количества слов в каждом отзыве. Обзор может состоять всего из одного слова, например «Отлично!» или быть полномасштабным эссе с тысячами слов, и в зависимости от варианта использования вам нужно будет по-разному справляться с этими случаями. Чтобы вычислить количество слов в каждом обзоре, мы будем использовать грубую эвристику, основанную на разбиении каждого текста по пробелам.

Зададим простую функцию, которая вычисляет число слов в каждом отзыве: 

```py
def compute_review_length(example):
    return {"review_length": len(example["review"].split())}
```

В отличие от функции `lowercase_condition()`, `compute_review_length()` возвращает словарь, чьи ключи не соответствуют ни одному названию колонки в нашем датасете. В этом случае при исполнении  `compute_review_length()` (переданного в `Dataset.map()`) функция будет применена ко всем строкам в датасете и создаст новый столбец с именем `review_length`:  

```py
drug_dataset = drug_dataset.map(compute_review_length)
# Посмотрим на первый объект обучающей части датасета
drug_dataset["train"][0]
```

```python out
{'patient_id': 206461,
 'drugName': 'Valsartan',
 'condition': 'left ventricular dysfunction',
 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"',
 'rating': 9.0,
 'date': 'May 20, 2012',
 'usefulCount': 27,
 'review_length': 17}
```

Как и ожадалось, мы видим колонку с именем `review_length`, которая добавлена к нашему обучающему датасету. Мы можем отсортировать по этой колонке наш датасет с помощью функции `Dataset.sort()` и посмотреть на «экстремальные» значения: 

```py
drug_dataset["train"].sort("review_length")[:3]
```

```python out
{'patient_id': [103488, 23627, 20558],
 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'],
 'condition': ['birth control', 'muscle spasm', 'pain'],
 'review': ['"Excellent."', '"useless"', '"ok"'],
 'rating': [10.0, 1.0, 6.0],
 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'],
 'usefulCount': [5, 2, 10],
 'review_length': [1, 1, 1]}
```

Как и ожидалось, некоторые отзывы содержат одно слово, хотя это и может быть допустимо для задачи оценки тональности текста, вряд ли будет полезно если мы хотим предсказывать состояние пациента. 

<Tip>

🙋 Альтернативный вариант добавления нового столбца в датасет – использовать функцию `Dataset.add_column()`. Она позволяет создать новый столбец из Python-списка или NumPy-массива, что может быть удобно, если функция `Dataset.map()` не очень подходит для вашего случая.

</Tip>

Давайте применим функцию `Dataset.filter()` для удаления отзывов, содержащих меньше 30 слов. Схожим образом мы применяли её для столбца  `condition`: мы можем отфильтровать отзывы, в которых число слов меньше порога:

```py
drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30)
print(drug_dataset.num_rows)
```

```python out
{'train': 138514, 'test': 46108}
```

Как вы можете увидеть, эта функция удалила около 15% отзывов из наших исходных обучающих и тестовых наборов данных. 

<Tip>

✏️ **Попробуйте!** Используйте функцию `Dataset.sort()` для проверки наиболее длинных отзывов. Изучите [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) чтобы понять, какой аргумент нужно передать в функцию, чтобы сортировка произошла в убывающем порядке.

</Tip>

Последняя вещь, которую нам необходимо сделать, это справиться с присутствием HTML-кодами символов в наших отзывах. Мы можем использовать модуль `html` и метод `unescape()` чтобы избавиться от них: 

```py
import html

text = "I&#039;m a transformer called BERT"
html.unescape(text)
```

```python out
"I'm a transformer called BERT"
```

Для этого будем использовать `Dataset.map()` на всем нашем корпусе текстов: 

```python
drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])})
```

Как видите, метод `Dataset.map()` крайне полезен для препроцессинга данных -- хотя мы и воспользовались только малой частью его возможностей!

## Суперспособности метода `map()`

Метод `Dataset.map()` принимает аргумент `batched`, который, если установлен в значение `True`, заставляет его сразу отправлять батч элементов в функцию `map()` (размер батча можно настроить, но по умолчанию он равен 1000). Например, предыдущая функция `map()`, которая экранировала весь HTML-код, требовала некоторого времени для запуска (вы можете узнать время взглянув на индикаторы выполнения процесса). Мы можем ускорить это, обрабатывая несколько элементов одновременно, используя list comprehension.

Когда вы указываете `batched=True`, функция получает словарь с полями набора данных, но каждое значение теперь представляет собой _список значений_, а не просто одно значение. Возвращаемое значение `Dataset.map()` должно быть одинаковым: словарь с полями, которые мы хотим обновить или добавить в наш набор данных, и список значений. Например, вот еще один способ устранить все символы HTML, но с использованием `batched=True`:

```python
new_drug_dataset = drug_dataset.map(
    lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True
)
```

Если вы запустите этот код в блокноте, вы увидите, что эта команда выполняется намного быстрее, чем предыдущая. И это не потому, что наши отзывы уже были HTML-экранированными — если вы повторно выполните инструкцию из предыдущего раздела (без `batched=True`), это займет столько же времени, сколько и раньше. Это связано с тем, что обработка списков обычно выполняется быстрее, чем выполнение того же кода в цикле `for`, мы также повышаем производительность за счет одновременного доступа к множеству элементов, а не по одному.

Использование `Dataset.map()` с `batched=True` – хороший способ «разблокировать» скоростные ограничения "быстрых" токенизаторов, с которыми мы познакомимся в [главе 6](../chapter6), которые могут быстро токенизировать большие списки текста. Например, чтобы токенизировать все отзывы на лекарства с помощью быстрого токенизатора, мы можем использовать функцию, подобную этой:  

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")


def tokenize_function(examples):
    return tokenizer(examples["review"], truncation=True)
```

Как вы видели в [главе 3](../chapter3/1), мы можем передать один или несколько элементов в токенизатор, так что мы можем использовать эту функцию без параметра `batched=True`. Давайте воспользуемся этой возможностью и сравним производительность. В ноутбуке можно замерить время выполнения функции путем добавления `%time` перед строкой кода, время исполнения которой вы хотите измерить: 

```python no-format
%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True)
```

Также присутствует возможность измерить время выполнения всей ячейки: нужно заменить `%time` на `%%time` в начале ячейки. На нашем оборудовании это заняло 10.8 секунд. Это значение расположено после слов "Wall time".

<Tip>

✏️ **Попробуйте!** Выполните эту же инструкцию с и без параметра `batched=True`, затем попробуйте сделать это с "медленным" токенизатором (добавьте `use_fast=False` в метод `AutoTokenizer.from_pretrained()`) и посмотрите, какие значения вы получите на своем оборудовании.

</Tip>

Вот результаты, которые мы получили без и с применением батчинга, и двумя разными по скорости токенизаторами: 

Options         | Fast tokenizer | Slow tokenizer
:--------------:|:--------------:|:-------------:
`batched=True`  | 10.8s          | 4min41s
`batched=False` | 59.2s          | 5min3s

По результатам видно, что использование быстрого токенизатора с параметром `batched=True` приводит к ускорению выполнения в 30 раз – это потрясающе! Это главная причина, почему быстрые токенизаторы применяются по умолчанию при использовании класса `AutoTokenizer` (и почему они называются "быстрыми"). Возможность достичь такой скорости выполнения достигается засчет исполнения кода токенизаторов на языке Rust, который легко позволяет распараллелить выполнение кода. 

Параллелизация также позволяет почти в 6 раз ускорить быстрые токенизаторы с использованием `batched=True`: вы не можете пареллелизовать едничную операцию токенизации, но когда вы токенизируете много различных текстов одновременно, вы можете распределить выполнение на несколько процессов, каждый из которых будет отвечать за собственный текст. 

`Dataset.map()` также обладает возможностями параллелизации. Поскольку метод не реализован на Rust, он не позволят "медленному" токенизатору "догнать" быстрый, но все же может быть полезен (особенно если вы используете токенизатор, у которого нет быстрой версии). Чтобы включить многопроцессорность, используйте аргумент `num_proc` и укажите количество процессов, которые будут использоваться в вашем вызове `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)
```

Вы можете поэкспериментировать и выяснить, какое число `num_proc` даст наилучший результат, в нашем случае значение 8 стало оптимальным. Вот значения, которые мы получили с и без использования мультипроцессинга: 

Options         | Fast tokenizer | Slow tokenizer
:--------------:|:--------------:|:-------------:
`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

Это гораздо более разумные результаты для "медленного" токенизатора, но производительность быстрого токенизатора также существенно выросла. Однако, обратите внимание, что это не всегда так — для значений `num_proc`, отличных от 8, наши тесты показали, что быстрее использовать `batched=True` без этой опции. Как правило, мы не рекомендуем использовать мультипроцессинг Python для "быстрых" токенизаторов с параметром `batched=True`.

<Tip>

Использование `num_proc` для ускорения обработки обычно отличная идея, но только в тех случаях, когда функция сама по себе не производит никакой параллелизации. 

</Tip>

Объединение всей этой функциональности во всего лишь один метод само по себе прекрасно, но это еще не все! Используя `Dataset.map()` и `batched=True` вы можете поменять число элементов в датасете. Это очень полезно во множестве ситуаций, например, когда вы хотите создать несколько обучающих признаков из одного экземпляра текста. Мы воспользуеся этой возможностью на этапе препроцессинга для нескольких NLP-задач, которые рассмотрим в [главе 7](../chapter7)

<Tip>

💡 В машинном обучении экземпляром (объектом, элементом выборки) является множество _признаков_, которые мы должны подать на вход модели. В некоторых контекстах это множество признаков будет множеством колонок в `Dataset`, а в других (как в текущем примере или в задачах ответов на вопросы) признаки будут софрмированы из одного столбца. 

</Tip>

Давайте посмотрим как это работает! В этом примере мы токенизируем наши тексты и обрежем их до максимальной длины в 128, однако мы попросим токенизатор вернуть нам *все* получившиеся токены, а не только начальные. Это может быть сделано с помощью параметра `return_overflowing_tokens=True`: 

```py
def tokenize_and_split(examples):
    return tokenizer(
        examples["review"],
        truncation=True,
        max_length=128,
        return_overflowing_tokens=True,
    )
```

Давайте протестируем это на одном тексте прежде, чем использовать `Dataset.map()` на всем датасете: 

```py
result = tokenize_and_split(drug_dataset["train"][0])
[len(inp) for inp in result["input_ids"]]
```

```python out
[128, 49]
```

Итак, наш первый текст в обучающей части выборки стал состоять из двух признаков, т.к. токенизатор токенизировал не только первые 128 элементов, но и оставшиеся 49 тоже. Давайте применим токенизатор ко всем элементам датасета!

```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
```

О, нет! Не сработало! Почему? Посмотрим на ошибку: несовпадение в длинах, один из которых длиной 1463, а другой – 1000. Если вы обратитесь в [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) `Dataset.map()`, вы можете увидеть, что одно из этих чисел – число объектов, поданных на вход функции, а другое –

Проблема заключается в том, что мы пытаемся смешать два разных датасета разной размерности: число колонок датасета `drug_dataset` равняется 1000, а нужный нам `tokenized_dataset` имеет 1463 колонки. Чтобы избежать этой ошибки, необходимо удалить несколько столбцов из старого датасета и сделать оба датасета одинакового размера. Мы можем достичь этого с помощью аргумента `remove_columns`: 

```py
tokenized_dataset = drug_dataset.map(
    tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names
)
```

Теперь это работает без ошибок. Мы можем проверить, что наш новый датасет имеет бОльшее число элементов, просто сравним длины датасетов: 

```py
len(tokenized_dataset["train"]), len(drug_dataset["train"])
```

```python out
(206772, 138514)
```

Мы упоминали о том, что мы можем справиться с несовпадением длин путем исправления числа колонок старого датасета. Чтобы сделать это, нам необходимо получить поле `overflow_to_sample_mapping`, которое вернет нам токенизатор, если мы зададим аргумент `return_overflowing_tokens=True`. Это даст нам необходимое соответствие между индексом новых и старых признаков. После этого мы сможем ассоциировать каждый ключ нашего оригинального датасета со списком значений нужного размера, повторяя значения каждого примера столько раз, сколько он генерирует новые функции:

Для этого нам понадобится поле `overflow_to_sample_mapping`, которое возвращает токенизатор, когда мы устанавливаем `return_overflowing_tokens=True`. Это дает нам сопоставление индекса новой функции с индексом выборки, из которой он произошел. Используя это, мы можем связать каждый ключ, присутствующий в нашем исходном наборе данных, со списком значений нужного размера, повторяя значения каждого примера столько раз, сколько он генерирует новые функции:

```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
```

Мы можем убедиться, что это сработало в `Dataset.map()` и без удаления старых столбцов: 

```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
    })
})
```

Мы получаем то же количество признаков, что и раньше, но здесь мы сохранили все старые поля. Если они вам нужны для некоторой постобработки после применения вашей модели, вы можете использовать этот подход.

Теперь вы видели, как 🤗 Datasets можно использовать для предварительной обработки набора данных различными способами. Хотя функции обработки 🤗 Datasets покроют большую часть ваших потребностей в обучении модели,
могут быть случаи, когда вам нужно будет переключиться на Pandas, чтобы получить доступ к более мощным функциям, таким как `DataFrame.groupby()` или API высокого уровня для визуализации. К счастью, 🤗 Datasets предназначены для взаимодействия с такими библиотеками, как Pandas, NumPy, PyTorch, TensorFlow и JAX. Давайте посмотрим, как это работает.

## От `Dataset`а к `DataFrame`ам и назад

<Youtube id="tfcY1067A5Q"/>

Для включения конвертации между различными библиотеками 🤗 Datasets предоставляет функцию `Dataset.set_format()`. Эта функция только изменяет _выходной формат_ датасета, так что вы можете переключиться на другой формат не изменяя саму _структуру данных_, которая остается Apache Arrow. Смена формата происходит in place. Для демонстрации давайте попробуем сконвертировать наш датасет в формат Pandas: 

```py
drug_dataset.set_format("pandas")
```

Теперь при обращении к элементам датасета мы будем получать `pandas.DataFrame` вместо словаря: 

```py
drug_dataset["train"][:3]
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>patient_id</th>
      <th>drugName</th>
      <th>condition</th>
      <th>review</th>
      <th>rating</th>
      <th>date</th>
      <th>usefulCount</th>
      <th>review_length</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>95260</td>
      <td>Guanfacine</td>
      <td>adhd</td>
      <td>"My son is halfway through his fourth week of Intuniv..."</td>
      <td>8.0</td>
      <td>April 27, 2010</td>
      <td>192</td>
      <td>141</td>
    </tr>
    <tr>
      <th>1</th>
      <td>92703</td>
      <td>Lybrel</td>
      <td>birth control</td>
      <td>"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..."</td>
      <td>5.0</td>
      <td>December 14, 2009</td>
      <td>17</td>
      <td>134</td>
    </tr>
    <tr>
      <th>2</th>
      <td>138000</td>
      <td>Ortho Evra</td>
      <td>birth control</td>
      <td>"This is my first time using any form of birth control..."</td>
      <td>8.0</td>
      <td>November 3, 2015</td>
      <td>10</td>
      <td>89</td>
    </tr>
  </tbody>
</table>

Давайте создадим `pandas.DataFrame` для всего обучающего множества, выбрав все элементы из `drug_dataset["train"]`:

```py
train_df = drug_dataset["train"][:]
```

<Tip>

🚨 Внутри `Dataset.set_format()` изменяет формат, возвращаемый методом `__getitem__()`. Это означает, что когда мы хотим создать новый объект, например, `train_df`, из `Dataset`, формата `"pandas"`, мы должны сделать slice всего датасета и получить `pandas.DataFrame`. Вы можете проверить, что тип `drug_dataset["train"]` – формата `Dataset`, несмотря на выходной формат (который станет `pandas.DataFrame`). 

</Tip>

Начиная с этого момента мы можем использовать всю функциональность Pandas. Например, мы можем иначе посчитать расределение `condition` среди нашей выборки: 

```py
frequencies = (
    train_df["condition"]
    .value_counts()
    .to_frame()
    .reset_index()
    .rename(columns={"index": "condition", "count": "frequency"})
)
frequencies.head()
```

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>condition</th>
      <th>frequency</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>birth control</td>
      <td>27655</td>
    </tr>
    <tr>
      <th>1</th>
      <td>depression</td>
      <td>8023</td>
    </tr>
    <tr>
      <th>2</th>
      <td>acne</td>
      <td>5209</td>
    </tr>
    <tr>
      <th>3</th>
      <td>anxiety</td>
      <td>4991</td>
    </tr>
    <tr>
      <th>4</th>
      <td>pain</td>
      <td>4744</td>
    </tr>
  </tbody>
</table>


И как только мы закончим наш анализ Pandas, мы всегда можем создать новый объект `Dataset` с помощью функции `Dataset.from_pandas()` следующим образом:

```py
from datasets import Dataset

freq_dataset = Dataset.from_pandas(frequencies)
freq_dataset
```

```python out
Dataset({
    features: ['condition', 'frequency'],
    num_rows: 819
})
```

<Tip>

✏️ **Попробуйте!** Вычислите средний рейтинг по подному лекарству и сохраните результат в новом датасете типа `Dataset`.

</Tip>

На этом мы заканчиваем наш обзор различных техник препроцессинга, доступных в 🤗 Datasets. Чтобы завершить этот раздел, давайте создадим валидационную часть выборки. Прежде, чем сделать это, мы сбросим формат `drug_dataset` обратно к `"arrow"`: 

```python
drug_dataset.reset_format()
```

## Создание валидационной выборки

Хотя у нас есть тестовая часть датасета, которую мы могли бы использовать для оценки качества модели, хорошей практикой является оставить тестовое множество нетронутым и создать отдельный набор для проверки. Как только вы будете довольны производительностью своих моделей на валидационном датасете, вы можете выполнить окончательную проверку работоспособности на тестовом. Этот процесс помогает снизить риск переобучения модели и промышленного применения модели, которая не работает на реальных данных.

🤗 Наборы данных предоставляют функцию `Dataset.train_test_split()`, основанную на известной функциональности из `scikit-learn`. Давайте используем её, чтобы разделить наш обучающий датасет непосредственно на обучающий и валидационный (мы устанавливаем аргумент `seed` для воспроизводимости):

```py
drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42)
# Переименуем "test"  в "validation"
drug_dataset_clean["validation"] = drug_dataset_clean.pop("test")
# Добавим "test" в наш `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
    })
})
```

Отлично, теперь мы подготовили датасет, на котором можно обучить некоторые модели. В [разделе 5](../chapter5/5) мы покажем, как загрузить датасеты на Hugging Face Hub, а пока закончим наш обзор и посмотрим несколько способов сохранения датасетов на локальный компьютер. 

## Сохранение датасетов

<Youtube id="blF9uxYcKHo"/>

Несмотря на то, что 🤗 Datasets будут кэшировать все загруженные датасеты и операции, которые над ними выполняются, будут случаи, когда вам будет необходимо сохранить датасет на диск (например, если кэш был очищен). Как показано в таблице ниже,  🤗 Datasets предоставляет три главных функции для сохранения датасета в разных форматах. 

| Data format |        Function        |
| :---------: | :--------------------: |
|    Arrow    | `Dataset.save_to_disk()` |
|     CSV     |    `Dataset.to_csv()`    |
|    JSON     |   `Dataset.to_json()`    |

Для примера сохраним наш очищенный датасет в формате Arrow: 

```py
drug_dataset_clean.save_to_disk("drug-reviews")
```

Эта функция создаст директорию следующей структуры: 

```
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
```

где мы можем увидеть каждый сплит данных, ассоциированный с собственной таблицей *dataset.arrow*, и некоторыми метаданными, хранящимися в файлах *dataset_info.json* и *state.json*. Вы можете рассматривать формат Arrow просто как таблицу, которая оптимизирована для построения высокопроизводительных приложений для обработки и передачи больших датасетов. 

После сохранения датасета мы можем загрузить его с использованием функции `load_from_disk()` следующим образом: 

```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
    })
})
```

Для форматов CSV и JSON мы должны сохранять каждый сплит как отдельный файл. Один из способов это сделать – проитерироваться по ключам и значениям в объекте `DatasetDict`: 

```py
for split, dataset in drug_dataset_clean.items():
    dataset.to_json(f"drug-reviews-{split}.jsonl")
```

Этот код сохранит каждый блок нашего датасета в формате [JSON Lines](https://jsonlines.org), где каждая строка будет сохранена как JSON-объект. Вот как будет выглядеть первый элемент нашей выборки: 

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

Мы можем использовать приёмы из [раздела 2](../chapter5/2) для загрузки JSON-файлов: 

```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)
```

Вот и все, что нужно для нашего экскурса при работе с 🤗 Datasets! Мы очистили датасет для обучения модели, вот некоторые идеи, которые вы могли бы реализовать самостоятельно: 

1. Примените знания из [раздела 3](../chapter3/1) для обучения классификатора, который может предсказывать состояние пациента по отзыву на лекарство.
2. Используйте pipeline `summarization` из [раздела 1](../chapter1/1)для генерации саммари отзывов. 

Далее мы посмотрим, как 🤗 Datasets могут помочь вам в работе с громадными датасетами, которые _невозможно_ обработать на вашем ноутбуке!



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter5/3.mdx" />

### Семантический поиск с помощью FAISS
https://huggingface.co/learn/course/ru/chapter5/6.md

# Семантический поиск с помощью FAISS

{#if fw === 'pt'}

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section6_tf.ipynb"},
]} />

{/if}

В [разделе 5](../chapter5/5) мы создали набор данных о issues и комментариях GitHub из репозитория 🤗 Datasets. В этом разделе мы будем использовать эту информацию для создания поисковой системы, которая поможет нам найти ответы на самые насущные вопросы о библиотеке!

<Youtube id="OATCgQtNX2o"/>

## Использование эмбеддингов для семанического поиска

Как мы видели в [Главе 1](../chapter1/1), языковые модели на основе Transformer представляют каждую лексему в текстовом фрагменте как _эмбеддинг-вектор_. Оказывается, можно «объединить» отдельные вложения, чтобы создать векторное представление для целых предложений, абзацев или (в некоторых случаях) документов. Затем эти вложения можно использовать для поиска похожих документов в корпусе путем вычисления скалярного произведения (или какой-либо другой метрики сходства) между каждым вложением и возврата документов с наибольшим перекрытием.

В этом разделе мы будем использовать вложения для разработки семантической поисковой системы. Эти поисковые системы предлагают несколько преимуществ по сравнению с традиционными подходами, основанными на сопоставлении ключевых слов в запросе с документами.

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/semantic-search.svg" alt="Semantic search."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter5/semantic-search-dark.svg" alt="Semantic search."/>
</div>

## Загрузка и подготовка датасета 

Первое, что нам нужно сделать, это загрузить наш набор данных об issues GitHub, поэтому давайте воспользуемся библиотекой 🤗 Hub для получения URL-адреса, по которому наш файл хранится в Hugging Face Hub:

```py
from huggingface_hub import hf_hub_url

data_files = hf_hub_url(
    repo_id="lewtun/github-issues",
    filename="datasets-issues-with-comments.jsonl",
    repo_type="dataset",
)
```

С URL-адресом, сохраненным в `data_files`, мы можем загрузить удаленный набор данных, используя метод, представленный в [раздел 2](../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
})
```

Здесь мы указали подвыборку `train` по умолчанию в `load_dataset()`, поэтому он возвращает `Dataset` вместо `DatasetDict`. Первым делом нужно отфильтровать запросы на pull-requests, поскольку они, как правило, редко используются для ответов на вопросы пользователей и создают шум в нашей поисковой системе. Как должно быть уже известно, мы можем использовать функцию `Dataset.filter()`, чтобы исключить эти строки из нашего набора данных. Давайте также отфильтруем строки без комментариев, поскольку они не дают ответов на запросы пользователей:

```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
})
```

Мы видим, что в нашем наборе данных много столбцов, большинство из которых нам не нужно для создания нашей поисковой системы. С точки зрения поиска наиболее информативными столбцами являются `title`, `body` и `comments`, а `html_url` содержит нам ссылку на исходную проблему. Давайте воспользуемся функцией `Dataset.remove_columns()`, чтобы удалить остальные столбцы:

```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
})
```

Чтобы создать наши эмбеддинги, мы дополним каждый комментарий заголовком и телом проблемы, поскольку эти поля часто содержат полезную контекстную информацию. Поскольку наш столбец `comments` в настоящее время представляет собой список комментариев для каждой проблемы, нам нужно «развернуть» столбец, чтобы каждая строка состояла из кортежа `(html_url, title, body, comment)`. В Pandas мы можем сделать это с помощью функции [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), которая создает новую строку для каждого элемента в столбце, похожем на список, при копировании всех других значений столбца. Чтобы увидеть это в действии, давайте сначала переключимся на формат Pandas `DataFrame`:

```py
issues_dataset.set_format("pandas")
df = issues_dataset[:]
```

Если мы проверим первую строку в этом `DataFrame`, мы увидим, что есть четыре комментария, связанных с этой проблемой:

```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...']
```

Когда мы «развернем» `df`, мы ожидаем получить по одной строке для каждого из этих комментариев. Проверим, так ли это:

```py
comments_df = df.explode("comments", ignore_index=True)
comments_df.head(4)
```

<table border="1" class="dataframe" style="table-layout: fixed; word-wrap:break-word; width: 100%;">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>html_url</th>
      <th>title</th>
      <th>comments</th>
      <th>body</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>the bug code locate in ：\r\n    if data_args.task_name is not None...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>1</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>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...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>2</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>cannot connect，even by Web browser，please check that  there is some  problems。</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
    <tr>
      <th>3</th>
      <td>https://github.com/huggingface/datasets/issues/2787</td>
      <td>ConnectionError: Couldn't reach https://raw.githubusercontent.com</td>
      <td>I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...</td>
      <td>Hello,\r\nI am trying to run run_glue.py and it gives me this error...</td>
    </tr>
  </tbody>
</table>

Отлично, мы видим, что строки были скопированы, а столбец `comments` содержит отдельные комментарии! Теперь, когда мы закончили с Pandas, мы можем быстро вернуться к `Dataset`, загрузив `DataFrame` в память:

```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
})
```

Хорошо, это дало нам несколько тысяч комментариев для работы!


<Tip>

✏️ **Попробуйте!** Посмотрите, сможете ли вы использовать `Dataset.map()`, чтобы развернуть столбец `comments` столбца `issues_dataset` _без_ использования Pandas. Это немного сложно; вы можете найти раздел ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) документации 🤗 Datasets, полезным для этой задачи.

</Tip>

Теперь, когда у нас есть один комментарий в строке, давайте создадим новый столбец `comments_length`, содержащий количество слов в комментарии:

```py
comments_dataset = comments_dataset.map(
    lambda x: {"comment_length": len(x["comments"].split())}
)
```

Мы можем использовать этот новый столбец для фильтрации коротких комментариев, которые обычно содержат такие слова, как «cc @lewtun» или Thanks!», которые не имеют отношения к нашей поисковой системе. Нет точного числа для выбора порога числа слов, но около 15 слов кажется хорошим началом:

```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
})
```

Немного очистив наш набор данных, давайте соединим название issue, описание и комментарии вместе в новом столбце `text`. Как обычно, мы напишем простую функцию, которую мы можем передать в `Dataset.map()`:

```py
def concatenate_text(examples):
    return {
        "text": examples["title"]
        + " \n "
        + examples["body"]
        + " \n "
        + examples["comments"]
    }


comments_dataset = comments_dataset.map(concatenate_text)
```

Наконец-то мы готовы создать несколько эмбеддингов'! Давайте взглянем.

## Создание текстовых эмбединнгов

В [Главе 2](../chapter2/1) мы видели, что можно получить эмбеддингов токенов с помощью класса AutoModel. Все, что нам нужно сделать, это выбрать подходящую контрольную точку для загрузки модели. К счастью, есть библиотека под названием `sentence-transformers`, предназначенная для создания эмбеддингов. Как описано в [документации](https://www.sbert.net/examples/applications/semantic-search/README.html#симметричный-vs-асимметричный-semantic-search) библиотеки, наш вариант использования является примером _асимметричного семантического поиска_ потому что у нас есть короткий запрос, ответ на который мы хотели бы найти в более длинном документе, например, в комментарии к проблеме. В удобной [таблице обзора модели](https://www.sbert.net/docs/pretrained_models.html#model-overview) в документации указано, что контрольная точка `multi-qa-mpnet-base-dot-v1` имеет лучшую производительность для семантического поиска, поэтому мы будем использовать её для нашего приложения. Мы также загрузим токенизатор, используя ту же контрольную точку:

{#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)
```
Чтобы ускорить процесс построения эмбеддингов, рекомендуется переместить модель и входные данные на устройстве с графическим процессором, поэтому давайте сделаем это сейчас:

```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)
```

Обратите внимание, что мы установили `from_pt=True` в качестве аргумента метода `from_pretrained()`. Это связано с тем, что контрольная точка `multi-qa-mpnet-base-dot-v1` имеет только веса PyTorch, поэтому установка `from_pt=True` автоматически преобразует их в формат TensorFlow для нас. Как видите, переключаться между фреймворками в 🤗 Трансформеры очень просто!

{/if}

Как мы упоминали ранее, мы хотели бы представить каждую запись в нашем корпусе issues GitHub как единый вектор, поэтому нам нужно каким-то образом «объединить» или усреднить наши вложения токенов. Одним из популярных подходов является выполнение *CLS pooling* выходных данных нашей модели, когда мы просто собираем последнее скрытое состояние для специального токена `[CLS]`. Следующая функция поможет нам:

```py
def cls_pooling(model_output):
    return model_output.last_hidden_state[:, 0]
```

Далее мы создадим вспомогательную функцию, которая разметит список документов, поместит тензоры в GPU, передаст их в модель и, наконец, применит CLS pooling к выходным данным:

{#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)
```
Мы можем проверить работу функции, передав ей первую текстовую запись в нашем корпусе и проверив размерности данных на выходе:

```py
embedding = get_embeddings(comments_dataset["text"][0])
embedding.shape
```

```python out
torch.Size([1, 768])
```

Отлично, мы преобразовали первую запись в нашем корпусе в 768-мерный вектор! Мы можем использовать `Dataset.map()`, чтобы применить нашу функцию `get_embeddings()` к каждой строке в нашем корпусе, поэтому давайте создадим новый столбец `embeddings` следующим образом:

```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)
```
Мы можем проверить работу функции, передав ей первую текстовую запись в нашем корпусе и проверив размерности данных на выходе:

```py
embedding = get_embeddings(comments_dataset["text"][0])
embedding.shape
```

```python out
TensorShape([1, 768])
```
Отлично, мы преобразовали первую запись в нашем корпусе в 768-мерный вектор! Мы можем использовать `Dataset.map()`, чтобы применить нашу функцию `get_embeddings()` к каждой строке в нашем корпусе, поэтому давайте создадим новый столбец `embeddings` следующим образом:

```py
embeddings_dataset = comments_dataset.map(
    lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]}
)
```

{/if}

Обратите внимание, что мы преобразовали вложения в массивы NumPy — это потому, что 🤗 Datasets требуют этого формата, когда мы пытаемся проиндексировать их с помощью FAISS, что мы сделаем дальше.


## Использование FAISS для эффективного семантического поиска

Теперь, когда у нас есть датасет с эмбеддингами, нам нужен способ поиска по ним. Для этого мы будем использовать специальную структуру данных из 🤗 Datasets, называемую _FAISS index_. [FAISS](https://faiss.ai/) (сокращение от Facebook AI Similarity Search) — это библиотека, предоставляющая эффективные алгоритмы для быстрого поиска и кластеризации эмбеддингов.

Основная идея FAISS состоит в том, чтобы создать специальную структуру данных, называемую _index_, которая позволяет найти, какие эмбеддинги подобны входным эмбеддингам. Создать индекс FAISS в 🤗 Datasets очень просто — мы используем функцию `Dataset.add_faiss_index()` и указываем, какой столбец нашего набора данных мы хотим проиндексировать:

```py
embeddings_dataset.add_faiss_index(column="embeddings")
```

Теперь мы можем выполнять запросы к этому индексу, выполняя поиск ближайшего соседа с помощью функции `Dataset.get_nearest_examples()`. Давайте проверим это, сначала внедрив вопрос следующим образом:

{#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}

Как и в случае с документами, теперь у нас есть 768-мерный вектор, представляющий запрос, который мы можем сравнить со всем корпусом, чтобы найти наиболее похожие объекты:

```py
scores, samples = embeddings_dataset.get_nearest_examples(
    "embeddings", question_embedding, k=5
)
```

Функция `Dataset.get_nearest_examples()` возвращает набор оценок, которые ранжируют совпадение между запросом и документом, и соответствующий набор образцов (здесь 5 лучших совпадений). Давайте соберем их в `pandas.DataFrame`, чтобы мы могли легко их отсортировать:

```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)
```

Теперь мы можем пройтись по первым нескольким строкам, чтобы увидеть, насколько наш запрос соответствует имеющимся комментариям:

```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
==================================================
"""
```

Неплохо! Наше второе обращение, кажется, соответствует запросу.

<Tip>

✏️ **Попробуйте!** Создайте свой собственный запрос и посмотрите, сможете ли вы найти ответ в найденных документах. Возможно, вам придется увеличить параметр `k` в `Dataset.get_nearest_examples()`, чтобы расширить поиск.

</Tip>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter5/6.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter5/8.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

Эта глава охватила много вопросов! Не волнуйтесь, если вы не поняли всех деталей; следующие главы помогут вам понять, как все работает внутри.

Однако, прежде чем двигаться дальше, давайте проверим то, что вы узнали в этой главе.
### Из каких источников функция `load_dataset()` в 🤗 Datasets позволяет загружать наборы данных?

<Question
	choices={[
		{
			text: "Локальных, то есть с моего компьютера",
			explain: "Правильно! Вы можете передать пути к локальным файлам в аргумент <code>data_files</code> функции <code>load_dataset()</code> для загрузки локальных наборов данных.",
			correct: true
		},
		{
			text: "Hugging Face Hub",
			explain: "Правильно! Вы можете загружать наборы данных в Hub, указав идентификатор набора данных, например. <code>load_dataset('emotion')</code>.",
			correct: true
		},
		{
			text: "Удаленный сервер",
			explain: "Правильно! Вы можете передать URLs в аргумент <code>data_files</code> фунции  <code>load_dataset()</code>. ",
			correct: true
		},
	]}
/>

### 2. Предположим, вы загружаете одну из задач GLUE следующим образом:

```py
from datasets import load_dataset

dataset = load_dataset("glue", "mrpc", split="train")
```

Какая из следующих команд создаст случайную выборку из 50 элементов из `dataset`?

<Question
	choices={[
		{
			text: "<code>dataset.sample(50)</code>",
			explain: "Это неверно — нет метода <code>Dataset.sample()</code>."
		},
		{
			text: "<code>dataset.shuffle().select(range(50))</code>",
			explain: "Правильный! Как вы видели в этой главе, вы сначала перемешиваете набор данных, а затем выбираете из него подмножества.",
			correct: true
		},
		{
			text: "<code>dataset.select(range(50)).shuffle()</code>",
			explain: "Это неверно — хотя код запустится, он перемешает только первые 50 элементов в наборе данных."
		}
	]}
/>

### 3. Предположим, у вас есть набор данных о домашних питомцах под названием `pets_dataset`, в котором есть столбец `name`, обозначающий имя каждого питомца. Какой из следующих подходов позволит вам отфильтровать набор данных для всех домашних животных, имена которых начинаются с буквы «L»?

<Question
	choices={[
		{
			text: "<code>pets_dataset.filter(lambda x : x['name'].startswith('L'))</code>",
			explain: "Правильно! Использование лямбда-функции Python для этих быстрых фильтров — отличная идея. Можете ли вы придумать другое решение?",
			correct: true
		},
		{
			text: "<code>pets_dataset.filter(lambda x['name'].startswith('L'))</code>",
			explain: "Это неверно — лямбда-функция принимает общую форму <code>lambda *arguments* : *expression*</code>, поэтому в этом случае вам необходимо предоставить аргументы."
		},
		{
			text: "Create a function like <code>def filter_names(x): return x['name'].startswith('L')</code> and run <code>pets_dataset.filter(filter_names)</code>.",
			explain: "Правильно! Как и в случае с <code>Dataset.map()</code>, вы можете передавать явные функции в <code>Dataset.filter()</code>. Это полезно, когда у вас есть сложная логика, которая не подходит для короткой лямбда-функции. Какое из других решений будет работать?",
			correct: true
		}
	]}
/>

### 4. Что такое отображение в память?

<Question
	choices={[
		{
			text: "Сопоставление между ЦП (CPU) и ОЗУ (RAM) графического процессора",
			explain: "Нет, попробуй еще раз!",
		},
		{
			text: "Отображение между RAM и файловой системой компьютера",
			explain: "Правильно! 🤗 Datasets рассматриваtт каждый набор данных как файл с отображением в памяти. Это позволяет библиотеке получать доступ к элементам набора данных и работать с ними без необходимости полной загрузки его в память.",
			correct: true
		},
		{
			text: "Сопоставление между двумя файлами в кэшэ 🤗 Datasets",
			explain: "Нет, попробуй еще раз!"
		}
	]}
/>

### 5. Что из перечисленного ниже является основным преимуществом отображения памяти?

<Question
	choices={[
		{
			text: "Доступ к файлам, отображенным в памяти, быстрее, чем чтение или запись на диск.",
			explain: "Правильно! Это позволяет 🤗 Datasets работать быстро. Однако это не единственное преимущество.",
			correct: true
		},
		{
			text: "Приложения могут получать доступ к сегментам данных в очень большом файле без необходимости сначала считывать весь файл в ОЗУ.",
			explain: "Правильно! Это позволяет 🤗 Datasets загружать многогигабайтные наборы данных на ваш ноутбук, не нагружая ваш процессор. Какие еще преимущества предлагает сопоставление памяти?",
			correct: true
		},
		{
			text: "Он потребляет меньше энергии, поэтому батарея работает дольше.",
			explain: "Это неправильно - попробуй еще раз!"
		}
	]}
/>

### 6. Почему следующий код не работает?

```py
from datasets import load_dataset

dataset = load_dataset("allocine", streaming=True, split="train")
dataset[0]
```

<Question
	choices={[
		{
			text: "Он пытается передать набор данных, который слишком велик для размещения в ОЗУ.",
			explain: "Это неверно — потоковые наборы данных распаковываются на лету, и вы можете обрабатывать наборы данных размером в терабайт с очень небольшим объемом оперативной памяти!",
		},
		{
			text: "Он пытается получить доступ к <code>IterableDataset</code>.",
			explain: "Правильно! <code>IterableDataset</code> — это генератор, а не контейнер, поэтому вы должны получить доступ к его элементам, используя <code>next(iter(dataset))</code>.",
			correct: true
		},
		{
			text: "Набор данных <code>allocine</code> не имеет разделения <code>train</code>.",
			explain: "Это неверно — проверьте [<code>allocine</code> карточку набора данных](https://huggingface.co/datasets/allocine) в Hub, чтобы увидеть, какие разбиения он содержит."
		}
	]}
/>

### 7. Что из перечисленного является основными преимуществами создания карточки датасета?

<Question
	choices={[
		{
			text: "Она предоставляет информацию о предполагаемом использовании и поддерживаемых задачах набора данных, чтобы другие участники сообщества могли принять обоснованное решение об его использовании.",
			explain: "Правильно! Недокументированные наборы данных могут использоваться для обучения моделей, которые могут не отражать намерения создателей наборов данных, или могут создавать модели с неясным юридическим статусом, если они обучены на данных, нарушающих конфиденциальность или лицензионные ограничения. Но это не единственный плюс!",
			correct : true
		},
		{
			text: "Это помогает привлечь внимание к искажениям, присутствующим в корпусе.",
			explain: "Правильно! Почти все наборы данных имеют некоторую форму предвзятости, которая может привести к негативным последствиям в дальнейшем. Знание о них помогает разработчикам моделей понять, как устранять подобные искажения. В чем еще помогают карточки наборов данных?»",
			correct : true
		},
		{
			text: "Это повышает шансы на то, что другие участники сообщества будут использовать мой набор данных.",
			explain: "Правильно! Хорошо написанная карточка набора данных, как правило, приведет к более активному использованию вашего драгоценного датасета. Какие еще могут быть преимущества?",
			correct: true
		},
	]}
/>


### 8. Что такое семантический поиск?

<Question
	choices={[
		{
			text: "Способ поиска точных совпадений между словами в запросе и документами в корпусе",
			explain: "Это неверно — этот тип поиска называется *лексическим поиском*, и это то, что вы обычно видите в традиционных поисковых системах"
		},
		{
			text: "Способ поиска совпадающих документов путем понимания контекстного значения запроса",
			explain: "Правильно! Семантический поиск использует вложенные векторы для представления запросов и документов и использует метрику подобия для измерения степени совпадения между ними. Как еще вы могли бы это описать?",
			correct: true
		},
		{
			text: "Способ повысить точность поиска",
			explain: "Правильно! Семантические поисковые системы могут улавливать цель запроса намного лучше, чем сопоставление ключевых слов, и обычно извлекают документы с более высокой точностью. Но это не единственный правильный ответ - что еще дает семантический поиск?",
			correct: true
		}
	]}
/>

### 9. Для асимметричного семантического поиска можно использовать:

<Question
	choices={[
		{
			text: "Короткий запрос и более длинный абзац, отвечающий на запрос. ",
			explain: "Правильно!",
			correct : true
		},
		{
			text: "Запросы и абзацы примерно одинаковой длины. ",
			explain: "Это пример симметричного семантического поиска – попробуй еще раз!"
		},
		{
			text: "Длинный запрос и более короткий абзац, который отвечает на запрос.",
			explain: "Неправильно, попробуй еще раз!"
		}
	]}
/>

### 10. Могу ли я использовать 🤗 Datasets для загрузки данных и решения задач в других областях, например для обработки речи?

<Question
	choices={[
		{
			text: "Нет",
			explain: "Это неверно — 🤗 Datasets в настоящее время поддерживают табличные данные, аудио и компьютерное зрение. Ознакомьтесь с <a href='https://huggingface.co/datasets/mnist'>набором данных MNIST</a> в Hub для примера компьютерного зрения."
		},
		{
			text: "Да",
			explain: "Правильно! Ознакомьтесь с захватывающими разработками в области речи и зрения в библиотеке 🤗 Transformers, чтобы узнать, как 🤗 Datasets используются в этих областях.",
			correct : true
		},
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter5/8.mdx" />

### Big data? 🤗 Datasets спешат на помощь!
https://huggingface.co/learn/course/ru/chapter5/4.md

# Big data? 🤗 Datasets спешат на помощь!

<CourseFloatingBanner chapter={5}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter5/section4.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section4.ipynb"},
]} />

В настоящее время нередко приходится работать с многогигабайтными наборами данных, особенно если вы планируете предварительно обучить трансформер, такой как BERT или GPT-2, с нуля. В этих случаях даже _загрузка_ данных может стать проблемой. Например, корпус WebText, используемый для предобучения GPT-2, состоит из более чем 8 миллионов документов и 40 ГБ текста — загрузка этого в оперативную память вашего ноутбука может привести к сердечному приступу!

К счастью, 🤗 Datasets спроектирована так, что позволит избежать таких ограничений. Библиотека избавляет вас от необходимости управлять памятью и рассматривает датасеты как [файлы, отображаемые в память](https://habr.com/ru/post/55716/) (memory-mapped files, MMF), также обходит ограничения жестких дисков путем формирования потоков записей из корпуса текстов. 

<Youtube id="JwISwTCPPWo"/>

В этом разделе мы рассмотрим эти особенности 🤗 Datasets с огромным корпусом объемом 825 ГБ, известным как [Pile] (https://pile.eleuther.ai). Давайте начнем!

## Что такое the Pile?

The Pile — это корпус текстов на английском языке, созданный [EleutherAI] (https://www.eleuther.ai) для обучения крупномасштабных языковых моделей. Он включает в себя широкий спектр наборов данных, включая научные статьи, репозитории кода GitHub и отфильтрованный веб-текст. Учебный корпус доступен в виде [фрагментов по 14 ГБ] (https://mystic.the-eye.eu/public/AI/pile/), и вы также можете загрузить несколько [отдельных компонентов] (https://mystic .the-eye.eu/public/AI/pile_preliminary_components/). Начнем с набора данных PubMed Abstracts, который представляет собой свод аннотаций из 15 миллионов биомедицинских публикаций в [PubMed] (https://pubmed.ncbi.nlm.nih.gov/). Набор данных находится в [формате JSON Lines] (https://jsonlines.org) и сжат с использованием библиотеки `zstandard`, поэтому сначала нам нужно установить библиотеку `zstandart`:

```py
!pip install zstandard
```

Затем мы можем загрузить набор данных, используя метод для подгрузки файлов, который мы изучили в [разделе 2](../chapter5/2):

```py
from datasets import load_dataset

# Этой займет несколько минут, пока ожидаете – сделайте кофе или чай :)
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
})
```

Мы видим, что в нашем наборе данных 15 518 009 строк и 2 столбца — это очень много!

<Tip>

✎ По умолчанию 🤗 Datasets распаковывает файлы, необходимые для загрузки набора данных. Если вы хотите сохранить место на жестком диске, вы можете передать `DownloadConfig(delete_extracted=True)` в аргумент `download_config` функции `load_dataset()`. Дополнительные сведения см. в [документации](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig).

</Tip>

Давайте посмотрим на содержимое первого экземпляра: 

```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 ...'}
```

Отлично, выглядит как аннотация медицинской статьи. Теперь давайте посмотрим объем памяти, который мы использовали при загрузке данных:

## Магия отображения в память

Простой способ измерить использование памяти в Python — использовать библиотеку [`psutil`](https://psutil.readthedocs.io/en/latest/), которую можно установить с помощью `pip` следующим образом:

```python
!pip install psutil
```

Она предоставляет класс Process, который позволяет нам проверить использование памяти текущим процессом следующим образом:

```py
import psutil

# Process.memory_info вовзращает объем в байтах, мы пересчитаем в мегабайты
print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB")
```

```python out
RAM used: 5678.33 MB
```

Здесь атрибут `rss` относится к _резидентному размеру набора_, который представляет собой долю памяти, которую процесс занимает в ОЗУ. Это измерение также включает память, используемую интерпретатором Python и загруженными нами библиотеками, поэтому фактический объем памяти, используемый для загрузки набора данных, немного меньше. Для сравнения давайте посмотрим, насколько велик набор данных на диске, используя атрибут `dataset_size`. Поскольку результат, как и раньше, выражается в байтах, нам нужно вручную преобразовать его в гигабайты:

```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
```

Приятно — несмотря на то, что он весит почти 20 ГБ, мы можем загрузить и получить доступ к набору данных с гораздо меньшим объемом оперативной памяти!

<Tip>

✏️ **Попробуйте!** Выберите один из [компонентов](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) из Pile, который больше, чем оперативная память вашего ноутбука или настольного компьютера, загрузите его с 🤗 Datasets и измерьте объем используемой оперативной памяти. Обратите внимание, что для получения точных измерений вам потребуется сделать это в новом процессе. Вы можете найти распакованные размеры каждого компонента в Таблице 1 [документации Pile] (https://arxiv.org/abs/2101.00027).

</Tip>

Если вы знакомы с Pandas, этот результат может стать неожиданностью из-за знаменитого [эмпирического правила] Уэса Кинни (https://wesmckinney.com/blog/apache-arrow-pandas-internals/), согласно которому вам обычно требуется 5 до 10 раз больше оперативной памяти, чем размер вашего набора данных. Так как же 🤗 Datasets решают эту проблему управления памятью? 🤗 Datasets рассматривают каждый набор данных как [файл с отображением в память] (https://en.wikipedia.org/wiki/Memory-mapped_file), который обеспечивает сопоставление между оперативной памятью и хранилищем файловой системы, что позволяет библиотеке получать доступ к элементам и работать с ними без необходимости полной загрузки его в память.

Memory-mapped файлы также могут совместно использоваться несколькими процессами, что позволяет распараллеливать такие методы, как `Dataset.map()`, без необходимости перемещать или копировать набор данных. Под капотом все эти возможности реализованы в формате [Apache Arrow](https://arrow.apache.org) и [`pyarrow`](https://arrow.apache.org/docs/python/index. .html), которые делают загрузку и обработку данных молниеносной. (Для получения более подробной информации об Apache Arrow и сравнении с Pandas ознакомьтесь с [публикацией в блоге Деяна Симика] (https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a). Чтобы увидеть это в действии давайте проведем небольшой тест скорости, перебирая все элементы в наборе данных 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'
```

Здесь мы использовали модуль `timeit` Python для измерения времени выполнения `code_snippet`. Обычно вы сможете перебирать набор данных со скоростью от нескольких десятых долей ГБ/с до нескольких ГБ/с. Это прекрасно работает для подавляющего большинства приложений, но иногда вам придется работать с набором данных, который слишком велик даже для хранения на жестком диске вашего ноутбука. Например, если бы мы попытались загрузить весь Pile, нам потребовалось бы 825 ГБ свободного места на диске! Чтобы справиться с такими случаями 🤗 Datasets предоставляют функцию потоковой передачи, которая позволяет нам загружать и получать доступ к элементам на лету, без необходимости загружать весь набор данных. Давайте посмотрим, как это работает.

<Tip>

💡 В Jupyter notebooks вы также можете измерить время исполнения ячейки с использованием  [`%%timeit` magic function](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit).

</Tip>

## Потоковая передача датасета

Чтобы включить потоковую передачу набора данных, вам просто нужно передать аргумент `streaming=True` в функцию `load_dataset()`. Например, давайте снова загрузим набор данных PubMed Abstracts, но в потоковом режиме:

```py
pubmed_dataset_streamed = load_dataset(
    "json", data_files=data_files, split="train", streaming=True
)
```

Вместо знакомого `Dataset`, с которым мы уже встречались в других местах этой главы, объект, возвращаемый с `streaming=True`, является `IterableDataset`. Как следует из названия, чтобы получить доступ к элементам `IterableDataset`, нам нужно выполнить итерацию по нему. Мы можем получить доступ к первому элементу нашего набора потоковых данных следующим образом:

```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 ...'}
```

Элементы из потокового набора данных можно обрабатывать на лету с помощью `IterableDataset.map()`, что полезно во время обучения, если вам нужно токенизировать входные данные. Процесс точно такой же, как тот, который мы использовали для токенизации нашего набора данных в [Главе 3](../chapter3/1), с той лишь разницей, что выходные данные возвращаются один за другим:

```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, ...]}
```

<Tip>

💡 Чтобы ускорить токенизацию с потоковой передачей, вы можете передать `batched=True`, как мы делали в последнем разделе. Он будет обрабатывать примеры батчами; размер батча по умолчанию составляет 1000 и может быть указан в аргументе `batch_size`.

</Tip>

Вы также можете перемешать потоковые наборы данных, используя `IterableDataset.shuffle()`, но в отличие от `Dataset.shuffle()`, это только перемешивает элементы в предопределенном `buffer_size`:

```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 ...'}
```

В этом примере мы выбрали случайный пример из первых 10 000 примеров в буфере. После обращения к примеру его место в буфере заполняется следующим примером в корпусе (т. е. 10 001-м примером в приведенном выше случае). Вы также можете выбирать элементы из потокового набора данных, используя функции `IterableDataset.take()` и `IterableDataset.skip()`, которые действуют аналогично `Dataset.select()`. Например, чтобы выбрать первые 5 примеров в наборе данных PubMed Abstracts, мы можем сделать следующее:

```py
dataset_head = pubmed_dataset_streamed.take(5)
list(dataset_head)
```

```python out
[{'meta': {'pmid': 11409574, 'language': 'eng'},
  'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'},
 {'meta': {'pmid': 11409575, 'language': 'eng'},
  'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'},
 {'meta': {'pmid': 11409576, 'language': 'eng'},
  'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."},
 {'meta': {'pmid': 11409577, 'language': 'eng'},
  'text': 'Oxygen concentrators and cylinders ...'},
 {'meta': {'pmid': 11409578, 'language': 'eng'},
  'text': 'Oxygen supply in rural africa: a personal experience ...'}]
```

Точно так же вы можете использовать функцию `IterableDataset.skip()` для создания обучающих и проверочных сплитов из перемешанного набора данных следующим образом:

```py
# Пропустить первые 1000 объектов и включить остальные в обучающую выборку
train_dataset = shuffled_dataset.skip(1000)
# Взять первые 1000 объектов в валидационную выборку
validation_dataset = shuffled_dataset.take(1000)
```

Давайте завершим наше исследование потоковой передачи наборов данных общим приложением: объединение нескольких наборов данных вместе для создания единого корпуса. 🤗 Datasets предоставляют функцию `interleave_datasets()`, которая преобразует список объектов `IterableDataset` в один `IterableDataset`, где элементы нового набора данных получаются путем чередования исходных примеров. Эта функция особенно полезна, когда вы пытаетесь объединить большие наборы данных, поэтому в качестве примера давайте воспроизведем компонент FreeLaw из Pile, который представляет собой набор данных юридических заключений судов США объемом 51 ГБ:

```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...'}
```

Этот набор данных достаточно велик, чтобы нагружать оперативную память большинства ноутбуков, но мы смогли загрузить его и получить к нему доступ! Давайте теперь объединим примеры из наборов данных FreeLaw и PubMed Abstracts с функцией `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...'}]
```

Здесь мы использовали функцию `islice()` из модуля `itertools` Python, чтобы выбрать первые два объекта из объединенного набора данных, и мы видим, что они соответствуют первым примерам из каждого из двух исходных наборов данных.

Наконец, если вы хотите получить в потоковом режиме весь Pile целиком (825 ГБ), вы можете получить все подготовленные файлы следующим образом:

```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...'}
```

<Tip>

✏️ **Попробуйте!** Используйте один из больших корпусов Common Crawl, например [`mc4`](https://huggingface.co/datasets/mc4) или [`oscar`](https://huggingface.co/ datasets/oscar) для создания потокового многоязычного набора данных, который представляет пропорции разговорных языков в стране по вашему выбору. Например, в Швейцарии есть четыре национальных языка: немецкий, французский, итальянский и рето-романский, поэтому вы можете попробовать создать швейцарский корпус, выбрав подмножества Оскаров в соответствии с их разговорной пропорцией.

</Tip>

Теперь у вас есть все инструменты, необходимые для загрузки и обработки наборов данных всех форм и размеров, но, если только вам не повезет, в вашем путешествии по НЛП наступит момент, когда вам придется фактически создать собственный набор данных для решения проблемы. Это тема следующего раздела!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter5/4.mdx" />

### 🤗 Datasets, итоги!
https://huggingface.co/learn/course/ru/chapter5/7.md

# 🤗 Datasets, итоги!

<CourseFloatingBanner
    chapter={5}
    classNames="absolute z-10 right-0 top-0"
/>

Что ж, это было настоящее путешествие по библиотеке 🤗 Datasets — поздравляем, вы зашли так далеко! Со знаниями, которые вы получили из этой главы, вы сможете:

- Загружать наборы данных из любого места, будь то Hugging Face Hub, ваш ноутбук или удаленный сервер в вашей компании.
- Обрабатывать свои данные, используя сочетание функций `Dataset.map()` и `Dataset.filter()`.
- Быстро переключаться между форматами данных, такими как Pandas и NumPy, с помощью `Dataset.set_format()`.
- Создавать свой собственный набор данных и отправлять его в Hugging Face Hub.
- Строить свои эмбеддинги документов с помощью модели Transformer и создавать семантический поисковик с помощью FAISS.

В [Главе 7](../chapter7) мы будем использовать все это с пользой, поскольку мы углубимся в основные задачи NLP, для которых отлично подходят модели Transformer. Однако, прежде чем идти вперед, проверьте свои знания о 🤗 Datasets с помощью быстрого теста!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter5/7.mdx" />

### Что делать, если моего датасета на нет на Hub?
https://huggingface.co/learn/course/ru/chapter5/2.md

# Что делать, если моего датасета на нет на Hub?

<DocNotebookDropdown
  classNames="absolute z-10 right-0 top-0"
  options={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter5/section2.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter5/section2.ipynb"},
]} />

Вы знаете, как использовать [Hugging Face Hub](https://huggingface.co/datasets) для скачивания датасетов, но часто складывается ситуация, когда нужные данные не хранятся у вас локально или на удаленном сервере. В этом разделе мы посмотрим, как библиотека 🤗 Datasets может быть использована для загрузки датасетов, которые не хранятся на Hugging Face Hub. 

<Youtube id="HyQgpJTkRdE"/>

## Работа с локальными и удаленными датасетами

🤗 Datasets предоставляет скрипты для загрузки собственных датасетов. Библиотека поддерживает несколько распространенных форматов:

|    Data format     | Loading script |                         Example                         |
| :----------------: | :------------: | :-----------------------------------------------------: |
|     CSV & TSV      |     `csv`      |     `load_dataset("csv", data_files="my_file.csv")`     |
|     Text files     |     `text`     |    `load_dataset("text", data_files="my_file.txt")`     |
| JSON & JSON Lines  |     `json`     |   `load_dataset("json", data_files="my_file.jsonl")`    |
| Pickled DataFrames |    `pandas`    | `load_dataset("pandas", data_files="my_dataframe.pkl")` |

Как показано в таблице, для каждого формата мы должны задать тип скрипта загрузки в функции `load_dataset()` вместе с аргументом `data_files`, который указывает путь к одному или нескольким файлам. Начнем с загрузки набора данных из локальных файлов; позже мы увидим, как сделать то же самое с файлами, расположены на удаленном сервере.

## Загрузка локального датасета

Для этого примера мы будем использовать датасет [SQuAD-it dataset](https://github.com/crux82/squad-it/). Это большой датасет для задачи question answering на итальянском языке. 

Обучающая и тестовая часть расположены на GitHub, мы можем скачать файлы с помощью простой команды `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
```
Выполнение этих команд запустит процесс скачивания файлов *SQuAD_it-train.json.gz* и *SQuAD_it-test.json.gz*, которые мы можем распаковать с помощью 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
```
После выполнения команд мы увидим, что архивы будут заменены файлами _SQuAD_it-train.json_ и _SQuAD_it-text.json_ в формате JSON. 

<Tip>
✎ Причина, по которой в примере выше перед командами расположен `!` заключается в том, что мы выполняем их в Jupyter notebook. Если вы хотите запустить эти команды в терминале – просто удалите `!`. 
</Tip>

Для загрузки JSON файла с помощью функции `load_dataset()` необходимо знать, с каким типом JSON-файла мы имеем дело: обычный JSON (похожий на вложенный словарь) или JSON, сформированный построчно. Как и многие датасеты для задач question-answering, SQuAD-it использует формат обычного JSON'а с текстом, хранящимся в поле `data`. Это означает, что мы можем подгрузить датасет, задав аргумент `field` следующим образом: 

```py
from datasets import load_dataset

squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data")
```

По умолчанию при загрузке локальных файлов создается объект `DatasetDict` с меткой `train`. Мы можем изучить объект `squad_it_dataset`:   

```py
squad_it_dataset
```

```python out
DatasetDict({
    train: Dataset({
        features: ['title', 'paragraphs'],
        num_rows: 442
    })
})
```

Выше распечатана информация об объекте: число строк и колонки обучающего датасета. Мы можем посмотреть на один объект, проиндексировав его как `train` следующим образом: 

```py
squad_it_dataset["train"][0]
```

```python out
{
    "title": "Terremoto del Sichuan del 2008",
    "paragraphs": [
        {
            "context": "Il terremoto del Sichuan del 2008 o il terremoto...",
            "qas": [
                {
                    "answers": [{"answer_start": 29, "text": "2008"}],
                    "id": "56cdca7862d2951400fa6826",
                    "question": "In quale anno si è verificato il terremoto nel Sichuan?",
                },
                ...
            ],
        },
        ...
    ],
}
```
Отлично! Мы загрузили наш первый датасет! Но пока мы это сделали только для обучающей части данных, хотя нам нужны и `train`, и `test` в одном `DatasetDict`, чтобы мы могли применить функцию `Dataset.map()` на оба подмножества сразу. Чтобы сделать это, мы можем передать в словарь в `data_files`. Сделать это можно так: 

```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
    })
})
```

Это ровно то, чего мы хотели добиться! Далее мы можем применять различные приемы для препроцессинга данных: очистку, токенизацию  и прочее. 

<Tip>

Аргумент `data_files` функции `load_dataset()` очень гибкий и может являться путем к файлу, списком путей файлов или словарем, в котором указаны названия сплитов (обучающего и тестового) и пути к соответствующим файлам. Вы также можете найти все подходящие файлы в директории с использованием маски по правилам Unix-консоли (т.е. указать путь к директории и указать `data_files="*.json"` для конкретного сплита). Более подробно это изложено в [документации](https://huggingface.co/docs/datasets/loading#local-and-remote-files) 🤗 Datasets. 

</Tip>

Скрипты загрузки 🤗 Datasets также поддерживают автоматическую распаковку входных файлов, поэтому мы можем пропустить команду `gzip` просто передав в аргумент `data_files` пути к архивам: 

```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")
```

Это может быть полезно, если вы не хотите вручную разархивировать GZIP файлы. Автоматическое разархивирование также поддерживает распространенные форматы вроде ZIP и TAR, так что вы можете передавать и пути к таким файлам.  

Теперь, когда вы знаете, как загрузить локально хранящиеся файлы, мы посмотрим, как подгрузить данные с удаленных серверов. 

## Загрузка файлов с удаленного сервера

Если вы работаете data scientist или программистом в компании, скорее всего ваши данные хранятся на сервере. К счастью, загрузка файлов с удаленных машин настолько же простая, насколько и загрузка их со локальной машины! Вместо пути к локальным файлам мы передаем аргументу `data_files` один или несколько URL, указывающих на нужные файлы. К примеру, датасет SQuAD-it расположен на GitHub, мы можем просто указать ссылку на файлы следующим образом: 

```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")
```

Эта операция вернет такой же `DatasetDict`, какой мы получали ранее, но избавит нас от загрузки и разархивирования файлов _SQuAD_it-*.json.gz_ вручную. 
На этом мы завершаем наш обзор различных способов загрузки датасетов, которые не размещены на Hugging Face Hub. Теперь, когда у нас есть датасет, с которым можно поиграться, давайте погрузимся в различные методы обработки данных!

<Tip>

✏️ **Попробуйте!** Выберите другой датасет, расположенный на GitHub или в архиве [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) и попробуйте загрузить его с локальной машины и с удаленного сервера. В качестве бонуса попробуйте загрузить датасет в формате CSV или обычного тектового файла (см. детали по поддерживаемым форматам в [документации](https://huggingface.co/docs/datasets/loading#local-and-remote-files)). 

</Tip>




<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter5/2.mdx" />

### Глоссарий
https://huggingface.co/learn/course/ru/glossary/1.md

# Глоссарий

|             Оригинал            |               Перевод                   |
|---------------------------------|-----------------------------------------|
| Abstraction                     | абстракция                              |
| Account                         | учетная запись                          |
| Accuracy                        | accuracy                                |
| Artificial General Intelligence | сильный искусственный интеллект         |
| Attention                       | внимание                                |
| Attention mask (layer)          | маска внимания (слой)                   |
| Backward Pass\*                 | обратный проход                         |
| Batch                           | батч                                    |
| Bias                            | смещение                                |
| Causal Language Modeling        | каузальное языковое моделирование       |
| Chapter                         | глава                                   |
| Checkpoint(s)                   | чекпоинт                                |
| Class                           | класс                                   |
| Classification                  | классификация                           |
| Code                            | код                                     |
| Colab Notebook                  | блокнот Colab                           |
| Command                         | команда                                 |
| Computer Vision                 | компьютерное зрение                     |
| Configuration                   | конфигурация                            |
| Course                          | курс                                    |
| Decoder                         | декодировщик / декодер                  |
| Dependency                      | зависимость                             |
| Deployment                      | развертывание (программного обеспечения)|
| Development                     | разработка                              |
| Dictionary                      | dictionary                              |
| Distribution                    | распределение                           |
| Download                        | download                                |
| Encoder                         | кодировщик / энкодер                    |
| Extractive question answering   | выделительная вопросно-ответная система |
| F1 score                        | F1-мера                                 |
| Feature                         | признак                                 |
| Fine-tune                       | дообучать                               |
| Fine-tuning                     | дообучение                              |
| Folder                          | папка / директория                      |
| Forward Pass\*                  | прямой проход                           |
| Function                        | функция                                 |
| Generative question answering   | генеративная вопросно-ответная система  |
| Google                          | Google                                  |
| Hugging Face                    | Hugging Face                            |
| Incompatibility                 | несовместимость                         |
| Inference                       | инференс                                |
| Input                           | вход                                    |
| Input data                      | входные данные                          |
| Label (verb)                    | размечать                               |
| Label (subj)                    | метка класса                            |
| Layer                           | слой                                    |
| Library                         | библиотека                              |
| Linux                           | Linux                                   |
| Load                            | загружать                               |
| Loss function                   | функция потерь                          |
| Machine Learning                | машинное обучение                       |
| macOS                           | macOS                                   |
| Mask                            | маска                                   |
| Mask Filling                    | предсказание замаскированного токена    |
| Mask Token                      | токен-маска                             |
| Masked Language Modeling        | маскированное языковое моделирование    |
| Model                           | модель                                  |
| Model Hub                       | Model Hub                               |
| Module                          | модуль                                  |
| Named Entities                  | именованные сущности                    |
| Named Entity Recognition        | распознавание именованных сущностей     |
| Natural Language Processing     | обработка естественного языка           |
| Output                          | выход                                   |
| Package                         | пакет                                   |
| Package Manager                 | менеджер пакетов                        |
| Padding (объект)                | padding                                 |
| Padding (действие)              | дополнение                              |
| Parameter                       | параметр                                |
| Postprocessing                  | постобработка / последующая обработка   |
| Preprocessing                   | предобработка / предварительная обработка|
| Pretraining                     | предварительное обучение / предобучение |
| Pretrained model                | предварительно обученная модель         |
| Pretrained model                | предобученная модель                    |
| Prompt                          | начальный текст                         |
| Python                          | Python                                  |
| Pytorch                         | Pytorch                                 |
| Question Answering              | вопросно-ответная система               |
| Save                            | сохранять                               |
| Sample                          | пример                                  |
| Script                          | скрипт                                  |
| Self-Attention                  | самовнимание                            |
| Self-Contained                  | самостоятельный                         |
| Sentiment analysis              | анализ тональности текста (сентимент-анализ)|
| Sequence-to-sequence models     | sequence-to-sequence модель             |
| Setup                           | установка (программы) / настройка (среды)|
| Speech Processing               | обработка речи                          |
| Speech Recognition              | распознавание речи                      |
| Summarization                   | суммаризация                            |
| Target                          | целевая переменная                      |
| Task                            | задача                                  |
| TensorFlow                      | Tensorflow                              |
| Terminal                        | терминал                                |
| Text generation                 | генерация текста                        |
| Tokenizer                       | Tokenizer (библиотека) / токенизатор    |
| Train                           | обучение (обучать)                      |
| Transfer Learning               | Transfer Learning / трансферное обучение|
| Transformer                     | трансформер                             |
| Transformer models              | архитектура трансформер                 |
| Translation                     | (машинный) перевод                      |
| Virtual Environment             | виртуальное окружение                   |
| Weight                          | вес                                     |
| Weights                         | веса                                    |
| Windows                         | Windows                                 |
| Working Environment             | рабочее окружение                       |
| Workload                        | нагрузка                                |
| Workspace                       | Workspace                               |
| Zero-shot classification        | zero-shot классификация                 |
=======

\* Данные термины могут употребляться взаимозаменяемо с их английской версией

## Сокращения

| Оригинал  | Перевод     |
|-----------|-------------|
| NLP       | NLP         |
| API       | API         |
| GPU       | GPU         |
| TPU       | TPU         |
| ML        | ML          |

## Notes

Please refer to [TRANSLATING.txt](/chapters/ru/TRANSLATING.txt) for a translation guide. Here are some excerpts relevant to the glossary:

- Refer and contribute to the glossary frequently to stay on top of the latest
  choices we make. This minimizes the amount of editing that is required.
  Add new terms alphabetically sorted.

- The Russian language accepts English words especially in modern contexts more 
    than many other languages (i.e. Anglicisms). Check for the correct usage of 
    terms in computer science and commonly used terms in other publications.

- Don't translate industry-accepted acronyms. e.g. TPU or GPU.

- If translating a technical word, keep the choice of Russian translation consistent.
   This does not apply for non-technical choices, as in those cases variety actually
   helps keep the text engaging.

- Be exact when choosing equivalents for technical words. Package is package.
  Library is library. Don't mix and match.



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/glossary/1.mdx" />

### Обработка нескольких последовательностей[[handling-multiple-sequences]]
https://huggingface.co/learn/course/ru/chapter2/5.md

# Обработка нескольких последовательностей[[handling-multiple-sequences]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section5_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="M6adb1j2jPI"/>
{:else}
<Youtube id="ROxrFOEbsQE"/>
{/if}

В предыдущем разделе мы рассмотрели самый простой вариант использования: проведение инференса на одной последовательности небольшой длины. Однако уже сейчас возникают некоторые вопросы:

- Как нам работать с несколькими последовательностями?
- Как нам работать с несколькими последовательностями *разной длины*?
- Являются ли индексы словаря единственными входными данными, которые позволяют модели работать хорошо?
- Существует ли такая вещь, как слишком длинная последовательность?

Давайте посмотрим, какие проблемы возникают в связи с этими вопросами и как их можно решить с помощью 🤗 Transformers API.

## Модели ожидают батч входов[[models-expect-a-batch-of-inputs]]

В предыдущем упражнении вы видели, как последовательности преобразуются в списки чисел. Давайте преобразуем этот список чисел в тензор и передадим его в модель:

{#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)
# Эта строка выдаст ошибку.
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)
# Эта строка выдаст ошибку.
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 по умолчанию ожидают несколько предложений. Здесь мы попытались сделать все то, что токенизатор делал за кулисами, когда мы применяли его к `последовательности`. Но если вы приглядитесь, то увидите, что токенизатор не просто преобразовал список входных идентификаторов в тензор, а добавил к нему еще одно измерение:

{#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}

Мы выводим входные идентификаторы, а также результирующие логиты - вот результат:

{#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}

*Батчинг* - это отправка нескольких предложений через модель одновременно. Если у вас есть только одно предложение, вы можете просто создать батч с одной последовательностью:

```
batched_ids = [ids, ids]
```

Это батч из двух одинаковых последовательностей!

<Tip>

✏️ **Попробуйте!** Преобразуйте этот список `batched_ids` в тензор и пропустите его через вашу модель. Проверьте, что вы получаете те же логиты, что и раньше (но дважды)!

</Tip>

Батчинг позволяет модели работать, когда вы подаете ей несколько последовательностей. Использование нескольких последовательностей так же просто, как и создание батча с одной последовательностью. Однако есть и вторая проблема. Когда вы пытаетесь собрать в батч два (или более) предложения, они могут быть разной длины. Если вы когда-нибудь работали с тензорами, то знаете, что они должны иметь прямоугольную форму, поэтому вы не сможете напрямую преобразовать список входных идентификаторов в тензор. Чтобы обойти эту проблему, мы обычно прибегаем к *дополнению (pad)* входных данных.

## Дополнение входов[[padding-the-inputs]]

Следующий список списков не может быть преобразован в тензор:

```py no-format
batched_ids = [
    [200, 200, 200],
    [200, 200]
]
```

Чтобы обойти эту проблему, мы будем использовать *дополнение (padding)*, чтобы придать тензорам прямоугольную форму. Дополнение обеспечивает одинаковую длину всех предложений, добавляя специальное слово *токен дополнения* к предложениям с меньшим количеством значений. Например, если у вас есть 10 предложений с 10 словами и 1 предложение с 20 словами, то при дополнении все предложения будут состоять из 20 слов. В нашем примере результирующий тензор выглядит следующим образом:

```py no-format
padding_id = 100

batched_ids = [
    [200, 200, 200],
    [200, 200, padding_id],
]
```

Идентификатор токена дополнения можно найти в `tokenizer.pad_token_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=<AddmmBackward>)
tensor([[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
tensor([[ 1.5694, -1.3895],
        [ 1.3373, -1.2163]], grad_fn=<AddmmBackward>)
```
{: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}

Что-то не так с логитами в наших батчах: во втором ряду должны быть те же логиты, что и для второго предложения, но мы получили совершенно другие значения!

Это связано с тем, что ключевой особенностью моделей Transformer являются слои внимания (attention layers), которые *контекстуализируют* каждый токен. Они учитывают токены дополнений, так как рассматривают все токены последовательности. Чтобы получить одинаковый результат при прохождении через модель отдельных предложений разной длины или при прохождении батча с одинаковыми предложениями и дополнениями, нам нужно указать слоям внимания игнорировать дополняющие токены. Для этого используется маска внимания (attention mask).

## Маски внимания[[attention-masks]]

*Маски внимания (Attention masks)* - это тензоры той же формы, что и тензор входных идентификаторов, заполненные 0 и 1: 1 означает, что соответствующие токены должны "привлекать внимание", а 0 означает, что соответствующие токены не должны "привлекать внимание" (т.е. должны игнорироваться слоями внимания модели).

Дополним предыдущий пример маской внимания:

{#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=<AddmmBackward>)
```
{: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}

Теперь мы получим такие же логиты для второго предложения в батче.

Обратите внимание, что последнее значение второй последовательности - это идентификатор дополнения (padding ID), который в маске внимания имеет значение 0.

<Tip>

✏️ **Попробуйте! ** Примените токенизацию вручную к двум предложениям, использованным в разделе 2 ("I've been waiting for a HuggingFace course my whole life." и "I hate this so much!"). Пропустите их через модель и проверьте, что вы получите те же логиты, что и в разделе 2. Теперь объедините их в батч с использованием токена дополнения, а затем создайте соответствующую маску внимания. Проверьте, что при прохождении через модель вы получаете те же результаты!

</Tip>

## Более длинные последовательности[[longer-sequences]]

В моделях Transformer существует ограничение на длину последовательностей, которые мы можем передавать моделям. Большинство моделей работают с последовательностями длиной до 512 или 1024 токенов и терпят крах при необходимости обработки более длинных последовательностей. Есть два решения этой проблемы:

- Использовать модель с большей поддерживаемой длиной последовательности.
- Усечение (truncate) последовательностей.

Модели имеют разную поддерживаемую длину последовательности, а некоторые специализируются на работе с очень длинными последовательностями. Одним из примеров является [Longformer](https://huggingface.co/docs/transformers/model_doc/longformer), а другим - [LED](https://huggingface.co/docs/transformers/model_doc/led). Если вы работаете над задачей, требующей очень длинных последовательностей, мы рекомендуем вам обратить внимание на эти модели.

В противном случае мы рекомендуем использовать усечение последовательностей, указав параметр `max_sequence_length`:

```py
sequence = sequence[:max_sequence_length]
```


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/5.mdx" />

### Введение[[introduction]]
https://huggingface.co/learn/course/ru/chapter2/1.md

# Введение[[introduction]]

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

Как вы видели в [Главе 1](../chapter1), модели трансформеров обычно очень большие. С миллионами и десятками *миллиардов* параметров, обучение и развертывание этих моделей - сложная задача. Кроме того, поскольку новые модели выходят практически ежедневно и каждая из них имеет свою собственную реализацию, попробовать их все - задача не из легких.

Для решения этой проблемы была создана библиотека 🤗 Transformers. Ее цель - предоставить единый API, с помощью которого можно загрузить, обучить и сохранить любую модель Transformer. Основными особенностями библиотеки являются:

- **Простота использования**: Скачать, загрузить и использовать современную модель NLP для инференса можно всего в две строчки кода.
- **Гибкость**: По своей сути все модели представляют собой простые классы PyTorch `nn.Module` или TensorFlow `tf.keras.Model` и могут быть обработаны как любые другие модели в соответствующих фреймворках машинного обучения (ML).
- **Простота**: В библиотеке почти нет абстракций. Концепция "Все в одном файле" является основной: прямой проход модели полностью определяется в одном файле, так что сам код понятен и доступен для изменения.

Эта последняя особенность делает 🤗 Transformers совершенно непохожей на другие ML-библиотеки. Модели не строятся на основе модулей 
которые совместно используются в разных файлах; вместо этого каждая модель имеет свои собственные слои. Помимо того, что это делает модели более доступными и понятными, это позволяет легко экспериментировать с одной моделью, не затрагивая другие.

Эта глава начнется со сквозного примера, в котором мы используем модель и токенизатор вместе, чтобы воссоздать функцию `pipeline()`, представленную в [Главе 1](../chapter1). Далее мы обсудим API модели: мы погрузимся в модель и классы конфигурации, покажем, как загрузить модель и как она обрабатывает числовые данные для вывода прогнозов. 

Затем мы рассмотрим API токенизатора, который является другим основным компонентом функции `pipeline()`. Токенизаторы берут на себя первый и последний шаги препроцессинга, обработку преобразования текста в числовые входы для нейронной сети и обратное преобразование в текст, когда это необходимо. Наконец, мы покажем вам, как обрабатывать несколько предложений, передавая их в модель в подготовленном батче, затем завершим все это более подробным рассмотрением высокоуровневой функции `tokenizer()`.

<Tip>
⚠️ Чтобы воспользоваться всеми возможностями, доступными в Model Hub и 🤗 Transformers, мы рекомендуем <a href="https://huggingface.co/join">создать учетную запись</a>.
</Tip>

<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/1.mdx" />

### Модели[[models]]
https://huggingface.co/learn/course/ru/chapter2/3.md

# Модели[[models]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section3_tf.ipynb"},
]} />

{/if}

{#if fw === 'pt'}
<Youtube id="AhChOFRegn4"/>
{:else}
<Youtube id="d3JVgghSOew"/>
{/if}

{#if fw === 'pt'}
В этом разделе мы подробно рассмотрим создание и использование модели. Мы будем использовать класс `AutoModel`, который удобен, когда вы хотите инстанцировать любую модель из контрольной точки.

Класс `AutoModel` и все его представители на самом деле являются простыми обертками для широкого спектра моделей, доступных в библиотеке. Это умная обертка, поскольку она может автоматически определить архитектуру модели, подходящую для вашей контрольной точки, а затем инстанцировать модель с этой архитектурой.

{:else}
В этом разделе мы подробно рассмотрим создание и использование модели. Мы будем использовать класс `TFAutoModel`, который удобен, когда вы хотите инстанцировать любую модель из контрольной точки.

Класс `TFAutoModel` и все его родственники на самом деле являются простыми обертками для широкого спектра моделей, доступных в библиотеке. Это умная обертка, поскольку она может автоматически определить подходящую архитектуру модели для вашей контрольной точки, а затем инстанцировать модель с этой архитектурой.

{/if}

Однако если вы знаете тип модели, которую хотите использовать, вы можете напрямую использовать класс, определяющий ее архитектуру. Давайте рассмотрим, как это работает на примере модели BERT.

## Создание Transformer[[creating-a-transformer]]

Первое, что нам нужно сделать для инициализации модели BERT, - загрузить объект конфигурации:

{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

# Создание конфигурации
config = BertConfig()

# Создание модели на основе конфигурации
model = BertModel(config)
```
{:else}
```py
from transformers import BertConfig, TFBertModel

# Создание конфигурации
config = BertConfig()

# Создание модели на основе конфигурации
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_states`, а `num_hidden_layers` определяет количество слоев в модели Transformer.

### Различные методы загрузки[[different-loading-methods]]

При создании модели из конфигурации по умолчанию она инициализируется случайными значениями:

{#if fw === 'pt'}
```py
from transformers import BertConfig, BertModel

config = BertConfig()
model = BertModel(config)

# Модель инициализируется случайным образом!
```
{:else}
```py
from transformers import BertConfig, TFBertModel

config = BertConfig()
model = TFBertModel(config)

# Модель инициализируется случайным образом!
```
{/if}

Модель можно использовать и в таком состоянии, но она будет выдавать тарабарщину; сначала ее нужно обучить. Мы могли бы обучить модель с нуля для конкретной задачи, но, как вы видели в [Главе 1](../chapter1), это потребовало бы много времени и большого количества данных, а также оказало бы немалое влияние на окружающую среду. Чтобы избежать ненужных и дублирующих усилий, крайне важно иметь возможность обмениваться уже обученными моделями и повторно их использовать.

Загрузить уже обученную модель Transformer очень просто - мы можем сделать это с помощью метода `from_pretrained()`:

{#if fw === 'pt'}
```py
from transformers import BertModel

model = BertModel.from_pretrained("bert-base-cased")
```

Как вы видели ранее, мы можем заменить `BertModel` на эквивалентный класс `AutoModel`. В дальнейшем мы будем поступать именно так, поскольку таким образом мы получаем код, не зависящий от контрольных точек; если ваш код работает на одной контрольной точке, он должен без проблем работать и на другой. Это касается даже разных архитектур, если контрольная точка была обучена для схожей задачи (например, задачи анализа настроений).

{:else}
```py
from transformers import TFBertModel

model = TFBertModel.from_pretrained("bert-base-cased")
```

Как вы видели ранее, мы можем заменить `TFBertModel` на эквивалентный класс `TFAutoModel`. В дальнейшем мы будем поступать именно так, поскольку таким образом мы получаем код, не зависящий от контрольных точек; если ваш код работает на одной контрольной точке, он должен без проблем работать и на другой. Это касается даже разных архитектур, если контрольная точка была обучена для схожей задачи (например, задачи анализа настроений).

{/if}

В приведенном выше примере кода мы не использовали `BertConfig`, а вместо этого загрузили предварительно обученную модель через идентификатор `bert-base-cased`. Это контрольная точка модели, которая была обучена самими авторами BERT; более подробную информацию о ней можно найти в ее [карточке модели](https://huggingface.co/bert-base-cased).

Теперь эта модель инициализирована всеми весами контрольной точки. Ее можно использовать непосредственно для инференса на задачах, для которых она была обучена, а также для дообучения на новой задаче. Обучаясь с предварительно подготовленными весами, а не с нуля, мы можем быстро добиться хороших результатов.

Веса были загружены и кэшированы (чтобы последующие вызовы метода `from_pretrained()` не загружали их заново) в папке кэша, которая по умолчанию находится в *~/.cache/huggingface/transformers*. Вы можете настроить папку кэша, установив переменную окружения `HF_HOME`.

Идентификатор, используемый для загрузки модели, может быть идентификатором любой модели на Model Hub, если она совместима с архитектурой BERT. Полный список доступных контрольных точек BERT можно найти [здесь](https://huggingface.co/models?filter=bert).

### Методы сохранения[[saving-methods]]

Сохранить модель так же просто, как и загрузить ее - мы используем метод `save_pretrained()`, который аналогичен методу `from_pretrained()`:

```py
model.save_pretrained("directory_on_my_computer")
```

При этом на диск сохраняются два файла:

{#if fw === 'pt'}
```
ls directory_on_my_computer

config.json model.safetensors
```
{:else}
```
ls directory_on_my_computer

config.json tf_model.h5
```
{/if}

Если вы посмотрите на файл *config.json*, то узнаете атрибуты, необходимые для построения архитектуры модели. Этот файл также содержит некоторые метаданные, такие как место создания контрольной точки и версию 🤗 Transformers, которую вы использовали при последнем сохранении контрольной точки.

{#if fw === 'pt'}
Файл *model.safetensors* известен как *словарь состояний (state dictionary)*; он содержит все веса вашей модели. Эти два файла неразрывно связаны друг с другом; конфигурация необходима для того, чтобы знать архитектуру модели, а веса модели - это ее параметры.

{:else}
Файл *tf_model.h5* известен как *словарь состояний (state dictionary)*; он содержит все веса вашей модели. Эти два файла неразрывно связаны друг с другом; конфигурация необходима для того, чтобы знать архитектуру модели, а веса модели - это ее параметры.

{/if}

## Использование модели Transformer для инференса[[using-a-transformer-model-for-inference]]

Теперь, когда вы знаете, как загружать и сохранять модель, давайте попробуем использовать ее для прогнозирования. Модели 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}

### Использование тензоров в качестве входов в модель[[using-the-tensors-as-inputs-to-the-model]]

Использовать тензоры с моделью очень просто - мы просто вызываем модель с входами:

```py
output = model(model_inputs)
```

Хотя модель принимает множество различных аргументов, только идентификаторы входов являются необходимыми. О том, что делают остальные аргументы и когда они нужны, мы расскажем позже, 
но сначала нам нужно подробнее рассмотреть токенизаторы, которые формируют входные данные (входы), которые может понять модель Transformer.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/3.mdx" />

### Собираем все воедино[[putting-it-all-together]]
https://huggingface.co/learn/course/ru/chapter2/6.md

# Собираем все воедино[[putting-it-all-together]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section6_tf.ipynb"},
]} />

{/if}

В последних нескольких разделах мы старались делать большую часть работы вручную. Мы изучили, как работают токенизаторы, рассмотрели токенизацию, преобразование во входные идентификаторы, дополнении, усечении и маски внимания.

Однако, как мы видели в разделе 2, 🤗 Transformers API может обработать все это для нас с помощью высокоуровневой функции, которую мы рассмотрим здесь. Когда вы вызываете свой `tokenizer` непосредственно на предложении, вы получаете обратно входы, готовые к передаче в вашу модель:

```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 это идентификаторы входов, а также маска внимания. В других моделях, принимающих дополнительные входы, они также будут формироваться выходами объекта `tokenizer`.

Как мы увидим на нескольких примерах ниже, это очень мощный метод. Во-первых, он может токенизировать одну последовательность:

```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
# Дополнение последовательностей до максимальной длины последовательности
model_inputs = tokenizer(sequences, padding="longest")

# Дополнение последовательностей до максимальной длины модели
# (512 для BERT или DistilBERT)
model_inputs = tokenizer(sequences, padding="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!"]

# Усечение последовательностей, длина которых превышает максимальную длину модели
# (512 для BERT или DistilBERT)
model_inputs = tokenizer(sequences, truncation=True)

# Усечение последовательностей, длина которых превышает заданную максимальную длину
model_inputs = tokenizer(sequences, max_length=8, truncation=True)
```

Объект `tokenizer` может выполнять преобразование в тензоры конкретных фреймворков, которые затем могут быть напрямую переданы в модель. Например, в следующем примере кода мы задаем токенизатору возвращать тензоры для различных фреймворков - `"pt"` возвращает тензоры PyTorch, `"tf"` возвращает тензоры TensorFlow, а `"np"` возвращает массивы NumPy:

```py
sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"]

# Вернуть тензоры PyTorch
model_inputs = tokenizer(sequences, padding=True, return_tensors="pt")

# Вернуть тензоры TensorFlow
model_inputs = tokenizer(sequences, padding=True, return_tensors="tf")

# Вернуть массивы NumPy
model_inputs = tokenizer(sequences, padding=True, return_tensors="np")
```

## Специальные токены[[special-tokens]]

Если мы посмотрим на идентификаторы входа, возвращаемые токенизатором, то увидим, что они немного отличаются от тех, что мы получали ранее:

```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]
```

Один идентификатор токена был добавлен в начале, а другой - в конце. Давайте декодируем две последовательности идентификаторов, приведенные выше, чтобы понять, в чем дело:

```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]` в конце. Это связано с тем, что модель была предварительно обучена с ними, поэтому для получения тех же результатов при инференсе нам нужно добавить и их. Обратите внимание, что некоторые модели не добавляют специальные слова или добавляют другие слова; модели также могут добавлять эти специальные слова только в начале или только в конце. В любом случае, токенизатор знает, какие из них ожидаются, и справится с этим сам.

## Подведение итогов: От токенизатора к модели[[wrapping-up-from-tokenizer-to-model]]

Теперь, когда мы рассмотрели все отдельные шаги, которые использует объект `tokenizer` при работе с текстами, давайте в последний раз посмотрим, как он может работать с множественными последовательностями (дополнение!), очень длинными последовательностями (усечение!) и несколькими типами тензоров с помощью своего основного 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}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/6.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter2/8.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

### 1. Каков порядок работы конвейера языкового моделирования?

<Question
	choices={[
		{
			text: "Сначала модель, которая обрабатывает текст и возвращает необработанные прогнозы. Затем токенизатор осмысливает эти предсказания и при необходимости преобразует их обратно в текст.",
			explain: "Модель не может понять текст! Токенизатор должен сначала обработать текст и преобразовать его в идентификаторы, чтобы он был понятен модели."
		},
		{
			text: "Сначала работает токенизатор, который обрабатывает текст и возвращает идентификаторы. Модель обрабатывает эти идентификаторы и выдает прогноз, который может представлять собой некоторый текст.",
			explain: "Прогноз модели не может сразу стать текстом. Для преобразования прогноза в текст необходимо использовать токенизатор!"
		},
		{
			text: "Токенизатор обрабатывает текст и возвращает идентификаторы. Модель обрабатывает эти идентификаторы и выдает прогноз. Затем токенизатор можно снова использовать для преобразования этих прогнозов обратно в текст.",
			explain: "Правильно! Токенизатор может использоваться как для токенизации, так и для де-токенизации.",
            correct: true
		}
	]}
/>

### 2. Сколько измерений имеет тензор, выводимый базовой моделью Transformer, и каковы они?

<Question
	choices={[
		{
			text: "2: Длина последовательности и размер батча",
			explain: "Неверно! Тензор, выводимый моделью, имеет третье измерение: скрытый размер."
		},
		{
			text: "2: Длина последовательности и скрытый размер",
			explain: "Неверно! Все модели Transformer работают с батчами, даже с одной последовательностью; размер батча будет равен 1!"
		},
		{
			text: "3: Длина последовательности, размер батча и скрытый размер",
			explain: "Верно!",
            correct: true
		}
	]}
/>

### 3. Что из перечисленного ниже является примером токенизации по подсловам?

<Question
	choices={[
		{
			text: "WordPiece",
			explain: "Да, это один из примеров токенизации по подсловам!",
            correct: true
		},
		{
			text: "Посимвольная токенизация",
			explain: "Токенизация по символам не является разновидностью токенизации по подсловам."
		},
		{
			text: "Разделение по пробелам и пунктуации",
			explain: "Это схема токенизации по подсловам!"
		},
		{
			text: "BPE",
			explain: "Да, это один из примеров токенизации по подсловам!",
            correct: true
        },
		{
			text: "Unigram",
			explain: "Да, это один из примеров токенизации по подсловам!",
            correct: true
        },
		{
			text: "Ничего из вышеперечисленного",
			explain: "Неверно!"
        }
	]}
/>

### 4. Что такое голова модели?

<Question
	choices={[
		{
			text: "Компонент базовой сети Transformer, который перенаправляет тензоры на корректные слои",
			explain: "Неверно! Такого компонента не существует."
		},
		{
			text: "Также известный как механизм самовнимания, он адаптирует представление токена в соответствии с другими токенами последовательности",
			explain: "Неверно! Слой самовнимания действительно содержит \"головы\" внимания, но это не головы адаптации."
		},
		{
			text: "Дополнительный компонент, обычно состоящий из одного или нескольких слоев, для преобразования прогнозов трансформера в специфический для конкретной задачи выход",
			explain: "Именно так. Адаптационные головы, также известные просто как головы, встречаются в разных формах: головы языкового моделирования, головы ответов на вопросы, головы классификации последовательностей... ",
			correct: true
		} 
	]}
/>

{#if fw === 'pt'}
### 5. Что такое AutoModel?

<Question
	choices={[
		{
			text: "Модель, которая автоматически обучается на ваших данных",
			explain: "Неверно. Вы не путаете это с нашим продуктом <a href='https://huggingface.co/autotrain'>AutoTrain</a>?"
		},
		{
			text: "Объект, возвращающий правильную архитектуру на основе контрольной точки",
			explain: "Именно: в <code>AutoModel</code> для возврата правильной архитектуры достаточно знать контрольную точку, с которой нужно инициализироваться.",
			correct: true
		},
		{
			text: "Модель, которая автоматически определяет язык, используемый для входов, чтобы загрузить правильные веса",
			explain: "Неверно; хотя некоторые контрольные точки и модели способны работать с несколькими языками, встроенных инструментов для автоматического выбора контрольной точки в зависимости от языка не существует. Вам следует обратиться в <a href='https://huggingface.co/models'>Model Hub</a>, чтобы найти лучшую контрольную точку для вашей задачи!"
		} 
	]}
/>

{:else}
### 5. Что такое TFAutoModel?

<Question
	choices={[
		{
			text: "Модель, которая автоматически обучается на ваших данных",
			explain: "Неправильно. Вы путаете это с нашим продуктом <a href='https://huggingface.co/autotrain'>AutoTrain</a>?"
		},
		{
			text: "Объект, возвращающий правильную архитектуру на основе контрольной точки",
			explain: "Именно так: в <code>TFAutoModel</code> для возврата правильной архитектуры достаточно знать контрольную точку, с которой нужно инициализироваться.",
			correct: true
		},
		{
			text: "Модель, которая автоматически определяет язык, используемый на входах, чтобы загрузить правильные веса",
			explain: "Неверно; хотя некоторые контрольные точки и модели способны работать с несколькими языками, встроенных инструментов для автоматического выбора контрольной точки в зависимости от языка не существует. Вам следует обратиться в <a href='https://huggingface.co/models'>Model Hub</a>, чтобы найти лучшую контрольную точку для вашей задачи!"
		} 
	]}
/>

{/if}

### 6. На какие техники следует обратить внимание при объединении в батч последовательностей разной длины?

<Question
	choices={[
		{
			text: "Усечение (truncating)",
			explain: "Да, усечение - это правильный способ выровнять последовательности так, чтобы они вписывались в прямоугольную форму. Но разве это единственный способ?",
			correct: true
		},
		{
			text: "Возврат тензоров",
			explain: "Хотя другие методы позволяют возвращать прямоугольные тензоры, возвращение тензоров не поможет при объединении последовательностей в батч."
		},
		{
			text: "Дополнение (padding)",
			explain: "Да, дополнение - это правильный способ выровнять последовательности так, чтобы они вписывались в прямоугольную форму. Но разве это единственный способ?",
			correct: true
		}, 
		{
			text: "Маскирование внимания (attention masking)",
			explain: "Обязательно! Маски внимания имеют первостепенное значение при работе с последовательностями разной длины. Однако это не единственная техника, о которой следует помнить.",
			correct: true
		} 
	]}
/>

### 7. В чем смысл применения функции SoftMax к логитам, выводимым моделью классификации последовательностей?

<Question
	choices={[
		{
			text: "Она смягчает логиты, чтобы они были более надежными.",
			explain: "Нет, функция SoftMax не влияет на достоверность результатов."
		},
		{
			text: "Она применяет нижнюю и верхнюю границы, чтобы они были понятны.",
			explain: "Правильно! Результирующие значения ограничены между 0 и 1. Однако это не единственная причина, по которой мы используем функцию SoftMax.",
            correct: true
		},
		{
			text: "Общая сумма вывода равна 1, что позволяет получить вероятностную интерпретацию.",
			explain: "Правильно! Однако это не единственная причина, по которой мы используем функцию SoftMax.",
            correct: true
		}
	]}
/>

### 8. Какой метод является основным в API токенизатора?

<Question
	choices={[
		{
			text: "<code>encode</code>, поскольку он может кодировать текст в идентификаторы и идентификаторы в прогнозы.",
			explain: "Неверно! Если метод <code>encode</code> существует в токенизаторах, то в моделях его нет."
		},
		{
			text: "Вызов объекта токенизатора напрямую.",
			explain: "Точно! Метод <code>__call__</code> токенизатора - это очень мощный метод, который может обрабатывать практически все. Это также метод, используемый для получения прогнозов из модели.",
			correct: true
		},
		{
			text: "<code>pad</code>",
			explain: "Неверно! Дополнение очень полезно, но это всего лишь одна из частей API токенизатора."
		},
		{
			text: "<code>tokenize</code>",
			explain: "Метод <code>tokenize</code>, пожалуй, один из самых полезных методов, но он не является ядром API токенизатора."
		}
	]}
/>

### 9. Что содержит переменная `result` в этом примере кода?

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
result = tokenizer.tokenize("Hello!")
```

<Question
	choices={[
		{
			text: "Список строк, каждая из которых является токеном",
			explain: "Конечно! Преобразуйте это в идентификаторы и отправьте их в модель!",
            correct: true
		},
		{
			text: "Список идентификаторов",
			explain: "Неверно; для этого и нужен метод <code>__call__</code> или <code>convert_tokens_to_ids</code>!"
		},
		{
			text: "Строка, содержащая все токены",
			explain: "Это было бы неоптимально, поскольку цель состоит в том, чтобы разбить строку на множество токенов."
		}
	]}
/>

{#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)
```

<Question
	choices={[
		{
			text: "Нет, кажется, все правильно.",
			explain: "К сожалению, сопряжение модели с токенизатором, который был обучен на другой контрольной точке, редко бывает хорошей идеей. Модель не была обучена извлекать смысл из вывода этого токенизатора, поэтому вывод модели (если она вообще сможет работать!) не будет иметь никакого смысла."
		},
		{
			text: "Токенизатор и модель всегда должны быть из одной контрольной точки.",
			explain: "Правильно!",
            correct: true
		},
		{
			text: "Хорошей практикой является дополнении и усечении с помощью токенизатора, поскольку каждый вход - это батч.",
			explain: "Действительно, каждый вход модели должен быть батчем. Однако в усечении или дополнении этой последовательности нет смысла, поскольку она только одна, а это техника объединения в батч списка предложений."
		}
	]}
/>

{: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)
```

<Question
	choices={[
		{
			text: "Нет, кажется, все правильно.",
			explain: "К сожалению, сопряжение модели с токенизатором, который был обучен на другой контрольной точке, редко бывает хорошей идеей. Модель не была обучена извлекать смысл из вывода этого токенизатора, поэтому вывод модели (если она вообще сможет работать!) не будет иметь никакого смысла."
		},
		{
			text: "Токенизатор и модель всегда должны быть из одной контрольной точки.",
			explain: "Верно!",
            correct: true
		},
		{
			text: "Хорошей практикой является дополнении и усечении с помощью токенизатора, поскольку каждый вход - это батч.",
			explain: "Действительно, каждый вход модели должен быть батчем. Однако в усечении или дополнении этой последовательности нет смысла, поскольку она только одна, а это техника объединения в батч списка предложений."
		}
	]}
/>

{/if}


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/8.mdx" />

### Токенизаторы[[tokenizers]]
https://huggingface.co/learn/course/ru/chapter2/4.md

# Токенизаторы[[tokenizers]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section4_tf.ipynb"},
]} />

{/if}

<Youtube id="VFp38yj8h3A"/>

Токенизаторы - один из основных компонентов конвейера NLP. Они служат одной цели: преобразовать текст в данные, которые могут быть обработаны моделью. Модели могут обрабатывать только числа, поэтому токенизаторы должны преобразовывать наш текст в числовые данные. В этом разделе мы рассмотрим, что именно происходит в конвейере токенизации. 

В задачах NLP данные, которые обычно подвергаются обработке, представляют собой необработанный текст. Вот пример такого текста:

```
Jim Henson was a puppeteer
```

Однако модели могут обрабатывать только числа, поэтому нам нужно найти способ преобразовать исходный текст в числа. Этим занимаются токенизаторы, и существует множество способов сделать это. Цель состоит в том, чтобы найти наиболее осмысленное представление - то есть то, которое имеет наибольший смысл для модели, - и, если возможно, наименьшее представление.

Давайте рассмотрим несколько примеров алгоритмов токенизации и постараемся ответить на некоторые вопросы, которые могут у вас возникнуть по токенизации.

## На основе слов[[word-based]]

<Youtube id="nhJxYji1aho"/>

Первый тип токенайзера, который приходит на ум, - это _на основе слов (word-based)_. Как правило, его очень легко настроить и использовать с помощью всего нескольких правил, и он часто дает достойные результаты. Например, на изображении ниже цель состоит в том, чтобы разбить исходный текст на слова и найти для каждого из них числовое представление:

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization.svg" alt="Пример токенизации на базе слов."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/word_based_tokenization-dark.svg" alt="Пример токенизации на базе слов."/>
</div>

Разделить текст можно разными способами. Например, мы можем использовать пробельные символы, чтобы разделить текст на слова, применив функцию Python `split()`:

```py
tokenized_text = "Jim Henson was a puppeteer".split()
print(tokenized_text)
```

```python out
['Jim', 'Henson', 'was', 'a', 'puppeteer']
```

Существуют также разновидности токенизаторов слов, которые содержат дополнительные правила для пунктуации. Используя такой токенизатор, мы можем получить довольно большие "словари", где словарь определяется общим количеством независимых токенов, которые есть в нашем корпусе.

Каждому слову присваивается идентификатор, начиная с 0 и заканчивая размером словаря. Модель использует эти идентификаторы для идентификации каждого слова.

Если мы хотим полностью покрыть язык с помощью токенизатора, основанного на словах, нам понадобится идентификатор для каждого слова в языке, что приведет к созданию огромного количества токенов. Например, в английском языке более 500 000 слов, поэтому, чтобы построить карту соответствия каждого слова входному идентификатору, нам нужно будет отслеживать такое количество идентификаторов. Кроме того, такие слова, как "dog", представляются иначе, чем слова типа "dogs", и модель изначально не будет знать, что "dog" и "dogs" похожи: она определит эти два слова как несвязанные. То же самое относится и к другим похожим словам, например "run" и "running", которые модель изначально не будет воспринимать как похожие.

Наконец, нам нужен специальный токен для обозначения слов, которых нет в нашем словаре. Это так называемый "unknown" токен, часто представляемый как "[UNK]" или "&lt;unk&gt;". Обычно это плохой знак, если вы видите, что токенизатор выдает много таких токенов, поскольку он не смог получить разумное представление слова, и вы теряете информацию на этом этапе. При создании словаря целью является сделать это таким образом, чтобы токенизатор как можно меньше слов токенизировал как неизвестный токен.

Один из способов уменьшить количество неизвестных токенов - это пойти на один уровень глубже, используя _основанный на символах (character-based)_ токенизатор.

## На основе символов[[character-based]]

<Youtube id="ssLq_EK2jLE"/>

Токенизаторы на основе символов (character-based) разбивают текст на символы, а не на слова. Это дает два основных преимущества:

- Словарь намного меньше.
- Неизвестных токенов гораздо меньше, поскольку каждое слово может быть образовано из символов.

Но и здесь возникают некоторые вопросы, связанные с пробелами и пунктуацией:

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization.svg" alt="Пример токенизации, основанной на символах."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/character_based_tokenization-dark.svg" alt="Пример токенизации, основанной на символах."/>
</div>

Такой подход тоже не идеален. Поскольку представление теперь основано на символах, а не на словах, можно утверждать, что интуитивно оно менее осмысленно: каждый символ сам по себе мало что значит, в то время как в случае со словами это не так. Однако это опять же зависит от языка: например, в Китайском языке каждый символ несет больше информации, чем символ в латинском языке.

Еще один момент, который следует учитывать, - это то, что в итоге мы получим очень большое количество токенов для обработки нашей моделью: если при использовании токенизатора, основанного на словах, слово будет состоять только из одного токена, то при преобразовании в символы оно может легко превратиться в 10 или более токенов.

Чтобы получить лучшее из обоих миров, мы можем использовать третью технику, которая объединяет эти два подхода: *токенизацию по подсловам (subword tokenization)*.

## Токенизация по подсловам[[subword-tokenization]]

<Youtube id="zHvTiHr506c"/>

Алгоритмы токенизации подслов (subword tokenization) основываются на принципе, согласно которому часто используемые слова не должны разбиваться на более мелкие подслова, а редкие слова должны быть разложены на значимые подслова.

Например, "annoyingly" может считаться редким словом и может быть разложено на "annoying" и "ly". Оба они, скорее всего, будут чаще появляться как самостоятельные подслова, но в то же время значение " annoyingly" сохраняется за счет составного значения "annoying" и "ly".

Вот пример, показывающий, как алгоритм токенизации подслов будет токенизировать последовательность "Let's do tokenization!":

<div class="flex justify-center">
  <img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword.svg" alt="Алгоритм токенизации по подсловам."/>
  <img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/bpe_subword-dark.svg" alt="Алгоритм токенизации по подсловам."/>
</div>

Эти подслова в конечном итоге несут в себе большой семантический смысл: например, в приведенном выше примере "tokenization" было разделено на "token" и "ization" - два токена, которые несут в себе семантический смысл и при этом занимают мало места (для представления длинного слова требуется всего два токена). Это позволяет нам получить относительно хорошее покрытие при небольшом размере словаря и почти полном отсутствии неизвестных токенов.

Этот подход особенно полезен в агглютинативных языках, таких как турецкий, где вы можете образовывать (почти) произвольно длинные сложные слова, соединяя подслова.

### И не только![[and-more]]

Неудивительно, что существует множество других техник. Вот лишь некоторые из них:

- Byte-level BPE, на уровне байтов, используется в GPT-2
- WordPiece, используемый в BERT
- SentencePiece или Unigram, используемый в нескольких многоязычных моделях

Теперь у вас должно быть достаточно знаний о том, как работают токенизаторы, чтобы приступить к работе с API.

## Загрузка и сохранение[[loading-and-saving]]

Загрузка и сохранение токенизаторов так же проста, как и в случае с моделями. Фактически, они основаны на тех же двух методах: `from_pretrained()` и `save_pretrained()`. Эти методы загружают или сохраняют алгоритм, используемый токенизатором (что-то вроде *архитектуры* модели), а также его словарь (что-то вроде *весов* модели).

Загрузка токенизатора BERT, обученного на той же контрольной точке, что и BERT, выполняется так же, как и загрузка модели, за исключением того, что мы используем класс `BertTokenizer`:

```py
from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
```

{#if fw === 'pt'}
Подобно `AutoModel`, класс `AutoTokenizer` будет захватывать нужный класс токенизатора в библиотеке, основываясь на имени контрольной точки, и может быть использован непосредственно с любой контрольной точкой:

{:else}
Подобно `TFAutoModel`, класс `AutoTokenizer` захватит нужный класс токенизатора в библиотеке, основываясь на имени контрольной точки, и может быть использован непосредственно с любой контрольной точкой:

{/if}

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
```

Теперь мы можем использовать токенизатор, как показано в предыдущем разделе:

```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]}
```

Сохранение токенизатора идентично сохранению модели:

```py
tokenizer.save_pretrained("directory_on_my_computer")
```

Подробнее о `token_type_ids` мы поговорим в [Главе 3](../chapter3/1), а ключ `attention_mask` мы объясним чуть позже. Сначала давайте посмотрим, как генерируются `input_ids`. Для этого нам понадобится рассмотреть промежуточные методы токенизатора.

## Кодирование[[encoding]]

<Youtube id="Yffk5aydLzg"/>

Перевод текста в числа называется _кодированием (encoding)_. Кодирование выполняется в два этапа: токенизация, а затем преобразование во входные идентификаторы.

Как мы уже видели, первым шагом является разбиение текста на слова (или части слов, знаки препинания и т. д.), обычно называемые *токенами*. Существует множество правил, которые могут управлять этим процессом, поэтому нам нужно инстанцировать токенизатор, используя имя модели, чтобы убедиться, что мы используем те же правила, которые были использованы во время предварительного обучения модели.

Второй шаг - преобразование этих токенов в числа, чтобы мы могли построить из них тензор и передать его в модель. Для этого у токенизатора есть *словарь*, который мы загружаем, когда инстанцируем его с помощью метода `from_pretrained()`. Опять же, нам нужно использовать тот же словарь, который использовался при предварительном обучении модели.

Чтобы лучше понять эти два этапа, мы рассмотрим их по отдельности. Обратите внимание, что мы будем использовать некоторые методы, выполняющие части конвейера токенизации отдельно, чтобы показать вам промежуточные результаты этих шагов, но на практике вы должны вызывать токенизатор непосредственно на ваших входных данных (как показано в разделе 2).

### Токенизация[[tokenization]]

Процесс токенизации выполняется методом `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)
```

Результатом работы этого метода является список строк, или токенов:

```python out
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
```

Этот токенизатор является токенизатором подслов: он разбивает слова до тех пор, пока не получит токены, которые могут быть представлены в его словаре. В данном случае слово `transformer` разбивается на два токена: `transform` и `##er`.

### От токенов к идентификаторам входа[[from-tokens-to-input-ids]]

Преобразование во входные идентификаторы выполняется методом токенизатора `convert_tokens_to_ids()`:

```py
ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)
```

```python out
[7993, 170, 11303, 1200, 2443, 1110, 3014]
```

Эти выходы, преобразованные в тензор соответствующего фреймворка, могут быть использованы в качестве входов в модель, как было показано ранее в этой главе.

<Tip>

✏️ **Попробуйте! ** Повторите два последних шага (токенизацию и преобразование во входные идентификаторы) на входных предложениях, которые мы использовали в разделе 2 ("I've been waiting for a HuggingFace course my whole life." и "I hate this so much!"). Убедитесь, что вы получили те же самые входные идентификаторы, которые мы получали ранее!

</Tip>

## Декодирование[[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` не только преобразует индексы обратно в токены, но и группирует токены, которые были частью одних и тех же слов, чтобы создать читаемое предложение. Такое поведение будет очень полезно, когда мы будем использовать модели, прогнозирующие новый текст (либо текст, сгенерированный из подсказки (prompt), либо для решения задачи преобразования последовательности-в-последовательность (sequence-to-sequence), такой как перевод или резюмирование).

Теперь вы должны понимать, какие атомарные операции может выполнять токенизатор: токенизация, преобразование в идентификаторы и преобразование идентификаторов обратно в строку. Однако мы лишь пощупали верхушку айсберга. В следующем разделе мы рассмотрим ограничения нашего подхода и посмотрим, как их преодолеть.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/4.mdx" />

### Базовое использование завершено![[basic-usage-completed]]
https://huggingface.co/learn/course/ru/chapter2/7.md

# Базовое использование завершено![[basic-usage-completed]]

<CourseFloatingBanner
    chapter={2}
    classNames="absolute z-10 right-0 top-0"
/>

Отличная работа по изучению курса до этого места! Напомним, что в этой главе вы:

- Узнали об основных составляющих блоках модели Transformer.
- Узнали, из чего состоит конвейер токенизации.
- Узнали, как использовать модель Transformer на практике.
- Узнали, как использовать токенизатор для преобразования текста в тензоры, понятные модели.
- Настроили токенизатор и модель вместе, чтобы перейти от текста к прогнозам.
- Узнали об ограничениях входных идентификаторов и познакомились с масками внимания.
- Поиграли с универсальными и настраиваемыми методами токенизатора.

С этого момента вы должны свободно ориентироваться в документации 🤗 Transformers: лексикон будет звучать знакомо, и вы уже познакомились с методами, которые будете использовать чаще всего.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/7.mdx" />

### За конвейером[[behind-the-pipeline]]
https://huggingface.co/learn/course/ru/chapter2/2.md

# За конвейером[[behind-the-pipeline]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter2/section2_tf.ipynb"},
]} />

{/if}

<Tip>
Это первый раздел, в котором содержание немного отличается в зависимости от того, используете ли вы PyTorch или TensorFlow. Переключите переключатель в верхней части заголовка, чтобы выбрать платформу, которую вы предпочитаете!
</Tip>

{#if fw === 'pt'}
<Youtube id="1pedAIvTWXk"/>
{:else}
<Youtube id="wVN12smEvqg"/>
{/if}

Давайте начнем с полноценного примера и посмотрим, что произошло за кулисами, когда мы выполнили следующий код в [Главе 1](../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!",
    ]
)
```

и получили:

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

Как мы видели в [Главе 1] (../chapter1), этот конвейер объединяет три этапа: предобработку, пропуск входных данных через модель и постобработку:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline.svg" alt="Полный конвейер NLP: токенизация текста, конвертация в идентификаторы, инференс через модель Transformer и голову модели."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline-dark.svg" alt="Полный конвейер NLP: токенизация текста, конвертация в идентификаторы, инференс через модель Transformer и голову модели."/>
</div>

Давайте быстро пройдемся по каждому из них.

## Предобработка с помощью токенизатора[[preprocessing-with-a-tokenizer]]

Как и другие нейронные сети, модели Transformer не могут напрямую обрабатывать сырой текст, поэтому первым шагом нашего конвейера является преобразование текстовых данных в числа, которые сможет воспринимать модель. Для этого мы используем *токенизатор*, который будет отвечать за:

- Разбиение входных данных на слова, подслова или символы (например, пунктуацию), которые называются *токенами*
- Сопоставление каждого токена с целым числом
- Добавление дополнительных входных данных, которые могут быть полезны для модели

Вся эта предобработка должна быть выполнена точно так же, как и при предварительном обучении модели, поэтому сначала нам нужно загрузить эту информацию из [Model Hub](https://huggingface.co/models). Для этого мы используем класс `AutoTokenizer` и его метод `from_pretrained()`. Используя имя контрольной точки нашей модели, он автоматически получает данные, ассоциированные с токенизатором модели, и кэширует их (таким образом, они скачиваются только в первый раз, когда вы выполняете приведенный ниже код).

Поскольку контрольной точкой по умолчанию конвейера `sentiment-analysis` является `distilbert-base-uncased-finetuned-sst-2-english` (вы можете посмотреть ее карточку модели [здесь](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), мы выполняем следующее:

```python
from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
```

Когда у нас есть токенизатор, мы можем напрямую передать ему наши предложения и получить в ответ словарь, готовый к передаче в нашу модель! Осталось только преобразовать список входных идентификаторов в тензоры.

Вы можете использовать 🤗 Transformers, не задумываясь о том, какой ML-фреймворк используется в качестве бэкенда; это может быть PyTorch или TensorFlow, или Flax для некоторых моделей. Однако модели Transformer принимают на вход только *тензоры*. Если вы впервые слышите о тензорах, то можете считать их массивами NumPy. Массив NumPy может быть скаляром (0D), вектором (1D), матрицей (2D) или иметь больше измерений. По сути, это тензор; тензоры других ML-фреймворков ведут себя аналогично, и их обычно так же просто инстанцировать, как и массивы NumPy.

Чтобы указать тип тензоров, которые мы хотим получить в ответ (PyTorch, TensorFlow или обычный NumPy), мы используем аргумент `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}

Не беспокойтесь пока о дополнении и усечении, мы расскажем об этом позже. Главное, что нужно запомнить: вы можете передать одно предложение или список предложений, а также указать тип тензоров, которые вы хотите получить в ответ (если тип не указан, то в результате вы получите список списков).

{#if fw === 'pt'}

Вот как выглядят результаты в виде тензоров 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, 1, 0, 0, 0, 0, 0, 0, 0, 0]
    ])
}
```
{:else}

Вот как выглядят результаты в виде тензоров TensorFlow:

```python out
{
    'input_ids': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [  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]
        ], dtype=int32)>, 
    'attention_mask': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [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, 0, 0]
        ], dtype=int32)>
}
```
{/if}

Сам результат представляет собой словарь, содержащий два ключа, `input_ids` и `attention_mask`. `input_ids` содержит две строки целых чисел (по одной на каждое предложение), которые являются уникальными идентификаторами токенов в каждом предложении. Мы объясним, что такое `attention_mask` позже в этой главе.  

## Проходя сквозь модель[[going-through-the-model]]

{#if fw === 'pt'}
Мы можем загрузить нашу предварительно обученную модель так же, как мы это делали с нашим токенизатором. 🤗 Transformers предоставляет класс `AutoModel`, у которого также есть метод `from_pretrained()`:

```python
from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
```
{:else}
Мы можем загрузить нашу предварительно обученную модель так же, как мы это делали с нашим токенизатором. 🤗 Transformers предоставляет класс `TFAutoModel`, в котором также есть метод `from_pretrained`:

```python
from transformers import TFAutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModel.from_pretrained(checkpoint)
```
{/if}

В этом фрагменте кода мы загрузили ту же контрольную точку, которую использовали в нашем конвейере ранее (на самом деле она уже должна была быть кэширована), и инстанцировали модель с ее помощью.

Эта архитектура содержит только базовый модуль Transformer: при наличии некоторых входных данных он выводит то, что мы будем называть *скрытыми состояниями (hidden states)*, также известными как *признаки (features)*. Для каждого входа модели мы получим многомерный вектор, представляющий **контекстное понимание этого входа моделью Transformer**.

Если вы не поняли смысла, не волнуйтесь. Мы объясним всё это позже.

Хотя эти скрытые состояния могут быть полезны сами по себе, обычно они являются входными данными для другой части модели, известной как *голова (head)*. В [Главе 1](../chapter1) различные задачи могли быть выполнены с помощью одной и той же архитектуры, но каждая из этих задач будет связана с разной головой.

### Многомерный вектор?[[a-high-dimensional-vector]]

Вектор, возвращаемый модулем Transformer, обычно большой. Обычно он имеет три измерения:

- **Размер батча (Batch size)**: Количество последовательностей, обрабатываемых за один раз (в нашем примере - 2).
- **Длина последовательности (Sequence length)**: Длина числового представления последовательности (в нашем примере - 16).
- **Скрытый размер (Hidden size)**: Размерность вектора каждого входа модели.

О нем говорят как о "многомерном" из-за последнего значения. Скрытый размер может быть очень большим (768 - обычное явление для небольших моделей, а в больших моделях он может достигать 3072 и более).

Мы можем убедиться в этом, если подадим в нашу модель входные данные, которые мы подвергли предобработке:

{#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}

Обратите внимание, что выходы моделей 🤗 Transformers ведут себя как `именованные кортежи` или словари. Вы можете обращаться к элементам по атрибутам (как мы это делали), по ключу (`outputs["last_hidden_state"]`) или даже по индексу, если вы точно знаете, где находится искомый элемент (`outputs[0]`).

### Головы моделей: Извлечение смысла из чисел[[model-heads-making-sense-out-of-numbers]]

Головы модели принимают на вход многомерный вектор скрытых состояний и проецируют его на другое измерение. Обычно они состоят из одного или нескольких линейных слоев:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head.svg" alt="Сеть-трансформер с головой."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head-dark.svg" alt="Сеть-трансформер с головой."/>
</div>

Выход модели Transformer передается непосредственно в голову модели для обработки.

На этой диаграмме модель представлена слоем эмбеддингов и последующими слоями. Слой эмбеддингов преобразует каждый входной идентификатор в токенизированном входе в вектор, который представляет соответствующий токен. Последующие слои манипулируют этими векторами с помощью механизма внимания, чтобы получить окончательное представление предложений.

Существует множество различных архитектур 🤗 Transformers, каждая из которых предназначена для решения определенной задачи. Вот неполный список:

- `*Model` (извлечение скрытых состояний)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- и другие 🤗

{#if fw === 'pt'}
Для нашего примера нам понадобится модель с головой классификации последовательности (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Поэтому мы будем использовать не класс `AutoModel`, а `AutoModelForSequenceClassification`:

```python
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
```
{:else}
Для нашего примера нам понадобится модель с головой классификации последовательности (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Поэтому мы будем использовать не класс `TFAutoModel`, а `TFAutoModelForSequenceClassification`:

```python
from transformers import TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(inputs)
```
{/if}

Теперь, если мы посмотрим на форму наших выходов, размерность будет гораздо ниже: голова модели принимает на вход высокоразмерные векторы, которые мы видели ранее, и возвращает векторы, содержащие два значения (по одному на метку):

```python
print(outputs.logits.shape)
```

{#if fw === 'pt'}
```python out
torch.Size([2, 2])
```
{:else}
```python out
(2, 2)
```
{/if}

Поскольку у нас всего два предложения и две метки, результат, полученный с помощью нашей модели, имеет форму 2 x 2.

## Постобработка вывода[[postprocessing-the-output]]

Значения, которые мы получаем на выходе из нашей модели, не всегда имеют смысл сами по себе. Давайте посмотрим:

```python
print(outputs.logits)
```

{#if fw === 'pt'}
```python out
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
```
{:else}
```python out
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-1.5606991,  1.6122842],
           [ 4.169231 , -3.3464472]], dtype=float32)>
```
{/if}

Наша модель спрогнозировала `[-1.5607, 1.6123]` для первого предложения и `[ 4.1692, -3.3464]` для второго. Это не вероятности, а *логиты*, сырые, ненормированные оценки, выведенные последним слоем модели. Чтобы преобразовать их в вероятности, они должны пройти через слой [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (все модели 🤗 Transformers выводят логиты, поскольку функция потерь для обучения обычно объединяет последнюю функцию активации, такую как SoftMax, с фактической функцией потерь, такой как кросс-энтропия):

{#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=<SoftmaxBackward>)
```
{:else}
```python out
tf.Tensor(
[[4.01951671e-02 9.59804833e-01]
 [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32)
```
{/if}

Теперь мы видим, что модель спрогнозировала `[0.0402, 0.9598]` для первого предложения и `[0.9995, 0.0005]` для второго. Это узнаваемые оценки вероятности.

Чтобы получить метки, соответствующие каждой позиции, мы можем обратиться к атрибуту `id2label` в конфигурации модели ( более подробно об этом в следующем разделе):

```python
model.config.id2label
```

```python out
{0: 'NEGATIVE', 1: 'POSITIVE'}
```

Теперь мы можем сделать вывод, что модель спрогнозировала следующее:
 
- Первое предложение: NEGATIVE: 0.0402, POSITIVE: 0.9598
- Второе предложение: NEGATIVE: 0.9995, POSITIVE: 0.0005

Мы успешно воспроизвели три этапа конвейера: предобработку с помощью токенизаторов, прохождение входных данных через модель и постобработку! Теперь давайте уделим немного времени тому, чтобы углубиться в каждый из этих этапов.

<Tip>

✏️ **Попробуйте! ** Выберите два (или более) собственных текста и пропустите их через конвейер `sentiment-analysis`. Затем повторите описанные здесь шаги и убедитесь, что вы получили те же результаты!

</Tip>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter2/2.mdx" />

### Введение
https://huggingface.co/learn/course/ru/chapter0/1.md

# Введение

Добро пожаловать на курс от Hugging Face! Это введение поможет настроить рабочее окружение. Если вы только начинаете курс, мы рекомендуем сначала заглянуть в [Главу 1](../chapter1/1), затем вернуться и настроить среду, чтобы попробовать запустить код самостоятельно. 

Все библиотеки, которые мы будем использовать в этом курсе, доступны в качестве Python-пакетов. В этом уроке мы покажем, как установить окружение и необходимые библиотеки.

Мы рассмотрим два пути настройки окружения: с использованием Google Colab и виртуального окружения Python. Можно выбрать любой из вариантов, исходя из собственных предпочтений. Если вы начинающий, то лучше начать с Google Colab. 

Если вы пользуетесь операционной системой Windows, то мы рекомендуем сразу использовать Google Colab, т.к. мы не будем рассматривать эту операционную систему в качестве платформы для работы. Если вы используете Linux или MacOS, то можно воспользоваться любым из описанных здесь подходов. 

Для прохождения курса вам понадобится аккаунт на Hugging Face, бесплатно можно зарегистрироваться здесь: [создать учетную запись](https://huggingface.co/join).

## Использование Colab

Использование Colab – самый простой вариант: просто загрузите блокнот в браузере и приступайте к работе! 

Если вы не знакомы с Google Colab, то мы рекомендуем начать с изучения [Введения](https://colab.research.google.com/notebooks/intro.ipynb). Colab позволяет использовать более мощную аппаратную базу (GPU - видеокарты, TPU - тензорные процессоры) и он бесплатен для небольших нагрузок. 

Как только вы освоитесь в Colab, создайте новый блокнот:

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/new_colab.png" alt="An empty colab notebook" width="80%"/>
</div>

Следующий шаг - установка библиотек, которые мы будем использовать в курсе. В качестве менеджера пакетов будет использоваться `pip`. Для запуска системной команды в ячейке блокнота необходимо в самом начале дописать символ `!`. Библиотеку  🤗 Transformers можно установить так: 

```
!pip install transformers
```

Вы можете удостовериться, что библиотека установилась, просто сделав ее импорт: 

```
import transformers
```

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter0/install.gif" alt="A gif showing the result of the two commands above: installation and import" width="80%"/>
</div>

Это установка самой базовой версии 🤗 Transformers. В частности, никаких библиотек машинного обучения (например, PyTorch или TensorFloat) установлено не будет. Так как мы будем использовать множество различных возможностей библиотеки 🤗 Transformers, мы рекомендуем установить версию для разработчиков, в состав которой сразу входят все необходимые зависимости:  

```
!pip install transformers[sentencepiece]
```

Это займет определенное время, но сразу после установки вы будете готовы двигаться дальше!

## Использование виртуального окружения Python

Если вы хотите использовать виртуальное окружение Python, сначала необходимо установить Python. Мы рекомендуем использовать [эти инструкции](https://realpython.com/installing-python/) для установки. 

После установки Python у вас появится возможность запускать Python-команды в терминале. Прежде чем переходить дальше, запустите в терминале команду `python --version`. В результате должна быть распечатана версия Python, доступная для работы. 

Когда вы запускаете Python-команду в терминале (например, `python --version`), эту команду обрабатывает _основной_ Python-интерпретатор вашей системы. Мы не рекомендуем устанавливать в его окружение дополнительные библиотеки, лучше для каждого проекта создавать отдельное виртуальное окружение. Каждый проект будет обладать собственными зависимостями и пакетами, если проекты будут в разных окружениях, то вам меньше придется следить за совместимостью библиотек. 

В Python такой подход можно реализовать с помощью разных библиотек, а подробнее об окружениях можно почитать [тут](https://docs.python.org/3/tutorial/venv.html). Каждое окружение будет содержать в себе необходимую версию языка и набор библиотек. Все эти окружения изолированы друг от друга. Среди самых популярных инструментов для работы с виртуальными окружениями можно отметить [`venv`](https://docs.python.org/3/library/venv.html#module-venv).

Для начала создайте папку в домашней директории, в которой будут храниться ваши файлы курса (ее можно назвать произвольным именем, например, *transformers-course*): 

```
mkdir ~/transformers-course
cd ~/transformers-course
```
Находясь в директории *transformers-course*, создайте виртуальное окружение с использованием `venv`: 

```
python -m venv .env
```

Теперь в папке должна появиться директория с именем *.env*: 

```
ls -a
```

```out
.      ..    .env
```

Переключаться между окружениями можно с помощью команд `activate` и `deactivate`: 

```
# Активировать виртуальное окружение
source .env/bin/activate

# Деактивировать окружение
deactivate
```

Вы можете убедиться, что окружение активировано с помощью команды `which python`: если ее результат указывает на виртуальное окружение, значит, вы успешно активировали его!

```
which python
```

```out
/home/<user>/transformers-course/.env/bin/python
```

### Установка зависимостей

Как и в предыдущем разделе, посвященном Google Colab, вам необходимо установить библиотеку 🤗 Transformers(желательно, версию для разработчиков) с помощью менеджера `pip`: 

```
pip install "transformers[sentencepiece]"
```

Теперь все установлено! Двигаемся дальше!


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter0/1.mdx" />

### Итоги
https://huggingface.co/learn/course/ru/chapter1/9.md

# Итоги

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

В этой главе вы увидели, как подходить к различным задачам NLP, используя высокоуровневую функцию `pipeline()` из библиотеки 🤗 Transformers. Вы также увидели, как искать и использовать модели в Hub, а также как использовать Inference API для тестирования моделей прямо в браузере.

Мы обсудили, как трансформеры работают на высоком уровне, и поговорили о важности трансферного обучения и дообучения. Ключевым аспектом является то, что вы можете использовать всю архитектуру или только кодировщик или декодировщик, в зависимости от того, какую задачу вы хотите решить. Следующая таблица резюмирует это:


| Модель                  | Примеры                                    | Задачи                                                                                                  |
|-------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------|
| Кодировщик              | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Классификация предложений, распознавание именованных сущностей, выделительные вопросно-ответные системы |
| Декодировщик            | CTRL, GPT, GPT-2, Transformer XL           | Генерация текста                                                                                        |
| Кодировщик-декодировщик | BART, T5, Marian, mBART                    | Автоматическое реферирование, перевод, генеративные вопросно-ответные системы                            |


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/9.mdx" />

### Модели-кодировщики
https://huggingface.co/learn/course/ru/chapter1/5.md

# Модели-кодировщики

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="MUqNwgPjJvQ" />

Кодировщики используют только компонент кодировщика трансформера. На каждом этапе слой внимания может использовать все слова исходного предложения. Эти модели часто характеризуют как имеющие двунаправленное внимание (англ. bi-directional attention), и часто называют моделями *автокодировщиками*. 

Предварительное обучение этих моделей обычно заключается в том, чтобы как-то исказить предложение (например, путем маскировки в нем случайных слов) и поставить перед моделью задачу найти или восстановить исходное предложение.

Кодировщики лучше всего подходят для задач, требующих _понимания_ всего предложения, таких как классификация предложений, распознавание именованных сущностей (и, в более общем смысле, классификация слов) и ответы на вопросы с извлечением информации из контекста (выделительные вопросно-ответные системы).

К представителям этого семейства моделей относятся: 

- [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)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/5.mdx" />

### Введение
https://huggingface.co/learn/course/ru/chapter1/1.md

# Введение

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

## Добро пожаловать на 🤗 курс!

<Youtube id="00GKzGyWFEs" />

В этом курсе вы научитесь основам обработки естественного языка (NLP) с использованием библиотек от [Hugging Face](https://huggingface.co/). Экосистема состоит из: моделей ([🤗 Transformers](https://github.com/huggingface/transformers)), датасетов ([🤗 Datasets](https://github.com/huggingface/datasets)), вспомогательных библиотек ([🤗 Accelerate](https://github.com/huggingface/accelerate), [🤗 Tokenizers](https://github.com/huggingface/tokenizers)), а также репозитория [Hugging Face Hub](https://huggingface.co/models). Это полностью бесплатно!

## Чего ожидать от курса?

Краткое описание курса:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary.svg" alt="Brief overview of the chapters of the course.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/summary-dark.svg" alt="Brief overview of the chapters of the course.">
</div>

- Главы 1-4 содержат в себе введение в главные концепции библиотеки 🤗 Transformers. К концу этой части курса вы будете знакомы с тем, как функционируют трансформеры, как применять модели из репозитория [Hugging Face Hub](https://huggingface.co/models), как дообучить модели на собственных данных и опубликовать результаты на Hugging Face Hub!
- Главы 5-8 научат вас основам библиотек 🤗 Datasets и 🤗 Tokenizers (датасеты и токенизаторы); это необходимо для дальнейшего погружения в область обработки естественного языка. К концу этой части вы научитесь решать наиболее распространенные задачи в  NLP самостоятельно!
- Главы 9-12 выходят за рамки NLP, в них описано, как можно применять трансформеры в задачах обработки речи и компьютерном зрении. Также вы узнаете, как создавать и демонстрировать свои модели, оптимизировать их для промышленного использования. После изучения этой части вы будете в силах применить 🤗 Transformers к (почти) любой задаче машинного обучения!

Этот курс: 

* Требует хорошего знания Python
* Будет лучше усвоен после ознакомления с курсом по глубокому обучению, например: [fast.ai's](https://www.fast.ai/) [Practical Deep Learning for Coders](https://course.fast.ai/) или одной из программ, подготовленных [DeepLearning.AI](https://www.deeplearning.ai/)
* Не предполагает предварительных знаний библиотек: [PyTorch](https://pytorch.org/) или [TensorFlow](https://www.tensorflow.org/), однако знакомство с ними поможет вам в дальнейшем

После прохождения текущего курса мы рекомендуем ознакомиться со специализацией от DeepLearning.AI: [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh), которая покрывает широкий спектр традиционных моделей NLP: от наивного Байеса до LSTM-сетей!


## Кто мы?

Об авторах:

[**Abubakar Abid**](https://huggingface.co/abidlabs) окончил PhD в области прикладного машинного обучения в Стэндфордском университете. Во время PhD, он основал [Gradio](https://github.com/gradio-app/gradio) - свободная библиотека для Python, с помощью которой увидели свет свыше 600000 тысяч демоверсий моделей машинного обучения. Hugging Face приобрел Gradio, и теперь Abubakar работает с нами в качестве руководителя разработки машинного обучения.

[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) - ML-инженер в Hugging Face. Живет в Дублине, Ирландия, и ранее работал инженером по машинному обучению в Parse.ly, а до этого — научным сотрудником в Тринити-колледже в Дублине. Он не верит, что мы сможем реализовать теорию сильного искусственного интеллекта за счет масштабирования существующих архитектур, но все равно возлагает большие надежды на бессмертие роботов.

[**Lysandre Debut**](https://huggingface.co/lysandre) - ML-инженер в Hugging Face, работает над библиотекой 🤗 Transformers с самых ранних этапов разработки. Его цель — сделать NLP доступным для всех, разработав инструменты с очень простым API.

[**Sylvain Gugger**](https://huggingface.co/sgugger) – инженер-исследователь в Hugging Face и один из ключевых участников разработки библиотеки 🤗 Transformers. Ранее работал научным сотрудником в fast.ai и написал книгу _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ в соавторстве с Jeremy Howard. Основное внимание в его исследованиях уделяется тому, чтобы сделать глубокое обучение более доступным путем разработки и улучшения методов, позволяющих моделям быстро обучаться при ограниченных ресурсах.

[**Dawood Khan**](https://huggingface.co/dawoodkhan82) - ML-инженер в Hugging Face. Dawood из Нью-Йорка, где он окончил Нью-Йоркский университет и получил степень бакалавра компьютерных наук. Проработав несколько лет iOS инженером, Dawood решил сменить работу и стал сооснователем Gradio. Позднее Hugging Face приобрел Gradio.

[**Merve Noyan**](https://huggingface.co/merve) - developer advocate в Hugging Face, работает над разработкой инструментов и созданием контента на их основе, чтобы  машинное обучение более доступным. 

[**Lucile Saulnier**](https://huggingface.co/SaulLu) - ML-инженер в Hugging Face, разрабатывающая и поддерживающая использование инструментов с открытым исходным кодом. Она также активно участвует во многих исследовательских проектах в области NLP, таких как совместное обучение и BigScience. 

[**Lewis Tunstall**](https://huggingface.co/lewtun) - ML-инженер в Hugging Face, сосредоточен на разработке инструментов с открытым исходным кодом и обеспечении их доступности для более широкого сообщества. Соавтор будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/).

[**Leandro von Werra**](https://huggingface.co/lvwerra) - ML-инженер в команде, работающей над открытым исходным кодом Hugging Face и соавтор будущей будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Обладает большим опытом реализации NLP-проектов в промышленности.

## ЧАВО

Мы собрали ответы на несколько часто задаваемых вопросов:

- **Получу ли я сертификат после прохождения этого курса?**
На данный момент у нас нет сертификации для этого курса. Мы работаем над получением сертификации для экосистемы Hugging Face. Следите за новостями!

- **Сколько времени мне нужно будет потратить на прохождение этого курса?**
Каждая глава этого курса рассчитана на неделю работы, то есть примерно 6-8 часов в неделю. Однако, вы можете проходить курс в любом удобном для вас ритме.

- **Где я могу задать вопрос по материалам курса?**
Если у вас возникли какие-либо вопросы по поводу любой части курса, просто нажмите на "*Ask a question*" наверху страницы, и вы будете автоматически перенаправлены в соответствующий раздел [форума Hugging Face](https://discuss.huggingface.co/) (форум на английском языке):

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/forum-button.png" alt="Link to the Hugging Face forums" width="75%">

Обратите внимание, что на форуме также доступен список [идей для проектов](https://discuss.huggingface.co/c/course/course-event/25), если вы хотите применить полученные знания на практике после прохождения курса.

- **Где я могу посмотреть на код, используемый в этом курсе?**
Внутри каждого раздела наверху страницы есть баннер, который позволит запустить код в Google Colab или Amazon SageMaker Studio Lab:

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/notebook-buttons.png" alt="Link to the Hugging Face course notebooks" width="75%">

Блокноты Jupyter со всем кодом, используемом в материалах курса, доступны в репозитории [`huggingface/notebooks`](https://github.com/huggingface/notebooks). Если вы хотите сгенерировать их на своем компьютере, вы можете найти инструкцию в репозитории [`course`](https://github.com/huggingface/course#-jupyter-notebooks) на GitHub.

- **Как я могу внести свой вклад в развитие курса?**
Существует множество способов внести свой вклад в наш курс! Если вы найдете опечатку или баг, пожалуйста, откройте вопрос (issue) в репозитории [`course`](https://github.com/huggingface/course). Если вы хотите помочь с переводом на ваш родной язык, вы можете найти инструкцию [здесь](https://github.com/huggingface/course#translating-the-course-into-your-language).

- **Какие стандарты использовались при переводе?**
Каждый перевод содержит глоссарий и файл `TRANSLATING.txt`, в которых описаны стандарты, используемые для перевода терминов и т.д. Вы можете посмотреть на пример для немецкого языка [здесь](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt).

- **Могу ли я использовать этот курс в своих целях?**
Конечно! Этот курс распространяется по либеральной лицензии [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0.html). Это означает, что вы должны упомянуть создателей этого курса, предоставить ссылку на лицензию и обозначить все изменения. Все это может быть сделано любым приемлемым способов, который, однако, не подразумевает, что правообладатель поддерживает вас или ваши действия по отношению этого курса. Если вы хотите процитировать этот курс, пожалуйста, используйте следующий BibTex:

```
@misc{huggingfacecourse,
  author = {Hugging Face},
  title = {The Hugging Face Course, 2022},
  howpublished = "\url{https://huggingface.co/course}",
  year = {2022},
  note = "[Online; accessed <today>]"
}
```

## Поехали!
Вы готовы начать? В этой главе вы узнаете: 

* Как использовать `pipeline()` для решения NLP-задач генерации и классификации текста
* Об архитектуре трансформеров
* Как различать архитектуры кодировщика, декодировщика и кодировщика-декодировщика и варианты их использования


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/1.mdx" />

### Трансформеры: на что они способны?
https://huggingface.co/learn/course/ru/chapter1/3.md

# Трансформеры: на что они способны? 

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter1/section3.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter1/section3.ipynb"},
]} />

В этом разделе мы посмотрим, на что способны трансформеры и первый раз применим функцию из библиотеки 🤗 Transformers: `pipeline()`.



<Tip>
👀 Видите кнопку <em>Open in Colab</em> справа сверху? Нажмите на нее, чтобы открыть блокнот в Google Colab с примерами кода этого раздела. Эта кнопка будет во всех разделах, которые содержат примеры кода.

Если вы хотите запускать ноутбуки локально, то обратите внимание на раздел <a href="/course/chapter0">setup</a>.
</Tip>

## Трансформеры повсюду!

Трансформеры используются для решения всех видов задач NLP, перечисленных в предыдущем разделе. Библиотека Transformers используется некоторыми компаниями и организациями, которые делятся своими моделями с сообществом Hugging Face: 

<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/companies.PNG" alt="Companies using Hugging Face" width="100%">

Библиотека [🤗 Transformers](https://github.com/huggingface/transformers) предоставляет различную функциональность для создания и использования этих моделей. [Model Hub](https://huggingface.co/models) содержит тысячи предобученных моделей, которые может скачать и использовать любой. Вы также можете загружать свои модели на Model Hub!

<Tip>
⚠️ Hugging Face Hub не ограничивается только моделями. Любой человек может поделиться своими моделями или датасетами! Для этого нужно создать учетную запись: <a href="https://huggingface.co/join">Create a huggingface.co</a> 
</Tip>


Прежде чем погрузиться в механизм работы трансформеров, давайте взглянем на несколько примеров того, как они могут быть использованы для решения задач NLP. 

## Работа с пайплайнами

<Youtube id="tiZFewofSLM" />

Самый базовый объект библиотеки 🤗 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}]
```

По умолчанию этот пайплайн выбирает специальную модель, которая была предобучена для оценки тональности предложений на английском языке. Модель загружается и кэшируется когда вы создадаете объект `classifier`. Если вы перезапустите команду, будет использована кэшированная модель, т.е. загрузки новой модели не произойдет. 

В процессе обработки пайплайном текста, который вы ему передали, есть три главных шага: 

1. Текст предобрабатывается в формат, который понятен модели. 
2. Предобработанный текст передается в модель. 
3. Результаты модели проходят через постобработку и вы получаете читаемый ответ. 

Некоторые из [доступных пайплайнов](https://huggingface.co/transformers/main_classes/pipelines.html) are:

- `feature-extraction` (превращение текста в векторы)
- `fill-mask`
- `ner` (распознавание именованных сущностей)
- `question-answering`
- `sentiment-analysis`
- `summarization`
- `text-generation`
- `translation`
- `zero-shot-classification`

Давайте взглянем на некоторые из них!

## Zero-shot classification

Мы начнем с более сложной задачи, где нам нужно классифицировать тексты, которые не были размечены (для них нет ответов - позитивный или негативный, агрессивный или нейтральный и тд). Это распространенный сценарий в реальных проектах, потому что разметка текста обычно занимает много времени и требует знаний в предметной области. Для этого варианта использования очень мощным является пайплайн `zero-shot-classification`: он позволяет указать, какие метки использовать для классификации, поэтому вам не нужно полагаться на метки предварительно обученной модели. Вы уже видели, как модель может классифицировать предложение как позитивное или негативное, используя эти две метки, но она также может классифицировать текст, используя любой другой набор меток, который вам подходит.

```python
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
classifier(
    "This is a course about the Transformers library",
    candidate_labels=["education", "politics", "business"],
)
```

```python out
{'sequence': 'This is a course about the Transformers library',
 'labels': ['education', 'business', 'politics'],
 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]}
```

Этот пайплайн называется _zero-shot_ потому, что вам нет необходимости дообучать модель для использования. Она может вернуть вам оценки вероятности для любого списка меток, который вы хотите!

<Tip>

✏️ **Попробуйте** Поэкспериментируйте с собственными предложениями и метками, и посмотрите как ведет себя модель!

</Tip>


## Генерация текста

Теперь давайте взглянем на пайплайн генерации текста (англ. text generation). Главная идея заключается в следующем: вы передаете на вход модели небольшой фрагмент текста, а модель будет продолжать его. Это похоже на предсказание следующего слова в клавиатурах различных смартфонов. Генерация текста содержит в себе элемент случайности, поэтому ваш результат может отличаться от того, который приведен ниже в примере. 

```python
from transformers import pipeline

generator = pipeline("text-generation")
generator("In this course, we will teach you how to")
```

```python out
[{'generated_text': 'In this course, we will teach you how to understand and use '
                    'data flow and data interchange when handling user data. We '
                    'will be working with one or more of the most commonly used '
                    'data flows — data flows of various types, as seen by the '
                    'HTTP'}]
```

Вы можете указать, сколько разных «ответов» сгенерирует модель, задав параметр `num_return_sequences`. Чтобы изменить длину ответной последовательности, нужно передать значение в аргумент `max_length`.

<Tip>

✏️ **Попробуйте!** Измените `num_return_sequences` и `max_length` так, чтобы были сгенерированы два ответа каждый из которых будет состоять из 15 слов. 

</Tip>


## Использование произвольной модели из Hub в пайплайне

Предыдущие примеры использовали модель по умолчанию для решения конкретной задачи, но у вас есть возможность выбрать произвольную модель из Hub и передать ее в пайплайн для конкретной задачи. Например, для генерации текста. Перейдите по ссылке [Model Hub](https://huggingface.co/models) и кликните на соответствующий тег слева, чтобы получить список доступных для этой задачи моделей. Вы должны увидеть страницу, подобную [этой](https://huggingface.co/models?pipeline_tag=text-generation). 


Давайте попробуем модель [`distilgpt2`](https://huggingface.co/distilgpt2)! Тут показано, как загрузить ее в такой же пайплайн, как в примерах выше:

```python
from transformers import pipeline

generator = pipeline("text-generation", model="distilgpt2")
generator(
    "In this course, we will teach you how to",
    max_length=30,
    num_return_sequences=2,
)
```

```python out
[{'generated_text': 'In this course, we will teach you how to manipulate the world and '
                    'move your mental and physical capabilities to your advantage.'},
 {'generated_text': 'In this course, we will teach you how to become an expert and '
                    'practice realtime, and with a hands on experience on both real '
                    'time and real'}]
```

Вы можете уточнить, для какого языка вам нужна модель, щелкнув на языковые теги, и выбрать ту, которая будет генерировать текст на другом языке. На Model Hub даже есть предобученные модели для многоязычных задач. 

Как только вы выберете модель, вы увидите, что есть виджет, позволяющий вам попробовать ее прямо на сайте. Таким образом, вы можете быстро протестировать возможности модели перед ее загрузкой.

<Tip>

✏️ **Попробуйте!** Найдите модель для другого языка и используйте виджет для проверки!

</Tip>

### The Inference API

Все модели могут быть протестированы прямо на сайте с использованием inference API, доступного по адресу [https://huggingface.co/](https://huggingface.co/). Вы можете попробовать применить модель, вводя различный текст и сразу же получая результат. 

Inference API также представляется как платный продукт, что пригодится для интегрирования моделей в свои рабочие процессы. Подробнее можно узнать на странице с [ценами](https://huggingface.co/pricing). 



## Заполнение пропусков

Следующая задача, на которую мы обратим внимание, связана с заполнением пропусков в тексте (англ. mask filling). Идея очень проста, мы продемонстрируем ее на простом тексте:


```python
from transformers import pipeline

unmasker = pipeline("fill-mask")
unmasker("This course will teach you all about <mask> 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>`, которое часто интерпретируют как *mask token (токен-маска)*. Другие модели могут использовать другие токены для обозначения пропуска, всегда лучше проверять это. Один из способов сделать это - обратить внимание на виджет для соответствующей модели. 

<Tip>

✏️ **Попробуйте!** Найдите в поиске модель `bert-based-cased` и обратите внимание на его токен-маску в виджете. Что эта модель предскажет, если применить ее в предыдущем примере?

</Tip>

## Распознавание именованных сущностей (NER)

Распознавание именованных сущностей (англ. named entity recognition) - это задача, в которой модели необходимо найти части текста, соответствующие некоторым сущностям, например: персонам, местам, организациям. Давайте посмотрим на пример: 

```python
from transformers import pipeline

ner = pipeline("ner", grouped_entities=True)
ner("My name is Sylvain and I work at Hugging Face in Brooklyn.")
```

```python out
[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, 
 {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, 
 {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57}
]
```

В этом примере модель корректно обозначила Sylvain как персону (PER), Hugging Face как организацию (ORG) и Brooklyn как локацию (LOC). 

Мы передали в пайплайн аргумент `grouped_entities=True` для того, чтобы модель сгруппировала части предложения, соответствующие одной сущности: в данном случае модель объединила "Hugging" и "Face" несмотря на то, что название организации состоит из двух слов. На самом деле, как мы увидим в следующей главе, препроцессинг делит даже отдельные слова на несколько частей. Например, `Sylvain` будет разделено на 4 части: `S`, `##yl`, `##va`, and `##in`. На этапе постпроцессинга пайплайн успешно объединит эти части. 

<Tip>

✏️ **Попробуйте!** Найдите на Model Hub модель, позволяющую решать задачу определения частей речи в предложении (part of speech tagging, POS). Что модель предскажет для предложения из примера выше?

</Tip>

## Вопросно-ответные системы

Пайплайн `question-answering` позволяет сгенерировать ответ на вопрос по данному контексту: 

```python
from transformers import pipeline

question_answerer = pipeline("question-answering")
question_answerer(
    question="Where do I work?",
    context="My name is Sylvain and I work at Hugging Face in Brooklyn",
)
```

```python out
{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'}
```

Обратите внимание, что пайплайн извлекает информацию для ответа из переданного ему контекста


## Автоматическое реферирование (саммаризация)

Автоматическое реферирование (англ. summarization) - задача, в которой необходимо сократить объем текста, но при этом сохранить все важные аспекты (или большинство из них) изначального текста. Вот пример: 

```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"`). Однако самый простой способ - попробовать использовать [Model Hub](https://huggingface.co/models). Здесь мы попробуем перевести текст с французского на английский язык: 


```python
from transformers import pipeline

translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en")
translator("Ce cours est produit par Hugging Face.")
```

```python out
[{'translation_text': 'This course is produced by Hugging Face.'}]
```

Так же, как и в задачах генерации и автоматического реферирования текста, вы можете указать максимальную длину `max_length` или минимальную длину `min_length` результата. 


<Tip>

✏️ **Попробуйте!** Найдите модель, которая переведет предложение из примера выше на другие языки

</Tip>

Показанные пайплайны в основном носят демонстрационный характер, потому что настроены на решение конкретных задач. В следующей главе вы узнаете, как изменить поведение функции `pipeline()`. 



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/3.mdx" />

### Модели-декодировщики
https://huggingface.co/learn/course/ru/chapter1/6.md

# Модели-декодировщики

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="d_ixlCubqQw" />

Декодировщики используют только компонент декодирования трансформера. На каждом этапе для текущего слова слой внимания может получить доступ только к словам, которые были расположены до него в предложении. Такие модели часто называются *авторегрессионными моделями*.

Процесс предобучения декодировщиков обычно заключается в предсказании следующего слова в предложении.

Такие модели лучше всего подходят для задач, связанных с генерацией текста. 

Представителями этого семейства моделей являются:

- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html)
- [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt)
- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html)
- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/6.mdx" />

### Тест в конце главы[[end-of-chapter-quiz]]
https://huggingface.co/learn/course/ru/chapter1/10.md

# Тест в конце главы[[end-of-chapter-quiz]]

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

В этой главе было много материала! Если вы чувствуете, что все еще всецело не познали все премудрости трансформеров - не переживайте! В следующих главах мы детально расскажем, как все устроено "под капотом".

Сперва, однако, давайте проверим, что вы узнали в этой главе!


### 1. Зайдите на Hub и найдите чекпоинт модели `roberta-large-mnli`. Какую задачу она решает?


<Question
	choices={[
		{
			text: "Автоматическое реферирование (саммаризация)",
			explain: "Посмотрите получше на <a href=\"https://huggingface.co/roberta-large-mnli\">странице roberta-large-mnli</a>."
		},
		{
			text: "Классификация текстов",
			explain: "В частности, модель определяет, являются ли два предложения логически связанными и присваивает одну из трех меток: противопоставление, нейтральная связь, импликация (англ. contradiction, neutral, entailment). Эта задача называется <em>автоматическое определение логической связи между текстами (англ. natural language inference)</em>.",
			correct: true
		},
		{
			text: "Генерация текста",
			explain: "Посмотрите получше на <a href=\"https://huggingface.co/roberta-large-mnli\">странице roberta-large-mnli</a>."
		}
	]}
/>

### 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.")
```

<Question
	choices={[
		{
			text: "Пайплайн вернет следующие метки классов для этого предложения: \"positive\" или \"negative\".",
			explain: "Неверно — для этого используется пайплайн <code>sentiment-analysis</code>."
		},
		{
			text: "Пайплайн вернет текст, сгенерированный на основе данного предложения.",
			explain: "Неверно — для этого используется пайплайн <code>text-generation</code>.",
		},
		{
			text: "Пайплайн вернет слова, обозначающие персон, организаций или географических локаций.",
			explain: "Кроме того, с аргументом <code>grouped_entities=True</code>, пайплайн сгруппирует слова, принадлежащие одной и той же сущности, например, \"Hugging Face\".",
			correct: true
		}
	]}
/>

### 3. Чем нужно заменить ... в данном коде?

```py
from transformers import pipeline

filler = pipeline("fill-mask", model="bert-base-cased")
result = filler("...")
```

<Question
	choices={[
		{
			text: "This &#60;mask> has been waiting for you.",
			explain: "Неверно. Прочитайте карточку модели <code>bert-base-cased</code> и попробуйте найти, где вы ошиблись."
		},
		{
			text: "This [MASK] has been waiting for you.",
			explain: "Верно! Токен-маска для этой модели - [MASK].",
			correct: true
		},
		{
			text: "This man has been waiting for you.",
			explain: "Неверно. Этот пайплайн предсказывает замаскированный токен, а для этого нужно предоставить токен-маску."
		}
	]}
/>

### 4. Почему этот код выдаст ошибку?

```py
from transformers import pipeline

classifier = pipeline("zero-shot-classification")
result = classifier("This is a course about the Transformers library")
```

<Question
	choices={[
		{
			text: "Этому пайплайну требуются метки классов.",
			explain: "Верно — правильный код должен содержать аргумент <code>candidate_labels=[...]</code>.",
			correct: true
		},
		{
			text: "Этому пайплайну требуются несколько предложений, а не одно.",
			explain: "Неверно. Хотя, если использовать этот пайплайн правильно, он может принимать на вход массив предложений (как и все остальные пайплайны)."
		},
		{
			text: "Опять библиотека 🤗 Transformers не работает как положено.",
			explain: "Мы даже не будем комментировать этот ответ!"
		},
		{
			text: "Этому пайплайну требуются более длинные предложения - это слишком короткое.",
			explain: "Неверно. Однако, стоит отметить, что этот пайплайн обрежет очень длинный текст, для того, чтобы его корректно обработать."
		}
	]}
/>

### 5. Что такое «трансферное обучение»?

<Question
	choices={[
		{
			text: "Передача знаний от предобученной модели к новой модели путем ее обучения на тех же данных.",
			explain: "Нет - результатом этого будут две версии той же самой модели."
		},
		{
			text: "Передача знаний от предобученной модели к новой модели путем инициализирования новой модели с весами предобученной.",
			explain: "Верно - когда новая модель будет обучена на новой задаче, она <em>получит</em> знания предобученной модели. Другими словами, предобученная модель <em>передаст</em> свои знания новой.",
			correct: true
		},
		{
			text: "Передача знаний от предобученной модели к новой модели путем проектирования новой модели с той же самой архитектурой, что и у предобученной.",
			explain: "Архитектура - это лишь «скелет» модели; в этом случае никой передачи знаний не происходит."
		}
	]}
/>

### 6. Правда или ложь? Для предобучения языковой модели обычно не требуются метки классов.

<Question
	choices={[
		{
			text: "Правда",
			explain: "Предобучение обычно <em>самостоятельно (англ. self-supervised)</em>. Это означает, что метки классов создаются автоматически на основе входных данных (например, предсказание следующего или замаскированного слова).",
			correct: true
		},
		{
			text: "Ложь",
			explain: "Это неверный ответ."
		}
	]}
/>

### 7. Выберите предложение, которое наилучшим способом описывает следующие термины: «модель», «архитектура» и «веса».

<Question
	choices={[
		{
			text: "Модель - это здание, ее архитектура - чертеж, а веса - люди, которые там живут.",
			explain: "Чтобы это сравнение было верным, веса должны быть кирпичами и другими материалами из которых построено здание."
		},
		{
			text: "Архитектура - это карта, по которой можно построить модель, а веса - это города на этой карте.",
			explain: "Проблема этого сравнения в том, что карта обычно отображает одну существующую реальность (во Франции только один город под названием Париж). В то время как одна и та же архитектура может быть заполнена разными наборами весов."
		},
		{
			text: "Архитектура - это последовательность математических функций для создания модели, а веса - параметры этих функций.",
			explain: "Один и тот же набор математических функций (архитектура) можно использовать для построения разных моделей, используя разные параметры (веса).",
			correct: true
		}
	]}
/>


### 8. Какую из этих моделей вы выберете для дополнения текста по введенной его части?

<Question
	choices={[
		{
			text: "Кодировщик",
			explain: "Кодировщик генерирует векторное представление всего предложения, что больше подходит для таких задач как классификация."
		},
		{
			text: "Декодировщик",
			explain: "Декодировщик идеально подходит для дополнения текста по введенной его части.",
			correct: true
		},
		{
			text: "Кодировщик-декодировщик",
			explain: "Модели sequence-to-sequence больше подходят для задач, в которых вам нужно сгенерировать предложения, основываясь на входных предложениях целиком, а не на части текста, которую нужно продолжить."
		}
	]}
/>

### 9. Какую из этих моделей вы выберете для автоматического реферирования?

<Question
	choices={[
		{
			text: "Кодировщик",
			explain: "Кодировщик генерирует векторное представление всего предложения, что больше подходит для таких задач как классификация."
		},
		{
			text: "Декодировщик",
			explain: "Декодировщих хорошо продходит для генерации текста (например, рефератов), но он не способен брать во внимание контекст (например, документ целиком) для реферирования."
		},
		{
			text: "Кодировщик-декодировщик",
			explain: "Кодировщик-декодировщик идеально подходит для задачи автоматического реферирования.",
			correct: true
		}
	]}
/>

### 10. Какую из этих моделей вы выберете для классификации текстов путем присвоения им определенных меток?

<Question
	choices={[
		{
			text: "Кодировщик",
			explain: "Кодировщик генерирует векторное представление всего предложения, что идеально подходит для такой задачи, как классификация.",
			correct: true
		},
		{
			text: "Декодировщик",
			explain: "Декодировщих хорошо подходит для генерации текста, а не для присвоения метки документу."
		},
		{
			text: "Кодировщик-декодировщик",
			explain: "Модели sequence-to-sequence больше подходят для задач, в которых вам нужно сгенерировать предложения, основываясь на входных предложениях целиком, а не для присвоения метки документу.",
		}
	]}
/>

### 11. Что может быть одной из причин предвзятости модели?

<Question
	choices={[
		{
			text: "Модель была дообучена на основе предобученной модели, которая содержала в себе предвзятость.",
			explain: "Используя трансферное обучение, предвзятость предобученной модели также передастся новой модели.",
			correct: true
		},
		{
			text: "В обучающей выборке были тексты предвзятого содержания.",
			explain: "Это самый очевидный источник предвзятости, но далеко не единственный.",
			correct: true
		},
		{
			text: "Метрика, выбранная для оптимизации модели, была склонна к предвзятости.",
			explain: "Менее очевидный источник предвзятости кроется в организации ее обучения. Модель будет слепо следовать выбранной метрике, так как она неспособна «задуматься» о ее корректности.",
			correct: true
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/10.mdx" />

### Предвзятости и ограничения
https://huggingface.co/learn/course/ru/chapter1/8.md

# Предвзятости и ограничения

<CourseFloatingBanner chapter={1}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/ru/chapter1/section8.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter1/section8.ipynb"},
]} />

Если вы намерены использовать предварительно обученную модель или ее дообученную версию в рабочей среде, имейте в виду, что, хотя эти модели являются мощными инструментами, они имеют ограничения. Самое большое из них заключается в том, что для предварительного обучения на больших объемах данных исследователи часто очищают весь контент, который они могут найти, беря как лучшее, так и худшее из того, что доступно в Интернете.

Для иллюстрации вернемся к примеру пайплайна `fill-mask` с моделью BERT:

```python
from transformers import pipeline

unmasker = pipeline("fill-mask", model="bert-base-uncased")
result = unmasker("This man works as a [MASK].")
print([r["token_str"] for r in result])

result = unmasker("This woman works as a [MASK].")
print([r["token_str"] for r in result])
```

```python out
['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic']
['nurse', 'waitress', 'teacher', 'maid', 'prostitute']
```

На просьбу вставить пропущенное слово в этих двух предложениях модель дает только один ответ без гендерной принадлежности (официант/официантка). Другие рабочие профессии обычно ассоциируются с одним конкретным полом — и да, проститутка попала в топ-5 вариантов, которые модель ассоциирует с "женщиной" и "работой". Это происходит даже несмотря на то, что BERT — одна из редких моделей трансформеров, созданная не путем сбора данных со всего Интернета, а с использованием явно нейтральных данных (он обучен на [английской Википедии](https://huggingface.co/datasets/wikipedia) и наборе данных [BookCorpus](https://huggingface.co/datasets/bookcorpus).

Поэтому, когда вы используете эти инструменты, вам нужно помнить, что исходная модель, которую вы используете, может очень легко генерировать сексистский, расистский или гомофобный контент. Дообучение модели на ваших данных не сможет устранить эту внутреннюю предвзятость.



<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/8.mdx" />

### Как работают трансформеры?
https://huggingface.co/learn/course/ru/chapter1/4.md

# Как работают трансформеры?

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

В этом разделе мы посмотрим в общих чертах на то, как работают трансформеры. 

## Немного истории

Здесь приведены несколько ссылок на (короткую) историю трансформеров:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono.svg" alt="A brief chronology of Transformers models.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_chrono-dark.svg" alt="A brief chronology of Transformers models.">
</div>

[Архитектура трансформеров](https://arxiv.org/abs/1706.03762) была опубликована в июне 2017. Основной фокус оригинального исследования был сосредоточен на задачах перевода. Эта публикация повлекла за собой несколько влиятельных моделей: 

- **Июнь 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), первая предобученная модель для тонкой настройки или дообучения (fine-tuning), которая показала результаты высокого качества для многих NLP-задач.

- **Октябрь 2018**: [BERT](https://arxiv.org/abs/1810.04805), другая большая предобученная модель, была разработана для извлечения более точного содержания из предложений (больше мы узнаем об этом в следующей главе!)

- **Февраль 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), улучшенная (и более объемная) версия GPT, которая не была сразу опубликована по этическим соображениям

- **Октябрь 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), «дистиллированная» версия BERT, которая на 60% быстрее и на 40% менее объемная, однако сохраняющая 97% производительности BERT

- **Октябрь 2019**: [BART](https://arxiv.org/abs/1910.13461) and [T5](https://arxiv.org/abs/1910.10683), две больших модели, повторяющие архитектуру классического трансформера

- **Май 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), еще более крупная версия GPT-2, способная хорошо справляться с различными задачами без необходимости fine-tuning (так называемое _zero-shot learning_)

Этот список далеко не полный, он всего лишь призван выделить несколько разновидностей моделей трансформеров. 
В широком смысле трансформеры могут быть классифицированы на три типа: 
- GPT-подобные модели (также часто называемые _авторегрессионные_ трансформеры)
- BERT-подобные модели (также часто называемые _автокодирующие_ трансформеры (_auto-encoding_))
- тип BART/T5 модели (также часто называются модели класса _последовательность-последовательность_ (_sequence-to-sequence, seq2seq_))

Мы рассмотрим эти семейства более детально позже. 

## Трансформеры - языковые модели 

Все модели трансформеров, упомянутые выше (GPT, BERT, BART, T5, etc.) обучены как *языковые модели (англ. language models)*. Это означает, что они обучены на огромном количестве текста, используя технику самостоятельного обучения (англ. self-supervised learning). Самостоятельное обучение - это такой способ обучения, в котором цель обучения автоматически вычисляется на основе входных данных. Это означает, что люди не должны размечать данные! 

Такой тип моделей реализует статистическое понимание языка, на котором он был обучен, но он не очень полезен для конкретных практических задач. Из-за этого базовая предварительно обученная модель потом подвергается процедуре, называемой *трансферным обучением (англ. transfer learning)*. В ходе этого процесса модель настраивается под конкретные наблюдения, т.е. размеченными человеком данными для конкретной задачи.

В качестве примера можно привести предсказание следующего слова в предложении на основе *n* предыдущих слов. Это называется *каузальным языковым моделированием (англ. causal language modeling)*, потому что модель зависит от прошлых и текущих слов, но не от будущих.


<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/causal_modeling-dark.svg" alt="Example of causal language modeling in which the next word from a sentence is predicted.">
</div>

Другой пример - *маскированная языковая модель (англ. masked language modeling)*, которая предсказывает замаскированное слово в предложении. 

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/masked_modeling-dark.svg" alt="Example of masked language modeling in which a masked word from a sentence is predicted.">
</div>

## Трансформеры - большие модели

За исключением нескольких моделей (например, DistilBERT), общий подход к достижению высокого качества заключается в увеличении размера моделей и увеличении количества данных для обучения. 

<div class="flex justify-center">
<img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/model_parameters.png" alt="Number of parameters of recent Transformers models" width="90%">
</div>

К сожалению, обучение модели, особенно большой, требует большого количества данных. Это приводит к увеличению времени и вычислительных потребностей. Это воздействует даже на окружающую среду, что можно увидеть графике ниже. 

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint.svg" alt="The carbon footprint of a large language model.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/carbon_footprint-dark.svg" alt="The carbon footprint of a large language model.">
</div>

<Youtube id="ftWlj4FBHTg"/>

И это наглядная демонстрация проекта по разработке (очень большой) модели, которым руководит команда, _сознательно_ пытающаяся уменьшить воздействие предобучения на окружающую среду. Углеродный след от проведения множества экспериментов для получения лучших гиперпараметров будет еще выше.

Представьте себе, что каждый раз, когда исследовательская группа, студенческая организация или компания хотели бы обучить модель, они делали бы это с нуля. Это привело бы к огромным, ненужным глобальным затратам!

Вот почему распространение языковых моделей имеет первостепенное значение: распространение обученных весов и построение новых моделей на их основе снижает общую стоимость вычислений и углеродный след сообщества.

Кстати, вы можете измерить углеродный след, который оставят ваши модели, при помощи нескольких инструментов. Например, интегрированные в 🤗 Transformers [ML CO2 Impact](https://mlco2.github.io/impact/) или [Code Carbon](https://codecarbon.io/). Чтобы узнать больше о этом, вы можете прочитать этот [блог-пост](https://huggingface.co/blog/carbon-emissions-on-the-hub), в котором мы рассказываем как сгенерировать файл `emissions.csv`, содержащий прогноз объемов выброса углерода во время обучения модели, а также [документацию](https://huggingface.co/docs/hub/model-cards-co2) 🤗 Transformers, в которой затрагивается эта тема.

## Трансферное обучение 

<Youtube id="BqqfQnyjmgg" />

*Предобучение (англ. pretraining)* - это процесс обучения модели с нуля: веса модели случайным образом инициализируются, после начинается обучение без предварительных настроек. 

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining.svg" alt="The pretraining of a language model is costly in both time and money.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/pretraining-dark.svg" alt="The pretraining of a language model is costly in both time and money.">
</div>

Предобучение обычно происходит на огромных наборах данных, сам процесс может занять несколько недель. 

*Дообучение (англ. fine-tuning)*, с другой стороны, это обучение *после* того, как модель была предобучена. Для дообучения вы сначала должны выбрать предобученную языковую модель, а после продолжить ее обучение ее на данных собственной задачи. Стойте -- почему не обучить модель сразу же на данных конкретной задачи? Этому есть несколько причин: 

* Предобученная модель уже обучена на датасете, который имеет много сходств с датасетом для дообучения. Процесс дообучения может использовать знания, которые были получены моделью в процессе предобучения (например, в задачах NLP предварительно обученная модель будет иметь представление о статистических закономерностях языка, который вы используете в своей задаче).

* Так как предобученная модель уже "видела" много данных, процесс дообучения требует меньшего количества данных для получения приемлемых результатов.

* По этой же причине требуется и намного меньше времени для получения хороших результатов. 

Например, можно использовать предварительно обученную на английском языке модель, а затем дообучить ее на корпусе arXiv, в результате чего получится научно-исследовательская модель. Для дообучения потребуется лишь ограниченный объем данных: знания, которые приобрела предварительно обученная модель, «передаются» (осуществляют трансфер), отсюда и термин «трансферное обучение».

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money.">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/finetuning-dark.svg" alt="The fine-tuning of a language model is cheaper than pretraining in both time and money.">
</div>

Таким образом, дообучение модели требует меньше времени, данных, финансовых и экологических затрат. Также быстрее и проще перебирать различные схемы дообучения, поскольку оно требует меньше усилий, чем полное предварительное обучение.

Этот процесс также даст лучшие результаты, чем обучение с нуля (если только у вас нет большого количества данных), поэтому вы всегда должны пытаться использовать предобученную модель — модель, максимально приближенную к поставленной задаче, а потом дообучить ее.

## Общая архитектура

В этом разделе мы рассмотрим общую архитектуру модели трансформера. Не беспокойтесь, если вы не понимаете некоторых понятий; далее есть подробные разделы, посвященные каждому из компонентов.

<Youtube id="H39Z_720T5s" />

## Введение

Модель состоит из двух блоков: 

* **Кодировщик (слева)** (англ. encoder): кодировщик получает входные данные и строит их репрезентацию (формирует признаки). Это означает, модель нацелена на "понимание" входных данных. 
* **Декодировщик (справа)** (англ. decoder): декодировщик использует репрезентации (признаки) кодировщика с другими входными данными для создания нужной последовательности. Это означает, что модель нацелена на генерацию выходных данных. 

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks.svg" alt="Architecture of a Transformers models">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers_blocks-dark.svg" alt="Architecture of a Transformers models">
</div>

Каждая из этих частей может быть использована отдельно, это зависит от задачи:

* **Модели-кодировщики**: полезны для задач, требующих понимания входных данных, таких как классификация предложений и распознавание именованных сущностей.
* **Модели-декодировщики**: полезны для генеративных задач, таких как генерация текста.
* **Модели типа "кодировщик-декодировщик"** или **seq2seq-модели**: полезны в генеративных задачах, требующих входных данных. Например: перевод или автоматическое реферирование текста.

Мы изучим эти архитектуры подробнее в следующих разделах. 

## Слой внимания

Ключевой особенностью трансформеров является наличие в архитектуре специального слоя, называемого *слоем внимания (англ. attention layer)*. Статья, в которой была впервые представлена архитектура трансформера, называется ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) ("Внимание - все, что вам нужно")! Мы изучим детали этого слоя позже. На текущий момент мы сформулируем механизм его работы так: слой внимания помогает модели "обращать внимание" на одни слова в поданном на вход предложении, а другие слова в той или иной степени игнорировать. И это происходит в процессе анализа каждого слова.

Чтобы поместить это в контекст, рассмотрим задачу перевода текста с английского на французский язык. Для предложения "You like this course", модель должна будет также учитывать соседнее слово "You", чтобы получить правильный перевод слова "like", потому что во французском языке глагол "like" спрягается по-разному в зависимости от подлежащего. Однако остальная часть предложения бесполезна для перевода этого слова. В том же духе при переводе "like" также необходимо будет обратить внимание на слово "course", потому что "this" переводится по-разному в зависимости от того, стоит ли ассоциированное существительное в мужском или женском роде. Опять же, другие слова в предложении не будут иметь значения для перевода "this". С более сложными предложениями (и более сложными грамматическими правилами) модели потребуется уделять особое внимание словам, которые могут оказаться дальше в предложении, чтобы правильно перевести каждое слово.

Такая же концепция применима к любой задаче, связанной с обработкой естественного языка: слово само по себе имеет некоторое значение, однако значение очень часто зависит от контекста, которым может являться слово (или слова), стоящие вокруг искомого слова. 

Теперь, когда вы знакомы с идеей внимания в целом, посмотрим поближе на архитектуру всего трансформера.

## Первоначальная архитектура

Архитектура трансформера изначально была разработана для перевода. Во время обучения кодировщик получает входные данные (предложения) на определенном языке, а декодировщик получает те же предложения на желаемом целевом языке. В кодировщике слои внимания могут использовать все слова в предложении (поскольку, как мы только что видели, перевод данного слова может зависеть от того, что в предложении находится после и перед ним). Декодировщик, в свою очередь, работает последовательно и может обращать внимание только на слова в предложении, которые он уже перевел (то есть только на слова перед генерируемым в данный момент словом). Например, когда мы предсказали первые три слова переведенной цели, мы передаем их декодировщику, который затем использует все входные данные кодировщика, чтобы попытаться предсказать четвертое слово.

Чтобы ускорить процесс во время обучения (когда модель имеет доступ к целевым предложениям), декодировщик получает целевое предложение полностью, но ему не разрешается использовать будущие слова (если он имел доступ к слову в позиции 2 при попытке предсказать слово на позиции 2, задача не будет сложной!). Например, при попытке предсказать четвертое слово слой внимания будет иметь доступ только к словам в позициях с 1 по 3.

Первоначальная архитектура Transformer выглядела так: кодировщик слева и декодировщик справа:

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers.svg" alt="Architecture of a Transformers models">
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers-dark.svg" alt="Architecture of a Transformers models">
</div>

Обратите внимание, что первый слой внимания в блоке декодировщика обращает внимание на все (прошлые) входные данные декодировщика, а второй слой внимания использует выходные данные кодировщика. Таким образом, он может получить доступ ко всему входному предложению, чтобы наилучшим образом предсказать текущее слово. Это очень полезно, так как разные языки могут иметь грамматические правила, которые располагают слова в разном порядке, или некоторый контекст, предоставленный в предложении далеко от текущего слова. Контекст может быть полезен для определения наилучшего перевода данного слова.

*Маска внимания (англ. attention mask)* также может использоваться в кодировщике/декодировщике, чтобы модель не обращала внимания на некоторые специальные слова — например, специальное несуществующее слово-заполнитель (англ. padding), используемое для придания всем входным данным одинаковой длины при группировке предложений.

## Архитектуры и чекпоинты

По мере погружения в трансформеры, вы будете встречать термины *архитектуры* и *чекпоинты* (англ. checkpoints) в смысле *модели*. Эти термины имеют разный смысл:

**Архитектура** - скелет модели -- слои, связи и операции, которые выполняются в модели. 
**Чекпоинт** - веса модели, которые могут быть загружены для конкретной архитектуры.
**Модель** - зонтичный термин, который может означать и архитектуру, и веса для конкретной архитектуры. В этом курсе мы будем точнее использовать термины *архитектуры* и *чекпоинт*, если это будет важно для лучшего понимания.

Например, BERT - это архитектура, а `bert-base-cased` - набор весов, подготовленный Google к первому выпуску BERT'а, - это чекпоинт. Однако можно сказать и "модель BERT", и "модель bert-base-cased". 


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/4.mdx" />

### Модели вида "seq2seq"
https://huggingface.co/learn/course/ru/chapter1/7.md

# Модели вида "seq2seq"

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

<Youtube id="0_4KEb08xrE" />

Модели типа кодировщик-декодировщик (также называемые *sequence-to-sequence models*) используют обе части трансформера. На каждом этапе слой внимания кодировщика получает доступ ко всем словам в исходной последовательности, тогда как слой внимания декодировщика получает доступ только к тем словам, которые расположены до текущего слова. 

Предобучение таких моделей может быть выполнено на задачах, используемых для предобучения моделей кодировщиков или декодировщиков, но обычно все немного сложнее. Например, модель [T5](https://huggingface.co/t5-base) была предобучена путем замены случайных фрагментов текста (фрагменты могут содержать несколько слов) на специальную маску, цель модели - предсказать текст, который заменила маска. 

Модели seq2seq лучше всего подходят для задач генерации новых предложений, зависящих от входного массива данных, например: автоматическое реферирование текста, перевод или в генеративных вопросно-ответных системах. 

Представителями этого семейства являются: 

- [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)


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/7.mdx" />

### Обработка естественного языка
https://huggingface.co/learn/course/ru/chapter1/2.md

# Обработка естественного языка 

<CourseFloatingBanner
    chapter={1}
    classNames="absolute z-10 right-0 top-0"
/>

Прежде, чем перейти к трансформерам, сделаем быстрый обзор того, что такое обработка естественного языка (NLP), и почему мы заинтересованы в этой сфере. 

## Что такое NLP?

NLP - область лингвистики и машинного обучения, которая изучает все, что связано с естественными языками. Главная цель NLP не просто понимать отдельные слова, но и иметь возможность понимать контекст, в котором эти слова находятся.

Список типичных NLP-задач с некоторыми примерами: 

- **Классификация предложений**: определить эмоциональную окраску отзыва, детектировать среди входящих писем спам, определить грамматическую правильность предложения или даже проверить, являются ли два предложения связанными между собой логически
- **Классификация каждого слова в предложении**: вычленить грамматические составляющие предложения (существительное, глагол, прилагательное) или определить именованные сущности (персона, локация, организация)
- **Генерация текста**: закончить предложение на основе некоторого запроса, заполнить пропуски в тексте, содержащем замаскированные слова
- **Сформулировать ответ на вопрос**: получить ответ на заданный по тексту вопрос
- **Сгенерировать новое предложение исходя из предложенного**: перевести текст с одного языка на другой, выполнить автоматическое реферирование текста

NLP не ограничивается только письменным текстом. Есть множество сложных задач, связанных с распознаванием речи и компьютерным зрением, таких как транскрибирование аудио или описание изображений. 

## Почему это сложно? 

Компьютеры не обрабатывают информацию так же, как люди. Например, когда мы читаем предложение «Я голоден», мы можем легко понять его значение. Точно так же, имея два предложения, такие как «Я голоден» и «Мне грустно», мы можем легко определить, насколько они похожи. Для моделей машинного обучения (ML) такие задачи сложнее. Текст должен быть обработан так, чтобы модель могла учиться на нем. А поскольку язык сложен, нам нужно тщательно продумать, как должна выполняться эта обработка. Было проведено много исследований того, как представлять текст, и мы рассмотрим некоторые методы в следующей главе.


<EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/ru/chapter1/2.mdx" />
