作者: Sam (甄峰) sam_code@hotmail.com
0. 传统神经网络存在的问题:
全连接深度神经网络,顾名思义,就是每个神经元都与相邻层的每个神经元相连接。
以图片为例:
有一组100x100的图片要训练。则输入层拥有100x100=10000个神经元。假设只有一个隐藏层,也是10000个神经元。则W1的shape为[10000,
10000]. 有10000x10000个权值要确认。
我们知道,数据量(样本数)要和网络复杂度相匹配。
数据少,但网络太复杂,容易过拟合。
数据过多,网络太简单,则容易欠拟合。
一些人说:数据量要相当于未知权值的5-30倍。
那10000x10000个未知权值,需要的数据量太大,且计算量也太大。
1. 卷积神经网络的三个基本思想:
A. 局部感受野(Local receptive fields)
B. 权值共享。
C. 池化(pooling)
1.1: 局部感受野:
1962年,哈佛医学眼的几位教授在研究猫眼神经时。发现目标在猫的眼睛不同区域移动时,猫眼神经对应区域会活跃。提出局部感受野概念。
在全连接神经网络中,把输入层的每个神经元都与第一隐藏层的每个神经元相连接。
在CNN中,第一隐藏层的神经元,只与局部区域输入层的神经元相连接。这个局部区域即为局部感受野。
可以认为,隐藏层的神经元,学会了只分析它的视野范围内的特征。
举例说明:
MNIST的每张图片大小为28x28.
每个数字代表一个像素。则CNN的输入层有28x28个神经元,第一隐藏层中,每个神经元只与5x5个输入层神经元相连接。隐藏层的每个神经元,都有一个5x5的权值与之对应。
1.2:权值共享:
隐藏层的神经元,权值和偏移值是共享的。
所以可以这样看待:每个权值(例如:5x5)像一个滑动窗,
依次从输入层滑过,把获取的值填入后面的层.
这个滑动窗提测(detect)到的是输入层同一种特征(feature).
在滑动中,把整个输入层的特征记录到后面的隐藏层中了。
一个窗口只能学到一种特征。
窗口还可以称为: 卷积核(Kernel), 过滤器(filter).
在图像识别中,光学习一个特征,肯定不够。要学习多个特征,就要有更多的卷积核(kernel)。
卷积操作时,使用不同的卷积核,能得到不同的特征图,多个特征图对分类有极大提升。
例如:可以使用32个卷积核,生成32份特征。 窗口和窗口之间的W和b不是共享的。
32个窗口,就表示有32个W(5x5)矩阵和B。
权值共享可以极大地减少模型参数个数。
1.3:池化(pooling):
池化层通常接在卷积层后面。 池化的目的在于简化卷积层的输出。
例如:pooling窗口为2x2. 采用max pooling.
在卷积层后,每2x2个数据,去除最大值作为池化层的一个数据。
常用的池化有三种:
A: max pooling, 取窗口中最大值。
B: mean pooling, 取平均值。
C: 随机 pooling.
随机取某一个。
2. 卷积和池化时的padding方式:
卷积的Padding方式:
A. SAME Padding:
给输入层的周边补0,让特征图的大小与采样图的大小相同。
B. VALID Padding:
输入层的周边不会补0,卷积核不会超出采样图边界。
所以特征图的大小为: N-n+1.
N: 采样图长度。
n: 卷积核长度。
池化的Padding:
池化的Padding与卷积的Padding分类相同,但涵义不同。
A. SAME Padding:
可能会补0. 当采样图的大小不够时,会补0.
B. VALID Padding:
绝对不会补0.
3. Data_format:
所谓Data_format,其实是指4维数组的排列方式。它定义了一批图片数据的存储顺序。分NCHW,
NHWC两种。Tensorflow中,默认的是NHWC.
N: Batch数。
H: Height
W: Width
C: Channel数目。灰度图:1.
RGB:3
这个问题的实质是:将一张二维张量(h*w)拼接成三维(w*h*c)时,有两种做法:
NHWC: [batch, height, width, channels]
NCHW: [batch, channels, height, width]
例如:
NCHW: [batch, channels, height, width]
[4, 3, 128, 128]: 每份数据为4张图,RGB, 128x128.
[[[R:128x128],[G:128x128],[B:128x128]],
[[R:128x128],[G:128x128],[B:128x128]],
[[R:128x128],[G:128x128],[B:128x128]]
[[R:128x128],[G:128x128],[B:128x128]]]
共有4分,每份中分三个数据块,每个数据块是一个channel的数据。(RGB各个色度的数据集中存放)
NHWC:[batch, height,width, channels]
[4, 128, 128, 3]:每份数据为4张图, 128x128, RGB
[[RGB]....128x128个,
[RGB]....128x128个,
[RGB]....128x128个,
[RGB]....128x128个,
]
4. Tensorflow 中 和CNN相关的几个几个接口:
4.1:卷积:
tf.nn.conv2d( input, filter, strides, padding, use_cudnn_on_gpu=True, data_format='NHWC',dilations=[1, 1, 1, 1], name=None, )
使用4-D输入 input(样本), filter(卷积核), 计算一个2-D卷积。
参数讲解:
data_format: 一个string. 有两个选项:"NHWC"/"NCHW".
用来指定输入--input(样本)和输出的数据格式。
input: 一个tensor. 作为输入。 数据类型必须为:half, bfloat16, float32,
float64. 它是个4-D Tensor. 维度顺序由data_format决定。
filter: 卷积核。是个4-D
Tensor,数据类型与input相同。[filter_height, filter_width,
in_channels, out_channels]
strides: 卷积核滑动步长。一个List, 长度为4.
在每个维度上的滑动步长。维度顺序由data_format决定。如果为NHWC,则[1,n,n,1].
0和3位置一定为1.
padding: 一个string. 卷积的padding模式。“VALID”/ "SAME"
4.2: 池化(Pooling):
tf.nn.max_pool( value, ksize, strides, padding, data_format='NHWC', name=None, )
在input(value)上执行max pooling.
参数讲解:
value: 一个4-D Tensor. 格式由data_format决定。它将用来被池化。
ksize: 一个List或数组。4个int值。 用来描述各个维度上的池化核的大小.[1, n, n, 1].
0,3位一定为1.
strides: 一个List或数组。4个int值。 描述每个维度的滑动步长。[1,n,n, 1]
padding: 池化 padding模式。 “VALID”/ "SAME"
data_format: 一个string. 有两个选项:"NHWC"/"NCHW".
5. CNN卷积,池化的实际使用:
5.1:创建权值和偏移量:
def weight_variable(shape):
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)
要创建的权值和偏移量,都必须是个Variable. 才可以被训练。
偏移量,可以设定为0.1
权值,则采用正则分布的值。
tf.truncated_normal( shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None, )
输出random的值,为一段正则分布数据。
shape: shape.
mean:分布的中间值。
stddev:标准偏移量。
seed:种子。
5.2:Tensor的重新设置shape:
x = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, 10])
x_image = tf.reshape(x, [-1, 28,28,1])
可以看到,x本来分配了一个Nx784的placeholder. 可现在需要把这个2-D Tensor转成 4-D Tensor。
可以使用reshape. 但数据一定要能够对上。 比如None份,就是batch.
28x28=784.
5.3:以MNIST为例,查看CNN训练过程:
5.3.1:第一层卷积
# 5x5. 1 ch. 32 conv kernel=32 output
W_conv1 = weight_variable([5,5,1,32])
b_conv1 = bias_variable([32])
首先创建第一层隐藏层的权值和偏移量。
卷积核:5x5.
卷积核个数:32个。 则输出32个特征图。
池化
#h_conv1. [100, 28,28,32]
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
#h_pool1: [100, 14, 14, 32]
h_pool1 = max_pool_2x2(h_conv1)
5.3.2:第二层卷积:
第一层卷积池化后,有32张特征图,每张图大小14x14.
第二层卷积层,卷积核大小为:5x5, 第一层卷积层的输出是32个特征图,此处作为输入,输出则为64个特征图
W_conv2 = weight_variable([5,5,32,64])
b_conv2 = bias_variable([64])
#h_conv2 [100, 14,14,64]
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
#h_pool2 [100, 7, 7, 64]
h_pool2 = max_pool_2x2(h_conv2)
第二层卷积池化后, 每份数据有64张特征图, 每张图7x7 大小。
两个全连接层,一个神经元个数为1000,另一个为输出层,神经元个数为10.
#全连接
W_fc1 = weight_variable([7*7*64, 1024])# 第一个全连接层,
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)
#dropout
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2) +
b_fc2)
#交叉熵代价函数
cross_entropy =
tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y,
logits=prediction))
train_step =
tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)