比较链式输出
假设你有两个不同的提示(或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偏好存在偏见,包括像输出顺序这样的平凡偏见。在选择偏好时,可能不考虑“真实情况”,这可能导致得分不具有实用性。
