TensorFlow搭建卷積網(wǎng)絡(luò)識(shí)別交通標(biāo)志

目標(biāo)是修改LeNet用于識(shí)別德國(guó)交通標(biāo)志,共43個(gè)類別。LeNet有兩個(gè)卷積層、三個(gè)全連接層,我增加了一層卷積,修改了kernel大小。另外,在全連接層中加入了dropou用于防止過擬合。這里記錄下搭建網(wǎng)絡(luò)的過程,總結(jié)使用TensorFlow搭建并訓(xùn)練卷積網(wǎng)絡(luò)的方法。

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']

訓(xùn)練、驗(yàn)證、測(cè)試集各有34799、4410、12630張圖片,圖片都為(32, 32, 3),共有43個(gè)類別。

  • 接下來是data augment。

我用了兩個(gè)特別簡(jiǎn)單的方法來擴(kuò)大數(shù)據(jù)集:一個(gè)是將圖片都轉(zhuǎn)為灰色,也就是將三個(gè)channel上的像素都取為它們的均值;另一個(gè)是對(duì)每張圖片都加上一個(gè)100以內(nèi)的整數(shù)。

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

"""
Data Augmen
"""
n_classes = y_train.max() + 1
n_train = X_train.shape[0]
# 第二種方法,先隨機(jī)生成X_train.shape[0]個(gè)正整數(shù)
bias = np.random.randint(0,100,(n_train,1,1,1))
# broadcasting,并保證范圍都在[0,255]以內(nèi)
X_train_aug0 = np.clip(X_train+bias, 0, 255).astype(np.uint8)
y_train_aug0 = y_train

# 沿各個(gè)圖片的channel取均值
# 本來為(34799, 32, 32, 3),取均值后為(34799, 3, 3, 1)
a = np.mean(X_train,axis=3,keepdims=True).astype(np.uint8)
# 將均值擴(kuò)到3個(gè)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)

# 添加到訓(xùn)練集,只使用了四分之一的新數(shù)據(jù)
# 現(xiàn)在訓(xù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訓(xùn)練、驗(yàn)證、測(cè)試集
    就使訓(xùn)練集均值為零后除以標(biāo)準(zhǔn)差
# 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

對(duì)測(cè)試、訓(xùn)練集做同樣的操作,保證分布一致:

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. 搭建網(wǎng)絡(luò)

接下來就是搭建網(wǎng)絡(luò)的過程了。網(wǎng)絡(luò)結(jié)構(gòu)為:

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和全連接層權(quán)值的建立

三層kernel分別為:16個(gè)3x3x3、32個(gè)3x3x16、64個(gè)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用于控制卷積層和全連接層是否訓(xùn)練,記不得當(dāng)時(shí)我為什么要控制這個(gè)了......
# regularizer是想傳入通過tf.contrib.layers.l2_regularizer()返回的函數(shù)來著。
# 將其用于regularize全連接層參數(shù),但是訓(xùn)練效果不好,最后就沒用
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,并設(shè)置了reuse=tf.AUTO_REUSE,配合tf.get_variable()達(dá)到共享變量的目標(biāo)。沒有變量時(shí)創(chuàng)建,有的話就調(diào)用創(chuàng)建好的。
        with tf.variable_scope('conv1', reuse=tf.AUTO_REUSE):
            # 第一個(gè)卷基層的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ù),則將對(duì)全連接層權(quán)值normalize的tensor加入集合'regularize'中。在cost函數(shù)中通過集合加入這些regularize項(xiàng)。
    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ù),則在集合中加個(gè)常量0就好。
        # 因?yàn)楹竺媸褂胻f.add_n(tf.get_collection('regularize'))來調(diào)出集合中的tensor
        # 若集合為空則會(huì)報(bào)錯(cuò)的。
        tf.add_to_collection('regularize', tf.constant(0., dtype=tf.float32))
    
    # 各個(gè)可訓(xùn)練的參數(shù)都在改字典中了
    # 搭建網(wǎng)絡(luò)結(jié)構(gòu)時(shí)調(diào)用就好
    return parameters

建立權(quán)值的函數(shù)寫好后,下面就是搭建網(wǎng)絡(luò)前向傳播的函數(shù)了:

其中關(guān)于tensorflow中的same padding和valid padding的介紹可看這里。

# x為建立的placeholder,為(None, 32, 32, 3)
# parameters就是上一函數(shù)的返回
# keep_prob用于控制各層dropout的概率。卷基層概率都設(shè)為1。
# 必須通過參數(shù)控制。
# 因?yàn)樵谟?xùn)練過程中和在測(cè)試網(wǎng)絡(luò)性能過程中其值不同。
# 返回的tensor logits為全連接的輸出,為(None, 43),沒有接softmax
# 因?yàn)楹竺媸褂玫氖莟f.nn.softmax_cross_entropy_with_logits()來構(gòu)造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))
# 因?yàn)閿?shù)據(jù)集中的label都為0-42的整數(shù),為減少工作(偷懶)
# 沒有對(duì)數(shù)據(jù)集的label轉(zhuǎn)為one-hot形式,而是這里通過調(diào)用tf.one_hot()來轉(zhuǎn)~~
# 另外,由于要在訓(xùn)練和測(cè)試中分別控制dropout的概率
# 所以將這個(gè)keep_prob也設(shè)為placeholder。

y = tf.placeholder(dtype=tf.int32, shape=(None))
keep_prob = tf.placeholder(dtype=tf.float32, shape=(5))
# one_hot_y為(None, 43),每一行只有一個(gè)1,其他為0.
one_hot_y = tf.one_hot(y, n_classes)

3. cost和優(yōu)化器

下面是一些參數(shù)的設(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()

# 訓(xùn)練的epoch和batch size
EPOCHS = 25
BATCH_SIZE = 128

# 初始學(xué)習(xí)率
rate = 0.005
# 下降率
decay_rate = 0.8
# 多少次一下降
decay_steps = 500
trainable = [True,True] 
lamb = 0.01

# 返回的regularizer是一個(gè)函數(shù),可以對(duì)輸入tensor計(jì)算正則項(xiàng)
regularizer = tf.contrib.layers.l2_regularizer(lamb)

# 調(diào)用上面的函數(shù)建立參數(shù)
parameters = initialize_parameters(trainable, regularizer=None)
# 前向傳播得到輸出
logits = LeNet_forwardpass(x, parameters, keep_prob)
# 對(duì)網(wǎng)絡(luò)的輸出softmax后與label計(jì)算cross entropy
# 返回的是各個(gè)example的cross entropy,下面要對(duì)其求平均
# 該API不再推薦了,推薦使用v2版本
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=one_hot_y, logits=logits)

# 求平均后,加上regularization項(xiàng),但這里別沒有
loss_operation = tf.reduce_mean(cross_entropy) + tf.add_n(tf.get_collection('regularize'))

# learning rate decay
# 這個(gè)global_step是一個(gè)不可訓(xùn)練的變量,需要傳給優(yōu)化器對(duì)象的minimize方法
# 在訓(xùn)練時(shí),每一次優(yōu)化都會(huì)對(duì)其加1
global_step = tf.Variable(0, trainable=False)
# 返回的為tensor,可看文檔,是對(duì)初始學(xué)習(xí)率decay的學(xué)習(xí)率tensor。
decay_rate = tf.train.exponential_decay(decay_rate=decay_rate, decay_steps=decay_steps, 
                                        global_step=global_step, learning_rate=rate)

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

在正式訓(xùn)練之前還要建立各評(píng)估函數(shù),用于每次訓(xùn)練完之后,調(diào)用以便看看模型在訓(xùn)練集及驗(yàn)證集上的準(zhǔn)確率。

4. 評(píng)估函數(shù)

思路很簡(jiǎn)單,對(duì)于網(wǎng)絡(luò)的輸出,找到最大值的索引,也就是該實(shí)例的類別,與其本身的label相比較,相等說明分類正確。然后計(jì)算分類正確的比例,也就是準(zhǔn)確率了。用到的API:

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

# 很簡(jiǎn)單,accuracy_operation也就是準(zhǔn)去率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))

# 建立個(gè)函數(shù)在Session中調(diào)用,方便
def evaluate(X_data, y_data):
    num_examples = len(X_data)
    total_accuracy = 0
    sess = tf.get_default_session()
    # 這里是按batch評(píng)估準(zhǔn)確率,最后計(jì)算總準(zhǔn)確率了
    # 這樣的話應(yīng)該運(yùn)行速度比一次計(jì)算整體數(shù)據(jù)集的準(zhǔn)確率快。
    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]
        # 因?yàn)槭菧y(cè)試模型準(zhǔn)確率,所以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對(duì)象用于訓(xùn)練完成后保存模型。
saver = tf.train.Saver()

5. 訓(xùn)練模型

訓(xùn)練的過程就是在batch上不斷調(diào)用優(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

# 記錄每次訓(xùn)練后batch上的cost值
cost = []
# 記錄訓(xùn)練完一個(gè)epoch后,在整個(gè)數(shù)據(jù)集上的cost。
cost_epoch = []
# keep_prob
# 訓(xùn)練時(shí)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ù)集。感覺弄一個(gè)數(shù)據(jù)集的索引列表,
        # shuffle該列表應(yīng)該快一些。
        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中不用擔(dān)心索引超出數(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)
        
        # 分別在訓(xùn)練、測(cè)試集上評(píng)估正確率。
        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()
        
    # 保存訓(xùn)練好的模型
    saver.save(sess, './model/model.ckpt')
    print("Model saved")

運(yùn)行上面的代碼,就開始訓(xùn)練模型了。從第十幾個(gè)epoch開始,在驗(yàn)證集上的正確率就穩(wěn)定在96.x%了。在訓(xùn)練集上都是99.9x%。過擬合還是很嚴(yán)重的,多使用些方法做data augment會(huì)降低過擬合。

注意,在退出上下文管理器tf.Session()后,該會(huì)話就關(guān)閉了,該會(huì)話所擁有的變量的值也就被釋放了。但是沒關(guān)系,已經(jīng)將訓(xùn)練好的模型保存到了文件中。

下面畫出的是列表cost,也就是每個(gè)batch后的cost值,可看到mini-batch gradient descent的特點(diǎn):

下面畫出的是列表cost_epoch,也就是每個(gè)epoch后的cost值:

6. 評(píng)估測(cè)試集

下面就要在測(cè)試集上評(píng)估模型的性能了。用到的API:
tf.train.Saver.restore()

with tf.Session() as sess:
    # restore的過程就相當(dāng)于將保存的訓(xùn)練好的模型加載到當(dāng)前graph中
    # 構(gòu)建好的結(jié)構(gòu)中。在這一過程中,各變量的值都被初始化為保存好的
    # 變量的值。
    # 也就不需要再調(diào)用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))

模型在測(cè)試集上的精度可達(dá)到0.943。

7. 觀看網(wǎng)絡(luò)狀態(tài)

下面的函數(shù)可以畫出對(duì)于某張圖片,網(wǎng)絡(luò)各層的輸出。將輸出形式化的顯示。只是最基本的顯示,只能對(duì)第一層卷積的輸出有比較好的觀察。

下面函數(shù)的一個(gè)輸入要求傳遞的是網(wǎng)絡(luò)中某一層(要看的那一層)激勵(lì)函數(shù)的輸出Tensor對(duì)象。但有個(gè)問題是,我是通過函數(shù)來搭建網(wǎng)絡(luò)結(jié)構(gòu)的,函數(shù)只是輸出了網(wǎng)絡(luò)輸出層的Tensor。對(duì)于在函數(shù)中使用的conv1、conv2等變量無法再引用了。

但這沒關(guān)系,因?yàn)榇罱ǖ倪@些Tensor和Operation都存在于Graph中,由于沒有顯式創(chuàng)建Graph對(duì)象,所以TensorFlow自動(dòng)創(chuàng)建了個(gè)default graph。通過查看tf.Graph文檔,可發(fā)現(xiàn)其有個(gè)get_operations()方法可以返回圖上所有的Operation對(duì)象。

但我們現(xiàn)在需要的是激勵(lì)函數(shù)創(chuàng)建的Tensor,能不能得到圖上所有的Tensor對(duì)象呢?很遺憾,沒有這樣的API。但是沒關(guān)系,找到了Operation,就可以找到其輸出的Tensor。因?yàn)門ensor對(duì)象的命名方式是 “op:num”,就是產(chǎn)生該Tensor的Operation名,后面接該Tensor來自O(shè)peration的第幾個(gè)輸出。

這就好辦了,可先通過tf.get_default_graph()得到默認(rèn)的圖對(duì)象,再通過tf.Graph.get_operations()得到圖上的所有Operation,因?yàn)樵谡{(diào)用激勵(lì)函數(shù)leaky_relu時(shí)我給了名字,比如'conv1_out',所以可以通過這個(gè)來查看。

但查找的時(shí)候發(fā)現(xiàn)和我預(yù)想的不一樣,沒有名字為'conv1_out'的Operation對(duì)象,但有三個(gè)類似的:

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

這三個(gè)應(yīng)該是在進(jìn)行l(wèi)eaky_relu運(yùn)算中的三個(gè)操作,我猜測(cè)找的應(yīng)該是<tf.Operation 'conv1_out/Maximum' type=Maximum>,因?yàn)樵趌eaky_relu運(yùn)算的最后一步就是比較大小。

所以對(duì)應(yīng)的Tensor對(duì)象名就為'conv1_out/Maximum:0',再使用tf.get_default_graph().get_tensor_by_name得到該Tensor對(duì)象,這樣的話可以使用下面代碼來得到該Tensor對(duì)象。

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

還有另一方法可得到該Tensor對(duì)象,因?yàn)?a target="_blank" rel="nofollow">tf.Operation類中有個(gè)屬性,outputs,保存的是該op輸出的Tensor對(duì)象列表。所以可以使用tf.get_default_graph().get_operation_by_name('conv1_out/Maximum')得到該Operation對(duì)象后,再訪問outputs屬性來得到目標(biāo)Tensor對(duì)象。即:

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

可見二者是一樣的。

說了這么多,終于到了顯示網(wǎng)絡(luò)狀態(tài)的函數(shù)了,其實(shí)很簡(jiǎn)單,就是在Session中,對(duì)網(wǎng)絡(luò)傳入圖片前向傳播,將某層激勵(lì)函數(shù)的輸出都當(dāng)做灰度圖像顯示。比如第一個(gè)卷積層輸出了16個(gè)channel,那么就將這16個(gè)channel都當(dāng)做灰度圖片顯示。代碼如下:

# image_input為四維(batch, img_weight, img_height, channel)
# tf_activation就是激勵(lì)函數(shù)輸出的Tensor
# activation_min、activation_max、plt_num是對(duì)matplotlib顯示圖片的控制,不用管,使用默認(rèn)值就好
# i指出使用image_input中的第幾個(gè)圖片
# 該函數(shù)要在Session會(huì)話中調(diào)用。
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]})
    # 得到激勵(lì)函數(shù)輸出的channel數(shù)
    featuremaps = activation.shape[3]
    plt.figure(plt_num, figsize=(15,5))
    for featuremap in range(featuremaps):
        # 我這里看的是第一層卷積的輸出,共有16個(gè)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")

調(diào)用該函數(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個(gè)channel顯示如下:

下面再給些其他圖片的結(jié)果:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容