TensorFlow之高层封装(TensorFlow-Slim、TFLearn、Keras、Estimator、tensorflow-thub)学习总结

说明:以下所有代码使用版本TensorFlow1.4.0或1.12.0版本

import tensorflow as tf
print(tf.__version__)
1.12.0
10.TensorFlow高层封装
TensorFlow的高层封装有很多

10.1 TensorFlow高层封装总览
TensorFlow高层封装主要有4个:

TensorFlow-Slim
TFLearn
Keras
Estimator。
TensorFlow-Slim
TensorFlow-Slim是Google官方给出的相对较早的TensorFlow高层封装
Google通过TensorFlow-Slim开源了一些已经训练好的图像分析的模型,所以目前在图像识别问题中TensorFlow-Slim扔被较多地使用。
TensorFlow-Slim最特别的一个地方是它对一些标准的神经网络进行了封装,比如VGG、Inception以及ResNet,而且google开源的训练好的图像分析模型基本是通过TensorFlow-Slim实现的。
TensorFlow-Slim实现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])
    
    # 定义第一层卷积层,深度为32,过滤器大小为5*5,使用全0填充
    net = slim.conv2d(inputs, 32, [5,5], padding='SAME', scope='layer1-conv')
    # 定义一个最大池化层,其过滤器大小为2*2,步长为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')
    
    # 使用flatten()将4维矩阵转为2维,这样可以方便后面的全连接层计算。
    # 通过封装好的函数,用户不再需要自己计算通过卷积层之后的矩阵的大小。
    net = slim.flatten(net, scope='flatten')
    
    # 定义全连接层,该全连接层有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')
    
    y = lenet5(x)
    
    # 定义损失函数和训练方法
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
        logits=y, labels=tf.argmax(y_, 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 {} training step(s), loss on training batch is {}'.format(
                i, loss_value))
                
def main(argv=None):
    mnist=input_data.read_data_sets('MNIST_data', one_hot=True)
    train(mnist)
    
main()
Please use tf.one_hot on tensors.
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
WARNING:tensorflow:From /Users/jliang/anaconda3/envs/py36/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:290: DataSet.__init__ (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use alternatives such as official/mnist/dataset.py from tensorflow/models.
After 0 training step(s), loss on training batch is 2.3039710521698
After 1000 training step(s), loss on training batch is 0.7978101372718811
...
After 8000 training step(s), loss on training batch is 0.295499712228775
After 9000 training step(s), loss on training batch is 0.24138087034225464
TFLearn
TFLearn是一个更加简洁的TensorFlow高层封装,可以更加容易地完成模型定义、模型训练以及模型评测的过程。
TFLearn没有集成在TensorFlow的安装包中,需要单独安装:pip install tflearn
与原生TensorFlow不同的地方在于,TFLearn不仅使神经网络定义更加简洁,还将模型训练的过程也进行了封装。
在定义神经网络的前向传播过程之后,TFLearn可以通过regression函数来指定损失函数和优化方法。
更方便的是,不仅TFLearn能很好地封装模型定义,tflearn.DNN也能很好地封装模型训练的过程,使用fit函数可以指定训练中使用的数据和训练轮数。
TFLearn官网:http://tflearn.org/
TFLearn实现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='MNIST_data', one_hot=True)
 
# 将图像数据reshape成卷积神经网络输出的格式
trainX = trainX.reshape([-1, 28, 28, 1])
testX = testX.reshape([-1, 28, 28, 1])
 
# 构建神经网络,这个过程和TensorFlow-Slim比较类似
net = input_data(shape=[None, 28, 28, 1], name='input')
 
# 通过TFLearn封装好的API定义一个深度为5,过滤器为5*5,激活函数为ReLU的卷积层
net = conv_2d(net, 32, 5, activation='relu')
# 定义一个2*2的最大池化层
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')
 
# 通过定义的网络结构训练模型,并在指定的验证集数据上验证模型效果。
model = tflearn.DNN(net, tensorboard_verbose=0)
 
model.fit(trainX, trainY, n_epoch=20, validation_set=([testX, testY]), 
         show_metric=True)
Training Step: 17199  | total loss: [1m[32m0.41563[0m[0m | time: 95.406s
| SGD | epoch: 020 | loss: 0.41563 - acc: 0.9683 -- iter: 54976/55000
Training Step: 17200  | total loss: [1m[32m0.37548[0m[0m | time: 101.037s
| SGD | epoch: 020 | loss: 0.37548 - acc: 0.9715 | val_loss: 0.02762 - val_acc: 0.9901 -- iter: 55000/55000
--
10.2 Keras介绍
Keras是目前使用最为广泛的深度学习工具之一,它的底层可以支持TensorFlow、MXNet、CNTK和Theano。
如今,Keras更是被直接引入了TensorFlow的核心代码库,称为TensorFlow官方提供的高层封装之一。
与TFLearn类似,Keras API也对模型定义、损失函数、训练过程等进行了封装,整个训练过程和TFLearn基本一致,可分为数据处理、模型定义和模型训练三部分。
使用原生keras API需要先安装:pip install keras
Keras实现LeNet-5网络
先定义一个Sequential类,然后再Sequential实例中通过add函数添加网络层。
Keras把卷积层、池化层、RNN结构(LSTM、GRU)、全连接层等常用的神经网络结构都做了封装,可以很方便地实现深层神经网络。
在网络结构定义好后,Sequential实例可以通过compile函数指定优化函数、损失函数以及训练过程中需要监控的指标。
Keras对优化函数、损失函数以及监控指标都有封装,同时也支持使用自定义的方式。
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_classes = 10
img_rows, img_cols = 28, 28
 
# 通过keras加载MNIST数据,其中trainX就是一个60000*28*28的数组,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(testX.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_classes)
testY = keras.utils.to_categorical(testY, num_classes)
 
# 使用Keras定义模型
model = Sequential()
# 一层深度为32, 过滤器大小为5*5的卷积层
model.add(Conv2D(32, kernel_size=(5,5), activation='relu', input_shape=input_shape))
# 过滤器大小2*2的最大池化层
model.add(MaxPooling2D(pool_size=(2,2)))
# 深度为64,过滤器大小为5*5的卷积层
model.add(Conv2D(64, kernel_size=(5,5), activation='relu'))
# 过滤器大小2*2的最大池化层
model.add(MaxPooling2D(pool_size=(2,2)))
 
# 将卷积层的输出拉直后作为下面全连接层的输入。
model.add(Flatten())
# 全连接层有500个节点
model.add(Dense(500, activation='relu'))
# 全连接层得到最后的输出
model.add(Dense(num_classes, activation='softmax'))
 
# 定义损失函数、优化函数和评测方法
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)
print('Test loss:{}'.format(score[0]))
print('Test accuracy:{}'.format(score[1]))
Train on 60000 samples, validate on 10000 samples
Epoch 1/20
60000/60000 [==============================] - 41s 691us/step - loss: 1.0176 - acc: 0.7375 - val_loss: 0.3000 - val_acc: 0.9148
...
Epoch 20/20
60000/60000 [==============================] - 40s 673us/step - loss: 0.0433 - acc: 0.9869 - val_loss: 0.0424 - val_acc: 0.9859
10000/10000 [==============================] - 2s 220us/step
Test loss:0.04243028581570834
Test accuracy:0.9859
Keras实现自然语言情感分类问题
Keras对于循环神经网络的支持也是非常出色的,循环神经网络体结构可以通过一句命令完成。


 

from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Embedding
from keras.layers import LSTM
from keras.datasets import imdb
 
# 最多使用的单词数
max_features = 20000
# 循环神经网络的截断长度
maxlen = 80 
batch_size = 32
 
# 价值数据并将单词转化为ID, max_features给出了最多使用的单词数。和自然语言模型类似,
# 会将出现频率较低的单词替换为统一的ID。通过keras封装的API会生成25000条训练数据和2500条测试数据,
# 每一条数据可以看出一段话,并且每段话都有一个好评或差评的标签。
(trainX, trainY), (testX, testY) = imdb.load_data(num_words=max_features)
print('train sequences len={}, shape={}'.format(len(trainX), trainX.shape))
print('test sequences len={}, shape={}'.format(len(testX), testX.shape))
 
# 在自然语言中,每一段话的长度是不一样的,但循环神经网络的循环长度是固定的,所以在这里需要先将所有
# 段落统一成固定长度。对于长度不够的段落,要使用默认值0来填充,对于超过长度的段落则直接忽略超过的部分。
trainX = sequence.pad_sequences(trainX, maxlen=maxlen)
testX = sequence.pad_sequences(testX, maxlen=maxlen)
print('train sequences len={}, shape={}'.format(len(trainX), trainX.shape))
print('test sequences len={}, shape={}'.format(len(testX), testX.shape))
print(trainX[0])
print(trainY[0])
 
# 在完成数据预处理之后构建模型
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_sequences参数设置为True
model.add(Dense(1, activation='sigmoid'))
 
# 与MNIST样例类似地指定损失函数、优化函数和评测指标
model.compile(loss='binary_crossentropy', 
             optimizer='adam', metrics=['accuracy'])
 
model.fit(trainX, trainY, batch_size=batch_size, epochs=10, 
          validation_data=(testX, testY))
 
# 在测试集上评测模型
score = model.evaluate(testX, testY, batch_size=batch_size)
print('Test loss:{}'.format(score[0]))
print('Test accuracy:{}'.format(score[1]))
train sequences len=25000, shape=(25000,)
test sequences len=25000, shape=(25000,)
train sequences len=25000, shape=(25000, 80)
test sequences len=25000, shape=(25000, 80)
[   15   256     4     2     7  3766     5   723    36    71    43   530
   476    26   400   317    46     7     4 12118  1029    13   104    88
     4   381    15   297    98    32  2071    56    26   141     6   194
  7486    18     4   226    22    21   134   476    26   480     5   144
    30  5535    18    51    36    28   224    92    25   104     4   226
    65    16    38  1334    88    12    16   283     5    16  4472   113
   103    32    15    16  5345    19   178    32]
1
Train on 25000 samples, validate on 25000 samples
Epoch 1/10
25000/25000 [==============================] - 98s 4ms/step - loss: 0.4568 - acc: 0.7831 - val_loss: 0.3860 - val_acc: 0.8339
...
Epoch 10/10
25000/25000 [==============================] - 100s 4ms/step - loss: 0.0276 - acc: 0.9912 - val_loss: 0.9365 - val_acc: 0.8154
25000/25000 [==============================] - 14s 574us/step
Test loss:0.9364762434792518
Test accuracy:0.81544
10.2.2 Keras高级用法
Keras中一个最重要的封装就是Sequential类,所有的神经网络模型定义和训练都是通过Sequential实例来实现的,但是它只支持顺序模型的定义。
类似Inception这样的模型结构,通过Sequential类就不容易直接实现了。
为了支持更加灵活的模型定义方法,keras支持以返回值的形式定义网络层结构。
Keras以返回值的形式定义网络层结构例子
import keras
from keras.datasets import mnist
from keras.layers import Input, Dense
from keras.models import Model
 
num_classes = 10
img_rows, img_cols = 28, 28
 
# 使用”Keras实现LeNet-5网络“中类似的方法生成trainX、trainY、testX、testY,唯一不同的是这
# 里只用了全连接层,所以不需要将输入整理成三维矩阵。
# 通过keras加载MNIST数据,其中trainX就是一个60000*28*28的数组,trainY是每张图片对应的数字
(trainX, trainY), (testX, testY) = mnist.load_data()
 
# 将图像像素转化为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_classes)
testY = keras.utils.to_categorical(testY, num_classes)
 
trainX = trainX.reshape(trainX.shape[0], img_rows*img_cols)
testX = testX.reshape(testX.shape[0], img_rows*img_cols)
 
############################################################################
 
# 定义输入,这里指定的维度不用考虑batch大小
inputs = Input(shape=(784,))
# 定义一个全连接层,该层有500隐藏节点,使用relu激活函数。
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=20, 
          validation_data=(testX, testY))
 
# 在测试集上评测模型
score = model.evaluate(testX, testY, batch_size=batch_size)
print('Test loss:{}'.format(score[0]))
print('Test accuracy:{}'.format(score[1]))
Train on 60000 samples, validate on 10000 samples
Epoch 1/20
60000/60000 [==============================] - 3s 45us/step - loss: 1.1015 - acc: 0.7564 - val_loss: 0.6032 - val_acc: 0.8669
...    
Epoch 20/20
60000/60000 [==============================] - 2s 41us/step - loss: 0.2116 - acc: 0.9419 - val_loss: 0.2058 - val_acc: 0.9419
10000/10000 [==============================] - 0s 34us/step
Test loss:0.20578643749803305
Test accuracy:0.9419
Keras实现Inception模型结构
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)
 
# 定义第三个分支。
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)
Keras非顺序模型,多个输入或者输出的模型例子
keras训练过程会显示每个输出层的loss和accuracy。
因为output1只使用个了一个维度为1的隐藏节点,所以正确率只有24.6%。
虽然输出层output2使用了正确答案作为输入,但是因为在损失函数中权重较低(只有0.1),所以它的收敛速度较慢。


 

import keras
from keras.datasets import mnist
from keras.layers import Input, Dense
from keras.models import Model
 
num_classes = 10
img_rows, img_cols = 28, 28
 
# 通过keras加载MNIST数据,其中trainX就是一个60000*28*28的数组,trainY是每张图片对应的数字
(trainX, trainY), (testX, testY) = mnist.load_data()
 
# 将图像像素转化为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_classes)
testY = keras.utils.to_categorical(testY, num_classes)
 
trainX = trainX.reshape(trainX.shape[0], img_rows*img_cols)
testX = testX.reshape(testX.shape[0], img_rows*img_cols)
 
############################################################################
 
# 定义两个输入,一个输入为原始的图片信息,另一个输入为正确答案
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_weight参数来完成。
model.compile(loss=keras.losses.categorical_crossentropy,
             optimizer=keras.optimizers.SGD(),
             loss_weights=[1, 0.1], metrics=['accuracy'])
 
# 模型训练过程。因为有两个输入和输出,所以这里提供的数据也需要有两个输入和答案。
# 通过列表方式提供数据时,keras会假设数据给出的顺序和定义model类时输如输出的顺序对应。
# 还可以使用字典方式给出输入输出数据,
# 如 model.fit({'input1': trainX, 'input2': trainY}, {'output1': 'trainY, 'output2': trainY})
model.fit([trainX, trainY], [trainY, trainY], batch_size=128, epochs=20, 
          validation_data=([testX, testY], [testY, testY]))
Train on 60000 samples, validate on 10000 samples
Epoch 1/20
60000/60000 [==============================] - 1s 17us/step - loss: 2.4621 - output1_loss: 2.2200 - output2_loss: 2.4207 - output1_acc: 0.1338 - output2_acc: 6.6667e-05 - val_loss: 2.3812 - val_output1_loss: 2.1426 - val_output2_loss: 2.3866 - val_output1_acc: 0.1659 - val_output2_acc: 1.0000e-03
...
Epoch 20/20
60000/60000 [==============================] - 1s 10us/step - loss: 1.9757 - output1_loss: 1.8315 - output2_loss: 1.4425 - output1_acc: 0.2476 - output2_acc: 0.9037 - val_loss: 1.9547 - val_output1_loss: 1.8129 - val_output2_loss: 1.4187 - val_output1_acc: 0.2446 - val_output2_acc: 0.9140

Keras返回值的方式定义的网络
虽然通过返回值的方式已经可以实现大部分的神经网络模型,然而Keras API还存在两个大问题。
原生态Keras API对训练数据的处理流程支持不太好,基本上需要一次性将数据全部加载到内存。
原生态Keras API无法支持分布式训练。
为了解决这两个问题,keras提供了一种与原生态tensorflow结合得更加紧密的方式。
Keras与原生态TensorFlow紧密结合的例子
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
 
mnist_data = input_data.read_data_sets('./MNIST_data', one_hot=True)
 
# 通过tensorflow中的placeholder定义输入。这样就可以避免了一次性加载所有数据的问题。
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 {} training steps, loss on training batch is {}'.format(i, loss_value))
            
    print(acc_value.eval(feed_dict={x: mnist_data.test.images, y_: mnist_data.test.labels}))
Extracting ./MNIST_data/train-images-idx3-ubyte.gz
Extracting ./MNIST_data/train-labels-idx1-ubyte.gz
Extracting ./MNIST_data/t10k-images-idx3-ubyte.gz
Extracting ./MNIST_data/t10k-labels-idx1-ubyte.gz
After 0 training steps, loss on training batch is 2.3463857173919678
After 1000 training steps, loss on training batch is 0.12192763388156891
...
After 9000 training steps, loss on training batch is 0.001580432173795998
0.9837
10.3 Estimator介绍
TensorFlow从1.3版本开始推出了官方支持的高层封装tf.estimator。
用户只需要关注模型的输入以及模型的结构,其他的工作都可以通过Estimator自动完成。
Estimator基本用法
tf.estimator.DNNClassifier:定义网络结构

feature_columns参数给出了神经网络输入层需要用到的数据,列表类型,如果是多输入,则有多个字段
DNNClassifier只能定义多层全连接层神经网络,所有参数都通过hidden_units参数给出,每个值代表一层全连接层的神经元数量
n_classes给出了总共类目的数据
optimizer给出了使用的优化函数
模型训练过程中的loss变化以及一些其他指标保存到model_dir目录下,通过TensorFlow可以可视化这些指标的变化过程。
tf.estimator.inputs.numpy_input_fn:定义输入数据

x中需要给出所有的输入数据,DNNClassifier的feature_colums定义的每个输入都通过'名字:输入数据'的形式在x中定义
y中需要提供每一个x对应的正确答案,这里邀请分类的结果是正整数。
num_epochs指定了数据循环使用的轮数。比如在测试时可以将这个参数指定为1。
batch_size指定一个batch的大小。
shuffle指定是否需要对数据进行随机打乱。
estimator.train:训练模型

input_fn为tf.estimator.inputs.numpy_input_fn格式的输入数据
steps为迭代的轮数
estimator.evaluate:评测训练好的模型效果

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('MNIST_data', one_hot=False)
 
# 指定神经网络输入层
feature_columns = [tf.feature_column.numeric_column('image', shape=[784])]
 
# feature_columns参数给出了神经网络输入层需要用到的数据,hidden_units参数给出了神经网络的结构。
# DNNClassifier只能定义多层全连接层神经网络,而hidden_units列表中给出了每一层隐藏层的结点个数。
# n_classes给出了总共类目的数据,optimizer给出了使用的优化函数。
# Estimator会将模型训练过程中的loss变化以及一些其他指标保存到model_dir目录下,
# 通过TensorFlow可以可视化这些指标的变化过程。
estimator = tf.estimator.DNNClassifier(
    feature_columns=feature_columns,
    hidden_units=[500],
    n_classes=10,
    optimizer=tf.train.AdamOptimizer(),
    model_dir='log'
)
 
# 定义输入数据。
# x中需要给出所有的输入数据。
# 因为上面feature_colums只定义了一组输入,所以这里只需要指定一个就好。如果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
)
 
# 通过evalute评测训练好的模型效果
accuracy_score = estimator.evaluate(input_fn=test_input_fn)['accuracy']
print('Test accuracy: {}'.format(accuracy_score*100))
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
INFO:tensorflow:Using default config.
INFO:tensorflow:Using config: {'_model_dir': 'log', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_service': None, '_cluster_spec': , '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from log/model.ckpt-10000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 10000 into log/model.ckpt.
INFO:tensorflow:loss = 0.024581486, step = 10001
INFO:tensorflow:global_step/sec: 216.068
INFO:tensorflow:loss = 0.010451173, step = 10101 (0.465 sec)
INFO:tensorflow:global_step/sec: 255.163
...
INFO:tensorflow:loss = 0.0030434667, step = 19901 (0.402 sec)
INFO:tensorflow:Saving checkpoints for 20000 into log/model.ckpt.
INFO:tensorflow:Loss for final step: 0.0012465222.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2019-03-09-06:26:06
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from log/model.ckpt-20000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Finished evaluation at 2019-03-09-06:26:07
INFO:tensorflow:Saving dict for global step 20000: accuracy = 0.9823, average_loss = 0.092365846, global_step = 20000, loss = 11.691879
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 20000: log/model.ckpt-20000
Test accuracy: 98.22999835014343
使用Estimator自定义的模型结构实现卷积神经网络例子
预先定义好的Estimator功能有限,比如目前无法很好地实现卷积神经网络或者循环神经网络,也没有办法支持自定义的损失函数。
若使用预先定义好的模型,处理不能领会选择模型的结构,模型使用的损失函数和每一层使用的激活函数等都是预先定义好的。

使用以下函数构建神经网络结构

tf.reshape
tf.layers.conv2d
tf.layers.max_pooling2d
tf.contrib.layers.flatten
tf.layers.dense
tf.layers.dropout:dropout层需要指明当前是否在训练,因为评测/预测时dropout不产生作用
使用TensorFlow原生接口定义损失函数、优化函数、训练过程、评测标准

tf.reduce_mean:定义损失函数
tf.train.GradientDescentOptimizer:优化函数
optimizer.minimize:训练过程
tf.metrics.accuracy:评测标准
 
import numpy as np
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个函数。通过这个参数
# 可以判断当前是否是训练过程。最后paras是一个字典,这个字典中可以给出模型相关的任何超参数(hyper-parameter)
# 比如这里将学习率放在param中。
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('MNIST_data', one_hot=False)
 
# 通过自定义的方式生成Estimator类,这里需要提供模型定义的函数并通过paras参数指定模型定义时的超参数
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(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
)
test_results = estimator.evaluate(test_input_fn)
 
# 使用my_metric中的内容就是model_fn中eval_metric_ops定义的评测指标。
accuracy_score = test_results['my_metric']
print('Test accuracy:{}'.format(accuracy_score))
 
# 使用训练好的模型在新数据上预测结果
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):
    print('Prediction {}: {}'.format(i+1, p['result']))
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
INFO:tensorflow:Using default config.
WARNING:tensorflow:Using temporary folder as model directory: /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpjxfrlzbd
INFO:tensorflow:Using config: {'_model_dir': '/var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpjxfrlzbd', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_service': None, '_cluster_spec': , '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 0 into /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpjxfrlzbd/model.ckpt.
INFO:tensorflow:loss = 2.3190107, step = 1
INFO:tensorflow:global_step/sec: 11.7019
INFO:tensorflow:loss = 1.5502594, step = 101 (8.547 sec)
...
INFO:tensorflow:loss = 0.0700361, step = 9901 (8.190 sec)
INFO:tensorflow:Saving checkpoints for 10000 into /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpjxfrlzbd/model.ckpt.
INFO:tensorflow:Loss for final step: 0.034948707.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2019-03-09-08:17:47
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpjxfrlzbd/model.ckpt-10000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Finished evaluation at 2019-03-09-08:17:50
INFO:tensorflow:Saving dict for global step 10000: global_step = 10000, loss = 0.043541424, my_metric = 0.986
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 10000: /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpjxfrlzbd/model.ckpt-10000
Test accuracy:0.9860000014305115
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpjxfrlzbd/model.ckpt-10000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
Prediction 1: 7
Prediction 2: 2
Prediction 3: 1
Prediction 4: 0
Prediction 5: 4
Prediction 6: 1
Prediction 7: 4
Prediction 8: 9
Prediction 9: 5
Prediction 10: 9
10.3.3 使用数据集(Dataset)作为Estimator输入
Estimator可以非常好地和数据集集合,这样就能够很容易地支持海量数据读入或者复杂的数据预处理流程。

# 1. 训练自定义模型。
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:]
 
    # 使用数据集处理输入数据。数据集的具体使用方法可以参考第7章。
    # 旧版本实现:1.4.0
    # dataset = (tf.contrib.data.TextLineDataset(file_path).skip(1).map(decode_csv)) 
    # 新版本实现:1.12.0
    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(32) 
    iterator = dataset.make_one_shot_iterator()
    # 通过定义的数据集得到一个batch的输入数据。这个就是整个自定义的输入过程的返回结果。
    batch_features, batch_labels = iterator.get_next()
    # 如果是为预测过程提供输入数据,那么batch_labels可以直接使用None
    return batch_features, batch_labels
 
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)
 
# 使用labmda表达式将训练相关信息传入自定义输入数据处理函数并生产Estimator需要的输入函数。
classifier.train(
    input_fn=lambda: my_input_fn("./data/iris_training.csv", True, 100))
 
# 2. 测试模型。
# 使用lambda表达式将测试相关信息传入自定义输入数据处理函数并生产Estimator需要的输入函数。
test_results = classifier.evaluate(
    input_fn=lambda: my_input_fn("./data/iris_test.csv", False, 1))
print("\nTest accuracy: %g %%" % (test_results["accuracy"]*100))
 
INFO:tensorflow:Using default config.
WARNING:tensorflow:Using temporary folder as model directory: /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpcpnmixda
INFO:tensorflow:Using config: {'_model_dir': '/var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpcpnmixda', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_service': None, '_cluster_spec': , '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Saving checkpoints for 0 into /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpcpnmixda/model.ckpt.
INFO:tensorflow:loss = 41.302177, step = 1
INFO:tensorflow:global_step/sec: 341.029
INFO:tensorflow:loss = 15.995764, step = 101 (0.295 sec)
INFO:tensorflow:global_step/sec: 462.638
INFO:tensorflow:loss = 9.882986, step = 201 (0.216 sec)
INFO:tensorflow:global_step/sec: 453.719
INFO:tensorflow:loss = 6.136808, step = 301 (0.220 sec)
INFO:tensorflow:Saving checkpoints for 375 into /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpcpnmixda/model.ckpt.
INFO:tensorflow:Loss for final step: 5.1995726.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2019-03-09-08:01:59
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpcpnmixda/model.ckpt-375
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Finished evaluation at 2019-03-09-08:01:59
INFO:tensorflow:Saving dict for global step 375: accuracy = 0.96666664, average_loss = 0.17618403, global_step = 375, loss = 5.285521
INFO:tensorflow:Saving 'checkpoint_path' summary for global step 375: /var/folders/z8/4pqbb_410b3frmdyc62zdzsc0000gn/T/tmpcpnmixda/model.ckpt-375
 
Test accuracy: 96.6667 %
 

 

今年年初,伴随着 tensorflow 更新到 1.7.0 版本,Google 发布了 tensorflow hub。tensorflow hub 的主要目标是为模型提供一种简便的封装方式,同时可以简便地复用已封装的模型,可以说 tf hub 是为迁移学习而生的。

熟悉自然语言处理的同学都知道大部分 nlp 模型的底层都是 word2vec 词向量作为一个词的特征,当然近几年越来越多的模型会构建于语言模型之上,例如用 ELMo 代替词向量。其实无论哪种方式,downstream 的任务都建立在这些预训练好的向量之上,downstream 的任务与底层 embedding 的训练是高度解耦的。所以完全可以有专门的团队负责底层 embedding 的优化与开发,让后将它们用 tf hub 封装成 module 供下游应用团队使用,这些 module 对于使用人员就是黑盒子,他们无需关心 module 的实现细节。
在图像领域也是一样的,通常一些 downstream 的任务都会建立在一些经典的模型(vgg, resnet, mobilenet 等)之上,它们会利用这些模型预训练好的权重及结构作为特征提取器。

这里结合 nlp 中的 embedding 的封装和使用介绍一下 tensorflow hub 的细节,安装方式看 github。主要参考了官网和 github:
https://tensorflow.google.cn/hub/
https://github.com/tensorflow/hub

我们先看看怎么使用一个别人为我们封装好的模型:

 

hub_module = hub.Module(self.get_temp_dir())
tokens = tf.constant(["cat", "lizard", "dog"])
embeddings = hub_module(tokens)
with tf.Session() as session:
    session.run(tf.tables_initializer())
    session.run(tf.global_variables_initializer())
    self.assertAllClose(
        session.run(embeddings),
        [[1.11, 2.56, 3.45], [0.0, 0.0, 0.0], [1.0, 2.0, 3.0]])

代码的第一行表示加载一个封装好的 tensorflow hub 模型,参数可以是模型的路径也可以是一个保存有模型的 http 地址。
第二行创建了一个包含三个字符串的张量。
第三行调用了刚刚创建的 hub 模型,字符串张量作为模型的输入,embeddings就是模型的输出了。
调用 session.run 就能得到具体的 embeddings 输出值。

我第一次见到这个 demo 的时候惊喜之处在于,我们往常使用 embedding 时都需要将舒服的单词转换成相应的 id,将 id 作为输入查询相应的 embedding。难道这里把转换操作也封装进 hub module 里的?为了满足好奇心我们可以看一下如何封装这样一个 module。

hub module 封装:

 

def module_fn():
    """Spec function for a token embedding module."""
    tokens = tf.placeholder(shape=[None], dtype=tf.string, name="tokens")

    embeddings_var = tf.get_variable(
        initializer=tf.zeros([vocab_size + num_oov_buckets, embeddings_dim]),
        name=EMBEDDINGS_VAR_NAME,
        dtype=tf.float32)

    lookup_table = tf.contrib.lookup.index_table_from_file(
        vocabulary_file=vocabulary_file,
        num_oov_buckets=num_oov_buckets,
    )
    ids = lookup_table.lookup(tokens)
    combined_embedding = tf.nn.embedding_lookup(params=embeddings_var, ids=ids)
    hub.add_signature("default", {"tokens": tokens},
                      {"default": combined_embedding})

spec = hub.create_module_spec(module_fn)
with tf.Graph().as_default():
      m = hub.Module(spec)
      p_embeddings = tf.placeholder(tf.float32)
      load_embeddings = tf.assign(m.variable_map[EMBEDDINGS_VAR_NAME],
                                  p_embeddings)

      with tf.Session() as sess:
        sess.run([load_embeddings], feed_dict={p_embeddings: embeddings})
        m.export(export_path, sess)

根据上面代码,创建 hub module 的流程如下:
1、调用 hub.create_module_spec 创建一个 spec,函数的参数是 module 的计算图创建函数
2、调用 hub.Module 创建一个 module 对象,参数是上一步创建的 spec
3、在 session 中训练模型,这个 demo 里面没有训练,而是直接利用 tf 的赋值操作将一个 numpy 矩阵赋值给了模型的参数
4、调用 export 函数将当前的 session 保存到某个路径中

在 module_fn 函数的最后调用了 hub.add_signature,第一个参数是创建的这个 hub module 的名称,第二个参数是 module 的输入,它是一个字典,支持多个输入,第三个参数是 module 能提供的输出,同样也是字典,支持输出多个数据。
可以看到,module 的输入定义为一个 字符创类型的 placeholder,然后利用 index_table_from_file 创建了一个 lookup_table,这个 table 就可以将字符串转化为相应的 id,这里就解答了之前的好奇。

总结

第一次看到 tensorflow hub 就觉得很优雅,以前做 nlp 的工作会花大量的时间在准备数据上,而利用 tf hub 以后所有的任务都可以使用统一的 embedding module,并且可以直接将字符串作为输入,不用再手动转换。
hub module 在使用时还能设定为参数可训练或者参数不可训练,这样对于不同的任务就能有更灵活的选择。对于一些训练样本较少的情况,可以冻结底层 module 的参数,做完全的迁移学习。
首次尝试 tensorflow hub 还是相当欣喜的,以后也会尽量使用。

 

TensorFlow之高层封装(TensorFlow-Slim、TFLearn、Keras、Estimator、tensorflow-thub)学习总结_第1张图片



 

你可能感兴趣的:(Tensenflow)