导读:本文将深入浅出地介绍RALM(Real-time Attention-based Look-alike Model)模型在LBS社交匹配系统中的应用。我们将从模型的核心原理出发,结合实际业务场景,详细讲解模型的架构设计、工程实现以及优化技巧,帮助读者全面理解这一创新性的推荐算法。
一、背景与问题引入
1.1 业务场景
想象一下这样的场景:你打开一个社交应用,想要找到附近志趣相投的人。传统的"附近的人"功能只能告诉你谁在你周围,但无法判断这些人是否真的适合你。这就是我们要解决的核心问题:如何在海量用户中,找到既在你附近,又与你相似的人?
这个问题看似简单,实则包含了两个核心挑战:
- 地理位置匹配:如何快速找到附近的用户?(已通过Geohash技术解决)
- 特征相似度匹配:如何判断用户之间是否真正相似?(本文重点)
1.2 传统方案的局限
在RALM模型出现之前,业界通常采用以下几种方案:
方案1:基于规则的匹配
- 通过人工设定规则(如年龄、性别、兴趣标签)进行匹配
- 问题:规则简单粗暴,无法捕捉复杂的用户行为模式
方案2:协同过滤
- 基于"相似用户喜欢相似内容"的假设
- 问题:冷启动问题严重,新用户缺乏历史数据
方案3:传统深度学习模型(如DNN)
- 使用深度神经网络学习用户特征
- 问题:特征融合方式简单(concat),无法动态调整特征权重
1.3 RALM模型的创新点
RALM模型由腾讯微信团队在KDD 2019上提出,专门用于解决Look-alike人群扩展问题。它的核心创新包括:
- 双塔架构:种子用户和目标用户分别通过独立的表示学习塔
- 注意力机制:动态调整不同特征域的重要性
- 实时性:支持在线更新,无需频繁重训练整个模型
- 多样性:通过Self-Attention和Productive Attention捕捉复杂关系
二、RALM模型核心原理
2.1 什么是Look-alike?
Look-alike(相似人群扩展):给定一组种子用户,找到与他们相似的目标用户。
举个通俗的例子:
- 种子用户:你的好友圈(假设都是喜欢运动的人)
- 目标用户:所有在你附近的人
- Look-alike的目标:找出那些虽然不在你好友圈,但和你好友圈的人兴趣相似的人
2.2 RALM模型架构
RALM模型采用经典的双塔结构(Two-Tower Architecture):
┌─────────────────────────────────────────────────────┐
│ RALM模型架构 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 种子用户 │ │ 目标用户 │ │
│ │ 特征输入 │ │ 特征输入 │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 种子塔 │ │ 目标塔 │ │
│ │ (Seed Tower)│ │(Target Tower)│ │
│ │ │ │ │ │
│ │ 1. DNN层 │ │ 1. DNN层 │ │
│ │ 2. 批归一化 │ │ 2. 批归一化 │ │
│ │ 3. 激活函数 │ │ 3. 激活函数 │ │
│ │ 4. Dropout │ │ 4. Dropout │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │Self-Attention│ │Self-Attention│ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Productive │ │ Productive │ │
│ │ Attention │ │ Attention │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │空间转换(PReLU)│ │空间转换(PReLU)│ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ └──────────┬──────────────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 特征拼接 │ │
│ └──────┬───────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │相似度计算层 │ │
│ │(MLP + Softmax)│ │
│ └──────┬───────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │相似度得分 │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
2.3 注意力机制详解
RALM模型使用了两种注意力机制:
2.3.1 Self-Attention(自注意力)
作用:捕捉特征内部的相互关系。
原理:
# Self-Attention的计算过程
Q = W_q * X # Query矩阵
K = W_k * X # Key矩阵
V = W_v * X # Value矩阵
# 计算注意力权重
Attention_Score = softmax(Q * K^T / sqrt(d_k))
# 加权求和
Output = Attention_Score * V
通俗理解: 想象你在分析一个用户的特征:
- 用户经常在咖啡馆打卡(位置特征)
- 用户喜欢阅读文学作品(兴趣特征)
- 用户周末活跃度高(时间特征)
Self-Attention能够发现这些特征之间的关联:
- "咖啡馆" + "阅读" → 可能是文艺青年
- "周末活跃" + "咖啡馆" → 可能有较多空闲时间
2.3.2 Productive Attention(生产性注意力)
作用:通过外积计算更复杂的特征交互。
原理:
# Productive Attention使用外积
Q_expanded = Q.unsqueeze(-1) # 扩展维度
K_expanded = K.unsqueeze(-2)
# 计算外积
Outer_Product = Q_expanded * K_expanded
# 得到更丰富的特征表示
Attention_Score = softmax(sum(Outer_Product, dim=-1))
Output = Attention_Score * V
通俗理解: 如果说Self-Attention是在问"哪些特征重要",那么Productive Attention就是在问"哪些特征组合重要"。它能捕捉更复杂的模式,比如:
- "年龄25-30" + "互联网从业" + "北京" → 典型的北漂程序员画像
三、特征工程实践
3.1 用户特征体系
在我们的LBS社交匹配系统中,用户特征分为六大维度:
3.1.1 地理位置特征(Geographic Features)
包含内容:
# 1. 基础位置信息(2维)
- 纬度归一化:lat / 90.0
- 经度归一化:lon / 180.0
# 2. Geohash编码特征(4维)
- 不同精度级别的Geohash编码:[6位, 7位, 8位, 9位]
- 示例:wx4g0e → 转换为数值特征
# 3. 位置稳定性(1维)
- 根据历史轨迹计算位置变化的标准差
- 分数越高表示用户位置越稳定
# 4. 活动范围(1维)
- 计算用户活动的最大半径
- 反映用户的移动模式
# 5. 位置偏好(5维)
- 家庭、工作、娱乐、购物、交通等场所的偏好分数
业务价值:
- 位置稳定性高的用户可能是本地居民,更适合长期社交
- 活动范围大的用户可能喜欢户外活动,适合推荐运动类好友
3.1.2 行为特征(Behavioral Features)
时间窗口设计:
# 多时间窗口捕捉用户行为模式
time_windows = [1天, 7天, 30天]
# 每个时间窗口提取
for window in time_windows:
- 交互频率:normalize(interaction_count / 100)
- 交互多样性:unique_types / all_types
- 时间分布:[早晨, 下午, 晚上, 深夜] 的活跃度
应用使用特征:
# 1. 使用时长(1维)
daily_minutes / 1440.0 # 归一化到24小时
# 2. 使用频率(1维)
session_count / 50.0
# 3. 活跃时段(4维)
[morning_active, afternoon_active, evening_active, night_active]
业务洞察:
- 早晨活跃的用户可能是上班族,适合推荐通勤路线附近的人
- 深夜活跃的用户可能是夜猫子,匹配时考虑生活作息
3.1.3 社交特征(Social Features)
社交网络指标:
# 1. 基础社交指标(2维)
- 好友数量:min(friend_count / 1000, 1.0)
- 共同好友数:min(mutual_friends / 500, 1.0)
# 2. 网络密度(1维)
- 密度 = 互相关注数 / 总关注数
- 反映社交圈的紧密程度
# 3. 影响力指标(1维)
- 影响力 = (关注者数 * 互动率) / 1000
- 识别KOL和普通用户
# 4. 社交活跃度(3维)
- 发帖频率、评论频率、分享频率
# 5. 社交圈层(2维)
- 圈层数量、圈层活跃度
业务应用:
- 高影响力用户匹配高影响力用户,提高社交体验
- 社交圈层相似的用户更容易建立联系
3.1.4 人口统计特征(Demographic Features)
# 1. 年龄组编码(1维)
age_groups = [18, 25, 35, 45, 55, 65]
age_group_index / len(age_groups)
# 2. 性别(1维)
gender_code # 0/1编码
# 3. 教育程度(1维)
education_level / 5 # 归一化到5个级别
# 4. 收入水平(1维)
income_level / 5
# 5. 职业类型(1维)
occupation_types = ["student", "professional", "service", "business", "other"]
occupation_code / len(occupation_types)
# 6. 婚姻状况(1维)
marital_status / 4
# 7. 居住地(2维)
- 城市等级:city_level / 5
- 居住时长:min(residence_years / 20, 1.0)
# 8. 语言能力(1维)
min(language_count / 5, 1.0)
3.1.5 偏好特征(Preference Features)
# 1. 兴趣类别(20维)
interest_categories = [
"运动", "音乐", "旅行", "美食", "阅读",
"游戏", "艺术", "科技", "时尚", "自然", ...
]
# One-hot编码 + 强度权重
# 2. 活动偏好(10维)
activity_types = [
"sports", "music", "travel", "food", "reading",
"gaming", "art", "technology", "fashion", "nature"
]
# 每个活动的偏好分数
# 3. 位置偏好(15维)
location_types = [
"urban", "suburban", "rural", "coastal", "mountain",
"park", "mall", "restaurant", "cafe", "gym", ...
]
# 4. 时间偏好(6维)
- 工作日偏好、周末偏好
- 四季偏好:春夏秋冬
3.1.6 上下文特征(Contextual Features)
# 1. 时间上下文(3维)
- 当前小时:hour / 24
- 星期几:weekday / 7
- 月份:month / 12
# 2. 天气上下文(2维)
- 温度:(temperature + 20) / 60 # 归一化到-20至40度
- 天气状况:weather_code / 5
# 3. 事件上下文(2维)
- 附近事件数量:min(event_count / 10, 1.0)
- 事件类型多样性
# 4. 交通上下文(1维)
- 拥堵程度:congestion_level / 5
3.2 特征处理技巧
3.2.1 特征归一化
为什么需要归一化?
- 不同特征的量纲差异巨大(如好友数可能是0-5000,而经纬度是-180到180)
- 归一化后模型训练更稳定,收敛更快
归一化方法:
# 1. Min-Max归一化(适用于有明确边界的特征)
normalized_value = (value - min_value) / (max_value - min_value)
# 示例:年龄归一化
age_normalized = (age - 18) / (65 - 18)
# 2. 上限截断归一化(适用于无明确上界的特征)
normalized_value = min(value / threshold, 1.0)
# 示例:好友数归一化
friends_normalized = min(friend_count / 1000, 1.0)
3.2.2 时间衰减
为什么需要时间衰减?
- 用户的兴趣和行为会随时间变化
- 近期行为比历史行为更能代表当前状态
指数衰减实现:
def time_decay_weight(timestamp, decay_factor=0.95):
"""计算时间衰减权重"""
days_ago = (current_timestamp - timestamp) / 86400 # 转换为天数
weight = decay_factor ** days_ago
return weight
# 应用示例
for interaction in user_interactions:
weight = time_decay_weight(interaction.timestamp)
weighted_score = interaction.score * weight
3.2.3 Embedding处理
对离散特征的处理:
# Geohash编码转换为数值
def geohash_to_numeric(geohash_str, precision):
"""将Geohash字符串转换为数值特征"""
# Geohash使用32进制编码
hash_value = int(geohash_str[:precision], 32)
# 归一化
normalized_value = hash_value / (32 ** precision)
return normalized_value
# 示例
geohash = "wx4g0e"
feature_6 = geohash_to_numeric(geohash, 6) # 6位精度
feature_7 = geohash_to_numeric(geohash, 7) # 7位精度
四、模型实现详解
4.1 代码架构
项目的RALM模型实现位于两个核心文件:
app/models/ralm_model.py:模型定义app/services/ralm_model.py:模型服务(完全相同,用于服务层)
4.2 核心组件实现
4.2.1 Self-Attention实现
class SelfAttention(nn.Module):
"""自注意力机制"""
def __init__(self, hidden_dim: int, num_heads: int = 8, dropout: float = 0.1):
super().__init__()
self.hidden_dim = hidden_dim
self.num_heads = num_heads
self.head_dim = hidden_dim // num_heads # 每个头的维度
# 定义Q、K、V的线性变换
self.query = nn.Linear(hidden_dim, hidden_dim)
self.key = nn.Linear(hidden_dim, hidden_dim)
self.value = nn.Linear(hidden_dim, hidden_dim)
self.dropout = nn.Dropout(dropout)
self.output_proj = nn.Linear(hidden_dim, hidden_dim)
def forward(self, x: torch.Tensor) -> torch.Tensor:
batch_size, seq_len, _ = x.size()
# 计算Q, K, V并重塑为多头格式
# [batch, seq_len, hidden_dim] → [batch, num_heads, seq_len, head_dim]
Q = self.query(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
K = self.key(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
V = self.value(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# 计算注意力分数:Q * K^T / sqrt(d_k)
scores = torch.matmul(Q, K.transpose(-2, -1)) / np.sqrt(self.head_dim)
# Softmax归一化得到注意力权重
attention_weights = F.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)
# 应用注意力权重到V
context = torch.matmul(attention_weights, V)
# 重塑回原始维度
context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_dim)
# 输出投影
output = self.output_proj(context)
return output
关键设计点:
-
多头注意力(Multi-Head Attention)
- 将hidden_dim分割为num_heads个头
- 每个头独立计算注意力,捕捉不同方面的特征关系
- 最后将所有头的输出拼接
-
缩放点积(Scaled Dot-Product)
- 除以sqrt(head_dim)防止梯度消失
- 对于大的head_dim,点积结果可能很大,softmax会退化
-
Dropout正则化
- 在attention_weights上应用dropout
- 防止过拟合,提高泛化能力
4.2.2 Productive Attention实现
class ProductiveAttention(nn.Module):
"""生产性注意力机制"""
def __init__(self, hidden_dim: int, num_heads: int = 8, dropout: float = 0.1):
super().__init__()
self.hidden_dim = hidden_dim
self.num_heads = num_heads
self.head_dim = hidden_dim // num_heads
self.query = nn.Linear(hidden_dim, hidden_dim)
self.key = nn.Linear(hidden_dim, hidden_dim)
self.value = nn.Linear(hidden_dim, hidden_dim)
self.dropout = nn.Dropout(dropout)
self.output_proj = nn.Linear(hidden_dim, hidden_dim)
def forward(self, x: torch.Tensor) -> torch.Tensor:
batch_size, seq_len, _ = x.size()
# 计算Q, K, V
Q = self.query(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
K = self.key(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
V = self.value(x).view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
# 生产性注意力:使用外积计算注意力分数
Q_expanded = Q.unsqueeze(-1) # [batch, heads, seq_len, head_dim, 1]
K_expanded = K.unsqueeze(-2) # [batch, heads, seq_len, 1, head_dim]
# 计算外积:[batch, heads, seq_len, seq_len, head_dim]
outer_product = torch.matmul(Q_expanded, K_expanded)
# 求和得到注意力分数
scores = torch.sum(outer_product, dim=-1) # [batch, heads, seq_len, seq_len]
attention_weights = F.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)
# 应用注意力权重
context = torch.matmul(attention_weights, V)
context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.hidden_dim)
# 输出投影
output = self.output_proj(context)
return output
与Self-Attention的区别:
| 特性 | Self-Attention | Productive Attention |
|---|---|---|
| 计算方式 | Q * K^T | Q ⊗ K(外积) |
| 复杂度 | O(n²d) | O(n²d²) |
| 特征交互 | 线性交互 | 高阶交互 |
| 适用场景 | 捕捉基本依赖关系 | 捕捉复杂组合模式 |
4.2.3 User Representation Tower实现
class UserRepresentationTower(nn.Module):
"""用户表示学习塔"""
def __init__(self, input_dim: int = 64, hidden_dims: List[int] = [128, 256, 128],
output_dim: int = 64):
super().__init__()
self.input_dim = input_dim
self.hidden_dims = hidden_dims
self.output_dim = output_dim
# 构建多层网络
layers = []
prev_dim = input_dim
for hidden_dim in hidden_dims:
layers.extend([
nn.Linear(prev_dim, hidden_dim), # 全连接层
nn.BatchNorm1d(hidden_dim), # 批归一化
nn.ReLU(), # 激活函数
nn.Dropout(0.2) # Dropout正则化
])
prev_dim = hidden_dim
# 输出层
layers.append(nn.Linear(prev_dim, output_dim))
self.network = nn.Sequential(*layers)
# 注意力机制
self.self_attention = SelfAttention(output_dim)
self.productive_attention = ProductiveAttention(output_dim)
# 空间转换
self.spatial_transform = nn.Sequential(
nn.Linear(output_dim, output_dim),
nn.PReLU() # Parametric ReLU
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# 1. 通过基础网络提取初步特征
features = self.network(x)
# 2. 添加序列维度用于注意力机制
if len(features.shape) == 2:
features = features.unsqueeze(1) # [batch, 1, features]
# 3. 自注意力层
attended_features = self.self_attention(features)
# 4. 生产性注意力层
productive_features = self.productive_attention(attended_features)
# 5. 空间转换
transformed_features = self.spatial_transform(productive_features)
# 6. 返回最终表示
return transformed_features.squeeze(1) # [batch, features]
设计亮点:
-
渐进式特征提取
- 64 → 128 → 256 → 128 → 64
- 先扩展维度捕捉更多信息,再压缩维度提取核心特征
-
Batch Normalization
- 加速训练收敛
- 缓解梯度消失/爆炸问题
-
双层注意力
- Self-Attention:捕捉特征内部关系
- Productive Attention:捕捉特征组合模式
-
PReLU激活函数
- 相比ReLU,允许负值有小幅度输出
- 参数可学习,增强模型表达能力
4.2.4 RALM完整模型
class RALMModel(nn.Module):
"""RALM模型 - 实时注意力机制下的Look-alike模型"""
def __init__(self, input_dim: int = 64, hidden_dims: List[int] = [128, 256, 128],
output_dim: int = 64, num_classes: int = 2):
super().__init__()
self.input_dim = input_dim
self.output_dim = output_dim
self.num_classes = num_classes
# 种子用户塔(专门学习种子用户的特征表示)
self.seed_tower = UserRepresentationTower(input_dim, hidden_dims, output_dim)
# 目标用户塔(专门学习目标用户的特征表示)
self.target_tower = UserRepresentationTower(input_dim, hidden_dims, output_dim)
# 相似度计算层
self.similarity_layer = nn.Sequential(
nn.Linear(output_dim * 2, hidden_dims[-1]),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(hidden_dims[-1], hidden_dims[-1] // 2),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(hidden_dims[-1] // 2, num_classes)
)
# 初始化权重
self._init_weights()
def _init_weights(self):
"""初始化模型权重"""
for module in self.modules():
if isinstance(module, nn.Linear):
nn.init.xavier_uniform_(module.weight) # Xavier初始化
if module.bias is not None:
nn.init.zeros_(module.bias)
def forward(self, seed_features: torch.Tensor, target_features: torch.Tensor) -> torch.Tensor:
"""
前向传播
Args:
seed_features: 种子用户特征 [batch_size, input_dim]
target_features: 目标用户特征 [batch_size, input_dim]
Returns:
相似度得分 [batch_size, num_classes]
"""
# 通过各自的塔获取用户表示
seed_representation = self.seed_tower(seed_features)
target_representation = self.target_tower(target_features)
# 拼接特征
combined_features = torch.cat([seed_representation, target_representation], dim=1)
# 计算相似度得分
similarity_scores = self.similarity_layer(combined_features)
return similarity_scores
def get_user_representation(self, user_features: torch.Tensor,
tower_type: str = "target") -> torch.Tensor:
"""
获取用户表示向量(用于计算余弦相似度等)
Args:
user_features: 用户特征 [batch_size, input_dim]
tower_type: 塔类型 ("seed" 或 "target")
Returns:
用户表示向量 [batch_size, output_dim]
"""
if tower_type == "seed":
return self.seed_tower(user_features)
else:
return self.target_tower(user_features)
关键设计决策:
-
为什么使用双塔结构?
- 种子用户和目标用户的特征分布可能不同
- 独立的塔可以学习各自的特征空间
- 便于离线预计算目标用户的embedding,提高推理速度
-
相似度计算层的设计
- 输入:seed_repr + target_repr(128维)
- 多层MLP逐步提取交互特征
- 输出:2分类(相似/不相似)
-
Xavier初始化
- 根据输入输出维度自适应初始化权重
- 保持前向传播和反向传播时方差的一致性
4.3 模型训练流程
4.3.1 数据准备
def _prepare_data_loader(self, data: List[Dict], batch_size: int, shuffle: bool = False):
"""准备数据加载器"""
# 提取特征和标签
seed_features_list = []
target_features_list = []
labels_list = []
for item in data:
seed_features = item.get("seed_features", [0] * self.input_dim)
target_features = item.get("target_features", [0] * self.input_dim)
label = item.get("label", 0) # 0: 不相似, 1: 相似
# 确保特征维度正确
if len(seed_features) != self.input_dim:
seed_features = seed_features[:self.input_dim] + [0] * (self.input_dim - len(seed_features))
if len(target_features) != self.input_dim:
target_features = target_features[:self.input_dim] + [0] * (self.input_dim - len(target_features))
seed_features_list.append(seed_features)
target_features_list.append(target_features)
labels_list.append(label)
# 转换为张量
seed_features_tensor = torch.FloatTensor(seed_features_list).to(self.device)
target_features_tensor = torch.FloatTensor(target_features_list).to(self.device)
labels_tensor = torch.LongTensor(labels_list).to(self.device)
# 创建数据集和加载器
dataset = torch.utils.data.TensorDataset(
seed_features_tensor, target_features_tensor, labels_tensor
)
data_loader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, shuffle=shuffle
)
return data_loader
4.3.2 训练循环
def train_model(self, training_data: List[Dict], validation_data: List[Dict] = None,
epochs: int = 100, batch_size: int = 32, learning_rate: float = 0.001):
"""训练RALM模型"""
logger.info(f"开始训练RALM模型,训练数据: {len(training_data)} 条")
# 准备训练数据
train_loader = self._prepare_data_loader(training_data, batch_size, shuffle=True)
# 优化器和损失函数
optimizer = torch.optim.Adam(self.model.parameters(), lr=learning_rate)
criterion = nn.CrossEntropyLoss() # 交叉熵损失
# 训练循环
train_losses = []
val_losses = []
for epoch in range(epochs):
# 训练阶段
self.model.train()
epoch_loss = 0.0
for batch in train_loader:
seed_features, target_features, labels = batch
# 前向传播
outputs = self.model(seed_features, target_features)
loss = criterion(outputs, labels)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_loss += loss.item()
avg_train_loss = epoch_loss / len(train_loader)
train_losses.append(avg_train_loss)
# 验证阶段
if validation_data and epoch % 10 == 0:
val_loss = self._validate_model(validation_data, criterion)
val_losses.append(val_loss)
logger.info(f"Epoch {epoch+1}/{epochs}, Train Loss: {avg_train_loss:.4f}, Val Loss: {val_loss:.4f}")
# 早停检查
if len(val_losses) > 5 and val_losses[-1] > val_losses[-5]:
logger.info("验证损失增加,提前停止训练")
break
# 保存模型
self.save_model()
return {
"epochs_completed": len(train_losses),
"final_train_loss": train_losses[-1] if train_losses else 0,
"final_val_loss": val_losses[-1] if val_losses else 0,
"train_losses": train_losses,
"val_losses": val_losses
}
训练技巧:
-
学习率选择
- 初始学习率:0.001(Adam优化器的经典选择)
- 可以使用学习率衰减策略进一步优化
-
批次大小
- 建议:32-128
- 太小:训练不稳定
- 太大:显存占用高,泛化能力弱
-
早停机制
- 监控验证集损失
- 连续5个epoch验证损失不下降则停止
- 避免过拟合
4.3.3 模型推理
def predict_similarity(self, seed_user_id: int, target_user_id: int,
seed_user_data: Dict, target_user_data: Dict) -> Dict:
"""预测用户相似度"""
# 1. 构建用户特征
seed_features = self.feature_service.build_user_features(seed_user_id, seed_user_data)
target_features = self.feature_service.build_user_features(target_user_id, target_user_data)
# 2. 提取特征向量
seed_feature_vector = seed_features["feature_vector"]
target_feature_vector = target_features["feature_vector"]
# 3. 转换为张量
seed_tensor = torch.FloatTensor([seed_feature_vector]).to(self.device)
target_tensor = torch.FloatTensor([target_feature_vector]).to(self.device)
# 4. 模型推理
self.model.eval()
with torch.no_grad():
# 计算相似度得分
similarity_scores = self.model(seed_tensor, target_tensor)
probabilities = F.softmax(similarity_scores, dim=1)
# 获取相似度概率(正类概率)
similarity_score = probabilities[0][1].item()
# 获取用户表示向量
seed_representation = self.model.get_user_representation(seed_tensor, "seed")
target_representation = self.model.get_user_representation(target_tensor, "target")
# 计算余弦相似度(作为辅助指标)
cosine_similarity = F.cosine_similarity(seed_representation, target_representation).item()
return {
"seed_user_id": seed_user_id,
"target_user_id": target_user_id,
"similarity_score": similarity_score, # 主要相似度得分
"cosine_similarity": cosine_similarity, # 辅助相似度指标
"prediction_confidence": max(probabilities[0]).item(), # 预测置信度
"timestamp": int(datetime.now().timestamp())
}
推理优化:
-
批量推理
- 一次处理多个目标用户
- 提高GPU利用率
-
Embedding缓存
- 预计算所有用户的embedding
- 推理时只需查表和计算相似度
-
模型量化
- FP16量化减少显存占用
- 几乎不影响精度
五、业务场景应用
5.1 "附近的人"推荐
场景描述: 用户打开应用,想找附近志趣相投的人。
实现流程:
# 1. 获取用户当前位置
user_location = get_user_location(user_id)
# 2. 通过Geohash找到附近用户(地理筛选)
nearby_users = geohash_service.find_nearby_users(
lat=user_location.lat,
lon=user_location.lon,
radius=5000 # 5公里
)
# 3. 构建种子用户(可以是用户自己,或者他的好友)
seed_user_data = get_user_data(user_id)
# 4. 批量计算相似度(特征筛选)
similarities = []
for target_user in nearby_users:
target_user_data = get_user_data(target_user.id)
similarity = ralm_service.predict_similarity(
seed_user_id=user_id,
target_user_id=target_user.id,
seed_user_data=seed_user_data,
target_user_data=target_user_data
)
similarities.append(similarity)
# 5. 综合排序
for sim in similarities:
distance_score = 1 - (sim["distance"] / 5000) # 距离得分
feature_score = sim["similarity_score"] # 特征相似度
# 综合得分 = 60%距离 + 40%特征相似度
sim["final_score"] = 0.6 * distance_score + 0.4 * feature_score
# 6. 按综合得分排序并返回Top-N
recommendations = sorted(similarities, key=lambda x: x["final_score"], reverse=True)[:20]
效果评估:
- 精准度:推荐用户与当前用户的兴趣匹配度>80%
- 多样性:避免所有推荐都来自同一圈层
- 实时性:推荐结果<1秒返回
5.2 活动参与者推荐
场景描述: 用户创建了一个线下活动(如羽毛球局),希望邀请附近合适的人参加。
实现要点:
# 1. 分析活动特征
activity_features = {
"type": "sports", # 运动类
"subtype": "badminton", # 羽毛球
"location": activity.location,
"time": "weekend_afternoon", # 周末下午
"skill_level": "intermediate" # 中级水平
}
# 2. 构建种子用户画像(已报名参加的用户)
seed_users = get_activity_participants(activity_id)
seed_features = aggregate_user_features(seed_users)
# 3. 找到附近对羽毛球感兴趣的用户
nearby_sports_fans = find_users_with_interest(
location=activity.location,
radius=10000, # 10公里
interests=["sports", "badminton"]
)
# 4. 使用RALM模型筛选相似用户
recommendations = ralm_service.batch_predict_similarity(
seed_user_id=seed_features,
target_users_data=nearby_sports_fans
)
# 5. 过滤不符合条件的用户
filtered_recommendations = []
for rec in recommendations:
user = rec["target_user"]
# 检查用户是否在活动时间有空
if is_user_available(user.id, activity.time):
# 检查用户技能水平是否匹配
if abs(user.skill_level - activity.skill_level) <= 1:
filtered_recommendations.append(rec)
# 6. 发送邀请
for rec in filtered_recommendations[:10]: # Top 10
send_activity_invitation(activity_id, rec["target_user_id"])
业务价值:
- 提高参与率:推荐的用户更可能对活动感兴趣
- 保证体验:技能水平匹配,活动体验更好
- 扩大社交圈:帮助用户认识志同道合的新朋友
5.3 基于种子用户的人群扩展
场景描述: 营销团队想找到与现有VIP用户相似的潜在高价值用户。
实现流程:
# 1. 选择种子用户(VIP用户)
seed_users = get_vip_users(min_lifetime_value=10000)
# 2. 提取种子用户的共同特征
seed_features_aggregated = {
"age_range": [25, 35],
"income_level": [4, 5], # 高收入
"interests": ["travel", "food", "luxury"],
"location_preferences": ["urban", "mall", "restaurant"],
"app_usage": {
"daily_minutes": 60,
"purchase_frequency": "high"
}
}
# 3. 在全量用户中找相似用户
all_users = get_all_active_users()
# 4. 批量计算相似度
lookalike_users = []
for user in all_users:
if user.id not in seed_users: # 排除已经是VIP的用户
similarity = ralm_service.predict_similarity(
seed_user_id=seed_features_aggregated,
target_user_id=user.id,
seed_user_data=seed_features_aggregated,
target_user_data=get_user_data(user.id)
)
if similarity["similarity_score"] > 0.7: # 相似度阈值
lookalike_users.append({
"user_id": user.id,
"similarity_score": similarity["similarity_score"],
"estimated_ltv": estimate_lifetime_value(user.id, similarity["similarity_score"])
})
# 5. 按预估价值排序
lookalike_users.sort(key=lambda x: x["estimated_ltv"], reverse=True)
# 6. 定向营销
for user in lookalike_users[:1000]: # Top 1000
send_vip_promotion(user["user_id"], personalized=True)
ROI分析:
- 转化率提升:相比随机营销,转化率提升3-5倍
- 成本节约:精准投放减少无效营销支出
- 长期价值:找到的新用户LTV与种子用户接近
六、性能优化实践
6.1 模型压缩
6.1.1 知识蒸馏
原理: 用小模型(Student)学习大模型(Teacher)的输出分布。
class StudentRALMModel(nn.Module):
"""轻量级RALM模型"""
def __init__(self, input_dim=64, hidden_dims=[64, 128, 64], output_dim=32):
super().__init__()
# 更小的网络结构
self.seed_tower = UserRepresentationTower(input_dim, hidden_dims, output_dim)
self.target_tower = UserRepresentationTower(input_dim, hidden_dims, output_dim)
self.similarity_layer = nn.Sequential(
nn.Linear(output_dim * 2, 32),
nn.ReLU(),
nn.Linear(32, 2)
)
def distillation_loss(student_outputs, teacher_outputs, labels, temperature=3.0, alpha=0.5):
"""蒸馏损失函数"""
# 软标签损失(学习teacher的输出分布)
soft_loss = nn.KLDivLoss()(
F.log_softmax(student_outputs / temperature, dim=1),
F.softmax(teacher_outputs / temperature, dim=1)
) * (temperature ** 2)
# 硬标签损失(学习真实标签)
hard_loss = nn.CrossEntropyLoss()(student_outputs, labels)
# 组合损失
return alpha * soft_loss + (1 - alpha) * hard_loss
效果:
- 模型大小减少50-70%
- 推理速度提升2-3倍
- 精度损失<5%
6.1.2 模型量化
import torch.quantization as quantization
# 动态量化(推理时量化)
quantized_model = quantization.quantize_dynamic(
model, # 原始FP32模型
{nn.Linear}, # 量化Linear层
dtype=torch.qint8 # 量化为INT8
)
# 静态量化(需要校准数据)
model.qconfig = quantization.get_default_qconfig('fbgemm')
model_prepared = quantization.prepare(model)
# 使用校准数据运行
for batch in calibration_data:
model_prepared(batch)
# 转换为量化模型
quantized_model = quantization.convert(model_prepared)
效果:
- 模型大小减少75%(FP32 → INT8)
- 推理速度提升2-4倍
- 精度损失<2%
6.2 推理加速
6.2.1 Embedding预计算
class EmbeddingCache:
"""用户Embedding缓存"""
def __init__(self, model, redis_client):
self.model = model
self.redis = redis_client
self.cache_ttl = 3600 # 1小时
def get_or_compute_embedding(self, user_id: int, user_features: Dict):
"""获取或计算用户Embedding"""
cache_key = f"user_embedding:{user_id}"
# 尝试从缓存读取
cached_embedding = self.redis.get(cache_key)
if cached_embedding:
return pickle.loads(cached_embedding)
# 计算Embedding
feature_vector = extract_features(user_features)
tensor = torch.FloatTensor([feature_vector])
with torch.no_grad():
embedding = self.model.get_user_representation(tensor, "target")
# 存入缓存
self.redis.setex(
cache_key,
self.cache_ttl,
pickle.dumps(embedding.cpu().numpy())
)
return embedding
def batch_compute_embeddings(self, users: List[Dict]):
"""批量计算并缓存Embedding"""
# 批量推理比逐个推理快得多
feature_vectors = [extract_features(u) for u in users]
tensors = torch.FloatTensor(feature_vectors)
with torch.no_grad():
embeddings = self.model.get_user_representation(tensors, "target")
# 批量写入缓存
pipe = self.redis.pipeline()
for user, embedding in zip(users, embeddings):
cache_key = f"user_embedding:{user['id']}"
pipe.setex(cache_key, self.cache_ttl, pickle.dumps(embedding.cpu().numpy()))
pipe.execute()
效果:
- 缓存命中率>90%时,推理速度提升10倍
- 减少重复计算,节省GPU资源
6.2.2 近似最近邻搜索(ANN)
当用户量很大时,遍历所有用户计算相似度会很慢。使用ANN算法加速:
import faiss
class FAISSIndex:
"""基于FAISS的快速相似度搜索"""
def __init__(self, dimension=64):
self.dimension = dimension
# 使用内积搜索(等价于余弦相似度,如果向量已归一化)
self.index = faiss.IndexFlatIP(dimension)
self.user_ids = []
def add_users(self, user_embeddings: np.ndarray, user_ids: List[int]):
"""添加用户到索引"""
# L2归一化(使内积等价于余弦相似度)
faiss.normalize_L2(user_embeddings)
self.index.add(user_embeddings)
self.user_ids.extend(user_ids)
def search_similar_users(self, query_embedding: np.ndarray, top_k: int = 20):
"""搜索最相似的用户"""
# 归一化查询向量
faiss.normalize_L2(query_embedding)
# 搜索
similarities, indices = self.index.search(query_embedding, top_k)
# 返回用户ID和相似度
results = []
for i, sim in zip(indices[0], similarities[0]):
results.append({
"user_id": self.user_ids[i],
"similarity": float(sim)
})
return results
效果:
- 百万级用户中搜索Top-20:<10ms
- 相比暴力搜索,速度提升100-1000倍
6.3 训练优化
6.3.1 混合精度训练
from torch.cuda.amp import autocast, GradScaler
def train_with_mixed_precision(model, train_loader, epochs=100):
"""使用混合精度训练"""
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
scaler = GradScaler() # 梯度缩放器
for epoch in range(epochs):
for seed_features, target_features, labels in train_loader:
optimizer.zero_grad()
# 使用自动混合精度
with autocast():
outputs = model(seed_features, target_features)
loss = criterion(outputs, labels)
# 缩放损失并反向传播
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
效果:
- 训练速度提升2-3倍
- 显存占用减少50%
- 精度几乎无损失
6.3.2 梯度累积
当GPU显存不足以使用大batch size时:
def train_with_gradient_accumulation(model, train_loader, accumulation_steps=4):
"""梯度累积训练"""
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
model.train()
optimizer.zero_grad()
for i, (seed_features, target_features, labels) in enumerate(train_loader):
outputs = model(seed_features, target_features)
loss = criterion(outputs, labels)
# 损失归一化
loss = loss / accumulation_steps
loss.backward()
# 每accumulation_steps步更新一次参数
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
效果:
- 等效于4倍的batch size
- 只占用1倍的显存
七、模型评估与调优
7.1 评估指标
7.1.1 离线评估
def evaluate_model(model, test_data):
"""离线评估模型性能"""
model.eval()
all_predictions = []
all_labels = []
all_probabilities = []
with torch.no_grad():
for seed_features, target_features, labels in test_loader:
outputs = model(seed_features, target_features)
probabilities = F.softmax(outputs, dim=1)
predictions = torch.argmax(probabilities, dim=1)
all_predictions.extend(predictions.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
all_probabilities.extend(probabilities[:, 1].cpu().numpy())
# 计算各项指标
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score
metrics = {
"accuracy": accuracy_score(all_labels, all_predictions),
"precision": precision_score(all_labels, all_predictions),
"recall": recall_score(all_labels, all_predictions),
"f1_score": f1_score(all_labels, all_predictions),
"auc": roc_auc_score(all_labels, all_probabilities)
}
return metrics
指标解读:
| 指标 | 含义 | 业务意义 |
|---|---|---|
| Accuracy | 整体准确率 | 模型整体表现 |
| Precision | 查准率 | 推荐的用户有多少真的相似 |
| Recall | 查全率 | 相似的用户被找到了多少 |
| F1 Score | 精确率和召回率的调和平均 | 平衡指标 |
| AUC | ROC曲线下面积 | 排序能力 |
7.1.2 在线A/B测试
class ABTestingService:
"""A/B测试服务"""
def create_ab_test(self, test_name: str, model_a_path: str, model_b_path: str,
traffic_split: float = 0.5, duration_days: int = 7):
"""创建A/B测试"""
test_config = {
"test_name": test_name,
"model_a": self.load_model(model_a_path),
"model_b": self.load_model(model_b_path),
"traffic_split": traffic_split,
"start_time": datetime.now(),
"end_time": datetime.now() + timedelta(days=duration_days),
"metrics": {
"model_a": {"impressions": 0, "clicks": 0, "conversions": 0},
"model_b": {"impressions": 0, "clicks": 0, "conversions": 0}
}
}
self.active_tests[test_name] = test_config
return test_config
def assign_user_to_group(self, test_name: str, user_id: str) -> str:
"""将用户分配到A/B组"""
# 使用哈希保证一致性(同一用户始终在同一组)
hash_value = int(hashlib.md5(f"{test_name}:{user_id}".encode()).hexdigest(), 16)
test_config = self.active_tests[test_name]
if hash_value % 100 < test_config["traffic_split"] * 100:
return "model_a"
else:
return "model_b"
def record_event(self, test_name: str, user_id: str, event_type: str):
"""记录用户事件"""
group = self.assign_user_to_group(test_name, user_id)
self.active_tests[test_name]["metrics"][group][event_type] += 1
def analyze_results(self, test_name: str):
"""分析A/B测试结果"""
test_config = self.active_tests[test_name]
metrics_a = test_config["metrics"]["model_a"]
metrics_b = test_config["metrics"]["model_b"]
# 计算关键指标
ctr_a = metrics_a["clicks"] / max(metrics_a["impressions"], 1)
ctr_b = metrics_b["clicks"] / max(metrics_b["impressions"], 1)
cvr_a = metrics_a["conversions"] / max(metrics_a["clicks"], 1)
cvr_b = metrics_b["conversions"] / max(metrics_b["clicks"], 1)
# 统计显著性检验(卡方检验)
from scipy.stats import chi2_contingency
contingency_table = [
[metrics_a["clicks"], metrics_a["impressions"] - metrics_a["clicks"]],
[metrics_b["clicks"], metrics_b["impressions"] - metrics_b["clicks"]]
]
chi2, p_value, dof, expected = chi2_contingency(contingency_table)
return {
"model_a": {"ctr": ctr_a, "cvr": cvr_a},
"model_b": {"ctr": ctr_b, "cvr": cvr_b},
"improvement": (ctr_b - ctr_a) / ctr_a if ctr_a > 0 else 0,
"statistically_significant": p_value < 0.05,
"p_value": p_value
}
在线评估指标:
| 指标 | 计算方式 | 业务意义 |
|---|---|---|
| CTR | 点击数/曝光数 | 推荐的吸引力 |
| CVR | 转化数/点击数 | 推荐的质量 |
| GMV | 成交金额 | 商业价值 |
| 用户留存率 | 7日/30日留存 | 长期价值 |
7.2 超参数调优
7.2.1 网格搜索
def grid_search_hyperparameters():
"""网格搜索最佳超参数"""
param_grid = {
"learning_rate": [0.0001, 0.001, 0.01],
"hidden_dims": [[64, 128, 64], [128, 256, 128], [256, 512, 256]],
"num_heads": [4, 8, 16],
"dropout": [0.1, 0.2, 0.3]
}
best_score = 0
best_params = None
for lr in param_grid["learning_rate"]:
for hidden_dims in param_grid["hidden_dims"]:
for num_heads in param_grid["num_heads"]:
for dropout in param_grid["dropout"]:
# 训练模型
model = RALMModel(
hidden_dims=hidden_dims,
num_heads=num_heads,
dropout=dropout
)
score = train_and_evaluate(model, lr)
if score > best_score:
best_score = score
best_params = {
"learning_rate": lr,
"hidden_dims": hidden_dims,
"num_heads": num_heads,
"dropout": dropout
}
return best_params, best_score
7.2.2 贝叶斯优化
from bayes_opt import BayesianOptimization
def objective_function(learning_rate, dropout, num_heads):
"""目标函数"""
model = RALMModel(
learning_rate=learning_rate,
dropout=dropout,
num_heads=int(num_heads)
)
score = train_and_evaluate(model)
return score
# 定义参数范围
pbounds = {
"learning_rate": (0.0001, 0.01),
"dropout": (0.1, 0.5),
"num_heads": (4, 16)
}
# 贝叶斯优化
optimizer = BayesianOptimization(
f=objective_function,
pbounds=pbounds,
random_state=42
)
optimizer.maximize(init_points=5, n_iter=25)
print("最佳参数:", optimizer.max)
调优建议:
-
学习率
- 从0.001开始
- 观察loss曲线,震荡过大则减小
- 可以使用学习率调度器(如CosineAnnealingLR)
-
隐藏层维度
- 遵循"先扩展后压缩"的原则
- 不宜过大(容易过拟合)
- 建议:[128, 256, 128]
-
注意力头数
- 通常8个头是个好选择
- 过多的头可能导致计算冗余
- 需要保证hidden_dim能被num_heads整除
-
Dropout率
- 训练集loss下降但验证集loss不降,增大dropout
- 一般0.2-0.3是合理范围
7.3 模型诊断
7.3.1 过拟合诊断
症状:
- 训练集loss持续下降,验证集loss上升
- 训练集准确率很高,验证集准确率低
解决方案:
# 1. 增加Dropout
self.dropout = nn.Dropout(0.3) # 从0.2增加到0.3
# 2. 添加L2正则化
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)
# 3. 使用早停
if val_loss > best_val_loss:
patience_counter += 1
if patience_counter >= patience:
break
# 4. 数据增强
def augment_features(features):
"""特征数据增强"""
noise = torch.randn_like(features) * 0.01
return features + noise
7.3.2 欠拟合诊断
症状:
- 训练集和验证集loss都很高
- 模型准确率接近随机猜测
解决方案:
# 1. 增加模型容量
hidden_dims = [256, 512, 256] # 增大隐藏层
# 2. 增加训练轮数
epochs = 200 # 从100增加到200
# 3. 调整学习率
learning_rate = 0.01 # 从0.001增加到0.01
# 4. 增加特征维度
input_dim = 128 # 从64增加到128
7.3.3 梯度问题诊断
def check_gradients(model):
"""检查梯度状态"""
for name, param in model.named_parameters():
if param.grad is not None:
grad_norm = param.grad.norm().item()
if grad_norm > 10: # 梯度爆炸
print(f"梯度爆炸: {name}, norm={grad_norm}")
elif grad_norm < 1e-7: # 梯度消失
print(f"梯度消失: {name}, norm={grad_norm}")
# 解决梯度爆炸
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 解决梯度消失
# 使用残差连接或Layer Normalization
class ResidualBlock(nn.Module):
def forward(self, x):
return x + self.layer(x) # 残差连接
八、生产环境部署
8.1 模型服务化
8.1.1 FastAPI服务封装
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="RALM Model Service")
# 全局模型实例
ralm_service = RALMService(model_path="models/ralm_model.pth")
class SimilarityRequest(BaseModel):
"""相似度预测请求"""
seed_user_id: int
target_user_id: int
seed_user_data: Dict
target_user_data: Dict
class SimilarityResponse(BaseModel):
"""相似度预测响应"""
seed_user_id: int
target_user_id: int
similarity_score: float
cosine_similarity: float
prediction_confidence: float
timestamp: int
@app.post("/api/v1/similarity/predict", response_model=SimilarityResponse)
def predict_similarity(request: SimilarityRequest):
"""预测用户相似度"""
try:
result = ralm_service.predict_similarity(
seed_user_id=request.seed_user_id,
target_user_id=request.target_user_id,
seed_user_data=request.seed_user_data,
target_user_data=request.target_user_data
)
return SimilarityResponse(**result)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
def health_check():
"""健康检查"""
return {"status": "healthy", "model_loaded": ralm_service.model is not None}
九、常见问题与解决方案
9.1 冷启动问题
问题描述: 新用户没有历史行为数据,无法准确预测相似度。
解决方案:
def handle_cold_start_user(user_data: Dict):
"""处理冷启动用户"""
# 1. 基于人口统计特征的初始推荐
demographic_features = extract_demographic_features(user_data)
# 2. 基于地理位置的初始推荐
location_features = extract_location_features(user_data)
# 3. 使用默认的种子用户群
default_seed_users = get_popular_users_in_area(user_data["location"])
# 4. 逐步收集行为数据
if user_data.get("interaction_count", 0) < 10:
# 使用基于规则的推荐
return rule_based_recommendation(demographic_features, location_features)
else:
# 切换到RALM模型推荐
return ralm_based_recommendation(user_data)
9.2 数据稀疏问题
问题描述: 某些特征维度的数据非常稀疏,影响模型效果。
解决方案:
# 1. 特征平滑
def smooth_sparse_features(features, alpha=0.1):
"""拉普拉斯平滑"""
return (features + alpha) / (1 + alpha * len(features))
# 2. 使用Embedding
class SparseFeatureEmbedding(nn.Module):
def __init__(self, num_features, embedding_dim=8):
super().__init__()
self.embedding = nn.Embedding(num_features, embedding_dim)
def forward(self, x):
return self.embedding(x)
# 3. 多任务学习
class MultiTaskRALMModel(nn.Module):
def __init__(self):
super().__init__()
self.shared_tower = UserRepresentationTower()
self.similarity_head = nn.Linear(64, 2)
self.auxiliary_head = nn.Linear(64, 10) # 辅助任务
def forward(self, x):
features = self.shared_tower(x)
similarity = self.similarity_head(features)
auxiliary = self.auxiliary_head(features)
return similarity, auxiliary
9.3 实时性问题
问题描述: 推理速度跟不上业务需求,用户等待时间过长。
解决方案:
# 1. 异步推理
import asyncio
async def async_predict(seed_user, target_users):
"""异步批量推理"""
tasks = []
for target_user in target_users:
task = asyncio.create_task(
predict_similarity_async(seed_user, target_user)
)
tasks.append(task)
results = await asyncio.gather(*tasks)
return results
# 2. 结果缓存
from functools import lru_cache
@lru_cache(maxsize=10000)
def cached_predict(seed_user_id, target_user_id):
"""带缓存的预测"""
return predict_similarity(seed_user_id, target_user_id)
# 3. 预计算
def precompute_popular_pairs():
"""预计算热门用户对的相似度"""
popular_users = get_popular_users(limit=1000)
for i, user1 in enumerate(popular_users):
for user2 in popular_users[i+1:]:
similarity = predict_similarity(user1.id, user2.id)
cache.set(f"sim:{user1.id}:{user2.id}", similarity, ttl=3600)
9.4 模型更新问题
问题描述: 用户行为不断变化,模型需要定期更新,但重训练成本高。
解决方案:
# 1. 在线学习
class OnlineLearningService:
"""在线学习服务"""
def __init__(self, model, learning_rate=0.0001):
self.model = model
self.optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
self.criterion = nn.CrossEntropyLoss()
self.sample_buffer = []
self.buffer_size = 1000
def add_sample(self, seed_features, target_features, label):
"""添加训练样本"""
self.sample_buffer.append((seed_features, target_features, label))
# 缓冲区满时触发更新
if len(self.sample_buffer) >= self.buffer_size:
self._update_model()
self.sample_buffer = []
def _update_model(self):
"""更新模型"""
self.model.train()
for seed_features, target_features, label in self.sample_buffer:
outputs = self.model(seed_features, target_features)
loss = self.criterion(outputs, label)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# 2. 增量训练
def incremental_training(model, new_data, epochs=5):
"""增量训练"""
# 冻结部分层,只微调顶层
for param in model.seed_tower.parameters():
param.requires_grad = False
for param in model.target_tower.parameters():
param.requires_grad = False
# 只训练相似度计算层
optimizer = torch.optim.Adam(
model.similarity_layer.parameters(),
lr=0.0001
)
for epoch in range(epochs):
for batch in new_data:
# 训练逻辑
pass
# 3. 模型版本管理
class ModelVersionManager:
"""模型版本管理器"""
def __init__(self):
self.models = {}
self.active_version = None
def register_model(self, version: str, model_path: str):
"""注册新模型版本"""
model = load_model(model_path)
self.models[version] = model
def switch_version(self, version: str):
"""切换模型版本"""
if version in self.models:
self.active_version = version
logger.info(f"切换到模型版本: {version}")
else:
raise ValueError(f"模型版本不存在: {version}")
def get_active_model(self):
"""获取当前活跃模型"""
return self.models[self.active_version]