spark ml 隨機森林原始碼筆記一
以迴歸為例吧,迴歸在某些場合可能更精準
支援連續變數和類別變數,類別變數就是某個屬性有三個值,a,b,c,需要用Feature Transformers中的vectorindexer處理
上來是一堆引數
setMaxDepth:最大樹深度
setMaxBins:最大裝箱數,為了近似統計變數,比如變數有100個值,我只分成10段去做統計
setMinInstancesPerNode:每個節點最少例項
setMinInfoGain:最小資訊增益
setMaxMemoryInMB:最大記憶體MB單位,這個值越大,一次處理的節點劃分就越多
setCacheNodeIds:是否快取節點id,快取可以加速深層樹的訓練
setCheckpointInterval:檢查點間隔,就是多少次迭代固化一次
setImpurity:隨機森林有三種方式,entropy,gini,variance,迴歸肯定就是variance
setSubsamplingRate:設定取樣率,就是每次選多少比例的樣本構成新樹
setSeed:取樣種子,種子不變,取樣結果不變
setNumTrees:設定森林裡有多少棵樹
setFeatureSubsetStrategy:設定特徵子集選取策略,隨機森林就是兩個隨機,構成樹的樣本隨機,每棵樹開始分裂的屬性是隨機的,其他跟決策樹區別不大,註釋這麼寫的
* The number of features to consider for splits at each tree node.
* Supported options:
* - "auto": Choose automatically for task://預設策略
* If numTrees == 1, set to "all." //決策樹選擇所有屬性
* If numTrees > 1 (forest), set to "sqrt" for classification and //決策森林 分類選擇屬性數開平方,迴歸選擇三分之一屬性
* to "onethird" for regression.
* - "all": use all features
* - "onethird": use 1/3 of the features
* - "sqrt": use sqrt(number of features)
* - "log2": use log2(number of features) //還有取對數的
* (default = "auto")
*
* These various settings are based on the following references:
* - log2: tested in Breiman (2001)
* - sqrt: recommended by Breiman manual for random forests
* - The defaults of sqrt (classification) and onethird (regression) match the R randomForest
* package.
引數完畢,下面比較重要的是這段程式碼
val categoricalFeatures: Map[Int, Int] =
MetadataUtils.getCategoricalFeatures(dataset.schema($(featuresCol)))
這個地比較蛋疼的是dataset.schema($(featuresCol))
/** An alias for [[getOrDefault()]]. */
protected final def $[T](param: Param[T]): T = getOrDefault(param)
這段程式碼說明了$(featuresCol))只是求出一個欄位名,實戰中直接data.schema("features") ,data.schema("features")出來的是StructField,
case classStructField(name: String, dataType: DataType, nullable: Boolean = true, metadata: Metadata = Metadata.empty) extendsProduct with Serializable
StructField包含四個內容,最好知道一下,機器學習程式碼很多都用回頭說下getCategoricalFeatures,這個方法是識別一個屬性是二值變數還是名義變數,例如a,b就是二值變數,a,b,c就是名義變數,最終把屬性索引和變數值的個數放到一個map
這個函式的功能和vectorindexer類似,但是一般都用vectorindexer,因為實戰中我們大都從sql讀資料,sql讀出來的資料metadata是空,無法識別二值變數還是名義變數
後面是
val oldDataset: RDD[LabeledPoint] = extractLabeledPoints(dataset)
val strategy =
super.getOldStrategy(categoricalFeatures, numClasses = 0, OldAlgo.Regression, getOldImpurity)
val trees =
RandomForest.run(oldDataset, strategy, getNumTrees, getFeatureSubsetStrategy, getSeed)
.map(_.asInstanceOf[DecisionTreeRegressionModel])
val numFeatures = oldDataset.first().features.size
new RandomForestRegressionModel(trees, numFeatures)
可以看出還是調的RDD的舊方法,run這個方法是核心有1000多行,後面會詳細跟蹤,最後返回的是RandomForestRegressionModel,裡面有Array[DecisionTreeRegressionModel] ,就是生成的一組決策樹模型,也就是決策森林,另外一個是屬性數,我們繼續看RandomForestRegressionModel
在1.6版本每棵樹的權重都是1,裡面還有這麼一個方法
override protected def transformImpl(dataset: DataFrame): DataFrame = {
val bcastModel = dataset.sqlContext.sparkContext.broadcast(this)
val predictUDF = udf { (features: Any) =>
bcastModel.value.predict(features.asInstanceOf[Vector])
}
dataset.withColumn($(predictionCol), predictUDF(col($(featuresCol))))
}
可以看到把模型通過廣播的形式傳給exectors,搞一個udf預測函式,最後通過withColumn把預測資料粘到原資料後面,
注意這個寫法dataset.withColumn($(predictionCol), predictUDF(col($(featuresCol)))) ,第一個引數是列名,第二個是計算出來的col,col是列型別,預測方法如下
override protected def predict(features: Vector): Double = {
// TODO: When we add a generic Bagging class, handle transform there. SPARK-7128
// Predict average of tree predictions.
// Ignore the weights since all are 1.0 for now.
_trees.map(_.rootNode.predictImpl(features).prediction).sum / numTrees
}
可見預測用的是每個樹的跟節點,predictImpl(features)返回這個根節點分配的葉節點,這是一個遞迴呼叫的過程,關於如何遞迴,後面也會拿出來細說,最後再用.prediction方法把所有樹預測的結果相加求平均
後面有一個計算各屬性重要性的方法
lazy val featureImportances: Vector = RandomForest.featureImportances(trees, numFeatures)
實現如下
private[ml] def featureImportances(trees: Array[DecisionTreeModel], numFeatures: Int): Vector = {
val totalImportances = new OpenHashMap[Int, Double]()
trees.foreach { tree =>
// Aggregate feature importance vector for this tree 先計算每棵樹的屬性重要性值
val importances = new OpenHashMap[Int, Double]()
computeFeatureImportance(tree.rootNode, importances)
// Normalize importance vector for this tree, and add it to total.
// TODO: In the future, also support normalizing by tree.rootNode.impurityStats.count?
val treeNorm = importances.map(_._2).sum
if (treeNorm != 0) {
importances.foreach { case (idx, impt) =>
val normImpt = impt / treeNorm
totalImportances.changeValue(idx, normImpt, _ + normImpt)
}
}
}
// Normalize importances
normalizeMapValues(totalImportances)
// Construct vector
val d = if (numFeatures != -1) {
numFeatures
} else {
// Find max feature index used in trees
val maxFeatureIndex = trees.map(_.maxSplitFeatureIndex()).max
maxFeatureIndex + 1
}
if (d == 0) {
assert(totalImportances.size == 0, s"Unknown error in computing RandomForest feature" +
s" importance: No splits in forest, but some non-zero importances.")
}
val (indices, values) = totalImportances.iterator.toSeq.sortBy(_._1).unzip
Vectors.sparse(d, indices.toArray, values.toArray)
}
computeFeatureImportance的實現如下
/**
* Recursive method for computing feature importances for one tree.
* This walks down the tree, adding to the importance of 1 feature at each node.
* @param node Current node in recursion
* @param importances Aggregate feature importances, modified by this method
*/
private[impl] def computeFeatureImportance(
node: Node,
importances: OpenHashMap[Int, Double]): Unit = {
node match {
case n: InternalNode =>
val feature = n.split.featureIndex
val scaledGain = n.gain * n.impurityStats.count
importances.changeValue(feature, scaledGain, _ + scaledGain)
computeFeatureImportance(n.leftChild, importances)
computeFeatureImportance(n.rightChild, importances)
case n: LeafNode =>
// do nothing
}
}
由於屬性重要性是由gain概念擴充套件而來,這裡以gain來說明如何計算屬性重要性。
這裡首先可以看出為什麼每次樹的呼叫都回到rootnode的呼叫,因為要遞迴的沿著樹的層深往下游走,這裡遊走到葉節點什麼也不做,其他分裂節點也就是程式碼裡的InternalNode ,先找到該節點劃分的屬性索引,然後該節點增益乘以該節點資料量,然後更新屬性重要性值,這樣繼續遞迴左節點,右節點,直到結束
然後回到featureImportances方法,val treeNorm = importances.map(_._2).sum是把剛才計算的每棵樹的屬性重要性求和,然後計算每個屬性重要性佔這棵樹總重要性的比值,這樣整棵樹就搞完了,foreach走完,所有樹的屬性重要性就累加到totalImportances裡了,然後normalizeMapValues(totalImportances)再按剛才的方法算一遍,這樣出來的屬性值和就為1了,有了屬性個數和排好序的屬性重要性值,裝入向量,就是最終輸出的結果
入口方法就這些了
現在我們還有run方法的1000多行,還有如何遞迴分配節點這兩個點需要講,後面會繼續
相關文章
- 寫一個Spark DataSource的隨手筆記Spark筆記
- 【spark筆記】在idea用maven匯入spark原始碼Spark筆記IdeaMaven原始碼
- 直播商城原始碼,記一次 js隨機密碼原始碼JS隨機密碼
- 一句話總結隨機森林隨機森林
- 隨機森林演算法隨機森林演算法
- 隨機森林的祕密隨機森林
- R:microtable包隨機森林隨機森林
- 隨機森林、EM、HMM、LDA隨機森林HMMLDA
- 隨機森林和機器學習隨機森林機器學習
- 隨機森林演算法梳理隨機森林演算法
- 決策樹和隨機森林隨機森林
- 6. 整合學習&隨機森林隨機森林
- spark筆記Spark筆記
- jQuery原始碼學習筆記一jQuery原始碼筆記
- spark 原始碼分析之十五 -- Spark記憶體管理剖析Spark原始碼記憶體
- Spark RPC框架原始碼分析(三)Spark心跳機制分析SparkRPC框架原始碼
- Bagging(Bootstrap aggregating)、隨機森林(random forests)、AdaBoostboot隨機森林randomREST
- 隨機森林R語言預測工具隨機森林R語言
- 隨機森林演算法深入淺出隨機森林演算法
- spark 原始碼分析之十六 -- Spark記憶體儲存剖析Spark原始碼記憶體
- spark學習筆記--Spark SQLSpark筆記SQL
- spark學習筆記-- Spark StreamingSpark筆記
- 【機器學習】--決策樹和隨機森林機器學習隨機森林
- 微信MMKV原始碼閱讀隨筆原始碼
- Spark Shuffle機制詳細原始碼解析Spark原始碼
- 【筆記】如何產生隨機數筆記隨機
- 隨機過程複習筆記隨機筆記
- 一文讀懂隨機森林的解釋和實現隨機森林
- 20240505記錄《程式碼隨想錄》筆記筆記
- 聊聊基於Alink庫的隨機森林模型隨機森林模型
- 隨機森林n_estimators 學習曲線隨機森林
- 隨筆記筆記
- 筆記:ML-LHY-22: Ensemble筆記
- Spark學習筆記(三)-Spark StreamingSpark筆記
- 【機器學習】整合學習——Bagging與隨機森林機器學習隨機森林
- C程式隨機種子原始碼C程式隨機原始碼
- Python物件初探(《Python原始碼剖析》筆記一)Python物件原始碼筆記
- Spark簡明筆記Spark筆記
- spark學習筆記Spark筆記