本文目录导读:
- 任务建模为序列标注问题
- CRF 模型的数学形式
- 特征工程(手工特征 + 自动特征)
- 训练流程(以 BiLSTM-CRF 为例)
- 预测过程
- 为什么选择 CRF 而非其他模型?
- 实践建议
- 简单代码示例(PyTorch + torchcrf)
条件随机场(CRF)是一种序列标注模型,非常适合处理中文分词和命名实体识别(NER)这种需要依赖上下文信息的任务,CRF 的主要优势在于它能够考虑相邻标签之间的转移关系(即标签之间的约束),从而避免输出不合逻辑的标签序列。
以下分别介绍如何使用 CRF 进行中文分词和命名实体识别,包括核心思想、特征构建、训练和预测流程。
任务建模为序列标注问题
无论是中文分词还是 NER,都需要将输入序列(如一个个汉字)映射为对应的标签。
中文分词标签体系(常用:BMES)
- B(Begin):词的开头
- M(Middle):词的中间
- E(End):词的结尾
- S(Single):单字成词
示例:
输入:我 爱 自 然 语 言 处 理
标签:S S B M E B M E
分词结果:我 / 爱 / 自然 / 语言 / 处理
命名实体识别标签体系(常用:BIO 或 BIOES)
- B-ORG:机构名开始
- I-ORG:机构名内部
- B-PER:人名开始
- I-PER:人名内部
- O:非实体
示例:
输入:张 三 在 北 京 大 学 工 作
标签:B-PER I-PER O B-ORG I-ORG I-ORG I-ORG O O
实体:张三(人名)、北京大学(机构)
CRF 模型的数学形式
CRF 定义在给定观测序列 (X = (x_1, x_2, \dots, x_n)) 上的标签序列 (Y = (y_1, y_2, \dots, y_n)) 的条件概率为:
[ P(Y|X) = \frac{1}{Z(X)} \exp\left( \sum{i=1}^{n} \sum{k} \lambda_k fk(y{i-1}, y_i, X, i) \right) ]
- (f_k) 是特征函数(可以是状态特征或转移特征)
- (\lambda_k) 是学习到的权重
- (Z(X)) 是归一化因子(通过动态规划计算)
核心思想:不是独立预测每个位置的标签,而是考虑相邻标签的兼容性。
特征工程(手工特征 + 自动特征)
在传统 CRF 中,特征通常由人工设计,现代实践中,常用预训练 embedding(如 BERT)作为输入特征,再使用 CRF 层输出。
手工特征(传统方法)
- 当前字符、前后1-2个字符
- 字符类型:是否数字、是否标点、是否英文
- 词边界信息:是否有词表匹配(北京”是一个词)
- n-gram 特征:前后2-3字组合
自动特征(深度学习 + CRF)
- 使用 BiLSTM 或 BERT 提取每个位置的高维特征向量
- 将特征向量输入到 线性层,得到每个位置的 发射分数(Emission Score)
- 再通过 CRF 层处理 转移分数(Transition Score)
训练流程(以 BiLSTM-CRF 为例)
# 伪代码流程 1. 输入:字符序列 [c1, c2, ..., cn] 2. 字符嵌入 -> BiLSTM -> 每个位置的特征向量 hi 3. 线性层:score_i = W * hi + b # 得到每个位置对每个标签的分数 4. CRF 层: - 定义转移矩阵 T(形状 [tag_num, tag_num]) - 计算路径得分:score = sum(score_i[yi]) + sum(T[yi-1, yi]) - 使用维特比算法(Viterbi)解码最优标签序列 5. 损失函数:最大化正确路径的对数概率(负对数似然)
常见深度学习框架(PyTorch / TensorFlow)中已有 CRF 层实现(如 torchcrf 或 keras-crf)。
预测过程
- 输入新句子
- 用训练好的模型计算每个位置的发射分数
- 加上学习到的转移分数
- 使用 维特比算法 找出全局最优的标签序列
- 根据标签还原分词或实体
示例(BERT-CRF 预测 NER):
输入:王小明在北京大学读书
标签:B-PER I-PER I-PER O B-ORG I-ORG I-ORG O O
实体:[王小明](人名),[北京大学](机构)
为什么选择 CRF 而非其他模型?
| 特性 | CRF | Softmax(独立分类) | RNN/Transformer 直接输出 |
|---|---|---|---|
| 考虑标签依赖 | ✅ 是 | ❌ 否 | ❌ 否(除非特殊设计) |
| 全局最优解码 | ✅ 是 | ❌ 贪婪解码 | ❌ 通常贪婪解码 |
| 处理 BIO 约束 | ✅ 如 I 不能跟 O | ❌ 可能输出 B 后跟 O | ❌ 同上 |
CRF 的典型优势:I-ORG”前面必须是“B-ORG”或“I-ORG”,而“O”后面不能直接跟“I-ORG”开头,CRF 能自动学习这类约束。
实践建议
工具推荐
- 传统 CRF:
CRF++(C++,有 Python 封装)、sklearn-crfsuite - 深度学习 + CRF:
pytorch-crf(PyTorch 版)transformers库中的TokenClassification+CRFflair、spaCy内置 CRF 实现
数据量要求
- 传统 CRF(手工特征):几千到几万标注样本即可
- 深度学习 CRF(BiLSTM/BERT + CRF):通常需要数万到数十万句,但有预训练模型(如 BERT)时可极大减少需求
调参要点
- 转移矩阵的初始值:常用均匀初始化或从训练数据统计
- 学习率:对 CRF 层可适当调小
- 正则化:防止过拟合,尤其是特征数量很大时
简单代码示例(PyTorch + torchcrf)
import torch
import torch.nn as nn
from torchcrf import CRF
class BiLSTM_CRF(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_dim, num_tags):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim, bidirectional=True)
self.linear = nn.Linear(hidden_dim * 2, num_tags) # 发射分数
self.crf = CRF(num_tags, batch_first=True) # CRF 层
def forward(self, x, tags=None):
# x: [batch, seq_len]
emb = self.embedding(x)
lstm_out, _ = self.lstm(emb)
emissions = self.linear(lstm_out) # [batch, seq_len, num_tags]
if tags is not None:
# 训练:计算负对数似然损失
loss = -self.crf(emissions, tags)
return loss
else:
# 预测:维特比解码
return self.crf.decode(emissions)
- 中文分词和命名实体识别的本质都是序列标注任务。
- CRF 通过引入转移矩阵和全局解码,能有效避免不合逻辑的标签序列。
- 现代实践中,通常使用 BiLSTM/BERT + CRF 的组合,兼顾上下文表示和标签依赖建模。
- 特征工程、转移矩阵学习、维特比解码是 CRF 的核心环节。
如果你需要具体的中文分词或 NER 的完整代码示例(含数据处理、训练循环),也可以继续问我。
标签: 序列标注