多层感知机
基本概念
背景知识
卷积神经网络(英文Convolutional Neural Network,简称CNN(PS:和某媒体同名))。这个结构最初设计是为了解决图像识别一类的问题,不过现在CNN也被广泛应用于时间序列类信号如音频和文本数据等等。此外卷积神经网络也是首个成功进行多层训练的神经网络结构,即LeCun的LeNet5.
起源历史
卷积神经网络最早出自19世纪60年代提出的感受野(Recepive Field)。这个概念是当时科学家通过研究猫的视觉皮层细胞后发现每一个视觉神经元只会处理一小块区域的视觉图像(也就是感知野)。
而后到了20世纪80年代,日本科学家提出神经认知机(Neocognitron),这个被认作为卷积神经网络最初的实现原型。在神经认知机中包含两类神经元:一类是抽取特征的S-cells(S layer),另一类是用于抗形变的C-cells(C layer)。其中S-cell对应我们现在的卷积神经网络中的卷积核滤波操作,而C-cell则对应激活函数和最大池化(Max-Pooling)等操作。
特性
特征提取
概念
在图像识别的研究中,难度最大的就是有效地组织特征,因为图像数据和传统的数据不同无法通过总结规律来人工设定特征。以股票预测模型为例,可以定向提取交易价格波动、市盈率、市净率等金融概念(或因子),这种人工总结的方法就是特征工程。当然对着一堆灰度值或者RGB像素值是很难人为组织什么特征了。
传统方法
深度学习出现前,我们必须使用SIFT、HoG等算法提取具有良好区分性的特征而后运用SVM等机器学习算法来进行图像识别。
特征不变性
概念
这个特性应该说是图像处理所必需的,毕竟不能说吧一张照片旋转了就认不出照片里的人了。这个特性在SIFT算法中就已经实现了,具体现为提取的特征低于缩放、平移、旋转等畸变具有不变性以及泛化性。
权值共享结构
概念
在每一个卷基层中可以有多个不同的卷积核,而每一卷积核都对应一个滤波后映射出的新图像,同一个新图像中每一个像素都来自完全相同的卷积核,这就是卷积核的权值共享。换言之,每一个卷积操作所使用的的都是共享的固定权值。
作用与优势
这种处理本身就是一种低级特征逐级整合高级特征的处理,此外这种处理比起书中之前的神经网络结构还具有模型复杂度低、减轻过拟合、降低计算量的优势。(具体可以参考前面多层感知机的随机采样编码设计)
具体能优化多少杂度呢?因为我们之前用的仅仅是28x28的图片可能很难感觉到。假设我们处理的是1000x1000pixel的图片(即100w个像素),我们就需要处理的是一个维度为100w的数据,而当我们连接一个相同大小的隐含层则会产生100wx100w=一万亿个链接。光是一个全连接层(Fully connected Layers, 即我们之前所使用的普通权重模型),就会产生一万亿个权重去训练。很明显一般的硬件能力并不足以处理。经由局部连接处理后,隐含层节点不直接与像素点链接而是与局部像素节点(感受野)链接。当我们设定局部感受野大小为10x10,我们拥有100w个节点的隐含层只需要创建10x10x100w个连接就可以了。相比较普通的全连接链接数降低了一万倍。另外根据前面权值共享的定义,我们训练所需的参数也仅仅是100个。但这个好事也仅仅存在于仅有一个卷积核的情况下。卷积核是一个滤波工具(Filter,可以理解为过滤器),所得到的结果就是退一类特征的映射(例如点线)。一般来说第一层有100个卷积核已经足够,在这个情况下100(卷积核数量)x100(核内参数数量)也就是一万个参数(注意卷积核的数量增加不会导致连接数增加,运算中共用一个链接)。此外我们之前也提到过,隐含层的节点数是可以调整的,卷基层的隐含层节点数也是可以调整的,而这取决于模型的取样步长,当步长为1时,隐含层节点数与输入像素数一致,当步长为5时隐含节点数就会缩水25倍,可以说神经网络中的每一个细节的压力都得到了减轻。
具体实现
引入数据和库
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets("MNIST_data/",one_hot=True)
sess = tf.InteractiveSession()
定义权值和偏差值生成的方法
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)
定义卷积层
卷积层的定义需要两样东西,一个是x即输入数据,二是卷积参数W来定义卷积核的参数。卷积参数W一般由四维组成,前二维是关于卷积核的尺寸,第三维表示数据channel(图像中是颜色,目前只有灰度所以待会儿定义第一层时会是1),第四维是卷积核数量,即输出的特征数。Strides 代表卷积末班移动的步长,一般2D图片对应的是第二维和第三维的数据(都是1的情况下就说明每一个像素点都不会遗漏)。最后的padding指的是边缘的处理,当边缘处像素数量不足是否输出特征。这里padding只有两种情况"SAME"和"VALID",前者会在自动补充不足的像素来保证输出与输入一致,而后者只会计算像素点完整的情况。
def conv2d(x, W):
return tf.nn.conv2d(x,W, strides=[1,1,1,1], padding='SAME')
定义池化层
池化层的功能是将图片(数据压缩)。这里我们会将每个2x2的限速块压缩为1x1的像素,而该像素会保留四个像素中灰度值最大的那个(即最特征最明显的)。ksize和strides的规则一样,表示压缩的像素尺寸,为了确保压缩效果还需要将strides与ksize一致确保生成的是一个缩小的图片。
def max_pool_2x2(x):
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
定义和处理输入变量
和以前不同的是,这次我们不再是直接取用一维数据而是通过reshape将其还原为28x28的数据块。因为卷积神经网络会处理像素间的空间关系。而reshape
第一个参数的第一维指的是限制的输入数据量(-1指无上限),而第四维指的是通道量(其实也就是第三维,不过在2D图片中还可以做颜色数据的处理)。
x = tf.placeholder(tf.float32,[None,784])
y_ = tf.placeholder(tf.float32,[None,10])
x_image = tf.reshape(x, [-1,28,28,1])
定义卷积操作
此处我们一口气设置了两个卷积层,具体参数在上面的方法已经有解释。这中间多出了一个激活层操作略去了不会激起反应的特征。值得注意的是第一层输出的时候通道已经由1变成了32(因为有32个特征)(个人理解ReLU激活操作和最大值池化操作会逐层进行),第二蹭的输入也要变成32。
W_conv1 = weight_variable([5,5,1,32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
W_conv2 = weight_variable([5,5,32,64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1,W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
一维化操作
完成了高级特征的提取,我们要做的就是和之前的图像分析一样的操作,在这之前我们需要把产生的高级特征再次一维化。
因为我们进行了两次2x2的池化,所以我们的图像已经不再是28x28而是7x7了,此外我们产生了64个高级特征所以权重的第一维是7x7x64,在根据这个量级设定隐含层节点为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)
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])
y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
设定损失函数和训练步骤等
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_conv),reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
启动神经网络
tf.global_variables_initializer().run()
for i in range(20000):
batch = mnist.train.next_batch(50)
if i%100 ==0:
train_accuracy = accuracy.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0})
print("step %d, training accuracy %g"%(i, train_accuracy))
train_step.run(feed_dict = {x:batch[0],y_: batch[1],keep_prob:0.5})
print("test accuracy %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))