您的位置:首页 > 其它

使用Apriori进行关联分析

2018-01-19 23:55 323 查看
使用Apriori进行关联分析
 
最典型的关联分析的案例就是沃尔玛的“啤酒与尿布”的故事,这个看起来完全不搭嘎的商品在经过对过去一年的数据分析后发现周四晚上奶爸们会来超市采购尿布同时顺手买走自己喜欢的啤酒,于是超市保证当天的备货充足并显眼的摆在一起,就可以创造销量奇迹。
大规模数据集中寻找物品间的隐含关系被称作关联分析(association analysis)或者关联规则学习(association
rule learning)。
 
在开始算法之前,需要先理解几个概念和量化规则。
 
项集:元素的组合,例如上面例子中{啤酒}、{尿布}、{啤酒+尿布}这就是3个项集。单元素的项集对数据分析意义不大,我们真正关心的是多元素的项集。
 
频繁项集:在记录中某项集超过一定概率出现,被称为频繁项集,这个概率是我们人为设定的。同上,我们关心的是多元素的项集,它们一起抱团出现才是我们该兴趣的。
频繁项集的意义在于,我们不可能对所有元素的各种组合都去进行数据分析,因为那个计算量是指数级增长的,我们只选取大概率的我们关系的项量做分析,于是我们要找出频繁项集来减少数据分析中的计算消耗。
 
支持度:数据集中包含该项集的记录所占的比例,我们给数据集输入一个“最小支持度”便可以求得频繁项集。
 
可信度:用X1àX2来标识,计算公式是
X1àX2
= 支持度{X1+X2}|支持度{X1}
X1àX2并不代表X2àX1,还是上面的案例买尿布的男士很可能会去买啤酒,但是买啤酒的男士还会买尿布的概率就微乎其微了。所以可信度是单向的。
 
 
概念掌握了,再回头看下Apriori原理,前面也说过随着元素的增加项量是指数级增长的,我们必须除了频繁项集之外的原理来支持我们削减数据才能满足更多元素的数据分析。
 
我开了一个烧烤摊,我只卖4种烤串“羊肉串”、“牛肉串”、“骨肉相连”、“烤土豆”,对它们编号后是0、1、2、3,那么一个顾客购买的订单就有如下组合:



已经很恐怖了,多卖几种组合会更复杂。
 
Apriori原理:
如果某个项集是频繁的,那么它的所有子集也是频繁的。
这句很好理解吧,{X1+X2}是频繁的,那么{X1}和{X2}都是频繁的,听起来是个废话,对我们没什么鸟用,但是这句话反过来理解就精髓了:
如果某个项集不是频繁的,那么带有该项集的所有超集都不是频繁的。



如上,在低元素层判断出某个非频繁项集后,跟它相关的高元素层我们就可以直接忽略了。可能你觉得虽然可以帮我们减少计算量,但是并没有很逆天的表现嘛。好吧,上图假如只有0是频繁的,我们只需要计算完第一元素层,就可以结束了!
 
纸上谈兵这么久,开始给我的烧烤摊搞代码:
辅助函数1,造假账:
def loadDataSet() :
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]

辅助函数2,筛选账单中的单元素:
def createC1(dataSet) :
C1 = []
for transaction in dataSet :
for item in transaction :
if not [item] in C1 :
C1.append([item])
C1.sort()
#frozenset是指被“冰冻”的集合,就是说它们是不可改变的,即用户不能修改它们
return map(frozenset,C1)
辅助函数3,基于数据集D,计算Ck里项集的支持度,并过滤出频繁项集:

#D:数据集
#Ck:候选项集
#minSupport: 最小支持度
def scanD(D, Ck, minSupport):
ssCnt = {}
for tid in D:
for can in Ck :
#筛选出数据条数并给相应的候选项集加一
if can.issubset(tid) :
if not ssCnt.__contains__(can): ssCnt[can]=1
else : ssCnt[can] += 1
numItems = float(len(D))
retList = []
supportData = {}
for key in ssCnt:
#每个支持度
support = ssCnt[key]/numItems
if support>=minSupport :
#retList里保存的就是满足支持度的内容
retList.insert(0,key)
supportData[key] = support
return retList, supportData

先简单测试下单元素这一层的支持度,加深一下前面概念的理解:
dataSet = loadDataSet()
#此处被坑很久,python3的map函数做了变动
#这里的Ck很简单,是单元素那一层
Ck = list(createC1(dataSet))
D = list(map(set,dataSet))
#大于0.7的才是频繁项集
retList, supportData = scanD(D,Ck,0.7)
print(retList)
print(supportData)

测试结果是:
[frozenset({5}), frozenset({2}), frozenset({3})]
{frozenset({1}): 0.5, frozenset({3}): 0.75, frozenset({4}): 0.25, frozenset({2}): 0.75, frozenset({5}): 0.75}

 
目前为止只是万里长征第一步,只涉及到单元素层,补齐完整版的Apriori算法:
再添加一个辅助函数4,项集的合并:
#该函数,把1k的元素合并成k个元素的任意组合
def aprioriGen(lk, k) :
retList = []
lenLk = len(lk)
for i in range(lenLk) :
for j in range(i+1,lenLk) :
#这块不好理解,初始的k-2个元素相同就进行合并,组成一个更高一层的项集
#例如[2,3]、[2,5] L1=L2=2,合并后组成[2,3,5]
#[:k-2]看似bug,如果是[2,3][3,5]就无法组成[2,3,5]了,其实如果[2,5]进不来说明已经被淘汰了,[2,3,5]也就不满足
L1 = list(lk[i])[:k-2]
L2 = list(lk[j])[:k-2]
L1.sort()
L2.sort()
if L1==L2 :
retList.append(lk[i] | lk[j])
return retList
Apriori主函数:
#支持分层的求dataSet数据集中满足支持度minSupport的频繁项集
def apriori(dataSet, minSupport=0.5) :
C1 = list(createC1(dataSet))
#D = map(set, dataSet)
D = dataSet
#获得满足支持度的单元素以及所有相关的支持度的值
L1, supportData = scanD(D,C1,minSupport)
#新建一个高维数组L,每层保存在一个list里
L = [L1]
k = 2
#如果上一层有值,证明其可合并。
#书中是>0,把0换成1,效果会更好
while len(L[k-2])>1:
#要将L[k-2]这一层的数据合并成k个元素的数据
#第k层的数据是由k个元素构成的
ck = aprioriGen(L[k-2],k)
#求ck在数据集D中的支持度
lk,supK = scanD(D,ck,minSupport)
supportData.update(supK)
L.append(lk)
k +=1
return L, supportData

测试下:

D = loadDataSet()
L, supportData = apriori(D,minSupport=0.5)
print(L)
print(supportData)

测试结果:
[[frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})], [frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5}), frozenset({1, 3})], [frozenset({2, 3, 5})]]
{frozenset({1}): 0.5, frozenset({3}): 0.75, frozenset({4}): 0.25, frozenset({2}): 0.75, frozenset({5}): 0.75, frozenset({1, 3}): 0.5, frozenset({2, 5}): 0.75, frozenset({3, 5}): 0.5, frozenset({2, 3}): 0.5, frozenset({1, 5}): 0.25, frozenset({1, 2}): 0.25, frozenset({2, 3, 5}): 0.5}


 
到目前为止经过原理学习和代码训练,已经掌握了频繁项集和支持度,有没有发现supportData变量我们还没有利用到,下面开始在频繁项集中挖掘关联规则。
 
从一个频繁项集中可以产生多少条关联规则?如图:



与前面单元素可以组成多少项集一样,又是个指数及的增长,甚至更复杂,我们得想个办法减少计算量。
 
如果某条规则并不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求。
例如上图黑色部分:只要012à3不成立,任何{012}组成的项集也不能满足。推理如下:
012à3
=Support{0,1,2,3}|Support{0,1,2}
其中Support{0,1,2,3}为常量,我们设为P,现在012à3
= P|Support{0,1,2}
01à23=
Support{0,1,2,3}|Support{0,1}=P|Support{0,1}
由于Support{0,1}一定>=
Support{0,1,2}
所以01à23一定<=012à3
同理所有子集都小于012à3

计算可信度的python代码如下:
#L:频繁项集合,元素数和元素组合的2维数组
#supportData:包含那些频繁项集支持数据的字典
#minConf:最小可信度
def generateRule(L, supportData, minConf=0.7) :
#存储满足最小可信度的部分
bigRuleList = []
#分层处理,第0层是单元素层,直接跳过
for i in range(1 ,len(L)) :
for freqSet in L[i] :
#遍历freqSet中的元素组成一个list H1
H1 = [frozenset([item]) for item in freqSet]
#i>1说明至少是L的第2层,该层包含了至少3个元素,进行进一步合并
if i>1 :
# 源码有个bug,25-->3这样的情况推算不到,这一行要加下
calcConf(freqSet, H1, supportData, bigRuleList, minConf)
rulesFromConseq(freqSet, H1,supportData,bigRuleList,minConf)
else :
#i=1是双元元素那一层
calcConf(freqSet, H1,supportData,bigRuleList,minConf)
return bigRuleList

#对可信度进行评估和筛选
#该函数比较简单
def calcConf(freqSet, H,supportData,bigRuleList,minConf=0.7) :
pruneH = []
for conseq in H :
conf = supportData[freqSet]/supportData[freqSet-conseq]
if conf>minConf :
bigRuleList.append((freqSet-conseq,conseq,conf))
pruneH.append(conseq)
print(freqSet-conseq,'--->',conseq, '可信度:',conf)
return pruneH

#生成候选规则
def rulesFromConseq(freqSet, H,supportData,bigRuleList,minConf=0.7) :
m = len(H[0])
if len(freqSet) > m + 1:
#生成更高一层元素
Hmpl = aprioriGen(H, m + 1)
Hmpl = calcConf(freqSet, Hmpl, supportData, bigRuleList, minConf)
#如果结果可合并,继续迭代
if len(Hmpl) > 1 :
rulesFromConseq(freqSet, Hmpl, supportData, bigRuleList, minConf)

 
最后给了一个鉴定毒蘑菇的案例,将Apriopri应用于真实生活。将采集到的蘑菇的属性定义成很多种特性。例如第一列蘑菇是否有毒,有毒则为2,无毒则为1.第二个特性是蘑菇伞的形状,有园顶、塔顶、伞状等6种特性,分别用整数3-8表示。后续还很多属性,属性与属性之间标称的定义需要区分开,因为我们上面Apriori代码需要做并集的处理。
案例数据在git(https://github.com/yejingtao/forblog/blob/master/MachineLearning/trainingSet/mushroom.dat)上,样式如下:
1 3 9 13 23 25 34 36 38 40 52 54 59 63 67 76 85 86 90 93 98 107 113
2 3 9 14 23 26 34 36 39 40 52 55 59 63 67 76 85 86 90 93 99 108 114
2 4 9 15 23 27 34 36 39 41 52 55 59 63 67 76 85 86 90 93 99 108 115
1 3 10 15 23 25 34 36 38 41 52 54 59 63 67 76 85 86 90 93 98 107 113
2 3 9 16 24 28 34 37 39 40 53 54 59 63 67 76 85 86 90 94 99 109 114
2 3 10 14 23 26 34 36 39 41 52 55 59 63 67 76 85 86 90 93 98 108 114
2 4 9 15 23 26 34 36 39 42 52 55 59 63 67 76 85 86 90 93 98 108 115
2 4 10 15 23 27 34 36 39 41 52 55 59 63 67 76 85 86 90 93 99 107 115
1 3 10 15 23 25 34 36 38 43 52 54 59 63 67 76 85 86 90 93 98 110 114
找到哪些特性的蘑菇是有毒的,代码如下:
mushData = [line.split() for line in open('C:\\2017\\提高\\机器学习\\训练样本\\mushroom.dat').readlines()]
L, supportData = apriori(mushData,minSupport=0.3)
#这些里面有毒的部分才是我们关心的部分,过滤出来
m = len(L)
newL = []
for i in range(1,m) :
array = []
for item in L[i] :
# 交集
if item.intersection('2'):
array.append(item)
if len(array)>0 :
newL.append(array)
#求可信度
bigRuleList = generateRule(newL,supportData,minConf=0.9)
#我不关心所有的可信度,我只关心哪些可以推断出有毒,
for line in bigRuleList:
if line[1].intersection('2'):
print(line)


运行结果很庞大,选中其中一些有代表性的,例如
#(frozenset({'59', '28', '90'}), frozenset({'2'}), 0.9967741935483871)
#(frozenset({'53', '28'}), frozenset({'2'}), 1.0)


像这样的数据已经很明显了,特别是28这个属性,从数据集推算出吃了28的蘑菇必死无疑了。这就是数据之间关系的奥秘,它们之间有千丝万缕的联系等待我们去挖掘分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息