古汉语NLP工具包Jiayan:文言文分词、词性标注与断句标点实战指南
简介甲言Jiayan是一套专注于古代汉语处理的NLP工具包面向古汉语研究者、文史爱好者和自然语言处理开发者旨在解决通用中文NLP工具对文言文分词、词性标注、断句标点处理效果欠佳的问题。资源包共28个文件以20个Python源码文件为主辅以词典数据、文本语料、说明文档、许可证及常规工程配置压缩包仅217KB模块划分清晰便于按需调用和二次开发。目前已有3314人学习下载。完整源码覆盖文言词库自动合成、基于有向无环词图与动态规划的最大概率分词、词性序列标注及句子分段标点等核心功能并附带示例脚本、基础词典和测试数据可直接运行验证效果也可嵌入到古汉语信息处理流程中适合从入门尝试到工程集成等多类场景使用。 做NLP这些年我一直有个感受古汉语古汉语古文文言文文言是被主流工具无意间忽略的角落。把中文分词、词性标注工具直接扔到文言文上效果通常惨不忍睹——不是模型不够强而是任务的基本假设就不一样。Jiayan甲言就是奔着这个场景来的一套专注于古代汉语古汉语古文文言文文言处理的NLP工具包支持文言词库合成、分词、词性标注、断句和标点。这篇文章我不会只贴API文档而是把项目核心设计、我从零跑通的实操过程、以及实测中踩过的坑一次讲清楚。想做古籍数字化、文言文教学工具或者语料库建设的同学应该能从里面找到可以立刻用起来的东西。1. 为什么通用NLP工具搞不定文言文古汉语处理的三个硬骨头1.1 文言词边界和现代汉语几乎不是一回事现代中文分词工具能跑通靠的是大规模现代汉语语料和相对稳定的词表。古汉语的情况完全不同文本里单字词占比极高但又不全是单字成词。拿“学而时习之”来说“学”“而”“时”“习”“之”基本一字一义可“君子”“夫子”“天下”这类双音节词又稳定存在“所以”“然而”这类凝固结构更不是两个词简单相加。更麻烦的是同一个双字组合在不同上下文里可能是词也可能不是词。比如“妻子”在现代汉语里是一个词在《孟子》里却是“妻”和“子”两个词指的是老婆和孩子“地方”在现代汉语里是名词在文言里是“地”和“方”两个词表示土地方圆。通用工具没有这种古今差异的意识切出来自然不对。对文言文来说判断一个双字组合该不该合成词必须看上下文看它在句子里的语法位置这比现代汉语难得多。1.2 词类活用和一词多义把统计模型彻底搞晕词性标注在文言文上遇到的麻烦不只是“词表没有”而是同一个词在相邻的句子里可能完全是不同词性。“衣锦还乡”的“衣”是动词穿衣服“解衣推食”的“衣”是名词衣服。“老吾老以及人之老”一句话里三个“老”一个动词两个名词或形容词。这种词类活用不是特殊修辞而是文言的基本语法规则几乎每篇文本都能遇到。通用模型如果按照现代汉语的词性分布建模遇到活用几乎必错。高频虚词更是重灾区“之”可以是结构助词可以是代词还可以是动词最典型的就是“项伯乃夜驰之沛公军”里的“之”意思是“到、去”。这些用法分布在不同时代、不同文体里的概率都不一样模型很难只靠表面词形学会。想在这个任务上做好必须针对文言自身的语法规律设计特征和标签体系而不是指望通用模型自己“悟”出来。1.3 缺标点、缺规范语料连训练目标都不好定现代NLP做句法、标点都依赖大规模有标点语料但古人写文章本来就没有现代标点只有“句读”是读者为了读懂加上的。断句位置不同语义可能完全翻转。《韩非子》里“夔一足也”这句话鲁哀公原本理解为“夔只有一只脚”后来孔子解释正确的断句应当是“夔一足也”意思是“有一个夔就够了”。这种歧义说明标点本质上是阅读理解的结果而不是原文本自带的属性。不同校注版本对同一段古文给出的标点可能互相矛盾又没有统一的词性标注规范模型能学到的数据天然带噪声。想在古文上做好标点恢复必须先把词汇、词性、句法这些信息综合起来判断这正是专门为古汉语设计工具包而不是直接套用现代NLP方案的根本原因。这三条叠在一起结论已经很清楚你要的不是一个换上古文词表的中文分词器而是一套从词库构建、分词、词性标注到断句和标点恢复都围绕文言特性设计的完整流程。项目取名Jiayan甲言意图也很直白先把古汉语文本处理这件事做到第一等再谈其他扩展。2. Jiayan的核心功能与设计思路从词库合成到标点恢复2.1 词库合成解决的是“领域知识进模型”的问题市面上能做分词的组件不少但多数给你一个固定词表你改不了或者改了影响也有限。Jiayan的“文言词库合成”允许用户把自己准备的语料和词汇表合入基础词库让模型在通用文言知识之上吸收你所在子领域的专名和惯用语。我做古籍项目时最花时间的从来不是调参而是把项目涉及的人名、地名、官职名、篇名确认清楚。没有这一步模型会把“留侯”切碎也会把“酂侯”标错。词库合成相当于给模型配了一个“项目简报”让它知道接下来要处理的文本里哪些组合是稳固的表达。在执行层面通常的做法是先做词典最大匹配再对候选结果用语言模型重排把“词典倾向”和“上下文概率”加权求和而不是让任何单一信号说了算。这个设计思路对工程落地很关键因为纯统计模型在数据不足时不会主动信任领域词典而纯词典匹配又扛不住灵活多变的古文语法两者必须融合决策。2.2 分词和词性标注耦合避免“错上加错”分词和词性标注在流程上的先后关系很微妙。如果先分词再标词性分词的任何错误都会原封不动送给词性标注先做词性标注再做分词又缺乏词的边界信息。Jiayan把两个任务放进同一套序列标注框架让模型在解码时同时考虑“这里是不是词的边界”和“这个词最可能的词性是什么”。落实到具体建模上每个字除了要预测词边界标签还要预测词性标签最后用维特比算法找一条联合概率最大的路径。这样做的好处是即使遇到未登录词模型还能借助周围虚词和句法位置的约束猜出一个概率较高的切分和词性组合。文言里的未登录词比例比现代汉语高得多各种生僻字、通假字、异体字层出不穷这种联合建模带来的容错能力正是古文场景下最需要的东西。2.3 断句和标点恢复是两个层次的任务标题里同时列了“断句”和“标点”很多人以为是一件事其实工程上差别很大。断句解决的是“哪里是完整句子的边界”标点解决的是“这个边界该用句号、问号、感叹号还是逗号”。Jiayan的处理逻辑是先用分词和词性信息确定句读位置再结合语气词和句式特征判定标点类型。断句层可以理解为一个序列二分类问题逐字判断当前位置是否为句子边界标点层则是在已经被判定为边界的候选位置上做多分类判断具体该放什么标点。单独看标点层难在古文语气词不是可靠的触发器“乎”“哉”“邪”经常出现在疑问句里但“嗟乎”“於乎”又是感叹用法“也”“矣”是陈述语气词可后面也可能接疑问。必须把整个句子的结构信息聚合起来才能给出合理的标点。这种层次化设计比一个黑盒模型直接输出标点更容易定位错误也更方便在项目里按需调优。理解了这个设计逻辑再去看官方文档或者跑实验就不会被一堆术语绕晕可以直接上手把它跑起来。3. 从零跑通Jiayan的完整实操记录语料、词库与主流程3.1 环境准备最容易被忽视的是输入文本本身跑通Jiayan的代码依赖并不复杂Python环境、预训练模型文件、语料目录基本就这三样。我第一次做的时候直接把手头带现代标点的全文丢进去结果断句效果很不稳定。后来认真读文档才明白断句模型训练时期望看到的是无标点的裸文你把现成标点传进去模型反而会被这些“答案”干扰判断逻辑全乱。正确做法是先把文本统一成UTF-8编码去掉现代标点和多余空格再按段落或者自然句分成长短适中的行。这个预处理步骤看着不起眼实际对结果的影响比换模型参数大得多。语料越接近模型训练时的分布结果越可靠这个原则在任何NLP项目里都成立。3.2 构建专属词库的三个落地步骤词库合成不是把词表丢进去就完事。我实际操作下来一般分三步。第一步从项目语料里抽出全部专名和惯用语包括人名、地名、官职名、书名篇名、特殊术语。第二步把每个词条整理成“词语、词性、频次”三列频次可以先按语料里的出现次数估算不确定的先给一个偏中等的值。第三步执行合成操作让基础词库和领域词库合并并重新统计词频。合成之后一定要做回归测试拿一批和你项目领域无关的文言句子跑一遍确认通用能力没有被领域词库带偏。我见过有人把某个专名的词频调得太高结果全文里所有同形字都被粗暴合并反而毁了其他句子的分词。词库比例的控制本质上是在“领域覆盖”和“通用性”之间找平衡点。3.3 分词、词性标注、断句标点的调用链路主流程的代码结构大致是下面这样我把加载模型、分词、词性标注、断句标点的调用顺序放在一起方便看出它们之间的数据流向。接口名以你拿到的版本或官方文档为准重点是理解先加载语言模型再初始化三个处理模块这个顺序决定了后面所有模块共享同一套语言统计知识。from jiayan import load_lm from jiayan import CharHMMSegmenter, POSTagger, SentencePunctuator lm load_lm(models/jiayan.klm) segmenter CharHMMSegmenter(lm) text 学而时习之不亦说乎 segments segmenter.segment(text) tagger POSTagger(lm) tagged tagger.postag(segments) punct SentencePunctuator(lm) restored punct.punctuate(学而时习之不亦说乎)实际跑完分词结果是一个词序列词性标注结果是每个词对应的标签序列断句标点接口返回的是补上标点后的整段文本。批量处理古籍时我建议先把输出落成TSV或者JSON格式一列原文、一列分词、一列词性标点单独一列方便后续检索和人工校对。不要在正式跑全量语料之前省掉小规模试跑宁可多花十分钟确认格式也别等几万条记录跑完才发现字段错位。4. 实测中踩过的坑与调优心得词库比例、文体差异与错误级联4.1 词库不是越大越好噪音词条比漏词更伤精度我第一次构建史书相关词库时几乎见到像词的双字组合就塞进去结果分词准确率反而下降。原因不难理解大量低频、上下文不稳定的组合进入候选集合后解码器会被诱导在原本应该单字切分的位置强行组词。比如“之治”“其时”这类短语看着像词在语料里出现频率也不低但并不是稳固的词汇单位硬塞进去只会引入噪声。后来我把入词库的频次阈值拉高只收在语料里反复稳定出现的词效果很快回升。这里值得记住的经验是漏词最多让模型把词切碎你还能从结果里看出来误加词则会让模型把原本切对的句子切成错的反而更难排查。词库构建宁可保守也不要贪多。4.2 断句和标点模型要按文体分开评估古汉语内部的文体差异比很多人想象中大得多。《左传》《史记》这类叙事史书句子节奏清楚人物对话频繁断句点相对容易判断先秦诸子和唐宋论说文逻辑层次多、长句多逗号和句号的分布规律和叙事文本完全不同。同一套预训练标点模型在叙事类文本上的表现通常好于论说类因为论说文的句间语义联系更紧密边界更依赖推理而不是局部形式。如果项目主要处理论说文别指望通用模型一步到位最好收集三五百篇同文体文本做二次微调。这个准备工作看起来费时间但比在线上反复试错划算得多。另外对话多的文本还要留意引文边界人物对话的起止位置经常和叙事句粘连人工校对时最容易在这里漏看。4.3 错误级联是流水线架构最大的隐形成本分词、词性标注、断句、标点是有先后依赖关系的流水线上游的错误会顺着管道往下传而且越到后面被放得越大。一个双字组合被错切词性标注就会跟着错词性错了句法层面的特征就不可靠句法特征不可靠断句和标点自然给出奇怪结果。我在排错时会把每一层中间输出都单独打印出来从分词开始一层层对而不是只看最末端的标点结果。定位问题的顺序一定是先看词对不对再看词性合不合理最后才去质疑断句。另一点值得提醒处理历史文献时一定要保留原文和处理结果的映射关系避免在批量环节里把原始文本弄丢。原始数据是不可再生的唯一基准这个冗余是必须的。5. 拿Jiayan能做什么古籍整理、教学辅助与后续扩展5.1 古籍数字化流水线的“标点初稿机”古籍数字化平台目前最卡人的工序就是把OCR出来的无标点文本变成可读的标点版本。完全人工标点速度慢、成本高而且校对标准难统一。把Jiayan放进流水线后可以让它先产出一版置信度比较高的断句标点初稿人工只需要在初稿上做修改确认而不是对着白纸一个字一个字标。实测下来叙事类文本里标点初稿的可用率相当高人工主要处理的是一些长难句和特殊语境。这个“机器出初稿、人来终审”的模式是古籍整理数字化落地最现实的一条路径。单纯追求全自动反而容易在质量验收上卡住因为最终发布还是需要人来背书。5.2 文言文教学和阅读器的逐词分析能力文言文学习工具最缺的不是释义而是让学习者看清“哪个字是什么词性、在句子里做什么成分”。把Jiayan的分词和词性标注接进课件或者阅读器之后一篇《岳阳楼记》可以自动生成逐词对照表学生点开任何一个词就能看到机器的词性预判。机器预判不一定全对但能给师生一个讨论的起点老师再讲“之”字在这里为什么是取消句子独立性学生理解起来会快很多。这类功能的工程成本不高但对学习体验的提升非常明显也是投入产出比比较高的应用方向。如果再结合断句标点结果还能把“无标点原文”和“加标点版本”做成自动对比练习帮助学生快速建立句读直觉。5.3 后续可以继续做的三个方向如果继续往下做我目前最想推进的有三件事。第一把断句标点结果用于古籍检索与版本对齐让无标点的影印本和带标点的整理本自动匹配减少人工比对工作量。第二基于稳定分词做文言虚词的计量研究比如统计某部书里“之”“其”“以”在不同语法位置上的频率这种分析必须建立在准确分词和词性标注之上才有意义。第三把Jiayan作为文言文翻译模型的预处理模块先用分词和词性约束候选再让生成模型出译文能明显减少自由生成带来的漏译和错译翻译结果也更忠实于原文的语法结构。最后聊一点个人体会。做古文NLP看起来是个小众领域但好处是需求非常明确古籍数字化、文言教学、数字人文研究都在等一套能用的分词和标点工具。通用NLP工具在这个场景下水土不服不是模型能力的问题而是任务定义和训练数据根本不在一条轨道上。Jiayan让我踏实的地方在于它把古汉语特有的词库合成、分词、词性标注、断句和标点串成了一条可以上线的链路而不是零散地给我几个脚本。后面我想在标点置信度评估和跨版本异文兼容这两个方向上继续做深如果你也在折腾文言文语料欢迎一起交流。本文还有配套的精品资源点击获取