C4.5算法總結


C4.5是一系列用在機器學習和數據挖掘的分類問題中的算法。它的目標是監督學習:給定一個數據集,其中的每一個元組都能用一組屬性值來描述,每一個元組屬於一個互斥的類別中的某一類。C4.5的目標是通過學習,找到一個從屬性值到類別的映射關系,並且這個映射能用於對新的類別未知的實體進行分類。

    C4.5由J.Ross Quinlan在ID3的基礎上提出的。ID3算法用來構造決策樹。決策樹是一種類似流程圖的樹結構,其中每個內部節點(非樹葉節點)表示在一個屬性上的測試,每個分枝代表一個測試輸出,而每個樹葉節點存放一個類標號。一旦建立好了決策樹,對於一個未給定類標號的元組,跟蹤一條有根節點到葉節點的路徑,該葉節點就存放着該元組的預測。決策樹的優勢在於不需要任何領域知識或參數設置,適合於探測性的知識發現。

    從ID3算法中衍生出了C4.5和CART兩種算法,這兩種算法在數據挖掘中都非常重要。下圖就是一棵典型的C4.5算法對數據集產生的決策樹。

比如我們判斷一個人能不能結婚,那么每個人就可以作為一個具體的對象,該對象有着很多屬性,比如年齡,性別,帥不帥,工作NB不,有沒有女朋友,是不是富二代6個屬性,而結婚也作為該對象的一個屬性,而”結婚”屬性就可以作為我們的預測屬性!然后根據其他屬性來預測我們的目標屬性--結婚屬性,比如說,年齡:30,性別:男,長的帥,工作不錯,又女朋友,還是富二代!根據這些屬性我們就可以得出該人今年可以結婚!當然這是預測出來的!這時,我們肯定有個疑問了,這是如何預測的呢?這實質上是根據我們的統計數據得出的,比如我們統計10000個人,根據這一萬個人的6個屬性以及目標屬性(結婚)最終得出一組數據,我們用這組數據做成一個決策樹!而其中這10000個人的樣本我們則稱為訓練樣本!

 

我們還是拿”打高爾夫球”這個經典的例子來作具體研究吧!該例其實就是通過一些列的屬性來決定是否適合打高爾夫!剛剛說了訓練樣本,我們就來看看訓練樣本吧!圖1是我用WPF做了一個簡單的CRUD界面,用來把我們的樣本顯示的展現出來。具體看圖1。。

 

image

 

 

圖1  數據集

 我們從圖中可以看出,該表中共有6列,而每一列中的列名對應一個屬性,而我們以實踐經驗知道,“Day”即日期這個屬性並不能幫我們預測今天是否適合去打Golf.故該屬性我們就應該選擇摒棄!再看該系統中的其他5給屬性。很顯然,圖1中我用紅筆畫出來的屬性“Play Golf”該屬性就是我們的預測屬性。而其他4個屬性“Outlook”(天氣)”、Temperature”(溫度) 、“Humdity”(濕度)、“Windy”(是否刮風)這四個屬性進行判斷今天去 Play Golf。

那我們接下來的工作自然就是根據屬性1-4得出我們的決策樹了!那么我們來想想該決策樹的算法,實質上其遵循一種統一的遞歸模式:即,首先用根節點表示一個給定的數據集(比如在這,就是我們的14個樣本);然后,從根節點開始在每個節點上測試一個特定的屬性,把節點數據集划分成更小的子集(這一步,比如根據屬性Outlook划分,可以划分出三個子集出來,即屬於Sunny的一個子集樣本,屬於Overcast的子集樣本,屬於Rainy的子集樣本),該子集並用子樹進行表示;該過程就開始一直進行,直到子集稱為“純的”,也就是說直到子集中的所有實例都屬於同一個類別,樹才停止生長。

根據算法產生的決策樹:

 

        image

 

        圖2   在數據集上通過C4.5生成的決策樹

 看圖2,首先是根據Outlook屬性進行划分,根據Outlook的三個屬性值(Sunny、Overcast、Rainy)划分出了三個組合,而其中Overcast划分中的集合是“純”的了。故此子樹就停止生長了。而根據Sunny屬性值划分中的樣例集合1,2,8,9,11顯然還不是“純”的(該組樣例中有的PlayGolf是Yes,而有的是No),故需要再次對其進行划分,直到分組中的所有樣例都是“純”的位置,才停止生長。

    算法描述

 

    C4.5並不一個算法,而是一組算法—C4.5,非剪枝C4.5和C4.5規則。下圖中的算法將給出C4.5的基本工作流程:

 

    image

 

    圖3  C4.5算法流程

 

    我們可能有疑問,一個元組本身有很多屬性,我們怎么知道首先要對哪個屬性進行判斷,接下來要對哪個屬性進行判斷?換句話說,在圖2中,我們怎么知道第一個要測試的屬性是Outlook,而不是Windy?其實,能回答這些問題的一個概念就是屬性選擇度量。

 

    屬性選擇度量

 

     屬性選擇度量又稱分裂規則,因為它們決定給定節點上的元組如何分裂。屬性選擇度量提供了每個屬性描述給定訓練元組的秩評定,具有最好度量得分的屬性被選作給定元組的分裂屬性。目前比較流行的屬性選擇度量有--信息增益、增益率和Gini指標。

 

    先做一些假設,設D是類標記元組訓練集,類標號屬性具有m個不同值,m個不同類Ci(i=1,2,…,m),CiD是D中Ci類的元組的集合,|D|和|CiD|分別是D和CiD中的元組個數。

 

    (1)信息增益

 

    信息增益實際上是ID3算法中用來進行屬性選擇度量的。它選擇具有最高信息增益的屬性來作為節點N的分裂屬性。該屬性使結果划分中的元組分類所需信息量最小。對D中的元組分類所需的期望信息為下式:

 

        image (1)

 

Info(D)又稱為熵。

 

    現在假定按照屬性A划分D中的元組,且屬性A將D划分成v個不同的類。在該划分之后,為了得到准確的分類還需要的信息由下面的式子度量:

 

        image       (2)

 

    信息增益定義為原來的信息需求(即僅基於類比例)與新需求(即對A划分之后得到的)之間的差,即

 

        image       (3)

 

    我想很多人看到這個地方都覺得不是很好理解,所以我自己的研究了文獻中關於這一塊的描述,也對比了上面的三個公式,下面說說我自己的理解。

 

    一般說來,對於一個具有多個屬性的元組,用一個屬性就將它們完全分開幾乎不可能,否則的話,決策樹的深度就只能是2了。從這里可以看出,一旦我們選擇一個屬性A,假設將元組分成了兩個部分A1和A2,由於A1和A2還可以用其它屬性接着再分,所以又引出一個新的問題:接下來我們要選擇哪個屬性來分類?對D中元組分類所需的期望信息是Info(D) ,那么同理,當我們通過A將D划分成v個子集Dj(j=1,2,…,v)之后,我們要對Dj的元組進行分類,需要的期望信息就是Info(Dj),而一共有v個類,所以對v個集合再分類,需要的信息就是公式(2)了。由此可知,如果公式(2)越小,是不是意味着我們接下來對A分出來的幾個集合再進行分類所需要的信息就越小?而對於給定的訓練集,實際上Info(D)已經固定了,所以選擇信息增益最大的屬性作為分裂點。

 

    但是,使用信息增益的話其實是有一個缺點,那就是它偏向於具有大量值的屬性。什么意思呢?就是說在訓練集中,某個屬性所取的不同值的個數越多,那么越有可能拿它來作為分裂屬性。例如一個訓練集中有10個元組,對於某一個屬相A,它分別取1-10這十個數,如果對A進行分裂將會分成10個類,那么對於每一個類Info(Dj)=0,從而式(2)為0,該屬性划分所得到的信息增益(3)最大,但是很顯然,這種划分沒有意義。

 

  (2)信息增益率

 

   正是基於此,ID3后面的C4.5采用了信息增益率這樣一個概念。信息增益率使用“分裂信息”值將信息增益規范化。分類信息類似於Info(D),定義如下:

 

        image    (4)

 

這個值表示通過將訓練數據集D划分成對應於屬性A測試的v個輸出的v個划分產生的信息。信息增益率定義:

 

        image         (5)

 

選擇具有最大增益率的屬性作為分裂屬性。

 

    (3)Gini指標

 

    Gini指標在CART中使用。Gini指標度量數據划分或訓練元組集D的不純度,定義為:

 

        image         (6)

C#實現代碼如下: 

計算信息增益率:

  1 using System;
  2 using System.Collections;
  3 using System.Collections.Generic;
  4 using System.Data;
  5 using System.Linq;
  6 using System.Text;
  7 using System.Threading.Tasks;
  8 
  9 namespace C4._5.BLL
 10 {
 11     public class Entropy
 12     {
 13         public int[] statNum = new int[2];//訓練統計結果:0->No  1->Yes
 14         public double EntropyValue = 0;
 15         private int mTotal = 0;
 16         private string mTargetAttribute = "PlayGolf";
 17 
 18         public void getEntropy(DataTable samples)
 19         {
 20             CountTotalClass(samples,out statNum[0],out statNum[1]);
 21             EntropyValue = CalcEntropy(statNum[0],statNum[1]);
 22         }
 23         /// <summary>
 24         /// 統計各個樣本集合中所包含的目標屬性Yes或者No的數目
 25         /// </summary>
 26         public void CountTotalClass(DataTable samples,out int no,out int yes)
 27         {
 28             yes = no = 0;
 29             foreach (DataRow aRow in samples.Rows)
 30             {
 31                 if ((string)aRow[mTargetAttribute] == "Yes")
 32                     yes++;
 33                 else if ((string)aRow[mTargetAttribute] == "No")
 34                     no++;
 35                 else
 36                     throw new Exception("出錯!");
 37             }
 38         }
 39         /// <summary>
 40         /// 計算熵值
 41         /// </summary>
 42         /// <returns></returns>
 43         public double CalcEntropy(int no,int yes)
 44         {
 45             double entropy = 0;
 46             double total = (double)(yes + no);
 47             double p = 0;
 48             if (no != 0)
 49             {
 50                 p = no / total;
 51                 entropy += -p * Math.Log(p,2);
 52             }
 53             if (yes != 0)
 54             {
 55                 p = yes / total;
 56                 entropy += -p * Math.Log(p, 2);
 57             }
 58             return entropy;
 59         }
 60         /// <summary>
 61         /// 該注釋可能有問題,從屬性中的樣本集合中得到yes或者no的數目
 62         /// </summary>
 63         /// <param name="samples"></param>
 64         /// <param name="attribute"></param>
 65         /// <param name="value"></param>
 66         /// <param name="no"></param>
 67         /// <param name="yes"></param>
 68         public void GetValuesToAttribute(DataTable samples, Attribute attribute, string value, out int no, out int yes)
 69         {
 70             no = yes = 0;
 71             foreach (DataRow row in samples.Rows)
 72             {
 73                 if ((string)row[attribute.AttributeName] == value)
 74                 {
 75                     if ((string)row[mTargetAttribute] == "No")
 76                     {
 77                         no++;
 78                     }
 79                     else if ((string)row[mTargetAttribute] == "Yes")
 80                     {
 81                         yes++;
 82                     }
 83                     else
 84                     {
 85                         throw new Exception("出錯");
 86                     }
 87                 }   
 88             }
 89         }
 90         /// <summary>
 91         /// 計算信息收益
 92         /// </summary>
 93         /// <param name="samples"></param>
 94         /// <param name="attribute"></param>
 95         /// <returns></returns>
 96         public double Gain(DataTable samples, Attribute attribute)
 97         {
 98             mTotal = samples.Rows.Count;
 99             string[] values=attribute.values;
100             double sum=0.0;
101             for (int i = 0; i < values.Length; i++)
102             {
103                 int no, yes;
104                 no = yes = 0;
105                 GetValuesToAttribute(samples,attribute,values[i],out no,out yes);
106                 if (yes == (yes + no) || no == (yes + no))
107                 {
108                     sum += 0;
109                 }
110                 else
111                 {
112                     sum += (double)(yes + no) / (double)mTotal * (-(double)yes / (double)(yes + no) * Math.Log(((double)yes / (double)(yes + no)), 2) - (double)no / (double)(yes + no) * Math.Log(((double)no / (double)(yes + no)), 2));
113                 }
114             }
115             return SplitInfo(samples,mTargetAttribute)- sum;
116         }
117         /// <summary>
118         /// 獲得targetAttribute屬性下的所有屬性值
119         /// </summary>
120         /// <param name="samples"></param>
121         /// <param name="targetAttribute"></param>
122         /// <returns></returns>
123         private ArrayList GetDistinctValues(DataTable samples, string targetAttribute)
124         {
125             ArrayList distinctValues = new ArrayList(samples.Rows.Count);
126             foreach (DataRow row in samples.Rows)
127             {
128                 if (distinctValues.IndexOf(row[targetAttribute]) == -1)
129                     distinctValues.Add(row[targetAttribute]);
130             }
131             return distinctValues;
132         }
133         /// <summary>
134         /// 按某個屬性值計算該屬性的熵值
135         /// </summary>
136         /// <param name="samples"></param>
137         /// <param name="attribute"></param>
138         /// <returns></returns>
139         public double SplitInfo(DataTable samples, string attribute)
140         {
141             ArrayList values = GetDistinctValues(samples,attribute);
142             for (int i = 0; i < values.Count; i++)
143             {
144                 if (values[i] == null || (string)values[i] == "")
145                 {
146                     values.RemoveAt(i);
147                 }
148             }
149             int[] count=new int[values.Count];
150             for (int i = 0; i < values.Count; i++)
151             {
152                 foreach (DataRow aRow in samples.Rows)
153                 {
154                     if ((string)aRow[attribute] == (string)values[i])
155                         count[i]++;
156                 }
157             }
158             double entropy = 0;
159             double total = samples.Rows.Count;
160             double p = 0;
161             for (int i = 0; i < values.Count; i++)
162             {
163                 if (count[i] != 0)
164                 {
165                     p = count[i] / total;
166                     entropy += -p * Math.Log(p,2);
167                 }
168             }
169             return entropy;
170         }
171         /// <summary>
172         /// 獲得指定屬性的信息增益率
173         /// </summary>
174         /// <param name="samples">樣本集合</param>
175         /// <param name="attribute"></param>
176         /// <returns></returns>
177         public double GainRatio(DataTable samples, Attribute attribute)
178         {
179             double splitInfoA = this.SplitInfo(samples,attribute.AttributeName);//計算各個屬性的熵值
180             double gainA = Gain(samples,attribute);//信息增益
181             double gainRatioA = gainA / splitInfoA;
182             return gainRatioA;
183         }
184     }
185 }

構造決策樹:

 public class DTree_ID3
    {
        private string mTargetAttribute = "result";
        public Entropy en = new Entropy();
        public TreeNode roots;
        /// <summary>
        /// 獲得信息增益率最大的屬性
        /// </summary>
        /// <param name="samples"></param>
        /// <param name="attributes"></param>
        /// <returns></returns>
        private Attribute getBestAttribute(DataTable samples,Attribute[] attributes)
        {
            double maxGain = 0.0;
            Attribute bestAttribute = null;
            foreach (Attribute attribute in attributes)
            {
                double aux = en.GainRatio(samples,attribute);
                if (aux > maxGain)
                {
                    maxGain = aux;
                    bestAttribute = attribute;
                }
            }
            return bestAttribute;
        }
        /// <summary>
        /// 判斷樣例集是否屬於同一類,即該樣例集是否是"純"的,是則返回此屬性值,否則返回Null
        /// </summary>
        /// <param name="samples"></param>
        /// <param name="targetAttribute"></param>
        /// <returns></returns>
        public string AllSamplesSameClass(DataTable samples, string targetAttribute)
        {
            DataRow row = samples.Rows[0];
            string targetValue = (string)row[targetAttribute];
            for (int i = 1; i < samples.Rows.Count; i++)
            {
                if (targetValue!=samples.Rows[i][targetAttribute].ToString())
                {
                    return null;
                }
            }
            return targetValue;
        }
        /// <summary>
        /// 獲得屬性的目標屬性的值(解釋有可能錯誤)
        /// </summary>
        /// <param name="samples"></param>
        /// <param name="targetAttribute"></param>
        /// <returns></returns>
        private ArrayList GetDistinctValues(DataTable samples, string targetAttribute)
        {
            ArrayList distinctValues = new ArrayList(samples.Rows.Count);
            foreach (DataRow row in samples.Rows)
            {
                if (distinctValues.IndexOf(row[targetAttribute]) == -1)
                    distinctValues.Add(row[targetAttribute]);
            }
            return distinctValues;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="samples"></param>
        /// <param name="targetAttribute"></param>
        /// <returns></returns>
        private object GetMostCommonValue(DataTable samples, string targetAttribute)
        {
            ArrayList distinctValues = GetDistinctValues(samples,targetAttribute);
            int[] count=new int[distinctValues.Count];
            foreach (DataRow row in samples.Rows)
            {
                int index = distinctValues.IndexOf(row[targetAttribute]);
                count[index]++;
            }
            int MaxIndex = 0;
            int MaxCount = 0;
            for (int i = 0; i < count.Length; i++)
            {
                if (count[i] > MaxCount)
                {
                    MaxCount = count[i];
                    MaxIndex = i;
                }
            }
            return distinctValues[MaxIndex];
        }
        /// <summary>
        /// 構造決策樹
        /// </summary>
        /// <param name="samples">樣本集合</param>
        /// <param name="targetAttribute">目標屬性</param>
        /// <param name="attributes">該樣本所含的屬性集合</param>
        /// <returns></returns>
        private TreeNode BuildTree(DataTable samples, string targetAttribute, Attribute[] attributes)
        {
            TreeNode temp = new TreeNode();
            //如果samples中的元祖是同一類C
            string c = AllSamplesSameClass(samples,targetAttribute);
            if (c != null)   //返回N作為葉節點,以類C標記
                return new TreeNode(new Attribute(c).AttributeName + c);
            
            //if attributes為空,then
            if (attributes.Length == 0)//返回N作為葉子節點,標記為D中的多數類,多數表決
            {
                return new TreeNode(new Attribute(GetMostCommonValue(samples,targetAttribute)).AttributeName);
            }
            //計算目標屬性的熵值,即PlayGolf的熵值
            mTargetAttribute = targetAttribute;
            en.getEntropy(samples);
            //找出最好的分類屬性,即信息熵最大的
            Attribute bestAttribute = getBestAttribute(samples,attributes);
            //標記為節點root
            DTreeNode root = new DTreeNode(bestAttribute);
            temp.Text = bestAttribute.AttributeName;

            DataTable aSample = samples.Clone();
            //為bestAttribute的每個輸出value划分元祖並產生子樹
            foreach (string value in bestAttribute.values)
            {
                aSample.Rows.Clear();
                //aSamples為滿足輸出value的集合,即一個划分(分支)
                DataRow[] rows = samples.Select(bestAttribute.AttributeName+"="+"'"+value+"'");
                foreach (DataRow row in rows)
                {
                    aSample.Rows.Add(row.ItemArray);
                }
                //刪除划分屬性
                ArrayList aArributes = new ArrayList(attributes.Length-1);
                for (int i = 0; i < attributes.Length; i++)
                {
                    if (attributes[i].AttributeName != bestAttribute.AttributeName)
                    {
                        aArributes.Add(attributes[i]);
                    }
                }
                //如果aSample為空,加一個樹葉到節點N,標記為aSample中的多數類
                if (aSample.Rows.Count == 0)
                {
                    TreeNode leaf = new TreeNode();
                    leaf.Text = GetMostCommonValue(samples, targetAttribute).ToString() + "(" + value + ")";
                    temp.Nodes.Add(leaf);
                }
                else  //加一個由BulidTree(samples,targetAttribute,attributes)返回的節點到節點N
                {
                    DTree_ID3 dc3 = new DTree_ID3();
                    TreeNode ChildNode = dc3.BuildTree(aSample,targetAttribute,(Attribute[])aArributes.ToArray(typeof(Attribute)));
                    ChildNode.Text += "(" + value + ")";
                    temp.Nodes.Add(ChildNode);
                }
            }
            roots = temp;
            return temp;
        }
        public TreeNode MountTree(DataTable samples, string targetAttribute, Attribute[] attributes)
        {
            return BuildTree(samples, targetAttribute, attributes);
        }
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM