1. CNN基础概念与核心原理
1.1 卷积神经网络概述
卷积神经网络(CNN)作为深度学习领域的重要分支,在计算机视觉任务中展现出非凡的能力。我第一次接触CNN是在2016年参加ImageNet竞赛时,当时就被它处理图像数据的独特方式所震撼。与传统神经网络不同,CNN通过模拟人类视觉皮层的工作机制,实现了对图像特征的自动提取和学习。
CNN最显著的特点是它的层次化结构。就像我们人类观察物体时,会先识别边缘、轮廓等基础特征,再逐步组合成更复杂的形状和物体。CNN通过卷积层、池化层等组件的堆叠,实现了从低级特征到高级特征的逐层抽象。这种结构使得CNN在图像识别、目标检测等任务中表现出色。
1.2 CNN的三大核心机制
1.2.1 局部感知机制
在传统全连接网络中,每个神经元都与输入图像的所有像素相连。对于一张32×32的RGB图像,这意味着单个神经元就需要32×32×3=3072个权重参数!这种全连接方式不仅计算量大,还容易导致过拟合。
CNN通过局部感知野(Local Receptive Field)解决了这个问题。在我的实践中,通常使用3×3或5×5的小型卷积核,让每个神经元只"看"图像的局部区域。这种设计带来了三个显著优势:
- 参数数量大幅减少:一个3×3卷积核仅需9个参数(单通道情况下)
- 更关注局部特征:适合捕捉边缘、纹理等局部模式
- 保留了空间信息:不像全连接网络会破坏图像的二维结构
1.2.2 权值共享机制
权值共享是CNN的另一个精妙设计。在传统网络中,每个位置的神经元都有自己独立的权重。而CNN中,同一个卷积核会滑动扫描整个图像,在不同位置使用相同的权重。
举个例子,假设我们有一个检测垂直边缘的卷积核:
code复制[ 1, 0, -1 ]
[ 1, 0, -1 ]
[ 1, 0, -1 ]
这个卷积核会在图像的每个位置寻找垂直边缘,无论边缘出现在图像的哪个区域。这种设计带来了两个关键好处:
- 参数效率极高:一个3×3×3的卷积核(3通道)仅需27个参数,却能处理任意尺寸的输入图像
- 平移不变性:无论特征出现在图像的哪个位置,都能被同样检测到
1.2.3 池化机制
池化层(Pooling Layer)是CNN中用于降维的重要组件。在我处理CIFAR-10数据集时,最大池化(Max Pooling)是最常用的选择。它的工作原理很简单:在2×2的窗口内取最大值作为输出。
为什么需要池化层?从我的实践经验看,主要有三个原因:
- 降低计算复杂度:通过降维减少后续层的计算量
- 增强平移鲁棒性:小的位置变化不会影响池化结果
- 防止过拟合:相当于一种隐式的正则化
注意:池化层虽然有用,但在现代CNN架构中(如ResNet)有时会被带步长的卷积替代,以获得更好的性能。
1.3 CNN的基本网络结构
1.3.1 输入层设计
处理图像数据时,输入层的设计至关重要。以CIFAR-10为例,输入张量的形状为[batch_size, 3, 32, 32],其中:
- batch_size:一次处理的图像数量(如64)
- 3:RGB三个通道
- 32×32:图像的高度和宽度
在实际编码中,我经常使用PyTorch的DataLoader来组织输入数据:
python复制transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.DataLoader(trainset, batch_size=64, shuffle=True)
1.3.2 卷积层详解
卷积层是CNN的核心组件。在PyTorch中,我们使用nn.Conv2d来定义卷积层。一个典型的卷积层包含以下参数:
- in_channels:输入通道数(如前一层输出的特征图数量)
- out_channels:输出通道数(即卷积核数量)
- kernel_size:卷积核尺寸(如3或(3,3))
- stride:步长(默认为1)
- padding:边缘填充(保持空间尺寸)
在我的CIFAR-10分类模型中,第一层卷积通常这样定义:
python复制self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
这表示:输入3通道(RGB),输出32个特征图,使用3×3卷积核,边缘填充1像素以保持尺寸。
1.3.3 激活函数选择
ReLU(Rectified Linear Unit)是目前CNN中最常用的激活函数,定义为max(0,x)。相比传统的sigmoid或tanh函数,ReLU有几个明显优势:
- 计算简单:没有指数运算,速度更快
- 缓解梯度消失:正区间梯度恒为1
- 稀疏激活:负输入直接输出0
在PyTorch中实现非常简单:
python复制x = F.relu(self.conv1(x))
1.3.4 池化层实现
最大池化在PyTorch中通过nn.MaxPool2d实现。常见配置是2×2窗口,步长2:
python复制self.pool = nn.MaxPool2d(2, 2)
这会将特征图尺寸减半。例如,32×32的特征图经过池化后变为16×16。
1.3.5 全连接层设计
在卷积和池化层提取特征后,全连接层负责最终的分类。设计时需要特别注意:
- 输入尺寸:需要将特征图展平为一维向量
- 隐藏层大小:通常选择512或1024个神经元
- 输出层:神经元数量等于类别数(CIFAR-10为10)
实现示例:
python复制self.fc1 = nn.Linear(128 * 4 * 4, 512) # 假设前一层输出128个4×4特征图
self.fc2 = nn.Linear(512, 10)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. PyTorch实现CNN图像分类
2.1 项目环境配置
2.1.1 Python环境搭建
我推荐使用Anaconda创建独立的Python环境,避免包冲突。以下是创建环境的命令:
bash复制conda create -n cnn_pytorch python=3.8
conda activate cnn_pytorch
2.1.2 关键库安装
PyTorch的安装需要根据CUDA版本选择合适命令。对于没有GPU的用户:
bash复制pip install torch torchvision torchaudio
有NVIDIA GPU的用户建议安装CUDA版本以获得加速:
bash复制pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113
2.1.3 开发工具准备
我习惯使用Jupyter Notebook进行原型开发,再用PyCharm编写完整项目。其他有用工具包括:
- TensorBoard:可视化训练过程
- Weights & Biases:实验跟踪
- OpenCV:图像预处理
2.2 数据准备与增强
2.2.1 CIFAR-10数据集解析
CIFAR-10包含10类共60000张32×32彩色图像,每类6000张。官方划分为:
- 训练集:50000张
- 测试集:10000张
在PyTorch中加载非常简单:
python复制from torchvision import datasets
train_data = datasets.CIFAR10('data', train=True, download=True)
test_data = datasets.CIFAR10('data', train=False, download=True)
2.2.2 数据增强技巧
为了防止过拟合,我通常会添加以下数据增强:
python复制from torchvision import transforms
train_transform = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(10),
transforms.RandomAffine(0, shear=10),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))
])
这些变换的用途:
- RandomHorizontalFlip:水平翻转,简单有效的增强
- RandomRotation:小幅旋转增加鲁棒性
- RandomAffine:轻微形变增强泛化能力
- Normalize:标准化到零均值单位方差
2.2.3 数据加载优化
使用DataLoader可以高效加载数据:
python复制from torch.utils.data import DataLoader
train_loader = DataLoader(train_data, batch_size=64, shuffle=True, num_workers=4)
test_loader = DataLoader(test_data, batch_size=64, shuffle=False, num_workers=4)
关键参数:
- batch_size:根据GPU内存选择,通常64-256
- num_workers:并行加载进程数,建议设为CPU核心数
- pin_memory:GPU训练时应设为True加速数据传输
2.3 CNN模型实现
2.3.1 模型架构设计
基于CIFAR-10的CNN典型结构如下:
python复制import torch.nn as nn
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
self.conv3 = nn.Conv2d(64, 128, 3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(128 * 4 * 4, 512)
self.fc2 = nn.Linear(512, 10)
self.dropout = nn.Dropout(0.2)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = self.pool(F.relu(self.conv3(x)))
x = x.view(-1, 128 * 4 * 4)
x = self.dropout(F.relu(self.fc1(x)))
x = self.fc2(x)
return x
这个设计的特点是:
- 三层卷积,每层后接ReLU和池化
- 特征图数量逐层加倍(32→64→128)
- 全连接层前使用Dropout防止过拟合
2.3.2 模型初始化技巧
正确的初始化对训练至关重要。我通常使用He初始化:
python复制def init_weights(m):
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.xavier_normal_(m.weight)
nn.init.constant_(m.bias, 0)
model = CNN()
model.apply(init_weights)
2.3.3 模型可视化
使用torchsummary可以查看模型结构和参数数量:
python复制from torchsummary import summary
summary(model, (3, 32, 32))
输出示例:
code复制----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 32, 32, 32] 896
Conv2d-2 [-1, 64, 16, 16] 18,496
Conv2d-3 [-1, 128, 8, 8] 73,856
Linear-4 [-1, 512] 1,048,576
Linear-5 [-1, 10] 5,130
================================================================
Total params: 1,146,954
Trainable params: 1,146,954
Non-trainable params: 0
----------------------------------------------------------------
2.4 训练过程实现
2.4.1 损失函数选择
对于多分类问题,交叉熵损失是最佳选择:
python复制criterion = nn.CrossEntropyLoss()
它结合了Softmax和负对数似然,数值稳定性更好。
2.4.2 优化器配置
Adam优化器通常是不错的选择:
python复制optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
关键参数:
- lr:学习率,通常从1e-3开始尝试
- weight_decay:L2正则化强度
2.4.3 训练循环实现
完整的训练循环示例:
python复制def train(model, train_loader, criterion, optimizer, epoch):
model.train()
running_loss = 0.0
correct = 0
total = 0
for batch_idx, (inputs, targets) in enumerate(train_loader):
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
train_loss = running_loss / len(train_loader)
acc = 100. * correct / total
print(f'Train Epoch: {epoch} | Loss: {train_loss:.4f} | Acc: {acc:.2f}%')
return train_loss, acc
2.4.4 验证与测试
验证集评估代码:
python复制def validate(model, val_loader, criterion):
model.eval()
val_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for inputs, targets in val_loader:
inputs, targets = inputs.to(device), targets.to(device)
outputs = model(inputs)
loss = criterion(outputs, targets)
val_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
val_loss /= len(val_loader)
acc = 100. * correct / total
print(f'Validation: Loss: {val_loss:.4f} | Acc: {acc:.2f}%')
return val_loss, acc
2.5 高级训练技巧
2.5.1 学习率调度
使用ReduceLROnPlateau实现动态学习率调整:
python复制scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='max', factor=0.1, patience=3, verbose=True
)
# 在每个epoch后调用
scheduler.step(val_acc)
2.5.2 早停机制
防止过拟合的早停实现:
python复制class EarlyStopping:
def __init__(self, patience=5, delta=0):
self.patience = patience
self.delta = delta
self.counter = 0
self.best_score = None
self.early_stop = False
def __call__(self, val_loss):
score = -val_loss
if self.best_score is None:
self.best_score = score
elif score < self.best_score + self.delta:
self.counter += 1
if self.counter >= self.patience:
self.early_stop = True
else:
self.best_score = score
self.counter = 0
2.5.3 模型保存与加载
保存最佳模型:
python复制def save_checkpoint(state, filename='best_model.pth'):
torch.save(state, filename)
# 训练中调用
if val_acc > best_acc:
print('Saving best model...')
best_acc = val_acc
save_checkpoint({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'acc': best_acc,
})
加载保存的模型:
python复制def load_checkpoint(model, optimizer, filename):
checkpoint = torch.load(filename)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
return checkpoint['epoch'], checkpoint['acc']
3. 模型优化与调参实战
3.1 超参数调优策略
3.1.1 学习率实验
学习率是最关键的超参数之一。我通常会进行网格搜索:
python复制learning_rates = [0.1, 0.01, 0.001, 0.0001]
for lr in learning_rates:
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
# 训练并记录结果
从我的经验看,对于CIFAR-10:
- 0.1:通常太大,会导致震荡
- 0.01:可能收敛快但不稳定
- 0.001:比较安全的起点
- 0.0001:收敛太慢
3.1.2 批次大小影响
批次大小影响训练稳定性和内存使用。常见选择:
- 32:小批次,噪声大但泛化好
- 64:平衡选择
- 128/256:大批次,训练快但可能泛化差
我建议从64开始,根据GPU内存调整。
3.1.3 正则化技术
防止过拟合的几种方法:
- Dropout:通常0.2-0.5
python复制self.dropout = nn.Dropout(0.3) - L2正则化:
python复制optimizer = torch.optim.Adam(model.parameters(), weight_decay=1e-4) - 数据增强:如前所述的各种图像变换
3.2 模型架构改进
3.2.1 添加BatchNorm
批量归一化可以加速训练并提高性能:
python复制self.bn1 = nn.BatchNorm2d(32)
# 在前向传播中
x = self.pool(F.relu(self.bn1(self.conv1(x))))
3.2.2 残差连接
实现简单的残差块:
python复制class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super(ResidualBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride=stride, padding=1)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
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=stride),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += self.shortcut(x)
out = F.relu(out)
return out
3.2.3 深度可分离卷积
更高效的卷积方式:
python复制class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.depthwise = nn.Conv2d(in_channels, in_channels, 3, stride=stride, padding=1, groups=in_channels)
self.pointwise = nn.Conv2d(in_channels, out_channels, 1)
def forward(self, x):
x = self.depthwise(x)
x = self.pointwise(x)
return x
3.3 高级训练技巧
3.3.1 混合精度训练
使用Apex实现FP16训练:
python复制from apex import amp
model, optimizer = amp.initialize(model, optimizer, opt_level="O1")
# 在训练循环中
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
3.3.2 标签平滑
减轻过拟合:
python复制class LabelSmoothingLoss(nn.Module):
def __init__(self, classes, smoothing=0.1):
super(LabelSmoothingLoss, self).__init__()
self.confidence = 1.0 - smoothing
self.smoothing = smoothing
self.classes = classes
def forward(self, pred, target):
pred = pred.log_softmax(dim=-1)
with torch.no_grad():
true_dist = torch.zeros_like(pred)
true_dist.fill_(self.smoothing / (self.classes - 1))
true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
return torch.mean(torch.sum(-true_dist * pred, dim=-1))
3.3.3 知识蒸馏
使用教师模型指导学生模型:
python复制def distillation_loss(y, labels, teacher_outputs, T=2.0, alpha=0.5):
return nn.KLDivLoss()(F.log_softmax(y/T, dim=1),
F.softmax(teacher_outputs/T, dim=1)) * (T*T * alpha) + \
F.cross_entropy(y, labels) * (1. - alpha)
4. 模型评估与部署
4.1 性能评估指标
4.1.1 准确率与混淆矩阵
计算各类别准确率:
python复制from sklearn.metrics import confusion_matrix
def get_metrics(model, test_loader):
model.eval()
all_preds = []
all_targets = []
with torch.no_grad():
for inputs, targets in test_loader:
inputs = inputs.to(device)
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
all_preds.extend(preds.cpu().numpy())
all_targets.extend(targets.numpy())
cm = confusion_matrix(all_targets, all_preds)
class_acc = cm.diagonal() / cm.sum(axis=1)
return cm, class_acc
4.1.2 ROC曲线与AUC
对于多分类问题:
python复制from sklearn.metrics import roc_curve, auc
from sklearn.preprocessing import label_binarize
def plot_roc(model, test_loader, n_classes=10):
model.eval()
y_score = []
y_test = []
with torch.no_grad():
for inputs, targets in test_loader:
inputs = inputs.to(device)
outputs = model(inputs)
y_score.append(F.softmax(outputs, dim=1).cpu().numpy())
y_test.append(targets.numpy())
y_test = np.concatenate(y_test)
y_score = np.concatenate(y_score)
y_test = label_binarize(y_test, classes=range(n_classes))
fpr = dict()
tpr = dict()
roc_auc = dict()
for i in range(n_classes):
fpr[i], tpr[i], _ = roc_curve(y_test[:, i], y_score[:, i])
roc_auc[i] = auc(fpr[i], tpr[i])
# 绘制ROC曲线
plt.figure()
for i in range(n_classes):
plt.plot(fpr[i], tpr[i], label=f'Class {i} (AUC = {roc_auc[i]:.2f})')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()
plt.show()
4.2 模型部署实践
4.2.1 TorchScript导出
将模型转换为TorchScript:
python复制model.eval()
example_input = torch.rand(1, 3, 32, 32).to(device)
traced_script_module = torch.jit.trace(model, example_input)
traced_script_module.save("cifar10_model.pt")
4.2.2 ONNX格式导出
导出为ONNX格式:
python复制torch.onnx.export(model, example_input, "cifar10_model.onnx",
input_names=["input"], output_names=["output"],
dynamic_axes={"input": {0: "batch_size"},
"output": {0: "batch_size"}})
4.2.3 Flask Web服务
简单的Flask API:
python复制from flask import Flask, request, jsonify
import torch
from PIL import Image
import io
import numpy as np
app = Flask(__name__)
model = torch.jit.load("cifar10_model.pt")
model.eval()
classes = ["飞机", "汽车", "鸟类", "猫", "鹿", "狗", "青蛙", "马", "船", "卡车"]
@app.route('/predict', methods=['POST'])
def predict():
if 'file' not in request.files:
return jsonify({"error": "no file"})
file = request.files['file'].read()
image = Image.open(io.BytesIO(file)).convert('RGB')
image = image.resize((32, 32))
# 预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))
])
image = transform(image).unsqueeze(0)
# 预测
with torch.no_grad():
outputs = model(image)
_, pred = torch.max(outputs, 1)
return jsonify({"class": classes[pred.item()]})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
4.3 模型解释性分析
4.3.1 特征可视化
可视化卷积核:
python复制def visualize_filters(layer, n_filters=16):
filters = layer.weight.data.cpu().numpy()
plt.figure(figsize=(10, 10))
for i in range(n_filters):
plt.subplot(4, 4, i+1)
plt.imshow(filters[i, 0], cmap='gray')
plt.axis('off')
plt.show()
visualize_filters(model.conv1)
4.3.2 Grad-CAM可视化
实现Grad-CAM:
python复制class GradCAM:
def __init__(self, model, target_layer):
self.model = model
self.target_layer = target_layer
self.gradients = None
self.activations = None
target_layer.register_forward_hook(self.save_activations)
target_layer.register_backward_hook(self.save_gradients)
def save_activations(self, module, input, output):
self.activations = output
def save_gradients(self, module, grad_input, grad_output):
self.gradients = grad_output[0]
def __call__(self, x, class_idx=None):
self.model.zero_grad()
output = self.model(x)
if class_idx is None:
class_idx = torch.argmax(output)
one_hot = torch.zeros_like(output)
one_hot[0][class_idx] = 1
output.backward(gradient=one_hot)
pooled_gradients = torch.mean(self.gradients, dim=[0, 2, 3])
activations = self.activations.squeeze()
for i in range(activations.size(0)):
activations[i, :, :] *= pooled_gradients[i]
heatmap = torch.mean(activations, dim=0).detach().cpu()
heatmap = np.maximum(heatmap, 0)
heatmap /= torch.max(heatmap)
return heatmap.numpy()
5. 常见问题与解决方案
5.1 训练问题排查
5.1.1 损失不下降
可能原因及解决方案:
- 学习率太小:尝试增大学习率10倍
- 模型容量不足:增加层数或每层通道数
- 数据问题:检查输入数据是否正确
- 初始化问题:尝试不同的初始化方法
5.1.2 验证集性能差
过拟合解决方案:
- 增加数据增强
- 添加更多正则化(Dropout、L2)
- 减少模型复杂度
- 使用早停机制
5.1.3 训练不稳定
解决方法:
- 使用BatchNorm
- 减小学习率
- 梯度裁剪:
python复制torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
5.2 性能优化技巧
5.2.1 加速数据加载
使用DALI加速:
python复制from nvidia.dali import pipeline_def
import nvidia.dali.types as types
@pipeline_def
def create_pipeline():
images, labels = fn.readers.file(file_root="data", random_shuffle=True)
images = fn.decoders.image(images, device="mixed")
images = fn.resize(images, resize_x=32, resize_y=32)
images = fn.crop_mirror_normalize(
images,
mean=[0.4914*255, 0.4822*255, 0.4465*255],
std=[0.2470*255, 0.2435*255, 0.2616*255],
dtype=types.FLOAT
)
return images, labels
5.2.2 混合精度训练
使用自动混合精度:
python复制scaler = torch.cuda.amp.GradScaler()
for inputs, targets in train_loader:
optimizer.zero_grad()
with torch.cuda.amp.autocast():
outputs = model(inputs)
loss = criterion(outputs, targets)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
5.2.3 模型剪枝
实现简单剪枝:
python复制from torch.nn.utils import prune
parameters_to_prune = (
(model.conv1, 'weight'),
(model.conv2, 'weight'),
(model.fc1, 'weight'),
)
prune.global_unstructured(
parameters_to_prune,
pruning_method=prune.L1Unstructured,
amount=0.2,
)
5.3 模型调试技巧
5.3.1 激活值检查
检查激活值分布:
python复制def plot_activations(model, sample_input):
activations = {}
def get_activation(name):
def hook(model, input, output):
activations[name] = output.detach()
return hook
hooks = []
for name, layer in model.named_modules():
if isinstance(layer, nn.Conv2d) or isinstance(layer, nn.Linear):
hooks.append(layer.register_forward_hook(get_activation(name)))
model(sample_input)
for hook in hooks:
hook.remove()
plt.figure(figsize=(10, 6))
for i, (name, act) in enumerate(activations.items()):
plt.subplot(len(activations), 1, i+1)
plt.hist(act.cpu().numpy().flatten(), bins=50)
plt.title(name)
plt.tight_layout()
plt.show()
5.3.2 梯度检查
检查梯度流动:
python复制def plot_gradients(model, sample_input, sample_target):
model.zero_grad()
output = model(sample_input)
loss = criterion(output, sample_target)
loss.backward()
gradients = []
names = []
for name, param in model.named_parameters():
if param.grad is not None and "weight" in name:
gradients.append(param.grad.abs().mean().item())
names.append(name)
plt.figure(figsize=(10, 5))
plt.bar(names, gradients)
plt.xticks(rotation=90)
plt.ylabel("Average Gradient")
plt.title("Gradient Flow")
plt.show()
5.3.3 学习率查找
实现学习率范围测试:
python复制def find_lr(model, train_loader, optimizer, criterion, init_value=1e-8, end_value=10.0, beta=0.98):
num = len(train_loader)-1
mult = (end_value / init_value) ** (1/num)
lr = init_value
optimizer.param_groups[0]['lr'] = lr
avg_loss = 0.0
best_loss = 0.0
batch_num = 0
losses = []
log_lrs = []
for inputs, targets in train_loader:
batch_num += 1
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
avg_loss = beta * avg_loss + (1-beta) * loss.item()
smoothed_loss = avg_loss / (1 - beta**batch_num)
if batch_num > 1 and smoothed_loss > 4 * best_loss:
return log_lrs, losses
if smoothed_loss < best_loss or batch_num == 1:
best_loss = smoothed_loss
losses.append(smoothed_loss)
log_lrs.append(math.log10(lr))
loss.backward()
optimizer.step()
lr *= mult
optimizer.param_groups[0]['lr'] = lr
return log_lrs, losses
