数据挖掘——关联规则算法之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 TI)。每一个事务具有唯一的事务标识TID。设A是一个由项目构成的集合,称为项集。事务T包含项集A,当且仅当 A ⊑ T A\sqsubseteq T AT。如果项集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 XY的逻辑蕴含式,其中 X ⊏ I , Y ⊏ I , 且 X ∩ Y ≠ ∅ X\sqsubset I,Y\sqsubset I,且X \cap Y \neq \varnothing XI,YI,XY=
如果事务数据库D中有s%的事务包含 X ∪ Y X\cup Y XY,则称关联规则 X ⇒ Y X\Rightarrow Y XY的支持度为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(XY)/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(XY)=P(XY)confidence(XY)=P(YX)P(X)P(X,Y)

说的太官方,看的云里雾里。那么简单地来表达:在进行关联规则挖掘之前要找到一个东西,这个东西就是频繁项集,根据这个频繁项集找到哪些商品出现的次数多,在那些次数大于阈值的项集上再去进行有意义的挖掘人物。看上面的公式可知,支持度表示的是两种商品同时出现的概率。 信任度时用来产出规则的,用来评价X对Y的影响大,还是Y对X的影响大。

二、强关联规则

强关联规则就是支持度和信任度分别满足用户给定阈值的规则。
------------------------------------------------------------------------------------
举例

交易ID购买的商品
2000A,B,C
1000A,C
4000A,D
5000B,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)) AC(50%,66.6%(2/3))CA(50%,100%(2/2))
那么就可以说,在满足最小支持度的基础上,一位顾客购买A再购买C的可能是66.6%,而购买C再购买A的可能是100%。

三、关联规则挖掘算法

这里仅列出,本文未一一详述,只介绍Apriori算法,如有可能会在本人的其他博客中再详细说明。

  • AIS、Apriori 和AprioriTid
  • SETM
  • DHP
  • PARTITION
  • FPGrowth

其中,最有效和有影响的算法已用加粗字体标出。

下面开始说Apriori算法

Apriori算法将发现关联规则的过程分为两个步骤:

  1. 通过迭代,检索出事务数据库中的所有频繁项集,即支持度不低于用户设定的阈值的项集。在这个过程中连接步和剪枝步互相融合,最终得到最大频繁项集 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菜品名称
12014/8/2110118491
22014/8/211018693啦啦
32014/8/211018705啦啦啦
42014/8/211028842饿
52014/8/211027794饿饿
62014/8/211038842饿饿饿饿
72014/8/211038693

将上表的事务数据整理成关联规则模型所需的数据结构,从中抽取10个点餐订单最为事务数据库,设支持度为0.2(10个数据,那么支持度计数为2),为方便起见将菜品{18491,8842,8693,7794,8705}分别简记为{a,b,c,d,e},见下表:

订单号菜品id菜品id
118491,8693,8705a,c,e
28842,7794b,d
38842,8693b,c
418491,8842,8693,7794a,b,c,d
518491,8842a,b
68842,8693b,c
718491,8842a,b
818491,8842,8693,8705a,b,c,e
918491,8842,8693a,b,c
1018491,8693,8705a,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(AB)=P(AB)=Support(A)Support(AB)=Supportcount(A)Supportcount(AB)

其中 S u p p o r t c o u n t ( A ∩ B ) Support_count(A\cap B) Supportcount(AB)是包含项集 A ∩ B A\cap B AB的事务数, 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)

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值