关于FM算法

一、FM算法的简介:

⑴FM 算法可进行回归和二分类预测的一种算法。
⑵FM的主要目标就是解决在数据稀疏的情况下,特征怎样组合的问题
⑶FM的特点是考虑了特征之间的相互作用,是一种非线性模型。
⑷目前FM算法是推荐领域被验证的效果较好的推荐方案之一,在诸多电商、广告、直播厂商的推荐领域有广泛应用。
⑸原作者在提出FM的时候,曾以SVM、MF为相比较的例子。
①SVM是可以适用多场合的,但有个场合不适用,就是数据集是那种高度稀疏的场合(就是有一堆0,茫茫的0中夹杂零星几个1或者其他数字),这种场合在推荐领域极为常见,但FM在这种场合下也运作的很好 。其实本质上就是SVM并没有像FM那样通过参数共享的方法解决要用少量记录数据拟合大量参数的问题.
②但有些问题实在不容易用线性代数的语言定义啊,所以注定了MF这类算法适用面较窄,都只能针对一类问题解决;但FM能把所有问题打包,FM稍作改动能变成MF,但MF却不能变为FM。

二、FM算法的优点:

⑴FM模型可以在非常稀疏的数据中进行合理的参数估计,而SVM做不到这点
⑵在FM模型的复杂度是线性的,优化效果很好,而且不需要像SVM一样依赖于支持向量。
⑶FM是一个通用模型,它可以用于任何特征为实值的情况。而其他的因式分解模型只能用于一些输入数据比较固定的情况。

三、FM算法解析:

⑴讨论二阶多项式模型。具体的模型表达式如下:
图1
在多项式模型中,特征Xi与Xj的组合用XiXj表示,n表示样本的特征数量,Xi表示第i个特征

⑵FM求解
在这里插入图片描述
在这里插入图片描述
省略了推导过程,采用SGD训练模型,模型各个参数的梯度更新公式如下:
在这里插入图片描述

四.用Python实现FM算法:

(1)构造二分类数据集

import pandas as pd
import numpy as np

pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
df = pd.read_csv('u.data', sep='\t', header=None, names=rnames, engine='python')

(2)保留one-hot特征

df['rating']=df['rating'].map(lambda x: -1 if x>=3 else 1) #1,2是label=1  3,4,5是label=0

#one-hot encoder
from sklearn.preprocessing import OneHotEncoder
columns=['user_id', 'movie_id']

for i in columns:
    get_dummy_feature=pd.get_dummies(df[i])
    df=pd.concat([df, get_dummy_feature],axis=1)
    df=df.drop(i, axis=1)

df=df.drop(['timestamp'], axis=1)

(3)损失函数

from sklearn.model_selection import train_test_split

X=df.drop('rating', axis=1)
Y=df['rating']

X_train,X_val,Y_train,Y_val=train_test_split(X, Y, test_size=0.3, random_state=123)

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def logit(y, y_hat): #对每一个样本计算损失
    if y_hat == 'nan':
        return 0
    else:
        return np.log(1 + np.exp(-y * y_hat))

def df_logit(y, y_hat):
    return sigmoid(-y * y_hat) * (-y)

(4)FM核心

class FactorizationMachine(BaseEstimator):
    def __init__(self, k=5, learning_rate=0.01, iternum=2):
        self.w0 = None
        self.W = None
        self.V = None
        self.k = k
        self.alpha = learning_rate
        self.iternum = iternum

    def _FM(self, Xi):
        interaction = np.sum((Xi.dot(self.V)) ** 2 - (Xi ** 2).dot(self.V ** 2))
        y_hat = self.w0 + Xi.dot(self.W) + interaction / 2
        return y_hat[0]

    def _FM_SGD(self, X, y):
        m, n = np.shape(X)
        # 初始化参数
        self.w0 = 0
        self.W = np.random.uniform(size=(n, 1))
        self.V = np.random.uniform(size=(n, self.k))  # Vj是第j个特征的隐向量  Vjf是第j个特征的隐向量表示中的第f维

        for it in range(self.iternum):
            total_loss = 0
            for i in range(m):  # 遍历训练集
                y_hat = self._FM(Xi=X[i])  # X[i]是第i个样本  X[i,j]是第i个样本的第j个特征

                total_loss += logit(y=y[i], y_hat=y_hat)  # 计算logit损失函数值
                dloss = df_logit(y=y[i], y_hat=y_hat)  # 计算logit损失函数的外层偏导

                dloss_w0 = dloss * 1  # 公式中的w0求导,计算复杂度O(1)
                self.w0 = self.w0 - self.alpha * dloss_w0

                for j in range(n):
                    if X[i, j] != 0:
                        dloss_Wj = dloss * X[i, j]  # 公式中的wi求导,计算复杂度O(n)
                        self.W[j] = self.W[j] - self.alpha * dloss_Wj
                        for f in range(self.k):  # 公式中的vif求导,计算复杂度O(kn)
                            dloss_Vjf = dloss * (X[i, j] * (X[i].dot(self.V[:, f])) - self.V[j, f] * X[i, j] ** 2)
                            self.V[j, f] = self.V[j, f] - self.alpha * dloss_Vjf
        print('iter={}, loss={:.4f}'.format(it+1, total_loss / m))

        return self

    def _FM_predict(self, X):
        predicts, threshold = [], 0.5  # sigmoid阈值设置
        for i in range(X.shape[0]):  # 遍历测试集
            y_hat = self._FM(Xi=X[i])  # FM的模型方程
            predicts.append(-1 if sigmoid(y_hat) < threshold else 1)
        return np.array(predicts)

    def fit(self, X, y):
        if isinstance(X, pd.DataFrame):
            X = np.array(X)
            y = np.array(y)

        return self._FM_SGD(X, y)

    def predict(self, X):
        if isinstance(X, pd.DataFrame):
            X = np.array(X)

        return self._FM_predict(X)

    def predict_proba(self, X):
        pass

from sklearn.metrics import roc_auc_score, confusion_matrix

model=FactorizationMachine(k=10, learning_rate=0.001, iternum=2)
model.fit(X_train, Y_train)

(5)结果出示

y_pred=model.predict(X_train)

print('训练集roc: {:.2%}'.format(roc_auc_score(Y_train.values, y_pred)))
print('混淆矩阵: \n',confusion_matrix(Y_train.values, y_pred))

y_pred=model.predict(X_val)

print('验证集roc: {:.2%}'.format(roc_auc_score(Y_val.values, y_pred)))
print('混淆矩阵: \n',confusion_matrix(Y_val.values, y_pred))

在这里插入图片描述
(因为条件问题,对部分参数进行了部分改动)

        print('iter={}, loss={:.4f}'.format(it+1, total_loss / m))

        return self

 def __init__(self, k=5, learning_rate=0.01, iternum=2):

model=FactorizationMachine(k=10, learning_rate=0.001, iternum=2)

(6)完整的代码

import pandas as pd
import numpy as np

pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

rnames = ['user_id', 'movie_id', 'rating', 'timestamp']
df = pd.read_csv('u.data', sep='\t', header=None, names=rnames, engine='python')
#构造2分类数据集

df['rating']=df['rating'].map(lambda x: -1 if x>=3 else 1) #1,2是label=1  3,4,5是label=0

#one-hot encoder
from sklearn.preprocessing import OneHotEncoder
columns=['user_id', 'movie_id']

for i in columns:
    get_dummy_feature=pd.get_dummies(df[i])
    df=pd.concat([df, get_dummy_feature],axis=1)
    df=df.drop(i, axis=1)

df=df.drop(['timestamp'], axis=1)
#这些特征可以进一步挖掘。这里都不要了,只保留one-hot特征

from sklearn.model_selection import train_test_split

X=df.drop('rating', axis=1)
Y=df['rating']

X_train,X_val,Y_train,Y_val=train_test_split(X, Y, test_size=0.3, random_state=123)

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def logit(y, y_hat): #对每一个样本计算损失
    if y_hat == 'nan':
        return 0
    else:
        return np.log(1 + np.exp(-y * y_hat))

def df_logit(y, y_hat):
    return sigmoid(-y * y_hat) * (-y)


from sklearn.base import BaseEstimator, ClassifierMixin
from collections import Counter


class FactorizationMachine(BaseEstimator):
    def __init__(self, k=5, learning_rate=0.01, iternum=2):
        self.w0 = None
        self.W = None
        self.V = None
        self.k = k
        self.alpha = learning_rate
        self.iternum = iternum

    def _FM(self, Xi):
        interaction = np.sum((Xi.dot(self.V)) ** 2 - (Xi ** 2).dot(self.V ** 2))
        y_hat = self.w0 + Xi.dot(self.W) + interaction / 2
        return y_hat[0]

    def _FM_SGD(self, X, y):
        m, n = np.shape(X)
        # 初始化参数
        self.w0 = 0
        self.W = np.random.uniform(size=(n, 1))
        self.V = np.random.uniform(size=(n, self.k))  # Vj是第j个特征的隐向量  Vjf是第j个特征的隐向量表示中的第f维

        for it in range(self.iternum):
            total_loss = 0
            for i in range(m):  # 遍历训练集
                y_hat = self._FM(Xi=X[i])  # X[i]是第i个样本  X[i,j]是第i个样本的第j个特征

                total_loss += logit(y=y[i], y_hat=y_hat)  # 计算logit损失函数值
                dloss = df_logit(y=y[i], y_hat=y_hat)  # 计算logit损失函数的外层偏导

                dloss_w0 = dloss * 1  # 公式中的w0求导,计算复杂度O(1)
                self.w0 = self.w0 - self.alpha * dloss_w0

                for j in range(n):
                    if X[i, j] != 0:
                        dloss_Wj = dloss * X[i, j]  # 公式中的wi求导,计算复杂度O(n)
                        self.W[j] = self.W[j] - self.alpha * dloss_Wj
                        for f in range(self.k):  # 公式中的vif求导,计算复杂度O(kn)
                            dloss_Vjf = dloss * (X[i, j] * (X[i].dot(self.V[:, f])) - self.V[j, f] * X[i, j] ** 2)
                            self.V[j, f] = self.V[j, f] - self.alpha * dloss_Vjf
        print('iter={}, loss={:.4f}'.format(it+1, total_loss / m))

        return self

    def _FM_predict(self, X):
        predicts, threshold = [], 0.5  # sigmoid阈值设置
        for i in range(X.shape[0]):  # 遍历测试集
            y_hat = self._FM(Xi=X[i])  # FM的模型方程
            predicts.append(-1 if sigmoid(y_hat) < threshold else 1)
        return np.array(predicts)

    def fit(self, X, y):
        if isinstance(X, pd.DataFrame):
            X = np.array(X)
            y = np.array(y)

        return self._FM_SGD(X, y)

    def predict(self, X):
        if isinstance(X, pd.DataFrame):
            X = np.array(X)

        return self._FM_predict(X)

    def predict_proba(self, X):
        pass

from sklearn.metrics import roc_auc_score, confusion_matrix

model=FactorizationMachine(k=10, learning_rate=0.001, iternum=2)
model.fit(X_train, Y_train)

y_pred=model.predict(X_train)

print('训练集roc: {:.2%}'.format(roc_auc_score(Y_train.values, y_pred)))
print('混淆矩阵: \n',confusion_matrix(Y_train.values, y_pred))

y_pred=model.predict(X_val)

print('验证集roc: {:.2%}'.format(roc_auc_score(Y_val.values, y_pred)))
print('混淆矩阵: \n',confusion_matrix(Y_val.values, y_pred))


五.参考资料

  1. https://my.oschina.net/u/1464083/blog/3084038
  2. https://blog.csdn.net/g11d111/article/details/77430095
  3. https://blog.csdn.net/bitcarmanlee/article/details/52143909
  4. https://blog.csdn.net/u013015493/article/details/79426548
  5. https://blog.csdn.net/qq_24819773/article/details/86308868
  6. 参考文献:Factorization Machines
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值