python3.5《機器學習實戰》學習筆記(一):k近鄰演算法
轉載請註明作者和出處:http://blog.csdn.net/u013829973
系統版本:window 7 (64bit)
python版本:python 3.5
IDE:Spyder (一個比較方便的辦法是安裝anaconda,那麼Spyder和jupyter以及python幾個常用的包都有了,甚至可以方便的安裝TensorFlow等,安裝方法連結)
程式碼和資料集在: GitHub
k近鄰演算法
1.1 k近鄰演算法概述
k近鄰(k-nearest neighbor,KNN)是一種基本的分類與迴歸演算法。於1968年由Cover和Hart提出。k近鄰的輸入是例項的特徵向量,對應於特徵空間的點;輸出為例項的類別,可以取多類。k近鄰演算法假設給定一個訓練資料集,其中的例項類別已定,分類時,對新的例項,根據其k個最近鄰的訓練例項的類別,通過多數表決等方式進行預測。因此,k近鄰法不具有顯式的學習過程。簡單的說,給定一個訓練資料集,對新的輸入例項,在訓練集中找到與該例項最近鄰的k個例項,這k個例項的多數屬於哪個類,就把該輸入例項分為這個類。這就是k近鄰演算法中k的出處,通常k是不大於20的整數。
k近鄰演算法的三個基本要素:k值的選擇、距離度量、分類決策規則
下面我們就用一個簡單的例子來更好的理解k近鄰演算法:
已知表格的前四部電影,根據打鬥鏡頭和接吻鏡頭判斷一個新的電影所屬類別?
電影名稱 | 打鬥鏡頭 | 接吻鏡頭 | 電影型別 |
---|---|---|---|
1 | 3 | 104 | 愛情片 |
2 | 2 | 100 | 愛情片 |
3 | 99 | 5 | 動作片 |
4 | 98 | 2 | 動作片 |
未知電影? | 18 | 90 | 未知 |
已知的訓練集包含兩個特徵(打鬥鏡頭和接吻鏡頭)和類別(愛情片還是動作片)。根據經驗,動作片往往打鬥鏡頭比較多,而愛情片往往就是接吻的鏡頭比較多了。但是knn演算法可沒有我們這麼感性的認識。
1.2 視覺化與距離計算
我們首先對訓練資料進行視覺化:
圖1.1 電影分類(圖中的紅色點代表愛情片,藍色點代表動作片,橙色點代表未知電影)
那麼,knn是通過計算什麼來判斷未知電影屬於哪一類的呢?答案:距離。我們首先計算訓練集的所有電影與未知電影的歐式距離:(這裡的距離除了歐式距離,還有曼哈頓距離、切比雪夫距離、閔可夫斯基距離等等)
歐式距離(Euclidean Distance)計算公式:兩個n維向量
對於本例子n=2,
電影1與未知電影距離:20.5;
電影,2與未知電影距離:18.7;
電影3與未知電影距離:117.4;
電影4與未知電影距離:118.9;
現在我們得到了訓練集中所有樣本與未知電影的距離,按照距離遞增排序,可以找到距離最近的電影,假設k=3,則三個最靠近的電影依次是電影1、電影2、電影3,而這三部電影類別2個為愛情片,一個為動作片,所以該未知電影的所屬類別是愛情片。
有了上面的這個例子,我們來總結一下k近鄰演算法步驟
對未知類別屬性的資料集的每個點依次執行以下操作
(1) 計算已知類別資料集中的點與當前點之間的距離;
(2) 按照距離遞增次序排序;
(3)選取與當前點距離最小的k個點;
(4)確定前k個點所在類別的出現頻率;
(5)返回前k個點所出現頻率最高的類別作為當前點的預測分類
1.3 python實現KNN
我們仍然以上面的電影分類進行程式碼編寫
1.3.1 準備資料
對錶1.1中的訓練資料,用numpy建立資料集和標籤
'''
函式功能:建立資料集
Input: 無
Output: group:資料集
labels:類別標籤
'''
import numpy as np
def createDataSet():#建立資料集
group = np.array([[3,104],[2,100],[99,5],[98,2]])
labels = ['愛情片','愛情片','動作片','動作片']
return group, labels
'''
函式功能: 主函式
'''
if __name__ == '__main__':
group,labels = createDataSet()#建立資料集
print('group:\n',group)#列印資料集
print('labels:',labels)
程式執行結果:
1.3.2 k近鄰演算法
'''
函式功能: kNN分類
Input: inX: 測試集 (1xN)
dataSet: 已知資料的特徵(NxM)
labels: 已知資料的標籤或類別(1xM vector)
k: k近鄰演算法中的k
Output: 測試樣本最可能所屬的標籤
'''
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # shape[0]返回dataSet的行數
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
# tile(inX,(a,b))函式將inX重複a行,重複b列
sqDiffMat = diffMat**2 #作差後平方
sqDistances = sqDiffMat.sum(axis=1)
#sum()求和函式,sum(0)每列所有元素相加,sum(1)每行所有元素相加
distances = sqDistances**0.5 #開平方,求歐式距離
sortedDistIndicies = distances.argsort()
#argsort函式返回的是陣列值從小到大的索引值
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
#取出前k個距離對應的標籤
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#計算每個類別的樣本數。字典get()函式返回指定鍵的值,如果值不在字典中返回預設值0
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
#reverse降序排列字典
#python2版本中的iteritems()換成python3的items()
#key=operator.itemgetter(1)按照字典的值(value)進行排序
#key=operator.itemgetter(0)按照字典的鍵(key)進行排序
return sortedClassCount[0][0] #返回字典的第一條的key,也即是測試樣本所屬類別
1.3.3 KNN演算法完整程式碼
# -*- coding: utf-8 -*-
'''
Created on Sep 10, 2017
kNN: k近鄰(k Nearest Neighbors) 電影分類
author:we-lee
'''
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import operator
'''
函式功能:建立資料集
Input: 無
Output: group:資料集
labels:類別標籤
'''
def createDataSet():#建立資料集
group = np.array([[3,104],[2,100],[99,5],[98,2]])
labels = ['愛情片','愛情片','動作片','動作片']
return group, labels
'''
函式功能: kNN分類
Input: inX: 測試集 (1xN)
dataSet: 已知資料的特徵(NxM)
labels: 已知資料的標籤或類別(1xM vector)
k: k近鄰演算法中的k
Output: 測試樣本最可能所屬的標籤
'''
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # shape[0]返回dataSet的行數
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # tile(inX,(a,b))函式將inX重複a行,重複b列
sqDiffMat = diffMat**2 #作差後平方
sqDistances = sqDiffMat.sum(axis=1)#sum()求和函式,sum(0)每列所有元素相加,sum(1)每行所有元素相加
distances = sqDistances**0.5 #開平方,求歐式距離
sortedDistIndicies = distances.argsort() #argsort函式返回的是陣列值從小到大的索引值
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]#取出前k個距離對應的標籤
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#計算每個類別的樣本數。字典get()函式返回指定鍵的值,如果值不在字典中返回預設值0
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
#reverse降序排列字典
#python2版本中的iteritems()換成python3的items()
#key=operator.itemgetter(1)按照字典的值(value)進行排序
#key=operator.itemgetter(0)按照字典的鍵(key)進行排序
return sortedClassCount[0][0] #返回字典的第一條的key,也即是測試樣本所屬類別
'''
函式功能: 主函式
'''
if __name__ == '__main__':
group,labels = createDataSet()#建立資料集
print('group:\n',group)#列印資料集
print('labels:',labels)
zhfont = matplotlib.font_manager.FontProperties(fname=r'c:\windows\fonts\simsun.ttc')#設定中文字型路徑
fig = plt.figure(figsize=(10,8))#視覺化
ax = plt.subplot(111) #圖片在第一行,第一列的第一個位置
ax.scatter(group[0:2,0],group[0:2,1],color='red',s=50)
ax.scatter(group[2:4,0],group[2:4,1],color='blue',s=50)
ax.scatter(18,90,color='orange',s=50)
plt.annotate('which class?', xy=(18, 90), xytext=(3, 2),arrowprops=dict(facecolor='black', shrink=0.05),)
plt.xlabel('打鬥鏡頭',fontproperties=zhfont)
plt.ylabel('接吻鏡頭',fontproperties=zhfont)
plt.title('電影分類視覺化',fontproperties=zhfont)
plt.show()
testclass = classify0([18,90], group, labels, 3)#用未知的樣本來測試演算法
print('測試結果:',testclass)#列印測試結果
程式執行結果:
2 k近鄰演算法總結
優點:
1. 簡單、有效、精度高;
2. 對離群值不敏感;
3. 無資料輸入假定;
4 .可用於數值型資料和離散型資料;
缺點:
1. 計算複雜度高、空間複雜度高;
2. 樣本不平衡問題(即有的類別的樣本數量很多,而其它類別的樣本數量很少),影響分類效果;(關於樣本不平衡問題的解決方法,參見http://blog.csdn.net/u013829973/article/details/77675147)
3. 一般數值很大的時候不用這個,計算量太大。但是單個樣本又不能太少 ,否則容易發生誤分;
4. 最大的缺點是無法給出資料的內在含義,無法給出任何資料的基礎結構資訊,無法知曉平均例項樣本和典型例項樣本具有什麼特徵。
注意事項
1. K值的設定
較小的k值,學習的近似誤差減小,但是估計誤差會增大,意味著整體模型變得複雜,容易過擬合。
較大的k值,學習的近似誤差增大,但是估計誤差會減小,意味著整體模型變得簡單。
在應用中k值一般取一個比較小的數值。採用交叉驗證選取最優的k值。
2.優化改進
在確定最終的類別時,不是簡單的使用多數表決投票,而是進行加權投票,距離越近權重越高。
k近鄰法中,當訓練集、距離度量、k值和分類決策規則確定後,其結果唯一確定。
k近鄰法的實現需要考慮如何快速搜尋k個最近鄰點。kd樹是一種便於對k維空間中的資料進行快速檢索的資料結構。詳見李航《統計學習方法》第三章
下一篇,我們會將knn演算法應用到實際問題中,實戰:改進約會網站的配對效果和手寫識別系統!
如有不當之處,請留言,謝謝!