PyTorch入门指南:从环境搭建到自动求导的NLP学习实战
1. 项目概述:为什么从Pytorch开始我的NLP学习之旅
如果你和我一样,对自然语言处理(NLP)充满好奇,想亲手搭建一个能理解文本、生成对话甚至写诗的模型,那么你大概率会和我走上同一条路:从选择一个趁手的深度学习框架开始。在TensorFlow、JAX、MXNet等一众框架中,我最终选择了Pytorch作为我的主力工具。这并不是一个随意的决定,而是基于它“动态计算图”的核心设计哲学,这种设计让代码的编写和调试过程,尤其是对于NLP这种数据结构灵活多变的领域,变得异常直观和友好。想象一下,你正在搭建一个处理变长文本序列的模型,每一步操作都像在用Python原生语法写一个流程图,可以随时打印中间变量的值,随时中断查看,这种“所见即所得”的编程体验,对于初学者深入理解模型运作的“黑箱”至关重要。
这个系列笔记,就是我基于Pytorch框架,系统学习NLP核心技术的实战记录。它不仅仅是一份安装指南或API手册,更是一个从零开始,一步步构建NLP认知体系和实操能力的路线图。无论你是刚入门的学生,还是希望从其他框架转向Pytorch的开发者,我相信这份融合了基础概念、核心原理、避坑经验和项目实战的笔记,都能为你提供一条清晰、可复现的学习路径。我们将从最基础的环境搭建和Tensor操作开始,逐步深入到词嵌入、循环神经网络、注意力机制乃至Transformer等现代NLP的基石。我会把学习过程中每一个让我“恍然大悟”的关键点,以及那些官方文档里不会写的“坑”,都毫无保留地记录下来。
2. 环境搭建:避开新手的第一道坎
万事开头难,而深度学习的环境配置堪称“开头难”中的经典难题。网上教程五花八门,但往往因为系统版本、硬件配置的细微差别而失效。我的原则是:在个人电脑上,优先使用CPU版本进行学习和验证;在有稳定GPU资源后,再系统性地配置CUDA环境。这能让你快速跳过复杂的驱动和库兼容性问题,把精力集中在学习Pytorch本身和NLP概念上。
2.1 Anaconda:不可或缺的Python环境管理器
我强烈建议所有初学者都从Anaconda开始。它不仅仅是一个Python发行版,更是一个强大的环境管理工具。NLP项目常常依赖不同版本、甚至相互冲突的库(比如某个古老的文本处理库只支持Python 3.6,而新的Pytorch需要Python 3.8+)。Anaconda可以让你为每个项目创建独立的、纯净的虚拟环境,互不干扰。
安装Anaconda后,打开你的终端(Windows是Anaconda Prompt或PowerShell,macOS/Linux是Terminal),第一件事就是为我们的NLP学习创建一个专属环境:
conda create -n nlp_study python=3.9这里,-n nlp_study指定了环境名称,python=3.9指定了Python版本。选择Python 3.9是因为它在稳定性和对新库的兼容性上取得了很好的平衡。创建完成后,激活这个环境:
conda activate nlp_study你会看到命令行提示符前面变成了(nlp_study),这表示你已经进入了这个独立的沙箱,接下来所有的操作都只影响这个环境。
注意:很多教程会教你用
conda install pytorch直接安装。但根据我的经验,conda源中的Pytorch版本有时更新不及时,或者默认安装的是不包含CUDA的版本,容易造成混淆。我更倾向于使用Pytorch官网提供的安装命令,它更精确、更可控。
2.2 安装Pytorch CPU版本:最稳妥的起步
对于绝大多数入门学习和模型原型验证,CPU版本完全够用。它避免了显卡驱动、CUDA版本、cuDNN版本这一系列令人头疼的兼容性问题。访问 Pytorch官网 ,你会看到一个交互式的安装命令生成器。
在安装时,请严格按照以下选择来获取CPU版本:
- PyTorch Build: Stable (稳定版)
- Your OS: 你的操作系统(Windows, Linux, Mac)
- Package: Conda (因为我们用Anaconda管理环境)
- Language: Python
- Compute Platform: CPU
以Windows系统为例,官网生成的命令可能类似于:
conda install pytorch torchvision torchaudio cpuonly -c pytorch在已经激活的nlp_study环境中执行这条命令。-c pytorch指定从Pytorch官方的conda频道安装,确保版本最新最正。cpuonly这个包是一个元包,它会自动确保安装的是不依赖CUDA的Pytorch版本及其相关CPU计算库。
安装完成后,验证一下。在Python交互环境中(命令行输入python)执行:
import torch print(torch.__version__) # 输出Pytorch版本,如 2.1.0 print(torch.cuda.is_available()) # 输出 False, 确认是CPU版本 x = torch.rand(5, 3) print(x)如果顺利输出版本号、一个5x3的随机矩阵,并且cuda.is_available()是False,那么恭喜你,最基础、最稳定的Pytorch学习环境已经搭建成功。
2.3 关于GPU版本:何时以及如何升级
当你开始训练稍复杂的模型(如LSTM、Transformer),或者需要处理大规模数据集时,GPU的并行计算能力将带来几十甚至上百倍的加速。这时就需要安装GPU版本的Pytorch。
关键前提:你的电脑必须有一块NVIDIA显卡,并且安装了正确版本的显卡驱动和CUDA工具包。这是一个比安装CPU版本复杂得多的过程,充满了版本依赖的“坑”。
- 确定你的CUDA版本:在命令行输入
nvidia-smi,查看右上角显示的CUDA Version。例如,显示“CUDA Version: 12.1”,这指的是驱动支持的最高CUDA版本,不代表你已经安装了CUDA 12.1。 - 安装对应版本的CUDA工具包:你需要从NVIDIA官网下载并安装一个小于等于驱动支持版本的CUDA Toolkit。例如,驱动支持12.1,你可以安装CUDA 11.8或12.1。我通常选择比最高版本低一个的稳定版本(如11.8),因为生态兼容性更好。
- 使用匹配的Pytorch安装命令:再次回到Pytorch官网安装命令生成器。这次在“Compute Platform”中选择与你安装的CUDA版本匹配的选项,比如“CUDA 11.8”。它会生成新的命令,例如:
执行此命令,它会安装与CUDA 11.8兼容的Pytorch GPU版本。conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
实操心得:不要盲目追求最新版本的CUDA和Pytorch。新版本可能遇到库不兼容、教程资料少的问题。选择一个经过社区充分验证的“稳定组合”,如Pytorch 2.0 + CUDA 11.8,能避开很多不必要的麻烦。对于RTX 40系(如4060)或50系显卡,通常需要CUDA 11.8或更高版本,具体需查阅Pytorch官网的兼容性表格。
3. Pytorch核心基石:Tensor操作全解析
Pytorch的核心数据结构是Tensor(张量),你可以把它理解为Numpy中ndarray的GPU加速版,并且自带自动求导(Autograd)功能。熟练掌握Tensor操作,是书写高效Pytorch代码的基础。
3.1 Tensor的创建与基础属性
创建Tensor的方式多种多样,最常用的有以下几种:
import torch # 1. 从Python列表或Numpy数组创建 data = [[1, 2], [3, 4]] x_numpy = np.array(data) x_torch_from_list = torch.tensor(data) # 从列表创建 x_torch_from_np = torch.from_numpy(x_numpy) # 从numpy创建,共享内存(谨慎操作!) # 2. 创建特定数值的Tensor zeros_tensor = torch.zeros(2, 3) # 2行3列的全0矩阵 ones_tensor = torch.ones(2, 3, dtype=torch.float32) # 指定数据类型为float32 eye_tensor = torch.eye(3) # 3x3的单位矩阵 # 3. 创建随机Tensor(初始化神经网络权重常用) rand_tensor = torch.rand(2, 3) # 均匀分布 U(0, 1) randn_tensor = torch.randn(2, 3) # 标准正态分布 N(0, 1) randint_tensor = torch.randint(low=0, high=10, size=(2, 3)) # 整数随机 # 查看Tensor属性 print(rand_tensor.shape) # 形状: torch.Size([2, 3]) print(rand_tensor.dtype) # 数据类型: torch.float32 print(rand_tensor.device) # 所在设备: cpu (或 cuda:0)数据类型(dtype)的选择至关重要。在深度学习中,我们常用torch.float32(单精度浮点数)进行训练和推理,它在精度和内存/速度之间取得了平衡。torch.float64(双精度)精度更高但更耗资源;torch.float16(半精度)或bfloat16用于混合精度训练,可以大幅减少显存占用并加速计算,但对某些操作敏感。
3.2 索引、切片与形状变换
Tensor的索引和切片语法与Numpy、Python列表几乎一致,非常直观。
x = torch.arange(12).reshape(3, 4) # 创建一个3x4的矩阵,值为0-11 print(x) # tensor([[ 0, 1, 2, 3], # [ 4, 5, 6, 7], # [ 8, 9, 10, 11]]) # 索引 print(x[0]) # 第一行: tensor([0, 1, 2, 3]) print(x[:, 1]) # 第二列: tensor([1, 5, 9]) print(x[1, 2]) # 第二行第三列的元素: tensor(6) # 切片 print(x[0:2, :]) # 前两行所有列 print(x[:, 1:3]) # 所有行的第2到3列(左闭右开) # 形状变换 - 这是NLP中极其频繁的操作,因为文本序列长度可变 x_flat = x.flatten() # 展平为一维: tensor([0, 1, ..., 11]) x_reshaped = x.reshape(2, 6) # 重塑为2x6,元素总数必须不变(12) x_transposed = x.T # 转置,变为4x3 # 增加或减少维度(unsqueeze/squeeze)- NLP中用于对齐张量维度 # 例如,一个批次的句子,每个句子是词索引序列,需要增加批次维和特征维 seq = torch.tensor([1, 2, 3, 4]) seq_with_batch = seq.unsqueeze(0) # 在0维(最前面)增加一维,形状 [1, 4] seq_with_feature = seq.unsqueeze(-1) # 在-1维(最后面)增加一维,形状 [4, 1] seq_back = seq_with_feature.squeeze() # 压缩所有大小为1的维度,变回 [4]3.3 广播机制与原地操作
广播(Broadcasting)是Pytorch/Numpy中一个强大但容易出错的特性。它允许在不同形状的张量之间进行算术运算。规则是:从尾部维度开始对齐,维度大小为1的维度可以自动扩展以匹配另一个张量的对应维度大小。
a = torch.arange(3).reshape(3, 1) # shape: [3, 1] b = torch.arange(2) # shape: [2] # a: [[0], [1], [2]] b: [0, 1] # 运算时,a的第二个维度(1)扩展为2,b的第一个维度(缺失)扩展为3 c = a + b # shape: [3, 2] # c = [[0+0, 0+1], # [1+0, 1+1], # [2+0, 2+1]] = [[0,1], [1,2], [2,3]]理解广播能让你写出更简洁、高效的向量化代码,避免不必要的循环。
原地操作(In-place Operations)是指直接修改张量内容而不创建新张量的操作,方法名通常以下划线_结尾。它们能节省内存,但会破坏计算图,影响梯度传播,需要谨慎使用。
x = torch.tensor([1.0, 2.0], requires_grad=True) # 设置需要计算梯度 y = x + 2 z = y * y * 3 out = z.mean() out.backward() # 反向传播,计算x的梯度 print(x.grad) # tensor([6., 9.]), 正常 # 如果在计算图中使用了原地操作 x = torch.tensor([1.0, 2.0], requires_grad=True) y = x + 2 y.add_(10) # 原地操作, y被修改,同时破坏了y的原始来源信息 z = y * y * 3 out = z.mean() # out.backward() # 这里可能会报错或计算出错误的梯度,因为计算图被破坏了核心避坑点:在定义需要自动求导(
requires_grad=True)的张量计算流程时,尽量避免使用原地操作,除非你非常清楚自己在做什么,并且不需要对那个变量求导。对于不需要梯度的中间变量或最终处理,使用原地操作(如x.add_(1))可以提升性能。
4. 自动求导(Autograd):神经网络训练的引擎
Pytorch的自动微分引擎autograd是其灵魂。它自动追踪所有对requires_grad=True的张量进行的操作,并构建一个动态计算图。当你调用.backward()时,它会自动计算所有梯度。
4.1 计算图与梯度计算
让我们通过一个简单的例子来理解整个过程:
import torch # 1. 创建叶子节点(leaf node)张量,并标明需要梯度 x = torch.tensor(2.0, requires_grad=True) w = torch.tensor(3.0, requires_grad=True) b = torch.tensor(1.0, requires_grad=True) # 2. 进行前向传播计算,autograd会自动记录 y = w * x + b # y = 3*2 + 1 = 7 # 3. 计算损失(这里用一个简单的平方误差) loss = (y - 5) ** 2 # loss = (7-5)^2 = 4 # 4. 反向传播,计算所有 requires_grad=True 的张量的梯度 loss.backward() # 5. 查看梯度 print(x.grad) # d(loss)/dx = 2*(y-5)*w = 2*2*3 = 12 print(w.grad) # d(loss)/dw = 2*(y-5)*x = 2*2*2 = 8 print(b.grad) # d(loss)/db = 2*(y-5)*1 = 2*2 = 4这个过程模拟了神经网络训练中最核心的步骤:前向计算输出和损失,反向传播误差并计算每个参数的梯度(即参数需要调整的方向和幅度)。
4.2 梯度累积与清零
在训练循环中,梯度是累积的。这意味着每次调用.backward(),计算出的梯度会加到.grad属性上,而不是替换它。这在你需要累加多个小批次的梯度时有用,但在标准的随机梯度下降(SGD)中,我们需要在每个批次处理后清零梯度,防止不同批次的梯度相互干扰。
# 模拟一个简单的训练循环 optimizer = torch.optim.SGD([x, w, b], lr=0.01) # 优化器,用于更新参数 for epoch in range(10): # 前向传播 y = w * x + b loss = (y - 5) ** 2 # 反向传播前,必须将梯度清零! optimizer.zero_grad() # 等价于 x.grad.zero_(); w.grad.zero_(); b.grad.zero_() # 反向传播 loss.backward() # 用优化器更新参数 (x, w, b) -= lr * x.grad optimizer.step() print(f'Epoch {epoch}: loss = {loss.item()}, x={x.item()}')optimizer.zero_grad()是训练循环中必不可少的一步。忘记它会导致梯度爆炸,模型无法收敛。
4.3 控制梯度追踪:detach()与no_grad()
在某些场景下,我们不需要或不想追踪某些计算图的梯度。
detach(): 返回一个与当前计算图分离的新Tensor,共享数据但不参与梯度计算。常用于固定预训练模型的一部分参数,或者将张量移出计算图用于其他目的(如计算指标)。x = torch.tensor([1.0], requires_grad=True) y = x * 2 z = y.detach() # z是从y分离出来的,不参与y之前的梯度计算 w = z * 3 # w的计算不会影响x的梯度 w.backward() # 这里会报错,因为w的计算图里没有需要梯度的叶子节点 # 正确的用法:通常用于阻止梯度向某部分传播torch.no_grad(): 一个上下文管理器,在其作用域内的所有计算都不会被记录在计算图中。这是最常用、最高效的禁用梯度方法,用于模型推理(evaluation)、数据预处理等场景,可以显著减少内存消耗并加速计算。x = torch.tensor([1.0], requires_grad=True) # 训练阶段 y = x * 2 loss = y.sum() loss.backward() # 正常计算梯度 # 推理/评估阶段 with torch.no_grad(): # 进入无梯度模式 prediction = model(x) # model的forward计算不会被记录 # 可以对prediction做各种操作,不会构建计算图,速度快,省内存 # 退出with块后,梯度追踪恢复
经验之谈:在编写训练代码时,养成习惯:在训练循环的每次迭代开始
optimizer.zero_grad();在模型验证或测试时,用with torch.no_grad():包裹前向传播过程。这是保证代码正确性和高效性的两个关键细节。
5. 实战演练:用Pytorch实现一个简单的线性回归
理论说再多,不如动手写一行代码。让我们用刚学的知识,实现一个经典的线性回归模型,来预测一个简单的数据集。我们将手动实现梯度下降,而不是用torch.optim,以彻底理解autograd是如何工作的。
5.1 问题定义与数据准备
假设我们想拟合一个函数y = 2 * x + 1,并加入一些噪声来模拟真实数据。
import torch import matplotlib.pyplot as plt # 设置随机种子,保证结果可复现 torch.manual_seed(42) # 1. 准备数据 # 真实参数 true_w = 2.0 true_b = 1.0 # 生成合成数据 num_samples = 100 x = torch.rand(num_samples, 1) * 10 # 特征x,在0-10之间均匀分布 noise = torch.randn(num_samples, 1) * 1.5 # 高斯噪声 y = true_w * x + true_b + noise # 带噪声的标签y # 划分训练集和测试集(简单按比例划分) train_ratio = 0.8 train_size = int(num_samples * train_ratio) x_train, y_train = x[:train_size], y[:train_size] x_test, y_test = x[train_size:], y[train_size:] # 可视化数据 plt.scatter(x_train, y_train, label='Training Data', alpha=0.6) plt.scatter(x_test, y_test, label='Test Data', alpha=0.6) plt.plot([0, 10], [true_b, true_w*10+true_b], 'r-', label='True Function', linewidth=2) plt.xlabel('x') plt.ylabel('y') plt.legend() plt.show()5.2 模型定义与训练循环
我们将手动初始化参数w和b,并在循环中执行前向传播、计算损失、反向传播、更新参数。
# 2. 初始化模型参数(这些就是我们要学习的“模型”) # requires_grad=True 告诉Pytorch我们需要计算这些参数的梯度 w = torch.randn(1, requires_grad=True) # 随机初始化权重 b = torch.zeros(1, requires_grad=True) # 偏置初始化为0 # 3. 设置超参数 learning_rate = 0.01 num_epochs = 500 loss_history = [] # 记录损失变化 # 4. 训练循环 for epoch in range(num_epochs): # --- 前向传播 --- # 线性模型:y_pred = w * x + b # 这里利用了广播,w和b是标量,x_train是 [train_size, 1] y_pred = w * x_train + b # --- 计算损失 --- # 使用均方误差 (MSE) 作为损失函数 loss = torch.mean((y_pred - y_train) ** 2) loss_history.append(loss.item()) # 记录损失值,.item()将单元素张量转为Python数字 # --- 反向传播 --- # 清零上一轮累积的梯度(非常重要!) if w.grad is not None: w.grad.zero_() if b.grad is not None: b.grad.zero_() # 计算当前损失关于w和b的梯度 loss.backward() # --- 参数更新(手动梯度下降)--- # 更新公式:参数 = 参数 - 学习率 * 梯度 # 注意:必须在 torch.no_grad() 上下文内进行更新操作,否则更新操作本身会被记录进计算图 with torch.no_grad(): w -= learning_rate * w.grad b -= learning_rate * b.grad # 每100轮打印一次进度 if (epoch + 1) % 100 == 0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') print(f'训练完成。学到的参数: w = {w.item():.4f}, b = {b.item():.4f}') print(f'真实参数: w = {true_w}, b = {true_b}')5.3 结果评估与可视化
训练完成后,我们评估模型在测试集上的表现,并可视化拟合的直线。
# 5. 评估模型(在测试集上) with torch.no_grad(): # 评估时不需计算梯度 y_test_pred = w * x_test + b test_loss = torch.mean((y_test_pred - y_test) ** 2) print(f'在测试集上的损失 (MSE): {test_loss.item():.4f}') # 6. 可视化训练过程与最终结果 fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 子图1:损失下降曲线 axes[0].plot(loss_history) axes[0].set_xlabel('Epoch') axes[0].set_ylabel('Loss (MSE)') axes[0].set_title('Training Loss History') axes[0].grid(True) # 子图2:拟合结果 axes[1].scatter(x_train, y_train, label='Training Data', alpha=0.6) axes[1].scatter(x_test, y_test, label='Test Data', alpha=0.6) # 绘制真实函数 axes[1].plot([0, 10], [true_b, true_w*10+true_b], 'r-', label='True Function', linewidth=2) # 绘制学到的函数 x_plot = torch.linspace(0, 10, 100).reshape(-1, 1) with torch.no_grad(): y_plot = w * x_plot + b axes[1].plot(x_plot, y_plot, 'g--', label='Learned Function', linewidth=2) axes[1].set_xlabel('x') axes[1].set_ylabel('y') axes[1].set_title('Linear Regression Fit') axes[1].legend() axes[1].grid(True) plt.tight_layout() plt.show()通过这个完整的例子,你应该能清晰地看到:
- Tensor如何存储数据和参与计算。
- Autograd如何通过
.backward()自动计算出梯度w.grad和b.grad。 - 如何利用梯度手动更新参数,实现优化过程。
torch.no_grad()在评估和参数更新时的关键作用。- 一个完整的机器学习项目流程:数据准备、模型定义、训练循环、评估与可视化。
6. 常见问题与排查技巧实录
在实际操作中,你一定会遇到各种报错和意料之外的行为。这里我记录了几个初期最常碰到的问题及其解决方法。
6.1 版本兼容性与安装失败
- 问题:
conda install或pip install时出现冲突、长时间卡住或报错。 - 排查:
- 确认环境已激活:确保命令行提示符前有
(your_env_name)。 - 使用官方命令:始终从Pytorch官网获取安装命令,不要随意复制老旧博客的命令。CUDA版本、操作系统、包管理器的选择必须匹配。
- 创建纯净环境:如果当前环境很乱,尝试
conda create -n new_env python=3.9创建一个全新的环境再安装。 - 使用国内镜像加速:如果下载慢,可以为conda或pip配置清华、阿里云等国内镜像源。但安装Pytorch时,有时从官方源
-c pytorch更可靠。 - 检查Python版本:Pytorch新版本可能不再支持旧的Python(如3.6)。使用
python=3.8或3.9更稳妥。
- 确认环境已激活:确保命令行提示符前有
6.2 运行时错误:形状不匹配 (Shape Error)
- 问题:在矩阵乘法
torch.matmul()或广播运算时,出现RuntimeError: The size of tensor a must match the size of tensor b等错误。 - 排查:
- 打印形状:在出错的行之前,打印所有参与运算的Tensor的
.shape。这是最有效的调试手段。 - 理解广播规则:回顾广播机制,检查是否在非预期的维度上发生了扩展。使用
torch.unsqueeze()或torch.squeeze()显式地调整维度,比依赖隐式广播更安全。 - NLP中的典型场景:处理一批变长文本时,需要填充(padding)到相同长度。填充后,你的张量形状可能是
[batch_size, max_seq_len, embedding_dim]。进行注意力计算或全连接层操作时,务必清楚每个维度代表的意义。
- 打印形状:在出错的行之前,打印所有参与运算的Tensor的
6.3 梯度为None或计算图被破坏
- 问题:调用
loss.backward()后,参数的.grad属性仍然是None,或者报错RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation。 - 排查:
- 检查
requires_grad:确保你想要训练的参数(如模型权重)在创建时或通过.requires_grad_(True)设置了requires_grad=True。 - 警惕原地操作:这是最常见的原因。检查代码中是否有
x.add_(y),x[:, 0] = 10,x.data = ...这类操作。在计算图中,尽量使用非原地版本x = x + y。 - 分离(detach)与无梯度(no_grad):检查是否无意中将某个中间变量用
.detach()分离了,或者将关键计算放在了with torch.no_grad():块中。 - 从计算图中取数值:如果只需要Tensor的数值而不需要其梯度历史,使用
.detach()或.item()(针对标量)。
- 检查
6.4 GPU相关错误 (CUDA errors)
- 问题:在尝试将Tensor移到GPU(
.cuda()或.to(‘cuda’))时,报错CUDA error: out of memory或CUDA error: device-side assert triggered。 - 排查:
- 内存不足 (OOM):这是最普遍的GPU错误。使用
nvidia-smi命令监控GPU显存使用情况。- 降低批次大小 (batch_size):这是立竿见影的方法。
- 使用梯度累积:如果无法降低batch size,可以多次前向传播累积梯度,再一次性更新参数,模拟大batch效果。
- 使用混合精度训练:采用
torch.cuda.amp自动混合精度模块,用torch.float16代替部分torch.float32计算,可显著节省显存。 - 及时释放不用的变量:将不再需要的大张量设为
None或调用del,并手动触发垃圾回收import gc; gc.collect()。
- 设备间张量混用:确保进行运算的所有Tensor都在同一个设备上(CPU或同一个GPU)。使用
.to(device)统一设备。device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) data = data.to(device) - Device-side assert:这通常是代码逻辑错误在GPU上的体现,比如索引越界。尝试先在CPU上运行代码,看是否有同样的错误,CPU的错误信息通常更易读。
- 内存不足 (OOM):这是最普遍的GPU错误。使用
6.5 训练不收敛或损失为NaN
- 问题:训练过程中损失不下降,震荡剧烈,或者突然变成
NaN。 - 排查:
- 学习率过大:这是首要怀疑对象。尝试将学习率(
lr)降低一个数量级(例如从0.01降到0.001)。 - 数据未归一化/标准化:如果输入特征尺度差异巨大(如一个特征范围是0-1,另一个是0-10000),会导致优化困难。对数据进行标准化处理。
- 梯度爆炸:损失突然变成NaN,通常是梯度爆炸所致。可以使用梯度裁剪(Gradient Clipping)。
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 在loss.backward()之后,optimizer.step()之前调用 - 损失函数或模型有误:检查模型前向传播的逻辑是否正确,损失函数是否适用于你的任务(如分类任务用交叉熵,回归任务用均方误差)。
- 初始化问题:糟糕的权重初始化可能导致训练初期就失败。对于深度学习模型,使用
torch.nn.init中的方法(如kaiming_normal_)进行初始化。
- 学习率过大:这是首要怀疑对象。尝试将学习率(
记录下这些错误和解决方法,形成你自己的“排查清单”,下次再遇到时就能快速定位。编程和调试是学习过程中不可或缺的一部分,每一次解决错误,你对框架和模型的理解都会更深一层。
