Занятие 6

Лабораторная 6

1. Базовые понятия обучения с подкреплением

Агент — это сущность, которая принимает решения. Это может быть робот, компьютерная программа или даже человек. Агент действует в среде, чтобы достичь определенной цели.

Среда — это мир, в котором действует агент. Она отвечает на действия агента, предоставляя новое состояние и награду. Среда может быть как физической (например, реальный мир), так и виртуальной (например, видеоигра).

Состояние ($s$) — это описание текущего положения агента в среде. Оно содержит всю информацию о среде, необходимую для принятия решений. Например, в игре в шахматы состояние включает расположение всех фигур на доске.

$$ s_t \in S $$
  • $s_t$: текущее состояние в момент времени $t$.
  • $S$: множество всех возможных состояний.

Действие ($a$) — это выбор, который делает агент в каждом состоянии. Действия могут быть дискретными (например, движение "влево", "вправо") или непрерывными (например, угол поворота робота).

$$ a_t \in A(s_t) $$
  • $a_t$: действие, выбранное агентом в момент времени $t$.
  • $A(s_t)$: множество допустимых действий в состоянии $s_t$.

Награда ($r$) — это числовая обратная связь, которую агент получает от среды после выполнения действия. Цель агента — максимизировать общую награду за время взаимодействия со средой.

$$ r_t = R(s_t, a_t) $$
  • $r_t$: награда, полученная в момент времени $t$.
  • $R(s_t, a_t)$: функция награды, зависящая от состояния $s_t$ и действия $a_t$.

Политика ($\pi$) — это стратегия, которую использует агент для выбора действий. Политика может быть детерминированной (выбирается одно конкретное действие) или стохастической (каждому действию присваивается вероятность).

$$ \pi(a_t | s_t) $$
  • $\pi(a_t | s_t)$: вероятность выбора действия $a_t$ в состоянии $s_t$.

Траектория — это последовательность состояний, действий и наград, которые агент проходит за время взаимодействия со средой:

$$ \tau = (s_0, a_0, r_0, s_1, a_1, r_1, \dots, s_T) $$
  • $T$: момент окончания эпизода (если он существует).

Возврат ($G_t$) — это сумма всех будущих наград, дисконтированных с учетом временного фактора:

$$ G_t = \sum_{k=0}^{T-t} \gamma^k r_{t+k+1} $$
  • $\gamma \in [0, 1]$: коэффициент дисконтирования, который определяет важность будущих наград по сравнению с текущими.
  • Если $\gamma = 0$, агент заботится только о немедленной награде.
  • Если $\gamma = 1$, агент учитывает все будущие награды одинаково.

Функция ценности ($V(s)$) оценивает, насколько хорошо находиться в данном состоянии:

$$ V^\pi(s) = \mathbb{E}_\pi \left[ G_t | s_t = s \right] $$
  • $V^\pi(s)$: ожидаемый возврат при следовании политике $\pi$ из состояния $s$.

Q-функция ($Q(s, a)$) оценивает, насколько хорошо выполнить данное действие в данном состоянии:

$$ Q^\pi(s, a) = \mathbb{E}_\pi \left[ G_t | s_t = s, a_t = a \right] $$
  • $Q^\pi(s, a)$: ожидаемый возврат при выполнении действия $a$ в состоянии $s$ и дальнейшем следовании политике $\pi$.

Уравнение Беллмана связывает значение состояния с значениями последующих состояний:

$$ V^\pi(s) = \sum_{a} \pi(a | s) \sum_{s'} P(s' | s, a) \left[ R(s, a, s') + \gamma V^\pi(s') \right] $$$$ Q^\pi(s, a) = \sum_{s'} P(s' | s, a) \left[ R(s, a, s') + \gamma \sum_{a'} \pi(a' | s') Q^\pi(s', a') \right] $$
  • $P(s' | s, a)$: вероятность перехода из состояния $s$ в состояние $s'$ при выполнении действия $a$.
  • $R(s, a, s')$: награда за переход из $s$ в $s'$ при действии $a$.

Оптимальная политика ($\pi^*$) максимизирует ожидаемый возврат для всех состояний:

$$ \pi^* = \arg\max_\pi V^\pi(s), \quad \forall s \in S $$

Оптимальная Q-функция ($Q^*(s, a)$) также определяется аналогично:

$$ Q^*(s, a) = \max_\pi Q^\pi(s, a) $$

Библиотека Gym

Gym — это библиотека для создания и тестирования алгоритмов обучения с подкреплением.

env = gym.make('CartPole-v1')
  • 'CartPole-v1': название среды. В данном случае это задача CartPole.
  • env: объект среды, который предоставляет методы для взаимодействия.

Основные методы среды:

  • reset(): Сбрасывает среду в начальное состояние и возвращает начальное состояние.
  • step(action): Выполняет действие в среде и возвращает кортеж (next_state, reward, done, info).
    • next_state: новое состояние после выполнения действия.
    • reward: награда за выполненное действие.
    • done: флаг, указывающий, завершился ли эпизод.
    • info: дополнительная информация (например, диагностические данные).
  • render(): Визуализирует текущее состояние среды.
  • close(): Закрывает окно визуализации.

Каждая среда имеет два основных пространства:

  • observation_space: пространство состояний (states). Описывает возможные состояния среды.
  • action_space: пространство действий (actions). Описывает возможные действия агента.
print("Observation space:", env.observation_space)
print("Action space:", env.action_space)
In [ ]:
import gymnasium as gym
env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0]  # Размерность состояний (4)
action_dim = env.action_space.n            # Количество действий (2)
  1. Действие $0$ (влево):

    • Тележка движется влево.
    • Если шест начинает отклоняться вправо, он может вернуться к вертикальному положению.
  2. Действие $1$ (вправо):

    • Тележка движется вправо.
    • Если шест отклоняется влево, он может вернуться к вертикальному положению.
  3. Награда:

    • За каждый шаг агент получает награду $r = 1$, пока шест остается в вертикальном положении.
  4. Завершение эпизода:

    • Эпизод завершается, если:
      • Угол шеста превышает 15 градусов от вертикали.
      • Тележка выходит за пределы экрана ($|x| > 2.4$).
In [ ]:
import matplotlib.pyplot as plt

# Создаем среду с режимом визуализации "rgb_array"
env = gym.make('CartPole-v1', render_mode="rgb_array")

# Сбрасываем среду в начальное состояние
state = env.reset()

# Выполняем несколько случайных действий
for i in range(100):
    # Получаем изображение среды
    if i % 10 == 0:
      rgb_array = env.render()

      # Отображаем изображение с помощью matplotlib
      plt.imshow(rgb_array)
      plt.axis('off')  # Отключаем оси
      plt.show()

    action = env.action_space.sample()  # Случайное действие
    next_state, reward, done, info, _ = env.step(action)  # Выполняем действие
    print(f"State: {next_state}, Action: {action}, Reward: {reward}, Done: {done}")

    if done:
        print("Эпизод завершен!")
        break

# Закрываем окно визуализации
env.close()

Задача1 (2 балла)

  1. Какое число состояний в данной задаче?
  2. Какое число действий?
  3. Чему равна награда?
  4. Предложите наивный (какой захотите) метод управления системой и проилюстрируйте его работу.

3. ε-жадная стратегия (ε-Greedy Policy)

Идея:

  1. Использование (Exploitation): С вероятностью $1 - \epsilon$ агент выбирает действие, которое, как он считает, является наилучшим в текущем состоянии.
  2. Исследование (Exploration): С вероятностью $\epsilon$ агент выбирает случайное действие, чтобы исследовать новые возможности.

Пусть $Q(s, a)$ — это оценка Q-функции для состояния $s$ и действия $a$. Тогда вероятность выбора действия $a$ в состоянии $s$ задается следующим образом:

$$ \pi(a | s) = \begin{cases} 1 - \epsilon + \frac{\epsilon}{|A|}, & \text{если } a = \arg\max_{a'} Q(s, a') \\ \frac{\epsilon}{|A|}, & \text{иначе} \end{cases} $$
  • $|A|$: количество доступных действий.
  • $\arg\max_{a'} Q(s, a')$: действие с максимальным Q-значением в состоянии $s$.

Алгоритм выбора действия можно записать так:

$$ a = \begin{cases} \arg\max_{a'} Q(s, a'), & \text{с вероятностью } 1 - \epsilon \\ \text{случайное действие из } A, & \text{с вероятностью } \epsilon \end{cases} $$
  • $\epsilon \in [0, 1]$: параметр, управляющий балансом между исследованием и использованием.
  • Если $\epsilon = 0$, агент всегда выбирает наилучшее известное действие (чистое использование).
  • Если $\epsilon = 1$, агент всегда выбирает случайное действие (чистое исследование).

2.1 Алгоритм

  1. Инициализация:

    • Инициализировать Q-функцию $Q(s, a)$ (например, нулями).
    • Задать начальное значение $\epsilon$.
  2. Выбор действия:

    • В каждом состоянии $s$:
      • С вероятностью $1 - \epsilon$ выбрать действие $a = \arg\max_{a'} Q(s, a')$.
      • С вероятностью $\epsilon$ выбрать случайное действие $a \sim A$.
  3. Обновление Q-функции:

    • После выполнения действия $a$ в состоянии $s$ получить награду $r$ и новое состояние $s'$.
    • Обновить Q-функцию с помощью правила обновления (например, Q-learning):
$$ Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma \max_{a'} Q(s', a') - Q(s, a) \right] $$
  • $\alpha$: скорость обучения ($0 < \alpha \leq 1$).
  • $\gamma$: коэффициент дисконтирования ($0 \leq \gamma \leq 1$).
  1. Уменьшение ε:

    • Уменьшить $\epsilon$ по заранее определенному правилу (например, экспоненциально).
  2. Повторение:

    • Повторять шаги 2–4 до завершения обучения.

2.2 Среда Taxi

Рассмотрим среду (игру) Taxi.

  • Состояния (States):
    • Всего 500 возможных состояний ($5 \times 5 \times 5 \times 4$):
      • $5 \times 5$: местоположение такси.
      • $5$: возможные места подбора/высадки пассажира.
      • $4$: текущее состояние пассажира (в такси или нет).
  • Действия (Actions):
    • Дискретное множество из 6 действий:
      1. Движение на север.
      2. Движение на юг.
      3. Движение на восток.
      4. Движение на запад.
      5. Подобрать пассажира.
      6. Высадить пассажира.
  • Награды (Rewards):
    • +20 за успешную доставку пассажира.
    • -1 за каждый временной шаг (чтобы стимулировать быстрое выполнение задачи).
    • -10 за неправильные действия (например, попытка подобрать пассажира не в том месте).
In [ ]:
import gymnasium as gym

# Создаем среду
env = gym.make('Taxi-v3', render_mode="rgb_array")

# Сбрасываем среду в начальное состояние
state = env.reset()

# Выполняем заданную последовательность действий
actions = [1, 1, 2, 2, 4, 2, 1, 5]  # Пример последовательности действий
for action in actions:
    if i % 1 == 0:
      rgb_array = env.render()

      # Отображаем изображение с помощью matplotlib
      plt.imshow(rgb_array)
      plt.axis('off')  # Отключаем оси
      plt.show()

    next_state, reward, done, info, _ = env.step(action)  # Выполняем действие
    print(f"Action: {action}, Reward: {reward}, Done: {done}")

    if done:
        print("Эпизод завершен!")
        break

# Закрываем окно визуализации
env.close()

Задача 2 (4 балла)

Реализуйте e-жадную стратегию для данной задачи:

  1. Реализуйте choose_action (функцию выбора действия).
  2. Реализуйте update_q_value (обновление Q-функции по формуле $ Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma \max_{a'} Q(s', a') - Q(s, a) \right]. $)
  3. Реализуйте функцию обучения агента.
  4. Постройте график зависимости награды от эпизода.
In [ ]:
class EpsilonGreedyAgent:
    def __init__(self, num_states, num_actions, epsilon=0.1, alpha=0.1, gamma=0.99):
        self.num_states = num_states
        self.num_actions = num_actions
        self.epsilon = epsilon
        self.alpha = alpha
        self.gamma = gamma
        self.q_table = np.zeros((num_states, num_actions))  # Q-таблица

    def choose_action(self, state):
        # ВАШ КОД

    def update_q_value(self, state, action, reward, next_state):
        # ВАШ КОД
In [ ]:
def train_agent(env, agent, num_episodes=1000, max_steps=200):
    rewards_history = []
    for episode in range(num_episodes):
        state = env.reset()[0]
        total_reward = 0
        done = False

        for step in range(max_steps):

            # выполните действие
            # ВАШ КОД
            # получите новое состояние и награду
            # ВАШ КОД
            # обновите q функцию
            # ВАШ КОД

            state = next_state
            total_reward += reward

            if done:
                break

        rewards_history.append(total_reward)

        if episode % 100 == 0:
            print(f"Episode {episode}: Total Reward = {total_reward}")

    return rewards_history
In [ ]:
import numpy as np
# Создаем среду
env = gym.make('Taxi-v3')

# Создаем агента
num_states = env.observation_space.n  # Количество состояний (500)
num_actions = env.action_space.n      # Количество действий (6)
agent = EpsilonGreedyAgent(num_states, num_actions, epsilon=0.1)

# Обучаем агента
rewards_history = train_agent(env, agent)

3. Глубокое Q-обучение (Deep Q-Learning, DQN)

3.1. Q-функция

Q-функция $ Q(s, a) $ оценивает, насколько хорошо выполнить действие $ a $ в состоянии $ s $. Цель агента — найти оптимальную Q-функцию:

$$ Q^*(s, a) = \max_\pi \mathbb{E} \left[ G_t | s_t = s, a_t = a \right] $$

где $ G_t $ — возврат (discounted reward).

В DQN Q-функция аппроксимируется нейронной сетью:

$$ Q(s, a; \theta) \approx Q^*(s, a) $$

где $ \theta $ — параметры нейронной сети.

3.2. Обновление Q-функции

Обновление Q-значений выполняется по формуле:

$$ Q(s, a; \theta) \leftarrow Q(s, a; \theta) + \alpha \left[ r + \gamma \max_{a'} Q(s', a'; \theta) - Q(s, a; \theta) \right] $$

где:

  • $ r $: награда за выполненное действие.
  • $ \gamma $: коэффициент дисконтирования.
  • $ s' $: новое состояние.

3.3 Выбор действия

Агент выбирает действие с помощью ε-жадной стратегии:

$$ a = \begin{cases} \arg\max_{a'} Q(s, a'; \theta), & \text{с вероятностью } 1 - \epsilon \\ \text{случайное действие}, & \text{с вероятностью } \epsilon \end{cases} $$

3.4 Алгоритм обучения

Опытный реплей:

Для стабилизации обучения используется буфер опыта (replay buffer), который хранит переходы $(s, a, r, s', \text{done})$. На каждом шаге обучения случайная мини-батч извлекается из буфера.

Обновление параметров:

На каждом шаге выполняются следующие действия:

  1. Вычисление текущих Q-значений: $$ Q(s, a; \theta) $$

  2. Вычисление целевых Q-значений: $$ y = r + \gamma \max_{a'} Q(s', a'; \theta), \quad \text{если эпизод не завершен} $$ $$ y = r, \quad \text{если эпизод завершен} $$

  3. Минимизация функции потерь: $$ L = \mathbb{E} \left[ (y - Q(s, a; \theta))^2 \right] $$

  4. Обновление параметров сети с помощью градиентного спуска.

Задача 3 (4 балла)

Реализуйте DQN для задачи CartPole-v1:

  1. Реализуйте Qfunction(nn.Module) (класс полносвязной нейронной сети).
  2. Реализуйте select_action (e-выбор действия)
  3. Реализуйте метод get_action получение действия
  4. Реализуйт метод fit обучение нейронной сети.
  5. Постройте график зависимости награды от эпизода.
  6. Постройте график зависимости награды от эпизода для различных значений $γ$, batch_size и epsilon_decrease. (для каждого параметра 3 различных значений).
In [ ]:
import torch.nn as nn
import torch

import numpy as np

import random

import gym
import matplotlib.pyplot as plt
In [ ]:
import numpy as np
print(np.__version__)
# необходимо numpy == 1.23.5
In [ ]:
# Q-функция
class Qfunction(nn.Module):
  # ВАШ КОД
In [ ]:
# Deep Q-Networks
class DQN():
  def __init__(self, state_dim, action_dim, gamma=0.99, batch_size=32, lr=0.01, epsilon_decrease=0.01, epsilon_min=0.01):
    self.state_dim = state_dim
    self.action_dim = action_dim
    self.q_function = Qfunction(self.state_dim, self.action_dim)
    self.batch_size = batch_size
    self.gamma = gamma
    self.epsilon = 1
    self.epsilon_decrease = epsilon_decrease
    self.epsilon_min = epsilon_min
    self.memory = []
    self.lr = lr
    self.optimizer = torch.optim.Adam(self.q_function.parameters(), lr = self.lr)

  def get_action(self, state):
    # ВАШ КОД
    return action

  def get_batch(self):
    batch = random.sample(self.memory, self.batch_size)

    states, actions, rewards, dones, next_states = [], [], [], [], []
    for five in batch:
      states.append(five[0])
      actions.append(five[1])
      rewards.append(five[2])
      dones.append(five[3])
      next_states.append(five[4])

    states = torch.FloatTensor(states)
    actions = torch.LongTensor(actions)
    rewards = torch.FloatTensor(rewards)
    dones = torch.FloatTensor(dones)
    next_states = torch.FloatTensor(next_states)

    return states, actions, rewards, dones, next_states

  def fit(self, state, action, reward, done, next_state):
    self.memory.append([state, action, reward, done, next_state])

    if len(self.memory) > self.batch_size:

      states, actions, rewards, dones, next_states = self.get_batch()

      # обновляем targets (y в формуле выше)
      # ВАШ КОД
      # расчитываем q_values
      # ВАШ КОД
      # расчитываем loss
      # ВАШ КОД
      # делаем шаг градиентного спуска
      # ВАШ КОД
      self.optimizer.step()
      self.optimizer.zero_grad()

      if self.epsilon > self.epsilon_min:
        self.epsilon -= self.epsilon_decrease
In [ ]:
# Работа метода
env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n

agent = DQN(state_dim, action_dim)

list_of_reward = []
episode_n = 50
t_max = 500

for episode in range(episode_n):
  total_reward = 0

  state = env.reset()
  for t in range(t_max):
    action = agent.get_action(state)
    next_state, reward, done, _ = env.step(action)

    total_reward = total_reward + reward

    agent.fit(state, action, reward, done, next_state)

    state = next_state
    if done:
      break

  print("total_reward:", total_reward)
  list_of_reward.append(total_reward)