>>1.00<<>>1.00<<Видео проигрыватель загружается.Воспроизвести видеоВоспроизвестиБез звукаТекущее время 0:00/Продолжительность 6:05Загрузка: 0.00%0:00Тип потока ОНЛАЙНSeek to live, currently behind liveОНЛАЙНОставшееся время -6:05 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%Стиль края текстаНичегоПоднятыйПониженныйОдинаковыйТеньШрифтПропорциональный без засечекМоноширинный без засечекПропорциональный с засечкамиМоноширинный с засечкамиСлучайныйПисьменныйМалые прописныеСбросить сбросить все найстройки по умолчаниюГотовоЗакрыть модальное окноКонец диалогового окна.
В нашем примере мы будем использовать attention. На коде attention не будем останавливаться подробно, скажем лишь, что здесь мы будем использовать стандартный attention, который посчитает нам веса по нашей входной последовательности. Теперь давайте перейдём к коду декодера. Архитектура декодера аналогична архитектуре энкодедра — это GRU unit, первый слой получает пару с прошлого шага и прогоняет её через нашу сеть вместе с текущим токеном, чтобы предсказать новую пару — таким образом, уравнения декодера будут очень похожи на уравнения энкодера. Затем мы прогоняем наше скрытое состояние с верхнего слоя через линейный слой, чтобы сделать предсказание следующего токена в выходном генерируемом предложении. Входные аргументы класса "decoder" похоже на аргументы класса "encoder", исключения — у нас здесь есть параметр, который называется "output_dimension" (это размер one-hot векторов, которые подаются на вход декодеру). Это число должно быть равно размеру словаря таргета. Forward-метод декодера принимает батч входных данных, а также скрытое состояние с предыдущего шага. Мы применяем unsqueeze к выходным токенам, чтобы добавить ещё одну размерность. Далее, аналогично энкодеру, применяем эмбеддинг-слой и дропаут. Дальше применяем attention, и потом батч с токенами передаём в RNN вместе с "h" и "c" векторами с предыдущего шага. Аналогично энкодеру, мы получаем на выходе output (это скрытое состояние с верхнего слоя нашей сети), новое скрытое состояние и новое cell состояние, то есть новые вектора "h" и "c". И мы прогоняем output (после того, как избавились от лишней размерности) через линейный слой, чтобы получить предсказание следующего слова в нашей последовательности. И возвращать здесь мы будем, собственно — output, скрытое состояние и вектор "а". Последняя часть нашей имплементации — создаём модуль для seq2seq модели. Она будет содержать внутри себя энкодер, декодер и уметь обрабатывать входные последовательности (то есть вопросы) и выдавать сниппеты в качестве ответов. Входные параметры seq2seq модели —это энкодер, декодер, device, а также токены, с помощью которых мы кодируем паддинг, начало последовательности и конец последовательности. В этой имплементации их можно менять. Нам нужно убедиться, что количество слоёв и размерность скрытых состояний — одинакова для энкодера и декодера. Это не обязательное условие, но в противном случае (в случае разного количества слоёв) нам придётся придумывать некоторые способы это обойти. Например, если в энкодере два слоя, а в декоре один, то можно использовать оба вектора, либо можно их усреднить. "forward"-метод в классе seq2seq берёт на вход вопрос, сниппет с кодом и "teacher forcing ratio". На выход он отдаёт нам output, а также веса attention, которые мы посчитали в модуле attention. Теперь мы можем инициализировать модель. Как сказано ранее, входная и выходная размерность определяются размерами словарей. Сделаем так, чтобы количество слоёв у энкодера и декодера, а также размерности векторов скрытых состояний были одинаковыми. Давайте зададим размерность скрытого состояния равную "100", размерность для эмбеддингов — "256". Также зададим dropout — он будет достаточно большим ("0.8"). Далее мы увидим, что если ставить дропаут достаточно маленьким, то сеть будет плохо учиться и результат будет достаточно некрасивым. Теперь инициализируем веса для обеих моделей. В статье, которую мы пытаемся имплементировать, брали равномерное распределение от -0.08 до +0.08. Создадим функцию "init_weights" и добавим её в нашу модель с помощью метода apply. Равномерное распределение мы берём из nn.init.uniform (то есть, берём просто равномерное распределение из pytorch). Также посчитаем количество параметров, которое содержит наша модель. У нас получилось около 1 миллиона параметров — достаточно много, но замечательно, что у нас достаточно высокий процент дропаута, это поможет нам не переобучиться. В качестве оптимизатора будем использовать Adam, в качестве loss-функции — кросс энтропию. Теперь мы можем написать функцию, которая задаст наш цикл обучения. Сначала мы переводим модель в training mode (с помощью model.train). Это включит dropout и батч-нормализацию (если бы она была, но, в нашем случае, мы её не используем). А потом будем итерироваться через батч-итератор. Что мы делаем на каждой итерации? На каждой итерации мы берём входное и выходное предложение из батча, вместо входного предложения мы получаем пару — "входное предложение и его длина" (как мы уже обсуждали ранее). Далее мы делаем zero_grad — обнуляем градиенты, посчитанные на предыдущем шаге, затем мы передаём source и target в нашу модель и получаем некоторый выход и веса attention, мы считаем градиенты с помощью "loss backward", предварительно посчитав функцию потерь, и дальше клипаем (clip) градиенты, делаем шаг нашим оптимизатором и считаем лосс. Замечательно, на выход наша функция будет возвращать нормализованный лосс по нашей эпохе. Также у нас есть функция, которую мы будем использовать для оценивания качества модели (для evaluate). Здесь, вначале, мы переводим нашу модель в состояние оценивания качества (это означает "выключить dropout", "выключить батч-нормализацию") и дальше проделываем примерно такие же шаги, как и в функции "train". Кроме того, давайте напишем функцию, которая будет замерять время, потраченное на каждую эпоху, и назовём её "epoch_time".
К сожалению, у нас пока нет статистики ответов на данный вопрос,
но мы работаем над этим.