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

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

0. 寫在前面

參考書git

《TensorFlow:實戰Google深度學習框架》(第2版)github

劃重點編程

==從今天開始(20190505-1521),個人博客都用Markdown語法來編寫啦,也不知道之後的本身會不會被人所知,會不會有大佬來看過去的我,給我挖墳呢。想一想就有點期待呢!但願本身還能更加努力!更加優秀吧!==api

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

相關文章
相關標籤/搜索