Почему мой агент DQN не может найти оптимальную политику в недетерминированной среде?

edit: Следующее, похоже, также относится к FrozenLake-v0. Обратите внимание, что меня не интересует простое Q-обучение, поскольку я хочу видеть решения, которые работают с непрерывными пространствами наблюдения.

Недавно я создал banana_gym OpenAI. Сценарий следующий:

У вас банан. Он должен быть продан в течение 2 дней, потому что это будет плохо на 3-й день. Вы можете выбрать цену x, но банан будет продаваться только с вероятностью

enter image description here

Награда x - 1. Если банан не продается на третий день, вознаграждение - -1. (Интуиция: вы заплатили 1 евро за банан). Следовательно, среда не является детерминированной (стохастической).

Действия: вы можете установить цену на что-либо в {0,00, 0,10, 0,20,..., 2,00}

Наблюдения: оставшееся время (источник)

Я рассчитал оптимальную политику:

Opt at step  1: price 1.50 has value -0.26 (chance: 0.28)
Opt at step  2: price 1.10 has value -0.55 (chance: 0.41)

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

Оптимальный расчет политики

Я уверен, что это правильно, но ради полноты

#!/usr/bin/env python

"""Calculate the optimal banana pricing policy."""

import math
import numpy as np


def main(total_time_steps, price_not_sold, chance_to_sell):
    """
    Compare the optimal policy to a given policy.

    Parameters
    ----------
    total_time_steps : int
        How often the agent may offer the banana
    price_not_sold : float
        How much do we have to pay if we don't sell until
        total_time_steps is over?
    chance_to_sell : function
        A function that takes the price as an input and outputs the
        probabilty that a banana will be sold.
    """
    r = get_optimal_policy(total_time_steps,
                           price_not_sold,
                           chance_to_sell)
    enum_obj = enumerate(zip(r['optimal_prices'], r['values']), start=1)
    for i, (price, value) in enum_obj:
        print("Opt at step {:>2}: price {:>4.2f} has value {:>4.2f} "
              "(chance: {:>4.2f})"
              .format(i, price, value, chance_to_sell(price)))


def get_optimal_policy(total_time_steps,
                       price_not_sold,
                       chance_to_sell=None):
    """
    Get the optimal policy for the Banana environment.

    This means for each time step, calculate what is the smartest price
    to set.

    Parameters
    ----------
    total_time_steps : int
    price_not_sold : float
    chance_to_sell : function, optional

    Returns
    -------
    results : dict
        'optimal_prices' : List of best prices to set at a given time
        'values' : values of the value function at a given step with the
                   optimal policy
    """
    if chance_to_sell is None:
        chance_to_sell = get_chance
    values = [None for i in range(total_time_steps + 1)]
    optimal_prices = [None for i in range(total_time_steps)]

    # punishment if a banana is not sold
    values[total_time_steps] = (price_not_sold - 1)

    for i in range(total_time_steps - 1, -1, -1):
        opt_price = None
        opt_price_value = None
        for price in np.arange(0.0, 2.01, 0.10):
            p_t = chance_to_sell(price)
            reward_sold = (price - 1)
            value = p_t * reward_sold + (1 - p_t) * values[i + 1]
            if (opt_price_value is None) or (opt_price_value < value):
                opt_price_value = value
                opt_price = price
        values[i] = opt_price_value
        optimal_prices[i] = opt_price
    return {'optimal_prices': optimal_prices,
            'values': values}


def get_chance(x):
    """
    Get probability that a banana will be sold at a given price x.

    Parameters
    ----------
    x : float

    Returns
    -------
    chance_to_sell : float
    """
    return (1 + math.exp(1)) / (1. + math.exp(x + 1))


if __name__ == '__main__':
    total_time_steps = 2
    main(total_time_steps=total_time_steps,
         price_not_sold=0.0,
         chance_to_sell=get_chance)

Выделение политики DQN +

Следующий агент DQN (реализованный с Keras-RL) работает для CartPole-v0, но изучает политику

1: Take action 19 (price= 1.90)
0: Take action 14 (price= 1.40)

для банановой среды. Он идет в правильном направлении, но он последовательно изучает эту стратегию, а не оптимальную стратегию:

Почему агент DQN не изучает оптимальную стратегию?

Выполнить:

$ python dqn.py --env Banana-v0 --steps 50000

Код для dqn.py:

#!/usr/bin/env python

import numpy as np
import gym
import gym_banana

from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten
from keras.optimizers import Adam

from rl.agents.dqn import DQNAgent
from rl.policy import LinearAnnealedPolicy, EpsGreedyQPolicy
from rl.memory import EpisodeParameterMemory


def main(env_name, nb_steps):
    # Get the environment and extract the number of actions.
    env = gym.make(env_name)
    np.random.seed(123)
    env.seed(123)

    nb_actions = env.action_space.n
    input_shape = (1,) + env.observation_space.shape
    model = create_nn_model(input_shape, nb_actions)

    # Finally, we configure and compile our agent.
    memory = EpisodeParameterMemory(limit=2000, window_length=1)

    policy = LinearAnnealedPolicy(EpsGreedyQPolicy(), attr='eps', value_max=1.,
                                  value_min=.1, value_test=.05,
                                  nb_steps=1000000)
    agent = DQNAgent(model=model, nb_actions=nb_actions, policy=policy,
                     memory=memory, nb_steps_warmup=50000,
                     gamma=.99, target_model_update=10000,
                     train_interval=4, delta_clip=1.)
    agent.compile(Adam(lr=.00025), metrics=['mae'])
    agent.fit(env, nb_steps=nb_steps, visualize=False, verbose=1)

    # Get the learned policy and print it
    policy = get_policy(agent, env)
    for remaining_time, action in sorted(policy.items(), reverse=True):
        print("{:>2}: Take action {:>2} (price={:>5.2f})"
              .format(remaining_time, action, 2 / 20. * action))


def create_nn_model(input_shape, nb_actions):
    """
    Create a neural network model which maps the input to actions.

    Parameters
    ----------
    input_shape : tuple of int
    nb_actoins : int

    Returns
    -------
    model : keras Model object
    """
    model = Sequential()
    model.add(Flatten(input_shape=input_shape))
    model.add(Dense(32, activation='relu'))
    model.add(Dense(64, activation='relu'))
    model.add(Dense(64, activation='relu'))
    model.add(Dense(512, activation='relu'))
    model.add(Dense(nb_actions, activation='linear'))  # important to be linear
    print(model.summary())
    return model


def get_policy(agent, env):
    policy = {}
    for x_in in range(env.TOTAL_TIME_STEPS):
        action = agent.forward(np.array([x_in]))
        policy[x_in] = action
    return policy


def get_parser():
    """Get parser object for script xy.py."""
    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
    parser = ArgumentParser(description=__doc__,
                            formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument("--env",
                        dest="environment",
                        help="OpenAI Gym environment",
                        metavar="ENVIRONMENT",
                        default="CartPole-v0")
    parser.add_argument("--steps",
                        dest="steps",
                        default=10000,
                        type=int,
                        help="how steps are trained?")
    return parser


if __name__ == "__main__":
    args = get_parser().parse_args()
    main(args.environment, args.steps)

Ответ 1

Если я правильно интерпретирую ваш код, он выглядит так, как будто вы используете 50K шагов обучения:

$ python dqn.py --env Banana-v0 --steps 50000

Но также имеет период прогрева 50K шагов, вставив в конструктор DQNAgent следующее:

nb_steps_warmup=50000

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

Для справок в будущем (или, если я ошибаюсь в своей интерпретации вашего кода выше), я бы рекомендовал всегда создавать график кривой обучения (награды эпизодов по оси Y, этапы обучения по оси x). Это всегда полезно, чтобы понять, что происходит, и может помочь вам сосредоточиться на отладке важных частей вашего кода. Если награды не растут вообще, вы знаете, что она не учится вообще по какой-либо причине. Если они некоторое время увеличиваются, но затем плато, вы можете, например, попытаться снизить скорость обучения. Если они действительно увеличиваются и продолжают расти до конца, вы знаете, что это, вероятно, просто еще не сходилось, и вы можете попытаться увеличить количество этапов обучения или увеличить скорость обучения.