学习笔记【机器学习重点与实战】——11 频繁项集与关联关系挖掘算法

1 频繁项集与关联关系挖掘算法

从大规模数据集中寻找物品间的隐含关系被称作关联分析(association analysis ) 或者关联规则学习(association rule learning)。关联分析是用于发现大数据集中元素间有趣关系的一个工具集,可以采用两种方式来量化这些有趣的关系。第一种方式是使用频繁项集,它会给出经常在一起出现的元素项。第二种方式是关联规则,每条关联规则意味着元素项之间的“如果……那么”关系。

关联分析是数据挖掘中最活跃的研究方法之一,目的是在一个数据集中找出各项之间的关联关系,而这种关系并没有在数据中直接表现出来。

这里的主要问题在于,发现元素项间不同的组合是一项十分耗时的任务,不可避免需要大量昂贵的计算资源,蛮力搜索方法并不能解决这个问题,所以需要用更智能的方法在合理的时间范围内找到频繁项集。 其中常见的关联关系算法如下表所示。

常用关联规则算法

算法名称 算法描述
Apriori 关联规则最常用也是最经典的挖掘频繁项集的算法,其核心思想是通过连接产生候选项及其支持度然后通过剪枝生成频繁项集
FP-Tree 针对Apriori算法的固有的多次扫描事务数据集的缺陷,提出的不产生候选频繁项集的方法。Apriori和FP-Tree都是寻找频繁项集的算法
Eclat算法 Eclat算法是一种深度优先算法,采用垂直数据表示形式,在概念格理论的基础上利用基于前缀的等价关系将搜索空间划分为较小的子空间
灰色关联法 分析和确定各因素之间的影响程度或是若干个子因素(子序列)对主因素(母序列)的贡献度而进行的一种分析方法

(1)关联规则的一般形式

项集A、B同时发生的概率称为关联规则的支持度(也称相对支持度)。

S u p p o r t ( A B ) = P ( A U B )

项集A发生,则项集B发生的概率为关联规则的置信度。
C o n f i d e n c e ( A B ) = P ( B | A )

(2)最小支持度和最小置信度

最小支持度是用户或专家定义的衡量支持度的一个阀值,表示项目集在统计意义上的最低重要性;最小置信度是用户或专家定义的衡量置信度的一个阈值,表示关联规则的最低可靠性。

同时满足最小支持度阈值和最小置信度网值的规则称作强规则。

(3)项集

项集是项的集合。包含k个项的项集称为k项集,如集合{牛奶,麦片,糖}是一个3项集。

项集的出现频率是所有包含项集的事务计数,又称作绝对支持度或支持度计数。如果项集 I 的相对支持度满足预定义的最小支持度阈值,则 I 是频繁项集。频繁k项集通常记作k。

(4)支持度计数

项集A的支持度计数是事务数据集中包含项集A的事务个数,简称为项集的频率或计数。

已知项集的支持度计数,则规则A→B的支持度和置信度很容易从所有事务计数、项集A和项集AUB的支持度计数推出。

(5) S u p p o r t ( A B ) = A , B = S u p p o r t _ c o u n t ( A B ) T o t a l _ c o u n t ( A ) (6) C o n f i d e n c e ( A B ) = P ( B | A ) = S u p p o r t ( A B ) S u p p o r t ( A ) = S u p p o r t _ c o u n t ( A B ) S u p p o r t _ c o u n t ( A )

也就是说,一旦得到所有事务个数,A,B和A∩B的支持度计数,就可以导出对应的关联规则A→B和B→A,并可以检查该规则是否是强规则。

2 Apriori

Apriori算法是最经典的挖掘频繁项集的算法,第一次实现了在大数据集上可行的关联规则提取,其核心思想是通过连接产生候选项与其支持度,然后通过剪枝生成频繁项集。

为了降低所需的计算时间,研究人员发现一种所谓的Apriori原理。Apriori原理可以帮我们减少可能感兴趣的项集。Apriori原理是说如果某个项集是频繁的,那么它的所有子集也是频繁的。也就是说如果一个项集是非频繁集,那么它的所有超集也是非频繁的。 使用该原理就可以避免项集数目的指数增长,从而在合理时间内计算出频繁项集。

优 点:易编码实现。

缺点:在大数据集上可能较慢。

适用数据类型:数值型或者标称型数据。

——《机器学习实战》 P 201

实现代码如下:

from numpy import *

def loadDataSet():
    '''
    创建数据集
    :return: 数据集
    '''
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

def createC1(dataSet):
    """
    创建不重复、排序并转换为frozenset的候选项集列表C1

    Parameters
    ----------
    :param dataSet: 数据集

    Returns
    -------
    :param frozenset: 候选项集列表

    Author:  dkjkls
    Blog:    https://blog.csdn.net/dkjkls
    Modify:  2018/5/5 9:25
    """
    C1 = []
    for transaction in dataSet:     # 遍历数据集
        for item in transaction:    # 遍历项集中元素
            if not [item] in C1:    # 去重
                C1.append([item])
    C1.sort()                       # 升序排序
    return list(map(frozenset, C1))

def scanD(D, Ck, minSupport):
    """
    计算支持度符合要求的候选项集和支持度字典

    Parameters
    ----------
    :param D: 数据集
    :param Ck: 候选项集
    :param minSupport: 最小支持度

    Returns
    -------
    :param retList: 支持度大于 最小支持度 的集合
    :param supportData: 候选项集支持度字典

    Author:  dkjkls
    Blog:    https://blog.csdn.net/dkjkls
    Modify:  2018/5/5 9:35
    """
    ssCnt = {}                      # 存放候选集元素的频率
    #  统计候选项集元素的频率
    for tid in D:                   # 遍历数据集
        for can in Ck:              # 遍历候选项集
            if can.issubset(tid):   # 候选项集元素在数据集中,则计数
                if not ssCnt.__contains__(can): ssCnt[can]=1
                else: ssCnt[can] += 1
    numItems = float(len(D))        # 数据集大小
    retList = []                    # 支持度大于 minSupport 的集合
    supportData = {}                # 候选项集支持度字典
    for key in ssCnt:
        support = ssCnt[key]/numItems   # 支持度大小
        if support >= minSupport:   # 支持度大于最小支持度,则添加集合
            retList.insert(0,key)
        supportData[key] = support  # 存储候选项集支持度
    return retList, supportData

def aprioriGen(Lk, k):
    """
    生成合并后的频繁项集

    Parameters
    ----------
    :param Lk: 频繁项集列表
    :param k: 项集元素个数

    Returns
    -------
    :param retList: 合并后的频繁项集列表

    Author:  dkjkls
    Blog:    https://blog.csdn.net/dkjkls
    Modify:  2018/5/5 9:52
    """
    retList = []
    lenLk = len(Lk)
    # 遍历频繁项集
    for i in range(lenLk):
        for j in range(i+1, lenLk):
            # 分别取前k-2个元素并排序
            L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]
            L1.sort(); L2.sort()
            # 前k-2个元素相同,则进行合并
            if L1==L2:
                retList.append(Lk[i] | Lk[j])
    return retList

def apriori(dataSet, minSupport = 0.5):
    """
    生成频繁项集和支持度集

    Parameters
    ----------
    :param dataSet: 数据集
    :param minSupport: 最小支持度

    Returns
    -------
    :param L: 频繁项集列表
    :param supportData: 所有元素的支持度字典

    Author:  dkjkls
    Blog:    https://blog.csdn.net/dkjkls
    Modify:  2018/5/5 10:34
    """
    C1 = createC1(dataSet)      # 创建不重复、排序并转换为frozenset的候选项集列表C1
    D = list(map(set, dataSet)) # 转换为set
    # 计算支持度符合要求的候选项集和支持度字典
    L1, supportData = scanD(D, C1, minSupport)
    L = [L1]
    k = 2   # 项集元素个数
    while (len(L[k-2]) > 0):                # 遍历,直到没有新生成频繁项
        Ck = aprioriGen(L[k-2], k)          # 生成合并后的频繁项集
        Lk, supK = scanD(D, Ck, minSupport) # 计算支持度符合要求的候选项集和支持度字典
        supportData.update(supK)            # 更新候选项集的支持度字典
        L.append(Lk)                        # 更新频繁项集
        k += 1
    return L, supportData

def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    """
    计算可信度

    Parameters
    ----------
    :param freqSet: 频繁项集元素
    :param H: 频繁项集中元素集合
    :param supportData: 支持度字典
    :param brl: 关联规则列表
    :param minConf: 最小可信度

    Returns
    -------
    :param prunedH: 可信度大于最小可信度的集合

    Author:  dkjkls
    Blog:    https://blog.csdn.net/dkjkls
    Modify:  2018/5/5 12:55
    """
    # 可信度大于最小可信度的集合
    prunedH = []
    for conseq in H:
        # 计算可信度
        conf = supportData[freqSet]/supportData[freqSet-conseq]
        if conf >= minConf: 
            print(freqSet-conseq,'-->',conseq,'conf:',conf)
            # 关联规则列表新增规则
            brl.append((freqSet-conseq, conseq, conf))
            prunedH.append(conseq)
    return prunedH

def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    """
    递归计算频繁项集关联规则

    Parameters
    ----------
    :param freqSet: 频繁项集元素
    :param H: 频繁项集中元素集合
    :param supportData: 支持度字典
    :param brl: 关联规则列表
    :param minConf: 最小可信度

    Author:  dkjkls
    Blog:    https://blog.csdn.net/dkjkls
    Modify:  2018/5/5 13:04
    """
    m = len(H[0])
    if (len(freqSet) > (m + 1)):    # 频繁项集中元素可以合并
        Hmp1 = aprioriGen(H, m+1)   # 生成合并后的频繁项集
        # 计算可信度,并得到可信度大于最小可信度的集合
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
        if (len(Hmp1) > 1):         # 集合大小大于1则递归计算频繁项集关联规则
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

def generateRules(L, supportData, minConf=0.7):
    """
    生成关联规则

    Parameters
    ----------
    :param L: 频繁项集列表
    :param supportData: 支持度字典
    :param minConf: 最小可信度

    Returns
    -------
    :param bigRuleList: 包含可信度的规则列表

    Author:  dkjkls
    Blog:    https://blog.csdn.net/dkjkls
    Modify:  2018/5/5 12:20
    """
    bigRuleList = []
    # 从含有两个元素的频繁项集开始遍历
    for i in range(1, len(L)):
        for freqSet in L[i]:    # 遍历频繁项集中的元素
            H1 = [frozenset([item]) for item in freqSet]    # 频繁项数据转换
            if (i > 1):
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:   # 两个元素的组合直接计算
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList

if __name__ == '__main__':
    L, supportData = apriori(loadDataSet(),0.1)
    rules = generateRules(L, supportData,0.1)

每次增加频繁项集的大小,Apriori算法都会重新扫描整个数据集。当数据集很大时,这会显著降低频繁项集发现的速度。

3 FP-growth

FP-growth算法将数据集存储在一个特定的称作FP树的结构之后发现频繁项集或者频繁项对,即常在一块出现的元素项的集合FP树。该算法只需要对数据库进行两次扫描,而Apriori算法对于每个潜在的频繁项集都会扫描数据集判定给定模式是否频繁,因此FP-gr0wth算法的速度要比Apri0ri算法快,通常性能要好两个数量级以上。

这种算法虽然能更为高效地发现频繁项集,但不能用于发现关联规则。

优 点:一般要快于Apriori。

缺点:实现比较困难,在某些数据集上性能会下降。

适用数据类型:标称型数据。

——《机器学习实战》 P 224

FP代表频繁模式(Frequent Pattern)。一棵FP树看上去与计算机科学中的其他树结构类似,但是它通过链接(link)来连接相似元素,被连起来的元素项可以看成一个链表。

这里写图片描述

同搜索树不同的是,一个元素项可以在一棵FP树中出现多次。FP树会存储项集的出现频率,而每个项集会以路径的方式存储在树中。存在相似元素的集合会共享树的一部分。只有当集合之间完全不同时,树才会分叉。 树节点上给出集合中的单个元素及其在序列中的出现次数,路径会给出该序列的出现次数。

FP树类定义如下:

class treeNode:
    def __init__(self, nameValue, numOccur, parentNode):
        self.name = nameValue       # 节点名
        self.count = numOccur       # 计数值
        self.nodeLink = None        # 相似元素项的链接
        self.parent = parentNode    # 父节点
        self.children = {}          # 子节点

    # 对count变量增加给定值
    def inc(self, numOccur):
        self.count += numOccur

    # 文本形式显示树
    def disp(self, ind=1):
        print('  '*ind, self.name, ' ', self.count)
        for child in self.children.values():
            child.disp(ind+1)

FP-growth只会扫描数据集两次,发现频繁项集的基本过程如下:

  1. 构建FP树
  2. 通过查找元素项的条件基及构建条件FP树来发现频繁项集
  3. 过程2不断以更多元素作为条件重复进行,直到FP树只包含一个元素为止

4 参考

  1. 《机器学习实战》第11章 使用Apriori算法进行关联分析
  2. 《机器学习实战》第12章 使用FP-growth算法来高效发现频繁项集
  3. 《Python数据分析与挖掘实战》5.3 关联规则

===========文档信息============
学习笔记由博主整理编辑,供非商用学习交流用
如本文涉及侵权,请随时留言博主,必妥善处置
版权声明:非商用自由转载-保持署名-注明出处
署名(BY) :dkjkls(dkj卡洛斯)
文章出处:http://blog.csdn.net/dkjkls

猜你喜欢

转载自blog.csdn.net/dkjkls/article/details/80205688
今日推荐