頻繁模式挖掘apriori算法介紹及Java實現


頻繁模式是頻繁地出如今數據集中的模式(如項集、子序列或者子結構)。比如。頻繁地同一時候出如今交易數據集中的商品(如牛奶和面包)的集合是頻繁項集。


一些基本概念

支持度:support(A=>B)=P(A並B)

置信度:confidence(A=>B)=P(B|A)

頻繁k項集:假設項集I的支持度滿足提前定義的最小支持度閾值。則稱I為頻繁項集,包括k個項的項集稱為k項集。


算法思想

Apriori算法是Agrawal和R. Srikant於1994年提出。為布爾關聯規則挖掘頻繁項集的原創性算法。

通過名字能夠看出算法基於這樣一個事實:算法使用頻繁項集性質的先驗知識。

apriori算法使用一種成為逐層搜索的迭代算法,當中k項集用於探索(k+1)項集。首先,通過掃描數據庫,累計每一個項的計數。並搜集滿足最小支持度的項,找出頻繁1項集的集合。該集合記為L1。然后,使用L1找出頻繁2項集的集合L2,使用L2找出L3。如此下去,直到不能再找到頻繁k項集。

能夠想象。該算法基本思路計算復雜度是很大的。為了提高頻繁項集的產生效率,使用先驗性質(頻繁項集的全部非空子集也一定是頻繁的;換句話說。若某個集合存在一個非空子集不是頻繁項集,則該集合不是頻繁項集)來壓縮搜索空間。

怎樣在算法中使用先驗性質?為了理解這一點。我們考察怎樣使用Lk-1找出Lk,當中k>=2。

主要由兩步構成:連接步和剪枝步。

連接步:為找出Lk。通過將Lk-1與自身相連接產生候選集k項集的集合。

該候選集的集合記為Ck。設l1和l2是Lk-1中的項集。記號li[j]表示li的第j項(比如。l1[k-2]表示l1的倒數第2項)。為了有效實現。apriori算法假定事務或項集中的項按字典序排列。

對於(k-1)項集li,這意味着把項排序,使得li[1]<li[2]<...<li[k-1]。連接Lk-1和Lk-1;當中Lk-1的元素是可連接的。假設它們前(k-2)項同樣。即Lk-1的元素l1和l2是可連接的,假設(l1[1]=l2[1])^(l1[2]=l2[2])^...^(l1[k-2]=l2[k-2])^(l1[k-1]<l2[k-1])。條件l1[k-1]<l2[k-1]是簡單保證不產生反復。連接l1和l2產生的結果項集是{l1[1],l1[2],...,l1[k-1],l2[k-1]}

剪枝步: CK是LK的超集,也就是說,CK的成員可能是也可能不是頻繁的。

通過掃描全部的事務(交易),確定CK中每一個候選的計數,推斷是否小於最小支持度計數,假設不是。則覺得該候選是頻繁的。為了壓縮Ck,能夠利用Apriori性質:任一頻繁項集的全部非空子集也必須是頻繁的,反之,假設某個候選的非空子集不是頻繁的,那么該候選肯定不是頻繁的,從而能夠將其從CK中刪除。

(該步利用了標紅的先驗性質)


圖例



偽代碼

算法:Apriori
輸入:D - 事務數據庫;min_sup - 最小支持度計數閾值
輸出:L - D中的頻繁項集
方法:
     L1=find_frequent_1-itemsets(D); // 找出全部頻繁1項集
     For(k=2;Lk-1!=null;k++){
        Ck=apriori_gen(Lk-1); // 產生候選,並剪枝
        For each 事務t in D{ // 掃描D進行候選計數
            Ct =subset(Ck,t); // 得到t的子集
            For each 候選c 屬於 Ct
                         c.count++;
        }
        Lk={c屬於Ck | c.count>=min_sup}
}
Return L=全部的頻繁集;

Procedure apriori_gen(Lk-1:frequent(k-1)-itemsets)
      For each項集l1屬於Lk-1
              For each項集 l2屬於Lk-1
                       If((l1[1]=l2[1])&&( l1[2]=l2[2])&&…….
&& (l1[k-2]=l2[k-2])&&(l1[k-1]<l2[k-1])) then{
                   c=l1連接l2 //連接步:產生候選
                   if has_infrequent_subset(c,Lk-1) then
                       delete c; //剪枝步:刪除非頻繁候選
                   else add c to Ck;
                  }
          Return Ck;

Procedure has_infrequent_sub(c:candidate k-itemset; Lk-1:frequent(k-1)-itemsets)
        For each(k-1)-subset s of c
            If s不屬於Lk-1 then
               Return true;
        Return false;


Java實現

該java代碼基本上是嚴格依照偽代碼的流程寫的。比較easy理解。

package com.zhyoulun.apriori;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Apriori2
{
	private final static int SUPPORT = 2; // 支持度閾值
	private final static double CONFIDENCE = 0.7; // 置信度閾值
	private final static String ITEM_SPLIT = ";"; // 項之間的分隔符
	private final static String CON = "->"; // 項之間的分隔符
	
	/**
	 * 算法主程序
	 * @param dataList
	 * @return
	 */
	public Map<String, Integer> apriori(ArrayList<String> dataList)
	{
		Map<String, Integer> stepFrequentSetMap = new HashMap<>();
		stepFrequentSetMap.putAll(findFrequentOneSets(dataList));
		
		Map<String, Integer> frequentSetMap = new HashMap<String, Integer>();//頻繁項集
		frequentSetMap.putAll(stepFrequentSetMap);
		
		while(stepFrequentSetMap!=null && stepFrequentSetMap.size()>0)
		{
			Map<String, Integer> candidateSetMap = aprioriGen(stepFrequentSetMap);
			
			Set<String> candidateKeySet = candidateSetMap.keySet();
			
			//掃描D,進行計數
			for(String data:dataList)
			{
				for(String candidate:candidateKeySet)
				{
					boolean flag = true;
					String[] strings = candidate.split(ITEM_SPLIT);
					for(String string:strings)
					{
						if(data.indexOf(string+ITEM_SPLIT)==-1)
						{
							flag = false;
							break;
						}
					}
					if(flag)
						candidateSetMap.put(candidate, candidateSetMap.get(candidate)+1);
				}
			}
			
			//從候選集中找到符合支持度的頻繁項集
			stepFrequentSetMap.clear();
			for(String candidate:candidateKeySet)
			{
				Integer count = candidateSetMap.get(candidate);
				if(count>=SUPPORT)
					stepFrequentSetMap.put(candidate, count);
			}
	
			// 合並全部頻繁集
			frequentSetMap.putAll(stepFrequentSetMap);
		}
		
		return frequentSetMap;
	}
	
	/**
	 * find frequent 1 itemsets
	 * @param dataList
	 * @return
	 */
	private Map<String, Integer> findFrequentOneSets(ArrayList<String> dataList)
	{
		Map<String, Integer> resultSetMap = new HashMap<>();
		
		for(String data:dataList)
		{
			String[] strings = data.split(ITEM_SPLIT);
			for(String string:strings)
			{
				string += ITEM_SPLIT;
				if(resultSetMap.get(string)==null)
				{
					resultSetMap.put(string, 1);
				}
				else {
					resultSetMap.put(string, resultSetMap.get(string)+1);
				}
			}
		}
		return resultSetMap;
	}
	
	/**
	 * 依據上一步的頻繁項集的集合選出候選集
	 * @param setMap
	 * @return
	 */
	private Map<String, Integer> aprioriGen(Map<String, Integer> setMap)
	{
		Map<String, Integer> candidateSetMap = new HashMap<>();
		
		Set<String> candidateSet = setMap.keySet();
		for(String s1:candidateSet)
		{
			String[] strings1 = s1.split(ITEM_SPLIT);
			String s1String = "";
			for(String temp:strings1)
				s1String += temp+ITEM_SPLIT;
			
			for(String s2:candidateSet)
			{
				String[] strings2 = s2.split(ITEM_SPLIT);
				
				
				boolean flag = true;
				for(int i=0;i<strings1.length-1;i++)
				{
					if(strings1[i].compareTo(strings2[i])!=0)
					{
						flag = false;
						break;
					}
				}
				if(flag && strings1[strings1.length-1].compareTo(strings2[strings1.length-1])<0)
				{
					//連接步:產生候選
					String c = s1String+strings2[strings2.length-1]+ITEM_SPLIT;
					if(hasInfrequentSubset(c, setMap))
					{
						//剪枝步:刪除非頻繁的候選
					}
					else {
						candidateSetMap.put(c, 0);
					}
				}
			}
		}
		
		return candidateSetMap;
	}
	
	/**
	 * 使用先驗知識,推斷候選集是否是頻繁項集
	 * @param candidate
	 * @param setMap
	 * @return
	 */
	private boolean hasInfrequentSubset(String candidateSet, Map<String, Integer> setMap)
	{
		String[] strings = candidateSet.split(ITEM_SPLIT);
		
		//找出候選集全部的子集,並推斷每一個子集是否屬於頻繁子集
		for(int i=0;i<strings.length;i++)
		{
			String subString = "";
			for(int j=0;j<strings.length;j++)
			{
				if(j!=i)
				{
					subString += strings[j]+ITEM_SPLIT;
				}
			}
			
			if(setMap.get(subString)==null)
				return true;
		}
		
		return false;
	}
	
	/**
	 * 由頻繁項集產生關聯規則
	 * @param frequentSetMap
	 * @return
	 */
	public Map<String, Double> getRelationRules(Map<String, Integer> frequentSetMap)
	{
		Map<String, Double> relationsMap = new HashMap<>();
		Set<String> keySet = frequentSetMap.keySet();

		for(String key:keySet)
		{
			List<String> keySubset = subset(key);
			for(String keySubsetItem:keySubset)
			{
				//子集keySubsetItem也是頻繁項
				Integer count = frequentSetMap.get(keySubsetItem);
				if(count!=null)
				{
					Double confidence = (1.0*frequentSetMap.get(key))/(1.0*frequentSetMap.get(keySubsetItem));
					if(confidence>CONFIDENCE)
						relationsMap.put(keySubsetItem+CON+expect(key, keySubsetItem), confidence);
				}
			}
		}
		
		return relationsMap;
	}
	
	/**
	 * 求一個集合全部的非空真子集
	 * 
	 * @param sourceSet
	 * @return
	 * 為了以后能夠用在其它地方。這里我們不是用遞歸的方法
	 * 
	 * 參考:http://blog.163.com/xiaohui_1123@126/blog/static/3980524020109784356915/
	 * 思路:如果集合S(A,B,C,D)。其大小為4。擁有2的4次方個子集,即0-15,二進制表示為0000,0001。...,1111。
	 * 相應的子集為空集。{D},...。{A,B,C,D}。
	 */
	private List<String> subset(String sourceSet)
	{
		List<String> result = new ArrayList<>();
		
		String[] strings = sourceSet.split(ITEM_SPLIT);
		//非空真子集
		for(int i=1;i<(int)(Math.pow(2, strings.length))-1;i++)
		{
			String item = "";
			String flag = "";
			int ii=i;
			do
			{
				flag += ""+ii%2;
				ii = ii/2;
			} while (ii>0);
			for(int j=flag.length()-1;j>=0;j--)
			{
				if(flag.charAt(j)=='1')
				{
					item = strings[j]+ITEM_SPLIT+item;
				}
			}
			result.add(item);
		}
		
		return result;
	}
	
	/**
	 * 集合運算,A/B
	 * @param A
	 * @param B
	 * @return
	 */
	private String expect(String stringA,String stringB)
	{
		String result = "";
		
		String[] stringAs = stringA.split(ITEM_SPLIT);
		String[] stringBs = stringB.split(ITEM_SPLIT);
		
		for(int i=0;i<stringAs.length;i++)
		{
			boolean flag = true;
			for(int j=0;j<stringBs.length;j++)
			{
				if(stringAs[i].compareTo(stringBs[j])==0)
				{
					flag = false;
					break;
				}
			}
			if(flag)
				result += stringAs[i]+ITEM_SPLIT;
		}
		
		return result;
	}
	
	
	
	public static void main(String[] args)
	{
		ArrayList<String> dataList = new ArrayList<>();
		dataList.add("1;2;5;");
		dataList.add("2;4;");
		dataList.add("2;3;");
		dataList.add("1;2;4;");
		dataList.add("1;3;");
		dataList.add("2;3;");
		dataList.add("1;3;");
		dataList.add("1;2;3;5;");
		dataList.add("1;2;3;");
		
		System.out.println("=數據集合==========");
		for(String string:dataList)
		{
			System.out.println(string);
		}
		
		Apriori2 apriori2 = new Apriori2();
		
		System.out.println("=頻繁項集==========");
		
		Map<String, Integer> frequentSetMap = apriori2.apriori(dataList);
		Set<String> keySet = frequentSetMap.keySet();
		for(String key:keySet)
		{
			System.out.println(key+" : "+frequentSetMap.get(key));
		}
		
		System.out.println("=關聯規則==========");
		Map<String, Double> relationRulesMap = apriori2.getRelationRules(frequentSetMap);
		Set<String> rrKeySet = relationRulesMap.keySet();
		for (String rrKey : rrKeySet)
		{
			System.out.println(rrKey + "  :  " + relationRulesMap.get(rrKey));
		}
		
	}
}


計算結果

=數據集合==========
1;2;5;
2;4;
2;3;
1;2;4;
1;3;
2;3;
1;3;
1;2;3;5;
1;2;3;
=頻繁項集==========
1;2; : 4
1;3; : 4
5; : 2
2;3; : 4
4; : 2
2;4; : 2
1;5; : 2
3; : 6
2; : 7
1; : 6
1;2;5; : 2
1;2;3; : 2
2;5; : 2
=關聯規則==========
4;->2;  :  1.0
5;->1;2;  :  1.0
5;->1;  :  1.0
1;5;->2;  :  1.0
5;->2;  :  1.0
2;5;->1;  :  1.0


參考:

http://blog.csdn.net/zjd950131/article/details/8071414

http://www.cnblogs.com/zacard-orc/p/3646979.html

數據挖掘:概念與技術


轉載請注明出處:http://blog.csdn.net/zhyoulun/article/details/41978401



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM