大数据——协同过滤推荐算法:线性回归算法

news/2024/5/19 23:41:14 标签: 算法, 大数据, 推荐算法

推荐系统中的协同过滤算法一般分为两大类:

  • 基于行为的协同过滤算法(Memory-Based CF),利用用户行为数据计算相似度,包括用户之间的相似度和物品之间的相似度。
  • 基于模型的协同过滤算法(Model-Based CF),利用机器学习算法预测用户的喜好程度,一般用户数据较为稀疏的时候更适合这种方法。

本文主要介绍Model-Based协同过滤算法

1、Model-Based CF基于模型协同过滤算法

利用用户-物品评分矩阵训练机器学习模型,从而预测用户对物品的评分,主要可以分为以下几类:

2、基于回归模型算法的协同过滤

回归模型的前提是连续的值,我们将评分看做连续的值,采用以下Baseline(基准预测)实现策略。其思想是运用每个人的偏好不同:

有些用户比较好心,他的评分高于其他用户,有些用户比较苛刻,他的评分低于其他用户;而部分物品比较受欢迎,它的评分高于一般物品,部分物品可能会被嫌弃,它的评分会低于一般物品。

而Baseline则是通过找出每个用户与其他用户的评分偏置值 b u b_u bu,每个物品与其他物品的偏置值 b i b_i bi,最终的目标也就变成了寻找最优的 b u b_u bu b i b_i bi。所以Baseline算法的步骤如下:

  1. 计算所有电影的平均评分 u u u;
  2. 计算每个用户的评分与平均评分的偏置值 b u b_u bu;
  3. 计算每部电影的评分与平均评分的偏置值 b i b_i bi
  4. 预测用户对电影的评分:
    r ^ u i = b u i = u + b u + b i \hat{r}_{ui} = b_{ui} = u+b_u+b_i r^ui=bui=u+bu+bi

以用户A对《封神第一部》的评分为例:

  1. 首先计算所有电影的平均评分 u = 3.5 u=3.5 u=3.5
  2. 用户A比较好心,普遍比平均分高1分,偏置值 b u = 1 b_u=1 bu=1
  3. 《封神第一部》一开始差评比较多,评分比平均分低0.5分,偏置值 b i = − 0.5 b_i=-0.5 bi=0.5
  4. 则用户A对《封神第一部》的评分为:3.5+1-0.5=4.1分。

在线性回归问题中,我们用平方差构建损失函数:
C o s t = ∑ u , i ∈ R ( r u i − r ^ u i ) 2 = ∑ u , i ∈ R ( r u i − u − b u − b i ) 2 Cost = \sum_{u,i∈R}(r_{ui}-\hat{r}_{ui})^2 = \sum_{u,i∈R}(r_{ui}-u-b_u-b_i)^2 Cost=u,iR(ruir^ui)2=u,iR(ruiububi)2
为了防止过拟合,需要加入L2范式,最后的公示如下:
C o s t = ∑ u , i ∈ R ( r u i − u − b u − b i ) 2 + λ ( ∑ u b u 2 + ∑ i b i 2 ) Cost = \sum_{u,i∈R}(r_{ui}-u-b_u-b_i)^2 + \lambda(\sum_u{b_u}^2+\sum_i{b_i}^2) Cost=u,iR(ruiububi)2+λ(ubu2+ibi2)
我们希望得到损失函数的最小值,一般会采用随机梯度下降法或者最小二乘法来优化实现。

2.1 Baseline随机梯度下降法算法

step1:梯度下降法推导:
J ( θ ) = f ( b u , b i ) J(θ) = f(b_u,b_i) J(θ)=f(bu,bi)
梯度下降参数更新的原始公式:
θ j : = θ j − α ∂ ∂ θ j J ( θ ) \theta_j :=\theta_j-\alpha\frac{∂}{∂\theta_j}J(\theta) θj:=θjαθjJ(θ)
对参数求偏导:
∂ ∂ b u J ( θ ) = ∂ ∂ b u f ( b u , b i ) = − 2 ∑ u , i ∈ R ( r u i − u − b u − b i ) + 2 λ b u \frac{∂}{∂b_u}J(\theta) = \frac{∂}{∂b_u}f(b_u,b_i) = -2\sum_{u,i∈R}(r_{ui}-u-b_u-b_i) + 2\lambda b_u buJ(θ)=buf(bu,bi)=2u,iR(ruiububi)+2λbu
代入梯度下降参数更新公式:
b u : = b u + α ( ∑ u , i ∈ R ( r u i − u − b u − b i ) − λ b u ) b_u:=b_u+\alpha(\sum_{u,i∈R}(r_{ui}-u-b_u-b_i) -\lambda b_u) bu:=bu+α(u,iR(ruiububi)λbu)
b i : = b i + α ( ∑ u , i ∈ R ( r u i − u − b u − b i ) − λ b i ) b_i:=b_i+\alpha(\sum_{u,i∈R}(r_{ui}-u-b_u-b_i) -\lambda b_i) bi:=bi+α(u,iR(ruiububi)λbi)
step2:随机梯度下降
随机梯度下降法本质上是用每个样本的损失来更新参数,不用每次求出全部的损失和。
单样本损失值:
e r r o r = r u i − r ^ u i = r u i − u − b u − b i error = r_{ui} - \hat{r}_{ui} = r_{ui} - u-b_u-b_i error=ruir^ui=ruiububi
所以梯度下降公式可以更新为:
b u : = b u + α ( e r r o r − λ b u ) b_u:=b_u+\alpha(error -\lambda b_u) bu:=bu+α(errorλbu)
b i : = b i + α ( e r r o r − λ b i ) b_i:=b_i+\alpha(error -\lambda b_i) bi:=bi+α(errorλbi)
step3算法实现
导入模块和数据

# 随机梯度下降算法实现
import pandas as pd
import numpy as np
df = pd.read_csv("ml-latest-small/ratings.csv", usecols=range(3))
df

Baseline梯度下降算法实现

class BaselineCFBySGD(object):
    '''max_epochs 梯度下降迭代次数
        alpha 学习率
        reg 过拟合参数
        columns 数据字段名称'''
    def __init__(self,max_epochs, alpha,reg,columns=['uid','mid','rating']):
        self.max_epochs = max_epochs
        self.alpha = alpha
        self.reg = reg
        self.columns = columns
        
    def fit(self,data):
        '''
        :param data:uid,mid,rating
        :return:'''
        self.data = data
        # 用户评分数据
        self.users_rating = data.groupby(self.columns[0]).agg([list])[[self.columns[1], self.columns[2]]]
        # 电影评分数据
        self.items_rating = data.groupby(self.columns[1]).agg([list])[[self.columns[0], self.columns[2]]]
        # 全局平均分
        self.global_mean = self.data[self.columns[2]].mean()
        # 调用随机梯度下降训练模型参数
        self.bu,self.bi = self.sgd()
        
    def sgd(self):
        '''
        随机梯度下降,优化bu和bi值
        :return: bu bi'''
        bu = dict(zip(users_rating.index, np.zeros(len(users_rating))))
        bi = dict(zip(items_rating.index, np.zeros(len(items_rating))))
        
        for i in range(max_epochs):
            # 将dataframe的每一行数据单独读出来,代入梯度下降参数公式
            for uid, mid, real_rating in df.itertuples(index=False):
                error = real_rating - (global_mean+bu[uid]+bi[mid])
                bu[uid] += alpha*(error - reg*bu[uid])
                bi[mid] += alpha*(error - reg*bi[mid])
        return bu,bi
    
    def predict(self,uid,mid):
        '''
        使用评分公式进行预测
        param uid,mid;
        return predict_rating;'''
        predict_rating = self.global_mean+self.bu[uid]+self.bi[mid]
        return predict_rating
    
    def test(self,testset):
        '''
        使用预测函数预测测试集数据
        param testset;
        return yield;'''
        for uid,mid,real_rating in testset.itertuples(index=False):
            try:
                # 使用predict函数进行预测
                pred_rating = self.predict(uid,mid)
            except Exception as e:
                print(e)
            else:
                # 返回生成器对象
                yield uid,mid,real_rating,pred_rating

测试集和训练集划分函数

# 训练集和测试集的划分
def data_split(data_path, x=0.8, random=False):
    ratings = pd.read_csv(data_path, usecols=range(3))
    testset_index = []
    
    for uid in ratings.groupby('userId').any().index:
        user_rating_data = ratings.where(ratings['userId']==uid).dropna()
        if random:
            index = list(user_rating_data.index)
            np.random.shuffle(index)
            _index = round(len(user_rating_data)*x)
            testset_index += list(index[_index:])
        else:
            index = round(len(user_rating_data)*x)
            testset_index += list(user_rating_data.index.values[index:])
            
    testset = ratings.loc[testset_index]
    trainset = ratings.drop(testset_index)
    return trainset,testset

算法评估函数

def accuray(predict_reselts, method='all'):
    # 计算均方根误差
    def rmse(predict_reselts):
        length = 0
        _rmse_sum = 0
        for uid,mid, real_rating, pred_rating in predict_reselts.itertuples(index=False):
            length+=1
            _rmse_sum += (pred_rating - real_rating)**2
        return round(np.sqrt(_rmse_sum/length),4)
    
    # 计算绝对值误差
    def mae(predict_reselts):
        length=0
        _mae_sum=0
        for uid,mid,real_rating,pred_rating in predict_reselts.itertuples(index=False):
            length +=1
            _mae_sum += abs(pred_rating-real_rating)
        return round(_mae_sum/length,4)
    
    # 两个都计算
    def rmse_mae(predict_reselts):
        length = 0
        _rmse_sum=0
        _mae_sum=0
        for uid,mid,real_rating,pred_rating in predict_reselts.itertuples(index=False):
            length +=1
            _mae_sum += abs(pred_rating-real_rating)
            _rmse_sum += (pred_rating - real_rating)**2
        return round(np.sqrt(_rmse_sum/length),4),round(_mae_sum/length,4)
    
    # 根据输入的参数放回对应的评估方法
    if method.lower() =='rmse':
        return rmse(predict_reselts)
    elif method.lower() == 'mae':
        return mae(predict_reselts)
    else:
        return rmse_mae(predict_reselts)

将数据代入算法和评估函数中

trainset, testset = data_split('ml-latest-small/ratings.csv',random=True)
bcf = BaselineCFBySGD(20,0.1,0.1,['userId','movieId','rating'])
bcf.fit(trainset)
pred_test = bcf.test(testset)
# 生成器对象用list进行转化,然后转化为dataframe格式
df_pred = pd.DataFrame(list(pred_test), columns=[['userId','movieId','rating','pred_rating']])

rmse, mae = accuray(df_pred,'all')
print('rmse:',rmse,';mae:',mae)

rmse: 0.8647 ;mae: 0.6595

2.2 Baseline交替最小二乘法算法

step1:交替最小二乘法推导
核心思想:对损失函数求偏导,然后让偏导为0。
损失函数如下:
J ( θ ) = f ( b u , b i ) J(θ) = f(b_u,b_i) J(θ)=f(bu,bi)
对参数求偏导:
∂ ∂ b u J ( θ ) = ∂ ∂ b u f ( b u , b i ) = − 2 ∑ u , i ∈ R ( r u i − u − b u − b i ) + 2 λ b u \frac{∂}{∂b_u}J(\theta) = \frac{∂}{∂b_u}f(b_u,b_i) = -2\sum_{u,i∈R}(r_{ui}-u-b_u-b_i) + 2\lambda b_u buJ(θ)=buf(bu,bi)=2u,iR(ruiububi)+2λbu
偏导为0,则可得:
∑ u , i ∈ R ( r u i − u − b u − b i ) = 2 λ b u \sum_{u,i∈R}(r_{ui}-u-b_u-b_i) = 2\lambda b_u u,iR(ruiububi)=2λbu
∑ u , i ∈ R ( r u i − u − b i ) = ∑ u ∈ R b u + λ b u \sum_{u,i∈R}(r_{ui}-u-b_i) = \sum_{u∈R}b_u+\lambda b_u u,iR(ruiubi)=uRbu+λbu
为了方便计算,令 ∑ u ∈ R b u ≈ ∣ R ( u ) ∣ ∗ b u \sum_{u∈R}b_u≈|R(u)|*b_u uRbuR(u)bu,则可得:
b u : = ∑ u , i ∈ R ( r u i − u − b i ) λ 1 + ∣ R ( u ) ∣ b_u:=\frac{\sum_{u,i∈R}(r_{ui}-u-b_i)}{\lambda_1+|R(u)|} bu:=λ1+R(u)u,iR(ruiubi)

∣ R ( u ) ∣ |R(u)| R(u)表示用户u有评分的数量

同理可得:
b i : = ∑ u , i ∈ R ( r u i − u − b u ) λ 2 + ∣ R ( i ) ∣ b_i:=\frac{\sum_{u,i∈R}(r_{ui}-u-b_u)}{\lambda_2+|R(i)|} bi:=λ2+R(i)u,iR(ruiubu)
step2:交替最小二乘法(ALS)
我们推导了各自的表达式,但表达式互相包含对方,因此我们用交替最小二乘法进行计算:

  1. 先固定其中一项值,求另一个值;
  2. 然后固定另一项值,求第一项的值;如此反复更新二者的值,最后求得结果

要求 b u b_u bu时,先将 b i b_i bi看做已知;求 b i b_i bi时,先将 b u b_u bu看做已知

step3算法实现
总体代码跟随机梯度下降差不多

# 最小二乘法算法实现
class BaselineCFByALS(object):
    '''max_epochs 梯度下降迭代次数
        alpha 学习率
        reg 过拟合参数
        columns 数据字段名称'''
    def __init__(self,max_epochs,reg_bu,reg_bi,columns=['userId','movieId','rating']):
        self.max_epochs = max_epochs
        self.reg_bu = reg_bu
        self.reg_bi = reg_bi
        self.columns = columns
        
    def fit(self,data):
        '''
        :param data:uid,mid,rating
        :return:'''
        self.data = data
        # 用户评分数据
        self.users_rating = data.groupby(self.columns[0]).agg([list])[[self.columns[1], self.columns[2]]]
        # 电影评分数据
        self.items_rating = data.groupby(self.columns[1]).agg([list])[[self.columns[0], self.columns[2]]]
        # 全局平均分
        self.global_mean = self.data[self.columns[2]].mean()
        # 调用随机梯度下降训练模型参数
        self.bu,self.bi = self.als()
        
    def als(self):
        '''
        最小二乘法,优化bu和bi值
        :return: bu bi'''
        bu = dict(zip(users_rating.index, np.zeros(len(users_rating))))
        bi = dict(zip(items_rating.index, np.zeros(len(items_rating))))
        
        for i in range(max_epochs):
            # 计算bi
            for mid, uids, real_ratings in items_rating.itertuples(index=True):
                _sum=0
                for uid,rating in zip(uids,real_ratings):
                    _sum += rating - global_mean-bu[uid]
                bi[mid] = _sum/(self.reg_bi+len(uids))
                    
                # 计算bu
            for uid,mids,real_ratings in users_rating.itertuples(index=True):
                _sum=0
                for mid,rating in zip(mids,real_ratings):
                    _sum+= rating -self.global_mean-bi[mid]
                bu[uid] = _sum/(self.reg_bu+len(mids))
        return bu,bi
    
    def predict(self,uid,mid):
        '''
        使用评分公式进行预测
        param uid,mid;
        return predict_rating;'''
        predict_rating = self.global_mean+self.bu[uid]+self.bi[mid]
        return predict_rating
    
    def test(self,testset):
        '''
        使用预测函数预测测试集数据
        param testset;
        return yield;'''
        for uid,mid,real_rating in testset.itertuples(index=False):
            try:
                # 使用predict函数进行预测
                pred_rating = self.predict(uid,mid)
            except Exception as e:
                print(e)
            else:
                # 返回生成器对象
                yield uid,mid,real_rating,pred_rating

应用最小二乘法算法

trainset, testset = data_split('ml-latest-small/ratings.csv',random=True)
bcf = BaselineCFByALS(20,25,15,['userId','movieId','rating'])
bcf.fit(trainset)
pred_test = bcf.test(testset)
# 生成器对象用list进行转化,然后转化为dataframe格式
df_pred_als = pd.DataFrame(list(pred_test), columns=[['userId','movieId','rating','pred_rating']])
rmse, mae = accuray(df_pred_als,'all')
print('rmse:',rmse,';mae:',mae)

rmse: 0.8403 ;mae: 0.6462


http://www.niftyadmin.cn/n/4930292.html

相关文章

Debian10:安装PHPVirtualBox

PHPVirtualBox 是一个用 PHP 编写,用于管理 VirtualBox 的 Web 前端(由AJAX实现)。 参考文章:VirtualBoxPHPVirtualBox部署_骡子先生的博客-CSDN博客php virualbox,浏览器远程控制VBox 虚拟机phpVirtualBox_weixin_39815879的博客…

关于达梦网络通信异常问题

一.问题说明 springboot的项目,多数据源,其中一个数据源是达梦数据库。有个根据主键id查询详情的接口,一直报错网络通信异常,或连接尚未建立或者已经关闭。可以确保访问数据库的网络一切正常,单单一张表的接口一直报上…

Linux用户管理命令

一、系统存储用户信息的文件 (1)/etc/passwd 存储用户基本信息: 通过vi /etc/passwd查看用户基本信息: (2)/etc/group 存储用户组的信息: 通过vi /etc/group查看用户组信息: &…

C语言——自定义类型详解[结构体][枚举][联合体]

自定义类型详解 前言:一、结构体1.1结构体的声明1.2结构体内存对齐1.3位段(位域) 二、枚举2.1枚举类型的定义2.2枚举类型的优点2.3枚举的使用 三、联合体3.1联合体类型的定义3.2联合体的特点3.3联合体大小的计算 前言: 我打算把结…

试用智能编程助手

1 简介 网传有了大模型之后,很多人都要失业了,其中也包括一部分程序员,确实大模型可以减轻开发者的工作量,但是具体到减轻了多少工作量,哪种类型的工作,学习成本,使用成本如何?不捧…

【调整奇数偶数顺序】

调整奇数偶数顺序 1.题目 输入一个整数数组,实现一个函数, 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分, 所有偶数位于数组的后半部分。 2.题目分析 这道题首先用到的方法是冒泡排序的思想,首先通过冒泡排序…

C++友元函数和友元类的使用

1.友元介绍 在C中,友元(friend)是一种机制,允许某个类或函数访问其他类的私有成员。通过友元,可以授予其他类或函数对该类的私有成员的访问权限。友元关系在一些特定的情况下很有用,例如在类之间共享数据或…

CTF之流量分析之密码文件

题目地址:BUUCTF在线评测 题目: 深夜里,Hack偷偷的潜入了某公司的内网,趁着深夜偷走了公司的秘密文件,公司的网络管理员通过通过监控工具成功的截取Hack入侵时数据流量,但是却无法分析出Hack到底偷走了什…