简介:本资源是黄海广教授主讲的《PyTorch入门》完整版PPT课件(最新修订版),面向零基础或初学机器学习的高校学生、转行开发者及AI爱好者,系统解决PyTorch核心概念理解难、环境配置易错、张量操作与模型构建流程不清晰等入门痛点。压缩包为RAR格式,共含数十页结构化PPT文件(含代码示例、图解原理、对比表格与典型NLP任务建模流程),总大小107.66MB,内容覆盖Tensor基础、Autograd机制、nn.Module构建、数据加载与训练循环等关键模块,逻辑层层递进,适合作为课堂讲义或自学主线。已有93人学习下载,PPT中嵌入大量可复现的简明代码片段与可视化示意图,配合清晰的知识导图与常见误区标注,帮助读者快速建立PyTorch编程直觉,夯实从理论到实践的转化能力。
1. 这份“PyTorch入门(黄海广教授)完整版PPT”不是课件搬运,而是你搭起第一个神经网络前最该吃透的「认知脚手架」
如果你刚在知乎、CSDN或B站搜索“PyTorch 入门”,点开一堆标题带“黄海广”的PPT资源,却发现下载后全是文字堆砌、公式悬浮、代码缺失——这不是PPT本身的问题,而是你缺了一把钥匙:把PPT里的概念映射到可执行、可调试、可验证的本地环境里。黄海广教授的这套材料之所以被高频引用,核心在于它用极简路径覆盖了PyTorch最不可跳过的三块基石:张量计算的本质、自动微分的触发逻辑、以及模型定义与训练循环的耦合关系。它不教你怎么调参,但教你为什么torch.nn.Module必须继承、为什么loss.backward()之后optimizer.step()不能少、为什么.to(device)要写在数据和模型上而不是只写一边。适合两类人:一是刚写完print("Hello World")、正卡在“为什么我的模型不收敛”门口的Python新手;二是已会用Keras/TensorFlow但想真正理解底层调度机制的转岗工程师。本文不复述PPT每页内容,而是以它为路标,带你从零构建一个能跑通、能打断点、能改参数、能看梯度的最小闭环。
2. 用conda+pip在本地跑通PyTorch最小训练循环:避开CUDA版本错配与包冲突的实操路径
黄海广PPT第3讲强调“环境是第一道门槛”,这不是客套话。大量初学者卡在ImportError: libcudnn.so.8: cannot open shared object file或No module named 'torch',本质是没理清Python解释器、CUDA驱动、PyTorch二进制包三者的版本绑定链。我们跳过“先装Anaconda再装PyTorch”的模糊指引,直接给出2024年主流Linux/macOS/Windows下可验证、可回滚、可复现的最小命令集。
2.1 创建隔离环境并安装匹配的PyTorch二进制包
提示:不要用
pip install torch——它默认安装CPU版,且不校验CUDA兼容性。必须从 PyTorch官网 复制对应你显卡驱动版本的安装命令。
假设你的NVIDIA驱动版本≥535(对应CUDA 12.1),执行:
# 创建新环境(Python 3.10是当前PyTorch 2.3+最稳版本) conda create -n pytorch_env python=3.10 conda activate pytorch_env # 安装PyTorch 2.3.0 + CUDA 12.1(2024年Q2最广泛适配组合) pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121验证是否成功:
import torch print(torch.__version__) # 应输出 2.3.0 print(torch.cuda.is_available()) # 应输出 True print(torch.cuda.device_count()) # 应输出 ≥1若is_available()返回False,不是PyTorch装错了,而是你的系统CUDA驱动版本低于535。此时需降级PyTorch(如换cu118)或升级驱动——PPT里“GPU加速”一页的底层约束就在这里。
2.2 复现PPT第7页“张量创建与运算”的可调试代码
黄海广PPT用表格对比torch.tensor()、torch.Tensor()、torch.zeros()的区别,但没告诉你何时该用哪个。实际开发中,错误选择会导致隐式拷贝或设备不一致:
import torch # ✅ 正确:明确指定设备,避免后续.to(device)冗余 x = torch.tensor([1, 2, 3], dtype=torch.float32, device='cuda') # ❌ 危险:torch.Tensor()是旧接口,不支持device参数,且默认CPU y = torch.Tensor([4, 5, 6]) # 类型为float64,且在CPU上 # ✅ 张量运算必须同设备同dtype z = x * y.to('cuda').float() # .to()强制迁移,.float()统一类型 # 验证:打印设备与dtype print(f"x device: {x.device}, dtype: {x.dtype}") # cuda:0, torch.float32 print(f"z sum: {z.sum().item()}") # 32.0注意:PPT中“张量广播”示例常省略
.item(),但实际调试时若直接print(z.sum())会输出tensor(32., device='cuda:0')——这个tensor对象无法参与Python原生运算,必须.item()提取标量值。这是新手在损失函数打印时最常见的报错源头。
2.3 构建PPT第12页“线性回归训练循环”的最小可运行版本
PPT用伪代码描述for epoch in range(epochs):,但没给出optimizer.zero_grad()为何必须在loss.backward()之前。以下代码严格对应PPT流程图,且添加断点调试标记:
import torch import torch.nn as nn import torch.optim as optim # 1. 定义模型(对应PPT“nn.Module子类化”页) class LinearModel(nn.Module): def __init__(self): super().__init__() self.linear = nn.Linear(1, 1) # 输入1维,输出1维 def forward(self, x): return self.linear(x) model = LinearModel().to('cuda') # 显式迁移至GPU # 2. 准备数据(PPT强调“数据需tensor化”) X = torch.randn(100, 1).to('cuda') # 100个样本,1维特征 y = 2 * X + 1 + 0.1 * torch.randn(100, 1).to('cuda') # 真实权重2,偏置1 # 3. 定义损失与优化器(PPT第11页公式对应) criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=0.01) # 4. 训练循环(严格遵循PPT四步:前向→损失→反向→更新) for epoch in range(100): # 前向传播 pred = model(X) # 调用forward() # 计算损失 loss = criterion(pred, y) # 反向传播(关键!zero_grad必须在此前) optimizer.zero_grad() # 清空上一轮梯度,否则累加! loss.backward() # 计算所有参数梯度 # 参数更新 optimizer.step() # 应用梯度更新权重 # 调试:每10轮打印一次,验证收敛 if epoch % 10 == 0: print(f"Epoch {epoch}, Loss: {loss.item():.4f}") # 检查权重是否在更新 w = model.linear.weight.item() b = model.linear.bias.item() print(f" Weight: {w:.4f}, Bias: {b:.4f}") # 最终验证:预测值应接近真实y final_pred = model(X) print(f"Final MSE: {nn.MSELoss()(final_pred, y).item():.4f}")这段代码的关键在于optimizer.zero_grad()的位置——它必须在loss.backward()之前,否则梯度会累积导致爆炸。PPT中“梯度清零”一页的图示若没配上这行代码,就只是理论。运行后你会看到Loss从初始~5.0降至0.01以下,权重趋近2.0,偏置趋近1.0,这才是PPT里“训练完成”的真实信号。
3. 解析PPT第15页“CNN图像分类”:从MNIST数据加载到模型结构的逐层拆解
黄海广PPT用一张LeNet-5结构图概括CNN,但没说明每个层的输入输出尺寸如何推导。本节以PPT中提到的MNIST为例,用torchsummary可视化每一层的tensor shape变化,并指出常见尺寸错配陷阱。
3.1 数据加载必须满足PPT第14页“归一化与批处理”要求
PPT强调“图像需标准化至[0,1]并按batch组织”,但未给出transforms.Compose的具体组合。以下代码严格复现PPT推荐的预处理链:
from torch.utils.data import DataLoader from torchvision import datasets, transforms # PPT第14页要求:归一化 + 转tensor transform = transforms.Compose([ transforms.ToTensor(), # PIL → [C,H,W] tensor,值域[0,1] transforms.Normalize((0.1307,), (0.3081,)) # MNIST均值/标准差,中心化至[-1,1] ]) train_dataset = datasets.MNIST( root='./data', train=True, download=True, transform=transform ) # PPT强调“batch_size影响内存与收敛”,此处设32为平衡点 train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)注意:
transforms.Normalize的参数(0.1307,)是MNIST全局均值,(0.3081,)是标准差。若用错(如填(0.5, 0.5)),模型将难以收敛——PPT中“数据预处理”一页的数值不是示意,而是实测最优值。
3.2 构建LeNet-5模型并验证各层尺寸(对应PPT第15页结构图)
PPT结构图显示:Conv2d(1,6,5)→ReLU→MaxPool2d(2)→Conv2d(6,16,5)→MaxPool2d(2)→Linear(256,120)→Linear(120,84)→Linear(84,10)。我们用torchsummary打印实际shape:
import torch.nn as nn from torchsummary import summary class LeNet5(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 6, 5) # 输入1通道,输出6通道,5×5卷积核 self.pool = nn.MaxPool2d(2, 2) # 2×2池化,步长2 self.conv2 = nn.Conv2d(6, 16, 5) # 输入6通道,输出16通道 self.fc1 = nn.Linear(16 * 4 * 4, 120) # 关键!16*4*4来自conv2输出尺寸 self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.pool(torch.relu(self.conv1(x))) x = self.pool(torch.relu(self.conv2(x))) x = x.view(-1, 16 * 4 * 4) # 展平:16通道 × 4高 × 4宽 x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = self.fc3(x) return x # 打印模型结构(需先安装:pip install torchsummary) model = LeNet5().to('cuda') summary(model, (1, 28, 28)) # 输入:1通道,28×28图像输出关键行:
================================================================= Layer (type:depth-idx) Output Shape Param # ================================================================= Conv2d-1 [-1, 6, 24, 24] 156 MaxPool2d-2 [-1, 6, 12, 12] 0 Conv2d-3 [-1, 16, 8, 8] 2416 MaxPool2d-4 [-1, 16, 4, 4] 0 Linear-5 [-1, 120] 30840 ...尺寸推导逻辑(PPT未展开但必须掌握):
Conv2d(1,6,5):输入28×28,卷积核5×5,无padding → 输出(28-5+1)=24→24×24MaxPool2d(2):24÷2=12 →12×12Conv2d(6,16,5):12-5+1=8 →8×8MaxPool2d(2):8÷2=4 →4×4Linear输入:16 channels × 4 height × 4 width = 256→ 但PPT写16*4*4=256,而代码中fc1定义为16*4*4=256→PPT第15页“Linear(256,120)”的256正是此处计算所得
3.3 训练循环加入PPT强调的“验证集评估”环节
PPT第16页指出“仅看训练loss会过拟合”,必须加入验证集。以下代码在训练循环中插入验证步骤:
# 加载验证集(PPT要求与训练集同预处理) val_dataset = datasets.MNIST('./data', train=False, transform=transform) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False) def validate(model, val_loader): model.eval() # 关闭dropout/batchnorm correct = 0 total = 0 with torch.no_grad(): # 省显存,禁梯度 for data, target in val_loader: data, target = data.to('cuda'), target.to('cuda') outputs = model(data) _, predicted = torch.max(outputs.data, 1) total += target.size(0) correct += (predicted == target).sum().item() return 100 * correct / total # 在训练循环中每5轮验证一次 for epoch in range(10): model.train() for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to('cuda'), target.to('cuda') optimizer.zero_grad() output = model(data) loss = criterion(output, target) loss.backward() optimizer.step() if epoch % 5 == 0: acc = validate(model, val_loader) print(f"Epoch {epoch}, Val Accuracy: {acc:.2f}%")运行后你会看到验证准确率从初始~10%升至98%+,这比训练loss下降更能说明模型学到泛化能力——PPT中“过拟合”一页的警示,此刻才具象为一个百分数。
4. 调试PPT第18页“模型保存与加载”:解决RuntimeError: Error(s) in loading state_dict的3个必查点
黄海广PPT第18页给出torch.save(model.state_dict(), 'model.pth')和model.load_state_dict(torch.load('model.pth'))两行代码,但实际使用时90%的报错源于三个隐藏细节。本节提供可复现的调试方案。
4.1 模型结构变更后加载失败:用strict=False定位缺失键
假设你先保存了一个LeNet-5模型,后修改结构(如增加一层fc4),再尝试加载:
# 保存原始模型 torch.save(model.state_dict(), 'lenet5.pth') # 修改模型:新增fc4层 class LeNet5V2(LeNet5): def __init__(self): super().__init__() self.fc4 = nn.Linear(10, 10) # 新增层 def forward(self, x): x = super().forward(x) return self.fc4(x) model_v2 = LeNet5V2().to('cuda') # ❌ 直接加载会报错:Missing key(s) in state_dict # ✅ 用strict=False并打印差异 checkpoint = torch.load('lenet5.pth') model_v2.load_state_dict(checkpoint, strict=False) print("Loaded keys:", list(checkpoint.keys())) print("Model keys:", list(model_v2.state_dict().keys()))输出会显示fc4.weight和fc4.bias未被加载——这正是PPT未说明的“结构兼容性”约束。生产环境中,必须用strict=False配合日志确认哪些层被跳过。
4.2 GPU模型在CPU上加载:map_location参数的正确写法
PPT未提设备迁移。若你在GPU上保存模型,却在无GPU的机器上加载:
# GPU上保存 torch.save(model.state_dict(), 'model_gpu.pth') # CPU上加载(必须指定map_location) model_cpu = LeNet5() model_cpu.load_state_dict( torch.load('model_gpu.pth', map_location='cpu') # 关键! ) model_cpu.eval() # 切换为推理模式若省略map_location='cpu',会报错Attempting to deserialize object on a CUDA device。PPT中“跨设备部署”一页的实践基础就在此。
4.3 保存完整模型 vs 仅保存state_dict:选型决策表
| 保存方式 | 代码示例 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
state_dict | torch.save(model.state_dict(), 'ckpt.pth') | 文件小(仅参数)、加载快、结构解耦 | 需重新定义模型类 | 生产部署、模型共享 |
| 完整模型 | torch.save(model, 'full_model.pth') | 保存结构+参数+方法,加载即用 | 文件大、依赖源码、版本敏感 | 快速实验、notebook调试 |
提示:PPT第18页默认采用
state_dict方式,因其符合工业界最佳实践。但初学者常误用torch.save(model, ...),导致后续无法在不同Python环境中加载——因为model对象序列化时会绑定当前模块路径。
5. 用PPT第20页“实战:CIFAR-10分类”验证你的环境:5分钟跑通并定位性能瓶颈
黄海广PPT最后一章给出CIFAR-10任务作为综合检验,但未提供可直接运行的完整脚本。本节给出精简版(<100行),并教你用PyTorch内置工具定位训练慢的根源。
5.1 构建ResNet-18精简版(适配PPT“深度网络”页)
import torch import torch.nn as nn from torchvision import datasets, transforms class BasicBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride, 1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.conv2 = nn.Conv2d(out_channels, out_channels, 3, 1, 1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels) self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, stride, bias=False), nn.BatchNorm2d(out_channels) ) def forward(self, x): out = torch.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out += self.shortcut(x) return torch.relu(out) class ResNet18(nn.Module): def __init__(self, num_classes=10): super().__init__() self.conv1 = nn.Conv2d(3, 64, 3, 1, 1, bias=False) self.bn1 = nn.BatchNorm2d(64) self.layer1 = nn.Sequential(BasicBlock(64, 64), BasicBlock(64, 64)) self.layer2 = nn.Sequential(BasicBlock(64, 128, 2), BasicBlock(128, 128)) self.layer3 = nn.Sequential(BasicBlock(128, 256, 2), BasicBlock(256, 256)) self.layer4 = nn.Sequential(BasicBlock(256, 512, 2), BasicBlock(512, 512)) self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(512, num_classes) def forward(self, x): x = torch.relu(self.bn1(self.conv1(x))) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avgpool(x) x = torch.flatten(x, 1) return self.fc(x)5.2 CIFAR-10数据加载与训练(含性能分析)
# 数据预处理(PPT强调“CIFAR需不同归一化参数”) transform_train = transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) # CIFAR专用 ]) train_dataset = datasets.CIFAR10('./data', train=True, download=True, transform=transform_train) train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=4) # 初始化模型与优化器 model = ResNet18().to('cuda') criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 启动PyTorch Profiler(PPT未提但至关重要) with torch.profiler.profile( activities=[torch.profiler.ProfilerActivity.CPU, torch.profiler.ProfilerActivity.CUDA], record_shapes=True, profile_memory=True, with_stack=True ) as prof: for data, target in train_loader: data, target = data.to('cuda'), target.to('cuda') optimizer.zero_grad() output = model(data) loss = criterion(output, target) loss.backward() optimizer.step() break # 仅分析1个batch # 输出性能瓶颈报告 print(prof.key_averages(group_by_stack_n=5).table( sort_by="cuda_time_total", row_limit=10 ))运行后,Profiler会输出类似:
----------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ Name Self CPU % Self CPU time CPU total % CPU total time CUDA total % CUDA total time ----------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ aten::cudnn_convolution 42.34% 12.542ms 42.34% 12.542ms 68.21% 18.34ms aten::native_layer_norm_backward 15.21% 4.512ms 15.21% 4.512ms 12.05% 3.24ms ...关键解读:
- 若
cudnn_convolution占比>60%,说明GPU计算是瓶颈,需考虑增大batch_size或升级显卡; - 若
DataLoader相关操作(如_MultiProcessingDataLoaderIter._next_data)占比高,说明数据加载慢,需调num_workers或启用pin_memory=True; - 若
aten::copy_频繁出现,说明tensor设备不一致(如CPU数据喂给GPU模型),需检查.to('cuda')是否遗漏。
这正是PPT第20页“实战”背后隐藏的工程思维——跑通只是起点,可观测性才是生产级落地的门槛。
本文还有配套的精品资源,点击获取