4. 基于深度学习的分词方法:BiLSTM-CRF、BERT分词、预训练模型的应用
好,咱们进入深度学习分词这个重头戏。说实话,我刚开始接触NLP那会儿,分词还在用隐马尔可夫和条件随机场。后来深度学习一上来,整个格局就变了。你想想看,传统方法说白了是在跟特征工程死磕,而深度学习呢?它自己就能从数据里学出特征来。
这一章我带你看看三个主流方案:BiLSTM-CRF、BERT分词,还有预训练模型怎么落地。嗯,都是我在项目里真刀真枪用过的。
4.1 从序列标注到深度学习
分词本质上是个序列标注问题。每个字对应一个标签:B(词首)、M(词中)、E(词尾)、S(单字成词)。传统CRF靠手工特征,深度学习直接端到端学习。
我个人习惯把深度学习分词分成两派:
- LSTM派:BiLSTM-CRF是经典代表,擅长捕捉长距离依赖
- Transformer派:BERT、RoBERTa这些,靠自注意力机制,并行计算效率高
我在项目中遇到过一个问题:LSTM虽然能处理长序列,但训练速度慢。后来换成BERT,速度上来了,但模型大了好几倍。说白了,这就是个trade-off。
4.2 BiLSTM-CRF:经典的双向序列模型
BiLSTM-CRF这个架构,我用了好几年。它由两部分组成:
- BiLSTM层:双向LSTM,每个位置输出一个隐状态向量
- CRF层:在标签序列上建模转移概率,保证输出合法
为什么会这样设计?因为单靠BiLSTM,每个位置的标签是独立预测的。你想想看,如果前一个字预测为B,后一个字预测为S,这就不合理——B后面应该跟M或E才对。CRF就是来解决这个问题的。
核心公式:CRF层计算整个标签序列的得分,包括发射分数(BiLSTM输出)和转移分数(标签间转移概率)。训练时最大化正确序列的得分。
# 伪代码示例:BiLSTM-CRF前向过程
def forward(self, input_ids):
# BiLSTM编码
lstm_out, _ = self.bilstm(input_ids)
# 线性层映射到标签空间
emissions = self.classifier(lstm_out) # [batch, seq_len, num_tags]
# CRF解码(维特比算法)
best_path = self.crf.decode(emissions)
return best_path
我记得有一次做医疗文本分词,BiLSTM-CRF在专业术语上表现很好。但有个坑:如果训练数据里某个标签组合出现太少,CRF的转移矩阵就会学偏。我曾经因为这个,在“阿司匹林”这种词上分错了三次。
避坑指南:我曾经在CRF层忘记加mask,导致padding位置也参与了转移概率计算。结果模型在短文本上表现奇差。记住:一定要对padding位置做mask处理。
4.3 BERT分词:预训练的力量
BERT出来之后,分词效果又上了一个台阶。它用的是子词分词(WordPiece),先把句子切成子词,再对每个子词做序列标注。
你可能会问:为什么不直接用BERT的tokenizer分词?嗯,这里要注意:BERT的tokenizer是粗粒度的,它会把“北京大学”切成“北京”和“大学”。但中文分词任务要求的是细粒度,比如“北京大学”作为一个词。所以我们需要在BERT之上再加一层序列标注。
具体做法是这样的:
- 用BERT的tokenizer把句子转成子词序列
- 每个子词对应一个BERT embedding
- 在BERT输出层接一个线性分类器,预测B/M/E/S标签
- 把子词级别的标签映射回原始字级别
小技巧:我建议在BERT后面加一个CRF层。虽然BERT本身已经很强了,但CRF能保证标签序列的合法性。我在电商评论分词项目里试过,加CRF后F1值提升了0.3%左右。
# BERT分词模型结构
class BertSegmentation(nn.Module):
def __init__(self, bert_model, num_tags):
super().__init__()
self.bert = bert_model
self.classifier = nn.Linear(768, num_tags)
self.crf = CRF(num_tags, batch_first=True)
def forward(self, input_ids, attention_mask, labels=None):
outputs = self.bert(input_ids, attention_mask=attention_mask)
sequence_output = outputs.last_hidden_state
emissions = self.classifier(sequence_output)
if labels is not None:
loss = -self.crf(emissions, labels, mask=attention_mask.bool())
return loss
else:
return self.crf.decode(emissions, mask=attention_mask.bool())
4.4 预训练模型的应用与微调
预训练模型的应用,说白了就是“拿来主义”。但怎么拿、怎么调,这里头有门道。
| 预训练模型 | 分词特点 | 适用场景 |
|---|---|---|
| BERT-base | 通用性强,中文用全词掩码 | 通用领域分词 |
| RoBERTa-wwm | 全词掩码,对中文更友好 | 新闻、文学文本 |
| ERNIE | 知识增强,实体识别强 | 专业领域(医疗、法律) |
| MacBERT | 纠错式掩码,鲁棒性好 | 噪声较多的文本 |
我个人习惯用RoBERTa-wwm做中文分词。为什么?因为它的全词掩码策略更符合中文特点。你想想看,BERT的随机掩码会把“北京”拆成“北”和“京”分别掩码,但RoBERTa会把整个“北京”一起掩码,这样模型能学到更完整的词义。
微调的时候有几个要点:
- 学习率:预训练模型参数已经很好,学习率要小,我一般用2e-5到5e-5
- 冻结策略:可以先冻结BERT层,只训练分类器,等分类器收敛后再解冻
- 数据增强:我在项目中用过回译和随机替换,对分词任务帮助不大,反而引入了噪声
经验之谈:我曾经在一个金融文本分词项目里,直接用BERT-base微调,效果一般。后来换成RoBERTa-wwm,F1值从96.2%涨到了97.1%。但代价是模型大了30%,推理速度慢了20%。嗯,这就是精度和效率的取舍。
4.5 三种方法的对比与选型
说了这么多,到底该用哪个?我整理了一个对比表:
| 方法 | 精度 | 速度 | 资源消耗 | 适用场景 |
|---|---|---|---|---|
| BiLSTM-CRF | 中等(95-97%) | 快 | 低 | 资源受限、实时性要求高 |
| BERT+CRF | 高(97-98.5%) | 慢 | 高 | 精度优先、离线处理 |
| 预训练+微调 | 最高(98-99%) | 最慢 | 最高 | 学术研究、标杆系统 |
我的建议是这样的:
- 如果你在做产品,资源有限,BiLSTM-CRF就够了。我在一个移动端项目里就用它,效果不错。
- 如果精度是第一位,比如做知识图谱构建,那就上BERT+CRF。
- 预训练微调适合做研究或者标杆系统,生产环境慎用——推理速度是个大问题。
最后说一句:别迷信模型越大越好。我见过有人用GPT-2做分词,结果效果还不如BiLSTM-CRF。选模型要看任务、看数据、看资源。说白了,合适才是最好的。