k-近鄰算法(k-nearest neighbor)

1.原理

給定一個訓練數據集,對新的輸入實例,在訓練數據集中找到與該實例最鄰近的k個實例,這k個實例的多數實例屬於某個類別,就把這個輸入實例分爲這個類。

算法:

輸入:訓練數據T=(x(1),y(1)),(x(2),y(2)),...,(x(N),y(N))(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),...,(x^{(N)},y^{(N)}),其中,,i=1,2,3…N,測試實例x。
輸出:測試實例x所屬的類別y
(1)根究給定的距離度量,在訓練集T中找出與x最鄰近的k個點。
(2)計算這k個點的各個類別的樣本數,選取樣本數最多的類別y。
在這裏插入圖片描述
I爲指示函數,yi=ciy_i=c_i時I爲1,否則爲0.

重要的是K值的選取和點距離的計算
和k-means的差別:就是畫一個圈,KNN是讓進來圈子裏的人變成自己人,Kmeans是讓原本在圈內的人歸成一類人。

2.距離計算

特徵空間中兩個實例點的距離是兩個實例點相似程度的放映。
x(i)=(x1(i),y1(i)),(x2(i),y2(i)),...,(xn(i),yn(i))x^{(i)}=(x^{(i)}_1,y^{(i)}_1),(x_2^{(i)},y_2^{(i)}),...,(x_n^{(i)},y_n^{(i)})
x(j)=(x1(j),y1(j)),(x2(j),y2(j)),...,(xn(j),yn(j))x^{(j)}=(x^{(j)}_1,y^{(j)}_1),(x_2^{(j)},y_2^{(j)}),...,(x_n^{(j)},y_n^{(j)})
x(i)x^{(i)}x(j)x^{(j)}的距離定義爲:
在這裏插入圖片描述

  • 當p=2時,稱爲歐氏距離(Euclidean distance),即
    在這裏插入圖片描述
  • 當p=1時,稱爲曼哈頓距離(Manhattan distance),即
    在這裏插入圖片描述
  • 當p=無窮大時,它是各個座標距離的最大值,即

在這裏插入圖片描述

二維空間兩個點的歐式距離計算公式如下:
在這裏插入圖片描述
這個高中應該就有接觸到的了,其實就是計算(x1,y1)和(x2,y2)的距離。拓展到多維空間,則公式變成這樣:

在這裏插入圖片描述
KNN算法最簡單粗暴的就是將預測點與所有點距離進行計算,然後保存並排序,選出前面K個值看看哪些類別比較多。但其實也可以通過一些數據結構來輔助,比如最大堆,這裏就不多做介紹,有興趣可以百度最大堆相關數據結構的知識。

3.算法過程

圖中綠色的點就是我們要預測的那個點,假設K=3。那麼KNN算法就會找到與它距離最近的三個點(這裏用圓圈把它圈起來了),看看哪種類別多一些,比如這個例子中是藍色三角形多一些,新來的綠色點就歸類到藍三角了。
在這裏插入圖片描述

但是,當K=5的時候,判定就變成不一樣了。這次變成紅圓多一些,所以新來的綠點被歸類成紅圓。從這個例子中,我們就能看得出K的取值是很重要的。

在這裏插入圖片描述

4.K值得選擇

k值的選擇會對k近鄰法的結果產生重大影響。

  • 如果選擇較小的k值,就相當於用較小的鄰域中的訓練實例進行預測,“學習”的近似誤差(approximation error)會減小,只有與輸入實例較近的(相似的)訓練實例纔會對預測結果起作用。但缺點是“學習”的估計誤差(estimation error)會増大,預測結果會對近鄰的實例點非常敏感。如果鄰近的實例點恰巧是噪聲,預測就會出錯。換句話說,A值的減小就意味着整體模型變得複雜,容易發生過擬合

  • 如果選擇較大的k值,就相當於用較大鄰域中的訓練實例進行預測。其優點是可以減少學習的估計誤差。但缺點是學習的近似誤差會增大。這時與輸入實例較遠的(不相似的)訓練實例也會對預測起作用,使預測發生錯誤。k值的増大就意味着整體的模型變得簡單。

  • 如果k = N,那麼無論輸入實例是什麼,都將簡單地預測它屬於在訓練實例
中最多的類別。這時,模型過於簡單,完全忽略訓練實例中的大量有用信息,是不可取的。

在應用中,k值一般取一個比較小的數值。通常釆用交叉驗證法來選取最優的k值。或是逐一計算檢測。

估計誤差:度量預測結果與最優結果的相近程度
近似誤差:度量預測結果與最優誤差之間的相近程度

5.優缺點

優點:精度高、對異常值不敏感、 數據輸入假定。
缺點:計算複雜度高、空間複雜度高。
適用數據範圍:數值型和標稱型。

6.k近鄰的實現:kd樹

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

7.代碼

7.1.原理的實現

# coding: utf-8
# Author: shelley
# 2020/5/916:05
from numpy import *
import numpy as np
import operator
from os import listdir


def classify0(inX, dataSet, labels, k):
    """
    knn分類算法
    :param inX: (1,3)
    :param dataSet: (n,3)
    :param labels: (n,3)
    :param k: int, k個相似度樣本最大的樣本,這些樣本中相同標籤最大的爲測試樣本的標籤
    :return:測試樣本的標籤
    """
    dataSetSize = dataSet.shape[0]  # 樣本個數
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 測試樣本-訓練樣本
    sqDiffMat = diffMat ** 2  # 平方差
    sqDistances = sqDiffMat.sum(axis=1)  # 將每一行的3個特徵進行相加 sum(0)列相加,sum(1)行相加
    distances = sqDistances ** 0.5  # 開方
    sortedDistIndicies = distances.argsort()  # argsort函數返回的是distances值從小到大的--索引值
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    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):
    """
    讀取文件的數據
    :param filename: 文件名
    :return: 特徵矩陣[fea1,fea2,fea3](n*3),標籤[label](n*1)
    """
    fr = open(filename)
    numberOfLines = len(fr.readlines())  # 獲得文件的所有文本行
    returnMat = zeros((numberOfLines, 3))  # 定義存儲數據的矩陣
    classLabelVector = []  # 定義存儲標籤的矩陣
    fr = open(filename)
    index = 0  # 表示第幾個樣本
    for line in fr.readlines():
        line = line.strip()  # 去掉每一行首尾的空白符,例如'\n','\r','\t',' '
        listFromLine = line.split('\t')  # 每一行根據\t進行切片
        returnMat[index, :] = listFromLine[0:3]  # 前三列是特徵
        classLabelVector.append(int(listFromLine[-1]))  # 最後一列是標籤
        index += 1
    return returnMat, classLabelVector


def autoNorm(dataSet):
    """
    數據歸一化
    :param dataSet: 特徵矩陣[fea1,fea2,fea3](n*3)
    :return:歸一化的特徵矩陣,特徵取值範圍(1,3),特徵最小值(1,3)
    """
    minVals = dataSet.min(0)  # (1,3) 每一列的最小值,即一個特徵中所有樣本的最小值
    maxVals = dataSet.max(0)  # (1,3) dataSet.min(1),每一行的最小值
    ranges = maxVals - minVals  # 每個特徵的取值範圍
    m = dataSet.shape[0]  # 樣本個數
    #  原始值減去最小值(x-xmin)
    normDataSet = dataSet - np.tile(minVals, (m, 1))  # (n,3)複製n個minVals,
    # 差值處以最大值和最小值的差值(x-xmin)/(xmax-xmin)
    normDataSet = normDataSet / np.tile(ranges, (m, 1))  # element wise divide
    return normDataSet, ranges, minVals


def datingClassTest():
    """
    dating數據分類
    :return:
    """
    hoRatio = 0.1  # 保留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("knn獲得的標籤: %d, 真實標籤: %d" % (classifierResult, datingLabels[i]))

        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print ("錯誤率: %f %%" % (errorCount / float(numTestVecs)*100))

    print(errorCount)


def img2vector(filename):
    """

    :param filename:  文件名,有32行,一行32個字符
    :return:  文件裏的數據,(1,1024)
    """
    returnVect = zeros((1, 1024))  # 定義(1,1024)形狀的變量,32*32=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])  # 每行數據順序存儲在returnVect
    return returnVect


def handwritingClassTest():
    """
    手寫數字分類
    :return:
    """
    hwLabels = []
    trainingFileList = listdir('trainingDigits')  # 獲得trainingDigits下所有文件的路徑
    m = len(trainingFileList)  # 文件個數
    trainingMat = zeros((m, 1024))  # 樣本個數*特徵維度
    for i in range(m):
        fileNameStr = trainingFileList[i]  # 獲得其中一個文件名testDigits\0_0.txt
        fileStr = fileNameStr.split('.')[0]  # 去掉 .txt
        classNumStr = int(fileStr.split('_')[0])  # 按_進行切分,第一個字符串是類別
        hwLabels.append(classNumStr)
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)  # 獲得特徵矩陣
    testFileList = listdir('testDigits')  # 讀取測試數據
    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("分類結果爲: %d, 真實結果爲: %d" % (classifierResult, classNumStr))

        if (classifierResult != classNumStr): errorCount += 1.0
    print("\n分類錯誤的個數: %d" % errorCount)

    print("\n錯誤率: %f" % (errorCount / float(mTest)))


if __name__ == '__main__':
    # dating數據分類
    # datingClassTest()
    # 手寫數字分類
    handwritingClassTest()

7.2.sklearn的實現

KNeighborsClassifier(n_neighbors = 5,
weights=‘uniform’,
algorithm = ‘’,
leaf_size = ‘30’,
p = 2,
metric = ‘minkowski’,
metric_params = None,
n_jobs = None
)

(1)n_neighbors:這個值就是指 KNN 中的 “K”了。前面說到過,通過調整 K 值,算法會有不同的效果。
(2)weights(權重):最普遍的 KNN 算法無論距離如何,權重都一樣,但有時候我們想搞點特殊化,比如距離更近的點讓它更加重要。這時候就需要 weight 這個參數了,這個參數有三個可選參數的值,決定了如何分配權重。參數選項如下:
• ‘uniform’:不管遠近權重都一樣,就是最普通的 KNN 算法的形式。
• ‘distance’:權重和距離成反比,距離預測目標越近具有越高的權重。
• 自定義函數:自定義一個函數,根據輸入的座標值返回對應的權重,達到自定義權重的目的。
(3)algorithm:在 sklearn 中,要構建 KNN 模型有三種構建方式,1. 暴力法,就是直接計算距離存儲比較的那種放鬆。2. 使用 kd 樹構建 KNN 模型 3. 使用球樹構建。 其中暴力法適合數據較小的方式,否則效率會比較低。如果數據量比較大一般會選擇用 KD 樹構建 KNN 模型,而當 KD 樹也比較慢的時候,則可以試試球樹來構建 KNN。參數選項如下:
• ‘brute’ :蠻力實現
• ‘kd_tree’:KD 樹實現 KNN
• ‘ball_tree’:球樹實現 KNN
• ‘auto’: 默認參數,自動選擇合適的方法構建模型
不過當數據較小或比較稀疏時,無論選擇哪個最後都會使用 ‘brute’
(4)leaf_size:如果是選擇蠻力實現,那麼這個值是可以忽略的,當使用KD樹或球樹,它就是是停止建子樹的葉子節點數量的閾值。默認30,但如果數據量增多這個參數需要增大,否則速度過慢不說,還容易過擬合。
(5)p:和metric結合使用的,當metric參數是"minkowski"的時候,p=1爲曼哈頓距離, p=2爲歐式距離。默認爲p=2。
(6)metric:指定距離度量方法,一般都是使用歐式距離。
• ‘euclidean’ :歐式距離
• ‘manhattan’:曼哈頓距離
• ‘chebyshev’:切比雪夫距離
• ‘minkowski’: 閔可夫斯基距離,默認參數
(7)n_jobs:指定多少個CPU進行運算,默認是-1,也就是全部都算。

cross_val_score(estimator,
X,
y=None,
groups=None,
scoring=None,
cv=None,
n_jobs=1,
verbose=0,
fit_params=None,
pre_dispatch=‘2*n_jobs’)

(1)estimator : 分類對象,並有fit函數的實現,用來訓練數。
(2)X:用來訓練的數據
(3)y : 目標數據
(4)groups : array-like形狀 (n_samples,)組標籤,用來把數據分成訓練和測試集
(5)scoring : 計分的方式
(6)cv : 將數據分成的分數

# coding: utf-8
# Author: shelley
# 2020/5/1116:40

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap

from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn import neighbors

iris = load_iris()
x = iris.data
y = iris.target

# 確定k的值
k_range = range(1,31)
k_error = []
for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, x, y, cv=6, scoring='accuracy')
    k_error.append(1-scores.mean())
plt.plot(k_range, k_error)
plt.xlabel('value of k for knn')
plt.ylabel('error')
plt.show()

n_neighbors = 11
h = .02  # 網格中的步長

# 創建彩色的圖
cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#AAAAFF'])
cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#0000FF'])

# weights,兩種權重參數下KNN的效果圖
for weights in ['uniform', 'distance']:
    # 創建了一個knn分類器的實例,並擬合數據。
    clf = neighbors.KNeighborsClassifier(n_neighbors, weights=weights)
    clf.fit(x, y)

    # 繪製決策邊界。爲此,我們將爲每個分配一個顏色
    # 來繪製網格中的點 [x_min, x_max]x[y_min, y_max].
    x_min, x_max = x[:, 0].min() - 1, x[:, 0].max() + 1
    y_min, y_max = x[:, 1].min() - 1, x[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])

    # 將結果放入一個彩色圖中
    Z = Z.reshape(xx.shape)
    plt.figure()
    plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

    # 繪製訓練點
    plt.scatter(x[:, 0], x[:, 1], c=y, cmap=cmap_bold)
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.title("3-Class classification (k = %i, weights = '%s')"
              % (n_neighbors, weights))

plt.show()

8.代碼和數據

鏈接:https://pan.baidu.com/s/1QbeG8g_9JhVwJRhERViTMg
提取碼:80su

9.參考

機器學習實戰
一些博客,如有侵權,請聯繫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章