Видео проигрыватель загружается.Воспроизвести видеоВоспроизвестиБез звукаТекущее время 0:00/Продолжительность 7:46Загрузка: 0.00%0:00Тип потока ОНЛАЙНSeek to live, currently behind liveОНЛАЙНОставшееся время -7:46 1xСкорость воспроизведения2x1.75x1.5x1.25x1x, выбрано0.75x0.5xГлавыГлавыОписанияОтключить описания, выбраноСубтитрынастройки субтитров, откроется диалог настройки субтитровСубтитры выкл., выбраноЗвуковая дорожкаPicture-in-PictureПолноэкранный режимThis is a modal window.Начало диалоговго окна. Кнопка Escape закроет или отменит окноТекстColorБелыйЧерныйКрасныйЗеленыйСинийЖелтыйПурпурныйГолубойTransparencyПрозрачностьПолупрозрачныйФонColorЧерныйБелыйКрасныйЗеленыйСинийЖелтыйПурпурныйГолубойTransparencyПрозрачностьПолупрозрачныйПрозрачныйОкноColorЧерныйБелыйКрасныйЗеленыйСинийЖелтыйПурпурныйГолубойTransparencyПрозрачныйПолупрозрачныйПрозрачностьРазмер шрифта50%75%100%125%150%175%200%300%400%Стиль края текстаНичегоПоднятыйПониженныйОдинаковыйТеньШрифтПропорциональный без засечекМоноширинный без засечекПропорциональный с засечкамиМоноширинный с засечкамиСлучайныйПисьменныйМалые прописныеСбросить сбросить все найстройки по умолчаниюГотовоЗакрыть модальное окноКонец диалогового окна.
Наша модель реализуется базовым классом "LanguageModel". Мы передаём размер словаря (это 1000, в данном случае), размер эмбеддинга (256 элементов) и он же — это размер модели. А также передаём туда экземпляр backbone нейросети, то есть — это та нейросеть, которая будет производить агрегацию контекстов, будет сравнивать слова с соседними словами и, таким образом, на вход она будет получать эмбеддинги отдельных токенов, а на выходе у неё уже будут эмбеддинги фраз, предложений... То есть — более крупных конструкций. Мы будем использовать три слоя self-attention и будем использовать не очень большой dropout. Я предлагаю вам поиграться с этими параметрами, чтобы понять, насколько от них зависит качество. Как мы видим, модель содержит примерно 2 миллиона параметров... Это не очень большая модель (по современным меркам). Давайте попробуем обучить модель. Для этого мы будем использовать нашу стандартную функцию, которую мы использовали весь курс до этого. На что здесь нужно обратить внимание — это то, что мы передаём сюда функцию потерь, "lm cross entropy", то есть — это та функция, которую мы определили, это не просто функция кросс-энтропии из pytorch. А также здесь стоит большой batch_size. Если вы будете запускать этот ноутбук на видеокарте с небольшим количеством памяти (меньше 11 гигабайт), то вам нужно будет этот "batch_size" изменить — так, чтобы ваши данные влезали в видеокарту. Я поставил его таким большим просто потому, что в моём случае так быстрее и всё хорошо учится. А ещё я поставил большое количество эпох здесь. То есть, я ожидаю, что модель будет сходиться не очень быстро. В самом деле, на каждой эпохе мы делаем всего лишь 11 итераций, потому что у нас небольшой датасет (как мы помним, в нём примерно 6000 обучающих фрагментов и 3000 тестовых), и при таком большом размере батча мы делаем маленькое количество обновлений, поэтому нам нужно побольше эпох. Зато каждая эпоха проходит очень быстро. В моём случае, потребовалось примерно 800 эпох, то есть чуть больше чем 8000 градиентных шагов, чтобы модель более-менее сошлась. Сходимость модели мы проверяем по среднему значению функции потерь на отложенной выборке. То есть, спустя примерно 800 тысяч шагов, функция потерь на валидации перестала улучшаться, поэтому мы досрочно остановили обучение. Хорошая практика — сохранять модели на промежуточных итерациях для того чтобы если, например, вам придётся досрочно остановить обучение или вы захотите усреднить модели (веса моделей с разных итераций) между собой (это, кстати, хорошая практика, она позволяет немного улучшить качество). В данном случае мы сохраняем только последнюю лучшую модель и тут же её загружаем, чтобы поиграться немножко. Как вы помните из лекции, существует два основных подхода к декодированию текста из языковой модели. Это полностью жадный алгоритм декодирования — то есть, когда мы на каждом шаге берём наиболее вероятный токен. И, при этом, наиболее вероятный токен мы выбираем без учёта совместного распределения токенов (то есть — вот на этом шаге модель сказала, что токен "А" самый лучший — мы его и берём вне зависимости от того, какой следующий токен может быть, или предыдущий). Этот алгоритм мы реализовали в нашей библиотеке в классе "GreedyGenerator". Он получает на вход обученную модель и "tokenizer". А затем он будет получать на вход текст и выдавать новый текст. Давайте посмотрим, как это всё работает. Собственно, алгоритм предельно простой — сначала мы токенизируем наше предложение. Затем мы делаем некоторое количество шагов, но не более заданного числа шагов. И, на каждом шаге, мы полностью перевычисляем модель, то есть мы берём все входные токены, которые накопили к данному шагу, заворачиваем их в тензор, копируем на видеокарту и прогоняем через модель, а потом выбираем наиболее вероятный последний токен. Здесь "batch_size" равен 1, то есть мы берём в этом "indexer" первый элемент батча (он соответствует нашему предложению) и последний элемент в последовательности. Таким образом, после взятия этих индексов у нас на выходе будет вектор размерности, соответствующей размеру словаря. И мы просто берём токен с наибольшим весом из этого вектора. Как вы помните, наша модель возвращает не распределение вероятностей, а логиты, то есть это какие-то ненормированное числа. Чтобы получить распределение вероятностей, в этом случае, нам нужно ещё софтмакс применить к этим векторам. Но если мы хотим делать только "arg max", то нам не нужен "softmax", потому что он сглаживает величины, приводит их в диапазон от нуля до единицы, но он их не переупорядочивает, а значит точка максимума не изменится. Вот мы выбираем лучший токен, используя всего лишь предсказание модели именно для этой позиции. Если на очередном шаге мы предсказали конец последовательности, то мы прекращаем генерировать дальше. А если это ещё не конец последовательности, то мы добавляем текущий токен к нашему списку токенов и снова засовываем это всё в модель на следующей итерации. Затем, когда мы сделали достаточное количество шагов, мы просто декодируем список номеров токенов в строку (всё просто). Давайте посмотрим, что же наша модель может генерировать. На вход модели мы подаём какой-то небольшой фрагмент текста. Хочу обратить ваше внимание, что мы подаём не только фрагменты текстов, но даже фрагменты слова. То есть — мы не полностью подали последнее слово, и модель сгенерировала на выходе вот такой фрагмент. То есть она, во-первых, закончила слово, а потом продолжила относительно связным текстом. У неё даже получилось смешать французский и русский язык. Обратите внимание, что данная реализация — она не очень эффективная, потому что она полностью перевычисляет всю модель, хотя, казалось бы, мы можем часть активаций сохранить, потому что когда мы в цикле в "greedy_generator" выбираем следующий лучший токен, мы всё равно берём все предыдущие токены, прогоняем их через модель, и, активации на большом количестве слоёв — они будут те же самые. То есть, казалось бы, их можно закэшировать и не прогонять одни и те же токены через модель повторно. Просто закэшировав активации. Но реализация этого не очень очевидная, более того, она достаточно сложная, и поэтому в семинаре мы не стали её делать. Также на экране вы видите парочку других примеров, которые были сгенерированы моделью из практически того же текста, но с небольшими изменениями. Мы видим что, несмотря на то, что изменения в исходном тексте небольшие, результирующий текст кардинально меняется.

К сожалению, у нас пока нет статистики ответов на данный вопрос, но мы работаем над этим.