赞
踩
一个CNN包含一个输入层、一个卷积层、一个输出层,但是在真正使用的时候一般会使用多层卷积神经网络不断的提取特征,特征越抽象,越有利于识别(分类)。CNN一般包括以下几个部分:
输入层:数据输入
卷积层:使用给定核函数对输入数据进行特征提取,并依据核函数的数据产生若干个卷积特征结果
池化层:数据降维,减少数据特征
全连接层:对已有数据特征进行重新提取并输出结果(对图像进行分类)
卷积神经网络是从信号处理中衍生过来的一种数字信号处理的方式,发展到图像信号处理上演变为一种专门用来处理具有矩阵特征的网络结构处理方式,卷积神经网络在很多应用上都有独特的优势,甚至可以说是无可比拟的,例如:音频处理、图像处理。
数字图像处理中一种最为基本的处理方法,即线性滤波。(图像转换成大型矩阵[m, n],图像中的每一个像素看成矩阵中的一个元素,像素的大小即矩阵中的元素值)
经过卷积层的图像被分别提取特征后获得了同样大小的图片分块,后对分解的图片使用小星神经网络做进一步的处理,即将二维矩阵转化成一维数组。
使用的滤波工具即为卷积核([km, kn])。(卷积核大小小于图像矩阵;纬度为奇数;卷积后矩阵纬度[(m-km)/2+1, (n-kn)/2+1]; )
数字图像处理卷积运算主要有两种思维,即“稀疏矩阵”和“权值共享”
稀疏矩阵:卷积核的大小远远小于输入数据矩阵的大小。即图像矩阵通过与卷积核进行卷积运算之后能够获取更少的参数特征,极大的减少了后续的计算量。
权值共享:卷积核的每一个元素都被用在输入的每一个位置上,而在这个过程中,仅需要学习一个参数集合就能把这个参数应用到所有的图片元素中。此时的神经元就是图像处理中的滤波器,即卷积层的每个滤波器都会有自己所关注一个图像特征,比如垂直边缘,水平边缘,颜色,纹理等等,这些所有神经元加起来就好比就是整张图像的特征提取器集合。
权值共享原因:
①对于图像数组来说,局部数组的值经常是高度相关的,可以形成容易被探测到的独特的局部特征
②“图像和其他信号的局部统计特征”与其位置相关性不大,如果特征图能在图片的一个部分出现,也能出现在任何地方。所以,不同位置的单元共享同样的权重,并在数组的不同部分探测相同的模式。
TensorFlow中卷积函数:
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
input: 图像矩阵张量, 类型要求为float32或float64。[batch, in_height, in_width, in_channels] -> [训练时batch的图片数量,图片高度,图片宽度,图像通道数]
filter: 相当于CNN中的卷积核。张量。[filter_height, filter_width, in_channels, out_channels] -> [高度,宽度,图像通道数,卷积核个数]
strides: 卷积时卷积核滑动的步长。[1, 平行滑行步长, 竖直滑行步长,1]
padding: string类型的量,决定卷积结果是否与原输入矩阵大小一致,即是否在原始图像外圈补0。(SAME(结果等于原有图像)和VALID(结果小于原有图像)之一)
use_cudnn_on_gpu:bool类型,是否使用cudnn加速,默认为True
- # 卷积操作具体实现
- import struct
- import matplotlib.pyplot as plt
- import numpy as np
- import tensorflow as tf
-
-
- def convolve(dataMat, kernel):
- m, n = dataMat.shape
- km, kn = kernel.shape
- # 卷积后矩阵大小:(m-km+1, n-kn+1)
- newMat = np.zeros((m-km+1, n-kn+1))
- tempMat = np.zeros((km, kn))
- # 计算卷积后矩阵元素
- for row in range(m-km+1):
- for col in range(n-kn+1):
- for m_k in range(km):
- for n_k in range(kn):
- tempMat[m_k, n_k] = dataMat[row+m_k, col+n_k] * kernel[m_k, n_k]
- newMat[row, col] = np.sum(tempMat)
- return newMat
-
-
- if "__main__" == __name__:
- dataMat = np.ones((7, 7))
- kernel = np.array([[2, 1, 1], [3, 0, 1], [1, 1, 0]])
-
- print(dataMat)
- print(convolve(dataMat, kernel))
图像特征标注是指图像内部的一个子区域由计算机自动进行标注的方式,在实际的使用过程中经常采用不同的卷积核实现。
- import os
- import numpy as np
- import tensorflow as tf
- import cv2
- os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
-
-
- img = cv2.imread("love.png")
- img_raw = img
- # 将图像矩阵转换成 "array" + "float32"
- img = np.array(img, dtype=np.float32)
-
- # 定义卷积操作的 “图像格式” + “卷积核”
- x_image = tf.reshape(img, [1, 400, 400, 3])
- kernel = tf.Variable(tf.ones([7, 7, 3, 1]))
-
- init = tf.global_variables_initializer()
- with tf.Session() as sess:
- sess.run(init)
- res = tf.nn.conv2d(x_image, kernel, strides=[1, 1, 1, 1], padding="SAME")
- res_image = sess.run(tf.reshape(res, [400, 400]))/125 + 1
-
- cv2.imshow("love2", res_image.astype("uint8"))
- cv2.imshow("love1", img_raw.astype("uint8"))
- cv2.waitKey()
即:将卷积层的输出做非线性映射。CNN采用的激励函数一般为ReLU(The Rectified Linear Unit/修正线性单元),它的特点是收敛快,求梯度简单,但较脆弱,常用的一些激励函数如下链接:
激励层的实践经验:
①不要用sigmoid!不要用sigmoid!不要用sigmoid!
② 首先试RELU,因为快,但要小心点
③ 如果2失效,请用Leaky ReLU或者Maxout
④ 某些情况下tanh倒是有不错的结果,但是很少激活函数作用机理:
特征提取之后,则希望对这些特征进行分类。但是,此时存在诸多的问题:由于卷积后的特征图像具有一种“静态性”的属性,也就意味着在某个区域内有用的特征极有可能在另外一个区域同样适用。因此,为了描述大的图像,一个很自然的想法就是对不同位置的特征进行聚合统计。具有:平移不变性、特征降维和防止过拟合等优点。
简而言之,针对图像处理池化层的主要作用就是压缩图像。
池化分为:“最大池化” (实际中较为常用)、 “平均池化”
池化一个非常重要的作用就是能够帮助输入的数据表示近似不变性。对于平移不变性指的是对输入的数据进行少量平移时,经过池化后的输出结果并不会发生改变。局部平移不变性是一个很有用的性质,尤其是当关心某个特征是否出现而不关心它出现的具体位置时。
例如,当判定一张图像中是否包含 人脸时,并不需要判定眼睛的位置,而是需要知道有一只眼睛出现在脸部的左侧,而另外有一只出现在右侧就可以了。
TensorFlow中的池化函数:
tf.nn.max_pool(value, ksize, strides, padding, name=None):最大池化
value: 与之连接的卷积层的输出。[batch, height, width, channels]
ksize: 池化窗口大小。[1, height, width, 1] -> 不在batch和channels上做池化,所以设置为1
strides:窗口在每一个纬度上滑动的步长。[1, 平行滑行步长, 竖直滑行步长,1]
padding:池化的方式。 “VALID”和“SAME”。返回一个张量,类型仍然是[batch, height, width, channels]
- import tensorflow as tf
-
-
- data = tf.constant([
- [[3.0, 2.0, 3.0, 4.0],
- [2.0, 6.0, 2.0, 4.0],
- [1.0, 2.0, 1.0, 5.0],
- [4.0, 3.0, 2.0, 1.0]]
- ])
- data = tf.reshape(data, [1, 4, 4, 1])
-
- # 最大值池化
- maxPooling = tf.nn.max_pool(data, [1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")
- # # 平均值池化
- # maxPooling = tf.nn.avg_pool(data, [1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")
-
- init = tf.global_variables_initializer()
- with tf.Session() as sess:
- sess.run(init)
- print(sess.run(maxPooling))
- import tensorflow as tf
- import os
- import cv2
- import numpy as np
- os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
-
-
- img = cv2.imread("love.png")
- img_raw = img
- img = np.array(img, np.float32)
-
- x_image = tf.reshape(img, [1, 400, 400, 3])
- kernel = tf.Variable(tf.ones([7, 7, 3, 1]))
-
- init = tf.global_variables_initializer()
- with tf.Session() as sess:
- sess.run(init)
- conv = tf.nn.conv2d(x_image, kernel, strides=[1, 2, 2, 1], padding="SAME")
- pool = tf.nn.max_pool(conv, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="VALID")
- res_image = sess.run(tf.reshape(pool, [100, 100]))/128 + 1
-
- cv2.imshow("love2", res_image.astype("uint8"))
- cv2.imshow("love1", img_raw)
- cv2.waitKey()
两层之间所有神经元都有权重连接,通常全连接层在卷积神经网络尾部。也就是跟传统的神经网络神经元的连接方式相同。
下图中连线最密集的2个地方就是全连接层,这很明显的可以看出全连接层的参数的确很多。在前向计算过程,也就是一个线性的加权求和的过程,全连接层的每一个输出都可以看成前一层的每一个结点乘以一个权重系数W,最后加上一个偏置值b得到,即 。如下图中第一个全连接层,输入有50*4*4个神经元结点,输出有500个结点,则一共需要50*4*4*500=400000个权值参数W和500个偏置参数b。
- def linear_layer(data, weights_size, biases_size):
- """
- 全连接层
- :param data:
- :param weights_size:
- :param biases_size:
- :return:
- """
- weights = tf.get_variable("weigths", weights_size, initializer=tf.random_normal_initializer())
- biases = tf.get_variable("biases", biases_size, initializer=tf.random_normal_initializer())
-
- return tf.add(tf.matmul(data, weights), biases)
优点:
(1)前面通过多种实例和方法说明了卷积运算(共享卷积核)可以对图像特征所提取出的数据进行特征提取和压缩,这在神经网络中可以极大地提高运算效率和获取图像的特征。
(2)特征自动选择,无需手动选择
缺点:
(1)需要调参,需大量样本
(2)在图像进行卷积与池化时可能导致欠拟合。当训练模型需要保存精确的图像特征时,使用卷积和池化会加大训练误差,或者当卷积核在图像上移动的步伐过大或过小时,会导致拟合不合适。
(3)并不知道每个卷积层到底提取到何种特征,而且神经网络本身就是一种难以解释的“黑箱模型”,即物理含义不明确
在上文例子中涉及到的是单分类问题,且预测标签和真实标签均经过one-hot处理,因此在对输出层进行误差计算时采用的是交叉熵的函数:
交叉熵文章:https://blog.csdn.net/admin_maxin/article/details/88050175
5.2推导说明
首先定义灵敏度δk,表示当前输出层的误差对于该层输入的偏导数。
对于反馈神经网络,要求层l的每个神经元对应的权值更新,就需要先求层l+1的每个神经节点的灵敏度,简单来说,总体只有以下几个权重以及数值需要在传递的过程中进行计算:
输入层——卷积层
卷积层——池化层
池化层——全连接层
全连接层——输出层
而,当权值更新时,需要对其进行反向更新,即:
输出层——全连接层
全连接层——池化层
池化层——卷积层
卷积层——输入层
①LeNet,这是最早用于数字识别的CNN(识别支票上的手写数字)
②AlexNet, 2012 ILSVRC比赛远超第2名的CNN,比
③LeNet更深,用多层小卷积层叠加替换单大卷积层。
④ZF Net, 2013 ILSVRC比赛冠军
⑤GoogLeNet, 2014 ILSVRC比赛冠军
⑥VGGNet, 2014 ILSVRC比赛中的模型,图像识别略差于GoogLeNet,但是在很多图像转化学习问题(比如object detection)上效果奇好
Caffe
• 源于Berkeley的主流CV工具包,支持C++,python,matlab
• Model Zoo中有大量预训练好的模型供使用
Torch
• Facebook用的卷积神经网络工具包
• 通过时域卷积的本地接口,使用非常直观
• 定义新网络层简单
TensorFlow
• Google的深度学习框架
• TensorBoard可视化很方便
• 数据和模型并行化好,速度快
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。