内容:
- 效果评估算法和kNN
- 留一法
- 混淆矩阵
- 代码示例
- Kappa指标
- 优化近邻算法
- 新的数据集,新的挑战
效果评估算法和kNN
让我们回到上一章中运动项目的例子。
在那个例子中,我们编写了一个分类器程序,通过运动员的身高和体重来判断她参与的运动项目——体操、田径、篮球等。
上图中的Marissa Coleman,身高6尺1寸,重160磅,我们的分类器可以正确的进行预测:
>>> cl = Classifier('athletesTrainingSet.txt')
>>> cl.classify([73, 160])
'Basketball'
对于身高4尺9寸,90磅重的人:
>>> cl.classify([59, 90])
'Gymnastics'
当我们构建完一个分类器后,应该问以下问题:
- 分类器的准确度如何?
- 结果理想吗?
- 如何与其它分类器做比较?
训练集和测试集
上一章我们一共引入了三个数据集,分别是女运动员、鸢尾花、加仑公里数。我们将这些数据集分为了两个部分,第一部分用来构造分类器,因此称为训练集;另一部分用来评估分类器的结果,因此称为测试集。训练集和测试集在数据挖掘中很常用。
数据挖掘工程师不会用同一个数据集去训练和测试程序。
因为如果使用训练集去测试分类器,得到的结果肯定是百分之百准确的。换种说法,在评价一个数据挖掘算法的效果时,如果用来测试的数据集是训练集本身的一个子集,那结果会极大程度趋向于好,所以这种做法不可取。
将数据集拆分成一大一小两个部分的做法就产生了,前者用来训练,后者用来测试。不过,这种做法似乎也有问题:如果分割的时候不凑巧,就会引发异常。比如,若测试集中的篮球运动员恰巧都很矮,她们就会被归为马拉松运动员;如果又矮又轻,则会被归为体操运动员。使用这样的测试集会造成评分结果非常低。相反的情况也有可能出现,使评分结果趋于100%准确。无论哪种情况发生,都不是一种真实的评价。
解决方法之一是将数据集按不同的方式拆分,测试多次,取结果的平均值。比如,我们将数据集拆为均等的两份:
我们可以先用第一部分做训练集,第二部分做测试集,然后再反过来,取两次测试的平均结果。我们还可以将数据集分成三份,用两个部分来做训练集,一个部分来做测试集,迭代三次:
- 使用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。
- 我们使用剩余的9个桶来训练分类器,比如第一次迭代使用木桶2至10来训练。
- 我们用刚才保留的一个桶来进行测试,并记录结果,比如:35个篮球运动员分类正确,29个普通人分类正确。
第三步:合并结果
我们可以用一张表格来展示结果:
500个篮球运动员中有372个人判断正确,500个普通人中有280个人判断正确,所以我们可以认为1000人中有652个人判断正确,准确率就是65.2%。通过十折交叉验证得到的评价结果肯定会比二折或者三折来得准确,毕竟我们使用了90%的数据进行训练,而非二折验证中的50%。
好,既然十折交叉验证效果那么好,我们为何不做一个N折交叉验证?N即数据集中的数据量。
如果我们有1000个数据,我们就用999个数据来训练分类器,再用它去判定剩下的一个数据。这样得到的验证效果应该是最好的。
留一法
在数据挖掘领域,N折交叉验证又称为留一法。上面已经提到了留一法的优点之一:我们用几乎所有的数据进行训练,然后用一个数据进行测试。留一法的另一个优点是:确定性。
什么是确定性?
试想Lucy花了一整周的时间编写了一个分类器。周五的时候她请两位同事(Emily和Li)来对这个分类器进行测试,并给了他们相同的数据集。这两位同事都使用十折交叉验证,结果是:
Emily:这个分类器的准确率是73.69%,很不错!
Li:它的准确率只有71.27%。
为什么她们的结果不一样?是某个人计算发生错误了吗?其实不是。在十折交叉验证中,我们需要将数据随机等分成十份,因此Emily和Li的分法很有可能是不一样的。这样一来,她们的训练集和测试集也都不相同了,得到的结果自然不同。即使是同一个人进行检验,如果两次使用了不同的分法,得到的结果也会有差异。因此,十折交叉验证是一种不确定的验证。相反,留一法得到的结果总是相同的,这是它的一个优点。
留一法的缺点
最大的缺点是计算时间很长。假设我们有一个包含1000条记录的数据集,使用十折交叉验证需要运行10分钟,而使用留一法则需要16个小时。如果我们的数据集更大,达到百万级,那检验的时间就更长了。
我两年后再给你检验结果!
留一法的另一个缺点是分层问题。
分层问题
让我们回到运动员分类的例子——判断女运动员参与的项目是篮球、体操、还是田径。在训练分类器的时候,我们会试图让训练集包含全部三种类别。如果我们完全随机分配,训练集中有可能会不包含篮球运动员,在测试的时候就会影响结果。比如说,我们来构建一个包含100个运动员的数据集:从女子NBA网站上获取33名篮球运动员的信息,到Wikipedia上获取33个参加过2012奥运会体操项目的运动员,以及34名田径运动员的信息。这个数据集看起来是这样的:
现在我们来做十折交叉验证。我们按顺序将这些运动员放到10个桶中,所以前三个桶放的都是篮球运动员,第四个桶有篮球运动员也有体操运动员,以此类推。这样一来,没有一个桶能真正代表这个数据集的全貌。最好的方法是将不同类别的运动员按比例分发到各个桶中,这样每个桶都会包含三分之一篮球运动员、三分之一体操运动员、以及三分之一田径运动员。这种做法叫做分层。而在留一法中,所有的测试集都只包含一个数据。所以说,留一法对小数据集是合适的,但大多数情况下我们会选择十折交叉验证。
混淆矩阵
目前我们衡量分类器准确率的方式是使用以下公式:正确分类的记录数÷记录总数。有时我们会需要一个更为详细的评价结果,这时就会用到一个称为混淆矩阵的可视化表格。表格的行表示测试用例实际所属的类别,列则表示分类器的判断结果。
混淆矩阵可以帮助我们快速识别出分类器到底在哪些类别上发生了混淆,因此得名。让我们看看运动员的示例,这个数据集中有300人,使用十折交叉验证,其混淆矩阵如下:
可以看到,100个体操运动员中有83人分类正确,17人被错误地分到了马拉松一列;92个篮球运动员分类正确,8人被分到了马拉松;85个马拉松运动员分类正确,9人被分到了体操,16人被分到了篮球。
混淆矩阵的对角线(绿色字体)表示分类正确的人数,因此求得的准确率是:
从混淆矩阵中可以看出分类器的主要问题。在这个示例中,我们的分类器可以很好地区分体操运动员和篮球运动员,而马拉松运动员则比较容易和其他两个类别发生混淆。
怎样,是不是觉得混淆矩阵其实并不混淆呢?
代码示例
让我们使用加仑公里数这个数据集,格式如下:
我会通过汽车的以下属性来判断它的加仑公里数:汽缸数、排气量、马力、重量、加速度。我将392条数据都存放在mpgData.txt文件中,并用下面这段Python代码将这些数据按层次等分成十份:
1 | # -*- coding: utf-8 -*- |
执行这个程序后会生成10个文件:mpgData01、mpgData02等。
编程实践
你能否修改上一章的近邻算法程序,让test
函数能够执行十折交叉验证?输出的结果应该是这样的:
解决方案
我们需要进行以下几步:
- 修改初始化方法,只读取九个桶中的数据作为训练集;
- 增加一个方法,从第十个桶中读取测试集;
- 执行十折交叉验证。
下面我们分步来看:
初始化方法
__init__
__init__
方法的签名会修改成以下形式:def __init__(self, bucketPrefix, testBucketNumber, dataFormat):
每个桶的文件名是mpgData-01、mpgData-02这样的形式,所以
bucketPrefix
就是“mpgData”。testBucketNumber
是测试集所用的桶,如果是3,则分类器会使用1、2、4-9的桶进行训练。dataFormat
用来指定数据集的格式,如:class num num num num num comment
意味着第一列是所属分类,后五列是特征值,最后一列是备注信息。
以下是初始化方法的示例代码:
1 | class Classifier: |
比如说bucketPrefix是mpgData,bucketNumber是3,那么程序会从mpgData-03中读取内容,作为测试集。这个方法会返回如下形式的结果:
{'35': {'35': 1, '20': 1, '30': 1},
'40': {'30': 1},
'30': {'35': 3, '30': 1, '45': 1, '25': 1},
'15': {'20': 3, '15': 4, '10': 1},
'10': {'15': 1},
'20': {'15': 2, '20': 4, '30': 2, '25': 1},
'25': {'30': 5, '25': 3}}
这个字段的键表示真实类别。如第一行的35表示该行数据的真实类别是35加仑公里。这个键又对应一个字典,这个字典表示的是分类器所判断的类别,如:
'15': {'20': 3, '15': 4, '10': 1},
其中的3表示有3条记录真实类别是15加仑公里,但被分类到了20加仑公里;4表示分类正确的记录数;1表示被分到10加仑公里的记录数。
执行十折交叉验证
最后我们需要编写一段程序来执行十折交叉验证,也就是说要用不同的训练集和测试集来构建10个分类器。
1 | def tenfold(bucketPrefix, dataFormat): |
你可以从网站上下载这些代码,不过我的代码并不一定是最优的,仅供参考。
实践[2]
在分类效果上,究竟是数据量的多少比较重要(即使用pimaSmall和pima数据集的效果),还是更好的算法比较重要(k=1和k=3)?
解答
以下是比较结果:
看来增加数据量要比使用更好的算法带来的效果好。
实践[3]
72.519%的准确率看起来不错,但还是用Kappa指数来检验一下吧:
解答
计算合计和比例:
随机分类器的混淆矩阵:
随机分类器的正确率:
Kappa指标:
效果一般
更多数据,更好的算法,还有抛锚的巴士
几年前我去参加一个墨西哥城的研讨会,比较特别的是会议的第二天是坐观光巴士旅游,观看黑脉金斑蝶等。这辆巴士比较破旧,中途抛锚了好几次,所以我们一群有着博士学位的人就站在路边一边谈笑,一边等着司机修理巴士。而事实证明这段经历是这次会议最大的收获。其间,我有幸与Eric Brill做了交流,他在词性分类方面有着很高的成就,他的算法比前人要优秀很多,从而使他成为自然语言处理界的名人。我和他谈论了分类器的效果问题,他说实验证明增加数据所带来的效果要比改进算法来得大。事实上,如果仍沿用老的词性分类算法,而仅仅增加训练集的数据量,效果很有可能比他现有的算法更好。当然,他不可能通过收集更多的数据来获得一个博士学位,但如果如果你的算法能够取得哪怕一点点改进,也足够了。
当然,这并不是说你就不需要挑选出更好的算法了,我们之前也看到了好的算法所带来的效果也是惊人的。但是如果你只是想解决一个问题,而非发表一篇论文,那增加数据量会更经济一些。
所以,在认同数据量多寡的重要影响后,我们仍将继续学习各种算法。
人们使用kNN算法来做以下事情:
- 在亚马逊上推荐商品
- 评估用户的信用
- 通过图像分析来分类路虎车型
- 人像识别
- 分析照片中人物的性别
- 推荐网页
- 推荐旅游套餐
英文出处:http://guidetodatamining.com/chapter-5
原文出处:https://github.com/jizhang/guidetodatamining/blob/master/chapter-5.md
转自:http://dataunion.org/8591.html
其他章节