[完]机器学习实战 第十一章 使用Apriori算法进行关联分析

本章内容:

  • Apriori算法
  • 频繁项集生成
  • 关联规则生成
  • 投票中的关联规则发现

从大规模数据集中寻找物品间的隐含关系被称作关联分析(association analysis)或者关联规则学习(association rule learning)。寻找物品的不同组合十分耗时,计算代价高,蛮力搜索方法不能解决这个问题。使用Apriori算法可解决这个问题。

一、关联分析

关联分析是一种在大规模数据集中寻找有趣关系的任务。这些关系有两种形式:频繁项集、关联规则。频繁项集(frequent item sets)是经常出现在一块的物品的集合;关联规则(association rules)暗示两种物品之间可能存在很强的关系。

一个项集的支持度(support)被定义为数据集中包含该项集的记录所占的比例。可定义一个最小支持度,只保留满足最小支持度的项集。

可信度或置信度(confidence)是针对一条关联规则来定义的。例如:{尿布, 葡萄酒}的支持度为3/5,尿布的支持度为4/5,所以“尿布->葡萄酒”的可信度为3/4=0.75。

支持度和可信度是用来量化关联分析是否成功的方法。

二、Apriori原理

一个商店的目标是找到经常在一起购买的物品集合,可使用集合的支持度来度量其出现频率。一个集合的支持度是指有多少比例的交易记录包含该集合。这需要遍历数据记录,而随着物品数目的增加,遍历次数会急剧增长。对于包含N中物品的数据集共有 2N1 中项集组合,对于只出售100中商品的商店也会有 1.26×1030 中可能的项集组合。对于现代计算机,需要很长的时间才能完成运算。

Apriori可以减少感兴趣的项集。Apriori原理:如果某个项集是频繁的,那么它的所有子集也是频繁的。反过来,如果一个项集是非频繁集,那么它的所有超集也是非频繁的。

三、使用Apriori算法发现频繁集

关联分析的目标包括:发现频繁集和发现关联规则。首先要找到频繁项集,然后才能获得关联规则。

Apriori是发现频繁集的一种方法,它的两个输入参数分别是最小支持度和数据集。首先,该算法会生成所有单个物品的项集列表。接着,扫描交易记录来查看哪些项集满足最小支持度要求,去掉不满足最小支持度的集合。然后,对剩下来的集合进行组合以生成包含两个元素的项集。接下来,再重新扫描交易记录,去掉不满足最小支持度的项集。该过程重复进行直到所有项集都被去掉。

3-1 生成候选项集

创建一个用于构建初始集合的函数,也会创建一个通过扫描数据集以寻找交易记录子集的函数。数据集扫描的伪代码大致如下:

对数据集中的每条交易记录tran
对每个候选项集can :
    检查一下can是否是tran的子集 :
    如果是,则增加can的计数值
对每个候选项集 :
如果其支持度不低于最小值,则保留该项集
返回所有频繁项集列表  
# coding=utf-8

# 创建一个简单的测试数据集
def loadDataSet() :
    return [[1,3,4], [2,3,5], [1,2,3,5], [2,5]]

# 构建集合C1,C1是大小为1的所有候选项集的集合。
def createC1(dataSet) :
    # C1是空列表,用来存储所有不重复的项值。如果某个物品项没有在C1中出现,则将其添加到C1中。
    # 这里并不是简单地每个物品项,而是添加只包含该物品项的一个列表。Python不能创建只有一个整
    # 数的集合,因此这里实现必须使用列表
    C1 = []
    for transaction in dataSet :
        for item in transaction :
            if not [item] in C1 :
                C1.append([item])
    C1.sort()
    # frozenset是指被“冰冻”的集合,就是说它们是不可改变
    return map(frozenset, C1)

# D: 数据集
# Ck: 候选项集列表
# minSupport: 感兴趣集的最小支持度minSupport
# 该函数会返回一个包含支持度的字典以备后用
def scanD(D, Ck, minSupport) :
    ssCnt = {}
    for tid in D :
        for can in Ck :
            if can.issubset(tid) :
                if not ssCnt.has_key(can) : ssCnt[can] = 1
                else : ssCnt[can] += 1
    numItems = float(len(D))
    retList = []
    supportData = {}
    for key in ssCnt :
        # 计算所有项集的支持度
        support = ssCnt[key]/numItems
        if support >= minSupport :
            # 在列表的首部插入新的集合
            retList.insert(0, key)
        supportData[key] = support
    return retList, supportData
>>> import ml.apriori as apriori
# 导入数据集
>>> dataSet = apriori.loadDataSet()
>>> dataSet
[[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
# 构建第一个候选项集集合C1
>>> C1 = apriori.createC1(dataSet)
>>> C1
[frozenset([1]), frozenset([2]), frozenset([3]), frozenset([4]), frozenset([5])]
# 构建集合表示的数据集D
>>> D = map(set, dataSet)
>>> D
[set([1, 3, 4]), set([2, 3, 5]), set([1, 2, 3, 5]), set([2, 5])]
# 去掉不满足最小支持度的项集,0.5为最小支持度
>>> L1, suppData0 = apriori.scanD(D, C1, 0.5)
# 下面四个项集构成了L1列表,该列表中每个单物品项集至少出现在50%以上的记录中
>>> L1
[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])]

3.2 组织完整的Apriori算法

伪代码如下:

当集合中项的个数大于0时
    构建一个k个项组成的候选项集的列表
    检查数据以确认每个项集都是频繁的
    保留频繁项集并构建k+1项组成的候选项集的列表

具体算法代码:

# 创建候选项集Ck
# Lk,频繁项集列表
# k,项集元素的个数
def aprioriGen(Lk, k) : # create Ck
    # 创建一个空列表
    retList = []
    # 计算Lk中的元素
    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()
            if L1==L2 :
                # python中集合的并操作对应的操作符为|
                retList.append(Lk[i] | Lk[j])
    return retList

# dataSet,数据集
# minSupport,支持度
# 此函数会生成候选项集的列表
def apriori(dataSet, minSupport = 0.5) :
    C1 = createC1(dataSet)
    # map函数将set()映射到dataSet列表中的每一项
    D = map(set, dataSet)
    L1, supportData = scanD(D, C1, minSupport)
    # 将L1放入L列表中
    L = [L1]
    k = 2
    # while循环将L2, L3, L4, ... 放入L列表中,直到下一个大的项集为空
    while (len(L[k-2]) > 0) :
        # 调用aprioriGen()创建候选项集Ck
        Ck = aprioriGen(L[k-2], k)
        # 扫描数据集,从Ck得到Lk
        Lk, supK = scanD(D, Ck, minSupport)
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L, supportData

程序执行效果:

>>> reload(apriori)
<module 'ml.apriori' from 'C:\Python27\ml\apriori.pyc'>
>>> L, supportData = apriori.apriori(dataSet)
>>> L
[[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])], [frozenset([1
, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])], [frozenset([2,
3, 5])], []]
>>> L[0]
[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])]
>>> L[1]
[frozenset([1, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])]
>>> L[2]
[frozenset([2, 3, 5])]
>>> L[3]
[]
>>> apriori.aprioriGen(L[0], 2)
[frozenset([1, 3]), frozenset([1, 2]), frozenset([1, 5]), frozenset([2, 3]), fro
zenset([3, 5]), frozenset([2, 5])]
>>> L,support = apriori.apriori(dataSet, minSupport=0.7)
>>> L
[[frozenset([3]), frozenset([2]), frozenset([5])], [frozenset([2, 5])], []]

四、从频繁项集中挖掘关联规则

关联分析的两个重要目标是发现频繁项集关联规则。要找到关联规则,首先从一个频繁项集开始,集合中的元素是不重复的,但我们想知道基于这些元素能否获得其他内容。某个元素或者某个元素集合可能会推导出另一个元素。例如,一个频繁项集{豆奶, 莴苣},可能有一条关联规则“豆奶 莴苣”,箭头的左边集合称作前件,箭头右边的集合称为后件

每个频繁项集可产生许多关联规则,如果能够减少规则数目来确保问题的可解性,那么计算起来会好很多。如果某条规则并不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求。利用此性质来减少测试的规则数目,可以先从一个频繁项集开始,接着创建一个规则列表,其中规则右部只包含一个元素,然后对这些规则测试。接下来合并所有剩余规则来创建一个新的规则列表,其中右部包含两个元素。这种方法称为分级法。具体代码:

# 关联规则生成函数,此函数调用其他两个函数rulesFromConseq、calcConf
# L: 频繁项集列表
# supportData: 包含那些频繁项集支持数据的字典
# minConf: 最小可信度阈值,默认是0.7
# 函数最后要生成一个包含可信度的规则列表,后面可以基于可信度对它们进行排序
# 这些规则存放在bigRuleList中。
def generateRules(L, supportData, minConf=0.7) :
    bigRuleList = []
    # 遍历L中的每一个频繁项集并对每个频繁项集创建只包含单个元素集合的列表H1,
    # 因为无法从单元素项集中构建关联规则,所以要从包含两个或者更多元素的项集开始规则构建过程。

    # 只获取有两个或更多元素的集合
    for i in range(1, len(L)) :
        for freqSet in L[i] :
            H1 = [frozenset([item]) for item in freqSet]
            if i > 1 :
                # 如果频繁项集的元素数目超过2,那么会考虑对它做进一步的合并,合并通过
                # rulesFromConseq来完成
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else :
                # 如果项集中只有两个元素,那么需要使用calcConf()来计算可信度值
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList

# 对规则进行评估
# 目标是计算规则的可信度以及找到满足最小可信度要求的规则
# 函数会返回一个满足最小可信度要求的规则列表,空列表prunedH保存这些规则
def calcConf(freqSet, H, supportData, brl, minConf=0.7) :
    prunedH = []
    # 遍历H中的所有项集并计算它们的可信度值
    for conseq in H :
        # 可信度计算时使用supportData中的支持度数据
        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

# 用于生成候选规则集合,从最初的项集中生成更多的关联规则
# freqSet: 频繁项集
# H: 可以出现在规则右部的元素列表
def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7) :
    # H中频繁项集大小m
    m = len(H[0])
    # 查看该频繁项集是否大到可以移除大小为m的子集
    if (len(freqSet) > (m+1)) :
        # 生成H中元素的无重复组合,结果存储在Hmp1,这也是下一次迭代的H列表
        Hmp1 = aprioriGen(H, m+1)
        # Hmp1包含所有可能的规则,利用calcConf()来测试它们的可信度以确定是否满足要求
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
        # 如果不止一条规则满足要求,那么使用Hmp1迭代调用函数rulesFromConseq
        if (len(Hmp1) > 1) :
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

实际运行效果:

>>> import ml.apriori as apriori
>>> dataSet = apriori.loadDataSet()
>>> dataSet
[[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
>>> L, support = apriori.apriori(dataSet, minSupport=0.5)
>>> L, supportData = apriori.apriori(dataSet, minSupport=0.5)
>>> rules = apriori.generateRules(L, supportData, minConf=0.7)
frozenset([1]) --> frozenset([3]) conf: 1.0
frozenset([5]) --> frozenset([2]) conf: 1.0
frozenset([2]) --> frozenset([5]) conf: 1.0
>>> rules
[(frozenset([1]), frozenset([3]), 1.0), (frozenset([5]), frozenset([2]), 1.0), (
frozenset([2]), frozenset([5]), 1.0)]
# 降低可信度阈值后的结果
>>> rules = apriori.generateRules(L, supportData, minConf=0.5)
frozenset([3]) --> frozenset([1]) conf: 0.666666666667
frozenset([1]) --> frozenset([3]) conf: 1.0
frozenset([5]) --> frozenset([2]) conf: 1.0
frozenset([2]) --> frozenset([5]) conf: 1.0
frozenset([3]) --> frozenset([2]) conf: 0.666666666667
frozenset([2]) --> frozenset([3]) conf: 0.666666666667
frozenset([5]) --> frozenset([3]) conf: 0.666666666667
frozenset([3]) --> frozenset([5]) conf: 0.666666666667
frozenset([5]) --> frozenset([2, 3]) conf: 0.666666666667
frozenset([3]) --> frozenset([2, 5]) conf: 0.666666666667
frozenset([2]) --> frozenset([3, 5]) conf: 0.666666666667
>>> rules
[(frozenset([3]), frozenset([1]), 0.6666666666666666), (frozenset([1]), frozense
t([3]), 1.0), (frozenset([5]), frozenset([2]), 1.0), (frozenset([2]), frozenset(
[5]), 1.0), (frozenset([3]), frozenset([2]), 0.6666666666666666), (frozenset([2]
), frozenset([3]), 0.6666666666666666), (frozenset([5]), frozenset([3]), 0.66666
66666666666), (frozenset([3]), frozenset([5]), 0.6666666666666666), (frozenset([
5]), frozenset([2, 3]), 0.6666666666666666), (frozenset([3]), frozenset([2, 5]),
 0.6666666666666666), (frozenset([2]), frozenset([3, 5]), 0.6666666666666666)]

六、示例:发现毒蘑菇的相似特性

有时并不需要寻找所有频繁项集,而只对包含某个特定元素项的项集感兴趣。示例会寻找毒蘑菇中的一些公共特征,利用这些特征就能避免吃到那些有毒的蘑菇。

>>> import ml.apriori as apriori
# 导入数据
>>> mushDataSet = [line.split() for line in open('c:\python27\\mushroom.dat').readlines()]
# 在数据集上运行apriori算法
>>> L, suppData = apriori.apriori(mushDataSet, minSupport=0.3)
# 在结果中搜索包含有毒特征值2的频繁项集
>>> for item in L[1] :
...     if item.intersection('2') : print item
...
frozenset(['2', '59'])
frozenset(['39', '2'])
frozenset(['2', '67'])
frozenset(['2', '34'])
frozenset(['2', '23'])
frozenset(['2', '86'])
frozenset(['76', '2'])
frozenset(['90', '2'])
frozenset(['2', '53'])
frozenset(['93', '2'])
frozenset(['63', '2'])
frozenset(['2', '28'])
frozenset(['2', '85'])
frozenset(['2', '36'])
# 对更大项集来重复上述过程
>>> for item in L[3] :
...     if item.intersection('2') : print item
...
frozenset(['63', '59', '2', '93'])
frozenset(['39', '2', '53', '34'])
frozenset(['2', '59', '23', '85'])
frozenset(['2', '59', '90', '85'])
frozenset(['39', '2', '36', '34'])
frozenset(['39', '63', '2', '85'])
frozenset(['39', '2', '90', '85'])
frozenset(['2', '59', '90', '86'])
frozenset(['2', '90', '93', '86'])
frozenset(['39', '2', '63', '86'])
......

七、本章小结

关联分析是用于发现大数据集中元素间有趣关系的工具集,可使用频繁项集、关联规则来量化这些关系。发现元素间不同的组合是十分耗时的,使用Apriori可减少在数据库上进行检查的集合的数目。Apriori原理是如果一个元素项是不频繁的,那么包含该元素的超集也是不频繁的。Apriori算法从单元素项集开始,通过组合满足最小支持度要求的项集来形成更大的集合。支持度用来度量一个集合在原始数据中出现的频率。

猜你喜欢

转载自blog.csdn.net/namelessml/article/details/52718628
今日推荐