Как обнаружить галлюцинации в LLM?

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

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

Что это такое?

Дадим следующее определение галлюцинации (hallucination) — это сгенерированный контент, который не соответствует оригинальному предоставленному материалу и не имеет логической ценности. Исходником, или оригинальным предоставленным материалом, может служить, например, мировое знание, если задача представляет собой вопросно-ответную систему.

(JI, Ziwei et al. Survey of hallucination in natural language generation. ACM Computing Surveys, v. 55, n. 12, p. 1–38, 2023)

Для примера рассмотрим как раз формат вопроса-ответа:

6264a6c78bf0cf8aba04011f106044b3.png

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

Попробуем другое:

b8f6a9f2d9a8f509515c8e3c73362252.png

Здесь мы не задаем контекста, оттого получаем неверный ответ, точнее он может быть правильным, но не в нашем случае, поскольку мы спрашивали именно про модели (мы можем это легко исправить, расписав наш вопрос более детально).

Или пример из другой статьи про галлюцинации:

12df84f9328795a09c994fe7b868022f.png

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

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

BertScore

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

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

https://arxiv.org/pdf/1904.09675.pdf

Шаг 1: Создание контекстуальных векторов: Эталонные тексты и предлагаемые варианты текста кодируются в контекстуальные векторы, которые учитывают окружающие слова. Эти векторы получаются с помощью передовых моделей, таких как BERT, Roberta, XLNET и XLM.

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

Шаг 3: Выравнивание токенов для оценки точности и полноты: Каждый токен из предложенного текста сопоставляется с наиболее подходящим токеном из эталонного текста и наоборот. Это позволяет оценить точность и полноту перевода. Результаты этих оценок затем объединяются для расчёта итогового показателя F1.

b4d3864a92c651fbbf464b508ca0a6d1.png

Шаг 4: Придание веса редким словам: Применяется обратная частота документов (IDF), чтобы учесть значимость редких слов, повышая их вес в расчёте BERTScore. Это внедрение не является обязательным и может варьироваться в зависимости от специфики задачи.

Шаг 5: Нормализация результатов: Чтобы сделать результаты BERTScore более понятными для человека, они подвергаются линейному масштабированию. Таким образом, значения приводятся к диапазону, который легче интерпретировать, с использованием данных из монолингвальных корпусов, таких как Common Crawl.

d9b39f32edb391a3d6ae08291b08d0f6.png

# Импорт функции загрузки из модуля evaluate
from evaluate import load
try:
# Загрузка модель BERT для оценки
bert_model = load("bert")
# Задаем текст запроса и ответа
original_text = "В этой статье мы будем обнаруживать галлюцинации в наших данных,..."
translated_text = "Галлюцинации в данных представляют собой неточные или несвязанные с запросами ответы..."

# Вычисление оценку BERT для перевода
bert_result = bert_model.compute(predictions=[translated_response], references=[original_prompt])

# Вывод результат
print("Результат оценки BERT:", bert_result)

except Exception as e:
# Обработка исключения при ошибке загрузки или вычисления
print("Произошла ошибка:", e)

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

from whylogs.util import WhyLogs
from whylogs.metrics import BERTScore
from evaluate import load  # Предполагается, что функция load определена в модуле evaluate
Инициализация объекта WhyLogs
whylogs = WhyLogs()
Определение пользовательской метрики для оценки BERTScore
@whylogs.register_metric("bert_score")
def compute_bert_score(text):
# Проверка наличия загруженной модели BERT перед использованием
if "bert" not in globals():
raise ValueError("Model 'bert' is not loaded. Please load it before using.")
# Вычисление оценки BERTScore
result = bert.compute(predictions=[text["response"]], references=[text["prompt"]])
return [result["f1"]]

# Применение пользовательской метрики к данным
annotated_chats = whylogs.applyUDFsFromSchema(CHATS_DATASET)
# Вывод первых 10 записей с BERTScore менее 0.5
filtered_chats = annotated_chats[annotated_chats["bert_score"] < 0.5]
print(filtered_chats.head(10))

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

BLEU Score

BLEU (Bilingual Evaluation Understudy) представляет собой метрику для оценки качества автоматического перевода текстов, сравнивая его с одним или несколькими эталонными переводами.

Оценка BLEU, выраженная числом между 0 и 1, отражает степень соответствия машинного перевода эталонным версиям: 0 указывает на отсутствие совпадений (низкое качество), тогда как 1 свидетельствует о полном совпадении (высокое качество).

Как правило, оценка BLEU снижается по мере удлинения переводимого предложения. Тем не менее, степень этого снижения может отличаться в зависимости от конкретной модели перевода, применяемой в процессе. Для наглядности приведем график, который демонстрирует изменение оценки BLEU в зависимости от длины предложения:

871e769d1bc8851bad1be7374f6e66cb.png

С математической точки зрения, формулу можно отобразить так:

b444f699a82652f8a2a38d597d0c0846.png

Здесь BP является Brevity Penalty, который можно определить так:

dae1b4cce2b12d4b44e4e724d061c93b.png

И pn — это модифицированный балл точности, который определяется как:

a901a0da2a415befae96f041eb2f7964.png

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

# Импорт функции load из модуля evaluate
from evaluate import load

try:
    # Загрузка языковой модели для оценки качества
    language_model = load("blue")

    # Задание текста запроса и ответа
    original_text = "В этой статье мы будем обнаруживать галлюцинации в наших данных,..."
    translated_text = "Галлюцинации в данных представляют собой неточные или несвязанные с запросами ответы..."

    # Вычисление оценки с использованием языковой модели
    blue_score = language_model.compute(predictions=[translated_text], references=[[original_text]])

    # Вывод результата оценки
    print("Blue score:", blue_score)

except Exception as e:
    # Обработка исключений при возникновении ошибок
    print("An error occurred:", e)

Через процесс визуализации распределения оценок BLEU и детального рассмотрения примеров с низкими показателями можно выявить возможные случаи галлюцинаций в переводе. Однако крайне важно учитывать, что низкие оценки BLEU не всегда свидетельствуют о галлюцинациях; они могут также отражать различия в стиле изложения или понимании смысла.

from whylogs.util import WhyLogs
from whylogs.metrics import BleuScore

# Инициализация объекта WhyLogs
whylogs = WhyLogs()

# Определение пользовательской метрики для BLEU-оценки
@whylogs.register_metric("bleu_score")
def compute_bleu_score(text):
    blue_score = blue.compute(predictions=[text["response"]], references=[[text["prompt"]]])
    return [blue_score["blue"]]

# Применение пользовательской метрики к данным
annotated_chats = whylogs.applyUDFsFromSchema(CHATS_DATASET)

# Группировка данных по BLEU-оценке и вывод первых 10 результатов
bleu_scores_grouped = annotated_chats.groupby("bleu_score").count().reset_index()
top_10_bleu_scores = bleu_scores_grouped.sort_values("bleu_score", ascending=True).head(10)
print(top_10_bleu_scores)

Response Self-Similarity

Сопоставление запросов и ответов может предоставить значимые взгляды, но для глубокого понимания феномена галлюцинаций более продуктивно сравнивать различные ответы, созданные одной языковой моделью на один и тот же запрос. Такая методика, которую называют Response Self-Similarity, дает возможность оценить устойчивость и консистентность результатов, предоставляемых языковой моделью.

Для расчета Response Self-Similarity мы применяем векторные представления, которые отражают смысловую нагрузку целых фраз или текстовых фрагментов, вместо анализа индивидуальных слов. Измеряя косинусное расстояние между такими векторными представлениями оригинального ответа и его вариаций, мы можем точно определить уровень их взаимного сходства.

from sentence_transformers import SentenceTransformer
from whylogs.util import WhyLogs
from whylogs.metrics import SentenceEmbeddingSelfSimilarity
from sentence_transformers.util import cos_sim

# Загрузка модели SentenceTransformer для получения векторных представлений предложений
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

# Задание трех ответов
response1 = "Галлюцинации в данных представляют собой неточные или несвязанные с запросами ответы..."
response2 = "Галлюцинации возникают, когда LLM генерирует ответы, которые не имеют отношения или фактически неверны."
response3 = "Галлюцинации - это явление, когда LLM производит выводы, не связанные с входным запросом."

# Получение векторных представлений для каждого ответа
embedding1 = model.encode(response1)
embedding2 = model.encode(response2)
embedding3 = model.encode(response3)

# Инициализация объекта WhyLogs для записи метрик
wl = WhyLogs()

# Определение пользовательской метрики для оценки самоподобия ответов
@wl.register_metric("response_self_similarity")
def response_self_similarity(text):
    embeddings = [
        model.encode(text["response"]),
        model.encode(text["response2"]),
        model.encode(text["response3"])
    ]
    # Рассчитываем косинусное сходство между векторами предложений
    similarities = [cos_sim(embeddings[0], embedding) for embedding in embeddings[1:]]
    # Среднее значение сходства
    return [sum(similarities) / len(similarities)]

# Применение пользовательской метрики к данным
annotated_chats = wl.applyUDFsFromSchema(CHATS_DATASET)

# Вывод первых 10 записей с самоподобием ответов менее 0.8
filtered_chats = annotated_chats[annotated_chats["response_self_similarity"] < 0.8]
print(filtered_chats.head(10))

Через визуализацию распределения оценок Response Self-Similarity и детальный анализ случаев с низкими значениями, мы имеем возможность обнаружить ситуации, когда ответы, сгенерированные языковой моделью, заметно отличаются друг от друга. Это может свидетельствовать о наличии галлюцинаций или нестыковках в логике модели.

LLM Self-Evaluation

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

LLM self-evaluation, имитирующая человеческое мышление, дает модели возможность самостоятельно анализировать и оценивать корректность сгенерированных цепочек рассуждений. Она может выявлять правильные умозаключения, которые выделяются синим цветом, и ошибки, помеченные красным. https://arxiv.org/pdf/2311.09214.pdf

LLM self-evaluation, имитирующая человеческое мышление, дает модели возможность самостоятельно анализировать и оценивать корректность сгенерированных цепочек рассуждений. Она может выявлять правильные умозаключения, которые выделяются синим цветом, и ошибки, помеченные красным. https://arxiv.org/pdf/2311.09214.pdf

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

5a91ba0d6ef810caf702c928886edec9.png

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

import openai

# Функция для оценки самоподобия ответов LLM
def evaluate_self_similarity(data, index):
    # Создание промпта для запроса оценки согласованности
    prompt = f"""
    Контекст: {data.loc[index, 'prompt']}
    Ответ 1: {data.loc[index, 'response']}
    Ответ 2: {data.loc[index, 'response2']}
    Ответ 3: {data.loc[index, 'response3']}
    Оцените согласованность Ответа 1 с предоставленным контекстом (Ответами 2 и 3) на шкале от 0 до 1, где 0 означает полное несоответствие, а 1 - полное соответствие.
    """
    try:
        # Запрос оценки согласованности у модели OpenAI
        response = openai.Completion.create(
            engine="text-davinci-003",
            prompt=prompt,
            max_tokens=100,
            n=1,
            stop=None,
            temperature=0.7,
        )
        # Возвращение текста оценки согласованности
        return response.choices[0].text.strip()
    except Exception as e:
        # В случае возникновения ошибки выводим сообщение об ошибке
        print("An error occurred while evaluating self-similarity:", e)
        return None

# Инициализация объекта WhyLogs для записи метрик
wl = WhyLogs()

# Определение пользовательской метрики для оценки самоподобия
@wl.register_metric("prompted_self_similarity")
def prompted_self_similarity(text):
    # Вызов функции для оценки самоподобия ответов
    similarity_score = evaluate_self_similarity(text, text.name)
    # Преобразование результата в числовой формат и возвращение
    return [float(similarity_score)] if similarity_score is not None else None

# Применение пользовательской метрики к данным
annotated_chats = wl.applyUDFsFromSchema(CHATS_DATASET)

# Вывод первых 10 записей с оценкой самоподобия менее 0.8
filtered_chats = annotated_chats[annotated_chats["prompted_self_similarity"] < 0.8]
print(filtered_chats.head(10))

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

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

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

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

Спасибо за прочтение (: Будем рады видеть вас в комментариях!

© Habrahabr.ru