Эта статья является продолжением статьи, которую я написал около года назад о своей работе по реализации чего-то очень похожего на алгоритм 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» хранится как
float16
s. - "eval32": эта модель обучается с использованием обучающих пакетов для самостоятельной игры, созданных "main". «eval32» хранится как
float32
s. - "eval": эта модель предназначена для оценки в матчах Go с "основной" моделью (это копия модели "eval32", преобразованная в
float16
s). Как только он может с достаточно высокой вероятностью выиграть у «основного», его продвигают (копируют/затирают) «основной» модели.
Обратное распространение никогда не происходит напрямую в моделях «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 с использованием минимального оборудования, определенно меня обнадеживает. Сети могут быть не на уровне Ли Седоля, но они все еще могут быть достаточно хорошими, чтобы быть хорошим противником против многих из нас :)