Главная / Статьи / Оценка больших языковых моделей без зависимостей с реально надежным арбитром

Оценка больших языковых моделей без зависимостей с реально надежным арбитром

Создайте простую систему оценки небольших LLM на основе реальных лог-файлов, проверки кода и судьи, оценивающего по одному критерию, затем откалибруйте этого судью с использованием человеческих меток, чтобы его оценки имели смысл.

2147 слов

Единичные тесты работают потому, что детерминистический код всегда даёт один и тот же результат: если проверить, что функция возвращает четыре, она действительно всегда это делает. Вывод языковой модели меняется при каждом вызове, поэтому нет определённой строки для сравнения, и команды часто решают эту проблему, используя платформу оценки с другой моделью для проверки результатов. Именно в модели оценки большинство процедур оценивания фактически перестают что-либо измерять, поскольку никто не подтверждал её согласованность с результатами оценки человеком. Этот гид позволяет создать полноценную систему оценивания на простом Python за несколько часов, при этом самый важный раздел посвящён настройке модели оценки так, чтобы полученные числовые значения можно было обосновать.

Три компонента любой оценки

Если отбросить инструменты, оценка состоит из трёх компонентов, для которых не требуется никаких библиотек:

  • Набор данных входных значений, желательно взятых из реального использования, а не сгенерированных искусственно.
  • Способ генерации результата для каждого входного данных, которым является ваша тестируемая система.
  • Функция оценки, преобразующая каждый результат в вердикт, который можно подсчитать.
  • Панели управления, отслеживание и хранимые наборы данных — это удобства, созданные на основе этих компонентов. Некоторые из них действительно полезны, но суть решения помещается примерно в восемьдесят строк кода на Python, и такой небольшой код будет продолжать работать долгое время после того, как какая-либо конкретная платформа изменится или исчезнет.

    Шаг 1: Соберите сто реальных входных данных

    Используйте данные из логов производственной среды. Избегайте синтетических примеров, случаев, успех которых вам уже известен, а также очищенных образцов. Сто случаев — это достаточно, чтобы выявить реальные проблемы, и в то же время достаточно мало, чтобы позже можно было вручную их классифицировать, что оказывается крайне важным. Намеренно включайте сложные и необычные данные: набор простых случаев дает стабильный результат, даже когда качество работы системы ухудшается.

    Загрузка этих данных — дело простое:

    import json
    with open("requests.jsonl") as f:
        cases = [json.loads(line) for line in f][:100]
    

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

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

    Шаг 2: Разделение проверок кода и суждений

    Этот шаг чаще всего пропускается командами, но он определяет ход всех последующих действий. Многие свойства можно проверить детерминированно, и их никогда не следует отправлять в модель:

    • Результат представляет собой валидный JSON.
    • Категория принадлежит известному набору.
    • Число находится в допустимом диапазоне.
    • Присутствуют обязательные поля.
    • Длина ответа не превышает установленного лимита.

    Каждое из этих условий является утверждением. Следующая функция (на Python, независимо от её метки) выполняет несколько таких проверок и возвращает словарь с именованными результатами:

    def code_checks(output):
        checks = {}
        try:
            parsed = json.loads(output)
            checks["valid_json"] = True
            checks["has_fields"] = all(k in parsed for k in ("answer", "confidence"))
        except json.JSONDecodeError:
            checks["valid_json"] = False
            checks["has_fields"] = False
        checks["under_limit"] = len(output) < 2000
        return checks
    

    Возвращение именованных результатов проверок вместо одного булевого значения позволяет определить, какое свойство нарушилось при возникновении проблемы. То, что остается после детерминистичных проверок, требует суждения: ответил ли он на реальный вопрос, не были ли в нем упомянуты факты, отсутствующие в исходном материале, отказался ли он там, где следовало согласиться? Для оценки этих свойств нужен судья, а судье — подтверждение корректности решения.

    Шаг 3: Написать судью, оценивающего одну характеристику

    Каждому вызову судьи следует передавать ровно один критерий. Рубрика, требующая оценки пяти качеств одновременно, приводит к смешанному вердикту, и при его негативном результате невозможно определить, какое качество отсутствовало. Приведённый ниже судья проверяет одно свойство, отсутствие обоснованных утверждений и требует строгого формата ответа:

    JUDGE = """You are grading one property of a response.
    
    PROPERTY: Does the response contain any claim that is not supported by the
    source text provided?
    
    SOURCE:
    {source}
    
    RESPONSE:
    {response}
    
    Answer with exactly one word, PASS or FAIL, then a new line, then one
    sentence explaining your verdict. Do not explain anything else."""
    
    def judge(source, response, call_model):
        out = call_model(JUDGE.format(source=source, response=response))
        verdict = out.strip().split("\n")[0].strip().upper()
        return verdict == "PASS", out
    

    Ограничения по формату имеют такое же значение, как и сам критерий. Требование указать на первой строке одно слово — PASS или FAIL — позволяет легко парсить результат. Оценки в свободной форме различаются по структуре от одного случая к другому, и если сбой при парсинге будет рассматриваться как успешный результат, все последующие оценки будут завышены, причем никто этого не заметит. В данной реализации выбран более осторожный подход: любой вариант, отличный от точного значения PASS, включая PASS. или оценку, заключённую в форматирование, считается неудачей. Стоит фиксировать, как часто это происходит, поскольку резкий скачок количества непарсируемых ответов сам по себе является сигналом.

    Шаг 4: Запустить всё и сохранить необработанный вывод

    Сохраняйте полный вывод и объяснение судьи по каждому случаю, а не только сам вердикт. Когда меняется оценка, вам понадобится узнать, что именно изменилось, а сохранённый булево значение этого не покажет.

    results = []
    for case in cases:
        output = call_model(case["prompt"])
        passed, reasoning = judge(case["source"], output, call_model)
        results.append({
            "id": case["id"],
            "output": output,
            "code_checks": code_checks(output),
            "judge_pass": passed,
            "judge_reasoning": reasoning,
        })
    rate = sum(r["judge_pass"] for r in results) / len(results)
    print(f"{rate:.1%} pass on {len(results)} cases")
    

    Цикл фиксирует результаты детерминистичных проверок вместе с вердиктом судьи, хотя основной показатель учитывает только вердикт судьи. На практике следует указывать оба значения, поскольку ответ, не прошедший валидацию JSON, считается ошибкой независимо от мнения судьи. Также обратите внимание, что здесь одна и та же функция call_model отвечает за генерацию и оценку результатов; часто используется другая или более сильная модель для оценки вердикта судьи, но какую бы модель вы ни выбрали, ключевым фактором доверия будет следующий шаг.

    На этом этапе у вас есть процентный показатель. Он пока ничего не значит.

    Шаг 5: Калибровка судьи с использованием собственных меток

    Этот шаг обычно опускается в большинстве руководств, и он занимает примерно тридцать минут. Выберите пятьдесят из ста случаев и сами присвойте каждому из них метку PASS или FAIL, не заглядывая сначала в вердикт судьи. Затем сравните два набора меток:

    agree = sum(1 for r, human in zip(results[:50], human_labels)
                if r["judge_pass"] == human)
    print(f"judge agrees with me on {agree}/50 = {agree/50:.0%}")
    both_fail = sum(1 for r, h in zip(results[:50], human_labels)
                    if not r["judge_pass"] and not h)
    judge_fails = sum(1 for r in results[:50] if not r["judge_pass"])
    human_fails = sum(1 for h in human_labels if not h)
    print(f"judge caught {both_fail}/{human_fails} of the failures I found")
    print(f"judge flagged {judge_fails - both_fail} things I considered fine")
    

    Скрипт показывает три показателя: общий уровень согласованности, количество ошибок, которые обнаружил судья, и количество случаев, где судья ошибся, но которые вы считаете приемлемыми. Код предполагает, что human_labels — это список логических значений в том же порядке, что и первые пятьдесят результатов; при отсутствии ошибок происходит деление на ноль, что само по себе является признаком того, что ваш набор данных слишком простой.

    Почему общий уровень согласованности вводит в заблуждение

    Общий уровень согласованности — это наименее надежный показатель, несмотря на то, что его чаще всего цитируют. Если девяносто процентов ваших случаев считаются правильными, судья, отвечающий «ПРАВИЛЬНО» на все, добьется девяностопроцентного уровня согласованности, не обнаружив при этом ничего.

    Важный показатель: обнаруженные ошибки

    То, на что стоит обратить внимание, — это доля ваших собственных меток FAIL, которые также были отмечены судьей. С точки зрения классификации это является показателем точности судьи в выявлении ошибок. Судья, который замечает две из одиннадцати ваших ошибок, на самом деле ничего не оценивает; он просто утверждает всё, приложив убедительное объяснение. Третий показатель — ложные сигналы — тоже важен, поскольку судья, который отмечает правильные ответы, может побудить вас «исправлять» то, что на самом деле не было сломано.

    Когда вы и судья не согласны

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

    Критерии оценки неоднозначны

    Судья ошибается, потому что описание свойства допускает разные толкования. Уточните его. Хорошим критерием будет возможность сформулировать это свойство в одном предложении так, чтобы новый коллега использовал его точно так же, как вы.

    Ваши собственные метки неверны

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

    Эта характеристика по своей природе субъективна

    Некоторые качества невозможно объективно оценить кем-либо. Если ситуация такова, ни один судья не будет надежным. Либо разделите эту характеристику на более мелкие, проверяемые подхарактеристики, либо согласитесь с тем, что ее невозможно оценивать по шкале.

    Повторяйте процесс: корректируйте критерии оценки, при необходимости меняйте метки и запускайте процедуру заново, пока судья не обнаружит достаточно ваших ошибок, чтобы вы могли их обосновать на встрече. Затем зафиксируйте критерии оценки и сохраните их версию вместе с каждым последующим результатом. Результаты, оцененные по разным критериям, нельзя сравнивать. Чтобы узнать, как поддерживать «здоровье» судьи при непрерывной работе в производственных условиях, ознакомьтесь с управлением LLM в качестве судьи как живой производственной системы.

    Что дает вам калиброванный инструмент оценки

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

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

    Ограничения оценки на сто случаев

    Перед тем как использовать этот подход, стоит понимать три ограничения.

    Незаметны небольшие отклонения

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

    Ручная маркировка не масштабируется

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

    Истечение срока калибровки

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

    Два дополнения после запуска

    • Ведите версионирование всего. Храните версию модели и версию критериев оценки вместе с каждым результатом. Оценка без одного из этих элементов не может быть сравнена ни с чем другим, так же как показатель эффективности без соответствующих условий оценки почти ничего не говорит.
    • Сохраняйте набор случаев несогласия. Ситуации, когда у вас и рецензента были разные мнения, являются наиболее информативными записями во всем задании. Перезапуск их после каждой изменения критериев — самый быстрый способ выяснить, помогло ли это изменение.

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

    Основные выводы

    • Система оценки — это набор данных, генератор и функция подсчета баллов; все остальное — дополнительные инструменты.
  • Используйте реальные, сложные примеры входных данных и проверяйте любые детерминистичные процессы с помощью кода, а не модели.
  • Укажите каждому судье один критерий и строгий, структурированный формат вывода.
  • Отчитывайтесь о количестве ошибок, обнаруженных судьей при помощи человека, а не только об общем уровне согласия.
  • Заморозьте и версионируйте критерии оценки, повторно проверяйте их после изменений в модели и используйте сравнения парами, когда эффекты незначительны.