一 代码实现
#导入必要的库
import torch
import torch.nn as nn #神经网络模块
import torch.optim as optim #优化器模块
import torch.nn.functional as F #函数式神经网络操作
from torchvision import datasets, transformer, models #计算机视觉相关模块
from torch.utils.data import DataLoader, Dataset #数据加载和处理
import numpy as np #数值计算
import matplotlib.pyplot as plt #数据可视化
import time #时间相关操作
import os #操作系统接口
from tqdm import tqdm #进度条显示
#定义AlexNet模型
class AlexNet(nn.Module):
/*
AlexNet 模型实现,
ImageNet Classification with deep convolutional Neural Networks
*/
def __init__(self, num_classes=1000, dropout=0.5):
/*
初始化AlexNet模型
参数:num_classes 分类的类别数量,ImageNet默认为1000类
dropout Dropout概率,用于防止过拟合
*/
#调用父类nn.Module的初始化方法
super(AlexNet, self).__init__()
#保存参数
self.num_classes = num_classes
#特征提取部分 卷积层
#第一层卷积层
self.conv2 = nn.Conv2d(
in_channels=3, #输入通道数(RGB图像为3通道)
out_channels=96, #输出通道数 卷积核数量
kernel_size=11, #卷积核大小 11x11
stride=4, #卷积步长,每次移动4个像素
padding=2, #边缘填充 保持特征图大小
)
#第一层激活函数 - ReLU
#ReLU函数 f(x) = max(0, x) 用于增加非线性
self.relu1 = nn.ReLU(inplace=True)
#第一层最大池化层
self.pool1 = nn.MaxPool2d(
kernel_size=3, #池化窗口大小3x3
stride=2, #池化步长
)
#第二层卷积层
self.conv2 = nn.Conv2d(
in_channels=96, #输入通道数,来自上一层输出
out_channels=256,#输出通道数
kernel_size=5, #卷积核大小 5x5
padding=2, #边缘填充
)
#第二层激活函数
self.relu2 = nn.ReLU(inplace=True)
#第二层最大池化层
self.pool2 = nn.MaxPool2d(
kernel_size=3, #池化窗口大小
stride=2, #池化步长
)
#第三层卷积层
self.conv3 = nn.Conv2d(
in_channels=256, #输入通道数
out_channels=384, #输出通道数
kernel_size=3, #卷积核大小3x3
padding=1,#边缘填充
)
#第三层激活函数
self.relu3 = nn.ReLU(inplace=True)
#第四层卷积层
self.conv4=nn.Conv2d(
in_channels=384, #输入通道数
out_channels=384,#输出通道数
kernel_size=3, #卷积核大小3x3
padding=1, #边缘填充
)
#第四层激活函数
self.relu4 = nn.ReLU(inplace=True)
#第五层卷积层
self.conv5=nn.Conv2d(
in_channels=384,#输入通道数
out_channels=256,#输出通道数
kernel_size=3, #卷积核大小 3x3
padding=1,#边缘填充
)
#第五层激活函数
self.relu5 = nn.ReLU(inplace=True)
#第五层最大池化层
self.pool5 = nn.MaxPool2d(
kernel_size=3, #池化窗口大小
stride=2, #池化步长
)
#分类部分(全连接层)
#Dropout 层-随机丢弃一部分神经元,防止过拟合
self.dropout = nn.Dropout(p = dropout)
#第一个全连接层 FC6
#计算输入尺寸,经过5层卷积核池化后,特征图大小为6x6
#输入特征数:256个通道 * 6 * 6 = 9216
self.fc6 = nn.Linear(
in_features=256 * 6 * 6, #输入特征维度
out_features=4096, #输出特征维度
)
#第一个全连接层的激活函数
self.relu6 = nn.ReLU(inplace=True)
#第二个全连接层FC7
self.fc7 = nn.Linear(
in_features=4096,#输入特征维度
out_features=4096,#输出特征维度
)
#第二个全连接层的激活函数
self.relu7 = nn.ReLU(inplace=True)
#第三个全连接层FC8 - 输出层
self.fc8 = nn.Linear(
in_features=4096, #输出特征维度
out_features=num_classes, #输出类别数
)
#初始化权重
self._initialize_weights()
#打印模型结构信息
print(f"AlexNet模型初始化完成,分类类别数: {num_classes}")
def _initialize_weights(self):
/*
初始化模型权重
使用Xavier初始化方法初始化卷积层和全连接的权重
*/
#遍历所有模块
for m in self.modules():
#对卷积层进行初始化
if isinstance(m, nn.Conv2d):
#使用Xavier均匀分布初始化权重
nn.init.xavier_uniform_(m.weight)
#如果有偏置项,初始化为0
if m.bias is not None:
nn.init.constant_(m.bias, 0)
#对全连接层进行初始化
elif isinstance(m, nn.Linear):
#使用Xavier均匀分布初始化权重
nn.init.xavier_uniform_(m.weight)
#偏置项初始化为0
nn.init.constant_(m.bias, 0)
print("模型权重初始化完成")
def forward(self, x):
/*
前向传播函数
参数:
x 输入张量,形状为batch_size, 3, 224, 224
返回:output 输出张量,形状为batch_size, num_classes
*/
#保存原始输入形状用于调试
batch_size = x.shape[0]
#卷积层部分
#第一层卷积 + ReLU + 池化
x = self.conv1(x) #输出形状,batch_size, 96, 55, 55
x = self.relu1(x) #保持形状不变
x = self.pool1(x) #输出形状 batch_size, 96, 27, 27
#第二层卷积 + ReLU + 池化
x = self.conv2(x) #输出形状,batch_size, 256, 27, 27
x = self.relu2(x) #保持形状不变
x = self.pool2(x) #输出形状 batch_size, 256, 13, 13
#第三层卷积 + ReLU
x = self.conv3(x) #输出形状 batch_size, 384, 13, 13
x = self.relu3(x) #保持形状不变
#第四层卷积 + ReLU
x = self.conv4(x) #输出形状batch_size, 384, 13, 13
x = self.relu4(x) #保持形状不变
#第五层卷积 + ReLU + 池化
x = self.conv5(x) #输出形状,batch_size, 256, 13, 13
x = self.relu5(x) #保持形状不变
x = self.pool5(x) #输出形状,batch_size, 256, 6, 6
#全连接层部分
#将特征图展平为一维向量
#将batch_size 256, 6 ,6展平为 batch_size, 256 * 6 * 6
x = x.view(batch_size, -1) #-1表示自动计算该维度大小
#第一个全连接层 + ReLU + Dropout
x = self.fc6(x) #输出形状, batch_size, 4096
x = self.relu6(x) #保持形状不变
x = self.dropout(x) #随机丢弃一部分神经元
#第二个全连接层 + ReLU + Dropout
x = self.fc7(x) #输出形状 batch_size, 4096
x = self.relu7(x) #保持形状不变
x = self.dropout(x) #随机丢弃一部分神经元
#第三个全连接层 输出层
x = self.fc8(x) #输出形状:batch_size, num_classes
#注意,不在这里应用softmax,因为PyTorch的CrossEntropyLoss内部会处理
reutrn x
def get_deature_maps(self, x, layer_index):
/*
获取制定层的特征图
参数:x 输入张量
layer_index 层索引
返回feature_map 特征图
*/
features = []
#第一层
x = self.conv1(x)
x = self.relu1(x)
if layer_index == 1:
return x
x = self.pool1(x)
#第二层
x = self.conv2(x)
x = self.relu2(x)
if layer_index == 2:
return x
x = self.pool2(x)
#第三层
x = self.conv3(x)
x = self.relu3(x)
if layer_index == 3:
return x
#第四层
x = self.conv4(x)
x = self.relu4(x)
if layer_index == 4:
return x
#第五层
x = self.conv5(x)
x = self.relu5(x)
if layer_index == 5:
return x
return None
#定义AlexNet的简化版本(适用于CIFAR-10等小数据集)
class AlexNetSmall(nn.Module):
/*
适用于小尺寸图像,如CIFAR-10的32x32 的AlexNet简化版本
修改了第一层卷积的参数以适应小图像
*/
def __init__(self, num_classes=10):
/*
初始化简化版AlexNet
参数:num_classes 分类类别数,CIFAR-10为10类
super(AlexNetSmall, self).__init__()
*/
#修改第一层卷积以适应32x32输入
self.features = nn.Sequential(
#第一层卷积,32x32x3 -> 32x32x64
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2), #32x32->16x16
#第二层卷积:16x16x64->16x16x192
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2), #16x16->8x8
#第三层卷积 8x8x192->8x8x384
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True)
#第四层卷积 8x8x384->8x8x256
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
#第五层卷积:8x8x256 -> 8x8x256
nn.Conv2d(256, 256, kernel_size, padding=1)
nn.ReLU(inplace=True)
nn.MaxPool2d(kernel_size=2, stride=2)
)
#分类器部分
self.classifier = nn.Sequential(
nn.Dropout(p=0.5),
nn.Linear(256 * 4 * 4, 4096), #4x4x256 ->4096
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Linear(4096, num_classes),
)
#初始化权重
self._initialize_weights()
def _initialize_weights(self):
/*初始化权重*/
for m in self.modules()
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.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
def forward(self, x):
/*前向传播*/
x = self.features(x) #卷积特征提取
x = x.view(x.size(0), -1)#展平
x = self.classifier(x) #分类
return x
#数据准备加载
def prepare_cifar10_data(batch_size=128, data_dir='./data'):
/*
准备CIFAR-10数据集
参数:
batch_size 批次大小
data_dir 数据存储目录
返回:
train_loader 训练数据加载器
test_loader 测试数据加载器
classes 类别名称列表
*/
#定义数据转换,数据增强和归一化
transformer_train = transforms.Compose([
transformer.RandomCrop(32, padding=4), #随机裁剪,数据增强
transformer.RandomHorizontalFlip(), #随机水平翻转,数据增强
transformer.ToTensor(), #转换为张量[0, 1]
transformer.Normalize( #归一化道[-1, 1]
mean=[0.485, 0.456, 0.406] #ImageNet均值
std=[0.229, 0.224, 0.225] #ImageNet标准差
)
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(
mean = [0.485, 0.456, 0.406],
std = [0.229, 0.224, 0.225]
)
])
#下载并加载CIFAR-10数据集
train_dataset = datasets.CIFAR10(
root=data_dir,
train=True,
download=True,
transform = transform_train
)
test_dataset = datasets.CIFAR10(
root=data_dir,
train=False,
download=True,
transform=transform_test
)
#创建数据加载器
train_loader = DataLoader(
train_dataset,
batch_size=batch_size,
shuffle=True, #训练时打乱数据
num_workers=2, #使用2个工作进程加载数据
pin_memory=True #使用锁页内存加速数据转移
)
test_loader = DataLoader(
test_dataset,
batch_size = batch_size,
shuffle = False, #测试时不打乱数据
num_workers=2,
pin_memory=True
)
#CIFAR-10类别名称
classes = ('plane', 'car', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
print(f"训练集大小: {len(train_dataset)}")
print(f"测试集大小: {len(test_dataset)}")
return train_lodaer, test_loader, classes
#训练函数
def train_model(model, train_laoder test_loader, device, epochs=50, lr=0.001)
/*
训练模型
参数:model 要训练的模型
train_loader 训练数据加载器
test_loader 测试数据加载器
device 训练设备
epochs 训练轮数
l学习率
返回:
model 训练好的模型
history 训练历史记录
*/
#将模型移动到指定设备
model = model.to(device)
#定义损失函数-交叉墒损失,适用于多分类问题
criterion = nn.CrossEntropyLoss()
#定义优化器-随机梯度下降SGD
optimizer = optim.SGD(
model.parameters(),
lr=lr,
momentum=0.9, #动量加速收敛
weight_decay=5e-4 #权重衰减(L2正则化,防止过拟合)
)
#定义学习率 调度器 -每30轮学习 率乘以0.1
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.)1
#记录训练历史
history={
'train_loss': [],
'train_acc': [],
'test_loss': [],
'test_acc': [],
'learning_rate': []
}
#训练循环
for epoch in range(epochs):
print(f"\nEpoch {epoch+1}/{epochs}")
#训练阶段
model.train() #设置模型为训练模式
train_loss=0.0
correct=0
total=0
#使用tqdm显示进度条
pbar = tqdm(train_loader, desc='训练')
for batch_idx, (inputs, targets) in enumerate(pbar):
#将数据移动到设备
inputs, targets = inputs.to(device), targets.to(device)
#清空梯度
optimizer.zero_grad()
#前向传播
outputs = model(inputs)
#计算损失
loss = criterion(outputs, targets)
#反向传播
loss.backward()
#更新参数
optimizer.step()
#统计训练信息
train_loss += loss.item()
_, predicted = outputs.max(1) #获取预测类别
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
#更新进度条
pbar.set_postfix({
'loss': train_loss/(batch_idx+1),
'acc': 100.*correct/total
})
#计算训练准确率
train_acc = 100 * correct / total
avg_train_loss = train_loss / len(train_loadaer)
#测试阶段
model.eval() 设置模型为评估模式
test_loss = 0.0
correct = 0
total = 0
#禁用梯度计算 节省内存和计算时间
with torch.no_grad():
for inputs, targets in test_loader:
inputs, targets = inputs.to(device), targets.to(device)
outputs = model(inputs)
loss = criterion(outputs, targets)
test_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
#计算测试准确率
test_acc = 100 * correct / total
avg_test_loss = test_loss / len(test_loader)
#更新学习率
scheduler.step()
current_lr = optimizer.param_groups[0]['lr']
#记录历史
history['train_loss'].append(avg_train_loss)
history['train_acc'].append(train_acc)
history['test_loss'].append(avg_test_loss)
history['test_acc'].append(test_acc)
history['learning_rate'].append(current_lr)
#打印结果
print(f"训练损失: {avg_train_loss:.4f}, 训练准确率: {train_acc:.2f}%")
print(f"测试损失: {avg_test_loss:.4f}, 测试准确率: {test_acc:.2f}%")
print(f"学习率: {current_lr:.6f}")
print("训练完成!")
return model, history
#可视化函数
def visualize_results(history, model_name="AlexNet")
/*
可视化训练结果
参数:
history 训练历史记录
model_name 模型名称
*/
fig, axe = plt.subplots(2, 2, figsize=(12, 8))
#绘制训练和预测损失
axes[0, 0].plot(history['train_loss'], label='训练损失', linewidth=2)
axes[0, 0].plot(history['test_loss'], label='测试损失', linewidth=2)
axes[0, 0].set_xlabel('Epoch')
axes[0, 0].set_ylabel('损失')
axes[0, 0].set_title(f'{model_name} - 训练和测试损失')
axes[0, 0].legend()
axes[0, 0].grid(True)
#绘制训练和测试准确率
axes[0, 1].plot(history['train_acc'], label='训练准确率', linewidth=2)
axes[0, 1].plot(history['test_acc'], label='测试准确率', linewidth=2)
axes[0, 1].set_xlabel('Epoch')
axes[0, 1].set_ylabel('准确率 (%)')
axes[0, 1].set_title(f'{model_name} - 训练和测试准确率')
axes[0, 1].legend()
axes[0, 1].grid(True)
#绘制学习率变化
axes[1, 0].plot(history['learning_rate'], 'r-', linewidth=2)
axes[1, 0].set_xlabel('Epoch')
axes[1, 0].set_ylabel('学习率')
axes[1, 0].set_title(f'{model_name} - 学习率变化')
axes[1, 0].grid(True)
#绘制训练和测试损失的对比
axes[1, 1].plot(history['train_loss'], alpha=0.7, label='训练损失')
axes[1, 1].plot(history['test_loss'], alpha=0.7, label='测试损失')
axes[1, 1].set_xlabel('Epoch')
axes[1, 1].set_ylabel('损失')
axes[1, 1].set_title(f'{model_name} - 损失对比 (对数坐标)')
axes[1, 1].set_yscale('log')
axes[1, 1].legend()
axes[1, 1].grid(True)
plt.tight_layout()
plt.show()
def visualize_feature_maps(model, test_loader, device, num_images=5):
/*
可视化卷积层的特征图
参数:
model:训练好的模型
test_loader: 测试数据加载器
device 设备
num_images: 要可视化的图像数量
*/
print("可视化特征图...")
#获取一批测试数据
data_iter = iter(test_loader)
images, labels = next(data_iter)
#只取前结构图像
images = images[:num_images].to(device)
#设置模型为评估模式
model.eval()
#创建图形
fig, axes = plt.subplots(num_images, 6, figsize=(15, num_images * 2.5))
#如果只有一张图像,调整axes维度
if num_images == 1:
axes = axes.reshape(1, -1)
for i in range(num_images):
#获取单张图像
img = images[i:i+1]
#显示原始图像
img_np = img.cpu().squeeze().numpy()
#反归一化
img_np = np.transpose(img_np, (1, 2, 0))
img_np = img_np * np.array([0.229, 0.224, 0.225]) + np.array([0.485, 0.456, 0.406])
img_np = np.clip(img_np, 0, 1)
axes[i, 0].imshow(img_np)
axes[i, 0].set_title(f"原始图像")
axes[i, 0].axis('off')
#获取并显示每一层的特征图
for layer_idx in range(1, )6
with torch.no_grad():
#获取特征图
feature_map = model.get_feature_maps(img, layer_idx)
#获取前几个通道的特征图
feature_map = feature_map.cpu().numpy()
#显示特征图
#提取3个通道或所有通道的平均值
if feature_map.shape[1] >= 3:
#取前三个通道
fm_show = feature_map[0, :3, :, :]
fm_show = np.transpose(fm_show, (1,2,0))
else:
#获取所有通道的平均值
fm_show = feature_map[0].mean(0)
axes[i, layer_idx].imshow(fm_show, cmap='viridis')
axes[i, layer_idx].set_title(f"卷积层 {layer_idx}")
axes[i, layer_idx].axis('off')
plt.suptitle("AlexNet特征图可视化")
plt.tight_layout()
plt.show()
#模型评估函数
def evaluate_model(model, test_loader, device, classes):
/*
评估模型能
参数:model 要评估的模型
test_loader 测试数据加载器
device 设备
classes :类别名称
返回:
overall_accuracy 总体准确率
class_accuracy 每个类别的准确率
confusion_matrix 混淆矩阵
*/
model.eval()
model = model.to(device)
#初始化统计变量
class_correct = list(0, for _in range(len(classes)))
class_total = list(0. for _in range(len(classes)))
#初始化混淆矩阵
confusion_matrix = np.zeros((len(classes), len(classes)), dtype=int)
#禁用梯度计算
with torch.no_grad():
for inputs, targets in test_loader:
inputs, targets = inputs.to(device), targets.to(device)
#前向传播
outputs = model(inputs)
_, predicted = outputs.max(1)
#同级每个类别的预测结果
for i in range(len(targets)):
label = targets[i]
pred = predicted[i]
confusion_matrix[label][pred] += 1
class_total[label] += 1
if label == pred:
class_correct[label] += 1
#计算总体准确率
overall_accuracy = 100 * sum(class_correct)/sum(class_total)
#计算每个类别的准确率
class_accuracy = []
for i in range(len(classes)):
if class_total[i] > 0:
acc = 100 * class_correct[i] / class_total[i]
else:
acc = 0.0
class_accuracy.append(acc)
# 打印结果
print(f"总体准确率: {overall_accuracy:.2f}%")
print("\n每个类别的准确率:")
for i in range(len(classes)):
print(f" {classes[i]}: {class_accuracy[i]:.2f}%")
return overall_accuracy, class_accuracy, confusion_matrix
#主函数
def main:
/*
主函数,训练和评估AlexNet模型
print("=" * 60)
print("AlexNet模型演示")
print("=" * 60)
*/
#设置随机种子,确保结果可重复
torch.manual_seed(42)
np.random.seed(42)
#检查是否有可用的GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")
#参数设置
batch_size = 128
epochs = 50
learning_rate = 0.001
num_classes = 10 #CIFAR-10有10个类别
#准确数据
print("\n1. 准备数据...")
train_loader, test_loader, classes = prepare_cifar10_data(batch_size)
# 2. 创建模型
print("\n2. 创建模型...")
# 使用简化版AlexNet(适用于CIFAR-10的小图像)
model = AlexNetSmall(num_classes=num_classes)
# 打印模型结构
print("\n模型结构:")
print(model)
二 模型逻辑
输入层
输入图像尺寸:227x227x3( RGB 图像)
卷积层
第一层 (C1):包含 96 个 11x11 的卷积核,步长为 4,没有填充(padding),输出尺寸为 55x55x96。
第二层 (C2):包含 256个5x5 的卷积核,步长为 1,使用 2 的填充,输出尺寸为 27x27x256。
第三层 (C3):包含 384个3x3 的卷积核,步长为 1,使用 1 的填充,输出尺寸为 13x13x384。
第四层 (C4):包含 384个3x3 的卷积核,步长为 1,使用 1 的填充,输出尺寸为 13x13x384。
第五层 (C5):包含 256个3x3 的卷积核,步长为 1,使用 1 的填充,输出尺寸为 13x13x256。
池化层
在第一层和第二层卷积层后,分别有一个最大池化层,池化窗口大小为 3x3,步长为 2,这样减少了特征图的尺寸,同时保留了重要特征。
第五层卷积层后也有一个最大池化层,同样使用 3x3 的窗口和步长 2。
全连接层
第一个全连接层 (FC6):将前一层的输出展平为一维向量,然后通过一个含有 4096 个神经元的全连接层。
第二个全连接层 (FC7):同样含有 4096 个神经元。
第三个全连接层 (FC8):输出层,含有 1000 个神经元,对应 ImageNet 数据集中的 1000 个类别。
激活函数
在每个卷积层和全连接层之后,使用 ReLU(Rectified Linear Unit)作为激活函数,ReLU 函数可以增加网络的非线性表达能力,同时也加速了训练过程。
特殊层
局部响应归一化 (Local Response Normalization, LRN):在网络的前面几层中使用,以增强特征的鲁棒性。LRN对每个位置上的特征进行标准化,使得响应较大的特征更加突出,类似于生物视觉系统中的侧抑制效应。
训练技巧
使用重叠的最大池化(Overlapping Pooling)来减少过拟合。
在全连接层中使用 Dropout 技术来进一步防止过拟合。
数据增强,包括随机裁剪和水平翻转,以及颜色抖动,以增加模型的泛化能力。
并行处理
AlexNet 最初是在两个 GPU 上训练的,网络被分割成两半,每半在单独的 GPU 上处理,然后在最后合并结果。
以上就是 AlexNet 的完整结构,它的设计和训练方法为后来的深度学习研究者提供了许多有价值的思路和实践指导。

被折叠的 条评论
为什么被折叠?



