机器学习经典算法之Apriori

1、 搞懂关联规则中的几个概念html

关联规则这个概念,最先是由 Agrawal 等人在 1993 年提出的。在 1994 年 Agrawal 等人又提出了基于关联规则的 Apriori 算法,至今 Apriori 还是关联规则挖掘的重要算法。python

/*请尊重做者劳动成果,转载请标明原文连接:*/web

/* https://www.cnblogs.com/jpcflyer/p/11146587.html * /算法

关联规则挖掘可让咱们从数据集中发现项与项(item 与 item)之间的关系,它在咱们的生活中有不少应用场景,“购物篮分析”就是一个常见的场景,这个场景能够从消费者交易记录中发掘商品与商品之间的关联关系,进而经过商品捆绑销售或者相关推荐的方式带来更多的销售量。因此说,关联规则挖掘是个很是有用的技术。数组

我举一个超市购物的例子,下面是几名客户购买的商品列表:浏览器

 

什么是支持度呢?app

支持度是个百分比,它指的是某个商品组合出现的次数与总次数之间的比例。支持度越高,表明这个组合出现的频率越大。ide

在这个例子中,咱们能看到“牛奶”出现了 4 次,那么这 5 笔订单中“牛奶”的支持度就是 4/5=0.8。函数

一样“牛奶 + 面包”出现了 3 次,那么这 5 笔订单中“牛奶 + 面包”的支持度就是 3/5=0.6。工具

 

什么是置信度呢?

它指的就是当你购买了商品 A,会有多大的几率购买商品 B,在上面这个例子中:

置信度(牛奶→啤酒)=2/4=0.5,表明若是你购买了牛奶,有多大的几率会购买啤酒?

置信度(啤酒→牛奶)=2/3=0.67,表明若是你购买了啤酒,有多大的几率会购买牛奶?

咱们能看到,在 4 次购买了牛奶的状况下,有 2 次购买了啤酒,因此置信度 (牛奶→啤酒)=0.5,而在 3 次购买啤酒的状况下,有 2 次购买了牛奶,因此置信度(啤酒→牛奶)=0.67。

因此说置信度是个条件概念,就是说在 A 发生的状况下,B 发生的几率是多少。

 

什么是提高度呢?

咱们在作商品推荐的时候,重点考虑的是提高度,由于提高度表明的是“商品 A 的出现,对商品 B 的出现几率提高的”程度。

仍是看上面的例子,若是咱们单纯看置信度 (可乐→尿布)=1,也就是说可乐出现的时候,用户都会购买尿布,那么当用户购买可乐的时候,咱们就须要推荐尿布么?

实际上,就算用户不购买可乐,也会直接购买尿布的,因此用户是否购买可乐,对尿布的提高做用并不大。咱们能够用下面的公式来计算商品 A 对商品 B 的提高度:

提高度 (A→B)= 置信度 (A→B)/ 支持度 (B)

这个公式是用来衡量 A 出现的状况下,是否会对 B 出现的几率有所提高。

因此提高度有三种可能:

提高度 (A→B)>1:表明有提高;

提高度 (A→B)=1:表明有没有提高,也没有降低;

提高度 (A→B)<1:表明有降低。

 

2、 Apriori 的工做原理

明白了关联规则中支持度、置信度和提高度这几个重要概念,咱们来看下 Apriori 算法是如何工做的。

首先咱们把上面案例中的商品用 ID 来表明,牛奶、面包、尿布、可乐、啤酒、鸡蛋的商品 ID 分别设置为 1-6,上面的数据表能够变为:

Apriori 算法其实就是查找频繁项集 (frequent itemset) 的过程,因此首先咱们须要定义什么是频繁项集。

频繁项集就是支持度大于等于最小支持度 (Min Support) 阈值的项集,因此小于最小值支持度的项目就是非频繁项集,而大于等于最小支持度的项集就是频繁项集。

项集这个概念,英文叫作 itemset,它能够是单个的商品,也能够是商品的组合。咱们再来看下这个例子,假设我随机指定最小支持度是 50%,也就是 0.5。

 

咱们来看下 Apriori 算法是如何运算的。

首先,咱们先计算单个商品的支持度,也就是获得 K=1 项的支持度:

由于最小支持度是 0.5,因此你能看到商品 四、6 是不符合最小支持度的,不属于频繁项集,因而通过筛选商品的频繁项集就变成:

在这个基础上,咱们将商品两两组合,获得 k=2 项的支持度:

咱们再筛掉小于最小值支持度的商品组合,能够获得:

咱们再将商品进行 K=3 项的商品组合,能够获得:

再筛掉小于最小值支持度的商品组合,能够获得:

到这里,你已经和我模拟了一遍整个 Apriori 算法的流程,下面我来给你总结下 Apriori 算法的递归流程:

K=1,计算 K 项集的支持度;

筛选掉小于最小支持度的项集;

若是项集为空,则对应 K-1 项集的结果为最终结果。

不然 K=K+1,重复 1-3 步。

 

3、 Apriori 的改进算法:FP-Growth 算法

能看到 Apriori 在计算的过程当中有如下几个缺点:

可能产生大量的候选集。由于采用排列组合的方式,把可能的项集都组合出来了;

每次计算都须要从新扫描数据集,来计算每一个项集的支持度。

 

因此 Apriori 算法会浪费不少计算空间和计算时间,为此人们提出了 FP-Growth 算法,它的特色是:

建立了一棵 FP 树来存储频繁项集。在建立前对不知足最小支持度的项进行删除,减小了存储空间。我稍后会讲解如何构造一棵 FP 树;

整个生成过程只遍历数据集 2 次,大大减小了计算量。

因此在实际工做中,咱们经常使用 FP-Growth 来作频繁项集的挖掘,下面我给你简述下 FP-Growth 的原理。

 

1. 建立项头表(item header table)

建立项头表的做用是为 FP 构建及频繁项集挖掘提供索引。

这一步的流程是先扫描一遍数据集,对于知足最小支持度的单个项(K=1 项集)按照支持度从高到低进行排序,这个过程当中删除了不知足最小支持度的项。

项头表包括了项目、支持度,以及该项在 FP 树中的链表。初始的时候链表为空。

 

2. 构造 FP 树

FP 树的根节点记为 NULL 节点。

整个流程是须要再次扫描数据集,对于每一条数据,按照支持度从高到低的顺序进行建立节点(也就是第一步中项头表中的排序结果),节点若是存在就将计数 count+1,若是不存在就进行建立。同时在建立的过程当中,须要更新项头表的链表。

 

3. 经过 FP 树挖掘频繁项集

到这里,咱们就获得了一个存储频繁项集的 FP 树,以及一个项头表。咱们能够经过项头表来挖掘出每一个频繁项集。

具体的操做会用到一个概念,叫“条件模式基”,它指的是以要挖掘的节点为叶子节点,自底向上求出 FP 子树,而后将 FP 子树的祖先节点设置为叶子节点之和。

我以“啤酒”的节点为例,从 FP 树中能够获得一棵 FP 子树,将祖先节点的支持度记为叶子节点之和,获得:

你能看出来,相比于原来的 FP 树,尿布和牛奶的频繁项集数减小了。这是由于咱们求得的是以“啤酒”为节点的 FP 子树,也就是说,在频繁项集中必定要含有“啤酒”这个项。你能够再看下原始的数据,其中订单 1{牛奶、面包、尿布}和订单 5{牛奶、面包、尿布、可乐}并不存在“啤酒”这个项,因此针对订单 1,尿布→牛奶→面包这个项集就会从 FP 树中去掉,针对订单 5 也包括了尿布→牛奶→面包这个项集也会从 FP 树中去掉,因此你能看到以“啤酒”为节点的 FP 子树,尿布、牛奶、面包项集上的计数比原来少了 2。

条件模式基不包括“啤酒”节点,并且祖先节点若是小于最小支持度就会被剪枝,因此“啤酒”的条件模式基为空。

同理,咱们能够求得“面包”的条件模式基为:

因此能够求得面包的频繁项集为{尿布,面包},{尿布,牛奶,面包}。一样,咱们还能够求得牛奶,尿布的频繁项集,这里就再也不计算展现。

 

4、 如何使用 Apriori 工具包

Apriori 虽然是十大算法之一,不过在 sklearn 工具包中并无它,也没有 FP-Growth 算法。这里教你个方法,来选择 Python 中可使用的工具包,你能够经过 https://pypi.org/ 搜索工具包。

这个网站提供的工具包都是 Python 语言的,你能找到 8 个 Python 语言的 Apriori 工具包,具体选择哪一个呢?建议你使用第二个工具包,即 efficient-apriori。后面我会讲到为何推荐这个工具包。

首先你须要经过 pip install efficient-apriori 安装这个工具包。

而后看下如何使用它,核心的代码就是这一行:

1 itemsets, rules = apriori(data, min_support,  min_confidence)

其中 data 是咱们要提供的数据集,它是一个 list 数组类型。min_support 参数为最小支持度,在 efficient-apriori 工具包中用 0 到 1 的数值表明百分比,好比 0.5 表明最小支持度为 50%。min_confidence 是最小置信度,数值也表明百分比,好比 1 表明 100%。

 

接下来咱们用这个工具包,跑一下前面讲到的超市购物的例子。下面是客户购买的商品列表:

具体实现的代码以下:

 1 from efficient_apriori import apriori
 2 # 设置数据集
 3 data = [('牛奶','面包','尿布'),
 4            ('可乐','面包', '尿布', '啤酒'),
 5            ('牛奶','尿布', '啤酒', '鸡蛋'),
 6            ('面包', '牛奶', '尿布', '啤酒'),
 7            ('面包', '牛奶', '尿布', '可乐')]
 8 # 挖掘频繁项集和频繁规则
 9 itemsets, rules = apriori(data, min_support=0.5,  min_confidence=1)
10 print(itemsets)
11 print(rules)

运行结果:

1 {1: {('啤酒',): 3, ('尿布',): 5, ('牛奶',): 4, ('面包',): 4}, 2: {('啤酒', '尿布'): 3, ('尿布', '牛奶'): 4, ('尿布', '面包'): 4, ('牛奶', '面包'): 3}, 3: {('尿布', '牛奶', '面包'): 3}}
2 [{啤酒} -> {尿布}, {牛奶} -> {尿布}, {面包} -> {尿布}, {牛奶, 面包} -> {尿布}]

你能从代码中看出来,data 是个 List 数组类型,其中每一个值均可以是一个集合。实际上你也能够把 data 数组中的每一个值设置为 List 数组类型,好比:

1 data = [['牛奶','面包','尿布'],
2            ['可乐','面包', '尿布', '啤酒'],
3            ['牛奶','尿布', '啤酒', '鸡蛋'],
4            ['面包', '牛奶', '尿布', '啤酒'],
5            ['面包', '牛奶', '尿布', '可乐']]

二者的运行结果是同样的,efficient-apriori 工具包把每一条数据集里的项式都放到了一个集合中进行运算,并无考虑它们之间的前后顺序。由于实际状况下,同一个购物篮中的物品也不须要考虑购买的前后顺序。

而其余的 Apriori 算法可能会由于考虑了前后顺序,出现计算频繁项集结果不对的状况。因此这里采用的是 efficient-apriori 这个工具包。

 

5、 挖掘导演是如何选择演员的

在实际工做中,数据集是须要本身来准备的,好比咱们要挖掘导演是如何选择演员的数据状况,可是并无公开的数据集能够直接使用。所以咱们须要使用以前讲到的 Python 爬虫进行数据采集。

不一样导演选择演员的规则是不一样的,所以咱们须要先指定导演。数据源咱们选用豆瓣电影。

先来梳理下采集的工做流程。

 

首先咱们先在 https://movie.douban.com 搜索框中输入导演姓名,好比“宁浩”。

页面会呈现出来导演以前的全部电影,而后对页面进行观察,你能观察到如下几个现象:

页面默认是 15 条数据反馈,第一页会返回 16 条。由于第一条数据实际上这个导演的概览,你能够理解为是一条广告的插入,下面才是真正的返回结果。

每条数据的最后一行是电影的演出人员的信息,第一我的员是导演,其他为演员姓名。姓名之间用“/”分割。

有了这些观察以后,咱们就能够编写抓取程序了。在代码讲解中你能看出这两点观察的做用。抓取程序的目的是为了生成宁浩导演(你也能够抓取其余导演)的数据集,结果会保存在 csv 文件中。完整的抓取代码以下:

 1 # -*- coding: utf-8 -*-
 2 # 下载某个导演的电影数据集
 3 from efficient_apriori import apriori
 4 from lxml import etree
 5 import time
 6 from selenium import webdriver
 7 import csv
 8 driver = webdriver.Chrome()
 9 # 设置想要下载的导演 数据集
10 director = u'宁浩'
11 # 写 CSV 文件
12 file_name = './' + director + '.csv'
13 base_url = 'https://movie.douban.com/subject_search?search_text='+director+'&cat=1002&start='
14 out = open(file_name,'w', newline='', encoding='utf-8-sig')
15 csv_write = csv.writer(out, dialect='excel')
16 flags=[]
17 # 下载指定页面的数据
18 def download(request_url):
19     driver.get(request_url)
20     time.sleep(1)
21     html = driver.find_element_by_xpath("//*").get_attribute("outerHTML")
22     html = etree.HTML(html)
23     # 设置电影名称,导演演员 的 XPATH
24     movie_lists = html.xpath("/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='title']/a[@class='title-text']")
25     name_lists = html.xpath("/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='meta abstract_2']")
26     # 获取返回的数据个数
27     num = len(movie_lists)
28     if num > 15: # 第一页会有 16 条数据
29         # 默认第一个不是,因此须要去掉
30         movie_lists = movie_lists[1:]
31         name_lists = name_lists[1:]
32     for (movie, name_list) in zip(movie_lists, name_lists):
33         # 会存在数据为空的状况
34         if name_list.text is None:
35             continue
36         # 显示下演员名称
37         print(name_list.text)
38         names = name_list.text.split('/')
39         # 判断导演是否为指定的 director
40         if names[0].strip() == director and movie.text not in flags:
41             # 将第一个字段设置为电影名称
42             names[0] = movie.text
43             flags.append(movie.text)
44             csv_write.writerow(names)
45     print('OK') # 表明这页数据下载成功
46     print(num)
47     if num >= 14: # 有可能一页会有 14 个电影
48         # 继续下一页
49         return True
50     else:
51         # 没有下一页
52         return False
53 
54 # 开始的 ID 为 0,每页增长 15
55 start = 0
56 while start<10000: # 最多抽取 1 万部电影
57     request_url = base_url + str(start)
58     # 下载数据,并返回是否有下一页
59     flag = download(request_url)
60     if flag:
61         start = start + 15
62     else:
63         break
64 out.close()
65 print('finished')

代码中涉及到了几个模块,我简单讲解下这几个模块。

在引用包这一段,咱们使用 csv 工具包读写 CSV 文件,用 efficient_apriori 完成 Apriori 算法,用 lxml 进行 XPath 解析,time 工具包可让咱们在模拟后有个适当停留,代码中我设置为 1 秒钟,等 HTML 数据彻底返回后再进行 HTML 内容的获取。使用 selenium 的 webdriver 来模拟浏览器的行为。

在读写文件这一块,咱们须要事先告诉 python 的 open 函数,文件的编码是 utf-8-sig(对应代码:encoding=‘utf-8-sig’),这是由于咱们会用到中文,为了不编码混乱。

编写 download 函数,参数传入咱们要采集的页面地址(request_url)。针对返回的 HTML,咱们须要用到以前讲到的 Chrome 浏览器的 XPath Helper 工具,来获取电影名称以及演出人员的 XPath。我用页面返回的数据个数来判断当前所处的页面序号。若是数据个数 >15,也就是第一页,第一页的第一条数据是广告,咱们须要忽略。若是数据个数 =15,表明是中间页,须要点击“下一页”,也就是翻页。若是数据个数 <15,表明最后一页,没有下一页。

在程序主体部分,咱们设置 start 表明抓取的 ID,从 0 开始最多抓取 1 万部电影的数据(一个导演不会超过 1 万部电影),每次翻页 start 自动增长 15,直到 flag=False 为止,也就是不存在下一页的状况。

你能够模拟下抓取的流程,得到指定导演的数据,好比我上面抓取的宁浩的数据。这里须要注意的是,豆瓣的电影数据多是不全的,但基本上够咱们用。

有了数据以后,咱们就能够用 Apriori 算法来挖掘频繁项集和关联规则,代码以下:

 1 # -*- coding: utf-8 -*-
 2 from efficient_apriori import apriori
 3 import csv
 4 director = u'宁浩'
 5 file_name = './'+director+'.csv'
 6 lists = csv.reader(open(file_name, 'r', encoding='utf-8-sig'))
 7 # 数据加载
 8 data = []
 9 for names in lists:
10      name_new = []
11      for name in names:
12            # 去掉演员数据中的空格
13            name_new.append(name.strip())
14      data.append(name_new[1:])
15 # 挖掘频繁项集和关联规则
16 itemsets, rules = apriori(data, min_support=0.5,  min_confidence=1)
17 print(itemsets)
18 print(rules)

代码中使用的 apriori 方法和开头中用 Apriori 获取购物篮规律的方法相似,好比代码中都设定了最小支持度和最小置信系数,这样咱们能够找到支持度大于 50%,置信系数为 1 的频繁项集和关联规则。

这是最后的运行结果:

1 {1: {('徐峥',): 5, ('黄渤',): 6}, 2: {('徐峥', '黄渤'): 5}}
2 [{徐峥} -> {黄渤}]

 你能看出来,宁浩导演喜欢用徐峥和黄渤,而且有徐峥的状况下,通常都会用黄渤。你也能够用上面的代码来挖掘下其余导演选择演员的规律。

相关文章
相关标签/搜索