ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PyTorch深度学习入门:从Tensor基础到Logistic回归实战

PyTorch深度学习入门:从Tensor基础到Logistic回归实战 1. 从零开始为什么PyTorch是深度学习入门的首选如果你刚开始接触深度学习面对TensorFlow、PyTorch、JAX这些框架可能会有点懵。我当年也一样花了不少时间在“选哪个”这个问题上打转。现在回过头看对于绝大多数初学者和研究者来说PyTorch无疑是那个最友好、最能让你快速“跑起来”并理解背后原理的选择。这不仅仅是因为它流行更是因为它设计哲学上的“Pythonic”和“动态图”特性让你写代码的感觉就像在用NumPy做科学计算一样自然。这个实验我们称之为“基本操作实验”其核心目的绝不是让你死记硬背几个API。它的真正价值在于帮你搭建起从数学理论到代码实现的思维桥梁。很多教程一上来就讲复杂的网络结构但地基没打牢后面全是空中楼阁。这个实验就是来打地基的你知道一个Logistic回归的损失函数是交叉熵那在PyTorch里这个“知道”如何变成一行代码你知道梯度下降要更新参数那PyTorch是如何替你自动、高效地完成求导的搞明白这些你才算是真正“入门”了深度学习编程而不是仅仅在调用黑盒。所以无论你是跟着学校课件还是自己在网上找资料学习这个“实验1”都是无法跳过的一环。接下来我会带你一步步拆解不仅告诉你“怎么做”更重点解释“为什么这么做”以及我在实际教学和项目中总结的那些容易踩坑的细节。2. 实验环境搭建避坑指南比安装步骤更重要很多人觉得环境搭建就是“复制粘贴命令”结果往往在这里就卡住半天。一个稳定、兼容的环境是后续所有实验的基础这一步必须稳扎稳打。2.1 Python与包管理器的选择首先强烈建议使用Anaconda或Miniconda来管理你的Python环境。深度学习项目依赖库多版本冲突是家常便饭。为这个实验单独创建一个虚拟环境能保证你的实验环境是纯净、可复现的。例如可以创建一个叫dl_lab1的环境conda create -n dl_lab1 python3.9 conda activate dl_lab1为什么选Python 3.9而不是最新的3.12因为深度学习框架和很多科学计算库对新版本Python的支持会有滞后3.9是目前兼容性最广、最稳定的版本之一能避免很多不必要的麻烦。2.2 PyTorch的安装CPU、GPU与版本之谜这是第一个容易迷糊的点。访问PyTorch官网https://pytorch.org/get-started/locally/你会看到一个配置生成器。你需要根据自己电脑的硬件和操作系统做出选择你的电脑有NVIDIA显卡吗如果有并且你想利用GPU加速后续训练模型会快很多你需要安装CUDA版本的PyTorch。首先你需要确认你显卡驱动支持的CUDA版本在命令行输入nvidia-smi查看。比如你的驱动支持CUDA 11.8那么在PyTorch官网就选择CUDA 11.8的安装命令。没有显卡或不想折腾直接选择CPU版本。对于这个基本操作实验数据量小CPU版本完全足够而且能避开CUDA环境配置这个深坑。很多同学在Windows上配置CUDA失败导致实验无法开始不如先用CPU版快速上手。一个典型的CPU安装命令如下使用pip并在国内使用清华镜像源加速pip install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple一个关键经验安装完成后务必写一个简单的验证脚本而不是仅仅导入不报错就认为成功了。import torch print(fPyTorch版本: {torch.__version__}) print(fCUDA是否可用: {torch.cuda.is_available()}) if torch.cuda.is_available(): print(fGPU设备名称: {torch.cuda.get_device_name(0)}) # 尝试创建一个张量 x torch.randn(3, 3) print(f随机张量:\n{x}) print(f张量设备: {x.device}) # 应该显示‘cpu’这段代码能一次性检查版本、GPU状态和基本功能。如果torch.cuda.is_available()返回True但你安装的是CPU版或者反之都说明安装可能有问题。2.3 配套工具库Jupyter还是PyCharm对于学习和实验Jupyter Notebook/Lab是极佳的选择。它的交互式特性允许你逐块执行代码、即时查看结果和中间变量非常适合理解每一步操作。你可以通过conda install jupyter安装。但如果你在进行一个稍大的项目或者更喜欢集成开发环境IDE那么PyCharm专业版或VS Code配合Python插件是更专业的选择。它们能提供更好的代码补全、调试和版本管理支持。我的建议是实验阶段用Jupyter探索和理解项目开发阶段用PyCharm或VS Code。3. TensorPyTorch世界的基石与核心操作PyTorch中所有的数据无论是输入、输出、还是模型参数其载体都是Tensor张量。你可以把它理解为N维数组是NumPy ndarray在GPU上的升级版并且自带“自动求导”的潜力。3.1 Tensor的创建多种方式及其适用场景创建Tensor的方式有很多每种都有其用意。import torch import numpy as np # 1. 从Python列表或NumPy数组创建 (最常用) data [[1, 2], [3, 4]] x_list torch.tensor(data) # 从列表创建 x_np torch.from_numpy(np.array(data)) # 从NumPy数组创建 (共享内存修改一个会影响另一个) # 2. 特定数值的初始化 zeros_t torch.zeros(2, 3) # 2行3列的全0张量 ones_t torch.ones(2, 3) # 全1张量 eye_t torch.eye(3) # 3x3的单位矩阵 rand_t torch.rand(2, 3) # [0, 1)均匀分布随机数 randn_t torch.randn(2, 3) # 标准正态分布随机数 (均值0方差1)常用于初始化神经网络权重 # 3. 指定数据类型和设备 # 深度学习中对精度很敏感默认是float32。有时为节省内存会用float16有时为保持精度用float64。 x_f16 torch.tensor(data, dtypetorch.float16, devicecpu) # 半精度CPU上 # 如果CUDA可用可以移到GPU上这是深度学习加速的关键 if torch.cuda.is_available(): x_gpu x_f16.to(cuda) # 或者 x_f16.cuda()关键理解torch.tensor()总是会拷贝数据而torch.from_numpy()在默认情况下与原始NumPy数组共享内存。这意味着如果你修改了x_np对应的NumPy数组x_np张量也会变。这有时能提升效率但也要小心 unintended side effects。3.2 Tensor的索引、切片与形状操作这部分和NumPy几乎一模一样学会一个就等于学会了另一个。x torch.randn(4, 5, 3) # 一个4x5x3的张量可以想象成4张5行3列的图片 # 索引 first_element x[0, 0, 0] # 取第0张“图片”的第0行第0列元素得到一个0维张量标量 first_image x[0] # 取第0张“图片”形状是(5, 3) first_row x[0, 0] # 取第0张图片的第0行形状是(3,) # 切片 first_two_images x[:2] # 取前两张图片形状是(2, 5, 3) even_rows x[:, ::2, :] # 所有图片的所有偶数列形状是(4, 3, 3) (假设5行中取第0,2,4行) # 形状操作 (极其重要) print(f原始形状: {x.shape}) x_reshaped x.view(4, 15) # view: 改变形状但要求总元素数不变。将(4,5,3) - (4,15) x_flattened x.view(-1, 3) # -1表示自动推导。将(4,5,3) - (20, 3)即“展平”前两维 x_squeezed torch.randn(1, 5, 1, 3).squeeze() # 移除所有大小为1的维度变成(5,3) x_unsqueezed x[0].unsqueeze(0) # 在指定维度(0)增加一个大小为1的维度(5,3) - (1,5,3)常用于给单张图片增加batch维度经验之谈view()和reshape()功能类似但view()要求张量在内存中是连续的contiguous否则会报错。reshape()更安全它会自动处理连续性问题必要时会拷贝数据。在不确定时用reshape()更省心。另外进行矩阵乘法等操作时经常需要unsqueeze来对齐维度这是调试形状错误时第一个要检查的地方。3.3 张量运算广播机制与原地操作PyTorch的运算支持高效的广播Broadcasting这是向量化编程的核心。a torch.randn(3, 1, 5) # 形状(3,1,5) b torch.randn(1, 4, 5) # 形状(1,4,5) # 广播从后往前比对维度如果维度相等或其中一个为1则可以广播。 # a: (3,1,5) - 可以扩展为 (3,4,5) # b: (1,4,5) - 可以扩展为 (3,4,5) c a b # 结果的形状是(3,4,5)a和b都被自动“扩展”了 # 矩阵乘法 vs 逐元素乘法 mat1 torch.randn(2, 3) mat2 torch.randn(3, 4) mat_mul torch.mm(mat1, mat2) # 或 mat1 mat2, 形状(2,4) elem1 torch.randn(2, 3) elem2 torch.randn(2, 3) elem_mul elem1 * elem2 # 逐元素相乘形状仍是(2,3)原地操作In-place Operation是一个需要特别注意的点。它们通常以_结尾会直接修改原张量不创建新的副本。x torch.ones(3, 3) y x.add(1) # 非原地操作x不变y是新的张量值为2 print(x) # 全1 print(y) # 全2 x.add_(1) # 原地操作x自己被改变了 print(x) # 全2注意原地操作能节省内存但在计算梯度时可能会出问题因为PyTorch的自动求导需要记录历史。在定义需要求导的计算图时谨慎使用原地操作除非你很清楚自己在做什么。4. Autograd让PyTorch拥有“灵魂”的自动求导手动推导和编写梯度是深度学习早期研究员的噩梦。PyTorch的autograd包解决了这个问题。其核心是在张量上进行的所有操作都会被自动记录到一个有向无环图DAG中。当你计算输出相对于某个输入的梯度时PyTorch会沿着这个图反向传播利用链式法则自动计算所有梯度。4.1 requires_grad、backward()与grad属性要启用一个张量的自动求导跟踪只需设置requires_gradTrue。# 创建需要求导的张量通常是模型参数 w torch.randn(3, 1, requires_gradTrue) # 标记w需要计算梯度 b torch.zeros(1, requires_gradTrue) # 标记b需要计算梯度 # 前向传播构建计算图 x torch.tensor([[1.0], [2.0], [3.0]]) # 输入数据不需要梯度 y_pred x w b # 矩阵乘法和加法操作被记录 loss ((y_pred - 2.0) ** 2).mean() # 假设真实值都是2计算均方误差损失 print(loss) # 此时loss是一个包含计算历史的张量 print(loss.grad_fn) # 可以查看创建这个张量的函数MeanBackward现在计算loss对w和b的梯度# 反向传播自动计算梯度 loss.backward() # 这是关键触发反向传播 # 梯度现在存储在张量的 .grad 属性中 print(w.grad) # 查看loss对w的梯度 print(b.grad) # 查看loss对b的梯度理解backward()loss必须是一个标量Scalar。因为梯度是标量对向量的导数。如果你试图对一个非标量张量调用backward()你需要传入一个形状相同的“梯度权重”张量这常用于更复杂的场景如RNN。对于简单的损失函数我们总是先汇总成一个标量如.mean(),.sum()。4.2 计算图与梯度清零每次backward()计算出的梯度会累加到tensor.grad属性中而不是覆盖。这是因为在有些模型如RNN中我们需要对同一个参数在不同时间步的梯度进行累加。但在最常见的随机梯度下降SGD中我们需要在每次参数更新前将旧的梯度清零。# 模拟一次参数更新梯度下降 learning_rate 0.01 with torch.no_grad(): # 重要在这个上下文管理器中的操作不会被autograd跟踪 w - learning_rate * w.grad b - learning_rate * b.grad # 更新后必须清零梯度否则下次backward()时梯度会累加 w.grad.zero_() b.grad.zero_() # 也可以更简洁地对模型的所有参数一起操作 # for param in [w, b]: # param.grad.zero_()为什么需要torch.no_grad()参数更新w - ...是一个不应该被记录到新计算图里的操作。我们只是根据计算出的梯度来修改参数值这个修改本身不需要求导。torch.no_grad()上下文管理器能临时关闭梯度跟踪提升计算效率并防止出错。4.3 梯度中断detach()与no_grad()的实战区别有时我们需要从计算图中“抽取”出一个张量的值用于评估或记录但又不希望后续计算影响到它的梯度。这时就需要detach()。x torch.randn(3, requires_gradTrue) y x * 2 z y.mean() z.backward() print(x.grad) # 有梯度 # 新场景我们想用y的值去做别的事但不想影响关于x的梯度计算 x2 torch.randn(3, requires_gradTrue) y2 x2 * 2 y2_detached y2.detach() # 从计算图中分离创建一个新张量requires_gradFalse # y2_detached与x2的计算历史断了后续操作不会影响x2的梯度 y2_detached.zero_() # 这个操作不会影响x2.grad # 对比 torch.no_grad() with torch.no_grad(): y3 x2 * 2 # y3的requires_gradFalse且整个计算过程不被记录简单区分detach()作用于一个已经存在的张量把它从图中“切下来”。torch.no_grad()是一个上下文管理器在这个环境里所有新产生的张量都不会有梯度。在模型测试/推理时我们通常用with torch.no_grad():包裹整个前向过程以节省内存和计算。5. 实战Logistic回归串联所有知识点现在让我们用PyTorch的基础知识亲手实现一个完整的Logistic回归模型。这将串联起Tensor创建、自动求导和优化流程。5.1 问题定义与数据准备Logistic回归用于二分类。我们假设一个简单的数据集根据两个特征x1和x2来预测类别0或1。import torch import torch.nn as nn import numpy as np # 1. 生成模拟数据 np.random.seed(42) # 固定随机种子确保结果可复现 num_samples 100 # 生成两类数据均服从正态分布但中心点不同 X_class0 np.random.randn(num_samples, 2) np.array([-2, -2]) # 类别0的中心在(-2,-2) X_class1 np.random.randn(num_samples, 2) np.array([2, 2]) # 类别1的中心在(2,2) X_np np.vstack((X_class0, X_class1)).astype(np.float32) # 合并特征形状(200, 2) # 生成标签前100个为0后100个为1 y_np np.hstack((np.zeros(num_samples), np.ones(num_samples))).astype(np.float32) y_np y_np.reshape(-1, 1) # 变成(200, 1)的列向量方便计算 # 2. 转换为PyTorch张量 X torch.from_numpy(X_np) y torch.from_numpy(y_np) # 3. 划分训练集和测试集 (简单按比例分割) train_ratio 0.8 num_train int(len(X) * train_ratio) indices torch.randperm(len(X)) # 随机打乱索引 X_train, y_train X[indices[:num_train]], y[indices[:num_train]] X_test, y_test X[indices[num_train:]], y[indices[num_train:]] print(f训练集大小: {X_train.shape}, 测试集大小: {X_test.shape})5.2 手动实现模型、损失与优化器我们不使用torch.nn的高级API而是用最基础的Tensor和Autograd来实现以加深理解。# 1. 初始化模型参数 (权重w和偏置b) # 输入特征数是2输出是1个标量表示属于类别1的概率 # 用正态分布初始化权重偏置初始化为0 torch.manual_seed(42) # 固定PyTorch随机种子 w torch.randn(2, 1, requires_gradTrue, dtypetorch.float32) b torch.zeros(1, requires_gradTrue, dtypetorch.float32) # 2. 定义模型前向传播函数 def logistic_model(X, w, b): Logistic回归模型: z X w b, 然后经过sigmoid激活函数 linear torch.mm(X, w) b # 线性部分X形状(n,2), w形状(2,1), 结果形状(n,1) # Sigmoid函数: σ(z) 1 / (1 exp(-z)) y_pred 1 / (1 torch.exp(-linear)) return y_pred # 3. 定义损失函数 (二元交叉熵损失) def binary_cross_entropy_loss(y_pred, y_true): 手动实现二元交叉熵损失。 公式: loss - [y_true * log(y_pred) (1-y_true) * log(1-y_pred)] 的均值 为了防止log(0)导致数值问题NaN通常会对y_pred进行裁剪。 epsilon 1e-7 # 一个很小的数用于数值稳定性 # 将预测值限制在[epsilon, 1-epsilon]区间内 y_pred_clipped torch.clamp(y_pred, epsilon, 1 - epsilon) # 计算损失 loss - (y_true * torch.log(y_pred_clipped) (1 - y_true) * torch.log(1 - y_pred_clipped)) return loss.mean() # 4. 定义准确率计算函数 def accuracy(y_pred, y_true): 将预测概率转换为类别0.5为1否则为0并计算准确率 y_pred_class (y_pred 0.5).float() correct (y_pred_class y_true).float() acc correct.mean() return acc # 5. 训练循环 learning_rate 0.1 num_epochs 500 loss_history [] acc_history [] for epoch in range(num_epochs): # 前向传播 y_pred_train logistic_model(X_train, w, b) loss binary_cross_entropy_loss(y_pred_train, y_train) # 反向传播 loss.backward() # 梯度会累积到w.grad和b.grad中 # 手动梯度下降更新参数 (不记录此操作到计算图) with torch.no_grad(): w - learning_rate * w.grad b - learning_rate * b.grad # 梯度清零至关重要 w.grad.zero_() b.grad.zero_() # 记录损失和准确率 loss_history.append(loss.item()) train_acc accuracy(y_pred_train, y_train) acc_history.append(train_acc.item()) # 每100轮打印一次信息 if (epoch 1) % 100 0: print(fEpoch [{epoch1}/{num_epochs}], Loss: {loss.item():.4f}, Acc: {train_acc:.4f}) # 6. 在测试集上评估 with torch.no_grad(): # 测试时不需计算梯度 y_pred_test logistic_model(X_test, w, b) test_loss binary_cross_entropy_loss(y_pred_test, y_test) test_acc accuracy(y_pred_test, y_test) print(f\n测试集结果 - 损失: {test_loss.item():.4f}, 准确率: {test_acc:.4f})逐行解读与避坑点参数初始化torch.manual_seed(42)确保了每次运行代码w的初始随机值都是一样的这对实验的可复现性至关重要。Sigmoid函数我们手动实现了1 / (1 torch.exp(-z))。在实际中更常用torch.sigmoid()函数它经过了数值优化。损失函数中的数值稳定性这是最容易出错的地方之一。直接计算log(y_pred)当y_pred为0时会得到-inf负无穷导致后续计算全为NaN。因此必须用torch.clamp或torch.where将预测值限制在一个很小的范围内如[1e-7, 1-1e-7]。PyTorch内置的nn.BCELoss已经处理了这个问题。梯度清零w.grad.zero_()是必须的。如果忘记下一次loss.backward()计算出的梯度会与上一次的累加导致优化方向错误模型无法收敛。with torch.no_grad()在参数更新w - learning_rate * w.grad时我们不需要PyTorch记录这个减法操作因为这不是前向传播的一部分所以要用no_grad()包裹起来提升效率。5.3 使用PyTorch内置模块重构代码手动实现有助于理解但在真实项目中我们肯定使用PyTorch内置的、经过高度优化的模块。重构后的代码更简洁、更健壮。import torch import torch.nn as nn import torch.optim as optim # 1. 定义模型类 (继承自nn.Module) class LogisticRegressionModel(nn.Module): def __init__(self, input_dim): super(LogisticRegressionModel, self).__init__() # 使用nn.Linear定义线性层它内部已经包含了权重w和偏置b self.linear nn.Linear(input_dim, 1) # 注意nn.Linear后面没有Sigmoid因为BCELoss期望输入是未经过Sigmoid的logits # 另一种做法是在这里加nn.Sigmoid然后使用nn.BCELoss def forward(self, x): # 前向传播线性变换 out self.linear(x) return out # 2. 初始化模型、损失函数、优化器 input_dim 2 model LogisticRegressionModel(input_dim) # 使用带Sigmoid的BCE损失需要将模型输出通过Sigmoid criterion nn.BCEWithLogitsLoss() # 这个损失函数内部包含了Sigmoid和BCELoss数值更稳定 # 等价于criterion nn.BCELoss() 但模型forward需要返回 torch.sigmoid(self.linear(x)) optimizer optim.SGD(model.parameters(), lr0.1) # SGD优化器传入模型的所有参数 # 3. 训练循环 (更简洁) num_epochs 500 for epoch in range(num_epochs): # 前向传播 outputs model(X_train) # 直接调用model.forward(X_train) loss criterion(outputs, y_train) # 计算损失 # 反向传播与优化 optimizer.zero_grad() # 清空过往梯度比手动zero_grad()更方便 loss.backward() # 反向传播计算当前梯度 optimizer.step() # 根据梯度更新参数封装了 w - lr * w.grad if (epoch 1) % 100 0: # 计算训练准确率需要将logits转换为概率和类别 with torch.no_grad(): # 对输出应用sigmoid得到概率 probas torch.sigmoid(outputs) y_pred_class (probas 0.5).float() acc (y_pred_class y_train).float().mean() print(fEpoch [{epoch1}/{num_epochs}], Loss: {loss.item():.4f}, Acc: {acc:.4f}) # 4. 测试 model.eval() # 将模型设置为评估模式如果模型有Dropout、BatchNorm层此步骤很重要 with torch.no_grad(): test_outputs model(X_test) test_loss criterion(test_outputs, y_test) test_probas torch.sigmoid(test_outputs) test_pred_class (test_probas 0.5).float() test_acc (test_pred_class y_test).float().mean() print(f\n测试集结果 - 损失: {test_loss.item():.4f}, 准确率: {test_acc:.4f})使用内置模块的优势nn.Module管理所有参数自动将requires_grad设为True方便地移动到GPUmodel.to(‘cuda’)以及保存/加载模型torch.save(model.state_dict(), ...)。nn.BCEWithLogitsLoss结合了Sigmoid激活和BCE损失数值计算上比分开更稳定避免了手动处理log(0)的问题。optim.SGD优化器对象自动管理所有参数的更新和梯度清零通过optimizer.zero_grad()。它还支持动量momentum、权重衰减等高级特性。model.eval()这是一个好习惯。虽然我们的简单模型没有Dropout或BatchNorm但在复杂模型中eval()会关闭这些训练阶段特有的行为如随机失活确保评估结果的一致性。6. 实验拓展与深度思考完成基本实验后我们可以从几个方向进行拓展这能极大加深你对PyTorch和机器学习原理的理解。6.1 可视化损失曲线与决策边界“看见”模型的训练过程和结果是调试和理解的关键。import matplotlib.pyplot as plt # 1. 绘制损失曲线和准确率曲线 plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(loss_history, labelTraining Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(Training Loss Curve) plt.legend() plt.grid(True) plt.subplot(1, 2, 2) plt.plot(acc_history, labelTraining Accuracy, colororange) plt.xlabel(Epoch) plt.ylabel(Accuracy) plt.title(Training Accuracy Curve) plt.legend() plt.grid(True) plt.tight_layout() plt.show() # 2. 绘制数据点和决策边界 def plot_decision_boundary(model, X, y): 绘制二维数据点和模型的决策边界 # 设置网格范围 x_min, x_max X[:, 0].min() - 1, X[:, 0].max() 1 y_min, y_max X[:, 1].min() - 1, X[:, 1].max() 1 h 0.02 # 网格步长 xx, yy np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) # 将网格点转换为PyTorch张量并进行预测 grid_tensor torch.from_numpy(np.c_[xx.ravel(), yy.ravel()]).float() model.eval() with torch.no_grad(): Z model(grid_tensor) Z torch.sigmoid(Z) # 转换为概率 Z (Z 0.5).float() # 转换为类别 Z Z.numpy().reshape(xx.shape) # 绘图 plt.contourf(xx, yy, Z, alpha0.3, cmapplt.cm.RdYlBu) scatter plt.scatter(X[:, 0], X[:, 1], cy.squeeze(), edgecolorsk, cmapplt.cm.RdYlBu) plt.xlabel(Feature 1) plt.ylabel(Feature 2) plt.title(Decision Boundary) plt.legend(*scatter.legend_elements(), titleClasses) plt.show() # 使用我们训练好的模型内置模块版本进行可视化 plot_decision_boundary(model, X.numpy(), y.numpy())通过损失曲线你可以判断模型是否在有效学习损失是否稳定下降以及是否过拟合训练损失持续下降但测试损失上升。决策边界图则直观地展示了模型是如何在特征空间中对两类数据进行划分的。6.2 尝试不同的优化器我们之前使用了最基础的SGD。PyTorch的torch.optim提供了多种优化器。# 比较SGD, Adam, RMSprop optimizers { SGD: optim.SGD(model.parameters(), lr0.1), Adam: optim.Adam(model.parameters(), lr0.01), # Adam通常需要更小的学习率 RMSprop: optim.RMSprop(model.parameters(), lr0.01) } for opt_name, optimizer in optimizers.items(): print(f\n 使用 {opt_name} 优化器 ) # 重新初始化模型参数 model LogisticRegressionModel(input_dim) if opt_name SGD: criterion nn.BCEWithLogitsLoss() else: criterion nn.BCEWithLogitsLoss() loss_hist [] for epoch in range(300): outputs model(X_train) loss criterion(outputs, y_train) optimizer.zero_grad() loss.backward() optimizer.step() loss_hist.append(loss.item()) # 绘制该优化器的损失曲线 plt.plot(loss_hist, labelopt_name) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(Comparison of Different Optimizers) plt.legend() plt.grid(True) plt.show()你会发现Adam优化器通常收敛得更快、更平稳因为它自适应地调整每个参数的学习率。对于这个简单问题SGD也能工作得很好但对于更复杂的网络和问题Adam往往是默认的首选。6.3 探索学习率的影响学习率Learning Rate可能是深度学习中最重要的超参数。它决定了参数更新的步长。learning_rates [0.001, 0.01, 0.1, 1.0] plt.figure(figsize(10, 6)) for lr in learning_rates: model LogisticRegressionModel(input_dim) criterion nn.BCEWithLogitsLoss() optimizer optim.SGD(model.parameters(), lrlr) loss_hist [] for epoch in range(200): outputs model(X_train) loss criterion(outputs, y_train) optimizer.zero_grad() loss.backward() optimizer.step() loss_hist.append(loss.item()) plt.plot(loss_hist, labelfLR{lr}) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(Effect of Learning Rate (SGD)) plt.legend() plt.yscale(log) # 使用对数坐标轴更容易观察差异 plt.grid(True) plt.show()你会观察到LR0.001收敛非常慢200轮后损失可能还没降到最低。LR0.01收敛速度适中且稳定。LR0.1收敛很快是我们之前使用的“合适”值。LR1.0学习率过大损失值可能会震荡甚至发散变得非常大无法收敛。这个实验直观地展示了选择合适学习率的重要性。在实际项目中我们经常使用学习率调度器如torch.optim.lr_scheduler.StepLR来在训练过程中动态调整学习率。7. 从实验到项目下一步该学什么完成这个基本操作实验你已经掌握了PyTorch最核心的三大件Tensor、Autograd和nn.Module。这相当于学会了汽车的油门、刹车和方向盘。接下来你可以沿着以下几个方向深入深入torch.nn模块学习更复杂的网络层如卷积层 (nn.Conv2d)、循环层 (nn.LSTM)、归一化层 (nn.BatchNorm2d)、丢弃层 (nn.Dropout) 等。用它们搭建一个图像分类的CNN或处理文本的RNN。掌握数据管道torch.utils.data真实数据往往存储在文件夹或数据库中。学习使用Dataset和DataLoader来高效地加载、预处理和批量提供数据这是处理大规模数据集的关键。理解设备管理torch.cuda将模型和数据移动到GPU上实现真正的加速。理解.to(device)的使用以及多GPU训练 (nn.DataParallel,nn.DistributedDataParallel) 的基本概念。项目实战在Kaggle或天池找一个入门级比赛如手写数字识别、房价预测尝试用PyTorch完整地走一遍流程数据探索、清洗、构建模型、训练、调参、提交结果。这是巩固知识的最佳方式。阅读优秀代码去GitHub上找一些经典的PyTorch实现项目如PyTorch官方Examples、TorchVision模型库看看别人的代码是如何组织的学习模块化、可配置的工程化写法。这个实验就像编程里的“Hello World”它简单但涵盖了所有核心概念。我见过很多同学急着去跑复杂的模型结果卡在张量形状不对、梯度没清零这种基础问题上。把这里面的每一步都亲手敲一遍理解每一个变量背后的意义你后续的学习道路会顺畅得多。当你再看到复杂的网络结构时你会发现它们无非是这些基本操作的组合与堆叠。
返回列表