(第一部分 機器學習基礎(chǔ))
第01章 機器學習概覽
第02章 一個完整的機器學習項目(上)
第02章 一個完整的機器學習項目(下)
第03章 分類
第04章 訓練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學習和隨機森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學習)
第9章 啟動和運行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強化學習(上)
第16章 強化學習(下)
在第 11 章,我們討論了幾種可以明顯加速訓練的技術(shù):更好的權(quán)重初始化,批量標準化,復雜的優(yōu)化器等等。 但是,即使采用了所有這些技術(shù),在具有單個 CPU 的單臺機器上訓練大型神經(jīng)網(wǎng)絡(luò)可能需要幾天甚至幾周的時間。
在本章中,我們將看到如何使用 TensorFlow 在多個設(shè)備(CPU 和 GPU)上分配計算并將它們并行運行(參見圖 12-1)。 首先,我們會先在一臺機器上的多個設(shè)備上分配計算,然后在多臺機器上的多個設(shè)備上分配計算。
與其他神經(jīng)網(wǎng)絡(luò)框架相比,TensorFlow 對分布式計算的支持是其主要亮點之一。 它使您可以完全控制如何跨設(shè)備和服務(wù)器分布(或復制)您的計算圖,并且可以讓您以靈活的方式并行和同步操作,以便您可以在各種并行方法之間進行選擇。
我們來看一些最流行的方法來并行執(zhí)行和訓練一個神經(jīng)網(wǎng)絡(luò),這讓我們不再需要等待數(shù)周才能完成訓練算法,而最終可能只會等待幾個小時。 這不僅可以節(jié)省大量時間,還意味著您可以更輕松地嘗試各種模型,并經(jīng)常重新訓練模型上的新數(shù)據(jù)。
還有其他很好的并行化例子,包括當我們在微調(diào)模型時可以探索更大的超參數(shù)空間,并有效地運行大規(guī)模神經(jīng)網(wǎng)絡(luò)。
但我們必須先學會走路才能跑步。 我們先從一臺機器上的幾個 GPU 上并行化簡單圖形開始。
一臺機器上多設(shè)備
只需添加 GPU 顯卡到單個機器,您就可以獲得主要的性能提升。 事實上,在很多情況下,這就足夠了。 你根本不需要使用多臺機器。 例如,通常在單臺機器上使用 8 個 GPU,而不是在多臺機器上使用 16 個 GPU(由于多機器設(shè)置中的網(wǎng)絡(luò)通信帶來的額外延遲),可以同樣快地訓練神經(jīng)網(wǎng)絡(luò)。
在本節(jié)中,我們將介紹如何設(shè)置您的環(huán)境,以便 TensorFlow 可以在一臺機器上使用多個 GPU 卡。 然后,我們將看看如何在可用設(shè)備上進行分布操作,并且并行執(zhí)行它們。
安裝
為了在多個 GPU 卡上運行 TensorFlow,首先需要確保 GPU 卡具有 NVidia 計算能力(大于或等于3.0)。 這包括 Nvidia 的 Titan,Titan X,K20 和 K40(如果你擁有另一張卡,你可以在 https://developer.nvidia.com/cuda-gpus 查看它的兼容性)。
提示:
如果您不擁有任何 GPU 卡,則可以使用具有 GPU 功能的主機服務(wù)器,如 Amazon AWS。 在 ?igaAvsec 的博客文章中,提供了在 Amazon AWS GPU 實例上使用 Python 3.5 設(shè)置 TensorFlow 0.9 的詳細說明。將它更新到最新版本的 TensorFlow 應(yīng)該不會太難。 Google 還發(fā)布了一項名為 Cloud Machine Learning 的云服務(wù)來運行 TensorFlow 圖表。 2016 年 5 月,他們宣布他們的平臺現(xiàn)在包括配備張量處理器(TPU)的服務(wù)器,專門用于機器學習的處理器,比許多 GPU 處理 ML 任務(wù)要快得多。 當然,另一種選擇只是購買你自己的 GPU 卡。 Tim Dettmers 寫了一篇很棒的博客文章(http://timdettmers.com/2018/11/05/which-gpu-for-deep-learning/)來幫助你選擇,他會定期更新它。
您必須下載并安裝相應(yīng)版本的 CUDA 和 cuDNN 庫(如果您使用的是 TensorFlow 1.0.0,則為 CUDA 8.0 和 cuDNN 5.1),并設(shè)置一些環(huán)境變量,以便 TensorFlow 知道在哪里可以找到 CUDA 和 cuDNN。 詳細的安裝說明可能會相當迅速地更改,因此最好按照 TensorFlow 網(wǎng)站上的說明進行操作。
Nvidia 的 CUDA 允許開發(fā)者使用支持 CUDA 的 GPU 進行各種計算(不僅僅是圖形加速)。 Nvidia 的 CUDA 深度神經(jīng)網(wǎng)絡(luò)庫(cuDNN)是針對 DNN 的 GPU 加速原語庫。 它提供了常用 DNN 計算的優(yōu)化實現(xiàn),例如激活層,歸一化,前向和后向卷積以及池化(參見第 13 章)。 它是 Nvidia Deep Learning SDK 的一部分(請注意,它需要創(chuàng)建一個 Nvidia 開發(fā)者帳戶才能下載它)。 TensorFlow 使用 CUDA 和 cuDNN 來控制 GPU 卡并加速計算(見圖 12-2)。
您可以使用nvidia-smi
命令來檢查 CUDA 是否已正確安裝。 它列出了可用的 GPU 卡以及每張卡上運行的進程:
$ nvidia-smi
Wed Sep 16 09:50:03 2016
+------------------------------------------------------+
| NVIDIA-SMI 352.63 Driver Version: 352.63 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GRID K520 Off | 0000:00:03.0 Off | N/A |
| N/A 27C P8 17W / 125W | 11MiB / 4095MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
最后,您必須安裝支持 GPU 的 TensorFlow。 如果你使用virtualenv
創(chuàng)建了一個獨立的環(huán)境,你首先需要激活它:
$ cd $ML_PATH # Your ML working directory (e.g., $HOME/ml)
$ source env/bin/activate
然后安裝合適的支持 GPU 的 TensorFlow 版本:
$ pip3 install --upgrade tensorflow-gpu
現(xiàn)在您可以打開一個 Python shell 并通過導入 TensorFlow 并創(chuàng)建一個會話來檢查 TensorFlow 是否正確檢測并使用 CUDA 和 cuDNN:
>>> import tensorflow as tf
I [...]/dso_loader.cc:108] successfully opened CUDA library libcublas.so locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcudnn.so locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcufft.so locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcuda.so.1 locally
I [...]/dso_loader.cc:108] successfully opened CUDA library libcurand.so locally
>>> sess = tf.Session()
[...]
I [...]/gpu_init.cc:102] Found device 0 with properties:
name: GRID K520
major: 3 minor: 0 memoryClockRate (GHz) 0.797
pciBusID 0000:00:03.0
Total memory: 4.00GiB
Free memory: 3.95GiB
I [...]/gpu_init.cc:126] DMA: 0
I [...]/gpu_init.cc:136] 0: Y
I [...]/gpu_device.cc:839] Creating TensorFlow device
(/gpu:0) -> (device: 0, name: GRID K520, pci bus id: 0000:00:03.0)
看起來不錯!TensorFlow 檢測到 CUDA 和 cuDNN 庫,并使用 CUDA 庫來檢測 GPU 卡(在這種情況下是 Nvidia Grid K520 卡)。
管理 GPU 內(nèi)存
默認情況下,TensorFlow 會在您第一次運行圖形時自動獲取所有可用 GPU 中的所有 RAM,因此當?shù)谝粋€程序仍在運行時,您將無法啟動第二個 TensorFlow 程序。 如果你嘗試,你會得到以下錯誤:
E [...]/cuda_driver.cc:965] failed to allocate 3.66G (3928915968 bytes) from device: CUDA_ERROR_OUT_OF_MEMORY
一種解決方案是在不同的 GPU 卡上運行每個進程。 為此,最簡單的選擇是設(shè)置CUDA_VISIBLE_DEVICES
環(huán)境變量,以便每個進程只能看到對應(yīng)的 GPU 卡。 例如,你可以像這樣啟動兩個程序:
$ CUDA_VISIBLE_DEVICES=0,1 python3 program_1.py
# and in another terminal:
$ CUDA_VISIBLE_DEVICES=3,2 python3 program_2.py
程序 #1 只會看到 GPU 卡 0 和 1(分別編號為 0 和 1),程序 #2 只會看到 GPU 卡 2 和 3(分別編號為 1 和 0)。 一切正常工作(見圖 12-3)。
另一種選擇是告訴 TensorFlow 只抓取一小部分內(nèi)存。 例如,要使 TensorFlow 只占用每個 GPU 內(nèi)存的 40%,您必須創(chuàng)建一個ConfigProto
對象,將其gpu_options.per_process_gpu_memory_fraction
選項設(shè)置為 0.4,并使用以下配置創(chuàng)建session
:
config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.4
session = tf.Session(config=config)
現(xiàn)在像這樣的兩個程序可以使用相同的 GPU 卡并行運行(但不是三個,因為3×0.4> 1
)。 見圖 12-4。
如果在兩個程序都運行時運行nvidia-smi
命令,則應(yīng)該看到每個進程占用每個卡的總 RAM 大約 40%:
$ nvidia-smi
[...]
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| 0 5231 C python 1677MiB |
| 0 5262 C python 1677MiB |
| 1 5231 C python 1677MiB |
| 1 5262 C python 1677MiB |
[...]
另一種選擇是告訴 TensorFlow 只在需要時才抓取內(nèi)存。 為此,您必須將config.gpu_options.allow_growth
設(shè)置為True
。但是,TensorFlow 一旦抓取內(nèi)存就不會釋放內(nèi)存(以避免內(nèi)存碎片),因此您可能會在一段時間后內(nèi)存不足。 是否使用此選項可能難以確定,因此一般而言,您可能想要堅持之前的某個選項。
好的,現(xiàn)在你已經(jīng)有了一個支持 GPU 的 TensorFlow 安裝。 讓我們看看如何使用它!
設(shè)備布置操作
TensorFlow 白皮書介紹了一種友好的動態(tài)布置器算法,該算法能夠自動將操作分布到所有可用設(shè)備上,并考慮到以前運行圖中所測量的計算時間,估算每次操作的輸入和輸出張量的大小, 每個設(shè)備可用的 RAM,傳輸數(shù)據(jù)進出設(shè)備時的通信延遲,來自用戶的提示和約束等等。 不幸的是,這種復雜的算法是谷歌內(nèi)部的,它并沒有在 TensorFlow 的開源版本中發(fā)布。它被排除在外的原因似乎是,由用戶指定的一小部分放置規(guī)則實際上比動態(tài)放置器放置的更有效。 然而,TensorFlow 團隊正在努力改進它,并且最終可能會被開放。
在此之前,TensorFlow都是簡單的放置,它(如其名稱所示)非?;?。
簡單放置
無論何時運行圖形,如果 TensorFlow 需要求值尚未放置在設(shè)備上的節(jié)點,則它會使用簡單放置器將其放置在未放置的所有其他節(jié)點上。 簡單放置尊重以下規(guī)則:
- 如果某個節(jié)點已經(jīng)放置在圖形的上一次運行中的某個設(shè)備上,則該節(jié)點將保留在該設(shè)備上。
- 否則,如果用戶將一個節(jié)點固定到設(shè)備上(下面介紹),則放置器將其放置在該設(shè)備上。
- 否則,它默認為 GPU#0,如果沒有 GPU,則默認為 CPU。
正如您所看到的,將操作放在適當?shù)脑O(shè)備上主要取決于您。 如果您不做任何事情,整個圖表將被放置在默認設(shè)備上。 要將節(jié)點固定到設(shè)備上,您必須使用device()
函數(shù)創(chuàng)建一個設(shè)備塊。 例如,以下代碼將變量a
和常量b
固定在 CPU 上,但乘法節(jié)點c
不固定在任何設(shè)備上,因此將放置在默認設(shè)備上:
with tf.device("/cpu:0"):
a = tf.Variable(3.0)
b = tf.constant(4.0)
c = a * b
其中,"/cpu:0"
設(shè)備合計多 CPU 系統(tǒng)上的所有 CPU。 目前沒有辦法在特定 CPU 上固定節(jié)點或僅使用所有 CPU 的子集。
記錄放置位置
讓我們檢查一下簡單的放置器是否遵守我們剛剛定義的布局約束條件。 為此,您可以將log_device_placement
選項設(shè)置為True
;這告訴放置器在放置節(jié)點時記錄消息。例如:
>>> config = tf.ConfigProto()
>>> config.log_device_placement = True
>>> sess = tf.Session(config=config)
I [...] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GRID K520, pci bus id: 0000:00:03.0)
[...]
>>> x.initializer.run(session=sess)
I [...] a: /job:localhost/replica:0/task:0/cpu:0
I [...] a/read: /job:localhost/replica:0/task:0/cpu:0
I [...] mul: /job:localhost/replica:0/task:0/gpu:0
I [...] a/Assign: /job:localhost/replica:0/task:0/cpu:0
I [...] b: /job:localhost/replica:0/task:0/cpu:0
I [...] a/initial_value: /job:localhost/replica:0/task:0/cpu:0
>>> sess.run(c)
12
Info
中以大寫字母I
開頭的行是日志消息。 當我們創(chuàng)建一個會話時,TensorFlow 會記錄一條消息,告訴我們它已經(jīng)找到了一個 GPU 卡(在這個例子中是 Grid K520 卡)。 然后,我們第一次運行圖形(在這種情況下,當初始化變量a
時),簡單布局器運行,并將每個節(jié)點放置在分配給它的設(shè)備上。正如預期的那樣,日志消息顯示所有節(jié)點都放在"/cpu:0"
上,除了乘法節(jié)點,它以默認設(shè)備"/gpu:0"
結(jié)束(您可以先忽略前綴:/job:localhost/replica:0/task:0;
我們將在一會兒討論它)。 注意,我們第二次運行圖(計算c
)時,由于 TensorFlow 需要計算的所有節(jié)點c
都已經(jīng)放置,所以不使用布局器。
動態(tài)放置功能
創(chuàng)建設(shè)備塊時,可以指定一個函數(shù),而不是設(shè)備名稱。TensorFlow 會調(diào)用這個函數(shù)來進行每個需要放置在設(shè)備塊中的操作,并且該函數(shù)必須返回設(shè)備的名稱來固定操作。 例如,以下代碼將固定所有變量節(jié)點到"/cpu:0"
(在本例中只是變量a
)和所有其他節(jié)點到"/gpu:0"
:
def variables_on_cpu(op):
if op.type == "Variable":
return "/cpu:0"
else:
return "/gpu:0"
with tf.device(variables_on_cpu):
a = tf.Variable(3.0)
b = tf.constant(4.0)
c = a * b
您可以輕松實現(xiàn)更復雜的算法,例如以循環(huán)方式用GPU鎖定變量。
操作和內(nèi)核
對于在設(shè)備上運行的 TensorFlow 操作,它需要具有該設(shè)備的實現(xiàn);這被稱為內(nèi)核。 許多操作對于 CPU 和 GPU 都有內(nèi)核,但并非全部都是。 例如,TensorFlow 沒有用于整數(shù)變量的 GPU 內(nèi)核,因此當 TensorFlow 嘗試將變量i放置到 GPU#0 時,以下代碼將失?。?/p>
>>> with tf.device("/gpu:0"):
... i = tf.Variable(3)
[...]
>>> sess.run(i.initializer)
Traceback (most recent call last):
[...]
tensorflow.python.framework.errors.InvalidArgumentError: Cannot assign a device to node 'Variable': Could not satisfy explicit device specification
請注意,TensorFlow 推斷變量必須是int32
類型,因為初始化值是一個整數(shù)。 如果將初始化值更改為 3.0 而不是 3,或者如果在創(chuàng)建變量時顯式設(shè)置dtype = tf.float32
,則一切正常。
軟放置
默認情況下,如果您嘗試在操作沒有內(nèi)核的設(shè)備上固定操作,則當 TensorFlow 嘗試將操作放置在設(shè)備上時,您會看到前面顯示的異常。 如果您更喜歡 TensorFlow 回退到 CPU,則可以將allow_soft_placement
配置選項設(shè)置為True
:
with tf.device("/gpu:0"):
i = tf.Variable(3)
config = tf.ConfigProto()
config.allow_soft_placement = True
sess = tf.Session(config=config)
sess.run(i.initializer) # the placer runs and falls back to /cpu:0
到目前為止,我們已經(jīng)討論了如何在不同設(shè)備上放置節(jié)點。 現(xiàn)在讓我們看看 TensorFlow 如何并行運行這些節(jié)點。
并行運行
當 TensorFlow 運行圖時,它首先找出需要求值的節(jié)點列表,然后計算每個節(jié)點有多少依賴關(guān)系。 然后 TensorFlow 開始求值具有零依賴關(guān)系的節(jié)點(即源節(jié)點)。 如果這些節(jié)點被放置在不同的設(shè)備上,它們顯然會被并行求值。 如果它們放在同一個設(shè)備上,它們將在不同的線程中進行求值,因此它們也可以并行運行(在單獨的 GPU 線程或 CPU 內(nèi)核中)。
TensorFlow 管理每個設(shè)備上的線程池以并行化操作(參見圖 12-5)。 這些被稱為 inter-op 線程池。 有些操作具有多線程內(nèi)核:它們可以使用其他線程池(每個設(shè)備一個)稱為 intra-op 線程池(下面寫成內(nèi)部線程池)。
例如,在圖 12-5 中,操作A
,B
和C
是源操作,因此可以立即進行求值。 操作A
和B
放置在 GPU#0 上,因此它們被發(fā)送到該設(shè)備的內(nèi)部線程池,并立即進行并行求值。 操作A正好有一個多線程內(nèi)核; 它的計算被分成三部分,這些部分由內(nèi)部線程池并行執(zhí)行。 操作C
轉(zhuǎn)到 GPU#1 的內(nèi)部線程池。
一旦操作C
完成,操作D
和E
的依賴性計數(shù)器將遞減并且都將達到 0,因此這兩個操作將被發(fā)送到操作內(nèi)線程池以執(zhí)行。
您可以通過設(shè)置inter_op_parallelism_threads
選項來控制內(nèi)部線程池的線程數(shù)。 請注意,您開始的第一個會話將創(chuàng)建內(nèi)部線程池。 除非您將use_per_session_threads
選項設(shè)置為True
,否則所有其他會話都將重用它們。 您可以通過設(shè)置intra_op_parallelism_threads
選項來控制每個內(nèi)部線程池的線程數(shù)。
控制依賴關(guān)系
在某些情況下,即使所有依賴的操作都已執(zhí)行,推遲對操作的求值可能也是明智之舉。例如,如果它使用大量內(nèi)存,但在圖形中只需要更多內(nèi)存,則最好在最后一刻對其進行求值,以避免不必要地占用其他操作可能需要的 RAM。 另一個例子是依賴位于設(shè)備外部的數(shù)據(jù)的一組操作。 如果它們?nèi)客瑫r運行,它們可能會使設(shè)備的通信帶寬達到飽和,并最終導致所有等待 I/O。 其他需要傳遞數(shù)據(jù)的操作也將被阻止。 順序執(zhí)行這些通信繁重的操作將是比較好的,這樣允許設(shè)備并行執(zhí)行其他操作。
推遲對某些節(jié)點的求值,一個簡單的解決方案是添加控制依賴關(guān)系。 例如,下面的代碼告訴 TensorFlow 僅在求值完a
和b
之后才求值x
和y
:
a = tf.constant(1.0)
b = a + 2.0
with tf.control_dependencies([a, b]):
x = tf.constant(3.0)
y = tf.constant(4.0)
z = x + y
顯然,由于z
依賴于x
和y
,所以求值z
也意味著等待a
和b
進行求值,即使它并未顯式存在于control_dependencies()
塊中。 此外,由于b
依賴于a
,所以我們可以通過在[b]
而不是[a,b]
上創(chuàng)建控制依賴關(guān)系來簡化前面的代碼,但在某些情況下,“顯式比隱式更好”。
很好!現(xiàn)在你知道了:
- 如何以任何您喜歡的方式在多個設(shè)備上進行操作
- 這些操作如何并行執(zhí)行
- 如何創(chuàng)建控制依賴性來優(yōu)化并行執(zhí)行
是時候?qū)⒂嬎惴植荚诙鄠€服務(wù)器上了!
多個服務(wù)器的多個設(shè)備
要跨多臺服務(wù)器運行圖形,首先需要定義一個集群。 一個集群由一個或多個 TensorFlow 服務(wù)器組成,稱為任務(wù),通常分布在多臺機器上(見圖 12-6)。 每項任務(wù)都屬于一項作業(yè)。 作業(yè)只是一組通常具有共同作用的任務(wù),例如跟蹤模型參數(shù)(例如,參數(shù)服務(wù)器通常命名為"ps"
,parameter server)或執(zhí)行計算(這樣的作業(yè)通常被命名為"worker"
)。
以下集群規(guī)范定義了兩個作業(yè)"ps"
和"worker"
,分別包含一個任務(wù)和兩個任務(wù)。 在這個例子中,機器A托管著兩個 TensorFlow 服務(wù)器(即任務(wù)),監(jiān)聽不同的端口:一個是"ps"
作業(yè)的一部分,另一個是"worker"
作業(yè)的一部分。 機器B僅托管一臺 TensorFlow 服務(wù)器,這是"worker"
作業(yè)的一部分。
cluster_spec = tf.train.ClusterSpec({
"ps": [
"machine-a.example.com:2221", # /job:ps/task:0
],
"worker": [
"machine-a.example.com:2222", # /job:worker/task:0
"machine-b.example.com:2222", # /job:worker/task:1
]})
要啟動 TensorFlow 服務(wù)器,您必須創(chuàng)建一個服務(wù)器對象,并向其傳遞集群規(guī)范(以便它可以與其他服務(wù)器通信)以及它自己的作業(yè)名稱和任務(wù)編號。 例如,要啟動第一個輔助任務(wù),您需要在機器 A 上運行以下代碼:
server = tf.train.Server(cluster_spec, job_name="worker", task_index=0)
每臺機器只運行一個任務(wù)通常比較簡單,但前面的例子表明 TensorFlow 允許您在同一臺機器上運行多個任務(wù)(如果需要的話)。 如果您在一臺機器上安裝了多臺服務(wù)器,則需要確保它們不會全部嘗試抓取每個 GPU 的所有 RAM,如前所述。 例如,在圖12-6中,"ps"
任務(wù)沒有看到 GPU 設(shè)備,想必其進程是使用CUDA_VISIBLE_DEVICES =""
啟動的。 請注意,CPU由位于同一臺計算機上的所有任務(wù)共享。
如果您希望進程除了運行 TensorFlow 服務(wù)器之外什么都不做,您可以通過告訴它等待服務(wù)器使用join()
方法來完成,從而阻塞主線程(否則服務(wù)器將在您的主線程退出)。 由于目前沒有辦法阻止服務(wù)器,這實際上會永遠阻止:
server.join() # blocks until the server stops (i.e., never)
開始一個會話
一旦所有任務(wù)啟動并運行(但還什么都沒做),您可以從位于任何機器上的任何進程(甚至是運行中的進程)中的客戶機上的任何服務(wù)器上打開會話,并使用該會話像普通的本地會議一樣。比如:
a = tf.constant(1.0)
b = a + 2
c = a * 3
with tf.Session("grpc://machine-b.example.com:2222") as sess:
print(c.eval()) # 9.0
這個客戶端代碼首先創(chuàng)建一個簡單的圖形,然后在位于機器 B(我們稱之為主機)上的 TensorFlow 服務(wù)器上打開一個會話,并指示它求值c
。 主設(shè)備首先將操作放在適當?shù)脑O(shè)備上。 在這個例子中,因為我們沒有在任何設(shè)備上進行任何操作,所以主設(shè)備只將它們?nèi)糠旁谒约旱哪J設(shè)備上 - 在這種情況下是機器 B 的 GPU 設(shè)備。 然后它只是按照客戶的指示求值c
,并返回結(jié)果。
主機和輔助服務(wù)
客戶端使用 gRPC 協(xié)議(Google Remote Procedure Call)與服務(wù)器進行通信。 這是一個高效的開源框架,可以調(diào)用遠程函數(shù),并通過各種平臺和語言獲取它們的輸出。它基于 HTTP2,打開一個連接并在整個會話期間保持打開狀態(tài),一旦建立連接就可以進行高效的雙向通信。
數(shù)據(jù)以協(xié)議緩沖區(qū)的形式傳輸,這是另一種開源 Google 技術(shù)。 這是一種輕量級的二進制數(shù)據(jù)交換格式。
TensorFlow 集群中的所有服務(wù)器都可能與集群中的任何其他服務(wù)器通信,因此請確保在防火墻上打開適當?shù)亩丝凇?/p>
每臺 TensorFlow 服務(wù)器都提供兩種服務(wù):主服務(wù)和輔助服務(wù)。 主服務(wù)允許客戶打開會話并使用它們來運行圖形。 它協(xié)調(diào)跨任務(wù)的計算,依靠輔助服務(wù)實際執(zhí)行其他任務(wù)的計算并獲得結(jié)果。
固定任務(wù)的操作
通過指定作業(yè)名稱,任務(wù)索引,設(shè)備類型和設(shè)備索引,可以使用設(shè)備塊來鎖定由任何任務(wù)管理的任何設(shè)備上的操作。 例如,以下代碼將a
固定在"ps"
作業(yè)(即機器 A 上的 CPU)中第一個任務(wù)的 CPU,并將b
固定在"worker"
作業(yè)的第一個任務(wù)管理的第二個 GPU (這是 A 機上的 GPU#1)。 最后,c
沒有固定在任何設(shè)備上,所以主設(shè)備將它放在它自己的默認設(shè)備上(機器 B 的 GPU#0 設(shè)備)。
with tf.device("/job:ps/task:0/cpu:0")
a = tf.constant(1.0)
with tf.device("/job:worker/task:0/gpu:1")
b = a + 2
c = a + b
如前所述,如果您省略設(shè)備類型和索引,則 TensorFlow 將默認為該任務(wù)的默認設(shè)備; 例如,將操作固定到"/job:ps/task:0"
會將其放置在"ps"
作業(yè)(機器 A 的 CPU)的第一個任務(wù)的默認設(shè)備上。 如果您還省略了任務(wù)索引(例如,"/job:ps"
),則 TensorFlow 默認為"/task:0"
。如果省略作業(yè)名稱和任務(wù)索引,則 TensorFlow 默認為會話的主任務(wù)。
跨多個參數(shù)服務(wù)器的分片變量
正如我們很快會看到的那樣,在分布式設(shè)置上訓練神經(jīng)網(wǎng)絡(luò)時,常見模式是將模型參數(shù)存儲在一組參數(shù)服務(wù)器上(即"ps"
作業(yè)中的任務(wù)),而其他任務(wù)則集中在計算上(即 ,"worker"
工作中的任務(wù))。 對于具有數(shù)百萬參數(shù)的大型模型,在多個參數(shù)服務(wù)器上分割這些參數(shù)非常有用,可以降低飽和單個參數(shù)服務(wù)器網(wǎng)卡的風險。 如果您要將每個變量手動固定到不同的參數(shù)服務(wù)器,那將非常繁瑣。 幸運的是,TensorFlow 提供了replica_device_setter()
函數(shù),它以循環(huán)方式在所有"ps"
任務(wù)中分配變量。 例如,以下代碼將五個變量引入兩個參數(shù)服務(wù)器:
with tf.device(tf.train.replica_device_setter(ps_tasks=2):
v1 = tf.Variable(1.0) # pinned to /job:ps/task:0
v2 = tf.Variable(2.0) # pinned to /job:ps/task:1
v3 = tf.Variable(3.0) # pinned to /job:ps/task:0
v4 = tf.Variable(4.0) # pinned to /job:ps/task:1
v5 = tf.Variable(5.0) # pinned to /job:ps/task:0
您不必傳遞ps_tasks
的數(shù)量,您可以傳遞集群spec = cluster_spec
,TensorFlow 將簡單計算"ps"
作業(yè)中的任務(wù)數(shù)。
如果您在塊中創(chuàng)建其他操作,則不僅僅是變量,TensorFlow 會自動將它們連接到"/job:worker"
,默認為第一個由"worker"
作業(yè)中第一個任務(wù)管理的設(shè)備。 您可以通過設(shè)置worker_device
參數(shù)將它們固定到其他設(shè)備,但更好的方法是使用嵌入式設(shè)備塊。 內(nèi)部設(shè)備塊可以覆蓋在外部塊中定義的作業(yè),任務(wù)或設(shè)備。 例如:
with tf.device(tf.train.replica_device_setter(ps_tasks=2)):
v1 = tf.Variable(1.0) # pinned to /job:ps/task:0 (+ defaults to /cpu:0)
v2 = tf.Variable(2.0) # pinned to /job:ps/task:1 (+ defaults to /cpu:0)
v3 = tf.Variable(3.0) # pinned to /job:ps/task:0 (+ defaults to /cpu:0)
[...]
s = v1 + v2 # pinned to /job:worker (+ defaults to task:0/gpu:0)
with tf.device("/gpu:1"):
p1 = 2 * s # pinned to /job:worker/gpu:1 (+ defaults to /task:0)
with tf.device("/task:1"):
p2 = 3 * s # pinned to /job:worker/task:1/gpu:1
這個例子假設(shè)參數(shù)服務(wù)器是純 CPU 的,這通常是這種情況,因為它們只需要存儲和傳送參數(shù),而不是執(zhí)行密集計算。
(負責這章的人沒有全部完成,等整理完最后一章,我再來補上)
(第一部分 機器學習基礎(chǔ))
第01章 機器學習概覽
第02章 一個完整的機器學習項目(上)
第02章 一個完整的機器學習項目(下)
第03章 分類
第04章 訓練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學習和隨機森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學習)
第9章 啟動和運行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強化學習(上)
第16章 強化學習(下)