Inspect 评 分 指标: 准确 率、 标准 误、 头条 数字 和 多 轮 归 约
Inspect 的评分器默认带准确率和标准误。头条数字可用 headline_metric 指定;分组用 grouped(),聚类标准误用 stderr(cluster=)。多轮默认按 mean 归约,frequency() 则看未归约的每一轮。

本文目录
Inspect 评分指标:准确率、标准误、头条数字和多轮归约
本文说明 Inspect 里评分指标怎么选、怎么分组、怎么报标准误,以及多个轮次的分数怎样合成一个数。Inspect 是英国人工智能安全研究所发布的评测框架。指标(metric)是把许多样本的分数收成一个数或一组数的函数。本文依据 Inspect 文档《Scoring Metrics》,许可证为 MIT。
版权声明按 MIT 许可证保留:Copyright (c) 2024 UK AI Security Institute。允许复制、修改和再发布本软件及其配套文档,但须保留版权声明和许可声明。
###### 概述
每个评分器自带一个或多个内置指标,通常是准确率(accuracy,答对的比例)和标准误(stderr,standard error,样本均值波动有多大的估计)。这些是该评分器最常用的指标。
可以把另一份指标清单传给 Task,从而换掉评分器的内置指标。例如:
Task(
dataset=dataset,
solver=[
system_message(SYSTEM_MESSAGE),
multiple_choice()
],
scorer=choice(),
metrics=[custom_metric()]
)如果仍要计算内置指标,就把它们和自定义指标一起再写上:
metrics=[accuracy(), stderr(), custom_metric()]内置指标
Inspect 带有计算准确率、均值等的简单内置指标。它们从 inspect_ai.scorer 模块导入。下面是这些指标的摘要。完整函数签名和选项见 inspect_ai.scorer 参考。
- accuracy():计算全部答案里正确的比例。对标成 1 或 0 的对错分数,可以选择把部分正确记为 0.5。
- mean():全部分数的均值。
- var():全部分数上的样本方差。
- std():全部分数上的标准差。聚类标准误的算法见下文。
- stderr():均值的标准误。
- bootstrap_stderr():对均值做自助法估计后的标准差。默认取 1000 个样本,可用 num_samples 修改。自助法(bootstrap)是反复有放回地重抽样本,看估计值怎么晃。
- ci():均值的置信区间,报告成带下界和上界的映射。默认是 95% 的学生 t 区间(均值 ± t · 标准误,自由度为 n − 1;若设置了 cluster=,自由度为簇数 − 1)。用 level= 改置信水平,或用 method="bootstrap" 做百分位(聚类)自助区间。它可用于判断,例如两个模型的准确率区间是否重叠。对二元分数,优先用 ci_wilson()。
- ci_wilson():二元(0/1)分数均值的 Wilson 得分置信区间,把它当成二项比例。Wilson 区间是一种让比例的置信界始终落在 0 和 1 之间的算法。对准确率这类二元分数,优先用它而不是 ci():界始终留在 [0, 1] 内,并且在小样本、比例靠近 0 或 1 时仍然校准得好。用 level= 改置信水平;加上 cluster= 时,区间使用由聚类标准误导出的有效样本量。
- frequency():每个不同的类别分数值出现的频率。默认报告已评分观察上的比例;传入 normalize=False 则报告次数。若任务使用轮次(epoch,同一道样本重复跑的一次),frequency() 收到的是尚未归约的各轮分数,因此每个已评分的样本轮次都是一次观察。
- categorical():给类别评分器的便利函数。它返回带可选声明类别的 frequency(),类别通常来自一个 StrEnum,这样次数为零的类别也会出现,并且指标可以从日志重算。
- krippendorff_alpha():多个评判者或评分器之间的 Krippendorff α 系数。Krippendorff α 是衡量多名评判者是否一致的一个数,1 表示完全一致。每个样本的 Score.value 必须是一串逐评判者的评分(每个评判者一个元素)。做法是把 multi_scorer() 和 collect 归约器配在一起(见「多评判者一致性」)。该指标在全部样本上计算 α。测量尺度要和评分匹配:
- level="nominal"(默认):无序类别,例如对错标签或类别编号。任何不同都算完全不一致。
- level="ordinal":间隔不相等的有序类别,例如 1–5 的李克特评分。距离按中间等级的边际频率加权(Krippendorff 2007)。
- level="interval":等间隔尺度上的数字。距离是数值差的平方。
对 ordinal 或 interval,非数字评分需要一个 to_float 映射。传入 to_float=value_to_float(),把 CORRECT、INCORRECT、PARTIAL、NOANSWER 这些字符串映射成 1、0、0.5、0;也可以为自己的文本类别提供排序。两个评判者时,名义尺度上的 α 与 Scott 的 π 重合(其多评判者类比是 Fleiss 的 κ);由于 α 有小样本修正,精确重合只发生在大样本极限。
头条指标
有几处要把一次评测收成单个数字:查看器里的日志列表、运行时的进度行,以及 evals_df() 的 score_headline_* 列。默认这个数字是第一个分数的第一个指标。这个默认通常是对的,但在评分器把 stderr() 排在 accuracy() 前面、一个便宜的健全性评分器跑在前面,或你关心的数字来自某个特定轮次归约器时,就不是。
在 Task 上声明 headline_metric,可以明确指定一个。普通字符串指名评分器:
Task(
dataset=dataset,
scorer=[sanity_check(), model_graded_qa()],
headline_metric="model_graded_qa",
)传入 HeadlineMetric 可以多说一点。它的四个字段都可选,没写的字段退回默认约定:
| 字段 | 匹配对象 | 不设置的含义 |
|---|---|---|
| scorer | EvalScore.scorer | 每一个分数。若同时设置了 metric,则是带有该指标的第一个分数;否则是第一个分数。 |
| score | EvalScore.name | 所选评分器的每一个分数。 |
| metric | EvalScore.metrics 的一个键 | 所选分数的第一个指标。 |
| reducer | EvalScore.reducer | 第一个匹配的分数,不论它的归约器是什么。 |
因此常见情形可以写得很短。在单评分器任务里改选另一个指标:
from inspect_ai.log import HeadlineMetric
headline_metric=HeadlineMetric(metric="accuracy")返回多个值的评分器会为每个值的键产生一个分数,名字就是那个键。字符串形式用 "<scorer>.<score>" 指向其中一个:
headline_metric="grader.coherence"这个简写只在字符串上可用。HeadlineMetric 的字段按字面匹配,所以名字里本身带点的评分器(@scorer(name="judge.v2"))要写在 scorer 字段上:
headline_metric=HeadlineMetric(scorer="grader", score="coherence", metric="mean")
headline_metric=HeadlineMetric(scorer="judge.v2")按组计算指标
grouped() 把给定指标用到由样本元数据里某个键定义的子组上,为每一组各建一个指标,并另建一个跨全部样本或全部分组汇总的 "all" 指标。每个样本对用于分组的那个键都必须有值。
例如,要为 Sample 元数据里每个不同的 “category” 变量各做一个准确率指标:
@task
def gpqa():
return Task(
dataset=read_gpqa_dataset("gpqa_main.csv"),
solver=[
system_message(SYSTEM_MESSAGE),
multiple_choice(),
],
scorer=choice(),
metrics=[grouped(accuracy(), "category"), stderr()]
)传给 Task 的指标会覆盖 choice() 评分器的默认指标。
注意:"all" 指标默认是在全部样本上计算所选指标。若希望它取各分组值的均值,传入 all="groups":
grouped(accuracy(), "category", all="groups")可以用 name_template 自定义指标名。模板用 {group_name} 作为组值的占位符:
grouped(accuracy(), "category", name_template="category_{group_name}")这样得到的指标名是 category_physics、category_chemistry 等,而不是 physics、chemistry。它不影响 “all” 指标,那个可以另外命名。
按键汇总
有些评分器发出的 Score.value 是字典,每个样本有若干数值字段。aggregate() 把给定指标用到由 key 选出的单个字段上,因此任何标准指标都可以按键计算:
from inspect_ai.scorer import aggregate, mean, stderr
@metric
def element_accuracy() -> Metric:
return aggregate("element_acc", mean())
@metric
def element_accuracy_stderr() -> Metric:
return aggregate("element_acc", stderr())默认把抽出的值直接交给内部指标,因此适用内部指标自己的转换。只有内部指标自己不能转换该值时才传入 to_float=。例如把 "C"、"I" 这种字符串等级送进期望数值的 mean():
from inspect_ai.scorer import value_to_float
aggregate("verdict", mean(), to_float=value_to_float())键缺失(或存在但为 None)的样本走 on_missing:"error"(默认)会抛出,"skip" 排除该样本,"zero" 把它计为 0.0。某一键上的 NaN 始终视为未评分并跳过,与框架在别处处理 NaN 分数的方式一致。若每个样本都被滤掉,aggregate() 返回 NaN。
聚类标准误
stderr() 支持用 cluster 参数计算聚类标准误。聚类标准误把同一组里的样本当成相关的,避免把重复观测当成独立样本而把标准误算得过小。多数评分器已经把 stderr() 当作内置指标,所以要算聚类标准误,就得为任务指定自定义指标,这会覆盖评分器的内置指标。
例如,按 Sample 元数据里的 “category” 变量聚类:
@task
def gpqa():
return Task(
dataset=read_gpqa_dataset("gpqa_main.csv"),
solver=[
system_message(SYSTEM_MESSAGE),
multiple_choice(),
],
scorer=choice(),
metrics=[accuracy(), stderr(cluster="category")]
)传给 Task 的指标会覆盖 choice() 评分器的默认指标。
多评判者一致性
krippendorff_alpha() 衡量给每个样本打分的那些评判者之间的一致程度,因此它需要 Score.value 是一列逐评判者的评分,而不是一个已经汇总的值。多数 multi_scorer() 归约器("mean"、"mode" 等)把多个评判者收成单个值,丢掉了 α 需要的逐评判者信息。改用 "collect" 归约器,它把每个评判者的评分留成一个列表:
from inspect_ai.scorer import krippendorff_alpha, multi_scorer
@task
def judge_reliability():
return Task(
...,
scorer=multi_scorer(
scorers=[judge1, judge2, judge3],
reducer="collect",
),
metrics=[krippendorff_alpha(level="ordinal")],
)这时每个样本的 Score.value 是 [judge1_rating, judge2_rating, judge3_rating],krippendorff_alpha() 跨样本汇总来计算 α。无序类别标签用 level="nominal",李克特式评分用 level="ordinal",连续分数用 level="interval"。细节见该指标的文档字符串。
自定义指标
也可以用带 @metric 装饰器的函数添加自己的指标。下面是 mean 指标的实现:
import numpy as np
from inspect_ai.scorer import Metric, Score, metric
@metric
def mean() -> Metric:
"""Compute mean of all scores.
Returns:
mean metric
"""
def metric(scores: list[SampleScore]) -> float:
return np.mean([score.score.as_float() for score in scores]).item()
return metricScore 类包含一个 Value,它是若干标量和集合类型的联合。为方便起见,Score 提供一组访问方法,把值当成更简单的形式(例如上面用了 score.as_float())。
示例
这个任务把一个浮点评分器和自定义的 pass_rate() 指标配在一起。pass_rate() 是分数达到或超过阈值的样本比例,并与内置的 mean() 和 stderr() 一起报告:
from inspect_ai import Task, task
from inspect_ai.dataset import Sample
from inspect_ai.scorer import (
Metric,
SampleScore,
Score,
Target,
mean,
metric,
scorer,
stderr,
)
from inspect_ai.solver import TaskState, generate
@metric
def pass_rate(threshold: float = 0.5) -> Metric:
"""Proportion of samples scoring at or above `threshold`."""
def metric(scores: list[SampleScore]) -> float:
if not scores:
return 0.0
passed = [s for s in scores if s.score.as_float() >= threshold]
return len(passed) / len(scores)
return metric
@scorer(metrics=[mean(), stderr(), pass_rate()])
def word_overlap():
async def score(state: TaskState, target: Target) -> Score:
output = state.output.completion.lower()
words = target.text.lower().split()
hits = sum(1 for word in words if word in output)
return Score(value=hits / len(words) if words else 0.0)
return score
@task
def colors():
return Task(
dataset=[Sample(input="Name three primary colors.", target="red green blue")],
solver=generate(),
scorer=word_overlap(),
)评测日志为 word_overlap 评分器报告 mean、stderr 和 pass_rate。因为 pass_rate 通过 @scorer(metrics=...) 挂在评分器上,它会自动应用。也可以像概述里那样,按任务覆盖评分器的指标。
合并多个轮次
若任务跑了不止一个轮次,每个样本会生成多个分数。指标通常作用在归约后的分数视图上:每个样本各轮的分数被合成一个分数。归约器(reducer)就是做这个合成的函数。
默认的归约视图用 mean 建立。可以传入 Epochs 来指定其他策略。Epochs 同时包含次数,以及一个或多个用来合并样本分数的归约器。例如:
注意:一个整数形式的轮次覆盖只改次数,并保留任务定义的归约器。这适用于 eval(..., epochs=n)、eval_set(..., epochs=n) 和 --epochs n。要替换任务的归约器,传入 Epochs(n, reducer),或把 --epochs n 与 --epochs-reducer reducer 合用。把 --epochs n 与 --no-epochs-reducer 合用,则关闭轮次归约。
带有固定自定义归约器的任务,可以直接在 Task 里定义次数;用户覆盖次数时,归约器仍然有效。当改变次数也应该改变归约器配置时,任务参数 epochs 才有用。
@task
def gpqa():
return Task(
dataset=read_gpqa_dataset("gpqa_main.csv"),
solver=[
system_message(SYSTEM_MESSAGE),
multiple_choice(),
],
scorer=choice(),
epochs=Epochs(5, "mode"),
)也可以指定不止一个归约器,指标会用每一个归约器各算一遍。例如:
@task
def gpqa():
return Task(
...
epochs=Epochs(5, ["at_least_2", "at_least_5"]),
)有些指标需要尚未归约的轮次分数。这些指标在原始的样本–轮次分数上只跑一次,即使配置了显式归约器:
@scorer(metrics=[accuracy(), frequency()])
def my_scorer() -> Scorer:
...在 epochs=Epochs(5, "mode") 时,accuracy() 收到每个样本一个按众数归约的分数,而 frequency() 收到全部已评分的样本–轮次值。有多个归约器时,需要归约的指标对每个归约器跑一次,不归约的指标跑一次。
若用 Epochs(n, []) 或 --no-epochs-reducer 关闭归约器,旧的 scores="auto" 指标会收到未归约的样本–轮次分数,以保持既有行为。明确声明 scores="reduced" 的指标,在存在多个轮次时要求有一个归约器。
内置归约器
Inspect 包含若干内置归约器,摘要如下。
| 归约器 | 说明 |
|---|---|
| mean | 归约成全部分数的平均。 |
| median | 归约成全部分数的中位数。 |
| mode | 归约成最常见的分数。 |
| majority | 归约成被超过一半的待归约分数所持有的那个分数,否则为未评分。 |
| max | 归约成全部分数的最大值。 |
| pass_at_{k} | 给定 k 个轮次,至少有 1 个样本正确的概率(https://arxiv.org/pdf/2107.03374)。 |
| pass_k_{k} | 全部 k 次轮次尝试都成功的概率(https://arxiv.org/pdf/2406.12045)。 |
| at_least_{k} | 至少 k 个样本正确则为 1,否则为 0。 |
| collect | 把全部分数收成一个列表,保留每个值,而不是汇总。 |
注意:内置归约器会为分数计算一个归约值,并且只有当 answer 和 explanation 在所有轮次上相等时才填写这两个字段。metadata 字段始终归约成第一个轮次的 metadata 值。若自定义指标函数对字段归约需要不同行为,还应实现自己的自定义归约器,并以某种方式合并或保留字段。
自定义归约器
也可以用带 @score_reducer 装饰器的函数添加自己的归约器。下面是 mean 归约器代码的一个略作简化的版本:
import statistics
from inspect_ai.scorer import (
Score, ScoreReducer, score_reducer, value_to_float
)
@score_reducer(name="mean")
def mean_score() -> ScoreReducer:
to_float = value_to_float()
def reduce(scores: list[Score]) -> Score:
"""Compute a mean value of all scores."""
values = [to_float(score.value) for score in scores]
mean_value = statistics.mean(values)
return Score(value=mean_value)
return reduce指标与归约器
指标可以用 scores 声明自己需要未归约的分数才能正确计算。例如:
@metric(scores="unreduced")
def frequency_by_label() -> Metric:
...默认 scores="auto",因此除非显式关闭轮次归约器,指标收到的是归约后的分数。需要每个样本一个分数的指标用 scores="reduced";需要每个样本轮次一个分数的指标用 scores="unreduced"。
| 指标的分数视图 | 使用轮次时的输入 |
|---|---|
| scores="auto" | 归约后的分数,除非轮次归约器被显式关闭。 |
| scores="reduced" | 归约后的分数;存在多个轮次分数时要求有一个轮次归约器。 |
| scores="unreduced" | 原始的样本–轮次分数,每个已评分轮次一次观察。 |
例如,frequency() 声明 scores="unreduced",因为它报告的是全部已评分观察上类别结果的分布。
出处:UK AI Security Institute,Scoring Metrics,https://inspect.aisi.org.uk/metrics.html,MIT License,Copyright (c) 2024 UK AI Security Institute。
觉得有用,转给同事
微信扫码
用微信扫一扫,在手机上打开后即可转发。