TensorFlow高層封裝:從入門到噴這本書


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

img

我哭了!找了我半天錯誤,才發現少寫了一句。

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呢?

我也沒看懂,非要騷一下吧。。。


好了正確的運行結果出來了:

img

如果我們把剛才說的那句代碼改為:

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

試試看?

哇哦~正常運行了有沒有!!!

img

所以呢?所以為什么這里要非要用有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。。。封裝萬歲!!!(對我這種菜雞而言,不要跟我談底層,我!不!夠!格!)

運行結果:

img

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

運行之后(跑了我一夜呀我滴媽。。。):

img

下面是用原生態的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])

睡了個午覺就跑完啦:

img

img

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

修改之后運行可以得到:

img


通過這樣的方式,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/

img

是不是有種似曾相識的感覺。。。

踏馬的根本就沒有想着去實現好嗎?

我也是醉了的,我就問一句,不是一直在用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])

運行結果:

img

說明,我改了之后是能跑的。。。

對了,如果有杠精問我,人家只是拋磚引玉,讓讀者舉一反三。。。那我沒什么好說的。。。

又花了一晚上跑完。。。

img


用原生態的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])
)

運行結果:

img

我們可以看出,由於輸出層1只使用了一個一維的隱藏節點,所以正確率很低,輸出層2雖然使用了正確答案最為輸入,但是損失函數中的權重較低,所以收斂速度較慢,准確率只有0.804。現在我們把權重設置相同,運行得到:

img

這樣輸出二經過了足夠的訓練,精度就提高了很多。


雖然通過返回值的方式已經可以實現大部分的神經網絡模型,然而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}))

運行結果:

img

通過和原生態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))

運行可得:

img

使用下面的命令開啟tensorboard之旅:(我又要噴了,書里根本沒說怎么開啟tensorboard,我完全靠自行百度摸索的。。。)

tensorboard --logdir=""

引號里面填自己的log所在的地址。然后運行:

img

復制最下面的那個地址,在瀏覽器(我是谷歌瀏覽器)粘貼並轉到。

記住!是粘貼並轉到,不是ctrl+v,是右鍵,粘貼並轉到

別問!問就是吃了好多虧。。。

反正我的電腦是粘貼並轉到之后,卡了一會兒,就出現了這個界面:

img

雖然跟書上的圖的布局不一樣,下面折疊的指標,展開也有圖就是了。。。

當然GRAPHS也是有的嘿嘿。。。

img

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"]))

運行之后得到:

img

預測結果:

img

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

運行之后,玄學報錯。。。

img

我佛了,查了一萬種方法,也解決不了玄學報錯。。。

然后,我換了個機器,macbook。。。就正常運行了。。。

img

頭就很疼。。。

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 李英俊小朋友


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM