CH 15 / SOURCE EXAMPLE

user_item_context_features.py

ch15 特征工程/code/ch01_feature_system/user_item_context_features.py

这个示例讲什么

│ ├── user_item_context_features.py §15.1.1–§15.1.4 用户/物品/上下文/行为特征清单与覆盖率

用户/物品/上下文特征的体系化组织(第15章 §15.1) 本脚本把同一批业务数据整理成"特征清单":每条特征标注来源(用户/物品/上下文/ 行为/多模态)、表示形式(Sparse/Dense/Sequence/Embedding)、基数与覆盖率, 用来演示正文的核心观点——特征体系要同时回答"特征来自哪里"和"特征以什么形式 进入模型"两个问题。 运行:python3 ch01_feature_system/user_item_context_features.py

上面来自章节 README 或源码自身的说明,不是自动验证的运行结果,也不能代替 PDF 的书中论断。讲解时请结合实际代码、输入输出与原书页码。

源码结构与阅读入口

  1. feature_inventory · 第 23 行按正文的分类整理特征清单,并统计基数/覆盖率。
  2. main · 第 67 行

完整源码 · 109 行

1"""2用户/物品/上下文特征的体系化组织(第15章 §15.1)34本脚本把同一批业务数据整理成"特征清单":每条特征标注来源(用户/物品/上下文/5行为/多模态)、表示形式(Sparse/Dense/Sequence/Embedding)、基数与覆盖率,6用来演示正文的核心观点——特征体系要同时回答"特征来自哪里"和"特征以什么形式7进入模型"两个问题。89运行:python3 ch01_feature_system/user_item_context_features.py10"""1112from __future__ import annotations1314import pathlib15import random16import sys1718sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))1920from common import make_catalog, make_events, make_users, print_table  # noqa: E402212223def feature_inventory(users: list[dict], items: list[dict], events: list[dict]) -> list[list]:24    """按正文的分类整理特征清单,并统计基数/覆盖率。"""25    n_users, n_items, n_events = len(users), len(items), len(events)2627    def ratio(pred, seq):28        if not seq:29            return 0.030        return sum(1 for x in seq if pred(x)) / len(seq)3132    # 覆盖率:Dense 特征为"非缺失/非默认值"的比例;Sparse 特征为有取值的比例33    click_events = [e for e in events if e["action"] in ("click", "watch")]34    watch_events = [e for e in events if e["action"] == "watch"]35    interacted = {e["user_id"] for e in click_events}36    popular_items = {e["item_id"] for e in click_events}3738    return [39        # 来源, 特征, 表示形式, 基数, 覆盖率, 示例40        ["用户", "user_id", "Sparse", n_users, 1.0, "u_000123"],41        ["用户", "age_band", "Sparse", 5, 0.97, "3"],42        ["用户", "gender", "Sparse", 2, 0.92, "1"],43        ["用户", "city_tier", "Sparse", 4, 0.88, "2"],44        ["用户", "intensity", "Dense", "-", 1.0, "0.7341"],45        ["用户", "ctr_7d", "Dense", "-", f"{len(interacted) / n_users:.2%}", "0.0831"],46        ["用户", "watch_time_30d", "Dense", "-", f"{len(watch_events) and len({e['user_id'] for e in watch_events}) / n_users:.2%}", "412.7s"],47        ["用户", "action_seq", "Sequence", n_users, f"{len(interacted) / n_users:.2%}", "[i12,i88,i31,...]"],48        ["物品", "item_id", "Sparse", n_items, 1.0, "v_000042"],49        ["物品", "category", "Sparse", len({it["category"] for it in items}), 1.0, "sports"],50        ["物品", "author_id", "Sparse", len({it["author"] for it in items}), 1.0, "a_011"],51        ["物品", "duration", "Dense", "-", 1.0, "37.4s"],52        ["物品", "quality", "Dense", "-", 1.0, "0.8123"],53        ["物品", "empirical_ctr", "Dense", "-", f"{len(popular_items) / n_items:.2%}", "0.0612"],54        ["物品", "cover_embedding", "Embedding", "-", 0.63, "d=64"],55        ["上下文", "hour", "Sparse", 24, 1.0, "21"],56        ["上下文", "weekday", "Sparse", 7, 1.0, "5"],57        ["上下文", "network", "Sparse", 4, 1.0, "wifi"],58        ["上下文", "device_price", "Dense", "-", 0.95, "2399"],59        ["上下文", "session_len", "Dense", "-", 1.0, "12"],60        ["上下文", "cand_size", "Dense", "-", 1.0, "200"],61        ["行为", "click_1d/7d/30d", "Dense", "-", 1.0, "3 / 21 / 88"],62        ["行为", "recency_last_click", "Dense", "-", f"{len(interacted) / n_users:.2%}", "0.42h"],63        ["行为", "watch_ratio_7d", "Dense", "-", 0.91, "0.618"],64    ]656667def main() -> None:68    rng = random.Random(42)69    users = make_users(200, 8, rng)70    items = make_catalog(400, 8, rng)71    events = make_events(200, 20_000, 400, 8, 30, random.Random(2024))7273    rows = feature_inventory(users, items, events)74    # 覆盖率统一成百分比字符串75    for r in rows:76        r[4] = f"{r[4]:.1%}" if isinstance(r[4], float) else r[4]77    print("特征清单(来源 × 表示形式)")78    print_table(["来源", "特征", "表示", "基数", "覆盖率", "示例"], rows)7980    by_source: dict[str, int] = {}81    by_repr: dict[str, int] = {}82    for r in rows:83        by_source[r[0]] = by_source.get(r[0], 0) + 184        by_repr[r[2]] = by_repr.get(r[2], 0) + 185    print("\n来源分布:", "  ".join(f"{k}={v}" for k, v in by_source.items()))86    print("表示分布:", "  ".join(f"{k}={v}" for k, v in by_repr.items()))8788    # ---- 同一信息的不同表示形式:以"用户近7天观看体育类内容次数"为例 ----89    print("\n同一业务信息(7 天内观看体育内容次数 = 6)的四种表示:")90    print_table(91        ["表示形式", "模型输入", "信息损失", "参数量", "备注"],92        [93            ["Sparse ID", "Embedding[6]", "完全丢失数值顺序", "词表大小 × d", "6 与 7 是两个无关类别"],94            ["Dense 原值", "6.0(可标准化)", "无", "0", "保留数值语义,最省参数"],95            ["分桶 Embedding", "Embedding[bucket(6)]", "桶内信息丢失", "桶数 × d", "兼顾非线性与语义"],96            ["Sequence", "[c,c,g,c,c,c]", "无(但更贵)", "序列编码器参数", "保留次序与具体内容"],97        ],98    )99100    # ---- 覆盖率与缺失的代价 ----101    missing = 0.38102    usable = 1 - missing103    print(f"\n示例:某商品类 Dense 特征覆盖率 {usable:.0%},"104          f"若按默认值 0 填充,模型看到的是 {missing:.0%} 的'零'样本,"105          "这部分样本会把该特征的权重向 0 拉偏(正文 §15.6.4 特征质量)。")106107108if __name__ == "__main__":109    main()

如何用于学习或编写新代码

  1. 先看章节 README 的依赖和运行成本,定位这个例子的输入、输出及关键函数。
  2. 在原书 PDF 中找到对应概念的具体页;代码演示与书中论断分开标注。
  3. 只在依赖和耗时可控时运行单个示例,修改一个参数并记录真实输出与失败情形。
  4. 参考实现新代码时说明复用、改动、测试与尚未完成的生产依赖;教学示例并非生产系统。