#!/usr/bin/env python
# encoding: utf-8
'''
<<机器学习实战>> 读书笔记 第11章 使用Apriori算法进行关联分析
关键:
1 关联分析
含义:从大规模数据集中寻找物品之间的隐含关系
主要问题: 暴力搜索物品的不同组合很困难
2 Apriori算法
优点: 容易编码实现
缺点: 在大数据集上可能较慢
适用数据类型: 数值型或标称型数据
关系形式: 频繁项集或者关联规则
频繁项集: 经常出现在一块的物品的集合
关联规则: 暗示两种物品之间可能存在很强的关系
例子:
交易号码 商品
0 豆奶,莴苣
1 莴苣,尿布,葡萄酒,甜菜
2 豆奶,尿布,葡萄酒,橙汁
3 莴苣,豆奶,尿布, 葡萄酒
4 莴苣,豆奶,尿布,橙汁
支持度: 数据集中包含该项集额记录所占的比例。
上述例子中,{豆奶}支持度为4/5
5条交易记录中有3条: {豆奶,尿布},{豆奶,尿布}的支持度为3/5
支持度特点: 可定义最小支持度
可信度/置信度:
含义: 是指针对一条诸如{尿布}->{葡萄酒}的关联规则来定义的。
规则可信度被定义为: "支持度({尿布,葡萄酒})/支持度({尿布})
由于{尿布,葡萄酒}支持度为3/5, {尿布}支持度为4/5,
所以 尿布 -> 葡萄酒 的可信度=3/4=0.75
意味着,对于包含尿布的所有记录,我们的规则对其中75%的记录都适用。
3 Apriori原理
作用: 帮我们减少感兴趣的项集。
原理: 如果某个项集是频繁的,那么它的所有子集也是频繁的。
如果一个项集是非频繁集,那么它的所有超集也是非频繁的。
举个例子:如果{0, 1}是频繁的,那么{0}和{1}也一定是频繁的。
apriori: 来自以前 。 定义问题时,会使用先验知识或假设,这被称作是一个先验。
例如: 贝叶斯统计。
先验知识可能来自领域知识,先前的一些测量结果等。
我的理解: 实际Apriori的原理就是类似剪枝,减小搜索规模。
4 使用Apriori算法来发现频繁项集
关联分析的目标: 发现频繁项集和发现关联规则。
处理过程: 先发现频繁项集,然后获得关联规则。
本节只关注发现频繁项集。
Apriori算法:
作用: 发现频繁项集
算法思想:
步骤1: 生成所有单个物品的项集列表
步骤2:扫描交易记录来查看哪些项集满足最小支持度要求,过滤不满足的
步骤3: 对剩余集合进行组合来生成包含两个元素的项集
步骤4:重新扫描交易记录,去掉不满足最小支持度的项集。
步骤5: 重复上述步骤,直到所有项集被去掉。
5 完整的Apriori算法
思想:
当集合项中的个数大于0时
构建一个k个项组成的候选项集的列表
确保每个项集都是频繁的
保留频繁项集并构建k+1项组成的候选项集的列表
apriori算法完整过程:
输入参数: 数据集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 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)
# C1 = createC1(dataSet)
# print "C1: {C1}".format(C1=C1)
# D = map(set, dataSet)
# print "D: {D}".format(D=D)
# minSupport = 0.5
# L1, canToSupport = scanD(D, C1, minSupport)
# print "L1: {L1},canToSupport: {canToSupport}".format(L1=L1, canToSupport=canToSupport)
if __name__ == "__main__":
process()