TensorFlow高層封裝:從入門到噴這本書
0. 寫在前面
參考書
《TensorFlow:實戰Google深度學習框架》(第2版)
划重點
從今天開始(20190505-1521),我的博客都用Markdown語法來編寫啦,也不知道以后的自己會不會被人所知,會不會有大佬來看過去的我,給我挖墳呢。想想就有點期待呢!希望自己還能更加努力!更加優秀吧!
1. TensorFlow高層封裝總覽
目前比較主流的TensorFlow高層封裝有4個,分別是TensorFlow-Slim、TFLearn、Keras和Estimator。
首先,這里介紹先用TensorFlow-Slim在MNIST數據集上實現LeNet-5模型。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: slim_learn.py
@time: 2019/4/22 10:53
@desc: 使用TensorFlow-Slim在MNIST數據集上實現LeNet-5模型。
"""
import tensorflow as tf
import tensorflow.contrib.slim as slim
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
# 通過TensorFlow-Slim來定義LeNet-5的網絡結構
def lenet5(inputs):
# 將輸入數據轉化為一個4維數組。其中第一維表示batch大小,另三維表示一張圖片。
inputs = tf.reshape(inputs, [-1, 28, 28, 1])
# 定義第一層卷積層。從下面的代碼可以看到通過TensorFlow-Slim定義的網絡結構
# 並不需要用戶去關心如何聲明和初始化變量,而只需要定義網絡結構即可。下一行代碼中
# 定義了一個卷積層,該卷積層的深度為32,過濾器的大小為5x5,使用全0補充。
net = slim.conv2d(inputs, 32, [5, 5], padding='SAME', scope='layer1-conv')
# 定義一個最大池化層,其過濾器大小為2x2,步長為2.
net = slim.max_pool2d(net, 2, stride=2, scope='layer2-max-pool')
# 類似的定義其他網絡層結構
net = slim.conv2d(net, 64, [5, 5], padding='SAME', scope='layer3-conv')
net = slim.max_pool2d(net, 2, stride=2, scope='layer4-max-pool')
# 直接使用TensorFlow-Slim封裝好的flatten函數將4維矩陣轉為2維,這樣可以
# 方便后面的全連接層的計算。通過封裝好的函數,用戶不再需要自己計算通過卷積層之后矩陣的大小。
net = slim.flatten(net, scope='flatten')
# 通過TensorFlow-Slim定義全連接層,該全連接層有500個隱藏節點。
net = slim.fully_connected(net, 500, scope="layer5")
net = slim.fully_connected(net, 10, scope="output")
return net
# 通過TensorFlow-Slim定義網絡結構,並使用之前章節中給出的方式訓練定義好的模型。
def train(mnist):
# 定義輸入
x = tf.placeholder(tf.float32, [None, 784], name='x-input')
y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')
# 使用TensorFLow-Slim定義網絡結構
y = lenet5(x)
# 定義損失函數和訓練方法
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1)) # 1 means axis=1
loss = tf.reduce_mean(cross_entropy)
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
# 訓練過程
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(10000):
xs, ys = mnist.train.next_batch(100)
_, loss_value = sess.run([train_op, loss], feed_dict={x: xs, y_: ys})
if i % 1000 == 0:
print("After %d training step(s), loss on training batch is %g." % (i, loss_value))
def main(argv=None):
mnist = input_data.read_data_sets('D:/Python3Space/BookStudy/book2/MNIST_data', one_hot=True)
train(mnist)
if __name__ == '__main__':
main()
OK!運行吧皮卡丘!
第一個例子都報錯。。。(ValueError: Rank mismatch: Rank of labels (received 1) should equal rank of logits minus 1 (received 4).)
我哭了!找了我半天錯誤,才發現少寫了一句。
net = slim.flatten(net, scope='flatten')
可把我愁壞了,整了半天才弄好。。。
網上都是什么神仙回答,解釋的有板有眼的,都說這本書是垃圾,害得我差點立刻在我對這本書評價的博客上再加上幾句芬芳。
好歹是學到了知識了。對logits和labels加深了印象了。
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
logits:是計算得到的結果
labels:是原來的數據標簽。
千萬不要記混了!
labels=tf.argmax(y_, 1)
labels輸入的是[0, 0, 0, 1, 0, 0, 0, 0, 0, 0](以MNIST為例),
而在tf.nn.sparse_softmax_cross_entropy_with_logits函數中
labels的輸入格式需要是[3],也就是說,是類別的編號。
誒!問題來了!
logits=y
logits的格式與labels一樣嗎?
不一樣!
logits的格式與labels轉換前的一樣,也就是
[0.2, 0.3, 0.1, 0.9, 0.1, 0.1, 0.2, 0.2, 0.4, 0.6]
如果不轉換labels的話,可以用tf.nn.softmax_cross_entropy_with_logits達到同樣的效果。
誒?那為什么非要轉換一下labels呢?
我也沒看懂,非要騷一下吧。。。
好了正確的運行結果出來了:
如果我們把剛才說的那句代碼改為:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=y, labels=y_)
試試看?
哇哦~正常運行了有沒有!!!
所以呢?所以為什么這里要非要用有sparse的這個函數呢?
反正我是沒看懂(攤手┓( ´∀` )┏)。。。
與TensorFlow-Slim相比,TFLearn是一個更加簡潔的高層封裝。
因為TFLearn並沒有集成在TensorFlow中,所以首先是用pip安裝。
安裝完后,下面是用TFLearn在MNIST數據集上實現LeNet-5模型。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: tflearn_learn.py
@time: 2019/5/5 16:53
@desc: 使用TFLearn在MNIST數據集上實現LeNet-5模型。
"""
import tflearn
from tflearn.layers.core import input_data, fully_connected
from tflearn.layers.conv import conv_2d, max_pool_2d
from tflearn.layers.estimator import regression
import tflearn.datasets.mnist as mnist
# 讀取mnist數據
trainX, trainY, testX, testY = mnist.load_data(data_dir="D:/Python3Space/BookStudy/book2/MNIST_data", one_hot=True)
# 將圖像數據reshape成卷積神經網絡輸入的格式
trainX = trainX.reshape([-1, 28, 28, 1])
testX = testX.reshape([-1, 28, 28, 1])
# 構建神經網絡,這個過程和TensorFlow-Slim比較類似。input_data定義了一個placeholder來接入輸入數據。
net = input_data(shape=[None, 28, 28, 1], name='input')
# 通過TFLearn封裝好的API定義一個深度為5,過濾器為5x5,激活函數為ReLU的卷積層
net = conv_2d(net, 32, 5, activation='relu')
# 定義一個過濾器為2x2的最大池化層
net = max_pool_2d(net, 2)
# 類似地定義其他的網絡結構。
net = conv_2d(net, 64, 5, activation='relu')
net = max_pool_2d(net, 2)
net = fully_connected(net, 500, activation='relu')
net = fully_connected(net, 10, activation='softmax')
# 使用TFLearn封裝好的函數定義學習任務。指定優化器為sgd,學習率為0.01,損失函數為交叉熵。
net = regression(net, optimizer='sgd', learning_rate=0.01, loss='categorical_crossentropy')
# 通過定義的網絡結構訓練模型,並在指定的驗證數據上驗證模型的效果。
# TFLearn將模型的訓練過程封裝到了一個類中,這樣可以減少非常多的冗余代碼。
model = tflearn.DNN(net, tensorboard_verbose=0)
model.fit(trainX, trainY, n_epoch=20, validation_set=([testX, testY]), show_metric=True)
個人感相較於Slim,TFLearn好用太多了吧。。。特別是model.fit真的是給我眼前一亮的感覺,這也太帥了吧,瞧這交叉熵小黃字,瞧這epoch,瞧這step。。。封裝萬歲!!!(對我這種菜雞而言,不要跟我談底層,我!不!夠!格!)
運行結果:
2. Keras介紹
2.1 Keras基本用法
下面是用原生態的Keras在MNIST數據集上實現LeNet-5模型。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: keras_learn.py
@time: 2019/5/5 17:42
@desc: 使用Keras在MNIST數據集上實現LeNet-5模型。
"""
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D
from keras import backend as K
num_calsses = 10
img_rows, img_cols = 28, 28
# 通過Keras封裝好的API加載MNIST數據。其中trainX就是一個60000x28x28的數組,
# trainY是每一張圖片對應的數字。
(trainX, trainY), (testX, testY) = mnist.load_data()
# 因為不同的底層(TensorFlow或者MXNet)對輸入的要求不一樣,所以這里需要根據對圖像
# 編碼的格式要求來設置輸入層的格式。
if K.image_data_format() == 'channels_first':
trainX = trainX.reshape(trainX.shape[0], 1, img_rows, img_cols)
testX = testX.reshape(trainX.shape[0], 1, img_rows, img_cols)
# 因為MNIST中的圖片是黑白的,所以第一維的取值為1
input_shape = (1, img_rows, img_cols)
else:
trainX = trainX.reshape(trainX.shape[0], img_rows, img_cols, 1)
testX = testX.reshape(testX.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
# 將圖像像素轉化為0到1之間的實數。
trainX = trainX.astype('float32')
testX = testX.astype('float32')
trainX /= 255.0
testX /= 255.0
# 將標准答案轉化為需要的格式(One-hot編碼)。
trainY = keras.utils.to_categorical(trainY, num_calsses)
testY = keras.utils.to_categorical(testY, num_calsses)
# 使用Keras API定義模型
model = Sequential()
# 一層深度為32,過濾器大小為5x5的卷積層
model.add(Conv2D(32, kernel_size=(5, 5), activation='relu', input_shape=input_shape))
# 一層過濾器大小為2x2的最大池化層。
model.add(MaxPooling2D(pool_size=(2, 2)))
# 一層深度為64, 過濾器大小為5x5的卷積層。
model.add(Conv2D(64, (5, 5), activation='relu'))
# 一層過濾器大小為2x2的最大池化層。
model.add(MaxPooling2D(pool_size=(2, 2)))
# 將卷積層的輸出拉直后作為下面全連接的輸入。
model.add(Flatten())
# 全連接層,有500個節點。
model.add(Dense(500, activation='relu'))
# 全連接層,得到最后的輸出。
model.add(Dense(num_calsses, activation='softmax'))
# 定義損失函數、優化函數和測評的方法。
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(), metrics=['accuracy'])
# 類似TFLearn中的訓練過程,給出訓練數據,batch大小、訓練輪數和驗證數據,Keras可以自動完成模型的訓練過程。
model.fit(trainX, trainY, batch_size=128, epochs=20, validation_data=(testX, testY))
# 在測評數據上計算准確率
score = model.evaluate(testX, testY)
print('Test loss: ', score[0])
print('Test accuracy: ', score[1])
運行之后(跑了我一夜呀我滴媽。。。):
下面是用原生態的Keras實現循環神經網絡。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: keras_rnn.py
@time: 2019/5/6 12:30
@desc: 用原生態的Keras實現循環神經網絡
"""
from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Embedding, LSTM
from keras.datasets import imdb
# 最多使用的單詞數
max_features = 20000
# 循環神經網絡的截斷長度。
maxlen = 80
batch_size = 32
# 加載數據並將單詞轉化為ID,max_features給出了最多使用的單詞數。和自然語言模型類似,
# 會將出現頻率較低的單詞替換為統一的的ID。通過Keras封裝的API會生成25000條訓練數據和
# 25000條測試數據,每一條數據可以被看成一段話,並且每段話都有一個好評或者差評的標簽。
(trainX, trianY), (testX, testY) = imdb.load_data(num_words=max_features)
print(len(trainX), 'train sequences')
print(len(testX), 'test sequences')
# 在自然語言中,每一段話的長度是不一樣的,但循環神經網絡的循環長度是固定的,所以這里需要先將
# 所有段落統一成固定長度。對於長度不夠的段落,要使用默認值0來填充,對於超過長度的段落
# 則直接忽略掉超過的部分。
trainX = sequence.pad_sequences(trainX, maxlen=maxlen)
testX = sequence.pad_sequences(testX, maxlen=maxlen)
print('trainX shape', trainX.shape)
print('testX shape: ', testX.shape)
# 在完成數據預處理之后構建模型
model = Sequential()
# 構建embedding層。128代表了embedding層的向量維度。
model.add(Embedding(max_features, 128))
# 構建LSTM層
model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
# 構建最后的全連接層。注意在上面構建LSTM層時只會得到最后一個節點的輸出,
# 如果需要輸出每個時間點的結果,呢么可以將return_sequence參數設為True。
model.add(Dense(1, activation='sigmoid'))
# 與MNIST樣例類似的指定損失函數、優化函數和測評指標。
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# 在測試數據上評測模型。
score = model.evaluate(testX, testY, batch_size=batch_size)
print('Test loss: ', score[0])
print('Test accuracy: ', score[1])
睡了個午覺就跑完啦:
2.2 Keras高級用法
面對上面的例子,都是順序搭建的神經網絡模型,類似於Inception這樣的模型結構,就需要更加靈活的模型定義方法了。
在這里我真的是忍不住要吐槽一下書上的內容,簡直完全沒有講清楚在說什么鬼。。。沒說清楚究竟是用的那一部分的數據,是MNIST還是rnn的數據。。。搗鼓了半天才知道是MNIST。然后這里的意思應該是用全連接的方式,即輸入數據為(60000, -1),也就是說樣本是60000個,然后把圖片的維度拉伸為1維。(這里我也是摸索了好久才知道的),所以在代碼中需要對數據進行reshape處理。不然會報錯:
ValueError: Error when checking input: expected input_1 to have 2 dimensions, but got array with shape (60000, 28, 28)
參考鏈接:https://blog.csdn.net/u012193416/article/details/79399679
是真的坑爹,只能說。。。什么也沒有說清楚,就特么瞎指揮。。。(然鵝,我是真的菜。。。攤手。。。)
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: keras_inception.py
@time: 2019/5/6 14:29
@desc: 用更加靈活的模型定義方法在MNIST數據集上實現全連接層模型。
"""
import keras
from keras.layers import Input, Dense
from keras.models import Model
from keras.datasets import mnist
# 使用前面介紹的類似方法生成trainX、trainY、testX、testY,唯一的不同是這里只用了
# 全連接層,所以不需要將輸入整理成三維矩陣。
num_calsses = 10
img_rows, img_cols = 28, 28
# 通過Keras封裝好的API加載MNIST數據。其中trainX就是一個60000x28x28的數組,
# trainY是每一張圖片對應的數字。
(trainX, trainY), (testX, testY) = mnist.load_data()
trainX = trainX.reshape(len(trainX), -1)
testX = testX.reshape(len(testX), -1)
# 將圖像像素轉化為0到1之間的實數。
trainX = trainX.astype('float32')
testX = testX.astype('float32')
trainX /= 255.0
testX /= 255.0
# 將標准答案轉化為需要的格式(One-hot編碼)。
trainY = keras.utils.to_categorical(trainY, num_calsses)
testY = keras.utils.to_categorical(testY, num_calsses)
# 定義輸入,這里指定的維度不用考慮batch大小。
inputs = Input(shape=(784, ))
# 定義一層全連接層,該層有500隱藏節點,使用ReLU激活函數。這一層的輸入為inputs
x = Dense(500, activation='relu')(inputs)
# 定義輸出層。注意因為keras封裝的categorical_crossentropy並沒有將神經網絡的輸出
# 再經過一層softmax,所以這里需要指定softmax作為激活函數。
predictions = Dense(10, activation='softmax')(x)
# 通過Model類創建模型,和Sequential類不同的是Model類在初始化的時候需要指定模型的輸入和輸出
model = Model(inputs=inputs, outputs=predictions)
# 使用與前面類似的方法定義損失函數、優化函數和評測方法。
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(), metrics=['accuracy'])
# 使用與前面類似的方法訓練模型。
model.fit(trainX, trainY, batch_size=128, epochs=10, validation_data=(testX, testY))
修改之后運行可以得到:
通過這樣的方式,Keras就可以實現類似Inception這樣的模型結構了。
現在又要說坑爹的部分了,這本書在這里直接照抄的Keras的手冊中的例子,來解釋用Keras實現Inception-v3的模型結構,所以給出的代碼是這樣的
from keras.layers import Conv2D, MaxPooling2D, Input
# 定義輸入圖像尺寸
input_img = Input(shape=(256, 256, 3))
# 定義第一個分支。
tower_1 = Conv2D(64, (1, 1), padding='same', activation='relu')(input_img)
tower_1 = Conv2D(64, (3, 3), padding='same', activation='relu')(tower_1)
# 定義第二個分支。與順序模型不同,第二個分支的輸入使用的是input_img,而不是第一個分支的輸出。
tower_2 = Conv2D(64, (1, 1), padding='same', activation='relu')(input_img)
tower_2 = Conv2D(64, (5, 5), padding='same', activation='relu')(tower_2)
# 定義第三個分支。類似地,第三個分支的輸入也是input_img。
tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding='same')(input_img)
tower_3 = Conv2D(64, (1, 1), padding='same', activation='relu')(tower_3)
# 將三個分支通過concatenate的方式拼湊在一起。
output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)
你可能要問“這就完啦?”,我想告訴你的是,對的。關於Inception-v3的部分就這么點。然后我給你看一眼網上官方的代碼:
參考鏈接:https://keras.io/zh/getting-started/functional-api-guide/
是不是有種似曾相識的感覺。。。
踏馬的根本就沒有想着去實現好嗎?
我也是醉了的,我就問一句,不是一直在用MNIST數據集作為例子嗎!那這個
input_img = Input(shape=(256, 256, 3))
圖像尺寸怎么突然就編程(256, 256, 3)了呢?而不是(28, 28, 1)呢?
這本書一點都不走心好嗎!
我也是佛了,那么我只能靠自己理解,並自己寫例子了。這里面的艱辛我就不說了,不賣慘了,是真的恨,我只希望每一個例子都能夠有始有終,都能夠有輸出有結果,能運行!
下面貼一下我自己想的改的代碼吧:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: keras_inception2.py
@time: 2019/5/6 15:43
@desc: 用原生態的Keras實現Inception
"""
from keras.layers import Conv2D, MaxPooling2D, Input, Dense, Flatten
import keras
from keras.models import Model
from keras.datasets import mnist
from keras import backend as K
# 使用前面介紹的類似方法生成trainX、trainY、testX、testY,唯一的不同是這里只用了
# 全連接層,所以不需要將輸入整理成三維矩陣。
num_calsses = 10
img_rows, img_cols = 28, 28
# 通過Keras封裝好的API加載MNIST數據。其中trainX就是一個60000x28x28的數組,
# trainY是每一張圖片對應的數字。
(trainX, trainY), (testX, testY) = mnist.load_data()
if K.image_data_format() == 'channels_first':
trainX = trainX.reshape(trainX.shape[0], 1, img_rows, img_cols)
testX = testX.reshape(trainX.shape[0], 1, img_rows, img_cols)
# 因為MNIST中的圖片是黑白的,所以第一維的取值為1
input_shape = (1, img_rows, img_cols)
else:
trainX = trainX.reshape(trainX.shape[0], img_rows, img_cols, 1)
testX = testX.reshape(testX.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
# 將圖像像素轉化為0到1之間的實數。
trainX = trainX.astype('float32')
testX = testX.astype('float32')
trainX /= 255.0
testX /= 255.0
# 將標准答案轉化為需要的格式(One-hot編碼)。
trainY = keras.utils.to_categorical(trainY, num_calsses)
testY = keras.utils.to_categorical(testY, num_calsses)
# 定義輸入圖像尺寸
input_img = Input(shape=(28, 28, 1))
# 定義第一個分支。
tower_1 = Conv2D(64, (1, 1), padding='same', activation='relu')(input_img)
tower_1 = Conv2D(64, (3, 3), padding='same', activation='relu')(tower_1)
# 定義第二個分支。與順序模型不同,第二個分支的輸入使用的是input_img,而不是第一個分支的輸出。
tower_2 = Conv2D(64, (1, 1), padding='same', activation='relu')(input_img)
tower_2 = Conv2D(64, (5, 5), padding='same', activation='relu')(tower_2)
# 定義第三個分支。類似地,第三個分支的輸入也是input_img。
tower_3 = MaxPooling2D((3, 3), strides=(1, 1), padding='same')(input_img)
tower_3 = Conv2D(64, (1, 1), padding='same', activation='relu')(tower_3)
# 將三個分支通過concatenate的方式拼湊在一起。
output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis=1)
# 將卷積層的輸出拉直后作為下面全連接的輸入。
tower_4 = Flatten()(output)
# 全連接層,有500個節點。
tower_5 = Dense(500, activation='relu')(tower_4)
# 全連接層,得到最后的輸出。
predictions = Dense(num_calsses, activation='softmax')(tower_5)
# 通過Model類創建模型,和Sequential類不同的是Model類在初始化的時候需要指定模型的輸入和輸出
model = Model(inputs=input_img, outputs=predictions)
# 使用與前面類似的方法定義損失函數、優化函數和評測方法。
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(), metrics=['accuracy'])
# 使用與前面類似的方法訓練模型。
model.fit(trainX, trainY, batch_size=128, epochs=20, validation_data=(testX, testY))
# 在測試數據上評測模型。
score = model.evaluate(testX, testY, batch_size=128)
print('Test loss: ', score[0])
print('Test accuracy: ', score[1])
運行結果:
說明,我改了之后是能跑的。。。
對了,如果有杠精問我,人家只是拋磚引玉,讓讀者舉一反三。。。那我沒什么好說的。。。
又花了一晚上跑完。。。
用原生態的Keras實現非順序模型,多輸入和多輸出模型。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: keras_inception3.py
@time: 2019/5/7 14:54
@desc: 用原生態的Keras實現非順序模型,多輸入和多輸出模型
"""
import keras
from tflearn.layers.core import fully_connected
from keras.datasets import mnist
from keras.layers import Input, Dense
from keras.models import Model
from keras import backend as K
# 類似前面的方式生成trainX、trainY、testX、testY
num_calsses = 10
img_rows, img_cols = 28, 28
# 通過Keras封裝好的API加載MNIST數據。其中trainX就是一個60000x28x28的數組,
# trainY是每一張圖片對應的數字。
(trainX, trainY), (testX, testY) = mnist.load_data()
trainX = trainX.reshape(len(trainX), -1)
testX = testX.reshape(len(testX), -1)
# 將圖像像素轉化為0到1之間的實數。
trainX = trainX.astype('float32')
testX = testX.astype('float32')
trainX /= 255.0
testX /= 255.0
# 將標准答案轉化為需要的格式(One-hot編碼)。
trainY = keras.utils.to_categorical(trainY, num_calsses)
testY = keras.utils.to_categorical(testY, num_calsses)
# 定義兩個輸入,一個輸入為原始的圖片信息,另一個輸入為正確答案。
input1 = Input(shape=(784, ), name='input1')
input2 = Input(shape=(10, ), name='input2')
# 定義一個只有一個隱藏節點的全連接網絡。
x = Dense(1, activation='relu')(input1)
# 定義只使用了一個隱藏節點的網絡結構的輸出層。
output1 = Dense(10, activation='softmax', name='output1')(x)
# 將一個隱藏節點的輸出和正確答案拼接在一起,這個將作為第二個輸出層的輸入。
y = keras.layers.concatenate([x, input2])
# 定義第二個輸出層。
output2 = Dense(10, activation='softmax', name='output2')(y)
# 定義一個有多個輸入和多個輸出的模型,這里只需要將所有的輸入和輸出給出即可。
model = Model(inputs=[input1, input2], outputs=[output1, output2])
# 定義損失函數、優化函數和評測方法。若多個輸出的損失函數相同,可以只指定一個損失函數。
# 如果多個輸出的損失函數不同,則可以通過一個列表或一個字典來指定每一個輸出的損失函數。
# 比如可以使用:loss = {'output1': 'binary_crossentropy', 'output2': 'binary_crossentropy'}
# 來為不同的輸出指定不同的損失函數。類似的,Keras也支持為不同輸出產生的損失指定權重,
# 這可以通過通過loss_weights參數來完成。在下面的定義中,輸出output1的權重為1,output2
# 的權重為0.1。所以這個模型會更加偏向於優化第一個輸出。
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(), loss_weights=[1, 0.1], metrics=['accuracy'])
# 模型訓練過程。因為有兩個輸入和輸出,所以這里提供的數據也需要有兩個輸入和兩個期待的正確
# 答案輸出。通過列表的方式提供數據時,Keras會假設數據給出的順序和定義Model類時輸入輸出
# 給出的順序是對應的。為了避免順序不一致導致的問題,這里更推薦使用字典的形式給出。
model.fit(
[trainX, trainY], [trainY, trainY],
batch_size=128,
epochs=20,
validation_data=([testX, testY], [testY, testY])
)
運行結果:
我們可以看出,由於輸出層1只使用了一個一維的隱藏節點,所以正確率很低,輸出層2雖然使用了正確答案最為輸入,但是損失函數中的權重較低,所以收斂速度較慢,准確率只有0.804。現在我們把權重設置相同,運行得到:
這樣輸出二經過了足夠的訓練,精度就提高了很多。
雖然通過返回值的方式已經可以實現大部分的神經網絡模型,然而Keras API還存在兩大問題。一是對訓練數據的處理流程支持的不太好;二十無法支持分布式訓練。為了解決這兩個問題,Keras提供了一種與原生態TensorFlow結合得更加緊密的方式。下面的代碼是:實現Keras與TensorFlow聯合起來解決MNIST問題。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: keras_test4.py
@time: 2019/5/7 15:45
@desc: 實現Keras與TensorFlow聯合起來解決MNIST問題。
"""
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist_data = input_data.read_data_sets('D:/Python3Space/BookStudy/book2/MNIST_data', one_hot=True)
# 通過TensorFlow中的placeholder定義輸入。類似的,Keras封裝的網絡層結構也可以支持使用
# 前面章節中介紹的輸入隊列。這樣可以有效避免一次性加載所有數據的問題。
x = tf.placeholder(tf.float32, shape=(None, 784))
y_ = tf.placeholder(tf.float32, shape=(None, 10))
# 直接使用TensorFlow中提供的Keras API定義網絡結構。
net = tf.keras.layers.Dense(500, activation='relu')(x)
y = tf.keras.layers.Dense(10, activation='softmax')(net)
# 定義損失函數和優化方法。注意這里可以混用Keras的API和原生態TensorFlow的API
loss = tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y_, y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(loss)
# 定義預測的正確率作為指標。
acc_value = tf.reduce_mean(tf.keras.metrics.categorical_accuracy(y_, y))
# 使用原生態TensorFlow的方式訓練模型。這樣可以有效地實現分布式。
with tf.Session() as sess:
tf.global_variables_initializer().run()
for i in range(10000):
xs, ys = mnist_data.train.next_batch(100)
_, loss_value = sess.run([train_step, loss], feed_dict={x: xs, y_: ys})
if i % 1000 == 0:
print("After %d training step(s), loss on training batch is %g." % (i, loss_value))
print(acc_value.eval(feed_dict={x: mnist_data.test.images,
y_: mnist_data.test.labels}))
運行結果:
通過和原生態TensorFlow更緊密地結合,可以使建模的靈活性進一步提高,但是同時也會損失一部分封裝帶來的易用性。所以在實際問題中,需要根據需求合理的選擇封裝的程度。
3. Estimator介紹
3.1 Estimator基本用法
基於MNIST數據集,通過Estimator實現全連接神經網絡。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: estimator_test1.py
@time: 2019/5/7 16:22
@desc: 基於MNIST數據集,通過Estimator實現全連接神經網絡。
"""
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 將TensorFlow日志信息輸出到屏幕
tf.logging.set_verbosity(tf.logging.INFO)
mnist = input_data.read_data_sets('D:/Python3Space/BookStudy/book2/MNIST_data', one_hot=True)
# 指定神經網絡的輸入層,所有這里指定的輸入都會拼接在一起作為整個神經網絡的輸入。
feature_columns = [tf.feature_column.numeric_column("image", shape=[784])]
# 通過TensorFlow提供的封裝好的Estimator定義神經網絡模型。feature_columns參數
# 給出了神經網絡輸入層需要用到的數據,hidden_units列表中給出了每一層
# 隱藏層的節點數。n_classes給出了總共類目的數量,optimizer給出了使用的優化函數。
# Estimator會將模型訓練過程中的loss變化以及一些其他指標保存到model_dir目錄下,
# 通過TensorFlow可以可視化這些指標的變化過程。並通過TensorBoard可視化監控指標結果。
estimator = tf.estimator.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[500],
n_classes=10,
optimizer=tf.train.AdamOptimizer(),
model_dir="./log"
)
# 定義數據輸入。這里x中需要給出所有的輸入數據。因為上面feature_columns只定義了一組
# 輸入,所以這里只需要制定一個就好。如果feature_columns中指定了多個,那么這里也需要
# 對每一個指定的輸入提供數據。y中需要提供每一個x對應的正確答案,這里要求分類的結果
# 是一個正整數。num_epochs指定了數據循環使用的輪數。比如在測試時可以將這個參數指定為1.
# batch_size指定了一個batch的大小。shuffle指定了是否需要對數據進行隨機打亂。
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"image": mnist.train.images},
y=mnist.train.labels.astype(np.int32),
num_epochs=None,
batch_size=128,
shuffle=True
)
# 訓練模型。注意這里沒有指定損失函數,通過DNNClassifier定義的模型會使用交叉熵作為損失函數。
estimator.train(input_fn=train_input_fn, steps=10000)
# 定義測試時的數據輸入。指定的形式和訓練時的數據輸入基本一致。
test_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"image": mnist.test.images},
y=mnist.test.labels.astype(np.int32),
num_epochs=1,
batch_size=128,
shuffle=False
)
# 通過evaluate評測訓練好的模型的效果。
accuracy_score = estimator.evaluate(input_fn=test_input_fn)["accuracy"]
print("\nTest accuracy: %g %%" % (accuracy_score*100))
運行可得:
使用下面的命令開啟tensorboard之旅:(我又要噴了,書里根本沒說怎么開啟tensorboard,我完全靠自行百度摸索的。。。)
tensorboard --logdir=""
引號里面填自己的log所在的地址。然后運行:
復制最下面的那個地址,在瀏覽器(我是谷歌瀏覽器)粘貼並轉到。
記住!是粘貼並轉到,不是ctrl+v,是右鍵,粘貼並轉到。
別問!問就是吃了好多虧。。。
反正我的電腦是粘貼並轉到之后,卡了一會兒,就出現了這個界面:
雖然跟書上的圖的布局不一樣,下面折疊的指標,展開也有圖就是了。。。
當然GRAPHS也是有的嘿嘿。。。
3.2 Estimator自定義模型
通過自定義的方式使用卷積神經網絡解決MNIST問題:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: estimator_test2.py
@time: 2019/5/9 12:31
@desc: 通過自定義的方式使用卷積神經網絡解決MNIST問題
"""
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
tf.logging.set_verbosity(tf.logging.INFO)
# 通過tf.layers來定義模型結構。這里可以使用原生態TensorFlow API或者任何
# TensorFlow的高層封裝。X給出了輸入層張量,is_training指明了是否為訓練。
# 該函數返回前向傳播的結果。
def lenet(x, is_training):
# 將輸入轉化為卷積層需要的形狀
x = tf.reshape(x, shape=[-1, 28, 28, 1])
net = tf.layers.conv2d(x, 32, 5, activation=tf.nn.relu)
net = tf.layers.max_pooling2d(net, 2, 2)
net = tf.layers.conv2d(net, 64, 3, activation=tf.nn.relu)
net = tf.layers.max_pooling2d(net, 2, 2)
net = tf.contrib.layers.flatten(net)
net = tf.layers.dense(net, 1024)
net = tf.layers.dropout(net, rate=0.4, training=is_training)
return tf.layers.dense(net, 10)
# 自定義Estimator中使用的模型,定義的函數有4個輸入,features給出了在輸入函數中
# 會提供的輸入層張量。注意這是一個字典,字典里的內容是通過tf.estimator.inputs.numpy_input_fn
# 中x參數的內容指定的。labels是正確答案,這個字段的內容是通過numpy_input_fn中y參數給出的。
# mode的取值有3中可能,分別對應Estimator類的train、evaluate和predict這3個函數。通過
# 這個參數可以判斷當前會否是訓練過程。最后params是一個字典,這個字典中可以給出模型相關的任何超參數
# (hyper-parameter)。比如這里將學習率放在params中。
def model_fn(features, labels, mode, params):
# 定義神經網絡的結構並通過輸入得到前向傳播的結果。
predict = lenet(features["image"], mode == tf.estimator.ModeKeys.TRAIN)
# 如果在預測模式,那么只需要將結果返回即可。
if mode == tf.estimator.ModeKeys.PREDICT:
# 使用EstimatorSpec傳遞返回值,並通過predictions參數指定返回的結果。
return tf.estimator.EstimatorSpec(
mode=mode,
predictions={"result": tf.argmax(predict, 1)}
)
# 定義損失函數
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=predict, labels=labels))
# 定義優化函數。
optimizer = tf.train.GradientDescentOptimizer(learning_rate=params["learning_rate"])
# 定義訓練過程。
train_op = optimizer.minimize(loss=loss, global_step=tf.train.get_global_step())
# 定義評測標准,在運行evaluate時會計算這里定義的所有評測標准。
eval_metric_ops = {
"my_metric": tf.metrics.accuracy(tf.argmax(predict, 1), labels)
}
# 返回模型訓練過程需要使用的損失函數、訓練過程和評測方法。
return tf.estimator.EstimatorSpec(
mode=mode,
loss=loss,
train_op=train_op,
eval_metric_ops=eval_metric_ops
)
mnist = input_data.read_data_sets("D:/Python3Space/BookStudy/book2/MNIST_data", one_hot=False)
# 通過自定義的方式生成Estimator類。這里需要提供模型定義的函數並通過params參數指定模型定義時使用的超參數。
model_params = {"learning_rate": 0.01}
estimator = tf.estimator.Estimator(model_fn=model_fn, params=model_params)
# 和前面的類似,訓練和評測模型。
train_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"image": mnist.train.images},
y=mnist.train.labels.astype(np.int32),
num_epochs=None,
batch_size=128,
shuffle=True
)
estimator.train(input_fn=train_input_fn, steps=30000)
test_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"image": mnist.test.images},
y=mnist.test.labels.astype(np.int32),
num_epochs=1,
batch_size=128,
shuffle=False
)
test_results = estimator.evaluate(input_fn=test_input_fn)
# 這里使用的my_metric中的內容就是model_fn中eval_metric_ops定義的評測指標。
accuracy_score = test_results["my_metric"]
print("\nTest accuracy: %g %%" % (accuracy_score*100))
# 使用訓練好的模型在新數據上預測結果。
predict_input_fn = tf.estimator.inputs.numpy_input_fn(
x={"image": mnist.test.images[:10]},
num_epochs=1,
shuffle=False
)
predictions = estimator.predict(input_fn=predict_input_fn)
for i, p in enumerate(predictions):
# 這里result就是tf.estimator.EstimatorSpec的參數predicitons中指定的內容。
# 因為這個內容是一個字典,所以Estimator可以很容易支持多輸出。
print("Prediction %s : %s" % (i + 1, p["result"]))
運行之后得到:
預測結果:
3.3 使用數據集(Dataset)作為Estimator輸入
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# coding=utf-8
"""
@author: Li Tian
@contact: 694317828@qq.com
@software: pycharm
@file: estimator_test3.py
@time: 2019/5/9 14:13
@desc: 通過Estimator和數據集相結合的方式完成整個數據讀取和模型訓練的過程。
"""
import tensorflow as tf
tf.logging.set_verbosity(tf.logging.INFO)
# Estimator的自定義輸入函數需要每一次被調用時可以得到一個batch的數據(包括所有的
# 輸入層數據和期待的正確答案標注),通過數據集可以很自然地實現這個過程。雖然Estimator
# 要求的自定義輸入函數不能有參數,但是通過python提供的lambda表達式可以快速將下面的
# 函數轉化為不帶參數的函數。
def my_input_fn(file_path, perform_shuffle=False, repeat_count=1):
# 定義解析csv文件中一行的方法。
def decode_csv(line):
# 將一行中的數據解析出來。注意iris數據中最后一列為正確答案,前面4列為特征。
parsed_line = tf.decode_csv(line, [[0.], [0.], [0.], [0.], [0]])
# Estimator的輸入函數要求特征是一個字典,所以這里返回的也需要是一個字典。
# 字典中key的定義需要和DNNclassifier中feature_columns的定義匹配。
return {"x": parsed_line[:-1]}, parsed_line[-1:]
# 使用數據集處理輸入數據。數據集的具體使用方法可以參考前面。
dataset = (tf.data.TextLineDataset(file_path).skip(1).map(decode_csv))
if perform_shuffle:
dataset = dataset.shuffle(buffer_size=256)
dataset = dataset.repeat(repeat_count)
dataset = dataset.batch(12)
iterator = dataset.make_one_shot_iterator()
# 通過定義的數據集得到一個batch的輸入數據。這就是整個自定義的輸入過程的返回結果。
batch_features, batch_labels = iterator.get_next()
# 如果是為預測過程提供輸入數據,那么batch_labels可以直接使用None。
return batch_features, batch_labels
# 與前面中類似地定義Estimator
feature_columns = [tf.feature_column.numeric_column("x", shape=[4])]
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[10, 10],
n_classes=3
)
# 使用lambda表達式將訓練相關的信息傳入自定義輸入數據處理函數並生成Estimator需要的輸入函數
classifier.train(input_fn=lambda: my_input_fn("./iris_data/iris_training (1).csv", True, 10))
# 使用lambda表達式將測試相關的信息傳入自定義輸入數據處理函數並生成Estimator需要的輸入函數。
# 通過lambda表達式的方式可以大大減少冗余代碼。
test_results = classifier.evaluate(input_fn=lambda: my_input_fn("./iris_data/iris_test (1).csv", False, 1))
print("\nTest accuracy: %g %%" % (test_results["accuracy"]*100))
運行之后,玄學報錯。。。
我佛了,查了一萬種方法,也解決不了玄學報錯。。。
然后,我換了個機器,macbook。。。就正常運行了。。。
頭就很疼。。。
4. 總結
要什么總結,不就是幾種常見的TensorFlow高層封裝嘛!包括TensorFlow-Slim、TFLearn、Keras和Estimator。反正就是結合起來,反正就是頭疼。
我的CSDN:https://blog.csdn.net/qq_21579045
我的博客園:https://www.cnblogs.com/lyjun/
我的Github:https://github.com/TinyHandsome
紙上得來終覺淺,絕知此事要躬行~
歡迎大家過來OB~
by 李英俊小朋友