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()。