K--NN(近鄰)模型算法


運行平台: Windows 

Python版本: Python3.x 

 

1.1 k-近鄰法簡介

   k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與回歸方法。它的工作原理是:存在一個樣本數據集合,也稱作為訓練樣本集,並且樣本集中每個數據都存在標簽,即我們知道樣本集中每一個數據與所屬分類的對應關系。輸入沒有標簽的新數據后,將新的數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取樣本最相似數據(最近鄰)的分類標簽。一般來說,我們只選擇樣本數據集中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大於20的整數。最后,選擇k個最相似數據中出現次數最多的分類,作為新數據的分類。

 舉個簡單的例子,我們可以使用k-近鄰算法分類一個電影是愛情片還是動作片。

電影名稱 打斗鏡頭 接吻鏡頭 電影類型
電影1 1 101 愛情片
電影2 5 89 愛情片
電影3 108 5 動作片
電影4 115 8 動作片

   

  這個數據集有兩個特征,即打斗鏡頭數和接吻鏡頭數。除此之外,我們也知道每個電影的所屬類型,即分類標簽。用肉眼粗略地觀察,接吻鏡頭多的,是愛情片。打斗鏡頭多的,是動作片。以我們多年的看片經驗,這個分類還算合理。如果現在給我一部電影,你告訴我這個電影打斗鏡頭數和接吻鏡頭數。不告訴我這個電影類型,我可以根據你給我的信息進行判斷,這個電影是屬於愛情片還是動作片。而k-近鄰算法也可以像我們人一樣做到這一點,不同的地方在於,我們的經驗更”牛逼”,而k-鄰近算法是靠已有的數據。比如,你告訴我這個電影打斗鏡頭數為2,接吻鏡頭數為102,我的經驗會告訴你這個是愛情片,k-近鄰算法也會告訴你這個是愛情片。你又告訴我另一個電影打斗鏡頭數為49,接吻鏡頭數為51,我”邪惡”的經驗可能會告訴你,這有可能是個”愛情動作片”,畫面太美,我不敢想象。 (如果說,你不知道”愛情動作片”是什么?請評論留言與我聯系,我需要你這樣像我一樣純潔的朋友。) 但是k-近鄰算法不會告訴你這些,因為在它的眼里,電影類型只有愛情片和動作片,它會提取樣本集中特征最相似數據(最鄰近)的分類標簽,得到的結果可能是愛情片,也可能是動作片,但絕不會是”愛情動作片”。當然,這些取決於數據集的大小以及最近鄰的判斷標准等因素。

那么k-鄰近算法是什么呢?k-近鄰算法步驟如下:

  1. 計算已知類別數據集中的點與當前點之間的距離;
  2. 按照距離遞增次序排序;
  3. 選取與當前點距離最小的k個點;
  4. 確定前k個點所在類別的出現頻率;
  5. 返回前k個點所出現頻率最高的類別作為當前點的預測分類。

    比如,現在我這個k值取3,那么在電影例子中,按距離依次排序的三個點分別是動作片(108,5)、動作片(115,8)、愛情片(5,89)。在這三個點中,動作片出現的頻率為三分之二,愛情片出現的頻率為三分之一,所以該紅色圓點標記的電影為動作片。這個判別過程就是k-近鄰算法。

1.2 創建kNN_test01.py文件,K-NN的k值為3,編寫代碼如下:

 1 # -*- coding: UTF-8 -*-
 2 import numpy as np
 3 import operator
 4 
 5 def createDataSet():
 6     #四組二維特征
 7     group = np.array([[1,101],[5,89],[108,5],[115,8]])
 8     #四組特征的標簽
 9     labels = ['愛情片','愛情片','動作片','動作片']
10     return group, labels
11 
12 """
13 函數說明:kNN算法,分類器
14 
15 Parameters:
16     inX - 用於分類的數據(測試集)
17     dataSet - 用於訓練的數據(訓練集)
18     labes - 分類標簽
19     k - kNN算法參數,選擇距離最小的k個點
20 Returns:
21     sortedClassCount[0][0] - 分類結果
22 
23 Modify:
24     2017-07-13
25 """
26 def classify0(inX, dataSet, labels, k):
27     #numpy函數shape[0]返回dataSet的行數
28     dataSetSize = dataSet.shape[0]
29     #在列向量方向上重復inX共1次(橫向),行向量方向上重復inX共dataSetSize次(縱向)
30     diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
31     #二維特征相減后平方
32     sqDiffMat = diffMat**2
33     #sum()所有元素相加,sum(0)列相加,sum(1)行相加
34     sqDistances = sqDiffMat.sum(axis=1)
35     #開方,計算出距離
36     distances = sqDistances**0.5
37     #返回distances中元素從小到大排序后的索引值
38     sortedDistIndices = distances.argsort()
39     #定一個記錄類別次數的字典
40     classCount = {}
41     for i in range(k):
42         #取出前k個元素的類別
43         voteIlabel = labels[sortedDistIndices[i]]
44         #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。
45         #計算類別次數
46         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
47     #python3中用items()替換python2中的iteritems()
48     #key=operator.itemgetter(1)根據字典的值進行排序
49     #key=operator.itemgetter(0)根據字典的鍵進行排序
50     #reverse降序排序字典
51     sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
52     #返回次數最多的類別,即所要分類的類別
53     return sortedClassCount[0][0]
54 
55 if __name__ == '__main__':
56     #創建數據集
57     group, labels = createDataSet()
58     #測試集
59     test = [101,20]
60     #kNN分類
61     test_class = classify0(test, group, labels, 3)
62     #打印分類結果
63     print(test_class)

測試結果:

print(test_class)為動作片。

 

2.1 k-近鄰算法實戰之約會網站配對效果判定

上一小結學習了簡單的k-近鄰算法的實現方法,但是這並不是完整的k-近鄰算法流程,k-近鄰算法的一般流程:

  1. 收集數據:可以使用爬蟲進行數據的收集,也可以使用第三方提供的免費或收費的數據。一般來講,數據放在txt文本文件中,按照一定的格式進行存儲,便於解析及處理。
  2. 准備數據:使用Python解析、預處理數據。
  3. 分析數據:可以使用很多方法對數據進行分析,例如使用Matplotlib將數據可視化。
  4. 測試算法:計算錯誤率。
  5. 使用算法:錯誤率在可接受范圍內,就可以運行k-近鄰算法進行分類。

    已經了解了k-近鄰算法的一般流程,下面開始進入實戰內容。

2.2 實戰背景

海倫女士一直使用在線約會網站尋找適合自己的約會對象。盡管約會網站會推薦不同的任選,但她並不是喜歡每一個人。經過一番總結,她發現自己交往過的人可以進行如下分類:

  • 不喜歡的人
  • 魅力一般的人
  • 極具魅力的人

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

海倫收集的樣本數據主要包含以下3種特征:

  • 每年獲得的飛行常客里程數
  • 玩視頻游戲所消耗時間百分比
  • 每周消費的冰淇淋公升數

 didntLike:不喜歡   largeDoses:極具魅力    smallDOses:魅力一般

數據下載地址:https://github.com/Jack-Cherish/Machine-Learning/tree/master/kNN/2.%E6%B5%B7%E4%BC%A6%E7%BA%A6%E4%BC%9A

 2.3 分析數據:數據可視化

在kNN_test02.py文件中編寫名為showdatas的函數,用來將數據可視化

  1 import matplotlib.lines as mlines
  2 import matplotlib.pyplot as plt
  3 import numpy as np
  4 
  5 
  6 """
  7 函數說明:打開並解析文件,對數據進行分類:1代表不喜歡,2代表魅力一般,3代表極具魅力
  8 
  9 Parameters:
 10     filename - 文件名
 11 Returns:
 12     returnMat - 特征矩陣
 13     classLabelVector - 分類Label向量
 14 
 15 Modify:
 16     2017-12-28
 17 """
 18 def file2matrix(filename):
 19     #打開文件
 20     fr = open(filename)
 21     #讀取文件所有內容
 22     arrayOLines = fr.readlines()
 23     #得到文件行數
 24     numberOfLines = len(arrayOLines)
 25     #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列
 26     returnMat = np.zeros((numberOfLines,3))
 27     #返回的分類標簽向量
 28     classLabelVector = []
 29     #行的索引值
 30     index = 0
 31     for line in arrayOLines:
 32         #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ')
 33         line = line.strip()
 34         #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。
 35         listFromLine = line.split('\t')
 36         #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特征矩陣
 37         returnMat[index,:] = listFromLine[0:3]
 38         #根據文本中標記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
 39         if listFromLine[-1] == 'didntLike':
 40             classLabelVector.append(1)
 41         elif listFromLine[-1] == 'smallDoses':
 42             classLabelVector.append(2)
 43         elif listFromLine[-1] == 'largeDoses':
 44             classLabelVector.append(3)
 45         index += 1
 46     #print(returnMat)
 47     return returnMat, classLabelVector
 48 
 49 def showdatas(returnMat, classLabelVector):
 50     #設置漢字格式
 51     plt.rcParams['font.sans-serif'] = ['SimHei']  # 用來正常顯示中文標簽
 52     plt.rcParams['font.serif'] = ['SimHei']
 53     plt.rcParams['axes.unicode_minus'] = False  # 用控制中文亂碼
 54     #將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小為(13,8)
 55     #當nrow=2,nclos=2時,代表fig畫布被分為四個區域,axs[0][0]表示第一行第一個區域
 56     fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8))
 57     numberOfLabels = len(classLabelVector)
 58     LabelsColors = []
 59     for i in classLabelVector:
 60         if i == 1:
 61             LabelsColors.append('black')
 62         if i == 2:
 63             LabelsColors.append('orange')
 64         if i == 3:
 65             LabelsColors.append('red')
 66     #畫出散點圖,以returnMat矩陣的第一(飛行常客例程)、第二列(玩游戲)數據畫散點數據,散點大小為15,透明度為0.5
 67     axs[0][0].scatter(x=returnMat[:,0], y=returnMat[:,1], color=LabelsColors,s=30, alpha=.5)
 68     #設置標題,x軸label,y軸label
 69     axs0_title_text = axs[0][0].set_title('每年獲得的飛行常客里程數與玩視頻游戲所消耗時間占比')
 70     axs0_xlabel_text = axs[0][0].set_xlabel('每年獲得的飛行常客里程數')
 71     axs0_ylabel_text = axs[0][0].set_ylabel('玩視頻游戲所消耗時間占')
 72     plt.setp(axs0_title_text, size=12, weight='bold', color='red')
 73     plt.setp(axs0_xlabel_text, size=9, weight='bold', color='black')
 74     plt.setp(axs0_ylabel_text, size=9, weight='bold', color='black')
 75 
 76     #畫出散點圖,以returnMat矩陣的第一(飛行常客例程)、第三列(冰激凌)數據畫散點數據,散點大小為15,透明度為0.5
 77     axs[0][1].scatter(x=returnMat[:,0], y=returnMat[:,2], color=LabelsColors,s=30, alpha=.5)
 78     #設置標題,x軸label,y軸label
 79     axs1_title_text = axs[0][1].set_title('每年獲得的飛行常客里程數與每周消費的冰激淋公升數')
 80     axs1_xlabel_text = axs[0][1].set_xlabel('每年獲得的飛行常客里程數')
 81     axs1_ylabel_text = axs[0][1].set_ylabel('每周消費的冰激淋公升數')
 82     plt.setp(axs1_title_text, size=12, weight='bold', color='red')
 83     plt.setp(axs1_xlabel_text, size=9, weight='bold', color='black')
 84     plt.setp(axs1_ylabel_text, size=9, weight='bold', color='black')
 85 
 86     #畫出散點圖,以returnMat矩陣的第二(玩游戲)、第三列(冰激凌)數據畫散點數據,散點大小為15,透明度為0.5
 87     axs[1][0].scatter(x=returnMat[:,1], y=returnMat[:,2], color=LabelsColors,s=30, alpha=.5)
 88     #設置標題,x軸label,y軸label
 89     axs2_title_text = axs[1][0].set_title('玩視頻游戲所消耗時間占比與每周消費的冰激淋公升數')
 90     axs2_xlabel_text = axs[1][0].set_xlabel('玩視頻游戲所消耗時間占比')
 91     axs2_ylabel_text = axs[1][0].set_ylabel('每周消費的冰激淋公升數')
 92     plt.setp(axs2_title_text, size=12, weight='bold', color='red')
 93     plt.setp(axs2_xlabel_text, size=9, weight='bold', color='black')
 94     plt.setp(axs2_ylabel_text, size=9, weight='bold', color='black')
 95     #設置圖例
 96     didntLike = mlines.Line2D([], [], color='black', marker='.',
 97                       markersize=6, label='didntLike')
 98     smallDoses = mlines.Line2D([], [], color='orange', marker='.',
 99                       markersize=6, label='smallDoses')
100     largeDoses = mlines.Line2D([], [], color='red', marker='.',
101                       markersize=6, label='largeDoses')
102     #添加圖例
103     axs[0][0].legend(handles=[didntLike,smallDoses,largeDoses], loc='upper right')
104     axs[0][1].legend(handles=[didntLike,smallDoses,largeDoses], loc='upper right')
105     axs[1][0].legend(handles=[didntLike,smallDoses,largeDoses], loc='upper right')
106     #顯示圖片
107     plt.show()
108 
109 """
110 函數說明:main函數
111 
112 Parameters:
113 114 Returns:
115 116 
117 Modify:
118     2017-12-28
119 """
120 if __name__ == '__main__':
121     #打開的文件名
122     filename = "datingTestSet.txt"
123     #打開並處理數據
124     returnMat, classLabelVector = file2matrix(filename)
125     showdatas(returnMat, classLabelVector)

運行結果:

  通過數據可以很直觀的發現數據的規律,比如以玩游戲所消耗時間占比與每年獲得的飛行常客里程數,只考慮這二維的特征信息,給我的感覺就是海倫喜歡有生活質量的男人。為什么這么說呢?每年獲得的飛行常客里程數表明,海倫喜歡能享受飛行常客獎勵計划的男人,但是不能經常坐飛機,疲於奔波,滿世界飛。同時,這個男人也要玩視頻游戲,並且占一定時間比例。能到處飛,又能經常玩游戲的男人是什么樣的男人?很顯然,有生活質量,並且生活悠閑的人。我的分析,僅僅是通過可視化的數據總結的個人看法。我想,每個人的感受應該也是不盡相同。

2.4 使用算法:構建完整可用系統

    我們可以給海倫一個小段程序,通過該程序海倫會在約會網站上找到某個人並輸入他的信息。程序會給出她對男方喜歡程度的預測值。

 代碼如下:

  1 #!/usr/bin/env python
  2 # _*_ coding:utf-8 _*_
  3 
  4 import numpy as np
  5 import operator
  6 
  7 """
  8 函數說明:kNN算法,分類器
  9 
 10 Parameters:
 11     inX - 用於分類的數據(測試集)
 12     dataSet - 用於訓練的數據(訓練集)
 13     labes - 分類標簽
 14     k - kNN算法參數,選擇距離最小的k個點
 15 Returns:
 16     sortedClassCount[0][0] - 分類結果
 17 
 18 Modify:
 19     2017-12-28
 20 """
 21 def classify0(inX, dataSet, labels, k):
 22     #numpy函數shape[0]返回dataSet的行數
 23     dataSetSize = dataSet.shape[0]
 24     #在列向量方向上重復inX共1次(橫向),行向量方向上重復inX共dataSetSize次(縱向)
 25     diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
 26     #二維特征相減后平方
 27     sqDiffMat = diffMat**2
 28     #sum()所有元素相加,sum(0)列相加,sum(1)行相加
 29     sqDistances = sqDiffMat.sum(axis=1)
 30     #開方,計算出距離
 31     distances = sqDistances**0.5
 32     #返回distances中元素從小到大排序后的索引值
 33     sortedDistIndices = distances.argsort()
 34     #定一個記錄類別次數的字典
 35     classCount = {}
 36     for i in range(k):
 37         #取出前k個元素的類別
 38         voteIlabel = labels[sortedDistIndices[i]]
 39         #dict.get(key,default=None),字典的get()方法,返回指定鍵的值,如果值不在字典中返回默認值。
 40         #計算類別次數
 41         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
 42     #python3中用items()替換python2中的iteritems()
 43     #key=operator.itemgetter(1)根據字典的值進行排序
 44     #key=operator.itemgetter(0)根據字典的鍵進行排序
 45     #reverse降序排序字典
 46     sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
 47     #返回次數最多的類別,即所要分類的類別
 48     return sortedClassCount[0][0]
 49 
 50 
 51 """
 52 函數說明:打開並解析文件,對數據進行分類:1代表不喜歡,2代表魅力一般,3代表極具魅力
 53 
 54 Parameters:
 55     filename - 文件名
 56 Returns:
 57     returnMat - 特征矩陣
 58     classLabelVector - 分類Label向量
 59 
 60 Modify:
 61     2017-12-28
 62 """
 63 def file2matrix(filename):
 64     #打開文件
 65     fr = open(filename)
 66     #讀取文件所有內容
 67     arrayOLines = fr.readlines()
 68     #得到文件行數
 69     numberOfLines = len(arrayOLines)
 70     #返回的NumPy矩陣,解析完成的數據:numberOfLines行,3列
 71     returnMat = np.zeros((numberOfLines,3))
 72     #返回的分類標簽向量
 73     classLabelVector = []
 74     #行的索引值
 75     index = 0
 76     for line in arrayOLines:
 77         #s.strip(rm),當rm空時,默認刪除空白符(包括'\n','\r','\t',' ')
 78         line = line.strip()
 79         #使用s.split(str="",num=string,cout(str))將字符串根據'\t'分隔符進行切片。
 80         listFromLine = line.split('\t')
 81         #將數據前三列提取出來,存放到returnMat的NumPy矩陣中,也就是特征矩陣
 82         returnMat[index,:] = listFromLine[0:3]
 83         #根據文本中標記的喜歡的程度進行分類,1代表不喜歡,2代表魅力一般,3代表極具魅力
 84         if listFromLine[-1] == 'didntLike':
 85             classLabelVector.append(1)
 86         elif listFromLine[-1] == 'smallDoses':
 87             classLabelVector.append(2)
 88         elif listFromLine[-1] == 'largeDoses':
 89             classLabelVector.append(3)
 90         index += 1
 91     return returnMat, classLabelVector
 92 
 93 """
 94 函數說明:對數據進行歸一化
 95 
 96 Parameters:
 97     dataSet - 特征矩陣
 98 Returns:
 99     normDataSet - 歸一化后的特征矩陣
100     ranges - 數據范圍
101     minVals - 數據最小值
102 
103 Modify:
104     2017-12-28
105 """
106 def autoNorm(dataSet):
107     #獲得數據的最小值
108     minVals = dataSet.min(0)
109     maxVals = dataSet.max(0)
110     #最大值和最小值的范圍
111     ranges = maxVals - minVals
112     #shape(dataSet)返回dataSet的矩陣行列數
113     normDataSet = np.zeros(np.shape(dataSet))
114     #返回dataSet的行數
115     m = dataSet.shape[0]
116     #原始值減去最小值
117     normDataSet = dataSet - np.tile(minVals, (m, 1))
118     #除以最大和最小值的差,得到歸一化數據
119     normDataSet = normDataSet / np.tile(ranges, (m, 1))
120     #返回歸一化數據結果,數據范圍,最小值
121     return normDataSet, ranges, minVals
122 
123 """
124 函數說明:通過輸入一個人的三維特征,進行分類輸出
125 
126 Parameters:
127 128 Returns:
129 130 
131 Modify:
132     2017-12-28
133 """
134 def classifyPerson():
135     #輸出結果
136     resultList = ['討厭','有些喜歡','非常喜歡']
137     #三維特征用戶輸入
138     precentTats = float(input("每年獲得的飛行常客里程數:"))
139     ffMiles = float(input("玩視頻游戲所耗時間百分比:"))
140     iceCream = float(input("每周消費的冰激淋公升數:"))
141     #打開的文件名
142     filename = "datingTestSet.txt"
143     #打開並處理數據
144     datingDataMat, datingLabels = file2matrix(filename)
145     #訓練集歸一化
146     normMat, ranges, minVals = autoNorm(datingDataMat)
147     #生成NumPy數組,測試集
148     inArr = np.array([precentTats, ffMiles, iceCream])
149 
150     #測試集歸一化
151     norminArr = (inArr - minVals) / ranges
152 
153     #返回分類結果
154     classifierResult = classify0(norminArr, normMat, datingLabels, 3)
155 
156     #打印結果
157     print("你可能%s這個人" % (resultList[classifierResult-1]))
158 
159 """
160 函數說明:main函數
161 
162 Parameters:
163 164 Returns:
165 166 
167 Modify:
168     2017-12-28
169 """
170 if __name__ == '__main__':
171     classifyPerson()

並輸入數據(44000,12,,0.5),預測結果是”你可能有些喜歡這個人”,也就是這個人魅力一般。一共有三個檔次:討厭、有些喜歡、非常喜歡,對應着不喜歡的人、魅力一般的人、極具魅力的人

 

 

3.1 kNN算法的優缺點

優點

  • 簡單好用,容易理解,精度高,理論成熟,既可以用來做分類也可以用來做回歸;
  • 可用於數值型數據和離散型數據;
  • 訓練時間復雜度為O(n);無數據輸入假定;
  • 對異常值不敏感。

缺點:

  • 計算復雜性高;空間復雜性高;
  • 樣本不平衡問題(即有些類別的樣本數量很多,而其它樣本的數量很少);
  • 一般數值很大的時候不用這個,計算量太大。但是單個樣本又不能太少,否則容易發生誤分。
  • 最大的缺點是無法給出數據的內在含義。

 博客轉至:http://blog.csdn.net/c406495762/article/details/75172850

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM