海倫去約會——kNN算法

下午於屋中閒居,因而翻開《機器學習實戰》一書看了看「k-鄰近算法」的內容,並學習了一位很厲害的博主Jack Cui的代碼,本身照着碼了一遍。在此感謝博主Jack Cui的知識分享。html

1、k-鄰近算法簡介


k-鄰近算法做爲最簡單的機器學習算法之一,其原理也淺顯易懂,即:若是一個樣本在特徵空間中的k個最類似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。python

事實上,k-鄰近算法並無進行數據的訓練,而是直接將未知數據與已知數據進行比較的。所以,k-鄰近算法不具備顯式的學習過程。 git

2、算法實現


2.1 算法模板

首先給出書中的基礎樣例代碼:github

『python』算法

# -*- coding: UTF-8 -*-
import numpy as np
import operator

def createDataSet():
    #一組2維特徵
    group = np.array([[1,101], [5,89], [108,5], [115,8]])
    #對應的標籤
    labels = ['愛情片','愛情片','動做片','動做片']
    return group, labels

def classify0(inX, dataSet, labels, k):
    #numpy函數shape[0]返回dataSet的行數
    dataSetSize = dataSet.shape[0]
    #在列方向上重複inX共1次(橫向),行方向上重複inX共dataSetSize次(縱向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #二維特徵相減後平方
    sqDiffMat = diffMat**2
    #sum()全部元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #開方,計算出平均距離
    distances = sqDistances**0.5
    #返回distances中元素從小到大排序後的索引值
    sortedDisIndices = distances.argsort()
    #記錄類別次數的字典
    classCount = {}
    for i in range(k):
        #取出前k個元素的類別
        voteIlabel = labels[sortedDisIndices[i]]
        #dict,get(key, default=None),字典的get()方法,返回指定鍵的值,若是值不在字典返回默認值
        #計算類別次數
        classCount[voteIlabel]=classCount.get(voteIlabel, 0) + 1
    #python3中items()替換python2中的iteritems()
    #key=operator.itemgetter(1)根據字典的值進行排序
    #key=operator.itemgetter(0)根據字典的鍵進行排序
    #reverse降序排序字典
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    #返回次數最多的類別,即所要分類的類別
    return sortedClassCount[0][0]


if __name__== '__main__':
    #建立數據集
    group,labels=createDataSet()
    #測試集
    test = [101,20]
    #kNN分類
    test_class = classify0(test, group, labels, 3)
    #打印分類結果
    print(test_class)

運行結果:windows

 2.2 項目實戰

背景

海倫女士一直使用在線約會網站尋找適合本身的約會對象。儘管約會網站會推薦不一樣的人選,但她並非喜歡每個人。通過一番總結,她發現本身交往過的人能夠進行以下分類:數組

  1. 不喜歡的人
  2. 魅力通常的人
  3. 極具魅力的人

海倫收集約會數據已經有了一段時間,她把這些數據存放在文本文件datingTestSet.txt中,每一個樣本數據佔據一行,總共有1000行。(下載數據集app

海倫收集的樣本數據主要包含如下3種特徵:機器學習

  1. 每一年得到的飛行常客里程數
  2. 玩視頻遊戲所消耗時間百分比
  3. 每週消費的冰淇淋公升數

解析數據

在將上述特徵數據輸入到分類器前,必須將待處理的數據的格式改變爲分類器能夠接收的格式,即特徵矩陣和對應的分類標籤向量。標籤被設置爲:函數

  • 1表示不喜歡的人(didntLike)
  • 2表示魅力通常的人(smallDoses)
  • 3表示極具魅力的人(largeDoses)

『python』

import numpy as np

def file2matrix(filename):
    #打開文件
    fr = open(filename)
    #讀取文件全部內容
    arrayOLines = fr.readlines()
    #獲得文件行數
    numberOfLines = len(arrayOLines)
    #返回NumPy矩陣,解析完成的數據:numberOfLines行,3列
    returnMat = np.zeros((numberOfLines,3))
    #返回的分類標籤向量
    classLabelVector = []
    #行的索引值
    index = 0
    for line in arrayOLines:
        #s.strip(rm),當rm爲空時,默認刪除空白符(包括'\n','\r','\t',' ')
        line = line.strip()
        #使用s.split(str="", num=string, cout(str))將字符根據'\t'分隔進行切片
        listFormLine = line.split('\t')
        #將數據的前三列提取出來,存放在returnMat的NumPy矩陣當中,也就是特徵矩陣
        returnMat[index,:] = listFormLine[0:3]
        #根據文本中標記的喜歡的程度進行分類,1表明不喜歡,2表明魅力通常,3表明頗有魅力
        if listFormLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFormLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFormLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector

if __name__ == '__main__':
    #打開文件名
    filename = "datingTestSet.txt"
    #打開並處理數據
    datingDataMat, datingLabels = file2matrix(filename)
    print(datingDataMat)
    print(datingLabels)

『運行結果』

數據可視化

編寫showdata函數,用於將數據可視化:

『python』

def showdata(datingDataMat, datingLabels):
    # 設置漢字格式
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
    # 將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小爲(13,8)
    # 當nrow=2,nclos=2時,表明fig畫布被分爲四個區域,axs[0][0]表示第一行第一個區域
    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))

    numberOfLabels = len(datingLabels)
    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')
    # 畫出散點圖,以datingDataMat矩陣的第一列(飛行常客例程)、第二列(玩遊戲)數據畫散點數據,散點大小爲15,透明度爲0.5
    axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
    # 設置標題,x軸label,y軸label
    axs0_title_text = axs[0][0].set_title(u'每一年得到的飛行常客里程數與玩視頻遊戲所消耗時間佔比', FontProperties=font)
    axs0_xlabel_text = axs[0][0].set_xlabel(u'每一年得到的飛行常客里程數', FontProperties=font)
    axs0_ylabel_text = axs[0][0].set_ylabel(u'玩視頻遊戲所消耗時間佔比', FontProperties=font)
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')

    # 畫出散點圖,以datingDataMat矩陣的第一(飛行常客例程)、第三列(冰激凌)數據畫散點數據,散點大小爲15,透明度爲0.5
    axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 設置標題,x軸label,y軸label
    axs1_title_text = axs[0][1].set_title(u'每一年得到的飛行常客里程數與每週消費的冰激淋公升數', FontProperties=font)
    axs1_xlabel_text = axs[0][1].set_xlabel(u'每一年得到的飛行常客里程數', FontProperties=font)
    axs1_ylabel_text = axs[0][1].set_ylabel(u'每週消費的冰激淋公升數', FontProperties=font)
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

    # 畫出散點圖,以datingDataMat矩陣的第二(玩遊戲)、第三列(冰激凌)數據畫散點數據,散點大小爲15,透明度爲0.5
    axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 設置標題,x軸label,y軸label
    axs2_title_text = axs[1][0].set_title(u'玩視頻遊戲所消耗時間佔比與每週消費的冰激淋公升數', FontProperties=font)
    axs2_xlabel_text = axs[1][0].set_xlabel(u'玩視頻遊戲所消耗時間佔比', FontProperties=font)
    axs2_ylabel_text = axs[1][0].set_ylabel(u'每週消費的冰激淋公升數', FontProperties=font)
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
    # 設置圖例
    didntLike = mlines.Line2D([], [], color='black', marker='.',
                              markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.',
                               markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.',
                               markersize=6, label='largeDoses')
    # 添加圖例
    axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
    # 顯示圖片
    plt.show()

『運行結果』

經過將數據可視化,能夠獲得一些比較直觀的信息。好比:每一年的飛行常客里程數代表海倫喜歡愛旅遊、懂得生活的男性,但也不能疲於奔波、忙於公務出差而無暇顧家。又好比:玩視頻遊戲時間佔比代表海倫喜歡會打遊戲的男性(可能由於會打遊戲的男性每每比較聰明,或者海倫本身就喜歡遊戲,期待男朋友帶一帶本身,等等),但若是花費太多時間沉迷於遊戲則也會讓海倫感到厭煩。

數據歸一化

能夠採用歐式距離來計算樣本間的距離。但這會帶來一個問題,即數字差值大的樣本屬性對結果的影響最大;若是認爲這三項屬性應當是同等重要的,就要對數據進行歸一化,e.g.採用公式newValue = (oldValue - min)/(max - min)將取值範圍限定在0到1之間。基於此,編寫autoNorm函數將數據歸一化:

『python』

def autoNorm(dataSet):
    #得到數據的最小值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    #最大值和最小值的範圍
    ranges = maxVals - minVals
    #shape(dataSet)返回dataSet的矩陣行列數
    normDataSet = np.zeros(np.shape(dataSet))
    #返回dataSet的行數
    m = dataSet.shape[0]
    #原始值減去最小值
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    #除以最大和最小值的差,獲得歸一化數據
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    #返回歸一化數據結果,數據範圍,最小值
    return normDataSet, ranges, minVals

『運行結果』

將數據歸一化獲得結果以下:

測試算法

分類器的結果並非百分之百正確的,而其性能也會收到如分類器設置、測試數據集等諸多因素的影響。爲測試分類器的效果,咱們使用已知標籤的數據,檢測分類器是否能給出正確結果。經過大量的數據測試,最終估測出分類器的錯誤率。

爲評估算法的正確率,一般只提供已有數據的90%做爲訓練樣原本訓練分類器,而使用其他的10%數據去測試分類器,檢測分類器的正確率。下面編寫datingClassTest函數來測試算法的正確率。

『python』

def datingClassTest():
    #打開的文件名
    filename = "datingTestSet.txt"
    #將返回的特徵矩陣和分類向量分別存儲到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix(filename)
    #取全部數據的百分之十
    hoRatio = 0.10
    #數據歸一化,返回歸一化後的矩陣,數據範圍,數據最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    #得到normMat的行數
    m = normMat.shape[0]
    #百分之十的測試數據的個數
    numTestVecs = int(m * hoRatio)
    #分類錯誤計數
    errorCount = 0.0

    for i in range(numTestVecs):
        #前numTestVecs個數據做爲測試集,後m-numTestVecs個數據做爲訓練集
        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],
            datingLabels[numTestVecs:m], 4)
        print("分類結果:%s\t真實類別:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("錯誤率:%f%%" % (errorCount / float(numTestVecs) * 100))

其中,classify0函數定義以下:

『python』

def classify0(inX, dataSet, labels, k):
    #numpy函數shape[0]返回dataSet的行數
    dataSetSize = dataSet.shape[0]
    #在列向量方向上重複inX共1次(橫向),行向量方向上重複inX共dataSetSize次(縱向)
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    #二維特徵相減後平方
    sqDiffMat = diffMat**2
    #sum()全部元素相加,sum(0)列相加,sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    #開方,計算出距離
    distances = sqDistances**0.5
    #返回distances中元素從小到大排序後的索引值
    sortedDistIndices = distances.argsort()
    #定一個記錄類別次數的字典
    classCount = {}
    for i in range(k):
        #取出前k個元素的類別
        voteIlabel = labels[sortedDistIndices[i]]
        #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,若是值不在字典中返回默認值。
        #計算類別次數
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #python3中用items()替換python2中的iteritems()
    #key=operator.itemgetter(1)根據字典的值進行排序
    #key=operator.itemgetter(0)根據字典的鍵進行排序
    #reverse降序排序字典
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    print(sortedClassCount)
    #返回次數最多的類別,即所要分類的類別
    return sortedClassCount[0][0]

『測試結果』

 

應用算法

編寫函數classifyPerson函數,輸入相關屬性,利用kNN預測海倫是否喜歡這位男性。

『python』

def classifyPerson():
    #輸出結果
    resultList = ['討厭','有些喜歡','很是喜歡']
    #三維特徵用戶輸入
    precentTats = float(input("玩視頻遊戲所耗時間百分比:"))
    ffMiles = float(input("每一年得到的飛行常客里程數:"))
    iceCream = float(input("每週消費的冰激淋公升數:"))
    #打開的文件名
    filename = "datingTestSet.txt"
    #打開並處理數據
    datingDataMat, datingLabels = file2matrix(filename)
    #訓練集歸一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    #生成NumPy數組,測試集
    inArr = np.array([ffMiles, precentTats, iceCream])
    #測試集歸一化
    norminArr = (inArr - minVals) / ranges
    #返回分類結果
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)
    #打印結果
    print("你可能%s這我的" % (resultList[classifierResult-1]))

『運行結果』 

 

參考:

1.[M]Peter Harrington.機器學習實戰.人民郵電出版社

2.https://cuijiahua.com/blog/2017/11/ml_1_knn.html

相關文章
相關標籤/搜索