VKВлад КуликовОткрытые лекции
RL для LLM2 / 13

Модуль 2. Состояние, ценность и уравнения Беллмана

Сначала попробуйте решить упражнение. Ответы, подробные решения и проектные ориентиры раскрываются отдельно и сохраняют своё состояние при переключении RU/EN.

Скачать файлы курса (.zip) 382,1 КБ

Архив содержит файлы на обоих языках и вспомогательные материалы с сохранением структуры каталогов.

Состояние, наблюдение и скрытая динамика

Рассмотрите четыре ситуации. Для каждой выберите наиболее точный вариант:

  • A. наблюдение уже является достаточным марковским состоянием;
  • B. наблюдение неполно, и естественна постановка с частичной наблюдаемостью.
  1. Видна только координата мяча, но не его скорость; динамика инерционная.

  2. Задана полная шахматная позиция, очередь хода, права рокировки и возможность взятия на проходе.

  3. В чистой авторегрессионной генерации известны запрос и весь префикс; переход состоит только в добавлении выбранного токена.

  4. Агент видит только последние два сообщения. Ранее инструмент сообщил, какой файл открыт, но это сообщение удалено из контекста; следующий ответ зависит от выбранного файла.

Запишите ответ как 1-?, 2-?, 3-?, 4-?. Затем одним предложением объясните, почему фраза «состояние — это сжатая история» полезна, но не является универсальным определением состояния среды.

Показать ответ и решение

Ответ: 1-B, 2-A, 3-A, 4-B.

  1. Одна координата не определяет следующий переход: при одинаковом положении разные скорости создают разные распределения следующего положения.

  2. Полная шахматная позиция вместе со служебными признаками содержит всю информацию, необходимую правилам следующего хода.

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

  4. Две полные истории могут содержать разные сообщения о выбранном файле, но совпадать в последних двух сообщениях. Утраченное наблюдение меняет прогноз следующего ответа; короткое окно недостаточно.

В теории MDP состояние — переменная, относительно которой выполняется марковское свойство; представление истории является лишь одним способом построить такое состояние для агента. Оно может быть неполным, избыточным или вообще не совпадать с физическим состоянием среды.

Из чего состоит MDP

Выберите три корректных утверждения о конечном марковском процессе принятия решений (Markov decision process, MDP).

  1. Политика π(a∣s)\pi(a\mid s) является частью динамики P(s′∣s,a)P(s'\mid s,a) и не может меняться без изменения MDP.

  2. Среднее rˉ(s,a)\bar r(s,a) получается усреднением rˉ(s,a,s′)\bar r(s,a,s') по следующим состояниям с вероятностями P(s′∣s,a)P(s'\mid s,a); условная независимость не требуется.

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

  4. Любая текстовая история взаимодействия с инструментом автоматически является полным состоянием.

  5. Один и тот же MDP можно сочетать с несколькими политиками.

  6. Если награда выдаётся на переходе в терминал, её нужно отбросить, потому что ценность терминального состояния равна нулю.

Показать ответ и решение

Ответы: 2, 3 и 5.

  • По формуле полного ожидания rˉ(s,a)=∑s′P(s′∣s,a)rˉ(s,a,s′)\bar r(s,a)=\sum_{s'}P(s'\mid s,a)\bar r(s,a,s'). Равенство всех условных средних для разных следующих состояний было бы дополнительным предположением; для самого усреднения оно не требуется.
  • Марковское свойство относится к достаточности состояния для будущей динамики при заданном действии.
  • Политика описывает поведение агента, а не физические правила среды; один MDP допускает разные политики.

Пункт 1 смешивает агента со средой. Пункт 4 неверен для произвольного текстового окна, которое может терять существенные прежние наблюдения; наличие скрытого состояния само по себе не опровергает достаточность полной истории. Пункт 6 путает награду на переходе с ценностью после перехода: финальная награда сохраняется в Rt+1R_{t+1}, а нулём становится только дальнейшее продолжение из терминала.

Завершение и усечение

Для каждого перехода выберите корректную одношаговую TD-цель. Пусть r=1r=1, γ=0.9\gamma=0.9, V(s′)=4V(s')=4.

  1. Агент достиг терминальной цели задачи.

  2. Эпизод остановлен внешним лимитом сбора данных, но процесс мог бы продолжиться.

  3. Задача определена как «завершить не более чем за 100 шагов»; оставшийся бюджет входил в состояние, и лимит исчерпан.

Варианты:

  • A. y=r=1y=r=1;
  • B. y=r+γV(s′)=4.6y=r+\gamma V(s')=4.6.

Запишите ответ как 1-?, 2-?, 3-?. Одним предложением объясните, почему одного старого признака done недостаточно.

Показать ответ и решение

Ответ: 1-A, 2-B, 3-A.

Удобно записать цель через маску продолжения

mt=1{переход не терминальный},yt=rt+1+γmtV(st+1).m_t=\mathbf 1\{\text{переход не терминальный}\}, \qquad y_t=r_{t+1}+\gamma m_tV(s_{t+1}).
  1. Истинный терминал: mt=0m_t=0, поэтому y=1y=1.

  2. Внешнее усечение не уничтожает продолжение задачи: mt=1m_t=1, поэтому y=1+0.9⋅4=4.6y=1+0.9\cdot4=4.6.

  3. Лимит является частью самой задачи и уже представлен в состоянии; его исчерпание — терминальный исход, поэтому y=1y=1.

Один done не сообщает, нужно ли обнулить бутстрэппинг или сохранить оценку продолжения.

Ценности V, Q и преимущество

Выберите три корректных утверждения.

  1. Vπ(s)V^\pi(s) всегда равна max⁡aQπ(s,a)\max_aQ^\pi(s,a).

  2. Vπ(s)=∑aπ(a∣s)Qπ(s,a)V^\pi(s)=\sum_a\pi(a\mid s)Q^\pi(s,a).

  3. Aπ(s,a)>0A^\pi(s,a)>0 означает, что действие лучше среднего действия текущей политики в этом состоянии.

  4. Если произвольно изменить награды MDP, AπA^\pi обязательно останется неизменным.

  5. Для любого состояния ∑aAπ(s,a)=0\sum_a A^\pi(s,a)=0 независимо от политики.

  6. Если ко всем Q(s,a)Q(s,a) одного состояния прибавить одну константу c(s)c(s) и затем пересчитать VV, преимущества в этом состоянии не изменятся.

Выберите все утверждения, которые следуют из определений без изменения MDP.

Показать ответ и решение

Ответы: 2, 3 и 6.

По определению

Vπ(s)=∑aπ(a∣s)Qπ(s,a),Aπ(s,a)=Qπ(s,a)−Vπ(s).V^\pi(s)=\sum_a\pi(a\mid s)Q^\pi(s,a), \qquad A^\pi(s,a)=Q^\pi(s,a)-V^\pi(s).

Поэтому положительное преимущество означает превосходство над средним действием той же политики, а не безусловную «хорошесть» действия.

Пункт 6 — полезное следствие тех же определений:

Q′(s,a)=Q(s,a)+c(s)⟹V′(s)=V(s)+c(s)⟹A′(s,a)=A(s,a).Q'(s,a)=Q(s,a)+c(s) \Longrightarrow V'(s)=V(s)+c(s) \Longrightarrow A'(s,a)=A(s,a).

Пункт 5 пропускает веса политики: верно ∑aπ(a∣s)Aπ(s,a)=0\sum_a\pi(a\mid s)A^\pi(s,a)=0. Произвольная смена наград MDP не обязана сохранять преимущества.

Уравнения и операторы Беллмана

Выберите пять корректных утверждений.

  1. Для фиксированной политики уравнение ожидания Беллмана линейно по VπV^\pi.

  2. Оператор оптимальности заменяет усреднение по действиям политики максимумом по действиям.

  3. Уравнение Беллмана само по себе является конкретным алгоритмом обучения нейросети.

  4. При 0≤γ<10\le\gamma<1 операторы TπT_\pi и T∗T_* являются γ\gamma-сжатиями в норме максимума.

  5. При γ=1\gamma=1 отдельные корректные эпизодические задачи могут решаться, но обычное доказательство через строгое сжатие уже не применимо.

  6. Малый остаток Беллмана всегда означает такую же по величине ошибку ценности независимо от γ\gamma.

  7. Неподвижная точка TπT_\pi соответствует ценности фиксированной политики.

Показать ответ и решение

Ответы: 1, 2, 4, 5 и 7.

Для фиксированной политики TπV=rπ+γPπVT_\pi V=r_\pi+\gamma P_\pi V — аффинный оператор (линейный лишь при rπ=0r_\pi=0), а уравнение его неподвижной точки является линейной системой по VV. Оператор оптимальности использует максимум и имеет неподвижную точку V∗V^*. При 0≤γ<10\le\gamma<1 оба оператора являются γ\gamma-сжатиями в ∥⋅∥∞\|\cdot\|_\infty.

Пункт 3 неверен: уравнение Беллмана — условие одношаговой согласованности. Из него можно построить разные алгоритмы, но формула не задаёт автоматически данные, параметризацию и оптимизатор.

Пункт 5 фиксирует важную границу: при γ=1\gamma=1 отдельные эпизодические задачи могут оставаться корректными, но для них нужны дополнительные условия. Пункт 6 неверен, потому что при γ<1\gamma<1

∥V^−Vπ∥∞≤∥TπV^−V^∥∞1−γ.\|\widehat V-V^\pi\|_\infty \le \frac{\|T_\pi\widehat V-\widehat V\|_\infty}{1-\gamma}.

При γ\gamma близком к единице множитель может быть большим.

MC, TD(0) и многошаговые цели

Сопоставьте метод и целевое значение.

МетодЦель
A. MC1. Rt+1+γV(St+1)R_{t+1}+\gamma V(S_{t+1})
B. TD(0)2. полный наблюдённый GtG_t
C. nn-шаговая цель3. несколько наблюдённых наград, затем V(St+n)V(S_{t+n})

Запишите A-?, B-?, C-?.

Затем выберите наиболее точное утверждение:

  1. MC всегда имеет меньшую дисперсию, чем TD.

  2. TD всегда точнее MC при любом бюджете данных.

  3. Компромисс зависит от среды, горизонта, представления, размера шага и бюджета; универсального рейтинга нет.

  4. Бутстрэппинг в TD означает повторную выборку эпизодов с возвращением.

Показать ответ и решение

Соответствие: A-2, B-1, C-3.
Дополнительный ответ: 3.

MC использует наблюдённый хвост эпизода. TD(0) заменяет весь неизвестный хвост текущей оценкой после одного шага. nn-шаговая цель сначала берёт несколько реальных наград и лишь затем выполняет бутстрэппинг.

Бутстрэппинг в RL означает обновление одной оценки при помощи другой текущей оценки. Это не статистический бутстрэп с повторной выборкой. Дисперсия, смещение и скорость обновления зависят от конкретного протокола, поэтому простого общего рейтинга нет.

Почему Q-learning является off-policy

Действия собирает ε\varepsilon-жадная политика. Выберите одно наиболее точное утверждение.

  1. SARSA и Q-learning оба off-policy, потому что используют случайные действия.

  2. SARSA использует фактически выбранное At+1A_{t+1}, а Q-learning — max⁡a′Q(St+1,a′)\max_{a'}Q(S_{t+1},a'); поэтому Q-learning оценивает жадную целевую политику по данным ε\varepsilon-жадного поведения.

  3. Q-learning становится off-policy только после добавления буфера повторного использования опыта.

  4. SARSA не может использовать ε\varepsilon-жадное исследование.

Показать ответ и решение

Ответ: 2.

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

YtSARSA=Rt+1+γQ(St+1,At+1).Y_t^{\mathrm{SARSA}}=R_{t+1}+\gamma Q(S_{t+1},A_{t+1}).

Q-learning строит цель по жадному продолжению:

YtQ=Rt+1+γmax⁡a′Q(St+1,a′).Y_t^Q=R_{t+1}+\gamma\max_{a'}Q(S_{t+1},a').

Следовательно, данные могут приходить от ε\varepsilon-жадной политики, а вывод делается о другой, жадной политике. Буфер опыта не входит в определение on-policy и off-policy.

Групповая базовая оценка и смещение максимизации

Выберите четыре корректных утверждения.

  1. Средняя награда группы ответов в канонической GRPO является точным токеновым Vπ(st)V^\pi(s_t) для каждого префикса.

  2. Групповая нормировка даёт относительный сигнал без отдельного обучаемого критика, но остаётся выборочной статистикой группы полных ответов.

  3. Максимум по шумным несмещённым оценкам может иметь положительное смещение отбора.

  4. Double Q-learning разделяет выбор действия и его оценивание между двумя оценивателями.

  5. Double Q-learning гарантирует абсолютно несмещённую оценку при любой зависимости ошибок.

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

Показать ответ и решение

Ответы: 2, 3, 4 и 6.

Пункт 6 является ограниченной аналогией: статистический механизм отбора похож, но алгоритмы и объекты остаются разными.

В канонической GRPO статистики группы дают относительный ответный сигнал без обучаемого критика. Они не восстанавливают точную токеновую функцию ценности для каждого префикса.

Даже если Q^(a)\widehat Q(a) несмещённа для каждого действия отдельно,

E[max⁡aQ^(a)]≥max⁡aQ(a),\mathbb E[\max_a\widehat Q(a)]\ge\max_aQ(a),

потому что максимум предпочитает положительные ошибки. Double Q-learning ослабляет этот механизм, разделяя выбор и оценивание, но не устраняет все виды ошибки и может недооценивать в некоторых режимах.

Вычислите V и A

В состоянии ss два действия:

Qπ(s,a1)=2,Qπ(s,a2)=6,Q^\pi(s,a_1)=2, \qquad Q^\pi(s,a_2)=6, π(a1∣s)=0.25,π(a2∣s)=0.75.\pi(a_1\mid s)=0.25, \qquad \pi(a_2\mid s)=0.75.

Вычислите Vπ(s)V^\pi(s), Aπ(s,a1)A^\pi(s,a_1) и Aπ(s,a2)A^\pi(s,a_2). Затем проверьте нулевое среднее преимущества по политике.

Показать ответ и решение
Vπ(s)=0.25⋅2+0.75⋅6=5.V^\pi(s)=0.25\cdot2+0.75\cdot6=5.

Следовательно,

Aπ(s,a1)=2−5=−3,Aπ(s,a2)=6−5=1.A^\pi(s,a_1)=2-5=-3, \qquad A^\pi(s,a_2)=6-5=1.

Проверка:

0.25⋅(−3)+0.75⋅1=0.0.25\cdot(-3)+0.75\cdot1=0.

Обычная невзвешенная сумма преимуществ равна −2-2, а не нулю. Нулевым является именно среднее по политике.

Один шаг оператора оптимальности

В состоянии ss доступны два действия.

  • a1a_1 детерминированно ведёт в s1s_1 с наградой 11, V(s1)=3V(s_1)=3;
  • a2a_2 с вероятностью 0.50.5 ведёт в s2s_2 с наградой 00, V(s2)=6V(s_2)=6, и с вероятностью 0.50.5 — в s3s_3 с наградой 22, V(s3)=1V(s_3)=1.

Пусть γ=0.9\gamma=0.9. Вычислите одношаговые Q(s,a1)Q(s,a_1) и Q(s,a2)Q(s,a_2), затем (T∗V)(s)(T_*V)(s) и жадное действие.

Показать ответ и решение

Для первого действия

Q(s,a1)=1+0.9⋅3=3.7.Q(s,a_1)=1+0.9\cdot3=3.7.

Для второго действия нужно усреднить два перехода:

Q(s,a2)=0.5(0+0.9⋅6)+0.5(2+0.9⋅1)=2.7+1.45=4.15.\begin{aligned} Q(s,a_2) &=0.5(0+0.9\cdot6)+0.5(2+0.9\cdot1)\\ &=2.7+1.45=4.15. \end{aligned}

Поэтому

(T∗V)(s)=max⁡(3.7,4.15)=4.15,(T_*V)(s)=\max(3.7,4.15)=4.15,

а жадное действие — a2a_2. Типичная ошибка — сначала усреднить только значения следующих состояний, но забыть, что награда также зависит от конкретного перехода.

Маленькая линейная система Беллмана

Есть одно нетерминальное состояние ss и терминал. Из ss фиксированная политика:

  • с вероятностью 0.50.5 переходит в терминал с наградой 00;
  • с вероятностью 0.50.5 возвращается в ss с наградой 11.

При γ=0.9\gamma=0.9 запишите уравнение Беллмана для Vπ(s)V^\pi(s) и решите его точно.

Показать ответ и решение

Награда на переходе в терминал равна нулю, а терминальное продолжение также нулевое. Для возврата в ss получаем награду 1 и продолжение Vπ(s)V^\pi(s):

Vπ(s)=0.5⋅0+0.5(1+0.9Vπ(s)).V^\pi(s)=0.5\cdot0+0.5\bigl(1+0.9V^\pi(s)\bigr).

Отсюда

Vπ(s)=0.5+0.45Vπ(s),V^\pi(s)=0.5+0.45V^\pi(s), 0.55Vπ(s)=0.5,Vπ(s)=1011≈0.90909.0.55V^\pi(s)=0.5, \qquad V^\pi(s)=\frac{10}{11}\approx0.90909.

TD-обновление и терминальная маска

Дано

V(s)=2,V(s′)=3,r=1,γ=0.9,α=0.1.V(s)=2, \quad V(s')=3, \quad r=1, \quad \gamma=0.9, \quad \alpha=0.1.

Вычислите новое V(s)V(s) в трёх случаях:

  1. обычный нетерминальный переход;

  2. настоящий терминальный переход;

  3. техническое усечение, после которого процесс мог бы продолжиться.

Показать ответ и решение

Обновление имеет вид

V(s)←V(s)+α(y−V(s)).V(s)\leftarrow V(s)+\alpha(y-V(s)).
  1. Нетерминальный переход:
y=1+0.9⋅3=3.7,Vnew(s)=2+0.1(3.7−2)=2.17.y=1+0.9\cdot3=3.7, \qquad V_{new}(s)=2+0.1(3.7-2)=2.17.
  1. Истинный терминал:
y=1,Vnew(s)=2+0.1(1−2)=1.9.y=1, \qquad V_{new}(s)=2+0.1(1-2)=1.9.
  1. Внешнее усечение не обнуляет продолжение, поэтому цель снова равна 3.73.7, а новое значение — 2.172.17.

Трёхшаговая цель

Наблюдены награды [1,2,3][1,2,3], γ=0.5\gamma=0.5, а V(St+3)=4V(S_{t+3})=4. Вычислите

Gt:t+3=Rt+1+γRt+2+γ2Rt+3+γ3V(St+3).G_{t:t+3} =R_{t+1}+\gamma R_{t+2}+\gamma^2R_{t+3}+\gamma^3V(S_{t+3}).

Как изменится ответ, если третий переход завершил задачу терминально?

Показать ответ и решение

Для нетерминального конца окна

Gt:t+3=1+0.5⋅2+0.52⋅3+0.53⋅4=1+1+0.75+0.5=3.25.\begin{aligned} G_{t:t+3} &=1+0.5\cdot2+0.5^2\cdot3+0.5^3\cdot4\\ &=1+1+0.75+0.5=3.25. \end{aligned}

Если третий переход является истинным терминалом, бутстрэппинг обнуляется:

Gt:t+3=1+1+0.75=2.75.G_{t:t+3}=1+1+0.75=2.75.

Сама третья награда не исчезает: терминальность удаляет только продолжение после неё.

Обычная и Double Q цели

Пусть в следующем состоянии

QA(s′,⋅)=[1,4,3],QB(s′,⋅)=[10,2,8],Q^A(s',\cdot)=[1,4,3], \qquad Q^B(s',\cdot)=[10,2,8],

r=0.5r=0.5, γ=0.9\gamma=0.9.

  1. Вычислите обычную Q-learning цель, используя QAQ^A и максимум той же таблицы.

  2. Вычислите Double Q-цель, если действие выбирается по QAQ^A, а оценивается по QBQ^B.

  3. Объясните, почему числа заметно различаются.

Показать ответ и решение

Обычная Q-learning цель использует максимум QAQ^A:

max⁡aQA(s′,a)=4,yQ=0.5+0.9⋅4=4.1.\max_aQ^A(s',a)=4, \qquad y_Q=0.5+0.9\cdot4=4.1.

Double Q-learning также выбирает второе действие по QAQ^A, но оценивает тот же индекс по QBQ^B:

yDouble=0.5+0.9⋅QB(s′,a2)=0.5+0.9⋅2=2.3.y_{Double}=0.5+0.9\cdot Q^B(s',a_2) =0.5+0.9\cdot2=2.3.

Таблица выбора считает действие a2a_2 лучшим, а независимая таблица оценивания присваивает ему низкую ценность. Разделение не позволяет положительной ошибке, вызвавшей выбор, автоматически попасть в целевое значение.

V из Q и преимущество

Реализуйте две небольшие функции. Обе получают массивы формы (n_states, n_actions).

Python
import numpy as np


def v_from_q(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    """V(s) = sum_a policy[s,a] * q_values[s,a]."""
    raise NotImplementedError


def advantage(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    """A(s,a) = Q(s,a) - V(s)."""
    raise NotImplementedError

Минимальная проверка:

Python
Q = np.array([[1., 3.], [0., 2.]])
pi = np.array([[0.5, 0.5], [0.2, 0.8]])
np.testing.assert_allclose(v_from_q(Q, pi), [2.0, 1.6])
np.testing.assert_allclose(advantage(Q, pi), [[-1.0, 1.0], [-1.6, 0.4]])
np.testing.assert_allclose((pi * advantage(Q, pi)).sum(axis=1), 0.0, atol=1e-12)
Показать ответ и решение
Python
import numpy as np


def v_from_q(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    q = np.asarray(q_values, dtype=float)
    pi = np.asarray(policy, dtype=float)
    if q.ndim != 2 or pi.shape != q.shape:
        raise ValueError("q_values and policy must have the same 2D shape")
    if np.any(pi < 0.0) or not np.allclose(pi.sum(axis=1), 1.0):
        raise ValueError("each policy row must be a probability distribution")
    return np.sum(pi * q, axis=1)


def advantage(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    q = np.asarray(q_values, dtype=float)
    return q - v_from_q(q, policy)[:, None]

Индексирование [:, None] превращает вектор формы (n_states,) в столбец (n_states, 1). Затем NumPy вычитает одно значение V(s)V(s) из всех действий соответствующего состояния.

Точное оценивание политики

Реализуйте решение линейной системы по нетерминальным состояниям:

Python
def exact_policy_value(
    transitions: np.ndarray,  # (nS, nA, nS)
    rewards: np.ndarray,      # E[R | s,a,s'], та же форма
    policy: np.ndarray,       # (nS, nA)
    gamma: float,
    terminals: np.ndarray,    # bool, (nS,)
) -> np.ndarray:
    """Терминальные значения остаются нулевыми."""
    raise NotImplementedError

Используйте

Pπ(s,s′)=∑aπ(a∣s)P(s′∣s,a),rπ(s)=∑a,s′π(a∣s)P(s′∣s,a)rˉ(s,a,s′).P_\pi(s,s')=\sum_a\pi(a\mid s)P(s'\mid s,a), \qquad r_\pi(s)=\sum_{a,s'}\pi(a\mid s)P(s'\mid s,a)\bar r(s,a,s').

Функция должна вернуть нули для терминалов и решить (I−γPNN)VN=rN(I-\gamma P_{NN})V_N=r_N для множества нетерминальных состояний NN.

Минимальная проверка:

Python
P = np.zeros((2, 1, 2))
R = np.zeros_like(P)
P[0, 0] = [0.5, 0.5]
R[0, 0] = [1.0, 0.0]
P[1, 0, 1] = 1.0
pi = np.ones((2, 1))
terminal = np.array([False, True])
np.testing.assert_allclose(
    exact_policy_value(P, R, pi, 0.9, terminal),
    [10.0 / 11.0, 0.0],
)
Показать ответ и решение
Python
import numpy as np


def exact_policy_value(
    transitions: np.ndarray,
    rewards: np.ndarray,
    policy: np.ndarray,
    gamma: float,
    terminals: np.ndarray,
) -> np.ndarray:
    p = np.asarray(transitions, dtype=float)
    r = np.asarray(rewards, dtype=float)
    pi = np.asarray(policy, dtype=float)
    terminal = np.asarray(terminals, dtype=bool)

    if p.ndim != 3 or r.shape != p.shape:
        raise ValueError("transitions and rewards must have shape (nS, nA, nS)")
    if pi.shape != p.shape[:2] or terminal.shape != (p.shape[0],):
        raise ValueError("policy or terminal mask has the wrong shape")

    p_pi = np.einsum("sa,san->sn", pi, p)
    r_pi = np.einsum("sa,san,san->s", pi, p, r)

    nonterminal = np.flatnonzero(~terminal)
    values = np.zeros(p.shape[0], dtype=float)
    if nonterminal.size:
        p_nn = p_pi[np.ix_(nonterminal, nonterminal)]
        system = np.eye(nonterminal.size) - gamma * p_nn
        values[nonterminal] = np.linalg.solve(system, r_pi[nonterminal])
    return values

Терминалы исключены из набора неизвестных. Награда на входе в терминал уже присутствует в rπr_\pi строки предыдущего состояния; нулевым становится только дальнейшее значение терминала.

Одно оптимальное обновление Беллмана

Реализуйте локальную механику итерации ценности: сначала вычислите значения всех действий из старого вектора VV, затем максимум по действиям.

Python
def bellman_optimality_sweep(
    transitions: np.ndarray,
    rewards: np.ndarray,
    values: np.ndarray,
    gamma: float,
    terminals: np.ndarray,
) -> np.ndarray:
    """Верните T_* V; терминальные компоненты равны нулю."""
    raise NotImplementedError

Важно: функция выполняет один синхронный проход, а не обновляет состояния последовательно на месте.

Минимальная проверка одного синхронного прохода:

Python
P = np.zeros((3, 2, 3))
R = np.zeros_like(P)
P[0, 0, 1] = 1.0
P[0, 1, 2] = 1.0
R[0, 0, 1] = 1.0
R[0, 1, 2] = 2.0
P[1, :, 1] = 1.0
P[2, :, 2] = 1.0
V = np.array([0.0, 3.0, 1.0])
out = bellman_optimality_sweep(P, R, V, 0.9, np.zeros(3, dtype=bool))
assert abs(out[0] - 3.7) < 1e-12
Показать ответ и решение
Python
import numpy as np


def bellman_optimality_sweep(
    transitions: np.ndarray,
    rewards: np.ndarray,
    values: np.ndarray,
    gamma: float,
    terminals: np.ndarray,
) -> np.ndarray:
    p = np.asarray(transitions, dtype=float)
    r = np.asarray(rewards, dtype=float)
    v = np.asarray(values, dtype=float)
    terminal = np.asarray(terminals, dtype=bool)

    if p.ndim != 3 or r.shape != p.shape:
        raise ValueError("P and R must have shape (nS, nA, nS)")
    if v.shape != (p.shape[0],) or terminal.shape != v.shape:
        raise ValueError("values or terminals has the wrong shape")

    continuation = v.copy()
    continuation[terminal] = 0.0
    q = np.sum(
        p * (r + gamma * continuation[None, None, :]),
        axis=2,
    )
    updated = np.max(q, axis=1)
    updated[terminal] = 0.0
    return updated

Синхронность обеспечивается тем, что все Q(s,a)Q(s,a) строятся из одного неизменяемого continuation. Полная итерация ценности повторяет эту функцию до выполнения критерия остановки; такая реализация есть в ноутбуке.

Цели TD с явной терминальностью

Реализуйте одношаговую и nn-шаговую цели. Внешнее truncated=True само по себе не должно обнулять продолжение.

Python
def td_target(
    reward: float,
    next_value: float,
    gamma: float,
    *,
    terminated: bool,
    truncated: bool = False,
) -> float:
    raise NotImplementedError


def n_step_target(
    rewards: list[float],
    bootstrap_value: float,
    gamma: float,
    *,
    terminated_at_end: bool,
) -> float:
    raise NotImplementedError

Проверки:

Python
assert td_target(1, 2, 0.9, terminated=False) == 2.8
assert td_target(1, 2, 0.9, terminated=True) == 1.0
assert td_target(1, 2, 0.9, terminated=False, truncated=True) == 2.8
assert abs(n_step_target([1, 2, 3], 4, 0.5, terminated_at_end=False) - 3.25) < 1e-12
assert abs(n_step_target([1, 2, 3], 4, 0.5, terminated_at_end=True) - 2.75) < 1e-12
Показать ответ и решение
Python
import math


def td_target(
    reward: float,
    next_value: float,
    gamma: float,
    *,
    terminated: bool,
    truncated: bool = False,
) -> float:
    if not math.isfinite(reward) or not math.isfinite(gamma):
        raise ValueError("reward and gamma must be finite")
    if not 0.0 <= gamma <= 1.0:
        raise ValueError("gamma must be in [0, 1]")
    if not isinstance(terminated, bool) or not isinstance(truncated, bool):
        raise TypeError("flags must be boolean")
    if terminated:
        return float(reward)
    if not math.isfinite(next_value):
        raise ValueError("next_value must be finite for a non-terminal target")
    return float(reward + gamma * next_value)


def n_step_target(
    rewards: list[float],
    bootstrap_value: float,
    gamma: float,
    *,
    terminated_at_end: bool,
) -> float:
    if not math.isfinite(gamma) or not 0.0 <= gamma <= 1.0:
        raise ValueError("gamma must be finite and in [0, 1]")
    if not isinstance(terminated_at_end, bool):
        raise TypeError("terminated_at_end must be boolean")
    total = 0.0
    discount = 1.0
    for reward in rewards:
        reward = float(reward)
        if not math.isfinite(reward):
            raise ValueError("rewards must be finite")
        total += discount * reward
        discount *= gamma
    if terminated_at_end:
        return total
    if not math.isfinite(bootstrap_value):
        raise ValueError("bootstrap_value must be finite for a non-terminal target")
    return total + discount * float(bootstrap_value)

Параметр truncated остаётся в интерфейсе, чтобы различие причин окончания было явным. Математическую маску задаёт terminated. Если лимит является частью задачи, вызывающий код должен передать terminated=True и включить оставшийся бюджет в состояние.

Цели SARSA и Q-learning

Реализуйте две функции для уже известного вектора Q(s′,⋅)Q(s',\cdot).

Python
def q_learning_target(
    reward: float,
    next_q_values: np.ndarray,
    gamma: float,
    *,
    terminated: bool,
) -> float:
    raise NotImplementedError


def sarsa_target(
    reward: float,
    next_q_values: np.ndarray,
    next_action: int,
    gamma: float,
    *,
    terminated: bool,
) -> float:
    raise NotImplementedError

Терминальный переход не должен обращаться к продолжению. Для нетерминального перехода Q-learning использует максимум, а SARSA — значение фактически выбранного next_action.

Минимальная проверка:

Python
q_next = np.array([1.0, 4.0, 3.0])
assert q_learning_target(0.5, q_next, 0.9, terminated=False) == 4.1
assert sarsa_target(0.5, q_next, 2, 0.9, terminated=False) == 3.2
assert q_learning_target(1.0, np.array([]), 0.9, terminated=True) == 1.0
assert sarsa_target(1.0, np.array([]), 99, 0.9, terminated=True) == 1.0
Показать ответ и решение
Python
import numpy as np


def q_learning_target(
    reward: float,
    next_q_values: np.ndarray,
    gamma: float,
    *,
    terminated: bool,
) -> float:
    if terminated:
        return float(reward)
    q_next = np.asarray(next_q_values, dtype=float)
    if q_next.ndim != 1 or q_next.size == 0:
        raise ValueError("next_q_values must be a non-empty vector")
    return float(reward + gamma * np.max(q_next))


def sarsa_target(
    reward: float,
    next_q_values: np.ndarray,
    next_action: int,
    gamma: float,
    *,
    terminated: bool,
) -> float:
    if terminated:
        return float(reward)
    q_next = np.asarray(next_q_values, dtype=float)
    if q_next.ndim != 1 or not 0 <= next_action < q_next.size:
        raise ValueError("invalid next-action value")
    return float(reward + gamma * q_next[next_action])

Различие действительно помещается в одну строку кода, но эта строка задаёт разные целевые политики. В полноценной SARSA-реализации next_action должен быть выбран той же политикой поведения, которая будет действовать дальше.

Выбор и оценивание в Double Q-learning

Реализуйте Double Q-цель: одна таблица выбирает индекс действия, другая оценивает выбранный индекс.

Python
def double_q_target(
    reward: float,
    selector_q: np.ndarray,
    evaluator_q: np.ndarray,
    gamma: float,
    *,
    terminated: bool,
) -> float:
    raise NotImplementedError

Проверка:

Python
qa = np.array([1., 4., 3.])
qb = np.array([10., 2., 8.])
assert double_q_target(0.5, qa, qb, 0.9, terminated=False) == 2.3
assert double_q_target(0.5, qa, qb, 0.9, terminated=True) == 0.5
Показать ответ и решение
Python
import numpy as np


def double_q_target(
    reward: float,
    selector_q: np.ndarray,
    evaluator_q: np.ndarray,
    gamma: float,
    *,
    terminated: bool,
) -> float:
    if terminated:
        return float(reward)
    selector = np.asarray(selector_q, dtype=float)
    evaluator = np.asarray(evaluator_q, dtype=float)
    if selector.ndim != 1 or evaluator.shape != selector.shape:
        raise ValueError("selector_q and evaluator_q must be equal-length vectors")
    if selector.size == 0:
        raise ValueError("Q vectors must be non-empty")
    selected = int(np.argmax(selector))
    return float(reward + gamma * evaluator[selected])

Важно не брать np.max(evaluator): тогда вторая таблица снова выбирала бы собственное действие. Double Q-learning требует оценить именно индекс, выбранный первой таблицей.