ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

头条项目推荐的相关技术(五): 离线用户画像计算与增量更新

2021-03-16 20:29:44  阅读:186  来源: 互联网

标签:离线 用户 user article row 画像 id 头条


1. 写在前面

这里是有关于一个头条推荐项目的学习笔记,主要是整理工业上的推荐系统用到的一些常用技术, 这是第五篇, 上一篇文章整理了离线文章画像的计算与更新的全过程以及文章相似度的计算,本篇介绍的技术是离线用户画像的计算, 首先会介绍离线用户画像建立的流程,然后就介绍离线的用户画像如何计算及更新,最后借鉴王喆老师的书,补充一下推荐系统应该如何选取和处理特征的相关知识, 主要内容如下:

  • 离线用户画像建立流程
  • 如何构建用户画像(用户行为日志的处理,用户画像标签及权重的计算,用户画像的HBase存储与Hive关联)
  • 离线用户画像增量更新(spark完成用户画像的增量定时更新)
  • 推荐系统应该如何选取和处理特征(特征工程原则,常用特征,常用特征处理方法以及特征工程与业务)

Ok, let’s go!

2. 离线用户画像的建立流程

2.1 为什么要进行用户画像

要做精准推送同样可以使用多种推荐算法,例如:基于用户协同推荐、基于内容协同的推荐等其他的推荐方式,但是以上方式多是基于相似进行推荐。而构建用户画像,不仅可以满足根据分析用户进行推荐,更可以运用在全APP所有功能上。 用户画像就是理解用户喜欢啥,想要啥东西。

建立用户画像确实是一个一劳多得的事情,不仅可以运用于精准推送、精准推荐、精准营销,更可以作为网站的用户属性分析,用户行为分析,商业化转化分析等。同时网站共用一套用户画像,可以对用户有统一的认知。

2.2 用户画像计算设计

建立用户画像是基于了用后自身的信息和用户行为构建的, 具体流程如下:

在这里插入图片描述
用户画像的第一层主要是原始数据库,此数据库主要囊括后续分析所需要的所有原始数据。也是通过大量数据的分析和处理,后面能提炼成用户的画像得以运用。

原始数据如下:

在这里插入图片描述
用户行为记录我们之前第二篇文章的时候收集过,原始数据就是用户的行为日志记录,在profile数据库里面的user_action,数据格式如下:

2019-03-05 10:19:40             0       {"action":"exposure","userId":"2","articleId":"[16000, 44371, 16421, 16181, 17454]","algorithmCombine":"C2"} 2019-03-05

对于这样的数据,我们希望处理成一个完整的统计用户基本信息的表格:

在这里插入图片描述
第三块内容会介绍如何从原始的数据中做一个这样的表格出来(用户行为日志处理), 但在这之前,还需要整理点知识。即用户画像标签的建立。

  • 喜欢发生行为的文章,进行标签提取给用户建立
  • 各频道的文章标签(行为发生), 各个用户的基础信息等

用户行为原始数据,我们得到了一张庞大的行为记录表。但是想要把这个表格的内容运用起来,我们需要把用户行为更为具象化,也就是需要把用户画像构建起来。

其实用户标签并不等同于用户画像,只是用户标签是用户画像直观的呈现,并且是比较好且常用的运用方式。

构建用户标签库其实比较简单,因为我们在上述采集用户行为过程中,已经把用户喜好的内容采集下来了,所以基础标签并可以直接运用内容的标签。也就是通过用户喜欢的内容给用户贴标签。最直接的方式就是文章标签化,即之前我们建立好的文章标签,利用这些标签给用户贴上相应标签。

在这里插入图片描述
我们在实际对用户进行标签画像提取的时候,是按照频道去提取的,这样会更加细致些,就是用户在频道1方面喜欢什么类型的文章, 在频道2上喜欢什么类型的文章, 毕竟不同的频道差距还是比较大的(python,C++, 前端, 大数据,人工智能等)。

3. 如何构建用户画像

3.1 用户行为日志的处理

这里就是把我们的用户行为日志,处理成上面的那个结构表的形式。首先对用户基础行为日志进行处理过滤,解析参数,从user_action—>user_article_basic表。步骤如下:

  1. 创建Hive基本数据表
  2. 读取固定时间内的用户行为日志
  3. 进行用户日志数据处理
  4. 存储到user_article_basec表中

3.1.1 创建Hive基本数据表

首先, 在profile.db数据库中建立一个user_article_basec表。这里可以直接关联到Hdfs上的对应表格。

create table user_article_basic(
user_id BIGINT comment "userID",
action_time STRING comment "user actions time",
article_id BIGINT comment "articleid",
channel_id INT comment "channel_id",
shared BOOLEAN comment "is shared",
clicked BOOLEAN comment "is clicked",
collected BOOLEAN comment "is collected",
exposure BOOLEAN comment "is exposured",
read_time STRING comment "reading time")
COMMENT "user_article_basic"
CLUSTERED by (user_id) into 2 buckets
STORED as textfile
LOCATION '/user/hive/warehouse/profile.db/user_article_basic';

3.1.2 读取固定时间内用户行为日志

下面是读取增量用户行为数据 — 固定时间内的用户行为日志, 这里实例化一个sparksession:

# 前面是加入那些变量和导入包的操作,可以参考前面几篇的内容
class UpdateUserProfile(SparkSessionBase):
    """离线相关处理程序
    """
    SPARK_APP_NAME = "updateUser"
    ENABLE_HIVE_SUPPORT = True

    SPARK_EXECUTOR_MEMORY = "7g"

    def __init__(self):

        self.spark = self._create_spark_session()
 
uup = UpdateUserProfile()

在进行日志信息的处理之前,先将我们之前建立的user_action表之间进行所有日期关联,spark hive不会自动关联。Hadoop和Hive的内部属性是分开的。所以我们在Hive终端, 去关联所有的历史日期分区。第三步还会看到这个。

# 手动关联所有日期文件
import pandas as pd
from datetime import datetime

def datelist(beginDate, endDate):   # 开始日期 到结束日期的数据,都需要在Hive中手动关联分区
    date_list=[datetime.strftime(x,'%Y-%m-%d') for x in list(pd.date_range(start=beginDate, end=endDate))]
    return date_list

# 从2019年3月5号开始的
dl = datelist("2019-03-05", time.strftime("%Y-%m-%d", time.localtime()))

fs = pyhdfs.HdfsClient(hosts='hadoop-master:50070')   # 获取hadoop的hdfs下的目录
for d in dl:
    try:
    	# 先判断hadoop里面有没有这个目录 hadoop里面真实存在的分区数据,才关联到user_action分区属性
        _localions = '/user/hive/warehouse/profile.db/user_action/' + d
        if fs.exists(_localions):   # 如果有这个目录,就增加分区属性关联日期, 没增加分区属性,肯定读不了hadoop下的相应分区数据
            uup.spark.sql("alter table user_action add partition (dt='%s') location '%s'" % (d, _localions))
    except Exception as e:
        # 已经关联过的异常忽略,partition与hdfs文件不直接关联
        pass

接下来, 读取固定的时间内的用户行为日志。 注意,每天有数据都要关联一次日期文件与Hive表,因为之前就记录过, 如果flume收集了一个新日期下的日志行为,但此时在user_action中是查不出来的。需要将user_action添加一个相应时间的分区和位置才可以。如果用户行为日志有分区,一定要手动进行添加分区

# 如果hadoop没有今天该日期文件,则没有日志数据,结束
time_str = time.strftime("%Y-%m-%d", time.localtime())
_localions = '/user/hive/warehouse/profile.db/user_action/' + time_str
if fs.exists(_localions):
    # 如果有该文件直接关联,捕获关联重复异常
    try:
        uup.spark.sql("alter table user_action add partition (dt='%s') location '%s'" % (time_str, _localions))
    except Exception as e:
        pass

    sqlDF = uup.spark.sql(
"select actionTime, readTime, channelId, param.articleId, param.algorithmCombine, param.action, param.userId from user_action where dt={}".format(time_str))
else:
    pass

这样,就得到了原始的日志数据(sqlDF):

在这里插入图片描述

3.1.3 进行用户日志数据处理

首先, 先看下我们最终处理的数据表的样子,然后给出处理思路。

在这里插入图片描述
处理思路:按照user_id的行为一条条的处理, 根据用户的行为类型进行判别。这里会看到,对于一个用户,会同时给他曝光了多篇文章,是一个列表的形式, 但都存在了一行里面(原始数据表), 而下面这个表会把这多篇文章进行展开成[user_id, itemi, 曝光TRUE]的形式。也就是一行数据会被处理成多行了。

  • 由于sqlDF每条数据可能会返回多条结果, 我们可以使用rdd.flatMap函数或者yield
    格式:["user_id", "action_time", "article_id", "channel_id", "shared", "clicked", "collected", "exposure", "read_time"]

代码如下:

if sqlDF.collect():   # 如果当前时间段收集到了用户数据
    def _compute(row):     # 拿过来一行数据
        # 进行判断行为类型
        _list = []
        if row.action == "exposure":   # 如果当前行为是曝光, 里面对应很多文章
            for article_id in eval(row.articleId):   # 把每一篇曝光文章解析,按照上面的格式添加特征
                # ["user_id", "action_time", "article_id", "channel_id", "shared", "clicked", "collected", "exporesure", "read_time"]
                _list.append(
                    [row.userId, row.actionTime, article_id, row.channelId, False, False, False, True, row.readTime])
            return _list       # 这就是多行数据了
        else: # 曝光要单独拿出来,因为对应的article_id是个列表,需要拆分成多行,而其他行为,article就是1个,所以在一行里面处理就OK,所以分成了两块逻辑
            class Temp(object):
                shared = False
                clicked = False
                collected = False
                read_time = ""

            _tp = Temp()
            if row.action == "share":
                _tp.shared = True
            elif row.action == "click":
                _tp.clicked = True
            elif row.action == "collect":
                _tp.collected = True
            elif row.action == "read":
                _tp.clicked = True
            else:
                pass
            _list.append(
                [row.userId, row.actionTime, int(row.articleId), row.channelId, _tp.shared, _tp.clicked, _tp.collected,
                 True,
                 row.readTime])
            return _list
    # 进行处理
    # 查询内容,将原始日志表数据进行处理
    _res = sqlDF.rdd.flatMap(_compute)
    data = _res.toDF(["user_id", "action_time","article_id", "channel_id", "shared", "clicked", "collected", "exposure", "read_time"])

这里面用到了一个新函数叫做flatMap, 这个东西的作用:处理一行数据,可以返回多行结果的DataFrame数据, 这个与map, mapPartitions是不一样的。

3.1.4 合并历史数据,存储到user_article_basic表中

这里的一个注意点就是用户的历史行为是会更新的,比如昨天的时候,用户可能没有点击某篇文章,我们把用户信息记录了点击那里是False, 而今天用户又可能点击了昨天的那篇文章,所以这时候该用户点击那里就需要改成TRUE了。所以,这里有个需求就是新的行为数据越要和历史行为数据按照用户id, article_id进行分组合并, 变成一行数据

# 合并历史数据,插入表中
old = uup.spark.sql("select * from user_article_basic")
# 由于合并的结果中不是对于user_id和article_id唯一的,一个用户会对文章多种操作
new_old = old.unionAll(data)       #这里先按照行拼接起来

HIVE目前支持hive终端操作ACID(update, delete),但是不支持python的pyspark原子性操作,并且开启配置中开启原子性相关配置也不行。因为spark SQL hive主要是用来做离线数据分析,不要求实时性,所以这里更新的时候,并不是在原来的数据上进行的操作,而是删掉原来的数据,然后插入新的数据进来。也就是没有原子性更新(在原来的数据更新相应的字段)

# 注册一个temptable
new_old.registerTempTable("temptable")
# 按照用户,文章分组存放进去
# overwrite是覆盖式的形式,也就是把原来的数据删掉,然后重新插入新数据,而不是在原来的数据上更新某个字段这种
uup.spark.sql(
        "insert overwrite table user_article_basic select user_id, max(action_time) as action_time, "
        "article_id, max(channel_id) as channel_id, max(shared) as shared, max(clicked) as clicked, "
        "max(collected) as collected, max(exposure) as exposure, max(read_time) as read_time from temptable "
        "group by user_id, article_id")   
# 上面这个SQL语句,先按照用户和文章进行分组,然后把相同的进行合并
# 这里有个问题就是合并的时候,不应该用最新的行为覆盖掉原来的行为吗?  
# 哦哦,懂了,这里的max操作其实就可以保证这个问题了,因为只要发生行为,就改为TRUE, 那么改的时候只可能原来没看,现在看了,所以取最大就是最新结果,不可能是之前看过, 现在不看,那也是看过,那么也是TRUE,所以取最大即可。

这样,用户的行为特征就处理好了, 接下来基于这些行为数据,去构建用户的画像。

3.2 用户标签权重计算

3.2.1 画像存储

这里先说用户画像的存储问题,用户画像,作为特征提供给一些算法排序,方便与快速读取使用,选择存储在Hbase当中。如果离线分析也想要使用我们可以建立HIVE到Hbase的外部表。 那么到底是谁应该关联谁呢? 这里又两个方案(重点知识):

  • 方案一Hive存储,HBase关联到Hive中:如果存到HIVE,建立HBASE关联过去,删除Hive表对HBase没有影响,但是先删除HBase表Hive就会报TableNotFoundException, 虽然出乎意料,但事实就是这样。尽快HBase关联的Hive,但一旦关联,然后删除HBase中的表,Hive里面的表就会受到影响
  • 方案二HBase存储, Hive关联到HBase:这时候删掉Hive是对HBase没有影响的,并且HBase支持原子性更新,HBase中有同样的主键的行会被更新成最新插入的。可以依靠hbase来 新增/修改单条记录, 然后利用hive这个外表来实现hbase数据统计。 所以第二种方案实施读取快,并且Hive也能离线数据分析,是建议用的方式。

那么这里我们也是选择方案二,用户画像存储到HBase,然后Hive进行关联过去。 HBase表设计:

create 'user_profile', 'basic','partial','env'   # 3个列族

# 示例:  user:2是主键
put 'user_profile', 'user:2', 'partial:{channel_id}:{topic}': weights
put 'user_profile', 'user:2', 'basic:{info}': value
put 'user_profile', 'user:2', 'env:{info}': value

在HBase中,用户的数据就是按照上面这个格式存储,那么如果想Hive中查询分析的时候呢? 就需要建立表进行关联:

create external table user_profile_hbase(
user_id STRING comment "userID",
information map<string, DOUBLE> comment "user basic information",
article_partial map<string, DOUBLE> comment "article partial",
env map<string, INT> comment "user env")
COMMENT "user profile table"
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'  # 存储形式  固定写法
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,basic:,partial:,env:")  # HBase的列族和key要和Hive中建立的列进行对应起来
TBLPROPERTIES ("hbase.table.name" = "user_profile");    # 表名固定写法

HBase存储, Hive关联,有下面几个注意点:之前建表不太一样了:

  1. external table
  2. 注意HBase的存储字段要映射到Hive相应的列中(HBase的列族映射关系)
  3. STORED BY后面的存储形式,这是固定的写法
  4. 最后一行的表名关联,也是固定写法,

这样关联完了之后,Hive还不能读取数据,因为缺一些依赖包。

3.2.2 Spark SQL关联表读取问题?

创建关联表之后,离线读取表内容需要一些依赖包。解决办法:

  • 拷贝/opt/bigdata/hbase/hbase1.2/lib/下面hbase-*.jar 到 /opt/bigdata/spark/spark2.2/jars/目录下
  • 拷贝/opt/bigdata/hive/hive2.1/lib/h*.jar 到 /opt/bigdata/spark/spark2.2/jars/目录下

三台虚拟机都执行一遍。

说完了怎么存储的问题, 接下来就是看看要存储些啥东西。

3.2.3 用户行为画像关键词获取与权重计算

目标: 获取用户1~25频道(不包括推荐频道)的关键词,并计算权重

步骤:

  1. 读取user_article_basic表,合并行为表文章画像中的主题词
  2. 进行用户权重计算公式,同时落地存储

下面开始。

读取user_article_profile表:

# 获取基本用户行为信息,然后进行文章画像的主题词合并
uup.spark.sql("use profile")
# 取出日志中的channel_id
user_article_ = uup.spark.sql("select * from user_article_basic").drop('channel_id')

uup.spark.sql('use article')
article_label = uup.spark.sql("select article_id, channel_id, topics from article_profile")
# 合并使用文章中正确的channel_id
click_article_res = user_article_.join(article_label, how='left', on=['article_id'])

对channel_id进行处理的原因:日志中的频道号,是通过Web后台进行埋点,有些并没有真正对应文章所属频道(推荐频道为0号频道,获取曝光文章列表时候埋点会将文章对应的频道在日志中是0频道), 这样就得到了下面形式的数据:

在这里插入图片描述这样的主题词列表进行计算权重不方便对于用户的每个主题词权重计算,需要进行explode,也就是再次一行数据拆分成多行。

在这里插入图片描述
代码如下:

# 将字段的列表爆炸
import pyspark.sql.functions as F
click_article_res = click_article_res.withColumn('topic', F.explode('topics')).drop('topics')

3.2.4 用户行为画像之标签权重算法

这里有个公式: 用户标签权重 =( 行为类型权重之和) × 时间衰减

  • 行为类型权重, 分值的确定需要整体协商

    在这里插入图片描述
    完成对关键行为赋予权重分值后,即可开始计算,首先我们把用户浏览(收听、观看)的内容全部按照上面内容标签化的方式打散成标签。

  • 时间衰减: 1 l o g ( t ) + 1 \frac{1}{log(t)+1} log(t)+11​ , t t t为时间发生时间距离当前时间的大小。 因为用户的兴趣并不是一成不变的,而是随时间变化的,所以最新时间发生行为,显然是要比过去的行为关键的。越近发生的行为,时间衰减系数越大, 越远,稀疏越小。

具体代码如下:

# 计算每个用户对每篇文章的标签的权重
def compute_weights(rowpartition):
    """处理每个用户对文章的点击数据
    """
    # 行为类型权重
    weightsOfaction = {
        "read_min": 1,
        "read_middle": 2,
        "collect": 2,
        "share": 3,
        "click": 5
    }

    import happybase
    from datetime import datetime
    import numpy as np
    #  用于读取hbase缓存结果配置
    pool = happybase.ConnectionPool(size=10, host='192.168.19.137', port=9090)

    # 读取文章的标签数据
    # 计算权重值
    # 时间间隔
    # 循环每个用户对应每个关键词处理
    for row in rowpartition:
		
		# 计算间隔时间
        t = datetime.now() - datetime.strptime(row.action_time, '%Y-%m-%d %H:%M:%S')    # 行为发生时间与当前时刻的距离
        # 时间衰减系数
        time_exp = 1 / (np.log(t.days + 1) + 1)  # 里面加1防止log里面是0
		
		# 判断一下这个关键词对应的操作文章时间大小的权重处理
        if row.read_time == '':  # 没有阅读行为
            r_t = 0
        else:
            r_t = int(row.read_time)
        # 浏览时间分数
        is_read = weightsOfaction['read_middle'] if r_t > 1000 else weightsOfaction['read_min']

        # 每个词的权重分数 
        weigths = time_exp * (
                    row.shared * weightsOfaction['share'] + row.collected * weightsOfaction['collect'] + row.
                    clicked * weightsOfaction['click'] + is_read)
# 存储到HBase
#        with pool.connection() as conn:
#            table = conn.table('user_profile')
#            table.put('user:{}'.format(row.user_id).encode(),
#                      {'partial:{}:{}'.format(row.channel_id, row.topic).encode(): json.dumps(
#                          weigths).encode()})
#            conn.close()

click_article_res.foreachPartition(compute_weights)

落地Hbase中之后,在HBASE中查询,happybase或者hbase终端

import happybase
#  用于读取hbase缓存结果配置
pool = happybase.ConnectionPool(size=10, host='192.168.19.137', port=9090)

with pool.connection() as conn:
    table = conn.table('user_profile')
    # 获取每个键 对应的所有列的结果
    data = table.row(b'user:2', columns=[b'partial'])
    conn.close()

同时也可以在Hive中查询:

hive> select * from user_profile_hbase limit 1;
OK
user:1  {"birthday":0.0,"gender":null}  {"18:##":0.25704484358604845,"18:&#":0.25704484358604845,"18:+++":0.23934588700996243,"18:+++++":0.23934588700996243,"18:AAA":0.2747964402379244,"18:Animal":0.2747964402379244,"18:Author":0.2747964402379244,"18:BASE":0.23934588700996243,"18:BBQ":0.23934588700996243,"18:Blueprint":1.6487786414275463,"18:Code":0.23934588700996243,"18:DIR

3.2.5 用户基础信息画像更新

同时对于用户的基础信息也需要更新到用户的画像中。基础信息包括用户的年龄, 性别等。

    def update_user_info(self):
        """
        更新用户的基础信息画像
        :return:
        """
        self.spark.sql("use toutiao")  # toutiao数据库是存储业务数据的

        user_basic = self.spark.sql("select user_id, gender, birthday from user_profile")

        # 更新用户基础信息
        def _udapte_user_basic(partition):
            """更新用户基本信息
            """
            import happybase
            #  用于读取hbase缓存结果配置
            pool = happybase.ConnectionPool(size=10, host='172.17.0.134', port=9090)
            for row in partition:

                from datetime import date
                age = 0
                if row.birthday != 'null':
                    born = datetime.strptime(row.birthday, '%Y-%m-%d')
                    today = date.today()
                    age = today.year - born.year - ((today.month, today.day) < (born.month, born.day))  # 这里过了生日,就增加一岁

                with pool.connection() as conn:
                    table = conn.table('user_profile')
                    table.put('user:{}'.format(row.user_id).encode(),
                              {'basic:gender'.encode(): json.dumps(row.gender).encode()})
                    table.put('user:{}'.format(row.user_id).encode(),
                              {'basic:birthday'.encode(): json.dumps(age).encode()})
                    conn.close()

        user_basic.foreachPartition(_udapte_user_basic)
        logger.info(
            "{} INFO completely update infomation of basic".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))

4. 用户画像的增量更新

这里是为了得到最新的用户数据,包括最新的行为数据和基本信息数据。 具体操作的话和文章那边非常的蕾西,就是先把上面的这些代码进行合并,写到pycharm项目里面去。在offline下面新建立一个update_user.py文件,然后把上面的各个函数写到里面。然后加入到定时处理任务里面:

在update.py文件中加定时更新用户画像的代码逻辑。

from offline.update_user import UpdateUserProfile
def update_user_profile():
    """
    更新用户画像
    """
    # 这些函数就是上面写的那些函数了, 这里是从行为日志里面读取,并合并历史数据,存储
    uup = UpdateUserProfile()
    if uup.update_user_action_basic():    # 如果用新用户了
        uup.update_user_label()         # 画像标签的权重计算以及存储到habase
        uup.update_user_info()         # 更新用户的基础信息

在main.py里面加入这个Job

scheduler.add_job(update_user_profile, trigger='interval', hours=2)

添加之后,进行supervisor的update。

5. 推荐系统应该如何选取和处理特征

这里是额外补充的一块内容,来自王喆老师《深度学习推荐系统》第5长,整理一些特征选取和处理的相关知识。

5.1 推荐系统的特征工程

推荐系统中,特征的本质是对某个行为过程相关信息的抽象表达。构建特征工程的原则:尽可能的抽取出一组特征能够保留推荐环境及用户行为过程中的所有信息,尽量摒弃冗余信息。 比如, 上面的文章推荐场景中, 我们要考虑对于某篇文章用户是否会喜欢的话, 这个过程受什么因素影响呢? 假设我们是用户的话, 至少会考虑:

  1. 自己对于文章类型的兴趣偏好
  2. 该文章是关于什么内容的, 或者是什么主题
  3. 该文章是否是热点文章,是否比较新
  4. 文章的篇幅,字数
  5. 之前是否阅读过该文章
  6. 写文章的作者是不是大牛,或者自己是否喜欢
  7. 周围的环境,比如是否适合阅读啊,设备啊等等

那么做特征的时候,就得考虑上这些因素,然后结合真实给定的数据,进行制作特征。

  • 自己对于文章类型的兴趣偏好: 这个可以从文章自身出发做特征,比如文章的id(进一步可以embedding)
  • 文章的内容或者主题: 文章的关键词信息, 文章的主题
  • 该文章是否已经过时,热点: 可以提取热点特征, 时间特征等
  • 之前是否阅读过该文章或者有没有阅读过类似的: 用户的观看历史序列,是否观看等
  • 文章的篇幅,字数特性
  • 写文章的作者特征
  • 周围环境这个可以尽量的提取,比如地点,设备等

5.2 推荐系统中常用特征

推荐系统中的常用的特征类别如下:

  1. 用户行为数据:
    最常用,最关键的数据,蕴含用户潜在兴趣,对物品的真实评价等。主要包括显性和隐性行为:

    在这里插入图片描述


    显性行为关键,但是很难收集,量少,隐性行为量大,如果可以好好利用,也能挖掘出很重要的信息。在用户的行为类特征处理上,①mult-hot编码作为特征向量 ②embedding然后平均或者加权平均

  2. 用户关系数据
    推荐系统中可以利用的有价值信息,有强弱关系之分。 在推荐系统中,利用用户关系的数据很多种,①将用户关系作为召回层的一种物品召回方式 ②建立用户关系图,然后图embedding生成用户和物品的embedding用于召回 ③直接利用关系数据,通过“好友”特征加到特征上 ④ 利用关系数据直接建立社会化推荐系统

  3. 属性标签类数据
    本质上是描述用户或者物品的特征。包括用户的基本信息(性别,年龄,住址),物品的基本信息描述(类别,价格,主题,字数), 用户兴趣标签,物品标签, 这些信息是非常重要的描述类特征,对于标签的这种,不同的推荐场景处理方式和获取方式不同。 具体利用的话multihot编码或者embedding。

  4. 内容类数据
    这些一般是非结构化的了,需要用到NLP或者CV的一些技术处理成结构化的特征才能使用。比如上面文章推荐里面的大篇幅句子,就是通过NLP里面的分词啊,W2V等,得到关键词的embedding当做文章的标签,然后利用。

  5. 上下文信息
    这是描述行为产生的场景信息。最常用的是时间信息和地点信息。 当然还有什么季节,月份,是否节假日,天气,空气质量,社会大事件等。 引入上下文的目的是尽可能保存推荐行为发生场景的信息。典型的深夜看惊悚电影,旁玩看轻松浪漫电影,晚上看娱乐性的文章,点击热点事件,周六日买商品等等,双11啥的。

  6. 统计类特征
    通过统计方法计算的特征,例如历史CTR,历史CVR,物品热门程度,物品流行程度等。统计特征一般是连续类特征, 仅仅需要标准归一化就可以使用。统计类特征往往与最后的预测目标有较强相关性,所以很重要,本质上是一些粗粒度的预测指标。

  7. 组合类特征
    不同特征组合后生成的新特征。最常见的“年龄+性别”组成的人口属性分段特征。 不过深度学习来了之后,这种特征一般不需要人工过多的做。

5.3 常用的特征处理方法

这里主要是从连续型和离散型两大类进行梳理, 其实我在DIEN的最后面也梳理过了,这里再根据王喆老师写的加以补充:

  1. 连续型特征: 用户年龄,统计类,物品发布时间,影片播放时长等数值型,这种常用手段归一化, 离散化, 加非线性等方法。

    • 归一化: 主要是统一量纲, 将连续型特征归一到[0,1]
    • 离散化: 通过确定分位数的形式,将原来的连续型值分桶。主要目的: 防止连续型特征带来的过拟合及特征值分布不均匀的情况。 离散化之后,就可以embedding了
    • 加非线性函数,直接把原来的特征非线性变换, x a , l o g a ( x ) , l o g ( x 1 − x ) x^a, log_a(x), log(\frac{x}{1-x}) xa,loga​(x),log(1−xx​), 目的是更好的捕获特征与优化目标间的非线性关系,增强模型非线性表达
  2. 类别型特征: 用户的历史行为数据,属性标签类,id等。 最常用的one-hot,然后embedding。历史行为数据一般是变长id,这类embedding之后,分组汇合, 或者RNN的一些思路。

特征工程还是基于工程师对自身业务的处理,不能仅仅依赖于深度模型的,很多深度学习模型的利用需要工程师对业务的洞察,比如W&D, 哪些特征给W,哪些特征给D等。

参考:

标签:离线,用户,user,article,row,画像,id,头条
来源: https://blog.csdn.net/wuzhongqiang/article/details/114667114

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有