Занятие 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)
import gymnasium as gym
env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0] # Размерность состояний (4)
action_dim = env.action_space.n # Количество действий (2)
Действие $0$ (влево):
- Тележка движется влево.
- Если шест начинает отклоняться вправо, он может вернуться к вертикальному положению.
Действие $1$ (вправо):
- Тележка движется вправо.
- Если шест отклоняется влево, он может вернуться к вертикальному положению.
Награда:
- За каждый шаг агент получает награду $r = 1$, пока шест остается в вертикальном положении.
Завершение эпизода:
- Эпизод завершается, если:
- Угол шеста превышает 15 градусов от вертикали.
- Тележка выходит за пределы экрана ($|x| > 2.4$).
- Эпизод завершается, если:
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 балла)¶
- Какое число состояний в данной задаче?
- Какое число действий?
- Чему равна награда?
- Предложите наивный (какой захотите) метод управления системой и проилюстрируйте его работу.
3. ε-жадная стратегия (ε-Greedy Policy)¶
Идея:
- Использование (Exploitation): С вероятностью $1 - \epsilon$ агент выбирает действие, которое, как он считает, является наилучшим в текущем состоянии.
- Исследование (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 Алгоритм¶
Инициализация:
- Инициализировать Q-функцию $Q(s, a)$ (например, нулями).
- Задать начальное значение $\epsilon$.
Выбор действия:
- В каждом состоянии $s$:
- С вероятностью $1 - \epsilon$ выбрать действие $a = \arg\max_{a'} Q(s, a')$.
- С вероятностью $\epsilon$ выбрать случайное действие $a \sim A$.
- В каждом состоянии $s$:
Обновление Q-функции:
- После выполнения действия $a$ в состоянии $s$ получить награду $r$ и новое состояние $s'$.
- Обновить Q-функцию с помощью правила обновления (например, Q-learning):
- $\alpha$: скорость обучения ($0 < \alpha \leq 1$).
- $\gamma$: коэффициент дисконтирования ($0 \leq \gamma \leq 1$).
Уменьшение ε:
- Уменьшить $\epsilon$ по заранее определенному правилу (например, экспоненциально).
Повторение:
- Повторять шаги 2–4 до завершения обучения.
2.2 Среда Taxi¶
Рассмотрим среду (игру) Taxi.
- Состояния (States):
- Всего 500 возможных состояний ($5 \times 5 \times 5 \times 4$):
- $5 \times 5$: местоположение такси.
- $5$: возможные места подбора/высадки пассажира.
- $4$: текущее состояние пассажира (в такси или нет).
- Всего 500 возможных состояний ($5 \times 5 \times 5 \times 4$):
- Действия (Actions):
- Дискретное множество из 6 действий:
- Движение на север.
- Движение на юг.
- Движение на восток.
- Движение на запад.
- Подобрать пассажира.
- Высадить пассажира.
- Дискретное множество из 6 действий:
- Награды (Rewards):
- +20 за успешную доставку пассажира.
- -1 за каждый временной шаг (чтобы стимулировать быстрое выполнение задачи).
- -10 за неправильные действия (например, попытка подобрать пассажира не в том месте).
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-жадную стратегию для данной задачи:
- Реализуйте choose_action (функцию выбора действия).
- Реализуйте update_q_value (обновление Q-функции по формуле $ Q(s, a) \leftarrow Q(s, a) + \alpha \left[ r + \gamma \max_{a'} Q(s', a') - Q(s, a) \right]. $)
- Реализуйте функцию обучения агента.
- Постройте график зависимости награды от эпизода.
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):
# ВАШ КОД
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
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})$. На каждом шаге обучения случайная мини-батч извлекается из буфера.
Обновление параметров:
На каждом шаге выполняются следующие действия:
Вычисление текущих Q-значений: $$ Q(s, a; \theta) $$
Вычисление целевых Q-значений: $$ y = r + \gamma \max_{a'} Q(s', a'; \theta), \quad \text{если эпизод не завершен} $$ $$ y = r, \quad \text{если эпизод завершен} $$
Минимизация функции потерь: $$ L = \mathbb{E} \left[ (y - Q(s, a; \theta))^2 \right] $$
Обновление параметров сети с помощью градиентного спуска.
Задача 3 (4 балла)¶
Реализуйте DQN для задачи CartPole-v1:
- Реализуйте Qfunction(nn.Module) (класс полносвязной нейронной сети).
- Реализуйте select_action (e-выбор действия)
- Реализуйте метод get_action получение действия
- Реализуйт метод fit обучение нейронной сети.
- Постройте график зависимости награды от эпизода.
- Постройте график зависимости награды от эпизода для различных значений $γ$, batch_size и epsilon_decrease. (для каждого параметра 3 различных значений).
import torch.nn as nn
import torch
import numpy as np
import random
import gym
import matplotlib.pyplot as plt
import numpy as np
print(np.__version__)
# необходимо numpy == 1.23.5
# Q-функция
class Qfunction(nn.Module):
# ВАШ КОД
# 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
# Работа метода
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)