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

目标是修改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显示如下:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容