对于同一个模型,预测结果随输入样本数量而改变

目前在做一个 softmax 的分类,非常基础,是分类电视台标的。输入维度是 [None, 9635] 遇到个问题,对于模型的训练,精度可以达到 99.9%。把这个模型保存下来,然后调用这个模型进行预测,发现当输入的预测图像矩阵是一副图片,即 [1,9635],不论输入啥图片,哪怕是 zeros,都会得到同一个判断分类,但是,如果测试输入矩阵变多,比如一次 10 幅图 [10,96*35],输出就正常了。想问下我哪里做错了导致模型不变的情况下判断结果跟输入的图片个数有关?

下面是我的测试代码

def recognize (img_input, pb_file_path):
    with tf.Graph ().as_default ():
        output_graph_def = tf.GraphDef ()

    with open (pb_file_path, "rb") as f:
        output_graph_def.ParseFromString (f.read ())
        _ = tf.import_graph_def (output_graph_def, name="")

    with tf.Session () as sess:
        init = tf.global_variables_initializer ()
        sess.run (init)

        input_x = sess.graph.get_tensor_by_name ("Input:0")
        # print input_x
       out_softmax = sess.graph.get_tensor_by_name ("Y_predict:0")
            
       img_try = img_input

       test_input = np.float32 (img_try/255)
       img_out_softmax = sess.run (out_softmax, feed_dict={input_x:test_input})

       prediction_labels = np.argmax (img_out_softmax, axis=1)

       return prediction_labels


 #----------------------Use Sub Function to Predict --------------------------------------------
start = 10
for i in range (1,10):  # 将测试图像从一次带入 1 张遍历到一次带入 10 张,看测试结果
    r = recognize (img_test [start:start+i], "./pretrained/graph.pb")
    print (listmapping (r,TV_LOGO_s))

测试结果,可以看到图像为 1 张的时候结果误判成 cctv5+,但是随着一次带入图片变多(图片没有变化,还是这些图,可以理解为总共 10 张图,第一次带入第一张,第二次带入第一第二张,以此类推),结果就正确了


提问者 oceancjc,2018-5-7 23:17:42

帖子补充:

yunhai_luo:

请问楼主一个可能不相关的问题:楼主的 pb_file 是怎么得到的?代码里貌似看不出来,也没有导出过计算图。如果是确实是 frozen 的模型,那就没有变量了,为何楼主要初始化变量呢?

oceancjc:

回复 yunhai_luo: 你好,我的理解是,图的结构是定死的,但是输入节点还是一个变量,在 run 的时候需要 feed 数据的,输出也还是一个变量,中间的权重啥的的确定死了,所以还是需要初始化变量的

yunhai_luo:

回复 oceancjc: 就我所知,固定好的模型应该是不需要初始化变量的,学习了。楼主能说一下 tf.global_variables () 有什么变量吗?

oceancjc:

回复 yunhai_luo :两个变量,代码的第 13 和 15 行

yunhai_luo:

回复 oceancjc :楼主方便分享一下 pb 文件吗?想学习一下楼主的模型,我在下面解释了,谢谢!

你好,我的理解是,图的结构是定死的,但是输入节点还是一个变量,在 run 的时候需要 feed 数据的,输出也还是一个变量,中间的权重啥的的确定死了,所以还是需要初始化变量的


oceancjc,发表于 2018-5-8 08:11:24

楼主的问题我解决不了,但我挺好奇楼主 frozen 的模型是什么情况。我在点评中也提到过,就我所知,固定好的模型应该是不需要初始化变量的。除此之外,输入和输出一般都是不会作为变量存在的。这里的变量指的是 tf.global_variables () 所给出的 GraphKeys.GLOBAL_VARIABLES 所涵盖的变量。楼主这里导入固定模型后,所有变量都确实被固化了,但输入和输出成为了 GraphKeys.GLOBAL_VARIABLES 中的变量,也因此需要初始化变量。不知道楼主构建和训练模型时输入和输出是否也是变量,但这一现象个人没有见过,应该跟楼主模型的特异性有关,不知道楼主是否方便分享一下 pb 文件以供学习。这里提供一下我的测试代码,模型很傻,请包涵,只是为了解释一下我上面说过的现象:

首先,构建训练模型:

import tensorflow as tf
import numpy as np

x = tf.placeholder (tf.float32, shape=[None, 1])
y_ = tf.layers.dense (x, units=1, name='pred')
loss = tf.losses.mean_squared_error (labels=x, predictions=y_)
optimizer = tf.train.GradientDescentOptimizer (0.01)
train_op = optimizer.minimize (loss)
init = tf.global_variables_initializer ()
saver = tf.train.Saver ()

print (tf.global_variables ())

with tf.Session () as sess:
    sess.run (init)
    for _ in range (10):
        sess.run (train_op, feed_dict={x: np.arange (10).reshape ((-1, 1))})
    tf.train.write_graph (sess.graph.as_graph_def (), r'.\test', 'test.pb', False)
    saver.save (sess, r'.\test\test')

从输出的tf.global_variables ()可以看出,输入和输出都不是变量。另外,这段代码会在当前路径下新建test文件夹,其中保存了计算图,一些元数据和 10 步训练之后的参数,用于下面 freeze 模型。

from tensorflow.python.tools import freeze_graph

freeze_graph.freeze_graph (input_graph=r'test\test.pb',
                          input_saver='',
                          input_binary=True,
                          input_checkpoint=r'test\test',
                          output_node_names='pred/BiasAdd',
                          restore_op_name="save/restore_all",
                          filename_tensor_name="save/Const:0",
                          output_graph=r'frozen.pb',
                          clear_devices=True,
                          initializer_nodes='')

这段代码会在当前路径下保存一个 frozen.pb 文件,是固化保存的模型,用于下面恢复模型。根据楼主提供的信息,我不确定楼主是如何保存和恢复模型的,鉴于楼主用的是 pb 文件,按常见情况,我斗胆推测是这样固化的,请楼主指正。

下面这段代码需要重开 python 终端运行,目的是排除之前计算图中的残存变量对于理解新恢复模型的干扰。

import numpy as np
import tensorflow as tf

graph_def = tf.GraphDef ()
with open (r'frozen.pb', "rb") as f:
    graph_def.ParseFromString (f.read ())
tf.import_graph_def (graph_def, name='infer')
x = tf.get_default_graph ().get_tensor_by_name ('infer/Placeholder:0')
y = tf.get_default_graph ().get_tensor_by_name ('infer/pred/BiasAdd:0')
print (tf.global_variables ())

test = np.random.permutation (10).reshape ((-1, 1))

with tf.Session () as sess:
    for i in range (10):
        y_val = sess.run (y, feed_dict={x: test [:i]})
        print (dict (zip (test [:i].flatten ().tolist (), np.around (y_val.astype (np.float64), 2).flatten ().tolist ())))

这段代码是恢复模型并用于预测,在这个例子中,从输出的tf.global_variables ()可以看出,这里没有任何变量,输入和输出也都不是变量,所以无须初始化变量,得到的预测也大致正确。


yunhai_luo,发表于 2018-5-8 12:56:08

赞赞赞,又学到了~我先保存下


M 丶 Sulayman ,发表于 2018-5-8 16:56:52

你好,对于引用模型不需要初始化变量的观点是正确的,我已经改正,但是注释掉这两句话并不会对结果有任何影响。还是不对的。为了方便大神们帮我看问题,我将数据集换成了 minst,发现有类似的结果,我将所有代码都上传上来。麻烦帮我看下,包括模型,以及 pb 文件保存方法 1.建立并训练模型,保存 checkpoint

import numpy as np
import tensorflow as tf
import tensorflow.examples.tutorials.mnist.input_data as input_data
mnist = input_data.read_data_sets (r"D:\MachineLearning\minst", one_hot=True)

def batch_normal (Y,offset_Beta,scale_J,iteration):
    exp_moving_avg = tf.train.ExponentialMovingAverage (0.998, iteration) # adding the iteration prevents from averaging across non-existing iterations
    mean, variance = tf.nn.moments (Y, [0])
    update_moving_averages = exp_moving_avg.apply ([mean, variance])
    m = exp_moving_avg.average (mean)
    v = exp_moving_avg.average (variance)
    # Ybn = tf.nn.batch_normalization (Y, m, v, offset_Beta, scale_J, 1e-5)
    Ybn = tf.nn.batch_normalization (Y, mean, variance, offset_Beta, scale_J, 1e-5)
    return Ybn,update_moving_averages


def hidden_layer (X, sizeOutput, iteration = 10, non_linear_name = '',enable_bn=False):
    sizeInput = X.shape [1]
    W = tf.Variable ( tf.truncated_normal ([int (sizeInput),int (sizeOutput)],stddev=0.001) )
    
    if enable_bn:   
        Z = tf.matmul (X,W)         #In bach norm, the bias B can be ommited due to the offset
        scale_J = tf.Variable (tf.ones ([int (sizeOutput)]))
        offset_Beta = tf.Variable (tf.zeros ([int (sizeOutput)]))
        Y, update_moving_averages = batch_normal (Z,offset_Beta,scale_J, iteration)
    else:           
        B = tf.Variable (tf.zeros ([1,sizeOutput]))
        Y = tf.matmul (X,W) + B
        
        
    if non_linear_name == '':                     return   Y,0
    elif non_linear_name == 'softmax':     A = tf.nn.softmax (Y)
    elif non_linear_name == 'relu':           A = tf.nn.relu (Y)
    elif non_linear_name == 'sigmoid':     A = tf.nn.sigmoid (Y)
    return A, update_moving_averages


#X       = tf.placeholder (tf.float32, [None, 96*35],name = 'Input')
#Y_LABEL = tf.placeholder (tf.float32, [None, CLASSES], name = 'Label')
X       = tf.placeholder (tf.float32, [None, 28*28],name = 'Input')
Y_LABEL = tf.placeholder (tf.float32, [None, 10], name = 'Label')
lr      = tf.placeholder (tf.float32, name = 'LearningRate')
iters   = tf.placeholder (tf.int32, name = 'Iterations')

A0, update_ema0 = hidden_layer (X,512, iters, 'relu',True)
A1, update_ema1 = hidden_layer (A0,256, iters, 'relu',True)
A2, update_ema2 = hidden_layer (A1,128,iters, 'relu',True)
A3, update_ema3 = hidden_layer (A2,64, iters, 'relu',True)
A4, update_ema4 = hidden_layer (A3,32, iters, 'relu',True)
Y_linear,nocare = hidden_layer (A4, Y_LABEL.shape [1]) 
Y_predict       = tf.nn.softmax (Y_linear,name = 'Y_predict')
update_ema = tf.group (update_ema0, update_ema1, update_ema2, update_ema3, update_ema4)
#update_ema = tf.group (update_ema0, update_ema2, update_ema3)

#cross_entropy = tf.reduce_mean (
#    tf.nn.softmax_cross_entropy_with_logits (labels=Y_LABEL, logits=Y_linear))
cross_entropy = tf.reduce_mean ( -tf.reduce_sum (Y_LABEL * tf.log (Y_predict), reduction_indices=[1]) )
#train_step = tf.train.GradientDescentOptimizer (0.01).minimize (cross_entropy)
train_step = tf.train.AdamOptimizer (lr).minimize (cross_entropy)
sess = tf.InteractiveSession ()
tf.global_variables_initializer ().run ()


max_learning_rate = 0.1
min_learning_rate = 0.001
decay_speed = 2000.0


saver = tf.train.Saver ()
img_train_float = np.float32 (mnist.train.images)
img_test_float = np.float32 (mnist.test.images)
print (img_train_float.shape)
for _ in range (2000):
    #batch_xs, batch_ys= next_batch (img_train_float,label_train,500)
    batch_xs, batch_ys= batch_xs, batch_ys = mnist.train.next_batch (500)
    learning_rate = min_learning_rate + (max_learning_rate - min_learning_rate) * np.exp (-_/decay_speed)
    sess.run (train_step, feed_dict={X: batch_xs, Y_LABEL: batch_ys, lr:learning_rate})
    if _%100 == 0:
        correct_prediction = tf.equal (tf.argmax (Y_linear, 1), tf.argmax (Y_LABEL, 1))
        accuracy = tf.reduce_mean (tf.cast (correct_prediction, tf.float32))
        #print ('This is round {} ...'.format (_))
        #print (batch_ys [0])
        #showImgfrombytes (batch_xs [0])
        saver.save (sess, "./Model_Minst/TVLogo_5_relu_softmax.ckpt",global_step= _ )
        #print (sess.run (accuracy, feed_dict={X:img_test_float, Y_LABEL: label_test}))
        print (sess.run (accuracy, feed_dict={X:img_test_float, Y_LABEL: mnist.test.labels}))

2.保存成 pb 文件

import tensorflow as tf
with tf.Session () as sess:

    # 初始化变量
    sess.run (tf.global_variables_initializer ())

    # 获取最新的 checkpoint,其实就是解析了 checkpoint 文件
    latest_ckpt = tf.train.latest_checkpoint ("./Model_Minst")

    # 加载图
    restore_saver = tf.train.import_meta_graph ('./Model_Minst/TVLogo_5_relu_softmax.ckpt-1900.meta')

    # 恢复图,即将 weights 等参数加入图对应位置中
    restore_saver.restore (sess, latest_ckpt)

    # 将图中的变量转为常量
    output_graph_def = tf.graph_util.convert_variables_to_constants (
    sess, sess.graph_def , ["Y_predict"])
    # 将新的图保存到"/pretrained/graph.pb"文件中
    tf.train.write_graph (output_graph_def, 'pretrained', "graph_minst.pb", as_text=False)

3.调用 2 中生成的 pb 文件预测数据

def recognize (img_input, pb_file_path):
    with tf.Graph ().as_default ():
        output_graph_def = tf.GraphDef ()

        with open (pb_file_path, "rb") as f:
            output_graph_def.ParseFromString (f.read ())
            _ = tf.import_graph_def (output_graph_def, name="")

        with tf.Session () as sess:
            #init = tf.global_variables_initializer ()
            #sess.run (init)

            input_x = sess.graph.get_tensor_by_name ('Input:0')
            #print (input_x)
            out_softmax = sess.graph.get_tensor_by_name ("Y_predict:0")
            # print out_softmax
            # out_label = sess.graph.get_tensor_by_name ("output:0")
            # print out_label

            # img = io.imread (jpg_path)
            # img = transform.resize (img, (224, 224, 3))
            
            img_try = img_input
            test_input = np.float32 (img_try/255)
            img_out_softmax = sess.run (out_softmax, feed_dict={input_x:test_input})

            #print ("img_out_softmax:",img_out_softmax)
            prediction_labels = np.argmax (img_out_softmax, axis=1)
            #print ("label:",prediction_labels)
            return prediction_labels

start = 30
for i in range (1,30):
    r = recognize (mnist.test.images [start:start+i], "./pretrained/graph_minst.pb")
    print (r)

结果:

TV Logo Recog_5relusoftmax_reducing_lr -Debug On Minst.rar (1.9 KB)


oceancjc(提问者),发表于 2018-5-8 22:33:40

谢谢分享!简单看了一下,不知道是不是 batch_normalization 的问题,因为貌似你的均值和方差是来源于整个 batch 的,batch 的任何改变都会反应在你模型的参数变化上,这不仅仅是样本数量,可能即使都是 30 个样本,但改变其中一些样本,你对没变样本的预测也会改变;而对于 batch_normalization 的使用应该是区分训练和预测两种情况的。这只是我的感觉,等我有时间仔细实验一下再请教。


yunhai_luo,2018-5-9 02:48

首先再次感谢楼主的分享,借楼主的模型学习了一下 batch normalization,收益良多。对于楼主的代码我还没有能力修改,对于出现不稳定性的原因,我比较确信是 batch normalization 的原因,但是由于没有能力直接对楼主的代码进行修改测试,不好说百分百肯定,希望下面我的一些心得能对楼主有帮助。在分享拙见之前,想请教楼主两个问题:

  1. 我只对 batch normalization 代码方面有些许收获,关于它对模型拟合学习上的作用(比如说数学上的特性)完全不了解,楼主能否谈谈 batch normalization 对于模型哪方面有所提升?有什么原理?有什么适用问题?

  2. 楼主模型中每一层都进行了 batch normalization,在其他例子中也见过这样的,请问这是常见或者必要的作法吗?它跟其他层应该如何搭配比较好?

先重复一下我点评里的分析,楼主模型中 batch normalization 部分,均值和方差都是来源于输入特征数据的,而且与 batch 的构成有关。此外,均值和方差都是张量而不是变量,所以不存在训练和固定的问题,也就是说在楼主导入固定模型后,这两个量仍然会随输入特征数据变化,定性的说,这就有可能解释楼主说的 “预测结果随输入样本数量而改变”。至于我说的关于即使样本数量不变而只改变样本构成的做法,根据上述解释,可以推测也同样会出现不稳定性。这个假说可以通过将楼主代码最后的循环改为以下代码来证实:

start = 30
for i in range (1,30):
    r = recognize (mnist.test.images [i:i+30], "graph_minst.pb")
    print (np.pad (r, (i,0), 'constant'))

结果我就不贴了,随着这 30 个样本抽样窗口的移动,我的运行可以看到不稳定性出现。这个结果算是从侧面说明了楼主的问题很有可能与 batch normalization 有关。

下面说有什么办法,我只有一个比较简单又比较无脑的例子,应该不适用于楼主,但算是给出一个稳定的例子,加上后面的一些想法,希望楼主能找到适合自己的解决办法。下面这个例子用的是 tf.layers.batch_normalization 而不是 tf.nn.batch_normalization,也就是捡现成的,tf.layers.batch_normalization 有一个"training"参数可以用来区分训练和预测(到这个地步我不打算深入关于区分的实现了,比较懒,不好意思)。

构建训练模型(这里特别缩短了训练次数以便降低精确度,这样后面的不稳定性更容易看到):

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets ("test", one_hot=True)

tf.reset_default_graph ()
x = tf.placeholder ('float32', (None, 784), name='x')
y = tf.placeholder ('float32', (None, 10), name='y')
phase = tf.placeholder (tf.bool, name='phase')

dense = tf.layers.dense (x, 100)
batchnorm = tf.layers.batch_normalization (dense, training=phase)
activation = tf.nn.relu (batchnorm, 'relu')
logits = tf.layers.dense (activation, 10)
predicts = tf.argmax (logits, 1, name='predicts')
accuracy = tf.reduce_mean (tf.cast (
    tf.equal (tf.argmax (y, 1), predicts), tf.float32))
loss = tf.losses.softmax_cross_entropy (onehot_labels=y, logits=logits)

update_ops = tf.get_collection (tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies (update_ops):
    train_op = tf.train.GradientDescentOptimizer (0.01).minimize (loss)

saver = tf.train.Saver ()
with tf.Session () as sess:
    sess.run (tf.global_variables_initializer ())
    for _ in range (200):
        x_train, y_train = mnist.train.next_batch (100)
        sess.run (train_op, feed_dict={'x:0': x_train, 
                            'y:0': y_train, 
                            'phase:0': 1})
    print (sess.run (accuracy, feed_dict={'x:0': mnist.test.images,
                                        'y:0': mnist.test.labels,
                                        'phase:0': 0}))
    saver.save (sess, 'testpb/testpb.ckpt')

保存模型:

import tensorflow as tf

restore_saver = tf.train.import_meta_graph ('testpb/testpb.ckpt.meta')
with tf.Session () as sess:
    restore_saver.restore (sess, tf.train.latest_checkpoint ('testpb'))
    output_graph_def = tf.graph_util.convert_variables_to_constants (
        sess, sess.graph_def , ['predicts'])
tf.train.write_graph (output_graph_def, '.', 'graph_mnist.pb', as_text=False)

导入模型进行预测:

import numpy as np
import tensorflow as tf
import tensorflow.examples.tutorials.mnist.input_data as input_data

mnist = input_data.read_data_sets ('test', one_hot=True)

graph_def = tf.GraphDef ()
with open ('graph_mnist.pb', "rb") as f:
    graph_def.ParseFromString (f.read ())
tf.import_graph_def (graph_def, name='')

with tf.Session () as sess:
    for i in range (30):
        print (sess.run ('predicts:0', feed_dict={'x:0': mnist.test.images [:i],
                                                'phase:0': 1}))

当你将最后一行的’phase:0’: 1 改为’phase:0’: 0 后,应该可以观察到区别。

最后说一下对于实现训练预测区分的一个小想法,楼主代码中用到了"tf.train.ExponentialMovingAverage",我大胆猜测是想要训练完成后用这个替代均值和方差,从而实现模型的稳定。看过一些例子,确实有训练完替换的做法,想来这应该可以解决楼主模型稳定性的问题。


yunhai_luo,发表于 2018-5-9 15:22:43

是的,我以为 freeze 了就把 batch norm 里的参数也定死了,嗯嗯,这点是我弄错了,你分析的对,如果这些参数不是定死的,那么就是随着 batch size 而变化,所以会根据测试的 batch size 而得到不同的结果,我尝试去重新改下模型架构后再交流,离正确答案越来越近了,非常感谢。


oceancjc(提问者),发表于 2018-5-9 23:39:59