隨機森林演算法4種實現方法對比測試:DolphinDB速度最快,XGBoost表現最差
隨機森林是常用的機器學習演算法,既可以用於分類問題,也可用於迴歸問題。本文對scikit-learn、Spark MLlib、DolphinDB、XGBoost四個平臺的隨機森林演算法實現進行對比測試。評價指標包括記憶體佔用、執行速度和分類準確性。本次測試使用模擬生成的資料作為輸入進行二分類訓練,並用生成的模型對模擬資料進行預測。
1.測試軟體
本次測試使用的各平臺版本如下:
scikit-learn:Python 3.7.1,scikit-learn 0.20.2
Spark MLlib:Spark 2.0.2,Hadoop 2.7.2
DolphinDB:0.82
XGBoost:Python package,0.81
2.環境配置
CPU:Intel® Xeon® CPU E5-2650 v4 2.20GHz(共24核48執行緒)
RAM:512GB
作業系統:CentOS Linux release 7.5.1804
在各平臺上進行測試時,都會把資料載入到記憶體中再進行計算,因此隨機森林演算法的效能與磁碟無關。
3.資料生成
本次測試使用DolphinDB指令碼產生模擬資料,並匯出為CSV檔案。訓練集平均分成兩類,每個類別的特徵列分別服從兩個中心不同,標準差相同,且兩兩獨立的多元正態分佈N(0, 1)和N(2/sqrt(20), 1)。訓練集中沒有空值。
假設訓練集的大小為n行p列。本次測試中n的取值為10,000、100,000、1,000,000,p的取值為50。
由於測試集和訓練集獨立同分布,測試集的大小對模型準確性評估沒有顯著影響。本次測試對於所有不同大小的訓練集都採用1000行的模擬資料作為測試集。
產生模擬資料的DolphinDB指令碼見附錄1。
4.模型引數
在各個平臺中都採用以下引數進行隨機森林模型訓練:
- 樹的棵數:500
- 最大深度:分別在4個平臺中測試了最大深度為10和30兩種情況
- 劃分節點時選取的特徵數:總特徵數的平方根,即integer(sqrt(50))=7
- 劃分節點時的不純度(Impurity)指標:基尼指數(Gini index),該引數僅對Python scikit-learn、Spark MLlib和DolphinDB有效
- 取樣的桶數:32,該引數僅對Spark MLlib和DolphinDB有效
- 併發任務數:CPU執行緒數,Python scikit-learn、Spark MLlib和DolphinDB取48,XGBoost取24。
在測試XGBoost時,嘗試了引數nthread(表示執行時的併發執行緒數)的不同取值。但當該引數取值為本次測試環境的執行緒數(48)時,效能並不理想。進一步觀察到,線上程數小於10時,效能與取值成正相關。線上程數大於10小於24時,不同取值的效能差異不明顯,此後,執行緒數增加時效能反而下降。該現象在XGBoost社群中也有人討論過。因此,本次測試在XGBoost中最終使用的執行緒數為24。
5.測試結果
測試指令碼見附錄2~5。
當樹的數量為500,最大深度為10時,測試結果如下表所示:
當樹的數量為500,最大深度為30時,測試結果如下表所示:
從準確率上看,Python scikit-learn、Spark MLlib和DolphinDB的準確率比較相近,略高於XGBoost的實現;從效能上看,從高到低依次為DolphinDB、Python scikit-learn、XGBoost、Spark MLlib。
在本次測試中,Python scikit-learn的實現使用了所有CPU核。
Spark MLlib的實現沒有充分使用所有CPU核,記憶體佔用最高,當資料量為10,000時,CPU峰值佔用率約8%,當資料量為100,000時,CPU峰值佔用率約為25%,當資料量為1,000,000時,它會因為記憶體不足而中斷執行。
DolphinDB的實現使用了所有CPU核,並且它是所有實現中速度最快的,但記憶體佔用是scikit-learn的2-7倍,是XGBoost的3-9倍。DolphinDB的隨機森林演算法實現提供了numJobs引數,可以通過調整該引數來降低並行度,從而減少記憶體佔用。詳情請參考DolphinDB使用者手冊。
XGBoost常用於boosted trees的訓練,也能進行隨機森林演算法。它是演算法迭代次數為1時的特例。XGBoost實際上在24執行緒左右時效能最高,其對CPU執行緒的利用率不如Python和DolphinDB,速度也不及兩者。其優勢在於記憶體佔用最少。另外,XGBoost的具體實現也和其他平臺的實現有所差異。例如,沒有bootstrap這一過程,對資料使用無放回抽樣而不是有放回抽樣。這可以解釋為何它的準確率略低於其它平臺。
6.總結
Python scikit-learn的隨機森林演算法實現在效能、記憶體開銷和準確率上的表現比較均衡,Spark MLlib的實現在效能和記憶體開銷上的表現遠遠不如其他平臺。DolphinDB的隨機森林演算法實現效能最優,並且DolphinDB的隨機森林演算法和資料庫是無縫整合的,使用者可以直接對資料庫中的資料進行訓練和預測,並且提供了numJobs引數,實現記憶體和速度之間的平衡。而XGBoost的隨機森林只是迭代次數為1時的特例,具體實現和其他平臺差異較大,最佳的應用場景為boosted tree。
附錄
1.模擬生成資料的DolphinDB指令碼
def genNormVec(cls, a, stdev, n) {\treturn norm(cls * a, stdev, n)}def genNormData(dataSize, colSize, clsNum, scale, stdev) {\tt = table(dataSize:0, `cls join (\u0026quot;col\u0026quot; + string(0..(colSize-1))), INT join take(DOUBLE,colSize))\tclassStat = groupby(count,1..dataSize, rand(clsNum, dataSize))\tfor(row in classStat){\t\tcls = row.groupingKey\t\tclassSize = row.count\t\tcols = [take(cls, classSize)]\t\tfor (i in 0:colSize)\t\t\tcols.append!(genNormVec(cls, scale, stdev, classSize))\t\ttmp = table(dataSize:0, `cls join (\u0026quot;col\u0026quot; + string(0..(colSize-1))), INT join take(DOUBLE,colSize))\t\tinsert into t values (cols)\t\tcols = NULL\t\ttmp = NULL\t}\treturn t}colSize = 50clsNum = 2t1m = genNormData(10000, colSize, clsNum, 2 / sqrt(20), 1.0)saveText(t1m, \u0026quot;t10k.csv\u0026quot;)t10m = genNormData(100000, colSize, clsNum, 2 / sqrt(20), 1.0)saveText(t10m, \u0026quot;t100k.csv\u0026quot;)t100m = genNormData(1000000, colSize, clsNum, 2 / sqrt(20), 1.0)saveText(t100m, \u0026quot;t1m.csv\u0026quot;)t1000 = genNormData(1000, colSize, clsNum, 2 / sqrt(20), 1.0)saveText(t1000, \u0026quot;t1000.csv\u0026quot;)
2.Python scikit-learn的訓練和預測指令碼
import pandas as pdimport numpy as npfrom sklearn.ensemble import RandomForestClassifier, RandomForestRegressorfrom time import *test_df = pd.read_csv(\u0026quot;t1000.csv\u0026quot;)def evaluate(path, model_name, num_trees=500, depth=30, num_jobs=1): df = pd.read_csv(path) y = df.values[:,0] x = df.values[:,1:] test_y = test_df.values[:,0] test_x = test_df.values[:,1:] rf = RandomForestClassifier(n_estimators=num_trees, max_depth=depth, n_jobs=num_jobs) start = time() rf.fit(x, y) end = time() elapsed = end - start print(\u0026quot;Time to train model %s: %.9f seconds\u0026quot; % (model_name, elapsed)) acc = np.mean(test_y == rf.predict(test_x)) print(\u0026quot;Model %s accuracy: %.3f\u0026quot; % (model_name, acc))evaluate(\u0026quot;t10k.csv\u0026quot;, \u0026quot;10k\u0026quot;, 500, 10, 48) # choose your own parameter
3.Spark MLlib的訓練和預測程式碼
import org.apache.spark.mllib.tree.configuration.FeatureType.Continuousimport org.apache.spark.mllib.tree.model.{DecisionTreeModel, Node}object Rf { def main(args: Array[String]) = { evaluate(\u0026quot;/t100k.csv\u0026quot;, 500, 10) // choose your own parameter } def processCsv(row: Row) = { val label = row.getString(0).toDouble val featureArray = (for (i \u0026lt;- 1 to (row.size-1)) yield row.getString(i).toDouble).toArray val features = Vectors.dense(featureArray) LabeledPoint(label, features) } def evaluate(path: String, numTrees: Int, maxDepth: Int) = { val spark = SparkSession.builder.appName(\u0026quot;Rf\u0026quot;).getOrCreate() import spark.implicits._ val numClasses = 2 val categoricalFeaturesInfo = MapInt, Int val featureSubsetStrategy = \u0026quot;sqrt\u0026quot; val impurity = \u0026quot;gini\u0026quot;val maxBins = 32 val d_test = spark.read.format(\u0026quot;CSV\u0026quot;).option(\u0026quot;header\u0026quot;,\u0026quot;true\u0026quot;).load(\u0026quot;/t1000.csv\u0026quot;).map(processCsv).rdd d_test.cache() println(\u0026quot;Loading table (1M * 50)\u0026quot;) val d_train = spark.read.format(\u0026quot;CSV\u0026quot;).option(\u0026quot;header\u0026quot;,\u0026quot;true\u0026quot;).load(path).map(processCsv).rdd d_train.cache() println(\u0026quot;Training table (1M * 50)\u0026quot;) val now = System.nanoTime val model = RandomForest.trainClassifier(d_train, numClasses, categoricalFeaturesInfo, numTrees, featureSubsetStrategy, impurity, maxDepth, maxBins) println(( System.nanoTime - now )/1e9) val scoreAndLabels = d_test.map { point =\u0026gt; val score = model.trees.map(tree =\u0026gt; softPredict2(tree, point.features)).sum if (score * 2 \u0026gt; model.numTrees) (1.0, point.label) else (0.0, point.label) } val metrics = new MulticlassMetrics(scoreAndLabels) println(metrics.accuracy) } def softPredict(node: Node, features: Vector): Double = { if (node.isLeaf) { //if (node.predict.predict == 1.0) node.predict.prob else 1.0 - node.predict.prob node.predict.predict } else { if (node.split.get.featureType == Continuous) { if (features(node.split.get.feature) \u0026lt;= node.split.get.threshold) { softPredict(node.leftNode.get, features) } else { softPredict(node.rightNode.get, features) } } else { if (node.split.get.categories.contains(features(node.split.get.feature))) { softPredict(node.leftNode.get, features) } else { softPredict(node.rightNode.get, features) } } } } def softPredict2(dt: DecisionTreeModel, features: Vector): Double = { softPredict(dt.topNode, features) }}
4.DolphinDB的訓練和預測指令碼
def createInMemorySEQTable(t, seqSize) {\tdb = database(\u0026quot;\u0026quot;, SEQ, seqSize)\tdataSize = t.size()\tts = ()\tfor (i in 0:seqSize) {\t\tts.append!(t[(i * (dataSize/seqSize)):((i+1)*(dataSize/seqSize))])\t}\treturn db.createPartitionedTable(ts, `tb)}def accuracy(v1, v2) {\treturn (v1 == v2).sum() \\ v2.size()}def evaluateUnparitioned(filePath, numTrees, maxDepth, numJobs) {\ttest = loadText(\u0026quot;t1000.csv\u0026quot;)\tt = loadText(filePath); clsNum = 2; colSize = 50\ttimer res = randomForestClassifier(sqlDS(\u0026lt;select * from t\u0026gt;), `cls, `col + string(0..(colSize-1)), clsNum, sqrt(colSize).int(), numTrees, 32, maxDepth, 0.0, numJobs)\tprint(\u0026quot;Unpartitioned table accuracy = \u0026quot; + accuracy(res.predict(test), test.cls).string())}evaluateUnpartitioned(\u0026quot;t10k.csv\u0026quot;, 500, 10, 48) // choose your own parameter
5.XGBoost的訓練和預測指令碼
import pandas as pdimport numpy as npimport XGBoost as xgbfrom time import *def load_csv(path): df = pd.read_csv(path) target = df['cls'] df = df.drop(['cls'], axis=1) return xgb.DMatrix(df.values, label=target.values)dtest = load_csv('/hdd/hdd1/twonormData/t1000.csv')def evaluate(path, num_trees, max_depth, num_jobs): dtrain = load_csv(path) param = {'num_parallel_tree':num_trees, 'max_depth':max_depth, 'objective':'binary:logistic', 'nthread':num_jobs, 'colsample_bylevel':1/np.sqrt(50)} start = time() model = xgb.train(param, dtrain, 1) end = time() elapsed = end - start print(\u0026quot;Time to train model: %.9f seconds\u0026quot; % elapsed) prediction = model.predict(dtest) \u0026gt; 0.5 print(\u0026quot;Accuracy = %.3f\u0026quot; % np.mean(prediction == dtest.get_label()))evaluate('t10k.csv', 500, 10, 24) // choose your own parameter
作者介紹
王一能,浙江智臾科技有限公司,重點關注大資料、時序資料庫領域。
更多內容,請關注AI前線
相關文章
- 隨機森林演算法原理與Python實現隨機森林演算法Python
- Python實現隨機森林RF並對比自變數的重要性Python隨機森林變數
- PHP生成隨機密碼的4種方法及效能對比PHP隨機密碼
- Linux Shell 實現隨機數多種方法Linux隨機
- 隨機森林演算法隨機森林演算法
- 從決策樹到隨機森林:樹型演算法的原理與實現隨機森林演算法
- SOA實現中的4個最差實踐
- 隨機森林演算法梳理隨機森林演算法
- 一文讀懂隨機森林的解釋和實現隨機森林
- 特徵重要性評估的隨機森林演算法與Python實現(三)特徵隨機森林演算法Python
- 隨機森林演算法原理剖析隨機森林演算法
- 介面測試返回結構對比實現思路記錄
- php實現4種排序演算法PHP排序演算法
- 隨機之美,隨機森林隨機森林
- Verilog乘法的實現——幾種使用多級流水實現方法對比(2)
- 隨機森林R語言預測工具隨機森林R語言
- 隨機森林演算法深入淺出隨機森林演算法
- 如何實現介面異常場景測試?測試方法探索與測試工具實現
- 基於隨機森林演算法進行硬碟故障預測隨機森林演算法硬碟
- 隨機森林RF模型超引數的最佳化:Python實現隨機森林模型Python
- 演算法金 | 決策樹、隨機森林、bagging、boosting、Adaboost、GBDT、XGBoost 演算法大全演算法隨機森林
- JavaScript實現非同步的4種方法JavaScript非同步
- 權重隨機演算法的java實現隨機演算法Java
- 如何在MLlib中實現隨機森林和梯度提升樹(GBTs)?隨機森林梯度
- 隨機森林和機器學習隨機森林機器學習
- 用什麼方法對log檔案分析速度最快?
- PHP序列化的四種實現方法與橫向對比教程PHP
- C#中實現併發的幾種方法的效能測試C#
- 4種實現WordPress表格的方法,史上最全!
- MATLAB實現隨機森林(RF)迴歸與自變數影響程度分析Matlab隨機森林變數
- 對於JavaScript實現排序演算法的一些其他測試JavaScript排序演算法
- Java實現隨機抽獎的方法有哪些Java隨機
- 實現隨機顏色隨機
- 隨機森林的祕密隨機森林
- 隨機森林、EM、HMM、LDA隨機森林HMMLDA
- R:microtable包隨機森林隨機森林
- DolphinDB +Python Airflow 高效實現資料清洗PythonAI
- hive表連線和oracle測試對比HiveOracle