程序猿成长之路之数据挖掘篇——频繁项集挖掘介绍

频繁项集挖掘可以说是数据挖掘中的重点,下面我们来分析以下频繁项集挖掘的过程和目标

如果对数据挖掘没有概念的小伙伴可以查看上次的文章
https://blog.csdn.net/qq_31236027/article/details/137046475

什么是频繁项集?

在回答这个问题之前,我们可以看一个例子:
小明、小刚、小红三人去同一家商店购物,小明、小刚两人购买了鸡蛋,牛奶和面包,小红购买了鸡蛋和牛奶,这时候一位聪明的店员便推荐小红购买面包,并且说这个面包很适合购买了鸡蛋和牛奶的客户,小红心动了。
在这个例子中,我们可以看到小明、小刚和小红都有去购买商品的行为,而其中的每一个商品可以称为一个,小明、小刚、小红所购买的商品的集合就成为项集。那么什么是频繁项集呢?所谓的频繁项集理解起来也相对容易了,就是用户频繁购买的商品的集合,也就是说会被大部分用户购买的商品的集合。显而易见,在例子中鸡蛋、牛奶可以称为一个频繁项集。

频繁项集有啥用处?

再次回到之前的那个例子中,那个聪明的店员根据小明和小刚的购买记录进行商品的推荐,这个就利用到了频繁项集的一个优势:允许系统(店员)利用已有的频繁项集(顾客的购买记录) 针对某一客户进行商品推荐。那么为什么可以这么做呢?这么做的依据是什么呢?下面让我们来看一下频繁项集挖掘的过程。

频繁项集挖掘过程

先上个例子(基于Apriori):
已知
用户B收藏了物品A、B、C,
用户C收藏了物品A、D,
用户D收藏了物品A、B、C、D,
用户E收藏了物品A、B、E
用户A收藏了物品A、B,现在需要针对用户A进行物品推荐。

  1. 首先我们选取一个集合,就设置为{物品A}
  2. 我们不难发现购买了物品A的用户3/4都收藏了物品B,这时候我们可以设定一个阈值,只有频率(出现次数)超过这个值的项集才会被保留,这个值又称作最小支持度(min_support)。假设最小支持度为0.5, 也就是说物品D不会被推荐,因为只有一个用户在收藏了物品A后收藏了物品D(是用户C), 收藏D的后验概率为1/3 < 0.5。
  3. 于是集合扩容为{物品A,物品B}
  4. 之后而我们推出收藏了物品A、B后收藏物品C的概率为 1/2,也就是{用户B,用户D}/ {用户B,用户C,用户D、用户E},而用户收藏A、B、E的概率为1/4 < 0.5 因此不被保留
  5. 集合扩容为{物品A,物品B,物品C}
  6. 因为购买物品A,物品B,物品C后购买物品D的项集只出现1次,1/4 < min_support = 0.5 因此,该项集非频繁项集,因此最大频繁项集为{物品A,物品B,物品C}
  7. 之后针对用户A进行推荐,这时候需要逐层进行筛选,不难得出{物品A,物品B} => {物品A,物品B,物品C} 的概率为2/3,超过了我们设定的第二个阈值,称为最小置信度(也就是最小关联度),而{物品A,物品B} = > {物品A,物品B,物品E} 的概率为1/3 < 0.5 。不推荐。因此会向用户A推荐物品C,

Apriori算法

好了,朋友们,看到了现在这一步可以恭喜你已经初步了解频繁项集的挖掘过程了。
下面我们来看一下Apriori的算法:

package apriori;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * apriori算法
 * @author zygswo
 *
 */
public class Apriori {
	/**
	 * 最小支持度
	 */
	private static final double MIN_SUPPORT = 0.5;
	
	/**
	 * 最小置信度
	 */
	private static final double MIN_FAITH = 0.5;

	/**
	 * 算法核心
	 * @param trainDataSet 训练集
	 * @param usersCollection 用户喜好
	 * @return
	 */
	private static List<String>
			getResult(Map<String,String[]> trainDataSet,String[] usersCollection) {
		//1.训练集训练
		Map<String,Double> res = new ConcurrentHashMap<String,Double>();
		res = trainData(res,trainDataSet);
		System.out.println(res.toString());
		//2.推荐
		return recommend(res, usersCollection);
	}
	
	/**
	 * 推荐
	 * @param res
	 * @param usersCollection
	 * @return
	 */
	private static List<String> recommend(Map<String, Double> res, String[] usersCollection) {
		// TODO Auto-generated method stub
		String key = "";
		List<String> list = new ArrayList<>();
		for (String str: usersCollection) {
			key += str;
		}
		double countNb = res.get(key);
		for (String str: res.keySet()) {
			if (str.length() != key.length() + 1) {
				continue;
			}
			boolean contains = true;
			for (char ch : key.toCharArray()) {
				if (str.indexOf(ch) == -1) {
					contains = false;
					break;
				}
			}
			if (contains){
				if (res.get(str) /countNb * 1.0 >= MIN_FAITH) {
					System.out.println(key + " -->"  + str + " faith = " +  res.get(str) / countNb * 1.0);
					list.add(str.replace(key, ""));
				}	
			}
		}
		return list;
	}

	/**
	 * 训练训练集
	 * @param res
	 * @param trainDataSet
	 * @return
	 */
	private static Map<String, Double> trainData(
			Map<String, Double> res,
			Map<String, String[]> trainDataSet) {
		res.putAll(trainData(trainDataSet.size(),0, res, trainDataSet));
		return res;
	}

	/**
	 * 训练训练集
	 * @param initSize 初始数组长度
	 * @param roundNb 轮数
	 * @param res 结果map
	 * @param trainDataSet 训练数据集
	 * @return
	 */
	private static Map<String, Double> trainData(
			int initSize,
			int roundNb,
			Map<String,Double> res,
			Map<String,String[]> trainDataSet
	) {
		//统计
//		System.out.println("roundNb = " + roundNb);
		for (String[] itemArr : trainDataSet.values()) {
			//获取当前用户的收藏item集合,也就是获取项集
			String tempStr = "";
			for (String item:itemArr) {
				tempStr += item;
			}
			//针对项集统计频率
			if (roundNb == 0) {
				for (String item:itemArr) {
					if (res.get(item) == null) {
						res.put(item, 1.0);
					} else {
						res.put(item, res.get(item) + 1.0);
					}	
				}
			} else {
				for (String resStr : res.keySet()) {
					//如果字符串长度不为roundNb+1就说明不是当前的那层项集
					if (resStr.length() != roundNb + 1) {
						continue;
					}
					boolean contains = true;
					for (char ch : resStr.toCharArray()) {
						if (tempStr.indexOf(ch) == -1) {
							contains = false;
							break;
						}
					}
					if (contains){
						res.put(resStr, res.get(resStr) + 1.0);
					}
				}
			}
		}
		//筛选
		for (String str:res.keySet()) {
			if (res.get(str) < MIN_SUPPORT * initSize) {
				res.remove(str);
			}
		}
		//新增
		Map<String,Double> newRes = new ConcurrentHashMap<String, Double>();
		for (String str:res.keySet()) {
			if (str.length() != roundNb + 1) {
				continue;
			}
			for (String substr:res.keySet()) {
				//每次获取一位,之后叠加
				if (substr.length() != 1) {
					continue;
				}
				String lastChar = str.charAt(str.length() - 1) + "";
				//判断大小,只允许字符串递增排列,如AC,AB,AD,CD
				if(substr.compareTo(lastChar) > 0) {
					newRes.put(str+substr, 0.0);
				}
			}
		}
		if (newRes.isEmpty()) {
			return res;
		} else {
			res.putAll(newRes);
			return trainData(initSize,roundNb+1,res,trainDataSet);
		}
	}

	public static void main(String[] args) {
		Map<String,String[]> trainDataSet = new ConcurrentHashMap<>();
		trainDataSet.put("userB", new String[]{"A","B","C"});
		trainDataSet.put("userC", new String[]{"A","D"});
		trainDataSet.put("userD", new String[]{"A","B","C","D"});
		trainDataSet.put("userE", new String[]{"A","B","E"});
		trainDataSet.put("userF", new String[]{"A","B","C","E"});
		System.out.println("推荐结果为" + getResult(trainDataSet,new String[]{"A","B"}));
	}
}


运行截图
在这里插入图片描述

—————————————2024年4月10日:代码解析—————————————————————

代码解析:

主要步骤为以下两步骤,如下面的代码所示:
1. 根据数据集进行训练
2. 根据得到的频繁项集及用户的喜好列表进行项集的推荐

/**
	 * 算法核心
	 * @param trainDataSet 训练集
	 * @param usersCollection 用户喜好
	 * @return
	 */
	private static List<String>
			getResult(Map<String,String[]> trainDataSet,String[] usersCollection) {
		//1.训练集训练
		Map<String,Double> res = new ConcurrentHashMap<String,Double>();
		res = trainData(res,trainDataSet);
		System.out.println(res.toString());
		//2.推荐
		return recommend(res, usersCollection);
	}

训练集训练

主要参数:

  1. trainDataSet 训练集(格式:<用户名,喜好列表>)
  2. res 结果集
  3. initSize 训练集长度,设置的目的为了能够计算最小支持度并进行剪枝
  4. roundNb 轮数,设置的目的是为了能够筛选出合适长度的项集(项集长度=轮数)进行挖掘计算

主要原理:

  1. 从第0轮开始,每一轮round+1,详见
return trainData(initSize,roundNb+1,res,trainDataSet);
  1. 每一轮中进行每一项出现次数(频率)的统计,详见,具体统计的方式如下:
    resStr 表示结果集中的每一个项集,tempStr表示每一条训练集中的记录
    如果有resStr = [‘苹果’,‘香蕉’], tempStr = [‘苹果’,‘西瓜’,‘香蕉’],那么tempStr包含了resStr中的所有元素,就让resStr在map中的计数值+1(也就是出现次数(频率)加1
			//获取当前用户的收藏item集合,也就是获取训练集的每一条记录
			String tempStr = "";
			for (String item:itemArr) {
				tempStr += item;
			}
			//针对项集统计频率
			if (roundNb == 0) {
				for (String item:itemArr) {
					if (res.get(item) == null) {
						res.put(item, 1.0);
					} else {
						res.put(item, res.get(item) + 1.0);
					}	
				}
			} else {
				for (String resStr : res.keySet()) {
					//如果字符串长度不为roundNb+1就说明不是当前的那层项集
					if (resStr.length() != roundNb + 1) {
						continue;
					}
					//判断每一个训练集是否包含当前项集,如果包含就加1
					//ex: resStr = ['苹果','香蕉'],  tempStr = ['苹果','西瓜','香蕉'],那么tempStr包含了resStr中的所有元素,就让resStr在map中的计数值+1(也就是出现次数(频率)加1)
					boolean contains = true;
					for (char ch : resStr.toCharArray()) {
						//tempStr 表示训练集的一条记录,resStr表示项集
						if (tempStr.indexOf(ch) == -1) {
							contains = false;
							break;
						}
					}
					if (contains){
						res.put(resStr, res.get(resStr) + 1.0);
					}
				}
			}
  1. 根据最小支持度进行筛选,用到了数组长度initSize
		//筛选
		for (String str:res.keySet()) {
			if (res.get(str) < MIN_SUPPORT * initSize) {
				res.remove(str);
			}
		}
  1. 进行频繁项集和子项集的交集运算
    str 表示当前频繁项集;substr表示长度为1的子项集。按照字典排序从小到大进行字符串拼接
		Map<String,Double> newRes = new ConcurrentHashMap<String, Double>();
		for (String str:res.keySet()) {
			if (str.length() != roundNb + 1) {
				continue;
			}
			for (String substr:res.keySet()) {
				//每次获取一位,之后叠加
				if (substr.length() != 1) {
					continue;
				}
				String lastChar = str.charAt(str.length() - 1) + "";
				//判断大小,只允许字符串递增排列,如AC,AB,AD,CD
				if(substr.compareTo(lastChar) > 0) {
					newRes.put(str+substr, 0.0);
				}
			}
		}
  1. 最后判断是否不存在更深层次的频繁项集,如果不存在就返回结果集
		if (newRes.isEmpty()) {
			return res;
		} else {
			res.putAll(newRes);
			return trainData(initSize,roundNb+1,res,trainDataSet);
		}

项集推荐

主要参数:res-频繁项集结果集,usersCollection-当前用户喜好集合
主要原理:

  1. 将用户喜好集合进行字符串拼接成一个长条字符串,这里也可以用分隔符分割,方便后续的分割操作
  2. 获取包含当前用户喜好集合的父频繁项集(也就是size+1的频繁项集),之后去除重复项(用replace方法)将大于最小支持度的项集其加入到推荐列表中,并打印最小支持度
	/**
	 * 推荐
	 * @param res
	 * @param usersCollection
	 * @return
	 */
	private static List<String> recommend(Map<String, Double> res, String[] usersCollection) {
		// TODO Auto-generated method stub
		String key = "";
		List<String> list = new ArrayList<>();
		for (String str: usersCollection) {
			key += str;
		}
		double countNb = res.get(key);
		for (String str: res.keySet()) {
			if (str.length() != key.length() + 1) {
				continue;
			}
			boolean contains = true;
			for (char ch : key.toCharArray()) {
				if (str.indexOf(ch) == -1) {
					contains = false;
					break;
				}
			}
			if (contains){
				if (res.get(str) /countNb * 1.0 >= MIN_FAITH) {
					System.out.println(key + " -->"  + str + " faith = " +  res.get(str) / countNb * 1.0);
					list.add(str.replace(key, ""));
				}	
			}
		}
		return list;
	}

——————————————————结尾————————————————————————
最后在放一个最新的版本,支持项集和推荐项集的字典排序:

package apriori;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * apriori算法
 * @author zygswo
 *
 */
public class Apriori {
	/**
	 * 最小支持度
	 */
	private static final double MIN_SUPPORT = 0.2;
	
	/**
	 * 最小置信度
	 */
	private static final double MIN_CONFI = 0.5;

	/**
	 * 算法核心
	 * @param trainDataSet 训练集
	 * @param usersCollection 用户喜好
	 * @return
	 */
	private static List<String>
			getResult(Map<String,String[]> trainDataSet,String[] usersCollection) {
		//1.训练集训练
		Map<String,Double> res = new ConcurrentHashMap<String,Double>();
		res = trainData(res,trainDataSet);
		//2.推荐
		return recommend(res, usersCollection);
	}
	
	/**
	 * 推荐
	 * @param res
	 * @param usersCollection
	 * @return
	 */
	private static List<String> recommend(Map<String, Double> res, String[] usersCollection) {
		Arrays.sort(usersCollection);
		String key = "";
		List<String> list = new ArrayList<>();
		for (String str: usersCollection) {
			key += str + "、";
		}
		//推荐系统冷启动
		if(key == null || key.isEmpty()) {
			for (String str: res.keySet()) {
				String[] substrArr = str.split("、");
				if (substrArr.length != 1) {
					continue;
				}
				list.add(substrArr[0]);
			}
		} else {
			key = key.substring(0,key.length() -1);
			Double countNb = res.get(key);
			if (countNb == null) {
				return new ArrayList<>();
			}
			for (String str: res.keySet()) {
				String[] strArr = str.split("、");
				String[] keyArr = key.split("、");
				if (strArr.length != keyArr.length + 1) {
					continue;
				}
				boolean contains = true;
				String restItem = str; //推荐的对象
				for (String ch : keyArr) {
					if (str.indexOf(ch) == -1) {
						contains = false;
						break;
					} else {
						restItem = restItem.replace(ch + "、",""); //删除前缀
						restItem = restItem.replace("、" + ch,""); //删除后缀
					}
				}
				//如果包含用户收藏的餐馆就加入
				if (contains){
					if (res.get(str) /countNb * 1.0 >= MIN_CONFI) {
						System.out.println(key + " -->"  + str + " confidence = " +  res.get(str) / countNb * 1.0);
						list.add(restItem);
					}	
				}
			}
		}		
		Collections.sort(list);//排序
		return list;
	}

	/**
	 * 训练训练集
	 * @param res
	 * @param trainDataSet
	 * @return
	 */
	private static Map<String, Double> trainData(
			Map<String, Double> res,
			Map<String, String[]> trainDataSet) {
		res.putAll(trainData(trainDataSet.size(),0, res, trainDataSet));
		return res;
	}

	/**
	 * 训练训练集
	 * @param initSize 初始数组长度
	 * @param roundNb 轮数
	 * @param res 结果map
	 * @param trainDataSet 训练数据集
	 * @return
	 */
	private static Map<String, Double> trainData(
			int initSize,
			int roundNb,
			Map<String,Double> res,
			Map<String,String[]> trainDataSet
	) {
		//统计
		for (String[] itemArr : trainDataSet.values()) {
			//排除特殊情况
			if (itemArr == null || itemArr.length == 0) {
				continue;
			}
			//获取当前用户的收藏item集合,也就是获取项集
			String tempStr = "";
			for (String item:itemArr) {
				tempStr += item + "、";
			}
			tempStr = tempStr.substring(0,tempStr.length()-1);
			//针对项集统计频率
			if (roundNb == 0) {
				//初始化结果map
				for (String item:itemArr) {
					if (res.get(item) == null) {
						res.put(item, 1.0);
					} else {
						res.put(item, res.get(item) + 1.0);
					}	
				}
			} else {
				for (String resStr : res.keySet()) {
					String[] substrArr = resStr.split("、");
					//如果字符串长度不为roundNb+1就说明不是当前的那层项集
					if (substrArr.length != roundNb + 1) {
						continue;
					}
					//判断tempstr(用户的频繁项集)是否包含resStr字串(当层项集)
					boolean contains = true;
					for (String ch : substrArr) {
						if (tempStr.indexOf(ch) == -1) {
							contains = false;
							break;
						}
					}
					//如果包含就计数
					if (contains){
						res.put(resStr, res.get(resStr) + 1.0);
					}
				}
			}
		}
		//筛选
		for (String str:res.keySet()) {
			if (res.get(str) < MIN_SUPPORT * initSize) {
				res.remove(str);
			}
		}
		//新增
		Map<String,Double> newRes = new ConcurrentHashMap<String, Double>();
		for (String str:res.keySet()) {
			String[] strArr = str.split("、");
			if (strArr.length != roundNb + 1) {
				continue;
			}
			for (String substr:res.keySet()) {
				//每次获取一位,之后叠加,如A+B = AB, A+C = AC
				String[] substrArr = substr.split("、");
				if (substrArr.length != 1) {
					continue;
				}
				if(!str.contains(substrArr[0]) 
						&& str.compareTo(substrArr[0]) > 0) {
					newRes.put(substrArr[0]+ "、" + str, 0.0);
				}
			}
		}
		if (newRes.isEmpty()) {
			return res;
		} else {
			res.putAll(newRes);
			return trainData(initSize,roundNb+1,res,trainDataSet);
		}
	}

	public static void main(String[] args) {
		Map<String,String[]> trainDataSet = new ConcurrentHashMap<>();
//		trainDataSet.put("userB", new String[]{"milk","coffee","apple"});
//		trainDataSet.put("userC", new String[]{"milk","bread"});
//		trainDataSet.put("userD", new String[]{"milk","coffee","apple","tomato"});
//		trainDataSet.put("userE", new String[]{"milk","coffee","tomato"});
//		trainDataSet.put("userF", new String[]{"milk","coffee","apple","bread"});
//		trainDataSet.put("userG", new String[]{"milk","coffee","tomato"});
//		System.out.println("推荐结果为" + getResult(trainDataSet,new String[]{"milk","coffee"}));
		trainDataSet.put("userB", new String[]{"A","B","C"});
		trainDataSet.put("userC", new String[]{"A","D"});
		trainDataSet.put("userD", new String[]{"A","B","C","E"});
		trainDataSet.put("userE", new String[]{"A","B","E"});
		trainDataSet.put("userF", new String[]{"A","B","C","E"});
		trainDataSet.put("userG", new String[]{"A","B","E"});
		trainDataSet.put("userH", new String[]{"A","B","C","E","F"});
		trainDataSet.put("userI", new String[]{"A","B","E","F"});
		trainDataSet.put("userJ", new String[]{"A","D","F"});
		System.out.println("推荐结果为" + getResult(trainDataSet,new String[]{"E","A"}));
	}
}

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zygswo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值