TensorFlow搭建卷积网络识别交通标志

作者: ydlstartx | 来源:发表于2018-05-18 17:08 被阅读67次

    目标是修改LeNet用于识别德国交通标志,共43个类别。LeNet有两个卷积层、三个全连接层,我增加了一层卷积,修改了kernel大小。另外,在全连接层中加入了dropou用于防止过拟合。这里记录下搭建网络的过程,总结使用TensorFlow搭建并训练卷积网络的方法。

    1. 数据集的载入、augment与normalization

    • 载入数据集
    """
    载入数据集
    """
    # 下载好的数据集是用pickl序列化了的,所以要用pickle来读取
    import pickle
    import numpy as np
    import matplotlib.pyplot as plt
    %matplotlib inline
    
    import tensorflow as tf
    # flatten用于最后一层卷积后的flatten
    from tensorflow.contrib.layers import flatten
    
    training_file = "./datasets/traffic-signs-data/train.p"
    validation_file= "./datasets/traffic-signs-data/valid.p"
    testing_file = "./datasets/traffic-signs-data/test.p"
    
    with open(training_file, mode='rb') as f:
        train = pickle.load(f)
    with open(validation_file, mode='rb') as f:
        valid = pickle.load(f)
    with open(testing_file, mode='rb') as f:
        test = pickle.load(f)
        
    X_train, y_train = train['features'], train['labels']
    X_valid, y_valid = valid['features'], valid['labels']
    X_test, y_test = test['features'], test['labels']
    

    训练、验证、测试集各有34799、4410、12630张图片,图片都为(32, 32, 3),共有43个类别。

    • 接下来是data augment

    我用了两个特别简单的方法来扩大数据集:一个是将图片都转为灰色,也就是将三个channel上的像素都取为它们的均值;另一个是对每张图片都加上一个100以内的整数。

    但并没有使用全部的数据,考虑到我用CPU训练,只是使用了五万多张图片。

    """
    Data Augmen
    """
    n_classes = y_train.max() + 1
    n_train = X_train.shape[0]
    # 第二种方法,先随机生成X_train.shape[0]个正整数
    bias = np.random.randint(0,100,(n_train,1,1,1))
    # broadcasting,并保证范围都在[0,255]以内
    X_train_aug0 = np.clip(X_train+bias, 0, 255).astype(np.uint8)
    y_train_aug0 = y_train
    
    # 沿各个图片的channel取均值
    # 本来为(34799, 32, 32, 3),取均值后为(34799, 3, 3, 1)
    a = np.mean(X_train,axis=3,keepdims=True).astype(np.uint8)
    # 将均值扩到3个channel,(34799, 3, 3, 1)->(34799, 32, 32, 3)
    X_train_aug1 = np.concatenate((a,a,a),axis=3)
    y_train_aug1 = y_train
    
    X_train_aug = np.concatenate((X_train_aug0,X_train_aug1), axis=0)
    y_train_aug = np.concatenate((y_train_aug0,y_train_aug1), axis=0)
    
    # 保存新数据
    with open('./datasets/traffic-signs-data/train_aug.p','wb') as f:
        pickle.dump(dict(features=X_train_aug, 
                         labels=y_train_aug, 
                         sizes=np.concatenate((train['sizes'],train['sizes']),axis=0),
                         coords=np.concatenate((train['coords'],train['coords']),axis=0)), 
                    f)
    
    # 添加到训练集,只使用了四分之一的新数据
    # 现在训练集有52199张图片
    X_train = np.concatenate((X_train,X_train_aug[0:-1:4,:,:,:]), axis=0)
    y_train = np.concatenate((y_train,y_train_aug[0:-1:4]), axis=0)
    
    • normalize训练、验证、测试集
      就使训练集均值为零后除以标准差
    # zero out the mean
    train_image_mean = np.mean(X_train, axis=0)
    X_train_norm = X_train - train_image_mean
    # normalize the variance
    train_image_std = np.std(X_train_norm, axis=0)
    X_train_norm = X_train_norm / train_image_std
    

    对测试、训练集做同样的操作,保证分布一致:

    X_valid_norm = X_valid - train_image_mean
    X_valid_norm = X_valid / train_image_std
    
    X_test_norm = X_test - train_image_mean
    X_test_norm = X_test / train_image_std
    

    2. 搭建网络

    接下来就是搭建网络的过程了。网络结构为:

    Layer Description
    Input 32x32x3 RGB image
    Convolution 3x3 1x1 stride, same padding, outputs 32x32x16
    LEAKY_RELU
    Max pooling 2x2 2x2 stride, outputs 16x16x16
    Convolution 3x3 1x1 stride, same padding, outputs 16x16x32
    LEAKY_RELU
    Max pooling 2x2 2x2 stride, outputs 8x8x32
    Convolution 3x3 1x1 stride, same padding, outputs 8x8x64
    LEAKY_RELU
    Max pooling 3x3 2x2 stride, outputs 3x3x64
    Flatten output 576
    Fully connected output 120
    LEAKY_RELU
    Dropout
    Fully connected outout 84
    LEAKY_RELU
    Dropout
    Fully connected output 43
    Softmax
    • kernel和全连接层权值的建立

    三层kernel分别为:16个3x3x3、32个3x3x16、64个3x3x32
    全连接层参数为:576x120、120x84、84x43

    用到的API为:
    tf.variable_scope()
    tf.get_variable()
    tf.truncated_normal_initializer()
    tf.add_to_collection()

    mu = 0
    sigma = 0.1
    # trainable用于控制卷积层和全连接层是否训练,记不得当时我为什么要控制这个了......
    # regularizer是想传入通过tf.contrib.layers.l2_regularizer()返回的函数来着。
    # 将其用于regularize全连接层参数,但是训练效果不好,最后就没用
    def initialize_parameters(trainable=[True,True], regularizer=None):
        # 将创建好的变量放入字典中返回
        parameters = {}
        with tf.variable_scope('parameters', reuse=tf.AUTO_REUSE):
    # Layer 1: Convolutional. 
            # 每一层都用了variable_scope,并设置了reuse=tf.AUTO_REUSE,配合tf.get_variable()达到共享变量的目标。没有变量时创建,有的话就调用创建好的。
            with tf.variable_scope('conv1', reuse=tf.AUTO_REUSE):
                # 第一个卷基层的kernel,3x3,输入#channel,输出#channel
                conv1_W = tf.get_variable("conv1_W", [3,3,3,16], 
                                  initializer = tf.truncated_normal_initializer(mu,sigma), dtype=tf.float32,
                                  trainable=trainable[0]) 
                # 除了kernel以外,别忘了bias
                conv1_b = tf.get_variable("conv1_b", [1,1,1,16],
                                  initializer = tf.zeros_initializer(), dtype=tf.float32,
                                  trainable=trainable[0])
                parameters["conv1_W"] = conv1_W 
                parameters["conv1_b"] = conv1_b 
        
    # Layer 2: Convolutional. 
            with tf.variable_scope('conv2', reuse=tf.AUTO_REUSE):
                conv2_W = tf.get_variable("conv2_W", [3,3,16,32],
                                  initializer = tf.truncated_normal_initializer(mu,sigma), dtype=tf.float32,
                                  trainable=trainable[0]) 
                conv2_b = tf.get_variable("conv2_b", [1,1,1,32],
                                  initializer = tf.zeros_initializer(), dtype=tf.float32,
                                  trainable=trainable[0])
                parameters["conv2_W"] = conv2_W 
                parameters["conv2_b"] = conv2_b 
    
        
    # Layer 3: Convolutional.
            with tf.variable_scope('conv3', reuse=tf.AUTO_REUSE):
                conv3_W = tf.get_variable("conv3_W", [3,3,32,64],
                                  initializer = tf.truncated_normal_initializer(mu,sigma), dtype=tf.float32,
                                  trainable=trainable[0]) 
                conv3_b = tf.get_variable("conv3_b", [1,1,1,64],
                                  initializer = tf.zeros_initializer(), dtype=tf.float32,
                                  trainable=trainable[0])
        
                parameters["conv3_W"] = conv3_W 
                parameters["conv3_b"] = conv3_b 
    
        
    # Layer 4: Fully Connected. 
            with tf.variable_scope('fc1', reuse=tf.AUTO_REUSE):
                fc1_W = tf.get_variable("fc1_W", [576,120],
                                initializer=tf.truncated_normal_initializer(mu,sigma), dtype=tf.float32,
                                trainable=trainable[1]) 
                fc1_b = tf.get_variable("fc1_b", [1,120],
                                initializer=tf.zeros_initializer(), dtype=tf.float32,
                                trainable=trainable[1])
                parameters["fc1_W"] = fc1_W 
                parameters["fc1_b"] = fc1_b 
    
        
    # Layer 5: Fully Connected. 
            with tf.variable_scope('fc2', reuse=tf.AUTO_REUSE):
                fc2_W = tf.get_variable("fc2_W", [120,84],
                                initializer=tf.truncated_normal_initializer(mu,sigma), dtype=tf.float32,
                                trainable=trainable[1]) 
                fc2_b = tf.get_variable("fc2_b", [1,84],
                                initializer=tf.zeros_initializer(), dtype=tf.float32,
                                trainable=trainable[1])
                parameters["fc2_W"] = fc2_W 
                parameters["fc2_b"] = fc2_b 
    
        
    # Layer 6: Fully Connected. 
            with tf.variable_scope('fc3', reuse=tf.AUTO_REUSE):
                fc3_W = tf.get_variable("fc3_W", [84,n_classes],
                                initializer=tf.truncated_normal_initializer(mu,sigma), dtype=tf.float32,
                                trainable=trainable[1]) 
                fc3_b = tf.get_variable("fc3_b", [1,n_classes],
                                initializer=tf.zeros_initializer(), dtype=tf.float32,
                                trainable=trainable[1])
                parameters["fc3_W"] = fc3_W 
                parameters["fc3_b"] = fc3_b 
                
        # add l2_regularization to collection 'regularize'
        # 如果传入了用于normalize的函数,则将对全连接层权值normalize的tensor加入集合'regularize'中。在cost函数中通过集合加入这些regularize项。
        if regularizer != None:
            tf.add_to_collection('regularize', regularizer(fc1_W))
            tf.add_to_collection('regularize', regularizer(fc2_W))
            tf.add_to_collection('regularize', regularizer(fc3_W))
        else:
            # 如果没有传入regularize函数,则在集合中加个常量0就好。
            # 因为后面使用tf.add_n(tf.get_collection('regularize'))来调出集合中的tensor
            # 若集合为空则会报错的。
            tf.add_to_collection('regularize', tf.constant(0., dtype=tf.float32))
        
        # 各个可训练的参数都在改字典中了
        # 搭建网络结构时调用就好
        return parameters
    

    建立权值的函数写好后,下面就是搭建网络前向传播的函数了:

    其中关于tensorflow中的same padding和valid padding的介绍可看这里

    # x为建立的placeholder,为(None, 32, 32, 3)
    # parameters就是上一函数的返回
    # keep_prob用于控制各层dropout的概率。卷基层概率都设为1。
    # 必须通过参数控制。
    # 因为在训练过程中和在测试网络性能过程中其值不同。
    # 返回的tensor logits为全连接的输出,为(None, 43),没有接softmax
    # 因为后面使用的是tf.nn.softmax_cross_entropy_with_logits()来构造cost
    def LeNet_forwardpass(x, parameters, keep_prob):    
    # Layer 1: Convolutional. Input = 32x32x3. Output = 32x32x16.
        conv1_W = parameters["conv1_W"]
        conv1_b = parameters["conv1_b"]
        conv1   = tf.nn.conv2d(x, conv1_W, strides=[1,1,1,1], padding='SAME') + conv1_b
        # Activation.
        conv1   = tf.nn.leaky_relu(conv1, name="conv1_out")
    
        # Pooling. Input = 32x32x16. Output = 16x16x16.
        conv1   = tf.nn.max_pool(conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
        
        # dropout
        conv1   = tf.nn.dropout(conv1, keep_prob=keep_prob[0])
    
    # Layer 2: Convolutional. Input = 16x16x16. Output = 16x16x32.
        conv2_W = parameters["conv2_W"]
        conv2_b = parameters["conv2_b"]
        conv2   = tf.nn.conv2d(conv1, conv2_W, strides=[1,1,1,1], padding='SAME') + conv2_b
        
        # Activation.
        conv2   = tf.nn.leaky_relu(conv2, name="conv2_out")
    
        # Pooling. Input = 16x16x32. Output = 8x8x32.
        conv2   = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
        
        # dropout
        conv2   = tf.nn.dropout(conv2, keep_prob=keep_prob[1])
    
    # Layer 3: Convolutional. Input = 8x8x32. Output = 8x8x64.
        conv3_W = parameters["conv3_W"]
        conv3_b = parameters["conv3_b"]
        conv3   = tf.nn.conv2d(conv2, conv3_W, strides=[1,1,1,1], padding='SAME') + conv3_b
        
        # Activation.
        conv3   = tf.nn.leaky_relu(conv3, name="conv3_out")
        
        # Pooling. Input = 8x8x64. Output = 3x3x64.
        conv3   = tf.nn.max_pool(conv3, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID')
        
        # dropout
        conv3   = tf.nn.dropout(conv3, keep_prob=keep_prob[2])
    
    # Flatten. Input = 3x3x64. Output = 576.
        fc0     = flatten(conv3)
        
    # Layer 4: Fully Connected. Input = 576. Output = 120.
        fc1_W = parameters["fc1_W"]
        fc1_b = parameters["fc1_b"]
        fc1     = tf.matmul(fc0, fc1_W) + fc1_b
        
        # Activation.
        fc1     = tf.nn.leaky_relu(fc1, name="fc1_out")
        # Dropout
        fc1     = tf.nn.dropout(fc1, keep_prob=keep_prob[3])
    
    # Layer 5: Fully Connected. Input = 120. Output = 84.
        fc2_W = parameters["fc2_W"]
        fc2_b = parameters["fc2_b"]
        fc2   = tf.matmul(fc1, fc2_W) + fc2_b
        
        # Activation.
        fc2     = tf.nn.leaky_relu(fc2, name="fc2_out")
        # Dropout
        fc2     = tf.nn.dropout(fc2, keep_prob=keep_prob[4])
    
    # Layer 6: Fully Connected. Input = 84. Output = n_classes.
        fc3_W = parameters["fc3_W"]
        fc3_b = parameters["fc3_b"]
        logits  = tf.matmul(fc2, fc3_W) + fc3_b
        
        return logits
    
    x = tf.placeholder(dtype=tf.float32, shape=(None, 32, 32, 3))
    # 因为数据集中的label都为0-42的整数,为减少工作(偷懒)
    # 没有对数据集的label转为one-hot形式,而是这里通过调用tf.one_hot()来转~~
    # 另外,由于要在训练和测试中分别控制dropout的概率
    # 所以将这个keep_prob也设为placeholder。
    
    y = tf.placeholder(dtype=tf.int32, shape=(None))
    keep_prob = tf.placeholder(dtype=tf.float32, shape=(5))
    # one_hot_y为(None, 43),每一行只有一个1,其他为0.
    one_hot_y = tf.one_hot(y, n_classes)
    

    3. cost和优化器

    下面是一些参数的设置,cost函数的建立,和优化器的建立。这里使用了learning rate decay。
    用到的API:
    tf.contrib.layers.l2_regularizer()
    tf.nn.softmax_cross_entropy_with_logits()
    tf.reduce_mean()
    tf.add_n()
    tf.get_collection()
    tf.Variable()
    tf.train.exponential_decay()
    tf.train.AdamOptimizer()

    # 训练的epoch和batch size
    EPOCHS = 25
    BATCH_SIZE = 128
    
    # 初始学习率
    rate = 0.005
    # 下降率
    decay_rate = 0.8
    # 多少次一下降
    decay_steps = 500
    trainable = [True,True] 
    lamb = 0.01
    
    # 返回的regularizer是一个函数,可以对输入tensor计算正则项
    regularizer = tf.contrib.layers.l2_regularizer(lamb)
    
    # 调用上面的函数建立参数
    parameters = initialize_parameters(trainable, regularizer=None)
    # 前向传播得到输出
    logits = LeNet_forwardpass(x, parameters, keep_prob)
    # 对网络的输出softmax后与label计算cross entropy
    # 返回的是各个example的cross entropy,下面要对其求平均
    # 该API不再推荐了,推荐使用v2版本
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=one_hot_y, logits=logits)
    
    # 求平均后,加上regularization项,但这里别没有
    loss_operation = tf.reduce_mean(cross_entropy) + tf.add_n(tf.get_collection('regularize'))
    
    # learning rate decay
    # 这个global_step是一个不可训练的变量,需要传给优化器对象的minimize方法
    # 在训练时,每一次优化都会对其加1
    global_step = tf.Variable(0, trainable=False)
    # 返回的为tensor,可看文档,是对初始学习率decay的学习率tensor。
    decay_rate = tf.train.exponential_decay(decay_rate=decay_rate, decay_steps=decay_steps, 
                                            global_step=global_step, learning_rate=rate)
    
    # 建立优化器对象。learning_rate参数可传入浮点数或tensor
    # 这里就传入的tensor
    optimizer = tf.train.AdamOptimizer(learning_rate = decay_rate)
    # trainig_operation是一个Operation,指出优化的是loss_operation.
    # 文档中也说了:If global_step was not None, that operation also increments global_step.
    # 现在传入了global_step,那么每次执行该Operation就会对global_step加1.
    training_operation = optimizer.minimize(loss_operation, global_step=global_step)
    

    在正式训练之前还要建立各评估函数,用于每次训练完之后,调用以便看看模型在训练集及验证集上的准确率。

    4. 评估函数

    思路很简单,对于网络的输出,找到最大值的索引,也就是该实例的类别,与其本身的label相比较,相等说明分类正确。然后计算分类正确的比例,也就是准确率了。用到的API:

    tf.argmax()
    tf.equal()
    tf.cast()
    tf.reduce_mean()
    tf.get_default_session()
    tf.Session.run()
    tf.train.Saver()

    # 很简单,accuracy_operation也就是准去率tensor了
    correct_prediction = tf.equal(tf.argmax(logits, 1, output_type=tf.int32), y)
    accuracy_operation = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    
    # 建立个函数在Session中调用,方便
    def evaluate(X_data, y_data):
        num_examples = len(X_data)
        total_accuracy = 0
        sess = tf.get_default_session()
        # 这里是按batch评估准确率,最后计算总准确率了
        # 这样的话应该运行速度比一次计算整体数据集的准确率快。
        for offset in range(0, num_examples, BATCH_SIZE):
            batch_x, batch_y = X_data[offset:offset+BATCH_SIZE], y_data[offset:offset+BATCH_SIZE]
            # 因为是测试模型准确率,所以dropout概率为1,就是没有dropout。
            accuracy = sess.run(accuracy_operation, feed_dict={x: batch_x, y: batch_y, keep_prob: [1.0,1.0,1.0,1.0,1.0]})
            total_accuracy += (accuracy * len(batch_x))
        return total_accuracy / num_examples
    
    # 建立各Saver对象用于训练完成后保存模型。
    saver = tf.train.Saver()
    

    5. 训练模型

    训练的过程就是在batch上不断调用优化器优化cost的过程。用到的API:
    tf.Session()
    tf.global_variables_initializer()
    tf.Session.run()
    tf.train.Saver.save()

    # 用于在每次epoch中shuffle数据集
    from sklearn.utils import shuffle
    
    # 记录每次训练后batch上的cost值
    cost = []
    # 记录训练完一个epoch后,在整个数据集上的cost。
    cost_epoch = []
    # keep_prob
    # 训练时dropout的概率,卷积层不dropout
    k = [1.,1.,1.,0.4,0.5]
    with tf.Session() as sess:
        # 可别忘了初始化全局变量
        sess.run(tf.global_variables_initializer())
        num_examples = len(X_train_norm)
        
        print("Training...")
        print()
        for i in range(EPOCHS):
            # 这里shuffle的数据集。感觉弄一个数据集的索引列表,
            # shuffle该列表应该快一些。
            X_train_norm, y_train = shuffle(X_train_norm, y_train)
            for offset in range(0, num_examples, BATCH_SIZE):
                end = offset + BATCH_SIZE
                # 在numpy中不用担心索引超出数组边界。
                batch_x, batch_y = X_train_norm[offset:end], y_train[offset:end]
                sess.run(training_operation, feed_dict={x: batch_x, y: batch_y, keep_prob: k})
                loss = sess.run(loss_operation, feed_dict={x: batch_x, y: batch_y, keep_prob: [1.0,1.0,1.0,1.0,1.0]})
                cost.append(loss)
            
            loss = sess.run(loss_operation, feed_dict={x: X_train_norm, y: y_train, keep_prob: [1.0,1.0,1.0,1.0,1.0]})
            cost_epoch.append(loss)
            
            # 分别在训练、测试集上评估正确率。
            train_accuracy = evaluate(X_train_norm, y_train)    
            validation_accuracy = evaluate(X_valid_norm, y_valid)
            print("EPOCH {} ...".format(i+1))
            print("Train Accuracy = {:.5f}".format(train_accuracy))
            print("Validation Accuracy = {:.5f}".format(validation_accuracy))
            print()
            
        # 保存训练好的模型
        saver.save(sess, './model/model.ckpt')
        print("Model saved")
    

    运行上面的代码,就开始训练模型了。从第十几个epoch开始,在验证集上的正确率就稳定在96.x%了。在训练集上都是99.9x%。过拟合还是很严重的,多使用些方法做data augment会降低过拟合。

    注意,在退出上下文管理器tf.Session()后,该会话就关闭了,该会话所拥有的变量的值也就被释放了。但是没关系,已经将训练好的模型保存到了文件中。

    下面画出的是列表cost,也就是每个batch后的cost值,可看到mini-batch gradient descent的特点:

    下面画出的是列表cost_epoch,也就是每个epoch后的cost值:

    6. 评估测试集

    下面就要在测试集上评估模型的性能了。用到的API:
    tf.train.Saver.restore()

    with tf.Session() as sess:
        # restore的过程就相当于将保存的训练好的模型加载到当前graph中
        # 构建好的结构中。在这一过程中,各变量的值都被初始化为保存好的
        # 变量的值。
        # 也就不需要再调用sess.run(tf.global_variables_initializer())了
        saver.restore(sess, './model/model.ckpt')
    
        test_accuracy = evaluate(X_test_norm, y_test)
        print("Test Accuracy = {:.3f}".format(test_accuracy))
    

    模型在测试集上的精度可达到0.943。

    7. 观看网络状态

    下面的函数可以画出对于某张图片,网络各层的输出。将输出形式化的显示。只是最基本的显示,只能对第一层卷积的输出有比较好的观察。

    下面函数的一个输入要求传递的是网络中某一层(要看的那一层)激励函数的输出Tensor对象。但有个问题是,我是通过函数来搭建网络结构的,函数只是输出了网络输出层的Tensor。对于在函数中使用的conv1、conv2等变量无法再引用了。

    但这没关系,因为搭建的这些Tensor和Operation都存在于Graph中,由于没有显式创建Graph对象,所以TensorFlow自动创建了个default graph。通过查看tf.Graph文档,可发现其有个get_operations()方法可以返回图上所有的Operation对象。

    但我们现在需要的是激励函数创建的Tensor,能不能得到图上所有的Tensor对象呢?很遗憾,没有这样的API。但是没关系,找到了Operation,就可以找到其输出的Tensor。因为Tensor对象的命名方式是 “op:num”,就是产生该Tensor的Operation名,后面接该Tensor来自Operation的第几个输出。

    这就好办了,可先通过tf.get_default_graph()得到默认的图对象,再通过tf.Graph.get_operations()得到图上的所有Operation,因为在调用激励函数leaky_relu时我给了名字,比如'conv1_out',所以可以通过这个来查看。

    但查找的时候发现和我预想的不一样,没有名字为'conv1_out'的Operation对象,但有三个类似的:

     <tf.Operation 'conv1_out/alpha' type=Const>,
     <tf.Operation 'conv1_out/mul' type=Mul>,
     <tf.Operation 'conv1_out/Maximum' type=Maximum>,
    

    这三个应该是在进行leaky_relu运算中的三个操作,我猜测找的应该是<tf.Operation 'conv1_out/Maximum' type=Maximum>,因为在leaky_relu运算的最后一步就是比较大小。

    所以对应的Tensor对象名就为'conv1_out/Maximum:0',再使用tf.get_default_graph().get_tensor_by_name得到该Tensor对象,这样的话可以使用下面代码来得到该Tensor对象。

    tf.get_default_graph().get_tensor_by_name('conv1_out/Maximum:0')
    # 结果为:
    <tf.Tensor 'conv1_out/Maximum:0' shape=(?, 32, 32, 16) dtype=float32>
    

    还有另一方法可得到该Tensor对象,因为tf.Operation类中有个属性,outputs,保存的是该op输出的Tensor对象列表。所以可以使用tf.get_default_graph().get_operation_by_name('conv1_out/Maximum')得到该Operation对象后,再访问outputs属性来得到目标Tensor对象。即:

    tf.get_default_graph().get_operation_by_name('conv1_out/Maximum').outputs
    # 结果为
    <tf.Tensor 'conv1_out/Maximum:0' shape=(?, 32, 32, 16) dtype=float32>
    

    可见二者是一样的。

    说了这么多,终于到了显示网络状态的函数了,其实很简单,就是在Session中,对网络传入图片前向传播,将某层激励函数的输出都当做灰度图像显示。比如第一个卷积层输出了16个channel,那么就将这16个channel都当做灰度图片显示。代码如下:

    # image_input为四维(batch, img_weight, img_height, channel)
    # tf_activation就是激励函数输出的Tensor
    # activation_min、activation_max、plt_num是对matplotlib显示图片的控制,不用管,使用默认值就好
    # i指出使用image_input中的第几个图片
    # 该函数要在Session会话中调用。
    def outputFeatures(image_input, tf_activation, activation_min=-1, activation_max=-1 ,plt_num=1, i=0):
        activation = tf_activation.eval(session=sess,feed_dict={x : image_input, keep_prob: [1.0,1.0,1.0,1.0,1.0]})
        # 得到激励函数输出的channel数
        featuremaps = activation.shape[3]
        plt.figure(plt_num, figsize=(15,5))
        for featuremap in range(featuremaps):
            # 我这里看的是第一层卷积的输出,共有16个channel
            # 所以这里subplot是2x8,如果是其他数目的channel需要修改
            plt.subplot(2,8, featuremap+1) # sets the number of feature maps to show on each row and column
            plt.title('FeatureMap ' + str(featuremap)) # displays the feature map number
            if activation_min != -1 & activation_max != -1:
                plt.imshow(activation[i,:,:, featuremap], interpolation="nearest", vmin =activation_min, vmax=activation_max, cmap="gray")
            elif activation_max != -1:
                plt.imshow(activation[i,:,:, featuremap], interpolation="nearest", vmax=activation_max, cmap="gray")
            elif activation_min !=-1:
                plt.imshow(activation[i,:,:, featuremap], interpolation="nearest", vmin=activation_min, cmap="gray")
            else:
                plt.imshow(activation[i,:,:, featuremap], interpolation="nearest", cmap="gray")
    

    调用该函数显示第一层卷积的输出:

    with tf.Session() as sess:
        saver.restore(sess, './model/model.ckpt')
        outputFeatures(im, tf.get_default_graph().get_tensor_by_name('conv1_out/Maximum:0'),  i=8)
    

    我上面的i=8,显示索引为8的图片,该图片本身为:

    第一层卷积输出的16个channel显示如下:

    下面再给些其他图片的结果:

    相关文章

      网友评论

      本文标题:TensorFlow搭建卷积网络识别交通标志

      本文链接:https://www.haomeiwen.com/subject/wglidftx.html