在关联规则挖掘领域最经典的算法法是Apriori,其致命的缺点是需要多次扫描事务数据库。于是人们提出了各种裁剪(prune)数据集的方法以减少I/O开支,韩嘉炜老师的FP-Tree算法就是其中非常高效的一种。

支持度和置信度

严格地说Apriori和FP-Tree都是寻找频繁项集的算法,频繁项集就是所谓的“支持度”比较高的项集,下面解释一下支持度和置信度的概念。

设事务数据库为:

A  E  F  G

A  F  G

A  B  E  F  G

E  F  G

则{A,F,G}的支持度数为3,支持度为3/4。

{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算法

我们举个例子来详细讲解FP-Tree算法的完整实现。

事务数据库如下,一行表示一条购物记录:

牛奶,鸡蛋,面包,薯片

鸡蛋,爆米花,薯片,啤酒

鸡蛋,面包,薯片

牛奶,鸡蛋,面包,爆米花,薯片,啤酒

牛奶,面包,啤酒

鸡蛋,面包,啤酒

牛奶,面包,薯片

牛奶,鸡蛋,面包,黄油,薯片

牛奶,鸡蛋,黄油,薯片

我们的目的是要找出哪些商品总是相伴出现的,比如人们买薯片的时候通常也会买鸡蛋,则[薯片,鸡蛋]就是一条频繁模式(frequent pattern)。

FP-Tree算法第一步:扫描事务数据库,每项商品按频数递减排序,并删除频数小于最小支持度MinSup的商品。(第一次扫描数据库)

薯片:7鸡蛋:7面包:7牛奶:6啤酒:4                       (这里我们令MinSup=3)

以上结果就是频繁1项集,记为F1。

第二步:对于每一条购买记录,按照F1中的顺序重新排序。(第二次也是最后一次扫描数据库)

薯片,鸡蛋,面包,牛奶

薯片,鸡蛋,啤酒

薯片,鸡蛋,面包

薯片,鸡蛋,面包,牛奶,啤酒

面包,牛奶,啤酒

鸡蛋,面包,啤酒

薯片,面包,牛奶

薯片,鸡蛋,面包,牛奶

薯片,鸡蛋,牛奶

第三步:把第二步得到的各条记录插入到FP-Tree中。刚开始时后缀模式为空。

插入每一条(薯片,鸡蛋,面包,牛奶)之后

插入第二条记录(薯片,鸡蛋,啤酒)

插入第三条记录(面包,牛奶,啤酒)

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

上图中左边的那一叫做表头项,树中相同名称的节点要链接起来,链表的第一个元素就是表头项里的元素。

如果FP-Tree为空(只含一个虚的root节点),则FP-Growth函数返回。

此时输出表头项的每一项+postModel,支持度为表头项中对应项的计数。

第四步:从FP-Tree中找出频繁项。

遍历表头项中的每一项(我们拿“牛奶:6”为例),对于各项都执行以下(1)到(5)的操作:

(1)从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

因为每一项末尾都是牛奶,可以把牛奶去掉,得到条件模式基(Conditional Pattern Base,CPB),此时的后缀模式是:(牛奶)。

薯片:1,鸡蛋:1

薯片:3,鸡蛋:3,面包:3

薯片:1,面包:1

面包:1

(2)我们把上面的结果当作原始的事务数据库,返回到第3步,递归迭代运行。

没讲清楚,你可以参考这篇博客,直接看核心代码吧:

  1. public void FPGrowth(List<List<String>> transRecords,
  2. List<String> postPattern,Context context) throws IOException, InterruptedException {
  3. // 构建项头表,同时也是频繁1项集
  4. ArrayList<TreeNode> HeaderTable = buildHeaderTable(transRecords);
  5. // 构建FP-Tree
  6. TreeNode treeRoot = buildFPTree(transRecords, HeaderTable);
  7. // 如果FP-Tree为空则返回
  8. if (treeRoot.getChildren()==null || treeRoot.getChildren().size() == 0)
  9. return;
  10. //输出项头表的每一项+postPattern
  11. if(postPattern!=null){
  12. for (TreeNode header : HeaderTable) {
  13. String outStr=header.getName();
  14. int count=header.getCount();
  15. for (String ele : postPattern)
  16. outStr+="\t" + ele;
  17. context.write(new IntWritable(count), new Text(outStr));
  18. }
  19. }
  20. // 找到项头表的每一项的条件模式基,进入递归迭代
  21. for (TreeNode header : HeaderTable) {
  22. // 后缀模式增加一项
  23. List<String> newPostPattern = new LinkedList<String>();
  24. newPostPattern.add(header.getName());
  25. if (postPattern != null)
  26. newPostPattern.addAll(postPattern);
  27. // 寻找header的条件模式基CPB,放入newTransRecords中
  28. List<List<String>> newTransRecords = new LinkedList<List<String>>();
  29. TreeNode backnode = header.getNextHomonym();
  30. while (backnode != null) {
  31. int counter = backnode.getCount();
  32. List<String> prenodes = new ArrayList<String>();
  33. TreeNode parent = backnode;
  34. // 遍历backnode的祖先节点,放到prenodes中
  35. while ((parent = parent.getParent()).getName() != null) {
  36. prenodes.add(parent.getName());
  37. }
  38. while (counter-- > 0) {
  39. newTransRecords.add(prenodes);
  40. }
  41. backnode = backnode.getNextHomonym();
  42. }
  43. // 递归迭代
  44. FPGrowth(newTransRecords, newPostPattern,context);
  45. }
  46. }
public void FPGrowth(List<List<String>> transRecords,
List<String> postPattern,Context context) throws IOException, InterruptedException {
// 构建项头表,同时也是频繁1项集
ArrayList<TreeNode> HeaderTable = buildHeaderTable(transRecords);
// 构建FP-Tree
TreeNode treeRoot = buildFPTree(transRecords, HeaderTable);
// 如果FP-Tree为空则返回
if (treeRoot.getChildren()==null || treeRoot.getChildren().size() == 0)
return;
//输出项头表的每一项+postPattern
if(postPattern!=null){
for (TreeNode header : HeaderTable) {
String outStr=header.getName();
int count=header.getCount();
for (String ele : postPattern)
outStr+="\t" + ele;
context.write(new IntWritable(count), new Text(outStr));
}
}
// 找到项头表的每一项的条件模式基,进入递归迭代
for (TreeNode header : HeaderTable) {
// 后缀模式增加一项
List<String> newPostPattern = new LinkedList<String>();
newPostPattern.add(header.getName());
if (postPattern != null)
newPostPattern.addAll(postPattern);
// 寻找header的条件模式基CPB,放入newTransRecords中
List<List<String>> newTransRecords = new LinkedList<List<String>>();
TreeNode backnode = header.getNextHomonym();
while (backnode != null) {
int counter = backnode.getCount();
List<String> prenodes = new ArrayList<String>();
TreeNode parent = backnode;
// 遍历backnode的祖先节点,放到prenodes中
while ((parent = parent.getParent()).getName() != null) {
prenodes.add(parent.getName());
}
while (counter-- > 0) {
newTransRecords.add(prenodes);
}
backnode = backnode.getNextHomonym();
}
// 递归迭代
FPGrowth(newTransRecords, newPostPattern,context);
}
}

对于FP-Tree已经是单枝的情况,就没有必要再递归调用FPGrowth了,直接输出整条路径上所有节点的各种组合+postModel就可了。例如当FP-Tree为:

我们直接输出:

3  A+postModel

3  B+postModel

3  A+B+postModel

就可以了。

如何按照上面代码里的做法,是先输出:

3  A+postModel

3  B+postModel

然后把B插入到postModel的头部,重新建立一个FP-Tree,这时Tree中只含A,于是输出

3  A+(B+postModel)

两种方法结果是一样的,但毕竟重新建立FP-Tree计算量大些。

Java实现

FP树节点定义

  1. package fptree;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class TreeNode implements Comparable<TreeNode> {
  5. private String name; // 节点名称
  6. private int count; // 计数
  7. private TreeNode parent; // 父节点
  8. private List<TreeNode> children; // 子节点
  9. private TreeNode nextHomonym; // 下一个同名节点
  10. public TreeNode() {
  11. }
  12. public TreeNode(String name) {
  13. this.name = name;
  14. }
  15. public String getName() {
  16. return name;
  17. }
  18. public void setName(String name) {
  19. this.name = name;
  20. }
  21. public int getCount() {
  22. return count;
  23. }
  24. public void setCount(int count) {
  25. this.count = count;
  26. }
  27. public TreeNode getParent() {
  28. return parent;
  29. }
  30. public void setParent(TreeNode parent) {
  31. this.parent = parent;
  32. }
  33. public List<TreeNode> getChildren() {
  34. return children;
  35. }
  36. public void addChild(TreeNode child) {
  37. if (this.getChildren() == null) {
  38. List<TreeNode> list = new ArrayList<TreeNode>();
  39. list.add(child);
  40. this.setChildren(list);
  41. } else {
  42. this.getChildren().add(child);
  43. }
  44. }
  45. public TreeNode findChild(String name) {
  46. List<TreeNode> children = this.getChildren();
  47. if (children != null) {
  48. for (TreeNode child : children) {
  49. if (child.getName().equals(name)) {
  50. return child;
  51. }
  52. }
  53. }
  54. return null;
  55. }
  56. public void setChildren(List<TreeNode> children) {
  57. this.children = children;
  58. }
  59. public void printChildrenName() {
  60. List<TreeNode> children = this.getChildren();
  61. if (children != null) {
  62. for (TreeNode child : children) {
  63. System.out.print(child.getName() + " ");
  64. }
  65. } else {
  66. System.out.print("null");
  67. }
  68. }
  69. public TreeNode getNextHomonym() {
  70. return nextHomonym;
  71. }
  72. public void setNextHomonym(TreeNode nextHomonym) {
  73. this.nextHomonym = nextHomonym;
  74. }
  75. public void countIncrement(int n) {
  76. this.count += n;
  77. }
  78. @Override
  79. public int compareTo(TreeNode arg0) {
  80. // TODO Auto-generated method stub
  81. int count0 = arg0.getCount();
  82. // 跟默认的比较大小相反,导致调用Arrays.sort()时是按降序排列
  83. return count0 - this.count;
  84. }
  85. }
package fptree;

import java.util.ArrayList;
import java.util.List; public class TreeNode implements Comparable<TreeNode> { private String name; // 节点名称
private int count; // 计数
private TreeNode parent; // 父节点
private List<TreeNode> children; // 子节点
private TreeNode nextHomonym; // 下一个同名节点 public TreeNode() { } public TreeNode(String name) {
this.name = name;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} public TreeNode getParent() {
return parent;
} public void setParent(TreeNode parent) {
this.parent = parent;
} public List<TreeNode> getChildren() {
return children;
} public void addChild(TreeNode child) {
if (this.getChildren() == null) {
List<TreeNode> list = new ArrayList<TreeNode>();
list.add(child);
this.setChildren(list);
} else {
this.getChildren().add(child);
}
} public TreeNode findChild(String name) {
List<TreeNode> children = this.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 = this.getChildren();
if (children != null) {
for (TreeNode child : children) {
System.out.print(child.getName() + " ");
}
} else {
System.out.print("null");
}
} public TreeNode getNextHomonym() {
return nextHomonym;
} public void setNextHomonym(TreeNode nextHomonym) {
this.nextHomonym = nextHomonym;
} public void countIncrement(int n) {
this.count += n;
} @Override
public int compareTo(TreeNode arg0) {
// TODO Auto-generated method stub
int count0 = arg0.getCount();
// 跟默认的比较大小相反,导致调用Arrays.sort()时是按降序排列
return count0 - this.count;
}
}

挖掘频繁模式

  1. package fptree;
  2. import java.io.BufferedReader;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. import java.util.ArrayList;
  6. import java.util.Collections;
  7. import java.util.Comparator;
  8. import java.util.HashMap;
  9. import java.util.LinkedList;
  10. import java.util.List;
  11. import java.util.Map;
  12. import java.util.Map.Entry;
  13. import java.util.Set;
  14. public class FPTree {
  15. private int minSuport;
  16. public int getMinSuport() {
  17. return minSuport;
  18. }
  19. public void setMinSuport(int minSuport) {
  20. this.minSuport = minSuport;
  21. }
  22. // 从若干个文件中读入Transaction Record
  23. public List<List<String>> readTransRocords(String... filenames) {
  24. List<List<String>> transaction = null;
  25. if (filenames.length > 0) {
  26. transaction = new LinkedList<List<String>>();
  27. for (String filename : filenames) {
  28. try {
  29. FileReader fr = new FileReader(filename);
  30. BufferedReader br = new BufferedReader(fr);
  31. try {
  32. String line;
  33. List<String> record;
  34. while ((line = br.readLine()) != null) {
  35. if(line.trim().length()>0){
  36. String str[] = line.split(",");
  37. record = new LinkedList<String>();
  38. for (String w : str)
  39. record.add(w);
  40. transaction.add(record);
  41. }
  42. }
  43. } finally {
  44. br.close();
  45. }
  46. } catch (IOException ex) {
  47. System.out.println("Read transaction records failed."
  48. + ex.getMessage());
  49. System.exit(1);
  50. }
  51. }
  52. }
  53. return transaction;
  54. }
  55. // FP-Growth算法
  56. public void FPGrowth(List<List<String>> transRecords,
  57. List<String> postPattern) {
  58. // 构建项头表,同时也是频繁1项集
  59. ArrayList<TreeNode> HeaderTable = buildHeaderTable(transRecords);
  60. // 构建FP-Tree
  61. TreeNode treeRoot = buildFPTree(transRecords, HeaderTable);
  62. // 如果FP-Tree为空则返回
  63. if (treeRoot.getChildren()==null || treeRoot.getChildren().size() == 0)
  64. return;
  65. //输出项头表的每一项+postPattern
  66. if(postPattern!=null){
  67. for (TreeNode header : HeaderTable) {
  68. System.out.print(header.getCount() + "\t" + header.getName());
  69. for (String ele : postPattern)
  70. System.out.print("\t" + ele);
  71. System.out.println();
  72. }
  73. }
  74. // 找到项头表的每一项的条件模式基,进入递归迭代
  75. for (TreeNode header : HeaderTable) {
  76. // 后缀模式增加一项
  77. List<String> newPostPattern = new LinkedList<String>();
  78. newPostPattern.add(header.getName());
  79. if (postPattern != null)
  80. newPostPattern.addAll(postPattern);
  81. // 寻找header的条件模式基CPB,放入newTransRecords中
  82. List<List<String>> newTransRecords = new LinkedList<List<String>>();
  83. TreeNode backnode = header.getNextHomonym();
  84. while (backnode != null) {
  85. int counter = backnode.getCount();
  86. List<String> prenodes = new ArrayList<String>();
  87. TreeNode parent = backnode;
  88. // 遍历backnode的祖先节点,放到prenodes中
  89. while ((parent = parent.getParent()).getName() != null) {
  90. prenodes.add(parent.getName());
  91. }
  92. while (counter-- > 0) {
  93. newTransRecords.add(prenodes);
  94. }
  95. backnode = backnode.getNextHomonym();
  96. }
  97. // 递归迭代
  98. FPGrowth(newTransRecords, newPostPattern);
  99. }
  100. }
  101. // 构建项头表,同时也是频繁1项集
  102. public ArrayList<TreeNode> buildHeaderTable(List<List<String>> transRecords) {
  103. ArrayList<TreeNode> F1 = null;
  104. if (transRecords.size() > 0) {
  105. F1 = new ArrayList<TreeNode>();
  106. Map<String, TreeNode> map = new HashMap<String, TreeNode>();
  107. // 计算事务数据库中各项的支持度
  108. for (List<String> record : transRecords) {
  109. for (String item : record) {
  110. if (!map.keySet().contains(item)) {
  111. TreeNode node = new TreeNode(item);
  112. node.setCount(1);
  113. map.put(item, node);
  114. } else {
  115. map.get(item).countIncrement(1);
  116. }
  117. }
  118. }
  119. // 把支持度大于(或等于)minSup的项加入到F1中
  120. Set<String> names = map.keySet();
  121. for (String name : names) {
  122. TreeNode tnode = map.get(name);
  123. if (tnode.getCount() >= minSuport) {
  124. F1.add(tnode);
  125. }
  126. }
  127. Collections.sort(F1);
  128. return F1;
  129. } else {
  130. return null;
  131. }
  132. }
  133. // 构建FP-Tree
  134. public TreeNode buildFPTree(List<List<String>> transRecords,
  135. ArrayList<TreeNode> F1) {
  136. TreeNode root = new TreeNode(); // 创建树的根节点
  137. for (List<String> transRecord : transRecords) {
  138. LinkedList<String> record = sortByF1(transRecord, F1);
  139. TreeNode subTreeRoot = root;
  140. TreeNode tmpRoot = null;
  141. if (root.getChildren() != null) {
  142. while (!record.isEmpty()
  143. && (tmpRoot = subTreeRoot.findChild(record.peek())) != null) {
  144. tmpRoot.countIncrement(1);
  145. subTreeRoot = tmpRoot;
  146. record.poll();
  147. }
  148. }
  149. addNodes(subTreeRoot, record, F1);
  150. }
  151. return root;
  152. }
  153. // 把交易记录按项的频繁程序降序排列
  154. public LinkedList<String> sortByF1(List<String> transRecord,
  155. ArrayList<TreeNode> F1) {
  156. Map<String, Integer> map = new HashMap<String, Integer>();
  157. for (String item : transRecord) {
  158. // 由于F1已经是按降序排列的,
  159. for (int i = 0; i < F1.size(); i++) {
  160. TreeNode tnode = F1.get(i);
  161. if (tnode.getName().equals(item)) {
  162. map.put(item, i);
  163. }
  164. }
  165. }
  166. ArrayList<Entry<String, Integer>> al = new ArrayList<Entry<String, Integer>>(
  167. map.entrySet());
  168. Collections.sort(al, new Comparator<Map.Entry<String, Integer>>() {
  169. @Override
  170. public int compare(Entry<String, Integer> arg0,
  171. Entry<String, Integer> arg1) {
  172. // 降序排列
  173. return arg0.getValue() - arg1.getValue();
  174. }
  175. });
  176. LinkedList<String> rest = new LinkedList<String>();
  177. for (Entry<String, Integer> entry : al) {
  178. rest.add(entry.getKey());
  179. }
  180. return rest;
  181. }
  182. // 把record作为ancestor的后代插入树中
  183. public void addNodes(TreeNode ancestor, LinkedList<String> record,
  184. ArrayList<TreeNode> F1) {
  185. if (record.size() > 0) {
  186. while (record.size() > 0) {
  187. String item = record.poll();
  188. TreeNode leafnode = new TreeNode(item);
  189. leafnode.setCount(1);
  190. leafnode.setParent(ancestor);
  191. ancestor.addChild(leafnode);
  192. for (TreeNode f1 : F1) {
  193. if (f1.getName().equals(item)) {
  194. while (f1.getNextHomonym() != null) {
  195. f1 = f1.getNextHomonym();
  196. }
  197. f1.setNextHomonym(leafnode);
  198. break;
  199. }
  200. }
  201. addNodes(leafnode, record, F1);
  202. }
  203. }
  204. }
  205. public static void main(String[] args) {
  206. FPTree fptree = new FPTree();
  207. fptree.setMinSuport(3);
  208. List<List<String>> transRecords = fptree
  209. .readTransRocords("/home/orisun/test/market");
  210. fptree.FPGrowth(transRecords, null);
  211. }
  212. }
package fptree;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; public class FPTree { private int minSuport; public int getMinSuport() {
return minSuport;
} public void setMinSuport(int minSuport) {
this.minSuport = minSuport;
} // 从若干个文件中读入Transaction Record
public List<List<String>> readTransRocords(String... filenames) {
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;
List<String> record;
while ((line = br.readLine()) != null) {
if(line.trim().length()>0){
String str[] = line.split(",");
record = new LinkedList<String>();
for (String w : str)
record.add(w);
transaction.add(record);
}
}
} finally {
br.close();
}
} catch (IOException ex) {
System.out.println("Read transaction records failed."
+ ex.getMessage());
System.exit(1);
}
}
}
return transaction;
} // FP-Growth算法
public void FPGrowth(List<List<String>> transRecords,
List<String> postPattern) {
// 构建项头表,同时也是频繁1项集
ArrayList<TreeNode> HeaderTable = buildHeaderTable(transRecords);
// 构建FP-Tree
TreeNode treeRoot = buildFPTree(transRecords, HeaderTable);
// 如果FP-Tree为空则返回
if (treeRoot.getChildren()==null || treeRoot.getChildren().size() == 0)
return;
//输出项头表的每一项+postPattern
if(postPattern!=null){
for (TreeNode header : HeaderTable) {
System.out.print(header.getCount() + "\t" + header.getName());
for (String ele : postPattern)
System.out.print("\t" + ele);
System.out.println();
}
}
// 找到项头表的每一项的条件模式基,进入递归迭代
for (TreeNode header : HeaderTable) {
// 后缀模式增加一项
List<String> newPostPattern = new LinkedList<String>();
newPostPattern.add(header.getName());
if (postPattern != null)
newPostPattern.addAll(postPattern);
// 寻找header的条件模式基CPB,放入newTransRecords中
List<List<String>> newTransRecords = new LinkedList<List<String>>();
TreeNode backnode = header.getNextHomonym();
while (backnode != null) {
int counter = backnode.getCount();
List<String> prenodes = new ArrayList<String>();
TreeNode parent = backnode;
// 遍历backnode的祖先节点,放到prenodes中
while ((parent = parent.getParent()).getName() != null) {
prenodes.add(parent.getName());
}
while (counter-- > 0) {
newTransRecords.add(prenodes);
}
backnode = backnode.getNextHomonym();
}
// 递归迭代
FPGrowth(newTransRecords, newPostPattern);
}
} // 构建项头表,同时也是频繁1项集
public ArrayList<TreeNode> buildHeaderTable(List<List<String>> transRecords) {
ArrayList<TreeNode> F1 = null;
if (transRecords.size() > 0) {
F1 = new ArrayList<TreeNode>();
Map<String, TreeNode> map = new HashMap<String, TreeNode>();
// 计算事务数据库中各项的支持度
for (List<String> record : transRecords) {
for (String item : record) {
if (!map.keySet().contains(item)) {
TreeNode node = new TreeNode(item);
node.setCount(1);
map.put(item, node);
} else {
map.get(item).countIncrement(1);
}
}
}
// 把支持度大于(或等于)minSup的项加入到F1中
Set<String> names = map.keySet();
for (String name : names) {
TreeNode tnode = map.get(name);
if (tnode.getCount() >= minSuport) {
F1.add(tnode);
}
}
Collections.sort(F1);
return F1;
} else {
return null;
}
} // 构建FP-Tree
public TreeNode buildFPTree(List<List<String>> transRecords,
ArrayList<TreeNode> F1) {
TreeNode root = new TreeNode(); // 创建树的根节点
for (List<String> transRecord : transRecords) {
LinkedList<String> record = sortByF1(transRecord, F1);
TreeNode subTreeRoot = root;
TreeNode tmpRoot = null;
if (root.getChildren() != null) {
while (!record.isEmpty()
&& (tmpRoot = subTreeRoot.findChild(record.peek())) != null) {
tmpRoot.countIncrement(1);
subTreeRoot = tmpRoot;
record.poll();
}
}
addNodes(subTreeRoot, record, F1);
}
return root;
} // 把交易记录按项的频繁程序降序排列
public LinkedList<String> sortByF1(List<String> transRecord,
ArrayList<TreeNode> F1) {
Map<String, Integer> map = new HashMap<String, Integer>();
for (String item : transRecord) {
// 由于F1已经是按降序排列的,
for (int i = 0; i < F1.size(); i++) {
TreeNode tnode = F1.get(i);
if (tnode.getName().equals(item)) {
map.put(item, i);
}
}
}
ArrayList<Entry<String, Integer>> al = new ArrayList<Entry<String, Integer>>(
map.entrySet());
Collections.sort(al, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> arg0,
Entry<String, Integer> arg1) {
// 降序排列
return arg0.getValue() - arg1.getValue();
}
});
LinkedList<String> rest = new LinkedList<String>();
for (Entry<String, Integer> entry : al) {
rest.add(entry.getKey());
}
return rest;
} // 把record作为ancestor的后代插入树中
public void addNodes(TreeNode ancestor, LinkedList<String> record,
ArrayList<TreeNode> F1) {
if (record.size() > 0) {
while (record.size() > 0) {
String item = record.poll();
TreeNode leafnode = new TreeNode(item);
leafnode.setCount(1);
leafnode.setParent(ancestor);
ancestor.addChild(leafnode); for (TreeNode f1 : F1) {
if (f1.getName().equals(item)) {
while (f1.getNextHomonym() != null) {
f1 = f1.getNextHomonym();
}
f1.setNextHomonym(leafnode);
break;
}
} addNodes(leafnode, record, F1);
}
}
} public static void main(String[] args) {
FPTree fptree = new FPTree();
fptree.setMinSuport(3);
List<List<String>> transRecords = fptree
.readTransRocords("/home/orisun/test/market");
fptree.FPGrowth(transRecords, null);
}
}

输入文件

牛奶,鸡蛋,面包,薯片
鸡蛋,爆米花,薯片,啤酒
鸡蛋,面包,薯片
牛奶,鸡蛋,面包,爆米花,薯片,啤酒
牛奶,面包,啤酒
鸡蛋,面包,啤酒
牛奶,面包,薯片
牛奶,鸡蛋,面包,黄油,薯片
牛奶,鸡蛋,黄油,薯片

输出

6    薯片    鸡蛋
5 薯片 面包
5 鸡蛋 面包
4 薯片 鸡蛋 面包
5 薯片 牛奶
5 面包 牛奶
4 鸡蛋 牛奶
4 薯片 面包 牛奶
4 薯片 鸡蛋 牛奶
3 面包 鸡蛋 牛奶
3 薯片 面包 鸡蛋 牛奶
3 鸡蛋 啤酒
3 面包 啤酒

用Hadoop来实现

在上面的代码我们把整个事务数据库放在一个List<List<String>>里面传给FPGrowth,在实际中这是不可取的,因为内存不可能容下整个事务数据库,我们可能需要从关系关系数据库中一条一条地读入来建立FP-Tree。但无论如何 FP-Tree是肯定需要放在内存中的,但内存如果容不下怎么办?另外FPGrowth仍然是非常耗时的,你想提高速度怎么办?解决办法:分而治之,并行计算。

我们把原始事务数据库分成N部分,在N个节点上并行地进行FPGrowth挖掘,最后把关联规则汇总到一起就可以了。关键问题是怎么“划分”才会不遗露任何一条关联规则呢?参见这篇博客。这里为了达到并行计算的目的,采用了一种“冗余”的划分方法,即各部分的并集大于原来的集合。这种方法最终求出来的关联规则也是有冗余的,比如在节点1上得到一条规则(6:啤酒,尿布),在节点2上得到一条规则(3:尿布,啤酒),显然节点2上的这条规则是冗余的,需要采用后续步骤把冗余的规则去掉。

代码:

Record.java

  1. package fptree;
  2. import java.io.DataInput;
  3. import java.io.DataOutput;
  4. import java.io.IOException;
  5. import java.util.Collections;
  6. import java.util.LinkedList;
  7. import org.apache.hadoop.io.WritableComparable;
  8. public class Record implements WritableComparable<Record>{
  9. LinkedList<String> list;
  10. public Record(){
  11. list=new LinkedList<String>();
  12. }
  13. public Record(String[] arr){
  14. list=new LinkedList<String>();
  15. for(int i=0;i<arr.length;i++)
  16. list.add(arr[i]);
  17. }
  18. @Override
  19. public String toString(){
  20. String str=list.get(0);
  21. for(int i=1;i<list.size();i++)
  22. str+="\t"+list.get(i);
  23. return str;
  24. }
  25. @Override
  26. public void readFields(DataInput in) throws IOException {
  27. list.clear();
  28. String line=in.readUTF();
  29. String []arr=line.split("\\s+");
  30. for(int i=0;i<arr.length;i++)
  31. list.add(arr[i]);
  32. }
  33. @Override
  34. public void write(DataOutput out) throws IOException {
  35. out.writeUTF(this.toString());
  36. }
  37. @Override
  38. public int compareTo(Record obj) {
  39. Collections.sort(list);
  40. Collections.sort(obj.list);
  41. return this.toString().compareTo(obj.toString());
  42. }
  43. }
package fptree;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList; import org.apache.hadoop.io.WritableComparable; public class Record implements WritableComparable<Record>{ LinkedList<String> list; public Record(){
list=new LinkedList<String>();
} public Record(String[] arr){
list=new LinkedList<String>();
for(int i=0;i<arr.length;i++)
list.add(arr[i]);
} @Override
public String toString(){
String str=list.get(0);
for(int i=1;i<list.size();i++)
str+="\t"+list.get(i);
return str;
} @Override
public void readFields(DataInput in) throws IOException {
list.clear();
String line=in.readUTF();
String []arr=line.split("\\s+");
for(int i=0;i<arr.length;i++)
list.add(arr[i]);
} @Override
public void write(DataOutput out) throws IOException {
out.writeUTF(this.toString());
} @Override
public int compareTo(Record obj) {
Collections.sort(list);
Collections.sort(obj.list);
return this.toString().compareTo(obj.toString());
} }

DC_FPTree.java

  1. package fptree;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.util.ArrayList;
  6. import java.util.BitSet;
  7. import java.util.Collections;
  8. import java.util.Comparator;
  9. import java.util.HashMap;
  10. import java.util.LinkedList;
  11. import java.util.List;
  12. import java.util.Map;
  13. import java.util.Map.Entry;
  14. import java.util.Set;
  15. import org.apache.hadoop.conf.Configuration;
  16. import org.apache.hadoop.conf.Configured;
  17. import org.apache.hadoop.fs.FSDataInputStream;
  18. import org.apache.hadoop.fs.FileSystem;
  19. import org.apache.hadoop.fs.Path;
  20. import org.apache.hadoop.io.IntWritable;
  21. import org.apache.hadoop.io.LongWritable;
  22. import org.apache.hadoop.io.Text;
  23. import org.apache.hadoop.mapreduce.Job;
  24. import org.apache.hadoop.mapreduce.Mapper;
  25. import org.apache.hadoop.mapreduce.Reducer;
  26. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
  27. import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
  28. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
  29. import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
  30. import org.apache.hadoop.util.Tool;
  31. import org.apache.hadoop.util.ToolRunner;
  32. public class DC_FPTree extends Configured implements Tool {
  33. private static final int GroupNum = 10;
  34. private static final int minSuport=6;
  35. public static class GroupMapper extends
  36. Mapper<LongWritable, Text, IntWritable, Record> {
  37. List<String> freq = new LinkedList<String>(); // 频繁1项集
  38. List<List<String>> freq_group = new LinkedList<List<String>>(); // 分组后的频繁1项集
  39. @Override
  40. public void setup(Context context) throws IOException {
  41. // 从文件读入频繁1项集
  42. FileSystem fs = FileSystem.get(context.getConfiguration());
  43. Path freqFile = new Path("/user/orisun/input/F1");
  44. FSDataInputStream in = fs.open(freqFile);
  45. InputStreamReader isr = new InputStreamReader(in);
  46. BufferedReader br = new BufferedReader(isr);
  47. try {
  48. String line;
  49. while ((line = br.readLine()) != null) {
  50. String[] str = line.split("\\s+");
  51. String word = str[0];
  52. freq.add(word);
  53. }
  54. } finally {
  55. br.close();
  56. }
  57. // 对频繁1项集进行分组
  58. Collections.shuffle(freq); // 打乱顺序
  59. int cap = freq.size() / GroupNum; // 每段分为一组
  60. for (int i = 0; i < GroupNum; i++) {
  61. List<String> list = new LinkedList<String>();
  62. for (int j = 0; j < cap; j++) {
  63. list.add(freq.get(i * cap + j));
  64. }
  65. freq_group.add(list);
  66. }
  67. int remainder = freq.size() % GroupNum;
  68. int base = GroupNum * cap;
  69. for (int i = 0; i < remainder; i++) {
  70. freq_group.get(i).add(freq.get(base + i));
  71. }
  72. }
  73. @Override
  74. public void map(LongWritable key, Text value, Context context)
  75. throws IOException, InterruptedException {
  76. String[] arr = value.toString().split("\\s+");
  77. Record record = new Record(arr);
  78. LinkedList<String> list = record.list;
  79. BitSet bs=new BitSet(freq_group.size());
  80. bs.clear();
  81. while (record.list.size() > 0) {
  82. String item = list.peekLast(); // 取出record的最后一项
  83. int i=0;
  84. for (; i < freq_group.size(); i++) {
  85. if(bs.get(i))
  86. continue;
  87. if (freq_group.get(i).contains(item)) {
  88. bs.set(i);
  89. break;
  90. }
  91. }
  92. if(i<freq_group.size()){     //找到了
  93. context.write(new IntWritable(i), record);
  94. }
  95. record.list.pollLast();
  96. }
  97. }
  98. }
  99. public static class FPReducer extends Reducer<IntWritable,Record,IntWritable,Text>{
  100. public void reduce(IntWritable key,Iterable<Record> values,Context context)throws IOException,InterruptedException{
  101. List<List<String>> trans=new LinkedList<List<String>>();
  102. while(values.iterator().hasNext()){
  103. Record record=values.iterator().next();
  104. LinkedList<String> list=new LinkedList<String>();
  105. for(String ele:record.list)
  106. list.add(ele);
  107. trans.add(list);
  108. }
  109. FPGrowth(trans, null,context);
  110. }
  111. // FP-Growth算法
  112. public void FPGrowth(List<List<String>> transRecords,
  113. List<String> postPattern,Context context) throws IOException, InterruptedException {
  114. // 构建项头表,同时也是频繁1项集
  115. ArrayList<TreeNode> HeaderTable = buildHeaderTable(transRecords);
  116. // 构建FP-Tree
  117. TreeNode treeRoot = buildFPTree(transRecords, HeaderTable);
  118. // 如果FP-Tree为空则返回
  119. if (treeRoot.getChildren()==null || treeRoot.getChildren().size() == 0)
  120. return;
  121. //输出项头表的每一项+postPattern
  122. if(postPattern!=null){
  123. for (TreeNode header : HeaderTable) {
  124. String outStr=header.getName();
  125. int count=header.getCount();
  126. for (String ele : postPattern)
  127. outStr+="\t" + ele;
  128. context.write(new IntWritable(count), new Text(outStr));
  129. }
  130. }
  131. // 找到项头表的每一项的条件模式基,进入递归迭代
  132. for (TreeNode header : HeaderTable) {
  133. // 后缀模式增加一项
  134. List<String> newPostPattern = new LinkedList<String>();
  135. newPostPattern.add(header.getName());
  136. if (postPattern != null)
  137. newPostPattern.addAll(postPattern);
  138. // 寻找header的条件模式基CPB,放入newTransRecords中
  139. List<List<String>> newTransRecords = new LinkedList<List<String>>();
  140. TreeNode backnode = header.getNextHomonym();
  141. while (backnode != null) {
  142. int counter = backnode.getCount();
  143. List<String> prenodes = new ArrayList<String>();
  144. TreeNode parent = backnode;
  145. // 遍历backnode的祖先节点,放到prenodes中
  146. while ((parent = parent.getParent()).getName() != null) {
  147. prenodes.add(parent.getName());
  148. }
  149. while (counter-- > 0) {
  150. newTransRecords.add(prenodes);
  151. }
  152. backnode = backnode.getNextHomonym();
  153. }
  154. // 递归迭代
  155. FPGrowth(newTransRecords, newPostPattern,context);
  156. }
  157. }
  158. // 构建项头表,同时也是频繁1项集
  159. public ArrayList<TreeNode> buildHeaderTable(List<List<String>> transRecords) {
  160. ArrayList<TreeNode> F1 = null;
  161. if (transRecords.size() > 0) {
  162. F1 = new ArrayList<TreeNode>();
  163. Map<String, TreeNode> map = new HashMap<String, TreeNode>();
  164. // 计算事务数据库中各项的支持度
  165. for (List<String> record : transRecords) {
  166. for (String item : record) {
  167. if (!map.keySet().contains(item)) {
  168. TreeNode node = new TreeNode(item);
  169. node.setCount(1);
  170. map.put(item, node);
  171. } else {
  172. map.get(item).countIncrement(1);
  173. }
  174. }
  175. }
  176. // 把支持度大于(或等于)minSup的项加入到F1中
  177. Set<String> names = map.keySet();
  178. for (String name : names) {
  179. TreeNode tnode = map.get(name);
  180. if (tnode.getCount() >= minSuport) {
  181. F1.add(tnode);
  182. }
  183. }
  184. Collections.sort(F1);
  185. return F1;
  186. } else {
  187. return null;
  188. }
  189. }
  190. // 构建FP-Tree
  191. public TreeNode buildFPTree(List<List<String>> transRecords,
  192. ArrayList<TreeNode> F1) {
  193. TreeNode root = new TreeNode(); // 创建树的根节点
  194. for (List<String> transRecord : transRecords) {
  195. LinkedList<String> record = sortByF1(transRecord, F1);
  196. TreeNode subTreeRoot = root;
  197. TreeNode tmpRoot = null;
  198. if (root.getChildren() != null) {
  199. while (!record.isEmpty()
  200. && (tmpRoot = subTreeRoot.findChild(record.peek())) != null) {
  201. tmpRoot.countIncrement(1);
  202. subTreeRoot = tmpRoot;
  203. record.poll();
  204. }
  205. }
  206. addNodes(subTreeRoot, record, F1);
  207. }
  208. return root;
  209. }
  210. // 把交易记录按项的频繁程序降序排列
  211. public LinkedList<String> sortByF1(List<String> transRecord,
  212. ArrayList<TreeNode> F1) {
  213. Map<String, Integer> map = new HashMap<String, Integer>();
  214. for (String item : transRecord) {
  215. // 由于F1已经是按降序排列的,
  216. for (int i = 0; i < F1.size(); i++) {
  217. TreeNode tnode = F1.get(i);
  218. if (tnode.getName().equals(item)) {
  219. map.put(item, i);
  220. }
  221. }
  222. }
  223. ArrayList<Entry<String, Integer>> al = new ArrayList<Entry<String, Integer>>(
  224. map.entrySet());
  225. Collections.sort(al, new Comparator<Map.Entry<String, Integer>>() {
  226. @Override
  227. public int compare(Entry<String, Integer> arg0,
  228. Entry<String, Integer> arg1) {
  229. // 降序排列
  230. return arg0.getValue() - arg1.getValue();
  231. }
  232. });
  233. LinkedList<String> rest = new LinkedList<String>();
  234. for (Entry<String, Integer> entry : al) {
  235. rest.add(entry.getKey());
  236. }
  237. return rest;
  238. }
  239. // 把record作为ancestor的后代插入树中
  240. public void addNodes(TreeNode ancestor, LinkedList<String> record,
  241. ArrayList<TreeNode> F1) {
  242. if (record.size() > 0) {
  243. while (record.size() > 0) {
  244. String item = record.poll();
  245. TreeNode leafnode = new TreeNode(item);
  246. leafnode.setCount(1);
  247. leafnode.setParent(ancestor);
  248. ancestor.addChild(leafnode);
  249. for (TreeNode f1 : F1) {
  250. if (f1.getName().equals(item)) {
  251. while (f1.getNextHomonym() != null) {
  252. f1 = f1.getNextHomonym();
  253. }
  254. f1.setNextHomonym(leafnode);
  255. break;
  256. }
  257. }
  258. addNodes(leafnode, record, F1);
  259. }
  260. }
  261. }
  262. }
  263. public static class InverseMapper extends
  264. Mapper<LongWritable, Text, Record, IntWritable> {
  265. @Override
  266. public void map(LongWritable key, Text value, Context context)
  267. throws IOException, InterruptedException {
  268. String []arr=value.toString().split("\\s+");
  269. int count=Integer.parseInt(arr[0]);
  270. Record record=new Record();
  271. for(int i=1;i<arr.length;i++){
  272. record.list.add(arr[i]);
  273. }
  274. context.write(record, new IntWritable(count));
  275. }
  276. }
  277. public static class MaxReducer extends Reducer<Record,IntWritable,IntWritable,Record>{
  278. public void reduce(Record key,Iterable<IntWritable> values,Context context)throws IOException,InterruptedException{
  279. int max=-1;
  280. for(IntWritable value:values){
  281. int i=value.get();
  282. if(i>max)
  283. max=i;
  284. }
  285. context.write(new IntWritable(max), key);
  286. }
  287. }
  288. @Override
  289. public int run(String[] arg0) throws Exception {
  290. Configuration conf=getConf();
  291. conf.set("mapred.task.timeout", "6000000");
  292. Job job=new Job(conf);
  293. job.setJarByClass(DC_FPTree.class);
  294. FileSystem fs=FileSystem.get(getConf());
  295. FileInputFormat.setInputPaths(job, "/user/orisun/input/data");
  296. Path outDir=new Path("/user/orisun/output");
  297. fs.delete(outDir,true);
  298. FileOutputFormat.setOutputPath(job, outDir);
  299. job.setMapperClass(GroupMapper.class);
  300. job.setReducerClass(FPReducer.class);
  301. job.setInputFormatClass(TextInputFormat.class);
  302. job.setOutputFormatClass(TextOutputFormat.class);
  303. job.setMapOutputKeyClass(IntWritable.class);
  304. job.setMapOutputValueClass(Record.class);
  305. job.setOutputKeyClass(IntWritable.class);
  306. job.setOutputKeyClass(Text.class);
  307. boolean success=job.waitForCompletion(true);
  308. job=new Job(conf);
  309. job.setJarByClass(DC_FPTree.class);
  310. FileInputFormat.setInputPaths(job, "/user/orisun/output/part-r-*");
  311. Path outDir2=new Path("/user/orisun/output2");
  312. fs.delete(outDir2,true);
  313. FileOutputFormat.setOutputPath(job, outDir2);
  314. job.setMapperClass(InverseMapper.class);
  315. job.setReducerClass(MaxReducer.class);
  316. //job.setNumReduceTasks(0);
  317. job.setInputFormatClass(TextInputFormat.class);
  318. job.setOutputFormatClass(TextOutputFormat.class);
  319. job.setMapOutputKeyClass(Record.class);
  320. job.setMapOutputValueClass(IntWritable.class);
  321. job.setOutputKeyClass(IntWritable.class);
  322. job.setOutputKeyClass(Record.class);
  323. success |= job.waitForCompletion(true);
  324. return success?0:1;
  325. }
  326. public static void main(String[] args) throws Exception{
  327. int res=ToolRunner.run(new Configuration(), new DC_FPTree(), args);
  328. System.exit(res);
  329. }
  330. }
package fptree;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner; public class DC_FPTree extends Configured implements Tool { private static final int GroupNum = 10;
private static final int minSuport=6; public static class GroupMapper extends
Mapper<LongWritable, Text, IntWritable, Record> {
List<String> freq = new LinkedList<String>(); // 频繁1项集
List<List<String>> freq_group = new LinkedList<List<String>>(); // 分组后的频繁1项集 @Override
public void setup(Context context) throws IOException {
// 从文件读入频繁1项集
FileSystem fs = FileSystem.get(context.getConfiguration());
Path freqFile = new Path("/user/orisun/input/F1");
FSDataInputStream in = fs.open(freqFile);
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
try {
String line;
while ((line = br.readLine()) != null) {
String[] str = line.split("\\s+");
String word = str[0];
freq.add(word);
}
} finally {
br.close();
}
// 对频繁1项集进行分组
Collections.shuffle(freq); // 打乱顺序
int cap = freq.size() / GroupNum; // 每段分为一组
for (int i = 0; i < GroupNum; i++) {
List<String> list = new LinkedList<String>();
for (int j = 0; j < cap; j++) {
list.add(freq.get(i * cap + j));
}
freq_group.add(list);
}
int remainder = freq.size() % GroupNum;
int base = GroupNum * cap;
for (int i = 0; i < remainder; i++) {
freq_group.get(i).add(freq.get(base + i));
}
} @Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String[] arr = value.toString().split("\\s+");
Record record = new Record(arr);
LinkedList<String> list = record.list;
BitSet bs=new BitSet(freq_group.size());
bs.clear();
while (record.list.size() > 0) {
String item = list.peekLast(); // 取出record的最后一项
int i=0;
for (; i < freq_group.size(); i++) {
if(bs.get(i))
continue;
if (freq_group.get(i).contains(item)) {
bs.set(i);
break;
}
}
if(i<freq_group.size()){ //找到了
context.write(new IntWritable(i), record);
}
record.list.pollLast();
}
}
} public static class FPReducer extends Reducer<IntWritable,Record,IntWritable,Text>{
public void reduce(IntWritable key,Iterable<Record> values,Context context)throws IOException,InterruptedException{
List<List<String>> trans=new LinkedList<List<String>>();
while(values.iterator().hasNext()){
Record record=values.iterator().next();
LinkedList<String> list=new LinkedList<String>();
for(String ele:record.list)
list.add(ele);
trans.add(list);
}
FPGrowth(trans, null,context);
}
// FP-Growth算法
public void FPGrowth(List<List<String>> transRecords,
List<String> postPattern,Context context) throws IOException, InterruptedException {
// 构建项头表,同时也是频繁1项集
ArrayList<TreeNode> HeaderTable = buildHeaderTable(transRecords);
// 构建FP-Tree
TreeNode treeRoot = buildFPTree(transRecords, HeaderTable);
// 如果FP-Tree为空则返回
if (treeRoot.getChildren()==null || treeRoot.getChildren().size() == 0)
return;
//输出项头表的每一项+postPattern
if(postPattern!=null){
for (TreeNode header : HeaderTable) {
String outStr=header.getName();
int count=header.getCount();
for (String ele : postPattern)
outStr+="\t" + ele;
context.write(new IntWritable(count), new Text(outStr));
}
}
// 找到项头表的每一项的条件模式基,进入递归迭代
for (TreeNode header : HeaderTable) {
// 后缀模式增加一项
List<String> newPostPattern = new LinkedList<String>();
newPostPattern.add(header.getName());
if (postPattern != null)
newPostPattern.addAll(postPattern);
// 寻找header的条件模式基CPB,放入newTransRecords中
List<List<String>> newTransRecords = new LinkedList<List<String>>();
TreeNode backnode = header.getNextHomonym();
while (backnode != null) {
int counter = backnode.getCount();
List<String> prenodes = new ArrayList<String>();
TreeNode parent = backnode;
// 遍历backnode的祖先节点,放到prenodes中
while ((parent = parent.getParent()).getName() != null) {
prenodes.add(parent.getName());
}
while (counter-- > 0) {
newTransRecords.add(prenodes);
}
backnode = backnode.getNextHomonym();
}
// 递归迭代
FPGrowth(newTransRecords, newPostPattern,context);
}
} // 构建项头表,同时也是频繁1项集
public ArrayList<TreeNode> buildHeaderTable(List<List<String>> transRecords) {
ArrayList<TreeNode> F1 = null;
if (transRecords.size() > 0) {
F1 = new ArrayList<TreeNode>();
Map<String, TreeNode> map = new HashMap<String, TreeNode>();
// 计算事务数据库中各项的支持度
for (List<String> record : transRecords) {
for (String item : record) {
if (!map.keySet().contains(item)) {
TreeNode node = new TreeNode(item);
node.setCount(1);
map.put(item, node);
} else {
map.get(item).countIncrement(1);
}
}
}
// 把支持度大于(或等于)minSup的项加入到F1中
Set<String> names = map.keySet();
for (String name : names) {
TreeNode tnode = map.get(name);
if (tnode.getCount() >= minSuport) {
F1.add(tnode);
}
}
Collections.sort(F1);
return F1;
} else {
return null;
}
} // 构建FP-Tree
public TreeNode buildFPTree(List<List<String>> transRecords,
ArrayList<TreeNode> F1) {
TreeNode root = new TreeNode(); // 创建树的根节点
for (List<String> transRecord : transRecords) {
LinkedList<String> record = sortByF1(transRecord, F1);
TreeNode subTreeRoot = root;
TreeNode tmpRoot = null;
if (root.getChildren() != null) {
while (!record.isEmpty()
&& (tmpRoot = subTreeRoot.findChild(record.peek())) != null) {
tmpRoot.countIncrement(1);
subTreeRoot = tmpRoot;
record.poll();
}
}
addNodes(subTreeRoot, record, F1);
}
return root;
} // 把交易记录按项的频繁程序降序排列
public LinkedList<String> sortByF1(List<String> transRecord,
ArrayList<TreeNode> F1) {
Map<String, Integer> map = new HashMap<String, Integer>();
for (String item : transRecord) {
// 由于F1已经是按降序排列的,
for (int i = 0; i < F1.size(); i++) {
TreeNode tnode = F1.get(i);
if (tnode.getName().equals(item)) {
map.put(item, i);
}
}
}
ArrayList<Entry<String, Integer>> al = new ArrayList<Entry<String, Integer>>(
map.entrySet());
Collections.sort(al, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> arg0,
Entry<String, Integer> arg1) {
// 降序排列
return arg0.getValue() - arg1.getValue();
}
});
LinkedList<String> rest = new LinkedList<String>();
for (Entry<String, Integer> entry : al) {
rest.add(entry.getKey());
}
return rest;
} // 把record作为ancestor的后代插入树中
public void addNodes(TreeNode ancestor, LinkedList<String> record,
ArrayList<TreeNode> F1) {
if (record.size() > 0) {
while (record.size() > 0) {
String item = record.poll();
TreeNode leafnode = new TreeNode(item);
leafnode.setCount(1);
leafnode.setParent(ancestor);
ancestor.addChild(leafnode); for (TreeNode f1 : F1) {
if (f1.getName().equals(item)) {
while (f1.getNextHomonym() != null) {
f1 = f1.getNextHomonym();
}
f1.setNextHomonym(leafnode);
break;
}
} addNodes(leafnode, record, F1);
}
}
}
} public static class InverseMapper extends
Mapper<LongWritable, Text, Record, IntWritable> {
@Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String []arr=value.toString().split("\\s+");
int count=Integer.parseInt(arr[0]);
Record record=new Record();
for(int i=1;i<arr.length;i++){
record.list.add(arr[i]);
}
context.write(record, new IntWritable(count));
}
} public static class MaxReducer extends Reducer<Record,IntWritable,IntWritable,Record>{
public void reduce(Record key,Iterable<IntWritable> values,Context context)throws IOException,InterruptedException{
int max=-1;
for(IntWritable value:values){
int i=value.get();
if(i>max)
max=i;
}
context.write(new IntWritable(max), key);
}
} @Override
public int run(String[] arg0) throws Exception {
Configuration conf=getConf();
conf.set("mapred.task.timeout", "6000000");
Job job=new Job(conf);
job.setJarByClass(DC_FPTree.class);
FileSystem fs=FileSystem.get(getConf()); FileInputFormat.setInputPaths(job, "/user/orisun/input/data");
Path outDir=new Path("/user/orisun/output");
fs.delete(outDir,true);
FileOutputFormat.setOutputPath(job, outDir); job.setMapperClass(GroupMapper.class);
job.setReducerClass(FPReducer.class); job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
job.setMapOutputKeyClass(IntWritable.class);
job.setMapOutputValueClass(Record.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputKeyClass(Text.class); boolean success=job.waitForCompletion(true); job=new Job(conf);
job.setJarByClass(DC_FPTree.class); FileInputFormat.setInputPaths(job, "/user/orisun/output/part-r-*");
Path outDir2=new Path("/user/orisun/output2");
fs.delete(outDir2,true);
FileOutputFormat.setOutputPath(job, outDir2); job.setMapperClass(InverseMapper.class);
job.setReducerClass(MaxReducer.class);
//job.setNumReduceTasks(0); job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
job.setMapOutputKeyClass(Record.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputKeyClass(Record.class); success |= job.waitForCompletion(true); return success?0:1;
} public static void main(String[] args) throws Exception{
int res=ToolRunner.run(new Configuration(), new DC_FPTree(), args);
System.exit(res);
}
}

结束语

在实践中,关联规则挖掘可能并不像人们期望的那么有用。一方面是因为支持度置信度框架会产生过多的规则,并不是每一个规则都是有用的。另一方面大部分的关联规则并不像“啤酒与尿布”这种经典故事这么普遍。关联规则分析是需要技巧的,有时需要用更严格的统计学知识来控制规则的增殖。 

原文来自:博客园(华夏35度)http://www.cnblogs.com/zhangchaoyang 作者:Orisun

FPGrowth 实现的更多相关文章

  1. FPGrowth算法总结复习

    摘要: 1.算法概述 2.算法推导 3.算法特性及优缺点 4.注意事项 5.实现和具体例子 6.适用场合 内容: 1.算法概述 关联规则(associatio rules):从大规模数据集中寻找物品建 ...

  2. FP-growth高效频繁项集发现

    FP-growth 算法优缺点: 优点:一般快于Apriori 缺点:实现比较困难,在某些数据上性能下降 适用数据类型:标称型数据 算法思想: FP-growth算法是用来解决频繁项集发现问题的,这个 ...

  3. 使用 FP-growth 算法高效挖掘海量数据中的频繁项集

    前言 对于如何发现一个数据集中的频繁项集,前文讲解的经典 Apriori 算法能够做到. 然而,对于每个潜在的频繁项,它都要检索一遍数据集,这是比较低效的.在实际的大数据应用中,这么做就更不好了. 本 ...

  4. 大数据挖掘: FPGrowth初识--进行商品关联规则挖掘

    @(hadoop)[Spark, MLlib, 数据挖掘, 关联规则, 算法] [TOC] 〇.简介 经典的关联规则挖掘算法包括Apriori算法和FP-growth算法.Apriori算法多次扫描交 ...

  5. FP-Growth算法及演示程序

    FP-Growth算法 FP-Growth(频繁模式增长)算法是韩家炜老师在2000年提出的关联分析算法,它采取如下分治策略:将提供频繁项集的数据库压缩到一棵频繁模式树(FP-Tree),但仍保留项集 ...

  6. 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...

  7. 数据挖掘系列(2)--关联规则FpGrowth算法

    上一篇介绍了关联规则挖掘的一些基本概念和经典的Apriori算法,Aprori算法利用频繁集的两个特性,过滤了很多无关的集合,效率提高不少,但是我们发现Apriori算法是一个候选消除算法,每一次消除 ...

  8. Machine Learning in Action -- FP-growth

    要解决的问题,频繁项集 最暴力的方法,就是遍历所有的项集组合,当然计算量过大 最典型的算法apriori, 算法核心思想,当一个集合不是频繁项集,那么它的超集也一定不是频繁项集 这个结论是很明显的,基 ...

  9. 使用Apriori算法和FP-growth算法进行关联分析

    系列文章:<机器学习实战>学习笔记 最近看了<机器学习实战>中的第11章(使用Apriori算法进行关联分析)和第12章(使用FP-growth算法来高效发现频繁项集).正如章 ...

  10. FP-Growth算法之频繁项集的挖掘(python)

    前言: 关于 FP-Growth 算法介绍请见:FP-Growth算法的介绍. 本文主要介绍从 FP-tree 中提取频繁项集的算法.关于伪代码请查看上面的文章. FP-tree 的构造请见:FP-G ...

随机推荐

  1. Android图表库MPAndroidChart(三)——双重轴线形图的实现,这次就so easy了

    Android图表库MPAndroidChart(三)--双重轴线形图的实现,这次就so easy了 在学习本课程之前我建议先把我之前的博客看完,这样对整体的流程有一个大致的了解 Android图表库 ...

  2. Ajax PHP JavaScript MySQL实现简易的无刷新在线聊天室

    思路 消息显示区 发消息 板块 消息显示 消息发送 优化 显示非重复性的数据 优化显示 加上滚动条 每次都显示最新消息 完整代码 前端代码 数据库表结构 服务器端代码 总结与展望 总结 展望 为更好的 ...

  3. Android 的 Sqlite基本操作

    在 SQL 数据库中保存数据 使用数据库 将数据保存到数据库对于重复或结构化数据(比如契约信息)而言是理想之选. 本课程假定您基本熟悉 SQL 数据库并且可帮助您开始在 Android 中使用 SQL ...

  4. 剑指Offer——求职必备神器

    剑指Offer--求职必备神器 前言   不管是公司网申.银行招聘.面试等等,"谈谈你的职业规划"."以往工作中遇到了哪些棘手问题?你是如何解决的?".&quo ...

  5. HttpClient4.5.2调用示例(转载+原创)

    操作HttpClient时的一个工具类,使用是HttpClient4.5.2 package com.xxxx.charactercheck.utils; import java.io.File; i ...

  6. shell入门之变量测试

    格式:test 测试条件 字符串测试: 注意空格: test str1 == str2 测试字符串是否相等 test str1 != str2 测试字符串是否不相等 test str1 测试字符串是否 ...

  7. 01 Android修改新建虚拟机存放的位置

    创建的Android虚拟机(即AVD)的位置是在C盘的当前用户文件夹下(C:\Users\用户名\.android\avd) 这一默认设置不怎么好,特别是C盘空间吃紧的话. 这里提供2个解决办法(分2 ...

  8. [java面试]宇信易诚 广州分公司 java笔试题目回忆录

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/28479895 作者:sushengmiyan -------------------- ...

  9. 公司间INVOICE的库存设置

     公司间INVOICE  库存设置信息 实施多组织支持的步骤 1. 开发组织架构 2. 定义主要分类帐 3. 定义组织 4. 定义组织间关系 5. 定义职责 6. 为职责设置业务实体配置文件选项 ...

  10. 【一天一道LeetCode】#371. Sum of Two Integers

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Calcula ...