Python:优化函数以查找给定候选项集的大小为 k 的频繁项集

Posted

技术标签:

【中文标题】Python:优化函数以查找给定候选项集的大小为 k 的频繁项集【英文标题】:Python: Optimise the function to find frequent itemsets of size k given candidate itemsets 【发布时间】:2021-02-25 16:14:31 【问题描述】:

我已经编写了一个函数来查找给定候选项目集大小为 k 的项目集的频率。数据集包含超过 16000 个事务。有人可以帮我优化这个功能吗?因为在当前形式下执行 minSupport=1 大约需要 45 分钟。

样本数据集

【问题讨论】:

你能分享print(dataSet.head(1))的结果吗? @abysslover 在更新中添加。请看一看。 一个 small 优化:您正在使用 all 函数并使用您通过列表理解构建的列表调用它。但是你不需要建立一个列表。生成器表达式也可以正常工作,并且不会浪费时间和内存来实际创建列表。 要实现这一点,您只需从使用all 的行中删除方括号。 您告诉过您有 16000 笔交易,这些交易在 45 分钟内处理完毕。你能告诉我在这 45 分钟的跑步中你使用了多少 Ck 里面的元素吗? Ck 的每个元素也是一个元组,你能说出这个元组在你 45 分钟的跑步中的平均长度是多少吗?还有交易中有多少列,即transactions.values.shape[1]的值是多少? 【参考方案1】:

算法 0(请参阅下面的其他算法)

使用Numba 实现算法的增强。 Numba 是一个JIT 编译器,可将 Python 代码转换为高度优化的 C++ 代码,然后编译为机器代码。对于许多算法,Numba 实现了 50-200 倍的速度提升。

要使用 numba,您必须通过 pip install numba 安装它,注意 Numba 仅支持 Python

我已经稍微重写了你的代码以满足 Numba 编译要求,我的代码在行为上应该与你的相同,请做一些测试。

我的 numba 优化代码应该会给你很好的加速!

我也创建了一些人工的简短示例输入数据,以进行测试。

Try it online!

import numba, numpy as np, pandas as pd

@numba.njit(cache = True)
def selectLkNm(dataSet,Ck,minSupport):
    dict_data = 
    transactions = dataSet.shape[0]
    for items in Ck:
        count = 0
        while count < transactions:
            if items not in dict_data:
                dict_data[items] = 0
            for item in items:
                for e in dataSet[count, :]:
                    if item == e:
                        break
                else:
                    break
            else:
                dict_data[items] += 1
            count += 1
    Lk = 
    for k, v in dict_data.items():
        if v >= minSupport:
            Lk[k] = v
    return Lk
    
def selectLk(dataSet, Ck, minSupport):
    tCk = numba.typed.List()
    for e in Ck:
        tCk.append(e)
    return selectLkNm(dataSet.values, tCk, minSupport)

dataset = pd.DataFrame([[100,160,100,160],[170,180,190,200],[100,160,190,200]])
C1 = set()
C1.add((100, 160))
C1.add((170, 180))
C1.add((190, 200))
Lk = selectLk(dataset, C1, 2)
print(Lk)

输出:

(100, 160): 2, (190, 200): 2

算法 1(请参阅下面的其他算法)

我通过对数据进行排序改进了算法 0(上图),如果 Ck 中有很多值或者 Ck 中的每个元组都很长,它会提供很好的加速。

Try it online!

import numba, numpy as np, pandas as pd

@numba.njit(cache = True)
def selectLkNm(dataSet,Ck,minSupport):
    assert dataSet.ndim == 2
    dataSet2 = np.empty_like(dataSet)
    for i in range(dataSet.shape[0]):
        dataSet2[i] = np.sort(dataSet[i])
    dataSet = dataSet2
    dict_data = 
    transactions = dataSet.shape[0]
    for items in Ck:
        count = 0
        while count < transactions:
            if items not in dict_data:
                dict_data[items] = 0
            for item in items:
                ix = np.searchsorted(dataSet[count, :], item)
                if not (ix < dataSet.shape[1] and dataSet[count, ix] == item):
                    break
            else:
                dict_data[items] += 1
            count += 1
    Lk = 
    for k, v in dict_data.items():
        if v >= minSupport:
            Lk[k] = v
    return Lk
    
def selectLk(dataSet, Ck, minSupport):
    tCk = numba.typed.List()
    for e in Ck:
        tCk.append(e)
    return selectLkNm(dataSet.values, tCk, minSupport)

dataset = pd.DataFrame([[100,160,100,160],[170,180,190,200],[100,160,190,200]])
C1 = set()
C1.add((100, 160))
C1.add((170, 180))
C1.add((190, 200))
Lk = selectLk(dataset, C1, 2)
print(Lk)

输出:

(100, 160): 2, (190, 200): 2

算法 2(请参阅下面的其他算法)

如果您不允许使用 Numba,那么我建议您对算法进行下一步改进。我对您的数据集进行了预排序,以便不是在 O(N) 时间而是在 O(Log(N)) 时间搜索每个项目,这要快得多。

我在你的代码中看到你使用了 pandas 数据框,这意味着你已经安装了 pandas,如果你安装了 pandas 那么你肯定有 Numpy,所以我决定使用它。如果您正在处理 pandas 数据框,则不能没有 Numpy。

Try it online!

import numpy as np, pandas as pd, collections

def selectLk(dataSet,Ck,minSupport):
    dataSet = np.sort(dataSet.values, axis = 1)
    dict_data = collections.defaultdict(int)
    transactions = dataSet.shape[0]
    for items in Ck:
        count = 0
        while count < transactions:
            for item in items:
                ix = np.searchsorted(dataSet[count, :], item)
                if not (ix < dataSet.shape[1] and dataSet[count, ix] == item):
                    break
            else:
                dict_data[items] += 1
            count += 1
    Lk = k : v for k, v in dict_data.items() if v >= minSupport
    return Lk
    
dataset = pd.DataFrame([[100,160,100,160],[170,180,190,200],[100,160,190,200]])
C1 = set()
C1.add((100, 160))
C1.add((170, 180))
C1.add((190, 200))
Lk = selectLk(dataset, C1, 2)
print(Lk)

输出:

(100, 160): 2, (190, 200): 2

算法 3

我只是想到算法 2 的排序部分可能不是瓶颈,可能事务 while 循环可能是瓶颈。

所以为了改善情况,我决定实现并使用更快的算法和 2D searchsorted 版本(没有内置的 2D 版本,所以它必须单独实现),它没有任何长的纯 python 循环,大部分时间都花在了 Numpy 函数上。

请尝试这个算法 3 是否会更快,如果不是排序是瓶颈而是内部 while 循环,它应该会更快。

Try it online!

import numpy as np, pandas as pd, collections

def selectLk(dataSet, Ck, minSupport):
    def searchsorted2d(a, bs):
        s = np.r_[0, (np.maximum(a.max(1) - a.min(1) + 1, bs.ravel().max(0)) + 1).cumsum()[:-1]]
        a_scaled = (a + s[:, None]).ravel()
        def sub(b):
            b_scaled = b + s
            return np.searchsorted(a_scaled, b_scaled) - np.arange(len(s)) * a.shape[1]
        return sub

    assert dataSet.values.ndim == 2, dataSet.values.ndim
    dataSet = np.sort(dataSet.values, axis = 1)
    dict_data = collections.defaultdict(int)
    transactions = dataSet.shape[0]
    Ck = np.array(list(Ck))
    assert Ck.ndim == 2, Ck.ndim
    ss = searchsorted2d(dataSet, Ck)
    for items in Ck:
        cnts = np.zeros((dataSet.shape[0],), dtype = np.int64)
        for item in items:
            bs = item.repeat(dataSet.shape[0])
            ixs = np.minimum(ss(bs), dataSet.shape[1] - 1)
            cnts[...] += (dataSet[(np.arange(dataSet.shape[0]), ixs)] == bs).astype(np.uint8)
        dict_data[tuple(items)] += int((cnts == len(items)).sum())
    return k : v for k, v in dict_data.items() if v >= minSupport
    
dataset = pd.DataFrame([[100,160,100,160],[170,180,190,200],[100,160,190,200]])
C1 = set()
C1.add((100, 160))
C1.add((170, 180))
C1.add((190, 200))
Lk = selectLk(dataset, C1, 2)
print(Lk)

输出:

(100, 160): 2, (190, 200): 2

【讨论】:

非常感谢。不幸的是,除了我使用的 dict 之外,我不能使用 Numba 或其他外部库。如果您可以通过这些限制更改现有功能,那将有很大帮助。 @Ashar 当您使用熊猫数据框时,我也可以使用 Numpy 吗?它总是在 pandas 所在的地方,所以肯定应该在你 pandas 数据框的地方安装 numpy,并且根据你的代码,你肯定有 pandas! 是的,Numpy 也是允许的。 @Ashar 请参阅我的回答中的 算法 2,刚刚更新。它不使用 Numba,只使用 Numpy。我在使用之前对您的数据进行排序。它将大大提高搜索速度。因为可以在O(Log(N)) 时间而不是O(N) 中搜索排序数组,这要快得多。 @Ashar 如果您要在一个脚本中多次运行此算法,请不要忘记重用已排序的数据集。然后第二次运行将非常快,因为它不需要排序。【参考方案2】:

我已更改您的代码的执行顺序。但是,由于我无法访问您的实际输入数据,因此很难检查优化后的代码是否产生预期的输出以及您获得了多少速度。

算法0

import pandas as pd
import numpy as np
from collections import defaultdict

def selectLk(dataSet,Ck,minSupport):
    dict_data = defaultdict(int)
    for _, row in dataSet.iterrows():
        for items in Ck:
            dict_data[items] += all(item in row.values for item in items)
    Lk =  k : v for k,v in dict_data.items() if v > minSupport
    return Lk

if __name__ == '__main__':
    data = list(range(0, 1000, 10))
    df_data = 
    for i in range(26):
        sample = np.random.choice(data, size=16000, replace=True)
        df_data[f"di"] = sample
    dataset = pd.DataFrame(df_data)
    C1 = set()
    C1.add((100, 160))
    C1.add((170, 180))
    C1.add((190, 200))
    Lk1 = selectLk(dataset, C1, 1)
    dataset = pd.DataFrame([[100,160,100,160],[170,180,190,200],[100,160,190,200]])
    Lk2 = selectLk(dataset, C1, 1)
    print(Lk1)
    print(Lk2)

算法 1

算法 1 使用numpy.equal.outer,它为 Ck 元组中的任何匹配元素创建一个布尔掩码。然后,应用.all() 操作。

def selectLk(dataSet, Ck, minSupport):
    dict_data = defaultdict(int)
    dataSet_np = dataSet.to_numpy(copy=False)
    for items in Ck:
        dict_data[items] = dataSet[np.equal.outer(dataSet_np, items).any(axis=1).all(axis=1)].shape[0]
    Lk =  k : v for k, v in dict_data.items() if v > minSupport
    return Lk

结果:

(190, 200): 811, (170, 180): 797, (100, 160): 798
(190, 200): 2, (100, 160): 2

【讨论】:

谢谢。我的执行时间从 45 分钟大幅缩短到 10 分钟! @Ashar 如果您使用算法 1,您将获得更高的性能。我的环境速度提高了大约 100 倍。 算法 - 1 非常快。花了我不到一分钟!!! @Ashar 如果您能接受我的回答,如果它解决了您的问题,我将不胜感激。 Yours 和 @Arty 两种算法都解决了我的问题。很抱歉,很遗憾,我无法将两者都标记为已解决,所以我对你的投了赞成票。

以上是关于Python:优化函数以查找给定候选项集的大小为 k 的频繁项集的主要内容,如果未能解决你的问题,请参考以下文章

给定数据框中项集的计数频率

FP增长算法

python实现apriori算法的关联规则之支持度置信度提升度

python实现apriori算法的关联规则之支持度置信度提升度

关联分析中寻找频繁项集的FP-growth方法

FP-Growth算法之频繁项集的挖掘(python)