標籤:機器學習 分類器 weka 演算法 源碼
(接上篇)
一、剪枝過程
上篇分析完了tree節點的構建過程,在REPTree.buildClassifier之後如果設定了剪枝選項,則還有一個剪枝和backfit過程。
if (!m_NoPruning) { m_Tree.insertHoldOutSet(prune); m_Tree.reducedErrorPrune(); m_Tree.backfitHoldOutSet(); }
其中insertHoldOutSet就是把剪枝用到的資料集傳進去,不具體的區跟代碼了。
重點卡一下reducedErrorPrune和backfitHoldOutSet過程。
二、Tree.reducedErrorPrune
protected double reducedErrorPrune() throws Exception {<span style="white-space:pre"></span>//這個函數會返回該樹及其子樹的一個錯誤情況,如果是枚舉類型返回的是分錯的instance數量,數實值型別返回的是與正確值的偏差的平方和 // 如果是葉子節點就不做任何操作 if (m_Attribute == -1) {return m_HoldOutError;//簡單的說一下這個error怎麼計算來的,使用<span style="font-size:18px;">insertHoldOutSet傳入資料時會根據原先訓練時的分布,來預測出傳入資料的class,然後根據這個結果和真正的class值進行比對,就知道是否分的正確了</span> } //計算一下所有的子樹的偏差 double errorTree = 0; for (int i = 0; i < m_Successors.length; i++) {errorTree += m_Successors[i].reducedErrorPrune(); } if (errorTree >= m_HoldOutError) {m_Attribute = -1;//如果子樹偏差大於本身的偏差,那子樹就沒啥存在的意義了,直接去掉。m_Successors = null;return m_HoldOutError; } else {return errorTree; } }可以看出,這個剪枝過程和J48相比還是簡單不少的。
三、Tree.backfitHoldOutSet
protected void backfitHoldOutSet() throws Exception { // Insert instance into hold-out class distribution if (m_Info.classAttribute().isNominal()) {// Nominal caseif (m_ClassProbs == null) { m_ClassProbs = new double[m_Info.numClasses()];}System.arraycopy(m_Distribution, 0, m_ClassProbs, 0, m_Info.numClasses()); for (int i = 0; i < m_HoldOutDist.length; i++) { m_ClassProbs[i] += m_HoldOutDist[i]; } if (Utils.sum(m_ClassProbs) > 0) { Utils.normalize(m_ClassProbs); } else { m_ClassProbs = null; } } else {// Numeric case double sumOfWeightsTrainAndHoldout = m_Distribution[1] + m_HoldOutDist[0]; if (sumOfWeightsTrainAndHoldout <= 0) { return; }if (m_ClassProbs == null) { m_ClassProbs = new double[1];} else { m_ClassProbs[0] *= m_Distribution[1]; }m_ClassProbs[0] += m_HoldOutDist[1];m_ClassProbs[0] /= sumOfWeightsTrainAndHoldout; } // The process is recursive if (m_Attribute != -1) { for (int i = 0; i < m_Successors.length; i++) { m_Successors[i].backfitHoldOutSet(); } } }可以看出,就是一個根據新傳入的資料集對原資料的分布進行重新計算,並且再對子樹進行遞迴的調用backfit的過程,不再詳細對代碼進行注釋了。
四、REPTree和J48的比較
同樣都是分類樹,REPTree和J48有很多不同點,下面簡單的說一說這些差異。
1、對連續值排序的處理
J48在處理連續值的時候,每一個subset都要進行排序,而REPTree是先在主流程中對所有屬性進行排序,並產生index傳給Tree節點來進行處理的。
因此J48所耗時間比較長,而REPTree則佔用較大記憶體(資料數量*資料屬性列數量,因此也可以看到REPTree的代碼中不斷的有顯式置空去嘗試釋放記憶體的操作),這是一個典型的時間和空間的tradeoff。
2、遞迴允出準則
J48的分裂停止條件有5個,
(1)所有的instances已經屬於同一個分類(selectModel裡)
(2)instances數量小於2*minNoObj(selectModel裡)
(3)一個分裂產生的資訊增益石0(selectModel裡)
(4)對離散值進行分裂節點的計算時,超過一個的Bag裡的instance數量小於minNoObj(spliter裡)
(5)對連續值進行分裂計算時,有效instances數量小於2*minNoObj(spliter裡)
REPTree的停止條件有4個
(1)訓練集數量小於2*minNum
(2)如果枚舉類型,且在一個類中
(3)如果數值類型,方差小於一個給定值
(4)達到最大深度
可以看出,主要的不同在於REPTree使用方差來判斷連續值是否結束分裂。
3、節點選擇方式
J48使用資訊增益率,REPTree使用資訊增益
4、剪枝與backfit
J48的剪枝較為複雜,分成了collapse()和prune()兩個操作,而REPTree的剪枝從邏輯上講只是J48的collapse操作,並沒有子樹上提等較為激進的剪枝策略。
J48沒有backfit,REPTree有backfit,這是因為J48就自己獨立的classifyInstance過程並不依賴樣本集的分布,而J48的classifyInstance是調用基類過程,需要自己儲存一個分布,進而使用backfit來防止過擬合。
Weka演算法Classifier-trees-REPTree源碼分析(二)