《統計學習方法》 第三章 k近鄰法總結及代碼實現

本文總結內容參考於李航老師的《統計學習方法》及其配套課件

原文代碼作者: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

 

 

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