統計學習方法c++實現之四 決策樹

決策樹

前言

決策樹是一種基本的分類和迴歸算法,書中主要是討論了分類的決策樹。決策樹在每個結點分支規則是一種if-then規則,即知足某種條件就繼續搜索左子樹,不符合就去右子樹,看起來是用二叉樹實現對吧,實際的CART決策樹就是二叉樹,等會再介紹。如今先來看看決策樹的理論部分。代碼地址https://github.com/bBobxx/statistical-learning/blob/master/src/decisiontree.cppc++

決策樹相關理論

決策樹的學習一般包括三個部分:特徵選擇決策樹生成決策樹修剪git

特徵選擇github

咱們拋開煩人的公式和術語,用通俗的思想(沒辦法,本人只有通俗的思想)來理解一下,如今給你不少數據,有不少類,每一個數據有n維的特徵,怎麼分?最簡單的,不如來個全鏈接神經網絡,把數據丟進去,讓模型本身去學習去,恩.....這個辦法多是準確率最高的,可是咱們這裏學習的是決策樹,並且有些場景根本不須要神經網絡也能夠分類的很準確,如今讓咱們用決策樹解決這個問題。算法

首先,面對n維特徵,和k個類別,彷彿無從下手。咋辦呢,笨一點的辦法,就從第一個特徵開始,若是第一個特徵有m個不一樣取值,那我就按這個特徵取值把數據分紅m份,對這份特徵子集,我再選第二個特徵,第二個特徵好比有l個不一樣取值,那麼對於m個子集,每一個又能夠最多分出l個子集(最多而不是必定,由於m某個子集中的數據的第二維特徵可能取不全l個值),那麼如今咱們最多有\(m\times l\) 個子集,而後是第三維特徵......直到第n維特徵或者某個子集中的數據類別幾乎同樣咱們就中止。對於這種分法很明顯確實是個樹結構對吧,只不過你的樹多是這樣子的:網絡

在這裏插入圖片描述

很差意思,弄錯了,通常樹結構是這樣子的:學習

在這裏插入圖片描述

思路很簡單,可是過程很複雜對吧,沒錯,這就是決策樹,可是若是真寫成上面這樣也太沒效率了,好比說,如今給你不少人的數據,讓你分出是男是女,特徵有這麼幾個:身高,體重,頭髮長短,身份證上的性別。沒錯最後一個特徵通常不會給出的。如今開始按照上面的思路分類,就分10000個數據吧,身高的取值有十種,就當作150到190取十個數吧,體重先不談,若是從身高這個特徵開始分就能把你分吐血。聰明的同窗(應該是不笨的)一眼就能看出來,我直接用最後一個特徵,一會兒就分出來了,就算沒有最後這個特徵,我用頭髮長短這個也能夠很好的分。
沒錯,看出特徵選擇的重要了吧,這就是決策樹的第一步,要先選擇最具備分類能力的特徵,注意每一維特徵只用一次。怎麼選呢,這就涉及到了信息增益(ID3決策樹), 信息增益比(C4.5決策樹),和基尼指數(CART決策樹)。皮一下,這裏就只介紹基尼指數吧,其餘的就看書吧。測試

基尼指數:\(Gini(D,A)=\frac{|D_1|}{|D|}Gini(D_1)+\frac{|D_2|}{|D|}Gini(D_2)\)spa

其中,A表明某一維特徵,D表明的數據集合,根據A是否取a將D分爲\(D_1\)\(D_2\)兩個子集,\(|D|,|D_1|,|D_2|\)分別表明各自的數量。code

其中,\(Gini(D) = \sum_{k=1}^{K}\frac{|C_k|}{|D|}(1-\frac{|C_k|}{|D|})\)blog

\(C_k\)表明某一類,\(\frac{|C_k|}{|D|}\)表明這個集合中樣本是第k類的機率。

基尼指數越大,表示樣本集合的不肯定性越大,咱們在選取A的時候確定但願分完後集合越肯定越好,因此之後在進行特徵選擇的時候就須要選取基尼指數最小的那個特徵。

決策樹(CART)生成算法

  1. 對於當前根節點Root,對現有的樣本集D,對全部的特徵\(A_i\)的全部可能取值\(a_j\)計算基尼指數,選擇使基尼指數最小的\(A_i\)\(a_j\),根據樣本點對\(A_i=a_j\)的測試爲「是」或「否」將D分爲\(D_1\)\(D_2\)
  2. \(D_1\)做爲根節點Root的左子樹的根節點Root_L的樣本集,\(D_2\)做爲根節點Root的右子樹的根節點Root_R的樣本集。
  3. 重複1,2直到結點中樣本個數小於閾值,或樣本集基本屬於同一類,或者沒有更多特徵(表明已經將全部的特徵都過一遍了)。

CART剪枝

請自行看書,反正我也沒實現。

決策樹的c++實現

代碼結構

在這裏插入圖片描述

實現

這裏只展現如何肯定分割的特徵和值

pair<int, double> DecisionTree::createSplitFeature(vector<vector<double >>& valRange){
    priority_queue<pair<double, pair<int, double>>, vector<pair<double, pair<int, double>>>, std::greater<pair<double, pair<int, double>>>> minheap;
      //pair<double, pair<int, double>> first value is Gini value, second pair (pair<int, double>) first value is split
      //axis, second value is split value
    vector<map<double, int>> dataDivByFeature(indim);  //vector size is num of axis, map's key is the value of feature, map's value is
      //num belong to feature'value
    vector<set<double>> featureVal(indim);  //store value for each axis
    vector<map<pair<double, double>, int>> datDivByFC(indim);  //vector size is num of axis, map's key is the feature value and class value, map's value is
      //num belong to that feature value and class
    set<double> cls;  //store num of class
    for(const auto& featureId:features) {
        if (featureId<0)
            continue;
        map<double, int> dataDivByF;
        map<pair<double, double>, int> dtDivFC;
        set<double> fVal;
        for (auto& data:valRange){  //below data[featureId] is the value of one feature axis, data.back() is class value
            cls.insert(data.back());
            fVal.insert(data[featureId]);
            if (dataDivByF.count(data[featureId]))
                dataDivByF[data[featureId]] += 1;
            else
                dataDivByF[data[featureId]] = 0;
            if (dtDivFC.count(std::make_pair(data[featureId], data.back())))
                dtDivFC[std::make_pair(data[featureId], data.back())] += 1;
            else
                dtDivFC[std::make_pair(data[featureId], data.back())] = 0;
        }
        featureVal[featureId] = fVal;
        dataDivByFeature[featureId] = dataDivByF;
        datDivByFC[featureId] = dtDivFC;
    }
    for (auto& featureId: features) {  // for each feature axis
        if (featureId<0)
            continue;
        for (auto& feVal: featureVal[featureId]){  //for each feature value
            double gini1 = 0 ;
            double gini2 = 0 ;

            double prob1 = dataDivByFeature[featureId][feVal]/double(valRange.size());
            double prob2 = 1 - prob1;
            for (auto& c : cls){  //for each class
                double pro1 = double(datDivByFC[featureId][std::make_pair(feVal, c)])/dataDivByFeature[featureId][feVal];
                gini1 += pro1*(1-pro1);
                int numC = 0;
                for (auto& feVal2: featureVal[featureId])
                    numC += datDivByFC[featureId][std::make_pair(feVal2, c)];
                double pro2 = double(numC-datDivByFC[featureId][std::make_pair(feVal, c)])/(valRange.size()-dataDivByFeature[featureId][feVal]);
                gini2 += pro2*(1-pro2);
            }
            double gini = prob1*gini1+prob2*gini2;

            minheap.push(std::make_pair(gini, std::make_pair(featureId, feVal)));
        }
    }
    features[minheap.top().second.first]=-1;
    return minheap.top().second;
}

這裏使用循環嵌套計算符合條件的數據的數量,效率很低,有更好方法的同窗麻煩告知一下,叩拜~

相關文章
相關標籤/搜索