关联规则进行数据挖掘 Apriori 算法

1. 关联分析(Association analysis)


理解:

  • 从大规模的数据中, 发现事物(物品)间的隐含关系的过程就是关联分析(association analysis)或者关联规则学习(association rule learning).
  • 是一种在大规模数据中发现有趣关系的任务.

这些有趣关系, 主要分为两种: 频繁项集或者关联规则

频繁项集(frequent item sets): 经常在一起出现的物品的集合
关联规则(association rule): 暗示两个物品的集合之间可存在某种很强的依赖的关系;后面会给出明确定义。
以下,将说明未然频繁项集的产生与关联规则的产生进行说明
因为频繁项集的产生需要对大规模数据进行遍历,所以产生了Apriori算法来挖掘频繁项集。

2. 相关概念


  • 支持度(support): 某个项集的支持度被定义为: 包含该项集的事务的比例( 包含该项集的事务 / 所有事务)
  • 置信度(confidence): 针对两个项集 如 {尿布} -->{啤酒} 的关联规则, 其置信度为 : support( {啤酒, 尿布} ) / support( {尿布} ) ,即可以理解为: 在尿布存在的情况下,啤酒存在的概率 或 由尿布可以推出啤酒这件事的确定性。
  • 项集(sets): 事务中的每一个物品的集合, 如商店每个购物车里的每件商品自由组合; 毒蘑菇每种蘑菇的特征的组合。所有标称数值的值的集合。
  • 事务: 现实世界中每一件完整的事项, 如商店每个人的购物车,美国国会的每一次投票, 毒蘑菇的每一种类。
  • 候选项集(Candidate sets): 一般用C来表示,候选项集里面的部分或全部项集组成相应的频繁项集
  • 最小支持度(min-support): 根据实际业务情况来定义的一个最小支持度,大于该支持度的项集可以看做为有趣的项集。
  • 频繁项集(frequent sets): 一般用L表示,候选项集中 支持度大于最小支持度的项集
  • 最小置信度(min confidence):根据实际业务情况确定的一个最小置信度,大于该置信度的规则可以看做是有趣的。
  • n项集: 表示该项集中一共有n项物品,Cn表示候选n项集,Ln表示频繁n项集

3. Apriori算法查找频繁项集

3.1 Apriori算法的原理:

Apriori 算法的一般过程

  1. 收集数据:使用任意方法
  2. 准备数据:任何数据类型都可以, 因为我们需要的只是集合
  3. 分析数据:使用任一方法
  4. 训练算法:使用Apriori算法来找到频繁项集
  5. 测试算法:不需要测试
  6. 使用算法:用于发现频繁项集以及物品之间的关联规则

基本原理

  • 如果一个项集是频繁的,那么它的所有子集也是频繁的,
    如: {1, 0} 是一个频繁项集,那么 {1}, {0}也是频繁项集
  • 反之,如果一个项集是非频繁的,那么它的所有超集也是非频繁的
    如:{1,2} 是一个非频繁项集,那么 {1,2,3},{0,1,2,}也是非频繁项集
  • 在下面可以看到,有了这个先验知识,就可以对频繁项集的寻找过程进行剪枝

3.2 Apriori算法步骤

主要步骤

  1. 根据原始数据生成候选1项集 C1
  2. 根据C1生成频繁1项集 L1
  3. 迭代
    根据Lk项集生成Ck+1项集 //连接
    对Ck+1项集剪枝 //剪枝
    如果Ck+1剪枝后为空,算法结束
    根据Ck+1生成Lk+1项集

连接 由Ck生成Ck+1

  • 笛卡尔乘积
  • 例如 对于频繁项集L2: [ {1, 2}, {1, 3}, {2, 3}, {2, 4} ], 连接结果为C3: [ {1, 2, 3}, {2, 3, 4} ]
  • 具体过程: 对于频繁项集Lk中的每一内部有序的项Lk[i]与其他所有内部有序的项Lk[j], 判断Lk[i][:k-1] == Lk[j][:k-1], 如果相等,则 C3.append(Lk[i] | Lk[j]), 这里的 | 是集合的并操作

剪枝 由Ck+1生成Lk+1

  • 根据基本原理,对于生成的候选项集Ck,利用Apriori的原理:其子集若为非频繁项集,则该项集也为非频繁项集。
  • 具体来说,就是对于Ck中的每个元素Ck[i], 若Ck[i]的所有k-1项的子集在Lk-1中不存在,则说明Ck[i]肯定不是频繁的,剪枝
  • 根据最小支持度得出Lk
    遍历原始事务数据集,计算 support = 含有Ck[i]的事务的数量 / 总事务数量, support >= min-support 添加到 Lk中

Python 实现

  • 输入:事务的列表: [ [事务1的项], [事务2的项], … ]
  • 输出:
    1. 频繁项集列表freqSets: [ [频繁1项集], [频繁2项集], … ];
    2. 支持度字典support:{ {项集}:支持度, {项集}:支持度,… }
  • 数据结构:
    项集:冻结集合, frozenset , 可以用来作为字典的key
    频繁项集,候选项集: 列表
    项集与其支持度:字典

我的代码

这里我因为之前忘记了 frozenset这个数据结构, 所以用的元组来代替, 所以很多冗余代码, 懒得改了, 就这样吧, 实现的算法看主要步骤, 数据结构看上面, 很清楚了

def read_data(path):
    """
    读取初始数据集, 给数据集格式化为字典
    :param path: 文件路径,
    :return: {key: [value1, value2, ...], key2: [...], ...}
    """
    data = dict()
    with open(path, 'r') as f:
        lines = f.readlines()
        for line in lines:
            cart = line.split(" ")
            data[cart[0]] = [c.strip() for c in cart[1:]]
            data[cart[0]].sort()
    print(data)
    return data


def get_frequent_itemsets(c, min_support, num_itmes):
    l = list()
    support = {}
    for i in filter(lambda key: c.get(key) if c.get(key)/float(num_itmes) >= min_support else None, c):
        support[i] = c.get(i)/float(num_itmes)
        l.append(i)
    l.sort()
    return l, support


def find_frequent_1_itemsets(data, min_support):
    c_1 = dict()
    for v in data.values():
        for j in v:
            r = c_1.get((j,))
            if r is not None:
                c_1[(j,)] = r + 1
            else:
                c_1[(j, )] = 1
    num_itmes = len(data)
    return get_frequent_itemsets(c_1, min_support, num_itmes)


def find_frequent_n_itemsets(data, c, min_support):
    c_n = dict()
    for i in c:  # 每个元组 项集
        for key in data:
            for j in i:  # 元组里面的元素 项
                try:
                    data.get(key).index(j)
                except ValueError as e:
                    break
            else:
                r = c_n.get(i)
                if r is not None:
                    c_n[i] = r + 1
                else:
                    c_n[i] = 1
    num_items = len(data)
    return get_frequent_itemsets(c_n, min_support, num_items)


def decartes(c1, c2):
    c = list()
    for i_1 in c1:
        for i_2 in c2:
            if i_1[0:-1] == i_2[0:-1] and i_1[-1] < i_2[-1]:
                li = list()
                li.extend(i_1)
                li.append(i_2[-1])
                tu = tuple(li)
                c.append(tu)
    return c


def cut(c, pre_c):
    """
     找出c_k中所有k-1项的子集, 并判断在前一个(c_k-1)项集中是否存在该子集
     若不存在,则将该项去除
    """
    num = len(c[0])
    for i in c:
        for lost in range(num):
            tmp = list(i)
            tmp.remove(i[lost])
            try:
                pre_c.index(tuple(tmp))
            except ValueError:  # 不存在该项集 移除
                c.remove(i)
                break
    return c


def generate_freqSet_support_list(data, min_support):
    l, support = find_frequent_1_itemsets(data, min_support)
    freq_support = {}

    while len(l) > 0:
        print("正在计算{0}项集的support...".format(len(l[0])))
        freq_support.update(support)
        c = decartes(l, l)
        if len(c) == 0:
            break
        cut_c = cut(c, l)
        l, support = find_frequent_n_itemsets(data, cut_c, min_support)
    return freq_support

3.3 Apriori 算法优劣

  • 优势:
    1. 实现简单
    2. 处理大型数据的时候,可以存储到外存上
  • 劣势
    1. 效率低,每次计算Li项集的时候,就需要遍历一遍原始数据来计算支持度

3.4 另一种频繁项集计算:FP_growth (frequent pattern)

4. 根据频繁项集挖掘关联规则

原理

在上一步, 我们已经得到了 频繁项集支持度计数(support)
根据关联规则公式: 对于关联规则 p --> r 有:conf = support( p | r ) / support( p )
因此 我们只需要不断从上一步的support结果中选择 support 就能得到confidence了

算法主要步骤(python实现)

算法简单,直接看代码

from Apriori.apriori import decartes  # 计算笛卡尔乘积
from Apriori import apriori


def tuple2frozenset(freq_support):
    """
    将数据结构{元组:支持度, 元组:支持度}, 转换为: {frozenset: 支持度, frozenset:支持度}
    :param freq_support:{元组:支持度, 元组:支持度}
    :return:{frozenset: 支持度, frozenset:支持度}
    """
    # freq_support的key转换成frozenset
    support = dict()
    for i in freq_support:
        support[frozenset(i)] = freq_support.get(i)
    return support


def generateRules(res_path, support, min_conf=0.7):
    """
    生成关联规则
    :param res_path: 关联规则的文件写入路径 
    :param support: apriori算法得到的支持度字典 {frozenset: 支持度, frozenset:支持度}
    :param min_conf: 最小置信度
    :return: 强关联规则的结果集 对于 p->q conf 有 [(p, q, conf), ()]
    """
    with open(res_path, 'w+') as f:
        ruleList = []
        for freqSet in support:  # freqSet: 频繁项集(frozenset)
            h1 = [frozenset([item]) for item in freqSet]  # 对频繁项集的每一项生成一个项集

            if len(freqSet) == 2:  # 如果是频繁二项集, h1连接之后也是2项集, 所以只需要计算一次 h1
                caculConf(freqSet, support, h1, min_conf, ruleList, f)
            else:
                rulesFromConseq(freqSet, support, h1, min_conf, ruleList, f)
    return ruleList


def caculConf(freqSet, support, h1, min_conf, ruleList, *f):
    for conSeq in h1:
        try:
            conf = support[freqSet]/support[freqSet-conSeq]  # freqSet-conSeq --> conSeq
        except ValueError:
            print("集合不存在该频繁项集!")
            continue

        if conf > min_conf:
            str = "{0}-->{1}:   support:{2:.2}, conf:{3:.2}".format(set(freqSet-conSeq), set(conSeq), support[freqSet], conf)
            f[0].write(str+'\n')  # 该强关联规则写入文件
            ruleList.append((freqSet-conSeq, conSeq, conf))  # 该强关联规则加入最后的结果集


def rulesFromConseq(freqSet, support, h1, min_conf, ruleList, *f):
    """
    循环
    1.  计算 freqSet - h1[i] --> h1[i] 的置信度
    2.  h1进行笛卡尔乘积连接 成为新的h1
    3.  h1不为空 and  len(h1[i]) < freqSet
            返回 1.
        否则
            退出循环
    :param freqSet: 
    :param support: 
    :param h1: 
    :param min_conf: 
    :param ruleList: 存储所有的 强关联规则 
    :param f: 写入文件路径
    :return: 
    """
    while len(h1[0]) < len(freqSet):
        caculConf(freqSet, support, h1, min_conf, ruleList, f[0])
        # 以下对h1进行连接
        c = list(map(tuple, h1))  
        h1 = [i for i in map(frozenset, decartes(c, c))]
        if len(h1) == 0:
            break

猜你喜欢

转载自blog.csdn.net/weixin_40978095/article/details/83272056