用Scikit-learn和TensorFlow進行機器學習(三)

分類

一、MNIST

MNIST數據集:70000 張規格較小的手寫數字圖片。

二、獲取數據

1、從網絡獲取

from sklearn.datasets import fetch_mldata

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

輸出結果

{'__header__': b'MATLAB 5.0 MAT-file Platform: posix, Created on: Sun Mar 30 03:19:02 2014', '__version__': '1.0', '__globals__': [], 'mldata_descr_ordering': array([[array(['label'], dtype='<U5'), array(['data'], dtype='<U4')]],
      dtype=object), '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), 'label': array([[0., 0., 0., ..., 9., 9., 9.]])}

一般而言,sklearn 加載的數據集有着相似的字典結構,包括:__header____version____globals__mldata_descr_orderingdatalabel

2、本地讀取

會出現無法現在的情況,本博客提供數據集資源:
傳送門 鏈接: https://pan.baidu.com/s/1VLD1CmMqWIoDotqf-9umUA 提取碼: exw9

from sklearn.datasets import fetch_mldata
import scipy.io as sio
import numpy as np

mnist = sio.loadmat('./datasets/mnist/mnist-original.mat')
print(mnist)

X, y = mnist["data"].T, mnist["label"].T
print(X.shape)
print(y.shape)

import matplotlib.pyplot as plt
import matplotlib

## 查看樣例
some_digit = X[39000]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap=matplotlib.cm.binary, interpolation="nearest")
plt.axis("off")
plt.show()
print(y[39000])


## 創建測試集
#  前 60000 張圖片爲訓練集
#  最後 10000 張圖片爲測試集
X_train, X_test, y_train, y_test = X[:,60000], X[60000,:], y[:60000], y[60000:]

## 亂序
import numpy as np

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

輸出結果

{'__header__': b'MATLAB 5.0 MAT-file Platform: posix, Created on: Sun Mar 30 03:19:02 2014', '__version__': '1.0', '__globals__': [], 'mldata_descr_ordering': array([[array(['label'], dtype='<U5'), array(['data'], dtype='<U4')]],
      dtype=object), '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), 'label': array([[0., 0., 0., ..., 9., 9., 9.]])}
(70000, 784)
(70000, 1)
[[6.]]

在這裏插入圖片描述

三、訓練一個二分類器

二分類:類別爲 “是6” 和 “非6”

from sklearn.linear_model import SGDClassifier

## 創建分類標籤
y_train_6 = (y_train == 6)
y_test_6 = (y_test == 6)

## 隨機梯度下降 SGD 分類器
#  SGD 一次只處理一條數據 ==》在線學習(Online Learning)
sgd_clf = SGDClassifier(random_state=2019)
sgd_clf.fit(X_train, y_train_6)

## 預測
print(sgd_clf.predict([some_digit]))

輸出結果

[ True]

四、性能評估

1、交叉驗證——精度

K折交叉驗證:將訓練集分成K折,然後使用一個模型對其中一折進行預測,對其他折進行訓練。

(1)輪子版 cross_val_score()

過程:StratifiedKFold 類實現了分層採樣,生成的折(fold)包含了各類相應比例的樣例。在每一次迭代,下面的代碼生成分類器的一個克隆版本,在訓練折(training folds)的克隆版本上進行訓練,在測試折(test folds)上進行測試。最後計算準確率

from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.base import clone

skfolds = StratifiedShuffleSplit(n_splits=3, random_state=2019)
for train_index, test_index in skfolds.split(X_train, y_train_6):
    clone_clf = clone(sgd_clf)
    X_train_folds = X_train[train_index]
    y_train_folds = (y_train_6[train_index])
    X_test_fold = X_train[test_index]
    y_test_fold = (y_train_6[test_index])
    clone_clf.fit(X_train_folds, y_train_folds.ravel())
    y_pred = clone_clf.predict(X_test_fold)
    n_correct = sum(y_pred == y_test_fold.ravel())
    print(n_correct / len(y_pred))

輸出結果

0.982
0.9753333333333334
0.976

(2)函數版 cross_val_score()

from sklearn.model_selection import cross_val_score
score = cross_val_score(sgd_clf, X_train, y_train_6, cv=3, scoring="accuracy")
print(score)  ## 精度
# [0.9820009  0.97985    0.98024901]

輸出結果

[0.97780111 0.982      0.98429921]

(3)笨分類器

from sklearn.base import BaseEstimator
class Never6Classifier(BaseEstimator):
    def fit(self, X, y=None):
        pass
    def predict(self, X):
        return np.zeros((len(X), 1), dtype=bool)
never_6_clf = Never6Classifier()
score6 = cross_val_score(never_6_clf, X_train, y_train_6, cv=3, scoring="accuracy")
print(score6)

輸出結果

[0.90045 0.9028  0.90085]

由於數據的分佈,致使笨分類器也有90%的精度
==》精度通常不是很好的性能度量指標,特別是處理有偏差的數據集,eg:數據不平衡:其中一些類比其他類頻繁得多

2、混淆矩陣

cross_val_predict() 函數同樣使用 K 折交叉驗證。返回每一個測試數據的預測值;
confusion_matrix() 函數,可獲得一個混淆矩陣,參數爲groundtruth和預測值

思想:類別 A 被分類成類別 B 的次數,eg:爲了知道分類器將 5 誤分爲 3 的次數, 你需要查看混淆矩陣的第五行第三列。

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_6, cv=3)
print(confusion_matrix(y_train_6, y_train_pred))

輸出結果

[[53419   663]
 [  529  5389]]

解讀:

  • 混淆矩陣中的每一行表示一個實際的類, 而每一列表示一個預測的類;
    • 該矩陣的第一行認爲“非6”( 反例) 中的 53419 張被正確歸類爲 “非 6”( 他們被稱爲真反例, true negatives) , 而其餘663被錯誤歸類爲"是 6" ( 假正例, false positives) 。 第二行認爲“是 6” ( 正例) 中的 529 被錯誤地歸類爲“非 6”( 假反例, false negatives) , 其餘 5389 正確分類爲 “是 6”類( 真正例, true positives)
  • 完美的分類器將只有真反例和真正例, 所以混淆矩陣的非零值僅在其主對角線( 左上至右下)

(1)準確率(precision)

precision=TPTP+FPprecision=\frac{TP}{TP+FP}
其中,TP表示真正例的數目,FP表示假正例的數目

(2)召回率(recall)

召回率,也稱敏感度(sensitivity)或者真正例率(true positive rate,TPR):正例被分類器正確探測出的比例。
recall=TPTP+FNrecall=\frac{TP}{TP+FN}
其中,FN表示假反例的數目。
在這裏插入圖片描述

3、準確率與召回率

from sklearn.metrics import precision_score, recall_score

precision = precision_score(y_train_6, y_train_pred)
recall = recall_score(y_train_6, y_train_pred)
print("The precision is ", precision)
print("The recall is ", recall)

輸出結果

The precision is  0.8694196428571429
The recall is  0.921426157485637

4、F1值

F1值是準確率和召回率的調和平均。調和平均會給小的值更大的權重,若要得到一個高的F1值,需要召回率和準確率同時高。
F1=21precision+1recall=2precisionrecallprecision+recall=TPTP+FN+FP2F1=\frac{2}{\frac{1}{precision}+\frac{1}{recall}}=2*\frac{precision*recall}{precision +recall}=\frac{TP}{TP+\frac{FN+FP}{2}}

調用 f1_score() 即可獲得F1值

from sklearn.metrics import f1_score

f1 = f1_score(y_train_6,y_train_pred)
print("The F1 score is ", f1)

輸出結果

The F1 score is  0.8615836283567914

5、準確率/召回率之間的折衷——PR曲線

根據使用的場景不同,會更注重召回率或準確率,增加準確率會降低召回率,反之亦然。
==》準確率與召回率之間的折衷

預測過程:通過將預測值與閾值進行對比,分別正例和反例。通過降低閥值可以提高召回率、降低準確率。

sklearn中通過設置決策分數的方法,調用 decision_function() 方法,該方法返回每一個樣例的分數值,然後基於這個分數值,使用自定義閥值做出預測。

y_scores = sgd_clf.decision_function([some_digit])
print(y_scores)

## 設置閥值1
threshould = 0
y_some_digit_pred = (y_scores > threshould)
print(y_some_digit_pred)

## 設置閥值2
threshould = 200000
y_some_digit_pred = (y_scores > threshould)
print(y_some_digit_pred)

輸出結果

[97250.73888009]
[ True]
[False]

==》提高閥值會降低召回率
==》閥值選擇

from sklearn.metrics import precision_recall_curve

## 返回決策分數,而非預測值
y_scores = cross_val_predict(sgd_clf, X_train, y_train_6, cv=3, method="decision_function")
precisions, recalls, threshoulds = precision_recall_curve(y_train_6, y_scores)

def plot_precision_recall_vs_threshold(precisions, recalls, threshoulds):
    plt.plot(threshoulds, precisions[:-1],'b--', label="Precision")
    plt.plot(threshoulds, recalls[:-1],"g-", label="Recall")
    plt.xlabel("Threshold")
    plt.legend(loc="upper left")
    plt.ylim([0, 1])
plot_precision_recall_vs_threshold(precisions, recalls, threshoulds)
plt.show()

# 要達到90%的準確率
y_train_pred_90 = (y_scores > 70000)
precision = precision_score(y_train_6, y_train_pred_90)
recall = recall_score(y_train_6, y_train_pred_90)
print("The precision is ", precision)
print("The recall is ", recall)

輸出結果
在這裏插入圖片描述

The precision is  0.9231185706551164
The recall is  0.8643122676579925

6、ROC 曲線

受試者工作特徵( ROC) 曲線是真正例率(true positive rate,TPR,也稱召回率)對假正例率(false positive rate,FPR)的曲線。

需要計算不同閥值下的TPR、FPR,使用roc_curve()函數

## ROC曲線
from sklearn.metrics import roc_curve
fpr, tpr, threshoulds = roc_curve(y_train_6, 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')
    plt.legend(loc="lower right")
plot_roc_curve(fpr, tpr)
plt.show()

在這裏插入圖片描述

比較分類器之間優劣的方法:測量ROC曲線下的面積(AUC)——完美分類器 ROC AUC等於1,一個純隨機分類器的ROC AUC等於0.5

from sklearn.metrics import roc_auc_score
auc = roc_auc_score(y_train_6, y_scores)
print(auc)

輸出結果

0.9859523237334556

7、PR曲線 vs. ROC曲線

優先使用PR曲線當正例很少或關注假正例多於假反例的時候,其他情況使用ROC曲線

from sklearn.ensemble import RandomForestClassifier
## 不提供decision_function()方法,提供predict_proba()方法
forest_clf = RandomForestClassifier(random_state=2019)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_6, cv=3, method="predict_proba")

# 使用正例的概率作爲樣例的分數
y_scores_forest = y_probas_forest[:, 1]
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_6, 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.show()

auc_forest = roc_auc_score(y_train_6, y_scores_forest)
print("The AUC is ", auc_forest)

在這裏插入圖片描述
輸出結果

The AUC is  0.9956826118210167

分析:RandomForest的ROC曲線比SGDClassifier好:它更靠近左上角。

四、多類分類

可以直接處理多分類器的算法:隨機森林、樸素貝葉斯
嚴格二分類器:SVM、線性分類器

1、二分類器 ==》多類分類器

(eg:要分爲10類)

  • 一對所有(OvA)策略:訓練10個分類器,每個對應一個分類的類別(類別1與其他,類別2與其他…)
  • 一對一(OvO)策略:對每個分類類別都訓練一個二分類器。若有N個類,需要訓練 N*(N-1)/2 個分類器。
    • 優點:每個分類器只需要在訓練集的部分數據上面進行訓練。這部分數據是它所需要區分的那兩類對應的數據。

對於一些算法(eg:SVM)在訓練集上的大小很難擴展==》OvO(可在小數據集上更多的訓練)
大數據集==》OvA

在sklearn中,使用二分類器完成多分類,自動執行OvA(SVM爲OvO)

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

some_digit_scores = sgd_clf.decision_function([some_digit])
print(some_digit_scores)
## 最大值的類別
print(np.argmax(some_digit_scores))
## 獲取目標類別
print(sgd_clf.classes_)
print(sgd_clf.classes_[6])

輸出結果

[6.]
[[-493795.59394766 -316594.71495827  -59032.18005876 -300444.77319706
  -434956.4672297  -292368.411729    276453.49558919 -750703.98392662
  -296673.25971762 -565079.84324395]]
6
[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
6.0

強制使用OvO或OvA策略:OneVsOneClassifier, OneVsRestClassifier

## 創建基於SGDClassifier的OvO策略的分類器
from sklearn.multiclass import OneVsOneClassifier
ovo_clf = OneVsOneClassifier(SGDClassifier(random_state=2019))
ovo_clf.fit(X_train, y_train)
ovo = ovo_clf.predict([some_digit])
print(ovo)
print(len(ovo_clf.estimators_))  # 獲得分類器的個數

## 訓練一個RandomForestClassifier
forest_clf.fit(X_train, y_train)
forest = forest_clf.predict([some_digit])
print(forest)
# 得到樣例對應的類別的概率值的列表
forest_proba = forest_clf.predict_proba([some_digit])
print(forest_proba)
# 交叉驗證評估分類器
forest_score = cross_val_score(forest_clf, X_train, y_train, cv=3, scoring="accuracy")
print(forest_score)

## 加入預處理:將輸入正則化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
score_std = cross_val_score(forest_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")
print(score_std)

輸出結果

[6.]
45
[6.]
[[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]
[0.940012   0.93944697 0.94039106]
[0.940012   0.93949697 0.94034105]

五、誤差分析

當得到一個不錯的模型並需要改善它,則需要分析模型產生的誤差類型

1、檢查混淆矩陣

cross_val_predict() 做出預測 =》 confusion_matrix() 計算混淆矩陣 =》 matshow()顯示混淆矩陣

y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
conf_mx = confusion_matrix(y_train, y_train_pred)
print(conf_mx)
## 以圖像的形式顯示混淆矩陣
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()

輸出結果
在這裏插入圖片描述

2、分析混淆矩陣

分析:數字5對應的格子比其他數字要暗淡許多。
可能原因:1. 數據集中數字5的圖片比較少;2.分類器對於數字5的表現不如其他數字好

比較錯誤率,而不是絕對的錯誤數。方法:將混淆矩陣 的每一個值除以相應類別(真實值的個數)的圖片的總數目。

rows_sums = conf_mx.sum(axis=1, keepdims=True)
norm_conf_mx = conf_mx / rows_sums
## 使用0對對角線進行填充
np.fill_diagonal(norm_conf_mx, 0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
plt.show()

在這裏插入圖片描述
注意:行代表實際類別,列代表預測的類別。不是嚴格對稱的。
第8、9列亮,表示許多圖片被誤分類爲數字8或數字9;特別黑,代表大多數被正確分類;將數字 8 誤分類爲數字 5 的數量, 有更多的數字 5 被誤分類爲數字 8。
==》努力改善分類器在數字8和數字9上的表現,糾正3/5的混淆。
==》收集數據、構建新的特徵、對輸入進行預處理(eg:圖片預處理來確保它們可以很好地中心化和不過度旋轉)

六、多標籤分類

輸出多個標籤的分類系統稱爲多標籤分類系統。

1、訓練預測

from sklearn.neighbors import KNeighborsClassifier

# 創建 y_multilabel 數組,裏面包含兩個目標標籤。
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)
pred_knn = knn_clf.predict([some_digit])
print(pred_knn)

輸出結果
數字6不是大數,同時不是奇數

[[False False]]

2、評估

評估分類器、選擇正確的度量標準

對每個個體標籤去度量 F1 值,然後計算平均值。

y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_train, cv=3)
f1_score_knn = f1_score(y_train, y_train_knn_pred, average="macro")
print(f1_score_knn)

輸出結果

0.9684568539645069

標籤的權重,eg:標籤權重等於支持度(該標籤的樣例的數目),將 average="weighted"

七、多輸出分類

多輸出-多類分類,簡稱多輸出分類。

例子:圖片去噪,輸出是多標籤的(一個像素一個標籤)和每個標籤可以有多個值(像素強度取值範圍從0到255),所以是一個多輸出分類系統。

import random as rnd
## 添加噪聲
noise_train = rnd.randint(0, 100, len(X_train), 784)
noise_test = rnd.randint(0, 100, len(X_test), 784)
X_train_mod = X_train + noise_train
X_test_mod = X_test + noise_test
y_train_mod = X_train
y_test_mod = X_test

## 訓練、預測
knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict(X_test_mod[some_index])
plot_digit(clean_digit)

輸出結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章