1. 程式人生 > >TensorFlow之多核GPU的並行運算

TensorFlow之多核GPU的並行運算

形式 特征 上下文 函數 輸出 過期 zip 最大值 多核

tensorflow多GPU並行計算

TensorFlow可以利用GPU加速深度學習模型的訓練過程,在這裏介紹一下利用多個GPU或者機器時,TensorFlow是如何進行多GPU並行計算的。

首先,TensorFlow並行計算分為:模型並行,數據並行。模型並行是指根據不同模型設計不同的並行方式,模型不同計算節點放在不同GPU或者機器上進行計算。數據並行是比較通用簡便的實現大規模並行方式,同時使用多個硬件資源計算不同batch數據梯度,匯總梯度進行全局參數更新。

在這裏我們主要介紹數據並行的多GPU並行方法。數據並行,多塊GPU同時訓練多個batch數據,運行在每塊GPU上的模型基於同一神經網絡,網絡結構一樣,共享模型參數。數據並行也分為兩個部分,同步數據並行和異步數據並行。

在每一輪叠代中,前向傳播算法會根據當前參數的取值,計算出在一小部分訓練數據上的預測值,然後反向傳播算法,根據loss function計算參數的梯度並且更新參數。而不同的數據並行模式的區別在於參數的更新方式不同。

1.數據異步並行

技術分享圖片

圖一:數據異步並行模式流程圖

從圖一可以看出,在每一輪叠代時候,不同的設備會讀取參數最新的取值。但是因為不同的設備,讀取參數取值的時間不一樣,得到的值也有可能不一樣。

也就是說數據異步並行模式根據當前參數的取值和隨機獲取的一小部分數據數據在不同設備上各自運行,不等待所有GPU完成一次訓練,哪個GPU完成訓練,立即將梯度更新到共享模型參數。

2.數據同步並行

技術分享圖片

圖二:數據同步並行模式流程圖

與數據異步並行模式不同的是數據同步並行模式在所有設備完成反向傳播的計算之後,需要計算出不同設備上參數梯度的平均值,最後在根據平均值對參數進行更新。

總結:

並行分為:模型並行和數據並行,數據並行又分為數據同步並行和數據異步並行。數據同步並行需要等所有的GPU都通過當前批次的訓練語料求得損失值進而求得梯度之後對所有的GPU所得的梯度求均值,然後再以此均值進行參數的更新。

具體的分析可參考:https://blog.csdn.net/qq_29462849/article/details/81185126

優缺點的比較:

主要應用的是數據的並行化處理,因此主要是對數據的並行化處理的兩種方法進行比較。數據同步並行,同步更新的信息開銷很大,有時並不一定比直接利用一個GPU進行運算快,同時存在短板效應,所需的時間是由性能最差的那個GPU所決定。雖然其每個批次的計算時間由於信息開銷的原因會變大,但是其每次對參數更新相當於以batch_size*GPU個數這麽多的數據對參數進行更新,實際上相當於應用了更大批次的數據,這樣也能解決批次的大小限制(若批次數據過大可能顯存不夠的情況)。數據異步並行會存在過期梯度的問題。

並行化代碼的實現:

先分析下一個NLP方面的神經網絡分類任務大體的流程:

一般NLP任務會輸入一個三維的向量第一維mini-batch的大小,第二維人為設定的句子的最大長度,第三維詞向量,數據輸入之後會經過各種類型的網絡(相同於前饋神經網絡的功能)得到的向量是對原文的特征表示。因為NLP任務屬於對序列文本的處理一般使用的網絡要能夠獲得上下文信息也即為能夠很好的對序列信息進行處理,因為學到了上下文信息,例如用LSTM,得到LSTM的輸出後要把0,1維進行轉換,轉換為時間批次優先的形式,取結果的[-1]得到一個二維的向量第一維是批次大小,第二維是隱藏節點的個數,此時這個二維數據就是對輸入原文數據的表示,對於應用較多的分類任務,隨後會把對原文的特征表示做為輸入,經過一個線性變換進行分類。得到二維數據pred,第一維是批次大小,第二維是類別的個數。再用精度進行結果的評估代碼為:

correct_pred=tf.equal(tf.argmax(pred,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.cast(correct_pred,tf.float32))

因為pred是二維的,第二維的數據表示預測為各個類別的概率大小,這裏可以用個softmax進行概率歸一化也可不用,tf.argmax(pred,1)其作用為得到第二維最大值的那個下腳標,返回的值是一維的[batch]即為預測的是何種類別,tf.equal用於對比預測類別和正確的類別標簽,返回的也是一維的[batch]但是bool類型的,隨後accuracy=tf.reduce_mean(tf.cast(correct_pred,tf.float32))是精度的計算,先把bool類型的轉換為float類型數據,再求均值即可。直到這裏正向傳播結束,隨後需要進行反向傳播,對模型中的參數進行更新,使獲得的對於原文的表示outputs(從訓練文本中抽取的特征)越來越優,以便於分類的結果越來越精確。反向傳播即為通過優化函數對網絡模型中的參數進行更新,一般優化的過程都是通過設定的目標函數,對目標函數進行優化,對模型中的參數進行求導,以此來更新參數。損失函數一般選用交叉熵函數

cost=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=pred))

通過如上代碼獲得損失值,交叉熵函數是用於求兩個概率分布之間的距離,因此要對pred結果通過softmax轉換為概率分布的數據。獲得損失值傳入優化函數對模型進行優化。這裏的tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=pred)獲得的數據是[batch]的,然後對其求均值得到損失值,這就體現了通過一個batch的數據對參數進行更新。

#反向傳播優化函數
optimizer=tf.train.AdamOptimizer(learning_rate=FLAGS.init_learning_rate).minimize(cost)

優化函數會對參數進行優化,一般都是通過設置運行優化函數的次數epoch來控制何時停止對參數進行更新,epoch的設置可以根據精度值進行判斷具體的大小。這裏的minimize完成了計算梯度和應用梯度的過程(傳入的cost會用於求梯度,具體源碼中沒看懂),AdamOptimizer是確定選取何種方法對參數進行更新,因為它在對參數進行更新的過程中會自動調節學習率,故學習率可以不設置為衰減的(這一般在SGD中使用。)

主要代碼的改變在於梯度的計算相關的方面:

平均梯度的計算:

 1 def average_grident(tower_grads):
 2     average_grads = []
 3     for val_and_grad in zip(*tower_grads):
 4         grads = []
 5         for g,_ in val_and_grad:
 6             grad = tf.expand_dims(g, 0)
 7             grads.append(grad)
 8         grad = tf.concat(grads, 0)
 9         grad = tf.reduce_mean(grad, 0)
10         v = val_and_grad[0][1]
11         grad_and_var = (grad, v)
12         average_grads.append(grad_and_var)
13     return average_grads

需要更新的是反向傳播的部分:

tower_grad = []
#計算損失值選擇優化器
#反向傳播優化函數
global_step = tf.Variable(0, name="global_step", trainable=False)
optimizer=tf.train.AdamOptimizer(learning_rate=FLAGS.init_learning_rate)
#以下默認使用的兩個GPU,batch_size是128
for i in range(2):
    with tf.device(/gpu:%d % i):
        if i==0:
            cur_loss=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y[:64],logits=pred[:64]))
            grads = optimizer.compute_gradients(cur_loss)
            tower_grad.append(grads)
        else:
            cur_loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y[64:], logits=pred[64:]))
            grads = optimizer.compute_gradients(cur_loss)
            tower_grad.append(grads)
grads = average_grident(tower_grad)
train_op = optimizer.apply_gradients(grads,global_step=global_step)

TensorFlow之多核GPU的並行運算