【推荐系统】深度学习训练框架(十三):模型输入——《特征索引》与《特征向量》的边界

从"特征索引"到"特征向量"的数据流

整个数据处理流程分为离线训练和在线服务两个阶段,下图清晰地展示了从原始请求到模型最终输出的完整过程:

flowchart LR A["线上请求
(原始特征字典)"] --> B["在线特征拼接服务
Feature Serving"] subgraph C [离线训练与模型定义] C1["训练数据
(包含特征索引)"] --> C2["模型定义
(包含Embedding层)"] end B --> D["模型预测服务
Model Serving"] C2 -- "模型加载" --> D subgraph E [模型内部Forward过程] D --> F["接收: 特征索引/值
(如 user_id=12345)"] F --> G["Embedding层查表
(转换索引为向量)"] G --> H["拼接、交叉等深度计算"] H --> I["输出: 点击概率"] end I --> J[返回推荐结果]

下面我们拆解上图中的关键环节:

  1. 离线训练阶段:准备索引和映射

    • 原始日志(如 用户ID=12345,物品ID=678,城市=北京)会被处理成训练样本。
    • 每个类别特征(称为一个 fieldslot)会建立一张从原始值到连续整数索引的映射表(称为 vocabularyfeat_map)。
    • 训练时forward 函数接收的输入是一个形如 [batch_size, num_fields] 的张量,其中每个值都是对应特征的整数索引。模型内部的 nn.Embedding 层会根据这些索引,查找对应的向量权重。
  2. 在线服务阶段:从请求到预测

    • 当用户刷新信息流时,推荐引擎(如Rank服务)会收到一个包含大量原始特征的请求。
    • 特征服务 会将这些原始特征(字符串或数字)实时转换为对应的整数索引。
    • 然后,这个由索引构成的数组,被送入部署好的模型进行 forward 计算。

1. 一个简化的代码示例对比

python 复制代码
# 1. 模型定义(包含Embedding层)
class RankingModel(nn.Module):
    def __init__(self, vocab_sizes):
        super().__init__()
        # 为每个特征域定义一个嵌入层
        self.user_emb = nn.Embedding(vocab_sizes['user'], 16) # 假设用户ID有10万个,嵌入为16维
        self.item_emb = nn.Embedding(vocab_sizes['item'], 16)
        # ... 其他层(如全连接层)

    def forward(self, inputs):
        # inputs 是一个特征索引的字典
        user_id_idx = inputs['user_id'] # 形状: [batch_size, 1]
        item_id_idx = inputs['item_id'] # 形状: [batch_size, 1]

        # ★ 关键步骤:在forward内部,将索引转换为向量
        user_emb_vec = self.user_emb(user_id_idx) # 形状变为: [batch_size, 1, 16]
        item_emb_vec = self.item_emb(item_id_idx)

        # 将向量展平并拼接,输入后续网络
        concat_feat = torch.cat([user_emb_vec.squeeze(), item_emb_vec.squeeze()], dim=1)
        # ... 后续计算
        return prediction

# 2. 在线服务时的调用(伪代码)
# 假设一次请求,特征服务返回的原始特征已转为索引:
raw_request = {'user_id': 12345, 'item_id': 678, 'city': 1}
# 预处理为模型输入格式
model_input = {
    'user_id': torch.tensor([raw_request['user_id']]),
    'item_id': torch.tensor([raw_request['item_id']]),
    # ...
}
# 调用模型
output = model_serving_instance.forward(model_input)

2. 为什么这样设计?关键原因

  1. 效率与解耦 :模型只关心"计算",不关心"特征工程"。特征的处理(缺失值填充、归一化、哈希、索引化)由上游的特征平台统一负责,保证线上线下一致。
  2. 灵活性 :当需要新增一个特征时,只需在特征平台配置,并在模型定义中添加一个新的 Embedding 层或数值处理层即可,无需改动模型核心架构。
  3. 性能 :直接传输整数索引比传输高维浮点向量网络开销小得多。模型在GPU/CPU上通过查表(Embedding)将索引转为向量,这个操作高度优化,效率极高。
  4. 一致性 :这是最重要的原因。必须保证训练推理时,特征进入模型的方式完全一致。使用同一套索引映射和模型权重,是保证效果不衰减的基石。

这是一个非常关键的问题。多值和序列特征(例如用户的兴趣标签列表、历史行为序列)是信息流推荐系统的核心,它们的处理方式与单值特征有显著不同。

核心原则依然是:forward函数接收的是经过映射的索引,而不是原始字符串或向量 。但针对多值和序列,其索引的组织形式模型内部的后续处理有专门的设计。

3. 两类特征的处理流程对比

特征类型 示例 特征处理后的索引形式 (输入forward前) 模型内部 (forward函数内)的关键操作
多值特征 用户兴趣标签 ["体育", "科技"] Multi-hot 向量索引列表[0, 0, 1, 0, 1] (长度=总标签数) 或 [23, 45] 通过一个共享的Embedding层 ,将每个激活的索引转换为向量,然后进行聚合(求和、平均等)。
序列特征 用户点击历史 [item_id_101, item_id_302, ...] 定长/变长的索引数组[101, 302, 0, 0] (填充后) 或 [101, 302] (带长度信息) 1. 嵌入查找 :将每个索引变为向量,得到向量序列。 2. 序列建模 :通过Pooling、RNN、Transformer 等结构,将序列聚合成一个固定长度的兴趣表示向量

4. 具体实现与forward输入示例

结合单值、多值、序列特征,一个真实的信息流排序模型在训练/推理时,forward函数接收的输入字典大致如下:

python 复制代码
{
    # --- 单值特征 (直接索引) ---
    "user_id": torch.tensor([12894]),      # 用户ID索引
    "item_id": torch.tensor([356]),        # 候选物品ID索引
    "city_id": torch.tensor([12]),         # 城市索引

    # --- 多值特征 (以用户兴趣标签为例) ---
    # 形式1: Multi-hot稀疏向量 (知道总类别数时)
    "user_tags": torch.tensor([[0, 1, 0, 1, 0, 1]]), # 形状: [batch_size, total_num_tags]
    # 形式2: 变长索引列表 + 长度 (更灵活)
    "user_tag_list": torch.tensor([23, 45, 67]),     # 所有标签索引展平
    "user_tag_len": torch.tensor([3]),               # 该样本的实际标签数量

    # --- 序列特征 (以用户最近点击的10个物品ID为例) ---
    # 被处理成固定长度的索引序列,未满部分用0填充
    "hist_item_seq": torch.tensor([[101, 302, 454, 0, 0, ..., 0]]), # 形状: [batch_size, seq_len]
    # 通常还会传入序列的实际有效长度,供RNN/Attention使用
    "hist_seq_len": torch.tensor([3]), # 表示该序列只有前3个是真实的
}

在模型的forward函数内部,会这样处理这些输入:

python 复制代码
def forward(self, inputs):
    # 1. 处理单值特征:查表得到向量
    user_emb = self.emb_user(inputs['user_id']) # (batch, 1, emb_dim)
    item_emb = self.emb_item(inputs['item_id']) # (batch, 1, emb_dim)

    # 2. 处理多值特征(以Multi-hot形式为例)
    tag_emb_table = self.emb_tag # 共享的标签嵌入表
    # 利用矩阵乘法实现高效的多值查找与聚合(求和)
    user_tag_emb = torch.matmul(inputs['user_tags'].float(), tag_emb_table) # (batch, emb_dim)

    # 3. 处理序列特征(关键步骤)
    hist_item_seq = inputs['hist_item_seq'] # (batch, seq_len)
    seq_emb = self.emb_item(hist_item_seq) # (batch, seq_len, emb_dim)
    # 使用注意力机制(如DIN)或Transformer,利用候选物品动态加权聚合序列
    interest_emb = self.attention_net(seq_emb, item_emb, inputs['hist_seq_len']) # (batch, emb_dim)

    # 4. 拼接所有特征向量,输入全连接层
    concat_feat = torch.cat([user_emb, item_emb, user_tag_emb, interest_emb, ...], dim=1)
    output = self.dnn_layers(concat_feat)
    return output

5. 核心要点与工程意义

  1. 为什么不像单值特征那样直接Embedding?

    • 对于多值特征 ,直接Embedding会丢失"这是一个集合"的信息。通过先查表后聚合(通常是求和或平均),模型能学到这个集合的整体表征。
    • 对于序列特征 ,其价值在于顺序和结构 。通过专门的序列模型层 (如注意力机制),模型可以捕捉用户兴趣的动态演变,并实现与候选物品的动态交互(例如,用户历史中与当前物品相似的部分会获得更高注意力权重),这是信息流推荐效果提升的关键。
  2. 工程实现的一致性

    • 无论特征多么复杂,最终都需在特征工程阶段 转化为数值型索引,以保证线上服务的高效和一致性。
    • 特征的复杂语义(如序列的时序关系、多值的集合关系)主要由模型结构forward函数内的计算图)来刻画和解释。

总而言之,forward函数输入的是一组结构化、索引化的数字 ,而模型内部的计算路径 (嵌入、聚合、序列建模)才是将这些索引转化为具有丰富语义的向量表示,并最终做出预测的关键。这种设计完美地分离了数据预处理模型计算,是大规模系统得以稳定高效运行的基础。

如果你在考虑具体实现,选择多值特征的表示方式 (Multi-hot vs. 列表)和序列模型的复杂度(Pooling vs. Transformer)时,需要在效果和推理延迟之间进行权衡。

相关推荐
All The Way North-33 分钟前
PyTorch nn.L1Loss 完全指南:MAE 原理、梯度计算与不可导点处理详解
pytorch·深度学习·机器学习·mae损失函数·l1loss损失函数
CoovallyAIHub34 分钟前
如何让SAM3在医学图像上比专用模型还强?一个轻量Adapter如何让它“秒变”专家?
深度学习·算法·计算机视觉
suoge22340 分钟前
热传导控制方程有限元弱形式推导-有限元编程入门
算法
希望有朝一日能如愿以偿40 分钟前
力扣每日一题:统计梯形的数目
算法·leetcode·职场和发展
小女孩真可爱42 分钟前
大模型学习记录(八)---------RAG评估
linux·人工智能·python
阿里云大数据AI技术42 分钟前
MaxCompute SQL AI:让 SQL 成为你的 AI 语言
人工智能·sql
www7691 小时前
从神经科学到软件工程:一个智能体架构的设计反思
人工智能
Feisy1 小时前
使用深度学习检测元器件是否缺失零件-怎么快速地批量采集深度学习训练用的图片
人工智能·深度学习
阿里云大数据AI技术1 小时前
MaxCompute SQL AI:让SQL成为你的AI语言
人工智能·sql