Друзья-роботы объединились, чтобы научиться управлять дроном.
Делиться

Помните тот раздражающий зависший дрон из моего прошлого поста? Тот, который научился спускаться к платформе, пролетать сквозь неё, а потом просто… зависать под ней навсегда? Да, я тоже. Я провел целый день, наблюдая за его зависанием, накапливая негативные последствия, словно в замедленной съемке крушения, и я даже не мог злиться, потому что технически он делал именно то, что я ему приказал.
Основная проблема заключалась в том, что моя функция вознаграждения могла видеть только текущее состояние , а не траекторию. Когда я вознаграждал дрон за приближение к платформе, она не могла отличить дрон, приближающийся к посадке, от дрона, который уже пролетел сквозь платформу и теперь использует структуру вознаграждения снизу. Функция вознаграждения r(s') учитывала только местоположение дрона, а не то, как он туда попал или куда направлялся. (Кстати, это станет повторяющейся темой. На данный момент тема инженерии вознаграждений преследует меня даже во сне.)
Но вот тут начинается самое интересное. Пока я в сотый раз смотрел на свой дрон, зависший под платформой, я всё думал: зачем я жду, пока закончится весь эпизод, прежде чем чему-либо научиться? REINFORCE заставил меня собрать полную траекторию, посмотреть, как дрон падает (или иногда приземляется), вычислить все возвраты, а затем обновить стратегию. А что если бы мы могли просто… учиться после каждого шага ? Например, получать мгновенную обратную связь по мере полёта дрона? Разве это не было бы гораздо эффективнее?
Это Actor-Critic . И, спойлер: он работает намного лучше, чем я ожидал. Ну, после того, как я исправил три серьёзные ошибки, дважды переписал функцию вознаграждения, два дня думал, что PyTorch сломан (он не сломался, я просто неправильно его использовал), и наконец понял, почему мой коэффициент скидки делал награды в терминале совершенно невидимыми. Но об этом позже.
В этом посте я расскажу вам обо всем своем пути внедрения методов Actor-Critic для задачи посадки дрона. Вы увидите успехи, досадные неудачи и марафоны отладки. Вот что мы рассмотрим:
Базовый Actor-Critic с ошибкой TD позволил мне достичь 68% успеха и сошлся в два раза быстрее, чем REINFORCE. Эта часть работала на удивление хорошо после того, как я исправил ошибку с движущейся целью (подробнее об этом кошмаре позже).
Моя попытка использовать обобщенную оценку преимущества (GAE) полностью провалилась. Я потратил целых три дня на отладку, пытаясь понять, почему мои критические значения взлетали до тысяч, перепробовал все возможные решения и в конце концов просто… сдался и пошел дальше. Иногда нужно знать, когда следует изменить курс. (Честно говоря, я до сих пор немного обижен на это.)
Проксимальная оптимизация политики (PPO) , которая наконец-то обеспечила мне стабильную и надежную работу и объяснила, почему вся индустрия обучения с подкреплением использует ее по умолчанию. Оказывается, когда OpenAI говорит: «Это то, что нужно», они, вероятно, правы.
Но что еще важнее, вы узнаете о трех критических ошибках, которые едва не сорвали все планы. Это не мелкие ошибки типа «ой, опечатка». Это ошибки, из-за которых вы будете шесть часов рассматривать кривые обучения, гадая, не неправильно ли вы понимаете нейронные сети:
- Проблема движущейся цели , из-за которой мои потери критика бесконечно колебались, потому что я не отделил цель TD (эта проблема заставила меня усомниться во всем моем понимании обратного распространения ошибки).
- Значение гаммы было слишком низким , и в результате награды за посадку после скидки стоили буквально 0,00000006, поэтому мой агент просто сразу же начал падать, потому что зачем вообще стараться? (Я распечатал фактические значения со скидкой и посмеялся, а потом заплакал).
- Эксплуатация системы вознаграждений, когда мой дрон научился пролетать мимо платформы на максимальной скорости, собирать награды за пройденное расстояние и падать далеко, потому что это почему-то было лучше, чем приземление. Это научило меня тому, что 90% обучения с подкреплением — это действительно разработка системы вознаграждений, а остальные 90% — это отладка того, почему ваша разработка системы вознаграждений не сработала. (Да, я знаю, что это 180%. Вот сколько работы это требует.)
Давайте начнём. Заварите себе кофе, он вам понадобится. Весь код можно найти в моём репозитории на GitHub.
Что такое актёр-критик?
У REINFORCE была одна фундаментальная проблема: нам приходилось ждать . Ждать, пока дрон разобьется. Ждать, пока эпизод закончится. Ждать, пока будет вычислен полный результат. Только после этого мы могли обновить политику. Один обучающий сигнал на эпизод. Для траектории в 150 шагов это одно обновление после просмотра 150 выполненных действий.
Я запустил алгоритм REINFORCE на 1200 итераций (6 часов на моей машине), чтобы достичь 55% успеха. И всё это время я думал: это кажется пустой тратой времени. Почему я не могу учиться в процессе?
Actor-Critic решает эту проблему с помощью простой идеи: обучить вторую нейронную сеть («критика») оценивать будущую доходность для любого состояния. Затем использовать эти оценки для обновления политики после каждого шага . Больше не нужно ждать окончания эпизодов. Только непрерывное обучение во время полета дрона.
Результат? 68% успеха за 600 итераций (3 часа). Вдвое меньше времени. Лучшая производительность. То же оборудование.
Принцип работы: две сети взаимодействуют в режиме реального времени.
Исполнитель (π(a|s)): Та же самая сеть управления, что и в REINFORCE. Принимает текущее состояние и выдает вероятности действий. Это сеть, которая фактически управляет дроном.
Критик (V(s)): Новая нейронная сеть. Принимает текущее состояние и оценивает, «насколько хорошее это состояние?». Выдает одно значение, представляющее ожидаемые будущие вознаграждения. Не привязана к какому-либо конкретному действию, просто оценивает состояния.
Вот в чем хитрость: критик дает мгновенную обратную связь. Действующее лицо совершает действие, окружающая среда обновляется, и критик немедленно оценивает, привело ли это нас к лучшему или худшему состоянию. Действующее лицо учится на этом сигнале и корректирует свои действия. Критик одновременно учится делать более точные прогнозы. Обе нейронные сети улучшаются вместе по мере развития событий.

В коде они выглядят так:
class DroneGamerBoi(nn.Module): «»»Актер: выводит вероятности действий»»» def __init__(self, state_dim=15): super().__init__() self.network = nn.Sequential( nn.Linear(state_dim, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 64), nn.LayerNorm(64), nn.ReLU(), nn.Linear(64, 3), # Три независимых двигателя nn.Sigmoid() ) def forward(self, state): return self.network(state) # Вывод: вероятности для каждого класса двигателей DroneTeacherBoi(nn.Module): «»»Критик: выводит оценку значения состояния»»» def __init__(self, state_dim=15): super().__init__() self.network = nn.Sequential( nn.Linear(state_dim, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 64), nn.LayerNorm(64), nn.ReLU(), nn.Linear(64, 1) # Одно значение: V(s) ) def forward(self, state): return self.network(state) # Вывод: оценка скалярного значения
Обратите внимание, что нейронная сеть критика практически идентична сети актора, за исключением того, что последний слой выдает одно значение (насколько хороша эта ситуация?) вместо вероятностей действий.
Секрет успешного старта за счет собственных средств
Итак, вот тут начинается самое интересное. В REINFORCE нам требовалась полная налоговая декларация для обновления политики:
[ G_t = r_t + gamma r_{t+1} + gamma^2 r_{t+2} + cdots + gamma^{Tt} r_T ]
Нам пришлось дождаться конца эпизода, чтобы узнать обо всех наградах. Но что, если бы… мы этого не сделали? Что, если бы мы просто предсказывали будущее, используя нашу сеть критиков?
Вместо того чтобы вычислять фактическую доходность, мы её оцениваем:
[ G_t approx r_t + gamma V(s_{t+1}) ]
Это называется бутстреппингом . Критик «бутстреппингует» свою собственную оценку стоимости, чтобы приблизительно оценить полную доходность. Мы используем его прогноз «насколько хороша будет следующая стадия?» для оценки текущей доходности.

Почему это помогает?
Меньшая дисперсия. Мы не ждём фактической случайной последовательности будущих вознаграждений. Мы используем оценку, основанную на том, что мы узнали о состояниях в целом. Это более зашумлённо, чем истина (критик может ошибаться!), но менее зашумлённо, чем любой результат отдельного эпизода.
Онлайн-обучение. Мы можем мгновенно обновлять информацию на каждом этапе. Нет необходимости сначала досматривать эпизод. Как только дрон совершает одно действие, мы сразу же узнаем о вознаграждении и можем оценить, что будет дальше, чтобы учиться.
Повышенная эффективность использования выборки. В REINFORCE с 6 параллельными играми каждый дрон обучается один раз за завершение эпизода. В Actor-Critic с 6 параллельными играми каждый дрон обучается на каждом шаге (около 150 шагов за эпизод). Это в 150 раз больше обучающих сигналов за эпизод!
Конечно, здесь есть компромисс: мы вносим предвзятость. Если наш критик ошибается (а он будет ошибаться, особенно на ранних этапах обучения), наш агент учится на неверных оценках. Но критик не обязательно должен быть идеальным. Он просто должен быть менее шумным, чем результат отдельного эпизода. По мере того, как критик постепенно улучшается, актёр учится на более качественной обратной связи. Они взаимно улучшают друг друга. На практике снижение дисперсии настолько эффективно, что стоит смириться с небольшой предвзятостью.
Ошибка TD: Новое преимущество
Теперь нам нужно ответить на вопрос: насколько лучше или хуже оказались эти действия, чем ожидалось?
В REINFORCE у нас было преимущество : фактическая доходность минус базовый уровень. Базовым уровнем был глобальный средний показатель. Но мы можем добиться гораздо лучших результатов. Вместо глобального базового уровня мы используем оценку критика по отдельным штатам.
Ошибка временной разницы (TD) — наше новое преимущество:
[ delta_t = r_t + gamma V(s_{t+1}) – V(s_t) ]
Проще говоря:
- (r_t + gamma V(s_{t+1})) = целевое значение TD. Непосредственное вознаграждение плюс наша оценка ценности следующего состояния.
- (V(s_t)) = Наш прогноз для текущего состояния.
- (delta_t) = Разница. Мы справились лучше или хуже, чем ожидали?
Если (delta_t > 0), мы показали результаты лучше, чем ожидали, → подкрепите это действие.
Если (delta_t < 0), мы показали результаты хуже ожидаемых → снизьте вероятность выполнения этого действия.
Если (delta_t approx 0), то мы были абсолютно правы → действия были примерно средними.
Это гораздо информативнее, чем глобальная базовая линия REINFORCE. Сигнал теперь зависит от состояния. Дрон, находящийся в сложном вращении, может получить -10 очков награды, и это на самом деле довольно неплохо (обычно там -50). Но если он спокойно зависает над платформой, -10 — это ужасно. Критик знает разницу. Ошибка TD это фиксирует.
Вот как это происходит в цикле обучения (в упрощенном виде):
# 1. Выполните одно действие в каждой параллельной игре: action = actor(state) next_state, reward = env.step(action) # 2. Получите оценки значений: value_current = critic(state) value_next = critic(next_state) # 3. Вычислите ошибку TD (наше преимущество): td_error = reward + gamma * value_next — value_current # 4. Обновите критика: он должен был предсказать лучше. # Критик стремится минимизировать ошибку прогнозирования, поэтому мы используем квадратичную ошибку. # Градиент затем приближает прогнозы критика к фактическим результатам. critic_loss = td_error ** 2 critic_loss.backward() critic_optimizer.step() # 5. Обновите актора: подкрепляйте или отговаривайте на основе ошибки TD. # (тот же градиент политики, что и REINFORCE, но с ошибкой TD вместо результатов) actor_loss = -log_prob(action) * td_error actor_loss.backward() actor_optimizer.step()
Обратите внимание, что мы обновляем обе сети на каждом шаге, а не в каждом эпизоде. В этом и заключается магия онлайн-обучения.
Ещё одно сравнение, чтобы это стало предельно ясно:
| Метод | Чему мы учимся из | Время | Исходный уровень |
|---|---|---|---|
| УСИЛЕНИЕ | Полный возврат G_t | После окончания эпизода | Глобальный средний показатель доходности всех показателей |
| Актер-критик | Ошибка TD δ_t | После каждого шага | V(s_t), специфичное для состояния |
Второй вариант более точный, информативный и приходит гораздо раньше.

Вот почему Actor-Critic сходился за 600 итераций на моей машине, в то время как REINFORCE потребовалось 1200. Одна и та же функция вознаграждения, одна и та же среда, один и тот же дрон. Но получать обратную связь после каждого шага, а не каждые 150 шагов? Это дает 150-кратное преимущество в информации за итерацию.
Три жучка: Одиссея отладки
Ладно, сейчас я расскажу вам о трёх ошибках, которые чуть меня не сломали. Не так, что я получил ошибку «ой, смещение на единицу». Я имею в виду такую поломку, когда ты шесть часов смотришь на кривые обучения, всерьёз сомневаешься, понимаешь ли ты обратное распространение ошибки, пять раз отлаживаешь свой код, а потом ещё два часа тратишь на чтение научных статей, чтобы убедить себя, что ты не сошёл с ума.
Эти ошибки настолько незаметны, что даже опытным специалистам по обучению с подкреплением приходится быть осторожными. Хорошая новость: как только вы их поймете, они станут очевидными. Плохая новость: сначала нужно их понять, и я убедился в этом на собственном горьком опыте.
Ошибка №1: Проблема движущейся мишени
Настройка
Я реализовал Actor-Critic именно так, как мне показалось логичным. У меня две нейронные сети. Одна предсказывает действия, другая — значения. Просто, правда? Я написал алгоритм вычисления ошибки TD:
# Вычисление оценок значений values = critic(batch_data['states']) next_values = critic(batch_data['next_states']) # Вычисление целевых значений и ошибок TD td_targets = rewards + gamma * next_values * (1 — dones) td_errors = td_targets — values # Функция потерь критического алгоритма critic_loss = (td_errors ** 2).mean() # Обратный проход critic_loss.backward()
Мне это показалось вполне разумным. Мы вычисляем ожидаемые значения, вычисляем ожидаемые значения (td_targets), измеряем ошибку и обновляем. Стандартные методы обучения с учителем.
Симптом: Ничего не помогает
Я обучал модель 200 итераций, и функция потерь критика… колебалась в районе 500-1000 и не менялась. Не уменьшалась, не увеличивалась, а просто дико осциллировала, как синусоида. Я проверил функцию вознаграждения. Выглядела нормально. Я проверил сеть критика. Стандартная архитектура, ничего странного. Я проверил сами значения ошибки TD. Они скакали между -50 и +50, что казалось разумным, учитывая масштаб вознаграждения.
Но потери так и не сократились.
Я потратил на это два дня. Я добавил Dropout, думая, что, возможно, происходит переобучение. (Неправильная задача, не помогло.) Я уменьшил скорость обучения с 1e-3 до 1e-4, думая, что, возможно, оптимизатор перенастраивается. (Нет, просто обучение замедлилось во время колебаний.) Я проверил, возвращает ли моя среда значения NaN. (Не возвращает.) Я даже задумался, нет ли ошибки в функции автоградуировки PyTorch. (Спойлер: с PyTorch всё в порядке, ошибка была во мне.)
Прорыв
Я читал главу об Actor-Critic в книге Саттона и Барто (уже в пятый раз), когда кое-что привлекло мое внимание. В псевдокоде была строка о «вычислении оценки следующего значения». И я подумал: подождите, когда я вычисляю next_values = critic(next_states), что происходит с этими градиентами во время обратного распространения ошибки?
И тут у меня в голове щелкнуло. О нет. Цель движется, пока мы пытаемся оптимизировать процесс в соответствии с ней. Это называется проблемой движущейся цели .
Почему это всё разрушает
Когда мы вычисляем next_values = critic(next_states) без отсоединения, функция autograd в PyTorch передает градиенты как через V(s), так и через V(s'). Это означает, что мы одновременно обновляем предсказание и целевое значение — критик следует за целевым значением, которое изменяется при каждом обновлении. Градиент становится следующим:
[ frac{partial L}{partial theta} = 2 cdot (r + gamma V(s') – V(s)) cdot left( gamma frac{partial V(s')}{partial theta} – frac{partial V(s)}{partial theta} right) ]
Проблема заключается в члене γ · ∂V(s')/∂θ — мы говорим критику изменить целевую функцию, а не просто предсказание. Функция потерь колеблется бесконечно.
Решение (наконец-то)
Мне нужно было рассматривать целевое значение TD как фиксированную константу. В PyTorch это означает отсоединение градиентов:
# ✅ ПРАВИЛЬНЫЕ значения = critic(batch_data['states']) с torch.no_grad(): # КРИТИЧЕСКАЯ ЛИНИЯ next_values = critic(batch_data['next_states']) td_targets = rewards + gamma * next_values * (1 — dones) td_errors = td_targets — values critic_loss = (td_errors ** 2).mean() critic_loss.backward()
Контекст-менеджер torch.no_grad() сообщает: «Вычислите следующие значения, но не помните, как вы их вычислили. Для целей градиента рассматривайте это как константу». Теперь во время обратного прохода:
[ frac{partial L}{partial theta} = 2 cdot (r + gamma V(s') – V(s)) cdot left( – frac{partial V(s)}{partial theta} right) ]
Проблемный член исчез! Теперь мы обновляем только V(s), предсказание, чтобы оно соответствовало фиксированной целевой величине r + γV(s'). Это именно то, что нам нужно.
Цель TD становится тем, чем она и должна быть: фиксированной меткой , подобной эталонным данным в контролируемом обучении. Мы больше не пытаемся попасть в движущуюся мишень. Мы просто пытаемся предсказать что-то стабильное.
Я изменил всего одну строку. Функция потерь критика перестала хаотично колебаться в диапазоне 500-1000 и плавно снижаться: 500 → 250 → 100 → 35 → 8 за 200 итераций. Эта ошибка коварна, потому что код выглядит вполне разумно — но всегда отключайте целевые значения TD.

Ошибка №2: Слишком низкая гамма (невидимые награды)
Настройка
Ладно, ошибка №1 была незаметной. Сейчас, оглядываясь назад, она кажется до смешного очевидной. Но знаете что? Иногда самые очевидные ошибки легче всего пропустить, потому что не ожидаешь, что проблема окажется настолько простой.
Я исправил ошибку с движущейся целью, и внезапно функция потерь критика начала сходиться. Фантастика! На мгновение я почувствовал себя настоящим инженером. Но потом я запустил агента на полную итерацию обучения, и… ничего. Абсолютно ничего не улучшилось. Дрон совершал несколько случайных движений, а затем тут же врезался в землю или вылетал за пределы экрана. Никакого обучения. Никаких улучшений. Никаких признаков жизни.
На самом деле, подождите. Критик учился. Потери уменьшались. Но дрон не становился лучше. Это казалось нелогичным. Зачем критику учиться предсказывать значения, если агент ничего не узнает из этих значений?
Открытие
Я распечатал целевые значения TD, и все они оказались отрицательными — от -5 до -30. Никаких признаков награды за попадание в цель в +500. Затем я произвел расчеты: при 150-шаговых эпизодах и гамме = 0,90:
[ 500 times 0.90^{150} approx 0.00000006 ]
Вознаграждение за посадку было обесценено. Агент научился мгновенно разбиваться, потому что попытка посадки была буквально невидима для функции ценности.
Коэффициент дисконтирования γ контролирует эффективный горизонт (≈ 1/(1-γ)). При γ = 0,90 это всего 10 шагов — слишком мало для эпизодов в 100-300 шагов.
Решение: изменить значение гаммы с 0,90 на 0,99.
Влияние
Я изменил значение гаммы с 0,90 на 0,99. Сеть та же, награды те же, всё остальное то же самое.
Результат: Итерация 5, дрон двинулся к платформе. Итерация 50, он замедлился при приближении. Итерация 100, первая посадка. К итерации 600, показатель успешности 68%.
Изменение одного параметра привело к совершенно другому поведению агента. Конечная награда из невидимой стала кристально ясной. Всегда проверяйте: эффективный горизонт (1/(1-γ)) должен соответствовать длине вашего эпизода.
Ошибка №3: Использование уязвимостей в системе вознаграждений (Гонка вооружений)
К этому моменту я исправил и проблему с движущейся целью, и проблему с гаммой. Мой агент действительно учился! Он приближался к платформе, иногда замедлялся и даже иногда приземлялся. Я был искренне рад. Затем я начал внимательнее следить за неудачами, и произошло нечто странное.
После исправления ошибок № 1 и № 2 агент обнаружил две новые уязвимости:
Пролет мимо : Разгоняемся до максимальной скорости, промахиваемся, врезаемся в отдаленное место. Чистая награда: -140 (награда за приближение +60, штраф за столкновение -200). Лучше, чем сразу же разбиться (-300), но не приземлиться.

Парение : Подойдите близко к платформе и вибрируйте на месте с помощью крошечных движений (скорость 0,01-0,02), чтобы бесконечно получать награды за приближение, избегая при этом штрафов за столкновение.

Почему это происходит: фундаментальная проблема
Вот что меня беспокоило: моя функция вознаграждения могла видеть только текущее состояние, а не траекторию.
Функция вознаграждения — r(s', a): имея следующее состояние и только что совершенное действие, вычислить мое вознаграждение. Она не имеет памяти. Она не может отличить:
- Дрон уверенно движется к посадке: приближается сверху, совершая контролируемое, целенаправленное снижение.
- Дроны, использующие систему вознаграждений для фарма : зависание в воздухе с бессмысленными микро-движениями.
В обоих сценариях могут присутствовать следующие особенности:
- distance_to_platform < 0.3 (близко к целевому значению)
- скорость > 0 (технически движение)
- velocity_alignment > 0 (направлено вправо)
Агент не глуп. Он делает именно то, что я ему сказал — максимизирует скалярные вознаграждения, которые я ему предоставляю. Проблема в том, что вознаграждения на самом деле кодируют не приземление, а близость и движение. А близость без приземления — это уязвимое место.
В этом и заключается основная идея взлома системы вознаграждений: агент найдет лазейки в вашей спецификации вознаграждения не потому, что он умён, а потому, что вы недостаточно точно определили задачу.
Решение: поощряйте переходы состояний, а не снимки состояния.
Решение: вознаграждение должно основываться на переходах состояний r(s, s'), а не только на текущем состоянии r(s'). Вместо вопроса «Расстояние < 0,3?», следует спросить: «Мы приблизились (distance_delta > 0) И двигались достаточно быстро, чтобы это было правдой (speed ≥ 0,15)?»
def calc_reward(state: DroneState, prev_state: DroneState = None): if prev_state is not None: distance_delta = prev_state.distance_to_platform — state.distance_to_platform speed = state.speed velocity_toward_platform = calculate_alignment(state) # cosine similarity MIN_MEANINGFUL_SPEED = 0.15 if speed >= MIN_MEANINGFUL_SPEED and velocity_toward_platform > 0.1: speed_multiplier = 1.0 + speed * 2.0 rewards['approach'] = distance_delta * 15.0 * speed_multiplier elif speed < 0.05: rewards['hovering_penalty'] = -1.0
Ключевые изменения: (1) Награда за расстояние_delta (прогресс), а не за близость, (2) Порог MIN_SPEED блокирует наведение курсора, (3) Множитель скорости стимулирует решительные действия.
Для этого отслеживайте prev_state в цикле обучения и передавайте его в функцию calc_reward(next_state, prev_state).
90% обучения с подкреплением — это разработка системы вознаграждений. Остальные 90% — это отладка этой системы. Вознаграждения — это спецификация целевой функции, и агент найдет каждую лазейку.
Базовые результаты Actor-Critic
Должен признаться, когда я исправил третью ошибку (функцию вознаграждения, взвешенную по величине скорости) и запустил новый тренировочный запуск со всеми тремя исправлениями, я был настроен скептически. Я потратил столько времени, пытаясь разобраться с этими алгоритмами, что почти ожидал, что Actor-Critic столкнется с какой-нибудь новой, неожиданной проблемой. Но произошло нечто удивительное: он просто… заработал.
И я имею в виду, что это действительно сработало. Даже лучше, чем REINFORCE — заметно лучше. После сотен часов отладки системы взлома вознаграждений REINFORCE я ожидал, что Actor-Critic хотя бы сравняется с ней по производительности. Вместо этого он её превзошёл.

Почему это лучше, чем REINFORCE (и почему это важно):
Онлайн-обновления Actor-Critic создают замкнутый цикл обратной связи, недоступный REINFORCE. На каждом этапе критик шепчет актеру на ухо: «Эй, это состояние хорошее» или «Это состояние плохое». Это не глобальная базовая оценка, как в REINFORCE. Это оценка, специфичная для каждого состояния, которая становится все лучше и лучше по мере того, как критик учится.
Именно поэтому сходимость происходит в 2 раза быстрее. Именно поэтому конечная производительность на 13% выше. Именно поэтому кривые обучения такие ровные.
И всё это зависело от трёх вещей: отсоединения целевого значения TD, использования правильного коэффициента дисконтирования и отслеживания переходов состояний в функции вознаграждения. Никаких новых алгоритмических ухищрений не требовалось. Просто правильная реализация.
Что дальше: выход за рамки отношений актёр-критик
Несмотря на то, что Actor-Critic работает нормально, вы могли заметить, что политика постоянно приземляет дрон на левую сторону платформы, а также движения немного нестабильны. Для решения этой проблемы я работаю над внедрением оптимизации политики Proximal Policy Optimization (PPO), которая, как предполагается, поможет «сделать процесс обучения более стабильным». Хорошо, что этот метод используется исследователями из OpenAI для обучения их флагманских моделей «GPT».
Ссылки
Фундаментальные работы по реляционной лингвистике
- Саттон, Р.С., и Барто, А.Г. (2018). Обучение с подкреплением: Введение (2-е изд.). Издательство MIT Press.
- Библия обучения с подкреплением. Главы 6-7 посвящены обучению с временной зависимостью и методам Actor-Critic.
- Бесплатно онлайн: http://incompleteideas.net/book/the-book-2nd.html
Методы актора-критика
- Конда, В.Р., и Цициклис, Дж.Н. (2000). «Алгоритмы, критические по отношению к действующему лицу». Журнал SIAM по управлению и оптимизации, 42(4), 1143-1166.
- Теоретические основы теории актора-критика с доказательствами сходимости.
- Мних, В., Бадиа, А.П., Мирза, М. и др. (2016). «Асинхронные методы для глубокого обучения с подкреплением». Международная конференция по машинному обучению.
- A3C: Асинхронный Actor-Critic, масштабируемый для параллельных сред.
- https://arxiv.org/abs/1602.01783
Обучение с использованием временной разницы
- Саттон, Р.С. (1988). «Обучение прогнозированию с помощью методов временных различий». Машинное обучение, 3(1), 9-44.
- Оригинальная статья по обучению TD
Предыдущие публикации в этой серии
- Джумле, В. (2025). «Глубокое обучение с подкреплением: от 0 до 100 – градиенты политики (REINFORCE)».
- Часть 0: Внедрение REINFORCE
Репозиторий кода и реализация
- Джумле, В. (2025). «Основы обучения с подкреплением: посадка дрона-доставщика».
- Полная реализация со всеми тремя исправлениями ошибок.
- GitHub: https://github.com/vedant-jumle/reinforcement-learning-101
- Полный рабочий код см. в файле Actor_Critic_Basic.ipynb.
Все изображения в этой статье либо сгенерированы искусственным интеллектом (с помощью Gemini или Sora), либо созданы мной лично, либо представляют собой скриншоты и сюжеты, созданные мной, если не указано иное.
Источник: towardsdatascience.com



























