category
在这篇文章中,我们将着眼于使用通过OpenAI API可用的置信度分数。
在第一部分中,我们将从对这些分数的温和探索开始,并在自定义聊天界面的帮助下了解它们的含义。
在第二节中,我们将探讨在代码中使用置信度得分。
探索“信心”
首先,快速入门LLM在其响应中为每个令牌做了什么:
- 该模型为其词汇表中的每个标记输出一个值(~100000个值)
- 然后,这些值被转化为我们(怀疑地)称之为“概率”的值。这些价值观是本文的重点。
- 然后以概率方式选择单个令牌(有时是具有最高值的令牌,有时不是)并在响应中使用
现在,让我们对一些术语进行排序:我们在这篇文章中使用的值并不是真正的“概率”(在“某事发生的可能性有多大”的意义上),也不是任何有意义的“信心”。它们只是LLM输出的数字,经过调整使其为正并加1(对于数学家来说,这足以获得任何一组数字的标签“概率分布”)。
因此,你可以将“概率”添加到术语列表中,这些术语在学术界意味着一件事,但在现实世界中却略有不同,导致广泛的误解(以及“理论”、“意义”等)。
在我看来,将这些价值观视为“信心”是有道理的,但请记住,LLM就像人类一样:仅仅因为他们有信心,并不意味着他们是对的。简而言之,除非另有证明,否则这些值毫无意义。
让我们来看一些使用聊天界面的示例,该界面执行以下操作:
- 对于LLM响应中的每个标记,它都用红色下划线表示信心——较亮的红色表示较低的信心。
- 当你将鼠标悬停在一个单词上(在带有鼠标光标的设备上)时,它会显示该位置的前10个可能标记,按置信度得分排名。
您可以在下面尝试,也可以在gptconfidence.streamlit.app上尝试。
File "/home/adminuser/venv/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 85, in exec_func_with_error_handling
result = func()
^^^^^^File "/home/adminuser/venv/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 576, in code_to_exec
exec(code, module.__dict__)File "/mount/src/gpt_confidence/main.py", line 31, in <module>
client = get_client()
^^^^^^^^^^^^File "/home/adminuser/venv/lib/python3.12/site-packages/streamlit/runtime/caching/cache_utils.py", line 168, in wrapper
return cached_func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^File "/home/adminuser/venv/lib/python3.12/site-packages/streamlit/runtime/caching/cache_utils.py", line 197, in __call__
return self._get_or_create_cached_value(args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^File "/home/adminuser/venv/lib/python3.12/site-packages/streamlit/runtime/caching/cache_utils.py", line 224, in _get_or_create_cached_value
return self._handle_cache_miss(cache, value_key, func_args, func_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^File "/home/adminuser/venv/lib/python3.12/site-packages/streamlit/runtime/caching/cache_utils.py", line 280, in _handle_cache_miss
computed_value = self._info.func(*func_args, **func_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^File "/mount/src/gpt_confidence/main.py", line 28, in get_client
return OpenAI(api_key=st.secrets.OPENAI_API_KEY)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^File "/home/adminuser/venv/lib/python3.12/site-packages/openai/_client.py", line 123, in __init__
super().__init__(File "/home/adminuser/venv/lib/python3.12/site-packages/openai/_base_client.py", line 844, in __init__
self._client = http_client or SyncHttpxClientWrapper(
^^^^^^^^^^^^^^^^^^^^^^^File "/home/adminuser/venv/lib/python3.12/site-packages/openai/_base_client.py", line 742, in __init__
super().__init__(**kwargs)
如果你想在本地运行它(并使用gpt-4o-mini以外的模型),你可以在这里克隆仓库。
让我们从简单开始,让它选择一个数字。
首先要注意的是,对于第二个令牌,它可能会说“选择”、“选择”或“去”等。尽管只有21%的机会选择,但它还是选择了。(这是第一课,如果你还不知道的话:LLM不会只选择“最有可能的下一个令牌”,除非你配置它们这样做。)
然后,它继续选择/选择/去选择数字5,我们可以看到这根本不是一个统一的选择。所以,如果你有疑问:你不应该使用LLM进行“随机”选择。
你可能想知道你是否可以用这些信息来检测幻觉。好吧,是的,也不是。
这有点偏离主题,但试图回答这个问题将加深我们对这些价值观意义的理解。
一个有趣的例子是不可能的问题,比如列出名人的名字中有一个interpunct。
这是问题和答案,显示了名字开头的令牌的置信度工具提示。
让我们从模型的角度来考虑这一点,记住模型只关心预测另一个令牌。
假设它收到了提示和高达1的响应。**它的工作是计算出哪个令牌会进入下一个插槽,即第一个人的名字。现在说“我不知道”或“这是一个愚蠢的问题”已经太晚了……它必须说点什么,但没有好的答案,所以它试图通过想出一个字母(J、M、G等)来推卸责任。你可以看到它对这些不太有信心,所以即使是得分最高的代币也低于30%。这种行为是一个好兆头,表明这个特定的插槽没有明显的标记,而且模型即将说一些不正确的话。
但是,你能在不查看单个标记的情况下,在整个反应的水平上检测到幻觉吗?那么,将上面的红色量与下面的红色量进行比较,这个问题有一个相当明确的答案:
它知道答案的问题和它被迫产生幻觉的问题之间似乎有很大的区别。
但并非所有幻觉都涉及低信心。
在这里,模型非常确定一个完全有效的查询将无法工作。
当然,并非所有低置信度的表征都意味着幻觉,正如我们在第一个例子中看到的那样,模型可以说“选择”、“挑选”或“选择”。这就是自然语言的工作方式,通常有几种方式来表达同一件事。
最后,我们不希望LLMs总是正确的,因为它们向人类学习,而人类总是错误的(有时是故意的!)
在这里,该模型非常确定,一个无稽之谈的心理学概念是真实的,因为很多人谈论它就像它是真实的一样。
(宗教将是另一个很好的例子,除了该模型经过训练,可以避开涉及宗教和真理的问题。)
所以,不,查看信心分数并不是检查LLM是否真的正确的神奇方法,尽管有迹象表明它可以帮助发现一些幻觉病例。
让我们朝着这篇文章的目标前进,尝试一个模型可能出错的封闭问题:哈萨克斯坦的首都。(如果你不了解哈萨克斯坦的最新消息:有一段时间首都是阿斯塔纳,然后在2019年改为努尔苏丹,然后在2022年又改回阿斯塔纳,当时有人意识到“阿斯塔纳”在哈萨克语中意为“首都”。)
模型的训练数据在这个问题上是不一致的,因此模型无法确定资本是多少。
您可以在“Ast”令牌的工具提示中看到,它(错误地)回答Nur(-Sultan)的可能性为88%,但在这次特定的运行中,它选择了Ast(ana)。
记住,模型所做的只是一次预测一个令牌,所以一旦选择了“Ast”,下一个令牌肯定会是“ana”,其余的响应将继续与该选择保持一致。
如果它碰巧选择了“努尔”而不是“阿斯塔纳”,那么其余的回应将被迫支持这一说法,方便地“忘记”它知道首都在2022年变回阿斯塔纳。
或者另一种思考方式:一旦回复断言首都是努尔苏丹,在回复的其余部分,法学硕士将只利用努尔苏丹是首都时产生的训练数据(这是一个有点可疑的说法,但这可能是一种思考黑匣子内发生的事情的有趣方式)。
旁注:这显示了人类和LLM学习方式的根本区别。我们人类是按顺序学习的。如果一个新事实与一个现有事实相冲突(只有一个可能是真的),我们会执行一些冲突解决操作来找出真正的真相。与此同时,法学硕士以概率的方式学习:如果一些培训数据显示阿斯塔纳是首都,其中一些数据显示努尔苏丹是首都,法学硕士就会知道首都要么是努尔苏丹,要么是阿斯塔纳。
如果你仍然认为这些值代表了简单英语意义上的“概率”,请考虑一下:
- 如果你问GPT-4o“哈萨克斯坦的首都是努尔苏丹吗?”,它会说是(85%)。
- 如果你问它“哈萨克斯坦的首都是阿斯塔纳吗?”,它会说是(91%)。
让我们进入一些代码。
以编程方式使用置信度
对于简单的情况,您应该尝试将响应压缩到一个令牌中(因此是一个置信度分数)。这意味着要么将你的问题结构化为多项选择题,要么指示模型从一系列单个标记词中选择(例如是/否)。
让我们从一个简单的是/否问题开始。
import math
from openai import OpenAI
client = OpenAI()
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
dict(
role="user",
content="Is the Great Wall of China visible from the Moon?",
)
],
temperature=0,
max_tokens=1,
logprobs=True,
)
choice = completion.choices[0]
confidence = math.exp(choice.logprobs.content[0].logprob)
print(f"Answer: {choice.message.content} ({confidence:.4%})")
# Answer: No (60.5926%)
有趣的部分是:
- 温度=0,以确保我们得到具有最高置信度的令牌。
- max_tokens=1,因为我们只希望在响应中有一个令牌。
- logprobs=True,告诉API我们希望在响应对象中使用“对数概率”。
我不完全理解为什么OpenAI返回对数概率而不仅仅是概率,但它们确实如此,所以我们需要使用math.exp将这些值转换回0到1之间的值。
如果你想知道,返回的logprob值不受温度的影响。
顺便说一句,API还有一个logit_bias属性,理论上应该允许我们强制模型在响应中使用某些令牌(如“是”和“否”),但我没有运气让它可靠地运行。
接下来,我们将加入一些现实世界的复杂性,并扩展它来回答有关图像的问题。
假设你正在构建一个系统,要求用户上传驾照(或驾照,如果你愿意的话)的照片进行身份验证。您希望执行一些自动检查,以确保它是来自您所在国家的当前许可证,并且许可证上的名称与用户名匹配。
你可以尝试训练像ResNet这样的传统机器学习模型,但这只能让你进行图像识别,而不能理解图像;您将无法询问模型所看到的内容。为此,多模式LLM可能会获得更好的结果。
from datetime import datetime
import base64
import math
from pathlib import Path
from openai import OpenAI
client = OpenAI()
def classify_with_confidence(file_path, name) -> tuple[str, float]:
user_prompt = f"""
Is this a current Australian driver's licence, belonging to {name}?
Answer only 'Yes' or 'No'.
Today's date is {datetime.today().strftime("%Y-%m-%d")}.
"""
# Encode the image as a base64 data URL
encoded_img = base64.b64encode(Path(file_path).read_bytes())
img_url = f"data:image/jpeg;base64,{encoded_img.decode()}"
# Create a message from the prompt and the image
message = dict(
role="user",
content=[
dict(type="text", text=user_prompt),
dict(type="image_url", image_url=dict(url=img_url)),
],
)
# Call the API, requesting logprobs
completion = client.chat.completions.create(
model="gpt-4o",
messages=[message],
temperature=0,
max_tokens=1,
logprobs=True,
)
# Return the response and confidence
choice = completion.choices[0]
confidence = math.exp(choice.logprobs.content[0].logprob)
return choice.message.content, confidence
is_valid, confidence = classify_with_confidence(
file_path="my_licence_expired.jpg",
name="David Gilbertson",
)
print(f"Answer: {is_valid} ({confidence:.4%})")
# Answer: No (99.9955%)
虽然使用LLM时不需要训练数据,但您仍然需要一些示例进行评估和校准。
在评估过程中,你可能会发现模型有时是错误的,那么这个价值百万美元的问题就变成了:响应的正确性和返回的置信度得分之间是否存在相关性?
在识别许可证的情况下,你会在LLM上举一堆例子,并为每个例子记录两条信息:它是对是错,以及模型的信心。如果幸运的话,当你绘制这些图时,你会看到这样的东西:
每条蓝线是正确答案的实例,每条橙线是错误答案的实例。有一些重叠,但我们可以看到一个非常明确的迹象,即在这种情况下,信心并非完全没有意义。
下一步是考虑你更喜欢假阳性还是假阴性,并选择一个合适的临界点。在代码中,这可能看起来像这样:
if is_valid == "Yes":
if confidence > 0.95:
... # Success
else:
... # Success, but flag for human review
elif is_valid == "No":
if confidence > 0.99:
... # Fail. Ask the LLM what's wrong, send that info to user
else:
... # Success, but flag for human review
随着时间的推移,你应该建立一组评估,这样你就可以定期调整这些截止点(并在新模型出现时轻松测试其准确性)。
顺便说一句:尽管我在这个玩具示例中展示了什么,但你不应该让LLM做你可以在常规代码中做的事情。如果我真的在执行这项检查,我会要求LLM返回到期日期。然后,我会在代码中进行比较,以确定它是否是最新的。(你知道吗,当比较两个日期并计算出一个日期是否先于另一个日期时,Python的准确率是100%?这真是一个美好的时刻!)
现在,如果你运行你的评估,发现正确性和“信心”之间没有明确的关系,那么你就倒霉了。你所能做的就是尝试不同的法学硕士。对于一项任务,我发现GPT-4o给出了有用的置信度分数,而GPT-4o-mini的分数没有用(例如,错误时的高置信度)。截至2024年8月,Gemini和Claude API不支持logprobs。
与人工智能工程领域的所有事情一样,如果其他事情都不起作用,请将其停放并设置提醒,以便在三个月后再次尝试。
多选选择
用多选代替是/否逻辑很容易,只需将选项以数字列表的形式呈现,并告诉LLM用一个数字回答。在GPT-4o中,所有高达999的整数都是单个令牌,因此您不必局限于A/B/C/D类型的问题。我用一份约200个国家的名单对其进行了测试,并给了它一些小测验问题(“哪个国家是铂金的主要生产国?”),它很容易按数字选择正确答案。
但我们可以做一些比选择一个选项更有趣的事情:我们可以从一个令牌槽中提取多个选项。
例如,您可以使用此功能为文章添加多个标签,或者在以下示例中,选择一些类型应用于电影。
提示符看起来像这样:
Which genre best describes the movie 'Gladiator'?
Select one from the following list and return only the number:
0. Action
1. Adventure
2. Animation
3. Biography
4. Comedy
5. Crime
6. Documentary
7. Drama
8. Family
9. Fantasy
10. Film-Noir
11. History
12. Horror
13. Music
14. Musical
15. Mystery
16. Romance
17. Sci-Fi
18. Short
19. Sport
20. Thriller
21. War
22. Western
在解析了单个令牌响应之后,我们最终会得到一个类似这样的值字典:
Drama: 43.46%
History: 33.85%
Adventure: 14.11%
Action: 8.56%
我们将要求模型选择一个类型,并强制它返回一个令牌,但我们也会要求它考虑的前10个其他令牌。我们通过传入top_logprobs参数来实现这一点。
import math
from openai import OpenAI
client = OpenAI()
movie_name = "Gladiator"
genres = ["Action", "Adventure", "Animation", "Biography", "Comedy",
"Crime", "Documentary", "Drama", "Family", "Fantasy", "Film-Noir",
"History", "Horror", "Music", "Musical", "Mystery", "Romance", "Sci-Fi",
"Short", "Sport", "Thriller", "War", "Western"]
genre_string = "\n".join([f"{i}. {g}" for i, g in enumerate(genres)])
prompt = f"""\
Which genre best describes the movie {movie_name!r}?
Select one from the following list and return only the number:
{genre_string}
"""
# Call the API, requesting logprobs and 10 top_logprobs
completion = client.chat.completions.create(
model="gpt-4o",
messages=[dict(role="user", content=prompt)],
max_tokens=1,
logprobs=True,
top_logprobs=10,
)
# Extract the options and confidences
label_dict = {}
for item in completion.choices[0].logprobs.content[0].top_logprobs:
if (confidence := math.exp(item.logprob)) > 0.01:
genre = genres[int(item.token)]
label_dict[genre] = confidence
for genre, confidence in label_dict.items():
print(f"{genre}: {confidence:.2%}")
记住,如果你要求top_logprobs=10,你总是会得到10个选项,并且不能保证它们都是合理的令牌。
例如,当它真的认为最佳答案是3时,前10个标记可能包括3、03和3(Unicode Chonky Three)。所有无意义的标记往往具有非常低的置信度得分,这就是为什么上述代码只包括置信度>0.01的类型。
具有高级提示
如果你愿意只输出一个令牌,上述方法效果很好。但是,如果你也想让模型使用思维链(CoT),或者解释为什么它认为许可证无效呢?
这仍然是可能的,只需要更多的代码来处理响应并提取置信度得分。下面是电影类型的例子,但我现在告诉LLM先考虑一下,然后把答案放在<answer>标签中。需要明确的是,<answer>标签并没有什么神奇之处,它只是在响应中寻找的一串字符。
在解析响应时,下面的代码构建了一个到目前为止的答案的运行字符串,如果以<answer>结尾,则意味着当前令牌必须是答案,因此它会循环该插槽的前10个令牌以提取多个类型,与上面的代码相同。
import math
from openai import OpenAI
client = OpenAI()
movie_name = "Gladiator"
genres = ["Action", "Adventure", "Animation", "Biography", "Comedy", "Crime",
"Documentary", "Drama", "Family", "Fantasy", "Film-Noir", "History", "Horror",
"Music", "Musical", "Mystery", "Romance", "Sci-Fi", "Short", "Sport", "Thriller", "War", "Western"]
genre_string = "\n".join([f"{i}. {g}" for i, g in enumerate(genres)])
prompt = f"""\
Which genre best describes the movie {movie_name!r}?
Consider a few likely genres and explain your reasoning,
then pick an answer from the list below
and show it in answer tags, like: <answer>4</answer>
{genre_string}
"""
# Call the API, requesting logprobs and 10 top_logprobs
completion = client.chat.completions.create(
model="gpt-4o-mini",
messages=[dict(role="user", content=prompt)],
logprobs=True,
top_logprobs=10,
)
# Extract the responses and confidences
label_dict = {}
text = ""
for tokenLogProb in completion.choices[0].logprobs.content:
# When we get to the token following '<answer>', extract alternatives listed in top_logprobs
if text.endswith("<answer>"):
for item in tokenLogProb.top_logprobs:
if (confidence := math.exp(item.logprob)) > 0.01:
genre = genres[int(item.token)]
label_dict[genre] = confidence
text += tokenLogProb.token
for genre, confidence in label_dict.items():
print(f"{genre}: {confidence:.2%}")
郑重声明:在这种情况下,预推理似乎使模型在所选类型中更加确定,因此用处不大。
那么,这一切是否比仅仅要求模型“JSON列表中的前几大流派”更好呢?这将取决于用例。让LLM给你一个真正可变数量的选项可能很棘手,无论内容如何,它们每次都会给你相似数量的示例/标签/流派。然而,在查看顶级替代方案时,你可以控制临界点(基于信心),所以如果有一个明显的答案,你会得到一个,如果有七个可行的选择,你会有七个。
这和所有即时的工程“智慧”一样,需要对自己的数据进行评估;在你能证明它适用于你的情况之前,一切都只是一个假设。
上述示例侧重于提取响应中单个令牌的置信度,但您可以将其扩展到多个令牌。
想象一下,你要求LLM返回一个JSON对象列表,每个对象都有一个状态字段“打开”或“关闭”。你可以循环返回的令牌,将它们连接成一个字符串(如上所述),每当字符串以“status”结尾时:“,你就知道下一个令牌将是一个状态。因此,你可以提取该令牌的置信度,将其保存在列表中,然后将该列表与JSON对象混合作为status_lisure。
使用置信度进行模型选择
最后一个用例:您可以使用快速/廉价的模型来尝试解决问题,如果它报告的置信度低,请切换到更好/更昂贵的模型(或更慢的过程,例如RAG步骤)。
与往常一样,只有当置信度得分与准确性相关时,这才有意义,到目前为止,我的发现表明,较小的模型比较大的模型具有更少的有用置信度。
- 登录 发表评论