在关联规则挖掘领域最经典的算法法是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. }

对于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. }

挖掘频繁模式

  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. }

输入文件

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

输出

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. }

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. }

结束语

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

Aprior算法的更多相关文章

  1. 数据关联分析 association analysis (Aprior算法,python代码)

    1基本概念 购物篮事务(market basket transaction),如下表,表中每一行对应一个事务,包含唯一标识TID,和购买的商品集合.本文介绍一种成为关联分析(association a ...

  2. 数据挖掘系列 (1) 关联规则挖掘基本概念与 Aprior 算法

    转自:http://www.cnblogs.com/fengfenggirl/p/associate_apriori.html 数据挖掘系列 (1) 关联规则挖掘基本概念与 Aprior 算法 我计划 ...

  3. 数据挖掘Aprior算法详解及c++源码

    [算法大致描述] Aprior算法主要有两个操作,扫描数据库+统计.计算每一阶频繁项集都要扫描一次数据库并且统计出满足支持度的n阶项集. [算法主要步骤] 一.频繁一项集 算法开始第一步,通过扫描数据 ...

  4. Frequent Pattern 挖掘之一(Aprior算法)(转)

    数据挖掘中有一个很重要的应用,就是Frequent Pattern挖掘,翻译成中文就是频繁模式挖掘.这篇博客就想谈谈频繁模式挖掘相关的一些算法. 定义 何谓频繁模式挖掘呢?所谓频繁模式指的是在样本数据 ...

  5. 数据挖掘系列(1)关联规则挖掘基本概念与Aprior算法

    整理数据挖掘的基本概念和算法,包括关联规则挖掘.分类.聚类的常用算法,敬请期待.今天讲的是关联规则挖掘的最基本的知识. 关联规则挖掘在电商.零售.大气物理.生物医学已经有了广泛的应用,本篇文章将介绍一 ...

  6. 关联规则之Aprior算法(购物篮分析)

    0.支持度与置信度 <mahout实战>与<机器学习实战>一起该买的记录数占所有商品记录总数的比例——支持度(整体) 买了<mahout实战>与<机器学习实战 ...

  7. 关联规则之Aprior算法

    关联规则挖掘在电商.零售.大气物理.生物医学已经有了广泛的应用,本篇文章将介绍一些基本知识和Aprori算法. 啤酒与尿布的故事已经成为了关联规则挖掘的经典案例,还有人专门出了一本书<啤酒与尿布 ...

  8. 频繁项集挖掘之Aprior和FPGrowth算法

    频繁项集挖掘的应用多出现于购物篮分析,现介绍两种频繁项集的挖掘算法Aprior和FPGrowth,用以发现购物篮中出现频率较高的购物组合. 基础知识 项:“属性-值”对.比如啤酒2罐.  项集:项的集 ...

  9. Apriori算法原理总结

    Apriori算法是常用的用于挖掘出数据关联规则的算法,它用来找出数据值中频繁出现的数据集合,找出这些集合的模式有助于我们做一些决策.比如在常见的超市购物数据集,或者电商的网购数据集中,如果我们找到了 ...

随机推荐

  1. VS2010每次调试都出现“此项目已经过期”提示

    问题描述   最近因为项目需要,开发平台从VS2005切换成了VS2010,把一些老项目也转换到VS2010平台,因为是从低到高升级,微软还是做了很多兼容,基本上可以无缝切换,编译调试也基本正常,但是 ...

  2. ROS机器人程序设计(原书第2版)补充资料 (叁) 第三章 可视化和调试工具

    ROS机器人程序设计(原书第2版)补充资料 (叁) 第三章 可视化和调试工具 书中,大部分出现hydro的地方,直接替换为indigo或jade或kinetic,即可在对应版本中使用. ~$ rosl ...

  3. N个鸡蛋放到M个篮子中

    N个鸡蛋放到M个篮子中,篮子不能为空,要满足:对任意不大于N的数量,能用若干个篮子中鸡蛋的和表示. 写出函数,对输入整数N和M,输出所有可能的鸡蛋的放法. 比如对于9个鸡蛋5个篮子 解至少有三组: 1 ...

  4. Android Multimedia框架总结(十四)Camera框架初识及自定义相机案例

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52738492 前言:国庆节告一段 ...

  5. 有n个数,输出其中所有和为s的k个数的组合。

    分析:此题有两个坑,一是这里的n个数是任意给定的,不一定是:1,2,3...n,所以可能有重复的数(如果有重复的数怎么处理?):二是不要求你输出所有和为s的全部组合,而只要求输出和为s的k个数的组合. ...

  6. 验证码程序Demo

    小伙伴都有这样的经历,册各种网站,总是输不对验证码,双十一那天狂买的小伙伴是不是对输入验证码有着不一样的感触呢,以前觉得验证码真是个麻烦鬼,一个不小心,一个眼拙,哎呦,没有输入正确,又是一阵子大眼瞪小 ...

  7. Java中Excel导入功能实现、excel导入公共方法_POI -

    这是一个思路希望能帮助到大家:如果大家有更好的解决方法希望分享出来 公司导入是这样做的 每个到导入的地方 @Override public List<DataImportMessage> ...

  8. 插件开发之360 DroidPlugin源码分析(三)Binder代理

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52138483 Hook机制中Binder代理类关系图 Hook机制中Binder代理时 ...

  9. android git上开源的项目收藏

    本文为那些不错的Android开源项目第一篇--个性化控件(View)篇,主要介绍Android上那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Ga ...

  10. Android的搜索框SearchView的用法-android学习之旅(三十九)

    SearchView简介 SearchView是搜索框组件,他可以让用户搜索文字,然后显示.' 代码示例 这个示例加了衣蛾ListView用于为SearchView增加自动补全的功能. package ...