这几天测试了下 aws 的 personalize service, 看看能不能替换掉产品里现有的一些推荐逻辑.
大致的流程:
- 导入数据
- 选择 recipe 进行 training, 得到一个 solution version
- 选择最佳 solution version 创建 compaign
- 调用 api, 根据 compaign 得到 recommendations
一些 iam 权限相关的设置就不写了, 具体看文档吧, 这里只记录下主要步骤.
导入数据
首先需要准备用来 training 的数据, 分成三种数据集:
- User
- Item
- User-Item interaction
其中 User-Item interaction 是必须的 dataset, 所有 recipe 都会用到, User 和 Item 被称作 metadata dataset, 只有个别 recipe 会用到.
每个 dataset 创建的时候需要建立一个 schema, 来描述各自的结构(avro 格式).
example User schema:
{
"type": "record",
"name": "Users",与与
"namespace": "com.amazonaws.personalize.schema",
"fields": [
{
"name": "user_id",
"type": "string"
},
{
"name": "birthday",
"type": "int与
},
{
"name": "gender",
"type": "string",
"categorical": true
},
{
"name": "location",
"type": "string",
"categorical": true
}
],
"version": "1.0"
}
其中 user_id
是必填字段, 其他都是可选的自定义字段, 其他字段如果是 string, 必须加上"categorical": true
, 表示它是用来分类的.
example Item schema:
{
"type": "record",
"name": "Items",
"namespace": "com.amazonaws.personalize.schema",
"fields": [
{
"name": "item_id",
"type": "string"
},
{
"name": "category",
"type": "string",
"categorical": true,
},
{
"name": "bookmarks",
"type": "int"
}
],
"version": "1.0"
}
item_id
必填, 其他性质同 User dataset.
example User-Item interaction schema:
{
"type": "record",
"name": "Interactions",
"namespace": "com.amazonaws.personalize.schema",
"fields": [
{
"name": "user_id",
"type": "string"
},
{
"name": "item_id",
"type": "string"
},与{
"name": "event_type",
"type": "string"
},
{
"name": "event_value",
"type": "float"
},
{
"name": "timestamp",
"type": "long"
}
],
"version": "1.0"
}
user_id
, item_id
, timestamp
必填, event_type
, event_value
可选.
这张 schema 用来记录 user 和 item 的交互信息.
比如 item 是一系列帖子, 那我们就能用 event_type
来记录用户的 view, upvote, downvote, reply 等不同行为, event_value
可以给不同的 event_type
设置不同的权重.
training 的时候也能根据 event_type
做筛选, 只使用一种来 training. 或 event_value
高于某个值的数据进行 training.
之后在 dataset 页面里创建一个 import job, 就能从 s3 导入数据了(这里需要正确设置 iam role 和 s3 bucket policy). 大概需要十几分钟.
不方便的地方
- dataset schema 没法更改, 只能创建一个新的 dataset 然后再建新的 dataset schema, 重新导入数据.
- 界面上没法删除 dataset schema, 只能用 aws cli 进行删除.
- schema 中定义的每一个字段,导入时都不能为空.
增量数据
User-Item interaction 数据可以通过创建 event tracker 来增量更新, 不需要重新 train.
User 和 Item 没办法增量更新,只能重新 import 全量数据, 这个有点傻,不过使用的 recipe 没用到它们就无所谓.
选择 recipe 进行 training
支持以下几种预定义的 recipe:
- popularity-count, 计算全局热度, 只使用 User-Item interaction, 逻辑和线上现有的排序算法类似, 不是个性化的推荐, 任何
user_id
得到的推荐结果都一样的, 一般可以作为一个 baseline 的结果, 和其他 recipe 做对比. - sims(item-to-item similarities), 传统个性化推荐系统的做法, 基于协同过滤, 只使用 User-Item interaction. 使用的时候 input 是
item_id
, 返回相似的 item, 如果item_id
不存在或没有足够的交互数据,会返回全局最热的 item. - HRNN, 基于循环神经网络, 只使用 User-Item interaction, 特点是可以根据时序给用户的历史行为设定权重, 每个用户得到的推荐都是不一样的, input 是
user_id
, output 是 list ofitem_id
. RNN 用于用户推荐的缺点是无法记录太长的用户行为, HRNN 把用户的历史行为划分成不同的 session, 假定用户在同一个 session 内兴趣都是一样的, 比如现在我在找战争电影, 那我接下来一段时间的浏览行为都和战争电影有关系, 如果我超过半小时没有活动, 就进入一个新的 session. - HRNN-metadata, 基于 HRNN, metadata 指的是 User 和 Item dataset, 如果这两者有比较好的可用于分类的数据, 比如 age, location, gender 等, 加入训练可能得到比 HRNN 更好的数据.
- HRNN-coldstart, 基于 HRNN-metadata, 使用场景是你要经常加入新的 item, 一般新的 item 用户交互都比较少(这样的 item 被认为是 coldstart), 而你又希望给这些新的 item 预热(warm up), 而不是那些老的很热门的东西, 这个 recipe 能 filter 出一个数据子集来进行 train, 比如只推荐5天内用户交互行为少于 10 次的 item.
- Personalized-Ranking: 基于 HRNN, 只使用 User-Item interaction, 给一个 user_id 和 一个列表的
item_id
, 根据用户的历史交互情况对 item 进行排序后返回. 使用场景如先用 ElasticSearch 根据用户输入的 keyword 找出一列 item, 再由 personalize 根据用户的历史 行为对结果进行排序.
每种 recipe 的 training 时间不一样, 在我的测试过的 recipe 中耗时依次是 HRNN-coldstart > HRNN-metadata > sims > popularity-count. popularity-count training hour 大概1小时, HRNN-coldstart training hour 花了 39 小时. training hour 是消耗的机器时间, 并行的, 我这边实际的等待时间是半小时到2小时.
评估 solution 的效果
personalize 把输入的 dataset, 随机划分成90% 的 training data 和 10% 的 testing data, solution 使用 training data 创建. 测试时候会使用 training data 中最老的 90% 作为输入, 将生成结果和 testing data 中最近的 10% 进行比较.
评价 solution 好坏有以下几个 metrics, 都是数字越大越好:
- coverage: 所有查询中返回的 items 个数在所有 items 中的占比
- mean_reciprocal_rank_at_25: 在 top 25 个推荐中, 第一个命中的推荐所在位置的倒数, 假如命中了2,10,15 位, 这个值就是 1/2, 如果你对单个排名最高的推荐感兴趣, 可以看这个数字.
- normalized_discounted_cumulative_gain_at_K: 这个指标根据命中的推荐所在位置,每个给一个权重, 再相加. 基本假设是排名越靠前的推荐用户越感兴趣, 权重就越高. 权重系数是
1/log(1+position)
, 比如查询结果中第2,5位命中了, 计算1/log(1+2) + 1/log(1+5)
, 得到的值叫DCG(discounted cumulative gain), 而理想情况下最好当然是命中第1,2位了,理想值应该是1/log(1+1) +1/log(1+2)
, 这个理想值叫做 ideal DCG. 我们需要的 NDCG = DCG/idea DCG, 即(1/log(1+2) + 1/log(1+5))/(1/log(1+1) + 1/log(1+2)) = 0.6241
- precision_at_K: 前 K 个推荐中命中数目除以K.
白话一点, personlaize 的评价标准是推荐结果中的命中推荐出现的越早越好, 命中数目越多越好.
创建 compaign
solution 就是 train 好的 model, 要使用它需要创建一个关联的 compaign, 才能在 api 中调用. 创建 compaign 时候指定需要的最小 tps, 如果实际 tps 超过这个值会自动 scale up. campaign 也可以理解成一个 api endpoint
Get recommendations
用 python 获取推荐的例子:
import boto3
client = boto3.client('personalize-runtime')
response = client.get_recommendations(
campaignArn='arn:aws:personalize:us-east-1:xxxxx:campaign/name',
userId='111111',
)
根据 user id 得到推荐的 recommendation, 适用于 popularity-count, hrnn, hrnn-metadata, hrnn-coldstart.
response = client.get_recommendations(
campaignArn='arn:aws:personalize:us-east-1:xxxxx:campaign/name',
itemId='2222',
)
由 item id 得到相似 item 推荐, 适用于 sims.
response = client.get_personalized_ranking(
campaignArn='string',
inputList=[
'aaaa',
'bbbb',
...
],
userId='11111',
)
输入 user id, 和一列 item id, 根据用户的历史兴趣对 inputList 进行排序返回. 适用于 Personalized-Ranking.