RL для программистов: туториал без математики (код внутри) | AiManual
AiManual Logo Ai / Manual.
09 Июл 2026 Гайд

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

Практический гайд по Reinforcement Learning для программистов. Напишем Q-learning агента на Python без формул. Реальный код, минимум теории. Начни сейчас!

RL без матана — это вообще законно?

Да, я знаю, о чём ты подумал: Reinforcement Learning — это же сплошные уравнения Беллмана, марковские процессы и градиенты, от которых голова пухнет. Но давай честно: 90% материала по RL пишут математики для математиков. А мы программисты. Нам проще один раз написать код и увидеть, как агент учится, чем читать три страницы доказательств.

В этом гайде я покажу, как написать настоящего RL-агента на Python, который научится балансировать палку на тележке (CartPole). Ни одной формулы — только код, интуиция и немного магии нейросетей. Если ты когда-нибудь писал игру на Pygame или крутил цикл while True с коллизиями — ты уже готов.

А зачем нам вообще RL, если есть supervised learning?

Хороший вопрос. Supervised learning — это когда ты показываешь модели готовые ответы: вот котик, вот собачка, запоминай. RL — это когда модель сама шляется по миру, получает плюхи за плохие действия и печеньки за хорошие, и учится на своих ошибках. Звучит как дрессировка кота — и это почти так и есть. Только кота заменяет нейросеть, а печеньки — числовая награда.

Где это нужно? Роботы, игры, управление дронами, оптимизация цепочек поставок, рекомендательные системы. Даже ChatGPT в какой-то момент дообучали RLHF — той же самой механикой. Так что RL — не игрушка, а рабочий инструмент. Но чтобы в него въехать, не обязательно штудировать Спиваковского.

Если тебя всё ещё пугает математика, прочитай мою статью «Математика в современном ML: почему теория отстаёт от практики» — там я рассказываю, почему на практике можно обгонять теорию головой.

Что нам понадобится (и ничего лишнего)

  • Python 3.11+ — у тебя уже стоит, не ври.
  • gymnasium — преемник знаменитого OpenAI Gym, библиотека с готовыми средами.
  • PyTorch — чтобы собрать маленькую нейросеть-политику. Можно и на numpy, но с PyTorch проще считать градиенты.
  • numpy — куда ж без него.

Установка одной строкой:

pip install gymnasium torch numpy

Готово. Теперь мы официально RL-инженеры. (Шутка. Но скоро станем.)

Среда: что видит агент и что он может делать

Возьмём классику — CartPole-v1. На тележке стоит палка, может упасть. Агент толкает тележку влево или вправо. Задача — удержать палку вертикально как можно дольше. Среда даёт агенту 4 числа: положение тележки, скорость, угол отклонения палки и угловую скорость. Действий два: 0 (влево) и 1 (вправо). Награда: +1 за каждый шаг, пока палка не упала.

В RL это называется state (состояние), action (действие) и reward (награда). Всё. Больше терминов не будет.

Первый блин: рандомный агент (чтобы понять baseline)

Прежде чем писать умного агента, давай посмотрим, сколько наберёт полный дурак, который действует случайно. Код:

import gymnasium as gym

env = gym.make('CartPole-v1', render_mode='human')
for episode in range(3):
    state, _ = env.reset()
    total_reward = 0
    done = False
    while not done:
        action = env.action_space.sample()  # случайное действие
        state, reward, done, truncated, _ = env.step(action)
        total_reward += reward
        done = done or truncated
    print(f'Episode {episode+1}: {total_reward}')
env.close()

Скорее всего, ты увидишь rewards около 15-25. Палка падает быстро. Наша цель — переплюнуть хотя бы 200 (это максимальное значение в среде).

Мозг агента: маленькая Q-сеть

Теперь самое интересное. Мы не будем писать Q-таблицу (она не влезет в непрерывные состояния). Вместо этого научим нейросеть аппроксимировать Q-функцию: сколько примерно награды мы ожидаем получить за каждое действие в текущем состоянии.

Архитектура — котёнок: 4 входа (состояние), один скрытый слой 128 нейронов, ReLU, 2 выхода (Q-значения для каждого действия).

import torch
import torch.nn as nn
import torch.optim as optim

class DQN(nn.Module):
    def __init__(self, state_dim, action_dim):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(state_dim, 128),
            nn.ReLU(),
            nn.Linear(128, action_dim)
        )
    def forward(self, x):
        return self.net(x)

Всё. Никаких градиентов политики, никакого TRPO. Чистый DQN (Deep Q-Network) в 10 строк.

Цикл обучения: от хаоса к стабильности

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

Это называется experience replay — агент учится на своих же воспоминаниях, как мы пересматриваем свои ошибки перед сном.

Соберём всё вместе:

import gymnasium as gym
import numpy as np
import torch
import random
from collections import deque

# Гиперпараметры
LEARNING_RATE = 1e-3
GAMMA = 0.99
EPSILON_START = 1.0
EPSILON_MIN = 0.01
EPSILON_DECAY = 0.995
BATCH_SIZE = 64
MEMORY_SIZE = 10000
MAX_EPISODES = 500

def train():
    env = gym.make('CartPole-v1')
    state_dim = env.observation_space.shape[0]
    action_dim = env.action_space.n
    
    policy_net = DQN(state_dim, action_dim)
    target_net = DQN(state_dim, action_dim)
    target_net.load_state_dict(policy_net.state_dict())
    target_net.eval()
    
    optimizer = optim.Adam(policy_net.parameters(), lr=LEARNING_RATE)
    memory = deque(maxlen=MEMORY_SIZE)
    epsilon = EPSILON_START
    
    for episode in range(MAX_EPISODES):
        state, _ = env.reset()
        state = np.array(state, dtype=np.float32)
        total_reward = 0
        done = False
        
        while not done:
            # выбор действия epsilon-greedy
            if random.random() < epsilon:
                action = env.action_space.sample()
            else:
                with torch.no_grad():
                    q_values = policy_net(torch.tensor(state))
                    action = q_values.argmax().item()
            
            next_state, reward, done, truncated, _ = env.step(action)
            next_state = np.array(next_state, dtype=np.float32)
            done = done or truncated
            total_reward += reward
            
            memory.append((state, action, reward, next_state, done))
            state = next_state
            
            # обучение, если в памяти достаточно примеров
            if len(memory) >= BATCH_SIZE:
                batch = random.sample(memory, BATCH_SIZE)
                states, actions, rewards, next_states, dones = zip(*batch)
                states = torch.tensor(np.array(states))
                actions = torch.tensor(actions).unsqueeze(1)
                rewards = torch.tensor(rewards)
                next_states = torch.tensor(np.array(next_states))
                dones = torch.tensor(dones, dtype=torch.bool)
                
                current_q = policy_net(states).gather(1, actions).squeeze()
                with torch.no_grad():
                    next_q = target_net(next_states).max(1)[0]
                    target_q = rewards + GAMMA * next_q * (~dones)
                
                loss = nn.MSELoss()(current_q, target_q)
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()
        
        # обновление target-сети раз в несколько эпизодов
        if episode % 10 == 0:
            target_net.load_state_dict(policy_net.state_dict())
        
        epsilon = max(EPSILON_MIN, epsilon * EPSILON_DECAY)
        print(f'Episode {episode}, Reward {total_reward:.1f}, Epsilon {epsilon:.3f}')
        
        if total_reward >= 500:
            print('Solved!')
            break
    
    env.close()

if __name__ == '__main__':
    train()

Запусти — и через 100–300 эпизодов увидишь, как агент начнёт держать палку по 200+ шагов. Это не магия. Это RL.

⚠️
Типичная ошибка: не обновлять target-сеть или обновлять слишком часто. Если target_net будет меняться каждый шаг, обучение зашатается. Лучше замораживать её на несколько эпизодов, как в коде.

Но я же ничего не понимаю в матане! Поможет?

А ты заметил, что мы нигде не использовали ни одной формулы? Все «уравнения Беллмана» спрятались в строчку target_q = rewards + GAMMA * next_q * (~dones). Это и есть Bellman update в коде. Ты можешь не знать, как его вывести — но ты его написал. И он работает.

Современный ML — это инженерия на 80%. Математика — 20% понимания, куда крутить ручки. Я уже писал об этом в статье «Код по наитию»: современные инструменты позволяют писать меньше, а думать — больше над архитектурой, а не над производными. RL — не исключение.

Что дальше? Как улучшать агента

  • Double DQN — избегает переоценки Q-значений, добавить одну строчку в расчёт.
  • Dueling DQN — разделяет ценность состояния и преимущество действия.
  • Noisy Networks — вместо epsilon-greedy шум в весах.
  • Procgen или Minigrid — более сложные среды, чтобы прокачаться.
  • Stable-Baselines3 — библиотека с готовыми алгоритмами (PPO, A2C, DQN). Когда захочешь перестать писать велосипеды — ставь её.

Но для первого раза хватит и нашего маленького DQN. Ты теперь можешь сказать: «Я написал RL-агент, который учится сам». И это правда.

Хочешь стать ML-инженером профессионально?

Если после этого гайда ты захочешь глубже погрузиться в машинное обучение и Reinforcement Learning, но не знаешь, с чего начать — есть структурированные курсы, которые ведут от нуля до первых проектов. Например, Skillbox предлагает программу Machine Learning Engineer с нуля — за год ты пройдёшь всю математическую базу, нарешаешься в Python и сделаешь несколько проектов, в том числе с RL. Или более сжатый вариант — Machine Learning с нуля до Junior. Выбирай, что ближе.

Но даже если ты не пойдёшь на курсы — ты уже сделал первый шаг. Теперь попробуй поменять награду, размер сети, добавь Double DQN. Экспериментируй. RL — это не страшно. Это просто код.

Подписаться на канал