tl; dr: вы можете скрыть / инкапсулировать состояние произвольных повторяющихся сетей с помощью одной страницы кода

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

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

Этот процесс особенно опасен при работе с рекуррентными архитектурами (также известными как «рекуррентные нейронные сети»): вычислительными графами, которые являются DG (ориентированными графами), но не DAG (ориентированными ациклическими графами). . Рекуррентные архитектуры особенно хороши для моделирования / генерации последовательных данных - языка, музыки, видео, даже видеоигр - всего, где вам важен порядок данных, а не только чистое отображение ввода / вывода.

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

Теперь, если вы просто используете ванильный LSTM / GRU, есть готовые компоненты, которые вы можете легко склеить изолентой. Проблема не в этом. Сложнее всего взять новую повторяющуюся архитектуру и попытаться закодировать новый граф, также обрабатывая все трюки с развернутым состоянием, без внесения новых ошибок.

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

Вздох.

Везде, где вы видите t-1 в качестве подстрочного индекса, есть еще одно место (и да, Вирджиния, в этом маленьком кирпичике символов 7), о котором вам нужно беспокоиться о повторяющемся состоянии: его инициализация, его получение из прошлого и сохраняя его на будущее.

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

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

Здесь переменная bp - это объект BPTT, который отвечает за все повторяющиеся состояния. Здесь есть два интересных вызова методов. bp.get_past_variable () обрабатывает как инициализацию из случайной константы, так и извлечение прошлого состояния (t-1), а bp.name_variable () сохраняет текущее состояние для будущих женихов. .

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

Единственные мысленные накладные расходы - это получение повторяющейся переменной (непосредственно перед использованием) и ее сохранение (в соответствии с использованием), все в локальном контексте. Нигде в коде построения графа нет ссылки на это состояние. Фактически, остальной код поразительно похож на разовый TensorFlow.

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

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

Во время обучения (или вывода) есть три места, где все это повторяющееся состояние должно быть снова задействовано. Во-первых, мы должны отправить рабочее состояние в словарь каналов (либо инициализированные константы, определенные выше, либо рабочее состояние из предыдущего цикла обучения), и вставить данные обучения в развернутый заполнители. Во-вторых, мы должны определить переменные состояния, которые мы хотим вернуть из метода session.run (). В-третьих, мы должны взять результаты post-session.run () и извлечь состояние на будущее.

Объект BPTT также занимается этим бухгалтерским учетом.

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

Когда мы делаем вывод, мы используем флаг bp.SHALLOW, который имеет другой набор переменных-заполнителей и, следовательно, должен управлять другим конвейером состояний. Существует также удобный метод (copy_state_forward ()) для копирования окончательного развернутого / глубокого состояния (повторяющиеся переменные) в циклическую / мелкую сеть перед началом вывода.

Повторное глубокое обучение в TensorFlow может быть - если не простым - немного проще.

Хотите проверить код + пример использования? Больше ничего не говори.