前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >稀疏数据如何建模-场感知因子分解机

稀疏数据如何建模-场感知因子分解机

作者头像
ZackSock
发布2023-08-28 17:43:29
1780
发布2023-08-28 17:43:29
举报
文章被收录于专栏:ZackSockZackSock

一、前言

在许多机器学习算法中,都假设各个特征之间无关,比如逻辑回归和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模块。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 新建文件夹X 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、因子分解机
    • 2.1 因子分解机原理
      • 2.2 代码实现
      • 三、场感知因子分解机
        • 3.1 场感知因子分解机原理
          • 3.2 代码实现
          • 四、总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档