深度学习基础10/11:CNN
·
10、basic CNN
import torch
from torch import nn # 导入神经网络模块
from torchvision import transforms # 导入数据集模块
from torchvision import datasets # 导入数据集模块
from torch.utils.data import DataLoader # 导入数据加载器
import torch.nn.functional as F # 导入函数式接口(如ReLU、sigmoid等)
import torch.optim as optim # 导入优化器模块
batch_size = 64 # 设置批次大小,每次训练使用64个样本
# 定义数据预处理管道
transform = transforms.Compose([transforms.ToTensor(), # 将PIL图像或numpy数组转为Tensor,并归一化到[0,1]
transforms.Normalize((0.1307,), (0.3081,))]) # 标准化:(x-mean)/std
# 加载MNIST训练集
train_dataset = datasets.MNIST(root='../dataset/mnist/', # 数据集存储路径
train=True, # 使用训练集
transform=transform, # 应用上面定义的数据变换
download=True) # 如果本地没有则下载
# 创建训练数据加载器
train_loader = DataLoader(train_dataset, # 训练数据集
batch_size=batch_size, # 批次大小64
shuffle=True) # 每个epoch打乱数据顺序,防止过拟合
# 加载MNIST测试集
test_dataset = datasets.MNIST(root='../dataset/mnist/', # 同训练集路径
train=False, # 使用测试集
transform=transform, # 应用相同的变换
download=True) # 如果本地没有则下载
test_loader = DataLoader(test_dataset, # 测试数据集
batch_size=batch_size, # 批次大小64
shuffle=False) # 测试集不需要打乱,保持固定顺序
class Net(torch.nn.Module): # 继承nn.Module基类
def __init__(self):
super(Net, self).__init__() # 调用父类初始化
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5) # 卷积层1:输入1通道,输出10通道,5x5卷积核
self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5) # 卷积层2:输入10通道,输出20通道,5x5卷积
self.pooling = torch.nn.MaxPool2d(2) # 2x2最大池化层
self.fc = torch.nn.Linear(320, 10) # 全连接层:320维输入,10维输出(10个类别)
def forward(self, x): # 定义前向传播
batch_size = x.size(0) # 获取批次大小
# 卷积 → 池化 → ReLU激活
x = F.relu(self.pooling(self.conv1(x))) # 第一层:[batch,1,28,28] → [batch,10,12,12]
x = F.relu(self.pooling(self.conv2(x))) # 第二层:[batch,10,12,12] → [batch,20,4,4]
x = x.view(batch_size, -1) # 展平:[batch,20,4,4] → [batch,320]
x = self.fc(x) # 全连接层:[batch,320] → [batch,10]
return x
model = Net() # 实例化神经网络模型
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 设备选择:优先使用GPU,否则使用CPU
model.to(device) # 将模型移动到指定设备
criterion = torch.nn.CrossEntropyLoss() # 定义损失函数(适用于多分类问题,内部包含LogSoftmax)
optimizer = torch.optim.SGD(model.parameters(), # 需要优化的参数(模型所有权重和偏置)
lr=0.01, # 学习率
momentum=0.5) # 动量,加速收敛并减少震荡
def train(epoch):
running_loss = 0.0 # 累计损失,用于计算平均损失
# 遍历所有训练批次
for batch_idx, data in enumerate(train_loader, 0):
inputs, target = data # 解包:inputs是图像,target是标签
inputs, target = inputs.to(device), target.to(device) # 将数据移动到GPU/CPU
outputs = model(inputs) # 前向传播:输入→模型→输出
loss = criterion(outputs, target) # 计算损失:预测值 vs 真实值
optimizer.zero_grad() # 清空梯度缓存,防止梯度累积
loss.backward() # 反向传播:计算所有参数的梯度
optimizer.step() # 参数更新:根据梯度更新模型参数
running_loss += loss.item() # 累加当前批次损失
if batch_idx % 300 == 299: # 每300个批次打印一次平均损失
print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0 # 重置累计损失
def test():
correct = 0 # 正确预测的样本数
total = 0 # 总样本数
with torch.no_grad(): # 测试时不计算梯度,节省内存和计算
for data in test_loader: # 遍历所有测试批次
inputs, target = data # 解包
inputs, target = inputs.to(device), target.to(device) # 将数据移动到GPU/CPU
outputs = model(inputs) # 前向传播获取预测值
# torch.max返回两个值:最大值和最大值索引
# dim=1表示在类别维度(第1维)找最大值
# predicted是预测的类别索引(0-9)
_, predicted = torch.max(outputs.data, dim=1)
total += target.size(0) # 累加当前批次样本数
correct += (predicted == target).sum().item() # 累加正确预测数
# 计算并打印准确率
print('Accuracy of the test set: %d %%' % (100 * correct / total))
if __name__ == '__main__': # 主程序入口,防止多进程问题
for epoch in range(10): # 训练10个epoch
train(epoch) # 训练一个epoch
test() # 测试当前模型性能
11.1、Adcanced CNN
import torch
from torch import nn # 导入神经网络模块
from torchvision import transforms # 导入数据集模块
from torchvision import datasets # 导入数据集模块
from torch.utils.data import DataLoader # 导入数据加载器
import torch.nn.functional as F # 导入函数式接口(如ReLU、sigmoid等)
import torch.optim as optim # 导入优化器模块
batch_size = 64 # 设置批次大小,每次训练使用64个样本
# 定义数据预处理管道
transform = transforms.Compose([transforms.ToTensor(), # 将PIL图像或numpy数组转为Tensor,并归一化到[0,1]
transforms.Normalize((0.1307,), (0.3081,))]) # 标准化:(x-mean)/std
# 加载MNIST训练集
train_dataset = datasets.MNIST(root='../dataset/mnist/', # 数据集存储路径
train=True, # 使用训练集
transform=transform, # 应用上面定义的数据变换
download=True) # 如果本地没有则下载
# 创建训练数据加载器
train_loader = DataLoader(train_dataset, # 训练数据集
batch_size=batch_size, # 批次大小64
shuffle=True) # 每个epoch打乱数据顺序,防止过拟合
# 加载MNIST测试集
test_dataset = datasets.MNIST(root='../dataset/mnist/', # 同训练集路径
train=False, # 使用测试集
transform=transform, # 应用相同的变换
download=True) # 如果本地没有则下载
test_loader = DataLoader(test_dataset, # 测试数据集
batch_size=batch_size, # 批次大小64
shuffle=False) # 测试集不需要打乱,保持固定顺序
class InceptionA(nn.Module):
def __init__(self, in_channels):
super(InceptionA, self).__init__() # 调用父类nn.Module的初始化方法
# 1x1卷积分支,输入通道数为in_channels,输出通道数为16,卷积核大小为1x1。
self.branch1x1 = nn.Conv2d(in_channels, 16, kernel_size=1)
# 5x5卷积分支的第一层:1x1卷积,用于降维,减少计算量。输入通道数in_channels,输出通道数16。
self.branch5x5_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
# 5x5卷积分支的第二层:5x5卷积,输入通道数16(来自上一层输出),输出通道数24。
# padding=2:填充2圈0,使得5x5卷积后特征图尺寸不变(假设步长为1)。
self.branch5x5_2 = nn.Conv2d(16, 24, kernel_size=5, padding=2)
# 3x3卷积分支的第一层:1x1卷积,用于降维。输入通道数in_channels,输出通道数16。
self.branch3x3_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
# 3x3卷积分支的第二层:3x3卷积,输入通道数16,输出通道数24,padding=1保持尺寸不变。
self.branch3x3_2 = nn.Conv2d(16, 24, kernel_size=3, padding=1)
# 3x3卷积分支的第三层:3x3卷积,输入通道数24,输出通道数24,padding=1保持尺寸不变。
self.branch3x3_3 = nn.Conv2d(24, 24, kernel_size=3, padding=1)
# 池化分支后的1x1卷积,输入通道数in_channels,输出通道数24。
self.branch_pool = nn.Conv2d(in_channels, 24, kernel_size=1)
def forward(self, x):
# 1x1卷积分支的前向计算
branch1x1 = self.branch1x1(x)
# 5x5卷积分支:先经过1x1卷积,再经过5x5卷积
branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)
# 3x3卷积分支:先1x1卷积,再两个3x3卷积
branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)
# 平均池化:使用3x3的池化窗口,步长为1,填充为1,因此输出尺寸不变。
# 注意:这里使用函数式接口F.avg_pool2d,与nn.AvgPool2d效果相同,但无需在__init__中定义层。
branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)
# 将池化后的特征图通过1x1卷积,调整通道数
outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
# 将四个分支的输出在通道维度(dim=1)上拼接起来。
# 假设每个分支的输出形状为[N, C, H, W],拼接后通道数变为各分支通道数之和16 + 24 + 24 + 24 = 88。
# 空间尺寸不变:H×W不变
return torch.cat(outputs, 1)
class Net(nn.Module): # 继承nn.Module基类
def __init__(self):
super(Net, self).__init__() # 调用父类初始化
# 第一层卷积:从1通道(灰度)→10通道
# kernel_size=5, stride默认=1, padding默认=0
# 输出尺寸计算:(28-5+1)=24 → 输出:[batch, 10, 24, 24]
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
# 第二层卷积:88通道→20通道(信息压缩,筛选重要特征)
# 输出尺寸:(24-5+1)=20 → [batch, 20, 20, 20]
self.conv2 = nn.Conv2d(88, 20, kernel_size=5) # 卷积层2:输入10通道,输出20通道,5x5卷积
# 第一层Inception模块:10通道→88通道
# InceptionA输出通道=88,输入输出尺寸不变(24×24)
self.incep1 = InceptionA(in_channels=10)
# 第二层Inception模块:20通道→88通道
self.incep2 = InceptionA(in_channels=20)
# 最大池化层:2x2池化,stride默认=2
# 作用:下采样,减少计算量,增加感受野,提供平移不变性,输出尺寸减半
self.mp = nn.MaxPool2d(2)
# 全连接层:1408维输入→10维输出(10个类别)
# 输入:28x28 → conv1+pool: 28→24→12 → incep1: 12不变 → conv2+pool: 12→8→4 → incep2: 4不变
# 最终特征图:88个通道 × 4×4 = 88×16 = 1408
self.fc = nn.Linear(1408, 10)
def forward(self, x): # 定义前向传播
# 获取批次大小,用于后续view操作
in_size = x.size(0)
# 第一层:卷积conv1(28→24)→mp(stride=2,24→12)池化→ReLU激活
x = F.relu(self.mp(self.conv1(x)))
# 第一层:Inception模块,尺寸不变:[batch, 88, 12, 12]
x = self.incep1(x)
# 第二层:卷积conv1(12→8)→mp(stride=2,8→4)池化→ReLU激活
x = F.relu(self.mp(self.conv2(x)))
# 第二层:Inception模块,尺寸不变:[batch, 88, 4, 4】
x = self.incep2(x)
# 展平操作:将4D特征图展平为2D矩阵 [batch, 88×4×4] = [batch, 1408]
# -1表示自动计算该维度大小,保持总元素数不变
x = x.view(in_size, -1)
# 全连接层:1408维→10维 输出:[batch, 10]
# 交叉熵损失函数内部包含LogSoftmax,因此这里不需要额外Softmax
x = self.fc(x)
return x
model = Net() # 实例化神经网络模型
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 设备选择:优先使用GPU,否则使用CPU
model.to(device) # 将模型移动到指定设备
criterion = torch.nn.CrossEntropyLoss() # 定义损失函数(适用于多分类问题,内部包含LogSoftmax)
optimizer = torch.optim.SGD(model.parameters(), # 需要优化的参数(模型所有权重和偏置)
lr=0.01, # 学习率
momentum=0.5) # 动量,加速收敛并减少震荡
def train(epoch):
running_loss = 0.0 # 累计损失,用于计算平均损失
# 遍历所有训练批次
for batch_idx, data in enumerate(train_loader, 0):
inputs, target = data # 解包:inputs是图像,target是标签
inputs, target = inputs.to(device), target.to(device) # 将数据移动到GPU/CPU
outputs = model(inputs) # 前向传播:输入→模型→输出
loss = criterion(outputs, target) # 计算损失:预测值 vs 真实值
optimizer.zero_grad() # 清空梯度缓存,防止梯度累积
loss.backward() # 反向传播:计算所有参数的梯度
optimizer.step() # 参数更新:根据梯度更新模型参数
running_loss += loss.item() # 累加当前批次损失
if batch_idx % 300 == 299: # 每300个批次打印一次平均损失
print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0 # 重置累计损失
def test():
correct = 0 # 正确预测的样本数
total = 0 # 总样本数
with torch.no_grad(): # 测试时不计算梯度,节省内存和计算
for data in test_loader: # 遍历所有测试批次
inputs, target = data # 解包
inputs, target = inputs.to(device), target.to(device) # 将数据移动到GPU/CPU
outputs = model(inputs) # 前向传播获取预测值
# torch.max返回两个值:最大值和最大值索引
# dim=1表示在类别维度(第1维)找最大值
# predicted是预测的类别索引(0-9)
_, predicted = torch.max(outputs.data, dim=1)
total += target.size(0) # 累加当前批次样本数
correct += (predicted == target).sum().item() # 累加正确预测数
# 计算并打印准确率
print('Accuracy of the test set: %d %%' % (100 * correct / total))
if __name__ == '__main__': # 主程序入口,防止多进程问题
for epoch in range(10): # 训练10个epoch
train(epoch) # 训练一个epoch
test() # 测试当前模型性能
11.2、Simple Residual Network
import torch
from torch import nn # 导入神经网络模块
from torchvision import transforms # 导入数据集模块
from torchvision import datasets # 导入数据集模块
from torch.utils.data import DataLoader # 导入数据加载器
import torch.nn.functional as F # 导入函数式接口(如ReLU、sigmoid等)
import torch.optim as optim # 导入优化器模块
batch_size = 64 # 设置批次大小,每次训练使用64个样本
# 定义数据预处理管道
transform = transforms.Compose([transforms.ToTensor(), # 将PIL图像或numpy数组转为Tensor,并归一化到[0,1]
transforms.Normalize((0.1307,), (0.3081,))]) # 标准化:(x-mean)/std
# 加载MNIST训练集
train_dataset = datasets.MNIST(root='../dataset/mnist/', # 数据集存储路径
train=True, # 使用训练集
transform=transform, # 应用上面定义的数据变换
download=True) # 如果本地没有则下载
# 创建训练数据加载器
train_loader = DataLoader(train_dataset, # 训练数据集
batch_size=batch_size, # 批次大小64
shuffle=True) # 每个epoch打乱数据顺序,防止过拟合
# 加载MNIST测试集
test_dataset = datasets.MNIST(root='../dataset/mnist/', # 同训练集路径
train=False, # 使用测试集
transform=transform, # 应用相同的变换
download=True) # 如果本地没有则下载
test_loader = DataLoader(test_dataset, # 测试数据集
batch_size=batch_size, # 批次大小64
shuffle=False) # 测试集不需要打乱,保持固定顺序
class InceptionA(nn.Module):
def __init__(self, in_channels):
super(InceptionA, self).__init__() # 调用父类nn.Module的初始化方法
# 1x1卷积分支,输入通道数为in_channels,输出通道数为16,卷积核大小为1x1。
self.branch1x1 = nn.Conv2d(in_channels, 16, kernel_size=1)
# 5x5卷积分支的第一层:1x1卷积,用于降维,减少计算量。输入通道数in_channels,输出通道数16。
self.branch5x5_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
# 5x5卷积分支的第二层:5x5卷积,输入通道数16(来自上一层输出),输出通道数24。
# padding=2:填充2圈0,使得5x5卷积后特征图尺寸不变(假设步长为1)。
self.branch5x5_2 = nn.Conv2d(16, 24, kernel_size=5, padding=2)
# 3x3卷积分支的第一层:1x1卷积,用于降维。输入通道数in_channels,输出通道数16。
self.branch3x3_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
# 3x3卷积分支的第二层:3x3卷积,输入通道数16,输出通道数24,padding=1保持尺寸不变。
self.branch3x3_2 = nn.Conv2d(16, 24, kernel_size=3, padding=1)
# 3x3卷积分支的第三层:3x3卷积,输入通道数24,输出通道数24,padding=1保持尺寸不变。
self.branch3x3_3 = nn.Conv2d(24, 24, kernel_size=3, padding=1)
# 池化分支后的1x1卷积,输入通道数in_channels,输出通道数24。
self.branch_pool = nn.Conv2d(in_channels, 24, kernel_size=1)
def forward(self, x):
# 1x1卷积分支的前向计算
branch1x1 = self.branch1x1(x)
# 5x5卷积分支:先经过1x1卷积,再经过5x5卷积
branch5x5 = self.branch5x5_1(x)
branch5x5 = self.branch5x5_2(branch5x5)
# 3x3卷积分支:先1x1卷积,再两个3x3卷积
branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)
# 平均池化:使用3x3的池化窗口,步长为1,填充为1,因此输出尺寸不变。
# 注意:这里使用函数式接口F.avg_pool2d,与nn.AvgPool2d效果相同,但无需在__init__中定义层。
branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
branch_pool = self.branch_pool(branch_pool)
# 将池化后的特征图通过1x1卷积,调整通道数
outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
# 将四个分支的输出在通道维度(dim=1)上拼接起来。
# 假设每个分支的输出形状为[N, C, H, W],拼接后通道数变为各分支通道数之和16 + 24 + 24 + 24 = 88。
# 空间尺寸不变:H×W不变
return torch.cat(outputs, 1)
class Net(nn.Module): # 继承nn.Module基类
def __init__(self):
super(Net, self).__init__() # 调用父类初始化
# 第一层卷积:从1通道(灰度)→10通道
# kernel_size=5, stride默认=1, padding默认=0
# 输出尺寸计算:(28-5+1)=24 → 输出:[batch, 10, 24, 24]
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
# 第二层卷积:88通道→20通道(信息压缩,筛选重要特征)
# 输出尺寸:(24-5+1)=20 → [batch, 20, 20, 20]
self.conv2 = nn.Conv2d(88, 20, kernel_size=5) # 卷积层2:输入10通道,输出20通道,5x5卷积
# 第一层Inception模块:10通道→88通道
# InceptionA输出通道=88,输入输出尺寸不变(24×24)
self.incep1 = InceptionA(in_channels=10)
# 第二层Inception模块:20通道→88通道
self.incep2 = InceptionA(in_channels=20)
# 最大池化层:2x2池化,stride默认=2
# 作用:下采样,减少计算量,增加感受野,提供平移不变性,输出尺寸减半
self.mp = nn.MaxPool2d(2)
# 全连接层:1408维输入→10维输出(10个类别)
# 输入:28x28 → conv1+pool: 28→24→12 → incep1: 12不变 → conv2+pool: 12→8→4 → incep2: 4不变
# 最终特征图:88个通道 × 4×4 = 88×16 = 1408
self.fc = nn.Linear(1408, 10)
def forward(self, x): # 定义前向传播
# 获取批次大小,用于后续view操作
in_size = x.size(0)
# 第一层:卷积conv1(28→24)→mp(stride=2,24→12)池化→ReLU激活
x = F.relu(self.mp(self.conv1(x)))
# 第一层:Inception模块,尺寸不变:[batch, 88, 12, 12]
x = self.incep1(x)
# 第二层:卷积conv1(12→8)→mp(stride=2,8→4)池化→ReLU激活
x = F.relu(self.mp(self.conv2(x)))
# 第二层:Inception模块,尺寸不变:[batch, 88, 4, 4】
x = self.incep2(x)
# 展平操作:将4D特征图展平为2D矩阵 [batch, 88×4×4] = [batch, 1408]
# -1表示自动计算该维度大小,保持总元素数不变
x = x.view(in_size, -1)
# 全连接层:1408维→10维 输出:[batch, 10]
# 交叉熵损失函数内部包含LogSoftmax,因此这里不需要额外Softmax
x = self.fc(x)
return x
model = Net() # 实例化神经网络模型
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 设备选择:优先使用GPU,否则使用CPU
model.to(device) # 将模型移动到指定设备
criterion = torch.nn.CrossEntropyLoss() # 定义损失函数(适用于多分类问题,内部包含LogSoftmax)
optimizer = torch.optim.SGD(model.parameters(), # 需要优化的参数(模型所有权重和偏置)
lr=0.01, # 学习率
momentum=0.5) # 动量,加速收敛并减少震荡
def train(epoch):
running_loss = 0.0 # 累计损失,用于计算平均损失
# 遍历所有训练批次
for batch_idx, data in enumerate(train_loader, 0):
inputs, target = data # 解包:inputs是图像,target是标签
inputs, target = inputs.to(device), target.to(device) # 将数据移动到GPU/CPU
outputs = model(inputs) # 前向传播:输入→模型→输出
loss = criterion(outputs, target) # 计算损失:预测值 vs 真实值
optimizer.zero_grad() # 清空梯度缓存,防止梯度累积
loss.backward() # 反向传播:计算所有参数的梯度
optimizer.step() # 参数更新:根据梯度更新模型参数
running_loss += loss.item() # 累加当前批次损失
if batch_idx % 300 == 299: # 每300个批次打印一次平均损失
print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0 # 重置累计损失
def test():
correct = 0 # 正确预测的样本数
total = 0 # 总样本数
with torch.no_grad(): # 测试时不计算梯度,节省内存和计算
for data in test_loader: # 遍历所有测试批次
inputs, target = data # 解包
inputs, target = inputs.to(device), target.to(device) # 将数据移动到GPU/CPU
outputs = model(inputs) # 前向传播获取预测值
# torch.max返回两个值:最大值和最大值索引
# dim=1表示在类别维度(第1维)找最大值
# predicted是预测的类别索引(0-9)
_, predicted = torch.max(outputs.data, dim=1)
total += target.size(0) # 累加当前批次样本数
correct += (predicted == target).sum().item() # 累加正确预测数
# 计算并打印准确率
print('Accuracy of the test set: %d %%' % (100 * correct / total))
if __name__ == '__main__': # 主程序入口,防止多进程问题
for epoch in range(10): # 训练10个epoch
train(epoch) # 训练一个epoch
test() # 测试当前模型性能
更多推荐
所有评论(0)