2. k近鄰演算法
阿新 • • 發佈:2019-01-07
一. 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
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: 容易遭受維數災難
- 維數災難: 隨著維度的增加,"看似相近"的兩個點之間的距離越來越大
- 實際中很多情況下, 都是非常多維的資料
- 但也有一個解決辦法:降維