Day05|NLP 文本分类入门:词向量 + RNN 从 0 训练情感分类
2026/6/25 16:21:18 网站建设 项目流程

苦猿的大模型日记 · 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

两个致命毛病:

  1. 稀疏到爆炸:5 万维向量只有一个 1,其余全 0,白白浪费内存
  2. 完全没语义:"好"和"棒"在 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_xW_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]) # 用最后一步隐状态做分类

几个关键工程直觉:

  1. padding_idx=0:告诉 Embedding,位置 0 是<pad>,它的向量永远保持全 0,不参与训练——免得模型把"占位符"也学成一个有意义的词
  2. h[-1]做分类:LSTM 输出的h[num_layers, batch, hidden],取[-1]就是最后一层的最终隐状态——它"读完了整句话",信息最全
  3. 替代方案: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

几个关键洞察:

  1. 顺序信息值钱:RNN/LSTM 比 MLP 高出 3-6 个百分点,纯靠"能读上下文"
  2. LSTM 比 RNN 稳:那些藏在长评论里"不过""但是"的转折信号,只有 LSTM 扛得住
  3. 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 学进简历

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询