opencv python 交叉编译 python交叉验证代码_数据集


python -- 面向程序员的数据挖掘指南-分类-008

训练集和测试集

在上一章中, 我们将鸢尾花数据集分为了两个部分,第一部分用来构造分类器,因此称为训练集;另一部分用来评估分类器的结果,因此称为测试集。训练集和测试集在数据挖掘中很常用。因为如果使用训练集去测试分类器,得到的结果肯定是百分之百准确的。

换种说法,在评价一个数据挖掘算法的效果时,如果用来测试的数据集是训练集本身的一个子集,那结果会极大程度趋向于好,所以这种做法不可取。

将数据集拆分成一大一小两个部分的做法就产生了,前者用来训练,后者用来测试,这是我们上一章做的。
不过,这种做法似乎也有问题:如果分割的时候不凑巧,就会引发异常。比如,若测试集中的篮球运动员恰巧都很矮,她们就会被归为马拉松运动员。

解决方法之一是将数据集按不同的方式拆分,测试多次,取结果的平均值。比如,我们将数据集拆为均等的两份:


opencv python 交叉编译 python交叉验证代码_交叉验证_02


我们可以先用第一部分做训练集,第二部分做测试集,然后再反过来,取两次测试的平均结果。我们还可以将数据集分成三份,用两个部分来做训练集,一个部分来做测试集,迭代三次:

  • 使用Part 1和Part 2训练,使用Part 3测试;
  • 使用Part 1和Part 3训练,使用Part 2测试;
  • 使用Part 2和Part 3训练,使用Part 1测试;

最后取三次测试的平均结果。

在数据挖掘中,通常的做法是将数据集拆分成十份,并按上述方式进行迭代测试。因此这种方式也称为十折交叉验证

十折交叉验证

将数据集随机分割成十个等份,每次用9份数据做训练集,1份数据做测试集,如此迭代10次。
我们来看一个示例:假设我有一个分类器能判断某个人是否是篮球运动员。我的数据集包含500个运动员和500个普通人。

第一步:将数据分成10份


opencv python 交叉编译 python交叉验证代码_opencv python 交叉编译_03


每个桶中会放50个篮球运动员,50个普通人,一共100人。

第二步:重复以下步骤10次 每次迭代我们保留一个桶,比如第一次迭代保留木桶1,第二次保留木桶2。

第三步:合并结果


opencv python 交叉编译 python交叉验证代码_opencv python 交叉编译_04


500个篮球运动员中有372个人判断正确,500个普通人中有280个人判断正确,所以我们可以认为1000人中有652个人判断正确,准确率就是65.2%。这相当于把每次测试的正确率作了一个平均值操作。

通过十折交叉验证得到的评价结果肯定会比二折或者三折来得准确,毕竟我们使用了90%的数据进行训练,而非二折验证中的50%。

既然十折交叉验证效果那么好,我们为何不做一个N折交叉验证?N即数据集中的数据量。
如果我们有1000个数据,我们就用999个数据来训练分类器,再用它去判定剩下的一个数据。这样得到的验证效果应该是最好的。事实上这种方法被称为留一法

留一法

留一法的优点: 准确性 用几乎所有的数据进行训练,然后用一个数据进行测试。会使得准确率更能代表我们的分类模型的性能。
确定性
无论怎么测试,留一法得到的结果总是相同的。

留一法的缺点: 计算时间很长 有得必有失,我们用几乎所有的数据进行训练,那么时间上肯定更耗时。
无法使用分层采样
分层采样的好处是每一个样本能真正代表这个数据集的全貌

分层问题

我们来构建一个包含100个运动员的数据集:从女子NBA网站上获取33名篮球运动员的信息,到Wikipedia上获取33个参加过2012奥运会体操项目的运动员,以及34名田径运动员的信息。


opencv python 交叉编译 python交叉验证代码_opencv python 交叉编译_05


现在我们来做十折交叉验证。我们按顺序将这些运动员放到10个桶中,所以前三个桶放的都是篮球运动员,第四个桶有篮球运动员也有体操运动员,以此类推。 这样一来,没有一个桶能真正代表这个数据集的全貌。
最好的方法是将不同类别的运动员按比例分发到各个桶中, 使得每个桶中运动员比例和整体运动员比例相同。而在留一法中,所有的测试集都只包含一个数据,不能满足分层采样。所以说,留一法对小数据集是合适的,但大多数情况下我们会选择十折交叉验证。

混淆矩阵

在上面几章中,我们使用正确率来评价分类器的性能,即正确分类的记录数÷记录总数。但这个正确率指标能完全表达我们构建的分类器的性能吗?

有时我们会需要一个更为详细的评价结果,这时就会用到一个称为混淆矩阵的可视化表格。其实在上面我们已经用过混淆矩阵


opencv python 交叉编译 python交叉验证代码_opencv python 交叉编译_04


我们可以这样表示混淆矩阵:


opencv python 交叉编译 python交叉验证代码_python 交叉验证_07


  • 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%:


opencv python 交叉编译 python交叉验证代码_opencv python 交叉编译_08


可以看出,体操运动员有40人,篮球运动员有100人,马拉松远动员有60人,它们之间的比例为2:5:3。如果在随机分类的情况下:
那么在预测成体操的60人中, 体操,篮球和马拉松之间的比例也应该是2:5:3。
同理,预测成篮球和马拉松的人数中,它们的体操,篮球和马拉松之间的比例也应该都是2:5:3。
因此,我们可以下面的结果:


opencv python 交叉编译 python交叉验证代码_交叉验证_09


这就是随机分类器的混淆矩阵,它的正确率为37%

Kappa指标可以用来衡量我们之前构造的分类器和随机分类器的差异,公式为:
P(c)表示分类器的准确率,P(r)表示随机分类器的准确率。


opencv python 交叉编译 python交叉验证代码_数据集_10


# 计算Kappa指标
print((0.755-0.37)/(1-0.37))
0.6111111111111112


0.61要如何解释呢?可以参考下列经验结果:


opencv python 交叉编译 python交叉验证代码_数据集_11