>>1.00<<>>1.00<<Видео проигрыватель загружается.Воспроизвести видеоВоспроизвестиБез звукаТекущее время 0:00/Продолжительность 9:46Загрузка: 0.00%0:00Тип потока ОНЛАЙНSeek to live, currently behind liveОНЛАЙНОставшееся время -9: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%Стиль края текстаНичегоПоднятыйПониженныйОдинаковыйТеньШрифтПропорциональный без засечекМоноширинный без засечекПропорциональный с засечкамиМоноширинный с засечкамиСлучайныйПисьменныйМалые прописныеСбросить сбросить все найстройки по умолчаниюГотовоЗакрыть модальное окноКонец диалогового окна.
OK, два базовых кирпичика у нас уже есть — это механизм внимания с несколькими головами и механизм "self-attention" (то есть, механизм внутреннего внимания). Теперь мы можем собрать слой "трансформер". Давайте сразу перейдём к методу forward. Интерфейс у метода forward — такой же, как и у self attention, то есть он принимает на вход входную последовательность — это трёхмерный тензор (батч на длину на размер модели), это маска паддингов (прямоугольная матрица) и маска зависимости позиции (это квадратная матрица). Первым делом мы здесь выполняем агрегацию контекста. То есть мы хотим понять смысл каждого токена в контексте всей входнуой последовательности. Для этого мы вызываем механизм "self attention". Затем, к признакам, которые мы получили из механизма внимания, мы применяем dropout и складываем с исходными признаками последовательности. То есть мы здесь получаем, как бы, "skip connection", или это вам может напоминать блок ResNet. Skip connection очень хорошо помогают учить глубокие нейросети. "Skip connection" или "вычисления без нелинейностей" можно применять в абсолютно любых архитектурах, в реккурентках, свёрточных нейросетях, вот — в трансформере они тоже применяются. И затем к полученным признакам мы применяем "Layer norm". Вообще, в обработке последовательностей, такие способы нормализации как "Batch norm" не очень удобно применять, потому что они накапливают статистики и непонятно, как вообще статистики считать, когда у нас количество элементов последовательности вообще отличается — может меняться от батча к батчу. Наиболее часто используемый способ нормализации в обработке текстов — это "Layer norm", его используют и в рекуррентках, и в трансформере. Хорошо, теперь переменная "sequence" у нас содержит признаки с учётом контекста. Механизм внимания хорошо учитывает контекст, но, как нелинейность, он не очень мощный, поэтому давайте применим некоторую нейросеть (независимо) к признакам каждого токена. В классическом трансформере для этого используется двухслойный перцептрон (с двумя линейными слоями, функцией активации ReLU и с dropout). Применять эту нелинейность мы также будем через ResNet-блок, то есть мы суммируем вход нелинейности и выход нелинейности. И, также, второй раз применяем "Layer norm". Таким образом, один слой трансформера состоит из двух residual слоёв (то есть двух блоков со skip connection) и первый блок содержит self attention (то есть, агрегирует контекст), а второй блок преобразовывает признаки каждого элемента последовательности независимо от других элементов последовательности. То есть, больше играет роль нелинейности и обогащает модель. Мы сделали уже три базовых кирпичика — это механизм внимания с несколькими головами, это self attention и это один слой трансформера. Теперь нам нужно собрать "encoder трансформер". По сути, это просто стопка из нескольких слоёв трансформера, который мы только что описали. Для того чтобы создать энкодер для трансформера, мы должны взять несколько экземпляров класса "my transformer encoding layer" (это класс, который мы только что описали) и слепить из них последовательность. Кроме того, мы должны здесь проинициализировать веса всех этих слоёв. Эти слои применяются последовательно, каждый принимает на вход результат работы предыдущего. Кроме того, мы передаём в каждый слой одни и те же маски — это маска паддингов и маска зависимостей. Названия переменных здесь выбраны так, чтобы они соответствовали названиям параметров в реализации pytorch, для того, чтобы мы могли просто взять экземпляр этого класса и подставить его вместо стандартного трансформера. Ну что ж, создаём экземпляр языковой модели уже с нашей реализацией трансформера, передаём туда примерно те же самые параметры. Здесь можно видеть, что количество параметров в нашей реализации немножко меньше, чем в стандартной. В принципе мы не задавались целью скопировать реализацию один в один. Вы можете открыть исходники pytorch и сами посмотреть — там всё достаточно понятно, хотя реализация механизма внимания с несколькими головами там очень общая и достаточно громоздкая. В этом семинаре мы сделали более простую реализацию, отказавшись от некоторой гибкости, в угоду понятности и простоте. Обучаем нашу модель... Вы можете заметить, что наша реализация медленнее, стандартная реализация проходит эпоху за 2 с копейками секунды, а здесь требуется 6. Наиболее вероятная причина замедления — это то, что мы использовали перемножение тензоров с помощью энштейновского суммирования, то есть помощью функции "torch einsum". Эта функция очень удобная, она гибкая, но она помедленнее. Ничего удивительного в этом нет — бесплатных завтраков не бывает[1]. Мы бы тоже могли обойтись без этой функции, но нам бы пришлось сделать несколько дополнительных транспонирований, в результате мы бы получили гораздо менее читаемый код. Нашей модели также требуется порядка 8-9 тысяч градиентных шагов для того, чтобы сойтись. Ну, и давайте проверим, вообще, работает ли наша модель, генерирует ли она что-то осмысленное... В целом — да. Заметьте, что из того же начального предложения эта модель выдаёт другое предложение, в отличие от первой модели, которую мы обучили в этом семинаре. На самом деле, если мы запустим обучение ещё раз, оно сойдётся к другому минимуму и эта модель тоже будет генерить что-то другое. Помните, из функции, которая реализует механизм внимания с несколькими головами, мы возвращали ещё и карты внимания (то есть тензор нормированных релевантностей — то, что получается после софтмакса и после dropout). Мы это делали не случайно, а для того, чтобы иметь возможность нарисовать эти карты активации. Я не буду подробно останавливаться на алгоритме, который используется для отрисовки этих графиков. Здесь мы видим несколько графиков, каждая вот такая вот строка соответствует слою трансформера (то есть это самый первый слой трансформера, дальше идёт второй слой трансформера...), а каждый столбец соответствует голове (то есть, это — первые головы, это — вторые головы для каждого слоя). Можно больше, у нас в модели всего 16 голов, но, просто — не влезло бы. По строкам здесь отложены запросы, и каждая позиция помечена токеном во входной последовательности, который стоял на этой позиции. По столбцам отложены ключи. Здесь у нас запросы (queries), а здесь у нас ключи (keys). И, чем ярче клеточка на пересечении строки и столбца, тем более значим этот ключ для этого запроса (по мнению модели). Мы видим, что на первом слое карты активации очень контрастные, очень разреженные. То есть, для каждой выходной позиции близок только один ключ. Интересно обратить внимание вот на эти строки. Эти стройки соответствуют токенам, то есть N-граммам из имени Бонапарта. Интересно, что для почти всех N-грамм из имени, наиболее значимым ключом является первая N-грамма этого имени (то есть у нас есть такие связи). То есть для вычисления признаков (вот, например, для этой позиции) очень важны признаки начала имени. Это и есть учёт контекста. У второй головы карта активации совершенно другая. Здесь, кажется, в вычислении признаков для всех токенов после запятой самый важный исходный токен — это сама "запятая", то есть мы, как бы, отделяем деепричастный оборот здесь. Конечно, это всего лишь — мои попытки натянуть какую-то рационализацию на эти карты активации, неочевидно, что вообще эти карты активации для человека будут понятны или полезны. Но иногда на них просто интересно посмотреть. На втором слое трансформера карты активации уже более сглаженные, уже нету каких-то отдельных, выделенных, наиболее значимых позиций, уже каждая позиция обращает внимание, в принципе, на все предыдущие позиции. Обратите внимание, что верхняя половина этих карт — тёмная. Это именно то, чего мы хотели добиться, применяя маску зависимостей. То есть здесь в маске зависимости на этих позициях стоит −∞-\infty−∞.
[1] https://en.wikipedia.org/wiki/No_free_lunch_theorem

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