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

    Android 接口定义语言 (AIDL) AIDL(Android 接口定义语言)与您可能使用过的其他 IDL 类似. 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的 ...

  2. DOS界面下的翻译软件制作

    准备 素材 依赖 接口 地址 参数 返回值解析 编码及测试 功能代码 运行脚本 环境变量 结果展示 英语转汉语 汉语转英语 总结 昨天看到一篇关于Linux下的桌面词典的文章,于是就想实现一个Wind ...

  3. CSS简单使用

    CSS简单使用 标签 : 前端技术 CSS(Cascading Style Sheet : 层叠样式表单)用来定义网页显示效果. 可以解决HTML代码对样式定义的重复,提高后期样式代码的可维护性,并增 ...

  4. not in 前面/后面存在null值时的处理

    表声明 order_header表中有ship_method列: ship_method_map表中ship_method为主键列. 需求 找出order_header表中所有ship_method不 ...

  5. testng的使用

    TestNG教程 TestNG是一个测试框架,其灵感来自JUnit和NUnit,但同时引入了一些新的功能,使其功能更强大,使用更方便. TestNG设计涵盖所有类型的测试:单元,功能,端到端,集成等, ...

  6. UNIX网络编程——epoll的 et,lt关注点

           epoll模型有两种工作模式,ET和LT两种模式下都有一些细节值得注意,以下是一些思考:   一.ET模式下 Q1:调用accept时,到底TCP完成队列里有多少个已经建立好的连接? 这 ...

  7. JAVA之旅(三十一)——JAVA的图形化界面,GUI布局,Frame,GUI事件监听机制,Action事件,鼠标事件

    JAVA之旅(三十一)--JAVA的图形化界面,GUI布局,Frame,GUI事件监听机制,Action事件,鼠标事件 有段时间没有更新JAVA了,我们今天来说一下JAVA中的图形化界面,也就是GUI ...

  8. unity使用ugui自制调色面板

    突然想实现一个调色面板,然后开工... 首先找找有没有什么接口可调,木有找到,找到一些调用win32实现的本地颜色面板的调用,感觉不科学,反正多平台肯定是搞不定的. 既然没找到,还是老老实实的自己写吧 ...

  9. Linux之mailx的使用

    mailx是UNIX系统上用来处理邮件的工具,使用它可以发送,读取邮件.下面看看如何使用它来发送邮件. 发送格式 mailx -s subject user@xxx.com < message_ ...

  10. ORACLE--Connect By、Level、Start With的使用(Hierarchical query-层次查询)

    查找员工编号为7369的领导: 1 SELECT LEVEL,E.* FROM EMP E CONNECT BY PRIOR E.MGR = E.EMPNO START WITH E.EMPNO = ...