ランダムフォレストは、一般的に使用される機械学習アルゴリズムであり、分類問題と回帰問題の両方に使用できます。この記事では、scikit-learn、Spark MLlib、DolphinDB、およびxgboostの4つのプラットフォームでのランダムフォレストアルゴリズムの実装を比較およびテストします。評価指標には、メモリ使用量、実行速度、分類精度が含まれます。このテストでは、シミュレートされたデータをバイナリ分類トレーニングの入力として使用し、生成されたモデルを使用してシミュレートされたデータを予測します。
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パッケージ、0.81
2.環境構成
CPU:Intel(R)Xeon(R)CPU E5-2650 v4 2.20GHz(合計24コアと48スレッド)
RAM:512GB
オペレーティングシステム:CentOSLinuxリリース7.5.1804
各プラットフォームでテストする場合、データはメモリにロードされてから計算されるため、ランダムフォレストアルゴリズムのパフォーマンスはディスクとは関係ありません。
3.データ生成
このテストでは、DolphinDBスクリプトを使用してシミュレーションデータを生成し、CSVファイルとしてエクスポートします。トレーニングセットは2つのカテゴリに均等に分割されます。各カテゴリの特徴列は、2つの異なる中心、同じ標準偏差、およびペアごとに独立した多変量正規分布N(0、1)およびN(2 / sqrt(20)、1 )。トレーニングセットにnull値はありません。
トレーニングセットのサイズがn行p列であるとします。このテストでは、nの値は10,000、100,000、1,000,000であり、pの値は50です。
テストセットとトレーニングセットは独立しており、同じように分散されているため、テストセットのサイズはモデルの精度評価に大きな影響を与えません。このテストでは、さまざまなサイズのすべてのトレーニングセットのテストセットとして、1000行のシミュレートされたデータを使用します。
シミュレーションデータを生成するDolphinDBスクリプトについては、付録1を参照してください。
4.モデルパラメータ
次のパラメータは、各プラットフォームでのランダムフォレストモデルトレーニングに使用されます。
- 木の数:500
- 最大深度:最大深度10および30は、4つのプラットフォームでテストされました。
- ノードを分割するときに選択されるフィーチャの数:フィーチャの総数の平方根、つまりinteger(sqrt(50))= 7
- ノード分割時の不純物インデックス:Giniインデックス、このパラメーターはPython scikit-learn、Spark MLlib、およびDolphinDBでのみ有効です。
- サンプリングバケットの数:32、このパラメーターはSparkMLlibおよび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、SparkMLlibです。 。
このテストでは、Pythonscikit-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は、ブーストされたツリーのトレーニングでよく使用され、ランダムフォレストアルゴリズムを実行することもできます。これは、アルゴリズムの反復回数が1の場合の特殊なケースです。Xgboostは実際には約24スレッドで最高のパフォーマンスを発揮し、CPUスレッドの使用率はPythonやDolphinDBほど良くなく、速度も両方ほど良くありません。その利点は、メモリ使用量が最も少ないことにあります。さらに、xgboostの特定の実装も、他のプラットフォームの実装とは異なります。たとえば、置換ありのサンプリングではなく、置換なしのサンプリングを使用するブートストラッププロセスはありません。これは、その精度が他のプラットフォームよりもわずかに低い理由を説明できます。
6.まとめ
Python scikit-learnのランダムフォレストアルゴリズムは、バランスの取れたパフォーマンス、メモリオーバーヘッド、および精度を実現します。SparkMLlibの実装のパフォーマンスは、パフォーマンスとメモリオーバーヘッドの点で他のプラットフォームよりもはるかに劣ります。DolphinDBのランダムフォレストアルゴリズムは最高のパフォーマンスを実現し、DolphinDBのランダムフォレストアルゴリズムとデータベースはシームレスに統合されます。ユーザーはデータベース内のデータを直接トレーニングおよび予測し、numJobsパラメーターを提供してメモリと速度の違いを実現できます。残高。xgboostのランダムフォレストは、反復回数が1の場合の特殊なケースであり、特定の実装は他のプラットフォームとはまったく異なります。最適なアプリケーションシナリオは、ブーストツリーです。
付録
1.データ生成をシミュレートするDolphinDBスクリプト
def genNormVec(cls、a、stdev、n){ return norm(cls * a、stdev、n)} def genNormData(dataSize、colSize、clsNum、scale、stdev){ t = table(dataSize:0、 `cls join( "col" + string(0 ..(colSize-1)))、INT join take(DOUBLE、colSize)) classStat = groupby(count、1..dataSize、rand(clsNum、dataSize)) for(row in classStat) { cls = row.groupingKey classSize = row.count cols = [take(cls、classSize)] for(i in 0:colSize) cols.append!(genNormVec(cls、scale、stdev、classSize)) tmp = table(dataSize :0、 `cls join(" col "+ string(0 ..(colSize-1)))、INT join take(DOUBLE、colSize)) t値に挿入(cols) cols = NULL tmp = NULL } return t} colSize = 50clsNum = 2t1m = genNormData(10000、colSize、clsNum、2 / sqrt(20)、1.0)saveText(t1m、 "t10k.csv")t10m = genNormData(100000、colSize、clsNum、2 / sqrt( 20)、1.0)saveText(t10m、 "t100k.csv")t100m = genNormData(1000000、colSize、clsNum、2 / sqrt(20)、1.0)saveText(t100m、 "t1m.csv")t1000 = genNormData(1000、 colSize、clsNum、2 / sqrt(20)、1.0)saveText(t1000、 "t1000.csv")
2. Pythonscikit-トレーニングと予測のスクリプトを学ぶ
import pandas as pdimport numpy as npfrom sklearn.ensemble import RandomForestClassifier, RandomForestRegressorfrom time import * test_df = pd.read_csv("t1000.csv")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("Time to train model %s: %.9f seconds" % (model_name, elapsed)) acc = np.mean(test_y == rf.predict(test_x)) print("Model %s accuracy: %.3f" % (model_name, acc)) evaluate("t10k.csv", "10k", 500, 10, 48) # choose your own parameter
3. Spark MLlib的训练和预测代码(Scala实现)
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("/t100k.csv", 500, 10) // choose your own parameter } def processCsv(row: Row) = { val label = row.getString(0).toDouble val featureArray = (for (i <- 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("Rf").getOrCreate() import spark.implicits._ val numClasses = 2 val categoricalFeaturesInfo = Map[Int, Int]() val featureSubsetStrategy = "sqrt" val impurity = "gini" val maxBins = 32 val d_test = spark.read.format("CSV").option("header","true").load("/t1000.csv").map(processCsv).rdd d_test.cache() println("Loading table (1M * 50)") val d_train = spark.read.format("CSV").option("header","true").load(path).map(processCsv).rdd d_train.cache() println("Training table (1M * 50)") 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 => val score = model.trees.map(tree => softPredict2(tree, point.features)).sum if (score * 2 > 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) <= 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) { db = database("", SEQ, seqSize) dataSize = t.size() ts = () for (i in 0:seqSize) { ts.append!(t[(i * (dataSize/seqSize)):((i+1)*(dataSize/seqSize))]) } return db.createPartitionedTable(ts, `tb) }def accuracy(v1, v2) { return (v1 == v2).sum() \ v2.size() }def evaluateUnparitioned(filePath, numTrees, maxDepth, numJobs) { test = loadText("t1000.csv") t = loadText(filePath); clsNum = 2; colSize = 50 timer res = randomForestClassifier(sqlDS(<select * from t>), `cls, `col + string(0..(colSize-1)), clsNum, sqrt(colSize).int(), numTrees, 32, maxDepth, 0.0, numJobs) print("Unpartitioned table accuracy = " + accuracy(res.predict(test), test.cls).string()) } evaluateUnpartitioned("t10k.csv", 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("Time to train model: %.9f seconds" % elapsed) 予測= model.predict(dtest)> 0.5 print( "Accuracy =%。3f"%np.mean(prediction == dtest.get_label())) evaluate( 't10k.csv'、500、10、24)//独自のパラメーターを選択