您的位置:首页 > 编程语言

基于用户(user-based)的协同过滤推荐算法的初步理解以及代码实现

2017-08-04 18:21 856 查看
总论

协同过滤是目前最经典的推荐算法。
分而理之,协同,指通过在线数据找到用户可能喜欢的物品;过滤,滤掉一些不值得推荐的数据。
协同过滤推荐分为三种类型。第一种是基于用户(user-based)的协同过滤,第二种是基于项目(item-based)的协同过滤,第三种是基于模型(model based)的协同过滤。
我认为,选择哪种类型,取决于业务场景。需要考虑的是,user和item的数量比,谁的数量级小就选择哪个模式的cf;在业务处于的场景中,user和item的多变性比较,选择多变性较小的一方,在后续的维护,会方便很多;最后一点,明确你的推荐覆盖要求。
今天主要讲基于用户。
凡是推荐算法离不开大数据的基础,基于用户类型的数据一般是如下格式:
用户id,外物id,用户对外物的评分,以上是一般物联网的算法模型,(举一反三,分析系统日志,建立如下模型,用户id,资源id,用户对资源的访问次数,--->实现推荐页面 )


算法基础实现

看这块的时候,我是真的后悔大学没好好学数学!!!
首先让我们拆分下过程。 基于用户的协同过滤 可分为2步,
1.找到相似度高的用户
2.根据1步骤获取的信息,推荐源用户相对喜欢,并且未采取过的行为


下面这个是大名鼎鼎的Jaccard公式,很简单, 取2个用户的选择集的交集,跟2个用户的选择集的并集,进行计算。但很显然,若要计算整个用户集合,
时间复杂度是O(n^2),太慢了。




后续就有了利用倒排查表进行优化如下: 可以建立个5X5矩阵,以用户、行为为维度。


行为     用户
1      1,2,3
2      2,3,5
3      1,3,5
4      3,4,5
5      2,3,4


到这里,已经可以获得相似用户了。

下面这个公式可以完成之前提到的步骤二,p(u,i)-用户u对行为i的权重,S(u,k)表示和用户u相似的K个用户,N(i)表示采取过行为i的用户集合,Wuv表示用户u和用户v的相似度,Rvi表示用户v对行为i的权重。
Rvi部分可以大做文章,用户对某个行为的权重值定义,需要以业务为基础。在这里举个例子,如果某个行为在整个用户群里执行的次数不多,但某两个用户多次执行,可判断这两个用户对该行为权重极大,也就是对该行为兴趣浓厚,也就是这两个用户极其得相似,我把这个现象称为,单热群冷现象。




算法数学总结到此结束,感觉数学功底是真的差,上面那个公式看了半天,后来还是咨询了大学里数学师父~~~


Mahout实现

Mahout是hadoop全家桶里的一员,提供一些可扩展的机器学习领域经典算法的实现,旨在帮助开发人员更加方便快捷地创建智能应用程序。
之前说的一大堆,其实开发中,都是用不到的,它已经在它的库里给你实现了~~直接上代码,代码也很简单。


public class MahoutTest {

public static void main(String[] args) throws IOException, TasteException {
String file = "D:\\test.txt";
//模型建立
DataModel model = new FileDataModel(new File(file));
//根据模型获取userId迭代器
LongPrimitiveIterator iter = model.getUserIDs();
UserSimilarity user = new EuclideanDistanceSimilarity(model);
//2代表--限制在模型中的用户数量
NearestNUserNeighborhood neighbor = new NearestNUserNeighborhood(2, user, model);
Recommender r = new GenericUserBasedRecommender(model, neighbor, user);
while (iter.hasNext()) {
long uid = iter.nextLong();
//3代表--所需要的行为数
List<RecommendedItem> list = r.recommend(uid, 3);
System.out.printf("uid:%s", uid);
for (RecommendedItem ritem : list) {
System.out.printf("(%s,%f)", ritem.getItemID(), ritem.getValue());
}
System.out.println();
}
}
}


源码解析

核心类如下,粗略讲一下我这里用到的genericUserBasedRecommender






与上图代码顺序一致
1.校验传参
2.获取相似用户id
3.获取相似用户的所有行为信息
4.获取评估信息
5.获取结果集 getTopItems方法如下


public static List<RecommendedItem> getTopItems(int howMany,
LongPrimitiveIterator possibleItemIDs,
IDRescorer rescorer,
Estimator<Long> estimator) throws TasteException {
Preconditions.checkArgument(possibleItemIDs != null, "argument is null");
Preconditions.checkArgument(estimator != null, "argument is null");

Queue<RecommendedItem> topItems = new PriorityQueue<RecommendedItem>(howMany + 1,
Collections.reverseOrder(ByValueRecommendedItemComparator.getInstance()));
boolean full = false;
double lowestTopValue = Double.NEGATIVE_INFINITY;
while (possibleItemIDs.hasNext()) {
long itemID = possibleItemIDs.next();
if (rescorer == null || !rescorer.isFiltered(itemID)) {
double preference;
try {
preference = estimator.estimate(itemID);
} catch (NoSuchItemException nsie) {
continue;
}
double rescoredPref = rescorer == null ? preference : rescorer.rescore(itemID, preference);
if (!Double.isNaN(rescoredPref) && (!full || rescoredPref > lowestTopValue)) {
topItems.add(new GenericRecommendedItem(itemID, (float) rescoredPref));
if (full) {
topItems.poll();
} else if (topItems.size() > howMany) {
full = true;
topItems.poll();
}
lowestTopValue = topItems.peek().getValue();
}
}
}
int size = topItems.size();
if (size == 0) {
return Collections.emptyList();
}
List<RecommendedItem> result = Lists.newArrayListWithCapacity(size);
result.addAll(topItems);
Collections.sort(result, ByValueRecommendedItemComparator.getInstance());
return result;
}


这里用了PriorityQueue,利用了其特殊构造函数 指定比较器,指定初始容量。
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}

查看了优先级队列的源码,其本质是:PriorityQueue会对入队的元素进行排序,所以在队列顶端的总是最小的元素。


end!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: