1. 程式人生 > >教你用TensorFlow搭建AlexNet

教你用TensorFlow搭建AlexNet

勿滿足於眼前的小小成就。你要問自己,我這輩子只有這樣嗎。

AlexNet模式是在2012年提出來的,並且在當年獲得了ILSVRC的冠軍,top-5的錯誤率為16.4%,比第二名的成績26.2%高出許多,也從此開始確立了深度學習在計算機視覺的統治地位,是一塊重要的里程碑。

該模型包含了幾個比較新的技術點:

  1. 成功使用ReLu作為CNN的啟用函式,驗證了其效果在較深網路中比Sigmoid更好
  2. 成功應用Dropout,驗證了其在實際中的效果
  3. 在CNN中使用重疊的最大池化,打破了之前普遍使用平均池化的模式,AlexNet全部使用最大池化,避免平均池化的模糊化效果,並提出了讓步長比池化核尺寸小一些可以提升特徵的豐富性
  4. 提出了LRN層,對區域性神經元的活動建立競爭機制,使其中響應大的更大,小的更小,增強了模型的泛化能力
  5. 使用CUDA加速深度卷積網路的訓練
  6. 使用了資料增強,使用了PCA

接下來將會使用TensorFlow搭建AlexNet,使用的資料為隨機圖片資料,如果你有興趣可以自行下載ImageNet的資料進行訓練兵測試

首先匯入常用庫並定義batch_size,num_batches

from datetime import datetime
import math
import time
import tensorflow as tf

batch_size = 32
num_batches = 100

定義一個用於展示網路層結構的函式

def print_activations(t):
    print(t.op.name, ' ', t.get_shape().as_list())

定義一個函式inference,接受images為引數,返回第五個池化層和所有需要訓練的模型引數

首先定義第一個卷積層,使用tf.truncated_normal截斷的正太分佈函式初始化卷積核的引數kernel,卷積核的尺寸為11X11,顏色通道為3,卷積核的數量為64。使用tf.nn.conv2d對輸入的images進行卷積操作,將strides步長設定為4X4,padding模式設定為SAME,將卷積核的biases全部初始化為0,再使用tf.nn.bias_ass將conv和biases加起來,並使用啟用函式tf.nn.relu對結果進行非線性處理,最後使用開始定義的print_activations將結構打印出來,將可訓練的引數新增到parameters中。

def inference(images):
    parameters = []
    with tf.name_scope('conv1') as scope:
        kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64], dtype=tf.float32, stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32), trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv1 = tf.nn.relu(bias, name=scope)
        print_activations(conv1)
        parameters += [kernel, biases]

在第一層卷積後新增LRN層和最大池化層,先使用tf.nn.lrn對前面輸出的tensor進行LRN處理,將depth_radius設為4,bias設為1,alpha設為0.001/9,beta設為0.75。對前面的輸出進行最大池化,池化尺寸為3X3,padding模式設為VALID。

    lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, alpha=0.001 / 9, beta=0.75, name='lrn1')
    pool1 = tf.nn.max_pool(lrn1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID', name='pool1')
    print_activations(pool1)

定義後面的卷積層和池化層,步驟大體和前面相似,最後返回第五層池化和需要訓練的引數,正式使用時需要自行新增3個全連線層。

    with tf.name_scope('conv2') as scope:
        kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32, stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[192], dtype=tf.float32), trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv2 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
    print_activations(conv2)

    lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9, beta=0.75, name='lrn2')
    pool2 = tf.nn.max_pool(lrn2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID', name='pool2')
    print_activations(pool2)

    with tf.name_scope('conv3') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384], dtype=tf.float32, stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32), trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv3 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv3)

    with tf.name_scope('conv4') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256], dtype=tf.float32, stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32), trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv4 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv4)

    with tf.name_scope('conv5') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256], dtype=tf.float32, stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[256], dtype=tf.float32), trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv5 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
        print_activations(conv5)

    pool5 = tf.nn.max_pool(conv5, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID', name='pool5')
    print_activations(pool5)
    return pool5, parameters

定義一個評估AlexNet每輪計算時間的函式time_tensorflow_run,該函式的第一個輸入是TensorFlow的Session,第二個變數是需要評測的運算運算元,第三個變數是測試的名稱。num_strps_burn_in的作用是給程式熱身,前幾輪迭代有視訊記憶體載入、cache命中等因素所以跳過。迴圈結束後計算每輪迭代的平均耗時和標準差,列印輸出。

def time_tensorflow_run(session, target, info_string):
    num_steps_burn_in = 10
    total_duration = 0.0
    total_duration_squard = 0.0

    for i in range(num_batches + num_steps_burn_in):
        start_time = time.time()
        _ = session.run(target)
        duration = time.time() - start_time
        if i >= num_steps_burn_in:
            if not i % 10:
               print('%s:step %d,duration=%.3f'%(datetime.now(),1-num_steps_burn_in,duration))
               total_duration+=duration
               total_duration_squard+=duration*duration

    mn=total_duration/num_batches
    vr=total_duration_squard/num_batches-mn*mn
    sd=math.sqrt(vr)
    print('%s: %s across %d steps,%.3f +/- %.3f sec/batch'%(datetime.now(),info_string,num_batches,mn,sd))

定義主函式,首先使用with.Graph().as_default()定義預設的Graph方便後面使用,使用tf.random_normal函式構造正態分佈的隨機tensor代替輸入的圖片資料,使用之前定義的inference構建整個AlexNet網路,使用tf.Session()建立新的Session並初始化所有引數。然後進行forword計算的評測,使用tf.nn.l2_loss計算pool5d的loss,再使用tf.gardients求相對於loss的所有模型引數的梯度。

最後執行主函式。

def run_benchmark():
    with tf.Graph().as_default():
        image_size=224
        images=tf.Variable(tf.random_normal([batch_size,image_size,image_size,3],dtype=tf.float32,stddev=1e-1))
        pool5,parameters=inference(images)
        init=tf.global_variables_initializer()
        sess=tf.Session()
        sess.run(init)
        time_tensorflow_run(sess,pool5,'Forward')
        objective=tf.nn.l2_loss(pool5)
        grad=tf.gradients(objective,parameters)
        time_tensorflow_run(sess,grad,'Forward-backward')

run_benchmark()

執行程式會首先打印出AlexNet的網路結構,然後會打印出每輪的迭代時間消耗

conv1   [32, 56, 56, 64]
pool1   [32, 27, 27, 64]
conv2   [32, 27, 27, 192]
pool2   [32, 13, 13, 192]
conv3   [32, 13, 13, 384]
conv4   [32, 13, 13, 256]
conv5   [32, 13, 13, 256]
pool5   [32, 6, 6, 256]
2018-12-12 14:08:11.891345:step -9,duration=0.525
2018-12-12 14:08:16.898325:step -9,duration=0.501
2018-12-12 14:08:21.928768:step -9,duration=0.508