TensorFlow入門(三)構(gòu)建簡單的卷積神經(jīng)網(wǎng)絡

Deep MNIST for Experts

TensorFlow是一個用于大數(shù)據(jù)計算強有力的庫,擅長的任務之一就是實現(xiàn)及訓練深度神經(jīng)網(wǎng)絡。這個教程中我們將學習TensorFlow構(gòu)建深度卷積mnist分類器的基本框架。

About this tutorial


教程的第一部分解釋mnist_softmax.py的代碼,這是TensorFlow模型的一個基本框架。第二部分展示了一些提高準確率的方法。

你可以復制粘貼代碼段到python環(huán)境中,或者選擇僅閱讀代碼。

我們在教程中將要完成:

  • 基于圖片像素值構(gòu)建認知mnist數(shù)字的softmax回歸
  • 用TensorFlow訓練數(shù)以千計的樣本來認知數(shù)字
  • 用測試集數(shù)據(jù)檢驗模型的準確性
  • 構(gòu)建、訓練、測試多層卷積神經(jīng)網(wǎng)絡來提高結(jié)果

Setup


在構(gòu)建模型之前,需要先加載mnist數(shù)據(jù)集,開始TensorFlow會話。

Load MNIST Data

如果你復制粘貼教程中的代碼,會由下兩行代碼開始,自動下載和讀取數(shù)據(jù)

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

這里,mnist是一個輕量級的類,用numpy矩陣存儲訓練集、驗證集、測試集,它也提供了方法來迭代數(shù)據(jù)的minibatch。

Start TensorFlow InteractiveSession

Tensorflow依賴于一個高效的C++后端來進行計算。與后端的這個連接叫做session。一般而言,使用TensorFlow的流程是先創(chuàng)建一個計算圖,然后在session中啟動它。

這里,我們使用更加方便的InteractiveSession類。通過它,你可以更加靈活地構(gòu)建你的代碼。它能讓你在運行圖的時候,插入一些計算圖來交錯節(jié)點。這對于工作在交互式環(huán)境中的人們來說非常便利,比如使用IPython。如果你沒有使用InteractiveSession,那么你需要在啟動session之前構(gòu)建整個計算圖,然后啟動該計算圖。

import tensorflow as tf
sess = tf.InteractiveSession()

Computation Graph

為了在Python中進行高效的數(shù)值計算,我們通常會使用像NumPy一類的庫,將一些諸如矩陣乘法的耗時操作在Python環(huán)境的外部來計算,這些計算通常會通過其它語言并用更為高效的代碼來實現(xiàn)。

但遺憾的是,每一個操作切換回Python環(huán)境時仍需要不小的開銷。如果你想在GPU或者分布式環(huán)境中計算時,這一開銷更加可怖,這一開銷主要可能是用來進行數(shù)據(jù)遷移。

TensorFlow也是在Python外部完成其主要工作,但是進行了改進以避免這種開銷。其并沒有采用在Python外部獨立運行某個耗時操作的方式,而是先讓我們描述一個交互操作圖,然后完全將其運行在Python外部。這與Theano或Torch的做法類似。

因此Python代碼的目的是用來構(gòu)建這個可以在外部運行的計算圖,以及安排計算圖的哪一部分應該被運行。

Build a Softmax Regression Model


這一章節(jié)我們用單個線性層來構(gòu)建softmax回歸。下一章節(jié),我們將延伸用多層卷積網(wǎng)絡來構(gòu)建softmax回歸。

Placeholders

我們開始創(chuàng)建輸入圖像和目標輸出的節(jié)點構(gòu)建計算圖。

x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])

這里,x和y_不是特定值,而是placeholder(占位符)——可以在TensorFlow運行某一計算時根據(jù)該占位符輸入具體的值。

輸入圖片x是一個2維的浮點數(shù)張量。這里,分配給它的shape為[None, 784],其中784是一張平鋪的MNIST圖片的維度。None表示其值大小不定,在這里作為第一個維度值,用以指代batch的大小,意即x的數(shù)量不定。輸出類別值y_也是一個2維張量,其中每一行為一個10維的one-hot向量,用于代表對應某一MNIST圖片的類別。

雖然placeholder的shape參數(shù)是可選的,但有了它,TensorFlow能夠自動捕捉因數(shù)據(jù)維度不一致導致的錯誤。

Variables

我們現(xiàn)在為模型定義權(quán)重W和偏置b。可以將它們當作額外的輸入,但是TensorFlow有一個更好的處理方式:變量。一個變量代表著TensorFlow計算圖中的一個值,能夠在計算過程中使用,甚至進行修改。在機器學習的應用過程中,模型參數(shù)一般用Variable來表示。

W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))

我們在調(diào)用tf.Variable的時候傳入初始值。在這個例子里,我們把W和b都初始化為零向量。W是一個784x10的矩陣(因為我們有784個特征和10個輸出值)。b是一個10維的向量(因為我們有10個分類)。

變量需要通過seesion初始化后,才能在session中使用。這一初始化步驟為,為初始值指定具體值(本例當中是全為零),并將其分配給每個變量,可以一次性為所有變量完成此操作。

sess.run(tf.global_variables_initializer())

Predicted Class and Loss Function

現(xiàn)在我們可以實現(xiàn)我們的回歸模型了。這只需要一行!我們把向量化后的圖片x和權(quán)重矩陣W相乘,加上偏置b,然后計算每個分類的softmax概率值。

y = tf.matmul(x,W) + b

我們可以很容易的指定損失函數(shù),損失表示了模型在單一樣本上預測的好壞,但我們試圖在所有訓練樣本上最小化損失,這里,我們的損失函數(shù)是目標類別和預測類別之間的交叉熵。

cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))

Train the Model


我們已經(jīng)定義好模型和訓練用的損失函數(shù),那么用TensorFlow進行訓練就很簡單了。因為TensorFlow知道整個計算圖,它可以使用自動求導找到對于各個變量的損失的梯度值。TensorFlow有大量內(nèi)置的優(yōu)化算法 這個例子中,我們用最速下降法讓交叉熵下降,步長為0.5.

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

這一行代碼實際上是用來往計算圖上添加一個新節(jié)點,其中包括計算梯度,計算每個參數(shù)的步長變化,并且計算出新的參數(shù)值

返回的節(jié)點train_step,運行時更新梯度下降的參數(shù)。因此可以重復運行train_step來訓練模型。

for _ in range(1000):
  batch = mnist.train.next_batch(100)
  train_step.run(feed_dict={x: batch[0], y_: batch[1]})

在每次訓練迭代時我們加載100個訓練樣本。我們運行train_step節(jié)點,用feed_dict參數(shù)更新placeholder張量x和y_。注意,你可以用feed_dict參數(shù)來更新計算圖中的張量,并不限制于placeholder。

Evaluate the Model

那么我們的模型性能如何呢?

首先讓我們找出那些預測正確的標簽。tf.argmax 是一個非常有用的函數(shù),它能給出某個tensor對象在某一維上的其數(shù)據(jù)最大值所在的索引值。由于標簽向量是由0,1組成,因此最大值1所在的索引位置就是類別標簽,比如tf.argmax(y,1)返回的是模型對于任一輸入x預測到的標簽值,而 tf.argmax(y_,1) 代表正確的標簽,我們可以用 tf.equal 來檢測我們的預測是否真實標簽匹配(索引位置一樣表示匹配)。

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

返回我們一序列的邏輯值,我們計算浮點數(shù)然后取均值,比如,[True, False, True, True]變成[1,0,1,1], 即 0.75.

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

最后,我們在測試集上評估準確性,大概有92%。

print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

Build a Multilayer Convolutional Network


mnist 92%的準確率是不好的,甚至是差的。這一章節(jié),我們將用一個小的卷積神經(jīng)網(wǎng)絡來完善它。這將會達到99.2%的準確率。

Weight Initialization

為了構(gòu)建模型,我們需要創(chuàng)建大量權(quán)重和偏差。這個模型中的權(quán)重在初始化時應該加入少量的噪聲來打破對稱性以及避免0梯度。由于我們使用的是ReLU神經(jīng)元,因此比較好的做法是用一個較小的正數(shù)來初始化偏差,以避免神經(jīng)元節(jié)點輸出恒為0的問題(dead neurons)。為了不在建立模型的時候反復做初始化操作,我們定義兩個函數(shù)用于初始化。

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)

Convolution and Pooling

TensorFlow在convolution(卷積)和pooling(池化)上有很強的靈活性。我們怎么處理邊界?步長應該設(shè)多大?這里,我們會一直使用vanilla版本。我們的卷積使用1步長(stride size),0邊距(padding size)的模板,保證輸出和輸入是同一個大小。我們的池化用簡單傳統(tǒng)的2x2大小的模板做max pooling。為了代碼更簡潔,我們把這部分抽象成一個函數(shù)。

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')

First Convolutional Layer

現(xiàn)在我們可以開始實現(xiàn)第一層了。它由一個卷積接一個max pooling組成。卷積在每個5x5的patch中算出32個特征。卷積的權(quán)重張量形狀是[5, 5, 1, 32],前兩個維度是patch的大小,接著是輸入的通道數(shù)目,最后是輸出的通道數(shù)目。 而對于每一個輸出通道都有一個對應的偏置。

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

為了用這一層,我們把x變成一個4d張量,其第2、第3維對應圖片的寬、高,最后一維代表圖片的顏色通道數(shù)(因為是灰度圖所以這里的通道數(shù)為1,如果是rgb彩色圖,則為3)。

x_image = tf.reshape(x, [-1,28,28,1])

我們把x_image和權(quán)值向量進行卷積,加上偏置項,然后應用ReLU激活函數(shù),最后進行max pooling.max_pool_2x2將產(chǎn)生14*14的圖片。

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

Second Convolutional Layer

為了構(gòu)建一個更深的網(wǎng)絡,我們會把幾個類似的層堆疊起來。第二層中,每個5x5的patch會得到64個特征。

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)

Densely Connected Layer

現(xiàn)在,圖片尺寸減小到7x7,我們加入一個有1024個神經(jīng)元的全連接層,用于處理整個圖片。我們把池化層輸出的張量reshape成一些向量,乘上權(quán)重矩陣,加上偏置,然后對其使用ReLU。

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

為了減少過擬合,我們在輸出層之前加入dropout。我們用一個placeholder來代表一個神經(jīng)元的輸出在dropout中保持不變的概率。這樣我們可以在訓練過程中啟用dropout,在測試過程中關(guān)閉dropout。 TensorFlow的tf.nn.dropout操作除了可以屏蔽神經(jīng)元的輸出外,還會自動處理神經(jīng)元輸出值的scale。所以用dropout的時候可以不用考慮scale。

keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

Readout Layer

最后,我們添加一個softmax層,就像前面的單層softmax regression一樣。

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

Train and Evaluate the Model

這個模型的效果如何呢?

為了進行訓練和評估,我們使用與之前簡單的單層SoftMax神經(jīng)網(wǎng)絡模型幾乎相同的一套代碼,區(qū)別在于:

  • 用更加復雜的ADAM優(yōu)化器來代替梯度最速下降。
  • 在feed_dict中加入額外的參數(shù)keep_prob來控制dropout比例。
  • 訓練中每100次迭代輸出一次日志
cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
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))
sess.run(tf.global_variables_initializer())
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}))

以上代碼,在最終測試集上的準確率大概是99.2%。

目前為止,我們已經(jīng)學會了用TensorFlow快捷地搭建、訓練和評估一個相對復雜的深度學習模型。

在這個小型的卷積網(wǎng)絡中,就算沒有用dropout,性能也是比較理想的。dropout對于減少過擬合非常有效,但通常用于訓練大型的神經(jīng)網(wǎng)絡。

附上mnist_deep.py代碼:

# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

"""A deep MNIST classifier using convolutional layers.

See extensive documentation at
https://www.tensorflow.org/get_started/mnist/pros
"""
# 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

from tensorflow.examples.tutorials.mnist import input_data

import tensorflow as tf

FLAGS = None


def deepnn(x):
  """deepnn builds the graph for a deep net for classifying digits.

  Args:
    x: an input tensor with the dimensions (N_examples, 784), where 784 is the
    number of pixels in a standard MNIST image.

  Returns:
    A tuple (y, keep_prob). y is a tensor of shape (N_examples, 10), with values
    equal to the logits of classifying the digit into one of 10 classes (the
    digits 0-9). keep_prob is a scalar placeholder for the probability of
    dropout.
  """
  # Reshape to use within a convolutional neural net.
  # Last dimension is for "features" - there is only one here, since images are
  # grayscale -- it would be 3 for an RGB image, 4 for RGBA, etc.
  x_image = tf.reshape(x, [-1, 28, 28, 1])

  # First convolutional layer - maps one grayscale image to 32 feature maps.
  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)

  # Pooling layer - downsamples by 2X.
  h_pool1 = max_pool_2x2(h_conv1)

  # Second convolutional layer -- maps 32 feature maps to 64.
  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)

  # Second pooling layer.
  h_pool2 = max_pool_2x2(h_conv2)

  # Fully connected layer 1 -- after 2 round of downsampling, our 28x28 image
  # is down to 7x7x64 feature maps -- maps this to 1024 features.
  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 - controls the complexity of the model, prevents co-adaptation of
  # features.
  keep_prob = tf.placeholder(tf.float32)
  h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

  # Map the 1024 features to 10 classes, one for each digit
  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):
  """conv2d returns a 2d convolution layer with full stride."""
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')


def max_pool_2x2(x):
  """max_pool_2x2 downsamples a feature map by 2X."""
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')


def weight_variable(shape):
  """weight_variable generates a weight variable of a given shape."""
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)


def bias_variable(shape):
  """bias_variable generates a bias variable of a given 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
  x = tf.placeholder(tf.float32, [None, 784])

  # Define loss and optimizer
  y_ = tf.placeholder(tf.float32, [None, 10])

  # Build the graph for the deep net
  y_conv, keep_prob = deepnn(x)

  cross_entropy = tf.reduce_mean(
      tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
  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))

  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    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}))

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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容