一、前言
在许多机器学习算法中,都假设各个特征之间无关,比如逻辑回归和SVM各个特征对应一个特定的权重。基于这一假设,模型可以非常简单,而且参数量也不会过多。但是实际场景中,特征之间关联是非常大的,尤其是经过one-hot编码后的类别特征。
为了建立特征之间的关联,可以组合各个特征并赋予一个权重。比如数据有x1、x2、...、xn这n个特征,在逻辑回归中会对应w1、w2、...、wn这n个权重。为了建立特征之间的关联,加入组合特征x1x1、x1x2、...、x2x1、x2x2、...,并给组合特征赋予一个权重w11、w12...
此时权重从n个变成了n n^2个,虽然达到了建立特征关联的效果,但是参数量过多。而因子分解机可以解决这一问题。
二、因子分解机
2.1 因子分解机原理
因子分解机也叫Factorization Machines,简称FM。其思想就是在模型中加入组合特征和组合特征权重,并使用因子分解减少模型的参数。
在组合特征后,可以用W矩阵来表示组合特征的权重,比如x1x2的权重为W12。当W矩阵低秩时,可以将W分解成长度为n的向量即:
这样权重就从n^2变成了n了,原本W12可以用v1v2替代,此时参数量在容许范围内。虽然W矩阵并不一定是低秩的,但是我们可以假设W低秩,基于这一假设,因子分解机的表达式可以写成:
如果要完成分类任务,那么需要对结果直线softmax或sigmoid函数。相比简单的逻辑回归,因子分解机考虑了特征之间的关系,并对这种关系进行了简化。比如vivj表示特征xi和xj联合起来对结果的影响权重。
2.2 代码实现
在因子分解机中,存在有两组权重和一个偏置,分别为W、V、b,其中W和V都是长度为n_features的向量。我们把公式分为两个部分,第一部分就是线性回归的计算:
用PyTorch实现如下:
代码语言:javascript复制a = torch.matmul(X, W) b
其中X形状为“样本数×1×特征数”,W的形状为“特征数×1”。第二部分是组合特征的计算:
用PyTorch实现如下:
代码语言:javascript复制xx = torch.matmul(X.transpose(1, 2), X)
vv = torch.matmul(V, V.T)
b = torch.sum(xx * vv, dim=[1, 2])
其中X的形状与前面是一样的,V的形状与W一样。由此可以知道xx的形状为“样本数×特征数×特征数”,vv的形状为“特征数×特征数”。知道了这些,FM就很好实现了,下面是FM的代码:
代码语言:javascript复制import torch
from torch import nn
class FM:
def __init__(self, n_features, lr=0.001, max_iter=500):
# 学习率
self.lr = lr
# 最大迭代次数
self.max_iter = max_iter
# 特征数量
self.n_features = n_features
# 初始化权重
self.W = torch.randn(n_features, 1, requires_grad=True)
self.V = torch.randn(n_features, 1, requires_grad=True)
self.b = torch.zeros(1, requires_grad=True)
def fit(self, X, y):
for iter in range(self.max_iter):
# 计算结果
pred = self.predict(X)
# 计算loss
loss = self.loss(pred, y)
# 求梯度
self.gradient_step(loss)
if (iter 1) % 100 == 1:
print(f"iter: {iter 1}/{self.max_iter}, loss: {loss.item()}")
def predict(self, X):
n_samples = X.size(0)
if X.ndim == 2:
X = X.view(n_samples, 1, -1)
a = torch.matmul(X, self.W) self.b
xx = torch.matmul(X.transpose(1, 2), X)
vv = torch.matmul(self.V, self.V.T)
b = torch.sum(xx * vv, dim=[1, 2])
return a.view(n_samples, 1) b.view(n_samples, 1)
def loss(self, pred, y):
return torch.pow(pred - y, 2).mean()
def gradient_step(self, loss):
# 计算梯度
gW, gV, gb = torch.autograd.grad(loss, [self.W, self.V, self.b])
# 更新权重
self.W = torch.subtract(self.W, self.lr * gW)
self.V = torch.subtract(self.V, self.lr * gV)
self.b = torch.subtract(self.b, self.lr * gb)
# 创建数据
X = torch.randn(2000, 5)
a = torch.sum(torch.matmul(X ** 2, torch.randn(5, 1)), dim=1)
b = torch.sum(torch.matmul(X, torch.randn(5, 1)), dim=1)
y = a b torch.randn(2000)
# 构建模型
fm = FM(5, lr=0.01, max_iter=2000)
# 训练
fm.fit(X, y)
三、场感知因子分解机
因子分解机可以解决逻辑回归中无法学习组合特征的问题,同时也解决了参数量过大的问题。但是在因子分解机中,组合特征的权重矩阵秩为1,这限制了模型的能力。并且在因子分解机中,默认所有特征两两之间都是有关系的,这一假设也不符合常理。为了解决上述两个问题,在因子分解机中引入场(特征组)的概念。
3.1 场感知因子分解机原理
在场感知因子分解机中,n个特征会被分为f个场,每个场中的特征有较强的相关性。比如一个类别变量进行one-hot编码后得到k个特征,这k个特征就可以被划分到一个场。引入场后的模型被称为场感知因子分解机(Field-aware Factorization Machine FFM)。
场感知因子分解机可以看作有f个组合特征的因子分解机,其表达如下:
此时模型参数量为fn,当f为1时,模型简化为因子分解机;当f为n时,和最开始的策略一致。
3.2 代码实现
场感知因子分解机的代码与因子分解机类似,这里只需要修改V的初始化和predict方法即可。代码如下:
代码语言:javascript复制import torch
from torch import nn
class FFM:
def __init__(self, n_features, n_field=2, lr=0.001, max_iter=500):
self.lr = lr
self.max_iter = max_iter
# 场的数量
self.n_field = n_field
self.n_features = n_features
self.W = torch.randn(n_features, 1, requires_grad=True)
# 形状为“场数×特征数×1”
self.V = torch.randn(n_field, n_features, 1, requires_grad=True)
self.b = torch.zeros(1, requires_grad=True)
self.criterion = nn.MSELoss()
def fit(self, X, y):
pass
def predict(self, X):
n_samples = X.size(0)
if X.ndim == 2:
X = X.view(n_samples, 1, -1)
a = torch.matmul(X, self.W) self.b
xx = torch.matmul(X.transpose(1, 2), X).view(n_samples, -1)
vv = torch.matmul(self.V, self.V.transpose(1, 2)).view(self.n_field, -1).transpose(0, 1)
b = torch.sum(torch.matmul(xx, vv), dim=[1])
return a.view(n_samples, 1) b.view(n_samples, 1)
def loss(self, pred, y):
pass
def gradient_step(self, loss):
pass
这里使用的是比较简单的实现,即把原本的向量V改成一个f*n的矩阵,其余部分可以复用FM的代码。在上述代码中,场没有刻意去设计。
相比逻辑回归,场因子分解机有很多优点。当面临稀疏数据时,逻辑回归的效果会非常差。在数据挖掘中,类别数据大量存在,结果one-hot编码后,数据会特别稀疏而使用场感知因子分解机可以很好解决该问题。
四、总结
本文讨论了对回归模型的改进,在文章开始提出线性回归的缺陷,即特征之间没有关联。基于这一问题,提出了几种解决方案,分别是加入组合特征、因子分解机、场感知因子分解机。三种方案各有优缺点,因子分解机的模型表达能力会偏弱,而加入全部组合特征模型的复杂度非常高。场感知因子分解机则是在两者之间取的一个平衡,利用场的概念简化让模型即有较强的表达能力,又有相当较少的参数。
最后,关于FM和FFM的实现可以参考libfm模块。