【问题标题】:TensorFlow InferenceTensorFlow 推理
【发布时间】:2017-04-30 16:25:38
【问题描述】:

我一直在研究这个问题。我找到了很多文章;但没有一个真正将张量流推理显示为简单的推理。它总是“使用服务引擎”或使用预编码/定义的图表。

问题是:我有一台设备偶尔会检查更新的模型。然后它需要加载该模型并通过模型运行输入预测。

在 keras 中,这很简单:构建模型;训练模型并调用 model.predict()。在 scikit-learn 中也是一样的。

我可以抓取一个新模型并加载它;我可以打印出所有的重量;但是我到底要如何对它进行推理呢?

加载模型和打印权重的代码:

    with tf.Session() as sess:
        new_saver = tf.train.import_meta_graph(MODEL_PATH + '.meta', clear_devices=True)
        new_saver.restore(sess, MODEL_PATH)
        for var in tf.trainable_variables():
            print(sess.run(var))

我打印了我所有的收藏,我有: ['queue_runners', 'variables', 'losses', 'summaries', 'train_op', 'cond_context', 'trainable_variables']

我尝试使用sess.run(train_op);然而,这才刚刚开始完整的培训课程;这不是我想做的。我只想对我提供的一组不同的输入进行推理,这些输入不是 TF 记录。

再详细一点:

设备可以使用C++或Python;只要我能生成一个.exe。如果我想为系统提供信息,我可以设置一个提要字典。我用TFRecords 训练过;但在生产中我不会使用TFRecords;它是一个实时/接近实时的系统。

感谢您的任何意见。我将示例代码发布到此 repo:https://github.com/drcrook1/CIFAR10/TensorFlow,它会执行所有训练和示例推理。

非常感谢任何提示!

------------编辑----- 我将模型重建如下:

def inference(images):
    '''
    Portion of the compute graph that takes an input and converts it into a Y output
    '''
    with tf.variable_scope('Conv1') as scope:
        C_1_1 = ld.cnn_layer(images, (5, 5, 3, 32), (1, 1, 1, 1), scope, name_postfix='1')
        C_1_2 = ld.cnn_layer(C_1_1, (5, 5, 32, 32), (1, 1, 1, 1), scope, name_postfix='2')
        P_1 = ld.pool_layer(C_1_2, (1, 2, 2, 1), (1, 2, 2, 1), scope)
    with tf.variable_scope('Dense1') as scope:
        P_1 = tf.reshape(C_1_2, (CONSTANTS.BATCH_SIZE, -1))
        dim = P_1.get_shape()[1].value
        D_1 = ld.mlp_layer(P_1, dim, NUM_DENSE_NEURONS, scope, act_func=tf.nn.relu)
    with tf.variable_scope('Dense2') as scope:
        D_2 = ld.mlp_layer(D_1, NUM_DENSE_NEURONS, CONSTANTS.NUM_CLASSES, scope)
    H = tf.nn.softmax(D_2, name='prediction')
    return H

请注意,我将名称 'prediction' 添加到 TF 操作中,以便稍后检索它。

在训练时,我使用tfrecords 的输入管道和输入队列。

GRAPH = tf.Graph()
with GRAPH.as_default():
    examples, labels = Inputs.read_inputs(CONSTANTS.RecordPaths,
                                          batch_size=CONSTANTS.BATCH_SIZE,
                                          img_shape=CONSTANTS.IMAGE_SHAPE,
                                          num_threads=CONSTANTS.INPUT_PIPELINE_THREADS)
    examples = tf.reshape(examples, [CONSTANTS.BATCH_SIZE, CONSTANTS.IMAGE_SHAPE[0],
                                     CONSTANTS.IMAGE_SHAPE[1], CONSTANTS.IMAGE_SHAPE[2]])
    logits = Vgg3CIFAR10.inference(examples)
    loss = Vgg3CIFAR10.loss(logits, labels)
    OPTIMIZER = tf.train.AdamOptimizer(CONSTANTS.LEARNING_RATE)

我试图在图中的加载操作上使用feed_dict;但是现在它只是简单地挂起......

MODEL_PATH = 'models/' + CONSTANTS.MODEL_NAME + '.model'

images = tf.placeholder(tf.float32, shape=(1, 32, 32, 3))

def run_inference():
    '''Runs inference against a loaded model'''
    with tf.Session() as sess:
        #sess.run(tf.global_variables_initializer())
        new_saver = tf.train.import_meta_graph(MODEL_PATH + '.meta', clear_devices=True)
        new_saver.restore(sess, MODEL_PATH)
        pred = tf.get_default_graph().get_operation_by_name('prediction')
        rand = np.random.rand(1, 32, 32, 3)
        print(rand)
        print(pred)
        print(sess.run(pred, feed_dict={images: rand}))
        print('done')

run_inference()

我认为这不起作用,因为原始网络是使用 TFRecords 训练的。在样本 CIFAR 数据集中,数据很小;我们的真实数据集非常庞大,我认为 TFRecords 是训练网络的默认最佳实践。从生产的角度来看,feed_dict 非常完美;我们可以启动一些线程并从我们的输入系统中填充那个东西。

所以我猜我有一个经过训练的网络,我可以得到预测操作;但是我如何告诉它停止使用输入队列并开始使用feed_dict?请记住,从生产的角度来看,我无法获得科学家们为制造它所做的任何事情。他们做他们的事;我们使用任何商定的标准将其投入生产。

-------输入操作--------

tf.Operation 'input/input_producer/Const' type=Const, tf.Operation 'input/input_producer/Size' type=Const, tf.Operation 'input/input_producer/Greater/y' type=Const, tf.Operation 'input/input_producer/Greater' type=Greater, tf.Operation 'input/input_producer/Assert/Const' type=Const, tf.Operation 'input/input_producer/Assert/Assert/data_0' type=Const, tf.Operation 'input /input_producer/Assert/Assert' type=Assert, tf.Operation 'input/input_producer/Identity' type=Identity, tf.Operation 'input/input_producer/RandomShuffle' type=RandomShuffle, tf.Operation 'input/input_producer' type=FIFOQueueV2 , tf.Operation 'input/input_producer/input_producer_EnqueueMany' type=QueueEnqueueManyV2, tf.Operation 'input/input_producer/input_producer_Close' type=QueueCloseV2, tf.Operation 'input/input_producer/input_producer_Close_1' type=QueueCloseV2, tf.Operation 'input/input_producer /input_producer_Size' type=QueueSizeV2, tf.Operation 'input/input_producer/Cast' type=Cast, tf.Operation 'input/input_pro ducer/mul/y' type=Const, tf.Operation 'input/input_producer/mul' type=Mul, tf.Operation 'input/input_producer/fraction_of_32_full/tags' type=Const, tf.Operation 'input/input_producer/fraction_of_32_full' type=ScalarSummary, tf.Operation 'input/TFRecordReaderV2' type=TFRecordReaderV2, tf.Operation 'input/ReaderReadV2' type=ReaderReadV2,

-----结束输入操作-----

----更新 3----

我相信我需要做的是终止使用 TF Records 训练的图形的输入部分,并将第一层的输入重新连接到新的输入。有点像做手术;但如果我使用 TFRecords 进行训练,就像听起来一样疯狂,这是我能找到的唯一推理方法……

全图:

要杀死的部分:

所以我认为问题变成了:如何杀死图表的输入部分并将其替换为feed_dict

对此的跟进是:这真的是正确的方法吗?这看起来很疯狂。

----结束更新 3----

---链接到检查点文件---

https://drcdata.blob.core.windows.net/checkpoints/CIFAR_10_VGG3_50neuron_1pool_1e-3lr_adam.model.zip?st=2017-05-01T21%3A56%3A00Z&se=2020-05-02T21%3A56%3A00Z&sp=rl&sv=2015-12-11&sr=b&sig=oBCGxlOusB4NOEKnSnD%2FTlRYa5NKNIwAX1IyuZXAr9o%3D

--结束检查点文件的链接---

-----更新 4 -----

我屈服了,只是试了一下“正常”的推理方式,假设我可以让科学家们简单地腌制他们的模型,然后我们就可以抓住模型腌制;解压它,然后对其进行推理。所以为了测试我尝试了正常的方法,假设我们已经解压了它......它也不值得一个豆子......

import tensorflow as tf
import CONSTANTS
import Vgg3CIFAR10
import numpy as np
from scipy import misc
import time

MODEL_PATH = 'models/' + CONSTANTS.MODEL_NAME + '.model'
imgs_bsdir = 'C:/data/cifar_10/train/'

images = tf.placeholder(tf.float32, shape=(1, 32, 32, 3))

logits = Vgg3CIFAR10.inference(images)

def run_inference():
'''Runs inference against a loaded model'''
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        new_saver = tf.train.import_meta_graph(MODEL_PATH + '.meta')#, import_scope='1', input_map={'input:0': images})
        new_saver.restore(sess, MODEL_PATH)
        pred = tf.get_default_graph().get_operation_by_name('prediction')
        enq = sess.graph.get_operation_by_name(enqueue_op)
        #tf.train.start_queue_runners(sess)
        print(rand)
        print(pred)
        print(enq)
        for i in range(1, 25):
            img = misc.imread(imgs_bsdir + str(i) + '.png').astype(np.float32) / 255.0
            img = img.reshape(1, 32, 32, 3)
            print(sess.run(logits, feed_dict={images : img}))
            time.sleep(3)
        print('done')

run_inference()

Tensorflow 最终使用来自加载模型的推理函数构建了一个新图;然后它将其他图表中的所有其他内容附加到它的末尾。因此,当我填充 feed_dict 期望得到推论时;我只是得到一堆随机垃圾,好像是第一次通过网络......

再次;这看起来很疯狂;我真的需要编写自己的框架来序列化和反序列化随机网络吗?这必须在之前完成......

-----更新 4 -----

再次;谢谢!

【问题讨论】:

    标签: python c++ tensorflow


    【解决方案1】:

    好吧,这花了太多时间来弄清楚;所以这是世界其他地方的答案。

    快速提醒:我需要保留一个可以动态加载和推断的模型,而无需了解其工作原理。

    第 1 步:将模型创建为类并理想地使用接口定义

    class Vgg3Model:
    
        NUM_DENSE_NEURONS = 50
        DENSE_RESHAPE = 32 * (CONSTANTS.IMAGE_SHAPE[0] // 2) * (CONSTANTS.IMAGE_SHAPE[1] // 2)
    
        def inference(self, images):
            '''
            Portion of the compute graph that takes an input and converts it into a Y output
            '''
            with tf.variable_scope('Conv1') as scope:
                C_1_1 = ld.cnn_layer(images, (5, 5, 3, 32), (1, 1, 1, 1), scope, name_postfix='1')
                C_1_2 = ld.cnn_layer(C_1_1, (5, 5, 32, 32), (1, 1, 1, 1), scope, name_postfix='2')
                P_1 = ld.pool_layer(C_1_2, (1, 2, 2, 1), (1, 2, 2, 1), scope)
            with tf.variable_scope('Dense1') as scope:
                P_1 = tf.reshape(P_1, (-1, self.DENSE_RESHAPE))
                dim = P_1.get_shape()[1].value
                D_1 = ld.mlp_layer(P_1, dim, self.NUM_DENSE_NEURONS, scope, act_func=tf.nn.relu)
            with tf.variable_scope('Dense2') as scope:
                D_2 = ld.mlp_layer(D_1, self.NUM_DENSE_NEURONS, CONSTANTS.NUM_CLASSES, scope)
            H = tf.nn.softmax(D_2, name='prediction')
            return H
    
        def loss(self, logits, labels):
            '''
            Adds Loss to all variables
            '''
            cross_entr = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels)
            cross_entr = tf.reduce_mean(cross_entr)
            tf.summary.scalar('cost', cross_entr)
            tf.add_to_collection('losses', cross_entr)
            return tf.add_n(tf.get_collection('losses'), name='total_loss')
    

    第 2 步:使用您想要的任何输入来训练您的网络;就我而言,我使用了 Queue Runners 和 TF Records。请注意,此步骤由不同的团队完成,该团队迭代、构建、设计和优化模型。这也可能随着时间而改变。他们产生的输出必须能够从远程位置提取,以便我们可以在设备上动态加载更新的模型(重新刷新硬件很痛苦,尤其是在地理分布的情况下)。在这种情况下;团队删除了与图形保护程序关联的 3 个文件;但也是用于该培训课程的模型的泡菜

    model = vgg3.Vgg3Model()
    
    def create_sess_ops():
        '''
        Creates and returns operations needed for running
        a tensorflow training session
        '''
        GRAPH = tf.Graph()
        with GRAPH.as_default():
            examples, labels = Inputs.read_inputs(CONSTANTS.RecordPaths,
                                              batch_size=CONSTANTS.BATCH_SIZE,
                                              img_shape=CONSTANTS.IMAGE_SHAPE,
                                              num_threads=CONSTANTS.INPUT_PIPELINE_THREADS)
            examples = tf.reshape(examples, [-1, CONSTANTS.IMAGE_SHAPE[0],
                                         CONSTANTS.IMAGE_SHAPE[1], CONSTANTS.IMAGE_SHAPE[2]], name='infer/input')
            logits = model.inference(examples)
            loss = model.loss(logits, labels)
            OPTIMIZER = tf.train.AdamOptimizer(CONSTANTS.LEARNING_RATE)
            gradients = OPTIMIZER.compute_gradients(loss)
            apply_gradient_op = OPTIMIZER.apply_gradients(gradients)
            gradients_summary(gradients)
            summaries_op = tf.summary.merge_all()
            return [apply_gradient_op, summaries_op, loss, logits], GRAPH
    
    def main():
        '''
        Run and Train CIFAR 10
        '''
        print('starting...')
        ops, GRAPH = create_sess_ops()
        total_duration = 0.0
        with tf.Session(graph=GRAPH) as SESSION:
            COORDINATOR = tf.train.Coordinator()
            THREADS = tf.train.start_queue_runners(SESSION, COORDINATOR)
            SESSION.run(tf.global_variables_initializer())
            SUMMARY_WRITER = tf.summary.FileWriter('Tensorboard/' + CONSTANTS.MODEL_NAME, graph=GRAPH)
            GRAPH_SAVER = tf.train.Saver()
    
            for EPOCH in range(CONSTANTS.EPOCHS):
                duration = 0
                error = 0.0
                start_time = time.time()
                for batch in range(CONSTANTS.MINI_BATCHES):
                    _, summaries, cost_val, prediction = SESSION.run(ops)
                    error += cost_val
                duration += time.time() - start_time
                total_duration += duration
                SUMMARY_WRITER.add_summary(summaries, EPOCH)
                print('Epoch %d: loss = %.2f (%.3f sec)' % (EPOCH, error, duration))
                if EPOCH == CONSTANTS.EPOCHS - 1 or error < 0.005:
                    print(
                    'Done training for %d epochs. (%.3f sec)' % (EPOCH, total_duration)
                )
                    break
            GRAPH_SAVER.save(SESSION, 'models/' + CONSTANTS.MODEL_NAME + '.model')
            with open('models/' + CONSTANTS.MODEL_NAME + '.pkl', 'wb') as output:
                pickle.dump(model, output)
            COORDINATOR.request_stop()
            COORDINATOR.join(THREADS)
    

    第 3 步:运行一些推理。加载您的腌制模型;通过将新占位符管道传输到 logits 来创建新图形;然后调用会话恢复。不要恢复整个图表;只是变量。

    MODEL_PATH = 'models/' + CONSTANTS.MODEL_NAME + '.model'
    imgs_bsdir = 'C:/data/cifar_10/train/'
    
    images = tf.placeholder(tf.float32, shape=(1, 32, 32, 3))
    with open('models/vgg3.pkl', 'rb') as model_in:
    model = pickle.load(model_in)
    logits = model.inference(images)
    
    def run_inference():
        '''Runs inference against a loaded model'''
        with tf.Session() as sess:
            sess.run(tf.global_variables_initializer())
            new_saver = tf.train.Saver()
            new_saver.restore(sess, MODEL_PATH)
            print("Starting...")
            for i in range(20, 30):
                print(str(i) + '.png')
                img = misc.imread(imgs_bsdir + str(i) + '.png').astype(np.float32) / 255.0
                img = img.reshape(1, 32, 32, 3)
                pred = sess.run(logits, feed_dict={images : img})
                max_node = np.argmax(pred)
                print('predicted label: ' + str(max_node))
            print('done')
    
    run_inference()
    

    肯定有办法使用接口来改进这一点,也许可以更好地打包所有东西;但这是行之有效的,为我们将如何前进奠定了基础。

    最后说明当我们最终将其推向生产时,我们最终不得不将愚蠢的 `mymodel_model.py 文件连同构建图表的所有内容一起交付。因此,我们现在对所有模型强制执行命名约定,并且还有用于生产模型运行的编码标准,因此我们可以正确执行此操作。

    祝你好运!

    【讨论】:

    • 对于那些有兴趣的人来说,这实在是太歌舞了。我们现在改用 CNTK...为所有团队标准化一切并一致地投入生产更容易。
    【解决方案2】:

    虽然它不像 model.predict() 那样简单粗暴,但它仍然非常琐碎。

    在您的模型中,您应该有一个计算您感兴趣的最终输出的张量,我们将该张量命名为 output。您目前可能只有一个损失函数。如果是这样,请创建另一个实际计算所需输出的张量(模型中的变量)。

    例如,如果你的损失函数是:

    tf.nn.sigmoid_cross_entropy_with_logits(last_layer_activation, labels)
    

    并且您希望每个类的输出在 [0,1] 范围内,创建另一个变量:

    output = tf.sigmoid(last_layer_activation)
    

    现在,当您调用 sess.run(...) 时,只需请求 output 张量即​​可。不要请求通常会训练它的优化 OP。当你请求这个变量时,tensorflow 会做最少的工作来产生这个值(例如,它不会受到反向传播、损失函数等的影响,因为计算output 所需要的只是一个简单的前馈传递。

    因此,如果您要创建一个服务来返回模型的推断,您需要将模型加载到内存/gpu 中,然后重复:

    sess.run(output, feed_dict={X: input_data})
    

    您不需要向它提供标签,因为 tensorflow 不会费心计算生成您请求的输出不需要的操作。你不必改变你的模型或任何东西。

    虽然这种方法可能不像model.predict(...) 那样明显,但我认为它更加灵活。如果您开始使用更复杂的模型,您可能会学会喜欢这种方法。 model.predict() 就像“在盒子里思考”。

    【讨论】:

    • 我唯一的后续问题是;如何从图中提取“输出”?我很高兴总是将最终的张量“输出”命名为“接口”定义。问题是设备只能访问模型/检查点文件;它没有图形构造代码。
    • 我为 softmax 层添加了一个名称,希望我可以检索该张量并试一试:H = tf.nn.softmax(D_2, name='prediction') 之前该张量没有明确命名。
    • 试一试;它似乎只是锁定了:with tf.Session() as sess: new_saver = tf.train.import_meta_graph(MODEL_PATH + '.meta', clear_devices=True) new_saver.restore(sess, MODEL_PATH) pred = tf.get_default_graph( ).get_operation_by_name('prediction') print(sess.run(pred, feed_dict={images: np.random.rand(1, 32, 32, 3)}))
    • 我想我遇到了一个问题,它期望输入队列正在运行并将数据填充到队列中。我没有使用 feed_dict 来训练;我用过 tfRecords...
    • result_output = sess.run(output) 将计算并返回张量 output。如果您正在读取队列并从 tfRecords 加载这些队列,则需要启动一个在循环中运行 enqueue 操作的线程,QueueRunner 类旨在执行此操作。 output 这个名字没什么特别的,随便你怎么称呼。您需要将该张量传递给sess.run(...),因此要么按名称查找它(有很多关于按名称查找张量的 S.O. 文章),要么在创建图表时将其保存在某个地方。我将图形创建为对象并创建张量属性。
    猜你喜欢
    • 2017-02-21
    • 2018-10-18
    • 2018-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多