Factorization Machine(FM模型,因子分解机)

一、FM 是什么?干什么用的?

FM(Factorization Machine,因子分解机)Steffen Rendle 于 2010 年 在论文 "Factorization Machines" (ICDM) 中提出。

一句话定位 :FM 是一种 专为高维稀疏数据设计的监督学习模型 ,它在线性模型的基础上,自动建模所有特征的两两交叉(二阶交互) ,但通过 矩阵分解/隐向量内积 的技巧,把参数量从 O(n²) 压到 O(n·k),从而在稀疏场景下 既能学到交叉特征,又不会因数据稀疏而垮掉

典型用途

领域 具体任务
推荐系统 评分预测、个性化排序(user × item × context)
计算广告 CTR(点击率)预估、CVR(转化率)预估
风控/营销 高维稀疏特征下的二分类/回归预测
搜索排序 Learning-to-Rank 中的特征交互建模

本质上,FM 是 多项式回归的表达力矩阵分解的参数效率 的结合体。


二、为什么需要 FM:传统方法的痛点

线性模型太"笨"------忽略特征交互

在 CTR 预估中,一个经典事实是:

"男性 ∧ 大学生 ∧ 游戏类广告" → 点击率很高

但单独的「男性」「大学生」「游戏类」每个都不够说明问题

如果用纯线性模型(如 Logistic Regression):

y^=w0+∑i=1nwixi\hat{y} = w_0 + \sum_{i=1}^{n} w_i x_iy^=w0+i=1∑nwixi

它把每个特征当独立变量,完全捕捉不到"特征之间的组合效应"

人工特征交叉不可行------维度爆炸 + 极度稀疏

一个朴素的改进:加入 二阶交叉项(二次多项式模型):

y^=w0+∑iwixi+∑i=1n∑j=i+1nwij⋅xixj\hat{y} = w_0 + \sum_{i} w_i x_i + \sum_{i=1}^{n}\sum_{j=i+1}^{n} w_{ij} \cdot x_i x_jy^=w0+i∑wixi+i=1∑nj=i+1∑nwij⋅xixj

问题来了:

  • 特征维度 n 动辄 上万甚至百万(想想 user_id、item_id 做了 one-hot 之后)
  • 交叉项参数 wijw_{ij}wij 的数量是 O(n²) → 根本存不下、学不了
  • 更致命的是:数据是 极度稀疏的 (每行只有极少几个非零值),绝大多数交叉组合在训练集里 从未同时出现过 → 对应的 wijw_{ij}wij 根本没有样本支撑,无法估计

这就是为什么 FM 的诞生如此关键------它需要一种方式,让"没见过的特征组合"也能被合理估计


三、FM 的核心原理:用「隐向量内积」替代独立权重

核心思想

与其给每个交叉组合 (i,j)(i, j)(i,j) 分配一个 独立参数 wijw_{ij}wij,不如:

每个特征 i 分配一个 低维隐向量(latent vector) vi∈Rk\mathbf{v}_i \in \mathbb{R}^kvi∈Rk,然后令交叉权重等于两个隐向量的 内积

wij=⟨vi,vj⟩=∑f=1kvi,f⋅vj,fw_{ij} = \langle \mathbf{v}i, \mathbf{v}j \rangle = \sum{f=1}^{k} v{i,f} \cdot v_{j,f}wij=⟨vi,vj⟩=f=1∑kvi,f⋅vj,f

这就叫 Factorization(因子分解) ------把原本需要 O(n2)O(n^2)O(n2) 个独立参数的交叉权重矩阵 W\mathbf{W}W,分解为两个 n×kn \times kn×k 的低秩隐矩阵的乘积(近似),k≪nk \ll nk≪n。

完整 FM 预测公式(二阶 FM)

y^(x)=w0+∑i=1nwixi+∑i=1n∑j=i+1n⟨vi,vj⟩⋅xixj\boxed{\hat{y}(\mathbf{x}) = w_0 + \sum_{i=1}^{n} w_i x_i + \sum_{i=1}^{n}\sum_{j=i+1}^{n} \langle \mathbf{v}_i, \mathbf{v}_j \rangle \cdot x_i x_j}y^(x)=w0+i=1∑nwixi+i=1∑nj=i+1∑n⟨vi,vj⟩⋅xixj

含义
w0w_0w0 全局偏置(bias)
∑wixi\sum w_i x_i∑wixi 一阶项:每个特征自身的线性贡献
∑∑⟨vi,vj⟩xixj\sum\sum \langle\mathbf{v}_i,\mathbf{v}_j\rangle x_i x_j∑∑⟨vi,vj⟩xixj 二阶交互项:所有特征两两交叉的贡献,权重由隐向量内积给出

其中隐向量维度 k 是超参数(常见取 8~128,实践中 10~50 就很好)。

为什么隐向量内积能解决"稀疏"问题?

这是 FM 最精妙的地方:

假设特征对 (i,j)(i, j)(i,j) 在训练集中 从未同时非零 (所以你没有直接样本学 wijw_{ij}wij),但只要特征 iii 和 jjj 各自 在其它样本中出现过,它们的隐向量 vi\mathbf{v}_ivi 和 vj\mathbf{v}_jvj 就会被 其它交叉组合间接更新 (梯度会通过所有包含 iii 或 jjj 的项传播)。最终 ⟨vi,vj⟩\langle\mathbf{v}_i,\mathbf{v}_j\rangle⟨vi,vj⟩ 仍能给出一个 合理的估计

这正是从 矩阵分解(Matrix Factorization) 那里借来的思想------用户没评分的物品,仍可通过隐因子推断。


四、关键优化:把 O(n²) 的计算变成 O(n·k)

直接按双重循环算交叉项是 O(n²·k),不可接受。FM 有一个著名的数学恒等式(Rendle trick):

∑i=1n∑j=i+1n⟨vi,vj⟩xixj=12∑f=1k(∑i=1nvi,fxi)2−∑i=1nvi,f2xi2\sum_{i=1}^{n}\sum_{j=i+1}^{n} \langle\mathbf{v}_i,\mathbf{v}j\rangle x_i x_j = \frac{1}{2} \sum{f=1}^{k} \left \\left(\\sum_{i=1}\^{n} v_{i,f} x_i\\right)\^2 - \\sum_{i=1}\^{n} v_{i,f}\^2 x_i\^2 \\righti=1∑nj=i+1∑n⟨vi,vj⟩xixj=21f=1∑k (i=1∑nvi,fxi)2−i=1∑nvi,f2xi2

推导直觉

(∑ivi,fxi)2=∑ivi,f2xi2+2∑i<jvi,fvj,fxixj(\sum_i v_{i,f}x_i)^2 = \sum_i v_{i,f}^2 x_i^2 + 2\sum_{i<j} v_{i,f}v_{j,f}x_i x_j(i∑vi,fxi)2=i∑vi,f2xi2+2i<j∑vi,fvj,fxixj

移项即可得到上述等式。

复杂度对比

方式 时间复杂度 空间复杂度
朴素二阶交叉 O(n²·k) O(n²)
FM + Rendle 恒等式 O(n·k)(稀疏下 ≈ O(nnz·k)) O(n·k)
多项式核 SVM 通常 O(n²) 或依赖 dual ---

在稀疏场景下(每行只有几十~几百个非零值),FM 的推断/训练代价是 线性于非零特征数 的,这就让它能在工业级数据上跑起来。


五、运行原理示意流程(从原始数据到预测)

下面用一个 电影推荐/CTR 场景 走一遍完整流程:

Step 1:原始数据示例

样本 用户 电影 类型 时间槽 标签:评分/点击
1 用户#103 电影#579 动作 晚上 1(点击/喜欢)
2 用户#887 电影#1203 喜剧 下午 0

Step 2:特征编码(One-hot / Multi-hot → 超高维稀疏向量)

user_idmovie_idgenretime_slot 做 one-hot 编码后,每条样本变成一个 几万~百万维稀疏向量 x,其中只有少数几个位置为 1(或计数),其余全是 0。

例如:x=0,...,1user#103,...⏟user域,0,...,1movie#579,...⏟item域,0,...,1动作,...⏟genre域,...x = \\underbrace{0,...,\\mathbf{1}_{user\\#103},...}_{\\text{user域}}, \\underbrace{0,...,\\mathbf{1}_{movie\\#579},...}_{\\text{item域}}, \\underbrace{0,...,\\mathbf{1}_{动作},...}_{\\text{genre域}}, ...x=user域 0,...,1user#103,...,item域 0,...,1movie#579,...,genre域 0,...,1动作,...,...

Step 3:FM 模型参数初始化

  • w0∈Rw_0 \in \mathbb{R}w0∈R:全局偏置
  • w∈Rn\mathbf{w} \in \mathbb{R}^nw∈Rn:一阶权重(实际只需存非零特征对应的)
  • V∈Rn×k\mathbf{V} \in \mathbb{R}^{n \times k}V∈Rn×k:每个特征的隐向量(同样只需存活跃特征对应的行)

初始化一般用小的随机值(如 Gaussian(0, 0.01))。

Step 4:前向预测(对一条样本 x)

复制代码
① 一阶项:linear = w₀ + Σᵢ∈active(wᵢ · xᵢ)

② 二阶项(用Rendle trick):
   for f = 1..k:
     s₁ = Σᵢ∈active(vᵢ,ₙ · xᵢ)      // 先求 sum
     s₂ = Σᵢ∈active(vᵢ,ₙ² · xᵢ²)    // 求平方和
     interaction += (s₁² - s₂)
   interaction = 0.5 · interaction

③ ŷ = linear + interaction
④ 如果是CTR→ p_click = sigmoid(ŷ)

Step 5:训练(SGD / Mini-batch SGD / Adam)

对二分类(CTR),损失用 LogLoss

L=−ylog⁡σ(y\^)+(1−y)log⁡(1−σ(y\^))+λ(∥w∥2+∥V∥2)\mathcal{L} = -y \\log \\sigma(\\hat{y}) + (1-y)\\log(1-\\sigma(\\hat{y})) + \lambda(\|\mathbf{w}\|^2 + \|\mathbf{V}\|^2)L=−ylogσ(y\^)+(1−y)log(1−σ(y\^))+λ(∥w∥2+∥V∥2)

梯度(关键):

∂y^∂w0=1,∂y^∂wi=xi,∂y^∂vi,f=xi(∑j=1nvj,fxj)−vi,fxi2\frac{\partial \hat{y}}{\partial w_0} = 1, \quad \frac{\partial \hat{y}}{\partial w_i} = x_i, \quad \frac{\partial \hat{y}}{\partial v_{i,f}} = x_i\left(\sum_{j=1}^{n} v_{j,f} x_j\right) - v_{i,f} x_i^2∂w0∂y^=1,∂wi∂y^=xi,∂vi,f∂y^=xi(j=1∑nvj,fxj)−vi,fxi2

注意到 ∑jvj,fxj\sum_j v_{j,f} x_j∑jvj,fxj 对每个样本只需要算一次 (就是上面 trick 里的 s₁),然后分发到每个活跃特征 iii。

Step 6:推断/排序

训练完后,对新用户-物品对构造稀疏向量 x ,跑一次前向预测,输出的 y^\hat{y}y^(或 sigmoid 后概率)就是 预估分数,用于排序推荐。


六、FM 的优劣势一览

优势 局限
自动二阶交叉,不需人工特征工程 只显式建模 二阶交互(不擅长更高阶)
参数量 O(n·k),适合高维稀疏 隐向量是"全局共享"的,无法区分特征所属的 field 语义差异(FFM 解决)
稀疏数据下泛化能力强(没见过的组合也能估) 二阶表达能力有天花板,复杂非线性关系需更深模型
训练和推断复杂度线性于 nnz,工程友好 相比纯 DNN 方案,拟合能力上限较低
可作为强基线,也可嵌入更大网络(DeepFM等) ---

七、应用场景全景

1. 推荐系统(最核心阵地)

  • 协同过滤的泛化版:FM = 矩阵分解 + 还能用 side information(用户画像、物品属性、上下文)
  • 场景:电商"猜你喜欢"、视频推荐 feed 排序、豆瓣评分预测
  • 特征一般分域编码:[user_id_onehot | item_id_onehot | user_age_bucket | item_category | hour_of_day | ...]

2. 广告 CTR / CVR 预估

  • Criteo、Avazu 等经典 CTR 竞赛中 FM/FFM 曾拿冠军
  • 特征包括:广告 id、广告主 id、用户兴趣标签、设备类型、媒体站点、时段......
  • 这些数据天然 高基数 + one-hot → 极稀疏 → FM 的理想战场

3. 搜索排序(Learning to Rank)

  • 将 query 特征、doc 特征、query×doc 匹配特征编码为稀疏向量,FM 捕捉交叉

4. 风控 / 反欺诈 / 营销响应

  • 客群标签 × 产品类型 × 渠道 × 时段的稀疏交叉建模

八、FM 家族 & 类似模型:从 FM 到今天的全景对比

这是理解 FM 在模型进化树上位置的 最关键部分

演进脉络

复制代码
Linear Model (LR)
  │
  ├── 人工交叉特征 + LR  → 繁琐、爆炸、不可持续
  │
  ├── 二次多项式模型(Poly-2)→ 参数 O(n²),稀疏下崩
  │     │
  │     └── ★ FM ← 用隐向量内积降参到 O(n·k),救活了交叉
  │           │
  │           ├── FFM(Field-aware FM):每个特征在每个field用不同隐向量
  │           ├── AFM(Attention FM):给交叉项加权(注意力机制)
  │           ├── NFM(Neural FM):用神经网络替代/增强交叉池化
  │           │
  │           └── FM + DNN 融合路线:
  │                 ├── Wide & Deep(Google '16):Wide=人工交叉 + Deep=DNN
  │                 ├── DeepFM(华为 '17):直接用FM替掉Wide部分 → 全自动
  │                 ├── xDeepFM:显式向量级高阶交叉
  │                 └── DCN / DCN-M:cross network 做特征交叉

详细对比表

模型 核心机制 交叉能力 参数量 是否需要人工交叉 典型场景
LR 纯线性 ❌ 无 O(n) ✅ 必须手工做 早期工业基线
Poly-2 显式二阶交叉,独立 wijw_{ij}wij 二阶 O(n²) 不需要 理论可用,但稀疏下不可行
★ FM 二阶交叉,但 wij=⟨vi,vj⟩w_{ij}=\langle v_i,v_j\ranglewij=⟨vi,vj⟩(共享隐向量) 二阶(参数共享) O(n·k) ❌ 全自动 推荐/CTR 主力基线
FFM FM + Field 概念:特征 iii 在不同 field 用不同隐向量 vi,fjv_{i,f_j}vi,fj 二阶(field感知) O(n·k·F) 竞赛利器,效果常优于FM,但更慢
AFM FM + Attention:交叉项加注意力权重,αij\alpha_{ij}αij 学出哪些组合更重要 二阶(加权) O(n·k) 想强化"关键交叉"
NFM FM的BI层(交叉池化) → 接MLP,学高阶 二阶→高阶 O(n·k)+DNN 兼顾表达能力与交叉
Wide & Deep Wide=线性/人工交叉 + Deep=DNN高阶 低阶+高阶 取决于DNN ✅ Wide部分要人工 Google Play 推荐
DeepFM FM部分 (自动低阶交叉)+ DNN部分(高阶),共享Embedding 低阶+高阶,全自动 O(n·k)+DNN ❌ ✅ 工业界最常用的 FM+DL 融合
DCN / xDeepFM Cross Network / Compressed Interaction Network 做结构化高阶交叉 显式高阶 可控 追求更强交叉表达

FFM 的关键补充

FM 中每个特征只有一个隐向量 vi\mathbf{v}_ivi,跟谁交叉都用同一个。

FFM 认为:特征 iii 来自 user 域还是 item 域语义不同,所以给它 按 field 分组 :特征 iii 在与 field fjf_jfj(特征 jjj 所属 field)交叉时用 vi,fj\mathbf{v}_{i,f_j}vi,fj。参数量变为 n·F·k(F = field数),效果更好但慢一些。


九、极简 NumPy 示意代码(帮助建立直觉)

python 复制代码
import numpy as np

class FM:
    def __init__(self, n_features, k=10, lr=0.05, reg=1e-3):
        self.w0 = 0.0
        self.W  = np.zeros(n_features)
        self.V  = np.random.randn(n_features, k) * 0.01
        self.lr = lr
        self.reg = reg

    def _predict(self, x_sparse_indices, x_sparse_values):
        """x 是稀疏表示: indices=非零位列表, values=对应值"""
        # 一阶
        linear = self.w0 + np.dot(self.W[x_sparse_indices], x_sparse_values)

        # 二阶 (Rendle trick)
        vx = self.V[x_sparse_indices] * x_sparse_values[:, None]   # (nnz, k)
        sum_vx = vx.sum(axis=0)                                     # (k,)
        sum_sq = (vx ** 2).sum(axis=0)                               # (k,)
        interaction = 0.5 * ((sum_vx ** 2) - sum_sq).sum()

        return linear + interaction

    def fit(self, data, epochs=50):
        """data: list of (indices, values, y)"""
        for ep in range(epochs):
            np.random.shuffle(data)
            for idxs, vals, y in data:
                y_hat = self._predict(idxs, vals)
                p = 1.0 / (1.0 + np.exp(-y_hat))
                err = p - y  # ∂L/∂y_hat for LogLoss

                # 更新
                self.w0 -= self.lr * (err + self.reg * self.w0)
                self.W[idxs] -= self.lr * (err * vals + self.reg * self.W[idxs])

                vx = self.V[idxs] * vals[:, None]
                sum_vx = self.V[idxs].T @ vals          # 或直接累: vx.sum(0)
                # 完整梯度更新省略细节------工程实现建议用现成库

实际生产中别手写------用 xlearnlibffmlightfm 或 PyTorch 版实现更稳。


相关推荐
绘梨衣5471 小时前
豆包Seed PDF解析企业落地方法论
大数据·python·pdf
SilentSamsara1 小时前
消息队列集成:Python + Kafka/RabbitMQ 生产实践
服务器·开发语言·分布式·python·kafka·rabbitmq
少许极端1 小时前
算法奇妙屋(五十二)-备战+复习2
java·算法
luj_17681 小时前
硝酸核关联假说缺乏实验证据
c语言·开发语言·c++·经验分享·算法
zkkkkkkkkkkkkk1 小时前
python爬虫模拟拖动滑块缺口验证码
爬虫·python·ddddocr
再玩一会儿看代码2 小时前
Java浅拷贝和深拷贝理解笔记
java·linux·开发语言·笔记·python·学习
love530love2 小时前
【笔记】ComfyUI 源码部署版更新后一键修复:从手动补丁到自动化工作流
运维·人工智能·windows·笔记·python·自动化·comfyui
石榴树下的七彩鱼2 小时前
发票OCR识别API接入教程:从图像到结构化数据的完整实战(附Python/Java/PHP/JS代码)
java·python·ocr·api接口·财务自动化·石榴智能·发票ocr