基础篇 | 06 Apriori 算法

Apriori 算法的作用

Apriori 算法的核心思想是计算频繁项集出现的概率(即支持度)和频繁项集内部的关联规则(即置信度)。

支持度

什么是支持度

 support
如图:如果订单总数为N,包含某个组合的订单为M,那么M组合出现的支持度(概率)为M/N.。

Note
我们这里认为样本数据和我们现实世界的真实情况是同分布的。

如何计算支持度

在数学上我们如何找出某一项集的出现概率呢?
我们先列出所有的一项集,然后计算每一项集在在总数据中出现的概率,再计算它们所有的组合产生的概率,如果数据有成千上万,这个计算量是非常大的,这种情况下,在数学上合理的逻辑在计算机上并不可行,那么Apriori算法的思路是什么呢。
 Apriori logic

支持度的核心思想
如果AB的组合频繁出现,是频繁项集,那么子集A、B必然频繁出现;
反过来,如果A、B中有一项不是频繁项集,那么AB集合必然不是频繁项集。

我们利用概率论的思想来解决这个问题,根据Apriori算法的思路:如果AB组合频繁出现,那么A、B自身必然频繁出现,反过来,如果A、B不是频繁出现的,那AB组合也就不是频繁出现的。所以说Apriori算法提出逐层组合计算思路。
首先列出所以的一项集,计算每一项的支持度,剔除支持度过低的数据项,如图,也就是后两项,然后将前四项组成二项集,再计算每一个种组合的支持度,剔除支持度低的项集,支持度的滤值可以根据实际情况自行设定。然后再组合成三项集、四项集,依次类推,直到n项集为空为止,也就是某个维度的频繁项集不存在,为空集。
那我需要推到什么时候呢,数据量这么大,这就提到了一个概念,阈值threshold,对这个推荐算法来说,我是不是推到四五层就足够了,我迭代到15层,这有15层的组合是不是就足够满足我的需求了呢。我设定这样一个阈值。或这说我支持度达到一个什么程度,我也可以设置一个阈值。概率达到什么程度的阈值。满足这个条件的时候我自然而然的停止,而不是一直计算到为空集为止。这样就能大大提高算法的执行效率也能满足我们的需求。这个和传统咱们计算出所有排列组合的情况是完全不一样的。
这样最后得到的结果项集里面的所有所有可能组合就是频繁项集,这样的项集可能有多个。
比如我们得到最终的频繁项集结果:
[‘I1, I2, I3’, ‘I1, I2, I5’]
比如对于:I1,I2,I3 这个频繁集,我们可以得到它的子集:{I1}, {I2}, {I3}, {I1, I2}, {I1,I3}, {I2,I3}。

那么,如何得到项集内部的规则,也就是关联关系呢。这就涉及另一个概念:置信度。

置信度

释义:A出现的时候,B出现的概率。

这里面还有一个问题是,A和B的关联关系,比如,买C++ Primer Plus,可能会买 C Primer Plus,因为他要研究一下底层的东西,可是买C Primer Plus的人不会买 C++ Primer Plus,他做底层开发,只看底层就够了,也可能我们需要向他汇编一类的书籍。所以,A、B存在因果关系,A是B出现的原因,B却不是A出现的原因,对于这这关系的计算称之为置信度,置信度是找出组合内部的关联关系,支持度是找出在一起出现的频繁项集。这种项集内部的因果关系我们称之为关联规则。

如果A是B出现的原因,我们判断支持度强弱的方法是用A和B并集{support(A|B)的并集指的是A集合和B集合的出现的概}的支持度除以A的支持度。这样我们就可以推断A出现的情况下,B出现的概率。

如果A出现的时候B经常出现,那么A导致B的概率就非常高。如图
confidence
A导致B出现称为置信度。

举个例子
support define
我们有ABCD组合,可以拆成下面为四种组合,我们依次从右边拿出一个放到左边,这样每种组合又能派生出三种组合, 这种情况下,依次类推,直到左边有三项右边有一项为止,这样我们就可以生成所有合理的关联规则组合,在这个基础上,我们再计算所有关联规则的置信度confidence,剔除置信度小于某个阈值threshold的关联规则,这就是关联分析。

所以Apriori算法是找出频繁项集和分析组合内部的因果关系(关联分析)的算法。最后,我们会设定一个阈值,不可能计算到空集位置,这种情况太少见了,推到10 - 15个的项集组合的时候可能就足够了,这种情况下可能会限定一个threshold,避免很多无意义的计算,实际项目当中可能有几百万的项目,以现在的计算能力计算出所有的组合是不可能的吗,所有希望用足够少的时间计算出满足我们需求的结果。所有需要设定threshold,或者设定n,推导到多少次,这个思想其实非常重要,大家要注意,在机器学习当中,这个是机器学习的惯有思维和处理方式,很多时候我们传统的数学模型需要把所有的结果计算出来才能进行下一步推导。 机器学习呢,是逐层计算、逐层推导,它需要一个总体的计算或者说一个结果。 这是机器学习非常重要的一个思想。Apriori算法的核心思想是通过迭代推导每一次产生新项集的方式得到满足需求的结果。这也是机器学习非常关键的核心思想,讲其它算法的时候,机器学习都是按照这个思想走下去的,公式不一样,迭代方法类似。

接下来我们看一下Apriori算法的代码实现。

Apriori 算法实现

目录结构
table cotent
首先,看一下数据我们的数据集apriorData.txt
每一行是一个书籍的订单。

C++ Primer Plus C Primer Plus
C Primer Plus   C和指针    C陷阱与缺陷  C程序设计语言
C++ Primer Plus C和指针    C陷阱与缺陷  C++程序设计语言
C Primer Plus   C++ Primer Plus C和指针    C陷阱与缺陷
C Primer Plus   C++ Primer Plus C和指针    C++程序设计语言

接下来看一下Apriori的核心算法实现代码 apriori.py

"""Apriori算法实现"""
# TODO:补充实现关联规则提取

// transactions:原始数据,supportThreshold:支持度的阈值
def apriori(transactions, supportThreshold):
    """Apriori实现"""
    # 将每一个事务转换成集合,提高检索效率
    transactionSets = [set(transaction) for transaction in transactions]
    # 生成1项集
    itemSets = createItemSets(transactions)
    # 计算1项集支持度并筛选符合要求的项集,将支持度过低的剔除
    filteredItemSets, supports = filterBySupport(transactionSets, 
    itemSets, supportThreshold)
    # 保存符合要求的1项集
    itemSetsList = [filteredItemSets]

    # 如果前一次筛选的项集为空则停止循环,这里为空, 并不是计算到为空集为止,
    # 刚刚我们设定了threshold,我们会在达到这个阈值的时候终止迭代。
    # 将n项集组成n+1项集(上一次的计算结果组成新的项集不断迭代)
    while len(filteredItemSets) > 0:
        # 将k-1项集合并生成k项集
        itemSets = combineItemSets(itemSets)
        # 筛选符合要求的k项集
        filteredItemSets, newSupports = filterBySupport(
            transactionSets, itemSets, supportThreshold)

        # 保存新项集的支持度
        supports.update(newSupports)
        # 保存符合要求的k项集
        itemSetsList.append(filteredItemSets)

    return itemSetsList, supports


def createItemSets(transactions):
    """根据事务创建1项集"""
    itemSets = []

    # 遍历所有事务
    for transaction in transactions:
        # 遍历事务中项目并构造项集
        for item in transaction:
            itemSet = [item]

            if itemSet not in itemSets:
                itemSets.append(itemSet)

    # 排序并返回每一个项集的frozenset
    # frozenset是不可变集合,可以作为字典的key
    itemSets.sort()
    return [frozenset(itemSet) for itemSet in itemSets]


def filterBySupport(transactions, itemSets, threshold):
    """计算项集支持度,并根据支持度阈值筛选满足要求的项集"""
    itemSetCounts = {}

    # 遍历所有事务
    for transaction in transactions:
        # 遍历项集并计算项集在事务中的出现次数
        for itemSet in itemSets:
            # 判断一个集合的所有元素是否都包含于另一个集合
            if itemSet.issubset(transaction):
                # 如果项集没有出现过则返回0
                itemSetCount = itemSetCounts.get(itemSet, 0)
                itemSetCounts[itemSet] = itemSetCount + 1

    filteredItems = []
    supports = {}
    transactionCount = len(transactions)

    # 遍历所有的项集计数
    for itemSet, itemSetCount in itemSetCounts.items():
        # 计算支持度
        support = itemSetCount / transactionCount
        # 根据阈值筛选项集
        if support >= threshold:
            filteredItems.append(itemSet)
        # 保存支持度
        supports[itemSet] = support

    return filteredItems, supports


def combineItemSets(itemSets):
    """将k-1项集合并成k项集"""
    newItemSets = []
    itemSetsCount = len(itemSets)

    # 遍历提取两个项集
    for itemSet1Index in range(itemSetsCount):
        for itemSet2Index in range(itemSet1Index + 1, itemSetsCount):
            itemSet1 = itemSets[itemSet1Index]
            itemSet2 = itemSets[itemSet2Index]

            # 分别计算两个项集的差集
            diff1 = itemSet1 - itemSet2
            diff2 = itemSet2 - itemSet1

            # 如果两个项集各有一个项目不同,其他项目相同则组合成一个新项集
            if len(diff1) == 1 and len(diff2) == 1:
                newItemSet = itemSet1 | itemSet2
                if newItemSet not in newItemSets:
                    newItemSets.append(newItemSet)

    return newItemSets

注意
这里面只实现了支持度的计算,置信度有个公式A与B的并集的支持度除以A的支持度。其实关联规则的核心思想和遍历方式, 跟支持度(支持度的公式m/n)的计算几乎一样,公式不一样,迭代方式类似,大家可以自己参考实现一下。

有了算法之后,我们看一下如果调用算法实现Apriori的计算。
新建:apriorisample.py

from common.io.tsv import TsvDataSetReader
from algorithm.apriori import apriori

dataReader = TsvDataSetReader()
transactions = dataReader.loadDataSet('aprioriData.txt')

itemSetsList, supports = apriori(transactions, supportThreshold=0.5)

print(transactions)
/*
Prints:
[['C++ Primer Plus', 'C Primer Plus'], 
 ['C Primer Plus', 'C和指针', 'C陷阱与缺陷', 'C程序设计语言'], 
 ['C Primer Plus', 'C++ Primer Plus', 'C和指针', 'C陷阱与缺陷'], 
 ['C Primer Plus', 'C++ Primer Plus', 'C和指针', 'C++程序设计语言']]
*/

print(itemSetsList)
/*
Prints:
[[frozenset({'C Primer Plus'}), 
  frozenset({'C++ Primer Plus'}), 
  frozenset({'C陷阱与缺陷'})], 
 [frozenset({'C++ Primer Plus', 'C Primer Plus'}), 
  frozenset({'C Primer Plus', 'C和指针'}), 
  frozenset({'C++ Primer Plus', 'C和指针'})], 
 []]
*/

print(supports)
/*
Prints:
{frozenset({'C Primer Plus'}): 0.8, 
 frozenset({'C++ Primer Plus'}): 0.8,
 frozenset({'C和指针'}): 0.8, 
 frozenset({'C陷阱与缺陷'}): 0.6,
 frozenset({'C++程序设计语言'}): 0.4, 
 frozenset({'C和指针', 'C Primer Plus'}): 0.6, 
 frozenset({'C陷阱与缺陷', 'C Primer Plus'}): 0.4, 
 frozenset({'C和指针', 'C陷阱与缺陷'}): 0.6, 
 frozenset({'C++程序设计语言', 'C++ Primer Plus'}): 0.4, 
 frozenset({'C和指针', 'C++ Primer Plus'}): 0.6, 
 frozenset({'C陷阱与缺陷', 'C++ Primer Plus'}): 0.4, 
 frozenset({'C++程序设计语言', 'C陷阱与缺陷'}): 0.2, 
 frozenset({'C++程序设计语言', 'C Primer Plus'}): 0.2, 
 frozenset({'C和指针', 'C Primer Plus', 'C陷阱与缺陷'}): 0.4, 
 frozenset({'C程序设计语言', 'C和指针', 'C陷阱与缺陷'}): 0.2, 
 frozenset({'C++程序设计语言', 'C陷阱与缺陷', 'C++ Primer Plus'}): 0.2, 
 frozenset({'C++程序设计语言', 'C和指针', 'C陷阱与缺陷'}): 0.2, 
 frozenset({'C陷阱与缺陷', 'C Primer Plus', 'C++ Primer Plus'}): 0.2, 
 frozenset({'C++程序设计语言', 'C Primer Plus', 'C++ Primer Plus'}): 0.2, 
 frozenset({'C++程序设计语言', 'C和指针', 'C Primer Plus'}): 0.2}
*/

猜你喜欢

转载自blog.csdn.net/wudizhanshen/article/details/80036375
今日推荐