我正在尝试为每个用户获得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,值=数组的字典
答案 0 :(得分:1)
有几件事会导致 导致代码性能下降。这些因素的影响取决于您的Python版本(2.x或3.x),RAM速度以及诸如此类。您需要自己进行实验并确定各种潜在改进的基准。
一个明显的潜在问题是,TFIDF自然会返回稀疏数据(例如,一个段落在整个书中使用的唯一词都不多),而当数据为空时,使用诸如numpy数组之类的密集结构是一个奇怪的选择。几乎到处都是零。
如果将来要进行相同的分析,则制作/使用具有稀疏数组输出的TFIDF版本可能会有所帮助,以便在提取令牌时可以跳过零值。对于缓存中的每个用户而言,这可能会带来整个稀疏数组的次要好处,并防止您进行排序和其他操作时进行昂贵的RAM访问。
无论如何,值得保存数据。在我的马铃薯上,对应该与您的数据相似的数据进行快速基准测试表明,该过程可以在大约30秒内完成。该过程用高度优化的例程(用C编码并包装为在Python中使用)代替了您正在做的许多工作。唯一的实际成本是第二次通过非零条目,但是除非首先通过该过程非常有效,否则最好使用稀疏数据。
如果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()
的修改版本或修饰的版本,而不是原始版本。根据您的应用程序,将装饰器链接在一起可能是有益的。
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
计算。
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语言以足够的时间使大多数事情变得更快,但是您已经有了相当快的例程,这些例程可以立即执行您想要的操作。付出额外的工程努力可能不值得任何潜在的收获。