文章目錄
MNIST
數據介紹:本章使用MNIST數據集,這是一組由美國高中生和人口調查局員工手寫的70000個數字的圖片。每張圖像都用其代表的數字標記。這個數據集被廣爲使用,因此也被稱作是機器學習領域的“Hello World”:但凡有人想到了一個新的分類算法,都會想看看在MNIST上的執行結果。因此只要是學習機器學習的人,早晚都要面對MNIST。
# 使用sklearn的函數來獲取MNIST數據集
from sklearn.datasets import fetch_openml
import numpy as np
import os
import time
# to make this notebook's output stable across runs
np.random.seed(42)
# To plot pretty figures
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
# 爲了顯示中文
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False
# 耗時巨大
def sort_by_target(mnist):
reorder_train=np.array(sorted([(target,i) for i, target in enumerate(mnist.target[:60000])]))[:,1]
reorder_test=np.array(sorted([(target,i) for i, target in enumerate(mnist.target[60000:])]))[:,1]
mnist.data[:60000]=mnist.data[reorder_train]
mnist.target[:60000]=mnist.target[reorder_train]
mnist.data[60000:]=mnist.data[reorder_test+60000]
mnist.target[60000:]=mnist.target[reorder_test+60000]
下面這一部分有點耗時,需要一點耐心,如果運行時間過長建議重新運行,之前有一次運行了15分鐘纔出來,沒想明白爲什麼
a=time.time()
mnist=fetch_openml('mnist_784',version=1,cache=True)
mnist.target=mnist.target.astype(np.int8)
sort_by_target(mnist)
b=time.time()
b-a
運行時間:
41.92971682548523
mnist["data"], mnist["target"]
(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.]]),
array([0, 0, 0, …, 9, 9, 9], dtype=int8))
mnist.data.shape
(70000, 784)
X,y=mnist["data"],mnist["target"]
X.shape
(70000, 784)
y.shape
(70000,)
28*28
784
# 展示圖片
def plot_digit(data):
image = data.reshape(28, 28)
plt.imshow(image, cmap = mpl.cm.binary,
interpolation="nearest")
plt.axis("off")
some_digit = X[36000]
plot_digit(X[36000].reshape(28,28))
y[36000]
5
# 更好看的圖片展示
def plot_digits(instances,images_per_row=10,**options):
size=28
# 每一行有一個
image_pre_row=min(len(instances),images_per_row)
images=[instances.reshape(size,size) for instances in instances]
# 有幾行
n_rows=(len(instances)-1) // image_pre_row+1
row_images=[]
n_empty=n_rows*image_pre_row-len(instances)
images.append(np.zeros((size,size*n_empty)))
for row in range(n_rows):
# 每一次添加一行
rimages=images[row*image_pre_row:(row+1)*image_pre_row]
# 對添加的每一行的額圖片左右連接
row_images.append(np.concatenate(rimages,axis=1))
# 對添加的每一列圖片 上下連接
image=np.concatenate(row_images,axis=0)
plt.imshow(image,cmap=mpl.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)
plt.show()
接下來,我們需要創建一個測試集,並把其放在一邊。
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
同樣,我們還需要對訓練集進行洗牌,這樣可以保證交叉驗證的時候,所有的摺疊都差不多。此外,有些機器學習算法對訓練示例的循序敏感,如果連續輸入許多相似的實例,可能導致執行的性能不佳。給數據洗牌,正是爲了確保這種情況不會發生。
import numpy as np
shuffer_index=np.random.permutation(60000)
X_train,y_train=X_train[shuffer_index],y_train[shuffer_index]
訓練一個二分類器
現在,我們先簡化問題,只嘗試識別一個數字,比如數字5,那麼這個"數字5檢測器",就是一個二分類器的例子,它只能區分兩個類別:5和非5。先爲此分類任務創建目錄標量
y_train_5=(y_train==5)
y_test_5=(y_test==5)
接着挑選一個分類器並開始訓練。一個好的選擇是隨機梯度下降(SGD)分類器,使用sklearn的SGDClassifier類即可。這個分類器的優勢是:能夠有效處理非常大型的數據集。這部分是因爲SGD獨立處理訓練實例,一次一個(這也使得SGD非常適合在線學習任務)。
from sklearn.linear_model import SGDClassifier
sgd_clf=SGDClassifier(max_iter=5,tol=-np.infty,random_state=42)
sgd_clf.fit(X_train,y_train_5)
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=5,
n_iter_no_change=5, n_jobs=None, penalty=‘l2’, power_t=0.5,
random_state=42, shuffle=True, tol=-inf, validation_fraction=0.1,
verbose=0, warm_start=False)
sgd_clf.predict([some_digit])
array([ True])
性能考覈
評估分類器比評估迴歸器要困難很多,因此本章將會用很多篇幅來討論這個主題,同時也會涉及許多性能考覈的方法。
使用交叉驗證測量精度
隨機交叉驗證和分層交叉驗證效果對比
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.96225, 0.9645 , 0.94765])
# 類似於分層採樣,每一折的分佈類似
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))
0.96225
0.9645
0.94765
我們可以看到兩種交叉驗證的準確率都達到了95%上下,看起來很神奇,不過在開始激動之前,讓我們來看一個蠢笨的分類器,將所有圖片都預測爲‘非5’
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%的圖像是數字5,所以只要猜一張圖片不是5,那麼有90%的時間都是正確的,簡直超過了大預言家。
這說明,準確率通常無法成爲分類器的首要性能指標,特別是當我們處理偏斜數據集的時候(也就是某些類別比其他類更加頻繁的時候)
混淆矩陣
評估分類器性能的更好的方法是混淆矩陣。總體思路就是統計A類別實例被分成B類別的次數。例如,要想知道分類器將數字3和數字5混淆多少次,只需要通過混淆矩陣的第5行第3列來查看。
要計算混淆矩陣,需要一組預測才能將其與實際目標進行比較。當然可以通過測試集來進行預測,但是現在我們不動它(測試集最好保留到項目的最後,準備啓動分類器時再使用)。最爲代替,可以使用cross_val_predict()函數:
cross_val_predict
和 cross_val_score
不同的是,前者返回預測值,並且是每一次訓練的時候,用模型沒有見過的數據來預測
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([[53417, 1162], [ 1350, 4071]], dtype=int64)
上面的結果表明:第一行所有’非5’(負類)的圖片中,有53417被正確分類(真負類),1162,錯誤分類成了5(假負類);第二行表示所有’5’(正類)的圖片中,有1350錯誤分類成了非5(假正類),有4071被正確分類成5(真正類).
一個完美的分類器只有真正類和真負類,所以其混淆矩陣只會在其對角線(左上到右下)上有非零值
y_train_perfect_predictions = y_train_5
confusion_matrix(y_train_5, y_train_perfect_predictions)
array([[54579, 0], [ 0, 5421]], dtype=int64)
混淆矩陣能提供大量信息,但有時我們可能會希望指標簡潔一些。正類預測的準確率是一個有意思的指標,它也稱爲分類器的精度(如下)。
其中TP是真正類的數量,FP是假正類的數量。
做一個簡單的正類預測,並保證它是正確的,就可以得到完美的精度(精度=1/1=100%)
這並沒有什麼意義,因爲分類器會忽略這個正實例之外的所有內容。因此,精度通常會與另一個指標一起使用,這就是召回率,又稱爲靈敏度或者真正類率(TPR):它是分類器正確檢測到正類實例的比率(如下):
FN是假負類的數量
精度和召回率
# 使用sklearn的工具度量精度和召回率
from sklearn.metrics import precision_score, recall_score
precision_score(y_train_5, y_train_pred)
0.7779476399770686
recall_score(y_train_5, y_train_pred)
0.7509684560044272
我們可以看到,這個5-檢測器,並不是那麼好用,大多時候,它說一張圖片爲5時,只有77%的概率是準確的,並且也只有75%的5被檢測出來了
下面,我們可以將精度和召回率組合成單一的指標,稱爲F1分數。
要計算F1分數,只需要調用f1_score()即可
from sklearn.metrics import f1_score
f1_score(y_train_5, y_train_pred)
0.7642200112633752
F1分數對那些具有相近的精度和召回率的分類器更爲有利。
精度/召回率權衡
在分類中,對於每個實例,都會計算出一個分值,同時也有一個閾值,大於爲正例,小於爲負例。通過調節這個閾值,可以調整精度和召回率。
y_scores = sgd_clf.decision_function([some_digit])
y_scores
array([150526.40944343])
threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
array([ True])
threshold = 200000
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
array([False])
# 返回決策分數,而不是預測結果
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
method="decision_function")
y_scores.shape
(60000,)
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", linewidth=2)
plt.plot(thresholds, recalls[:-1], "g-", label="Recall", linewidth=2)
plt.xlabel("Threshold", fontsize=16)
plt.title("精度和召回率VS決策閾值", fontsize=16)
plt.legend(loc="upper left", fontsize=16)
plt.ylim([0, 1])
plt.figure(figsize=(8, 4))
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.xlim([-700000, 700000])
plt.show()
可以看見,隨着閾值提高,召回率下降了,也就是說,有真例被判負了,精度上升,也就是說,有部分原本被誤判的負例,被丟出去了。
你可以會好奇,爲什麼精度曲線會比召回率曲線要崎嶇一些,原因在於,隨着閾值提高,精度也有可能會下降 4/5 => 3/4(雖然總體上升)。另一方面,閾值上升,召回率只會下降。
現在就可以輕鬆通過選擇閾值來實現最佳的精度/召回率權衡了。還有一種找到最好的精度/召回率權衡的方法是直接繪製精度和召回率的函數圖。
def plot_precision_vs_recall(precisions, recalls):
plt.plot(recalls, precisions, "b-", linewidth=2)
plt.xlabel("Recall", fontsize=16)
plt.title("精度VS召回率", fontsize=16)
plt.ylabel("Precision", fontsize=16)
plt.axis([0, 1, 0, 1])
plt.figure(figsize=(8, 6))
plot_precision_vs_recall(precisions, recalls)
plt.show()
可以看見,從80%的召回率往右,精度開始急劇下降。我們可能會盡量在這個陡降之前選擇一個精度/召回率權衡–比如召回率60%以上。當然,如何選擇取決於你的項目。
假設我們決定瞄準90%的精度目標。通過繪製的第一張圖(放大一點),得出需要使用的閾值大概是70000.要進行預測(現在是在訓練集上),除了調用分類器的predict方法,也可以使用這段代碼:
y_train_pred_90 = (y_scores > 70000)
precision_score(y_train_5, y_train_pred_90)
0.8712083540527101
recall_score(y_train_5, y_train_pred_90)
0.6463752075262866
現在我們就有了一個精度接近90%的分類器了,如果有人說,“我們需要99%的精度。”,那麼我就要問:“召回率是多少?”
ROC曲線
還有一種經常與二元分類器一起使用的工具,叫做受試者工作特徵曲線(簡稱ROC)。它與精度/召回率曲線非常相似,但繪製的不是精度和召回率,而是真正類率(召回率的另一種稱呼)和假正類率(FPR)。FPR是被錯誤分爲正類的負類實例比率。它等於1-真負類率(TNR),後者正是被正確分類爲負類的負類實例比率,也稱爲奇異度。因此ROC曲線繪製的是靈敏度和(1-奇異度)的關係
~ | 1 | 0 |
---|---|---|
1 | TP | FN |
0 | FP | TN |
# 使用 roc_curve()函數計算多種閾值的TPR和FPR
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', fontsize=16)
plt.ylabel('True Positive Rate', fontsize=16)
plt.figure(figsize=(8, 6))
plot_roc_curve(fpr, tpr)
plt.show()
這裏同樣面對一個折中權衡:召回率(TPR)很高,分類器產生的假正類(FPR)就越多。虛線表示純隨機的ROC曲線;一個優秀的分類器(向左上角)。
有一種比較分類器的方式是測量曲線下面積(AUC)。完美的ROC AUC等於1,純隨機分類的ROC AUC等於0.5
from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5, y_scores)
0.9562435587387078
ROC曲線和精度/召回率(或PR)曲線非常相似,因此,你可能會問,如何決定使用哪種曲線。
一個經驗法則是,當正類非常少見或者你更關注假正類而不是假負類時,應該選擇PR曲線,反之選擇ROC曲線。
例如,看前面的ROC曲線圖時,以及ROC AUC分數時,你可能會覺得分類器真不錯。但這主要是應爲跟負類(非5)相比,正類(數字5)的數量真的很少。相比之下,PR曲線清楚地說明分類器還有改進的空間(曲線還可以更接近右上角)
訓練一個隨機森林分類器,並計算ROC和ROC AUC分數
# 具體RF的原理,第七章介紹
from sklearn.ensemble import RandomForestClassifier
forest_clf = RandomForestClassifier(n_estimators=10, 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] # score = proba of positive class
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5,y_scores_forest)
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, "b:", linewidth=2, label="SGD")
plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")
plt.title("SGD和RL的ROC曲線對比")
plt.legend(loc="lower right", fontsize=16)
plt.show()
roc_auc_score(y_train_5, y_scores_forest)
0.9931243366003829
測量精度和召回率
y_train_pred_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3)
precision_score(y_train_5, y_train_pred_forest)
0.9852973447443494
recall_score(y_train_5, y_train_pred_forest)
0.8282604685482383
多類別分類器
二元分類器在兩個類別中區分,而多類別分類器(也稱爲多項分類器),可以區分兩個以上的類別。
隨機森林算法和樸素貝葉斯分類器可以直接處理多個類別。也有一些嚴格的二元分類器,比如支持向量分類器或線性分類器。但有多種策略,可以讓我們用幾個二元二類器實現多類別分類的目的
例如:我們可以訓練0-9的10個二元分類器組合,那個分類器給的高,就分爲哪一類,這稱爲一對多(OvA)策略
另一種方法,是爲每一對數字訓練一個二元分類器:一個用來區分0-1,一個區分0-2,一個區分1-2,依次類推。這稱爲一對一(OvO)策略,解決N分類,需要(N)*(N-1)/2分類器,比如MNIST問題,需要45個分類器。OvO的主要優點在於每個分類器只需要用到部分訓練集對其必須區分的兩個類別進行訓練。
有些算法(例如支持向量機算法),在數據規模增大時,表現糟糕,因此對於這類算法,OvO是一個優秀的選擇,由於在較小的訓練集上分別訓練多個分類器比在大型數據集上訓練少數分類器要快得多。但對於大多數二元分類器,OvA策略還是更好的選擇。
# 使用0-9進行訓練,在sgd內部,sklearn使用了10個二元分類器,
#獲得它們對圖片的決策分數,然後選擇最高的類別
sgd_clf.fit(X_train, y_train)
sgd_clf.predict([some_digit])
array([5], dtype=int8)
我們可以看到 sgd對輸入的結果輸出了10個預測分數,而不是1個
some_digit_scores = sgd_clf.decision_function([some_digit])
some_digit_scores
array([[-152619.46799791, -441052.22074349, -249930.3138537 ,
-237258.35168498, -447251.81933158, 120565.05820991,
-834139.15404835, -188142.48490477, -555223.79499145,
-536978.92518594]])
np.argmax(some_digit_scores)
5
訓練分類器的時候,目標類別的列表會存儲在classes_這個屬性中,按值的大小進行排序
sgd_clf.classes_
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int8)
強制使用OVO策略
from sklearn.multiclass import OneVsOneClassifier
ovo_clf = OneVsOneClassifier(SGDClassifier(max_iter=5, tol=-np.infty, random_state=42))
ovo_clf.fit(X_train, y_train)
ovo_clf.predict([some_digit])
array([5], dtype=int8)
len(ovo_clf.estimators_)
45
隨機森林的多分類,不需要OvA或者OVO策略
forest_clf.fit(X_train, y_train)
forest_clf.predict([some_digit])
array([5], dtype=int8)
forest_clf.predict_proba([some_digit])
array([[0.1, 0. , 0. , 0.1, 0. , 0.8, 0. , 0. , 0. , 0. ]])
對分類器進行評估
cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
array([0.84993001, 0.81769088, 0.84707706])
評測結果大概都爲80%以上,如果是隨機分類器,準確率大概是10%左右,所以這個結果不是太糟糕,但是依然有提升的空間,比如使用標準化,進行簡單的縮放
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.91211758, 0.9099955 , 0.90643597])
錯誤分析
如果這是一個真正的項目,我們將遵循第二章機器學習項目清單的步驟:探索數據準備的選項,嘗試多個模型,列出最佳模型並使用GridSearchCV對超參數進行微調,儘可能自動化,等等。在這裏,假設我們已經找到一個有潛力的模型,現在希望找到一些方法,對其進一步改進。方法之一就是分析其類型錯誤。
首先,看一下混淆矩陣
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([[5749, 4, 22, 11, 11, 40, 36, 11, 36, 3],
[ 2, 6490, 43, 24, 6, 41, 8, 12, 107, 9],
[ 53, 42, 5330, 99, 87, 24, 89, 58, 159, 17],
[ 46, 41, 126, 5361, 1, 241, 34, 59, 129, 93],
[ 20, 30, 35, 10, 5369, 8, 48, 38, 76, 208],
[ 73, 45, 30, 194, 64, 4614, 106, 30, 170, 95],
[ 41, 30, 46, 2, 44, 91, 5611, 9, 43, 1],
[ 26, 18, 73, 30, 52, 11, 4, 5823, 14, 214],
[ 63, 159, 69, 168, 15, 172, 54, 26, 4997, 128],
[ 39, 39, 27, 90, 177, 40, 2, 230, 78, 5227]],
dtype=int64)
def plot_confusion_matrix(matrix):
"""If you prefer color and a colorbar"""
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(111)
cax = ax.matshow(matrix)
fig.colorbar(cax)
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()
5稍微暗一點,可能意味着數據集中5的圖片少,也可能是分類器在5上的執行效果不行。實際上,這二者都屬實。
讓我們把焦點都放在錯誤上。首先,我們需要將混淆矩陣中的每個值都除以相應類別中的圖片數,這樣比較的而是錯誤率,而不是錯誤的絕對值(後者對圖片數量較多的類別不公平)
row_sums = conf_mx.sum(axis=1, keepdims=True)
norm_conf_mx = conf_mx / row_sums
行表示實際類別,列表示預測的類別,可以看到 8 9 列比較亮,容易其他數字容易被分錯爲8 9, 8 9 行業比較亮,說明 8 9 容易被錯誤分爲其他數字。此外3 容易被錯分爲 5,5也容易被錯分爲4
np.fill_diagonal(norm_conf_mx, 0) # 填充主對稱軸
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()
分析混淆矩陣,通常可以幫助我們深入瞭解如何改進分類器。通過上面的圖,我們可以花費更多時間來改進8 9的分類,以及修正 3 5 的混淆上。
例如,可以試着收集更多這些數字的訓練集,
或者開發新特徵來改進分類器–舉個例子,寫一個算法來計算閉環的數量,比如(8有兩個,6有一個,5沒有)。
再或者,對圖片進行預處理,讓某些模式更加突出,比如閉環之類的。
分析單個錯誤也可以爲分類器提供洞察:它在做什麼?爲什麼失敗?但這通常更加困難和耗時。例如,我們來看看數字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()
我們可以看到,雖然有一些數字容易混淆,但大多數,還是比較好分類的,但算法還是會分錯。因爲SGD模型是一個線性模型,它所做的就是爲每一個像素分配一個各個類別的權重,當它看到新的圖像時,將加權後的像素強度彙總,從而得到一個分數進行分類。而數字3和5只在一部分像素位上有區別,所以分類器很容易將其搞混.
數字3和5之間的主要區別在於連接頂線和下方弧線中間的小線條的位置。如果我們寫的數字3將連續點略往左移,分類器就可能將其分類爲5,反之亦然。換言之,這個分類器對圖像位移和旋轉非常敏感,因此,減少3 5混淆的方法之一是對數字進行預處理,確保他們位於中心位置,並且沒有旋轉。這也有助於減少其他錯誤。
多標籤分類
到目前位置,每個實例都只有一個輸出,但某些情況下,我們需要分類器爲每個實例產出多個類別,比如,爲照片中的每個人臉附上一個標籤。
假設分類器經過訓練,已經可以識別三張臉 A B C,那麼當看到A和C的合照時,應該輸出[1,0,1],這種輸出多個二元標籤的分類系統成爲多標籤分類系統
下面以k近鄰算法爲例(不是所有的分類器都支持多標籤)
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’)
knn_clf.predict([some_digit])
array([[False, True]])
結果正確,5顯然小於7,同時是奇數
評估多標籤分類器的方法很多,如何選擇正確的度量指標取決於我們的項目。比如方法之一是測量每個標籤的F1分數(或者是之前討論過的任何其他二元分類器指標),然後簡單的平均。
# 耗時巨大
y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3, n_jobs=-1)
f1_score(y_multilabel, y_train_knn_pred, average="macro")
結果如下:
這裏假設了所有的標籤都是同等重要,但實際的數據可能並不均衡,可以修改average="weighted"
,來給每個標籤設置一個等於其自身支持的權重
多輸出分類
現在,我們將討論最後一種分類任務–多輸出多分類任務(簡稱爲多輸出分類)。簡單而言,它是多標籤分類的泛化,其標籤也可以是多種類別的(比如有兩個以上的值)
說明:構建一個去除圖片中噪聲的系統。給它輸入一個帶噪聲的圖片,它將(希望)輸出一張乾淨的數字圖片,跟其他MNIST圖片一樣,以像素強度的一個數組作爲呈現方式。
需要注意的是:這個分類器的輸出時多個標籤(一個像素點一個標籤),每一個標籤有多個值(0-255)。所以這是一個多輸出分類器系統的例子。
創建訓練集和測試集,使用Numpy的randint 來給Mnist圖片的像素強度增加噪聲。目標是將圖片還原爲原始圖片。
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
some_index = 5500
plt.subplot(121); plot_digit(X_test_mod[some_index])
plt.subplot(122); plot_digit(y_test_mod[some_index])
plt.show()
運行結果如下:
左邊爲添加噪聲後。
knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[some_index]])
plot_digit(clean_digit)
運行結果如下: