目录
前言:
本篇主要记录在研究翻拍图片识别程序源码过程中的理论学习成果、对模型设计的解读和其中的数学原理。由于程序代码实现来自外部,在未经作者和公司允许的情况下,这里不会贴出程序的源代码。
主要表述都是个人的想法和认识,避免摘抄文档材料,可能会有偏差或错误,欢迎感兴趣的同学一起讨论研究。
一、需求背景
此需求的提出是基于这样一个业务场景:
XX企业在提供售后服务如安装、检测、维修等上门服务时,服务人员需要使用终端将产品包含机编码的部分拍照上传系统,并且终端会在图片上增加工单号、拍照时间和拍摄地址信息的水印。
在这个过程中,发现一种情况,存在部分服务人员没有现场拍摄照片上传,而是翻拍以前的或别人的照片上传。
基于此场景,客户提出希望实现系统自动识别出翻拍图片。
简单分析需求可以得出,需要实现这样一个算法程序:输入为一张图片,经过算法模型处理,输出为一个分类(正常图片, 翻拍图片)。
拿到样本图片后,浏览正常图片和翻拍图片,首先对分类特征可以有一个直观的认识,基于经验,我们知道如果你对着电脑或手机屏幕翻拍图片后,会发现照片上有水状波纹(见下图),这种波纹叫做莫尔条纹,它的产生是因为屏幕刷新频率低于手机的快门速度产生的。所以可以得出,识别翻拍图片是可以有明显统一特征的。
图片1
这种特征是人脑可以识别的,那么机器又该如何识别呢?
实践中,深度学习在这一领域有成熟的应用,深度学习的模型有很多种,其中对于图像识别效果最稳定的就是卷积神经网络模型(CNN)。
二、知识储备
这里对相关知识做简要说明,不做展开,如果对相关理论有兴趣,可以自行查阅资料。
1、深度学习
深度学习是机器学习的一个方向,主要在三个领域广泛应用:计算机视觉、声音识别、自然语言处理。而机器学习的“学习”二字,在我看来,体现在其中的模型机制使模型自身具有可学习的特性,即训练的次数和样本越多,预测的结果就越准确。
深度学习基本上可以跟人工神经网络挂钩,因为深度学习的概念就来自于对人工神经网络的研究,其目的是模拟人脑的机制来解释复杂数据并进行分析学习。
比如人看到一张牛的图片立刻就能给它分类到牛这个类别中,这个过程中,人接受到的信息是这张图片假设1024*1024个像素点,以及每个像素点上的颜色,通过这些像素点组合出一些低层次的特征比如耳朵的形状、牛角的形状、头的形状、身体的花色、蹄子的形状等等,在通过低层次的特征抽象成高层次的属性类别或特征,最后归为一个认知。
当然人不可能出生就认识这是个牛,而是在长大后他可能看过实物、看过图片、看过简笔画,然后被父母老师指出这就是牛(即监督角色,对训练样本打标签),他甚至不需要被教授牛都有什么特征,而是在训练的过程中就自然而然学习到了牛的各种特征。
这就是深度学习的机制,通过大量的训练,在学习的过程中通过不断监督反馈,总结出各种分布式低层次表征,形成记忆和认知模式。当出现新的样本时,就可以对其通过进行低层次特征的识别,而形成更加抽象的高层次认知了,即准确预测。
在机器学习中,不论是分类问题还是回归问题,对最终结果的表述都称为预测,因为在没有人工确认之前,机器识别的结果都是一个概率的猜测,是有几率失真的,所以对分类问题的识别动作也称为预测。
深度学习的模型使用非循环的有序流向图来表示,流向图中的每一个节点表示一个基本计算并产生一个计算结果,计算的结果又被他的子节点所使用。因流向图有一个重要的属性,深度:从输入到输出的最长路径的长度。所以被称为深度学习。
深度学习区别于传统的浅层学习的特点:
- 强化了模型结构的深度,通常有几层甚至几十层的隐藏节点。
- 浅层学习是人工来构造特征规则,即算法,虽然其参数可反馈优化,但学习程度受限于固定的特征规则;而深度学习利用大数据来自我学习特征(只提供训练样本的标签),并且通过多层的特征转换,将样本原空间的特征变换到一个又一个新的特征空间,形成一个多层又有联系的大特征空间,在适应性和准确性方面都更优秀。
2、卷积神经网络
卷积神经网络(CNN),也被称作平移不变人工神经网络,是深度学习的代表算法之一。如果说深度学习是模仿人脑的学习机制,那么卷积神经网络就是更具体的模仿大脑的视知觉机制来构建的。其特点是包含卷积计算,可以对多维数据进行平移不变的特征提取,然后一层一层的对较小的特征提取更大的特征,最终总结为模式识别或分类的认知。
建议学习一片博文,有精彩的说明:https://www.cnblogs.com/wj-1314/p/9754072.html
3、PyTorch框架
PyTorch是Facebook在深度学习框架Torch的基础上使用Python重写的一个全新的深度学习框架。PyTorch支持GPU计算,所以可以利用GPU并行加速计算;支持动态神经网络,利用梯度下降法的反向求导技术,可以自动改变网络的行为;源码简洁直观,提供很多高级功能,可以快速搭建神经网络和进行训练。
程序作者是使用Python编写,使用PyTorch框架实现。本篇不涉及代码实现,所以不需要了解框架,想在深度学习方向发展的同学可以自行学习,类似的框架还有Tensorflow、Keras、Theano、Caffe等等。
4、张量
张量是PyTorch和TensorFlow框架处理的核心数据类型,原始数据经过变形转化为张量类型的数据集合输入模型中,张量可以是任意维度。其数学形式可以理解为N维数组,框架中的张量作为数据类型包含很多的对象函数和类方法方便使用。
单个数值称为标量。
向量的概念大多数人清楚,其表示一个既有长度又有方向的量,在坐标系中用数组[a, b]作为数学表示,这是二维空间向量,三个坐标则是三维空间向量,更多的则是多维空间向量。这里的维度是指代表的空间维度,其实不管什么向量其实都是一维数组,也就是一维张量。
所以标量=零维张量,向量=一维张量,矩阵=二维张量。
张量的概念不过是对事物描述的高层次架构。
这里插一句,我之前做过对验证码的识别,应用的是谷歌的Tesseract模型,基本原理是使用多维向量表示一个识别对象的特征,如一个数字0的特征使用50维向量表示,多维向量超越三维向量的空间想象,所以称为超立方体模型。其原理与卷积神经网络相印证,就贯通了,超立方体模型不过是通过像素位置直接提取特征映射到分类中,即只有一个神经网络的线性层。
5、梯度下降法
我们的主旨是应用,大家也不会想看梯度下降的公式是什么。主要是理解梯度下降的概念、原理和作用。
在模型训练过程中,如何确定训练的参数符合要求了呢?会通过使用一个损失(误差/代价)函数,损失函数的目标是让结果趋近于0。怎么调整参数才能让损失函数接近于0呢?其实就是让计算机自己猜,但是不能随便猜,不然哪里知道什么时候会猜对。这就要根据梯度去猜,梯度指的是这个点在函数的导数。
导数我们回忆一下,高中教过,某个点导数大于0说明它周围是单调递增,导数小于0说明是单调递减。如果是单调递增,就把猜的数减小;如果单调递减,就把猜的数增大。也就是说按导数相反的方向走,这是梯度下降法的原理。
举个例子:假设损失函数是x平方。见下图:
图片2
x平方的导数还记得吧:2x。x大于0时导数大于0,x小于0时导数小于0。所以计算机猜测x的值,初始值如果在第一象限,计算机就按梯度下降的原则一步一步向下走,最终接近损失函数的目标。
对于多元函数就是求偏导数。到此看到二元偏导数时,我就放弃了。
不过对原理的直观理解还是可以有的:想像一个山谷,你在山腰但是周围都是大雾,只能看到有限的范围,需要走到谷底,怎么走?
应用时,求导自然是计算机去做,甚至对于PyTorch深度学习框架,可以自动实现链式求导,从而使模型无延迟的通过反馈学习参数。
三、模型解读
完整的卷积神经网络模型包含了输入层、隐藏层和输出层。
1、输入层
卷积神经网络可以处理多维数据,得益于其模型的数据基本单元Tensor(张量)。所以输入层的任务就是对原始数据(图片、声音、文字)进行预处理,转换成多维度的张量,并进行标准化和归一化处理,以便于模型计算。
本篇模型的输入层处理过程:
- 客户端将图片base64加密,在网络传输中使图片不直接可读。
- 服务端接收数据后,对其进行base64解密,得到图片数据。
- 将图片数据利用OpenCV视觉库转换为三维数组,即图片的数学表示:二维像素点阵+RGB三原色通道(所以图片天然就是一个三维张量,其数学表示不会丢失任何数据)。
- 对三维数组进行标准化,其实际意义为对图片大小进行标准化。如果图片像素点不是512*512,就利用视觉库对它进行缩放,这样数组大小就标准化为512*512*3。
- 每一个像素点的数学表示是三原色的值(范围在0~255)的数组,如[ 17, 51, 127],但读出来的颜色通道是反的,即BGR,所以需要转换为标准的RGB格式:如[127 , 51, 17]。
- 这时我们发现数组的结构并不是我们所想要的,因为最内层是RGB的数值,上一层是图片的宽度,相当于每次看到的是一条横线的像素点+颜色,这对于图像识别没有意义。我们想要的是将一张图片转换R、G、B三个通道的的像素点阵,这样才能对图像的轮廓正确描述,就像一张宣纸揭成三张宣纸一样,见下图:
图片3
- 所以,对数据进行矩阵转置,变换成3*512*512的数组。
- 对于PyTorch框架来说,输入数据的张量从外到内要设置四个维度,对于图片就是(批量样本数,通道数、高度、宽度),所以在最外层增加一个维度,我们每次输入一个样本,就转换成1*3*512*512的数组。
- 然后对数组的数据进行归一化处理,归一化的目的是提高模型学习效率和表现。刚才说到其中数值在0~255之间,我们需要归一化到-1~1之间。方法是:首先对数组除以极差255,得到0~1的数值范围,再-0.5,得到-0.5~0.5,最后除以0.5,得到 -1~1的数值范围。
- 最后将四维数组转化成四维张量(1,3,512,512),交给CNN隐藏层来处理就可以了。输入层处理完成。
2、隐藏层
卷积神经网络的隐藏层包含卷积层、激励层、池化层和线性层(全连接层),其中激励层和池化层的作用是对卷积层提取的特征进行非线性映射和稀疏处理,所以不认为是独立的神经元层次。从下面的结构定义可以看出,本篇的CNN隐藏层共有7个神经元层次。
从源码中打印此CNN隐藏层的结构定义:
ClassNet(
(Block): Sequential(
(0): Conv2d(3, 4, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): MaxPool2d(kernel_size=4, stride=4, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(4, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(4): ReLU()
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Conv2d(8, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(7): ReLU()
(8): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(9): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(10): ReLU()
(11): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(12): Conv2d(16, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU()
(14): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(15): Conv2d(16, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(16): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(17): ReLU()
(18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(classer): Sequential(
(0): Dropout(p=0.5, inplace=False)
(1): Linear(in_features=512, out_features=2, bias=True)
)
)
隐藏层结构翻译:
神经网络
特征提取网络
0:卷积层:输入通道数3,输出通道数4,卷积核大小3*3,边界填充1*1
1:激活函数:修正线性单元函数
2:池化层:池化窗口大小4,移动步长4
3:卷积层:输入通道数4,输出通道数8,卷积核大小3*3,边界填充1*1
4:激活函数:修正线性单元函数
5:池化层:池化窗口大小2,移动步长2
6:卷积层:输入通道数8,输出通道数16,卷积核大小3*3,边界填充1*1
7:激活函数:修正线性单元函数
8:池化层:池化窗口大小2,移动步长2
9:卷积层:输入通道数16,输出通道数16,卷积核大小3*3,边界填充1*1
10:激活函数:修正线性单元函数
11:池化层:池化窗口大小2,移动步长2
12:卷积层:输入通道数16,输出通道数16,卷积核大小3*3,边界填充1*1
13:激活函数:修正线性单元函数
14:池化层:池化窗口大小2,移动步长2
15:卷积层:输入通道数16,输出通道数32,卷积核大小3*3,边界填充1*1
16:批量标准化:输入特征数32,权重和偏置将被使用
17:激活函数:修正线性单元函数
18:池化层:池化窗口大小2,移动步长2
特征映射网络
0:神经连接丢弃:随机丢弃神经连接概率0.5,非特定位置丢弃
1:线性层:输入特征数512,输出特征数2,使用偏置
超参数的确定:
在设计神经网络模型时,首先需要确定网络的层数和每层的节点数。
关于网络的层数,实际没有什么理论化的方法,基本就是根据经验随便拍一个,或者多尝试几个层数,哪个效果好就用哪个。我们知道的只是网络层数越多模型效果越好,但反过来需要学习的参数就更多,模型训练的难度就越大。因为需要更多的训练样本和训练次数,每次的计算量也需要更多才能达到理想的训练效果。
所以有人说深度学习是个手艺活,有些手艺很有技术含量,有些手艺真的让人无语。
输入层的节点数是可以确定的,卷积神经网络中就是通道数。
隐藏层的节点数基本还是靠经验的,几个到几十万个都有可能。不过卷积神经网络遵循头轻脚重的原则,下一层节点数一般是上一层的2倍或者相等。后面经过流向图推演可以对这种原则有一定理解。
输出层的节点数就是最终分类数。
下面对每一层逐个详细解释:
1)卷积层
从结构定义中,卷积层和池化层都是2d可以看出本篇CNN模型是二维卷积模型。此外还有一维卷积和三维卷积,可以通过二维卷积的理解展开。
下面重点解释CNN的核心概念,卷积:
假设神经网络第一层有3个节点,如果一个样本输入的是4个值,那么经典神经网络每个节点就要设置4个权重参数;如果输入的是512*512的矩阵,那这一层就要有512*512*3个参数。而我们说训练神经网络模型,让它学习到数据的特征,方式就是通过损失函数反馈使神经元优化调整这些参数,那么当一个样本数据量过大,计算量就会非常巨大。所以经典神经网络对于图像声音等的处理是无能为力的。
而卷积神经网络的模型更符合人类对事物的认知,即先对微小的局部有感知,然后对更大的范围有感知,而逐步形成整体的认知。这种方式在模型中称为局部感受野,还是上面的例子,每个神经元我只开一个3*3大小的窗口,用这个窗口扫过整个图片,得到的就是所有3*3大小局部的认知,因为整幅图片使用同一个窗口,也就是共享这些参数,所以这一层总共计算3*3*3个权重参数就可以了。
这个窗口称为卷积核,由3*3个权重参数矩阵组成,卷积核相当于一个特征过滤器,其内部的权重参数就是当前要匹配的特征,用参数矩阵乘以图片同等大小的矩阵得到的是这个局部的特征匹配程度,当卷积核扫过整个图片完成匹配的过程称为卷积计算。
以第0卷积层为例:
输入层输入的是张量(1,3,512,512),即1个样本有3个通道的512*512像素矩阵。通道可以理解为神经网络的节点,即输入层有3个节点,每个节点掌握一个原色的像素矩阵并向下传递。
输入通道数3,要与输入层通道数相等。
输出通道数4,代表这一层有4个节点,本层输出的就是一个4通道张量,每个节点掌握其中一个通道。
卷积核3*3,即二维卷积核的大小。每个节点接收3个通道的矩阵,则需要3个卷积核作为特征来匹配,见下图,就是其中一个节点的初始卷积核。
图片4
对于一个节点的单通道计算来说,接收到512*512的矩阵数据,卷积核的9个参数在模型建立初始是随机生成的,如何通过训练反馈优化调整这些参数,后面在模型训练的部分会讲到。这些参数的值也会在-1~1之间,卷积计算时,卷积核与局部感受野做矩阵相乘,结果求和得到该区域匹配此特征的程度。
此时细心的会发现,如果3*3的卷积核按步长1扫过512*512的数据时,得到是511*511的结果,有部分特征丢失了。为什么会出现这种情况呢?因为卷积核扫过的时候,所有的像素点都作为卷积核中心点计算过了,除了边界的那些,所以丢失的就是这部分数据的特征。模型通过在边界外填充数据的方法解决这个问题,即填充成513*513的矩阵,填充的值为0,这里的0注意不是表示没有数,其实0是数据范围的平均值,同时0作为乘数又不影响计算,这样既保留了边界局部特征,又不会对特征计算造成影响。
计算可得,本层需要学习的参数有(3*3卷积核*3输入通道*4个节点)=108个,同时模型对于输出通道也有权重参数,4个,共计112个参数。
经过卷积计算,并且输出通道为4,所以生成张量(1,4,512,512),向下传递。
2)激活函数
激活函数,顾名思义,其作用是设定一个阈值,将达到阈值的特征激活后传,没达到阈值的特征截留。类似于生物神经元传递信号的机制,刺激达到触发条件才会向后传递电信号。其数学意义在于将线性计算结果做非线性映射。我们的输入数据是二维矩阵,卷积计算过程的函数是线性的,而模型最终的输出结果却是非线性的二元分类。所以每个卷积层后面都会添加激活函数这个非线性因素。
卷积神经网络常用激活函数是ReLU(修正线性单元函数),公式很简单max(0,T),意思就是如果输入是负值就输出0,大于0则原样输出。
激活函数对于神经网络的意义,我认为是过滤了不明显的特征,使之不再参与下层特征值的计算。
激活函数因为加入了非线性因素,使模型的收敛速度更快,同时求梯度也非常简单。
以第1激活函数为例:
ReLU的作用就是将卷积层计算得到的张量(1,4,512,512),其中的负值全部替换为0.
激活函数层没有学习参数。
3)池化层
池化层的作用同样是减少计算量,如果说激活函数是过滤了不明显的特征,那么池化的作用就是在过滤后提取相对更重要的特征。
池化的处理很简单,如果池化窗口是2*2,则取窗口中这4个数中最大的值作为代表输出,参与后面的计算。
最大池化法保留每个窗口中的最大值,就相当于保留了这个区域的最佳特征匹配结果,这符合人类认知过程,我不关心这个区域哪个像素更好的匹配了特征,而只关心这个区域匹配特征的程度。
池化方法除了最大池化法,还有平均值池化法。
以第2池化层为例:
最大池化法,池化窗口4*4,步长为4,对于单个通道,扫过激活函数输入的512*512个特征值矩阵,对处于窗口的4*4个值,取最大值,作为这16个特征值的代表输出。这样经过池化层后得到是张量(1,4,128,128),交给下一层卷积层处理。
池化层没有学习参数。
看后面的池化层,会发现池化窗口都是2*2,第一个池化层为什么是4*4呢?我认为因为第一层卷积计算的是3*3个像素,其能表示的特征十分微小,所以前面的层完全可以扩大特征提取范围,减少后面的计算量。
卷积节点+激活函数+池化节点,我认为是这个结构组成了卷积神经网络中完整功能的神经元结构。个人理解的图形表示:
图片5
4)流向图推演
张量数据在流向图中的变化类似于下图(图片来源于网上资料,对数字图片0-9的识别):
图片6
观察此图并推演特征张量的变化,可以发现:每一个特征矩阵在变小,但是特征矩阵的层数(通道数)越来越多,最后在分类器中拉平为特征向量,映射到最终分类。
明白了上述三层的处理过程,就可以根据模型定义向后推演流向图:
第3卷积层:八个节点,输入张量(1,4,128,128),输出张量(1,8,128,128)。可学习参数3*3*4*8+8个。第一个卷积层输出的每个特征值代表3*3像素点的特征,虽然这一层卷积核仍然是3*3,但是实际计算的像素点范围扩大了,也就是局部感受野变大了,提取到的特征层次更高,这有点间接选举的意思。
第4激活函数:张量(1,8,128,128)负值变为0,后传。
第5池化层:池化窗口2*2,提取重要特征,得到张量(1,8,64,64),后传。
第6卷积层:十六个节点,输入张量(1,8,64,64),输出张量(1,16,64,64)。可学习参数3*3*8*16+16个。
第7激活函数:张量(1,16,64,64)负值变为0,后传。
第8池化层:池化窗口2*2,提取重要特征,变为张量(1,16,32,32),后传。
第9卷积层:十六个节点,输入张量(1,16,32,32),输出张量(1,16,32,32)。可学习参数3*3*16*16+16个。
第10激活函数:张量(1,16,32,32)负值变为0,后传。
第11池化层:池化窗口2*2,提取重要特征,变为张量(1,16,16,16),后传。
第12卷积层:十六个节点,输入张量(1,16,16,16),输出张量(1,16,16,16)。可学习参数3*3*16*16+16个。
第13激活函数:张量(1,16,16,16)负值变为0,后传。
第14池化层:池化窗口2*2,提取重要特征,变为张量(1,16,8,8),后传。
第15卷积层:三十二个节点,输入张量(1,16,8,8),输出张量(1,32,8,8)。可学习参数3*3*16*32+32个。
第16批量标准化:作用是避免卷积网络中的梯度消失或爆炸,梯度影响模型的收敛速度,梯度变小时训练次数会变多,梯度过大时,会难以得到最优解。经过多层卷积计算,如果大多数特征值都接近于0,下一步的激活函数梯度就会消失,如果大多数值都接近-1和1,那么激活函数的梯度会无限大。所以批量标准化通过减均值除以标准差的方法,调整输入下层的数据为均值0方差1的正态分布。其中还有一个分母加极小数1e-05,并使用权重gamma和偏置beta,这两个值是可学习的参数。
第17激活函数:张量(1,32,8,8)负值变为0,后传。
第18池化层:池化窗口2*2,提取重要特征,变为张量(1,32,4,4),后传。
5)线性层
卷积计算经过上面所有层次之后,单个通道的矩阵就只有4*4了,再对它进行卷积计算就是在大面积重复计算特征,意义不大。这时候就应该进行最后一步,将计算得到的所有特征映射到两个最终特征(正常图片分类,翻拍图片分类)。
线性层,也叫做全连接层,即经典神经网络的节点构型,一次性读取输入的全部数据,并对每一个数据增加权重参数。卷积神经网络中的线性层,起到了最终分类器的作用,将计算特征映射到最终分类上。
解读本篇的特征映射网络
0:神经连接丢弃:随机丢弃神经连接概率0.5,非特定位置丢弃
1:线性层:输入特征数512,输出特征数2,使用偏置
首先线性层是一次性读取,不能读取多维张量的结构,需要将张量(1,32,4,4)拉平,转成一个32*4*4=512维度的向量。所以线性层设置输入特征数512,输出特征数就是最终的分类数2.
偏置:其中使用偏置是指不但每个特征要有权重,还要加一个常数偏置项。增加偏置是为了提高模型的拟合能力,提高灵活性。比如f(x)=ax这个函数无论参数是多少都必然是一条直线经过坐标系原点,函数的位置被限制住了。所以一般都需要设置这样一个偏置项来提供更多的拟合能力。
神经连接丢弃:Dropout这个配置项与偏置的作用不同,是为了防止过拟合。此例中512个特征值都要使用吗?如果我认为满足所有特征才能认定它的分类,那这个模型适用性就太差了。比如你认识一个人,不会因为他带了眼镜、剪了头发、换了衣服就不认识了,甚至这次疫情中,大家都戴口罩,作为最重要识别特征的脸挡住了大部分,我们认识的依然认识。随机丢弃概率0.5,就是对这512个特征,线性层随机丢弃其中一半的连接,使用剩下的一半特征来分类。
3、输出层
隐藏层的线性层最终映射计算得出的是一个向量,如[0.9803, 0.0197],其中数值代表的是样本属于相应分类的概率或者说置信度。本例中第一个位置定义为翻拍图片,第二个位置是正常图片,所以这个向量意思是CNN认为这个样本属于翻拍图片的概率是98%,属于正常图片的概率是2%。输出层的工作就是找到所有概率中的最大值落在哪个分类,然后输出模型的预测结果:预测这张图片是翻拍图片的分类及其置信度。
模型的搭建完成。
四、模型训练
模型训练部分的代码我没有拿到,只能根据参考资料和研究成果推测。
模型搭建完成后不可能直接起作用,就像初生的婴儿,是一张白纸。只有经过训练,才能达到准确预测的目的。
首先需要定义损失函数和优化方法。
1、损失函数
损失函数的作用是计算模型输出的预测结果与样本实际标签的误差,在训练时将误差结果反馈到模型中,达到调优模型内参数的目的。
Torch框架中有很多定义好的损失函数供使用,包括均方误差函数、平均绝对误差函数、交叉熵误差函数等。
2、优化方法
优化方法指的是选用什么样的算法对模型参数进行优化。前面说到的梯度下降法是所有优化器的基本方法,以损失函数为初始,对模型参数链式求导。
框架中优化是自动的,只需要选择优化器就可以了,比如SGD、AdaGrad、RMSProp、Adam等。这些损失函数和优化算法,每一个都够喝一壶的,就交给数学家吧,我们只要了解和应用就可以了。
其次需要准备训练集,训练集包括各个分类的训练样本及其标签。一般要求各个分类的样本数量要均衡,样本不均衡是采样处理中要避免的问题,很好理解,比如样本提供1000个正常图片而只提供10个翻拍图片,那这个模型训练出来的效果就很值得怀疑了。
假设准备了1000张正常图片和1000张翻拍图片。
3、训练过程
假设训练100次,就设置批次循环100次。
每批次取出训练集所有样本循环执行下列步骤:
- 对优化器的模型参数梯度归零,如果参数梯度不归零,那么计算的梯度会一直累加,影响后续的计算。
- 让样本图片经过CNN计算。
- 使用损失函数计算预测结果和实际标签的误差。
- 将误差反馈到模型中,实现自动梯度更新。实现自动梯度的步骤可以理解为:模型参数在模型按顺序向前传播过程中生成一张计算图,根据这个计算图和损失函数计算出每个参数的当前的梯度(框架中会自动完成这个求导过程),最后根据反馈的误差值逆向传播完成对梯度的更新。整个过程框架中只需要调用一个backward()函数。
- 优化器执行优化,即根据上一步更新的每一个参数的梯度按学习效率(步长)调整相应参数。
最后将模型及其参数保存成文件就可以了。
训练样本3批:
正常图片:90+189+467=746,翻拍图片32+73+1807=1912:。
模型训练并不是一蹴而就的,需要根据训练后模型的预测效果评估,来调整模型设计、训练样本、训练次数和训练方法等。
五、模型评估
机器学习的模型评估方法都是通用的,主要包括分类问题和回归问题两大类的方法。
本例是分类问题,最常用的评估方法自然是准确率:
准确率=正确预测的样本数/总的样本数
测试集样本提供了355张翻拍图片和139张正常图片。
经模型测试,得到如下结果:
总数494,识别正确371,准确率75.10%
写在最后:
源码来自于公司外部资源,拿到手将其测试、部署、改写外部接口以供项目组使用,都没有问题。然而我意识到这是真正进入深度学习领域一次难得的机会,将CNN模型算法的原理和应用掌握,才是最有价值的事情。
这个程序的python代码,不过200行,但想要读懂,首先要学习PyTorch深度学习框架,还有提供web服务的flask框架。在研究PyTorch的过程中,发现并不能理解:模型为什么这样搭建、参数为什么这样设置、如何实现了训练和预测的能力,这些问题框架只是告诉你如何实现,但没有为什么。于是又开始研究卷积神经网络的理论和数学原理,甚至连微积分、线性代数都翻出来了。发现:数学,果然是自然科学的基石。
目前为止,对于深度学习,依然感觉是管中窥豹,需要继续研究实践。