在序列数据处理任务中,传统的全连接神经网络往往力不从心,因为它无法捕捉数据在时间维度上的依赖关系。无论是自然语言处理中的文本理解,还是金融领域的时间序列预测,都需要模型能够“记住”过去的信息。本文将带你从零开始,在 PyTorch 框架下,深入实战循环神经网络(RNN)及其重要变体——长短期记忆网络(LSTM)。我们将从核心概念讲起,逐步搭建一个完整的文本情感分类项目,涵盖数据预处理、模型构建、训练与评估全流程,并提供可复现的代码和常见问题排查指南。无论你是刚接触序列建模的新手,还是希望巩固 PyTorch 实战经验的开发者,都能从中获得清晰的指引。
1. 循环神经网络与长短期记忆网络核心概念
在深入代码之前,理解 RNN 和 LSTM 的基本原理至关重要。这能帮助我们在模型表现不佳时,知道从何处着手调优,而不是盲目调整超参数。
1.1 循环神经网络(RNN)为何而生
想象一下,你要预测一句话的下一个单词。如果模型只看到当前单词,而不知道前面说了什么,预测将非常困难。全连接网络(Dense Network)或卷积神经网络(CNN)处理的是独立同分布的数据点,它们没有“记忆”能力来处理这种前后关联的序列数据。
循环神经网络(RNN)的核心思想是引入“循环”结构,使网络能够保留之前时间步的信息。其关键是一个“隐藏状态”(Hidden State),它像一个记忆单元,随着序列的推进而不断更新。在每一个时间步t,RNN 单元会接收两个输入:当前时间步的输入x_t和上一个时间步的隐藏状态h_{t-1}。然后,它计算当前时间步的输出y_t和新的隐藏状态h_t,h_t将被传递到下一个时间步。
一个简化的 RNN 单元计算过程如下:
h_t = tanh(W_{hh} * h_{t-1} + W_{xh} * x_t + b_h) y_t = W_{hy} * h_t + b_y其中,W和b是可学习的权重和偏置。
1.2 RNN 的挑战:梯度消失与爆炸
虽然 RNN 的设计很巧妙,但在实践中,尤其是处理长序列时,它面临一个严峻挑战:梯度消失或梯度爆炸问题。
在反向传播过程中,梯度需要沿着时间步反向传播。当序列很长时,梯度需要连续乘以多个权重矩阵。如果这些权重矩阵的特征值小于1,梯度会指数级衰减到接近0(梯度消失),导致网络无法学习到长距离的依赖关系;如果特征值大于1,梯度则会指数级增长(梯度爆炸),导致训练不稳定。
1.3 长短期记忆网络(LSTM)的解决方案
为了克服 RNN 的长期依赖问题,长短期记忆网络(LSTM)被提出。LSTM 通过引入精妙的“门控”机制,有选择性地记住或忘记信息,从而有效地在长序列中传递信息。
LSTM 单元的核心是“细胞状态”(Cell State),它像一条传送带,贯穿整个序列,只有少量的线性交互,使得信息可以很容易地保持不变地流过。LSTM 通过三个门来控制这条传送带:
- 遗忘门(Forget Gate):决定从细胞状态中丢弃哪些信息。它查看
h_{t-1}和x_t,并输出一个介于0到1之间的数给细胞状态C_{t-1},1表示“完全保留”,0表示“完全丢弃”。 - 输入门(Input Gate):决定将哪些新信息存入细胞状态。它包含两部分:一个 Sigmoid 层决定更新哪些值,一个 tanh 层创建新的候选值向量
\tilde{C}_t。 - 输出门(Output Gate):基于更新后的细胞状态
C_t,决定输出什么隐藏状态h_t。首先,一个 Sigmoid 层决定细胞状态的哪些部分将输出,然后让细胞状态通过 tanh(将其值压到-1和1之间)并与 Sigmoid 门的输出相乘,得到最终的h_t。
这些门控机制使得 LSTM 能够学习到在长序列中,哪些信息是重要的需要长期保留,哪些信息是临时的可以忘记,从而极大地提升了处理长序列数据的能力。
2. 环境准备与 PyTorch 安装
工欲善其事,必先利其器。一个稳定、版本匹配的 PyTorch 环境是成功运行后续代码的前提。这里我们提供两种主流的安装方式。
2.1 使用 Conda 安装(推荐)
Conda 是一个强大的包和环境管理器,能很好地解决依赖冲突。如果你使用 Anaconda 或 Miniconda,安装 PyTorch 非常方便。
创建并激活虚拟环境(强烈推荐,避免污染系统环境):
# 创建一个名为 pytorch_rnn 的 Python 3.9 环境 conda create -n pytorch_rnn python=3.9 # 激活环境 conda activate pytorch_rnn访问 PyTorch 官网获取安装命令: 前往 PyTorch 官网 ,根据你的操作系统、包管理器(Conda/Pip)、Python版本以及是否需要 CUDA(GPU加速)来选择合适的配置。官网会生成对应的安装命令。
- 仅 CPU 版本:适合没有 NVIDIA 显卡或初学者。
- CUDA 版本:如果你有 NVIDIA 显卡并已安装对应版本的 CUDA 驱动和工具包,可以选择此版本以加速训练。
执行安装命令: 例如,在2024年,一个常见的 Conda 安装命令(CUDA 11.8)可能如下:
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia对于仅 CPU 版本:
conda install pytorch torchvision torchaudio cpuonly -c pytorch
2.2 使用 Pip 安装
如果你不使用 Conda,或者希望在已有的 Python 环境中安装,可以使用 pip。
确保 pip 已更新:
pip install --upgrade pip安装 PyTorch: 同样,建议从官网获取准确的 pip 命令。例如:
# CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 仅 CPU pip install torch torchvision torchaudio
2.3 验证安装
安装完成后,在 Python 交互环境中运行以下代码进行验证:
import torch print(f"PyTorch 版本: {torch.__version__}") print(f"CUDA 是否可用: {torch.cuda.is_available()}") if torch.cuda.is_available(): print(f"CUDA 版本: {torch.version.cuda}") print(f"GPU 设备名称: {torch.cuda.get_device_name(0)}")如果输出显示了正确的版本号,并且 CUDA 可用性符合你的预期,说明安装成功。
3. PyTorch 中 RNN 与 LSTM 的核心 API 详解
PyTorch 在torch.nn模块中提供了高度封装的 RNN、LSTM 和 GRU 层,让我们无需从零实现复杂的循环单元。
3.1torch.nn.RNN基础用法
nn.RNN是最基础的循环网络层。
import torch import torch.nn as nn # 定义参数 input_size = 10 # 输入特征的维度(例如,词向量的维度) hidden_size = 20 # 隐藏状态的维度 num_layers = 2 # RNN的层数(堆叠的层数) batch_first = True # 如果为True,则输入/输出张量的形状为 (batch, seq_len, feature) # 创建RNN层 rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True, nonlinearity='tanh') # 默认是tanh,也可以是relu # 准备输入数据 batch_size = 3 seq_len = 5 # 输入形状: (batch_size, seq_len, input_size) x = torch.randn(batch_size, seq_len, input_size) # 初始隐藏状态形状: (num_layers, batch_size, hidden_size) h0 = torch.zeros(num_layers, batch_size, hidden_size) # 前向传播 output, hn = rnn(x, h0) print(f"输出形状: {output.shape}") # (batch_size, seq_len, hidden_size) print(f"最终隐藏状态形状: {hn.shape}") # (num_layers, batch_size, hidden_size)关键参数解释:
input_size: 输入x中每个时间步特征的维度。hidden_size: 隐藏状态h的维度,决定了RNN单元的记忆容量。num_layers: 堆叠的RNN层数。多层RNN可以学习更复杂的模式,但也会增加计算量和过拟合风险。batch_first: 一个非常实用的参数。PyTorch默认的RNN输入形状是(seq_len, batch, feature)。设置为True后,输入输出形状变为(batch, seq_len, feature),更符合我们通常组织数据的习惯。output: 包含每个时间步最后一层RNN的隐藏状态。形状为(batch, seq_len, hidden_size)或(seq_len, batch, hidden_size)。hn: 最后一个时间步所有层的隐藏状态。形状为(num_layers, batch, hidden_size)。
3.2torch.nn.LSTM深入解析
LSTM 的 API 与 RNN 非常相似,但因为它有细胞状态,所以返回两个状态。
# 定义LSTM层 lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2, batch_first=True, dropout=0.3) # 初始隐藏状态和细胞状态 # h0 形状: (num_layers, batch_size, hidden_size) # c0 形状: (num_layers, batch_size, hidden_size) h0 = torch.zeros(2, 3, 20) c0 = torch.zeros(2, 3, 20) # 前向传播 # LSTM返回:输出, (最终隐藏状态, 最终细胞状态) output, (hn, cn) = lstm(x, (h0, c0)) print(f"LSTM输出形状: {output.shape}") # (3, 5, 20) print(f"最终隐藏状态形状: {hn.shape}") # (2, 3, 20) print(f"最终细胞状态形状: {cn.shape}") # (2, 3, 20)新增关键参数:
dropout: 如果num_layers > 1,则在除最后一层外的每个RNN层输出后引入Dropout层,用于防止过拟合。注意:最后一层不加dropout。
3.3 处理变长序列:torch.nn.utils.rnn.pack_padded_sequence
在实际任务中,如处理一批句子,每个句子的长度(序列长度)可能不同。为了高效计算,我们通常会将它们填充(Padding)到相同长度,但希望RNN在处理时忽略这些填充的部分。pack_padded_sequence和pad_packed_sequence就是为此而生。
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence import torch # 假设我们有3个句子,长度分别为5, 3, 4 sequences = [torch.randn(5, 10), torch.randn(3, 10), torch.randn(4, 10)] # 按长度降序排序(pack_padded_sequence的要求) lengths = [5, 4, 3] sequences_sorted = sorted(zip(sequences, lengths), key=lambda x: x[1], reverse=True) sequences_sorted, lengths_sorted = zip(*sequences_sorted) # 填充并创建批次 padded_sequences = torch.nn.utils.rnn.pad_sequence(sequences_sorted, batch_first=True) # shape: (3, 5, 10) # 打包 packed_input = pack_padded_sequence(padded_sequences, lengths=lengths_sorted, batch_first=True, enforce_sorted=True) # 通过RNN lstm = nn.LSTM(input_size=10, hidden_size=20, batch_first=True) packed_output, (hn, cn) = lstm(packed_input) # 解包 output, output_lengths = pad_packed_sequence(packed_output, batch_first=True) print(f"解包后输出形状: {output.shape}") # (3, 5, 20) ,但最后的时间步是填充的 print(f"实际输出长度: {output_lengths}") # tensor([5, 4, 3])流程:填充 -> 排序 -> 打包 -> 送入RNN -> 解包。这样做可以确保RNN只对有效长度进行计算,提升效率并避免填充值影响隐藏状态。
4. 实战项目:基于LSTM的文本情感分类
现在,我们将运用所学知识,构建一个完整的文本情感分类模型。我们将使用 IMDB 电影评论数据集,目标是判断一条评论是正面(positive)还是负面(negative)。
4.1 数据准备与预处理
我们使用torchtext库来方便地加载和处理文本数据。首先安装必要的库:
pip install torchtextimport torch import torch.nn as nn import torch.optim as optim from torchtext.data import get_tokenizer from torchtext.vocab import build_vocab_from_iterator from torchtext.datasets import IMDB from torch.utils.data import DataLoader, Dataset from collections import Counter import random # 1. 定义分词器 tokenizer = get_tokenizer('basic_english') # 基础英文分词器 # 2. 加载IMDB数据集(训练集和测试集) train_iter, test_iter = IMDB(split=('train', 'test')) # 3. 构建词汇表(Vocabulary) def yield_tokens(data_iter): for label, line in data_iter: yield tokenizer(line) # 构建词汇表,设置最小词频,并添加特殊符号 vocab = build_vocab_from_iterator(yield_tokens(train_iter), specials=['<unk>', '<pad>', '<bos>', '<eos>'], min_freq=5) vocab.set_default_index(vocab['<unk>']) # 设置默认索引,用于未知词 print(f"词汇表大小: {len(vocab)}") # 4. 文本转数字索引的函数 text_pipeline = lambda x: [vocab['<bos>']] + [vocab[token] for token in tokenizer(x)] + [vocab['<eos>']] label_pipeline = lambda x: 1 if x == 'pos' else 0 # 5. 创建Dataset和DataLoader class IMDBDataset(Dataset): def __init__(self, data_iter, vocab, tokenizer, max_len=200): self.data = [] for label, text in data_iter: # 将文本转换为索引序列,并截断或填充到固定长度max_len token_ids = text_pipeline(text)[:max_len] if len(token_ids) < max_len: token_ids = token_ids + [vocab['<pad>']] * (max_len - len(token_ids)) self.data.append((torch.tensor(token_ids, dtype=torch.long), label_pipeline(label))) def __len__(self): return len(self.data) def __getitem__(self, idx): return self.data[idx] # (token_indices, label) # 创建数据集 max_length = 200 train_dataset = IMDBDataset(train_iter, vocab, tokenizer, max_len=max_length) test_dataset = IMDBDataset(test_iter, vocab, tokenizer, max_len=max_length) # 创建DataLoader batch_size = 64 train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, drop_last=True) test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)4.2 构建LSTM分类模型
我们的模型将包含以下几个部分:
- 嵌入层(Embedding Layer):将单词索引映射为密集向量。
- LSTM层:处理序列,捕捉上下文信息。
- 全连接分类层:将LSTM的最终输出转换为二分类结果。
class LSTMSentimentClassifier(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers, output_dim, dropout_rate): super().__init__() # 嵌入层 self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=vocab['<pad>']) # LSTM层 self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers=num_layers, batch_first=True, dropout=dropout_rate if num_layers > 1 else 0, bidirectional=False) # 可以先使用单向LSTM # Dropout层 self.dropout = nn.Dropout(dropout_rate) # 全连接输出层 self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, text): # text shape: [batch_size, seq_len] embedded = self.embedding(text) # [batch_size, seq_len, embed_dim] # 通过LSTM # output: [batch_size, seq_len, hidden_dim] # (hidden, cell): ([num_layers, batch_size, hidden_dim], ...) output, (hidden, cell) = self.lstm(embedded) # 我们取最后一个时间步的隐藏状态作为整个序列的表示 # hidden shape: [num_layers, batch_size, hidden_dim] # 对于单向LSTM,取最后一层的隐藏状态 hidden[-1] last_hidden = hidden[-1] # [batch_size, hidden_dim] # 应用Dropout和全连接层 dropped = self.dropout(last_hidden) out = self.fc(dropped) # [batch_size, output_dim] return out # 初始化模型 vocab_size = len(vocab) embed_dim = 100 hidden_dim = 256 num_layers = 2 output_dim = 2 # 正面和负面 dropout_rate = 0.5 model = LSTMSentimentClassifier(vocab_size, embed_dim, hidden_dim, num_layers, output_dim, dropout_rate) print(model)4.3 模型训练与评估
定义训练循环和评估函数。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) def train_epoch(model, data_loader, criterion, optimizer, device): model.train() total_loss = 0 correct_predictions = 0 total_samples = 0 for batch_idx, (data, target) in enumerate(data_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) loss = criterion(output, target) loss.backward() # 梯度裁剪,防止梯度爆炸(对RNN/LSTM尤其重要) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() total_loss += loss.item() _, predicted = torch.max(output, dim=1) correct_predictions += (predicted == target).sum().item() total_samples += target.size(0) avg_loss = total_loss / len(data_loader) accuracy = correct_predictions / total_samples return avg_loss, accuracy def evaluate(model, data_loader, criterion, device): model.eval() total_loss = 0 correct_predictions = 0 total_samples = 0 with torch.no_grad(): for data, target in data_loader: data, target = data.to(device), target.to(device) output = model(data) loss = criterion(output, target) total_loss += loss.item() _, predicted = torch.max(output, dim=1) correct_predictions += (predicted == target).sum().item() total_samples += target.size(0) avg_loss = total_loss / len(data_loader) accuracy = correct_predictions / total_samples return avg_loss, accuracy # 开始训练 num_epochs = 10 for epoch in range(1, num_epochs + 1): train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device) test_loss, test_acc = evaluate(model, test_loader, criterion, device) print(f'Epoch: {epoch:02d}') print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%') print(f'\t Test Loss: {test_loss:.3f} | Test Acc: {test_acc*100:.2f}%')4.4 模型推理与预测
训练完成后,我们可以用模型对新的评论进行预测。
def predict_sentiment(model, sentence, vocab, tokenizer, device, max_len=200): model.eval() # 处理输入句子 tokenized = [vocab['<bos>']] + [vocab[token] for token in tokenizer(sentence)] + [vocab['<eos>']] if len(tokenized) < max_len: tokenized = tokenized + [vocab['<pad>']] * (max_len - len(tokenized)) else: tokenized = tokenized[:max_len] tokenized = torch.tensor(tokenized, dtype=torch.long).unsqueeze(0).to(device) # shape: [1, max_len] with torch.no_grad(): output = model(tokenized) probabilities = torch.softmax(output, dim=1) predicted_class = torch.argmax(probabilities, dim=1).item() sentiment = '正面' if predicted_class == 1 else '负面' confidence = probabilities[0][predicted_class].item() return sentiment, confidence # 测试几个例子 test_sentences = [ "This movie is fantastic! The acting is superb and the plot is thrilling.", "A waste of time. Boring from start to finish with terrible dialogue.", "It was okay, not great but not terrible either." ] for sent in test_sentences: sentiment, confidence = predict_sentiment(model, sent, vocab, tokenizer, device) print(f'评论: "{sent[:50]}..."') print(f'预测情感: {sentiment} (置信度: {confidence:.2%})') print('-' * 60)5. 常见问题与排查思路
在实战中,你可能会遇到以下问题。这里提供一些排查思路。
| 问题现象 | 可能原因 | 排查与解决方案 |
|---|---|---|
| 训练损失为 NaN | 1. 学习率过高。 2. 梯度爆炸。 3. 数据中存在异常值(如 NaN)。 | 1. 降低学习率(如从 1e-3 降到 1e-4)。 2. 使用梯度裁剪( torch.nn.utils.clip_grad_norm_)。3. 检查数据预处理,确保输入中没有无效值。 |
| 模型不收敛(损失居高不下) | 1. 学习率过低。 2. 模型架构过于简单或复杂。 3. 数据标签错误或噪声太大。 4. 权重初始化问题。 | 1. 尝试增大学习率或使用学习率调度器。 2. 调整隐藏层维度、层数。 3. 检查数据集质量。 4. 尝试不同的权重初始化方法。 |
| 过拟合(训练精度高,测试精度低) | 1. 模型复杂度过高。 2. 训练数据不足。 3. 缺乏正则化。 | 1. 增加 Dropout 比率。 2. 使用 L2 权重衰减。 3. 获取更多训练数据或使用数据增强。 4. 简化模型(减少层数或隐藏单元)。 |
| GPU 内存溢出(CUDA out of memory) | 1. 批次大小(Batch Size)太大。 2. 序列长度或模型参数量过大。 3. 未及时释放缓存。 | 1.减小 Batch Size是最直接有效的方法。 2. 对长序列进行截断或使用动态批处理。 3. 在验证/测试时使用 torch.no_grad()和model.eval()。4. 使用 torch.cuda.empty_cache()清理缓存。 |
| 运行速度慢 | 1. 在 CPU 上运行大模型。 2. 未使用 pack_padded_sequence处理变长序列。3. 数据加载是瓶颈。 | 1. 检查是否在 GPU 上运行(model.to(device))。2. 对文本数据使用打包序列,避免对填充部分计算。 3. 增加 DataLoader的num_workers参数,使用 PIN 内存。 |
| 预测结果全部一样 | 1. 模型权重未正确更新(梯度消失)。 2. 最后一层激活函数使用不当(如二分类用了 Softmax?应用 Sigmoid)。 3. 类别极度不平衡。 | 1. 检查梯度值,尝试使用 LSTM/GRU 代替朴素 RNN,或减少层数。 2. 二分类任务,输出层用 nn.Linear接nn.BCEWithLogitsLoss,或nn.Linear接nn.CrossEntropyLoss(输出维度为2)。确保匹配正确。3. 检查损失函数和标签格式。 |
6. 进阶优化与最佳实践
掌握了基础模型后,可以通过以下方法进一步提升性能和效果。
6.1 使用预训练词向量
我们之前随机初始化了嵌入层。使用在大规模语料上预训练的词向量(如 GloVe, FastText)可以显著提升模型性能,尤其是在训练数据有限的情况下。
from torchtext.vocab import GloVe # 加载预训练的GloVe词向量 glove = GloVe(name='6B', dim=100) # 使用100维的GloVe向量 # 用预训练向量初始化嵌入层权重 pretrained_embeddings = glove.get_vecs_by_tokens(vocab.get_itos()) model.embedding.weight.data.copy_(pretrained_embeddings) # 固定嵌入层权重(可选,微调时可能不固定) # model.embedding.weight.requires_grad = False6.2 双向LSTM(BiLSTM)
单向LSTM只利用了上文信息。双向LSTM通过两个独立的LSTM层分别处理前向和后向序列,并将它们的隐藏状态拼接,从而同时捕捉上下文信息,在情感分析等任务中通常效果更好。
class BiLSTMSentimentClassifier(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers, output_dim, dropout_rate): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=vocab['<pad>']) # 关键:bidirectional=True self.lstm = nn.LSTM(embed_dim, hidden_dim, num_layers=num_layers, batch_first=True, dropout=dropout_rate if num_layers > 1 else 0, bidirectional=True) # 因为是双向,LSTM最终输出的特征维度是 hidden_dim * 2 self.dropout = nn.Dropout(dropout_rate) self.fc = nn.Linear(hidden_dim * 2, output_dim) def forward(self, text): embedded = self.embedding(text) output, (hidden, cell) = self.lstm(embedded) # 对于双向LSTM,hidden的形状是 [num_layers * 2, batch, hidden_dim] # 我们取最后两个方向在最后一层的隐藏状态,然后拼接 # hidden[-2, :, :] 是最后一层的前向最终状态 # hidden[-1, :, :] 是最后一层的后向最终状态 last_hidden_forward = hidden[-2, :, :] last_hidden_backward = hidden[-1, :, :] last_hidden = torch.cat((last_hidden_forward, last_hidden_backward), dim=1) out = self.fc(self.dropout(last_hidden)) return out6.3 多层LSTM与Dropout
增加LSTM层数可以增强模型的表示能力,但也会增加过拟合风险和训练难度。在多层LSTM之间或之后使用Dropout是有效的正则化手段。注意:nn.LSTM的dropout参数仅在num_layers > 1时在层与层之间生效。
6.4 超参数调优策略
- 网格搜索/随机搜索:对学习率、隐藏层维度、Dropout率、层数等进行系统性的搜索。
- 学习率调度:使用
torch.optim.lr_scheduler中的ReduceLROnPlateau或StepLR,在训练停滞时动态降低学习率。 - 早停法(Early Stopping):监控验证集损失,当其在连续多个epoch不再下降时停止训练,防止过拟合。
- 梯度裁剪:对LSTM/RNN,设置
max_norm=1.0或5.0的梯度裁剪是标准操作。
6.5 工程化部署考虑
- 模型保存与加载:使用
torch.save和torch.load。# 保存 torch.save({ 'model_state_dict': model.state_dict(), 'vocab': vocab, 'model_params': {'vocab_size':len(vocab), 'embed_dim':100, ...} }, 'sentiment_model.pt') # 加载 checkpoint = torch.load('sentiment_model.pt', map_location=device) model.load_state_dict(checkpoint['model_state_dict']) - TorchScript 导出:如果需要脱离Python环境部署(如C++服务),可以使用
torch.jit.script或torch.jit.trace将模型转换为TorchScript。 - ONNX 导出:如果需要与其他框架交互,可以导出为ONNX格式。
通过本教程,你不仅学会了如何在 PyTorch 中实现 RNN 和 LSTM,还完成了一个端到端的文本情感分类项目。从理论理解、环境搭建、API学习到实战开发、问题排查和优化建议,形成了一个完整的学习闭环。理解门控机制的原理能帮助你在模型表现不佳时进行有效诊断,而掌握pack_padded_sequence、梯度裁剪等技巧则是工程实践中避开深坑的关键。下一步,你可以尝试在更复杂的数据集(如机器翻译、文本生成)上应用这些知识,或者探索更先进的序列模型,如 Transformer,这将是你深入自然语言处理领域的坚实起点。