情感分析项目代码详解三textrnn.py 模型搭建前两篇完成了数据准备词汇表建好了文本也变成了定长的数字序列还写好了分批读取的迭代器。这一篇开始搭建真正的大脑——情感分类模型。textrnn.py 用经典的 RNN循环神经网络家族成员 LSTM 来做文本分类代码只有二十几行但每一行都值得细看。一、导入库import torch import torch.nn as nntorch 是 PyTorch 主库nn 是它的神经网络模块里面封装好了各种常用层嵌入层、LSTM、全连接层等。我们只需要像搭积木一样把它们拼起来。二、定义模型类class Model(nn.Module): def __init__(self, embedding_pretained, n_vocab, embed, num_classes): super(Model, self).__init__()在 PyTorch 里所有自定义模型都要继承 nn.Module 这个基类这样它才具备可训练、可保存、可迁移等能力。__init__ 接收四个参数embedding_pretained预训练的字向量没有就传 None、n_vocab词汇表大小、embed每个字的向量维度、num_classes分类数本项目是 4 种情绪。super().__init__() 先调用父类的初始化方法这是继承的标准写法必须放在第一行。三、嵌入层 Embeddingif embedding_pretained is not None: self.embedding nn.Embedding.from_pretrained(embedding_pretained, padding_idxn_vocab - 1, freezeFalse) else: self.embedding nn.Embedding(n_vocab, embed, padding_idxn_vocab - 1)嵌入层的作用把字编号变成向量。编号本身只是个整数没有任何含义Embedding 相当于一张大查找表把每个编号映射成一个稠密向量让意思相近的字向量也相近。if 分支如果提供了预训练向量比如别人在大语料上训练好的就用 from_pretrained 直接加载freezeFalse 表示不冻结训练过程中还可以继续微调这些向量。else 分支没有预训练向量就新建一个 Embedding 层随机初始化边训练边学。padding_idxn_vocab-1第二篇里 PAD 的编号是最后一个n_vocab-1。指定它之后填充位置的向量永远是 0梯度也不会更新它——模型不会把填充当成有效信息。四、LSTM 层self.lstm nn.LSTM(embed, 128, 3, bidirectionalTrue, batch_firstTrue, dropout0.3)参数逐个看embed 是输入维度字的向量长度128 是隐藏层大小可以理解为 LSTM 的记忆容量3 是层数堆叠 3 层 LSTM越深能学到越抽象的特征。bidirectionalTrue双向 LSTM一条从句首往句尾读一条从句尾往句首读。比如单独一个不字光看前半句不知道它后面跟什么双向就能兼顾前后文。batch_firstTrue约定输入格式为 [批次, 长度, 维度]和第二篇迭代器输出的形状对得上省去转置的麻烦。dropout0.3训练时随机丢弃30% 的神经元防止模型死记硬背训练集过拟合。五、全连接层self.fc nn.Linear(128 * 2, num_classes)LSTM 是双向的正向 128 维 反向 128 维拼在一起是 256 维所以输入维度写成 128*2。输出 num_classes 个分数对应 4 种情绪哪个分数最高就预测哪一类。六、forward 前向传播上def forward(self, x): x, seq_len x emb self.embedding(x) # [B, L, E]forward 定义了数据在模型里怎么流动调用 model(x) 时会自动执行它。第二篇的迭代器返回 ((x, seq_len), y)所以第一行先把元组拆开x 是字编号矩阵seq_len 是每条样本的真实长度。注释里的 B、L、E 分别代表批次大小、序列长度70、字向量维度。嵌入后每个字都变成一条 E 维向量。七、forward 前向传播中out, _ self.lstm(emb) # [B, L, 2H]把向量送进 LSTMout 是每一个时刻每个字位置的输出形状 [B, 70, 256]。LSTM 还会返回最后一刻的隐藏状态和记忆单元这里用下划线 _ 接收后直接丢掉——因为我们接下来要用 seq_len 精确取位置用不上它。八、取真实最后一步idx (seq_len - 1).clamp(min0) # [B] last out[torch.arange(out.size(0), deviceout.device), idx] # [B, 2H]关键点短句子后面补了 PAD如果直接用最后一位取到的是一堆填充向量等于白看。所以要根据 seq_len 找到每个样本真实的最后一个字的位置。seq_len - 1 是最后一个字的索引索引从 0 开始clamp(min0) 是保险防止空句出现 -1。torch.arange(out.size(0)) 生成 [0,1,2,...] 批次序号配合 idx 做花式索引对第 i 条样本取它第 idx[i] 个位置的输出。一句话从每条序列里抠出真实最后一个字的 LSTM 编码。九、分类输出return self.fc(last)把 256 维的特征送进全连接层得到 4 个类别的分数并返回。下一篇的训练代码会拿这个分数和真实标签计算损失。小结textrnn.py 虽然短但完整走通了编号→向量→LSTM 编码→取真实最后状态→分类的链路。模型建好了下一篇就来看怎么训练它、评估它、测试它。
阅读完成 · 觉得有帮助?