Глядя на то, что пошло не так, и становишься менее жадным

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

Player      |  NN Player 1st          |  NN Player 2nd  
==============================================================
Random      | Not bad but not perfect | Kind of but not really
Min Max     | Mixed — All or nothing  | Mixed — All or nothing
Rnd Min Max | Sometimes / Mixed       | Nope

Что могло пойти не так?

Давайте начнем с рассмотрения нашего кода и попытаемся определить возможные причины, по которым что-то пошло не так, как нужно:

  • Ошибки в коде или другие фундаментальные ошибки.
  • Определенная нами сеть не подходит для этой задачи: входные характеристики не оптимальны, недостаточно слоев, недостаточно большие слои, неоптимальная функция активации, неоптимальный оптимизатор, неоптимальная инициализация веса, неоптимальная функция потерь.
  • Плохие значения для других гиперпараметров: скорость обучения оптимизатора, скидка на вознаграждение, награды за выигрыш / проигрыш / ничью.
  • Данные для обучения не были оптимальными.
  • Функция Tic Tac Toe Q принципиально не поддается изучению нейронными сетями.

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

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

Ошибки в коде или другие фундаментальные ошибки.

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

Сама наша сеть.

Особенности ввода

Мы кодируем состояние платы как 3x9 = 27 бит, указывающих Naughts, Crosses и пустые поля на доске. Есть и другие возможные способы сделать это:

  1. Мы могли бы использовать массив из 9 целых чисел, кодирующих крестики, нолики и пустые пробелы.
  2. Мы могли бы передать хеш-значение нашей платы как единственное целое число.
  3. Мы могли бы создать и добавить некоторые элементы ручной работы: другой массив, указывающий ходы, которые позволят выиграть или проиграть игру; обозначить потенциальные вилки и т. д.
  4. Мы могли бы подавать плату в виде 2D-плоскостей признаков вместо одномерных векторов признаков.

Вариант 1) возможен, но, насколько я помню, все сходятся во мнении, что функции битовых векторов лучше, чем функции с кодировкой значений для таких случаев, как состояния игровой доски. Это также то, что, кажется, используют профессионалы, например DeepMind для AplhaZero. Есть существенные различия в других частях нашего подхода по сравнению с тем, который используется в AlphaZero, и я, кажется, не могу найти никаких жестких ссылок для кодирования битов и значений прямо сейчас, но я достаточно уверен, что мы, вероятно, не так уж и плохо в том, что мы здесь делаем.

Вариант 2) в этом случае должен быть еще хуже.

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

Вариант 4) интересен. Тот факт, что мы используем 1D-вектор, безусловно, теряет некоторые из явно существующих 2D-функций на плате. Для человека было бы очень сложно играть в крестики-нолики, если бы им представили доску таким образом, особенно если ему придется делать это, не преобразовывая ее обратно в 2D-представление в своей голове. С другой стороны, я не понимаю, как наша конкретная сеть сможет использовать 2D-представление. Если бы мы использовали сверточную нейронную сеть, все было бы иначе, но для нашей простой сети я не думаю, что это будет иметь значение. Однако не стесняйтесь попробовать и доложить. Особенно если сработало!

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

Сама сеть

Мы используем только 1 скрытый слой, и этот слой не очень большой. Может, нам стоит попробовать больше или больше скрытых слоев.

Мы используем ReLu в качестве функции активации, и есть другие варианты, которые мы могли бы попробовать, например tanh, leaky relu, sigmoid и т. Д. Возможно, стоит попробовать, но ReLu, похоже, обычно рассматривается как универсальный, который можно использовать, если у вас нет конкретных причина не делать этого. Основываясь на моем очень ограниченном понимании того, что я здесь делаю, я действительно не вижу веской причины использовать какие-либо другие, поэтому мы пока оставим и эту. Если вы хотите поиграть с этим, возможно, попробуйте leaky ReLu и расскажите нам, как все прошло.

Подобно функции активации, GradientDescentOptimizer должен быть здесь хорошим универсалом. Но, опять же, есть много других вариантов, которые можно попробовать, например популярный AdamOptimizer.

Что касается функции потерь, я здесь достаточно уверен. Мы хотим аппроксимировать функцию (Q-функция Tic Tac Toe), поэтому это должно сделать ее проблемой регрессии, что, как я понимаю, должно означать, что Среднеквадратичная ошибка должна быть хорошим выбором.

По моему ограниченному опыту, инициализация веса на удивление важна и может значительно повлиять на производительность.

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

Существуют также различные другие, более сложные сетевые топологии, которые мы могли бы использовать для обучения нейронной сети игре в крестики-нолики: сеть Double Q, сеть Deep Q, сеть Dueling Q, градиент политики, асинхронные действующие и критические агенты (A3C) и т. Д. .

Другие гиперпараметры

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

Данные обучения

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

  • В настоящее время мы всегда выбираем ход, который, по нашему мнению, является лучшим в текущей ситуации. Это называется жадной стратегией / политикой. Это несет в себе риск того, что мы застрянем в определенной модели и никогда не пойдем на риск и не исследуем другие потенциальные ходы. Например. в худшем случае это может означать, что из-за инициализации случайных весов лучший ход в конечном итоге получит оценочное значение Q, которое настолько плохо, что оно никогда не будет выбрано. Поскольку мы никогда не выберем этот ход, у него никогда не будет шанса вызвать положительное вознаграждение и, таким образом, получить шанс скорректировать оценку значения Q. Потенциальным решением для этого является использование ϵ - жадной стратегии, при которой мы большую часть времени используем ход с наибольшим значением Q, но иногда (с вероятностью ϵ - отсюда и название) выбрал неоптимальный ход, чтобы побудить к лучшему исследованию нашего возможного пространства действия.
  • Еще одно ограничение нашего текущего подхода состоит в том, что мы загружаем только последнюю игру на шаг обучения. Риск здесь состоит в том, что если сеть много теряет, она в основном будет получать убытки в качестве обучающих входных данных. Это потенциально может вызвать самоусиливающуюся ситуацию, когда отсутствие каких-либо положительных вознаграждений приведет к тому, что сеть будет предсказывать, что независимо от того, что она делает, всегда будет проигрывать, поэтому все ходы просто плохие. Мы должны проверить эту гипотезу, и, если она верна, может потребоваться добавить кеш ранее сыгранных игр для обратной связи в сети, потенциально искусственно увеличивая количество выборок с положительными результатами вознаграждения.

Мы попробуем и посмотрим, поможет ли это.

Функция Tic Tac Toe Q принципиально не поддается изучению нейронными сетями.

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

Однако наиболее серьезные подходы используют несколько иной подход, чем мы, комбинируя нейронную сеть с другими методами, такими как варианты поиска по дереву, например Поиск дерева Монте-Карло. Это не имело бы особого смысла в нашем сценарии Tic Tac Toe, поскольку мы видели в части Min Max Player, что поиск по дереву сам по себе может справиться с Tic Tac Toe уже, не беспокоясь. Нейронная сеть мало что может сделать здесь, даже если мы сильно ограничим глубину поиска.

Стать менее жадным

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

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

В начале обучения мы хотим быть довольно смелыми и пробовать все виды движений. Чем больше продвигается наше обучение, тем более уверенными мы должны быть в наших оценках значения Q и, следовательно, тем меньше вероятность того, что мы будем пробовать другие случайные ходы. Это означает, что нам также понадобится второй параметр, чтобы постоянно уменьшаться ϵ с течением времени.

Это реализовано в EGreedyNNQPlayer.py.

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

move = np.argmax(probs)

мы сейчас делаем

if self.training is True and np.random.rand(1) < self.random_move_prob:
    move = board.random_empty_spot()
else:
    move = np.argmax(probs)

а во время обучения уменьшаем вероятность сделать случайный ход:

self.random_move_prob *= self.random_move_decrease

Посмотрим, как пойдет. Мы будем использовать новую ϵ-жадную стратегию, а также немного поиграем с другими гиперпараметрами.

Начнем с того, что нейронная сеть будет первой против детерминированного Min Max:

Я не буду здесь перечислять все комбинации. Единственное, что я включу сюда, идет вторым по сравнению с недетерминированным Min Max:

Попробуйте поиграть с приведенным выше примером и попробовать различные комбинации.

К сожалению, в целом я не заметил особых изменений. Однако иногда ϵ-жадный игрок, казалось, мог вырваться из полной проигрышной серии и найти стратегию, которая приводила к 100% ничьим при игре первым против Min-Max. Однако, чтобы увидеть, является ли это статистически значимым, потребуется довольно много прогонов. Намного больше, чем у меня есть время или терпение. Тем более, что он явно не решал проблему в приемлемой степени, поскольку он все еще проигрывает недетерминированному минимальному максимальному значению.

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

Остальные части этой серии можно найти здесь:

Исходный код и записные книжки Jupyter для всех частей этой серии доступны на GitHub.