分類-MNIST(手寫數字識別)

這是學習《Hands-On Machine Learning with Scikit-Learn and TensorFlow》的筆記,若是此筆記對該書有侵權內容,請聯繫我,將其刪除。
這裏面的內容目前條理還不是特別清析,後面有時間會更新整理一下。
下面的代碼運行環境爲jupyter + python3.6python

獲取數據

# from sklearn.datasets import fetch_mldata
# from sklearn import datasets

# mnist = fetch_mldata('MNIST   original') 
# mnist

好像下載不到它的數據,直接從網上找到它的數據,放到當面目錄下的\datasets\mldata目錄下。MNIST data的百度網盤連接: https://pan.baidu.com/s/1Np4r6uepYkPDHZsdMU4l-w 提取碼: 9dq2,若是連接失效,可在下面評論區告知我,或者本身去網上找同樣的,相信各位小夥伴的能力呀。git

輸入以下代碼:算法

from sklearn.datasets import fetch_mldata
from sklearn import datasets
import numpy as np

mnist = fetch_mldata('mnist-original', data_home = './datasets/') 
mnist

上面的代碼中的data_home表示你的數據集的文件路徑,寫的是一個相對路徑,若是你沒有將你的數據集放在你當前代碼的目錄下,你可能須要使用絕對路徑。數組

輸出:app

{'DESCR': 'mldata.org dataset: mnist-original',
 'COL_NAMES': ['label', 'data'],
 'target': array([0., 0., 0., ..., 9., 9., 9.]),
 '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)}

能夠看出,咱們成功讀到了它的數據,網上有不少的說法是錯誤的,沒有辦法讀成功,只有這個纔是正解😄。dom

上面的數據給出了一些基本的描述信息,裏面有target, data,分別是標籤和數據內容。進一步地咱們能夠看看數據和標籤的維度信息。函數

輸入以下代碼:工具

X, y = mnist['data'], mnist['target']
print(X.shape)
print(y.shape)

輸出:性能

(70000, 784)
(70000,)

從上面看出來,X是一個\(7000\times784\)的一個矩陣,通常來講,7000行表示有7000個樣本,784列,表示樣本有784這麼多個屬性。學習

%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()

output_6_0.png

說個數看起來像是5,我以爲更像是6,咱們可查看一下它的標籤。

y[36000]

輸出:

5.0

好吧,它的標籤是5,可能這個標籤寫錯了都不必定,咱們得新寫一下這個標籤,說不定能夠提升模型的準確率呢。這只是我我的在這裏開玩笑說的,不用當真哈😉。

# EXTRA
def plot_digits(instances, images_per_row=10, **options):
    size = 28
    images_per_row = min(len(instances), images_per_row)
    images = [instance.reshape(size,size) for instance in instances]
    n_rows = (len(instances) - 1) // images_per_row + 1
    row_images = []
    n_empty = n_rows * images_per_row - len(instances)
    images.append(np.zeros((size, size * n_empty)))
    for row in range(n_rows):
        rimages = images[row * images_per_row : (row + 1) * images_per_row]
        row_images.append(np.concatenate(rimages, axis=1))
    image = np.concatenate(row_images, axis=0)
    plt.imshow(image, cmap = matplotlib.cm.binary, **options)
    plt.axis("off")

plt.figure(figsize=(9,9))
example_images = np.r_[X[:12000:600], X[13000:30600:600], X[30600:60000:590]]
plot_digits(example_images, images_per_row=10)
# save_fig("more_digits_plot")
plt.show()

輸出以下圖片:

output_10_0.png

在作數據的訓練前,應該找出測試集,這裏MNIST已經幫咱們把測試集作好了。前面60000個看成訓練集,後面10000個當測試集。

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

MNIST的數據是按數字大小順序排列的,所咱們先要打亂它的順序,這樣能夠保證咱們的交叉驗證是每一次都是類似的。

import numpy as np

shuffle_index = np.random.permutation(60000)
shuffle_index

輸出:

array([52603, 56601, 42625, ..., 17778, 24267, 29358])

注: np.random.permutation 是隨機排列一個序列。上面的例子就是從0~60000的隨機序列

輸入以下代碼,打亂它的順序。

X_train, y_train = X_train[shuffle_index],y_train[shuffle_index]

訓練一個二分類器

先不作一個多類器,咱們不去識別裏面的手寫數字是0~9中的某一個數。目前作一個最簡單的,判斷它是不是5,即將數據分紅兩個類別:「5」和「非5」

首先地,咱們須要將標籤更改一下,改爲「是5」和「非5」的標籤,很簡單。

輸出下面代碼,造成一個了一個邏輯數組:

# 這是一個邏輯數組,5:True, 非5:False
y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)

如今開始用一個分類器去訓練它。用隨機梯度降低分類器SGD。用Scikit-Learn的SGDClassifier類。這個分類器有一個好處是可以高效地處理很是大的數據集。部分緣由是它每次只處理一條數據。

注: 咱們暫時不用過多地在乎SGD是個什麼樣的分類器,只須要知道它是一個分類器就好啦😀

輸入以下代碼:

from sklearn.linear_model import SGDClassifier

sgd_clf = SGDClassifier(random_state = 32)
sgd_clf.fit(X_train, y_train_5)

就這樣子,咱們訓練好了一個分類器了,就是這麼簡單,實在是太容易了,也許你都尚未反應過來,就作了一件聽起來這麼牛逼的事情😎。

對上面的代碼簡單介紹一下,第3行,就是生成一個分類器,而後給一個random_state的參數,由於這個分類器有必定的隨機性,因此它需一個隨機種子。第4行,就是將咱們的訓練數據與咱們的訓練標籤進行訓練(擬合/迴歸)。

下面是它的輸出:

SGDClassifier(alpha=0.0001, average=False, class_weight=None,
       early_stopping=False, epsilon=0.1, eta0=0.0, fit_intercept=True,
       l1_ratio=0.15, learning_rate='optimal', loss='hinge', max_iter=None,
       n_iter=None, n_iter_no_change=5, n_jobs=None, penalty='l2',
       power_t=0.5, random_state=32, shuffle=True, tol=None,
       validation_fraction=0.1, verbose=0, warm_start=False)

接下來,咱們用這個訓練好的模型來預測一下,看看它到底怎麼樣。

輸入以下代碼:

sgd_clf.predict([some_digit])

輸出:

array([ True])

這裏面的some_digit是我前的數字5,不信能夠翻到前面去看看。從輸出的結果True能夠看出來,這個模型預測正確了,確實是個5,看起來還不錯。

這個模型的準確度你爲彷佛受隨機種子的影響比較大,若是我將模型的隨機種改成42,咱們再來看一下它預測的結果是否是正確的

sgd_clf = SGDClassifier(random_state = 42)
sgd_clf.fit(X_train, y_train_5)
sgd_clf.predict([some_digit])

輸出:

array([ True])

嗯,仍是正確的,可是在你的電腦上,可能不必定,能夠試試看。

對性能的評估

下面來總體評估一下這個分類的性能。上面咱們只是讓咱們這個模型預測了一個數字,並不能表明什麼,說不定是那個模型運氣好,猜中了。咱們接下來得總體看一下它的準確率,這樣子纔有說服力。

使用交叉驗證測量準確性

交叉驗證,簡單來說就是將咱們的訓練集又細分紅好幾份,好比說咱們將它分紅3份,使其中的2份來訓練,1份用於測試,計算出它的準確率(或者其它指標)。這裏每一份都須要用做測試,也須要被看成訓練,因此要交叉3次,若是你對此還有所疑問,請百度或google一下。

在交叉驗證過程當中,有時候咱們會須要更多的控制權,相較於函數cross_val_score()或者其餘類似函數所提供的功能。下面代碼作了和cross_val_score()相同的事情

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

skfolds = StratifiedKFold(n_splits = 3, random_state = 42)
clone_clf = clone(sgd_clf)
for train_index, test_index in skfolds.split(X_train, y_train_5):
    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))

注: StratfiedKFold 類實現了分層採樣,生成的折包含了各種相應比例的樣例。在每一次迭代,上述代碼生成分類器的一個克隆,在克隆的模型上訓練,在測試折上進行預測

輸出:

0.9612

0.9531

0.9688

從上面的輸出能夠看出來,它的準確在95%以上,看起來還不錯。

下面直接使用sklearn中的庫進行交叉評估。使用cross_val_score函數來評估SGDClassifier模型。

from sklearn.model_selection import cross_val_score

cross_val_score(sgd_clf, X_train, y_train_5, cv = 3, scoring = "accuracy")

輸出:

array([0.9612, 0.9531, 0.9688])

這精度看起來還不錯,有大於95%的精度,有點讓人興奮,感受作個分類仍是挺容易的,一點都不難。可是不要高興得太早。

咱們再來看下一個很是簡單的分類器去分類,看看它在「非5」這個類上的表現。

from sklearn.base import BaseEstimator
# 這個模型的預測的策略就是將全部的數據都認爲是'非5'
class Never5Classifier(BaseEstimator):
    def fit(self,X,y=None):
        pass
    def predict(self,X):
        return np.zeros((len(X),1), dtype=bool)

這個分類器就是,無論青紅皁白,都認爲這個數字不是5,即將它歸爲非5。

never_5_clf = Never5Classifier()
cross_val_score(never_5_clf, X_train, y_train_5, cv = 3, scoring = "accuracy")

輸出:

array([0.90815, 0.9124 , 0.9084 ])

這麼一個簡單的分類器也有90%的精度😲,咱們費了大半天勁,好像也只比這個準確率高那麼一點點,有點掇敗感。

這是由於只有10%的樣本是5,其它都是非5,因此只咱們一直猜這個圖像不是5,固然有90%的精度,這叫數據不平衡。就像咱們若是在日本,站到大街上,見到人就猜他是一個日本人,咱們幾乎確定是正確的。

因此精度並非一個好的性能度量指標,特別是在咱們數據不平衡的時候。

混淆矩陣

對通常分類器來講,一人好得多的性能評估指標是混淆矩陣。大致思路是:輸出類別A被分紅類別B的次數。

爲了計算混淆矩陣,首先你須要有一系列的預測值,這樣才能將預測值與真實值作比較。你或許想在測試集上作預測。

from sklearn.model_selection import cross_val_predict

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

confusion_matrix(y_train_5, y_train_pred)
array([[54306,   273],
       [ 2065,  3356]], dtype=int64)

混淆矩陣中的每一行表示一個實際的類,而每一列表一個預測的類。該矩陣的第一行認爲"非5"中的53993張被正確地歸類爲非5(這被稱爲真反例,true negatives),而其他586被錯誤歸類爲5(這被稱爲假正例,false positive),其他3905正確分類爲"5"類(真正例,true positive)。一個完美的分類器將只有真反例和真正例,所混淆矩陣的非零值僅在其主對角線(左上至右下)。

# confusion_matrix(y_train_5, y_train_perfect_predictions)

混淆矩陣能夠提供不少信息。有時候你會想要更加簡明的指標。一個有趣的指標是正例預測的精度,也叫作分類器的準確率(precision)

\[ precision = \frac{TP}{TP + FP} \tag{3-1} \]

其中\(TP\)真正例的數目,\(FP\)假正例的數目。

以準確率通常會伴隨另外一個指標一塊兒使用,這個指標叫作召回率(recall),也叫作敏感度(sensitivity)或者真正例率(true positive rate, TPR)。這是正例被分類器正確探測出的比率。

\[ recall = \frac{TP}{TP+FN} \tag{3-2} \]

\(FN\)是假反例的數目。

from sklearn.metrics import precision_score, recall_score

print(precision_score(y_train_5, y_train_pred))
print(recall_score(y_train_5, y_train_pred))

輸出:

0.924772664645908
0.6190739715919572

這樣看起,這個分類器的準確率並不高,只有56.8%左右,並且只是分紅兩類的一個分類器,這跟咱們猜差很少。

一般結合準確率和召回率會更加方便,這個指標叫作F1值,特別是當你須要一個簡單的方法去比較兩個分類器的優劣的時時候。F1值是準確率和召回率的調和平均

\[ F1 = \frac{2}{\frac{1}{precision}+\frac{1}{recall}} = 2 \times \frac{precision \times recall}{precision + recall} = \frac{TP}{TP + \frac{FN+FP}{2}} \tag{3-3} \]

計算F1值,簡單調用f1_score()便可。

from sklearn.metrics import f1_score

f1_score(y_train_5, y_train_pred)
0.7416574585635358

F1支持那些有着相近準確率和召回率的分類(意思是隻有當準確率和召回率同樣大的時個,F1值纔會大)。但並非所的時候,咱們都關心F1值,有時候咱們只關心準確率(precision),或者有時候咱們只關心召回率(recall)。

這裏,咱們再次理解一下準確率的含義:若是一個分類器的每次幾乎都能把咱們所要分的類別準確地分類出來,那麼無疑,這個分類器的準確率是高的;何時準備率低呢,就是它把咱們所要分的類,預測錯了。好比咱們這裏的例子,咱們要預測這張手寫圖片的數字是不是5,若是那張圖真的是5,而咱們的分類器預測它是5,那麼它預測對了,固然預測對了,不是咱們區分準確率與召回率的狀況。若是將一張不是5的圖片預測成5,那麼咱們會說它個分類器不是很準,它有低準確率。

什麼是召回率?當咱們將一張是5的圖片預測成不是5,說明這個分類器仍是比較嚴格的,那和它有較低的如回率。

總的來講,準確率低的緣由就產將那些看起來像5(只是像,實際並非5)的預測成了5;而召回率低的緣由是把那些看起來不像5(其實是5,只是可能那個5寫得比較醜)預測成不是5。

在這裏,我以本身的理解,舉兩個例子,好比公司想找我的當總經理,有一羣人來應聘它。咱們這時候的目標是,找到的這我的確定是可以當總經理的,就算有的人看起來像是能當總經理,可是爲了確保萬無一失,咱們要找一個看起來很是很是像可以當總經理的人。這個時候咱們固然有着很高的準確率,由於咱們找的人幾乎確定是可以當總經理的,可是此時,咱們會犯另外一個錯誤,就是有些人確實有能力當總經理,只是咱們沒有看出來(人不可貌像),因此咱們拒絕他,所以咱們有低的召回率,這在統計學上被稱爲犯了第一類錯誤,即棄真。這樣作是合理的,由於即便棄真,但咱們保真了。

另外一種狀況是,好比警察在一羣人中想找出幾個犯罪的人,這個時候咱們就不能要超高的準確率了,由於有可能把真正的犯人放走。找犯人的原則通常是,只要他看起來像個犯人,都應該審查一下,即便最後真像大白後,他真的不是一個犯人。咱們平時聽到的寧肯錯殺一千,不可放走一個說的就是這個道理,所以這有着比較低的準確率,可是有高的召回率,這在統計學上被稱爲犯了第二類錯誤,即取僞

準備率/召回率之間的折中

y_scores = sgd_clf.decision_function([some_digit])
y_scores

輸出:
array([15905.22111141])

threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred

輸出:
array([ True])

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

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

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.1])
    
plot_precision_recall_vs_threshold(precisions,recalls,thresholds)
plt.grid()
plt

output_51_1.png

ROC曲線

受試者工做特徵(ROC)曲線是另外一個二分類器經常使用的工具。它很是相似與準確率/召回率曲線,但不是畫出準確率對召回率的曲線,,ROC曲線是真正例率(true positive rate,另外一個名字叫作召回率)對假正例率(false positive rate, FPR)的曲線。FPR是反例被錯誤分紅正例的比率。它等於1減去真反例率(true negative rate,TNR)。TNR是反例被正確分類的比率。TNR也叫作特異性。

爲了畫出ROC曲線,你首先須要計算各類不一樣閾值下的TPR、FPR,使用roc_curve()函數:

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

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

output_53_1.png

一個比較分類器之間優劣的方法是:測量ROC曲線下的面積(AUC)。一個完美的分類器的 ROC AUC 等於1,而一個純隨機分類器的ROC AUC等於0.5。Scikit-Learn提供了一個函數來計算ROC AUC:

from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5,y_scores)
0.9623990527630832
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")
y_scores_forest = y_probas_forest[:,1]
fpr_forest, tpr_forest, thresholds_forest=roc_curve(y_train_5,y_scores_forest)
plt.plot(fpr,tpr,"b:",label="SGD") 
plot_roc_curve(fpr_forest,tpr_forest,"Random Forest") 
plt.legend(loc="bottom  right") 
plt

output_57_2.png

# 將機率大於0.5的,置爲true, 不然爲false
print(precision_score(y_train_5, y_scores_forest > 0.5))
print(recall_score(y_train_5, y_scores_forest > 0.5))
0.9844298245614035
0.8280760007378712

能夠看出來,它的準確率還可,挺高的。

下面咱們將分類出更多的數字,而不只僅是5。

多類分類

二分類器只能分出兩個類,而多分類器能分出多於兩個類別的類。

一些算法(好比隨機森林分類器或者樸素貝葉斯分類器)能夠直接處理多類分類問題。其餘一些算法(好比SVM分類器或者線性分類器)則是嚴格的二分類器,而後有許多策略可讓你用二分類器去執行多類分類。

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

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

你能夠調用decision_function()方法。不是返回每一個樣例的一個數值,而是返回10個數值,一個數值對應於一個類。

some_digit_scores = sgd_clf.decision_function([some_digit])
some_digit_scores
array([[-253639.46707377, -425198.63904333, -354213.80127786,
        -229676.13263264, -376404.48500382,   15905.22111141,
        -564592.12430579, -194289.65607053, -748913.30208666,
        -597652.52038338]])

最高的數值對應類別5

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

若是你想強制Scikit-Learn使用OvO策略或者OvA策略,你可使用OneVsOneClassifier類或者OneVsRestClassifier類。建立一個樣例,傳遞一個二分類器給它的構造函數。舉例子,下面的代碼會建立一個多類分類器,使用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.])

訓練一個RandomForestClassifier一樣簡單:

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

此次Scikit-Learn沒有必要去運行OvO或者OvA, 由於隨機森林分類器可以直接將一個樣例分到多個類別。你可調用predict_proba(),獲得樣例對應的類別的機率值的列表:

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

接下來,咱們固然想評估一下這些分類器。像之前同樣,想便用交叉驗證。讓咱們用cross_val_score來評估SGDClassifier的精度。

cross_val_score(sgd_clf, X_train, y_train,cv = 3, scoring = "accuracy")
array([0.86002799, 0.8760438 , 0.88093214])

咱們能夠看到這個分類器有86.3%的精度,這個精度還不錯,比咱們隨便亂猜的精度要高出很多(若是咱們隨機猜,那麼精度只有10%)。看起來也並不差,這裏可使輸入正則化,獲得更高的精度,能夠將其精度提升到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.9080184 , 0.91049552, 0.91043657])

偏差分析

分析模型產生的偏差,首先,咱們能夠檢查混淆矩陣。須要使用cross_val_predict()作出預測,而後調用confusion_matrix()函數,像之前作的那樣

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([[5739,    3,   22,    8,    9,   50,   43,    7,   38,    4],
       [   2, 6451,   50,   23,    6,   46,    5,   14,  133,   12],
       [  58,   38, 5348,   87,   76,   26,   83,   56,  169,   17],
       [  50,   40,  134, 5300,    2,  267,   37,   64,  140,   97],
       [  25,   26,   36,    7, 5356,    9,   54,   32,   83,  214],
       [  68,   37,   34,  179,   74, 4617,  106,   30,  171,  105],
       [  35,   21,   42,    2,   39,   98, 5630,    6,   44,    1],
       [  27,   18,   66,   27,   52,   10,    7, 5793,   17,  248],
       [  58,  150,   68,  140,   16,  156,   51,   29, 5050,  133],
       [  43,   29,   24,   84,  158,   36,    3,  194,   83, 5295]],
      dtype=int64)

這裏是一堆數字,使用Matplotlib的matshow()函數,將混淆矩陣以圖像的方式呈現,將會更加方便。

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

output_80_0.png

能夠看到,幾乎全部的圖片都在對角線上,這意味着分類幾乎所有正確。現咱們只看看其偏差的圖像

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

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

output_82_0.png

如今能夠清楚看出分類器的各種偏差,其中行表明實際類別,列表明預測的類別。第八、9列很亮,這說明不少圖片被誤分紅數字8或者數字9。

分析混淆矩陣一般能夠提供深入的看法去改善分類器。回顧這幅圖,看樣子應該努力改善分類器在數字8和數字9上的表現,和糾正3/5的混淆。舉例子,你能夠嘗試去收集更多的數據,或者你能夠構造新的、有助於分類器的特徵(新的分類器的特徵,咱們能夠在數據裏面加一個新的列———這至關添加了一個新的屬性,好比字數8有兩個環,數字6有一個,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)
# save_fig("error_analysis_digits_plot")
plt.show()

output_84_0.png

多標籤分類

到目前爲止,全部的樣例都老是被分配到僅一個類(好比咱們前面訓練的分類,要麼輸出是1,要麼是2,3,...,9,一次只能輸出一個類別)。有些狀況下,你也許想讓你的分類器給一個樣例輸出多個類別。好比有時候咱們想識別某我的臉,想判斷它的性別,還有是否爲中國人,這就有兩個類別了([gender, isChinese])。。這種輸出多個二值標籤的分類系統被叫作多標籤分類系統。

目前不打算深刻臉部識別。咱們能夠先看一個簡單點的例子。

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)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=None, n_neighbors=5, p=2,
           weights='uniform')

這段代碼創造了一個y_multilabel數組,裏面包含兩個目標標籤。第一個標籤指出這個數字是否爲大數(便是否爲7,8,9),第二個標籤指示這個數字是否爲奇數

knn_clf.predict([some_digit])
array([[False,  True]])

這個預測器預測對,咱們輸入的數據表明5,5不是一個大數,可是是一個奇數。

# 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")

多輸出分類

咱們即將討論最後一種分類任務,被叫作"多輸出-多分類"(或者簡稱多輸出分類)。在這裏每個標籤能夠是多類別的(好比咱們前面所舉的例子)

爲了說明這點,咱們創建一個系統,它能夠去除圖片當中的噪音。它將一張混有噪音的圖片做爲輸入,期待它輸出一張乾淨的數字圖片,用一個像素強度的數組表示,就像 MNIST圖片那樣。注意到這個分類器的輸出是多標籤的(一個像素一個標籤)和每一個標籤能夠有多個值 (像素強度取值範圍從0到255)。因此它是一個多輸出分類系統的例子。

咱們從MNIST的圖版建立訓練集和測試集開始,而後給圖片的像素強度添加噪聲,這裏是用NumPy的randint()函數。目標圖像是原始圖像。

noise = np.random.randint(0, 100, (len(X_train), 784))
X_train_mod = X_train + noise
noise = np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise
y_train_mod = X_train
y_test_mod = X_test
def plot_digit(data):
    image = data.reshape(28, 28)
    plt.imshow(image, cmap = matplotlib.cm.binary,
               interpolation="nearest")
    plt.axis("off")
    
some_index = 5500

plt.subplot(121); plot_digit(X_test_mod[some_index])
plt.subplot(122); plot_digit(y_test_mod[some_index])
# save_fig("noisy_digit_example_plot")
plt.show()

output_94_0.png

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

output_95_0.png

上面的圖片看起來還行,比較接近原圖片,去噪的效果還能夠。

到這裏,分類的知識學得差很少了。

相關文章
相關標籤/搜索