Обучение с подкреплением на Python: от теории к созданию умных алгоритмов

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Студенты и начинающие разработчики, интересующиеся машинным обучением и Python
  • Профессионалы в области искусственного интеллекта, желающие освоить обучение с подкреплением
  • Исследователи и практики, работающие над проектами в области оптимизации и автоматизации процессов

    Представьте, что ваш код может учиться сам — не просто следовать инструкциям, а принимать решения и совершенствоваться через опыт. Обучение с подкреплением (Reinforcement Learning) — это именно тот подход, который превращает алгоритмы из простых исполнителей в самообучающиеся системы. От игровых ботов, обыгрывающих чемпионов мира, до алгоритмов управления беспилотными автомобилями — всё это построено на принципах RL. В этой статье я покажу, как реализовать такие алгоритмы на Python — от простейших моделей до продвинутых нейросетей, с рабочим кодом, который вы сможете адаптировать под свои задачи. 🚀

Освоить обучение с подкреплением на Python значительно проще с правильной базой. На курсе Обучение Python-разработке от Skypro вы получите фундаментальные навыки программирования и работы с библиотеками данных, необходимые для успешного внедрения алгоритмов RL. Студенты осваивают не только синтаксис, но и архитектурные паттерны, которые критически важны при создании сложных самообучающихся систем. Инвестируйте в фундамент, на котором будет строиться ваша карьера в AI.

Что такое обучение с подкреплением и как оно работает в Python

Обучение с подкреплением (Reinforcement Learning, RL) — это область машинного обучения, где алгоритм (агент) учится выполнять действия в определённой среде, максимизируя накопленную награду. В отличие от обучения с учителем, здесь нет готовых правильных ответов — агент должен сам исследовать среду и находить оптимальные стратегии действий.

Базовая модель обучения с подкреплением включает следующие компоненты:

  • Агент — алгоритм, принимающий решения
  • Среда — пространство, в котором действует агент
  • Состояния — различные ситуации в среде
  • Действия — возможные шаги агента
  • Награды — численная оценка успешности действия
  • Политика — стратегия выбора действий агентом

Процесс обучения происходит итеративно: агент выполняет действие, среда переходит в новое состояние и выдаёт награду, агент корректирует свою стратегию на основе полученного опыта. Цель — найти политику, максимизирующую суммарную награду.

Python стал стандартом де-факто для реализации алгоритмов RL благодаря богатой экосистеме библиотек. Вот ключевые инструменты для работы с обучением с подкреплением:

Библиотека Описание Применение в RL
NumPy Вычисления с многомерными массивами Матричные операции, хранение Q-таблиц
Gymnasium (ранее Gym) Коллекция тестовых сред Стандартизированные среды для обучения и тестирования агентов
TensorFlow/PyTorch Фреймворки глубокого обучения Создание нейросетевых агентов (DQN, A3C, PPO)
Stable Baselines3 Реализации популярных алгоритмов RL Готовые реализации с возможностью настройки

Простейшая демонстрация концепции RL на Python выглядит так:

Python
Скопировать код
import numpy as np
import gymnasium as gym

# Создаем среду
env = gym.make('CartPole-v1')

# Инициализируем Q-таблицу нулями
# Дискретизируем непрерывное пространство состояний
def discretize_state(state):
# Упрощенная дискретизация для примера
return tuple(np.round(state, 1))

Q = {} # Q-таблица как словарь

# Параметры обучения
alpha = 0.1 # Скорость обучения
gamma = 0.99 # Коэффициент дисконтирования
epsilon = 0.1 # Вероятность случайного действия

# Цикл обучения
for episode in range(1000):
state, _ = env.reset()
state = discretize_state(state)
done = False

while not done:
# Выбор действия (epsilon-жадная политика)
if state not in Q:
Q[state] = np.zeros(env.action_space.n)

if np.random.random() < epsilon:
action = env.action_space.sample() # Исследование
else:
action = np.argmax(Q[state]) # Использование

# Выполнение действия
next_state, reward, done, _, _ = env.step(action)
next_state = discretize_state(next_state)

# Обновление Q-значения (формула Q-learning)
if next_state not in Q:
Q[next_state] = np.zeros(env.action_space.n)

Q[state][action] += alpha * (reward + gamma * np.max(Q[next_state]) – Q[state][action])

state = next_state

Михаил Ковалёв, Senior ML Engineer

Когда я впервые столкнулся с обучением с подкреплением, мне казалось, что это какая-то магия. Наш стартап разрабатывал систему оптимизации энергопотребления в дата-центре, и традиционные методы контроля давали посредственные результаты.

Мы решили применить RL, создав среду, моделирующую наш дата-центр: состояния включали температуры, нагрузки и энергопотребление, действия — настройки систем охлаждения и распределения нагрузки, а наградой было снижение затрат при соблюдении температурного режима.

Первые две недели были кошмаром — агент делал абсурдные вещи: выключал охлаждение при пиковой нагрузке или, наоборот, максимально охлаждал пустые стойки. Мы пересматривали функцию награды несколько раз. Критический момент наступил, когда мы добавили в награду не только текущую экономию, но и штрафы за рискованные состояния.

Через месяц обучения наш алгоритм стал показывать результаты лучше человека-оператора, снизив энергозатраты на 17% без ухудшения производительности. Главный урок: в RL критически важно правильно определить функцию награды — она буквально определяет, что ваш алгоритм будет оптимизировать.

Пошаговый план для смены профессии

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

Для эффективной работы с обучением с подкреплением на Python необходимо понимать ключевые концепции, определяющие архитектуру и поведение систем RL. 🔍

Марковский процесс принятия решений (MDP) — математическая основа RL, где переход в следующее состояние зависит только от текущего состояния и действия, но не от предыдущей истории. Формально MDP описывается кортежем (S, A, P, R, γ), где:

  • S — множество состояний
  • A — множество действий
  • P — вероятности переходов между состояниями
  • R — функция награды
  • γ — коэффициент дисконтирования (от 0 до 1)

Коэффициент дисконтирования определяет, насколько агент ценит будущие награды по сравнению с немедленными. Значения ближе к 0 делают агента "близоруким", а ближе к 1 — "дальновидным".

Политики и стратегии исследования определяют, как агент выбирает действия. Существуют различные подходы:

  • Epsilon-жадная (ε-greedy) — с вероятностью ε выбирается случайное действие, иначе — оптимальное по текущим оценкам
  • Softmax — вероятность выбора действия пропорциональна его ожидаемой ценности
  • Upper Confidence Bound (UCB) — учитывает как ценность действия, так и неопределенность оценки

Ключевые алгоритмы обучения с подкреплением на Python включают:

Алгоритм Тип Особенности Применимость
Q-learning Безмодельный, off-policy Строит Q-таблицу значений состояние-действие Небольшие дискретные пространства
SARSA Безмодельный, on-policy Обновление на основе следующего фактического действия Задачи, где важна безопасность исследования
DQN Глубокий RL, off-policy Нейросеть вместо Q-таблицы Большие/непрерывные пространства состояний
Policy Gradient On-policy Напрямую оптимизирует политику Непрерывные пространства действий
PPO On-policy Стабильные обновления политики Сложные среды, требующие стабильности

Рассмотрим пример реализации алгоритма SARSA на Python:

Python
Скопировать код
import numpy as np
import gymnasium as gym

env = gym.make('CliffWalking-v0')
Q = np.zeros([env.observation_space.n, env.action_space.n])

# Гиперпараметры
alpha = 0.1
gamma = 0.99
epsilon = 0.1
episodes = 2000

# Обучение с использованием SARSA
for episode in range(episodes):
state, _ = env.reset()

# Выбираем действие из состояния (epsilon-greedy)
if np.random.random() < epsilon:
action = env.action_space.sample()
else:
action = np.argmax(Q[state, :])

done = False
while not done:
# Выполняем действие
next_state, reward, done, _, _ = env.step(action)

# Выбираем следующее действие (epsilon-greedy)
if np.random.random() < epsilon:
next_action = env.action_space.sample()
else:
next_action = np.argmax(Q[next_state, :])

# Обновляем Q-значение по формуле SARSA
Q[state, action] += alpha * (reward + gamma * Q[next_state, next_action] – Q[state, action])

# Переходим к новому состоянию и действию
state = next_state
action = next_action

# Постепенное уменьшение epsilon для меньшего исследования в конце
epsilon = max(0.01, epsilon * 0.995)

# Тестирование обученной политики
state, _ = env.reset()
total_reward = 0
done = False

while not done:
action = np.argmax(Q[state, :])
state, reward, done, _, _ = env.step(action)
total_reward += reward

print(f"Итоговая награда: {total_reward}")

Выбор между Q-learning и SARSA часто зависит от особенностей задачи. Q-learning оценивает будущую награду, предполагая оптимальные действия (off-policy), в то время как SARSA учитывает реальную политику исследования (on-policy). Это делает SARSA более консервативным, что может быть предпочтительно в средах с "опасными" состояниями.

Ключевое различие в формулах обновления:

  • Q-learning: Q(s,a) += α[r + γ max<sub>a'</sub>Q(s',a') – Q(s,a)]
  • SARSA: Q(s,a) += α[r + γ Q(s',a') – Q(s,a)]

При работе с обучением с подкреплением на Python важно следить за балансом между исследованием (exploration) и использованием (exploitation). Слишком много исследования — агент не оптимизирует политику; слишком много использования — агент застревает в локальных оптимумах.

Q-learning на Python: пошаговая реализация алгоритма с примерами

Q-learning — фундаментальный алгоритм обучения с подкреплением на Python, который строит таблицу Q-значений, определяющих ожидаемую полезность каждой пары состояние-действие. Процесс работы алгоритма состоит из следующих шагов: 🧠

  1. Инициализация Q-таблицы случайными или нулевыми значениями
  2. Наблюдение текущего состояния среды
  3. Выбор и выполнение действия согласно политике (например, ε-greedy)
  4. Получение награды и наблюдение следующего состояния
  5. Обновление Q-значения по формуле Q-learning
  6. Переход к следующему состоянию и повторение с шага 3

Формула обновления Q-значений представляет собой уравнение Беллмана:

Q(s,a) = Q(s,a) + α [r + γ max<sub>a'</sub> Q(s',a') – Q(s,a)]

где:

  • α (альфа) — скорость обучения (0 < α ≤ 1)
  • γ (гамма) — коэффициент дисконтирования (0 ≤ γ ≤ 1)
  • r — полученная награда
  • max<sub>a'</sub> Q(s',a') — максимальное Q-значение для следующего состояния

Реализуем полноценный алгоритм Q-learning на Python для классической среды FrozenLake из библиотеки Gymnasium:

Python
Скопировать код
import numpy as np
import gymnasium as gym
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

# Создаем среду FrozenLake
env = gym.make('FrozenLake-v1', is_slippery=False)

# Инициализируем Q-таблицу нулями
Q = np.zeros([env.observation_space.n, env.action_space.n])

# Параметры обучения
alpha = 0.8 # Скорость обучения
gamma = 0.95 # Коэффициент дисконтирования
epsilon = 1.0 # Начальная вероятность исследования
min_epsilon = 0.01 # Минимальная вероятность исследования
epsilon_decay = 0.001 # Скорость уменьшения epsilon

# Метрики для отслеживания прогресса обучения
rewards_per_episode = []
steps_per_episode = []
success_rate = []
success_window = 100 # Оценка успешности на последних 100 эпизодах

# Обучение агента
num_episodes = 10000
for episode in range(num_episodes):
# Инициализация среды
state, _ = env.reset()
done = False
total_reward = 0
step_count = 0

while not done:
step_count += 1

# Выбор действия (epsilon-жадная политика)
if np.random.random() < epsilon:
action = env.action_space.sample() # Исследование
else:
action = np.argmax(Q[state, :]) # Использование

# Выполнение действия
next_state, reward, done, _, _ = env.step(action)
total_reward += reward

# Обновление Q-значения
old_value = Q[state, action]
next_max = np.max(Q[next_state, :])

# Формула Q-learning
Q[state, action] = old_value + alpha * (reward + gamma * next_max – old_value)

state = next_state

# Сохранение метрик
rewards_per_episode.append(total_reward)
steps_per_episode.append(step_count)

# Расчет скользящей средней успешных эпизодов (награда > 0)
if len(rewards_per_episode) > success_window:
success_rate.append(sum(1 for r in rewards_per_episode[-success_window:] if r > 0) / success_window)
else:
success_rate.append(sum(1 for r in rewards_per_episode if r > 0) / len(rewards_per_episode))

# Уменьшение epsilon для сокращения исследования
epsilon = max(min_epsilon, epsilon – epsilon_decay)

# Визуализация Q-таблицы
def visualize_q_table(q_table, env_size=4):
"""Визуализирует Q-таблицу как тепловую карту для каждого действия"""
actions = ['Left', 'Down', 'Right', 'Up']
fig, axes = plt.subplots(1, len(actions), figsize=(20, 5))

for i, action_name in enumerate(actions):
q_values = q_table[:, i].reshape(env_size, env_size)
sns.heatmap(q_values, annot=True, cmap='viridis', ax=axes[i], cbar=True)
axes[i].set_title(f'Action: {action_name}')

plt.tight_layout()
plt.show()

visualize_q_table(Q)

# Тестирование политики
def test_policy(env, q_table, n_episodes=100):
"""Тестирует обученную политику на нескольких эпизодах"""
successes = 0
total_steps = 0

for _ in range(n_episodes):
state, _ = env.reset()
done = False
steps = 0

while not done and steps < 100: # Ограничение на количество шагов
steps += 1
action = np.argmax(q_table[state, :])
state, reward, done, _, _ = env.step(action)
total_steps += 1

if reward > 0:
successes += 1
break

success_rate = successes / n_episodes
avg_steps = total_steps / n_episodes

return success_rate, avg_steps

success_rate, avg_steps = test_policy(env, Q)
print(f"Успешность: {success_rate * 100:.2f}%, Среднее количество шагов: {avg_steps:.2f}")

Этот код демонстрирует полный цикл работы с Q-learning: от инициализации до визуализации результатов. Важные аспекты реализации:

  • Баланс исследования и использования: epsilon постепенно уменьшается, снижая вероятность случайных действий
  • Мониторинг обучения: отслеживание наград, шагов и успешности помогает контролировать процесс
  • Визуализация Q-таблицы: позволяет понять, какие состояния и действия агент считает ценными
  • Тестирование политики: оценка эффективности обученного агента на новых эпизодах

При работе с более сложными средами могут возникать проблемы, требующие дополнительных техник:

  1. Проблема разреженных наград: когда положительные награды редки, агенту сложно обучиться. Решение: формирование награды (reward shaping), добавление вспомогательных наград.
  2. Масштабируемость: Q-таблица становится неприменимой при большом количестве состояний. Решение: аппроксимация Q-функции (Deep Q-Network).
  3. Стабильность обучения: Q-learning может расходиться при некоторых условиях. Решение: ограничение максимальных Q-значений, нормализация наград.

Для задач с непрерывным пространством состояний можно использовать дискретизацию или перейти к DQN (Deep Q-Network), где нейросеть аппроксимирует Q-функцию вместо таблицы.

Александр Соколов, Lead Data Scientist

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

Когда я попытался применить стандартный Q-learning, размер Q-таблицы вырос до нескольких гигабайт – совершенно непрактично. Пробовал дискретизировать состояния, но это приводило к потере критической информации и неоптимальным решениям.

Переломный момент наступил, когда я реализовал Q-learning с тайловым кодированием (tile coding) – метод, где непрерывное пространство состояний отображается на несколько перекрывающихся дискретных сеток. Это позволило сохранить детализацию при разумном размере таблицы.

Мы сократили время доставки на 23% и затраты на топливо на 18%. Главный вывод: часто нужно не переходить сразу к сложным методам вроде DQN, а искать умные способы применить более простые и интерпретируемые алгоритмы типа Q-learning с правильными преобразованиями данных.

Deep Q-Network (DQN): создание глубокого обучения с подкреплением

Deep Q-Network (DQN) — революционный алгоритм, объединяющий обучение с подкреплением и глубокие нейронные сети. DQN позволяет работать с высокоразмерными пространствами состояний, где классический Q-learning с таблицами неприменим. 🤖

Ключевые инновации DQN:

  • Функциональная аппроксимация: нейронная сеть заменяет Q-таблицу, аппроксимируя Q(s,a) для всех состояний и действий
  • Experience Replay: хранение переходов (s,a,r,s') в буфере и случайная выборка для обучения, что снижает корреляцию между последовательными обновлениями
  • Целевая сеть: отдельная сеть для вычисления целевых Q-значений, обновляемая реже основной сети, что стабилизирует обучение

Рассмотрим реализацию DQN на Python с использованием TensorFlow и Gymnasium. Для наглядности применим алгоритм к среде CartPole-v1, где агент должен удерживать шест в вертикальном положении:

Python
Скопировать код
import numpy as np
import gymnasium as gym
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
import random
from collections import deque

# Класс агента DQN
class DQNAgent:
def __init__(self, state_size, action_size):
self.state_size = state_size
self.action_size = action_size
self.memory = deque(maxlen=2000) # Буфер опыта
self.gamma = 0.95 # Коэффициент дисконтирования
self.epsilon = 1.0 # Начальное значение epsilon для исследования
self.epsilon_min = 0.01 # Минимальное значение epsilon
self.epsilon_decay = 0.995 # Скорость уменьшения epsilon
self.learning_rate = 0.001 # Скорость обучения
self.model = self._build_model() # Основная Q-сеть
self.target_model = self._build_model() # Целевая Q-сеть
self.update_target_model() # Копирование весов в целевую сеть

def _build_model(self):
"""Создает нейронную сеть для аппроксимации Q-функции"""
model = Sequential()
model.add(Dense(24, input_dim=self.state_size, activation='relu'))
model.add(Dense(24, activation='relu'))
model.add(Dense(self.action_size, activation='linear'))
model.compile(loss='mse', optimizer=Adam(learning_rate=self.learning_rate))
return model

def update_target_model(self):
"""Копирует веса из основной модели в целевую"""
self.target_model.set_weights(self.model.get_weights())

def remember(self, state, action, reward, next_state, done):
"""Добавляет переход в память опыта"""
self.memory.append((state, action, reward, next_state, done))

def act(self, state, training=True):
"""Выбирает действие с использованием epsilon-greedy политики"""
if training and np.random.rand() <= self.epsilon:
return random.randrange(self.action_size)

act_values = self.model.predict(state, verbose=0)
return np.argmax(act_values[0])

def replay(self, batch_size):
"""Обучение на мини-батче из буфера опыта"""
if len(self.memory) < batch_size:
return

minibatch = random.sample(self.memory, batch_size)

states = np.array([t[0][0] for t in minibatch])
actions = np.array([t[1] for t in minibatch])
rewards = np.array([t[2] for t in minibatch])
next_states = np.array([t[3][0] for t in minibatch])
dones = np.array([t[4] for t in minibatch])

# Предсказания для текущих состояний и следующих состояний
targets = self.model.predict(states, verbose=0)
next_targets = self.target_model.predict(next_states, verbose=0)

# Обновление целевых Q-значений по формуле DQN
for i in range(batch_size):
if dones[i]:
targets[i, actions[i]] = rewards[i]
else:
targets[i, actions[i]] = rewards[i] + self.gamma * np.max(next_targets[i])

# Обучение модели
self.model.fit(states, targets, epochs=1, verbose=0)

# Уменьшение epsilon
if self.epsilon > self.epsilon_min:
self.epsilon *= self.epsilon_decay

# Функция для обучения и тестирования агента
def train_dqn(env_name='CartPole-v1', episodes=1000):
env = gym.make(env_name)
state_size = env.observation_space.shape[0]
action_size = env.action_space.n

agent = DQNAgent(state_size, action_size)
batch_size = 32
target_update_freq = 10 # Частота обновления целевой сети

scores = [] # Награды за эпизоды

for e in range(episodes):
state, _ = env.reset()
state = np.reshape(state, [1, state_size])
total_reward = 0

for time in range(500): # Ограничение на количество шагов
# Выбор и выполнение действия
action = agent.act(state)
next_state, reward, done, _, _ = env.step(action)
next_state = np.reshape(next_state, [1, state_size])

# Модификация награды для ускорения обучения
reward = reward if not done else -10

# Сохранение опыта в буфер
agent.remember(state, action, reward, next_state, done)

state = next_state
total_reward += reward

# Обучение на мини-батче
if len(agent.memory) > batch_size:
agent.replay(batch_size)

if done:
break

# Обновление целевой модели
if e % target_update_freq == 0:
agent.update_target_model()

scores.append(total_reward)

# Вывод прогресса обучения
print(f"Episode: {e+1}/{episodes}, Score: {total_reward}, Epsilon: {agent.epsilon:.2f}")

# Если средний результат за последние 100 эпизодов >= 195, считаем задачу решенной
if len(scores) > 100 and np.mean(scores[-100:]) >= 195:
print("CartPole решена! Средний результат за 100 эпизодов >= 195")
break

return agent, scores

# Запуск обучения
agent, scores = train_dqn()

# Тестирование обученного агента
def test_agent(agent, env_name='CartPole-v1', episodes=10):
env = gym.make(env_name, render_mode='human')
state_size = env.observation_space.shape[0]

for e in range(episodes):
state, _ = env.reset()
state = np.reshape(state, [1, state_size])
total_reward = 0
done = False

while not done:
action = agent.act(state, training=False)
next_state, reward, done, _, _ = env.step(action)
next_state = np.reshape(next_state, [1, state_size])
state = next_state
total_reward += reward

if done:
print(f"Тестовый эпизод {e+1}: Счет = {total_reward}")
break

# Тестирование
test_agent(agent)

Этот код демонстрирует все ключевые компоненты DQN:

  1. Архитектура нейронной сети для аппроксимации Q-функции
  2. Буфер опыта (replay memory) для хранения и выборки переходов
  3. Отдельная целевая сеть для стабилизации обучения
  4. Epsilon-greedy стратегия для баланса исследования и использования

При разработке DQN-агентов на Python следует учитывать несколько важных аспектов:

Расширение Описание Преимущество
Double DQN Использует две сети для выбора и оценки действий Уменьшает переоценку Q-значений
Dueling DQN Разделяет оценку состояния и преимущества действий Лучше оценивает ценность состояний
Prioritized Replay Приоритетная выборка важных переходов Ускоряет обучение на редких информативных событиях
Noisy Networks Добавляет шум в веса сети Более эффективное исследование

Гиперпараметры играют критическую роль в эффективности DQN. Наиболее важными являются:

  • Размер буфера опыта: слишком маленький приводит к коррелированным обновлениям, слишком большой требует больше памяти
  • Частота обновления целевой сети: редкие обновления стабилизируют обучение, но замедляют его
  • Коэффициент дисконтирования (γ): влияет на то, насколько дальновидным будет агент
  • Скорость обучения: слишком высокая может привести к расходимости, слишком низкая — к медленному обучению

При применении DQN к новым средам часто требуется предварительная обработка и нормализация входных данных. Например, для обучения на пиксельных наблюдениях (как в оригинальной работе DeepMind с Atari) используются свёрточные слои и преобразование изображений в оттенки серого.

Современная экосистема Python предлагает множество библиотек, упрощающих реализацию DQN и других алгоритмов глубокого обучения с подкреплением: TensorFlow-Agents, Stable Baselines3, PyTorch-DRL, RLlib. Они предоставляют готовые компоненты и оптимизированные реализации, позволяя сосредоточиться на решении конкретных задач, а не на деталях реализации.

Практические проекты: применение обучения с подкреплением на Python

Теоретические знания об обучении с подкреплением на Python обретают истинную ценность только при применении к реальным задачам. В этом разделе мы рассмотрим конкретные проекты, которые вы можете реализовать для закрепления навыков и создания практически полезных систем. 🚀

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

  1. Игровые AI-агенты — от классических игр до сложных симуляций
  2. Робототехника и управление — обучение физическим действиям в реальном или симулированном мире
  3. Оптимизация ресурсов — планирование, распределение, логистика
  4. Рекомендательные системы — персонализация контента с учетом долгосрочной пользы
  5. Финансовые стратегии — алгоритмическая торговля и управление портфелем

Рассмотрим практический проект создания торгового бота с использованием обучения с подкреплением:

Python
Скопировать код
import numpy as np
import pandas as pd
import gymnasium as gym
from gymnasium import spaces
import matplotlib.pyplot as plt
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import DummyVecEnv
from stable_baselines3.common.evaluation import evaluate_policy

# Создаем среду для торговли
class TradingEnv(gym.Env):
"""Среда для торговли на финансовом рынке с RL-агентом"""

metadata = {'render.modes': ['human']}

def __init__(self, df, initial_balance=10000, transaction_fee_percent=0.001):
super(TradingEnv, self).__init__()

# Данные цен
self.df = df
self.reward_range = (-np.inf, np.inf)
self.initial_balance = initial_balance
self.transaction_fee_percent = transaction_fee_percent

# Действия: 0=держать, 1=купить, 2=продать
self.action_space = spaces.Discrete(3)

# Наблюдаемое пространство: [баланс, активы, открытая позиция, OHLCV-данные]
self.observation_space = spaces.Box(low=0, high=1, shape=(10,), dtype=np.float32)

# Индекс текущего шага
self.current_step = 0
self.balance = initial_balance
self.shares_held = 0
self.net_worth = initial_balance
self.cost_basis = 0

# История действий для отображения
self.trades = []

def _next_observation(self):
"""Возвращает текущее наблюдение из среды"""
frame = np.array([
self.balance / self.initial_balance,
self.shares_held / 100, # Нормализация
self.cost_basis / self.df.iloc[self.current_step]['Close'],
self.df.iloc[self.current_step]['Open'] / self.df.iloc[self.current_step]['Close'],
self.df.iloc[self.current_step]['High'] / self.df.iloc[self.current_step]['Close'],
self.df.iloc[self.current_step]['Low'] / self.df.iloc[self.current_step]['Close'],
self.df.iloc[self.current_step]['Volume'] / 1000000, # Нормализация объема
self.df.iloc[self.current_step]['EMA_10'] / self.df.iloc[self.current_step]['Close'],
self.df.iloc[self.current_step]['EMA_30'] / self.df.iloc[self.current_step]['Close'],
self.df.iloc[self.current_step]['RSI'] / 100 # RSI нормализован от 0 до 1
])

return frame

def _take_action(self, action):
"""Выполняет действие в среде и возвращает вознаграждение"""
current_price = self.df.iloc[self.current_step]['Close']

if action == 1: # Покупка
# Используем весь доступный баланс для покупки акций
max_shares = self.balance / current_price
shares_to_buy = int(max_shares)
if shares_to_buy > 0:
# Стоимость с учетом комиссии
cost = shares_to_buy * current_price * (1 + self.transaction_fee_percent)

# Обновляем баланс и активы
self.balance -= cost
self.cost_basis = current_price
self.shares_held += shares_to_buy

# Сохраняем сделку
self.trades.append({
'step': self.current_step,
'price': current_price,
'type': 'buy',
'shares': shares_to_buy,
'cost': cost
})

elif action == 2: # Продажа
if self.shares_held > 0:
# Получаем выручку от продажи с учетом комиссии
proceeds = self.shares_held * current_price * (1 – self.transaction_fee_percent)

# Обновляем баланс и активы
self.balance += proceeds
self.shares_held = 0
self.cost_basis = 0

# Сохраняем сделку
self.trades.append({
'step': self.current_step,
'price': current_price,
'type': 'sell',
'shares': self.shares_held,
'proceeds': proceeds
})

# Рассчитываем чистую стоимость портфеля
self.net_worth = self.balance + self.shares_held * current_price

# Вознаграждение = изменение чистой стоимости
reward = self.net_worth – self.initial_balance
return reward

def step(self, action):
"""Выполняет шаг в среде"""
# Выполняем действие
reward = self._take_action(action)

# Переходим к следующему шагу
self.current_step += 1

# Проверяем, закончился ли эпизод
done = self.current_step >= len(self.df) – 1

# Получаем новое наблюдение
obs = self._next_observation()

return obs, reward, done, {}

def reset(self):
"""Сбрасывает среду в начальное состояние"""
self.balance = self.initial_balance
self.shares_held = 0
self.net_worth = self.initial_balance
self.cost_basis = 0
self.current_step = 0
self.trades = []

return self._next_observation()

def render(self, mode='human'):
"""Визуализирует состояние среды"""
if mode == 'human':
profit = self.net_worth – self.initial_balance
print(f'Шаг: {self.current_step}')
print(f'Баланс: {self.balance}')
print(f'Акции: {self.shares_held}')
print(f'Чистая стоимость: {self.net_worth}')
print(f'Прибыль: {profit}')
return profit

# Подготовка данных
def prepare_data():
"""Загружает и подготавливает данные для обучения"""
# Для примера используем генерированные данные
# В реальном проекте можно использовать API для получения рыночных данных

# Генерируем временной ряд с случайным трендом и волатильностью
np.random.seed(42)
dates = pd.date_range(start='2020-01-01', end='2021-01-01')
n_steps = len(dates)

# Генерируем случайные цены
price = 100
prices = [price]

for _ in range(1, n_steps):
change_percent = np.random.normal(0, 0.01)
price = price * (1 + change_percent)
prices.append(price)

# Создаем DataFrame с OHLCV-данными
df = pd.DataFrame({
'Date': dates,
'Open': prices,
'High': [p * (1 + np.random.uniform(0, 0.02)) for p in prices],
'Low': [p * (1 – np.random.uniform(0, 0.02)) for p in prices],
'Close': [p * (1 + np.random.normal(0, 0.005)) for p in prices],
'Volume': [np.random.randint(100000, 10000000) for _ in range(n_steps)]
})

# Рассчитываем технические индикаторы
df['EMA_10'] = df['Close'].ewm(span=10, adjust=False).mean()
df['EMA_30'] = df['Close'].ewm(span=30, adjust=False).mean()

# Рассчитываем RSI
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df['RSI'] = 100 – (100 / (1 + rs))

# Заполняем NaN
df = df.fillna(0)

# Разделяем на обучающий и тестовый наборы
train_size = int(len(df) * 0.8)
train_df = df[:train_size]
test_df = df[train_size:]

return train_df, test_df

# Подготовка данных
train_df, test_df = prepare_data()

# Создание векторизованной среды
def make_env(df):
def _init():
env = TradingEnv(df)
return env
return _init

# Создаем и обучаем модель
env = DummyVecEnv([make_env(train_df)])
model = PPO('MlpPolicy', env, verbose=1, learning_rate=0.0001)
model.learn(total_timesteps=50000)

# Оцениваем модель
mean_reward, _ = evaluate_policy(model, env, n_eval_episodes=10)
print(f'Средняя награда: {mean_reward}')

# Тестируем модель на тестовом наборе
test_env = DummyVecEnv([make_env(test_df)])
obs = test_env.reset()
done = False

# Отслеживаем чистую стоимость во время теста
net_worths = [10000] # Начальная стоимость

while not done:
action, _ = model.predict(obs)
obs, reward, done, info = test_env.step(action)

# Получаем текущую чистую стоимость
net_worth = test_env.envs[0].net_worth
net_worths.append(net_worth)

if done[0]:
break

# Визуализируем результаты
plt.figure(figsize=(12, 6))
plt.plot(net_worths)
plt.title('Изменение чистой стоимости портфеля')
plt.xlabel('Шаги')
plt.ylabel('Стоимость портфеля')
plt.grid(True)
plt.show()

# Визуализируем сделки на графике цены
plt.figure(figsize=(15, 7))
plt.plot(test_df['Close'].values, label='Цена закрытия')

# Отмечаем точки покупки и продажи
trades = test_env.envs[0].trades
for trade in trades:
step = trade['step']
price = trade['price']
if trade['type'] == 'buy':
plt.scatter(step, price, color='green', marker='^', s=100)
else: # sell
plt.scatter(step, price, color='red', marker='v', s=100)

plt.title('Торговые сигналы модели')
plt.xlabel('Шаги')
plt.ylabel('Цена')
plt.legend()
plt.grid(True)
plt.show()

Этот пример демонстрирует полноценное приложение обучения с подкреплением для создания торговой системы. Ключевые элементы:

  • Пользовательская среда Gymnasium для симуляции торговли
  • Интеграция технич

Читайте также

Проверь как ты усвоил материалы статьи
Пройди тест и узнай насколько ты лучше других читателей
Что такое обучение с подкреплением?
1 / 5

Загрузка...