本文總結內容參考於李航老師的《統計學習方法》及其配套課件
原文代碼作者:https://github.com/wzyonggege/statistical-learning-method
k近鄰法的輸入爲實例的特徵向量,對應於特徵空間的點;輸出爲實例 的類別,可以取多類。k近鄰法假設給定一個訓練數據集,其中的實例類別已定。分類 時,對新的實例,根據其k個最近鄰的訓練實例的類別,通過多數表決等方式進行預測。 因此,k近鄰法不具有顯式的學習過程。k近鄰法實際上利用訓練數據集對特徵向量空間進 行劃分,並作爲其分類的“模型”。
KNN算法的一般流程——
收集數據:可以使用任何方法
準備數據:距離計算所需要的數值,最後是結構化的數 據格式。
分析數據:可以使用任何方法
訓練算法: (此步驟kNN)中不適用
測試算法:計算錯誤率
使用算法:首先需要輸入樣本數據和結構化的輸出結果, 然後運行k-近鄰算法判定輸入數據分別屬於哪個分類, 最後應用對計算出的分類執行後續的處理。
- 模型
特徵空間中,對每個訓練實例點ix,距離該點比其他點更近的所有點組成一個區域, 叫作單元(cell)。每個訓練實例點擁有一個單元,所有訓練實例點的單元構成對特徵空 間的一個劃分。最近鄰法將實例ix的類iy作爲其單元中所有點的類標記(class label)。這 樣,每個單元的實例點的類別是確定的。
- 距離度量
- K值的選擇
如果選擇較小的K值:“學 習”的近似誤差(approximation error)會減小,但 “學習”的估計誤差(estimation error) 會增大;噪聲敏感 ;K值的減小就意味着整體模型變得複雜,容易發生過擬合.
如果選擇較大的K值, 減少學習的估計誤差,但缺點是學習的近似誤差會增大. K值的增大 就意味着整體的模型變得簡單.
- 分類決策規則
代碼在jupyter notebook中運行
import math
from itertools import combinations
'''
p = 1 曼哈頓距離
p = 2 歐氏距離
p = inf 閔式距離minkowski_distance
'''
#度量距離
def L(x, y, p =2):
if len(x) == len(y) and len(x) >1 :
sum = 0
for i in range(len(x)):
sum += math.pow(abs(x[i] - y[i]), p)
return math.pow(sum, 1/p)
else:
return 0
x1 = [1, 1]
x2 = [5, 1]
x3 = [4, 4]
for i in range(1, 5):
#format格式化函數,通過 {} 和 : 來代替以前的 %
#此時r爲一個字典,keys爲點與點,values爲度量距離
r = { '1-{}'.format(c):L(x1, c, p= i) for c in [x2, x3]}
#zip()將對象中的對應元素打包成一個個元組
print(min(zip(r.values(), r.keys())))
輸出結果爲:
(4.0, '1-[5, 1]')
(4.0, '1-[5, 1]')
(3.7797631496846193, '1-[4, 4]')
(3.5676213450081633, '1-[4, 4]')
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
iris = load_iris()
#矩陣數據表,將列名設爲iris的特徵
df = pd.DataFrame(iris.data,columns = iris.feature_names)
#加入一列爲分類標籤
df['label'] = iris.target
#重命名列名(需要將每個都列出)
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
df
得到150條數據
#將標籤爲0、1的兩種花,根據特徵爲長度和寬度打點表示
plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
#按行索引,取出第0列第1列和最後一列,即取出sepal長度、寬度和標籤
data = np.array(df.iloc[:100, [0, 1, -1]])
##X爲sepal length,sepal width y爲標籤
X, y = data[:,:-1], data[:,-1]
# train_test_split函數用於將矩陣隨機劃分爲訓練子集和測試子集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
class KNN:
def __init__(self, X_train, y_train, n_neighbors=3, p=2):
"""
parameter: n_neighbors 臨近點個數
parameter: p 距離度量
"""
self.n = n_neighbors
self.p = p
self.X_train = X_train
self.y_train = y_train
def predict(self, X):
# 取出n個點,放入空的列表,列表中存放預測點與訓練集點的距離及其對應標籤
knn_list = []
for i in range(self.n):
#np.linalg.norm 求範數
dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
knn_list.append((dist, self.y_train[i]))
#再取出訓練集剩下的點,然後與n_neighbor個點比較大叫,將距離大的點更新
#保證knn_list列表中的點是距離最小的點
for i in range(self.n, len(self.X_train)):
'''此處 max(num,key=lambda x: x[0])用法:
x:x[]字母可以隨意修改,求最大值方式按照中括號[]裏面的維度,
[0]按照第一維,
[1]按照第二維
'''
max_index = knn_list.index(max(knn_list, key=lambda x: x[0]))
dist = np.linalg.norm(X - self.X_train[i], ord=self.p)
#g更新最近鄰中距離比當前點遠的點
if knn_list[max_index][0] > dist:
knn_list[max_index] = (dist, self.y_train[i])
# 統計分類最多的點,確定預測數據的分類
knn = [k[-1] for k in knn_list]
#counter爲計數器,按照標籤計數
count_pairs = Counter(knn)
#排序
max_count = sorted(count_pairs, key=lambda x:x)[-1]
return max_count
#預測的正確率
def score(self, X_test, y_test):
right_count = 0
n = 10
for X, y in zip(X_test, y_test):
label = self.predict(X)
if label == y:
right_count += 1
return right_count / len(X_test)
clf = KNN(X_train, y_train)
clf.score(X_test, y_test)
#預測點
test_point = [6.0, 3.0]
#預測結果
print('Test Point: {}'.format(clf.predict(test_point)))
plt.scatter(df[:50]['sepal length'], df[:50]['sepal width'], label='0')
plt.scatter(df[50:100]['sepal length'], df[50:100]['sepal width'], label='1')
#打印預測點
plt.plot(test_point[0], test_point[1], 'bo', label='test_point')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
得到:Test Point: 1.0
可以從圖中看到預測點明顯分類正確
print(clf.score(X_test, y_test))
#結果爲1.0