TensorFlow高性能計算提升及分析技巧

這段時間,我師傅交給我一些任務,讓我加速TensorFlow模型的計算速度。主要還都是他在提想法,我負責實現就行了。這篇博客主要將學到的知識進行一下總結。由於之前在科研上多數應用的是Pytorch構建模型,現在忽然讓用TensorFlow,對其中的一些計算機制的不熟悉,在實現過程中遇到了不少問題
文章主要分爲兩點:

  • TFRecordDataset的原理和使用
  • Pipeline的原理和使用
  • Profile 性能分析

TFRecordDataset的原理和使用

TensorFlow中有一個tf.data的API,可以根據簡單的可重用片段構建複雜的輸入管道:比如圖片模型的管道可能會匯聚分佈式文件系統中的文件中的數據,對每個圖片進行隨機擾動,並將隨機選擇的圖片合併用於訓練的批次。
tf.data在TensorFlow中引入了兩個新的抽象類:

  • tf.data.Dataset 表示一系列元素,其中每個元素包含一個或者多個tensor對象。比如在圖像管道中,元素可能是單個訓練樣本,具有一對錶示圖像數據和標籤的張量。可以是通過兩種不同方式來創建數據集。
    • 創建來源(Dataset.from_tensor_slice()),以通過一個或者多個tf.Tensor對象構建數據集
    • 應用轉換(Dataset.batch()),通過一個或者多個tf.data.Dataset對象構建數據集
  • tf.data.Iterator 提供從數據集中提取元素的主要方法,Iterator.get_next()返回的操作會在執行時生成Dataset的下一個元素,並且此操作通常充當輸入管道代碼和模型之間的接口。最簡單的迭代器是‘單次迭代器’,它和特定的Dataset相關聯,並對其進行一次迭代。要實現更復雜的用途,可以通過Iterator.inititalizer操作使用不同的數據集重新初始化和參數化迭代器

如何使用TFRecord數據
tf.dataAPI支持多種文件格式,可以 處理那些不適合在內存中存儲的大型數據集,比如,TFRecord文件格式是一種面向記錄的二進制格式,很多TensorFlow應用此格式來訓練數據。通過tf.data.TFRecordDataset類,可以將一個或者多個TFRecord文件的內容作爲輸入管道的一部分進行流式傳輸。

# 創建一個從兩個文件讀取數據集
filenames = ["/var/file1.tfrecord","/var/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)

TFRecordDataset初始化程序的filenames的參數可以是字符串,字符串列表,也可以是字符串tf.Tensor。因此,面對需要訓練和驗證的文件,可以使用tf.placeholder(tf.string)來表示文件名,並使用適當的文件名初始化迭代器:

filenames = tf.placeholder(tf.string,shape=[None])
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...)  # parse the record into tensors
dataset = dataset.repeat()  # repeat the input indefinitely
dataset = dataset.batch(32)
iterator = dataset.make_initializable_iterator()

##  初始化 訓練數據
training_filenames = ["/var/validation1.tf.record",....]
sess.run(iterator.initializer,feed_dict={filenames: validation_filenames})

還有一些函數的使用細節,大家可以在第一個參考文獻中看到。

Pipeline的原理和使用

pipeline也叫作數據輸入流水線性能優化。這個性能優化策略簡單來說就是,並行處理CPU數據讀取和GPU計算,讓數據讀取和GPU計算交替進行縮短整個模型的時間。
太長不看(最佳做法摘要):

  • 使用prefetch轉換可將提供方和使用方的工作重疊,將prefetch(n)(其中n是單步訓練使用的元素數/批次數)添加到輸入流水線的末尾,以便在CPU上執行的轉換與加速器上執行的訓練重疊
  • 通過設置num_parallel_calls參數並行處理map轉換。建議將其值設置爲CPU核心數量
  • 使用batch轉換將預處理元素組合到一個批次中,建議使用map_and_batch混合轉換
  • 如果要處理遠程存儲的數據並/或需要反序列化,可以使用parallel_interleave轉換來重疊從不同文件讀取數據的操作
  • 向量化傳遞給map轉換的低開銷用戶定義函數,來分攤與調度和執行相應函數相關的開銷。
  • 如果內存可以容納數據,就請使用cache轉換在第一個週期中將數據緩存在內存中,以便後續週期可以避免與讀取、解析和轉換該數據的相關開銷
  • 如果預處理操作會增加數據大小,建議先應用interleaveprefetchshuffle減少內存的使用量
  • 建議在repeat轉換之前先應用shuffle轉換,最好使用shuffle_and_repeat混合轉換

輸入流水線結構

典型的TensorFlow訓練輸入流水線可以看做是ETL流程:

  • 提取: 從存儲上讀取數據
  • 轉換: 使用CPU核心解析數據並執行預處理操作,比如圖像解壓縮,數據增強轉換,重排和批處理
  • 加載:將轉換之後的數據加載到執行機器學習模型的加速器設備上

這種模式就已經能夠高效利用CPU,同時預留加速器來完成對模型進行訓練的繁重工作。使用tf.estimator.EstimatorAPI時,前兩個階段(提取和轉換)是在input_fn(傳遞給tf.estimator.Estimator,train)中捕獲的。代碼:

def parse_fn(example):
	"Parse TFExample records and perform simple data augmentation"
	example_fmt ={
	"image":tf.FixedLengthFeature((),tf.string,""),
	"label":tf.FixedLengthFeature((),tf.int64,-1)
	}
	parsed = tf.parse_single_example(example,example_fmt)
	image = tf.image.decord_image(parsed["image"])
	image = _augment_helper(image)  # augments image using slice,reshape,resize_bilinear
	return image,parsed["label"]
def input_fn():
	files = tf.data.Dataset.list_files("/path/train-*.tfrecord")
	dataset = files.interleave(tf.data.TFRecordDataset)
	dataset = dataset.shuffle(buffer_size = FLAGS.shuffle_buffer_size)
	dataset = dataset.map(map_func = parse_fn)
	dataset = dataset.batch(batch_size = FLAGS.batch_size)

由於GPU或者TPU可以不斷提升神經網絡的訓練速度,因此,CPU處理很容易成爲瓶頸。tf.dataAPI 爲用戶提供構建塊來設計可高效利用CPU的輸入流水線,並優化ETL流程的每個步驟。
在執行訓練步驟,必須提取並轉換訓練數據,然後將其提供給加速器上運行的模型。
在這裏插入圖片描述
如上所示,在一個簡單的同步實現中,當CPU準備數據時,GPU處於空閒狀態,當GPU訓練模型的時候,CPU處於空閒的狀態。所以訓練步的用時是CPU+GPU的和
如果使用流水線可以顯著減少空閒時間:
在這裏插入圖片描述

  • 流水線:tf.dataAPI 通過tf.data.Dataset.prefetch轉換提供了一種軟件流水線機制,該機制可用於將生成數據的時間和使用數據的時間分離開,具體來說,該轉換使用了後臺線程和內部緩衝,以便在請求元素之前從輸入數據集中預取這些元素。因此爲了實現上面的流水線效果,可以將prefetch(1)作爲最終轉換添加到數據集流水線中(如果單步訓練使用n個元素,就需要添加prefetch(n)
  • 並行處理數據轉換:準備批次數據,可能需要預處理輸入元素。爲此,tf.dataAPI提供了tf.data.Dataset.map轉換,將用戶定義的函數應用於輸入數據集的每個元素。由於輸入數據彼此獨立,因此可以跨多個CPU核心並行執行處理。爲了實現這一點,map轉換提供了num_parallel_calls參數來指定並行處理級別。
    在這裏插入圖片描述
    設置num_parallel_calls參數選擇最佳值取決於硬件,訓練數據的特徵,映射函數的成本以及同時在CPU上進行的其他處理
  • 並行處理數據提取: 實際設置中,輸入數據可能會遠程存儲,這是因爲輸入數據不適合本地存儲,或是因爲訓練是分佈式訓練,因此每臺機器上覆制輸入數據沒有意義。非常適合在本地上讀取數據的數據集流水線在遠程讀取數據時可能會遇到IO瓶頸,這是因爲本地和遠程存儲之間存在一些差異:
    • 首字節時間:與本地存儲相比,從遠程存儲讀取文件的首字節所用時間可能要多出幾個數量級
    • 讀取吞吐量:雖然遠程存儲通常可提供較大的聚合帶寬,但讀取單個文件可能只能利用此帶寬中的一小部分
      爲了降低各種數據提取開銷的影響,tf.dataAPI提供了tf.contrib.data.parallel_interleave轉換。使用此轉換可以並行執行其他數據集並交錯這些數據集中的內容,通過cycle_length參數指定要重疊的數據集數量
      在這裏插入圖片描述
      具體相關的操作可以參看第二個文獻。

Profile 性能分析

當程序達到一個性能瓶頸之後,需要按照每步的性能損耗來進行優化,在第三篇的參考文獻中,講述了七款python的性能分析工具,其中我使用的是line_profiler 可以統計每行代碼執行的次數和執行時間等,時間單位是微秒。
測試代碼:
C:\Python34\test.py

import time
@profile
def fun():
    a = 0
    b = 0
    for i in range(100000):
        a = a + i * i
    for i in range(3):
        b += 1
        time.sleep(0.1)
    return a + b
fun()

使用:
1.在需要測試的函數加上@profile裝飾,這裏我們把測試代碼寫在C:\Python34\test.py文件上.
2.運行命令行:kernprof -l -v C:\Python34\test.py
顯示效果的說明:

Total Time:測試代碼的總運行時間 
Hits:表示每行代碼運行的次數  
Time:每行代碼運行的總時間  
Per Hits:每行代碼運行一次的時間  
% Time:每行代碼運行時間的百分比

參考資料

TensorFlow 導入數據
TensorFlow輸入流水線性能
Python的7種性能測試工具:timeit、profile、cProfile、line_profiler、memory_profiler、PyCharm圖形化性能測試工具、objgraph

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章