📖 导读 :
这份笔记整合了Code05/NLP下的所有代码,每一行都讲清楚是干啥的。看完能直接上手。
- 零遗漏原则:所有关键代码完整覆盖
- 显微镜式解析:逐行/逐参数讲解
- 场景化映射:代码对应实际应用
🗺️ 一、文本预处理
1.1 jieba分词
python
import jieba
# ========== 精确模式 ========
text = "传智教育是一家上市公司"
words = jieba.lcut(text)
print(words)
# ['传智教育', '是', '一家', '上市公司']
💡 代码解析:
jieba.lcut():返回列表形式- 精确模式:尽量保持词语完整
python
# ========== 全模式 ========
words_all = jieba.lcut(text, cut_all=True)
print(words_all)
# ['传智', '教育', '是', '一', '家', '上市', '公司']
# ========== 搜索引擎模式 ========
words_search = jieba.lcut_for_search(text)
print(words_search)
# ['传智', '教育', '上市', '公司', '传智教育', '上市公司']
📊 三种模式对比
原句: "传智教育是一家上市公司"
精确模式: ['传智教育', '是', '一家', '上市公司'] ← 推荐
全模式: ['传智', '教育', '是', '一', '家', '上市', '公司']
搜索引擎: ['传智', '教育', '上市', '公司', '传智教育', '上市公司']
1.2 词性标注
python
import jieba.posseg as pseg
text = "我爱自然语言处理"
result = pseg.lcut(text)
for word, flag in result:
print(f"{word}: {flag}")
# 输出:
# 我: r (代词)
# 爱: v (动词)
# 自然语言: n (名词)
# 处理: vn (动名词)
💡 代码解析:
jieba.posseg:分词+词性一次性搞定
1.3 自定义词典
python
# ====== 方式1: 代码中添加 ======
jieba.add_word("传智教育")
jieba.add_word("黑马程序员")
# ====== 方式2: 词典文件 ======
# my_dict.txt 内容:
# 传智教育 10 n
# 黑马程序员 8 n
jieba.load_userdict("my_dict.txt")
text = "传智教育旗下有黑马程序员"
print(jieba.lcut(text))
# ['传智教育', '旗下', '有', '黑马程序员']
💡 代码解析:
add_word():临时添加load_userdict():从文件批量加载
1.4 停用词过滤
python
# 常见停用词列表
stopwords = {
'的', '了', '是', '在', '我', '有', '和',
'就', '不', '人', '都', '一', '一个', '上', '也', '很'
}
text = "我是一名程序员,在北京工作"
words = jieba.lcut(text)
# 过滤停用词
filtered = [w for w in words if w not in stopwords and len(w) > 1]
print(filtered)
# ['程序员', '北京', '工作']
💻 二、文本向量化
2.1 One-Hot编码
python
from tensorflow.keras.preprocessing.text import Tokenizer
# 词表
vocabs = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"]
tokenizer = Tokenizer()
tokenizer.fit_on_texts(vocabs)
# 词表索引
print(tokenizer.word_index)
# {'苹果': 1, '香蕉': 2, '橙子': 3, '葡萄': 4, '西瓜': 5}
# 转One-Hot矩阵
one_hot_matrix = tokenizer.texts_to_matrix(vocabs, mode='binary')
print(one_hot_matrix)
# 输出:
# [[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.]]
💡 代码解析:
fit_on_texts():构建词表texts_to_matrix():转One-Hot
⚠️ One-Hot问题
稀疏: 10000个词 = 10000维向量,几乎全是0
无法计算相似度: 苹果(1,0,0...) 香蕉(0,1,0...) 距离=√2
2.2 Word2Vec
python
import fasttext
# ====== 训练词向量 ======
# 需要准备大量文本文件: corpus.txt
model = fasttext.train_unsupervised(
'corpus.txt',
model='cbow', # cbow或skipgram
dim=100, # 向量维度
epoch=5, # 训练轮数
lr=0.1, # 学习率
thread=10 # 线程数
)
# 保存模型
model.save_model('word2vec.bin')
# ====== 使用 ======
# 获取词向量
vec = model.get_word_vector('苹果')
print(vec.shape) # (100,)
# 找相似词
similar = model.get_nearest_neighbors('苹果')
print(similar)
# [(0.92, '香蕉'), (0.87, '水果'), (0.82, '橙子')]
💡 代码解析:
cbow:用上下文预测中间词skipgram:用中间词预测上下文dim:词向量维度
🍎 Word2Vec vs One-Hot
One-Hot: 苹果=[1,0,0,0] 香蕉=[0,1,0,0] → 距离=√2
Word2Vec: 苹果≈[0.8,0.2] 香蕉≈[0.75,0.25] → 距离≈0.07
结论:相似词向量距离更近!
2.3 词嵌入Embedding层
python
import torch
import torch.nn as nn
# 假设词表大小10000,嵌入成128维
embedding = nn.Embedding(num_embeddings=10000, embedding_dim=128)
# 输入: 词的索引 (batch内)
word_indices = torch.tensor([1, 5, 10, 99]) # 4个词
# 输出: 4个词的128维向量
vectors = embedding(word_indices)
print(vectors.shape) # (4, 128)
💡 代码解析:
num_embeddings:词表大小embedding_dim:向量维度
📊 三、文本数据分析
3.1 标签分布
python
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 读取数据
train_df = pd.read_csv('train.tsv', sep='\t')
# 统计
print(train_df['label'].value_counts())
# 画图
sns.countplot(x='label', data=train_df)
plt.title('Label Distribution')
plt.xlabel('Label')
plt.ylabel('Count')
plt.show()
3.2 句子长度分布
python
# 计算每句长度
train_df['length'] = train_df['text'].apply(lambda x: len(x))
# 统计
print(train_df['length'].describe())
# 按标签看
sns.boxplot(x='label', y='length', data=train_df)
plt.title('Text Length by Label')
plt.show()
3.3 词频统计
python
from collections import Counter
import jieba
# 统计词频
all_words = []
for text in train_df['text']:
words = jieba.lcut(text)
all_words.extend(words)
word_count = Counter(all_words)
# Top 30
print(word_count.most_common(30))
🧠 四、RNN系列
4.1 RNN实现
python
import torch
import torch.nn as nn
# 参数
input_size = 10 # 输入维度
hidden_size = 20 # 隐藏状态维度
num_layers = 2 # 层数
batch_size = 3 # batch大小
# 创建RNN
rnn = nn.RNN(
input_size=input_size,
hidden_size=hidden_size,
num_layers=num_layers,
batch_first=True
)
# 输入: (batch, seq_len, input_size)
x = torch.randn(batch_size, 5, input_size)
# 初始隐藏状态: (num_layers, batch, hidden_size)
h0 = torch.zeros(num_layers, batch_size, hidden_size)
# 前向传播
output, hn = rnn(x, h0)
print(f"output形状: {output.shape}") # (3, 5, 20)
print(f"hn形状: {hn.shape}") # (2, 3, 20)
💡 代码解析:
output:所有时间步的输出hn:最后一个隐藏状态
4.2 LSTM实现
python
# LSTM: 比RNN多一个细胞状态
lstm = nn.LSTM(
input_size=10,
hidden_size=20,
num_layers=2,
batch_first=True,
bidirectional=True # 双向
)
# 初始: 隐藏状态 + 细胞状态
h0 = torch.zeros(4, 3, 20) # num_layers*2 for bidirectional
c0 = torch.zeros(4, 3, 20)
output, (hn, cn) = lstm(x, (h0, c0))
print(f"output: {output.shape}") # (3, 5, 40) 双向=20*2
print(f"hn: {hn.shape}") # (4, 3, 20)
💡 代码解析:
- 三个门:遗忘门、输入门、输出门
- 解决RNN的长序列梯度消失问题
4.3 GRU实现
python
# GRU: LSTM的简化版,只有两个门
gru = nn.GRU(
input_size=10,
hidden_size=20,
num_layers=2,
batch_first=True,
bidirectional=True
)
output, hn = gru(x, h0)
print(f"output: {output.shape}") # (3, 5, 40)
💡 代码解析:
- 两个门:更新门、重置门
- 比LSTM参数量少
🎯 五、注意力机制
5.1 原理
🏀 比喻:
翻译 "I saw a saw" (我看到一把锯子)
→ 第一个saw(看到) vs 第二个saw(锯子)
→ 需要关注不同位置!
Attention = 告诉模型"看哪里"
5.2 实现
python
import torch
import torch.nn as nn
import torch.nn.functional as F
class Attention(nn.Module):
def __init__(self, hidden_size):
super().__init__()
self.attn = nn.Linear(hidden_size * 2, hidden_size)
self.v = nn.Parameter(torch.rand(hidden_size))
def forward(self, hidden, encoder_outputs):
# hidden: (batch, hidden_size)
# encoder_outputs: (seq_len, batch, hidden_size)
seq_len = encoder_outputs.size(0)
# 复制hidden到每个时间步
hidden = hidden.unsqueeze(0).repeat(seq_len, 1, 1)
# 计算能量
energy = torch.tanh(
self.attn(torch.cat([hidden, encoder_outputs], dim=2))
)
# (batch, hidden, seq_len)
energy = energy.permute(1, 2, 0)
# v是学习到的"查询向量"
v = self.v.repeat(encoder_outputs.size(1), 1).unsqueeze(1)
scores = torch.bmm(v, energy).squeeze(1)
# 归一化
attn_weights = F.softmax(scores, dim=1)
# 加权求和
context = torch.bmm(
attn_weights.unsqueeze(1),
encoder_outputs.permute(1, 0, 2)
).squeeze(1)
return context, attn_weights
💡 代码解析:
- 计算相似度 → softmax归一化 → 加权求和
🔥 六、Transformer
6.1 核心组件
python
import torch
import torch.nn as nn
import math
class SelfAttention(nn.Module):
def __init__(self, embed_size, heads):
super().__init__()
self.embed_size = embed_size
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):
N = query.shape[0]
# 分头
values = self.values(values).view(N, -1, self.heads, self.head_dim)
keys = self.keys(keys).view(N, -1, self.heads, self.head_dim)
queries = self.queries(query).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]).reshape(
N, -1, self.heads * self.head_dim
)
return self.fc_out(out)
💡 代码解析:
- 多头注意力:并行计算多个注意力
einsum:爱因斯坦求和约定,简洁高效
6.2 完整Transformer
python
class Transformer(nn.Module):
def __init__(self, src_vocab, trg_vocab, src_pad_idx, trg_pad_idx,
embed_size=256, num_layers=6, heads=8, forward_expansion=4,
dropout=0.1):
super().__init__()
self.src_pad_idx = src_pad_idx
self.trg_pad_idx = trg_pad_idx
self.src_embedding = nn.Embedding(src_vocab, embed_size)
self.trg_embedding = nn.Embedding(trg_vocab, embed_size)
self.transformer = nn.Transformer(
embed_size, heads, num_layers, forward_expansion, dropout
)
self.fc_out = nn.Linear(embed_size, trg_vocab)
self.dropout = nn.Dropout(dropout)
def forward(self, src, trg):
src_mask = (src != self.src_pad_idx).unsqueeze(1).unsqueeze(2)
trg_mask = torch.tril(torch.ones(trg.size(1), trg.size(1))).unsqueeze(0)
src_emb = self.dropout(self.src_embedding(src))
trg_emb = self.dropout(self.trg_embedding(trg))
out = self.transformer(
src_emb, trg_emb,
src_key_padding_mask=~src_mask,
tgt_mask=~trg_mask
)
return self.fc_out(out)
🚀 七、迁移学习
7.1 FastText分类
python
import fasttext
# 训练文本分类模型
# 格式: __label__positive 这是一个好产品
# __label__negative 这个很糟糕
model = fasttext.train_supervised(
'train.txt',
lr=0.1,
epoch=5,
word_ngrams=2,
dim=100,
loss='softmax'
)
# 预测
pred, prob = model.predict('这个产品太好了!')
print(pred) # __label__positive
print(prob) # 0.98
💡 代码解析:
- 训练数据格式:
__label__标签 文本word_ngrams:n-gram特征
7.2 Hugging Face BERT
python
from transformers import AutoModel, AutoTokenizer, AutoModelForSequenceClassification
import torch
# 加载预训练模型
tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
model = AutoModel.from_pretrained('bert-base-chinese')
# 分类模型
model = AutoModelForSequenceClassification.from_pretrained(
'bert-base-chinese',
num_labels=2
)
# 编码
inputs = tokenizer("你好世界", return_tensors="pt")
# 前向
outputs = model(**inputs)
logits = outputs.logits
print(logits)
💡 代码解析:
AutoTokenizer:自动选择分词器AutoModel:加载预训练模型
7.3 完整微调流程
python
from transformers import Trainer, TrainingArguments
from datasets import Dataset
# 准备数据
train_dataset = Dataset.from_pandas(train_df)
# 预处理
def tokenize_function(examples):
return tokenizer(
examples["text"],
padding="max_length",
truncation=True,
max_length=128
)
train_dataset = train_dataset.map(tokenize_function, batched=True)
# 训练参数
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=16,
learning_rate=2e-5,
weight_decay=0.01,
)
# 训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
)
trainer.train()
💡 代码解析:
TrainingArguments:训练超参数Trainer:高级训练封装
🐛 八、避坑指南
❌ 错误1: 维度顺序
python
# batch_first=True vs False
rnn1 = nn.RNN(10, 20, batch_first=True)
input1 = torch.randn(3, 5, 10) # (batch, seq, feature)
rnn2 = nn.RNN(10, 20, batch_first=False)
input2 = torch.randn(5, 3, 10) # (seq, batch, feature)
❌ 错误2: mask没设置
python
# Transformer的mask很重要
# 否则会看到PAD位置的信息
def make_src_mask(src, pad_idx):
return (src != pad_idx).unsqueeze(1).unsqueeze(2)
❌ 错误3: 学习率太大
BERT微调:学习率2e-5左右
从头训练:学习率1e-3左右
差100倍!用错直接不收敛
📝 九、总结
核心要点
| 模块 | 关键点 |
|---|---|
| 预处理 | jieba分词、停用词 |
| 向量化 | One-Hot→Word2Vec→Embedding |
| RNN | 记忆、梯度消失 |
| LSTM | 门控、解决长序列 |
| Attention | 加权求和、找重点 |
| Transformer | 自注意力、并行 |
| 迁移学习 | FastText、BERT |
代码对照表
| 功能 | 代码位置 |
|---|---|
| 分词 | 01_文本预处理/01_文本加载与特征提取 |
| 向量 | 01_文本预处理/02_文本向量化表示 |
| 分析 | 01_文本预处理/03_文本数据分析 |
| RNN | 02_RNN循环神经网络/01_RNN与LSTM与GRU |
| Attention | 02_RNN循环神经网络/02_注意力机制 |
| Transformer | 03_Transformer |
| 迁移学习 | 04_迁移学习 |
学完这篇,NLP实战就入门了! 🚀