卷积神经网络学习自我总结
一.卷积神经网络知识点总结串连
卷积神经网络的核心包括卷积层,全连接层和池化层。区别于一般的神经网络,卷积神经网络中的卷积层和池化层大大简化了处理数据的难度,是一个包含了卷积计算且具有深度结构的前馈神经网络。
对于卷积神经网络中的卷积层,池化层和全链接层,我将用我学到的东西试着解释它们的关系和原理。
- 全链接层
1.神经元
我个人认为全链接层是网络的基础,其中包括了激活函数,输入输出,权值等网络的关键知识,所以我从全链接层入手。
全链接层由许许多多的神经元组成,神经元是该层中或者说神经网络中最为基础也是最为重要的部分,下面展示一张神经元图片。
神经元的输入:输入1(激励)*权重1+输入2*权重2+…+输入n*权重n=输入;输入-偏置=最终输入。
神经元的输出:将最终输入带入激活函数f(x),得到输出y=f(最终输入)。
一个神经元的基本工作模式就是如上所述,并不复杂,但其中设计到几个关键知识点,需要理解:
(1).激活函数
激活函数就是负责将神经元的输入映射到输出端,也就是上述工作原理中的f(x),引入激活函数主要是为了增加神经网络模型的非线性,没有激活函数的每层都相当于矩阵相乘,就算叠加了若干层之后,无非还是个矩阵相乘罢了。所以激活函数可以协助网络更加有效的提取目标特征,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。
激活函数包括传统的sigmoid函数和tanh函数,但是这两个激活函数以及有明显的缺点,在神经网络反向传播时常常会出现梯度消失和梯度爆炸等问题,而Relu函数可以较好的解决上述问题,在如今也得到了广泛的运用,所以这里主要以Relu函数为主进行介绍。
Relu函数的解析式:Relu=max(0,x)
Relu函数称为线性整流函数,也就是数学中的一次(斜坡)函数,而在神经网络中,线性整流作为神经元的激活函数,定义了该神经元在线性变换之后的非线性输出结果。换言之,对于进入神经元的来自上一层神经网络的输入向量x,使用线性整流激活函数的神经元会输出max(0,
)至下一层神经元或作为整个神经网络的输出。
(2).梯度消失和梯度爆炸
介绍完激活函数之后,借由之前提到的sigmoid函数和tanh函数,可以再说一些梯度消失和梯度爆炸的知识点。
梯度更新:目前优化神经网络的方法都是基于反向传播的思想,即根据损失函数计算的误差通过梯度反向传播的方式,指导深度网络权值的更新优化,参数进行调整。
神经网络的输出由许多非线性层堆叠而来,每一层非线性层都可以视为是一个非线性函数 f ( x ) f(x) f(x)(非线性来自于非线性激活函数,整个深度网络可以视为是一个复合的非线性多元函数
F ( x ) = f n ( . . . f 3 ( f 2 ( f 1 ( x ) ∗ θ 1 + b ) ∗ θ 2 + b ) . . . )
假设不同的输入,输出的最优解是 g ( x ) g(x) g(x) ,则可以得到损失函数
Loss=∣∣g(x)−f(x)∣∣22
层数增多的时候,最终的求出的梯度更新将以指数形式增加,即发生梯度爆炸
那么随着层数增多,求出的梯度更新信息将会以指数形式衰减,即发生了梯度消失
从深层网络角度来讲,不同的层学习的速度差异很大,表现为网络中靠近输出的层学习的情况很好,靠近输入的层学习的很慢,有时甚至训练了很久,前几层的权值和刚开始随机初始化的值差不多。因此,梯度消失、爆炸,其根本原因在于反向传播训练法则,属于先天不足。
2.完整全链接层
介绍单个的神经元后,就可以看向整个全连接层,将各个神经元的工作结果链接成最终的成品了。
上图就是比较直观的全链接层概念图,可见该图中包括了输入层输出层以及两层隐层,而每层都由许许多多之前所述的神经元所构成。
其中,输入输出层顾名思义就是处理目标的输入以及结果的输出,而隐层是除输入层和输出层以外的其他各层,其不直接接受外界的信号,也不直接向外界发送信号,主要有处理输入的数据,提取特征和链接特征等作用。
这里引用DB神经网络中的一个实例进行解释。(即手写数字识别例子)
对于任意手写的0到9的十个数字:
输入:
输入处理:rgb彩色图片灰度图片
二值化图片
二值化后得到一个黑白图像
用一个filter(也可以称为模板,核)在图片上滑动,逐一计算卷积核内黑白像素的比例,得到一个像素比例矩阵
将矩阵按行展开,得到一个行向量。
该行向量就是神经网络的输入层。
输出:
例如用onehot编码,就是对每个期望情况进行唯一化表示
例如0用1000表示,1用0100表示,2是0010表示,这样以此类推。
也就是将输入的特征和真实的事物链接在一起,达到辨别什么是什么的目的,有了这样的输入和输出,就可以对神经网络进行训练了。
- 卷积层
卷积层是卷积神经网络的核心,也是卷积神经网络区别于其他网络的关键所在。
卷积取的是局部特征,全连接就是把以前的局部特征重新通过权值矩阵组装成完整的图。
卷积核对应的其实是一个神经元的工作,例如一个[101,010,101]的卷积核,该卷积核在输入特征图上的操作如下:
- 在图像的某个位置上覆盖滤波器;
- 将滤波器中的值与图像中的对应像素的值相乘;
- 把上面的乘积加起来,得到的和是输出图像中目标像素的值;
- 对图像的所有位置重复此操作。
通过如上操作,得到一个结果矩阵,这个结果矩阵就对目标进行一个特征提取。
而相对于普通神经元的特征提取,卷积核有着局部感知能力以及权值也不会发生变化(权值共享)的特点,这两个特点可以大大简化提取特征中的计算难度以及之后的计算量。
- 池化层
池化层也叫下采样层,是对卷积之后得到的特征图进行稀疏处理,减少数据的运算量。
例如最大下采样就是将特征矩阵中某给定范围内的最大值依次求出,输出一个较小的特征矩阵。
又例如平均下采样就是将特征矩阵中的给定范围内的所有值求平均数,在依次计算每块范围内的平均数,得到一个较小的特征矩阵。
注意,池化层没有核,没有模板,没有参数,只是对输入是特征矩阵进行处理,简化数据。
总而言之,池化层就是一个在尽量保留原矩阵的特征的情况下,简化特征矩阵,减少计算量的‘优化层’。
二.卷积神经网络小总结
综上所述,可以得到如图所示的卷积神经网络的简单基本结构图。
输入的图片通过卷积层提取特征,再经过池化层进行对结果的优化和简化(在真实的卷积神经网络中这一步其实会重复很多次,也就是需要很多的卷积层和池化层),然后再将得到的很多个特征矩阵进行全连接,张开识别,最后完成神经网络的训练。
三.卷积神经网络零散知识点补充
- 过拟合与欠拟合
机器学习的根本问题是优化和泛化之间的对立。
优化(optimization)是指调节模型以在训练数据上得到最佳性能
泛化(generalization)是指训练好的模型在前所未见的数据上的性能好坏
训练数据上的损失越小,测试数据上的损失也越小。这时的模型是欠拟合(underfit)的,即仍有改进的空间,网络还没有对训练数据中所有相关模式建模。
泛化不再提高,验证指标先是不变,然后开始变差,即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来说是错误的或无关紧要的。
过拟合:在既定的数据中训练过度。解决方法是引入更全面的数据,减小模型大小。在容量过大与容量不足之间要找到一个折中。
一般的工作流程是开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层,直到这种增加对验证损失的影响变得很小。
- 添加权重正则化
给定一些训练数据和一种网络架构,很多组权重值(即很多模型)都可以解释这些数据。简单模型比复杂模型更不容易过拟合。
这里的简单模型(simple model)是指参数值分布的熵更小的模型(或参数更少的模型,比如上一节的例子)。因此,一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更加规则(regular)。这种方法叫作权重正则化。
L1 正则化(L1 regularization):添加的成本与权重系数的绝对值[权重的 L1 范数(norm)]成正比。
L2 正则化(L2 regularization):添加的成本与权重系数的平方(权重的 L2 范数)成正比。
//权重正则化就是使得训练结果更规整更易成功
添加 dropout 正则化
对某一层使用 dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为 0)。假设在训练过程中,某一层对给定输入样本的返回值应该是向量 [0.2, 0.5,1.3, 0.8, 1.1] 。使用 dropout 后,这个向量会有几个随机的元素变成 0,比如 [0, 0.5,1.3, 0, 1.1] 。
总结一下,防止神经网络过拟合的常用方法包括:
(1).获取更多的训练数据
(2).减小网络容量
(3).添加权重正则化
(4).添加 dropout
四.代码实践操作
- 模型代码model
from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model
class MyModel(Model): #继承来自tensorflow.keras为父类的子类
def __init__(self): #定义在搭建网络过程中所需要的模块
super(MyModel, self).__init__()
self.conv1 = Conv2D(32, 3, activation='relu') #卷积层,Conv2D导入自tensorflow.keras.layers
#32代表卷积核的个数,卷积核的大小为3*3的,激活函数为relu
self.flatten = Flatten()
self.d1 = Dense(128, activation='relu') #全连接层
#128个节点,激活函数为relu
self.d2 = Dense(10, activation='softmax') #全连接层
#十个类别10个节点,激活函数为softmax
def call(self, x, **kwargs): #定义网络正向传播的过程,x就是输入的一批数据
x = self.conv1(x) # input[batch, 28, 28, 1] output[batch, 26, 26, 32] 数据通过都应该卷积层
x = self.flatten(x) # output [batch, 21632] 输出进行张平处理,成为一个一位向量
x = self.d1(x) # output [batch, 128] 128个节点输出128个值
return self.d2(x) # output [batch, 10] 输出为一个概率分布
- 训练代码train
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
from model import MyModel
tf.enable_eager_execution()
import numpy as np
import matplotlib.pyplot as plt
def main():
mnist = tf.keras.datasets.mnist
# download and load data
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Add a channels dimension
x_train = x_train[..., tf.newaxis] #增加深度信息
x_test = x_test[..., tf.newaxis] #增加深度信息
# create data generator
train_ds = tf.data.Dataset.from_tensor_slices( #用该函数载入数据
(x_train, y_train)).shuffle(10000).batch(32) #图像和标签元组的形式载入
#在内存中载入10000张图片随机打乱,然后从中依次提取32张图片进行载入
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)
# create model
model = MyModel() #实例化定义好的模型
# define loss
loss_object = tf.keras.losses.SparseCategoricalCrossentropy() #损失函数,提供标签的真实数值,而不是onehot编码
# define optimizer
optimizer = tf.keras.optimizers.Adam() #定义adam优化器
# define train_loss and train_accuracy
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')
# define train_loss and train_accuracy
test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')
# define train function including calculating loss, applying gradient and calculating accuracy
@tf.function
def train_step(images, labels):
with tf.GradientTape() as tape:
predictions = model(images)
loss = loss_object(labels, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
train_loss(loss)
train_accuracy(labels, predictions)
# define test function including calculating loss and calculating accuracy
@tf.function
def test_step(images, labels):
predictions = model(images)
t_loss = loss_object(labels, predictions)
test_loss(t_loss)
test_accuracy(labels, predictions)
EPOCHS = 5 #将样本迭代5轮
for epoch in range(EPOCHS):
train_loss.reset_states() # clear history info
train_accuracy.reset_states() # clear history info
test_loss.reset_states() # clear history info
test_accuracy.reset_states() # clear history info
for images, labels in train_ds:
train_step(images, labels)
for test_images, test_labels in test_ds:
test_step(test_images, test_labels)
template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
print(template.format(epoch + 1,
train_loss.result(),
train_accuracy.result() * 100,
test_loss.result(),
test_accuracy.result() * 100))
if __name__ == '__main__':
main()
- 代码中我遇到的问题以及一些小提示
这段代码需要python3.6或python3.7的环境,如果电脑没有安装,则需要创建虚拟环境并导入pycharm,这里我使用的是annaconda创建虚拟环境,集体创建方法和导入操作在百度上有很多教程,都很详细,这里就不多说了。
在搭建完环境之后,需要准备相关的python包,这里需要用到tensorflow包,这里要介绍到tensorflow2和tensorflow1,tensorflow2的性能肯定是比1要好的,还整合了很多内置包的调用,也有很多可视化方面的提升,而且网上的各类教学对该段代码用到都是tensorflow2,但是!tensorflow2需要用到 gpu,同时还需要NVIDIA下的CUDA和Cudnn,这几项条件已经让我的电脑无能为力了,不过好在这段代码还没有那么复杂,使用tensorflow1再自己添加一些语句依然是可以用的,这里我在train代码中添加了‘from model import MyModel’该段代码,也顺利跑通了。
- 代码调试以及总结
在这两段代码中我添加了比较详细的注解,这里就不作太详细的解读了。
下面是提取的测试集中前三张图片看到的效果:
可以看到手写的721在电脑中得到了识别并有输出。
下面是代码进行5次迭代学习后得到的结果(我的电脑差点被送走了)
可以看到测试集的准确度越来越高,我的电脑最后达到了百分之98.2左右。