HanLP中文分词实验包:词典匹配与二元语法实战

发布时间:2026/10/24 17:16:00
HanLP中文分词实验包:词典匹配与二元语法实战 简介这份资源是南京邮电大学自然语言处理课程实验一的完整实验报告面向正在学习中文分词与句法分析的高校学生及自学者帮助其梳理词典分词、二元语法分词两大核心任务的原理与实操流程。压缩包内仅含1个doc文档约232KB按实验目的、环境配置、原理内容与结果记录的顺序组织可直接对照填写或复盘。报告覆盖HanLP的segment分词指令与--no-tag参数用法、文件输入输出批处理、parse句法分析依存关系解读并给出前向最长匹配、后向最长匹配、双向最长匹配三种词典分词算法的结果对比与差异说明以及DoubleArrayTrieSegment、AhoCorasickDoubleArrayTrieSegment等类的Python实现代码。已有428人学习适合需要完成同类实验、理解分词歧义与依存句法输出的读者参考借鉴。1. 一份能跑通的分词实验包从词典匹配到二元语法拿到一份自然语言处理课程实验包最怕的不是代码难而是环境跑不起来、结果对不上、报告写不出。这份实验一资源围绕中文分词的两个基础任务展开词典分词和二元语法分词配套 HanLP 工具链、Python 脚本和语料文件覆盖命令行调用、Python API 调用、模型训练与加载全流程。它适合正在学 NLP 基础课、需要交实验报告的学生也适合想快速摸清中文分词底层逻辑的开发者。核心价值在于所有步骤都有可复现的命令和代码分词结果、词频统计、模型文件作用都能直接对照验证不是纸上谈兵。2. 环境搭不起来后面全是白搭HanLP 安装与命令行验证2.1 为什么选 HanLP 而不是自己手写分词器中文分词入门阶段很多人第一反应是拿词典加规则自己写一个最长匹配。但真跑起来会发现光一个“结婚的和尚未结婚的”就能让前向匹配翻车——它切出“和尚”而不是“和/尚未”。这类歧义靠手写规则越补越乱而 HanLP 内置了词典、二元语法模型和多种分词算法命令行和 Python 接口都能直接调省掉大量造轮子的时间。实验环境要求 Windows 加 Python 3.7 或 3.8这个组合在机房和老笔记本上都稳不建议上太高版本pyhanlp 对 3.9 以上偶尔会有 JPype 兼容问题。安装分两步先装 pyhanlp再让它自动下载数据包。常见做法是pip install pyhanlp hanlp第一条命令装 Python 包第二条命令触发 HanLP 的初始化它会提示你下载 data 包。如果机房网络受限可以手动把 data 目录放到pyhanlp/static/data下路径后面会用到。装完后在命令行输入hanlp能看到 segment、parse 等子命令说明说明环境通了。2.2 命令行分词与词性标注的开关实验里第一个任务是观察hanlp segment的输出。直接输入句子它会返回“词/词性”的格式比如“商品/n, 和/cc, 服务/vn”。词性标注对后续句法分析有用但如果你只想要纯分词结果加--no-tag就行hanlp segment --no-tag输入“结婚的和尚未结婚的确实在干扰分词啊”不加参数时输出带词性加了之后只留词语。这个开关在批量处理文本时很实用能减少输出体积。注意命令行交互模式下是逐行输入的粘贴多行句子时别带多余空行否则会多出空结果。文件输入输出是实验里容易卡住的一步。要求对input1.txt分词并输出到output1.txt指令是hanlp segment input1.txt output1.txt -a crf这里-a crf指定用 CRF 模型做分词比默认的词典匹配更适合处理未登录词。重定向符号和在 Windows 的 cmd 和 PowerShell 里都支持但 PowerShell 对编码更敏感如果输出文件乱码优先换 cmd 执行。跑完后打开 output1.txt应该能看到“春分/t 最/d 具/vg 灵性/n”这样的逐词标注和实验报告里的示例对得上说明流程没问题。2.3 Python 接口调用segment 和 parse 的最小可用代码命令行验证通过后实验要求用 Python 代码复现分词和句法分析。pyhanlp 的接口很直接from pyhanlp import * # 分词返回词性标注结果 print(HanLP.segment(商品和服务)) print(HanLP.segment(当下雨天地面积水分外严重)) print(HanLP.segment(王总和小丽结婚了)) print(HanLP.segment(欢迎新老师生前来就餐)) print(HanLP.segment(结婚的和尚未结婚的确实在干扰分词啊)) # 依存句法分析 print(HanLP.parseDependency(徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。))HanLP.segment返回的是 Term 列表每个 Term 带 word 和 nature 属性直接 print 就是“词/词性”格式。HanLP.parseDependency返回依存树输出每行的格式是“序号 词语 词语 词性 中心词序号 依存关系”比如“徐先生”对应“主谓关系”中心词指向“帮助”。这段代码跑通说明 Python 环境和 HanLP 数据包都正常。如果报ModuleNotFoundError检查是不是在错误的 conda 环境里执行如果报 JPype 相关错误多半是 JDK 版本不匹配装个 JDK 8 或 11 再试。3. 词典分词三种匹配算法前向、后向、双向到底差在哪3.1 最长匹配法的实现逻辑与分词结果对比实验要求对“结婚的和尚未结婚的”分别用前向最长匹配、后向最长匹配、双向最长匹配切分。这三种算法的核心区别在于扫描方向和歧义消解策略。前向最长匹配从左往右每次取词典中最长的词后向从右往左双向则同时跑前向和后向再按规则选一个更优的结果。用 Python 实现时核心是加载词典并做最大长度匹配。常见做法是把词典读成集合记录最大词长然后逐位置尝试匹配def forward_max_match(text, word_dict, max_len): result [] i 0 while i len(text): matched None for length in range(min(max_len, len(text) - i), 0, -1): candidate text[i:i length] if candidate in word_dict: matched candidate break if matched: result.append(matched) i len(matched) else: result.append(text[i]) i 1 return result这段代码里max_len是词典中最长词的长度用来限制每次匹配的窗口。word_dict是词典集合text是待分词句子。前向匹配从左往右扫后向匹配把 text 反转后同样处理再反转结果。双向匹配则分别跑前向和后向然后比较两者结果如果词数不同取词数少的如果词数相同取单字词更少的还相同就取后向结果。实际跑“结婚的和尚未结婚的”前向结果是[结婚, 的, 和尚, 未, 结婚, 的]后向和双向都是[结婚, 的, 和, 尚未, 结婚, 的]。前向翻车的原因很直观它从左往右贪心匹配到“和”字时发现“和尚”在词典里就直接切走了完全不管后面“尚未”能不能成词。后向从右往左先锁定“结婚的”再往前处理“和尚未”自然切出“和/尚未”。双向匹配在这里选了后向结果因为后向的单字词更少。3.2 DoubleArrayTrieSegment 与 AhoCorasick 分词器的差异实验还要求用DoubleArrayTrieSegment和AhoCorasickDoubleArrayTrieSegment两个类对同一段春分文字做词典分词。这两个类的底层数据结构不同前者用双数组 Trie 做前缀匹配后者用 Aho-Corasick 自动机做多模式匹配。对普通文本两者结果接近但 AC 自动机在长文本和大量模式串场景下更快。调用方式from pyhanlp import * # DoubleArrayTrieSegment segment DoubleArrayTrieSegment() print(segment.seg(春分之特别在一个“均”字均分了昼夜均分了寒暑均分了春天。)) # AhoCorasickDoubleArrayTrieSegment HanLP.Config.ShowTermNature False segment_ac JClass(com.hankcs.hanlp.seg.Other.AhoCorasickDoubleArrayTrieSegment)() print(segment_ac.seg(春分之特别在一个“均”字均分了昼夜均分了寒暑均分了春天。))DoubleArrayTrieSegment的输出里每个词后面带/null因为词典分词不附带词性ShowTermNature默认开着就会补 null。把HanLP.Config.ShowTermNature设为 False 后AC 分词器输出纯词语列表。注意 AC 分词器在这个例子里切得很碎出现“春/分/之/特/别”这种单字结果原因是它的词典加载方式和 DAT 不同对未登录词的处理更保守。这不是 bug而是两种分词器设计目标的差异DAT 适合通用分词AC 适合关键词提取和敏感词过滤这类多模式匹配任务。3.3 停用词过滤从 CoreStopWordDictionaryPath 到替换脚本停用词去除是文本预处理的高频操作。实验要求找到stopwords.txt路径并写代码过滤。HanLP 的停用词表路径可以通过HanLP.Config.CoreStopWordDictionaryPath拿到通常在pyhanlp/static/data/dictionary/stopwords.txt。过滤代码用双数组 Trie 做最长匹配替换from jpype import JString from pyhanlp import * HanLP.Config.ShowTermNature False def load_from_file(path): map JClass(java.util.TreeMap)() with open(path, encodingutf-8) as src: for word in src: word word.strip() map[word] word return JClass(com.hankcs.hanlp.collection.trie.DoubleArrayTrie)(map) def replace_stopwords_text(text, replacement, trie): searcher trie.getLongestSearcher(JString(text), 0) offset 0 result while searcher.next(): begin searcher.begin end begin searcher.length if begin offset: result text[offset:begin] result replacement offset end if offset len(text): result text[offset:] return result trie load_from_file(HanLP.Config.CoreStopWordDictionaryPath) text 走在春分二候的京城植物园的玉兰在蓝天下盛开…… print(replace_stopwords_text(text, *, trie))load_from_file把停用词逐行读入 TreeMap再构建 DoubleArrayTrie。replace_stopwords_text用getLongestSearcher在文本里找最长匹配的停用词命中就替换成*没命中的片段原样保留。这里有个血泪经验打开停用词文件必须指定encodingutf-8否则 Windows 默认 GBK 解码会报UnicodeDecodeError实验报告里也记录了这个坑。替换符用*只是方便肉眼确认哪些词被干掉了实际预处理时直接删掉或替换成空格更常见。4. 二元语法分词从语料训练到 Viterbi 解码4.1 训练语料的格式与 NatureDictionaryMaker 的三个输出文件二元语法分词的核心是用统计模型算词频和相邻词共现频率再用 Viterbi 算法找最优切分路径。实验给了一份极小的语料my_cws_corpus.txt商品 和 服务 商品 和服 物美价廉 服务 和 货币每行是一句已分好词的句子词之间用空格隔开。训练代码用CorpusLoader读语料给每个词打上词性标签“n”再交给NatureDictionaryMaker统计from pyhanlp import * CorpusLoader SafeJClass(com.hankcs.hanlp.corpus.document.CorpusLoader) NatureDictionaryMaker SafeJClass(com.hankcs.hanlp.corpus.dictionary.NatureDictionaryMaker) def train_bigram(corpus_path, model_path): sents CorpusLoader.convert2SentenceList(corpus_path) for sent in sents: for word in sent: word.setLabel(n) maker NatureDictionaryMaker() maker.compute(sents) maker.saveTxtTo(model_path) train_bigram(my_cws_corpus.txt, my_cws_corpus_model)convert2SentenceList把每行转成 Sentence 对象setLabel(n)统一词性避免词性不统一导致统计分散。maker.compute统计一元词频和二元共现saveTxtTo落盘生成三个文件my_cws_corpus_model.txt是一元语法模型每行“单词 词性 频次”.tr.txt和词性标注相关.ngram.txt是二元语法模型记录相邻词对的频次。这三个文件里一元模型决定单个词的概率二元模型决定相邻词搭配的合理性Viterbi 解码时两者一起用。4.2 加载模型查词频CoreDictionary 的路径覆盖技巧训练完模型后实验要求查“商品”和“和”的频率。关键操作是覆盖 HanLP 的默认词典路径让它指向刚训练出来的模型from pyhanlp import * def load_bigram(model_path): HanLP.Config.CoreDictionaryPath model_path .txt HanLP.Config.BiGramDictionaryPath model_path .ngram.txt CoreDictionary SafeJClass(com.hankcs.hanlp.dictionary.CoreDictionary) print(商品: , CoreDictionary.getTermFrequency(商品)) print(和: , CoreDictionary.getTermFrequency(和)) load_bigram(my_cws_corpus_model)CoreDictionaryPath指向一元模型BiGramDictionaryPath指向二元模型。getTermFrequency返回词频这里“商品”和“和”各出现 2 次和语料对得上。注意路径覆盖必须在加载 CoreDictionary 之前完成否则 HanLP 已经用默认词典初始化过了改路径不生效。这个顺序问题在实验里很容易翻车表现就是查出来的频次和语料对不上。4.3 ViterbiSegment 解码“商品和服务”的完整链路最后一步是用训练好的模型对“商品和服务”做分词。代码把训练和预测串在一起from pyhanlp import * CorpusLoader SafeJClass(com.hankcs.hanlp.corpus.document.CorpusLoader) NatureDictionaryMaker SafeJClass(com.hankcs.hanlp.corpus.dictionary.NatureDictionaryMaker) def train_bigram(corpus_path, model_path): sents CorpusLoader.convert2SentenceList(corpus_path) for sent in sents: for word in sent: word.setLabel(n) maker NatureDictionaryMaker() maker.compute(sents) maker.saveTxtTo(model_path) def predict(): HanLP.Config.CoreDictionaryPath my_cws_corpus_model.txt HanLP.Config.BiGramDictionaryPath my_cws_corpus_model.ngram.txt ViterbiSegment JClass(com.hankcs.hanlp.seg.Viterbi.ViterbiSegment) segment ViterbiSegment() s segment.seg(商品和服务) print(s) train_bigram(my_cws_corpus.txt, my_cws_corpus_model) predict()输出是[商品/n, 和/n, 服务/n]。ViterbiSegment 在解码时枚举所有可能切分用一元词频和二元共现频次算路径概率取概率最大的那条。语料里“商品 和 服务”出现过所以“商品/和/服务”这条路径得分最高。如果语料里只有“商品 和服”结果可能变成“商品/和服”这就是二元语法模型对语料分布的敏感性。参数上CoreDictionaryPath和BiGramDictionaryPath必须成对指向同一套模型混用默认词典和自训练二元模型会导致概率计算错位。5. 避坑与排查五个让实验卡住的真实问题5.1 停用词文件读取报 UnicodeDecodeError现象运行停用词过滤代码时抛UnicodeDecodeError: gbk codec cant decode byte。原因Windows 下open()默认用 GBK 解码而停用词表是 UTF-8 编码。解决打开文件时显式加encodingutf-8所有读写文本文件的地方都统一加上别依赖系统默认编码。5.2 模型路径覆盖后词频查询结果不对现象训练完模型查“商品”频次返回的是默认词典的值不是语料里的 2。原因HanLP.Config.CoreDictionaryPath的赋值发生在 CoreDictionary 已经初始化之后HanLP 内部缓存了旧词典。解决把路径覆盖放在所有 HanLP 词典调用之前或者在新进程里先设路径再查词频。实验代码里load_bigram函数先改路径再SafeJClass加载顺序不能反。5.3 命令行重定向在 PowerShell 下输出乱码现象hanlp segment input1.txt output1.txt在 PowerShell 里跑完output1.txt 打开是乱码。原因PowerShell 的默认输出编码和 HanLP 的 UTF-8 输出不一致。解决换 cmd 执行或者在 PowerShell 里先执行chcp 65001切到 UTF-8 代码页。更稳的做法是用 Python 脚本读写文件绕开 shell 编码问题。5.4 AhoCorasick 分词器切出大量单字现象用AhoCorasickDoubleArrayTrieSegment对春分文本分词结果全是“春/分/之/特/别”这种单字。原因AC 分词器加载的词典和 DAT 不同对未登录词不做额外合并只做严格的多模式匹配。解决这不是错误是设计差异。需要通用分词就用DoubleArrayTrieSegment或HanLP.segmentAC 分词器留给关键词匹配场景。5.5 pyhanlp 导入报 JPype 相关错误现象from pyhanlp import *时报jpype._jexception或找不到 JVM。原因机器上没装 JDK或者 JDK 版本和 pyhanlp 不兼容。解决装 JDK 8 或 11设置JAVA_HOME确保java -version能正常输出。conda 环境里如果装了多个 Python 版本确认 pip 装到了当前激活的环境。6. 把实验包用出报告之外的价值三个可复用的验证习惯这份实验包的价值不止于交一次作业。我带人做分词项目时习惯把里面的验证思路固化下来后面换语料、换模型都能直接套。第一个习惯是每次训练完二元模型先查几个高频词和低频词的频次和语料肉眼对一遍。频次对不上后面 Viterbi 解码全是错的这一步相当于黑匣子前的自检。第二个习惯是分词结果不只看整体专门挑歧义句跑前向、后向、双向三组对比记录哪种算法在什么句子上翻车。时间长了你会对“最长匹配的贪心边界”有肌肉记忆看到“和尚未”这种结构就知道前向要出事。第三个习惯是路径覆盖类操作一律写成一个函数函数里先改HanLP.Config再加载类绝不把赋值散落在代码各处。下面这个模板可以直接抄def load_custom_model(model_prefix): HanLP.Config.CoreDictionaryPath model_prefix .txt HanLP.Config.BiGramDictionaryPath model_prefix .ngram.txt CoreDictionary SafeJClass(com.hankcs.hanlp.dictionary.CoreDictionary) ViterbiSegment JClass(com.hankcs.hanlp.seg.Viterbi.ViterbiSegment) return CoreDictionary, ViterbiSegment()调用时先load_custom_model(my_cws_corpus_model)再查频次或分词顺序固定不会出现“改了路径但没生效”的玄学问题。另外停用词过滤的替换符建议从*改成空字符串或空格方便直接进入下游任务如果要做词云或关键词统计保留*反而会污染词表。从那以后我每次跑分词实验都强制先做三件事命令行hanlp segment验证环境、查两个词的频次验证模型路径、拿一个歧义句跑三种匹配算法验证理解。这三步走完后面写代码基本不会翻车。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询