开源软件学习之Crab

来源:互联网 发布:方文山 知乎 编辑:程序博客网 时间:2024/04/27 18:32
最新实现了协同过滤的简单代码,正好知道有个用python写的开源项目,就下载下来对比学习一下。

Crab的官网: http://muricoca.github.io/crab/

一、安装
前一篇博文:
http://blog.csdn.net/joyce0625/article/details/42340831


二、使用
我打算先走通一条线,先以user-based recommender为例,item_based也是差不多的


三、结构

官网上也有个结构说明,我觉得我这个更清楚一些(同样以user-based 为例)



四、优化的地方
总的来说,和原来直接用简单的python写出来的协同过滤代码比较起来,Crab里面主要就是用了Numpy和Scipy,把原来很多的for循环都变成了直接的数组运算,提高了效率。

1. 使用Numpy的数组类,而不是dict
MatrixPreferenceDataModel/MatrixBooleanPrefDataModel 类把原来无序的字典转化成了一个规范的矩阵。


原来每位用户评论的item的顺序不同;或者说评论过某个item的用户顺序也不同,如图一所示,每次计算similarity都需要通过两个for循环去不同用户共同评论过的items或者评论过items的相同users;而现在
通过取出所有的user_id和item_id,并对他们分别sort(),再把rating值放到相应的位置,没有rating的地方就用NaN填充,变成了一个规范的矩阵,如图二所示。

图一:按字典方式存放的数据


图二:按矩阵方式存放的数据形式


2.最费时的相似度计算方法:
(1)计算前还是需要找common ratings,不再用两次for 循环来找,二是使用了numpy.intersect1d()  

其中 intersectId的源码 
def intersect1d(ar1, ar2, assume_unique=False):
    if not assume_unique:
        ar1 = unique(ar1)
        ar2 = unique(ar2)
    aux = np.concatenate((ar1, ar2))
    aux.sort()
    return aux[:-1][aux[1:] == aux[:-1]]  #aux[:-1]到最后一个之前的内容

#这段找共同值的方法还是挺有技巧的,把要比较的两个list(A,B)数据先unique(),然后串联起来(C)排序,那些两个list里面都有的数就会被排在一起
#串联排序后的数据,做错位的与运算,就是一个切掉C的第一个,得到C', 一个切掉C的最后一个,得到C'',list C'和list C''做与运算,因为前面sort()后相同的数肯定是前后连在一起的,所以相同的数所在的位肯定有一个是True

(2) 相似度原来直接用* 来完成计算,现在
使用Scipy中 scipy.spatial.distance子模块中的.cdist(X, Y, 'euclidean'), .cdist(X, Y, 'sqeuclidean') 和cdist(X, Y, 'correlation', 2)方法来计算。
通过查看cdist的源码可以看到它又用了dist = norm(u - v) numpy.linalg.norm(xord=Noneaxis=None)来完成平方再开根号的计算;
计算euclidean:

如果只要平方和,可以用numpy.dot(), 比如计算correlation: 


【PS: cdist里面有各种超赞的常用距离计算公式,比如cosine, euclidean(2维的),jaccard,matching(boolean vectors)等等】

3.data models(values and boolean) 和 similarity(item-based, user_based), recommender都是先写一个超类,再用子类继承的方式

4.recommender
(1)找到neighbors(score > minimal_similarity)评价过但是user自己还没有评价过的item
同样地,不需要用for循环,而直接采用nump.setdiff1d()方法就可以了


(2)找到top_match items

5.评估结果
(1) cross validation
*对于一般的KFold
利用了一个生成器,来进行划分


*对于shuffleSplit, 先打乱原始数据,然后再划分的方法(由于这种方法每次都会重排一下,并不能保证每次划分得到的训练集都是不同的)
采用的方法是
①用方法sklearn.utils.check_random_state(seed) 返回一个np.random.RandomState 实例
②使用RandomState.permutation(x) 返回一个打散的array,代码如下:


其实可以直接用包装好的方法sklearn.cross_validation.train_test_split(*arrays, **options),这样更简洁。


(2)metric
在计算之前用check_arrays(real, pred)检查下两个array的第一维是不是一样的(from ..utils import check_arrays, unique_labels)


五:改进原来的代码
其中我发现在_top_matches为user 预估preference的时候,并没有办法保证某个neighbor 的preference一定是乘以他和user的similarity,看我下面举的例子:

我把代码里
        prefs = prefs[~np.isnan(prefs)]
        similarities = similarities[~np.isnan(prefs)]

        prefs_sim = np.sum(prefs[~np.isnan(similarities)] *
                             similarities[~np.isnan(similarities)])
        total_similarity = np.sum(similarities)
改成了
        temp_prefs = [~np.isnan(prefs)]
        temp_similarities = [~np.isnan(similarities)]
        noNaN_indices = np.logical_and(temp_prefs, temp_similarities)
        
        prefs_sim = np.sum(prefs[noNaN_indices[0] == True] *
                             similarities[noNaN_indices[0] == True])
                             
        similarities = similarities[~np.isnan(similarities)]
        total_similarity = np.sum(similarities)

还是同样的例子,就可以得到保证preference和similarity一定是一一对应的。


第二种得到指定indice的array的方法


不知道我理解的有没有错,已经把修改的内容提交给作者了,等待他的回复吧。

0 0
原创粉丝点击