4、基于内容的推荐配对:用户画像构建、物品特征提取、基于内容的匹配算法
好,咱们进入第四讲。前面几章我们聊了协同过滤,那种「物以类聚,人以群分」的思路。但现实中有个问题——新用户怎么办?新物品怎么办?这就是所谓的冷启动问题。
我个人习惯的做法是,当数据稀疏到没法做协同过滤时,就果断切换到基于内容的方法。说白了,就是让机器去理解「用户喜欢什么」和「物品有什么」,然后做匹配。
4.1 用户画像构建:给用户贴标签
用户画像,听起来高大上,其实核心就一件事:把用户的行为和属性,转成结构化的特征向量。
我在项目中遇到过这样一个场景:一个婚恋交友平台,用户注册时填了一大堆信息——年龄、职业、兴趣爱好、星座、收入范围……但这些是原始数据,不能直接用来做匹配。你得加工。
4.1.1 显式特征与隐式特征
用户画像分两类特征:
- 显式特征:用户主动填写的。比如「我喜欢运动」、「我月收入2万+」。这些直接可用,但可能不真实(你懂的,有人会夸大)。
- 隐式特征:从行为中挖掘的。比如用户点击了哪些类型的照片、在哪个页面停留时间长、给哪些人点了赞。这些更真实,但需要算法去提取。
4.1.2 画像构建的常用方法
嗯,这里要注意,构建画像不是简单地把字段拼起来。你需要做特征工程:
- 离散特征编码:比如性别、职业,用 one-hot 或 embedding。
- 连续特征归一化:年龄、收入,缩放到 [0,1] 区间,不然收入这个特征会主导一切。
- 文本特征提取:用户写的自我介绍,用 TF-IDF 或 Word2Vec 转成向量。
- 行为特征聚合:过去7天点击了哪些类别,用时间衰减加权。
# 一个简单的用户画像构建示例(伪代码)
def build_user_profile(user_id):
# 显式特征
explicit = {
'age': normalize(user.age, 18, 60),
'income': normalize(user.income, 0, 100000),
'gender_onehot': one_hot(user.gender),
'job_embedding': job_to_vec(user.job)
}
# 隐式特征(从行为日志中提取)
implicit = {
'click_category_dist': get_click_distribution(user_id, days=30),
'avg_stay_time': get_avg_stay_time(user_id),
'like_ratio': get_like_ratio(user_id)
}
return merge_features(explicit, implicit)
4.2 物品特征提取:让机器看懂「东西」
用户画像搞定了,接下来是物品。在配对场景里,物品可以是人(相亲对象)、商品(推荐商品)、文章(内容推荐)。
你想想看,一个物品有哪些特征?
- 结构化属性:价格、品牌、类别、尺寸……这些直接拿过来用就行。
- 非结构化内容:图片、文本、视频。这些需要深度学习模型来提取特征。
4.2.1 文本特征提取
我记得有一次做简历匹配系统,需要提取职位描述和候选人简历的文本特征。当时用了两种方法:
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| TF-IDF | 短文本、关键词匹配 | 简单、可解释性强 | 无法捕捉语义 |
| BERT Embedding | 长文本、语义匹配 | 理解上下文 | 计算量大 |
| Word2Vec + 平均池化 | 中等长度文本 | 效率与效果平衡 | 忽略词序 |
4.2.2 图像特征提取
如果物品是图片(比如头像、商品图),我会用预训练的 CNN 模型(ResNet、EfficientNet)提取特征向量。这些向量通常有 512 或 2048 维,直接作为物品的「视觉指纹」。
# 使用预训练模型提取图像特征
from torchvision import models, transforms
import torch
model = models.resnet18(pretrained=True)
model = torch.nn.Sequential(*list(model.children())[:-1]) # 去掉分类层
def extract_image_features(img_tensor):
with torch.no_grad():
features = model(img_tensor)
return features.flatten().numpy() # 输出512维向量
4.3 基于内容的匹配算法
好,现在用户画像有了,物品特征也有了。怎么匹配?
说白了,就是计算用户画像向量和物品特征向量之间的相似度。我常用的方法有这么几种:
4.3.1 余弦相似度
最经典的方法。把用户和物品都表示成向量,计算夹角余弦值。值越接近1,说明越匹配。
def cosine_similarity(user_vec, item_vec):
dot = sum(u * i for u, i in zip(user_vec, item_vec))
norm_u = sum(u**2 for u in user_vec) ** 0.5
norm_i = sum(i**2 for i in item_vec) ** 0.5
return dot / (norm_u * norm_i + 1e-8) # 加小常数防除零
4.3.2 加权匹配
不是所有特征都同等重要。比如在婚恋场景,「价值观」的权重应该远大于「身高」。所以我会给每个特征维度加一个权重:
def weighted_match(user_profile, item_features, weights):
score = 0
for key in user_profile:
sim = cosine_similarity(user_profile[key], item_features[key])
score += weights.get(key, 1.0) * sim
return score / sum(weights.values())
4.3.3 基于决策树的匹配规则
有时候,简单的规则比复杂模型更有效。我习惯先用决策树做一轮硬过滤:
- 年龄差 < 5岁 → 保留
- 收入差距 < 50% → 保留
- 至少有一个共同爱好 → 保留
- 否则 → 直接过滤掉
然后再对通过过滤的候选集做精细的相似度排序。这样既保证了效率,又保证了质量。
4.4 知识体系总览
下面这张图,是我自己总结的基于内容推荐配对的完整流程。你看一眼,就能把整个章节串起来:
4.5 避坑指南
最后,分享几个我亲身踩过的坑:
- 特征维度爆炸:one-hot 编码几百个职业,结果向量稀疏得没法算相似度。后来改用 embedding,维度降到 32 维,效果反而更好。
- 冷启动不是万能药:基于内容的方法虽然能解决冷启动,但容易陷入「信息茧房」——推荐来推荐去都是同类内容。需要配合探索策略(比如随机曝光一些新类别)。
- 用户画像要动态更新:人的偏好会变。我见过一个系统,用户画像三个月不更新,推荐结果越来越差。后来加了实时流处理,每 6 小时更新一次画像,点击率提升了 23%。
嗯,这一章的内容就到这儿。基于内容的推荐配对,核心就是「理解用户、理解物品、做匹配」。方法不复杂,但细节决定成败。