【机器学习实战】3_Classification代码《Hands-On Machine Learning with Scikit-Learn&TensorFlow》

手写数字图片分类

# -*- coding: utf-8 -*-
"""
Created on Tue Dec 25 13:43:39 2018

@author: Administrator
"""
#from sklearn.datasets import fetch_mldata
import numpy as np
import scipy.io as io
import os
import matplotlib
import matplotlib.pyplot as plt
#读取matlab文件
mnist = io.loadmat(r'D:\chengmi_吴限\python\handson-ml-master\handson-ml-master\datasets\mnist-original\mnist-original')
mnist


#保存图片
def save_fig(fig_id,tight_layout=True,fig_extension='png',resolution=300):
    path = os.path.join(r'D:\chengmi_吴限\python\\handson-ml-master\handson-ml-master\datasets\mnist-original',fig_id + "." + fig_extension)
    print('Saving figure',fig_id)
    if tight_layout:
        plt.tight_layout() #紧凑显示图片
    plt.savefig(path,format = fig_extension,dpi = resolution)

type(mnist)
'''
一般而言,由 sklearn 加载的数据集有着相似的字典结构,这包括:
DESCR 键描述数据集
data 键存放一个数组,数组的一行表示一个样例,一列表示一个特征
target 键存放一个标签数组
'''

X,y = mnist['data'].T,mnist['label'].T
X.shape
y.shape

2**8
#X.reshape(70000,784)
some_digit = X[36000]
X[36000].shape
some_digit_image = some_digit.reshape(28,28)
some_digit.reshape(28,28).shape
28*28
y[36000]
y[1]
# =============================================================================
# MNIST70000 张图片,每张图片有 784 个特征。这是因为每个图片都是 28*28 像素的,
# 并且每个像素的值介于 0~255 之间。2**8
# =============================================================================
plt.imshow(some_digit_image,cmap = matplotlib.cm.binary,interpolation='nearest')
plt.axis('off')
save_fig('some_digit_plot')
plt.show()

def plot_digit(data):
    image = data.reshape(28,28)
    plt.imshow(image,cmap = matplotlib.cm.binary,interpolation='nearest')
    plt.axis('off')
    
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()
# =============================================================================
# (len(example_images)-1)//11
# min(len(example_images),10)
# =============================================================================
y[36000]
# =============================================================================
# 你总是应该先创建测试集,并且在验证数据之前先把测试集晾到一边。MNIST 数
# 据集已经事先被分成了一个训练集(前 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]
'''
# =============================================================================
# 训练一个二分类器
# =============================================================================
'''
# =============================================================================
# 让我们打乱训练集。这可以保证交叉验证的每一折都是相似(你不会期待某一折缺少某类数
# 字)。而且,一些学习算法对训练样例的顺序敏感,当它们在一行当中得到许多相似的样
# 例,这些算法将会表现得非常差。打乱数据集将保证这种情况不会发生
# =============================================================================
y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)
# =============================================================================
# 现在让我们挑选一个分类器去训练它。用随机梯度下降分类器 SGD,是一个不错的开始。使
# 用 Scikit-Learn 的 SGDClassifier 类。这个分类器有一个好处是能够高效地处理非常大的数据
# 集。这部分原因在于SGD一次只处理一条数据,这也使得 SGD 适合在线学习(online
# learning)。我们在稍后会看到它。让我们创建一个 SGDClassifier 和在整个数据集上训练
# 它。
# =============================================================================
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(max_iter=5,random_state=42)
sgd_clf.fit(X_train,y_train_5)
#现在你可以用它来查出数字 5 的图片
#分类器猜测这个数字代表 5( True )。看起来在这个例子当中,它猜对了。现在让我们评估
#这个模型的性能
sgd_clf.predict([some_digit])
# =============================================================================
#评估一个分类器,通常比评估一个回归器更加玄学。
# 使用交叉验证测量准确性
# =============================================================================
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf,X_train,y_train_5,cv=3,scoring='accuracy')
    
# =============================================================================
# 在交叉验证过程中,有时候你会需要更多的控制权,相较于函数 cross_val_score() 或者其他
# 相似函数所提供的功能。这种情况下,你可以实现你自己版本的交叉验证。事实上它相当直
# 接。以下代码粗略地做了和 cross_val_score() 相同的事情,并且输出相同的结果。
# =============================================================================
# =============================================================================
# StratifiedKFold 类实现了分层采样(详见第二章的解释),生成的折(fold)包含了各
# 类相应比例的样例。在每一次迭代,上述代码生成分类器的一个克隆版本,在训练折
# (training folds)的克隆版本上进行训,在测试折(test folds)上进行预测。然后它计算
# 出被正确预测的数目和输出正确预测的比例。
# 让我们使用 cross_val_score() 函数来评估 SGDClassifier 模型,同时使用 K 折交叉验证,此
# 处让 k=3 。记住:K 折交叉验证意味着把训练集分成 K 折(此处 3 折),然后使用一个模型
# 对其中一折进行预测,对其他折进行训练
# =============================================================================
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))
# =============================================================================
# 让我们来看一个非常笨的分类器去分类,看看其在“非 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')  
# =============================================================================
# 没错,这个笨的分类器也有 90% 的精度。这是因为只有 10% 的图片是数字 5,所以你总是
# 猜测某张图片不是 5,你也会有90%的可能性是对的。
# 这证明了为什么精度通常来说不是一个好的性能度量指标,特别是当你处理有偏差的数据
# 集,比方说其中一些类比其他类频繁得多    
# =============================================================================
# =============================================================================
'''# 混淆矩阵 混淆矩阵也称误差矩阵,是表示精度评价的一种标准格式,用n行n列的矩阵形式来表示。
具体评价指标有总体精度、制图精度、用户精度等,这些精度指标从不同的侧面反映了图像分类的精度。 
在人工智能中,混淆矩阵(confusion matrix)是可视化工具,特别用于监督学习,在无监督学习一般叫做匹配矩阵。
在图像精度评价中,主要用于比较分类结果和实际测得值,可以把分类结果的精度显示在一个混淆矩阵里面。
混淆矩阵是通过将每个实测像元的位置和分类与分类图像中的相应位置和分类相比较计算的。'''
# 对分类器来说,一个好得多的性能评估指标是混淆矩阵。大体思路是:输出类别A被分类成类
# 别 B 的次数。举个例子,为了知道分类器将 5 误分为 3 的次数,你需要查看混淆矩阵的第五
# 航第三列。
# 为了计算混淆矩阵,首先你需要有一系列的预测值,这样才能将预测值与真实值做比较。你
# 或许想在测试集上做预测。但是我们现在先不碰它。(记住,只有当你处于项目的尾声,当
# 你准备上线一个分类器的时候,你才应该使用测试集)。相反,你应该使
# 用 cross_val_predict() 函数
# =============================================================================
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)

from sklearn.metrics import precision_score,recall_score
precision_score(y_train_5,y_train_pred)
#4067/(824+4067)
recall_score(y_train_5,y_train_pred)
#4067/(1354+4067)
#confusion_matrix(y_train_5,y_train_perfect_predictions)
# =============================================================================
# 通常结合准确率和召回率会更加方便,这个指标叫做“F1 值”,特别是当你需要一个简单的方
# 法去比较两个分类器的优劣的时候。F1 值是准确率和召回率的调和平均。普通的平均值平等
# 地看待所有的值,而调和平均会给小的值更大的权重。所以,要想分类器得到一个高的 F1
# 值,需要召回率和准确率同时高
# =============================================================================
from sklearn.metrics import f1_score
f1_score(y_train_5,y_train_pred)
# =============================================================================
# 准确率/召回率之间的折衷
# 为了弄懂这个折衷,我们看一下 SGDClassifier 是如何做分类决策的。对于每个样例,它根据
# 决策函数计算分数,如果这个分数大于一个阈值,它会将样例分配给正例,否则它将分配给反
# 例。图 3-3 显示了几个数字从左边的最低分数排到右边的最高分。假设决策阈值位于中间的
# 箭头(介于两个 5 之间):您将发现4个真正例(数字 5)和一个假正例(数字 6)在该阈值
# 的右侧。因此,使用该阈值,准确率为 80%4/5)。但实际有 6 个数字 5,分类器只检测 4,
# 所以召回是 67%4/6)。现在,如果你 提高阈值(移动到右侧的箭头),假正例(数字
# 6)成为一个真反例,从而提高准确率(在这种情况下高达 100%),但一个真正例 变成假反
# 例,召回率降低到 50%。相反,降低阈值可提高召回率、降低准确率

#Scikit-Learn 不让你直接设置阈值,但是它给你提供了设置决策分数的方法,这个决策分数可
#以用来产生预测。它不是调用分类器的 predict() 方法,而是调用 decision_function() 方
#法。这个方法返回每一个样例的分数值,然后基于这个分数值,使用你想要的任何阈值做出
#预测。
# =============================================================================
y_scores = sgd_clf.decision_function([some_digit])
y_scores

threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
# =============================================================================
# SGDClassifier 用了一个等于 0 的阈值,所以前面的代码返回了跟 predict() 方法一样的结果
# (都返回了 true )。让我们提高这个阈值
# =============================================================================
threshold = 200000
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
# =============================================================================
# 这证明了提高阈值会降调召回率。这个图片实际就是数字 5,当阈值等于 0 的时候,分类器
# 可以探测到这是一个 5,当阈值提高到 20000 的时候,分类器将不能探测到这是数字 5。

#那么,你应该如何使用哪个阈值呢?首先,你需要再次使用 cross_val_predict() 得到每一个
#样例的分数值,但是这一次指定返回一个决策分数,而不是预测值。
# =============================================================================
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
                             method="decision_function")

y_scores.shape
if y_scores.ndim == 2:
    y_scores = y_scores[:,1]
# =============================================================================
# 现在有了这些分数值。对于任何可能的阈值,使用 precision_recall_curve() ,你都可以计算
# 准确率和召回率: 
# =============================================================================
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.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])
save_fig('precision_recall_vs_threshold_plot')
plt.show()

# =============================================================================
# 你也许会好奇为什么准确率曲线比召回率曲线更加起伏不平。原因是准确率有时候会降
# 低,尽管当你提高阈值的时候,通常来说准确率会随之提高。回头看图 3-3,留意当你从
# 中间箭头开始然后向右移动一个数字会发生什么: 准确率会由 4/580%)降到
# 3/475%)。另一方面,当阈值提高时候,召回率只会降低。这也就说明了为什么召回
# 率的曲线更加平滑。
# =============================================================================

(y_train_pred == (y_scores >0)).all()

y_train_pred_90 = (y_scores> 70000)

precision_score(y_train_5,y_train_pred_90)

recall_score(y_train_5,y_train_pred_90)
# =============================================================================
# 很棒!你拥有了一个(近似) 90% 准确率的分类器。它相当容易去创建一个任意准确率的分
# 类器,只要将阈值设置得足够高。但是,一个高准确率的分类器不是非常有用,如果它的召
# 回率太低!
# =============================================================================

# =============================================================================
# 现在你可以选择适合你任务的最佳阈值。另一个选出好的准确率/召回率折衷的方法是直接画
# 出准确率对召回率的曲线
# =============================================================================
def plot_precision_vs_recall(precisions,recalls):
    plt.plot(recalls,precisions,'b-',linewidth=2)
    plt.xlabel('Recall',fontsize=16)
    plt.ylabel('Precision',fontsize=16)
    plt.axis([0,1,0,1])
    
plt.figure(figsize=(8,6))
plot_precision_vs_recall(precisions,recalls)
save_fig('precision_vs_recall_plot')
plt.show()

# =============================================================================
# ROC 曲线
# 受试者工作特征(ROC)曲线是另一个二分类器常用的工具。它非常类似与准确率/召回率曲
# 线,但不是画出准确率对召回率的曲线,ROC 曲线是真正例率(true positive rate,另一个名
# 字叫做召回率)对假正例率(false positive rate, FPR)的曲线。FPR 是反例被错误分成正例
# 的比率。它等于 1 减去真反例率(true negative rate, TNR)。TNR是反例被正确分类的比
# 率。TNR也叫做特异性。所以 ROC 曲线画出召回率对(1 减特异性)的曲线。
#为了画出 ROC 曲线,你首先需要计算各种不同阈值下的 TPRFPR,使用 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',fontsize=16)
    plt.ylabel('True Positive Rate',fontsize=16)
    
plt.figure(figsize=(8,6))
plot_roc_curve(fpr,tpr)
save_fig('roc_curve_plot')
plt.show()

from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5,y_scores)

# =============================================================================
# 因为 ROC 曲线跟准确率/召回率曲线(或者叫 PR)很类似,你或许会好奇如何决定使用哪一
# 个曲线呢?一个笨拙的规则是,优先使用 PR 曲线当正例很少,或者当你关注假正例多于假
# 反例的时候。其他情况使用 ROC 曲线。举例子,回顾前面的 ROC 曲线和 ROC AUC 数值,
# 你或许人为这个分类器很棒。但是这几乎全是因为只有少数正例(“是 5”),而大部分是反例
# (“非 5”)。相反,PR 曲线清楚显示出这个分类器还有很大的改善空间(PR 曲线应该尽可能
# 地靠近右上角)
# =============================================================================

# =============================================================================
# 让我们训练一个 RandomForestClassifier ,然后拿它的的ROC曲线和ROC AUC数值去
# 跟 SGDClassifier 的比较。首先你需要得到训练集每个样例的数值。但是由于随机森林分类器
# 的工作方式, RandomForestClassifier 不提供 decision_function() 方法。相反,它提供
# 了 predict_proba() 方法。Skikit-Learn分类器通常二者中的一个。 predict_proba() 方法返回
# 一个数组,数组的每一行代表一个样例,每一列代表一个类。数组当中的值的意思是:给定
# 一个样例属于给定类的概率。比如,70%的概率这幅图是数字 5。
# =============================================================================

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.figure(figsize=(8,6))
plt.plot(fpr,tpr,'b:',linewidth=2,label='SGD')
plot_roc_curve(fpr_forest,tpr_forest,'Random Forest')
plt.legend(loc='lower right',fontsize=16)
save_fig('roc_curve_comparison_plot')
plt.show()
# =============================================================================
# 如你所见, RandomForestClassifier 的 ROC 曲线比 SGDClassifier 的好得多:它更靠近左上
# 角。所以,它的 ROC AUC 也会更大
# =============================================================================
roc_auc_score(y_train_5,y_scores_forest)
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)
recall_score(y_train_5,y_train_pred_forest)
# =============================================================================
# 多类分类
# 二分类器只能区分两个类,而多类分类器(也被叫做多项式分类器)可以区分多于两个类
# =============================================================================
# 一些算法(比如随机森林分类器或者朴素贝叶斯分类器)可以直接处理多类分类问题。其他
# 一些算法(比如 SVM 分类器或者线性分类器)则是严格的二分类器。然后,有许多策略可以
# 让你用二分类器去执行多类分类。
# =============================================================================
# =============================================================================
# =============================================================================
# 举例子,创建一个可以将图片分成 10 类(从 09)的系统的一个方法是:训练10个二分类
# 器,每一个对应一个数字(探测器 0,探测器 1,探测器 2,以此类推)。然后当你想对某张
# 图片进行分类的时候,让每一个分类器对这个图片进行分类,选出决策分数最高的那个分类
# 器。这叫做“一对所有”(OvA)策略(也被叫做“一对其他”)。
# 另一个策略是对每一对数字都训练一个二分类器:一个分类器用来处理数字 0 和数字 1,一
# 个用来处理数字 0 和数字 2,一个用来处理数字 12,以此类推。这叫做“一对一”(OvO)
# 策略。如果有 N 个类。你需要训练 N*(N-1)/2 个分类器。对于 MNIST 问题,需要训练 45 个
# 二分类器!当你想对一张图片进行分类,你必须将这张图片跑在全部45个二分类器上。然后
# 看哪个类胜出。OvO 策略的主要有点是:每个分类器只需要在训练集的部分数据上面进行训
# 练。这部分数据是它所需要区分的那两个类对应的数据。
# 一些算法(比如 SVM 分类器)在训练集的大小上很难扩展,所以对于这些算法,OvO 是比
# 较好的,因为它可以在小的数据集上面可以更多地训练,较之于巨大的数据集而言。但是,
# 对于大部分的二分类器来说,OvA 是更好的选择。
# Scikit-Learn 可以探测出你想使用一个二分类器去完成多分类的任务,它会自动地执行
# OvA(除了 SVM 分类器,它使用 OvO)。让我们试一下 SGDClassifier
# =============================================================================
##Multiclass classification
sgd_clf.fit(X_train,y_train)
sgd_clf.predict([some_digit])
# =============================================================================
# 很容易。上面的代码在训练集上训练了一个 SGDClassifier 。这个分类器处理原始的目标
# class,从 09( y_train ),而不是仅仅探测是否为 5 ( y_train_5 )。然后它做出一个
# 判断(在这个案例下只有一个正确的数字)。在幕后,Scikit-Learn 实际上训练了 10 个二分
# 类器,每个分类器都产到一张图片的决策数值,选择数值最高的那个类
# =============================================================================
# =============================================================================
# 为了证明这是真实的,你可以调用 decision_function() 方法。不是返回每个样例的一个数
# 值,而是返回 10 个数值,一个数值对应于一个类
# =============================================================================
some_digit_scores = sgd_clf.decision_function([some_digit])
some_digit_scores

np.argmax(some_digit_scores)
sgd_clf.classes_
sgd_clf.classes_[5]
# =============================================================================
# 一个分类器被训练好了之后,它会保存目标类别列表到它的属性 classes_ 中去,按照值
# 排序。在本例子当中,在 classes_ 数组当中的每个类的索引方便地匹配了类本身,比
# 如,索引为 5 的类恰好是类别 5 本身。但通常不会这么幸运。
# =============================================================================
# =============================================================================
# 如果你想强制 Scikit-Learn 使用 OvO 策略或者 OvA 策略,你可以使用 OneVsOneClassifier 类
# 或者 OneVsRestClassifier 类。创建一个样例,传递一个二分类器给它的构造函数。举例子,
# 下面的代码会创建一个多类分类器,使用 OvO 策略,基于 SGDClassifier 。
# =============================================================================
from sklearn.multiclass import OneVsOneClassifier
ovo_clf = OneVsOneClassifier(SGDClassifier(max_iter=5,random_state=42))
ovo_clf.fit(X_train,y_train)
ovo_clf.predict([some_digit])

len(ovo_clf.estimators_)

forest_clf.fit(X_train,y_train)
forest_clf.predict([some_digit])
# =============================================================================
# 这次 Scikit-Learn 没有必要去运行 OvO 或者 OvA,因为随机森林分类器能够直接将一个样例
# 分到多个类别。你可以调用 predict_proba() ,得到样例对应的类别的概率值的列表:
# =============================================================================
forest_clf.predict_proba([some_digit])
# =============================================================================
# 你可以看到这个分类器相当确信它的预测:在数组的索引 5 上的 0.8,意味着这个模型以
# 80% 的概率估算这张图片代表数字 5。它也认为这个图片可能是数字 0 或者数字 3,分别都
# 是 10% 的几率
# =============================================================================
# =============================================================================
# 现在当然你想评估这些分类器。像平常一样,你想使用交叉验证。让我们
# 用 cross_val_score() 来评估 SGDClassifier 的精度。
# =============================================================================
cross_val_score(sgd_clf,X_train,y_train,cv=3,scoring='accuracy')
# =============================================================================
# 在所有测试折(test fold)上,它有 84% 的精度。如果你是用一个随机的分类器,你将会得
# 到 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')
# =============================================================================
# 误差分析
# 当然,如果这是一个实际的项目,你会在你的机器学习项目当中,跟随以下步骤(见附录
# B):探索准备数据的候选方案,尝试多种模型,把最好的几个模型列为入围名单,
# 用 GridSearchCV 调试超参数,尽可能地自动化,像你前面的章节做的那样。在这里,我们假
# 设你已经找到一个不错的模型,你试图找到方法去改善它。一个方式是分析模型产生的误差
# 的类型
# =============================================================================
# =============================================================================
# 首先,你可以检查混淆矩阵。你需要使用 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

def plot_confusion_matrix(matrix):
    fig = plt.figure(figsize=(8,8))
    ax = fig.add_subplot(111)
    cax = ax.matshow(matrix)
    fig.colorbar(cax)
# =============================================================================
# 这里是一对数字。使用 Matplotlib 的 matshow() 函数,将混淆矩阵以图像的方式呈现,将会更
# 加方便。
# =============================================================================    
plt.matshow(conf_mx,cmap=plt.cm.gray)
save_fig('confusion_matrix_plot',tight_layout=False)
plt.show()
# =============================================================================
# 这个混淆矩阵看起来相当好,因为大多数的图片在主对角线上。在主对角线上意味着被分类
# 正确。数字 5 对应的格子看起来比其他数字要暗淡许多。这可能是数据集当中数字 5 的图片
# 比较少,又或者是分类器对于数字 5 的表现不如其他数字那么好。你可以验证两种情况。
# =============================================================================
# =============================================================================
# 让我们关注仅包含误差数据的图像呈现。首先你需要将混淆矩阵的每一个值除以相应类别的
# 图片的总数目。这样子,你可以比较错误率,而不是绝对的错误数(这对大的类别不公
# 平)
# =============================================================================
row_sums = conf_mx.sum(axis=1,keepdims=True)
norm_conf_mx = conf_mx / row_sums
# =============================================================================
# 现在让我们用 0 来填充对角线。这样子就只保留了被错误分类的数据。让我们画出这个结
# 果。
# =============================================================================
np.fill_diagonal(norm_conf_mx,0)
plt.matshow(norm_conf_mx,cmap=plt.cm.gray)
save_fig('confusion_matrix_errors_plot',tight_layout=False)
plt.show()
# =============================================================================
# 现在你可以清楚看出分类器制造出来的各类误差。记住:行代表实际类别,列代表预测的类
# 别。第 89 列相当亮,这告诉你许多图片被误分成数字 8 或者数字 9。相似的,第 89 行
# 也相当亮,告诉你数字 8、数字 9 经常被误以为是其他数字。相反,一些行相当黑,比如第
# 一行:这意味着大部分的数字 1 被正确分类(一些被误分类为数字 8 )。留意到误差图不是
# 严格对称的。举例子,比起将数字 8 误分类为数字 5 的数量,有更多的数字 5 被误分类为数
# 字 8。
# =============================================================================
# =============================================================================
# 分析混淆矩阵通常可以给你提供深刻的见解去改善你的分类器。回顾这幅图,看样子你应该
# 努力改善分类器在数字 8 和数字 9 上的表现,和纠正 3/5 的混淆。举例子,你可以尝试去收
# 集更多的数据,或者你可以构造新的、有助于分类器的特征。举例子,写一个算法去数闭合
# 的环(比如,数字 8 有两个环,数字 6 有一个, 5 没有)。又或者你可以预处理图片(比
# 如,使用 Scikit-Learn,Pillow, OpenCV)去构造一个模式,比如闭合的环
# =============================================================================
# =============================================================================
# 分析独特的误差,是获得关于你的分类器是如何工作及其为什么失败的洞见的一个好途径。
# 但是这相对难和耗时。举例子,我们可以画出数字 35 的例子
# =============================================================================
cl_a,cl_b = 3,5
X_aa = X_train[(y_train.T== cl_a) & (y_train_pred == cl_a)] #.ravel()扁平化类似.T
X_ab = X_train[(y_train.ravel() == cl_a) & (y_train_pred == cl_b)]
X_ba = X_train[(y_train.ravel() == cl_b) & (y_train_pred == cl_a)]
X_bb = X_train[(y_train.ravel() == cl_b) & (y_train_pred == cl_b)]
# =============================================================================
# 左边两个 5*5 的块将数字识别为 3,右边的将数字识别为 5。一些被分类器错误分类的数字
# (比如左下角和右上角的块)是书写地相当差,甚至让人类分类都会觉得很困难(比如第 8
# 行第 1 列的数字 5,看起来非常像数字 3 )。但是,大部分被误分类的数字,在我们看来都
# 是显而易见的错误。很难明白为什么分类器会分错。原因是我们使用的简单
# 的 SGDClassifier ,这是一个线性模型。它所做的全部工作就是分配一个类权重给每一个像
# 素,然后当它看到一张新的图片,它就将加权的像素强度相加,每个类得到一个新的值。所
# 以,因为 35 只有一小部分的像素有差异,这个模型很容易混淆它们。
# =============================================================================
# =============================================================================
# 35 之间的主要差异是连接顶部的线和底部的线的细线的位置。如果你画一个 3,连接处稍
# 微向左偏移,分类器很可能将它分类成 5。反之亦然。换一个说法,这个分类器对于图片的位
# 移和旋转相当敏感。所以,减轻 3/5 混淆的一个方法是对图片进行预处理,确保它们都很好
# 地中心化和不过度旋转。这同样很可能帮助减轻其他类型的错误。
# =============================================================================
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()


# =============================================================================
# #多标签分类
# #到目前为止,所有的样例都总是被分配到仅一个类。有些情况下,你也许想让你的分类器给
# #一个样例输出多个类别。比如说,思考一个人脸识别器。如果对于同一张图片,它识别出几
# #个人,它应该做什么?当然它应该给每一个它识别出的人贴上一个标签。比方说,这个分类
# #器被训练成识别三个人脸,Alice,Bob,Charlie;然后当它被输入一张含有 Alice 和 Bob 的
# #图片,它应该输出 [1, 0, 1] (意思是:Alice 是,Bob 不是,Charlie 是)。这种输出多个二
# #值标签的分类系统被叫做多标签分类系统
# =============================================================================
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])

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')

###多输出分类###########
noise = np.random.randint(0,100,(len(X_train),784))
X_train_mod = X_train + noise
noise = np.random.randint(0,100,(len(X_train),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])
save_fig('noisy_digit_example_plot')
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)
save_fig('cleaned_digit_example_plot')

########extra material############
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值