冷启动与流量调控
Goal
把概念变成可检查的输入、运算、梯度与对照。
数据类型:真实属性 + 模拟流量。本地教学参数不是生产默认值;机制实验不是原论文完整复现。
Setup
每本首次从 Setup 起顺序运行,后面的格会复用前面的变量。之后只改一个参数,重跑对应格及下游格。不要把构造数据当作真实业务收益。
from pathlib import Path
import sys
import inspect
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display, Code
PROJECT = next(p for p in [Path.cwd(), *Path.cwd().parents]
if (p / "reclab/models.py").exists())
if str(PROJECT) not in sys.path:
sys.path.insert(0, str(PROJECT))
from reclab.data import Dataset
from reclab.paths import ARTIFACTS, PROCESSED, read_json
plt.rcParams.update({"figure.figsize": (9, 3.8), "font.size": 11,
"axes.spines.top": False, "axes.spines.right": False,
"axes.labelcolor": "#182b2b", "text.color": "#182b2b",
"axes.facecolor": "white", "figure.facecolor": "white",
"grid.color": "#d4ddda", "axes.axisbelow": True})
dataset = Dataset()
print("Project:", PROJECT)
print("Dataset version:", dataset.manifest["dataset_version"])
print("Python:", sys.executable)
assert Path(sys.executable).parent == PROJECT / ".venv/bin", "请选择 Recommendation Lab 内核"Notebook 保存的输出 · 本页未执行
Project: /Users/lizuolin/local/recommendation-lab Dataset version: 2772cab0aca8cb81 Python: /Users/lizuolin/local/recommendation-lab/.venv/bin/python
Steps
讲义对应:07_ColdStart_01.pdf, 07_ColdStart_02.pdf, 07_ColdStart_03.pdf, 07_ColdStart_04.pdf, 07_ColdStart_05.pdf, 07_ColdStart_06.pdf
隐藏 ID 后只用类目召回
训练目录内人为隐藏 ID 的压力练习,不是真实新发布商品效果。这里不用隐藏物品的已训练 ID 向量宣称冷启成功。
先预测: 类目缺失或全部是 unknown 时,这条通道会怎样?
categories=sorted({x["category"] for x in dataset.items});lookup={c:i for i,c in enumerate(categories)}
side=np.eye(len(categories),dtype=np.float32)[[lookup[x["category"]] for x in dataset.items]]
user=next(iter(dataset.histories));history=dataset.histories[user]
profile=side[np.array(history[-20:])-1].mean(0);scores=side@profile
hidden_ids=set(range(200))
chosen=[i for i in np.argsort(-scores,kind="stable") if i in hidden_ids][:10]
display(pd.DataFrame([{"item":dataset.items[i]["item_id"],"category":dataset.items[i]["category"],"score":scores[i]} for i in chosen]))Notebook 保存的输出 · 本页未执行
| item | category | score | |
|---|---|---|---|
| 0 | 10034 | 1219 | 0.916667 |
| 1 | 18519 | 1219 | 0.916667 |
| 2 | 403 | unknown | 0.083333 |
| 3 | 546 | unknown | 0.083333 |
| 4 | 2008 | unknown | 0.083333 |
| 5 | 8497 | unknown | 0.083333 |
| 6 | 496 | 707 | 0.000000 |
| 7 | 829 | 707 | 0.000000 |
| 8 | 869 | 1650 | 0.000000 |
| 9 | 1152 | 1051 | 0.000000 |
聚类与 Look-Alike 人群扩散
真实类目与训练历史的机制实验。没有真实图文或作者字段,不复现 CNN/BERT 内容相似度训练。
先预测: 如果种子群体偏了,扩散会修正偏差还是放大偏差?
from reclab.learning.retrieval import kmeans
centers,labels=kmeans(side[:600],clusters=8,iterations=5)
user_ids=list(dataset.histories)[:500]
profiles=np.array([side[np.array(dataset.histories[u][-20:])-1].mean(0) for u in user_ids])
seed_category=dataset.items[0]["category"]
seeds=[i for i,u in enumerate(user_ids) if any(dataset.items[j-1]["category"]==seed_category for j in dataset.histories[u])] or [0]
audience=profiles[seeds].mean(0)
audience=audience/max(float(np.linalg.norm(audience)),1e-8)
normalized=profiles/np.maximum(np.linalg.norm(profiles,axis=1,keepdims=True),1e-8)
expanded=np.argsort(-(normalized@audience))[:10]
display(pd.DataFrame({"user":[dataset.users[user_ids[i]] for i in expanded],"similarity":(normalized@audience)[expanded]}))
print("Cluster counts:",np.unique(labels,return_counts=True))Notebook 保存的输出 · 本页未执行
| user | similarity | |
|---|---|---|
| 0 | 104555 | 0.866971 |
| 1 | 113172 | 0.866971 |
| 2 | 99319 | 0.866382 |
| 3 | 77609 | 0.843095 |
| 4 | 78724 | 0.740322 |
| 5 | 19967 | 0.733718 |
| 6 | 102661 | 0.711574 |
| 7 | 44071 | 0.699376 |
| 8 | 71099 | 0.684696 |
| 9 | 39027 | 0.657632 |
Cluster counts: (array([0, 1, 2, 3, 4, 5, 6, 7]), array([557, 16, 2, 4, 4, 6, 7, 4]))
保量、差异化目标与供需约束
全部是模拟流量。100 次基础曝光只是讲义示例。供给不足不可能全保量;即使流量足够,固定 boost 也未必成功。
先预测: 保证数量、内容质量与消费体验能否同时最大化?
from reclab.learning.systems import allocate_quotas,bucket
quality=np.linspace(.1,1.,20);QUOTA=100;rows=[]
for requests in [500,2000,4000]:
exposure=allocate_quotas(quality,requests,quota=QUOTA,boost=3.)
rows.append({"requests":requests,"required_supply":len(quality)*QUOTA,
"items_reaching_quota":int((exposure>=QUOTA).sum()),"allocated":int(exposure.sum())})
assert exposure.sum()==requests
display(pd.DataFrame(rows))
assert rows[0]["items_reaching_quota"]<len(quality)Notebook 保存的输出 · 本页未执行
| requests | required_supply | items_reaching_quota | allocated | |
|---|---|---|---|---|
| 0 | 500 | 2000 | 0 | 500 |
| 1 | 2000 | 2000 | 1 | 2000 |
| 2 | 4000 | 2000 | 1 | 4000 |
作者侧 A/B 的流量干扰
构造两个作者池;共享预算下 A 增量会挤占 B。没有真实作者发布和用户留存的因果结论。
先预测: 如何区分业务净增与实验组之间搬运流量?
budget=10000;base=np.array([1.,1.]);boost=np.array([1.5,1.])
before=budget*base/base.sum();after=budget*boost/boost.sum()
display(pd.DataFrame({"before":before,"after":after,"delta":after-before},index=["authorA","authorB"]))
assert np.isclose((after-before).sum(),0)
print("Reproducible author buckets:",[bucket(i,"author",2) for i in range(6)])Notebook 保存的输出 · 本页未执行
| before | after | delta | |
|---|---|---|---|
| authorA | 5000.0 | 6000.0 | 1000.0 |
| authorB | 5000.0 | 4000.0 | -1000.0 |
Reproducible author buckets: [0, 1, 1, 1, 0, 0]
Checks
所有断言和输出都可回溯到代码。它们验证算子、数据快照或机制,不是线上收益。
Next Steps
先记录预测,只改一个变量,再运行对应实验格。需要查看辅助函数时,打开 reclab/learning/。