永興的tensorflow筆記-9 全連接MNIST初體驗(手寫字識別)

在這裏插入圖片描述

一、什麼是全聯接神經網絡?

全連接神經網絡(fully connected neural network),顧名思義,就是相鄰兩層之間任意兩個節點之間都有連接。全連接神經網絡是最爲普通的一種模型(比如和CNN相比),由於是全連接,所以會有更多的權重值和連接,因此也意味着佔用更多的內存和計算。
在這裏插入圖片描述
全連接層的每一個結點都與上一層的所有結點相連,用來把前邊提取到的特徵綜合起來。由於其全相連的特性,一般全連接層的參數也是最多的。
在這裏插入圖片描述

二、什麼是MNIST?

MNIST是一個入門級的計算機視覺數據集,它包含各種手寫數字圖片:
在這裏插入圖片描述
它也包含每一張圖片對應的標籤,告訴我們這個是數字幾。比如,上面這四張圖片的標籤分別是5,0,4,1。
我們將訓練一個機器學習模型用於預測圖片裏面的數字。所以,我們這裏會從一個很簡單的數學模型開始,它叫做Softmax Regression。

三、MNIST數據集獲取:

1、下載數據集:

MNIST數據集的官網是:點擊打開
在這裏插入圖片描述
下載下來的數據集被分成兩部分:60000行的訓練數據集(mnist.train)和10000行的測試數據集(mnist.test)。這樣的切分很重要,在機器學習模型設計時必須有一個單獨的測試數據集不用於訓練而是用來評估這個模型的性能,從而更加容易把設計的模型推廣到其他數據集上(泛化)。

正如前面提到的一樣,每一個MNIST數據單元有兩部分組成:一張包含手寫數字的圖片和一個對應的標籤。我們把這些圖片設爲“xs”,把這些標籤設爲“ys”。訓練數據集和測試數據集都包含xs和ys,比如訓練數據集的圖片是 mnist.train.images ,訓練數據集的標籤是 mnist.train.labels。
在這裏插入圖片描述
在python環境下直接運行下面的代碼生成數據集文件
複製下列代碼,放入python文件夾內命名爲:input_data.py
運行後會自動創建MNIST_data文件,將下載的4個文件放入

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import gzip
import os
import tensorflow.python.platform
import numpy
from six.moves import urllib
from six.moves import xrange  # pylint: disable=redefined-builtin
import tensorflow as tf
SOURCE_URL = 'http://yann.lecun.com/exdb/mnist/'
def maybe_download(filename, work_directory):
  """Download the data from Yann's website, unless it's already here."""
  if not os.path.exists(work_directory):
    os.mkdir(work_directory)
  filepath = os.path.join(work_directory, filename)
  if not os.path.exists(filepath):
    filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath)
    statinfo = os.stat(filepath)
    print('Successfully downloaded', filename, statinfo.st_size, 'bytes.')
  return filepath
def _read32(bytestream):
  dt = numpy.dtype(numpy.uint32).newbyteorder('>')
  return numpy.frombuffer(bytestream.read(4), dtype=dt)[0]
def extract_images(filename):
  """Extract the images into a 4D uint8 numpy array [index, y, x, depth]."""
  print('Extracting', filename)
  with gzip.open(filename) as bytestream:
    magic = _read32(bytestream)
    if magic != 2051:
      raise ValueError(
          'Invalid magic number %d in MNIST image file: %s' %
          (magic, filename))
    num_images = _read32(bytestream)
    rows = _read32(bytestream)
    cols = _read32(bytestream)
    buf = bytestream.read(rows * cols * num_images)
    data = numpy.frombuffer(buf, dtype=numpy.uint8)
    data = data.reshape(num_images, rows, cols, 1)
    return data
def dense_to_one_hot(labels_dense, num_classes=10):
  """Convert class labels from scalars to one-hot vectors."""
  num_labels = labels_dense.shape[0]
  index_offset = numpy.arange(num_labels) * num_classes
  labels_one_hot = numpy.zeros((num_labels, num_classes))
  labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1
  return labels_one_hot
def extract_labels(filename, one_hot=False):
  """Extract the labels into a 1D uint8 numpy array [index]."""
  print('Extracting', filename)
  with gzip.open(filename) as bytestream:
    magic = _read32(bytestream)
    if magic != 2049:
      raise ValueError(
          'Invalid magic number %d in MNIST label file: %s' %
          (magic, filename))
    num_items = _read32(bytestream)
    buf = bytestream.read(num_items)
    labels = numpy.frombuffer(buf, dtype=numpy.uint8)
    if one_hot:
      return dense_to_one_hot(labels)
    return labels
class DataSet(object):
  def __init__(self, images, labels, fake_data=False, one_hot=False,
               dtype=tf.float32):
    """Construct a DataSet.
    one_hot arg is used only if fake_data is true.  `dtype` can be either
    `uint8` to leave the input as `[0, 255]`, or `float32` to rescale into
    `[0, 1]`.
    """
    dtype = tf.as_dtype(dtype).base_dtype
    if dtype not in (tf.uint8, tf.float32):
      raise TypeError('Invalid image dtype %r, expected uint8 or float32' %
                      dtype)
    if fake_data:
      self._num_examples = 10000
      self.one_hot = one_hot
    else:
      assert images.shape[0] == labels.shape[0], (
          'images.shape: %s labels.shape: %s' % (images.shape,
                                                 labels.shape))
      self._num_examples = images.shape[0]
      # Convert shape from [num examples, rows, columns, depth]
      # to [num examples, rows*columns] (assuming depth == 1)
      assert images.shape[3] == 1
      images = images.reshape(images.shape[0],
                              images.shape[1] * images.shape[2])
      if dtype == tf.float32:
        # Convert from [0, 255] -> [0.0, 1.0].
        images = images.astype(numpy.float32)
        images = numpy.multiply(images, 1.0 / 255.0)
    self._images = images
    self._labels = labels
    self._epochs_completed = 0
    self._index_in_epoch = 0
  @property
  def images(self):
    return self._images
  @property
  def labels(self):
    return self._labels
  @property
  def num_examples(self):
    return self._num_examples
  @property
  def epochs_completed(self):
    return self._epochs_completed
  def next_batch(self, batch_size, fake_data=False):
    """Return the next `batch_size` examples from this data set."""
    if fake_data:
      fake_image = [1] * 784
      if self.one_hot:
        fake_label = [1] + [0] * 9
      else:
        fake_label = 0
      return [fake_image for _ in xrange(batch_size)], [
          fake_label for _ in xrange(batch_size)]
    start = self._index_in_epoch
    self._index_in_epoch += batch_size
    if self._index_in_epoch > self._num_examples:
      # Finished epoch
      self._epochs_completed += 1
      # Shuffle the data
      perm = numpy.arange(self._num_examples)
      numpy.random.shuffle(perm)
      self._images = self._images[perm]
      self._labels = self._labels[perm]
      # Start next epoch
      start = 0
      self._index_in_epoch = batch_size
      assert batch_size <= self._num_examples
    end = self._index_in_epoch
    return self._images[start:end], self._labels[start:end]
def read_data_sets(train_dir, fake_data=False, one_hot=False, dtype=tf.float32):
  class DataSets(object):
    pass
  data_sets = DataSets()
  if fake_data:
    def fake():
      return DataSet([], [], fake_data=True, one_hot=one_hot, dtype=dtype)
    data_sets.train = fake()
    data_sets.validation = fake()
    data_sets.test = fake()
    return data_sets
  TRAIN_IMAGES = 'train-images-idx3-ubyte.gz'
  TRAIN_LABELS = 'train-labels-idx1-ubyte.gz'
  TEST_IMAGES = 't10k-images-idx3-ubyte.gz'
  TEST_LABELS = 't10k-labels-idx1-ubyte.gz'
  VALIDATION_SIZE = 5000
  local_file = maybe_download(TRAIN_IMAGES, train_dir)
  train_images = extract_images(local_file)
  local_file = maybe_download(TRAIN_LABELS, train_dir)
  train_labels = extract_labels(local_file, one_hot=one_hot)
  local_file = maybe_download(TEST_IMAGES, train_dir)
  test_images = extract_images(local_file)
  local_file = maybe_download(TEST_LABELS, train_dir)
  test_labels = extract_labels(local_file, one_hot=one_hot)
  validation_images = train_images[:VALIDATION_SIZE]
  validation_labels = train_labels[:VALIDATION_SIZE]
  train_images = train_images[VALIDATION_SIZE:]
  train_labels = train_labels[VALIDATION_SIZE:]
  data_sets.train = DataSet(train_images, train_labels, dtype=dtype)
  data_sets.validation = DataSet(validation_images, validation_labels,
                                 dtype=dtype)
  data_sets.test = DataSet(test_images, test_labels, dtype=dtype)
  return data_sets
2、導入數據庫:

然後我們可以使用導入數據集

import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

每一張圖片包含28像素X28像素。我們可以用一個數字數組來表示這張圖片:
在這裏插入圖片描述
我們把這個數組展開成一個向量,長度是 28x28 = 784。如何展開這個數組(數字間的順序)不重要,只要保持各個圖片採用相同的方式展開。從這個角度來看,MNIST數據集的圖片就是在784維向量空間裏面的點, 並且擁有比較複雜的結構 (提醒: 此類數據的可視化是計算密集型的)。

展平圖片的數字數組會丟失圖片的二維結構信息。這顯然是不理想的,最優秀的計算機視覺方法會挖掘並利用這些結構信息,我們會在後續教程中介紹。但是在這個教程中我們忽略這些結構,所介紹的簡單數學模型,softmax迴歸(softmax regression),不會利用這些結構信息。
在這裏插入圖片描述
因此,在MNIST訓練數據集中,mnist.train.images 是一個形狀爲 [60000, 784] 的張量,第一個維度數字用來索引圖片,第二個維度數字用來索引每張圖片中的像素點。在此張量裏的每一個元素,都表示某張圖片裏的某個像素的強度值,值介於0和1之間。
在這裏插入圖片描述
相對應的MNIST數據集的標籤是介於0到9的數字,用來描述給定圖片裏表示的數字。爲了用於這個教程,我們使標籤數據是"one-hot vectors"。 一個one-hot向量除了某一位的數字是1以外其餘各維度數字都是0。所以在此教程中,數字n將表示成一個只有在第n維度(從0開始)數字爲1的10維向量。比如,標籤0將表示成([1,0,0,0,0,0,0,0,0,0,0])。因此, mnist.train.labels 是一個 [60000, 10] 的數字矩陣。

四、構建神經網絡:

1、我們使用的是Softmax迴歸:

我們知道MNIST的每一張圖片都表示一個數字,從0到9。我們希望得到給定圖片代表每個數字的概率。比如說,我們的模型可能推測一張包含9的圖片代表數字9的概率是80%但是判斷它是8的概率是5%(因爲8和9都有上半部分的小圓),然後給予它代表其他數字的概率更小的值。

這是一個使用softmax迴歸(softmax regression)模型的經典案例。softmax模型可以用來給不同的對象分配概率。即使在之後,我們訓練更加精細的模型時,最後一步也需要用softmax來分配概率。

softmax迴歸(softmax regression)分兩步:第一步

爲了得到一張給定圖片屬於某個特定數字類的證據(evidence),我們對圖片像素值進行加權求和。如果這個像素具有很強的證據說明這張圖片不屬於該類,那麼相應的權值爲負數,相反如果這個像素擁有有利的證據支持這張圖片屬於這個類,那麼權值是正數。

下面的圖片顯示了一個模型學習到的圖片上每個像素對於特定數字類的權值。紅色代表負數權值,藍色代表正數權值。
在這裏插入圖片描述
我們也需要加入一個額外的偏置量(bias),因爲輸入往往會帶有一些無關的干擾量。因此對於給定的輸入圖片 x 它代表的是數字 i 的證據可以表示爲:
在這裏插入圖片描述
其中 代表權重, 代表數字 i 類的偏置量,j 代表給定圖片 x 的像素索引用於像素求和。然後用softmax函數可以把這些證據轉換成概率 y:
在這裏插入圖片描述
這裏的softmax可以看成是一個激勵(activation)函數或者鏈接(link)函數,把我們定義的線性函數的輸出轉換成我們想要的格式,也就是關於10個數字類的概率分佈。因此,給定一張圖片,它對於每一個數字的吻合度可以被softmax函數轉換成爲一個概率值。softmax函數可以定義爲:
在這裏插入圖片描述
展開等式右邊的子式,可以得到:
在這裏插入圖片描述
但是更多的時候把softmax模型函數定義爲前一種形式:把輸入值當成冪指數求值,再正則化這些結果值。這個冪運算表示,更大的證據對應更大的假設模型(hypothesis)裏面的乘數權重值。反之,擁有更少的證據意味着在假設模型裏面擁有更小的乘數係數。假設模型裏的權值不可以是0值或者負值。Softmax然後會正則化這些權重值,使它們的總和等於1,以此構造一個有效的概率分佈。
對於softmax迴歸模型可以用下面的圖解釋,對於輸入的xs加權求和,再分別加上一個偏置量,最後再輸入到softmax函數中:
在這裏插入圖片描述
如果把它寫成一個等式,我們可以得到:
在這裏插入圖片描述
我們也可以用向量表示這個計算過程:用矩陣乘法和向量相加。這有助於提高計算效率。(也是一種更有效的思考方式)
在這裏插入圖片描述
更進一步,可以寫成更加緊湊的方式:

在這裏插入圖片描述

2、搭建前向傳播:

我們通過操作符號變量來描述這些可交互的操作單元,可以用下面的方式創建一個:

x = tf.placeholder("float", [None, 784])
y_ = tf.placeholder("float", [None,10])

x和y_不是一個特定的值,而是一個佔位符placeholder,我們在TensorFlow運行計算時輸入這個值。我們希望能夠輸入任意數量的MNIST圖像,每一張圖展平成784維的向量。我們用2維的浮點數張量來表示這些圖,這個張量的形狀是[None,784 ]。(這裏的None表示此張量的第一個維度可以是任何長度的。)

我們的模型也需要權重值和偏置量,當然我們可以把它們當做是另外的輸入(使用佔位符),但TensorFlow有一個更好的方法來表示它們:Variable 。 一個Variable代表一個可修改的張量,存在在TensorFlow的用於描述交互性操作的圖中。它們可以用於計算輸入值,也可以在計算中被修改。對於各種機器學習應用,一般都會有模型參數,可以用Variable表示。

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

我們賦予tf.Variable不同的初值來創建不同的Variable:在這裏,我們都用全爲零的張量來初始化W和b。因爲我們要學習W和b的值,它們的初值可以隨意設置。

注意,W的維度是[784,10],因爲我們想要用784維的圖片向量乘以它以得到一個10維的證據值向量,每一位對應不同數字類。b的形狀是[10],所以我們可以直接把它加到輸出上面。

現在,我們可以實現我們的模型啦。只需要一行代碼!

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

首先,我們用tf.matmul(​​X,W)表示x乘以W,對應之前等式裏面的,這裏x是一個2維張量擁有多個輸入。然後再加上b,把和輸入到tf.nn.softmax函數裏面。

3、計算損失函數:

了訓練我們的模型,我們首先需要定義一個指標來評估這個模型是好的。其實,在機器學習,我們通常定義指標來表示一個模型是壞的,這個指標稱爲成本(cost)或損失(loss),然後儘量最小化這個指標。但是,這兩種方式是相同的。
在這裏插入圖片描述
一個非常常見的,非常漂亮的成本函數是“交叉熵”(cross-entropy)。交叉熵產生於信息論裏面的信息壓縮編碼技術,但是它後來演變成爲從博弈論到機器學習等其他領域裏的重要技術手段。

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

首先,用 tf.log 計算 y 的每個元素的對數。接下來,我們把 y_ 的每一個元素和 tf.log(y_) 的對應元素相乘。最後,用 tf.reduce_sum 計算張量的所有元素的總和。(注意,這裏的交叉熵不僅僅用來衡量單一的一對預測和真實值,而是所有100幅圖片的交叉熵的總和。對於100個數據點的預測表現比單一數據點的表現能更好地描述我們的模型的性能。

現在我們知道我們需要我們的模型做什麼啦,用TensorFlow來訓練它是非常容易的。因爲TensorFlow擁有一張描述你各個計算單元的圖,它可以自動地使用反向傳播算法(backpropagation algorithm)來有效地確定你的變量是如何影響你想要最小化的那個成本值的。然後,TensorFlow會用你選擇的優化算法來不斷地修改變量以降低成本。

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

在這裏,我們要求TensorFlow用梯度下降算法(gradient descent algorithm)以0.01的學習速率最小化交叉熵。梯度下降算法(gradient descent algorithm)是一個簡單的學習過程,TensorFlow只需將每個變量一點點地往使成本不斷降低的方向移動。當然TensorFlow也提供了其他許多優化算法:只要簡單地調整一行代碼就可以使用其他的算法。

TensorFlow在這裏實際上所做的是,它會在後臺給描述你的計算的那張圖裏面增加一系列新的計算操作單元用於實現反向傳播算法和梯度下降算法。然後,它返回給你的只是一個單一的操作,當運行這個操作時,它用梯度下降算法訓練你的模型,微調你的變量,不斷減少成本。

現在,我們已經設置好了我們的模型。在運行計算之前,我們需要添加一個操作來初始化我們創建的變量:

init = tf.initialize_all_variables()
with tf.Session() as sess:
	sess.run(init)

然後開始訓練模型,這裏我們讓模型循環訓練10000次!
在Session下:

    for i in range(10000):
        batch_xs, batch_ys = mnist.train.next_batch(100)
        sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

該循環的每個步驟中,我們都會隨機抓取訓練數據中的100個批處理數據點,然後我們用這些數據點作爲參數替換之前的佔位符來運行train_step。

使用一小部分的隨機數據來進行訓練被稱爲隨機訓練(stochastic training)- 在這裏更確切的說是隨機梯度下降訓練。在理想情況下,我們希望用我們所有的數據來進行每一步的訓練,因爲這能給我們更好的訓練結果,但顯然這需要很大的計算開銷。所以,每一次訓練我們可以使用不同的數據子集,這樣做既可以減少計算開銷,又可以最大化地學習到數據集的總體特性。

4、評估我們的模型:

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

首先讓我們找出那些預測正確的標籤。tf.argmax 是一個非常有用的函數,它能給出某個tensor對象在某一維上的其數據最大值所在的索引值。由於標籤向量是由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))

這行代碼會給我們一組布爾值。爲了確定正確預測項的比例,我們可以把布爾值轉換成浮點數,然後取平均值。例如,[True, False, True, True] 會變成 [1,0,1,1] ,取平均值後得到 0.75.

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

最後,我們計算所學習到的模型在測試數據集上面的正確率。

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

這個最終結果值應該大約是91%。

這個結果好嗎?嗯,並不太好。事實上,這個結果是很差的。這是因爲我們僅僅使用了一個非常簡單的模型。不過,做一些小小的改進,我們就可以得到97%的正確率。最好的模型甚至可以獲得超過99.7%的準確率!

文章借鑑Tensorflow官方解釋

附件1:完整代碼展示

import tensorflow as tf
import lesson_forth.input_data as input_data #也可直接 import input_data 
#input_data 需要直接創建
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
x = tf.placeholder("float", [None, 784])
y_ = tf.placeholder("float", [None,10])
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x,W) + b)
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
init = tf.initialize_all_variables()
with tf.Session() as sess:
    sess.run(init)
    for i in range(10000):
        batch_xs, batch_ys = mnist.train.next_batch(100) #沒次喂入100個數據
        sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
        if i % 1000 == 0:
            correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
            accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
            print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

在這裏插入圖片描述
如果電腦報錯:
可以查看:點擊打開

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章