苦猿的大模型日记 · Day05 · 词向量 + RNN 文本分类-帮普通人把AI学进简历系列
前言:学弟的一通电话
上周三晚上 11 点,我接到学弟的电话。
他第一句就是:"哥,MNIST 我跑通了,准确率 99.1%,老板让我下周一交个差评分类的 demo。"
我心想这有什么难的,Day04 的训练循环搬过去改改就是。
结果他下一句:"……但评论是文字,我不知道怎么把它喂给神经网络。"
我愣了两秒。
对啊。MNIST 那种图像,像素天然是 0-255 的数字,扔进模型就行。
可文字呢?"这部电影太好看了"——这 7 个字,是几个数?
CV 那套地基建好的管道,到了 NLP 这一步直接断。
电话里我跟他说了一个小时,讲明白:文本要进神经网络,中间隔着一座桥——词向量 + RNN。
挂完电话我就想,这个卡点不止他一个人有。
Day04 我们把 PyTorch 训练循环的模板钉死了,但那张模板只跑过图像。今天 Day05,把这张模板复用到文本上,顺便为后面的 LLM 铺最后一块地基。
读完你能:
- 搞懂文本如何变成数字(Token / 词表 / ID 序列)
- 理解 Embedding 层——从 one-hot 到稠密词向量的进化
- 掌握 RNN 结构,知道为啥要"循环"、隐状态怎么传
- 知道 LSTM 解决了 RNN 的什么毛病(三个门直觉)
- 把 Day04 训练循环模板复用到 IMDb 情感分类,跑出第一组数字
PART 01:文本如何变成数字
回到那个问题——"这部电影太好看了",是几个数?
答案是:先把它拆开。
第一步:分词(Token)
中文要先用工具切(比如 jieba),英文省事,按空格和标点切。
"this movie is awesome" → ["this", "movie", "is", "awesome"] "这部电影太好看了" → ["这部", "电影", "太", "好看", "了"]第二步:建词表(Vocab)
把训练集里所有的 token 扫一遍,统计出现频率,给每个 token 编个号。
vocab = {"<pad>": 0, "<unk>": 1, "this": 2, "movie": 3, "is": 4, "awesome": 5, ...}<pad>是占位符(等下讲为啥要它)<unk>是 unknown——词表里没收录的生词统一塞这
第三步:转 ID 序列
把每条评论的 token 流,换成 ID 流。
["this", "movie", "is", "awesome"] → [2, 3, 4, 5]到这里,人话已经被翻译成机器话了。
但还有个坑:长度不齐。
模型要批处理,一批的输入必须等长。可评论有长有短,咋办?
Padding:短的补 0,长的截断,统一到固定长度(比如 200)。
# 原始:[2, 3, 4, 5] # pad 到长度 8:[2, 3, 4, 5, 0, 0, 0, 0]这就是<pad>那个 0 的用处——告诉模型"这里是占位,不是真的词"。
文本不是数字,但任何 AI 任务的第一步,都是把人话翻译成机器话。
PART 02:Embedding 层——从 one-hot 到稠密向量
ID 序列有了,但直接把[2, 3, 4, 5]喂给神经网络,模型啥也学不到。
为啥?因为 ID 是个编号,编号之间没有语义关系。
"movie" 是 3,"awesome" 是 5,数字上 5 比 3 大,可这俩词的关系凭啥用大小来表达?
最早的笨办法:one-hot。
词表 5 万,每个词就用 5 万维向量表示,自己的位置是 1,其他全是 0。
"movie" = [0, 0, 1, 0, ..., 0] # 第 3 位是 1 "awesome" = [0, 0, 0, 0, 1, ..., 0] # 第 5 位是 1两个致命毛病:
- 稀疏到爆炸:5 万维向量只有一个 1,其余全 0,白白浪费内存
- 完全没语义:"好"和"棒"在 one-hot 里完全正交,模型看不出它俩意思接近
Embedding 的核心思想:换个表示方式。
不要 5 万维,要100 维稠密向量——每个词被映射到 100 维空间里的一个点。
关键魔法:让语义接近的词,在空间里也接近。
这就是 Word2Vec 那篇神论文干的事。最经典的一个例子:
向量(国王) - 向量(男人) + 向量(女人) ≈ 向量(王后)模型自己学出来:"国王"和"王后"在空间里靠得近,"好"和"棒"靠得近,"好"和"差"离得远。
PyTorch 里 Embedding 长这样:
self.embedding = nn.Embedding(num_embeddings=50000, embedding_dim=100) # 输入:[batch, seq_len] 的整数 ID # 输出:[batch, seq_len, 100] 的稠密向量本质就是查表——给个词 ID,返回它对应的 100 维向量。
embedding_dim选多大?小数据集 50-100,大数据集 300,LLM 里能到几千。
Embedding 是 LLM 的第一块砖。GPT-4 内部的第一个模块,就是它。
PART 03:RNN——为啥要"循环"
Embedding 解决了"词怎么表示",但还有个更要命的硬伤:顺序。
"我喜欢这部电影" 和 "这部电影我喜欢",词向量都一样,但语序不同。
MLP 和 CNN 都把它们当独立单元看——一句评论进去,把每个词向量拼一拼,前向一次出结果。
可语言是有顺序的。"我喜欢"后面接"这部电影",和后面接"这个垃圾",意思天差地别。
模型得一边读一边积累上下文。
RNN 就是为这事发明的。
RNN 的核心思想:带"记忆"地读
想象你读一本小说——每读一段,脑子里都在积累剧情。新的一段来了,你不是从零开始,而是基于上一段留下的记忆 + 当前段落,更新你的理解。
RNN 干的就是这个事。它有一个叫隐状态(hidden state)的东西,记作h。
每读一个词x_t,它就更新一次h:
h_t = tanh(W_x · x_t + W_h · h_{t-1} + b) ↑ ↑ ↑ 新记忆 当前词 上一时刻记忆关键就这一行——同一组参数W_x、W_h在每个时间步重复使用,这就是"循环"的来源。
PyTorch 调用一行:
self.rnn = nn.RNN(input_size=100, hidden_size=128, batch_first=True)读完一整句话,最后那个h就承载了整句话的信息——拿它去做分类就行。
RNN 的死穴:长期依赖崩溃
但 RNN 有个致命毛病:记不住远处的词。
经典反例:
我出生在法国……(中间隔了 50 个词的废话)……我能流利地说__。
读到"说"的时候,RNN 早把"法国"忘了。梯度消失 / 爆炸,长距离信号传不过去。
LSTM 救场:三个门
LSTM(Long Short-Term Memory)就是来修这个毛病的。它加了一条主线记忆c,配三个门控制信息流。
| 门 | 干啥的 | 类比 |
|---|---|---|
| 遗忘门 | 决定从主线记忆里扔掉啥 | 笔记本里划掉过期信息 |
| 输入门 | 决定把新信息写进主线记忆 | 往笔记本里记新内容 |
| 输出门 | 决定现在用哪部分输出 | 翻到对应页给当前问题用答案 |
PyTorch 接口跟 RNN 几乎一样,换一行就行:
self.rnn = nn.LSTM(input_size=100, hidden_size=128, batch_first=True)LSTM 比 RNN 稳——长评论里的关键信号(比如"不过""但是""然而"这种转折词)能被它记住,RNN 看一眼就忘。
RNN 把"顺序"引进了神经网络,LSTM 让这份记忆能扛过 50 个词的距离。
PART 04:PyTorch 定义 RNN 分类器(复用 Day04 训练循环)
理论讲完了,上代码。
模型骨架(关键片段)
import torch import torch.nn as nn class TextRNN(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.rnn = nn.LSTM(embed_dim, hidden_dim, batch_first=True) self.fc = nn.Linear(hidden_dim, num_classes) def forward(self, x): x = self.embedding(x) # [B, seq] → [B, seq, embed] out, (h, c) = self.rnn(x) # h: [1, B, hidden] return self.fc(h[-1]) # 用最后一步隐状态做分类几个关键工程直觉:
padding_idx=0:告诉 Embedding,位置 0 是<pad>,它的向量永远保持全 0,不参与训练——免得模型把"占位符"也学成一个有意义的词- 用
h[-1]做分类:LSTM 输出的h是[num_layers, batch, hidden],取[-1]就是最后一层的最终隐状态——它"读完了整句话",信息最全 - 替代方案:
out.mean(dim=1)做平均池化,比取最后一步更稳,实战常用
重头戏:复用 Day04 训练循环
下面这段,和 Day04 一字不差——
def train(model, loader, epochs=5, lr=1e-3): loss_fn = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=lr) model.to(device) for epoch in range(epochs): model.train() for x, y in loader: x, y = x.to(device), y.to(device) pred = model(x) loss = loss_fn(pred, y) optimizer.zero_grad() loss.backward() optimizer.step()唯一区别:Day04 的x是[B, 1, 28, 28]图像张量,Day05 的x是[B, seq_len]整数 ID。
损失函数还是CrossEntropyLoss,优化器还是 Adam,反向传播还是那三行。
这就是 Day04 的价值——训练循环是模板,会写一次就会写一百次。
PART 05:IMDb 实战结果 + 踩坑 + 下篇预告
IMDb 数据集简介
IMDb(Internet Movie Database)电影评论——5 万条训练 + 5 万条测试,标签就两类:正面 / 负面。
NLP 入门的标准"MNIST"。
英文按空格分词,词表控制在2.5 万-5 万,序列长度截到200-300,大部分评论够用。
三模型对比
我用三种架构跑了一遍 IMDb,结果:
| 模型 | 测试准确率 | 参数量 | 单 epoch(CPU) |
|---|---|---|---|
| MLP(平均池化) | ~82% | ~500k | ~60s |
| RNN | ~85% | ~340k | ~120s |
| LSTM | ~88% | ~380k | ~180s |
几个关键洞察:
- 顺序信息值钱:RNN/LSTM 比 MLP 高出 3-6 个百分点,纯靠"能读上下文"
- LSTM 比 RNN 稳:那些藏在长评论里"不过""但是"的转折信号,只有 LSTM 扛得住
- 88% 不是天花板:这个数在 NLP 圈属于"能写进简历的入门分数",但远不是终点——后面 Transformer 直接吊打到 92%+
简历写法示例(承接 Day04 的"简历级项目"叙事):
用 PyTorch 实现 LSTM 文本分类,IMDb 数据集准确率 ~88%
踩坑 5 个
坑 1:nn.Embedding输入忘了转 LongTensor
Embedding 只吃整数 ID,但你从 DataLoader 取出来的可能是 float。
x = x.long() # 一行救命坑 2:padding_idx=0没设,模型把<pad>学成了一个有意义的词
不设的话,所有那些 0 都被 Embedding 当真词学,模型开始对"长度"过度敏感。
坑 3:LSTM 输出取错维度
out, (h, c) = self.rnn(x) # ❌ h 形状 [num_layers, batch, hidden]——不能直接喂 Linear # ✅ h[-1] 形状 [batch, hidden]——对坑 4:序列太长,LSTM 训得巨慢
500 词以上的评论直接喂,LSTM 单 epoch 能跑 5 分钟。截到 200-300,准确率不掉,速度快 3 倍。
坑 5:测试漏model.eval()+torch.no_grad()
Day04 坑 1 的复现。这俩在 NLP 任务里一样致命,漏一个准确率掉 2 个点 / 显存爆。
写完这些坑我又意识到一遍:深度学习的门槛,从来不是数学,是工程——CV 是,NLP 也是。
结尾:训练循环没变,变的只是模型
Day04 跑 MNIST 的时候,我说过一句话:
学深度学习最大的错觉,是以为自己懂了;唯一的解药,是亲手训一个模型。
Day05 把这句话从图像搬到了文本。
从 MNIST 到 IMDb,模型在变(MLP/CNN → Embedding/RNN/LSTM),数据在变(像素 → 词向量),训练循环没变。
这就是工程能力——底层模板打牢,换领域只是换输入。
但 RNN/LSTM 也有它的天花板:长距离依赖还是没那么稳,训练慢,并行差。
下一篇 Day06,我们正式扔掉"循环"这个包袱,进Transformer / 自注意力——
让模型一次性看完整个句子,而不是一个词一个词读。
这是 LLM 的真正起点。
从 MNIST 到 IMDb,模型在变,训练循环没变;从 RNN 到 Transformer,结构在变,把人话变成向量这件事,从来不会变。
互动时间:你做 NLP 时最头疼的是数据预处理还是模型调参?评论区聊聊,我挑高频的写进 Day06。
— END —
苦猿 · 帮普通人把 AI 学进简历