百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

推荐模型:“推荐系统中的掌上明珠”

toyiye 2024-07-03 02:03 11 浏览 0 评论

推荐模型是推荐系统中最重要的部分,推荐模型的好坏直接决定了最终物品排序的结果,换而言之,推荐模型的好坏也直接影响着推荐效果的优劣。

而且,从某种意义上讲,推荐系统的整体架 构都是围绕着推荐模型搭建的,用于支持推荐模型的上线、训练、评估、服务。

说起推荐模型,我们首先想到的肯定是经典的协同过滤算法。它是最经典的推荐算法。其他模型和它或多或少有关系。因此,掌握协同过滤模型是有必要的。

今天我们来学习一下,经典的协同过滤模型和相关的代码实现。

协同过滤的思想

我们前面已经提过:“用户行为数据是推荐系统最常用,也是最关键的数据。用户的潜在兴趣、用户对物品的评价好坏都反映在用户的行为历史中”

协同过滤算法是一种完全依赖用户和物品之间行为关系的推荐算法。我们从它的名字“协同过滤”中,也可以窥探到它背后的原理,就是 “协同大家的反馈、评价和意见一起对海量的信息进行过滤,从中筛选出用户可能感兴趣的信息”。

具体来说,协同过滤的思路是通过群体的行为来找到某种相似性(用户之间的相似性或者物品之间的相似性),通过该相似性来为用户做决策和推荐。

现实生活中有很多协同过滤的案例及思想体现,我认为人类喜欢追求相亲中的“门当户对”,其实也是一种协同过滤思想的反射,门当户对实际上是建立了相亲男女的一种“相似度”(家庭背景、出身、生活习惯、为人处世、消费观、甚至价值观可能会相似),给自己找一个门当户对的伴侣就是一种“过滤”,当双方”门当户对“时,各方面的习惯及价值观会更相似,未来幸福的概率也会更大。如果整个社会具备这样的传统和风气,以及在真实”案例“中”门当户对“的夫妻确实会更和谐,通过”协同过滤“作用,大家会越来越认同这种方式。我个人也觉得”门当户对“是有一定道理的。

协同过滤的算法原理

我们知道了协同过滤的思想是根据用户之前得喜好以及其他兴趣相近得用户得选择来给用户推荐物品(基于对用户历史行为数据的挖掘发现用户的喜好偏向,并预测用户可能喜好的产品进行推荐),一般仅仅基于用户的行为数据(评价,购买,下载等),而不依赖于物品的任何附加信息(物品自身特征)或者用户的任何附加信息(年龄,性别等)。可以概括为“人以类聚,物以群分”。

协同过滤算法分类

用户协同过滤(UserCF)相似的用户可能喜欢相同物品。如加了好友的两个用户,或者点击行为类似的用户被视为相似用户。如我兄弟和她的太太互加了抖音好友,他们两人各自喜欢的视频,可能会产生互相推荐。

讲过笑话:我有个兄弟,是抖音的点赞狂魔,他的点赞次数高达6924次,而且他大多数的赞都是给那些青春靓丽的小姐姐们,如下图。看他的抖音推荐内容,都是满目的小姐姐唱啊跳啊不亦乐乎,他也觉得甚爽。不过,好景不长,没多久他就跟我说:“我再也不敢再点了,我老婆已经发现我给小姐姐们点了上1000个赞,而且知道我点赞的视频,也会推荐给她”

把好友看过的视频推荐给用户,这就是用户协同过滤。

物品协同过滤(ItemCF)相似的物品可能被同个用户喜欢。这个就是著名的世界杯期间沃尔玛尿布和啤酒的故事了。这里因为世界杯期间,奶爸要喝啤酒看球,又要带娃,啤酒和尿布同时被奶爸所需要,也就是相似商品,可以放在一起销售。

UserCF和ItemCF优缺点的对比


UserCF

ItemCF

性能

适用于用户较少的场合,如果用户很多,计算用户相似度矩阵代价很大

适用于物品数明显小于用户数的场合,如果物品很多(网页),计算物品相似度矩阵代价很大

领域

时效性较强,用户个性化兴趣不太明显的领域

长尾物品丰富,用户个性化需求强烈的领域

实时性

用户有新行为,不一定造成推荐结果的立即变化

用户有新行为,一定会导致推荐结果的实时变化

冷启动

在新用户对很少的物品产生行为后,不能立即对他进行个性化推荐,因为用户相似度表是每隔一段时间离线计算的


新用户只要对一个物品产生行为,就可以给他推荐和该物品相关的其他物品

推荐结果

很难提供令用户信服的推荐解释

利用用户的历史行为给用户做推荐解释,可以令用户比较信服

那怎么计算呢?依据是什么?

  • 一是用户相似度到底该怎么定义?
  • 二是用户评分的预测,即推荐分数该怎么计算呢?

计算用户相似度其实并不是什么难事,因为在共现矩阵中,每个用户对应的行向量其实就可以当作一个用户的 Embedding 向量。相信你早已经熟悉 Embedding 相似度的计算方法,那我们这里依葫芦画瓢就可以知道基于共现矩阵的用户相似度计算方法啦。

最经典的方法就是利用余弦相似度了,它衡量了用户向量 i 和用户向量 j 之间的向量夹角大 小。夹角越小,余弦相似度越大,两个用户越相似,它的定义如下:

除了最常用的余弦相似度之外,相似度的定义还有皮尔逊相关系数、欧式距离等等

用户评分的预测:假设“目标用户与其相似用户的喜好是相似的”,我们可以利用相似用户的已有评价对目标用户的偏好进行预测。最常用的方式是,利用用户相似度和相似用户评价的加权平均值,来获得目标用户的评价预测,公式如下所示。

其中,权重是用户u和用户s的相似度, 是用户s对物品p的评分。

矩阵分解(SVD)使用过程

先看看例子,假设一个平台只有4个用户和4本图书。

1、数据:用户对物品评分1-5分,且有以下评分记录。

2、学习算法SVD:根据线性代数,一个矩阵可以分解为多个矩阵的乘积。在推荐系统领域,可以简单的认为,SVD就是将一个矩阵,在一定的精度损失下,将一个矩阵分解成两个矩阵。运用这个算法,我们可以将上图的矩阵做以下的近似分解:

其中,用户矩阵部分代表着每个用户的偏好在一个二维隐语义空间上的映射。同样地,物品矩阵代表着每本图书的特性在一个二维隐语义空间上的映射。

这两个矩阵也就是模型的结果。这样,我们训练模型的时候,就只需要训练用户矩阵中的8个参数和物品矩阵中的8个参数即可。大大减少了计算量。

模型训练的过程,简单地说,就是通过最小二乘法,不断将用户评分数据迭代入矩阵中计算,直到把均方误差优化到最小。

3、预测决策:通过模型训练,我们得到用户矩阵Q和物品矩阵P后,全部用户对全部图书的评分预测可以通过R = PQ来获得。如上图中,用户A的向量(1.40,-1.18)乘以物品2的向量(2.19,0.73)则可得用户A对物品1的评分预测为:1.40×(-1.18)+2.19×0.73=2.21。

对所有的用户和物品都执行相同操作,可以得到全部用户对全部物品的评分。如下图右侧矩阵:

得到全部的评分预测后,我们就可以对每本图书进行择优推荐。需要注意的是,用户矩阵和物品矩阵的乘积,得到的评分预估值,与用户的实际评分不是全等关系,而是近似相等的关系。如上图中两个矩阵绿色部分,用户实际评分和预估评分都是近似的,有一定的误差。我们给出矩阵分解损失函数的定义如下。

在现在的实际应用中,SVD一般作为协同过滤的离线召回使用。一般地,将需要给用户推荐的物品提前离线计算好,存在HBASE中,在用户有请求的时候,直接读取推荐的结果,放入初排阶段的召回集中

矩阵分解算法的 Pytorch实现

数据集:采用GroupLens提供的MovieLens数据集MovieLens数据集有3个不同版本,本章选用中等大小的数据集该数据集包含6000多名用户对4000多部电影的100万条评分。

数据集格式:

* ratings.dat: UserID::MovieID::Rating::Timestamp  

	- UserID:用户ID范围从1到6040  
	- MovieID:电影ID范围从1到3952  
	- Ratings:评分有1到5的5个等级
	- 每个用户最少有20条评分数据

* users.dat: UserID::Gender::Age::Occupation::Zip-code

	- Gender: "M"代表男,"F"代表女  
	- Age: 年龄从下面的范围中选择
	
		*  1:  "Under 18"
		* 18:  "18-24"
		* 25:  "25-34"
		* 35:  "35-44"
		* 45:  "45-49"
		* 50:  "50-55"
		* 56:  "56+"
		
	- Occupation: 职业包括下面的类别
	
		*  0:  "other" or not specified
		*  1:  "academic/educator"
		*  2:  "artist"
		*  3:  "clerical/admin"
		*  4:  "college/grad student"
		*  5:  "customer service"
		*  6:  "doctor/health care"
		*  7:  "executive/managerial"
		*  8:  "farmer"
		*  9:  "homemaker"
		* 10:  "K-12 student"
		* 11:  "lawyer"
		* 12:  "programmer"
		* 13:  "retired"
		* 14:  "sales/marketing"
		* 15:  "scientist"
		* 16:  "self-employed"
		* 17:  "technician/engineer"
		* 18:  "tradesman/craftsman"
		* 19:  "unemployed"
		* 20:  "writer"

* movies.dat: MovieID::Title::Genres  

	- Genres:电影类别包括以下的类别  
	
		* Action
		* Adventure
		* Animation
		* Children's
		* Comedy
		* Crime
		* Documentary
		* Drama
		* Fantasy
		* Film-Noir
		* Horror
		* Musical
		* Mystery
		* Romance
		* Sci-Fi
		* Thriller
		* War
		* Western

矩阵分解的代码

import torch
import torch.nn as nn
import pandas as pd
import numpy as np

from torch.utils.data import DataLoader, Dataset, TensorDataset
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from utils.loss_plot import semilogy
from utils.k_fold import get_k_fold_data

# 设置基础参数
batch_size = 1024
device = torch.device('cpu')
num_epochs = 50
learning_rate = 0.0006
weight_decay = 0.1


class MfDataset(Dataset):
    def __init__(self, u_id, i_id, rating):
        self.u_id = u_id
        self.i_id = i_id
        self.rating = rating

    def __getitem__(self, index):
        return self.u_id[index], self.i_id[index], self.rating[index]

    def __len__(self):
        return len(self.rating)


# 定义模型
class MF(nn.Module):
    def __init__(self, num_users, num_items, mean, embedding_size=100):
        super(MF, self).__init__()
        self.user_emb = nn.Embedding(num_users, embedding_size)
        self.user_bias = nn.Embedding(num_users, 1)
        self.item_emb = nn.Embedding(num_items, embedding_size)
        self.item_bais = nn.Embedding(num_items, 1)

        self.user_emb.weight.data.uniform_(0, 0.005)  # 0-0.05之间均匀分布
        self.user_bias.weight.data.uniform_(-0.01, 0.01)
        self.item_emb.weight.data.uniform_(0, 0.005)
        self.item_bais.weight.data.uniform_(-0.01, 0.01)

        # 将不可训练的tensor转换成可训练的类型parameter,并绑定到module里,net.parameter()中就有了这个参数
        self.mean = nn.Parameter(torch.FloatTensor([mean]), False)

    def forward(self, u_id, i_id):
        U = self.user_emb(u_id)
        b_u = self.user_bias(u_id).squeeze()
        I = self.item_emb(i_id)
        b_i = self.item_bais(i_id).squeeze()
        return (U * I).sum(1) + b_i + b_u + self.mean


def train(model, X_train, y_train, X_valid, y_valid, loss_func, num_epochs, learning_rate, weight_decay, batch_size):
    train_ls, valid_ls = [], []

    train_dataset = MfDataset(X_train[:, 0], X_train[:, 1], y_train)
    train_iter = DataLoader(train_dataset, batch_size)

    # 使用Adam优化算法
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay)
    model = model.float()
    for epoch in range(num_epochs):
        model.train()  # 如果模型中有Batch Normalization或Dropout层,需要在训练时添加model.train(),使起作用
        total_loss, total_len = 0.0, 0
        for x_u, x_i, y in train_iter:
            x_u, x_i, y = x_u.to(device), x_i.to(device), y.to(device)
            y_pred = model(x_u, x_i)
            l = loss_func(y_pred, y).sum()
            optimizer.zero_grad()
            l.backward()
            optimizer.step()

            total_loss += l.item()
            total_len += len(y)
        train_ls.append(total_loss / total_len)
        if X_valid is not None:
            model.eval()
            with torch.no_grad():
                n = y_valid.shape[0]
                valid_loss = loss_func(model(X_valid[:, 0], X_valid[:, 1]), y_valid)
            valid_ls.append(valid_loss / n)
        print('epoch %d, train mse %f, valid mse %f' % (epoch + 1, train_ls[-1], valid_ls[-1]))
    return train_ls, valid_ls


# 训练,k折交叉验证
def train_k_fold(k, X_train, y_train, num_epochs, learning_rate, weight_decay, batch_size, num_users, num_items,
                 mean_rating):
    train_l_sum, valid_l_sum = 0.0, 0.0
    loss = torch.nn.MSELoss(reduction="sum").to(device)
    for i in range(k):
        model = MF(num_users, num_items, mean_rating).to(device)
        data = get_k_fold_data(k, i, X_train, y_train)
        train_ls, valid_ls = train(model, *data, loss, num_epochs, learning_rate, weight_decay, batch_size)
        train_l_sum += train_ls[-1]
        valid_l_sum += valid_ls[-1]
        if i == k:
            semilogy(range(1, num_epochs + 1), train_ls, "epochs", "mse", range(1, num_epochs + 1), valid_ls,
                     ["train", "valid"])
        print('fold %d, train mse %f, valid mse %f' % (i, train_ls[-1], valid_ls[-1]))
        print("-------------------------------------------")


def main():
    # 加载数据
    data = pd.read_csv('../dataset/u.data', header=None, delimiter='\t')
    X, y = data.iloc[:, :2], data.iloc[:, 2]
    # 转换成tensor
    X = torch.tensor(X.values, dtype=torch.int64)
    y = torch.tensor(y.values, dtype=torch.float32)
    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=2020)

    mean_rating = data.iloc[:, 2].mean()
    num_users, num_items = max(data[0]) + 1, max(data[1]) + 1

    # 交叉验证选择最优超参数
    # train_k_fold(8, X_train, y_train, num_epochs=num_epochs, learning_rate=learning_rate,
    #              weight_decay=weight_decay, batch_size=batch_size, num_users=num_users, num_items=num_items,
    #              mean_rating=mean_rating)

    model = MF(num_users, num_items, mean_rating).to(device)
    loss = torch.nn.MSELoss(reduction="sum")
    train_ls, test_ls = train(model, X_train, y_train, X_test, y_test, loss, num_epochs, learning_rate, weight_decay, batch_size)

    semilogy(range(1, num_epochs + 1), train_ls, "epochs", "mse", range(1, num_epochs + 1), test_ls, ["train", "test"])
    print("\nepochs %d, mean train loss = %f, mse = %f" % (num_epochs, np.mean(train_ls), np.mean(test_ls)))

if __name__ == '__main__':
    main()

相关推荐

钧正平:编造传播这种谣言,荒谬(钧正公司)

来源:钧正平工作室官方微博【钧评编造传播这种谣言,荒谬!】目前,乌克兰安全形势还在迅速变化之中,各方面安全风险上升。相关事件网上热度极高,倍受瞩目。然而,有一些人却借机大肆制造散播一些低级谣言,比如...

幸运角色过去了,谈一谈DNF起源的元素

总的来说伤害比上个版本强太多了,打卢克每日和团本明显能感觉的到。目前打团B套+圣耀稍微打造下应该都能随便二拖了。组队基本上都是秒秒秒(以前得强力辅助,现在随便带个毒奶都行)。单刷除了王座和顶能源阿斯兰...

DNF元素超大凉打桩测试(把括号的伤害加起来好像比较正常)

最近修练场的二觉老是很奇怪,发现以前都是习惯性先减抗然后丢二觉,结果伤害。。。直接丢二觉就正常了下面是其他技能伤害,没达到BUG线,估计问题不大。装备打造方面:全身红字加起来353(41*5+74*2...

ANSYS接触和出图技巧(ansys rough接触)

1.ANSYS后处理时如何按灰度输出云图?1)你可以到utilitymenu-plotctrls-style-colors-windowcolors试试2)直接utilitymenu-plotctr...

ANSYS有限元使用经验总结-后处理(4)

28.求塑性极限荷载时,结构的变形应该较大,建议把大变形打开。...

CFopen21.1、CFopen21.2都来了(cfile open)

[呲牙][赞][加油]

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

取消回复欢迎 发表评论:

请填写验证码