1. 程式人生 > >機器學習演算法--KNN近鄰分類演算法

機器學習演算法--KNN近鄰分類演算法

KNN近鄰分類演算法

演算法思想:

       存在一個樣本資料集合,也稱為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一資料與所屬分類對應的關係。輸入沒有標籤的資料後,將新資料中的每個特徵與樣本集中資料對應的特徵進行比較,提取出樣本集中特徵最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料集中前k個最相似的資料,這就是k近鄰演算法中k的出處,通常k是不大於20的整數。最後選擇k個最相似資料中出現次數最多的分類作為新資料的分類。

給定一組樣本資料以及分類資訊,給定一組新樣本,求出新樣本的類別情況。

演算法虛擬碼:

對未知類別屬性的資料集中每個點依次執行以下操作:

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

1.距離計算

        當給定一組樣本資料時,當樣本的特徵屬性是有序屬性,可以直接進行數值計算來度量兩個樣本之間的距離。如果樣本屬性是有序的可以度量的,把樣本抽象成空間的點,可以採用歐式距離來度量點與點之間的距離。

對於一個函式dist,若其表示距離度量,則應滿足一下性質:

非負性:dist(xi, xj)>=0

同一性:dist(xi, xj)=0,當且僅當xi=xj

對稱性:dist(xi,xj)=dist(xj,xi)

直遞性:dist(xi,xj)<=dist(xi,xk)+dist(xk,xj)

對於樣本Xi=(xi1,xi2...xin),Xj=(xj1,xj2...xjn),常用距離閔科夫斯基距離:

當p=2時,即歐式距離:

當p=1時,即曼哈頓距離:

對於無序屬性,不能直接在屬性值上進行距離計算,可採用VDM方式:

mua  屬性u上取值為a大的樣本數   muai  第i個樣本簇在屬性u上取值為a的樣本數,k為總簇數

假定有nc個有序屬性  n-nc個無序屬性

當樣本中屬性權重不同是,可以採用加權距離:

在這裡,為了便於計算,採用歐式距離進行計算

2.資料處理

以機器學習實戰樣例資料為準,

對於一個人的分類有不喜歡的人1,魅力一般的人2,極具魅力的人3。

主要與以下因素有關:每年飛行里程數  玩遊戲所耗時間比  每週消費冰淇淋數

將資料檔案中資料處理,分別讀取到特徵屬性數值矩陣以及類別矩陣,前三列為特徵屬性數值  最後一列為類別數值

def file2matrix(filename):
    #開啟檔案
	fr = open(filename)
	#獲取資料的總行數
	numberOfLines = len(fr.readlines())
	#生成一個[numberOfLines][3]二維矩陣存放特徵屬性值
	returnMat = zeros((numberOfLines, 3))
	#分類屬性存放
	classLabelVector = []
	fr = open(filename)
	index = 0
	#遍歷每一行
	for line in fr.readlines():
		#去掉回車
		line = line.strip()
		#以tab進行切分
		listFromLine = line.split('\t')
		#前三個元素複製到特徵資料矩陣
		returnMat[index,:] = listFromLine[0:3]
		#最後一個元素放入類別矩陣
		classLabelVector.append(int(listFromLine[-1]))
		index += 1
	return returnMat, classLabelVector

3.分析資料

使用Matplotlib建立原始資料散點圖  ,觀察類別資訊

檢視python已經安裝module

安裝matplotlib

python -m pip install matplotlib

以特徵屬性矩陣二三列顯示如下:

重新載入kNN

>>> import kNN  
>>> from numpy import *   
>>> import matplotlib   
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> mat,lab = kNN.file2matrix('datingTestSet.txt')
>>> ax.scatter(mat[:,1], mat[:,2], 15.0*array(map(int,lab)),15.0*array(map(int,lab))) 
>>> plt.show()

第二三列以及第一二列的顯示結果,可知資料具有明顯的分類概念

                 

4.歸一化特徵屬性矩陣

歸一化屬性值,消除不同量綱對於結果的影響,常見的歸一化方法有:

4.1 min-max標準化(Min-max normalization)

也叫離差標準化,是對原始資料的線性變換,使結果落到[0,1]區間,轉換函式如下:

其中max為樣本資料的最大值,min為樣本資料的最小值。

def Normalization(x):     return [(float(i)-min(x))/float(max(x)-min(x)) for i in x]

如果想要將資料對映到[-1,1],則將公式換成:

x∗=x−xmeanxmax−xmin  x_mean表示資料的均值。

def Normalization2(x):     return [(float(i)-np.mean(x))/(max(x)-min(x)) for i in x]

這種方法有一個缺陷就是當有新資料加入時,可能導致max和min的變化,需要重新定義。

4.2 log函式轉換

通過以10為底的log函式轉換的方法同樣可以實現歸一下,具體方法如下:

看了下網上很多介紹都是x*=log10(x),其實是有問題的,這個結果並非一定落到[0,1]區間上,應該還要除以log10(max),max為樣本資料最大值,並且所有的資料都要大於等於1。

4.3 atan函式轉換

用反正切函式也可以實現資料的歸一化。

使用這個方法需要注意的是如果想對映的區間為[0,1],則資料都應該大於等於0,小於0的資料將被對映到[-1,0]區間上,而並非所有資料標準化的結果都對映到[0,1]區間上。

4.4 z-score 標準化(zero-mean normalization)

最常見的標準化方法就是Z標準化,也是SPSS中最為常用的標準化方法,spss預設的標準化方法就是z-score標準化。

也叫標準差標準化,這種方法給予原始資料的均值(mean)和標準差(standard deviation)進行資料的標準化。

經過處理的資料符合標準正態分佈,即均值為0,標準差為1,其轉化函式為:

x∗=x−μσ 其中μ為所有樣本資料的均值,σ為所有樣本資料的標準差。

z-score標準化方法適用於屬性A的最大值和最小值未知的情況,或有超出取值範圍的離群資料的情況。

標準化的公式很簡單,步驟如下

  1.求出各變數(指標)的算術平均值(數學期望)xi和標準差si ;   2.進行標準化處理:   zij=(xij-xi)/si   其中:zij為標準化後的變數值;xij為實際變數值。   3.將逆指標前的正負號對調。   標準化後的變數值圍繞0上下波動,大於0說明高於平均水平,小於0說明低於平均水平。

def z_score(x, axis):
    x = np.array(x).astype(float)
    xr = np.rollaxis(x, axis=axis)
    xr -= np.mean(x, axis=axis)
    xr /= np.std(x, axis=axis)
    # print(x)
    return x

這裡採用min-max歸一化方法

def autoNorm(dataSet):
	#取出每一列的最小值 1x3
    minVals = dataSet.min(0)
	#取出每一列的最大值
    maxVals = dataSet.max(0)
	#每一列的最大和最小之差
    ranges = maxVals - minVals
	#生成歸一化陣列
    normDataSet = zeros(shape(dataSet))
	#行數
    m = dataSet.shape[0]
	#擴充minVals矩陣為mx3,以原先1x3複製m行
    normDataSet = dataSet - tile(minVals, (m,1))
	#進行矩陣對應位置相除歸一化
    normDataSet = normDataSet/tile(ranges, (m,1))   #element wise divide
    return normDataSet, ranges, minVals

5.KNN演算法實現

        計算每個待分類樣本與已分類樣本的距離,排序,選取最小的k個樣本,統計其中每種類別的出現次數排序,返回出現次數最多的類別作為待分類樣本的類別

具體實現如下所示:

def classify0(inX, dataSet, labels, k):
	#樣本資料矩陣的行數
    dataSetSize = dataSet.shape[0]
	#把待計算行向量複製成dataSetSize,與樣本資料矩陣相減
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
	#計算矩陣的平方
    sqDiffMat = diffMat**2
	#矩陣每一行相加,組成一個dataSetSize維陣列
    sqDistances = sqDiffMat.sum(axis=1)
	#開平方,計算距離
    distances = sqDistances**0.5
	#將distances中的元素從小到大排列,提取其對應的index(索引),然後輸出到sortedDistIndicies
    sortedDistIndicies = distances.argsort()   
	#定義一個字典  key 類別  value 該類別總次數
    classCount={}
    #計算最近的k個樣本類別出現的次數	
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
		#不存在對應key返回0
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
	#按照第二個元素降序排列
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

python3  dict.items方法,而非dict.iteritems

其中

1.shape[0]表示矩陣行數

2.tile函式:

tile(a,x):           結果是一維陣列,x是控制a重複次數 tile(a,(x,y)):   結果是一個二維矩陣,其中行數為x,列數是一維陣列a的長度和y的乘積 tile(a,(x,y,z)):   結果是一個三維矩陣,其中矩陣的行數為x,矩陣的列數為y,而z表示矩陣每個單元格里a重複的次數

3.**表示次方

4.argsort() 從小到大排序  返回對應索引

5.sorted函式:sorted 可以對所有可迭代的物件進行排序操作

sorted(iterable[, cmp[, key[, reverse]]])

引數說明:

  • iterable -- 可迭代物件。
  • cmp -- 比較的函式,這個具有兩個引數,引數的值都是從可迭代物件中取出,此函式必須遵守的規則為,大於則返回1,小於則返回-1,等於則返回0。
  • key -- 主要是用來進行比較的元素,只有一個引數,具體的函式的引數就是取自於可迭代物件中,指定可迭代物件中的一個元素來進行排序。
  • reverse -- 排序規則,reverse = True 降序 , reverse = False 升序(預設)

返回排序之後的物件列表。

6.測試演算法

給定資料集,一部分用於作為樣本資料,一部分作為待測試資料,測試比例為0.1。程式碼如下:

def datingClassTest():
    #測試資料的比例
    hoRatio = 0.10      #hold out 10%
	#從檔案中讀取資料
    datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')       #load data setfrom file
	#歸一化處理資料
    normMat, ranges, minVals = autoNorm(datingDataMat)
	#樣本總數
    m = normMat.shape[0]
	#測試樣本的數量
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
	    #前1--numTestVecs作為待分類樣本   numTestVecs--m作為標準樣本資料
        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)))
    print (errorCount)

結果如下所示:

7.使用例項--手寫識別系統

trainingDigits中存放樣本資料   testDigits存放測試資料,其中數字8示例如下:

               

每個數字由32x32二維陣列來表示

第一步進行資料處理,將32x32陣列讀取到一個1x1024的一維陣列中,便於進行計算。

def img2vector(filename):
    #產生一個1024陣列
    returnVect = zeros((1,1024))
    fr = open(filename)
	#讀取每一行
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
		    #進行座標變換  第i行第j個元素  放入到32*i+j一維陣列中
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

進行訓練和測試:

載入訓練集,處理訓練集資料,產生訓練集資料矩陣以及類別矩陣

載入測試集,對於每個測試樣本利用KNN演算法計算類別結果,並與標準結果進行比較,統計分類失敗的個數

def handwritingClassTest():
    hwLabels = []
	#載入訓練集
    trainingFileList = listdir('trainingDigits')           #load the training set
	#訓練樣本總數
    m = len(trainingFileList)
	#存放訓練樣本總數矩陣mx1024
    trainingMat = zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]     #take off .txt
        classNumStr = int(fileStr.split('_')[0])
		#取出該檔案資料的類別
        hwLabels.append(classNumStr)
		#將資料放入到特徵值資料矩陣
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
    testFileList = listdir('testDigits')        #iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]     #take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print ("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print ("\nthe total number of errors is: %d" % errorCount)
    print ("\nthe total error rate is: %f" % (errorCount/float(mTest)))

8.總結

KNN演算法屬於監督學習中的分類,KNN沒有顯示的訓練過程,它是“懶惰學習”的代表,它在訓練階段只是把資料儲存下來,訓練時間開銷為0,等收到測試樣本後進行處理。

優點: 精度高  對異常值不敏感  無資料假定輸入。

缺點:計算複雜度,空間複雜度很高。無法給出任何資料的基礎結構資訊,無法知曉平均例項和典型例項的特徵。