1. 程式人生 > >2. k近鄰演算法

2. k近鄰演算法

一. k近鄰演算法基礎(knn)

  • knn演算法簡單的描述,就是新的資料 與 已知的多型別的資料 進行'距離'比較, 找出距離最近的k個數據
  • k個數據中, 佔比最大的是哪種型別資料, 就把這新的資料 歸類到這類資料中

建立測試資料

knn演算法實現


二. scikit-learn中機器學習演算法封裝

將前一節的knn演算法封裝成函式

import numpy as np
from math import sqrt
from collections import Counter


def kNN_classify(k, X_train, y_train, x):

    assert 1 <= k <= X_train.shape[0], "k must be valid"
    assert X_train.shape[0] == y_train.shape[0], \
        "the size of X_train must equal to the size of y_train"
    assert X_train.shape[1] == x.shape[0], \
        "the feature number of x must be equal to X_train"

    distances = [sqrt(np.sum((x_train - x)**2)) for x_train in X_train]
    nearest = np.argsort(distances)

    topK_y = [y_train[i] for i in nearest[:k]]
    votes = Counter(topK_y)

    return votes.most_common(1)[0][0]

  • nootbook中呼叫它 

疑問: 前一節的knn演算法運用並沒有用法模型啊?

  • k近鄰演算法是非常特殊的, 可以被認為是沒有模型的演算法
  • 為了和其他演算法同一, 可以認為訓練資料集就是模型本身

sklearn中的knn


按sklearn的形式封裝自己的knn類

import numpy as np
from math import sqrt
from collections import Counter


class KNNClassifier:

    def __init__(self, k):
        """初始化kNN分類器"""
        assert k >= 1, "k must be valid"
        self.k = k
        self._X_train = None
        self._y_train = None

    def fit(self, X_train, y_train):
        """根據訓練資料集X_train和y_train訓練kNN分類器"""
        assert X_train.shape[0] == y_train.shape[0], \
            "the size of X_train must be equal to the size of y_train"
        assert self.k <= X_train.shape[0], \
            "the size of X_train must be at least k."

        self._X_train = X_train
        self._y_train = y_train
        return self

    def predict(self, X_predict):
        """給定待預測資料集X_predict,返回表示X_predict的結果向量"""
        assert self._X_train is not None and self._y_train is not None, \
                "must fit before predict!"
        assert X_predict.shape[1] == self._X_train.shape[1], \
                "the feature number of X_predict must be equal to X_train"

        y_predict = [self._predict(x) for x in X_predict]
        return np.array(y_predict)

    def _predict(self, x):
        """給定單個待預測資料x,返回x的預測結果值"""
        assert x.shape[0] == self._X_train.shape[1], \
            "the feature number of x must be equal to X_train"

        distances = [sqrt(np.sum((x_train - x) ** 2))
                     for x_train in self._X_train]
        nearest = np.argsort(distances)

        topK_y = [self._y_train[i] for i in nearest[:self.k]]
        votes = Counter(topK_y)

        return votes.most_common(1)[0][0]

    def __repr__(self):
        return "KNN(k=%d)" % self.k

  • notebook中呼叫自己的knn類 

三. 訓練資料集, 測試資料集

判斷機器學習演算法的效能

  • 不將所有的資料都當訓練資料,而是留一部分作為測試資料, 對訓練資料得到的模型 進行測試


自己編寫測試程式碼

import numpy as np


def train_test_split(X, y, test_ratio=0.2, seed=None):
    """將資料 X 和 y 按照test_ratio分割成X_train, X_test, y_train, y_test"""
    assert X.shape[0] == y.shape[0], \
        "the size of X must be equal to the size of y"
    assert 0.0 <= test_ratio <= 1.0, \
        "test_ration must be valid"

    if seed:
        np.random.seed(seed)

    shuffled_indexes = np.random.permutation(len(X))

    test_size = int(len(X) * test_ratio)
    test_indexes = shuffled_indexes[:test_size]
    train_indexes = shuffled_indexes[test_size:]

    X_train = X[train_indexes]
    y_train = y[train_indexes]

    X_test = X[test_indexes]
    y_test = y[test_indexes]

    return X_train, X_test, y_train, y_test

  • 此時的目錄結構
 ── playML
    ├── __init__.py
    ├── kNN.py                # knn類
    └── model_selection.py    # 測試
  • notebook中呼叫自己的程式碼 

 


sklearn中的train_test_split


四.分類準確度

  • 上一節 最後檢視分類準確度的程式碼, 可以封裝成函式 metrics.py
import numpy as np


def accuracy_score(y_true, y_predict):
    '''計算y_true和y_predict之間的準確率'''
    assert y_true.shape[0] == y_predict.shape[0], \
        "the size of y_true must be equal to the size of y_predict"

    return sum(y_true == y_predict) / len(y_true)

  • 在knn類中進行呼叫
...
from .metrics import accuracy_score

class KNNClassifier:

   ...

    def score(self, X_test, y_test):
        """根據測試資料集 X_test 和 y_test 確定當前模型的準確度"""

        y_predict = self.predict(X_test)
        return accuracy_score(y_test, y_predict)

    def __repr__(self):
        return "KNN(k=%d)" % self.k



  • notebook中呼叫 

  • sklearn中的accuracy_score 


五. 超引數

超引數: 執行機器演算法前需要指定的引數, 比如knn演算法中的k。機器學習術語:調參一般就是指除錯超引數。

模型引數: 演算法過程中學習的引數,knn演算法沒有模型引數

尋找最好的超引數

  • 這裡如果k離11很近, 我們必須把k可取的範圍擴大

考慮權重的k近鄰演算法

  • 另外在遇到平票的問題時, 考慮權重就可以解決了.

 

明可夫斯基距離

  • 曼哈頓距離:∑i=1n(xia−xib)11∑i=1n​(xia​−xib​)11​
  • 尤拉距離:(∑i=1n(xia−xib)2)(∑i=1n​(xia​−xib​)2)21​
  • 明可夫斯基距離:(∑i=1n(xia−xib)p)(∑i=1n​(xia​−xib​)p)p1​
  • 當p=1時, 明可夫斯基距離就是曼哈頓距離
  • 當p=2時,明可夫斯基距離就是尤拉距離
  • 所以p也可以作為knn演算法的一個超引數

  • 圖中的k和p兩重迴圈, 就可以看作網格搜尋

六.網格搜尋與k近鄰演算法中更多超引數

sklearn的網格搜尋使用


七. 資料歸一化

以腫瘤資料為例

  • 腫瘤大小和發現時間,因為單位不同,數值大小差異很大
  • 這樣在求距離的時候, 會對結果的正確性造成很大影響 

解決方案

  • 將所有的資料對映到同一尺度

最值歸一化

  • 最值歸一化 normalization: 把所有資料對映到0-1之間, 公式如下:

 

  • 最值歸一化的適用範圍: 適合分佈有明顯邊界的情況
  • 受outlier極端值,影響較大

均值方差歸一化

  • 均值方差歸一化: 把所有資料歸一到均值為0方差為1的分佈中
  • 適用於:資料分佈沒有明顯的邊界;有可能存在極端資料值的情況

實際演示


  • sklearn中也有最值歸一化sklearn.preprocessing.MinMaxScaler
  • 但是均值方差歸一化 是比較常用的

八. scikit-learn中的Scaler

如何對測試資料集進行歸一化?

  • 假如訓練資料集得到的均值和方差為: mean_train和 std_train
  • 測試資料集的歸一化必須使用訓練資料集的均值和方差
  • 測試資料集的歸一化: (x_test-mean_train)/std_train
  • 因此需要儲存訓練資料集得到的均值和方差, scikit-learn中使用Scaler類

 


自己實現一下StandardScaler

preprocessing.py

import numpy as np


class StandardScaler:

    def __init__(self):
        self.mean_ = None
        self.scale_ = None

    def fit(self, X):
        """根據訓練資料集X獲得資料的均值和方差"""
        assert X.ndim == 2, "The dimension of X must be 2"

        self.mean_ = np.array([np.mean(X[:,i]) for i in range(X.shape[1])])
        self.scale_ = np.array([np.std(X[:,i]) for i in range(X.shape[1])])

        return self

    def transform(self, X):
        """將X根據這個StandardScaler進行均值方差歸一化處理"""
        assert X.ndim == 2, "The dimension of X must be 2"
        assert self.mean_ is not None and self.scale_ is not None, \
               "must fit before transform!"
        assert X.shape[1] == len(self.mean_), \
               "the feature number of X must be equal to mean_ and std_"

        resX = np.empty(shape=X.shape, dtype=float)
        for col in range(X.shape[1]):  # 每一列 均值方差歸一化
            resX[:,col] = (X[:,col] - self.mean_[col]) / self.scale_[col]
        return resX

  • notebook中呼叫自己的程式碼 


九. knn演算法總結

優點

  • 可以解決分類問題
  • 天然可以解決多分類問題
  • 思想簡單, 效果強大

缺點1:效率低下

  • 如果訓練m個樣本, n個特徵, 則預測
  • 每一個新的資料, 需要O(m*n)的時間複雜度

缺點2:高度資料相關

  • 比如k=3, 則找到的3個近鄰資料 中, 恰巧有錯誤的資料, 就會對結果造成極大的影響

缺點3:預測結果不具有可解釋性

  • 很多情況下, 我們希望得到的預測結果 是可以解釋的

缺點4: 容易遭受維數災難

  • 維數災難: 隨著維度的增加,"看似相近"的兩個點之間的距離越來越大
  • 實際中很多情況下, 都是非常多維的資料
  • 但也有一個解決辦法:降維