最近发现周围的大佬都在用pytorch了,作为一个还在用tensorflow1.14的渣中渣:),决定也跟随大佬们的步伐,学习一波pytorch
在网上看了看相关教程(看文档学习是不可能的,这辈子都不可能的),对pytorch有了一点了解,手动搞了个CIFAR10的分类(手动弱鸡),虽然比较弱,还是过来写个总结吧
关于pytorch中相关函数的介绍就不写了,来来去去就是什么卷积啊,激活函数啊,损失函数之类的,基本上tensorflow有的,pytorch都有。不过pytorch有些功能往往是返回一个对象,而不像tensorflow,基本上都是直接的函数,比如写个交叉熵损失,刚入手很可能会写成torch.nn.CrossEntropyLoss(score, label),然后发现报错了,一脸懵。其实CrossEntropyLoss()返回的是一个交叉熵损失对象,不是交叉熵损失的值,标准写法是:criterion=torch.nn.CrossEntropyLoss() loss=criterion(score,label)。不止是交叉熵损失,包括卷积也是一样的,不过激活函数和池化啥的还是函数的形式,写的时候需要注意。
首先讲讲的数据的读取吧
pytorch在torch.utils.data中提供了一个Dataset的类,我们要读取数据时,需要自定义一个数据类,并且继承Dataset,然后torch.utils.data中还提供了一个DataLoader的类,专门用来根据Dataset的子类对象进行数据读取。说的很复杂,其实就是DataLoader对象操作Dataset子类的对象,一段代码解君愁。
先写个大框架
#自定义的数据类
class MyDataset(data.Dataset):
def __init__(self, root):
'''
这里保存所有数据的路径(精确到每个文件的路径,不是root)以及预处理(可有可无)
:param root: root是存放数据的路径
'''
def __getitem__(self, index):
'''
根据index找到第index个数据的路径,读取出内容以及标签,并进行预处理(可有可无)
:param index:
:return:
'''
def __len__(self):
'''
返回所有数据的数量
'''
#DataLoader的操作
myDataset = MyDataset(...)
loader = DataLoader(myDataset, batch_size=cfg.batch_size, shuffle=True, drop_last=True)
for data in loader:#由于DataLoader定义时告诉了batch_size,是否shuffle,drop_last表示最后 不够一个batch的数据遗弃所以直接遍历就能得到按batch_size分隔好,乱序的数据,很方便
inputs, label = data
#拿到数据去跑模型
还是有点迷茫,别着急,上个实例:
from torch.utils import data
from torchvision import transforms #transforms提供一些数据预处理的操作
import os
import cv2
class MyDataset(data.Dataset):
def __init__(self, root):
self.root = root
self.path = os.listdir(self.root) #存放所有数据的路径
self.transforms = transforms.Compose([
transforms.ToTensor(), #把输入图像从0~255转成0~1
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) #图像的标准化两组0.5意味着图像从0~1转成了-1~1
])#这是transforms的标准用法,按顺序执行,transforms还有很多其他功能
def __getitem__(self, index): #这一步是读取单个数据的具体操作,也就是DataLoader的具体操作
path = os.path.join(self.root, self.path[index])#获取单个数据的
pic = cv2.imread(path)#读取数据
pic = cv2.cvtColor(pic, cv2.COLOR_BGR2RGB)#自己写的预处理
pic = self.transforms(pic)#上面定义的transforms预处理
label = int(self.path[index].split('_')[0])#label怎么生成自己定,一般读取数据都是数据和标签一起返回
return pic, label
def __len__(self):
return len(self.path)#数据量
DataLoader部分:
###DataLoader的定义部分
trainSet = MyDataset('./source/pics/train/')
testSet = MyDataset('./source/pics/test/')
trainLoader = DataLoader(trainSet, batch_size=cfg.batch_size, shuffle=True, drop_last=True)
testLoader = DataLoader(testSet, batch_size=cfg.batch_size, shuffle=True, drop_last=True)
###下面是读取后的操作
for n, data in enumerate(trainLoader):
inputs, labels = data
inputs = Variable(inputs)#数据放入模型之前貌似都要转成Variable(torch.autograd.Variable)
labels = Variable(labels)
opt.zero_grad()
outputs = myNet(inputs)
ls = torch.nn.CrossEntropyLoss()(outputs, labels)
ls.backward()
opt.step()
以上是最基本的数据读取部分,还是很方便的。实际操作中你也可以自己写个函数来读数据(还要搞个随机数来保证乱序,没错我干过),但是就,emmm,比较蠢的感觉。
接下来说一下模型搭建部分吧
在创建模型类时,需要继承torch.nn.Module类,自定义的模型类必须有两个方法,一个是__init__(),一个是forward()。在实际创建中往往会把有需要学习的部分放在__init__中先定义,比如卷积层,全连接层。而没有需要学习的部分则直接在forward中使用。先上个模板:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
'''
这里定义一些需要学习的层
'''
def forward(self, x):#前向函数可以有多个输入,这里只试验了单输入的
'''
这里写网络的具体模型,也就是前向
'''
再上个具体例子:
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)#这里把要学习的内容定义好,forward调用
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), 2)#像池化这种不用学习的就直接用
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
####模型调用部分
net = Net()
output = net(input)#这里需要注意,不要net.forward(input),直接net(input)走的就是forward
从__init__部分可以看到,定义的内容都是返回了一个对象,比如conv1接收的是个对象,而且调用也是直接‘对象名(输入)’的形式和模型的调用很像,所以,模型中可以嵌套模型!!
最后总结一下模型训练部分
模型训练无非就是两个部分,一个是选择合适的损失函数,一个是选择合适的优化器。损失函数一般都在torch.nn中,比如CrossEntropyLoss、MSELoss等,优化器则在torch.optim中,常用的优化器都有,比如SGD,Adam。模型训练的主要步骤如下:
net = Net()#创建模型
opt = optim.优化器(net.parameters(), lr=学习率)#创建优化器,传入学习参数
for ... in dataset:#进入训练循环
output = net(input)
#清空所有待学习参数(定义优化器时已经告诉优化器哪些参数要学习了)的梯度
#不然他会拿累积的梯度来更新数据,这样不对,我们是那每一轮的梯度更新数据
opt.zero_grad()
loss = 损失函数(output, input)#算出本轮损失
loss.backward()#利用loss反向传播计算出本轮的参数梯度
opt.step()#根据梯度更新参数
照例上个具体实现:
trainLoader = DataLoader(trainSet, batch_size=cfg.batch_size, shuffle=True, drop_last=True)
testLoader = DataLoader(testSet, batch_size=cfg.batch_size, shuffle=True, drop_last=True)
myNet = Net()
opt = optim.Adam(myNet.parameters(), lr=cfg.learning_rate)#优化器定义时需要传入需要学习的参数
for i in range(1, cfg.epoch+1):
for n, data in enumerate(trainLoader):#用DataLoader很方便的以batch形式遍历数据集
inputs, labels = data
inputs = Variable(inputs)#输入数据得先转为Variable
labels = Variable(labels)
opt.zero_grad()#把优化器的梯度先清空
outputs = myNet(inputs)
ls = torch.nn.CrossEntropyLoss()(outputs, labels)
ls.backward()#根据损失反向传播算出所有待学习参数的梯度
opt.step()#优化器定义时已经明确了待学习参数有哪些,直接根据反向传播得到的梯度更新参数
完整的代码放在了Github上面,有兴趣的可以去康康:https://github.com/Zou-CM/pytorch_practice/tree/master/CIFAR10
由于要研究一下自己的数据是如何读取的,我把原来的CIFAR10文件读取出来存成png格式了(文件名包含了相应的label),并分好的test和train,如果想测试代码,把数据下载下来,放在CIFAR10/source/pics下(由于数据没放上github,然后github又传不上空文件夹,所以source/pics文件夹得自己创)。
CIFAR10百度云: https://pan.baidu.com/s/17XcvCu2rI5Ta5wwHVpsifg 提取码: j6ec
最后训练的结果准确率在63左右
别看准确率好像很低,毕竟10分类,随机抽只有10的准确率,意味着模型训练是正常的,准确率不高只是因为用的网络很简单
后面还会再写一个复杂一点的实践来研究研究数据预处理部分和模型的保存和读取,大概12月中旬吧,原本这个月搞搞的,突然被拉去搞投标我服了,做好了通宵两周的准备:)