推荐系统入门实践系列

推荐系统入门实践(一)

《动手深度学习》第二版,推荐系统章节的个人理解以及pytorch代码实现。

李沐大神的《动手深度学习》第二版已经在更新了,目前只有英文版。并且推荐系统章节只有mxnet实现,这是本人看完之后的理解以及自己写的pytorch代码实现。代码和mxnet版本大部分相同,有兴趣的可以看一看。



前言

对于推荐系统入门而言,本文不会过多的对协同过滤、召回、排序等概念进行强调。更多的是从推荐二字直观的概念上进行讲解,比较适合入门者学习。


一、什么是推荐?

如果以商品为例,推荐简单来说就是从海量商品中选出用户可能喜欢的少量商品并呈现给用户。这一过程是一个双向共赢的过程,对用户可以节约时间,对企业可以增加商品购买率。那么如何进行推荐系统的设计?
如果你对词嵌入/词向量的概念有所了解的话可能会有一个不错的想法。我们可以将每一个用户用一个 k k k维向量 u ∈ R k u\in\mathbb R^k uRk表示,每一个商品用一个 k k k维向量 i ∈ R k i\in\mathbb R^k iRk表示。然后对 k k k i i i做内积,如果结果比较大那么可以将该商品推荐给用户,否则就不推荐。事实上,这种思想就是矩阵分解,也是我们首先要说的推荐算法思想。

下面考虑如何找出这些用户向量和商品向量。

二、协同过滤和矩阵分解

假设用户的数量为 m m m, 商品的数量为 n n n。那么我们需要找出的就是用户矩阵 P ∈ R m × k P\in\mathbb R^{m \times k} PRm×k和商品矩阵 Q ∈ R n × k Q\in\mathbb R^{n \times k} QRn×k P P P矩阵的第 i i i u i u^i ui代表第 i i i个用户的向量。 Q Q Q矩阵的含义同理。下面讲述具体方法。

1.协同过滤

在讲解协同过滤的概念之前我们首先要有一个假设:每一个用户都对一小部分商品做出过分数评价,每一件商品都被一小部分用户评价过。

根据假设我们可以得到一个以用户为行以商品为列的共现矩阵 M a t r i x ∈ R m × n Matrix\in\mathbb R^{m \times n} MatrixRm×n,如果用户 u u u评价过商品 i i i就将 M a t r i x [ u ] [ i ] Matrix [u][i] Matrix[u][i]设为具体的评分值,否则置为0(含义是未知)。很明显该 m × n m \times n m×n矩阵只有一小部分非零元素。而协同过滤的思想就是用这一小部分的非零元素推出 M a t r i x Matrix Matrix矩阵的未知元素。这是一个协同大家的反馈、评价和意见对海量信息进行过滤的过程,因此被称为协同过滤。

2.矩阵分解

了解了协同过滤的概念之后让我们回到最初的问题。我们的目的是找出代表用户们喜好的用户矩阵 P ∈ R m × k P\in\mathbb R^{m \times k} PRm×k和商品矩阵 Q ∈ R n × k Q\in\mathbb R^{n \times k} QRn×k。共现矩阵就是让我们找到这两个矩阵的工具。看下图:矩阵分解模型图
对此图片通俗的解释就是:如果用户 u u u对商品 i i i做出了评分 s c o r e score score,那么我们就调整 U U U矩阵中的 u u u向量和 I I I中的 i i i向量让它们的内积值尽可能接近 s c o r e score score。因此,在我们最初的假设成立的情况下,理论上可以根据有限的评分值调整出合理的用户矩阵和商品矩阵。随后,根据得到的 P P P Q Q Q就可以再计算出 M a t r i c Matric Matric的所有值。

事实上,这一过程并不适合叫做矩阵分解,因为它更像是一个反向合成的过程。通过不断调整用户向量 u u u和商品向量 i i i使得它们的内积逼近已知的具体评分。不过,因为得到 P P P Q Q Q还可以采用奇异值分解的方法,所以这种方法才被称为矩阵分解。

3.代码部分

为了实现代码,我们还需要具体的损失函数公式等信息。这一部分会一边实现一边讲解。

(1)数据集

我们使用的是 MovieLens数据集,该数据集有四列,含义分别是用户编号、电影编号、用户评价分数、时间戳。数据集中共有943名用户、1682部电影以及5个不同评级,经过数据清理后保证每名用户评价了至少二十部电影,最终数据共有100000行。

在开始前,你还需要一份《动手深度学习》第二版官方的工具包(第一版的不行),该工具包是本书前面章节写过的代码的集合。我把链接放在下面。
链接: 百度网盘.
提取码:c6wd

此外,还有一点需要注意,该工具包包含许多Ipython的内容,因此确保你使用的是anaconda以及Juptyer notebook

首先导入包

from d2l import torch as d2l
import torch
import numpy as np
from torch import nn
import os
import pandas as pd

然后调用工具包中的下载程序,返回的是data(100000行四列),用户数量(943),电影数量(1682),此外,data中的数据此时已经按用户评价时间排序。

d2l.DATA_HUB['ml-100k'] = (
    'http://files.grouplens.org/datasets/movielens/ml-100k.zip',
    'cd4dcac4241c8a4ad7badc7ca635da8a69dddb83')

def read_data_ml100k():
    data_dir = d2l.download_extract('ml-100k')
    names = ['user_id', 'item_id', 'rating', 'timestamp']
    data = pd.read_csv(os.path.join(data_dir, 'u.data'), '\t', names=names,
                       engine='python')
    num_users = data.user_id.unique().shape[0]
    num_items = data.item_id.unique().shape[0]
    return data, num_users, num_items

分割测试集和训练集,事实上我们接下来使用的都是’seq-aware‘模式。我们将用户最新的评价作为测试集,用过去预测现在。

def split_data_ml100k(data, num_users, num_items,
                      split_mode='random', test_ratio=0.1):
    if split_mode == 'seq-aware':
        train_items, test_items, train_list = {}, {}, []
        for line in data.itertuples():
            u, i, rating, time = line[1], line[2], line[3], line[4]
            train_items.setdefault(u, []).append((u, i, rating, time))
            if u not in test_items or test_items[u][-1] < time:
                test_items[u] = (i, rating, time)
        for u in range(1, num_users + 1):
            train_list.extend(sorted(train_items[u], key=lambda k: k[3]))
        test_data = [(key, *value) for key, value in test_items.items()]
        train_data = [item for item in train_list if item not in test_data]
        train_data = pd.DataFrame(train_data)
        test_data = pd.DataFrame(test_data)
    else:
        mask = [True if x == 1 else False for x in np.random.uniform(
            0, 1, (len(data))) < 1 - test_ratio]
        neg_mask = [not x for x in mask]
        train_data, test_data = data[mask], data[neg_mask]
    return train_data, test_data

加载出协同过滤中的共现矩阵,我们暂时用的是’explicit‘模式。

def load_data_ml100k(data, num_users, num_items, feedback='explicit'):
    users, items, scores = [], [], []
    inter = torch.zeros((num_items, num_users)) if feedback == 'explicit' else {}
    for line in data.itertuples():
        user_index, item_index = int(line[1] - 1), int(line[2] - 1)
        score = int(line[3]) if feedback == 'explicit' else 1
        users.append(user_index)
        items.append(item_index)
        scores.append(score)
        if feedback == 'implicit':
            inter.setdefault(user_index, []).append(item_index)
        else:
            inter[item_index, user_index] = score
    return users, items, scores, inter

最后,分割出用于训练和测试的迭代数据集。

def split_and_load_ml100k(split_mode='seq-aware', feedback='explicit',
                          test_ratio=0.1, batch_size=256):
    data, num_users, num_items = read_data_ml100k()
    train_data, test_data = split_data_ml100k(
        data, num_users, num_items, split_mode, test_ratio)
    train_u, train_i, train_r, _ = load_data_ml100k(
        train_data, num_users, num_items, feedback)
    test_u, test_i, test_r, _ = load_data_ml100k(
        test_data, num_users, num_items, feedback)

    train_set = torch.utils.data.TensorDataset(torch.tensor(np.array(train_u)), torch.tensor(np.array(train_i)), torch.tensor(np.array(train_r)))
    test_set = torch.utils.data.TensorDataset(torch.tensor(np.array(test_u)), torch.tensor(np.array(test_i)), torch.tensor(np.array(test_r)))
    train_iter = torch.utils.data.DataLoader(train_set, shuffle=True, batch_size=batch_size)
    test_iter = torch.utils.data.DataLoader(test_set, batch_size=batch_size)
    return  num_users, num_items, train_iter, test_iter

最终我们的数据集会处理成类似三元组的形式(u, i, score),表示用户 u u u对电影 i i i做出了 s c o r e score score的评价。此时,我们已经把离散的共现矩阵拆成了具体的值的形式,但是后面还会用到矩阵形式。

(2)模型部分

具体的模型很简单,只需要调用nn模块的Embedding层,最后计算内积即可。

class MF(nn.Module):
    def __init__(self, num_factors, num_users, num_items):
        super().__init__()
        self.P = nn.Embedding(num_embeddings=num_users, embedding_dim=num_factors)
        self.Q = nn.Embedding(num_embeddings=num_items, embedding_dim=num_factors)
        self.user_bias = nn.Embedding(num_embeddings=num_users, embedding_dim=1)
        self.item_bias = nn.Embedding(num_embeddings=num_items, embedding_dim=1)

    def forward(self, user_id, item_id):
        user_id = user_id.long()
        item_id = item_id.long()
        P_u = self.P(user_id)
        Q_i = self.Q(item_id)
        b_u = self.user_bias(user_id)
        b_i = self.item_bias(item_id)
        outputs = (P_u * Q_i).sum(dim=-1) + torch.squeeze(b_u) + torch.squeeze(b_i)
        return outputs.flatten()

损失函数部分,首先注意模型部分我们还设计了self.user_bias和self.item_bias部分,这两块和普通的bias作用类似分别代表用户偏差和商品偏差。比如,善良的用户可能会给出至少三分的评价,其余用户会给出很低的评分,这种偏差应该由user_bias记录。
根据模型我们知道,我们预测的用户评分公式如下:

R ^ u i = p u q i ⊤ + b u + b i \hat{\mathbf{R}}_{ui} = \mathbf{p}_u\mathbf{q}^\top_i + b_u + b_i R^ui=puqi+bu+bi

损失函数采用均方根误差函数RMSE以及L2正则化,我们计算实际的 s c o r e score score值和预测的值之间的误差。公式如下:

a r g m i n P , Q , b ∑ ( u , i ) ∈ K ∥ R u i − R ^ u i ∥ 2 + λ ( ∥ P ∥ F 2 + ∥ Q ∥ F 2 + b u 2 + b i 2 ) \underset{\mathbf{P}, \mathbf{Q}, b}{\mathrm{argmin}} \sum_{(u, i) \in \mathcal{K}} \| \mathbf{R}_{ui} - \hat{\mathbf{R}}_{ui} \|^2 + \lambda (\| \mathbf{P} \|^2_F + \| \mathbf{Q} \|^2_F + b_u^2 + b_i^2 ) P,Q,bargmin(u,i)KRuiR^ui2+λ(PF2+QF2+bu2+bi2)

由于,pytorch没有默认的RMSE函数,所以我们自己实现。正则化只需要最后在迭代器添加参数即可。

class RMSE(nn.Module):
    def forward(self, labels, pred):
        outputs = torch.sqrt(torch.mean((labels - pred) ** 2))
        return outputs

评估函数同样采用RMSE即可。

def evaluator(net, test_iter, device):
    rmse = RMSE()
    rmse_list = []
    for idx, (users, items, ratings) in enumerate(test_iter):
        users = users.to(device)
        items = items.to(device)
        ratings = ratings.to(device)
        r_hat = [net(u, i) for u, i in zip(users, items)]
        rmse_list.append(rmse(ratings, torch.tensor(r_hat, device=device)))
    return torch.mean(torch.tensor(rmse_list))

最后实现训练函数

def train_recsys_rating(net, train_iter, test_iter, loss, optimizer, num_epochs, devices=torch.device('cuda'), evaluator=None, **kwargs):
    timer = d2l.Timer()
    net = net.to(devices)
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 2],
                            legend=['train loss', 'test RMSE'])

    for epoch in range(num_epochs):
        metric, train_l, i = d2l.Accumulator(3), 0, 0
        for users, items, scores in train_iter:
            i += 1
            users = users.to(devices)
            items = items.to(devices)
            scores = scores.to(devices)
            pred = net(users, items)
            optimizer.zero_grad()
            l = loss(scores.float(), pred)
            train_l += l.cpu().item()

            l.backward()
            optimizer.step()
            metric.add(l, 1, users.shape[0])
            timer.stop()
        if len(kwargs) > 0:
            test_rmse = evaluator(net, test_iter, kwargs['inter_mat'],
                                  devices)
        else:
            test_rmse = evaluator(net, test_iter, devices)

        animator.add(epoch + 1, (train_l / i, test_rmse))
    print(f'train loss {metric[0] / metric[1]:.3f}, '
          f'test RMSE {test_rmse:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
          f'on {str(devices)}')

设置运行参数就可以跑了,没有GPU的要设置成CPU。

devices = torch.device('cuda')
num_users, num_items, train_iter, test_iter = split_and_load_ml100k(
    test_ratio=0.1, batch_size=512)
net = MF(30, num_users, num_items)
for parm in net.parameters():
    nn.init.normal_(parm, std=0.01)
lr, num_epochs, wd = 0.002, 20, 1e-5
optimizer = torch.optim.Adam(net.parameters(), lr=0.002, weight_decay=wd)
loss = torch.nn.MSELoss()
train_recsys_rating(net, train_iter, test_iter, loss, optimizer, num_epochs,
                    devices, evaluator)

在这里插入图片描述

总结

注意把加载数据的函数复制到工具包中以便后面继续使用,其他的就不用了。

第二部分链接: AutoRec模型.
第三部分:神经网络模型NeuMF传送门
注:转载请注明出处

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于Arduino的嵌入式系统入门实践是一门学习如何使用Arduino来构建嵌入式系统的课程。Arduino是一款开源的硬件平台,简单易用,是学习嵌入式系统开发的理想选择。 这门课程将首先介绍Arduino平台的基本知识,包括Arduino的硬件组成和基本功能,如数字输入输出、模拟输入输出、通信接口等。学习者将学会如何搭建Arduino开发环境,并了解如何使用Arduino的集成开发环境(IDE)进行编程。 接下来,课程将教授如何使用Arduino编写简单的程序来控制外部硬件设备,如LED灯、蜂鸣器、温度传感器等。学习者将了解如何通过编写代码来读取和控制外部设备,从而实现自己设计的嵌入式系统。 此外,课程还将介绍Arduino与其他传感器和执行器的配合使用,如声音传感器、光线传感器、舵机等。学习者将学会如何在嵌入式系统中添加更多的功能和交互性。 在实践部分,学习者将进行一系列的实验和项目,例如制作智能家居控制系统、温湿度监控系统等。通过这些实践项目,学习者将巩固所学知识,并提升自己的实际操作能力。 通过学习基于Arduino的嵌入式系统入门实践,学习者将获得嵌入式系统开发的基础知识和技能。这门课程不仅适合对嵌入式系统感兴趣的学生,也适合刚入门的开发者和爱好者。无论是从事相关行业的工程师,还是对嵌入式系统有兴趣的非专业人士,都能够通过学习此课程快速上手,并在嵌入式系统的开发和应用中取得成功。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值