这段代码表现不佳,如何让它变得更好?

时间:2017-05-29 15:42:57

标签: python performance python-3.x numpy cython

我正在尝试为学校项目构建推荐算法。在某些时候,我有这个功能,这对性能至关重要。它返回与用户具有最高相似度的5个项目。我做了三种不同的方式。

(1),我认为它是最“pythonic”的,但是最慢的方法,完成大约需要800ms。

(2)虽然增加了排序操作,但是更加丑陋但却出乎意料地更快(600毫秒)。

然后,我就像在旧式C ++(3)中那样做,这是最快的,但不是很多(550ms):

import math
import numpy as np
import pickle
import bisect

shrink=10
return_itemcount=5
target_users_list=[1,3,4]
already_rated_set={ 1: {1,2}, 3: {1}, 4: {5}}
user_features_dict={1: np.array([5,6]), 3: np.array([6,7]), 4: np.array([11])}
item_features_dict={1: np.array([6,7]), 2: np.array([7,8]), 3: np.array([6,11])}
items_set=set(item_features_dict.keys())

#(1)
def get_item_similarfeatures1(user):
    res=[]
    for item in items_set-already_rated_set[user]:
        bisect.insort(res,[useritem_similarity(user,item),item])
    return [x[1] if x[0]>0 else -1 for x in res[(-return_itemcount-1):]]

#(2)
def get_item_similarfeatures2(user):
    res={}
    for item in items_set-already_rated_set[user]:
        sim=useritem_similarity(user,item)
        if sim>0:
            res[item]=sim
    res=sorted(res.items(), key=lambda x: x[1],reverse=True)[0:5]
    return list(map(lambda x: x[0],res))

#(3)
def get_item_similarfeatures3(user):
    res=[(-1,0)]*return_itemcount
    threshold=0
    for item in (items_set-already_rated_set[user]):
        sim=useritem_similarity(user,item)        
        if sim>threshold:
            for idx, _ in enumerate(res):
                if _[1]<sim: 
                    res.insert(idx,(item,sim))
                    del res[return_itemcount]
                    threshold=res[return_itemcount-1][1]
                    break
    return list(map(lambda x: x[0],res))

def useritem_similarity(user,item):
    if ( user not in user_features_dict): return 0
    features_user=user_features_dict[user]
    features_item=item_features_dict[item]
    return np.in1d(features_user,features_item).sum()/np.sqrt(features_user.size*features_item.size+shrink)


feature_recommendations_dict={ user : get_item_similarfeatures1(user) for user in target_users_list}
feature_recommendations_dict

我想了解的是:

  • 为什么(2)比(1)快?

编辑:评论中ali_m已对此进行了解答,我已根据gboffi的建议更新了代码:

def get_item_similarfeatures1(user):
    res=[[0,-1]]*return_itemcount
    for item in items_withfeatures_set-already_rated_set[user]:
        sim=useritem_similarity(user,item)
        if (sim>res[0][0] or res[return_itemcount-1][0]==0):
            bisect.insort(res,[sim,item])
            del res[0]
    return [x[1] if x[0]>0 else -1 for x in res[-return_itemcount-1:]]

但这仍然比(3)慢一些(580ms)。

  • 在cython中编译(3)或useritem_similarity是否值得?它会产生更好的表现吗?

2 个答案:

答案 0 :(得分:0)

我有一个建议,不能在评论的限制范围内制定。

我认为你过度了,你可以保持最好的5个结果,比如我已经构建的这个例子

In [1]: import bisect as bs

In [2]: from random import random, seed

In [3]: seed(8888)

In [4]: l = [random() for _ in range(100)]

In [5]: r = [0  for i in range(5)]

In [6]: for x in l:
   ...:     if x>r[0]:
   ...:         bs.insort(r, x)
   ...:         r.pop(0)
   ...:         

In [7]: print(r) ; print(sorted(l)[-5:])
[0.9734802621456982, 0.9764741228388244, 0.9806769811156987, 0.9820424903993659, 0.9884636482746705]
[0.9734802621456982, 0.9764741228388244, 0.9806769811156987, 0.9820424903993659, 0.9884636482746705]

In [8]: 

当然,您应该将这个简单的示例应用于您更复杂的用例,但避免移动大量数据(插入和/或排序)可能会有很大帮助。

另一种可能性是heapq module

In [1]: from random import random, seed
In [2]: from heapq import heappushpop, heapreplace
In [3]: seed(8888)
In [4]: l = [random() for _ in range(100)]
In [5]: r = [0  for i in range(5)]
In [6]: for x in l:
   ...:     if x>r[0]: heapreplace(r, x)
In [7]: r
Out[7]: 
[0.9734802621456982,
 0.9764741228388244,
 0.9806769811156987,
 0.9820424903993659,
 0.9884636482746705]
In [8]: 

继续heapq模块提供的可能性,

In [10]: from heapq import nlargest
In [11]: nlargest(5, l)
Out[11]: 
[0.9884636482746705,
 0.9820424903993659,
 0.9806769811156987,
 0.9764741228388244,
 0.9734802621456982]

(顺序颠倒过来)。

虽然heapreplace的时间略微,但始终优于insort时间,nlargest明显更快。

我没有引用时间因为你必须对你的数据的不同可能性进行基准测试,请记住,存储所有结果并最终排序的方法可能比上面的更快,具体取决于您正在处理的数据量,正如我在评论中提到的那样。

答案 1 :(得分:0)

由于您已经拥有多个执行所需功能的算法,因此您现在可以对它们进行基准测试/计时,从而找到性能最佳的算法。

然后,您将分析获胜代码以找出其瓶颈所在。

然后,您将使用我提到的here技术/工具来消除这些瓶颈。

我详细说明了上述每一项内容,包括在my talk at PyCon中查看每项工具。