作为一名长期奋战在NLP一线的算法工程师,我深知初学者在学习自然语言处理时最需要的是什么——不是晦涩的理论推导,而是可以直接上手实践的代码示例和避坑指南。这份笔记整合了我多年项目经验中最核心的NLP实战代码,每一行都经过生产环境验证,并附上显微镜级的解析。
中文NLP的第一步永远是分词。我们以jieba这个工业级分词工具为例,演示三种经典分词模式:
python复制import jieba
text = "传智教育是一家上市公司"
# 精确模式(推荐日常使用)
words = jieba.lcut(text)
# ['传智教育', '是', '一家', '上市公司']
# 全模式(召回所有可能分词)
words_all = jieba.lcut(text, cut_all=True)
# ['传智', '教育', '是', '一', '家', '上市', '公司']
# 搜索引擎模式(细粒度切分)
words_search = jieba.lcut_for_search(text)
# ['传智', '教育', '上市', '公司', '传智教育', '上市公司']
实际项目中精确模式使用率超过80%,但在搜索场景建议使用搜索引擎模式。全模式会产生大量无效分词,仅建议在特定召回场景使用。
jieba的词性标注功能可以帮我们识别每个词的语法角色:
python复制import jieba.posseg as pseg
text = "我爱自然语言处理"
for word, flag in pseg.lcut(text):
print(f"{word}: {flag}")
# 我: r (代词)
# 爱: v (动词)
# 自然语言: n (名词)
# 处理: vn (动名词)
遇到专业术语时,必须使用自定义词典避免错误切分:
python复制# 方法1:临时添加
jieba.add_word("传智教育")
# 方法2:批量加载词典文件
# my_dict.txt格式:词语 词频 词性
jieba.load_userdict("my_dict.txt")
text = "传智教育旗下有黑马程序员"
print(jieba.lcut(text)) # 正确识别专有名词
停用词过滤是提升模型效果的关键步骤:
python复制stopwords = {'的', '了', '是', '在', '我'} # 建议使用哈工大停用词表
text = "我是一名程序员,在北京工作"
words = jieba.lcut(text)
# 过滤停用词+单字词
filtered = [w for w in words if w not in stopwords and len(w) > 1]
print(filtered) # ['程序员', '北京', '工作']
实际项目中建议:
- 根据业务补充停用词(如电商场景过滤"包邮"等高频但无意义词)
- 保留否定词("不"、"没有"等)避免语义反转
python复制from tensorflow.keras.preprocessing.text import Tokenizer
vocabs = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"]
tokenizer = Tokenizer()
tokenizer.fit_on_texts(vocabs)
one_hot_matrix = tokenizer.texts_to_matrix(vocabs, mode='binary')
"""
[[1. 0. 0. 0. 0.] # 苹果
[0. 1. 0. 0. 0.] # 香蕉
[0. 0. 1. 0. 0.] # 橙子
[0. 0. 0. 1. 0.] # 葡萄
[0. 0. 0. 0. 1.]] # 西瓜
"""
One-Hot的致命缺陷:
python复制import fasttext
# 训练词向量(需准备corpus.txt语料)
model = fasttext.train_unsupervised(
'corpus.txt',
model='cbow', # 或'skipgram'
dim=100, # 向量维度
epoch=5 # 训练轮数
)
# 获取词向量
vec = model.get_word_vector('苹果') # (100,)
# 找相似词
similar = model.get_nearest_neighbors('苹果')
# [(0.92, '香蕉'), (0.87, '水果'), (0.82, '橙子')]
Word2Vec的优势:
深度学习模型通常使用Embedding层:
python复制import torch.nn as nn
# 词表大小10000,嵌入为128维
embedding = nn.Embedding(num_embeddings=10000, embedding_dim=128)
# 输入词索引 → 输出词向量
word_indices = torch.tensor([1, 5, 10]) # 3个词的索引
vectors = embedding(word_indices) # (3, 128)
实际项目经验:
- 预训练词向量初始化Embedding层可提升效果
- 微调(finetune)参数适应具体任务
python复制import seaborn as sns
# 统计标签分布
print(df['label'].value_counts())
# 可视化
sns.countplot(x='label', data=df)
plt.title('Label Distribution')
plt.show()
标签不均衡时的处理策略:
python复制# 计算文本长度
df['length'] = df['text'].apply(len)
# 箱线图分析
sns.boxplot(x='label', y='length', data=df)
根据长度分布确定max_length超参数:
python复制from collections import Counter
# 统计词频
all_words = []
for text in df['text']:
all_words.extend(jieba.lcut(text))
word_count = Counter(all_words)
print(word_count.most_common(30))
高频词分析能发现:
python复制import torch.nn as nn
rnn = nn.RNN(
input_size=10, # 输入特征维度
hidden_size=20, # 隐藏状态维度
num_layers=2, # 堆叠层数
batch_first=True # 输入格式为(batch, seq, feature)
)
# 输入形状:(3, 5, 10)
x = torch.randn(3, 5, 10)
# 前向传播
output, hn = rnn(x)
# output形状: (3, 5, 20) # 各时间步输出
# hn形状: (2, 3, 20) # 最终隐藏状态
RNN的梯度消失问题:
python复制# LSTM(解决长程依赖)
lstm = nn.LSTM(
input_size=10,
hidden_size=20,
num_layers=2,
bidirectional=True # 双向LSTM
)
# GRU(参数更少)
gru = nn.GRU(
input_size=10,
hidden_size=20,
num_layers=2
)
门控机制对比:
实际项目选型建议:
- 效果:LSTM ≈ GRU > RNN
- 速度:GRU > LSTM > RNN
- 默认先用GRU,效果不佳再试LSTM
想象翻译句子"I saw a saw":
注意力机制让模型动态关注相关部分,而非固定处理所有输入。
python复制class SelfAttention(nn.Module):
def __init__(self, embed_size, heads):
super().__init__()
self.heads = heads
self.head_dim = embed_size // heads
self.values = nn.Linear(embed_size, embed_size)
self.keys = nn.Linear(embed_size, embed_size)
self.queries = nn.Linear(embed_size, embed_size)
self.fc_out = nn.Linear(embed_size, embed_size)
def forward(self, values, keys, query, mask):
# 分头处理
values = values.view(N, -1, self.heads, self.head_dim)
# 注意力得分
energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])
# 掩码处理
if mask is not None:
energy = energy.masked_fill(mask == 0, float("-1e20"))
# 注意力权重
attention = torch.softmax(energy / (self.embed_size ** (1/2)), dim=3)
# 加权求和
out = torch.einsum("nhql,nlhd->nqhd", [attention, values])
return self.fc_out(out)
python复制class Transformer(nn.Module):
def __init__(self, src_vocab, trg_vocab, embed_size=256, heads=8):
super().__init__()
self.encoder = Encoder(src_vocab, embed_size, heads)
self.decoder = Decoder(trg_vocab, embed_size, heads)
self.fc_out = nn.Linear(embed_size, trg_vocab)
def forward(self, src, trg):
enc_src = self.encoder(src)
output = self.decoder(trg, enc_src)
return self.fc_out(output)
Transformer的核心优势:
python复制import fasttext
# 数据格式:__label__类别 文本
model = fasttext.train_supervised(
'train.txt',
lr=0.1,
epoch=5,
word_ngrams=2, # 使用bigram特征
dim=100
)
# 预测
pred, prob = model.predict("这个产品太糟糕了")
# pred: __label__negative
适用场景:
python复制from transformers import BertTokenizer, BertForSequenceClassification
# 加载预训练模型
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained(
'bert-base-chinese',
num_labels=2 # 二分类
)
# 微调训练
training_args = TrainingArguments(
output_dir='./results',
learning_rate=2e-5, # 必须小学习率
per_device_train_batch_size=16,
num_train_epochs=3
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset
)
trainer.train()
BERT使用要点:
python复制# 错误:batch_first=False时输入应为(seq, batch, feature)
rnn = nn.RNN(10, 20, batch_first=False)
input = torch.randn(3, 5, 10) # 错误形状!
python复制# 必须对padding位置加mask
src_mask = (src != pad_idx).unsqueeze(1).unsqueeze(2)
code复制BERT微调:2e-5
从头训练:1e-3
python复制# 微调时应冻结底层参数
for param in model.base_model.parameters():
param.requires_grad = False
python复制# 设置合理的max_length
lengths = [len(text) for text in texts]
max_len = int(np.percentile(lengths, 95)) # 覆盖95%样本
python复制# 错误:在整体数据上做分词后再划分训练测试集
# 正确:先划分数据集,再分别处理
python复制# RNN/LSTM训练时需添加
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
python复制# 添加类别权重
weights = torch.tensor([1.0, 5.0]) # 少数类权重更大
criterion = nn.CrossEntropyLoss(weight=weights)
python复制# 时间序列数据必须按时间划分
# 普通数据需分层抽样保持分布
python复制# 专业领域需重新训练词向量
# 或使用领域适配的预训练模型
code复制nlp_project/
├── data/ # 数据目录
│ ├── raw/ # 原始数据
│ ├── processed/ # 处理后的数据
│ └── stopwords.txt # 停用词表
├── models/ # 模型存储
├── notebooks/ # 探索性分析
├── src/
│ ├── preprocessing.py # 预处理代码
│ ├── modeling.py # 模型定义
│ └── train.py # 训练脚本
└── config.yaml # 配置文件
关键成功要素:
这份笔记浓缩了我从入门到精通NLP过程中积累的实战经验,建议读者按照以下路径学习:
NLP领域日新月异,但这些基础方法和工程实践将长期有效。在实际项目中,我通常会先搭建一个基线系统(如FastText或BERT),再逐步迭代优化。记住:没有放之四海皆准的完美模型,只有适合具体业务场景的解决方案。