對于某些應(yīng)用,估計器的性能(主要是預(yù)測時間的延遲和吞吐量)至關(guān)重要??紤]訓(xùn)練吞吐量也可能是有意義的,但是在生產(chǎn)設(shè)置(通常在脫機(jī)中運(yùn)行)通常是不太重要的。
我們將在這里審查您可以從不同上下文中的一些 scikit-learn估計器預(yù)期的數(shù)量級,并提供一些 解決性能瓶頸的技巧和訣竅。
將預(yù)測的延遲作為進(jìn)行預(yù)測所需的耗費時間(例如,以微秒為單位)進(jìn)行測量。延遲通常被認(rèn)為一種分布,運(yùn)營工程師通常將注意力集中在該分布的給定百分位數(shù)(例如 90 百分位數(shù))上的延遲。
預(yù)測吞吐量被定義為軟件可以在給定的時間量內(nèi)可預(yù)測的預(yù)測數(shù)(例如每秒的預(yù)測)。
性能優(yōu)化需要注意的一個重要方面是它可能會損害預(yù)測精度。 實際上,更簡單的模型(例如 線性的,而不是 non-非線性的,或者具有較少參數(shù)的模型)通常運(yùn)行得更快,但是可能會丟失數(shù)據(jù)中包含的信息。
在使用/選擇機(jī)器學(xué)習(xí)工具包時可能遇到的最直接的問題之一是生產(chǎn)環(huán)境中發(fā)生的預(yù)測延遲。
影響 預(yù)測延遲的主要因素是
最后一個主要參數(shù)是在批量預(yù)測 或 一次執(zhí)行一個預(yù)測模式下進(jìn)行預(yù)測的可能性。
通常進(jìn)行批量預(yù)測 (可能有更多的方法) 非常有效的原因有很多(分支預(yù)測, CPU緩存, 線性代數(shù)庫的優(yōu)化 等.)。
在這里,我們看到一些具有很少特征的設(shè)置,獨立于估計器選擇,批量模式總是更快,而對于其中的一些,它們的數(shù)量大約是 1 到 2 個數(shù)量級:
為了對您的案例的不同的估計器進(jìn)行基準(zhǔn)測試,您可以在此示例中簡單地更改 n_features
參數(shù): 預(yù)測延遲. 這應(yīng)該給你估計預(yù)測延遲的數(shù)量級。
Scikit-learn 對數(shù)據(jù)進(jìn)行了一些驗證,從而增加了對 predict
和類似函數(shù)的調(diào)用開銷。特別地,檢查這些特征是有限制的(非空 或無限)涉及對數(shù)據(jù)的完全傳遞。如果你確定自己的數(shù)據(jù)是沒問題的,你可以通過在導(dǎo)入 scikit-learn 之前將環(huán)境變量配置 SKLEARN_ASSUME_FINITE
設(shè)置為非空字符串來抑制檢查有限性,或者使用以下方式在 Python 中配置 sklearn.set_config
。為了比這些全局設(shè)置更多的控制 config_context
允許您在指定的上下文中設(shè)置此配置:
>>> import sklearn
>>> with sklearn.config_context(assume_finite=True):
... pass # do learning/prediction here with reduced validation
注意這個配置會影響上下文中的 sklearn.utils.assert_all_finite
所有功能.
顯然,當(dāng)特征數(shù)量增加時,每個示例的內(nèi)存消耗量也會增加。實際上,對于具有個特征的 個實例的矩陣,空間復(fù)雜度在 。從 computing (計算)角度來看,這也意味著 the number of basic operations (基本操作的數(shù)量)(例如,線性模型中向量矩陣乘積的乘法)也增加。以下是 prediction latency (預(yù)測延遲)與 number of features(特征數(shù)) 的變化圖:
總的來說,您可以預(yù)期預(yù)測時間至少隨特征數(shù)量線性增加非線性情況可能會發(fā)生,取決于全局內(nèi)存占用和 估計。
Scipy 提供 用來優(yōu)化稀疏矩陣的稀疏矩陣數(shù)據(jù)結(jié)構(gòu). 稀疏矩陣最主要的特征是不存儲0值,因此當(dāng)你的數(shù)據(jù)是稀疏的這樣可以消耗更少的內(nèi)存。稀疏(CSR or CSC) 矩陣中的一個非零值僅需占用32bit的位置+64位浮點型的值+額外的32bit表示矩陣中的行或者列。在密集(或者稀疏)線性模型上使用稀疏輸入可以加速預(yù)測的速度,只有非零值特征才會影響點乘以至于影響預(yù)測結(jié)果。因此如果你在一個1e6維空間中只有100個非零值,那么你只需要100次乘法和加法運(yùn)算而不是1e6。
然而,密度表示的計算可以利用 BLAS 中高度優(yōu)化的向量操作和多線程,并且往往導(dǎo)致更少的 CPU 高速緩存 misses 。因此,sparse input (稀疏輸入)表示的 sparsity (稀疏度)通常應(yīng)相當(dāng)高(10% 非零最大值,要根據(jù)硬件進(jìn)行檢查)比在具有多個 CPU 和優(yōu)化 BLAS 實現(xiàn)的機(jī)器上的 dense input (密集輸入)表示更快。
以下是測試輸入 sparsity (稀疏度)的示例代碼:
def sparsity_ratio(X):
return 1.0 - np.count_nonzero(X) / float(X.shape[0] * X.shape[1])
print("input sparsity ratio:", sparsity_ratio(X))
根據(jù)經(jīng)驗,您可以考慮如果 sparsity ratio (稀疏比)大于 90% , 您可能會從 sparse formats (稀疏格式)中受益。有關(guān)如何構(gòu)建(或?qū)?shù)據(jù)轉(zhuǎn)換為) sparse matrix formats (稀疏矩陣格式)的更多信息,請參閱 Scipy 的稀疏矩陣格式文檔 documentation 。大多數(shù)的時候, CSR
和 CSC
格式是最有效的。
一般來說,當(dāng)模型的復(fù)雜成都增加了,預(yù)測性能和延遲也會一起增加。增長的預(yù)測性能通常是好的,但是對于許多應(yīng)用我們最好不要增加太多的預(yù)測延遲。我們將通過不同類別的監(jiān)督學(xué)習(xí)模型來重新認(rèn)識這個問題。
對于 sklearn.linear_model
(例如 Lasso, ElasticNet, SGDClassifier/Regressor, Ridge & RidgeClassifier, PassiveAggressiveClassifier/Regressor, LinearSVC, LogisticRegression…) 使用的決策函數(shù)是一致的 (點積) , 所以延遲應(yīng)該也是一致的。這里給一個例子使用 sklearn.linear_model.SGDClassifier
與 elasticnet
penalty(彈性網(wǎng)懲罰)。正則化的強(qiáng)度由alpha參數(shù)全局決定。一個足夠高的alpha,可以增加 elasticnet
的 l1_ratio
參數(shù),以在模型參數(shù)中執(zhí)行各種稀疏程度。這里的 Higher sparsity (較高稀疏度)被解釋為 less model complexity (較少的模型復(fù)雜度),因為我們需要較少的系數(shù)充分描述它。當(dāng)然, sparsity (稀疏性)會隨著稀疏點積 產(chǎn)生時間大致與非零系數(shù)的數(shù)目成比例地影響 prediction time (預(yù)測時間)。
對于具有非線性內(nèi)核的 sklearn.svm
算法系列,延遲與支持向量的數(shù)量有關(guān)(越少越快)。 在 SVC 或 SVR 模型中 延遲 和 吞吐量隨著支持向量的數(shù)量線性增長。內(nèi)核也將影響延遲,因為它用于計算每個支持向量一次輸入向量的投影。在下面的圖中, sklearn.svm.NuSVR
的 nu
參數(shù)用于影響支持向量的數(shù)量。
對于sklearn.ensemble
的 trees (例如 RandomForest, GBT, ExtraTrees 等)樹的數(shù)量及其深度發(fā)揮著最重要的作用。延遲和吞吐量應(yīng)與樹的數(shù)量呈線性關(guān)系。在這種情況下,我們直接使用 sklearn.ensemble.gradient_boosting.GradientBoostingRegressor
的 n_estimators
參數(shù)。
在任何情況下都應(yīng)該警告,降低的 model complexity (模型復(fù)雜性)可能會損害如上所述的準(zhǔn)確性。例如,可以用快速線性模型來處理 non-linearly separable problem (非線性可分離問題),但是在該過程中預(yù)測能力將很可能受到影響。
大多數(shù) scikit-learn 模型通常非常快,因為它們可以通過編譯的 Cython 擴(kuò)展或優(yōu)化的計算庫來實現(xiàn)。 另一方面,在許多現(xiàn)實世界的應(yīng)用中,特征提取過程(即,將 數(shù)據(jù)庫行或網(wǎng)絡(luò)分組的原始數(shù)據(jù)轉(zhuǎn)換為 numpy arrays )來控制總體預(yù)測時間。例如在路透社文本分類任務(wù)中,根據(jù)所選擇的模型,整個準(zhǔn)備(讀取和解析 SGML 文件,將文本進(jìn)行標(biāo)記并將其散列為公共向量空間)的時間比實際預(yù)測代碼的時間長 100 到 500 倍。
因此,在很多情況下,建議您仔細(xì)地對 carefully time and profile your feature extraction code ( 特征提取代碼進(jìn)行時間預(yù)估和簡檔),因為當(dāng)您的 overall latency (整體延遲)對您的應(yīng)用程序來說太慢時,可能是開始優(yōu)化的好地方。
另一個度量生產(chǎn)系統(tǒng)大小的重要的指標(biāo)是預(yù)測吞吐量,即在給定時間內(nèi)能做出預(yù)測的數(shù)量。
以下是 Prediction Latency 示例的基準(zhǔn)測試,該示例針對合成數(shù)據(jù)的多個 estimators (估計器)測量此數(shù)量:
這些 throughputs(吞吐量)早單個進(jìn)程上實現(xiàn)。提高應(yīng)用程序吞吐量的一個明顯的方法是產(chǎn)生其他實例(通常由于 GIL而在 Python 中進(jìn)行處理)共享相同模型。還可能添加機(jī)器來分布式負(fù)載。關(guān)于如何實現(xiàn)這一點的詳細(xì)解釋超出了本文檔的范圍。
由于 scikit-learn 在很大程度上依賴于 Numpy/Scipy 和 線性代數(shù),所以需要理解這些庫的版本。 基本上,你應(yīng)該確保使用優(yōu)化的 BLAS / LAPACK 構(gòu)建numpy庫.
并非所有的模型都受益于優(yōu)化的 BLAS 和 Lapack 實現(xiàn)。例如,基于(隨機(jī)化)決策樹的模型通常不依賴于內(nèi)部循環(huán)中的 BLAS 調(diào)用,kernel SVMs (SVC
, SVR
, NuSVC
, NuSVR
) 。另一方面,使用 BLAS DGEMM 調(diào)用(通過 numpy.dot
)實現(xiàn)的線性模型通常將受益于調(diào)整的 BLAS 實現(xiàn),并且導(dǎo)致非優(yōu)化 BLAS 的數(shù)量級加速。
你可以使用以下命令顯示您的 NumPy / SciPy / scikit-learn 安裝使用的 BLAS / LAPACK 實現(xiàn):
from numpy.distutils.system_info import get_info
print(get_info('blas_opt'))
print(get_info('lapack_opt'))
優(yōu)化的 BLAS / LAPACK 實現(xiàn)如下:
Atlas (需要通過在目標(biāo)機(jī)器上 rebuilding 進(jìn)行硬件特定調(diào)整)
OpenBLAS
MKL
Apple Accelerate and vecLib frameworks (僅適用于OSX系統(tǒng) )
有關(guān)更多信息,請參見 Scipy install page 和 Daniel Nouri 的 blog post 在這篇博客中介紹了如何一步一步在 Debian / Ubuntu中安裝配置的信息.
當(dāng)直接使用numpy的向量化操作執(zhí)行計算的時候,需要使用大量的臨時內(nèi)存空間。這個很可能會耗盡系統(tǒng)的內(nèi)存空間。在可以以固定內(nèi)存塊執(zhí)行計算的地方,我們嘗試這樣做,并允許用戶使用 sklearn.set_config
或config_context
提示該工作內(nèi)存的最大大小(默認(rèn)為1GB)。以下建議將臨時工作記憶限制在128mib:
>>> import sklearn
>>> with sklearn.config_context(working_memory=128):
... pass # do chunked work here
遵循此設(shè)置的塊操作的一個示例是metric.pairwise_distances_chunked,用于計算成對距離矩陣的行壓縮。
在scikit-learn中模型壓縮一般只在線性模型中考慮。在這種情況下,這意味著我們要控制模型稀疏度(即 模型向量中的非零坐標(biāo)數(shù))。將模型稀疏度與稀疏輸入數(shù)據(jù)表示相結(jié)合是一個好主意。
以下是示例代碼,說明了如何使用 sparsify()
方法:
clf = SGDRegressor(penalty='elasticnet', l1_ratio=0.25)
clf.fit(X_train, y_train).sparsify()
clf.predict(X_test)
在這個示例中,我們更喜歡 elasticnet
懲罰,因為它通常是模型緊湊性和預(yù)測能力之間的一個很好的妥協(xié)。還可以進(jìn)一步調(diào)整 l1_ratio
參數(shù)(結(jié)合正則化強(qiáng)度 alpha
)來控制這個權(quán)衡。
對于合成數(shù)據(jù),典型的 benchmark在模型和輸入時都會降低 30% 的延遲。稀疏(分別為 0.000024 和 0.027400 非零系數(shù)比)。您的里程可能會因您的數(shù)據(jù)和模型的稀疏性和大小而有所不同。 因此,為了減少部署在生產(chǎn)服務(wù)器上的預(yù)測模型的內(nèi)存使用,擴(kuò)展可能非常有用。
模型重塑在于僅選擇一部分可用功能以適應(yīng)模型。換句話說,如果模型在學(xué)習(xí)階段丟棄特征,我們可以從輸入中刪除這些特征。這有幾個好處。首先,它減少了模型本身的內(nèi)存(因此是減少了時間)的開銷。一旦知道要從上一次運(yùn)行中保留哪些功能,它也允許在 pipeline 中丟棄顯式的特征選擇組件。最后,它可以通過不收集和構(gòu)建模型丟棄的特征來幫助減少數(shù)據(jù)訪問和 特征提取層上游的處理時間和 I/O 的使用。例如,如果原始數(shù)據(jù)來自數(shù)據(jù)庫,則可以通過使查詢返回較輕的記錄,從而可以編寫更簡單和更快速的查詢或減少 I/O 的使用。 目前,重塑需要在 scikit-learn 中手動執(zhí)行。 在 稀疏輸入(特別是 CSR
格式)的情況下,通常不能生成相關(guān)的特征,使其列為空。
更多建議: