项目代码
从数据集中获取有趣信息的方法,最常用的两种分别是频繁项集与关联规则。
Apriori算法是发现数据集中的频繁项集、及数据之间的关联规则。
看一个例子解释几个概念:
频繁项集是指那些经常出现在一起的物品,例如上图的{葡萄酒、尿布、豆奶},从上面的数据集中也可以找到尿布->葡萄酒的关联规则,这意味着有人买了尿布,那很有可能他也会购买葡萄酒。那如何定义和表示频繁项集和关联规则呢?这里引入支持度和可信度(置信度)。
支持度:一个项集的支持度被定义为数据集中包含该项集的记录所占的比例,上图中,豆奶的支持度为4/5,(豆奶、尿布)为3/5。支持度是针对项集来说的,因此可以定义一个最小支持度,只保留最小支持度的项集。
可信度(置信度):针对如{尿布}->{葡萄酒}这样的关联规则来定义的。计算为 支持度{尿布,葡萄酒}/支持度{尿布},其中{尿布,葡萄酒}的支持度为3/5,{尿布}的支持度为4/5,所以“尿布->葡萄酒”的可行度为3/4=0.75,这意味着尿布的记录中,我们的规则有75%都适用。
有了可以量化的计算方式,我们却还不能立刻运算,这是因为如果我们直接运算所有的数据,运算量极其的大,很难实现,这里说明一下,假设我们只有 4 种商品:商品0,商品1,商品 2,商品3. 那么如何得可能被一起购买的商品的组合?
上图显示了物品之间所有可能的组合,从上往下一个集合是 Ø,表示不包含任何物品的空集,物品集合之间的连线表明两个或者更多集合可以组合形成一个更大的集合。我们的目标是找到经常在一起购买的物品集合。这里使用集合的支持度来度量其出现的频率。一个集合出现的支持度是指有多少比例的交易记录包含该集合。例如,对于上图,要计算 0,3 的支持度,直接的想法是遍历每条记录,统计包含有 0 和 3 的记录的数量,使用该数量除以总记录数,就可以得到支持度。而这只是针对单个集合 0,3. 要获得每种可能集合的支持度就需要多次重复上述过程。对于上图,虽然仅有4中物品,也需要遍历数据15次。随着物品数目的增加,遍历次数会急剧增加,对于包含 N 种物品的数据集共有 2^N−1 种项集组合。为了降低计算时间,研究人员发现了 Apriori 原理,可以帮我们减少感兴趣的频繁项集的数目。
Apriori 的原理:如果某个项集是频繁项集,那么它所有的子集也是频繁的。即如果 {0,1} 是频繁的,那么 {0}, {1} 也一定是频繁的。
这个原理直观上没有什么用,但是反过来看就有用了,也就是说如果一个项集是非频繁的,那么它的所有超集也是非频繁的。如下图所示:
代码实现:
def create_C1(data):
"""创建所有候选项集的集合"""
C1 = []
for tran in data:
for item in tran:
if [item] not in C1:
C1.append([item])
C1.sort()
return map(frozenset,C1)
# Test
# 输入数据 data = [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]];create_C1(data)
# 输出:frozenset({1}) frozenset({2}) frozenset({3}) frozenset({4}) frozenset({5})
def scan_D(data,candidate_set,min_support=0.5):
'''
:param data: 包含候选集合的数据集
:param candidate_set: 候选集合
:param min_support: 最小支持度
:return: 满足最小支持的候选集,每个候选集的支持度
'''
C1 = [i for i in candidate_set]
cs_cnt = {}
for tid in data: # 循环数据集中的每条交易记录tid
for cs in C1: # 循环每个候选集cs
if cs.issubset(tid): # 检查cs是否是tid的子集
if cs not in cs_cnt:
cs_cnt[cs]=1
else:cs_cnt[cs]+=1 # 如果是,则cs的计数增加
num_items = len(data)
ret_list = []
support_data = {}
for k in cs_cnt: # 对每个候选集
support = cs_cnt[k]/num_items # 计算其支持度
if support>=min_support: # 如果其支持度不小于最小值,则保留该候选集
ret_list.append(k)
support_data[k] = support
return ret_list,support_data
# Test
# 输入数据 data = [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
# 输入数据 candidate_set = [frozenset({1}) frozenset({2}) frozenset({3}) frozenset({4}) frozenset({5})]
# 输入数据 min_support = 0.5
# 输出数据:
# ret_list:[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})]
# support_data:{frozenset({1}): 0.5, frozenset({3}): 0.75, frozenset({4}): 0.25, frozenset({2}): 0.75, frozenset({5}): 0.75}
from utils import create_C1, scan_D
def apriori_gen(Lk, k):
ret_list = []
len_LK = len(Lk)
for i in range(len_LK):
for j in range(i + 1, len_LK):
L1 = list(Lk[i])[:k - 2].sort()
L2 = list(Lk[j])[:k - 2].sort()
if L1 == L2:
ret_list.append(Lk[i] | Lk[j])
return ret_list
def apriori(data, min_support=0.5):
C1 = create_C1(data)
L1, support_data = scan_D(data, C1, min_support)
L = [L1]
k = 2
while len(L[k - 2]) > 0: # 集合中项的个数大于0时
Ck = apriori_gen(L[k - 2], k) # 构建一个k个项构成的候选集列表
Lk, supk = scan_D(data, Ck, min_support) # 确认每个候选集都是频繁的
support_data.update(supk)
L.append(Lk) # 保留频繁项
k += 1 # 为构建k+1项组成的候选集列表做准备
return L, support_data
# Test
# 输入数据 data = [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
# 输入数据 min_support = 0.5
# 输出:[[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({1, 3}),
# frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5})], [frozenset({2, 3, 5})], []]
要找到关联规则,先从一个频繁集开始,我们想知道对于频繁项集中的元素能否获取其它内容,即某个元素或者某个集合可能会推导出另一个元素。从表1 可以得到,如果有一个频繁项集 {豆奶,莴苣},那么就可能有一条关联规则 “豆奶 --> 莴苣”,意味着如果有人购买了豆奶,那么在统计上他会购买莴苣的概率较大。但是这一条反过来并不一定成立。
从一个频繁项集可以产生多少条关联规则呢?可以基于该频繁项集生成一个可能的规则列表,然后测试每条规则的可信度,如果可信度不满足最小值要求,则去掉该规则。类似于前面讨论的频繁项集生成,一个频繁项集可以产生许多可能的关联规则,如果能在计算规则可信度之前就减少规则的数目,就会很好的提高计算效率。
这里有一条规律就是:如果某条规则并不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求,例如下图的解释:
所以,可以利用上图所示的性质来减少测试的规则数目。
代码实现:
from apriori import apriori_gen
def calc_conf(freq_set, H, support_data, brl, min_conf=0.7):
"""
计算规则可信度及找到满足最小可信度要求的规则
:param freq_set: 频繁项集
:param H: apriori_gen生成的频繁项集
:param support_data: 频繁项集的支持度
:param brl: big_rule_list
:param min_conf: 最小可信度阈值
:return: 满足最小可信度要求规则的列表
"""
prunedH = [] # 初始化空规则列表
for conseq in H:
conf = support_data[freq_set] / support_data[freq_set - conseq] # 计算可信度
if conf >= min_conf:
print(freq_set - conseq, ' --> ', conseq, ' conf: ', conf)
brl.append((freq_set - conseq, conseq, conf))
prunedH.append(conseq)
return prunedH
def rules_from_conseq(freq_set, H, support_data, brl, min_conf=0.7):
"""
从最初的的项集中生成更多的关联规则
:param freq_set: 频繁项集
:param H: 出现在规则右部的元素列表
:param support_data: 频繁项集的支持度
:param brl: big_rule_list
:param min_conf: 最小可信度阈值
:return:
"""
m = len(H[0])
if len(freq_set) > (m + 1):
Hmp1 = apriori_gen(H, m + 1)
Hmp1 = calc_conf(freq_set, Hmp1, support_data, brl, min_conf)
if len(Hmp1) > 1:
rules_from_conseq(freq_set, Hmp1, support_data, brl, min_conf)
def gen_rules(L, support_data, min_conf=0.7):
"""
:param L: 频繁项集列表
:param support_data: 包含频繁项集支持数据的列表
:param min_conf: 最小可信度阈值
:return: 可信度的规则列表
"""
big_rule_list = []
for i in range(1, len(L)): # 无法从单个元素项集中构建关联规则,因此跳过第一个单元素项集
for freq_set in L[i]:
H1 = [frozenset([item]) for item in freq_set] # 例子{0,1,2} ---> [{0},{1},{2}]
if i > 1: # 如果项集中的元素个数大于2个,作合并处理,通过rules_from_conseq方法完成
rules_from_conseq(freq_set, H1, support_data, big_rule_list, min_conf)
else: # 如果项集中的元素个数只有2个,使用calc_conf方法,计算可信度
calc_conf(freq_set, H1, support_data, big_rule_list, min_conf)
return big_rule_list
# data = read_data()
# ret, support = apriori(data,0.5)
# rules = gen_rules(ret,support,0.7)
# min_conf=0.7 时的输出结果
# frozenset({1}) --> frozenset({3}) conf: 1.0
# frozenset({5}) --> frozenset({2}) conf: 1.0
# frozenset({2}) --> frozenset({5}) conf: 1.0
# frozenset({5}) --> frozenset({2, 3}) conf: 2.0
# frozenset({3}) --> frozenset({2, 5}) conf: 2.0
# frozenset({2}) --> frozenset({3, 5}) conf: 2.0
# [(frozenset({1}), frozenset({3}), 1.0), (frozenset({5}), frozenset({2}), 1.0),
# (frozenset({2}), frozenset({5}), 1.0), (frozenset({5}), frozenset({2, 3}), 2.0),
# (frozenset({3}), frozenset({2, 5}), 2.0), (frozenset({2}), frozenset({3, 5}), 2.0)]