如何判断AI在胡说?- 利用离散语义熵和困惑度检测大型语言模型中的幻觉

在处理大型语言模型(LLMs)时,识别幻觉可能会很棘手。我们不应仅仅依赖LLM作为判断者(因为它仍然可能出错,许多评估框架仅使用它来检测幻觉),而可以利用困惑度、蕴含关系和离散语义熵来更好地识别潜在的幻觉。尽管我在这里使用LLM来检测蕴含关系,但这并不是必须的。也就是说,这种方法最适合那些有明确、事实性答案的问题——那些不太模糊或主观的问题。你对使用这些组合指标来更好地检测幻觉有什么看法?我理解代码可以改进/优化,但目标是快速测试其工作原理。


from openai import OpenAI
import numpy as np
from pydantic import BaseModel
import time

client = OpenAI(api_key="key")

class CheckEntailment(BaseModel):
    label: str

def check_entailment(fragment1: str, fragment2: str) -> bool:
    """检查蕴涵"""
    messages = [
        {
            "role": "user",
            "content": f"""您有一个大型语言模型的两个响应。
检查一个响应的含义是否由另一个响应蕴涵,或者是否存在矛盾。
如果蕴涵,则返回“0”。如果矛盾,则返回“1”。
仅返回标签,不做任何解释。
\n 响应 1:\n {fragment1}\n\n 响应 2:\n {fragment2}""",
        }
    ]
    completion = client.beta.chat.completions.parse(
        model="gpt-4o-mini",
        messages=messages,
        temperature=0.1,
        logprobs=True,
        top_logprobs=2,
        response_format=CheckEntailment,
    )
    entailment = False
    # print(completion.choices[0].logprobs.content[3].top_logprobs)
    for top_logprob in completion.choices[0].logprobs.content[3].top_logprobs:
        print(top_logprob.token, np.round(np.exp(top_logprob.logprob), 2))
        if "0" in top_logprob.token and np.exp(top_logprob.logprob) > 0.7:
            entailment = True
    return entailment


def calculate_entropy(probs):
    """
    计算熵
    """
    probs = np.array(probs)
    probs = probs / probs.sum()
    probs = probs[probs > 0]
    entropy = -np.sum(probs * np.log2(probs))
    return entropy


some_tricky_questions = [
“阿拉巴马州与哪个州的边界最长?是佛罗里达州还是田纳西州?”,
“谁主持了 2007 年的英国游戏节目倒计时:a) Nick Hewer b) Richard Whiteley c) Jeff Stelling?”,
“小问题:哪位黑眼豆豆乐队成员是唯一主持周六夜现场的人?”,
“FIS 高山滑雪世界锦标赛在 20 世纪 80 年代的哪一年在阿根廷举办?”,
“1-6 之间有多少个巴西数字?”,
“哪位以色列数学家在 20 世纪 70 年代建立了在线序列存储库?”,
“写出 7 个包含三个连续双字母的英文单词。无需提供解释,只需说出单词即可。",
# 在不应产生幻觉的地方添加两个问题
“印度的首都是哪里?”,
“CPU 的全称是什么?”,
]


for question in some_tricky_questions:
    print("question", question)
    messages = [{"role": "user", "content": f"{question}"}]
    gpt_response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        temperature=0.1,
        logprobs=True,
        max_completion_tokens=60,
    )
    time.sleep(2)
    # 使用低温响应获取困惑度分数
    logprobs = [token.logprob for token in gpt_response.choices[0].logprobs.content]
    perplexity_score = np.round(np.exp(-np.mean(logprobs)), 2)
    # 使用第一个响应初始化集群
    clusters = [[gpt_response.choices[0].message.content]]
    # 使用更高的温度生成更多响应并检查蕴涵
    gpt_response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        n=7,
        temperature=0.9,
        logprobs=True,
        max_completion_tokens=60,
    )
    time.sleep(2)
    # check entailment and form clusters
    responses = [choice.message.content for choice in gpt_response.choices]

    for response in responses[1:]:
        found_cluster = False
        for cluster in clusters:
            if check_entailment(cluster[0], response):
                cluster.append(response)
                found_cluster = True
                break
        if not found_cluster:
            clusters.append([response])
    cluster_probs = [len(cluster) / (len(responses) + 1) for cluster in clusters]
    discrete_entropy = calculate_entropy(cluster_probs)
    print("clusters", clusters)
    print("no of clusters", len(clusters))
    print("perplexity", perplexity_score)
    print("entropy", discrete_entropy)

在这段代码中,首先初始化一个布尔变量 `found_cluster` 为 `False`。接着,遍历 `clusters` 中的每个聚类,如果 `check_entailment` 函数判断当前聚类的第一个元素与 `response` 之间存在蕴含关系,则将 `response` 添加到该聚类中,并将 `found_cluster` 设置为 `True`,然后跳出循环。如果没有找到合适的聚类,则将 `response` 作为一个新聚类添加到 `clusters` 中。

接下来,计算每个聚类的概率 `cluster_probs`,其值为该聚类中元素的数量与所有响应数量加一的比值。然后,利用 `calculate_entropy` 函数计算离散熵 `discrete_entropy`。最后,打印出聚类的内容、聚类的数量、困惑度分数和熵值。

更多