J6第J6周:Inception v1算法实战与解析*
·
J6第J6周:Inception v1算法实战与解析
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
"""
Inception v1 (GoogLeNet) — 猴痘病识别 完整代码
==============================================
任务:
1. 了解卷积层运算量的计算
2. 了解卷积层的并行结构与1x1卷积核
3. 使用 Inception v1 完成猴痘病识别
数据目录:
D:/Adashujuxuexi/T4
Monkeypox/ ← 猴痘图片
Others/ ← 其他皮肤病图片
"""
# ============================================================
# 步骤1:导入必要的库
# ============================================================
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader, random_split
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime
# ============================================================
# 步骤2:定义 Inception Module(核心模块)
# ============================================================
class inception_block(nn.Module):
"""
Inception Module 四分支并行结构:
branch1: 1x1 conv -> 提取局部特征
branch2: 1x1 conv -> 3x3 conv -> 中等感受野
branch3: 1x1 conv -> 5x5 conv -> 大感受野
branch4: 3x3 maxpool -> 1x1 conv -> 池化后提取特征
关键:1x1 卷积核实现降维,减少通道数,降低计算量
"""
def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
super(inception_block, self).__init__()
self.branch1 = nn.Sequential(
nn.Conv2d(in_channels, ch1x1, kernel_size=1),
nn.BatchNorm2d(ch1x1),
nn.ReLU(inplace=True)
)
self.branch2 = nn.Sequential(
nn.Conv2d(in_channels, ch3x3red, kernel_size=1),
nn.BatchNorm2d(ch3x3red),
nn.ReLU(inplace=True),
nn.Conv2d(ch3x3red, ch3x3, kernel_size=3, padding=1),
nn.BatchNorm2d(ch3x3),
nn.ReLU(inplace=True)
)
self.branch3 = nn.Sequential(
nn.Conv2d(in_channels, ch5x5red, kernel_size=1),
nn.BatchNorm2d(ch5x5red),
nn.ReLU(inplace=True),
nn.Conv2d(ch5x5red, ch5x5, kernel_size=5, padding=2),
nn.BatchNorm2d(ch5x5),
nn.ReLU(inplace=True)
)
self.branch4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
nn.Conv2d(in_channels, pool_proj, kernel_size=1),
nn.BatchNorm2d(pool_proj),
nn.ReLU(inplace=True)
)
def forward(self, x):
return torch.cat([
self.branch1(x), self.branch2(x),
self.branch3(x), self.branch4(x)
], dim=1)
# ============================================================
# 步骤3:搭建 Inception v1 完整网络
# ============================================================
class InceptionV1(nn.Module):
def __init__(self, num_classes=2):
super(InceptionV1, self).__init__()
# Stem
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.conv2 = nn.Conv2d(64, 64, kernel_size=1)
self.conv3 = nn.Conv2d(64, 192, kernel_size=3, padding=1)
self.maxpool2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# Stage 3
self.inception3a = inception_block(192, 64, 96, 128, 16, 32, 32)
self.inception3b = inception_block(256, 128, 128, 192, 32, 96, 64)
self.maxpool3 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# Stage 4
self.inception4a = inception_block(480, 192, 96, 208, 16, 48, 64)
self.inception4b = inception_block(512, 160, 112, 224, 24, 64, 64)
self.inception4c = inception_block(512, 128, 128, 256, 24, 64, 64)
self.inception4d = inception_block(512, 112, 144, 288, 32, 64, 64)
self.inception4e = inception_block(528, 256, 160, 320, 32, 128, 128)
self.maxpool4 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# Stage 5
self.inception5a = inception_block(832, 256, 160, 320, 32, 128, 128)
self.inception5b = nn.Sequential(
inception_block(832, 384, 192, 384, 48, 128, 128),
nn.AvgPool2d(kernel_size=7, stride=1),
nn.Dropout(0.4)
)
# 分类器
self.classifier = nn.Sequential(
nn.Linear(1024, 1024),
nn.ReLU(),
nn.Linear(1024, num_classes),
)
def forward(self, x):
x = F.relu(self.conv1(x))
x = self.maxpool1(x)
x = F.relu(self.conv2(x))
x = F.relu(self.conv3(x))
x = self.maxpool2(x)
x = self.inception3a(x)
x = self.inception3b(x)
x = self.maxpool3(x)
x = self.inception4a(x)
x = self.inception4b(x)
x = self.inception4c(x)
x = self.inception4d(x)
x = self.inception4e(x)
x = self.maxpool4(x)
x = self.inception5a(x)
x = self.inception5b(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
# ============================================================
# 步骤4:数据预处理 & 加载
# ============================================================
class TransformSubset(torch.utils.data.Dataset):
"""对 Subset 应用不同的 transform"""
def __init__(self, dataset, indices, transform):
self.dataset = dataset
self.indices = indices
self.transform = transform
def __getitem__(self, idx):
from PIL import Image
img_path, label = self.dataset.samples[self.indices[idx]]
img = Image.open(img_path).convert('RGB')
if self.transform:
img = self.transform(img)
return img, label
def __len__(self):
return len(self.indices)
def get_dataloaders(data_dir, batch_size=8, img_size=224):
"""
自动处理两种目录结构:
1. 有 train/val 子目录 -> 直接用
2. 没有(Monkeypox/Others 直接在根目录)-> 自动 8:2 划分
"""
train_transform = transforms.Compose([
transforms.RandomResizedCrop(img_size),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
val_transform = transforms.Compose([
transforms.Resize(int(img_size * 256 / 224)),
transforms.CenterCrop(img_size),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
if os.path.isdir(os.path.join(data_dir, 'train')) and os.path.isdir(os.path.join(data_dir, 'val')):
train_dataset = datasets.ImageFolder(os.path.join(data_dir, 'train'), train_transform)
val_dataset = datasets.ImageFolder(os.path.join(data_dir, 'val'), val_transform)
classes = train_dataset.classes
else:
print("未找到 train/val 子目录,自动按 80:20 划分...")
full_dataset = datasets.ImageFolder(data_dir, transform=train_transform)
total = len(full_dataset)
train_size = int(total * 0.8)
val_size = total - train_size
train_dataset, val_dataset = random_split(full_dataset, [train_size, val_size])
val_dataset = TransformSubset(full_dataset, val_dataset.indices, val_transform)
classes = full_dataset.classes
use_pin = torch.cuda.is_available()
train_loader = DataLoader(train_dataset, batch_size=batch_size,
shuffle=True, num_workers=0, pin_memory=use_pin)
val_loader = DataLoader(val_dataset, batch_size=batch_size,
shuffle=False, num_workers=0, pin_memory=use_pin)
print(f"类别: {classes}")
print(f"训练集: {len(train_dataset)} 张 | 验证集: {len(val_dataset)} 张")
return train_loader, val_loader, classes
# ============================================================
# 步骤5:训练 & 验证
# ============================================================
def train_one_epoch(model, loader, criterion, optimizer, device):
model.train()
total_loss, correct, total = 0, 0, 0
for batch_idx, (images, labels) in enumerate(loader):
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item() * images.size(0)
_, preds = outputs.max(1)
correct += (preds == labels).sum().item()
total += labels.size(0)
print(f' Batch [{batch_idx+1}/{len(loader)}] Loss: {loss.item():.4f}', end='\r')
print()
return total_loss / total, correct / total
@torch.no_grad()
def validate(model, loader, criterion, device):
model.eval()
total_loss, correct, total = 0, 0, 0
for images, labels in loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
total_loss += loss.item() * images.size(0)
_, preds = outputs.max(1)
correct += (preds == labels).sum().item()
total += labels.size(0)
return total_loss / total, correct / total
def plot_curves(train_losses, val_losses, train_accs, val_accs):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.plot(train_losses, label='Train')
ax1.plot(val_losses, label='Val')
ax1.set_xlabel('Epoch')
ax1.set_ylabel('Loss')
ax1.set_title('Loss Curve')
ax1.legend()
ax2.plot(train_accs, label='Train')
ax2.plot(val_accs, label='Val')
ax2.set_xlabel('Epoch')
ax2.set_ylabel('Accuracy')
ax2.set_title('Accuracy Curve')
ax2.legend()
# 时间水印(右下角)
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
fig.text(0.99, 0.01, timestamp, ha='right', va='bottom',
fontsize=10, color='gray', alpha=0.7)
plt.tight_layout()
plt.savefig('training_curves.png', dpi=150)
plt.show()
print(f'训练曲线已保存 (时间水印: {timestamp})')
# ============================================================
# 步骤6:主函数
# ============================================================
def main():
# ---- 超参数 ----
data_dir = 'D:/Adashujuxuexi/T4'
num_classes = 2
batch_size = 8
epochs = 10
lr = 0.001
save_path = './best_inception_v1.pth'
# ---- 设备 ----
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'设备: {device}')
# ---- 数据 ----
train_loader, val_loader, classes = get_dataloaders(data_dir, batch_size)
# ---- 模型 ----
model = InceptionV1(num_classes=num_classes).to(device)
total_params = sum(p.numel() for p in model.parameters())
print(f'模型参数量: {total_params:,}')
# ---- 损失 & 优化器 ----
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
# ---- 训练循环 ----
train_losses, val_losses, train_accs, val_accs = [], [], [], []
best_acc = 0.0
for epoch in range(epochs):
print(f'\nEpoch [{epoch+1}/{epochs}] LR: {optimizer.param_groups[0]["lr"]:.6f}')
train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device)
val_loss, val_acc = validate(model, val_loader, criterion, device)
scheduler.step()
train_losses.append(train_loss)
val_losses.append(val_loss)
train_accs.append(train_acc)
val_accs.append(val_acc)
print(f' Train Loss: {train_loss:.4f} Acc: {train_acc:.4f}')
print(f' Val Loss: {val_loss:.4f} Acc: {val_acc:.4f}')
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), save_path)
print(f' ✓ 最佳模型已保存')
print(f'\n{"="*50}')
print(f'训练完成!最佳验证准确率: {best_acc:.4f}')
# ---- 绘制训练曲线(带时间水印)----
plot_curves(train_losses, val_losses, train_accs, val_accs)
if __name__ == '__main__':
main()
设备: cpu
未找到 train/val 子目录,自动按 80:20 划分...
类别: ['Monkeypox', 'Others']
训练集: 1713 张 | 验证集: 429 张
模型参数量: 7,039,122
Epoch [1/10] LR: 0.001000
Batch [215/215] Loss: 0.6285
Train Loss: 0.6930 Acc: 0.5855
Val Loss: 0.6705 Acc: 0.6107
✓ 最佳模型已保存
Epoch [2/10] LR: 0.001000
Batch [215/215] Loss: 0.5475
Train Loss: 0.6711 Acc: 0.6141
Val Loss: 0.6590 Acc: 0.5967
Epoch [3/10] LR: 0.001000
Batch [215/215] Loss: 0.4853
Train Loss: 0.6596 Acc: 0.6246
Val Loss: 0.6823 Acc: 0.5758
Epoch [4/10] LR: 0.001000
Batch [215/215] Loss: 0.8752
Train Loss: 0.6521 Acc: 0.6346
Val Loss: 0.6593 Acc: 0.6573
✓ 最佳模型已保存
Epoch [5/10] LR: 0.001000
Batch [215/215] Loss: 0.8479
Train Loss: 0.6499 Acc: 0.6427
Val Loss: 0.6608 Acc: 0.6317
Epoch [6/10] LR: 0.001000
Batch [215/215] Loss: 0.6347
Train Loss: 0.6498 Acc: 0.6439
Val Loss: 0.6402 Acc: 0.6527
Epoch [7/10] LR: 0.001000
Batch [215/215] Loss: 0.5337
Train Loss: 0.6485 Acc: 0.6381
Val Loss: 0.6515 Acc: 0.6410
Epoch [8/10] LR: 0.001000
Batch [215/215] Loss: 0.6512
Train Loss: 0.6607 Acc: 0.6229
Val Loss: 0.7119 Acc: 0.4802
Epoch [9/10] LR: 0.001000
Batch [215/215] Loss: 1.0085
Train Loss: 0.6741 Acc: 0.6013
Val Loss: 0.6331 Acc: 0.6643
✓ 最佳模型已保存
Epoch [10/10] LR: 0.001000
Batch [215/215] Loss: 0.5512
Train Loss: 0.6592 Acc: 0.6305
Val Loss: 0.6506 Acc: 0.6084
==================================================
训练完成!最佳验证准确率: 0.6643

训练曲线已保存 (时间水印: 2026-07-24 18:06:59)
本次基于 PyTorch 完成 Inception v1 网络实现猴痘皮肤病二分类任务,掌握了 Inception 模块四分支并行结构与 1×1 卷积降维减少计算量的核心原理。数据集共 2142 张图片,按 8:2 划分训练、验证集,模型参数量约 704 万,使用 Adam 优化器训练 10 轮。最终最佳验证准确率仅 66.43%,效果较差。训练过程出现验证准确率震荡、后期精度下滑,存在轻微过拟合问题。分析原因:仅简单数据增强、学习率调度策略单一、epoch 数量不足,且 CPU 训练限制调参效率。通过本次实操熟悉了图像分类完整流程,后续计划增加图像增强手段、调整学习率策略、引入预训练权重,进一步提升皮肤病识别精度。
更多推荐




所有评论(0)