8.1 · Статистика evals: шум и размер выборки
Неделя 8 · Evals · ⏱ ~4 ч · Теория
Зачем это AI-инженеру
Заголовок раздела «Зачем это AI-инженеру»На неделе 8 мы сравниваем конфигурации ассистента поддержки, и цифры отличаются на несколько процентов. Разница 72% против 75% на 50 вопросах — это 1–2 вопроса, и она легко возникает от одной только случайности модели и судьи. Без базовой статистики система «улучшается» подбрасыванием монетки, а в README попадает выдуманный прогресс. На собеседованиях вопрос «как вы поняли, что улучшение реальное?» — проверка на зрелость.
Метрика — это оценка, а не истина
Заголовок раздела «Метрика — это оценка, а не истина»Скрипт eval/run.py из 6.4 печатает одно число на метрику, например correctness 0,72. Возникает соблазн считать его качеством ассистента. Но correctness 0,72 на 50 вопросах — оценка «настоящего» качества ассистента на всех возможных вопросах клиентов такого рода. Другие 50 вопросов того же вида дали бы, скажем, 0,66 или 0,78. Есть три источника шума:
- Выборка вопросов. Какие именно вопросы попали в набор.
- Недетерминизм системы. Модель-ответчик с temperature > 0 отвечает по-разному; даже при temperature 0 ответы API не гарантированно идентичны.
- Недетерминизм судьи. Тот же ответ в пограничном случае судья оценивает то PASS, то FAIL.
Аналогия из жизни магазина: промокод TEA10 неделю, TEA15 следующую неделю, по 50 заказов с каждым. Средний чек с TEA15 на 3% выше, но объявлять TEA15 лучшим никто не станет: заказов мало, покупатели разные, неделя на неделю не похожа. С evals то же самое.
Доверительный интервал для доли «на пальцах»
Заголовок раздела «Доверительный интервал для доли «на пальцах»»Если из n вопросов прошли k, доля p = k/n. Её стандартная ошибка:
SE = sqrt(p × (1 − p) / n)Грубый 95% доверительный интервал: p ± 2·SE (точнее 1,96). Смысл на пальцах: если повторять сбор набора много раз, такие интервалы накроют настоящее значение примерно в 95% случаев. Ширину интервала и разницу между долями измеряем в процентных пунктах (п.п.): 0,72 → 0,75 — это +3 п.п., а не +3%.
| n | p | SE | 95% интервал (±) |
|---|---|---|---|
| 50 | 0,72 | 0,064 | ±12 п.п. |
| 100 | 0,75 | 0,043 | ±8,5 п.п. |
| 200 | 0,75 | 0,031 | ±6 п.п. |
| 500 | 0,75 | 0,019 | ±4 п.п. |
Вывод, который стоит запомнить: на 50 вопросах интервал шириной около ±12 п.п. Точность растёт как корень из n: чтобы сузить интервал вдвое, нужно вчетверо больше вопросов.
Для маленьких n и долей у краёв (0,95, 0,05) формула p ± 2·SE врёт (может дать интервал за пределами [0, 1]). Лучше интервал Уилсона:
import math
def wilson(k: int, n: int, z: float = 1.96) -> tuple[float, float]: # Wilson score interval for a binomial proportion if n == 0: return (0.0, 1.0) p = k / n denom = 1 + z * z / n center = (p + z * z / (2 * n)) / denom half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / denom return (center - half, center + half)
print(wilson(36, 50)) # ~ (0.58, 0.83)Сравнение двух конфигураций: парный подход
Заголовок раздела «Сравнение двух конфигураций: парный подход»Интервал описывает одну конфигурацию. В экспериментах нас интересует другое: лучше ли B, чем A. Возьмём две конфигурации на одной dev-части eval/golden.jsonl из 60 вопросов: A прошла 43/60, B — 46/60. Наивно: у A интервал Уилсона [0,59; 0,81], у B [0,65; 0,86], интервалы перекрываются, значит разницы нет. Это слишком строго, потому что A и B гонялись на одних и тех же вопросах. Шум «какие вопросы попали в набор» у них общий, и его можно вычесть.
Парный подход: смотрим только на вопросы, где результаты разошлись.
B: PASS B: FAIL A: PASS 39 b = 4 ← A лучше на 4 вопросах A: FAIL c = 7 10 ← B лучше на 7 вопросахA — 43/60 (71,7%), B — 46/60 (76,7%). Вопросы, где обе прошли или обе провалились, ничего не говорят о разнице. Важны только 11 расхождений: 7 в пользу B, 4 в пользу A. Если бы A и B были одинаковы, расхождения делились бы как броски монетки, 50/50. Насколько 7:4 необычно для монетки?
Тест знаков (точный тест Макнемара): вероятность получить расклад 7:4 или ещё более перекошенный при честной монетке:
from math import comb
def sign_test(b: int, c: int) -> float: # two-sided exact binomial test on discordant pairs (exact McNemar) n, k = b + c, min(b, c) if n == 0: return 1.0 tail = sum(comb(n, i) for i in range(k + 1)) / 2 ** n return min(1.0, 2 * tail)
print(sign_test(4, 7)) # ~0.55 -> easily noiseprint(sign_test(1, 9)) # ~0.02 -> unlikely to be noise7:4 — p ≈ 0,55, абсолютно обычный расклад для монетки. +5 п.п. здесь ничего не доказывает. А 9:1 при том же размере набора (это +13 п.п.) — уже p ≈ 0,02.
Порог 0,05 — условность, а не закон природы. Важнее логика: чем меньше расхождений и чем ближе они к 50/50, тем меньше оснований верить в улучшение.
Бутстрэп: универсальный инструмент
Заголовок раздела «Бутстрэп: универсальный инструмент»Для метрик, которые не являются простой долей (MRR, стоимость ответа, латентность, разница MRR между A и B), удобен бутстрэп: много раз выбираем вопросы из набора случайно с возвращением, пересчитываем метрику и смотрим, как она гуляет.
import random
def paired_bootstrap(a: list[float], b: list[float], iters: int = 5000, seed: int = 0): # a[i], b[i] are per-question scores of configs A and B on the same question i rnd = random.Random(seed) n = len(a) diffs = [] for _ in range(iters): idx = [rnd.randrange(n) for _ in range(n)] diffs.append(sum(b[i] - a[i] for i in idx) / n) diffs.sort() return diffs[int(0.025 * iters)], diffs[int(0.975 * iters)] # 95% interval of B - A
# if the interval contains 0, the difference is not establishedПересэмплируются вопросы, а не отдельные ответы, и одни и те же индексы для A и B: так сохраняется парность.
Недетерминизм: несколько прогонов
Заголовок раздела «Недетерминизм: несколько прогонов»Интервал выше учитывает только «какие вопросы попали». Шум модели и судьи меряется повторными прогонами одного и того же конфига:
| Прогон | correctness | faithfulness | hit@5 |
|---|---|---|---|
| 1 | 0,70 | 0,86 | 0,82 |
| 2 | 0,74 | 0,84 | 0,82 |
| 3 | 0,71 | 0,88 | 0,82 |
| разброс | 0,70–0,74 | 0,84–0,88 | 0 |
(Цифры иллюстративные.) Важная деталь: hit rate при детерминированном поиске не гуляет вообще, а метрики с судьёй гуляют. Отсюда практическое правило: разница между конфигами меньше разброса повторных прогонов — не разница.
Полезная метрика-диагностика: доля «мигающих» вопросов — тех, что в одном прогоне PASS, в другом FAIL. Если мигает 15% вопросов, ответчик или судья нестабилен на пограничных случаях; их стоит посмотреть глазами. Часто там неоднозначный вопрос («сколько стоит доставка?» без веса и города — то, что ловит eval/ambiguity_check.py) или нечёткий критерий судьи.
Как уменьшить шум:
- temperature 0 у судьи (и у ответчика на время экспериментов, если продукт это допускает и модель поддерживает параметр);
- кеш вердиктов судьи по хешу входа: одинаковый ответ — одинаковый вердикт;
- для решающих сравнений — 3 прогона и среднее по вопросу;
- исправить неоднозначные вопросы в наборе;
- больше вопросов.
Вопросы не независимы
Заголовок раздела «Вопросы не независимы»Есть ещё одно допущение, которое мы до сих пор молча принимали. Все формулы выше предполагают, что вопросы независимы. Если 10 синтетических вопросов из eval/generate.py сгенерированы по одному чанку или касаются одной страницы (скажем, kb/delivery.md с таблицей тарифов), они ведут себя как один «большой» вопрос: падают и проходят вместе. Эффективный размер набора меньше, чем число строк, и интервалы на самом деле шире. Статья Anthropic про статистический подход к evals рекомендует в таких случаях кластеризованные стандартные ошибки; естественный кластер в нашем наборе — страница из source_urls. Достаточно помнить идею: 10 вариаций одного вопроса — не 10 свидетельств, и разнообразие набора важнее числа строк.
Подгонка под набор (overfitting to the eval)
Заголовок раздела «Подгонка под набор (overfitting to the eval)»До сих пор шум был случайным. Подгонка — систематическая ошибка, которую вносим мы сами. Каждый раз, когда мы смотрим на результат и решаем, что менять дальше, система немного подгоняется под эти конкретные 60 вопросов. После 20 экспериментов на одном наборе:
- одна из 20 конфигураций окажется «лучше» на несколько п.п. просто по везению (если сделать 20 проверок с порогом 0,05, в среднем одна «выстрелит» случайно);
- промпт ответчика обрастает правилами под конкретные вопросы («если спрашивают про LAMP-LIGHT-MINI, напомни, что гарантия 6 месяцев»).
Защита:
- Holdout из 6.3 (
split: holdout): финальную конфигурацию проверяем на нём один раз. Если на holdout выигрыш исчез, он был подгонкой. - Гипотеза до эксперимента, записанная письменно (8.2): «ожидаю +X по hit rate, потому что…». Это не даёт задним числом объявить успехом любую случайную разницу.
- Мало экспериментов, каждый осмысленный, а не перебор всех комбинаций.
- Правки промпта общие, а не под конкретный вопрос.
Сколько вопросов нужно
Заголовок раздела «Сколько вопросов нужно»Обратная задача: какой набор нужен, чтобы заметить улучшение на d п.п.? Для независимых выборок при p около 0,75 и разнице 5 п.п. нужно больше тысячи вопросов на каждую конфигурацию. Парное сравнение на одном наборе требует заметно меньше (зависит от числа расхождений), но и с ним 60 вопросов надёжно ловят только крупные эффекты (порядка 10+ п.п. или очень однобокие расхождения). Реалистичный вывод для Проекта 3: на нашем наборе видны большие улучшения (сменили чанкинг: +15 п.п. hit rate), а мелкие (+3 п.п. от перефразировки промпта) честно записываются как «в пределах шума».
Правило для себя
Заголовок раздела «Правило для себя»Всё сказанное сводится к одному правилу, по которому мы будем принимать или отклонять изменения на неделе 8. Правило записываем заранее, до экспериментов. Пример:
Изменение считается улучшением, если выполнены все три условия:
- Разница больше разброса трёх повторных прогонов базового конфига.
- Парное сравнение: тест знаков p < 0,1 или 95% бутстрэп-интервал разницы не содержит 0.
- Ни одна другая ключевая метрика (faithfulness, доля правильных отказов
refusal_ok,false_refusals) не ухудшилась больше чем на разброс, аarchived_in_contextостался нулём.Для финальной конфигурации — подтверждение на holdout. Если условие 2 не выполнено, но эффект согласуется с гипотезой, пишем «тенденция, не доказано».
Как это выглядит в таблице экспериментов
Заголовок раздела «Как это выглядит в таблице экспериментов»Вместо correctness 0.74 пишем:
| Эксперимент | correctness | Δ к baseline | расхождения (+/−) | p |
|---|---|---|---|---|
| baseline (3 прогона) | 0,72 (0,70–0,74) | — | — | — |
| headings chunking (3 прогона) | 0,84 (0,82–0,85) | +12 п.п. | 9 / 2 | 0,07 |
Колонка в скобках — разброс повторных прогонов; «расхождения» — число вопросов (из 60), где новый конфиг лучше / хуже. Цифры иллюстративные. Даже +12 п.п. на 60 вопросах дают p ≈ 0,07, то есть «скорее настоящее, но не железно».
DeepSeek и Ollama: как это сделать у нас
Заголовок раздела «DeepSeek и Ollama: как это сделать у нас»Статистика от провайдера не зависит: интервал Уилсона, тест знаков и бутстрэп одинаковы. Зависят от провайдера источники шума и то, чем их можно прижать. Сверено в ноябре 2026 по api-docs.deepseek.com и docs.ollama.com.
Повторные прогоны не меняют правило приватности: облачный судья видит только вопросы набора, эталоны и куски публичной kb/. Личные данные клиентов, рабочий код и всё, что нельзя показывать посторонним, в облако не уходят — ни в DeepSeek (серверы в Китае), ни к другим провайдерам; настоящие сообщения клиентов обрабатывает только локальная модель.
Откуда берётся шум на нашем стеке
Заголовок раздела «Откуда берётся шум на нашем стеке»| Источник шума | Claude | OpenAI | DeepSeek | Ollama |
|---|---|---|---|---|
| Сэмплирование ответа | temperature, с ограничениями у части новых моделей | temperature; у reasoning-моделей её нет | temperature работает только с выключенным thinking; thinking включён по умолчанию | temperature + seed |
Фиксированный seed |
нет | есть у части моделей, без гарантии | в справочнике API не описан | есть |
| Модель под тем же именем меняется | снапшоты с датой в id | снапшоты с датой в id | да: deepseek-flash сейчас обслуживает V4.1-Flash-1017, старые имена перенаправлены на новые модели |
нет, пока мы сами не сделаем ollama pull того же тега |
| Нагрузка на сервис | влияет на латентность | влияет на латентность | влияет на латентность; при перегрузе 429 по лимиту одновременных запросов | своя машина: соседние процессы, загрузка модели после простоя |
| Обрезка контекста | нет (ошибка при превышении) | нет (ошибка при превышении) | нет при наших объёмах | да, молча, если контекст сервера меньше промпта |
Отсюда практические правила:
- Судья DeepSeek — с выключенным thinking и temperature 0. Иначе temperature не действует, и «мигающих» вопросов будет больше. Если всё же выбран судья с рассуждениями (по согласию с разметкой в главе 6.4 · Скрипт оценки: retrieval + faithfulness), разброс трёх прогонов обязателен.
- В каждый прогон записываем
resp.modelи дату. Если между прогонами baseline и варианта DeepSeek обновил модель под тем же именем, парное сравнение сравнивает уже две разные модели. Еслиresp.modelв двух прогонах различается — перепрогоняем baseline. - Локальная модель даёт самый воспроизводимый вывод: temperature 0, фиксированный
seed, фиксированный тег, достаточный контекст. Бит-в-бит одинаковых ответов это не гарантирует (такого обещания в документации Ollama нет) — проверяем двумя прогонами. Но воспроизводимо не значит верно: по памяти это модель класса 4B при 8 ГБ RAM, 7–8B при 16 ГБ, 14B при 32 ГБ и больше, и даже 14B слабее судьиdeepseek-v4-pro, а маленькая слабее и оцениваемой системы. Это свойство полезно для локального ответчика в сравнениях, а судья остаётсяdeepseek-v4-pro. - Кеш промптов DeepSeek не влияет на ответы, только на цену. А наш кеш вердиктов влияет: при измерении разброса судьи его выключаем.
Цена повторных прогонов
Заголовок раздела «Цена повторных прогонов»Три прогона baseline на 60 вопросах при ответе deepseek-flash и судье deepseek-v4-pro без thinking — около $1,8 в часы пик (3 × ≈ $0,60) и вдвое меньше вне пика (оценка из главы 6.4; пик — будни 01:00–04:00 и 06:00–10:00 UTC). На Ollama повторные прогоны бесплатны, но занимают время. Поэтому совет «3 прогона на конфиг» на нашем стеке почти ничего не стоит — экономить на нём не нужно. Для сравнения те же прогоны на сильных моделях Claude или OpenAI считаем по их страницам цен.
Эксперимент: thinking против выключенного thinking у судьи
Заголовок раздела «Эксперимент: thinking против выключенного thinking у судьи»Хорошее упражнение на эту главу: три прогона одного и того же набора ответов (ответы зафиксированы, меняется только судья) в двух режимах судьи. Сравниваем долю мигающих вердиктов и согласие с нашей разметкой из eval/judge_labels.jsonl. Обычно получается компромисс «точнее, но шумнее и дороже» против «стабильнее и дешевле», и решение принимается по цифрам, а не по ощущению.
Маршрут изучения
Заголовок раздела «Маршрут изучения»- Открываем Anthropic: A statistical approach to model evaluations → читаем целиком (≈25 мин). Пять рекомендаций: стандартная ошибка, кластеризация, уменьшение дисперсии через повторные ответы, парные сравнения, анализ мощности. Это ровно план этой главы.
- (По желанию, глубже) Статья целиком на arXiv: Adding Error Bars to Evals → разделы про парные различия и мощность (≈40 мин). Формулы можно пропускать, главное — выводы.
- Открываем Wikipedia: Binomial proportion confidence interval → разделы про нормальное приближение и интервал Уилсона (≈20 мин). Обращаем внимание, почему нормальное приближение плохо работает при малых n.
- Открываем Wikipedia: McNemar’s test → определение и пример (≈15 мин). Цель — понять, почему значение имеют только расходящиеся пары.
- В Hamel: AI Evals FAQ ищем вопросы про размер набора и статистическую значимость (≈15 мин): позиция практиков — «смотреть на данные», статистика не заменяет разбор ошибок.
- Открываем DeepSeek: Thinking Mode и Create Chat Completion (≈15 мин): какие параметры не действуют в режиме thinking и какие параметры вообще есть (убеждаемся, что
seedв справочнике нет). - Открываем Ollama: FAQ и Context length (≈15 мин): контекст по умолчанию,
keep_alive,OLLAMA_NUM_PARALLEL. Это источники «шума» локальной модели, которые на самом деле не шум, а настройки.
На что обратить внимание
Заголовок раздела «На что обратить внимание»- «Улучшение на 2 п.п.» на 50 вопросах — один вопрос. Проценты всегда переводим обратно в число вопросов.
- Сравнивать интервалы двух конфигов «на глаз» неправильно, когда вопросы общие: нужно парное сравнение.
- Один прогон на конфиг при недетерминированном судье — лотерея. Хотя бы для baseline нужны три прогона, чтобы узнать разброс.
- Смена версии набора ломает сравнимость: парное сравнение возможно только на одинаковых вопросах (
dataset_shaвconfig.jsonпрогона должен совпадать). - p-value не размер эффекта. p = 0,03 при +1 вопросе на огромном наборе может быть неважным практически, а +15 п.п. при p = 0,12 на 40 вопросах — интересной тенденцией, которую стоит проверить на большем наборе.
- Срезы малы. Correctness по
comparativeили поcategory: warrantyна 10 вопросах имеет интервал около ±30 п.п. Выводы по срезам — только как подсказки для разбора ошибок. - Поиск лучшего из многих. Лучший из 10 конфигов по dev имеет оптимистичную dev-цифру. Честная цифра — на holdout.
- «temperature 0» у судьи DeepSeek без отключения thinking — параметр молча игнорируется, разброс судьи больше, чем кажется.
- Тихая смена модели у DeepSeek. Baseline месячной давности и новый прогон могут быть сделаны разными моделями под одним именем. Сравниваем
resp.modelвresults.jsonl. - Обрезка контекста в Ollama выглядит как нестабильность. Если локальный ответчик или судья мигает на длинных вопросах (сравнение «Свет» и «Свет мини», таблица тарифов) и стабилен на коротких, сначала проверяем контекст (
ollama ps,prompt_eval_count), потом считаем статистику.
Практика
Заголовок раздела «Практика»Код: projects/02-support-kb-rag/eval/.
- Создаём
eval/stats.pyс функциямиwilson(k, n),sign_test(b, c),paired_bootstrap(a, b). Проверяем на примерах из теории:wilson(36, 50)≈ (0,58; 0,83),sign_test(4, 7)≈ 0,55,sign_test(1, 9)≈ 0,02. - Прогоняем
eval/run.py --config eval/configs/baseline.jsonна dev 3 раза (кеш судьи на это время выключаем, иначе разброс судьи не виден). - Скрипт
eval/compare.py RUN_A RUN_B: принимает две папки изeval/runs/, читает ихresults.jsonl, сопоставляет поid, печатает для каждой метрики: значения, Δ, расхождения b/c, p теста знаков, бутстрэп-интервал разницы. - Считаем для трёх прогонов baseline: разброс correctness/faithfulness, долю «мигающих» вопросов. Выписываем мигающие вопросы и смотрим на 5 из них: в чём причина нестабильности (вопрос, эталон, судья)?
- Прогоняем
compare.pyна двух прогонах одного и того же baseline. Ожидаемый результат: p большое, интервал содержит 0. Это проверка инструмента: если он «находит» разницу между одинаковыми конфигами, что-то не так. - Записываем правило значимости в
notes/evals.mdи в шапку будущей таблицы экспериментов. - Считаем интервал Уилсона для correctness по каждому
typeи поcategory. Наглядно видно, насколько широки интервалы на срезах. - В
results.jsonlкаждого прогона сохраняемresp.modelответчика и судьи и время вызова;compare.pyпредупреждает, если модели илиdataset_shaв двух прогонах различаются. - Меряем долю мигающих вердиктов для судьи
deepseek-v4-proв двух режимах (thinking выключен / включён) на трёх прогонах одних и тех же ответов. Записываем вnotes/evals.md, какой режим стабильнее и какой ближе к нашей разметке. - Для локального ответчика (
qwen3:4bили крупнее, если позволяет память; temperature 0,seed) делаем два прогона и считаем, сколько ответов различаются. Сравниваем с долей различий уdeepseek-flashбез thinking. - (Опционально, для сравнения) То же для судьи Claude или OpenAI на подвыборке из 20 ответов.
Готово, когда
Заголовок раздела «Готово, когда»- В таблице экспериментов у baseline указан разброс трёх прогонов.
- Есть
eval/stats.pyиeval/compare.py, проверенные на примерах и на сравнении baseline с самим собой. - Правило «какая разница значима» записано до начала экспериментов.
Тест для самопроверки
Заголовок раздела «Тест для самопроверки»Вопрос 1
Correctness 0,70 на 50 вопросах. Примерно какой ширины 95% интервал?
c. SE = sqrt(0,7·0,3/50) ≈ 0,065, ±2·SE ≈ ±13 п.п.
Вопрос 2
Во сколько раз нужно увеличить набор, чтобы сузить интервал вдвое?
В 4 раза: ширина интервала пропорциональна 1/√n.
Вопрос 3
Почему при сравнении двух конфигураций на одном наборе важны только вопросы, где результаты разошлись?
На вопросах, где обе прошли или обе провалились, конфиги одинаковы; разница между ними целиком определяется расхождениями, а общий шум выборки вычитается.
Вопрос 4
A и B на 60 вопросах: B лучше на 6 вопросах, хуже на 5. Что можно сказать?
6:5 — расклад, обычный для монетки (p ≈ 1); разницы не установлено.
Вопрос 5
Три прогона одного конфига дали correctness 0,70, 0,74, 0,71. Новый конфиг дал 0,73 за один прогон. Улучшение?
Нет: 0,73 внутри разброса baseline (0,70–0,74), это неотличимо от шума.
Вопрос 6
Почему hit rate при детерминированном поиске не меняется между прогонами, а correctness меняется?
Поиск по фиксированному индексу детерминирован, а ответ модели и вердикт судьи содержат случайность.
Вопрос 7
Что не так в этом выводе: «Мы попробовали 15 вариантов промпта, лучший дал 0,81 против 0,74 у baseline на dev-наборе, это +7 п.п., берём в продакшен»?
Выбор лучшего из 15 на том же наборе — подгонка: одна из многих попыток почти наверняка окажется «лучше» по везению. Нет разброса, нет парного теста, нет проверки на holdout.
Вопрос 8
Почему 10 синтетических вопросов, сгенерированных из одного чанка, дают меньше информации, чем 10 вопросов из разных мест?
Они коррелированы: успех или провал зависит от одного куска, поэтому они ведут себя почти как один вопрос; эффективный размер набора меньше.
Вопрос 9
Что делает бутстрэп и почему при сравнении A и B индексы вопросов для них должны быть одинаковыми?
Многократно пересэмплирует вопросы с возвращением и пересчитывает метрику, получая её распределение. Одинаковые индексы сохраняют парность: сравниваются A и B на одних и тех же вопросах, и общий шум вычитается.
Вопрос 10
compare.py показывает p = 0,01 при сравнении двух прогонов одного и того же конфига. Что это значит?
Скорее всего, ошибка в инструменте (не сопоставлены id, перепутаны папки прогонов) или очень нестабильный судья/ответчик, дающий систематически разные результаты; надо разбираться, прежде чем верить любым сравнениям.
Вопрос 11
Зачем записывать правило значимости до экспериментов?
Чтобы задним числом не подгонять критерий под желаемый результат и не объявлять успехом случайную разницу.
Вопрос 12
Три прогона baseline с судьёй deepseek-v4-pro и temperature=0 дали разброс correctness 0,66–0,78. Что проверить первым?
Выключен ли у судьи режим thinking: он включён по умолчанию, и в нём temperature не действует. Затем — нет ли ошибок ERROR, посчитанных как FAIL, и мигающих вопросов с неоднозначными формулировками.
Вопрос 13
Baseline прогнан 2 недели назад, вариант сегодня, у обоих answer_model: deepseek-flash. Почему парное сравнение может быть некорректным и как это обнаружить?
DeepSeek мог за это время переназначить имя на новую версию модели, и сравниваются две разные модели. Обнаружить по resp.model в results.jsonl; при расхождении перепрогнать baseline.
Вопрос 14
Какой ответчик при прочих равных даст самый воспроизводимый результат и почему? Годится ли он поэтому в судьи?
c. У локальной модели фиксированы и тег, и seed, и temperature, и никто не обновит модель без нашего ollama pull; у DeepSeek нет описанного seed, а модель под именем может меняться. Бит-в-бит повторяемость всё равно стоит проверить двумя прогонами. В судьи она не годится: в доступной памяти помещается модель 4–14B, слабее судьи deepseek-v4-pro (а маленькая — и оцениваемой системы), и стабильная ошибка остаётся ошибкой.
Что дальше
Заголовок раздела «Что дальше»Теперь можно ставить первый настоящий эксперимент: 8.2 · Эксперимент: chunking A vs B — гипотеза, два прогона, парное сравнение и строка в таблице экспериментов.