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

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

作者: 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