論文位址:(V2)https://arxiv.org/abs/1502.03167v2
本文所包含代碼GitHub位址:https://github.com/shankezh/DL_HotNet_Tensorflow
如果對機器學習有興趣,不僅僅滿足将深度學習模型當黑盒模型使用的,想了解為何機器學習可以訓練拟合最佳模型,可以看我過往的部落格,使用數學知識推導了機器學習中比較經典的案例,并且使用了python撸了一套簡單的神經網絡的代碼架構用來加深了解:https://blog.csdn.net/shankezh/article/category/7279585
項目幫忙或工作機會請郵件聯系:[email protected]
資料集下載下傳位址:https://www.kaggle.com/c/cifar-10 or http://www.cs.toronto.edu/~kriz/cifar.html
更正,我還是用了inceptionv2複現了訓練。往下看即可 。
論文精華(V2)
關鍵資訊提取
1.提出Batch Normalization方法,用來加速網絡訓練;
2.文章把模型訓練難,調參難,初始化不友善等現象,統一稱作Internal Covariate Shift(内均方差偏移?),是以提出了一個強有力的方法就是在每一次訓練的小批次樣本的時候,對模型結構進行Normalization(規範化);
3.BN允許使用大一點的學習速率,且不用太在意初始化參數問題,也可以作為正則化項,并在一些情況下不需要使用dropout;
4.頂尖分類圖像模型中,使用BN使得模型訓練14次都少于原來的訓練步驟( 我覺得這裡應該是減少14倍的意思吧?),并且比原來的模型更加優秀;
5.使用組合的BN網絡,提升ImageNet分類結果達到 top-5 error 4.9%(val sets)和4.8%(test sets),超越了人類的水準;
6.介紹了SGD優化器,典型的湊字數...
7.網絡變深,訓練複雜,所有的前置層參數輸入都會産生影響,小的改變都會導緻網絡參數放大;
8.層輸入分布改變帶來一個問題,因為層需要連續不斷的适應新的分布,當輸入分布作用在一個學習系統中改變,根據經驗就叫做covariate shift.這種概念可以擴充到整個學習系統中,應用到部分中,如1個子網絡或者1個層;
9.固定輸入分布至子網落,将會有積極的結果對于子網落。
10.實踐中,飽和問題和梯度消失通常出現在使用ReLU,初始化,以及國小習速率上;進而确認非線性分布輸入會讓網絡訓練更穩定,優化器将更不可能卡在飽和狀态,訓練便會加速(重點);
11.為了白化輸入層,做了兩個必要的簡化:a)不将輸入和輸出層的特征進行白化,将會規範化每個具有依賴的縮放特征,通過對其做零均值和1的均方差;b)當使用小批次随機梯度訓練時,每個小批次都會生成均值和方差的估計值;
12.BN算法過程見Figure1,訓練階段;
13.激活規範化依賴在mini-batch,使其訓練高效,但其既不必要的也不适合在推理階段;
14.為了讓BN在推理階段也能用,那麼就需要産生BN變體來應對推理,具體算法過程見Figure2;
15.傳統深度網絡使用太高的學習速率容易梯度爆炸或者梯度消失,也容易困在局部極小值,BN就能幫助解決這個問題(重點);
16.通常大學習速率會增加層參數尺度,緊接着在反向傳播中放大梯度,使得模型爆炸;然而BN在反向傳播通過一層的時候是不受參數尺度影響的;BN(Wu) = BN((aW)u),尺度因子a,對輸入u和權重 W進行求導,會發現左右求導,求導u時候u不受a影響,求導W的時候,左邊乘上1/a等于右邊,這個時候放大的尺度a在BN變成了1/a,再和層參數的a相乘就變成了1,是以BN可以避免收到尺度影響;
17.通常使用dropout來防止過拟合,但使用了BN的網絡,我們發現可以移除dropout或者減少dropout的比例;
18.加速BN網絡:簡單添加BN并不會發揮所有效果,是以需要跟随以下步驟進行:a)增加學習速率,BN不怕大學習速率帶來的問題;b)移除Dropout;c)減少L2權重正則化;d)加速學習速率的衰減(因為訓練太快了);e)移除LRN(BN更強,還要啥自行車);f)盡可能的打亂訓練資料;g)減少光照扭曲(訓練太快了,是以希望用更真實的圖檔來訓練);
結構細節
1.驗證時使用了LeCun的MNIST資料集,28x28的二進制圖像作為輸入,3層全連接配接,每一層為100激活數,使用sigmoid計算含隐層,權重w使用高斯随機分布初始化;最後一個全連接配接使用了10激活數(總共就10類),交叉熵做誤差函數;跑了5w步,每個mini-batch為60個圖像;BN添加在每一個隐藏層;比較了原始網絡和BN網絡,而非實作最佳性能在MNIST上,對比情況在下文的Figure3;
2.應用BN到Inception變體網路,訓練ImageNet分類任務,卷積層使用ReLU,主要不同是網絡中的5x5卷積全被2個3x3使用128個濾波器替代,網絡共包含136w個參數,頂部使用softmax層(老外喜歡從下往上看),沒有全連接配接;更多細節在附錄中;
3.訓練使用momentum;mini-batch size為32;
4.對每張圖使用了剪裁;
5.論文附錄給出了inceptionv2的結構:5x5卷積被2個3x3代替,這将會導緻增加9個權重層;28x28的inception子產品從2個變成3個;有一部分沒有使用池化層在兩個Inception子產品中間,但使用了Stride為2的卷積/池化在3c,4e中;模型使用了稀疏卷積層,depth_multiplier為8在第一個卷積層上;具體結構圖看圖Figure4.;
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs8GcYplc1cVWv50MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2UTOxQTMwUTMwIzMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
Figure1.BN的算法
其中,γ和β是需要學習的參數,x是mini-batch中的值,可以清晰的看到過程,計算均值,方差,标準化,縮放和偏移;
Figure2.推理BN算法,BNtr表示是訓練時候的BN,BNinf表示的是推理的BN,訓練的是時候統計了BN均值和方差,通過統計結果最後求出了全局的方差,訓練的時候求出了γ和β,推理的時候直接拿來用;
Figure3.使用BN和不使用BN的對比,bc對比展示了輸入分布和内均方差漂移情況。
Figure5. InceptionV2結構圖
Tensorflow代碼實作
說明
使用Tensorflow搭建論文網絡,搭建過程遵循論文原意,并且确認google官方給出的IncetpionV2的代碼與我的差別,基本一緻,由于論文中結果網絡圖太長,請大家自行觀察論文去看;
代碼
模型
InceptionV2.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Created by Inception on 19-1-23
import tensorflow as tf
import tensorflow.contrib.slim as slim
def inception_moudle_v2(net,scope,filters_num,pool_type,stride):
with tf.variable_scope(scope):
if filters_num[0] != 0:
with tf.variable_scope('bh1'):
bh1 = slim.conv2d(net,filters_num[0],1,stride=stride,scope='bh1_conv1_1x1')
with tf.variable_scope('bh2'):
bh2 = slim.conv2d(net,filters_num[1],1,stride=1,scope='bh2_conv1_1x1')
bh2 = slim.conv2d(bh2,filters_num[2],3,stride=stride,scope='bh2_conv2_3x3')
with tf.variable_scope('bh3'):
bh3 = slim.conv2d(net,filters_num[3],1,stride=1,scope='bh3_conv1_1x1')
bh3 = slim.conv2d(bh3,filters_num[4],3,stride=1,scope='bh3_conv2_3x3')
bh3 = slim.conv2d(bh3,filters_num[5],3,stride=stride,scope='bh3_conv3_3x3')
with tf.variable_scope('bh4'):
if pool_type == 'avg':
bh4 = slim.avg_pool2d(net,3,stride=stride,scope='bh4_avg_3x3')
elif pool_type == 'max':
bh4 = slim.max_pool2d(net,3,stride=stride, scope='bh4_max_3x3')
else:
raise TypeError("沒有此參數類型(params valid)")
if filters_num[0] != 0:
bh4 = slim.conv2d(bh4,filters_num[6],1,stride=1,scope='bh4_conv_1x1')
net = tf.concat([bh1,bh2,bh3,bh4],axis=3)
else:
net = tf.concat([bh2,bh3,bh4],axis=3)
return net
def V2_slim(inputs, num_cls, keep_prob=0.8,is_training = False, spatital_squeeze = True):
batch_norm_params = {
'decay': 0.998,
'epsilon': 0.001,
'scale': False,
'updates_collections': tf.GraphKeys.UPDATE_OPS,
'is_training': is_training
}
net = inputs
with tf.name_scope('reshape'):
net = tf.reshape(net,[-1,224,224,3])
with tf.variable_scope('GoogLeNet_V2'):
with slim.arg_scope(
[slim.conv2d,slim.separable_conv2d],
weights_initializer=slim.xavier_initializer(),
normalizer_fn= slim.batch_norm,
normalizer_params= batch_norm_params,
# normalizer_fn = tf.layers.batch_normalization,
# normalizer_params = params
):
with slim.arg_scope(
[slim.conv2d,slim.max_pool2d,slim.avg_pool2d,slim.separable_conv2d],
stride=1,
padding='SAME'
):
with slim.arg_scope([slim.batch_norm],**batch_norm_params):
net = slim.separable_conv2d(net, 64, 7, depth_multiplier=8, stride=2,
weights_initializer=slim.xavier_initializer(), scope='layer1')
net = slim.max_pool2d(net, 3, stride=2, padding='SAME', scope='layer2')
net = slim.conv2d(net, 64, 1, stride=1, padding='SAME', scope='layer3')
net = slim.conv2d(net, 192, 3, stride=1, padding='SAME', scope='layer4')
net = slim.max_pool2d(net, 3, stride=2, padding='SAME', scope='layer5')
net = inception_moudle_v2(net,scope='layer6_3a',filters_num=[64, 64, 64, 64, 96, 96, 32],pool_type='avg',stride=1)
net = inception_moudle_v2(net,scope='layer9_3b',filters_num=[64, 64, 96, 64, 96, 64, 64],pool_type='avg',stride=1)
net = inception_moudle_v2(net,scope='layer12_3c',filters_num=[0, 128,160,64, 96, 96],pool_type='max',stride=2)
net = inception_moudle_v2(net,scope='layer15_4a',filters_num=[224,64,96,96,128,128,128],pool_type='avg',stride=1)
net = inception_moudle_v2(net,scope='layer18_4b',filters_num=[192,96,128,96,128,128,128],pool_type='avg',stride=1)
net = inception_moudle_v2(net,scope='layer21_4c',filters_num=[160,128,160,128,160,160,128],pool_type='avg',stride=1)
net = inception_moudle_v2(net,scope='layer24_4d',filters_num=[96,128,192,160,192,192,128],pool_type='avg',stride=1)
net = inception_moudle_v2(net,scope='layer27_4e',filters_num=[0,128,192,192,256,256],pool_type='max',stride=2)
net = inception_moudle_v2(net,scope='layer30_5a',filters_num=[352,192,320,160,224,224,128],pool_type='avg',stride=1)
net = inception_moudle_v2(net,scope='layer33_5b',filters_num=[352,192,320,192,224,224,128],pool_type='max',stride=1)
net = slim.avg_pool2d(net,7,stride=1,padding='VALID',scope="layer36_avg")
net = slim.dropout(net,keep_prob=keep_prob,scope="dropout")
net = slim.conv2d(net,num_cls,1,activation_fn=None,normalizer_fn=None,scope="layer37")
if spatital_squeeze:
net = tf.squeeze(net,[1,2],name='squeeze')
net = slim.softmax(net,scope="softmax")
return net
在以上代碼中的net = inception_moudle_v2(net,scope='layer9_3b',filters_num=[64, 64, 96, 64, 96, 64, 64],pool_type='avg',stride=1) ,其中參數填寫按照論文,應改為net = inception_moudle_v2(net,scope='layer9_3b',filters_num=[64, 64, 96, 64, 96, 96, 64],pool_type='avg',stride=1),屬于當時寫代碼的時候,看圖看差了。多謝小生gogogo同學提醒
訓練
import tensorflow as tf
import coms.utils as utils
import coms.pre_process as pre_pro
import coms.coms as coms
import net.GoogLeNet.InceptionV2 as InceptionV2
import coms.learning_rate as LR_Tools
import time
import cv2
import numpy as np
import os
def run():
model_dir = ''
logdir = ''
img_prob = [224, 224, 3]
num_cls = 10
is_train = False
is_load_model = False
is_stop_test_eval = True
BATCH_SIZE = 100
EPOCH_NUM = 150
ITER_NUM = 500 # 50000 / 100
LEARNING_RATE_VAL = 0.001
if utils.isLinuxSys():
logdir = r''
model_dir = r''
else:
model_dir = r'D:\DataSets\cifar\cifar\model_flie\inceptionv2'
logdir = r'D:\DataSets\cifar\cifar\logs\train\inceptionv2'
if is_train:
train_img_batch, train_label_batch = pre_pro.get_cifar10_batch(is_train = True, batch_size=BATCH_SIZE, num_cls=num_cls,img_prob=[224,224,3])
test_img_batch, test_label_batch = pre_pro.get_cifar10_batch(is_train=False,batch_size=BATCH_SIZE,num_cls=num_cls,img_prob=[224,224,3])
inputs = tf.placeholder(tf.float32,[None, img_prob[0], img_prob[1], img_prob[2]])
labels = tf.placeholder(tf.float32,[None, num_cls])
is_training = tf.placeholder(tf.bool)
LEARNING_RATE = tf.placeholder(tf.float32)
calc_lr = LR_Tools.CLR_EXP_RANGE()
# layer_batch_norm_params = {
# 'training': is_training
# }
logits = InceptionV2.V2_slim(inputs, num_cls, is_training=is_training)
train_loss = coms.loss(logits,labels)
train_optim = coms.optimizer_bn(lr=LEARNING_RATE,loss=train_loss)
train_eval = coms.evaluation(logits,labels)
saver = tf.train.Saver(max_to_keep=4)
max_acc = 0.
config = tf.ConfigProto(allow_soft_placement=True)
with tf.Session(config=config) as sess:
if utils.isHasGpu():
dev = '/gpu:0'
else:
dev = '/cpu:0'
with tf.device(dev):
sess.run(tf.global_variables_initializer())
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess= sess, coord=coord)
try:
if is_train:
if is_load_model:
ckpt = tf.train.get_checkpoint_state(model_dir)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess,ckpt.model_checkpoint_path)
print('model load successful ...')
else:
print('model load failed ...')
return
n_time = time.strftime("%Y-%m-%d %H-%M", time.localtime())
logdir = os.path.join(logdir, n_time)
writer = tf.summary.FileWriter(logdir, sess.graph)
for epoch in range(EPOCH_NUM):
if coord.should_stop():
print('coord should stop ...')
break
for step in range(1,ITER_NUM+1):
if coord.should_stop():
print('coord should stop ...')
break
script_kv = utils.readFile('train_script')
if script_kv != None:
if 'iter' in script_kv.keys():
# 找到對應step,準備儲存模型參數,或者修改學習速率
if int(script_kv['iter']) == step:
if 'lr' in script_kv.keys():
LEARNING_RATE_VAL = float(script_kv['lr'])
print('read train_scrpit file and update lr to {}'.format(LEARNING_RATE_VAL))
if 'save' in script_kv.keys():
saver.save(sess,model_dir + '/' + 'cifar10_{}_step_{}.ckpt'.format(str(epoch),str(step)),global_step=step)
print('read train_script file and save model successful ...')
LEARNING_RATE_VAL = calc_lr.calc_lr(step,ITER_NUM,0.001,0.01,gamma=0.9998)
# LEARNING_RATE_VAL = coms.clr(step,2*ITER_NUM,0.001,0.006)
batch_train_img, batch_train_label = sess.run([train_img_batch,train_label_batch])
_, batch_train_loss, batch_train_acc = sess.run([train_optim,train_loss,train_eval],feed_dict={inputs:batch_train_img,
labels:batch_train_label,
LEARNING_RATE:LEARNING_RATE_VAL,
is_training:is_train})
global_step = int(epoch * ITER_NUM + step + 1)
print("epoch %d , step %d train end ,loss is : %f ,accuracy is %f ... ..." % (epoch, step, batch_train_loss, batch_train_acc))
train_summary = tf.Summary(value=[tf.Summary.Value(tag='train_loss',simple_value=batch_train_loss)
,tf.Summary.Value(tag='train_batch_accuracy',simple_value=batch_train_acc)
,tf.Summary.Value(tag='learning_rate',simple_value=LEARNING_RATE_VAL)])
writer.add_summary(train_summary,global_step)
writer.flush()
if is_stop_test_eval:
if not is_load_model:
if epoch < 3:
continue
if step % 100 == 0:
print('test sets evaluation start ...')
ac_iter = int(10000/BATCH_SIZE) # cifar-10測試集數量10000張
ac_sum = 0.
loss_sum = 0.
for ac_count in range(ac_iter):
batch_test_img, batch_test_label = sess.run([test_img_batch,test_label_batch])
test_loss, test_accuracy = sess.run([train_loss,train_eval],feed_dict={inputs:batch_test_img,
labels:batch_test_label,
is_training:False})
ac_sum += test_accuracy
loss_sum += test_loss
ac_mean = ac_sum / ac_iter
loss_mean = loss_sum / ac_iter
print('epoch {} , step {} , accuracy is {}'.format(str(epoch),str(step),str(ac_mean)))
test_summary = tf.Summary(
value=[tf.Summary.Value(tag='test_loss', simple_value=loss_mean)
, tf.Summary.Value(tag='test_accuracy', simple_value=ac_mean)])
writer.add_summary(test_summary,global_step=global_step)
writer.flush()
if ac_mean >= max_acc:
max_acc = ac_mean
saver.save(sess, model_dir + '/' + 'cifar10_{}_step_{}.ckpt'.format(str(epoch),str(step)),global_step=step)
print('max accuracy has reaching ,save model successful ...')
# print('saving last model ...')
# saver.save(sess, model_dir + '/' + 'cifar10_last.ckpt')
print('train network task was run over')
else:
model_file = tf.train.latest_checkpoint(model_dir)
saver.restore(sess, model_file)
cls_list = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship',
'truck']
for i in range(1, 11):
name = str(i) + '.jpg'
img = cv2.imread(name)
img = cv2.resize(img, (32, 32))
img = cv2.resize(img, (224, 224))
# img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img / 255.
img = np.array([img])
res = sess.run(logits, feed_dict={inputs:img , is_training:False})
# print(res)
print('{}.jpg detect result is : '.format(str(i)) + cls_list[np.argmax(res)] )
except tf.errors.OutOfRangeError:
print('done training -- opoch files run out of ...')
finally:
coord.request_stop()
coord.join(threads)
sess.close()
if __name__ == '__main__':
np.set_printoptions(suppress=True)
run()
結果
複現訓練使用了cifar10資料集,由于inceptionv2的網絡結構,無法直接使用32x32像素的資料,是以将其轉換成了224x224的大小,這也是論文原文使用的尺寸,但由于cifar10源資料隻有32x32的分辨率,是以轉換成224x224的效果後,依舊十分模糊,訓練出來的模型,對于高清像素轉換成的224x224的資料集合效果并不是很好,是以我在檢測網上下下來的十張圖檔時,先resize成32x32,然後在resize成224x224,這樣檢測分類的效果會比直接resize成224x224的效果好很多,原因其實也很簡單,卷積核學到的特征就是32x32變成224x224的模糊圖檔上的特征,學出來的就是馬賽克形式,是以更擅長檢測馬賽克圖檔;
BN的配置寫在了arg_score()中,normalizer_fn= slim.batch_norm是配置batch_norm, normalizer_params= batch_norm_params是配置BN的參數,注意裡面的is_training,訓練的時候要設定為True,測試的時候要設定為False,本次訓練的最高準确率到達了79.9%;
以下是百度上下載下傳的十張圖檔:
檢測結果:
可以看到,十張錯了3張,對了7張;
同時我對比了使用BN和不适用BN的訓練過程:
不使用BN的loss,如下圖:
不使用BN的正确率曲線,如下圖:
使用BN的loss,如下圖,:
使用BN的正确率,如下圖:
可以看到結果,不使用BN時候,訓練剛開始沒有任何效果,5K後才開始慢慢下降,正确率也提升較慢,而使用了BN的網絡結構,在訓練剛開始就下降很快,并且隻花了15K左右就到達了不用BN的50K之後的效果:
結論:
BN确實是一種非常有用的訓練手段,将訓練過程的離散的資料重新進行了超平面映射,使其變得更容易進行拟合,同時去除了全連接配接的inception結構,使得模型參數變得非常小,這樣就更加有在嵌入式裝置應用的前景了。
至此,InceptionV2複現完畢;
訓練模型已經上傳至百度網盤,GitHub可以下到我的源碼,位址在最上部分,感興趣的可以下載下傳學習,或者試用;
權重檔案,百度雲位址為連結:連結:https://pan.baidu.com/s/1BdMZYvkiYT9Fts0dLIgrog
提取碼:0rmi