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

Модуль 3. Бандиты, исследование и одношаговые решения

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

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

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

Бандит или последовательная задача

Для каждого случая выберите наиболее точную модель: обычный бандит, контекстный бандит или марковский процесс принятия решений (Markov decision process, MDP).

  1. Сервис на каждом независимом запросе выбирает одну из трёх моделей; известны признаки запроса, после ответа приходит скалярная оценка, а выбор не влияет на будущие запросы.

  2. Агент открывает страницы сайта, извлекает сведения и решает, какую ссылку открыть дальше.

  3. Для одинаковой аудитории поочерёдно показываются два неизменных баннера; регистрируется только клик по показанному баннеру.

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

Для каждого ответа укажите одно предположение, нарушение которого изменило бы выбранную модель.

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

  2. Марковский процесс принятия решений (Markov decision process, MDP). Открытая страница меняет доступные ссылки и сведения, то есть действие создаёт следующее состояние.

  3. Обычный бандит. Контекст считается одинаковым, награда наблюдается только для показанного баннера. Если аудитория или привлекательность баннеров меняются со временем, появляется нестационарность или контекст.

  4. MDP либо частично наблюдаемая последовательная задача. Реплика меняет будущий диалог. Бандитная модель была бы допустима только при оценке одной законченной реплики без продолжения.

Главный тест: зависит ли ценность действия от управляемого будущего состояния? Если да, одного раунда недостаточно.

Что измеряет регрет

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

  1. Псевдорегрет конкретной траектории можно вычислить без знания истинных средних наград.

  2. Разложение RT=∑aΔaE[Na(T)]R_T=\sum_a\Delta_a\mathbb{E}[N_a(T)] показывает, что неоптимальное действие с малым разрывом может выбираться чаще без большой мгновенной цены.

  3. Сублинейный регрет означает RT/T→0R_T/T\to0.

  4. Логарифмический регрет гарантирует, что алгоритм превосходит все другие алгоритмы на каждом конечном горизонте.

  5. Реализованная накопленная награда случайна даже при фиксированной последовательности действий, если награды шумные.

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

Корректны 2, 3 и 5.

  1. Неверно: для псевдорегрета нужны истинные средние μa\mu_a или их знание симулятором. В реальном неизвестном бандите они недоступны напрямую.

  2. Верно: каждый выбор действия aa стоит Δa\Delta_a; малый разрыв означает небольшую цену одного ошибочного выбора.

  3. Верно: это определение сублинейного роста.

  4. Неверно: асимптотический порядок не задаёт универсальный рейтинг на конечном горизонте; важны константы и экземпляр.

  5. Верно: при случайных наградах одна и та же последовательность действий может давать разные суммы.

Постоянное ε и оптимистичная инициализация

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

  1. При постоянном ε>0\varepsilon>0 ε-жадная стратегия продолжает с положительной частотой выбирать неоптимальные действия.

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

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

  4. Чистая жадность всегда имеет линейный регрет на каждом запуске.

  5. Убывающее εt\varepsilon_t автоматически даёт оптимальный регрет независимо от скорости убывания.

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

Корректны 1 и 3.

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

Оптимистичная инициализация не является универсальной гарантией, чистая жадность не обязана провалиться на каждом конкретном запуске, а слишком быстро убывающее εt\varepsilon_t может прекратить исследование до обнаружения лучшего действия.

Область действия классической гарантии

Метод верхней доверительной границы (upper confidence bound, UCB) применяется к стационарному бандиту; UCB1 — его классический вариант.

Выберите три условия или оговорки, необходимые для корректного чтения гарантии UCB1.

  1. Награды ограничены, в стандартной теореме — интервалом [0,1][0,1].

  2. Средние наград не меняются со временем.

  3. Логарифмическая оценка не зависит от разрывов Δa\Delta_a.

  4. Конечный регрет может быть заметным, если два действия почти одинаковы.

  5. Любой численный показатель неопределённости нейросети является доверительной границей UCB.

  6. Множество действий может произвольно меняться по ходу обучения, а классическое доказательство UCB1 остаётся без изменений.

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

Метод верхней доверительной границы (upper confidence bound, UCB) и его классический вариант UCB1 требуют читать теорему вместе с предположениями. Корректны 1, 2 и 4.

  • Классический UCB1 использует концентрацию для наград в [0,1][0,1].
  • Средние должны оставаться стационарными.
  • Оценка зависит от 1/Δa1/\Delta_a: почти равные действия различить трудно, поэтому конечный регрет может быть велик.

Пункт 3 неверен именно из-за зависимости от разрывов. Пункт 5 смешивает теоретическую доверительную границу с произвольным показателем неопределённости. Пункт 6 меняет саму постановку: при меняющемся множестве действий классическое доказательство нельзя переносить без новой проверки.

Что делает сэмплирование Томпсона

Для бернуллиевского действия задано априорное распределение Beta⁡(1,1)\operatorname{Beta}(1,1). После 8 успехов и 2 неудач выберите три корректных утверждения.

  1. Апостериорное распределение равно Beta⁡(9,3)\operatorname{Beta}(9,3).

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

  3. На каждом раунде сэмплируется одно правдоподобное среднее для каждого действия.

  4. Слабо изученное действие может быть выбрано из-за широкого апостериорного распределения.

  5. Дата публикации идеи в 1933 году сама доказывает современную логарифмическую гарантию.

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

Корректны 1, 3 и 4.

С априорным Beta⁡(1,1)\operatorname{Beta}(1,1) восемь успехов и две неудачи дают

Beta⁡(1+8,1+2)=Beta⁡(9,3).\operatorname{Beta}(1+8,1+2)=\operatorname{Beta}(9,3).

Алгоритм сэмплирует правдоподобное среднее каждого действия и выбирает максимальный сэмпл. Поэтому широкое распределение малоизученного действия иногда приводит к его выбору. Это не жадность по апостериорному среднему. Историческая дата идеи не заменяет отдельный анализ регрета.

LLM как контекстный бандит

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

  1. Если запрос считается контекстом, полный ответ — одним действием, а после скалярной награды эпизод заканчивается, внешняя постановка является контекстным бандитом.

  2. Из одношаговой абстракции с наградой за полный ответ следует, что токены перестают быть последовательными действиями.

  3. Для многоходового агента с инструментами обычно нужно состояние, содержащее последствия предыдущих действий.

  4. Выбор между несколькими моделями для независимых запросов может быть контекстным бандитом.

  5. Любая процедура Best-of-N автоматически является многоруким бандитом.

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

Корректны 1, 3 и 4.

Внешняя постановка с наградой за полный ответ является контекстным бандитом, если после награды раунд заканчивается. Внутри ответа остаётся токеновая последовательность. Многоходовый агент меняет состояние инструментов и диалога, поэтому требует последовательной модели. Выбор маршрута или модели для независимых запросов — естественный контекстный бандит.

Best-of-N с фиксированной политикой — повторное сэмплирование и отбор, а не автоматически многорукий бандит.

Перекрытие при оценивании по журналу

Оценивание с обратной вероятностью выбора (inverse propensity scoring, IPS) используется для новой политики π\pi по журналу поведенческой политики μ\mu. Его самонормированная версия — self-normalized inverse propensity scoring (SNIPS), а оцениватель, объединяющий модель награды и веса вероятностей, — оценивателем с двойной устойчивостью (doubly robust estimator, DR).

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

  1. Для всех пар (x,a)(x,a) с π(a∣x)>0\pi(a\mid x)>0 требуется μ(a∣x)>0\mu(a\mid x)>0.

  2. Малые вероятности поведения могут создавать большие веса и высокую дисперсию.

  3. Если вероятности поведения не были сохранены, их всегда можно точно восстановить из выбранных действий.

  4. SNIPS-оценка в общем случае имеет смещение.

  5. DR создаёт наблюдаемую награду для действия, которое никогда не выбиралось и не моделировалось.

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

Оценивание с обратной вероятностью выбора (inverse propensity scoring, IPS), его самонормированная версия self-normalized inverse propensity scoring (SNIPS) и оцениватель с двойной устойчивостью (doubly robust estimator, DR) требуют явного учёта того, как журнал был собран. Корректны 1, 2 и 4.

IPS может оценить целевое действие только там, где поведенческая политика давала ему положительную вероятность. Вес π/μ\pi/\mu резко растёт при малой μ\mu, увеличивая дисперсию. SNIPS уменьшает чувствительность к общему масштабу весов, но в общем случае вносит смещение.

По одному выбранному действию невозможно точно восстановить вероятность, с которой политика случайно выбрала его. DR тоже не создаёт фактическую контрфактическую награду без перекрытия; вне области покрытия остаётся экстраполяция модели.

Температура, Best-of-N и энтропия

В пункте 6 используется групповая относительная оптимизация политики (group relative policy optimization, GRPO). Выберите четыре корректных утверждения.

  1. Повышение температуры чистого softmax при фиксированных логитах делает распределение более плоским.

  2. Температурная генерация фиксированной LLM сама по себе обновляет политику по награде.

  3. Для независимых кандидатов с одной вероятностью успеха pp формула 1−(1−p)N1-(1-p)^N задаёт вероятность наличия хотя бы одного успеха в пуле.

  4. Комбинаторный оцениватель pass@kk по nn кандидатам и cc успехам равен 1−(n−ck)/(nk)1-\binom{n-c}{k}/\binom{n}{k}.

  5. Снижение энтропии всегда означает ухудшение политики.

  6. Если все награды в группе GRPO одинаковы, после вычитания группового среднего относительный сигнал равен нулю.

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

Корректны 1, 3, 4 и 6.

Температура меняет форму фиксированного softmax-распределения, но не обновляет параметры по награде. Формула покрытия требует независимости и одинаковой вероятности успеха pp при фиксированном запросе. Идеальный выбор нужен лишь для отождествления покрытия с точностью возвращённого ответа; наличие успеха в пуле само по себе этого не гарантирует. Комбинаторная формула оценивает pass@kk по конечной выборке. Снижение энтропии может быть нормальным следствием уверенного улучшения; оно становится проблемой только в определённом динамическом контексте. Одинаковые групповые награды дают нулевые центрированные преимущества.

Инкрементальное среднее

Действие выбиралось 4 раза. Его текущая оценка равна Q4=0.35Q_4=0.35. Пятая награда равна 11.

  1. Вычислите Q5Q_5 инкрементально.

  2. Найдите сумму первых четырёх наград, совместимую с Q4Q_4.

  3. Проверьте результат прямым средним по пяти наблюдениям.

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

Инкрементальное обновление:

Q5=0.35+15(1−0.35)=0.35+0.13=0.48.Q_5 =0.35+\frac{1}{5}(1-0.35) =0.35+0.13 =0.48.

Сумма первых четырёх наград:

S4=4Q4=1.4.S_4=4Q_4=1.4.

Прямая проверка:

S4+15=2.45=0.48.\frac{S_4+1}{5} = \frac{2.4}{5} =0.48.

Разложение регрета

Средние трёх действий равны

μ=(0.2, 0.5, 0.7).\mu=(0.2,\ 0.5,\ 0.7).

За T=100T=100 раундов ожидаемые числа выборов равны

E[N(T)]=(10, 30, 60).\mathbb{E}[N(T)]=(10,\ 30,\ 60).
  1. Найдите μ∗\mu^* и разрывы Δa\Delta_a.

  2. Вычислите ожидаемый регрет.

  3. Как изменится регрет, если 10 выборов второго действия заменить 10 выборами третьего?

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

Лучшее среднее:

μ∗=0.7.\mu^*=0.7.

Разрывы:

Δ=(0.5, 0.2, 0).\Delta=(0.5,\ 0.2,\ 0).

Регрет:

RT=0.5⋅10+0.2⋅30+0⋅60=5+6=11.R_T =0.5\cdot10+0.2\cdot30+0\cdot60 =5+6 =11.

Если десять выборов второго действия заменить десятью выборами третьего, регрет уменьшится на

10⋅(0.2−0)=2,10\cdot(0.2-0)=2,

то есть станет равен 99.

Один индекс UCB1

На раунде t=101t=101 два действия имеют оценки и числа выборов:

μ^1=0.55,N1=80,\widehat\mu_1=0.55,\quad N_1=80, μ^2=0.48,N2=20.\widehat\mu_2=0.48,\quad N_2=20.

Для индекса

Ia=μ^a+2log⁡tNaI_a=\widehat\mu_a+\sqrt{\frac{2\log t}{N_a}}

вычислите I1I_1 и I2I_2 с точностью до 0.0010.001 и укажите выбранное действие. Объясните, почему действие с меньшим средним может победить.

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

При t=101t=101:

I1=0.55+2log⁡10180≈0.890,I_1 =0.55+\sqrt{\frac{2\log101}{80}} \approx0.890, I2=0.48+2log⁡10120≈1.159.I_2 =0.48+\sqrt{\frac{2\log101}{20}} \approx1.159.

Выбирается действие 2. Его среднее меньше, но оно исследовано в четыре раза слабее, поэтому бонус неопределённости значительно больше.

Этот выбор не утверждает, что действие 2 действительно лучше; он утверждает, что при текущих данных оптимистичная правдоподобная оценка для него выше.

Апостериорное бета-распределение

Для двух бернуллиевских действий используется априорное распределение Beta⁡(1,1)\operatorname{Beta}(1,1).

  • действие 1: 12 успехов и 8 неудач;
  • действие 2: 3 успеха и 1 неудача.
  1. Запишите оба апостериорных распределения.

  2. Вычислите их средние.

  3. Какое действие имеет большее апостериорное среднее?

  4. Почему сэмплирование Томпсона всё же может выбрать другое действие?

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

Для действия 1:

μ1∣D∼Beta⁡(1+12,1+8)=Beta⁡(13,9).\mu_1\mid\mathcal D \sim \operatorname{Beta}(1+12,1+8) = \operatorname{Beta}(13,9).

Среднее:

E[μ1∣D]=1322≈0.591.\mathbb E[\mu_1\mid\mathcal D] =\frac{13}{22} \approx0.591.

Для действия 2:

μ2∣D∼Beta⁡(4,2),\mu_2\mid\mathcal D \sim \operatorname{Beta}(4,2), E[μ2∣D]=46≈0.667.\mathbb E[\mu_2\mid\mathcal D] =\frac{4}{6} \approx0.667.

Большее апостериорное среднее у действия 2. Но его распределение основано только на четырёх наблюдениях и шире. На отдельном раунде сэмпл из Beta⁡(13,9)\operatorname{Beta}(13,9) может оказаться выше сэмпла из Beta⁡(4,2)\operatorname{Beta}(4,2), поэтому Томпсон иногда выберет действие 1.

IPS, SNIPS и DR на трёх строках

Журнал содержит:

iirir_iμ(ai∣xi)\mu(a_i\mid x_i)π(ai∣xi)\pi(a_i\mid x_i)r^(xi,ai)\widehat r(x_i,a_i)∑aπ(a∣xi)r^(xi,a)\sum_a\pi(a\mid x_i)\widehat r(x_i,a)
110.501.000.600.70
200.250.500.300.40
310.800.400.700.65
  1. Вычислите веса wi=π/μw_i=\pi/\mu.

  2. Вычислите V^IPS=n−1∑iwiri\widehat V_{\mathrm{IPS}}=n^{-1}\sum_iw_ir_i.

  3. Вычислите V^SNIPS=∑iwiri/∑iwi\widehat V_{\mathrm{SNIPS}}=\sum_iw_ir_i/\sum_iw_i.

  4. Вычислите DR-оценку.

  5. Укажите, какая строка сильнее всего влияет на дисперсию IPS и почему.

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

Веса:

w=(1/0.5, 0.5/0.25, 0.4/0.8)=(2, 2, 0.5).w=(1/0.5,\ 0.5/0.25,\ 0.4/0.8) =(2,\ 2,\ 0.5).

IPS:

V^IPS=2⋅1+2⋅0+0.5⋅13=2.53≈0.8333.\widehat V_{\mathrm{IPS}} =\frac{2\cdot1+2\cdot0+0.5\cdot1}{3} =\frac{2.5}{3} \approx0.8333.

SNIPS:

V^SNIPS=2.52+2+0.5=2.54.5≈0.5556.\widehat V_{\mathrm{SNIPS}} =\frac{2.5}{2+2+0.5} =\frac{2.5}{4.5} \approx0.5556.

Построчные DR-слагаемые:

0.70+2(1−0.60)=1.50,0.70+2(1-0.60)=1.50, 0.40+2(0−0.30)=−0.20,0.40+2(0-0.30)=-0.20, 0.65+0.5(1−0.70)=0.80.0.65+0.5(1-0.70)=0.80.

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

V^DR=1.50−0.20+0.803=0.70.\widehat V_{\mathrm{DR}} =\frac{1.50-0.20+0.80}{3} =0.70.

Строки 1 и 2 имеют одинаково большие веса 2 и сильнее влияют на дисперсию, чем строка 3. В общем случае особенно опасна строка с наименьшей вероятностью поведения при ненулевой вероятности целевой политики.

Энтропийная политика, Best-of-N и pass@k

Оценки двух действий равны q=(0,1)q=(0,1), температура τ=0.5\tau=0.5.

  1. Вычислите оптимальную энтропийно-регуляризованную политику π∗(a)∝exp⁡(q(a)/τ)\pi^*(a)\propto\exp(q(a)/\tau).

  2. Если вероятность успеха одного независимого кандидата p=0.2p=0.2, найдите вероятность хотя бы одного успеха при N=5N=5.

  3. Найдите минимальное NN, обеспечивающее вероятность не менее 0.90.9.

  4. Для n=10n=10, c=2c=2, k=3k=3 вычислите оцениватель pass@kk по конечной выборке.

  5. Объясните, почему ответы из пунктов 2 и 4 описывают разные статистические объекты.

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

При q=(0,1)q=(0,1) и τ=0.5\tau=0.5:

π∗(1)=e0e0+e2≈0.1192,\pi^*(1) =\frac{e^0}{e^0+e^2} \approx0.1192, π∗(2)=e21+e2≈0.8808.\pi^*(2) =\frac{e^2}{1+e^2} \approx0.8808.

Best-of-5:

1−(1−0.2)5=1−0.85=0.67232.1-(1-0.2)^5 =1-0.8^5 =0.67232.

Для 90%:

N≥log⁡0.1log⁡0.8≈10.32,N\ge\frac{\log0.1}{\log0.8}\approx10.32,

поэтому минимальное целое N=11N=11.

Конечновыборочный pass@3:

1−(83)(103)=1−56120=64120≈0.5333.1-\frac{\binom{8}{3}}{\binom{10}{3}} =1-\frac{56}{120} =\frac{64}{120} \approx0.5333.

Число 0.672320.67232 предполагает истинную вероятность p=0.2p=0.2 и независимые новые генерации. Число 0.53330.5333 вычислено из конечного набора из десяти уже полученных кандидатов с двумя успехами.

Псевдорегрет по действиям

Реализуйте функцию:

Python
import numpy as np

def pseudo_regret_curve(means: np.ndarray, actions: np.ndarray) -> np.ndarray:
    """Вернуть накопленный псевдорегрет после каждого действия."""
    ...

Требования:

  • means — одномерный массив средних;
  • actions — одномерный массив целых индексов;
  • неверный индекс вызывает ValueError;
  • результат имеет тип float и ту же длину, что actions.

Проверка:

Python
means = np.array([0.2, 0.5, 0.7])
actions = np.array([0, 2, 1])
# ожидается [0.5, 0.5, 0.7]

Открытые проверки:

Python
np.testing.assert_allclose(
    pseudo_regret_curve([0.2, 0.5, 0.7], [0, 2, 1]),
    [0.5, 0.5, 0.7],
)
Показать ответ и решение
Python
import numpy as np

def pseudo_regret_curve(means: np.ndarray, actions: np.ndarray) -> np.ndarray:
    means = np.asarray(means, dtype=float)
    actions = np.asarray(actions, dtype=int)
    if means.ndim != 1 or means.size < 2:
        raise ValueError("means must be one-dimensional")
    if actions.ndim != 1:
        raise ValueError("actions must be one-dimensional")
    if np.any((actions < 0) | (actions >= means.size)):
        raise ValueError("invalid action index")
    gaps = means.max() - means[actions]
    return np.cumsum(gaps, dtype=float)

Проверка:

Python
result = pseudo_regret_curve(
    np.array([0.2, 0.5, 0.7]),
    np.array([0, 2, 1]),
)
assert np.allclose(result, [0.5, 0.5, 0.7])

Псевдорегрет не убывает, потому что каждое приращение μ∗−μAt\mu^*-\mu_{A_t} неотрицательно.

ε-жадный бернуллиевский бандит

Реализуйте:

Python
import numpy as np

def run_epsilon_greedy(
    probabilities: np.ndarray,
    horizon: int,
    epsilon: float,
    seed: int,
) -> tuple[np.ndarray, np.ndarray]:
    """Вернуть массивы действий и наград."""
    ...

Правила:

  • награда выбранного действия — rng.random() < probabilities[action];
  • при жадной ничьей выбирайте случайно среди максимумов;
  • оценка обновляется выборочным средним;
  • используйте np.random.default_rng(seed);
  • на каждом раунде сначала решается вопрос исследования, затем генерируется награда.

Сравните ε=0\varepsilon=0, 0.050.05 и 0.20.2 на одном наборе вероятностей минимум по 100 независимым запускам. Постройте таблицу среднего псевдорегрета: вывод по одному начальному значению генератора случайных чисел недостаточен.

Открытые проверки:

Python
actions, rewards = run_epsilon_greedy([0.1, 0.4, 0.6], 40, 0.1, 17)
assert actions.shape == (40,)
assert rewards.shape == (40,)
assert np.all((rewards == 0.0) | (rewards == 1.0))
Показать ответ и решение
Python
import numpy as np

def run_epsilon_greedy(probabilities, horizon, epsilon, seed):
    p = np.asarray(probabilities, dtype=float)
    if p.ndim != 1 or p.size < 2 or np.any((p < 0) | (p > 1)):
        raise ValueError("invalid probabilities")
    if horizon <= 0 or not 0 <= epsilon <= 1:
        raise ValueError("invalid horizon or epsilon")

    rng = np.random.default_rng(seed)
    q = np.zeros(p.size)
    n = np.zeros(p.size, dtype=int)
    actions = np.empty(horizon, dtype=int)
    rewards = np.empty(horizon, dtype=float)

    for t in range(horizon):
        if rng.random() < epsilon:
            action = int(rng.integers(p.size))
        else:
            best = np.flatnonzero(np.isclose(q, q.max()))
            action = int(rng.choice(best))
        reward = float(rng.random() < p[action])
        q[action] += (reward - q[action]) / (n[action] + 1)
        n[action] += 1
        actions[t] = action
        rewards[t] = reward
    return actions, rewards

Для сравнения значений ε нужно усреднять результаты по множеству независимых начальных значений генератора случайных чисел. Один удачный или неудачный запуск жадной стратегии не показывает её ожидаемого поведения.

UCB1

Реализуйте UCB1 для наград в [0,1][0,1]:

Python
import numpy as np

def run_ucb1(
    probabilities: np.ndarray,
    horizon: int,
    seed: int,
) -> tuple[np.ndarray, np.ndarray]:
    ...

Условия:

  1. первые KK раундов выберите действия 0, 1, ..., K-1;

  2. затем используйте μ^a+2log⁡t/Na\widehat\mu_a+\sqrt{2\log t/N_a};

  3. обновляйте среднее инкрементально;

  4. верните действия и награды.

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

Открытые проверки:

Python
actions, rewards = run_ucb1([0.1, 0.4, 0.6], 3, 5)
assert np.array_equal(actions, [0, 1, 2])
assert rewards.shape == (3,)
Показать ответ и решение
Python
import numpy as np

def run_ucb1(probabilities, horizon, seed):
    p = np.asarray(probabilities, dtype=float)
    if p.ndim != 1 or p.size < 2 or np.any((p < 0) | (p > 1)):
        raise ValueError("invalid probabilities")
    if horizon <= 0:
        raise ValueError("horizon must be positive")

    rng = np.random.default_rng(seed)
    k = p.size
    q = np.zeros(k)
    n = np.zeros(k, dtype=int)
    actions = np.empty(horizon, dtype=int)
    rewards = np.empty(horizon, dtype=float)

    for t in range(horizon):
        if t < k:
            action = t
        else:
            index = q + np.sqrt(2 * np.log(t + 1) / n)
            best = np.flatnonzero(np.isclose(index, index.max()))
            action = int(rng.choice(best))
        reward = float(rng.random() < p[action])
        q[action] += (reward - q[action]) / (n[action] + 1)
        n[action] += 1
        actions[t] = action
        rewards[t] = reward
    return actions, rewards

Проверки:

Python
means = np.array([0.1, 0.4, 0.6])
actions, rewards = run_ucb1(means, 100, 23)
assert np.array_equal(actions[:3], [0, 1, 2])
regret = np.cumsum(means.max() - means[actions])
assert np.all(np.diff(regret) >= -1e-12)

Сэмплирование Томпсона

Реализуйте сэмплирование Томпсона с бета-бернуллиевской моделью награды:

Python
import numpy as np

def run_thompson(
    probabilities: np.ndarray,
    horizon: int,
    seed: int,
    prior_alpha: float = 1.0,
    prior_beta: float = 1.0,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """Вернуть действия, награды и итоговые alpha/beta."""
    ...

После каждого успеха увеличивайте alpha[action], после неудачи — beta[action]. Сравните с UCB1 на нескольких экземплярах, включая случай с близкими средними. В отчёте отделите наблюдение вашего эксперимента от общей гарантии.

Открытые проверки:

Python
actions, rewards, alpha, beta = run_thompson(
    [0.1, 0.4, 0.6], 10, 23
)
assert actions.shape == rewards.shape == (10,)
assert alpha.shape == beta.shape == (3,)
assert np.isclose((alpha + beta).sum(), 16.0)
Показать ответ и решение
Python
import numpy as np

def run_thompson(
    probabilities,
    horizon,
    seed,
    prior_alpha=1.0,
    prior_beta=1.0,
):
    p = np.asarray(probabilities, dtype=float)
    if p.ndim != 1 or p.size < 2 or np.any((p < 0) | (p > 1)):
        raise ValueError("invalid probabilities")
    if horizon <= 0 or prior_alpha <= 0 or prior_beta <= 0:
        raise ValueError("invalid parameters")

    rng = np.random.default_rng(seed)
    alpha = np.full(p.size, float(prior_alpha))
    beta = np.full(p.size, float(prior_beta))
    actions = np.empty(horizon, dtype=int)
    rewards = np.empty(horizon, dtype=float)

    for t in range(horizon):
        samples = rng.beta(alpha, beta)
        action = int(np.argmax(samples))
        reward = float(rng.random() < p[action])
        alpha[action] += reward
        beta[action] += 1.0 - reward
        actions[t] = action
        rewards[t] = reward
    return actions, rewards, alpha, beta

В отчёте следует писать, например: «на 200 независимых запусках данного бернуллиевского стенда сэмплирование Томпсона дало меньший средний псевдорегрет к T=2000T=2000». Формулировка «сэмплирование Томпсона всегда лучше UCB» из такого опыта не следует.

IPS, SNIPS и двойная устойчивость

Реализуйте три функции:

Python
def ips_value(rewards, behavior_probs, target_probs) -> float:
    ...

def snips_value(rewards, behavior_probs, target_probs) -> float:
    ...

def doubly_robust_value(
    rewards,
    behavior_probs,
    target_probs,
    predictions_logged,
    predictions_target,
) -> float:
    ...

predictions_target[i] уже содержит ∑aπ(a∣xi)r^(xi,a)\sum_a\pi(a\mid x_i)\widehat r(x_i,a). Выдавайте ValueError, если вероятность поведения неположительна. Добавьте эксперимент, где одна вероятность уменьшается от 0.50.5 до 0.010.01, и покажите, как меняется IPS-вес.

Открытые проверки:

Python
actual = ips_value(
    [1.0, 0.0, 1.0],
    [0.5, 0.25, 0.5],
    [1.0, 0.0, 0.5],
)
assert np.isclose(actual, 1.0)
Показать ответ и решение
Python
import numpy as np

def _arrays(rewards, behavior_probs, target_probs):
    r = np.asarray(rewards, dtype=float)
    p = np.asarray(behavior_probs, dtype=float)
    q = np.asarray(target_probs, dtype=float)
    if r.ndim != 1 or p.shape != r.shape or q.shape != r.shape or r.size == 0:
        raise ValueError("equal non-empty vectors required")
    if np.any(p <= 0) or np.any(p > 1) or np.any((q < 0) | (q > 1)):
        raise ValueError("invalid probabilities")
    return r, p, q

def ips_value(rewards, behavior_probs, target_probs):
    r, p, q = _arrays(rewards, behavior_probs, target_probs)
    return float(np.mean(q * r / p))

def snips_value(rewards, behavior_probs, target_probs):
    r, p, q = _arrays(rewards, behavior_probs, target_probs)
    w = q / p
    if w.sum() <= 0:
        raise ValueError("zero total target weight")
    return float(np.dot(w, r) / w.sum())

def doubly_robust_value(
    rewards,
    behavior_probs,
    target_probs,
    predictions_logged,
    predictions_target,
):
    r, p, q = _arrays(rewards, behavior_probs, target_probs)
    logged = np.asarray(predictions_logged, dtype=float)
    target = np.asarray(predictions_target, dtype=float)
    if logged.shape != r.shape or target.shape != r.shape:
        raise ValueError("prediction shapes must match")
    return float(np.mean(target + q / p * (r - logged)))

Если π(a∣x)=0.5\pi(a\mid x)=0.5, то при μ=0.5\mu=0.5 вес равен 1, а при μ=0.01\mu=0.01 — уже 50. Одно наблюдение с таким весом способно доминировать в оценке; это и есть практическая цена слабого перекрытия.

Энтропия, Best-of-N и pass@k

Реализуйте:

Python
import numpy as np

def stable_softmax(logits, temperature=1.0) -> np.ndarray:
    ...

def categorical_entropy(probabilities) -> float:
    ...

def best_of_n_success(p: float, n: int) -> float:
    ...

def pass_at_k(n: int, c: int, k: int) -> float:
    ...

Требования:

  • softmax устойчив к большим логитам;
  • энтропия измеряется в натах и использует соглашение 0log⁡0=00\log0=0;
  • Best-of-N использует устойчивые log1p/expm1 для малых pp и больших NN;
  • pass@kk реализуется как 1−(n−ck)/(nk)1-\binom{n-c}{k}/\binom{n}{k} либо эквивалентное устойчивое произведение;
  • недопустимые n,c,k,pn,c,k,p вызывают ValueError.

Проверьте, что для фиксированных логитов энтропия возрастает при температурах 0.50.5, 11 и 22, но не делайте из этого вывода о качестве или истинности ответа.

Открытые проверки:

Python
p = stable_softmax([1000.0, 1001.0])
assert np.isclose(p.sum(), 1.0)
assert np.all(p > 0.0)
Показать ответ и решение
Python
from math import comb
import numpy as np

def stable_softmax(logits, temperature=1.0):
    z = np.asarray(logits, dtype=float)
    if z.ndim != 1 or z.size == 0 or not np.all(np.isfinite(z)):
        raise ValueError("invalid logits")
    if temperature <= 0 or not np.isfinite(temperature):
        raise ValueError("temperature must be positive")
    scaled = z / temperature
    weights = np.exp(scaled - scaled.max())
    return weights / weights.sum()

def categorical_entropy(probabilities):
    p = np.asarray(probabilities, dtype=float)
    if p.ndim != 1 or np.any(p < 0) or not np.isclose(p.sum(), 1):
        raise ValueError("invalid probability vector")
    positive = p[p > 0]
    return float(-np.dot(positive, np.log(positive)))

def best_of_n_success(p, n):
    if not 0 <= p <= 1 or n < 1:
        raise ValueError("invalid p or n")
    if p == 1:
        return 1.0
    return float(-np.expm1(n * np.log1p(-p)))

def pass_at_k(n, c, k):
    if n <= 0 or c < 0 or c > n or k <= 0 or k > n:
        raise ValueError("invalid n, c, k")
    if n - c < k:
        return 1.0
    return float(1 - comb(n - c, k) / comb(n, k))

Проверка энтропии:

Python
logits = np.array([0.0, 1.0, 2.0])
entropies = [
    categorical_entropy(stable_softmax(logits, temperature=t))
    for t in (0.5, 1.0, 2.0)
]
assert entropies[0] < entropies[1] < entropies[2]

Это свойство чистого softmax при фиксированных логитах. Оно не утверждает, что более высокая температура улучшает качество, калибровку или истинность ответа.