《大话文渊慧典》:七
第七篇:后处理的魔法——OCR的最后一公里
——大胖老师:“前面几讲,咱们把PDF拆了,把图洗了,把版面也分析完了,字也认出来了。是不是该收工了?”
——小菜:“那肯定啊,字都认出来了,剩下的不就是复制粘贴吗?”
——二黑推了推眼镜,把一叠打印纸推到小菜面前:“那你看看这个。这是昨天跑完的一本县志,原书乾隆年间的。识别出来的字,单独看每个都对,连起来读——‘已所不欲,勿施於人’,‘日’和‘曰’随机切换,‘玄’字缺一笔变成了乱码,双行夹注和正文混在一起,中间还插了一段眉批,完全不通。这就是你以为的‘复制粘贴’?”
——小菜翻了翻,脸皱成一团:“这……这也叫识别完了?”
——大胖老师端起保温杯,慢悠悠地说:“识别完,只是把矿石挖出来了。后处理,才是把矿石炼成铁、打成刀的过程。今天咱们就讲讲,从一堆乱七八糟的字符串,到一本可搜索、可复制、看着跟原书一样的双层PDF,中间到底经历了什么。”
一、后处理的第一道工序:置信度标注与校对辅助
大胖老师在白板上画了一个方框,里面写着“置信度”三个字。
“OCR对每个字都有一个打分,0到1之间。1就是‘我百分百确定是这个字’,0就是‘我瞎猜的’。对古籍来说,大部分字的置信度在0.9以上,但总有那么几个字——模糊的、虫蛀的、异体的——得分很低。传统做法是整篇人工校对,一个字一个字地比对,王大姐一天只能校二十页,校到退休也校不完一个馆。”
“我们的思路是:只让王大姐看那些模型‘不确定’的字。”二黑调出文渊慧典的校对界面,“系统设了一个阈值,0.85。置信度低于0.85的字,自动标黄高亮。校对人员打开文档,所有黄字一目了然,其他的默认正确,不用看。这一下,校对效率提升了至少三倍。”
小菜凑近屏幕:“那万一把对的标黄了,错的没标怎么办?”
“这就是阈值的艺术。”大胖老师解释,“设高了,黄字太多,累;设低了,漏网之鱼太多,险。0.85是我们用几千页古籍反复测试后找到的平衡点。而且王大姐在实际校对中如果发现黄字是对的,点一下‘确认’就消掉了,系统会记住这个反馈,以后类似情况会调高置信度。时间越长,系统越准,黄字越少。我们用活的数据,让模型和王大姐一起成长。”
小菜感慨:“这就像老师改卷子,只改那些答案写得不清楚的题,其他全算对。”
二黑面无表情地补了一句:“比喻很贴切,但我希望下次你交作业的时候不要用这个理论。”
二、后处理的第二道工序:语言模型自动纠错
“置信度标注解决的是‘告诉你在哪儿有问题’,但问题本身还没解决。”大胖老师又画了一个方框,写上“上下文纠错”。
“古籍里最常见的错误,不是乱码,而是形近字混淆。‘己’和‘已’,‘曰’和‘日’,‘人’和‘入’,‘末’和‘未’。这些字长得像,OCR经常犹豫不决。单看字形,神仙也难辨;但如果看上下文,答案往往很清晰。”
二黑调出一组实例:“比如‘己所不欲,勿施於人’。OCR可能把‘己’识别成‘已’,置信度0.7。后处理系统会怎么做?它把这个句子送进一个古文语言模型——一个用四库全书语料训练过的模型。模型一看‘X所不欲’,就知道前面应该是‘己’,因为‘己所不欲’是固定搭配,它在训练数据里见过几万次,‘已所不欲’一次都没见过。于是系统自动把‘已’纠正为‘己’。”
“这个语言模型是我们自己微调过的。”大胖老师补充,“通用语言模型主要基于现代汉语训练,遇到古文会水土不服。我们把《四库全书》《大藏经》这些语料整理出来,在BERT-base-Chinese的基础上做古文领域适配,训练了一个专用的古文语言模型。它不仅知道‘己所不欲’,还知道‘子曰’的后面多半跟着冒号,而‘某日’后面跟着干支或事件。这模型就像一个读过万卷古书的老学究,专门帮OCR纠正那些犹豫不决的字。”
小菜问:“那要是有两种解释在古文里都通呢?比如‘之’和‘也’混了?”
“那就保留原字,标黄,让人来判。”二黑说,“机器只纠正高度确定的错误。不确定的,老老实实交给王大姐。这是我们的一条铁律——宁可不改,不可改错。古籍学者最怕的不是错字,而是机器自作主张把原本正确的字改错了,你还看不出来。”
三、后处理的第三道工序:避讳字自动补全
大胖老师从抽屉里拿出一本清代刻本,翻到一页,指着其中一个字:“你们看这个‘玄’字。”
小菜凑过去,只见“玄”字的最后一点若有若无,像是故意没写全。“这是刻板的时候漏墨了?”
“这是避讳。”二黑接过书,“康熙皇帝叫玄烨,所以清代刻书里,‘玄’字必须缺最后一点,这叫‘缺笔避讳’。乾隆叫弘历,‘弘’字也要缺笔。还有直接改字的,比如把‘玄’改成‘元’。古籍里这种避讳字到处都是,传统OCR根本不认识这些残字,要么跳过,要么乱猜。”
大胖老师点头:“我们是怎么处理的呢?两个手段。第一,在OCR模型训练阶段,用数据增强——随机擦除字形的部分笔画,模拟缺笔效果,强迫模型学会根据残余笔画推断原字。第二,在后处理阶段,加载一个避讳字映射表,记录了清代所有皇帝的名字和对应的避讳规则。当OCR输出一个低置信度的字,且上下文提示它可能是避讳字时,系统自动补全。比如‘X之又X’,前后都是‘玄’,中间那个缺笔的残字,自动恢复为‘玄’。”
“但这里有一个分寸。”二黑强调,“自动补全只适用于出版时间明确的清刻本。如果是明代以前的书,就没有避讳问题,缺笔就是真缺笔,可能是虫蛀或刻工失误,不能乱补。所以我们的避讳字模块,会根据用户输入的书籍元数据——年代、版本——来判断是否启用。宋代刻本,避讳规则完全不一样,是避赵匡胤的‘匡’、赵昚的‘昚’。这个映射表是我们请历史系的教授帮忙整理的,花了好几个月。”
小菜感叹:“原来一个缺笔字背后,还藏着这么深的历史知识。”
大胖老师笑了:“这就是古籍OCR和普通OCR的本质区别。普通OCR只需要认识字,古籍OCR需要理解字背后的历史。没有领域知识,代码写得再好也没用。”
四、后处理的第四道工序:异体字标准化策略
“接下来这个工序,是我们被用户骂出来的。”大胖老师苦笑,“最开始我们的系统输出是‘原字原样’——OCR在古书上看到什么异体字就输出什么。结果被两个用户同时骂。”
二黑接过话:“学者骂我们,说‘你们怎么把异体字改成简体了?我研究的就是字形演变,要的就是原字!’普通读者骂我们,说‘这个字我不认识,你们能不能把它变成现在的标准写法?’同一套输出,两种完全相反的需求。”
“这就是异体字标准化的两难。”大胖老师在白板上写了几个例子:羣/群、峽/峽、衞/衛、説/說。“古籍里一个‘群’字可能有四五种写法。学者需要原汁原味,普通读者需要现代标准。怎么同时满足?”
“我们的解决方案是:输出两个版本。”二黑调出系统设置页面,“默认导出的TXT和双层PDF,保留原字原样——OCR看到‘羣’,就输出‘羣’。但同时,系统在后台做了一个异体字映射表,包含了三万多条异体字到标准字的对应关系。用户可以在设置里勾选‘异体字标准化’,勾上之后,‘羣’自动映射为‘群’,‘衞’变成‘衛’,方便大众阅读和全文检索。”
“更精细的做法是,双层PDF里保留原字,但在搜索索引里同时包含原字和标准字。你搜‘群’能搜到‘羣’,搜‘羣’也能搜到‘羣’。”大胖老师补充,“这个功能对学术研究特别友好。以后我们还计划支持用户自定义映射表——有些学者研究的就是异体字,他们可以上传自己的映射规则。”
小菜感慨:“原来同一个字,给不同的人看,要长不同的样子。”
二黑推了推眼镜:“这就是工程。技术不是做最炫的功能,是做最对的选择。”
五、后处理的第五道工序:双层PDF的“隐身文字术”
“终于讲到最后一关了。”大胖老师打开一份双层PDF,“这是文渊慧典最受欢迎的输出格式。你们看,画面是原汁原味的古籍扫描件,发黄的纸、模糊的藏书印、甚至虫蛀的小洞都原样保留。但是——”他鼠标一划,竟然选中了一行文字,复制粘贴到记事本里,一字不差。
“底层是原始图片层,上面叠加了一层透明文字层。”二黑调出结构示意图,“文字坐标是版面分析给的——每一行、每一个字的精确位置都记录在案。生成双层PDF用的是PyMuPDF,它支持插入透明文本框。我们给每个字或词放一个不可见的矩形框,字体颜色设为白色,字号极小——小到肉眼完全看不见,但PDF阅读器能识别为文字。”
“这样做的好处是什么?”小菜问。
“好处太多了。”大胖老师掰着手指,“第一,看着是原书,能引用、能作为学术证据。第二,文字可搜索——你搜‘子曰’,直接跳转到那一页那个位置。第三,可复制——学者写论文引用,不用手敲,直接复制粘贴,再标出处。第四,兼容性好——任何标准的PDF阅读器都能打开,不需要专用软件。第五,长期保存——PDF格式是国际标准,五十年后还能打开,不像某些私有格式,软件一死数据就废。”
“不过有一个细节。”二黑补充,“双层PDF的文字层是透明的,所以对字体的要求极低——因为我们根本不用字体显示文字,只是用它占位。这就绕过了古籍数字化一个常见的大坑:很多古籍用字在现代字体里根本没有,导致文本层显示为乱码或空白。我们不给它显示的机会,直接隐身,只留搜索功能。这招叫‘隐身文字术’。”
小菜试着搜了一下“玄”字,果然瞬间跳转,而且避讳缺笔的那个残字也被搜出来了。“避讳字也能搜?”
“能。因为我们在文字层里同时嵌入了原字和补全后的标准字。你搜‘玄’,能找到残缺的‘玄’;你搜‘元’(清代避讳改字),也能找到。这是两个版本的索引并存,各取所需。”
六、后处理的最后一步:标准格式导出与数据对接
“除了双层PDF,系统还支持纯文本TXT和带格式的Word导出。”大胖老师继续,“但真正的重头戏,是ALTO XML。”
“ALTO XML是国际古籍数字化领域最通用的结构化格式。”二黑解释,“它可以把每一页、每一行、每一个字的坐标、置信度、识别结果,全都结构化地存下来。比如某个字被标黄了,ALTO文件里就有对应的标签。这样,我们的识别结果可以直接导入任何支持ALTO的数字图书馆系统,实现跨平台互操作。”
“这是一张‘长期饭票’。”大胖老师比喻,“王大姐今天用文渊慧典识别一批书,导出ALTO XML存着。十年后,不管OCR技术怎么升级,不管文渊慧典还在不在,只要数据是ALTO标准格式,未来任何系统都能读取、能更新、能继续用。这才是真正的‘数字化’,不是锁死在今天的软件里。”
七、后处理的三个“差点翻车”瞬间
第一翻:语言模型把对的改错了
“有一次,语言模型把‘未之有也’自动纠正成了‘未之者也’,因为它觉得‘者也’比‘有也’高频。但原文就是‘未之有也’,出自《孟子》。二黑发现了这个问题,加了一条规则:如果原文置信度本身就高于0.9,语言模型不能强行纠错。宁可漏网,不可妄改。”
第二翻:避讳字把不是避讳的字补了
“有一页宋版书,‘玄’字因为虫蛀缺了点。系统一看,清刻本?不对,是宋版。但之前的版本没做好年代判断,自动补全了。结果一个好好的虫蛀‘玄’,被硬生生补成了避讳缺笔再补全的‘玄’,绕了一圈。后来加了年代判断,宋版不启用清代避讳规则,才解决。”
第三翻:双层PDF把透明文字印出来了
“有个用户打印双层PDF,打印机设置成了‘打印注释和标记’,结果透明文字层全被印成了小黑点,糊在古籍图像上。我们紧急更新了导出设置,把文字层的‘可打印’属性改为False。现在,屏幕阅读正常,打印只出图像层。吃一堑,长一智。”
八、下期预告
大胖老师看看窗外天色:“后处理的魔法,今天就讲到这儿。从一堆乱码到一本可搜索的双层PDF,我们走了五道工序。但工序走完了,不代表项目做完了。”
二黑收起笔记:“接下来才是真正的考验——实战。我们这套系统,从实验室到王大姐的电脑,中间隔着一百个意想不到的bug。怎么填坑?怎么测试?怎么让它在老爷机上跑得稳如老狗?这才是最考验工程功力的地方。”
小菜举手:“是不是该讲我们被内存泄漏折磨了三天,最后发现是一个变量名写错了的那次?”
“对。”大胖老师苦笑,“下期,开发实战与问题解决。主题就叫——”
《开发文渊慧典这两年,我们填过的坑比大胖老师的枸杞还多》
“把你内存泄漏那次的故事准备好。二黑,把‘玄学代码’那个bug的复现步骤整理一下。下期,咱们把开发过程中的十大翻车现场,一个一个摆出来。”
小菜揉了揉手腕,二黑打开了bug跟踪系统,大胖老师拧开保温杯,续上了今天的第四杯枸杞水。
实验室的窗外,校图书馆的灯已经全亮了。王大姐刚用文渊慧典跑完了一本明天读者要用的方志,在群里发了一条消息:“这系统越来越快了,茶还没凉,书就认完了。”
大胖老师看到这条消息,把手机转向二黑和小菜:“听见了吗?这才是我们最好的技术指标——茶还没凉。”
本文为注水技术版,您看看即可,不必当真,写此文字就是图一乐:-)
