Python 如何使apriori算法更快? TLDR问题

Python 如何使apriori算法更快? TLDR问题,python,algorithm,data-structures,apriori,Python,Algorithm,Data Structures,Apriori,这是一个很长的问题,因此这里有一个TLDR版本: 我正在实现apriori算法,速度很慢。缓慢的部分是当我试图生成Lk表单Ck时,它必须扫描整个数据集(或多或少),以确定候选对象是否频繁。我怎样才能使这部分更快?是否存在加速的数据结构 这是通过数据集进行的重复搜索吗 长问题 我的任务是用python编写apriori算法。约束条件是不使用pandas,也不使用任何实现类似apyori的aperiori的模块。因此,生成C1和L1根本不是问题。从Lk-1生成Ck也可以。整个过程的瓶颈是从Ck生成L

这是一个很长的问题,因此这里有一个TLDR版本: 我正在实现apriori算法,速度很慢。缓慢的部分是当我试图生成
Lk
表单
Ck
时,它必须扫描整个数据集(或多或少),以确定候选对象是否频繁。我怎样才能使这部分更快?是否存在加速的数据结构 这是通过数据集进行的重复搜索吗

长问题 我的任务是用python编写apriori算法。约束条件是不使用
pandas
,也不使用任何实现类似
apyori
的aperiori的模块。因此,生成
C1
L1
根本不是问题。从
Lk-1
生成
Ck
也可以。整个过程的瓶颈是从
Ck
生成
Lk
。本节将每个候选项与整个数据集进行比较,该数据集需要较长的时间才能获得最小的_支持

我花了一些时间寻找apriori的改进版本,在我能理解的版本中,这是最好的(IMO):

本文提供了为每个项目保留一个事务列表,指出该项目/项目集出现在哪些事务中(我们称之为
found\u in
)。有了它,我们可以在从
Ck
生成
Lk
时减少搜索次数,因为我们只能扫描该列表中提到的元素(
中找到)

我实现了它,它减少了4倍左右的时间,这是惊人的。然而,由于我要提取40000个频繁模式,所以它的速度还不够快

所以我想也许这个算法很好,但是我使用的python数据结构太慢了,赶不上。下面是我的问题:

  • 我可以通过使用更好的数据结构使这个算法更快吗?可能是
    ctype
    中的什么
  • 我的代码有什么问题使它挂起吗?这个算法的结果在我看来很合理(与
    apyori
    的输出相比)
  • 有什么建议可以改善它或改善某些情况吗
  • 我知道这个问题需要花费大量的时间来正确地调查,我并不期待。因此,任何小小费都很感激

    代码中速度较慢的部分:

    def gen_Lk(Ck: dict, dataset: list, min_support_count: int) -> dict:
        subset_time = 0
        Lk = {}
        for candidate, TIDs in Ck.items():
            if len(TIDs) < min_support_count:
                continue
            new_TIDs = set()
            tt = time()
            for TID in TIDs:
                comp_transaction = dataset[TID]
                # equivalent to: if candidate.issubset(dataset[TID])
                # this is the slowest part of the code and this is how to make it a
                # bit faster
                if not any(item not in comp_transaction for item in candidate):
                    new_TIDs.add(TID)
            if len(new_TIDs) < min_support_count:
                continue
            Lk[candidate] = new_TIDs
        return Lk
    
    def gen_Lk(Ck:dict,dataset:list,min\u support\u count:int)->dict:
    时间=0
    Lk={}
    对于候选人,Ck.items()中的TID为:
    如果长度(TID)<最小支持计数:
    持续
    new_TIDs=set()
    tt=时间()
    对于TID中的TID:
    comp_事务=数据集[TID]
    #等效于:if candidate.issubset(数据集[TID])
    #这是代码中最慢的部分,这是如何使其成为
    #快一点
    如果没有(候选项中的项目不在comp_事务中):
    新增(TID)
    如果len(新的TID)
    整个代码(很抱歉注释不好):

    来自itertools导入组合的
    
    进口泡菜
    从时间导入时间
    def具有\u不频繁\u子集(候选项:集合,上一个\u L:列表)->bool:
    """
    对某些候选对象的函数
    参数
    ----------
    候选者——检查其所有子集是否频繁的集合。
    如果任何子集不频繁,函数将返回True,
    否则返回False。
    previous_L——检查候选子集的元组列表。
    在“示例”部分可以找到以前的例子。
    退换商品
    -------
    布尔值。True表示候选中有一些子集
    与前一个_L相比不频繁,且该值不应为
    包含在最终Ck结果中。False表示所有子集都是频繁的且
    我们将把这个候选人包括在我们的Ck结果中。
    例子
    --------
    >>>以前的_L=[(1,2,4)、(2,3,6)、(2,3,8)、(2,6,7)、(2,6,8)、(3,4,5)、(3,6,8)]
    >>>具有不频繁的子集((2,3,6,8),以前的子集)
    假的
    >>>具有不频繁的子集((2,3,6,7),以前的子集)
    真的
    """
    子集=组合(候选,len(候选)-1)
    对于子集中的子集:#子集是元组
    如果子集不在上一个目录中:
    返回真值
    返回错误
    def apriori_gen(上一个:dict)->dict:
    """
    函数生成关于Lk-1(前一个)的候选项。尝试
    在has_unfrequent_subset()的帮助下,为每个新的
    如果其所有长度为k-1的子集都不是
    在Lk-1(上一个)中频繁出现,它不会添加到结果中。
    """
    Ck={}
    对于项目1,上一个项目()中的TIDs1:
    对于项目2,上一个项目()中的TIDs2:
    如果项目1[:-1]==项目2[:-1]和项目1[-1]<项目2[-1]:
    新项目=元组([*item\u 1,item\u 2[-1]])
    如果有\u不频繁的\u子集(新的\u项,以前的\u L):
    持续
    如果len(TIDs1)dict:
    """
    从给定数据集中生成与最小支持计数有关的L1项集
    参数
    ----------
    数据集——列表的列表。每个内部列表表示一个
    其内容是在该交易中购买的物品。外部列表是
    包含所有事务的数据集。
    min_support_count——一个整数,用于检查一个项目是否正确
    频繁与否。
    退换商品
    -------
    具有表示L1频繁项、词源和值的键的字典
    表示该项出现在中的事务。值为集合。
    如本文所示,这些值将在以后有用:
    https://arxiv.org/pdf/1403.3948.pdf
    
    from itertools import combinations
    import pickle
    from time import time
    
    
    def has_infrequent_subset(candidate: set, previous_L: list) -> bool:
        """
        A function to prone some of candidates
    
        Parameters
        ----------
        candidate -- a set to check whether all of its subsets are frequent or not.
            if any subset is not frequent, the function will returns True,
            otherwise returns False.
        previous_L -- a list of tuples to check candidate's subsets against it.
            an instance of previous_L could be found in 'Examples' part.
    
        Returns
        -------
        a boolean value. True means there are some subsets in the candidate that
        are not frequent with respect to previous_L and this value should no be
        included in the final Ck result. False means all subsets are frequent and
        we shall include this candidate in our Ck result.
    
        Examples
        --------
        >>> previous_L = [(1,2,4),(2,3,6),(2,3,8),(2,6,7),(2,6,8),(3,4,5),(3,6,8)]
        >>> has_infrequent_subset((2,3,6,8), previous_L)
        False
        >>> has_infrequent_subset((2,3,6,7), previous_L)
        True
        """
        subsets = combinations(candidate, len(candidate)-1)
        for subset in subsets:  # subset is a tuple
            if subset not in previous_L:
                return True
        return False
    
    
    def apriori_gen(previous_L: dict) -> dict:
        """
        A function generate candidates with respect to Lk-1 (previous_L). tries
        prone the results with the help of has_infrequent_subset(). for every new
        candidate found, if all of its subsets with the length of k-1 are not
        frequent in Lk-1 (previous_L), it will not be added to the result.
        """
        Ck = {}
        for item_1, TIDs1 in previous_L.items():
            for item_2, TIDs2 in previous_L.items():
                if item_1[:-1] == item_2[:-1] and item_1[-1] < item_2[-1]:
                    new_item = tuple([*item_1, item_2[-1]])
                    if has_infrequent_subset(new_item, previous_L):
                        continue
                    new_TIDs = TIDs1 if len(TIDs1) < len(TIDs2) else TIDs2
                    Ck[new_item] = new_TIDs
        return Ck
    
    
    def generate_L1(dataset: list, min_support_count: int) -> dict:
        """
        Generates L1 itemset from given dataset with respect to min_support_count
    
        Parameters
        ----------
        dataset -- a list of lists. each inner list represent a transaction which
            its content are items bought in that transacton. the outer list is the
            dataset which contain all transactions.
        min_support_count -- an integer which is used to check whether one item is
            frequent or not.
    
        Returns
        -------
        a dictionary with keys representing L1 frequent items fount and values
        representing what transactions that item appeared in. the values are sets.
        the values will be useful later as this paper demonstrates:
        https://arxiv.org/pdf/1403.3948.pdf
    
        Examples
        --------
        >>> generate_L1([[1,2,3], [3,4,1], [3,4,5]], 3)
        {(3,): {0, 1, 2}}
        >>> generate_L1([[1,2,3], [3,4,1], [3,4,5]], 2)
        {(1,): {0, 1}, (3,): {0, 1, 2}, (4,): {1, 2}}
        """
        L1 = {}
        for TID, transaction in enumerate(dataset):
            for item in transaction:
                if (item,) not in L1:
                    L1[(item,)] = set()
                L1[(item,)].add(TID)
        return {item: TIDs for item, TIDs in L1.items()
                if len(TIDs) >= min_support_count}
    
    
    def gen_Lk(Ck: dict, dataset: list, min_support_count: int) -> dict:
        st = time()
        Lk = {}
        for candidate, TIDs in Ck.items():
            if len(TIDs) < min_support_count:
                continue
            new_TIDs = set()
            tt = time()
            for TID in TIDs:
                comp_transaction = dataset[TID]
                # equivalent to: if candidate.issubset(dataset[TID])
                # this is the slowest part of the code and this is how to make it a
                # bit faster
                if not any(item not in comp_transaction for item in candidate):
                    new_TIDs.add(TID)
            if len(new_TIDs) < min_support_count:
                continue
            Lk[candidate] = new_TIDs
        return Lk
    
    
    def apriori(min_support_count: int, dataset: list):
        st = time()
        L1 = generate_L1(dataset, min_support_count)
        L = {1: L1}
        for k in range(2, 1000):
            if len(L[k-1]) < 2:
                break
            Ck = apriori_gen(L[k-1])
            L[k] = gen_Lk(Ck, dataset, min_support_count)
        return L
    
    
    if __name__ == '__main__':
        with open(paths.q1.listed_ds, 'rb') as f:
            dataset = pickle.load(f)
        L = apriori(len(dataset)*0.03, dataset)
    
        result = []
        for _, Lk in L.items():
            result.extend(Lk.keys())
        print(result, len(result))
    
    new_TIDs = TIDs1.intersection(TIDs2)
    
    def gen_Lk(Ck: dict, dataset: list, min_support_count: int) -> dict:
        Lk = {}
        for candidate, newTIDs in Ck.items():
            if len(newTIDs) < min_support_count:
                continue
            Lk[candidate] = new_TIDs
        return Lk