数据挖掘——关联规则算法之Apriori
数据挖掘——关联规则一、关联规则的基本概念二、强关联规则三、关联规则挖掘算法一、关联规则的基本概念设I=i1,i2,...,imI={i_{1},i_{2},...,i_{m}}I=i1,i2,...,im为所有项目的集合,D为事务数据库,事务T是一个项目子集(T⊑IT\sqsubseteq IT⊑I)。每一个事务具有唯一的事务标识TID。设A是一个由项目构成的集合,称为项集。事务T包含..
数据挖掘——关联规则算法之Apriori
一、关联规则的基本概念
设 I = i 1 , i 2 , . . . , i m I={i_{1},i_{2},...,i_{m}} I=i1,i2,...,im为所有项目的集合,D为事务数据库,事务T是一个项目子集( T ⊑ I T\sqsubseteq I T⊑I)。每一个事务具有唯一的事务标识TID。设A是一个由项目构成的集合,称为项集。事务T包含项集A,当且仅当 A ⊑ T A\sqsubseteq T A⊑T。如果项集A中包含k个项目,则称其为k项集。
-------------------------------------------------------------------------------
用通俗的方式解释下上面的定义:
以超市的情况为例 ,该超市有的那些商品的集合可以被看成I( i 1 i_{1} i1=牛奶, i 2 i_{2} i2=面包,……);事务T可以看成每一个顾客单词购买的所有的商品;那么需要我们挖掘的就是项集,牛奶和面包就是二项集,其他以此类推。
-------------------------------------------------------------------------------
再给两个定义:
项集A在事务数据库D中出现的次数占D中总事务的百分比叫做项集的支持度。
如果项集的支持度超过用户给定的最小支持阈值,就称该项集是频繁项集(或频集)。
-------------------------------------------------------------------------------
再用通俗的方式解释下上面的两个定义:
还是以超市的情况为例 ,假设有1000个顾客,其中有200个人购买了牛奶,那么此时的支持度:200/1000 = 20%,它能够体现出牛奶在所有人购物中的频度,只有当这个频度高到一定阈值的时候 ,才认为该商品是对挖掘有意义的。
-------------------------------------------------------------------------------
关联规则是形如 X ⇒ Y X\Rightarrow Y X⇒Y的逻辑蕴含式,其中 X ⊏ I , Y ⊏ I , 且 X ∩ Y ≠ ∅ X\sqsubset I,Y\sqsubset I,且X \cap Y \neq \varnothing X⊏I,Y⊏I,且X∩Y=∅。
如果事务数据库D中有s%的事务包含 X ∪ Y X\cup Y X∪Y,则称关联规则 X ⇒ Y X\Rightarrow Y X⇒Y的支持度为s%。
关联规则的信任度为 s u p p o r t ( X ∪ Y ) / s u p p o r t ( X ) support(X\cup Y)/support(X) support(X∪Y)/support(X),也就是:
s u p p o r t ( X ⇒ Y ) = P ( X ∪ Y ) c o n f i d e n c e ( X ⇒ Y ) = P ( Y ∣ X ) P ( X , Y ) P ( X ) support(X\Rightarrow Y)=P(X\cup Y)\\ confidence(X\Rightarrow Y)=P(Y|X)\frac{P(X,Y)}{P(X)} support(X⇒Y)=P(X∪Y)confidence(X⇒Y)=P(Y∣X)P(X)P(X,Y)
说的太官方,看的云里雾里。那么简单地来表达:在进行关联规则挖掘之前要找到一个东西,这个东西就是频繁项集,根据这个频繁项集找到哪些商品出现的次数多,在那些次数大于阈值的项集上再去进行有意义的挖掘人物。看上面的公式可知,支持度表示的是两种商品同时出现的概率。 信任度时用来产出规则的,用来评价X对Y的影响大,还是Y对X的影响大。
二、强关联规则
强关联规则就是支持度和信任度分别满足用户给定阈值的规则。
------------------------------------------------------------------------------------
举例:
交易ID | 购买的商品 |
---|---|
2000 | A,B,C |
1000 | A,C |
4000 | A,D |
5000 | B,E,F |
设最小支持度为50%,最小可信度为50%,则可得到:
(1)先计算最小支持度:
s u p p o r t ( A ) = 3 4 = 0.75 = 75 % → 大 于 最 小 支 持 度 50 % s u p p o r t ( B ) = 2 4 = 0.5 = 50 % → 等 于 最 小 支 持 度 50 % s u p p o r t ( C ) = 2 4 = 0.5 = 50 % → 等 于 最 小 支 持 度 50 % s u p p o r t ( D ) = s u p p o r t ( E ) = s u p p o r t ( F ) = 1 4 = 0.25 = 25 % → 小 于 最 小 支 持 度 50 % support(A)=\frac{3}{4}=0.75= 75\% \rightarrow 大于最小支持度50\%\\support(B)=\frac{2}{4}=0.5= 50\%\rightarrow 等于最小支持度50\%\\ support(C)=\frac{2}{4}=0.5= 50\% \rightarrow 等于最小支持度50\%\\ support(D)=support(E)=support(F)=\frac{1}{4}=0.25= 25\% \rightarrow 小于最小支持度50\% support(A)=43=0.75=75%→大于最小支持度50%support(B)=42=0.5=50%→等于最小支持度50%support(C)=42=0.5=50%→等于最小支持度50%support(D)=support(E)=support(F)=41=0.25=25%→小于最小支持度50%
由以上结果可知,按照支持度的定义,此时分析商品A,B,C是有意义的。
(2)计算可信度(只计算A和C):
A ⇒ C ( 50 % , 66.6 % ( 2 / 3 ) ) C ⇒ A ( 50 % , 100 % ( 2 / 2 ) ) A\Rightarrow C (50 \%,66.6\%(2/3))\\ C \Rightarrow A (50\%,100\%(2/2)) A⇒C(50%,66.6%(2/3))C⇒A(50%,100%(2/2))
那么就可以说,在满足最小支持度的基础上,一位顾客购买A再购买C的可能是66.6%,而购买C再购买A的可能是100%。
三、关联规则挖掘算法
这里仅列出,本文未一一详述,只介绍Apriori算法,如有可能会在本人的其他博客中再详细说明。
其中,最有效和有影响的算法已用加粗字体标出。
下面开始说Apriori算法
Apriori算法将发现关联规则的过程分为两个步骤:
- 通过迭代,检索出事务数据库中的所有频繁项集,即支持度不低于用户设定的阈值的项集。在这个过程中连接步和剪枝步互相融合,最终得到最大频繁项集 L k L_{k} Lk
(1)连接步:
连接步的目的是找到K项集。对于给定的最小支持度阈值,分别对1项候选集C1,剔除小于该阈值的项集得到1项频繁集L1;下一步由L1自身连接产生2项候选集C2,保留C2中满足约束条件的项集得到2项频繁集,记为L2;再下一步 有L2与L3连接产生3项候选集C3,保留C3中满足约束条件的项集得到3项频繁集,记为L3,……这样循环下去,得到最大频繁集Lk。
(2)剪枝步:
剪枝步紧接着连接步,在产生候选项Ck的过程中起到减小搜索空间的目的。由于Ck是Lk-1和L1连接产生的,根据Apriori算法的性质频繁项集的所有非空子集也必须是频繁项集,所以不满足该性质的项集不会存在于Ck中。
2.由频繁项集产生强关联规则:由过程1可知未超过预定的最小支持度阈值的项集已经被剔除,如果剩下这些规则又满足了预定的最小可信度阈值,那么就挖掘出了强关联规则。
注:挖掘或识别出所有频繁项集是该算法的核心,占整个计算量的大部分
举例:
下面结合餐饮行业的实例来讲解Apriori关联规则算法挖掘的实现过程。数据库中部分点餐数据见下表:
序列 | 时间 | 订单号 | 菜品id | 菜品名称 |
---|---|---|---|---|
1 | 2014/8/21 | 101 | 18491 | 啦 |
2 | 2014/8/21 | 101 | 8693 | 啦啦 |
3 | 2014/8/21 | 101 | 8705 | 啦啦啦 |
4 | 2014/8/21 | 102 | 8842 | 饿 |
5 | 2014/8/21 | 102 | 7794 | 饿饿 |
6 | 2014/8/21 | 103 | 8842 | 饿饿饿饿 |
7 | 2014/8/21 | 103 | 8693 | 哦 |
… | … | … | … | … |
将上表的事务数据整理成关联规则模型所需的数据结构,从中抽取10个点餐订单最为事务数据库,设支持度为0.2(10个数据,那么支持度计数为2),为方便起见将菜品{18491,8842,8693,7794,8705}分别简记为{a,b,c,d,e},见下表:
订单号 | 菜品id | 菜品id |
---|---|---|
1 | 18491,8693,8705 | a,c,e |
2 | 8842,7794 | b,d |
3 | 8842,8693 | b,c |
4 | 18491,8842,8693,7794 | a,b,c,d |
5 | 18491,8842 | a,b |
6 | 8842,8693 | b,c |
7 | 18491,8842 | a,b |
8 | 18491,8842,8693,8705 | a,b,c,e |
9 | 18491,8842,8693 | a,b,c |
10 | 18491,8693,8705 | a,c,e |
过程一: 找到最大k项频繁集
(1)算法简单扫描所有的事务,事务中的每一项都是候选集1项集的集合 C 1 C_{1} C1的成员,计算每一项的支持度。
P ( a ) = 项 集 a 的 支 持 度 计 数 所 有 事 务 个 数 = 7 10 = 0.7 P ( b ) = 8 10 = 0.8 P ( c ) = 7 10 = 0.7 P ( d ) = 2 10 = 0.2 P ( e ) = 3 10 = 0.3 P({a})=\frac{项集{a}的支持度计数}{所有事务个数}=\frac{7}{10}=0.7\\ P({b})= \frac{8}{10}=0.8\\ P({c})=\frac{7}{10}=0.7\\ P({d})=\frac{2}{10}=0.2\\ P({e})=\frac{3}{10}=0.3 P(a)=所有事务个数项集a的支持度计数=107=0.7P(b)=108=0.8P(c)=107=0.7P(d)=102=0.2P(e)=103=0.3
这样就得到了事务中的1项集 C 1 C_{1} C1
(2)对 C 1 C_{1} C1中个项集的支持度与预先设定的最小支持度阈值进行比较,保留大于或等于该阈值的项,得1项频繁集 L 1 L_{1} L1
L 1 ⇒ L_{1}\Rightarrow L1⇒
1项频繁集 | 支持度 |
---|---|
{a} | 0.7 |
{b} | 0.8 |
{c} | 0.7 |
{d} | 0.2 |
{e} | 0.3 |
(3)扫描所有事务, L 1 L_{1} L1和 L 1 L_{1} L1连接的候选2项集 C 2 C_{2} C2,并计算每一项的支持度,如 P ( a , b ) = 项 集 a , b 的 支 持 度 计 数 所 有 事 务 个 数 = 5 10 = 0.5 P({a,b})= \frac{项集{a,b}的支持度计数}{所有事务个数}=\frac{5}{10}=0.5 P(a,b)=所有事务个数项集a,b的支持度计数=105=0.5。接下来就是剪枝步,由于 C 2 C_{2} C2的每个子集(即L1)都是频繁项集,所以没有项集冲 C 2 C_{2} C2中剔除。
(4)对 C 2 C_{2} C2中各项集的支持度与预先设定的最小支持度阈值进行比较,保留大于或者等于该阈值的项,得2项频繁集 L 2 L_{2} L2:
C 2 ⇒ C_{2}\Rightarrow C2⇒
项集 | 支持度 |
---|---|
{a,b} | 0.5 |
{a,c} | 0.5 |
{a,d} | 0.1 |
{a,e} | 0.3 |
{b,c} | 0.5 |
{b,d} | 0.2 |
{b,e} | 0.1 |
{c,d} | 0.1 |
{c,e} | 0.3 |
{d,e} | 0 |
根据阈值,得 L 2 ⇒ L_{2}\Rightarrow L2⇒
2项频繁集 | 支持度 |
---|---|
{a,b} | 0.5 |
{a,c} | 0.5 |
{a,e} | 0.3 |
{b,c} | 0.5 |
{b,d} | 0.2 |
{c,e} | 0.3 |
(5)扫描所有事务, L 2 L_{2} L2和 L 1 L_{1} L1连接的候选3项集 C 3 C_{3} C3,并计算每一项的支持度,如: P ( a , b , c ) = 项 集 a , b , c 的 支 持 度 个 数 所 有 事 务 个 数 = 3 10 = 0.3 P({a,b,c})=\frac{项集{a,b,c}的支持度个数}{所有事务个数}=\frac{3}{10}=0.3 P(a,b,c)=所有事务个数项集a,b,c的支持度个数=103=0.3。
接下来就是剪枝步, L 2 L_{2} L2与 L 1 L_{1} L1连接的所有项集为 { a , b , c } , { a , b , d } , { a , b , e } , { a , c , d } , { a , c , e } , { b , c , d } , { b , c , e } \{a,b,c\},\{a,b,d\},\{a,b,e\},\{a,c,d\},\{a,c,e\},\{b,c,d\},\{b,c,e\} {a,b,c},{a,b,d},{a,b,e},{a,c,d},{a,c,e},{b,c,d},{b,c,e},根据Apriori算法,频繁集的所有非空子集也必须是频繁集,因为 { b , d } , { b , d } , { b , e } , { c , d } \{b,d\},\{b,d\},\{b,e\},\{c,d\} {b,d},{b,d},{b,e},{c,d}不包含在b项频繁 L 2 L_{2} L2中,即不是频繁集,应剔除,最后的 C 3 C_{3} C3中的项集只有{a,b,c}和{a,c,e}
C 3 ⇒ C_{3}\Rightarrow C3⇒
项集 | 支持度 |
---|---|
{a,b,c} | 0.3 |
{a,c,e} | 0.3 |
(6)对 C 3 C_{3} C3中各项集的支持度与预先设定的最小支持度进行比较,保留大于等于该阈值的项,得3项频繁集 L 3 L_{3} L3
(7) L 3 L_{3} L3与 L 2 L_{2} L2连接得到候选4项集 C 4 C_{4} C4,易得剪枝后为空集。所以最后得到最大3项频繁集{a,b,c}和{a,c,e}。
过程二 由频繁集产生关联规则
C o n f i d e n c e ( A ⇒ B ) = P ( A ∣ B ) = S u p p o r t ( A ∩ B ) S u p p o r t ( A ) = S u p p o r t c o u n t ( A ∩ B ) S u p p o r t c o u n t ( A ) Confidence(A \Rightarrow B)=P(A|B)=\frac{Support(A\cap B)}{Support(A)}=\frac{Support_count(A\cap B)}{Support_count(A)} Confidence(A⇒B)=P(A∣B)=Support(A)Support(A∩B)=Supportcount(A)Supportcount(A∩B)
其中 S u p p o r t c o u n t ( A ∩ B ) Support_count(A\cap B) Supportcount(A∩B)是包含项集 A ∩ B A\cap B A∩B的事务数, S u p p o r t c o u n t ( A ) Support_count(A) Supportcount(A)是包含项集A的事务数,根据该公式,产生如下关联规则:
Rule | (Support,Confidence) |
---|---|
a->b | (50%,71.4286%) |
b->a | (50%62.5%) |
a->c | (50%,71.4286%) |
c->a | (30%,71.4286%) |
b->c | (50%,62.5%) |
c->b | (50%,71.4286%) |
e->a | (30%,100%) |
e->c | (30%,100%) |
a,b->c | (30%,60%) |
a,c->b | (30%,60%) |
b,c->a | (30%,60%) |
e->a,c | (30%,100%) |
a,c->e | (30%,60%) |
a,e->c | (30%,100%) |
c,e->a | (30%,100%) |
d->b | (20%,100%) |
**解释:**客户同时点菜品a和b的概率是50%,点了菜品a,再点菜品b的概率是71.4286%。其他以此类推。
Apriori算法的不足 :
1)交易数据库可能非常大,比如频集最多包含10个项,那么就需要扫描交易数据10遍。
2)需要很大的I/O负载
代码
# -*- coding: utf-8 -*-
# apriori算法的一个简单实现
from sys import exit, exc_info
from itertools import combinations
from collections import defaultdict
from time import clock
from optparse import OptionParser
def parse_arguments(parser):
'''
解析命令行给定的参数,运行apriori算法
'''
parser.add_option('-i', '--input', type='string', help='input file',
dest='input')
parser.add_option('-s', '--support', type='float', help='min support',
dest='support')
parser.add_option('-c', '--confidence', type='float',
help='min confidence', dest='confidence')
(options, args) = parser.parse_args()
if not options.input:
parser.error('Input filename not given')
if not options.support:
parser.error('Support not given')
if not options.confidence:
parser.error('Confidence not given')
return(options, args)
def get_transactions_from_file(file_name):
'''
读取文件,返回所有“购物篮”的结果
返回的格式是list,其中每个元素都是一个“购物篮”中的“商品”组成的frozenset
'''
try:
with open(file_name) as f:
content = f.readlines()
f.close()
except IOError as e:
print 'I/O error({0}): {1}'.format(e.errno, e.strerror)
exit()
except:
print 'Unexpected error: ', exc_info()[0]
exit()
transactions = []
for line in content:
transactions.append(frozenset(line.strip().split()))
return transactions
def print_results(itemsets_list, rules, transactions):
'''
输出结果
'''
for idx, itemsets in enumerate(itemsets_list):
if len(itemsets) == 0:
continue
print 'Itemsets of size', idx + 1
formatted_itemsets = []
for itemset, freq in itemsets.iteritems():
support = float(freq) / len(transactions)
formatted_itemsets.append((','.join(sorted(map(str, itemset))),
round(support, 3)))
sorted_itemsets = sorted(formatted_itemsets,
key=lambda tup: (-tup[1], tup[0]))
for itemset, support in sorted_itemsets:
print itemset, '{0:.3f}'.format(support)
print
print 'RULES'
formatted_rules = [(','.join(sorted(map(str, rule[0]))) + ' -> ' +
','.join(sorted(map(str, rule[1]))),
round(acc, 3))
for rule, acc in rules]
sorted_rules = sorted(formatted_rules, key=lambda tup: (-tup[1], tup[0]))
for rule, acc in sorted_rules:
print rule, '{0:.3f}'.format(acc)
def remove_itemsets_without_min_support(itemsets, min_sup, transactions):
'''
删除不满足最小支持度的itemsets
'''
for itemset, freq in itemsets.items():
if float(freq) / len(transactions) < min_sup:
del itemsets[itemset]
def generate_itemsets(itemsets_list, min_sup):
'''
给定1-项集,这个函数会通过和自己join生成itemsets,然后删除不满足最小支持度的itemsets
参数:
1)itemsets_list: 一个包含1-项集的list
2)min_sup:最小支持度
'''
try:
next_candidate_item_sets = self_join(itemsets_list[0])
except IndexError:
return
while(len(next_candidate_item_sets) != 0):
itemsets_list.append(defaultdict(int))
for idx, item_set in enumerate(next_candidate_item_sets):
for transaction in transactions:
if item_set.issubset(transaction):
itemsets_list[-1][item_set] += 1
remove_itemsets_without_min_support(itemsets_list[-1], min_sup,
transactions)
try:
next_candidate_item_sets = self_join(itemsets_list[-1])
except IndexError:
break
def build_k_minus_one_members_and_their_occurrences(itemsets, k):
k_minus_one_members_and_occurrences = defaultdict(list)
for itemset in itemsets:
# small cheat to make a list a hashable type
k_minus_one_members = ''.join(sorted(itemset)[:k - 1])
k_minus_one_members_and_occurrences[k_minus_one_members].\
append(itemset)
return k_minus_one_members_and_occurrences
def generate_itemsets_from_kmomo(kmomo):
new_itemsets = []
for k_minus_one_members, occurrences in kmomo.items():
if len(occurrences) < 2:
# delete those k_minus_one_members that have only one occurrence
del kmomo[k_minus_one_members]
else: # generate the new itemsets
for combination in combinations(occurrences, 2):
union = combination[0].union(combination[1])
new_itemsets.append(union)
return new_itemsets
def self_join(itemsets):
itemsets = itemsets.keys() # we are only interested on the itemsets
# themselves, not the frequencies
k = len(itemsets[0]) # length of the itemsets
# making sure all the itemsets have the same length
assert(all(len(itemset) == k for itemset in itemsets))
kmomo = build_k_minus_one_members_and_their_occurrences(itemsets, k)
return generate_itemsets_from_kmomo(kmomo)
def build_one_consequent_rules(itemset, freq, itemsets_list):
accurate_consequents = []
rules = []
for combination in combinations(itemset, 1):
consequent = frozenset(combination)
antecedent = itemset - consequent
ant_len_itemsets = itemsets_list[len(antecedent) - 1]
conf = float(freq) / ant_len_itemsets[antecedent]
if conf >= min_conf:
accurate_consequents.append(consequent)
rules.append(((antecedent, consequent), conf))
return accurate_consequents, rules
def build_n_plus_one_consequent_rules(itemset, freq, accurate_consequents,
itemsets_list):
rules = []
consequent_length = 2
while(len(accurate_consequents) != 0 and
consequent_length < len(itemset)):
new_accurate_consequents = []
for combination in combinations(accurate_consequents, 2):
consequent = frozenset.union(*combination)
if len(consequent) != consequent_length:
# combined itemsets must share n-1 common items
continue
antecedent = itemset - consequent
ant_len_itemsets = itemsets_list[len(antecedent) - 1]
conf = float(freq) / ant_len_itemsets[antecedent]
if conf >= min_conf:
new_accurate_consequents.append(consequent)
rules.append(((antecedent, consequent), conf))
accurate_consequents = new_accurate_consequents
consequent_length += 1
return rules
def generate_rules(itemsets, min_conf, itemsets_list):
'''
参数
1)itemsets: 用于生成规则的相同长度的itemsets
2)min_conf: 最小信任度
3)itemsets_list: 相同长度的itemsets组成的字典
返回结果
4)返回的规则: 规则是这样的格式为 [((antecedent, consequent), confidence), ...]
'''
rules = []
for itemset, freq in itemsets.iteritems():
accurate_consequents, new_rules = \
build_one_consequent_rules(itemset, freq, itemsets_list)
rules += new_rules
# 如果现在itemset大小已经小于1,直接continue
if len(itemset) <= 2:
continue
rules += build_n_plus_one_consequent_rules(itemset, freq,
accurate_consequents,
itemsets_list)
return list(set(rules))
if __name__ == '__main__':
usage_text = 'Usage: %prog -s minsup -c minconf [-a minatm]'
parser = OptionParser(usage=usage_text)
(options, args) = parse_arguments(parser)
min_sup = options.support
min_conf = options.confidence
t1 = clock()
transactions = get_transactions_from_file(options.input)
itemsets_list = [defaultdict(int)]
# 生成长度为1的itemsets
for transaction in transactions:
for item in transaction:
itemsets_list[0][frozenset([item])] += 1
remove_itemsets_without_min_support(itemsets_list[0], min_sup,
transactions)
# 生成长度>1的itemsets
generate_itemsets(itemsets_list, min_sup)
# 生成规则
rules = []
for itemsets in list(reversed(itemsets_list))[:-1]:
rules += generate_rules(itemsets, min_conf, itemsets_list)
t2 = clock()
print_results(itemsets_list, rules, transactions)
更多推荐
所有评论(0)