三行Python代碼,讓數據預處理速度提升2到6倍

在 Python 中,咱們能夠找到原生的並行化運算指令。本文能夠教你僅使用 3 行代碼,大大加快數據預處理的速度。html

Python 是機器學習領域內的首選編程語言,它易於使用,也有不少出色的庫來幫助你更快處理數據。但當咱們面臨大量數據時,一些問題就會顯現……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 上,這種速度看起來是難以讓人接受的,看看咱們能作點什麼。oop

更快的方法

爲了便於理解並行化的提高,假設咱們須要執行相同的任務,好比將 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」一節)。

 

推薦閱讀:極簡Python帶你探索分類與迴歸的奧祕

相關文章
相關標籤/搜索