引言
前面带大家搭建了一个简单的神经网络进行曲线拟合,现在通过大家对神经网络的初步了解,我们今天将进行更深入的学习。
正文
由于源码在前面几章公布了,所以有看不懂代码顺序或者不知道怎么拼接使用的可以直接去下载工程源码。话不多说,进入正题。
CNN(卷积神经网络)的核心基础
在写代码之前,我们先来了解一点卷积神经网络的核心基础,其中涉及卷积层、池化层、全连接层在神经网络中扮演的角色,以及实现的具体功能。
卷积层
卷积层的主要作用是对输入的数据进行特征提取,而完成该功能的是卷积层中的卷积核。我们可以将卷积核看作是一个指定窗口大小的扫描器,扫描器通过一次又一次的扫面输入的数据,来提取数据中的特征。如果我们输入的是图像数据,那么通过卷积核的处理后,就可以识别出图像中的重要特征了。 举一个例子,假设有一张32_32_3的输入图像,也就是图像长宽为32_32,并且有RGB三个通道。我们则可以定义一个窗口大小为5_5_3的卷积核,这里面5_5也是指的窗口长宽,3是指的卷积核的深度,对应的就是原图像的三个色彩通道。如果图像时灰度图像,仅有单个色彩通道,那么卷积核深度就是1。
我们在代码的编写过程中还需要对窗口滑动的步长进行定义,卷积核的步长其实就是卷积核窗口每次滑动经过的图像上的像素点数量。
池化层
卷积神经网络中的池化层可以被看作卷积神经网络中的一种提取输入数据的核心特征的方式,不仅实现了对原数据的压缩,还大量减少了参与模型计算的参数,从某种意义上提升了计算效率。其中最常被用到的池化层方法是平均池化层和最大池化层,池化层处理的输入数据在一般情况下是经过卷积操作之后生成的特征图。 池化层也需要定义一个类似卷积层中卷积核的滑动窗口,但是这个滑动窗口仅用来提取特征图中的重要特征,本身并没有参数。
全连接层
全连接层的主要作用是将输入图像在经过卷积和池化的操作后提取的特征进行压缩,并且根据压缩的特征完成模型的分类功能。 其实全连接层的计算比卷积层和池化层更简单,如下图,输入就是我们通过卷积层和池化层提取的输入图像的核心特征,与全连接层中定义的权重参数相乘,最后被压缩成仅有的10个输出参数,这10个输出参数其实已经是一个分类的结果,再经过激活函数的进一步处理,就能让我们的分类预测结果更明显。激活函数的输出结果就是模型预测的输入图像对应各个类别的可能性值。
代码教学
介绍完了核心基础,我们现在进入我们的工程,我们开始搭建CNN网络用于识别我们制作好的数据集。代码如下:
class CNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Sequential(
nn.Conv2d( #(3,60,40)
in_channels=3, #输入图片的通道rgb三通道
out_channels=16,
kernel_size=5, #卷积核
stride=1,
padding=2 #padding = (kernel_size-1)/2
),
nn.ReLU(), #维度变换(3,60,40) --> (16,60,40)
nn.MaxPool2d(kernel_size=2) #维度变换(16,60,40) --> (16,30,20)
)
self.conv2 = nn.Sequential( #(16,30,20)
nn.Conv2d(
in_channels=16,
out_channels=32, # (32,30,20)
kernel_size=5,
stride=1,
padding=2
),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2) #维度变换(32,30,20) --> (32,15,10)
)
self.output = nn.Linear(32*15*10, 24)
def forward(self, x):
out = self.conv1(x)
out = self.conv2(out) #维度变换(Batch,32,25,50)
out = out.view(out.size(0), -1) #(Batch,32*25*50)将其展平
out = self.output(out)
return out
我们定义了卷积核的大小,也就是5_5_3,并且我们将数据集的图片尺寸全部设置成了60*40,因为图片并不复杂因此缩小尺寸不会让图片的特征缺失很多,并且也能够大幅度减小计算量,其实这里把数据集转成二值化的进行识别会更减小计算量,因为这样的话卷积核的深度就可以设置成1,不过当时想体验彩色图片的训练效率怎么样,因此并没有选择二值化。 我们代码中池化层的尺寸是2,以ReLU函数(线性整流函数)作为我们的激活函数。 最后在输出的最后一个参数需要写入我们分类的数量,这个也要跟之前制作的标签里的分类数量进行同步。
搭建好了数据集和神经网络,我们可以进入训练的阶段了,代码如下:
train_path = 'D:/PycharmProjects/Num_distinguish/train_data/labels.txt'
test_path = 'D:/PycharmProjects/Num_distinguish/test_data'
train_data = CNN_h.MyDataset(train_path)
train_loader = Data.DataLoader(dataset=train_data, batch_size=1, shuffle=True, num_workers=0)
cnn = CNN_h.CNN()
optimizer = torch.optim.Adam(cnn.parameters(), lr=0.001)
loss_func = nn.CrossEntropyLoss()
for i in range(10):
for step, (x, y) in enumerate(train_loader):
real_x = Variable(x)
real_y = Variable(y)
pre_y = cnn(real_x)
loss = loss_func(pre_y, real_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
#print('loss:', float(loss.data))
img_dist = IMG_h.Carlicense_distinguish()
img_dist.carlicense_distinguish()
test_data = CNN_h.predicted_data(test_path)
decode_input = []
for i in range(len(test_data)):
pre_test = cnn(test_data[i])
decode_input.append(int(torch.max(pre_test,1)[1].data.numpy().squeeze()))
print(CNN_h.decode_output(decode_input))
UI_h.window(CNN_h.decode_output(decode_input))
我们实例化网络,定义完优化函数和损失函数之后,对图片进行了训练,过程和上一章的训练过程基本一样,因此就不多讲解了,我们下面来看一下实现的效果如何。
能够看到,准确度并不是很高,不过由于我们的数据集样本做的比较粗糙,而且数量也比较少,每个字符估计也就几张图片样本吧,所以能有这样的效果,已经很不错了。
总结
到此我们完成了这个项目数据集搭建,以及神经网络搭建了最后只剩下一些优化的部分,这个我会在下一节最终章提一下。 不过到这一步,已经基本上实现了这个项目,如果希望有更好的结果可以继续钻研神经网络的其他网络算法,优化数据集等。 最后,这章基本结束了我们这个系列,虽然后面还有一章节会提一下如何把训练好的网络模型导出,这样就不用每次都训练再识别了。 虽然这个系列不是很长,只有短短的六七个章节,不过希望大家能从中收获~ 喜欢的可以点赞~~