一种基于共现的推荐算法

参考原文《亚马逊推荐系统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个问题:

  1. 如果Y是一个很热门的商品,则它倾向于和任意商品共现。也就是说如果$count(X,Y)$比较大,并不能直接说明X和Y的相关度就大,也可能是因为Y商品非常地热门。
  2. 对于热衷于购物的用户,她会买很多东西,这些东西之间不一定有相关度。也就是说如果一个用户购物比较少,我们倾向于认为这些商品之间的相关度比较高;如果一个用户购物比较多,我们倾向于认为这些商品之间的相关度比较低。

符号约定:

$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)

  

 

posted @ 2017-07-04 15:53  张朝阳  阅读(1212)  评论(0编辑  收藏