文章目录
前言
最近在学习一些深度学习的相关知识,为了更好地弄清楚神经网络的搭建过程,记录学习到的一些代码,本文将以笔记的形式进行内容呈现。文章主要是为了方便自己日后学习,同时也希望可以为刚刚接触深度学习的小伙伴提供一些参考。
文章代码的来源为:Pytorch:手把手教你搭建简单的卷积神经网络(CNN),实现MNIST数据集分类任务
文章中对代码进行了一些改动,并对深度学习的相关知识以及所用相关代码进行较为详细的介绍。
一、MNIST数据集
MNIST数据集是一个手写数字识别数据集,由60,000个训练图像和10,000个测试图像组成。该数据集中的每个图像都是28x28像素的灰度图像,图像中是0~9数字,每个图片有其对应的标签。
该数据集部分图片数据如下:
二、使用步骤
1.基本库的导入和随机种子的设定
代码如下:
import numpy as np
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import time
np.random.seed(1234)
np.random.seed()函数:
1.利用随机数种子,每次生成的随机数相同。
具体解释:调用 np.random.randn() 生成随机数时,每一次生成的数都是随机的。但如果先使用 np.random.seed(x) 设定好种子(x 可以是任意数字),接着调用np.random.randn(),其生成的随机数将会是同一个。
2.MINIST数据集的下载、保存与加载
代码如下(示例):
# 引用MNIST数据集,这里用的是torchvision已经封装好的MINST数据集
trainset = torchvision.datasets.MNIST(
root='MNIST', # root是下载MNIST数据集保存的路径,可以自行修改
train=True,
transform=torchvision.transforms.ToTensor(),
download=True
)
testset = torchvision.datasets.MNIST(
root='MNIST',
train=False,
transform=torchvision.transforms.ToTensor(),
download=True
)
# 下载之后利用DataLoader实例化为适合遍历的训练集和测试集 batch_size一般设置较小的然后根据硬件条件逐渐增加
trainloader = DataLoader(dataset=trainset, batch_size=16,
shuffle=True) # DataLoader是一个很好地能够帮助整理数据集的类,可以用来分批次,打乱以及多线程等操作
testloader = DataLoader(dataset=testset, batch_size=16, shuffle=True)
torchvision.datasets.MNIST()参数介绍:
- root:下载数据的目录。
- train:为True表示下载的是训练集,为False表示下载的不是训练集。
- transform 函数是一个 PyTorch 转换操作,它可将图像转换为张量。
如果还想对转换的张量进行标准化可以参考下面的代码:图像转换为张量并对其进行标准化,其中均值为 0.1307,标准差为 0.3081。
标准化的作用:标准化每一张图像的尺度,从而更有利用训练模型
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
])
DataLoader()参数介绍:
- batch_size:每次迭代所使用的数据样本数。
- shuffle (bool): 是否在每个 epoch 之前打乱数据。如果设置为 True,则在每个 epoch 之前重新排列数据集以获得更好的训练效果。
关于batch_size的相关知识:
batch_size是一个很重要的超参数,它决定了模型在训练过程中一次处理的数据量大小
合适的batch_size对不仅对模型的训练效果至关重要,也对保护我们自己电脑至关重要,通常来说batch_size最开始一般设置为较小的值如16,32,如果硬件配置较好可以考虑设置为64,158,256。
详细的相关知识可以参考这篇博客batch_size详细介绍
注意:跑网上相关代码时,一定要先看看代码中batch_size的设置,如果电脑配置不行尽量先设置小一点,不然可能会强制关机,对电脑硬件设置也有一定的损伤。
可视化某一批图像数据
# 可视化某一批数据
train_img, train_label=next(iter(trainloader)) # iter迭代器,可以用来遍历trainloader里面每一个数据,这里只迭代一次来进行可视化
fig, axes = plt.subplots(4, 4, figsize=(10, 10)) #四行四列,16张图片
axes_list = []
# 输入到网络的图像
for i in range(axes.shape[0]):
for j in range(axes.shape[1]):
axes[i, j].imshow(train_img[i*4+j,0,:,:],cmap="gray") ## # 这里画出来的就是我们想输入到网络里训练的图像,与之对应的标签用来进行最后分类结果损失函数的计算
axes[i, j].axis("off")
# 对应的标签
plt.show() # 显示图像
print(train_label)
3.用pytorch搭建CNN
# 卷积模块,由卷积核和激活函数组成
class conv_block(nn.Module):
def __init__(self,ks,ch_in,ch_out):
super(conv_block,self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(ch_in, ch_out, kernel_size=ks,stride=1,padding=1,bias=True), # 二维卷积核,用于提取局部的图像信息
nn.ReLU(inplace=True), #这里用ReLU作为激活函数
nn.Conv2d(ch_out, ch_out, kernel_size=ks,stride=1,padding=1,bias=True),
nn.ReLU(inplace=True),
)
def forward(self,x):
return self.conv(x)
1.nn.Conv2d()函数:对由多个输入平面组成的输入信号进行二维卷积。具体可参考nn.Conv2d()函数详解,下面只针对上述代码做介绍:
1)ch_in:输入图像通道数。
2)ch_out:卷积产生的通道数。
3)kernel_size:卷积核尺寸。
4)stride:卷积步长。
5)padding:填充操作,padding为1表示在原始输入图像的二维矩阵周围拓展一圈。
6)bias:为真则在输出中添加一个可学习的偏差。默认为True。
2.nn.ReLU()函数:inplace为True还是False 都不会改变Relu后的结果。
inplace=True时可以利用in-place计算可以节省内(显)存,同时还可以省去反复申请和释放内存的时间。
CNN的主体部分:由卷积模块和全连接组成。
注意:可以根据自己电脑的性能调整卷积层、全连接层和神经元的个数
class CNN(nn.Module):
def __init__(self, kernel_size, in_ch, out_ch):
super(CNN, self).__init__()
feature_list = [16, 32, 64] # 代表每一层网络的特征数,扩大特征空间有助于挖掘更多的局部信息
self.conv1 = conv_block(kernel_size, in_ch, feature_list[0])
self.conv2 = conv_block(kernel_size, feature_list[0], feature_list[1])
self.conv3 = conv_block(kernel_size, feature_list[1], feature_list[2])
self.fc = nn.Sequential( # 全连接层主要用来进行分类,整合采集的局部信息以及全局信息
nn.Linear(feature_list[2] * 28 * 28, 512), # 此处28为MINST一张图片的维度
nn.ReLU(),
nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
def forward(self, x):
device = x.device
x1 = self.conv1(x)
x2 = self.conv2(x1)
x3 = self.conv3(x2)
x5 = x3.view(x3.size()[0], -1) # 全连接层相当于做了矩阵乘法,所以这里需要将维度降维来实现矩阵的运算
out = self.fc(x5)
return out
4.训练CNN并保存损失最小的模型
网络参数的定义:
# 网络参数定义,包括优化器,迭代轮数,学习率,运行硬件等等的确定
device = torch.device("cuda") #此处根据电脑配置进行选择,如果没有cuda就用cpu
#device = torch.device("cpu")
net = CNN(3,1,1).to(device = device,dtype = torch.float32)
epochs = 50 #训练轮次
optimizer = torch.optim.Adam(net.parameters(), lr=1e-4, weight_decay=1e-8) #使用Adam优化器
criterion = nn.CrossEntropyLoss() #分类任务常用的交叉熵损失函数
train_loss = []
optimizer = torch.optim.Adam(net.parameters(), lr=1e-4, weight_decay=1e-8)详解:
- model.parameters():返回模型中所有可训练参数的迭代器。Adam优化器将使用这些参数来更新模型的权重和偏差。
- lr:学习率(一个超参数)。表示每一次参数更新时的步长大小,需要进行调整以确保模型在训练过程中能够收敛到合适的解。
- weight_deca:权重衰减是一种正则化项,用于减少模型的过拟合风险。权重衰减会惩罚模型中较大的权重值,以鼓励模型学习简单的权重。
每一轮训练的主体部分:
# Begin training 每一轮训练的主体
MinTrainLoss = 999
for epoch in range(1, epochs + 1):
total_train_loss = [] # 存储当前epoch中每个batch的损失值
net.train() # 进入训练模式
start = time.time() # 记录当前时间
for input_img, label in trainloader:
input_img = input_img.to(device=device, dtype=torch.float32) # 将取出来的训练集数据进行torch能够运算的格式转换
label = label.to(device=device, dtype=torch.float32) # 输入和输出的格式都保持一致才能进行运算
optimizer.zero_grad() # 每一次算loss前需要将之前的梯度清零,这样才不会影响后面的更新
pred_img = net(input_img)
loss = criterion(pred_img, label.long()) # 采用交叉熵损失函数计算损失
loss.backward() # 反向传播
optimizer.step() # 优化器进行下一次迭代
total_train_loss.append(loss.item())
train_loss.append(np.mean(total_train_loss)) # 将一个minibatch里面的损失取平均作为这一轮的loss
end = time.time()
# 打印当前的loss
print(
"epochs[%3d/%3d] current loss: %.5f, time: %.3f" % (epoch, epochs, train_loss[-1], (end - start))) # 打印每一轮训练的结果
# train_loss[-1]表示获取train_loss的最后一个元素,即当前epoch的平均损失
if train_loss[-1] < MinTrainLoss:
torch.save(net.state_dict(), "./model_min_train.pth") # 保存loss最小的模型
MinTrainLoss = train_loss[-1]
4.测试训练好的CNN模型
首先可视化改批测试数据,然后进行测试和评估,输出预测结果和对应的标签。
# 导入网络模型,输入某一批测试数据,查看结果
# 可视化测试集某一批数据
test_img,test_label=next(iter(testloader))
fig, axes = plt.subplots(4, 4, figsize=(10, 10))##
axes_list = []
#输入到网络的图像
for i in range(axes.shape[0]):
for j in range(axes.shape[1]):
axes[i, j].imshow(test_img[i*4+j,0,:,:],cmap="gray")##
axes[i, j].axis("off")
# 预测我拿出来的那一批数据进行展示
cnn = CNN(3, 1, 1).to(device=device, dtype=torch.float32)
cnn.load_state_dict(torch.load("./model_min_train.pth", map_location=device)) # 导入我们之前已经训练好的模型
cnn.eval() # 评估模式
test_img = test_img.to(device=device, dtype=torch.float32)
test_label = test_label.to(device=device, dtype=torch.float32)
pred_test = cnn(test_img) # 记住,输出的结果是一个长度为10的tensor
test_pred = np.argmax(pred_test.cpu().data.numpy(), axis=1) # 所以我们需要对其进行最大值对应索引的处理,从而得到我们想要的预测结果
# 预测结果以及标签
print("预测结果")
print(test_pred)
print("标签")
print(test_label.cpu().data.numpy())
总结
下面是batch_size为64的一些结果图:
1.某一批训练集标签,64个标签对于64张图片
2.训练迭代过程:
3.某一批测试集的预测结果和其对应的正确标签:
文章大部分来源于网络,若有侵权,可联系作者删除。