- 1.FM模型
- 2.数据集
- 3.FM求解
这里可以查看我之前的写的MF模型作为学习基础, 推荐系统MF——SVD与SVD++矩阵分解 1.FM模型
FM模型在原本线性模型的基础上,考虑到特征两两之间的关联,对特征进行组合,数据模型上表达特征xi,xj的组合用xixj表示。现在只考虑两阶多项式模型,也就是特征两两组合的问题。公式如下:
首先实现基本的线性模型公式,用随机生成的一个长度为10353-1(除了需要预测的rating) 的符合正态分布的向量来表示各特征的单独权重,有个博主写的关于FM推导非常详细【推荐算法】FM高阶分解模型 - 含特征交叉、POLY2模型
FM要解决的就是高阶矩阵分解,来处理处理额外信息比如时间、标签时存在局限。所以需要的就是高阶数据,这里依然选择使用movicelen数据集。在原有的用户的电影的评分基础上,添加电影类型的向量信息和用户评论的时间标签
特征索引名 | 含义 |
---|---|
movieId | 电影id |
userId | 用户id |
genres | 电影类型 |
timestamp | 时间戳 |
rating | 评分 |
其中电影类型有
数据读入合并
import numpy as np
import pandas as pd
datapath1=r"ml-latest-small\ratings.csv"
data1=pd.read_csv(datapath1)
datapath2=r"ml-latest-small\movies.csv"
data2=pd.read_csv(datapath2)
data1,data2
结果👇
这里我们需要的是movies.csv的genres电影类型信息信息。因为一个电影可能有多个类别。所以使用onehots热编码进行处理。
string="* Action* Adventure* Animation* Children's* Comedy* Crime* Documentary* Drama* Fantasy* Film-Noir* Horror* Musical* Mystery* Romance* Sci-Fi* Thriller* War* Western"
list=string.split("* ")[1:]
list
number=len(list)
ans=[]
for t,row in data2.iterrows():
type_mov=[0]*(number+1)
k=row["genres"].split("|")
type_mov[number]=row["movieId"]
for t in k:
for t2,st in enumerate(list):
if(t==st):
type_mov[t2]=1
ans.append(type_mov.copy())
column=["type_%d" %t for t in range(number)]
column.append("movieId")
ans_pd=pd.DataFrame(ans,columns=column)
ans_pd
同样data1的userid和movieId,作为数值型数据,本身并没有线性关联,这里也进行onehot编码
one_hot_user = pd.get_dummies(data1["userId"].values,prefix="user")
one_hot_mov = pd.get_dummies(data1["movieId"].values,prefix="mov")
one_hot_user,one_hot_mov
然后根据movieId将data1与ans_pd合并和 onehot编码后的movieId和userid合并
# one_hot_user,one_hot_mov
data=data1.join(one_hot_user).join(one_hot_mov)
data=pd.merge(data,ans_pd)
data
时间戳因为时间太大,可能会导致老师说的问题对时间处理不好暂时不使用,也可以使用分箱进行处理这个之后再实现。所以删除原本的userid,movieId,timestamp列.至此数据处理完成,得到了一个100836 rows × 10356 columns的巨大稀疏矩阵。
data=data.drop(columns=["userId","movieId","timestamp"])
data
3.FM求解
FM模型在原本线性模型的基础上,考虑到特征两两之间的关联,对特征进行组合,数据模型上表达特征xi,xj的组合用xixj表示。现在只考虑两阶多项式模型,也就是特征两两组合的问题。公式如下:
首先实现基本的线性模型公式,用随机生成的一个长度为10353-1(除了需要预测的rating) 的符合正态分布的向量来表示各特征的单独权重
w0=0
import numpy as np
w=np.random.normal(0,0.5,10353-1)
w
此时的线性回归模型表示为
# w=pd.DataFrame(w)
ans_data=np.dot(data.drop(columns=["rating"]),w)+w0
pd.DataFrame(ans_data)
预测值与实践评分相减的绝对值总和作为损失函数
abs(data["rating"]-ans_data).sum()
FM在这个基础上加上了两函数之间的特征两两组合,这里用10352*10352矩阵表示X[i][j]表示第i个和第j个矩阵的关系权重,依然使用正态分布生成的方式,根据测试方差在0.001比较接近原评分,减少初期迭代次数。计算预测值并不需要全部的矩阵 只需要对角线一半,因为全部计算除了对角线都会重复计算,使用np.triu将下三角已经对角线设为0
wij=np.random.normal(0,0.001,[10352,10352])
wij=np.triu(wij, k=1)
pd.DataFrame(wij)
修改添加两两特征权重后的公式表示修改为如下。如果取全部数据,运行超过5分钟,说明100836 × 10353 与100836 × 10353的矩阵乘法过于庞大,这里只取前100份进行运算
data_ij=data.drop(columns=["rating"])[100815:]
data_ij
k=[]
from tqdm import tqdm
for name,data_t in tqdm(data_ij.iterrows(),desc="遍历到第几个用户"):
data_ij2=np.dot(data_t.T,data_t)
ans_ij=np.dot(wij,data_ij2).sum()
k.append(ans_ij)
k
ans_data=np.dot(data_ij,w)+k+w0
pd.DataFrame(ans_data)
(data["rating"][100815:]-ans_data).sum()
因为1035210352矩阵的太过巨大影响到计算的速率,因为只需要上三角矩阵,所以可以通过矩阵分解的方法,每一个特征分量xi引入辅助向量V。令VVt=W。V是k10352的矩,继续如上的 *** 作
K=10
V=np.random.normal(0,0.01,[10352,K])
wij=np.dot(V,V.T)
wij=np.triu(wij, k=1)
pd.DataFrame(wij)
k=[]
from tqdm import tqdm
for name,data_t in tqdm(data_ij.iterrows(),desc="遍历到第几个用户"):
data_ij2=np.dot(data_t.T,data_t)
ans_ij=np.dot(wij,data_ij2).sum()
k.append(ans_ij)
k
ans_data=np.dot(data_ij,w)+k+w0
pd.DataFrame(ans_data)
loss=(data["rating"][100815:]-ans_data).sum()
loss
之后是根据结果的偏导对参数进行更新,使用的也是使用随机梯度下降的方法,即随机选原矩阵中非空的一个值,通过特定Q,P拟合矩阵的值,减去偏导。更新权重参数,学习率设为U=0.1.偏导公式如下
import random
number=random.randrange(0,len(data),1)
data_t=data.drop(columns=["rating"]).iloc[[number]]
data_t
data_ij2=np.dot(data_t.T,data_t)
ans_ij=np.dot(wij,data_ij2).sum()
ans_ij
ans_data=np.dot(data.drop(columns=["rating"]).iloc[[number]],w)+ans_ij+w0
ans_data
loss=(data["rating"].iloc[[number]]-ans_data).sum()
loss
i=0
j=0
f=0
V[j][f]*data["rating"].iloc[[j]]-V[i][f]*data["rating"].iloc[[j]]**2
U=0.1
w0+=U*1*loss
w+=(data["rating"].iloc[[number]]*U*loss).iloc[0]
tidu_V=0
for i in tqdm(range(len(V))):
for f in range(K):
total=0
for j in range(K):
now=(V[j][f]*data["rating"].iloc[[j]]-V[i][f]*data["rating"].iloc[[j]]**2).iloc[0]
total+=now
V[i][f]=data["rating"].iloc[[j]]*total
w0,w,V
直接使用for循环速度还是太慢了,但已经可以使用了。嘿嘿嘿~~~~
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)