超級干貨;Python優化之使用pandas讀取和訓練千萬級數據


環境:Linux-cenos5

processor : 31

model : 62

model name : Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz

cpu MHz : 2000.066

cache size : 20480 KB

memory : 125G

 

在如上所述的單機環境中,使用一些優化可以使基於pandas數據格式的模型訓練數據容量由600W增長為至少2000W,訓練時間減少為1/5。具體方案如下:

 

數據讀取優化

數據量4200W行,193列,每列存儲為string類型的單精度浮點數,文件表由csv格式存儲,總大小16GB+。通過如下語句讀取到dataframe中去:

df_train = pd.read_csv(path,header=None,sep=',',nrows=40000000,error_bad_lines=False,delimiter="\t",lineterminator="\n",
                       keep_default_na=True)

  經過測試,當nrows讀取行數超過800W條時,df_train占內存超過80G,在后續的步驟中涉及到切片和數據集復制時會直接崩潰,超過1200W條時會直接無法讀取。首先考慮優化讀取方式:

na_vals = ["\\N"," ","","NULL"]
df_tmp = []
df_train = pd.DataFrame(index=["0"],dtype=np.float32)
count = 0
for chunk in pd.read_csv(path,header=None,sep=',',chunksize=200000,nrows=10000000,error_bad_lines=False,delimiter="\t",lineterminator="\n",
                         keep_default_na=True, na_values=na_vals):
    df_tmp.append(chunk[1:])
    del chunk
    print("the chunk " + str(count) + " has been stored...")
    print("the mem-cost is now: ", str(sys.getsizeof(df_tmp)/(1) ), "MB \n")

  

可以分為以下幾個點解釋

使用na_values進行讀取時的空值替換,代替了讀取全部數據后的空值replace方式替換,減少了后續的處理時間

使用chunksize進行數據讀取,read_csv()方法中有一個參數chunksize可以指定一個CHUNKSIZE分塊大小來讀取文件。與直接使用DF進行遍歷不同的是,此時它是一個TextFileReader類型的對象。

通過循環每次讀取分塊數據,再通過list拼接起來。為什么不直接用concat進行迭代拼接?因為concat只能使用值語義賦值,每次concat時都會先創建一個臨時副本,再賦值給對應變量,這個過程中當數據量很大時,內存占用在賦值完成,刪除原有dataframe前會造成極高的內存占用峰值。而python的list可以進行連續內存塊的直接追加,不會產生額外的內存開銷。

每次讀取完一個chunk,都進行刪除,釋放對應內存,點擊進入,免費領取高品質python學習資料大全,適合在校大學生,小白,想轉行,想提升自己的都可以加入。

 

數據轉換優化

經過讀取優化后,腳本最多可讀取超過2000W的數據量,是之前讀取數據量的三倍以上。但是此時讀取完成后,在后續的處理中仍然會產生內存報錯,首先報錯的就是

df_X = pd.DataFrame(data=df_tmp, dtype=np.float)

 

報錯原因是當我們將讀取的數據list經由pandas的構造函數進行類型轉換時,會產生兩個超大的dataframe/list,一個是等號右邊的臨時表,一個是df_tmp本身,此時內存超過限制,程序崩潰。經過分析采用如下方案可避免此部分報錯

idx = 0
for i in range(len(df_tmp)):
    tmp = pd.DataFrame(data=df_tmp[idx], dtype=np.float)
    df_train = pd.concat([df_train,tmp], ignore_index=True)
    del df_tmp[idx],tmp
    print(i)
    print("the remaining chunk is: ", count)
    print("the frame size is: ", df_train.memory_usage().sum() / (1024 ** 2), "MB")
count -= 1

  

同樣通過循環,每次只處理df_tmp這個存儲了所有數據的臨時list中的一個chunk,並在數據轉換期間進行迭代拼接,每次增量更新df_train主表。在更新完成后立即釋放該list中對應的內存。經過優化,此部分內存占用減半

 

數據類型優化

 

pandas中的許多數據類型具有多個子類型,它們可以使用較少的字節去表示不同數據,比如,float型就有float16、float32和float64這些子類型。這些類型名稱的數字部分表明了這種類型使用了多少比特來表示數據,比如剛才列出的子類型分別使用了2、4、8個字節。下面這張表列出了pandas中常用類型的子類型:

 

一個int8類型的數據使用1個字節(8位比特)存儲一個值,可以表示256(2^8)個二進制數值。這意味着我們可以用這種子類型去表示從-128到127(包括0)的數值。原始數據表基本都是由浮點數組成,因此可以考慮使用數據類型進行優化。

tmp0 = pd.DataFrame(data=df_tmp[idx])
tmp1 = pd.DataFrame(data=df_tmp[idx], dtype=object)
tmp2 = pd.DataFrame(data=df_tmp[idx], dtype=np.float)
tmp3 = pd.DataFrame(data=df_tmp[idx], dtype=np.float32)
# print("the remaining chunk is: ", count)
print("the frame size is: ", df_train.memory_usage().sum() / (1024 ** 2), "MB")

  數據量為2W的情況下,pandas內存占用情況如下(默認類型為str)

 

 

一開始的代碼中沒有進行類型的轉換,因此默認根據csv的格式進行初始化,所以Dataframe的數據類型全都是str,在讀取之后的存儲開銷非常大,16G的csv讀取進來后直接變成120G。使用數據類型優化后,結合之前的分塊迭代讀取,2000W的數據(8G)讀取由60G的內存占用減少為不到15G,且省去了后續直接的類型轉換。

 

小技巧

在每次使用完某個對象后,如果其在后續的建模過程中沒有別的用處,可以手動刪除,或者刪除變量引用,觸發GC機在讀入數據集的時候指定列的最優數據類型。pandas.read_csv()函數有一些參數可以做到這一點。dtype參數接受一個以列名(string型)為鍵字典、以Numpy類型對象為值的字典

 

dict = {x:np.float16 for x in col}
​
df_train = pd.read_csv(path,header=None,sep=',',error_bad_lines=False,delimiter="\t",lineterminator="\n",
                       keep_default_na=True, converters=dict, na_vals=na_vals)

  可以考慮使用開源的pandas分布式機器學習庫dask,在單機環境下運行即可

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM