【数据挖掘】作业二

数据挖掘 作业二

151220129 计科 吴政亿 [email protected]

1 实验介绍

1.1 实验要求

  1. 应用数据挖掘相关知识,对给定的两个数据集寻找频繁项集与关联规则
  2. 通过改变置信度与支持度,比较Apriori、FP-Growth和暴力搜索挖掘频繁项集,在生成的频繁项集,挖掘规则时所用内存和以秒为单位的消耗时间代价进行比较。
  3. 应用Apriori或FP-Growth发现一些有趣的关联规则,并讨论这些规则内在的逻辑。

1.2 实验数据

1.2.1 GroceryStore

数据以以下格式存储与csv中,是一家超市的每一个顾客每次购买的物品记录。

id  {item1, item2, ... , itemn}

1.2.2 UNIX

数据以以下格式存储,从USER0~USER8,分别记录了9位用户登陆UNIX的每一次会话与他们的命令,由**SOF**开始,**EOF**结束。

**SOF**
命令1
命令2
.。。
命令n
**EOF**

2 实验方法

2.1 Apriori

2.1.1 算法原理

在Apriori原理前,有两个先验原理:

  1. 如果一个集合是频繁项集,则它的所有子集都是频繁项集。
  2. 如果一个集合不是频繁项集,则它的所有超集都不是频繁项集。

可见其每一次不断的进行联结操作产生候选,并应用先验知识进行剪枝,从而寻找到频繁项集。他每一次应用”K-1项集”寻找”K项集”,将整个代码划分为连接步与剪枝步

2.1.2 代码实现

这里给出两个核心的主函数。

runApriori(data_iter, minSupport, minConfidence)

输入处理过的数据、最小支持度与最小置信度,输出频繁项集items与关联规则rules

# Apriori主函数,对于给定的最小支持度与最小置信度,输出频繁项集items与关联规则rules
def runApriori(data_iter, minSupport, minConfidence):
    itemSet, transactionList = getItemSetTransactionList(data_iter)
    freqSet = defaultdict(int)
    largeSet = dict() # 用于存储频繁项集,其中key为itemsets,value为支持度
    tempSet = returnItemsWithMinSupport(itemSet, transactionList, minSupport, freqSet)
    nowSet = tempSet
    k = 2
    while(nowSet != set([])):
        largeSet[k-1] = nowSet
        nowSet = joinSet(nowSet, k)
        tempSet = returnItemsWithMinSupport(nowSet,transactionList,minSupport,freqSet)
        nowSet = tempSet
        k = k + 1

    def getSupport(item):
        """local function which Returns the support of an item"""
        return float(freqSet[item])/len(transactionList)

    tempItems = []
    for key, value in allSet.items():
        tempItems.extend([(tuple(item), getSupport(item)) for item in value])

    tempRules = []
    for key, value in allSet.items()[1:]:
        for item in value:
            _subsets = map(frozenset, [x for x in subsets(item)])
            for element in _subsets:
                remain = item.difference(element)
                if len(remain) > 0:
                    confidence = getSupport(item)/getSupport(element)
                    if confidence >= minConfidence:
                        tempRules.append(((tuple(element), tuple(remain)),
                                           confidence))
    return tempItems, tempRules
returnItemsWithMinSupport(itemSet, transactionList, minSupport, freqSet)

根据minSupport,计算itemSet中的所有物品的支持度,并返回一个他的满足最小支持度的子集

# 根据minSupport,计算itemSet中的所有物品的支持度,并返回一个他的满足最小支持度的子集
def returnItemsWithMinSupport(itemSet, transactionList, minSupport, freqSet):
    _item = set()
    localSet = defaultdict(int)

    for item in _item:
        for tran in tranList:
            if item.issubset(tran):
                freqSet[item] += 1
                localSet[item] += 1

    for item, count in localSet.items():
        support = float(count)/len(tranList)
        if support >= minSupport:
                _item.add(item)

    return _item

2.2 FP-Growth

2.2.1 算法原理

FP-Growth通过使用FP树,通过模式增长挖掘频繁项集。流程如下:

  1. 先扫描一遍数据集,得到频繁项为1的项目集,定义最小支持度(项目出现最少次数),删除那些小于最小支持度的项目,然后将原始数据集中的条目按项目集中降序进行排列。
  2. 第二次扫描,创建项头表(从上往下降序),以及FP树。
  3. 对于每个项目(可以按照从下往上的顺序)找到其条件模式基(CPB,conditional patten base),递归调用树结构,删除小于最小支持度的项。如果最终呈现单一路径的树结构,则直接列举所有组合;非单一路径的则继续调用树结构,直到形成单一路径即可。

2.2.2 代码实现

class FPNode(object)

首先定义一个节点数的类

class FPNode(object):
    def __init__(self, tree, item, count=1):
        self._tree = tree
        self._item = item
        self._count = count
        self._parent = None
        self._children = {}
        self._neighbor = None

    def add(self, child):
        if not isinstance(child, FPNode):
            raise TypeError("Can only add other FPNodes as children")

        if not child.item in self._children:
            self._children[child.item] = child
            child.parent = self

    def search(self, item):
        try:
            return self._children[item]
        except KeyError:
            return None 

    def inspect(self, depth=0):
        print ('  ' * depth) + repr(self)
        for child in self.children:
            child.inspect(depth + 1)
Name Information
_tree 树节点
_item 项集
_count 出现的次数
_parent 母亲节点
_children 子节点
_neighbor 邻居节点
add(self, child) 为当前节点添加一个子节点
search(self, item) 检查当前节点是否包含项集item,有则返回,无则返回null
inspect(self, depth=0) 输出节点和子节点的FP树结构
find_frequent_itemsets(transactions, minimum_support, include_support=False)

对于给定的数据,应用FP-Growth寻找频繁项集

def find_frequent_itemsets(transactions, minimum_support, include_support=False):
    items = defaultdict(lambda: 0) 

    # 导入数据与支持度
    for transaction in transactions:
        for item in transaction:
            items[item] += 1

    # 移除低于最小支持度的项集
    items = dict((item, support) for item, support in items.iteritems()
        if support >= minimum_support)

    # 对所有项集进行排序(按照支持度),并构造FP树
    def clean_transaction(transaction):
        transaction = filter(lambda v: v in items, transaction)
        transaction.sort(key=lambda v: items[v], reverse=True)
        return transaction

    master = FPTree()
    for transaction in imap(clean_transaction, transactions):
        master.add(transaction)

    def find_with_suffix(tree, suffix):
        for item, nodes in tree.items():
            support = sum(n.count for n in nodes)
            if support >= minimum_support and item not in suffix:
                # 如果找到新的频繁项集
                found_set = [item] + suffix
                yield (found_set, support) if include_support else found_set

                # 建立一个候选树,并递归的寻找频繁项集
                cond_tree = conditional_tree_from_paths(tree.prefix_paths(item))
                for s in find_with_suffix(cond_tree, found_set):
                    yield s

    # 搜索频繁项集并返回
    for itemset in find_with_suffix(master, []):
        yield itemset

2.3 Brute Force

2.3.1 算法原理

暴风(Brute Force) 算法是普通的模式匹配算法,BF算法的思想就是将目标串 S 的第一个字符与模式串 T 的第一个字符进行匹配,若相等,则继续比较 S 的第二个字符和 T 的第二个字符;若不相等,则比较 S 的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。

2.3.2 代码实现

start = datetime.datetime.now()
dataSet = loadData()
minSupportRate = 0.01
minSupport = minSupportRate * len(dataSet)
maxItemLength = max([len(each) for each in dataSet])
allItems = []
for i in dataSet:
    for j in i:
        if not j in allItems:
            allItems.append(j)
dic = {}
for i in range(maxItemLength):
    nr = i + 1
    for each in combinations(allItems, nr):
        if not each in dic.keys():
            dic[each] = 1
        else:
            dic[each] += 1
for each in list(filter(lambda x: dic[x] > minSupport, dic.keys())):
    print(each, dic[each])
end = datetime.datetime.now()
print((end - start).seconds)

3 实验环境

3.1 数据处理

对于Groceries.csvsanitized_all.981115184025,我应用python进行处理将他们处理为如下格式,便于进行读取。

特别的,我将第二个数据进行了清理,将正则匹配<[0-9]+>的部分删除,并把重复的命令删除了。

citrus fruit,semi-finished bread,margarine,ready soups
tropical fruit,yogurt,coffee
whole milk
pip fruit,yogurt,cream cheese ,meat spreads
other vegetables,whole milk,condensed milk,long life bakery product
whole milk,butter,yogurt,rice,abrasive cleaner
whoami,pwd,ls,dir,vi,source,exit
whereis,mkdir,vi,ls,source
elm,log
elm,man,fg,finger,|,more,finger,fg
elm,logout
elm,cd,ls,vi,fg,exit
elm,logout
elm,exit
elm,log

其中,处理的部分python代码如下:

with open('sanitized_all.981115184025', 'r') as f:
    with open('data.csv', 'w') as g:
        lines = f.readlines()
        # w = []
        # print(lines(0))
        begin = False
        for l in lines:
            t = l.strip('\n')
            # g.write(l)
            # g.write(',')
            if t == "**SOF**":
                begin = True
                continue
            elif t == "**EOF**":
                g.write('\n')
            else:
                if begin:
                    g.write(t)
                    begin = False
                else:
                    g.write(',')
                    g.write(t)

4 实验结果

4.1 Apriori算法

4.1.1 Groceries

支持度 置信度 频繁项集数 耗时 内存
0.01 0.05 333 7.032s 24.676MiB
0.01 0.1 333 7.320s 26.496MiB
0.05 0.08 31 0.609S 22.559MiB
0.05 0.1 31 0.594s 22.602MiB
0.1 0.1 8 0.250s 22.324MiB

4.1.2 UNIX

USER 耗时 内存
0 0.110s 19.914MiB
1 0.765s 31.215MiB
2 0.228s 18.324MiB
3 0.264s 54.000MiB
4 1.233s 33.992MiB
5 1.762s 154.660MiB
6 0.578s 21.316MiB
7 0.109s 17.547MiB
8 1.410s 16.230MiB

4.2 FP-Growth算法

4.2.1 Groceries

支持度 频繁项集数 耗时 内存
0.01 333 1.411s 75.8MiB
0.05 31 0.062s 47.4MiB
0.1 8 0.390s 20.0MiB

4.2.2 UNIX

由于应用了递归,导致内存占用非常的大,昂贵的空间代价

USER 耗时 内存
0 0.014s 38.6MiB
1 0.075s 45.5MiB
2 0.023s 43.6MiB
3 0.162s 48.4MiB
4 0.210s 93.0MiB
5 0.256s 91.9MiB
6 0.152s 91.6MiB
7 0.092s 39.5MiB
8 0.143s 90.9MiB

4.3 Brute-Force

应用Brute-Force算法,由于方法太过朴素,数据集过大,两个数据集都无法得到数据,程序一运行就会进入假死状态,故时间和空间代价超出电脑性能……无法估计代价。

4.4 Weka应用

另外,我将USER0~USER8的数据合并,并且用Weka进行了数据挖掘,并进行了对比统计,对于Groceries的数据,由于数据集较大,必须用稀疏矩阵存储,但是Weka的arff格式在我读取后将false也作为了频繁项集,我尝试了由@attribute name {true,false}@attribute name {true,false}都无法进行购物篮分析。由于结果错误,我将错误示例放在了最后的总结中,不在此列出。

算法 支持度 时间 内存
Apriori 0.1 28s 300M
FP-Growth 0.1 0.77s 300M

可见二者的时间差很大,但是内存应该是因为软件本身就申请了300M内存,所以二者内存占用相同。

5 关联规则

5.1 Groceries

5.1.1 Support:0.05 Confidence:0.1
Rule: ('whole milk',) ==> ('yogurt',) , 0.219
Rule: ('whole milk',) ==> ('rolls/buns',) , 0.222
Rule: ('whole milk',) ==> ('other vegetables',) , 0.293
Rule: ('rolls/buns',) ==> ('whole milk',) , 0.308
Rule: ('other vegetables',) ==> ('whole milk',) , 0.387
Rule: ('yogurt',) ==> ('whole milk',) , 0.402

看起来买酸奶的人顺手也会买牛奶,所以放在一起卖会好很多。

另外买蔬菜的人也有很大概率会买牛奶,大概是因为他们都是生活必需的消耗品?

5.2 UNIX

5.2.1 Support:0.1 Confidence:0.1 (将9位用户混合后得到的普适性规律,来自weka)
FPGrowth found 30 rules (displaying top 10)

 1. [ll=TRUE]: 1115 ==> [cd=TRUE]: 1051   0.94)> lift:(2.33) lev:(0.07) conv:(10.21) 
 2. [ls=TRUE, vi=TRUE]: 1334 ==> [cd=TRUE]: 1225   0.92)> lift:(2.27) lev:(0.08) conv:(7.22) 
 3. [ls=TRUE, rm=TRUE]: 1056 ==> [cd=TRUE]: 932   0.88)> lift:(2.18) lev:(0.06) conv:(5.03) 
 4. [vi=TRUE]: 1946 ==> [cd=TRUE]: 1710   0.88)> lift:(2.17) lev:(0.1) conv:(4.89) 
 5. [rm=TRUE]: 1440 ==> [cd=TRUE]: 1260   0.88)> lift:(2.16) lev:(0.07) conv:(4.74) 
 6. [ls=TRUE]: 2826 ==> [cd=TRUE]: 2348   0.83)> lift:(2.05) lev:(0.13) conv:(3.51) 
 7. [cd=TRUE, rm=TRUE]: 1260 ==> [ls=TRUE]: 932   0.74)> lift:(2.38) lev:(0.06) conv:(2.64) 
 8. [rm=TRUE]: 1440 ==> [ls=TRUE]: 1056   0.73)> lift:(2.36) lev:(0.07) conv:(2.58) 
 9. [cd=TRUE, vi=TRUE]: 1710 ==> [ls=TRUE]: 1225   0.72)> lift:(2.3) lev:(0.08) conv:(2.42) 
10. [vi=TRUE]: 1946 ==> [ls=TRUE]: 1334   0.69)> lift:(2.2) lev:(0.08) conv:(2.19) 

从关联规则看,ls/ll非常的白搭:

  1. 为了看目录,然后使用cd进入
  2. 看文件名,然后调用vi编辑器进行编辑
  3. 看文件名,然后调用rm删了

另外,还有一些其他的规律

  1. vi编辑完文件后,cd回退到其他目录
  2. rm删除完文件后,ls看一下删干净没有,再cd回退上级目录(猜的)
  3. ls看完目录下文件后,执行cd进入下一目录,然后再一次的ls

5 实验总结

对于Groceries的数据,由于数据集较大,必须用稀疏矩阵存储,但是Weka的arff格式在我读取后将false也作为了频繁项集,我尝试了由@attribute name {true,false}@attribute name {true,false}都无法进行购物篮分析,错误示例如下:

对于Brute-Force算法,由于其计算量与内存占用过大,其项集数目随着k以指数增长,因此程序会进入龟速假死状态。

在Apriori与FP-Growth的比较中,我发现后者的速度在数量级大的时候,是远远快于前者的,但是在数量级较小的时候,二者时间接近。在内存占用方面,FP-Growth表现的远不如Apriori,大量具有重复性的精准良好结果均表明,递归创建树是内存占用 的最大原因。

Apriori算法在每次生成候选集时都需要遍历一次频繁项集中的内容,在不断递归的过程中需要花不少时间,而FP-growth算法虽然也要每次递归,但由于先建立了FP-tree,每次在生成候选集时只要从根节点开始遍历即可,节省了很多遍历不符合条件的候选集的时间,因此FP-growth算法在最小支持度和最小置信度变化的情况下运行时间不会受到太大影响,而Apriori算法则受影响很大。

结论:Apriori算法空间代价较小,但效率较低且波动明显;FP-growth算法效率高,但建立FP-tree所花费的空间较大。

参考

  1. https://github.com/asaini/Apriori
  2. https://github.com/enaeseth/python-fp-growth
  3. http://cs.nju.edu.cn/lim/courses/IntroDM/IntroDM.htm Part4

你可能感兴趣的:(数据挖掘)