基于列表理解的功能

时间:2018-08-03 06:59:23

标签: python pandas list-comprehension

我正在尝试为每个用户获得15个最相关的项目,但是我尝试的每个功能都花了很长时间。 (超过6小时后,我关闭了它……)

我有418个唯一用户,3718个唯一项。 U2tfifd dict也有418个条目,并且tfidf_feature_names中有32645个单词。 我的interacts_full_df的形状为(40733,3)

我尝试过:

  def index_tfidf_users(user_id) : 
    return [users for users in U2tfifd[user_id].flatten().tolist()]

def get_relevant_items(user_id):
    return sorted(zip(tfidf_feature_names, index_tfidf_users(user_id)), key=lambda x: -x[1])[:15]

def get_tfidf_token(user_id) : 
    return [words for words, values in get_relevant_items(user_id)]

然后interactions_full_df["tags"] = interactions_full_df["user_id"].apply(lambda x : get_tfidf_token(x))

def get_tfidf_token(user_id) : 
    tags =  []
    v = sorted(zip(tfidf_feature_names, U2tfifd[user_id].flatten().tolist()), key=lambda x: -x[1])[:15]
    for words, values in v :
        tags.append(words)
    return tags

def get_tfidf_token(user_id) : 
    v = sorted(zip(tfidf_feature_names, U2tfifd[user_id].flatten().tolist()), key=lambda x: -x[1])[:15]
       tags =  [words for words in v]
    return tags

U2tfifd是具有键= user_id,值=数组的字典

1 个答案:

答案 0 :(得分:1)

有几件事会导致 导致代码性能下降。这些因素的影响取决于您的Python版本(2.x或3.x),RAM速度以及诸如此类。您需要自己进行实验并确定各种潜在改进的基准。

1。 TFIDF稀疏性(根据稀疏性,速度提高约10倍)

一个明显的潜在问题是,TFIDF自然会返回稀疏数据(例如,一个段落在整个书中使用的唯一词都不多),而当数据为空时,使用诸如numpy数组之类的密集结构是一个奇怪的选择。几乎到处都是零。

如果将来要进行相同的分析,则制作/使用具有稀疏数组输出的TFIDF版本可能会有所帮助,以便在提取令牌时可以跳过零值。对于缓存中的每个用户而言,这可能会带来整个稀疏数组的次要好处,并防止您进行排序和其他操作时进行昂贵的RAM访问。

无论如何,值得保存数据。在我的马铃薯上,对应该与您的数据相似的数据进行快速基准测试表明,该过程可以在大约30秒内完成。该过程用高度优化的例程(用C编码并包装为在Python中使用)代替了您正在做的许多工作。唯一的实际成本是第二次通过非零条目,但是除非首先通过该过程非常有效,否则最好使用稀疏数据。

2。重复的努力和记忆力(约100倍加速)

如果U2tfifd有418个条目,而interactions_full_df有40733行,那么由于您已经计算出答案,因此至少浪费了40315(或99.0%)您对get_tfidf_token()的呼叫。那里有大量的备忘录装饰器,但是您并不需要任何非常复杂的用例。

def memoize(f):
    _cache = {}
    def _f(arg):
        if arg not in _cache:
            _cache[arg] = f(arg)
        return _cache[arg]
    return _f

@memoize
def get_tfidf_token(user_id):
    ...

打破这一点,函数memoize()返回了另一个函数。该函数的行为是在计算预期的返回值之前检查本地缓存,并在必要时进行存储。

@memoize...的语法类似于以下内容。

def uncached_get_tfidf_token(user_id):
    ...
get_tfidf_token = memoize(uncached_get_tfidf_token)

@符号用于表示我们想要get_tfidf_token()的修改版本或修饰的版本,而不是原始版本。根据您的应用程序,将装饰器链接在一起可能是有益的。

3。向量化操作(必须提高速度,进行基准测试)

Python并没有像其他语言那样真正具有 primitive 类型的概念,甚至整数在我的计算机上占用24个字节。列表通常不打包,因此您在浏览列表时可能会导致代价高昂的缓存未命中。不管CPU进行排序工作有多少工作,什么都没做,破坏整个全新的内存块以将您的阵列变成一个列表,而仅使用该全新的昂贵内存一次就会导致性能下降。

您尝试做的许多事情都具有快速的(SIMD矢量化,并行化,高效内存,压缩内存和其他有趣的优化)numpy等效项,并避免了不必要的数组副本和类型转换。看来您已经在使用numpy了,因此不会有任何额外的导入或依赖项。

作为一个示例,zip()在Python 2.x中的内存中创建了另一个列表,而当您只关心{{的 indices 1}}。要计算这些索引,可以使用类似于以下内容的方法,它避免了不必要的列表创建,并使用渐进复杂性稍强的优化例程作为补充。

tfidf_feature_names

根据def get_tfidf_token(user_id): temp = U2tfifd[user_id].flatten() ind = np.argpartition(temp, len(temp)-15)[-15:] return tfidf_feature_names[ind] # works if tfidf_feature_names is a numpy array return [tfidf_feature_names[i] for i in ind] # always works 的形状,您可以通过向U2tfifd[user_id]传递.flatten()参数并展平15个获得的索引来避免进行昂贵的axis计算。

4。奖金

np.argsort()函数支持一个sorted()参数,这样您就可以避免多余的计算,例如对每个值都抛出负数。只需使用

reverse

更好,因为您实际上并不在乎排序本身,而只是在乎15个最大的值

sorted(..., reverse=True)

为最大的15个索引,而不是反转排序并取最小的15个索引。如果您正在为sorted(...)[-15:] 之类的应用程序使用更好的功能,那并不重要,但是在未来。

您还可以通过将np.argpartition()替换为.apply(lambda x : get_tfidf_token(x))来避免某些函数调用,因为.apply(get_tfidf_token)已经是具有预期行为的函数。您真的不需要多余的get_tfidf_token

据我所知,大多数其他收益都是相当挑剔的,并且取决于系统。例如,您可以使用Cython或直C语言以足够的时间使大多数事情变得更快,但是您已经有了相当快的例程,这些例程可以立即执行您想要的操作。付出额外的工程努力可能不值得任何潜在的收获。