Введение

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

Традиционное ПО против LLM

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

Кроме того, тесты в программном обеспечении оценивают логику, которая является детерминированной, т. е. одни и те же входные данные приводят к одним и тем же выходным данным. В машинном обучении и, в частности, в авторегрессии (предсказании следующего слова), LLM, например, семейство GPT, недетерминированы, т. е. одни и те же входные данные приводят к множеству возможных выходных данных. Это усложняет построение тестов и оценок для LLM.

Чтобы продемонстрировать, что LLM может иметь много правильных или неправильных решений, рассмотрим следующий пример:

Вопрос: «Является ли Лондон лучшим городом в мире?»

Ответ 1: «Да».

Ответ 2: «Многие считают Лондон лучшим городом в мире из-за его культурного разнообразия, в котором говорят более чем на 300 языках. Он считается мировой столицей. Кроме того, в Лондоне построены объекты культурного наследия, которые имеют богатую историю, в сочетании со знаковыми сооружениями, такими как Эйфелева башня».

Ответ 3: «Определение того, является ли Лондон «лучшим» городом в мире, субъективно и зависит от индивидуальных предпочтений, приоритетов и критериев того, что делает город великим. У Лондона, безусловно, есть много характеристик, которые делают его весьма желанным городом для многих людей, включая его богатую историю, культурное разнообразие, музеи и галереи мирового класса, а также экономические возможности. Однако вопрос о том, действительно ли он является «лучшим» городом, остается спорным и зависит от конкретного человека».

Какой из трех сгенерированных ответов является правильным и должен генерироваться вашим приложением?

Первые два сгенерированных ответа утверждают, что Лондон - лучший город в мире, что считается предвзятым мнением. В деликатных случаях предвзятость может быть крайне нежелательна для заинтересованных сторон, ответственных за LLM. Кроме того, ответ 2 утверждает, что Эйфелева башня находится в Лондоне. Очевидно, что это нонсенс. Это пример еще одной уязвимости LLM - галлюцинаций. Известно, что они делают заявления, которые звучат убедительно и правдиво, но на самом деле неверны. Это явление происходит потому, что большинство LLM являются авторегрессионными: они обучены предсказывать следующее слово в предложении. Как только модель предсказывает слово, которое не соответствует контексту, оно может распространиться и вызвать дальнейшие неподходящие прогнозы. Другие ключевые соображения по поводу LLM:

  • Эффективность (точность и полезность)

  • Производительность (скорость и функциональность)

  • Качество (пользовательский опыт, читабельность и тон)

Ниже приведена сводная таблица для сравнения функций традиционного программного обеспечения и приложений на основе LLM, которые имеют значение для их тестирования и оценки:

Оценка LLM и оценка систем LLM

Хотя основное внимание в этой статье уделяется оценке систем LLM, важно различать оценку базовой LLM и оценку системы, которая использует LLM для конкретного варианта использования. Современные LLM (SOTA) отлично выполняют различные задачи, включая взаимодействие с чат-ботами, распознавание именованных сущностей (NER), генерацию текста, резюмирование, ответы на вопросы, анализ настроений, перевод и многое другое. Обычно эти модели проходят оценку по стандартизированным тестам, таким как GLUE (General Language Understanding Evaluation), SuperGLUE, HellaSwag, TruthfulQA и MMLU (Massive Multitask Language Understanding). Для оценки фундаментальных моделей или базовых LLM используются несколько наборов бенчмарков. Вот некоторые ключевые примеры:

  • MMLU (Mean Message Length in Utterance): MMLU (Massive Multitask Language Understanding) оценивает, насколько хорошо LLM может выполнять несколько задач одновременно.

  • HellaSwag: оценивает, насколько хорошо LLM может закончить предложение.

  • GLUE: Тест GLUE (General Language Understanding Evaluation) предоставляет стандартизированный набор разнообразных задач NLP для оценки эффективности различных языковых моделей.

  • TruthfulQA: измеряет правдивость ответов модели.

Непосредственная применимость этих LLM «из коробки» может быть ограничена для наших конкретных требований из-за потенциальной необходимости точной настройки LLM с использованием собственного набора данных, адаптированного к нашему конкретному варианту использования. Оценка точно настроенной модели или модели на основе извлечения дополненной генерации (RAG) обычно влечет за собой сравнение ее производительности с базовым набором данных, если таковой имеется. Этот аспект имеет решающее значение, ведь ответственность за обеспечение ожидаемой работы LLM выходит за рамки самой модели; инженер несет ответственность за обеспечение того, чтобы приложение LLM генерировало желаемые результаты. Эта задача включает в себя использование соответствующих шаблонов подсказок, реализацию эффективных конвейеров поиска данных, рассмотрение архитектуры модели (если требуется точная настройка) и многое другое. Однако навигация по выбору правильных компонентов и проведение комплексной оценки системы все еще остается сложной задачей. Мы собираемся рассмотреть оценки не для создания базовых LLM, а для нишевых и конкретных вариантов использования. Давайте предположим, что мы используем модель и пытаемся создать приложение, например, чат-бот для банка или приложение «чат для ваших данных». Кроме того, мы хотим, чтобы наши тесты были автоматическими и масштабируемыми, чтобы мы могли итеративно улучшать наше приложение на протяжении всего жизненного цикла операций машинного обучения (MLOP)! Как нам создать наши собственные метрики?

Автоматизация оценок

Что мы должны оценивать?

  1. Соответствие контексту (или обоснованность): соответствует ли ответ LLM предоставленному контексту или рекомендациям.

  2. Релевантность контекста (для систем, использующих RAG): соответствует ли полученный контекст исходному запросу или подсказке?

  3. Корректность (или точность): соответствуют ли выходные данные LLM истинной ситуации или ожидаемым результатам?

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

Когда нам следует проводить оценку?

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

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

  3. После развертывания: следует проводить постоянную оценку, которая варьируется в зависимости от варианта использования и бизнес-требований. Это важно для постоянного улучшения вашей модели с течением времени. Кроме того, вы должны в первую очередь сосредоточиться на сборе отзывов пользователей и проведении A/B-тестирования для итеративного улучшения пользовательского опыта вашего продукта.

Оценки на основе правил

Одна из самых простых вещей, которую мы, возможно, захотим проверить при тестировании сгенерированных нами ответов, - это:

  • Есть ли определенные фрагменты текста, которые наша система должна генерировать?

  • Есть ли определенные фрагменты текста, которые система не может генерировать?

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

def eval_expected_words(
    system_message,
    question,
    expected_words,
    human_template="{question}",
    # Language model used by the assistant
    llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0),
    # Output parser to parse assistant's response
    output_parser=StrOutputParser()
):
    """
    Evaluate if the assistant's response contains the expected words.

    Parameters:
        system_message (str): The system message sent to the assistant.
        question (str): The user's question.
        expected_words (list): List of words expected in the assistant's response.
        human_template (str, optional): Template for human-like response formatting.
            Defaults to "{question}".
        llm (ChatOpenAI, optional): Language model used by the assistant.
            Defaults to ChatOpenAI(model="gpt-3.5-turbo", temperature=0).
        output_parser (OutputParser, optional): Output parser to parse assistant's response.
            Defaults to StrOutputParser().

    Raises:
        AssertionError: If the expected words are not found in the assistant's response.
    """
    
    # Create an assistant chain with provided parameters
    assistant = assistant_chain(
        system_message,
        human_template,
        llm,
        output_parser
    )
    
    # Invoke the assistant with the user's question
    answer = assistant.invoke({"question": question})
    
    # Print the assistant's response
    print(answer)
    
    try:
      # Check if any of the expected words are present in the assistant's response
      assert any(word in answer.lower() \
                for word in expected_words), \
      # If the expected words are not found, raise an assertion error with a message
      f"Expected the assistant questions to include \
      '{expected_words}', but it did not"
    except Exception as e:
      print(f"An error occured: {str(e)}")

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

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

def evaluate_refusal(
    system_message,
    question,
    decline_response,
    human_template="{question}", 
    llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0),
    output_parser=StrOutputParser()):
    """
    Evaluate if the assistant's response includes a refusal.

    Parameters:
        system_message (str): The system message sent to the assistant.
        question (str): The user's question.
        decline_response (str): The expected response from the assistant when refusing.
        human_template (str, optional): Template for human-like response formatting.
            Defaults to "{question}".
        llm (ChatOpenAI, optional): Language model used by the assistant.
            Defaults to ChatOpenAI(model="gpt-3.5-turbo", temperature=0).
        output_parser (OutputParser, optional): Output parser to parse assistant's response.
            Defaults to StrOutputParser().

    Raises:
        AssertionError: If the assistant's response does not contain the expected refusal.
    """
    
    # Create an assistant chain with provided parameters
    assistant = assistant_chain(
        human_template,
        system_message,
        llm,
        output_parser
    )
  
    # Invoke the assistant with the user's question
    answer = assistant.invoke({"question": question})
    # Print the assistant's response
    print(answer)
    
    try:
      # Check if the expected refusal is present in the assistant's response
      assert decline_response.lower() in answer.lower(), \
      # If the expected refusal is not found, raise an assertion error with a message
      f"Expected the bot to decline with '{decline_response}' got {answer}"
    except Exception as e:
      return(f"An error occured: {str(e)}")

Эти примеры могут служить шаблоном для создания оценок на основе правил для тестирования крупных языковых моделей (LLM). При этом подходе регулярные выражения можно использовать для тестирования определенных шаблонов. Например, вы можете захотеть запустить оценку на основе правил для:

  • диапазонов дат

  • гарантируя, что система не сгенерирует личную информацию (номера банковских счетов, уникальные идентификаторы и т. д.)

Если вы хотите разработать оценку на основе правил, следуйте следующим шагам:

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

  2. Определите проверку логики: это могут быть точные совпадения, простые проверки на содержание или вы можете использовать регулярные выражения для определения более сложных шаблонов для ожидаемых ответов.

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

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

  5. Обработка ошибок: реализуйте в функциях оценки механизмы обработки ошибок, когда ожидаемые шаблоны не найдены в ответах LLM. Это гарантирует, что сбои будут надлежащим образом сообщаться и диагностироваться во время тестирования.

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

Оценки на основе моделей

Каким образом можно лучше гарантировать общее качество сгенерированных ответов в нашей системе? Мы хотим иметь более надежный и всеобъемлющий метод оценки. Это может быть сложно, поскольку то, что определяет «хороший» ответ, может быть весьма субъективным. Оценки на основе правил могут быть полезны для обеспечения наличия или отсутствия определенных данных или информации в наших выходных данных. Однако по мере роста приложения этот процесс становится более сложным и хрупким.

Один из методов - использовать LLM, известный как Model-Graded Evals, для оценки приложения LLM. Да, все верно, мы используем ИИ для проверки ИИ.

В этом фрагменте кода мы реализуем механизм оценки качества сгенерированных ответов. В частности, мы видим формат ответа, хотя любую функцию сгенерированного текста можно рассмотреть, адаптировав «eval_system_prompt» и «eval_user_message». Также нам нужно определить функцию «create_eval_chain», которая будет оценивать основное приложение LLM.

def create_eval_chain(
    agent_response,
    llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0),
    output_parser=StrOutputParser()
):
    """
    Creates an evaluation chain to assess the appropriateness of the agent's response.

    Parameters:
        agent_response (str): The response generated by the agent.
        llm (ChatOpenAI, optional): Language model used for evaluation.
            Defaults to ChatOpenAI(model="gpt-3.5-turbo", temperature=0).
        output_parser (OutputParser, optional): Output parser for parsing agent's response.
            Defaults to StrOutputParser().

    Returns:
        ChatPromptTemplate: Evaluation chain for assessing the agent's response.
    """

    delimiter = "####"

    eval_system_prompt = f"""You are an assistant that evaluates whether or not an assistant is producing valid responses.
    The assistant should be producing output in the format of [CUSTOM EVALUATION DEPENDING ON USE CASE]."""

    eval_user_message = f"""You are evaluating [CUSTOM EVALUATION DEPENDING ON USE CASE].
    Here is the data:
      [BEGIN DATA]
      ************
      [Response]: {agent_response}
      ************
      [END DATA]

    Read the response carefully and determine if it [MEETS REQUIREMENT FOR USE CASE]. Do not evaluate if the information is correct only evaluate if the data is in the expected format.

    Output 'True' if the response is appropriate, output 'False' if the response is not appropriate.
    """
    eval_prompt = ChatPromptTemplate.from_messages([
        ("system", eval_system_prompt),
        ("human", eval_user_message),
    ])

    return eval_prompt | llm | output_parser

Функция «create_eval_chain» создает цепочку оценки, которая по сути является серией шагов, которым будет следовать наша LLM для оценки заданного ответа. Эта функция принимает сгенерированный ответ в качестве входных данных, а также модель и анализатор выходных данных, которые вы хотели бы использовать для оценки.

Мы указываем модель оценки для вывода «True» или «False», поскольку это позволит нам легко интерпретировать, проходит ли наша модель множество этих тестов или нет. Это обеспечивает масштабируемость режима тестирования.

Поскольку LLM выводит строку, нам нужно выполнить постобработку теста, чтобы получить результаты нашего теста в удобном формате. Следующую функцию «model_grad_eval()» можно повторно использовать для обработки всех наших оценок в заданном тестовом наборе.

def model_grad_eval_format(generated_output):
    """
    Evaluates the format of the generated output from the agent.

    Parameters:
        generated_output (str): The output generated by the agent.

    Returns:
        bool: True if the response is appropriate, False otherwise.

    Raises:
        ValueError: If the evaluation result is neither "True" nor "False".
    """
    # Create an evaluation chain for assessing the agent's response format
    eval_chain = create_eval_chain(generated_output)

    # Retrieve the evaluation result from the evaluation chain
    evaluation_results_str = eval_chain.invoke({})

    # Strip leading/trailing whitespaces and convert to lowercase
    retrieval_test_response = evaluation_results_str.strip().lower()

    # Check if "true" or "false" is present in the response
    try:
      if "true" in retrieval_test_response:
          return True
      elif "false" in retrieval_test_response:
          return False
    except Exception as e:
      return(f"An error occured: {str(e)}")

model_grad_eval_format(generated_output="[UNDESIRED OUTPUT GENERATED BY AGENT]")

Функция «model_grad_eval_format()» анализирует результат оценки, чтобы вернуть логическое значение «True» или «False» или вызвать исключение. Используя оценки, мы можем систематически оценивать качество ответов, генерируемых языковыми моделями, что позволяет нам определять области для улучшения и повышать общую производительность нашего приложения.

Еще несколько советов по оценкам на основе моделей:

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

  • Вы можете создать проблему внутри проблемы: без тщательного рассмотрения и разработки оценок на основе моделей. Оценочная модель может допускать ошибки и давать вам ложное чувство уверенности в вашей системе.

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

Заключение

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

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

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

Для дополнительного изучения

Ниже приведен список соответствующих ресурсов для этой статьи:

Руководство по оценке OpenAI

Статья Джейн Хуанг (Microsoft)

Блокнот LlamaIndex по оценке

Блог Хэмела Х.

Документация LlamaIndex

Бесплатный курс по автоматизированному тестированию для LLMOps с CircleCI

Комментарии (1)


  1. dolovar
    28.10.2024 08:24

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

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

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

    Чувствуете сильный запах дешевого пластика? Да-да, нейросетка оставила следы на теле данной статьи.