k-近鄰算法(改進約會網站的配對效果)

示例背景:

我的朋友海倫一直使用在線約會網站尋找合適自己的約會對象。儘管約會網站會推薦不同的人選,但她並不是喜歡每一個人。經過一番總結,她發現曾交往過三種類型的人:

(1)不喜歡的人;

(2)魅力一般的人;

(3)極具魅力的人;

儘管發現了上述規律,但海倫依然無法將約會網站推薦的匹配對象歸入恰當的分類,她覺得可以在週一到週五約會那些魅力一般的人,而週末則更喜歡與那些極具魅力的人爲伴。海倫希望我們的分類軟件可以更好地幫助她將匹配對象劃分到確切的分類中。此外,海倫還收集了一些約會網站未曾記錄的數據信息,她認爲這些數據更助於匹配對象的歸類。

 

準備數據:從文本文件中解析數據

海倫收集約會數據已經有了一段時間,她把這些數據存放在文本文件datingTestSet.txt中,每個樣本數據佔據一行,總共有1000行。海倫的樣本主要包括以下3種特徵:

1.每年獲得的飛行常客里程數;

2.玩視頻遊戲所耗時間百分比;

3.每週消費的冰淇淋公升數;

在將上述特徵數據輸入到分類器之前,必須將待處理數據的格式改變爲分類器可以接受的格式。在kNN.py中創建名爲file2matrix的函數,以此來處理輸入格式問題。該函數的輸入爲文本文件名字符串,輸出爲訓練樣本矩陣和類標籤向量。

分析數據:使用Matplotlib創建散點圖

import matplotlib as mpl
import matplotlib.pyplot as plt
import operator


def file2matrix(filename):  #獲取數據
    f = open(filename)
    arrayOLines = f.readlines()
    numberOfLines = len(arrayOLines)
    returnMat = zeros((numberOfLines,3),dtype=float)
    #zeros(shape, dtype, order),創建一個shape大小的全爲0矩陣,dtype是數據類型,默認爲float,
       #order表示在內存中排列的方式(以C語言或Fortran語言方式排列),默認爲C語言排列
    classLabelVector = []
    rowIndex = 0
    for line in arrayOLines:
        line = line.strip()
        listFormLine = line.split('\t')
        returnMat[rowIndex,:] = listFormLine[0:3]
        classLabelVector.append(int(listFormLine[-1]))
        rowIndex += 1
    return returnMat, classLabelVector


if __name__ == "__main__":
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    fig = plt.figure()  #圖
    mpl.rcParams['font.sans-serif'] = ['KaiTi']
    mpl.rcParams['font.serif'] = ['KaiTi']
    plt.xlabel('玩視頻遊戲所耗時間百分比')
    plt.ylabel('每週消費的冰淇淋公升數')
    '''
    matplotlib.pyplot.ylabel(s, *args, **kwargs)

    override = {
       'fontsize'            : 'small',
       'verticalalignment'   : 'center',
       'horizontalalignment' : 'right',
       'rotation'='vertical' : }
    '''
    ax = fig.add_subplot(111) #將圖分成1行1列,當前座標系位於第1塊處(這裏總共也就1塊)
    ax.scatter(datingDataMat[: ,1], datingDataMat[: ,2],15.0*array(datingLabels), 15.0*array(datingLabels))
    #scatter是用來畫散點圖的
    # scatter(x,y,s=1,c="g",marker="s",linewidths=0)
    # s:散列點的大小,c:散列點的顏色,marker:形狀,linewidths:邊框寬度
    plt.show()

 

這是簡單的創建了一下散點圖,可以看到上面的圖中還缺少了圖例,所以下面的代碼以另兩列數據爲例創建了帶圖例的散點圖,代碼大致還是一樣的:

from numpy import *
import matplotlib as mpl
import matplotlib.pyplot as plt
import operator

def file2matrix(filename):  #獲取數據
    f = open(filename)
    arrayOLines = f.readlines()
    numberOfLines = len(arrayOLines)
    returnMat = zeros((numberOfLines,3),dtype=float)
    #zeros(shape, dtype, order),創建一個shape大小的全爲0矩陣,dtype是數據類型,默認爲float,order表示在內存中排列的方式(以C語言或Fortran語言方式排列),默認爲C語言排列
    classLabelVector = []
    rowIndex = 0
    for line in arrayOLines:
        line = line.strip()
        listFormLine = line.split('\t')
        returnMat[rowIndex,:] = listFormLine[0:3]
        classLabelVector.append(int(listFormLine[-1]))
        rowIndex += 1
    return returnMat, classLabelVector

if __name__ == "__main__":
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    fig = plt.figure()  #圖
    plt.title('散點分析圖')
    mpl.rcParams['font.sans-serif'] = ['KaiTi']
    mpl.rcParams['font.serif'] = ['KaiTi']
    plt.xlabel('每年獲取的飛行常客里程數')
    plt.ylabel('玩視頻遊戲所耗時間百分比')
    '''
    matplotlib.pyplot.ylabel(s, *args, **kwargs)

    override = {
       'fontsize'            : 'small',
       'verticalalignment'   : 'center',
       'horizontalalignment' : 'right',
       'rotation'='vertical' : }
    '''

    type1_x = []
    type1_y = []
    type2_x = []
    type2_y = []
    type3_x = []
    type3_y = []
    ax = fig.add_subplot(111) #將圖分成1行1列,當前座標系位於第1塊處(這裏總共也就1塊)

    index = 0
    for label in datingLabels:
        if label == 1:
            type1_x.append(datingDataMat[index][0])
            type1_y.append(datingDataMat[index][1])
        elif label == 2:
            type2_x.append(datingDataMat[index][0])
            type2_y.append(datingDataMat[index][1])
        elif label == 3:
            type3_x.append(datingDataMat[index][0])
            type3_y.append(datingDataMat[index][1])
        index += 1

    type1 = ax.scatter(type1_x, type1_y, s=30, c='b')
    type2 = ax.scatter(type2_x, type2_y, s=40, c='r')
    type3 = ax.scatter(type3_x, type3_y, s=50, c='y', marker=(3,1))

    '''
     scatter是用來畫散點圖的
     matplotlib.pyplot.scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None,**kwargs)
     其中,xy是點的座標,s點的大小
     maker是形狀可以maker=(5,1)5表示形狀是5邊型,1表示是星型(0表示多邊形,2放射型,3圓形)
     alpha表示透明度;facecolor=‘none’表示不填充。
    '''
    
    ax.legend((type1, type2, type3), ('不喜歡', '魅力一般', '極具魅力'), loc=0)
    '''
    loc(設置圖例顯示的位置)
    'best'         : 0, (only implemented for axes legends)(自適應方式)
    'upper right'  : 1,
    'upper left'   : 2,
    'lower left'   : 3,
    'lower right'  : 4,
    'right'        : 5,
    'center left'  : 6,
    'center right' : 7,
    'lower center' : 8,
    'upper center' : 9,
    'center'       : 10,
    '''
    plt.show()

 

準備數據:歸一化數值

當我們計算樣本之間的歐幾里得距離時,由於有些數值較大,所以它對結果整體的影響也就越大,那麼小數據的可能就毫無影響了。在這個例子中飛行常客里程數很大,然而其餘兩列數據很小。爲了解決這個問題,需要把數據相應的進行比例兌換,也就是這裏需要做的歸一化數值,將所有數值轉化爲[0,1]之間的值。

公式爲:
newValue=(oldValue−min)/(max−min)newValue=(oldValue−min)/(max−min)   (minmin和maxmax分別是數據集中的最小特徵值和最大特徵值)

def autoNorm(dataSet): #歸一化數值
    minVals = dataSet.min(0)  #0表示每列的最小值,1表示每行的最小值,以一維矩陣形式返回
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1))
    normDataSet = normDataSet/tile(ranges, (m,1))
    return normDataSet, ranges, minVals

測試並構造完整算法

根據這1000個數據,將其中的100個作爲測試數據,另900個作爲訓練集,看着100個數據集的正確率。

最後根據自己輸入的測試數據來判斷應該出現的結果是什麼。

from numpy import *
import matplotlib as mpl
import matplotlib.pyplot as plt
import operator

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize,1)) - dataSet  #統一矩陣,實現加減
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)  #進行累加,axis=0是按列,axis=1是按行
    distances = sqDistances**0.5  #開根號
    sortedDistIndicies = distances.argsort()  #按升序進行排序,返回原下標
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  #get是字典中的方法,前面是要獲得的值,後面是若該值不存在時的默認值
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


def file2matrix(filename):  #獲取數據
    f = open(filename)
    arrayOLines = f.readlines()
    numberOfLines = len(arrayOLines)
    returnMat = zeros((numberOfLines,3),dtype=float)
    #zeros(shape, dtype, order),創建一個shape大小的全爲0矩陣,dtype是數據類型,默認爲float,order表示在內存中排列的方式(以C語言或Fortran語言方式排列),默認爲C語言排列
    classLabelVector = []
    rowIndex = 0
    for line in arrayOLines:
        line = line.strip()
        listFormLine = line.split('\t')
        returnMat[rowIndex,:] = listFormLine[0:3]
        classLabelVector.append(int(listFormLine[-1]))
        rowIndex += 1
    return returnMat, classLabelVector


def autoNorm(dataSet): #歸一化數值
    minVals = dataSet.min(0)  #0表示每列的最小值,1表示每行的最小值,以一維矩陣形式返回
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1))
    normDataSet = normDataSet/tile(ranges, (m,1))
    return normDataSet, ranges, minVals


def datingClassTest(datingDataMat, datingLabels):  #測試正確率
    hoRatio = 0.1
    m = datingDataMat.shape[0]
    numTestVecs = int(hoRatio*m)
    numError = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(datingDataMat[i,:], datingDataMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        print('The classifier came back with: %d, the real answer is: %d.' %(classifierResult, datingLabels[i]))
        if (classifierResult != datingLabels[i]):
            numError += 1
    print('錯誤率爲 %f' %(numError/float(numTestVecs)))


def classifyPerson(datingDataMat, datingLabels, ranges, minVals):
    result = ['not at all', 'in small doses', 'in large doses']
    print('請輸入相應信息:')
    percentTats = float(input('percentage of time spent playing video games?'))
    ffMiles = float(input('frequent flier miles earned per year?'))
    iceCream = float(input('liters of ice cream consumed per year?'))
    inArr = array([ffMiles, percentTats, iceCream])
    classifyResult = classify0((inArr-minVals)/ranges, datingDataMat, datingLabels, 3)
    print('You will probably like this person: ', result[classifyResult-1])


if __name__ == "__main__":
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    datingDataMat, ranges, minVals = autoNorm(datingDataMat) #歸一化數值
    datingClassTest(datingDataMat, datingLabels)
    classifyPerson(datingDataMat, datingLabels, ranges, minVals)
    fig = plt.figure()  #圖
    plt.title('散點分析圖')
    mpl.rcParams['font.sans-serif'] = ['KaiTi']
    mpl.rcParams['font.serif'] = ['KaiTi']
    plt.xlabel('每年獲取的飛行常客里程數')
    plt.ylabel('玩視頻遊戲所耗時間百分比')
    '''
    matplotlib.pyplot.ylabel(s, *args, **kwargs)

    override = {
       'fontsize'            : 'small',
       'verticalalignment'   : 'center',
       'horizontalalignment' : 'right',
       'rotation'='vertical' : }
    '''

    type1_x = []
    type1_y = []
    type2_x = []
    type2_y = []
    type3_x = []
    type3_y = []
    ax = fig.add_subplot(111) #將圖分成1行1列,當前座標系位於第1塊處(這裏總共也就1塊)

    index = 0
    for label in datingLabels:
        if label == 1:
            type1_x.append(datingDataMat[index][0])
            type1_y.append(datingDataMat[index][1])
        elif label == 2:
            type2_x.append(datingDataMat[index][0])
            type2_y.append(datingDataMat[index][1])
        elif label == 3:
            type3_x.append(datingDataMat[index][0])
            type3_y.append(datingDataMat[index][1])
        index += 1

    type1 = ax.scatter(type1_x, type1_y, s=30, c='b')
    type2 = ax.scatter(type2_x, type2_y, s=40, c='r')
    type3 = ax.scatter(type3_x, type3_y, s=50, c='y', marker=(3,1))

    '''
     scatter是用來畫散點圖的
     matplotlib.pyplot.scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None,**kwargs)
     其中,xy是點的座標,s點的大小
     maker是形狀可以maker=(5,1)5表示形狀是5邊型,1表示是星型(0表示多邊形,2放射型,3圓形)
     alpha表示透明度;facecolor=‘none’表示不填充。
    '''

    ax.legend((type1, type2, type3), ('不喜歡', '魅力一般', '極具魅力'), loc=0)
    '''
    loc(設置圖例顯示的位置)
    'best'         : 0, (only implemented for axes legends)(自適應方式)
    'upper right'  : 1,
    'upper left'   : 2,
    'lower left'   : 3,
    'lower right'  : 4,
    'right'        : 5,
    'center left'  : 6,
    'center right' : 7,
    'lower center' : 8,
    'upper center' : 9,
    'center'       : 10,
    '''
    plt.show()

可以看到錯誤率爲5%:

 

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