最近一个月我都在折腾一件跟“风花雪月”不怎么沾边的事:把一间旧仓库里堆了四十年的民间文献、录音带和照片底片,变成一套能持续生长、能被检索、能被讨论的数字知识库。干到一半我才意识到,这活儿已经远远超出了“数字化存档”的范畴,更像是在给一段文明的碎片做养护。而真正把这件事撑起来的,恰好就是三个相互咬合的环节——我管它叫深渊、草图与回响。
这个叫法听起来有点玄,但说白了很简单:深渊是指我们面对的那些混乱、残缺、大量未知的原始材料;草图是指人工智能在信息不全时帮我们勾画出来的初步框架;回响则是指这套框架在真实使用中不断被验证、修正、重新反哺的循环。三者连在一起,就是一套可复用的“文明养护”方法论。这篇文章就是把这套方法从头到尾扒开讲一遍,从数据怎么清洗、模型怎么调参,到实体识别的踩坑实录,尽量给到可以直接抄作业的细节。适合做数字人文、文化资源整理、知识库搭建的人参考,也适合被“AI+文化”这个概念吸引,但不知道从哪下手的人。
1. 内容整体设计与思路拆解
1.1 为什么偏要用“三重奏”而不是“一条流水线”
最早我接这个项目时,甲方给的预期很朴素:把材料扫描、建个数据库、做个网页让别人能查。这看起来是一条标准流水线:扫描、OCR、录入、发布。前几周我也是这么干的,直到某天晚上对着屏幕上一份虫蛀掉三分之一的手稿,我发现自己不知道该往这个“标准数据库”里填什么字段。
那些手稿里,同一句话在不同信封背面出现了三个版本,年代互相矛盾;录音带里老人说的方言地名,跟县志上的写法完全不同;照片背面的钢笔字褪色到只能认出“195_年”。这些内容根本没法被塞进“标题—作者—日期—摘要”的四平八稳结构里。也就是说,真实文明遗存的第一属性不是“整洁”,而是“深渊”:深不见底、浑浊不清、处处都有暗流。
做文化整理最怕的一件事,就是假装深渊不存在。为了假装整洁,很多人会强行归类、强行纠错、强行补全,最后得到的是一个错误率很高、看起来很好看的数据库。所以我把设计思路整个反转过来:不追求一次到位的“成品”,而是先建立一个能容纳混乱、允许错误、支持反复修改的系统。这个系统从底层到顶层分了三层,正好对应三重奏里的三个意象:
- 深渊层:负责原始材料的采集、清洗、保存。目标是“不丢、不改、可追溯”,而不是“整齐”。
- 草图层:人工智能介入,把非结构化的文本、影像、语音转成半结构化的实体、关系、时间线。目标是“画得不准没关系,但轮廓要出来”。
- 回响层:把前两层产出的东西投入真实使用,收集使用者的补充、纠正、质疑,再把这些信号送回去更新上一层的框架。
这么设计最关键的理由是:文明养护不是一次性的修复,而是持续的照料。照料的前提是有生命体征监测、有反馈循环。流水线做不到这一点,流水线只会让东西从一端进去、另一端出来,然后放在那里落灰。三重奏不一样,它天然是一个闭环。
1.2 三个模块的边界划定,以及彼此怎么咬合
光有概念还不够,落地必须划清边界。我最初犯的错是让三层混在一起:一边做转录一边做校对一边做知识图谱更新,结果一个问题追着另一个问题跑,最后哪儿都做不好。
后来我定了三条很硬性的边界:
深渊层只做“输入”和“保全”。所有扫描件、录音文件、照片数字副本进来后,先计算校验值,生成唯一的资料ID,然后原样存档,任何人(包括我自己)都不得修改原始数字副本。所有清洗、字段补充都发生在另外一个“工作副本”上。这一层不追求理解内容,只追求“东西还在、来源清楚”。
草图层只做“粗粒度结构”。大模型负责抽取人名、地名、时间、事件、器物,并给出置信度分数,但不会直接写入正式的知识库。草图的结果存在另一个分区,等待人工复核,或等待后续“回响”给出的证据来支持或推翻。这一层要抑制住“AI全自动整理”的冲动,所有自动生成的内容都只能算“草稿”。
回响层只做“差异检测”。当一位研究者、一位村民、一位爱好者使用这套系统时,他们产生的每一个标注、每一条评论、每一次修改建议,都被记录成“回响事件”。系统定期把事件聚合起来,对比草图层的输出,给出“需要修改”的信号,再由人去决定改还是不改。
咬合关系也很简单:深渊供给草图,草图回应深渊中被发现的线索,回响同时修正草图和指导下一轮深渊的打捞方向。三者不是上下游,而是互为因果。
2. 核心细节解析与实操要点
2.1 深渊层:这层没技术含量,但决定项目生死
先说个真实案例。我们手里有一批上世纪六七十年代的老照片,底片是硝酸片基的,已经在发黏、变形。数字化时如果直接上平扫,药膜很容易二次损伤。后来借了一台非接触式翻拍台,才算把底片完整保下来。这件事给了我一个很重要的教训:所谓深渊层,首先不是数据问题,而是物理问题。
实体的采集必须讲规矩,按我的经验至少要控制好五个环节:
- 环境控制:纸质材料在扫描前最好在恒温恒湿环境放置24小时以上,避免脆裂;录音带、唱片不要反复试放,可以先做一次低音量快转检测,全程记录。
- 设备选择:书刊用高速扫描仪配V型书稿台;照片底片用非接触翻拍;录音用专业卡座加无损采集卡,采样率直接拉满到96kHz/24bit。宁可文件大,不要细节丢。
- 命名规范:文件命名用全数字ID,禁止在文件名里写中文、写空格、写人名。序列规则是“来源批次-载体类型-流水号”,例如
A07-TP-0318表示A批次第7箱、照片类型、第318件。 - 双层校验:每批扫描件抽10%人工抽检,比对原件确认没有缺页、切边、折痕信息丢失。
- 容错机制:所有原始数字副本做双介质备份,一份本地NAS,一份离线硬盘。校验值(我去用的是SHA-256)记录成清单,定期重算比对。
原始文件落库之后,要建立一个“工作副本”用于后续转录、标注。工作副本只是便于处理,不代表可以乱改。任何对文本的修正都必须在注释字段里写明“依据什么改的”或者“此处存疑”。
很多人会问:字段应该怎么设计?我踩过一段时间的坑,最后提炼出一套相对好用的“半结构化容器”,核心字段如下:
| 字段 | 类型 | 说明与原注意点 |
|---|---|---|
| 资料ID | 字符串 | 全局唯一,同时作为文件路径前缀 |
| 时间数据 | 列表 | 允许多值,例如[1965?, 1970-1973],带置信度标记 |
| 空间数据 | 列表 | 允许多对多,说明是“提及地”还是“来源地” |
| 主题标签 | 列表 | 用受控词表,不要自由输入 |
| 载体信息 | 对象 | 介质类型、损伤情况、物理尺寸 |
| 原始段落 | 长文本 | 尽量原样转写,“原文如此”不加修正 |
| 存疑标记 | 布尔+文本 | 标记需考证内容,附带理由 |
| 关联资料 | 列表 | 指向其他资料ID,构建“同批/相关”关系 |
这套容器的哲学是:允许一个条目里同时存在多个互相矛盾的时间,但不允许把矛盾悄悄抹平。因为矛盾往往就是线索,是回响层要处理的重点对象。
2.2 草图层:让AI先画一张可以改的图
深渊层把材料救回来之后,接下来就是“读”的问题。几千页手稿、几十个小时录音、几百张照片,靠人逐字逐句看,一年都读不完。这个阶段我确实用了大模型来做初步的草稿式标注,但“草稿”两个字是关键。
我搭了一套简单的文本处理流水线,大致是:OCR或ASR得到原始文本,切成长段落,然后调用大模型API逐段识别实体与关系。这里有个点必须强调:大模型的抽取结果不要直接当作事实入库,它是草图,你要给它加上两条东西:置信度和证据引用。
以口述录音为例,一位老人回忆“那年河上发大水,村里的木桥被冲走了”。模型可能抽取出:事件“发大水”、时间“那年”、地点“村里”、对象“木桥”。但“那年”是哪一年?模型不知道,它只是机械抽取。所以流程上我会要求模型输出JSON结构,附带说明哪几个词是推断的、推断依据是什么。然后再由后处理脚本给命名实体打分:有明确年份对上的,置信度高;只有模糊指示词的,置信度降半。
我做实体抽取时,会固定使用一套约束模板来尽量降低随机性,模板大致如下:
你是文本标注助手。请从给定的文本中抽取以下实体: 1) 人名(包括称谓、外号) 2) 地名(包括别名、俗称) 3) 时间(包括模糊表达如“解放前”“那年秋”) 4) 事件(包括有明确动词短语的事件) 5) 物件(包括工具、建筑、文书等) 要求: - 输出JSON列表,每项必须有"text"、"type"、"confidence"、"source_span"四个字段。 - confidence是0到1的浮点数,只在文本证据充分时才给高分。 - 拿不准的地方confidence设为0.5以下,并在notes字段里说明不确定原因。 - 不要自行补全缺失信息。这套模板最核心的一句话是“不要自行补全缺失信息”。没有这句话,大模型就会脑补出大量根本不存在的人名和年份,给后续回响层制造一大堆噪音。
在关系抽取与实体对齐上,我用的是比较轻量的方案。所有实体先经过同义词合并,比如“镇上”“街里”“老镇”按规则映射到规范地名“某某古镇”。这种规则映射,人工维护一个同义词典就够了,不需要上复杂的算法。只有模糊匹配时才用到向量相似度,阈值我长期测下来取0.82比较合适:低于0.82,误合并明显增加;高于0.88,大量同义实体拆散。这个数字每个数据集里都会有点浮动,但0.82可以作为起步默认值,之后再根据抽检结果微调。
草图层的最终产出,是一张“知识草图”:节点是实体,边是关系,但每个节点和边上都带着置信度指标。我没有用重型图数据库,只用关系表加全文索引,人名、地名、事件各建一张表,再用中间表建立关联。当数据量在几十万节点以内时,这套方案足够稳定,也方便不懂图数据库的人维护。
2.3 回响层:让知识库活过来的反作用力
草图画完之后,如果没人用,那它依然是死的。回响层就是专门负责“让知识库被使用、被验证、被修正”的机制。
我给这个系统加了一个轻量级的前端页面,支持三种基本操作:全文检索浏览、实体关联跳转、批注讨论。比较关键的是批注讨论的设计。使用者看到任何一个实体、任何一条关系、任何一个事件卡片,都可以在上面添加三种类型的标记:
- 存疑标记:用户认为这条信息可能不对、缺少证据。
- 补充标记:用户提供新的证据、新的关联资料或其他来源的佐证。
- 修正建议:用户直接给出建议修改后的文本、时间或关系方向。
这些标记并不会马上改动正式数据,而是进入“回响事件流”。每周我会跑一次聚合脚本,按主题分类,统计哪些节点被反复标记、哪些修改建议彼此支持。聚合结果出来后,我会做一轮人工仲裁:同一实体被至少两条独立记录交叉验证的,提升置信度;被两条以上记录质疑的,降置信度并加“待考证”标签。然后触发一次“草图重画”——把修改后的实体重新喂给抽取模型,让它学习新证据,再生成一轮新的草图。
为什么一定要走“回响事件流”而不是直接让用户编辑?因为文明养护追求的不是某个人的主观修正,而是多源证据的相互印证。直接编辑会把个人记忆、个人偏好、个人误读直接写进正式数据。事件流给了系统一个缓冲,让所有修改都有据可查、可回滚、可追溯到人。这跟代码评审是一个道理:不直接推主分支,先提Merge Request。
3. 实操过程与核心环节实现
3.1 从零搭建“三重奏”流水线
说完了设计,实际拉通一遍完整流程。我拿一条具体材料做例子:一卷录于九十年代初的磁带,是一位老手艺人讲他年轻时学手艺的过程。项目经理、也就是我自己,对这个材料的期望是:能提炼出他的师承关系、学艺时间线、关键工具与口诀,并让后来研究者能按图索骥找到相关材料。
第一步,把磁带放进卡座,用无损方式转录成WAV,采样率96kHz、24bit,转出来文件大概有1.2GB。这卷磁带原本没有数字备份,我顺手计算了芯片的读取次数和磁带走带时长,登记成元数据。
第二步,转写。WAV太大,我先切成了30秒一段的小片段,用本地语音识别引擎做初步转写。这里有个老问题:方言识别率很低,老手艺人的不少句子被识别成“嗯嗯啊啊”。我没有指望一次到位,而是先把转写文本和音频段落对齐,然后交给某助手级大模型,给它的prompt是“这是某地方言的近似普通话转写,请根据上下文把明显错漏的词修正为更合理的内容,但保留不确定的词并标注[存疑]”。这一步会丢掉一些原始发音细节,但我保留原始音频,所以不担心—修正只在工作副本上发生。
第三步,实体抽取。把修正后的转写文本切成800字左右的段落,送进大模型做实体抽取。输出JSON里除了五类实体,还要求输出每段话的摘要句。我把抽取后的结果落库,保留模型原始的confidence字段。
第四步,知识对齐。抽取出来的实体里有“师傅刘某某”“刘师傅”“老刘”这几个变体,同义词典把它们映射到一个人名规范节点“刘某(手艺师傅)”。时间是重灾区,“那是秋天”“大概十八九岁”“学了三年出师”全都是模糊表达。我设计了一个时间槽:可能年份范围、依据文本、置信度。系统不会去猜一个精确年份,只会记录“大概在1958到1962之间,依据是老人自称十八九岁学艺,而档案里他的年龄推算是1939年生”。
第五步,构建关系。用规则脚本识别“跟某某学艺”“学的是某某手艺”“用的工具是某某”这类句式,生成候选关系边。脚本规则表是手工维护的,很土但很稳:
| 句式模式 | 抽取关系 | 例子 |
|---|---|---|
| 跟/随/拜…学习/学徒 | entity_1——师承→entity_2 | 随刘某学习木作 |
| …教会了我 | entity_1——传授→entity_2 | 师傅教会了我开榫 |
| 当时用的是…/家伙什是… | entity_1——使用工具→entity_2 | 当时用的是锛子 |
第六步,发布与回响。图谱草图落库,前端可检索、可定位。我请了当地几位年纪大的居民试用,请他们看老照片、听录音片段、在页面上标记“这张照片我认识”“这句话应该是这个意思”。这些标记一周内就积累了二十多条,有两条直接推翻了草图里的一个时间线判断。
3.2 关键参数和控制点,我都设了什么值
这套流程里最需要精细调整的,就是各种参数。踩了若干次坑之后,我把常用配置固定下来了:
- OCR置信度阈值:我用的是0.75。低于这个值的字符块不进入后续标注,先标记为“疑似缺损”。原因:低于0.75的OCR结果出错误率太高,保留它只会让模型抽取更混乱。
- 实体合并相似度阈值:0.82,前面说过。合并前先用规则同义词典,规则词典优先于向量计算,这能显著降低误合并。
- 大模型温度:实体抽取任务固定在0.2,摘要任务用0.6。温度拉高会让摘要更好读,但实体抽取的格式和置信度会飘,不能容忍。
- 段落切片长度:800字。太长,实体信息容易挤爆上下文窗口导致漏抽;太短,句子之间的指代关系断裂,抽出来的关系边质量很差。
- 回响事件聚合周期:一周一次。太频繁,事件太少没有统计意义;太久,用户会觉得反馈石沉大海。
这些参数我不敢说通用,但作为起点应该能省不少试错时间。每个数据集跑一遍后,根据抽检结果调。
3.3 一条知识从“深渊”到“回响”的生命周期示例
下面我用时间线方式展示一条具体知识是怎么走完全程的:
- 第1天,磁带转录完成,WAV文件生成,SHA-256校验值入库。
- 第2天,语音识别初稿出来,错别字很多,进入“工作副本”等待修正。
- 第3天,大模型修正并追加“存疑”标记,识别出“刘师傅”“木作”“锛子”“三年学徒”等实体。
- 第4天,同义合并完成,实体“刘师傅”对齐到规范节点,confidence=0.91。
- 第5天,关系抽取脚本生成“师承”边:口述者→受业于→刘师傅,关系置信度0.85。
- 第6天,该实体对出现一次冲突:另一份文字材料记载刘师傅是“一九五零年来的”,而录音里口述者说“师傅是解放前来的”。两条记录在时间槽上打架,时间数据被打上“待考证”。
- 第9天,第一位试用者在“待考证”旁补充:“刘师傅的徒弟名单还在,可以找某某村的档案”。这是第一条回响。
- 第10天,聚合脚本自动产生工作项:前往某某村核验档案。
- 第15天,档案核验结果扫描并录入,确认刘师傅是1950年迁入,但学艺时间发生在更早。时间槽更新,原先互斥的冲突被化解成一条更精确的时间线。
- 第30天,这条知识的所有标记清零,confiidence从0.85升到0.96,正式进入“高置信”区。
整个过程看起来琐碎,但它证明了一件事:回响不是一次性的反馈收集,而是知识进入可信状态的必经路径。没有这条路径,初版AI生成的草图永远都是不可靠的猜测。
4. 常见问题与排查技巧实录
4.1 我踩过的坑,一个个说
坑一:OCR出来的错字被大模型“合理化”了。这是最隐蔽的坑。原始文本里把地名“漾濞”识别成“漾闭”,大模型在抽取时没有上下文常识,直接把这个错字当成正式地名写进实体,还给了0.9的高置信度。我后来加了一条硬规则:所有实体抽取结果必须回填原始文本中的字符跨度(source_span),一旦发现实体文本与OCR文本对不上,就自动降权。同时把OCR置信度低于0.75的段落直接屏蔽,不让它们进入抽取流程。
坑二:方言识别成了“灾难现场”。一段录音里老人说“木匠用的‘锛子’”,ASR转写成了“棍子”。大模型没听懂,抽出来的物件就是“棍子”,跟后面的工具描述完全对不上。真正的解法不是换更好的ASR,而是保留原始音频并让标注者先听一遍,把听不准的词标记出来。靠文本自己纠错永远追不回语音层次的损失。这个教训花了我整整一周。
坑三:关系抽取把否定句当成了肯定句。“他并不是刘师傅的徒弟”这种句子,规则脚本和模型都容易直接抽取成“师承”关系。我需要加一个否定词哨兵:在关系抽取之前先检测句子中是否存在“不是”“没”“非”“未曾”等否定词,如果存在,关系抽取结果一律降为低置信度并进入人工复核。这个简单的哨兵机制,把错误的师承关系数量直接降到了原来的两成以下。
坑四:同义词合并翻车,合并了两个同名不同人。当地有两位刘师傅,一位木作、一位皮影,语音转写里都叫“刘师傅”。规则词典只做了“刘师傅→刘师傅”,系统把两个人合并成一个节点,草图全乱了。后来加了一条规则:同义词合并必须先看类型,再看伴生实体。木作刘师傅大多跟“锛子”“刨子”伴生,皮影刘师傅大多跟“影人”“唱腔”伴生。类型不一致,绝不合并。
4.2 问题排查速查表
| 症状 | 可能原因 | 排查方法 | 预防手段 |
|---|---|---|---|
| 实体数量爆炸 | OCR噪声多、同义词未合并 | 查看实体名出现频率,检查低频异常词 | 打通OCR文本与抽取间的置信度过滤 |
| 关系边指向错误 | 否定句、因果句误读 | 抽样查看关系边对应原句 | 加否定词哨兵,人工复核低置信关系 |
| 时间线互相矛盾 | 模糊时间槽太多 | 列出同一实体的多组时间槽 | 强制要求时间数据带依据文本 |
| 回响事件没人使用 | 前端难用、检索太弱 | 查看用户操作日志,统计停留时长 | 第一版就做全文检索,不做炫酷图 |
| 模型输出不稳定 | prompt和参数未固定 | 复跑同一输入比对输出 | 固定温度、固定模板,输出加schema校验 |
4.3 几个可以少走弯路的经验
第一,永远保留一份“人类可读的原文摘录”。所有实体、关系、时间槽旁边都要挂一段原始文本片段或音频片段起止时间。这样后续每一层哪怕全错了,都能一键溯回原材料。这是“回响”能够发生的基本前提。
第二,先跑通一个微型闭环,再规模化。我最开始拿到几千条材料,雄心勃勃想全部建好图谱再说。结果做了一周,深渊层的清洗都做不完,就更别提草图层了。后来换思路,只挑出一条录音、两份纸质日记、三张照片,花三天把整个三重奏闭环跑通,再回过头定流程、写文档、分配工作量。效果立竿见影。
第三,警惕“整理洁癖”。有些人看到知识库里有大量低置信度节点和存疑标记,会觉得“不专业”,恨不得全部清理掉。别这么做。低置信度节点本身就是待考证的线索,一旦删掉,很多回响也就失去了靶点。允许不确定存在,才是一个养护型系统的健康状态。
第四,把回响奖励机制做进系统。任何一个用户,只要他提出的标记最后被人工仲裁采纳,系统里就给他加一点“影响因子”,并显示在他的个人主页上。这个功能看着不起眼,但对于调动长期参与特别管用。没人愿意在一个知识库里当免费校对员,除非他得到的正反馈是可见、可累积的。
回到开头那个仓库,现在我再看那一箱子旧物,感受不一样了。它们不是一堆等着被录入的“历史遗留问题”,而是一片随时会反馈给现在的深渊。我做的只是先让AI画一张可以修改的草图,然后让时间、让使用、让不同人的记忆一遍遍地流过它,不断磨出更清晰的轮廓。
这就是我理解的文明养护:不是把过去封存在玻璃罩里,而是让过去参与现在,并在碰撞中继续生长。深渊负责蕴藏,草图负责试探,回响负责验证。三重奏里没有哪一重是终局的,它们永远在循环。如果你手里也有一批意义不明但舍不得丢的材料,不管是家谱、口述史、老照片还是尘封的笔记,都可以试着按这个思路跑起来。不用一次做到位,先让第一稿草图出来,剩下的交给回响。