A floresta aleatória é um algoritmo de aprendizado de máquina comumente usado, que pode ser usado tanto para problemas de classificação quanto para problemas de regressão. Este artigo compara e testa a implementação de algoritmos de floresta aleatórios em quatro plataformas: scikit-learn, Spark MLlib, DolphinDB e xgboost. Os indicadores de avaliação incluem uso de memória, velocidade de execução e precisão de classificação. Este teste usa dados simulados como entrada para treinamento de classificação binária e usa o modelo gerado para prever os dados simulados.
1. Software de teste
As versões da plataforma usadas neste teste são as seguintes:
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 : Pacote Python , 0,81
2. Configuração do ambiente
CPU: Intel (R) Xeon (R) CPU E5-2650 v4 2,20 GHz (24 núcleos e 48 threads no total)
RAM : 512 GB
Sistema operacional: CentOS Linux versão 7.5.1804
Ao testar em cada plataforma, os dados serão carregados na memória e então calculados, de forma que o desempenho do algoritmo de floresta aleatória não tem nada a ver com o disco.
3. Geração de dados
Este teste usa o script DolphinDB para gerar dados de simulação e exportá-los como um arquivo CSV. O conjunto de treinamento é dividido igualmente em duas categorias. As colunas de recursos de cada categoria estão sujeitas a dois centros diferentes, o mesmo desvio padrão e distribuições normais multivariadas independentes de pares N (0, 1) e N (2 / sqrt (20), 1 ) Não há valores nulos no conjunto de treinamento.
Suponha que o tamanho do conjunto de treinamento seja de n linhas e p colunas. Neste teste, o valor de n é 10.000, 100.000, 1.000.000 e o valor de p é 50.
Como o conjunto de teste e o conjunto de treinamento são independentes e distribuídos de forma idêntica, o tamanho do conjunto de teste não tem efeito significativo na avaliação da precisão do modelo. Este teste usa 1000 linhas de dados simulados como o conjunto de teste para todos os conjuntos de treinamento de tamanhos diferentes.
Consulte o Apêndice 1 para o script DolphinDB que gera dados de simulação.
4. Parâmetros do modelo
Os seguintes parâmetros são usados para treinamento de modelo de floresta aleatório em cada plataforma:
- Número de árvores: 500
- Profundidade máxima: A profundidade máxima de 10 e 30 foram testadas em 4 plataformas.
- O número de recursos selecionados ao dividir os nós: a raiz quadrada do número total de recursos, que é inteiro (sqrt (50)) = 7
- Índice de impureza ao dividir nós: índice de Gini, este parâmetro é válido apenas para Python scikit-learn, Spark MLlib e DolphinDB
- Número de intervalos de amostra: 32, este parâmetro é válido apenas para Spark MLlib e DolphinDB
- Número de tarefas simultâneas: número de threads de CPU, 48 para Python scikit-learn, Spark MLlib e DolphinDB e 24 para xgboost.
Ao testar o xgboost, tentei diferentes valores do parâmetro nthread (representando o número de threads simultâneos em tempo de execução). Porém, quando o valor do parâmetro é o número de threads neste ambiente de teste (48), o desempenho não é ideal. Observa-se ainda que quando o número de encadeamentos é inferior a 10, o desempenho está positivamente correlacionado com o valor. Quando o número de threads é maior que 10 e menor que 24, a diferença de desempenho de diferentes valores não é óbvia.Após isso, o desempenho diminui quando o número de threads aumenta. Este fenômeno também foi discutido na comunidade xgboost . Portanto, o número final de encadeamentos usados no xgboost neste teste é 24.
5. Resultados do teste
Veja o apêndice 2 ~ 5 para o script de teste.
Quando o número de árvores é 500 e a profundidade máxima é 10, os resultados do teste são mostrados na seguinte tabela:
Quando o número de árvores é 500 e a profundidade máxima é 30, os resultados do teste são mostrados na seguinte tabela:
Em termos de precisão, a precisão de Python scikit-learn, Spark MLlib e DolphinDB são semelhantes, ligeiramente superiores à implementação de xgboost; em termos de desempenho, de alto a baixo, eles são DolphinDB, Python scikit-learn, xgboost, Spark MLlib .
Neste teste, a implementação do Python scikit-learn usa todos os núcleos da CPU.
A implementação do Spark MLlib não faz uso completo de todos os núcleos da CPU e tem o maior uso de memória. Quando o volume de dados é 10.000, a taxa de ocupação máxima da CPU é de cerca de 8%. Quando o volume de dados é 100.000, a taxa de ocupação máxima da CPU é de cerca de 25%. Em 1.000.000, ele interromperá a execução devido à memória insuficiente.
A implementação do banco de dados DolphinDB usa todos os núcleos de CPU, e é a mais rápida de todas as implementações, mas sua pegada de memória é de 2 a 7 vezes maior do que scikit-learn e de 3 a 9 vezes maior do que xgboost. A implementação do algoritmo de floresta aleatória do DolphinDB fornece o parâmetro numJobs, que pode ser ajustado para reduzir o grau de paralelismo, reduzindo assim o uso de memória. Para obter detalhes, consulte o manual do usuário DolphinDB .
xgboost é frequentemente usado no treinamento de árvores impulsionadas e também pode executar algoritmos de floresta aleatórios. É um caso especial quando o número de iterações do algoritmo é 1. Na verdade, o Xgboost tem o desempenho mais alto com cerca de 24 threads, e sua utilização de threads de CPU não é tão boa quanto Python e DolphinDB, e a velocidade não é tão boa quanto ambos. Sua vantagem reside no menor uso de memória. Além disso, a implementação específica do xgboost também é diferente daquela de outras plataformas. Por exemplo, não há processo de bootstrap, usando amostragem sem substituição em vez de amostragem com substituição. Isso pode explicar porque sua precisão é um pouco menor do que outras plataformas.
6. Resumo
O algoritmo de floresta aleatória do Python scikit-learn atinge um desempenho equilibrado, sobrecarga de memória e precisão. O desempenho da implementação do Spark MLlib é muito inferior a outras plataformas em termos de desempenho e sobrecarga de memória. O algoritmo de floresta aleatória do DolphinDB atinge o melhor desempenho, e o algoritmo de floresta aleatória do DolphinDB e o banco de dados são perfeitamente integrados.Os usuários podem treinar e prever diretamente os dados no banco de dados e fornecer o parâmetro numJobs para obter a diferença entre memória e velocidade. Saldo. A floresta aleatória de xgboost é apenas um caso especial quando o número de iterações é 1 e a implementação específica é bastante diferente de outras plataformas.O melhor cenário de aplicação é a árvore impulsionada.
apêndice
1. Script DolphinDB que simula geração de dados
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 (linha em classStat) { cls = row.groupingKey classSize = row.count cols = [take (cls, classSize)] para (i em 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)) inserir em valores 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. Script de predição e treinamento Python scikit-learn
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) prediction = model.predict (dtest)> 0.5 print ("Accuracy =% .3f"% np.mean (prediction == dtest.get_label ())) avaliar ('t10k.csv', 500, 10, 24) // escolha seu próprio parâmetro