首页 > 编程语言 >面向显式反馈的基于矩阵分解的推荐算法PMF

面向显式反馈的基于矩阵分解的推荐算法PMF

时间:2022-10-21 20:56:02浏览次数:73  
标签:self 矩阵 用户 item PMF user 显式 alpha

论文:Ruslan Salakhutdinov and Andriy Mnih. Probabilistic Matrix Factorization [C]. NeurIPS 2007. https://proceedings.neurips.cc/paper/2007/file/d7322ed717dedf1eb4e6e52a37ea7bcd-Paper.pdf

数据集:ML100K数据集 https://grouplens.org/datasets/movielens/100k/

协同过滤问题定义和数据集描述

问题定义

构造损失函数,然后根据包含用户行为的数据集训练得到用户矩阵(每行向量表示一个用户)和物品矩阵(每行向量表示一个物品),使得损失越小越好,即Ui(用户i特征向量)与Vj(物品j特征向量)的内积与训练集中的Rij(用户i对物品j的评分)越接近越好。

然后通过训练得到的用户矩阵和物品矩阵预测没有发生行为的用户-物品之间的分数,再根据分数向此用户进行物品推荐。

协同就是利用群体的行为来做决策(推荐),过滤,就是从可行的决策(推荐)方案(标的物)中将用户喜欢的方案(标的物)找(过滤)出来。

关键就是如何构建好的损失函数,考虑更多的因素,进而得到更优质的用户表征和物品表征。

数据集描述

本次作业使用Minnesota 大学GroupLens项目组创办的MovieLens100K数据集。

整个数据集包含了:

  • 来自943个用户、1682部电影的10000条评分,评分区间为 [1,2,3,4,5]。

  • 每个用户至少都评分了20部电影。

实际使用到的是MovieLens100K数据集下的u1.base和u1.test,前者作为训练集,后者作为测试集。数据格式如下:

  • userId :每个用户的Id,1~943

  • movieId:每部电影的Id,1~1682

  • rating:用户评分。

  • timestamp:时间戳,用户评分时的时间。

以下是使用pandas得到的数据集的描述:

数据集描述

基于矩阵分解的协同过滤推荐方法和实验

符号说明

符号 含义
\(n\) 用户数量
\(m\) 物品数量
\(u\in\left\{1,2,...,n\right\}\) 用户ID
\(i,i'\in\left\{1,2,...,m\right\}\) 物品ID
\(G,e.g.,G=\left\{1,2,3,4,5\right\}\) 评分集(评分范围)评分
\(R\in\left\{G\bigcup{?}\right\}^{n*m}\) 评分矩阵
\(y_{ui}\in\left\{0,1\right\}\) 1表示用户u有对物品i进行评分,反之没有
\(\Theta=\left\{(u,i,r_{ui})\right\}\) 观察到的评分记录
\(p=\sum_{u,i}y_{ui}=|\Theta|\) 观察到的评分数量
\(p/n/m\) 密度(稀疏度)
\(\mu\in{R}\) 全局评分均值
\(b_u\in{R}\) 用户偏差
\(b_i\in{R}\) 物品偏差
\(d\in{R}\) 向量维度
\(U_u\in{R^{1*d}}\) 用户特征向量
\(U\in{R^{n*d}}\) 用户特征矩阵
\(V_i\in{R^{1*d}}\) 物品特征向量
\(V\in{R^{n*d}}\) 物品特征矩阵
\(\alpha_u,\alpha_v\) 权衡参数

方法形式化

PMF的优化函数:

\(min\sum_{u=1}^n\sum_{i=1}^{m}y_{ui}[\frac{1}{2}(r_{ui}-U_{u\cdot}V_{i\cdot}^T)^2+\frac{\alpha_u}{2}||U_{u\cdot}||^2+\frac{\alpha_v}{2}||V_{v\cdot}||^2]\)

使用SGD方法使得损失函数最小化,对于每条记录(用户id、物品id、评分)都计算该记录产生的损失:

\(f_{ui}=\frac{1}{2}(r_{ui}-U_{u\cdot}V_{i\cdot}^T)^2+\frac{\alpha_u}{2}||U_{u\cdot}||^2+\frac{\alpha_v}{2}||V_{v\cdot}||^2\)

然后计算该记录用户特征向量和物品特征向量的梯度:

\(\nabla{U_{u\cdot}}=\frac{\partial f_{ui}}{\partial U_{u\cdot}}=-(r_{ui}-U_{u\cdot}V_{i\cdot}^T)V_{i\cdot}+\alpha_uU_{u\cdot}\)

\(\nabla{V_{i\cdot}}=\frac{\partial f_{ui}}{\partial V_{i\cdot}}=-(r_{ui}-U_{u\cdot}V_{i\cdot}^T)U_{u\cdot}+\alpha_vV_{i\cdot}\)

再然后对此用户的特征向量和此物品的特征向量进行更新:

\(U_{u\cdot}=U_{u\cdot}-\lambda\nabla{U_{u\cdot}}\)

\(V_{i\cdot}=V_{i\cdot}-\lambda\nabla{V_{i\cdot}}\)

迭代完一次之后,降低学习率:

\(\lambda=\lambda{*0.9}\)

算法伪代码

1:初始化模型参数(用户矩阵,物品矩阵,学习率,用户正则化权重,物品正则化权重)

2:for t = 1,...,T do

3: for j = 1,...,p do

4: 训练集中取recordj(u,i,rui)数据

5: 计算用户矩阵和物品矩阵的梯度

6: 更新用户矩阵和物品矩阵的参数

7: end for

8: 降低学习率 \(\lambda=\lambda{*0.9}\)

9:end for

核心代码

# PMF 模型
class PMF:
    # PMF 模型初始化,已经设置默认参数
    def __init__(self, user_set, item_set, record_list, dimensions=20, learning_rate=0.01, alpha_user=0.1, alpha_item=0.1):
        # 创建PMF时,表示用户id的set集合。调用vector_initialize函数后,表示用户的特征矩阵 {用户id:用户特征向量,...}
        self.users = user_set
        # 同上
        self.items = item_set
        # 训练集中的记录列表
        self.records = record_list
        # 用户和物品的特征维度,默认为20
        self.dimensions = dimensions
        # 学习率,默认为0.01
        self.learning_rate = learning_rate
        # 用户正则化的超参数,默认为0.1
        self.alpha_user = alpha_user
        # 物品正则化的超参数,默认为0.1
        self.alpha_item = alpha_item
        # 训练过程中的损失
        self.loss = 0

    # 初始化用户特征和物品特征
    def vector_initialize(self):
        # 用户和物品的特征使用字典来保存,Key是ID,Value是相应的特征向量
        users_dict = {}
        items_dict = {}
        # 用户特征初始化
        for user in self.users:
            # 生成维度20,服从0~1的均匀分布的向量
            user_vector = np.random.rand(self.dimensions)
            # 保存此用户的特征向量
            users_dict[user] = (user_vector - 0.5) * 0.01
        # 物品特征初始化
        for item in self.items:
            item_vector = np.random.rand(self.dimensions)
            items_dict[item] = (item_vector - 0.5) * 0.01
        # 更新模型的两个属性
        self.users = users_dict
        self.items = items_dict

    # 使用随机梯度下降方法训练用户和物品的特征
    def train(self, epochs):
        # 迭代次数
        for epoch in range(epochs):
            # 每次迭代开始,将模型的属性loss置0
            self.loss = 0
            # 遍历评分记录
            for record in self.records:
                # 该记录的用户特征向量
                user = self.users[record[0]]
                # 该记录的物品特征向量
                item = self.items[record[1]]
                # 该记录的用户对物品的评分
                rating = int(record[2])
                # 计算损失
                error = self.loss_function(user, item, rating)
                # 损失累加
                self.loss += error
                # 计算该用户特征向量的梯度
                grad_user = -(rating - np.dot(user, item)) * item + self.alpha_user * user
                # 计算该物品特征向量的梯度
                grad_item = -(rating - np.dot(user, item)) * user + self.alpha_item * item
                # 根据梯度对特征向量进行更新
                self.users[record[0]] -= self.learning_rate * grad_user
                self.items[record[1]] -= self.learning_rate * grad_item
            # 每迭代完一次,学习率降低
            self.learning_rate = self.learning_rate * 0.9
            # 打印每次迭代的损失
            print("epoch: ", epoch, "loss:", self.loss)

        # 训练完之后,将用户特征向量进行保存
        with codecs.open("pureResult/user_vector", "w") as f1:
            for u in self.users.keys():
                f1.write(str(u) + "\t")
                f1.write(str(list(self.users[u])))
                f1.write("\n")
        # 将物品特征向量进行保存
        with codecs.open("pureResult/item_vector", "w") as f2:
            for i in self.items.keys():
                f2.write(str(i) + "\t")
                f2.write(str(list(self.items[i])))
                f2.write("\n")

    # 损失函数定义
    def loss_function(self, user, item, rating):
        return 0.5 * math.pow((rating - np.dot(user, item)), 2) + \
               0.5 * self.alpha_user * math.pow(np.linalg.norm(user, ord=2), 2) + \
               0.5 * self.alpha_item * math.pow(np.linalg.norm(item, ord=2), 2)

实验结果和分析(含不同活跃程度用户上的结果)

在测试集 u1.test 进行模型测试,测试集中包含20000条记录,用户和电影都未完全包含训练集中出现的用户和电影。

实验结果如下:

平均误差

参考误差:

参考误差

不同活跃度的用户推荐效果:

不同活跃度的用户推荐效果

分析:

  1. PMF在均方根误差上均好于User-based CF、Item-based CF、Pure SVD和RSVD。
  2. PMF在平均绝对误差上优于Item-based CF、Pure SVD,略差于User-based CF和RSVD。
  3. 以测试集中用户的评分数量作为此用户的活跃度,评分越多,表示越活跃。可以看出活跃度低的用户,无论是均方根误差还是平均绝对误差,有很多大于1,甚至有大于4。而当活跃度高于30后,误差均小于1。符合常识:活跃度越高的用户,用于预测的数量越多,对此用户推荐的效果也越好,反之相反。

基于深度学习的协同过滤推荐方法和实验

模型示意图

模型示意图

核心代码

# 使用pytorch构造PMF模型
class PMF(torch.nn.Module):
    def __init__(self, alpha_user=0.1, alpha_item=0.1):
        super().__init__()
        # 用户正则化的超参数,默认为0.1
        self.alpha_user = alpha_user
        # 物品正则化的超参数,默认为0.1
        self.alpha_item = alpha_item

    # 前向传播函数,定义了损失函数
    # users 表示用户特征矩阵,items 表示物品特征矩阵,matrix 表示评分矩阵
    def forward(self, users, items, matrix):
        # 用用户特征矩阵和物品特征矩阵进行矩阵相乘得到评分预测矩阵
        predict_rating = torch.mm(users, items.t())
        # 得到评分矩阵有数据的标志矩阵,有评分的位置标志为1,反之为0,用于求损失函数时只对有评分的记录进行损失求和
        not_zero = (matrix != -1).type(torch.FloatTensor)
        # 评分与预测之间的误差平方求和的二分之一
        error = torch.sum(torch.div(torch.pow((matrix - predict_rating), 2), 2) * not_zero)
        # 用户正则化
        users_regularization = self.alpha_user * torch.sum(torch.pow(users.norm(2, dim=1), 2)) / 2
        # 物品正则化
        items_regularization = self.alpha_item * torch.sum(torch.pow(items.norm(2, dim=1), 2)) / 2
        return error + users_regularization + items_regularization


# 模型训练,model:PMF模型,users:用户特征矩阵,items:物品特征矩阵,ratings:评分矩阵,epochs:迭代次数
def train(model, users, items, ratings, epochs):
    for epoch in range(epochs):
        # 模型优化器梯度置零
        model.optimizer.zero_grad()
        # 执行model的前向传播函数,计算一次迭代的总loss
        loss = PMF_model(users, items, ratings)
        # 计算用户特征矩阵和物品特征矩阵的梯度
        loss.backward()
        # 梯度更新
        model.optimizer.step()
        # 打印此次迭代的loss
        print("epoch: ", epoch, "loss:", loss)
    # 迭代训练完成之后,保存用户特征矩阵和物品特征矩阵,用于测试
    torch.save(users, "pytorchResult/users.pt")
    torch.save(items, "pytorchResult/items.pt")


# 用户特征矩阵和物品特征矩阵的初始化,rating_matrix 表示评分矩阵
def vector_initialize(rating_matrix):
    # 评分矩阵的行数表示用户数量
    users_number = rating_matrix.shape[0]
    # 评分矩阵的列数表示物品数量
    items_number = rating_matrix.shape[1]
    # 服从均匀分布随机初始化一个用户数*20的矩阵,每行表示相应的用户特征向量
    user_vector = torch.rand(users_number, 20, requires_grad=True)
    # 对矩阵每个元素-0.5
    user_vector.data.add_(-0.5)
    # 对矩阵每个元素*0.01
    user_vector.data.mul_(0.01)
    # 服从均匀分布随机初始化一个物品数*20的矩阵,每行表示相应的物品特征向量
    item_vector = torch.rand(items_number, 20, requires_grad=True)
    item_vector.data.add_(-0.5)
    item_vector.data.mul_(0.01)
    return user_vector, item_vector

实验结果和分析(含与非深度学习方法的比较)

pytorch误差

训练过程

分析:

  1. 使用框架实现的PMF不论是均方根误差还是均绝对误差均不如非框架实现的PMF推荐效果好。
  2. 如果优化器使用SGD,会出现上图的现象,损失虽然一开始会变小,但是从第五次迭代开始就成爆炸式增长。而改用Adam优化器就则会顺利的得到正确的实验结果。

标签:self,矩阵,用户,item,PMF,user,显式,alpha
From: https://www.cnblogs.com/fireonfire/p/16814731.html

相关文章

  • Normal Matrix(法向量变换矩阵)
    我们都知道gl的坐标系统。它的工作是将坐标从一个坐标系转到另一个坐标系。其中我们用到了几个转换矩阵。其中最为重要的是模型(Model)、视图(View)、投影(Projection)三个矩阵。......
  • 广义矩阵乘法中二元运算符的条件
    一般地,如果矩阵中的加法和乘法满足一个半环,那么矩阵乘法满足交换律。一个半群由集合\(A\)和两个定义在\(A\)上的二元运算\(\oplus\)和\(\otimes\)构成,其中:\((A......
  • 矩阵连乘最小相乘次数的思想
    矩阵的乘法矩阵的概念来自线性代数矩阵乘法:只有当左边矩阵的列数等于右边矩阵的行数时,它们才可以相乘。结果为前一个矩阵的行元素×后一个矩阵的列元素  矩阵相......
  • 【数据结构-矩阵】矩阵的相关公式推导
    目录1数组1.1一维数组1.2二维数组2对称矩阵2.1上三角部分(i≤j)2.2下三角部分(i≥j)3三角矩阵3.1上三角矩阵(i≤j的元素不全为0)3.2下三角矩阵(i≥j的元素不......
  • 填空题:矩阵
    下列给定程序中,函数fun的功能是:有N×N矩阵,以主对角线为对称线,对称元素相加并将结果存放在左下三角元素中,右上三角元素置为0。例如,若N=3,有下列矩阵:1234567......
  • 矩阵与线性方程组
    高斯消元当我们用线性方程组来理解矩阵时,我们有矩阵的高斯消元。高斯消元本质上是一系列的对矩阵的“变换”或者说“操作”,这种操作总共有三种:1)给某一整行乘上非零常数\(......
  • Sam's Numbers 矩阵快速幂优化dp
    ​​https://www.hackerrank.com/contests/hourrank-21/challenges/sams-numbers​​设dp[s][i]表示产生的总和是s的时候,结尾符是i的所有合法方案数。那么dp[s][i]可以由dp[......
  • 796. 子矩阵的和
    输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。输入格式......
  • 矩阵还原——————数据结构作业
    /*给定一个一维数组,将其转化为对称矩阵(关于主对角线对称)*/#include<stdio.h>#include<string.h>constintMAXN=1e3;intmat[MAXN][MAXN];inta[MAXN];in......
  • FZU 1061 矩阵连乘
     Problem1061矩阵连乘Accept:445    Submit:1699TimeLimit:1000mSec    MemoryLimit:32768KB ProblemDescription给定n个矩阵{A1,A2,...,An},考察这n......