在经历了数据->黑白图像的应用实验后,我们在本次实验中将使用CIFAR10数据集进行多通道的彩色图像的分类。由于彩色图像内容更多,在模型训练时将更加收到数据集特性(大小、图像质量等)的影响,模型的效果也会有很大不同。我们在本次实验中,将分别使用经过预训练的和未经过与训练的ResNet18网络进行训练和评估。
import torch #PyTorch
import torch.nn as nn #PyTorch算子等
import numpy as np # numpy,用于数据处理
import matplotlib.pyplot as plt # matplotlib,用于图像绘制
from torchvision.datasets.cifar import CIFAR10 # CIFAR10数据集
from torchvision.models import resnet18 # resnet18网络
from torchvision.transforms import ToTensor # 图像转换函数
from torch.utils.data import DataLoader # 数据加载器
%matplotlib inline
plt.rcParams['font.family'] = 'Microsoft YaHei'
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 使用GPU加速
transform = ToTensor()
import ssl
ssl._create_default_https_context = ssl._create_unverified_context # 由于证书问题,我们需要屏蔽ssl验证
作为一个经典的数据集,cifar也在torchvision库中包含。我们使用如下代码即可加载数据集:
dataset_train = CIFAR10(
'./data/cifar', # 下载或加载的地址
True, # 是否为训练集(用于梯度追踪设置)
transform, # 数据转化
download=True # 是否下载
)
dataset_test = CIFAR10(
'./data/cifar',
False,
transform
)
dataloader_train = DataLoader(dataset_train, 32)
dataloader_test = DataLoader(dataset_test, 32)
fig, *ax = plt.subplots(2,4)
for i in range(8):
ax[0][i//4][i%4].set_xticks([])
ax[0][i//4][i%4].set_yticks([])
ax[0][i//4][i%4].imshow(dataset_train.data[i])
ax[0][i//4][i%4].set_xlabel(dataset_train.classes[i])
print('train size:%s'%(','.join(torch.tensor(dataset_train.data.shape).numpy().astype(np.str_))))
print('test size:%s'%(','.join(torch.tensor(dataset_test.data.shape).numpy().astype(np.str_))))
模型我们在前面一个实验中已经构建完毕,这边我们给出网络结构:
勘误: 在前一篇中,我的模型代码写错了,正确的模型初始化部分代码应当修改为:
def __init__(self, in_channels=1, num_classes=10, with_residual=True):
super(ResNet18, self).__init__()
self.sec1 = torch.nn.Sequential(
torch.nn.Conv2d(in_channels, 64, 7, stride=2, padding=3),
torch.nn.BatchNorm2d(64),
torch.nn.ReLU(),
torch.nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
self.sec2 = torch.nn.Sequential(
ResBlock(64, 64, 1, with_residual)
)
self.sec3 = torch.nn.Sequential(
ResBlock(64, 128, 2, with_residual)
)
self.sec4 = torch.nn.Sequential(
ResBlock(128, 256, 2, with_residual)
)
self.sec5 = torch.nn.Sequential(
ResBlock(256, 512, 2, with_residual)
)
self.pool = torch.nn.AdaptiveAvgPool2d(1)
self.flatten = torch.nn.Flatten()
self.linear = torch.nn.Linear(512, num_classes)
这边我们不需要再自己写模型了,只需要使用torchvision中内置的resnet18模型即可:
model_not_pre = resnet18(pretrained=False).to(device) # 未预训练的模型
model_yet_pre = resnet18(pretrained=True).to(device) # 预训练好的模型
这边,我们发现了一个新的东西:预训练。这是什么呢?
预训练就是使用别人已经训练了一部分的模型,迁移学习因此是使用其他数据集进行预训练后、再自己的数据集上使用这个预训练模型再训练的学习过程。
这边找到一个非常好的解释:
由于疫情原因,我的2080Ti被囚禁在出租屋了,因此本次训练使用的电脑是商务本,使用MX250显卡进行模型训练,本次训练两个模型,训练的代码如下:
crit = nn.CrossEntropyLoss()
opti = torch.optim.SGD(model_not_pre.parameters(), 0.01)
losses_not_pre = []
for epoch in range(8):
sum_loss = 0.0
for i, data in enumerate(dataloader_train):
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
opti.zero_grad()
outputs = model_not_pre(inputs)
loss = crit(outputs, labels)
loss.backward()
opti.step()
sum_loss += loss.item()
if i % 100 == 99:
print('[%d, %d] loss: %.03f'
% (epoch + 1, i + 1, sum_loss / 100))
losses_not_pre.append(sum_loss)
sum_loss = 0.0
with torch.no_grad():
correct = 0
total = 0
for data in dataloader_test:
images, labels = data
images, labels = images.to(device), labels.to(device)
outputs = model_not_pre(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('第%d个epoch的识别准确率为:%d%%' % (epoch + 1, (100 * correct / total)))
torch.save(model_not_pre.state_dict(), 'model_not_pre_%03d.pth' % ( epoch + 1))
结果如下。花了非常非常多的时间,效果还不好:
训练有预训练版本代码类似,这边给出训练结果:
我们将训练时的损失通过折线图的方式绘制出来:
plt.plot(losses_not_pre, label='pretrain = False', color='#cc0000', alpha=0.5)
plt.plot(losses_yet_pre, label='pretrain = True', color='#00cc00', alpha=0.5)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend()
plt.title('comparison of "pretrain"')
plt.show()
由对比图我们发现。尽管一开始的损失,预训练后的模型还略高于没有预训练的模型,但是在后续训练的过程中,我们明显可以看出,预训练模型收敛速度更快,模型效果更好。
**分析:**预训练就像老师教课,可能用到自己的题目上刚开始可能不太好,但是老师教过之后更容易融会贯通,因此表现会越来越好且远高于没有预训练。
我们依然直接调用系统摄像头进行测试:
import cv2
from PIL import Image
import numpy as np
import torch
import torch.nn as nn
import datetime
cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)
FPS = 24
cap.set(cv2.CAP_PROP_FPS, FPS)
model_yet_pre = model_yet_pre.eval()
while True:
ret, frame = cap.read()
if not ret:
print("Can't receive frame (stream end?). Exiting ...")
break
frame = Image.fromarray(frame)
frame = frame.resize((32,32),Image.BICUBIC)
frame = np.asarray(frame)
frame = np.array([frame])
frame = frame.transpose(0,3,1,2)
try:
print(datetime.datetime.now(),dataset_train.classes[torch.argmax(model_yet_pre(torch.tensor(frame,dtype=torch.float).to(device)))],end='\r')
except:
pass
cv2.namedWindow('frame', cv2.WND_PROP_FULLSCREEN)
cv2.imshow('frame', frame.transpose(0,2,3,1).squeeze(0))
if cv2.waitKey(1) == ord('q'): break
cap.release()
cv2.destroyAllWindows()
通过阅读论文(传送门),我们了解到了几种不同深度的ResNet。汇总表格如下:
首先是共同点:无论是深度为多少的ResNet都将网络分成了五部分。分别是:conv1,conv2_x,conv3_x,conv4_x,conv5_x。
然后是显而易见的不同点:每个网络深度不同。
那么,深度更深有什么作用呢?随着深度的加深,ResNet对于抽象特征的提取能力加强,因而能够在更加复杂精细的分类任务上展现优越的性能。但是,随着网络一同增加的还有计算量,因此:
看了很多论文,最终找到如下思维导图,觉得是对这些网络一个非常好的概括了:
本次实验是卷积神经网络的最后一个实验了。在经过前面的这些学习后,我们对卷积神经网络从概念、原理、实现等多方面进行了学习,通过自己动手搭建一个个网络,我们了解了其中部分的奥秘。当然,卷积神经网络由于其不保存历史输入,在一些时间关联性大的预测上效果不佳。因此,我们即将学习循环神经网络。
然后,关于CNN的思维导图如下:
1 — https://arxiv.org/pdf/1512.03385.pdf
2 — https://arxiv.org/pdf/1603.05027.pdf
3 — https://arxiv.org/pdf/1605.07146.pdf
4 — https://arxiv.org/pdf/1603.09382.pdf