阅读第三章
开始时间:2019-05-03
1、MNIST数据集
一组由美国高中生和人口调查局员工手写的70000个数字的图片。每张图像都用其代表的数字标
记。每张图片为2828像素,每个特征代表了一个像素点的强度。共计2828=784个特征。
被称为机器学习领域的“hello world”
1.1、Scikit-Learn加载的数据集的特征
Scikit-Learn加载的数据集通常具有类似的字典结构,包括:
·DESCR(describe,描述)键,描述数据集
·data键,包含一个数组,每个实例为一行,每个特征为一列
·target键,包含一个带有标记的数组
1.2、在开始深入研究数据之前应该先创建测试集
事实上MNIST数据集已经分成训练集(前6万张图像)和测试集(最后1万张图像)了:
X_train,X_test,y_train,y_test=X[:60000],X[60000:],y[:60000],y[60000:]
1.3、洗牌
import numpy as np
#随机排列(置换)
shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]
2、训练一个二元分类器
先简化问题,只尝试一个数字。
二元分类器。
2.1、先为分类任务创建目标向量
y_train_5 = (y_train == 5) # True for all 5s, False for all other digits.
y_test_5 = (y_test == 5)
2.2、随机梯度下降(SGD)
Scikit-learn的SGDClassifier类
这个分类器的优势:能够有效处理非常大型的数据集。这部分是因为SGD独立处理训练实例,一次一个(这也使得SGD非常适合在线学习),稍后我们将会看到。此时先创建一个SGDClassifier并在整个训练集上进行训练:
SGDClassifier在训练时是完全随机的(因此得名“随机”),如果你希望得到可复现的结果,需要设置参数random_state。
3、性能考核
评估分类器比评估回归器要困难的多
3.1、使用交叉验证测量精度
记住,K-fold交叉验证的意思是将训练集分解成K个折叠(在本例中,为3折),然后每次留其中1个折叠进行预测,剩余的折叠用来训练(参见第2章)
相比于cross_val_score()这一类交叉验证的函数,如果希望自己来控制交叉验证的进行,那么可以这样做:
#从选择模块导入k-折分层抽样器
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] #前60000图片中抽取出训练集折叠
y_train_folds = (y_train_5[train_index]) #后10000图片中抽取出训练集折叠
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
所有折叠交叉验证的准确率(正确预测的比率)超过95%?看起来挺神奇的,是吗?不过在你开始激动之前,我们来看一个蠢笨的分类器,它将每张图都分类成“非5”:
这书读着很有趣,原书作者功力深厚,书籍的译者功力也不容小觑!
没错,准确率超过90%!这是因为只有大约10%的图像是数字5,所以如果你猜一张图不是5,90%的时间你都是正确的,简直超越了大预言家!
这说明准确率通常无法成为分类器的首要性能指标,特别是当你处理偏斜数据集(skewed dataset)的时候(即某些类比其他类更为频繁)。
3.2、混淆矩阵(更好的评估分类器的方法)
总体思路就是统计A类别实例被分成为B类别的次数。例如,要想知道分类器将数字3和数字5混淆多少次,只需要通过混淆矩阵的第5行第3列来查看。
混淆矩阵中的行表示实际类别,列表示预测类别。
要计算混淆矩阵,需要先有一组预测才能将其与实际目标进行比较。当然可以通过测试集来进行预测,但是现在先不要动它(测试集最好留到项目最后,准备启动分类器时再使用)。作为替代,可以使用cross_val_predict()函数:
3.2.1、cross_val_prodict() 交叉预测
返回每个折叠的预测
与cross_val_score()函数一样,cross_val_predict()函数同样执行K-fold交叉验证,但返回的不是评估分数,而是每个折叠的预测。这意味着对于每个实例都可以得到一个干净的预测(“干净”的意思是模型预测时使用的数据,在其训练期间从未见过)。
from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
现在,可以使用confusion_matrix()函数来获取混淆矩阵了。只需要给出目标类别(y_train_5)和预测类别(y_train_pred)即可:
>>> from sklearn.metrics import confusion_matrix
>>> confusion_matrix(y_train_5, y_train_pred)
array([[53272, 1307],
[ 1077, 4344]])
混淆矩阵中的行表示实际类别,列表示预测类别。本例中第一行
表示所有“非5”(负类)的图片中:53272张被正确地分为“非5”类别
(真负类),1307张被错误地分类成了“5”(假正类);第二行表示
所有“5”(正类)的图片中:1077张被错误地分为“非5”类别(假负
类),4344张被正确地分在了“5”这一类别(真正类)。一个完美的
分类器只有真正类和真负类,所以它的混淆矩阵只会在其对角线(左
上到右下)上有非零值:
3.2.2、精度
TP是真正类的数量,FP是假正类的数量。
3.2.3、召回率(recall)也称为灵敏度(sensitivity)或真正类率(TPR)
它是分类器正确检测到的正类实例的比率
FN是假负类的数量。
3.2.4、精度和召回率组合成一个单一的指标:F1分数
>>> 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
当你需要一个简单的方法来比较两种分类器时,这是个非常不错的指标。F1分数是精度和召回率的谐波平均值。
>>> from sklearn.metrics import f1_score
>>> f1_score(y_train_5, y_pred)
0.78468208092485547
3.2.4.1、谐波平均值
正常的平均值平等对待所有的值,而谐波平均值会给予较低的值更高的权重。因此,只有当召回率和精度都很高时,分类器才能得到较高的F1分数。
3.3、精度/召回率权衡
F1分数对那些具有相近的精度和召回率的分类器更为有利。这不一定能一直符合你的期望:在某些情况下,你更关心的是精度,而另一些情况下,你可能真正关心的是召回率。
遗憾的是,鱼和熊掌不可兼得:你不能同时增加精度并减少召回率,反之亦然。这称为精度/召回率权衡。
要理解这个权衡过程,我们来看看SGDClassifier如何进行分类决策。对于每个实例,它会基于决策函数计算出一个分值,如果该值大于阈值,则将该实例判为正类,否则便将其判为负类。
Scikit-Learn不允许直接设置阈值,但是可以访问它用于预测的决策分数。不是调用分类器的predict()方法,而是调用
decision_function()方法,这个方法返回每个实例的分数,然后就
可以根据这些分数,使用任意阈值进行预测了:
>>> 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()方法一样(也就是True)。我们来试试提升阈值:
>>> threshold = 200000
>>> y_some_digit_pred = (y_scores > threshold)
>>> y_some_digit_pred
array([False], dtype=bool)
这证明了提高阈值确实可以降低召回率。这张图确实是5,当阈值为0时,分类器可以检测到该图,但是当阈值提高到200000时,就错过了这张图。
那么要如何决定使用什么阈值呢?
首先,使用
cross_val_predict()函数获取训练集中所有实例的分数,但是这次需
要它返回的是决策分数而不是预测结果:
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
method="decision_function")
有了这些分数,可以使用precision_recall_curve()函数来计算
所有可能的阈值的精度和召回率:
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
最后,使用Matplotlib绘制精度和召回率相对于阈值的函数图
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()
endtime:2019-05-03-11点48分
begintime:2019-05-04-09点11分
3.4、受试者工作特征曲线(简称ROC)
该工具经常与二元分类器一起使用
它与精度/召回率曲线非常相似,但绘制的不是精度和召回率,而是真正类率(召回率的另一名称)和假正类率(FPR)。FPR是被错误分为正类的负类实例比率。它等于1减去真负类率(TNR),后者是被正确分类为负类的负类实例比率,也称为特异度。因此,ROC曲线绘制的是灵敏度和(1-特异度)的关系。
要绘制ROC曲线,首先需要使用roc_curve()函数计算多种阈值
的TPR和FPR:
from sklearn.metrics import roc_curve
同样这里再次面临一个折中权衡:召回率(TPR)越高,分类器产生的假正类(FPR)就越多。虚线表示纯随机分类器的ROC曲线;一个优秀的分类器应该离这条线越远越好(向左上角)。
3.4.1、ROC AUC分数
有一种比较分类器的方法是测量曲线下面积(AUC)。完美的分类器的ROC AUC等于1,而纯随机分类器的ROC AUC等于0.5。Scikit-Learn提供计算ROC AUC的函数:
3.5、PR、ROC之间如何选择
由于ROC曲线与精度/召回率(或PR)曲线非常相似,因此你可能会问如何决定使用哪种曲线。有一个经验法则是,当正类非常少见或者你更关注假正类而不是假负类时,你应该选择PR曲线,反之
则是ROC曲线。例如,看前面的ROC曲线图(以及ROC AUC分数),你可能会觉得分类器真不错。但这主要是因为跟负类(非5)相比,正类(数字5)的数量真得很少。相比之下,PR曲线清楚地说明分类器还有改进的空间(曲线还可以更接近右上角)。
4、多类别分类器
二元分类器在两个类别中区分,而多类别分类器(也称为多项分类器)可以区分两个以上的类别。
有一些算法(如随机森林分类器或朴素贝叶斯分类器)可以直接处理多个类别
一些严格的二元分类器(如支持向量机分类器或线性分类器)
4.0.1、一对多(OvA)策略(也称为one-versus-therest)
针对每个类别建立一个分类器,在对数据进行检测分类时,获取每个分类器的决策分数,哪个分类器给分最高,就将其分为哪个类。
4.0.2、一对一(OvO)策略。
如果存在N个类别,那么这需要训练N×(N-1)÷2个分类器。对于MNIST问题,这意味着要训练45个二元分类器!当需要对一张图片进行分类时,你需要运行45个分类器来对图片进行分类,最后看哪个类别获胜最多。OvO的主要优点在于,每个分类器只需要用到部分训练集对其必须区分的两个类别进行训练。
当数据规模扩大时,有些算法(例如支持向量机分类器)的表现较差,因此对于这类算法,OvO是一个优先的选择,由于在较小训练集上分别训练多个分类器比在大型数据集上训练少数分类器要快的多。但是对大多数二元分类器来说,OvA策略还是更好的选择。
4.0.3、OvO和OvA的选择
Scikit-Learn可以检测到你尝试使用二元分类算法进行多类别分类任务,它会自动运行OvA(SVM分类器除外,它会使用OvO)
如果想要强制Scikit-Learn使用一对一或者一对多策略,可以使用OneVsOne Classifier或OneVsRestClassifier类。只需要创建一个实例,然后将二元分类器传给其构造函数。
4.0.4、decision_function()、predict_proba()方法的理解
sklearn.linear_model.SGDClassifier.decision_function(),获取模型预测实例的决策分数
注:官方文档中解释为预测样本的置信度。样本的置信度分数是样本到超平面的带符号距离。
sklearn.ensemble.RandomForestClassifier.predict_prob(self,X),获取X的概率分布。
predict_proba()方法会返回一个数组,其中每行为一个实例,每列代表一个类别,意思是某个给定实例属于某个给定类别的概率
官方文档中的描述:输入样本的预测类概率被计算为森林中树木的平均预测类概率。单个树的类概率是同一类的样本在叶子中的比例。
4.1、错误分析
4.1.1、方法一:分析错误类型,通过混淆矩阵
首先,看看混淆矩阵。就像之前做的,使用cross_val_predict()函数进行预测(获取每个实例的预测结果)
然后,调用confusion_matrix()函数(生成混淆矩阵)。
4.1.1.1、使用matplotlib的matshow()函数查看混淆矩阵的图像表示。
4.1.1.2、imshow函数颜色映射(cmap)的取值
详情可以看这一片博客https://blog.csdn.net/qq_28485501/article/details/82656614
plt.matshow(conf_mx, #混淆矩阵
cmap=plt.cm.gray) #颜色映射
save_fig("confusion_matrix_plot", tight_layout=False)
plt.show()
endtime:2019-05-04-11点47分
begintime:2019-05-05-08点15分
4.1.2、numpy中keepdims选项
keepdims=True,保持矩阵的二维特性。
keepdims=False,矩阵会被降为一维。
4.1.3、numpy.sum(a, axis=None, dtype=None, out=None, keepdims=<no value>, initial=<no value>)
计算数组给定轴上元素的和。
返回,与a具有相同形状的数组,但删除了指定的轴。如果a是0-d数组,或者axis是None,则返回标量。如果指定了输出数组,则返回对out的引用。
https://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html#numpy.sum
4.1.4、numpy.fill_diagonal(matrix,filldata)
在矩阵matrix的对角线上填充指定的值filldata。
4.1.5、图片预处理
Pillow、Scikit-Image或opencv
4.2、多标签分类
输出多个二元标签的分类系统称为多标签分类系统。
4.2.1、KnNeighborsClassifier支持多标签分类
不是所有的分类器都支持。
4.2.2、评估多标签分类器的方法
方法很多,取决于你的项目。
4.3、多输出-多类别分类
多标签分类的泛化,其标签也可以是多种类别的。
小结
endtime:2019-05-05-09点23分