介绍
与GSP一样,PrefixSpan算法也是序列模式分析算法的一种,不过与前者不同的是PrefixSpan算法不产生任何的侯选集,在这点上可以说已经比GSP好很多了。PrefixSpan算法可以挖掘出满足阈值的所有序列模式,可以说是非常经典的算法。序列的格式就是上文中提到过的类似于<a,b,(de)>这种的。
算法原理
PrefixSpan算法的原理是采用后缀序列转前缀序列的方式来构造频繁序列的。举个例子,

比如原始序列如上图所示,4条序列,1个序列中好几个项集,项集内有1个或多个元素,首先找出前缀为a的子序列,此时序列前缀为<a>,后缀就变为了:
??
"_"下标符代表前缀为a,说明是在项集中间匹配的。这就相当于从后缀序列中提取出1项加入到前缀序列中,变化的规则就是从左往右扫描,找到第1个此元素对应的项,然后做改变。然后根据此规则继续递归直到后续序列不满足最小支持度阈值的情况。所以此算法的难点就转变为了从后缀序列变为前缀序列的过程。在这个过程要分为2种情况,第1种是单个元素项的后缀提前,比如这里的a,对单个项的提前有分为几种情况,比如:
<b ?a ?c ?ad>,就会变为<c ?ad>,如果a是嵌套在项集中的情况<b ?c ?dad ?r>,就会变为< _d ? r>,_代表的就是a.如果a在一项的最末尾,此项也会被移除<b ?c ?dda ?r>变为<r>。但是如果是这种情况<_da ?d ?d>a包含在下标符中,将会做处理,应该此时的a是在前缀序列所属的项集内的。
还有1个大类的分类就是对于组合项的后缀提取,可以分为2个情况,1个是从_X中寻找,一个从后面找出连续的项集,比如在这里<a>的条件下,找出前缀<(ab)>的后缀序列

第一种在_X中寻找还有没有X=a的情况,因为_已经代表1个a了,还有一个是判断_X != _a的情况,从后面的项集中找到包含有连续的aa的那个项集,然后做变换处理,与单个项集的变换规则一致。
算法的递归顺序
想要实现整个的序列挖掘,算法的递归顺序就显得非常重要了。在探索递归顺序的路上还是犯了一些错误的,刚刚开始的递归顺序是<a>---><a ?a>----><a ? a ? a>,假设<a ?a ?a>找不到对应的后缀模式时,然后回溯到<a (aa)>进行递归,后来发现这样会漏掉情况,为什么呢,因为如果 <a a >没法进行到<a ?a ?a>,那么就不可能会有前缀<a ?(aa)>,顶多会判断到<(aa)>,从<a a>处回调的。于是我发现了这个问题,就变为了下面这个样子,经测试是对的。:
加入所有的单个元素的类似为a-f,顺序为
<a>,---><a a>.同时<(aa)>,然后<ab>同时<(ab)>,就是在a添加a-f的元素的时候,检验a所属项集添加a-f元素的情况。这样就不会漏掉情况了,用了2个递归搞定了这个问题。这个算法的整体实现可以对照代码来看会理解很多。最后提醒一点,在每次做出改变之后都会判断一下是否满足最小支持度阈值的。
PrefixSpan实例
这里举1个真实一点的例子,下面是输入的初始序列:
挖掘出的所有的序列模式为,下面是一个表格的形式

在<b>的序列模式中少了1个序列模式。可以与后面程序算法测试的结果做对比。
算法的代码实现
代码实现同样以这个为例子,这样会显得更有说服性。
测试数据:
- bd?c?b?ac??
- bf?ce?b?fg??
- ah?bf?a?b?f??
- be?ce?d??
- a?bd?b?c?b?ade??
Sequence.java:
ItemSet.java:
copy
??*?字符项集类?
public?class?ItemSet?{??
//?项集内的字符??
????private?ArrayList<String>?items;??
????public?ItemSet(String[]?str)?{??
????????items?=?new?ArrayList<>();??
????????for?(String?s?:?str)?{??
????????????items.add(s);??
????public?ItemSet(ArrayList<String>?itemsList)?{??
????????this.items?=?itemsList;??
????public?ItemSet(String?s)?{??
????????items?=?new?ArrayList<>();??
0;?i?<?s.length();?i++)?{??
????????????items.add(s.charAt(i)?+?"");??
????public?ArrayList<String>?getItems()?{??
????????return?items;??
????public?void?setItems(ArrayList<String>?items)?{??
????????this.items?=?items;??
?????*?获取项集最后1个元素?
????public?String?getLastValue()?{??
????????int?size?=?this.items.size();??
????????return?this.items.get(size?-? }??
PrefixSpanTool.java:
copy
? import?java.io.BufferedReader;??
import?java.io.File;??
import?java.io.FileReader;??
import?java.io.IOException;??
import?java.util.Collections;??
import?java.util.HashMap;??
import?java.util.Map;??
?*?PrefixSpanTool序列模式分析算法工具类?
?*??
?*?@author?lyq?
?*/??
public?class?PrefixSpanTool?{??
//?测试数据文件地址??
????private?String?filePath;??
//?最小支持度阈值比例??
????private?double?minSupportRate;??
//?最小支持度,通过序列总数乘以阈值比例计算??
????private?int?minSupport;??
//?原始序列组??
????private?ArrayList<Sequence>?totalSeqs;??
//?挖掘出的所有序列频繁模式??
????private?ArrayList<Sequence>?totalFrequentSeqs;??
//?所有的单一项,用于递归枚举??
????private?ArrayList<String>?singleItems;??
????public?PrefixSpanTool(String?filePath,?double?minSupportRate)?{??
????????this.filePath?=?filePath;??
????????this.minSupportRate?=?minSupportRate;??
????????readDataFile();??
?????*?从文件中读取数据?
????private?void?readDataFile()?{??
????????File?file?=?new?File(filePath);??
????????ArrayList<String[]>?dataArray?=?new?ArrayList<String[]>();??
????????try?{??
????????????BufferedReader?in?=?new?BufferedReader(new?FileReader(file));??
????????????String?str;??
????????????String[]?tempArray;??
????????????while?((str?=?in.readLine())?!=?null)?{??
????????????????tempArray?=?str.split("?");??
????????????????dataArray.add(tempArray);??
????????????in.close();??
????????}?catch?(IOException?e)?{??
????????????e.getStackTrace();??
????????minSupport?=?(int)?(dataArray.size()?*?minSupportRate);??
????????totalSeqs?=?new?ArrayList<>();??
????????totalFrequentSeqs?=?new?ArrayList<>();??
????????Sequence?tempSeq;??
????????ItemSet?tempItemSet;??
????????for?(String[]?str?:?dataArray)?{??
????????????tempSeq?=?new?Sequence();??
????????????for?(String?s?:?str)?{??
????????????????tempItemSet?=?new?ItemSet(s);??
????????????????tempSeq.getItemSetList().add(tempItemSet);??
????????????totalSeqs.add(tempSeq);??
????????System.out.println("原始序列数据:");??
????????outputSeqence(totalSeqs);??
?????*?输出序列列表内容?
?????*?@param?seqList?
?????*????????????待输出序列列表?
????private?void?outputSeqence(ArrayList<Sequence>?seqList)?{??
????????for?(Sequence?seq?:?seqList)?{??
????????????System.out.print("<");??
????????????for?(ItemSet?itemSet?:?seq.getItemSetList())?{??
????????????????if?(itemSet.getItems().size()?>? ????????????????????System.out.print("(");??
????????????????for?(String?s?:?itemSet.getItems())?{??
????????????????????System.out.print(s?+?"?");??
????????????????????System.out.print(")");??
????????????System.out.println(">");??
?????*?移除初始序列中不满足最小支持度阈值的单项?
????private?void?removeInitSeqsItem()?{??
????????int?count?=?0;??
????????HashMap<String,?Integer>?itemMap?=?new?HashMap<>();??
????????singleItems?=?new?ArrayList<>();??
????????for?(Sequence?seq?:?totalSeqs)?{??
????????????????????if?(!itemMap.containsKey(s))?{??
????????????????????????itemMap.put(s,? ????????String?key;??
????????for?(Map.Entry?entry?:?itemMap.entrySet())?{??
????????????count?=? ????????????key?=?(String)?entry.getKey();??
????????????for?(Sequence?seq?:?totalSeqs)?{??
????????????????if?(seq.strIsContained(key))?{??
????????????????????count++;??
????????????itemMap.put(key,?count);??
????????for?(Map.Entry?entry?:?itemMap.entrySet())?{??
????????????count?=?(int)?entry.getValue();??
????????????if?(count?<?minSupport)?{??
//?如果支持度阈值小于所得的最小支持度阈值,则删除该项??
????????????????for?(Sequence?seq?:?totalSeqs)?{??
????????????????????seq.deleteSingleItem(key);??
????????????????singleItems.add(key);??
????????Collections.sort(singleItems);??
?????*?递归搜索满足条件的序列模式?
?????*?@param?beforeSeq?
?????*????????????前缀序列?
?????*?@param?afterSeqList?
?????*????????????后缀序列列表?
????private?void?recursiveSearchSeqs(Sequence?beforeSeq,248)"> ????????????ArrayList<Sequence>?afterSeqList)?{??
????????Sequence?tempSeq2;??
????????ArrayList<Sequence>?tempSeqList?=?new?ArrayList<>();??
????????for?(String?s?:?singleItems)?{??
//?分成2种形式递归,以<a>为起始项,第一种直接加入独立项集遍历<a,a>,<a,b>?<a,c>..??
????????????if?(isLargerThanMinSupport(s,?afterSeqList))?{??
????????????????tempSeq?=?beforeSeq.copySeqence();??
????????????????totalFrequentSeqs.add(tempSeq);??
????????????????tempSeqList?=?new?ArrayList<>();??
????????????????for?(Sequence?seq?:?afterSeqList)?{??
????????????????????if?(seq.strIsContained(s))?{??
????????????????????????tempSeq2?=?seq.extractItem(s);??
????????????????????????tempSeqList.add(tempSeq2);??
????????????????recursiveSearchSeqs(tempSeq,?tempSeqList);??
//?第二种递归为以元素的身份加入最后的项集内以a为例<(aa)>,<(ab)>,<(ac)>...??
//?a在这里可以理解为一个前缀序列,里面可能是单个元素或者已经是多元素的项集??
????????????tempSeq?=?beforeSeq.copySeqence();??
????????????int?size?=?tempSeq.getItemSetList().size();??
????????????tempItemSet?=?tempSeq.getItemSetList().get(size?-? ????????????tempItemSet.getItems().add(s);??
????????????if?(isLargerThanMinSupport(tempItemSet,?afterSeqList))?{??
????????????????tempSeqList?=?new?ArrayList<>();??
????????????????for?(Sequence?seq?:?afterSeqList)?{??
????????????????????if?(seq.compoentItemIsContain(tempItemSet))?{??
????????????????????????tempSeq2?=?seq.extractCompoentItem(tempItemSet??
????????????????????????????????.getItems());??
????????????????totalFrequentSeqs.add(tempSeq);??
????????????????recursiveSearchSeqs(tempSeq,?tempSeqList);??
?????*?所传入的项组合在所给定序列中的支持度是否超过阈值?
?????*????????????所需匹配的项?
?????*?@param?seqList?
?????*????????????比较序列数据?
????private?boolean?isLargerThanMinSupport(String?s,?ArrayList<Sequence>?seqList)?{??
????????boolean?isLarge?=?false;??
????????for?(Sequence?seq?:?seqList)?{??
????????????if?(seq.strIsContained(s))?{??
????????????????count++;??
????????if?(count?>=?minSupport)?{??
????????????isLarge?=?true;??
????????return?isLarge;??
?????*?所传入的组合项集在序列中的支持度是否大于阈值?
?????*?@param?itemSet?
?????*????????????组合元素项集?
?????*????????????比较的序列列表?
????private?boolean?isLargerThanMinSupport(ItemSet?itemSet,??
????????????ArrayList<Sequence>?seqList)?{??
????????boolean?isLarge?=?false;??
????????int?count?=?0;??
????????if?(seqList?==?null)?{??
????????????return?false;??
????????????if?(seq.compoentItemIsContain(itemSet))?{??
????????????????count++;??
????????if?(count?>=?minSupport)?{??
????????????isLarge?=?true;??
????????return?isLarge;??
?????*?序列模式分析计算?
????public?void?prefixSpanCalculate()?{??
????????Sequence?seq;??
????????removeInitSeqsItem();??
//?从最开始的a,d开始递归往下寻找频繁序列模式??
????????????seq?=?new?Sequence();??
????????????itemSet?=?new?ItemSet(s);??
????????????seq.getItemSetList().add(itemSet);??
????????????????for?(Sequence?s2?:?totalSeqs)?{??
//?判断单一项是否包含于在序列中,包含才进行提取操作??
????????????????????if?(s2.strIsContained(s))?{??
????????????????????????tempSeq?=?s2.extractItem(s);??
????????????????????????tempSeqList.add(tempSeq);??
????????????????totalFrequentSeqs.add(seq);??
????????????????recursiveSearchSeqs(seq,153); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> ????????printTotalFreSeqs();??
?????*?按模式类别输出频繁序列模式?
????private?void?printTotalFreSeqs()?{??
????????System.out.println("序列模式挖掘结果:");??
??????????
????????ArrayList<Sequence>?seqList;??
new?HashMap<>();??
????????????seqList?=?new?ArrayList<>();??
????????????for?(Sequence?seq?:?totalFrequentSeqs)?{??
????????????????if?(seq.getItemSetList().get(0).getItems().get( ????????????????????seqList.add(seq);??
????????????seqMap.put(s,?seqList);??
????????for?(String?s?:?singleItems)?{??
????????????System.out.println();??
????????????System.out.println();??
????????????seqList?=?(ArrayList<Sequence>)?seqMap.get(s);??
????????????for?(Sequence?tempSeq?:?seqList)?{??
????????????????System.out.print("<");??
????????????????for?(ItemSet?itemSet?:?tempSeq.getItemSetList())?{??
????????????????????if?(itemSet.getItems().size()?>? ????????????????????????System.out.print("(");??
????????????????????for?(String?str?:?itemSet.getItems())?{??
????????????????????????System.out.print(str?+?"?");??
????????????????????????System.out.print(")");??
????????????????System.out.print(">,?");??
//?每5个序列换一行??
????????????????if?(count?==?5)?{??
????????????????????count?=? ????????????????????System.out.println();??
}??
调用类Client.java:
copy
??*?PrefixSpan序列模式挖掘算法?
?*?
public?class?Client?{??
????public?static?void?main(String[]?agrs){??
????????String?filePath?=?"C:UserslyqDesktopiconinput.txt";??
??????????
????????double?minSupportRate?=?0.4;??
????????PrefixSpanTool?tool?=?new?PrefixSpanTool(filePath,?minSupportRate);??
????????tool.prefixSpanCalculate();??
}??
输出的结果:
copy
? 原始序列数据:??
<(b?d?)c?b?(a?c?)>??
<(b?f?)(c?e?)b?(f?g?)>??
<(a?h?)(b?f?)a?b?f?>??
<(b?e?)(c?e?)d?>??
<a?(b?d?)b?c?b?(a?d?e?)>??
序列模式挖掘结果:??
<a?>,?<a?a?>,?<a?b?>,?<a?b?a?>,?<a?b?b?>,???
<b?>,?<b?a?>,?<b?b?>,?<b?b?a?>,?<b?b?c?>,???
<b?b?f?>,?<b?c?>,?<b?c?a?>,?<b?c?b?>,?<b?c?b?a?>,153); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> <b?c?d?>,?<b?(c?e?)>,?<b?d?>,?<(b?d?)>,?<(b?d?)a?>,248)"> <(b?d?)b?>,?<(b?d?)b?a?>,?<(b?d?)b?c?>,?<(b?d?)c?>,?<(b?d?)c?a?>,153); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> <(b?d?)c?b?>,?<(b?d?)c?b?a?>,?<b?e?>,?<b?f?>,?<(b?f?)>,248)"> <(b?f?)b?>,?<(b?f?)b?f?>,?<(b?f?)f?>,248)"> <c?>,?<c?a?>,?<c?b?>,?<c?b?a?>,?<c?d?>,153); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> <(c?e?)>,153); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> <d?>,?<d?a?>,?<d?b?>,?<d?b?a?>,?<d?b?c?>,248)"> <d?c?>,?<d?c?a?>,?<d?c?b?>,?<d?c?b?a?>,248)"> <e?>,248)"> <f?>,?<f?b?>,?<f?b?f?>,?<f?f?>,???
经过比对,与上述表格中的结果完全一致,从结果中可以看出他的递归顺序正是刚刚我所想要的那种。
算法实现时的难点
我在实现这个算法时确实碰到了不少的问题,下面一一列举。
1、Sequence序列在判断或者提取单项和组合项的时候,情况少考虑了,还有考虑到了处理的方式又可能错了。
2、递归的顺序在最早的时候考虑错了,后来对递归的顺序进行了调整。
3、在算法的调试时遇到了,当发现某一项出现问题时,不能够立即调试,因为里面陷入的递归层次实在太深,只能自己先手算此情况下的前缀,后缀序列,然后自己模拟出1个Seq调试,在纠正extract方法时用的比较多。
我对PrefixSpan算法的理解
实现了这个算法之后,再回味这个算法,还是很奇妙的,一个序列,通过从左往右的扫描,通过各个项集的子集,能够组合出许许多多的的序列模式,然后进行挖掘,PrefixSpan通过递归的形式全部找出,而且效率非常高,的确是个很强大的算法。
PrefixSpan算法整体的特点
首先一点,他不会产生候选序列,在产生投影数据库的时候(也就是产生后缀子序列),他的规模是不断减小的。PrefixSpan采用分治法进行序列的挖掘,十分的高效。唯一比较会有影响的开销就是在构造后缀子序列的过程,专业上的名称叫做构造投影数据库的时候。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|