TI男選隱形眼鏡之機器學習

背景

一天,程序員小東要去約會,但是他覺得戴着眼鏡賊不好看。他感覺梳個油頭,拿下眼鏡去約會會加分很多,但他一隻近視四眼狗沒有眼鏡,連女孩長什麼樣都看不清,很淒涼。於是,他決定要去配一副隱形眼鏡,但配隱形要做一下檢測什麼的吧,所以在去配眼鏡之前他決定自己搞點事情,去測一下自己配什麼類型的隱形眼鏡比較好。

選擇

既然確定目標,就要有所選擇,用什麼方法去實現比較好呢?小東最近在學習機器學習,他決定用機器學習去進行開發。他先在網上找相關的隱形眼鏡的數據集,找到了一個比較合適的數據集 Dataset of contact lenses

  分別有四個特徵量:age, prescript, astigmatic, tearRate
  每一個特徵量的值如下:
  age: young/pre/presbyopic
  prescript: myope/hyper
  astigmatic: yes/no
  tearRate: reduced/normal
  四個特徵量的取值不同對應了三種類別:no lenses, soft, hard

看到這些特徵量都是標稱型數值,他決定採用剛學的ID3算法構建決策樹,進行預測。

開發環境

小東電腦的系統開發環境是anaconda3, 可以在百度上直接搜索下載,裏面包含了很多機器學習所用到的環境和工具,包括python,在這裏說明一下,我用的是python3.6,如果有用python2的小夥伴,可能後面的代碼需要自己改動一下。所以,只要安裝anaconda3,你就可以擁有開發機器學習所需要的準備的東西,簡單方便。

過程

使用ID3算法構建決策樹,主要是採用遞歸的方法,將數據集劃分爲子數據集,直到子數據集都爲同一類別,返回該類別 或者 特徵量都使用完了,從子數據集中選取同一類別最多返回。
如何進行劃分是關鍵。

信息增量

劃分數據集的大原則是:將無序的數據變得更加有序
在劃分數據集之前之後信息發生的變化稱爲信息增量,知道如何計算信息增量,我們就可以計算每個特徵值劃分數據集獲得的信息增量,獲得信息增量最高的特徵就是最好的選擇

香農熵

集合信息的度量方式稱爲香農熵,簡稱爲熵
香農熵,這個概念是克勞德·香農提出來的,據說這個哥們是20世紀最聰明的人之一,大家可以自行了解一下。
這裏直接列出計算香儂熵的公式:
H(x) = -∑p(xi)log(2,p(xi)) (i=1,2,..n)
其中p(xi)是選擇該分類的概率
計算數據集香農熵的代碼如下:
from math import log
import numpy
import operator

'''
函數功能:計算香儂熵
'''
def calcShannonEnt(dataSet):
    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.0 #香儂熵
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries #計算每一種分類出現的概率
        shannonEnt -= prob * log(prob, 2) # 計算熵的公式
    return shannonEnt

根據特徵量劃分數據集

在計算最好劃分數據集的方法之前,要先寫一個根據某一特徵量和特徵值進行數據集劃分的方法,直接附上代碼
'''
函數功能:劃分數據集
'''
def splitDataSet(dataSet, axis, value):
    retDataSet = [] #初始化列表
    for featVec in dataSet:
        if featVec[axis] == value: #比較第axis中的特徵量與value相同與否
            reducedFeatVec = featVec[:axis] #複製featVec[0:axis]
            reducedFeatVec.extend(featVec[axis + 1:]) #將剩餘的的特徵也複製,用extend()方法
            retDataSet.append(reducedFeatVec) #將新列表添加進retDataSet中
    return retDataSet

選擇最好的劃分方式

主要根據每一個特徵量進行劃分後計算得出的香農熵與基本香農熵相見進行比較,看誰得大,就說明信息增量大,劃分最合適
代碼如下
'''
函數功能:選擇最好的劃分方式
'''
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1 #計算特徵量的個數
    baseEntropy = calcShannonEnt(dataSet) #計算整個數據集的基本香儂熵
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]#用列表推導式計算整個數據集第i個特徵量
        uniqueVals = set(featList)#用set生成集合數據,剔除相同值
        newEntropy = 0.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): #返回出現次數最多的分類名稱
    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 classCount[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]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:] #爲了保證每次調用createTree()時不改變原始列表的內容,使用新變量subLabels代替原始列表
        myTree [bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),\
               subLabels)
    return myTree #返回構造的決策樹

存儲決策樹

由於每次決策樹生成都會消耗大量時間,但進行分類計算的時候會很快。一般如果數據集不改變的情況下,我們可以將生成的決策樹通過pickle進行存儲,需要用的時候讀取出來,這樣會節約很多時間。
實現代碼如下:

'''
函數功能:利用pickle來存儲構造好的決策樹
'''
def storeTree(inputTree, filename):
    import pickle
    fw = open(filename, 'wb')
    pickle.dump(inputTree, fw)
    fw.close()
'''
函數功能:獲取構造好的決策樹
'''
def grabTree(filename):
    import pickle
    fr = open(filename, 'rb')
    return pickle.load(fr)

分類器構建

通過分類器,可以將我們輸入的參數得到適合我們自己的隱形眼鏡

'''
函數功能:構造分類器
'''
def classify(inputTree, featLabels, testVec):
    firstStr = list(inputTree.keys())[0] #獲取決策樹,第一個分類
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr) #獲取輸入的的數據的Label偏移量
    for key in secondDict.keys():
        if testVec[featIndex] == key: #讀出輸入數據的值與決策樹對應分類的值進行比較
            if type(secondDict[key]).__name__ == 'dict' :#如果這個值對應的是一個字典,說明還沒找到合適的分類,繼續遞歸去找
                classLabel = classify(secondDict[key], featLabels, testVec)
            else: #如果不是,就說明已經匹配出結果,返回匹配的類型結果
                classLabel = secondDict[key]
    return classLabel

交互方法

爲了更人性化的操作,需要寫一個簡單的交互方法,代碼如下:

'''
函數功能:獲取隱形眼鏡的數據
'''
def GetContactLensesData(filename):
    fr = open(filename)
    lenses = [inst.strip().split('\t') for inst in fr.readlines()]
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
    return lenses, lensesLabels

'''
函數功能:交互方法 
'''
def ChooseContactLenses():
    lenses, lensesLabels = GetContactLensesData(r'D:\Learning\DataSet\lenses.txt')
    labels = lensesLabels.copy() #因爲生成決策樹時,修改了labels,我們需要拷貝一份
    lensesTree =  createTree(lenses, labels)

    tearRate = input("How many tears?Options:reduced/normal\n")
    astigmatic = input("Does the eye have astigmatic? Options: yes/no\n")
    prescript = input("Are you myope or hyper? Options: myope/hyper\n")
    age = input("How many years you? Options:pre/presbyopic/young\n")
    personList = [age, prescript, astigmatic, tearRate]

    classLabel = classify(lensesTree, lensesLabels, personList)
    print("you should choose the contact lenses:%s" %classLabel)

附簡單數據集

young   myope   no  reduced no lenses
young   myope   no  normal  soft
young   myope   yes reduced no lenses
young   myope   yes normal  hard
young   hyper   no  reduced no lenses
young   hyper   no  normal  soft
young   hyper   yes reduced no lenses
young   hyper   yes normal  hard
pre myope   no  reduced no lenses
pre myope   no  normal  soft
pre myope   yes reduced no lenses
pre myope   yes normal  hard
pre hyper   no  reduced no lenses
pre hyper   no  normal  soft
pre hyper   yes reduced no lenses
pre hyper   yes normal  no lenses
presbyopic  myope   no  reduced no lenses
presbyopic  myope   no  normal  no lenses
presbyopic  myope   yes reduced no lenses
presbyopic  myope   yes normal  hard
presbyopic  hyper   no  reduced no lenses
presbyopic  hyper   no  normal  soft
presbyopic  hyper   yes reduced no lenses
presbyopic  hyper   yes normal  no lenses

大家也可以去找其他的資源。

總結

這是小東自己做的一個簡單的預測器,由於水平不足,很多內容都是一筆帶過,具體的含義大家閱讀相關書籍進行學習,小東這就可以去配隱形眼鏡了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章