转-推荐算法中的矩阵分解

一、推荐算法概述

对于推荐系统(Recommend System, RS),从广义上的理解为:为用户(User)推荐相关的商品(Items)。常用的推荐算法主要有:

  • 基于内容的推荐(Content-Based Recommendation)
  • 协同过滤的推荐(Collaborative Filtering Recommendation)
  • 基于关联规则的推荐(Association Rule-Based Recommendation)
  • 基于效用的推荐(Utility-Based Recommendation)
  • 基于知识的推荐(Knowledge-Based Recommendation)
  • 组合推荐(Hybrid Recommendation)

在推荐系统中,最重要的数据是用户对商品的打分数据,数据形式如下所示:

这里写图片描述

其中,<nobr style="box-sizing: border-box;">U1⋯U5</nobr>表示的是<nobr style="box-sizing: border-box;">5</nobr>个不同的用户,<nobr style="box-sizing: border-box;">D1⋯D4</nobr>表示的是<nobr style="box-sizing: border-box;">4</nobr>个不同的商品,这样便构成了用户-商品矩阵,在该矩阵中,有用户对每一件商品的打分,其中“-”表示的是用户未对该商品进行打分。

在推荐系统中有一类问题是对未打分的商品进行评分的预测。

二、基于矩阵分解的推荐算法

2.1、矩阵分解的一般形式

矩阵分解是指将一个矩阵分解成两个或者多个矩阵的乘积。对于上述的用户-商品矩阵(评分矩阵),记为<nobr style="box-sizing: border-box;">Rm×n</nobr>。可以将其分解成两个或者多个矩阵的乘积,假设分解成两个矩阵<nobr style="box-sizing: border-box;">Pm×k</nobr>和<nobr style="box-sizing: border-box;">Qk×n</nobr>,我们要使得矩阵<nobr style="box-sizing: border-box;">Pm×k</nobr>和<nobr style="box-sizing: border-box;">Qk×n</nobr>的乘积能够还原原始的矩阵<nobr style="box-sizing: border-box;">Rm×n</nobr>:

<nobr style="box-sizing: border-box;">Rm×n≈Pm×k×Qk×n=R^m×n</nobr>

其中,矩阵<nobr style="box-sizing: border-box;">Pm×k</nobr>表示的是<nobr style="box-sizing: border-box;">m</nobr>个用户与<nobr style="box-sizing: border-box;">k</nobr>个主题之间的关系,而矩阵<nobr style="box-sizing: border-box;">Qk×n</nobr>表示的是<nobr style="box-sizing: border-box;">k</nobr>个主题与<nobr style="box-sizing: border-box;">n</nobr>个商品之间的关系。

2.2、利用矩阵分解进行预测

在上述的矩阵分解的过程中,将原始的评分矩阵<nobr style="box-sizing: border-box;">Rm×n</nobr>分解成两个矩阵<nobr style="box-sizing: border-box;">Pm×k</nobr>和<nobr style="box-sizing: border-box;">Qk×n</nobr>的乘积:

<nobr style="box-sizing: border-box;">Rm×n≈Pm×k×Qk×n=R^m×n</nobr>

那么接下来的问题是如何求解矩阵<nobr style="box-sizing: border-box;">Pm×k</nobr>和<nobr style="box-sizing: border-box;">Qk×n</nobr>的每一个元素,可以将这个问题转化成机器学习中的回归问题进行求解。

2.2.1、损失函数

可以使用原始的评分矩阵<nobr style="box-sizing: border-box;">Rm×n</nobr>与重新构建的评分矩阵<nobr style="box-sizing: border-box;">R^m×n</nobr>之间的误差的平方作为损失函数,即:

<nobr style="box-sizing: border-box;">e2i,j=(ri,j−r^i,j)2=(ri,j−∑k=1Kpi,kqk,j)2</nobr>

最终,需要求解所有的非“-”项的损失之和的最小值:

<nobr style="box-sizing: border-box;">minloss=∑ri,j≠−e2i,j</nobr>

2.2.2、损失函数的求解

对于上述的平方损失函数,可以通过梯度下降法求解,梯度下降法的核心步骤是

  • 求解损失函数的负梯度:

<nobr style="box-sizing: border-box;">∂∂pi,ke2i,j=−2(ri,j−∑k=1Kpi,kqk,j)qk,j=−2ei,jqk,j</nobr>

<nobr style="box-sizing: border-box;">∂∂qk,je2i,j=−2(ri,j−∑k=1Kpi,kqk,j)pi,k=−2ei,jpi,k</nobr>

  • 根据负梯度的方向更新变量:

<nobr style="box-sizing: border-box;">pi,k′=pi,k−α∂∂pi,ke2i,j=pi,k+2αei,jqk,j</nobr>

<nobr style="box-sizing: border-box;">qk,j′=qk,j−α∂∂qk,je2i,j=qk,j+2αei,jpi,k</nobr>

通过迭代,直到算法最终收敛。

2.2.3、加入正则项的损失函数即求解方法

通常在求解的过程中,为了能够有较好的泛化能力,会在损失函数中加入正则项,以对参数进行约束,加入<nobr style="box-sizing: border-box;">L2</nobr>正则的损失函数为:

<nobr style="box-sizing: border-box;">E2i,j=(ri,j−∑k=1Kpi,kqk,j)2+β2∑k=1K(p2i,k+q2k,j)</nobr>

利用梯度下降法的求解过程为:

  • 求解损失函数的负梯度:

<nobr style="box-sizing: border-box;">∂∂pi,kE2i,j=−2(ri,j−∑k=1Kpi,kqk,j)qk,j+βpi,k=−2ei,jqk,j+βpi,k</nobr>

<nobr style="box-sizing: border-box;">∂∂qk,jE2i,j=−2(ri,j−∑k=1Kpi,kqk,j)pi,k+βqk,j=−2ei,jpi,k+βqk,j</nobr>

  • 根据负梯度的方向更新变量:

<nobr style="box-sizing: border-box;">pi,k′=pi,k−α(∂∂pi,ke2i,j+βpi,k)=pi,k+α(2ei,jqk,j−βpi,k)</nobr>

<nobr style="box-sizing: border-box;">qk,j′=qk,j−α(∂∂qk,je2i,j+βqk,j)=qk,j+α(2ei,jpi,k−βqk,j)</nobr>

通过迭代,直到算法最终收敛。

2.2.4、预测

利用上述的过程,我们可以得到矩阵<nobr style="box-sizing: border-box;">Pm×k</nobr>和<nobr style="box-sizing: border-box;">Qk×n</nobr>,这样便可以为用户<nobr style="box-sizing: border-box;">i</nobr>对商品<nobr style="box-sizing: border-box;">j</nobr>进行打分:

<nobr style="box-sizing: border-box;">∑k=1Kpi,kqk,j</nobr>

2.3、程序实现

对于上述的评分矩阵,通过矩阵分解的方法对其未打分项进行预测,最终的结果为:

这里写图片描述

程序代码如下:

#!/bin/python
'''
Date:20160411
@author: zhaozhiyong
'''
from numpy import *

def load_data(path):
    f = open(path)
    data = []
    for line in f.readlines():
        arr = []
        lines = line.strip().split("\t")
        for x in lines:
            if x != "-":
                arr.append(float(x))
            else:
                arr.append(float(0))
        #print arr
        data.append(arr)
    #print data
    return data

def gradAscent(data, K):
    dataMat = mat(data)
    print dataMat
    m, n = shape(dataMat)
    p = mat(random.random((m, K)))
    q = mat(random.random((K, n)))

    alpha = 0.0002
    beta = 0.02
    maxCycles = 10000

    for step in xrange(maxCycles):
        for i in xrange(m):
            for j in xrange(n):
                if dataMat[i,j] > 0:
                    #print dataMat[i,j]
                    error = dataMat[i,j]
                    for k in xrange(K):
                        error = error - p[i,k]*q[k,j]
                    for k in xrange(K):
                        p[i,k] = p[i,k] + alpha * (2 * error * q[k,j] - beta * p[i,k])
                        q[k,j] = q[k,j] + alpha * (2 * error * p[i,k] - beta * q[k,j])

        loss = 0.0
        for i in xrange(m):
            for j in xrange(n):
                if dataMat[i,j] > 0:
                    error = 0.0
                    for k in xrange(K):
                        error = error + p[i,k]*q[k,j]
                    loss = (dataMat[i,j] - error) * (dataMat[i,j] - error)
                    for k in xrange(K):
                        loss = loss + beta * (p[i,k] * p[i,k] + q[k,j] * q[k,j]) / 2

        if loss < 0.001:
            break
        #print step
        if step % 1000 == 0:
            print loss

    return p, q

if __name__ == "__main__":
    dataMatrix = load_data("./data")

    p, q = gradAscent(dataMatrix, 5)
    '''
    p = mat(ones((4,10)))
    print p
    q = mat(ones((10,5)))
    '''
    result = p * q
    #print p
    #print q

    print result

其中,利用梯度下降法进行矩阵分解的过程中的收敛曲线如下所示:

这里写图片描述
'''
Date:20160411
@author: zhaozhiyong
'''

from pylab import *
from numpy import *

data = []

f = open("result")
for line in f.readlines():
    lines = line.strip()
    data.append(lines)

n = len(data)
x = range(n)
plot(x, data, color='r',linewidth=3)
plt.title('Convergence curve')
plt.xlabel('generation')
plt.ylabel('loss')
show()

参考文献

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容