數據科學實戰系列之ML-KNN(一)

寫在開頭:最近確實事情比較多,每天沒有啥時間去學習其他的東西,也就沒有時間來繼續創作博客,不過最近學習了一些多標籤分類的東西,並簡單的實現了一下。

內容安排

對於多標籤分類任務還有許多常見的方法比如ML-DT(決策樹)、Rank-SVM等,由於ML-KNN沿襲KNN的思想比較容易上手,於是本文將對多標籤分類任務中的ML-KNN算法進行簡單的介紹,並通過代碼進行實例操作,使用數據及爲MULAN的eurlex-directory-codes(點擊可下載)。

1.ML-KNN算法簡介

ML-KNN的核心思想與KNN相似,即通過尋找K個最近的樣本來判斷當前測試樣本類別,不過在ML-KNN中是運用貝葉斯條件概率,來計算當前測試樣本標籤是存在還是不存在,如果存在的概率大於不存在的概率,那麼該標籤存在。這裏將論文的僞代碼放過來,講述思路借鑑基於ML-KNN的多標籤分類算法,僞代碼如下:
在這裏插入圖片描述其主要思想是單獨觀察樣本的每個標籤存在的概率,那麼通過僞代碼可以看到
Step1.(訓練階段第1到3行)利用knn算法計算出樣本集中每個樣本的K個最近鄰;
Step2.(第4到6行)計算標籤出現的概率、已經在K近鄰中出現的次數統計,計算公式如下
在這裏插入圖片描述第一個式子表示的時某個標籤在樣本總體中存在的頻率,其中,HjH_j表示的是標籤jj的出現,mm表示的是樣本總量,分子中的求和項表示的是存在標籤jj的個數總計,ss是拉普拉斯平滑項,避免某個標籤計算出的概率爲0,具體解釋可以參考這篇文章:平滑處理-拉普拉斯。與之對應的第二個式子表示的則是某個標籤在樣本總體中不存在的頻率。
在這裏插入圖片描述計算κj[r]\kappa_j[r]表示的就是當前標籤存在,並且當前樣本的K近鄰中標籤jj存在數爲rr的樣本總數。也就是統計多少樣本的K近鄰在標籤jj上出現rr次。其中δj(xi)\delta_j(x_i)就表示的是當前樣本的K近鄰中標籤jj存在的個數。簡單來說就是看總體情況下標籤jj存在,那麼其每個樣本的KNN近鄰存在次數的一個分佈,反之κ~j[r]\tilde{\kappa}_j[r]就是計算當總體中標籤jj不存在時,每個樣本的K近鄰在標籤jj上的一個分佈;
Step3.(預測階段第8行)計算測試樣本的K近鄰;
Step4.(第9到11行)計算測試樣本K近鄰中標籤jj爲1的個數
在這裏插入圖片描述
Step5.(第12行)計算測試樣本每個標籤出現的概率,並進行判斷,其計算公式如下,
在這裏插入圖片描述該式表示的是當測試樣本K近鄰中的標籤jjCjC_j個是存在時,其真實標籤也存在的概率如果大於真實標籤不存在的概率時,即認爲測試樣本的標籤jj存在的。然後可以對上述概率計算的式子進行變形,使用貝葉斯公式進行展開得到,
在這裏插入圖片描述在這裏插入圖片描述其中P(HjCj)P(H_j|C_j)通過貝葉斯公式轉變後的分子爲P(Hj)P(CjHj)P(H_j)\cdot P(C_j|H_j)P(CjHj)P(C_j|H_j)表示的是當測試樣本標籤jj存在的條件下,其K近鄰中標籤jj的個數爲CjC_j的概率。那麼通過這樣的計算流程就能夠實現對數據進行多分類處理。

2.ML-KNN優缺點

優點:

訓練時間複雜度比支持向量機之類的算法低,僅爲O(n)
和樸素貝葉斯之類的算法比,對數據沒有假設,準確度高,對異常點不敏感
KNN主要靠周圍有限的鄰近的樣本,而不是靠判別類域的方法來確定所屬類別的,因此對於類域的交叉或重疊較多的待分樣本集來說,KNN方法較其他方法更爲適合

缺點:

計算複雜性高;空間複雜性高;
樣本不平衡的時候,對稀有類別的預測準確率低
可解釋性差,無法給出決策樹那樣的規則。

優缺點參考博文ML模型1:KNN概述及優缺點

3.ML-KNN算法實現

對於ML-KNN的實現首先需要對KNN進行實現,代碼如下:

import numpy as np 
from numba import jit

class knn():
    def __init__(self, _train_data):
        self.train_data = _train_data

    @jit
    def knn_train(self, nth, k):
        self.distance = np.square(self.train_data - self.train_data[nth]).sum(axis=1) #計算距離
        self.distance[self.distance == 0] = float("inf")
        Knn = np.argpartition(self.distance, k)[:k] #選擇距離最小的K個數
        return Knn

    @jit
    def knn_test(self, _test_data, k):
        self.distance = np.square(self.train_data - _test_data).sum(axis=1)
        self.distance[self.distance == 0] = float("inf")
        Knn = np.argpartition(self.distance, k)[:k]
        return Knn

然後本文選用的是MULAN數據所以還需要對數據進行預處理,代碼如下,

import numpy as np

class mulan_loader():
    def __init__(self, _filepath):
        self.f = open(_filepath)
        self.lines = self.f.readlines()

    def sample_label_num(self, lines, label=False):
        num = 0
        if label == False:
            for line in self.lines:
                if '@' not in line:
                    if '\n' != line:
                        num += 1
        else:
            for line in self.lines:
                if '@attribute' in line:
                    num += 1
        return num

    def input_matrix(self):
        mat = np.zeros((self.sample_label_num(self.lines), self.sample_label_num(self.lines, label=True)))
        m = 0
        for i in range(len(self.lines)):
            if '@' not in self.lines[i]:
                if '\n' != self.lines[i]:
                    m += 1
                    sample = self.lines[i].split(',')
                    l = []
                    for key_value in sample:
                        l.append(key_value.split(' '))
                    l[0][0] = '0'
                    l[-1][1] = l[-1][1][0]
                    for j in range(len(l)):
                        mat[m-1][eval(l[j][0])] = eval(l[j][1])
        return mat
    
    def data_target_split(self, data_num):
        mat = self.input_matrix()
        data = mat[:, :data_num]
        target = mat[:, data_num:]
        return data, target
filepath = 'D:\\eurlex-directory-codes\\eurlex-dc-leaves-fold1-train.arff'
data, target = mulan_loader(filepath).data_target_split(5000)
print("數據維度爲",data.shape)
print("目標維度爲",target.shape)
數據維度爲 (17413, 5000)
目標維度爲 (17413, 412)

可以看到載入的數據維度挺大的,最後編寫ML-KNN的代碼按照前文的算法邏輯即可,

import numpy as np
from sklearn.model_selection import KFold
from Mulan_load import mulan_loader
from KNN import knn
import gc

class ML_KNN():
    def __init__(self, _train_data, _train_target, _k, _s, _it):
    # 初始化參數
        self.train_data = _train_data
        self.train_target =  _train_target
        self.train_num = self.train_data.shape[0]
        self.labels_num = self.train_target.shape[1]
        self.k = _k
        self.s = _s
        self.Peh1 = np.zeros((self.labels_num, self.k + 1))
        self.Peh0 = np.zeros((self.labels_num, self.k + 1))
        self.it = _it
    
    def fit(self):
        self.PH1 = (self.s + self.train_target.sum(axis=0))/(self.s*2 + self.train_num)
        self.PH0 = 1 - self.PH1
        for i in range(self.labels_num):
            if i % 5 == 0:
                print("第%d輪訓練進度:%d|%d (%.2f %%)"%(self.it, i, self.labels_num, i*100/self.labels_num))
            c1, c0= np.zeros((self.k + 1,)), np.zeros((self.k + 1,)) #c對應花k
            target = self.train_target[:, i]
            for j in range(self.train_num):
                if j % 100 == 0:
                    print("第%d輪中第%d個指標訓練進度:%d|%d (%.2f %%)"%(self.it, i, j, self.train_num, j*100/self.train_num))
                temp = 0
                KNN = knn(self.train_data).knn_train(j, self.k)
                temp = int(target[KNN].sum())
                if self.train_target[j][i] == 1:
                    c1[temp] = c1[temp] + 1
                else:
                    c0[temp] = c0[temp] + 1
    
            for l in range(self.k + 1):
                self.Peh1[i][l] = (self.s + c1[l])/(self.s*(self.k + 1) + c1.sum())
                self.Peh0[i][l] = (self.s + c0[l])/(self.s*(self.k + 1) + c0.sum())
        print("第%d輪訓練完成!%d|%d (100.00 %%)"%(self.it, self.labels_num, self.labels_num))
    
    def predict(self, _test_data):
        print("開始預測!")
        test_num = _test_data.shape[0]
        self.rtl = np.zeros((test_num, self.labels_num))
        self.predict_labels = np.zeros((test_num, self.labels_num))
        
        for i in range(test_num):
            if i % 5 == 0:
                print("測試進度:%d|%d (%.2f %%)"%(i, test_num, i*100/test_num))
            target = self.train_target[:,i]
            KNN = knn(self.train_data).knn_test(_test_data[i], self.k)
            for j in range(self.labels_num):
                temp = 0
                temp = int(target[KNN].sum())
                y1 = self.PH1[j] * self.Peh1[j][temp]
                y0 = self.PH0[j] * self.Peh0[j][temp]
                self.rtl[i][j] = y1 / (y1 + y0)
                if y1 > y0: #判斷條件
                    self.predict_labels[i][j] = 1
                else:
                    self.predict_labels[i][j] = 0
        print("預測完成!%d|%d (100.00 %%)"%(test_num, test_num))
        return self.predict_labels

if __name__ == "__main__":
    gc.disable()
    print("開始讀取數據,請等待>>>")
    filepath = 'D:\\eurlex-directory-codes\\eurlex-dc-leaves-fold1-train.arff'
    data, target = mulan_loader(filepath).data_target_split(5000)
    print("讀取數據完成,準備進行訓練>>>")
    kf = KFold(n_splits=10, shuffle=True, random_state=529)
    it = 0
    for trian_index , test_index in kf.split(data):
        it += 1
        print("*"*30)
        print("開始第%d輪訓練"%it)
        train_X, test_X = data[trian_index], data[test_index]
        train_Y, test_Y = target[trian_index], target[test_index]
        ml_knn = ML_KNN(train_X, train_Y, 5, 1, it)
        ml_knn.fit()
        labels = ml_knn.predict(test_X)

結語

這裏就不運行這個程序的結果了,因爲輸入的數據維度過大幾乎要大半年才能運行出結果,所以可以得到ML-KNN的缺點之一就是不適用大維度數據,計算複雜度過高。所以筆者將在下一篇文章擬提出一種方法能夠運行如此龐大的矩陣。
謝謝閱讀。

參考

1.基於ML-KNN的多標籤分類算法
2.平滑處理-拉普拉斯
3.ML模型1:KNN概述及優缺點

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