數據挖掘入門系列教程(二)之分類問題OneR演算法
- 2020 年 3 月 9 日
- 筆記
數據挖掘入門系列教程(二)之分類問題OneR演算法
數據挖掘入門系列部落格:https://www.cnblogs.com/xiaohuiduan/category/1661541.html
項目地址:GitHub
在上一篇部落格中,我們通過分析親和性來尋找數據集中數據與數據之間的相關關係。這篇部落格我們會討論簡單的分類問題。
分類簡介
分類問題,顧名思義我么就是去關注類別(也就是目標)這個變數。分類應用的目的是根據已知類別的數據集得到一個分類模型,然後通過這個分類模型去對類別未知的數據進行分類。這裡有一個很典型的應用,那就是垃圾郵件過濾器。
在這片部落格中,我們使用著名Iris(鳶尾屬)植物作為數據集。這個數據集共有150條植物數據,每條數據都給出了四個特徵:sepal length、sepal width、petal length、petal width(分別表示萼片和花瓣的長與寬),單位均為cm。一共有三種類別:Iris Setosa(山鳶尾)、Iris Versicolour(變色鳶尾)和Iris Virginica(維吉尼亞鳶尾)
數據集準備
在scikit-learn庫中內置了該數據集,我們首先pip安裝scikit-learn庫

下面的程式碼表示從sklearn中的數據集中載入iris數據集,並列印數據集中的說明(Description)。
from sklearn.datasets import load_iris dataset = load_iris() print(dataset.DESCR) # data 為特徵值 data = dataset.data # target為分類類別 target = dataset.target截一個數據集中的說明圖:

在數據集中,數據的特徵值一般是連續值,比如說花瓣的長度可能有無數個值,而當兩個值相近時,則表示相似度很大。(這個是由自然界決定的)
與之相反,數據的類別為離散值。因為一種植物就肯定是一種植物,通常使用數字表示類別,但是在這裡,數字的相近不能夠代表這兩個類別相似(因為這個類別是人為定義的)
數據集的特徵為連續數據,而類別是離散值,因此我們需要將連續值轉成類別值,這個稱之為離散化。
而將連續數據進行離散化有個很簡單的方法,就是設定一個閾值,高於這個閾值為1,低於這個閾值為0。具體怎麼實現我們在下面再說。
OneR演算法
OneR(one rule)演算法很簡單,當時挺有效的。on rule,一條規則,以上面的iris植物為例,就是我們選擇四個特種中分類效果最好的一個作為分類依據。這裡值得注意的是,選擇一個,選擇一個,選擇一個。
下面是演算法的具體步驟,為《Python數據挖掘入門與實踐》的原文。
演算法首先遍歷每個特徵的每一個取值,對於每一個特徵值,統計它在各個類別中的出現次數,找到它出現次數最多的類別,並統計它在其他類別中的出現次數。
舉例來說,假如數據集的某一個特徵可以取0或1兩個值。數據集共有三個類別。特徵值為0的情況下,A類有20個這樣的個體,B類有60個,C類也有20個。那麼特徵值為0的個體最可能屬於B類,當然還有40個個體確實是特徵值為0,但是它們不屬於B類。將特徵值為0的個體分到B類的錯誤率就是40%,因為有40個這樣的個體分別屬於A類和C類。特徵值為1時,計算方法類似,不再贅述;其他各特徵值最可能屬於的類別及錯誤率的計算方法也一樣。
統計完所有的特徵值及其在每個類別的出現次數後,我們再來計算每個特徵的錯誤率。計算方法為把它的各個取值的錯誤率相加,選取錯誤率最低的特徵作為唯一的分類準則(OneR),用於接下來的分類。
如果大家對OneR演算法為什麼能夠對花卉進行分類感到迷惑的話,可以繼續往下看,後面有說明。
在前面的前面我們介紹了特徵值的離散化,通過設定一個閾值,我們可以將特徵值變成簡單的0和1。具體怎麼做呢?可以看下面的圖片:

假如一共有三個類別,120條數據形成一個(120 × 3的矩陣),然後我們進行壓縮行,計算每一列的平均值,然後得到閾值矩陣(1 × 3的矩陣)。這個時候,我們就可以原先的數據進行離散化,變成0和1了。(這一步可以看示意圖)
在python的numpy中有一個方法,numpy.mean,裡面經常操作的參數為axis,以m*n的矩陣為例:
- axis = None,也就是不加這個參數,則是對m*n 個求平均值,返回一個實數
- axis = 0:壓縮行,對各列求均值,返回 1* n 矩陣
- axis =1 :壓縮列,對各行求均值,返回 m *1 矩陣
average_num = data.mean(axis = 0) import numpy as np data = np.array(data > average_num,dtype = "int") print(data)通過np.array去構建一個新的數組,當data 大於average_num的時候(為矩陣比較),就為True,否則為False,然後指定類型為int,則True變成了1,False變成了0。結果如下圖:

演算法實現
既然是去構建一個分類模型,那麼我們既需要去構建這個模型,也需要去測試這個模型。so,我們既需要訓練集,也需要測試集。根據二八法則,一共150條數據,那麼就有120個訓練集,30個測試集。
幸運的是sklearn提供了這個劃分訓練集的庫給我們,train_test_split中0.2 代表的是測試集所佔的比例(在我上傳到GitHub的源程式碼中,沒有設置這個值,默認是0.25),14代表的是隨機種子。
from sklearn.model_selection import train_test_split # 隨機獲得訓練和測試集 def get_train_and_predict_set(): data_train,data_predict,target_train,target_predict = train_test_split(data,target,test_size=0.2, random_state=14) return data_train,data_predict,target_train,target_predict data_train,data_predict,target_train,target_predict = get_train_and_predict_set() 這裡有一點需要注意,同時也是困擾了我一段時間的問題。那就是在OneR演算法中,只憑藉1個特徵,2種特徵值,憑什麼能夠對3種花卉進行識別??實際上,不能,除非有3個特徵值。在《Python數據挖掘入門與實踐》,用花卉這個例子舉OneR演算法不是很恰當,因為當演算法實現的時候,只能夠識別出兩種花卉。如下圖:

如果想看一個合適的例子,大家可以去看:https://www.saedsayad.com/oner.htm,在裡面最後識別的結果只有yes和no。
具體的訓練步驟是怎麼樣的呢?
首先我們假設有x,y,z三個特徵,每個特徵的特徵值為0和1,同時有A,B兩個類。因此我們可以得到下面的統計。
對於每一個特徵值,統計它在各個類別中的出現次數:

既然我們得到了統計,這時候,我們就開始來計算錯誤率。首先我們找到某個特徵值(如 $X = 0$)出現次數最多的類別。在下圖中,被框框圈住的部分就是出現次數最多的特徵值(如果有三個類別,任然是選擇次數最多的類別)。

再然後我們就是計算出每一個特徵的錯誤率了,下面以$X$為例

同理,我們可以得到$Y$,$Z$的錯誤錯誤率,然後選擇最小的錯誤率作為分類標準即可。
說了這麼多,現在來寫程式碼了。
下面是train_feature函數,目的是得到指定特徵,特徵值得到錯誤率最小的類別。也就是上面圖中的$b_{x0},a_{x1}$等等。
from collections import defaultdict from operator import itemgetter def train_feature(data_train,target_train,index,value): """ data_train:訓練集特徵 target_train:訓練集類別 index:特徵值的索引 value :特徵值 """ count = defaultdict(int) for sample,class_name in zip(data_train,target_train): if(sample[index] ==value): count[class_name] += 1 # 進行排序 sort_class = sorted(count.items(),key=itemgetter(1),reverse = True) # 擁有該特徵最多的類別 max_class = sort_class[0][0] max_num = sort_class[0][1] all_num = 0 for class_name,class_num in sort_class: all_num += class_num # print("{}特徵,值為{},錯誤數量為{}".format(index,value,all_num-max_num)) # 錯誤率 error = 1 - (max_num / all_num) return max_class,error在train函數中,我們對所有的特徵和特徵值進行計算,得到最小的特徵錯誤率。
def train(): errors = defaultdict(int) class_names = defaultdict(list) # 遍歷特徵 for i in range(data_train.shape[1]): # 遍歷特徵值 for j in range(0,2): class_name,error = train_feature(data_train,target_train,i,j) errors[i] += error class_names[i].append(class_name) return errors,class_names errors,class_names = train() # 進行排序 sort_errors = sorted(errors.items(),key=itemgetter(1)) best_error = sort_errors[0] # 得到最小錯誤率對應的特徵 best_feature = best_error[0] # 當特徵值取 0 ,1對應的類別。 best_class = class_names[best_feature] print("最好的特徵是{}".format(best_error[0])) print(best_class) 訓練完成後,我們就可以進行predict了。predict就是那測試集中數據進行測試,使用自己的模型進行預測,在與正確的作比較得到準確度。看下圖predict的流程:

下面是預測程式碼以及準確度檢測程式碼:
# 進行預測 def predict(data_test,feature,best_class): return np.array([best_class[int(data[feature])] for data in data_test]) result_predict = predict(data_predict,best_feature,best_class) print("預測準確度{}".format(np.mean(result_predict == target_predict) * 100)) print("預測結果{}".format(result_predict))結果
在以下條件下:
- 分割測試集和訓練集的隨機種子為14
- 默認的切割比例
最後的結果如下如所示:

在以下條件下:
- 切換比例為2:8
- 隨機種子為14
結果如下圖所示

OneR演算法很簡單,但是在某些情況下卻很有效,沒有完美的演算法,只有最適用的演算法。
結尾
GitHub地址:GitHub
參考書籍:Python數據挖掘入門與實踐
感謝蔣少華老師為我解惑。

