Модуль 10. Поиск, проверка и вычислительный бюджет на этапе генерации
Сначала попробуйте решить упражнение. Ответы, подробные решения и проектные ориентиры раскрываются отдельно и сохраняют своё состояние при переключении RU/EN.
Скачать файлы курса (.zip) 382,1 КБАрхив содержит файлы на обоих языках и вспомогательные материалы с сохранением структуры каталогов.
Четыре вида вычислительного бюджета
Система делает следующее: для простых запросов генерирует один ответ, для трудных — восемь; каждый ответ может содержать до 2 000 токенов; для двух лучших префиксов выполняется дополнительное ветвление.
Разнесите элементы по четырём категориям: параллельный, последовательный, поисковый и адаптивный бюджет. Объясните, почему одно число «8 попыток» не описывает всю стоимость.
Показать ответ и решение
- Адаптивный бюджет: простому запросу выделяется один ответ, трудному — восемь. Число попыток зависит от запроса.
- Параллельный бюджет: для трудного запроса создаются восемь кандидатов.
- Последовательный бюджет: один кандидат может продолжаться до 2 000 токенов.
- Поисковый бюджет: два наиболее перспективных префикса ветвятся и получают дополнительные продолжения.
Число «8» описывает только ширину одного этапа. Оно не сообщает длину кандидатов, число раскрытых ветвей, стоимость оценивания, задержку, возможность пакетной генерации и число вызовов внешней проверяющей системы. Для воспроизводимого сравнения нужны хотя бы токены, прямые проходы генератора, вызовы проверки, задержка и денежная стоимость.
Покрытие и качество вывода
Для задачи pass@32 равен 0.98, но точность ответа, который выдаёт система после ранжирования 32 кандидатов, равна 0.61.
Что можно заключить о генераторе и ранжировщике? Почему нельзя назвать 0.98 «точностью системы»?
Показать ответ и решение
pass@32 = 0.98 означает, что в 98% запросов среди 32 кандидатов присутствует хотя бы один правильный ответ — при заданном критерии проверки и протоколе генерации. Итоговая точность 0.61 означает, что ранжировщик часто не выбирает доступный правильный кандидат.
Из этого следует:
-
генератор обладает высоким покрытием при данном бюджете;
-
этап выбора является существенным ограничением системы;
-
полезно измерить точность ранжировщика условно на событиях, где правильный кандидат вообще присутствует;
-
стоит проверить, не ухудшает ли ранжирование ответ по сравнению с более простыми правилами: голосованием, точной проверкой или случайным выбором среди прошедших фильтр.
Нельзя называть 0.98 точностью всей системы, потому что пользователь получает один выбранный ответ, а не множество из 32 кандидатов.
Повторяющиеся ответы и условная независимость
Формула предсказывает заметный рост покрытия, но двадцать ответов модели почти дословно повторяют один и тот же неверный шаблон.
Доказывает ли одно это наблюдение нарушение условной независимости? Назовите альтернативные объяснения и две диагностики разнообразия, которые полезно добавить к pass@N.
Показать ответ и решение
Нет. Независимые выборки из распределения с низкой энтропией могут повторять одну ошибку. Расхождение также объясняют малая вероятность успеха на данном запросе, неточная оценка или объединение запросов с разными . Переиспользование префиксов и ветвей поиска — возможные механизмы зависимости, которые нужно установить по протоколу. Сравните покрытие по повторным группам с условно независимым прогнозом для каждого запроса и его неопределённостью.
Полезные диагностики:
- число уникальных канонических финальных ответов;
- средняя попарная текстовая или семантическая близость;
- энтропия распределения канонических ответов;
- доля крупнейшего кластера;
- различие между фактической кривой pass@N и кривой, предсказанной по pass@1 в независимой модели.
Одна метрика разнообразия недостаточна: разные формулировки могут скрывать одну и ту же ошибку, а почти одинаковые рассуждения иногда приводят к разным итоговым ответам.
Когда самосогласованность ухудшает ответ
Правильный канонический ответ имеет вероятность 0.32, а одна типовая ошибка — 0.44. Остальная масса распределена между многими редкими ответами.
Что произойдёт с голосованием при росте числа кандидатов? Может ли pass@N при этом стремиться к единице?
Показать ответ и решение
При большом числе независимо сгенерированных кандидатов доли канонических ответов сходятся к их вероятностям. Типовая ошибка с вероятностью 0.44 является единственной модой, поэтому голосование по относительному большинству с вероятностью, стремящейся к единице, выберет именно её. Точность голосования стремится к нулю.
Одновременно pass@N может стремиться к единице, потому что правильный ответ имеет положительную вероятность 0.32:
Это точная демонстрация различия между наличием правильного кандидата и правилом выбора. Голосование помогает, когда правильный канонический ответ является наиболее вероятной категорией, а ошибки достаточно рассеяны.
Область действия предела двоичной проверки
В учебной модели система случайно выбирает среди принятых двоичным проверяющим правилом, поэтому при большом точность стремится к доле правильных среди принятых.
Почему этот предел нельзя переносить на ранжировщик с непрерывным баллом и выбором максимума?
Показать ответ и решение
В учебном протоколе каждый кандидат либо принят, либо отклонён. Если принятые кандидаты выбираются равновероятно, при большом вероятность отсутствия принятых стремится к нулю, а качество выбранного ответа стремится к доле правильных среди принятых.
Ранжировщик с непрерывным баллом реализует другой статистический объект: выбирается максимум из шумных оценок. При росте экстремальное значение может всё сильнее определяться положительной ошибкой оценивателя. Предел зависит от совместного распределения истинного качества и оценки, хвостов ошибок, числа кандидатов и правила разрешения ничьих. Формула для случайного выбора среди двоично принятых кандидатов к такому максимуму не относится.
Прокси растёт, качество падает
При увеличении BoN с 32 до 512 средняя оценка модели награды выросла, а независимая ручная оценка снизилась.
Приведите два возможных объяснения и три изменения протокола, которые помогут отличить реальное улучшение от переоптимизации прокси.
Показать ответ и решение
Возможные объяснения:
-
Переоптимизация прокси: большой пул увеличивает шанс найти ответ, использующий систематическую ошибку модели награды.
-
Сдвиг распределения: ответы-победители при находятся дальше от данных, на которых оцениватель был обучен и проверен.
-
Изменение длины или стиля: модель награды может предпочитать признаки, слабо связанные с полезностью.
-
Шум ручной оценки: падение может быть случайным, если независимая выборка мала.
Изменения протокола:
- заранее выделить независимый набор запросов и независимый оцениватель;
- строить кривые прокси и целевой метрики на одной сетке с доверительными интервалами;
- анализировать длину, стиль, домен, уверенность и расхождение ансамбля оценивателей;
- проводить слепую ручную проверку выбранных ответов и случайных кандидатов;
- задавать условие остановки по независимой метрике, а не по прокси;
- обновлять данные оценивателя ответами из нового распределения.
Рост прокси сам по себе доказывает лишь то, что система научилась находить кандидаты с более высокой прокси-оценкой.
Что именно оценивает PRM
Для каждого префикса запускают десять продолжений текущей политики и записывают долю финальных успехов. Затем политика продолжения меняется.
Является ли старая метка «истинной ценностью состояния»? Что именно она оценивала и почему могла устареть?
Показать ответ и решение
Метка оценивала эмпирическую вероятность финального успеха при продолжении конкретной версией политики, конкретном декодировании, горизонте и проверяющей системе:
Это оценка ценности относительно заданной политики продолжения, а не вечное свойство префикса и не «истинная ценность» вне постановки. После изменения политики распределение продолжений меняется; меняются и вероятность успеха, и ошибки Монте-Карло. Метка может устареть, особенно для состояний, в которых новая политика ведёт себя иначе.
Нужно хранить версию политики, параметры декодирования, число продолжений, проверяющую систему и дату генерации меток.
Сложный поиск против простой базы
MCTS с PRM улучшает pass@1, но использует в четыре раза больше токенов и в двадцать раз больше вызовов оценивателя, чем повторная генерация. Какие сравнения нужны перед выводом, что MCTS «лучше»?
Показать ответ и решение
Нужно сравнить методы как минимум при нескольких паритетах:
-
одинаковое число сгенерированных токенов;
-
одинаковое число прямых проходов генератора;
-
одинаковое число вызовов PRM или другой проверки;
-
одинаковая задержка или денежная стоимость;
-
одинаковый максимальный контекст и правила остановки.
Следует измерять pass@1, pass@k/покрытие, качество выбранного ответа, долю технических ошибок, токены, задержку и стоимость. Нужны также сильные простые контрольные варианты: повторная генерация, BoN, самосогласованность и последовательное исправление.
MCTS может быть предпочтительнее при заданном ограничении качества или задержки, но утверждение «лучше вообще» без выбранного бюджета и метрики не определено.
pass@N руками
Одна попытка успешна с вероятностью . Попытки условно независимы.
-
Вычислите pass@6.
-
Вычислите прирост от шестой попытки относительно пяти попыток.
-
Сформулируйте, что не следует из этих чисел без правила выбора.
Показать ответ и решение
При :
Прирост именно от шестой попытки:
То есть шестой кандидат добавляет примерно 4.06 процентного пункта покрытия. Без правила выбора из этого не следует, что система выдаёт правильный ответ с вероятностью 0.9246: это вероятность наличия хотя бы одного успеха в корзине.
Конечновыборочный pass@k
Для одного запроса сгенерировано кандидатов, из которых прошли точную проверку. Вычислите
при . Дайте ответ с четырьмя знаками после запятой.
Показать ответ и решение
С четырьмя знаками:
Здесь мысленно выбираются 10 элементов без возвращения из уже сгенерированной сотни. Оцениватель не доказывает независимость исходных 100 генераций и не заменяет описание протокола генерации.
Двоичная проверяющая система и два резервных протокола
Кандидат правильный с вероятностью . Проверяющая система имеет TPR и FPR . Если принят хотя бы один кандидат, выбирается случайный принятый.
Для вычислите:
-
вероятность принятия ;
-
долю правильных среди принятых ;
-
долю правильных среди отклонённых ;
-
итоговую вероятность успеха, если при пустом множестве принятых выбирают случайного кандидата из той же отклонённой корзины;
-
итоговую вероятность успеха, если вместо этого порождают свежего непроверенного кандидата;
-
предел при .
Объясните, почему два резервных правила нельзя обозначать одним и тем же слагаемым.
Показать ответ и решение
Вероятность принятия:
Точность среди принятых:
Условная доля правильных среди отклонённых:
Вероятность, что все шесть отклонены:
Если резерв выбирается из той же отклонённой корзины,
Если создаётся свежий непроверенный кандидат,
При вероятность пустого множества принятых стремится к нулю, поэтому оба протокола имеют один предел:
Различие при конечном принципиально: наблюдение «кандидат отклонён» меняет вероятность его правильности. Слагаемое допустимо только для новой независимой попытки, а не для выбора среди уже отклонённых.
Предел голосования
Сравните два распределения канонических ответов:
В каждом случае первый ответ правильный. Каков предел точности голосования по относительному большинству (plurality voting) при независимо сгенерированных кандидатах и отсутствии точной ничьей в пределе?
Показать ответ и решение
В первом распределении правильный ответ имеет вероятность 0.34, а ближайшая ошибка — 0.33. Правильный ответ является единственной модой. По закону больших чисел его эмпирическая доля в пределе превосходит доли остальных категорий, поэтому
Сходимость может быть медленной из-за малого разрыва 0.01.
Во втором распределении неправильный второй ответ имеет вероятность 0.40, превышающую 0.30 правильного. Поэтому
При этом pass@N для правильного ответа во втором случае всё равно стремится к единице, поскольку его вероятность положительна.
Четыре агрегации PRM
Для шагов получены оценки
Вычислите минимум, среднее, произведение и последний балл. Как изменится произведение, если к цепочке добавить ещё десять шагов с оценкой 0.9?
Показать ответ и решение
Для :
После добавления десяти шагов с оценкой 0.9 произведение станет
Хотя все добавленные шаги имеют высокий балл, произведение уменьшается. Это корректно только при вероятностной семантике совместного события; иначе получается неявный штраф за длину.
Адаптивный бюджет и окупаемость
Часть 1. Для трёх запросов известны вероятности успеха одной независимой попытки
Общий бюджет — 9 попыток, минимум одна на запрос. Последовательно назначайте каждую дополнительную попытку запросу с максимальным . Найдите итоговое распределение и среднее покрытие.
Часть 2. Дополнительное обучение стоит 5 000 условных единиц и уменьшает стоимость запроса с 0.08 до 0.03. Найдите точку окупаемости только по стоимости.
Показать ответ и решение
Начинаем с . Предельный прирост для запроса при текущем числе попыток равен
Последовательное жадное распределение шести дополнительных попыток даёт
Среднее покрытие:
Точка окупаемости по стоимости решает
Отсюда
Это расчёт только денежных затрат. В реальном решении нужно добавить качество, задержку, риск и стоимость внедрения.
Кривые pass@N
Реализуйте устойчивые функции pass_at_n и aggregate_pass_curve. Обрабатывайте , , пустой список и некорректные вероятности.
from __future__ import annotations
from collections.abc import Iterable, Sequence
import numpy as np
def pass_at_n(p: float, n: int) -> float:
"""Вероятность хотя бы одного успеха."""
raise NotImplementedError
def aggregate_pass_curve(ps: Sequence[float], budgets: Iterable[int]) -> np.ndarray:
"""Среднее покрытие по запросам для каждого бюджета."""
raise NotImplementedErrorПроверка:
assert abs(pass_at_n(0.5, 4) - 0.9375) < 1e-12
curve = aggregate_pass_curve([0.5, 0.2, 0.05, 0.9], [1, 2, 4])
assert np.allclose(curve, [0.4125, 0.549375, 0.6783234375])Показать ответ и решение
from __future__ import annotations
from collections.abc import Iterable, Sequence
import math
import numpy as np
def pass_at_n(p: float, n: int) -> float:
p = float(p)
if not math.isfinite(p) or not 0.0 <= p <= 1.0:
raise ValueError("p must lie in [0, 1]")
if isinstance(n, bool) or int(n) != n or int(n) <= 0:
raise ValueError("n must be a positive integer")
n = int(n)
if p == 0.0:
return 0.0
if p == 1.0:
return 1.0
return float(-np.expm1(n * np.log1p(-p)))
def aggregate_pass_curve(ps: Sequence[float], budgets: Iterable[int]) -> np.ndarray:
probabilities = np.asarray(ps, dtype=np.float64)
budgets = list(budgets)
if probabilities.ndim != 1 or probabilities.size == 0:
raise ValueError("ps must be non-empty")
if not np.isfinite(probabilities).all() or np.any((probabilities < 0) | (probabilities > 1)):
raise ValueError("every probability must lie in [0, 1]")
if not budgets:
raise ValueError("budgets must be non-empty")
return np.asarray(
[np.mean([pass_at_n(float(p), int(n)) for p in probabilities]) for n in budgets],
dtype=np.float64,
)log1p и expm1 уменьшают потерю точности, когда мал, а велик.
Голосование с честным разрешением ничьей
Реализуйте моделирование голосования по относительному большинству. При ничьей победитель выбирается равновероятно среди всех категорий с максимальным числом голосов. Правильной считается категория с индексом correct_index.
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
def plurality_accuracy_mc(
probabilities: Sequence[float],
k: int,
*,
correct_index: int = 0,
trials: int = 100_000,
seed: int = 10,
) -> float:
raise NotImplementedErrorПроверьте в пределах статистической погрешности два случая:
assert abs(plurality_accuracy_mc([0.4, 0.15, 0.15, 0.15, 0.15], 16) - 0.7942) < 0.01
assert abs(plurality_accuracy_mc([0.3, 0.45, 0.25], 64) - 0.0771) < 0.01Почему np.argmax без дополнительной обработки является не нейтральным правилом ничьей?
Показать ответ и решение
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
def plurality_accuracy_mc(
probabilities: Sequence[float],
k: int,
*,
correct_index: int = 0,
trials: int = 100_000,
seed: int = 10,
) -> float:
p = np.asarray(probabilities, dtype=np.float64)
if p.ndim != 1 or p.size < 2:
raise ValueError("at least two categories are required")
if not np.isfinite(p).all() or np.any(p < 0) or not np.isclose(p.sum(), 1.0):
raise ValueError("probabilities must form a distribution")
if isinstance(k, bool) or int(k) != k or int(k) <= 0:
raise ValueError("k must be a positive integer")
if isinstance(trials, bool) or int(trials) != trials or int(trials) <= 0:
raise ValueError("trials must be a positive integer")
if not 0 <= int(correct_index) < p.size:
raise ValueError("correct_index is out of range")
rng = np.random.default_rng(seed)
counts = rng.multinomial(int(k), p, size=int(trials))
correct = 0
for row in counts:
winners = np.flatnonzero(row == row.max())
selected = int(rng.choice(winners))
correct += selected == int(correct_index)
return correct / int(trials)np.argmax возвращает первый максимальный индекс. Если правильный ответ хранится под индексом 0, все ничьи искусственно повышают измеренную точность; при другом индексе — понижают. Это свойство представления массива, а не протокола голосования.
Конечный N и явное резервное правило
Реализуйте формулу двоичной проверяющей системы с двумя вариантами fallback_mode: "rejected_pool" и "fresh_unverified". Функция должна возвращать , точность среди принятых и отклонённых, вероятность отсутствия принятых и итоговую вероятность успеха.
from __future__ import annotations
def binary_verifier_success(
n: int,
p_correct: float,
true_positive_rate: float,
false_positive_rate: float,
*,
fallback_mode: str = "rejected_pool",
) -> dict[str, float]:
raise NotImplementedErrorДля (8, 0.2, 0.9, 0.1) ожидаются:
r = binary_verifier_success(8, 0.2, 0.9, 0.1, fallback_mode="rejected_pool")
assert abs(r["q"] - 0.26) < 1e-12
assert abs(r["accepted_precision"] - 9/13) < 1e-12
assert abs(r["rejected_precision"] - 0.02/0.74) < 1e-12
assert abs(r["success"] - 0.632486004809728) < 1e-12Защищённые тесты должны проверять граничные вероятности и ошибочное имя режима.
Показать ответ и решение
from __future__ import annotations
import math
def binary_verifier_success(
n: int,
p_correct: float,
true_positive_rate: float,
false_positive_rate: float,
*,
fallback_mode: str = "rejected_pool",
) -> dict[str, float]:
if isinstance(n, bool) or int(n) != n or int(n) <= 0:
raise ValueError("n must be a positive integer")
values = [float(p_correct), float(true_positive_rate), float(false_positive_rate)]
if any((not math.isfinite(v)) or not 0.0 <= v <= 1.0 for v in values):
raise ValueError("probabilities must lie in [0, 1]")
if fallback_mode not in {"rejected_pool", "fresh_unverified"}:
raise ValueError("unknown fallback mode")
p, tpr, fpr = values
q = p * tpr + (1.0 - p) * fpr
accepted_precision = p if q == 0.0 else p * tpr / q
rejected_precision = p if q == 1.0 else p * (1.0 - tpr) / (1.0 - q)
no_accept = (1.0 - q) ** int(n)
fallback = rejected_precision if fallback_mode == "rejected_pool" else p
success = (1.0 - no_accept) * accepted_precision + no_accept * fallback
return {
"q": q,
"accepted_precision": accepted_precision,
"rejected_precision": rejected_precision,
"no_accept": no_accept,
"fallback_success": fallback,
"success": success,
}Ветвь q == 1 нужна, потому что условное распределение отклонённых тогда не определено, но событие резервного выбора имеет нулевую вероятность. Возвращаемое условное значение не влияет на success.
Агрегации процессных оценок
Реализуйте min, mean, product, last и log_product. Проверьте диапазон и пустой вход.
from __future__ import annotations
from collections.abc import Sequence
def process_score(step_scores: Sequence[float], method: str) -> float:
raise NotImplementedErrorДля [0.95, 0.95, 0.30, 0.95] произведение должно быть 0.2572125.
Открытые проверки:
assert abs(process_score([0.95, 0.95, 0.30, 0.95], "mean") - 0.7875) < 1e-12
assert abs(process_score([0.95, 0.95, 0.30, 0.95], "product") - 0.2572125) < 1e-12Показать ответ и решение
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
def process_score(step_scores: Sequence[float], method: str) -> float:
values = np.asarray(step_scores, dtype=np.float64)
if values.ndim != 1 or values.size == 0:
raise ValueError("step_scores must be non-empty")
if not np.isfinite(values).all() or np.any((values < 0) | (values > 1)):
raise ValueError("step scores must lie in [0, 1]")
method = method.lower()
if method == "min":
return float(values.min())
if method == "mean":
return float(values.mean())
if method == "product":
return float(values.prod())
if method == "last":
return float(values[-1])
if method == "log_product":
if np.any(values == 0):
return float("-inf")
return float(np.log(values).sum())
raise ValueError("unknown aggregation method")Логарифм произведения численно устойчивее для длинных цепочек. Он не исправляет семантическую проблему: перемножать оценки можно осмысленно лишь при подходящей вероятностной интерпретации.
Переоптимизация прокси в BoN
Воспроизведите эксперимент:
где и независимы и стандартно нормальны. Для каждого бюджета выбирается кандидат с максимальным proxy.
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
def proxy_overoptimization_curve(
budgets: Sequence[int],
*,
trials: int = 10_000,
seed: int = 202_610,
) -> tuple[np.ndarray, np.ndarray]:
raise NotImplementedErrorПостройте обе кривые на сетке [1,2,4,8,16,32,64,128,256,512]. Сопоставьте рост выбранной прокси-оценки с поведением независимой цели: найдите наблюдаемый максимум и проверьте, сохраняется ли ухудшение при больших бюджетах для нескольких начальных состояний генератора (seed). Допустимы как общий пул кандидатов с вложенными префиксами, так и независимый пул для каждого бюджета; опишите выбранную схему. Точный бюджет максимума зависит от конечной выборки и порядка генерации случайных чисел и не является условием зачёта.
Открытые проверки:
budgets = [1, 2, 4, 8, 16, 32, 64]
proxy, goal = proxy_overoptimization_curve(budgets, trials=2000, seed=1)
assert proxy.shape == goal.shape == (len(budgets),)
assert np.isfinite(proxy).all() and np.isfinite(goal).all()
again = proxy_overoptimization_curve(budgets, trials=2000, seed=1)
assert np.array_equal(proxy, again[0]) and np.array_equal(goal, again[1])
other = proxy_overoptimization_curve(budgets, trials=2000, seed=2)
assert not np.array_equal(proxy, other[0])
assert not np.array_equal(goal, other[1])Показать ответ и решение
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
def proxy_overoptimization_curve(
budgets: Sequence[int],
*,
trials: int = 10_000,
seed: int = 202_610,
) -> tuple[np.ndarray, np.ndarray]:
budgets = np.asarray(budgets, dtype=np.int64)
if budgets.ndim != 1 or budgets.size == 0 or np.any(budgets <= 0):
raise ValueError("budgets must be positive integers")
if trials <= 0:
raise ValueError("trials must be positive")
rng = np.random.default_rng(seed)
maximum = int(budgets.max())
useful = rng.normal(size=(trials, maximum))
spurious = rng.normal(size=(trials, maximum))
proxy = useful + spurious
goal = useful - np.maximum(spurious - 1.0, 0.0) ** 2
rows = np.arange(trials)
proxy_means = []
goal_means = []
for n in budgets:
selected = np.argmax(proxy[:, : int(n)], axis=1)
proxy_means.append(proxy[rows, selected].mean())
goal_means.append(goal[rows, selected].mean())
return np.asarray(proxy_means), np.asarray(goal_means)Этот эталон использует общий пул кандидатов, поэтому выбранный максимум прокси не убывает при расширении префикса в каждом испытании. Для независимых пулов монотонность относится к ожиданию, а отдельные выборочные средние могут колебаться. В опубликованном протоколе этого эталона (trials=10000, seed=202610, указанная сетка) максимум независимой цели наблюдается при ; другой допустимый порядок выборки может дать иной максимум. Открытые проверки проверяют форму, конечность, воспроизводимость и учёт параметра seed. Формулу цели и выбор кандидата дополнительно сверяйте по нескольким вручную рассчитанным пулам; сами эти проверки не доказывают правильность алгоритма.
Жадное распределение бюджета
Реализуйте распределение следующей попытки по максимальному предельному приросту
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
def allocate_budget_greedy(
ps: Sequence[float],
total_samples: int,
*,
minimum_per_prompt: int = 1,
) -> np.ndarray:
raise NotImplementedErrorКонтроль:
allocation = allocate_budget_greedy([0.02, 0.08, 0.2, 0.5], 16)
assert np.array_equal(allocation, [1, 5, 6, 4])Объясните, почему оптимальность этого жадного правила относится к заданным независимым кривым с невозрастающими предельными приростами, а не к произвольной реальной LLM-системе.
Показать ответ и решение
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
def allocate_budget_greedy(
ps: Sequence[float],
total_samples: int,
*,
minimum_per_prompt: int = 1,
) -> np.ndarray:
p = np.asarray(ps, dtype=np.float64)
if p.ndim != 1 or p.size == 0:
raise ValueError("ps must be non-empty")
if not np.isfinite(p).all() or np.any((p < 0) | (p > 1)):
raise ValueError("probabilities must lie in [0, 1]")
if minimum_per_prompt < 0 or int(minimum_per_prompt) != minimum_per_prompt:
raise ValueError("minimum_per_prompt must be a non-negative integer")
minimum = int(minimum_per_prompt)
if total_samples < minimum * p.size:
raise ValueError("budget is smaller than the required minimum")
allocation = np.full(p.size, minimum, dtype=np.int64)
for _ in range(int(total_samples) - minimum * p.size):
gains = p * np.power(1.0 - p, allocation)
allocation[int(np.argmax(gains))] += 1
return allocationДля независимых кривых предельные приросты невозрастают, поэтому последовательный выбор крупнейшего доступного прироста оптимален для дискретного бюджета. Реальная LLM-система может нарушать предположения: вероятность успеха неизвестна, кандидаты коррелированы, стоимость попыток неодинакова, а правило выбора несовершенно.