151220129 计科 吴政亿 [email protected]
数据以以下格式存储与csv中,是一家超市的每一个顾客每次购买的物品记录。
id {item1, item2, ... , itemn}
数据以以下格式存储,从USER0~USER8,分别记录了9位用户登陆UNIX的每一次会话与他们的命令,由**SOF**开始,**EOF**结束。
**SOF**
命令1
命令2
.。。
命令n
**EOF**
在Apriori原理前,有两个先验原理:
可见其每一次不断的进行联结操作产生候选,并应用先验知识进行剪枝,从而寻找到频繁项集。他每一次应用”K-1项集”寻找”K项集”,将整个代码划分为连接步与剪枝步
这里给出两个核心的主函数。
输入处理过的数据、最小支持度与最小置信度,输出频繁项集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
根据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
FP-Growth通过使用FP树,通过模式增长挖掘频繁项集。流程如下:
首先定义一个节点数的类
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树结构 |
对于给定的数据,应用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
暴风(Brute Force) 算法是普通的模式匹配算法,BF算法的思想就是将目标串 S 的第一个字符与模式串 T 的第一个字符进行匹配,若相等,则继续比较 S 的第二个字符和 T 的第二个字符;若不相等,则比较 S 的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
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)
对于Groceries.csv
与sanitized_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)
支持度 | 置信度 | 频繁项集数 | 耗时 | 内存 |
---|---|---|---|---|
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 |
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 |
支持度 | 频繁项集数 | 耗时 | 内存 |
---|---|---|---|
0.01 | 333 | 1.411s | 75.8MiB |
0.05 | 31 | 0.062s | 47.4MiB |
0.1 | 8 | 0.390s | 20.0MiB |
由于应用了递归,导致内存占用非常的大,昂贵的空间代价
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 |
应用Brute-Force算法,由于方法太过朴素,数据集过大,两个数据集都无法得到数据,程序一运行就会进入假死状态,故时间和空间代价超出电脑性能……无法估计代价。
另外,我将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内存,所以二者内存占用相同。
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
看起来买酸奶的人顺手也会买牛奶,所以放在一起卖会好很多。
另外买蔬菜的人也有很大概率会买牛奶,大概是因为他们都是生活必需的消耗品?
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非常的白搭:
另外,还有一些其他的规律
对于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所花费的空间较大。