java gsp_GSP序列模式分析算法
介紹
GSP算法是序列模式挖掘算法的一種,他是一種類Apriori的一種,整個過程與Apriori算法比較類似,不過在細節上會略有不同,在下面的描述中,將會有所描述。GSP在原有的頻繁模式定義的概念下,增加了3個的概念。
1、加入時間約束min_gap,max_gap,要求原來的連續變為只要滿足在規定的min_gap到max_gap之間即可。
2、加入time_windows_size,只要在windows_size內的item,都可以被認為是同一ItemSet。
3、加入分類標準。
以上3點新的中的第一條特征將會在后面的算法中著重展現。
算法原理
1、根據所輸入的序列,找出所有的單項集,即1頻繁模式,這里會經過最小支持度閾值的判斷。
2、根據1頻繁模式進行連接運算,產生2頻繁模式,這里會有進行最小閾值的判斷。
3、根據2頻繁模式連接產生3頻繁模式,會經過最小支持度判斷和剪枝操作,剪枝操作的原理在于判斷他的所有子集是否也全是頻繁模式。
4、3頻繁模式不斷的挖掘知道不能夠產生出候選集為止。
連接操作的原理
2個序列,全部變為item列表的形式,如果a序列去掉第1個元素后,b序列去掉最后1個序列,2個序列的item完全一致,則代表可以連接,由b的最后一個元素加入到a中,至于是以獨立項集的身份加入還是加入到a中最后1個項集中取決于b中的最后一個元素所屬項集是否為單項項集。
時間約束計算
這個是用在支持度計數使用的,GSP算法的支持度計算不是那么簡單,比如序列判斷<2, <3, 4>>是否在序列, 2>,這就不能僅僅判斷序列中是否只包含2,<3, 4>就行了,還要滿足時間間隔約束,這就要把2,和<3,4>的所有出現時間都找出來,然后再里面找出一條滿足時間約束的路徑就算包含。時間的定義是從左往右起1.2,3...繼續,以1個項集為單位,所有2的時間有2個分別為t=2和t=4,然后同理,因為<3,4>在序列中只有1次,所以時間為t=3,所以問題就變為了下面一個數組的問題
2 ?4
3
從時間數組的上往下,通過對多個時間的組合,找出1條滿足時間約束的方案,這里的方案只有2-3,4-3,然后判斷時間間隔,如果存在這樣的方式,則代表此序列支持所給定序列,支持度值加1,這個算法在程序的實現中是比較復雜的。
算法的代碼實現
測試數據輸入(格式:事務ID item數 item1 item2.....):
1 2 1 5
1 1 2
1 1 3
1 1 4
2 1 1
2 1 3
2 1 4
2 2 3 5
3 1 1
3 1 2
3 1 3
3 1 4
3 1 5
4 1 1
4 1 3
4 1 5
5 1 4
5 1 5最后組成的序列為:
<1 3 4 (3,5)>
<1 2 3 4 5>
<1 3 5>
<4 5>
也就是說同一序列都是同事務的。下面是關鍵的類
Sequence.java:
package DataMining_GSP;
import java.util.ArrayList;
/**
* 序列,每個序列內部包含多組ItemSet項集
*
* @author lyq
*
*/
public class Sequence implements Comparable, Cloneable {
// 序列所屬事務ID
private int trsanctionID;
// 項集列表
private ArrayList itemSetList;
public Sequence(int trsanctionID) {
this.trsanctionID = trsanctionID;
this.itemSetList = new ArrayList<>();
}
public Sequence() {
this.itemSetList = new ArrayList<>();
}
public int getTrsanctionID() {
return trsanctionID;
}
public void setTrsanctionID(int trsanctionID) {
this.trsanctionID = trsanctionID;
}
public ArrayList getItemSetList() {
return itemSetList;
}
public void setItemSetList(ArrayList itemSetList) {
this.itemSetList = itemSetList;
}
/**
* 取出序列中第一個項集的第一個元素
*
* @return
*/
public Integer getFirstItemSetNum() {
return this.getItemSetList().get(0).getItems().get(0);
}
/**
* 獲取序列中最后一個項集
*
* @return
*/
public ItemSet getLastItemSet() {
return getItemSetList().get(getItemSetList().size() - 1);
}
/**
* 獲取序列中最后一個項集的最后一個一個元素
*
* @return
*/
public Integer getLastItemSetNum() {
ItemSet lastItemSet = getItemSetList().get(getItemSetList().size() - 1);
int lastItemNum = lastItemSet.getItems().get(
lastItemSet.getItems().size() - 1);
return lastItemNum;
}
/**
* 判斷序列中最后一個項集是否為單一的值
*
* @return
*/
public boolean isLastItemSetSingleNum() {
ItemSet lastItemSet = getItemSetList().get(getItemSetList().size() - 1);
int size = lastItemSet.getItems().size();
return size == 1 ? true : false;
}
@Override
public int compareTo(Sequence o) {
// TODO Auto-generated method stub
return this.getFirstItemSetNum().compareTo(o.getFirstItemSetNum());
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
/**
* 拷貝一份一模一樣的序列
*/
public Sequence copySeqence(){
Sequence copySeq = new Sequence();
for(ItemSet itemSet: this.itemSetList){
copySeq.getItemSetList().add(new ItemSet(itemSet.copyItems()));
}
return copySeq;
}
/**
* 比較2個序列是否相等,需要判斷內部的每個項集是否完全一致
*
* @param seq
* 比較的序列對象
* @return
*/
public boolean compareIsSame(Sequence seq) {
boolean result = true;
ArrayList itemSetList2 = seq.getItemSetList();
ItemSet tempItemSet1;
ItemSet tempItemSet2;
if (itemSetList2.size() != this.itemSetList.size()) {
return false;
}
for (int i = 0; i < itemSetList2.size(); i++) {
tempItemSet1 = this.itemSetList.get(i);
tempItemSet2 = itemSetList2.get(i);
if (!tempItemSet1.compareIsSame(tempItemSet2)) {
// 只要不相等,直接退出函數
result = false;
break;
}
}
return result;
}
/**
* 生成此序列的所有子序列
*
* @return
*/
public ArrayList createChildSeqs() {
ArrayList childSeqs = new ArrayList<>();
ArrayList tempItems;
Sequence tempSeq = null;
ItemSet tempItemSet;
for (int i = 0; i < this.itemSetList.size(); i++) {
tempItemSet = itemSetList.get(i);
if (tempItemSet.getItems().size() == 1) {
tempSeq = this.copySeqence();
// 如果只有項集中只有1個元素,則直接移除
tempSeq.itemSetList.remove(i);
childSeqs.add(tempSeq);
} else {
tempItems = tempItemSet.getItems();
for (int j = 0; j < tempItems.size(); j++) {
tempSeq = this.copySeqence();
// 在拷貝的序列中移除一個數字
tempSeq.getItemSetList().get(i).getItems().remove(j);
childSeqs.add(tempSeq);
}
}
}
return childSeqs;
}
}ItemSet.java:
package DataMining_GSP;
import java.util.ArrayList;
/**
* 序列中的子項集
*
* @author lyq
*
*/
public class ItemSet {
/**
* 項集中保存的是數字項數組
*/
private ArrayList items;
public ItemSet(String[] itemStr) {
items = new ArrayList<>();
for (String s : itemStr) {
items.add(Integer.parseInt(s));
}
}
public ItemSet(int[] itemNum) {
items = new ArrayList<>();
for (int num : itemNum) {
items.add(num);
}
}
public ItemSet(ArrayList itemNum) {
this.items = itemNum;
}
public ArrayList getItems() {
return items;
}
public void setItems(ArrayList items) {
this.items = items;
}
/**
* 判斷2個項集是否相等
*
* @param itemSet
* 比較對象
* @return
*/
public boolean compareIsSame(ItemSet itemSet) {
boolean result = true;
if (this.items.size() != itemSet.items.size()) {
return false;
}
for (int i = 0; i < itemSet.items.size(); i++) {
if (this.items.get(i) != itemSet.items.get(i)) {
// 只要有值不相等,直接算作不相等
result = false;
break;
}
}
return result;
}
/**
* 拷貝項集中同樣的數據一份
*
* @return
*/
public ArrayList copyItems() {
ArrayList copyItems = new ArrayList<>();
for (int num : this.items) {
copyItems.add(num);
}
return copyItems;
}
}GSPTool.java(算法工具類):
package DataMining_GSP;
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;
/**
* GSP序列模式分析算法
*
* @author lyq
*
*/
public class GSPTool {
// 測試數據文件地址
private String filePath;
// 最小支持度閾值
private int minSupportCount;
// 時間最小間隔
private int min_gap;
// 時間最大間隔
private int max_gap;
// 原始數據序列
private ArrayList totalSequences;
// GSP算法中產生的所有的頻繁項集序列
private ArrayList totalFrequencySeqs;
// 序列項數字對時間的映射圖容器
private ArrayList>> itemNum2Time;
public GSPTool(String filePath, int minSupportCount, int min_gap,
int max_gap) {
this.filePath = filePath;
this.minSupportCount = minSupportCount;
this.min_gap = min_gap;
this.max_gap = max_gap;
totalFrequencySeqs = new ArrayList<>();
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();
}
HashMap mapSeq = new HashMap<>();
Sequence seq;
ItemSet itemSet;
int tID;
String[] itemStr;
for (String[] str : dataArray) {
tID = Integer.parseInt(str[0]);
itemStr = new String[Integer.parseInt(str[1])];
System.arraycopy(str, 2, itemStr, 0, itemStr.length);
itemSet = new ItemSet(itemStr);
if (mapSeq.containsKey(tID)) {
seq = mapSeq.get(tID);
} else {
seq = new Sequence(tID);
}
seq.getItemSetList().add(itemSet);
mapSeq.put(tID, seq);
}
// 將序列圖加入到序列List中
totalSequences = new ArrayList<>();
for (Map.Entry entry : mapSeq.entrySet()) {
totalSequences.add((Sequence) entry.getValue());
}
}
/**
* 生成1頻繁項集
*
* @return
*/
private ArrayList generateOneFrequencyItem() {
int count = 0;
int currentTransanctionID = 0;
Sequence tempSeq;
ItemSet tempItemSet;
HashMap itemNumMap = new HashMap<>();
ArrayList seqList = new ArrayList<>();
for (Sequence seq : totalSequences) {
for (ItemSet itemSet : seq.getItemSetList()) {
for (int num : itemSet.getItems()) {
// 如果沒有此種類型項,則進行添加操作
if (!itemNumMap.containsKey(num)) {
itemNumMap.put(num, 1);
}
}
}
}
boolean isContain = false;
int number = 0;
for (Map.Entry entry : itemNumMap.entrySet()) {
count = 0;
number = (int) entry.getKey();
for (Sequence seq : totalSequences) {
isContain = false;
for (ItemSet itemSet : seq.getItemSetList()) {
for (int num : itemSet.getItems()) {
if (num == number) {
isContain = true;
break;
}
}
if(isContain){
break;
}
}
if(isContain){
count++;
}
}
itemNumMap.put(number, count);
}
for (Map.Entry entry : itemNumMap.entrySet()) {
count = (int) entry.getValue();
if (count >= minSupportCount) {
tempSeq = new Sequence();
tempItemSet = new ItemSet(new int[] { (int) entry.getKey() });
tempSeq.getItemSetList().add(tempItemSet);
seqList.add(tempSeq);
}
}
// 將序列升序排列
Collections.sort(seqList);
// 將頻繁1項集加入總頻繁項集列表中
totalFrequencySeqs.addAll(seqList);
return seqList;
}
/**
* 通過1頻繁項集連接產生2頻繁項集
*
* @param oneSeq
* 1頻繁項集序列
* @return
*/
private ArrayList generateTwoFrequencyItem(
ArrayList oneSeq) {
Sequence tempSeq;
ArrayList resultSeq = new ArrayList<>();
ItemSet tempItemSet;
int num1;
int num2;
// 假如將,2個1頻繁項集做連接組合,可以分為
// 注意此時的每個序列中包含2個獨立項集
for (int i = 0; i < oneSeq.size(); i++) {
num1 = oneSeq.get(i).getFirstItemSetNum();
for (int j = 0; j < oneSeq.size(); j++) {
num2 = oneSeq.get(j).getFirstItemSetNum();
tempSeq = new Sequence();
tempItemSet = new ItemSet(new int[] { num1 });
tempSeq.getItemSetList().add(tempItemSet);
tempItemSet = new ItemSet(new int[] { num2 });
tempSeq.getItemSetList().add(tempItemSet);
if (countSupport(tempSeq) >= minSupportCount) {
resultSeq.add(tempSeq);
}
}
}
// 上面連接還有1種情況是每個序列中只包含有一個項集的情況,此時a,b的劃分則是
for (int i = 0; i < oneSeq.size(); i++) {
num1 = oneSeq.get(i).getFirstItemSetNum();
for (int j = i; j < oneSeq.size(); j++) {
num2 = oneSeq.get(j).getFirstItemSetNum();
tempSeq = new Sequence();
tempItemSet = new ItemSet(new int[] { num1, num2 });
tempSeq.getItemSetList().add(tempItemSet);
if (countSupport(tempSeq) >= minSupportCount) {
resultSeq.add(tempSeq);
}
}
}
// 同樣將2頻繁項集加入到總頻繁項集中
totalFrequencySeqs.addAll(resultSeq);
return resultSeq;
}
/**
* 根據上次的頻繁集連接產生新的侯選集
*
* @param seqList
* 上次產生的候選集
* @return
*/
private ArrayList generateCandidateItem(
ArrayList seqList) {
Sequence tempSeq;
ArrayList tempNumArray;
ArrayList resultSeq = new ArrayList<>();
// 序列數字項列表
ArrayList> seqNums = new ArrayList<>();
for (int i = 0; i < seqList.size(); i++) {
tempNumArray = new ArrayList<>();
tempSeq = seqList.get(i);
for (ItemSet itemSet : tempSeq.getItemSetList()) {
tempNumArray.addAll(itemSet.copyItems());
}
seqNums.add(tempNumArray);
}
ArrayList array1;
ArrayList array2;
// 序列i,j的拷貝
Sequence seqi = null;
Sequence seqj = null;
// 判斷是否能夠連接,默認能連接
boolean canConnect = true;
// 進行連接運算,包括自己與自己連接
for (int i = 0; i < seqNums.size(); i++) {
for (int j = 0; j < seqNums.size(); j++) {
array1 = (ArrayList) seqNums.get(i).clone();
array2 = (ArrayList) seqNums.get(j).clone();
// 將第一個數字組去掉第一個,第二個數字組去掉最后一個,如果剩下的部分相等,則可以連接
array1.remove(0);
array2.remove(array2.size() - 1);
canConnect = true;
for (int k = 0; k < array1.size(); k++) {
if (array1.get(k) != array2.get(k)) {
canConnect = false;
break;
}
}
if (canConnect) {
seqi = seqList.get(i).copySeqence();
seqj = seqList.get(j).copySeqence();
int lastItemNum = seqj.getLastItemSetNum();
if (seqj.isLastItemSetSingleNum()) {
// 如果j序列的最后項集為單一值,則最后一個數字以獨立項集加入i序列
ItemSet itemSet = new ItemSet(new int[] { lastItemNum });
seqi.getItemSetList().add(itemSet);
} else {
// 如果j序列的最后項集為非單一值,則最后一個數字加入i序列最后一個項集中
ItemSet itemSet = seqi.getLastItemSet();
itemSet.getItems().add(lastItemNum);
}
// 判斷是否超過最小支持度閾值
if (isChildSeqContained(seqi)
&& countSupport(seqi) >= minSupportCount) {
resultSeq.add(seqi);
}
}
}
}
totalFrequencySeqs.addAll(resultSeq);
return resultSeq;
}
/**
* 判斷此序列的所有子序列是否也是頻繁序列
*
* @param seq
* 待比較序列
* @return
*/
private boolean isChildSeqContained(Sequence seq) {
boolean isContained = false;
ArrayList childSeqs;
childSeqs = seq.createChildSeqs();
for (Sequence tempSeq : childSeqs) {
isContained = false;
for (Sequence frequencySeq : totalFrequencySeqs) {
if (tempSeq.compareIsSame(frequencySeq)) {
isContained = true;
break;
}
}
if (!isContained) {
break;
}
}
return isContained;
}
/**
* 候選集判斷支持度的值
*
* @param seq
* 待判斷序列
* @return
*/
private int countSupport(Sequence seq) {
int count = 0;
int matchNum = 0;
Sequence tempSeq;
ItemSet tempItemSet;
HashMap timeMap;
ArrayList itemSetList;
ArrayList> numArray = new ArrayList<>();
// 每項集對應的時間鏈表
ArrayList> timeArray = new ArrayList<>();
for (ItemSet itemSet : seq.getItemSetList()) {
numArray.add(itemSet.getItems());
}
for (int i = 0; i < totalSequences.size(); i++) {
timeArray = new ArrayList<>();
for (int s = 0; s < numArray.size(); s++) {
ArrayList childNum = numArray.get(s);
ArrayList localTime = new ArrayList<>();
tempSeq = totalSequences.get(i);
itemSetList = tempSeq.getItemSetList();
for (int j = 0; j < itemSetList.size(); j++) {
tempItemSet = itemSetList.get(j);
matchNum = 0;
int t = 0;
if (tempItemSet.getItems().size() == childNum.size()) {
timeMap = itemNum2Time.get(i).get(j);
// 只有當項集長度匹配時才匹配
for (int k = 0; k < childNum.size(); k++) {
if (timeMap.containsKey(childNum.get(k))) {
matchNum++;
t = timeMap.get(childNum.get(k));
}
}
// 如果完全匹配,則記錄時間
if (matchNum == childNum.size()) {
localTime.add(t);
}
}
}
if (localTime.size() > 0) {
timeArray.add(localTime);
}
}
// 判斷時間是否滿足時間最大最小約束,如果滿足,則此條事務包含候選事務
if (timeArray.size() == numArray.size()
&& judgeTimeInGap(timeArray)) {
count++;
}
}
return count;
}
/**
* 判斷事務是否滿足時間約束
*
* @param timeArray
* 時間數組,每行代表各項集的在事務中的發生時間鏈表
* @return
*/
private boolean judgeTimeInGap(ArrayList> timeArray) {
boolean result = false;
int preTime = 0;
ArrayList firstTimes = timeArray.get(0);
timeArray.remove(0);
if (timeArray.size() == 0) {
return false;
}
for (int i = 0; i < firstTimes.size(); i++) {
preTime = firstTimes.get(i);
if (dfsJudgeTime(preTime, timeArray)) {
result = true;
break;
}
}
return result;
}
/**
* 深度優先遍歷時間,判斷是否有符合條件的時間間隔
*
* @param preTime
* @param timeArray
* @return
*/
private boolean dfsJudgeTime(int preTime,
ArrayList> timeArray) {
boolean result = false;
ArrayList> timeArrayClone = (ArrayList>) timeArray
.clone();
ArrayList firstItemItem = timeArrayClone.get(0);
for (int i = 0; i < firstItemItem.size(); i++) {
if (firstItemItem.get(i) - preTime >= min_gap
&& firstItemItem.get(i) - preTime <= max_gap) {
// 如果此2項間隔時間滿足時間約束,則繼續往下遞歸
preTime = firstItemItem.get(i);
timeArrayClone.remove(0);
if (timeArrayClone.size() == 0) {
return true;
} else {
result = dfsJudgeTime(preTime, timeArrayClone);
if (result) {
return true;
}
}
}
}
return result;
}
/**
* 初始化序列項到時間的序列圖,為了后面的時間約束計算
*/
private void initItemNumToTimeMap() {
Sequence seq;
itemNum2Time = new ArrayList<>();
HashMap tempMap;
ArrayList> tempMapList;
for (int i = 0; i < totalSequences.size(); i++) {
seq = totalSequences.get(i);
tempMapList = new ArrayList<>();
for (int j = 0; j < seq.getItemSetList().size(); j++) {
ItemSet itemSet = seq.getItemSetList().get(j);
tempMap = new HashMap<>();
for (int itemNum : itemSet.getItems()) {
tempMap.put(itemNum, j + 1);
}
tempMapList.add(tempMap);
}
itemNum2Time.add(tempMapList);
}
}
/**
* 進行GSP算法計算
*/
public void gspCalculate() {
ArrayList oneSeq;
ArrayList twoSeq;
ArrayList candidateSeq;
initItemNumToTimeMap();
oneSeq = generateOneFrequencyItem();
twoSeq = generateTwoFrequencyItem(oneSeq);
candidateSeq = twoSeq;
// 不斷連接生產候選集,直到沒有產生出侯選集
for (;;) {
candidateSeq = generateCandidateItem(candidateSeq);
if (candidateSeq.size() == 0) {
break;
}
}
outputSeqence(totalFrequencySeqs);
}
/**
* 輸出序列列表信息
*
* @param outputSeqList
* 待輸出序列列表
*/
private void outputSeqence(ArrayList outputSeqList) {
for (Sequence seq : outputSeqList) {
System.out.print("
for (ItemSet itemSet : seq.getItemSetList()) {
System.out.print("(");
for (int num : itemSet.getItems()) {
System.out.print(num + ",");
}
System.out.print("), ");
}
System.out.println(">");
}
}
}調用類Client.java:
package DataMining_GSP;
/**
* GSP序列模式分析算法
* @author lyq
*
*/
public class Client {
public static void main(String[] args){
String filePath = "C:\\Users\\lyq\\Desktop\\icon\\testInput.txt";
//最小支持度閾值
int minSupportCount = 2;
//時間最小間隔
int min_gap = 1;
//施加最大間隔
int max_gap = 5;
GSPTool tool = new GSPTool(filePath, minSupportCount, min_gap, max_gap);
tool.gspCalculate();
}
}算法的輸出(挖掘出的所有頻繁模式):
算法實現的難點
1、算法花費了幾天的時間,難點首先在于對算法原理本身的理解,網上對于此算法的資料特別少,而且不同的人所表達的意思 都有少許的不同,講的也不是很詳細,于是就通過閱讀別人的代碼理解GSP算法的原理,我的代碼實現也是參考了參考資料的C語言的實現。
2、在實現時間約束的支持度計數統計的時候,調試了一段時間,做時間統計容易出錯,因為層級實在太多容易搞暈。
3、還有1個是Sequence和ItemSet的拷貝時的引用問題,在產生新的序列時一定要深拷貝1個否則導致同一引用會把原數據給改掉的。
GSP算法和Apriori算法的比較
我是都實現過了GSP算法和Apriori算法的,后者是被稱為關聯規則挖掘算法,偏向于挖掘關聯規則的,2個算法在連接的操作上有不一樣的地方,還有在數據的構成方式上,Apriori的數據會簡單一點,都是單項單項構成的,而且在做支持度統計的時候只需判斷存在與否即可。不需要考慮時間約束。Apriori算法給定K項集,連接到K-1項集算法就停止了,而GSP算法是直到不能夠產生候選集為止。
總結
以上是生活随笔為你收集整理的java gsp_GSP序列模式分析算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 判断端口是否被占用_java检
- 下一篇: java反射 初始化bean_通用jav