2022/09/26

Сегодня я попытался закодировать модель обучения с подкреплением, которая может играть в классическую игру Super Mario Bros для NES. Я откладывал свой журнал, поэтому я поиграл со Stable-Baselines3 в colab, который оказался более сложным, чем необходимо.

Первая проблема при внедрении Stable-Baselines3 в colab заключалась в том, что colab не поддерживает традиционный визуальный вывод для env.render(), который будет отображать каждый кадр и отображать его на экране пользователя. Так что я боролся с проблемой в течение нескольких недель и, наконец, понял, как захватить окружение, отрендерить его и, в конечном итоге, создать mp4-файл процесса.

import gnwrapper
import gym
env = gnwrapper.Monitor(gym.make('env_name'),directory="./")
o = env.reset()
done = False
while not done:
    action = [0]
    obs, reward, done, info = env.step(action)
    if done:
        o = env.reset()
env.display()

gnwrapper оборачивает среду и, если это «сделано», отображает видео процесса каждого шага.

!pip install gym-super-mario-bros
!pip install stable-baselines3

Я скачал необходимые библиотеки

from nes_py.wrappers import JoypadSpace
import gym_super_mario_bros
from gym_super_mario_bros.actions import SIMPLE_MOVEMENT
env = gym_super_mario_bros.make('SuperMarioBros-v0')
env.reset()
env = JoypadSpace(env, SIMPLE_MOVEMENT)

И создал среду

# action_space
# 0 : idle
# 1 : move right
# 2 : jump onece move right
# 3 : run right
# 4 : jump once run right
# 5 : jump once
# 6 : go left

И попробовал каждое действие из action_space вручную, чтобы узнать, что каждое действие делает в среде.

import gnwrapper
import gym
env = gnwrapper.Monitor(gym_super_mario_bros.make('SuperMarioBros-v0'),directory="./")
env = JoypadSpace(env, SIMPLE_MOVEMENT)
obs = env.reset()
done = False
for iter in range(1000):
    action = env.action_space.sample()
    obs, reward, done, info = env.step(action)
    if done:
        done = False
        env.reset()
env.display()

В течение 1000 итераций агент Марио будет выполнять случайное движение. Сначала мне нужно сбросить среду, прежде чем я смогу вызвать функцию шага, которая, по сути, является функцией, которая заставляет агента делать ход. Функция шага также возвращает 4 значения: наблюдение, вознаграждение, выполнено и информация. Наблюдение содержит мир, который может видеть агент, и на основе наблюдения модель может сделать ход. Вознаграждение — это основной ключ RL, максимальное кумулятивное вознаграждение с течением времени — это то, к чему мы стремимся. done говорит нам, завершена ли игра, выиграл ли агент, проиграл, умер, закончил и т. д. info включает в себя всю другую информацию, x_pos, y_pos, life_left может быть там. Если агент завершится до того, как число итераций достигнет 1000, мы должны сбросить его и установить значение False, чтобы начать новую симуляцию. Конечный результат выглядит так

Неплохо для случайного агента, не так ли? Проблема, которую я вскоре понял, заключается в том, что даже с обученной моделью это все, что я мог заставить Марио продвинуться.

Теперь давайте обучим агента.

from stable_baselines3 import PPO
model = PPO('MlpPolicy', env, verbose = 1)
model.learn(10000)

Я выбрал PPO без особых причин, кроме того, что он хорошо работает со многими другими средами. Я определяю свою модель и обучаю ее на 10000 временных шагов с помощью базового графического процессора Colab.

Это выглядит не лучше, чем случайный агент. Сначала я подумал, что это из-за того, что у него было слишком мало обучения, поэтому я увеличил временные шаги до 100 000, что дало почти такой же результат. я был в ярости

Но вскоре я понял проблему. Действие «2» — высокий прыжок вперед.

Путем кропотливых проб и ошибок я понял, что это нажатие кнопки прыжка. Но если его прерывали любой другой кнопкой, прыжок отменялся! И прыжок должен продолжаться около 20 итераций! Таким образом, Марио не мог перепрыгнуть через огромную трубу, потому что модель, вычисляющая 20 действий «2» подряд, заставила бы Марио перепрыгнуть через трубу, было бы слишком маловероятным, 1/7^(20)! Поэтому мне пришлось импровизировать.

env = gnwrapper.Monitor(gym_super_mario_bros.make('SuperMarioBros-v0'),directory="./")
env = JoypadSpace(env, SIMPLE_MOVEMENT)
obs = env.reset()
max_distance = 0
time_stuck = 0
done = False
action = 0
for iter in range(1000):
if iter % 20 == 0:
    action = model.predict(obs.copy())[0]
    obs, reward, done, info = env.step(action)
    if done:
        env.reset()
env.display()

Вместо того, чтобы принимать решение на каждой итерации, давайте заставим агента принимать решение каждые 20 итераций! Это решит проблему, верно?

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

env = gnwrapper.Monitor(gym_super_mario_bros.make('SuperMarioBros-v0'),directory="./")
env = JoypadSpace(env, SIMPLE_MOVEMENT)
obs = env.reset()
max_distance = 0
time_stuck = 0
done = False
actions = [0,2]
action = 0
max_distance = 0
time_stuck = 0
for iter in range(1000):
    action = model.predict(obs.copy())[0]
    obs, reward, done, info = env.step(action)
    if info['x_pos'] > max_distance:
        max_distance = info['x_pos']
        time_stuck = 0
    else:
        time_stuck += 1
        if time_stuck >= 50:
            for j in range(21):
                obs, reward, done, info = env.step(2)
                if done:
                    env.reset()
                    max_distance = 0
    if done:
        env.reset()
        max_distance = 0
env.display()

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

Неплохо!

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