基於Keras mnist手寫數字識別---Keras卷積神經網路入門教程
阿新 • • 發佈:2018-11-15
目錄
1、一些說明
本部落格是參考Tensorflow官方的使用Tensorflow實現的mnist手寫數字識別例子,使用Tensorflow內建的keras實現的mnist,獲得了和原有用tensorflow編寫的程式碼相當的效能。本文也可以作為真正意義上使用Keras實現的卷積神經網路入門Demo。參考連線如下:
專案目錄結構如下
mnist-keras
---data/ 存放的是下載的資料集
---logs/ 存放tensorflow日誌
---constants.py 定義常量
---test.py 載入模型預測單張圖片
---train.py 主檔案,模型定義以及訓練
---utils.py 定義工具函式
2、常量定義
constants.py如下
# coding=utf-8
# 全域性變數定義
# CVDF mirror of http://yann.lecun.com/exdb/mnist/
SOURCE_URL = 'http://yann.lecun.com/exdb/mnist/'
# 指定工作目錄,資料集就會儲存在這裡
WORK_DIRECTORY = 'data'
# 影象的大小
IMAGE_SIZE = 28
# 影象的通道數,為1,即為灰度影象
NUM_CHANNELS = 1
# 影象想素值的範圍
PIXEL_DEPTH = 255
# 分類數目,0~9總共有10類
NUM_LABELS = 10
# 驗證集大小
VALIDATION_SIZE = 5000 # Size of the validation set.
# 種子
SEED = 66478 # Set to None for random seed.
# 批次大小
BATCH_SIZE = 64
# 訓練多少個epoch
NUM_EPOCHS = 10
# 驗證集大小
EVAL_BATCH_SIZE = 64
3、工具函式
我把原來在convolutional.py中的部分工具函式移動到了utils.py這個檔案裡面,使主檔案(train.py)更加簡潔。知道每個函式的功能就行。utils.py如下
# coding=utf-8
# 相容python3
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import gzip
import os
import numpy
import tensorflow as tf
from six.moves import urllib
from six.moves import xrange # pylint: disable=redefined-builtin
from constants import *
def data_type(argv):
"""根據argv.use_fp16返回是否使用半精度"""
if argv.use_fp16:
return tf.float16
else:
return tf.float32
def maybe_download(filename):
"""
如果沒有下載檔案filename,那麼把檔案下載到WORK_DIRECTORY
"""
if not tf.gfile.Exists(WORK_DIRECTORY):
tf.gfile.MakeDirs(WORK_DIRECTORY)
filepath = os.path.join(WORK_DIRECTORY, filename)
if not tf.gfile.Exists(filepath):
filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath)
with tf.gfile.GFile(filepath) as f:
size = f.size()
print('Successfully downloaded', filename, size, 'bytes.')
return filepath
def extract_data(filename, num_images):
"""
解壓filename指定的影象資料集為4D tensor [num_images, y, x, channels]。
影象的值從[0, 255]被縮放到了[-0.5, 0.5]。
"""
print('Extracting', filename)
with gzip.open(filename) as bytestream:
bytestream.read(16)
buf = bytestream.read(IMAGE_SIZE * IMAGE_SIZE * num_images * NUM_CHANNELS)
data = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.float32)
data = (data - (PIXEL_DEPTH / 2.0)) / PIXEL_DEPTH
data = data.reshape(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)
return data
def extract_labels(filename, num_images):
"""把標籤解壓為一個int64的向量。"""
print('Extracting', filename)
with gzip.open(filename) as bytestream:
bytestream.read(8)
buf = bytestream.read(1 * num_images)
labels = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.int64)
return labels
def fake_data(num_images):
"""生成MNIST需要的假的資料集。"""
data = numpy.ndarray(
shape=(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS),
dtype=numpy.float32)
labels = numpy.zeros(shape=(num_images,), dtype=numpy.int64)
for image in xrange(num_images):
label = image % 2
data[image, :, :, 0] = label - 0.5
labels[image] = label
return data, labels
def error_rate(predictions, labels):
"""返回錯誤率"""
return 100.0 - (
100.0 *
numpy.sum(numpy.argmax(predictions, 1) == labels) /
predictions.shape[0])
4、模型定義以及訓練
接下來講train.py這個檔案,因為是模型定義以及訓練模型的檔案,我會講的詳細些。
4.1、匯入庫
# coding=utf-8
# 相容python3
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import scipy
# 從tensorflow裡匯入keras和keras.layer
from tensorflow import keras
from tensorflow.keras import layers
# 匯入工具函式
from utils import *
4.2、主入口
解析引數use_fp16和self_test,用到是ArgumentParser,就不詳細解釋了。
if __name__ == '__main__':
# 定義parser
parser = argparse.ArgumentParser()
parser.add_argument(
'--use_fp16',
default=False,
help='Use half floats instead of full floats if True.',
action='store_true')
parser.add_argument(
'--self_test',
default=False,
action='store_true',
help='True if running a self test.')
# 解析引數
FLAGS, unparsed = parser.parse_known_args()
# 呼叫主函式
main(FLAGS)
4.3、主函式
4.3.1、獲取訓練資料
def main(argv=None):
if argv.self_test:
"""
為了測試模型是否可以執行,生成了一些隨機資料集。
"""
print('Running self-test...')
# 生成訓練集
train_data, train_labels = fake_data(256)
# 生成驗證集
validation_data, validation_labels = fake_data(EVAL_BATCH_SIZE)
# 生成測試集
test_data, test_labels = fake_data(EVAL_BATCH_SIZE)
# 只訓練一個epoch
num_epochs = 1
else:
"""
準備手寫數字資料集。
"""
# 下載資料集
train_data_filename = maybe_download('train-images-idx3-ubyte.gz')
train_labels_filename = maybe_download('train-labels-idx1-ubyte.gz')
test_data_filename = maybe_download('t10k-images-idx3-ubyte.gz')
test_labels_filename = maybe_download('t10k-labels-idx1-ubyte.gz')
# 把下載的資料解壓為numpy陣列
train_data = extract_data(train_data_filename, 60000)
train_labels = extract_labels(train_labels_filename, 60000)
test_data = extract_data(test_data_filename, 10000)
test_labels = extract_labels(test_labels_filename, 10000)
# 分割train_data與train_labels,得到訓練集以及驗證集
validation_data = train_data[:VALIDATION_SIZE, ...]
validation_labels = train_labels[:VALIDATION_SIZE]
train_data = train_data[VALIDATION_SIZE:, ...]
train_labels = train_labels[VALIDATION_SIZE:]
num_epochs = NUM_EPOCHS
# 儲存一下第一張圖片,用來測試
img0 = test_data[0]
# 因為test_data被縮放到了-0.5~0.5,所以要恢復到原來的範圍[0, 255]
img0 = img0*PIXEL_DEPTH+PIXEL_DEPTH/2
# 儲存
cv2.imwrite('test0.png', img0)
# 對label進行one-hot編碼,因為模型的最後一層有10個輸出單元(10個類別)
train_labels = keras.utils.to_categorical(train_labels)
validation_labels = keras.utils.to_categorical(validation_labels)
test_labels = keras.utils.to_categorical(test_labels)
4.3.1、定義模型
在 Tensorflow mnist官方Demo中的手寫數字識別的網路結構如下(從下往上看)。最下的那個是輸入input(定義了每張圖的大小和通道),然後依次是卷積層conv1、池化層pool1、conv2、pool2,接著是一個flatten層(把pool2之後輸出展開為一維向量),然後全連線層fc1,然後是一個dropout層(在呼叫evalute和predict時自動失效),fc2,最後是softmax層。
模型的主要引數如下:
Name | Output shape | Kernel size | Kernel count | Strides | Activation | Padding | Use bias |
---|---|---|---|---|---|---|---|
input_1 | (None, 28, 28, 1) | ||||||
conv1 | (None, 28, 28, 32) | (5, 5) | 32 | (1, 1) | relu | same | True |
pool1 | (None, 14, 14, 32) | (2, 2) | (2, 2) | same | |||
conv2 | (None, 14, 14, 64) | (5, 5) | 64 | (1, 1) | relu | same | True |
pool2 | (None, 7, 7, 64) | (2, 2) | (2, 2) | same | |||
flatten | (None, 3136) | ||||||
fc1 | (None, 512) | relu | True | ||||
fc2 | (None, 10) | ||||||
softmax | (None, 10) |
實現模型的程式碼如下:
def inference(dtype):
"""
使用keras定義mnist模型
"""
# define a truncated_normal initializer
tn_init = keras.initializers.truncated_normal(0, 0.1, SEED, dtype=dtype)
# define a constant initializer
const_init = keras.initializers.constant(0.1, dtype)
# define a L2 regularizer
l2_reg = keras.regularizers.l2(5e-4)
# inputs: shape(None, 28, 28, 1)
inputs = layers.Input(shape=(IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS), dtype=dtype)
# conv1: shape(None, 28, 28, 32)
conv1 = layers.Conv2D(32, (5, 5), strides=(1, 1), padding='same',
activation='relu', use_bias=True,
kernel_initializer=tn_init, name='conv1')(inputs)
# pool1: shape(None, 14, 14, 32)
pool1 = layers.MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding='same', name='pool1')(conv1)
# conv2: shape(None, 14, 14, 64)
conv2 = layers.Conv2D(64, (5, 5), strides=(1, 1), padding='same',
activation='relu', use_bias=True,
kernel_initializer=tn_init,
bias_initializer=const_init, name='conv2')(pool1)
# pool2: shape(None, 7, 7, 64)
pool2 = layers.MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding='same', name='pool2')(conv2)
# flatten: shape(None, 3136)
flatten = layers.Flatten(name='flatten')(pool2)
# fc1: shape(None, 512)
fc1 = layers.Dense(512, 'relu', True, kernel_initializer=tn_init,
bias_initializer=const_init, kernel_regularizer=l2_reg,
bias_regularizer=l2_reg, name='fc1')(flatten)
# dropout
dropout1 = layers.Dropout(0.5, seed=SEED)(fc1)
# dense2: shape(None, 10)
fc2 = layers.Dense(NUM_LABELS, activation=None, use_bias=True,
kernel_initializer=tn_init, bias_initializer=const_init, name='fc2',
kernel_regularizer=l2_reg, bias_regularizer=l2_reg)(dropout1)
# softmax: shape(None, 10)
softmax = layers.Softmax(name='softmax')(fc2)
# make new model
model = keras.Model(inputs=inputs, outputs=softmax, name='nmist')
return model
接下來對上面用到的函式進行一些說明:
- Conv2D:Keras的卷積可由這一個函式完成,引數包含了一層的所需要的所有引數。主要引數如下
- filters: 整數。指定卷積核(在Keras中對應的概念為kernel,在tesorflow中為weights)的個數。這個數目也是本層輸出shape的最後一個元素大小。
- kernel_size: 一個整數(2D卷積視窗的寬和高均為這個整數)或者兩個整陣列成的列表(分別指定2D卷積視窗的高和寬)。和tensorflow不同的是,這裡指定卷積核大小時,只需要指定2D卷積視窗的寬和高,而不需要指定卷積核的第三個維度的大小(卷積核第三個維度的大小和輸入的shape的最後一個元素相同);例如,在tensorflow中,一個影象批次(batch)的shape為(16,28,28,1),卷積核的個數32,卷積核的大小為5,那麼,卷積核的shape需要為(32,5,5,1);在Keras中,32應該是第一個引數的值,(5,5)是kernel_size的值,而1,會根據輸入(16,28,28,1)的最後一個元素自動推算為1(相等)。
- strides: 一個整數(strides的寬和高均為這個整數)或者兩個整陣列成的列表(分別strides的高和寬)。對於Conv2D,strides也只需要指定寬和高。
- padding: “valid” 或者 “same”。 請參考吳恩達的卷積神經網路課程。
- data_format: 字串。 “channels_last” 或者 “channels_first”。如果是"channels_last"(預設),那麼,輸入資料的shape為(batch_size, height, width, channels);如果為"channels_first",那麼輸入資料為shape為(batch_size, channels, height, width)。這個引數就是指定通道數的位置。
- dilation_rate: 請參考官方文件。
- activation: 此層使用的啟用函式的名稱。不指定的話就不使用啟用函式。
- use_bias: 布林值。是否使用bias。
- kernel_initializer: 指定kernel_initializer。參見: initializers。
- bias_initializer: 指定bias_initializer。參見: initializers。
- kernel_regularizer: 指定kernel_regularizer。參見regularizer。
- bias_regularizer: 指定bias_regularizer。參見regularizer。
- activity_regularizer: 請參考官方文件。
- kernel_constraint: 請參考官方文件。
- bias_constraint:請參考官方文件。
其他函式請自行查閱文件,反正定義模型是要比tensorflow簡單多了。
4.3.2、編譯模型
# 獲取模型
model = inference(data_type(argv))
# 列印模型的資訊
model.summary()
# 編譯模型;第一個引數是優化器;第二個引數為loss,因為是多元分類問題,固為
# 'categorical_crossentropy';第三個引數為metrics,就是在訓練的時候需
# 要監控的指標列表。
model.compile(optimizer=keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=1e-5),
loss='categorical_crossentropy', metrics=['accuracy'])
4.3.3、訓練模型
# 設定回撥
callbacks = [
# 把TensorBoard的日誌寫入資料夾'./logs'
keras.callbacks.TensorBoard(log_dir='./logs'),
]
# 開始訓練
model.fit(train_data, train_labels, BATCH_SIZE, epochs=num_epochs,
validation_data=(validation_data, validation_labels), callbacks=callbacks)
4.3.4、評估以及儲存模型
# evaluate
print('', 'evaluating on test sets...')
loss, accuracy = model.evaluate(test_data, test_labels)
print('test loss:', loss)
print('test Accuracy:', accuracy)
# save model
model.save('mnist-model.h5')
4.3.5、預測單張圖片
test.py如下
# coding=utf-8
# 相容python3
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import cv2
import numpy as np
from tensorflow import keras
if __name__ == '__main__':
# 讀圖片
img0 = cv2.imread('test0.png', cv2.IMREAD_UNCHANGED)
print(img0.shape)
# 擴充套件維度為4維,因為模型的輸入需要是4維
img0 = np.resize(img0, (1, img0.shape[0], img0.shape[1], 1))
print(img0.shape)
# 恢復模型以及權重
model = keras.models.load_model('mnist-model.h5')
# 獲取模型最後一層,也就是softmax層的輸出,輸出的shape為(1, 10)
last_layer_output = model.predict(img0)
# 獲取最大值的索引
max_index = np.argmax(last_layer_output, axis=-1)
print('predict number: %d' % (int(max_index[0])))
print('probability: ', last_layer_output[0][max_index])
、