任务5 SVD分解推荐

任务5 SVD分解的推荐应用

SVD原理可以参考其他博客, 我觉得svd实际上是利用矩阵分解来获取得到用户和物品的embeddding. 然后利用这些embedding来计算相似度进行推荐(这里用的是余弦相似度)

1. 代码实现

1. 相关类的定义

class CFModel(nn.Module):
    '''
    类描述:
        用pytorch写的一个协同过滤函数,基于矩阵分解来实现的
    成员变量: 
        users:
            int类型
            用户数目
        movies:
            int类型
            电影数目
        features:
            int类型
            embedding的维度, 默认是100维度
        device:
            string类型
            用来训练的设备,可以是cpu也可以是GPU
    方法: 
    
    __init__: 初始化函数
    forward: 前向传播
    '''
    def __init__(self, users,movies, features=100, device='cpu'):
        '''
        函数说明:
            初始化函数
        输出:
        users:
            int类型
            用户数目
        movies:
            int类型
            电影数目
        features:
            int类型
            embedding的维度, 默认是100维度
        device:
            string类型
            用来训练的设备,可以是cpu也可以是GPU
        '''
        super(CFModel, self).__init__()
        self.device = device
        self.NUM_USER = users
        self.NUM_MOVIE = movies
        self.features = features
        self.params = nn.ParameterDict(
            {
                'X': nn.Parameter(nn.init.normal_(torch.empty(self.NUM_USER, self.features), std=0.35), requires_grad=True),
                'Y': nn.Parameter(nn.init.normal_(torch.empty(self.NUM_MOVIE, self.features), std=0.35), requires_grad=True),
            }
        )

    def forward(self):
        '''
        函数描述:
            就是简单的前向传播,在协同过滤里面,就是把用户和物品的embedding做个矩阵乘法
        返回值:
        tensor类型,维度为(用户数, 电影数)
            用户对物品的打分矩阵
        '''
        return self.params['X'].mm(self.params['Y'].T)
class CollaborativeFiltering(object):
    """协同过滤类, 包含数据集, 训练方法等

    member:
            NUM_USER (int): 用户的数目
            NUM_MOVIE(int): 电影的数目
            rating(tensor): 打分矩阵
            model(CFModel): 协同过滤对象
            test_case(DataFrame): 测试集
            lambda(float): 惩罚项系数
            lr(float)    : 学习率
            device       : 设备, 是用GPU还是用CPU来训练
    
    method:
        __init__: 初始化模型
        loss_obj: 损失函数
        RMSE    : 评价指标,RMSE
        train   : 训练函数
        save_model: 保存当前类当中的self.model的参数
        load_model: 读取当前对象当中的self.model的参数
        set_lambda: 设置惩罚项的大小
        load_data: 读取数据集,并利用这些数据集来初始化一些参数


    """
    def __init__(self, data_path,load_exist_model=False, name='ml-100k', features=100, Lambda = 0.2, lr=1e-3):
        """_summary_: 函数初始化

        Args:
            data_path (stirng): 数据集的路径
            load_exist_model (bool, optional): 是否读取已经存在的模型. Defaults to False.
            name (str, optional): 数据集的名称. Defaults to 'ml-100k'.
            features (int, optional): embedding的维数. Defaults to 50.
            Lambda (float, optional): 惩罚项的系数. Defaults to 0.2.
            lr (float, optional): 学习率. Defaults to 1e-3.
        """
        self.NUM_USER   = None
        self.NUM_MOVIE  = None
        self.rating     = None
        self.model      = None
        self.test_case  = None

        self.feature    = features
        self.Lambda     = Lambda
        self.lr         = lr

        self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

        if load_exist_model:
            self.load_entire_model()
            return

        self.load_data(datapath=data_path, name=name)
        self.init_model()

        
    def init_model(self):
        '''
        函数说明:
            初始化当前对象当中的CFModule,并且将模型移植到对应的device上面
        '''
        self.model = CFModel(self.NUM_USER, self.NUM_MOVIE, self.feature,self.device)
        self.model.to(self.device)

    def Loss_obj(self, pred):
        '''
        函数说明:
            计算损失函数:loss, 将每一个在rating矩阵当中大于0的位置的值,用真实值减去预测值来计算均方误差, 并且加上惩罚项

        输入:
            tensor
            一个预测的矩阵, 这个矩阵的形状必须与rating矩阵的形状是一样的
        
        输出:
            float
            对应的损失值
        
        '''
        index = (self.rating > 0).float()
        loss = ((pred * index - self.rating)**2).sum()
        # X_embedding = torch.norm(self.model.params['X'], dim=1)**2
        # y_embedding = torch.norm(self.model.params['Y'], dim=1)**2
        # return loss + self.Lambda * (X_embedding.sum() + y_embedding.sum())
        return loss

    def RMSE(self, ypred):
        '''
        函数说明:
            计算测试集和预测当中的RMSE
        
        输入:
            ypred
            输入的是预测的打分矩阵

        输出:
            float
            测试集和真实数据的损失值
        '''
        predict = ypred.cpu().detach().numpy()
        loss = 0.0
        for i in self.test_case.values:
            uid = i[0] - 1
            item = i[1] - 1
            target = i[2]
            loss += (predict[uid][item] - target)**2
        loss /= float(len(self.test_case))
        return np.sqrt(loss)


    def train(self, epoch):
        """_summary_
        模型训练函数

        Args:
            epoch (int): 训练的epoch次数

        Returns:
            history(list): 训练过程中产生的损失值
            metircs(list): 评估指标,每10个epoch记录一次
        """
        optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)
        history = []
        metrics = []
        with tqdm(total=epoch) as t1:
            for i in range(epoch):
                optimizer.zero_grad()
                pred= self.model.forward()
                loss = self.Loss_obj(pred)
                loss.backward()
                history.append(loss.item())
                optimizer.step()
                if (i + 1) % 10 == 0:
                    metric = self.RMSE(pred)
                    metrics.append(metric)
                t1.update(1)

        return history, metrics

    def save_model(self):
        """_summary_: 保存当前对象当中的self.model的模型参数
        """
        torch.save(self.model.state_dict(), './model/params/UserCF_params.pkl')

    def load_model(self):
        """_summary_: 读取模型参数给self.model
        """
        self.model.load_state_dict(torch.load('./model/params/UserCF_params.pkl'))
    
    def set_lambda(self, _lambda):
        """_summary_: 设置当前模型的惩罚项系数lambda

        Args:
            _lambda (float): 需要设置的惩罚项系数
        """
        self.Lambda = _lambda

    def load_data(self, datapath, name='ml-1m'):
        """_summary_: 读取数据集,并且划分训练集和测试集,最后利用数据集设置模型的一些参数
        Args:
            datapath (string): 数据集所在的路径
            name (str, optional): 数据集的名称,可选的只有两种ml-100k, ml-1m. Defaults to 'ml-1m'.
        """
        all_ratings = utils.loadData(filepath=datapath, name=name)

        self.NUM_USER = all_ratings['userid'].max()
        self.NUM_MOVIE = all_ratings['movieid'].max()

        self.rating, self.test_case = utils.Split_Dataset_P(all_ratings, test_size=0.2)
        self.rating = utils.rating2matrix(self.rating)
        self.rating = torch.tensor(self.rating).to(self.device)

    def save_entire_model(self, path='./model/params/', is_savemodel=True):
        """_summary_: 保存当前对象,首先先保存打分矩阵,然后保存测试集,最后保存模型的一些基本参数,最后可以选择的保存self.model的模型参数

        Args:
            path (str, optional): 保存模型的路径. Defaults to './model/params/'.
            is_savemodel (bool, optional):是否保存self.model . Defaults to True.
        """
        torch.save(self.rating, path+'rating.pt')
        self.test_case.to_csv(path +'test_case.csv', index=False)
        temp_dir = {k:v for k, v in self.__dict__.items()  if k not in ['rating', 'test_case', 'model']}
        torch.save(temp_dir, path + 'CollaborativeFiltering.pt')
        if is_savemodel:
            self.save_model()
        print('保存模型完成')
        

    def load_entire_model(self, path='./model/params/', is_loadmodel=True):
        """_summary_: 读取整个模型,先读取rating,在读取测试集,然后读取模型的一些基本参数
        最后可以选择是否读取self.model的模型参数

        Args:
            path (str, optional): 模型参数的存储路径. Defaults to './model/params/'.
            is_loadmodel (bool, optional): 是否读取self.model当中的模型参数. Defaults to True.
        """
        self.rating = torch.load(path+'rating.pt')
        self.test_case = pd.read_csv(path + 'test_case.csv')
        temp_dir = torch.load(path + 'CollaborativeFiltering.pt')
        for k, v in temp_dir.items():
            self.__dict__[k] = v
        if is_loadmodel:
            self.init_model()
            self.load_model()

1.2 开始训练

读取数据

model1 = CollaborativeFiltering('./data/', load_exist_model=False)

开始读取数据
用户人数为: 943
电影数目为: 1682
开始划分数据集
100%|██████████| 943/943 [00:07<00:00, 127.71it/s]
开始将数据转换成矩阵
用户人数为: 943
电影数目为: 1682
100%|██████████| 79619/79619 [00:06<00:00, 12641.68it/s]

训练500个epoch

history, metric = model1.train(500)

100%|██████████| 500/500 [00:10<00:00, 49.72it/s]

看看损失函数的变化曲线

import matplotlib.pyplot as plt
plt.plot(range(len(history)), history)

在这里插入图片描述
看看模型的RMSE

plt.plot(range(len(metric)), metric)

在这里插入图片描述
大概估计的分数也许是相差1.5分左右, 另外感觉训练并没有收敛到最小值点. 不过关系不大, 主要还是实现算法思路.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值