基于SVD思想的简单推荐系统的实现

简介

通过本文,你可以了解

  • Python的一些小tips
  • SVD算法的基本思想以及实现
  • 如何利用SVD来实现一个比较基础的推荐系统

实现环境

  • Pycharm Community Edition
  • Python3
  • numpy
  • pandas
  • sklearn

数据来源

  • MovieLens数据集

参考文献

《Advances in Collaborative Filtering》

环境的配置

pycharm & python3

配置方式参考上一篇BLOG。

numpy & pandas & sklearn

pandas直接用pip安装即可。
因为在windows环境下直接采用pip安装会出现错误,所以本文推荐在此下载网 1下载numpy+scpiy+sklearn的二进制文件到目录下采用

pip install ***.whl

安装即可。

核心思想

关于SVD的纯数学意义

在矩阵M的奇异值分解中

M=UV

  • V的列(columns)组成一套对 M 的正交”输入”或”分析”的基向量。这些向量是 MM 的特征向量。
  • U的列(columns)组成一套对 M 的正交”输出”的基向量。这些向量是 MM 的特征向量。
  • Σ对角线上的元素是奇异值,可视为是在输入与输出间进行的标量的”膨胀控制”。这些是 MM MM 的特征值的非负平方根,并与U和V的行向量相对应。

本算法所采用的思想

在基于SVD算法的推荐系统中,我们可以发现,对于一个打分矩阵M,可以将其分解成P,Q的两个矩阵的乘积,而我们可以分别认为P与Q两个矩阵为用户和电影对每一个因子的影响程度。
了解了以上的思想,我们发现,我们要做的就是根据一个不完整的评分矩阵(通常是如此,因为用户不会对所有的电影打分,因此才需要我们做出推荐)来得出 P Q 两个因子矩阵即可。但是我们发现,根据传统的SVD理论我们很难根据不完整的矩阵得到 P Q 两个矩阵,通常的做法例如置0或者随机赋值经过尝试后发现对于这种稀疏矩阵对结果的影响很大所以放弃,于是我们想到了采用机器学习的思想来解决这个问题。
在论文《Advances in Collaborative Filtering》中,我们找到了一种通过学习来得到 P Q 两个矩阵的方法,我们通过以下这个方程来进行打分

rui=μ+bi+bu+qTipu

其中
* μ 表示所有电影的平均分
* bi 表示用户评分偏离电影平均分的程度
* bu 表示电影评分偏离电影平均分的程度
* pu 表示第u个用户对各种因子的爱好程度
* qTi 表示第i部电影包含每个因子的程度

由此我们发现,问题的关键在于如何获得 P Q 两个矩阵了,在论文中提到了一种方法,即通过不断的学习,让下面这个方程

minb,q,p(u,i)K(ruiμbibuqTipu)2+λ4(b2i+b2u+qi2)+pu2)

取到最小值。同样该方法无法直接实现,于是论文中采用了梯度向下的方法进行学习,通过如下四个公式优化 bi,bu,qi,pu 四个参数

bubu+γ(euiλ4bu)

bibi+γ(euiλ4bi)

qiqi+γ(euipuλ4qi)

pupu+γ(euiqiλ4pu)

来得到 bi,bu,qi,pu 的值从而带到

rui=μ+bi+bu+qTipu

中去,来对每一段电影进行打分,其中当 γ=0.005,λ4=0.02 时我们可以得到较好的结果。
得到了具体的实现方法,我们需要一个合理的架构来实现它。通过多次迭代的方式来梯度下降学习到四个参数的值是一个比较好的思路,因此我们采用RMSE(均方根误差)来评价每次得到的结果如何。
到此我们已经基本阐述了一种简单的基于SVD的推荐系统,在接下来的部分我们主要介绍如何通过python来实现它。

代码实现

读取数据

通过研究数据集我们发现,数据集的结构为

用户ID::电影ID::打分::时间戳

这样的结构,我们采用pandas的read_csv方法将其读取进来。

file = '../DATA/Movie Lens/ratings.dat'
xls_data = pd.read_csv(file, sep='::', header=None)

其中参数sep='::'表示以’::’分割数据,header=None则表示该数据没有头行,即第一行就是数据。

划分训练集和测试集

python的sklearn库提供了非常方便的方法来划分测试集和训练集

train_X, test_X = sklearn.cross_validation.train_test_split(xls_data, test_size=0.2)

其中参数test_size=0.2表示测试集占总的比例为0.2

推荐系统的初始化

我们通过如下的方式来初始化推荐系统

def __init__(self,data,vector_length=20):
    #总共有多少用户,多少电影
    self.user_num=6040
    self.movie_num=3952
    # 数据集
    self.data=np.array(data)
    # 向量的长度
    self.vector_length=vector_length
    # 电影的平均分
    self.movie_average=np.mean(self.data[:,2])

    # 损失函数的四个需要优化的参数 bi,bu,qi,pu
    self.Bi = {}
    self.Bu = {}
    self.Qi = {}
    self.Pu = {}

    # 电影-用户矩阵
    self.movie_user_mat={}
    # 用户-电影矩阵
    self.user_movie_mat={}

    # 遍历数据,初始化数据集
    for i in range(self.data.shape[0]):
        user_id = self.data[i][0]
        movie_id = self.data[i][1]
        rating = self.data[i][2]

        #初始化两个矩阵的每一行并赋值
        self.movie_user_mat.setdefault(movie_id,{})
        self.user_movie_mat.setdefault(user_id,{})
        self.movie_user_mat[movie_id][user_id]=rating
        self.user_movie_mat[user_id][movie_id]=rating

        #通过随机赋值的方式初始化四个参数
        self.Bi.setdefault(movie_id,0)
        self.Bu.setdefault(user_id,0)
        t=np.random.random((self.vector_length,1))/10*(np.sqrt(self.vector_length))
        self.Qi.setdefault(movie_id,t)
        t=np.random.random((self.vector_length,1))/10*(np.sqrt(self.vector_length))
        self.Pu.setdefault(user_id,t)

以上代码的注释比较齐全就不在此赘述,只提一点,向量的长度就是因子数量

如何训练

训练的方法也比较简洁,贴出代码如下所示

def train_model(self,steps=20,alpha=0.15,beta=0.04):
    for step in range(steps):
        sum_Rmse=0
        for i in range(self.data.shape[0]):
        user_id=self.data[i][0]
        movie_id=self.data[i][1]
        rating=self.data[i][2]

        # 计算均方根误差
        temp_Rmse=rating-self.recommend(user_id,movie_id)
        sum_Rmse+=temp_Rmse**2

        # 根据结果采用随机梯度下降进行优化
        self.Bu[user_id]+=beta*(temp_Rmse-alpha*self.Bu[user_id])
        self.Bi[movie_id]+=beta*(temp_Rmse-alpha*self.Bi[movie_id])
        temp=self.Qi[movie_id]
        self.Qi[movie_id]+=beta*(temp_Rmse*self.Pu[user_id]-alpha*self.Qi[movie_id])
        self.Pu[user_id]+=beta*(temp_Rmse*temp-alpha*self.Pu[user_id])
    print('第'+str(step)+'步迭代的RMSE值为'+str(np.sqrt(sum_Rmse/self.data.shape[0])))
    alpha=alpha*0.93

具体的打分函数recommend

self.Bi.setdefault(movie_id,0)
self.Bu.setdefault(user_id,0)
self.Qi.setdefault(movie_id,np.zeros((self.vector_length,1)))
self.Pu.setdefault(user_id,np.zeros((self.vector_length,1)))
if (self.Qi[movie_id]==None):
    self.Qi[movie_id]=np.zeros((self.vector_length,1))
if (self.Pu[user_id]==None):
    self.Pu[user_id]=np.zeros((self.vector_length,1))
score=self.movie_average+self.Bi[movie_id]+self.Bu[user_id]+np.sum(self.Qi[movie_id]*self.Pu[user_id])
if score>5:
    return 5
elif score<1:
    return 1
return score

如何测试

测试的思想比较简单,通过读取测试数据进行打分再与真实分数进行比较并计算RMSE即可

test_data_rmse=0
test_data=np.array(test_data)
result_list=[]
for i in range(test_data.shape[0]):
    my_rating=self.recommend(test_data[i][0],test_data[i][1])
    result_list.append((test_data[i][0],test_data[i][1],my_rating))
    test_data_rmse+=(my_rating-test_data[i][2])**2
print('测试集的RMSE为'+str(np.sqrt(test_data_rmse/test_data.shape[0])))
return result_list

如何推荐

推荐的思路为首先遍历整个数据寻找仍未打分的数据,然后对该用户所有未打分的电影进行打分并排序,将得分最高的五个返回即可。

#寻找已经评分的电影
rated_num=[]
for i in range(self.data.shape[0]):
    i_user_id=self.data[i][0]
    i_movie_id=self.data[i][1]
    if (i_user_id==user_id):
        rated_num.append(i_movie_id)
#对未评分的电影打分
rating_list=[]
for i in range(self.movie_num):
    this_movie_id=i+1
    if (this_movie_id not in rated_num):
        score=self.recommend(user_id,this_movie_id)
        rating_list.append((this_movie_id,score))

# 对得分进行排序
rating_list.sort(key=operator.itemgetter(1),reverse=True)

# 选出得分最高的五部电影
return rating_list[:5]

结语 & 碎碎念

关于SVD

在最初由于只考虑到了SVD的数学意义即可把任意一个矩阵分解成三个矩阵的乘积,结果在完成后发现由于评分矩阵中存在着大量的未打分矩阵所以无法从数学意义上进行分解而需要采用机器学习的方法学习得到两个因子矩阵。而正是为此翻阅论文也是花费了不少时间。

关于python

python的pip是真的坑啊,python的对齐也是真的坑啊!

关于机器学习

机器学习的公式推导全是数学上的计算,感觉自己脆弱的数学根基受到了极大地摧残。

结语

实验终于结束啦,耶!

你可能感兴趣的:(python,数据挖掘,SVD)