姓名:王咫毅
學(xué)號:19021211150
【嵌牛導(dǎo)讀】深度學(xué)習(xí)應(yīng)用到實(shí)際問題中,一個非常大的問題在于訓(xùn)練深度學(xué)習(xí)模型需要的計(jì)算量太大棍矛。為了加速訓(xùn)練過程蕴掏,本章將介紹如何通過TensorFlow利用GPU或/和分布式計(jì)算進(jìn)行模型訓(xùn)練门坷。
【嵌牛鼻子】計(jì)算加速 tensorflow 機(jī)器學(xué)習(xí)
【嵌牛提問】如何使用tensorflow進(jìn)行計(jì)算機(jī)加速默勾?
【嵌牛正文】
轉(zhuǎn)載自:http://www.reibang.com/p/911ce4d180cd
然而要將深度學(xué)習(xí)應(yīng)用到實(shí)際問題中,一個非常大的問題在于訓(xùn)練深度學(xué)習(xí)模型需要的計(jì)算量太大乍恐。比如要將前面介紹的Inception-v3模型在單機(jī)上訓(xùn)練到78%的正確率需要將近半年的時(shí)間评疗,這樣的訓(xùn)練速度是完全無法應(yīng)用到實(shí)際生產(chǎn)中的。為了加速訓(xùn)練過程茵烈,本章將介紹如何通過TensorFlow利用GPU或/和分布式計(jì)算進(jìn)行模型訓(xùn)練百匆。
1.首先,我們將介紹如何在TensorFlow中使用單個GPU進(jìn)行計(jì)算加速呜投,也將介紹TensorFlow會話(tf.Session)時(shí)的一些常用參數(shù)加匈。通過這些參數(shù)可以使調(diào)試更加方便而且程序的可擴(kuò)展性更好。然而宙彪,在很多情況下矩动,單個GPU的加速效率無法滿足訓(xùn)練大型深度學(xué)習(xí)模型的計(jì)算量需求有巧,這時(shí)將需要利用更多的計(jì)算資源释漆。
2.為了同時(shí)利用多個GPU或者多臺機(jī)器,我們將介紹深度學(xué)習(xí)模型的并行方式篮迎。在這一節(jié)中也將給出具體的TensorFlow樣例程序來使用多GPU訓(xùn)練模型男图,并比較并行化效率提升的比率。
3.最后我們將介紹分布式TensorFlow甜橱,以及如何通過分布式TensorFlow訓(xùn)練深度學(xué)習(xí)模型逊笆。在這一節(jié)中將給出具體的TensorFlow樣例程序來實(shí)現(xiàn)不同的分布式深度學(xué)習(xí)訓(xùn)練模式。雖然TensorFlow可以支持分布式深度學(xué)習(xí)模型訓(xùn)練岂傲,但是它并不提供集群創(chuàng)建难裆、管理等功能。
4.為了更方便地使用分布式TensorFlow镊掖,我們將介紹才云科技基于Kubernetes容器云平臺搭建的分布式TensorFlow系統(tǒng)乃戈。
1.TensorFlow使用GPU
TensorFlow程序可以通過tf.device函數(shù)來指定運(yùn)行每一個操作的設(shè)備,這個設(shè)備可以是本地的CPU或者GPU亩进,也可以是某一臺遠(yuǎn)程的服務(wù)器症虑。但在本節(jié)中只關(guān)心本地的設(shè)備。TensorFlow會給每一個可用的設(shè)備一個名稱归薛,tf.device函數(shù)可以通過設(shè)備的名稱來指定執(zhí)行運(yùn)算的設(shè)備谍憔。比如CPU在TensorFlow中的名稱為/cpu:0.在默認(rèn)情況下匪蝙,即使機(jī)器有多個CPU,TensorFlow也不會區(qū)分它們习贫,所有的CPU都使用/cpu:0作為名稱逛球。而一臺機(jī)器上不同GPU的名稱是不同的,第n個GPU在TensorFlow中的名稱為/gpu:n.比如第一個GPU的名稱為/gpu:0苫昌,第二個GPU名稱為/gpu:1需忿,以此類推。
TensorFlow提供了一個快捷的方式來查看運(yùn)行每一個運(yùn)算的設(shè)備蜡歹。在生成會話時(shí)屋厘,可以通過設(shè)置log_device_placement參數(shù)來打印運(yùn)行每一個運(yùn)算的設(shè)備。下面的程序展示了如何使用log_device_placement這個參數(shù):
importtensorflowastfa=tf.constant([1.0,2.0,3.0],shape=[3],name='a')b=tf.constant([1.0,2.0,3.0],shape=[3],name='b')c=a+b# 通過log_device_placement參數(shù)來輸出運(yùn)行每一個運(yùn)算的設(shè)備sess=tf.Session(config=tf.ConfigProto(log_device_placement=True))print(sess.run(c))
運(yùn)行代碼月而,輸出:
[2. 4. 6.]
在以上代碼中汗洒,TensorFlow程序生成會話時(shí)加入了參數(shù)log_device_placement=True,所以程序會將每一個操作的設(shè)備輸出到屏幕父款。于是除了可以看到最后的計(jì)算結(jié)果之外溢谤,還可以看到類似“add:/job:localhost/replica:0/task:0/cpu:0”這樣的輸出。這些輸出顯示了執(zhí)行每一個運(yùn)算的設(shè)備憨攒。比如加法操作add是通過CPU來運(yùn)行的世杀,因?yàn)樗脑O(shè)備名稱中包含了/cpu:0
在配置好GPU環(huán)境的TensorFlow中,如果操作沒有明確地指定運(yùn)行設(shè)備肝集,那么TensorFlow會優(yōu)先選擇GPU瞻坝。比如將以上代碼在亞馬遜的實(shí)例上運(yùn)行時(shí),會得到以下運(yùn)行結(jié)果:
在亞馬遜實(shí)例運(yùn)行的結(jié)果
從上面的輸出可以看到在配置好GPU環(huán)境的TensorFlow中杏瞻,TensorFlow會優(yōu)先將運(yùn)算放置在GPU上所刀。不過,盡管亞馬遜實(shí)例有4個GPU捞挥,在默認(rèn)環(huán)境下浮创,TensorFlow只會將運(yùn)算優(yōu)先放到/gpu:0上。于是可以看見在上面的程序中砌函,所有的運(yùn)算都被放在了/gpu:0上斩披。如果需要將某些運(yùn)算放到不同的GPU或者CPU上,就需要通過tf.device來手工指定讹俊。以下程序給出了一個通過tf.device來手工指定運(yùn)行設(shè)備的樣例:
# 通過tf.device將運(yùn)算指定到特定的設(shè)備上withtf.device('/cpu:0'):a=tf.constant([1.0,2.0,3.0],shape=[3],name='a')b=tf.constant([1.0,2.0,3.0],shape=[3],name='b')withtf.device('/gpu:1'):c=a+bsess=tf.Session(config=tf.ConfigProto(log_device_placement=True))print(sess.run(c))
在AWS g2.8xlarge實(shí)例上運(yùn)行上述代碼可以得到以下結(jié)果:
在以上代碼中可以看到生成常量a和b的操作被加載到了CPU上垦沉,而加法操作被放到了第二個GPU"/gpu:1"上。在TensorFlow中劣像,不是所有的操作都可以被放在GPU上乡话,如果強(qiáng)行將無法放在GPU上的操作指定到GPU上,那么程序?qū)?bào)錯耳奕。以下代碼給出了一個報(bào)錯的樣例:
不同版本的TensorFlow對GPU的支持不一樣绑青,如果程序中全部使用強(qiáng)制指定設(shè)備的方式會降低程序的可移植性诬像。在TensorFlow的kernel中定義了哪些操作可以跑在GPU上。比如可以在variable_ops.cc程序中找到以下定義:
在這段定義中可以看到GPU只在部分?jǐn)?shù)據(jù)類型上支持tf.Variable操作闸婴。如果在TensorFlow代碼庫中搜索調(diào)用這段代碼的宏TF_CALL_GPU_NUMBER_TYPES,可以發(fā)現(xiàn)在GPU上坏挠,tf.Variable操作只支持實(shí)數(shù)型(float16,float32和double)的參數(shù)。而在報(bào)錯的樣例代碼中給定的參數(shù)是整數(shù)型的邪乍,所以不支持在GPU上運(yùn)行降狠。為避免這個問題,TensorFlow在生成會話時(shí)可以指定allow_soft_placement參數(shù)庇楞。當(dāng)allow_soft_placement參數(shù)設(shè)置為True時(shí)榜配,如果運(yùn)算無法由GPU執(zhí)行,那么TensorFlow會自動將它放到CPU上執(zhí)行吕晌。以下代碼給出了一個使用allow_soft_placement參數(shù)的樣例:
雖然GPU可以加速TensorFlow的計(jì)算蛋褥,但一般來說不會把所有的操作全部放在GPU上。一個比較好的實(shí)踐是將密集型的運(yùn)算放在GPU上睛驳,而把其他操作放到CPU上烙心。GPU是機(jī)器中相對獨(dú)立的資源,將計(jì)算放入或者轉(zhuǎn)出GPU都需要額外的時(shí)間乏沸。而且GPU需要將計(jì)算時(shí)用到的數(shù)據(jù)從內(nèi)存復(fù)制到GPU設(shè)備上淫茵,這也需要額外的時(shí)間。TensorFlow可以自動完成這些操作而不需要用戶特別處理蹬跃,但為了提高程序運(yùn)行的速度匙瘪,用戶也需要盡量將相關(guān)的運(yùn)算放在同一個設(shè)備上。
2.深度學(xué)習(xí)訓(xùn)練并行模式
TensorFlow可以很容易地利用單個GPU加速深度學(xué)習(xí)模型的訓(xùn)練過程炬转,但要利用更多的GPU或者機(jī)器辆苔,需要了解如何并行化地訓(xùn)練深度學(xué)習(xí)模型算灸。常用的并行化深度學(xué)習(xí)模型訓(xùn)練方式有兩種扼劈,同步模式和異步模式。本節(jié)中將介紹這兩種模式的工作方式及其優(yōu)劣菲驴。
為了幫助我們理解這兩種訓(xùn)練模式荐吵,本節(jié)首先簡單回顧一下如何訓(xùn)練深度學(xué)習(xí)模型。下圖展示了深度學(xué)習(xí)模型的訓(xùn)練流程圖:
深度學(xué)習(xí)模型訓(xùn)練流程圖
深度學(xué)習(xí)模型的訓(xùn)練是一個迭代的過程赊瞬。在每一輪迭代中先煎,前向傳播算法會根據(jù)當(dāng)前參數(shù)的取值計(jì)算出在一小部分訓(xùn)練數(shù)據(jù)上的預(yù)測值,然后反向傳播算法再根據(jù)損失函數(shù)計(jì)算參數(shù)的梯度并更新參數(shù)巧涧。在并行化地訓(xùn)練深度學(xué)習(xí)模型時(shí)薯蝎,不同設(shè)備(GPU或CPU)可以在不同訓(xùn)練數(shù)據(jù)上運(yùn)行這個迭代的過程,而不同并行模式的區(qū)別在于不同的參數(shù)更新方式谤绳。
下圖展示了異步模式的訓(xùn)練流程圖:
異步模式深度學(xué)習(xí)模型訓(xùn)練流程圖
從上圖可以看到占锯,每一輪迭代時(shí)袒哥,不同設(shè)備會讀取參數(shù)最新的取值,但因?yàn)椴煌O(shè)備讀取參數(shù)取值的時(shí)間不一樣消略,所以得到的值也有可能不一樣堡称。根據(jù)當(dāng)前參數(shù)的取值和隨機(jī)獲取的一小部分訓(xùn)練數(shù)據(jù),不同設(shè)備各自運(yùn)行反向傳播的過程并獨(dú)立地更新參數(shù)艺演∪唇簦可以簡單地認(rèn)為異步模式就是單機(jī)模式復(fù)制了多份,每一份使用不同的訓(xùn)練數(shù)據(jù)進(jìn)行訓(xùn)練胎撤。在異步模式下晓殊,不同設(shè)備之間是完全獨(dú)立的。
然而使用異步模式訓(xùn)練的深度學(xué)習(xí)模型可能無法達(dá)到較優(yōu)的訓(xùn)練效果伤提。下圖中給出了一個具體的樣例來說明異步模式的問題:
異步模式訓(xùn)練深度學(xué)習(xí)模型存在的問題示意圖
如上圖挺物,其中黑色曲線展示了模型的損失函數(shù),黑色小球表示了在t0時(shí)刻參數(shù)所對應(yīng)的損失函數(shù)的大小飘弧。假設(shè)兩個設(shè)備d0和d1在時(shí)間t0同時(shí)讀取了參數(shù)的取值识藤,那么設(shè)備d0和d1計(jì)算出來的梯度都會將小球向左移動。假設(shè)在時(shí)間t1設(shè)備d0已經(jīng)完成了反向傳播的計(jì)算并更新了參數(shù)次伶,修改后的參數(shù)處于圖中曉慧球的位置痴昧。然而這時(shí)的設(shè)備d1并不知道參數(shù)已經(jīng)被更新了,所以在時(shí)間t2時(shí)冠王,設(shè)備d1會繼續(xù)將小球向左移動赶撰,使得小球的位置達(dá)到如圖中白球的地方。從圖中可以看到柱彻,當(dāng)參數(shù)被調(diào)整到小白球的位置豪娜,將無法達(dá)到最優(yōu)點(diǎn)。
為了避免更新不同步的問題哟楷,可以使用同步模式瘤载。在同步模式下,所有的設(shè)備同時(shí)讀取參數(shù)的取值卖擅,并且當(dāng)反方向傳播算法完成之后同步更新參數(shù)的取值鸣奔。單個設(shè)備不會單獨(dú)對參數(shù)進(jìn)行更新,而會等待所有設(shè)備都完成反向傳播之后再統(tǒng)一更新參數(shù)惩阶。
下圖展示了同步模式的訓(xùn)練過程:
同步模式深度學(xué)習(xí)模型訓(xùn)練流程圖
從上圖可以看到挎狸,每一輪迭代時(shí),不同設(shè)備首先統(tǒng)一讀取當(dāng)前參數(shù)的取值断楷,并隨機(jī)獲取一小部分?jǐn)?shù)據(jù)锨匆。然后在不同設(shè)備上運(yùn)行反向傳播過程得到在各自訓(xùn)練數(shù)據(jù)上參數(shù)的梯度。注意雖然所有設(shè)備使用的參數(shù)是一致的冬筒,但是因?yàn)橛?xùn)練數(shù)據(jù)不同恐锣,所以得到參數(shù)的梯度就可能不一樣紊遵。當(dāng)所有設(shè)備完成反向傳播的計(jì)算之后,需要計(jì)算出不同設(shè)備上參數(shù)梯度的平均值侥蒙,最后再根據(jù)平均值對參數(shù)進(jìn)行更新暗膜。
同步模式解決了異步模式中存在的參數(shù)更新問題,然而同步模式的效率卻低于異步模式鞭衩。在同步模式下学搜,每一輪迭代都需要設(shè)備統(tǒng)一開始、統(tǒng)一結(jié)束论衍。如果設(shè)備的運(yùn)行速度不一致瑞佩,那么每一輪訓(xùn)練都需要等待最慢的設(shè)備結(jié)束才能開始更新參數(shù),于是很多時(shí)間將被花在等待上坯台。雖然理論上異步模式存在缺陷炬丸,但是因?yàn)橛?xùn)練深度學(xué)習(xí)模型時(shí)使用的隨機(jī)梯度下降本身就是梯度下降的一個近似解法,而且即使是梯度下降也無法保證達(dá)到全局最優(yōu)值蜒蕾,所以在實(shí)際應(yīng)用中稠炬,在相同時(shí)間內(nèi),使用異步模式訓(xùn)練的模型不一定比同步模式差咪啡。所以這兩種訓(xùn)練模式在實(shí)踐中都有非常廣泛的應(yīng)用首启。
3.多GPU并行
在第二節(jié)中介紹了常用的分布式深度學(xué)習(xí)模型訓(xùn)練模式。這一節(jié)將給出具體的TensorFlow代碼撤摸,在一臺機(jī)器的多個GPU上并行訓(xùn)練深度學(xué)習(xí)模型毅桃。因?yàn)橐话銇碚f一臺機(jī)器上的多個GPU性能相似,所以在這種設(shè)置下會更多地采用同步模式訓(xùn)練深度學(xué)習(xí)模型准夷。下面將給出具體的代碼钥飞,在多GPU上訓(xùn)練深度學(xué)習(xí)模型解決MNIST問題。本節(jié)的樣例代碼將沿用之前解決MNIST問題這一章的代碼框架衫嵌,并且使用mnist_inference.py程序來完成神經(jīng)網(wǎng)絡(luò)的前向傳播過程读宙。以下代碼給出了新的神經(jīng)網(wǎng)絡(luò)訓(xùn)練程序mnist_multi_gpu_train.py:
# coding=utf-8fromdatetimeimportdatetimeimportosimporttimeimporttensorflowastfimportmnist_inference# 定義訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí)需要用到的參數(shù)。BATCH_SIZE=100LEARNING_RATE_BASE=0.001LEARNING_RATE_DECAY=0.99REGULARAZTION_RATE=0.0001TRAINING_STEPS=1000MOVING_AVERAGE_DECAY=0.99N_GPU=4# 定義日志和模型輸出的路徑渐扮。MODEL_SAVE_PATH="logs_and_models/"MODEL_NAME="model.ckpt"DATA_PATH="output.tfrecords"# 定義輸入隊(duì)列得到訓(xùn)練數(shù)據(jù)论悴,具體細(xì)節(jié)可以參考第七章。defget_input():filename_queue=tf.train.string_input_producer([DATA_PATH])reader=tf.TFRecordReader()_,serialized_example=reader.read(filename_queue)# 定義數(shù)據(jù)解析格式墓律。features=tf.parse_single_example(serialized_example,features={'image_raw':tf.FixedLenFeature([],tf.string),'pixels':tf.FixedLenFeature([],tf.int64),'label':tf.FixedLenFeature([],tf.int64),})# 解析圖片和標(biāo)簽信息。decoded_image=tf.decode_raw(features['image_raw'],tf.uint8)reshaped_image=tf.reshape(decoded_image,[784])retyped_image=tf.cast(reshaped_image,tf.float32)label=tf.cast(features['label'],tf.int32)# 定義輸入隊(duì)列并返回幔亥。min_after_dequeue=10000capacity=min_after_dequeue+3*BATCH_SIZEreturntf.train.shuffle_batch([retyped_image,label],batch_size=BATCH_SIZE,capacity=capacity,min_after_dequeue=min_after_dequeue)# 定義損失函數(shù)耻讽。defget_loss(x,y_,regularizer,scope,reuse_variables=None):withtf.variable_scope(tf.get_variable_scope(),reuse=reuse_variables):y=mnist_inference.inference(x,regularizer)cross_entropy=tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(y,y_))regularization_loss=tf.add_n(tf.get_collection('losses',scope))loss=cross_entropy+regularization_lossreturnloss# 計(jì)算每一個變量梯度的平均值。defaverage_gradients(tower_grads):average_grads=[]# 枚舉所有的變量和變量在不同GPU上計(jì)算得出的梯度帕棉。forgrad_and_varsinzip(*tower_grads):# 計(jì)算所有GPU上的梯度平均值针肥。grads=[]forg,_ingrad_and_vars:expanded_g=tf.expand_dims(g,0)grads.append(expanded_g)grad=tf.concat(grads,0)grad=tf.reduce_mean(grad,0)v=grad_and_vars[0][1]grad_and_var=(grad,v)# 將變量和它的平均梯度對應(yīng)起來饼记。average_grads.append(grad_and_var)# 返回所有變量的平均梯度,這個將被用于變量的更新慰枕。returnaverage_grads# 主訓(xùn)練過程具则。defmain(argv=None):# 將簡單的運(yùn)算放在CPU上,只有神經(jīng)網(wǎng)絡(luò)的訓(xùn)練過程放在GPU上具帮。withtf.Graph().as_default(),tf.device('/cpu:0'):# 定義基本的訓(xùn)練過程x,y_=get_input()regularizer=tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)global_step=tf.get_variable('global_step',[],initializer=tf.constant_initializer(0),trainable=False)learning_rate=tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,60000/BATCH_SIZE,LEARNING_RATE_DECAY)opt=tf.train.GradientDescentOptimizer(learning_rate)tower_grads=[]reuse_variables=False# 將神經(jīng)網(wǎng)絡(luò)的優(yōu)化過程跑在不同的GPU上博肋。foriinrange(N_GPU):# 將優(yōu)化過程指定在一個GPU上束倍。withtf.device('/gpu:%d'%i):withtf.name_scope('GPU_%d'%i)asscope:cur_loss=get_loss(x,y_,regularizer,scope,reuse_variables)reuse_variables=Truegrads=opt.compute_gradients(cur_loss)tower_grads.append(grads)# 計(jì)算變量的平均梯度歉备。grads=average_gradients(tower_grads)forgrad,varingrads:ifgradisnotNone:tf.histogram_summary('gradients_on_average/%s'%var.op.name,grad)# 使用平均梯度更新參數(shù)绊率。apply_gradient_op=opt.apply_gradients(grads,global_step=global_step)forvarintf.trainable_variables():tf.histogram_summary(var.op.name,var)# 計(jì)算變量的滑動平均值速侈。variable_averages=tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)variables_to_average=(tf.trainable_variables()+tf.moving_average_variables())variables_averages_op=variable_averages.apply(variables_to_average)# 每一輪迭代需要更新變量的取值并更新變量的滑動平均值办陷。train_op=tf.group(apply_gradient_op,variables_averages_op)saver=tf.train.Saver(tf.all_variables())summary_op=tf.merge_all_summaries()init=tf.initialize_all_variables()withtf.Session(config=tf.ConfigProto(allow_soft_placement=True,log_device_placement=True))assess:# 初始化所有變量并啟動隊(duì)列樊销。init.run()coord=tf.train.Coordinator()threads=tf.train.start_queue_runners(sess=sess,coord=coord)summary_writer=tf.train.SummaryWriter(MODEL_SAVE_PATH,sess.graph)forstepinrange(TRAINING_STEPS):# 執(zhí)行神經(jīng)網(wǎng)絡(luò)訓(xùn)練操作请琳,并記錄訓(xùn)練操作的運(yùn)行時(shí)間狭吼。start_time=time.time()_,loss_value=sess.run([train_op,cur_loss])duration=time.time()-start_time# 每隔一段時(shí)間數(shù)據(jù)當(dāng)前的訓(xùn)練進(jìn)度稠通,并統(tǒng)計(jì)訓(xùn)練速度衬衬。ifstep!=0andstep%10==0:# 計(jì)算使用過的訓(xùn)練數(shù)據(jù)個數(shù)。num_examples_per_step=BATCH_SIZE*N_GPU? ? ? ? ? ? ? ? ? ? examples_per_sec=num_examples_per_step/duration? ? ? ? ? ? ? ? ? ? sec_per_batch=duration/N_GPU# 輸出訓(xùn)練信息改橘。format_str=('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f sec/batch)')print(format_str%(datetime.now(),step,loss_value,examples_per_sec,sec_per_batch))# 通過TensorBoard可視化訓(xùn)練過程佣耐。summary=sess.run(summary_op)summary_writer.add_summary(summary,step)# 每隔一段時(shí)間保存當(dāng)前的模型。ifstep%1000==0or(step+1)==TRAINING_STEPS:checkpoint_path=os.path.join(MODEL_SAVE_PATH,MODEL_NAME)saver.save(sess,checkpoint_path,global_step=step)coord.request_stop()coord.join(threads)if__name__=='__main__':tf.app.run()
在AWS的g2.8xlarge實(shí)例上運(yùn)行以上程序可以得到類似下面的結(jié)果:
新的神經(jīng)網(wǎng)絡(luò)訓(xùn)練程序運(yùn)行結(jié)果
在AWS的g2.8xlarge實(shí)例上運(yùn)行以上代碼可以同時(shí)使用4個GPU訓(xùn)練神經(jīng)網(wǎng)絡(luò)唧龄。下圖顯示了運(yùn)行樣例代碼時(shí)不同GPU的使用情況:
在AWS的g2.8xlarge實(shí)例上運(yùn)行MNST樣例程序時(shí)GPU的使用情況
因?yàn)樵谇懊娑x的神經(jīng)網(wǎng)絡(luò)規(guī)模比較小兼砖,所以上圖中顯示的GPU使用率不高。如果訓(xùn)練大型的神經(jīng)網(wǎng)絡(luò)模型既棺,TensorFlow將會占滿所有用到的GPU讽挟。
通過調(diào)整參數(shù)N_GPU,可以實(shí)驗(yàn)同步模式下隨著GPU個數(shù)的增加訓(xùn)練速度的加速比率丸冕。下圖展示了在給出的MNIST樣例代碼上耽梅,訓(xùn)練速度隨著GPU數(shù)量增加的變化趨勢:
訓(xùn)練速度隨著GPU數(shù)量增加的變化趨勢
從上圖中可以看出,當(dāng)使用兩個GPU時(shí)胖烛,模型的訓(xùn)練速度是使用一個GPU的1.92倍眼姐。也就是說當(dāng)GPU數(shù)量較小時(shí),訓(xùn)練速度基本可以隨著GPU的數(shù)量線性增長佩番。
下圖展示了當(dāng)GPU數(shù)量增多時(shí)众旗,訓(xùn)練速度隨著GPU數(shù)量增加的加速比:
訓(xùn)練速度隨著GPU增加的變化趨勢
從上圖可以看出,當(dāng)GPU數(shù)量增加時(shí)趟畏,雖然加速比不再是線性贡歧,但TensorFlow仍然可以通過增加GPU數(shù)量有效地加速深度學(xué)習(xí)模型的訓(xùn)練過程。
4.分布式TensorFlow
通過多GPU并行的方式可以達(dá)到很好地加速效果。然而一臺機(jī)器上能夠安裝的GPU有限利朵,要進(jìn)一步提升深度學(xué)習(xí)模型的訓(xùn)練速度律想,就需要將TensorFlow分布式運(yùn)行在多臺機(jī)器上。本節(jié)將介紹如何編寫以及運(yùn)行分布式TensorFlow程序绍弟。
1.首先在第一小節(jié)中將介紹分布式TensorFlow的工作原理技即,并給出最簡單的分布式TensorFlow樣例程序。在這一小節(jié)中也將介紹不同的TensorFlow分布式方式樟遣。
2.第二小節(jié)中將給出兩個完整的TensorFlow樣例程序來同步或者異步地訓(xùn)練深度學(xué)習(xí)模型而叼。
3.最后將總結(jié)目前生態(tài)分布式TensorFlow中的不足,并介紹才云科技(Caicloud.io)提供的分布式TensorFlow解決方案(TensorFlow as a Service, TaaS).
4.1分布式TensorFlow原理
在第二節(jié)中介紹了分布式TensorFlow訓(xùn)練深度學(xué)習(xí)模型的理論年碘。本小節(jié)將具體介紹如何使用TensorFlow在分布式集群中訓(xùn)練深度學(xué)習(xí)模型澈歉。以下代碼展示了如何創(chuàng)建一個最簡單的TensorFlow集群:
importtensorflowastfc=tf.constant("Hello, distributed Tensorflow!")# 創(chuàng)建一個本地TensorFlow集群server=tf.train.Server.create_local_server()# 在集群上創(chuàng)建一個會話sess=tf.Session(server.target)print(sess.run(c))
運(yùn)行代碼,結(jié)果如下:
b'Hello, distributed Tensorflow!'
在以上代碼中屿衅,首先通過tf.train.Server.create_local_server函數(shù)在本地建立了一個只有一臺機(jī)器的TensorFlow集群埃难。然后在該集群上生成了一個會話,并通過生成的會話將運(yùn)算運(yùn)行在創(chuàng)建的TensorFlow集群上涤久。雖然這只是一個單機(jī)集群涡尘,但它大致反映了TensorFlow集群的工作流程。TensorFlow集群通過一系列的任務(wù)(tasks)來執(zhí)行TensorFlow計(jì)算圖中的運(yùn)算响迂。一般來說考抄,不同任務(wù)跑在不同機(jī)器上。最主要的例外是使用GPU時(shí)蔗彤,不同任務(wù)可以使用同一臺機(jī)器上的不同GPU川梅。TensorFlow集群中的任務(wù)也會被聚合成工作(jobs),每個工作可以包含一個或者多個任務(wù)然遏。比如在訓(xùn)練深度學(xué)習(xí)模型時(shí)贫途,一臺運(yùn)行反向傳播的機(jī)器是一個任務(wù),而所有運(yùn)行反向傳播機(jī)器的集合是一種工作待侵。
上面的樣例代碼是只有一個任務(wù)的集群丢早。當(dāng)一個TensorFlow集群有多個任務(wù)時(shí),需要使用tf.train.ClusterSpec來指定運(yùn)行每一個任務(wù)的機(jī)器秧倾。比如以下代碼展示了在本地運(yùn)行有兩個任務(wù)的TensorFlow集群怨酝。第一個任務(wù)的代碼如下:
importtensorflowastf# 創(chuàng)建兩個集群c=tf.constant("Hello from server1!")# 生成一個有兩個任務(wù)的集群,一個任務(wù)跑在本地2222端口那先,另外一個跑在本地2223端口cluster=tf.train.ClusterSpec({"local":["localhost:2222","localhost:2223"]})# 通過上面生成的集群配置成server# job_name和task_index:指定當(dāng)前所啟動的任務(wù)农猬。server=tf.train.Server(cluster,job_name="local",task_index=0)# server.target:生成會話來使用TensorFlow集群中的資源。# log_device_placement:可以看到執(zhí)行每一個操作的任務(wù)sess=tf.Session(server.target,config=tf.ConfigProto(log_device_placement=True))print(sess.run(c))server.join()# 第二個任務(wù)的代碼importtensorflowastfc=tf.constant("Hello from server2!")cluster=tf.train.ClusterSpec({"local":["localhost:2222","localhost:2223"]})server=tf.train.Server(cluster,job_name="local",task_index=1)sess=tf.Session(server.target,config=tf.ConfigProto(log_device_placement=True))print(sess.run(c))server.join()
啟動第一個任務(wù)后胃榕,可以得到類似下面的輸出:
從第一個任務(wù)的輸出中可以看到盛险,當(dāng)只啟動第一個任務(wù)時(shí)瞄摊,程序會停下來等待第二個任務(wù)啟動勋又,而且持續(xù)輸出:fail to connect to...
當(dāng)?shù)诙€任務(wù)啟動后苦掘,可以看到從第一個任務(wù)中會輸出Hello from server1!的結(jié)果。第二個任務(wù)的輸出如下:
值得注意的是第二個任務(wù)中定義的計(jì)算也被放在了設(shè)備/job:/local/replica:0/task/cpu:0上楔壤。也就是說這個計(jì)算將由第一個任務(wù)來執(zhí)行鹤啡。從上面這個樣例可以看到,通過tf.train.Server.target生成的會話可以統(tǒng)一管理整個TensorFlow集群中的資源蹲嚣。
和使用多GPU類似递瑰,TensorFlow支持通過tf.device來指定操作運(yùn)行在哪個任務(wù)上。比如將第二個任務(wù)中定義計(jì)算的語句改為以下代碼隙畜,就可以看到這個計(jì)算將被調(diào)度到/job:local/replica:0/task:1/cpu:0上面抖部。
withtf.device("/job:local/task:1")c=tf.constant("Hello from server2!")
在上面的樣例中只定義了一個工作“l(fā)ocal”。但一般在訓(xùn)練深度學(xué)習(xí)模型時(shí)议惰,會定義兩個工作慎颗。一個工作專門負(fù)責(zé)存儲、獲取以及更新變量的取值言询,這個工作所包含的任務(wù)統(tǒng)稱為參數(shù)服務(wù)器(parameter server,ps)俯萎。另外一個工作負(fù)責(zé)運(yùn)行反向傳播算法來獲取參數(shù)梯度,這個工作所包含的任務(wù)統(tǒng)稱為服務(wù)器(worker)运杭。下面給出了一個比較常見的用于訓(xùn)練深度學(xué)習(xí)模型的TensorFlow集群配置方法:
TensorFlow集群配置方法
使用分布式TensorFlow訓(xùn)練深度學(xué)習(xí)模型一般有兩種方式夫啊。一種方式叫做計(jì)算圖內(nèi)分布式(in-graph replication)。使用這種分布式訓(xùn)練方式時(shí)辆憔,所有的任務(wù)都會使用一個TensorFlow計(jì)算圖中的變量(也就是深度學(xué)習(xí)模型中的參數(shù))撇眯,而只是將計(jì)算部分發(fā)布到不同的計(jì)算服務(wù)器上。上一節(jié)中給出的代碼也實(shí)現(xiàn)了參數(shù)的同步更新虱咧。然而因?yàn)橛?jì)算圖內(nèi)分布式需要有一個中心節(jié)點(diǎn)來生成這個計(jì)算圖并分配計(jì)算任務(wù)熊榛,所以當(dāng)數(shù)據(jù)量太大時(shí),這個中心節(jié)點(diǎn)容易造成性能瓶頸彤钟。
另外一種分布式TensorFlow訓(xùn)練深度學(xué)習(xí)模型的方式叫計(jì)算圖之間分布式(between-graph replication)来候。使用這種分布式方式時(shí),在每一個計(jì)算服務(wù)器上都會創(chuàng)建一個獨(dú)立的TensorFlow計(jì)算圖逸雹,但不同計(jì)算圖中的相同參數(shù)需要以一種固定的方式放到同一個參數(shù)服務(wù)器上营搅。TensorFlow提供了tf.train.replica_device_setter函數(shù)來幫助完成這一個過程,在下一小節(jié)將給出具體的樣例梆砸。因?yàn)槊總€計(jì)算服務(wù)器的TensorFlow計(jì)算圖是獨(dú)立的转质,所以這種方式的并行度要更高。但在計(jì)算圖之間分布式下進(jìn)行參數(shù)的同步更新比較困難帖世。為了解決這個問題休蟹,TensorFlow提供了tf.train.SyncReplicasOptimizer函數(shù)來幫助實(shí)現(xiàn)參數(shù)的同步更新。這讓計(jì)算圖之間分布式方式被更加廣泛地使用。下一小節(jié)將給出使用計(jì)算圖之間分布式的樣例程序來實(shí)現(xiàn)異步模式和同步模式的并行化深度學(xué)習(xí)模型訓(xùn)練過程赂弓。
4.2分布式TensorFlow模型訓(xùn)練
本小節(jié)將給出兩個樣例程序分別實(shí)現(xiàn)使用計(jì)算圖之間分布式(Between-graph replication)完成分布式深度學(xué)習(xí)模型訓(xùn)練的異步更新和同步更新绑榴。第一部分將給出使用計(jì)算圖之間分布式實(shí)現(xiàn)異步更新的TensorFlow程序。這一部分也會給出具體的命令行將該程序分布式的運(yùn)行在一個參數(shù)服務(wù)器和兩個計(jì)算服務(wù)器上盈魁,并通過TensorBoard可視化在第一個計(jì)算服務(wù)器上的TensorFlow計(jì)算圖翔怎。第二部分將給出計(jì)算圖之間分布式實(shí)現(xiàn)同步參數(shù)更新的TensorFlow程序。同步參數(shù)更新的代碼大部分和異步更新相似杨耙,所以在這一部分中將重點(diǎn)介紹它們之間的不同之處赤套。
異步模式樣例程序
下面的樣例代碼將扔采用MNIST數(shù)字識別問題的那一章給出的模式,并復(fù)用mnist_inference.py程序中定義的前向傳播算法珊膜,以下代碼實(shí)現(xiàn)了異步模式的分布式神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程:
# coding=utf-8importtimeimporttensorflowastffromtensorflow.examples.tutorials.mnistimportinput_dataimportmnist_inference# 配置神經(jīng)網(wǎng)絡(luò)的參數(shù)容握。BATCH_SIZE=100LEARNING_RATE_BASE=0.01LEARNING_RATE_DECAY=0.99REGULARAZTION_RATE=0.0001TRAINING_STEPS=1000MOVING_AVERAGE_DECAY=0.99# 模型保存的路徑和文件名。MODEL_SAVE_PATH="logs/log_async"DATA_PATH="../../datasets/MNIST_data"FLAGS=tf.app.flags.FLAGS# 指定當(dāng)前程序是參數(shù)服務(wù)器還是計(jì)算服務(wù)器车柠。tf.app.flags.DEFINE_string('job_name','worker',' "ps" or "worker" ')# 指定集群中的參數(shù)服務(wù)器地址剔氏。tf.app.flags.DEFINE_string('ps_hosts',' tf-ps0:2222,tf-ps1:1111','Comma-separated list of hostname:port for the parameter server jobs. e.g. "tf-ps0:2222,tf-ps1:1111" ')# 指定集群中的計(jì)算服務(wù)器地址。tf.app.flags.DEFINE_string('worker_hosts',' tf-worker0:2222,tf-worker1:1111','Comma-separated list of hostname:port for the worker jobs. e.g. "tf-worker0:2222,tf-worker1:1111" ')# 指定當(dāng)前程序的任務(wù)ID堪遂。tf.app.flags.DEFINE_integer('task_id',0,'Task ID of the worker/replica running the training.')# 定義TensorFlow的計(jì)算圖介蛉,并返回每一輪迭代時(shí)需要運(yùn)行的操作。defbuild_model(x,y_,is_chief):regularizer=tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)# 通過和5.5節(jié)給出的mnist_inference.py代碼計(jì)算神經(jīng)網(wǎng)絡(luò)前向傳播的結(jié)果溶褪。y=mnist_inference.inference(x,regularizer)global_step=tf.Variable(0,trainable=False)# 計(jì)算損失函數(shù)并定義反向傳播過程币旧。cross_entropy=tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(y_,1))cross_entropy_mean=tf.reduce_mean(cross_entropy)loss=cross_entropy_mean+tf.add_n(tf.get_collection('losses'))learning_rate=tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,60000/BATCH_SIZE,LEARNING_RATE_DECAY)train_op=tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)# 定義每一輪迭代需要運(yùn)行的操作。ifis_chief:# 計(jì)算變量的滑動平均值猿妈。? variable_averages=tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)variables_averages_op=variable_averages.apply(tf.trainable_variables())withtf.control_dependencies([variables_averages_op,train_op]):train_op=tf.no_op()returnglobal_step,loss,train_opdefmain(argv=None):# 解析flags并通過tf.train.ClusterSpec配置TensorFlow集群吹菱。ps_hosts=FLAGS.ps_hosts.split(',')worker_hosts=FLAGS.worker_hosts.split(',')cluster=tf.train.ClusterSpec({"ps":ps_hosts,"worker":worker_hosts})# 通過tf.train.ClusterSpec以及當(dāng)前任務(wù)創(chuàng)建tf.train.Server。server=tf.train.Server(cluster,job_name=FLAGS.job_name,task_index=FLAGS.task_id)# 參數(shù)服務(wù)器只需要管理TensorFlow中的變量彭则,不需要執(zhí)行訓(xùn)練的過程鳍刷。server.join()會# 一致停在這條語句上。ifFLAGS.job_name=='ps':withtf.device("/cpu:0"):server.join()# 定義計(jì)算服務(wù)器需要運(yùn)行的操作俯抖。is_chief=(FLAGS.task_id==0)mnist=input_data.read_data_sets(DATA_PATH,one_hot=True)device_setter=tf.train.replica_device_setter(worker_device="/job:worker/task:%d"%FLAGS.task_id,cluster=cluster)withtf.device(device_setter):# 定義輸入并得到每一輪迭代需要運(yùn)行的操作输瓜。x=tf.placeholder(tf.float32,[None,mnist_inference.INPUT_NODE],name='x-input')y_=tf.placeholder(tf.float32,[None,mnist_inference.OUTPUT_NODE],name='y-input')global_step,loss,train_op=build_model(x,y_,is_chief)# 定義用于保存模型的saver。saver=tf.train.Saver()# 定義日志輸出操作芬萍。summary_op=tf.summary.merge_all()# 定義變量初始化操作尤揣。init_op=tf.global_variables_initializer()# 通過tf.train.Supervisor管理訓(xùn)練深度學(xué)習(xí)模型時(shí)的通用功能。sv=tf.train.Supervisor(is_chief=is_chief,logdir=MODEL_SAVE_PATH,init_op=init_op,summary_op=summary_op,saver=saver,global_step=global_step,save_model_secs=60,save_summaries_secs=60)sess_config=tf.ConfigProto(allow_soft_placement=True,log_device_placement=False)# 通過tf.train.Supervisor生成會話柬祠。sess=sv.prepare_or_wait_for_session(server.target,config=sess_config)step=0start_time=time.time()# 執(zhí)行迭代過程北戏。whilenotsv.should_stop():xs,ys=mnist.train.next_batch(BATCH_SIZE)_,loss_value,global_step_value=sess.run([train_op,loss,global_step],feed_dict={x:xs,y_:ys})ifglobal_step_value>=TRAINING_STEPS:break# 每隔一段時(shí)間輸出訓(xùn)練信息。ifstep>0andstep%100==0:duration=time.time()-start_time? ? ? ? ? ? ? ? sec_per_batch=duration/global_step_value? ? ? ? ? ? ? ? format_str="After %d training steps (%d global steps), loss on training batch is %g.? (%.3f sec/batch)"printformat_str%(step,global_step_value,loss_value,sec_per_batch)step+=1sv.stop()if__name__=="__main__":tf.app.run()
假設(shè)上面代碼的文件名為dist_tf_mnist_async.py漫蛔,那么要啟動一個擁有一個參數(shù)服務(wù)器嗜愈、兩個計(jì)算服務(wù)器的集群旧蛾,需要先在運(yùn)行參數(shù)服務(wù)器的機(jī)器上啟動以下命令:
運(yùn)行參數(shù)服務(wù)器的機(jī)器上啟動的命令
然后在運(yùn)行第一個計(jì)算服務(wù)器的機(jī)器上啟動以下命令:
運(yùn)行第一個計(jì)算服務(wù)器的機(jī)器上啟動的命令
最后在運(yùn)行第二個計(jì)算服務(wù)器的機(jī)器上啟動以下命令:
運(yùn)行第二個計(jì)算服務(wù)器的機(jī)器上啟動的命令
在啟動第一個計(jì)算服務(wù)器之后,這個計(jì)算服務(wù)器就會嘗試連接其他的服務(wù)器(包括計(jì)算服務(wù)器和參數(shù)服務(wù)器)蠕嫁。如果其他服務(wù)器還沒有啟動锨天,則被啟動的計(jì)算服務(wù)器會報(bào)連接出錯的問題。下面展示了一個出錯信息:
連接出錯信息
不過這不會影響TensorFlow集群的啟動拌阴。當(dāng)TensorFlow集群中所有服務(wù)器都被啟動之后绍绘,每一個計(jì)算服務(wù)器將不再報(bào)錯奶镶。在TensorFlow集群完全啟動之后迟赃,訓(xùn)練過程將被執(zhí)行。
下圖展示了第一個計(jì)算服務(wù)器的TensorFlow計(jì)算圖:
通過TensorBoard可視化的分布式TensorFlow計(jì)算圖
從上圖可以看出厂镇,神經(jīng)網(wǎng)絡(luò)中定義的參數(shù)被放在了參數(shù)服務(wù)器上(圖中灰色節(jié)點(diǎn))纤壁,而反向傳播的計(jì)算過程則放在了當(dāng)前的計(jì)算服務(wù)器上(圖中的深灰色節(jié)點(diǎn))
在計(jì)算服務(wù)器訓(xùn)練神經(jīng)網(wǎng)絡(luò)的過程中,第一個計(jì)算服務(wù)器會輸出類似下面的信息:
第二個計(jì)算服務(wù)器會輸出類似下面的信息:
從輸出的信息中可以看到捺信,在第二個計(jì)算服務(wù)器啟動之前酌媒,第一個服務(wù)器已經(jīng)運(yùn)行了很多輪迭代了。在異步模式下迄靠,即使有計(jì)算服務(wù)器沒有正常工作秒咨,參數(shù)更新的過程仍可繼續(xù),而且全局的迭代輪數(shù)時(shí)所有計(jì)算服務(wù)器迭代輪數(shù)的和掌挚。
同步模式樣例程序
和異步模式類似雨席,下面給出的代碼同樣是基于MNIST數(shù)字識別問題那一章給出的框架。該代碼實(shí)現(xiàn)了同步模式的分布式神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程:
# coding=utf-8importtimeimporttensorflowastffromtensorflow.examples.tutorials.mnistimportinput_dataimportmnist_inference# 配置神經(jīng)網(wǎng)絡(luò)的參數(shù)吠式。BATCH_SIZE=100LEARNING_RATE_BASE=0.8LEARNING_RATE_DECAY=0.99REGULARAZTION_RATE=0.0001TRAINING_STEPS=10000MOVING_AVERAGE_DECAY=0.99MODEL_SAVE_PATH="logs/log_sync"DATA_PATH="../../datasets/MNIST_data"# 和異步模式類似的設(shè)置flags陡厘。FLAGS=tf.app.flags.FLAGStf.app.flags.DEFINE_string('job_name','worker',' "ps" or "worker" ')tf.app.flags.DEFINE_string('ps_hosts',' tf-ps0:2222,tf-ps1:1111','Comma-separated list of hostname:port for the parameter server jobs. e.g. "tf-ps0:2222,tf-ps1:1111" ')tf.app.flags.DEFINE_string('worker_hosts',' tf-worker0:2222,tf-worker1:1111','Comma-separated list of hostname:port for the worker jobs. e.g. "tf-worker0:2222,tf-worker1:1111" ')tf.app.flags.DEFINE_integer('task_id',0,'Task ID of the worker/replica running the training.')# 和異步模式類似的定義TensorFlow的計(jì)算圖。唯一的區(qū)別在于使用# tf.train.SyncReplicasOptimizer函數(shù)處理同步更新特占。defbuild_model(x,y_,n_workers,is_chief):regularizer=tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)y=mnist_inference.inference(x,regularizer)global_step=tf.Variable(0,trainable=False)variable_averages=tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)variables_averages_op=variable_averages.apply(tf.trainable_variables())cross_entropy=tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(y_,1))cross_entropy_mean=tf.reduce_mean(cross_entropy)loss=cross_entropy_mean+tf.add_n(tf.get_collection('losses'))learning_rate=tf.train.exponential_decay(LEARNING_RATE_BASE,global_step,60000/BATCH_SIZE,LEARNING_RATE_DECAY)# 通過tf.train.SyncReplicasOptimizer函數(shù)實(shí)現(xiàn)同步更新糙置。opt=tf.train.SyncReplicasOptimizer(tf.train.GradientDescentOptimizer(learning_rate),replicas_to_aggregate=n_workers,total_num_replicas=n_workers)train_op=opt.minimize(loss,global_step=global_step)ifis_chief:variable_averages=tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)variables_averages_op=variable_averages.apply(tf.trainable_variables())withtf.control_dependencies([variables_averages_op,train_op]):train_op=tf.no_op()returnglobal_step,loss,train_op,optdefmain(argv=None):# 和異步模式類似的創(chuàng)建TensorFlow集群。ps_hosts=FLAGS.ps_hosts.split(',')worker_hosts=FLAGS.worker_hosts.split(',')print('PS hosts are: %s'%ps_hosts)print('Worker hosts are: %s'%worker_hosts)n_workers=len(worker_hosts)cluster=tf.train.ClusterSpec({"ps":ps_hosts,"worker":worker_hosts})server=tf.train.Server(cluster,job_name=FLAGS.job_name,task_index=FLAGS.task_id)ifFLAGS.job_name=='ps':withtf.device("/cpu:0"):server.join()is_chief=(FLAGS.task_id==0)mnist=input_data.read_data_sets(DATA_PATH,one_hot=True)withtf.device(tf.train.replica_device_setter(worker_device="/job:worker/task:%d"%FLAGS.task_id,cluster=cluster)):x=tf.placeholder(tf.float32,[None,mnist_inference.INPUT_NODE],name='x-input')y_=tf.placeholder(tf.float32,[None,mnist_inference.OUTPUT_NODE],name='y-input')global_step,loss,train_op,opt=build_model(x,y_,n_workers,is_chief)# 和異步模式類似的聲明一些輔助函數(shù)是目。saver=tf.train.Saver()summary_op=tf.summary.merge_all()init_op=tf.global_variables_initializer()# 在同步模式下谤饭,主計(jì)算服務(wù)器需要協(xié)調(diào)不同計(jì)算服務(wù)器計(jì)算得到的參數(shù)梯度并最終更新參數(shù)。# 這需要主計(jì)算服務(wù)器完成一些額外的初始化工作懊纳。ifis_chief:# 獲取協(xié)調(diào)不同計(jì)算服務(wù)器的隊(duì)列揉抵。在更新參數(shù)之前,主計(jì)算服務(wù)器需要先啟動這些隊(duì)列长踊。chief_queue_runner=opt.get_chief_queue_runner()# 初始化同步更新隊(duì)列的操作功舀。init_tokens_op=opt.get_init_tokens_op(0)# 和異步模式類似的聲明tf.train.Supervisor。sv=tf.train.Supervisor(is_chief=is_chief,logdir=MODEL_SAVE_PATH,init_op=init_op,summary_op=summary_op,saver=saver,global_step=global_step,save_model_secs=60,save_summaries_secs=60)sess_config=tf.ConfigProto(allow_soft_placement=True,log_device_placement=False)sess=sv.prepare_or_wait_for_session(server.target,config=sess_config)# 在主計(jì)算服務(wù)器上啟動協(xié)調(diào)同步更新的隊(duì)列并執(zhí)行初始化操作身弊。ifis_chief:sv.start_queue_runners(sess,[chief_queue_runner])sess.run(init_tokens_op)# 和異步模式類似的運(yùn)行迭代的訓(xùn)練過程辟汰。step=0start_time=time.time()whilenotsv.should_stop():xs,ys=mnist.train.next_batch(BATCH_SIZE)_,loss_value,global_step_value=sess.run([train_op,loss,global_step],feed_dict={x:xs,y_:ys})ifglobal_step_value>=TRAINING_STEPS:breakifstep>0andstep%100==0:duration=time.time()-start_time? ? ? ? ? ? ? ? sec_per_batch=duration/(global_step_value*n_workers)format_str="After %d training steps (%d global steps), loss on training batch is %g.? (%.3f sec/batch)"printformat_str%(step,global_step_value,loss_value,sec_per_batch)step+=1sv.stop()if__name__=="__main__":tf.app.run()
和異步模式類似列敲,在不同機(jī)器上運(yùn)行以上代碼就可以啟動TensorFlow集群。但和異步模式不同的是帖汞,當(dāng)?shù)谝慌_計(jì)算服務(wù)器初始化完畢之后戴而,它并不能直接更新參數(shù)。這是因?yàn)樵诔绦蛑幸竺恳淮螀?shù)更新都需要來自兩個計(jì)算服務(wù)器的梯度翩蘸。在第一個計(jì)算服務(wù)器上所意,可以看到與下面類似的輸出:
第一臺計(jì)算服務(wù)器的輸出
第二個計(jì)算服務(wù)器的輸出如下:
第二胎計(jì)算服務(wù)器的輸出
在第一個計(jì)算服務(wù)器的第一行輸出中可以看到,前100輪迭代平均速度為0.176 (sec/batch)催首,要遠(yuǎn)遠(yuǎn)慢于最后的平均速度0.042(sec/batch)扶踊。這是因?yàn)樵诘谝惠喌_始之前,第一個計(jì)算服務(wù)器需要等待第二個計(jì)算服務(wù)器執(zhí)行初始化的過程郎任,于是導(dǎo)致前100輪迭代的平均速度是最慢的秧耗。這也反映了同步更新的一個問題。當(dāng)一個計(jì)算服務(wù)器被卡住時(shí)舶治,其他所有的計(jì)算服務(wù)器都需要等待這個最慢的計(jì)算服務(wù)器分井。
為了解決這個問題,可以調(diào)整tf.train.SyncReplicasOptimizer函數(shù)中的replicas_to_aggreate參數(shù)霉猛。當(dāng)replicas_to_aggreate小于計(jì)算服務(wù)器總數(shù)時(shí)尺锚,每一輪迭代就不需要手機(jī)所有的梯度,從而避免被最慢的計(jì)算服務(wù)器卡住惜浅。TensorFlow也支持通過調(diào)整同步隊(duì)列初始化操作tf.train.SyncReplicasOptimizer.get_init_tokens_op中的參數(shù)來控制對不同計(jì)算服務(wù)器之間的同步要求瘫辩。當(dāng)提供給初始化函數(shù)get_init_tokens_op的參數(shù)大于0時(shí),TensorFlow支持多次使用由同一個計(jì)算服務(wù)器得到的梯度赡矢,于是也可以緩解計(jì)算服務(wù)器性能瓶頸的問題杭朱。
4.3使用Caicloud運(yùn)行分布式TensorFlow
從上一小節(jié)中給出的樣例程序可以看出,每次運(yùn)行分布式TensorFlow都需要登錄不同的機(jī)器來啟動集群吹散。這使用起來非常不方便弧械。當(dāng)需要使用100臺機(jī)器運(yùn)行分布式TensorFlow時(shí),需要手動登錄到每一臺機(jī)器并啟動TensorFlow服務(wù)空民,這個過程十分繁瑣刃唐。而且,當(dāng)某個服務(wù)器上的程序死掉之后界轩,TensorFlow并不能自動重啟画饥,這給監(jiān)控工作帶來了巨大的難題。如果類比TensorFlow與Hadoop浊猾,可以發(fā)現(xiàn)TensorFlow只實(shí)現(xiàn)了相當(dāng)于Hadoop中MapReduce的計(jì)算框架抖甘,而沒有提供類似Yarn的集群管理工具以及HDFS的存儲系統(tǒng)。為了降低分布式TensorFlow的使用門檻葫慎,才云科技基于Kubernetes容器云平臺提供了一個分布式TensorFlow平臺TensorFlow as Service(Taas)衔彻。本節(jié)中將大致介紹如何使用Caicloud提供的Taas平臺運(yùn)行分布式TensorFlow薇宠。
從上一小節(jié)中給出的代碼可以看出,編寫分布式TensorFlow程序需要制定很多與模型訓(xùn)練無關(guān)的代碼來完成TensorFlow集群的設(shè)置工作艰额。為了降低分布式TensorFlow的學(xué)習(xí)成本澄港。Caicloud的TensorFlow as a Service(Taas)平臺首先對TensorFlow集群進(jìn)行了更高層的封裝,屏蔽了其中與模型訓(xùn)練無關(guān)的底層細(xì)節(jié)柄沮。其次回梧,Taas平臺結(jié)合了谷歌開源的容器云平臺管理工具Kubernetes來實(shí)現(xiàn)對分布式TensorFlow任務(wù)的管理和監(jiān)控,并支持通過UI設(shè)置分布式TensorFlow任務(wù)的節(jié)點(diǎn)個數(shù)祖搓、是否使用GPU等信息狱意。
Caicloud的Taas平臺提供了一個抽象基類CaicloudDistTensorflowBase,該類封裝了分布式TensorFlow集群的配置與啟動、模型參數(shù)共享與更新邏輯處理棕硫、計(jì)算節(jié)點(diǎn)之間的協(xié)同交互以及訓(xùn)練得到的模型和日志的保存等與模型訓(xùn)練過程無關(guān)的操作髓涯。用戶只需要繼承該基類,并實(shí)現(xiàn)與模型訓(xùn)練相關(guān)的函數(shù)即可哈扮。其代碼結(jié)構(gòu)如下:
用戶繼承的類需要選擇性地實(shí)現(xiàn)CaicloudDistTensorflowBase基類中的4個函數(shù),它們分別是build_model/get_init_fn/train和after_train蚓再。build_model給出了定義計(jì)算圖的接口滑肉。在這個函數(shù)中,用戶需要處理輸入數(shù)據(jù)摘仅、定義深度學(xué)習(xí)模型以及定義訓(xùn)練模型的過程靶庙。get_init_fn函數(shù)中可以定義在會話(tf.Session)生成之后需要額外完成的初始化工作,當(dāng)沒有特殊的初始化操作時(shí)娃属,用戶可以不用定義這個函數(shù)六荒。這個函數(shù)可以完成模型預(yù)加載的過程。train函數(shù)中定義的是每一輪訓(xùn)練中需要運(yùn)行的操作矾端。TaaS會自動完成迭代的過程掏击,但用戶需要定義每一輪迭代中需要執(zhí)行的操作。最后在訓(xùn)練結(jié)束之后秩铆,用戶可以通過定義after_train來評測以及保存最后得到的模型砚亭。下面將通過TaaS平臺實(shí)現(xiàn)分布式TensorFlow訓(xùn)練過程來解決MNIST問題。
代碼就不寫殴玛,直接粘貼了:
將以上代碼提交到Caicloud的TaaS平臺之后捅膘,可以看到類似下圖所示的監(jiān)控頁面。在監(jiān)控頁面中滚粟,TaaS提供了對資源利用率寻仗、訓(xùn)練進(jìn)度、訓(xùn)練模式凡壤、程序日志以及TensorBoard等多種信息的綜合展示署尤,用戶可以更好地了解訓(xùn)練的進(jìn)度和狀態(tài)蔬咬。因?yàn)槠奁直妫瑢aaS感興趣的讀者可以在Caicloud的官方網(wǎng)站caicloud.io上找到更多信息和教程式塌。
Caicloud提供的TaaS分布式TensorFlow任務(wù)詳情頁面
作者:逆蒼穹
鏈接:http://www.reibang.com/p/911ce4d180cd
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)宪塔,非商業(yè)轉(zhuǎn)載請注明出處混坞。