推荐系统FM - 超级详细python实战

推荐系统FM - 超级详细python实战,第1张

推荐系统FM - 超级详细python实战
  • 1.FM模型
  • 2.数据集
  • 3.FM求解

这里可以查看我之前的写的MF模型作为学习基础, 推荐系统MF——SVD与SVD++矩阵分解

1.FM模型

FM模型在原本线性模型的基础上,考虑到特征两两之间的关联,对特征进行组合,数据模型上表达特征xi,xj的组合用xixj表示。现在只考虑两阶多项式模型,也就是特征两两组合的问题。公式如下:


首先实现基本的线性模型公式,用随机生成的一个长度为10353-1(除了需要预测的rating) 的符合正态分布的向量来表示各特征的单独权重,有个博主写的关于FM推导非常详细【推荐算法】FM高阶分解模型 - 含特征交叉、POLY2模型

2.数据集

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循环速度还是太慢了,但已经可以使用了。嘿嘿嘿~~~~

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/799624.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存