1. 程式人生 > 其它 >【推薦演算法】因子分解機(Factorization Machines,FM)

【推薦演算法】因子分解機(Factorization Machines,FM)

因子分解機(Factorization Machines,FM)主要解決了LR的以下幾個痛點:

  1. 實現自動特徵交叉。LR只能只能手工設計特徵之間的交叉,依賴大量人力與業務知識,並且無法挖掘業務構建特徵的盲點;
  2. 在稀疏特徵上的效果更好。對LR進行暴力二階特徵交叉也能實現特徵自動交叉的效果(如POLY_v2),但是這樣的模型只能更新這個樣本對應的特徵pair。例如,某一個樣本擁有特徵A與B,該樣本只能更新A-B這個pair的權重,這個樣本對特徵A-C是沒有任何作用的。而FM對每一個特徵建立了一個隱向量(可以看做embedding),交叉特徵的權重等於兩個隱向量的內積。這樣,樣本對A-B可以同時更新特徵A與特徵B的隱向量,緩解了特徵稀疏的問題。

演算法

FM使用矩陣分解的方法,為每個特徵學習了一個隱權重向量。特徵交叉時,將兩個特徵對應的隱向量相乘,得到該交叉特徵的權重。與LR相比,多了個二階項。

\[\text{FM}(\mathbf{w, x})=w_{0}+\sum_{i=1}^{n} w_{i} x_{i}+\sum_{i=1}^{n} \sum_{j=i+1}^{n}\left\langle\mathbf{v}_{i}, \mathbf{v}_{j}\right\rangle x_{i} x_{j} \]

二次項可以化簡,訓練和推理的時間複雜度可以從\(O(kn^2)\)降到\(O(kn)\)\(n\)為向量維度:

\[\begin{aligned} & \sum_{i=1}^{n} \sum_{j=i+1}^{n}\left\langle\mathbf{v}_{i}, \mathbf{v}_{j}\right\rangle x_{i} x_{j} \\ =& \frac{1}{2} \sum_{i=1}^{n} \sum_{j=1}^{n}\left\langle\mathbf{v}_{i}, \mathbf{v}_{j}\right\rangle x_{i} x_{j}-\frac{1}{2} \sum_{i=1}^{n}\left\langle\mathbf{v}_{i}, \mathbf{v}_{i}\right\rangle x_{i} x_{i} \\ =& \frac{1}{2}\left(\sum_{i=1}^{n} \sum_{j=1}^{n} \sum_{f=1}^{k} v_{i, f} v_{j, f} x_{i} x_{j}-\sum_{i=1}^{n} \sum_{f=1}^{k} v_{i, f} v_{i, f} x_{i} x_{i}\right) \\ =& \frac{1}{2} \sum_{f=1}^{k}\left(\left(\sum_{i=1}^{n} v_{i, f} x_{i}\right)\left(\sum_{j=1}^{n} v_{j, f} x_{j}\right)-\sum_{i=1}^{n} v_{i, f}^{2} x_{i}^{2}\right) \\ =& \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}\right) \end{aligned} \]

模型輸入

FM的一階部分可以直接複用LR,只需要額外實現特徵的二階交叉。首先,我們需要表示每個特徵的embedding向量:

class FeaturesEmbedding(torch.nn.Module):
    def __init__(self, field_dims, embed_dim):
        super().__init__()
        self.embedding = torch.nn.Embedding(sum(field_dims), embed_dim)
        self.offsets = np.array((0, *np.cumsum(field_dims)[:-1]), dtype=np.long)
        torch.nn.init.xavier_uniform_(self.embedding.weight.data)

    def forward(self, x):
        """
        :param x: Long tensor of size ``(batch_size, num_fields)``
        """
        x = x + x.new_tensor(self.offsets).unsqueeze(0)
        return self.embedding(x)

根據上一節化簡後的公式,我們可以通過下面的程式碼計算二階交叉特徵:

class FactorizationMachine(torch.nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, x):
        """
        :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
        """
        square_of_sum = torch.sum(x, dim=1) ** 2
        sum_of_square = torch.sum(x ** 2, dim=1)
        ix = square_of_sum - sum_of_square
        ix = torch.sum(ix, dim=1, keepdim=True)
        return 0.5 * ix

最後,構建完整的FM前向傳播鏈路:

class FactorizationMachineModel(torch.nn.Module):
    def __init__(self, field_dims, embed_dim=10):
        super().__init__()
        self.embedding = FeaturesEmbedding(field_dims, embed_dim)
        self.linear = FeaturesLinear(field_dims)
        self.fm = FactorizationMachine(reduce_sum=True)

    def forward(self, x):
        x = self.linear(x) + self.fm(self.embedding(x))
        return torch.sigmoid(x.squeeze(1))



模型效果

設定:
資料集:ml-100k
優化方法:Adam
學習率:0.003

效果:
收斂epoch:10
train logloss: 0.51644
val auc: 0.77954
test auc: 0.78550