三行Python程式碼,讓資料預處理速度提高2到6倍
Python 是機器學習領域內的首選程式語言,它易於使用,也有很多出色的庫來幫助你更快處理資料。但當我們面臨大量資料時,一些問題就會顯現……
目前,大資料(Big Data)這個術語通常用於表示包含數十萬資料點的資料集。在這樣的尺度上,工作程序中加入任何額外的計算都需要時刻注意保持效率。在設計機器學習系統時,資料預處理非常重要——在這裡,我們必須對所有資料點使用某種操作。
在預設情況下,Python 程式是單個程序,使用單 CPU 核心執行。而大多數當代機器學習硬體都至少搭載了雙核處理器。這意味著如果沒有進行優化,在資料預處理的時候會出現「一核有難九核圍觀」的情況——超過 50% 的算力都會被浪費。在當前四核處理器(英特爾酷睿 i5)和 6 核處理器(英特爾酷睿 i7)大行其道的時候,這種情況會變得更加明顯。
幸運的是,Python 庫中內建了一些隱藏的特性,可以讓我們充分利用所有 CPU 核心的能力。通過使用 Python 的 concurrent.futures 模組,我們只需要 3 行程式碼就可以讓一個普通的程式轉換成適用於多核處理器並行處理的程式。
標準方法
讓我們舉一個簡單的例子,在單個資料夾中有一個圖片資料集,其中有數萬張圖片。在這裡,我們決定使用 1000 張。我們希望在所有圖片被傳遞到深度神經網路之前將其調整為 600×600 畫素解析度的形式。以下是你經常會在 GitHub 上看到的標準 Python 程式碼:
import glob import os import cv2 ### Loop through all jpg files in the current folder ### Resize each one to size 600x600 for image_filename in glob.glob("*.jpg"): ### Read in the image data img = cv2.imread(image_filename) ### Resize the image img = cv2.resize(img, (600, 600))
上面的程式遵循你在處理資料指令碼時經常看到的簡單模式:
1. 首先從需要處理內容的檔案(或其他資料)列表開始。
2. 使用 for 迴圈逐個處理每個資料,然後在每個迴圈迭代上執行預處理。
讓我們在一個包含 1000 個 jpeg 檔案的資料夾上測試這個程式,看看執行它需要多久:
time python standard_res_conversion.py
在我的酷睿 i7-8700k 6 核 CPU 上,執行時間為 7.9864 秒!在這樣的高階 CPU 上,這種速度看起來是難以讓人接受的,看看我們能做點什麼。
更快的方法
為了便於理解並行化的提升,假設我們需要執行相同的任務,比如將 1000 個釘子釘入木頭,假如釘入一個需要一秒,一個人就需要 1000 秒來完成任務。四個人組隊就只需要 250 秒。
在我們這個包含 1000 個影象的例子中,可以讓 Python 做類似的工作:
-
將 jpeg 檔案列表分成 4 個小組;
-
執行 Python 直譯器中的 4 個獨立例項;
-
讓 Python 的每個例項處理 4 個數據小組中的一個;
-
結合四個處理過程得到的結果得出最終結果列表。
這一方法的重點在於,Python 幫我們處理了所有棘手的工作。我們只需告訴它我們想要執行哪個函式,要用多少 Python 例項,剩下的就交給它了!只需改變三行程式碼。例項:
import glob import os import cv2 import concurrent.futures def load_and_resize(image_filename): ### Read in the image data img = cv2.imread(image_filename) ### Resize the image img = cv2.resize(img, (600, 600)) ### Create a pool of processes. By default, one is created for each CPU in your machine. with concurrent.futures.ProcessPoolExecutor() as executor: ### Get a list of files to process image_files = glob.glob("*.jpg") ### Process the list of files, but split the work across the process pool to use all CPUs ### Loop through all jpg files in the current folder ### Resize each one to size 600x600 executor.map(load_and_resize, image_files)
從以上程式碼中摘出一行:
with concurrent.futures.ProcessPoolExecutor() as executor:
你的 CPU 核越多,啟動的 Python 程序越多,我的 CPU 有 6 個核。實際處理程式碼如下:
executor.map(load_and_resize, image_files)
「executor.map()」將你想要執行的函式和列表作為輸入,列表中的每個元素都是我們函式的單個輸入。由於我們有 6 個核,我們將同時處理該列表中的 6 個專案!
如果再次用以下程式碼執行我們的程式:
time python fast_res_conversion.py
我們可以將執行時間降到 1.14265 秒,速度提升了近 6 倍!
注意:在生成更多 Python 程序及在它們之間整理資料時會有一些開銷,所以速度提升並不總是這麼明顯。但是總的來說,速度提升還是非常可觀的。
它總是那麼快嗎?
如果你有一個數據列表要處理,而且在每個資料點上執行相似的運算,那麼使用 Python 並行池是一個很好的選擇。但有時這不是最佳解決方案。並行池處理的資料不會在任何可預測的順序中進行處理。如果你對處理後的結果有特殊順序要求,那麼這個方法可能不適合你。
你處理的資料也必須是 Python 可以「炮製」的型別。所幸這些指定類別都很常見。以下來自 Python 官方檔案:
-
None, True, 及 False
-
整數、浮點數、複數
-
字串、位元組、位元組陣列
-
只包含可挑選物件的元組、列表、集合和字典
-
在模組頂層定義的函式(使用 def ,而不是 lambda )
-
在模組頂層定義的內建函式
-
在模組頂層定義的類
-
這種類的例項,其 __dict__ 或呼叫__getstate__() 的結果是可選擇的(參見「Pickling Class Instances」一節)。
原文連結:https://towardsdatascience.com/heres-how-you-can-get-a-2-6x-speed-up-on-your-data-pre-processing-with-python-847887e63be5