Association Rule Mining: Aprior

394 阅读7分钟

今天来说说在data mining中重要的关联规则。其实关联规则在我们生活中很常见,比如在超市中,我们经常会看见milk和bread这两个商品会摆在一起,这个其实就是关联规则的应用之一。实现关联规则中用到的最出名算法就是Aprior。 

首先我们先来引出一些相关的定义。 

item:指的是一个个的物品。 比如上面说的milk或者bread就是一个item。

U:是所有item的集合

itemset:指的是多个物品组成的集合,比如 I={milk,bread} 就是一个itemset。itemset就类似于我们平时在超市买东西时候的一个个购物篮里面的物品的集合。在例子中|I|=2,然后I就是一个2-itemset。

S:是一个大集合,集合里面的每个元素就是一个个的itemset,用T表示

那么itemset I 的支持度**support(I)**表示为:

                    support(I) = |{T ∈ S | I ⊆ T}|

接下来我们引出自信度。I1 和 I2 表示两个itemset集合,现在由一条关联规则 R:I1-->I2,表示由 I1 可以推出 I2,这里要求 I1 和 I2 都是非空集合,并且 I1 和 I2 的交集是空。

此时 support(R) = support(I1 ∪ I2)

那么 R 的自信度为:

例如:

U = {beer, bread, butter, milk, potato, onion}
S = { {beer, bread},
{beer, butter},
{butter, milk, potato},
{beer, bread, butter, milk, onion},
{beer, bread, butter, milk},
{beer, bread, milk, onion}  
}
I = {beer, bread}Then, 因为S中有四个元素是包含I的,所以 support(I) = 4
规则 “{beer} → {bread}” has support 4 and confidence 4/5. 
    “{beer} → {milk}” has support 3 and confidence 3/5. 
    “{butter,potato} → {milk}” has support 1 and confidence 1.

关联规则挖掘的目标就是:

Given (i) a set S of transactions, and
(ii) two constants minsup and minconf ,we want to find all the association rules R such that
sup(R) ≥ minsup                     
conf(R) ≥ minconf .

必须要两个条件都满足,比如 minsup=10,I1={A}, I2={B}, R = I1->I2
1) 如果只是满足sup(R),比如sup(I1)=99999,sup(I1∪I2)=11,sup(I1∪I2)>minsup=10,
   但规则R(I1->I2)是不可信的,因为即使sup(I1∪I2)>minsup,但sup(I1∪I2)的值只占sup(I1)很小的比值
2) 如果只是满足conf(R),比如有一个人买了很奇怪的组合{枪,鸡蛋},I1={枪},I2={鸡蛋},sup(I1)=1,sup(I1∪I2)=1,
   那么conf(R)=1,但是由枪推出鸡蛋这个规则是不可信的,因为只发生了一笔这样的订单

如果support(I)≥ minsup, 我们就说 I 是频繁集项。如果关联规则R( I1 → I2)是一个有意义的关联规则的前提是 itemset I1∪I2 必须是频繁集项。

定理1:

support(I1 ∪ I2) ≤ support(I1)  //反单调性质

推论1:

Suppose that I1 ⊆ I2.If I2 is frequent, then I1 must be frequent.
If I1 is not frequent, then I2 cannot be frequent.

for example: 
if {beer,bread} is frequent, then so must be {beer} and {bread},
Conversely, if {beer} is not frequent, then neither is {beer, bread}.

给定一个k值 k ∈ [1, n],用 Fk 表示所有的k-itemsets频繁集项. 那么给定集合的所有频繁集项等于: F1 ∪ F2 ∪...∪ Fn。 由推论1可知,如果Fi = ∅,那么任意的k≥i,Fk都是空集。

Apriori 算法主要有两步:

1. 频繁集项的计算,即求出所有满足条件的频繁集项
2. 产生规则,在1中找出的频繁集项中生成满足条件的关联ugi z

下面我们介绍一下frequent itemset(频繁集项)的计算,主要分为三步:

1. k = 1
2. find Fk. if Fk = ∅, terminate.
3. k←k+1;gotoLine2.

接下来我们详细说一下第二步:

Ck表示第k步的候选集合
situation 1: k = 1, finding F1
假设U有n个item,那么C1会有n个1-itemset,然后遍历C1计算支持度,找出满足条件的频繁集项

situation 2: k>1, finding Fk
首先要找出候选集合Ck,然后遍历Ck,找出满足条件的频繁集项
n个元素中找出k个,那么就有C(n,k)中组合,如果n很大的话,那么k组合的个数会非常庞大。下面我会介绍一种
求Ck并且保证个数尽量小。我们把U的item按照升序排列。我们用Fk-1来生成Ck
1. 每一个Fk-1排序,然后把前缀一样(前k-2个元素相等)的集合分成一个组
2. 在每一个组里面,循环遍历,对于组内每一个不同的itemset,例如:
{a1, a2, ..., ak−2, ak−1} and {a1, a2, ..., ak−2, ak }, 
把itemset{a1,a2,...,ak}加到Ck中。例如:
U = {beer, bread, butter, milk, potato, onion},minsup = 3S = {{beer, bread},{beer, bread},{butter, milk, potato},
     {beer, bread, butter, milk, onion},{beer, bread, butter, milk},
     {beer, bread, milk, onion} }
F1 = { {beer}, {bread}, {butter}, {milk}}.C2 = {{beer,bread},{beer,butter},{beer,milk},{bread,butter}, {bread,milk}, {butter,milk}}.F2 = {{beer,bread},{beer,butter},{beer,milk},{bread,milk}, {butter,milk}}.C3 = {{beer,bread,butter},{beer,bread,milk},{beer,butter,milk}}.
F3 = {{beer,bread,milk}}.C4 = ∅. Therefore, F4 = ∅.

此时我们已经找到了所有的频繁集项,接下来我们要讲的就是Apriori算法的第二步:在找出来的频繁集项中
生成关联规则。
原始方法:假设I是一个size>2的频繁集项,我们从I中产生关联规则的步骤是:
1.把I分成两个非空不重叠的子集I1,I2,并且I1 ∪ I2 = I while I1 ∩ I2 = ∅,此时I1 → I2是一个候选的关联规则
2.计算I1 → I2规则的自信度,自信度满足要求的就是满足条件的关联规则
这种方法有一个缺点就是当k很大是,会产生很多的候选关联规则(2的k次方减2)条,为此提出一种改进方法
改进方法:I1,I2 是I的一种非空不重叠划分 I1 ∪I2 = I. 类似的 I1′,I2′是另一种划分 I1′ ∪ I2′ = I. 我们有以下定理:如果 I1⊂I1,那么 conf(I1 →I2) ≤ conf(I1 →I2).

证明:conf (I1 → I2) = support(I)/support(I1) ≤ support(I)/support(I1′) = conf (I1′ → I2′).例如:I = {{beer,bread,milk}}. 那么一定有:
     conf({beer,bread} → {milk}) ≥ conf({beer} → {milk,bread}).

下面是我们的python代码实现,该代码只是实现了求频繁集项部分:

#!/usr/bin/env python
import sys
import time

filename = "data_b_test"
basket_list = []

f = open(filename)
line = f.readline()
while line:
    line_list = line.split()
    basket_list.append(list(map(int,line_list)))
    line = f.readline()
f.close()

basket_len = len(basket_list)


# whole_data_set是一个 list[list[item]], list[item]是一个basket
def create_c1(whole_data_set):
    c_1 = []
    for t in whole_data_set:
        for c1_item in t:
            item_list = [c1_item]
            if item_list not in c_1:
                c_1.append(item_list)
    return c_1


# 判断ck_item是否都是lk-1的子集组合成的
def is_apriori(ck_item, pre_lk):
    ck_item_set = set(ck_item)
    for temp_item in ck_item:
        sub_ck = ck_item_set - frozenset([temp_item])
        if sorted(sub_ck) not in pre_lk:
            return False
    return True


# 创建ck
def create_ck(pre_lk, k):
    c_k = []
    len_pre_lk = len(pre_lk)
    list_pre_lk = pre_lk
    for i1 in range(0, len_pre_lk-1):
        for j in range(i1 + 1, len_pre_lk):
            i_list = list(list_pre_lk[i1])
            j_list = list(list_pre_lk[j])
            if i_list[0:k - 2] == j_list[0:k - 2]:

                tem = frozenset(tuple(list_pre_lk[i1]))
                tem2 = frozenset(tuple(list_pre_lk[j]))
                # ck_item = list_pre_lk[i1] | list_pre_lk[j]
                ck_item_set = tem | tem2
                ck_item = sorted(ck_item_set)
                if is_apriori(ck_item, pre_lk):
                    c_k.append(ck_item)
            else:
                break
    return c_k


def generate_lk(dataset, ck_local, support):
    lk = []
    item_set_count = {}
    for basket in dataset:
        for item_set in ck_local:
            item_set = frozenset(item_set)
            if item_set.issubset(basket):
                item_set_count[item_set] = item_set_count.get(item_set, 0) + 1
    for item in item_set_count:
        if item_set_count[item] >= support:
            lk.append(sorted(list(item)))
    return sorted(lk)


# ck 和 lk 都是 set 里面装 set
main_support = 2
# item_line_dic = create_item_line_dic(basket_list)
print("\n")
# print("c1 start,time:\t" + str(time.asctime(time.localtime(time.time()))))
c1 = create_c1(basket_list)
print("ck end,i=1, ck len = " + str(len(c1)))
# print("l1 start,time:\t" + str(time.asctime(time.localtime(time.time()))))
l1 = generate_lk(basket_list, c1, main_support)
print("l1 end,i=1, len = " + str(len(l1)))
lk = l1
i = 2
total_set = set()
while True:
    print("\n")
    # print("ck start,i=" + str(i) + ",time:\t" + str(time.asctime(time.localtime(time.time()))))
    ck = create_ck(lk, i)
    print("ck end,i=" + str(i) + ", ck len = " + str(len(ck)))

    # print("lk start,i=" + str(i) + ",time:\t" + str(time.asctime(time.localtime(time.time()))))

    lk = generate_lk(basket_list, ck, main_support)

    # print("lk end,i=" + str(i) + ",time:\t" + str(time.asctime(time.localtime(time.time()))))
    print("lk end,i=" + str(i) + ", lk len = " + str(len(lk)))
    if len(lk) == 0:
        break
    i += 1

其中文件 data_b_test是csv文件,格式如下

25 52 164 240 274 328 368 448 538 561 630 687 730 775 825 834
39 120 124 205 401 581 704 814 825 834
35 249 674 712 733 759 854 950
39 422 449 704 825 857 895 937 954 964
15 229 262 283 294 352 381 708 738 766 853 883 966 978
26 104 143 320 569 620 798
7 185 214 350 529 658 682 782 809 849 883 947 970 979
120 177 579 593 648 745 862 895 934
71 192 208 272 279 280 300 333 496 529 530 597 618 674 675 720 855 914 932
183 193 217 256 276 277 374 474 483 496 512 529 626 653 706 878 939
161 175 177 424 490 571 597 623 766 795 853 910 960
125 130 327 698 699 839