實戰(zhàn)Google深度學(xué)習(xí)框架:TensorFlow計算加速
要將深度學(xué)習(xí)應(yīng)用到實際問題中,一個非常大的問題在于訓(xùn)練深度學(xué)習(xí)模型需要的計算量太大。比如Inception-v3模型在單機上訓(xùn)練到78%的正確率需要將近半年的時間 ,這樣的訓(xùn)練速度是完全無法應(yīng)用到實際生產(chǎn)中的。為了加速訓(xùn)練過程,本章將介紹如何通過TensorFlow利用GPU或/和分布式計算進行模型訓(xùn)練。本文節(jié)選自《TensorFlow:實戰(zhàn)Google深度學(xué)習(xí)框架》第十章。
本文將介紹如何在TensorFlow中使用單個GPU進行計算加速,也將介紹生成TensorFlow會話(tf.Session)時的一些常用參數(shù)。通過這些參數(shù)可以使調(diào)試更加方便而且程序的可擴展性更好。然而,在很多情況下,單個GPU的加速效率無法滿足訓(xùn)練大型深度學(xué)習(xí)模型的計算量需求,這時將需要利用更多的計算資源。為了同時利用多個GPU或者多臺機器,10.2節(jié)中將介紹訓(xùn)練深度學(xué)習(xí)模型的并行方式。然后,10.3節(jié)將介紹如何在一臺機器的多個GPU上并行化地訓(xùn)練深度學(xué)習(xí)模型。在這一節(jié)中也將給出具體的TensorFlow樣例程序來使用多GPU訓(xùn)練模型,并比較并行化效率提升的比率。最后在10.4節(jié)中將介紹分布式TensorFlow,以及如何通過分布式TensorFlow訓(xùn)練深度學(xué)習(xí)模型。在這一節(jié)中將給出具體的TensorFlow樣例程序來實現(xiàn)不同的分布式深度學(xué)習(xí)訓(xùn)練模式。雖然TensorFlow可以支持分布式深度學(xué)習(xí)模型訓(xùn)練,但是它并不提供集群創(chuàng)建、管理等功能。為了更方便地使用分布式TensorFlow,10.4節(jié)中將介紹才云科技基于Kubernetes容器云平臺搭建的分布式TensorFlow系統(tǒng)。
01
TensorFlow使用GPU
TensorFlow程序可以通過tf.device函數(shù)來指定運行每一個操作的設(shè)備,這個設(shè)備可以是本地的CPU或者GPU,也可以是某一臺遠(yuǎn)程的服務(wù)器。但在本節(jié)中只關(guān)心本地的設(shè)備。TensorFlow會給每一個可用的設(shè)備一個名稱,tf.device函數(shù)可以通過設(shè)備的名稱來指定執(zhí)行運算的設(shè)備。比如CPU在TensorFlow中的名稱為/cpu:0。在默認(rèn)情況下,即使機器有多個CPU,TensorFlow也不會區(qū)分它們,所有的CPU都使用/cpu:0作為名稱。而一臺機器上不同GPU的名稱是不同的,第n個GPU在TensorFlow中的名稱為/gpu:n。比如第一個GPU的名稱為/gpu:0,第二個GPU名稱為/gpu:1,以此類推。
TensorFlow提供了一個快捷的方式來查看運行每一個運算的設(shè)備。在生成會話時,可以通過設(shè)置log_device_placement參數(shù)來打印運行每一個運算的設(shè)備。下面的程序展示了如何使用log_device_placement這個參數(shù)。
'a'
在以上代碼中,TensorFlow程序生成會話時加入了參數(shù)log_device_placement=True,所以程序會將運行每一個操作的設(shè)備輸出到屏幕。于是除了可以看到最后的計算結(jié)果之外,還可以看到類似“add: /job:localhost/replica:0/task:0/cpu:0”這樣的輸出。這些輸出顯示了執(zhí)行每一個運算的設(shè)備。比如加法操作add是通過CPU來運行的,因為它的設(shè)備名稱中包含了/cpu:0。
在配置好GPU環(huán)境的TensorFlow中 ,如果操作沒有明確地指定運行設(shè)備,那么TensorFlow會優(yōu)先選擇GPU。比如將以上代碼在亞馬遜(Amazon Web Services, AWS)的 g2.8xlarge實例上運行時,會得到以下運行結(jié)果。
name
從上面的輸出可以看到在配置好GPU環(huán)境的TensorFlow中,TensorFlow會自動優(yōu)先將運算放置在GPU上。不過,盡管g2.8xlarge實例有4個GPU,在默認(rèn)情況下,TensorFlow只會將運算優(yōu)先放到/gpu:0上。于是可以看見在上面的程序中,所有的運算都被放在了/gpu:0上。如果需要將某些運算放到不同的GPU或者CPU上,就需要通過tf.device來手工指定。下面的程序給出了一個通過tf.device手工指定運行設(shè)備的樣例。
在以上代碼中可以看到生成常量a和b的操作被加載到了CPU上,而加法操作被放到了第二個GPU“/gpu:1”上。在TensorFlow中,不是所有的操作都可以被放在GPU上,如果強行將無法放在GPU上的操作指定到GPU上,那么程序?qū)箦e。以下代碼給出了一個報錯的樣例。
_cpu = tf.Variable(0, name="a_
不同版本的TensorFlow對GPU的支持不一樣,如果程序中全部使用強制指定設(shè)備的方式會降低程序的可移植性。在TensorFlow的kernel 中定義了哪些操作可以跑在GPU上。比如可以在variable_ops.cc程序中找到以下定義。
REGISTER_GPU_KERNELS(type)
在這段定義中可以看到GPU只在部分?jǐn)?shù)據(jù)類型上支持tf.Variable操作。如果在TensorFlow代碼庫中搜索調(diào)用這段代碼的宏TF_CALL_GPU_NUMBER_TYPES,可以發(fā)現(xiàn)在GPU上,tf.Variable操作只支持實數(shù)型(float16、float32和double)的參數(shù)。而在報錯的樣例代碼中給定的參數(shù)是整數(shù)型的,所以不支持在GPU上運行。為避免這個問題,TensorFlow在生成會話時可以指定allow_soft_placement參數(shù)。當(dāng)allow_soft_placement參數(shù)設(shè)置為True時,如果運算無法由GPU執(zhí)行,那么TensorFlow會自動將它放到CPU上執(zhí)行。以下代碼給出了一個使用allow_soft_placement參數(shù)的樣例。
雖然GPU可以加速TensorFlow的計算,但一般來說不會把所有的操作全部放在GPU上。一個比較好的實踐是將計算密集型的運算放在GPU上,而把其他操作放到CPU上。GPU是機器中相對獨立的資源,將計算放入或者轉(zhuǎn)出GPU都需要額外的時間。而且GPU需要將計算時用到的數(shù)據(jù)從內(nèi)存復(fù)制到GPU設(shè)備上,這也需要額外的時間。TensorFlow可以自動完成這些操作而不需要用戶特別處理,但為了提高程序運行的速度,用戶也需要盡量將相關(guān)的運算放在同一個設(shè)備上。
02
深度學(xué)習(xí)訓(xùn)練并行模式
TensorFlow可以很容易地利用單個GPU加速深度學(xué)習(xí)模型的訓(xùn)練過程,但要利用更多的GPU或者機器,需要了解如何并行化地訓(xùn)練深度學(xué)習(xí)模型。常用的并行化深度學(xué)習(xí)模型訓(xùn)練方式有兩種,同步模式和異步模式。本節(jié)中將介紹這兩種模式的工作方式及其優(yōu)劣。
為幫助讀者理解這兩種訓(xùn)練模式,本節(jié)首先簡單回顧一下如何訓(xùn)練深度學(xué)習(xí)模型。圖10-1展示了深度學(xué)習(xí)模型的訓(xùn)練流程圖。深度學(xué)習(xí)模型的訓(xùn)練是一個迭代的過程。在每一輪迭代中,前向傳播算法會根據(jù)當(dāng)前參數(shù)的取值計算出在一小部分訓(xùn)練數(shù)據(jù)上的預(yù)測值,然后反向傳播算法再根據(jù)損失函數(shù)計算參數(shù)的梯度并更新參數(shù)。在并行化地訓(xùn)練深度學(xué)習(xí)模型時,不同設(shè)備(GPU或CPU)可以在不同訓(xùn)練數(shù)據(jù)上運行這個迭代的過程,而不同并行模式的區(qū)別在于不同的參數(shù)更新方式。
圖10-2展示了異步模式的訓(xùn)練流程圖。從圖10-2中可以看到,在每一輪迭代時,不同設(shè)備會讀取參數(shù)最新的取值,但因為不同設(shè)備讀取參數(shù)取值的時間不一樣,所以得到的值也有可能不一樣。根據(jù)當(dāng)前參數(shù)的取值和隨機獲取的一小部分訓(xùn)練數(shù)據(jù),不同設(shè)備各自運行反向傳播的過程并獨立地更新參數(shù)。可以簡單地認(rèn)為異步模式就是單機模式復(fù)制了多份,每一份使用不同的訓(xùn)練數(shù)據(jù)進行訓(xùn)練。在異步模式下,不同設(shè)備之間是完全獨立的。
深度學(xué)習(xí)模型訓(xùn)練流程圖
異步模式深度學(xué)習(xí)模型訓(xùn)練流程圖
然而使用異步模式訓(xùn)練的深度學(xué)習(xí)模型有可能無法達到較優(yōu)的訓(xùn)練結(jié)果。圖10-3中給出了一個具體的樣例來說明異步模式的問題。其中黑色曲線展示了模型的損失函數(shù),黑色小球表示了在t0時刻參數(shù)所對應(yīng)的損失函數(shù)的大小。假設(shè)兩個設(shè)備d0和d1在時間t0同時讀取了參數(shù)的取值,那么設(shè)備d0和d1計算出來的梯度都會將小黑球向左移動。假設(shè)在時間t1設(shè)備d0已經(jīng)完成了反向傳播的計算并更新了參數(shù),修改后的參數(shù)處于圖10-3中小灰球的位置。然而這時的設(shè)備d1并不知道參數(shù)已經(jīng)被更新了,所以在時間t2時,設(shè)備d1會繼續(xù)將小球向左移動,使得小球的位置達到圖10-3中小白球的地方。從圖10-3中可以看到,當(dāng)參數(shù)被調(diào)整到小白球的位置時,將無法達到最優(yōu)點。
異步模式訓(xùn)練深度學(xué)習(xí)模型存在的問題示意圖
同步模式深度學(xué)習(xí)模型訓(xùn)練流程圖
為了避免更新不同步的問題,可以使用同步模式。在同步模式下,所有的設(shè)備同時讀取參數(shù)的取值,并且當(dāng)反向傳播算法完成之后同步更新參數(shù)的取值。單個設(shè)備不會單獨對參數(shù)進行更新,而會等待所有設(shè)備都完成反向傳播之后再統(tǒng)一更新參數(shù) 。圖10-4展示了同步模式的訓(xùn)練過程。從圖10-4中可以看到,在每一輪迭代時,不同設(shè)備首先統(tǒng)一讀取當(dāng)前參數(shù)的取值,并隨機獲取一小部分?jǐn)?shù)據(jù)。然后在不同設(shè)備上運行反向傳播過程得到在各自訓(xùn)練數(shù)據(jù)上參數(shù)的梯度。注意雖然所有設(shè)備使用的參數(shù)是一致的,但是因為訓(xùn)練數(shù)據(jù)不同,所以得到參數(shù)的梯度就可能不一樣。當(dāng)所有設(shè)備完成反向傳播的計算之后,需要計算出不同設(shè)備上參數(shù)梯度的平均值,最后再根據(jù)平均值對參數(shù)進行更新。