FunRec-task5

task5推荐系统构建

(Datawhale32期组队学习)

基础点

  • Offline
    • 热门
    • 推荐
  • Online

知识点

1Offine

离线计算已储存好的物料画像和用户画像,为用户提供热门和推荐页列表并缓存入redis,方便online服务的列表获取。

1.1推荐页

用户登录进入后即推荐页,online给当前用户推荐页列表,离线缓存好存入redis

1.千人千面

  1. 存好的用户画像+物料画像,制作特征
  2. 通过模型预测排序

2.新用户冷启动

  1. 年龄、性别等获取大类别文章
  2. 文章热度生成冷启动推荐列表
#根据用户的年龄和性别自定义分成了四类人群
def generate_cold_start_news_list_to_redis_for_register_user(self):
        """给已经注册的用户制作冷启动新闻列表
        """
        for user_info in self.register_user_sess.query(RegisterUser).all():
            if int(user_info.age) < 23 and user_info.gender == "female":
                redis_key = "cold_start_group:{}".format(str(1))
                self.copy_redis_sorted_set(user_info.userid, redis_key)
            elif int(user_info.age) >= 23 and user_info.gender == "female":
                redis_key = "cold_start_group:{}".format(str(2))
                self.copy_redis_sorted_set(user_info.userid, redis_key)
            elif int(user_info.age) < 23 and user_info.gender == "male":
                redis_key = "cold_start_group:{}".format(str(3))
                self.copy_redis_sorted_set(user_info.userid, redis_key)
            elif int(user_info.age) >= 23 and user_info.gender == "male":
                redis_key = "cold_start_group:{}".format(str(4))
                self.copy_redis_sorted_set(user_info.userid, redis_key)
            else:
                pass 
        print("generate_cold_start_news_list_to_redis_for_register_user.")

3.老用户个性化

  1. 推荐(召回→排序→重排→个性化列表生成)捕捉兴趣
    • 召回:根据用户部分特征,从海量物品库,快速找到小部分用户潜在感兴趣的物品交给精排(强调快)
    • 精排:融入更多特征,使用复杂模型,来做个性化推荐(强调准)
    • 重排:结合精排的结果,再加上各种业务策略,去重,插入,打散,多样性保证等,主要是技术产品策略主导或改善用户体验的
  2. 多环节组成推荐系统,生成个性化列表
1.2热门页

用户登录进入后,离线部分要为用户缓存好列表存入redis

1.热门页

  • 每篇文章,根据发布时间,通过它的行为记录(点赞收藏阅读),计算热度信息。
  • 根据热度值排序
  • 行为记录-》物料画像的动、静态信息

2.热门逻辑

  1. 静态:发布时间
  2. 动态:赞,收藏,阅读动态数
  3. 遍历物料池所有文章,获取时效性(当前时间-发布时间)
  4. 过滤时效性过长(已发布很久)文章
  5. 热度公式计算-》热度值
  6. 热度值排序-》热门列表
  7. 以zset(可根据HotValue自动排序)形式存入redis(初始化公共列表)
  8. 每个用户兴趣不同,会给每个用户单独生成一个热门页(初始是公共列表),过滤曝光后的内容
  9. 用户点击热门,从自己的热门页获取文章(涉及online获取)
def get_hot_rec_list(self):
        """获取物料的点赞,收藏和创建时间等信息,计算热度并生成热度推荐列表存入redis
        """
        # 遍历物料池里面的所有文章
        for item in self.feature_protrail_collection.find():
            news_id = item['news_id']
            news_cate = item['cate']
            news_ctime = item['ctime']
            news_likes_num = item['likes']
            news_collections_num = item['collections']
            news_read_num = item['read_num']
            news_hot_value = item['hot_value']

            #print(news_id, news_cate, news_ctime, news_likes_num, news_collections_num, news_read_num, news_hot_value)

            # 时间转换与计算时间差   前提要保证当前时间大于新闻创建时间,目前没有捕捉异常
            news_ctime_standard = datetime.strptime(news_ctime, "%Y-%m-%d %H:%M")
            cur_time_standard = datetime.now()
            time_day_diff = (cur_time_standard - news_ctime_standard).days
            time_hour_diff = (cur_time_standard - news_ctime_standard).seconds / 3600

            # 只要最近3天的内容
            if time_day_diff > 3:
                continue
            
            # 计算热度分,这里使用魔方秀热度公式, 可以进行调整, read_num 上一次的 hot_value  上一次的hot_value用加?  因为like_num这些也都是累加上来的, 所以这里计算的并不是增值,而是实时热度吧
            # news_hot_value = (news_likes_num * 6 + news_collections_num * 3 + news_read_num * 1) * 10 / (time_hour_diff+1)**1.2
            # 72 表示的是3天,
            news_hot_value = (news_likes_num * 0.6 + news_collections_num * 0.3 + news_read_num * 0.1) * 10 / (1 + time_hour_diff / 72) 

            #print(news_likes_num, news_collections_num, time_hour_diff)

            # 更新物料池的文章hot_value
            item['hot_value'] = news_hot_value
            self.feature_protrail_collection.update({'news_id':news_id}, item)

            #print("news_hot_value: ", news_hot_value)

            # 保存到redis中
            self.reclist_redis.zadd('hot_list', {'{}_{}'.format(news_cate, news_id): news_hot_value}, nx=True)

2 Online

用户进入系统后触发行为,提供的服务

2.1推荐

行为:下拉触发

  1. 判断新、老
  2. 新:Offline中冷启动列表中读取推荐列表
    • 去除已曝光文章
    • 更新曝光列表
    • 选择指定数目,推荐(一次触发推荐几条)
  3. 老:Offline中个性化列表中读取推荐列表
    • 去除已曝光文章
    • 更新曝光列表
    • 选择指定数目,推荐(一次触发推荐几条)
2.2热门

行为:用户点击热门页;热门页中浏览文章刷新下来过程中进行触发

  1. 判断新、老
  2. 新:Offline中冷启动列表中读取热门列表
    • 去除已曝光文章
    • 更新曝光列表
    • 选择指定数目,推荐(一次触发推几条)
  3. 老:Offline中个性化列表中读取热门列表
    • 去除已曝光文章
    • 更新曝光列表
    • 选择指定数目,推荐(一次触发推几条)
def get_hot_list(self, user_id):
        """热门页列表结果"""
        hot_list_key_prefix = "user_id_hot_list:"
        hot_list_user_key = hot_list_key_prefix + str(user_id)

        user_exposure_prefix = "user_exposure:"
        user_exposure_key = user_exposure_prefix + str(user_id)

        # 当数据库中没有这个用户的数据,就从热门列表中拷贝一份 
        if self.reclist_redis_db.exists(hot_list_user_key) == 0: # 存在返回1,不存在返回0
            print("copy a hot_list for {}".format(hot_list_user_key))
            # 给当前用户重新生成一个hot页推荐列表, 也就是把hot_list里面的列表复制一份给当前user, key换成user_id
            self.reclist_redis_db.zunionstore(hot_list_user_key, ["hot_list"])

        # 一页默认10个item, 但这里候选20条,因为有可能有的在推荐页曝光过
        article_num = 200

        # 返回的是一个news_id列表   zrevrange排序分值从大到小
        candiate_id_list = self.reclist_redis_db.zrevrange(hot_list_user_key, 0, article_num-1)

        if len(candiate_id_list) > 0:
            # 根据news_id获取新闻的具体内容,并返回一个列表,列表中的元素是按照顺序展示的新闻信息字典
            news_info_list = []
            selected_news = []   # 记录真正被选了的
            cou = 0

            # 曝光列表
            print("self.reclist_redis_db.exists(key)",self.exposure_redis_db.exists(user_exposure_key))
            if self.exposure_redis_db.exists(user_exposure_key) > 0:
                exposure_list = self.exposure_redis_db.smembers(user_exposure_key)
                news_expose_list = set(map(lambda x: x.split(':')[0], exposure_list))
            else:
                news_expose_list = set()

            for i in range(len(candiate_id_list)):
                candiate = candiate_id_list[i]
                news_id = candiate.split('_')[1]

                # 去重曝光过的,包括在推荐页以及hot页
                if news_id in news_expose_list:
                    continue

                # TODO 有些新闻可能获取不到静态的信息,这里应该有什么bug
                # bug 原因是,json.loads() redis中的数据会报错,需要对redis中的数据进行处理
                # 可以在物料处理的时候过滤一遍,json无法load的新闻
                try:
                    news_info_dict = self.get_news_detail(news_id)
                except Exception as e:
                    with open("/home/recsys/news_rec_server/logs/news_bad_cases.log", "a+") as f:
                        f.write(news_id + "\n")
                        print("there are not news detail info for {}".format(news_id))
                    continue
                # 需要确认一下前端接收的json,key需要是单引号还是双引号
                news_info_list.append(news_info_dict)
                news_expose_list.add(news_id)
                # 注意,原数的key中是包含了类别信息的
                selected_news.append(candiate)
                cou += 1
                if cou == 10:
                    break
            
            if len(selected_news) > 0:
                # 手动删除读取出来的缓存结果, 这个很关键, 返回被删除的元素数量,用来检测是否被真的被删除了
                removed_num = self.reclist_redis_db.zrem(hot_list_user_key, *selected_news)
                print("the numbers of be removed:", removed_num)

            # 曝光重新落表
            self._save_user_exposure(user_id,news_expose_list)
            return news_info_list 
        else:
            #TODO 临时这么做,这么做不太好
            self.reclist_redis_db.zunionstore(hot_list_user_key, ["hot_list"])
            print("copy a hot_list for {}".format(hot_list_user_key))
            # 如果是把所有内容都刷完了再重新拷贝的数据,还得记得把今天的曝光数据给清除了
            self.exposure_redis_db.delete(user_exposure_key)
            return  self.get_hot_list(user_id)

个人需要补充的点

  • 推荐模型

参考

  • https://github.com/datawhalechina/fun-rec
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值