【机器学习算法系列】如何用Apriori寻找到繁杂数据之间的隐藏关系
大型超市有海量交易数据,我们可以通过聚类算法寻找购买相似物品的人群,从而为特定人群提供更具个性化的服务。但是对于超市来讲,更有价值的是如何找出商品的隐藏关联,从而打包促销,以增加营业收入。其中最经典的案例就是关于尿不湿和啤酒的故事。怎样在繁杂的数据中寻找到数据之间的隐藏关系?当然可以使用穷举法,但代价高昂,所以需要使用更加智能的方法在合理时间内找到答案。Apriori就是其中的一种关联分析算法。
基本概念
关联分析是一种在大规模数据集中寻找有趣关系的非监督学习算法。这些关系可以有两种形式:频繁项集或者关联规则。频繁项集(frequent item sets)是经常出现在一块的物品的集合,关联规则(association rules)暗示两种物品之间可能存在很强的关系。
下图是一个乒乓球店的交易记录,〇表示顾客购买了商品。其中{底板,胶皮,浇水}就是一个频繁项集;从中可以找到底板->胶皮这样的关联规则:
支持度
怎样有效定义频繁和关联?其中最重要的两个概念是支持度和置信度。
支持度(support)从字面上理解就是支持的程度,一个项集的支持度(support)被定义为数据集中包含该项集的记录所占的比例。上图中{底板}的支持度=(5/6) * 100%。
这个概念其实经常在现实生活中出现,翻译成支持率似乎更好理解,典型的例子就是投票,比如英国脱欧的支持率为51.89%。
用数学去解释就是,设W 中有s%的事务同时支持物品集A和B,s%称为{A,B}的支持度,即:
support({A,B}) = num(A∪B) / W = P(A∩B)
num(A∪B)表示含有物品集{A,B}的事务集的个数,不是数学中的并集。
置信度
置信度(confidence)揭示了A出现时B是否一定出现,如果出现,则出现的概率是多大。如果A->B的置信度是100%,则说明A出现时B一定会出现(返回来不一定)。上图中底板共出现5次,其中4次同时购买了胶皮,底板->胶皮的置信度是80%。
用公式表示是,物品A->B的置信度=物品{A,B}的支持度 / 物品{A}的支持度:
Confidence(A->B) = support({A,B}) / support({A}) = P(B|A)
Apriori原理
本节摘自《机器学习实战》
假设我们在经营一家商品种类并不多的杂货店,我们对那些经常在一起被购买的商品非常感兴趣。我们只有4种商品:商品0,商品1,商品2和商品3。那么所有可能被一起购买的商品组合都有哪些?这些商品组合可能只有一种商品,比如商品0,也可能包括两种、三种或者所有四种商品。我们并不关心某人买了两件商品0以及四件商品2的情况,我们只关心他购买了一种或多种商品。
下图显示了物品之间所有可能的组合。为了让该图更容易懂,图中使用物品的编号0来取代物品0本身。另外,图中从上往下的第一个集合是Ф,表示空集或不包含任何物品的集合。物品集合之间的连线表明两个或者更多集合可以组合形成一个更大的集合。
前面说过,我们的目标是找到经常在一起购买的物品集合。我们使用集合的支持度来度量其出现的频率。一个集合的支持度是指有多少比例的交易记录包含该集合。如何对一个给定的集合,比如{0,3},来计算其支持度?我们遍历毎条记录并检查该记录包含0和3,如果记录确实同时包含这两项,那么就增加总计数值。在扫描完所有数据之后,使用统计得到的总数除以总的交易记录数,就可以得到支持度。上述过程和结果只是针对单个集合{0,3}。要获得每种可能集合的支持度就需要多次重复上述过程。我们可以数一下上图中的集合数目,会发现即使对于仅有4种物品的集合,也需要遍历数据15次。而随着物品数目的增加遍历次数会急剧增长。对于包含— 物品的数据集共有2N-1种项集组合。事实上,出售10000或更多种物品的商店并不少见。即使只出售100种商品的商店也会有1.26×1030种可能的项集组合。对于现代的计算机而言,需要很长的时间才能完成运算。
为了降低所需的计算时间,研究人员发现一种所谓的Apriori原理。Apriori原理可以帮我们减少可能感兴趣的项集。Apriori原理是说如果某个项集是频繁的,那么它的所有子集也是频繁的。上图给出的例子,这意味着如果{0,1}是频繁的,那么{0}、{1}也一定是频繁的。这个原理直观上并没有什么帮助,但是如果反过来看就有用了,也就是说如果一个项集是非频繁集,那么它的所有超集也是非频繁的,如下所示:
上图中,已知阴影项集{2,3}是非频繁的。利用这个知识,我们就知道项集{0,2,3} ,{1,2,3}以及{0,1,2,3}也是非频繁的。这也就是说,一旦计算出了{2,3}的支持度,知道它是非频繁的之后,就不需要再计算{0,2,3}、{1,2,3}和{0,1,2,3}的支持度,因为我们知道这些集合不会满足我们的要求。使用该原理就可以避免项集数目的指数增长,从而在合理时间内计算出频繁项集。
Apriori算法过程
关联分析的目标包括两项:发现频繁项集和发现关联规则。首先需要找到频繁项集,然后才能获得关联规则。
Apriori算法过程
发现频繁项集的过程如上图所示:
- 由数据集生成候选项集C1(1表示每个候选项仅有一个数据项);再由C1通过支持度过滤,生成频繁项集L1(1表示每个频繁项仅有一个数据项)。
- 将L1的数据项两两拼接成C2。
- 从候选项集C2开始,通过支持度过滤生成L2。L2根据Apriori原理拼接成候选项集C3;C3通过支持度过滤生成L3……直到Lk中仅有一个或没有数据项为止。
下面是一个超市的交易记录:
Apriori算法发现频繁项集的过程如下:
具体代码:
def loadDataSet():
return [[1,2,5],[2,4],[2,3],[1,2,4],[1,3],[2,3],[1,3],[1,2,3,5],[1,2,3]]
#1.构建候选1项集C1
def createC1(dataSet):
C1 = []
for transaction in dataSet:
for item in transaction:
if not [item] in C1:
C1.append([item])
C1.sort()
return list(map(frozenset, C1))
#将候选集Ck转换为频繁项集Lk
#D:原始数据集
#Cn: 候选集项Ck
#minSupport:支持度的最小值
def scanD(D, Ck, minSupport):
#候选集计数
ssCnt = {}
for tid in D:
for can in Ck:
if can.issubset(tid):
if can not in ssCnt.keys(): ssCnt[can] = 1
else: ssCnt[can] += 1
numItems = float(len(D))
Lk= [] # 候选集项Cn生成的频繁项集Lk
supportData = {} #候选集项Cn的支持度字典
#计算候选项集的支持度, supportData key:候选项, value:支持度
for key in ssCnt:
support = ssCnt[key] / numItems
if support >= minSupport:
Lk.append(key)
supportData[key] = support
return Lk, supportData
#连接操作,将频繁Lk-1项集通过拼接转换为候选k项集
def aprioriGen(Lk_1, k):
Ck = []
lenLk = len(Lk_1)
for i in range(lenLk):
L1 = list(Lk_1[i])[:k - 2]
L1.sort()
for j in range(i + 1, lenLk):
#前k-2个项相同时,将两个集合合并
L2 = list(Lk_1[j])[:k - 2]
L2.sort()
if L1 == L2:
Ck.append(Lk_1[i] | Lk_1[j])
return Ck
def apriori(dataSet, minSupport = 0.5):
C1 = createC1(dataSet)
L1, supportData = scanD(dataSet, C1, minSupport)
L = [L1]
k = 2
while (len(L[k-2]) > 0):
Lk_1 = L[k-2]
Ck = aprioriGen(Lk_1, k)
print("ck:",Ck)
Lk, supK = scanD(dataSet, Ck, minSupport)
supportData.update(supK)
print("lk:", Lk)
L.append(Lk)
k += 1
return L, supportData
dataset = loadDataSet()
L, supportData = apriori(dataset, minSupport=0.2)
控制台信息:
代码中的scanD方法可作一下修改:
def scanD(D, Ck, minSupport):
#候选集计数
ssCnt = {}
#数据集过滤
D2 = [item for item in D if len(item) >= len(Ck[0])]
for tid in D2:
for can in Ck:
if can.issubset(tid):
if can not in ssCnt.keys(): ssCnt[can] = 1
else: ssCnt[can] += 1
……
return Lk, supportData
需要注意的是,在上述代码的aprioriGen方法中,假定购买商品是有顺序的,可以通过频繁2项集{P1,P2},{P1,P3}推导出频繁项{P1,P2,P3},但是不能通过频繁2项集{P3,P4},{P1,P3}推导出频繁项{P1,P3,P4}。如果去掉假设,则需要修改aprioriGen的代码:
#将频繁Lk-1项集转换为候选k项集
def aprioriGen(Lk_1, k):
Ck = []
lenLk = len(Lk_1)
for i in range(lenLk):
L1 = Lk_1[i]
for j in range(i + 1, lenLk):
L2 = Lk_1[j]
if len(L1 & L2) == k - 2:
L1_2 = L1 | L2
if L1_2 not in Ck:
Ck.append(L1 | L2)
return Ck
发现关联规则
下篇继续。
·END·
- 一文读懂Hadoop、HBase、Hive、Spark分布式系统架构
- 《Spring敲门砖之基础教程第一季》 第二章(1) Spring框架之IOC首例-HelloWorld
- Java9中的GC调优基础
- javascript深入理解js闭包
- https连接的前几毫秒发生了什么
- android自定义view实现progressbar的效果
- 还是不靠谱!多维LSTM网络预测比特币价格【机器学习应用区块链系列二】
- picasso图片缓存框架
- Git基础和规范-协同开发
- JavaScript 知识点梳理 | 从基础语法到高级用法
- Java正则速成秘籍(一)之招式篇
- Rafy 框架 - 使用 SqlTree 查询
- 详细讲解Quartz.NET
- android AlarmManager讲解
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 宜信OCR技术探索之版面分析业务实践|技术沙龙直播速记
- Nginx的rewrite(地址重定向)剖析
- Prometheus监控神器-Kubernetes篇(二)
- C/C++实现你的浪漫表白:浪漫流星雨表白程序
- 烂了大街的 Spring 循环依赖问题,你觉得自己会了吗
- 软件测试丨自动化测试之读取配置文件
- 一文搞懂高频面试题之限流算法,从算法原理到实现,再到对比分析
- TRTC Android端开发接入学习之实现语音通话(六)
- Shell脚本加密经验分享
- python提示每天债券打新
- Python3下基于Scapy库完成网卡抓包解析
- pandas:解决groupby().apply()方法打印两次
- pandas:字段值插入数据表第一行的解决办法
- Recommending items to more than a billion people(面向十亿级用户的推荐系统)
- pandas:对字符串类型做差分比较