Обучение нейронной сети игре в блэкджек

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

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

  • Казино получают преимущество над игроками в блэкджек, заставляя игроков действовать раньше дилера (и действовать на основе неполной информации). Это в первую очередь подвергает их риску банкротства (так что все они могли разориться еще до того, как у дилера появится шанс действовать).
  • Игроки особенно подвержены опасности, когда их комбинация составляет от 12 до 16 (они рискуют проиграть следующей картой), а крупье показывает старшую карту. В этих случаях предполагается, что у дилера будет больше очков, поэтому игроки должны выпасть или погибнуть. Мы можем увидеть это визуально по вероятности выигрыша или ничьей между 12 и 16 (Долина Отчаяния).

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

Если вы не знакомы с игрой в блэкджек, в моем предыдущем посте также описывается, как в нее играют и правила.

Но может ли глубокое обучение лучше?

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

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

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

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

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

Создание наших обучающих данных

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

Что мы хотим предсказать? На мой взгляд, есть два кандидата на роль нашей целевой переменной:

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

На самом деле мне потребовалось время, чтобы придумать, как это лучше всего настроить. Но вот что я придумал.

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

  • Если игрок попадает и побеждает, то попадание (Y = 1) было правильным решением.
  • Если игрок попадает и проигрывает, то правильным решением было остаться (Y = 0).
  • Если игрок остается и выигрывает, то решение остаться (Y = 0) было правильным.
  • Если игрок остается и проигрывает, то правильным решением было попадание (Y = 1).

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

  1. Открытая карта дилера (вторая скрыта от глаз).
  2. Общая стоимость руки игрока.
  3. Есть ли у игрока туз или нет.
  4. Действие игрока (ударить или остаться).

И целевая переменная - это правильное решение, как определено приведенной выше логикой.

Обучение нейронной сети

Мы будем использовать библиотеку Keras для нашей нейронной сети. Давайте сначала разберемся с импортом:

from keras.models import Sequential
from keras.layers import Dense, LSTM, Flatten, Dropout

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

# Set up variables for neural net
feature_list = [i for i in model_df.columns if i not in
                ['dealer_card','Y','lose','correct_action']
               ]
train_X = np.array(model_df[feature_list])
train_Y = np.array(model_df['correct_action']).reshape(-1,1)

Строки кода для создания и обучения нашей нейронной сети довольно просты. Первая строка (строка 1) создает нейронную сеть последовательного типа, которая представляет собой линейную последовательность слоев нейронной сети. Строки после строки 1 добавляют слои к нашей модели один за другим (плотный - это простейший тип слоя, представляющий собой всего лишь набор нейронов) - числа вроде 16, 128 и т. Д. Определяют количество нейронов в каждом слое.

Наконец, для последнего слоя нам нужно выбрать функцию активации. Это преобразует необработанный вывод нейронной сети в нечто, что мы можем интерпретировать. Обратите внимание на два момента о последнем слое. Во-первых, он включает только один нейрон, потому что мы прогнозируем между двумя возможными исходами (проблема двух классов). Во-вторых, мы используем сигмовидную активацию, потому что хотим, чтобы наша нейронная сеть действовала как логистическая регрессия и предсказывала, будет ли правильным ходом ударить (Y = 1) или остаться (Y = 0) - другими словами, мы хотим знать вероятность того, что удар будет правильным ходом.

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

# Set up a neural net with 5 layers
model = Sequential()                         # line 1
model.add(Dense(16))
model.add(Dense(128))
model.add(Dense(32))
model.add(Dense(8)) 
model.add(Dense(1, activation='sigmoid'))    # final layer
model.compile(loss='binary_crossentropy', optimizer='sgd')
model.fit(train_X, train_Y, epochs=20, batch_size=256, verbose=1)

Проверка производительности нашей модели

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

На приведенном ниже графике показана ROC-кривая нашей нейронной сети для игры в блэкджек - нейронная сеть, кажется, добавляет справедливую ценность по сравнению с случайными угадыванием (красная пунктирная линия). Его площадь под кривой, или AUC, 0,73 значительно выше, чем AUC для случайного предположения (0,50).

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

Время играть!

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

Для этого я написал следующую функцию - функция model_decision принимает функции, которые требуются нейронной сети, делает прогноз, используя эти функции, и сравнивает этот прогноз с предопределенным порогом, чтобы решить, ударить или остаться. Я использую 0,52, потому что мы уже знаем с прошлого раза, что перебор - самый большой риск для игрока в блэкджек. Таким образом, использование 0,52 в качестве порогового значения для попадания делает нашу модель немного менее вероятной, и, следовательно, немного менее вероятной для разрушения.

def model_decision(model, player_sum, has_ace, dealer_card_num):
    input_array = np.array([player_sum, 0, has_ace,
                            dealer_card_num]).reshape(1,-1)
    predict_correct = model.predict(input_array)
    if predict_correct >= 0.52:
        return 1
    else:
        return 0

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

Наша модель довольно хороша!

Наконец, давайте сравним производительность нашей нейронной сети как с наивной, так и со случайной стратегиями. Напомнить всем:

  • Я провел около 300 000 симуляций блэкджека для каждого типа стратегии (нейросетевой, простой и случайной).
  • Наивная стратегия состоит в том, чтобы ударить только тогда, когда вероятность перебора равна нулю (ударить при сумме рук менее 12 и остаться при сумме рук 12 или более).
  • Случайная стратегия - подбросить монету - если выпадет орел, в противном случае останьтесь. А если вы попали и не разорились, то снова подбросьте монету и начните весь процесс заново.

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

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

Мы также можем посмотреть, как вероятность выигрыша или ничьей зависит от исходной суммы руки игрока. Это выглядит многообещающе - наша нейронная сеть работает так же или даже лучше по всем направлениям. И в отличие от наивной стратегии, которая работает даже хуже, чем случайное угадывание в Долине отчаяния (значения руки игрока от 12 до 16), наша нейронная сеть работает лучше.

Самый последний сюжет намекает на то, как нейронная сеть может превзойти наивную стратегию. Наивная стратегия (из-за того, как мы ее закодировали) не желает рисковать в любое время, когда есть хотя бы отдаленный риск банкротства. С другой стороны, нейронная сеть регулярно достигает 12, 13, 14 или 15 секунд. Более тонкое принятие решений и способность принимать на себя просчитанные риски, кажется, отличает его от наивной стратегии.

Мы можем взглянуть на то, что делает нейронная сеть, когда сумма рук игрока составляет от 12 до 16, чтобы попытаться улучшить нашу наивную стратегию (и не потерять столько денег в казино).

Похоже, что есть сильное предпочтение попасть, когда крупье показывает старшую карту (8, 9 или 10). Но даже когда дилер показывает низкую карту, например 3, нейронная сеть по-прежнему выбирает попадание в 60% случаев - это потому, что нейронная сеть принимает во внимание все функции, которые она имеет в своем распоряжении при принятии решения. Похоже, что мы не можем легко свести его решения к нескольким простым практическим правилам.

Заключение

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

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

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

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

Надеюсь, вам это показалось таким же интересным, как и мне. Ваше здоровье!

Подборка моих недавних сообщений, которые, я надеюсь, вы прочитаете:

Улучшение собеседований по науке о данных

Действительно ли ваша компания управляется данными?

Есть ли риск автоматизации для специалистов по данным?

Сколько зарабатывают специалисты по данным?

Сколько специалистов по данным делают Часть 2

Сколько зарабатывают инженеры-программисты?