2.1 K-近鄰算法概述
K-近鄰算法應該就是一個分類算法。採用測量不同特徵值之間的距離方法進行分類。
優點:精度高、對異常值不敏感、無數據輸入假定。
缺點:計算複雜度高、空間複雜度高。
適用範圍:數值型和標稱型。
書中舉了一個電影分類的例子,通過一些鏡頭來判斷這是愛情片還是動作片愛情動作片。
K-近鄰算法的一般流程
- 收集數據:可以使用任何方法。
- 準備數據:距離計算所需要的數值,最好是結構化的數據格式。
- 分析數據:可以使用任何方法。
- 訓練算法:此步驟不合適K-近鄰算法。
- 測試算法:計算錯誤率。
- 使用算法:首先需要輸入樣本數據和結構化的輸出結果,然後運行K-近鄰算法判定輸入數據分別屬於哪個分類,最後應用對計算出的分類執行後續的處理。
================================================================================
接下來是準備工作,使用 Python 導入數據。推薦手打,不要複製粘貼。
kNN.py
注意我用的文件可以從 http://www.ituring.com.cn/book/1021 下載,而且根據勘誤應該用 datingTestSet2.txt
#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊
def createDataSet():
group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group, labels
>>> import kNN
>>> group,labels = kNN.createDataSet()
>>> group
>>> labels
驗證下與書一樣就可以了。結果如下圖:
==========================================================================
接下來正式的 kNN 算法,需要說明的是,幾個輸入參數是什麼。
inX 是用於分類的輸入向量,dataSet 是輸入的訓練樣本,標籤向量爲 labels , 參數 k 表示的是用於選擇的最近鄰居的數目,其中標籤向量的元素數目和矩陣 dataSet 的行數相同 。
代碼如下:
#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊
def createDataSet():
group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group, labels
def classify0(inX, dataSet, labels, k):
# 距離計算
dataSetSize = dataSet.shape[0] # .shape 讀取矩陣的長度
diffMat = tile(inX,(dataSetSize,1)) - dataSet
# tile(A,n),功能是將數組A重複n次,構成一個新的數組
sqDiffMat = diffMat ** 2 # **就是乘方
sqDistances = sqDiffMat.sum(axis = 1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort() # argsort函數返回的是數組值從小到大的索引值
classCount = {}
# 選擇距離最小的 K 個點
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#dict.get(key, default=None) key在字典中查找,在key不存在的情況下返回值None。
# 排序 sorted 函數詳見 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器對象,返回鍵值對
key = operator.itemgetter(1), reverse = True)
# itemgetter(1) 使用元組的第二個元素對列表排序
# itemgetter(0) 使用元組的第一個元素對列表排序
return sortedClassCount[0][0]
第一部分的距離計算就是歐氏距離,沒什麼好說的。
然後,確定前 k 個距離最小元素所在的主要分類,輸入 k 總是正整數。
最後,把 classCount 字典分解爲元組列表,然後用 itemgetter 方法,按照第二個元素的次序對元組排序。
==========================================================================================
跑一遍試試:
可以看出 [0,0] 分類結果是 B
==========================================================================================
2.2 示例:使用K-近鄰算法改進約會網站配對效果
2.2.1準備數據
樣本主要包含以下三種特徵:
1.每年獲得的飛行常客里程數
2.玩視頻遊戲所花費時間百分比
3.每週消費冰激凌公升數
在 kNN.py 中創建一個 file2matrix 函數處理輸入格式問題。
增加一部分代碼,並且在本文一開始的地方下載所需要的數據文件。
#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊
def createDataSet():
group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group, labels
def classify0(inX, dataSet, labels, k):
# 距離計算
dataSetSize = dataSet.shape[0] # .shape 讀取矩陣的長度
diffMat = tile(inX,(dataSetSize,1)) - dataSet
# tile(A,n),功能是將數組A重複n次,構成一個新的數組
sqDiffMat = diffMat ** 2 # **就是乘方
sqDistances = sqDiffMat.sum(axis = 1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort() # argsort函數返回的是數組值從小到大的索引值
classCount = {}
# 選擇距離最小的 K 個點
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#dict.get(key, default=None) key在字典中查找,在key不存在的情況下返回值None。
# 排序 sorted 函數詳見 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器對象,返回鍵值對
key = operator.itemgetter(1), reverse = True)
# itemgetter(1) 使用元組的第二個元素對列表排序
# itemgetter(0) 使用元組的第一個元素對列表排序
return sortedClassCount[0][0]
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 文件的行數
returnMat = zeros((numberOfLines, 3)) # 創建返回的 NumPy 矩陣
# 創建給定類型的矩陣,並初始化爲0。zeros((A,B)),創建一個A行,B列的0矩陣
classLabelVector = []
index = 0
for line in arrayOLines: # 解析文件數據列表
line = line.strip() # strip() 方法用於移除字符串頭尾指定的字符(這裏是截掉回車字符)
listFromLine = line.split('\t') # tab 字符
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
爲了方便起見,我再寫了一個 use-kNN.py 文件,不用在命令行裏面一個一個輸入了,文件如下:
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
結果如下:
以上就是從文本文件導入數據並且轉化爲想要的格式。不過結果和書上例子不太一樣。
========================================================================================================
2.2.2 分析數據
我先建立一個 plot.py 用於畫圖,代碼如下:
import matplotlib
import matplotlib.pyplot as plt
import kNN
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,1], datingDataMat[:,2])
plt.show()
那麼就可以畫出圖了:
上圖存在的問題是,沒有用顏色來標記不同樣本分類。於是在執行的時候可以加點代碼。
# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 沒導入會報錯
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 應該指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.show()
結果如下:
稍微改動一下,完善一下,加個xy標籤,改動一下讀取的數據列:
# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 沒導入會報錯
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 應該指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.xlabel('Air Miles')
plt.ylabel('Video Games')
plt.show()
得到如下圖:
============================================================================
2.2.3 歸一化數值
原始數據文件裏面,飛行常客里程數遠大於其他兩者,這導致其影響程度也遠大於其他兩者,而我們希望三者是具有同樣權重的,因此需要歸一化處理。
通常我們將其取值範圍處理爲0到1或者-1到1之間,使用的公式如下:
newValue = (oldValue-min) / (max-min)
我們要在 kNN.py 程序裏面加上一個歸一化的函數,爲了簡短起見,就只寫這一段。
def autoNorm(dataSet):
minVals = dataSet.min(0) # 每列最小變量,參數 0 使得函數可以從列中選取最小值
maxVals = dataSet.max(0) # 每列最大變量
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet)) # 照着 dataSet 的樣子做一個 0 矩陣
m= dataSet.shape[0] # shape[0] 指的是矩陣第一維長度
normDataSet = dataSet - tile(minVals, (m,1)) # tile(A,n),功能是將數組A重複n次,構成一個新的數組
normDataSet = normDataSet/tile(ranges, (m,1))
return normDataSet, ranges, minVals
特徵值矩陣有 1000 *3 個值,而 minVals 和 range 的值都爲 1*3 ,爲此我們使用 tile() 函數將變量內容複製成輸入舉證同樣大小的矩陣。另外這裏的 / 是具體特徵值相除,而 NumPy 的矩陣除法是 linalg.solve(matA,matB) 。
現在執行 autoNorm 試一試,我們在 usekNN.py 裏面加一點
# usekNN.py
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
print '**********'
reload(kNN)
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print normMat
print '**********'
print ranges
print '**********'
print minVals
運行結果就不發了,反正正常...
=====================================================================================
2.2.4 測試算法
這裏主要是測試分類器效果,因此要寫測試代碼:
def datingClassTest():
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs): # 重複次數
classifierResult = classify0(normMat[i,:], normMat[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]):errorCount += 1.0 # 如果和正確答案不相等
print "the total error rate is: %f" % (errorCount/float(numTestVecs)) # 寫出正確率
接下來在 usekNN.py 裏面加上
reload(kNN)
kNN.datingClassTest()
這樣運行就知道錯誤率了,我運行出來的錯誤率是 5% ,並不算高。
=====================================================================================
2.2.5 使用算法
通過該程序可以在約會網站上面找到某個人輸入他的信息,程序會給出預測。
kNN.py 添加代碼如下:
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
percentTats = float(raw_input(\
"percentage of time spent playing video games?"))
ffMiles = float(raw_input("frequent flier miles earned per year?"))
iceCream = float(raw_input("liters of ice cream consumed per year?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = classify0((inArr-\
minVals)/ranges, normMat, datingLabels, 3)
print "You will probably like this person: ",\
resultList[classifierResult - 1]
然後 usekNN.py 添加執行:
print '**********'
reload(kNN)
kNN.classifyPerson()
結果:
===============================================================================================================
最後給一遍完整代碼,總共三個文件。
kNN.py:
#-*- coding:utf-8 -*-
from numpy import * # 科學計算包
import operator # 運算符模塊
def createDataSet():
group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group, labels
def classify0(inX, dataSet, labels, k):
# 距離計算
dataSetSize = dataSet.shape[0] # .shape 讀取矩陣的長度
diffMat = tile(inX,(dataSetSize,1)) - dataSet
# tile(A,n),功能是將數組A重複n次,構成一個新的數組
sqDiffMat = diffMat ** 2 # **就是乘方
sqDistances = sqDiffMat.sum(axis = 1)
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort() # argsort函數返回的是數組值從小到大的索引值
classCount = {}
# 選擇距離最小的 K 個點
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#dict.get(key, default=None) key在字典中查找,在key不存在的情況下返回值None。
# 排序 sorted 函數詳見 http://www.cnblogs.com/sysu-blackbear/p/3283993.html
sortedClassCount = sorted(classCount.iteritems(), # iteritems以迭代器對象,返回鍵值對
key = operator.itemgetter(1), reverse = True)
# itemgetter(1) 使用元組的第二個元素對列表排序
# itemgetter(0) 使用元組的第一個元素對列表排序
return sortedClassCount[0][0]
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 文件的行數
returnMat = zeros((numberOfLines, 3)) # 創建返回的 NumPy 矩陣
# 創建給定類型的矩陣,並初始化爲0。zeros((A,B)),創建一個A行,B列的0矩陣
classLabelVector = []
index = 0
for line in arrayOLines: # 解析文件數據列表
line = line.strip() # strip() 方法用於移除字符串頭尾指定的字符(這裏是截掉回車字符)
listFromLine = line.split('\t') # tab 字符
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
def autoNorm(dataSet):
minVals = dataSet.min(0) # 每列最小變量,參數 0 使得函數可以從列中選取最小值
maxVals = dataSet.max(0) # 每列最大變量
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet)) # 照着 dataSet 的樣子做一個 0 矩陣
m= dataSet.shape[0] # shape[0] 指的是矩陣第一維長度
normDataSet = dataSet - tile(minVals, (m,1)) # tile(A,n),功能是將數組A重複n次,構成一個新的數組
normDataSet = normDataSet/tile(ranges, (m,1))
return normDataSet, ranges, minVals
def datingClassTest():
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs): # 重複次數
classifierResult = classify0(normMat[i,:], normMat[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]):errorCount += 1.0 # 如果和正確答案不相等
print "the total error rate is: %f" % (errorCount/float(numTestVecs)) # 寫出正確率
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
percentTats = float(raw_input(\
"percentage of time spent playing video games?"))
ffMiles = float(raw_input("frequent flier miles earned per year?"))
iceCream = float(raw_input("liters of ice cream consumed per year?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = classify0((inArr-\
minVals)/ranges, normMat, datingLabels, 3)
print "You will probably like this person: ",\
resultList[classifierResult - 1]
usekNN.py:
# usekNN.py
import kNN
group,labels = kNN.createDataSet()
print group
print '**********'
print labels
print '**********'
print kNN.classify0([0,0],group,labels,3)
print '**********'
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
print datingDataMat
print '**********'
print datingLabels[0:20]
print '**********'
reload(kNN)
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print normMat
print '**********'
print ranges
print '**********'
print minVals
print '**********'
reload(kNN)
kNN.datingClassTest()
print '**********'
reload(kNN)
kNN.classifyPerson()
plot.py:
# -*- coding:utf-8 -*-
import matplotlib
import matplotlib.pyplot as plt
import kNN
import usekNN
from numpy import * # 沒導入會報錯
fig = plt.figure()
ax = fig.add_subplot(111)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')
ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
# datingDataMat[:,1], datingDataMat[:,2] 應該指的是二三列?
# ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.xlabel('Air Miles')
plt.ylabel('Video Games')
plt.show()