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

相关文章
相关标签/搜索