指南
教程:用检查和能看到图像的评判模型评估图像
评估一个生成图像的函数,这里是一个同时编写说明文字的商品缩略图生成器。用无需模型的度量检查缩略图,用一个能看到每张缩略图的模型评判说明文字,用一个人来度量这个评判模型,然后发布一次真正的修改。每一步都在本机上运行,无需模型也无需联网,另有一个可选步骤会换用真实的视觉模型。
你将构建什么
一个商品目录服务接收一张商品照片,保存一张缩略图,并写一行说明文字,说出商品的颜色。三个问题决定一个版本能否发布:
- 每张缩略图是否符合目录的要求:网页格式,最多 256 乘 256 像素,最多 50 kB?这是一项度量,由 media_check 根据缩略图的字节进行。
- 每张缩略图是否仍然是商品的照片,而不是空白图像、裁剪图或另一件商品?image_matches_reference 通过感知哈希把它与照片进行比较。
- 每条说明文字是否说出了顾客看到的颜色?这需要眼睛,所以会向一个评分细则评判模型展示缩略图。
照片和缩略图留在项目文件夹中。系统按引用记录它们(media/v1):每个文件在哪里、它的类型、大小和 sha256。用例、运行、评判模型和门禁等术语在核心概念中有定义;关于媒体的一切都在图像、音频和视频中。
前提条件
- Python 3.11 或更高版本,以及带媒体扩展的 Oloproof,该扩展添加了用于比较图像的 Pillow:
python3 -m venv .venv
. .venv/bin/activate
pip install 'oloproof[media]'- 示例项目,它随软件包一起提供。把它复制到一个新目录并在那里工作:
oloproof init --example media product-thumbnails
cd product-thumbnails- 为替身评判模型空出 8798 端口(如果不行,就在两处同时修改)。
在“可选:真实的视觉模型”之前的每一步都是离线且确定性的:没有 API 密钥,没有提供方账户,没有费用。
文件
product-thumbnails/
app.py the thumbnailer under test (baseline)
app_v2.py the candidate change
judge_server.py a stand-in vision judge speaking the OpenAI API on 127.0.0.1
rubrics/caption_names_colour.md the judge's rubric
oloproof.yaml the suite
release.yaml rules for a run
compare.yaml a rule for a comparison
data/products.jsonl 20 cases
photos/ the 20 product photos the cases name
make_photos.py how the photos were drawn
labels/reviewer_verdicts.csv one person's verdicts on the baseline's captions
fill_labels.py copies those verdicts into a labelling sheet在 product-thumbnails/ 中运行每条命令。
应用记录它的图像
# app.py (excerpt)
@system(name="thumbnailer", version=VERSION, records=("media/v1",))
def describe(case: dict[str, Any]) -> dict[str, str]:
photo_path = HERE / case["photo"]
photo = Image.open(photo_path).convert("RGB")
thumb = photo.copy()
thumb.thumbnail((320, 320))
out = HERE / "thumbs" / VERSION / photo_path.name
out.parent.mkdir(parents=True, exist_ok=True)
thumb.save(out)
given = media_file(photo_path, role="input")
made = media_file(out, width=thumb.width, height=thumb.height)
current_case().media([given, made])
centre = photo.getpixel((photo.width // 2, photo.height // 2))
return {"caption": f"A {colour_name(centre)} product."}media_file(path) 描述一个文件:它的 file:// 位置、MIME 类型、大小和 sha256,并以文件名命名。role="input" 标记系统收到的照片;缩略图是输出,即默认值。current_case().media(...) 把两者记录为一个 media/v1 产物,而 oloproof.yaml 中的 records: [media/v1] 声明系统会记录它。不会复制任何内容:检查或评判模型稍后会读回每个文件,并拒绝字节已不再与记录匹配的文件。
这个基线有两个缺陷。它的缩略图宽 320 像素,而它的说明文字说出的是照片中心像素的颜色,只要商品位于一侧,这个像素就是背景。每个版本把它的缩略图写在 thumbs/<version>/ 下,因此之后的运行绝不会覆盖之前的运行所记录的文件。
数据集
{"id": "p03", "input": {"photo": "photos/p03.png"}, "expected": {"photo": "photos/p03.png", "colour": "yellow", "shape": "triangle"}}input 是系统接收的内容。expected.photo 是与缩略图进行比较的参考;colour 和 shape 是给阅读用例的人看的,没有评估器读取它们。
选择评估器
evaluators:
# The catalogue's limits, measured from the thumbnail's bytes: a web format, at most 256 by
# 256 pixels, at most 50 kB.
- type: media_check
criterion: thumbnail_fits
kind: image
formats: [png, jpeg, webp]
max_width: 256
max_height: 256
max_bytes: 50000
# The thumbnail is still the product's photo: a perceptual hash within 10 of 64 bits.
- type: image_matches_reference
criterion: same_photo
reference_field: photo
max_distance: 10
# The caption, judged by a model that sees the thumbnail.
- type: rubric_judge
criterion: caption_names_colour
provider: openai_compatible
model: stand-in-vision
base_url: http://127.0.0.1:8798/v1
rubric_file: rubrics/caption_names_colour.md
media: outputmedia_check 度量每张输出图像:格式取自它的开头字节,尺寸取自它的文件头,绝不取自系统所声明的值。image_matches_reference 对缩略图和 expected.photo 指明的照片计算哈希,当两个哈希在 64 位中最多相差 10 位时通过;缩放或重新压缩只会让哈希变动几位,另一张图片则会让它变动很多位。两者都不调用模型,所以都从第一次运行起就作出决策。
media: output 把用例的输出图像附加到评判模型的请求中,每张都在提示词中给出名称。评判模型在人的标签表明它与人一致之前不决定任何事情,所以第三条规则要等待这一点。
替身评判模型
评分细则评判模型把它的提示词和附加的图像发送给一个模型,并读回 {"pass": true|false, "rationale": "..."}。这里的模型是 judge_server.py,它在 127.0.0.1 上使用同样的 API,但它不是模型:它解码附加的缩略图,找出除背景的白色和灰色之外最常见的颜色,并让说出这个颜色的说明文字通过。在第二个终端中启动它并让它保持运行:
python judge_server.py --port 8798策略
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
rules:
- id: thumbnails-fit
metric: thumbnail_fits
kind: observed_count
max_failures: 0
- id: same-photo
metric: same_photo
kind: observed_count
max_failures: 0
- id: caption-floor
metric: caption_names_colour
min: 0.80每张缩略图都必须符合要求,并且必须是它的照片。至少 80% 的说明文字必须说出正确的颜色,只有当区间的下界达到 80% 时才作出决策。
运行
oloproof runGate: BLOCK (exit 1)
│ thumbnails-fit │ thumbnail_fits │ FAIL │ observed_failures_exceed_limit │
│ same-photo │ same_photo │ PASS │ observed_failures_within_limit │
│ caption-floor │ caption_names_colour │ INSUFFICIENT_EVIDENCE │ evaluator_not_validated │
│ thumbnail_fits │ 0.0% │ [0.0%, 16.9%] │ 0 / 20 observed · 0 missing · 0 excluded │
│ same_photo │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ caption_names_colour │ 60.0% │ [36.0%, 80.9%] │ 12 / 20 observed · 0 missing · 0 excluded │没有一张缩略图符合要求,每张缩略图都是它的照片,20 条说明文字中有 12 条说出了正确的颜色。评判模型的规则尚未决定,因为还没有人度量过这个评判模型。
查看失败项
oloproof inspect RUN_ID --failuresp03
output: {"caption": "A white product."}
thumbnail_fits: failed
measured: p03.png: width 320 px is above 256 px
caption_names_colour: failed
judge text, not verified: the product is yellow; the caption says 'a white product.'检查的理由是一项度量,标记为 measured;评判模型的理由是它自己的话,没有人核对过。在 oloproof export RUN_ID 之后,照片和缩略图也会出现在工作台中用例页面上每个结论的旁边,从这个文件夹读取;托管工作区让你的浏览器从 oloproof collect 获取它们,自己从不持有它们(图像、音频和视频)。
用一个人来度量评判模型
从运行的用例中随机抽取一个样本放进一张表,不带评判模型的结论,让一个人查看每张缩略图和说明文字,然后导入他们的结论。这里 fill_labels.py 代替那个人,从 labels/reviewer_verdicts.csv 复制他们的结论:
oloproof labels export RUN_ID --criterion caption_names_colour --sample 20 --local --out sample.csv
python fill_labels.py sample.csv
oloproof labels import sample.csv然后对照这些标签度量评判模型,并记录是谁做的:
oloproof evaluators list
oloproof evaluators validate EVALUATOR_ID --by alicecaption_names_colour: sha256:53605dd6... is now VALIDATED
agreement 100.0% [83.1%, 100.0%] · 20 of 20 labelled cases agreed · 0 labelled but not judged · kappa 1.000能看到图像的评判模型与读取文本的评判模型的度量方式完全相同。在托管工作区中,评审队列向评审者展示每个用例的图像,由他们的浏览器从你的收集器获取(评判模型)。
oloproof gate RUN_ID --policy release.yamlthumbnails-fit: FAIL (observed_failures_exceed_limit)
same-photo: PASS (observed_failures_within_limit)
caption-floor: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)现在评判模型可以作出决策了,而 60% 不够。
做一次真正的修改
app_v2.py 生成 256 像素的缩略图,并说出照片中除背景之外最常见的颜色,无论商品位于何处。把它复制到 app.py 上覆盖,在 oloproof.yaml 的 system 下设置 version: main-colour,保持评判模型运行,然后:
oloproof runGate: ALLOW (exit 0)
│ thumbnails-fit │ thumbnail_fits │ PASS │ observed_failures_within_limit │
│ same-photo │ same_photo │ PASS │ observed_failures_within_limit │
│ caption-floor │ caption_names_colour │ PASS │ lower_bound_meets_minimum │评判模型是经过验证的同一个评估器版本,所以它的结论有效。
把候选与基线进行比较
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlthumbnail_fits: +100.0 points [+52.9, +100.0] · 20 paired · 0 missing · 0 excluded
caption_names_colour: +40.0 points [+1.8, +72.7] · 20 paired · 0 missing · 0 excluded
captions-better caption_names_colour superiority PASS difference_above_zero可选:真实的视觉模型
这一步离开了离线路径。它需要一个能读取图像的模型:本地可以用带视觉模型的 Ollama(ollama pull qwen2.5vl),无需密钥也无需费用;云端可以用 provider: anthropic 或 openai 并带上 api_key_env,按每张图像以及每个词计费。
# live_judge.yaml
type: rubric_judge
criterion: caption_names_colour
provider: openai_compatible
model: qwen2.5vl
base_url: http://localhost:11434/v1
rubric_file: rubrics/caption_names_colour.md
media: output
timeout_s: 300oloproof evaluators try live_judge.yaml这会用真实模型评判你的评审人已标注过的用例,并报告它的一致性,而不采用它。要采用它,就把它放进 oloproof.yaml,替换掉替身:新模型就是新的评估器版本,所以要像上面那样对照标签验证它。
故障排除
| 症状 | 原因与修复 |
|---|---|
| caption_names_colour 全部缺失 | 评判服务器没有运行,或不在 base_url 上;oloproof inspect RUN_ID --failures 会显示原因。 |
| image_matches_reference 停止运行并提到 Pillow | 安装媒体扩展:pip install 'oloproof[media]'。 |
| 一个用例因 media_unavailable 而缺失 | 运行之后,某个已记录的文件被移动或删除了。只要你保留一次运行,就保留它所记录的文件。 |
| 运行以 is not the recorded content 停止 | 运行之后,某个已记录的文件被修改了。检查或评判模型绝不会把不同的字节当作已记录的字节来读取;把每个版本的文件写到各自的位置。 |
| 验证之后仍出现 evaluator_not_validated | 你修改了评判模型(模型、端点、端口、评分细则、media),产生了一个新版本。验证那个版本。 |
局限
- 替身评判模型只是数像素。它演示的是工作流程,而不是评判质量。
- 感知哈希读取的是布局和明暗,而不是颜色:它能把照片的缩略图与空白图像或不同的场景区分开,却无法区分同一个摄影棚里的两件商品。有大片空白区域的平面图形比照片更容易让它变动。
- 二十个标签给出的一致性区间很宽。对于你所依赖的评判模型,请以随机且盲测的方式标注更多。
- 音频和视频以同样的方式工作,只是设置不同:见图像、音频和视频。