《Scikit-Learn與TensorFlow機器學(xué)習(xí)實用指南》 第03章 分類


(第一部分 機器學(xué)習(xí)基礎(chǔ))
第01章 機器學(xué)習(xí)概覽
第02章 一個完整的機器學(xué)習(xí)項目(上)
第02章 一個完整的機器學(xué)習(xí)項目(下)
第03章 分類
第04章 訓(xùn)練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學(xué)習(xí)和隨機森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí))
第9章 啟動和運行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強化學(xué)習(xí)(上)
第16章 強化學(xué)習(xí)(下)


在第一章我們提到過最常用的監(jiān)督學(xué)習(xí)任務(wù)是回歸(用于預(yù)測某個值)和分類(預(yù)測某個類別)。在第二章我們探索了一個回歸任務(wù):預(yù)測房價。我們使用了多種算法,諸如線性回歸,決策樹,和隨機森林(這個將會在后面的章節(jié)更詳細(xì)地討論)。現(xiàn)在我們將我們的注意力轉(zhuǎn)到分類任務(wù)上。

MNIST

本章中,我們會使用 MNIST 這個數(shù)據(jù)集,它有 70000 張規(guī)格較小的手寫數(shù)字圖片,由美國的高中生和美國人口調(diào)查局的職員手寫而成。這相當(dāng)于機器學(xué)習(xí)當(dāng)中的“Hello World”,人們無論什么時候提出一個新的分類算法,都想知道該算法在這個數(shù)據(jù)集上的表現(xiàn)如何。機器學(xué)習(xí)的初學(xué)者遲早也會處理 MNIST 這個數(shù)據(jù)集。

Scikit-Learn 提供了許多輔助函數(shù),以便于下載流行的數(shù)據(jù)集。MNIST 是其中一個。用下面的代碼獲取 MNIST:

>>> from sklearn.datasets import fetch_mldata
>>> mnist = fetch_mldata('MNIST original')
>>> mnist
{'COL_NAMES': ['label', 'data'],
'DESCR': 'mldata.org dataset: mnist-original',
'data': 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]], dtype=uint8),
'target': array([ 0., 0., 0., ..., 9., 9., 9.])}

一般而言,由 sklearn 加載的數(shù)據(jù)集有著相似的字典結(jié)構(gòu),這包括:

  • DESCR鍵描述數(shù)據(jù)集
  • data鍵存放一個數(shù)組,數(shù)組的一行表示一個實例,一列表示一個特征
  • target鍵存放一個標(biāo)簽數(shù)組

讓我們看一下這些數(shù)組

>>> X, y = mnist["data"], mnist["target"]
>>> X.shape
(70000, 784)
>>> y.shape
(70000,)

MNIST 有 70000 張圖片,每張圖片有 784 個特征。這是因為每個圖片都是28*28像素的,并且每個像素的值介于 0~255 之間。讓我們看一看數(shù)據(jù)集的某一個數(shù)字。你只需要將某個實例的特征向量,reshape28*28的數(shù)組,然后使用 Matplotlib 的imshow函數(shù)展示出來。


%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
some_digit = X[36000]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap = matplotlib.cm.binary, interpolation="nearest")
plt.axis("off")
plt.show()

這看起來像個 5,實際上它的標(biāo)簽告訴我們:

>>> y[36000]
5.0

圖3-1 展示了一些來自 MNIST 數(shù)據(jù)集的圖片。當(dāng)你處理更加復(fù)雜的分類任務(wù)的時候,它會讓你更有感覺。

圖3-1 MNIST數(shù)據(jù)集的一些數(shù)字圖片

先等一下!你總是應(yīng)該先創(chuàng)建測試集,并且在驗證數(shù)據(jù)之前先把測試集晾到一邊。MNIST 數(shù)據(jù)集已經(jīng)事先被分成了一個訓(xùn)練集(前 60000 張圖片)和一個測試集(最后 10000 張圖片)

X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]

讓我們打亂訓(xùn)練集。這可以保證交叉驗證的每一折都是相似(你不會期待某一折缺少某類數(shù)字)。而且,一些學(xué)習(xí)算法對訓(xùn)練樣例的順序敏感,當(dāng)它們在一行當(dāng)中得到許多相似的樣例,這些算法將會表現(xiàn)得非常差。打亂數(shù)據(jù)集將保證這種情況不會發(fā)生。

import numpy as np

shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]

訓(xùn)練一個二分類器

現(xiàn)在我們簡化一下問題,只嘗試去識別一個數(shù)字,比如說,數(shù)字 5。這個“數(shù)字 5 檢測器”就是一個二分類器,能夠識別兩類別,“是 5”和“非 5”。讓我們?yōu)檫@個分類任務(wù)創(chuàng)建目標(biāo)向量:

y_train_5 = (y_train == 5) # True for all 5s, False for all other digits.
y_test_5 = (y_test == 5)

現(xiàn)在讓我們挑選一個分類器去訓(xùn)練它。用隨機梯度下降分類器 SGD,是一個不錯的開始。使用 Scikit-Learn 的SGDClassifier類。這個分類器有一個好處是能夠高效地處理非常大的數(shù)據(jù)集。這部分原因在于SGD一次只處理一條數(shù)據(jù),這也使得 SGD 適合在線學(xué)習(xí)(online learning)。我們在稍后會看到它。讓我們創(chuàng)建一個SGDClassifier和在整個數(shù)據(jù)集上訓(xùn)練它。

from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)

SGDClassifier依賴于訓(xùn)練集的隨機程度(所以被命名為 stochastic,隨機之義)。如果你想重現(xiàn)結(jié)果,你應(yīng)該固定參數(shù)random_state

現(xiàn)在你可以用它來查出數(shù)字 5 的圖片。

>>> sgd_clf.predict([some_digit])
array([ True], dtype=bool)

分類器猜測這個數(shù)字代表 5(True)。看起來在這個例子當(dāng)中,它猜對了。現(xiàn)在讓我們評估這個模型的性能。

對性能的評估

評估一個分類器,通常比評估一個回歸器更加玄學(xué)。所以我們將會花大量的篇幅在這個話題上。有許多度量性能的方法,所以拿來一杯咖啡和準(zhǔn)備學(xué)習(xí)許多新概念和首字母縮略詞吧。

使用交叉驗證測量準(zhǔn)確性

評估一個模型的好方法是使用交叉驗證,就像第二章所做的那樣。

實現(xiàn)交叉驗證

在交叉驗證過程中,有時候你會需要更多的控制權(quán),相較于函數(shù)cross_val_score()或者其他相似函數(shù)所提供的功能。這種情況下,你可以實現(xiàn)你自己版本的交叉驗證。事實上它相當(dāng)簡單。以下代碼粗略地做了和cross_val_score()相同的事情,并且輸出相同的結(jié)果。

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone
skfolds = StratifiedKFold(n_splits=3, random_state=42)
for train_index, test_index in skfolds.split(X_train, y_train_5):
   clone_clf = clone(sgd_clf)
   X_train_folds = X_train[train_index]
   y_train_folds = (y_train_5[train_index])
   X_test_fold = X_train[test_index]
   y_test_fold = (y_train_5[test_index])
   clone_clf.fit(X_train_folds, y_train_folds)
   y_pred = clone_clf.predict(X_test_fold)
   n_correct = sum(y_pred == y_test_fold)
   print(n_correct / len(y_pred)) # prints 0.9502, 0.96565 and 0.96495

StratifiedKFold類實現(xiàn)了分層采樣(詳見第二章的解釋),生成的折(fold)包含了各類相應(yīng)比例的樣例。在每一次迭代,上述代碼生成分類器的一個克隆版本,在訓(xùn)練折(training folds)的克隆版本上進行訓(xùn),在測試折(test folds)上進行預(yù)測。然后它計算出被正確預(yù)測的數(shù)目和輸出正確預(yù)測的比例。

讓我們使用cross_val_score()函數(shù)來評估SGDClassifier模型,同時使用 K 折交叉驗證,此處讓k=3。記住:K 折交叉驗證意味著把訓(xùn)練集分成 K 折(此處 3 折),然后使用一個模型對其中一折進行預(yù)測,對其他折進行訓(xùn)練。

>>> from sklearn.model_selection import cross_val_score
>>> cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([ 0.9502 , 0.96565, 0.96495]

哇!在交叉驗證上有大于 95% 的精度(accuracy)?這看起來很令人吃驚。先別高興,讓我們來看一個非常笨的分類器去分類,看看其在“非 5”這個類上的表現(xiàn)。

from sklearn.base import BaseEstimator
class Never5Classifier(BaseEstimator):
    def fit(self, X, y=None):
        pass
    def predict(self, X):
        return np.zeros((len(X), 1), dtype=bool)

你能猜到這個模型的精度嗎?揭曉謎底:

>>> never_5_clf = Never5Classifier()
>>> cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([ 0.909 , 0.90715, 0.9128 ])

沒錯,這個笨的分類器也有 90% 的精度。這是因為只有 10% 的圖片是數(shù)字 5,所以你總是猜測某張圖片不是 5,你也會有90%的可能性是對的。

這證明了為什么精度通常來說不是一個好的性能度量指標(biāo),特別是當(dāng)你處理有偏差的數(shù)據(jù)集,比方說其中一些類比其他類頻繁得多。

混淆矩陣

對分類器來說,一個好得多的性能評估指標(biāo)是混淆矩陣。大體思路是:輸出類別A被分類成類別 B 的次數(shù)。舉個例子,為了知道分類器將 5 誤分為 3 的次數(shù),你需要查看混淆矩陣的第五行第三列。

為了計算混淆矩陣,首先你需要有一系列的預(yù)測值,這樣才能將預(yù)測值與真實值做比較。你或許想在測試集上做預(yù)測。但是我們現(xiàn)在先不碰它。(記住,只有當(dāng)你處于項目的尾聲,當(dāng)你準(zhǔn)備上線一個分類器的時候,你才應(yīng)該使用測試集)。相反,你應(yīng)該使用cross_val_predict()函數(shù)

from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)

就像 cross_val_score()cross_val_predict()也使用 K 折交叉驗證。它不是返回一個評估分?jǐn)?shù),而是返回基于每一個測試折做出的一個預(yù)測值。這意味著,對于每一個訓(xùn)練集的樣例,你得到一個干凈的預(yù)測(“干凈”是說一個模型在訓(xùn)練過程當(dāng)中沒有用到測試集的數(shù)據(jù))。

現(xiàn)在使用 confusion_matrix()函數(shù),你將會得到一個混淆矩陣。傳遞目標(biāo)類(y_train_5)和預(yù)測類(y_train_pred)給它。

>>> from sklearn.metrics import confusion_matrix
>>> confusion_matrix(y_train_5, y_train_pred)
array([[53272, 1307],
        [ 1077, 4344]])

混淆矩陣中的每一行表示一個實際的類, 而每一列表示一個預(yù)測的類。該矩陣的第一行認(rèn)為“非 5”(反例)中的 53272 張被正確歸類為 “非 5”(他們被稱為真反例,true negatives), 而其余 1307 被錯誤歸類為"是 5" (假正例,false positives)。第二行認(rèn)為“是 5” (正例)中的 1077 被錯誤地歸類為“非 5”(假反例,false negatives),其余 4344 正確分類為 “是 5”類(真正例,true positives)。一個完美的分類器將只有真反例和真正例,所以混淆矩陣的非零值僅在其主對角線(左上至右下)。

>>> confusion_matrix(y_train_5, y_train_perfect_predictions)
array([[54579, 0],
        [ 0, 5421]])

混淆矩陣可以提供很多信息。有時候你會想要更加簡明的指標(biāo)。一個有趣的指標(biāo)是正例預(yù)測的精度,也叫做分類器的準(zhǔn)確率(precision)。

公式 3-1 準(zhǔn)確率

其中 TP 是真正例的數(shù)目,F(xiàn)P 是假正例的數(shù)目。

想要一個完美的準(zhǔn)確率,一個繁復(fù)的方法是構(gòu)造一個單一正例的預(yù)測和,并確保這個預(yù)測是正確的(precision = 1/1 = 100%)。但是這什么用,因為分類器會忽略所有樣例,除了那一個正例。所以準(zhǔn)確率一般會伴隨另一個指標(biāo)一起使用,這個指標(biāo)叫做召回率(recall),也叫做敏感度(sensitivity)或者真正例率(true positive rate, TPR)。這是正例被分類器正確探測出的比率。

公式 3-2 Recall

FN 是假反例的數(shù)目。

如果你對于混淆矩陣感到困惑,圖 3-2 將對你有幫助。

圖3-2 混淆矩陣示意圖

準(zhǔn)確率與召回率

Scikit-Learn 提供了一些函數(shù)去計算分類器的指標(biāo),包括準(zhǔn)確率和召回率。

>>> from sklearn.metrics import precision_score, recall_score
>>> precision_score(y_train_5, y_pred) # == 4344 / (4344 + 1307)
0.76871350203503808
>>> recall_score(y_train_5, y_train_pred) # == 4344 / (4344 + 1077)
0.79136690647482011

當(dāng)你去觀察精度的時候,你的“數(shù)字 5 探測器”看起來還不夠好。當(dāng)它聲明某張圖片是 5 的時候,它只有 77% 的可能性是正確的。而且,它也只檢測出“是 5”類圖片當(dāng)中的 79%。

通常結(jié)合準(zhǔn)確率和召回率會更加方便,這個指標(biāo)叫做“F1 值”,特別是當(dāng)你需要一個簡單的方法去比較兩個分類器的優(yōu)劣的時候。F1 值是準(zhǔn)確率和召回率的調(diào)和平均。普通的平均值平等地看待所有的值,而調(diào)和平均會給小的值更大的權(quán)重。所以,要想分類器得到一個高的 F1 值,需要召回率和準(zhǔn)確率同時高。

公式 3-3 F1 值

為了計算 F1 值,簡單調(diào)用f1_score()

>>> from sklearn.metrics import f1_score
>>> f1_score(y_train_5, y_pred)
0.78468208092485547

F1 支持那些有著相近準(zhǔn)確率和召回率的分類器。這不會總是你想要的。有的場景你會更關(guān)心準(zhǔn)確率,而另外一些場景你會更關(guān)心召回率。舉例,如果你訓(xùn)練一個分類器去檢測視頻是否適合兒童觀看,你會傾向選擇那種即便拒絕了很多好視頻、但保證所保留的視頻都是好(高準(zhǔn)確率)的分類器,而不是那種高召回率、但讓壞視頻混入的分類器(這種情況下你或許想增加人工去檢測分類器選擇出來的視頻)。另一方面,加入你訓(xùn)練一個分類器去檢測監(jiān)控圖像當(dāng)中的竊賊,有著 30% 準(zhǔn)確率、99% 召回率的分類器或許是合適的(當(dāng)然,警衛(wèi)會得到一些錯誤的報警,但是幾乎所有的竊賊都會被抓到)。

不幸的是,你不能同時擁有兩者。增加準(zhǔn)確率會降低召回率,反之亦然。這叫做準(zhǔn)確率與召回率之間的折衷。

準(zhǔn)確率/召回率之間的折衷

為了弄懂這個折衷,我們看一下SGDClassifier是如何做分類決策的。對于每個樣例,它根據(jù)決策函數(shù)計算分?jǐn)?shù),如果這個分?jǐn)?shù)大于一個閾值,它會將樣例分配給正例,否則它將分配給反例。圖 3-3 顯示了幾個數(shù)字從左邊的最低分?jǐn)?shù)排到右邊的最高分。假設(shè)決策閾值位于中間的箭頭(介于兩個 5 之間):您將發(fā)現(xiàn)4個真正例(數(shù)字 5)和一個假正例(數(shù)字 6)在該閾值的右側(cè)。因此,使用該閾值,準(zhǔn)確率為 80%(4/5)。但實際有 6 個數(shù)字 5,分類器只檢測 4 個, 所以召回是 67% (4/6)。現(xiàn)在,如果你
提高閾值(移動到右側(cè)的箭頭),假正例(數(shù)字 6)成為一個真反例,從而提高準(zhǔn)確率(在這種情況下高達 100%),但一個真正例 變成假反例,召回率降低到 50%。相反,降低閾值可提高召回率、降低準(zhǔn)確率。

圖3-3 決策閾值與準(zhǔn)確度/召回率折衷

Scikit-Learn 不讓你直接設(shè)置閾值,但是它給你提供了設(shè)置決策分?jǐn)?shù)的方法,這個決策分?jǐn)?shù)可以用來產(chǎn)生預(yù)測。它不是調(diào)用分類器的predict()方法,而是調(diào)用decision_function()方法。這個方法返回每一個樣例的分?jǐn)?shù)值,然后基于這個分?jǐn)?shù)值,使用你想要的任何閾值做出預(yù)測。

>>> y_scores = sgd_clf.decision_function([some_digit])
>>> y_scores
array([ 161855.74572176])
>>> threshold = 0
>>> y_some_digit_pred = (y_scores > threshold)
array([ True], dtype=bool)

SGDClassifier用了一個等于 0 的閾值,所以前面的代碼返回了跟predict()方法一樣的結(jié)果(都返回了true)。讓我們提高這個閾值:

>>> threshold = 200000
>>> y_some_digit_pred = (y_scores > threshold)
>>> y_some_digit_pred
array([False], dtype=bool)

這證明了提高閾值會降調(diào)召回率。這個圖片實際就是數(shù)字 5,當(dāng)閾值等于 0 的時候,分類器可以探測到這是一個 5,當(dāng)閾值提高到 20000 的時候,分類器將不能探測到這是數(shù)字 5。

那么,你應(yīng)該如何使用哪個閾值呢?首先,你需要再次使用cross_val_predict()得到每一個樣例的分?jǐn)?shù)值,但是這一次指定返回一個決策分?jǐn)?shù),而不是預(yù)測值。

y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, 
                            method="decision_function")

現(xiàn)在有了這些分?jǐn)?shù)值。對于任何可能的閾值,使用precision_recall_curve(),你都可以計算準(zhǔn)確率和召回率:

from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)

最后,你可以使用 Matplotlib 畫出準(zhǔn)確率和召回率(圖 3-4),這里把準(zhǔn)確率和召回率當(dāng)作是閾值的一個函數(shù)。

def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
    plt.plot(thresholds, precisions[:-1], "b--", label="Precision")
    plt.plot(thresholds, recalls[:-1], "g-", label="Recall")
    plt.xlabel("Threshold")
    plt.legend(loc="upper left")
    plt.ylim([0, 1])
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.show()
圖3-4 準(zhǔn)確率和召回率和決策閾值的關(guān)系

筆記:你也許會好奇為什么準(zhǔn)確率曲線比召回率曲線更加起伏不平。原因是準(zhǔn)確率有時候會降低,盡管當(dāng)你提高閾值的時候,通常來說準(zhǔn)確率會隨之提高。回頭看圖 3-3,留意當(dāng)你從中間箭頭開始然后向右移動一個數(shù)字會發(fā)生什么: 準(zhǔn)確率會由 4/5(80%)降到 3/4(75%)。另一方面,當(dāng)閾值提高時候,召回率只會降低。這也就說明了為什么召回率的曲線更加平滑。

現(xiàn)在你可以選擇適合你任務(wù)的最佳閾值。另一個選出好的準(zhǔn)確率/召回率折衷的方法是直接畫出準(zhǔn)確率對召回率的曲線,如圖 3-5 所示。

圖3-5 準(zhǔn)確率vs召回率

可以看到,在召回率在 80% 左右的時候,準(zhǔn)確率急劇下降。你可能會想選擇在急劇下降之前選擇出一個準(zhǔn)確率/召回率折衷點。比如說,在召回率 60% 左右的點。當(dāng)然,這取決于你的項目需求。

我們假設(shè)你決定達到 90% 的準(zhǔn)確率。你查閱第一幅圖(放大一些),在 70000 附近找到一個閾值。為了作出預(yù)測(目前為止只在訓(xùn)練集上預(yù)測),你可以運行以下代碼,而不是運行分類器的predict()方法。

y_train_pred_90 = (y_scores > 70000)

讓我們檢查這些預(yù)測的準(zhǔn)確率和召回率:

>>> precision_score(y_train_5, y_train_pred_90)
0.8998702983138781
>>> recall_score(y_train_5, y_train_pred_90)
0.63991883416343853

很棒!你擁有了一個(近似) 90% 準(zhǔn)確率的分類器。它相當(dāng)容易去創(chuàng)建一個任意準(zhǔn)確率的分類器,只要將閾值設(shè)置得足夠高。但是,一個高準(zhǔn)確率的分類器不是非常有用,如果它的召回率太低!

如果有人說“讓我們達到 99% 的準(zhǔn)確率”,你應(yīng)該問“相應(yīng)的召回率是多少?”

ROC 曲線

受試者工作特征(ROC)曲線是另一個二分類器常用的工具。它非常類似于準(zhǔn)確率/召回率曲線,但不是畫出準(zhǔn)確率對召回率的曲線,ROC 曲線是真正例率(true positive rate,另一個名字叫做召回率)對假正例率(false positive rate, FPR)的曲線。FPR 是反例被錯誤分成正例的比率。它等于 1 減去真反例率(true negative rate, TNR)。TNR是反例被正確分類的比率。TNR也叫做特異性。所以 ROC 曲線畫出召回率對(1 減特異性)的曲線。

為了畫出 ROC 曲線,你首先需要計算各種不同閾值下的 TPR、FPR,使用roc_curve()函數(shù):

from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)

然后你可以使用 matplotlib,畫出 FPR 對 TPR 的曲線。下面的代碼生成圖 3-6.

def plot_roc_curve(fpr, tpr, label=None):
    plt.plot(fpr, tpr, linewidth=2, label=label)
    plt.plot([0, 1], [0, 1], 'k--')
    plt.axis([0, 1, 0, 1])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
plot_roc_curve(fpr, tpr)
plt.show()
圖3-6 ROC曲線

這里同樣存在折衷的問題:召回率(TPR)越高,分類器就會產(chǎn)生越多的假正例(FPR)。圖中的點線是一個完全隨機的分類器生成的 ROC 曲線;一個好的分類器的 ROC 曲線應(yīng)該盡可能遠(yuǎn)離這條線(即向左上角方向靠攏)。

一個比較分類器優(yōu)劣的方法是:測量ROC曲線下的面積(AUC,area under the curve)。一個完美的分類器的 ROC AUC 等于 1,而一個純隨機分類器的 ROC AUC 等于 0.5。Scikit-Learn 提供了一個函數(shù)來計算 ROC AUC:

>>> from sklearn.metrics import roc_auc_score
>>> roc_auc_score(y_train_5, y_scores)
0.97061072797174941

因為 ROC 曲線跟準(zhǔn)確率/召回率曲線(或者叫 PR)很類似,你或許會好奇如何決定使用哪一個曲線呢?一個笨拙的規(guī)則是,當(dāng)正例很少,或者當(dāng)你關(guān)注假正例多于假反例的時候,優(yōu)先使用 PR 曲線。其他情況使用 ROC 曲線。舉例子,回顧前面的 ROC 曲線和 ROC AUC 數(shù)值,你或許認(rèn)為這個分類器很棒。但是這幾乎全是因為只有少數(shù)正例(“是 5”),而大部分是反例(“非 5”)。相反,PR 曲線清楚顯示出這個分類器還有很大的改善空間(PR 曲線應(yīng)該盡可能地靠近右上角)。

讓我們訓(xùn)練一個RandomForestClassifier,然后拿它的的ROC曲線和ROC AUC數(shù)值去跟SGDClassifier的比較。首先你需要得到訓(xùn)練集每個樣例的數(shù)值。但是由于隨機森林分類器的工作方式,RandomForestClassifier不提供decision_function()方法。相反,它提供了predict_proba()方法。Skikit-Learn分類器通常二者中的一個。predict_proba()方法返回一個數(shù)組,數(shù)組的每一行代表一個樣例,每一列代表一個類。數(shù)組當(dāng)中的值的意思是:給定一個樣例屬于給定類的概率。比如,70%的概率這幅圖是數(shù)字 5。

from sklearn.ensemble import RandomForestClassifier
forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3,
                                    method="predict_proba")

但是要畫 ROC 曲線,你需要的是樣例的分?jǐn)?shù),而不是概率。一個簡單的解決方法是使用正例的概率當(dāng)作樣例的分?jǐn)?shù)。

y_scores_forest = y_probas_forest[:, 1] # score = proba of positive class
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5,y_scores_forest)

現(xiàn)在你即將得到 ROC 曲線。將前面一個分類器的 ROC 曲線一并畫出來是很有用的,可以清楚地進行比較。見圖 3-7。

plt.plot(fpr, tpr, "b:", label="SGD")
plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")
plt.legend(loc="bottom right")
plt.show()
圖3-7 比較ROC曲線

如你所見,RandomForestClassifier的 ROC 曲線比SGDClassifier的好得多:它更靠近左上角。所以,它的 ROC AUC 也會更大。

>>> roc_auc_score(y_train_5, y_scores_forest)
0.99312433660038291

計算一下準(zhǔn)確率和召回率:98.5% 的準(zhǔn)確率,82.8% 的召回率。還不錯。

現(xiàn)在你知道如何訓(xùn)練一個二分類器,選擇合適的標(biāo)準(zhǔn),使用交叉驗證去評估你的分類器,選擇滿足你需要的準(zhǔn)確率/召回率折衷方案,和比較不同模型的 ROC 曲線和 ROC AUC 數(shù)值。現(xiàn)在讓我們檢測更多的數(shù)字,而不僅僅是一個數(shù)字 5。

多類分類

二分類器只能區(qū)分兩個類,而多類分類器(也被叫做多項式分類器)可以區(qū)分多于兩個類。

一些算法(比如隨機森林分類器或者樸素貝葉斯分類器)可以直接處理多類分類問題。其他一些算法(比如 SVM 分類器或者線性分類器)則是嚴(yán)格的二分類器。然后,有許多策略可以讓你用二分類器去執(zhí)行多類分類。

舉例子,創(chuàng)建一個可以將圖片分成 10 類(從 0 到 9)的系統(tǒng)的一個方法是:訓(xùn)練10個二分類器,每一個對應(yīng)一個數(shù)字(探測器 0,探測器 1,探測器 2,以此類推)。然后當(dāng)你想對某張圖片進行分類的時候,讓每一個分類器對這個圖片進行分類,選出決策分?jǐn)?shù)最高的那個分類器。這叫做“一對所有”(OvA)策略(也被叫做“一對其他”)。

另一個策略是對每一對數(shù)字都訓(xùn)練一個二分類器:一個分類器用來處理數(shù)字 0 和數(shù)字 1,一個用來處理數(shù)字 0 和數(shù)字 2,一個用來處理數(shù)字 1 和 2,以此類推。這叫做“一對一”(OvO)策略。如果有 N 個類。你需要訓(xùn)練N*(N-1)/2個分類器。對于 MNIST 問題,需要訓(xùn)練 45 個二分類器!當(dāng)你想對一張圖片進行分類,你必須將這張圖片跑在全部45個二分類器上。然后看哪個類勝出。OvO 策略的主要有點是:每個分類器只需要在訓(xùn)練集的部分?jǐn)?shù)據(jù)上面進行訓(xùn)練。這部分?jǐn)?shù)據(jù)是它所需要區(qū)分的那兩個類對應(yīng)的數(shù)據(jù)。

一些算法(比如 SVM 分類器)在訓(xùn)練集的大小上很難擴展,所以對于這些算法,OvO 是比較好的,因為它可以在小的數(shù)據(jù)集上面可以更多地訓(xùn)練,較之于巨大的數(shù)據(jù)集而言。但是,對于大部分的二分類器來說,OvA 是更好的選擇。

Scikit-Learn 可以探測出你想使用一個二分類器去完成多分類的任務(wù),它會自動地執(zhí)行 OvA(除了 SVM 分類器,它使用 OvO)。讓我們試一下SGDClassifier.

>>> sgd_clf.fit(X_train, y_train) # y_train, not y_train_5
>>> sgd_clf.predict([some_digit])
array([ 5.])

很容易。上面的代碼在訓(xùn)練集上訓(xùn)練了一個SGDClassifier。這個分類器處理原始的目標(biāo)class,從 0 到 9(y_train),而不是僅僅探測是否為 5 (y_train_5)。然后它做出一個判斷(在這個案例下只有一個正確的數(shù)字)。在幕后,Scikit-Learn 實際上訓(xùn)練了 10 個二分類器,每個分類器都產(chǎn)到一張圖片的決策數(shù)值,選擇數(shù)值最高的那個類。

為了證明這是真實的,你可以調(diào)用decision_function()方法。不是返回每個樣例的一個數(shù)值,而是返回 10 個數(shù)值,一個數(shù)值對應(yīng)于一個類。

>>> some_digit_scores = sgd_clf.decision_function([some_digit])
>>> some_digit_scores
array([[-311402.62954431, -363517.28355739, -446449.5306454 ,
        -183226.61023518, -414337.15339485, 161855.74572176,
        -452576.39616343, -471957.14962573, -518542.33997148,
        -536774.63961222]])

最高數(shù)值是對應(yīng)于類別 5 :

>>> np.argmax(some_digit_scores)
5
>>> sgd_clf.classes_
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
>>> sgd_clf.classes_[5]
5.0

一個分類器被訓(xùn)練好了之后,它會保存目標(biāo)類別列表到它的屬性classes_ 中去,按照值排序。在本例子當(dāng)中,在classes_ 數(shù)組當(dāng)中的每個類的索引方便地匹配了類本身,比如,索引為 5 的類恰好是類別 5 本身。但通常不會這么幸運。

如果你想強制 Scikit-Learn 使用 OvO 策略或者 OvA 策略,你可以使用OneVsOneClassifier類或者OneVsRestClassifier類。創(chuàng)建一個樣例,傳遞一個二分類器給它的構(gòu)造函數(shù)。舉例子,下面的代碼會創(chuàng)建一個多類分類器,使用 OvO 策略,基于SGDClassifier

>>> from sklearn.multiclass import OneVsOneClassifier
>>> ovo_clf = OneVsOneClassifier(SGDClassifier(random_state=42))
>>> ovo_clf.fit(X_train, y_train)
>>> ovo_clf.predict([some_digit])
array([ 5.])
>>> len(ovo_clf.estimators_)
45

訓(xùn)練一個RandomForestClassifier同樣簡單:

>>> forest_clf.fit(X_train, y_train)
>>> forest_clf.predict([some_digit])
array([ 5.])

這次 Scikit-Learn 沒有必要去運行 OvO 或者 OvA,因為隨機森林分類器能夠直接將一個樣例分到多個類別。你可以調(diào)用predict_proba(),得到樣例對應(yīng)的類別的概率值的列表:

>>> forest_clf.predict_proba([some_digit])
array([[ 0.1, 0. , 0. , 0.1, 0. , 0.8, 0. , 0. , 0. , 0. ]])

你可以看到這個分類器相當(dāng)確信它的預(yù)測:在數(shù)組的索引 5 上的 0.8,意味著這個模型以 80% 的概率估算這張圖片代表數(shù)字 5。它也認(rèn)為這個圖片可能是數(shù)字 0 或者數(shù)字 3,分別都是 10% 的幾率。

現(xiàn)在當(dāng)然你想評估這些分類器。像平常一樣,你想使用交叉驗證。讓我們用cross_val_score()來評估SGDClassifier的精度。

>>> cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
array([ 0.84063187, 0.84899245, 0.86652998])

在所有測試折(test fold)上,它有 84% 的精度。如果你是用一個隨機的分類器,你將會得到 10% 的正確率。所以這不是一個壞的分?jǐn)?shù),但是你可以做的更好。舉例子,簡單將輸入正則化,將會提高精度到 90% 以上。

>>> from sklearn.preprocessing import StandardScaler
>>> scaler = StandardScaler()
>>> X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
>>> cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")
array([ 0.91011798, 0.90874544, 0.906636 ])

誤差分析

當(dāng)然,如果這是一個實際的項目,你會在你的機器學(xué)習(xí)項目當(dāng)中,跟隨以下步驟(見附錄 B):探索準(zhǔn)備數(shù)據(jù)的候選方案,嘗試多種模型,把最好的幾個模型列為入圍名單,用GridSearchCV調(diào)試超參數(shù),盡可能地自動化,像你前面的章節(jié)做的那樣。在這里,我們假設(shè)你已經(jīng)找到一個不錯的模型,你試圖找到方法去改善它。一個方式是分析模型產(chǎn)生的誤差的類型。

首先,你可以檢查混淆矩陣。你需要使用cross_val_predict()做出預(yù)測,然后調(diào)用confusion_matrix()函數(shù),像你早前做的那樣。

>>> y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
>>> conf_mx = confusion_matrix(y_train, y_train_pred)
>>> conf_mx
array([[5725, 3, 24, 9, 10, 49, 50, 10, 39, 4],
        [ 2, 6493, 43, 25, 7, 40, 5, 10, 109, 8],
        [ 51, 41, 5321, 104, 89, 26, 87, 60, 166, 13],
        [ 47, 46, 141, 5342, 1, 231, 40, 50, 141, 92],
        [ 19, 29, 41, 10, 5366, 9, 56, 37, 86, 189],
        [ 73, 45, 36, 193, 64, 4582, 111, 30, 193, 94],
        [ 29, 34, 44, 2, 42, 85, 5627, 10, 45, 0],
        [ 25, 24, 74, 32, 54, 12, 6, 5787, 15, 236],
        [ 52, 161, 73, 156, 10, 163, 61, 25, 5027, 123],
        [ 43, 35, 26, 92, 178, 28, 2, 223, 82, 5240]])

這里是一對數(shù)字。使用 Matplotlib 的matshow()函數(shù),將混淆矩陣以圖像的方式呈現(xiàn),將會更加方便。

plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()

這個混淆矩陣看起來相當(dāng)好,因為大多數(shù)的圖片在主對角線上。在主對角線上意味著被分類正確。數(shù)字 5 對應(yīng)的格子看起來比其他數(shù)字要暗淡許多。這可能是數(shù)據(jù)集當(dāng)中數(shù)字 5 的圖片比較少,又或者是分類器對于數(shù)字 5 的表現(xiàn)不如其他數(shù)字那么好。你可以驗證兩種情況。

讓我們關(guān)注僅包含誤差數(shù)據(jù)的圖像呈現(xiàn)。首先你需要將混淆矩陣的每一個值除以相應(yīng)類別的圖片的總數(shù)目。這樣子,你可以比較錯誤率,而不是絕對的錯誤數(shù)(這對大的類別不公平)。

row_sums = conf_mx.sum(axis=1, keepdims=True)
norm_conf_mx = conf_mx / row_sums

現(xiàn)在讓我們用 0 來填充對角線。這樣子就只保留了被錯誤分類的數(shù)據(jù)。讓我們畫出這個結(jié)果。

np.fill_diagonal(norm_conf_mx, 0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()

現(xiàn)在你可以清楚看出分類器制造出來的各類誤差。記住:行代表實際類別,列代表預(yù)測的類別。第 8、9 列相當(dāng)亮,這告訴你許多圖片被誤分成數(shù)字 8 或者數(shù)字 9。相似的,第 8、9 行也相當(dāng)亮,告訴你數(shù)字 8、數(shù)字 9 經(jīng)常被誤以為是其他數(shù)字。相反,一些行相當(dāng)黑,比如第一行:這意味著大部分的數(shù)字 1 被正確分類(一些被誤分類為數(shù)字 8 )。留意到誤差圖不是嚴(yán)格對稱的。舉例子,比起將數(shù)字 8 誤分類為數(shù)字 5 的數(shù)量,有更多的數(shù)字 5 被誤分類為數(shù)字 8。

分析混淆矩陣通常可以給你提供深刻的見解去改善你的分類器。回顧這幅圖,看樣子你應(yīng)該努力改善分類器在數(shù)字 8 和數(shù)字 9 上的表現(xiàn),和糾正 3/5 的混淆。比如,你可以嘗試去收集更多的數(shù)據(jù),或者你可以構(gòu)造新的、有助于分類器的特征。舉例子,寫一個算法去數(shù)閉合的環(huán)(比如,數(shù)字 8 有兩個環(huán),數(shù)字 6 有一個, 5 沒有)。又或者你可以預(yù)處理圖片(比如,使用 Scikit-Learn,Pillow, OpenCV)去構(gòu)造一個模式,比如閉合的環(huán)。

分析獨特的誤差,是獲得關(guān)于你的分類器是如何工作及其為什么失敗的洞見的一個好途徑。但是這相對難和耗時。舉例子,我們可以畫出數(shù)字 3 和 5 的例子

cl_a, cl_b = 3, 5
X_aa = X_train[(y_train == cl_a) & (y_train_pred == cl_a)]
X_ab = X_train[(y_train == cl_a) & (y_train_pred == cl_b)]
X_ba = X_train[(y_train == cl_b) & (y_train_pred == cl_a)]
X_bb = X_train[(y_train == cl_b) & (y_train_pred == cl_b)]
plt.figure(figsize=(8,8))
plt.subplot(221); plot_digits(X_aa[:25], ../images_per_row=5)
plt.subplot(222); plot_digits(X_ab[:25], ../images_per_row=5)
plt.subplot(223); plot_digits(X_ba[:25], ../images_per_row=5)
plt.subplot(224); plot_digits(X_bb[:25], ../images_per_row=5)
plt.show()

左邊兩個5*5的塊將數(shù)字識別為 3,右邊的將數(shù)字識別為 5。一些被分類器錯誤分類的數(shù)字(比如左下角和右上角的塊)是書寫地相當(dāng)差,甚至讓人類分類都會覺得很困難(比如第 8 行第 1 列的數(shù)字 5,看起來非常像數(shù)字 3 )。但是,大部分被誤分類的數(shù)字,在我們看來都是顯而易見的錯誤。很難明白為什么分類器會分錯。原因是我們使用的簡單的SGDClassifier,這是一個線性模型。它所做的全部工作就是分配一個類權(quán)重給每一個像素,然后當(dāng)它看到一張新的圖片,它就將加權(quán)的像素強度相加,每個類得到一個新的值。所以,因為 3 和 5 只有一小部分的像素有差異,這個模型很容易混淆它們。

3 和 5 之間的主要差異是連接頂部的線和底部的線的細(xì)線的位置。如果你畫一個 3,連接處稍微向左偏移,分類器很可能將它分類成 5。反之亦然。換一個說法,這個分類器對于圖片的位移和旋轉(zhuǎn)相當(dāng)敏感。所以,減輕 3/5 混淆的一個方法是對圖片進行預(yù)處理,確保它們都很好地中心化和不過度旋轉(zhuǎn)。這同樣很可能幫助減輕其他類型的錯誤。

多標(biāo)簽分類

到目前為止,所有的樣例都總是被分配到僅一個類。有些情況下,你也許想讓你的分類器給一個樣例輸出多個類別。比如說,思考一個人臉識別器。如果對于同一張圖片,它識別出幾個人,它應(yīng)該做什么?當(dāng)然它應(yīng)該給每一個它識別出的人貼上一個標(biāo)簽。比方說,這個分類器被訓(xùn)練成識別三個人臉,Alice,Bob,Charlie;然后當(dāng)它被輸入一張含有 Alice 和 Bob 的圖片,它應(yīng)該輸出[1, 0, 1](意思是:Alice 是,Bob 不是,Charlie 是)。這種輸出多個二值標(biāo)簽的分類系統(tǒng)被叫做多標(biāo)簽分類系統(tǒng)。

目前我們不打算深入臉部識別。我們可以先看一個簡單點的例子,僅僅是為了闡明的目的。

from sklearn.neighbors import KNeighborsClassifier
y_train_large = (y_train >= 7)
y_train_odd = (y_train % 2 == 1)
y_multilabel = np.c_[y_train_large, y_train_odd]
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_multilabel)

這段代碼創(chuàng)造了一個y_multilabel數(shù)組,里面包含兩個目標(biāo)標(biāo)簽。第一個標(biāo)簽指出這個數(shù)字是否為大數(shù)字(7,8 或者 9),第二個標(biāo)簽指出這個數(shù)字是否是奇數(shù)。接下來幾行代碼會創(chuàng)建一個KNeighborsClassifier樣例(它支持多標(biāo)簽分類,但不是所有分類器都可以),然后我們使用多目標(biāo)數(shù)組來訓(xùn)練它。現(xiàn)在你可以生成一個預(yù)測,然后它輸出兩個標(biāo)簽:

>>> knn_clf.predict([some_digit])
array([[False, True]], dtype=bool)

它工作正確。數(shù)字 5 不是大數(shù)(False),同時是一個奇數(shù)(True)。

有許多方法去評估一個多標(biāo)簽分類器,和選擇正確的量度標(biāo)準(zhǔn),這取決于你的項目。舉個例子,一個方法是對每個個體標(biāo)簽去量度 F1 值(或者前面討論過的其他任意的二分類器的量度標(biāo)準(zhǔn)),然后計算平均值。下面的代碼計算全部標(biāo)簽的平均 F1 值:

>>> y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_train, cv=3)
>>> f1_score(y_train, y_train_knn_pred, average="macro")
0.96845540180280221

這里假設(shè)所有標(biāo)簽有著同等的重要性,但可能不是這樣。特別是,如果你的 Alice 的照片比 Bob 或者 Charlie 更多的時候,也許你想讓分類器在 Alice 的照片上具有更大的權(quán)重。一個簡單的選項是:給每一個標(biāo)簽的權(quán)重等于它的支持度(比如,那個標(biāo)簽的樣例的數(shù)目)。為了做到這點,簡單地在上面代碼中設(shè)置average="weighted"

多輸出分類

我們即將討論的最后一種分類任務(wù)被叫做“多輸出-多類分類”(或者簡稱為多輸出分類)。它是多標(biāo)簽分類的簡單泛化,在這里每一個標(biāo)簽可以是多類別的(比如說,它可以有多于兩個可能值)。

為了說明這點,我們建立一個系統(tǒng),它可以去除圖片當(dāng)中的噪音。它將一張混有噪音的圖片作為輸入,期待它輸出一張干凈的數(shù)字圖片,用一個像素強度的數(shù)組表示,就像 MNIST 圖片那樣。注意到這個分類器的輸出是多標(biāo)簽的(一個像素一個標(biāo)簽)和每個標(biāo)簽可以有多個值(像素強度取值范圍從 0 到 255)。所以它是一個多輸出分類系統(tǒng)的例子。

分類與回歸之間的界限是模糊的,比如這個例子。按理說,預(yù)測一個像素的強度更類似于一個回歸任務(wù),而不是一個分類任務(wù)。而且,多輸出系統(tǒng)不限于分類任務(wù)。你甚至可以讓你一個系統(tǒng)給每一個樣例都輸出多個標(biāo)簽,包括類標(biāo)簽和值標(biāo)簽。

讓我們從 MNIST 的圖片創(chuàng)建訓(xùn)練集和測試集開始,然后給圖片的像素強度添加噪聲,這里是用 NumPy 的randint()函數(shù)。目標(biāo)圖像是原始圖像。

noise = rnd.randint(0, 100, (len(X_train), 784))
noise = rnd.randint(0, 100, (len(X_test), 784))
X_train_mod = X_train + noise
X_test_mod = X_test + noise
y_train_mod = X_train
y_test_mod = X_test

讓我們看一下測試集當(dāng)中的一張圖片(是的,我們在窺探測試集,所以你應(yīng)該馬上鄒眉):

左邊的加噪聲的輸入圖片。右邊是干凈的目標(biāo)圖片。現(xiàn)在我們訓(xùn)練分類器,讓它清潔這張圖片:

knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[some_index]])
plot_digit(clean_digit)

看起來足夠接近目標(biāo)圖片。現(xiàn)在總結(jié)我們的分類之旅。希望你現(xiàn)在應(yīng)該知道如何選擇好的量度標(biāo)準(zhǔn),挑選出合適的準(zhǔn)確率/召回率的折衷方案,比較分類器,更概括地說,就是為不同的任務(wù)建立起好的分類系統(tǒng)。

練習(xí)

  1. 嘗試在 MNIST 數(shù)據(jù)集上建立一個分類器,使它在測試集上的精度超過 97%。提示:KNeighborsClassifier非常適合這個任務(wù)。你只需要找出一個好的超參數(shù)值(試一下對權(quán)重和超參數(shù)n_neighbors進行網(wǎng)格搜索)。
  2. 寫一個函數(shù)可以是 MNIST 中的圖像任意方向移動(上下左右)一個像素。然后,對訓(xùn)練集上的每張圖片,復(fù)制四個移動后的副本(每個方向一個副本),把它們加到訓(xùn)練集當(dāng)中去。最后在擴展后的訓(xùn)練集上訓(xùn)練你最好的模型,并且在測試集上測量它的精度。你應(yīng)該會觀察到你的模型會有更好的表現(xiàn)。這種人工擴大訓(xùn)練集的方法叫做數(shù)據(jù)增強,或者訓(xùn)練集擴張。
  3. 拿 Titanic 數(shù)據(jù)集去搗鼓一番。開始這個項目有一個很棒的平臺:Kaggle!
  4. 建立一個垃圾郵件分類器(這是一個更有挑戰(zhàn)性的練習(xí)):
  • 下載垃圾郵件和非垃圾郵件的樣例數(shù)據(jù)。地址是Apache SpamAssassin 的公共數(shù)據(jù)集
  • 解壓這些數(shù)據(jù)集,并且熟悉它的數(shù)據(jù)格式。
  • 將數(shù)據(jù)集分成訓(xùn)練集和測試集
  • 寫一個數(shù)據(jù)準(zhǔn)備的流水線,將每一封郵件轉(zhuǎn)換為特征向量。你的流水線應(yīng)該將一封郵件轉(zhuǎn)換為一個稀疏向量,對于所有可能的詞,這個向量標(biāo)志哪個詞出現(xiàn)了,哪個詞沒有出現(xiàn)。舉例子,如果所有郵件只包含了"Hello","How","are", "you"這四個詞,那么一封郵件(內(nèi)容是:"Hello you Hello Hello you")將會被轉(zhuǎn)換為向量[1, 0, 0, 1](意思是:"Hello"出現(xiàn),"How"不出現(xiàn),"are"不出現(xiàn),"you"出現(xiàn)),或者[3, 0, 0, 2],如果你想數(shù)出每個單詞出現(xiàn)的次數(shù)。
  • 你也許想給你的流水線增加超參數(shù),控制是否剝過郵件頭、將郵件轉(zhuǎn)換為小寫、去除標(biāo)點符號、將所有 URL 替換成"URL",將所有數(shù)字替換成"NUMBER",或者甚至提取詞干(比如,截斷詞尾。有現(xiàn)成的 Python 庫可以做到這點)。
  • 然后 嘗試幾個不同的分類器,看看你可否建立一個很棒的垃圾郵件分類器,同時有著高召回率和高準(zhǔn)確率。

(第一部分 機器學(xué)習(xí)基礎(chǔ))
第01章 機器學(xué)習(xí)概覽
第02章 一個完整的機器學(xué)習(xí)項目(上)
第02章 一個完整的機器學(xué)習(xí)項目(下)
第03章 分類
第04章 訓(xùn)練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學(xué)習(xí)和隨機森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí))
第9章 啟動和運行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強化學(xué)習(xí)(上)
第16章 強化學(xué)習(xí)(下)


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容