4、基于内容的推荐(CB):特征提取与用户画像构建
好,咱们进入第四章节。基于内容的推荐,简称CB。说白了,就是让系统学会「看人下菜碟」——根据你过去喜欢什么,再给你推荐类似的东西。
我个人习惯把CB推荐比作一个「老派但靠谱」的图书管理员。他不认识你,但看你借过《三体》,就会把《银河帝国》递给你。嗯,就是这么个逻辑。
4.1 特征提取:标签、关键词、类别
特征提取是CB的基石。你想想看,如果连物品长什么样都描述不清,怎么推荐?
我在项目中遇到过最典型的场景:一个电商平台,商品标题是「新款春季男士休闲运动鞋」。机器怎么理解这双鞋?靠的就是特征提取。
4.1.1 标签提取
标签是人工或半自动打上去的。比如「运动」、「休闲」、「春季」。标签的好处是精准,坏处是成本高。
我曾经在一个视频网站项目里,光给100万条视频打标签,就花了三个团队两个月。所以,别迷信标签,能用算法解决的尽量用算法。
4.1.2 关键词提取
关键词是从文本里自动抽出来的。常用的方法有TF-IDF、TextRank。ES里自带了一个叫more_like_this的查询,其实就是关键词匹配的升级版。
举个例子,一篇新闻稿:「苹果公司发布新款iPhone,搭载A17芯片」。关键词可能是「苹果」、「iPhone」、「A17芯片」。
// ES中关键词提取的简单示例
// 使用more_like_this查询
GET /articles/_search
{
"query": {
"more_like_this": {
"fields": ["title", "content"],
"like": "苹果公司发布新款iPhone",
"min_term_freq": 1,
"max_query_terms": 12
}
}
}
这里要注意,min_term_freq设成1,意味着只要出现一次就算。我建议根据数据量调整,一般设2-3比较稳妥。
4.1.3 类别提取
类别是粗粒度的特征。比如「科技」、「体育」、「娱乐」。类别通常用分类模型或规则来提取。
我记得有一次做新闻推荐,类别搞错了——把「科技」新闻分到了「娱乐」类。结果用户点进来一看是芯片技术,直接跳出。嗯,分类的准确性直接影响用户体验。
| 特征类型 | 提取方式 | 适用场景 | 我踩过的坑 |
|---|---|---|---|
| 标签 | 人工/半自动 | 电商、视频 | 成本高,容易过时 |
| 关键词 | TF-IDF/TextRank | 新闻、文章 | 同义词问题没处理好 |
| 类别 | 分类模型/规则 | 全品类 | 类别粒度太粗或太细 |
4.2 用户画像构建
用户画像,说白了就是给用户「贴标签」。你想想看,一个用户看了10篇科技文章,点了5次体育新闻,收藏了3个美食视频。那他的画像就是:科技重度、体育中度、美食轻度。
构建用户画像,我一般分三步走:
- 收集行为数据:点击、收藏、购买、停留时长
- 计算特征权重:用时间衰减、行为类型加权
- 存储到ES:用嵌套文档或扁平结构
我曾经犯过一个错误:把所有行为一视同仁。结果一个用户误点了一次「减肥产品」,系统就疯狂推荐减肥药。后来我加了行为权重——购买权重10分,收藏5分,点击1分。这才正常。
// ES中用户画像的存储结构示例
PUT /user_profile/_doc/1001
{
"user_id": 1001,
"profile": {
"tag_weights": {
"科技": 0.85,
"体育": 0.45,
"美食": 0.30,
"娱乐": 0.10
},
"last_updated": "2024-01-15T10:30:00Z"
}
}
4.3 ES中实现基于内容的召回
好,特征有了,画像有了,怎么召回?ES提供了两种主力查询:multi_match和term查询。
4.3.1 使用multi_match进行多字段匹配
multi_match适合关键词匹配。比如用户画像里有「科技」、「iPhone」,那我们就去文章索引里找标题或内容包含这些词的文章。
// 基于用户画像的multi_match召回
GET /articles/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "科技 iPhone 芯片",
"fields": ["title^3", "tags^2", "content"],
"type": "best_fields",
"tie_breaker": 0.3
}
}
]
}
},
"size": 20
}
这里我用了title^3,意思是标题匹配的权重是3倍。为什么?因为标题通常最能代表文章内容。这是我踩过坑后总结的——一开始所有字段权重一样,结果内容里出现一次「科技」就排前面,标题里出现反而不重要,这不合理。
4.3.2 使用term查询进行精确匹配
term查询适合类别、标签这种精确字段。比如用户画像显示用户喜欢「科技」类别,那就直接term查询。
// 基于类别的term召回
GET /articles/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"category": "科技"
}
}
],
"must": [
{
"match": {
"status": "published"
}
}
]
}
},
"size": 50
}
注意,我用的是filter而不是must。为什么?因为filter不参与评分计算,性能更好。类别匹配不需要算分,只要「是或不是」就行。
4.3.3 组合使用:画像驱动的召回策略
在实际项目中,我很少只用一种查询。我习惯把multi_match和term组合起来:
// 组合召回策略
GET /articles/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "科技 iPhone 芯片",
"fields": ["title^3", "tags^2", "content"],
"type": "best_fields",
"tie_breaker": 0.3
}
}
],
"filter": [
{
"term": {
"category": "科技"
}
},
{
"range": {
"publish_date": {
"gte": "now-7d"
}
}
}
]
}
},
"size": 20
}
这个查询的意思是:先过滤出「科技」类别且7天内发布的文章,再按关键词匹配度排序。嗯,这样既保证了相关性,又保证了时效性。
好了,这一章的内容就这些。特征提取是基础,用户画像是核心,ES查询是工具。三者缺一不可。下一章我们会聊协同过滤,那又是另一套玩法了。