咱们在网上购物的时候,常常会遇到这样的推送, 好比买了A书的人, 同时购买了B书的情景, 在这个描述中: 包含以下的信息:python
好比用购物车的例子来讲, 购物车里面的每一件item都叫作一个项, 辣条是一个项, 薯片也是一个项, 全部的商品加起来叫作项集记做itemset, 若是有多件商品记做 k-itemset算法
transaction就是一组非空的 item的集合, 能够把它想成是购物时的小票。数据库
多个 transaction 记录的总和称为 datasetapp
关联规则的定义如上公式:机器学习
这样理解: 就是验证一下P到Q之间有什么关系, 其中P属于itemset ,Q也属于itemiset, 而且P并非Qide
好比:P是牙刷,Q是牙膏, 牙刷和牙膏都是商品,而且牙膏并非牙刷,咱们要作的就是推导出牙膏和牙刷之间之间被用户购买的rule函数
Lk表示有k个元素的频繁项集学习
CK表示有K个元素的候选项集, 既然是候选项集, 意味着它里面的元素极可能存在不知足场景规定的支持度大小, 须要被丢弃掉 (什么是支持度? 往下看...)spa
Cross Selling: 交叉销售, 好比很经典的数据挖掘问题, 购买啤酒的人一般会购买尿布 , 商家知道这个规则后就能够将这两个商品放在一块儿, 方便了购物者, 也能起到提醒的做用, 提升营业额。3d
介绍数据挖掘中的两个重要概念:
itemset的支持度(Support)
**公式: **
**含义: **Support就是频率的意思,好比说牛奶的支持度,好比一共10我的,其中3我的买了牛奶, 那么牛奶的支持度就是3/10 , 它是个比例。若是物品的支持度低,极可能会被忽略不计
关联规则的支持度
**公式: **
含义: 关联规则的支持度,说的其实也是频率,即同时购买X和Y的订单数量,占所有订单数量的百分比
置信度(Confidence)
公式:
含义: 什么是置信度? 好比X=牛奶, Y=面包。置信度就是同时购买牛奶和面包的人, 占购买面包的数量
实际上它描述的就是咱们原来学过的条件几率P(Y|X) =P(Y*X) / P(X)
,这个公式至少高中都学过
根据具体的场景,定义两个变量:
所谓频繁项集大白话说就是这个项集出现的次数很频繁,界定方法:这个项集的 支持度 要大于最小支持度
所谓强规则大白话说就是这个规则很强大颇有意义,界定方法:这个它置信度(条件几率)要大于 最小置信度
给定 I ,D ,σ 和 Φ, 而后咱们去找出 X->Y之间的全部的强规则
用大白话说就是: 当我有了全部的itemset ,dataset,最小支持度,最小置信度,而后去把X与Y之间,符合前面四个要要素的全部强规则都找出来,称为关联规则的挖掘
能够看到, 哪怕仅仅存在4件商品, 他们之间的排列组合也是至关的多
假设咱们如今有以下的数据: 一共是9条记录, 咱们规定这个场景下的支持度是3, 置信度是70%
transactions | itemset |
---|---|
1 | {a, b, c, d} |
2 | {a, c, d, f} |
3 | {a, c, d, e, g} |
4 | {a, b, d, f} |
5 | {b, c, h} |
6 | {d, f, g} |
7 | { a, b, g } |
8 | {c, d, f, g} |
9 | {b, c, d} |
若是咱们想找出频繁1项集, 也就是 L1的话, 很容易就能想到, 只要遍历全部的itemset中的每个item ,而后记录下他们出现的次数, 但凡是出现次数大于3的, 都符合要求而被保留, 小于3的都不知足要求而被丢弃, 因此咱们能获得下面的结果:
item | support |
---|---|
a | 5 |
b | 5 |
c | 6 |
d | 7 |
f | 4 |
g | 4 |
因此, 这个问题比较复杂的地方就是如何经过 lK -> CK+1 , 换句话说, 就是如何经过如今已知的 l1求出 C2
思路:
一样的咱们能够对L1进行排列组合, 得出全部的状况称它为set1, 而后再遍历set1每一项, 去看看最开始给定的itemsets中是否包含它, 若是找到了包含它的项, 咱们就给他的支持度计数+1, 一样的遍历完set1中全部的项后, 把支持度不知足的项删除, 保留支持度大于3的项, 至此, 也就意味着咱们找到了 C2, 因而咱们能够获得下面的结果
item | support |
---|---|
{a,b} | 3 |
{a,c} | 3 |
{a,d} | 4 |
{b,c} | 3 |
{b,d} | 3 |
{c,d} | 5 |
{d,f} | 4 |
{d,g} | 3 |
再日后就是从 L2 - > C3的过程:
思路1: 若是咱们仍然使用自由组合的规则生成C3的话, 能产生向下先这么多的组合,实际上里面有不少没必要要的项 根据Apriori算法的规定, 若是一个项集是不频繁的, 那么它的全部超集都是不频繁的
好比下面的{a,d,f} 其中的子项{a,d} 并非频繁二项集, 那么咱们根本不必把它也组合进来, 由于一旦被组合进来以后, 就意味着咱们要求去为他扫描数据库
item | support |
---|---|
{a,b,c} | 1 |
{a,b,d} | 2 |
{a,c,d} | 3 |
{b,c,d} | 2 |
{a,d,f} | X |
{a,d,g} | X |
{b,d,f} | X |
{b,d,g} | X |
{c,d,f} | X |
{c,d,g} | X |
{d,f,g} | X |
那若是不自由组合, 具体怎么作呢?
第一步: 对全部的itemset进行一次排序, 由于第二步就要进行相似前缀的操做
第二步: 若是前k-1项相同, 第k项不一样, 那么把第k项搬过去, 就获得新的 候选项, 重复这个动做
经过步骤二的选择方式, 能够排除掉大量的确定不频繁的候选项集, 可是也不能保证, 保存下来的候选项集就必定会是频繁的
因此, 若是咱们遵循Apriori算法规定的话, 咱们获得的集合以下应该长下面这样,
item | support |
---|---|
{a,b,c} | 1 |
{a,b,d} | 2 |
{a,c,d} | 3 |
{b,c,d} | 2 |
一样他们的支持度是具体是多少, 仍然得去扫描数据库, 实际上就是遍历这个C3, 而后看看一开始给定的itemsets中有多少个包含了它, 一旦发现就给它的计数+1 , 一样仅仅保存符合规则的, 获得以下的结果:
item | support |
---|---|
{a,c,d} | 3 |
找出全部的强关联规则, 大白话讲就是挨个计算全部的 L2 , L3 ... (频繁项集) 中, 他们的置信度是否在当前场景要求的70%以上, 若是知足了这个条件, 说明它就是强关联规则, (啤酒和尿布的关系)
计算公式就是它:
实际上就是计算:在Y发生的基础上, X发生的条件几率, 直接看公式就是 X和Y同时出现的次数 / X 出现的次数
举个例子: 好比求a->b , 就是 a b同时发生的支持度/ a的支持度 , 前者中C2中能够查处来, 后者从L1中能够查出来,也就是 3/5 = 60%
若是规定置信度是70% , 那么强关联规则以下:
频繁3项集是 {a,c,d}, 那么有以下几种状况
计算公式如上, {a,c,d}的置信度 / 箭头左边出现的项集的置信度
和标准的置信度比较,获得的结强关联规则以下:
# 加载数据集 def loadDataSet(): return [ ['a', 'b', 'c', 'd'], ['a', 'c', 'd', 'f'], ['a', 'c', 'd', 'e', 'g'], ['a', 'b', 'd', 'f'], ['b', 'c', 'h'], ['d', 'f', 'g'], ['a', 'b', 'g'], ['c', 'd', 'f', 'g'], ['b', 'c', 'd']] # 选取数据集的非重复元素组成候选集的集合C1 def createC1(dataSet): C1 = [] for transaction in dataSet: # 对数据集中的每条购买记录 for item in transaction: # 对购买记录中的每一个元素 if [item] not in C1: # 注意,item外要加上[],便于与C1中的[item]对比 C1.append([item]) C1.sort() return list(map(frozenset, C1)) # 将C1各元素转换为frozenset格式,注意frozenset做用对象为可迭代对象 # 如今的CK是全部的一项集, 经过下面的前两个循环计算出每个候选1项集出现的次数,进而才能判断出是否知足给定的支持度 # 由Ck产生Lk:扫描数据集D,计算候选集Ck各元素在D中的支持度,选取支持度大于设定值的元素进入Lk def scanD(DataSet, Ck, minSupport): ssCnt = {} # map字典结构, key = 项集 value = 支持度 for tid in DataSet: # 遍历每一条购买记录 for can in Ck: # 遍历Ck,也就是全部候选集 if can.issubset(tid): # 若是候选集包含在购买记录中,计数+1 ssCnt[can] = ssCnt.get(can, 0) + 1 numItems = float(len(DataSet)) # 购买记录数 retList = [] # 用于存放支持度大于设定值的项集 supportData = {} # 用于记录各项集对应的支持度 for key in ssCnt.keys(): support = ssCnt[key] / numItems if support >= minSupport: retList.insert(0, key) # 知足最小支持度就存储起来 supportData[key] = support return retList, supportData # 返回频繁1项集和他们各自的支持度 # 由Lk产生Ck+1 , 那第一次进来就是频繁1项集, 企图生成频繁二项集 def aprioriGen(Lk, k): # Lk的k和参数k不是同一个概念,Lk表示频繁k项集, 参数K = CK+1 中的K+1 retList = [] lenLk = len(Lk) for i in range(lenLk): # 分别遍历Lk中的每一项, 让它们和其余的项组合 # 下面的for循环, 使用的是正宗的 相似前缀匹配的方式生成候选项集的方式 for j in range(i + 1, lenLk): # 从i+1开始, 是不让当前元素和本身也进行组合 # 经过这两层循环, 就能实现相似这种组合 01, 02 ,03 ... 12,13,... L1 = list(Lk[i])[:k - 2] # 将当前的频繁项集转换成list ,而后进行切片, 若是lk是频繁1项集, 这里根本切不出东西来 L2 = list(Lk[j])[:k - 2] # 前缀匹配前确定须要先排序,让它们相对是有序的 L1.sort() L2.sort() if L1 == L2: # 若前k-2项相同,则合并这两项 retList.append(Lk[i] | Lk[j]) # 01, 02 ,03 ... 12,13,... return retList # Apriori算法主函数 def apriori(dataSet, minSupport=0.5): # 建立c1 C1 = createC1(dataSet) # todo 为啥须要去除重复? # 将dataset中全部的元素进行一次set去重, 而后转换成list返回获得D DataSet = list(map(set, dataSet)) # 获得频繁1项集和各个项集的最小支持度 L1, supportData = scanD(DataSet, C1, minSupport) L = [L1] k = 2 # 从频繁1项集开始, 进行从Lk -> CK+1的生成 while len(L[k - 2]) > 0: # 当L[k]为空时,中止迭代 Ck = aprioriGen(L[k - 2], k) # L[k-2]对应的值是Lk-1 # 获取到候选项集后, 从新扫描数据库, 计算出频繁项集,以及他们各自的支持度 Lk, supK = scanD(DataSet, Ck, minSupport) # 更新他们各自的支持度 supportData.update(supK) # 将新推导出来的频繁项集也添加进去 L.append(Lk) k += 1 return L, supportData # 处理两个item项的 可信度(confidence) def calcConf(freqSet, supportData, strong_rule, minConf): # 那么如今须要求出 frozenset([1]) -> frozenset([3]) 的可信度和 frozenset([3]) -> frozenset([1]) 的可信度 for conseq in freqSet: # print 'confData=', freqSet, H, conseq, freqSet-conseq # 支持度定义: a -> b = support(a | b) / support(a). the_list = list(conseq) left = the_list[0] right = the_list[1] # 计算a-b conf = supportData[conseq] / supportData[frozenset({left})] if conf >= minConf: strong_rule.append("{} -> {}".format(frozenset({left}),frozenset({right}))) # 计算b-a conf = supportData[conseq] / supportData[frozenset({right})] if conf >= minConf: strong_rule.append("{} -> {}".format(frozenset({right}), frozenset({left}))) def calcConfidence(Ck, fullSet,all_set,supportData, strong_rule, minConf): ''' Ck : k阶的频繁项集 fullSet : k阶频繁项集拆开后的元素 set : 由set 反向拆分出 Ck supportData, strong_rule, minConf ''' for itemset in Ck: the_list = list(itemset) temp = fullSet.copy() for item in the_list: temp.remove({item}) s = set() for i in temp: for j in i: s.add(j) # confidence(a->b) = support(a,b) / support(a) confidence1 = supportData[all_set] / supportData[frozenset(itemset)] if(confidence1>=minConf): strong_rule.append("{} -> {}".format(frozenset(itemset), frozenset(s))) # confidence(b->a) = support(a,b) / support(b) confidence2 = supportData[all_set] / supportData[frozenset(s)] if (confidence2 >= minConf): strong_rule.append("{} -> {}".format(frozenset(s), frozenset(itemset))) def rulesFromConseq(freqSet, supportData, strong_rule, minConf): for itemset in freqSet: for set in itemset: H = list(set) the_list = [] for i in H: the_list.append({i}) LL = [] LL.append(the_list) k = 2 # 从频繁1项集开始, 进行从Lk -> CK+1的生成 while len(LL[k - 2]) > 0: # 当L[k]为空时,中止迭代 # 直接对CK进行 统计置信度, 是由于根据Apriori算法的约定, 若是CK是频繁的,那么它的非空子集也是频繁的 Ck = aprioriGen(LL[k - 2], k) # L[k-2]对应的值是Lk-1 # temp = the_list - Ck if len(Ck[0]) == len(the_list): break else: # 统计强关联规则 calcConfidence(Ck, LL[k - 2],set,supportData, strong_rule, minConf) # 添加继续循环的条件 LL.append(Ck) k += 1 def get_strong_rule(L,supportData,confidence): freSet = [L[k] for k in range(2, len(L))] strong_rule = [] # 处理两项 calcConf(L[1], supportData, strong_rule, confidence) # 处理三项及以上 rulesFromConseq(freSet, supportData, strong_rule, confidence) return strong_rule if __name__ == '__main__': minSupport = 0.3 confidence = 0.7 # 加载数据 dataset = loadDataSet() # 找出各频繁项集, 及其支持度 L, supportData = apriori(dataset, minSupport) # 找出强规则 strong_rules = get_strong_rule(L, supportData,confidence) print('全部项集及其支持度: ',supportData) print('全部频繁项集: ',L) print("强规则: ",strong_rules)
算法参考: 《机器学习实战》 Peter Harrington Chapter11
另外, 添加了求强规则的逻辑
每次虽然从Lk->CK+1 都是尽量的生成出数量最少候选项集, 还保证了不会丢失频繁的候选项集, 可是依然须要校验生成出来的全部的候选项集的支持度是否符合场景要求的支持度大小, 不足的地方就体如今这里, 由于每次去校验的时候都得扫描一遍数据库, 若是数据库很大, 大量的IO会让算法变慢