深度学习Pytorch入门(1):手撕CIFAR 10影像分类
之前深度学习接触只是局限于下载yolo模型做目标检测,其实对于深度学习的内容一点也不是很清楚,包括python使用啥的,有点惭愧,最近得空,刷了下B站视频(小土堆,本节介绍完全仿该视频集),做一个阶段性的备份吧,算是最简单的入门:
后面再复现一些比较流行的网络结构练练手…
数据介绍
CIFAR 是32*32 10分类的数据集
官网:https://www.cs.toronto.edu/~kriz/cifar.html
实际不是从这里下的,直接在python里面下的,后面会说到
网络介绍和实现
比较简单,就是卷积 池化 拉直 线性连接 最后预测,网络实现,下面单独介绍:
0.新建模型.py
新建一个网络实现的.py,可以管理网络,单独拿出来
0.1 实现过程
0.1.1 引入头
import torch.nn as nn
import torch
0.1.2 定义网络
定义自己的网络,需要新建一个继承于nn.Module的类,重写def init(self):和def forward(self,x):即可,完整网络代码如下:
#继承父类写法
class CIFAR_Model(nn.Module):#重写__init__def __init__(self):super(CIFAR_Model, self).__init__()self.model1 = nn.Sequential(nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2),nn.MaxPool2d(kernel_size=2),nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2),nn.MaxPool2d(kernel_size=2),nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=2),nn.MaxPool2d(kernel_size=2),nn.Flatten(),nn.Linear(in_features=1024,out_features=64),nn.Linear(in_features=64,out_features=10))# 重写前向def forward(self,x):output = self.model1(x)return output
#写一个测试的例子
if __name__ == '__main__':lixiao = CIFAR_Model()input = torch.ones((64,3,32,32))output = lixiao(input)print(output)
详解用到的几个函数,直接组合以下这些层,就把网络窜起来了
- self.model1 = nn.Sequential()
定义一个model1,就是我们的模型,模型整体打包在Sequential中,每层用,隔开- nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2)
第一个卷积层(其他卷积层参数类型一致):
in_channels:输入是3通道
out_channels:输出是32通道
kernel_size:卷积核大小,图里面是55
stride:滑动窗口,步长是1
padding:边界填充2行列0数据,这是由于卷积核是55单数,输出和输入宽高一样,只能是填充2才能不变- nn.MaxPool2d(kernel_size=2)
池化层:最大池化
kernel_size:池化核大小 2- nn.Flatten()
拉直:将卷积核全部拉直,如上:64@4*4,拉直后为1024(64 * 4 * 4)个
nn.Linear(in_features=1024,out_features=64)
线性全连接层:
in_features:输入是1024
out_features:输出是64- def forward(self,x):
output = self.model1(x)
return output
重写前向,表示输入的影像x,经过网络model1,计算得到output,输出- 测试的例子(传入一些测试数组,看网络正常不):
lixiao = CIFAR_Model() 先实例化一个模型出来,用我的名字了
input = torch.ones((64,3,32,32)) 创建一个64 batch size 3 通道 3232的全是1的影像数据
output = lixiao(input) 影像输入网络
print(output) 打印输出结果 6410 64张影像,每张影像10种类别概率
代码逐行详解
1.导入头和函数
import torch #主要
import torchvision #图像相关
import torch.nn as nn #网络
from torch.utils.data import DataLoader #数据加载
from torch.utils.tensorboard import SummaryWriter #可视化日志
from CIFARModel import * #自己复现的网络,上面那个文件
import time #耗时统计
#学习率衰减器 好几种方法
from torch.optim.lr_scheduler import ExponentialLR #指数衰减
from torch.optim.lr_scheduler import ReduceLROnPlateau #动态衰减学习率
2.数据加载
2.1 下载数据,指定数据集
torchvision.datasets 数据集里面有很多内置的数据,我们直接使用CIFAR10
参数列表:
“./dataest” 表示下载到dataest文件夹
train=True/False 表示是否是训练集
transform=torchvision.transforms.ToTensor() 表示将数据读出来后,转换为tensor格式
download=True 没有下载的话,需要下载
train_data = torchvision.datasets.CIFAR10("./dataest",train=True,transform=torchvision.transforms.ToTensor(),download=True)
test_data = torchvision.datasets.CIFAR10("./dataest",train=False,transform=torchvision.transforms.ToTensor(),download=True)
2.1 加载数据
DataLoader
参数列表:
train_data/test_data 表对应的数据集
batch_size 批量读取的数据
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集的长度:{}".format(train_data_size))
print("测试数据集的长度:{}".format(test_data_size))
# 利用Dataloader来加载数据
train_dataloader = DataLoader(train_data,batch_size=64)
test_dataloader = DataLoader(test_data,batch_size=64)
2.2 加载模型+确认损失函数+优化器+学习率
- device 确认运行在cpu还是gpu
如果是GPU,则网络 影像 标签 都需要.to(device),CPU无所谓,但是为了代码一致,也可以to一下 - lixiao = CIFAR_Model()
实例化模型 - loss_fn = nn.CrossEntropyLoss()
分类模型,损失函数选择的是交叉熵 - optimizer = torch.optim.SGD(lixiao.parameters(),lr=learning_rate)
优化器选择的是SGD随机梯度下降法,学习率初始默认为0.01 - 学习率自适应方法,官方
指数衰减:学习率每次epoch下降
scheduler = ExponentialLR(optimizer,gamma=0.9) 指数类型,学习率指数下降
scheduler.step() 每次epoch后面调用一次,做一次学习率更新
动态衰减:如果损失不下降了或者提升了,会降低学习率继续学习
ReduceLROnPlateau(optimizer, ‘min’) 全参数介绍,可以看官网
scheduler.step(total_test_loss) 每次epoch后面调用一次,做一次学习率更新,需要传入损失值
注意:还是要指定一个合适的初始学习率的
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
........
........
........
# 创建网络模型 直接从模型文件里面加载
lixiao = CIFAR_Model()
#-------CUDA/CPU-------
#网络模型转换到cuda上面
lixiao = lixiao.to(device)# 损失函数
loss_fn = nn.CrossEntropyLoss()
#-------CUDA/CPU-------
loss_fn = loss_fn.to(device)#优化器
learning_rate = 0.01 #学习速率
optimizer = torch.optim.SGD(lixiao.parameters(),lr=learning_rate)
#学习率衰减器 官方
# scheduler = ExponentialLR(optimizer,gamma=0.9) #指数衰减
# scheduler = ReduceLROnPlateau(optimizer, 'min') #动态衰减 简写参数
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1,patience=2, verbose=True, threshold=0.0001, threshold_mode='rel',cooldown=0, min_lr=0, eps=1e-08)
2.3 定义一些超参数
不介绍了,下面注释很详细
#设置训练网络的一些参数
#训练次数
total_train_step = 0
#测试的次数
total_test_step = 0
#训练轮数
epoch = 200
#最小的损失,保存一次模型
min_loss = 9999
#添加 日志
writer = SummaryWriter("./logs_train")
#添加一个训练结束的标识 阈值
#如果损失小于0.001,且次数达到指定次数,退出,就提前跳出 分类可以用准确率来判断也行
loss_threshold = 0.01
novalid_num_threshold = 5
#记录上一次的损失
loss_last = 0.001
novalid_jsq = 0
2.4 开始训练
我直接贴上来源码,其实注释很详细,只做简单介绍
训练流程:
先确定有几个epoch,做最外层的循环
每次epoch
训练集
1 读取训练集,取出图像和标签,图像参与模型训练
2 模型输出和真实标签求损失
3 优化器梯度清零,损失做反向传播,优化器更新梯度
测试集
1 将模型置为验证状态、不进行梯度调优
2 读取测试集,取出图像和标签,参与验证
其他
1 学习率衰减
2 保存一下最新的模型和最好的模型
3 如果两次损失小于阈值(0.001等),且小于阈值的epoch次数达到阈值,提前跳出训练,表示损失基本不下降了
for i in range(epoch):print("---------第 {} 训练开始-----------".format(i+1))start_time = time.time()#取数据,训练开始lixiao.train() #训练状态 当有网络模型 Dropout 或者BatchNorm 等 对指定层有用 ,默认写上吧 或者看自己的网络结构for data in train_dataloader:#取标签和影像imgs,targets = data#-------CUDA/CPU-------imgs = imgs.to(device)targets = targets.to(device)#训练结果outputs = lixiao(imgs)#计算损失loss = loss_fn(outputs,targets)#优化器#1. 梯度清零optimizer.zero_grad()#2.反向传播loss.backward()#3.更新梯度optimizer.step()#每个batch 里面计数total_train_step += 1#每100次打印一次if total_train_step % 100 == 0:print("训练次数:{},loss:{}".format(total_train_step,loss.item())) #loss.item() tensor转真实的数据writer.add_scalar("train_loss",loss.item(),total_train_step)#训练完一轮后,看一下是否已经训练好了???在测试集上使用total_test_loss = 0# 整体正确的个数total_accuacy = 0lixiao.eval() # 验证状态 当有网络模型 Dropout 或者BatchNorm 等 对指定层有用,默认写上吧 或者看自己的网络结构with torch.no_grad(): #没有了梯度,不进行调优,直接测试for data in test_dataloader:imgs,targets = data#-------CUDA/CPU-------imgs = imgs.to(device)targets = targets.to(device)#前向推理outputs = lixiao(imgs)loss = loss_fn(outputs,targets) #loss是测试集每个轮次的损失total_test_loss += loss.item()#顺便测试下准确率 分类模型使用#概率输出 横向最大 和 实际测试集对比,求和,表示单次batch size 中有效个数accuacy = (outputs.argmax(1) == targets).sum()total_accuacy += accuacy#----------------------------------#---------动态学习率----------------scheduler.step(total_test_loss) #动态衰减学习率# 每次epoch 学习率衰减一次# scheduler.step() #指数衰减t_lr = optimizer.param_groups[0]['lr']print("loss={},epoch={}, lr={}".format(total_test_loss,i, t_lr))end_time = time.time()print("整体测试集上的loss:{}".format(total_test_loss))writer.add_scalar("test_loss", total_test_loss, total_test_step)print("整体测试集上面的准确率:{}".format(total_accuacy/test_data_size))writer.add_scalar("test_accuracy", total_accuacy/test_data_size, total_test_step)total_test_step += 1print("单次epoch耗时:{}".format(end_time - start_time))#保存模型#可以设置一些输出条件,比如只保留最好的和当前的,看实际需求# torch.save(lixiao,"./models/lixiao_{}_loss{}.pth".format(i,total_test_loss))torch.save(lixiao, "./models/last.pt")#以字典形保存,官方推荐#torch.save(lixiao.state_dict(),"./models/lixiao_{}.pth".format(i))print("模型已保存")# 更新最佳模型if min_loss>total_test_loss:min_loss = total_test_losstorch.save(lixiao, "./models/best.pt".format(i, total_test_loss))print("最佳模型已保存")# ----------------------------------# ---------判断是否直接跳出训练---------loss_dis = abs(loss_last - total_test_loss)print("损失差{},上一个{},当前{},计数器{}".format(loss_dis, loss_last, total_test_loss, novalid_jsq))# 覆盖一下损失loss_last = total_test_lossif loss_dis < loss_threshold:novalid_jsq += 1if novalid_jsq > novalid_num_threshold:#跳出循环,不训练了print("损失已经超过{}不再下降,提前结束训练".format(novalid_num_threshold))breakelse:#如果还在下降,把计数器清零novalid_jsq = 0
损失最开始是0.01,最后动态降到了1e-7
测试集1w张照片的分类准确度为69.54%
迭代停止时才40轮,我预设200轮,跳出了
2.5 开始验证
2.5.1 使用验证集测试
下面代码,直接从验证集里面取数据,取单张或者一个batch size 都行,看一下标签对比
test_data = torchvision.datasets.CIFAR10("./dataest",train=False,transform=torchvision.transforms.ToTensor(),download=True)# 利用Dataloader来加载数据
test_dataloader = DataLoader(test_data,batch_size=64)
# 实例化模型 加载模型参数
lixiao = CIFAR_Model()
lixiao = torch.load("./models/best.pt")
print(lixiao)
for data in test_dataloader:imgs, targets = dataprint(imgs.shape)# ---------------------取一个batch size的时候----------------------------outputs = lixiao(imgs) # 保持维度取数#----------------------取一张的时候----------------------------# index = 0 # 或者任意你想取的索引,范围是0到63# single_image_batch = x[index:index + 1] # 形状[1,3,32,32]# 如果你想要的是第1张(即人类意义上的第一张,也就是索引0),那么就是上面这样。#outputs = lixiao(imgs[0:1])#保持维度取数,取一张,解除注释label_batch = outputs.argmax(1)print(label_batch.shape)for label in range(len(label_batch)):print("预测标签:{},实际标签{}".format(label_batch[label],targets[label]))
2.5.2 自己找数据测试
流程就是:
实例化模型+加载模型+加载图片+图像转tensor结构+reshape(1,3,32,32)+模型输出概率+概率argmax 就是label
from PIL import Image
import torchvision
import torch
from CIFARModel import *# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
device = torch.device("cpu")
#加载模型
model = CIFAR_Model()
model = torch.load("./models/best_last.pt")
model = model.to(device)
print(model)# 打开影像
image_path = ".../dog.png"
image = Image.open(image_path)
print(image.size)# 转为tensor结构
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),torchvision.transforms.ToTensor()])
image = transform(image)
print(image.shape)
# 结构还不对,需要转4维
image = torch.reshape(image,(1,3,32,32))
print(image.shape)
#模型验证阶段model.eval()
with torch.no_grad():image = image.to(device)output = model(image)label = output.argmax(1)
print("label type id is {}".format(label.item()))
print(output)
输出label 是5,即数据集的第六类,就是狗狗,预测正确
最后把代码汇总,贴一下
模型.py
import torch.nn as nn
import torch#搭建神经网络
class CIFAR_Model(nn.Module):def __init__(self):super(CIFAR_Model, self).__init__()self.model1 = nn.Sequential(nn.Conv2d(in_channels=3,out_channels=32,kernel_size=5,stride=1,padding=2),nn.MaxPool2d(kernel_size=2),nn.Conv2d(in_channels=32,out_channels=32,kernel_size=5,stride=1,padding=2),nn.MaxPool2d(kernel_size=2),nn.Conv2d(in_channels=32,out_channels=64,kernel_size=5,stride=1,padding=2),nn.MaxPool2d(kernel_size=2),nn.Flatten(),nn.Linear(in_features=1024,out_features=64),nn.Linear(in_features=64,out_features=10))def forward(self,x):output = self.model1(x)return outputif __name__ == '__main__':lixiao = CIFAR_Model()input = torch.ones((64,3,32,32))output = lixiao(input)print(output)
训练.py
#----------------------------------------------------------
#-------------------CFIR10 GPU训练--------------------------
#----------------------------------------------------------
import torch
import torchvision
import torch.nn as nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from CIFARModel import *
import time
#学习率衰减器 好几种方法
from torch.optim.lr_scheduler import ExponentialLR #指数衰减
from torch.optim.lr_scheduler import ReduceLROnPlateau #动态衰减学习率
#定义一个设备device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")train_data = torchvision.datasets.CIFAR10("./dataest",train=True,transform=torchvision.transforms.ToTensor(),download=True)
test_data = torchvision.datasets.CIFAR10("./dataest",train=False,transform=torchvision.transforms.ToTensor(),download=True)train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集的长度:{}".format(train_data_size))
print("测试数据集的长度:{}".format(test_data_size))# 利用Dataloader来加载数据
train_dataloader = DataLoader(train_data,batch_size=64)
test_dataloader = DataLoader(test_data,batch_size=64)# 创建网络模型 直接从模型文件里面加载
lixiao = CIFAR_Model()
#-------CUDA/CPU-------
#网络模型转换到cuda上面
lixiao = lixiao.to(device)# 损失函数
loss_fn = nn.CrossEntropyLoss()
#-------CUDA/CPU-------
loss_fn = loss_fn.to(device)#优化器
learning_rate = 0.01 #学习速率
optimizer = torch.optim.SGD(lixiao.parameters(),lr=learning_rate)
#学习率衰减器 官方
# scheduler = ExponentialLR(optimizer,gamma=0.9) #指数衰减
# scheduler = ReduceLROnPlateau(optimizer, 'min') #动态衰减 简写参数
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1,patience=2, verbose=True, threshold=0.0001, threshold_mode='rel',cooldown=0, min_lr=0, eps=1e-08)#设置训练网络的一些参数
#训练次数
total_train_step = 0
#测试的次数
total_test_step = 0
#训练轮数
epoch = 200
#最小的损失,保存一次模型
min_loss = 9999
#添加tensorboard
writer = SummaryWriter("./logs_train")
#添加一个训练结束的标识 阈值
#如果损失小于0.001,且次数达到指定次数,退出,就提前跳出 分类可以用准确率来判断也行
loss_threshold = 0.01
novalid_num_threshold = 5
#记录上一次的损失
loss_last = 0.001
novalid_jsq = 0for i in range(epoch):print("---------第 {} 训练开始-----------".format(i+1))start_time = time.time()#取数据,训练开始lixiao.train() #训练状态 当有网络模型 Dropout 或者BatchNorm 等 对指定层有用 ,默认写上吧 或者看自己的网络结构for data in train_dataloader:#取标签和影像imgs,targets = data#-------CUDA/CPU-------imgs = imgs.to(device)targets = targets.to(device)#训练结果outputs = lixiao(imgs)#计算损失loss = loss_fn(outputs,targets)#优化器#1. 梯度清零optimizer.zero_grad()#2.反向传播loss.backward()#3.更新梯度optimizer.step()#每个batch 里面计数total_train_step += 1#每100次打印一次if total_train_step % 100 == 0:print("训练次数:{},loss:{}".format(total_train_step,loss.item())) #loss.item() tensor转真实的数据writer.add_scalar("train_loss",loss.item(),total_train_step)#训练完一轮后,看一下是否已经训练好了???在测试集上使用total_test_loss = 0# 整体正确的个数total_accuacy = 0lixiao.eval() # 验证状态 当有网络模型 Dropout 或者BatchNorm 等 对指定层有用,默认写上吧 或者看自己的网络结构with torch.no_grad(): #没有了梯度,不进行调优,直接测试for data in test_dataloader:imgs,targets = data#-------CUDA/CPU-------imgs = imgs.to(device)targets = targets.to(device)#前向推理outputs = lixiao(imgs)loss = loss_fn(outputs,targets) #loss是测试集每个轮次的损失total_test_loss += loss.item()#顺便测试下准确率 分类模型使用#概率输出 横向最大 和 实际测试集对比,求和,表示单次batch size 中有效个数accuacy = (outputs.argmax(1) == targets).sum()total_accuacy += accuacy#----------------------------------#---------动态学习率----------------scheduler.step(total_test_loss) #动态衰减学习率# 每次epoch 学习率衰减一次# scheduler.step() #指数衰减t_lr = optimizer.param_groups[0]['lr']print("loss={},epoch={}, lr={}".format(total_test_loss,i, t_lr))end_time = time.time()print("整体测试集上的loss:{}".format(total_test_loss))writer.add_scalar("test_loss", total_test_loss, total_test_step)print("整体测试集上面的准确率:{}".format(total_accuacy/test_data_size))writer.add_scalar("test_accuracy", total_accuacy/test_data_size, total_test_step)total_test_step += 1print("单次epoch耗时:{}".format(end_time - start_time))#保存模型#可以设置一些输出条件,比如只保留最好的和当前的,看实际需求# torch.save(lixiao,"./models/lixiao_{}_loss{}.pth".format(i,total_test_loss))torch.save(lixiao, "./models/last.pt")#以字典形保存,官方推荐#torch.save(lixiao.state_dict(),"./models/lixiao_{}.pth".format(i))print("模型已保存")# 更新最佳模型if min_loss>total_test_loss:min_loss = total_test_losstorch.save(lixiao, "./models/best.pt".format(i, total_test_loss))print("最佳模型已保存")# ----------------------------------# ---------判断是否直接跳出训练---------loss_dis = abs(loss_last - total_test_loss)print("损失差{},上一个{},当前{},计数器{}".format(loss_dis, loss_last, total_test_loss, novalid_jsq))# 覆盖一下损失loss_last = total_test_lossif loss_dis < loss_threshold:novalid_jsq += 1if novalid_jsq > novalid_num_threshold:#跳出循环,不训练了print("损失已经超过{}不再下降,提前结束训练".format(novalid_num_threshold))breakelse:#如果还在下降,把计数器清零novalid_jsq = 0
验证.py
from PIL import Image
import torchvision
import torch
from CIFARModel import *# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
device = torch.device("cpu")
#加载模型
model = CIFAR_Model()
model = torch.load("./models/best_last.pt")
model = model.to(device)
print(model)# 打开影像
image_path = "C:/Users/lixia/Desktop/dog.png"
image = Image.open(image_path)
print(image.size)# 转为tensor结构
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),torchvision.transforms.ToTensor()])
image = transform(image)
print(image.shape)
# 结构还不对,需要转4维
image = torch.reshape(image,(1,3,32,32))
print(image.shape)
#模型验证阶段model.eval()
with torch.no_grad():image = image.to(device)output = model(image)label = output.argmax(1)
print("label type id is {}".format(label.item()))print(output)