Harper 词性标注系统实战:以《爱丽丝梦游奇境》标注快照为例

📅 发布时间:2026/9/14 3:21:36
Harper 词性标注系统实战:以《爱丽丝梦游奇境》标注快照为例
Harper 词性标注系统实战以《爱丽丝梦游奇境》标注快照为例【免费下载链接】harperOffline, privacy-first grammar checker. Fast, open-source, Rust-powered项目地址: https://gitcode.com/GitHub_Trending/har/harper本文以 Harper 词法核心harper-core中一份约 6000 行的词性标注POS Tagging快照文件为切入点完整拆解 Harper 的标注记号体系、快照生成与回归校验机制并结合《爱丽丝梦游奇境》全文中的典型词条罗马数字、方言变体、缩略词、未收录专名等逐条演示如何阅读标注输出、如何在本地运行与扩展这套快照测试。1. 快照文件是什么双行式文本 标注语料被剖析的文件是 Alices Adventures in Wonderland.md位于harper-core/tests/text/tagged/目录由 POS 快照测试 自动生成。它与同目录上层的原始语料 Alices Adventures in Wonderland.mdMillennium Fulcrum 版本文本3000 行 Markdown一一对应。快照格式的规则很简单原文每一行被展开为两行——前缀行是原始文本#前缀行是对齐其下的词性标签见 pos_tags.rs 开头的文档注释 Alices Adventures in Wonderland # HeadingStart NPr$ NPl/V3 NPr/J/R/P NSg CHAPTER I : Down the Rabbit - Hole # HeadingStart NSg/VB ISg/#r . NSg/VB/J/P D NSg/VB . NSg/VB Alice was beginning to get very tired of sitting by her sister on the bank , and # NPr VLPt NSg/Vg/J P NSg/VB J/R VP/J P NSg/Vg/J P ISg/D$ NSg/VB J/P D NSg/VB . VB/C对齐逻辑由 pos_tags.rs 中的Formatter实现Formatter::add会把词与标签两侧用空格补齐到相同宽度使标签正好落在对应单词下方。空白行段落分隔TokenKind::ParagraphBreak会额外产生一对空的/#标记行Markdown 标题行还会在行首追加HeadingStart标记对应 TokenKind::HeadingStart如上面两个书眉标题所示。这个快照目录与原始语料目录一一对应tests/text/下共 10 个语料Alices Adventures in Wonderland、The Great Gatsby、The Constitution of the United States、Computer science、Difficult sentences、Part-of-speech tagging、Spell、Spell.US、Swear、this and thattests/text/tagged/下就是它们的 10 份标注快照。用长篇经典文学做语料的价值在于它同时覆盖了标题、罗马数字章节号、引语、省略号、诗歌、未收录专名等大量边缘情况是标注器最严格的压力测试之一。2. 生成机制一个会自我修复的快照测试快照不是人手写的而是测试运行时的产物。入口是 pos_tags.rs 的test_pos_tagger#[test] fn test_pos_tagger() { snapshot::snapshot_all_text_files(tagged, .md, |source, _| { let dict FstDictionary::curated(); let document Document::new_markdown_default(source, dict); let mut formatter Formatter::new(); for token in document.fat_string_tokens() { match token.kind { TokenKind::Space(_) { /* ignore */ } TokenKind::ParagraphBreak { formatter.new_line(); formatter.new_line(); } TokenKind::Newline(_) { formatter.new_line(); } kind { let text token.content; let tag format_tag(kind); formatter.add(text, tag); } } } formatter.finish() }); }调用链与关键实现语料发现snapshot.rs 的get_text_files遍历tests/text/收集所有.txt/.md文件方言覆盖try_get_dialect_override从文件名的方言缩写中解析Dialect——例如Spell.US.md会以美式英语标注而Alices Adventures in Wonderland.md不含缩写走默认方言解析与标注测试闭包中 FstDictionary::curated() 加载策展词典Document::new_markdown_default 把 Markdown 解析为 token 序列fat_string_tokens 提供带TokenKind与内容切片的迭代器比对与写回tag_file将新生成的标注与已有快照做全量字符串比较——一致则通过不一致或缺失则先把新内容写回快照文件再返回错误Snapshot mismatches!/No snapshot!并行执行snapshot_all_text_files用 rayon 并行处理全部语料任一文件出错就让整个测试 panic。这套先更新、再失败的语义是典型的快照回归模式本地运行cargo test一次即可把漂移的标注刷新到磁盘提交后 CI 再跑同一测试就能拦截未审查的行为变化。正如 pos_tags.rs 的文档注释 所说This test will fail if the snapshot files are not up to date. This ensures that CI will fail if the POS tagger changes its behavior. 新增语料的流程同样是零配置的把.md放进tests/text/跑一次测试tests/text/tagged/下会自动生成对应快照。值得注意的是该路径下的标注是纯词典驱动的文档注释明确写道标签based on theTokenKindandDictWordMetadataof a token。从仓库结构看harper-pos-utils与harper-brill目录下存在另一套统计式分块/标注组件含训练产物model.mpk、vocab.json但本快照测试只依赖harper-core本身的词典元数据两者是相互独立的标注路线。3. 标注记号一套紧凑的、可扩展的标签语法全部记号的权威定义在 pos_tags.rs 的文档注释生成逻辑在format_word_tag与format_tag。注释中说明该体系受 Penn Treebank 词性标签集启发。完整记号表如下3.1 词类主干与后缀主干含义后缀含义N名词Sg/Pl单数 / 复数Pr专有名词proper noun$所有格如Alices→NPr$ᴹ上标物质/不可数名词上标既可作不可数又可数如rabbit-hole→NSgI代词Sg/Pl/$单数 / 复数 / 所有格V动词L系动词linkingX助动词auxiliaryB原形/词根lemmaP过去式兼过去分词规则动词同时置位Pt/Pp仅过去式 / 仅过去分词不规则动词可分别标记g进行时-ing3第三人称单数现在时J形容词C/S比较级 / 最高级R副词——C连词——D限定词$/dem/q所有格 / 指示代词 / 数量词P介词——B脏话swear——名词短语成员np_member——后缀的取舍有明确的可读性设计见format_word_tag中名词分支的注释不可数名词不追加Sg专有名词不追加Sg避免输出被默认值淹没——所以你会看到NPr专有名词短语成员如标题中的Alices之后的Adventures是NPl/V3而Wonderland是NSg而不是啰嗦的全量标记。3.2 特殊 token 与不确定性记号记号来源TokenKind说明.Punctuation所有标点含破折号、连字符、引号#Number数字如书名页的3.0#dDecade年代如 1970s 一类写法#r词典元数据is_roman_numerals()罗马数字?Word(None)词在词典中无元数据K仅含撇号正字法信息的词缩略词如ImW?其余无标注词未知词UnlintableUnlintable内联代码块等所有 linter 忽略的区域HeadingStartHeadingStartMarkdown 标题行首标记Space/Newline等对应变体快照中被直接忽略或用作换行不确定性用/拼接词典驱动标注天然面对一个词多身份的问题标签按名词/代词/动词/形容词/副词/连词/限定词/介词的顺序把所有可能的词类都列出如NSg/VB/J/P表示该词同时被词典识别为可数/不可数名词、动词过去式兼过去分词与形容词。注释给出的示例是N/V/J名词、动词和/或形容词。方言标注get_dialect_annotations在词类标签后追加方言维度——Am/Br/Ca/Au表示仅限单一变体NoAm美 加与Comm英 澳 加表示跨变体分组。4. 精读《爱丽丝》快照七个典型标注案例以下所有行号均指 tagged 快照文件。4.1 章节标题罗马数字的双重身份 CHAPTER I : Down the Rabbit - Hole # HeadingStart NSg/VB ISg/#r . NSg/VB/J/P D NSg/VB . NSg/VBI被标成ISg/#r——它同时命中了代词I单数、短语成员与罗马数字#r两条元数据这正是多身份并列记号的直观体现。到第二章则更纯粹CHAPTER II中的II只输出#r。标题中的Rabbit - Hole被切分为三个 token连字符标为.说明 hyphen 复合词在 token 层面是被拆开的名词短语成员负责把它们在语义上重新关联。4.2 数字 token3.0不是两个词书名页THE MILLENNIUM FULCRUM EDITION 3.0的标注为D NSg NSg NSg #L9-L103.0整体是一个TokenKind::Number只输出单个#。这说明 Harper 的 lexer 会把十进制数整体吸收而不是3、点、0三段。4.3 首句逐词解码was为什么是VLPt Alice was beginning to get very tired of sitting by her sister on the bank , and # NPr VLPt NSg/Vg/J P NSg/VB J/R VP/J P NSg/Vg/J P ISg/D$ NSg/VB J/P D NSg/VB . VB/CL17-L18这段展示了几乎全部主干Alice→NPr专有名词、名词短语成员was→VLPt动词 系动词L 过去式P三合一beginning→NSg/Vg/J词典同时认为它可以是单数名词、-ing 动词或形容词标注器不替读者做裁决very→J/R既是形容词也是副词的经典歧义her→ISg/D$单数代词兼所有格限定词bank→NSg/VB名词/动词歧义bank确实有动词义and→VB/C连词且词典把它记为某种基础词形态B后缀——从源码看B在动词无显式形式标记时是默认值format_word_tag L191-L193。4.4 缩略词K与未收录词?的分界第 411 行Oh dear, what nonsense Im talking!中Im标为K第 5900 行youve had、第 5912 行its getting同理K。而DinahllL148却标为?。两者都是撇号缩略区别在于词典是否持有该词的正字法信息format_word_tag的兜底逻辑是无词类标签且正字法含撇号 →K否则 →W?而format_tag对Word(None)词典完全查不到直接输出?L237-L241、L284-L291。同理故事里的专名Gryphon在 L5951 标为?——词典不认识它于是所有依赖该词是专有名词的 linter 在这类词上会保守行事。4.5 方言变体dreamed与neighbouring结尾段L5928-L5929 First , she dreamed of little Alice herself , and once again the tiny hands were # NSg/J . ISg VP/J/NoAm/Au P NPr/I/J/Dq NPr ISg . VB/C NSg/C R D NSg/J NPl/V3 VLPtdreamed→VP/J/NoAm/Au从词典标注看这个过去式/分词形式只被北美与澳洲方言启用英式变体走另一条目而 L5944 的neighbouring→Nᴹ/Vg/J/Comm则标记为英联邦Comm拼写-our 结尾。这两个标签直接服务于 Harper 的regionalisms.rs、orthographic_consistency.rs一类跨方言一致性检查——词典里存了哪个方言有哪个拼写linter 才能判断这里是否用错了变体。4.6 诗歌段落的Unlintable区域第九章的打油诗L987-L994 “Fury said to a # Unlintable mouse, That he # Unlintable Unlintable从快照输出看这首诗被 lexer 整体识别为Unlintablethings like inline code blocks that should be ignored by all linters见 TokenKind 定义即整块跳过所有 lint 与标注。具体为何落入该区域需要对照原文的排版格式tests/text/下原文件的缩进写法判断但可以确定的是这是有意为之——不规则诗行若参与句法检查会产生大量噪音。4.7 引号、破折号与省略号文本中大量弯引号“ ” ‘ ’与长破折号—全部归入Punctuation→.不携带任何语义HeadingStart只出现在#/##标题。这意味着快照中每一个非.的符号 token 都是有语义决策的阅读快照时可以先扫.之外的部分快速抓住标注动作。5. 同一语料的双重身份标注快照 lint 快照tests/text/语料还驱动另一套快照测试 linters.rs它对同一批文档跑全部策展 linterLintGroup把每条诊断渲染成带行号上下文的 YAML 报告写入tests/text/linters/。对应的 Alices Adventures in Wonderland.snap.yml3700 余行里可以看到例如Lint: Readability (127 priority) Message: | 9 | Alice was beginning to get very tired of sitting by her sister on the bank, and | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ...This sentence is 57 words long.也就是说《爱丽丝》这份语料同时回归验证两条管线词性标注tagged/标签逐字符比对与lint 规则输出linters/诊断逐条比对。linters.rs中的Lines/print_errorL20-L120负责把字节偏移换算成行/列并补上下文行另有 run_tests.rs 用create_test!宏对tests/test_sources/下的场景文档断言精确的 lint 数量。三层测试共同构成 Harper 的行为基线。6. 运行与扩展在仓库根目录只读克隆下执行即可# 运行全部 POS 标注快照测试Alice 语料在其中被并行处理 cargo test --test pos_tags # 运行 lint 报告快照测试 cargo test --test linters # 只跑 Alice 相关用例名称过滤 cargo test --test pos_tags # 快照测试是单一 test内部并行处理所有文件扩展步骤与 pos_tags.rs 文档注释 一致把新语料放入harper-core/tests/text/.md或.txt文件名含方言缩写如Foo.US.md可覆盖默认方言运行cargo test --test pos_tags测试会把tests/text/tagged/Foo.md与tests/text/linters/Foo.snap.yml生成/刷新出来并在有漂移时失败——人工审阅 diff 后再提交即可把新语料纳入 CI 回归基线。7. 小结tagged/Alices Adventures in Wonderland.md 表面上是 6000 行单词下面垫标签的数据文件实际上它是 Harper 词性标注子系统的可执行规约记号体系以N/I/V/J/R/C/D/P为骨架、Sg/Pl/Pr/$/ᴹ/等后缀表达形态、/表达词类不确定性、#r/#/#d表达数字类 token、NoAm/Comm等后缀表达方言归属pos_tags.rs L25-L80生成与校验Document::new_markdown_default 策展词典 →fat_string_tokens→Formatter对齐输出 → 与快照全量比对先写回后报错的快照回归语义由 snapshot.rs 实现实战价值罗马数字歧义ISg/#r、方言拼写VP/J/NoAm/Au、Nᴹ/Vg/J/Comm、缩略词与未知词Kvs?、Unlintable豁免区等边缘行为都以可 diff 的纯文本形式固化在仓库里任何标注器或词典改动都会在 CI 中暴露成快照漂移。对希望理解 Harper 内部机制或其 linter 为何对某类词保守/激进的读者从这份快照逐行对照原文是成本最低的入口。【免费下载链接】harperOffline, privacy-first grammar checker. Fast, open-source, Rust-powered项目地址: https://gitcode.com/GitHub_Trending/har/harper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考