TensorFlow搭建卷積網絡識別交通標志

目標是修改LeNet用于識別德國交通標志,共43個類別茬末。LeNet有兩個卷積層、三個全連接層盖矫,我增加了一層卷積丽惭,修改了kernel大小。另外辈双,在全連接層中加入了dropou用于防止過擬合责掏。這里記錄下搭建網絡的過程,總結使用TensorFlow搭建并訓練卷積網絡的方法湃望。

1. 數(shù)據(jù)集的載入拷橘、augment與normalization

  • 載入數(shù)據(jù)集
"""
載入數(shù)據(jù)集
"""
# 下載好的數(shù)據(jù)集是用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诅挑。

我用了兩個特別簡單的方法來擴大數(shù)據(jù)集:一個是將圖片都轉為灰色四敞,也就是將三個channel上的像素都取為它們的均值;另一個是對每張圖片都加上一個100以內的整數(shù)拔妥。

但并沒有使用全部的數(shù)據(jù)忿危,考慮到我用CPU訓練,只是使用了五萬多張圖片没龙。

"""
Data Augmen
"""
n_classes = y_train.max() + 1
n_train = X_train.shape[0]
# 第二種方法铺厨,先隨機生成X_train.shape[0]個正整數(shù)
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)

# 保存新數(shù)據(jù)
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)

# 添加到訓練集,只使用了四分之一的新數(shù)據(jù)
# 現(xiàn)在訓練集有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
全連接層參數(shù)為: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()返回的函數(shù)來著。
# 將其用于regularize全連接層參數(shù)搅裙,但是訓練效果不好皱卓,最后就沒用
def initialize_parameters(trainable=[True,True], regularizer=None):
    # 將創(chuàng)建好的變量放入字典中返回
    parameters = {}
    with tf.variable_scope('parameters', reuse=tf.AUTO_REUSE):
# Layer 1: Convolutional. 
        # 每一層都用了variable_scope,并設置了reuse=tf.AUTO_REUSE部逮,配合tf.get_variable()達到共享變量的目標娜汁。沒有變量時創(chuàng)建,有的話就調用創(chuàng)建好的兄朋。
        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的函數(shù),則將對全連接層權值normalize的tensor加入集合'regularize'中峡扩。在cost函數(shù)中通過集合加入這些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函數(shù),則在集合中加個常量0就好教届。
        # 因為后面使用tf.add_n(tf.get_collection('regularize'))來調出集合中的tensor
        # 若集合為空則會報錯的响鹃。
        tf.add_to_collection('regularize', tf.constant(0., dtype=tf.float32))
    
    # 各個可訓練的參數(shù)都在改字典中了
    # 搭建網絡結構時調用就好
    return parameters

建立權值的函數(shù)寫好后驾霜,下面就是搭建網絡前向傳播的函數(shù)了:

其中關于tensorflow中的same padding和valid padding的介紹可看這里

# x為建立的placeholder买置,為(None, 32, 32, 3)
# parameters就是上一函數(shù)的返回
# keep_prob用于控制各層dropout的概率粪糙。卷基層概率都設為1。
# 必須通過參數(shù)控制忿项。
# 因為在訓練過程中和在測試網絡性能過程中其值不同蓉冈。
# 返回的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))
# 因為數(shù)據(jù)集中的label都為0-42的整數(shù)洒擦,為減少工作(偷懶)
# 沒有對數(shù)據(jù)集的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和優(yōu)化器

下面是一些參數(shù)的設置掸茅,cost函數(shù)的建立,和優(yōu)化器的建立柠逞。這里使用了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是一個函數(shù),可以對輸入tensor計算正則項
regularizer = tf.contrib.layers.l2_regularizer(lamb)

# 調用上面的函數(shù)建立參數(shù)
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是一個不可訓練的變量撒璧,需要傳給優(yōu)化器對象的minimize方法
# 在訓練時,每一次優(yōu)化都會對其加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)

# 建立優(yōu)化器對象硫椰。learning_rate參數(shù)可傳入浮點數(shù)或tensor
# 這里就傳入的tensor
optimizer = tf.train.AdamOptimizer(learning_rate = decay_rate)
# trainig_operation是一個Operation繁调,指出優(yōu)化的是loss_operation.
# 文檔中也說了:If global_step was not None, that operation also increments global_step.
# 現(xiàn)在傳入了global_step,那么每次執(zhí)行該Operation就會對global_step加1.
training_operation = optimizer.minimize(loss_operation, global_step=global_step)

在正式訓練之前還要建立各評估函數(shù)靶草,用于每次訓練完之后蹄胰,調用以便看看模型在訓練集及驗證集上的準確率。

4. 評估函數(shù)

思路很簡單奕翔,對于網絡的輸出烤送,找到最大值的索引,也就是該實例的類別糠悯,與其本身的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))

# 建立個函數(shù)在Session中調用,方便
def evaluate(X_data, y_data):
    num_examples = len(X_data)
    total_accuracy = 0
    sess = tf.get_default_session()
    # 這里是按batch評估準確率阅悍,最后計算總準確率了
    # 這樣的話應該運行速度比一次計算整體數(shù)據(jù)集的準確率快好渠。
    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上不斷調用優(yōu)化器優(yōu)化cost的過程寻行。用到的API:
tf.Session()
tf.global_variables_initializer()
tf.Session.run()
tf.train.Saver.save()

# 用于在每次epoch中shuffle數(shù)據(jù)集
from sklearn.utils import shuffle

# 記錄每次訓練后batch上的cost值
cost = []
# 記錄訓練完一個epoch后霍掺,在整個數(shù)據(jù)集上的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的數(shù)據(jù)集杆烁。感覺弄一個數(shù)據(jù)集的索引列表,
        # 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中不用擔心索引超出數(shù)組邊界兔魂。
            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開始铜涉,在驗證集上的正確率就穩(wěn)定在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. 觀看網絡狀態(tài)

下面的函數(shù)可以畫出對于某張圖片随闽,網絡各層的輸出。將輸出形式化的顯示肝谭。只是最基本的顯示掘宪,只能對第一層卷積的輸出有比較好的觀察。

下面函數(shù)的一個輸入要求傳遞的是網絡中某一層(要看的那一層)激勵函數(shù)的輸出Tensor對象攘烛。但有個問題是魏滚,我是通過函數(shù)來搭建網絡結構的,函數(shù)只是輸出了網絡輸出層的Tensor坟漱。對于在函數(shù)中使用的conv1鼠次、conv2等變量無法再引用了。

但這沒關系靖秩,因為搭建的這些Tensor和Operation都存在于Graph中须眷,由于沒有顯式創(chuàng)建Graph對象,所以TensorFlow自動創(chuàng)建了個default graph沟突。通過查看tf.Graph文檔花颗,可發(fā)現(xiàn)其有個get_operations()方法可以返回圖上所有的Operation對象。

但我們現(xiàn)在需要的是激勵函數(shù)創(chuàng)建的Tensor惠拭,能不能得到圖上所有的Tensor對象呢扩劝?很遺憾,沒有這樣的API职辅。但是沒關系棒呛,找到了Operation,就可以找到其輸出的Tensor域携。因為Tensor對象的命名方式是 “op:num”簇秒,就是產生該Tensor的Operation名,后面接該Tensor來自Operation的第幾個輸出秀鞭。

這就好辦了趋观,可先通過tf.get_default_graph()得到默認的圖對象,再通過tf.Graph.get_operations()得到圖上的所有Operation锋边,因為在調用激勵函數(shù)leaky_relu時我給了名字皱坛,比如'conv1_out',所以可以通過這個來查看豆巨。

但查找的時候發(fā)現(xiàn)和我預想的不一樣剩辟,沒有名字為'conv1_out'的Operation對象,但有三個類似的:

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

這三個應該是在進行l(wèi)eaky_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>

可見二者是一樣的。

說了這么多刺桃,終于到了顯示網絡狀態(tài)的函數(shù)了粹淋,其實很簡單,就是在Session中瑟慈,對網絡傳入圖片前向傳播桃移,將某層激勵函數(shù)的輸出都當做灰度圖像顯示。比如第一個卷積層輸出了16個channel葛碧,那么就將這16個channel都當做灰度圖片顯示借杰。代碼如下:

# image_input為四維(batch, img_weight, img_height, channel)
# tf_activation就是激勵函數(shù)輸出的Tensor
# activation_min、activation_max进泼、plt_num是對matplotlib顯示圖片的控制蔗衡,不用管,使用默認值就好
# i指出使用image_input中的第幾個圖片
# 該函數(shù)要在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]})
    # 得到激勵函數(shù)輸出的channel數(shù)
    featuremaps = activation.shape[3]
    plt.figure(plt_num, figsize=(15,5))
    for featuremap in range(featuremaps):
        # 我這里看的是第一層卷積的輸出绞惦,共有16個channel
        # 所以這里subplot是2x8,如果是其他數(shù)目的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")

調用該函數(shù)顯示第一層卷積的輸出:

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顯示如下:

下面再給些其他圖片的結果:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市菠发,隨后出現(xiàn)的幾起案子王滤,更是在濱河造成了極大的恐慌,老刑警劉巖雷酪,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淑仆,死亡現(xiàn)場離奇詭異涝婉,居然都是意外死亡哥力,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吩跋,“玉大人寞射,你說我怎么就攤上這事⌒颗ィ” “怎么了桥温?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梁丘。 經常有香客問我侵浸,道長,這世上最難降的妖魔是什么氛谜? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任掏觉,我火速辦了婚禮,結果婚禮上值漫,老公的妹妹穿的比我還像新娘澳腹。我一直安慰自己,他們只是感情好杨何,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布酱塔。 她就那樣靜靜地躺著,像睡著了一般危虱。 火紅的嫁衣襯著肌膚如雪羊娃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天槽地,我揣著相機與錄音迁沫,去河邊找鬼。 笑死捌蚊,一個胖子當著我的面吹牛集畅,可吹牛的內容都是我干的。 我是一名探鬼主播缅糟,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼挺智,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窗宦?” 一聲冷哼從身側響起赦颇,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赴涵,沒想到半個月后媒怯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡髓窜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年扇苞,在試婚紗的時候發(fā)現(xiàn)自己被綠了欺殿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡鳖敷,死狀恐怖脖苏,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情定踱,我是刑警寧澤棍潘,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站崖媚,受9級特大地震影響亦歉,放射性物質發(fā)生泄漏。R本人自食惡果不足惜畅哑,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一鳍徽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敢课,春花似錦阶祭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至圾结,卻和暖如春瑰剃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筝野。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工晌姚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人歇竟。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓挥唠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親焕议。 傳聞我的和親對象是個殘疾皇子宝磨,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容