推荐系统中的隐语意分析方法:SVD 和深度学习的比较,全是白话,没有公式

前言

现在绝大多数的推荐系统,采用的都是双塔模型,本质上是求解用户向量和item向量,在推荐的过程中进行相似度匹配。

如果你的系统保存有大量的用户信息和item信息,可以使用一些比较成熟的算法来进行特征的抽取,如YoutubeDNN,阿里的SDM那样。

当信息特别少的时候,或者说只有一个协同矩阵,则需要使用矩阵分解来求解两个稠密特征矩阵,即用户矩阵和物品矩阵。

这在数学上很好理解,用户矩阵和物品矩阵的乘积,则是用户对物品的偏好程度。

SVD 奇异值分解

SVD是矩阵分解常用的方式,原理我们这里忽略,只需要知道它能够将矩阵分解为 其他三个矩阵的乘积。

我们的目标是将一个用户偏好矩阵(稀疏矩阵),分解为用户矩阵和物品矩阵的乘积,并且通过后两个矩阵的乘积运算,将稀疏矩阵中为0的位置,填充上用户的偏好程度。那么我们就知道了每个用户对每个物品的偏好程度了。

所以我们需要使用SVD的升级版本:

  • FunkSVD :一种基于机器学习的"伪"SVD,可直接求解两个矩阵。通过拟合让分解得到的矩阵的部分元素不断接近原矩阵的有效元素,最终使得原矩阵的无效元素(0元素)也能被拟合预测出来。这种SVD最早被Simon Funk在博客中发布,因此又叫做Funk-SVD。
  • BiasSVD:FunkSVD的升级版,在其基础上加入了bias偏置
  • SVD++ :BiasSVD算法基础上进行了改进,加入了隐式因素,如浏览时长、点击情况等.

代码实现

数据预处理

这里我们使用数据集steam-200k,一个steam游戏平台,玩家游戏时长的数据集。

首先是数据清理,我们这里用游戏时长当做喜好程度,去掉购买信息。并且将游戏时长转换为喜好评分,因为头部游戏时长非常长,不利于评分计算。

python 复制代码
data = pd.read_csv("steam-200k.csv",header=None,names=['userid','gametitle','behavior','value','other'])
data = data[data.behavior.isin(['play'])]
value = []
for i in data['value'].values:
    if i <=1 :
        value.append(1)
    elif i>72:
        value.append(7)
    else:
        if i/7 > 1:
            value.append(i/7)
        else:
            value.append(1)

看一下数据分区情况,大部分是垃圾游戏,头部游戏少。

然后我们需要去掉 游戏和玩家过少的记录,因为记录太少会导致无法收敛。

python 复制代码
user_value = {}
item_value = {}
for i in data[['userid','gametitle','value']].values:
    if i[0] in user_value:
        user_value[i[0]] += i[2]
    else:
        user_value[i[0]] = i[2]
    if i[1] in item_value:
        item_value[i[1]] += i[2]
    else:
        item_value[i[1]] = i[2]
user_valid = []
item_valid = []
for k,v in user_value.items():
    if v >= MIN_USER_LIMIT:  # 一个用户最少需要几条记录
        user_valid.append(k)
for k,v in item_value.items():
    if v >= MIN_ITEM_LIMIT:  # 一个item最少需要几条记录
        item_valid.append(k)
        
data = data[data.userid.isin(user_valid)]
data = data[data.gametitle.isin(item_valid)]

data[['userid','gametitle','value',]].to_csv("./steam_play_ready.csv",index=False,header=False)

用surprise进行SVD求解

准备好数据我们就可以进行SVD分解了,这里我们使用surprise,他是scikit的一个推荐相关的库。

python 复制代码
import numpy as np
import pandas as pd
from surprise import SVD,KNNBasic,Dataset,accuracy,Reader
from surprise.model_selection import KFold,train_test_split,cross_validate
import matplotlib.pyplot as plt

rawdata = pd.read_csv("./steam_play_ready.csv",header=None,names=['uid','iid','value'])
print("加载数据集 大小:",rawdata.shape)
print(rawdata.head(5))
users = rawdata.uid.unique()
items = rawdata.iid.unique()
print("用户数量:",len(users)," 游戏数量:",len(items))

# rating_scale: 评分范围
reader = Reader(rating_scale=(1,10))
data = Dataset.load_from_df(rawdata,reader)

# 默认 biased=True 也就是我们这里用的biasSVD
algo = SVD(n_factors = 100,n_epochs = 20)

# 交叉验证
cross_validate(algo,data,measures=["RMSE", "MAE"],cv=5, verbose=True)

除了cross_validate还可以自己手动拟合,并打印损失

python 复制代码
algo.fit(trainset)
accuracy.rmse(algo.test(testset)) 

我们直接指定一个用户,查看预测值和实际值的比对效果

python 复制代码
preds = []
PRED_USER_ID = '94088853'
items = []
reality = []
for i in rawdata.values:
    if str(i[0]) == PRED_USER_ID:
        items.append(i[1])
        reality.append(i[2])
for i in items:
    pred = algo.predict(PRED_USER_ID,i)
    preds.append(pred.est)

result = pd.DataFrame({
    "items":items,
    "reality": reality,
    "pred":preds,
})
result = result.sort_values("pred",ascending=False)
print("user--> ",PRED_USER_ID)
print(result.head(20))

这是我的运行结果,直接看起来效果一般。

pytorch embedding 分解矩阵

我这里演示了最简单的两个embedding乘积的方法分解。

  • embedding 可以理解为一个m*n的矩阵,表示为共有m个事物(m个人 或者 m个物品),每个事务存在n个特征。
  • 我这里假设每个用户有10个特征,每个游戏有10个特征。
python 复制代码
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

stream_data = pd.read_csv("./steam_play_ready.csv",header=None,names=['uid','iid','rating'])

users = stream_data.uid.unique()
items = stream_data.iid.unique()

class StreamPreferenceNN(torch.nn.Module):
    def __init__(self, users,items) -> None:
        super().__init__()
        self.user_ebd = torch.nn.Embedding(len(users),10)
        self.item_ebd = torch.nn.Embedding(len(items),10)
        # todo 可以加入一些更复杂的网络
        self.user_map,self.item_map = {},{}
        for i,uid in enumerate(users):
            self.user_map[uid] = i
        for i,iid in enumerate(items):
            self.item_map[iid] = i
    
    def predict(self,uid,iid):
        user_feature = self.user_ebd(torch.tensor(self.user_map[uid]))
        item_feature = self.item_ebd(torch.tensor(self.item_map[iid]))
        return torch.mul(user_feature,item_feature).sum()
    
    def forward(self,x):
        ids = [[self.user_map[i[0]],self.item_map[i[1]]] for i in x]
        ids = torch.tensor(ids)
        user_features = self.user_ebd(ids[:,0])
        item_features = self.item_ebd(ids[:,1])
        return torch.mul(user_features,item_features).sum(dim=-1)

model =StreamPreferenceNN(users,items)
criterion = torch.nn.MSELoss()
optimize = torch.optim.Adam(model.parameters(),lr=0.1)

# 开始求解
loss_record = []
train_y = stream_data.rating.array
y = torch.tensor(train_y,dtype=torch.float32)
for i in range(0,100):
    pred = model(stream_data.values)
    loss = criterion(pred,y)
    loss_record.append(loss.item())
    loss.backward()
    optimize.step()
    optimize.zero_grad()

# 绘制收敛过程
plt.plot([i for i in range(0,len(loss_record))],loss_record)
plt.show()

# 这里只是简单求了一下前n个的评分,也可以和实际进行对比,因为收敛的还可以,我这里懒得写了。
userid = 11373749
user_item = []
for i in items:
    pred = model.predict(userid,i)
    user_item.append([i,pred])
result = pd.DataFrame(user_item,columns=['item','rating'])
result = result.sort_values("rating",ascending=False)
print(result.head(10))

看一下收敛的效果:

再看一下推荐的top:

尾语

在实际的推荐系统中,我们采用了更复杂的方法进行特征抽取,例如将用户特征,群体特征,历史行为 进行全连接,注意力机制,跨层的LSTM等网络 进行复杂变换。

如果你有兴趣,可以在上面的代码里加个全连接层(相当于bias)尝试一下。

其实,经典的推荐算法也好,基于深度学习的算法也好,都是求物品相似度。这在大多数的场景下是够用的。

但时代在进步,随着chatgpt的成功,在很多场景下,基于大模型推荐,正在颠覆传统推荐方法。

AI的时代迎面而来。

相关推荐
XYY36918 分钟前
搜索与图论 树的广度优先遍历 图中点的层次
算法·图论·宽度优先
淳于韻珊1 小时前
Java语言的散点图
开发语言·后端·golang
mmmayang3 小时前
Golang 项目平滑重启
开发语言·后端·golang
褚翾澜3 小时前
Go语言的可选链
开发语言·后端·golang
Fantasydg3 小时前
DAY 38 leetcode 15--哈希表.三数之和
算法·leetcode·散列表
编程绿豆侠3 小时前
力扣HOT100之链表:19. 删除链表的倒数第 N 个结点
算法·leetcode·链表
ゞ 正在缓冲99%…3 小时前
leetcode274.H指数
java·算法·leetcode
胖哥真不错4 小时前
数据分享:汽车测评数据
python·机器学习·数据分享·汽车测评数据·car evaluation
小诸葛的博客5 小时前
client-go如何监听自定义资源
开发语言·后端·golang
入 梦皆星河5 小时前
go原理刨析之channel
开发语言·后端·golang