1.原理
給定一個訓練數據集,對新的輸入實例,在訓練數據集中找到與該實例最鄰近的k個實例,這k個實例的多數實例屬於某個類別,就把這個輸入實例分爲這個類。
算法:
輸入:訓練數據T=,其中,,i=1,2,3…N,測試實例x。
輸出:測試實例x所屬的類別y
(1)根究給定的距離度量,在訓練集T中找出與x最鄰近的k個點。
(2)計算這k個點的各個類別的樣本數,選取樣本數最多的類別y。
I爲指示函數,時I爲1,否則爲0.
重要的是K值的選取和點距離的計算
。
和k-means的差別
:就是畫一個圈,KNN是讓進來圈子裏的人變成自己人,Kmeans是讓原本在圈內的人歸成一類人。
2.距離計算
特徵空間中兩個實例點的距離是兩個實例點相似程度的放映。
和的距離定義爲:
- 當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.參考
機器學習實戰
一些博客,如有侵權,請聯繫。