美文网首页推荐系统修行之路
实战_资讯场景中重排策略实践

实战_资讯场景中重排策略实践

作者: Nefelibatas | 来源:发表于2022-02-10 11:53 被阅读0次

推荐系统的重排服务

01.png

重排服务(机制服务)

重排序不是必须的

在排序阶段,通过把用户特征,item特征和上下文特征放入到模型里面,得到item的点击预估值。然后根据候选集里面每个item的点击预估值从大到小排序,然后取出topN。

假如没有重排序的话,则将这topN个item作为一个list返回给用户

然而这个list会存在一定的缺陷。因为我们的排序模型只考虑了单个item对用户的影响,并没有考虑到item与item之间作为整个list,它们之间是会相互影响。

例如我们经常会看到一些app经常连续给我们推荐特别相似的内容,这可能就是没有考虑到item与item之间作为list返回时候会相互影响

对排序后的结果需要多样性时,我们需要重排服务。

策略机制

热门重排

热门重排的核心就是对资讯物品计算出一个热度分值,这里也是通常我们需要准备一个热度计算公式。公式的整体模式:

W1*H1 + W2*H2 + ... + Wn*Hn

其中:

  1. W1+W2 + ... + Wn = 1

  2. W为权重,H为因子

比如:资讯的热度计算: 0.2 * 点击人数 + 0.4 * 有效阅读人数 + 0.4 * (分享+评论)

代码实践

# 基于热度重排
# 曝光量,点击量,已经点击率
import pandas as pd
import numpy as np
import redis
import traceback

ds = pd.read_csv(
    "../raw/train/behaviors.tsv",
    names=['index_id', 'user_id', 'timestamp', 'hist', 'doc_id'], sep='\t')

ds = ds[['doc_id']] #  只取资讯曝光
print(ds.head())

doc_show_count = {}
doc_click_count = {}

for item in ds['doc_id'].values:
    tmp_iter = item.split()
    for tmp in tmp_iter:
        item, behavior = tmp.split('-')
        doc_click_count.setdefault(item, 0)
        doc_show_count.setdefault(item, 0)
        if behavior == '1':
            doc_click_count[item] += 1
        doc_show_count[item] += 1

item_show_click_dic = []
for doc, show in doc_show_count.items():
    click = doc_click_count.get(doc, 0)
    item_show_click_dic.append(
        {
            "doc": doc,
            "show": show,
            "click": click,
        }
    )

item_show_click = pd.DataFrame(item_show_click_dic)
print(item_show_click.describe())

# show
item_show_click = item_show_click[item_show_click['show'] > 288]
print(len(item_show_click))

# click
# 方法一,基于点击数进行倒排
#####归一化函数#####
reg = lambda x: x / np.max(x)
item_show_click['click_reg'] = item_show_click[['click']].apply(reg)
print(item_show_click.head())

item_click_count = {}
for d in item_show_click[['doc', 'click_reg']].values:
    item_click_count[d[0]] = d[1]

# 方法二,基于点击数和点击率的加权求和进行倒排
item_show_click['ctr'] = item_show_click['click'] / item_show_click['show']
print(item_show_click.head(30))

w1 = 0.3
w2 = 0.7
item_show_click['ctr_click'] = w1 * item_show_click['click_reg'] + w2 * \
                               item_show_click['ctr']
print(item_show_click.head(30))

item_ctr_click_count = {}
for d in item_show_click[['doc', 'ctr_click']].values:
    item_click_count[d[0]] = d[1]


def save_redis(items, db=1):
    redis_url = 'redis://:123456@127.0.0.1:6379/' + str(db)
    pool = redis.from_url(redis_url)
    try:
        for item in items.items():
            pool.set(item[0], item[1])
    except:
        traceback.print_exc()


save_redis(item_click_count, db=11)
save_redis(item_ctr_click_count, db=12)

类别打散

类别打散的核心就是基于资讯的类别进行错位穿插排序

  1. 基于类别+分值划分出多个有序分组,然后依次按分组取最大的分值

  2. 对于类别不足的排序结果,进行截断补其他的类别热门

比如:

排序后的结果:

[item1, cate1, 0.9], [item2,cate1, 0.8], [item3, cate1, 0.7], [item4, cate2, 0.7] , [item5, cate2, 0.6]

类别打散后:

[item1, cate1, 0.9], [item4,cate2, 0.7], [item2, cate1, 0.8], [item5, cate2, 0.6] , [item3, cate1, 0.7]

代码实践

def cate_shuffle(items):
    cate_items = {}
    cate_sort = []
# 循环,依次取类别中score最大的进行输出
    for item in items:
        cate = item['cate']
        cate_items.setdefault(cate, [])
        cate_items[cate].append(item)
        if cate not in cate_sort:
            cate_sort.append(cate)
    #打散穿插
    result = []
    for i in range(len(items)):
        for c in cate_sort:
            res = cate_items[c]
            if i > len(res) - 1:
                continue
            result.append(res[i])

    return result


if __name__ == '__main__':
    items = [
        {'item_id': 'N2031', 'cate': '01', 'score': 0.92},
        {'item_id': 'N2032', 'cate': '01', 'score': 0.71},
        {'item_id': 'N2033', 'cate': '01', 'score': 0.70},
        {'item_id': 'N2034', 'cate': '02', 'score': 0.65},
        {'item_id': 'N2035', 'cate': '02', 'score': 0.64},
        {'item_id': 'N2036', 'cate': '03', 'score': 0.63},
        {'item_id': 'N2037', 'cate': '03', 'score': 0.61},
    ]

    result = cate_shuffle(items)

    for re in result:
        print(re)

性别过滤

性别过滤的核心就是我们推荐的物料是存在性别偏向的,比如,在电商场景中对应男性用户会过滤掉一些女性化妆品,在小说推荐中女性用户会过滤热血类书籍,男性用户会过滤掉言情类书籍等等。

  1. 基于特征服务获取到用户的性别,然后获取到物料的性别偏向,两者不一致就过滤掉

比如:

排序后的结果[item1, 男, 0.9], [item2,女, 0.8], [item3, 男, 0.7], [item4, 女, 0.7] , [item5, 男, 0.6]

用户是男性,因此重排后结果为: [item1, 男, 0.9], [item3, 男, 0.7], [item5, 男, 0.6]

代码实践

def gender_filter(target_gender, items):
    items_tmp = []
    for it in items:
        if it['cate'] in target_gender:
            items_tmp.append(it)

    return items_tmp


if __name__ == '__main__':
    target_gender = ['01', '03'] # 只要类别01与03
    items = [
        {'item_id': 'N2031', 'cate': '01', 'score': 0.92},
        {'item_id': 'N2032', 'cate': '01', 'score': 0.71},
        {'item_id': 'N2033', 'cate': '01', 'score': 0.70},
        {'item_id': 'N2034', 'cate': '02', 'score': 0.65},
        {'item_id': 'N2035', 'cate': '02', 'score': 0.64},
        {'item_id': 'N2036', 'cate': '03', 'score': 0.63},
        {'item_id': 'N2037', 'cate': '03', 'score': 0.61},
    ]

    items = gender_filter(target_gender, items)
    for item in items:
        print(item)

强插

强插,主要是针对业务层面上,某些运营的物料或者新的物料进行操作的,将该物料直接强制插入排序后的队列中,也是一种物品冷启动策略。

比如:

排序后的结果 [item1, 男, 0.9], [item2,女, 0.8], [item3, 男, 0.7], [item4, 女, 0.7] , [item5, 男, 0.6]

强插一个新物料[item9,男, -1] 在第二位后

[item1, 男, 0.9], [item9,男, -1] ,[item2,女, 0.8], [item3, 男, 0.7], [item4, 女, 0.7] , [item5, 男, 0.6]

代码实践

# 强插

def forced_insertion(new_doc, items, nums):
    items_tmp = []
    max_score = items[0]['score']
    if nums == 1: # 若在第一位则强插的对象score比其他都大
        for i, n in enumerate(new_doc):
            items_tmp.append(
                {'item_id': n, 'score': max_score + (len(new_doc) - i) * 0.01})
        for it in items:
            items_tmp.append(it)
        return items_tmp
    else:
        max_score = items[nums - 2]['score']
        min_score = items[nums - 1]['score']
        # 在中间值则算出均值
        score = (max_score - min_score - 0.01) / len(new_doc)

        for i, it in enumerate(items):
            if i == nums - 1:
                for j, n in enumerate(new_doc):
                    items_tmp.append(
                        {'item_id': n, 'score': max_score - (j + 1) * score})
            items_tmp.append(it)

    return items_tmp


if __name__ == '__main__':
    new_doc = ['N2073', 'N2075']
    items = [
        {'item_id': 'N2031', 'cate': '01', 'score': 0.92},
        {'item_id': 'N2032', 'cate': '01', 'score': 0.71},
        {'item_id': 'N2033', 'cate': '01', 'score': 0.70},
        {'item_id': 'N2034', 'cate': '02', 'score': 0.65},
        {'item_id': 'N2035', 'cate': '02', 'score': 0.64},
        {'item_id': 'N2036', 'cate': '03', 'score': 0.63},
        {'item_id': 'N2037', 'cate': '03', 'score': 0.61},
    ]

    items = forced_insertion(new_doc, items, 2)
    for item in items:
        print(item)

相关文章

网友评论

    本文标题:实战_资讯场景中重排策略实践

    本文链接:https://www.haomeiwen.com/subject/xrynkrtx.html