数据挖掘: 频繁项集挖掘(购物篮问题)

大家恐怕都听说过著名的啤酒与尿布, 这是典型的购物篮问题, 在数据挖掘界叫做频繁项集(Frequent Itemsets).
note: 数据类型写法按照Python的格式.

一. 目标与定义

1. 问题背景

超市中购物清单中总是有一些项目是被消费者一同购买的. 如果我们能够发现这些关联规则(association rules), 并合理地加以利用, 我们就能取得一定成果. 比如我们发现热狗和芥末存在这种关系, 我们对热狗降价促销, 而对芥末适当提价, 结果能显著提高超市的销售额.

2. 目标

找到频繁地共同出现在消费者结账小票中项目(比如啤酒和尿布), 来一同促销, 相互拉动, 提高销售额.

3. 定义

支持度support: 其实就是概率论中的频次frequency

支持度阈值support threshhold: 记为s, 指分辨频繁项集的临界值.

频繁项集: 如果I是一个项集(Itemset), 且I的出现频次(i.e.支持度)大于等于s, 那么我们说I是频繁项集.

一元项, 二元项, 三元项: 包含有一种商品, 两种, 三种商品的项集.

4. 关联规则

关联规则: 形式为I->j, 含义是如果I种所有项都出现在某个购物篮的话, 那么j很有可能也出现在这个购物篮中. 我们可以给出相应的confidence值(可信度, 即概率论中的置信度).

其中, 这个关联规则的可信度计算为Confidence = I∪{j} / I, 本身是非常符合直觉和常识的. 比如我们说关联规则{dog, cat} -> and 的可信度为0.6, 因为{dog, cat}出现在了1, 2, 3, 6, 7五个购物篮中, 而and出现在了1,2,7中, 因此我们可以算出Confidence = freq[{dog, cat, and}] / freq[{dog, cat}] = 3/5 = 0.6

注意到, 分子部分的频次总是比分母低, 这是因为{dog, cat} 出现的次数总是大于等于{dog, cat, and}的出现次数.

二. 购物篮与A-Priori算法

1. 购物篮数据表示

我们将有一个文本文件输入, 比如allBills.txt, 或者allBills.csv. 里面每行是一个购物篮.
文件的头两行可能是这样(df.show(2)):
{23, 456, 1001}
{3, 18, 92, 145}

我们假定这是一家大型连锁超市, 比如沃尔玛, 因此这个文本文件是非常大的, 比如20GB. 因此我们无法一次将该文件读入内存. 因此, 算法的主要时间开销都是磁盘IO.
我们同时还假定, 所有购物篮的平均规模是较小的, 因此在内存中产生所有大小项集的时间开销会比读入购物篮的时间少很多.
我们可以计算, 对于有n个项目组成的购物篮而言, 大小为k的所有子集的生成时间约为(n, k) = n! / ((n-k)!k!) = O(n^k/ k!), 其中我们只关注较小的频繁项集, 因此我们约定k=2或者k=3. 因此所有子集生成时间T = O(n^3).

Again, 我们认为在内存中产生所有大小项集的时间开销会比读入购物篮的时间少很多.

2. Itemset计数过程中的内存使用

我们必须要把整个k,v字典放在内存中, 否则来一个Itemset就去硬盘读取一次字典将十分十分地慢.
此处, 字典是k=(18, 145), v=15这种形式. 此处, 应当注意到, 如果有{bread, milk, orange}这样的String类型输入, 应当预先用一个字典映射成对应的整数值编码, 比如1920, 4453, 9101这样.

那么, 我们最多能用字典存储多少种商品?

先看下我们存储多少个count值.
我们假定项的总数目是n, 即超市有n种商品, 每个商品都有一个数字编号, 那么我们需要(n, 2) = n^2/2 的大小来存储所有的二元组合的count, 假设int是占4个byte, 那么需要(2·n^2)Byte内存. 已知2GB内存 = 2^31 Byte, 即2^31/2 = 2^30 >= n^2 --> n <= 2^15. 也就是说n<33 000, 因此我们说商品种类的最多是33k种.

但是, 这种计算方法存在一个问题, 并不是有10种商品, 那么这10种商品的任意二元组合都会出现的. 对于那些没出现的组合, 我们在字典中完全可以不存储, 从而节省空间.

同时, 别忘了我们同样也得存储key = (i, j), 这是至少额外的两个整数.

那么我们到底具体怎么存储这些计数值?
可以采用三元组的方式来构造字典. 我们采用[i, j, count]形式来存储, 其中i代表商品种类1, j代表商品种类2, 前两个值代表key, 后面的value就是count, 是这个二元组合下的计数.

现在, 让我们注意到我们(1)假定购物篮平均大小较小, 并(2)利用三元组(2个key的)字典和(3)不存储没出现组合优势. 假设有100k = 10^5种商品, 有10million=10^7个购物篮, 每个购物篮有10个项, 那么这种字典空间开销是(10, 2) · 10^7 = 45 x 10^7 x 3= 4.5x10^8x3 = 1.35x10^9 个整数. 这算出来约为4x10^8 Byte = 400MB, 处于正常计算机内存范围内.

3. 项集的单调性

如果项集I是频繁的, 那么它的所有子集也都是频繁的. 这个道理很符合常识, 因为{dog, cat} 出现的次数总是大于等于{dog, cat, and}的出现次数.

这个规律的推论, 就是严格地, 我们频繁一元组的个数> 频繁二元组的个数 > 频繁三元组的个数.

4. A-Priori算法

我们通过Itemset计数中内存使用的部门, 已经明确了我们总是有足够的内存用于所有存在的二元项集(比如{cat, dog})的计数. 这里, 我们的字典不存放不存在于购物篮中的任何二元项集合, 而且频繁二元组的数目将会大于三元频繁三元组> ...

我们可以通过单边扫描购物篮文件, 对于每个购物篮, 我们使用一个双重循环就可以生成所有的项对(即二元组). 每当我们生成一个项对, 就给其对应的字典中的value +1(也称为计数器). 最后, 我们会检查所有项对的计数结果,并且找出那些>=阈值s的项对, 他们就是频繁项对.

1) A-Priori算法的第一遍扫描

在第一遍扫描中, 我们将建立两个表. 第一张表将项的名称转换为1到n之间的整数, 从而把String类型这样的key转为空间大小更小的int类型. 第二张表将记录从1~n每个项在所有购物篮中出现的次数. 形式上类似
table 0(name table): {'dolphin': 7019, 'cat': 7020} //dict形式, 其实也可以做成list形式 [['dolphin', 7019], ['cat', 7020]]
table 1(single-item counter table): {7019: 15, 7020: 18} //dict形式, 其实也可以做成数组形式A[7019] = 2, A[7020] = 18

2) 第一遍扫描完的处理

第一遍扫描完后, 我们会按照自己设定的阈值s, 对整个table 1再进行一次mapping, 因为我们只关注最后counter值大于等于阈值的项目, 而且不关心其counter值具体多少. 因此, mapping策略是:

对凡是counter<s的, 一律把counter设成0; 对于counter>=s的, 按照次序, 把其设置成1~m的值(总共有m个满足要求的项)

3) 第二遍扫描

第二遍扫描所做的事有三:
(1) 对每个购物篮, 在table 1中检查其所有的商品项目, 把所有为频繁项的留下来建立一个list.
(2) 通过一个双重循环生成该list中的所有项对.
(3) 再走一次循环, 在新的数据结构table 2(dict或者list)中相应的位置+1. 此时的效果是dicta = {48: {13: 5}, 49: {71, 16}} 或者 lista [ [48, 13, 5],[49, 71, 16], ... ]

注意此时内存块上存储的结构: table1(name table), table2(single-item counter table), table3(double-item counter table)

5. 推广: 任意大小频繁项集上的A-Priori算法

我们对上面这个算法进行推广.
从任意集合大小k到下一个大小k+1的转移模式可以这么说:
(1) 对每个购物篮, 在table 1中检查其所有的商品项目, 把所有为频繁项的留下来建立一个list.
(2) 我们通过一个k+1重循环来生成该list中的所有(k+1)元组
(3) 对每个k+1元组, 我们生成其的(k+1 choose k)个k元组, 并检查这些k元组是否都在之前的table k中. (注意到k=1的时候, 这步与(1)是重复的, 可以省略)
(4)再走一次循环, 在新的数据结构table k+1(dict或者list)中相应的位置+1. 此时的效果是k=2, k+1=3, 生成dicta = {48: {13: {19: 4}}, 49: {71: {51: 10}}, ... } 或者 生成lista [ [48, 13, 19, 4],[49, 71, 51, 10], ... ]

注意, 在进入下一次扫描前, 我们还需要额外把counter中值小于s的元组的计数值都记为0.

模式总体是: C1 过滤后 L1 计数后 C2 置零后 C2' 过滤后 L2 计数后 C3 置零后 C3' ......

END.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容