文章目錄
- 1. 模型訓練過程劃分
- 1.1. 定義過程
-
- 1.2. 數據集加載過程
- 1.2.1. Dataset類:創建數據集
- 1.2.2. Dataloader類:加載數據集
- 1.3. 訓練循環
- 2. 模型訓練過程優化的總體思路
- 2.1. 提升數據從硬盤轉移到CPU內存的效率
- 2.2. 提升CPU的運算效率
- 2.3. 提升數據從CPU轉移到GPU的效率
- 2.4. 提升GPU的運算效率
- 3. 模型訓練過程優化分析
- 3.1. 定義過程
- 3.2. 數據集加載過程
- 3.3. 訓練循環
-
1. 模型訓練過程劃分
if __name__ == '__main__':...
1.1. 定義過程
1.1.1. 全局參數設置
參數名 | 作用 |
---|
num_epochs | 指定在訓練集上訓練的輪數 |
batch_size | 指定每批數據的樣本數 |
num_workers | 指定加載數據集的進程數 |
prefetch_factor | 指定每個進程的預加載因子(要求num_workers>0 ) |
device | 指定模型訓練使用的設備(CPU或GPU) |
lr | 學習率,控制模型參數的更新步長 |
1.1.2. 模型定義
組件 | 作用 |
---|
writer | 定義tensorboard的事件記錄器 |
net | 定義神經網絡結構 |
net.apply(init_weights) | 模型參數初始化 |
criterion | 定義損失函數 |
optimizer | 定義優化器 |
1.2. 數據集加載過程
1.2.1. Dataset類:創建數據集
- 作用:定義數據集的結構和訪問數據集中樣本的方式。定義過程中通常需要讀取數據文件,但這并不意味著將整個數據集加載到內存中。
- 如何創建數據集
- 繼承Dataset抽象類自定義數據集
- TensorDataset類:通過包裝張量創建數據集
1.2.2. Dataloader類:加載數據集
- 作用:定義數據集的加載方式,但這并不意味著正在加載數據集。
- 數據批量加載:將數據集分成多個批次(batches),并逐批次地加載數據。
- 數據打亂(可選):在每個訓練周期(epoch)開始時,DataLoader會對數據集進行隨機打亂,以確保在訓練過程中每個樣本被均勻地使用。
- 主要參數
參數 | 作用 |
---|
dataset | 指定數據集 |
batch_size | 指定每批數據的樣本數 |
shuffle=False | 指定是否在每個訓練周期(epoch)開始時進行數據打亂 |
sampler=None | 指定如何從數據集中選擇樣本,如果指定這個參數,那么shuffle必須設置為False |
batch_sampler=None | 指定生成每個批次中應包含的樣本數據的索引。與batch_size、shuffle 、sampler and drop_last參數不兼容 |
num_workers=0 | 指定進行數據加載的進程數 |
collate_fn=None | 指定將一列表的樣本合成mini-batch的方法,用于映射型數據集 |
pin_memory=False | 是否將數據緩存在物理RAM中以提高GPU傳輸效率 |
drop_last=False | 是否在批次結束時丟棄剩余的樣本(當樣本數量不是批次大小的整數倍時) |
timeout=0 | 定義在每個批次上等待可用數據的最大秒數。如果超過這個時間還沒有數據可用,則拋出一個異常。默認值為0,表示永不超時。 |
worker_init_fn=None | 指定在每個工作進程啟動時進行的初始化操作。可以用于設置共享的隨機種子或其他全局狀態。 |
multiprocessing_context=None | 指定多進程數據加載的上下文環境,即多進程庫 |
generator=None | 指定一個生成器對象來生成數據批次 |
prefetch_factor=2 | 控制數據加載器預取數據的數量,默認預取比實際所需的批次數量多2倍的數據 |
persistent_workers=False | 控制數據加載器的工作進程是否在數據加載完成后繼續存在 |
1.3. 訓練循環
for epoch in trange(num_epochs):...
- 循環內部主要有以下模塊:
for X, y in dataloader_train:X, y = X.to(device), y.to(device)loss = criterion(net(X), y)optimizer.zero_grad()loss.mean().backward()optimizer.step()
def evaluate_loss(dataloader):"""評估給定數據集上模型的損失"""metric = d2l.Accumulator(2) with torch.no_grad():for X, y in dataloader:X, y = X.to(device), y.to(device)loss = criterion(net(X), y)metric.add(loss.sum(), loss.numel())return metric[0] / metric[1]
2. 模型訓練過程優化的總體思路
注意: 以下只區分變量、對象是在GPU還是在CPU內存中處理。實際處理過程使用的硬件是CPU、內存和GPU,其中CPU有緩存cache,GPU有顯存。忽略具體的數據傳輸路徑和數據處理設備。談GPU包括GPU和顯存,談CPU內存包括CPU、緩存cache和內存。
主過程 | 子過程 | 追蹤情況 |
---|
定義過程 | 全局參數設置 | 變量的定義都是由CPU完成的 |
模型定義 | - 對象的定義都是由CPU完成的
- 模型參數和梯度信息可以轉移到GPU
|
數據集配置過程 | —— | 對象的定義都是由CPU完成的 |
訓練循環 | 訓練模型 | - 每批數據的加載是由CPU完成的,先加載到CPU內存,然后可以轉移到GPU
- 數據的前向傳播可以由GPU完成
- 誤差反向傳播(包括梯度計算)可以由GPU完成的
- 模型參數更新可以由GPU完成的
|
評估模型 | - 每批數據的加載是由CPU完成的,先加載到CPU內存,然后可以轉移到GPU
- 數據的前向傳播可以由GPU完成,此時可以禁用自動求導機制
|
由此,要提升硬件資源的利用率和訓練效率,總體上有以下角度:
2.1. 提升數據從硬盤轉移到CPU內存的效率
- 如果數據集較小,可以一次性讀入CPU內存,之后注意要將
num_workers
設置為0,由主進程加載數據集。否則會增加多余的過程(數據從CPU內存到CPU內存),而且隨進程數num_workers
增加而增加。 - 如果數據集很大,可以采用多進程讀取,
num_workers
設置為大于0的數,小于CPU內核數,加載數據集的效率隨著進程數num_workers
增加而增加;也隨著預讀取因子prefetch_factor
的增加而增加,之后大致不變,因為預讀取到了極限。 - 如果數據集較小,但是需要逐元素的預處理,可以采用多進程讀取,以稍微增加訓練時間為代價降低操作的復雜度。
2.2. 提升CPU的運算效率
2.3. 提升數據從CPU轉移到GPU的效率
- 數據傳輸未準備好也傳輸(即非阻塞模式):
non_blocking=True
- 將張量固定在CPU內存 :
pin_memory=True
2.4. 提升GPU的運算效率
- 使用自動混合精度(AMP,要求pytorch>=1.6.0):通過將模型和數據轉換為低精度的形式(如FP16),可以顯著減少GPU內存使用。
3. 模型訓練過程優化分析
3.1. 定義過程
- 特點:每次程序運行只需要進行一次。
- 優化思路:將模型轉移到GPU,同時
non_blocking=True
。
3.2. 數據集加載過程
- 特點:只是定義數據加載的方式,并沒有加載數據。
- 優化思路:合理設置數據加載參數,如
batch_size
:一般取能被訓練集大小整除的值。過小,則每次參數更新時所用的樣本數較少,模型無法充分地學習數據的特征和分布,同時參數更新頻繁,模型收斂速度提高,CPU到GPU的數據傳輸次數增加,CPU內存的消耗總量增加;過大,則每次參數更新時所用的樣本數較多,模型性能更穩定,對GPU、CPU內存的單次消耗增加,對硬件配置要求更高,同時參數更新緩慢,模型收斂速度下降。num_workers
:取小于CPU內核數的合適值,比如先取CPU內核數的一半。過小,則數據加載進程少,數據加載緩慢;過大,則數據加載進程多,對CPU要求高,同時也影響效率。pin_memory
:當設置為True時,它告訴DataLoader將加載的數據張量固定在CPU內存中,使數據傳輸到GPU的過程更快。prefetch_factor
:決定每次從磁盤加載多少個batch的數據到內存中,預先加載batch越多,在處理數據時,不會因為數據加載的延遲而影響整體的訓練速度,同時可以讓GPU在處理數據時保持忙碌,從而提高GPU利用率;過大,則會導致CPU內存消耗增加。
3.3. 訓練循環
- 優化思路:
- 訓練和評估過程分離或者減少評估的次數:模型從訓練到評估需要進行狀態切換,模型評估過程開銷很大。
- 盡量使用非局部變量:減少變量、對象的創建和銷毀過程
3.3.1. 訓練模型
- 特點:訓練結構固定
- 優化思路:
- 將數據轉移到GPU,同時
non_blocking=True
。 - 優化訓練結構:比如使用自動混合精度:
from torch.cuda.amp import autocast, GradScalergrad_scaler = GradScaler()
for epoch in range(num_epochs):start_time = time.perf_counter()for X, y in dataloader_train:X, y = X.to(device, non_blocking=True), y.to(device, non_blocking=True)with autocast():loss = criterion(net(X), y)optimizer.zero_grad()grad_scaler.scale(loss.mean()).backward()grad_scaler.step(optimizer)grad_scaler.update()
3.3.2. 評估模型
- 特點:評估結構固定
- 優化思路:
- 將數據轉移到GPU,同時
non_blocking=True
。 - 減少不必要的運算:比如梯度計算,即:
with torch.no_grad():...