Item-CF和User-CF算法训练过程优化的心得

〇、背景

数据集:MovieLens-1M(其中用户数6040,电影数3592,评分数1000000);

工具:Java、MySQL、Redis;

主要参考资料:《推荐系统实践》项亮;

 

一、Item-CF算法描述

  1.Item-CF算法思想

    为当前用户推荐——与当前用户感兴趣物品相似的 且 当前用户没有接触的物品

  2.实现步骤

    (1)计算物品之间的相似度;

    (2)根据物品相似度和用户的行为历史,作出推荐。

 

二、User-CF算法描述

  1.User-CF算法思想

    先找到目标用户的相似用户(即与他有相同兴趣的其他用户),然后把 相似用户喜欢的 且 目标用户未作出行为的 物品推荐给目标用户。

  2.实现步骤

    (1)计算用户兴趣的相似度;

    (2)找到与当前用户兴趣相似的用户集合,从该集合的历史物品列表中作出推荐。

 

三、遇到的问题

  基于物品/用户的协同过滤算法是实现推荐系统的基本算法,同时也算是学习推荐系统的入门。两个算法的思想简单明了,所涉及的知识点也比较容易理解,但实现过程中也会有一些坑,比如说——训练过程太耗时。

  以Item-CF为例,在算法实现过程中,我先计算物品之间的相似度,并将结果以set的数据结构暂存到Redis(一个基于内存的键值对数据库),方便使用。

  而Item-CF算法最耗时的地方是 计算用户对某物品兴趣度的过程,公式如下:

  

  其中Puj表示,用户u对物品j的兴趣度。N(u)表示用户u喜欢的物品集合,S(j,K)表示与物品j最相似的K个物品,这里取K=10。而N(u)∩S(j,K)表示与物品j相似,且用户u感兴趣的物品。wji表示物品j和物品i的相似度。rui表示用户u对物品i的兴趣。

   每个用户对每个物品都需要计算,其时间复杂度大概为O(U*I*10),U为用户数,I为物品数,10表示与当前物品最相似的10个物品。

 

四、重复步骤的优化

  这里的优化主要指的是,在MovieLens数据集下,计算用户对某电影兴趣度的过程优化。

  1.重复的步骤

  从下面的伪代码可以看出:当user=1 movie=1时,会从redis取与当前电影相似的10部电影;当user=2 movie=1时,又会取一遍!这样算下来,会有六千多次的多余,大大影响了效率。

1 for(int user=1;user<=6040;user++){
2      //从数据库中取当前用户的评分历史
3             
4      for(int movie=1;movie<=3952;movie++){
5              //从redis中取与当前电影相似的10部电影
6      }
7 
8  }

 

  2.优化方法:

  以空间换时间,在算法开始时就把所有电影的相似电影取出来当作Java静态变量待用。

  优点:避免了重复;缺点:占用了Java虚拟机的堆空间,使得垃圾回收更加频繁(当然,可以设置参数,加大堆内存解决)。

 

五、数据库查询的优化

  1.数据库的多次查询

  从下面的代码可以看出:6040个用户,每循环一次就要查询一次数据库,即对用户的查询需要六千多次。

  而一次查询一个用户的数据,反复查6040次,和一次直接查询6040个用户,时间差是非常大的。

1 for(int user=1;user<=6040;user++){
2    //从数据库中取当前用户的评分历史
3 
4 }

  

  2.优化方法

    通过一次查询多个用户数据而非一次查询一个用户,可以大大提高效率。

    本方法其实是使用到了SQL语句中的“in”关键字来一次性查询多个用户数据,例如:Select * from user id in 1,2,3....

    但该注意的地方是,若一次性查询太多个用户的数据,会造成Java虚拟机内存溢出(Out Of Memory异常),所以得在实践中找到最佳的可以一次性查询的数据数量。

 

六、计算过程优化

  这里的优化主要指的是,在MovieLens数据集下,计算用户对某电影兴趣度的过程优化。

  1.耗时

  即使通过上面的优化,但由于数据量大,且个人电脑的硬件限制等,耗时还是不可接受的(粗略计算超过12小时)。

 

  2.解决方法

  利用多线程,步骤如下:

  (1)方法切分

    将整个计算过程细分为若干个独立的计算单元,每个计算单元作一条线程。

    比如:将查询用户的评分历史作一条线程,将查询与本电影相似的电影作一条线程,将计算用户对某电影兴趣度作一条线程。

 

  (2)线程合作

    多线程的执行具有不可预知性,我们需要制约它们的前后顺序。我这里采用的是“读者-写者”的方法。

    比如:A线程查询用户后将结果传递给B线程;B线程收到A线程的数据后开始执行,并将本线程的结果传给C线程.....

    具体到Java中,可以使用BlockingQueue集合类来实现数据互斥地写入、读出。

 

  (3)效果

    用本方法优化后,可以达到一个"线程流水线"的效果,大大缩减算法训练时间,如图所示:

    

 

posted @ 2020-05-19 20:37  Drajun  阅读(1137)  评论(0编辑  收藏  举报