DQN не удается найти оптимальную политику

Вопрос или проблема

Основываясь на публикации DeepMind, я воссоздал среду и пытаюсь сделать так, чтобы DQN находил и сходился к оптимальной стратегии. Задача агента состоит в том, чтобы научиться устойчиво собирать яблоки (объекты), при этом восстановление яблок зависит от их пространственной конфигурации (чем больше яблок вокруг, тем выше восстановление). Короче говоря: агенту нужно найти способ собирать как можно больше яблок (за сбор яблока он получает вознаграждение +1), одновременно позволяя им восстанавливаться, что максимизирует его вознаграждение (если он слишком быстро исчерпает ресурс, он потеряет будущее вознаграждение). Сетка-игра видна на картинке ниже, где игрок – красный квадрат, его направление – серое, а яблоко – зеленое:
вставьте описание изображения здесь
Как указано в публикации, я построил DQN для решения этой игры. Тем не менее, независимо от изменений в скорости обучения, потере, коэффициенте исследования и его убыли, размере партии, оптимизаторе, запасе воспроизведения и увеличении размера нейронной сети, DQN не находит оптимальную стратегию, показанную ниже:
вставьте описание изображения здесь
Я задаюсь вопросом, есть ли какая-то ошибка в моем коде DQN (с похожей реализацией мне удалось решить задачу OpenAI Gym CartPole). Публикую свой код ниже:

class DDQNAgent(RLDebugger):
    def __init__(self, observation_space, action_space):
        RLDebugger.__init__(self)
        # получить размер состояния и действия
        self.state_size = observation_space[0]
        self.action_size = action_space
        # гиперпараметры
        self.learning_rate = .00025
        self.model = self.build_model()
        self.target_model = self.model
        self.gamma = 0.999
        self.epsilon_max = 1.
        self.epsilon = 1.
        self.t = 0
        self.epsilon_min = 0.1
        self.n_first_exploration_steps = 1500
        self.epsilon_decay_len = 1000000
        self.batch_size = 32
        self.train_start = 64
        # создаем память для воспроизведения с использованием deque
        self.memory = deque(maxlen=1000000)
        self.target_model = self.build_model(trainable=False)

    # приближенная Q функция с использованием нейронной сети
    # состояние является входом, а Q значение каждого действия является выходом сети
    def build_model(self, trainable=True):
        model = Sequential()
        # Это простая модель с одним скрытым слоем, хотя ее должно быть достаточно здесь,
        # гораздо проще обучать с различными архитектурами (добавлять слои, менять активацию)
        model.add(Dense(32, input_dim=self.state_size, activation='relu', trainable=trainable))
        model.add(Dense(32, activation='relu', trainable=trainable))
        model.add(Dense(self.action_size, activation='linear', trainable=trainable))
        model.compile(loss="mse", optimizer=RMSprop(lr=self.learning_rate))
        model.summary()
        # 1/ Вы можете попробовать разные функции потерь. Поскольку logcosh является дважды дифференцируемым приближением к Huber loss
        # 2/ С теоретической точки зрения скорость обучения должна уменьшаться с течением времени, чтобы гарантировать сходимость
        return model

    # получить действие от модели с использованием жадной стратегии
    def get_action(self, state):
        if random.random() < self.epsilon:
            return random.randrange(self.action_size)
        q_value = self.model.predict(state)
        return np.argmax(q_value[0])

    # уменьшить epsilon
    def update_epsilon(self):
        self.t += 1
        self.epsilon = self.epsilon_min + max(0., (self.epsilon_max - self.epsilon_min) *
                        (self.epsilon_decay_len - max(0.,
                                 self.t - self.n_first_exploration_steps)) / self.epsilon_decay_len)

    # обучить целевую сеть на выбранном действии и переходе
    def train_model(self, action, state, next_state, reward, done):

        # сохранить выборку <s,a,r,s'> в памяти воспроизведения
        self.memory.append((state, action, reward, next_state, done))

        if len(self.memory) >= self.train_start:
            states, actions, rewards, next_states, dones = self.create_minibatch()

            targets = self.model.predict(states)
            target_values = self.target_model.predict(next_states)

            for i in range(self.batch_size):
                # Приближенное Q-обучение
                if dones[i]:
                    targets[i][actions[i]] = rewards[i]
                else:
                    targets[i][actions[i]] = rewards[i] + self.gamma * (np.amax(target_values[i]))

            # и выполнить подгонку модели!
            loss = self.model.fit(states, targets, verbose=0).history['loss'][0]

            for i in range(self.batch_size):
                self.record(actions[i], states[i], targets[i], target_values[i], loss / self.batch_size, rewards[i])

    def create_minibatch(self):
        # случайный выбор образцов из памяти воспроизведения (с использованием batch_size)

        batch_size = min(self.batch_size, len(self.memory))
        samples = random.sample(self.memory, batch_size)

        states = np.array([_[0][0] for _ in samples])
        actions = np.array([_[1] for _ in samples])
        rewards = np.array([_[2] for _ in samples])
        next_states = np.array([_[3][0] for _ in samples])
        dones = np.array([_[4] for _ in samples])

        return (states, actions, rewards, next_states, dones)

    def update_target_model(self):
        self.target_model.set_weights(self.model.get_weights())

И вот код, который я использую для обучения модели:

from dqn_agent import *
from environment import *

env = GameEnv()
observation_space = env.reset()

agent = DDQNAgent(observation_space.shape, 7)

state_size = observation_space.shape[0]
last_rewards = []
episode = 0
max_episode_len = 1000
while episode < 2100:
    episode += 1
    state = env.reset()
    state = np.reshape(state, [1, state_size])
    #if episode % 100 == 0:
     #   env.render_env()
    total_reward = 0

    step = 0
    gameover = False
    while not gameover:
        step += 1
        #if episode % 100 == 0:
         #   env.render_env()
        action = agent.get_action(state)
        reward, next_state, done = env.step(action)
        next_state = np.reshape(next_state, [1, state_size])
        total_reward += reward
        agent.train_model(action, state, next_state, reward, done)
        agent.update_epsilon()
        state = next_state
        terminal = (step >= max_episode_len)
        if done or terminal:
            last_rewards.append(total_reward)
            agent.update_target_model()
            gameover = True

    print('эпизод:', episode, 'кумулятивное вознаграждение: ', total_reward, 'epsilon:', agent.epsilon, 'шаг', step)

Модель обновляется после каждого эпизода (эпизод = 1000 шагов).

Смотря на логи, агент иногда достигает очень высоких результатов несколько раз подряд, но всегда не удается стабилизироваться, и результаты от эпизода к эпизоду имеют чрезвычайно высокую дисперсию (даже после увеличения epsilon и запуска в течение нескольких тысяч эпизодов). Изучая мой код и игру, есть ли у вас какие-либо идеи о том, что может помочь алгоритму стабилизировать результаты/сойтись? Я много экспериментировал с гиперпараметрами, но ничего не дало значительного улучшения.

Некоторые параметры по игре и обучению:
Вознаграждение: +1 за сбор каждого яблока (зеленый квадрат)
Эпизод: 1000 шагов, после 1000 шагов или в случае полного истощения ресурсов игрока игра автоматически сбрасывается.
Обновление целевой модели: после каждого завершения игры
Гиперпараметры можно найти в приведенном выше коде.

Дайте знать, если у вас есть какие-либо идеи, рад поделиться репозиторием на GitHub. Не стесняйтесь писать мне на [email protected]

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

ИЗМЕНЕНИЕ: Добавил график вознаграждений (ниже).

вставьте описание изображения здесь

Вы говорите, что агент может достигать высоких результатов, так что, похоже, он чему-то учится, но не может сделать это последовательно. Есть ли значительное падение производительности в некоторые моменты?

Не наблюдается улучшения результатов, если вы используете Adam вместо RMSProp? Я был бы удивлен, если бы это была “сложная” среда, требующая множества настроек. Мне интересно, как ваш график вознаграждений выглядит по сравнению с просто “случайным” поведением, которое, как мне кажется, также могло бы выжить в этой среде.

Ответ или решение

Дорогой задающий вопрос,

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

### 1. Исследование и эксплуатация

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

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

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

### 3. Обновление целевой модели

Ваш код обновляет целевую модель после завершения каждой игры. Это может быть слишком часто. Некоторые исследования рекомендовали обновление целевой модели менее часто, например, каждые N эпизодов. Попробуйте обновлять целевую модель раз в 10 или 20 эпизодов, чтобы обеспечить более стабильное обучение.

### 4. Параметры обучения

Вы используете RMSprop в качестве оптимизатора. Хотя этот алгоритм работает в большинстве сценариев, попробуйте Adam, который может обеспечить более стабильное и быстрое обучение в сложных средах. Он автоматически подстраивает скорость обучения для каждой переменной.

### 5. Функция потерь и нормализация

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

### 6. Запоминание и реминистрация

Ваш механизм памяти использует deque для хранения опытов. Проверьте, что ваши воспоминания разнообразны и не содержат слишком много схожих взаимодействий. Это может привести к переобучению и снижению производительности. Вы можете рассмотреть возможности для “приоритетной” выборки из буфера воспоминаний, чтобы давать предпочтение более важным данным.

### 7. Вознаграждение и штрафы

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

### Заключение

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

Если вы хотите, я готов помочь вам более детально проанализировать ваш код и процесс тренировки. Не стесняйтесь делиться дополнительной информацией или задавать вопросы.

С уважением и надеждой на успешное разрешение вашей проблемы,
[Ваша подпись]

Оцените материал
Добавить комментарий

Капча загружается...