推荐系统的重排服务
01.png重排服务(机制服务)
重排序不是必须的
在排序阶段,通过把用户特征,item特征和上下文特征放入到模型里面,得到item的点击预估值。然后根据候选集里面每个item的点击预估值从大到小排序,然后取出topN。
假如没有重排序的话,则将这topN个item作为一个list返回给用户
然而这个list会存在一定的缺陷。因为我们的排序模型只考虑了单个item对用户的影响,并没有考虑到item与item之间作为整个list,它们之间是会相互影响。
例如我们经常会看到一些app经常连续给我们推荐特别相似的内容,这可能就是没有考虑到item与item之间作为list返回时候会相互影响
对排序后的结果需要多样性时,我们需要重排服务。
策略机制
热门重排
热门重排的核心就是对资讯物品计算出一个热度分值,这里也是通常我们需要准备一个热度计算公式。公式的整体模式:
其中:
-
W1+W2 + ... + Wn = 1
-
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)
类别打散
类别打散的核心就是基于资讯的类别进行错位穿插排序
-
基于类别+分值划分出多个有序分组,然后依次按分组取最大的分值
-
对于类别不足的排序结果,进行截断补其他的类别热门
比如:
排序后的结果:
[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)
性别过滤
性别过滤的核心就是我们推荐的物料是存在性别偏向的,比如,在电商场景中对应男性用户会过滤掉一些女性化妆品,在小说推荐中女性用户会过滤热血类书籍,男性用户会过滤掉言情类书籍等等。
- 基于特征服务获取到用户的性别,然后获取到物料的性别偏向,两者不一致就过滤掉
比如:
排序后的结果[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)
网友评论