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

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

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

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)时,需要在效果和推理延迟之间进行权衡。

相关推荐
是阿威啊几秒前
【第一站】本地虚拟机部署Hadoop分布式集群
大数据·linux·hadoop·分布式
B_lack0261 分钟前
字节转换算法应用_读取本地时间
数据结构·算法·数组·西门子plc·博途·时间处理·scl
WebInfra4 分钟前
Midscene v1.0 发布 - 视觉驱动,UI 自动化体验跃迁
javascript·人工智能·测试
xoliu15 分钟前
Pytorch核心基础入门
人工智能·pytorch·python
跨境卫士—小依7 分钟前
TikTok Shop 进化全解析,从内容驱动到品牌共建,抢占跨境新赛道
大数据·人工智能·跨境电商·亚马逊·防关联
一瞬祈望8 分钟前
ResNet50 图像分类完整实战(Notebook Demo + 训练代码)
人工智能·python·神经网络·数据挖掘
leiming610 分钟前
c++ string 容器
开发语言·c++·算法
其美杰布-富贵-李10 分钟前
PyTorch Lightning Callback 指南
人工智能·pytorch·python·回调函数·callback
哥布林学者18 分钟前
吴恩达深度学习课程四:计算机视觉 第三周:检测算法 (四)YOLO 的完整传播过程
深度学习·ai
Mintopia27 分钟前
🤖 2025 年的人类还需要 “Prompt 工程师” 吗?
人工智能·llm·aigc