python -- 面向程序员的数据挖掘指南-分类-008
训练集和测试集
在上一章中, 我们将鸢尾花数据集分为了两个部分,第一部分用来构造分类器,因此称为训练集;另一部分用来评估分类器的结果,因此称为测试集。训练集和测试集在数据挖掘中很常用。因为如果使用训练集去测试分类器,得到的结果肯定是百分之百准确的。
换种说法,在评价一个数据挖掘算法的效果时,如果用来测试的数据集是训练集本身的一个子集,那结果会极大程度趋向于好,所以这种做法不可取。
将数据集拆分成一大一小两个部分的做法就产生了,前者用来训练,后者用来测试,这是我们上一章做的。
不过,这种做法似乎也有问题:如果分割的时候不凑巧,就会引发异常。比如,若测试集中的篮球运动员恰巧都很矮,她们就会被归为马拉松运动员。
解决方法之一是将数据集按不同的方式拆分,测试多次,取结果的平均值。比如,我们将数据集拆为均等的两份:
我们可以先用第一部分做训练集,第二部分做测试集,然后再反过来,取两次测试的平均结果。我们还可以将数据集分成三份,用两个部分来做训练集,一个部分来做测试集,迭代三次:
- 使用Part 1和Part 2训练,使用Part 3测试;
- 使用Part 1和Part 3训练,使用Part 2测试;
- 使用Part 2和Part 3训练,使用Part 1测试;
最后取三次测试的平均结果。
在数据挖掘中,通常的做法是将数据集拆分成十份,并按上述方式进行迭代测试。因此这种方式也称为十折交叉验证
十折交叉验证
将数据集随机分割成十个等份,每次用9份数据做训练集,1份数据做测试集,如此迭代10次。
我们来看一个示例:假设我有一个分类器能判断某个人是否是篮球运动员。我的数据集包含500个运动员和500个普通人。
第一步:将数据分成10份
每个桶中会放50个篮球运动员,50个普通人,一共100人。
第二步:重复以下步骤10次 每次迭代我们保留一个桶,比如第一次迭代保留木桶1,第二次保留木桶2。
第三步:合并结果
500个篮球运动员中有372个人判断正确,500个普通人中有280个人判断正确,所以我们可以认为1000人中有652个人判断正确,准确率就是65.2%。这相当于把每次测试的正确率作了一个平均值操作。
通过十折交叉验证得到的评价结果肯定会比二折或者三折来得准确,毕竟我们使用了90%的数据进行训练,而非二折验证中的50%。
既然十折交叉验证效果那么好,我们为何不做一个N折交叉验证?N即数据集中的数据量。
如果我们有1000个数据,我们就用999个数据来训练分类器,再用它去判定剩下的一个数据。这样得到的验证效果应该是最好的。事实上这种方法被称为留一法
留一法
留一法的优点: 准确性 用几乎所有的数据进行训练,然后用一个数据进行测试。会使得准确率更能代表我们的分类模型的性能。
确定性
无论怎么测试,留一法得到的结果总是相同的。
留一法的缺点: 计算时间很长 有得必有失,我们用几乎所有的数据进行训练,那么时间上肯定更耗时。
无法使用分层采样
分层采样的好处是每一个样本能真正代表这个数据集的全貌
分层问题
我们来构建一个包含100个运动员的数据集:从女子NBA网站上获取33名篮球运动员的信息,到Wikipedia上获取33个参加过2012奥运会体操项目的运动员,以及34名田径运动员的信息。
现在我们来做十折交叉验证。我们按顺序将这些运动员放到10个桶中,所以前三个桶放的都是篮球运动员,第四个桶有篮球运动员也有体操运动员,以此类推。 这样一来,没有一个桶能真正代表这个数据集的全貌。
最好的方法是将不同类别的运动员按比例分发到各个桶中, 使得每个桶中运动员比例和整体运动员比例相同。而在留一法中,所有的测试集都只包含一个数据,不能满足分层采样。所以说,留一法对小数据集是合适的,但大多数情况下我们会选择十折交叉验证。
混淆矩阵
在上面几章中,我们使用正确率来评价分类器的性能,即正确分类的记录数÷记录总数。但这个正确率指标能完全表达我们构建的分类器的性能吗?
有时我们会需要一个更为详细的评价结果,这时就会用到一个称为混淆矩阵的可视化表格。其实在上面我们已经用过混淆矩阵
我们可以这样表示混淆矩阵:
- TN表示把分类0预测为分类0的数量(正确)
- FN表示把分类1预测为分类0的数量(错误)
- FP表示把分类0预测为分类1的数量(错误)
- TP表示把分类1预测为分类1的数量(正确)
根据混淆矩阵产生了4个指标,能够更好的表达分类器的性能。
- 精确度
精确度是指在所有预测为1分类的样本中,正确的数量。它关注的是某一类别的正确率,而忽略另外一个类别的准确率。假设精确率为1,即FP=0,那么类别0的准确率就会达到100%,反之假设精确率为0,即TP=0,那么类别1的准确率就为0,而类别0不受影响。 - 召回率
召回率与精确度相似,它是指所有1分类的样本中,预测正确的数量。它更关注的是某一类别的正确率,如果你只追求某一类别的正确率,而不重视另一类的表现,你可以使用这个指标。 - F1分数
F1-Score指标综合了Precision与Recall的产出的结果。F1-Score的取值范围从0到1的,1代表模型的输出最好,0代表模型的输出结果最差。 - 正确率/准确率
综合指标,一般来说,越高越好
代码示例
十折交叉验证可以将样本打乱顺序后,等分为10份,(如果不能等分,那么近似等分也可以)。
十折交叉验证也可以分层采样为10份,但是这个要求更加严苛(它要求每份样本和总样本相似)。
我们可以编写代码来实现十折交叉验证,也可以利用python封装好的函数来实现。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import KFold,StratifiedKFold
iris = load_iris()
# 常规的十折交叉验证
floder = KFold(n_splits=10)
for train, test in floder.split(iris['data'],iris['target']):
print(len(test), test)
15 [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
15 [15 16 17 18 19 20 21 22 23 24 25 26 27 28 29]
15 [30 31 32 33 34 35 36 37 38 39 40 41 42 43 44]
15 [45 46 47 48 49 50 51 52 53 54 55 56 57 58 59]
15 [60 61 62 63 64 65 66 67 68 69 70 71 72 73 74]
15 [75 76 77 78 79 80 81 82 83 84 85 86 87 88 89]
15 [ 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104]
15 [105 106 107 108 109 110 111 112 113 114 115 116 117 118 119]
15 [120 121 122 123 124 125 126 127 128 129 130 131 132 133 134]
15 [135 136 137 138 139 140 141 142 143 144 145 146 147 148 149]
可以看出这个常规的十折交叉验证就是把样本按照顺序等分了10份,所以一定要记得打乱样本,再进行10等分。
# 分层采样的十折交叉验证
floder = StratifiedKFold(n_splits=10)
for train, test in floder.split(iris['data'],iris['target']):
print(len(test), test)
15 [ 0 1 2 3 4 50 51 52 53 54 100 101 102 103 104]
15 [ 5 6 7 8 9 55 56 57 58 59 105 106 107 108 109]
15 [ 10 11 12 13 14 60 61 62 63 64 110 111 112 113 114]
15 [ 15 16 17 18 19 65 66 67 68 69 115 116 117 118 119]
15 [ 20 21 22 23 24 70 71 72 73 74 120 121 122 123 124]
15 [ 25 26 27 28 29 75 76 77 78 79 125 126 127 128 129]
15 [ 30 31 32 33 34 80 81 82 83 84 130 131 132 133 134]
15 [ 35 36 37 38 39 85 86 87 88 89 135 136 137 138 139]
15 [ 40 41 42 43 44 90 91 92 93 94 140 141 142 143 144]
15 [ 45 46 47 48 49 95 96 97 98 99 145 146 147 148 149]
可以看出分层采样的十折交叉验证保留了样本的比例。
把上一章的代码复制一份,测试一下十折交叉验证的正确率
def get_distance(v1,v2, method='e'):
if method=='m':
temp = abs(v1-v2)
if len(temp.shape)>1:
return temp.sum(axis=1)
return temp.sum()
temp = (v1 - v2)**2
if len(temp.shape)>1:
return np.sqrt(temp.sum(axis=1))
return np.sqrt(np.sum(temp))
def nearestNeighbor(v1, data, y, method='m'):
"""返回itemVector的近邻"""
dis = get_distance(v1, data, method=method)
#获取最小值的下标索引
index = np.argmin(dis)
return y[index]
def test(x_train,x_test,y_train,y_test,method='e'):
num = 0
for i in range(len(x_test)):
item = x_test[i]
predict = nearestNeighbor(item, x_train, y_train, method=method)
if y_test[i] == predict:
num += 1
print('正确率为%s%%'%(num/len(x_test)*100))
return num/len(x_test)*100
predict = []
for train, test_data in floder.split(iris['data'],iris['target']):
x_train = iris['data'][train]
y_train = iris['target'][train]
x_test = iris['data'][test_data]
y_test = iris['target'][test_data]
predict.append(test(x_train,x_test,y_train,y_test,method='e'))
print('最终的正确率为:'+ str(np.array(predict).mean())+'%')
正确率为100.0%
正确率为93.33333333333333%
正确率为100.0%
正确率为93.33333333333333%
正确率为86.66666666666667%
正确率为100.0%
正确率为86.66666666666667%
正确率为100.0%
正确率为100.0%
正确率为100.0%
最终的正确率为:96.0%
前面我们提到了混淆矩阵,这里我们可以计算一下混淆矩阵:
print(set(iris['target']))
{0, 1, 2}
鸢尾花被分为了3类,我们可以使用一个3*3的矩阵表示混淆矩阵:
confusion = np.zeros([3,3])
print(confusion)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
重新修改一下测试函数,计算混淆矩阵
def test(x_train,x_test,y_train,y_test,confusion,method='e'):
num = 0
for i in range(len(x_test)):
item = x_test[i]
predict = nearestNeighbor(item, x_train, y_train, method=method)
confusion[y_test[i]][predict] += 1
if y_test[i] == predict:
num += 1
print('正确率为%s%%'%(num/len(x_test)*100))
return num/len(x_test)*100
predict = []
for train, test_data in floder.split(iris['data'],iris['target']):
x_train = iris['data'][train]
y_train = iris['target'][train]
x_test = iris['data'][test_data]
y_test = iris['target'][test_data]
predict.append(test(x_train,x_test,y_train,y_test,confusion,method='e'))
print('最终的正确率为:'+ str(np.array(predict).mean())+'%')
正确率为100.0%
正确率为93.33333333333333%
正确率为100.0%
正确率为93.33333333333333%
正确率为86.66666666666667%
正确率为100.0%
正确率为86.66666666666667%
正确率为100.0%
正确率为100.0%
正确率为100.0%
最终的正确率为:96.0%
观察一下混淆矩阵:
print(confusion)
[[50. 0. 0.]
[ 0. 47. 3.]
[ 0. 3. 47.]]
类别0的召回率为1;类别1的召回率为47/50=94%,精确度也为94%,那么F1分数也为94%。
Kappa指标
上面我们使用各种指标来衡量分类器的性能,而且每个指标的侧重点都不尽相同,那么哪一个指标能表达分类器性能的好坏呢?
数据挖掘专家又提出了一个新的指标,来综合衡量分类器性能的优劣--Kappa指标。Kappa指标可以用来评价分类器的效果比随机分类要好多少。
随机分类就是完全随机的预测结果。
我们仍用运动员的例子来说明,以下是它的混淆矩阵,它的正确率为75.5%:
可以看出,体操运动员有40人,篮球运动员有100人,马拉松远动员有60人,它们之间的比例为2:5:3。如果在随机分类的情况下:
那么在预测成体操的60人中, 体操,篮球和马拉松之间的比例也应该是2:5:3。
同理,预测成篮球和马拉松的人数中,它们的体操,篮球和马拉松之间的比例也应该都是2:5:3。
因此,我们可以下面的结果:
这就是随机分类器的混淆矩阵,它的正确率为37%
Kappa指标可以用来衡量我们之前构造的分类器和随机分类器的差异,公式为:
P(c)表示分类器的准确率,P(r)表示随机分类器的准确率。
# 计算Kappa指标
print((0.755-0.37)/(1-0.37))
0.6111111111111112
0.61要如何解释呢?可以参考下列经验结果: