在计算机视觉领域,ResNet-18是一个经典的卷积神经网络架构,因其优秀的性能和相对轻量的结构而广受欢迎。本文将详细介绍如何使用自定义数据集训练ResNet-18模型的全过程,从数据准备到模型部署的完整流程。
ResNet-18作为残差网络的精简版本,通过引入跳跃连接(skip connection)解决了深层网络训练中的梯度消失问题。相比更深的ResNet变体,18层的结构在保持良好准确率的同时,对计算资源的需求更为友好,特别适合中小型数据集和资源有限的训练环境。
提示:虽然本文以ResNet-18为例,但所述方法同样适用于其他ResNet变体(如ResNet-34/50等),只需调整模型初始化部分即可。
一个规范的图像分类数据集应遵循以下目录结构:
code复制custom_dataset/
├── train/
│ ├── class1/
│ │ ├── img1.jpg
│ │ └── img2.jpg
│ └── class2/
│ ├── img1.jpg
│ └── img2.jpg
└── val/
├── class1/
└── class2/
这种结构明确区分了训练集和验证集,每个类别有独立的子目录。实践中建议保持:
为提高模型泛化能力,推荐使用以下增强组合:
python复制from torchvision import transforms
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=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])
])
注意:验证集只需进行确定性变换(如中心裁剪),不应包含随机增强操作。
PyTorch提供了预训练的ResNet-18模型:
python复制import torchvision.models as models
# 加载预训练模型(ImageNet权重)
model = models.resnet18(pretrained=True)
# 冻结所有卷积层参数
for param in model.parameters():
param.requires_grad = False
替换最后的全连接层以适应自定义类别数:
python复制num_classes = 10 # 根据实际类别数调整
model.fc = torch.nn.Linear(model.fc.in_features, num_classes)
# 仅训练分类头参数
optimizer = torch.optim.SGD(model.fc.parameters(), lr=0.001, momentum=0.9)
对于小数据集,建议:
完整训练代码框架:
python复制def train_model(model, criterion, optimizer, dataloaders, num_epochs=25):
for epoch in range(num_epochs):
# 训练阶段
model.train()
for inputs, labels in dataloaders['train']:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 验证阶段
model.eval()
with torch.no_grad():
for inputs, labels in dataloaders['val']:
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
# 计算准确率等指标
推荐组合使用以下策略:
python复制from torch.optim import lr_scheduler
# 每7个epoch衰减学习率为原来的0.1倍
exp_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)
# 或使用余弦退火
cos_lr_scheduler = lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)
除准确率外,还应关注:
python复制from sklearn.metrics import classification_report
def evaluate(model, dataloader):
all_preds = []
all_labels = []
with torch.no_grad():
for inputs, labels in dataloader:
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
print(classification_report(all_labels, all_preds))
# 可添加混淆矩阵等可视化
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 训练损失不下降 | 学习率过低/模型未解冻 | 检查参数requires_grad状态 |
| 验证准确率波动大 | 批量大小不合适 | 增大batch size或使用梯度累积 |
| 过拟合明显 | 数据量不足/增强不够 | 添加更多增强/正则化项 |
使用AMP加速训练:
python复制from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()
with autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
导出为ONNX格式:
python复制dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "resnet18.onnx",
input_names=["input"], output_names=["output"])
实际部署时建议:
类别不平衡处理:当某些类别样本过少时,可采用:
python复制weights = torch.tensor([1.0, 2.0]) # 第二类的权重加倍
criterion = torch.nn.CrossEntropyLoss(weight=weights)
学习率预热:前几个epoch逐步提高学习率:
python复制warmup_epochs = 5
for epoch in range(warmup_epochs):
lr = base_lr * (epoch + 1) / warmup_epochs
for param_group in optimizer.param_groups:
param_group['lr'] = lr
# 正常训练步骤...
模型剪枝:减小部署时的模型体积:
python复制from torch.nn.utils import prune
parameters_to_prune = [(module, 'weight') for module in model.modules()
if isinstance(module, torch.nn.Conv2d)]
prune.global_unstructured(parameters_to_prune, pruning_method=prune.L1Unstructured, amount=0.2)
训练过程中建议使用WandB或TensorBoard记录以下指标:
最后需要强调的是,不同数据集需要调整的超参数可能差异很大。建议从小学习率(如0.001)开始,通过观察前几个epoch的损失变化情况逐步调整。当验证准确率停滞时,可以尝试解冻更多卷积层进行微调,但要注意防止过拟合。