如何在GPU上加速數(shù)據(jù)科學(xué)
筆者按,數(shù)據(jù)科學(xué)家需要算力。無(wú)論您是用 pandas 處理一個(gè)大數(shù)據(jù)集,還是用 Numpy 在一個(gè)大矩陣上運(yùn)行一些計(jì)算,您都需要一臺(tái)強(qiáng)大的機(jī)器,以便在合理的時(shí)間內(nèi)完成這項(xiàng)工作。
在過(guò)去的幾年中,數(shù)據(jù)科學(xué)家常用的 Python 庫(kù)已經(jīng)非常擅長(zhǎng)利用 CPU 能力。
Pandas 的基礎(chǔ)代碼是用 C 語(yǔ)言編寫的,它可以很好地處理大小超過(guò) 100GB 的數(shù)據(jù)集。如果您沒(méi)有足夠的 RAM 來(lái)容納這樣的數(shù)據(jù)集,那么您可以使用分塊功能,它很方便,可以一次處理一個(gè)數(shù)據(jù)塊。
GPUs vs CPUs:并行處理
有了大量的數(shù)據(jù),CPU 就不會(huì)切斷它了。
一個(gè)超過(guò) 100GB 的數(shù)據(jù)集將有許多數(shù)據(jù)點(diǎn),數(shù)據(jù)點(diǎn)的數(shù)值在數(shù)百萬(wàn)甚至數(shù)十億的范圍內(nèi)。有了這么多的數(shù)據(jù)點(diǎn)要處理,不管你的 CPU 有多快,它都沒(méi)有足夠的內(nèi)核來(lái)進(jìn)行有效的并行處理。如果你的 CPU 有 20 個(gè)內(nèi)核(這將是相當(dāng)昂貴的 CPU),你一次只能處理 20 個(gè)數(shù)據(jù)點(diǎn)!
CPU 在時(shí)鐘頻率更重要的任務(wù)中會(huì)更好——或者根本沒(méi)有 GPU 實(shí)現(xiàn)。如果你嘗試執(zhí)行的流程有一個(gè) GPU 實(shí)現(xiàn),且該任務(wù)可以從并行處理中受益,那么 GPU 將更加有效。
深度學(xué)習(xí)已經(jīng)在利用 GPU 方面發(fā)揮了相當(dāng)大的作用。許多在深度學(xué)習(xí)中完成的卷積操作是重復(fù)的,因此在 GPU 上可以大大加速,甚至可以達(dá)到 100 次。
今天的數(shù)據(jù)科學(xué)沒(méi)有什么不同,因?yàn)樵S多重復(fù)的操作都是在大數(shù)據(jù)集上執(zhí)行的,庫(kù)中有 pandas、Numpy 和 scikit-learn。這些操作也不太復(fù)雜,無(wú)法在 GPU 上實(shí)現(xiàn)。
最后,還有一個(gè)解決方案。
用 Rapids 加速 GPU
Rapids 是一套軟件庫(kù),旨在利用 GPU 加速數(shù)據(jù)科學(xué)。它使用低級(jí)別的 CUDA 代碼實(shí)現(xiàn)快速的、GPU 優(yōu)化的算法,同時(shí)它上面還有一個(gè)易于使用的 Python 層。
Rapids 的美妙之處在于它與數(shù)據(jù)科學(xué)庫(kù)的集成非常順利,比如 pandas 數(shù)據(jù)幀就很容易通過(guò) Rapids 實(shí)現(xiàn) GPU 加速。下圖說(shuō)明了 Rapids 如何在保持頂層易用性的同時(shí)實(shí)現(xiàn)低層的加速。
Rapids 利用了幾個(gè) Python 庫(kù):
- cuDF-Python GPU 數(shù)據(jù)幀。它幾乎可以做 pandas 在數(shù)據(jù)處理和操作方面所能做的一切。
- cuML-cuGraph 機(jī)器學(xué)習(xí)庫(kù)。它包含了 Scikit-Learn 擁有的許多 ML 算法,所有算法的格式都非常相似。
- cuGraph-cuGraph 圖處理庫(kù)。它包含許多常見(jiàn)的圖分析算法,包括 PageRank 和各種相似性度量。
如何使用 Rapids
安裝
現(xiàn)在你將看到如何使用 Rapids!
要安裝它,請(qǐng)?jiān)L問(wèn)這個(gè)網(wǎng)站,在這里你將看到如何安裝 Rapids。你可以通過(guò) Conda 將其直接安裝到你的機(jī)器上,或者簡(jiǎn)單地使用 Docker 容器。
安裝時(shí),可以設(shè)置系統(tǒng)規(guī)范,如 CUDA 版本和要安裝的庫(kù)。例如,我有 CUDA 10.0,想要安裝所有庫(kù),所以我的安裝命令是:
- conda install -c nvidia -c rapidsai -c numba -c conda-forge -c pytorch -c defaults cudf=0.8 cuml=0.8 cugraph=0.8 python=3.6 cudatoolkit=10.0
一旦命令完成運(yùn)行,就可以開(kāi)始用 GPU 加速數(shù)據(jù)科學(xué)了。
設(shè)置我們的數(shù)據(jù)
對(duì)于本教程,我們將介紹 DBSCAN demo 的修改版本。我將使用 Nvidia 數(shù)據(jù)科學(xué)工作站和 2 個(gè) GPU 運(yùn)行這個(gè)測(cè)試。
DBSCAN 是一種基于密度的聚類算法,可以自動(dòng)對(duì)數(shù)據(jù)進(jìn)行分類,而無(wú)需用戶指定有多少組數(shù)據(jù)。在 Scikit-Learn 中有它的實(shí)現(xiàn)。
我們將從獲取所有導(dǎo)入設(shè)置開(kāi)始。先導(dǎo)入用于加載數(shù)據(jù)、可視化數(shù)據(jù)和應(yīng)用 ML 模型的庫(kù)。
- import os
- import matplotlib.pyplot as plt
- from matplotlib.colors import ListedColormap
- from sklearn.datasets import make_circles
make_circles 函數(shù)將自動(dòng)創(chuàng)建一個(gè)復(fù)雜的數(shù)據(jù)分布,類似于我們將應(yīng)用于 DBSCAN 的兩個(gè)圓。
讓我們從創(chuàng)建 100000 點(diǎn)的數(shù)據(jù)集開(kāi)始,并在圖中可視化:
- X, y = make_circles(n_samples=int(1e5), factor=.35, noise=.05)
- X[:, 0] = 3*X[:, 0]
- X[:, 1] = 3*X[:, 1]
- plt.scatter(X[:, 0], X[:, 1])
- plt.show()
CPU 上的 DBSCAN
使用 Scikit-Learn 在 CPU 上運(yùn)行 DBSCAN 很容易。我們將導(dǎo)入我們的算法并設(shè)置一些參數(shù)。
- from sklearn.cluster import DBSCAN
- db = DBSCAN(eps=0.6, min_samples=2)
我們現(xiàn)在可以通過(guò)調(diào)用 Scikit-Learn 中的一個(gè)函數(shù)對(duì)循環(huán)數(shù)據(jù)使用 DBSCAN。在函數(shù)前面加上一個(gè)「%」,就可以讓 Jupyter Notebook 測(cè)量它的運(yùn)行時(shí)間。
- %%time
- y_db = db.fit_predict(X)
這 10 萬(wàn)個(gè)點(diǎn)的運(yùn)行時(shí)間是 8.31 秒,如下圖所示:
GPU 上帶 Rapids 的 DBSCAN
現(xiàn)在,讓我們用 Rapids 進(jìn)行加速!
首先,我們將把數(shù)據(jù)轉(zhuǎn)換為 pandas.DataFrame 并使用它創(chuàng)建一個(gè) cudf.DataFrame。pandas.DataFrame 無(wú)縫轉(zhuǎn)換成 cudf.DataFrame,數(shù)據(jù)格式無(wú)任何更改。
- import pandas as pd
- import cudf
- X_df = pd.DataFrame({'fea%d'%i: X[:, i] for i in range(X.shape[1])})
- X_gpu = cudf.DataFrame.from_pandas(X_df)
然后我們將從 cuML 導(dǎo)入并初始化一個(gè)特殊版本的 DBSCAN,它是 GPU 加速的版本。DBSCAN 的 cuML 版本的函數(shù)格式與 Scikit-Learn 的函數(shù)格式完全相同:相同的參數(shù)、相同的樣式、相同的函數(shù)。
- from cuml import DBSCAN as cumlDBSCAN
- db_gpu = cumlDBSCAN(eps=0.6, min_samples=2)
最后,我們可以在測(cè)量運(yùn)行時(shí)間的同時(shí)運(yùn)行 GPU DBSCAN 的預(yù)測(cè)函數(shù)。
- %%time
- y_db_gpu = db_gpu.fit_predict(X_gpu)
GPU 版本的運(yùn)行時(shí)間為 4.22 秒,幾乎加速了 2 倍。由于我們使用的是相同的算法,因此結(jié)果圖也與 CPU 版本完全相同。
使用 Rapids GPU 獲得超高速
我們從 Rapids 獲得的加速量取決于我們正在處理的數(shù)據(jù)量。一個(gè)好的經(jīng)驗(yàn)法則是,較大的數(shù)據(jù)集將更加受益于 GPU 加速。在 CPU 和 GPU 之間傳輸數(shù)據(jù)有一些開(kāi)銷時(shí)間——對(duì)于較大的數(shù)據(jù)集,開(kāi)銷時(shí)間變得更「值得」。
我們可以用一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明這一點(diǎn)。
我們將創(chuàng)建一個(gè)隨機(jī)數(shù)的 Numpy 數(shù)組并對(duì)其應(yīng)用 DBSCAN。我們將比較常規(guī) CPU DBSCAN 和 cuML 的 GPU 版本的速度,同時(shí)增加和減少數(shù)據(jù)點(diǎn)的數(shù)量,以了解它如何影響我們的運(yùn)行時(shí)間。
下面的代碼說(shuō)明如何進(jìn)行測(cè)試:
- import numpy as np
- n_rows, n_cols = 10000, 100
- X = np.random.rand(n_rows, n_cols)
- print(X.shape)
- X_df = pd.DataFrame({'fea%d'%i: X[:, i] for i in range(X.shape[1])})
- X_gpu = cudf.DataFrame.from_pandas(X_df)
- db = DBSCAN(eps=3, min_samples=2)
- db_gpu = cumlDBSCAN(eps=3, min_samples=2)
- %%time
- y_db = db.fit_predict(X)
- %%time
- y_db_gpu = db_gpu.fit_predict(X_gpu)
檢查下面的 Matplotlib 結(jié)果圖:
當(dāng)使用 GPU 而不是 CPU 時(shí),數(shù)量會(huì)急劇增加。即使在 10000 點(diǎn)(最左邊),我們的速度仍然是 4.54x。在更高的一端,1 千萬(wàn)點(diǎn),我們切換到 GPU 時(shí)的速度是 88.04x!
本文轉(zhuǎn)自雷鋒網(wǎng),如需轉(zhuǎn)載請(qǐng)至雷鋒網(wǎng)官網(wǎng)申請(qǐng)授權(quán)。