关联推荐算法
基于关联规则的推荐(Association Rule-based Recommendation)是以关联规则为基础,把已购商品作为规则头,规则体为推荐对象。关联规则挖掘可以发现不同商品在销售过程中的相关性,在零售业中已经得到了成功的应用。管理规则就是在一个交易数据库中统计购买了商品集X的交易中有多大比例的交易同时购买了商品集Y,其直观的意义就是用户在购 买某些商品的时候有多大倾向去购买另外一些商品。比如购买牛奶的同时很多人会同时购买面包。
算法的第一步关联规则的发现最为关键且最耗时,是算法的瓶颈,但可以离线进行。其次,商品名称的同义性问题也是关联规则的一个难点。
以apriori算法为例,其挖掘步骤:
1.依据支持度找出所有频繁项集(频度)
2.依据置信度产生关联规则(强度)
主要代码如下:
package apriori; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import com.jolly.bi.aglorithm.conf.AprioriUnit; public class AprioriUtils { private final static String ITEM_SPLIT = ";"; // 项之间的分隔符 private static HashSet<String> getItem1FC(String trans) { HashSet<String> rItem1FcSet = new HashSet<String>();// 频繁1项集 String[] items = trans.split(ITEM_SPLIT); for (String item : items) { rItem1FcSet.add(item+ITEM_SPLIT); } return rItem1FcSet; } public static HashSet<String> getFC(String trans){ HashSet<String> frequentCollectionSet = new HashSet<String>();// 所有的频繁集 HashSet<String> tmp_1fc=getItem1FC(trans); frequentCollectionSet.addAll(tmp_1fc); //导入一项集 HashSet<String> itemkFcSet = new HashSet<String>(); itemkFcSet.addAll(tmp_1fc); int a=0; while (itemkFcSet!=null&&itemkFcSet.size()!=0&&a<=3) { HashSet<String> candidateCollection = getCandidateCollection(itemkFcSet); itemkFcSet.clear(); Iterator<String> it_cd= candidateCollection.iterator(); while (it_cd.hasNext()) { itemkFcSet.add(it_cd.next()); } frequentCollectionSet.addAll(itemkFcSet); a++; } return frequentCollectionSet; } public static HashSet<String> getCandidateCollection(HashSet<String> itemkFcSet){ HashSet<String> candidateCollection=new HashSet<String>(); Iterator<String> is1= itemkFcSet.iterator(); while (is1.hasNext()) { String itemk1=is1.next(); Iterator<String> is2= itemkFcSet.iterator(); while (is2.hasNext()) { String itemk2=is2.next(); String[] tmp1 = itemk1.split(ITEM_SPLIT); String[] tmp2 = itemk2.split(ITEM_SPLIT); String c = ""; if (tmp1.length == 1) { if (tmp1[0].compareTo(tmp2[0]) < 0) { c = tmp1[0] + ITEM_SPLIT + tmp2[0] + ITEM_SPLIT; } } else { boolean flag = true; for (int i = 0; i < tmp1.length - 1; i++) { if (!tmp1[i].equals(tmp2[i])) { flag = false; break; } } if (flag && (tmp1[tmp1.length - 1] .compareTo(tmp2[tmp2.length - 1]) < 0)) { c = itemk1 + tmp2[tmp2.length - 1] + ITEM_SPLIT; } } // 进行剪枝 boolean hasInfrequentSubSet = false; if (!c.equals("")) { String[] tmpC = c.split(ITEM_SPLIT); for (int i = 0; i < tmpC.length; i++) { String subC = ""; for (int j = 0; j < tmpC.length; j++) { if (i != j) { subC = subC + tmpC[j] + ITEM_SPLIT; } } } } else { hasInfrequentSubSet = true; } if (!hasInfrequentSubSet) { candidateCollection.add(c); } } } return candidateCollection; } private static void buildSubSet(List<String> sourceSet, List<List<String>> result) { // 仅有一个元素时,递归终止。此时非空子集仅为其自身,所以直接添加到result中 if (sourceSet.size() == 1) { List<String> set = new ArrayList<String>(); set.add(sourceSet.get(0)); result.add(set); } else if (sourceSet.size() > 1) { // 当有n个元素时,递归求出前n-1个子集,在于result中 buildSubSet(sourceSet.subList(0, sourceSet.size() - 1), result); int size = result.size();// 求出此时result的长度,用于后面的追加第n个元素时计数 // 把第n个元素加入到集合中 List<String> single = new ArrayList<String>(); single.add(sourceSet.get(sourceSet.size() - 1)); result.add(single); // 在保留前面的n-1子集的情况下,把第n个元素分别加到前n个子集中,并把新的集加入到result中; // 为保留原有n-1的子集,所以需要先对其进行复制 List<String> clone; for (int i = 0; i < size; i++) { clone = new ArrayList<String>(); for (String str : result.get(i)) { clone.add(str); } clone.add(sourceSet.get(sourceSet.size() - 1)); result.add(clone); } } } public static Map<String, Double> getRelationRules( String key,Map<String, Integer> frequentCollectionMap) { Map<String, Double> relationRules = new HashMap<String, Double>(); double countAll = frequentCollectionMap.get(key); String[] keyItems = key.split(ITEM_SPLIT); if (keyItems.length > 1) { List<String> source = new ArrayList<String>(); Collections.addAll(source, keyItems); List<List<String>> result = new ArrayList<List<String>>(); buildSubSet(source, result);// 获得source的所有非空子集 for (List<String> itemList : result) { if (itemList.size() < source.size()) {// 只处理真子集 List<String> otherList = new ArrayList<String>(); for (String sourceItem : source) { if (!itemList.contains(sourceItem)) { otherList.add(sourceItem); } } String reasonStr = "";// 前置 String resultStr = "";// 结果 for (String item : itemList) { reasonStr = reasonStr + item + ITEM_SPLIT; } for (String item : otherList) { resultStr = resultStr + item + ITEM_SPLIT; } double countReason = frequentCollectionMap .get(reasonStr); double itemConfidence = countAll / countReason;// 计算置信度 if (itemConfidence >= AprioriUnit._CONFIDENCE) { String rule = reasonStr + AprioriUnit._CON + resultStr; relationRules.put(rule, itemConfidence); } } } } return relationRules; } }
应用场景举例:
(1)数据输入为订单下的所有商品id
125403,185733,196095,117965,201975,212841,181789
149693,210991,13992,64312,54796,194527
(2)计算所有频繁项集,依据支持度过滤
(3)根据公式,得到符合置信度条件的所有推荐
104138,196705,0.1875
104138,196705,0.1800
结论是 购买了商品104138中购买196705的最多