在關聯規則挖掘領域最經典的算法法是Apriori,其致命的缺點是需要多次掃描事務數據庫。於是人們提出了各種裁剪(prune)數據集的方法以減少I/O開支,韓嘉煒老師的FP-Tree算法就是其中非常高效的一種。
名詞約定
舉個例子,設事務數據庫為:
A E F G
A F G
A B E F G
E F G
每一行為一個事務,事務由若干個互不相同的項目構成,任意幾個項目的組合稱為一個模式。
上例中一共有4個事務。
模式{A,F,G}的支持數為3,支持度為3/4。支持數大於閾值minSuport的模式稱為頻繁模式(Frequent Patten)。
{F,G}的支持度數為4,支持度為4/4。
{A}的支持度數為3,支持度為3/4。
{F,G}=>{A}的置信度為:{A,F,G}的支持度數 除以 {F,G}的支持度數,即3/4
{A}=>{F,G}的置信度為:{A,F,G}的支持度數 除以 {A}的支持度數,即3/3
強關聯規則挖掘是在滿足一定支持度的情況下尋找置信度達到閾值的所有模式。
FP-Tree算法描述
算法描述:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
輸入:事務集合 List<List<String>> transactions
輸出:頻繁模式集合及相應的頻數 Map<List<String>,Integer> FrequentPattens
初始化 PostModel=[],CPB=transactions
void
FPGrowth(List<List<String>> CPB,List<String> PostModel){
if
CPB為空:
return
統計CPB中每一個項目的計數,把計數小於最小支持數minSuport的刪除掉,對於CPB中的每一條事務按項目計數降序排列。
由CPB構建FP-Tree,FP-Tree中包含了表頭項headers,每一個header都指向了一個鏈表HeaderLinkList,鏈表中的每個元素都是FP-Tree上的一個節點,且節點名稱與header.name相同。
for
header in headers:
newPostModel=header.name+PostModel
把<newPostModel, header.count>加到FrequentPattens中。
newCPB=[]
for
TreeNode in HeaderLinkList:
得到從FP-Tree的根節點到TreeNode的全路徑path,把path作為一個事務添加到newCPB中,要重復添加TreeNode.count次。
FPGrowth(newCPB,newPostModel)
|
算法的核心是FPGrowth函數,這是一個遞歸函數。CPB的全稱是Conditional Pattern Base(條件模式基),我們可以把CPB理解為算法在不同階段的事務集合。PostModel稱為后綴模式,它是一個List。后文會詳細講CPB和PostModel是如何生成的,初始時令PostModel為空,令CPB就是原始的事務集合。
下面我們舉個例子來詳細講解FPGrowth函數的完整實現。
事務數據庫如下,一行表示一條購物記錄:
牛奶,雞蛋,面包,薯片
雞蛋,爆米花,薯片,啤酒
雞蛋,面包,薯片
牛奶,雞蛋,面包,爆米花,薯片,啤酒
牛奶,面包,啤酒
雞蛋,面包,啤酒
牛奶,面包,薯片
牛奶,雞蛋,面包,黃油,薯片
牛奶,雞蛋,黃油,薯片
令minSuport=3,統計每一個項目出現的次數,把次數低於minSuport的項目刪除掉,剩下的項目按出現的次數降序排列,得到F1:
薯片:7 雞蛋:7 面包:7 牛奶:6 啤酒:4
對於每一條事務,按照F1中的順序重新排序,不在F1中的被刪除掉。這樣整個事務集合變為:
薯片,雞蛋,面包,牛奶
薯片,雞蛋,啤酒
薯片,雞蛋,面包
薯片,雞蛋,面包,牛奶,啤酒
面包,牛奶,啤酒
雞蛋,面包,啤酒
薯片,面包,牛奶
薯片,雞蛋,面包,牛奶
薯片,雞蛋,牛奶
上面的事務集合即為當前的CPB,當前的PostModel依然為空。由CPB構建FP-Tree的步驟如下。
插入第一條事務(薯片,雞蛋,面包,牛奶)之后

插入第二條事務(薯片,雞蛋,啤酒)

插入第三條記錄(面包,牛奶,啤酒)

估計你也知道怎么插了,最終生成的FP-Tree是:

上圖中左邊的那一叫做表頭項,樹中相同名稱的節點要鏈接起來,鏈表的第一個元素就是表頭項里的元素。不論是表頭項節點還是FP-Tree中有節點,它們至少有2個屬性:name和count。
現在我們已進行完算法描述的第10行。go on
遍歷表頭項中的每一項,我們拿“牛奶:6”為例。
新的PostModel為“表頭項+老的PostModel”,現在由於老的PostModel還是空list,所以新的PostModel為:[牛奶]。新的PostModel就是一條頻繁模式,它的支持數即為表頭項的count:6,所以此處可以輸出一條頻繁模式<[牛奶], 6>
從表頭項“牛奶”開始,找到FP-Tree中所有的“牛奶”節點,然后找到從樹的根節點到“牛奶”節點的路徑。得到4條路徑:
薯片:7,雞蛋:6,牛奶:1 薯片:7,雞蛋:6,面包:4,牛奶:3 薯片:7,面包:1,牛奶:1 面包:1,牛奶:1
對於每一條路徑上的節點,其count都設置為牛奶的count
薯片:1,雞蛋:1,牛奶:1 薯片:3,雞蛋:3,面包:3,牛奶:3 薯片:1,面包:1,牛奶:1 面包:1,牛奶:1
因為每一項末尾都是牛奶,可以把牛奶去掉,得到新的CPB:
薯片:1,雞蛋:1 薯片:3,雞蛋:3,面包:3 薯片:1,面包:1 面包:1
然后遞歸調用FPGrowth(新的CPB,新的PostModel),當發現新有CPB為空時遞歸就可以退出了。
幾點說明
- 可以在構建FP-Tree之前就把CPB中低於minSuport的項目刪掉,也可以先不刪,而是在構建FP-Tree的過程當中如果遇到低於minSuport的項目不把它插入到FP-Tree中就可以了。FP-Tree算法之所以高效,就是因為它在每次FPGrowth遞歸時都對數據進行了這種裁剪。
- 沒必要每次FPGrowth遞歸時都把CPB中的事務按F1做一次重排序,只需要第一次構建CPB時按F1做一次排序,以后每次構建新的CPB時保持與老的CPB各項目順序不變就可以了。
- 對於FP-Tree已經是單枝的情況,就沒有必要再遞歸調用FPGrowth了,直接輸出整條路徑上所有節點的各種組合+postModel就可了。例如當FP-Tree為:

樹上只有一條路徑{A-B-C},在保證A-B-C這種順序的前提下,這三個節點的所有組合是:A,B,C,AB,AC,BC,ABC。每一種組合與postModel拼接形成一條頻繁模式,模式的支持數即為表頭項的計數(單枝的情況下所有表頭項和所有樹節點的計數都是相同的)。
Java實現
StrongAssociationRule.java
import java.util.List; public class StrongAssociationRule { public List<String> condition; public String result; public int support; public double confidence; }
TreeNode.java
import java.util.ArrayList; import java.util.List; /** * * @Description: FP樹的節點 * @Author orisun * @Date Jun 23, 2016 */ class TreeNode { /**節點名稱**/ private String name; /**頻數**/ private int count; private TreeNode parent; private List<TreeNode> children; /**下一個節點(由表頭項維護的那個鏈表)**/ private TreeNode nextHomonym; /**末節點(由表頭項維護的那個鏈表)**/ private TreeNode tail; @Override public String toString() { return name; } public TreeNode() { } public TreeNode(String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getCount() { return this.count; } public void setCount(int count) { this.count = count; } public TreeNode getParent() { return this.parent; } public void setParent(TreeNode parent) { this.parent = parent; } public List<TreeNode> getChildren() { return this.children; } public void addChild(TreeNode child) { if (getChildren() == null) { List<TreeNode> list = new ArrayList<TreeNode>(); list.add(child); setChildren(list); } else { getChildren().add(child); } } public TreeNode findChild(String name) { List<TreeNode> children = getChildren(); if (children != null) { for (TreeNode child : children) { if (child.getName().equals(name)) { return child; } } } return null; } public void setChildren(List<TreeNode> children) { this.children = children; } public void printChildrenName() { List<TreeNode> children = getChildren(); if (children != null) { for (TreeNode child : children) System.out.print(child.getName() + " "); } else System.out.print("null"); } public TreeNode getNextHomonym() { return this.nextHomonym; } public void setNextHomonym(TreeNode nextHomonym) { this.nextHomonym = nextHomonym; } public void countIncrement(int n) { this.count += n; } public TreeNode getTail() { return tail; } public void setTail(TreeNode tail) { this.tail = tail; } }
FPTree.java
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * * @Description: FPTree強關聯規則挖掘算法 * @Author orisun * @Date Jun 23, 2016 */ public class FPTree { /**頻繁模式的最小支持數**/ private int minSuport; /**關聯規則的最小置信度**/ private double confident; /**事務項的總數**/ private int totalSize; /**存儲每個頻繁項及其對應的計數**/ private Map<List<String>, Integer> frequentMap = new HashMap<List<String>, Integer>(); /**關聯規則中,哪些項可作為被推導的結果,默認情況下所有項都可以作為被推導的結果**/ private Set<String> decideAttr = null; public int getMinSuport() { return this.minSuport; } /** * 設置最小支持數 * * @param minSuport */ public void setMinSuport(int minSuport) { this.minSuport = minSuport; } public double getConfident() { return confident; } /** * 設置最小置信度 * * @param confident */ public void setConfident(double confident) { this.confident = confident; } /** * 設置決策屬性。如果要調用{@linkplain #readTransRocords(String[])},需要在調用{@code readTransRocords}之后再調用{@code setDecideAttr} * * @param decideAttr */ public void setDecideAttr(Set<String> decideAttr) { this.decideAttr = decideAttr; } /** * 獲取頻繁項集 * * @return * @Description: */ public Map<List<String>, Integer> getFrequentItems() { return frequentMap; } public int getTotalSize() { return totalSize; } /** * 根據一條頻繁模式得到若干關聯規則 * * @param list * @return */ private List<StrongAssociationRule> getRules(List<String> list) { List<StrongAssociationRule> rect = new LinkedList<StrongAssociationRule>(); if (list.size() > 1) { for (int i = 0; i < list.size(); i++) { String result = list.get(i); if (decideAttr.contains(result)) { List<String> condition = new ArrayList<String>(); condition.addAll(list.subList(0, i)); condition.addAll(list.subList(i + 1, list.size())); StrongAssociationRule rule = new StrongAssociationRule(); rule.condition = condition; rule.result = result; rect.add(rule); } } } return rect; } /** * 從若干個文件中讀入Transaction Record,同時把所有項設置為decideAttr * * @param filenames * @return * @Description: */ public List<List<String>> readTransRocords(String[] filenames) { Set<String> set = new HashSet<String>(); List<List<String>> transaction = null; if (filenames.length > 0) { transaction = new LinkedList<List<String>>(); for (String filename : filenames) { try { FileReader fr = new FileReader(filename); BufferedReader br = new BufferedReader(fr); try { String line = null; // 一項事務占一行 while ((line = br.readLine()) != null) { if (line.trim().length() > 0) { // 每個item之間用","分隔 String[] str = line.split(","); //每一項事務中的重復項需要排重 Set<String> record = new HashSet<String>(); for (String w : str) { record.add(w); set.add(w); } List<String> rl = new ArrayList<String>(); rl.addAll(record); transaction.add(rl); } } } finally { br.close(); } } catch (IOException ex) { System.out.println("Read transaction records failed." + ex.getMessage()); System.exit(1); } } } this.setDecideAttr(set); return transaction; } /** * 生成一個序列的各種子序列。(序列是有順序的) * * @param residualPath * @param results */ private void combine(LinkedList<TreeNode> residualPath, List<List<TreeNode>> results) { if (residualPath.size() > 0) { //如果residualPath太長,則會有太多的組合,內存會被耗盡的 TreeNode head = residualPath.poll(); List<List<TreeNode>> newResults = new ArrayList<List<TreeNode>>(); for (List<TreeNode> list : results) { List<TreeNode> listCopy = new ArrayList<TreeNode>(list); newResults.add(listCopy); } for (List<TreeNode> newPath : newResults) { newPath.add(head); } results.addAll(newResults); List<TreeNode> list = new ArrayList<TreeNode>(); list.add(head); results.add(list); combine(residualPath, results); } } private boolean isSingleBranch(TreeNode root) { boolean rect = true; while (root.getChildren() != null) { if (root.getChildren().size() > 1) { rect = false; break; } root = root.getChildren().get(0); } return rect; } /** * 計算事務集中每一項的頻數 * * @param transRecords * @return */ private Map<String, Integer> getFrequency(List<List<String>> transRecords) { Map<String, Integer> rect = new HashMap<String, Integer>(); for (List<String> record : transRecords) { for (String item : record) { Integer cnt = rect.get(item); if (cnt == null) { cnt = new Integer(0); } rect.put(item, ++cnt); } } return rect; } /** * 根據事務集合構建FPTree * * @param transRecords * @Description: */ public void buildFPTree(List<List<String>> transRecords) { totalSize = transRecords.size(); //計算每項的頻數 final Map<String, Integer> freqMap = getFrequency(transRecords); //先把頻繁1項集添加到頻繁模式中 // for (Entry<String, Integer> entry : freqMap.entrySet()) { // String name = entry.getKey(); // int cnt = entry.getValue(); // if (cnt >= minSuport) { // List<String> rule = new ArrayList<String>(); // rule.add(name); // frequentMap.put(rule, cnt); // } // } //每條事務中的項按F1排序 for (List<String> transRecord : transRecords) { Collections.sort(transRecord, new Comparator<String>() { @Override public int compare(String o1, String o2) { return freqMap.get(o2) - freqMap.get(o1); } }); } FPGrowth(transRecords, null); } /** * FP樹遞歸生長,從而得到所有的頻繁模式 * * @param cpb 條件模式基 * @param postModel 后綴模式 */ private void FPGrowth(List<List<String>> cpb, LinkedList<String> postModel) { // System.out.println("CPB is"); // for (List<String> records : cpb) { // System.out.println(records); // } // System.out.println("PostPattern is " + postPattern); Map<String, Integer> freqMap = getFrequency(cpb); Map<String, TreeNode> headers = new HashMap<String, TreeNode>(); for (Entry<String, Integer> entry : freqMap.entrySet()) { String name = entry.getKey(); int cnt = entry.getValue(); //每一次遞歸時都有可能出現一部分模式的頻數低於閾值 if (cnt >= minSuport) { TreeNode node = new TreeNode(name); node.setCount(cnt); headers.put(name, node); } } TreeNode treeRoot = buildSubTree(cpb, freqMap, headers); //如果只剩下虛根節點,則遞歸結束 if ((treeRoot.getChildren() == null) || (treeRoot.getChildren().size() == 0)) { return; } //如果樹是單枝的,則直接把“路徑的各種組合+后綴模式”添加到頻繁模式集中。這個技巧是可選的,即跳過此步進入下一輪遞歸也可以得到正確的結果 if (isSingleBranch(treeRoot)) { LinkedList<TreeNode> path = new LinkedList<TreeNode>(); TreeNode currNode = treeRoot; while (currNode.getChildren() != null) { currNode = currNode.getChildren().get(0); path.add(currNode); } //調用combine時path不宜過長,否則會OutOfMemory if (path.size() <= 20) { List<List<TreeNode>> results = new ArrayList<List<TreeNode>>(); combine(path, results); for (List<TreeNode> list : results) { int cnt = 0; List<String> rule = new ArrayList<String>(); for (TreeNode node : list) { rule.add(node.getName()); cnt = node.getCount();//cnt最FPTree葉節點的計數 } if (postModel != null) { rule.addAll(postModel); } frequentMap.put(rule, cnt); } return; } else { System.err.println("length of path is too long: " + path.size()); } } for (TreeNode header : headers.values()) { List<String> rule = new ArrayList<String>(); rule.add(header.getName()); if (postModel != null) { rule.addAll(postModel); } //表頭項+后綴模式 構成一條頻繁模式(頻繁模式內部也是按照F1排序的),頻繁度為表頭項的計數 frequentMap.put(rule, header.getCount()); //新的后綴模式:表頭項+上一次的后綴模式(注意保持順序,始終按F1的順序排列) LinkedList<String> newPostPattern = new LinkedList<String>(); newPostPattern.add(header.getName()); if (postModel != null) { newPostPattern.addAll(postModel); } //新的條件模式基 List<List<String>> newCPB = new LinkedList<List<String>>(); TreeNode nextNode = header; while ((nextNode = nextNode.getNextHomonym()) != null) { int counter = nextNode.getCount(); //獲得從虛根節點(不包括虛根節點)到當前節點(不包括當前節點)的路徑,即一條條件模式基。注意保持順序:你節點在前,子節點在后,即始終保持頻率高的在前 LinkedList<String> path = new LinkedList<String>(); TreeNode parent = nextNode; while ((parent = parent.getParent()).getName() != null) {//虛根節點的name為null path.push(parent.getName());//往表頭插入 } //事務要重復添加counter次 while (counter-- > 0) { newCPB.add(path); } } FPGrowth(newCPB, newPostPattern); } } /** * 把所有事務插入到一個FP樹當中 * * @param transRecords * @param F1 * @return */ private TreeNode buildSubTree(List<List<String>> transRecords, final Map<String, Integer> freqMap, final Map<String, TreeNode> headers) { TreeNode root = new TreeNode();//虛根節點 for (List<String> transRecord : transRecords) { LinkedList<String> record = new LinkedList<String>(transRecord); TreeNode subTreeRoot = root; TreeNode tmpRoot = null; if (root.getChildren() != null) { //延已有的分支,令各節點計數加1 while (!record.isEmpty() && (tmpRoot = subTreeRoot.findChild(record.peek())) != null) { tmpRoot.countIncrement(1); subTreeRoot = tmpRoot; record.poll(); } } //長出新的節點 addNodes(subTreeRoot, record, headers); } return root; } /** * 往特定的節點下插入一串后代節點,同時維護表頭項到同名節點的鏈表指針 * * @param ancestor * @param record * @param headers */ private void addNodes(TreeNode ancestor, LinkedList<String> record, final Map<String, TreeNode> headers) { while (!record.isEmpty()) { String item = (String) record.poll(); //單個項的出現頻數必須大於最小支持數,否則不允許插入FP樹。達到最小支持度的項都在headers中。每一次遞歸根據條件模式基本建立新的FPTree時,把要把頻數低於minSuport的排除在外,這也正是FPTree比窮舉法快的真正原因 if (headers.containsKey(item)) { TreeNode leafnode = new TreeNode(item); leafnode.setCount(1); leafnode.setParent(ancestor); ancestor.addChild(leafnode); TreeNode header = headers.get(item); TreeNode tail=header.getTail(); if(tail!=null){ tail.setNextHomonym(leafnode); }else{ header.setNextHomonym(leafnode); } header.setTail(leafnode); addNodes(leafnode, record, headers); } // else { // System.err.println(item + " is not F1"); // } } } /** * 獲取所有的強規則 * * @return */ public List<StrongAssociationRule> getAssociateRule() { assert totalSize > 0; List<StrongAssociationRule> rect = new ArrayList<StrongAssociationRule>(); //遍歷所有頻繁模式 for (Entry<List<String>, Integer> entry : frequentMap.entrySet()) { List<String> items = entry.getKey(); int count1 = entry.getValue(); //一條頻繁模式可以生成很多關聯規則 List<StrongAssociationRule> rules = getRules(items); //計算每一條關聯規則的支持度和置信度 for (StrongAssociationRule rule : rules) { if (frequentMap.containsKey(rule.condition)) { int count2 = frequentMap.get(rule.condition); double confidence = 1.0 * count1 / count2; if (confidence >= this.confident) { rule.support = count1; rule.confidence = confidence; rect.add(rule); } } else { System.err.println(rule.condition + " is not a frequent pattern, however " + items + " is a frequent pattern"); } } } return rect; } public static void main(String[] args) throws IOException { String infile = "trolley.txt"; FPTree fpTree = new FPTree(); fpTree.setConfident(0.6); fpTree.setMinSuport(3); if (args.length >= 2) { double confidence = Double.parseDouble(args[0]); int suport = Integer.parseInt(args[1]); fpTree.setConfident(confidence); fpTree.setMinSuport(suport); } List<List<String>> trans = fpTree.readTransRocords(new String[] { infile }); Set<String> decideAttr = new HashSet<String>(); decideAttr.add("雞蛋"); decideAttr.add("面包"); fpTree.setDecideAttr(decideAttr); long begin = System.currentTimeMillis(); fpTree.buildFPTree(trans); long end = System.currentTimeMillis(); System.out.println("buildFPTree use time " + (end - begin)); Map<List<String>, Integer> pattens = fpTree.getFrequentItems(); String outfile = "pattens.txt"; BufferedWriter bw = new BufferedWriter(new FileWriter(outfile)); System.out.println("模式\t頻數"); bw.write("模式\t頻數"); bw.newLine(); for (Entry<List<String>, Integer> entry : pattens.entrySet()) { System.out.println(entry.getKey() + "\t" + entry.getValue()); bw.write(joinList(entry.getKey()) + "\t" + entry.getValue()); bw.newLine(); } bw.close(); System.out.println(); List<StrongAssociationRule> rules = fpTree.getAssociateRule(); outfile = "rule.txt"; bw = new BufferedWriter(new FileWriter(outfile)); System.out.println("條件\t結果\t支持度\t置信度"); bw.write("條件\t結果\t支持度\t置信度"); bw.newLine(); DecimalFormat dfm = new DecimalFormat("#.##"); for (StrongAssociationRule rule : rules) { System.out.println(rule.condition + "->" + rule.result + "\t" + dfm.format(rule.support) + "\t" + dfm.format(rule.confidence)); bw.write(rule.condition + "->" + rule.result + "\t" + dfm.format(rule.support) + "\t" + dfm.format(rule.confidence)); bw.newLine(); } bw.close(); } private static String joinList(List<String> list) { if (list == null || list.size() == 0) { return ""; } StringBuilder sb = new StringBuilder(); for (String ele : list) { sb.append(ele); sb.append(","); } //把最后一個逗號去掉 return sb.substring(0, sb.length() - 1); } }
輸入trolley.txt
牛奶,雞蛋,面包,薯片
雞蛋,爆米花,薯片,啤酒
雞蛋,面包,薯片
牛奶,雞蛋,面包,爆米花,薯片,啤酒
牛奶,面包,啤酒
雞蛋,面包,啤酒
牛奶,面包,薯片
牛奶,雞蛋,面包,黃油,薯片
牛奶,雞蛋,黃油,薯片
輸出pattens.txt
模式 頻數
面包,啤酒 3 雞蛋,牛奶 4 面包,薯片 5 薯片,雞蛋 6 啤酒 4 薯片 7 面包,薯片,雞蛋,牛奶 3 雞蛋,啤酒 3 面包,牛奶 5 薯片,雞蛋,牛奶 4 面包,雞蛋,牛奶 3 面包 7 牛奶 6 面包,薯片,雞蛋 4 薯片,牛奶 5 雞蛋 7 面包,雞蛋 5 面包,薯片,牛奶 4
輸出rule.txt
條件 結果 支持度 置信度
[啤酒]->面包 3 0.75
[牛奶]->雞蛋 4 0.67
[薯片]->面包 5 0.71
[薯片]->雞蛋 6 0.86
[薯片, 雞蛋, 牛奶]->面包 3 0.75
[面包, 薯片, 牛奶]->雞蛋 3 0.75
[啤酒]->雞蛋 3 0.75
[牛奶]->面包 5 0.83
[薯片, 牛奶]->雞蛋 4 0.8
[雞蛋, 牛奶]->面包 3 0.75
[面包, 牛奶]->雞蛋 3 0.6
[薯片, 雞蛋]->面包 4 0.67
[面包, 薯片]->雞蛋 4 0.8
[雞蛋]->面包 5 0.71
[面包]->雞蛋 5 0.71
[薯片, 牛奶]->面包 4 0.8
MapReduce實現
在上面的代碼我們把整個事務數據庫放在一個List<List<String>>里面傳給FPGrowth,在實際中這是不可取的,因為內存不可能容下整個事務數據庫,我們可能需要從關系關系數據庫中一條一條地讀入來建立FP-Tree。但無論如何 FP-Tree是肯定需要放在內存中的,但內存如果容不下怎么辦?另外FPGrowth仍然是非常耗時的,你想提高速度怎么辦?解決辦法:分而治之,並行計算。
按照論文《FP-Growth 算法MapReduce 化研究》中介紹的方法,把以相同項目結尾的patten輸出一個Reducer里面去,在Reducer中僅對這一部分patten建立FPTree,這種FPTree會小很多,一般不會占用太多的內存。另外論文中的方法不需要維護表頭項。
結束語
在實踐中,關聯規則挖掘可能並不像人們期望的那么有用。一方面是因為支持度置信度框架會產生過多的規則,並不是每一個規則都是有用的。另一方面大部分的關聯規則並不像“啤酒與尿布”這種經典故事這么普遍。關聯規則分析是需要技巧的,有時需要用更嚴格的統計學知識來控制規則的增殖。
原文來自:博客園(華夏35度)http://www.cnblogs.com/zhangchaoyang 作者:Orisun

