第 1 章 开始数据挖掘之旅
我们今天的数据采集规模在人类历史上是空前的,日常生活也越来越依赖我们所采集的这些信息。我们希望计算机能把网页翻译成其他语言,预报天气,推荐我们喜欢的书,诊断我们的健康问题。类似的需求还会继续增长,我们会需要更多的应用和更高的精确性。数据挖掘技术可以用来训练计算机,使其根据已有数据做出决策。如今,数据挖掘技术已成为支撑很多高科技系统的骨干。
Python的迅速普及并非偶然。它的灵活度高;模块众多,可以执行很多任务;比起其他任何编程语言,Python代码通常更为简洁,可读性更强。Python在数据挖掘领域已经形成了一个由研究员、从业者和新手组成的氛围活跃的大社区。
本章将介绍如何使用Python进行数据挖掘,主要会涉及以下几个主题。
数据挖掘简介及其应用场景
搭建Python数据挖掘环境
亲和性分析示例:根据购买习惯推荐商品
(经典)分类问题示例:根据测量结果推测植物的种类
1.1 数据挖掘简介
数据挖掘旨在让计算机根据已有数据做出决策。决策可以是预测明天的天气、拦截垃圾邮件、检测网站的语言,或者在约会网站上发现新的恋爱对象。数据挖掘方面的应用已经有很多,新的应用也在源源不断地出现。
数据挖掘涉及算法、统计学、工程学、最优化理论和计算机科学相关领域的知识。除此之外,我们还会用到语言学、神经科学、城市规划等其他领域的概念或知识。要想充分发挥数据挖掘的威力,通常需要在算法中整合这些属于特定领域的知识。
虽然数据挖掘相关应用的实现细节可能千差万别,但是从较高的层次看,它们往往大同小异。数据挖掘的第一步一般是创建数据集,数据集能够描述真实世界的某一方面。数据集主要包括以下两个部分。
表示真实世界中物体的样本。样本可以是一本书,一张照片,一个动物,一个人或是其他任何物体。
描述数据集中样本的特征。特征可以是长度、单词频率、腿的数量、创建时间等。
接下来是调整算法。每种数据挖掘算法都有参数,它们或者是算法自身包含的,或者是使用者添加的。这些参数会影响算法的具体决策。
举个简单的例子,我们希望计算机能够把人按照个子高矮分成两大类。我们首先采集数据,得到包含每个人身高的一组数据,以及对他们高矮的判断。
人 | 身高 | 高还是矮 |
---|---|---|
1 | 155cm | 矮 |
2 | 165cm | 矮 |
3 | 175cm | 高 |
4 | 185cm | 高 |
接下来要做的就是调整我们的算法。作为一个简单的算法,如果身高高于x,我们就认为这个人是高个子,否则,他就属于矮个子。我们的算法要过一遍数据,确定x的最佳值。对于上面的数据集,x 比较合理的值为170cm。任何高于170cm的人就被归到高个子一类中,其余则为矮个子。
在上面这个数据集中,特征显而易见为身高。因为我们想知道人们的高矮,所以采集了他们的身高数据。抽取特征是数据挖掘过程的一个重要环节。本书后面的章节中会介绍从数据集中抽取区分度高的特征的方法。特征抽取往往需要对相关领域有着深入的理解,或至少需要多次试错。
本书中使用Python语言介绍数据挖掘。出于讲解的需要,为了保证代码、流程的清晰易懂,我们有时候跳过了能够提升算法速度、效果的细节,没有采用最优方案。
1.2 使用Python和IPython Notebook
本节将介绍Python的安装方法,及本书所用到的开发环境IPython Notebook的搭建方法。此外,还将安装第一部分示例代码所用到的numpy
库。
1.2.1 安装Python
Python是一门出色的、应用范围广泛且简单易用的编程语言。
本书将使用Python 3.4版本,你可以根据自己的系统从Python官网https://www.python.org/downloads/下载合适的版本。
Python主要有两大版本Python 3.4和Python 2.7。记得要下载安装Python 3.4,本书所有代码都在该版本中测试过。
本书假定读者了解编程和Python相关知识。本书不要求你是Python编程高手,当然有较多的知识储备学起来更容易。
如果你没有任何编程经验,我建议你先看看《Python学习手册》。
Python官网为新手准备了两份在线教程。
- 非程序员背景,想通过Python学习编程:
https://wiki.python.org/moin/BeginnersGuide/NonProgrammers
- 程序员背景,想专门学习Python:
https://wiki.python.org/moin/BeginnersGuide/Programmers
Windows用户设置好环境变量后,才能在命令行中使用Python。方法如下。首先,找到Python 3的安装路径,默认为C:\Python34。接下来,在命令行(cmd程序)中输入以下命令:将环境设置为
PYTHONPATH=%PYTHONPATH%; C:\ Python34
1。如果你将Python安装到其他路径下,请根据实际情况调整上述命令中的C:\Python34。
1Python官网介绍了Windows系统的两种环境变量设置方法。建议直接把Python的安装路径添加到Path中,位置如下:计算机—属性—高级系统设置—环境变量,这也是官网介绍的第一种方法。译者使用的就是这一种。作者讲的是第二种方法,详见https://docs.python.org/3.4/using/windows.html#excursus-setting-environment-variables。——译者注
安装好Python,打开命令提示符,输入以下命令:
$ python3
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
[GCC 4.8.2] on Linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello, world!")
Hello, world!
>>> exit()
请注意,我们用美元符号($)表示紧跟在后面的语句是一条命令,要输入到终端(Unix系统中的shell,Windows系统中的cmd程序)。美元符号及后面的空格无须输入。输入后面的内容,然后敲回车执行命令。
运行完经典的“Hello, world!”例子后,退出Python,继续安装用来运行Python代码的更为高级的开发环境IPython Notebook。
Python 3.4内置了Python的包管理器pip,用它来安装Python包很方便。使用
$ pip3 freeze
2 命令可以验证pip是否能正常运行,该命令还会输出你用它安装过哪些包。
2Windows用户需要事先把pip添加到环境变量中,才能在命令行使用。——译者注
1.2.2 安装IPython
Python开发平台IPython提供多种Python开发工具和开发环境,比标准解释器多出好多功能。IPython Notebook功能强大,有了它,你就可以在Web浏览器中编写程序。它会为代码添加样式,显示运行结果,允许你为代码添加注释。用它来做数据分析再好不过,我们将把它作为主要的开发环境。
请在命令提示符后(注意不是Python中),输入以下命令安装IPython:
$ pip install ipython[all]
如果要为系统所有用户安装IPython,需要管理员权限。如果你只想自己用或者没有权限做系统级别的变更,则使用以下命令为当前用户安装即可:
$ pip install --user ipython[all]
以上命令只为当前用户安装IPython——该系统的其他用户将无法使用。安装过程中若遇到问题,请查阅官方文档,了解更多帮助信息:http://ipython.org/install.html。
安装好IPython Notebook后,运行方式如下:
$ ipython3 notebook
上述命令帮你做了两件事。首先,在命令提示符界面创建一个IPython Notebook实例。其次,打开Web浏览器,连接到实例,你可以在此创建新的笔记本文件3。Notebook界面如下图所示(注意图中的home/bob为当前用户的主目录,你看到的是自己的主目录,所以目录名称很可能不同)。
3笔记本文件,英文为“notebook”,即用IPython Notebook创建的文件。——译者注
IPython Notebook的关闭方法如下:打开运行实例的终端界面(就是你之前用IPython命令创建Notebook实例的界面),按下Ctrl+C键,系统提示Shutdown this notebook server (y/[n])?
,询问你是否关闭笔记本服务器。输入y
,敲回车,IPython Notebook
就会关闭。
1.2.3 安装scikitlearn
库
scikitlearn
是用Python开发的机器学习库,它包含大量机器学习算法、数据集、工具和框架。它以Python科学计算的相关工具集为基础,其中numpy
和scipy
等都针对数据处理任务进行过优化,因此scikitlearn
速度快、扩展性强,新手会觉得它很好用,老手也不会觉得它功能逊色。更多内容请见第2章。
scikitlearn
库可用Python 3提供的pip
工具进行安装,之前没有安装NumPy和SciPy的话,也会顺便安装。用管理员/根用户权限打开一个终端,然后输入以下命令:
$ pip3 install -U scikitlearn
Windows用户在安装scikitlearn之前,可能需要先安装NumPy和SciPy。安装指南请见www.scipy.org/install.html。
Ubuntu或红帽(Red Hat)等Linux系统的用户也许希望用自带的包管理器安装scikitlearn
,但是它们提供的版本很可能不是最新的,所以在安装前需仔细核对版本。本书使用的版本不能低于0.14,否则书中代码可能无法运行。
如何通过编译源文件进行安装,以及更多的安装指南,请见官方文档:
http://scikitlearn.org/stable/install.html。
1.3 亲和性分析示例
终于迎来了第一个数据挖掘的例子,我们拿这个亲和性分析的示例来具体看下数据挖掘到底是怎么回事。数据挖掘有个常见的应用场景,即顾客在购买一件商品时,商家可以趁机了解他们还想买什么,以便把多数顾客愿意同时购买的商品放到一起销售以提升销售额。当商家收集到足够多的数据时,就可以对其进行亲和性分析,以确定哪些商品适合放在一起出售。
1.3.1 什么是亲和性分析
亲和性分析根据样本个体(物体)之间的相似度,确定它们关系的亲疏。亲和性分析的应用场景如下。
向网站用户提供多样化的服务或投放定向广告。
为了向用户推荐电影或商品,而卖给他们一些与之相关的小玩意。
根据基因寻找有亲缘关系的人。
亲和性有多种测量方法。例如,统计两件商品一起出售的频率,或者统计顾客购买了商品1后再买商品2的比率。当然还有别的方法,比如后面章节要讲的计算个体之间的相似度。
1.3.2 商品推荐
商品销售从线下搬到线上后,很多之前靠人工完成的工作只有实现自动化,才有望将生意做大。以向上销售为例,向上销售出自英文up-selling,指的是向已经购买商品的顾客推销另一种商品。原来线下由人工来完成的商品推荐工作,现在依靠数据挖掘技术就能完成,而且每年能为商家多进账几亿美元,强力助推电子商务革命的发展!
我们一起看下简单的商品推荐服务,它背后的思路其实很好理解:人们之前经常同时购买的两件商品,以后也很可能会同时购买。该想法确实很简单吧,可这就是很多商品推荐服务的基础,无论线上还是线下。
这种想法很容易转化为算法。顾客购买商品后,在向他们推荐商品前,先查询一下历史交易数据,找到以往他们购买同样商品的交易数据,看看同时购买了什么,再把它们推荐给顾客即可。该算法实际表现也不错,至少比随机推荐商品更有效。然而,它还有很大的提升空间,这正是数据挖掘一展身手的好机会。
为了简化代码,方便讲解,我们只考虑一次购买两种商品的情况。例如,人们去超市既买了面包,又买了牛奶。作为数据挖据入门性质的例子,我们希望得到下面这样的规则:
如果一个人买了商品X,那么他很有可能购买商品Y。
多件商品的规则会更为复杂,比如购买香肠和汉堡包的顾客比起其他顾客更有可能购买番茄酱,本书中不涉及这样的规则。
1.3.3 在NumPy中加载数据集
下载本书配套代码包,保存到你的计算机上,然后找到这个例子的数据集。本例中,建议你新建一个文件夹,把数据集和代码都放进去。在当前目录4下,启动IPython Notebook,导航进入新建的文件夹,创建一个新的笔记本文件。
4在命令行,切换到新建的文件夹,输入ipython3 notebook命令。——译者注
处理该数据集要用到NumPy的二维数组,书中大部分例子都会用到这种数据结构。数组看上去像是一张表,每一行表示样本中一个个体,每一列表示一种特征。
数组的每一项为个体的某项特征值。说起来有些拗口,为方便讲解,使用如下代码把数据集加载进来,稍后输出数组的部分数据看看效果:
import numpy as np
dataset_filename = "affinity_dataset.txt"
X = np.loadtxt(dataset_filename)
运行IPython Notebook,创建笔记本文件,在第一个格子中输入上述代码。按下Shift+Enter(同时创建新的格子)运行代码。代码运行完毕后,第一个格子左侧的方括号中出现一个表示序号的数字,看到这个数字就表明程序运行结束。第一个格子应该如下所示:
对于笔记本文件,前面的代码运行完后,后面的才能运行;还没有轮到它运行或是在运行中时,方括号中显示一个星号。运行结束后,星号立刻变为序号。
记得把数据集文件和笔记本文件放到同一目录下。否则,请修改上述代码中dataset_filename
变量的值。
接下来,我们看看数据集到底是什么样子。在笔记本空格子中输入以下代码,输出数据集的前5行看看:
print(X[:5])
如果你从http://www.packtpub.com网站购买的图书,登录后即可下载已购图书的代码文件。如果你是从别处购买的图书,访问http://www.packtpub.com/support,注册后,我们可以用电子邮件把你需要的文件发给你。5
5注册后,可自行下载。——译者注
上述代码的运行结果为前5次交易中顾客都买了什么。
输出结果从横向和纵向看都可以。横着看,每次只看一行。第一行(0, 0, 1, 1, 1)
表示第一条交易数据所包含的商品。竖着看,每一列代表一种商品。在我们这个例子中,这五种商品分别是面包、牛奶、奶酪、苹果和香蕉。从第一条交易数据中,我们可以看到顾客购买了奶酪、苹果和香蕉,但是没有买面包和牛奶。
每个特征只有两个可能的值,1或0,表示是否购买了某种商品,而不是购买商品的数量。1表示顾客至少买了1个单位的该商品,0表示顾客没有买该种商品。
1.3.4 实现简单的排序规则
正如之前所说,我们要找出“如果顾客购买了商品X,那么他们可能愿意购买商品Y”这样的规则6。简单粗暴的做法是,找出数据集中所有同时购买的两件商品。找出规则后,还需要判断其优劣,我们挑好的规则用。
6一条规则由前提条件和结论两部分组成。——译者注
规则的优劣有多种衡量方法,常用的是支持度(support)和置信度(confidence)。
支持度指数据集中规则应验的次数,统计起来很简单。有时候,还需要对支持度进行规范化,即再除以规则有效前提下的总数量。我们这里只是简单统计规则应验的次数。
支持度衡量的是给定规则应验的比例,而置信度衡量的则是规则准确率如何,即符合给定条件(即规则的“如果”语句所表示的前提条件)的所有规则里,跟当前规则结论一致的比例有多大。计算方法为首先统计当前规则的出现次数,再用它来除以条件(“如果”语句)相同的规则数量。
接下来,通过一个例子来说明支持度和置信度的计算方法,我们看一下怎么求“如果顾客购买了苹果,他们也会购买香蕉”这条规则的支持度和置信度。
如下面的代码所示,通过判断交易数据中sample[3]
的值,就能知道一个顾客是否买了苹果。这里,sample表示一条交易信息,也就是数据集里的一行数据。
同理,检测sample[4]
的值是否为1,就能确定顾客有没有买香蕉。现在可以计算题目给定规则在数据集中的出现次数,从而计算置信度和支持度。
我们需要统计数据集中所有规则的相关数据。首先分别为规则应验和规则无效这两种情况创建字典。字典的键是由条件和结论组成的元组,元组元素为特征在特征列表中的索引值,不要用实际特征名,比如“如果顾客购买了苹果,他们也会买香蕉”就用(3, 4)表示。如果某个个体的条件和结论均与给定规则相符,就表示给定规则对该个体适用,否则如果通过给定条件推出的结论与给定规则的结论不符,则表示给定规则对该个体无效。
为了计算所有规则的置信度和支持度,首先创建几个字典,用来存放计算结果。这里使用defaultdict
,好处是如果查找的键不存在,返回一个默认值。需要统计的量有规则应验、规则无效及条件相同的规则数量。
from collections import defaultdict
valid_rules = defaultdict(int)
invalid_rules = defaultdict(int)
num_occurances = defaultdict(int)
计算过程需要用到循环结构,依次对样本的每个个体及个体的每个特征值进行处理。第一个特征为规则的前提条件——顾客购买了某一种商品。
for sample in X:
for premise in range(5):
检测个体是否满足条件,如果不满足,继续检测下一个条件。
if sample[premise] == 0: continue
如果条件满足(即值为1),该条件的出现次数加1。在遍历过程中跳过条件和结论相同的情况,比如 “如果顾客买了苹果,他们也买苹果”,这样的规则没有多大用处7。
7n_samples, n_features = X.shape
,详见本书配套代码。——译者注
num_occurances[premise] += 1
for conclusion in range(n_features):
if premise == conclusion: continue
如果规则适用于个体,规则应验这种情况(valid_rules
字典中,键为由条件和结论组成的元组)增加一次,反之,违反规则情况(invalid_rules
字典中)就增加一次。
if sample[conclusion] == 1:
valid_rules[(premise, conclusion)] += 1
else:
invalid_rules[(premise, conclusion)] += 1
得到所有必要的统计量后,我们再来计算每条规则的支持度和置信度。如前所述,支持度就是规则应验的次数。
support = valid_rules
置信度的计算方法类似,遍历每条规则进行计算。
confidence = defaultdict(float)
for premise, conclusion in valid_rules.keys():
rule = (premise, conclusion)
confidence[rule] = valid_rules[rule] / num_occurances[premise]
我们得到了支持度字典和置信度字典,分别包含每条规则的支持度和置信度。我们再来声明一个函数,接收的参数有:分别作为前提条件和结论的特征索引值、支持度字典、置信度字典以及特征列表。输出每条规则及其支持度和置信度,对输出进行格式化,以方便查看。
def print_rule(premise, conclusion,
support, confidence, features):
之前建立的features列表派上用场了,每条规则的条件、结论就是用features列表中特征的索引来表示的。输出时,把索引替换成相应的特征,更容易读懂。
premise_name = features[premise]
conclusion_name = features[conclusion]
print("Rule: If a person buys {0} they will also buy
{1}".format(premise_name, conclusion_name))
接着输出规则的支持度和置信度。
print(" - Support: {0}".format(support[(premise,
conclusion)]))
print(" - Confidence: {0:.3f}".format(confidence[(premise,
conclusion)]))
写完后,自己测试一下代码是否可用——尝试更换条件和结论,看看输出结果如何。
1.3.5 排序找出最佳规则
得到所有规则的支持度和置信度后,为了找出最佳规则,还需要根据支持度和置信度对规则进行排序,我们分别看一下这两个标准。
要找出支持度最高的规则,首先对支持度字典进行排序。字典中的元素(一个键值对)默认为没有前后顺序;字典的items()
函数返回包含字典所有元素的列表。我们使用itemgetter()
类作为键,这样就可以对嵌套列表进行排序。itemgetter(1)
表示以字典各元素的值(这里为支持度)作为排序依据,reverse=True
表示降序排列。
from operator import itemgetter
sorted_support = sorted(support.items(), key=itemgetter(1), re
verse=True)
排序完成后,就可以输出支持度最高的前5条规则。
for index in range(5):
print("Rule #{0}".format(index + 1))
premise, conclusion = sorted_support[index][0]
print_rule(premise, conclusion, support, confidence, features)
结果如下所示:
同理,我们还可以输出置信度最高的规则。首先根据置信度进行排序。
sorted_confidence = sorted(confidence.items(), key=itemgetter(1),
reverse=True)
再次输出看看结果。注意输出方法相同,但是请留意下面第三行代码里sorted_confidence
的变化,不要继续使用sorted_support
。
for index in range(5):
print("Rule #{0}".format(index + 1))
premise, conclusion = sorted_confidence[index][0]
print_rule(premise, conclusion, support, confidence, features)
从排序结果来看,“顾客买苹果,也会买奶酪”和“顾客买奶酪,也会买香蕉”,这两条规则的支持度和置信度都很高。超市经理可以根据这些规则来调整商品摆放位置。例如,如果本周苹果促销,就在旁边摆上奶酪。但是香蕉和奶酪同时搞促销就没有多大意义了,因为我们发现购买奶酪的顾客中,接近66%的人即使不搞促销也会买香蕉——即使搞促销,也不会给销量带来多大提升。
从上面这个例子就能看出数据挖掘的洞察力有多强大。人们可以用数据挖掘技术探索数据集中各变量之间的关系,寻找新发现。接下来一节,我们看看数据挖掘的另一个功能:预测。
1.4 分类问题的简单示例
在上述亲和性分析例子中,我们寻找的是数据集中不同变量之间的相关性。而分类问题,只关注类别(也叫作目标)这个变量。上述例子中,假如我们关心的是怎样让顾客买更多的苹果,就可以把是否购买苹果作为类别,使用分类方法,只寻找促成顾客购买苹果的规则。
1.5 什么是分类
分类是数据挖掘领域最为常用的方法之一,不论是实际应用还是科研,都少不了它的身影。对于分类问题,我们通常能拿到表示实际对象或事件的数据集,我们知道数据集中每一条数据所属的类别,这些类别把一条条数据划分为不同的类。什么是类别?类别的值又是怎么回事?我们来看下面几个例子。
根据检测数据确定植物的种类。类别的值为“植物属于哪个种类?”。
判断图像中有没有狗。类别是“图像里有狗吗?”。
根据化验结果,判断病人有没有患上癌症。类别是“病人得癌症了吗?”。
上述三个问题中有两个是二值(是/否)问题,但正如第一个确定植物类别的问题,多个类别的情况也很常见。
分类应用的目标是,根据已知类别的数据集,经过训练得到一个分类模型,再用模型对类别未知的数据进行分类。例如,我们可以对收到的邮件进行分类,标注哪些是自己希望收到的,哪些是垃圾邮件,然后用这些数据训练分类模型,实现一个垃圾邮件过滤器,这样以后再收到邮件,就不用自己去确认它是不是垃圾邮件了,过滤器就能帮你搞定。
1.5.1 准备数据集
我们接下来将使用著名的Iris植物分类数据集。这个数据集共有150条植物数据,每条数据都给出了四个特征:sepal length、sepal width、petal length、petal width(分别表示萼片和花瓣的长与宽),单位均为cm。这是数据挖掘中的经典数据集之一(1936年就用到了数据挖掘领域!)。该数据集共有三种类别:Iris Setosa(山鸢尾)、Iris Versicolour(变色鸢尾)和Iris Virginica(维吉尼亚鸢尾)。我们这里的分类目的是根据植物的特征推测它的种类。
scikitlearn
库内置了该数据集,可直接导入。
from sklearn.datasets import load_iris
dataset = load_iris()
X = dataset.data
y = dataset.target
用print(dataset.DESCR)
命令查看数据集,大概了解一下,包括特征的说明。
数据集中各特征值为连续型,也就是有无数个可能的值。测量得到的数据就是这个样子,比如,测量结果可能是1、1.2或1.25,等等。连续值的另一个特点是,如果两个值相近,表示相似度很大。一种萼片长1.2cm的植物跟一种萼片宽1.25cm的植物很相像。
与此相反,类别的取值为离散型。虽然常用数字表示类别,但是类别值不能根据数值大小比较相似性。Iris数据集用不同的数字表示不同的类别,比如类别0、1、2分别表示Iris Setosa、Iris Versicolour、Iris Virginica。但是这不能说明前两种植物,要比第一种和第三种更相近——尽管单看表示类别的数字时确实如此。在这里,数字表示类别,只能用来判断两种植物是否属于同一种类别,而不能说明是否相似。
当然,还有其他类型的特征,后续章节会讲到其中几种。
数据集的特征为连续值,而我们即将使用的算法使用类别型特征值,因此我们需要把连续值转变为类别型,这个过程叫作离散化。
最简单的离散化算法,莫过于确定一个阈值,将低于该阈值的特征值置为0,高于阈值的置为1。我们把某项特征的阈值设定为该特征所有特征值的均值。每个特征的均值计算方法如下。
attribute_means = X.mean(axis=0)
我们得到了一个长度为4的数组,这正好是特征的数量。数组的第一项是第一个特征的均值,以此类推。接下来,用该方法将数据集打散,把连续的特征值转换为类别型。
X_d = np.array(X >= attribute_means, dtype='int')
后面的训练和测试,都将使用新得到的X_d
数据集(打散后的数组X),而不再使用原来的数据集(X)。
1.5.2 实现OneR算法
OneR算法的思路很简单,它根据已有数据中,具有相同特征值的个体最可能属于哪个类别进行分类。OneR是One Rule(一条规则)的简写,表示我们只选取四个特征中分类效果最好的一个用作分类依据。后续章节中的分类算法比起OneR要复杂很多,但这个看似不起眼的简单算法,在很多真实数据集上表现得也不凡。
算法首先遍历每个特征的每一个取值,对于每一个特征值,统计它在各个类别中的出现次数,找到它出现次数最多的类别,并统计它在其他类别中的出现次数。
举例来说,假如数据集的某一个特征可以取0或1两个值。数据集共有三个类别。特征值为0的情况下,A类有20个这样的个体,B类有60个,C类也有20个。那么特征值为0的个体最可能属于B类,当然还有40个个体确实是特征值为0,但是它们不属于B类。将特征值为0的个体分到B类的错误率就是40%,因为有40个这样的个体分别属于A类和C类。特征值为1时,计算方法类似,不再赘述;其他各特征值最可能属于的类别及错误率的计算方法也一样。
统计完所有的特征值及其在每个类别的出现次数后,我们再来计算每个特征的错误率。计算方法为把它的各个取值的错误率相加,选取错误率最低的特征作为唯一的分类准则(OneR),用于接下来的分类。
现在,我们就来实现该算法。首先创建一个函数,根据待预测数据的某项特征值预测类别,并给出错误率。在这之前需要导入前面用过的defaultdict
和itemgetter
模块。
from collections import defaultdict
from operator import itemgetter
下面创建函数声明,参数分别是数据集、类别数组、选好的特征索引值、特征值。
def train_feature_value(X, y_true, feature_index, value):
接下来遍历数据集中每一条数据(代表一个个体),统计具有给定特征值的个体在各个类别中的出现次数。
class_counts = defaultdict(int)
for sample, y in zip(X, y_true):
if sample[feature_index] == value:
class_counts[y] += 1
对class_counts
字典进行排序,找到最大值,就能找出具有给定特征值的个体在哪个类别中出现次数最多。
sorted_class_counts = sorted(class_counts.items(),
key=itemgetter(1), reverse=True)
most_frequent_class = sorted_class_counts[0][0]
接着计算该条规则的错误率。OneR算法会把具有该项特征值的个体统统分到上面找到的出现次数最多的类别中。错误率为具有该特征值的个体在其他类别(除出现次数最多的类别之外的)中的出现次数,它表示的是分类规则不适用的个体的数量。
incorrect_predictions = [class_count for class_value, class_count
in class_counts.items()
if class_value != most_frequent_class]
error = sum(incorrect_predictions)
最后返回使用给定特征值得到的待预测个体的类别和错误率。
return most_frequent_class, error
对于某项特征,遍历其每一个特征值,使用上述函数,就能得到预测结果和每个特征值所带来的错误率,然后把所有错误率累加起来,就能得到该特征的总错误率。我们来定义一个函数,实现这些操作。
函数声明如下,这次只用到三个参数,上面已经介绍过。
def train_on_feature(X, y_true, feature_index):
接下来找出给定特征共有几种不同的取值。下面这行代码X[:,feature_index]
以数组的形式返回由feature_index
所指的列。然后用set
函数将数组转化为集合,从而找出有几种不同的取值。
values = set(X[:,feature_index])
再创建字典predictors
,用作预测器。字典的键为特征值,值为类别。比如键为1.5、值为2,表示特征值为1.5的个体属于类别2。创建errors
列表,存储每个特征值的错误率。
predictors = {}
errors = []
函数的主干部分遍历选定特征的每个不同的特征值,用前面定义的train_feature_value()
函数找出每个特征值最可能的类别,计算错误率,并将其分别保存到预测器predictors和errors中。
for current_value in values:
most_frequent_class, error = train_feature_value(X,
y_true, feature_index, current_value)
predictors[current_value] = most_frequent_class
errors.append(error)
最后,计算该规则的总错误率,返回预测器及总错误率。
total_error = sum(errors)
return predictors, total_error
1.5.3 测试算法
上一节中亲和性分析算法的目标是从数据集中发现用以指导实践的规则。而分类问题有所不同,我们想建立一个能够根据已有知识对没有见过的个体进行分类的模型。
我们因此把机器学习流程分为两步:训练和测试。在训练阶段,我们从数据集中取一部分数据,创建模型。在测试阶段,我们测试模型在数据集上的分类效果。考虑到模型的目标是对新个体进行分类,因此不能用测试数据训练模型,因为这样做容易导致过拟合问题。
过拟合指的是模型在训练集上表现很好,但对于没有见过的数据表现很差。解决方法很简单:千万不要用训练数据测试算法。详细的处理方法很复杂,后续章节会有所涉及;我们这里简单化处理,把数据集分为两个小部分,分别用于训练和测试。具体流程接下来会介绍。
scikitlearn
库提供了一个将数据集切分为训练集和测试集的函数。
from sklearn.cross_validation import train_test_split
该函数根据设定的比例(默认把数据集的25%作为测试集)将数据集随机切分为两部分,以确保测试结果的可信度。
X_train, X_test, y_train, y_test = train_test_split(X_d, y, random_state=14)
这样我们就得到了两个数据集:训练集X_train
和测试集X_test
。y_train
和y_test
分别为以上两个数据集的类别信息。
切分函数的第三个参数random_state
用来指定切分的随机状态。每次切分,使用相同的随机状态,切分结果相同。虽然看起来是随机的,但是它所使用的算法是确定的,输出结果也是一致的。在书中所有用到random_state
的地方,我建议你跟我使用同一个值,这样你得到的结果就应该跟我的相同,这样便于你验证结果。把random_state
的值设置为none
,每次切分结果将是真正随机的。
接下来,计算所有特征值的目标类别(预测器)。记得只使用训练集。遍历数据集中的每个特征,使用我们先前定义的函数train_on_feature()
训练预测器,计算错误率。
all_predictors = {}
errors = {}
for feature_index in range(X_train.shape[1]):
predictors, total_error = train_on_feature(X_train, y_train,
feature_index)
all_predictors[feature_index] = predictors
errors[feature_index] = total_error
然后找出错误率最低的特征,作为分类的唯一规则。
best_variable, best_error = sorted(errors.items(), key=itemgetter(1))
[0]
对预测器进行排序,找到最佳特征值,创建model
模型。
model = {'feature': best_variable,
'predictor': all_predictors[best_variable]}
model
模型是一个字典结构,包含两个元素:用于分类的特征和预测器。有了模型后,就可以根据特征值对没有见过的数据进行分类。示例如下:
variable = model['variable']
predictor = model['predictor']
prediction = predictor[int(sample[variable])]
我们经常需要一次对多条数据进行预测,为此用上面的代码实现了下面这个函数,通过遍历数据集中的每条数据来完成预测。
def predict(X_test, model):
variable = model['variable']
predictor = model['predictor']
y_predicted = np.array([predictor[int(sample[variable])] for
sample in X_test])
return y_predicted
我们用上面这个函数预测测试集中每条数据的类别。
y_predicted = predict(X_test, model)
比较预测结果和实际类别,就能得到正确率是多少。
accuracy = np.mean(y_predicted == y_test) * 100
print("The test accuracy is {:.1f}%".format(accuracy))
输出结果为65.8%,对于只使用一条规则来说,这就很不错了!
1.6 小结
本章介绍了如何用Python进行数据挖掘。如果你能运行这一部分的代码8(见代码包第1章的文件夹),说明开发环境已搭建好,后续章节的大部分代码都能运行了。当然有些Python库还没装,随用随装就好。
8如果.ipynb文件在Notebook中打开时报错,请用JSON检查工具查找有无不合法的JSON字符,自行调整一下。——译者注
我们用IPython Notebook运行了代码,好处是能及时看到一小块代码的输出。它功能强大,后面会继续使用。
我们举了一个简单的亲和性分析的例子,用它找出顾客经常一起购买的商品。这种探索性的分析方法用处很大,能帮助人们发现商业流程、某个环境或场景中的潜在规律。亲和性分析可用在商业、医疗、人工智能等领域,说不定能这些领域带来突破。
本章还通过OneR算法介绍了分类的应用。该算法寻找最佳的特征值用于分类,该特征值在训练集中哪个类别中出现的次数最多,待预测数据就属于哪个类别。
后续章节会扩展分类和亲和性分析的概念,同时还会介绍scikitlearn
库以及它实现的一些数据挖掘算法。