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

Изучив основы машинного обучения — регрессию, кластеризацию и базовые нейронные сети, я начал практиковаться на некоторых распространенных наборах данных. Я также начал смотреть много видео Code Bullet на YouTube, и это вдохновило меня попробовать обучение с подкреплением и объединить мою любовь к машинному обучению и видеоиграм в проект! Я решил воссоздать известную игру Google Chrome Dinosaur, а затем обучить агента играть в нее.

Проект состоит из следующих частей:

  1. Воссоздание игры на Python
  2. Архитектура нейронной сети
  3. Гиперпараметры
  4. Функция обучения
  5. "Полученные результаты"

Воссоздание игры на Python

Я использовал модуль Pygame, чтобы воссоздать игру. Для справки, я следил за серией видеороликов Max Teaches Tech на YouTube о создании игры в pygame. Изображения для игры также были взяты из его репозитория.

За первые 300 очков в игре спаунятся только кактусы. Как только будет получено 300 очков, все препятствия могут быть созданы. Препятствия могут быть разных типов — маленький кактус, большой кактус и птица. У каждого есть шанс 1 из 50 появиться.

Хотя это воссоздание не совсем точно отражает оригинал, оно все же является достаточно хорошей воспроизведенной средой для обучения агента ИИ.

Архитектура нейронной сети

Для создания нейронной сети я использовал простую архитектуру ИНС с одним скрытым слоем. Модель имеет 7 входов и 3 выхода.

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Input(shape=(7,)))
model.add(tf.keras.layers.Dense(4, activation='relu'))
model.add(tf.keras.layers.Dense(3))
model.compile(
    optimizer='adam',
    loss=tf.keras.losses.MeanSquaredError(),
    metrics=['accuracy']
)

Входные данные, которые я использовал, следующие:

  • Расстояние динозавра от препятствия
  • Координата Y динозавра
  • координата Y препятствия
  • Ширина препятствия
  • Скорость игры (поочередно скорость динозавра)
  • Является ли препятствие на экране кактусом или птицей (горячее кодирование)

Выходы просто прыгают, пригибаются и бегают.

Гиперпараметры

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

Для исследования я использовал эпсилон-жадную стратегию с коэффициентом эпсилон-распада 0,997. Эпсилон был инициализирован на 0,45. Хотя я в основном видел, что он инициализирован с 0,99 или каким-то подобным значением, я обнаружил, что 0,45 работает намного лучше и сходится намного быстрее для моего приложения. Причина в том, что в то время как бег и приседание являются похожими действиями и требуют одинакового времени для выполнения, прыжок сильно отличается, поскольку для его выполнения требуется гораздо больше времени. В результате один случайный прыжок эквивалентен примерно 20–30 шагам бега/приседания в самом начале. Чтобы компенсировать это, я уменьшил начальное значение эпсилон, а также уменьшил вероятность прыжка.

Размер пакета для обучения модели составляет 64, а гамма (коэффициент дисконтирования) установлена ​​на 0,95. Кроме того, веса модели копируются в целевую модель после каждых 5 эпизодов.

Функция обучения

INIT_REPLAY_MEM_SIZE = 5_000
REPLAY_MEMORY_SIZE = 45_000
MODEL_NAME = "DINO"
MIN_REPLAY_MEMORY_SIZE = 1_000
MINIBATCH_SIZE = 64
DISCOUNT = 0.95
UPDATE_TARGET_THRESH = 5
EPSILON_INIT = 0.45
EPSILON_DECAY = 0.997
NUM_EPISODES = 2_000
MIN_EPSILON = 0.05
def train(self, terminal_state, step):
    if len(self.init_replay_memory) < MIN_REPLAY_MEMORY_SIZE:
        return
    
    total_mem = list(self.init_replay_memory)
    total_mem.extend(self.late_replay_memory)
    
    minibatch = random.sample(total_mem, MINIBATCH_SIZE)
    current_states = np.array([transition[0] for transition in minibatch])
    current_qs_list = self.model(current_states, training=False).numpy()
    new_current_states = np.array([transition[3] for transition in minibatch])
    future_qs_list = self.target_model(new_current_states, training=False).numpy()
    X = []
    y = []
    for index, (current_state, action, reward, new_current_state, done) in enumerate(minibatch):
        
        if not done:
            max_future_q = np.max(future_qs_list[index])
            new_q = reward + DISCOUNT * max_future_q
        else:
            new_q = reward
        current_qs = current_qs_list[index]
        current_qs[action] = new_q
        X.append(current_state)
        y.append(current_qs)

    self.model.fit(np.array(X), np.array(y), batch_size=MINIBATCH_SIZE, shuffle=False, verbose=0)
    if terminal_state:
        self.target_update_counter += 1
    
    if self.target_update_counter > UPDATE_TARGET_THRESH:
        self.target_model.set_weights(self.model.get_weights())
        self.target_update_counter = 0

Функция обучения во многом вдохновлена ​​​​учебным блогом Sentdex Deep Q Learning, который помог мне четко понять Q Learning и Deep Q Learning.

Полученные результаты

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

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

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

В любом случае, это была моя попытка обучить агента играть в копию Google Dino Game (конечно, за исключением упомянутых изменений) и моя первая попытка обучения с подкреплением! Хотя процесс настройки гиперпараметров временами разочаровывал, я также узнал много новых концепций в процессе. В целом, этот проект усилил мою страсть к глубокому обучению и обучению с подкреплением.

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

Репозиторий GitHub: AtharvaShekatkar/DinoGameAI_V2

Использованная литература:

[1] Переполнение стека

[2] Учебный блог Sentdex Deep Q Learning

[3] Серия видеороликов Max Teaches Tech на YouTube

[4] Учебник по глубокому Q-обучению: minDQN. Практическое руководство по глубоким Q-сетям | Майк Ван | На пути к науке о данных

P.S. Это моя первая попытка вести блог, поэтому любые указатели будут оценены! Ставьте аплодисменты, если вам понравилось прочитанное!