Вступление

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

Обучение с подкреплением

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

Проблема лабиринта

Как и в предыдущей статье, мы попытаемся решить проблему лабиринта. Мышь случайно падает в 1 из 6 мест лабиринта и должна научиться находить сыр (награда).

Как и в предыдущем посте, мы определяем лабиринт в программе следующим образом:

Архитектура DQN

Для этой задачи мы избавимся от Q-таблицы из предыдущего решения и заменим ее нейронной сетью. Нам понадобится 1 входной нейрон (Состояние) и 6 выходных нейронов (Действия). Я решил реализовать 1 скрытый слой с двумя нейронами больше, чем выходными нейронами (это казалось правильным). Каждый выходной нейрон представляет действие, которое необходимо выполнить в состоянии. Во входном слое есть 1 нейрон, который представляет текущее состояние нашей мыши. Используя Q-формат, я масштабирую вход, взяв текущее состояние и разделив его на максимально возможное состояние (т.е. 5, 6 состояний в сумме пронумерованы от 0 до 5). Для этого нейронная сеть DQN запрашивает среду и ожидает, что она инициализирует указатель на массив входных данных нейронной сети для заданного числового представления состояния.

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

Нейронная сеть DQN

Используемый код нейронной сети также взят из cppnnml. Чтобы получить представление о том, как работают эти нейронные сети, прочтите мою статью здесь. Нейронная сеть обучена предсказывать значение Q для каждого действия, предпринимаемого в заданном состоянии. После обучения нейронной сети мы выбираем действие, которое содержит наивысшее значение Q (т.е. выходной нейрон 0 == действие 0, выходной нейрон 1 == действие 1 и т. Д.).

Рисунок 1: Архитектура нейронной сети для примера решателя лабиринта DQN

Рисунок 2: Агент использует нейронную сеть для получения / установки значений Q

DQN определяется в dqn_mazelearner.h следующим образом:

Создание примера

Измените каталоги на пример кода в cppnnml / examples / dqn_maze. Мы создаем каталог для хранения созданной исполняемой программы, а затем компилируем пример. Нам нужно скомпилировать код LUT нейронной сети, поскольку мы все определяем тип (ы) LUT для компиляции. В этом случае нам нужна только функция активации tanh для подписанного типа Q-format Q16.16. См. Эту статью для подробного объяснения того, как работают нейронные сети в cppnnml.

mkdir -p ~/dqn_maze/dqn_maze
g++ -O3 -o ~/dqn_maze/dqn_maze dqn_maze.cpp dqn_mazelearner.cpp ../../cpp/lookupTables.cpp -I../../cpp -I../../apps/include/ -DTINYMIND_USE_TANH_16_16

Это создает более компактный пример программы лабиринта и помещает исполняемый файл в ~ / dqn_maze. Теперь мы можем перейти в каталог, в котором был сгенерирован исполняемый файл, и запустить пример программы.

cd ~/dqn_maze
./dqn_maze

Когда программа завершит работу, вы увидите последнее выходное сообщение, примерно такое:

take action 5
*** starting in state 4 ***
take action 5
*** starting in state 5 ***
take action 1
take action 5
*** starting in state 2 ***
take action 3
take action 1
take action 5
*** starting in state 0 ***
take action 4
take action 5
*** starting in state 1 ***
take action 5
*** starting in state 1 ***
take action 5
*** starting in state 3 ***
take action 1
take action 5
*** starting in state 5 ***
take action 1
take action 5
*** starting in state 4 ***
take action 5

Ваши сообщения могут немного отличаться, так как мы запускаем указатель мыши в случайной комнате на каждой итерации. Во время выполнения программы-примера мы сохраняем все действия мыши в файлы (dqn_maze_training.txt и dqn_maze_test.txt). В обучающем файле мышь выполняет случайные действия в течение первых 400 эпизодов, а затем случайность уменьшается со 100% случайности до 0% случайности для следующих 100 эпизодов. Чтобы увидеть первые несколько итераций обучения, вы можете сделать это:

head dqn_maze_training.txt

Вы должны увидеть что-то вроде этого:

1,3,4,3,1,5,
1,3,1,5,
1,3,4,3,1,5,
5,4,5,
1,5,
2,3,1,5,
3,2,3,4,0,4,3,1,5,
4,0,4,5,
4,0,4,0,4,0,4,5,
4,0,4,5,

Опять же, ваши сообщения будут выглядеть немного иначе. Первое число - это начальное состояние, а каждое значение, разделенное запятыми, после него - это случайное перемещение мыши из комнаты в комнату. Пример: В первой строке выше мы начали с комнаты 1, затем перешли в 3, затем в 4, затем в 1, затем в 5. Поскольку 5 - это наше целевое состояние, мы остановились. Причина, по которой это выглядит настолько беспорядочным, заключается в том, что в течение первых 400 итераций обучения мы принимаем случайное решение из наших возможных действий. Достигнув состояния 5, мы получаем награду и останавливаемся.

Визуализация обучения и тестирования

Я включил скрипт Python для построения данных обучения и тестирования. Если мы построим данные обучения для начального состояния == 2 (т.е. мышь опускается в комнату 2 в начале):

Каждая линия на графике представляет эпизод, в котором мы случайным образом поместили указатель мыши в комнату 2 в начале эпизода. Вы можете видеть, что в худшем случае мы сделали 18 случайных ходов, чтобы найти состояние цели (состояние 5). Это связано с тем, что на каждом шаге мы просто генерируем случайное число для выбора из доступных действий (т. Е. Какую комнату нужно переместить в следующую). Если мы используем сценарий для построения данных тестирования для начального состояния == 2:

Вы можете видеть, что после того, как мы завершили обучение, Q-Learner усвоил оптимальный путь: 2- ›3-› 1- ›5.

Определите размер Q-Learner

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

В любом случае, мы хотим измерить, сколько кода и данных требуется для реализации нашего DQN для решения лабиринтов:

g++ -c dqn_mazelearner.cpp -O3 -I../../cpp -I../../apps/include/ -DTINYMIND_USE_TANH_16_16 && mv dqn_mazelearner.o ~/dqn_maze/.
cd ~/dqn_maze/
size dqn_mazelearner.o

Вы должны увидеть следующий результат:

text data bss dec hex filename
12255 8 3652 15915 3e2b dqn_mazelearner.o

Мы видим, что вся DQN умещается в пределах 16 КБ. Довольно маленький, как идет DQN.

Заключение

На сегодняшний день в cppnnml поддерживаются 2 типа Q-Learning: Q-обучение на основе таблиц и DQN. Примеры программ, а также модульные тесты существуют в репозитории для демонстрации их использования. DQN меняет Q-таблицу на нейронную сеть, чтобы узнать взаимосвязь между состояниями, действиями и Q-значениями. Можно было бы использовать DQN, когда пространство состояний велико, а память, потребляемая Q-таблицей, была бы чрезмерно большой. Используя DQN, мы меняем память на циклы процессора. Накладные расходы ЦП для DQN будут намного больше, чем для Q-таблицы. Но DQN позволяет нам проводить Q-обучение, сохраняя при этом управляемый объем памяти для сложных сред.