← All writingHSTU:从行为序列到生成式推荐 · 01

HSTU 的起点:推荐是在预测什么序列?

区分内容、行为、目标与候选,理解 sequential transduction 和数据泄漏的边界。

Read the English edition →

HSTU 的起点:推荐是在预测什么序列?

从零开始的完整讲解

推荐系统预测的不是一个抽象的“喜欢”

一次推荐可以涉及曝光、点击、停留、收藏、购买等不同事件。预测点击与预测购买的标签、延迟和偏差不同。开始建序列之前,要写清楚在什么时刻、给定哪些历史、为哪些候选预测什么。没有这份任务定义,再复杂的 HSTU 也可能优化错误目标。

语言模型的 token 来自文本词表,推荐序列的“token”可以表示物品、动作、时间和上下文。物品 ID 仅是标识符,不自动包含视觉或语义;同一个 ID 的含义还需要和特征版本、商品状态等外部信息保持一致。

用一段行为日志构造训练样本

假设用户按时间看过 A、点击 B、购买 C。若训练下一物品预测,可以从前缀 [A] 预测 B,从 [A,B] 预测 C。但若目标是“看到候选后会不会购买”,还需要曝光候选与未购买样本,不能把未出现在历史中的所有商品都当作明确不喜欢。

动作类型也不能随便丢掉。“浏览十次未买”和“刚刚购买”可能意味着不同需求。时间间隔同样重要:昨天与一年前的行为不一定应有同样作用。序列建模提供表达这些差异的机会,最终是否利用它们取决于输入设计和训练目标。

时间切分防止模型偷看未来

构建时刻 t 的样本时,只能使用当时已经可用的信息。若使用商品未来才更新的标题、用户后来完成的购买或未来累计热度,就会发生泄漏。即使 attention mask 正确,特征生成阶段也可能已经带入未来信息。

随机拆分交互容易让同一用户相近时间的近重复历史散落在训练与测试中。更可靠的方案通常需要按时间模拟上线场景,并说明是否允许训练中见过的用户和物品。新用户、新物品与老用户老物品应分别报告,而不是让一个平均分掩盖冷启动。

Padding、长度和标签对齐

不同用户历史长度不一。稠密 batch 会补齐,padding ID 只是占位,必须从注意力、loss 和候选排序的相应位置排除。若从一段长度 L 的序列学习 L-1 个下一步目标,输入与标签要错位对齐。

下方浏览器实验为了直观使用 0、1、2 三个真实物品,没有 padding;完整 PyTorch 实验采用独立的 padding 约定。不要把两个实验的 ID 规则混用,否则可能把真实物品 0 排除掉,或者把 padding 当成可以推荐的商品。

负采样如何改变问题难度

从百万商品中随机抽几个负例,通常比在全量候选中找正确商品容易。HR@10 如果只在 100 个随机负例里计算,与全库 HR@10 不是同一任务。采样分布还会影响哪些错误被惩罚得更多,热门物品与长尾物品的曝光不同,不能简单把观测频率当作纯偏好。

离线实验应记录候选集合、负采样策略、重复物品处理和过滤规则。推荐中过去看过的物品有时仍然相关,是否过滤要由任务决定,而不是照抄通用代码。

先让一个微型序列实验可解释

浏览器 Notebook 用三个物品循环,输入历史,输出下一个物品概率。因为数据规则简单,模型可能很快学会;这验证代码和梯度,不证明理解了真实用户。将循环打乱、缩短历史或改掉目标,观察指标变化,能帮助你发现模型究竟利用了什么信息。

之后再引入真实时间间隔、动作和内容特征,逐项做消融。一次增加所有输入,即使效果变好,也很难解释是哪一项带来的贡献。

先别急着替换 Transformer

用户看到内容,产生点击、跳过、停留或购买等行为。HSTU 所在的生成式推荐框架把这类时间过程作为建模对象。“生成式”不意味着一定输出自然语言,也不意味着必须生成 Semantic ID。Retrieval 可以预测下一个内容表示,ranking 则可以在候选条件下预测行为。

本路线是现有生成式推荐第 16 章的代码延伸;原专题概览 介绍论文背景,这里重点放在数据到张量的转换。

监督的时间边界

假设历史为 (网球,点击,t1)、(攀岩,长观看,t2),t3 到来一个游泳视频候选。预测 t3 行为时,能读取前两次真实行为和当前候选内容,不能读取 t3 已完成的观看时长。时间戳、停留统计或聚合特征如果使用未来更新值,也会泄漏。

离线 next-item 教学任务常用 items[:-1] 预测 items[1:];这与工业 content/action 交织输入不是完全相同格式。Notebook 明确使用前者以便在 CPU 上训练,并不会声称复现论文工业系统。

从列表到 batch

import torch
sequences = [[1, 3, 4, 2], [2, 4, 1]]
inputs = torch.tensor([[1,3,4], [2,4,0]])
targets = torch.tensor([[3,4,2], [4,1,0]])
valid = inputs.ne(0)
print(inputs.shape, valid.sum(1))

这里 0 是保留 padding ID,不能作为普通商品参与 top-k。真实特征空间可包含 item、creator、类型、时间与内容 embedding;不同来源应注明更新时刻与缺失策略。未经训练的特征拼接不自动成为更好的模型。

评价之前先定义候选

在全部商品中检索,与从 100 个抽样负例中挑一个,难度完全不同。负例采样分布也会影响结果,尤其 popular-item 偏置。记录是否过滤历史已见商品;如果业务允许复购,机械过滤可能删掉正确目标。切分应遵守全局时间和特征可用性,而不仅每用户最后一条。

自测

问题: 只用预测时刻之前的行为序列,是否保证无泄漏?

展开答案不保证。商品 embedding、流行度、用户统计与候选集也可能由未来数据产生。需要逐字段检查 point-in-time 可用性。

原始资料与继续阅读

资料核对:2026-09-10。教学示例不代表生产基准;框架 API 和模型支持请以所链接版本为准。

NOTEBOOK · LIVE PYTHON

在文章里运行 Notebook

逐格改代码、运行,或者一键运行全部。变量在单元之间保留;离开页面或重启内核会清空变量。编辑上游单元后,请重新运行后续单元。

此版用真实 Python 实现可检查的小型计算与训练,不需要 GPU。PyTorch/CUDA 不在此浏览器内核中;完整预训练模型 Notebook 见另一个标签,大模型基础的 Notebook 还提供单独加载真实 MiniLM 的可编辑单元。

内核尚未加载;点击运行开始。

正在加载 Notebook…