Политический градиент не “обучается”

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

Я пытаюсь реализовать метод градиента политики из книги “Практическое машинное обучение” автора Жерона, который можно найти здесь. В блокноте используется Tensorflow, а я пытаюсь сделать это с помощью PyTorch.

Мои модели выглядят следующим образом:

model = nn.Sequential(
    nn.Linear(4, 128),
    nn.ELU(),
    nn.Linear(128, 2),
)

Критерий и оптимизаторы:

criterion = nn.BCEWithLogitsLoss()
optim = torch.optim.Adam(model.parameters(), lr=0.01)

Обучение:

env = gym.make("CartPole-v0")

n_games_per_update = 10
n_max_steps = 1000
n_iterations = 250
save_iterations = 10
discount_rate = 0.95

for iteration in range(n_iterations): # Запускаем игру 250 раз
    all_rewards = []
    all_gradients = []
    n_steps = []
    optim.zero_grad()
    for game in range(n_games_per_update): # Запускаем игру 10 раз для накопления градиентов
        current_rewards = []
        current_gradients = []
        obs = env.reset()
        for step in range(n_max_steps): # Запускаем одну игру максимум 1000 шагов

            logit = model(torch.tensor(obs, dtype=torch.float))
            output = F.softmax(logit, dim=0)
            c = Categorical(output)
            action = c.sample()

            y = torch.tensor([1.0 - action, action], dtype=torch.float)
            loss = criterion(logit, y)
            loss.backward()

            obs, reward, done, info = env.step(int(action))
            current_rewards.append(reward)
            current_gradients.append([p.grad for p in model.parameters()])
            if done:
                break
        n_steps.append(step)

        all_rewards.append(current_rewards)
        all_gradients.append(current_gradients)

    # Выполняем дисконтирование и нормализацию
    all_rewards = discount_and_normalize_rewards(all_rewards, discount_rate=discount_rate)

    # Для каждой партии из 10 игр умножаем дисконтированные вознаграждения на градиенты сети. Затем берем среднее для каждого слоя
    new_gradients = []
    for var_index, gradient_placeholder in enumerate(gradient_placeholders):
        means = []
        for game_index, rewards in enumerate(all_rewards):
            for step, reward in enumerate(rewards):
                means.append(reward * all_gradients[game_index][step][var_index])
        new_gradients.append(torch.mean(torch.stack(means), 0, True).squeeze(0))

    # Применяем новые градиенты к сети
    for p, g in zip(model.parameters(), new_gradients):
        p.grad = g.clone()
    optim.step()

Когда я запускаю код на 250 взаимодействиях, я печатаю среднюю длину игры, которую получаю:

Iteration: 50, Average Length: 18.2
Iteration: 100, Average Length: 23.4
Iteration: 150, Average Length: 29.9
Iteration: 200, Average Length: 11.2
Iteration: 250, Average Length: 38.6

Сеть на самом деле не улучшается, и более длительное обучение не помогает. У меня два вопроса:
1. Есть ли что-то очевидно неправильное в том, что я делаю?
2. Я заметил, что в реализации на tensorflow используется логарифм вероятности, но я не уверен, как его интегрировать здесь

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

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

Ваш шаг обновления это -log(P(action))*reward, который затем минимизируете с помощью вашего оптимизатора.

P(action)<1 следовательно log(P(action))<0, -log(P(action))>0

Если reward>0, -log(P(action))*reward>0. Минимизация этого значения эквивалентна максимизации log(P(action))*reward<0, что достигается, когда P(action)=1.

С другой стороны, если reward<0, -log(P(action))*reward<0. Это имеет противоположный эффект, при котором P(action) стремится к 0.

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

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

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

1. Обнаружение возможных проблем в коде

Ваш подход к реализации градиентного подъема политики (Policy Gradient) выглядит достаточно хорошо, но есть несколько моментов, которые следует пересмотреть:

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

  • Использование логарифмов вероятностей: В текущей реализации вы пытаетесь минимизировать значение функции потерь, что может вернуть вашу модель в локальные минимумы. Вместо этого вам следует сосредоточиться на максимизации ожидаемого вознаграждения, используя выражение -log(P(action)) * reward для расчетов градиентов.

  • Обработка градиентов: Убедитесь, что вы правильно обрабатываете градиенты. Использование метода loss.backward() для каждого действия в течение игры может привести к неэффективному расчету градиентов, поскольку они могут не суммироваться корректно. Лучше всего сохранять градиенты, но применять их только один раз за итерацию.

2. Интеграция логарифмов вероятностей

Чтобы интегрировать логарифмы вероятностей в вашу реализацию, вы можете переработать логику вашего кода следующим образом:

for step in range(n_max_steps):
    logit = model(torch.tensor(obs, dtype=torch.float))
    output = F.softmax(logit, dim=0)
    c = Categorical(output)
    action = c.sample()

    # Запись логарифма вероятности
    log_prob = c.log_prob(action)  # Логарифм вероятности выбранного действия

    obs, reward, done, info = env.step(int(action))
    current_rewards.append(reward)
    current_log_probs.append(log_prob)  # Сохраняем логарифм вероятности действия

    if done:
        break

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

new_gradients = []
for var_index in range(len(model.parameters())):
    means = []
    for game_index in range(n_games_per_update):
        for step, reward in enumerate(all_rewards[game_index]):
            # Используйте log_prob из текущего действия
            means.append(-current_log_probs[game_index][step] * (reward - baseline))  # baseline - это среднее значение вознаграждений
    new_gradients.append(torch.mean(torch.stack(means), 0).squeeze(0))

Заключение

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

Дополнительные советы:

  • Проводите регулярную визуализацию и анализ результатов для отслеживания прогресса и определения узких мест.
  • Рассмотрите возможность применения других методов оптимизации (например, A2C, PPO) для достижения более качественных результатов.

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

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

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