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

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

oos div nsa 男朋友 efi ted 淘寶 計算機 fine

轉: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

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