人工智能----线性分类
目录
一、在“机器学习”第二章“模型评估与旋转”学习基础上,理解“查准率”、“查全率”、“F1-Score”、“ROC”、“混淆矩阵”的定义。
1、查准率
查准率(Precision)(精度)是衡量某一检索系统的信号噪声比的一种指标,即检出的相关文献与检出的全部文献的百分比
查
准
率
=
检
索
出
的
相
关
信
息
量
检
索
出
的
信
息
总
量
∗
100
%
查准率= \frac{检索出的相关信息量}{检索出的信息总量}*100\%
查准率=检索出的信息总量检索出的相关信息量∗100%
2、查全率
查全率(召回率,recall),是衡量某一检索系统从文献集合中检出相关文献成功度的一项指标,即检出的相关文献与全部相关文献的百分比。
查
全
率
=
检
索
出
的
相
关
信
息
量
系
统
中
的
相
关
信
息
总
量
∗
100
%
查全率= \frac{检索出的相关信息量}{系统中的相关信息总量}*100\%
查全率=系统中的相关信息总量检索出的相关信息量∗100%
3、F1-Score
F1分数(F1 Score),是统计学中用来衡量二分类模型精确度的一种指标。它同时兼顾了分类模型的精确率和召回率。F1分数可以看作是模型精确率和召回率的一种加权平均,它的最大值是1,最小值是0。
数学定义:F1分数(F1-Score),又称为平衡F分数(BalancedScore),它被定义为精确率和召回率的调和平均数。
F
1
=
2
∗
p
r
e
c
i
s
i
o
n
∗
r
e
c
a
l
l
p
r
e
c
i
s
i
o
n
+
r
e
c
a
l
l
F1 = 2* \frac{precision*recall}{precision+recall}
F1=2∗precision+recallprecision∗recall
4、ROC
ROC的全名叫做Receiver Operating Characteristic,其主要分析工具是一个画在二维平面上的曲线——ROC 曲线。平面的横坐标是false positive rate(FPR),纵坐标是true positive rate(TPR)。对某个分类器而言,我们可以根据其在测试样本上的表现得到一个TPR和FPR点对。这样,此分类器就可以映射成ROC平面上的一个点。调整这个分类器分类时候使用的阈值,我们就可以得到一个经过(0, 0),(1, 1)的曲线,这就是此分类器的ROC曲线。一般情况下,这个曲线都应该处于(0, 0)和(1, 1)连线的上方。
5、混淆矩阵
混淆矩阵也称误差矩阵,是表示精度评价的一种标准格式,用n行n列的矩阵形式来表示。具体评价指标有总体精度、制图精度、用户精度等,这些精度指标从不同的侧面反映了图像分类的精度。
在人工智能中,混淆矩阵(confusion matrix)是可视化工具,特别用于监督学习,在无监督学习一般叫做匹配矩阵。
在图像精度评价中,主要用于比较分类结果和实际测得值,可以把分类结果的精度显示在一个混淆矩阵里面。混淆矩阵是通过将每个实测像元的位置和分类与分类图像中的相应位置和分类相比较计算的。
二、利用Jupyter编程完成对手写体Mnist数据集中10个字符 (0-9)的分类识别
1、MNIST数据集
MNIST数据集是机器学习领域中非常经典的一个数据集,由60000个训练样本和10000个测试样本组成,每个样本都是一张28 * 28像素的灰度手写数字图片。每张图像都用其代表的数字标记。这个数据集被广为使用,因此也被称作是机器学习领域的“Hello World”。
(1)使用sklearn的函数来获取MNIST数据集
from sklearn.datasets import fetch_openml
import numpy as np
import os
# 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]
(2)sklearn获取数据集所需时间较长,调用time类查看程序运行时间
import time
starttime=time.time()
mnist=fetch_openml('mnist_784',version=1,cache=True)
mnist.target=mnist.target.astype(np.int8)
sort_by_target(mnist)
endtime=time.time()
print("运行时间:",endtime-starttime,"seconds")
我的电脑读取数据集花费时间:
(3)数据集排序
mnist["data"], mnist["target"]
运行结果如下:
(4)查看数据集维度
方法一:
mnist.data.shape
运行结果如下:
方法二:
X,y=mnist["data"],mnist["target"]
X.shape
运行结果如下:
方法三:
y.shape
28*28
以上三种方式都可以实现数据集维度查看
(5)展示图片
# 展示图片
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))
程序运行结果如下:
(6)多行多列图片同时展示
# 多行多列图片展示函数定义
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()
程序运行结果如下:
(7)创建测试集
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
(8)对训练集进行洗牌
洗牌的目的是为了保证交叉验证的时候,所有的折叠都差不多
import numpy as np
shuffer_index=np.random.permutation(60000)
X_train,y_train=X_train[shuffer_index],y_train[shuffer_index]
2、训练一个二分类器
(1)创建目录标量
我们尝试识别一个数字,比如数字5,那么这个"数字5检测器",就是一个二分类器的例子,它只能区分两个类别:5和非5。先为此分类任务创建目录标量
y_train_5=(y_train==5)
y_test_5=(y_test==5)
(2)训练分类器
挑选一个分类器并开始训练。一个好的选择是随机梯度下降(SGD)分类器,使用sklearn的SGDClassifier类即可。这个分类器的优势是:能够有效处理非常大型的数据集。这部分是因为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)
程序运行结果如下:
(3)查看分类器训练结果
sgd_clf.predict([some_digit])
程序运行结果如下:
3、使用交叉验证测量精度
(1)随机交叉验证
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
程序运行结果如下:
(2)三层交叉验证
# 类似于分层采样,每一折的分布类似
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))
程序运行结果如下:
由结果可知,两种方式验证结果相差不大,但准确率都比较高
(3)将所有图片都预测为‘非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")
程序运行结果如下:
由输出结果可知,在将图片都预测为非5时,预测的准确率也高达90%,这是因为我们只有大约10%的图像是数字5,所以只要猜一张图片不是5,那么有90%的时间都是正确的。
这说明,准确率通常无法成为分类器的首要性能指标,特别是当我们处理偏斜数据集的时候
4、混淆矩阵
评估分类器性能的更好的方法是混淆矩阵。总体思路就是统计A类别实例被分成B类别的次数。例如,要想知道分类器将数字3和数字5混淆多少次,只需要通过混淆矩阵的第5行第3列来查看。
cross_val_predict
和 cross_val_score
不同的是,前者返回预测值,并且是每一次训练的时候,用模型没有见过的数据来预测。
(1)程序代码如下:
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)
程序运行结果如下:
上面的结果表明:第一行所有’非5’(负类)的图片中,有53417被正确分类(真负类),1162,错误分类成了5(假负类);第二行表示所有’5’(正类)的图片中,有1350错误分类成了非5(假正类),有4071被正确分类成5(真正类).
一个完美的分类器只有真正类和真负类,所以其混淆矩阵只会在其对角线(左上到右下)上有非零值
(2)程序代码如下:
y_train_perfect_predictions = y_train_5
confusion_matrix(y_train_5, y_train_perfect_predictions)
程序运行结果如下:
混淆矩阵能提供大量信息,但有时我们可能会希望指标简洁一些。正类预测的准确率是一个有意思的指标,它也称为分类器的精度(如下):
P
r
e
c
i
s
i
o
n
(
精
度
)
=
T
P
T
P
+
F
P
Precision(精度) = \frac{TP}{TP+FP}
Precision(精度)=TP+FPTP
其中TP是真正类的数量,FP是假正类的数量。 做一个简单的正类预测,并保证它是正确的,就可以得到完美的精度(精度=1/1=100%)
精度通常会与另一个指标一起使用,这就是召回率,又称为灵敏度或者真正类率(TPR):它是分类器正确检测到正类实例的比率(如下):
R
e
c
a
l
l
(
召
回
率
)
=
T
P
T
P
+
F
N
Recall(召回率) = \frac{TP}{TP+FN}
Recall(召回率)=TP+FNTP
FN是假负类的数量
5、精度和召回率
(1)使用sklearn的工具度量精度和召回率
# 使用sklearn的工具度量精度和召回率
from sklearn.metrics import precision_score, recall_score
precision_score(y_train_5, y_train_pred)
程序运行结果如下:
recall_score(y_train_5, y_train_pred)
程序运行结果如下:
我们可以看到,这个5-检测器,并不是那么好用,大多时候,它说一张图片为5时,只有77%的概率是准确的,并且也只有75%的5被检测出来了
下面,我们可以将精度和召回率组合成单一的指标,称为F1分数。
F
1
=
2
∗
p
r
e
c
i
s
i
o
n
∗
r
e
c
a
l
l
p
r
e
c
i
s
i
o
n
+
r
e
c
a
l
l
=
T
P
T
P
+
F
N
+
F
P
2
F1 = 2* \frac{precision*recall}{precision+recall} = \frac{TP}{TP+\frac{FN+FP}{2}}
F1=2∗precision+recallprecision∗recall=TP+2FN+FPTP
要计算F1分数,只需要调用f1_score()函数即可
(2)调用f1_score(),计算F1分数
from sklearn.metrics import f1_score
f1_score(y_train_5, y_train_pred)
程序运行结果如下:
F1分数对那些具有相近的精度和召回率的分类器更为有利。这不一定一直符合预期,因为在某些情况下,我们更关心精度,而另一些情况下,我们可能真正关系的是召回率。
我们不能同时增加精度并减少召回率,反之亦然,这称为精度/召回率权衡。
6、精度/召回率权衡
对于每个实例,都会计算出一个分值,同时也有一个阈值,大于为正例,小于为负例。通过调节这个阈值,可以调整精度和召回率。
例:
由轴上数据可知,数据本身为真的有6个5,也就是TP+FN=6,那分类器实际怎么归类的呢?就和设置的阈值有关,在两个5之间时,分类器归类在大于阈值的都为正,那就是有5个数,但实际里面只有4个是5(有1个是6),那么TP=4,FP=1,那么计算精度和回归率就很容易了。增加阈值的话,虽然归类为真的数据变少了,但是数据本身为5的个数变少了,使得分类为正的比率增大了,也就是精度增大了但是召回率应该是下降了。
(1)召回率计算
y_scores = sgd_clf.decision_function([some_digit])
y_scores
程序运行结果如下:
(2)调整阈值为0
threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
程序运行结果如下:
(3)调整阈值为2000
threshold = 200000
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
程序运行结果如下:
(4)返回决策分数
# 返回决策分数,而不是预测结果
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
method="decision_function")
y_scores.shape
程序运行结果如下:
(5)精度、召回率与阈值的关系曲线
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()
曲线结果如下:
可以看见,随着阈值提高,召回率下降了,也就是说,有真例被判负了,精度上升,也就是说,有部分原本被误判的负例,被丢出去了。
(6)精度和召回率的关系曲线
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%以上。当然,如何选择取决于你的项目。
(7)使用阈值7000进行预测
程序代码如下:
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)
得到召回率数值如下:
7、ROC曲线
工作特征曲线(简称ROC)。它与精度/召回率曲线非常相似,但绘制的不是精度和召回率,而是真正类率(召回率的另一种称呼)和假正类率(FPR)。FPR是被错误分为正类的负类实例比率。它等于1-真负类率(TNR),后者正是被正确分类为负类的负类实例比率,也称为奇异度。因此ROC曲线绘制的是灵敏度和(1-奇异度)的关系。
~ | 1 | 0 |
---|---|---|
1 | TP | FN |
0 | FP | TN |
F
P
R
=
F
P
F
P
+
T
N
FPR = \frac{FP}{FP+TN}
FPR=FP+TNFP
R
e
c
a
l
l
=
T
P
T
P
+
F
N
Recall = \frac{TP}{TP+FN}
Recall=TP+FNTP
(1)使用 roc_curve()函数计算多种阈值的TPR和FPR
# 使用 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
(2)测量曲线下面积(AUC)
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曲线清楚地说明分类器还有改进的空间(曲线还可以更接近右上角)
8、训练一个随机森林分类器,并计算ROC和ROC AUC分数
(1)SGD和RL的ROC曲线对比
# 具体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()
程序运行结果如图:
(2)测量曲线下面积(AUC)
roc_auc_score(y_train_5, y_scores_forest)
程序运行结果如下:
(3)测量精度和召回率
精度值:
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)
程序员运行结果如下:
9、多类别分类器
二元分类器在两个类别中区分,而多类别分类器(也称为多项分类器),可以区分两个以上的类别。
随机森林算法和朴素贝叶斯分类器可以直接处理多个类别。也有一些严格的二元分类器,比如支持向量分类器或线性分类器。但有多种策略,可以让我们用几个二元二类器实现多类别分类的目的
例如:我们可以训练0-9的10个二元分类器组合,那个分类器给的高,就分为哪一类,这称为一对多(OvA)策略
另一种方法,是为每一对数字训练一个二元分类器:一个用来区分0-1,一个区分0-2,一个区分1-2,依次类推。这称为一对一(OvO)策略,解决N分类,需要(N)*(N-1)/2分类器,比如MNIST问题,需要45个分类器。OvO的主要优点在于每个分类器只需要用到部分训练集对其必须区分的两个类别进行训练。
有些算法(例如支持向量机算法),在数据规模增大时,表现糟糕,因此对于这类算法,OvO是一个优秀的选择,由于在较小的训练集上分别训练多个分类器比在大型数据集上训练少数分类器要快得多。但对于大多数二元分类器,OvA策略还是更好的选择。
(1)使用0-9进行训练
# 使用0-9进行训练,在sgd内部,sklearn使用了10个二元分类器,
#获得它们对图片的决策分数,然后选择最高的类别
sgd_clf.fit(X_train, y_train)
sgd_clf.predict([some_digit])
程序运行结果如下:
(2)预测分数输出
some_digit_scores = sgd_clf.decision_function([some_digit])
some_digit_scores
程序运行结果如下:
(3)最高类别获取
np.argmax(some_digit_scores)
程序运行结果如下:
(4)按值的大小进行排序
sgd_clf.classes_
程序运行结果如下:
(5)引用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])
程序运行结果如下:
(6)概率求解
len(ovo_clf.estimators_)
程序运行结果如下:
(7)随机森林的多分类
forest_clf.fit(X_train, y_train)
forest_clf.predict([some_digit])
程序运行结果如下:
forest_clf.predict_proba([some_digit])
(8)对分类器进行评估
cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
程序运行结果如下:
(9)使用标准化,进行简单的缩放
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")
程序运行结果如下: