1. 程式人生 > >資料探勘入門系列教程(九)之基於sklearn的SVM使用

資料探勘入門系列教程(九)之基於sklearn的SVM使用

目錄

  • 介紹
  • 基於SVM對MINIST資料集進行分類
    • 使用SVM
  • SVM分析垃圾郵件
    • 載入資料集
    • 分詞
    • 構建詞雲
    • 構建資料集
    • 進行訓練
    • 交叉驗證
    • 煉丹術
  • 總結
    • 參考

介紹

在上一篇部落格:資料探勘入門系列教程(八點五)之SVM介紹以及從零開始公式推導中,詳細的講述了SVM的原理,並進行了詳細的數學推導。在這篇部落格中,主要是應用SVM,使用SVM進行資料分類,不會涉及到SVM的解釋,so,如果對svm並不是特別瞭解的話,非常建議先去看我的上一篇部落格(or其他博主的部落格),然後再來看這一篇部落格。因為在這篇並不是自己實現SVM而是基於sklearn中的svm包來進行應用。因此,我們可能使用幾行程式碼可能就可以對資料集進行訓練了。

我們不僅要知其然,更要知其所以然。

在這一章部落格中,我們會使用SVM做兩個任務:

  • 基於SVM對MINIST資料集進行分類。
  • 基於SVM對垃圾郵件進行判斷

基於SVM對MINIST資料集進行分類

在前面神經網路的部落格中,我們基於pybrain使用神經網路對MINIST手寫資料集進行分類,但是最後結果並不是很好(可以說得上是比較差了),只有:

這次我們使用SVM來進行繼續操作。資料集在前面的部落格中已經進行說明,就不再贅述。

直接看程式碼吧:

使用SVM

下面的程式碼沒有什麼好說的,就是載入下載處理好的資料集,然後在將資料集分割成訓練集和測試集(在Github中有這兩個資料集,先解壓再使用【其中dataset是壓縮包,需要解壓】):

import numpy as np
X = np.load("./Data/dataset.npy")
y = np.load("./Data/class.npy")
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(X,y,random_state=14 )

然後我們就可以使用SVM進行訓練了:

from sklearn import svm
predictor = svm.SVC(gamma='scale', C=1.0, decision_function_shape='ovr', kernel='rbf')
# 進行訓練
predictor.fit(x_train, y_train)

同樣關於SVM的官網介紹在這裡。關於svm包具體的使用可以看官方文件,官方文件寫的還是蠻詳細的。

這裡來解釋一下程式碼:

svm有很多型別的Estimators如下:

在這裡我們選擇SVC,也就是說使用SVM進行分類。具體的使用在這裡。關於程式碼中引數的介紹:

  1. C = 1.0

    在上一章部落格中,我們提到的軟間隔支援向量機中提到了以下公式:

    \[\begin{equation}\begin{aligned} \mathcal{L}(\boldsymbol{w}, b, \boldsymbol{\xi}, \boldsymbol{\alpha}, \boldsymbol{\beta}):=& \frac{1}{2} \boldsymbol{w}^{\top} \boldsymbol{w}+C \sum_{i=1}^{m} \xi_{i} \\ &+\sum_{i=1}^{m} \alpha_{i}\left(1-\xi_{i}-y_{i}\left(\boldsymbol{w}^{\top} \boldsymbol{\phi}\left(\boldsymbol{x}_{i}\right)+b\right)\right) \\ &+\sum_{i=1}^{m} \beta_{i}\left(-\xi_{i}\right) \end{aligned}\end{equation} \tag{18} \]

    其中C就是用來平衡結構風險和經驗風險的。

  2. kernel='rbf'

    kernel代表的是核函式,“rbf”代表的就是(高斯)徑向基函式核(英語:Radial basis function kernel)表示式如下:

    \[\begin{equation}K\left(\mathbf{x}, \mathbf{x}^{\prime}\right)=\exp \left(-\gamma\left\|\mathbf{x}-\mathbf{x}^{\prime}\right\|_{2}^{2}\right)\end{equation} \]

  3. gamma='scale'

    在rbf核函式中,我們有一個變數\(\gamma\),gamma='scale'代表\(\gamma = \frac{1}{(n\_features * X.var()) }\)

  4. decision_function_shape='ovr'

    在SVM的介紹中,我們詳細的推導過SVM二分類的問題,但是如果類別是多個(大於3),那麼怎麼辦呢?(比如說MINIST資料集中一共有0~9一共十個類別),此時我們可以使用一對一(one vs one),一對多(one vs rest)的方法來解決。

    • ovo

       其做法就是在任意兩個類樣本之間設計一個SVM,因此\(k\)個類別的樣本就需要設計\(\frac{k(k-1)}{2}\)個SVM。最後進行預測分類的時候,哪一個類別劃分的次數最多,則就判定為該類別。

    • ovr

       訓練時依次把某個類別的樣本歸為一類,其他剩餘的樣本歸為另一類,這樣k個類別的樣本就構造出了k個SVM。分類時將未知樣本分類為離超平面最遠的那類。

fit()就是表示對資料集進行訓練。

再然後,我們進行預測並使用F1進行評估:

# 預測結果
result = predictor.predict(x_test)
# 進行評估
from sklearn.metrics import f1_score
print("F-score: {0:.2f}".format(f1_score(result,y_test,average='micro')))

最後結果如下

\(97 \%\),這個結果還是不錯的。

本來呢,這篇部落格到這裡就ok了,怎麼使用也介紹完了,但是我覺得這篇部落格也太少了點,因此又決定再水一點內容。

SVM分析垃圾郵件

簡單點來說,我們就是想通過一封郵件是不是垃圾郵件。一共有1w+1條資料(50001垃圾郵件資料,5k正常郵件的資料)。資料集下載地址:GitHub

部分資料展示如下:

其中每一行資料就是一封郵件。

載入資料集

載入資料集還是挺簡單的,程式碼如下(資料集在我的GitHub中):

# 垃圾郵件檔案地址
spam_data_path = "./Data/resource/spam_5000.utf8"
# 正常郵件檔案地址
ham_data_path = "./Data/resource/ham_5000.utf8"

with open(spam_data_path,encoding='utf-8') as f:
    spam_txt_list = f.readlines()
with open(ham_data_path,encoding="utf-8") as f:
    ham_txt_list= f.readlines()

這裡還需要介紹一個概念——停止詞(百度百科介紹):

人類語言包含很多功能詞。與其他詞相比,功能詞沒有什麼實際含義。最普遍的功能詞是限定詞(“the”、“a”、“an”、“that”、和“those”),這些詞幫助在文字中描述名詞和表達概念,如地點或數量。介詞如:“over”,“under”,“above” 等表示兩個詞的相對位置。

這些功能詞的兩個特徵促使在搜尋引擎的文字處理過程中對其特殊對待。第一,這些功能詞極其普遍。記錄這些詞在每一個文件中的數量需要很大的磁碟空間。第二,由於它們的普遍性和功能,這些詞很少單獨表達文件相關程度的資訊。如果在檢索過程中考慮每一個詞而不是短語,這些功能詞基本沒有什麼幫助。

在資訊檢索中,這些功能詞的另一個名稱是:停用詞(stopword)。稱它們為停用詞是因為在文字處理過程中如果遇到它們,則立即停止處理,將其扔掉。將這些詞扔掉減少了索引量,增加了檢索效率,並且通常都會提高檢索的效果。停用詞主要包括英文字元、數字、數學字元、標點符號及使用頻率特高的單漢字等。

這裡我們使用的是百度的停用詞表,資料是來自GitHub,當然在我的GitHub上面,已經將這個詞表上傳上去了。

stop_word_path = "./Data/resource/stopword.txt"
with open(stop_word_path,encoding='utf-8') as f:
    # 去除空格以及換行符
    stop_words = f.read().strip()

分詞

什麼是分詞呢?對於中文來說,分詞就是將連續的一串子序列(句子)分成一個一個的詞。比如說”我喜歡你“可以分成”我“,”喜歡“,”你“。實際上在計算機中對中文進行分詞還是滿困難的。因為有很多歧義,新詞等等。這裡我們使用jieba庫進行分詞,舉例使用如下:

import jieba

a = "請不要把陌生人的些許善意,視為珍稀的瑰寶,卻把身邊親近人的全部付出,當做天經地義的事情,對其視而不見"
cut_a = jieba.cut(a)
print(list(cut_a))

結果如下:

使用jieba對垃圾郵件和正常郵件進行分詞程式碼如下(去除分詞中的停詞以及部分詞):

import jieba

spam_words = []
# 垃圾郵件
for spam_txt in spam_txt_list:
    words = []
    cut_txts  = jieba.cut(spam_txt)
    for cut_txt in cut_txts:
        # 判斷分詞是否是字母表組成的,是否是換行符,並且是否在停詞表中
        if(cut_txt.isalpha() and cut_txt!="\n" and cut_txt not in stop_words):
            words.append(cut_txt)
    # 將片語成句子
    sentence = " ".join(words)
    spam_words.append(sentence)

spam_words部分資料結果如下(資料型別為list):

也就是說,我們將每一封垃圾郵件資料分成了一個一個的詞(在詞的中間使用空格分開),然後組成這一封郵件的詞的特徵。

同理我們處理正常郵件資料:

import jieba

ham_words = []
for ham_txt in ham_txt_list:
    words = []
    cut_txts  = jieba.cut(ham_txt)
    for cut_txt in cut_txts:
        if(cut_txt.isalpha() and cut_txt!="\n" and cut_txt not in stop_words):
            words.append(cut_txt)
    sentence = " ”.join(words)
    ham_words.append(sentence)

構建詞雲

這個沒什麼好說的,就是使用WordCloud構建詞雲。text是一個字串,WordCloud會自動使用空格或者逗號對text進行分割。

%pylab inline
from wordcloud import WordCloud
import matplotlib.pyplot as plt
# 詞雲展示
def showWordCloud(text):
    wc = WordCloud(
        background_color = "white",
        max_words = 200,
        # 為了顯示中文,使用字型
        font_path = "./Data/resource/simhei.ttf",
        min_font_size = 15,
        max_font_size = 50,
        width = 600
    )
    wordcloud = wc.generate(text)
    plt.imshow(wordcloud, interpolation="bilinear")
    plt.axis("off")

展示垃圾郵件詞雲我們將資料list變成使用空格連線的字串。

showWordCloud(" ".join(spam_words))

展示正常郵件資料

showWordCloud(" ".join(ham_words))

構建資料集

通過前面的步驟我們已經得到了郵件進行分詞之後的結果。在SVM中我們知道,每一條資料的特徵的個數是一樣多的(也就是他們擁有相同的特徵,但是可能特徵值不同),但是很明顯對於文字資料,每一封郵件的特徵詞明顯是不一樣的。這裡我們可以想一想在資料探勘入門系列教程(七)之樸素貝葉斯進行文字分類中,我們使用了DictVectorizer轉換器將特徵字典轉換成了一個矩陣,這裡的資料是list資料,因此我們選擇CountVectorizer將list資料轉換成舉證。

from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
import numpy as np
from collections import defaultdict
data = []
data.extend(ham_words)
data.extend(spam_words)
# binary預設為False,一個關鍵詞在一篇文件中可能出現n次,如果binary=True,非零的n將全部置為1
# max_features 對所有關鍵詞的出現的頻率進行降序排序,只取前max_features個作為關鍵詞集
vectorizer = CountVectorizer(binary=False,max_features=1500)
result = vectorizer.fit_transform(data)

然後我們在加上列對應的名字(非必須,不影響訓練):

# 詞彙表,為字典型別,key為詞彙,value為索引
vocabulary = vectorizer.vocabulary_  
result = pd.DataFrame(result.toarray())
# 對索引進行從小到大的排序
colnames = sorted(vocabulary.items(),key = lambda item:item[1])
colname = []
for i in colnames:
    colname.append(i[0])
result.columns = colname

最後部分的result的資料如下所示(0代表此詞在郵件中沒有出現,非0則代表出現):

其中index中\([0,5000)\)是正常的郵件資料,\([5000,10001]\)是垃圾郵寄資料。

進行訓練

from sklearn.model_selection import train_test_split
from sklearn.svm import SVC

# 使用0,1劃分垃圾郵件和正常郵件
labels = []
labels.extend(np.ones(5000))
labels.extend(np.zeros(5001))

# 劃分訓練集和測試集
train,test,trainlabel,testlabel = train_test_split(result,labels,random_state=14)
predictor = SVC(gamma='scale', C=1.0, decision_function_shape='ovr', kernel='rbf')
predictor.fit(train,trainlabel)
predict_lable = predictor.predict(test)

最後進行評估結果為:

print("the accurancy is :",np.mean(predict_lable == testlabel))

交叉驗證

當然我們還可以進行交叉驗證來進行評估:

from sklearn.model_selection import cross_val_score
predictor = SVC(gamma='scale', C=1.0, decision_function_shape='ovr', kernel='rbf')
scores = cross_val_score(predictor,result,labels,scoring='f1')
print("Score: {}".format(np.mean(scores)))

這個準確度可以說是槓槓的:

煉丹術

繼續水一波,來試一下選擇不同數量的features,然後再觀察不同數量的features對精度的影響。

from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

labels = []
labels.extend(np.ones(5000))
labels.extend(np.zeros(5001))

# 畫圖的兩個軸
scores = []
indexs = []

data = []
data.extend(ham_words)
data.extend(spam_words)


for i in range(1,3000,50):
    # 轉換器
    vectorizer = CountVectorizer(binary=False,max_features=i)
    result = vectorizer.fit_transform(data)
    
    train,test,trainlabel,testlabel = train_test_split(result,labels,random_state=14)
    # 劃分訓練集和測試集    
    predictor = SVC(gamma='scale', C=1.0, decision_function_shape='ovr', kernel='rbf')
    predictor.fit(train,trainlabel)
    score = predictor.score(test,testlabel)
    scores.append(score)
    indexs.append(i)

然後,畫圖即可:


plt.plot(indexs,scores)  
plt.show()  

總的來說,結果還不錯咯,500個以上的features就可以達到越\(98\%\)以上的精度。

總結

啊,終於水完了!!

這一篇部落格主要是介紹基於sklearn的SVM的使用。可以很明顯的看到,基本上只需要幾行程式碼就ok了。因此我們更應該去關注svm的原理而不是簡單的學會呼叫包然後就表示:“啊!我會了!SVM真簡單”。實際上,如果我們不瞭解原理的話,我們甚至連調包都不知道里面的引數代表的含義是什麼。

專案地址:Github

參考

  • wiki:徑向基函式核
  • SVM實現多分類的三種方案
  • sklearn 實現中文資料集的垃圾郵件分類
  • 停止詞