推荐系统之矩阵分解模型

2020-04-24 11:05:19 浏览数 (1)

前言

最近在整理Embedding技术在推荐系统中的应用,总结了获取各类item2vec的方法,推荐系统中的矩阵分解作为解决item2vec问题初期技术方法之一,虽已在推荐领域摸爬滚打了十几年,但至今仍旧在工业界的推荐场景中扮演着重要的角色,本文就对推荐系统中的矩阵分解进行简单的介绍,为后续几篇介绍推荐系统中的Embedding技术做铺垫。

一个简单推荐系统场景

该文是公众号里面写的第一篇推荐系统相关的技术文章,这里对工业界的推荐场景进行一个简单的介绍,方便非推荐领域的童鞋粗略了解一下业务背景。

推荐,即在某个业务场景中,根据用户(User)在已有行为的商品(item)信息基础上,预测其在没有行为的item上的偏好概率,然后根据这个偏好概率的大小,由高到低的推荐给该用户。

简单来说,推荐系统就是度量User与Item之间的相似度,然后取Top K个相似的Item推荐给User。

推荐业务中可能拥有的业务数据:

  • User端信息

年龄、学历、性别、地址…[用户画像]

  • Item端信息

价格、所属分类、所属商铺...

  • User-Item交互信息

浏览、点击、购买、收藏、评分(评论)、点赞….

当然,在推荐业务的初期,可能并没有这么多业务数据积累。

假设,我们有以下这样一份用户商品交互数据:

大量用户对商品的评分(1~5分)信息,即User-Item的评分矩阵,如下图所示:

那么问题来了,如何根据上面这一份数据,预测用户5对商品1的评分是多少呢?

如果你不知道如何去实现,那么请认真阅读下面要介绍的推荐系统之矩阵分解模型吧,相信看完之后你将会得到答案(如果木有...那就是我写的烂...请见谅

矩阵分解

一提到矩阵分解(Matrix Factorization,MF),最先想到的我猜是大学线性代数课...那时候的线代课我听着都是云里雾里,还有高数的泰勒展开式...完全不知道这东西将来能有什么用?

直到研究生接触到机器学习、推荐系统才明白了那句古话:

Too young,Too simple,Too naive...

简单回顾一下常用的矩阵分解的方法:

  • 特征值分解:只适用于方阵
  • 奇异值分解(SVD):适用于任何矩阵

当然,本文也不会系统讲解矩阵分解,而是关注与推荐系统中的矩阵分解,除了推荐场景下,矩阵分解还在降维、Embedding等具有广泛的应用,是非常值得认真学习的。

推荐与矩阵分解关系

你可能会问推荐系统怎么跟矩阵分解扯上关系了呢?

在推荐系统中有一种协同过滤方法(Collaborative Filtering,CF),该方法主要由两大类方法构成:

  • 基于邻域的
    • ItemCF、UserCF
  • 基于模型的
    • 隐语义模型(Latent Factor Model,LFM)
      • 矩阵分解(MF)
      • LSA、pLSA、LDA
    • 基于贝叶斯网络
    • 基于SVM

协同过滤方法分为两大类,一类为上述基于邻域的方法,第二类为基于模型的方法,其中有基于隐语义模型的,而矩阵分解模型是隐语义模型最为成功的一种实现。

推荐中的矩阵分解

在10年前的Netfliex的电影评分预测竞赛中,矩阵分解方法打败众多研究团队开发的各种不同预测算法脱颖而出,其实验结果表明,在个性化推荐中使用矩阵分解模型要明显优于传统的基于邻域的协同过滤方法,这也使得矩阵分解成为了当时个性化推荐研究领域中的主流模型之一,时至今日仍旧在推荐领域发光发热。

从推荐的角度理解矩阵分解

矩阵分解,可以理解为将一个高维稀疏的矩阵M,分解为两个低秩的矩阵U与V的过程,后续用这两个低秩向量来近似还原原本的高维稀疏向量。其中低秩意味着有很多冗余信息,另外利用这种冗余信息,可以对缺失数据进行恢复,预测原本矩阵M中的缺失值。

在推荐中的User-Item评分矩阵,通过矩阵分解获得两个低秩向量,分别表示用户向量与商品向量,该过程相当于进行了特征提取或者数据的降维。

推荐领域的矩阵分解,可以认为它是协同过滤的「集体智慧」 隐语义的「深层关系」 机器学习的「有监督学习」的综合体。

  • 集体智慧

推荐中的矩阵分解属于协同过滤类算法,而协同过滤方法通过收集分析用户与商品之间的历史行为、活动、偏好等信息,来预测目标用户对特定商品的喜好程度,这是集体智慧的体现。

  • 隐语义

矩阵分解的一大特点便是隐语义的学习,这使得该算法能够挖掘出更深的用户和物品之间的联系,为我们提供了User与Item的向量形式。通过将两个矩阵中各取一行和一列向量做内积就可以得到对应评分。

  • User向量:代表用户对隐含特征的偏好矩阵
  • Item向量:表示产品所包含的隐含特征矩阵

  • 有监督的机器学习

根据已有用户商品的评分矩阵,对缺失值进行预测,本质上是一个矩阵填充问题,也是一个有监督的学习过程,通过优化方法不断减少预测值与真实值的误差,从而获得更优的User/Item矩阵。

推荐中矩阵分解的目标函数

通过上面的介绍,我们已经知道商品评分预测实际上是一个矩阵补全的过程,在矩阵分解的时候原来的User-Item矩阵是稀疏的,即有一部分有评分,有一部分是没有评过分的,不然也就没必要通过推荐系统来预测评分了。

所以评分预测的最终目的是:得到两个低维矩阵,通过这两个小矩阵的乘积来补全大矩阵中没有评分的位置。

对于矩阵分解的优化目标来说,问题转化成了如何获得两个最优的小矩阵,使原始User-Item评分矩阵有评分的位置(实际值)与两个小矩阵相乘得到的相应位置的评分(预测值)之间的误差最小即可,其实就是一个均方误差损失,这便是该有监督模型的目标函数,具体的目前函数形式如下所示:

其中rui为User-Item矩阵中的原始评分值,pu是用户矩阵,qi是商品矩阵,pu · qi 是代表两个矩阵进行点积,获得其预测的评分值。该目标函数通过最小化原始评分值与预测值的均方误差损失,来不断的优化用户矩阵与商品矩阵。

有时为了防止过拟合,目标函数中还会加上正则化项,如下所示:

推荐中矩阵分解的优化过程

上面我们已经获得了推荐中矩阵分解的目标函数,对该目标函数的优化过程不是凸的,也就是说,很难找到使得该总和最小的向量和的值(并且最优解甚至可能不是唯一的)。

但可以找使用ALS(Alternating least squares,交替最小二乘法)、SGD等优化方法得到其近似解,我们这里将使用 SGD(Stochastic Gradient Descent,随机梯度下降)推导其优化过程。

SGD优化方法

矩阵分解中我们待优化的函数为:

初始状态下,随机初始化的变量为q*p*,分别求两者对目标函数的偏导:

那么,此时SGD的优化过程就变为:

相关优化的代码片段:

代码语言:javascript复制
import numpy as np
import surprise  # run 'pip install scikit-surprise' to install surprise
class MatrixFacto(surprise.AlgoBase):
    '''A basic rating prediction algorithm based on matrix factorization.'''
    
    def __init__(self, learning_rate, n_epochs, n_factors):
        
        self.lr = learning_rate  # learning rate for SGD
        self.n_epochs = n_epochs  # number of iterations of SGD
        self.n_factors = n_factors  # number of factors
        
    def fit(self, trainset):
        '''Learn the vectors p_u and q_i with SGD'''
        
        print('Fitting data with SGD...')
        
        # Randomly initialize the user and item factors.
        p = np.random.normal(0, .1, (trainset.n_users, self.n_factors))
        q = np.random.normal(0, .1, (trainset.n_items, self.n_factors))
        
        # SGD procedure
        for _ in range(self.n_epochs):
            for u, i, r_ui in trainset.all_ratings():
                err = r_ui - np.dot(p[u], q[i])
                # Update vectors p_u and q_i
                p[u]  = self.lr * err * q[i]
                q[i]  = self.lr * err * p[u]
                # Note: in the update of q_i, we should actually use the previous (non-updated) value of p_u.
                # In practice it makes almost no difference.
        
        self.p, self.q = p, q
        self.trainset = trainset

    def estimate(self, u, i):
        '''Return the estmimated rating of user u for item i.'''
        
        # return scalar product between p_u and q_i if user and item are known,
        # else return the average of all ratings
        if self.trainset.knows_user(u) and self.trainset.knows_item(i):
            return np.dot(self.p[u], self.q[i])
        else:
            return self.trainset.global_mean
# data loading. We'll use the movielens dataset (https://grouplens.org/datasets/movielens/100k/)
# it will be downloaded automatically.
data = surprise.Dataset.load_builtin('ml-100k')
data.split(2)  # split data for 2-folds cross validation

algo = MatrixFacto(learning_rate=.01, n_epochs=10, n_factors=10)
surprise.evaluate(algo, data, measures=['RMSE'])

总结

本质上协同过滤和矩阵分解两者具有很紧密的内在联系,只是两种不同的表达形式,参考前人对协同过滤与隐语义模型(这里特指矩阵分解)两种的比较:

矩阵分解技术为作为初期解决item2vec的方法之一就先介绍到这里,后续计划更新的文章将正式进入万物皆可Embedding,概述各种item2vec技术在推荐系统中的应用。

10.9

历史文章推荐

  • AI极客-机器学习|逻辑回归(LR)基础知识点个人总结
  • AI极客-NLP|词向量(1)--从Word2Vec到ELMo
  • AI极客-NLP | 词向量(2)--从ELMo到Bert
  • AI极客-NLP | NLP界最强特征提取器--Transformer

0 人点赞