卷积神经网络的结构及原理

卷积层

卷积运算一个重要的特点就是:通过卷积运算,可以使原信号特征增强,并且降低噪音。
机器学习:卷积神经网络-LMLPHP
以二维为例,卷积核在二维平面上平移,对应位置相乘,得到一个新图像,即对图像的每个像素的邻域(邻域大小就是核的大小)加权求和得到该像素点的输出值。
机器学习:卷积神经网络-LMLPHP

池化层

通常使用的池化操作为平均值池化 (average-pooling)和最大值池化(max-pooling)。

池化层不包含需要学习的参数使用时仅需指定池化类型(average 或max)、池化操作的核大小(kernel size)和池化操作的步长等超参数即可。
机器学习:卷积神经网络-LMLPHP
!!!注意区分卷积和池化步幅的移动,都是需要定义的

池化层的引入是仿照人的视觉系统对视觉输入对象进行降维(下采样)和抽象。池化层有三种功效:

1.特征不变性。池化操作使模型更关注是否存在模型特征而不是特征具体的位置。
2.特征降维。
3.在一定程度上防止过拟合,更方便优化。

激活函数

又称非线性映射层。激活函数的引入为的是增加整个网络的表达能力(即非线性),否则,若干线性操作层的堆叠仍然只能起到线性映射的作用,无法形成复杂的函数。

激活函数应该具有的性质如下:

▨非线性。
▨连续可微。
▨范围最好不饱和,当有饱和的区间段时,若系统优化进入到该段,梯度近似为 0,网络的学习就会停止。
▨单调性。当激活函数是单调时,单层神经网络的误差函数是凸的,好优化。
▨在原点处近似线性,这样当权值初始化为接近0的随机值时,网络可以学习得较快,不用调节网络的初始值。
通常使用的激活函数有sigmoid、tanh和relu函数。

sigmoid函数

机器学习:卷积神经网络-LMLPHP
tanh 函数机器学习:卷积神经网络-LMLPHP
relu 函数

机器学习:卷积神经网络-LMLPHP
relu 函数在梯度下降中能够快速收敛。relu函数有效缓解了梯度消失的问题,但是随着训练的继续,可能会出现神经元死亡,权重无法更新的情况。也即是说,relu 函数下的神经元在训练中不可逆地死亡了

全连接层

起到“分类器”的作用。如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐藏层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用。

在基本的 CNN 网络中,全连接层的作用是将经过多个卷积层和池化层的图像特征图中的特征进行整合,获取图像特征具有的高层含义,之后用于图像分类。在 CNN 网络中,全连接层将卷积层产生的特征图映射成一个固定长度(一般为输入图像数据集中的图像类别数)的特征向量

反馈运算

调参的过程。

在卷积神经网络求解时,特别是针对大规模应用问题,常采用批处理的随机梯度下降法。批处理的随机梯度下降法在训练模型阶段随机选取 n 个样本作为一批样本,先通过前馈运算得到预测并计算其误差,后通过梯度下降法更新参数,梯度从后往前逐层反馈,直至更新到网络的第一层参数,这样的一个参数更新过程称为一个“批处理过程”。

批处理样本的大小不宜设置过小。过小时,由于样本采样随机,按照该样本上的误差更新模型参数不一定在全局上最优(此时仅为局部最优更新),会使得训练过程产生振荡。批处理大小的上限则主要取决于硬件资源的限制,如 GPU 显存大小。

使用MNIST数据集进行代码解析

MNIST数据集算是机器学习入门的数据,常用来做分类处理。总的标签类别就是0到9这十个数字,图片之间不同的是采用的是一些人群的手写体,目的就是根据不同手写体识别图片进而判断数字是几。

数据介绍

机器学习:卷积神经网络-LMLPHP
在MNIST数据集官网http://yann.lecun.com/exdb/mnist/ 下载后的压缩包不用解压缩,可直接放在一个文件夹下,直接调用。

数据包含55000个训练数据集,5000个验证数据集,10000个测试数据集(我还不知道验证数据集有什么用),每张图片有28*28个像素点,即 784 个像素点,我们可以把它展开形成一个向量,即长度为 784 的向量。

实现流程

先定义好权重偏置函数,卷积函数tf.nn.conv2d,池化函数(最大池化max_pool_2x2)

卷积 -> 激活函数 -> 池化 -> 全连接 -> 激活函数 -> Dropout层 -> AdamOptimizer优化器(反馈) -> 训练模型 -> 评估

卷积:突出局部的特征
激活函数:非线性处理,增加表达能力,输入的是(卷积结果 + 偏置)
池化:降维
全连接:一步步收敛,映射到标签y
Dropout层:防止过拟合,扔到部分神经元
AdamOptimizer优化器:模型复杂计算量大,直接使用此优化器进行反馈定义合适的权重和偏置值
训练模型:上面搭建好的框架,给入数据进行模型训练
评估:引入测试集数据,评估模型

代码实现

# -*- coding: utf-8 -*-
"""
Created on Thu Nov 17 21:30:31 2022

@author: Yangz
"""

'''
卷积 -> 激活函数 -> 池化 -> 全连接 -> 激活函数 -> Dropout层 -> AdamOptimizer优化器(反馈) -> 训练模型 -> 评估

卷积:突出局部的特征
激活函数:非线性处理,增加表达能力,输入的是(卷积结果 + 偏置)
池化:降维
全连接:一步步收敛,映射到标签y
Dropout层:防止过拟合,扔到部分神经元
AdamOptimizer优化器:模型复杂计算量大,直接使用此优化器进行反馈定义合适的权重和偏置值
训练模型:上面搭建好的框架,给入数据进行模型训练
评估:引入测试集数据,评估模型

'''

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow.compat.v1 as tf
#tensorflow版本原因2.0往上的用这种方式
tf.compat.v1.disable_eager_execution()

'''
读取MNIST数据,MNIST数据集中每一张图片大小都是28*28的,因此有784个像素点
所以输入值x应该是一个长度为784的向量,y_是标签长度为10

'''
mnist = input_data.read_data_sets("./MNIST_data",one_hot=True)
sess = tf.InteractiveSession() #与BP不同的另一种sess定义

train_nums = mnist.train.num_examples #训练集样本数据大小55000
validation_nums = mnist.validation.num_examples # 验证集样本数据大小5000
test_nums = mnist.test.num_examples  #测试集样本数据大小10000

# 设定两个参数,输入x、实际输出y_的占位符;x是数据,y_是标签
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])

keep_prob = tf.placeholder(tf.float32) #改变参与计算的神经元个数的值,全连接层的输出
#x_image = tf.reshape(x, [-1,28,28,1])  #对输入数据x进行变形

'''
权重、偏置函数
'''
def weight_variable(shape):
    # 产生随机变量,
    #truncated_normal()函数:选取位于正态分布均值=0.1附近的随机值
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape) #常量
    return tf.Variable(initial)

'''
卷积函数、池化函数
'''
#输入图像与卷积核,W=[filter_height, filter_width, in_channels, out_channels]
#具体含义是[卷积核的高度,卷积核的宽度,图像通道数,输出通道数]
def conv2d(x, W): 
    #stride = [1,in_height移动步长,in_width移动步长,1]
    #卷积层conv2d()函数里strides参数要求第一个、最后一个参数必须是1,即只能在一个样本的一个通道上的特征图上进行移动
    #padding:string类型的量,只能是"SAME","VALID"其中之一,
    #当其为‘SAME’时,表示卷积核可以停留在图像边缘,保留边界信息和图像大小
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):#选用最大池化方法,池化步长在这里固定为2
    #k_size : 池化窗口的大小,取一个四维向量,一般是[1, height, width, 1]
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],strides=[1, 2, 2, 1], padding='SAME')

#tf.reshape:哪一维使用了-1,那这一维度就不定义大小,而是根据你的数据情况进行匹配
x_image = tf.reshape(x, [-1,28,28,1])  #对输入数据x进行变形

'''
第一层卷积 + 池化
卷积核大小5*5,输入通道数1,输出通道数(深度)32,
'''
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
#激活函数使用reLU进行非线性处理,大小28*28*32
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) #卷积后的结果加上偏置值
#第一次池化 输出结果大小14*14*32
h_pool1 = max_pool_2x2(h_conv1)

'''
第二层卷积 + 池化
卷积核大小5*5,第输入的通道数是32,输出的通道数是64
'''
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
#第二次卷积,输出结果大小14*14*64
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) 
# 第二次池化,输出结果大小 7 *7 *64
h_pool2 = max_pool_2x2(h_conv2)

'''
全连接层,这里构造了两个全连接层
'''
#第一个全连接层,转化为一维,定义输出节点数1024

W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64])  #三维变形为二维
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) #tf.matmul矩阵相乘全连接
#防止过拟合,使用Dropout层,是在不同的训练过程中随机扔掉一部分神经元,
#仅仅是不参与计算,但权重保留,之后样本输入时依旧工作,依旧进行权重更新
#train的时候才是dropout起作用的时候,test的时候不应该让dropout起作用
#tf.nn.dropout: keep_prob,每个元素被保留下来的概率
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) 

#第二个全连接层,收敛对应10个标签,最后一个全连接层也可记为分类的过程
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

'''
反馈调参、求准确率
'''
#计算平均误差
#tf.nn.softmax_cross_entropy_with_logits:交叉熵损失函数,第一个为实际值,第二个为预测值
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction))
#对于神经网络部分的反馈运算,即权重、偏置更新过程,由于计算量庞大,
#一般都直接使用AdamOptimizer优化器,BP神经网络学习中已有介绍
#minimize(loss)根据其损失量学习自适应,损失量大则学习率大
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)
#结果存放在一个布尔型列表中,便于后续计算准确率
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1))
#求准确率
#tf.cast:数据类型转换,这里是将布尔类型值correct_prediction转换为浮点型
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

'''
模型训练
'''
saver = tf.train.Saver()  # defaults to saving all variables
init_op = tf.global_variables_initializer()
sess.run(init_op)


#tf.global_variables_initializer().run()
for i in range(1000):
    #每次训练固定训练集大小,10
    batch = mnist.train.next_batch(50)
    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})#只有一半的神经元参与计算
    #每100次计算一次准确率
    if i%100 == 0:
        #accuracy.eval 相当于 sess.run(accuracy,…)
        train_accuracy = accuracy.eval(feed_dict={x:batch[0], y_: batch[1], keep_prob: 1.0})
        print("step",i, "training accuracy",train_accuracy)
    
    #batch[0] = mnist.train.images
    #batch[1] = mnist.train.labels
    
#保存模型参数
#saver.save(sess, 'F://小组//Python//机器学习//卷积神经网络//model.ckpt')
#使用测试机数据估计模型准确率
print("test accuracy %g"%accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) #所有的神经元都参与评估计算
11-20 07:30