训练循环神经网络(RNN)模型并用TensorFlow和Flask在生产中服务

训练循环神经网络(RNN)模型并用TensorFlow和Flask在生产中服务

虽然我们认识到ConvNET目前提供最先进的图像分类,但我们的目标是向您展示如何在生产中提供RNN模型。因此,我们决定使用MNIST数据集和LSTM作为RNN,因为该数据集是图像分类研究的事实上的标准,因此是众所周知且容易获得的。它足够小,您可以在几分钟内在台式机或笔记本电脑上完成本教程。此外,我们相信,一旦读者了解如何提供简单模型,他们就可以将这种做法扩展到更复杂的模型。

1.训练基于LSTM的图像分类模型

TensorFlow使训练RNN模型变得非常容易和直观。我们将在LSTM层的顶部使用线性激活层。为了便于导出,我们将介绍模型的输入和输出,这两者在推理过程中提供数据时都很有用。由于我们主要关注推理,我们将保持训练简单。

A.加载必要的库

首先加载Python必要的库。我们将使用来自TensorFlow的着名的MNIST数据集。

from __future__

import print_function

import os

import numpy as np

import tensorflow as tf

from tensorflow.contrib import rnn

tf.app.flags.DEFINE_integer(‘model_version’, 1, ‘version number of the model.’)

FLAGS = tf.app.flags.FLAGS

# Import MNIST data

from tensorflow.examples.tutorials.mnist import input_data

B .下载训练和测试模型所需的数据

将数据下载到临时位置,并将标签转换为one_hot向量。若要使用RNN对图像进行分类,请将每个图像行视为一个像素序列。因为MNIST的图像形状是28 28像素,所以每个样本都有28个序列,28个步骤。

mnist = input_data.read_data_sets(“/tmp/data/”, one_hot=True)

C、设置训练和网络参数

使用随机梯度下降(SGD)保持学习率低,并确保模型不会过度拟合训练数据。

learning_rate = 0.001

training_steps = 10000

batch_size = 128

display_step = 200

# Network Parameters

num_input = 28 # MNIST data input (img shape: 28*28)

timesteps = 28 # timesteps

num_hidden = 128 # hidden layer num of features

num_classes = 10 # MNIST total classes (0–9 digits)

D.定义变量

在TensorFlow中使用placeholder和feed_dict在训练、测试和推断过程中将数据输入到模型中是很常见的。命名输入Input_X,以便在导出模型和推断期间使用。

# Training Parameters

# tf Graph input

X = tf.placeholder(“float”, [None, timesteps, num_input],name =’Input_X’)

Y = tf.placeholder(“float”, [None, num_classes])

# Define weights

weights = {

‘out’: tf.Variable(tf.random_normal([num_hidden, num_classes]))

}

biases = {

‘out’: tf.Variable(tf.random_normal([num_classes]))

}

E.定义模型

要定义一个简单的基于lstm的RNN模型,需要准备数据形状以匹配模型的需求。接下来,使用BasicLSTMCell创建一个LSTM cell,该cell应用于输入;在一个名为rnn的范围内创建一个static_rnn cell;并设置auto_reuse = true以重用模块。

最后,应用线性激活法计算逻辑。在推理图中,使用这个线性激活作为输出层,并在输出推理过程中应用softmax。要使用这个线性激活层作为输出,请将其放入范围并为logit操作分配一个名称。

def RNN(x, weights, biases):

# Prepare data shape to match `rnn` function requirements

# Current data input shape: (batch_size, timesteps, n_input)

# Required shape: 'timesteps' tensors list of shape (batch_size, n_input)

x = tf.unstack(x, timesteps, 1)

# Define a lstm cell with tensorflow

lstm_cell = rnn.BasicLSTMCell(num_hidden, forget_bias=1.0)

# Get lstm cell output

outputs, _ = rnn.static_rnn(lstm_cell, x, dtype=tf.float32)

# Linear activation, using rnn inner loop last output

with tf.name_scope('output_layer'):

logit = tf.add(tf.matmul(outputs[-1], weights['out']) , biases['out'],name ='add')

return logit

获得logits后,应用softmax操作进行预测。作为模型中线性激活层的替代方法,使用softmax层作为输出层,以便在推理过程中提供将来使用的名称。

logits = RNN(X, weights, biases)

prediction = tf.nn.softmax(logits,name=’prediction’)

F.计算损失和优化

为了使用损失函数, softmax_cross_entropy_with_logits_v2将预测输出与实际标签进行比较,并使用优化器(例如,Adam,SGD,RMSprop)来最小化损失。计算模型的预测和准确性,以及训练期间的打印精度和损失统计。

# Define loss and optimizer

loss_op = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(

logits=logits, labels=Y))

optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)

train_op = optimizer.minimize(loss_op)

# Evaluate model

correct_pred = tf.equal(tf.argmax(prediction, 1), tf.argmax(Y, 1))

accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

G.训练模型

要训​​练模型并使其生产就绪,您必须首先初始化变量,包括权重和偏差。定义冻结图的输入和输出。从图中的名称获取输入和输出张量。将输入变量定义为 Input_X,将 output_layer定义为 output_layer / add ; 然后添加 :0到两者的结尾。

名称本身在图表中显示为操作; 要将这些名称用作张量,必须将0添加到名称的末尾(即操作)。

# Initialize the variables (i.e. assign their default value)

init = tf.global_variables_initializer()

output_tensor = tf.get_default_graph().get_tensor_by_name(“output_layer/add:0”)

input_tensor = tf.get_default_graph().get_tensor_by_name(“Input_X:0”)

最后,定义Saver并将模型保存为检查点,随后可以加载该检查点以进行重新训练和推理。生成的图形具有许多不是推理所必需的操作,并且保存的模型的大小很大,这会减慢推理速度。训练完成后,使用定义的输入和输出张量导出更小,更快的模型进行推理。

通过创建新会话开始训练。运行变量初始化程序后,根据步骤C中预定义的步数设置训练循环。迭代训练数据并逐批将其提取到模型中,以优化模型并最大限度地减少损失。

saver = tf.train.Saver()

with tf.Session() as sess:

# Run the initializer

sess.run(init)

for step in range(1, training_steps+1):

batch_x, batch_y = mnist.train.next_batch(batch_size)

# Reshape data to get 28 seq of 28 elements

batch_x = batch_x.reshape((batch_size, timesteps, num_input))

# Run optimization op (backprop)

sess.run(train_op, feed_dict={X: batch_x, Y: batch_y})

if step % display_step == 0 or step == 1:

# Calculate batch loss and accuracy

loss, acc = sess.run([loss_op, accuracy], feed_dict={X: batch_x,

Y: batch_y})

print("Step " + str(step) + ", Minibatch Loss= " + \

"{:.4f}".format(loss) + ", Training Accuracy= " + \

"{:.3f}".format(acc))

for op in tf.get_default_graph().get_operations():

if output_layer[0] in op.name:

print(op.name)

print("Optimization Finished!")

如前所述,变量display_step将打印来自训练的损失和准确度数据,以确保损失减少并且准确度增加(参见下面的示例)。这种计算损失的过程也称为模型的内部评估。

Step 1, Minibatch Loss= 2.5360, Training Accuracy= 0.055

Step 1000, Minibatch Loss= 1.5179, Training Accuracy= 0.555

Step 2000, Minibatch Loss= 1.3314, Training Accuracy= 0.602

Step 3000, Minibatch Loss= 1.0988, Training Accuracy= 0.641

Step 4000, Minibatch Loss= 1.0893, Training Accuracy= 0.664

Step 5000, Minibatch Loss= 0.8246, Training Accuracy= 0.734

Step 6000, Minibatch Loss= 0.5758, Training Accuracy= 0.820

Step 7000, Minibatch Loss= 0.5413, Training Accuracy= 0.852

Step 8000, Minibatch Loss= 0.6734, Training Accuracy= 0.734

Step 9000, Minibatch Loss= 0.5125, Training Accuracy= 0.836

Step 10000, Minibatch Loss= 0.3872, Training Accuracy= 0.875

一旦模型在CPU上训练了五分钟并完成了10,000步,损失就会显着降低,准确度接近88%,这足以达到我们的目的。虽然ConvNET可能会取得更好的效果,但我们的目标是创建一个简单的RNN模型并在生产中提供服务。

2.保存和评估模型

通常的做法是在训练期间在检查点保存模型以执行内部和外部评估。外部评估可以包括评估开发数据集(如果需要)和测试评估。完整的评估需要验证数据来优化超参数并测试数据以评估模型的准确性。由于我们希望保持简单,我们只会执行测试评估,这需要以下两个步骤:

A.保存模型

完成训练循环后,保存检查点以备将来再训练; tf.train.Saver()使保存变得简单,可以在一行代码中完成。

# save the model for retraining

saver.save(sess,’./model.ckpt’)

B.评估模型

训练完成后,选择测试数据的子集,执行预测,并确保测试精度接近训练精度。为此评估选择128个样品的批次大小。如果使用大型测试数据集,请考虑使用循环/迭代器。

# Calculate accuracy for 128 mnist test images

test_len = 128

test_data = mnist.test.images[:test_len].reshape((-1, timesteps, num_input))

test_label = mnist.test.labels[:test_len]

sess.run(prediction, feed_dict={X: test_data}))

print(“Testing Accuracy:”, \

sess.run(accuracy, feed_dict={X: test_data, Y: test_label}))

测试精度为0.90625,即90.6%,非常接近训练精度。因此,该模型不会过度拟合,但可以进一步训练到大约98%的精度,这非常接近通过最先进的ConvNet / ResNet模型实现的精度。

3.导出经过训练的模型以进行推理

虽然保存的检查点包含推理不需要的元数据,但出于多种原因导出TensorFlow模型非常重要。对于生产,您需要的只是模型定义和权重,它们被导出以最小化模型的大小并使推理更快。下面,我们将解释如何导出可在生产中提供的RNN模型。

首先,使用SavedModelBuilder导出经过训练的推理模型, 并创建一个目录(如果尚不存在)。可以在此处添加模型版本,但出于演示目的,请在保存另一个模型之前删除先前保存的模型。

其次,使用SavedModelBuilder API 为导出模型的输入和输出构建张量信息。然后,定义tensor_info_x和tensor_info_y protocol buffers。

# Export the model for prediction

export_base_path = ‘./exportmodel’

export_path = os.path.join(

tf.compat.as_bytes(export_base_path),

tf.compat.as_bytes(str(FLAGS.model_version)))

# Removing previously exported model

# shutil.rmtree(export_path)

builder = tf.saved_model.builder.SavedModelBuilder(export_path)

tensor_info_x = tf.saved_model.utils.build_tensor_info(input_tensor)

tensor_info_y = tf.saved_model.utils.build_tensor_info(output_tensor)

第三,定义签名,这对预测很有用。使用键值映射构建签名定义。命名输入的键x_input(即, the protocol buffer for Input_X)和tensor_info_x输出作为y_output(即, the protocol buffer for the logit tensor, tensor_info_y),然后使用METHOD_NAME作为推理方法。我们使用预定义的常量进行推理。

prediction_signature = (

tf.saved_model.signature_def_utils.build_signature_def(

inputs={‘x_input’: tensor_info_x},

outputs={‘y_output’: tensor_info_y},

method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))

第四,使用SavedModelBuilder.add_meta_graph_and_variables()向构建中添加一个metagraph和变量,如Input_X和logit,并添加以下参数:

  • sess:支撑训练模型的TensorFlow会话。
  • tags:用于保存metagraph的一组标记。在这种情况下,由于我们打算在服务中使用图形,我们可以使用预定义的SavedModel标记常量中的serve标记。
  • signature_def_map:为 tensorflow :: SignatureDef映射的用户提供的签名密钥 ,它被添加到metagraph。签名指定在运行推理时要导出的模型类型以及要绑定的输入和输出张量。

若要将模型保存为frozen graph,请使用以下Python代码:

builder.add_meta_graph_and_variables(

sess, [tf.saved_model.tag_constants.SERVING],

signature_def_map={

tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:

prediction_signature

},

)

builder.save()

4.在生产中服务模型

从导出模型目录导出模型。现在,您可以进行推理。推理可以通过几种方式完成。例如,我们可以:

A. 使用Python Flask API提供模型

Flask是一个用Python编写的微web框架,基于Werkzeug工具包和Jinja2模板引擎。使用Flask框架的应用包括Pinterest、LinkedIn和Flask本身的community webpage。

Flask因其易于集成的应用程序而受到Python社区的欢迎。它还有服务器端和客户端。服务器加载模型并等待客户端请求提供响应。

使用来自客户端的POST请求发送图像数据并获取响应中的logit。Flask不需要其他应用程序来发出POST请求并发回数据。

使用JSON进行数据交换; POST请求不支持NumPy阵列的交换。此格式允许您发送任何类型的数据,具体取决于您训练的模型。首先,将客户端上的图像从NumPy数组转换为List,以便将其作为JSON发送。在以这种格式接收图像后,服务器将获得List数组,将其转换为NumPy数组,并根据模型的要求对其进行重新整形。

输入准备就绪后,将数据提供给模型以获取logits并应用softmax以获得NumPy数组形式的预测。将其重新转换为JSON以将其发送回客户端。

i. Flask Server

训练循环神经网络(RNN)模型并用TensorFlow和Flask在生产中服务

通过获取默认签名定义密钥来创建会话。接下来,分配用于保存模型输入和输出的密钥,并将TensorFlow SavedModel API中的导出模型作为metagraph加载。提取签名以从会话中按名称提取输入和输出张量,并将图形的输入指定为x,将logit指定为y。现在,我们已准备好进行推理。

if __name__ == '__main__':

tf.app.flags.DEFINE_string('model_path','./savedmodel/1/',help='model Path')

tf.app.flags.DEFINE_string('host','0.0.0.0',help='server ip address')

tf.app.flags.DEFINE_integer('port',5000,help='server port')

FLAGS = tf.app.flags.FLAGS

sess=tf.Session()

signature_key = tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY

input_key = 'x_input'

output_key = 'y_output'

export_path = FLAGS.model_path

meta_graph_def = tf.saved_model.loader.load(

sess,

[tf.saved_model.tag_constants.SERVING],

export_path)

signature = meta_graph_def.signature_def

x_tensor_name = signature[signature_key].inputs[input_key].name

y_tensor_name = signature[signature_key].outputs[output_key].name

x = sess.graph.get_tensor_by_name(x_tensor_name)

y = sess.graph.get_tensor_by_name(y_tensor_name)

app.run(host=FLAGS.host,port=FLAGS.port)

ii. Flask Client

客户端只需加载数据,并在添加超时后向服务器发送POST请求。收到响应并计算准确度后,我们需要进行一些错误处理,Python代码如下所示:

我们可以看到预测精度与89%的测试精度大致相同。

B.使用TensorFlow服务API提供模型

TensorFlow服务需要一个服务器和一个客户端。幸运的是,可以使用一行代码来启动TensorFlow服务器,尽管与Flask一样,它需要使用JSON数据。由于TensorFlow使用gRPC进行通信,因此不需要在NumPy和List之间进行转换,这减少了步骤的数量,提高了服务速度。

设置服务器如下:

tensorflow_model_server — port=9000 — model_name=mnist — model_base_path=/tmp/mnist_model/

现在,设置客户端。首先,通过提供主机IP和端口地址来准备通信信道。接下来,创建一个存根,它是客户端的服务对象。在这里,我们创建一个PredictRequest对象来分配模型规范,它采用模型名称(model_name)和签名名称(signature_name)。这些是在导出模型期间定义的prediction_signature的关键。

在导出的模型中,使用x_input作为输入用于prediction_signature通过创建一个张量发送数据protobuf_使用make_tensor_proto。在生产系统中,重要的是在请求和异常处理以及日志记录中添加超时; Stub.Predict接受请求并指定10秒的超时。

如果有任何例外,则只需打印即可。否则,客户端将获得softmax概率并使用argmax将输出与金标签进行比较。

最后,可以打印精度。这是完整的Python代码:

训练循环神经网络(RNN)模型并用TensorFlow和Flask在生产中服务

训练循环神经网络(RNN)模型并用TensorFlow和Flask在生产中服务

TensorFlow服务提供类似的准确性,但它比Flask API快两倍。

结论

我们刚刚完成了从训练到推理创建RNN模型的步骤。可以更改RNN结构或使用RNN模型执行某些任务,例如语言建模或机器翻译。要创建可在生产中提供的模型,必须相应地更改输入和输出。

使用TensorFlow中的docker镜像来为生产中的模型提供服务是很常见的。


分享到:


相關文章: