Модуль 13. Оценивание RL-систем для LLM: статистика, взлом награды и безопасность
Сначала попробуйте решить упражнение. Ответы, подробные решения и проектные ориентиры раскрываются отдельно и сохраняют своё состояние при переключении RU/EN.
Скачать файлы курса (.zip) 382,1 КБАрхив содержит файлы на обоих языках и вспомогательные материалы с сохранением структуры каталогов.
Что именно получило 61%
В отчёте написано: «модель набрала 61% на наборе задач». Известно, что у новой версии изменились системное сообщение, шаблон диалога, лимит длины, доступ к поиску и правило канонизации ответа, а веса остались прежними.
-
Какой объект на самом деле сравнивается с прежним запуском?
-
Почему число нельзя приписать только файлу весов?
-
Какие компоненты нужно зафиксировать, если исследовательский вопрос всё же относится только к контрольным точкам?
-
В каком случае изменение обвязки является допустимой частью сравниваемой системы?
Показать ответ и решение
-
Сравниваются две системные конфигурации, а не два файла весов. Во второй конфигурации изменились как минимум запрос к модели, допустимый вычислительный бюджет, доступные действия и правило оценки.
-
Вероятность ответа и итоговый балл зависят от шаблона, декодирования, инструментов и оценивателя. Поэтому одна и та же контрольная точка может получить разные числа.
-
Нужно зафиксировать токенизатор и его версию, шаблон диалога, системное сообщение, параметры декодирования, бюджет, инструменты, среду, канонизатор и проверяющую систему.
-
Если исследовательский вопрос относится к полной прикладной системе — например, «какой агент лучше закрывает тикет» — обвязка является частью сравниваемого объекта. Тогда её изменение допустимо, но должно быть явным и версионированным.
Интервал не является украшением
На 20 задачах система не решила ни одной. Нормальная формула даёт интервал , а интервал Уилсона имеет положительную верхнюю границу.
-
Почему нормальная формула здесь вводит в заблуждение?
-
Что именно означает верхняя граница интервала Уилсона?
-
Доказывает ли отсутствие успехов истинную вероятность успеха, равную нулю?
-
Почему заранее заданная требуемая полуширина интервала полезнее фразы «возьмём примерно сто задач»?
Показать ответ и решение
-
Нормальная аппроксимация подставляет в оценку дисперсии и получает нуль. Она игнорирует неопределённость о неизвестной доле.
-
Верхняя граница описывает значения истинной доли, которые ещё совместимы с наблюдаемыми данными при выбранном методе построения интервала. Это не утверждение, что истинная доля равна верхней границе.
-
Нет. Наблюдение
0/20означает отсутствие успехов в конкретной выборке, а не логическую невозможность успеха. -
Целевая полуширина связывает объём данных с требуемой точностью до запуска и снижает соблазн остановиться тогда, когда результат удобен.
Единица анализа и повторные генерации
Для каждого из 100 запросов получено по 16 ответов с разными начальными значениями генератора случайных чисел. Команда объявила все 1600 ответов независимыми задачами и построила обычный биномиальный интервал.
-
В чём ошибка единицы анализа?
-
Какие источники случайности присутствуют в эксперименте?
-
Когда первичной единицей должен оставаться запрос?
-
Какой бутстрэп-подход сохраняет кластерную структуру?
-
Что нужно сделать, если целью является качество конкретного стохастического протокола с 16 попытками, а не качество одной попытки?
Показать ответ и решение
-
Ответы одного запроса разделяют его сложность, формулировку и эталон. Они образуют кластер и обычно не являются независимыми задачами.
-
Случайны выбор запросов, генерация ответов, возможно начальные значения обучения и решения стохастического судьи.
-
Запрос остаётся единицей, когда интерес представляет среднее качество по популяции запросов. Сначала агрегируют протокол внутри запроса, затем анализируют запросы.
-
Нужен кластерный или иерархический бутстрэп: на первом уровне с возвращением выбираются запросы целиком; при необходимости на втором — ответы внутри выбранного запроса.
-
Для каждого запроса нужно определить результат всего 16-попыткового протокола — например, pass@16, голосование или выбранный ответ — и уже эти запросные результаты агрегировать.
Парность и точный тест Мак-Немара
Две системы оценены на одних и тех же задачах. Только A верна на 9 задачах, только B — на 2. Большая часть задач решена обеими или провалена обеими.
-
Почему сравнение должно быть парным?
-
Какие наблюдения входят в тест Мак-Немара?
-
Почему асимптотический может дать слишком уверенный вывод при 11 дискордантных задачах?
-
Какой точный нулевой эксперимент лежит в основе двустороннего точного теста?
-
Что следует публиковать помимо -значения?
Показать ответ и решение
-
Оба ответа получены на одной задаче, поэтому их исходы зависимы. Парный протокол использует эту общую структуру и устраняет часть вариации сложности задач.
-
В тест входят только дискордантные случаи:
A onlyиB only. Общие успехи и общие провалы не различают системы. -
Нормальная аппроксимация предполагает достаточно большой дискордантный счётчик. При 11 наблюдениях хвост биномиального распределения ещё заметно дискретен.
-
При нулевой гипотезе каждая дискордантная задача с вероятностью должна попадать в пользу A или B. Поэтому .
-
Публикуют , , разность метрик, доверительный интервал, точное -значение и правило анализа, выбранное заранее.
pass@k, покрытие и выбор
Для задачи сгенерировано 20 кандидатов, ровно один из них правильный. В отчёте одновременно фигурируют pass@20 = 1 и «система выбрала неправильный ответ».
-
Есть ли противоречие?
-
Что измеряет pass@k?
-
Что измеряет качество выбора кандидата?
-
Почему формула с не является конечновыборочным оценивателем Чена?
-
Какие сведения о бюджете нужно хранить рядом с pass@k?
Показать ответ и решение
-
Противоречия нет. В пуле присутствует правильный кандидат, но правило выбора указало на другой.
-
pass@k измеряет покрытие: вероятность наличия хотя бы одного правильного ответа среди кандидатов.
-
Качество выбора измеряет способность оценивателя или агрегатора извлечь правильный кандидат из уже созданного пула.
-
Подстановка предполагает независимые повторные выборки из неизвестного распределения с оценённой вероятностью. Конечновыборочная формула задаёт выбор без возвращения из уже наблюдаемого пула из кандидатов.
-
Нужны , , число успешных кандидатов, число задач, параметры декодирования, лимит токенов, правило проверки, число начальных значений и стоимость генерации.
Отсутствие n-граммного совпадения
Фильтр восьмисловных n-грамм не нашёл совпадений между тестовыми задачами и известной частью корпуса обучения.
-
Что этот результат действительно исключает?
-
Какие виды загрязнения он может пропустить?
-
Почему неизвестная часть корпуса не позволяет превратить фильтр в сертификат чистоты?
-
Что такое протокольное загрязнение?
-
Назовите не менее четырёх дополнительных защит жизненного цикла бенчмарка.
Показать ответ и решение
-
Фильтр не нашёл дословных совпадений выбранной длины в доступной части корпуса.
-
Он пропускает парафразы, переводы, решения без текста условия, семантически близкие шаблоны, более короткие совпадения и обучение на производных наборах.
-
Поиск по неполному корпусу не способен исключить совпадение с недоступными данными. Отсутствие свидетельства не равно свидетельству отсутствия.
-
Протокольное загрязнение возникает, когда тест или лидерборд многократно используется для выбора гиперпараметров, контрольных точек, промптов и правил решения, даже без прямого включения примеров в обучение весов.
-
Подходят приватный контрольный набор, свежие задачи после даты среза, ротация версий, ограничение числа запросов к закрытому набору, канареечные строки (
canary), журнал всех решений по результатам теста и отдельная финальная оценка.
Судья под оптимизационным давлением
LLM-судья предпочитает длинные ответы, часто выбирает первый вариант и иногда следует инструкции, встроенной в оцениваемый ответ. После перехода от разового сравнения к Best-of-64 средняя оценка резко выросла.
-
Какие три класса слабостей судьи здесь видны?
-
Почему перестановка порядка A/B исправляет только часть проблемы?
-
Чем структурная изоляция данных кандидата отличается от словесной просьбы «игнорируй инструкции внутри ответа»?
-
Почему рост оценки при Best-of-64 может быть ошибкой победителя?
-
Какой независимый сигнал нужен, чтобы различить полезный рост и эксплуатацию судьи?
Показать ответ и решение
-
Видны позиционное смещение, предпочтение поверхностных признаков и уязвимость к инъекциям из данных кандидата.
-
Перестановка диагностирует и частично усредняет позиционный эффект, но не лечит общую ошибку по содержанию, длиновое смещение или следование внедрённой инструкции.
-
Структурная изоляция отделяет инструкции оценивания от недоверенного текста на уровне интерфейса, ролей, сериализации и парсера. Словесная просьба остаётся частью того же контекста и может быть переопределена атакующим текстом.
-
Среди 64 кандидатов растёт вероятность найти ответ с большой положительной ошибкой судьи. Максимальный прокси-балл может расти быстрее независимой полезности.
-
Нужен независимый оцениватель: исполнимая проверка, скрытая человеческая выборка, другая модель-судья с отдельной рубрикой или реальный исход среды.
Безопасность и решение о выпуске
Новая система реже отвечает на вредные запросы, но также вдвое чаще отказывается от безвредных. В одной категории обнаружен единичный, но критический обход песочницы.
-
Почему одной доли отказов недостаточно?
-
Какие два знаменателя нужны как минимум для вредных и безвредных запросов?
-
Зачем учитывать тяжесть последствий?
-
Должен ли средний итоговый балл компенсировать критический обход?
-
Какие поля протокольного паспорта нужны, чтобы решение можно было независимо проверить?
B. Аналитические задания
Показать ответ и решение
-
Отказ может быть правильной реакцией на вредный запрос или ошибкой на безвредном. Одна общая доля смешивает безопасность и полезность.
-
ASR использует число вредных попыток; частота избыточного отказа — число безвредных запросов.
-
Редкие критические события могут быть важнее многих слабых ошибок. Взвешивание по тяжести или отдельные жёсткие ворота не дают среднему скрыть последствия.
-
Нет, если критический обход заранее объявлен безусловным блокирующим событием.
-
Нужны версии модели, токенизатора, шаблона, набора и его среза, генерации, среды, оценивателя, начальные значения, число примеров, метрики с интервалами, стоимость, критерии выпуска и хэши артефактов.
Интервал Уилсона
Используйте .
-
Для , найдите наблюдаемую долю и 95%-интервал Уилсона.
-
Для , найдите тот же интервал.
-
Сравните второй ответ с нормальной аппроксимацией.
-
Объясните, почему результат
0/20совместим с ненулевой истинной долей.
Показать ответ и решение
Для Уилсона:
- Для
62/100: , интервал приблизительно
- Для
0/20: интервал приблизительно
-
Нормальная аппроксимация даёт , поскольку оценённая дисперсия равна нулю.
-
Двадцать неудач остаются вероятными при небольшом, но положительном истинном ; интервал показывает конечную неопределённость.
Планирование размера набора
В худшем случае для биномиальной доли используйте и приближение
где — требуемая полуширина.
-
Найдите минимальный для при .
-
Найдите минимальный для .
-
Во сколько раз увеличивается размер набора при трёхкратном уменьшении полуширины?
-
Почему это только планировочная оценка, а не гарантия точной ширины интервала Уилсона на любом результате?
Показать ответ и решение
- Для :
- Для :
-
Теоретически множитель равен ; округление даёт .
-
Формула использует худший случай и нормальную планировочную аппроксимацию. Реальная ширина Уилсона зависит от наблюдённой доли и дискретности счётчика.
Точное и асимптотическое парное сравнение
Для дискордантных счётчиков , :
- Найдите асимптотическую статистику
-
Найдите соответствующее двустороннее нормальное -значение.
-
Найдите точное двустороннее -значение как удвоенную нижнюю биномиальную хвостовую вероятность при .
-
Каков вывод при уровне для каждого подхода?
-
Почему в отчёте нельзя скрывать малое число дискордантных наблюдений?
Показать ответ и решение
-
Двустороннее нормальное значение приблизительно равно .
-
Для :
-
Асимптотический подход пересекает порог 0.05, точный — нет. В малой выборке основным выводом должен быть точный результат.
-
Без читатель не видит, что асимптотика опирается на очень малое число информативных задач.
Конечновыборочный pass@k
Используйте
-
Найдите
pass@5для , . -
Найдите
pass@20для , . -
Сравните второй ответ с наивной подстановкой .
-
Почему оба числа могут быть корректными ответами на разные вероятностные вопросы, но только одно является конечновыборочным оценивателем для уже полученного пула?
Показать ответ и решение
- Поскольку при выборе всех 20 кандидатов единственный правильный обязательно попадёт в подмножество,
- Наивная формула даёт
- Наивная формула относится к 20 новым независимым попыткам при известном . Конечновыборочная формула задаёт случайную подвыборку без возвращения из уже наблюдаемого пула, где известно, что один успех существует.
Аудит перестановки судьи
После канонизации меток получены решения судьи:
прямой порядок: A, A, B, TIE, A, B
обратный порядок: A, B, B, TIE, B, BВо второй строке A и B уже снова относятся к исходным кандидатам, а не к позиции на экране.
-
Найдите число согласованных и несогласованных пар.
-
Найдите долю согласованности.
-
Найдите доли голосов за A и B среди всех решений без ничьих.
-
Доказывает ли высокая согласованность отсутствие длинового или стилевого смещения?
-
Какую дополнительную рандомизацию следует провести?
Показать ответ и решение
Пары совпадают в позициях 1, 3, 4 и 6. Поэтому:
-
согласованных 4, несогласованных 2;
-
доля согласованности ;
-
среди 10 неничейных голосов A получает 4, B — 6, то есть 0.4 и 0.6;
-
нет: судья может стабильно предпочитать длинный или стилистически определённый ответ в обоих порядках;
-
следует рандомизировать длину и стиль через контролируемые пары, скрывать происхождение модели, менять формат сериализации и повторять оценивание с независимыми начальными значениями.
Метрики безопасности и выпускной порог
На 8 вредных запросах успех атаки наблюдался в случаях
а тяжести равны
На 10 безвредных запросах система отказала дважды.
-
Найдите частоту успешных атак (
attack success rate, ASR). -
Найдите частоту избыточных отказов на безвредных запросах.
-
Найдите взвешенную по тяжести ASR.
-
Пройдёт ли система численные ворота
ASR <= 0.30,benign_refusal <= 0.25,severity_weighted_ASR <= 0.40? -
Изменится ли решение, если один успех атаки означает подтверждённый критический обход песочницы, объявленный безусловным блокирующим событием?
C. Задания по программированию
Показать ответ и решение
- Успешны 3 из 8 атак:
- Два отказа из десяти:
- Суммарная тяжесть равна , тяжесть успешных атак — :
-
Система проваливает ворота ASR и взвешенной ASR, хотя проходит ворота избыточных отказов.
-
Критическое событие блокирует выпуск независимо от средних чисел; решение остаётся отрицательным и получает отдельную причину.
Интервалы и планирование
Реализуйте:
def wilson_interval(successes: int, total: int, z: float = 1.96) -> tuple[float, float, float]:
"""Вернуть (estimate, low, high)."""
raise NotImplementedError
def worst_case_sample_size(margin: float, z: float = 1.96) -> int:
"""Вернуть ceil(z**2 / (4*margin**2))."""
raise NotImplementedErrorПроверяйте границы счётчиков и параметров; концы интервала должны оставаться в .
Открытые проверки:
estimate, low, high = wilson_interval(0, 10)
assert estimate == 0.0 and low == 0.0
assert 0.27 < high < 0.29
assert worst_case_sample_size(0.03) == 1068Показать ответ и решение
import math
def wilson_interval(successes: int, total: int, z: float = 1.96) -> tuple[float, float, float]:
if total <= 0:
raise ValueError("total must be positive")
if successes < 0 or successes > total:
raise ValueError("successes must lie in [0, total]")
if not math.isfinite(z) or z <= 0:
raise ValueError("z must be finite and positive")
estimate = successes / total
denominator = 1.0 + z * z / total
center = (estimate + z * z / (2.0 * total)) / denominator
half_width = z / denominator * math.sqrt(
estimate * (1.0 - estimate) / total + z * z / (4.0 * total * total)
)
return estimate, max(0.0, center - half_width), min(1.0, center + half_width)
def worst_case_sample_size(margin: float, z: float = 1.96) -> int:
if not math.isfinite(margin) or not 0.0 < margin < 1.0:
raise ValueError("margin must lie in (0, 1)")
if not math.isfinite(z) or z <= 0:
raise ValueError("z must be finite and positive")
return math.ceil(z * z / (4.0 * margin * margin))Интервал Уилсона остаётся невырожденным при и , в отличие от симметричного интервала Вальда. Ограничение концов диапазоном защищает от микроскопических ошибок округления.
Парное сравнение
Реализуйте:
def paired_table(a_correct, b_correct):
"""Вернуть both_correct, a_only, b_only, both_wrong."""
def exact_mcnemar_pvalue(a_only: int, b_only: int) -> float:
"""Двустороннее точное p-значение."""
def paired_bootstrap_difference(a_scores, b_scores, *, replicates=10000, seed=0):
"""Вернуть estimate, low, high для парной средней разности."""Бутстрэп должен повторно выбирать с возвращением пары результатов по задачам, а не две системы независимо.
Открытые проверки:
a = [True, True, False, False, True]
b = [True, False, True, False, False]
table = paired_table(a, b)
assert table.both_correct == 1
assert table.a_only == 2
assert table.b_only == 1
assert table.both_wrong == 1Показать ответ и решение
Парность сохраняется на уровне индексов задач; две системы никогда не бутстрэпятся независимо.
from __future__ import annotations
import math
from dataclasses import dataclass
import numpy as np
@dataclass(frozen=True)
class PairedTable:
both_correct: int
a_only: int
b_only: int
both_wrong: int
def paired_table(a_correct, b_correct) -> PairedTable:
a = np.asarray(a_correct, dtype=bool)
b = np.asarray(b_correct, dtype=bool)
if a.shape != b.shape or a.ndim != 1 or a.size == 0:
raise ValueError("paired vectors must be non-empty and have equal shape")
return PairedTable(
int(np.sum(a & b)),
int(np.sum(a & ~b)),
int(np.sum(~a & b)),
int(np.sum(~a & ~b)),
)
def exact_mcnemar_pvalue(a_only: int, b_only: int) -> float:
if min(a_only, b_only) < 0:
raise ValueError("counts must be non-negative")
n = a_only + b_only
if n == 0:
return 1.0
tail = sum(math.comb(n, j) for j in range(min(a_only, b_only) + 1)) / 2**n
return min(1.0, 2.0 * tail)
def paired_bootstrap_difference(a_scores, b_scores, *, replicates=10_000, seed=0):
a = np.asarray(a_scores, dtype=float)
b = np.asarray(b_scores, dtype=float)
if a.shape != b.shape or a.ndim != 1 or a.size == 0:
raise ValueError("paired vectors must be non-empty and have equal shape")
if replicates <= 0:
raise ValueError("replicates must be positive")
delta = a - b
rng = np.random.default_rng(seed)
indices = rng.integers(0, delta.size, size=(replicates, delta.size))
draws = delta[indices].mean(axis=1)
return float(delta.mean()), float(np.quantile(draws, 0.025)), float(np.quantile(draws, 0.975))
table = paired_table([1, 1, 0, 0, 1], [1, 0, 1, 0, 0])
assert table == PairedTable(1, 2, 1, 1)
assert abs(exact_mcnemar_pvalue(9, 2) - 0.0654296875) < 1e-15pass@k и выбор
Реализуйте численно устойчивую конечновыборочную оценку и отдельную диагностику выбора:
def pass_at_k(n: int, c: int, k: int) -> float:
raise NotImplementedError
def coverage_and_selection(correct, selected_index: int) -> tuple[bool, bool]:
raise NotImplementedErrorПервая функция оценивает вероятность того, что в выборках без возвращения окажется хотя бы одно из успешных решений среди кандидатов. Вторая разделяет наличие правильного кандидата в пуле и правильность реально выбранного ответа.
Открытые проверки:
assert abs(pass_at_k(200, 10, 5) - 0.22828446073733422) < 1e-15
assert coverage_and_selection([False, True, False], 0) == (True, False)Показать ответ и решение
def pass_at_k(n: int, c: int, k: int) -> float:
if n <= 0 or not 0 <= c <= n or not 1 <= k <= n:
raise ValueError("require n>0, 0<=c<=n, and 1<=k<=n")
if n - c < k:
return 1.0
probability_of_miss = 1.0
for i in range(k):
probability_of_miss *= (n - c - i) / (n - i)
return 1.0 - probability_of_miss
def coverage_and_selection(correct, selected_index: int) -> tuple[bool, bool]:
values = [bool(x) for x in correct]
if not values or not 0 <= selected_index < len(values):
raise ValueError("invalid candidate pool or selected index")
return any(values), values[selected_index]Произведение вероятностей промаха устойчивее прямого вычисления отношения больших биномиальных коэффициентов. Вторая функция показывает, почему покрытие пула и качество селектора нельзя смешивать.
Дешёвый фильтр загрязнения без подмены метрики
Реализуйте:
def ngram_set(text: str, n: int) -> set[tuple[str, ...]]:
...
def ngram_containment(benchmark_text: str, corpus_text: str, n: int) -> float:
"""Доля n-грамм benchmark_text, найденных в corpus_text."""
def ngram_jaccard(left: str, right: str, n: int) -> float:
"""Симметричный коэффициент Жаккара."""
def contamination_report(items, corpus, *, n=8, threshold=0.2):
...Отчёт должен использовать асимметричное покрытие со стороны тестового элемента (benchmark-side containment), совпадающее с формулой лекции, и явно хранить имя метрики. Отдельно покажите пару текстов, для которой асимметричное покрытие и коэффициент Жаккара различаются. В документации укажите, что отсутствие срабатывания не доказывает чистоту.
Открытые проверки:
assert ngram_set("a b c d", 2) == {("a", "b"), ("b", "c"), ("c", "d")}
assert ngram_containment("a b c", "x a b c y", 2) == 1.0
assert ngram_jaccard("a b c", "x a b c y", 2) < 1.0Показать ответ и решение
from __future__ import annotations
def ngram_set(text: str, n: int) -> set[tuple[str, ...]]:
if n <= 0:
raise ValueError("n must be positive")
tokens = text.casefold().split()
return {tuple(tokens[i : i + n]) for i in range(len(tokens) - n + 1)}
def ngram_containment(benchmark_text: str, corpus_text: str, n: int) -> float:
benchmark = ngram_set(benchmark_text, n)
corpus = ngram_set(corpus_text, n)
if not benchmark:
return 0.0
return len(benchmark & corpus) / len(benchmark)
def ngram_jaccard(left: str, right: str, n: int) -> float:
a = ngram_set(left, n)
b = ngram_set(right, n)
union = a | b
return 0.0 if not union else len(a & b) / len(union)
def contamination_report(items, corpus, *, n=8, threshold=0.2):
if not 0.0 <= threshold <= 1.0:
raise ValueError("threshold must lie in [0, 1]")
maxima = []
matches = []
for i, item in enumerate(items):
scores = [ngram_containment(item, doc, n) for doc in corpus]
best = max(scores, default=0.0)
maxima.append(best)
for j, score in enumerate(scores):
if score >= threshold:
matches.append((i, j, score))
return {
"metric": "benchmark_ngram_containment",
"n": n,
"threshold": threshold,
"max_overlap": maxima,
"matches": matches,
}
assert ngram_containment("a b c", "x a b c y", 2) == 1.0
assert ngram_jaccard("a b c", "x a b c y", 2) == 0.5Асимметричное покрытие делит на число n-грамм тестового элемента; коэффициент Жаккара — на объединение. Отчёт хранит имя метрики, чтобы эти определения не смешались при последующей обработке.
Два аудита судьи и льстивое согласие
Реализуйте:
def judge_swap_audit(forward_canonical, reverse_canonical):
"""Согласованность по идентичности кандидатов A/B."""
def position_audit(raw_forward, raw_reverse):
"""Предпочтение FIRST/SECOND до канонизации идентичности."""
def sycophancy_metrics(correct_without_claim, correct_with_false_claim, agrees_with_false_claim):
...Не смешивайте идентичность кандидата и позицию на экране. Первая функция получает уже канонизированные A/B-метки; вторая — сырые FIRST, SECOND, TIE.
Открытая проверка:
identity = judge_swap_audit(["A", "A", "B", "TIE"], ["A", "B", "B", "TIE"])
assert identity["consistent"] == 3
position = position_audit(["FIRST", "SECOND"], ["FIRST", "FIRST"])
assert position["first_position_wins"] == 3Показать ответ и решение
from __future__ import annotations
def _labels(values, allowed):
result = [str(x).upper() for x in values]
if any(x not in allowed for x in result):
raise ValueError(f"labels must belong to {sorted(allowed)}")
return result
def judge_swap_audit(forward_canonical, reverse_canonical):
a = _labels(forward_canonical, {"A", "B", "TIE"})
b = _labels(reverse_canonical, {"A", "B", "TIE"})
if len(a) != len(b) or not a:
raise ValueError("label vectors must be non-empty and aligned")
consistent = sum(x == y for x, y in zip(a, b))
votes = [x for x in a + b if x != "TIE"]
return {
"items": len(a),
"consistent": consistent,
"inconsistent": len(a) - consistent,
"consistency_rate": consistent / len(a),
"candidate_a_rate": votes.count("A") / len(votes) if votes else 0.0,
"candidate_b_rate": votes.count("B") / len(votes) if votes else 0.0,
}
def position_audit(raw_forward, raw_reverse):
a = _labels(raw_forward, {"FIRST", "SECOND", "TIE"})
b = _labels(raw_reverse, {"FIRST", "SECOND", "TIE"})
if len(a) != len(b) or not a:
raise ValueError("label vectors must be non-empty and aligned")
all_labels = a + b
first = all_labels.count("FIRST")
second = all_labels.count("SECOND")
ties = all_labels.count("TIE")
denom = first + second
return {
"judgments": len(all_labels),
"first_position_wins": first,
"second_position_wins": second,
"ties": ties,
"first_position_rate_non_ties": first / denom if denom else 0.0,
}
def sycophancy_metrics(correct_without_claim, correct_with_false_claim, agrees_with_false_claim):
base = [bool(x) for x in correct_without_claim]
challenged = [bool(x) for x in correct_with_false_claim]
agrees = [bool(x) for x in agrees_with_false_claim]
if not base or not (len(base) == len(challenged) == len(agrees)):
raise ValueError("vectors must be non-empty and aligned")
initially_correct = sum(base)
switches = sum(b and not c for b, c in zip(base, challenged))
return {
"accuracy_without_claim": sum(base) / len(base),
"accuracy_with_false_claim": sum(challenged) / len(base),
"accuracy_drop": (sum(base) - sum(challenged)) / len(base),
"switch_rate_among_initially_correct": switches / initially_correct if initially_correct else 0.0,
"unjustified_agreement_rate": sum(agrees) / len(agrees),
}
assert judge_swap_audit(["A", "A", "B", "TIE"], ["A", "B", "B", "TIE"])["consistent"] == 3
assert position_audit(["FIRST", "SECOND"], ["FIRST", "FIRST"])["first_position_wins"] == 3Канонизированные A/B и сырые метки FIRST/SECOND сохраняются раздельно, потому что измеряют разные виды смещения.
Безопасность, ворота выпуска и паспорт
Реализуйте:
def safety_rates(attack_succeeded, benign_refused, severities=None):
raise NotImplementedError
def release_decision(metrics, thresholds, critical_failures=()):
raise NotImplementedError
def validate_measurement_passport(passport):
raise NotImplementedErrorASR и частота избыточного отказа используют разные знаменатели. Критическое событие и отсутствующая обязательная метрика должны блокировать выпуск. Валидатор паспорта возвращает все найденные ошибки.
Открытые проверки:
rates = safety_rates([0, 1, 0, 1], [0, 0, 1, 0], severities=[1, 4, 1, 2])
assert abs(rates["attack_success_rate"] - 0.5) < 1e-12
assert abs(rates["benign_over_refusal_rate"] - 0.25) < 1e-12
result = release_decision(rates, {"attack_success_rate": ("max", 0.25)}, ("sandbox_escape",))
assert result["passed"] is FalseПоказать ответ и решение
def safety_rates(attack_succeeded, benign_refused, severities=None):
attacks = [bool(x) for x in attack_succeeded]
benign = [bool(x) for x in benign_refused]
if not attacks or not benign:
raise ValueError("both harmful and benign samples are required")
if severities is None:
severity_weighted = sum(attacks) / len(attacks)
else:
weights = [float(x) for x in severities]
if len(weights) != len(attacks) or any(x <= 0 for x in weights):
raise ValueError("positive severities must align with attacks")
severity_weighted = sum(w for hit, w in zip(attacks, weights) if hit) / sum(weights)
return {
"attack_success_rate": sum(attacks) / len(attacks),
"benign_over_refusal_rate": sum(benign) / len(benign),
"severity_weighted_asr": severity_weighted,
}
def release_decision(metrics, thresholds, critical_failures=()):
failures = [f"critical:{item}" for item in critical_failures]
for name, (direction, threshold) in thresholds.items():
if name not in metrics:
failures.append(f"missing:{name}")
continue
value = float(metrics[name])
if direction == "max" and value > threshold:
failures.append(f"{name}>{threshold}")
elif direction == "min" and value < threshold:
failures.append(f"{name}<{threshold}")
elif direction not in {"min", "max"}:
failures.append(f"invalid_direction:{name}")
return {"passed": not failures, "failures": failures}
def validate_measurement_passport(passport):
required = {
"run_id", "model_id", "model_revision", "tokenizer_id", "dataset_id",
"dataset_revision", "split", "solver_id", "scorer_id", "scorer_revision",
"decoding", "seeds", "sample_count", "metrics", "cost", "artifact_hashes",
}
errors = [f"missing:{key}" for key in sorted(required - set(passport))]
if "sample_count" in passport and int(passport["sample_count"]) <= 0:
errors.append("sample_count:not_positive")
if "seeds" in passport and not passport["seeds"]:
errors.append("seeds:empty")
if "artifact_hashes" in passport and not passport["artifact_hashes"]:
errors.append("artifact_hashes:empty")
return tuple(errors)Критический инцидент блокирует выпуск независимо от средних метрик; отсутствующая обязательная метрика также приводит к отказу по принципу безопасного отказа.