当前位置: 首页 > news >正文

别再死记硬背B/M/E/S了!用Python手把手带你跑通HMM中文分词(附完整代码与语料)

用Python实战HMM中文分词:从概率计算到Viterbi解码

自然语言处理中的中文分词一直是个有趣且实用的课题。想象一下,你正在开发一个电影评论分析系统,用户输入"这部电影太好看了",如何让计算机理解"这部/电影/太/好看/了"这样的合理切分?传统方法依赖词典匹配,但遇到新词就束手无策。隐马尔可夫模型(HMM)通过概率统计的方式,能自动学习分词规律,即使面对未登录词也有不错的表现。

今天我们不谈复杂的数学推导,直接动手用Python实现一个完整的HMM分词器。你会看到,从原始语料到最终分词结果,整个过程就像搭积木一样清晰有趣。我们将使用一个小型电影评论语料库作为示例,这样你可以立即看到模型在真实场景中的应用效果。

1. 环境准备与数据理解

首先确保你的Python环境安装了这些基础包:

pip install numpy tqdm

我们使用的训练数据是人工标注的电影评论,每个字后面跟着它的B/M/E/S标签:

  • B(词首)、M(词中)、E(词尾)、S(单字词)

示例数据片段:

这/B 部/E 电/B 影/E 真/S 的/S 好/B 看/E !/S 我/S 喜/B 欢/E 这/B 种/E 剧/B 情/E 片/E

关键理解:HMM认为每个字的标签(状态)只与前一个标签有关,而当前字(观测值)只与当前标签有关。这种"马尔可夫假设"大大简化了问题复杂度。

提示:实际项目中建议至少准备10万字的标注数据。我们这里为演示简化,只使用几十条评论。

2. 概率统计的三部曲

2.1 初始化概率矩阵

我们需要三个核心概率矩阵:

import numpy as np from collections import defaultdict # 初始化计数器 init_count = defaultdict(int) # 初始状态计数 trans_count = defaultdict(int) # 状态转移计数 emit_count = defaultdict(int) # 发射计数 state_count = defaultdict(int) # 状态出现总次数

统计过程示例:

def count_tags(sentences): for sentence in sentences: prev_tag = None for word, tag in sentence: if prev_tag is None: # 句首字 init_count[tag] += 1 else: # 非句首字 trans_count[(prev_tag, tag)] += 1 emit_count[(tag, word)] += 1 state_count[tag] += 1 prev_tag = tag

统计后的原始计数示例:

初始状态计数:{'B': 15, 'S': 32} 转移计数:{('B','E'):12, ('B','M'):3, ('E','B'):7, ...} 发射计数:{('B','电'):5, ('E','影'):8, ('S','我'):6, ...}

2.2 概率计算与平滑处理

原始统计会遇到零概率问题,需要拉普拉斯平滑:

def prob_with_smoothing(count, total, alpha=1.0, states=4): return (count + alpha) / (total + alpha * states)

生成概率矩阵的核心代码:

tags = ['B', 'M', 'E', 'S'] # 初始概率 init_prob = {tag: prob_with_smoothing(init_count[tag], sum(init_count.values())) for tag in tags} # 转移概率矩阵 trans_prob = {} for prev in tags: for curr in tags: key = (prev, curr) trans_prob[key] = prob_with_smoothing(trans_count.get(key,0), sum(v for k,v in trans_count.items() if k[0]==prev)) # 发射概率 emit_prob = {} for (tag, word), cnt in emit_count.items(): emit_prob[(tag, word)] = cnt / state_count[tag]

注意:实际应用中,发射概率对生僻字要做特殊处理,比如统一赋予一个极小值。

3. Viterbi算法实现

3.1 算法原理图解

Viterbi算法的精妙之处在于,它通过动态规划找到概率最大的状态路径。想象你在玩一个格子游戏:

  1. 每个格子代表一个字可能的状态(B/M/E/S)
  2. 格子之间的箭头代表转移概率
  3. 格子本身的亮度代表发射概率
  4. 从起点到终点要找出一条最亮的路径

3.2 Python代码实现

完整Viterbi实现:

def viterbi(sentence, init_prob, trans_prob, emit_prob, tags): # 初始化DP表格 dp = [{} for _ in range(len(sentence))] # 每个字的每个状态的最大概率 path = {} # 记录路径 # 初始化第一个字 for tag in tags: dp[0][tag] = init_prob.get(tag, 1e-6) * emit_prob.get((tag, sentence[0]), 1e-6) path[tag] = [tag] # 递推计算 for i in range(1, len(sentence)): new_path = {} for curr_tag in tags: max_prob = -1 best_prev_tag = None for prev_tag in tags: prob = dp[i-1][prev_tag] * \ trans_prob.get((prev_tag, curr_tag), 1e-6) * \ emit_prob.get((curr_tag, sentence[i]), 1e-6) if prob > max_prob: max_prob = prob best_prev_tag = prev_tag dp[i][curr_tag] = max_prob new_path[curr_tag] = path[best_prev_tag] + [curr_tag] path = new_path # 回溯最佳路径 best_tag = max(dp[-1], key=dp[-1].get) return path[best_tag]

测试用例:

text = "这部电影太精彩了" tags = viterbi(text, init_prob, trans_prob, emit_prob, ['B','M','E','S']) print("字\t标签") for char, tag in zip(text, tags): print(f"{char}\t{tag}")

输出示例:

字 标签 这 B 部 E 电 B 影 E 太 S 精 B 彩 E 了 S

4. 后处理与性能优化

4.1 从标签序列到分词结果

将标签序列转换为最终分词:

def tags_to_segs(sentence, tags): segs = [] word = [] for char, tag in zip(sentence, tags): word.append(char) if tag in ['E', 'S']: # 词结束或单字 segs.append(''.join(word)) word = [] return segs

4.2 工程优化技巧

  1. 对数空间计算:避免概率下溢
math.log(prob) # 将乘法转为加法
  1. 剪枝策略:每步只保留top-k路径
# 在Viterbi中增加 if len(dp[i]) > beam_size: dp[i] = dict(sorted(dp[i].items(), key=lambda x: -x[1])[:beam_size])
  1. 模型持久化:训练后保存概率矩阵
import pickle with open('hmm_model.pkl', 'wb') as f: pickle.dump((init_prob, trans_prob, emit_prob), f)

在实际项目中,我通常会先用jieba等成熟工具生成标注数据来训练自己的HMM模型,这样既能保证数据质量,又能理解底层机制。当遇到特定领域文本时,这种自训练的模型往往比通用模型表现更好。

http://www.jsqmd.com/news/944598/

相关文章:

  • 新手必看:Topxtral-4x7B-v0.1环境配置与依赖安装的极简步骤
  • 太强了!输入关键词,这几款AI论文写作工具自动生成毕业论文初稿!
  • 2026 成都离婚律所实测测评|打离婚官司优先选四川颂贤律师事务所 - 新闻快传
  • 自动驾驶协同感知架构的车道变换预测技术
  • 项目介绍 MATLAB实现基于DCT-XGB离散余弦变换(DCT)结合极端梯度提升(XGB)进行故障诊断分类预测(含模型描述及部分示例代码)专栏近期有大量优惠 还请多多点一下关注 加油 谢谢 你的鼓励
  • 从零搭建智能推送中枢:用LlamaIndex+RedisAI+自定义规则引擎,72小时内上线可商用版本
  • Step-Audio-Chat震撼发布:1300亿参数多模态语音大模型如何重塑人机交互体验?
  • 【新手向】 OpenClaw 部署分享,一键式安装包简化繁琐流程(含安装包)
  • Linux 内核中的 IO 调度优化:从信号捕获到自动维护监控系统
  • BALF框架:无需微调的模型压缩技术解析
  • 2026破圈!5款AI论文写作工具亲测,告别推倒重来,初稿一气呵成
  • 信创迁移:Oracle切换海量数据库,慢sql扫描
  • 别只看落款印章!字画鉴藏真正核心不在这 - 深鉴新闻
  • 2026年5月中职美术统考机构推荐,美术统考考前集训/中考美术辅导/美术统考冲刺/少儿美术培训,中职美术统考机构哪家可靠 - 品牌推荐师
  • 【RT-DETR实战】124、使用Vitis AI在FPGA上部署RT-DETR:从模型量化到板卡推理的实战踩坑记录
  • CryptoBERT安全指南:保护敏感金融数据的最佳实践 [特殊字符]️
  • 效率直接起飞!2026年好用一键生成论文工具榜单,高质初稿轻松写
  • 回答简单描述
  • AI驱动的智能治理闭环构建(2024政企合规刚需版):从工具孤岛到动态风控中枢
  • 图论入门:从基础到遍历算法
  • macOS第三方鼠标体验差?Mac Mouse Fix如何解决滚动卡顿与按键失灵问题
  • 高级java每日一道面试题-2026年01月18日-实战篇[Docker]-如何清理仓库中的旧镜像?
  • kkfile安全预览minio的文件
  • 住建部2026城市体检全面启动 ——“一网统管”平台将成为核心载体
  • 免费高效的跨语言语义工具:cross-en-de-fr-roberta-sentence-transformer安装与配置指南
  • 智能反馈不是“加个评分按钮”!深度解析Transformer-based Feedback Encoder在低信噪比场景下的F1提升23.6%实证
  • ProteinMPNN:当AI学会“设计“蛋白质,生物医药的未来会怎样?
  • Python中模块导入方式
  • AI 不听话?7 步排查清单,从「它又犯病了」到「我懂了」
  • 智能拼团合规红线预警(GDPR+《生成式AI服务管理暂行办法》双框架适配方案),法务+技术联合签发