prefixspan java_PrefixSpan序列模式挖掘算法
介紹
與GSP一樣,PrefixSpan算法也是序列模式分析算法的一種,不過與前者不同的是PrefixSpan算法不產生任何的侯選集,在這點上可以說已經比GSP好很多了。PrefixSpan算法可以挖掘出滿足閾值的所有序列模式,可以說是非常經典的算法。序列的格式就是上文中提到過的類似于這種的。
算法原理
PrefixSpan算法的原理是采用后綴序列轉前綴序列的方式來構造頻繁序列的。舉個例子,
比如原始序列如上圖所示,4條序列,1個序列中好幾個項集,項集內有1個或多個元素,首先找出前綴為a的子序列,此時序列前綴為,后綴就變為了:
"_"下標符代表前綴為a,說明是在項集中間匹配的。這就相當于從后綴序列中提取出1項加入到前綴序列中,變化的規則就是從左往右掃描,找到第1個此元素對應的項,然后做改變。然后根據此規則繼續遞歸直到后續序列不滿足最小支持度閾值的情況。所以此算法的難點就轉變為了從后綴序列變為前綴序列的過程。在這個過程要分為2種情況,第1種是單個元素項的后綴提前,比如這里的a,對單個項的提前有分為幾種情況,比如:
,就會變為,如果a是嵌套在項集中的情況,就會變為< _d ? r>,_代表的就是a.如果a在一項的最末尾,此項也會被移除變為。但是如果是這種情況<_da>a包含在下標符中,將會做處理,應該此時的a是在前綴序列所屬的項集內的。
還有1個大類的分類就是對于組合項的后綴提取,可以分為2個情況,1個是從_X中尋找,一個從后面找出連續的項集,比如在這里的條件下,找出前綴的后綴序列
第一種在_X中尋找還有沒有X=a的情況,因為_已經代表1個a了,還有一個是判斷_X != _a的情況,從后面的項集中找到包含有連續的aa的那個項集,然后做變換處理,與單個項集的變換規則一致。
算法的遞歸順序
想要實現整個的序列挖掘,算法的遞歸順序就顯得非常重要了。在探索遞歸順序的路上還是犯了一些錯誤的,剛剛開始的遞歸順序是--->---->,假設找不到對應的后綴模式時,然后回溯到進行遞歸,后來發現這樣會漏掉情況,為什么呢,因為如果 沒法進行到,那么就不可能會有前綴,頂多會判斷到,從處回調的。于是我發現了這個問題,就變為了下面這個樣子,經測試是對的。:
加入所有的單個元素的類似為a-f,順序為
,--->.同時,然后同時,就是在a添加a-f的元素的時候,檢驗a所屬項集添加a-f元素的情況。這樣就不會漏掉情況了,用了2個遞歸搞定了這個問題。這個算法的整體實現可以對照代碼來看會理解很多。最后提醒一點,在每次做出改變之后都會判斷一下是否滿足最小支持度閾值的。
PrefixSpan實例
這里舉1個真實一點的例子,下面是輸入的初始序列:
挖掘出的所有的序列模式為,下面是一個表格的形式
在的序列模式中少了1個序列模式。可以與后面程序算法測試的結果做對比。
算法的代碼實現
代碼實現同樣以這個為例子,這樣會顯得更有說服性。
測試數據:
bd c b ac
bf ce b fg
ah bf a b f
be ce d
a bd b c b adeSequence.java:
package DataMining_PrefixSpan;
import java.util.ArrayList;
/**
* 序列類
*
* @author lyq
*
*/
public class Sequence {
// 序列內的項集
private ArrayList itemSetList;
public Sequence() {
this.itemSetList = new ArrayList<>();
}
public ArrayList getItemSetList() {
return itemSetList;
}
public void setItemSetList(ArrayList itemSetList) {
this.itemSetList = itemSetList;
}
/**
* 判斷單一項是否包含于此序列
*
* @param c
* 待判斷項
* @return
*/
public boolean strIsContained(String c) {
boolean isContained = false;
for (ItemSet itemSet : itemSetList) {
isContained = false;
for (String s : itemSet.getItems()) {
if (itemSet.getItems().contains("_")) {
continue;
}
if (s.equals(c)) {
isContained = true;
break;
}
}
if (isContained) {
// 如果已經檢測出包含了,直接挑出循環
break;
}
}
return isContained;
}
/**
* 判斷組合項集是否包含于序列中
*
* @param itemSet
* 組合的項集,元素超過1個
* @return
*/
public boolean compoentItemIsContain(ItemSet itemSet) {
boolean isContained = false;
ArrayList tempItems;
String lastItem = itemSet.getLastValue();
for (int i = 0; i < this.itemSetList.size(); i++) {
tempItems = this.itemSetList.get(i).getItems();
// 分2種情況查找,第一種從_X中找出x等于項集最后的元素,因為_前綴已經為原本的元素
if (tempItems.size() > 1 && tempItems.get(0).equals("_")
&& tempItems.get(1).equals(lastItem)) {
isContained = true;
break;
} else if (!tempItems.get(0).equals("_")) {
// 從沒有_前綴的項集開始尋找,第二種為從后面的后綴中找出直接找出連續字符為ab為同一項集的項集
if (strArrayContains(tempItems, itemSet.getItems())) {
isContained = true;
break;
}
}
if (isContained) {
break;
}
}
return isContained;
}
/**
* 刪除單個項
*
* @param s
* 待刪除項
*/
public void deleteSingleItem(String s) {
ArrayList tempItems;
ArrayList deleteItems = new ArrayList<>();
for (ItemSet itemSet : this.itemSetList) {
tempItems = itemSet.getItems();
deleteItems = new ArrayList<>();
for (int i = 0; i < tempItems.size(); i++) {
if (tempItems.get(i).equals(s)) {
deleteItems.add(tempItems.get(i));
}
}
tempItems.removeAll(deleteItems);
}
}
/**
* 提取項s之后所得的序列
*
* @param s
* 目標提取項s
*/
public Sequence extractItem(String s) {
Sequence extractSeq = this.copySeqence();
ItemSet itemSet;
ArrayList items;
ArrayList deleteItemSets = new ArrayList<>();
ArrayList tempItems = new ArrayList<>();
for (int k = 0; k < extractSeq.itemSetList.size(); k++) {
itemSet = extractSeq.itemSetList.get(k);
items = itemSet.getItems();
if (items.size() == 1 && items.get(0).equals(s)) {
//如果找到的是單項,則完全移除,跳出循環
extractSeq.itemSetList.remove(k);
break;
} else if (items.size() > 1 && !items.get(0).equals("_")) {
//在后續的多元素項中判斷是否包含此元素
if (items.contains(s)) {
//如果包含把s后面的元素加入到臨時字符數組中
int index = items.indexOf(s);
for (int j = index; j < items.size(); j++) {
tempItems.add(items.get(j));
}
//將第一位的s變成下標符"_"
tempItems.set(0, "_");
if (tempItems.size() == 1) {
// 如果此匹配為在最末端,同樣移除
deleteItemSets.add(itemSet);
} else {
//將變化后的項集替換原來的
extractSeq.itemSetList.set(k, new ItemSet(tempItems));
}
break;
} else {
deleteItemSets.add(itemSet);
}
} else {
// 不符合以上2項條件的統統移除
deleteItemSets.add(itemSet);
}
}
extractSeq.itemSetList.removeAll(deleteItemSets);
return extractSeq;
}
/**
* 提取組合項之后的序列
*
* @param array
* 組合數組
* @return
*/
public Sequence extractCompoentItem(ArrayList array) {
// 找到目標項,是否立刻停止
boolean stopExtract = false;
Sequence seq = this.copySeqence();
String lastItem = array.get(array.size() - 1);
ArrayList tempItems;
ArrayList deleteItems = new ArrayList<>();
for (int i = 0; i < seq.itemSetList.size(); i++) {
if (stopExtract) {
break;
}
tempItems = seq.itemSetList.get(i).getItems();
// 分2種情況查找,第一種從_X中找出x等于項集最后的元素,因為_前綴已經為原本的元素
if (tempItems.size() > 1 && tempItems.get(0).equals("_")
&& tempItems.get(1).equals(lastItem)) {
if (tempItems.size() == 2) {
seq.itemSetList.remove(i);
} else {
// 把1號位置變為下標符"_",往后移1個字符的位置
tempItems.set(1, "_");
// 移除第一個的"_"下劃符
tempItems.remove(0);
}
stopExtract = true;
break;
} else if (!tempItems.get(0).equals("_")) {
// 從沒有_前綴的項集開始尋找,第二種為從后面的后綴中找出直接找出連續字符為ab為同一項集的項集
if (strArrayContains(tempItems, array)) {
// 從左往右找出第一個給定字符的位置,把后面的部分截取出來
int index = tempItems.indexOf(lastItem);
ArrayList array2 = new ArrayList();
for (int j = index; j < tempItems.size(); j++) {
array2.add(tempItems.get(j));
}
array2.set(0, "_");
if (array2.size() == 1) {
//如果此項在末尾的位置,則移除該項,否則進行替換
deleteItems.add(seq.itemSetList.get(i));
} else {
seq.itemSetList.set(i, new ItemSet(array2));
}
stopExtract = true;
break;
} else {
deleteItems.add(seq.itemSetList.get(i));
}
} else {
// 這種情況是處理_X中X不等于最后一個元素的情況
deleteItems.add(seq.itemSetList.get(i));
}
}
seq.itemSetList.removeAll(deleteItems);
return seq;
}
/**
* 深拷貝一個序列
*
* @return
*/
public Sequence copySeqence() {
Sequence copySeq = new Sequence();
ItemSet tempItemSet;
ArrayList items;
for (ItemSet itemSet : this.itemSetList) {
items = (ArrayList) itemSet.getItems().clone();
tempItemSet = new ItemSet(items);
copySeq.getItemSetList().add(tempItemSet);
}
return copySeq;
}
/**
* 獲取序列中最后一個項集的最后1個元素
*
* @return
*/
public String getLastItemSetValue() {
int size = this.getItemSetList().size();
ItemSet itemSet = this.getItemSetList().get(size - 1);
size = itemSet.getItems().size();
return itemSet.getItems().get(size - 1);
}
/**
* 判斷strList2是否是strList1的子序列
*
* @param strList1
* @param strList2
* @return
*/
public boolean strArrayContains(ArrayList strList1,
ArrayList strList2) {
boolean isContained = false;
for (int i = 0; i < strList1.size() - strList2.size() + 1; i++) {
isContained = true;
for (int j = 0, k = i; j < strList2.size(); j++, k++) {
if (!strList1.get(k).equals(strList2.get(j))) {
isContained = false;
break;
}
}
if (isContained) {
break;
}
}
return isContained;
}
}ItemSet.java:
package DataMining_PrefixSpan;
import java.util.ArrayList;
/**
* 字符項集類
*
* @author lyq
*
*/
public class ItemSet {
// 項集內的字符
private ArrayList items;
public ItemSet(String[] str) {
items = new ArrayList<>();
for (String s : str) {
items.add(s);
}
}
public ItemSet(ArrayList itemsList) {
this.items = itemsList;
}
public ItemSet(String s) {
items = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
items.add(s.charAt(i) + "");
}
}
public ArrayList getItems() {
return items;
}
public void setItems(ArrayList items) {
this.items = items;
}
/**
* 獲取項集最后1個元素
*
* @return
*/
public String getLastValue() {
int size = this.items.size();
return this.items.get(size - 1);
}
}PrefixSpanTool.java:
package DataMining_PrefixSpan;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
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 totalSeqs;
// 挖掘出的所有序列頻繁模式
private ArrayList totalFrequentSeqs;
// 所有的單一項,用于遞歸枚舉
private ArrayList singleItems;
public PrefixSpanTool(String filePath, double minSupportRate) {
this.filePath = filePath;
this.minSupportRate = minSupportRate;
readDataFile();
}
/**
* 從文件中讀取數據
*/
private void readDataFile() {
File file = new File(filePath);
ArrayList dataArray = new ArrayList();
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 seqList) {
for (Sequence seq : seqList) {
System.out.print("
for (ItemSet itemSet : seq.getItemSetList()) {
if (itemSet.getItems().size() > 1) {
System.out.print("(");
}
for (String s : itemSet.getItems()) {
System.out.print(s + " ");
}
if (itemSet.getItems().size() > 1) {
System.out.print(")");
}
}
System.out.println(">");
}
}
/**
* 移除初始序列中不滿足最小支持度閾值的單項
*/
private void removeInitSeqsItem() {
int count = 0;
HashMap itemMap = new HashMap<>();
singleItems = new ArrayList<>();
for (Sequence seq : totalSeqs) {
for (ItemSet itemSet : seq.getItemSetList()) {
for (String s : itemSet.getItems()) {
if (!itemMap.containsKey(s)) {
itemMap.put(s, 1);
}
}
}
}
String key;
for (Map.Entry entry : itemMap.entrySet()) {
count = 0;
key = (String) entry.getKey();
for (Sequence seq : totalSeqs) {
if (seq.strIsContained(key)) {
count++;
}
}
itemMap.put(key, count);
}
for (Map.Entry entry : itemMap.entrySet()) {
key = (String) entry.getKey();
count = (int) entry.getValue();
if (count < minSupport) {
// 如果支持度閾值小于所得的最小支持度閾值,則刪除該項
for (Sequence seq : totalSeqs) {
seq.deleteSingleItem(key);
}
} else {
singleItems.add(key);
}
}
Collections.sort(singleItems);
}
/**
* 遞歸搜索滿足條件的序列模式
*
* @param beforeSeq
* 前綴序列
* @param afterSeqList
* 后綴序列列表
*/
private void recursiveSearchSeqs(Sequence beforeSeq,
ArrayList afterSeqList) {
ItemSet tempItemSet;
Sequence tempSeq2;
Sequence tempSeq;
ArrayList tempSeqList = new ArrayList<>();
for (String s : singleItems) {
// 分成2種形式遞歸,以為起始項,第一種直接加入獨立項集遍歷, ..
if (isLargerThanMinSupport(s, afterSeqList)) {
tempSeq = beforeSeq.copySeqence();
tempItemSet = new ItemSet(s);
tempSeq.getItemSetList().add(tempItemSet);
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為例,,...
// a在這里可以理解為一個前綴序列,里面可能是單個元素或者已經是多元素的項集
tempSeq = beforeSeq.copySeqence();
int size = tempSeq.getItemSetList().size();
tempItemSet = tempSeq.getItemSetList().get(size - 1);
tempItemSet.getItems().add(s);
if (isLargerThanMinSupport(tempItemSet, afterSeqList)) {
tempSeqList = new ArrayList<>();
for (Sequence seq : afterSeqList) {
if (seq.compoentItemIsContain(tempItemSet)) {
tempSeq2 = seq.extractCompoentItem(tempItemSet
.getItems());
tempSeqList.add(tempSeq2);
}
}
totalFrequentSeqs.add(tempSeq);
recursiveSearchSeqs(tempSeq, tempSeqList);
}
}
}
/**
* 所傳入的項組合在所給定序列中的支持度是否超過閾值
*
* @param s
* 所需匹配的項
* @param seqList
* 比較序列數據
* @return
*/
private boolean isLargerThanMinSupport(String s, ArrayList seqList) {
boolean isLarge = false;
int count = 0;
for (Sequence seq : seqList) {
if (seq.strIsContained(s)) {
count++;
}
}
if (count >= minSupport) {
isLarge = true;
}
return isLarge;
}
/**
* 所傳入的組合項集在序列中的支持度是否大于閾值
*
* @param itemSet
* 組合元素項集
* @param seqList
* 比較的序列列表
* @return
*/
private boolean isLargerThanMinSupport(ItemSet itemSet,
ArrayList seqList) {
boolean isLarge = false;
int count = 0;
if (seqList == null) {
return false;
}
for (Sequence seq : seqList) {
if (seq.compoentItemIsContain(itemSet)) {
count++;
}
}
if (count >= minSupport) {
isLarge = true;
}
return isLarge;
}
/**
* 序列模式分析計算
*/
public void prefixSpanCalculate() {
Sequence seq;
Sequence tempSeq;
ArrayList tempSeqList = new ArrayList<>();
ItemSet itemSet;
removeInitSeqsItem();
for (String s : singleItems) {
// 從最開始的a,b,d開始遞歸往下尋找頻繁序列模式
seq = new Sequence();
itemSet = new ItemSet(s);
seq.getItemSetList().add(itemSet);
if (isLargerThanMinSupport(s, totalSeqs)) {
tempSeqList = new ArrayList<>();
for (Sequence s2 : totalSeqs) {
// 判斷單一項是否包含于在序列中,包含才進行提取操作
if (s2.strIsContained(s)) {
tempSeq = s2.extractItem(s);
tempSeqList.add(tempSeq);
}
}
totalFrequentSeqs.add(seq);
recursiveSearchSeqs(seq, tempSeqList);
}
}
printTotalFreSeqs();
}
/**
* 按模式類別輸出頻繁序列模式
*/
private void printTotalFreSeqs() {
System.out.println("序列模式挖掘結果:");
ArrayList seqList;
HashMap> seqMap = new HashMap<>();
for (String s : singleItems) {
seqList = new ArrayList<>();
for (Sequence seq : totalFrequentSeqs) {
if (seq.getItemSetList().get(0).getItems().get(0).equals(s)) {
seqList.add(seq);
}
}
seqMap.put(s, seqList);
}
int count = 0;
for (String s : singleItems) {
count = 0;
System.out.println();
System.out.println();
seqList = (ArrayList) seqMap.get(s);
for (Sequence tempSeq : seqList) {
count++;
System.out.print("
for (ItemSet itemSet : tempSeq.getItemSetList()) {
if (itemSet.getItems().size() > 1) {
System.out.print("(");
}
for (String str : itemSet.getItems()) {
System.out.print(str + " ");
}
if (itemSet.getItems().size() > 1) {
System.out.print(")");
}
}
System.out.print(">, ");
// 每5個序列換一行
if (count == 5) {
count = 0;
System.out.println();
}
}
}
}
}調用類Client.java:
package DataMining_PrefixSpan;
/**
* PrefixSpan序列模式挖掘算法
* @author lyq
*
*/
public class Client {
public static void main(String[] agrs){
String filePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";
//最小支持度閾值率
double minSupportRate = 0.4;
PrefixSpanTool tool = new PrefixSpanTool(filePath, minSupportRate);
tool.prefixSpanCalculate();
}
}輸出的結果:
原始序列數據:
序列模式挖掘結果:
, , , , ,
, , , , ,
, , , , ,
, , , , ,
, , , , ,
, , , , ,
, , ,
, , , , ,
,
, , , , ,
, , , ,
,
, , , ,經過比對,與上述表格中的結果完全一致,從結果中可以看出他的遞歸順序正是剛剛我所想要的那種。
算法實現時的難點
我在實現這個算法時確實碰到了不少的問題,下面一一列舉。
1、Sequence序列在判斷或者提取單項和組合項的時候,情況少考慮了,還有考慮到了處理的方式又可能錯了。
2、遞歸的順序在最早的時候考慮錯了,后來對遞歸的順序進行了調整。
3、在算法的調試時遇到了,當發現某一項出現問題時,不能夠立即調試,因為里面陷入的遞歸層次實在太深,只能自己先手算此情況下的前綴,后綴序列,然后自己模擬出1個Seq調試,在糾正extract方法時用的比較多。
我對PrefixSpan算法的理解
實現了這個算法之后,再回味這個算法,還是很奇妙的,一個序列,通過從左往右的掃描,通過各個項集的子集,能夠組合出許許多多的的序列模式,然后進行挖掘,PrefixSpan通過遞歸的形式全部找出,而且效率非常高,的確是個很強大的算法。
PrefixSpan算法整體的特點
首先一點,他不會產生候選序列,在產生投影數據庫的時候(也就是產生后綴子序列),他的規模是不斷減小的。PrefixSpan采用分治法進行序列的挖掘,十分的高效。唯一比較會有影響的開銷就是在構造后綴子序列的過程,專業上的名稱叫做構造投影數據庫的時候。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的prefixspan java_PrefixSpan序列模式挖掘算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 中文 图片_java之服务器端
- 下一篇: python编程狮app题库_Pyth