深度学习-基于Pytorch 的cifar-10_AlexNet 实践
ztj100 2024-10-31 16:14 31 浏览 0 评论
我是一个初学者,将学习《动手学习深度学习》课程的内容记录如下:
主要内容参考原文,也加了一些查阅资料。
cifar-10 是一个典型的图像分类问题,
首先进入数据训练模型阶段,我学习的时候将相关学习都写着注释里面,方便看,具体代码如下:
# -*- coding: utf-8 -*-
# @Time : 2021/12/7 9:43
# @Author :
# @Email :
# @File : cifar-10_alexNet.py
import numpy as np
import torch
import torchvision
import torchvision.transforms as transforms
# torchvision,实现了常用的一些深度学习的相关的图像数据的加载功能,
# 比如cifar10、Imagenet、Mnist等等的,保存在torchvision.datasets模块中
import torch.utils.data as Data
from torch import nn
from torch.nn import functional as F
import torch.optim as optim
from torch.autograd import Variable
from torchsummary import summary
import os
import time
import matplotlib.pyplot as plt
# 定义函数用于查看部分数据
def imshow(img,title = None):
#normalized x = (x - mean) / std
img = img * 0.225 + 0.45 #unnormalized dataloader 时做了 normalized
npimg = img.numpy() # 将 torch.FloatTensor 转换为 numpy的格式 *255
#npimg = npimg.astype(np.uint8)
# plt.imshow(np.transpose(npimg, (1, 2,0)))。
# 因为在plt.imshow在现实的时候输入的是(imagesize,imagesize,channels),
# 而def imshow(img,text,should_save=False)中,参数img的格式为(channels,imagesize,imagesize),
# 这两者的格式不一致,我们需要调用一次np.transpose函数,
plt.imshow(np.transpose(img,(1,2,0)))
plt.axis('off')
#plt.imshow(img) #此处有可能有警告,或者报错,原因就是归一化的时候和显示还原不一致。
if title is not None:
plt.title(title)
# 构建网络,继承nn.Module
class CNNNet(nn.Module):
def __init__(self):
super(CNNNet, self).__init__()
# 添加第一个卷积层
# Conv2d(1,20,5)表示,输入是1通道的图像,输出是20通道,也就是20个卷积核,卷积核尺寸是5*5,其余参数默认值
self.conv1 = nn.Conv2d(in_channels=3,out_channels=16,kernel_size=5,stride=1)
# MaxPool2d(2,2)
self.pool1 = nn.MaxPool2d(kernel_size=2,stride=2)
self.conv2 = nn.Conv2d(in_channels=16,out_channels=36,kernel_size=3,stride=1)
self.pool2 = nn.MaxPool2d(kernel_size=2,stride=2)
# 接着定义全连接层
self.fc1 = nn.Linear(1296,128)
self.fc2 = nn.Linear(128,10)
# 定义前向传播函数
def forward(self,x):
x = self.pool1(F.relu(self.conv1(x)))
x = self.pool2(F.relu(self.conv2(x)))
# print(x.shape) 应该是:torch.Size([4, 36, 6, 6])
# .view() 改变tensor size,但是元素总数不变
# -1 表示:这个参数由另一个元素确定,比如矩阵元素总数确定的情况下,列数确定了之后,行数也可以确定
# 全连接层 就是矩阵的乘法,保证2个矩阵可以相乘,所以把X 调整到正确的size
x = x.view(-1,36*6*6)
x = F.relu(self.fc2(F.relu(self.fc1(x))))
return x
# 定义AlexNet
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.Conv = nn.Sequential(
# IN : 3*32*32
nn.Conv2d(in_channels=3, out_channels=96, kernel_size=5, stride=2, padding=2),
# 论文中kernel_size = 11,stride = 4,padding = 2
nn.ReLU(),
# IN : 96*16*16
nn.MaxPool2d(kernel_size=2, stride=2), # 论文中为kernel_size = 3,stride = 2
# IN : 96*8*8
nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
# IN :256*8*8
nn.MaxPool2d(kernel_size=2, stride=2), # 论文中为kernel_size = 3,stride = 2
# IN : 256*4*4
nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.MaxPool2d(kernel_size=2, stride=2), # 论文中为kernel_size = 3,stride = 2
# OUT : 384*2*2
)
self.linear = nn.Sequential(
nn.Linear(in_features=384 * 2 * 2, out_features=4096),
nn.ReLU(),
nn.Linear(in_features=4096, out_features=4096),
nn.ReLU(),
nn.Linear(in_features=4096, out_features=10),
)
def forward(self, x):
x = self.Conv(x)
x = x.view(-1, 384 * 2 * 2)
x = self.linear(x)
return x
# 显示网络参数量
def Init_net():
model = AlexNet()
model.to(device)
data_input = Variable(torch.randn(8,3,32,32))
print(data_input.size())
data_input.to(device)
model(data_input)
print(summary(model,(3,32,32)))
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
# 有GPU,优先用GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# **由于torchvision的datasets的输出是[0,1]的PILImage,所以我们先先归一化为[-1,1]的Tensor**
# 首先定义了一个变换transform,利用的是上面提到的transforms模块中的Compose( )
# 把多个变换组合在一起,可以看到这里面组合了ToTensor和Normalize这两个变换
transform = transforms.Compose(
[transforms.ToTensor(), #range [0, 255] -> [0.0,1.0]
transforms.Normalize((0.45, 0.45, 0.45), (0.225, 0.225, 0.225))] # Normalize()函数去计算 x = (x - mean)/std
)
# 前面的(0.5,0.5,0.5)是RGB三个通道上的均值,后面(0.5, 0.5, 0.5)是三个通道的标准差,
# 注意通道顺序是RGB ,用过opencv的同学应该知道openCV读出来的图像是BRG顺序。
# 这两个tuple数据是用来对RGB图像做归一化的.这里均值和标准差都是近似的值,可以在数据集中计算。
# 利用torchvision数据收集,train=True表示拉取训练集,FALSE表示测试集,num_workers:用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)
# DataLoader:pin_memory=True,意味着生成的Tensor属于内存的锁业内存,这样转GPU的时候快一些
trainset = torchvision.datasets.CIFAR10('./data', train=True, download=True, transform=transform)
trainloader = Data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2,pin_memory=True)
testset = torchvision.datasets.CIFAR10('./data', train=False, download=True, transform=transform)
testloader = Data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2,pin_memory=True)
print("训练数据数量:", len(trainset))
print("训练数据分为4份后数量:", len(trainloader))
print("测试数据数量:", len(testset))
print("测试数据分为4份后数量:", len(testloader))
classes = ('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# 随机获取部分训练数据,创建一个迭代器,可以调用 next 下一个对象
dataiter = iter(trainloader)
imges,labels = dataiter.next() # 包大小为4,所以一份有 4 个值,len(x) = 4
#print(len(imges[0]))
print(device)
# 显示个batch 图像
for num in range(imges.shape[0]):
plt.subplot(1,4,num+1)
imshow(imges[num],classes[labels[num].item()])
plt.show() #此处有可能有警告,或者报错,原因就是归一化的时候和显示还原不一致。
# imshow(torchvision.utils.make_grid(imges)) #直接以格子的形式展现
# print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
# 打印初始化网络,查看网络情况
# Init_net()
# net = CNNNet()
net = AlexNet()
net = net.to(device=device)
# 可以打印网络查看一下
print(net)
# 定义优化器和损失函数
criterion = nn.CrossEntropyLoss() #nn工具箱中的交叉熵损失函数
# 使用nn工具箱中的 SGD 梯度优化算法(随机梯度下降)
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
# 优化器:pytorch将深度学习中常用的优化方法全部封装在torch.optim之中,所有的优化方法都是继承基类optim.Optimizier
# 如果要使用GPU,在定义优化器之前,就要完成 .cuda()操作
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
# 下面进入训练操作
start_time = time.time()
for epoch in range(6): # loop over the dataset multiple times 指定训练一共要循环几个epoch
running_loss = 0.0 #定义一个临时变量,用于记录和输出loss
for i,data in enumerate(trainloader,0):
#传入训练数据,enumerate
# data 是从enumerate 返回的,包含数据和标签信息,分别赋值inputs和labels
inputs,labels = data
# copy数据到设备,如果使用CPU,这一步是多余的
inputs,labels = inputs.to(device),labels.to(device)
# 将数据转换成Variable,
# 注意:虽然官网给的程序有这么一句 from torch.autograd import Variable,
# 但是此步中确实没有显式地用到variable,也可以不用转,下一步
# 只能说网络里运行的数据确实要以variable的形式存在
#inputs, labels = Variable(inputs), Variable(labels)
optimizer.zero_grad()#要把梯度重新归零,因为反向传播过程中梯度会累加上一次循环的梯度
# forward + backward + optimize
outputs = net(inputs) #把数据输入网络中
loss = criterion(outputs,labels) #计算损失值
# 想要计算各个variable的梯度,只需调用根节点的backward方法,Autograd就会自动沿着整个计算图进行反向计算
# 而在此例子中,根节点就是我们的loss,所以:
# 程序中的loss.backward()代码就是在实现反向传播,自动计算所有的梯度。
loss.backward() #反向传递损失函数
optimizer.step() #反向传播之后,把优化器的参数进行更新,方便下一轮运算
# 下面打印loss结果,方便查看损失值,但与训练关系不大
running_loss += loss.item() #此处选择每2000 累计一次损失值
# 从下面一行代码可以看出它是每循环0-1999共两千次才打印一次
if i % 2000 == 1999:
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
'[%d, %5d] loss: %.3f' %(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0 # 重新初始化,下次再使用
endtime = time.time()
print("训练完成,训练用时:%f S"%(endtime - start_time))
# 测试训练情况
# 查看测试集数据
dataiter = iter(testloader) #创建迭代器
images,labels = dataiter.next() #返回一个batch_size的图片
# imshow(torchvision.utils.make_grid(images)) # 展示这四张图片
# 显示个batch 图像
for num in range(imges.shape[0]):
plt.subplot(1, 4, num + 1)
imshow(imges[num], classes[labels[num].item()])
# plt.show()
# print('GroundTruth: ', ' '.join(
# '%5s' % classes[labels[j]] for j in range(4))) # python字符串格式化 ' '.join表示用空格来连接后面的字符串,参考python的join()方法
start_time = time.time()
# images, labels = images.to(device), labels.to(device)
# outputs = net(Variable(images)) # 注意这里的images是我们从上面获得的那四张图片,所以首先要转化成variable
# 返回返回输入Tensor中每行的最大值,并转换成指定的dim(维度);
# 参数dim=1,相当于调用了squeeze(1),如果dim=0,它其实是在返回每列的最大值
# 这里很明显,这个返回的元组的第一个元素是image data,即是最大的值,第二个元素是label, 即是最大的值的索引
# 我们只需要label(最大值的索引),所以就会有 _ , predicted这样的赋值语句
# _, predicted = torch.max(outputs.data, 1)
# 这个 _ , predicted是python的一种常用的写法,表示后面的函数其实会返回两个值
# 但是我们对第一个值不感兴趣,就写个_在那里,把它赋值给_就好,我们只关心第二个值predicted
# 比如 _ ,a = 1,2 这中赋值语句在python中是可以通过的,你只关心后面的等式中的第二个位置的值是多少
# print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4))) # python的字符串格式化
correct = 0 # 定义预测正确的图片数,初始化为0
total = 0 # 总共参与测试的图片数,也初始化为0
# 在使用pytorch时,并不是所有的操作都需要进行计算图的生成(计算过程的构建,以便梯度反向传播等操作)。
# 而对于tensor的计算操作,默认是要进行计算图的构建的.
# 在这种情况下,可以使用 with torch.no_grad():强制之后的内容不进行计算图构建。
with torch.no_grad():
for data in testloader: # 循环每一个batch
images, labels = data
# copy数据到设备,如果使用CPU,这一步是多余的
images, labels = images.to(device), labels.to(device)
outputs = net(images) # 输入网络进行测试
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0) # 更新测试图片的数量
correct += (predicted == labels).sum().item() # 更新正确分类的图片的数量
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total)) # 最后打印结果
endtime = time.time()
print("测试完成,测试用时:%f S"%(endtime - start_time))
# 打印各个类别的识别准确率,这看起来比随机预测要好,
# 随机预测的准确率为10%(随机预测出为10类中的哪一类)。
# 看来网络学到了东西。
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
t = 0
with torch.no_grad():
for data in testloader:
images, labels = data
images, labels = images.to(device), labels.to(device)
outputs = net(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1
for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i]))
# 只保存网络中训练好的权重文件,使用的时候会快一些
print('===> Saving models...')
path_model = './model'
state = {
'state': net.state_dict(),
'epoch': epoch # 将epoch一并保存
}
if not os.path.isdir('model'): # 如果没有这个目录就创建一个
os.mkdir('./model')
# 保存整个网络模型,也可以保存整个模型
torch.save(net,"./model/cifar-10_alxNet_model.pkl")
# 保存模型的参数,参数后续使用快于整个模型
torch.save(net.state_dict(), './model/cifar-10_alxNet_model_params.pkl')
接下来是训练好模型后具体的使用环节,下面还是继续用Python来体验:
# -*- coding: utf-8 -*-
# @Time : 2021/12/7 9:43
# @Author :
# @Email :
# @File : use_AlexNet.py
import os
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
import time
import json
import torch
from torch import nn
import torchvision.transforms as transforms
from PIL import Image,ImageFont,ImageDraw
import torch.optim as optim
from matplotlib import pyplot as plt
import torchvision.models as models
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
classes = ('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
#图像预处理,必须转换成和模型相同的尺寸,否则将无法识别
image_transforms = {
'test': transforms.Compose([
transforms.Resize(size=32),
transforms.CenterCrop(size=32),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])
}
# 定义AlexNet
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.Conv = nn.Sequential(
# IN : 3*32*32
nn.Conv2d(in_channels=3, out_channels=96, kernel_size=5, stride=2, padding=2),
# 论文中kernel_size = 11,stride = 4,padding = 2
nn.ReLU(),
# IN : 96*16*16
nn.MaxPool2d(kernel_size=2, stride=2), # 论文中为kernel_size = 3,stride = 2
# IN : 96*8*8
nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
# IN :256*8*8
nn.MaxPool2d(kernel_size=2, stride=2), # 论文中为kernel_size = 3,stride = 2
# IN : 256*4*4
nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.MaxPool2d(kernel_size=2, stride=2), # 论文中为kernel_size = 3,stride = 2
# OUT : 384*2*2
)
self.linear = nn.Sequential(
nn.Linear(in_features=384 * 2 * 2, out_features=4096),
nn.ReLU(),
nn.Linear(in_features=4096, out_features=4096),
nn.ReLU(),
nn.Linear(in_features=4096, out_features=10),
)
def forward(self, x):
x = self.Conv(x)
x = x.view(-1, 384 * 2 * 2)
x = self.linear(x)
return x
def predict(model, test_image_name):
transform = image_transforms['test']
test_image = Image.open(test_image_name).convert('RGB')
draw = ImageDraw.Draw(test_image)
test_image_tensor = transform(test_image)
if torch.cuda.is_available():
test_image_tensor = test_image_tensor.view(1, 3, 32, 32).cuda()
else:
test_image_tensor = test_image_tensor.view(1, 3, 32, 32)
with torch.no_grad():
model.eval()
out = model(test_image_tensor)
# print("out=",out)
ps = torch.exp(out)
topk, topclass = ps.topk(1, dim=1)
print("Prediction : ", classes[topclass.cpu().numpy()[0][0]], ", Score(exp): ", topk.cpu().numpy()[0][0])
text = classes[topclass.cpu().numpy()[0][0]] + "\nScore(exp):" + str(topk.cpu().numpy()[0][0])
font = ImageFont.truetype('arial.ttf', 36)
draw.text((0, 0), text, (255, 0, 0), font=font)
test_image.show()
if __name__ == "__main__":
# config
path_state_dict = os.path.join(BASE_DIR, "cifar-10_alxNet_model.pkl") #字符串组合,上文中保存的模型路径
path_img = os.path.join(BASE_DIR, "..", "data", "111.png") #填写图片地址和名称
# load model
# 由于训练模型是在GPU上训练的,如果在没有GPU的设备上运行时,需要加map_location
model = torch.load(path_state_dict, map_location=torch.device(device))
predict(model,path_img) #预测图像中的物体,为提高识别率,可采用集成的方式
识别效果:
可寻找对应图片数据传入模型预测:
相关推荐
- Java的SPI机制详解
-
作者:京东物流杨苇苇1.SPI简介SPI(ServiceProvicerInterface)是Java语言提供的一种接口发现机制,用来实现接口和接口实现的解耦。简单来说,就是系统只需要定义接口规...
- 一文读懂 Spring Boot 启动原理,开发效率飙升!
-
在当今的Java开发领域,SpringBoot无疑是最热门的框架之一。它以其“约定大于配置”的理念,让开发者能够快速搭建和启动应用,极大地提高了开发效率。但是,你是否真正了解Spring...
- ServiceLoader
-
ServiceLoader是Java提供的一种服务发现机制(ServiceProviderInterface,SPI)...
- 深入探索 Spring Boot3 中的自定义扩展操作
-
在当今互联网软件开发领域,SpringBoot无疑是最受欢迎的框架之一。随着其版本迭代至SpringBoot3,它为开发者们带来了更多强大的功能和特性,其中自定义扩展操作更是为我们在项目开发中...
- Spring Boot启动过程全面解析:从入门到精通
-
一、SpringBoot概述SpringBoot是一个基于Spring框架的快速开发脚手架,它通过"约定优于配置"的原则简化了Spring应用的初始搭建和开发过程。...
- Spring Boot 3.x 自定义 Starter 详解
-
今天星期六,继续卷springboot3.x。在SpringBoot3.x中,自定义Starter是封装和共享通用功能、实现“约定优于配置”理念的强大机制。通过创建自己的Starte...
- Spring Boot 的 3 种动态 Bean 注入技巧
-
在SpringBoot开发中,动态注入Bean是一种强大的技术,它允许我们根据特定条件或运行时环境灵活地创建和管理Bean。相比于传统的静态Bean定义,动态注入提供了更高的灵活性和可...
- 大佬用4000字带你彻底理解SpringBoot的运行原理!
-
SpringBoot的运行原理从前面创建的SpringBoot应用示例中可以看到,启动一个SpringBoot工程都是从SpringApplication.run()方法开始的。这个方法具体完成...
- Springboot是如何实现自动配置的
-
SpringBoot的自动配置功能极大地简化了基于Spring的应用程序的配置过程。它能够根据类路径中的依赖和配置文件中的属性,自动配置应用程序。下面是SpringBoot实现自动配置的...
- Spring Boot3.x 应用的生命周期深度解析
-
SpringBoot应用的生命周期可以清晰地划分为三个主要阶段:启动阶段(Startup)...
- Springboot 启动流程及各类事件生命周期那点事
-
前言本文通过Springboot启动方法分析SpringApplication逻辑。从静态run方法执行到各个阶段发布不同事件完成整个应用启动。...
- Spring框架基础知识-常用的接口1
-
BeanDefinition基本概念BeanDefinition是Spring框架中描述bean配置信息的核心接口,它包含了创建bean实例所需的所有元数据。...
- Java 技术岗面试全景备战!从基础到架构的系统性通关攻略分享
-
Java技术岗的面试往往是一项多维度的能力检验。本文将会从核心知识点、项目经验到面试策略,为你梳理一份系统性的备战攻略!...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)