天天看點

飛槳深度學習筆記(五)第二章 一個案例吃透深度學習(下)

文章目錄

  • 第二章 一個案例吃透深度學習(下)
    • 資源配置
      • CPU還是GPU?
      • 分布式訓練
      • 完整代碼
    • 訓練調試與優化
      • 計算模型的分類準确率
      • 檢查模型訓練過程,識别潛在訓練問題
      • 加入校驗或測試,更好評價模型效果
      • 加入正則化項,避免模型過拟合
      • 可視化分析

第二章 一個案例吃透深度學習(下)

接着上一章的四個部分的優化方法,今天學習另外三部分的優化。

資源配置

CPU還是GPU?

飛槳動态圖通過fluid.dygraph.guard(place=None)裡的place參數,設定在GPU上訓練還是CPU上訓練。

with fluid.dygraph.guard(place=fluid.CPUPlace()) #設定使用CPU資源訓神經網絡。
with fluid.dygraph.guard(place=fluid.CUDAPlace(0)) #設定使用GPU資源訓神經網絡,預設使用伺服器的第一個GPU卡。"0"是GPU卡的編号,比如一台伺服器有的四個GPU卡,編号分别為0、1、2、3。
           

分布式訓練

分布式訓練有兩種實作模式:模型并行和資料并行。

模型并行的方式一般适用于如下兩個場景:

  • 模型架構過大: 完整的模型無法放入單個GPU。如2012年ImageNet大賽的冠軍模型AlexNet是模型并行的典型案例,由于當時GPU記憶體較小,單個GPU不足以承擔AlexNet,是以研究者将AlexNet拆分為兩部分放到兩個GPU上并行訓練。
  • 網絡模型的結構設計相對獨立: 當網絡模型的設計結構可以并行化時,采用模型并行的方式。如在計算機視覺目标檢測任務中,一些模型(如YOLO9000)的邊界框回歸和類别預測是獨立的,可以将獨立的部分放到不同的裝置節點上完成分布式訓練。

    資料并行的方式與衆人拾柴火焰高的道理類似,如果把訓練資料比喻為磚頭,把一個裝置(GPU)比喻為一個人,那單GPU訓練就是一個人在搬磚,多GPU訓練就是多個人同時搬磚,每次搬磚的數量倍數增加,效率呈倍數提升。值得注意的是,每個裝置的模型是完全相同的,但是輸入資料不同,是以每個裝置的模型計算出的梯度是不同的。如果每個裝置的梯度隻更新目前裝置的模型,就會導緻下次訓練時,每個模型的參數都不相同。是以我們還需要一個梯度同步機制,保證每個裝置的梯度是完全相同的。

    飛槳深度學習筆記(五)第二章 一個案例吃透深度學習(下)
    飛槳深度學習筆記(五)第二章 一個案例吃透深度學習(下)

完整代碼

def train_multi_gpu():
    
    ##修改1-從環境變量擷取使用GPU的序号
    place = fluid.CUDAPlace(fluid.dygraph.parallel.Env().dev_id)

    with fluid.dygraph.guard(place):
    
        ##修改2-對原模型做并行化預處理
        strategy = fluid.dygraph.parallel.prepare_context()
        model = MNIST()
        model = fluid.dygraph.parallel.DataParallel(model, strategy)

        model.train()

        #調用加載資料的函數
        train_loader = load_data('train')
        ##修改3-多GPU資料讀取,必須確定每個程序讀取的資料是不同的
        train_loader = fluid.contrib.reader.distributed_batch_reader(train_loader)

        optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())
        EPOCH_NUM = 5
        for epoch_id in range(EPOCH_NUM):
            for batch_id, data in enumerate(train_loader()):
                #準備資料
                image_data, label_data = data
                image = fluid.dygraph.to_variable(image_data)
                label = fluid.dygraph.to_variable(label_data)

                predict = model(image)

                loss = fluid.layers.cross_entropy(predict, label)
                avg_loss = fluid.layers.mean(loss)

                # 修改4-多GPU訓練需要對Loss做出調整,并聚合不同裝置上的參數梯度
                avg_loss = model.scale_loss(avg_loss)
                avg_loss.backward()
                model.apply_collective_grads()
                # 最小化損失函數,清除本次訓練的梯度
                optimizer.minimize(avg_loss)
                model.clear_gradients()
                
                if batch_id % 200 == 0:
                    print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))

    #儲存模型參數
    fluid.save_dygraph(model.state_dict(), 'mnist')
           

訓練調試與優化

訓練過程優化思路主要有如下五個關鍵環節:1. 計算分類準确率,觀測模型訓練效果。2. 檢查模型訓練過程,識别潛在問題。3. 加入校驗或測試,更好評價模型效果。4. 加入正則化項,避免模型過拟合。5. 可視化分析。

計算模型的分類準确率

飛槳深度學習筆記(五)第二章 一個案例吃透深度學習(下)
飛槳深度學習筆記(五)第二章 一個案例吃透深度學習(下)

檢查模型訓練過程,識别潛在訓練問題

飛槳深度學習筆記(五)第二章 一個案例吃透深度學習(下)

加入校驗或測試,更好評價模型效果

飛槳深度學習筆記(五)第二章 一個案例吃透深度學習(下)

加入正則化項,避免模型過拟合

具體來說,在模型的優化目标(損失)中人為加入對參數規模的懲罰項。當參數越多或取值越大時,該懲罰項就越大。通過調整懲罰項的權重系數,可以使模型在“盡量減少訓練損失”和“保持模型的泛化能力”之間取得平衡。泛化能力表示模型在沒有見過的樣本上依然有效。正則化項的存在,增加了模型在訓練集上的損失。

飛槳支援為所有參數加上統一的正則化項,也支援為特定的參數添加正則化項。前者的實作如下代碼所示,僅在優化器中設定regularization參數即可實作。使用參數regularization_coeff調節正則化項的權重,權重越大時,對模型複雜度的懲罰越高。

可視化分析

訓練模型時,經常需要觀察模型的評價名額,分析模型的優化過程,以確定訓練是有效的。可選用這兩種工具:Matplotlib庫和VisualDL。

  • Matplotlib庫:Matplotlib庫是Python中使用的最多的2D圖形繪圖庫,它有一套完全仿照MATLAB的函數形式的繪圖接口,使用輕量級的PLT庫(Matplotlib)作圖是非常簡單的。
  • VisualDL:如果期望使用更加專業的作圖工具,可以嘗試VisualDL,飛槳可視化分析工具。VisualDL能夠有效地展示飛槳在運作過程中的計算圖、各種名額變化趨勢和資料資訊。

    Matplotlib示例代碼:

#引入matplotlib庫
import matplotlib.pyplot as plt

with fluid.dygraph.guard(place):
    model = MNIST()
    model.train() 
    
    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())
    
    EPOCH_NUM = 10
    iter=0
    iters=[]
    losses=[]
    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            #準備資料,變得更加簡潔
            image_data, label_data = data
            image = fluid.dygraph.to_variable(image_data)
            label = fluid.dygraph.to_variable(label_data)
            
            #前向計算的過程,同時拿到模型輸出值和分類準确率
            predict, acc = model(image, label)

            #計算損失,取一個批次樣本損失的平均值
            loss = fluid.layers.cross_entropy(predict, label)
            avg_loss = fluid.layers.mean(loss)
            
            #每訓練了100批次的資料,列印下目前Loss的情況
            if batch_id % 100 == 0:
                print("epoch: {}, batch: {}, loss is: {}, acc is {}".format(epoch_id, batch_id, avg_loss.numpy(), acc.numpy()))
                iters.append(iter)
                losses.append(avg_loss.numpy())
                iter = iter + 100

            #後向傳播,更新參數的過程
            avg_loss.backward()
            optimizer.minimize(avg_loss)
            model.clear_gradients()

    #儲存模型參數
    fluid.save_dygraph(model.state_dict(), 'mnist')
#畫出訓練過程中Loss的變化曲線
plt.figure()
plt.title("train loss", fontsize=24)
plt.xlabel("iter", fontsize=14)
plt.ylabel("loss", fontsize=14)
plt.plot(iters, losses,color='red',label='train loss') 
plt.grid()
plt.show()
           

VisualDL示例代碼:

#引入VisualDL庫,并設定儲存作圖資料的檔案位置
from visualdl import LogWriter
log_writer = LogWriter(logdir="./log")

with fluid.dygraph.guard():
    model = MNIST()
    model.train() 
    
    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())
    
    EPOCH_NUM = 10
    iter = 0
    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            #準備資料,變得更加簡潔
            image_data, label_data = data
            image = fluid.dygraph.to_variable(image_data)
            label = fluid.dygraph.to_variable(label_data)
            
            #前向計算的過程,同時拿到模型輸出值和分類準确率
            predict, avg_acc = model(image, label)

            #計算損失,取一個批次樣本損失的平均值
            loss = fluid.layers.cross_entropy(predict, label)
            avg_loss = fluid.layers.mean(loss)
            
            #每訓練了100批次的資料,列印下目前Loss的情況
            if batch_id % 100 == 0:
                print("epoch: {}, batch: {}, loss is: {}, acc is {}".format(epoch_id, batch_id, avg_loss.numpy(), avg_acc.numpy()))
                log_writer.add_scalar(tag = 'acc', step = iter, value = avg_acc.numpy())
                log_writer.add_scalar(tag = 'loss', step = iter, value = avg_loss.numpy())
                iter = iter + 100

            #後向傳播,更新參數的過程
            avg_loss.backward()
            optimizer.minimize(avg_loss)
            model.clear_gradients()

    #儲存模型參數
    fluid.save_dygraph(model.state_dict(), 'mnist')
           

在指令行 輸入 $ visualdl --logdir ./log --port 8080,查閱的網址在第三步的啟動指令後會列印出來(如http://127.0.0.1:8080/),将該網址輸入浏覽器位址欄重新整理頁面的效果如下圖所示。除了右側對資料點的作圖外,左側還有一個控制闆,可以調整諸多作圖的細節。

繼續閱讀