赞
踩
图像分类的算法有很多,大部分其实都用CNN来提取图像的特征,今天我们一起来学习用PyTorch做CIFAR10数据集的分类。CIFAR10数据集是一个10个类的图像数据集,图片大小是32*32的。首先我们来看LeNet模型,这个模型进行图像识别的流程入下图所示:
由上图可以清晰地看出整个的流程是经过一个卷积层,然后经过池化,再经过卷积,再进行一层池化层,最后是三个全连接层,最终输出10维的向量就是分类的结果,我们先来看看lenet5.py代码:
import torch from torch import nn #from torch.nn import functional as F class LeNet5(nn.Module): def __init__(self): super(LeNet5, self).__init__() # 卷积&池化 self.conv_unit = nn.Sequential( # 卷积层,RGB3个channel,然后6个卷积核进行卷积,卷积核是5*5的,步长是1,没有zero padding nn.Conv2d(3, 6, kernel_size=5, stride=1, padding=0), # 这里选择的是最大值池化,因为均值池化的效果很差,池化核是2*2的,步长是2,没有 zero padding nn.MaxPool2d(kernel_size=2, stride=2, padding=0), # 原理同上 nn.Conv2d(6, 16, kernel_size=5, stride=1, padding=0), # 原理同上 nn.MaxPool2d(kernel_size=2, stride=2, padding=0) ) # 全连接 self.fc_unit = nn.Sequential( # 线性层,输入维度是16*5*5,输出维度是120,在图中有标识 nn.Linear(16*5*5, 120), # 使用激活函数ReLU nn.ReLU(), # 第二个全连接层 nn.Linear(120, 84), # 激活函数 nn.ReLU(), # 输出层 nn.Linear(84, 10) ) tmp = torch.randn(2, 3, 32, 32) out = self.conv_unit(tmp) print('conv out: ', out.shape) # 正向传播函数 def forward(self, x): # 数据集batch的数量 batch_size = x.size(0) # 卷积&池化操作 x = self.conv_unit(x) # 通过view函数来调整x的大小 x = x.view(batch_size, 16*5*5) # 全连接层操作 logits = self.fc_unit(x) return logits def main(): # 实例化net模型 net = LeNet5() # 初始化两张3通道的32*32的图像 tmp = torch.randn(2, 3, 32, 32) 进行lenet操作流程 out = net(tmp) # print('LeNet out: ', out.shape) if __name__ == '__main__': main()
我们初始化好了lenet5神经网络之后,我们再来看看主函数的代码:
# 导入必要的python库 from torchvision import datasets from torchvision import transforms from torch.utils.data import DataLoader from cifiar.lenet5 import LeNet5 from torch import nn, optim import torch from cifiar.resnet import ResNet def main(): # 初始化batch size为32 batch_size = 32 # 定义cifar10训练集,确保训练集数据大小是32*32 cifar_train = datasets.CIFAR10('.', train=True, transform=transforms.Compose([ transforms.Resize((32, 32)), transforms.ToTensor() ]), download=True) # 使用数据加载器加载训练集,并随机打乱 cifar_train = DataLoader(cifar_train, batch_size=batch_size, shuffle=True) # 定义测试集 cifar_test = datasets.CIFAR10('.', train=False, transform=transforms.Compose([ transforms.Resize(32, 32), transforms.ToTensor() ]), download=True) # 使用数据加载器加载测试集 cifar_test = DataLoader(cifar_test, batch_size=batch_size, shuffle=True) x, label = iter(cifar_train).next() print('x: ', x.shape, 'label: ', label.shape) #device = torch.device('cuda') #model = LeNet5().to(device) # 初始化LeNet5模型 model = LeNet5() #model = ResNet() # 模型优化的标准是交叉熵 criteon = nn.CrossEntropyLoss() # 使用Adam进行优化,加载模型的参数,设置学习率为0.001 optimizer = optim.Adam(model.parameters(), lr=1e-3) print(model) # 循环100个epoch for epoch in range(100): # 将 module 设置为 training mode model.train() for batchidx, (x, label) in enumerate(cifar_train): logits = model(x) loss = criteon(logits, label) # 反向传播 optimizer.zero_grad() loss.backward() optimizer.step() print(epoch, loss.item()) # 将模型设置成 evaluation model.eval() # 表示下面的部分不需要求梯度 with torch.no_grad(): # test total_correct = 0 total_num = 0 for x, label in cifar_test: logits = model(x) # 预测的类别 pred = logits.argmax(dim=1) # 总的正确分类数 total_correct += torch.eq(pred, label).float().sum().item() total_num += x.size(0) # 正确率 acc = total_correct / total_num print(epoch, 'acc: ', acc) if __name__ == '__main__': main()
因为配置很挫,所以我就跑了10个epoch,然后我将前10个epoch的准确率画出来如下图所示:
可以看出,最后lenet模型的图片分类的准确度可以达到60%以上,效果不太好。针对这一问题,我们可以使用何恺明等华人学者提出的resnet,这个网络也被称作深度残差网络,这个模型允许网络尽可能加深,一般的神经网络如果加深的话很容易出现过拟合,而resnet模型中,如果这几个模块的网络效果不好,就将其shortcut,因此不会将前面几层的输出直接连接到后面几层中去。如图就是最经典的resnet神经网络模型:
可以看出整个的resnet网络模型是由很多个小的block级联起来的,每一个block都有一个像电路短路一样的shortcut,我们就可以根据resnet模型来编写模型的代码如下所示:
import torch from torch import nn from torch.nn import functional as F # 定义resnet网络模型的block class ResBlk(nn.Module): def __init__(self, ch_in, ch_out): """ :param ch_in: :param ch_out: """ super(ResBlk, self).__init__() # 对输入的数据进行卷积操作,卷积核是3*3,步长是1,有长为1的padding self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=1, padding=1) # 进行batch normalization 操作,就是将数据分布批量规范化,方便后续的训练 self.bn1 = nn.BatchNorm2d(ch_out) # 将上一层的输出作为这一层的输入进行训练,同样卷积核是3*3,步长是1,四周加了一层padding self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1) # 将第二层的数据进行batch normalization操作 self.bn2 = nn.BatchNorm2d(ch_out) # 需要保持输入和输出的size要相同,否则就加一个单元让输入和输出的size保持相同 self.extra = nn.Sequential() # [b, ch_in, h, w] =>[b, ch_out, h, w] if ch_out != ch_in: self.extra = nn.Sequential( nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=1), nn.BatchNorm2d(ch_out) ) def forward(self, x): """ :param x: :return: """ # 第一层卷积和batch norm操作,加一层ReLU激活函数 out = F.relu(self.bn1(self.conv1(x))) # 第二层卷积和batch norm操作 out = self.bn2(self.conv2(out)) # 将shortcut输出的x和前面block卷积操作的结果进行叠加 out = self.extra(x) + out return out # 为了减轻CPU/GPU负担,我将输入和输出维度以及block的个数设置得很小,其实用GPU的话,可以适当增加维度和block的个数 class ResNet(nn.Module): def __init__(self): super(ResNet18, self).__init__() # 定义卷积操作 self.conv1 = nn.Sequential( nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(16) ) # followed 4 blocks,这里我只用两个block,减轻CPU的负担 self.blk1 = ResBlk(16, 16) self.blk2 = ResBlk(16, 32) #self.blk3 = ResBlk(32, 64) #self.blk4 = ResBlk(64, 128) # 定义输出层,将维度降到10输出,完成图像分类 self.outlayer = nn.Linear(32 *32 * 32, 10) # 进行前向传播操作 def forward(self, x): """ :param x: :return: """ # 卷积核block级联 x = F.relu(self.conv1(x)) x = self.blk1(x) x = self.blk2(x) #x = self.blk3(x) #x = self.blk4(x) # 检查x的数据size并做调整 x = x.view(x.size(0), -1) # 输出结果 x = self.outlayer(x) return x def main(): blk = ResBlk(64, 128) tmp = torch.randn(2, 64, 32, 32) out = blk(tmp) print('blk: ', out.shape) model = ResNet18() tmp = torch.randn(2, 3, 32, 32) out = model(tmp) print('resnet: ', out.shape) if __name__ == '__main__': main()
写好了resnet网络模型之后,我们就可以将main.py中的模型设置改成model = ResNet()
,将原先的model = LeNet5()
屏蔽掉就可以了,然后我们就可以跑代码了:
因为为了节省消耗,我把网络模型设置成最简单的了,各位朋友如果GPU牛逼的话,可以设置更复杂的维度和网络模型,可以输出更好的结果,上图是第一个卷积层和两个block中的参数信息,因为这块代码的网络模型很简单且维度很低,所以图片分类的结果准确率也在60%左右,但是相信网络模型block级联数和维度的增加,准确率也会随之增加的,因为resnet网络模型不会因为网络模型的深度增加而降低准确率。这只是一个最简单的resnet模型,希望可以帮助大家对大神何恺明的resnet模型有一个很好的理解。文中如有纰漏,也请大家不吝指教;如有转载,也请注明出处,谢谢大家。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。