资讯动态

HanLP词典分词与二元语法分词实战:从实验到工程

发布时间:2026/10/16 14:43:31 来源:尧图企业网站定制
简介本资源为南京邮电大学自然语言处理课程实验一的完整实验报告面向修读该课程的学生及希望入门中文分词与句法分析的初学者。报告围绕词典分词与二元语法分词两大核心任务展开涵盖HanLP分词指令、词性标注、文件输入输出、句法依存分析及Python代码实现等关键环节并对比前向、后向与双向最长匹配算法的差异。资源包内含1个doc文档约232KB结构完整可直接作为实验参考与报告模板。目前已有424人学习下载。读者可从中获取实验目的、环境配置、操作步骤、输出示例与代码片段快速理解HanLP工具的使用方法掌握分词与句法分析的基本流程为后续自然语言处理学习打下实践基础。1. 从一份实验报告说起HanLP 词典分词与二元语法分词到底怎么落地很多人第一次接触中文分词都是在课程实验里跑一遍hanlp segment看到终端吐出一串带词性标注的结果觉得“哦分词就是这样”。但真到自己动手写代码、换语料、调模型的时候问题就来了为什么同一句话前向最长匹配和后向最长匹配结果不一样为什么DoubleArrayTrieSegment和AhoCorasickDoubleArrayTrieSegment跑同一段文字一个切出了“春分”另一个切成了“春/分”二元语法训练出来的.ngram.txt到底存了什么这份实验报告围绕 HanLP 工具包把词典分词和二元语法分词两条线都走了一遍命令行调用、Python API 调用、自定义语料训练、Viterbi 分词预测每一步都有代码和输出。它适合正在学 NLP 基础、需要交实验报告或者想搞明白 HanLP 分词底层逻辑的开发者。下面我按实际拆项目的思路把这份报告里的技术点重新串一遍该补的配置补上该解释的参数解释清楚争取让你拿到就能跑、跑完能改。2. 环境搭建与 HanLP 命令行初探从安装到 segment、parse 指令2.1 环境准备Python 3.7/3.8 HanLP 安装实验报告里写的环境是 Windows Python 3.7 或 3.8这个版本区间不是随便定的。HanLP 的 Python 封装pyhanlp依赖 JPype 做 Java 桥接而 JPype 在 Python 3.9 以上版本容易出现兼容性问题常见做法是锁在 3.7 或 3.8。安装步骤不复杂但有几个坑我提前说# 创建虚拟环境推荐避免和系统包冲突 conda create -n nlp_lab python3.8 conda activate nlp_lab # 安装 pyhanlp pip install pyhanlp # 首次运行会自动下载数据包也可以手动触发 python -c from pyhanlp import *; print(HanLP.segment(测试))第一次 import 的时候pyhanlp 会去下载 HanLP 的 data 包大概几百 MB。如果网络环境不稳定这一步容易卡住。我一般会先手动把 data 包放到site-packages/pyhanlp/static/下面再跑代码。数据包放对位置后HanLP.Config.CoreDictionaryPath就能正确指向核心词典文件。提示如果你在命令行输入hanlp提示找不到命令说明 pyhanlp 的脚本入口没加到 PATH 里。Windows 下可以检查Scripts目录是否在环境变量中或者直接用python -m pyhanlp代替。2.2 命令行分词segment 指令与 --no-tag 参数HanLP 装好之后命令行直接敲hanlp segment就能进交互模式。实验报告里测了五个句子我挑两个有代表性的说hanlp segment # 输入商品和服务 # 输出商品/n, 和/cc, 服务/vn # 输入结婚的和尚未结婚的确实在干扰分词啊 # 输出结婚/vi, 的/ude1, 和/cc, 尚未/d, 结婚/vi, 的/ude1, 确实/ad, 在/p, 干扰/vn, 分词/n, 啊/y这里n是名词cc是并列连词vi是不及物动词ude1是“的”作为结构助词。HanLP 默认输出词性如果你只想要词本身加--no-taghanlp segment --no-tag # 输入商品和服务 # 输出商品 和 服务这个参数在批量处理时很有用因为词性标注会拖慢速度而且很多下游任务比如关键词提取只需要词边界。2.3 文件输入输出与 CRF 分词模式实验报告里有一个步骤是对input1.txt做分词并输出到output1.txt用的命令是hanlp segment input1.txt output1.txt -a crf这里-a crf指定了分词算法为 CRF条件随机场。HanLP 默认用的是 Viterbi 算法CRF 在未登录词和歧义切分上表现更好但速度慢一些。输出结果里能看到“春分/t”被正确切出来了t表示时间词。如果你不加-a crf默认 Viterbi 可能会把“春分”切成“春/分”这就是算法选择带来的差异。2.4 句法分析parse 指令与依存关系解读hanlp parse做的是依存句法分析输出每个词和它父节点的关系。实验报告里对“徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标”这句话的分析结果核心关系落在“帮助”上编号 4。整个输出是一个有向图每个词有一个_父节点编号和关系类型。hanlp parse 徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。 # 输出中4 帮助 帮助 vv_0 核心关系 # 表示“帮助”是根节点父节点编号为 0这个功能在实验里主要是让你理解 HanLP 不只能分词还能做句法层面的分析。实际项目中依存句法分析常用于关系抽取、事件抽取但计算成本比分词高不少一般不会全量跑。3. Python API 实战分词、句法分析与停用词过滤3.1 用 pyhanlp 复现命令行分词实验报告里把命令行测试的五个句子用 Python 重写了一遍代码很直接from pyhanlp import * # 分词并输出词性 print(HanLP.segment(商品和服务)) print(HanLP.segment(当下雨天地面积水分外严重)) print(HanLP.segment(王总和小丽结婚了)) print(HanLP.segment(欢迎新老师生前来就餐)) print(HanLP.segment(结婚的和尚未结婚的确实在干扰分词啊))HanLP.segment()返回的是一个Term列表每个Term有word和nature两个属性。如果你打印出来看到的是商品/n这种格式说明Term.toString()把词性也带上了。想只拿词可以遍历terms HanLP.segment(商品和服务) words [term.word for term in terms] print(words) # [商品, 和, 服务]3.2 句法分析的 Python 调用句法分析用HanLP.parseDependency()from pyhanlp import * sentence 徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。 result HanLP.parseDependency(sentence) print(result)返回的是一个CoNLLSentence对象每个CoNLLWord包含ID、LEMMA、CPOSTAG、POSTAG、HEAD、DEPREL等字段。实验报告里输出的表格就是这些字段的展开。实际用的时候你可以遍历result拿到每个词和它的依存关系做后续的规则匹配。3.3 停用词过滤加载 stopwords.txt 并替换停用词过滤是文本预处理里的常规操作。实验报告里的代码用DoubleArrayTrie加载停用词表然后用最长匹配搜索做替换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))这段代码的关键在getLongestSearcher它用双数组 Trie 做最长匹配保证“春分二候”这种多字停用词能被整体识别。实验报告里输出结果把“的”“在”“着”这些词替换成了*。注意HanLP.Config.CoreStopWordDictionaryPath指向的是 HanLP 自带的停用词表如果你想用自己的表把路径换掉就行。注意打开停用词文件时一定要指定encodingutf-8。实验报告里提到一个报错UnicodeDecodeError: gbk codec cant decode byte...原因就是 Windows 默认用 GBK 解码而文件是 UTF-8。这个坑我见过太多次了凡是读文本文件编码参数别省。4. 词典分词算法拆解前向、后向、双向最长匹配与 Trie 实现4.1 三种最长匹配算法的差异实验报告里对“结婚的和尚未结婚的”这句话分别跑了前向、后向、双向最长匹配算法分词结果前向最长匹配[结婚, 的, 和尚, 未, 结婚, 的]后向最长匹配[结婚, 的, 和, 尚未, 结婚, 的]双向最长匹配[结婚, 的, 和, 尚未, 结婚, 的]前向从左边开始每次取词典中最长的匹配词所以“和尚”被切出来了。后向从右边开始优先匹配“尚未”结果更符合语义。双向最长匹配一般会结合前向和后向的结果按一定规则选一个比如词数最少、单字最少。这里双向的结果和后向一致说明后向在这个句子上更优。这三种算法实现起来不复杂核心就是维护一个词典 Trie然后按方向扫描。前向扫描时从当前位置往后找最长匹配后向扫描时从末尾往前找。双向则是两个都跑一遍再按启发式规则选。4.2 DoubleArrayTrieSegment 与 AhoCorasickDoubleArrayTrieSegment实验报告里用两个类对同一段文字做分词结果差异很大from pyhanlp import * # 方式一DoubleArrayTrieSegment segment DoubleArrayTrieSegment() print(segment.seg(春分之特别在一个“均”字均分了昼夜...)) # 输出[春分/null, 之/null, 特别/null, ...] # 方式二AhoCorasickDoubleArrayTrieSegment HanLP.Config.ShowTermNature False segment JClass(com.hankcs.hanlp.seg.Other.AhoCorasickDoubleArrayTrieSegment)() print(segment.seg(春分之特别在一个“均”字均分了昼夜...)) # 输出[春, 分, 之, 特, 别, ...]DoubleArrayTrieSegment用的是双数组 Trie 做最长匹配所以“春分”被整体切出来。AhoCorasickDoubleArrayTrieSegment用的是 AC 自动机它做的是全切分把所有可能的词都切出来所以“春”“分”单独成词。AC 自动机适合做关键词过滤、敏感词检测因为它能一次扫描找出所有匹配模式而最长匹配适合做常规分词保证切分结果不重叠。4.3 核心词典路径与自定义词典实验报告里有一行代码输出核心词典路径from pyhanlp import * print(HanLP.Config.CoreDictionaryPath) # 输出类似D:/miniconda3/envs/myenv/lib/site-packages/pyhanlp/static/data/dictionary/CoreNatureDictionary.txt这个文件就是 HanLP 的核心词典每行格式是词 词性 频次。如果你想加自定义词比如“春分二候”可以直接往这个文件里追加或者用HanLP.Config.CustomDictionaryPath指定额外词典。我一般不会直接改核心词典而是单独维护一个自定义词典文件在代码里配置路径这样升级 HanLP 的时候不会丢。5. 二元语法分词全流程语料训练、模型加载与 Viterbi 预测5.1 构建语料与训练二元语法模型实验报告里新建了一个my_cws_corpus.txt内容如下商品 和 服务 商品 和服 物美价廉 服务 和 货币然后调用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) # 统一词性为 n maker NatureDictionaryMaker() maker.compute(sents) maker.saveTxtTo(model_path) train_bigram(my_cws_corpus.txt, my_cws_corpus_model)训练完会生成三个文件文件名作用my_cws_corpus_model.txt一元语法模型每行是词 词性 频次my_cws_corpus_model.tr.txt词性标注相关模型my_cws_corpus_model.ngram.txt二元语法模型记录词对共现频次word.setLabel(n)这步是把所有词的词性统一设成名词因为实验只关心分词不关心词性。实际项目中如果你有标注语料可以保留原始词性。5.2 加载模型并查询词频训练完之后加载模型查“商品”和“和”的频率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) # 输出商品: 2和: 2这里把CoreDictionaryPath和BiGramDictionaryPath都指向训练出来的模型文件HanLP 就会用你的小语料词典而不是默认的大词典。getTermFrequency返回的是词在一元模型里的频次。5.3 ViterbiSegment 分词预测最后用训练好的模型对“商品和服务”做分词from pyhanlp import * 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) predict() # 输出[商品/n, 和/n, 服务/n]Viterbi 算法在分词里的作用是给定一个句子找到概率最大的词序列。它用动态规划的方式结合一元词频和二元转移概率算出最优路径。这里因为语料里“商品 和 服务”出现过所以模型正确切出了“商品/和/服务”。如果你换成“商品和服”模型可能会切出“商品/和服”因为“和服”在语料里也出现过。提示二元语法模型的效果高度依赖语料规模。实验里只有三行语料所以只能处理这几个词。实际项目中你需要几十万甚至上百万行的标注语料训练出来的模型才有泛化能力。6. 避坑与排查编码、路径、模型加载的常见问题6.1 UnicodeDecodeError: gbk codec cant decode现象读取停用词文件或语料文件时Python 报UnicodeDecodeError: gbk codec cant decode byte 0x90 in position 2519。原因Windows 中文环境下open()默认用 GBK 编码而文件实际是 UTF-8。GBK 解码遇到 UTF-8 的多字节序列就会报错。解决所有文本文件读取都显式指定encodingutf-8。如果文件确实是 GBK那就用encodinggbk但最好统一转成 UTF-8。6.2 HanLP 数据包下载失败或路径不对现象HanLP.segment()报错找不到词典或者HanLP.Config.CoreDictionaryPath指向一个不存在的路径。原因pyhanlp 首次运行需要下载 data 包网络问题会导致下载中断或者手动放置 data 包时目录层级不对。解决检查site-packages/pyhanlp/static/下是否有data目录里面应该有dictionary/CoreNatureDictionary.txt。如果没有重新触发下载或手动解压。路径不对的话可以用HanLP.Config.CoreDictionaryPath 你的路径手动指定。6.3 自定义模型加载后分词结果不变现象训练完二元语法模型改了CoreDictionaryPath和BiGramDictionaryPath但分词结果和默认模型一样。原因HanLP 的配置是全局的如果你在加载自定义模型之前已经调用过HanLP.segment()默认词典可能已经被缓存。另外ViterbiSegment实例化时会读取当前配置配置改晚了就不生效。解决把配置代码放在所有分词操作之前或者用SafeJClass重新加载类。我一般会在脚本开头就设好路径避免中途改配置。6.4 DoubleArrayTrieSegment 输出词性为 null现象用DoubleArrayTrieSegment分词结果里每个词的词性都是null。原因DoubleArrayTrieSegment只做词典匹配不加载词性标注模型所以词性字段为空。解决如果只需要词边界这不算问题如果需要词性换用HanLP.segment()或ViterbiSegment。或者手动加载词性词典但一般没必要。6.5 命令行 hanlp 命令找不到现象在 CMD 或 PowerShell 里输入hanlp提示“不是内部或外部命令”。原因pyhanlp 安装后会在Scripts目录生成hanlp.exe但这个目录没加到系统 PATH。解决找到 Python 环境的Scripts目录加到 PATH 里或者直接用python -m pyhanlp调用。conda 环境下激活环境后一般能直接用如果不行就检查 conda 的 Scripts 路径。7. 二次开发改造从实验代码到可复用分词模块实验报告里的代码是分散的脚本每个任务一段。如果你想把它们整合成一个可复用的分词模块我一般会做这几件事。第一把配置集中管理。HanLP 的路径配置散落在各个函数里改起来容易漏。我会写一个config.py# config.py from pyhanlp import HanLP def init_hanlp(core_dictNone, bigram_dictNone, stopword_pathNone): if core_dict: HanLP.Config.CoreDictionaryPath core_dict if bigram_dict: HanLP.Config.BiGramDictionaryPath bigram_dict if stopword_path: HanLP.Config.CoreStopWordDictionaryPath stopword_path HanLP.Config.ShowTermNature False第二把分词、停用词过滤、句法分析封装成类# segmenter.py from pyhanlp import * from config import init_hanlp class Segmenter: def __init__(self, core_dictNone, bigram_dictNone): init_hanlp(core_dict, bigram_dict) self.segment JClass(com.hankcs.hanlp.seg.Viterbi.ViterbiSegment)() def cut(self, text): return [term.word for term in self.segment.seg(text)] def cut_with_pos(self, text): return [(term.word, term.nature) for term in self.segment.seg(text)]第三加一个批量处理入口支持文件输入输出# batch.py import sys from segmenter import Segmenter def process_file(input_path, output_path): seg Segmenter() with open(input_path, encodingutf-8) as fin, \ open(output_path, w, encodingutf-8) as fout: for line in fin: words seg.cut(line.strip()) fout.write( .join(words) \n) if __name__ __main__: process_file(sys.argv[1], sys.argv[2])这样改造之后你换语料、换模型只需要改配置不用动核心逻辑。验证方法也简单拿实验报告里的五个句子跑一遍对比输出是否和HanLP.segment()一致再用自定义语料训练一个小模型看 Viterbi 分词是否按预期切分。还有一个技巧如果你要做对比实验比如比较前向、后向、双向最长匹配的效果可以把三种算法实现成统一接口输入句子输出词列表然后用同一批测试句跑评估。评估指标可以用准确率、召回率、F1前提是你有标注好的黄金标准。实验报告里没有这一步但这是从实验走向工程的关键一步。从那以后我每次换语料或改模型配置都强制走一遍“训练→加载→预测→对比”的流程确认输出符合预期再往下做。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑