#!/usr/bin/env python
# encoding: utf-8
'''
<<机器学习实战>> 读书笔记 第11章 使用Apriori算法进行关联分析---从频繁项集中挖掘关联规则
关键:
1 关联规则
某个元素或者某个元素集合可能会推导出另一个元素
举例: 如果有一个频繁项集{豆奶,莴苣},那么就可能有一条关联规则: 豆奶 --> 莴苣,
这意味着如果有人购买了豆奶,那么他购买莴苣的概率较大。
关联规则的量化指标: 可信度
一条规则P --> H的可信度定义为support(P | H) / support(P)
注意: Python中,操作符 | 表示集合的并操作
P|H: 是指所有出现在集合P或者集合H中的元素
2 频繁项集产生关联规则
举例: {0, 1, 2, 3}产生的规则列表如下
0123->空
123->0 023->1 013->2 012->3
23->01 13->02 12->03 03->12 02->13 01->23
3->012 2->013 1->023 0->123
如果: 0,1,2->3是一套低可信度规则,那么其他以3作为后件的规则的可信度也会较低
前件: 箭头左边的集合
后件: 箭头右边的集合
如果某条规则并不满足最小可信度要求-->那么该规则的所有子集也不会满足最小可信度要求
3 频繁项集到关联规则生成
算法思想:
从一个频繁项集开始,创建一个规则列表,其中规则右部值包含一个元素,然后对这些规则进行测试。
接下来合并所有剩余规则来创建一个新的规则列表,其中规则右部包含两个元素。
这种方法被称为分级法。
4 从频繁项集中挖掘关联规则算法
步骤1: 遍历频繁项集列表中的每个频繁项
1.1 对该频繁项中每个元素进行遍历,生成每个元素组成的frozenset得到的结果作为规则右部的候选元素列表H
1.2 如果频繁项集的元素数目只有2个,则计算该频繁项集对应规则右部的元素列表。否则,执行1.3
1.3 表明当前频繁项集中元素数目大于2个,则判定如果当前频繁项集的长度大于规则右部候选元素列表H中H[0]的长度,
则表明可以对规则右部候选元素列表生成H[0]长度+1的规则右部候选元素列表canH,
1.4 计算规则右部候选元素列表为canH时,当前频繁项集对应规则右部的元素列表realH
1.5 如果当前频繁项集对应规则右部的元素列表realH长度大于1,则使用realH作为规则右部的候选元素列表
递归处理,转步骤1
'''
'''
作用:
输入参数: 频繁项集列表L,包含哪些频繁项集支持数据的字典supportData, 最小可信度阈值
返回结果: 返回包含可信度的规则列表
算法:
步骤1: 遍历频繁项集列表中的每个频繁项
1.1 对该频繁项中每个元素进行遍历,生成每个元素组成的frozenset得到的结果作为规则右部的候选元素列表H
1.2 如果频繁项集的元素数目只有2个,则计算该频繁项集对应规则右部的元素列表。否则,执行1.3
1.3 表明当前频繁项集中元素数目大于2个,则判定如果当前频繁项集的长度大于规则右部候选元素列表H中H[0]的长度,
则表明可以对规则右部候选元素列表生成H[0]长度+1的规则右部候选元素列表canH,
1.4 计算规则右部候选元素列表为canH时,当前频繁项集对应规则右部的元素列表realH
1.5 如果当前频繁项集对应规则右部的元素列表realH长度大于1,则使用realH作为规则右部的候选元素列表
递归处理,转步骤1
'''
def generateRules(L, supportData, minConf=0.7):
ruleList = []
for i in range(1, len(L)):
for freqSet in L[i]:
candidateH = [frozenset([value]) for value in freqSet]
if i > 1:
rulesFromConseq(freqSet, candidateH, supportData, ruleList, minConf)
else:
calcConf(freqSet, candidateH, supportData, ruleList, minConf)
return ruleList
'''
作用: 如果频繁项集种玫瑰只有两个元素,则可用函数calcConf来计算可信度
输入参数:
频繁项集freqSet,
出现在规则右部的元素列表H,
频繁项集对应的支持度信息的字典supportData,
最小可信度minConf
输出参数:包含规则左部,规则右部,可信度的元组作为元素的列表ruleList
返回结果: 可以作为当前频繁项集对应关联规则右部的元素列表
算法:
步骤1: 创建一个空列表prunedH
步骤2: 遍历H中所有项集并计算他们的可信度值
2.1 如果某条规则满足最小可信度值,那么将规则打印,并将规则加入到结果列表中
'''
def calcConf(freqSet, H, supportData, ruleList, minConf=0.7):
if not freqSet:
print "freqSet is empty"
return
prunedH = list()
for consequence in H:
conf = supportData[freqSet] / supportData[freqSet - consequence]
if conf >= minConf:
print "{left}->{right}, conf: {conf}".format(
left=freqSet - consequence,
right=consequence,
conf=conf
)
ruleList.append((freqSet - consequence, consequence, conf))
prunedH.append(consequence)
return prunedH
'''
作用: 如果频繁项集的元素数目超过2,会对它做进一步合并
输入参数:
频繁项集freqSet,
出现在规则右部的元素列表H,
频繁项集对应的支持度信息的字典supportData,
最小可信度minConf
输出参数:包含规则左部,规则右部,可信度的元组作为元素的列表ruleList
返回结果:
算法:
步骤1: 先计算H中的频繁集大小m
步骤2: 查看该频繁项集是否达到可以移除大小为m的子集。
2.1 如果可以的话,将其移除。可以使用aprioriGen生成H中元素的无重复组合Hmp1
Hmp1是下一次迭代的H列表 ,包含所有可能的规则
2.2 计算该规则的可信度,看是否满足要求;
如果不止一条规则满足要求,使用Hmp1递归调用rulesFromConseq来判断是否可以进一步组合这些规则
'''
def rulesFromConseq(freqSet, H, supportData, ruleList, minConf=0.7):
hLen = len(H[0])
fLen = len(freqSet)
if fLen > hLen + 1:
candidateHList = aprioriGen(H, hLen + 1)
realHList = calcConf(freqSet, candidateHList, supportData, ruleList, minConf)
# 这意味着规则右部多于一条,就可以合并规则右部生成新的规则右部,继续处理
if len(realHList) > 1:
rulesFromConseq(freqSet, realHList, supportData, ruleList, minConf)
def loadDataSet():
return [ [1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5] ]
'''
作用: 创建大小为1的所有候选项集的集合
算法:
步骤1: 解析数组中每个元素
步骤2: 构建每个元素组成的数组;
2.1 如果该数组不在结果数组中,就放入到结果数组中;
步骤3: 对结果数组进行排序,并转换为fronzenset并返回
'''
def createC1(dataSet):
if not dataSet:
return frozenset()
result = []
for arr in dataSet:
for value in arr:
if [value] not in result:
result.append([value])
result.sort()
'''
之所以使用frozenset:冻结集合是因为,它们不可改变,需要将这些集合作为
字典键值使用。
'''
return map(frozenset, result)
'''
作用: 用于从C1生成L1。
输入参数: 数据集D, 候选项列表Ck, 感兴趣项集的最小支持度minSupport
返回值: 返回一个包含支持度的字典
算法:
步骤1: 先创建一个空字典ssCnt
步骤2: 遍历数据集中的所有交易记录以及C1中的所有候选集。
2.1 如果C1中的集合是记录中的一部分,则增加字典中对应的计数值。
步骤3: 构建一个空列表作为满足做小支持度的集合。
步骤4: 遍历步骤1中的字典ssCnt中每个元素,计算其支持度。
4.1 如果支持度满足最小支持度要求,则将字典元素放到结果列表中
4.2 更新频繁项集的支持度字典
步骤5: 返回频繁项集的结果列表,返回最频繁项集的支持度
总结: 扫描算法其实就是两个过程。
第一个过程就是根据候选项集找到包含候选项的超集,并统计该候选项集出现的次数
第二个过程就是根据候选项集的出现次数,计算候选项的支持度,将满足最小支持度的候选项加入到最终结果列表中。
'''
def scanD(D, Ck, minSupport):
if not D or not Ck:
print "D is empty or Ck is empty, D is: {D} , Ck is: {Ck}".format(D=D, Ck=Ck)
canToTimes = {}
for tid in D:
for can in Ck:
if can.issubset(tid):
if canToTimes.has_key(can):
canToTimes[can] += 1
else:
canToTimes[can] = 1
result = []
canToSupport = {}
length = len(D)
for can, times in canToTimes.iteritems():
support = float(times) / length
if support >= minSupport:
result.insert(0, can)
canToSupport[can] = support
return result, canToSupport
'''
作用:
输入参数: 频繁项集列表Lk, 项集元素个数k(是输入的频繁项集列表中任意元素的长度值+1)
返回结果: 输出Ck。
举例: 该函数以{0}, {1},{2}作为输入,会生成{0, 1}, {0, 2}和{1,2}
算法:
步骤1: 每次遍历频繁项集列表中任意两个不同的频繁项集
步骤2: 如果这两个频繁项集的前k-2是相同的,则将这两个频繁项集合并,并添加到结果列表中
'''
def aprioriGen(Lk, k):
if not Lk or k < 2:
print "Lk is empty or k < 2, Lk: {Lk}, k: {k}".format(Lk=Lk, k=k)
return
length = len(Lk)
results = []
for i in range(length):
for j in range(i + 1, length):
list1 = list(Lk[i])[: k - 2]
list2 = list(Lk[j])[: k - 2]
list1.sort()
list2.sort()
if list1 == list2:
result = Lk[i] | Lk[j]
results.append(result)
return results
'''
作用:
输入参数: 数据集dataSet(是一个列表, 列表中每个元素是列表); 最小支持度minSupport(浮点数)
返回结果: 频繁项集结果列表(该频繁项集结果列表中的每个元素是一个列表,列表中的每个元素是frozenset类型的结果)
每个频繁项集对应的支持度
算法:
步骤1: 获取长度为1的候选项列表C1,具体计算过程如下:
1.1 遍历数据集(实际是数组)中每个元素
1.1.1 构建每个元素组成的数组;
1.1.2 如果该数组不在结果数组中,就放入到结果数组中;
1.2 对结果数组进行排序,并转换为fronzenset并返回
步骤2: 根据C1,数据集和最小支持度计算出满足最小支持度的频繁项集L1,以及频繁项集的支持度信息,
具体从候选集计算得到频繁集的过程如下:
2.1: 先创建一个空字典ssCnt
2.2: 遍历数据集中的所有交易记录以及C1中的所有候选集。
2.2.1 如果C1中的集合是记录中的一部分,则增加字典中对应的计数值。
2.3: 构建一个空列表作为满足做小支持度的集合。
2.4: 遍历步骤2.1中的字典ssCnt中每个元素,计算其支持度。
2.4.1 如果支持度满足最小支持度要求,则将字典元素放到结果列表中
2.4.2 更新频繁项集的支持度字典
2.5: 返回频繁项集的结果列表,返回频繁项集的支持度信息
步骤3: 创建一个频繁项集结果列表L,设定k=2
步骤4: 只要L[k-2]的长度大于0,就进入步骤5;否则,算法结束,返回频繁项集列表L和频繁项集对应的支持度信息
步骤5:根据频繁项集列表L[k-2]和所要求出的频繁项集的长度k,计算得到候选项集Ck,具体计算过程如下:
5.1 每次遍历频繁项集列表中任意两个不同的频繁项集
5.2 如果这两个频繁项集的前k-2是相同的,则将这两个频繁项集合并,并添加到结果列表中
5.3 返回结果列表,即为Ck
步骤6: 根据Ck, 数据集和最小支持度计算出频繁项集Lk和Lk频繁项集的支持度信息,并更新总的频繁项集的支持度信息
步骤7: 向频繁项集总的结果列表L中加入当前频繁项集Lk, 并令k累加
总结:
根据候选集Ck,数据集,最小支持度 --> 频繁集Lk
根据频繁集Lk, 所要求出的频繁项的长度k+1 -> 候选集Ck+1
根据候选集Ck+1, 数据集,最小支持度 --> 频繁集Lk+1
...
'''
def apriori(dataSet, minSupport=0.5):
C1 = createC1(dataSet)
D = map(set, dataSet)
L1, supportDict = scanD(D, C1, minSupport)
L = [L1]
k = 2
while len(L[k - 2]) > 0:
Ck = aprioriGen(L[k-2], k)
Lk, partialSupportDict = scanD(D, Ck, minSupport)
supportDict.update(partialSupportDict)
# note, Lk may be empty, if Lk is empty, it needs to end this recycle
if not Lk:
break
L.append(Lk)
k += 1
return L, supportDict
def process():
dataSet = loadDataSet()
L, supportDict = apriori(dataSet)
print "频繁项集支持度信息: {supportDict}".format(
supportDict=supportDict
)
for i, arr in enumerate(L):
print "频繁项集长度为{length}的频繁项集如下: {arr} ".format(length=i+1, arr=arr)
rules = generateRules(L, supportDict, minConf=0.7)
print rules
if __name__ == "__main__":
process()