Обучение агента освоению Крестиков-ноликов через самостоятельную игру

Обучение агента Крестиков-ноликов через самостоятельную игру

Удивительно, программный агент никогда не устает от игры.

Ах! начальная школа! Это было время, когда мы изучали ценные навыки, такие как грамотность, арифметика и оптимальная игра в крестики-нолики.

Фото от Solstice Hannan на Unsplash

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

Мы не можем научить программного агента искусству избегания попадания в классе, но можем ли мы обучить агента овладеть игрой?

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

Вы можете найти код на Python в этом репозитории. Скрипт, выполняющий обучение, – learn_tictactoe.sh:

#!/bin/bashdeclare -i NUMBER_OF_GAMES=30000declare -i NUMBER_OF_EPOCHS=5export PYTHONPATH='./'python preprocessing/generate_positions_expectations.py \ --outputDirectory=./learn_tictactoe/output_tictactoe_generate_positions_expectations_level0 \    --game=tictactoe \    --numberOfGames=$NUMBER_OF_GAMES \    --gamma=0.95 \    --randomSeed=1 \    --agentArchitecture=None \    --agentFilepath=None \    --opponentArchitecture=None \    --opponentFilepath=None \    --epsilons="[1.0]" \    --temperature=0 dataset_filepath="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level0/dataset.csv" python train/train_agent.py \  $dataset_filepath \  --outputDirectory="./learn_tictactoe/output_tictactoe_train_agent_level1" \  --game=tictactoe \  --randomSeed=0 \  --validationRatio=0.2 \  --batchSize=64 \  --architecture=SaintAndre_1024 \  --dropoutRatio=0.5 \  --learningRate=0.0001 \  --weightDecay=0.00001 \  --numberOfEpochs=$NUMBER_OF_EPOCHS \  --startingNeuralNetworkFilepath=None   for level in {1..16}do dataset_filepath="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}/dataset.csv" python preprocessing/generate_positions_expectations.py \  --outputDirectory="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}" \  --game=tictactoe \  --numberOfGames=$NUMBER_OF_GAMES \  --gamma=0.95 \  --randomSeed=0 \  --agentArchitecture=SaintAndre_1024 \  --agentFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth" \  --opponentArchitecture=SaintAndre_1024 \  --opponentFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth" \  --epsilons="[0.5, 0.5, 0.1]" \  --temperature=0    declare -i next_level=$((level + 1)) python train/train_agent.py \  "./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}/dataset.csv" \  --outputDirectory="./learn_tictactoe/output_tictactoe_train_agent_level${next_level}" \  --game=tictactoe \  --randomSeed=0 \  --validationRatio=0.2 \  --batchSize=64 \  --architecture=SaintAndre_1024 \  --dropoutRatio=0.5 \  --learningRate=0.0001 \  --weightDecay=0.00001 \  --numberOfEpochs=$NUMBER_OF_EPOCHS \  --startingNeuralNetworkFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth"  done

Скрипт выполняет циклы вызовов двух программ:

  • generate_positions_expectations.py: Симулирует матчи и сохраняет состояния игры с дисконтированным ожидаемым доходом.
  • train_agent.py: Обучает нейронную сеть на несколько эпох на недавно сгенерированном наборе данных.

Цикл обучения

Обучение агента игре происходит через цикл генерации матчей и обучения предсказывать исход матча по состоянию игры:

Рисунок 1: Цикл генерации матчей и обучения нейронной сети. Изображение автора.

Генерация матчей

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

Зачем мы генерируем матчи, которые играются случайным образом?

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

На рисунке 2 показан пример матча между случайными игроками:

Рисунок 2: Пример игры в крестики-нолики, сыгранной случайным образом. Изображение автора.

Какой урок мы можем извлечь, наблюдая этот матч? С точки зрения игрока ‘X’, мы можем предположить, что это пример плохой игры, так как матч завершился поражением. Мы не знаем, какой ход(ы) отвечают за поражение, поэтому мы предполагаем, что все решения, принятые игроком ‘X’, были плохими. Если некоторые решения были хорошими, мы полагаемся на статистику (другие симуляции могут проходить через аналогичное состояние) для корректировки их предсказанной стоимости состояния.

Последнему действию от игрока ‘X’ присваивается значение -1. Остальные действия получают дисконтированное отрицательное значение, которое геометрически уменьшается с коэффициентом γ (гамма) ∈ [0, 1], по мере движения назад к первому ходу.

Рисунок 3: Целевые значения для состояний игры. Изображение автора.

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

Состояния игры в виде тензоров

Нам нужно тензорное представление для состояния игры. Мы будем использовать тензор размера [2x3x3], где первое измерение представляет каналы (0 для ‘X’ и 1 для ‘O’), а два других измерения представляют строки и столбцы. Занятость (строка, столбец) квадрата кодируется единицей в соответствующей ячейке (канал, строка, столбец).

Рисунок 4: Представление состояния игры с использованием тензора размера [2x3x3]. Изображение автора.

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

Внедрение случайности в игру

Первый раунд формирования матча противостоит случайным игрокам. Последующие раунды противостоят агенту самому себе (отсюда и “самоигра”). Агент оснащен нейронной сетью регрессии, обученной предсказывать результат матча, что позволяет ему выбирать правильное действие, которое дает наибольшее ожидаемое значение. Чтобы способствовать разнообразию, агент выбирает действия на основе эпсилон-жадного алгоритма: с вероятностью (1-ε) выбирается лучшее действие; в противном случае выбирается случайное действие.

Обучение

На рисунке 5 показано развитие функции потерь при проверке в течение пяти эпох для максимального количества 17 раундов обучения:

Рисунок 5: Развитие функции потерь при проверке для разного количества раундов обучения. Изображение автора.

Мы видим, что первые несколько раундов обучения показывают быстрое уменьшение функции потерь при проверке, а затем наблюдается плато около значения среднеквадратичной ошибки 0,2. Этот тренд показывает, что нейронная сеть регрессии агента становится лучше в предсказании результата матча, сыгранного против самого себя, из данного игрового состояния. Поскольку действия обоих игроков недетерминированы, есть предел предсказуемости результата матча. Это объясняет, почему функция потерь при проверке перестает улучшаться после некоторого количества раундов.

Улучшение от раунда к раунду

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

Используя ε = 0,5 для первого действия обоих игроков и ε = 0,1 для остальной части матча, проводя 1000 матчей для сравнения, мы получаем следующее:

Рисунок 6: Сравнение агента с предыдущей версией. Изображение автора.

Количество побед превышает количество поражений (показывая улучшение) до 10 раундов обучения. После этого агент не улучшается от раунда к раунду.

Тест

Пора посмотреть, как наш агент играет в крестики-нолики!

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

Ручная игра

Агент начинает, играя за ‘X’:

Рисунок 7: Игра, сыгранная против агента, с оценками действий. Изображение автора.

Вот как вас жестоко раздавит бесчувственная машина для крестики-нолики!

Как только я поставил ‘О’ в клетку (1, 0), ожидаемый результат увеличился с 0,142 до 0,419, и моя судьба была определена.

Посмотрим, как агент справляется, когда играет вторым:

Рисунок 8: Игра, сыгранная против агента, с оценками действий. Изображение автора.

Он не попал в ловушку, и матч закончился вничью.

Матчи против случайного игрока

Если мы симулируем большое количество матчей против случайного игрока, то получим следующее:

Рисунок 9: Результаты 1000 матчей против случайного игрока. Изображение от автора.

Из 1000 матчей (агент играл первым в половине матчей), агент выиграл 950 матчей, не проиграл ни одного и было 50 ничьих. Это не доказательство того, что наш агент играет оптимально, но он, безусловно, достиг достойного уровня игры.

Заключение

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

Код доступен в этом репозитории. Попробуйте его и дайте мне знать, что вы думаете!