比较链式输出
假设你有两个不同的提示(或LLMs)。你如何知道哪个会生成“更好”的结果?
一种自动预测首选配置的方法是使用PairwiseStringEvaluator
,例如PairwiseStringEvalChain
[1]。这个链式结构会提示LLM选择首选的输出,给定特定的输入。
对于这个评估,我们需要3个东西:
- 一个评估器
- 一个输入数据集
- 2个(或更多)LLMs、链式结构或代理来进行比较
然后我们将汇总结果来确定首选模型。
步骤1. 创建评估器
在这个例子中,你将使用gpt-4来选择首选的输出。
from langchain.evaluation import load_evaluator
eval_chain = load_evaluator("pairwise_string")
API参考:
- load_evaluator 来自
langchain.evaluation
第2步:选择数据集
如果您已经有了LLM的真实使用数据,可以使用一个代表性的样本。更多的例子可以提供更可靠的结果。我们将在这里使用一些关于如何使用langchain的示例查询。
from langchain.evaluation.loading import load_dataset
dataset = load_dataset("langchain-howto-queries")
API参考:
- load_dataset 来自
langchain.evaluation.loading
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 参考:
- initialize_agent 来自
langchain.agents
- Tool 来自
langchain.agents
- AgentType 来自
langchain.agents
- ChatOpenAI 来自
langchain.chat_models
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偏好存在偏见,包括像输出顺序这样的平凡偏见。在选择偏好时,可能不考虑“真实情况”,这可能导致得分不具有实用性。