关联分析FPGrowth算法在JavaWeb项目中的应用
Posted JalonY
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关联分析FPGrowth算法在JavaWeb项目中的应用相关的知识,希望对你有一定的参考价值。
关联分析(关联挖掘)是指在交易数据、关系数据或其他信息载体中,查找存在于项目集合或对象集合之间的频繁模式、关联、相关性或因果结构。关联分析的一个典型例子是购物篮分析。通过发现顾客放入购物篮中不同商品之间的联系,分析顾客的购买习惯。比如,67%的顾客在购买尿布的同时也会购买啤酒。通过了解哪些商品频繁地被顾客同时购买,可以帮助零售商制定营销策略。分析结果可以应用于商品货架布局、货存安排以及根据购买模式对顾客进行分类。
FPGrowth算法是韩嘉炜等人在2000年提出的关联分析算法,在算法中使用了一种称为频繁模式树(Frequent Pattern Tree)的数据结构,基于上述数据结构加快整个关联规则挖掘过程。采取如下分治策略:将提供频繁项集的数据库压缩到一棵频繁模式树(FP-Tree),但仍保留项集关联信息。该算法和Apriori算法最大的不同有两点:第一,不产生候选集,第二,只需要两次遍历数据库,大大提高了效率。
一、前言
首先理解频繁项集中的以下概念:
频繁项:在多个集合中,频繁出现的元素项。
频繁项集:在一系列集合中每项都含有某些相同的元素,这些元素形成一个子集,满足一定阀值就是频繁项集。
K项集:K个频繁项组成的一个集合。
下面用一个例子(事务数据库)说明支持度与置信度,每一行为一个事务,事务由若干个互不相同的项构成,任意几个项的组合称为一个项集。
A E F G
A F G
A B E F G
E F G
支持度:在所有项集中出现的可能性。如项集{A,F,G}的支持数为3,支持度为3/4。支持数大于阈值minSuport的项集称为频繁项集。{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。
综上所述,理论上可以通过FPGrowth算法从频繁集中挖掘相关规则,再通过置信度筛选出规则用于推荐功能。在本人这个JavaWeb项目中,使用FPGrowth算法基于所有用户搜索历史记录,结合当前搜索记录推荐用户可能感兴趣的(置信度大于阈值的搜索记录)、以及其他用户搜索过的(频繁项集中非当前搜索记录)。上述仅是个人观点,如有错误之处还请不吝赐教。
二、正文
1、用户搜索记录实体类:
1 package entity; 2 3 /** 4 * 用户搜索历史记录 5 * @author: yjl 6 * @date: 2018/5/24 7 */ 8 public class TQueryHistory { 9 10 private Integer id; 11 12 private String userAccount; //用户账号 13 14 private String queryCorpName; //用户搜索的企业 15 16 public TQueryHistory() { 17 } 18 19 public TQueryHistory(String userAccount, String queryCorpName) { 20 this.userAccount = userAccount; 21 this.queryCorpName = queryCorpName; 22 } 23 24 public TQueryHistory(Integer id, String userAccount, String queryCorpName) { 25 this.id = id; 26 this.userAccount = userAccount; 27 this.queryCorpName = queryCorpName; 28 } 29 30 public Integer getId() { 31 return id; 32 } 33 34 public void setId(Integer id) { 35 this.id = id; 36 } 37 38 public String getUserAccount() { 39 return userAccount; 40 } 41 42 public void setUserAccount(String userAccount) { 43 this.userAccount = userAccount; 44 } 45 46 public String getQueryCorpName() { 47 return queryCorpName; 48 } 49 50 public void setQueryCorpName(String queryCorpName) { 51 this.queryCorpName = queryCorpName; 52 } 53 54 55 @Override 56 public String toString() { 57 return "TQueryHistory{" + 58 "id=" + id + 59 ", userAccount=\'" + userAccount + \'\\\'\' + 60 ", queryCorpName=\'" + queryCorpName + \'\\\'\' + 61 \'}\'; 62 } 63 }
2、FPGrowth挖掘相关规则前的数据准备,类似于上述的事务数据库,corpName为用户当前搜索的企业,最后得到的interestedCorpList与otherSearchCorpList集合分别表示用户感兴趣的企业、其他用户搜索过的企业,若集合数量不足可以根据企业行业等属性补充:
1 //获取所有用户的搜索记录 2 List<TQueryHistory> allQueryHistory = searchCorpService.getAllQueryHistory(); 3 4 //根据用户账号分类 5 Map<String, Integer> accountMap = new HashMap(); 6 for(TQueryHistory tQueryHistory: allQueryHistory){ 7 accountMap.put(tQueryHistory.getUserAccount(),0); 8 } 9 10 //根据已分类账号分配 11 Map<String,List<String>> newQueryHistoryMap = new HashMap<>(); 12 for(Map.Entry<String,Integer> entry: accountMap.entrySet()){ 13 String account = entry.getKey(); 14 List<String> accountTQueryHistoryList = new ArrayList<>(); 15 for(TQueryHistory tQueryHistory: allQueryHistory){ 16 if(tQueryHistory.getUserAccount().equals(account)){ 17 accountTQueryHistoryList.add(tQueryHistory.getQueryCorpName()); 18 } 19 } 20 newQueryHistoryMap.put(account,accountTQueryHistoryList); 21 } 22 23 //遍历Map将企业名称写入文件,并传至FPTree 24 String outfile = "QueryHistory.txt"; 25 BufferedWriter bw = new BufferedWriter(new FileWriter(outfile)); 26 for(Map.Entry<String,List<String>> entry: newQueryHistoryMap.entrySet()){ 27 List<String> corpNameList = entry.getValue(); 28 29 bw.write(joinList(corpNameList)); 30 bw.newLine(); 31 } 32 bw.close(); 33 34 //Map取值分别放入对应的集合 35 Map<String, List<String>> corpMap = FPTree.introQueryHistory(outfile,corpName); 36 List<String> interestedCorpList = new ArrayList<>(); 37 List<String> otherSearchCorpList = new ArrayList<>(); 38 for(Map.Entry<String,List<String>> entry: corpMap.entrySet()){ 39 if("interestedCorpList".equals(entry.getKey())){ 40 interestedCorpList = entry.getValue(); 41 } 42 if("otherSearchCorpList".equals(entry.getKey())){ 43 otherSearchCorpList = entry.getValue(); 44 } 45 }
1 //设置文件写入规则 2 private static String joinList(List<String> list) { 3 if (list == null || list.size() == 0) { 4 return ""; 5 } 6 StringBuilder sb = new StringBuilder(); 7 for (String ele : list) { 8 sb.append(ele); 9 sb.append(","); 10 } 11 return sb.substring(0, sb.length() - 1); 12 }
3、FPStrongAssociationRule类为强关联规则变量:
1 package util; 2 3 import java.util.List; 4 5 public class FPStrongAssociationRule { 6 7 public List<String> condition; 8 9 public String result; 10 11 public int support; 12 13 public double confidence; 14 15 }
4、FPTreeNode类为FPTree的相关变量:
1 package util; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class FPTreeNode { 7 8 private String name; //节点名称 9 private int count; //频数 10 private FPTreeNode parent; //父节点 11 private List<FPTreeNode> children; //子节点 12 private FPTreeNode nextHomonym; //下一个节点(由表头项维护的那个链表) 13 private FPTreeNode tail; //末节点(由表头项维护的那个链表) 14 15 16 17 public FPTreeNode() { 18 } 19 20 public FPTreeNode(String name) { 21 this.name = name; 22 } 23 24 public String getName() { 25 return this.name; 26 } 27 28 public void setName(String name) { 29 this.name = name; 30 } 31 32 public int getCount() { 33 return this.count; 34 } 35 36 public void setCount(int count) { 37 this.count = count; 38 } 39 40 public FPTreeNode getParent() { 41 return this.parent; 42 } 43 44 public void setParent(FPTreeNode parent) { 45 this.parent = parent; 46 } 47 48 public List<FPTreeNode> getChildren() { 49 return this.children; 50 } 51 52 public void setChildren(List<FPTreeNode> children) { 53 this.children = children; 54 } 55 56 public FPTreeNode getNextHomonym() { 57 return this.nextHomonym; 58 } 59 60 public void setNextHomonym(FPTreeNode nextHomonym) { 61 this.nextHomonym = nextHomonym; 62 } 63 64 public FPTreeNode getTail() { 65 return tail; 66 } 67 68 public void setTail(FPTreeNode tail) { 69 this.tail = tail; 70 } 71 72 //添加子节点 73 public void addChild(FPTreeNode child) { 74 if (getChildren() == null) { 75 List<FPTreeNode> list = new ArrayList<>(); 76 list.add(child); 77 setChildren(list); 78 } else { 79 getChildren().add(child); 80 } 81 } 82 83 //查询子节点 84 public FPTreeNode findChild(String name) { 85 List<FPTreeNode> children = getChildren(); 86 if (children != null) { 87 for (FPTreeNode child : children) { 88 if (child.getName().equals(name)) { 89 return child; 90 } 91 } 92 } 93 return null; 94 } 95 96 97 public void countIncrement(int n) { 98 this.count += n; 99 } 100 101 102 @Override 103 public String toString() { 104 return name; 105 } 106 }
5、FPTree类为FPGrowth算法挖掘规则,introQueryHistory函数根据传入所有用户的搜索记录以及当前搜索的企业,得到用户可能感兴趣的企业以及其他用户搜索过的企业,以及限制每个集合中的企业数量:
1 package util; 2 3 import java.io.BufferedReader; 4 import java.io.FileReader; 5 import java.io.IOException; 6 import java.text.DecimalFormat; 7 import java.util.*; 8 import java.util.Map.Entry; 9 10 public class FPTree { 11 12 private int minSuport; //频繁模式的最小支持数 13 private double confident; //关联规则的最小置信度 14 private int totalSize; //事务项的总数 15 private Map<List<String>, Integer> frequentMap = new HashMap<>(); //存储每个频繁项及其对应的计数 16 private Set<String> decideAttr = null; //关联规则中,哪些项可作为被推导的结果,默认情况下所有项都可以作为被推导的结果 17 18 19 20 public void setMinSuport(int minSuport) { 21 this.minSuport = minSuport; 22 } 23 24 public void setConfident(double confident) { 25 this.confident = confident; 26 } 27 28 public void setDecideAttr(Set<String> decideAttr) { this.decideAttr = decideAttr;} 29 30 31 32 /** 33 * 获取强关联规则 34 * @return 35 * @Description: 36 */ 37 private List<FPStrongAssociationRule> getRules(List<String> list) { 38 List<FPStrongAssociationRule> rect = new LinkedList<>(); 39 if (list.size() > 1) { 40 for (int i = 0; i < list.size(); i++) { 41 String result = list.get(i); 42 if (decideAttr.contains(result)) { 43 List<String> condition = new ArrayList<>(); 44 condition.addAll(list.subList(0, i)); 45 condition.addAll(list.subList(i + 1, list.size())); 46 FPStrongAssociationRule rule = new FPStrongAssociationRule(); 47 rule.condition = condition; 48 rule.result = result; 49 rect.add(rule); 50 } 51 } 52 } 53 return rect; 54 } 55 56 57 /** 58 * 从若干个文件中读入Transaction Record,同时把所有项设置为decideAttr 59 * @return 60 * @Description: 61 */ 62 public List<List<String>> readTransRocords(String[] filenames) { 63 Set<String> set = new HashSet<>(); 64 List<List<String>> transaction = null; 65 if (filenames.length > 0) { 66 transaction = new LinkedList<>(); 67 for (String filename : filenames) { 68 try { 69 FileReader fr = new FileReader(filename); 70 BufferedReader br = new BufferedReader(fr); 71 try { 72 String line; 73 // 一项事务占一行 74 while ((line = br.readLine()) != null) { 75 if (line.trim().length() > 0) { 76 // 每个item之间用","分隔 77 String[] str = line.split(","); 78 //每一项事务中的重复项需要排重 79 Set<String> record = new HashSet<>(); 80 for (String w : str) { 81 record.add(w); 82 set.add(w); 83 } 84 List<String> rl = new ArrayList<>(); 85 rl.addAll(record); 86 transaction.add(rl); 87 } 88 } 89 } finally { 90 br.close(); 91 } 92 } catch (IOException ex) { 93 System.out.println("Read transaction records failed." + ex.getMessage()); 94 System.exit(1); 95 } 96 } 97 } 98 99 this.setDecideAttr(set); 100 return transaction; 101 } 102 103 104 /** 105 * 生成一个序列的各种子序列(序列是有顺序的) 106 * @param residualPath 107 * @param results 108 */ 109 private void combine(LinkedList<FPTreeNode> residualPath, List<List<FPTreeNode>> results) { 110 if (residualPath.size() > 0) { 111 //如果residualPath太长,则会有太多的组合,内存会被耗尽的 112 FPTreeNode head = residualPath.poll(); 113 List<List<FPTreeNode>> newResults = new ArrayList<>(); 114 for (List<FPTreeNode> list : results) { 115 List<FPTreeNode> listCopy = new ArrayList<>(list); 116 newResults.add(listCopy); 117 } 118 119 for (List<FPTreeNode> newPath : newResults) { 120 newPath.add(head); 121 } 122 results.addAll(newResults); 123 List<FPTreeNode> list = new ArrayList<>(); 124 list.add(head); 125 results.add(list); 126 combine(residualPath, results); 127 } 128 } 129 130 /** 131 * 判断是否为单节点 132 * @param root 133 */ 134 private boolean isSingleBranch(FPTreeNode root) { 135 boolean rect = true; 136 while (root.getChildren() != null) { 137 if (root.getChildren().size() > 1) { 138 rect = false; 139 break; 140 } 141 root = root.getChildren().get(0); 142 } 143 return rect; 144 } 145 146 /** 147 * 计算事务集中每一项的频数 148 * @param transRecords 149 * @return 150 */ 151 private Map<String, Integer> getFrequency(List<List<String>> transRecords) { 152 Map<String, Integer> rect = new HashMap<>(); 153 for (List<String> record : transRecords) { 154 for (String item : record) { 155 Integer cnt = rect.get(item); 156 if (cnt == null) { 157 cnt = new Integer(0); 158 } 159 rect.put(item, ++cnt); 160 } 161 } 162 return rect; 163 关联规则之FpGrowth算法