天天看點

k-近鄰算法 From Machine Learning

Python 3.6 WIN10環境下,PyCharm IDE寫代碼,前半部分(電影分類問題)使用cmd執行資料輸入,後半部分(約會網站問題開始)直接在IDE的Console區域執行資料輸入;path環境路徑使用aconda,暫時發現其他的環境路徑遇到matplotlib無法安裝成功的問題:

k-近鄰算法思想如下:

1.計算已知類别資料集中的點與目前點之間的距離

2.按照距離遞增次序排序

3.選取與目前點距離最小的k個點

4.确定前k個點所在類别的出現頻率

5.傳回前k個點出現頻率最高的類别作為目前點的預測分類

主要運用到高中數學知識,求歐幾裡得空間兩點距離:

k-近鄰算法 From Machine Learning

建立kNN.py檔案,cmd環境需要cd到kNN.py所在目錄。

1.電影分類問題,代碼如下:

①收集資料:可使用任何方法

②準備資料:距離計算所需要的數值,最好是結構化的資料格式

③分析資料:可使用任何方法

④訓練算法:此步驟不适用于k-近鄰算法

⑤測試算法:計算錯誤率

⑥使用算法:首先需要輸入樣本資料和結構化的輸出結果,然後運作k-近鄰算法判定輸入資料分别屬于哪個分類,最後應用對計算出的分類執行後續的處理。

首先建立資料集:

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
           

其cmd對應執行指令如下:

>>> group,labels = kNN.createDataSet()
>>> group
array([[ 1. ,  1.1],
       [ 1. ,  1. ],
       [ 0. ,  0. ],
       [ 0. ,  0.1]])
>>> labels
['A', 'A', 'B', 'B']
           

分類方法,在cmd中調用執行:inX-要分類的輸入量; dataSet-輸入的訓練樣本集;labels-标簽向量;k-旋轉最近鄰居的數目

最後傳回的就是,該輸入量最可能所屬的元素标簽,即發生頻率最高的标簽。

def classify0(inX, dataSet, labels, k):
    #求出樣本集的行數,也就是labels标簽的數目
    dataSetSize = dataSet.shape[0]
    #構造輸入值和樣本集的內插補點矩陣
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
    #計算歐式距離
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    #求距離從小到大排序的序号
    sortedDistIndicies = distances.argsort()
    #對距離最小的k個點統計對應的樣本标簽
    classCount={}
    for i in range(k): #選擇距離最小的k個點
        #取第i+1鄰近的樣本對應的類别标簽
        voteIlabel = labels[sortedDistIndicies[i]]
        #以标簽為key,标簽出現的次數為value将統計到的标簽及出現次數寫進字典
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #對字典按value從大到小排序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True) #排序
    #傳回排序後字典中最大value對應的key
    return sortedClassCount[0][0]
           

其對應的cmd指令執行操作:

>>> kNN.classify0([0,0], group, labels, 3)
'B'
>>> kNN.classify0([0,1], group, labels, 3)
'B'
>>> kNN.classify0([1,1.1], group, labels, 3)
'A'
           

2.約會網站問題

解析已知樣本資料,樣本資料包含特征值和目标值。将未知對象(由特征值定義)歸類(目标值),三類:1.不喜歡的人 2.魅力一般的人 3.極具魅力的人

流程如下:

①收集資料:提供文本檔案datingTestSet.txt

②準備資料:使用Python解析文本檔案

③分析資料:使用Matplotlib畫二維擴散圖

④訓練算法:此步驟不适用于k-近鄰算法

⑤測試算法:使用部分資料作為測試樣本。

    測試樣本和非測試樣本的差別在于:測試樣本時已經完成分類的資料,如果預測分類與實際類别不同,則标記為一個錯誤。

⑥使用算法:産生簡單的指令行程式,然後可輸入一些特征資料以判斷對方是否為自己喜歡的類型

代碼如下:

def file2matrix(filename):
    #打開檔案
    fr = open(filename)
    #得到檔案的行數
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #建立以零填充的矩陣,為了簡化處理,将該矩陣的另一次元設定為固定值3,可按照自己的實際需求增加相應的代碼以适應變化的輸入值
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()#截取掉所有的回車字元
        listFromLine = line.split('\t') #然後使用tab字元\t将上一步得到的整行資料分割成一個元素清單
        returnMat[index,:] = listFromLine[0:3] #選取前3個元素,将它們存儲到特征矩陣中
        if(listFromLine[-1] == 'largeDoses'): #Python語言可使用索引值-1表示清單中的最後一列元素,利用該負索引,可友善地将清單的最後一列存儲到向量classLabelVector中
            classLabelVector.append(3)#listFromLine[-1] = '3' #為之後的使用該算法來判斷是否喜歡一個人時而改
        elif (listFromLine[-1] == 'smallDoses'):
            classLabelVector.append(2)#listFromLine[-1] = '2' #為之後的使用該算法來判斷是否喜歡一個人時而改
        else:
            classLabelVector.append(1)#listFromLine[-1] = '1' #為之後的使用該算法來判斷是否喜歡一個人時而改
        #classLabelVector.append(float(listFromLine[-1]))#必須明确地通知解釋器,告訴它清單中存儲的元素值為整型,否則Python語言會将這些元素當做字元串處理
        index += 1

    return returnMat,classLabelVector
           

cmd下指令如下,包括使用matplotlib圖像化:

>>> import kNN
>>> from numpy import *
>>> datingDataMat,datingLabels = kNN.file2matrix('datingTestSet.txt')
>>> import matplotlib
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
<matplotlib.collections.PathCollection object at 0x000002B853F7F860>
>>> plt.show()
           

若報錯:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'array' is not defined
           

則添加:

>>>from numpy import *
           

效果如下,帶有樣本分類标簽的約會資料散點圖,雖然能夠比較容易地區分資料點從屬類别,但依然很難根據這張圖得出結論性資訊:

k-近鄰算法 From Machine Learning

将資料列1,2改為0,1:

ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*array(datingLabels), 15.0*array(datingLabels))
           

改為:

ax.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*array(datingLabels), 15.0*array(datingLabels))
           

效果如下,每年擷取的飛行常客裡程數與玩視訊遊戲所占百分比的約會資料散點圖。約會資料有三個特征,通過圖中展示的兩個特征更容易區分資料點從屬的類别:

k-近鄰算法 From Machine Learning

歸一化處理:

由歐式距離公式可知,每年獲得的飛行常客裡程數,對計算結果影響最大。而一般認為這三種特征值應該是同等重要的,是以作為三個等權重的特征之一,飛行常客裡程數不應該如此嚴重地影響到計算結果,是以要對數值進行歸一化處理,如将取值範圍處理為0到1或-1到1之間。下面的公式可以将任意取值範圍的特征值轉化為0到1區間内的值:

newValue = (oldValue - min)/(max - min)

其中min和max分别是資料集中的最小特征值和最大特征值。雖然改變數值取值範圍增加了分類器的複雜度,但為了得到準确結果,必須這麼做。是以就需要再增加一個新函數autoNorm()來将數字特征值轉化為0到1的區間

代碼如下:

def autoNorm(dataSet):
    minVals = dataSet.min(0) #每列最小值
    maxVals = dataSet.max(0) #每列最大值
    ranges = maxVals - minVals #函數計算可能的取值範圍
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - tile(minVals, (m,1)) #tile将變量内容複制成輸入矩陣同樣大小的矩陣
    normDataSet = normDataSet/tile(ranges, (m,1)) #特征值相除,為了歸一化特征值,必須使用目前值減去最小值,然後除以取值範圍;在某些數值處理軟體包,/可能意味着矩陣除法,NumPy庫中,
    return normDataSet,ranges,minVals            #矩陣除法需要使用函數linalg.solve(matA,matB)
           

cmd:

>>> import kNN
>>> datingDataMat,datingLabels=kNN.file2matrix('datingTestSet.txt')
>>> normMat,ranges,minVals = kNN.autoNorm(datingDataMat)
>>> normMat
array([[ 0.44832535,  0.39805139,  0.56233353],
       [ 0.15873259,  0.34195467,  0.98724416],
       [ 0.28542943,  0.06892523,  0.47449629],
       ...,
       [ 0.29115949,  0.50910294,  0.51079493],
       [ 0.52711097,  0.43665451,  0.4290048 ],
       [ 0.47940793,  0.3768091 ,  0.78571804]])
>>> ranges
array([  9.12730000e+04,   2.09193490e+01,   1.69436100e+00])
>>> minVals
array([ 0.      ,  0.      ,  0.001156])
           

測試算法,驗證分類器:

測試分類器,若分類器的正确率滿足要求,則可使用該軟體來處理約會網站提供的約會名單了。機器學習算法一個很重要的工作就是評估算法的正确率,通常隻提供已有資料的90%作為訓練樣本來訓練分類器,而使用其餘的10%資料去測試分類器,檢測分類器的正确率。需注意的是,10%的測試資料應該是随機選擇的,由于datingTestSet.txt提供的資料并沒有按照特定目的來排序,是以可随意選擇10%資料而影響其随機性。

要測試分類器效果,在檔案中再添加函數datingClassTest,該函數是自包含的,可在任何時候在Python運作環境中使用該函數測試分類器效果,代碼如下:

def datingClassTest():
    hoRatio = 0.1 #前10%的資料作為測試資料集,後90%資料作為訓練資料集
    #從檔案中讀取資料并将其轉換為歸一化特征值
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    #計算測試向量的數量,決定了normMat向量中哪些資料用于測試,哪些資料用于分類器的訓練樣本
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        #将測試資料和訓練資料輸入到分類器中
        #normMat[i,:] 取出第i行的所有資料
        #normMat[numTestVecs:m,:]取出numTestVecs之後到m的每行資料
        #datingLabels[numTestVecs:m]取出numTestVecs之後到m的每行的标簽
        #k值為3
        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]))
        #如果錯誤不一緻,則錯誤數加1
        if(classifierResult != datingLabels[i]):errorCount += 1.0
    #計算出錯誤率并輸出結果
    print("the total error rate is:%f"%(errorCount/float(numTestVecs)))
           

cmd,錯誤率為5%:

>>> import kNN
>>> from numpy import *
>>> kNN.datingClassTest()
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 1
the total error rate is:0.050000
           

其中,若改變hoRatio的值為0.01,也就是前1%的資料作為測試資料集,後99%的資料作為訓練資料集,可看到,測試資料樣本為10個,同時錯誤率也變為了0%:

>>> import kNN
>>> from numpy import *
>>> kNN.datingClassTest()
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 3
the total error rate is:0.000000
           

若hoRation改為0.2,也就是前20%資料作為測試資料集,後80%作為樣本資料集,則可看到錯誤率為8%。

最後使用該算法來判斷是否喜歡一個人:直接在PyCharm IDE中運作了,不再cmd中去運作了,再添加如下代碼:

def classifyPerson():
	#輸出結果
	resultList = ['不喜歡','有些喜歡','非常喜歡']
	#三維特征使用者輸入
	precentTats = float(input("玩視訊遊戲所耗時間百分比:"))
	ffMiles = float(input("每年獲得的飛行常客裡程數:"))
	iceCream = float(input("每周消費的冰激淋公升數:"))
	#打開的檔案名
	filename = "datingTestSet.txt"
	#打開并處理資料
	datingDataMat, datingLabels = file2matrix(filename)
	#訓練集歸一化
	normMat, ranges, minVals = autoNorm(datingDataMat)
	#生成NumPy數組,測試集
	inArr = np.array([ffMiles, precentTats, iceCream])
	#測試集歸一化
	norminArr = (inArr - minVals) / ranges
	#傳回分類結果
	classifierResult = classify0(norminArr, normMat, datingLabels, 3)
	#列印結果
	print("你可能%s這個人" % (resultList[classifierResult-1]))

if __name__ == '__main__':
    # datingClassTest()
    classifyPerson()
           

直接Run kNN.py,在Console區域輸出結果如下:

玩視訊遊戲所耗時間百分比:10
每年獲得的飛行常客裡程數:10000
每周消費的冰激淋公升數:0.5
你可能有些喜歡這個人
           

到此,約會網站的算法已基本完成,之後隻要具有某個人的三個特征值:玩視訊遊戲所耗時間百分比,每年獲得的飛行常客裡程數,每周消費的冰激淩公升數;然後将它們輸入該算法,即可判斷出對此人的喜歡程度。該模型所謂的喜歡程度的判斷标準完全基于之前的樣本訓練資料,是以這個資料選擇的前提也很重要。

整個完整代碼如下:

from numpy import *
import numpy as np
import operator

#分類器,k-近鄰算法
def classify0(inX, dataSet, labels, k):
    #求出樣本集的行數,也就是labels标簽的數目
    dataSetSize = dataSet.shape[0]
    #構造輸入值和樣本集的內插補點矩陣
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    #計算歐式距離
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    #求距離從小到大排序的序号
    sortedDistIndicies = distances.argsort()
    #對距離最小的k個點統計對應的樣本标簽
    classCount={}
    for i in range(k): #選擇距離最小的k個點
        #取第i+1鄰近的樣本對應的類别标簽
        voteIlabel = labels[sortedDistIndicies[i]]
        #以标簽為key,标簽出現的次數為value将統計到的标簽及出現次數寫進字典
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #對字典按value從大到小排序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True) #排序
    #傳回排序後字典中最大value對應的key
    return sortedClassCount[0][0]

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 file2matrix(filename):
    #打開檔案
    fr = open(filename)
    #得到檔案的行數
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #建立以零填充的矩陣,為了簡化處理,将該矩陣的另一次元設定為固定值3,可按照自己的實際需求增加相應的代碼以适應變化的輸入值
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()#截取掉所有的回車字元
        listFromLine = line.split('\t') #然後使用tab字元\t将上一步得到的整行資料分割成一個元素清單
        returnMat[index,:] = listFromLine[0:3] #選取前3個元素,将它們存儲到特征矩陣中
        if(listFromLine[-1] == 'largeDoses'): #Python語言可使用索引值-1表示清單中的最後一列元素,利用該負索引,可友善地将清單的最後一列存儲到向量classLabelVector中
            classLabelVector.append(3)# listFromLine[-1] = '3'
        elif (listFromLine[-1] == 'smallDoses'):
            classLabelVector.append(2)#listFromLine[-1] = '2'
        else:
            classLabelVector.append(1)#listFromLine[-1] = '1'
      #  classLabelVector.append(float(listFromLine[-1]))#必須明确地通知解釋器,告訴它清單中存儲的元素值為整型,否則Python語言會将這些元素當做字元串處理
        index += 1

    return returnMat,classLabelVector

#歸一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0) #每列最小值
    maxVals = dataSet.max(0) #每列最大值
    ranges = maxVals - minVals #函數計算可能的取值範圍
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m,1)) #tile将變量内容複制成輸入矩陣同樣大小的矩陣
    normDataSet = normDataSet/np.tile(ranges, (m,1)) #特征值相除,為了歸一化特征值,必須使用目前值減去最小值,然後除以取值範圍;在某些數值處理軟體包,/可能意味着矩陣除法,NumPy庫中,
    return normDataSet,ranges,minVals            #矩陣除法需要使用函數linalg.solve(matA,matB)

#測試算法:作為完整程式驗證分類器
def datingClassTest():
    hoRatio = 0.1
    #從檔案中讀取資料并将其轉換為歸一化特征值
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    #計算測試向量的數量,決定了normMat向量中哪些資料用于測試,哪些資料用于分類器的訓練樣本
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        #将測試資料和訓練資料輸入到分類器中
        #normMat[i,:] 取出第i行的所有資料
        #normMat[numTestVecs:m,:]取出numTestVecs之後到m的每行資料
        #datingLabels[numTestVecs:m]取出numTestVecs之後到m的每行的标簽
        #k值為3
        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]))
        #如果錯誤不一緻,則錯誤數加1
        if(classifierResult != datingLabels[i]):errorCount += 1.0
    #計算出錯誤率并輸出結果
    print("the total error rate is:%f"%(errorCount/float(numTestVecs)))

#預測函數:
"""
def classifyPerson():
    resultList = ['不喜歡', '有點喜歡', '非常喜歡']
    percentTats = float(input("玩視訊遊戲所耗時間百分比: "))
    ffMiles = float(input("每年獲得飛行常客裡程數: "))
    iceCream = float(input("每周消費冰激淩公升數: "))
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    inArr = np.array([percentTats, ffMiles, iceCream])
    norminArr = (inArr - minVals) / ranges
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)
    print("你可能對這個人:", resultList[classifierResult-1])
"""
def classifyPerson():
	#輸出結果
	resultList = ['不喜歡','有些喜歡','非常喜歡']
	#三維特征使用者輸入
	precentTats = float(input("玩視訊遊戲所耗時間百分比:"))
	ffMiles = float(input("每年獲得的飛行常客裡程數:"))
	iceCream = float(input("每周消費的冰激淋公升數:"))
	#打開的檔案名
	filename = "datingTestSet.txt"
	#打開并處理資料
	datingDataMat, datingLabels = file2matrix(filename)
	#訓練集歸一化
	normMat, ranges, minVals = autoNorm(datingDataMat)
	#生成NumPy數組,測試集
	inArr = np.array([ffMiles, precentTats, iceCream])
	#測試集歸一化
	norminArr = (inArr - minVals) / ranges
	#傳回分類結果
	classifierResult = classify0(norminArr, normMat, datingLabels, 3)
	#列印結果
	print("你可能%s這個人" % (resultList[classifierResult-1]))

if __name__ == '__main__':
    # datingClassTest()
    classifyPerson()
           

手寫識别系統:

from numpy import *
import numpy as np
import operator
from os import listdir
import time

#分類器,k-近鄰算法
def classify0(inX, dataSet, labels, k):
    #求出樣本集的行數,也就是labels标簽的數目
    dataSetSize = dataSet.shape[0]
    #構造輸入值和樣本集的內插補點矩陣
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    #計算歐式距離
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    #求距離從小到大排序的序号
    sortedDistIndicies = distances.argsort()
    #對距離最小的k個點統計對應的樣本标簽
    classCount={}
    for i in range(k): #選擇距離最小的k個點
        #取第i+1鄰近的樣本對應的類别标簽
        voteIlabel = labels[sortedDistIndicies[i]]
        #以标簽為key,标簽出現的次數為value将統計到的标簽及出現次數寫進字典
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    #對字典按value從大到小排序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True) #排序
    #傳回排序後字典中最大value對應的key
    return sortedClassCount[0][0]

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 file2matrix(filename):
    #打開檔案
    fr = open(filename)
    #得到檔案的行數
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    #建立以零填充的矩陣,為了簡化處理,将該矩陣的另一次元設定為固定值3,可按照自己的實際需求增加相應的代碼以适應變化的輸入值
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()#截取掉所有的回車字元
        listFromLine = line.split('\t') #然後使用tab字元\t将上一步得到的整行資料分割成一個元素清單
        returnMat[index,:] = listFromLine[0:3] #選取前3個元素,将它們存儲到特征矩陣中
        if(listFromLine[-1] == 'largeDoses'): #Python語言可使用索引值-1表示清單中的最後一列元素,利用該負索引,可友善地将清單的最後一列存儲到向量classLabelVector中
            classLabelVector.append(3)# listFromLine[-1] = '3'
        elif (listFromLine[-1] == 'smallDoses'):
            classLabelVector.append(2)#listFromLine[-1] = '2'
        else:
            classLabelVector.append(1)#listFromLine[-1] = '1'
      #  classLabelVector.append(float(listFromLine[-1]))#必須明确地通知解釋器,告訴它清單中存儲的元素值為整型,否則Python語言會将這些元素當做字元串處理
        index += 1

    return returnMat,classLabelVector

#歸一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0) #每列最小值
    maxVals = dataSet.max(0) #每列最大值
    ranges = maxVals - minVals #函數計算可能的取值範圍
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m,1)) #tile将變量内容複制成輸入矩陣同樣大小的矩陣
    normDataSet = normDataSet/np.tile(ranges, (m,1)) #特征值相除,為了歸一化特征值,必須使用目前值減去最小值,然後除以取值範圍;在某些數值處理軟體包,/可能意味着矩陣除法,NumPy庫中,
    return normDataSet,ranges,minVals            #矩陣除法需要使用函數linalg.solve(matA,matB)

#測試算法:作為完整程式驗證分類器
def datingClassTest():
    hoRatio = 0.1
    #從檔案中讀取資料并将其轉換為歸一化特征值
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    #計算測試向量的數量,決定了normMat向量中哪些資料用于測試,哪些資料用于分類器的訓練樣本
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        #将測試資料和訓練資料輸入到分類器中
        #normMat[i,:] 取出第i行的所有資料
        #normMat[numTestVecs:m,:]取出numTestVecs之後到m的每行資料
        #datingLabels[numTestVecs:m]取出numTestVecs之後到m的每行的标簽
        #k值為3
        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]))
        #如果錯誤不一緻,則錯誤數加1
        if(classifierResult != datingLabels[i]):errorCount += 1.0
    #計算出錯誤率并輸出結果
    print("the total error rate is:%f"%(errorCount/float(numTestVecs)))

#預測函數:
"""
def classifyPerson():
    resultList = ['不喜歡', '有點喜歡', '非常喜歡']
    percentTats = float(input("玩視訊遊戲所耗時間百分比: "))
    ffMiles = float(input("每年獲得飛行常客裡程數: "))
    iceCream = float(input("每周消費冰激淩公升數: "))
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    normMat,ranges,minVals = autoNorm(datingDataMat)
    inArr = np.array([percentTats, ffMiles, iceCream])
    norminArr = (inArr - minVals) / ranges
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)
    print("你可能對這個人:", resultList[classifierResult-1])
"""
def classifyPerson():
	#輸出結果
	resultList = ['不喜歡','有些喜歡','非常喜歡']
	#三維特征使用者輸入
	precentTats = float(input("玩視訊遊戲所耗時間百分比:"))
	ffMiles = float(input("每年獲得的飛行常客裡程數:"))
	iceCream = float(input("每周消費的冰激淋公升數:"))
	#打開的檔案名
	filename = "datingTestSet.txt"
	#打開并處理資料
	datingDataMat, datingLabels = file2matrix(filename)
	#訓練集歸一化
	normMat, ranges, minVals = autoNorm(datingDataMat)
	#生成NumPy數組,測試集
	inArr = np.array([ffMiles, precentTats, iceCream])
	#測試集歸一化
	norminArr = (inArr - minVals) / ranges
	#傳回分類結果
	classifierResult = classify0(norminArr, normMat, datingLabels, 3)
	#列印結果
	print("你可能%s這個人" % (resultList[classifierResult-1]))

def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

#把一個32x32的二進制圖像矩陣轉換為1x1024的向量,就可使用前面的分類器處理數字圖像資訊。
def handwritingClassTest():
    cpu_start = time.time()
    print('start:%f' % cpu_start)
    hwLabels = []
    #擷取目錄内容
    trainingFileList = listdir('trainingDigits')
    m = len(trainingFileList)
    trainingMat = zeros([m,1024])
    for i in range(m):
        #從檔案名解析分類數字
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
    testFileList = listdir('testDigits')
    errorCount = 0.0
    mTest = len(testFileList)
    #循環讀出檔案的前32個字元值存儲在NumPy數組中,最後傳回數組
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileNameStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' %fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("分類器傳回值為:%d, 實際值為:%d" %(classifierResult, classNumStr))
        if(classifierResult != classNumStr): errorCount += 1.0
    print("\n錯誤總數為: %d" % errorCount)
    print("\n總錯誤率為: %f" %(errorCount/float(mTest)))
    cpu_end = time.time()
    print('end:%f' % cpu_end)
    print("total time: %f S" % (cpu_end - cpu_start))
if __name__ == '__main__':
    # datingClassTest()
    #classifyPerson()
   # testVector=img2vector('testDigits/0_13.txt')
    #print(testVector[0,0:31])
   # print(testVector[0,32:63])
    handwritingClassTest()
           

運作結果類似如下:

分類器傳回值為:9, 實際值為:9
分類器傳回值為:9, 實際值為:9
分類器傳回值為:9, 實際值為:9

錯誤總數為: 10

總錯誤率為: 0.010571
end:1516780726.425686
total time: 33.872470 S
           

錯誤個數10個,錯誤率1.05%,耗時太長33.8s,這個還可以用其他方法改進。修改變量k值,函數handwritingClassTest随機選取訓練樣本、改變訓練樣本的數目,都會對k-近鄰算法的錯誤率産生影響。

執行效率不高,k決策樹就是k-近鄰算法的優化版,可節省大量的計算開銷。

testDigits以及trainingDigits

datingTestSet.txt如下:

第1列為每年獲得的飛行常客裡程數,第2列為玩視訊遊戲所耗時間百分比,第3列為每周消費的冰淇淋公升數,第4列為目标值:非常喜歡-largeDoses 有點喜歡-smallDoses 不喜歡-didntLike:

40920	8.326976	0.953952	largeDoses
14488	7.153469	1.673904	smallDoses
26052	1.441871	0.805124	didntLike
75136	13.147394	0.428964	didntLike
38344	1.669788	0.134296	didntLike
           

總結:k-近鄰算法是分類資料最簡單最有效的算法。本篇通過約會問題和手寫識别系統使用k-近鄰算法構造的分類器。k-近鄰算法是基于執行個體的學習,使用算法時需要有接近實際資料的訓練樣本資料。k-近鄰算法必須儲存全部資料集,若訓練資料集越大,則使用的存儲空間也會越大,此外,由于必須對資料集中的每個資料計算距離值,實際使用時會非常耗時,由上面的手寫識别系統可知。

k-近鄰算法的另一個缺陷是它無法給出如何資料的基礎結構資訊,是以也無法知曉平均執行個體樣本和典型是兩樣本具有什麼特征。在使用機率測量方法處理分類問題時,可解決該問題。

繼續閱讀