接下來介紹在paddlepaddle中如何使用多CPU來加速訓練。html
接着前面幾節講的手寫數字識別部分,在啓動訓練前,加載數據和網絡結構的代碼部分均不變。python
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
現實生活中,咱們可能會遇到更復雜的機器學習、深度學習任務,須要運算速度更高的硬件(GPU、TPU),甚至同時使用多個機器共同訓練一個任務(多卡訓練和多機訓練)。算法
飛槳動態圖經過fluid.dygraph.guard(place=None)裏的place參數,設置在GPU上訓練仍是CPU上訓練,好比:json
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]
在工業實踐中,許多較複雜的任務須要使用更強大的模型。強大模型加上海量的訓練數據,常常致使模型訓練耗時嚴重。好比在計算機視覺分類任務中,訓練一個在ImageNet數據集上精度表現良好的模型,大概須要一週的時間,由於咱們須要不斷嘗試各類優化的思路和方案。若是每次訓練均要耗時1周,這會大大下降模型迭代的速度。在機器資源充沛的狀況下,咱們能夠採用分佈式訓練,大部分模型的訓練時間可壓縮到小時級別。服務器
分佈式訓練有兩種實現模式:模型並行和數據並行。網絡
模型並行是將一個網絡模型拆分爲多份,拆分後的模型分到多個設備上(GPU)訓練,每一個設備的訓練數據是相同的。 模型並行的方式通常適用於:架構
模型架構過大,完整的模型沒法放入單個GPU。2012年ImageNet大賽的冠軍模型AlexNet是模型並行的典型案例。因爲當時GPU內存較小,單個GPU不足以承擔AlexNet。研究者將AlexNet拆分爲兩部分放到兩個GPU上並行訓練。app
網絡模型的設計結構能夠並行化時,採用模型並行的方式。例如在計算機視覺目標檢測任務中,一些模型(YOLO9000)的邊界框迴歸和類別預測是獨立的,能夠將獨立的部分分在不一樣的設備節點上完成分佈式訓練。dom
說明:當前GPU硬件技術快速發展,深度學習使用的主流GPU的內存已經足以知足大多數的網絡模型需求,因此大多數狀況下使用數據並行的方式。
數據並行與模型並行不一樣,數據並行每次讀取多份數據,讀取到的數據輸入給多個設備(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
訓練完成後,程序會在指定的./mylog文件夾下產生四個worklog文件。每一個文件存放對應設備的訓練過程日誌,其中worklog.0的內容以下:
grep: warning: GREP_OPTIONS is deprecated; please use an alias or scriptdev_id 0I1104 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.0W1104 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].....