百度PaddlePaddle入門-14(多個CPU加速訓練)


接下來介紹在paddlepaddle中如何使用多CPU來加速訓練。

接着前面幾節講的手寫數字識別部分,在啟動訓練前,加載數據和網絡結構的代碼部分均不變。

  1 # 加載相關庫
  2 import os
  3 import random
  4 import paddle
  5 import paddle.fluid as fluid
  6 from paddle.fluid.dygraph.nn import Conv2D, Pool2D, FC
  7 import numpy as np
  8 from PIL import Image
  9 
 10 import gzip
 11 import json
 12 
 13 # 定義數據集讀取器
 14 def load_data(mode='train'):
 15 
 16     # 讀取數據文件
 17     datafile = './work/mnist.json.gz'
 18     print('loading mnist dataset from {} ......'.format(datafile))
 19     data = json.load(gzip.open(datafile))
 20     # 讀取數據集中的訓練集,驗證集和測試集
 21     train_set, val_set, eval_set = data
 22 
 23     # 數據集相關參數,圖片高度IMG_ROWS, 圖片寬度IMG_COLS
 24     IMG_ROWS = 28
 25     IMG_COLS = 28
 26     # 根據輸入mode參數決定使用訓練集,驗證集還是測試
 27     if mode == 'train':
 28         imgs = train_set[0]
 29         labels = train_set[1]
 30     elif mode == 'valid':
 31         imgs = val_set[0]
 32         labels = val_set[1]
 33     elif mode == 'eval':
 34         imgs = eval_set[0]
 35         labels = eval_set[1]
 36     # 獲得所有圖像的數量
 37     imgs_length = len(imgs)
 38     # 驗證圖像數量和標簽數量是否一致
 39     assert len(imgs) == len(labels), \
 40           "length of train_imgs({}) should be the same as train_labels({})".format(
 41                   len(imgs), len(labels))
 42 
 43     index_list = list(range(imgs_length))
 44 
 45     # 讀入數據時用到的batchsize
 46     BATCHSIZE = 100
 47 
 48     # 定義數據生成器
 49     def data_generator():
 50         # 訓練模式下,打亂訓練數據
 51         if mode == 'train':
 52             random.shuffle(index_list)
 53         imgs_list = []
 54         labels_list = []
 55         # 按照索引讀取數據
 56         for i in index_list:
 57             # 讀取圖像和標簽,轉換其尺寸和類型
 58             img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
 59             label = np.reshape(labels[i], [1]).astype('int64')
 60             imgs_list.append(img) 
 61             labels_list.append(label)
 62             # 如果當前數據緩存達到了batch size,就返回一個批次數據
 63             if len(imgs_list) == BATCHSIZE:
 64                 yield np.array(imgs_list), np.array(labels_list)
 65                 # 清空數據緩存列表
 66                 imgs_list = []
 67                 labels_list = []
 68 
 69         # 如果剩余數據的數目小於BATCHSIZE,
 70         # 則剩余數據一起構成一個大小為len(imgs_list)的mini-batch
 71         if len(imgs_list) > 0:
 72             yield np.array(imgs_list), np.array(labels_list)
 73 
 74     return data_generator
 75 
 76 
 77 # 定義模型結構
 78 class MNIST(fluid.dygraph.Layer):
 79      def __init__(self, name_scope):
 80          super(MNIST, self).__init__(name_scope)
 81          name_scope = self.full_name()
 82          # 定義卷積層,輸出通道20,卷積核大小為5,步長為1,padding為2,使用relu激活函數
 83          self.conv1 = Conv2D(name_scope, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
 84          # 定義池化層,池化核為2,采用最大池化方式
 85          self.pool1 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
 86          # 定義卷積層,輸出通道20,卷積核大小為5,步長為1,padding為2,使用relu激活函數
 87          self.conv2 = Conv2D(name_scope, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
 88          # 定義池化層,池化核為2,采用最大池化方式
 89          self.pool2 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
 90          # 定義全連接層,輸出節點數為10,激活函數使用softmax
 91          self.fc = FC(name_scope, size=10, act='softmax')
 92          
 93     # 定義網絡的前向計算過程
 94      def forward(self, inputs):
 95          x = self.conv1(inputs)
 96          x = self.pool1(x)
 97          x = self.conv2(x)
 98          x = self.pool2(x)
 99          x = self.fc(x)
100          return x
View Code

 

單GPU訓練

現實生活中,我們可能會遇到更復雜的機器學習、深度學習任務,需要運算速度更高的硬件(GPU、TPU),甚至同時使用多個機器共同訓練一個任務(多卡訓練和多機訓練)。

飛槳動態圖通過fluid.dygraph.guard(place=None)里的place參數,設置在GPU上訓練還是CPU上訓練,比如:

1 with fluid.dygraph.guard(place=fluid.CPUPlace()) #設置使用CPU資源訓神經網絡。
2 with fluid.dygraph.guard(place=fluid.CUDAPlace(0)) #設置使用GPU資源訓神經網絡,默認使用機器的第一個GPU。

下面是采用GPU訓練實例(就是前三行):

 1 #僅前3行代碼有所變化,在使用GPU時,可以將use_gpu變量設置成True
 2 use_gpu = True
 3 place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()
 4 
 5 with fluid.dygraph.guard(place):
 6     model = MNIST("mnist")
 7     model.train()
 8     #調用加載數據的函數
 9     train_loader = load_data('train')
10     
11     #四種優化算法的設置方案,可以逐一嘗試效果
12     optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01)
13     #optimizer = fluid.optimizer.MomentumOptimizer(learning_rate=0.01)
14     #optimizer = fluid.optimizer.AdagradOptimizer(learning_rate=0.01)
15     #optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.01)
16     
17     EPOCH_NUM = 2
18     for epoch_id in range(EPOCH_NUM):
19         for batch_id, data in enumerate(train_loader()):
20             #准備數據,變得更加簡潔
21             image_data, label_data = data
22             image = fluid.dygraph.to_variable(image_data)
23             label = fluid.dygraph.to_variable(label_data)
24             
25             #前向計算的過程
26             predict = model(image)
27             
28             #計算損失,取一個批次樣本損失的平均值
29             loss = fluid.layers.cross_entropy(predict, label)
30             avg_loss = fluid.layers.mean(loss)
31             
32             #每訓練了100批次的數據,打印下當前Loss的情況
33             if batch_id % 200 == 0:
34                 print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
35             
36             #后向傳播,更新參數的過程
37             avg_loss.backward()
38             optimizer.minimize(avg_loss)
39             model.clear_gradients()
40 
41     #保存模型參數
42     fluid.save_dygraph(model.state_dict(), 'mnist')
loading mnist dataset from ./work/mnist.json.gz ......
epoch: 0, batch: 0, loss is: [3.2412765]
epoch: 0, batch: 200, loss is: [0.3588875]
epoch: 0, batch: 400, loss is: [0.21310554]
epoch: 1, batch: 0, loss is: [0.34854925]
epoch: 1, batch: 200, loss is: [0.22530955]
epoch: 1, batch: 400, loss is: [0.20724224]

多CPU分布式訓練

在工業實踐中,許多較復雜的任務需要使用更強大的模型。強大模型加上海量的訓練數據,經常導致模型訓練耗時嚴重。比如在計算機視覺分類任務中,訓練一個在ImageNet數據集上精度表現良好的模型,大概需要一周的時間,因為我們需要不斷嘗試各種優化的思路和方案。如果每次訓練均要耗時1周,這會大大降低模型迭代的速度。在機器資源充沛的情況下,我們可以采用分布式訓練,大部分模型的訓練時間可壓縮到小時級別。

分布式訓練有兩種實現模式:模型並行數據並行

1. 模型並行

模型並行是將一個網絡模型拆分為多份,拆分后的模型分到多個設備上(GPU)訓練,每個設備的訓練數據是相同的。 模型並行的方式一般適用於

  1. 模型架構過大,完整的模型無法放入單個GPU。2012年ImageNet大賽的冠軍模型AlexNet是模型並行的典型案例。由於當時GPU內存較小,單個GPU不足以承擔AlexNet。研究者將AlexNet拆分為兩部分放到兩個GPU上並行訓練。

  2. 網絡模型的設計結構可以並行化時,采用模型並行的方式。例如在計算機視覺目標檢測任務中,一些模型(YOLO9000)的邊界框回歸和類別預測是獨立的,可以將獨立的部分分在不同的設備節點上完成分布式訓練。

說明:當前GPU硬件技術快速發展,深度學習使用的主流GPU的內存已經足以滿足大多數的網絡模型需求,所以大多數情況下使用數據並行的方式。

2. 數據並行

數據並行與模型並行不同,數據並行每次讀取多份數據,讀取到的數據輸入給多個設備(GPU)上的模型,每個設備上的模型是完全相同的。數據並行的方式與眾人拾柴火焰高的道理類似,如果把訓練數據比喻為磚頭,把一個設備(GPU)比喻為一個人,那單GPU訓練就是一個人在搬磚,多GPU訓練就是多個人同時搬磚,每次搬磚的數量倍數增加,效率呈倍數提升。但是注意到,每個設備的模型是完全相同的,但是輸入數據不同,每個設備的模型計算出的梯度是不同的,如果每個設備的梯度更新當前設備的模型就會導致下次訓練時,每個模型的參數都不同了,所以我們還需要一個梯度同步機制,保證每個設備的梯度是完全相同的

數據並行中有一個參數管理服務器(parameter server)收集來自每個設備的梯度更新信息,並計算出一個全局的梯度更新。當參數管理服務器收到來自訓練設備的梯度更新請求時,統一更新模型的梯度。

飛槳有便利的數據並行訓練方式,僅改動幾行代碼即可實現多GPU訓練,如果想了解飛槳數據並行的基本思想,可以參考官網文檔-https://www.paddlepaddle.org.cn/documentation/docs/zh/user_guides/howto/training/cluster_howto.html

用戶只需要對程序進行簡單修改,即可實現在多GPU上並行訓練。飛槳采用數據並行的實現方式,在訓練前,需要配置如下參數:

1.從環境變量獲取設備的ID,並指定給CUDAPlace

1 device_id = fluid.dygraph.parallel.Env().dev_id
2 place = fluid.CUDAPlace(device_id)

2.對定義的網絡做預處理,設置為並行模式

1 strategy = fluid.dygraph.parallel.prepare_context() ## 新增
2 model = MNIST("mnist")
3 model = fluid.dygraph.parallel.DataParallel(model, strategy)  ## 新增

3.定義多GPU訓練的reader,將每批次的數據平分到每個GPU上

1 valid_loader = paddle.batch(paddle.dataset.mnist.test(), batch_size=16, drop_last=true)
2 valid_loader = fluid.contrib.reader.distributed_batch_reader(valid_loader)

4.收集每批次訓練數據的loss,並聚合參數的梯度

1 avg_loss = mnist.scale_loss(avg_loss)  ## 新增
2 avg_loss.backward()
3 mnist.apply_collective_grads()         ## 新增

完整程序如下所示。

 1 def train_multi_gpu():
 2     
 3     ##修改1-從環境變量獲取使用GPU的序號
 4     place = fluid.CUDAPlace(fluid.dygraph.parallel.Env().dev_id)
 5 
 6     with fluid.dygraph.guard(place):
 7     
 8         ##修改2-對原模型做並行化預處理
 9         strategy = fluid.dygraph.parallel.prepare_context()
10         model = MNIST("mnist")
11         model = fluid.dygraph.parallel.DataParallel(model, strategy)
12 
13         model.train()
14 
15         #調用加載數據的函數
16         train_loader = load_data('train')
17         ##修改3-多GPU數據讀取,必須確保每個進程讀取的數據是不同的
18         train_loader = fluid.contrib.reader.distributed_batch_reader(train_loader)
19 
20         optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01)
21         EPOCH_NUM = 5
22         for epoch_id in range(EPOCH_NUM):
23             for batch_id, data in enumerate(train_loader()):
24                 #准備數據
25                 image_data, label_data = data
26                 image = fluid.dygraph.to_variable(image_data)
27                 label = fluid.dygraph.to_variable(label_data)
28 
29                 predict = model(image)
30 
31                 loss = fluid.layers.square_error_cost(predict, label)
32                 avg_loss = fluid.layers.mean(loss)
33 
34                 # 修改4-多GPU訓練需要對Loss做出調整,並聚合不同設備上的參數梯度
35                 avg_loss = mnist.scale_loss(avg_loss)
36                 avg_loss.backward()
37                 model.apply_collective_grads()
38                 # 最小化損失函數,清除本次訓練的梯度
39                 optimizer.minimize(avg_loss)
40                 model.clear_gradients()
41                 
42                 if batch_id % 200 == 0:
43                     print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
44 
45     #保存模型參數
46     fluid.save_dygraph(model.state_dict(), 'mnist')

啟動多GPU的訓練,需要在命令行設置一些參數變量。打開終端,運行如下命令:

1 $ python -m paddle.distributed.launch --selected_gpus=0,1,2,3 --log_dir ./mylog train_multi_gpu.py
  • paddle.distributed.launch表示啟動分布式運行。
  • 通過selected_gpus設置使用的GPU的序號。當前機器需要是多GPU卡的機器,通過命令watch nvidia-smi可以查看GPU的序號。
  • log_dir用於存放訓練的log,如果不設置,每個GPU上的訓練信息都會打印到屏幕。
  • 多GPU運行的腳本是:train_multi_gpu.py,包含上述修改過的train_multi_gpu()函數。

訓練完成后,程序會在指定的./mylog文件夾下產生四個worklog文件。每個文件存放對應設備的訓練過程日志,其中worklog.0的內容如下:

grep: warning: GREP_OPTIONS is deprecated; please use an alias or script
dev_id 0
I1104 06:25:04.377323 31961 nccl_context.cc:88] worker: 127.0.0.1:6171 is not ready, will retry after 3 seconds...
I1104 06:25:07.377645 31961 nccl_context.cc:127] init nccl context nranks: 3 local rank: 0 gpu id: 1↩
W1104 06:25:09.097079 31961 device_context.cc:235] Please NOTE: device: 1, CUDA Capability: 61, Driver API Version: 10.1, Runtime API Version: 9.0
W1104 06:25:09.104460 31961 device_context.cc:243] device: 1, cuDNN Version: 7.5.
start data reader (trainers_num: 3, trainer_id: 0)
epoch: 0, batch_id: 10, loss is: [0.47507238]
epoch: 0, batch_id: 20, loss is: [0.25089613]
epoch: 0, batch_id: 30, loss is: [0.13120805]
epoch: 0, batch_id: 40, loss is: [0.12122715]
epoch: 0, batch_id: 50, loss is: [0.07328521]
epoch: 0, batch_id: 60, loss is: [0.11860339]
epoch: 0, batch_id: 70, loss is: [0.08205047]
epoch: 0, batch_id: 80, loss is: [0.08192863]
epoch: 0, batch_id: 90, loss is: [0.0736289]
epoch: 0, batch_id: 100, loss is: [0.08607423]
start data reader (trainers_num: 3, trainer_id: 0)
epoch: 1, batch_id: 10, loss is: [0.07032011]
epoch: 1, batch_id: 20, loss is: [0.09687119]
epoch: 1, batch_id: 30, loss is: [0.0307216]
epoch: 1, batch_id: 40, loss is: [0.03884467]
epoch: 1, batch_id: 50, loss is: [0.02801813]
epoch: 1, batch_id: 60, loss is: [0.05751991]
epoch: 1, batch_id: 70, loss is: [0.03721186]
.....


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM