PrefixSpan算法与代码

转载:https://www.cnblogs.com/pinard/p/6323182.html

       频繁项集挖掘的关联算法AprioriFP Tree。这两个算法都是挖掘频繁项集的。而今天我们要介绍的PrefixSpan算法也是关联算法,但是它是挖掘频繁序列模式的,因此要解决的问题目标稍有不同。

1. 项集数据和序列数据

    首先我们看看项集数据和序列数据有什么不同,如下图所示。

https://images2015.cnblogs.com/blog/1042406/201701/1042406-20170120160812015-470353744.png

  左边的数据集就是项集数据,在AprioriFP Tree算法中我们也已经看到过了,每个项集数据由若干项组成,这些项没有时间上的先后关系。而右边的序列数据则不一样,它是由若干数据项集组成的序列。比如第一个序列<a(abc)(ac)d(cf)>,它由a,abc,ac,d,cf5个项集数据组成,并且这些项有时间上的先后关系。对于多于一个项的项集我们要加上括号,以便和其他的项集分开。同时由于项集内部是不区分先后顺序的,为了方便数据处理,我们一般将序列数据内所有的项集内部按字母顺序排序。

2. 子序列与频繁序列

  了解了序列数据的概念,我们再来看看上面是子序列。子序列和我们数学上的子集的概念很类似,也就是说,如果某个序列A所有的项集在序列B中的项集都可以找到,则A就是B的子序列。当然,如果用严格的数学描述,子序列是这样的:

  对于序列A={a1,a2,...ana1,a2,...an}和序列B={b1,b2,...bmb1,b2,...bm},nmn≤m,如果存在数字序列1j1j2≤...≤jnm1≤j1≤j2≤...≤jn≤m, 满足a1⊆bj1,a2bj2...anbjna1bj1,a2bj2...anbjn,则称AB的子序列。当然反过来说, B就是A的超序列。

 

  而频繁序列则和我们的频繁项集很类似,也就是频繁出现的子序列。比如对于下图,支持度阈值定义为50%,也就是需要出现两次的子序列才是频繁序列。而子序列<(ab)c>是频繁序列,因为它是图中的第一条数据和第三条序列数据的子序列,对应的位置用蓝色标示。

https://images2015.cnblogs.com/blog/1042406/201701/1042406-20170120163415968-357017982.png

3. PrefixSpan算法的一些概念

   PrefixSpan算法的全称是Prefix-Projected Pattern Growth,即前缀投影的模式挖掘。里面有前缀和投影两个词。那么我们首先看看什么是PrefixSpan算法中的前缀prefix

  在PrefixSpan算法中的前缀prefix通俗意义讲就是序列数据前面部分的子序列。比如对于序列数据B=<a(abc)(ac)d(cf)>,而A=<a(abc)a>,AB的前缀。当然B的前缀不止一个,比如<a>, <aa>, <a(ab)> 也都是B的前缀。

  看了前缀,我们再来看前缀投影,其实前缀投影这儿就是我们的后缀,有前缀就有后缀嘛。前缀加上后缀就可以构成一个我们的序列。下面给出前缀和后缀的例子。对于某一个前缀,序列里前缀后面剩下的子序列即为我们的后缀。如果前缀最后的项是项集的一部分,则用一个“_”来占位表示。

  下面这个例子展示了序列<a(abc)(ac)d(cf)>的一些前缀和后缀,还是比较直观的。要注意的是,如果前缀的末尾不是一个完全的项集,则需要加一个占位符。

  在PrefixSpan算法中,相同前缀对应的所有后缀的结合我们称为前缀对应的投影数据库。

https://images2015.cnblogs.com/blog/1042406/201701/1042406-20170120204305781-1880778940.png

4. PrefixSpan算法思想

    现在我们来看看PrefixSpan算法的思想,PrefixSpan算法的目标是挖掘出满足最小支持度的频繁序列。那么怎么去挖掘出所有满足要求的频繁序列呢。回忆Aprior算法,它是从频繁1项集出发,一步步的挖掘2项集,直到最大的K项集。PrefixSpan算法也类似,它从长度为1的前缀开始挖掘序列模式,搜索对应的投影数据库得到长度为1的前缀对应的频繁序列,然后递归的挖掘长度为2的前缀所对应的频繁序列,。。。以此类推,一直递归到不能挖掘到更长的前缀挖掘为止。

   比如对应于我们第二节的例子,支持度阈值为50%。里面长度为1的前缀包括<a>, <b>, <c>, <d>, <e>, <f>,<g>我们需要对这6个前缀分别递归搜索找各个前缀对应的频繁序列。如下图所示,每个前缀对应的后缀也标出来了。由于g只在序列4出现,支持度计数只有1,因此无法继续挖掘。我们的长度为1的频繁序列为<a>, <b>, <c>, <d>, <e><f>。去除所有序列中的g,即第4条记录变成<e(af)cbc>

https://images2015.cnblogs.com/blog/1042406/201701/1042406-20170120214250703-974535125.png

    
  现在我们开始挖掘频繁序列,分别从长度为1的频繁项开始。这里我们以d为例子来递归挖掘,其他的节点递归挖掘方法和D一样。方法如下图,首先我们对d的后缀进行计数,得到{a:1, b:2, c:3, d:0, e:1, f:1_f:1}。注意f_f是不一样的,因为前者是在和前缀d不同的项集,而后者是和前缀d同项集。由于此时a,d,e,f,_f都达不到支持度阈值,因此我们递归得到的前缀为d2项频繁序列为<db><dc>。接着我们分别递归dbdc为前缀所对应的投影序列。首先看db前缀,此时对应的投影后缀只有<_c(ae)>,此时_c,a,e支持度均达不到阈值,因此无法找到以db为前缀的频繁序列。现在我们来递归另外一个前缀dc。以dc为前缀的投影序列为<_f>, <(bc)(ae)>, <b>,此时我们进行支持度计数,结果为{b:2, a:1, c:1, e:1, _f:1},只有b满足支持度阈值,因此我们得到前缀为dc的三项频繁序列为<dcb>。我们继续递归以<dcb>为前缀的频繁序列。由于前缀<dcb>对应的投影序列<(_c)ae>支持度全部不达标,因此不能产生4项频繁序列。至此以d为前缀的频繁序列挖掘结束,产生的频繁序列为<d><db><dc><dcb>

    同样的方法可以得到其他以<a>, <b>, <c>, <e>, <f>为前缀的频繁序列。

https://images2015.cnblogs.com/blog/1042406/201701/1042406-20170120222303515-615199517.png

5. PrefixSpan算法流程

  下面我们对PrefixSpan算法的流程做一个归纳总结。

  输入:序列数据集S和支持度阈值αα

  输出:所有满足支持度要求的频繁序列集

  1)找出所有长度为1的前缀和对应的投影数据库

  2)对长度为1的前缀进行计数,将支持度低于阈值αα的前缀对应的项从数据集S删除,同时得到所有的频繁1项序列,i=1.

  3)对于每个长度为i满足支持度要求的前缀进行递归挖掘:

    a) 找出前缀所对应的投影数据库。如果投影数据库为空,则递归返回。

    b) 统计对应投影数据库中各项的支持度计数。如果所有项的支持度计数都低于阈值αα,则递归返回。

    c) 将满足支持度计数的各个单项和当前的前缀进行合并,得到若干新的前缀。

    d) i=i+1,前缀为合并单项后的各个前缀,分别递归执行第3步。

6. PrefixSpan算法小结

  PrefixSpan算法由于不用产生候选序列,且投影数据库缩小的很快,内存消耗比较稳定,作频繁序列模式挖掘的时候效果很高。比起其他的序列挖掘算法比如GSP,FreeSpan有较大优势,因此是在生产环境常用的算法。

  PrefixSpan运行时最大的消耗在递归的构造投影数据库。如果序列数据集较大,项数种类较多时,算法运行速度会有明显下降。因此有一些PrefixSpan的改进版算法都是在优化构造投影数据库这一块。比如使用伪投影计数。

  当然使用大数据平台的分布式计算能力也是加快PrefixSpan运行速度一个好办法。比如SparkMLlib就内置了PrefixSpan算法。

  不过scikit-learn始终不太重视关联算法,一直都不包括这一块的算法集成,这就有点落伍了。

 

 

PrefixSpan代码实现

#!/usr/bin/python
# -*- coding: utf-8 -*-
#author: Tianming Lu

import sys
#import pdb
#pdb.set_trace()

PLACE_HOLDER = '_'


def read(filename):
    S = []
    with open(filename, 'r') as input:
        for line in input.readlines():
            elements = line.split(',')
            s = []
            for e in elements:
                s.append(e.split())
            S.append(s)
    return S


class SquencePattern:
    def __init__(self, squence, support):
        self.squence = []
        for s in squence:
            self.squence.append(list(s))
        self.support = support

    def append(self, p):
        if p.squence[0][0] == PLACE_HOLDER:
            first_e = p.squence[0]
            first_e.remove(PLACE_HOLDER)
            self.squence[-1].extend(first_e)
            self.squence.extend(p.squence[1:])
        else:
            self.squence.extend(p.squence)
        self.support = min(self.support, p.support)


def prefixSpan(pattern, S, threshold):
    print(S)
    patterns = []
    f_list = frequent_items(S, pattern, threshold)
    for i in f_list:
        p = SquencePattern(pattern.squence, pattern.support)
        p.append(i)
        patterns.append(p)
        
        
        p_S = build_projected_database(S, p)
        p_patterns = prefixSpan(p, p_S, threshold)
        patterns.extend(p_patterns)
    return patterns


def frequent_items(S, pattern, threshold):
    items = {}
    _items = {}
    f_list = []
    if S is None or len(S) == 0:
        return []

    if len(pattern.squence) != 0:
        last_e = pattern.squence[-1]
    else:
        last_e = []
    for s in S:
        #class 1
        is_prefix = True
        for item in last_e:
            if item not in s[0]:
                is_prefix = False
                break
        if is_prefix and len(last_e) > 0:
            index = s[0].index(last_e[-1])
            if index < len(s[0]) - 1:
                for item in s[0][index + 1:]:
                    if item in _items:
                        _items[item] += 1
                    else:
                        _items[item] = 1

        #class 2
        if PLACE_HOLDER in s[0]:
            for item in s[0][1:]:
                if item in _items:
                    _items[item] += 1
                else:
                    _items[item] = 1
            s = s[1:]

        #class 3
        counted = []
        for element in s:
            for item in element:
                if item not in counted:
                    counted.append(item)
                    if item in items:
                        items[item] += 1
                    else:
                        items[item] = 1

    #f_list.extend([SquencePattern([[PLACE_HOLDER, k]], v)
                   #for k, v in _items.iteritems()
                   #if v >= threshold])
    f_list.extend([SquencePattern([[k]], v)
                   for k, v in items.items()
                   if v >= threshold])
    sorted_list = sorted(f_list, key=lambda p: p.support)
    return sorted_list  
    


def build_projected_database(S, pattern):
    """
    suppose S is projected database base on pattern's prefix,
    so we only need to use the last element in pattern to
    build projected database
    """
    p_S = []
    last_e = pattern.squence[-1]
    last_item = last_e[-1]
    for s in S:
        p_s = []
        for element in s:
            is_prefix = False
            if PLACE_HOLDER in element:
                if last_item in element and len(pattern.squence[-1]) > 1:
                    is_prefix = True
            else:
                is_prefix = True
                for item in last_e:
                    if item not in element:
                        is_prefix = False
                        break

            if is_prefix:
                e_index = s.index(element)
                i_index = element.index(last_item)
                if i_index == len(element) - 1:
                    p_s = s[e_index + 1:]
                else:
                    p_s = s[e_index:]
                    index = element.index(last_item)
                    e = element[i_index:]
                    e[0] = PLACE_HOLDER
                    p_s[0] = e
                break
        if len(p_s) != 0:
            p_S.append(p_s)

    return p_S


def print_patterns(patterns):
    for p in patterns:
        print("pattern:{0}, support:{1}".format(p.squence, p.support))


if __name__ == "__main__":
    S = read("PrefixSpan.txt")
    print(S)
    patterns = prefixSpan(SquencePattern([], sys.maxsize), S, 2)
    print_patterns(patterns)

注意数据格式,行与行之间要加空格,对应split(),例如:

   a,a b c,a c,d,c f
   a d,c,b c,a e

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值