PyTorch-08 Cifar10与ResNet18实战(Lenet-5和Cifar10,ResNet和Cifar10)
一、Lenet-5和Cifar10详细步骤说明
1、首先针对training创建一次加载一个数据的loader
导入包
import torch
from torch.utils.data import DataLoader
#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;
from torchvision import datasets
from torchvision import transforms
from torch import nn, optim
#########################################下面开始加载数据#########################################
#########################################下面开始加载数据#########################################
下面的代码,一次只能加载一个数据,并不能加载一批次的数据。
#首先引入数据:
#参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
#参数2:是否为train训练集
#参数3:transform数据变换
#参数4:download是否下载
cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
transforms.Resize((32,32)), #调整照片的大小
transforms.ToTensor(), #直接将数据类型转变为tensor
#希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
2、一次加载一批数据
一次加载一批就需要使用多线程的特性。
此外batch_size参数可以设置大一些,太小的话training不会很稳定,因为梯度计算,是把当前batch_size的平均方向做update的方向,太小的话这个方向就不一定准确了,就有一定随机性了。
#将datasets进行多线程下载
batchsz = 32
cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)
#其中参数cifar_train是上面只加载一次的过程。
3、同样的流程,针对test创建一次加载一批
这里需要注意参数2要为False,意思为:不是train训练集。
#参数2:是否为train训练集
cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
transforms.Resize((32,32)),
transforms.ToTensor(),
#如果加了normalize,train和test必须都添加
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)
4、查看一下加载的数据情况,并将数据集和标签分开
#通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
#iter()后数据的类型
print(type(iter(cifar_train).next()))
#查看长度,为2,说明一个是数据集,一个是标签
print(len(iter(cifar_train).next()))
#一个是数据集,一个是标签
x, label = iter(cifar_train).next()
#查看其数据结构
print('x:',x.shape,'laberl:',label.shape)
#32个图片,每个图片三个通道,32*32的大小,均符合batch_size以及transforms.Resize后的结果。
###########################################结束加载数据###########################################
###########################################结束加载数据###########################################
附加了解的知识点:
1、pytorch之Dataloader,enumerate:
如果要了解pytorch加载数据和循环batch去使用数据(Dataloader和enumerate),我们单独创建了一组数据用于实验了解: (这部分是针对=>[10、编写运行函数train部分 => #7、编写for循环 => batchidx,可以发现一共分了0-1562组batch]为什么是1563组batch进行解释的。)
from torch.utils.data import TensorDataset
import torch
from torch.utils.data import DataLoader
a = torch.tensor(
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3],
[4, 5, 6], [7, 8, 9]])
b = torch.tensor([44, 55, 66, 44, 55, 66, 44, 55, 66, 44, 55, 66])
train_ids = TensorDataset(a, b) # 封装数据a与标签b
print('简单探索数据:')
print(a.shape) #torch.Size([12, 3])
print(b.shape) #torch.Size([12])
print(list(train_ids))
print(len([*train_ids]))
print('=' * 80)
# 切片输出
print('切片输出:')
print(train_ids[0:2])
print('=' * 80)
# 循环取数据
print('循环取数据:')
for x_train, y_label in train_ids:
print(x_train, y_label)
print('=' * 80)
# DataLoader进行数据封装
print('DataLoader进行数据封装:')
#batch_size=4 shuffle=False不打乱,按照原来的顺序
#batch_size为4,这样可分为三组,每个batch有4个元素,刚好是4+4+4 = 12个元素。
#重要:会按照这样分布的方式将train_ids中的所有元素都分配完。
train_loader = DataLoader(dataset=train_ids, batch_size=4, shuffle=False)
print(train_loader)
print(list(train_loader))
print('=' * 80)
#batch_size=5 shuffle=True 先打乱,再取batch
#batch_size为5,这样也可分为三组,但是最后一组只有原来数组的2个元素,前两组个包含5个元素,5+5+2 = 12个元素
train_loader = DataLoader(dataset=train_ids, batch_size=5, shuffle=True)
print(train_loader)
print(list(train_loader))
print('=' * 80)
#batch_size=4 shuffle=True 先打乱,再取batch
train_loader = DataLoader(dataset=train_ids, batch_size=4, shuffle=True)
print(train_loader)
print(list(train_loader))
print('=' * 80)
#查看一下enumerate的效果
print('查看一下enumerate的效果:')
print(enumerate(train_loader))
print(list(enumerate(train_loader)))
print(len(list(enumerate(train_loader)))) #长度是3,因为上面batch_size=4,原本的数据是12元素,所以刚好可以分为3组
print('=' * 80)
for i, data in enumerate(train_loader): # 注意enumerate返回值有两个,一个是序号,一个是数据(包含训练数据和标签)
print('i:',i)
print('data:',data)
print('--------------------')
print('将data分别赋予x_data和label:')
x_data, label = data
print(' batch:{0}\n x_data:{1}\nlabel: {2}'.format(i, x_data, label))
print('--------------------')
print('#' * 80)
for i, data in enumerate(train_loader, 10): #这里的参数2为10,表示i是从10开始的
#注意enumerate返回值有两个,一个是序号,一个是数据(包含训练数据和标签)
x_data, label = data
print(' batch:{0}\n x_data:{1}\nlabel: {2}'.format(i, x_data, label))
2、torch.argmax 函数详解
该内容参考链接为:
1、 函数介绍
torch.argmax(input, dim=None, keepdim=False)
返回指定维度最大值的序号
dim给定的定义是:the demention to reduce.也就是把dim这个维度的,变成这个维度的最大值的index。
举例说明:
例子1:torch.argmax()函数中dim表示该维度会消失。
这个消失是什么意思?
官方英文解释是:dim (int) – the dimension to reduce.
我们知道argmax就是得到最大值的序号索引,对于一个维度为(d0,d1) 的矩阵来说,我们想要求每一行中最大数的在该行中的列号,最后我们得到的就是一个维度为(d0,1) 的一维矩阵。这时候,列这一维度就要消失了。
因此,我们想要求每一行最大的列标号,我们就要指定dim=1,表示我们不要列了,保留行的size就可以了。
假如我们想求每一列的最大行标,就可以指定dim=0,表示我们不要行了,求出每一列的最大值的下标,最后得到(1,d1)的一维矩阵。
2、 实例演示
实例1:
import torch
a = torch.tensor(
[
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1]
])
b = torch.argmax(a, dim=0)
print(b)
print(a.shape)
dim=0的维度为3,即在那3组数据中作比较,求得是每一列中的最大行标,因此为[1,2,0,4]。
实例2:
import torch
a = torch.tensor([
[
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1]
],
[
[-1, 7, -5, 2],
[9, 6, 2, 8],
[3, 7, 9, 1]
]])
b = torch.argmax(a, dim=0)
print(b)
print(a.shape)
"""
tensor([[0, 1, 0, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])
torch.Size([2, 3, 4])"""
# dim=0,即将第一个维度消除,也就是将两个[3*4]矩阵只保留一个,因此要在两组中作比较,即将上下两个[3*4]的矩阵分别在对应的位置上比较大小
b = torch.argmax(a, dim=1)
"""
tensor([[1, 2, 0, 1],
[1, 2, 2, 1]])
torch.Size([2, 3, 4])
"""
# dim=1,即将第二个维度消除,这么理解:矩阵维度变为[2*4];
"""
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1];
纵向压缩成一维,因此变为[1,2,0,1];同理得到[1,2,2,1];
"""
b = torch.argmax(a,dim=2)
"""
tensor([[2, 0, 1],
[1, 0, 2]])
"""
# dim=2,即将第三个维度消除,这么理解:矩阵维度变为[2*3]
"""
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1];
横向压缩成一维
[2,0,1],同理得到下面的"""
++++++++++++++++++++++++++++++++++++开始编写一个Lenet5类++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++开始编写一个Lenet5类++++++++++++++++++++++++++++++++++++++++
5、新创建一个类,表示为lenet5,并编写lenet5类的卷积操作单元
lenet5的卷积操作单元是全连接层之前的卷积操作内容。
导入包
import torch
from torch import nn #任何类都要继承自nn.Module
from torch.nn import functional as F
创建Lenet5类,类要继承自nn.Module,按照下图给定的模型来编写:
我们先写这部分:先经过一个卷积层,再经过一个pooling层,再经过一个卷积层,再经过一个pooling层。这些是全连接层之前的内容。
class Lenet5(nn.Module):
"""
for cifar10 dataset.
"""
# 调用这个类的初始化方法,来初始化这个父类。
def __init__(self):
super(Lenet5, self).__init__()
#卷积层
#网络结构写在Sequential中,可以方便的组织网络结构。
self.conv_unit = nn.Sequential(
#x:[b张照片batch_size, 3, 32, 32] 输入的训练图片
#nn.Conv2d(3, 6)输入的是3个channel,输出的是6个channel,这里的输出的channel是由kernal的channel来决定。
#kernal_size=5表示一次关注长宽各为5个像素点的窗口
#stride表示kernal移动的步长
#padding表示左右上下添加0,使得卷积后的照片和之前输入的照片size类似
# 经过第一层后从x:[b, 3, 32, 32]=>到[b, 6, ]
nn.Conv2d(3, 6, kernel_size=5, stride=1, padding=0) #第一层:新建一个卷积层
#pooling层是不改变channel的,只改变长宽。
#[b, 6, ] => [b, 6, ]
,nn.AvgPool2d(kernel_size=2,stride=2,padding=0) #第二层:Pooling层
# [b, 6, ] => [b, 16, ]
,nn.Conv2d(6,16,kernel_size=5,stride=1,padding=0) #第三层:再建一个卷积层
# [b, 16, ] => [b, 16, 5, 5]
,nn.AvgPool2d(kernel_size=2, stride=2, padding=0) #第四层:Pooling层
#之后就是和全连接层连接了,需要做一个打平的操作,但是pytorch中没有打平flatten这个类。
#所以flatten的操作先不编写
)
# conv out: torch.Size([b, 16, 5, 5])
6、编写一个假的输入数据,获取Lenet5类中卷积单元的计算结果
可以发现输入一个临时的batch数据到这个卷积单元中,输出结果为 (b,16,5,5)
#这部分内容是写在Lenet5类里面的
# 一个假的batch数据包[b,3,32,32],这个数据是模仿cifar_train的数据
temp = torch.randn(2, 3, 32, 32)
# 将这个假的batch送进取
out = self.conv_unit(temp)
# conv out: torch.Size([2, 16, 5, 5])
print('conv out:', out.shape)
#这样实例化Lenet5,可以查看一下输出结果
def main():
net = Lenet5()
if __name__ =='__main__':
main()
7、编写lenet5类的全连接单元
#fc unit
#Full Connection(缩写fc)全连接层
self.fc_unit = nn.Sequential(
# 16*5*5,是根据假数据测试Lenet5类卷积单元计算结果所得到的,并经过flatten的结果,表示flatten的输入维度数量,目前这里暂时忽略flatten操作,直接手动的输入。
nn.Linear(16*5*5,120),
nn.ReLU(), #激活函数,sigmoid会有梯度离散的现象,这里选择ReLU
nn.Linear(120,84),
nn.ReLU(),
nn.Linear(84,10) #这里的10表示分的10类
)
8、编写lenet5类的forward单元
所有的网络结构都有一个forward函数,代表前项运算的流程。
为什么backward不用写呢,和numpy不一样,pytorch会自动记录前项的路径,当backward的时候就不需要自己来实现了,pytorch会自动的根据前面走过的路线,往回走一遍。
def forward(self, x):
"""
:param x: [b, 3, 32, 32]
:return:
"""
# x.size(0)是返回一个list(b,3,32,32)的第0号元素。
# 0就是取这个list的第0号元素,就等于x.shape[0]
batchsz = x.size(0)
#1、经过卷积单元
#[b,3,32,32] => [b,16,5,5]
x = self.conv_unit(x)
#2、经过Flatten打平操作:
#经过flatten x= x.view()就可以实现flatten的效果
#[b,16,5,5] => [b,16*5*5]
#flatten是没有写到Sequential中的
x = x.view(batchsz, 16*5*5)
#还可以写成:x = x.view(batchsz, -1) 这里的-1表示除了第一个维度batchsz,剩下的全部归于第二个维度。
#x = x.view(batchsz, 16*5*5) = x.view(batchsz, -1)
#3、经过全连接层Full Connection
#[b,16*5*5] => [b,10]
logits = self.fc_unit(x)
#返回logits, 一定要有返回值
return logits
lenet5类的forward单元就编写好了,实现一个输入的数据x,经过卷积单元、Flatten打平、最后经过全连接层的过程。
9、编写一个假的数据测试一下Lenet5这个类
def main():
net = Lenet5()
# 一个假的batch数据包[b,3,32,32]
temp = torch.randn(2, 3, 32, 32)
# 将这个假的batch送进取
out = net(temp)
# Lenet5 out: torch.Size([b, 10])
print('Lenet5 out:', out.shape)
print(type(out))
print(out)
if __name__ =='__main__':
main()
++++++++++++++++++++++++++++++++++++结束编写一个Lenet5类++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++结束编写一个Lenet5类++++++++++++++++++++++++++++++++++++
10、编写运行函数train部分
#将lenet5类引入
from lenet5_test import Lenet5
def main():
#1、导入数据
#==========这部分内容是步骤1、步骤2和步骤3的内容,导入数据===========
batchsz = 32
#首先引入数据:
#参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
#参数2:是否为train训练集
#参数3:transform数据变换
#参数4:download是否下载
cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
transforms.Resize((32,32)), #调整照片的大小
transforms.ToTensor(), #直接将数据类型转变为tensor
#希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
#将datasets进行多线程下载
cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)
cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
transforms.Resize((32,32)),
transforms.ToTensor(),
#如果加了normalize,train和test必须都添加
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)
#================================================================
#2、设置设备计算的GPU
#这样就可以使用GPU来进行计算
device = torch.device('cuda')
#3、实例化一下模型
#引入Lenet5类,并实例化Lenet5
model = Lenet5().to(device)
#4、打印输出一下模型网络结构
# print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
print('网络结构:')
print(model)
#5、创建一个计算loss的criteon
#nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
#predict与logits的区别在于,predict是logits经过了softmax操作的。
criteon = nn.CrossEntropyLoss().to(device)
#6、得到一个优化器
#做测试一般都使用Adam这个包。
#优化器optimizer就不需要转到GPU上面了
#参数1是要传入模型需要优化的参数,参数2是学习率
optimizer = optim.Adam(model.parameters(), lr=1e-3)
#7、编写for循环
for epoch in range(1000):
#training部分
#迭代每一次数据
#batchidx表示第多少个batch了
for batchidx, (x, label) in enumerate(cifar_train):
#如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
#我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
# print('batchidx:',batchidx)
#1、设置training数据和标签
# x: [b,3,32,32]
# label: [b]
x, label = x.to(device), label.to(device)
#2、将数据导入模型
#将数据传入实例化的Lenet5中
logits = model(x)
#3、模型计算结果与标签计算loss
#获得loss
#logits: [b,10] logits必须给出每个维度的probability
#label: [b] 没有经过one-hot-coding,label是不需要给出probability
#loss: tensor scalar 长度为0的标量。
loss = criteon(logits, label)
#4、backpropagation 反向传播
optimizer.zero_grad()#将梯度全部清0,如果不清0,则每次梯度更新后都会累加新的梯度。
loss.backward() #回退进行梯度计算
optimizer.step() #走一遍,更新了weight权值
#这里我们打印一下
print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来
if __name__ == '__main__':
main()
11、编写运行函数test部分
#test部分
#这部分是包含在for epoch in range(1000):内的
#与training部分for batchidx, (x, label) in enumerate(cifar_train):平级的
#将模型变为test模式
#train和test的计算方法是不一样的。
#比如说在test模式下,dropout的概率全为0,不进行drop out
model.eval()
# 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
# test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
#不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
with torch.no_grad():
# 做一个比对,统计对的,和总的数量
total_correct = 0;
total_num = 0
for batchidx, (x, label) in enumerate(cifar_test):
#1、设置test数据和标签
# [b, 3, 32, 32]
# [b]
x, label = x.to(device), label.to(device)
# 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
# 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
# print('batchidx:',batchidx)
# 2、将数据导入模型
# [b,10]
logits = model(x) #这个模型是经过optimizer调整后的
# print('logits:',logits.shape)
#logits: torch.Size([32, 10])
# 3、设置pred
#取logits元素最大的位置作为predict
#在1维上面选出数据最大的那个值所在的idex。
#[b]
pred = logits.argmax(dim=1)
# print('pred:',pred)
# print('label:',label)
#下面是其中一组预测pred和label
# pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
# 1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
# label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
# 9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')
# 4、预测pred与真实label进行比较,统计预测正确的图片数量
#pred与label做比较
#[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
#统计预测正确的图片数量
#这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
total_correct += torch.eq(pred, label).float().sum().item()
#.item()将tensor类型转为numpy
# print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
# print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型
#print('total_correct:',total_correct) #预测正确的图片数量
#total_correct: 4697.0
# 5、统计总图片数
total_num += x.size(0) #所有用于统计的图片总量10000张
# print('total_num:',total_num)
#total_num: 10000
# print('x.shape:',x.shape)
#x.shape: torch.Size([32, 3, 32, 32])
#6、预测准确度accuracy
acc = total_correct / total_num
print('epoch:', epoch, 'acc:', acc)
if __name__ == '__main__':
main()
二、将详细说明代码综合起来 Lenet-5和Cifar10
lenet5_test.py:
import torch
from torch import nn #任何类都要继承自nn.Module
from torch.nn import functional as F
class Lenet5(nn.Module):
"""
for cifar10 dataset.
"""
# 调用这个类的初始化方法,来初始化这个父类。
def __init__(self):
super(Lenet5, self).__init__()
#卷积层
#网络结构写在Sequential中,可以方便的组织网络结构。
self.conv_unit = nn.Sequential(
#x:[b张照片batch_size, 3, 32, 32] 输入的训练图片
#nn.Conv2d(3, 6)输入的是3个channel,输出的是6个channel,这里的输出的channel是由kernal的channel来决定。
#kernal_size=5表示一次关注长宽各为5个像素点的窗口
#stride表示kernal移动的步长
#padding表示左右上下添加0,使得卷积后的照片和之前输入的照片size类似
# 经过第一层后从x:[b, 3, 32, 32]=>到[b, 6, ]
nn.Conv2d(3, 6, kernel_size=5, stride=1, padding=0) #第一层:新建一个卷积层
#pooling层是不改变channel的,只改变长宽。
#[b, 6, ] => [b, 6, ]
,nn.AvgPool2d(kernel_size=2,stride=2,padding=0) #第二层:Pooling层
# [b, 6, ] => [b, 16, ]
,nn.Conv2d(6,16,kernel_size=5,stride=1,padding=0) #第三层:再建一个卷积层
# [b, 16, ] => [b, 16, 5, 5]
,nn.AvgPool2d(kernel_size=2, stride=2, padding=0) #第四层:Pooling层
#之后就是和全连接层连接了,需要做一个打平的操作,但是pytorch中没有打平flatten这个类。
)
#conv out: torch.Size([b, 16, 5, 5])
#flatten: 16*5*5
#fc unit
#Full Connection(缩写fc)全连接层
self.fc_unit = nn.Sequential(
# 16*5*5表示flatten的输入维度数量
nn.Linear(16*5*5,120),
nn.ReLU(), #激活函数,sigmoid会有梯度离散的现象,这里选择ReLU
nn.Linear(120,84),
nn.ReLU(),
nn.Linear(84,10)
)
def forward(self, x):
"""
:param x: [b, 3, 32, 32]
:return:
"""
# x.size(0)是返回一个list(b,3,32,32)的第0号元素。
# 0就是取这个list的第0号元素,就等于x.shape[0]
batchsz = x.size(0)
#1、经过卷积单元
#[b,3,32,32] => [b,16,5,5]
x = self.conv_unit(x)
#2、经过Flatten打平操作:
#经过flatten x= x.view()就可以实现flatten的效果
#[b,16,5,5] => [b,16*5*5]
#flatten是没有写到Sequential中的
x = x.view(batchsz, 16*5*5)
#还可以写成:x = x.view(batchsz, -1) 这里的-1表示除了第一个维度batchsz,剩下的全部归于第二个维度。
#x = x.view(batchsz, 16*5*5) = x.view(batchsz, -1)
#3、经过全连接层Full Connection
#[b,16*5*5] => [b,10]
logits = self.fc_unit(x)
#返回logits, 一定要有返回值
return logits
def main():
net = Lenet5()
# 一个假的batch数据包[b,3,32,32]
temp = torch.randn(2, 3, 32, 32)
# 将这个假的batch送进取
out = net(temp)
# Lenet5 out: torch.Size([2, 16, 5, 5])
print('Lenet5 out:', out.shape)
print(type(out))
print(out)
if __name__ =='__main__':
main()
main_test_lenet5.py:
import torch
from torch.utils.data import DataLoader
#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;
from torchvision import datasets
from torchvision import transforms
from torch import nn, optim
#将lenet5引入
from lenet5_test import Lenet5
#将Resnet引入
# from resnet import ResNet18
def main():
batchsz = 32
#首先引入数据:
#参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
#参数2:是否为train训练集
#参数3:transform数据变换
#参数4:download是否下载
cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
transforms.Resize((32,32)), #调整照片的大小
transforms.ToTensor(), #直接将数据类型转变为tensor
#希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
#将datasets进行多线程下载
cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)
cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
transforms.Resize((32,32)),
transforms.ToTensor(),
#如果加了normalize,train和test必须都添加
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)
#通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
#iter()后数据的类型
# print(type(iter(cifar_train).next()))
#查看长度
# print(len(iter(cifar_train).next()))
#一个是数据集,一个是标签
# x, label = iter(cifar_train).next()
#查看其数据结构
# print('x:',x.shape,'laberl:',label.shape)
#这样就可以使用GPU来进行计算
device = torch.device('cuda')
#引入Lenet5类,并实例化Lenet5
model = Lenet5().to(device)
# print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
print('网络结构:')
print(model)
# 创建一个计算loss的criteon
# nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
# predict与logits的区别在于,predict是logits经过了softmax操作的。
criteon = nn.CrossEntropyLoss().to(device)
#得到一个优化器
#做测试一般都使用Adam这个包。
#优化器optimizer就不需要转到GPU上面了
optimizer = optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(1000):
#===================training部分========================
# 迭代每一次数据
#batchidx表示第多少个batch了
for batchidx, (x, label) in enumerate(cifar_train):
# x: [b,3,32,32]
# label: [b]
x, label = x.to(device), label.to(device)
#如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
#我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
# print('batchidx:',batchidx)
#将数据传入实例化的Lenet5中
logits = model(x)
#logits: [b,10] logits必须给出每个维度的probability
#label: [b] 没有经过one-hot-coding,label是不需要给出probability
#loss: tensor scalar 长度为0的标量。
loss = criteon(logits, label)
#backpropagation 反向传播
optimizer.zero_grad()#将梯度全部清0
loss.backward() #回退进行梯度计算
optimizer.step() #走一遍,跟新了weight权值
#这里我们打印一下
print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来
#==================test部分========================
#将模型变为test模式
#train和test的计算方法是不一样的。
#比如说在test模式下,dropout的概率全为0,不进行drop out
model.eval()
# 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
# test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
#不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
with torch.no_grad():
# 做一个比对,统计对的,和总的数量
total_correct = 0;
total_num = 0
for batchidx, (x, label) in enumerate(cifar_test):
#1、设置test数据和标签
# [b, 3, 32, 32]
# [b]
x, label = x.to(device), label.to(device)
# 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
# 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
# print('batchidx:',batchidx)
# 2、将数据导入模型
# [b,10]
logits = model(x) #这个模型是经过optimizer调整后的
# print('logits:',logits.shape)
#logits: torch.Size([32, 10])
# 3、设置pred
#取logits元素最大的位置作为predict
#在1维上面选出数据最大的那个值所在的idex。
#[b]
pred = logits.argmax(dim=1)
# print('pred:',pred)
# print('label:',label)
#下面是其中一组预测pred和label
# pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
# 1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
# label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
# 9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')
# 4、预测pred与真实label进行比较,统计预测正确的图片数量
#pred与label做比较
#[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
#统计预测正确的图片数量
#这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
total_correct += torch.eq(pred, label).float().sum().item()
#.item()将tensor类型转为numpy
# print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
# print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型
#print('total_correct:',total_correct) #预测正确的图片数量
#total_correct: 4697.0
# 5、统计总图片数
total_num += x.size(0) #所有用于统计的图片总量10000张
# print('total_num:',total_num)
#total_num: 10000
# print('x.shape:',x.shape)
#x.shape: torch.Size([32, 3, 32, 32])
#6、预测准确度accuracy
acc = total_correct / total_num
print('epoch:', epoch, 'acc:', acc)
if __name__ == '__main__':
main()
三、ResNet和Cifar10详细步骤说明
1、首先加载数据,具体过程和Lenet-5和Cifar10详细步骤说明中的加载数据部分是相同的。
+++++++++++++++++++++++++++++++++++开始编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++开始编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++
这里需要注意:这里的ResNet18并不是原paper上的那个。这是因为这里使用的Cifar10数据集3232。而原paper的接受的输入是224224。输入shape不同,所以不能完全照搬。
2、创建一个新类,为ResBlk类,ResNet18的一个单元,如果要完成ResNet18类,就先实现这个block
import torch
from torch import nn
from torch.nn import functional as F
#新类ResBlk
class ResBlk(nn.Module):
"""
resnet block
"""
def __init__(self, ch_in, ch_out, stride=1):
"""
:param ch_in:
:param ch_out:
"""
super(ResBlk, self).__init__() #调用这个类的初始化方法,来初始化这个父类。
#1、构建两个convolution单元
self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=stride, padding=1)
# Batch Normalization的目的是使我们的一批(Batch)feature map满足均值为0,方差为1的分布规律。
self.bn1 = nn.BatchNorm2d(ch_out)
self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(ch_out)
#2、创建短接调整输入维度模块
#先判断一下输入的channel与输出的channel是不是不相同
#网络结构写在Sequential中,可以方便的组织网络结构。
#这部分是短接的额外单元extra module
if ch_out != ch_in:
#[b,ch_in,h,w] => [b,ch_out,h,w]
#这里使用的是1*1的卷积单元,为了是只见ch_in改变为ch_out,其他size都不变。
self.extra = nn.Sequential(
nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=stride),
nn.BatchNorm2d(ch_out)
)
else:
#这里是相同的情况,相同的话就什么也不做。
self.extra = nn.Sequential()
#3、编写forward函数
def forward(self, x):
"""
:param x: [b,ch,h,w]
:return:
"""
#4、调用init构建好的两个convolution单元,并在其中添加一个relu激活函数
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
#5、编写short cut部分
#这里需要注意的是,输出与输入的维度相同才可以进行矩阵相加
#所以我们给短接部分添加一个模块,这个模块用于调整输入的维度,使其能够和输出相加。
#这部分叫extra module
#如果ch_in != ch_out
#通过 extra module: 将[b,ch_in,h,w] =>变为 [b,ch_out,h,w]
# element.wise add 矩阵各个元素相加:
out = x = self.extra(x) + out
return out
def main():
blk = ResBlk(64,1, stride=4)
print(blk) #查看网络结构
tmp = torch.randn(1, 64, 32, 32)
out = blk(tmp)
print('block shape:', out.shape)
print('block:',out)
if __name__ == '__main__':
main()
3、创建一个新类,为ResNet18
这个类包含1个初始卷积层、4个ResBlk模块、一个线性层linear用于转换为10类
ResNet18类中用到了ResBlk类。
import torch
from torch import nn
from torch.nn import functional as F
#这里是ResBlk类:
class ResBlk(nn.Module):
"""
resnet block
"""
def __init__(self, ch_in, ch_out, stride=1):
"""
:param ch_in:
:param ch_out:
"""
super(ResBlk, self).__init__() #调用这个类的初始化方法,来初始化这个父类。
#1、构建两个convolution单元
#这里设置一个stride,用于减少数据量,衰减长和宽。
self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=stride, padding=1)
# Batch Normalization的目的是使我们的一批(Batch)feature map满足均值为0,方差为1的分布规律。
self.bn1 = nn.BatchNorm2d(ch_out)
self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(ch_out)
#2、创建短接调整输入维度模块
#先判断一下输入的channel与输出的channel是不是不相同
#网络结构写在Sequential中,可以方便的组织网络结构。
#这部分是短接的额外单元extra module
if ch_out != ch_in:
#[b,ch_in,h,w] => [b,ch_out,h,w]
#这里使用的是1*1的卷积单元,为了是只见ch_in改变为ch_out,其他size都不变。
#此外还要确保参数stride与最开始第一次卷积的stride保持一致。这样长和宽才是一致的。
self.extra = nn.Sequential(
nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=stride),
nn.BatchNorm2d(ch_out)
)
else:
#这里是相同的情况,相同的话就什么也不做。
self.extra = nn.Sequential()
#3、编写forward函数
def forward(self, x):
"""
:param x: [b,ch,h,w]
:return:
"""
#4、调用init构建好的两个convolution单元,并在其中添加一个relu激活函数
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
#5、编写short cut部分
#上面卷积层的结果,需要和最开始的输入进行相加操作。
#这里需要注意的是,输出与输入的维度相同才可以进行矩阵相加
#所以我们给短接部分添加一个模块,这个模块用于调整输入的维度,使其能够和输出相加。
#这部分叫extra module
#如果ch_in != ch_out
#通过 extra module: 将[b,ch_in,h,w] =>变为 [b,ch_out,h,w]
# element.wise add 矩阵各个元素相加:
out = x = self.extra(x) + out
return out
#这里是ResNet18类:
class ResNet18(nn.Module):
def __init__(self):
super(ResNet18, self).__init__()
#1、先创建一个卷积层,将ch_in=3转为ch_out=64。
#这里的stride设置为3
#padding设置为0
self.conv1 = nn.Sequential(
nn.Conv2d(3,64,kernel_size=3,stride=3,padding=0),
nn.BatchNorm2d(64)
)
# 2、紧接着4个ResBlk部分followed 4 block
# [b,64,h,w] => [b,128,h,w]
self.blk1 = ResBlk(64, 128,stride=2)
# [b,128,h,w] => [b,256,h,w]
self.blk2 = ResBlk(128, 256, stride=2)
# [b,256,h,w] => [b,512,h,w]
self.blk3 = ResBlk(256, 512,stride=2)
# [b,512,h,w] => [b,1024,h,w]
self.blk4 = ResBlk(512, 512,stride=2)
#3、跟一个线性层linear,变成10类
self.outlayer = nn.Linear(512*1*1, 10)
def forward(self,x):
"""
:param x:
:return:
"""
#1、初始卷积层经过激活函数
#将self.conv1(x)经过激活函数处理
x = F.relu(self.conv1(x))
print('经过初始卷积层和relu:',x.shape)
#2、经过4个block
# [b,64,h,w] => [b,1024,h,w]
x = self.blk1(x)
print('经过第一个blk1:',x.shape)
x = self.blk2(x)
print('经过第二个blk2:',x.shape)
x = self.blk3(x)
print('经过第三个blk3:',x.shape)
x = self.blk4(x)
print('经过第四个blk4:', x.shape)
# print('after conv:', x.shape) # [b, 512, 2, 2]
#3、最终特征图大小设置为(1,1)
#[b,512,h,w] => [b,512,1,1]
#adaptive_avg_pool2d意思就是不管之前的特征图尺寸为多少,只要设置为(1,1),那么最终特征图大小都为(1,1)
x = F.adaptive_avg_pool2d(x,[1,1])
print('经过adaptive_avg_pool2d:', x.shape) #after pool: torch.Size([2, 512, 1, 1])
#4、图像打平处理
#取第一个维度,即batch维度,其他维度相乘512*1*1
x = x.view(x.size(0), -1)
print('经过view:',x.shape) #torch.Size([2, 512])
#5、跟一个线性层linear,将打平后的x输入线性层中,变成10类
x = self.outlayer(x)
return x
def main():
#创建一个假的数据集
#这里测试的目的是查看是否报错,如果报错,说明网络中的shape存在不匹配match的情况。
#如果match,就不会报错,如果不match,就会报错。
x = torch.randn(2,3,32,32)
print('初始数据输入:',x.shape)
model = ResNet18()
out = model(x)
print('resnet18结果:',out.shape)
if __name__ == '__main__':
main()
+++++++++++++++++++++++++++++++++++结束编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++结束编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++
4、编写主程序部分,这部分与lenet5的主程序是一致的,只是将lenet5模型换成了ResNet18
import torch
from torch.utils.data import DataLoader
#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;
from torchvision import datasets
from torchvision import transforms
from torch import nn, optim
#将lenet5引入
# from lenet5_test import Lenet5
#将Resnet引入
from resnet_test import ResNet18
def main():
batchsz = 32
#首先引入数据:
#参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
#参数2:是否为train训练集
#参数3:transform数据变换
#参数4:download是否下载
cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
transforms.Resize((32,32)), #调整照片的大小
transforms.ToTensor(), #直接将数据类型转变为tensor
#希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
#将datasets进行多线程下载
cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)
cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
transforms.Resize((32,32)),
transforms.ToTensor(),
#如果加了normalize,train和test必须都添加
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)
#通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
#iter()后数据的类型
# print(type(iter(cifar_train).next()))
#查看长度
# print(len(iter(cifar_train).next()))
#一个是数据集,一个是标签
# x, label = iter(cifar_train).next()
#查看其数据结构
# print('x:',x.shape,'laberl:',label.shape)
#这样就可以使用GPU来进行计算
device = torch.device('cuda')
#引入Lenet5类,并实例化Lenet5
# model = Lenet5().to(device)
#引入ResNet18类
model = ResNet18().to(device)
# print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
print('网络结构:')
print(model)
# 创建一个计算loss的criteon
# nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
# predict与logits的区别在于,predict是logits经过了softmax操作的。
criteon = nn.CrossEntropyLoss().to(device)
#得到一个优化器
#做测试一般都使用Adam这个包。
#优化器optimizer就不需要转到GPU上面了
optimizer = optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(1000):
#===================training部分========================
# 迭代每一次数据
#batchidx表示第多少个batch了
for batchidx, (x, label) in enumerate(cifar_train):
# x: [b,3,32,32]
# label: [b]
x, label = x.to(device), label.to(device)
#如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
#我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
# print('batchidx:',batchidx)
#将数据传入实例化的Lenet5中
logits = model(x)
#logits: [b,10] logits必须给出每个维度的probability
#label: [b] 没有经过one-hot-coding,label是不需要给出probability
#loss: tensor scalar 长度为0的标量。
loss = criteon(logits, label)
#backpropagation 反向传播
optimizer.zero_grad()#将梯度全部清0
loss.backward() #回退进行梯度计算
optimizer.step() #走一遍,跟新了weight权值
#这里我们打印一下
print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来
#==================test部分========================
#将模型变为test模式
#train和test的计算方法是不一样的。
#比如说在test模式下,dropout的概率全为0,不进行drop out
model.eval()
# 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
# test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
#不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
with torch.no_grad():
# 做一个比对,统计对的,和总的数量
total_correct = 0;
total_num = 0
for batchidx, (x, label) in enumerate(cifar_test):
#1、设置test数据和标签
# [b, 3, 32, 32]
# [b]
x, label = x.to(device), label.to(device)
# 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
# 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
# print('batchidx:',batchidx)
# 2、将数据导入模型
# [b,10]
logits = model(x) #这个模型是经过optimizer调整后的
# print('logits:',logits.shape)
#logits: torch.Size([32, 10])
# 3、设置pred
#取logits元素最大的位置作为predict
#在1维上面选出数据最大的那个值所在的idex。
#[b]
pred = logits.argmax(dim=1)
# print('pred:',pred)
# print('label:',label)
#下面是其中一组预测pred和label
# pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
# 1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
# label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
# 9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')
# 4、预测pred与真实label进行比较,统计预测正确的图片数量
#pred与label做比较
#[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
#统计预测正确的图片数量
#这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
total_correct += torch.eq(pred, label).float().sum().item()
#.item()将tensor类型转为numpy
# print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
# print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型
#print('total_correct:',total_correct) #预测正确的图片数量
#total_correct: 4697.0
# 5、统计总图片数
total_num += x.size(0) #所有用于统计的图片总量10000张
# print('total_num:',total_num)
#total_num: 10000
# print('x.shape:',x.shape)
#x.shape: torch.Size([32, 3, 32, 32])
#6、预测准确度accuracy
acc = total_correct / total_num
print('epoch:', epoch, 'acc:', acc)
if __name__ == '__main__':
main()
四、将详细说明代码综合起来 ResNet和Cifar10
resnet_test.py
import torch
from torch import nn
from torch.nn import functional as F
class ResBlk(nn.Module):
"""
resnet block
"""
def __init__(self, ch_in, ch_out, stride=1):
"""
:param ch_in:
:param ch_out:
"""
super(ResBlk, self).__init__() #调用这个类的初始化方法,来初始化这个父类。
#1、构建两个convolution单元
#这里设置一个stride,用于减少数据量,衰减长和宽。
self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=stride, padding=1)
# Batch Normalization的目的是使我们的一批(Batch)feature map满足均值为0,方差为1的分布规律。
self.bn1 = nn.BatchNorm2d(ch_out)
self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(ch_out)
#2、创建短接调整输入维度模块
#先判断一下输入的channel与输出的channel是不是不相同
#网络结构写在Sequential中,可以方便的组织网络结构。
#这部分是短接的额外单元extra module
if ch_out != ch_in:
#[b,ch_in,h,w] => [b,ch_out,h,w]
#这里使用的是1*1的卷积单元,为了是只见ch_in改变为ch_out,其他size都不变。
#此外还要确保参数stride与最开始第一次卷积的stride保持一致。这样长和宽才是一致的。
self.extra = nn.Sequential(
nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=stride),
nn.BatchNorm2d(ch_out)
)
else:
#这里是相同的情况,相同的话就什么也不做。
self.extra = nn.Sequential()
#3、编写forward函数
def forward(self, x):
"""
:param x: [b,ch,h,w]
:return:
"""
#4、调用init构建好的两个convolution单元,并在其中添加一个relu激活函数
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
#5、编写short cut部分
#上面卷积层的结果,需要和最开始的输入进行相加操作。
#这里需要注意的是,输出与输入的维度相同才可以进行矩阵相加
#所以我们给短接部分添加一个模块,这个模块用于调整输入的维度,使其能够和输出相加。
#这部分叫extra module
#如果ch_in != ch_out
#通过 extra module: 将[b,ch_in,h,w] =>变为 [b,ch_out,h,w]
# element.wise add 矩阵各个元素相加:
out = x = self.extra(x) + out
return out
class ResNet18(nn.Module):
def __init__(self):
super(ResNet18, self).__init__()
#1、先创建一个卷积层,将ch_in=3转为ch_out=64。
#这里的stride设置为3
#padding设置为0
self.conv1 = nn.Sequential(
nn.Conv2d(3,64,kernel_size=3,stride=3,padding=0),
nn.BatchNorm2d(64)
)
# 紧接着4个ResBlk部分followed 4 block
# [b,64,h,w] => [b,128,h,w]
self.blk1 = ResBlk(64, 128,stride=2)
# [b,128,h,w] => [b,256,h,w]
self.blk2 = ResBlk(128, 256, stride=2)
# [b,256,h,w] => [b,512,h,w]
self.blk3 = ResBlk(256, 512,stride=2)
# [b,512,h,w] => [b,1024,h,w]
self.blk4 = ResBlk(512, 512,stride=2)
#跟一个线性层linear,变成10类
self.outlayer = nn.Linear(512*1*1, 10)
def forward(self,x):
"""
:param x:
:return:
"""
#将self.conv1(x)经过激活函数处理
x = F.relu(self.conv1(x))
# print('经过初始卷积层和relu:',x.shape)
#经过4个block
# [b,64,h,w] => [b,1024,h,w]
x = self.blk1(x)
# print('经过第一个blk1:',x.shape)
x = self.blk2(x)
# print('经过第二个blk2:',x.shape)
x = self.blk3(x)
# print('经过第三个blk3:',x.shape)
x = self.blk4(x)
# print('经过第四个blk4:', x.shape)
# print('after conv:', x.shape) # [b, 512, 2, 2]
#[b,512,h,w] => [b,512,1,1]
x = F.adaptive_avg_pool2d(x,[1,1])
# print('经过adaptive_avg_pool2d:', x.shape)
#取第一个维度,即batch维度,其他维度相乘512*1*1
x = x.view(x.size(0), -1)
# print('经过view:',x.shape) #torch.Size([2, 512])
# 跟一个线性层linear,变成10类
x = self.outlayer(x)
return x
def main():
# # 这里我们希望长和宽减半,减少数据量,因为如果不变的话,channel维度不断增加,会导致数据量翻倍。
# #所以我们设置stride步长,让长宽减小
# blk = ResBlk(64,128, stride=4)
# # print(blk) #查看网络结构
# # 创建一个假的数据集
# tmp = torch.randn(2,64, 32, 32)
# out = blk(tmp)
# print('block shape:', out.shape) #block shape: torch.Size([2, 128, 8, 8])
# # print('block:',out)
#创建一个假的数据集
#这里测试的目的是查看是否报错,如果报错,说明网络中的shape存在不匹配match的情况。
#如果match,就不会报错,如果不match,就会报错。
x = torch.randn(2,3,32,32)
# print('初始数据输入:',x.shape)
model = ResNet18()
out = model(x)
print('resnet18结果:',out.shape)
if __name__ == '__main__':
main()
main_test_ResNet.py
import torch
from torch.utils.data import DataLoader
#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;
from torchvision import datasets
from torchvision import transforms
from torch import nn, optim
#将lenet5引入
# from lenet5_test import Lenet5
#将Resnet引入
from resnet_test import ResNet18
def main():
batchsz = 32
#首先引入数据:
#参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
#参数2:是否为train训练集
#参数3:transform数据变换
#参数4:download是否下载
cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
transforms.Resize((32,32)), #调整照片的大小
transforms.ToTensor(), #直接将数据类型转变为tensor
#希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
#将datasets进行多线程下载
cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)
cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
transforms.Resize((32,32)),
transforms.ToTensor(),
#如果加了normalize,train和test必须都添加
transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
]),download=True)
cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)
#通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
#iter()后数据的类型
# print(type(iter(cifar_train).next()))
#查看长度
# print(len(iter(cifar_train).next()))
#一个是数据集,一个是标签
# x, label = iter(cifar_train).next()
#查看其数据结构
# print('x:',x.shape,'laberl:',label.shape)
#这样就可以使用GPU来进行计算
device = torch.device('cuda')
#引入Lenet5类,并实例化Lenet5
# model = Lenet5().to(device)
#引入ResNet18类
model = ResNet18().to(device)
# print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
print('网络结构:')
print(model)
# 创建一个计算loss的criteon
# nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
# predict与logits的区别在于,predict是logits经过了softmax操作的。
criteon = nn.CrossEntropyLoss().to(device)
#得到一个优化器
#做测试一般都使用Adam这个包。
#优化器optimizer就不需要转到GPU上面了
optimizer = optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(1000):
#===================training部分========================
# 迭代每一次数据
#batchidx表示第多少个batch了
for batchidx, (x, label) in enumerate(cifar_train):
# x: [b,3,32,32]
# label: [b]
x, label = x.to(device), label.to(device)
#如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
#我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
# print('batchidx:',batchidx)
#将数据传入实例化的Lenet5中
logits = model(x)
#logits: [b,10] logits必须给出每个维度的probability
#label: [b] 没有经过one-hot-coding,label是不需要给出probability
#loss: tensor scalar 长度为0的标量。
loss = criteon(logits, label)
#backpropagation 反向传播
optimizer.zero_grad()#将梯度全部清0
loss.backward() #回退进行梯度计算
optimizer.step() #走一遍,跟新了weight权值
#这里我们打印一下
print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来
#==================test部分========================
#将模型变为test模式
#train和test的计算方法是不一样的。
#比如说在test模式下,dropout的概率全为0,不进行drop out
model.eval()
# 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
# test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
#不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
with torch.no_grad():
# 做一个比对,统计对的,和总的数量
total_correct = 0;
total_num = 0
for batchidx, (x, label) in enumerate(cifar_test):
#1、设置test数据和标签
# [b, 3, 32, 32]
# [b]
x, label = x.to(device), label.to(device)
# 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
# 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
# print('batchidx:',batchidx)
# 2、将数据导入模型
# [b,10]
logits = model(x) #这个模型是经过optimizer调整后的
# print('logits:',logits.shape)
#logits: torch.Size([32, 10])
# 3、设置pred
#取logits元素最大的位置作为predict
#在1维上面选出数据最大的那个值所在的idex。
#[b]
pred = logits.argmax(dim=1)
# print('pred:',pred)
# print('label:',label)
#下面是其中一组预测pred和label
# pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
# 1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
# label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
# 9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')
# 4、预测pred与真实label进行比较,统计预测正确的图片数量
#pred与label做比较
#[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
#统计预测正确的图片数量
#这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
total_correct += torch.eq(pred, label).float().sum().item()
#.item()将tensor类型转为numpy
# print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
# print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型
#print('total_correct:',total_correct) #预测正确的图片数量
#total_correct: 4697.0
# 5、统计总图片数
total_num += x.size(0) #所有用于统计的图片总量10000张
# print('total_num:',total_num)
#total_num: 10000
# print('x.shape:',x.shape)
#x.shape: torch.Size([32, 3, 32, 32])
#6、预测准确度accuracy
acc = total_correct / total_num
print('epoch:', epoch, 'acc:', acc)
if __name__ == '__main__':
main()