1、殘差
殘差在數(shù)理統(tǒng)計中是指實際觀察值與估計值(擬合值)之間的差。在集成學(xué)習(xí)中可以通過基模型擬合殘差,使得集成的模型變得更精確;在深度學(xué)習(xí)中也有人利用layer去擬合殘差將深度神經(jīng)網(wǎng)絡(luò)的性能提高變強。這里筆者選了Gradient Boosting和Resnet兩個算法試圖讓大家更感性的認識到擬合殘差的作用機理。
2、Gradient Boosting
Gradient Boosting模型大致可以總結(jié)為三部:
訓(xùn)練一個基學(xué)習(xí)器Tree_1(這里采用的是決策樹)去擬合data和label。
接著訓(xùn)練一個基學(xué)習(xí)器Tree_2,輸入時data,輸出是label和上一個基學(xué)習(xí)器Tree_1的預(yù)測值的差值(殘差),這一步總結(jié)下來就是使用一個基學(xué)習(xí)器學(xué)習(xí)殘差。
最后把所有的基學(xué)習(xí)器的結(jié)果相加,做最終決策。
下方代碼僅僅做了3步的殘差擬合,最后一步就是體現(xiàn)出集成學(xué)習(xí)的特征,將多個基學(xué)習(xí)器組合成一個組合模型。
其實上方代碼就等價于調(diào)用sklearn中的GradientBoostingRegressor這個集成學(xué)習(xí)API,同時將基學(xué)習(xí)器的個數(shù)n_estimators設(shè)為3。
from sklearn.ensemble import GradientBoostingRegressorgbrt = GradientBoostingRegressor(max_depth=2, n_estimators=3, learning_rate=1.0)gbrt.fit(X, y)
形象的理解Gradient Boosting,其的過程就像射箭多次射向同一個箭靶,上一次射的偏右,下一箭就會盡量偏左一點,就這樣慢慢調(diào)整射箭的位置,使得箭的位置和靶心的偏差變小,最終射到靶心。這也是boosting的集成方式會減小模型bias的原因。
殘差網(wǎng)絡(luò)的作用:
(1)為什么殘差學(xué)習(xí)的效果會如此的好?與其他論文相比,深度殘差學(xué)習(xí)具有更深的網(wǎng)絡(luò)結(jié)構(gòu),此外,殘差學(xué)習(xí)也是網(wǎng)絡(luò)變深的原因?為什么網(wǎng)絡(luò)深度如此的重要?
解:一般認為神經(jīng)網(wǎng)絡(luò)的每一層分別對應(yīng)于提取不同層次的特征信息,有低層,中層和高層,而網(wǎng)絡(luò)越深的時候,提取到的不同層次的信息會越多,而不同層次間的層次信息的組合也會越多。
(2)為什么在殘差之前網(wǎng)絡(luò)的深度最深的也只是GoogleNet 的22 層, 而殘差卻可以達到152層,甚至1000層?
解:深度學(xué)習(xí)對于網(wǎng)絡(luò)深度遇到的主要問題是梯度消失和梯度爆炸,傳統(tǒng)對應(yīng)的解決方案則是數(shù)據(jù)的初始化(normlized initializatiton)和(batch normlization)正則化,但是這樣雖然解決了梯度的問題,深度加深了,卻帶來了另外的問題,就是網(wǎng)絡(luò)性能的退化問題,深度加深了,錯誤率卻上升了,而殘差用來設(shè)計解決退化問題,其同時也解決了梯度問題,更使得網(wǎng)絡(luò)的性能也提升了。
殘差網(wǎng)絡(luò)的基本結(jié)構(gòu):
將輸入疊加到下層的輸出上。對于一個堆積層結(jié)構(gòu)(幾層堆積而成)當(dāng)輸入為x時其學(xué)習(xí)到的特征記為H(x),現(xiàn)在我們希望其可以學(xué)習(xí)到殘差F(x)=H(x)-x,這樣其實原始的學(xué)習(xí)特征是F(x)+x 。當(dāng)殘差為0時,此時堆積層僅僅做了恒等映射,至少網(wǎng)絡(luò)性能不會下降,實際上殘差不會為0,這也會使得堆積層在輸入特征基礎(chǔ)上學(xué)習(xí)到新的特征,從而擁有更好的性能。
對著下方代碼我們可以更清晰的看到residual block的具體操作:
輸入x,
將x通過三層convolutiaon層之后得到輸出m,
將原始輸入x和輸出m加和。
就得到了residual block的總輸出,整個過程就是通過三層convolutiaon層去擬合residual block輸出與輸出的殘差m。
在resnet中殘差的思想就是去掉相同的主體部分,從而突出微小的變化,讓模型集中注意去學(xué)習(xí)一些這些微小的變化部分。這和我們之前討論的Gradient Boosting中使用一個基學(xué)習(xí)器去學(xué)習(xí)殘差思想幾乎一樣。
3、slim庫
要學(xué)習(xí)殘差網(wǎng)絡(luò),先學(xué)習(xí)slim庫的用法。
首先讓我們看看tensorflow怎么實現(xiàn)一個層,例如卷積層:
input = ...with tf.name_scope('conv1_1') as scope: kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32, stddev=1e-1), name='weights') conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME') biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32), trainable=True, name='biases') bias = tf.nn.bias_add(conv, biases) conv1 = tf.nn.relu(bias, name=scope)
然后slim的實現(xiàn):
但這個不是重要的,因為tenorflow目前也有大部分層的簡單實現(xiàn),這里比較吸引人的是slim中的repeat和stack操作:
假設(shè)定義三個相同的卷積層:
net = ...net = slim.conv2d(net, 256, [3, 3], scope='conv3_1')net = slim.conv2d(net, 256, [3, 3], scope='conv3_2')net = slim.conv2d(net, 256, [3, 3], scope='conv3_3')net = slim.max_pool2d(net, [2, 2], scope='pool2')
在slim中的repeat操作可以減少代碼量:
而stack是處理卷積核或者輸出不一樣的情況:
假設(shè)定義三層FC:
x = slim.fully_connected(x, 32, scope='fc/fc_1')x = slim.fully_connected(x, 64, scope='fc/fc_2')x = slim.fully_connected(x, 128, scope='fc/fc_3')
使用stack操作:
同理卷積層也一樣:
# 普通方法:x = slim.conv2d(x, 32, [3, 3], scope='core/core_1')x = slim.conv2d(x, 32, [1, 1], scope='core/core_2')x = slim.conv2d(x, 64, [3, 3], scope='core/core_3')x = slim.conv2d(x, 64, [1, 1], scope='core/core_4') # 簡便方法:slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')
采用如上方法,定義一個VGG也就十幾行代碼的事了。
這個沒什么好說的,說一下直接拿經(jīng)典網(wǎng)絡(luò)來訓(xùn)練吧。
import tensorflow as tfvgg = tf.contrib.slim.nets.vgg# Load the images and labels.images, labels = ...# Create the model.predictions, _ = vgg.vgg_16(images)# Define the loss functions and get the total loss.loss = slim.losses.softmax_cross_entropy(predictions, labels)
【注】slim的卷積層默認是SAME模式的padding,這也就意味著卷積之后和卷積之前的大小相同。殘差網(wǎng)絡(luò)恰好需要這個特性。
4、殘差網(wǎng)絡(luò)模型
編寫殘差網(wǎng)絡(luò)單元
5、訓(xùn)練網(wǎng)絡(luò)
import tensorflow as tfimport tensorflow.contrib.slim as slimfrom scrips import configfrom scrips import resUnitfrom scrips import read_tfrecordfrom scrips import convert2onehotimport numpy as np log_dir = config.log_dirmodel_dir = config.model_dirIMG_W = config.IMG_WIMG_H = config.IMG_HIMG_CHANNELS = config.IMG_CHANNELSNUM_CLASSES = config.NUM_CLASSESBATCH_SIZE = config.BATCH_SIZE tf.reset_default_graph() X_input = tf.placeholder(shape=[None,IMG_W,IMG_H,IMG_CHANNELS],dtype=tf.float32,name='input')y_label = tf.placeholder(shape=[None,NUM_CLASSES],dtype=tf.int32) #*****************************************************************************************************output = resUnit.resnet(X_input,3,64,NUM_CLASSES,5)#***************************************************************************************************** #loss and accuracyloss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_label, logits=output))train_step = tf.train.AdamOptimizer(config.lr).minimize(loss)accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(y_label, 1), tf.argmax(output, 1)), tf.float32)) #tensor boardtf.summary.scalar('loss',loss)tf.summary.scalar('accuracy',accuracy) #從tfrecord中讀取數(shù)據(jù)及對應(yīng)的標(biāo)簽image, label = read_tfrecord.read_and_decode(config.tfrecord_dir,IMG_W,IMG_H,IMG_CHANNELS)image_batches, label_batches = tf.train.shuffle_batch([image, label], batch_size=BATCH_SIZE, capacity=2000,min_after_dequeue=1000) #訓(xùn)練網(wǎng)絡(luò)init = tf.global_variables_initializer()saver = tf.train.Saver() with tf.Session() as sess: ckpt = tf.train.get_checkpoint_state(model_dir)#從中間模型加載權(quán)重 if ckpt and ckpt.model_checkpoint_path: print('Restore model from ',end='') print(ckpt.model_checkpoint_path) saver.restore(sess,ckpt.model_checkpoint_path) if (ckpt.model_checkpoint_path.split('-')[-1]).isdigit(): global_step = int(ckpt.model_checkpoint_path.split('-')[-1]) print('Restore step at #',end='') print(global_step) else: global_step = 0 else: global_step = 0 sess.run(init) tensor_board_writer = tf.summary.FileWriter(log_dir,tf.get_default_graph()) merged = tf.summary.merge_all() #sess.graph.finalize() threads = tf.train.start_queue_runners(sess=sess) while True: try: global_step += 1 X_train, y_train = sess.run([image_batches, label_batches]) y_train_onehot = convert2onehot.one_hot(y_train,NUM_CLASSES) feed_dict = {X_input: X_train, y_label: y_train_onehot} [_, temp_loss, temp_accuracy,summary] = sess.run([train_step, loss, accuracy,merged], feed_dict=feed_dict) tensor_board_writer.add_summary(summary,global_step) if global_step % config.display == 0: print('step at #{},'.format(global_step), end=' ') print('train loss: {:.5f}'.format(temp_loss), end=' ') print('train accuracy: {:.2f}%'.format(temp_accuracy * 100)) if global_step % config.snapshot== 0: saver.save(sess,model_dir+'/model.ckpt',global_step) except: tensor_board_writer.close() break;