1. 程式人生 > >演算法修煉——KNN小白版

演算法修煉——KNN小白版

機器學習演算法大概學了一遍但是覺得理解不深,所以決定從頭開始再學一遍,寫在部落格裡,加深一下理解,方便以後查閱。

knn作為機器學習的入門演算法,雖然它不是很複雜,但是學起來也沒有那麼簡單。因為剛剛接觸機器學習領域,knn並不僅僅是作為一個演算法來進行學習,它更多的是可以為我們解釋機器學習演算法使用過程中的細節問題,可以幫助我們更加完整的刻畫機器學習應用的流程。

本章我只介紹最為簡單的knn演算法,進階版的knn我會在下一章寫。

我使用的工具包括:Anaconda 整合環境,Jupyter Notebook 測試、編譯程式碼,Pycharm

knn 演算法的優點:

knn 演算法的缺點:

原理案例介紹

假設現在設計一個程式判斷一個新的腫瘤病人是良性腫瘤還是惡性腫瘤

先基於原有的腫瘤病人的發現時間和腫瘤大小(特徵)對應的良性/惡性(值)建立了一張散點圖,橫座標是腫瘤大小,縱座標是發現時間,紅色代表良性,藍色代表惡性,現在要預測的病人的顏色為綠色

  1. 首先需要取一個k值(這個k值的取法後面會介紹),然後找到距離要預測的病人的點(綠點)距離最近的k個點
  2. 然後用第一步中取到的三個點進行投票,比如本例中投票結果就是藍:紅 = 3:0 ,3>0,所以判斷這個新病人幻的事惡性腫瘤

knn的本質:

如果一個樣本在特徵空間中的k個最相似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。

knn演算法的一個簡單實現:

import numpy as np

import matplotlib as plt

 原始集合:

#特徵

raw_data_x= [ [3.393533211,2.331273381],

                       [2.110073483,1.781539638],

                       [1.343808831,3.368360954],

                       [3.582294042,4.679179110],

                       [2.280362439,2.866990263],

                       [7.423436942,4.696522875],

                       [5.745051997,3.533989803],

                       [9.172168622,2.511101045],

                       [7.792783481,3.424088941],

                       [7.939820817,0.791637231] ]

#所屬類別

                       raw_data_y = [0,0,0,0,0,1,1,1,1,1]

X_train = np.array(raw_data_x)

y_train = np.array(raw_data_y)

# 要預測的點
x = np.array([8.093607318,3.365731514])

接下來讓我們繪製一下資料集及預測的點:

scatter([X_train[y_train==0,0]],X_train[y_train==0,1],color='g')    # 目標值為0的點

scatter([X_train[y_train==1,0]],X_train[y_train==1,1],color='r')    #目標值為1的點

scatter(X[0],X[1],color='b')

 

KNN演算法的封裝:

import  numpy as np
from math import sqrt
from collections import  Counter
from .metrics     import accuracy_score

class KNNClassifier:

    def __init__(self,k):
        """初始化kNN分類器"""
        """ self在定義時需要定義,但是在呼叫時會自動傳入。
            self的名字並不是規定死的,但是最好還是按照約定是用self
            self總是指呼叫時的類的例項。"""
        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 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 if 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 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

讓我們再看一下機器學習的原理:

可以說kNN是一個不需要訓練過程的演算法
  k近鄰演算法是非常特殊的,可以被認為是沒有模型的演算法
  為了和其他演算法統一,可以認為訓練資料集就是模型

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

train test split

使用我們自己封裝的測試封裝函式去分割資料集

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)

    # 先亂序化處理
    shuffle_indexes = np.random.permutation(len(X))
    test_size = int(len(X) * test_ratio)
    test_indexes = shuffle_indexes[:test_size]
    train_indexes = shuffle_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

 測試我們的KNN演算法:

這裡我使用的是sklearn資料集的鳶尾花資料集。

import numpy as np from sklearn import datasets

iris = datasets.load_iris()

X = iris.data

y = iris.target 

 接下來讓我們看一下資料集X:

array([[5.1, 3.5, 1.4, 0.2], [4.9, 3. , 1.4, 0.2], [4.7, 3.2, 1.3, 0.2], [4.6, 3.1, 1.5, 0.2], [5. , 3.6, 1.4, 0.2], [5.4, 3.9, 1.7, 0.4], [4.6, 3.4, 1.4, 0.3], [5. , 3.4, 1.5, 0.2], [4.4, 2.9, 1.4, 0.2], [4.9, 3.1, 1.5, 0.1], [5.4, 3.7, 1.5, 0.2], [4.8, 3.4, 1.6, 0.2], [4.8, 3. , 1.4, 0.1], [4.3, 3. , 1.1, 0.1], [5.8, 4. , 1.2, 0.2], [5.7, 4.4, 1.5, 0.4], [5.4, 3.9, 1.3, 0.4], [5.1, 3.5, 1.4, 0.3], [5.7, 3.8, 1.7, 0.3], [5.1, 3.8, 1.5, 0.3], [5.4, 3.4, 1.7, 0.2], [5.1, 3.7, 1.5, 0.4], [4.6, 3.6, 1. , 0.2], [5.1, 3.3, 1.7, 0.5], [4.8, 3.4, 1.9, 0.2], [5. , 3. , 1.6, 0.2], [5. , 3.4, 1.6, 0.4], [5.2, 3.5, 1.5, 0.2], [5.2, 3.4, 1.4, 0.2], [4.7, 3.2, 1.6, 0.2], [4.8, 3.1, 1.6, 0.2], [5.4, 3.4, 1.5, 0.4], [5.2, 4.1, 1.5, 0.1], [5.5, 4.2, 1.4, 0.2], [4.9, 3.1, 1.5, 0.1], [5. , 3.2, 1.2, 0.2], [5.5, 3.5, 1.3, 0.2], [4.9, 3.1, 1.5, 0.1], [4.4, 3. , 1.3, 0.2], [5.1, 3.4, 1.5, 0.2], [5. , 3.5, 1.3, 0.3], [4.5, 2.3, 1.3, 0.3], [4.4, 3.2, 1.3, 0.2], [5. , 3.5, 1.6, 0.6], [5.1, 3.8, 1.9, 0.4], [4.8, 3. , 1.4, 0.3], [5.1, 3.8, 1.6, 0.2], [4.6, 3.2, 1.4, 0.2], [5.3, 3.7, 1.5, 0.2], [5. , 3.3, 1.4, 0.2], [7. , 3.2, 4.7, 1.4], [6.4, 3.2, 4.5, 1.5], [6.9, 3.1, 4.9, 1.5], [5.5, 2.3, 4. , 1.3], [6.5, 2.8, 4.6, 1.5], [5.7, 2.8, 4.5, 1.3], [6.3, 3.3, 4.7, 1.6], [4.9, 2.4, 3.3, 1. ], [6.6, 2.9, 4.6, 1.3], [5.2, 2.7, 3.9, 1.4], [5. , 2. , 3.5, 1. ], [5.9, 3. , 4.2, 1.5], [6. , 2.2, 4. , 1. ], [6.1, 2.9, 4.7, 1.4], [5.6, 2.9, 3.6, 1.3], [6.7, 3.1, 4.4, 1.4], [5.6, 3. , 4.5, 1.5], [5.8, 2.7, 4.1, 1. ], [6.2, 2.2, 4.5, 1.5], [5.6, 2.5, 3.9, 1.1], [5.9, 3.2, 4.8, 1.8], [6.1, 2.8, 4. , 1.3], [6.3, 2.5, 4.9, 1.5], [6.1, 2.8, 4.7, 1.2], [6.4, 2.9, 4.3, 1.3], [6.6, 3. , 4.4, 1.4], [6.8, 2.8, 4.8, 1.4], [6.7, 3. , 5. , 1.7], [6. , 2.9, 4.5, 1.5], [5.7, 2.6, 3.5, 1. ], [5.5, 2.4, 3.8, 1.1], [5.5, 2.4, 3.7, 1. ], [5.8, 2.7, 3.9, 1.2], [6. , 2.7, 5.1, 1.6], [5.4, 3. , 4.5, 1.5], [6. , 3.4, 4.5, 1.6], [6.7, 3.1, 4.7, 1.5], [6.3, 2.3, 4.4, 1.3], [5.6, 3. , 4.1, 1.3], [5.5, 2.5, 4. , 1.3], [5.5, 2.6, 4.4, 1.2], [6.1, 3. , 4.6, 1.4], [5.8, 2.6, 4. , 1.2], [5. , 2.3, 3.3, 1. ], [5.6, 2.7, 4.2, 1.3], [5.7, 3. , 4.2, 1.2], [5.7, 2.9, 4.2, 1.3], [6.2, 2.9, 4.3, 1.3], [5.1, 2.5, 3. , 1.1], [5.7, 2.8, 4.1, 1.3], [6.3, 3.3, 6. , 2.5], [5.8, 2.7, 5.1, 1.9], [7.1, 3. , 5.9, 2.1], [6.3, 2.9, 5.6, 1.8], [6.5, 3. , 5.8, 2.2], [7.6, 3. , 6.6, 2.1], [4.9, 2.5, 4.5, 1.7], [7.3, 2.9, 6.3, 1.8], [6.7, 2.5, 5.8, 1.8], [7.2, 3.6, 6.1, 2.5], [6.5, 3.2, 5.1, 2. ], [6.4, 2.7, 5.3, 1.9], [6.8, 3. , 5.5, 2.1], [5.7, 2.5, 5. , 2. ], [5.8, 2.8, 5.1, 2.4], [6.4, 3.2, 5.3, 2.3], [6.5, 3. , 5.5, 1.8], [7.7, 3.8, 6.7, 2.2], [7.7, 2.6, 6.9, 2.3], [6. , 2.2, 5. , 1.5], [6.9, 3.2, 5.7, 2.3], [5.6, 2.8, 4.9, 2. ], [7.7, 2.8, 6.7, 2. ], [6.3, 2.7, 4.9, 1.8], [6.7, 3.3, 5.7, 2.1], [7.2, 3.2, 6. , 1.8], [6.2, 2.8, 4.8, 1.8], [6.1, 3. , 4.9, 1.8], [6.4, 2.8, 5.6, 2.1], [7.2, 3. , 5.8, 1.6], [7.4, 2.8, 6.1, 1.9], [7.9, 3.8, 6.4, 2. ], [6.4, 2.8, 5.6, 2.2], [6.3, 2.8, 5.1, 1.5], [6.1, 2.6, 5.6, 1.4], [7.7, 3. , 6.1, 2.3], [6.3, 3.4, 5.6, 2.4], [6.4, 3.1, 5.5, 1.8], [6. , 3. , 4.8, 1.8], [6.9, 3.1, 5.4, 2.1], [6.7, 3.1, 5.6, 2.4], [6.9, 3.1, 5.1, 2.3], [5.8, 2.7, 5.1, 1.9], [6.8, 3.2, 5.9, 2.3], [6.7, 3.3, 5.7, 2.5], [6.7, 3. , 5.2, 2.3], [6.3, 2.5, 5. , 1.9], [6.5, 3. , 5.2, 2. ], [6.2, 3.4, 5.4, 2.3], [5.9, 3. , 5.1, 1.8]])

再看一下目標集y:

array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]) 

首先使用train_test_split去分割資料集。

from knn8.model_selection import train_test_split

X_train,y_train,X_test,y_test = train_test_split(X,y,0.2)

 在jupyter notebook中呼叫我們自己的knn演算法:

from knn8.knn import KNNClassifier knn_clf = KNNClassifier(k=6)   #生成k值為6的分類器

knn_clf.fit(X_train,y_train)    #訓練資料

y_predict = knn_clf.predict(X_test)  #預測測試資料。

y_predict

sum(y_predict==y_test) / len(y_test)  # 簡單的計算一下精度。

96%的精確度 ,還是可以的,hhhhh

好了,寫到這裡,最簡單的knn已經差不多結束了。下一次我會寫knn的進階版,包括超引數的選擇,會考慮距離的權重,明可夫斯基距離,網格搜尋,資料歸一化。

剛剛開始學習,有不對的地方,還望多多指正。