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

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

Итак, насколько хороши сети? Что ж, когда я играю против них (я экстремальный новичок, так что принимайте это как хотите) и сеть делает первый ход, я почти всегда проигрываю на 7x7 досках (единственный раз, когда я обыграл ее, это в игровое видео, которое я разместил). Когда я играю первый ход, а сеть играет второй, иногда я могу его обыграть. Вы можете посмотреть, как я играю против него несколько партий в этом видео на Youtube.

Нейронная сеть против GNU Go

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

  • Сеть играет первой (GNU Go играет вторым первым ходом):
    Сеть выигрывает 93% (119 побед из 128)
  • Сеть играет вторым (GNU Go делает первый ход):
    Сеть выигрывает 9,4% (12 побед из 128)

Среднее значение этих двух условий будет означать, что сеть выигрывает в 51% случаев. Обратите внимание, что приведенную выше статистику можно воспроизвести с помощью скрипта net_vs_gui.py в репозитории GitHub.

Обучение и сведения о модели

Обучение проходило в течение примерно двух месяцев на двух картах GPU (см. разделы ниже для более подробной информации о моей настройке) и в целом очень похоже на то, что я описывал ранее. Все тренировки для самостоятельной игры проводились в играх, длящихся 32 хода (где ход означает, что каждый игрок делает один ход, то есть каждый игрок получает возможность поставить камень 32 раза). Игры для самостоятельной игры были выпущены 800 раз. Я не реализовал какой-либо механизм отставки для сети, и все игры длились 32 хода (если сеть не могла сделать ход, игра все равно переходила к следующему ходу, не делая никаких ходов).

После того, как каждые 128*5 игр для самостоятельной игры были сгенерированы из «модели генератора», я выполнил обратное распространение по «текущей модели» для 32*5*5 тренировочных шагов по пулу пакетов для самостоятельной игры (который состоял из 35*128 игр, каждая из которых содержала 32 «ходов» [как определено выше]). После обновления градиента «текущая» и «генераторная» модели были сопоставлены друг с другом, чтобы увидеть, можно ли «текущую» модель повысить до следующей «генераторной» модели (см. второй рисунок ниже).

Я зафиксировал веса модели и определение в репозиторий (см. models/). Модель представляет собой 5 слоев, каждый из которых содержит 128 сверточных фильтров. Заголовки значения и политики имеют отдельный полносвязный слой со 128 фильтрами, за которым следует один последний полносвязный слой, соответствующий соответствующему выходному измерению (1 и 7x7 соответственно). В остальном детали модели (норма партии и т. д.) в целом аналогичны AlphaGo Zero и связанным с ней документам.

Первый набор графиков ниже — это обучающие кривые, а второй график — количество раз, когда сеть «продвигалась» (в настоящее время обученная модель была установлена ​​​​как новая модель «генератора» для создания новых обучающих примеров для самостоятельной игры). Вертикальные линии показывают, когда произошло продвижение по службе. Все рисунки в этой статье можно воспроизвести с блокнотом, который я включил в репозиторий (notebooks/training_visualizations.ipynb).

Примеры игр

Вы также можете увидеть несколько примеров моей игры в этом видео. Ниже приведены примеры сетевой игры против GNU Go, случайного противника и самой себя (самостоятельная игра).

Ошибка

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

Оставшаяся загадка

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

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

  • "main": эта модель используется для создания новых обучающих пакетов для самостоятельной игры. «main» хранится как float16s.
  • "eval32": эта модель обучается с использованием обучающих пакетов для самостоятельной игры, созданных "main". «eval32» хранится как float32s.
  • "eval": эта модель предназначена для оценки в матчах Go с "основной" моделью (это копия модели "eval32", преобразованная в float16s). Как только он может с достаточно высокой вероятностью выиграть у «основного», его продвигают (копируют/затирают) «основной» модели.

Обратное распространение никогда не происходит напрямую в моделях «main» и «eval» — они ниже по течению от модели «eval32». По этой причине кажется, что вы никогда не захотите запускать эти модели в режиме training — статистика должна быть установлена ​​и зафиксирована моделью «eval32» по мере ее обучения. Поэтому я думаю, что следующая конфигурация флагов training будет наиболее разумной:

  • "основной": training= False
  • "eval32": training= True
  • оценка: training= False

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

Код

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

Мои настройки

Весь код был протестирован и написан на Centos 8 с использованием Python 2.7.16, Tensorflow v1.15.0 и скомпилирован с помощью NVCC VV10.2.89 (компилятор Nvidia Cuda). Я запускал и тестировал весь код на установке с двумя графическими процессорами (с картой Nvidia 2080 Ti и Nvidia Titan X), четырехъядерном процессоре Intel i5–6600K с тактовой частотой 3,50 ГГц и 48 ГБ ОЗУ (сам код обычно достигает максимума). используя около 35 Гб). На альтернативных конфигурациях не тестировал (хотя какое-то время у меня стояла Ubuntu 18.04 вместо Centos 8 и там тоже все работало). Если бы вы запускали эту настройку, используя меньше ОЗУ, я бы рекомендовал использовать только один графический процессор (и сократить потребность в ОЗУ вдвое) вместо того, чтобы сокращать глубину поиска по дереву (что было бы альтернативным способом сокращения использования ОЗУ).

Дальше

Очевидным следующим шагом для этого будет просто увеличение размера платы и проверка того, насколько далеко я могу зайти в своей установке. Тем не менее, другие участники списка рассылки Computer Go предположили, что, по их мнению, я смогу получить еще более производительные модели с этим типом обучения. Их идеи включали настройку коми (в настоящее время я использую значение 0, что может лишить сеть обучающих сигналов, потому что черный [который играет первым] выигрывает большинство игр с самим собой), используя методы, обеспечивающие достаточное разнообразие игрового процесса. , а также уменьшить начальный размер буфера игр для самостоятельной игры, чтобы начать обучение раньше. Вы можете прочитать больше в теме списка рассылки.

Рамочные жалобы

Хотя я хотел бы продолжить некоторые из вышеизложенных идей, я, к сожалению, недавно обновил свою систему (обновление общего пакета дистрибутива с «yum update» — никаких обновлений для Tensorflow не производилось), и теперь часть моего кода с несколькими графическими процессорами дает сбой, когда он запускается на втором графическом процессоре — несмотря на то, что этот же код отлично работал около года — проблема, похоже, в сбое Tensorflow 1.15, когда я пытаюсь запустить модель.

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

До и во время первоначального выпуска Tensorflow в 2015 году (и до того, как я узнал о Tensorflow) я работал над своей собственной структурой нейронной сети, аналогичной по назначению Tensorflow (хотя мой вариант использования был более узким по объему, чем TF), где я вызывал и используя cuDNN напрямую в дополнение к некоторым другим ядрам CUDA, которые я написал. В любом случае, стоит отметить, что этот код все еще компилируется и работает сегодня, несмотря на то, что я не трогал его несколько лет с тех пор, как перешел на TF.

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

За пределами Go

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