Apriori算法
Apriori算法是常用的用于挖掘出数据关联规则的算法,它用来找出数据值中频繁出现的数据集合,有助于我们做一些决策。比如在常见的超市购物数据集,或者电商的网购数据集中,如果我们找到了频繁出现的数据集,那么对于超市,我们可以优化产品的位置摆放,对于电商,我们可以优化商品所在的仓库位置,达到节约成本,增加经济效益的目的。
(1) 频繁项集的评估标准
什么样的数据才是频繁项集呢?也许你会说,这还不简单,肉眼一扫,一起出现次数多的数据集就是频繁项集吗!的确,这也没有说错,但是有两个问题,第一是当数据量非常大的时候,我们没法直接肉眼发现频繁项集,这催生了关联规则挖掘的算法,比如Apriori, PrefixSpan, CBA。第二是我们缺乏一个频繁项集的标准。比如10条记录,里面A和B同时出现了三次,那么我们能不能说A和B一起构成频繁项集呢?因此我们需要一个评估频繁项集的标准。
常用的频繁项集的评估标准有支持度,置信度和提升度三个。
a. 支持度就是几个关联的数据在数据集中出现的次数占总数据集的比重。或者说几个数据关联出现的概率。如果我们有两个想分析关联性的数据X和Y,则对应的支持度为:
S
u
p
p
o
r
t
(
X
,
Y
)
=
P
(
X
Y
)
=
n
u
m
b
e
r
(
X
Y
)
n
u
m
(
A
l
l
S
a
m
p
l
e
s
)
Support(X,Y) = P(XY) = \frac{number(XY)}{num(All Samples)}
Support(X,Y)=P(XY)=num(AllSamples)number(XY)
以此类推,如果我们有三个想分析关联性的数据X,Y和Z,则对应的支持度为:
S
u
p
p
o
r
t
(
X
,
Y
,
Z
)
=
P
(
X
Y
Z
)
=
n
u
m
b
e
r
(
X
Y
Z
)
n
u
m
(
A
l
l
S
a
m
p
l
e
s
)
Support(X,Y,Z) = P(XYZ) = \frac{number(XYZ)}{num(All Samples)}
Support(X,Y,Z)=P(XYZ)=num(AllSamples)number(XYZ)
一般来说,支持度高的数据不一定构成频繁项集,但是支持度太低的数据肯定不构成频繁项集。
b. 置信度体现了一个数据出现后,另一个数据出现的概率,或者说数据的条件概率。如果我们有两个想分析关联性的数据X和Y,X对Y的置信度为:
C
o
n
f
i
d
e
n
c
e
(
X
⇐
Y
)
=
P
(
X
∣
Y
)
=
P
(
X
Y
)
/
P
(
Y
)
Confidence(X \Leftarrow Y) = P(X|Y)=P(XY)/P(Y)
Confidence(X⇐Y)=P(X∣Y)=P(XY)/P(Y)
也可以以此类推到多个数据的关联置信度,比如对于三个数据X,Y,Z,则X对于Y和Z的置信度为:
C
o
n
f
i
d
e
n
c
e
(
X
⇐
Y
Z
)
=
P
(
X
∣
Y
Z
)
=
P
(
X
Y
Z
)
/
P
(
Y
Z
)
Confidence(X \Leftarrow YZ) = P(X|YZ)=P(XYZ)/P(YZ)
Confidence(X⇐YZ)=P(X∣YZ)=P(XYZ)/P(YZ)
举个例子,在购物数据中,纸巾对应鸡爪的置信度为40%,支持度为1%。则意味着在购物数据中,总共有1%的用户既买鸡爪又买纸巾;同时买鸡爪的用户中有40%的用户购买纸巾。
c. 提升度表示含有Y的条件下,同时含有X的概率,与X总体发生的概率之比,即:
L
i
f
t
(
X
⇐
Y
)
=
P
(
X
∣
Y
)
/
P
(
X
)
=
C
o
n
f
i
d
e
n
c
e
(
X
⇐
Y
)
/
P
(
X
)
Lift(X \Leftarrow Y) = P(X|Y)/P(X) = Confidence(X \Leftarrow Y) / P(X)
Lift(X⇐Y)=P(X∣Y)/P(X)=Confidence(X⇐Y)/P(X)
提升度体先了X和Y之间的关联关系, 提升度大于1则
X
⇐
Y
X \Leftarrow Y
X⇐Y是有效的强关联规则, 提升度小于等于1则
X
⇐
Y
X \Leftarrow Y
X⇐Y是无效的强关联规则 。一个特殊的情况,如果X和Y独立,则有
L
i
f
t
(
X
⇐
Y
)
=
1
Lift(X \Leftarrow Y) = 1
Lift(X⇐Y)=1,因为此时
P
(
X
∣
Y
)
=
P
(
X
)
P(X|Y) = P(X)
P(X∣Y)=P(X)。
一般来说,要选择一个数据集合中的频繁数据集,则需要自定义评估标准。最常用的评估标准是用自定义的支持度,或者是自定义支持度和置信度的一个组合。
(2) Apriori算法思想
对于Apriori算法,我们使用支持度来作为我们判断频繁项集的标准。Apriori算法的目标是找到最大的K项频繁集。这里有两层意思,首先,我们要找到符合支持度标准的频繁集。但是这样的频繁集可能有很多。第二层意思就是我们要找到最大个数的频繁集。比如我们找到符合支持度的频繁集AB和ABE,那么我们会抛弃AB,只保留ABE,因为AB是2项频繁集,而ABE是3项频繁集。那么具体的,Apriori算法是如何做到挖掘K项频繁集的呢?
Apriori算法采用了迭代的方法,先搜索出候选1项集及对应的支持度,剪枝去掉低于支持度的1项集,得到频繁1项集。然后对剩下的频繁1项集进行连接,得到候选的频繁2项集,筛选去掉低于支持度的候选频繁2项集,得到真正的频繁二项集,以此类推,迭代下去,直到无法找到频繁k+1项集为止,对应的频繁k项集的集合即为算法的输出结果。第i次的迭代过程包括扫描计算候选频繁i项集的支持度,剪枝得到真正频繁i项集和连接生成候选频繁i+1项集三步。比如:
我们的数据集D有4条记录,分别是134,235,1235和25。现在我们用Apriori算法来寻找频繁k项集,最小支持度设置为50%。首先我们生成候选频繁1项集,包括我们所有的5个数据并计算5个数据的支持度,计算完毕后我们进行剪枝,数据4由于支持度只有25%被剪掉。我们最终的频繁1项集为1235,现在我们链接生成候选频繁2项集,包括12,13,15,23,25,35共6组。此时我们的第一轮迭代结束。
进入第二轮迭代,我们扫描数据集计算候选频繁2项集的支持度,接着进行剪枝,由于12和15的支持度只有25%而被筛除,得到真正的频繁2项集,包括13,23,25,35。现在我们链接生成候选频繁3项集,123, 135和235共3组,这部分图中没有画出。通过计算候选频繁3项集的支持度,我们发现123和135的支持度均为25%,因此接着被剪枝,最终得到的真正频繁3项集为235一组。由于此时我们无法再进行数据连接,进而得到候选频繁4项集,最终的结果即为频繁3三项集235。
(3) Aprior算法流程
输入:数据集合D,支持度阈值αα
输出:最大的频繁k项集
1)扫描整个数据集,得到所有出现过的数据,作为候选频繁1项集。k=1,频繁0项集为空集。
2)挖掘频繁k项集
a) 扫描数据计算候选频繁k项集的支持度
b) 去除候选频繁k项集中支持度低于阈值的数据集,得到频繁k项集。如果得到的频繁k项集为空,则直接返回频繁k-1项集的集合作为算法结果,算法结束。如果得到的频繁k项集只有一项,则直接返回频繁k项集的集合作为算法结果,算法结束。
c) 基于频繁k项集,连接生成候选频繁k+1项集。
3) 令k=k+1,转入步骤2。
从算法的步骤可以看出,Aprior算法每轮迭代都要扫描数据集,因此在数据集很大,数据种类很多的时候,算法效率很低。
引自https://www.cnblogs.com/pinard/p/6293298.html
(4) Aprior算法代码样例
'''
#请从最后的main方法开始看起
Apriori算法,频繁项集算法
A 1, B 2, C 3, D 4, E 5
1 [A C D] 1 3 4
2 [B C E] 2 3 5
3 [A B C E] 1 2 3 5
4 [B E] 2 5
min_support = 2 或 = 2/4
'''
def item(dataset): #求第一次扫描数据库后的 候选集,(它没法加入循环)
c1 = [] #存放候选集元素
for x in dataset: #就是求这个数据库中出现了几个元素,然后返回
for y in x:
if [y] not in c1:
c1.append( [y] )
c1.sort()
#print(c1)
return c1
def get_frequent_item(dataset, c, min_support):
cut_branch = {} #用来存放所有项集的支持度的字典
for x in c:
for y in dataset:
if set(x).issubset(set(y)): #如果 x 不在 y中,就把对应元素后面加 1
cut_branch[tuple(x)] = cut_branch.get(tuple(x), 0) + 1 #cut_branch[y] = new_cand.get(y, 0)表示如果字典里面没有想要的关键词,就返回0
#print(cut_branch)
Fk = [] #支持度大于最小支持度的项集, 即频繁项集
sup_dataK = {} #用来存放所有 频繁 项集的支持度的字典
for i in cut_branch:
if cut_branch[i] >= min_support: #Apriori定律1 小于支持度,则就将它舍去,它的超集必然不是频繁项集
Fk.append( list(i))
sup_dataK[i] = cut_branch[i]
#print(Fk)
return Fk, sup_dataK
def get_candidate(Fk, K): #求第k次候选集
ck = [] #存放产生候选集
for i in range(len(Fk)):
for j in range(i+1, len(Fk)):
L1 = list(Fk[i])[:K-2]
L2 = list(Fk[j])[:K-2]
L1.sort()
L2.sort() #先排序,在进行组合
if L1 == L2:
if K > 2: #第二次求候选集,不需要进行减枝,因为第一次候选集都是单元素,且已经减枝了,组合为双元素肯定不会出现不满足支持度的元素
new = list(set(Fk[i]) ^ set(Fk[j]) ) #集合运算 对称差集 ^ (含义,集合的元素在t或s中,但不会同时出现在二者中)
#new表示,这两个记录中,不同的元素集合
# 为什么要用new? 比如 1,2 1,3 两个合并成 1,2,3 我们知道1,2 和 1,3 一定是频繁项集,但 2,3呢,我们要判断2,3是否为频繁项集
#Apriori定律1 如果一个集合不是频繁项集,则它的所有超集都不是频繁项集
else:
new = set()
for x in Fk:
if set(new).issubset(set(x)) and list(set(Fk[i]) | set(Fk[j])) not in ck: #减枝 new是 x 的子集,并且 还没有加入 ck 中
ck.append( list(set(Fk[i]) | set(Fk[j])) )
#print(ck)
return ck
def Apriori(dataset, min_support = 2):
c1 = item (dataset) #返回一个二维列表,里面的每一个一维列表,都是第一次候选集的元素
f1, sup_1 = get_frequent_item(dataset, c1, min_support) #求第一次候选集
F = [f1] #将第一次候选集产生的频繁项集放入 F ,以后每次扫描产生的所有频繁项集都放入里面
sup_data = sup_1 #一个字典,里面存放所有产生的候选集,及其支持度
K = 2 #从第二个开始循环求解,先求候选集,在求频繁项集
while (len(F[K-2]) > 1): #k-2是因为F是从0开始数的 #前一个的频繁项集个数在2个或2个以上,才继续循环,否则退出
ck = get_candidate(F[K-2], K) #求第k次候选集
fk, sup_k = get_frequent_item(dataset, ck, min_support) #求第k次频繁项集
F.append(fk) #把新产生的候选集假如F
sup_data.update(sup_k) #字典更新,加入新得出的数据
K+=1
return F, sup_data #返回所有频繁项集, 以及存放频繁项集支持度的字典
if __name__ == '__main__':
dataset = [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]] #装入数据 二维列表
F, sup_data = Apriori(dataset, min_support = 2) #最小支持度设置为2
print("具有关联的商品是{}".format(F)) #带变量的字符串输出,必须为字典符号表示
print('------------------')
print("对应的支持度为{}".format(sup_data))
结果为:
具有关联的商品是[[[1], [2], [3], [5]], [[1, 3], [2, 3], [2, 5], [3, 5]], [[2, 3, 5]]]
------------------
对应的支持度为{(1,): 2, (2,): 3, (3,): 3, (5,): 3, (1, 3): 2, (2, 3): 2, (2, 5): 3, (3, 5): 2, (2, 3, 5): 2}
引自https://blog.csdn.net/qq_39872846/article/details/105291265