1. 程式人生 > >資料探勘入門系列教程(三)之scikit-learn框架基本使用(以K近鄰演算法為例)

資料探勘入門系列教程(三)之scikit-learn框架基本使用(以K近鄰演算法為例)

    • 資料探勘入門系列教程(三)之scikit-learn框架基本使用(以K近鄰演算法為例)
      • 簡介
      • scikit-learn 估計器
      • 載入資料集
      • 進行fit訓練
        • 設定引數
      • 預處理
      • 流水線
      • 結尾

資料探勘入門系列教程(三)之scikit-learn框架基本使用(以K近鄰演算法為例)

資料探勘入門系列部落格:https://www.cnblogs.com/xiaohuiduan/category/1661541.html

專案地址:GitHub

在上一篇部落格中,我們使用了簡單的OneR演算法對Iris進行分類,在這篇部落格中,我將介紹資料探勘通用框架的搭建以及使用(以scikit-learn框架為例子),並以K近鄰演算法為例展示過程。

簡介

在介紹框架之前,有幾個重要的概念如下所示:

  • 估計器(Estimator):用於分類,聚類和迴歸分析。
  • 轉換器(Transformer):用於資料的預處理和資料轉換。
  • 流水線(Pipeline):組合資料探勘流程,便於再次使用。

至於這幾個具體是怎麼回事,下面將以具體的例子來進行展示。

scikit-learn 估計器

估計器,根據上面概念的解釋,我們也知道估計器的作用是什麼了,它主要是為了訓練模型,用於分類任務。主要有兩個主要的函式:

  • fit():訓練演算法,裡面接受訓練集和類別兩個引數。
  • predict():裡面的引數為測試集,預測測試集的類別,返回預測後的類別陣列。

大多數的scikit-learn估計器接收和輸出的資料格式均為numpy陣列或者類似的格式。

下面將介紹使用scikit-learn實現KNN演算法,關於KNN演算法的一些介紹,可以去參考一下我上一篇的部落格。

載入資料集

其中,在這裡使用的資料集是叫做電離層(Ionosphere)。簡單點來說,通過採集的資料來判斷是否存在自由電子,存在則為"g",不存在則為'b'。下圖是一些資料的展示。


我們目標就是建立分類器,自動判斷資料的好壞。

資料集在這裡:GitHub,該資料是CSV格式的。每一行資料有35個值,前34個為採集資料,最後一個表示該資料是否能夠判斷自由電子的存在。

接下來展示資料的匯入。

import numpy as np

# 採集資料
x = np.zeros((351,34),dtype = "float")
# 類別資料
y = np.zeros((351),dtype = "byte")

# 資料檔名
file_name = "ionosphere.data"

with open(file_name,"r") as input_file:
    reader = csv.reader(input_file)
    for i,row in enumerate(reader):
        # 只遍歷前34個數據
        datas = [float(data) for data in row[:-1]]
        x[i] = datas
        y[i] = row[-1] == 'g'

此時我們就分別得到了採集的資料和類別資料。接下來就是建立訓練集和測試集。這一步在前面的部落格有詳細說明,就不再解釋了。

from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(x,y,random_state = 14)

進行fit訓練

在前面我們說過,我們使用KNN演算法實現資料的分類,但是需要我們實現KNN的函式嗎?儘管KNN的實現並不是很難,但是scikit-learn幫我們實現了。

通過下面的程式碼,我們匯入了一個K近鄰的分類器,其中裡面預設$K = 5$,我們可以自己進行調整K的取值。

from sklearn.neighbors import KNeighborsClassifier
estimator = KNeighborsClassifier()

前面我們說過estimator有兩個重要的函式fit和predict,這裡就讓我們來進行訓練吧!

estimator.fit(x_train,y_train)

在上面我們通過fit函式匯入訓練集和訓練類別就完成了KNN演算法的訓練,然後我們就可以進行預測了。其中使用predict函式,使用測試資料集作為引數,返回預測的類別。

y_predict = estimator.predict(x_test)
accuracy = np.mean(y_predict == y_test) * 100
print("精準度{0:.2f}%".format(accuracy))

最後的結果如下圖:


上面的結果與train_test_split的隨機數和測試集的大小有關。

拋開測試集的大小不談,通過改變隨機數,我們發現,精準度會隨著隨機數的改變而發生較大的變化。下圖是隨機種子為2的時候的精準度。


這個時候就會有一個疑問,如果我的運氣不好,隨機分割的訓練集沒有給好,即使再好的分類演算法豈不是會得到一個不好的分類結果?對,臉黑的話的確會有這種情況。


那怎麼解決呢?如果我們多進行幾次切分,並在在切分訓練集和測試集的時候每一次都與上一次不一樣就好了,並且每一條資料都被用來測試一次就更好了。演算法描述如下。

  • 將整個大資料集分為幾個部分。
  • 迴圈執行以下操作:
    • 將其中一部分作為當前測試集
    • 用剩餘部分訓練演算法
    • 在當前測試集上測試演算法
  • 記錄每次得分,然後得到平均分。
  • 每條資料只在訓練集出現一次。

以上的說法叫做交叉驗證。emm,夢想是好的,現實中更是好的,scikit-learn提供了一些交叉驗證的方法。我們匯入即可:

# 進行交叉驗證
from sklearn.model_selection import cross_val_score

cross_val_score預設使用Stratified K Fold方法切分資料集,它大體上保
證切分後得到的子資料集中類別分佈相同,以避免某些子資料集出現類別分佈失衡的情況。這個預設做法很不錯,現階段就不再把它搞複雜了。

cross_val_score裡面傳入估計器,資料集,類別集以及scoring。

這裡簡單的說一下scoring的作用。簡單來說他是一個計分器,來決定計分的規則。而傳入accuracy代表預測的結果要完全的匹配測試集中的類別。官方的解釋如下圖。


scores = cross_val_score(estimator, x, y, scoring='accuracy') 
average_accuracy = np.mean(scores) * 100 
print("平均的精準度為{0:.2f}%".format(average_accuracy)) 

最終的結果如下圖:


設定引數

前面我們說過,預設的KNN估計器中$K = 5$,那麼他是不是最好的呢?不一定!!K過小時,分類結果容易收到干擾,過大時,又會減弱近鄰的影響。因此我們希望設定一個合理的K,這個時候,我們可以使用一個for迴圈,遍歷K的取值,來選擇一個比較好的取值。

# 儲存每一次的平均分數
avg_scores = []
# 遍歷k的取值從1到25
for i in range(1,25):
    estimator = KNeighborsClassifier(i)
    scores = cross_val_score(estimator, x, y, scoring='accuracy') * 100
    avg_scores.append(np.mean(scores))

為了更加的形象化,選擇使用matplotlib 進行畫圖。

from matplotlib import pyplot as plt
x_len = list(range(1,len(avg_scores)+1))
plt.plot(x_len,avg_scores, '-x',color = "r") 

最後的結果如下圖所示:


總的來說,隨著$K$值的增加,準確度逐漸降低。

預處理

資料探勘的路不可能一帆風順,很可能我們就被第一波資料集的浪潮給拍死在岸邊上。因為我們的資料集很可能中間的某一些資料有問題,或者說有可能特徵的取值千差萬別,也有可能某些特徵沒有區分度等等原因。這個時候我們就需要對資料集進行預處理。

選擇具有區分度,建立新的特徵,對資料進行清洗……這些都屬於預處理的範疇。在scikit-learn中,預處理工具叫做轉換器(Transformer),它接受原始資料集,返回轉換後的資料集。轉換器主要有以下三個函式:

  • fit():訓練演算法,設定內部引數。
  • transform():資料轉換。
  • fit_transform():合併fit和transform兩個方法。

任然以上面的Ionosphere為例子,讓我們來對資料集進行一些破壞,然後進行訓練。

x_broken = np.array(x)
# 對numpy陣列進行切片,對於每一列,每隔2個單位除以10
x_broken[:,::2] /= 10

estimator = KNeighborsClassifier()
scores = cross_val_score(estimator, x_broken, y, scoring='accuracy') 
average_accuracy = np.mean(scores) * 100 
print("平均的精準度{0:.2f}%".format(average_accuracy)) 


預處理有很多種方式,其中有一個方式稱之為歸一化,也就是說將特徵值規範到01之間,最小值為0,最大值為1,其餘值介於01之間。

接下來讓我們對x_broken進行歸一化預處理。在scikit-learn中提供了一系列的前處理器,通過呼叫前處理器的轉換函式,可以完成資料集的轉換。

  • sklearn.preprocessing.MinMaxScaler:使資料歸一化

  • sklearn.preprocessing.Normalizer:使每條資料各特徵值的和為1

  • sklearn.preprocessing.StandardScaler:使各特徵的均值為0,方差為1

  • sklearn.preprocessing.Binarizer:使數值型特徵的二值化,大於閾值的為1,反之為0

# 歸一化
from sklearn.preprocessing import MinMaxScaler
x_formed = MinMaxScaler().fit_transform(x_broken)

# 然後進行訓練
estimator = KNeighborsClassifier()
scores = cross_val_score(estimator, x_formed, y, scoring='accuracy') 
average_accuracy = np.mean(scores) * 100 
print("平均的精準度{0:.2f}%".format(average_accuracy)) 

歸一化之後的精準度如下圖:


流水線

在上面我們看到,進行資料探勘有很多步驟需要去做:

  • 預處理資料集
  • 對資料集進行切割
  • 尋找最好的引數
  • ……

而隨著資料集的增加以及精準度的要求,實驗的操作複雜度越來越大,如果中間某一個步驟出現問題,比如說資料轉換錯誤,拉下一個步驟……

流水線結構可以解決這個問題。讓我們來匯入它吧:

from sklearn.pipeline import Pipeline

我們可以將Pipeline比喻成生產車間的流水線,原材料按順序一個一個的通過步驟加工,然後才能夠形成一個完整的產品。當然此pipeline非此流水線,但是哲學思想卻是差不多的。

首先,讓我們來看一看Pipeline物件一部分的介紹信:

Pipeline of transforms with a final estimator.

Sequentially apply a list of transforms and a final estimator.Intermediate steps of the pipeline must be transforms, that is, they must implement fit and transform methods.The final estimator only needs to implement fit.The transformers in the pipeline can be cached using memory argument.

The purpose of the pipeline is to assemble several steps that can be cross-validated together while setting different parameters.For this, it enables setting parameters of the various steps using their names and the parameter name separated by a '__', as in the example below. A step's estimator may be replaced entirely by setting the parameter with its name to another estimator, or a transformer removed by setting it to 'passthrough' or None.

簡單點來說,在這條流水線中,前面的步驟是一系列的transform,最後一個步驟必須是estimator。在transform中必須實現fittransform函式,而最後的estimator中必須實現fit函式。具體怎麼使用,讓我們來看一看吧。

from sklearn.pipeline import Pipeline

mining_pipe = Pipeline(
    [
       ('預處理',MinMaxScaler()),
        ('估計器',KNeighborsClassifier())
    ],
    verbose=True
)

在pipeline中,步驟使用的是元組來表示:(“名稱”,步驟)。名稱只是一個稱呼,all is ok!後面的步驟則就是estimator或者transfrom。verbose代表是否檢視每一個步驟的使用時間。

執行流水線很簡單:

# 傳入pipeline引數
scores = cross_val_score(mining_pipe, x_broken, y, scoring='accuracy') 

average_accuracy = np.mean(scores) * 100 
print("平均的精準度{0:.2f}%".format(average_accuracy)) 

結果如下圖:


時間為0s,這就很尷尬了。估計是資料集太小了,資料還沒反應過來就被處理了。結果是82.90%,與上一步的結果一樣。

結尾

這篇部落格主要是介紹scikit-learn的使用流程和使用方法。

GitHub地址:GitHub

參考書籍:Python資料探勘入門與實踐