训练数据里有没有完全缺席的人群,先做分组与标签的交叉计数,再决定是否需要补采。只看“总共有多少条样本”会遗漏空组;只看每组人数,又可能遗漏“某组没有正类”的组合。下面用 pandas 做一个可重复的覆盖检查,并把零样本组合导出。
这里检查的是数据覆盖,不能由人数直接判定模型公平性。本文不训练模型,也没有预测结果;因此不能给出各组的误报率、漏报率,更不能证明模型对某组没有偏差。

先确定应该覆盖哪些组,以及一行代表什么
假设你要训练一个面向成年人的文本分类器,已确认使用场景包含 18–29、30–49、50–64 和 65 岁以上四组,标签为 negative、positive。分组范围应来自实际服务范围和数据说明,不能只从当前文件里的值生成:文件里完全没有“65+”,自动提取分组就永远看不到这项缺口。
年龄组只是虚构教学字段;八条样本不是实际用户统计。真实项目先确认字段来源、分组定义和使用权限,不需要本任务的个人标识不必收集。如果年龄本来未知,保留未知状态并单独统计,不能猜测年龄填入某一组。
- sample_id:一条训练样本的唯一编号。
- age_group:按事先确定的定义分组。
- label:已经确认含义的训练标签。
本文按样本行计数。如果同一个人贡献了十条文本,这十行不等于十个人;检查独立人数时还需要另一个稳定人员编号,并明确同一人标签变化时如何计数。
运行完整交叉表示例
示例在 Windows、Python 3.11.15、pandas 3.0.6 下实际运行。新建空目录,在终端安装依赖,把下面完整代码保存为 coverage_audit.py;输出文件保存在运行命令的当前目录。
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install pandas==3.0.6
.\.venv\Scripts\python.exe coverage_audit.py
macOS 或 Linux 用 python3 创建环境,随后把命令中的解释器路径换成 .venv/bin/python。安装失败时先确认解释器版本和包安装结果,再运行正文代码。
pandas.crosstab 官方文档说明,默认交叉表计算频数;DataFrame.reindex 官方文档说明如何按指定行列重新索引,并用 fill_value 填补新增位置。本例先检查缺失值和未知分类,再重新索引,避免把输入错误悄悄变成合法的零计数。
from pathlib import Path
import pandas as pd
# Fictional sample rows, not population statistics.
data = pd.DataFrame({
'sample_id': ['s01','s02','s03','s04','s05','s06','s07','s08'],
'age_group': ['18-29','18-29','18-29','30-49','30-49','30-49','50-64','50-64'],
'label': ['negative','negative','positive','negative','positive','positive','negative','negative']
})
groups = ['18-29', '30-49', '50-64', '65+']
labels = ['negative', 'positive']
if data[['sample_id','age_group','label']].isna().any().any():
raise ValueError('Missing sample ID, age group or label: inspect before counting')
if data['sample_id'].duplicated().any():
raise ValueError('Sample ID is duplicated: define the counting unit first')
if not data['age_group'].isin(groups).all() or not data['label'].isin(labels).all():
raise ValueError('Unknown group or label: update schema or correct original data')
counts = pd.crosstab(data['age_group'], data['label']).reindex(
index=groups, columns=labels, fill_value=0)
print(counts.to_string())
print('rows_counted=', int(counts.to_numpy().sum()))
for group in groups:
if counts.loc[group].sum() == 0:
print('empty_group=', group)
for label in labels:
if counts.loc[group, label] == 0:
print('zero_cell=', group, label)
Path('coverage_counts.csv').write_text(counts.to_csv(), encoding='utf-8-sig')
assert counts.to_numpy().sum() == len(data)
assert counts.loc['50-64', 'positive'] == 0
assert counts.loc['65+'].sum() == 0
核对计数,解释两个不同的缺口
上述代码的实际输出如下:
label negative positive
age_group
18-29 2 1
30-49 1 2
50-64 2 0
65+ 0 0
rows_counted= 8
zero_cell= 50-64 positive
empty_group= 65+
zero_cell= 65+ negative
zero_cell= 65+ positive
核对总计为 8,再检查 50–64 岁的 positive 为 0、65+ 整行合计为 0。这两项分别说明“某个组与标签的组合缺席”和“整个组缺席”;后一项不能靠增加已有组的样本解决。
| 观察 | 能说明什么 | 下一步 |
|---|---|---|
| 50–64 / positive 为 0 | 这份文件中没出现这个组合 | 查标签定义与采集条件,确认该组合在目标场景是否可能出现 |
| 65+ 总计为 0 | 声明的目标人群没有进入这份样本 | 核对筛选条件和来源覆盖,若属于服务范围则规划合法补采 |
| 18–29 有 3 条 | 该组存在,但不能推断数量足够 | 结合目标分布、任务难度和后续独立评估确定需求 |
打开 coverage_counts.csv,确认行列顺序与打印结果一致。代码中的断言检查计数守恒;如果触发断言,不继续训练,先检查筛选前后行数和样本编号。
换成真实文件时,先解决输入错误
保留下面的校验和计数逻辑,只把 data 的构造替换为 pd.read_csv(“train.csv”, dtype={“sample_id”: “string”}),并按实际列名修改三个字段。仍由业务定义 groups 和 labels;不要直接用 unique() 覆盖这两个清单。
- 缺失分组或标签:单独导出缺失清单,补查来源;不要删掉后宣称已经覆盖完整。
- 编号重复:先决定是重复导出还是一个编号有多行记录,再固定计数单位。
- 出现清单之外的组:检查大小写、分组版本与拼写;确有新组时更新定义和说明。
- 零样本组合:确认任务上是否可能出现。结构上不可能的组合应注明原因,不能机械要求每格都有相同数量。
零样本不自动等于采集错误,非零也不自动等于代表性充分。还需要核对目标场景中的分组分布、数据来源、时间范围,以及各组后续评估的不确定性;不能规定一个通用的“每格至少多少条”就宣称数据合格。
为什么补齐人数后仍要评估模型?
人数回答“有没有、多少”,预测指标回答“模型对这些样本如何表现”。模型训练后,应在独立、适合使用场景的数据上按组检查误报与漏报,并说明样本量。一个人数均衡的文件仍可能有标签噪声、来源偏差或性能差异;覆盖表只是帮助你发现数据缺口的一步。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30608.html