1. 程式人生 > >轉:機器學習演算法原理解析 - 分類

轉:機器學習演算法原理解析 - 分類

轉:http://www.cnblogs.com/swordfall/p/9517988.html

常見分類模型與演算法

  1. 距離判別法,即最近鄰演算法KNN;
  2. 貝葉斯分類器;
  3. 線性判別法,即邏輯迴歸演算法;
  4. 決策樹;
  5. 支援向量機;
  6. 神經網路;

1. KNN分類演算法原理及應用

1.1 KNN概述

K最近鄰(k-Nearest Neighbor,KNN)分類演算法是最簡單的機器學習演算法。

KNN演算法的指導思想是“近朱者赤,近墨者黑”,由你的鄰居來推斷你的型別。

本質上,KNN演算法就是用距離來衡量樣本之間的相似度。

1.2 演算法圖示

  • 從訓練集中找到和新資料最接近的k條記錄,然後根據多數類來決定新資料類別
  • 演算法涉及3個主要因素
    • 1) 訓練資料集
    • 2) 距離或相似度的計算衡量
    • 3) k的大小

        

  • 演算法描述
  • 1) 已知兩類“先驗”資料,分別是藍方塊和紅三角,他們分佈在一個二維空間中;
  • 2) 有一個未知類別的資料(綠點),需要判斷它是屬於“藍方塊”還是“紅三角”類;
  • 3) 考察離綠點最近的3個(或k個)資料點的類別,佔多數的類別即為綠點判定類別;

1.3 演算法要點

1.3.1 計算步驟

計算步驟如下:

         1) 算距離:給定測試物件,計算它與訓練集中的每個物件的距離;

          2) 找鄰居:圈定距離最近的k個訓練物件,作為測試物件的近鄰;

          3) 做分類:根據這k個近鄰歸屬的主要類別,來對測試物件分類;

1.3.2 相似度的衡量

  • 距離越近應該意味著這兩個點屬於一個分類的可能性越大,但,距離不能代表一切,有些資料的相似度衡量並不適合用距離;
  • 相似度衡量方法:包括歐式距離、夾角餘弦等。

(簡單應用中,一般使用歐式距離,但對於文字分類來說,使用餘弦來計算相似度就比歐式距離更合適)

1.3.3 類別的判定

  • 簡單投票法:少數服從多數,近鄰中哪個類別的點最多就分為該類
  • 加權投票法:根據距離的遠近,對近鄰的投票進行加權,距離越近則權重越大(權重為距離平方的倒數)

1.4 演算法不足之處

1. 樣本不平衡容易導致結果錯誤

  • 如一個類的樣本容量很大,而其他類樣本容量很小時,有可能導致當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本佔多數。
  • 改善方法:對此可以採用權值的方法(和該樣本距離小的鄰居權值大)來改進。

2. 計算量較大

  • 因為對每一個待分類的文字都要計算它到全體已知樣本的距離,才能求得它的K個最近鄰點。
  • 改善方法:實現對已知樣本點進行剪輯,事先去除對分類作用不大的樣本。

:該方法比較適用於樣本容量比較大的類域的類域的分類,而那些樣本容量較小的類域採用這種演算法比較容易產生誤分。

1.5 KNN分類演算法Python實戰——KNN簡單資料分類實踐

1.5.1 需求

計算地理位置的相似度

有以下先驗資料,使用KNN演算法對未知類別資料分類

屬性1

屬性2

類別

1.0

0.9

A

1.0

1.0

A

0.1

0.2

B

0.0

0.1

B

未知類別資料

屬性1

屬性2

類別

1.2

1.0

?

0.1

0.3

?

1.5.2 Python實現

首先,我們新建一個KNN.py指令碼檔案,檔案裡面包含兩個函式,一個用來生成小資料集,一個實現KNN分類演算法。程式碼如下:

複製程式碼
##########################
# KNN: k Nearest Neighbors
#輸入:newInput: (1xN)的待分類向量
#         dataSet:   (NxM)的訓練資料集
#         labels:      訓練資料集的類別標籤向量
#         k:             近鄰數
# 輸出:可能性最大的分類標籤
##########################

from numpy import
import operator

#建立一個數據集,包含2個類別共4個樣本
def createDataSet():
        # 生成一個矩陣,每行表示一個樣本
        group = array([[1.0,0.9],[1.0,1.0],[0.1,0.2],[0.0,0.1]])
        # 4個樣本分別所屬的類別
        labels = ['A', 'A', 'B', 'B']
        return group, labels

# KNN分類演算法函式定義
def KNNClassify(newInput, dataSet, labels, k):
        numSamples = dataSet.shape[0]  #shape[0]表示行數
      
        ## step1:計算距離
        # tile(A, reps):構造一個矩陣,通過A重複reps次得到
        # the following copy numSamples rows for dataSet
        diff = tile(newInput, (numSamples, 1)) -dataSet #按元素求差值
        squareDiff = diff ** 2 #將差值平方
        squareDist = sum(squaredDiff, axis = 1) # 按行累加

        ##step2:對距離排序
        # argsort() 返回排序後的索引值
        sortedDistIndices = argsort(distance)
        classCount = {} # define a dictionary (can be append element)
        for i in xrange(k):
             ##step 3: 選擇k個最近鄰
             voteLabel = labels[sortedDistIndices[i]]

             ## step 4:計算k個最近鄰中各類別出現的次數
             # when the key voteLabel is not in dictionary classCount,get()
             # will return 0
             classCount[voteLabel] = classCount.get(voteLabel, 0) + 1

##step 5:返回出現次數最多的類別標籤
maxCount = 0
for key, value in classCount.items():
         if value > maxCount:
             maxCount = value
             maxIndex = key

         return maxIndex
複製程式碼

然後呼叫演算法進行測試

複製程式碼
import KNN
from numpy import * 
#生成資料集和類別標籤
dataSet,labels = KNN.createDataSet()
#定義一個未知類別的資料
testX = array([1.2, 1.0])
k=3
#呼叫分類函式對未知資料分類
outputLabel = KNN.KNNClassify(testX, dataSet, labels, 3)
print "Your input is:", testX, " and classified to class:", outputLabel

testX = array([0.1, 0.3])
outputLabel = KNN.KNNClassify(testX,dataSet, labels, 3)
print "Your input is:", testX, "and classified to class:", outputLabel
複製程式碼

這時候會輸出:

Your input is: [1.2 1.0] and classified to class: A
Your input is: [0.1 0.3] and classified to class: B

2. 樸素貝葉斯分類演算法原理

2.1 概述

貝葉斯分類演算法時一大類分類演算法的總稱。貝葉斯分類演算法以樣本可能屬於某類的概率來作為分類依據。樸素貝葉斯分類演算法時貝葉斯分類演算法中最簡單的一種。

:樸素的意思時條件概率獨立性

2.2 演算法思想

樸素貝葉斯的思想是這樣的:如果一個事物在一些屬性條件發生的情況下,事物屬於A的概率>屬於B的概率,則判定事物屬於A。

通俗來說比如,在某條大街上,有100人,其中有50個美國人,50個非洲人,看到一個講英語的黑人,那麼我們是怎麼去判斷他來自哪裡?

提取特徵

膚色:黑,語言:英語

 

先驗知識:

P(黑色|非洲人) = 0.8

P(講英語|非洲人)=0.1

 

P(黑色|美國人)= 0.2

P(講英語|美國人)=0.9

 

要判斷的概率是:

P(非洲人|(講英語,黑色) )

P(美國人|(講英語,黑色) )

 

思考過程:

P(非洲人|(講英語,黑色) )   的 分子= 0.1 * 0.8 *0.5 =0.04

P(美國人|(講英語,黑色) )   的 分子= 0.9 *0.2 * 0.5 = 0.09

從而比較這兩個概率的大小就等價於比較這兩個分子的值,可以得出結論,此人應該是:美國人。

其蘊含的數學原理如下:

p(A|xy)=p(Axy)/p(xy)=p(Axy)/p(x)p(y)=p(A)/p(x)*p(A)/p(y)* p(xy)/p(xy)=p(A|x)p(A|y)

樸素貝葉斯分類器

  講了上面的小故事,我們來樸素貝葉斯分類器的表示形式:

 

  當特徵為為x時,計算所有類別的條件概率,選取條件概率最大的類別作為待分類的類別。由於上公式的分母對每個類別都是一樣的,因此計算時可以不考慮分母,即

 

  樸素貝葉斯的樸素體現在其對各個條件的獨立性假設上,加上獨立假設後,大大減少了引數假設空間。  

2.3 演算法要點

2.3.1 演算法步驟

1. 分解各類先驗樣本資料中的特徵;

2. 計算各類資料中,各特徵的條件概率;(比如:特徵1出現的情況下,屬於A類的概率p(A|特徵1),屬於B類的概率p(B|特徵1),屬於C類的概率p(C|特徵1)......)

3. 分解待分類資料中的特徵(特徵1、特徵2、特徵3、特徵4......)

4. 計算各特徵的各條件概率的乘積,如下所示:

    判斷為A類的概率:p(A|特徵1) * p(A|特徵2) * p(A|特徵3) * p(A|特徵4)......

    判斷為B類的概率:p(B|特徵1) * p(B|特徵2) * p(B|特徵3) * p(B|特徵4)......

   判斷為C類的概率:p(C|特徵1) * p(C|特徵2) * p(C|特徵3) * p(C|特徵4)......

   ......

5. 結果中的最大值就是該樣本所屬的類別

2.3.2 演算法應用舉例

大眾點評、淘寶等電商上都會有大量的使用者評論,比如:

1、衣服質量太差了!!!!顏色根本不純!!!

2、我有一有種上當受騙的感覺!!!!

3、質量太差,衣服拿到手感覺像舊貨!!!

4、上身漂亮,合身,很帥,給賣家點贊

5、穿上衣服帥呆了,給點一萬個贊

6、我在他家買了三件衣服!!!!質量都很差!

0

0

0

1

1

0

其中1/2/3/6是差評,4/5是好評

現在需要使用樸素貝葉斯分類演算法來自動分類其他的評論,比如:

a、這麼差的衣服以後再也不買了

b、帥,有逼格

……

2.3.3 演算法應用流程

1. 分解出先驗資料中的各特徵

    (即分詞,比如“衣服”,“質量太差”,“差”,“不純”,“帥”,“漂亮”,“贊” ......)

2. 計算各類別(好評、差評)中,各特徵的條件概率

    (比如 p(“衣服” | 差評)、p(“衣服” | 好評)、p(“差”|好評)、p(“差”| 差評) ......)

3. 計算類別概率

    p(好評|(c1,c2,c5,c8))的分子=p(c1|好評) * p(c2|好評) * p(c3|好評) *......p(好評)

    p(差評|(c1,c2,c5,c8))的分子=p(c1|差評) * p(c2|差評) * p(c3|差評) *......p(差評)

   

4. 顯然p(差評)的結果值更大,因此a被判別為"差評"

2.4 樸素貝葉斯分類演算法案例

2.4.1 需求

利用大量郵件先驗資料,使用樸素貝葉斯分類演算法來自動識別垃圾郵件

2.4.2 python實現

複製程式碼
#過濾垃圾郵件
def textParse(bigString):      #正則表示式進行文字解析
      import re
      listOfTokens = re.split(r'\W*', bigString)
      return [tok.lower() for tok in listOfTokens if len(tok) > 2]

def spamTest()
      docList = []; classList = []; fullText = []
      for i in range(1,26):     #匯入並解析文字檔案
             wordList = textParse(open('email/spam/%d.txt'%i).read())
             docList.append(wordList)
             fullText.extend(wordList)
             classList.append(1)
             wordList = textParse(open('email/ham/%d.txt'%i).read())
             docList.append(wordList)
             fullText.extend(wordList)
             classList.append(0)
      vocabList = createVocabList(docList)
      trainingSet = range(50);testSet = []
      for i in range(10):                  #隨機構建訓練集
             randIndex = int(random.uniform(0, len(trainingSet)))
             testSet.append(trainingSet[randIndex]) #隨機挑選一個文件索引號放入測試集
              del(trainingSet[randIndex])  #將該文件索引號從訓練集中剔除
       trainMat = []; trainClasses = []
       for docIndex in trainingSet:
              trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
              trainClasses.append(classList[docIndex])
       p0V,p1V,pSpam = trainNBO(array(trainMat), array(trainClasses))
       errorCount = 0
       for docIndex in testSet:        #對測試集進行分類
             wordVector = setOfWords2Vec(vocabList, docList[docIndex])
             if classifyNB(array(wordVector), p0V,p1V != classList[docIndex]:
                 errorCount +=1
        print 'the error rate is:', float(errorCount)/len(testSet)
複製程式碼

3. logistic邏輯迴歸分類演算法及應用

3.1 概述

Lineage邏輯迴歸是一種簡單而又效果不錯的分類演算法。

什麼是迴歸:比如說我們有兩類資料,各有50個點組成,當我們把這些點畫出來,會有一條線區分這兩組資料,我們擬合出這個曲線(因為很有可能是非線性的),就是迴歸。我們通過大量的資料找出這條線,並擬合出這條線的表示式,再有新資料,我們就以這條線為區分來實現分類。

下圖是一個數據集的兩組資料,中間有一條區分兩組資料的線。

顯然,只有這種線性可分的資料分佈才適合用線性邏輯迴歸

3.2 演算法思想

Lineage迴歸分類演算法就是將線性迴歸應用在分類場景中

在該場景中,計算結果是要得到對樣本資料的分類標籤,而不是得到那條迴歸直線

3.2.1 演算法圖示

1) 演算法目標()?

大白話:計算各點的y值到擬合線的垂直距離,如果距離>0,分為類A;距離<0,分為類B。

2) 如何得到擬合線呢?

大白話:只能先假設,因為線或面的函式都可以表達成y(擬合)=w1 * x1 + w2 * x2 + w3 * x3 + ... ,其中的w是待定引數,而x是資料的各維度特徵值,因而上述問題就變成了樣本y(x) - y(擬合) > 0? A:B

3) 如何求解出一套最優的w引數呢?

基本思路:代入”先驗資料“來逆推求解,但針對不等式求解引數極其困難,通用的解決方法,將對不等式的求解做一個轉換:a.將”樣本y(x) - y(擬合)“的差值壓縮到一個0~1的小區間;b.然後代入大量的樣本特徵值,從而得到一系列的輸出結果;c.再將這些輸出結果跟樣本的先驗類別比較,並根據比較情況來調整擬合線的引數值,從而是擬合線的引數逼近最優。從而將問題轉化為逼近求解的典型數學問題。

 3.2.2 sigmoid函式

上述演算法思路中,通常使用sigmoid函式作為轉換函式

  • 函式表示式:

          ,注:此處的x是向量

  • 函式曲線:

    

 

       之所以使用sigmoid函式,就是讓樣板點經過運算後得到的結果限制在0~1之間,壓縮資料的巨幅震盪,從而方便得到樣本點的分類標籤(分類以sigmoid函式的計算結果是否大於0.5為依據)

3.3 演算法實現分析

1.3.1 實現思路

  • 演算法思想的數學表述

把資料集的特徵值設為x1,x2,x3......,求出它們的迴歸係數wj,設z=w1 * x1 + w2 * x2......,然後將z值代入sigmoid函式並判斷結果,即可得到分類標籤

 

問題在於如何得到一組合適的引數wj

通過解析的途徑很難求解,而通過迭代的方法可以比較便捷地找到最優解。簡單來說,就是不斷用樣本特徵值代入算式,計算出結果後跟其實際標籤進行比較,根據差值來修正引數,然後再代入新的樣本值計算,迴圈往復,直到無需修正或已到達預設的迭代次數。

注:此過程用梯度上升來實現。

 1.3.2 梯度上升演算法

梯度上升是指找到函式增長的方向。在具體實現的過程中,不停地迭代運算直到w的值幾乎不再變化為止。

如圖所示:

3.4 Lineage邏輯迴歸分類Python實戰

3.4.1 需求

對給定的先驗資料集,使用logistic迴歸演算法對新資料分類

3.4.2 python實現

3.4.2.1 定義sigmoid函式

複製程式碼
def loadDataSet():
       dataMat = []; labelMat = []
        fr = open('d:/testSet.txt')
        for line in fr.readlines():
               lineArr = line.strip().split()
               dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
               labelMat.append(int(lineArr[2]))
         return dataMat, labelMat

def sigmoid(inX):
      return 1.0/(1+exp(-inX))
複製程式碼

3.4.2.2 返回迴歸係數

對應於每個特徵值,for迴圈實現了遞迴梯度上升演算法。

複製程式碼
def gradAscent(dataMatln, classLabels):
      dataMatrix = mat(dataMatln)  # 將先驗資料集轉換為NumPy矩陣
      labelMat = mat(classLabels).transpose() #將先驗資料的類標籤轉換為NumPy矩陣
   
      m,n = shape(dataMatrix)
      alpha = 0.001     #設定逼近步長調整係數
      maxCycles = 500  #設定最大迭代次數為500
      weights = ones((n,1)) #weights即為需要迭代求解的引數向量

      for k in range(maxCycles): #heavy on matrix operations
            h = sigmoid(dataMatrix * weights) #代入樣本向量求得“樣本y” sigmoid轉換值
            error = (labelMat - h)    #求差
            weights = weights + alpha * dataMatrix.transpose() * error #根據差值調整引數向量
      return weights
複製程式碼

我們的資料集有兩個特徵值分別是x1,x2。在程式碼中又增設了x0變數。

結果,返回了特徵值的迴歸係數:

[[4.12414349]

 [0.48007329]

 [-0.6168482]]

我們得出x1和x2的關係(設x0 = 1),0=4.12414349+0.48007329*x1 - 0.6168482*x2

3.4.2.3 線性擬合線

畫出x1與x2的關係圖——線性擬合線

4.決策樹(Decision Tree)分類演算法原理及應用

4.1 概述

決策樹——是一種被廣泛使用的分類演算法。相比貝葉斯演算法,決策樹的優勢在於構造過程不需要任何領域知識或引數設定。在實際應用中,對於探測式的知識發現,決策樹更加適用

決策樹通常有三個步驟:特徵選擇、決策樹的生成、決策樹的修剪。

4.2 演算法思想

通俗來說,決策樹分類的思想類似於找物件。現想象一個女孩的母親要給這個女孩介紹男朋友,於是有了下面的對話:

  女兒:多大年紀了?
  母親:26
  女兒:長的帥不帥?
  母親:挺帥的。
  女兒:收入高不?
  母親:不算很高,中等情況。
  女兒:是公務員嗎?
  母親:是,公務員,在稅務局上班呢。
  女兒:那好,我去見見。

這個女孩的決策過程就是典型的分類樹決策。實質:通過年齡、長相、收入和是否公務員將男人分為兩個類別:見和不見

假設這個女孩對男人的要求是:30歲以下、長相中等以上並且是高收入者或中等以上收入的公務員,那麼這個可以用下圖表示女孩的決策邏輯。

上圖完整表達了這個女孩決定是否見一個約會物件的策略,其中:

  • 綠色節點表示判斷條件
  • 橙色節點表示決策結果
  • 箭頭表示在一個判斷條件在不同情況下的決策路徑

圖中紅色箭頭表示了上面例子中女孩的決策過程。這幅圖基本可以算是一顆決策樹,說它”基本可以算“是因為圖中的判定條件沒有量化,如收入高中低等等,還不能算是嚴格意義上的決策樹,如果將所有條件量化,則就變成真正的決策樹了。

決策樹分類演算法的關鍵就是根據”先驗資料“構造一棵最佳的決策樹,用以預測未知資料的類別

決策樹:是一個樹結構(可以是二叉樹或非二叉樹)。其每個非葉節點表示一個特徵屬性上的測試,每個分支代表這個特徵屬性在某個值域上的輸出,而每個葉節點存放一個類別。使用決策樹進行決策的過程就是從根節點開始,測試待分類項中相應的特徵屬性,並按照其值選擇輸出分支,直到到達葉子節點,將葉子節點存放的類別作為決策結果。

4.3 決策樹構造

4.3.1 決策樹構造樣例

假如有以下判斷蘋果好壞的資料樣本:

樣本    紅     大      好蘋果

0       1      1         1

1       1      0         1

2       0      1         0

3       0      0         0

樣本中有2個屬性,A0表示是否紅蘋果。A1表示是否大於蘋果。假如要根據這個資料樣本構建一棵自動判斷蘋果好壞的決策樹。由於本例中的資料只有2個屬性,因此,我們可以窮舉所有可能構造出來的決策樹,就2課樹,如下圖所示:

顯然左邊先使用A0(紅色)做劃分依據的決策樹要優於右邊用A1(大小)做劃分依據的決策樹。當然這是直覺的認知。而直覺顯然不適合轉化成程式的實現,所以需要有一種定量的考察來評價這兩棵樹的效能好壞。

決策樹的評價所用的定量考察方法為計算每種劃分情況的資訊熵增益:如果經過某個選定的屬性進行資料劃分後的資訊熵下降最多,則這個劃分屬性是最優選擇。

4.3.2 屬性劃分選擇(即構造決策樹)的依據

熵:資訊理論的奠基人夏農定義的用來資訊量的單位。簡單來說,熵就是“無序,混亂”的程度。

公式:H(X)=- Σ pi * logpi, i=1,2, ... , n,pi為一個特徵的概率

通過計算來理解:

1、原始樣本資料的熵:

樣例總數:4
好蘋果:2
壞蘋果:2

 熵:-(1/2 * log(1/2) + 1/2 * log(1/2)) =1

資訊熵為1表示當前處於最混亂,最無序的狀態

2、兩顆決策樹的劃分結果熵增益計算

  • 樹1先選A0作劃分,各子節點資訊熵計算如下:

          0,1葉子節點有2個正例,0個負例。資訊熵為:e1 = -(2/2 * log(2/2) + 0/2 * log(0/2)) =0。

          2,3葉子節點有0個正例,2個負例。資訊熵為:e2 = -(0/2 * log(0/2) + 2/2 * log(2/2)) =0。

          因此選擇A0劃分後的資訊熵為每個子節點的資訊熵所佔比重的加權和:E = e1 * 2/4 + e2 * 2/4 = 0。

          選擇A0做劃分的資訊熵增益G(S,A0) = S - E = 1 - 0 =1。

          事實上,決策樹葉子節點表示已經都屬於相同類別,因此資訊熵一定為0。

  • 樹2先選A1作劃分,各子節點資訊熵計算如下:

          0,2子節點有1個正例,1個負例。資訊熵為:e1 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。

          1,3子節點有1個正例,1個負例。資訊熵為:e2 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。

          因此選擇A1劃分後的資訊熵為每個子節點的資訊熵所佔比重的加權和:E = e1 * 2/4 + e2 * 2/4 = 1。也就是說分了跟沒分一樣!

           選擇A1做劃分的資訊熵增益G(S,A1) = S - E = 1 - 1 = 0。

           因此,每次劃分之前,我們只需要計算出資訊熵增益最大的那種劃分即可。

4.4 演算法要點

4.4.1 指導思想

經過決策屬性的劃分後,資料的無序度越來越低,也就是資訊熵越來越小

4.4.2 演算法實現

梳理出資料中的屬性,比較按照某特定屬性劃分後的資料的資訊熵增益,選擇資訊熵增益最大的那個屬性作為第一劃分依據,然後繼續選擇第二屬性,以此類推。

4.5 決策樹分類演算法Python實戰

4.5.1 案例需求

我們的任務就是訓練一個決策樹分類器,輸入身高和體重,分類器能給出這個人是胖子還是瘦子。

所用的訓練資料如下,這個資料一共有8個樣本,每個樣本有2個屬性,分別為頭髮和聲音,第三列為性別標籤,表示“男”或“女”。該資料儲存在1.txt中。

頭髮 聲音 性別

 

4.5.2 模型分析

決策樹對於“是非”的二值邏輯的分枝相當自然。

本例決策樹的任務是找到頭髮、聲音將其樣本兩兩分類,自頂向下構建決策樹。

在這裡,我們列出兩種方案:

①先根據頭髮判斷,若判斷不出,再根據聲音判斷,於是畫了一幅圖,如下:

 

於是,一個簡單、直觀的決策樹就這麼出來了。頭髮長、聲音粗就是男生;頭髮長、聲音細就是女生;頭髮短、聲音粗是男生;頭髮短、聲音細是女生。

② 先根據聲音判斷,然後再根據頭髮來判斷,決策樹如下:

那麼問題來了:方案①和方案②哪個的決策樹好些?計算機做決策樹的時候,面對多個特徵,該如何選哪個特徵為最佳多得劃分特徵?

劃分資料集的大原則是:將無序的資料變得更加有序。

我們可以使用多種方法劃分資料集,但是每種方法都有各自的優缺點。於是我們這麼想,如果我們能測量資料的複雜度,對比按不同特徵分類後的資料複雜度,若按某一特徵分類後複雜度減少的更多,那麼這個特徵即為最佳分類特徵。為此,Claude Shannon定義了熵和資訊增益,用熵來表示資訊的複雜度,熵越大,則資訊越複雜。資訊增益表示兩個資訊熵的差值。

首先計算未分類前的熵,總共有8位同學,男生3位,女生5位

熵(總)= -3/8*log2(3/8)-5/8*log2(5/8)=0.9544

接著分別計算方案①和方案②分類後資訊熵。

方案①首先按頭髮分類,分類後的結果為:長頭髮中有1男3女。短頭髮中有2男2女。

熵(長髮)= -1/4*log2(1/4)-3/4*log2(3/4)=0.8113

熵(短髮)= -2/4*log2(2/4)-2/4*log2(2/4)=1 

熵(方案①)= 4/8*0.8113+4/8*1=0.9057 (4/8為長頭髮有4人,短頭髮有4人)

資訊增益(方案①)= 熵(總)- 熵(方案①)= 0.9544 - 0.9057 = 0.0487

同理,按方案②的方法,首先按聲音特徵來分,分類後的結果為:聲音粗中有3男3女。聲音細中有0男2女。

熵(聲音粗)= -3/6*log2(3/6)-3/6*log2(3/6)=1 

熵(聲音細)= -2/2*log2(2/2)=0

熵(方案②)= 6/8*1+2/8*0=0.75 (6/8為聲音粗有6人,2/8為聲音細有2人)

資訊增益(方案②)= 熵(總)- 熵(方案②)= 0.9544 - 0.75 = 0.2087

按照方案②的方法,先按聲音特徵分類,資訊增益更大,區分樣本的能力更強,更具有代表性。

以上就是決策樹ID3演算法的核心思想。

4.5.3 python實現ID3演算法

 

複製程式碼
from math import log
import operator

def calcShannonEnt(dataSet):  # 計算資料的熵(entropy)
    numEntries=len(dataSet)  # 資料條數
    labelCounts={}
    for featVec in dataSet:
        currentLabel=featVec[-1] # 每行資料的最後一個字(類別)
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1  # 統計有多少個類以及每個類的數量
    shannonEnt=0
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries # 計算單個類的熵值
        shannonEnt-=prob*log(prob,2) # 累加每個類的熵值
    return shannonEnt

def createDataSet1(): # 創造示例資料
    dataSet = [['長', '粗', '男'],
               ['短', '粗', '男'],
               ['短', '粗', '男'],
               ['長', '細', '女'],
               ['短', '細', '女'],
               ['短', '粗', '女'],
               ['長', '粗', '女'],
               ['長', '粗', '女']]
    labels = ['頭髮', '聲音'] # 兩個特徵
    return dataSet, labels

def splitDataSet(dataSet, axis, value): # 按某個特徵分類後的資料
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

def chooseBestFeatureToSplit(dataSet): # 選擇最優的分類特徵
    numFeatures = len(dataSet[0]) - 1
    print(numFeatures)
    baseEntropy = calcShannonEnt(dataSet) # 原始的熵
    bestInfoGain = 0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntropy = 0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet) # 按特徵分類後的熵
        infoGain = baseEntropy - newEntropy # 原始熵與按特徵分類後的熵的差值
        if (infoGain > bestInfoGain): # 若按某特徵劃分後,熵值減少的最大,則次特徵為最優分類特徵
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature

def majorityCnt(classList): # 按分類後類別數量排序,比如:最後分類為2男1女,則判定為男:
    classCount={}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote]=0
        classCount[vote]+=1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet] # 類別:男或女
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet) # 選擇最優特徵
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel:{}} # 分類結果以字典形式儲存
    del(labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    #print(featValues)
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
    return myTree

if __name__ == '__main__':
    dataSet, labels = createDataSet1() # 創造示例資料
    print(createTree(dataSet, labels)) # 輸出決策樹模型結果
複製程式碼

這時候會輸出

{'聲音': {'細': '女', '粗': {'頭髮': {'長': '女', '短': '男'}}}}

4.5.4 決策樹的儲存

一棵決策樹的學習訓練是非常耗費運算時間的,因此,決策樹訓練出來後,可進行儲存,以便在預測新的資料時只需要直接載入訓練好的決策樹即可

本案例的程式碼中已經把決策樹的結構寫入了tree.dot中。開啟該檔案,很容易畫出決策樹,還可以看到決策樹的更多分類資訊。

本例的tree.dot如下所示:

複製程式碼
digraph Tree {
0 [label="X[1] <= 55.0000\nentropy = 0.954434002925\nsamples = 8", shape="box"] ;
1 [label="entropy = 0.0000\nsamples = 2\nvalue = [ 2.  0.]", shape="box"] ;
0 -> 1 ;
2 [label="X[1] <= 70.0000\nentropy = 0.650022421648\nsamples = 6", shape="box"] ;
0 -> 2 ;
3 [label="X[0] <= 1.6500\nentropy = 0.918295834054\nsamples = 3", shape="box"] ;
2 -> 3 ;
4 [label="entropy = 0.0000\nsamples = 2\nvalue = [ 0.  2.]", shape="box"] ;
3 -> 4 ;
5 [label="entropy = 0.0000\nsamples = 1\nvalue = [ 1.  0.]", shape="box"] ;
3 -> 5 ;
6 [label="entropy = 0.0000\nsamples = 3\nvalue = [ 0.  3.]", shape="box"] ;
2 -> 6 ;
}
複製程式碼

根據這個資訊,決策樹應該長的如下這個樣子:

 參考資料:

 https://blog.csdn.net/csqazwsxedc/article/details/65697652