环境搭建
关于cuda安装
网上已经有许多此方面的教程,这里只说几个关键点。
有一项不能勾选,需要安装vs才行
安装前先更新显卡驱动,就可以安装高版本cuda
显卡驱动不建议在geforce experience上安装,会安装失败(但是可以在这里看显卡型号挺方便)
官方驱动 | NVIDIA:在官网上搜索自己的显卡型号安装对应的显卡驱动
查看允许安装的cuda版本
nvidia-smi(中间没有空格,网上有的教程写了空格)可以查看当前驱动支持的最高cuda版本
nvcc -V(有空格)查看是否成功安装
关于torch和cuda兼容
官网安装torch
官网会根据cuda版本选择不同的安装语言,使用pip安装,建议先更新一下pip的版本
另外,cuda是向下兼容的,比如安装cuda12.0可以兼容cuda11.8(其他版本未尝试)我安装的是12.1,可以直接兼容最新版的pytorch,2.10
使用pip -V查看pip的位置,这里可以判断是安装在虚拟环境还是真实环境(anaconda可以创建虚拟环境)
代码部分
数据集导入
# 准备数据集
train_data = torchvision.datasets.CIFAR10(root='../data', train=True, transform=torchvision.transforms.ToTensor(),
download=False)
test_data = torchvision.datasets.CIFAR10(root='../data', train=False, transform=torchvision.transforms.ToTensor(),
download=False)
print("训练集的长度:{}".format(len(train_data)))
print("测试集的长度:{}".format(len(test_data)))
# DataLoader加载数据集,划分批次,降低内存占用,因为很多情况下无法将整个数据集一次性加载到内存中。
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
其中,root为已下载数据的存放位置(压缩包和解压文件),也可以将download改为True自动下载
DataLoader划分训练批次,每个批次64组数据
搭建神经网络
nn.BatchNorm2d
是PyTorch中的批归一化(Batch Normalization)层,用于在神经网络中增加模型的稳定性和加速训练过程。
nn.Dropout§
是一种正则化技术,用于减少神经网络的过拟合。在训练过程中,随机将网络中的一些神经元(p是丢弃的比重)的输出置为零(即丢弃)。这样可以防止网络过度依赖某些特定的神经元,有助于泛化。
# 搭建神经网络
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3,64,3,padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2,2),# 16*16
nn.Conv2d(64,128,3,padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2,2),# 8*8
nn.Conv2d(128, 256,3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2,2),# 4*4
nn.Flatten(),
nn.Linear(256*4*4,4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096,2048),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(2048,10)
)
def forward(self, x):
x = self.model(x)
return x
图像大小的变化
[参考链接](https://zhuanlan.zhihu.com/p/163017446#:~:text=众所周知,在定义卷积层的时候,我们一般会设置卷积核大小 (kernel_size),卷积步长 (stride),特征图填充宽度 (padding)等参数。,这些值的设置让卷积核可以从图片的第一个像素刚好扫描到最后一个像素,如下图所示 不难发现,经过卷积后的特征图尺寸等于卷积核滑动的次数 %2B 1,在这里等于 5。)
假设N为原图像尺寸,由于
N
+
2
∗
填充
=
(
o
u
t
−
1
)
∗
步长
+
卷积核大小
N+2*填充=(out-1)*步长+卷积核大小
N+2∗填充=(out−1)∗步长+卷积核大小
所以我们如果设置填充为下值,则卷积层输出的特征图像大小就等于原图像
p
a
d
d
i
n
g
=
(
N
−
1
)
∗
步长
−
N
+
卷积核大小
2
padding = \frac{(N-1)*步长-N+卷积核大小}{2}
padding=2(N−1)∗步长−N+卷积核大小
此时我们只要在池化层让特征图像变为原图像的一半大小,就可以轻松的随时知道卷积后特征图的尺寸了
对于卷积层:
H
o
u
t
=
高度
−
卷积核高度
+
2
∗
填充
步长
+
1
H_{out}=\frac{高度-卷积核高度+2*填充}{步长}+1
Hout=步长高度−卷积核高度+2∗填充+1
W o u t = 宽度 − 卷积核宽度 + 2 ∗ 填充 步长 + 1 W_{out}=\frac{宽度-卷积核宽度+2*填充}{步长}+1 Wout=步长宽度−卷积核宽度+2∗填充+1
对于池化层:
H
o
u
t
=
高度
−
池化核高度
步长
+
1
H_{out}=\frac{高度-池化核高度}{步长}+1
Hout=步长高度−池化核高度+1
W o u t = 宽度 − 池化核高度 步长 + 1 W_{out}=\frac{宽度-池化核高度}{步长}+1 Wout=步长宽度−池化核高度+1
nn.Flatten()
展平层,将输出转为一维向量,长度为
l
e
n
g
t
h
=
最后一层输出通道数
∗
特征图长
∗
特征图宽
length=最后一层输出通道数*特征图长*特征图宽
length=最后一层输出通道数∗特征图长∗特征图宽
nn.Linear(256*4*4,4096)
定义一个神经网络的线性层,输出是展平层得到的一维向量的长度。
损失函数和优化器
权重衰减 (weight_decay):
-
权重衰减是一种正则化技术,通过向损失函数添加一个项,惩罚较大的权重值,有助于防止过拟合。
-
具体来说,权重衰减项是在损失函数中添加的一个与权重值的平方成正比的项,乘以超参数
weight_decay
。 -
通常,
weight_decay
的值较小,例如5e-4。
# 损失函数
loss = nn.CrossEntropyLoss().cuda()
# 优化器
optimizer = torch.optim.Adam(model.parameters(),lr=0.0005,weight_decay=5e-4)
使用tensorboard可视化
每次运行程序前清空logs_tensorboard文件夹
# 添加tensorboard可视化数据
writer = SummaryWriter('logs_tensorboard')
writer.add_scalar('标题', y值, x值)
训练与测试
每个训练批次都经过数据提取、计算损失、优化这三个过程
# 数据分开 一个是图片数据,一个是真实值
imgs, targets = data
imgs = imgs.cuda()
targets = targets.cuda()
# 计算损失值
output = model(imgs)
loss_in = loss(output, targets)# 这里是本批次训练的损失函数值,本次训练的损失函数值是所有批次的和
# 优化:
optimizer.zero_grad()
# 反向传播+更新
loss_in.backward()
optimizer.step()
进行epoch次训练并计算正确率
for epoch in range(num_epoch):
for data in train_dataloader:
# 数据分开 一个是图片数据,一个是真实值
imgs, targets = data
imgs = imgs.cuda()
targets = targets.cuda()
# 计算损失值
output = model(imgs)
loss_in = loss(output, targets)# 这里是本批次训练的损失函数值,本次训练的损失函数值是所有批次的和
# 优化:
optimizer.zero_grad()
# 反向传播+更新
loss_in.backward()
optimizer.step()
#此处可以保存本次训练的损失值
writer.add_scalar('看一下训练集损失值', total_loss, epoch)
# 此处可以输出在测试集上的正确率(),也可以在模型训练好后单独计算。
accurate = 0
with torch.no_grad():#不用计算梯度
for data in test_dataloader:
# 每一次循环,都是一个训练批次,一次for循环里面有64个数据。
#同训练集,数据、损失,但是没有优化
accurate += (output.argmax(1) == targets).sum()#output.argmax(1)为预测的标签
writer.add_scalar('看一下当前测试集正确率', accurate / len(test_data) * 100, epoch+1)
# 保存模型,后续可以计算正确率,或单张图片可视化
torch.save(model, '文件位置\model_{}.pth'.format(epoch + 1))
中间层可视化
修改forward,传出各层输出
def forward(self, x):
layer_outputs = [] # 用于存储每一层的输出
for layer in self.model:
x = layer(x)
layer_outputs.append(x)
return layer_outputs
加载模型
# 这里要放之前写的模型,即class Model(nn.Module):
model = torch.load('model_10.pth')
传入待测试的图片
# 加载并预处理图像
image = Image.open('飞机.jpg')
# 这里使用了PIL库的convert方法,将图像转换为RGB模式。RGB表示红色、绿色和蓝色,是一种常见的图像表示方式,其中每个像素由这三个颜色通道的值组成。
image = image.convert('RGB')
# torchvision.transforms.Compose接受一个由转换函数组成的列表,并按照列表中的顺序依次应用这些转换。
# torchvision.transforms.Resize((32, 32)):这个转换将图像的大小调整为 (32, 32) 像素。
# torchvision.transforms.ToTensor():这个转换将PIL图像或numpy数组转换为PyTorch张量。PyTorch中的神经网络模型通常接受张量作为输入
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)),torchvision.transforms.ToTensor()])
image = transform(image)
进行前向传播并获取每一层的输出
with torch.no_grad():
image = image.cuda()
layer_outputs = model(image)
可视化每个通道的特征图
import matplotlib.pyplot as plt
import torchvision.transforms.functional as F
# 选择要可视化的层
layer_to_visualize = num
# 获取特定层的输出
feature_map = layer_outputs[layer_to_visualize].squeeze().cpu().numpy() # 使用.squeeze()去除维度为1的尺寸
# 可视化每个通道的特征图
num_channels = feature_map.shape[0]
plt.figure(figsize=(12, 8))
for i in range(num_channels):
#feature_map 是包含卷积层输出的 NumPy 数组,而 feature_map[i] 表示其中的第 i 个通道的特征图。
plt.imshow(feature_map[i], cmap='viridis')# cmap='viridis': 这是用于指定颜色映射的参数。
plt.axis('off')#Matplotlib将不显示坐标轴的刻度、标签和边框,即隐藏整个坐标轴。
# 保存每个通道的特征图到已有的目录中
save_path = f'文件路径\\feature_map_channel_{i}.png'
plt.savefig(save_path)
plt.clf() # 清空当前图形,以便绘制下一张
叠加所有特征图
import torchvision.transforms.functional as F
import numpy as np
# 获取特定层的输出
feature_map = layer_outputs[layer_to_visualize].squeeze().cpu().numpy()
# 叠加所有特征图
merged_feature_map = np.sum(feature_map, axis=0)
# 将值标准化到 0 到 1 的范围
min_value = np.min(merged_feature_map)
max_value = np.max(merged_feature_map)
merged_feature_map = (merged_feature_map - min_value) / (max_value - min_value)
# 将 numpy 数组转换为合适的数据类型
merged_feature_map = (merged_feature_map * 255).astype(np.uint8)
#astype(np.uint8) 是NumPy中的方法,用于将数组的数据类型转换为uint8,即无符号8位整数。在图像处理中,常常将像素值限定在0到255之间,而uint8正好可以表示这个范围的整数。
# 将 numpy 数组转换为 PIL 图像
merged_image = F.to_pil_image(merged_feature_map)
# 保存合并后的图像
merged_image.save(f'merged_feature_map{num}.png')
完整代码
训练并保存模型的代码
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 准备数据集
train_data = torchvision.datasets.CIFAR10(root='../data', train=True, transform=torchvision.transforms.ToTensor(),
download=False)
test_data = torchvision.datasets.CIFAR10(root='../data', train=False, transform=torchvision.transforms.ToTensor(),
download=False)
print("训练集的长度:{}".format(len(train_data)))
print("测试集的长度:{}".format(len(test_data)))
# DataLoader加载数据集,划分批次,降低内存占用,因为很多情况下无法将整个数据集一次性加载到内存中。
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 搭建神经网络
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3,64,3,padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2,2),# 16*16
nn.Conv2d(64,128,3,padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2,2),# 8*8
nn.Conv2d(128, 256,3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2,2),# 4*4
nn.Flatten(),
nn.Linear(256*4*4,4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096,2048),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(2048,10)
)
def forward(self, x):
x = self.model(x)
return x
# 创建网络模型
model = Model().cuda()
# 添加tensorboard可视化数据
writer = SummaryWriter('logs_tensorboard')
# 损失函数
loss = nn.CrossEntropyLoss().cuda()
# 优化器
optimizer = torch.optim.Adam(model.parameters(),lr=0.0005,weight_decay=5e-4)
i = 1 # 用于绘制测试集的tensorboard
# 开始循环训练
num_epoch = 30
for epoch in range(num_epoch):
total_loss = 0
num_time = 0 # 记录看看每轮有多少次训练
print('开始第{}轮训练'.format(epoch + 1))
model.train() # 也可以不写,规范的话是写,用来表明训练步骤
for data in train_dataloader:
# print("data:\n",data)
# 数据分开 一个是图片数据,一个是真实值
imgs, targets = data
# print("imgs:\n",imgs)
# print("targets:\n",targets)
imgs = imgs.cuda()
targets = targets.cuda()
# 预测值
output = model(imgs)
# 计算损失值
loss_in = loss(output, targets)
# 优化开始~ ~ 先梯度清零
optimizer.zero_grad()
# 反向传播+更新
loss_in.backward()
optimizer.step()
num_time += 1
total_loss += loss_in
writer.add_scalar('看一下训练集损失值', total_loss, epoch)
sum_loss = 0 # 记录总体损失值
# 每轮训练看一下测试集上的正确率
accurate = 0
with torch.no_grad():
for data in test_dataloader:
# 每一次循环,都是一个训练批次,一次for循环里面有64个数据。
imgs, targets = data
imgs = imgs.cuda()
targets = targets.cuda()
output = model(imgs)
loss_in = loss(output, targets)
sum_loss += loss_in
accurate += (output.argmax(1) == targets).sum()
print('第{}轮测试集的正确率:{:.2f}%'.format(epoch + 1, accurate / len(test_data) * 100))
writer.add_scalar('看一下测试集损失', sum_loss, i)
writer.add_scalar('看一下当前测试集正确率', accurate / len(test_data) * 100, i)
i += 1
torch.save(model, '..\model_pytorch\model_{}.pth'.format(epoch + 1))
print("第{}轮模型训练数据已保存".format(epoch + 1))
writer.close()
对单个图像做预测的代码
import torchvision
import torch
from PIL import Image
from torch import nn
image = Image.open('cat2.jpeg')
print(image)
image = image.convert('RGB')
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),
torchvision.transforms.ToTensor()])
image = transform(image)
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3,64,3,padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2,2),# 16*16
nn.Conv2d(64,128,3,padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2,2),# 8*8
nn.Conv2d(128, 256,3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2,2),# 4*4
nn.Flatten(),
nn.Linear(256*4*4,4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096,2048),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(2048,10)
)
def forward(self, x):
x = self.model(x)
return x
model = torch.load('../model_10.pth')
image = torch.reshape(image,(1,3,32,32))
print(image.shape)
model.eval()
with torch.no_grad():
image = image.cuda()
output = model(image)
print(output.argmax(1))
可视化中间层的代码(运行前需要修改图片保存的位置)
import numpy as np
import torchvision
import torch
from PIL import Image
from torch import nn
# 加载并预处理图像
# image = Image.open('cat.jpeg')
image = Image.open('飞机.jpg')
image = image.convert('RGB')
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)),
torchvision.transforms.ToTensor()])
image = transform(image)
# 创建你的模型实例
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3,64,3,padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2,2),# 16*16
nn.Conv2d(64,128,3,padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2,2),# 8*8
nn.Conv2d(128, 256,3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2,2),# 4*4
nn.Flatten(),
nn.Linear(256*4*4,4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096,2048),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(2048,10)
)
def forward(self, x):
layer_outputs = [] # 用于存储每一层的输出
for layer in self.model:
x = layer(x)
layer_outputs.append(x)
return layer_outputs
model = torch.load('model_10.pth')
# 设置模型为评估模式
model.eval()
# 调整图像形状以匹配期望的输入形状
image = torch.reshape(image, (1, 3, 32, 32))
# 进行前向传播并获取每一层的输出
with torch.no_grad():
image = image.cuda() if torch.cuda.is_available() else image # 检查是否有 GPU
layer_outputs = model(image)
# 打印每一层的输出形状
for i, output in enumerate(layer_outputs):
if(i==1):
print(f"Layer {i+1} output shape:", output)
def mydraw(num):
import matplotlib.pyplot as plt
import torchvision.transforms.functional as F
# 选择要可视化的层
layer_to_visualize = num
# 获取特定层的输出
feature_map = layer_outputs[layer_to_visualize].squeeze().cpu().numpy() # 使用.squeeze()去除维度为1的尺寸
# 可视化每个通道的特征图
num_channels = feature_map.shape[0]
plt.figure(figsize=(12, 8))
for i in range(num_channels):
plt.imshow(feature_map[i], cmap='viridis')
plt.axis('off')
# 保存每个通道的特征图到已有的目录中
save_path = f'E:\\pythonProjects\\机器学习\\深度卷积神经网络进行图像分类识别\\对单个图片的可视化\\picture\\{num}\\feature_map_channel_{i}.png'
plt.savefig(save_path)
plt.clf() # 清空当前图形,以便绘制下一张
# plt.show()
import torchvision.transforms.functional as F
import numpy as np
# 获取特定层的输出
feature_map = layer_outputs[layer_to_visualize].squeeze().cpu().numpy()
# 叠加所有特征图
merged_feature_map = np.sum(feature_map, axis=0)
# 将值标准化到 0 到 1 的范围
min_value = np.min(merged_feature_map)
max_value = np.max(merged_feature_map)
merged_feature_map = (merged_feature_map - min_value) / (max_value - min_value)
# 将 numpy 数组转换为合适的数据类型
merged_feature_map = (merged_feature_map * 255).astype(np.uint8)
# 将 numpy 数组转换为 PIL 图像
merged_image = F.to_pil_image(merged_feature_map)
# 保存合并后的图像
merged_image.save(f'merged_feature_map{num}.png')
for i in range(6):
mydraw(i)