最好走的路越走越难,最难走的路越走越容易

Follow guwendong on Web
  • Subscribe to Beyond Search via RSS
  • Join Resys Google Group
  • Follow @clickstone on Douban
  • Follow @clickstone on Twitter

Tag Archives: association-rule

啤酒和尿布的故事

Resys Group 里的这个讨论而起,又有朋友找我问起了啤酒和尿布的故事~

Long long ago,有这么一个故事。
在一家超市里,有一个有趣的现象:尿布和啤酒赫然摆在一起出售。但是这个奇怪的举措却使尿布和啤酒的销量双双增加了。这不是一个笑话,而是发生在美国沃尔玛连锁店超市的真实案例,并一直为商家所津津乐道。原来,美国的妇女们经常会嘱咐她们的丈夫下班以后要为孩子买尿布,而丈夫在买完尿布之后又要顺手买回自己爱喝的啤酒。

第一次听到这个故事,是在研一的数据挖掘课程上。当时导师讲完之后,我感觉这是个非常神奇的事情。

这之前我最崇拜的是一位名叫泰勒的大哥,此人左手一个小本,右手一支铅笔,脖子上挂着个秒表,没日没夜地,站在那里观察啊,观察啊,观察啊……观察什么啊?挖煤。对,确实是挖煤。就是这个办法,他凭借一己之力,开创了对工业界,尤其是对日本工业界影响深远的时间动作研究,被后人尊称为工业工程之父。

仰望着泰勒,我想,我要是也用泰勒大师的办法,是不是也能搞一个购物动作研究,成为超级市场之父呢?我为自己的灵光乍现而欢呼雀跃,I fucking couldn’t be happier!可转念又一想,那我得写秃多少支铅笔,按坏多少个秒表啊。我没有风险投资,这事儿干不了。

下课回到宿舍,我是埋头狂啃了几天关联规则算法。在自认为得道成仙之后,我便开始四处招摇,逢人便问。
我:你认识“啤酒兄”吗?
~摇头~
我:哦,不认识。
我:那你认识“尿布兄”吗?
~继续摇头~
我:什么,这个也不认识!
我:那算了,你还是回火星去吧。
时间长了,问得多了,我才弄明白,原来“啤酒兄”和“尿布兄”才是真正的火星人,在时间回旋里面,一时半会儿到不了地球,很不靠谱。

现在这事儿靠谱了,都有人专门为这两位仁兄著书立传了。等地球人都认识了他们,数据挖掘从业者的春天就真来了。

其实当年在课上,导师已经和我们说了,这个故事多半是有一些些杜撰的成分在里面的,并且这个故事其实是有多个版本的。但数据挖掘技术需要发展,需要进入业界,需要产业化,就必须有一个简单易懂的故事。就好像一提到进行诚实教育,大家自然就会想到“狼来了”,它通俗、易懂、好接受、容易记忆。故事不一定真实,但结论足够说明问题。

我有一位朋友,01 年本科毕业去了 IBM。在他给 IBM 的求职信上,有一段话让我印象深刻。原话记不住了,大约是这样:
“我喜欢写程序。
当我的同学们在浩渺的星际争霸战场上鏖战,或者围坐在饭岛爱老师身边激昂人生的时候,我却孤独地在 MFC 中深入浅出,每一行优雅的代码,都仿佛美丽的音符一般,让我深陷其中无法自拔。
学习就像太空冒险,越是深入,越能体会到他的博大精深。”

让我们共勉。

 

推荐系统:关联规则(3) —— FP-Growth 算法

在 1994 年 Rakesh Agrawal 提出了 Apriori 算法之后,关联规则挖掘技术的可用性得到了很大的提高。而且因为关联规则挖掘与生俱来的商业意义,使得它迅速成为了一个非常热门的研究领域,新的算法也不断地涌现出来。这其中,实用性比较强的一个算法,是由韩家玮教授提出的 FP-Growth 算法。FP-Growth 算法在 2000 年发表的这个 paper 《Mining Frequent Patterns without Candidate Generation》里有详细的介绍。读这篇 paper,我个人建议一定要同时把引文也都看一看,2000 年之前与关联规则挖掘相关的重要 paper,基本上都在里面了。

FP-Growth 算法的核心是 FP-Tree(Frequent Pattern Tree,频繁模式树)的构建,这个特殊的数据结构,是 FP-Growth 算法与 Apriori 算法相比,性能显著提高的原因所在。不过,仔细分析一下 FP-Tree 的实现,可以发现它与字符串处理算法中常用的 Prefix Tree 算法,有着异曲同工之妙。FP-Tree 通过合并一些重复路径,实现了数据的压缩,从而使得将频繁项集加载到内存中成为可能。之后以树遍历的操作,替代了 Apriori 算法中最耗费时间的事务记录遍历,从而大大提高了运算效率。详细的理论讲解可以阅读上面的论文,我这里还是把其中的例子翻译一下。

某数据库 DB 里有 5 条事务记录,取最小支持度(min support threshold)为 3,则生成 FP-Tree 的过程如下:

1、扫描一遍数据库,获取所有频繁项,删除频率小于最小支持度的项。在此操作的过程中,还可以得到每个项的出现频率,供后续步骤使用。这一步完成之后,我们得到以下频繁项, { (c:4), (f:4), (a:3), (b:3), (m:3), (p:3) },“:”之后的数字表示对应项的出现频率。这个结果是排好顺序的,首先按照频率从达到小排序,再按照字母顺序排序。需要注意的是这里的排序非常重要,之后每个事务中的项都要按照这个顺序进行排列,这个是有效合并重复路径的前提。

处理之后的数据库记录为:

TID 原始事务数据 处理后数据
100 f, a, c, d, g, i, m, p c, f, a, m, p
200 a, b, c, f, l, m, o c, f, a, b, m
300 b, f, h, j, o f, b
400 b, c, k, s, p c, b, p
500 a, f, c, e, l, p, m, n c, f, a, m, p

2、第二次扫描数据库,在第一次处理完成的结果基础上,构建 FP-Tree。

1) 取出第一条事务数据,构建 FP-Tree 的第一条路径,{ c, f, a, m, p }。注意其中项的排序与第一步中得到的频繁项集合的排序是一致的。
2) 取出第二条事务数据,{ c, f, a, b, m },不难发现,它与第一条路径共享了部分数据{ c, f, a }。因此,可以重复利用已有的路径,只需要将其计数加 1,即{ (c:2), (f:2), (a:2) }。而对于后面不同的部分,我们创建新的路径,{ (b:1), (m:1) },其中,b 为 a 的子节点,m 为 b 的子节点。
3) 取出第三条事务数据,{ f, b },发现没有重复路径存在。但 f 点是存在的,因此,可以重复利用 f 点,新建一个 b 节点,作为 f 的子节点,得到路径{ {f:3}, (b:1) }。注意,之前已经存在的 b 节点无法重复使用,因为其父节点为 a。
4) 取出第四条事务数据,{ c, b, p },发现没有重复路径存在。因此,从现有 c 点出发,构建一条新路径{ (c:3), (b:1), (p:1) }。
5) 取出第五条事务数据,{ c, f, a, m, p },同上原理构建路径,{ (c:4), (f:4), (a:3), (m:2), (p:2) }。

经过两遍数据库扫描,完成了 FP-Tree 的构建。在此例中,c 点为整个 FP-Tree 的唯一根节点,但其实多数情况下,根节点并不是唯一的,即有多棵子树。因此,为了方便树结构的遍历,可以人为添加一个超级根节点,通常标记为 root<null>。参照下图,可以更清楚的理解整个过程。

得到了 FP-Tree 树之后,再遍历整棵树获取满足一定置信度的关联规则,就比较简单了。具体的理论证明,以及与 Apriori 算法的 performance 对比,论文里讲得非常清楚,有兴趣的朋友可以看一下。

关联规则算法系列文章
1)关联规则介绍
2)Apriori 算法
3)FP-Growth 算法,这篇文章和上一篇隔得时间有些长了

 

推荐系统:关联规则(2)

Apriori Algorithm 是关联规则领域里最具影响力的基础算法。它是由 Rakesh Agrawal 在 1994 年提出的,详细的介绍在这里《Fast Algorithms for Mining Association Rules》。十几年过去了,不少学者围绕着 Apriori 进行了诸多改良。但与 1994 年相比,目前基于互联网的应用,数据量大了几十倍甚至是几百倍,因此,基于 Apriori 的算法逐渐暴露出其运算成本过高的问题。但不管怎样,对于大师及其做出的贡献,我们也只有高山仰止的份儿。

Apriori 是一种广度优先算法,通过多次扫描数据库来获取支持度大于最小支持度的频繁项集。它的理论基础是频繁项集的两个单调性原则:频繁项集的任一子集一定是频繁的;非频繁项集的任一超集一定是非频繁的。晦涩的理论我这里就不多写了,有兴趣的可以去看论文。我把里面的例子给翻译一下,图文并茂,简明易懂。
某数据库 DB 里有 4 条事务记录,取最小支持度(min support)为 0.5,则计算频繁项集的过程如下:

TID Items
100 A, C, D
200 B, C, E
300 A, B, C, E
400 B, E

扫描DB
Itemset Support
{A} 2 (0.5)
{B} 3 (0.75)
{C} 3 (0.75)
{D} 1 (0.25)
{E} 3 (0.75)

取满足
最小支持度
项集
Itemset Support
{A} 2
{B} 3
{C} 3
{E} 3

Itemset
{A, B}
{A, C}
{A, E}
{B, C}
{B, E}
{C, E}

扫描DB
Itemset Support
{A, B} 1 (0.25)
{A, C} 2 (0.5)
{A, E} 1 (0.25)
{B, C} 2 (0.5)
{B, E} 3 (0.75)
{C, E} 2 (0.5)

取满足
最小支持度
项集
Itemset Support
{A, C} 2
{B, C} 2
{B, E} 3
{C, E} 2

Itemset
{A, B, C}
{A, B, E}
{A, C, E}
{B, C, E}

扫描DB
Itemset Support
{A, B, C} 1 (0.25)
{A, B, E} 1 (0.25)
{A, C, E} 1 (0.35)
{B, C, E} 2 (0.5)

取满足
最小支持度
项集
Itemset Support
{B, C, E} 2 (0.5)

如上可以看出,在海量数据的情况下,Apriori 算法的运算过程有 2 个问题:

  1. 需要多次扫描数据库,时间成本很高;
  2. 运算过程中需要产生大量的候选集,空间成本也非常高。

针对 Apriori 算法所做的改进也基本上是围绕着解决这两个问题进行的,如在扫描DB前首先进行以便事务合并和压缩,数据分区或抽样等。

Weka 里有 Apriori 算法的 Java 实现,非常值得一看。

貌似 wikipedia 已经解封了,呵呵!

预报:关联规则(3),关于 FP-Growth 算法。

 

1. 持续关注 个性化推荐 技术;
2. 持续关注 Semantic Web 技术;
3. 评论与上两项相关的互联网业务与产品;

我相信技术的力量!
wendell.gu@GMail.com

Archives