基于Apriori关联规则的影戏推荐系统
1、效果图
demo点我跳转
2、算法原理
Apriori算法是一种用于挖掘关联规则的频繁项集算法,它采用逐层搜索的迭代方法来发现数据库中项集之间的关系并形成规则。
其核心思想是利用Apriori性子来压缩搜索空间,即假如一个项集好坏频繁的,那么它的全部父集也好坏频繁的,反之亦然。
Apriori算法的过程包罗连接和剪枝两个主要步骤。在连接步骤中,算法会生成候选项集,这些候选项集是由前一次迭代发现的频繁项集通过连接操作产生的。在剪枝步骤中,算法会去除那些支持度低于用户定义的最小支持度的项集。
根据生成的关联规则实现给用户推送精准的物品推荐。
3、案例说明
3.1、评分表
现有影戏评分表如下:
用户评分的影戏1肖申克的救赎、霸王别姬、茶馆2霸王别姬、阿甘正传3肖申克的救赎、霸王别姬、阿甘正传4肖申克的救赎、美丽人生5霸王别姬、美丽人生6霸王别姬、美丽人生7肖申克的救赎、美丽人生8肖申克的救赎、霸王别姬、美丽人生、茶馆9肖申克的救赎、霸王别姬、美丽人生根据表中的信息,评分的影戏有['肖申克的救赎','霸王别姬','美丽人生','阿甘正传','茶馆']五部,总共有9个用户举行评分。
注意: 用户评分的影戏组合越多,那么关联评分的大概性就越高。
这就需要根据经验,人为设置一个最小的评分组合出现次数。
由于上面的评分用户数量太少,所以就设置为2,就是只要用户评过分的影戏出现2部以上,就以为他有关联其他影戏评分的大概性存在。
因此需要统计用户评过分的每部影戏出现的次数。
由于有5部影戏,因此一个用户评分组合包含的影戏数量的大概性为 1-5,需要逐级分析。
3.2、只评分1部影戏的组合
这里把用户只评分1部影戏作为一个组合,是由于2部影戏的评分组合是基于1部影戏评分的组合产生的。
统计影戏评分中的组合,可以得到下表:
评分的影戏组合出现次数肖申克的救赎6霸王别姬7美丽人生6阿甘正传2茶馆2从表中可以看出,5部影戏评分的用户数量,都超过了设置的2次,就把这些影戏评分组合称作频繁影戏评分组合;因此,上表中的全部影戏组合都是频繁评分的;就会得到下面这个频繁影戏评分组合表:
评分的影戏组合出现次数肖申克的救赎6霸王别姬7美丽人生6阿甘正传2茶馆2虽然上述两个表看起来没有任何变化,但是实际上举行了一个对比:出现次数与设置的最小出现次数2举行了对比,只是都及格了。
3.3、只评分2部影戏的组合
接着对用户给2部影戏评分形成的组合举行统计(顺序不同不重复计数),可以得到下表:
评分的影戏组合出现次数肖申克的救赎、霸王别姬4肖申克的救赎、美丽人生4肖申克的救赎、阿甘正传1肖申克的救赎、茶馆2霸王别姬、美丽人生4霸王别姬、阿甘正传2霸王别姬、茶馆2美丽人生、阿甘正传0美丽人生、茶馆1阿甘正传、茶馆0根据第一次的操作顺序,对每个评分组合与最小出现的次数2举行比较。保留出现次数不小于2的组合,得到下表:
评分的影戏组合出现次数肖申克的救赎、霸王别姬4肖申克的救赎、美丽人生4肖申克的救赎、茶馆2霸王别姬、美丽人生4霸王别姬、阿甘正传2霸王别姬、茶馆2上面两个表的情况就清楚的反映了,一些评分组合的出现次数太少,意味着关联评分的大概性已经很小了,保留下来的就是有关联评分大概性的。
3.4、只评分3部影戏的组合
注意,假如用户只评分2部影戏是关联性已经很小的组合时,那么只评分3部影戏的组合大概性就会更小。因此不需要统计包含该2部影戏的评分3部影戏的组合。
同样的,对3部影戏评分组合举行统计:
评分的影戏组合出现次数肖申克的救赎、霸王别姬、美丽人生2肖申克的救赎、霸王别姬、茶馆2肖申克的救赎、美丽人生、茶馆0霸王别姬、美丽人生、阿甘正传0霸王别姬、美丽人生、茶馆1霸王别姬、阿甘正传、茶馆0会发现有些组合没有出如今表中,比如{肖申克的救赎、霸王别姬、阿甘正传},原因就是刚刚说明的,由于{肖申克的救赎、阿甘正传}的出现次数小于2,因此就将评分包含{肖申克的救赎、阿甘正传}的3部影戏组合排撤消了;
然后,继续统计满足最小出现次数不小于2的组合,得到下表:
评分的影戏组合出现次数肖申克的救赎、霸王别姬、美丽人生2肖申克的救赎、霸王别姬、茶馆2发现,还有2个评分组合满足我们的最小计数。
3.5、只评分4部影戏的组合
根据上面的原则,4个产品组合的表格如下:
评分的影戏组合出现次数肖申克的救赎、霸王别姬、美丽人生、茶馆1发现组合的出现次数小于2,因此排除,就会得到下表:
评分的影戏组合出现次数无无意味着对影戏的评分组合分类已经到头了,已经得到了不同个数的评分组合出现的次数。但是这还不敷,由于需要进一步考虑如何根据这些组合推荐影戏。
注意: 既然已经得到了影戏评分组合M,那么根据用户A已经评分的影戏O,推荐有该影戏的评分组合M里其他没有评过分的影戏就可以了。
原因如下: 假如想让用户在对【肖申克的救赎】这部影戏评分的情况下,完成{肖申克的救赎、霸王别姬}的评分组合;那么评分【霸王别姬】的大概性是创建在评分组合{肖申克的救赎}出现6次的基础上,而不是{肖申克的救赎、霸王别姬}出现4次的基础上。
因此,需要对每种关联评分的大概性举行计算,直观地观察每种关联评分的大概性的大小。
3.6、找出强关联评分的规则
什么是规则?规则就是我评分了一部影戏的情况下,再去评分另一部影戏;注意,这里的评分是有顺序的;先【肖申克的救赎】后【霸王别姬】和先【霸王别姬】后【肖申克的救赎】是不同的规则。
我们这里以{肖申克的救赎、霸王别姬、茶馆}举例,该组合总共出现2次,大概出现的关联评分情况如下:
已评分影戏组合已评分影戏组合次数推荐影戏期望的影戏组合大概性肖申克的救赎、霸王别姬4茶馆肖申克的救赎、霸王别姬、茶馆(2次)2/4=0.5肖申克的救赎、茶馆2霸王别姬肖申克的救赎、霸王别姬、茶馆(2次)2/2=1霸王别姬、茶馆2肖申克的救赎肖申克的救赎、霸王别姬、茶馆(2次)2/2=1肖申克的救赎6霸王别姬、茶馆肖申克的救赎、霸王别姬、茶馆(2次)2/6=0.33霸王别姬7肖申克的救赎、茶馆肖申克的救赎、霸王别姬、茶馆(2次)2/7=0.29茶馆2肖申克的救赎、霸王别姬肖申克的救赎、霸王别姬、茶馆(2次)2/2=1上表中,最终盼望用户达成的评分组合是{肖申克的救赎、霸王别姬、茶馆},因此根据用户已评分的组合情况,对应推荐未评分的影戏组合,大概性计算的方式就是最终期望影戏组合出现的次数除以已评分影戏组合出现的次数,也可以理解为最终期望组合占用户已评分组合的比例。
可以看到,大概性有高有低;实际分析中,不大概出现100%评分的情况,因此就要根据经验或者要求计划一个最低的大概性,比如至少大概性大于70%,我们才有意愿去把影戏组合推荐给用户,这就会得到最终的关联规则表:
已评分影戏组合已评分影戏组合次数推荐影戏期望的影戏组合大概性肖申克的救赎、茶馆2霸王别姬肖申克的救赎、霸王别姬、茶馆(2次)2/2=1霸王别姬、茶馆2肖申克的救赎肖申克的救赎、霸王别姬、茶馆(2次)2/2=1茶馆2肖申克的救赎、霸王别姬肖申克的救赎、霸王别姬、茶馆(2次)2/2=1上表的意思就是,当发现有用户满足上述已评分影戏组合,就把该影戏组合推荐给用户,用户评分的大概性是100%。
比如用户对【茶馆】举行了评分,那么就推荐【肖申克的救赎、霸王别姬】,用户对它们评分的大概性为100%。
4、Apriori 算法
4.1、名词表明
现有一数据集合有 N 条记载,关注其中元素组合 X、Y:
规则:根据组合 X 推出组合 Y
支持度:
在 N 条记载中,X 和 Y 被同时满足的记载数有 n 个,那么支持度就是 n/N;
支持度计数就是 n;
在举行分析时,需要人为设置最小支持度为 m/N ,其中 m 就是最小支持度计数;
不满足最小支持度的元素组合就会被剔除;- 在10个用户中,X 和 Y 同时被评分的用户数至少有 3 个,那么支持度就是 3/10,支持度计数就是 3;
- 进行分析时,设置最小支持度为 m/N,那最小支持度计数就是 m,不满足的产品组合就会被剔除;
复制代码
置信度:
在满足组合 X 的情况下,满足组合 Y 的概率;
也可以说是同时满足 X 和 Y 的记载数在只满足 X 的记载数的比率;
项集:
一个组合中包含几个元素,就叫做几项集。如:组合 X 为 {x1} 就叫做1项集;组合 Y 为 {Y1、Y2} 就叫做2项集;
频繁项集:
某个评分组合出现的频率大于或等于设置的支持度,那么这个评分组合就是频繁项集;
频繁项集的非空子集必须是频繁项集;
频繁项集中含有 i 个元素,就叫做频繁 i 项集。
5、算法流程
6、主体代码
- # -*- coding: utf-8 -*-
- """
- @contact: 微信 1257309054
- @file: apriori_recommend.py
- @time: 2024/3/31 0:06
- @author: LDC
- 使用Apriori关联规则实现一个频繁项集推荐算法
- """
- import math
- import time
- def get_item_set(data):
- '''
- 获取项的字典
- :param data: 数据集
- :return: 项的字典
- '''
- item_set = set()
- for d in data:
- item_set = item_set | set(d)
- return item_set
- def apriori(item_set, data, min_support=0.20):
- '''
- 获取频繁项集
- :param item_set: 项的字典
- :param data: 数据集
- :param min_support: 最小支持度,默认为0.20
- :return: None
- '''
- # 初始化存储非频繁项集的列表
- infrequent_list = []
- # 初始化存储频繁项集的列表
- frequent_list = []
- # 初始化存储频繁项集的支持度的列表
- frequent_support_list = []
- # 遍历获取 n-项集
- for n in range(1, len(item_set) + 1):
- c = []
- supports = []
- if len(frequent_list) == 0:
- # 计算 1-项集
- for item in item_set:
- items = {item}
- support = calc_support(data, items)
- # 如果支持度大于等于最小支持度就为频繁项集
- if support >= min_support:
- c.append(items)
- supports.append(support)
- else:
- infrequent_list.append(items)
- else:
- # 计算 n-项集,n > 1
- for last_items in frequent_list[-1]:
- for item in item_set:
- if item > list(last_items)[-1]:
- items = last_items.copy()
- items.add(item)
- # 如果items的子集没有非频繁项集才计算支持度
- if is_infrequent(infrequent_list, items) is False:
- support = calc_support(data, items)
- # 如果支持度大于等于最小支持度就为频繁项集
- if support >= min_support:
- c.append(items)
- supports.append(support)
- else:
- infrequent_list.append(items)
- frequent_list.append(c)
- frequent_support_list.append(supports)
- print(f"{n}-项集: {c} , 支持度分别为: {supports}")
- return infrequent_list, frequent_list, frequent_support_list
- def is_infrequent(infrequent_list, items):
- '''
- 判断是否属于非频繁项集的超集
- :param infrequent_list: 非频繁项集列表
- :param items: 项集
- :return: 是否属于非频繁项集的超集
- '''
- for infrequent in infrequent_list:
- if infrequent.issubset(items):
- return True
- return False
- def calc_support(data, items):
- '''
- 计算 support
- :param data: 数据集
- :param items: 项集
- :return: 计算好的支持度
- '''
- cnt = 0
- for d in data:
- if items.issubset(d):
- cnt += 1
- return round(cnt / len(data), 2)
- def generate_rules(frequent_list, data, min_confidence=0.60):
- '''
- 根据频繁项集和最小置信度生成规则
- :param frequent_list: 存储频繁项集的列表
- :param data: 数据集
- :param min_confidence: 最小置信度
- :return: 规则
- '''
- rule_key_set = set()
- rules = []
- for frequent in frequent_list:
- for items in frequent:
- if len(items) > 1:
- for n in range(1, math.ceil(len(items) / 2) + 1):
- front_set_list = get_all_combine(list(items), n)
- for front_set in front_set_list:
- back_set = items - front_set
- confidence = calc_confidence(front_set, items, data)
- if confidence >= min_confidence:
- rule = (front_set, back_set, confidence)
- key = f'{front_set} ==> {back_set} , confidence: {confidence}'
- if key not in rule_key_set:
- rule_key_set.add(key)
- rules.append(rule)
- print(f"规则{len(rules)}: {key}")
- return rules
- def get_all_combine(data_set, length):
- '''
- 在指定数据集种获取指定长度的所有组合
- :param data_set: 数据集
- :param length: 指定的长度
- :return: 所有符合约束的组合
- '''
- def dfs(cur_index, cur_arr):
- if cur_index < len(data_set):
- cur_arr.append(data_set[cur_index])
- if len(cur_arr) == length:
- combine_list.append(set(cur_arr))
- else:
- for index in range(cur_index + 1, len(data_set)):
- dfs(index, cur_arr.copy())
- combine_list = []
- for start_index in range(len(data_set)):
- dfs(start_index, [])
- return combine_list
- def calc_confidence(front_set, total_set, data):
- '''
- 计算规则 X==>Y 的置信度
- :param front_set: X
- :param total_set: X ∪ Y
- :param data: 数据集
- :return: 返回规则 X==>Y 的置信度
- '''
- front_cnt = 0
- total_cnt = 0
- for d in data:
- if front_set.issubset(d):
- front_cnt += 1
- if total_set.issubset(d):
- total_cnt += 1
- return round(total_cnt / front_cnt, 2)
- if __name__ == '__main__':
- # recommend_by_apriori(1)
- # 记录开始时间
- s = time.time()
- # 数据集
- data = [['肖申克的救赎', '霸王别姬', '茶馆'],
- ['霸王别姬', '阿甘正传'],
- ['肖申克的救赎', '霸王别姬', '阿甘正传'],
- ['肖申克的救赎', '美丽人生'],
- ['霸王别姬', '美丽人生'],
- ['霸王别姬', '美丽人生'],
- ['肖申克的救赎', '美丽人生'],
- ['肖申克的救赎', '霸王别姬', '美丽人生', '茶馆'],
- ['肖申克的救赎', '霸王别姬', '美丽人生'],
- ]
- # 获取项的字典
- item_set = get_item_set(data)
- print("项的字典:", item_set)
- # 根据 Apriori算法 获取 n-频繁项集
- infrequent_list, frequent_list, frequent_support_list = apriori(item_set, data, min_support=0.20)
- # 生成规则
- rule_set = generate_rules(frequent_list, data, min_confidence=0.60)
- print('rule_set', rule_set)
- # 推荐
- user_data = {'茶馆'} # 用户列表
- recommend_id = [] # 推荐列表
- for rule in rule_set:
- # 置信度要大于0.7
- if rule[-1] < 0.7:
- continue
- # 用户数据与规则有交集,则添加到推荐列表
- if user_data & rule[0]:
- recommend_id += list(rule[1])
- recommend_id = list(set(recommend_id))
- print('推荐recommend_id', recommend_id)
- # 输出总用时
- print("总用时:", (time.time() - s), "s")
复制代码 输出:- 项的字典: {'霸王别姬', '肖申克的救赎', '阿甘正传', '茶馆', '美丽人生'}
- 1-项集: [{'霸王别姬'}, {'肖申克的救赎'}, {'阿甘正传'}, {'茶馆'}, {'美丽人生'}] , 支持度分别为: [0.78, 0.67, 0.22, 0.22, 0.67]
- 2-项集: [{'霸王别姬', '肖申克的救赎'}, {'茶馆', '肖申克的救赎'}, {'霸王别姬', '阿甘正传'}, {'霸王别姬', '茶馆'}, {'霸王别姬', '美丽人生'}, {'肖申克的救赎', '美丽人生'}] , 支持度分别为: [0.44, 0.22, 0.22, 0.22, 0.44, 0.44]
- 3-项集: [{'霸王别姬', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'茶馆', '肖申克的救赎'}, {'霸王别姬', '阿甘正传'}, {'霸王别姬', '茶馆'}, {'霸王别姬', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'肖申克的救赎', '美丽人生'}] , 支持度分别为: [0.44, 0.22, 0.22, 0.22, 0.22, 0.22, 0.44, 0.22, 0.22, 0.44]
- 4-项集: [{'霸王别姬', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'茶馆', '肖申克的救赎'}, {'霸王别姬', '阿甘正传'}, {'霸王别姬', '茶馆'}, {'霸王别姬', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'肖申克的救赎', '美丽人生'}] , 支持度分别为: [0.44, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.44, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.44]
- 5-项集: [{'霸王别姬', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'霸王别姬', '茶馆', '肖申克的救赎'}, {'茶馆', '肖申克的救赎'}, {'霸王别姬', '阿甘正传'}, {'霸王别姬', '茶馆'}, {'霸王别姬', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'霸王别姬', '肖申克的救赎', '美丽人生'}, {'肖申克的救赎', '美丽人生'}] , 支持度分别为: [0.44, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.44, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.22, 0.44]
- 规则1: {'肖申克的救赎'} ==> {'霸王别姬'} , confidence: 0.67
- 规则2: {'茶馆'} ==> {'肖申克的救赎'} , confidence: 1.0
- 规则3: {'阿甘正传'} ==> {'霸王别姬'} , confidence: 1.0
- 规则4: {'茶馆'} ==> {'霸王别姬'} , confidence: 1.0
- 规则5: {'美丽人生'} ==> {'霸王别姬'} , confidence: 0.67
- 规则6: {'肖申克的救赎'} ==> {'美丽人生'} , confidence: 0.67
- 规则7: {'美丽人生'} ==> {'肖申克的救赎'} , confidence: 0.67
- 规则8: {'茶馆'} ==> {'霸王别姬', '肖申克的救赎'} , confidence: 1.0
- 规则9: {'霸王别姬', '茶馆'} ==> {'肖申克的救赎'} , confidence: 1.0
- 规则10: {'茶馆', '肖申克的救赎'} ==> {'霸王别姬'} , confidence: 1.0
- rule_set [({'肖申克的救赎'}, {'霸王别姬'}, 0.67), ({'茶馆'}, {'肖申克的救赎'}, 1.0), ({'阿甘正传'}, {'霸王别姬'}, 1.0), ({'茶馆'}, {'霸王别姬'}, 1.0), ({'美丽人生'}, {'霸王别姬'}, 0.67), ({'肖申克的救赎'}, {'美丽人生'}, 0.67), ({'美丽人生'}, {'肖申克的救赎'}, 0.67), ({'茶馆'}, {'霸王别姬', '肖申克的救赎'}, 1.0), ({'霸王别姬', '茶馆'}, {'肖申克的救赎'}, 1.0), ({'茶馆', '肖申克的救赎'}, {'霸王别姬'}, 1.0)]
- 推荐recommend_id ['霸王别姬', '肖申克的救赎']
- 总用时: 0.004998922348022461 s
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |