第2章 把多模态原件做成可追溯数据包
工作阶段: 定义工作。
公共核心项目: 完成“工作场所设备巡检记录数据包”,把授权原件、候选提取、契约校验、问题分流、来源级划分和数据卡连成完整工程。
核心技术: 多模态信息提取、样本与标签、数据类型与模式、数据契约、质量检查、数据划分和来源追踪。
输入输出链: 授权材料与原件 → 来源索引 → AI候选提取 → 数据契约校验 → 人工复核 → 清洗数据与数据卡。
应用中的AI: 从不同形式材料中提取候选字段的多模态或语言模型;参考工程使用明确标记的离线候选回放。
协助开发的AI: 帮助设计字段、生成校验程序、解释错误日志和限定修改的项目型AI助手。
人的责任: 确认授权与标签含义,保存原件,处理冲突和缺失,批准数据版本。
主要交付物: 原始材料索引、数据契约、候选与清洗数据、问题记录、数据字典、数据卡和版本说明。
明确边界: 不收集无关身份信息;模型提取结果只是候选;无法确认的内容保留缺失,不用猜测填充。
跨章关系: 承接第1章的任务合同,为第3章检索、第5—9章模型训练和第10—12章系统交付提供可靠输入。
学习目标
完成本章学习后,你将能够:
- 为原始材料建立授权、来源、版本和保存位置记录;
- 根据工作输出设计字段、类型、单位、标签和缺失表示;
- 运行AI生成的完整校验程序,发现缺失、重复、越界和来源错误;
- 解释样本、特征、标签以及训练、验证、测试划分在当前数据包中的含义;
- 区分真实模型输出、离线候选回放和人工录入基线,不虚构效率或准确率;
- 将原件、数据、程序和说明组成另一组可以复现的数据包。
项目导入
人工智能项目常从“手头有不少材料”开始。图片存在聊天记录中,巡检表使用不同版本,录音没有时间和地点,环境数值缺少单位。多模态模型能够阅读图片、听取录音并概括文档,但它不能自动知道材料是否获准使用、某个编号指向哪台设备、18表示摄氏度还是湿度,也不能替专业人员确认标签。
本章完成一个可运行的工作场所设备巡检记录数据包。为避免使用真实个人与企业资料,样例包含模拟值班文字、虚拟传感器CSV、铭牌图片的观察文字替身、录音转写,以及一个专门用于隐私和授权阻断的测试原件。真实项目可把文字替身换成获准的图片和音频,但数据契约与验收门不变。
candidate_adapter.py把候选提取与具体模型隔离。课堂默认读取is_model_output=false的离线回放,断网也能运行;接入真实模型时必须登记模型与日期,不得上传未授权原件。普通程序依据数据契约检查字段,学生再回到原件复核。项目目标不是“全部识别”,而是让每条可用数据能回到来源、每个未知项没有被偷偷猜出。
本章成果
data/raw/与source_index.csv:5个模拟原件及授权、隐私、SHA-256和来源组;contract.json:字段、类型、单位、允许值、缺失和隐私规则;candidate_adapter.py与workflow.py:候选入口、校验、clean/issues与来源级划分;notebooks/ch02_data_package_lab.ipynb:逐格观察来源、候选、契约、基线和数据包;evaluate_fixed_cases.py:6条冻结正常、边界、陌生、隐私和故障测试;DATA_CARD.md、真实参考报告和可由另一组运行的acceptance.py。
AI协同开发路线
学生先阅读TASK_CONTRACT.md并核对索引哈希,再观察离线候选怎样进入契约校验。学生亲手只改变一个单位规则,预测clean与issues数量变化;随后让协助开发的AI只增加“原件哈希变化”测试,审查差异并运行全部回归。接入真实多模态模型是可选扩展:模型候选先导出为带来源声明的JSON包,再通过--candidates进入与离线回放完全相同的校验主链;它不能改变原件只读、候选不等于事实和未授权来源必须阻断这三条规则。最终由另一组按README复现。
第一节 保留原件、授权和来源,再提取候选信息
一、原件先于提取结果
模型处理的是原件的某种表示,提取结果不是原件本身。项目目录先建立raw/,只读保存收到的文件;再建立index.csv,为每个原件分配source_id。索引至少记录文件名、材料类型、提供者、获取日期、授权范围、是否含个人信息、文件摘要和备注。文件摘要是由普通程序根据文件内容计算的短标识,可帮助发现原件是否被替换,不代表授权或真实性。
未经授权的人脸、姓名、联系方式和对话不得为了“训练AI”进入共享数据。若材料含完成任务所必需的个人信息,应按单位制度缩小访问范围,课堂样例优先使用模拟或脱敏数据。删除原件也要保留删除原因和责任人,不能让清洗后的表格变成没有来历的孤立数据。
配套工程位于resources/ch02/project/,关键目录如下:
project/
├─ data/raw/ # 不被候选覆盖的模拟原件
├─ data/source_index.csv # 授权、隐私、SHA与来源组
├─ data/contract.json # 可执行数据契约
├─ data/candidates_replay.json # 明确非模型实测的离线候选
├─ candidate_adapter.py # 可替换候选提取接口
├─ workflow.py # clean、issues、划分与报告
├─ evaluate_fixed_cases.py # 冻结契约测试
├─ DATA_CARD.md
└─ acceptance.py
二、多模态模型只产生候选
输入给模型的提取任务应包含字段定义、允许值、缺失表示和来源编号。例如:“只从S003图片中读取可见铭牌信息;看不清的字段填null;每个非空值附原图中的证据短句或区域说明;不要根据同类设备猜型号。”参考S003正确地把时间与数值保留为null,而不是根据文件名或同类设备猜测。模型输出后,学生随机抽查,也要优先复核影响大、置信低或与其他材料冲突的字段。
参考运行没有调用多模态模型。OfflineReplayExtractor读取教师编写的8条候选并验证来源声明;CallableExtractor只定义获准模型的接入边界。这样既能学习模型如何进入工程,也不会把没有发生的模型调用写成结果。
文字清晰、格式稳定的扫描表格,可以把专用文字识别作为速度基线;需要同时理解图片、表格和上下文时,多模态模型可能更方便;少量关键字段由人工录入通常更可靠。技术选择取决于材料和错误后果,而不是默认“越通用越好”。
三、建立人工基线
参考工程提供4条教师确认的人工录入作为基线,并与相同4个模拟来源的离线候选比较地点、类型、时间、数值、单位、标签和证据7个字段。两者均为28/28字段相符。人工耗时没有测量,离线候选又不是真实模型输出,因此本章不声称AI更快或更准。
真实课堂可重新记录两名学生的录入时间、分歧和复核次数,再与获准模型比较。如果模型速度快但增加了无法发现的错误,应限制它只处理低风险字段;如果材料只有十几条且格式稳定,人工录入可能就是更合适的首版。没有这些真实记录时,正确结论是“尚不能比较效率”。
四、来源证据必须跟着数据走
候选记录至少保留source_id与evidence。后续发现数值异常时,可以回到具体原件,而不是重新翻遍文件夹。一个字段由多个来源共同支持时,应保留全部来源;来源相互冲突时,不要选择模型更喜欢的一项,要生成冲突记录并交责任人。
图示规划: 在此设置“原件—来源索引—候选字段—规则校验—人工复核—发布数据”的分层图,突出原件和候选数据不能相互覆盖。
第二节 设计字段、类型、单位、标签和数据契约
一、字段来自工作,不来自现有表格
字段应由后续工作输出反推。本项目要支持设施观察与统计,因此需要记录编号、来源、地点、设施类别、观察时间、现象、数值、单位、状态标签和复核状态。若后续不需要人员姓名,就不应因为原表中有一列而继续收集。
数据类型说明机器可以对值做什么。日期应使用统一格式,数值应保存为数值而不是“18度左右”这样的字符串,类别应来自允许列表。缺失不等于零,也不等于“正常”。本项目用null表示没有可确认的值,用status_label="待复核"说明记录仍需人判断。
二、单位和标签是专业约定
“温度=26”只有与摄氏度、测量时间、位置和设备状态一起才可解释。同一字段混用摄氏度和华氏度,程序仍可能画出漂亮曲线,却没有可比意义。数据契约需规定单位,转换时同时保留原值和转换规则。
标签是人按任务规则给样本的目标名称。本项目的status_label只允许“正常、待复核、超出服务范围”,不把“未知”强行标为“正常”。标签定义要写出正例、反例和边界。两位标注者对同一记录意见不同,说明规则可能不清楚,应先修订说明再扩大数据量。
三、把约定写成数据契约
数据契约可采用JSON或表格表达。本章首版规定:
| 字段 | 类型与允许值 | 是否必需 | 说明 |
|---|---|---|---|
record_id |
字符串,唯一 | 是 | 由项目规则生成,不由模型猜测 |
source_id |
必须存在于索引 | 是 | 指向原件 |
location |
字符串或null |
是 | 未知时保留null |
asset_type |
照明、桌椅、业务设备、环境之一 | 是 | 其他材料进入待复核 |
observed_at |
ISO日期时间或null |
是 | 不从文件修改时间猜测 |
value |
数值或null |
是 | 仅传感记录使用 |
unit |
℃、%RH、lx或null |
是 | 有数值时必须有单位 |
status_label |
正常、待复核、超出范围 | 是 | 需符合标签说明 |
evidence |
原件证据短句或区域说明 | 是 | 支持候选值 |
契约既给模型看,也给程序和使用者看。修改字段或允许值必须提升版本号,并说明旧数据如何迁移。直接在表格里增加一列而不更新说明,会让不同成员对同一字段产生不同理解。
四、区分样本、特征和标签
一条设施观察记录可以作为一个样本。地点、时间、数值和现象中的可计算表示可能成为特征;经过专业规则确认的状态是标签。哪些字段成为特征取决于具体模型,不是所有收集来的信息都应输入模型。来源编号用于追踪,不应因其与某批次偶然相关就成为预测依据。
后续训练模型时,训练集用于调整参数,验证集用于选择设置,测试集用于最后检查。划分要按设备、批次或时间隔离。把同一设备同一分钟产生的相邻记录随机分散到训练和测试,会让测试结果过于乐观,这称为数据泄漏。
第三节 处理错误、缺失、重复、偏差和数据泄漏
一、运行完整最小校验程序
下面是便于课堂阅读的校验主链缩略。权威的workflow.py还检查原件SHA-256、授权、隐私、数值范围和来源级划分,并把每个问题保存为稳定代码。它不调用模型,也不替人改正事实;候选提取与确定性校验被有意分开。
from pathlib import Path
from datetime import datetime
import csv
import json
ROOT = Path(__file__).resolve().parent
ALLOWED_ASSETS = {"照明", "桌椅", "业务设备", "环境"}
ALLOWED_LABELS = {"正常", "待复核", "超出范围"}
ALLOWED_UNITS = {"℃", "%RH", "lx"}
def read_source_ids(path):
with path.open(encoding="utf-8-sig", newline="") as file:
rows = list(csv.DictReader(file))
return {row["source_id"] for row in rows if row.get("authorized") == "yes"}
def valid_time(value):
if value is None:
return True
try:
datetime.fromisoformat(value)
return True
except (TypeError, ValueError):
return False
def check(row, source_ids):
problems = []
required_fields = {
"record_id", "source_id", "location", "asset_type", "observed_at",
"value", "unit", "status_label", "evidence"
}
for field in sorted(required_fields - set(row)):
problems.append(f"{field}:字段不存在")
required_text = ["record_id", "source_id", "asset_type",
"status_label", "evidence"]
for field in required_text:
if field in row and (
not isinstance(row[field], str) or not row[field].strip()
):
problems.append(f"{field}:必须是非空字符串")
location = row.get("location")
if location is not None and (
not isinstance(location, str) or not location.strip()
):
problems.append("location:必须是非空字符串或null")
if row.get("source_id") not in source_ids:
problems.append("source_id:不存在或未授权")
if row.get("asset_type") not in ALLOWED_ASSETS:
problems.append("asset_type:不在允许列表")
if row.get("status_label") not in ALLOWED_LABELS:
problems.append("status_label:不在允许列表")
if not valid_time(row.get("observed_at")):
problems.append("observed_at:格式错误")
value, unit = row.get("value"), row.get("unit")
if value is not None:
if isinstance(value, bool) or not isinstance(value, (int, float)):
problems.append("value:必须是数值")
if unit not in ALLOWED_UNITS:
problems.append("unit:有数值时必须使用规定单位")
elif unit is not None:
problems.append("unit:没有数值时应为null")
return problems
def main():
source_ids = read_source_ids(ROOT / "data" / "source_index.csv")
payload = json.loads(
(ROOT / "data" / "candidates_replay.json").read_text(encoding="utf-8")
)
rows = payload["records"]
clean, issues = [], []
seen_ids, seen_evidence = set(), set()
for row in rows:
key = (row.get("source_id"), row.get("evidence"))
problems = check(row, source_ids)
record_id = row.get("record_id")
if record_id in seen_ids:
problems.append("record_id:重复")
if key in seen_evidence:
problems.append("duplicate:来源与证据重复")
seen_ids.add(record_id)
seen_evidence.add(key)
if problems:
issues.append({"record_id": row.get("record_id"),
"problems": ";".join(problems)})
else:
clean.append(row)
out = ROOT / "output"
out.mkdir(exist_ok=True)
(out / "clean.json").write_text(
json.dumps(clean, ensure_ascii=False, indent=2), encoding="utf-8"
)
with (out / "issues.csv").open("w", encoding="utf-8-sig", newline="") as file:
writer = csv.DictWriter(file, fieldnames=["record_id", "problems"])
writer.writeheader()
writer.writerows(issues)
print(f"通过结构校验{len(clean)}条,待处理{len(issues)}条")
if __name__ == "__main__":
main()
程序中的允许列表和字段规则就是可执行的数据契约。实际运行8条候选后,4条进入clean,4条因未授权与个人信息、未知来源、错误单位或重复编号进入issues;同一记录可产生多个问题代码。学生不能把clean直接称为真实正确数据,它只表示通过当前契约,地点与标签仍需回看原件并由人确认。
二、错误、缺失和重复要分别处理
错误是已有值与证据不符,应更正并记录前值;缺失是没有足够信息,应保留null并说明是否需要补采;重复是同一事件被多次记录,应依据来源、时间和业务规则确认是否合并。不能用删除重复的方式抹掉真实重复发生的事件。
模型可能把设备铭牌上的额定电压当成环境传感值,也可能把图片拍摄日期当成观察日期。校验程序能发现部分格式问题,却不理解所有专业含义。因此问题表要包含“规则发现”和“人工发现”两类来源,避免把未被程序捕获的错误误认为不存在。
三、观察分布和偏差
统计不同地点、设备、时间段和标签的数量。如果“正常”记录全部来自白天,“待复核”全部来自夜间,模型以后可能利用时间代替真实状态。数据量大不能自动消除这种偏差。学生应查明差异来自真实工作分布、采集方便性还是标签规则。
控制变量实验。 保持原件、模型、提取说明和测试记录不变,只在B版数据契约中增加“有数值时单位必填”规则。先预测B版会发现多少此前看似通过的记录,再运行同一校验程序和同一候选数据。比较待处理数量及人工复核时间。若同时改变模型提示和候选文件,就无法说明变化来自契约规则。
四、设计失败测试和划分检查
fixed_validation_cases.json冻结6条不参与候选构造的样例:N01为正常记录,B01为有值无单位的边界,X01为陌生来源,P01为隐私文本,F01为错误日期,O01为越界湿度。evaluate_fixed_cases.py比较期望问题代码与实际代码;参考运行6/6通过。此外,单元测试还模拟索引哈希被改变、必需字段缺失和重复编号。
本书统一把这组在规则确定后才运行、不能为追逐结果而反复改写的封存样例称为固定测试。它与训练数据或候选回放彼此隔离,也不参与字段规则设计。固定测试通过只说明当前六类已声明行为可以重复,不能据此推断所有现实原件都能被正确整理。课堂新增一种来源、单位或敏感字段时,应先用非封存练习材料完善合同和程序,再为下一版本另建从未参与修改的新固定测试,并由另一位同学复核版本与来源记录。
清洗记录按来源组划分:S001与S002进入train,S003进入validation,S004进入test;同一来源不跨集合。这里只是治理演示,4条记录绝不足以训练模型。测试结论仍要分层:结构通过、来源可追、专业确认和可用于训练不是同一件事。
第四节 形成可复现的数据包并交给另一位使用者
一、固定目录、版本和生成步骤
可复现数据包不依赖作者记忆。配套README.md说明数据用途、目录、输入输出、真实结果和限制。运行入口只有一条:
cd resources/ch02/project
python acceptance.py
它检查26个必需文件和Python语法,重新生成数据包,运行固定测试与13项单元测试,再核对参考结果。清洗数据由脚本生成,不手工覆盖;人工修订进入新候选或修订记录,再重新生成。
数据卡回答:数据为解决什么任务而建;包含哪些对象和时间范围;怎样采集、授权、清洗和划分;有哪些缺失和偏差;适合与不适合什么用途;由谁维护;如何反馈错误。数据卡不是宣传页,应明确写出不足。
二、让另一组真正复现
交接组只获得项目包和说明,不接受原组口头提示。它应能核对文件摘要,运行校验,得到相同的通过与问题数量,并随机回查3条记录的来源。如果复现者不知道某列单位、找不到原件、运行后覆盖了人工记录,说明数据包仍有隐含假设。
参考复现得到:5个原件摘要全部一致,8条候选中clean=4、rejected=4,冻结测试6/6;离线候选和人工基线均为28/28字段相符。结论为usable_for_teaching_only,因为原件是模拟材料、候选不是模型实测、人工时间未记录。差异可能来自文件丢失、脚本版本不同、格式被修改或说明不完整,必须保留日志再做限定修改。
复现时还应逐层解释“通过”的含义。5个摘要一致,只能证明本次读取的原件没有相对索引发生变化;4条进入clean_records.json,只说明它们通过当前字段、单位、范围、授权和隐私规则,不证明现场事实一定正确;6/6冻结测试说明校验器按预先约定处理了正常、边界、陌生、隐私、错误日期和越界数值,不说明规则覆盖了所有现实情况。最后仍要打开issues.csv,沿source_id回到原件,判断拒绝原因是否准确。另一组若只报告“验收成功”而说不清这三层差别,复现任务仍未完成。
工程把故障也作为交付的一部分:原件哈希变化时停止,必需字段缺失时报错,记录编号重复时两条都进入问题表,未授权或含个人信息的来源不得因候选内容看似无害而放行。学生可以请大语言模型解释某个问题代码,但不能让它直接改写原件、伪造授权或猜测缺失单位。每一次人工纠正都应保留旧值、依据、修改人和新版本,再由相同入口重新生成数据包。
可追溯比表面整洁更重要。
三、专业迁移卡
专业迁移卡A:设计与品牌素材库
输入改为品牌手册、标志文件、字体授权、历史海报和图片素材;规范包括颜色值、最小尺寸、版权和可修改范围;高代价错误是误用标志、混淆授权或泄露未发布作品。交付物为素材索引、权利字段、可用规格、缩略图和审核记录,由品牌负责人及权利审核人员批准。专业迁移卡B:智能制造设备日志
输入改为设备台账、控制器日志、点检记录和经授权的铭牌照片;单位、采样频率、设备编号和停机时刻必须统一;高代价错误是跨设备合并、时间错位或把维护后数据泄漏进此前的测试。交付物为按设备与批次划分的数据包,由设备工程人员确认标签和使用边界。专业迁移卡C:生物培养观察数据
输入改为培养批次、环境记录、显微或外观图像和实验备注;需要遵守样本编码、实验条件和伦理授权;高代价错误是混淆批次、单位或把探索性观察当成诊断结论。交付物为批次隔离数据、数据字典和实验复核表,由实验指导人员确认。
四、作出发布决定
数据包可以“发布供本项目使用”“限内部复核使用”或“退回补采”。本参考包只获准作为教材模拟数据使用,不得对外宣称真实工作现场数据或模型性能。决定依据包括授权完整性、来源可追性、字段通过率、标签一致性、偏差、泄漏风险和复现结果。缺少关键授权、原件摘要改变或测试集泄漏时,即使程序能运行,也必须停止发布。
本章小结
本章把零散材料变成了可追溯的数据底座。原件、授权和来源必须在AI提取之前建立;多模态模型产生候选值,数据契约规定字段、类型、单位、标签和缺失方式,普通程序执行确定性检查,专业人员确认事实和标签。结构通过不等于数据正确,可用于观察也不等于可用于训练。可靠数据包应记录错误、缺失、重复、偏差和划分方法,并由另一组按照说明复现。数据质量不是模型训练前的一次清洁工作,而是贯穿更新和交付的责任。
关键术语
- 原始材料:未经项目处理、按收到状态保存并带有来源和授权记录的文件或记录。
- 样本:一次观察、训练或测试使用的具体数据对象。
- 特征:从样本中选取或计算、供模型寻找规律的输入表示。
- 标签:依据任务规则为样本确认的目标类别或目标值。
- 数据契约:对字段、类型、单位、允许值、缺失和版本形成的可执行约定。
- 数据字典:面向使用者解释每个字段含义、格式和取值的文档。
- 来源追踪:让处理后的数据能够回到其原件、授权和处理记录的机制。
- 数据泄漏:训练阶段获得了本应只在验证、测试或未来工作中出现的信息。
- 数据卡:说明数据用途、来源、范围、处理、偏差、限制和维护责任的文件。
- 复现:另一位使用者按说明从相同输入得到相同或可解释结果的过程。
目标测试
- 多模态模型从铭牌图片中读出的型号首先应被视为什么?
A. 已确认事实 B. 候选值 C. 训练标签 D. 授权证明 - 数值字段为26但没有单位时,最恰当的处理是什么?
A. 默认摄氏度 B. 删除记录 C. 标记待处理并回查来源 D. 改为0 - 下列哪种划分最能降低同一设备数据泄漏?
A. 相邻记录随机拆分 B. 按设备或批次整体划分 C. 复制到三个集合 D. 只使用训练集 - 校验程序显示“通过结构校验”意味着什么?
A. 专业事实一定正确 B. 已获得所有授权 C. 记录符合当前可执行字段规则 D. 一定适合训练 - 判断并改错:“缺失值和数值0都表示没有信息,可以相互替换。”
- 判断并改错:“数据越多,采集偏差和标签偏差就会自动消失。”
- 为一张经授权的设备铭牌图片设计四项来源索引字段,并说明各自用途。
- 给出一条有数值、单位缺失的候选记录,说明校验、人工复核和修订记录应怎样衔接。
- 设计一次只改变“单位必填规则”的控制变量实验,写出预测、保持不变条件和比较指标。
- 交接组可以运行脚本,但随机抽查时找不到5条记录的原件。请作出发布、限用或退回结论,并列出两项修复要求。