Skip to main content

比较链式输出

假设你有两个不同的提示(或LLMs)。你如何知道哪个会生成“更好”的结果?

一种自动预测首选配置的方法是使用PairwiseStringEvaluator,例如PairwiseStringEvalChain[1]。这个链式结构会提示LLM选择首选的输出,给定特定的输入。

对于这个评估,我们需要3个东西:

  1. 一个评估器
  2. 一个输入数据集
  3. 2个(或更多)LLMs、链式结构或代理来进行比较

然后我们将汇总结果来确定首选模型。

步骤1. 创建评估器

在这个例子中,你将使用gpt-4来选择首选的输出。

from langchain.evaluation import load_evaluator

eval_chain = load_evaluator("pairwise_string")

API参考:

第2步:选择数据集

如果您已经有了LLM的真实使用数据,可以使用一个代表性的样本。更多的例子可以提供更可靠的结果。我们将在这里使用一些关于如何使用langchain的示例查询。

from langchain.evaluation.loading import load_dataset

dataset = load_dataset("langchain-howto-queries")

API参考:

Found cached dataset parquet (/Users/wfh/.cache/huggingface/datasets/LangChainDatasets___parquet/LangChainDatasets--langchain-howto-queries-bbb748bbee7e77aa/0.0.0/14a00e99c0d15a23649d0db8944380ac81082d4b021f398733dd84f3a6c569a7)

0%| | 0/1 [00:00<?, ?it/s]

第三步:定义要比较的模型

在这个案例中,我们将比较两个代理。

from langchain import SerpAPIWrapper  
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
from langchain.chat_models import ChatOpenAI


# 初始化语言模型
# 你可以通过添加 openai_api_key="<your_api_key>" 来添加自己的 OpenAI API 密钥
llm = ChatOpenAI(temperature=0, model="gpt-3.5-turbo-0613")

# 初始化用于搜索功能的 SerpAPIWrapper
# 将 openai_api_key="<your_api_key>" 中的 <your_api_key> 替换为你实际的 SerpAPI 密钥
search = SerpAPIWrapper()

# 定义代理提供的工具列表
tools = [
Tool(
name="搜索",
func=search.run,
coroutine=search.arun,
description="在回答有关当前事件的问题时非常有用。你应该提出有针对性的问题。",
),
]

API 参考:

functions_agent = initialize_agent(
tools, llm, agent=AgentType.OPENAI_MULTI_FUNCTIONS, verbose=False
)
conversations_agent = initialize_agent(
tools, llm, agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=False
)

第四步:生成回复

在评估模型之前,我们将为每个模型生成输出。

from tqdm.notebook import tqdm  
import asyncio

results = []
agents = [functions_agent, conversations_agent]
concurrency_level = 6 # 同时运行的代理数量。如果OpenAI限制速率,可能需要减少。

# 为了加快速度,我们只运行这个数据集的前20个例子
# 这将导致下游的置信区间更大。
batch = []
for example in tqdm(dataset[:20]):
batch.extend([agent.acall(example["inputs"]) for agent in agents])
if len(batch) >= concurrency_level:
batch_results = await asyncio.gather(*batch, return_exceptions=True)
results.extend(list(zip(*[iter(batch_results)] * 2)))
batch = []
if batch:
batch_results = await asyncio.gather(*batch, return_exceptions=True)
results.extend(list(zip(*[iter(batch_results)] * 2)))

步骤 5. 评估

现在是评估结果的时候了。 对于每个代理响应,运行评估链选择首选输出(或返回平局)。

随机选择输入顺序以减少一个模型出现这种情况的可能性(仅仅因为它首先出现就被优先选择)。

import random  

def predict_preferences(dataset, results) -> list:
preferences = []

for example, (res_a, res_b) in zip(dataset, results):
input_ = example["inputs"]
# 翻转硬币以减少持久的位置偏差
if random.random() < 0.5:
pred_a, pred_b = res_a, res_b
a, b = "a", "b"
else:
pred_a, pred_b = res_b, res_a
a, b = "b", "a"
eval_res = eval_chain.evaluate_string_pairs(
prediction=pred_a["output"] if isinstance(pred_a, dict) else str(pred_a),
prediction_b=pred_b["output"] if isinstance(pred_b, dict) else str(pred_b),
input=input_,
)
if eval_res["value"] == "A":
preferences.append(a)
elif eval_res["value"] == "B":
preferences.append(b)
else:
preferences.append(None) # 无偏好
return preferences
preferences = predict_preferences(dataset, results)  

打印出偏好比例。

from collections import Counter  

name_map = {
"a": "OpenAI Functions Agent",
"b": "Structured Chat Agent",
}
counts = Counter(preferences)
pref_ratios = {k: v / len(preferences) for k, v in counts.items()}
for k, v in pref_ratios.items():
print(f"{name_map.get(k)}: {v:.2%}")
OpenAI Functions Agent: 95.00%  
None: 5.00%

估计置信区间

结果似乎很明确,但如果您想更好地了解我们的置信度,即模型"A"(OpenAI函数代理)是首选模型,我们可以计算置信区间。

下面使用威尔逊得分来估计置信区间。

from math import sqrt

def wilson_score_interval(
preferences: list, which: str = "a", z: float = 1.96
) -> tuple:
"""使用威尔逊得分估计置信区间。

详见:https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Wilson_score_interval
获取更多细节,包括何时使用以及何时不应使用。
"""
total_preferences = preferences.count("a") + preferences.count("b")
n_s = preferences.count(which)

if total_preferences == 0:
return (0, 0)

p_hat = n_s / total_preferences

denominator = 1 + (z**2) / total_preferences
adjustment = (z / denominator) * sqrt(
p_hat * (1 - p_hat) / total_preferences
+ (z**2) / (4 * total_preferences * total_preferences)
)
center = (p_hat + (z**2) / (2 * total_preferences)) / denominator
lower_bound = min(max(center - adjustment, 0.0), 1.0)
upper_bound = min(max(center + adjustment, 0.0), 1.0)

return (lower_bound, upper_bound)


for which_, name in name_map.items():
low, high = wilson_score_interval(preferences, which=which_)
print(
f'"{name}"在95%的置信度下将有{low:.2%}{high:.2%}的时间被优先选择。'
)

输出结果如下:

"OpenAI函数代理"在95%的置信度下将有83.18%到100.00%的时间被优先选择。
"结构化聊天代理"在95%的置信度下将有0.00%到16.82%的时间被优先选择。

打印出p值。

from scipy import stats

preferred_model = max(pref_ratios, key=pref_ratios.get)
successes = preferences.count(preferred_model)
n = len(preferences) - preferences.count(None)
p_value = stats.binom_test(successes, n, p=0.5, alternative="two-sided")
print(
f"""p值为{p_value:.5f}。如果零假设成立(即所选的评估链实际上对模型之间没有偏好),
那么在{successes}次试验中,有{p_value:.5%}的概率观察到{preferred_model}被优先选择。"""
)

输出结果如下:

p值为0.00000。如果零假设成立(即所选的评估链实际上对模型之间没有偏好),
那么在19次试验中,有0.00038%的概率观察到OpenAI函数代理被优先选择。

/var/folders/gf/6rnp_mbx5914kx7qmmh7xzmw0000gn/T/ipykernel_15978/384907688.py:6: DeprecationWarning: 'binom_test' is deprecated in favour of 'binomtest' from version 1.7.0 and will be removed in Scipy 1.12.0.
p_value = stats.binom_test(successes, n, p=0.5, alternative="two-sided")

1. 注意:自动化评估仍然是一个开放的研究课题,最好与其他评估方法一起使用。LLM偏好存在偏见,包括像输出顺序这样的平凡偏见。在选择偏好时,可能不考虑“真实情况”,这可能导致得分不具有实用性。