这两天进行了fp-growth的学习,这块知识确实很难理解,书上只是搪塞了这一块的细节,并且作者还有一个疏忽,导致一个很大的错误出现,这在后面会提到。这让读者很是费解,网上的资料或者博客也并没有介绍实现的细节,大多复制粘贴,这两天一直在研究这个算法,这篇文章可能写的不是很清楚,但可能是网上目前介绍fp-growth思想的最好的文章了,如果看了其他fp-growth的介绍没有看懂可以看下,建议之前有对fp-growth的简单理解,否则可能看不懂,表达能力太差了。
fp-growth算法是韩佳炜在2000年提出的频繁项集挖掘算法,前面我们介绍了Apriori挖掘频繁项集并且进行关联分析,这次的fp-growth和Apriori选择频繁集有点类似的地方,但是本质和Apriori完全不一样。
先写一下核心思想,再写一下实现的细节,最后有一个新闻挖掘的小实例。
fp-growth的核心思想:
假设我们的找出了频繁集,我们采取一种将频繁集排序的方法,把频繁集中出现最少的放在最后一位,按照最后一位划分多个小的频繁集合,因为这样按照最后一位划分的几个频繁集合因为最后一位不同,没有重合,互不冲突。这样在每一次划分频繁集的时候会把频繁集分为不重合的几个集合,并且可以在最后一项或几项为后缀的fp-tree上递归进行上述的步骤,直到遇到停止条件。
停止条件1:条件模式基组成的fp-tree都不频繁,头指针表头headertable为None。
停止条件2:条件模式基没有元素。
通俗解释:和分班级一样,1班和2班因为班级不同永远不会冲突,然后我们在班级里在进行类似于分班级的分组,就是上述中的递归,在分组中加入班级号,就是上述中的以前几项为后缀,递归进行,直到没有进行进一步分组的条件,即停止条件。
以x为结尾的频繁项集为(...x),(.x),(.....x)等等,我们x前面的频繁项集通过条件模式基寻找,x前的元素(1:比x支持率高。2:频繁)。
FPGrowth是一种比Apriori更高效的频繁项挖掘方法,它只需要扫描项目表2次。其中第1次扫描获得当个项目的频率,去掉不符合支持度要求的项,并对剩下的项排序。第2遍扫描是建立一颗FP-Tree,后面递归还需要对每个小的项目表扫描但是规模小忽略不计,弥补了Apriori的缺点。
fp-grow的细节实现:
step1:
读取简单数据并且格式化:
数据为书中的简单数据:
#创建简单数据集 def loadDataSet(): data = [['r', 'z', 'h', 'j', 'p'], ['z', 'y', 'x', 'w', 'v', 'u', 't', 's'], ['z'], ['r', 'x', 'n', 'o', 's'], ['y', 'r', 'x', 'z', 'q', 't', 'p'], ['y', 'z', 'x', 'e', 'q', 's', 't', 'm']] return data #整理数据集格式 def initDataSet(dataset): res = {} for line in dataset: res[frozenset(line)] = 1 return res
定义树的节点:
#FP-tree的定义 class treeNode: def __init__(self, namevalue, countvalue, pnode): self.name = namevalue self.count = countvalue self.parent = pnode self.children = {} self.nextlink = None def inc(self, number): self.count += number def show(self, ind = 1): print ' ' * ind, self.name, ' ', self.count for i in self.children.values(): i.show(ind + 1)
创建fp-tree:
里面作者有个错误,在注释中。
#创建fp-tree def createTree(data, minsupport = 1): headertable = {} for line in data: for item in line: headertable[item] = headertable.get(item, 0) + data[line] for i in headertable.keys(): if headertable[i] < minsupport: del(headertable[i]) freqset = set(headertable.keys()) #如果没有频繁项集返回None if len(freqset) == 0: return None, None #初始化头指针表 for k in headertable.keys(): headertable[k] = [headertable[k], None] tree = treeNode('None set', 1, None) for line, count in data.items(): temp = {} for i in line: if i in freqset: temp[i] = headertable[i][0] newline = [v[0] for v in sorted(temp.items(), key = operator.itemgetter(1), \ reverse = True)] #书中疏忽了这一点,应判断一下newline是否为空 if len(newline) > 0: updateTree(tree, headertable, newline, count) return tree, headertable创建完的树为:
step4:
递归更新树的信息,递归更新headertable头指针表:
#更新树的信息 def updateTree(tree, headertable, newline, count): if newline[0] in tree.children: tree.children[newline[0]].inc(count) else: tempnode = treeNode(newline[0], count, tree) tree.children[newline[0]] = tempnode if headertable[newline[0]][1] == None: headertable[newline[0]][1] = tempnode else: updateHeaderTable(headertable, tempnode, newline[0]) if len(newline) > 1: updateTree(tree.children[newline[0]], headertable, newline[1 : ], count) #更新头指针信息 def updateHeaderTable(headertable, n, x): t = headertable[x][1] while (t.nextlink != None): t = t.nextlink t.nextlink = n
得到条件模式基:
#沿父节点回溯得到条件模式基 def getParent(tnode, pathlist): if tnode.parent != None: pathlist.append(tnode.name) getParent(tnode.parent, pathlist) #查找条件模式基(所有以该条件为结尾的所有路径) def findParent(x, tempnode): road = {} while tempnode != None: pathlist = [] getParent(tempnode, pathlist) if len(pathlist) > 1: road[frozenset(pathlist[1 : ])] = tempnode.count tempnode = tempnode.nextlink return roadstep6:
fp-tree算法挖掘频繁项集:
prefreq为前面的频繁集后缀,freqlist为保存的所有频繁项集。
#fp-growth算法发现频繁项集 def findFreqList(tree, headertable, minsupport, prefreq, freqlist): headerlist = [v[0] for v in sorted(headertable.items(),\ key = operator.itemgetter(1))] for i in headerlist: newfreq = prefreq.copy() newfreq.add(i) freqlist.append(newfreq) #条件模式基 parentroad = findParent(i, headertable[i][1]) newtree, newhead = createTree(parentroad, minsupport) if newhead != None: findFreqList(newtree, newhead, minsupport, newfreq, freqlist)挖掘后的频繁项集为:
和我们预期的频繁项集一致。
接下来我们看一个新闻挖掘的小应用,我们有个‘kosarak.dat’保存了每个读者都的新闻编号,我们想看一下那些新闻或者新闻集合读者喜欢读,新闻或者新闻集合即为频繁项集。文件有几百万条记录,进行fp-growth算法进行分析,我们省略了将数据得到的tree和headertable步骤,我们认为读取100000以上的新闻为被人们喜欢的新闻。
效果如下:
学完了fp-growth算法,非监督学习也告一段落了,接下来几章为相关工具降维的相关算法,推荐系统和大数据方面的应用。加油!