楼层: 首页/ 软件技术/ Python AI 数据科学/ PyTorch:深度学习入门
6

PyTorch:深度学习入门

PyTorch: Tensors, Autograd & nn.Module

PyTorch 是 Facebook(Meta)开源的深度学习框架,是 AI 研究圈的首选。它最大的优点:动态计算图——你写普通 Python 代码,它自动记录计算过程,需要时反向求导。你不用手动写求导公式。

Tensor:深度学习的 ndarray

import torch # 创建张量(跟 NumPy 数组几乎一样) x = torch.tensor([1, 2, 3]) print(x) # tensor([1, 2, 3]) # 全 0、全 1、随机 torch.zeros(3, 4) torch.ones(3, 4) torch.randn(3, 4) # 标准正态分布 # 跟 NumPy 互转 import numpy as np n = np.array([1, 2]) t = torch.from_numpy(n) back = t.numpy() # GPU 加速(有 NVIDIA 显卡时) device = "cuda" if torch.cuda.is_available() else "cpu" x = x.to(device) print("用的设备:", device)

自动微分 autograd

论深度学习的核心:自动求导

训练神经网络本质就是:算出损失,然后求损失对每个参数的梯度,沿梯度反方向调参数。PyTorch 的 autograd 帮你自动算梯度。你只要在需要求导的张量上设 requires_grad=True,跑完前向,调 .backward(),梯度就存在 .grad 里了。

x = torch.tensor(3.0, requires_grad=True) y = x * x + 2 * x + 1 # y = x² + 2x + 1 y.backward() # 反向传播,自动求导 print(x.grad) # tensor(8.) dy/dx = 2x+2 = 8,对的

nn.Module:定义你的网络

import torch.nn as nn class MyNet(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(784, 128) # 输入784维,输出128维 self.fc2 = nn.Linear(128, 10) # 128维压成10类 self.relu = nn.ReLU() def forward(self, x): x = self.relu(self.fc1(x)) x = self.fc2(x) return x model = MyNet().to(device)

训练循环:四步走

论训练循环的四步

① 前向:把数据喂进模型,算预测值。② 算 loss:预测值和真实标签差多少。③ 反向:loss.backward() 自动算梯度。④ 更新参数:优化器沿梯度走一步。然后把梯度清零,下一轮。这四步是所有深度学习训练的骨架。

from torch import optim # 损失函数:分类用交叉熵 criterion = nn.CrossEntropyLoss() # 优化器:Adam 是最常用的,学习率 0.001 optimizer = optim.Adam(model.parameters(), lr=0.001) # 假设有 train_loader(DataLoader,后面讲) for epoch in range(5): model.train() for images, labels in train_loader: images = images.to(device) labels = labels.to(device) optimizer.zero_grad() # ① 清空上一轮梯度 outputs = model(images) # ② 前向 loss = criterion(outputs, labels) # ③ 算 loss loss.backward() # ④ 反向传播算梯度 optimizer.step() # ⑤ 更新参数 print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")

Dataset 与 DataLoader

from torch.utils.data import Dataset, DataLoader class MyDataset(Dataset): def __init__(self, X, y): self.X = X self.y = y def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.y[idx] # DataLoader:自动分批、打乱、多进程加载 dataset = MyDataset(X_train, y_train) loader = DataLoader(dataset, batch_size=32, shuffle=True)

数据增强 transforms:免费加数据

from torchvision import transforms # 训练时:随机增强(让模型见过更多变体) train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 验证时:别随机,老老实实 resize + normalize val_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ])

实战:MNIST 手写数字识别

# 用 torchvision 自带的 MNIST from torchvision.datasets import MNIST from torchvision.transforms import ToTensor train_set = MNIST("./data", train=True, download=True, transform=ToTensor()) train_loader = DataLoader(train_set, batch_size=64, shuffle=True) # 把 28x28 图片拉平成 784 维向量 class MNISTNet(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(784, 256) self.fc2 = nn.Linear(256, 10) def forward(self, x): x = x.view(-1, 784) # 拉平 x = torch.relu(self.fc1(x)) return self.fc2(x) model = MNISTNet().to(device) # 然后套上面那个四步训练循环就行,跑 5 轮准确率能到 97%+

训练技巧:验证、学习率、正则化

技巧为什么 & 怎么用
验证集训练时每轮在验证集上评一次,别只看训练 loss。
model.train() / eval()训练时开 Dropout,验证时关掉。eval 模式行为不同。
torch.no_grad()验证/推理时包一层,不算梯度省内存。
学习率调度optim.lr_scheduler.StepLR:几轮后自动降学习率。
Dropout训练时随机"关掉"一部分神经元,防过拟合。
BatchNorm每层输入标准化,训练更稳、收敛更快。
数据增强图像随机翻转/裁剪,相当于免费加数据。

完整训练+验证循环模板

for epoch in range(10): # --- 训练阶段 --- model.train() for x, y in train_loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() loss = criterion(model(x), y) loss.backward() optimizer.step() # --- 验证阶段 --- model.eval() correct, total = 0, 0 with torch.no_grad(): for x, y in val_loader: x, y = x.to(device), y.to(device) pred = model(x).argmax(dim=1) total += y.size(0) correct += (pred == y).sum().item() print(f"Epoch {epoch}: 验证准确率 {correct/total:.2%}")

TensorBoard:看训练曲线

from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter("runs/exp1") # 在训练循环里 writer.add_scalar("loss/train", loss.item(), epoch) writer.add_scalar("acc/val", acc, epoch) # 命令行启动:tensorboard --logdir=runs # 浏览器打开 http://localhost:6006 看曲线

学习率调度器

from torch import optim optimizer = optim.Adam(model.parameters(), lr=0.01) # 每 10 轮学习率乘 0.1 scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) # 在训练循环里每轮结束调一下 for epoch in range(20): # ... 训练 ... scheduler.step() print("当前学习率:", optimizer.param_groups[0]["lr"])

没有 N 卡?用 Colab

论白嫖 GPU

Google Colab(colab.research.google.com)免费提供带 GPU 的 Jupyter 环境——新建 notebook,菜单"代码执行程序 → 更改运行时类型 → 硬件加速器选 GPU"。MNIST、CIFAR-10 这种小数据集,免费 GPU 几分钟就跑完。缺点是长时间跑会断,适合实验和学习,不适合正经生产。

完整训练循环模板:训练 / 验证 / 测试三阶段

论工程级训练骨架

真实项目的训练循环要包含:训练(前向+反向+更新)、验证(不更新权重,看泛化)、早停(验证 loss 不降就停)、学习率调度、模型保存(只存最好的那个)、测试集最终评估。下面是可以直接抄的模板。

import torch from torch import nn, optim from torch.utils.data import DataLoader device = "cuda" if torch.cuda.is_available() else "cpu" # 假设有 train_loader / val_loader / test_loader model = MyNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20) # ---- 早停类 ---- class EarlyStopping: def __init__(self, patience=5, path="best.pth"): self.patience = patience self.counter = 0 self.best_loss = float("inf") self.path = path def __call__(self, val_loss, model): if val_loss < self.best_loss: self.best_loss = val_loss self.counter = 0 torch.save(model.state_dict(), self.path) else: self.counter += 1 if self.counter >= self.patience: return True # 该停了 return False early_stopper = EarlyStopping(patience=5) # ---- 训练 ---- for epoch in range(30): # 训练阶段 model.train() total_train_loss = 0 for x, y in train_loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() out = model(x) loss = criterion(out, y) loss.backward() # 梯度裁剪,防梯度爆炸 nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() total_train_loss += loss.item() * x.size(0) train_loss = total_train_loss / len(train_loader.dataset) # 验证阶段 model.eval() total_val_loss, correct, total = 0, 0, 0 with torch.no_grad(): for x, y in val_loader: x, y = x.to(device), y.to(device) out = model(x) loss = criterion(out, y) total_val_loss += loss.item() * x.size(0) pred = out.argmax(dim=1) correct += (pred == y).sum().item() total += y.size(0) val_loss = total_val_loss / len(val_loader.dataset) val_acc = correct / total scheduler.step() print(f"Epoch {epoch+1:02d} | train {train_loss:.4f} | val {val_loss:.4f} | acc {val_acc:.2%}") # 早停 if early_stopper(val_loss, model): print("早停!") break # ---- 加载最优模型,在测试集上最终评估 ---- model.load_state_dict(torch.load("best.pth", weights_only=True)) model.eval() # ... 在 test_loader 上跑一遍 ...

自定义 Dataset 与数据增强 transforms

from torch.utils.data import Dataset, DataLoader from torchvision import transforms from PIL import Image import pandas as pd, os # 自定义 Dataset:读图片路径 + 标签 CSV class DogCatDataset(Dataset): def __init__(self, csv_file, img_dir, transform=None): self.df = pd.read_csv(csv_file) self.img_dir = img_dir self.transform = transform def __len__(self): return len(self.df) def __getitem__(self, idx): img_path = os.path.join(self.img_dir, self.df.iloc[idx, 0]) image = Image.open(img_path).convert("RGB") label = self.df.iloc[idx, 1] # 0=猫, 1=狗 if self.transform: image = self.transform(image) return image, label # 训练时的数据增强:随机裁剪、翻转、标准化 train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 验证/测试:不做随机增强 val_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # DataLoader:batch + shuffle + 多进程加载 train_ds = DogCatDataset("train.csv", "./imgs", transform=train_transform) train_loader = DataLoader( train_ds, batch_size=32, shuffle=True, num_workers=4, pin_memory=True, drop_last=True )

PyTorch 章节面试题

面试 · PyTorch / 深度学习基础

Q1. 什么是反向传播?为什么需要它?

查看答案

反向传播就是从损失出发,用链式法则逐层计算损失对每个参数的偏导数。它是自动求导的工程实现——有了它,你不用手推公式,PyTorch 的 autograd 帮你算。loss.backward() 就是触发反向传播。

Q2. SGD、Momentum、Adam、AdamW 的区别?

查看答案

SGD 最朴素,沿梯度走一步;Momentum 加"惯性",累积历史梯度;Adam 给每个参数自适应学习率(结合一阶矩和二阶矩);AdamW 把权重衰减从梯度更新里拆开,是当前的默认选择。新手用 AdamW,论文里常用 SGD+Momentum。

Q3. 激活函数为什么不能用线性的?ReLU 比 Sigmoid 好在哪?

查看答案

线性激活再叠多少层还是线性,网络退化成一个大矩阵乘。ReLU = max(0,x),计算简单、不饱和(缓解梯度消失)、稀疏激活;缺点是"神经元死亡"(负区间梯度为 0)。变体有 LeakyReLU、ELU、GELU。

Q4. 损失函数怎么选?

查看答案

回归用 MSE / MAE;二分类用 BCEWithLogitsLoss;多分类用 CrossEntropyLoss(自带 Softmax);如果输出已经过了 Sigmoid/Softmax,用 BCELoss / NLLLoss。别在 CrossEntropyLoss 前再加一层 Softmax。

Q5. model.train() 和 model.eval() 有什么区别?

查看答案

Dropout 在 train 模式随机关神经元,eval 模式全保留;BatchNorm 在 train 模式用当前 batch 的均值方差,eval 模式用累计的滑动平均。推理时一定要 eval() + torch.no_grad()。