前饋神經網絡的弊端
前一篇文章介紹過MNIST,是采用的前饋神經網絡的結構,這種結構有一個很大的弊端,就是提供的樣本必須面面俱到,否則就容易出現預測失敗。如下圖:
同樣是在一個圖片中找圓形,如果左邊為訓練樣本,右邊為測試樣本,如果只訓練了左邊的情況,右邊的一定會預測錯誤,然而在我們人眼看來,這兩個圓形的特征其實是一樣的,不過是移動了一個位置而已,但是因為前饋網絡結構的原因,導致在做權重分配的時候,把更多的權重分配給了左上角,右下角分配的較少,所以在做最終預測,便會出現較大的誤差。所以,我們需要在圖片中找出圓形的特征,即使是變換了位置,這些特征仍然是一樣的,這樣既減少了訓練成本,又提升了訓練的效率。就好像寫代碼把公共的部分封裝起來復用一樣。這個時候卷積神經網絡就隆重登場了。
卷積神經網絡(CNN)
根據前面的論述,我們發現,樣本可以理解為是一個特征集,我們要找出這個特征集並過濾掉重復的特征,以提升訓練的效率,降低成本。那么,我們如何來找尋圖片中的各個特征呢?讓我們來看看卷積神經網絡的結構就可以明白了。
卷積神經網絡一般由:卷積層,池化層,全連接層組成。其中卷積和池化會根據不同的需要來做多次。
在介紹這些結構之前,先要說一下圖片是如何用矩陣來表達的。看如下代碼:
#!/usr/bin/python # -*- coding: UTF-8 -*- import PIL.Image as Image import numpy as np import pandas as pd import os import matplotlib.pyplot as plt #顏色信息會被去除,如果要帶顏色,則是三維數組 def imageToMatrix(filename): # 讀取圖片 im = Image.open(filename) # 顯示圖片 # im.show() width, height = im.size #灰度化 im = im.convert("L") data = im.getdata() data = np.matrix(data, dtype="float") / 255.0 new_data = np.reshape(data, (height, width)) return new_data def matrixToImage(data): data = data * 255.0 new_im = Image.fromarray(data) return new_im filename = "m2.png" data = imageToMatrix(filename) dataframe = pd.DataFrame(data=data) dataframe.to_csv('out.csv', sep=' ', header=False, float_format='%.2f', index=False) #后面是將矩陣轉換為圖片 #new_im = matrixToImage(data) #plt.imshow(data, cmap=plt.cm.gray, interpolation="nearest") #new_im.show()
代碼所表達的意思就是將m2.png轉換為矩陣(去掉色彩信息),將矩陣輸出到out.csv文件中,然后我們將csv文件打開后,看看對比效果,如下圖:
(csv文件)
(原圖,50x50的png圖片)
csv文件中的數字表示的是每一個點的亮度,越靠近1越亮。如果是彩色圖片,則每個點都包含了RGB三種顏色(因為色彩可以認為是RGB三種顏色疊加在一起來表達的),將會變為一個三維數組。
1、卷積層
卷積層主要是使用過濾器(filter)找尋樣本特征的一個過程。過濾器,又叫kernel或者filter,是一個表示權重的矩陣,聽名字就可以知道(卷積神經網絡的卷積層)這一層非常重要。
由淺入深,我們先討論只含有灰度信息的圖片,也就是圖片的深度為1,這樣使用得filter就只含有w(寬)和h(高)。
一般我們會使用2x2,3x3,5x5等(一般來說,圖片越大,采用的過濾器都會大一點)矩陣來與圖片矩陣中相應位置的子矩陣做“點積”,如下圖(為了看得清楚我使用的是20x20的png):
(注:矩陣的點積就是矩陣對應位置的元素做乘法,然后相加,例如上圖黃色矩陣和藍色矩陣做點積就是:1x0+1x0+1x0+1x0+1x1+1x0+1x0+1x0+1x0=1,這個1就是第一次卷積得到的矩陣的第一個元素的值)。
上圖就是過濾器工作的方式,會向右以一定的步長來移動,到最右邊以后,會以一定的步長(stride)來向下移動,直到整個圖片結束。然后會得到一個新的矩陣,這個矩陣就是我們的特征矩陣。
所以,如果我們使用不同的過濾器(權重值不同)來對圖片做卷積,就會得到圖片不同的信息,比如:我如果使用
這樣的過濾器,就可以得到圖片的邊緣特征。所以,一般在卷積層,都會使用多個過濾器來采集圖片的特征。
這里有個鏈接可以觀看不同過濾器卷積的過程。
不難發現,如果是20x20的矩陣,采用3x3,步長為1的話,一次卷積過后,矩陣會變為:18x18,那么如果多幾個卷積層,那圖片豈不是要被卷沒了?所以為了讓卷積不改變圖片大小,增加了zero padding(0填充)的概念,就是往圖片的四周補0,這樣就不會改變圖片大小了。這里有一個公式來計算卷積以后圖片的大小:
(input size - filter size + 2 * zero padding size) / stride + 1
現在我們來看看帶有RGB的圖片如何做卷積。圖片帶有了R、G、B三種顏色通道,就變為了20x20x3,那么相應的過濾器也要帶有三個通道,所以之前的3x3,就變成了3x3x3,如下圖:
三個權重數值可以不同,然后做點積,結果就是:
Xr0 * Wr0 + Xr1 * Wr1 + Xr2 * Wr2 + Xr3 * Wr3 + Xr4 * Wr4 + Xr5 * Wr5 + Xr6 * Wr6 + Xr7 * Wr7 + Xr8 * Wr8 +
Xg0 * Wg0 + Xg1 * Wg1 + Xg2 * Wg2 + Xg3 * Wg3 + Xg4 * Wg4 + Xg5 * Wg5 + Xg6 * Wg6 + Xg7 * Wg7 + Xg8 * Wg8 +
Xb0 * Wb0 + Xb1 * Wb1 + Xb2 * Wb2 + Xb3 * Wb3 + Xb4 * Wb4 + Xb5 * Wb5 + Xb6 * Wb6 + Xb7 * Wb7 + Xb8 * Wb8 +
B(偏置量)
所以其實卷積最后得到的矩陣是:20x20。
經過卷積之后的矩陣,要做ReLU變換來增強擬合能力,ReLU可以保證得到的矩陣所有元素的取值都大於0。
2、池化層
在卷積過后,我們要將結果進行池化,池化層的目的就是為了降低不必要的冗余信息(downsampling),有兩種池化操作:求最大和求平均,一般都采用求最大值得方式。
選取一個2x2的filter,然后將卷積后的矩陣分割為不同的塊,選取步長為2,然后將里面的最大值取出來得到一個新的矩陣的過程,就是最大池化操作,如圖:
那么,我們不禁會問,將其中的一些值舍棄掉,只留最大值,對判定結果有沒有影響?
答案是會有一定影響,但是影響會比較小,因為選擇的過濾器比圖片本身要小很多,那經過過濾器篩選出來的特征本身也是很小的局部特征,而這些特征中,經過權值計算,權值比較小的值則表明對該局部特征的影響比較小,所以只保留影響最大的最大值,這樣既能減少矩陣大小,而又不會對預測結果產生太大影響。
3、全連接層
即是將所有特征組合在一起組成一個一維數組的過程。
總體
至此,卷積神經網絡的主要層級功能都介紹完畢了,我們來看看一種卷積神經網絡的整體結構,如下圖:
它采用了兩個卷積層,兩個池化層,一個全連接層來表達。
目前比較流行的卷積網絡的結構有:
LeNet(1990年誕生的第一個成功的卷積神經網絡的模型)、AlexNet、ZFNet、GoogLeNet、VGGNet、ResNet等,感興趣的朋友可以自行搜索一下。
TensorFlow的官方例子
TensorFlow在對MNIST的圖片集上采用了卷積神經網絡來做圖形識別,代碼如下:
# coding=utf-8 # Disable linter warnings to maintain consistency with tutorial. # pylint: disable=invalid-name # pylint: disable=g-bad-import-order from __future__ import absolute_import from __future__ import division from __future__ import print_function import argparse import sys import tempfile from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf FLAGS = None def deepnn(x): with tf.name_scope('reshape'): x_image = tf.reshape(x, [-1, 28, 28, 1]) ## 第一層卷積操作 ## with tf.name_scope('conv1'): 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) with tf.name_scope('pool1'): h_pool1 = max_pool_2x2(h_conv1) # Second convolutional layer -- maps 32 feature maps to 64. ## 第二層卷積操作 ## with tf.name_scope('conv2'): 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) with tf.name_scope('pool2'): h_pool2 = max_pool_2x2(h_conv2) ## 第三層全連接操作 ## with tf.name_scope('fc1'): 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) with tf.name_scope('dropout'): keep_prob = tf.placeholder(tf.float32) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) ## 第四層輸出操作 ## with tf.name_scope('fc2'): W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2 return y_conv, keep_prob def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 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) def main(_): # Import data mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True) # Create the model # 聲明一個占位符,None表示輸入圖片的數量不定,28*28圖片分辨率 x = tf.placeholder(tf.float32, [None, 784]) # 類別是0-9總共10個類別,對應輸出分類結果 y_ = tf.placeholder(tf.float32, [None, 10]) y_conv, keep_prob = deepnn(x) with tf.name_scope('loss'): cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv) cross_entropy = tf.reduce_mean(cross_entropy) with tf.name_scope('adam_optimizer'): train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) with tf.name_scope('accuracy'): correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1)) correct_prediction = tf.cast(correct_prediction, tf.float32) accuracy = tf.reduce_mean(correct_prediction) graph_location = tempfile.mkdtemp() print('Saving graph to: %s' % graph_location) train_writer = tf.summary.FileWriter(graph_location) train_writer.add_graph(tf.get_default_graph()) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for i in range(20000): batch = mnist.train.next_batch(50) if i % 1000 == 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})) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--data_dir', type=str, default='/tmp/tensorflow/mnist/input_data', help='Directory for storing input data') FLAGS, unparsed = parser.parse_known_args() tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
代碼比較清楚,就不過多講述了,基本就是:卷積->池化->卷積->池化->全連接->dropout->全連接->訓練->測試。
注意到代碼中的:with tf.name_scope('xxx'): 是在構建計算圖,看其中的代碼:
graph_location = tempfile.mkdtemp() print('Saving graph to: %s' % graph_location) train_writer = tf.summary.FileWriter(graph_location) train_writer.add_graph(tf.get_default_graph())
是將計算圖存儲下來,打印出路徑,我們找到相應的路徑以后,在命令行中執行:
將地址復制到Chrome中,點擊Graph標簽,就可以看到計算圖了。
上面的代碼都跟是遵循我們之前介紹的卷積網絡的卷積層,池化層,全連接層來組成的,其中多了一個dropout,如下:
with tf.name_scope('dropout'): keep_prob = tf.placeholder(tf.float32) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
dropout的目的是為了防止產生過擬合的問題,這里解釋一下過擬合和欠擬合。
過擬合(overfitting):當某個模型在經過訓練后,過度的學習了訓練集的噪音和干擾項,從而導致在測試集上表現很差,但是在訓練集上表現很好,就稱為過擬合。
欠擬合(underfitting):值模型無論在測試集還是訓練集上表現都不好,也就是說訓練樣本不夠。
那么dropout如何來避免這些噪音的影響呢?通常做法是隨機的將某些特征的權值置為0。
詳細解釋看這里:https://yq.aliyun.com/articles/68901
常見問題
1、官網的例子filter為什么要選32個?filter size為什么要選5x5?stride為什么要選2?
網上有人說是因為這些數字到底選多少是一門藝術和經驗的總結。
2、在做卷積的時候,為什么可以使用同一個filter(權重矩陣),而不是每移動一步就換一個矩陣?
因為filter的作用在於將圖片中的某一類特征卷積出來,將符合條件的特征都篩選出來。所以可以共享權值矩陣。
3、全連接層到底是做什么的?有什么用?
將之前的結果,例如官網例子中的第二輪pool2的結果 7x7x64,通過跟乘以權重矩陣(W),得到1024個元素的一維數組的過程。就跟之前講MNIST一樣,我們就是要通過訓練來得到這個W的各個元素的值。這個值會影響預測結果。
4、卷積網絡如何保證特征不變性(同樣的特征不因位置改變而改變)?
因為對同一個圖片來說,在同一深度同一卷積層下,權值是共享的,也就是說,相同的特征會被統一識別出來而不需要再次學習。
參考文章:
https://www.zhihu.com/question/39022858