一、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算法解析:
⑴讨论二阶多项式模型。具体的模型表达式如下:
在多项式模型中,特征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))
五.参考资料
- https://my.oschina.net/u/1464083/blog/3084038
- https://blog.csdn.net/g11d111/article/details/77430095
- https://blog.csdn.net/bitcarmanlee/article/details/52143909
- https://blog.csdn.net/u013015493/article/details/79426548
- https://blog.csdn.net/qq_24819773/article/details/86308868
- 参考文献:Factorization Machines