推荐中心逻辑

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


5.4 推荐中心逻辑

学习目标

  • 目标
  • 应用

5.4.1 推荐中心

推荐中一般作为整体召回结果读取与排序模型进行排序过程的作用,主要是产生推荐结果的部分。

推荐目录

  • server目录为整个推荐中心建立的目录
    • recall_service.:召回数据读取目录
    • reco_centor:推荐中心逻辑代码
    • redis_cache:推荐结果缓存目录
    • utils:装有自己封装的hbase数据库读取存储工具

5.4.2 推荐中心推荐存储设计

  • HBASE 数据库表设计一
    • history_recommend: 每次真正推荐出去给用户的历史推荐结果列表
      • 1、按照频道存储用户的历史推荐结果
      • 2、需要保留多个版本,才需要建立版本信息

创建一个历史hbase结果

create 'history_recommend', {NAME=>'channel', TTL=>7776000, VERSIONS=>999999}   86400
# 每次指定一个时间戳,可以达到不同版本的效果
put 'history_recommend', 'reco:his:1', 'channel:18', [17283, 140357, 14668, 15182, 17999, 13648, 12884, 17302, 13846, 18135]


# 修改的时候必须指定family名称
hbase(main):084:0> alter 'history_recommend',NAME => 'channel', TTL => '7776000'
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 2.0578 seconds

alter 'history_recommend',NAME => 'channel', VERSIONS=>999999, TTL=>7776000

放入历史数据,存在时间戳,到时候取出历史数据就是每个用户的历史时间戳可以

get "history_recommend", 'reco:his:1', {COLUMN=>'channel:18',VERSIONS=>1000, TIMESTAMP=>1546242869000}

这里与上次召回cb_recall以及history_recall有不同用处:

  • history_recall:存放召回过的数据,用户过滤推荐初始的产生结果

  • history_recommend:存放的是某个用户在某频道的真正推荐过的历史记录

    • 同时过滤掉新文章和热门文章的推荐结果

5.4.3 feed流 推荐中心逻辑

  • 目的:根据ABTest分流之后的用户,完成用户刷新逻辑,并进行制定算法的召回和排序读取
  • 步骤:
    • 1、Hbase数据库工具封装使用介绍
    • 2、feed下拉刷新、上拉加载历史的时间戳进行推荐逻辑判断
    • 3、加入召回读取(无实时排序)

创建特征中心类:

import os
import sys

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR))
import hashlib
from setting.default import RAParam
from server.utils import HBaseUtils
from server import pool
from server import recall_service
from datetime import datetime
import logging
import json

logger = logging.getLogger('recommend')

class RecoCenter(object):
    """推荐中心
    """
    def __init__(self):
        self.hbu = HBaseUtils(pool)
        # self.recall_service = recall_service.ReadRecall()

并且添加了获取结果打印日志设置

# 实施推荐日志
# 离线处理更新打印日志
trace_file_handler = logging.FileHandler(
  os.path.join(logging_file_dir, 'recommend.log')
)
trace_file_handler.setFormatter(logging.Formatter('%(message)s'))
log_trace = logging.getLogger('recommend')
log_trace.addHandler(trace_file_handler)
log_trace.setLevel(logging.INFO)

Hbase读取存储等工具类封装

为什么封装?

在写happybase代码的时候会有过多的重复代码,将这些封装成简便的工具,减少代码冗余

  • 包含方法
    • get_table_row(self, table_name, key_format, column_format=None, include_timestamp=False):
      • 获取具体表中的键、列族中的行数据
    • get_table_cells(self, table_name, key_format, column_format=None, timestamp=None, include_timestamp=False):
      • 获取Hbase中多个版本数据
    • get_table_put(self, table_name, key_format, column_format, data, timestamp=None):
      • 存储数据到Hbase当中
    • get_table_delete(self, table_name, key_format, column_format):
      • 删除Hbase中的数据
class HBaseUtils(object):
    """HBase数据库读取工具类
    """
    def __init__(self, connection):
        self.pool = connection

    def get_table_row(self, table_name, key_format, column_format=None, include_timestamp=False):
        """
        获取HBase数据库中的行记录数据
        :param table_name: 表名
        :param key_format: key格式字符串, 如表的'user:reco:1', 类型为bytes
        :param column_format: column, 列族字符串,如表的 column 'als:18',类型为bytes
        :param include_timestamp: 是否包含时间戳
        :return: 返回数据库结果data
        """
        if not isinstance(key_format, bytes):
            raise KeyError("key_format or column type error")

        if not isinstance(table_name, str):
            raise KeyError("table_name should str type")

        with self.pool.connection() as conn:
            table = conn.table(table_name)

            if column_format:
                data = table.row(row=key_format, columns=[column_format], include_timestamp=include_timestamp)
            else:
                data = table.row(row=key_format)
            conn.close()

        if column_format:
            return data[column_format]
        else:
            # [(b'[141440]', 1555519429582)]
            # {'[141440]'}
            return data

    def get_table_cells(self, table_name, key_format, column_format=None, timestamp=None, include_timestamp=False):
        """
        获取HBase数据库中多个版本数据
        :param table_name: 表名
        :param key_format: key格式字符串, 如表的'user:reco:1', 类型为bytes
        :param column_format: column, 列族字符串,如表的 column 'als:18',类型为bytes
        :param timestamp: 指定小于该时间戳的数据
        :param include_timestamp: 是否包含时间戳
        :return: 返回数据库结果data
        """
        if not isinstance(key_format, bytes) or not isinstance(column_format, bytes):
            raise KeyError("key_format or column type error")

        if not isinstance(table_name, str):
            raise KeyError("table_name should str type")

        with self.pool.connection() as conn:
            table = conn.table(table_name)

            data = table.cells(row=key_format, column=column_format, timestamp=timestamp,
                               include_timestamp=include_timestamp)

            conn.close()
        # [(,), ()]
        return data

    def get_table_put(self, table_name, key_format, column_format, data, timestamp=None):
        """

        :param table_name: 表名
        :param key_format: key格式字符串, 如表的'user:reco:1', 类型为bytes
        :param column_format: column, 列族字符串,如表的 column 'als:18',类型为bytes
        :param data: 插入的数据
        :param timestamp: 指定拆入数据的时间戳
        :return: None
        """
        if not isinstance(key_format, bytes) or not isinstance(column_format, bytes) or not isinstance(data, bytes):
            raise KeyError("key_format or column or data type error")

        if not isinstance(table_name, str):
            raise KeyError("table_name should str type")

        with self.pool.connection() as conn:
            table = conn.table(table_name)

            table.put(key_format, {column_format: data}, timestamp=timestamp)

            conn.close()
        return None

    def get_table_delete(self, table_name, key_format, column_format):
        """
        删除列族中的内容
        :param table_name: 表名称
        :param key_format: key
        :param column_format: 列格式
        :return:
        """
        if not isinstance(key_format, bytes) or not isinstance(column_format, bytes):
            raise KeyError("key_format or column type error")

        if not isinstance(table_name, str):
            raise KeyError("table_name should str type")
        with self.pool.connection() as conn:
            table = conn.table(table_name)
            table.delete(row=key_format, columns=[column_format])
            conn.close()
        return None

2、增加feed_recommend_logic函数,进行用户刷新时间戳逻辑判断

根据时间戳判断刷新加载逻辑

  • 时间戳T小于HBASE历史推荐记录
    • 则获取历史记录,返回该时间戳T上次的时间戳T-1
  • 时间戳T大于HBASE历史推荐记录,则获取新推荐,则获取HBASE数据库中最近的一次时间戳
    • 如果有缓存,从缓存中拿,并且写入推荐历史表中
    • 如果没有缓存,就进行一次指定算法组合的召回结果读取,排序,然后写入待推荐wait_recommend中,其中推荐出去的放入历史推荐表中

获取这个用户该频道的历史结果

# 判断用请求的时间戳大小决定获取历史记录还是刷新推荐文章
        try:
            last_stamp = self.hbu.get_table_row('history_recommend', 'reco:his:{}'.format(temp.user_id).encode(),
                                                'channel:{}'.format(temp.channel_id).encode(), include_timestamp=True)[
                1]
            logger.info("{} INFO get user_id:{} channel:{} history last_stamp".format(
                datetime.now().strftime('%Y-%m-%d %H:%M:%S'), temp.user_id, temp.channel_id))
        except Exception as e:
            logger.warning("{} WARN read history recommend exception:{}".format(
                datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            last_stamp = 0

如果历史时间戳最近的一次小于用户请求时候的时间戳,Hbase的时间戳是time.time() * 1000这个值的大小,与Web后台传入的一样类型,如果Web后台传入的不是改大小,注意修改

  • 然后返回推荐结果以及此次请求的上一次时间戳
    • 用于用户获取历史记录
if last_stamp < temp.time_stamp:
            # 1、获取缓存
            # res = redis_cache.get_reco_from_cache(temp, self.hbu)
            #
            # # 如果没有,然后走一遍算法推荐 召回+排序,同时写入到hbase待推荐结果列表
            # if not res:
            #     logger.info("{} INFO get user_id:{} channel:{} recall/sort data".
            #                 format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), temp.user_id, temp.channel_id))
            #
            #     res = self.user_reco_list(temp)

            # 2、直接拿推荐结果
            # res = self.user_reco_list(temp)

            #temp.time_stamp = int(last_stamp)

            track = add_track([], temp)

如果历史时间戳大于用户请求的这次时间戳,那么就是在获取历史记录,用户请求的历史时间戳是具体某个历史记录的时间戳T,Hbase当中不能够直接用T去获取,而需要去(T+N)>T的时间戳获取,才能拿到包含T时间的结果,并且使用get_table_cells去获取

  • 分以下情况考虑
    • 1、如果没有历史数据,返回时间戳0以及结果空列表
    • 2、如果历史数据只有一条,返回这一条历史数据以及时间戳正好为请求时间戳,修改时间戳为0,表示后面请求以后就没有历史数据了(APP的行为就是翻历史记录停止了)
    • 3、如果历史数据多条,返回最近的第一条历史数据,然后返回之后第二条历史数据的时间戳
        else:

            logger.info("{} INFO read user_id:{} channel:{} history recommend data".format(
                datetime.now().strftime('%Y-%m-%d %H:%M:%S'), temp.user_id, temp.channel_id))

            try:
                row = self.hbu.get_table_cells('history_recommend',
                                               'reco:his:{}'.format(temp.user_id).encode(),
                                               'channel:{}'.format(temp.channel_id).encode(),
                                               timestamp=temp.time_stamp + 1,
                                               include_timestamp=True)
            except Exception as e:
                logger.warning("{} WARN read history recommend exception:{}".format(
                    datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
                row = []
                res = []

            # 1、如果没有历史数据,返回时间戳0以及结果空列表
            # 2、如果历史数据只有一条,返回这一条历史数据以及时间戳正好为请求时间戳,修改时间戳为0
            # 3、如果历史数据多条,返回最近一条历史数据,然后返回
            if not row:
                temp.time_stamp = 0
                res = []
            elif len(row) == 1 and row[0][1] == temp.time_stamp:
                res = eval(row[0][0])
                temp.time_stamp = 0
            elif len(row) >= 2:
                res = eval(row[0][0])
                temp.time_stamp = int(row[1][1])

            res = list(map(int, res))
            logger.info(
                "{} INFO history:{}, {}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), res, temp.time_stamp))
            track = add_track(res, temp)
            # 曝光参数设置为空
            track['param'] = ''

        return track
  • 完整代码:
    def feed_recommend_logic(self, temp):
        """推荐流业务逻辑
        :param temp:ABTest传入的业务请求参数
        """

        # 判断用请求的时间戳大小决定获取历史记录还是刷新推荐文章
        try:
            last_stamp = self.hbu.get_table_row('history_recommend', 'reco:his:{}'.format(temp.user_id).encode(),
                                                'channel:{}'.format(temp.channel_id).encode(), include_timestamp=True)[1]
            logger.info("{} INFO get user_id:{} channel:{} history last_stamp".format(
                datetime.now().strftime('%Y-%m-%d %H:%M:%S'), temp.user_id, temp.channel_id))
        except Exception as e:
            logger.warning("{} WARN read history recommend exception:{}".format(
                datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
            last_stamp = 0

        # 如果小于,走一遍正常的推荐流程,缓存或者召回排序
        logger.info("{} INFO history last_stamp:{},temp.time_stamp:{}".
                    format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), last_stamp, temp.time_stamp))
        if last_stamp < temp.time_stamp:

            # 获取
            res = redis_cache.get_reco_from_cache(temp, self.hbu)

            # 如果没有,然后走一遍算法推荐 召回+排序,同时写入到hbase待推荐结果列表
            if not res:
                logger.info("{} INFO get user_id:{} channel:{} recall/sort data".
                            format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), temp.user_id, temp.channel_id))

                res = self.user_reco_list(temp)

            temp.time_stamp = int(last_stamp)

            track = add_track(res, temp)

        else:

            logger.info("{} INFO read user_id:{} channel:{} history recommend data".format(
                datetime.now().strftime('%Y-%m-%d %H:%M:%S'), temp.user_id, temp.channel_id))

            try:
                row = self.hbu.get_table_cells('history_recommend',
                                          'reco:his:{}'.format(temp.user_id).encode(),
                                          'channel:{}'.format(temp.channel_id).encode(),
                                          timestamp=temp.time_stamp + 1,
                                          include_timestamp=True)
            except Exception as e:
                logger.warning("{} WARN read history recommend exception:{}".format(
                    datetime.now().strftime('%Y-%m-%d %H:%M:%S'), e))
                row = []
                res = []

            # 1、如果没有历史数据,返回时间戳0以及结果空列表
            # 2、如果历史数据只有一条,返回这一条历史数据以及时间戳正好为请求时间戳,修改时间戳为0
            # 3、如果历史数据多条,返回最近一条历史数据,然后返回
            if not row:
                temp.time_stamp = 0
                res = []
            elif len(row) == 1 and row[0][1] == temp.time_stamp:
                res = eval(row[0][0])
                temp.time_stamp = 0
            elif len(row) >= 2:
                res = eval(row[0][0])
                temp.time_stamp = int(row[1][1])

            res = list(map(int, res))
            logger.info(
                "{} INFO history:{}, {}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), res, temp.time_stamp))
            track = add_track(res, temp)
            # 曝光参数设置为空
            track['param'] = ''
        return track

修改ABTest中的推荐调用

from server.reco_center import RecoCenter

# 推荐
track = RecoCenter().feed_recommend_logic(temp)

5.4.4 推荐中心时间戳获取逻辑测试

获取多版本历史记录:

hbase(main):045:0> get 'history_recommend', 'reco:his:1115629498121846784', {COLUMN=>'channel:18', VERSIONS=>999999}
COLUMN                     CELL                                                                        
 channel:18                timestamp=1559148615353, value=[15140, 16421, 19494, 14381, 17966]          
 channel:18                timestamp=1558236647437, value=[18904, 14300, 44412, 18238, 18103, 43986, 44
                           339, 17454, 14899, 18335]                                                   
 channel:18                timestamp=1558236629309, value=[43997, 14299, 17632, 17120]                 
 channel:18                timestamp=1558236535794, value=[44657, 15085, 18156, 44654, 19052, 44652, 18
                           795, 17385, 44137, 17889]

GRPC接口测试,使用不同的时间戳进行测试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

あずにゃん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值