阶段4 | NLP传统 · 第19课/共30课

📖 第19课:HMM词性标注

🎯 课程目标

隐马尔可夫模型(HMM)是序列标注的经典方法。本课从零实现HMM词性标注器,深入理解Viterbi算法。

📐 核心原理

HMM五元组

💡 Viterbi vs 贪心:贪心每步选最优,可能错过全局最优。Viterbi用动态规划保证全局最优路径,复杂度O(T·N²)。

🔬 从零实现HMM词性标注

HMM + Viterbi词性标注器

import numpy as np from collections import defaultdict class HMMPosTagger: def __init__(self): self.transition = defaultdict(lambda: defaultdict(float)) self.emission = defaultdict(lambda: defaultdict(float)) self.tag_counts = defaultdict(int) self.vocab = set() self.tags = set() def train(self, tagged_sentences): for sentence in tagged_sentences: prev_tag = '<START>' for word, tag in sentence: self.transition[prev_tag][tag] += 1 self.emission[tag][word] += 1 self.tag_counts[tag] += 1 self.vocab.add(word) self.tags.add(tag) prev_tag = tag # 平滑 + 归一化 for prev in self.transition: total = sum(self.transition[prev].values()) for tag in self.transition[prev]: self.transition[prev][tag] /= total for tag in self.emission: total = sum(self.emission[tag].values()) for word in self.emission[tag]: self.emission[tag][word] /= total def viterbi(self, words): tags = sorted(self.tags) n = len(words) V = [{} for _ in range(n)] back = [{} for _ in range(n)] for tag in tags: emit_p = self.emission[tag].get(words[0], 1e-6) trans_p = self.transition['<START>'].get(tag, 1e-6) V[0][tag] = trans_p * emit_p back[0][tag] = '<START>' for t in range(1, n): for tag in tags: emit_p = self.emission[tag].get(words[t], 1e-6) best_score, best_tag = -1, tags[0] for prev_tag in tags: score = V[t-1].get(prev_tag, 0) * self.transition[prev_tag].get(tag, 1e-6) * emit_p if score > best_score: best_score = score best_tag = prev_tag V[t][tag] = best_score back[t][tag] = best_tag best_tag = max(V[-1], key=V[-1].get) if V[-1] else tags[0] result = [best_tag] for t in range(n-1, 0, -1): best_tag = back[t][best_tag] result.insert(0, best_tag) return list(zip(words, result)) # 训练数据 train_data = [ [('我', 'r'), ('喜欢', 'v'), ('自然', 'n'), ('语言', 'n'), ('处理', 'v')], [('他', 'r'), ('在', 'p'), ('北京', 'ns'), ('学习', 'v'), ('计算机', 'n')], [('这', 'r'), ('是', 'v'), ('一个', 'm'), ('重要', 'a'), ('的', 'u'), ('问题', 'n')], [('人工', 'n'), ('智能', 'n'), ('技术', 'n'), ('发展', 'v'), ('迅速', 'a')], [('机器', 'n'), ('学习', 'v'), ('是', 'v'), ('人工智能', 'n'), ('的', 'u'), ('核心', 'n')], ] hmm = HMMPosTagger() hmm.train(train_data) # 测试 test_sentences = [ ['我', '学习', '计算机'], ['人工智能', '发展', '迅速'], ['他', '喜欢', '自然', '语言'], ] print("=== HMM词性标注 ===") for words in test_sentences: result = hmm.viterbi(words) print(f" {' '.join(words)} → {[(w, t) for w, t in result]}")
=== HMM词性标注 === 我 学习 计算机 → [('我', 'r'), ('学习', 'v'), ('计算机', 'n')] 人工智能 发展 迅速 → [('人工智能', 'n'), ('发展', 'v'), ('迅速', 'a')] 他 喜欢 自然 语言 → [('他', 'r'), ('喜欢', 'v'), ('自然', 'n'), ('语言', 'n')]

✅ 验证通过:代码实机运行结果如上

📊 HMM的两大假设与局限

  1. 马尔可夫假设:当前标签只依赖前一个标签 → 忽略远距离依赖
  2. 观测独立假设:当前词只依赖当前标签 → 忽略上下文词的影响

这两个假设太强,导致HMM无法利用"下一个词"的信息。CRF和BiLSTM正是为了解决这些局限。

📝 练习

动手练习

  1. 实现前向算法计算观测序列概率P(O|λ)
  2. 添加拉普拉斯平滑处理未登录词
  3. 对比HMM和jieba词性标注的准确率
🎯

🏆 成就解锁:序列解码者

从零实现了HMM词性标注器,理解了Viterbi动态规划解码的核心思想。

🧪 扩展:HMM的三个基本问题

1. 评估问题(前向算法)

给定模型λ=(A,B,π)和观测序列O,计算P(O|λ)

应用:判断观测序列属于哪个HMM模型

2. 解码问题(Viterbi算法)

给定模型和观测序列,求最可能的状态序列

应用:词性标注——给定词序列,求最优标签序列

3. 学习问题(Baum-Welch算法)

给定观测序列,估计模型参数使P(O|λ)最大

应用:从标注语料中自动学习转移概率和发射概率

📚 扩展阅读:维特比算法详解

维特比算法的动态规划本质

维特比算法的核心思想:到达状态i的最优路径必然经过到达前一个状态j的最优路径。

递推公式:δ_t(i) = max[δ_{t-1}(j) · a_{ji}] · b_i(o_t)

其中δ_t(i)是时刻t到达状态i的最大概率,a_{ji}是转移概率,b_i(o_t)是发射概率。

复杂度:O(T·N²),T是序列长度,N是状态数。比暴力枚举O(N^T)高效得多。

🔬 扩展实践:HMM的完整实现

前向算法实现

前向变量α_t(i)表示时刻t到达状态i,且观测为o_1,...,o_t的概率:

α_t(i) = Σ_j α_{t-1}(j) · a_{ji} · b_i(o_t)

初始化:α_1(i) = π_i · b_i(o_1)

终值:P(O|λ) = Σ_i α_T(i)

Baum-Welch算法

当没有标注数据时,可以用EM算法迭代估计HMM参数:

  1. E步:用当前参数计算期望的转移/发射次数(ξ和γ变量)
  2. M步:用期望计数重新估计参数
  3. 收敛判断:对数似然变化量小于阈值则停止

注意:Baum-Welch只能保证收敛到局部最优,对初始化敏感。

🧪 扩展:HMM的三个基本问题

1. 评估问题(前向算法)

给定模型λ和观测序列O,计算P(O|λ)。应用:判断观测序列属于哪个HMM模型。

2. 解码问题(Viterbi算法)

给定模型和观测序列,求最可能的状态序列。应用:词性标注。

3. 学习问题(Baum-Welch算法)

给定观测序列,估计模型参数使P(O|λ)最大。应用:从标注语料中学习参数。

维特比算法的动态规划本质

到达状态i的最优路径必然经过到达前一个状态j的最优路径。

递推公式:δ_t(i) = max[δ_{t-1}(j) · a_{ji}] · b_i(o_t)

复杂度:O(T·N²),比暴力枚举O(N^T)高效得多。

📚 参考文献

推荐阅读

在线资源

HMM词性标注 - 关键概念速查

概念定义应用
Pipeline处理流水线,各步骤串行执行文本处理系统
Tokenization将文本切分为词/子词单元分词、子词分割
Embedding将离散符号映射到连续向量空间词向量、句向量
Feature Engineering从原始数据提取有效数值特征传统ML模型输入
Fine-tuning在预训练模型基础上微调BERT等模型适配

本课要点回顾

  1. HMM词性标注是NLP技术栈的重要组件
  2. 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
  3. 代码验证是学习的好方法——动手跑一遍胜过读十遍
  4. 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
  5. 实践出真知——用自己的数据跑一遍,发现课本上没写的问题

❓ 常见问题FAQ

Q: 这个方法的数据需求量是多少?
A: 传统方法通常需要几百到几千条标注数据。深度学习方法通常需要万级以上。数据量少时优先用传统方法。
Q: 如何处理中文的特殊情况?
A: 中文没有天然词边界,分词质量直接影响下游效果。建议:1)使用领域词典优化分词 2)比较不同分词工具的效果 3)对关键任务做分词错误分析。
Q: 传统方法和深度学习如何选择?
A: 考虑三个因素:1)数据量——少数据用传统方法 2)可解释性——需要解释选传统方法 3)计算资源——资源受限选传统方法。深度学习在大数据+强算力场景下优势明显。
Q: 如何评估和调优?
A: 1)建立基线:用最简单方法确定性能下限 2)错误分析:找出模型最常犯的错误类型 3)针对性改进:根据错误模式选择改进策略 4)避免过拟合:在独立测试集上验证。
Q: 生产环境部署要注意什么?
A: 1)延迟要求:简单模型推理更快 2)吞吐量:批处理提升吞吐 3)模型版本:做好版本管理和回滚 4)监控:持续跟踪线上模型性能 5)A/B测试:新模型上线前小流量验证。

🛠️ 实用工具箱

工具用途安装
jieba中文分词pip install jieba
gensim词向量训练pip install gensim
scikit-learn传统MLpip install scikit-learn
nltk英文NLP工具pip install nltk
spaCy工业NLP管线pip install spacy
hanlp中文NLP工具包pip install hanlp
fasttextFacebook文本分类pip install fasttext
textblob简单NLP接口pip install textblob