一种基于共现的推荐算法
参考原文《亚马逊推荐系统20年》
现有所有用户的购物列表:
user1: G A B
user2: X C Y
user3: X E F
user4: X M Y Q N J H
user5: W Y
user6: L E Z W
现在要计算商品X的相似商品。
思路:要计算X和Y的相似度直接计算p(Y|X)即可。根据极大似然估计
\begin{equation} p(Y|X)=\frac{count(X,Y)}{count(X)}=\frac{2}{3} \label{orig} \end{equation}
公式(\ref{orig})有2个问题:
- 如果Y是一个很热门的商品,则它倾向于和任意商品共现。也就是说如果$count(X,Y)$比较大,并不能直接说明X和Y的相关度就大,也可能是因为Y商品非常地热门。
- 对于热衷于购物的用户,她会买很多东西,这些东西之间不一定有相关度。也就是说如果一个用户购物比较少,我们倾向于认为这些商品之间的相关度比较高;如果一个用户购物比较多,我们倾向于认为这些商品之间的相关度比较低。
符号约定:
$N_{XY}$:同时购买了商品X和Y的用户数
$p(Y)$:总体而言,商品Y被购买的概率。$p(Y)=\frac{Y被购买的次数}{卖出去的商品总数}$
$C_X$:购买了商品X的用户集合
$c$:$C_X$中的一个用户
$|c|$:用户$c$购物的总数减去用户$c$购买X的次数
先假设商品X和Y相互独立,则用户$c$除了买X外还买了$|c|$件其他商品,每次购买都看成是一次伯努力试验:是否购买商品Y。则用户$c$没有购买Y的概率为:
$$(1-p(Y))^{|c|}$$
用户$c$购买Y的期望为:
$$1-(1-p(Y))^{|c|}$$
用户集合$C_X$购买Y的期望次数为:
\begin{equation}E_{XY}=\sum_{c \in C_X}\left[1-(1-p(Y))^{|c|}\right] \label{E} \end{equation}
实际上X和Y可能不是相互独立的,它们之间的相关度为:
\begin{equation} relevance(X,Y)=\frac{N_{XY}-E_{XY}}{\sqrt{E_{XY}}} \label{R} \end{equation}
联合(\ref{E})式和(\ref{R})式可知:$p(Y)越大(即Y越热门),X和Y的相关度越低;$$|c|$越大(即用户的购物列表越长),X和Y的相关度越低。正好解决(或者说缓和)了本文一开头提出的那2个问题。
# coding=utf-8 __author__ = "orisun" import math from collections import defaultdict target_position = set( [3218357, 3222919, 3050675, 3097961, 3202165, 3122457, 2747795]) # 要为这些职位生成推荐 deliver_list_dict = defaultdict(list) # 包含target_position的投递列表 deliver_prob_dict = {} def deliverCount(deliverFile): """统计每个职位被投递的概率 """ global target_position global deliver_list_dict global deliver_prob_dict total_deliver = 0 # 总投递次数 deliver_count_dict = defaultdict(int) # 每个职位被投递的次数 with open(deliverFile, 'r') as f_in: for line in f_in: arr = line.strip().split() if len(arr) > 1: deliver_set = set() for ele in arr[1:]: brr = ele.split(",") if len(brr) == 3: if "1" == brr[0]: pid = int(brr[1]) total_deliver += 1 deliver_count_dict[pid] += 1 deliver_set.add(pid) for target in target_position: if target in deliver_set: deliver_list_dict[target].append(list(deliver_set)) for pid, cnt in deliver_count_dict.items(): deliver_prob_dict[pid] = 1.0 * cnt / total_deliver def recForPosition(pid): global deliver_list_dict global deliver_prob_dict if pid in deliver_list_dict: deliver_list_list = deliver_list_dict[pid] cooccur_count_dict = defaultdict(int) expect_count_dict = defaultdict(float) for deliver_list in deliver_list_list: for deliver in deliver_list: if deliver != pid: cooccur_count_dict[deliver] += 1 expect_count_dict[deliver] += 1 - \ math.pow( 1 - deliver_prob_dict[deliver], len(deliver_list) - 1) relevant_dict = {} for deliver, cnt in cooccur_count_dict.items(): relevant_dict[deliver] = 1.0 * (cnt - len(deliver_list_list) * expect_count_dict[ deliver]) / math.sqrt(expect_count_dict[deliver]) # 只取相关度最高的前50个作为推荐 return sorted(relevant_dict.items(), cmp=lambda x, y: cmp(y[1], x[1]))[:50] return None if __name__ == '__main__': deliverCount("/data/orisun/dnn_for_rec/behavior/allBehavior.txt") for target in target_position: print "neighbors of position", target print recForPosition(target)
本文来自博客园,作者:高性能golang,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/7117031.html