Глава 10. ОБУЧЕНИЕ С ПОДКРЕПЛЕНИЕМ

10.1. Функция вознаграждения

\[ R(s, a) = E[\text{Reward} | s, a] \] (Ожидаемое немедленное вознаграждение за выполнение действия \(a\) в состоянии \(s\))

Объяснение: Функция вознаграждения предоставляет немедленное вознаграждение, полученное после выполнения действия \(a\) в состоянии \(s\), направляя поведение агента.

Реализация (концептуально):


# state - текущее состояние
# action - выполненное действие
# rewards - структура данных, хранящая вознаграждения (например, словарь или массив)
def reward_function(state, action, rewards_lookup):
    """Возвращает вознаграждение за действие в состоянии."""
    # Пример: ищем вознаграждение в таблице или вычисляем его
    # return rewards_lookup.get((state, action), 0) # Пример с таблицей
    # Или может быть сложной функцией
    pass # Зависит от конкретной среды

10.2. Дисконтированное суммарное вознаграждение (Discounted Return)

\[ G_t = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}, \quad 0 \le \gamma < 1 \] (где \(G_t\) - суммарное вознаграждение начиная с шага t, \(\gamma\) - фактор дисконтирования, \(R_{t+k+1}\) - вознаграждение на шаге t+k+1)

Объяснение: Дисконтированное суммарное вознаграждение аккумулирует вознаграждения с течением времени, взвешивая будущие вознаграждения фактором дисконтирования \(\gamma\). Меньшее \(\gamma\) означает большую важность немедленных вознаграждений.

Реализация (вычисление по эпизоду):


import numpy as np

# rewards - список вознаграждений, полученных в эпизоде [R1, R2, ...]
# gamma - фактор дисконтирования
def discounted_return(rewards, gamma):
    """Вычисляет дисконтированное суммарное вознаграждение для всего эпизода (начиная с t=0)."""
    G = 0
    power = 1 # gamma^t
    for r in rewards:
        G += power * r
        power *= gamma
    return G

# Вычисление G_t для каждого шага t (чаще используется в алгоритмах)
def discounted_returns_per_step(rewards, gamma):
    """Вычисляет дисконтированные возвраты G_t для каждого шага t."""
    n = len(rewards)
    returns = np.zeros(n)
    G = 0
    # Идем от конца эпизода к началу
    for t in reversed(range(n)):
        G = rewards[t] + gamma * G
        returns[t] = G
    return returns

# Пример
episode_rewards = [1, 0, -1, 2]
discount_factor = 0.9
returns_g = discounted_returns_per_step(episode_rewards, discount_factor)
print(f"Episode rewards: {episode_rewards}")
print(f"Discounted returns (G_t): {returns_g}")
# G_3 = 2
# G_2 = -1 + 0.9 * 2 = 0.8
# G_1 = 0 + 0.9 * 0.8 = 0.72
# G_0 = 1 + 0.9 * 0.72 = 1.648

10.3. Уравнение Беллмана (для функции ценности состояния / State-Value Function)

\[ V^{\pi}(s) = E_{\pi}[R_{t+1} + \gamma V^{\pi}(S_{t+1}) | S_t = s] \] \[ = \sum_{a} \pi(a|s) \sum_{s', r} p(s', r | s, a) [r + \gamma V^{\pi}(s')] \] (где \(V^{\pi}(s)\) - ценность состояния s при следовании политике \(\pi\), \(\pi(a|s)\) - вероятность выбора действия a в состоянии s, \(p(s', r | s, a)\) - динамика среды)

Объяснение: Уравнение Беллмана связывает ценность состояния с ожидаемым немедленным вознаграждением и дисконтированной ценностью следующего состояния при следовании определенной политике \(\pi\).

Реализация (для оценки при известной модели):


import numpy as np

# V - текущая оценка ценности состояний (массив)
# s - индекс текущего состояния
# policy - политика pi(a|s) (например, матрица [n_states, n_actions])
# transition_prob - p(s'|s,a) (матрица [n_states, n_actions, n_states])
# rewards - r(s,a,s') или ожидаемое R(s,a) (массив)
# gamma - фактор дисконтирования
def bellman_state_value_update(V, s, policy, transition_prob, rewards, gamma):
    """Вычисляет ожидаемое значение V(s) по уравнению Беллмана."""
    expected_value = 0
    num_actions = policy.shape[1]
    for a in range(num_actions):
        action_prob = policy[s, a]
        # Ожидание по следующим состояниям и вознаграждениям
        # (Упрощенный пример, где reward зависит только от (s,a))
        next_state_values = 0
        for next_s in range(len(V)):
             # Здесь предполагается, что rewards[s,a] - это E[R | s,a]
             # и transition_prob[s, a, next_s] = p(next_s | s, a)
             reward_sa = rewards[s, a] # Упрощение
             next_state_values += transition_prob[s, a, next_s] * (reward_sa + gamma * V[next_s])

        expected_value += action_prob * next_state_values
    return expected_value

# В итеративной оценке политики (Policy Evaluation):
# new_V[s] = bellman_state_value_update(V, s, ...)

10.4. Уравнение Беллмана (для функции ценности действия / Action-Value Function)

\[ Q^{\pi}(s, a) = E_{\pi}[R_{t+1} + \gamma Q^{\pi}(S_{t+1}, A_{t+1}) | S_t = s, A_t = a] \] \[ = \sum_{s', r} p(s', r | s, a) [r + \gamma \sum_{a'} \pi(a'|s') Q^{\pi}(s', a')] \] (где \(Q^{\pi}(s, a)\) - ценность выполнения действия a в состоянии s при следовании политике \(\pi\))

Объяснение: Уравнение Беллмана для функции ценности действия выражает ценность выполнения действия \(a\) в состоянии \(s\) через ожидаемое немедленное вознаграждение и дисконтированную ожидаемую ценность следующей пары состояние-действие.

Реализация (для оценки при известной модели):


import numpy as np

# Q - текущая оценка Q-функции (матрица [n_states, n_actions])
# s, a - текущие состояние и действие
# policy - политика pi(a|s)
# transition_prob - p(s'|s,a)
# rewards - r(s,a,s') или R(s,a)
# gamma - фактор дисконтирования
def bellman_action_value_update(Q, s, a, policy, transition_prob, rewards, gamma):
    """Вычисляет ожидаемое значение Q(s,a) по уравнению Беллмана."""
    expected_value_for_sa = 0
    num_states = Q.shape[0]
    num_actions = Q.shape[1]

    # Суммируем по всем возможным следующим состояниям s'
    for next_s in range(num_states):
        # Ожидаемое значение Q в следующем состоянии s'
        expected_next_Q = 0
        for next_a in range(num_actions):
            expected_next_Q += policy[next_s, next_a] * Q[next_s, next_a]

        # Добавляем вклад от перехода в next_s
        # Упрощаем: reward = R(s,a)
        reward_sa = rewards[s, a]
        prob_next_s = transition_prob[s, a, next_s]
        expected_value_for_sa += prob_next_s * (reward_sa + gamma * expected_next_Q)

    return expected_value_for_sa

# В итеративной оценке политики для Q:
# new_Q[s, a] = bellman_action_value_update(Q, s, a, ...)

10.5. Обновление временных разностей (TD Update)

\[ V(S_t) \leftarrow V(S_t) + \alpha [R_{t+1} + \gamma V(S_{t+1}) - V(S_t)] \] (TD(0) обновление. \(\alpha\) - скорость обучения)

Объяснение: TD обновление улучшает оценку ценности состояния, используя разницу (TD ошибку) между текущей оценкой \(V(S_t)\) и более точной оценкой ("TD target": \(R_{t+1} + \gamma V(S_{t+1})\)), полученной на следующем шаге.

Реализация:


# V - массив ценностей состояний
# state - текущее состояние S_t
# reward - полученное вознаграждение R_{t+1}
# next_state - следующее состояние S_{t+1}
# alpha - скорость обучения
# gamma - фактор дисконтирования
def td_update(V, state, reward, next_state, alpha, gamma):
    """Выполняет TD(0) обновление для V(state)."""
    td_target = reward + gamma * V[next_state]
    td_error = td_target - V[state]
    V[state] += alpha * td_error
    # Возвращаем V или обновляем на месте
    # return V # Если V не изменяется на месте

10.6. Оценка политики методом Монте-Карло

\[ V(s) \leftarrow \text{Среднее}(G_t \text{ для всех эпизодов, где } S_t = s) \] (Оценка ценности состояния как среднее суммарных вознаграждений, полученных после первого посещения этого состояния в разных эпизодах)

Объяснение: Оценка методом Монте-Карло обновляет ценность состояния путем усреднения фактических суммарных вознаграждений (returns) по многим эпизодам, начинающимся (или проходящим через) это состояние.

Реализация (First-visit MC):


import numpy as np
from collections import defaultdict

# V - словарь или массив ценностей состояний
# state_returns - словарь {состояние: [список G_t для этого состояния]}
# state_counts - словарь {состояние: количество посещений} (для усреднения)
def monte_carlo_policy_evaluation_update(V, state_returns):
    """Обновляет ценности состояний на основе возвратов Монте-Карло."""
    for state, returns in state_returns.items():
        if returns: # Если были возвраты для этого состояния
            V[state] = np.mean(returns)
    # return V # Возвращаем обновленный V

# В цикле обучения:
# episode = generate_episode(policy) # [(s0,a0,r1), (s1,a1,r2), ...]
# Gt_list = discounted_returns_per_step(rewards_in_episode, gamma)
# visited_states = set()
# for t, (state, _, _) in enumerate(episode):
#     if state not in visited_states:
#         state_returns[state].append(Gt_list[t])
#         visited_states.add(state)
# V = monte_carlo_policy_evaluation_update(V, state_returns)

10.7. Улучшение политики (Policy Improvement)

\[ \pi'(s) = \arg \max_{a} Q(s, a) \] (Новая политика \(\pi'\) становится "жадной" по отношению к текущей оценке Q-функции)

Объяснение: Улучшение политики обновляет политику, выбирая для каждого состояния действие, которое максимизирует текущую оценку функции ценности действия \(Q(s, a)\).

Реализация (для детерминированной политики):


import numpy as np

# Q - матрица Q-значений (n_states, n_actions)
def policy_improvement(Q):
    """Улучшает политику, делая ее жадной по отношению к Q."""
    # np.argmax возвращает индекс максимального элемента по оси
    new_policy_deterministic = np.argmax(Q, axis=1) # Для каждого состояния выбираем лучшее действие
    return new_policy_deterministic

# Пример
# Q_table = np.array([[1, 2], [4, 3]]) # 2 состояния, 2 действия
# new_policy = policy_improvement(Q_table)
# print(new_policy) # [1, 0] (В сост. 0 выбираем действ. 1, в сост. 1 - действ. 0)

10.8. Обновление Q-обучения (Q-Learning Update)

\[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha [R_{t+1} + \gamma \max_{a} Q(S_{t+1}, a) - Q(S_t, A_t)] \]

Объяснение: Q-обучение — это алгоритм без модели (off-policy), который обновляет оценки ценности действия, используя максимальное Q-значение для следующего состояния (независимо от фактически выбранного действия).

Реализация:


import numpy as np

# Q - матрица Q-значений
# state, action - текущие S_t, A_t
# reward - R_{t+1}
# next_state - S_{t+1}
# alpha - скорость обучения
# gamma - фактор дисконтирования
def q_learning_update(Q, state, action, reward, next_state, alpha, gamma):
    """Выполняет обновление Q-обучения."""
    # Находим максимальное Q-значение для следующего состояния
    best_next_q = np.max(Q[next_state])
    # Вычисляем TD target
    td_target = reward + gamma * best_next_q
    # Вычисляем TD ошибку
    td_error = td_target - Q[state, action]
    # Обновляем Q-значение
    Q[state, action] += alpha * td_error
    # Обновляем Q на месте или возвращаем

10.9. Обновление SARSA

\[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha [R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t)] \]

Объяснение: SARSA — это алгоритм с моделью (on-policy), который обновляет Q-значения на основе кортежа (Состояние, Действие, Вознаграждение, Следующее Состояние, Следующее Действие), т.е., учитывая действие \(A_{t+1}\), фактически выбранное в следующем состоянии согласно текущей политике.

Реализация:


import numpy as np

# Q - матрица Q-значений
# state, action - текущие S_t, A_t
# reward - R_{t+1}
# next_state - S_{t+1}
# next_action - действие A_{t+1}, выбранное в S_{t+1}
# alpha - скорость обучения
# gamma - фактор дисконтирования
def sarsa_update(Q, state, action, reward, next_state, next_action, alpha, gamma):
    """Выполняет обновление SARSA."""
    # Q-значение для следующей пары состояние-действие
    next_q = Q[next_state, next_action]
    # Вычисляем TD target
    td_target = reward + gamma * next_q
    # Вычисляем TD ошибку
    td_error = td_target - Q[state, action]
    # Обновляем Q-значение
    Q[state, action] += alpha * td_error

10.10. Обновление итерации по ценности (Value Iteration Update)

\[ V_{k+1}(s) \leftarrow \max_{a} \sum_{s', r} p(s', r | s, a) [r + \gamma V_k(s')] \] (Обновляем ценность состояния \(V(s)\), выбирая действие \(a\), которое максимизирует ожидаемую будущую ценность)

Объяснение: Итерация по ценности итеративно обновляет ценности состояний, находя оптимальное действие (максимизирующее правую часть уравнения Беллмана) на каждом шаге, пока ценности не сойдутся к оптимальным \(V^*\).

Реализация (один шаг обновления для всех состояний):


import numpy as np

# V - массив текущих ценностей состояний
# rewards - R(s,a) или r(s,a,s')
# transition_prob - p(s'|s,a)
# gamma - фактор дисконтирования
# num_actions - количество действий
def value_iteration_update_step(V, rewards, transition_prob, gamma, num_actions):
    """Выполняет один шаг итерации по ценности для всех состояний."""
    num_states = len(V)
    new_V = np.zeros(num_states)
    for s in range(num_states):
        action_values = np.zeros(num_actions)
        for a in range(num_actions):
            # Вычисляем ожидаемое значение для действия a
            expected_val_sa = 0
            for next_s in range(num_states):
                 # Упрощение: reward = R(s,a)
                 reward_sa = rewards[s, a]
                 prob = transition_prob[s, a, next_s]
                 expected_val_sa += prob * (reward_sa + gamma * V[next_s])
            action_values[a] = expected_val_sa
        # Выбираем максимум по действиям
        new_V[s] = np.max(action_values)
    return new_V

# Цикл итерации по ценности:
# V = np.zeros(num_states)
# while True:
#    delta = 0
#    V_old = V.copy()
#    V = value_iteration_update_step(V_old, ...)
#    delta = max(delta, np.max(np.abs(V - V_old)))
#    if delta < threshold:
#        break

10.11. Обновление политики Актора-Критика (Actor-Critic Policy Update)

\[ \text{Параметры Актора: } \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} + \alpha_{\theta} \nabla_{\boldsymbol{\theta}} \log \pi_{\boldsymbol{\theta}}(a_t | s_t) \delta_t \] \[ \text{Параметры Критика (оценка V): } \mathbf{w} \leftarrow \mathbf{w} + \alpha_{w} \delta_t \nabla_{\mathbf{w}} V_{\mathbf{w}}(s_t) \] \[ \text{TD Ошибка (Преимущество): } \delta_t = R_{t+1} + \gamma V_{\mathbf{w}}(s_{t+1}) - V_{\mathbf{w}}(s_t) \]

Объяснение: Актор (политика) обновляет свою стратегию \(\pi_{\theta}\), используя оценку преимущества (TD ошибку \(\delta_t\)), предоставленную Критиком. Критик (оценка ценности \(V_w\)) обновляет свою оценку ценности состояния, чтобы лучше предсказывать будущие вознаграждения и давать более точную оценку преимущества.

Реализация (концептуально):


# actor - модель политики (например, нейросеть)
# critic - модель ценности состояния (например, нейросеть)
# state, action, reward, next_state - данные из среды
# alpha_actor, alpha_critic - скорости обучения
# gamma - фактор дисконтирования
def actor_critic_update(actor, critic, state, action, reward, next_state, alpha_actor, alpha_critic, gamma):
    """Выполняет шаг обновления для Актора-Критика."""

    # 1. Вычисляем TD ошибку (преимущество) с помощью Критика
    v_current = critic.predict(state) # V(st)
    v_next = critic.predict(next_state) # V(st+1)
    td_target = reward + gamma * v_next
    td_error_advantage = td_target - v_current # delta_t

    # 2. Обновляем Критика (минимизируем ошибку предсказания ценности)
    # grad_critic = td_error_advantage * critic.gradient(state) # Градиент (V(st) - target)^2 по w
    # critic.update_weights(alpha_critic * grad_critic) # Упрощенно
    critic.train_on_batch(state, td_target) # Обычно так в библиотеках

    # 3. Обновляем Актора (увеличиваем вероятность выгодных действий)
    # grad_actor = actor.policy_gradient(state, action) # grad log pi(a|s; theta)
    # actor.update_weights(alpha_actor * td_error_advantage * grad_actor) # Обновление политики
    actor.train_on_batch(state, action, sample_weight=td_error_advantage) # Упрощенно

10.12. Детерминированный градиент политики (Deterministic Policy Gradient - DPG)

\[ \nabla_{\boldsymbol{\theta}} J(\pi_{\boldsymbol{\theta}}) = E_{s \sim \rho^{\beta}} [\nabla_{a} Q^{\mu}(s, a)|_{a=\pi_{\boldsymbol{\theta}}(s)} \nabla_{\boldsymbol{\theta}} \pi_{\boldsymbol{\theta}}(s)] \] (где \(J\) - ожидаемое вознаграждение, \(\pi_{\theta}\) - детерминированная политика Актора, \(Q^{\mu}\) - Q-функция Критика, \(\rho^{\beta}\) - распределение состояний при некоторой политике исследования \(\beta\))

Объяснение: Детерминированные градиенты политики обновляют политику напрямую в непрерывном пространстве действий, используя градиенты Q-функции (Критика) по действиям.

Реализация (концептуальное обновление Актора):


# actor - детерминированная политика pi(s) -> a
# critic - Q-функция Q(s,a)
# state - текущее состояние
# alpha_actor - скорость обучения актора
def ddpg_actor_update(actor, critic, state, alpha_actor):
    """Обновление актора в DDPG."""
    # 1. Получаем действие от Актора
    action = actor.predict(state)
    # 2. Вычисляем градиент Q-функции Критика по действию при данном состоянии и действии
    grad_q_a = critic.action_gradient(state, action)
    # 3. Вычисляем градиент политики Актора по параметрам theta
    grad_pi_theta = actor.policy_gradient(state)
    # 4. Обновляем параметры Актора (градиентный подъем по J)
    # grad_J_theta ~ grad_q_a * grad_pi_theta (упрощенно, может потребоваться цепное правило)
    actor.update_weights(alpha_actor * grad_q_a * grad_pi_theta) # Примерное обновление

10.13. Фактор дисконтирования (\(\gamma\))

\[ G_t = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}, \quad 0 \le \gamma < 1 \]

Объяснение: Фактор дисконтирования \(\gamma\) определяет вес, придаваемый будущим вознаграждениям. Меньшее \(\gamma\) (ближе к 0) делает агента "близоруким", приоритезируя немедленные вознаграждения, тогда как большее \(\gamma\) (ближе к 1) заставляет агента учитывать долгосрочные вознаграждения.

Реализация: Фактор дисконтирования является гиперпараметром алгоритма.


gamma = 0.99 # Типичное значение для многих задач
# Используется в вычислениях TD target, возвратов и т.д. (см. предыдущие примеры)

10.14. Ожидаемый SARSA (Expected SARSA)

\[ Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha [R_{t+1} + \gamma E_{A' \sim \pi(S_{t+1})} [Q(S_{t+1}, A')] - Q(S_t, A_t)] \] \[ E_{A' \sim \pi(S_{t+1})} [Q(S_{t+1}, A')] = \sum_{a'} \pi(a'|S_{t+1}) Q(S_{t+1}, a') \]

Объяснение: Ожидаемый SARSA обновляет Q-значения, используя ожидаемое значение Q-функции для следующего состояния, усредненное по всем возможным действиям согласно текущей политике \(\pi\). Это уменьшает дисперсию по сравнению со стандартным SARSA.

Реализация:


import numpy as np

# Q - матрица Q-значений
# state, action - текущие S_t, A_t
# reward - R_{t+1}
# next_state - S_{t+1}
# policy - текущая политика pi(a|s) (матрица [n_states, n_actions])
# alpha - скорость обучения
# gamma - фактор дисконтирования
def expected_sarsa_update(Q, state, action, reward, next_state, policy, alpha, gamma):
    """Выполняет обновление Ожидаемого SARSA."""
    # Вычисляем ожидаемое Q-значение для следующего состояния
    expected_next_q = np.sum(policy[next_state] * Q[next_state])
    # td_target = reward + gamma * np.dot(policy[next_state], Q[next_state]) # Эквивалентно

    # Вычисляем TD target
    td_target = reward + gamma * expected_next_q
    # Вычисляем TD ошибку
    td_error = td_target - Q[state, action]
    # Обновляем Q-значение
    Q[state, action] += alpha * td_error

10.15. Обновление с трассами поощрения (Eligibility Traces Update - TD(\(\lambda\)))

\[ \delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t) \quad (\text{TD ошибка}) \] \[ e_t(s) = \gamma \lambda e_{t-1}(s) + \mathbb{I}(S_t = s) \quad (\text{Накапливающаяся трасса}) \] \[ \text{или } e_t(s) = \gamma \lambda e_{t-1}(s) + \nabla_{\mathbf{w}} V_{\mathbf{w}}(S_t) \quad (\text{для аппроксимации}) \] \[ \mathbf{w} \leftarrow \mathbf{w} + \alpha \delta_t \mathbf{e}_t \quad (\text{Обновление параметров}) \] (где \(\mathbf{e}_t\) - вектор трасс поощрения, \(\lambda \in [0, 1]\) - параметр затухания трассы)

Объяснение: TD(\(\lambda\)) объединяет методы TD и Монте-Карло с использованием трасс поощрения, которые отслеживают недавние состояния/действия. Обновление влияет не только на текущее состояние, но и на недавние состояния пропорционально их трассе.

Реализация (для табличного V и накапливающейся трассы):


import numpy as np

# V - массив ценностей состояний
# eligibility - массив трасс поощрения (того же размера, что и V)
# state - текущее состояние S_t
# reward - R_{t+1}
# next_state - S_{t+1}
# alpha - скорость обучения
# gamma - фактор дисконтирования
# lambda_trace - параметр затухания трассы
def td_lambda_update(V, eligibility, state, reward, next_state, alpha, gamma, lambda_trace):
    """Выполняет обновление TD(lambda) для табличного V."""

    # Вычисляем TD ошибку
    td_target = reward + gamma * V[next_state]
    td_error = td_target - V[state] # delta_t

    # Обновляем трассу для текущего состояния (накапливающаяся)
    eligibility[state] += 1
    # Или заменяющая трасса: eligibility[state] = 1

    # Обновляем ценности для всех состояний
    V += alpha * td_error * eligibility

    # Затухание трасс
    eligibility *= gamma * lambda_trace

    # Возвращаем V, eligibility или обновляем на месте

10.16. TD Ошибка

\[ \delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t) \quad (\text{для V-функции}) \] \[ \delta_t = R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t) \quad (\text{для Q-функции, SARSA}) \] \[ \delta_t = R_{t+1} + \gamma \max_{a} Q(S_{t+1}, a) - Q(S_t, A_t) \quad (\text{для Q-функции, Q-Learning}) \]

Объяснение: TD ошибка (Temporal Difference Error) измеряет разницу между текущей оценкой ценности (V или Q) и более точной оценкой (TD target), полученной на основе следующего шага. Она направляет обновления в обучении с временными разностями.

Реализация (для V-функции):


# V - массив ценностей состояний
# state - S_t
# reward - R_{t+1}
# next_state - S_{t+1}
# gamma - фактор дисконтирования
def td_error_v(V, state, reward, next_state, gamma):
    """Вычисляет TD ошибку для V-функции."""
    td_target = reward + gamma * V[next_state]
    td_error = td_target - V[state]
    return td_error

10.17. Стохастический градиентный спуск в RL

\[ \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \alpha \nabla_{\boldsymbol{\theta}} \mathcal{L}(\boldsymbol{\theta}) \] (где \(\mathcal{L}(\theta)\) - функция потерь, например, MSE для аппроксимации ценности: \(\mathcal{L} = (V_{\text{target}} - V_{\boldsymbol{\theta}}(s))^2\), или потери политики для policy gradient)

Объяснение: Стохастический градиентный спуск обновляет параметры модели (например, нейронной сети, аппроксимирующей V, Q или \(\pi\)) путем минимизации функции потерь, используя градиенты, вычисленные по одному переходу или батчу.

Реализация (общий шаг):


# theta - текущие параметры аппроксиматора
# grad - градиент функции потерь по theta
# alpha - скорость обучения
def sgd_update(theta, grad, alpha):
    """Один шаг SGD обновления."""
    return theta - alpha * grad

# Пример для аппроксимации V-функции
# target_v = reward + gamma * critic.predict(next_state) # TD Target
# current_v = critic.predict(state)
# loss_grad = (current_v - target_v) * critic.gradient(state) # Градиент 0.5*(V(s)-target)^2
# new_weights = sgd_update(critic.weights, loss_grad, alpha_critic)

10.18. Двойное Q-обучение (Double Q-Learning)

\[ Q_A(S_t, A_t) \leftarrow Q_A(S_t, A_t) + \alpha [R_{t+1} + \gamma Q_B(S_{t+1}, \arg \max_{a'} Q_A(S_{t+1}, a')) - Q_A(S_t, A_t)] \] (С вероятностью 0.5 обновляется \(Q_A\), используя \(Q_B\) для оценки ценности следующего действия, выбранного по \(Q_A\). И наоборот.)

Объяснение: Двойное Q-обучение уменьшает проблему переоценки Q-значений (которая есть в стандартном Q-обучении) путем использования двух Q-функций: одна выбирает лучшее действие, а другая оценивает его ценность.

Реализация (один шаг обновления, обновление Q_A):


import numpy as np

# Q_A, Q_B - две матрицы Q-значений
# state, action, reward, next_state - данные перехода
# alpha, gamma - параметры
def double_q_learning_update_A(Q_A, Q_B, state, action, reward, next_state, alpha, gamma):
    """Обновление Q_A в Double Q-Learning."""
    # 1. Выбираем лучшее действие в next_state по Q_A
    best_action_next_state = np.argmax(Q_A[next_state])
    # 2. Оцениваем ценность этого действия с помощью Q_B
    q_value_from_B = Q_B[next_state, best_action_next_state]
    # 3. Вычисляем TD target
    td_target = reward + gamma * q_value_from_B
    # 4. Обновляем Q_A
    td_error = td_target - Q_A[state, action]
    Q_A[state, action] += alpha * td_error

# В цикле обучения:
# if np.random.rand() < 0.5:
#     double_q_learning_update_A(Q_A, Q_B, ...)
# else:
#     double_q_learning_update_B(Q_B, Q_A, ...) # Симметричное обновление для Q_B

10.19. Advantage Actor-Critic (A2C)

\[ \text{Преимущество (Advantage): } A(s_t, a_t) = Q(s_t, a_t) - V(s_t) \] \[ \text{Оценка преимущества (TD ошибка): } \hat{A}_t = \delta_t = R_{t+1} + \gamma V(s_{t+1}) - V(s_t) \] \[ \text{Обновление Актора: } \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} + \alpha_{\theta} \nabla_{\boldsymbol{\theta}} \log \pi_{\boldsymbol{\theta}}(a_t | s_t) \hat{A}_t \] \[ \text{Обновление Критика (минимизация MSE для V): } \mathbf{w} \leftarrow \mathbf{w} - \alpha_{w} \hat{A}_t \nabla_{\mathbf{w}} V_{\mathbf{w}}(s_t) \]

Объяснение: A2C (и его синхронный вариант A3C) использует функцию преимущества (Advantage function), часто оцениваемую как TD ошибка, для уменьшения дисперсии в обновлениях градиента политики (Актора), в то время как Критик учится оценивать функцию ценности состояния V.

Реализация: Похожа на базовый Actor-Critic (10.11), но градиент политики взвешивается на оценку преимущества \(\hat{A}_t\) (TD ошибку), и Критик также обучается минимизировать эту ошибку.

10.20. Оценка вне политики (Off-Policy Evaluation) с использованием выборки по значимости (Importance Sampling)

\[ V^{\pi}(s) \approx \frac{\sum_{t \in \mathcal{T}(s)} \rho_{t:T(t)-1} G_t}{\sum_{t \in \mathcal{T}(s)} \rho_{t:T(t)-1}} \quad (\text{Взвешенная выборка по значимости}) \] \[ \text{Отношение значимости: } \rho_{t:\tau} = \prod_{k=t}^{\tau} \frac{\pi(A_k | S_k)}{b(A_k | S_k)} \] (где \(\pi\) - целевая политика, \(b\) - поведенческая политика (генерирующая данные), \(G_t\) - возврат с шага t, \(\mathcal{T}(s)\) - множество шагов времени, где \(S_t=s\))

Объяснение: Выборка по значимости корректирует расхождения между поведенческой политикой \(\mu\) (которая генерирует данные) и целевой политикой \(\pi\) (которую мы хотим оценить) при оценке возвратов/ценностей.

Реализация (вычисление взвешенного возврата):


import numpy as np

# importance_weights - массив отношений значимости rho для каждого эпизода/возврата
# returns - массив соответствующих возвратов G
def importance_sampling_evaluation(importance_weights, returns):
    """Оценивает средний возврат с помощью (простой) выборки по значимости."""
    # Простая оценка (может иметь высокую дисперсию)
    weighted_returns = importance_weights * returns
    return np.mean(weighted_returns)

def weighted_importance_sampling_evaluation(importance_weights, returns):
    """Оценивает средний возврат с помощью взвешенной выборки по значимости."""
    weighted_returns_sum = np.sum(importance_weights * returns)
    sum_of_weights = np.sum(importance_weights)
    if sum_of_weights == 0:
        return 0.0 # Или NaN
    return weighted_returns_sum / sum_of_weights

# Пример
# weights = np.array([0.5, 1.5, 1.0]) # Пример отношений rho
# episode_returns = np.array([10, 20, 15]) # Пример возвратов G
# simple_is_est = importance_sampling_evaluation(weights, episode_returns)
# weighted_is_est = weighted_importance_sampling_evaluation(weights, episode_returns)
# print(f"Simple IS estimate: {simple_is_est}")   # mean(0.5*10 + 1.5*20 + 1.0*15) = mean(5 + 30 + 15) = mean(50) = 50/3
# print(f"Weighted IS estimate: {weighted_is_est}") # (5+30+15) / (0.5+1.5+1.0) = 50 / 3.0

10.21. Правило обновления градиента политики

\[ \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} + \alpha \nabla_{\boldsymbol{\theta}} E_{\pi_{\boldsymbol{\theta}}} [G_t] \] \[ \nabla_{\boldsymbol{\theta}} E_{\pi_{\boldsymbol{\theta}}} [G_t] = E_{\pi_{\boldsymbol{\theta}}} [G_t \nabla_{\boldsymbol{\theta}} \log \pi_{\boldsymbol{\theta}}(A_t | S_t)] \quad (\text{Теорема о градиенте политики, REINFORCE}) \]

Объяснение: Алгоритм градиента политики (например, REINFORCE) обновляет параметры \(\boldsymbol{\theta}\) политики \(\pi_{\boldsymbol{\theta}}\) в направлении увеличения ожидаемого суммарного вознаграждения \(G_t\). Обновление взвешивается на полученный возврат \(G_t\).

Реализация (шаг обновления REINFORCE):


# policy_model - модель политики, возвращающая log_prob и градиент log_prob
# optimizer - оптимизатор (например, Adam)
# rewards - список вознаграждений за эпизод
# log_probs - список log pi(a|s) за эпизод
# grads_log_probs - список градиентов log pi(a|s) за эпизод
# gamma - фактор дисконтирования
# alpha - скорость обучения (управляется оптимизатором)
def reinforce_update_step(policy_model, optimizer, rewards, log_probs, grads_log_probs, gamma):
    """Один шаг обновления параметров политики в REINFORCE."""
    # 1. Вычисляем дисконтированные возвраты G_t для каждого шага
    returns = discounted_returns_per_step(rewards, gamma)
    # Опционально: нормализуем возвраты для стабильности
    returns = (returns - np.mean(returns)) / (np.std(returns) + 1e-8)

    # 2. Вычисляем потери (отрицание цели)
    policy_loss = []
    for log_prob, G_t, grad_log_prob in zip(log_probs, returns, grads_log_probs):
        # Потери = - G_t * log pi(a|s)
        # Градиент потерь = - G_t * grad log pi(a|s)
        policy_loss.append(-G_t * log_prob) # Сохраняем для информации
        # Обновление параметров: theta += alpha * G_t * grad_log_prob
        # Обычно это делается через фреймворк глубокого обучения:
        # loss = -G_t * log_prob
        # loss.backward() -> вычисляет градиенты dLoss/dTheta = -G_t * grad_log_prob

    # 3. Выполняем шаг оптимизатора (применяет вычисленные градиенты)
    # optimizer.zero_grad()
    # total_loss = torch.stack(policy_loss).sum() # Пример для PyTorch
    # total_loss.backward()
    # optimizer.step()
    pass # Реализация зависит от фреймворка

10.22. Цель мягкого Q-обучения (Soft Q-Learning Objective)

\[ \text{Минимизировать (потери): } \mathcal{L} = E_{(s, a) \sim D} \left[ (Q(s, a) - (r + \gamma (V_{\text{soft}}(s'))))^2 \right] \] \[ \text{где } V_{\text{soft}}(s) = \alpha \log \int \exp(Q(s, a')/\alpha) da' \quad (\text{или } E_{a' \sim \pi}[Q(s,a') - \alpha \log \pi(a'|s)]) \] (Цель - выучить Q-функцию, которая соответствует оптимальной политике, максимизирующей вознаграждение плюс энтропию.)

Объяснение: Мягкое Q-обучение (часть Soft Actor-Critic) оптимизирует политику, балансируя максимизацию вознаграждения и максимизацию энтропии политики (что поощряет исследование). Критик учит Q-функцию для этой "мягкой" оптимальной политики.

Реализация (обновление Q-функции / Критика в SAC):


# Q_network - нейросеть Q-функции
# target_Q_network - целевая сеть Q (для стабильности)
# policy_network - нейросеть политики (Актор)
# state, action, reward, next_state, done - данные перехода
# alpha_entropy - коэффициент энтропии (может быть обучаемым)
# gamma - фактор дисконтирования
# optimizer_Q - оптимизатор для Q-сети
def sac_q_update(Q_network, target_Q_network, policy_network, state, action, reward, next_state, done, alpha_entropy, gamma, optimizer_Q):
    """Обновление Q-функции в Soft Actor-Critic."""
    with torch.no_grad(): # Не вычисляем градиенты для целевых значений
        # 1. Получаем следующее действие и его log-вероятность от текущей политики
        next_action, log_prob_next_action = policy_network.sample(next_state) # Пример для стохаст. политики
        # 2. Вычисляем целевое Q-значение с помощью target Q-сети
        target_q_value = target_Q_network(next_state, next_action)
        # 3. Вычисляем целевое V-значение (мягкое)
        target_v_soft = target_q_value - alpha_entropy * log_prob_next_action
        # 4. Вычисляем TD target
        td_target = reward + gamma * (1 - done) * target_v_soft # Учитываем конец эпизода (done)

    # 5. Вычисляем текущее Q-значение
    current_q_value = Q_network(state, action)

    # 6. Вычисляем потери Q-функции (MSE)
    q_loss = F.mse_loss(current_q_value, td_target) # Пример для PyTorch

    # 7. Оптимизируем Q-сеть
    # optimizer_Q.zero_grad()
    # q_loss.backward()
    # optimizer_Q.step()
    pass # Реализация зависит от фреймворка

10.23. RL с регуляризацией энтропией

\[ \text{Цель политики: } \pi^* = \arg \max_{\pi} E_{\tau \sim \pi} \left[ \sum_t \gamma^t (R_t + \alpha H(\pi(\cdot|S_t))) \right] \] (где \(H(\pi(\cdot|S_t)) = - \sum_a \pi(a|S_t) \log \pi(a|S_t)\) - энтропия политики в состоянии \(S_t\), \(\alpha\) - коэффициент температуры/энтропии)

Объяснение: Регуляризация энтропией поощряет исследование (exploration) путем добавления энтропии политики к стандартной цели максимизации вознаграждения.

Реализация (в градиенте политики): Градиент политики модифицируется, чтобы включать градиент по члену энтропии. В SAC (см. 10.22, 10.24) это делается неявно через оптимизацию мягкой Q-функции.

10.24. Soft Actor-Critic (SAC)

Цели (упрощенно):

  • Критик (Q-функция): Минимизировать ошибку Беллмана для мягкой V-функции (см. 10.22).
  • Актор (Политика \(\pi_{\theta}\)): Максимизировать \(E_{s \sim D, a \sim \pi}[Q(s, a) - \alpha \log \pi_{\theta}(a|s)]\).
  • Температура \(\alpha\): (Опционально) Адаптивно настраивать \(\alpha\), чтобы поддерживать целевой уровень энтропии политики.

Объяснение: SAC сочетает регуляризацию энтропией с архитектурой актор-критик для достижения стабильного и эффективного обучения в непрерывных пространствах управления, хорошо балансируя исследование и эксплуатацию.

Реализация: Включает обновление Q-сетей (часто двух для стабильности, как в TD3), обновление политики (Актора) и опциональное обновление коэффициента энтропии \(\alpha\). См. пример обновления Q-функции в 10.22.

10.25. Trust Region Policy Optimization (TRPO)

\[ \max_{\boldsymbol{\theta}} E_{s \sim \pi_{\theta_{\text{old}}}, a \sim \pi_{\theta_{\text{old}}}} \left[ \frac{\pi_{\boldsymbol{\theta}}(a | s)}{\pi_{\boldsymbol{\theta}_{\text{old}}}(a | s)} \hat{A}_{\pi_{\theta_{\text{old}}}}(s, a) \right] \] \[ \text{при условии } E_{s \sim \pi_{\theta_{\text{old}}}} [D_{\text{KL}}(\pi_{\boldsymbol{\theta}_{\text{old}}}(\cdot | s) || \pi_{\boldsymbol{\theta}}(\cdot | s))] \le \delta \] (Максимизация суррогатной цели (ожидаемого преимущества с поправкой на значимость) при ограничении на изменение политики, измеряемое KL-дивергенцией)

Объяснение: TRPO обеспечивает монотонное улучшение политики, максимизируя суррогатную целевую функцию при ограничении на изменение политики (измеряемое KL-дивергенцией), чтобы избежать слишком больших и деструктивных обновлений.

Реализация: Требует решения оптимизационной задачи с ограничениями, часто с использованием метода сопряженных градиентов для приближенного вычисления произведения Гессиана KL-дивергенции на вектор.

Предыдущая глава

Другие статьи по этой теме: