任务
使用MNIST数据集,训练一个手写数字的分类器。
准备数据集
数据集是一个70000784的数组,70000张图片,每个有728个像素,可以通过resize成2828的图片展示出来。这里的所有训练过程运用的就是一条728长度的向量。但是可以看出来这种做法显然没有很好的运用到图像的二维信息,这也就是为什么卷积神经网络效果很好。
# 旧版本使用 mnist = fetch_mldata('MNIST original')
from sklearn.datasets import fetch_openml
mnist = fetch_openml('MNIST_784')
X, y = mnist["data"], mnist["target"]
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
-----------------------------
mnist['data'] #训练集70000*784,70000张黑白图像,一张大小28*28
mnist['target'] #['1','3','4'....]标签
训练一个二分类器
用一个简单的线性模型和随机梯度下降去训练一个判断是不是5的模型
import numpy as np
# 打乱顺序
shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]
# 二分类器
y_train_5 = (y_train == '5') # True for all 5s, False for all other digits.
y_test_5 = (y_test == '5')
# 用线性模型二分类,就是用二次型去进行分类
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)
sgd_clf.predict([50])
性能评估
- 交叉验证
最基本的交叉验证,“accuracy”就是精确率,所有预测中预测正确的比例。这种评价方法存在问题,就是如果我全部猜答案不是5,也会有百分之九十的精确率,所以我们需要更好的评价方式
# 交叉验证
from sklearn.model_selection import cross_val_score
# 分类问题,使用accuracy精确度作为分数
scores = cross_val_score(sgd_clf,X_train,y_train_5,scoring="accuracy", cv=3)
scores
- 准确率
就是所有检测为目标对象的样例中,检测正确的比例 - 召回率
就是所有目标里面被检测出来的比例
from sklearn.metrics import precision_score, recall_score
print(precision_score(y_train_5, y_train_pred)) #准确率,所有5中找出来的比例
recall_score(y_train_5, y_train_pred) #召回率,所有被认为5中,5的比例
- 显然,我们希望准确率和召回率都高,但是两者之间的关系却呈现跷跷板的关系
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])
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
所以我们要选择合适的阈值(使用precision_recall_curve可以获得阈值,即结构的可信程度)。有时候我们希望准确度高(比如说人脸识别),有时候我们希望召回率高(比如说质检,宁可多检,不能漏检)
- PR曲线
准确率和召回率的曲线,应尽量靠近右上角
plt.plot(precisions,recalls)
- F1 score
召回率和准确率的调和平均,可以作为检测分数
from sklearn.metrics import f1_score
f1_score(y_train_5, y_train_pred)
- ROC曲线
FPR 是反例被错误分成正例的比率
TNR是反例被正确分类的比率
应尽量靠近左上角
优先使用 PR 曲线当正例很少,或者当你关注假正例多于假反例的时候(宁可错杀一个)。其他情况使用 ROC 曲线。
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, None)
from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5, y_scores)
多分类问题
一些算法(比如随机森林分类器或者朴素贝叶斯分类器)可以直接处理多类分类问题。其他一些算法(比如 SVM 分类器或者线性分类器)则是严格的二分类器。然后,有许多策略可以让你用二分类器去执行多类分类。
一种策略,为每一种训练一个二分类,然后看哪个可信度最高。OvA
另一种,为每两个训练一个二分类,然后比赛。要训练非常多的分类器。OvO
一些算法(比如 SVM 分类器)在训练集的大小上很难扩展,所以对于这些算法,OvO 是比较好的,因为它可以在小的数据集上面可以更多地训练,较之于巨大的数据集而言。但是,对于大部分的二分类器来说,OvA 是更好的选择。
让我们来随便训练一个,除了SVM默认都是OVA
sgd_clf.fit(X_train, y_train) # y_train, not y_train_5
也可以强制使用ovo或ova策略
from sklearn.multiclass import OneVsOneClassifier
ovo_clf = OneVsOneClassifier(SGDClassifier(random_state=42))
ovo_clf.fit(X_train, y_train)
len(ovo_clf.estimators_)
随机森林这种多分类器不存在ova还是ovo的问题
多分类问题误差分析
混淆矩阵A[i][j],i类被分为j的个数
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype(np.float64)) #简单正则化
y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
conf_mx = confusion_matrix(y_train, y_train_pred)
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.show()
这张图能看出来分类器效果不错,但是想看到更有用的数据我们要进行一点处理。
首先将混淆矩阵每个值除相应类别的总数,然后用0填充对角线(因为对角线数字太大)
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()
可以看到哪些数字容易被分错
- 特例分析:我们可以把分错的数字单独拿出来,分析一下哪些情况下会分错,能不能通过一些手段增加这种特征,让训练能分辨出这些情况。
多标签分类
一张图片可能有很多标签。一个分类器被训练成识别三个人脸,Alice,Bob,Charlie;然后当它被输入一张含有 Alice 和 Bob 的图片,它应该输出 [1, 0, 1]
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)
knn_clf.predict([some_digit])
就是标签变成了多变量的数组,其他没变
我们使用F1的平均值作为评价分数,或者使用带权(某一标签数量)平均值,average=“weighted”。
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")
y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_train, cv=3)
f1_score(y_train, y_train_knn_pred, average="weighted")
多输出分类
多标签问题的泛化,每个标签有多个取值
比如输入一张带噪声的图像,输出一张干净的图像,每个点都是一个类,有0-255个值
import random as rnd
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
knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[some_index]])
plot_digit(clean_digit)