k-鄰近演算法實現約會網站的配對效果

娃哈哈店長發表於2020-01-24

k- 近鄰 (k-NearestNeighbor,kNN) 分類演算法是資料探勘分類技術中最簡單的方法之一。K- 近鄰演算法是通過測量不同特徵值之間的距離進行分類的。基本思路是:如果一個樣本在特徵空間中的k 個最鄰近樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。該方法在決定類別上只依據最近的一個或幾個樣本的類別來決定待分類樣本所屬的類別。KNN 演算法中,所選擇的鄰居都是已經正確分類的物件。該方法在分類決策上只依據最鄰近的一個或幾個樣本的類別來決定待分類樣本所屬的類別。

knn演算法詳解:https://boywithacoin.cn/article/fen-lei-su...

案例概要:女主海倫一直使用約會網站找合適自己的約會物件,但是一直沒找到合適的人。通過收集找過的約會物件來構造分類器。

經過一番總結,發信以前交往過三種型別的人:

  • 不喜歡的人-程式碼0
  • 魅力一般的人-程式碼1
  • 極具魅力的人-程式碼3

海倫希望通過分類軟體可以更好地幫助她將匹配物件劃分到確切地分類中

 0x00 收集/準備資料

案例資料收集:每個人收集三個特徵,分別是每年飛行常客的時間(每年乘坐飛機的時間、玩電子遊戲的時間比、每週吃冰淇淋的公升數,如下表所示(我也不知道為啥要分這三類、例題裡這樣寫的)。

序號 飛行公里數   玩遊戲所佔時間百分比 吃冰淇淋公升數    樣本標籤   
   1   40920   8.3    0.9      3
  2   14488    7.1     1.6    2 
  3   26052    1.4    0.8    1 
  4   75136      13.1    0.42   1  

整理測試資料集:

顯示資訊分別為飛行公里數、玩遊戲所佔比例、吃冰淇淋公升數、樣本標籤。

資料集下載地址:https://github.com/Freen247/database/blob/...

40920   8.326976    0.953952    3
14488   7.153469    1.673904    2
26052   1.441871    0.805124    1
75136   13.147394   0.428964    1
38344   1.669788    0.134296    1
72993   10.141740   1.032955    1
35948   6.830792    1.213192    3
42666   13.276369   0.543880    3
67497   8.631577    0.749278    1
.....(共準備1000條測試資料)

 0x01 設計演算法分析資料

通過檔名獲取測試資料集

構造knn演算法類,設計file2matrix方法來通過檔名獲取資料集,分別處理為

  • 二位陣列:儲存前三位屬性的位置分別是飛行公里數、玩遊戲所佔比例、吃冰淇淋公升數
  • 單list:儲存每個樣本屬性後的標籤,是什麼屬性的類別的人

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    #__author__ : stray_camel
    #pip_source : https://mirrors.aliyun.com/pypi/simple
    import sys,os
    import numpy as np 
    import operator # Operator module
    '''
    k-Nearest Neighbor,KNN
    '''
    class KNN_test():
    def __init__(self, 
    absPath:dict(type = str, help="Directory of the current file") = os.path.dirname(os.path.abspath(__file__)),
    ):
        self.absPath = absPath
    
    # Open file by file name to get test data
    def file2matrix(self,
    filename : dict(type = str, help="Relative path and name of the file"),
    )->dict(type=(np.array,list), help=('The two-dimensional array stores the first three digits of the sample data', 'and the list stores the tags of the sample data (type of person)')):
        fr = open(self.absPath+'/'+filename)
        arrayOLines = fr.readlines()
        # get the number of data lines
        numberOfLines = len(arrayOLines)  
        # Create a return matrix
        returnMat = np.zeros((numberOfLines, 3))
        classLabelVector = []
        index = 0
        for line in arrayOLines:
            # Remove whitespace
            line = line.strip()
    
            # split specifies the separator to slice the data
            # '\t' means to jump horizontally to the next tab position '\r' means enter '\n' means carriage return and line feed
            listFromLine = line.split('\t')
    
            # Select the first 3 elements (features) and store them in the returned matrix
            returnMat[index, :] = listFromLine[0:3]
    
            classLabelVector.append(int(listFromLine[-1]))
            # The -1 index indicates the last column of elements, and the bit 'label' information is stored in the 'classLabelVector'
    
            index += 1
        return returnMat, classLabelVector

測試一下程式碼:

if __name__ == "__main__":
    test = KNN_test()
    # print(test.file2matrix.__annotations__)
    print(test.file2matrix('datingTestSet2.txt'))

執行結果:

(array([[4.0920000e+04, 8.3269760e+00, 9.5395200e-01],
       [1.4488000e+04, 7.1534690e+00, 1.6739040e+00],
       [2.6052000e+04, 1.4418710e+00, 8.0512400e-01],
       ...,
       [2.6575000e+04, 1.0650102e+01, 8.6662700e-01],
       [4.8111000e+04, 9.1345280e+00, 7.2804500e-01],
       [4.3757000e+04, 7.8826010e+00, 1.3324460e+00]]), [3, 2, 1, 1,...(共一千條), 3, 3])

4.0920000e+04 表示為 4.0920000*10^4

通過資料集獲取歐氏距離

從樣表中的資料可以得知,如果要計算樣本2和3之間的距離,使用歐氏距離:

image.png

將k-鄰近演算法計算距離用python來實現:

    # get Euclidean distance
    def  get_Euclidean_distance(self,
    a : dict(type = np.array,help = "Normalized data set"),
    dataSet : dict(type = np.array, help = "The two-dimensional array stores the first three digits of the sample data"),
    labels : dict(type=list, help="Actual labeling of test sample data"),
    k : dict(type = int, help = "Number of nearest neighbors") ):
        # get the length of the first dimension維度 of the data matrix, Which is the length of each dimension
        dataSetSize = dataSet.shape[0]

        # tile repeating array 'a', with dataSet rows and 1 dataSet column, subtraction calculation difference
        diffMat = np.tile(a, (dataSetSize, 1)) - dataSet

        # **means the power operation, the Euclidean distance used here
        sqDiffMat = diffMat ** 2

        # default parameters of Ordinary 'sum' are axis = 0 for ordinary addition, and axis = 1 for row vector addition of one row 
        sqDisttances = sqDiffMat.sum(axis =1)

        # argsort() returns the index via value from samll to large(array index 0, 1, 2,3)
        distances = sqDisttances ** 0.5
        sortedDistIndicies = distances.argsort()

        # Select 'k' points with the smallest distance
        classCount = {}
        for _ in range(k):
            # Returns the first k labels that are close according to the index value of the sorted result
            voteIlabel = labels[sortedDistIndicies[_]]
            classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1

        # Sorting frequency
        sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)

        # return the most frequent
        return sortedClassCount[0][0]

numpy.tile():https://docs.scipy.org/doc/numpy/reference...

很容易發現,上面方程中數字差值最大的屬性對計算結構的影響是最大的,也就是說,每年玩遊戲所佔百分比和每週吃冰淇淋公升數的影響要遠遠小於每年飛行的公里數的影響。產生這種現象的原因,僅僅是飛行里程數遠遠大於其他特徵值。但是這3種特徵值同等重要,有相同的權重,飛行里程數不該如此嚴重地影響計算結果。

將上述資料進行歸一化

如果將特徵值全部轉化為一個小區間內,那麼計算結果將不會受到特徵值大小的影響,通常將這種方法稱為數值統一化。

下面公式將任意取值方位的特徵值轉化為0-1區間內的值

k-鄰近演算法實現約會網站的配對效果

其中min和max分別表示資料集的最小特徵值和最大特徵值

傳入資料集,將之前儲存三個資料屬性的二維陣列傳入,進行歸一化後傳出歸一化的資料集1000*3*1矩陣,和資料集中最大值和最小值的差1*3矩陣,資料集中最小值的1*3矩陣
下面是實現歸一化的python原始碼:

    # Normalized eigenvalues 歸一化特徵值 
    # Normalization formula公式: ('current value'-'minimum value') / range
    def autoNorm(self,
    dataSet : dict(type = np.array, help="The two-dimensional array stores the first three digits of the sample data"),
    )->dict(type=(normDataSet, ranges, minVals),help=("Normalized data matrix 1000 * 3 * 1", "maximum value minus minimum value data matrix 1 * 3", "matrix formed by minimum value in data set 1 * 3")):  

        # stores the minimum value of each column, parameter 0 makes it possible to select the minimum value from the column instead of the current row
        minVals = dataSet.min (0) 

        # stores the maximum value of each column
        maxVals = dataSet.max (0) 
        ranges = maxVals-minVals

        # initialize the normalization matrix to the read dataSet
        normDataSet = np.zeros(shape (dataSet)) 

        # m holds the first row
        # The feature matrix is 3x1000, and the min max range is 1x3. Therefore, the content of the variable is copied into the input matrix with the same size as the tile.

        m = dataSet.shape [0] 
        normDataSet = dataSet-tile (minVals, (m, 1))
        normDataSet = normDataSet / tile (ranges, (m, 1))
        return normDataSet, ranges, minVals

 0x02 測試/使用演算法

測試分類器和執行結果

下面將測試分類器的執行效果,如果分類器的正確率滿足要求,就可以使用這個軟體來處理約會網站提供的約會名單。通常只使用資料集中百分之九十的樣本作為訓練樣本來訓練分類器,而使用其餘百分之十的資料去測試分類器。

下面python原始碼用來測試約會網站分類器的效能。

    # Test Dating Site Classification Results Code
    def datingClassTest(self):
        hoRatio = 0.10  # hold out 10%
        datingDataMat, datingLabels = self.file2matrix('/datingTestSet2.txt')  # load data setfrom file
        normMat, ranges, minVals = self.autoNorm(datingDataMat)
        m = normMat.shape[0]
        numTestVecs = int(m * hoRatio)
        errorCount = 0.0
        for i in range(numTestVecs):
            classifierResult = self.get_Euclidean_distance(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
            print("分類器返回的值: %s, 正確的值: %s" % (classifierResult, datingLabels[i]))
            if (classifierResult != datingLabels[i]): errorCount += 1.0
        print("總的錯誤率是: %f" % (errorCount / float(numTestVecs)))
        print("錯誤的個數:%f" % errorCount)

測試一下分類器的效果:

if __name__ == "__main__":
    test = KNN_test()
    # print(test.file2matrix.__annotations__)
    test.datingClassTest()

執行結果:

分類器返回的值: 3, 正確的值: 3
...(測試資料百分之十,大概100條資料)...
分類器返回的值: 3, 正確的值: 1
總的錯誤率是: 0.050000
錯誤的個數:5.000000

分類器處理資料的的錯誤率是百分之2,結果還是比較不錯的,表明可以預測分類。

手動輸入的方式輸入測試資料集

# Complete Dating Website Prediction: Given a person, determine when it is suitable for dating
    def classifyPerson(self):
        resultList = ['不喜歡', '一般喜歡', '特別喜歡']
        percentTats = float(input("玩遊戲佔的百分比"))
        ffMiles = float(input("每年坐飛機多少公里"))
        iceCream = float(input("每年吃多少公升的冰淇淋"))
        datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
        normMat, ranges, minVals = autoNorm(datingDataMat)
        inArr = array([ffMiles, percentTats, iceCream])
        classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)

        print("你將有可能對這個人是:", resultList[int(classifierResult) - 1])
本作品採用《CC 協議》,轉載必須註明作者和本文連結

文章!!首發於我的部落格Stray_Camel(^U^)ノ~YO

相關文章