深度学习-基于Pytorch 的cifar-10_AlexNet 实践
ztj100 2024-10-31 16:14 16 浏览 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) #预测图像中的物体,为提高识别率,可采用集成的方式
识别效果:
可寻找对应图片数据传入模型预测:
相关推荐
- 如何将数据仓库迁移到阿里云 AnalyticDB for PostgreSQL
-
阿里云AnalyticDBforPostgreSQL(以下简称ADBPG,即原HybridDBforPostgreSQL)为基于PostgreSQL内核的MPP架构的实时数据仓库服务,可以...
- Python数据分析:探索性分析
-
写在前面如果你忘记了前面的文章,可以看看加深印象:Python数据处理...
- C++基础语法梳理:算法丨十大排序算法(二)
-
本期是C++基础语法分享的第十六节,今天给大家来梳理一下十大排序算法后五个!归并排序...
- C 语言的标准库有哪些
-
C语言的标准库并不是一个单一的实体,而是由一系列头文件(headerfiles)组成的集合。每个头文件声明了一组相关的函数、宏、类型和常量。程序员通过在代码中使用#include<...
- [深度学习] ncnn安装和调用基础教程
-
1介绍ncnn是腾讯开发的一个为手机端极致优化的高性能神经网络前向计算框架,无第三方依赖,跨平台,但是通常都需要protobuf和opencv。ncnn目前已在腾讯多款应用中使用,如QQ,Qzon...
- 用rust实现经典的冒泡排序和快速排序
-
1.假设待排序数组如下letmutarr=[5,3,8,4,2,7,1];...
- ncnn+PPYOLOv2首次结合!全网最详细代码解读来了
-
编辑:好困LRS【新智元导读】今天给大家安利一个宝藏仓库miemiedetection,该仓库集合了PPYOLO、PPYOLOv2、PPYOLOE三个算法pytorch实现三合一,其中的PPYOL...
- C++特性使用建议
-
1.引用参数使用引用替代指针且所有不变的引用参数必须加上const。在C语言中,如果函数需要修改变量的值,参数必须为指针,如...
- Qt4/5升级到Qt6吐血经验总结V202308
-
00:直观总结增加了很多轮子,同时原有模块拆分的也更细致,估计为了方便拓展个管理。把一些过度封装的东西移除了(比如同样的功能有多个函数),保证了只有一个函数执行该功能。把一些Qt5中兼容Qt4的方法废...
- 到底什么是C++11新特性,请看下文
-
C++11是一个比较大的更新,引入了很多新特性,以下是对这些特性的详细解释,帮助您快速理解C++11的内容1.自动类型推导(auto和decltype)...
- 掌握C++11这些特性,代码简洁性、安全性和性能轻松跃升!
-
C++11(又称C++0x)是C++编程语言的一次重大更新,引入了许多新特性,显著提升了代码简洁性、安全性和性能。以下是主要特性的分类介绍及示例:一、核心语言特性1.自动类型推导(auto)编译器自...
- 经典算法——凸包算法
-
凸包算法(ConvexHull)一、概念与问题描述凸包是指在平面上给定一组点,找到包含这些点的最小面积或最小周长的凸多边形。这个多边形没有任何内凹部分,即从一个多边形内的任意一点画一条线到多边形边界...
- 一起学习c++11——c++11中的新增的容器
-
c++11新增的容器1:array当时的初衷是希望提供一个在栈上分配的,定长数组,而且可以使用stl中的模板算法。array的用法如下:#include<string>#includ...
- C++ 编程中的一些最佳实践
-
1.遵循代码简洁原则尽量避免冗余代码,通过模块化设计、清晰的命名和良好的结构,让代码更易于阅读和维护...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- 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)