Краткое содержание предыдущих серий

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

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

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

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

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

Q-функция

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

Соответсвенно, логично предположить, что задачу коммивояжера можно сформулировать в терминах марковского процесса принятия решения (Markov Decision Process, MDP):

  • [s] - текущее состояние или город, в котором сейчас коммивояжер
  • [s’] - новое состояние или город, который коммивояжер собирается посетить
  • [a] - действие для перехода из [s] в [s’]
  • [r] - награда, которую получает коммивояжер после перемещения из одного города в другой

Теория MDP разрабатывалась вокруг идеи научить искусственный интеллект играть в игры, поэтому большинство примеров проистекает также из видеоигр. В случае с TSP коммивояжер не кушает тортики и не бросается в лаву, как герой видеоигры, но перемещается из одного города в другой и получает награду [r := r(s,a)], или точнее штрафы в виде пройденного отрезка от одного города к другому.

Тогда процесс можно описать следующим образом. В начальном состоянии мира [s_0] коммивояжер находится в каком-то городе, возможно, в любом из доступных, но я на практике всегда помещаю в один и тот же город N-ск. Коммивояжер может наблюдать сразу все возможные города для следующего визита и на базе каких-то пока мистических ощущений, выбирает действие [a], которое считает лучшим (оптимальным). Мир отвечает коммивояжеру генерацией награды и коммивояжер оказывается в следующем городе и в следующем состоянии [s_1], далее процесс продолжается пока все города не будут посещены.

Введем понятие Q-функции для некоторой стратегии принятия решения [\pi]: $$ Q^\pi(s, a) := \sum_{t \geq 0} \gamma^t \mathbb{E}_{\mathcal{T} \sim \pi \mid s_0, a_0} [r_t] $$ По определению, Q-функция — это награды, которые коммулятивно набирает коммивояжер для всех траекторий (маршрутов) посещений [\mathcal{T}] городов, полученных из стратегии [\pi] для стартовых состояния [s_0] и действия [a_0]. Другими словами, коммивояжер посещает города, принимая решения на базе своей стратегии, и потом аккуратненько в эксельку записывает сколько он прошел (награду [r]), если был в таком-то городе и отправился другой. В следующий день он повторяет этот процесс, добавляя в эксельку, возможно какие-то другие сведения для других городов и действий, потому что решил посещать города немного в ином порядке.

Необходимо обратить внимание на весовой коэффициент [\gamma], который согласно теории должен быть меньше единицы (обычно от 0,9 до 0,99). Коэффициент [\gamma] обеспечивает уменьшение вклада будущих наград в Q-функцию. Такой параметр позволяет управлять склонностью получить награду прямо сейчас, нежели получить награды в будущем. Фактически, речь идет о выборе журавля в небе или синицы в руках. Его значение влияет на приоритет текущих наград перед будущими. Например:

  • Если [\gamma \to 0], коммивояжер ориентируется только на текущую награду.
  • Если [\gamma \to 1], коммивояжер учитывает долгосрочные награды.

В данном случае коммивояжер заносит в эксельку возможные решения, в какой город пойдет первым, получив первую награду [0.9^0r_1], далее – в какой город пойдет вторым получив награду [0.9^1r_2], и далее [0.9^2r_3], и таким образом выводит сумму, что и будет Q-функцией, которая представляет собой ожидаемую суммарную дисконтированную награду от действия [a] в состоянии [s], если следовать стратегии [\pi].

Получается, что коммивояжер бегает по городам, принимая решения на базе стратегии, которую пытается сделать оптимальной. Будем считать, что оптимальность стратегии [\pi] достигается, когда максимизируются награды по траекториям (маршрутам). Действительно, если такие маршруты дают нам хорошие или даже лучшие награды, то чего же еще желать? $$ \pi(s):=\underset{a}{\operatorname{argmax}} Q(s, a) $$

Табличный алгоритм

Соответственно, если у коммивояжера есть экселька и две переменные: состояния и действия [(s, a)], а также их возможные комбинации, то они могут быть аккуратно уложены в табличку вида:

[s_1] [s_2] [s_3]
[a_1] 0 -50 -130
[a_2] -10 0 -30
[a_3] -40 -80 0

Здесь можно заметить, что использование таблички не является единственным доступным вариантом решения задачи. Вместо статичной записи, накопленных наград можно перейти к апроксимации Q-функции с использованием глубокого обучения. В таком случае на выходе получится архитектура DQN (Deep Q-learning). В то время как табличный метод подкупает своей простотой и наглядностью, DQN, очевидно, будет выглядеть более предпочтительно для задач большей размеренности.

Элементы в такой табличке инициализируются произвольно, например нулем, и означают награды коммивояжера, накопленные определенным образом, то есть с помощью формулы временной разности (Temporal Difference) для уравнения Беллмана: $$ Q_{k+1}(s, a) \leftarrow Q_k(s, a)+\alpha_k \underbrace{(\overbrace{r+\gamma \max_{a'}Q_k(s', a')}^{\text {target}}-Q_k(s, a))}_{\text {временная разность }} $$

, где [Q_k(s,a)] - это текущая оценка [Q^{\pi}(s, a)] на момент итерации

Несмотря на то, что данная формула имеет формальный вывод ее можно интерпретировать интуитивно. На каждом шаге совершается целевое (target) действие [a] по переходу из состояния [s] в состояние [s’], рассчитывается награда [r := r(s,a)], и извлекается [Q_k(s’,a’)] следующего состояния [s’] и следующего набора действий [a’], из которого выбирается лучшее (максимальное). Далее текущее приближение [Q_k(s,a)] просто сдвигается с некоторым коэффициентом (learning rate, скорость обучения) в сторону целевого (target) значения.

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

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

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

if(runif(1) > epsilon){ # epsilon - затухающий с итерациями гиперпараметр 
        # ЭКСПЛУАТАЦИЯ
        q = Q_mtrx[,state] # выбор вектора состояния из Q-матрицы
        # mem - это простой вектор, который хранит информацию о посещенных городах
        q[mem] = -Inf  # игнорирование посещенных городов
        action = which.max(q) # выбор лучшего следующего решения
      }
      else{  
        # ИССЛЕДОВАНИЕ
        options = n_seq[which(!n_seq %in% mem)] # доступные для посещения города
        action = ifelse(length(options) == 1, options, sample(options, size = 1)) # случаный выбор города
      }

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

if(i == n_cities){ # если коммивояжер выбрал последний город для посещения
    # Q-табличка обновляется наградой перехода из последнего города в первоначальный
    Q_mtrx[action, state] = Q_mtrx[action, state] + 
      lr*(reward + gamma*next_q[mem[1]] -Q_mtrx[action, state])
  }else{ # иначе ...
    next_q[mem] = -Inf  # исключаем посещенные города 
    # Q-табличка обновляется выражением временной разности
    Q_mtrx[action, state] = Q_mtrx[action, state] + 
      lr*(reward + gamma*which.max(next_q) - Q_mtrx[action, state])
      }

Выражение для терминального состояния next_q[mem[1]] указывает на возврат коммивояжера в изначальный город.

В итоге получается алгоритм Q-обучения для задачи коммивояжера. Теория говорит о том, что данный алгоритм является off-policy, то есть не требуется отслеживать опыт взаимодействия конкретной стратегии со средой. На практике это дает приятное свойство обучать коммивояжера на любом опыте или на любых траекториях посещения городов, в том числе на траекториях другого коммивояжера, которые просто сохранены в буфер памяти. В принципе, даже не обязательно проходить полностью весь маршрут, достаточно просто исследовать все возможные состояния. В данной реализации это свойство не используется, по причине того, что извлечение траекторий для задачи TSP является простой процедурой, тогда как для многих иных игровых и бизнес-задач такой способ будет неприемлем. Действительно, на производственном предприятии никто не будет менять режимы производства для того, чтобы получить отклик среды специально для обучения модели с подкреплением. Куда вероятнее запустить алгоритм оптимизации на имеющихся исторических данных. Однако на этом преимущества такого подхода заканчиваются.

По сути, такой подход сводится к обучению критика в связке “актер-критик”, которая концептуально является основным понятием обучения с подкреплением. Действительно, в выражении временной разности есть только итеративная оценка Q-функции, говорящая о качестве текущей стратегии в явном виде через табличное представление.

Сопоставление с Pointer Network

Если вернуться в самое начало заметки и вспомнить про Pointer Network, а также формулу для оценки лосса: $$ L=-R⋅\sum_{i=1}^nlog(\rho(a_i)) $$

  • [R] - это награда всего маршрута
  • [(a_i)] - вероятность принятия действия с индексом [i]

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

В данном подходе основной акцент делается на улучшении стратегии выбора действий, однако ничего не говорится об оценке этой стратегии, а также о Q-функции. Этот метод известен как REINFORCE. Его ключевая характеристика заключается в том, что он ориентирован на обучение актера – модели, способной непосредственно генерировать действия.

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

  • Актера для обучения стратегии [],
  • Критика для обучения оценочной функции.

Вот такое получается “кино” с актерами и критиками, нужными для реализации обучения с подкреплением в общем случае.

Практическая реализация Q-обучения

Несмотря на то что весь искусственный интеллект в контексте Q-обучения уложился в 50 строчек кода на R, я буду придерживаться практики публикации исходного скрипта реализации в репозитории проекта AI Optimization. В этой заметке хотелось бы остаться на уровне идей и ключевых результатов. Базовая задача для анализа остается прежней – это случайно сгенерированные координаты 16 городов точно такие, какие использовались для всех предыдущих заметок серии.

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

       V1  V2  V3   V4   V5   V6   V7   V8   V9  V10  V11  V12  V13  V14  V15  V16
1 0 -82 -83 -104 -88 -71 -62 -101 -45 -83 -151 -113 -168 -103 -102 -103
2 -39 0 -36 -59 -62 -34 -18 -58 -11 -30 -133 -70 -68 -61 -57 -54
3 -87 -33 0 -29 -46 -4 -35 -32 -51 -17 -5 -72 -32 -38 -35 -27
4 -103 -57 -27 0 -25 -31 -63 4 -64 -54 -30 -55 1 -6 -11 -9
5 -204 -70 -50 -18 0 -46 -78 -22 -46 -116 -70 -27 -36 -22 -5 -43
6 -72 -28 -4 -25 -37 0 -40 -37 -39 -26 -25 -70 -313 -29 -33 -31
7 -153 -18 -27 -65 -107 -313 0 -67 -44 -16 -50 -102 -75 -68 -105 -65
8 -106 -64 -41 2 -20 -38 -72 0 -66 -64 -36 -57 -4 -4 6 -20
9 -159 -16 -42 -57 -57 -37 -40 -63 0 -50 -71 -52 -73 -55 -55 -66
10 -73 -28 -18 -53 -67 -32 -15 -67 -44 0 -19 -95 -135 -61 -67 -42
11 -159 -55 -15 -32 -70 -34 -42 -37 -73 -28 0 -100 -38 -45 -42 -25
12 -78 -70 -76 -63 -35 -70 -101 -57 -55 -95 -100 0 -307 -43 -160 -67
13 -229 -70 -70 -6 -34 -43 -79 -16 -78 -60 -36 -80 0 -9 -22 -9
14 -188 -58 -36 -3 -12 -31 -65 -4 -57 -62 -46 -155 -19 0 0 -20
15 -108 -61 -43 -8 -7 -38 -72 -3 -56 -69 -40 -40 -13 -2 0 -24
16 -300 -60 -25 -4 -34 -32 -59 -8 -226 -48 -229 -79 -11 -17 -20 0

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

Тестирование

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

Теоретически Q-обучение в табличном виде должно не так хорошо справляться с задачами большой размерности, но в данном случае алгоритм выглядит предпочтительнее Pointer Net именно на размерностях более высокого порядка. Безусловно, все алгоритмы из мира ИИ пока выглядят гадкими утятами по сравнению с царь-алгоритмом Concorde или относительно простым Cristofides. Если посмотреть на вычислительную эффективность алгоритмов, то картинка для ИИ-алгоритмов выглядит не менее уныло:

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


Полезные ссылки:


Простой способ узнать о новых публикациях – подписаться на Telegram-канал: