FeatureColumn交叉仅预计算离散ID,不学真实交互;自定义乘法层(如Multiply)可学习数值×类别等动态交互,但需对齐维度、归一化数值特征并确保梯度流完整。

FeatureColumn做特征交叉:只适合简单组合,别指望它学交互
crossed_column 和 embedding_column 搭配用,能生成离散特征的笛卡尔积(比如 ["gender", "city"] → "male_X_shanghai"),但它不参与梯度更新,纯查表映射。
你看到的“交叉特征”其实是预计算好的 ID,模型后续只学这个 ID 对应的 embedding 向量,不是真正学习两个原始特征如何非线性交互。
- 仅支持
categorical_columnwith*类型,数值列必须先分桶(bucketized_column)才能进crossed_column - 组合后特征空间爆炸:10 个城市 × 2 种性别 × 5 个年龄段 = 100 个桶,如果漏掉某组合,对应 embedding 就是未训练的零向量
-
hash_bucket_size设太小会碰撞,设太大浪费内存;线上 serving 时 hash 行为必须和训练一致,否则 ID 对不上
示例中常见错误:crossed_column([age, gender], hash_bucket_size=10) —— age 是连续值,没过 bucketized_column,运行时报 TypeError: column must be categorical。
自定义乘法层做特征交叉:真学交互,但得管好维度和梯度流
想让模型自己学 “用户点击率 = f(年龄 × 兴趣权重 + 地域 × 偏好偏移)”,就得用可训练的乘法操作,比如 tf.keras.layers.Multiply 或手写 tf.multiply。
关键不是“能不能乘”,而是“乘完怎么接下去”:
- 两个输入张量 shape 必须对齐,比如都是
(None, 1)才能点乘;若一个是(None, 8)(embedding 输出),另一个是(None,)(标量特征),得先tf.expand_dims或tf.reshape - 直接
tf.multiply(a, b)没问题,但若 a 是 dense layer 输出、b 是原始输入,b 默认不可训练,梯度不会回传到 b 的上游(比如归一化层),容易误以为“交叉没效果” - 用
tf.keras.layers.Multiply更稳妥,它自动处理 broadcast,且在 Model 中显式可见,debug 时容易定位
常见翻车点:把 user_emb(shape=(None, 64))和 item_id(shape=(None,))直接相乘 —— 报错 Incompatible shapes,得先对 item_id 做 embedding,再确保维度一致。
数值特征×类别特征交叉:先统一成稠密向量,再乘
数值列不能直接和类别 embedding 相乘,因为维度不匹配,也不能靠 crossed_column(它只认离散 ID)。真实场景里,常要表达“高收入用户在一线城市点击更多”,这就需要数值 × 类别联合建模。
做法是:
- 数值列走一个小型 dense 网络(比如 1 层 8 维),输出
num_dense - 类别列走 embedding,输出
cat_emb(比如 (None, 8)) - 两者
tf.multiply(num_dense, cat_emb),得到逐元素加权后的 embedding - 再接 dense 层或拼接到主干网络
注意:
- 别省略数值列的归一化(
tf.keras.layers.Normalization),否则income=10000和income=50对乘法影响失衡 - 如果用
tf.keras.layers.Dot做内积,输出是标量,丢失了通道信息,一般不用在特征交叉主干上 - 这种乘法结果仍是稠密向量,可直接 concat 或 sum pooling,但别忘了后续 dropout/BN 要适配新维度
性能与部署陷阱:乘法本身快,但 shape 错误会让整个 pipeline 卡住
tf.multiply 计算开销几乎可忽略,真正拖慢训练或炸掉 OOM 的,是 shape 不对导致的隐式 broadcast 或中间张量膨胀。
- 在
tf.function里调试时,用tf.shape(x)查动态 shape,别信x.shape(它可能含None) - TPU 上尤其敏感:broadcast 不兼容会直接报
XLA compilation failed,而不是清晰的 shape error - SavedModel 导出后,输入 signature 必须明确指定所有交叉输入的 shape,比如数值特征不能只写
tf.TensorSpec(shape=[None], ...),得是shape=[None, 1],否则 serving 时 reshape 失败
最易被忽略的一点:当用 tf.keras.Model 构建多输入模型时,call 方法里所有乘法操作必须发生在 inputs 已经经过各自 preprocessing layer 之后——否则 preprocessing 的可导性断裂,交叉部分就变成静态规则了。










