在关联规则挖掘领域最经典的算法法是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. [linux RedHat]windows下使用putty远程连接linux 下载JDK和tomcat

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

  2. [ExtJS5学习笔记]第三十一节 sencha extjs 5使用cmd生成的工程部署到tomcat服务器

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

  3. EBS库存(INV)模块常用表

     select * from org_organization_definitions库存组织 select * from mtl_parameters组织参数 select * from mtl ...

  4. Oracle 执行计划(Explain Plan) 说明

    如果要分析某条SQL的性能问题,通常我们要先看SQL的执行计划,看看SQL的每一步执行是否存在问题. 如果一条SQL平时执行的好好的,却有一天突然性能很差,如果排除了系统资源和阻塞的原因,那么基本可以 ...

  5. java虚拟机 jvm java堆 方法区 java栈

    java堆是java应用程序最密切的内存空间.几乎所有的对象都存在堆中.java堆完全自动化管理,通过垃圾回收机制,垃圾对象会自动清理,不需要显式释放. 根据java垃圾回收机制的不同,java堆可能 ...

  6. linux下字节对齐

    一,内存地址对齐的概念    计算机内存中排列.访问数据的一种方式,包含基本数据对齐和结构体数据对齐.    32位系统中,数据总线宽度为32,每次能够读取4字节数据.地址总线为32,最大寻址空间为4 ...

  7. [IDE工具配置]myeclipse 2014 专业版 安装 svn插件

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/38342411 本文作者:sushengmiyan 团队合作的项目肯定少不了版本控制,那 ...

  8. C语言中extern关键字的使用

    C语言中extern关键字的使用,直接上代码. file1.c文件 #include<stdio.h> extern long power(int); int A = 2; int mai ...

  9. (国内)完美下载Android源码Ubuntu版

    今天写的文章莫名奇妙的没了,所以再重新写一篇. 首先,为了方便起见,我已经将系统更换成里Ubuntu,因为官方推荐使用这个Linux发行版.先来一张系统的截图: Ubuntu的版本是16.04(推荐用 ...

  10. UML 类图. 对象图. 接口图. 用例图 .包,参与者. 依赖关系. 泛化/继承关系. 关联关系 .聚合/聚集关系. 实现关系 组合关系。

    结构元素 结构元素包括,类,对象,接口,用例,参与者. 类图 类图图示      类图是UML中最基本的元素了吧?根据OO的思想"天下一切皆对象",而类是对象的抽象.      左 ...