Recommended System

###推荐系统 推荐系统的核心问题就在于为用户推荐与其兴趣类似度比较高的商品。好比在微博上,用户至上想打发时间,并非想准确的查看某条信息,在首页中查看每一条微博,为了帮助他筛选出一批他们可能感兴趣的信息,此时就须要分析出该用户的兴趣,从海量信息中选择出与用户兴趣类似的信息,并将这些信息推荐给用户。推荐系统就是这样,根据用户的历史和社交状况推荐与其喜爱相符的商品或信息。 这时候就须要一个类似度函数f(x),函数f(x)能够计算商品和我用户之间的复杂度,向用户推荐类似度较高的商品。为了可以预测出函数f(x),能够利用到历史诗句主要有:用户的历史行为数据,与该用户相关的其余用户信息,商品之间的类似性,文本描述等等。假设集合C表示全部的用户,集合S表示全部须要推荐的商品。函数f表示商品s到用户c之间的有效用函数,例如:$$f:CxS->R$$其中,R是一个全体排序集合。对于每个用户c \in C,但愿从商品的集合上选择出商品,即s \in S,以使得应用函数f的值最大。html

在功业场景通常都是这样,线下部分能够随意发挥,Python,MATLAB等等均可以,可是到线上部分就要考虑各类状况了,好比效率和并不是,甚至是可移植可拓展性等等。 ###推荐系统的评估方法 ####准确度 这个概念应该比较熟悉的了,对于准确度的评估,也要分系统来看,推荐系统有两种,一种是 打分系统,一种就是 TopN系统。打分系统其实就至关因而一种拟合方法,拟合用户的打分过程,而TopN系统就是给出前N个好的商品而已,因此这两个的准确度的评判标准是不同的。**对于打分系统:设 r_{ui}为用户 u对物品 i的实际打分, r_{ui}'为预测得分,偏差断定标准有两个:$$RMSE=\sqrt{\frac{\sum_{u,i \in T}(r_{ui}-r_{ui}')^2}{|T|}}$$

MAE=\frac{\sum_{u,i \in T}|r_{ui}-r_{ui}'|}{|T|}$$对于TopN的推荐系统,$R(u)$表明推荐的内容,$T(u)$就是用户选择的内容:$$Prescion=\frac{\sum_{u \in U}|R(u) \cap T(u)|}{\sum_{u \in U}|R(u)|}$$ $$Recall = \frac{\sum_{u \in U}|R(u) \cap T(u)|}{\sum_{u \in U}|T(u)|}$$这两个评价指标是互相牵制的,准确率高制约着召回率。**
####覆盖率
表示对物体长尾的发掘能力。有时候卖的多的物品会卖的更多,由于不少人看到别人也买就会跟风,但这样就会形成穷的会更穷,富的会更富,因此为了消除这种两极分化,也叫马太效应。能够用一个熵的概念来衡量$$H=-\sum_{i=1}^n p(i)log p(i)$$熵实际上是我的都知道,平均的时候是最大的,这就有利于消除贫富差距。
####多样性
多样性表示的就是推荐物品的种类多样程度,若是推荐的物品基本都是鞋子一类的,那么多样性就不好了,首先用$s(i,j)$来表示这两个物品的类似度,多样性表示:$$Diversity(R(u))=1-\frac{\sum_{i,j \in R(u),i !=j}s(i,j)}{\frac{1}{2}|R(u)|(|R(u)|-1)}
Diversity = \frac{1}{|U|}\sum_{u \in U}Diversity(R(u))

以上就是一些主要的推荐评价指标,固然仍是不少,好比新颖度,惊喜度,信任度,实时性等等。 ####冷启动 冷启动指的就是在一个新用户和新商品进来的时候,推荐系统对这个用户和商品是一无所知的,如何在这种状况下作到有效的推荐,这就叫作冷启动问题了。对于一个一无所知的用户,解决方法有不少:能够收集一些很是热门的商品,收集一些信息,在用户注册的时候收集一些信息,好比弹出来问一下你喜欢什么,或者问一下你感兴趣的是什么。不少游戏知乎都会有这种举措。在用户结束以后还能够进行一些互动的小游戏来肯定用户喜欢仍是不喜欢。 对于新商品,能够根据自己的属性,求与原来商品类似度。能够用item-based-CF推荐出去。 ###类似度的衡量 欧式距离,是我的都知道,不提了。 Pearson Correlation类似度:$$Corr(x,y) = \frac{<x-x',y-y'>}{|x-x'||y-y'|}$$<x,y>指的就是內积操做、皮尔逊类似度的好处就在于对于级数不敏感,级数相差过大带来的影响不大。 余弦类似度:$$cos(x,y) = \frac{<x,y>}{|x||y|}$$这个就不用多说了,通常就是用在了文本编码以后的向量类似度比较,由于这时候不少向量都不在欧式空间上了,通常就用他们夹角的大小来 ###推荐算法 ####基于内容的推荐 ####基于关联规则的推荐 ####协同过滤的推荐 ####基于知识的推荐 ####基于图的推荐 ####组合推荐 固然不会讲解这么多,只会提到一两个。 ####基于内容的推荐系统 基于内容就是基于用户喜欢的物品的属性/内容进行推荐,须要分析内容,无需考虑用户和用户直接的关系。一般就是在文本相关的产品上进行推荐。因此通常就是经过内容上的关键词进行推荐,而后编码比对物品内容的异同进行推荐。事实上编码以后就能够随意发挥了,直接使用KNN都是能够的。 最简单的文本推荐,对于一份文本,首先就是要创建资料这个时候就是叫编码过程了,由于不可能把里面的文字都抽取出来,这样工做量很是大,使用首先就是要分词去掉重复的词语,而后抽取关键字作编码。TF-IDF,词袋模型,ont-hot编码,词k_{ij}在文件d_j中权重w_{ij}都是能够的,获得这些向量以后,就能够用类似度衡量,选择合适的算法作类似度排序选择了。好比对于书本**《Building data mining application for CRM》**这个本书感兴趣。node

以上是商品总和。
出现过的单词扣一,没有的都是0,这种编码叫作one-hot编码,这里商品少,单词也就那么几个,因此ont-hot编码不长,就是一点点而已。比较牛逼点的就是TF-IDF了:
把文档出现词的几率算了进来。获得这些向量以后只须要作近似就行了,好比KNN最简单的。 ####协同过滤的推荐 NeiNeighborhood-based Algorithm是一种基于近邻的推荐,协同过滤有两种,一种是基于用户的协同过滤,一种是基于物品的协同过滤。 #####user-based CF 其实过程是很是简单的,简单到飞起。首先咱们是要获得一个类似度矩阵,这个类似度矩阵首先是一个对称的,其次它的对角线都是0,。计算完用户之间的类似度以后利用用户之间的类似度为没有打分的项打分。其实就是$$p(u,i) = \sum_{v \in N(j)}w_{u,v}r_{v,j}$$找到当前这个用户没有打分的商品,而后把对这个商品评价过的用户的得分乘上类似矩阵的对应权重相加便可。 代码实现 工具包的实现: 求余弦类似度:

def cos_sim(x, y):
    numerator = x * y.T
    denominator = np.sqrt(x * x.T)
    return (numerator / denominator)[0, 0]
复制代码

求类似度矩阵:git

def similarity(data):
    m = np.shape(data)[0]
    w = np.mat(np.zeros((m, m)))
    for i in range(m):
        for j in range(i, m):
            if j != i:
                w[i, j] = cos_sim(data[i, ], data[j, ])
                w[j, i] = w[i, j]
            else:
                w[i, j] = 0
    return w
复制代码

求top N的商品是什么:github

def top_k(predict, k):
    top_recom = []
    len_result = len(predict)
    if k >= len_result:
        top_recom = predict
    else:
        for i in range(k):
            top_recom.append(predict[i])
    return top_recom
复制代码

基于用户的协同过滤:算法

def user_based_recommend(data, w, user):
    m, n = np.shape(data)
    interaction = data[user, ]
    not_inter = []
    for i in range(n):
        if interaction[0, i] == 0:
            not_inter.append(i)
    predict = {}
    for x in not_inter:
        item = np.copy(data[:, x])
        for i in range(m):
            if item[i, 0] != 0:
                if x not in predict:
                    predict[x] = w[user, i] * item[i, 0]
                else:
                    predict[x] = predict[x] + w[user, i] * item[i, 0]
    return sorted(predict.items(), key=lambda  d:d[1], reverse=True)
复制代码

类似度矩阵
为每个用户所推荐的商品。 #####item_based CF 也是同样,只须要把数据转置一下就行了。

def item_based_recommend(data, w, user):
    m, n = np.shape(data)
    interation = data[:, user]
    not_inter = []
    for i in range(n):
        if interation[0, i] == 0:
            not_inter.append(i)

    predict = {}
    for x in not_inter:
        item = np.copy(interation)
        for j in range(m):
            if item[0, j] != 0:
                if x not in predict:
                    predict[x] = w[x, j] * item[0, j]
                else:
                    predict[x] = predict[x] + w[x, j] * item[0, j]
    return sorted(predict.items(), key=lambda d:d[1], reverse=True)
复制代码

类似度矩阵
每位用户的推荐。

###user-based vs item-based 这两种方法各有优缺点。对于UserCF,适用于用户较少的场景,若是用户是很是多的的状况下,计算用户类似度矩阵的代价是很大的。这个时候若是物品相对用户要少,那么就能够用ItemCF来计算类似度矩阵。对于两种算法的领域也有很大的区别,UserCF的时效性较强,对于用户兴趣不太明显的领域是有可能会推荐出来的,也就是说覆盖性会好一些,由于它是根据用户之间的类似度决定的,用户之间的兴趣点是类似可是不会都相同的,因此能够削弱马太效应。而对于ItemCF,这个物品类似度是死的,类似的基本都很类似,因此可能会有很大的长尾,通常是用户需求很强烈的地方使用。还有冷启动问题,对于一个新用户进来的时候,不能马上对他进行推荐,由于用户的类似度矩阵是一段时间后离线计算的,新物品上线以后一旦有用户对这个商品产生行为就能够将物品推荐给它产生行为的用户了。可是这样就没有办法在不离线更新类似度表的状况下将新物品推荐给用户了。还有就是推荐理由,对于UserCF,用一个我根本不认识的人的习惯来推荐给我,对于用户的说服力确定是不够的,商品就不同了,商品是死的。 ###CF的优缺点 优势:基于用户的行为对于推荐内容是不须要先验知识的,只须要创建用户或者是商品的类似矩阵便可,结构很简单,在用户丰富的状况下效果很好,并且很简单。 缺点:须要大量的行为,也就是历史数据,须要经过彻底相同的商品关联起来,类似的不行。并且·使用CF是有前提的了,要求就是用户只会彻底取决于以前的行为,上下文是没有关系的了。并且在数据稀疏的状况下效果是很差的,还可能须要二度关联,由于若是一下商品没有什么人买,基本就没得推荐了。 ###基于隐语义的推荐 基于隐语义的推荐,其实就是矩阵分解。以前写过一篇叫Matrix Factorization的文章讲的就是这个:www.jianshu.com/p/af4905383… 首先对用户进行一些简单的编码,由于用户若是是把名字记上去是作不了计算的,因此咱们把用户进行编码,好比有4个用户,一号用户就[1,0,0,0],二号[0,1,0,0]等等以此类推。bash

若是是使用神经网络来解决这个问题,首先就是要设计一个网络, N-d-M,N输入用户的数量,d就是网络的维度,M就是输出的结果,M就是物品的数量,由于最后的输出就是用户对商品的评分。
但实际上仍是有一个问题,就是对于激活函数,也就是非线性函数是否须要的问题。事实上是不须要的,由于若是只有一个 x是有效的,那么这个 x就是只有一行有效的,因此只会有一个x通过了tanh函数,从效果上讲一个tanh(x)和x是没有什么区别的。因此能够忽略非线性:
整合一下公式$$h(x) = W^TVx$$因为x就是一行有效,因此$$h(x) = W^Tv_n$$因此,第m个用户的第n个商品的评分就是$$r_{nm} = w_{m}^Tv_n$$
这样就分解获得了咱们要的公式,最后求导便可。事实上分解出来的两个矩阵里面的变量表明的数据的意义实际上是不明确的,也就是说比较抽象,怎么想象都行。分解出来的矩阵 R_{nxk},Q_{kxm},k表明的就是分解的度,分解的越多有可能会过拟合,因此这里也是能够加正则化的。
不要尝试的去理解全部分解的赢语义,这是没有意义的,好比神经网络的每个神经元,事实上大家是不知道他们究竟是在干什么,只是知道他们在观察而又,这里也是同样的,这里面分解出来的东西,若是是电影,你能够说是喜剧成分,武大成分,也能够说是演员等等,至关于一指示变量而已,只是指明了这个物品是多少分,可是没有给出具体的意义。 具体的公式以前的博客都有,例子也提到,这里就不重复了。 ####上面提到的只是最简单的MF模型,可是这类模型有一个缺点,他们分解出来的东西会有负数,这就尴尬了,由于不多有变量因素会其负做用的,因而便出现了非负矩阵分解。 非负矩阵分解出现的根本缘由就矩阵分解解释性差的问题。 ###NMF 矩阵非负分解,这个就有点厉害了。首先引入一下KL离散度:$$D(A|B) = \sum_{i,j}(A_{i,j}log \frac{A}{B}-A_{i,j}+B_{i,j})$$

###KL损失函数 **对于KL散度的推导其实很简单:网络

f(x) - f(x_0) = \nabla f(x_0)(x-x_0) \\
f(y) = f(x) - \nabla f(x) (x-y) $$因此一阶Tayor逼近的偏差是:$$f(x) - f(y) - \nabla f(x) (x-y)$$代入熵$f(x) = \sum_{i=1}^k x_iInx_i$:$$D(x||y) = xInx - yIny - (Iny + 1)(x-y) \\
D(x||y) = xlog \frac{x}{y} - x + y$$这样推导出来了。平方差损失函数对应着高斯分布,那么KL确定也对应着另外一个分布——泊松分布:$$P(X = x) = \frac{\lambda ^x}{x!}e^{-\lambda} \\
P_1(X = x) = \frac{\lambda_1 ^x}{x!}e^{-\lambda_1} \\
P_2(X = x) = \frac{\lambda_2 ^x}{x!}e^{-\lambda_2} \\
In \frac{P_1(X=x)}{P_2(X = x)} = xIn \frac{\lambda_1}{\lambda_2} - \lambda_1 + \lambda_2$$由于$x$是随机数,因此求一下指望:$$\sum_{x = 0}^{+\infty}P_1(X=x)In(\frac{P_1(X=x)}{P_2(X=x)}) = \sum_{i=1}^{+ \infty}P_1{X=x}[xIn \frac{\lambda_1}{\lambda_2}-\lambda_1+\lambda_2] \\ 
= \lambda_1 In \frac{\lambda_1}{\lambda_2}-\lambda_1 + \lambda_2$$因此KL离散度就对应了泊松分布。**

对于这个KL离散度:$$D(A|B) = \sum_{i,j}(A_{i,j}log \frac{A}{B}-A_{i,j}+B_{i,j})$$KL离散度必定是大于等于0的,当$A == B$的时候,这个离散度就是等于0的了,因此咱们要求的就是不断的$minimizeD(A||B)$便可。
因此如今NMF的损失函数有两种了①均方差损失函数:$loss = \frac{1}{2}(y - y')^2$;②KL离散度:$loss =  \sum_{i,j}(A_{i,j}log \frac{A}{B}-A_{i,j}+B_{i,j})$,仍是用第一种吧!
问题来了,既然是非负数的,怎么保证是正数是一个问题,这里有一个很牛逼的技巧:
$$loss = [R - (PQ)]^2 \\ 
\frac{\delta loss}{\delta Q} = 2[R - (PQ)]P \\
= -2[(P^TR)-(P^TPQ)]

因此更新方式:$$Q = Q + n*[(P^TR) - (P^TPQ)]$$要作的就是设计一个n,使得乘上以后能够抵消前面的Q便可。n = \frac{Q}{P^TPQ},带进去以后:$$Q = Q \frac{(P^TR)}{(P^TPQ)} \ P = P \frac{(RQ^T)}{PQQ^T}$$使用KL离散也是同样的。 ####代码实现app

def train(V, r, maxCycles, e):
    m, n = np.shape(V)
    W = np.mat(np.random.random((m, r)))
    H = np.mat(np.mat(np.random.random((r, n))))

    for step in range(maxCycles):
        V_pre = W * H
        E = V - V_pre
        err = 0.0
        for i in range(m):
            for j in range(n):
                err += E[i, j] * E[i, j]
        if err < e:
            break
        if step % 1000 == 0:
            print("\Interator: ", step, " Loss: ", err)
        a = W.T * V
        b = W.T * W * H
        for i in range(r):
            for j in range(n):
                if b[i, j] != 0:
                    H[i, j] = H[i, j] * a[i, j] / b[i, j]
        c = V * H.T
        d = W * H * H.T
        for i in range(m):
            for j in range(r):
                if d[i, j] != 0:
                    W[i, j] = W[i, j] * c[i, j] / d[i, j]
    return W, H

复制代码

效果: dom

效果仍是能够的。 ####CF VS隐语义 隐语义其实还有一种分解方式,就是SVD,SVD也能够的,可是SVD的分解是O(m^3),并且若是原矩阵是有不少缺省值的话,那么SVD就会用0填充,这样不太好的。相比之下CF跟简单,并且可解释性也强,可是隐语义模型能够挖掘出更深层的物体或者是用户之间的关系,覆盖性会更好,双方各有优缺点。 ###基于图的推荐 ###PageRank Algorithm 首先要提到的就是PageRank算法,等下还要改进一下这个算法才会获得真正的推荐系统算法。这个算法是在Google初期使用的一个网页排序算法,用于对网页重要性的排序。一开始的网页排序都是按照人工筛选或者是关键词筛选的,可是这样效率不高并且效果很差,常常是有不少网页有不少重复关键词的而后被置顶,这个时候就须要一个新的方法来从新评估网页的质量,从新排序了。 把互联网上的网页模拟成一个节点,出链就是指向其余网页的一条有向边,也就是当前这个网页有一条指向其余网页的边,入链就是一个指向当前页面的边,也就是说有一个网页是指向了当前这个网页的。这个时候当前网页就是其余网页转进来的了。 因此网页评估是带有如下的两个假设的:数量假设:一个网页的入度,也就是入链越大,质量越高。质量假设:一个节点的入度来源质量越高,那么当前节点质量就越高。可是,问题来了,网页A和网页B的质量有关系,网页B又和网页C有关系,以此类推要是网页C又和A有关系那就陷入了循环里面了。这样就解不出来了,因此简化一下模型,假设,全部的网页都和前一个网页有关系,这样就叫随机游走模型,也就是一阶马尔科夫模型。随机游走能够看作是马尔科夫链的一种特例。也叫作醉汉模型,没一次走的方向或者步数只于前一次走的有关系而已,这一点有点像布朗运动。 如今将这个模型数学化一下下,假设如今有N个网页,一开始初始化确定每个网页的几率都是同样大的,都是 \frac{1}{N},好比下图:
每个网页的几率就是 [\frac{1}{4},\frac{1}{4},\frac{1}{4},\frac{1}{4}],每个网页转向其余网页的几率都是同样的:

\left\{
 \begin{matrix}
   0 & \frac{1}{2} & 1 & 0 \\
   \frac{1}{3} & 0 & 0 & \frac{1}{2}\\
   \frac{1}{3} & 0 & 0 & \frac{1}{2}\\
  \frac{1}{3} & \frac{1}{2} & 0 & 0
  \end{matrix}
  \right\}

记这个矩阵为T矩阵,T[i][j]表明的就网页j跳转到网页i的几率,\sum_{j=1}^n T[i][j] = 1。根据这个矩阵,咱们是能够计算出用户访问每个网页的几率:ide

V_1 = T*V_{0} = \left\{
 \begin{matrix}
   0 & \frac{1}{2} & 1 & 0 \\
   \frac{1}{3} & 0 & 0 & \frac{1}{2}\\
   \frac{1}{3} & 0 & 0 & \frac{1}{2}\\
  \frac{1}{3} & \frac{1}{2} & 0 & 0
  \end{matrix}
  \right\} *
 \left\{
 \begin{matrix}
   \frac{1}{4} \\
   \frac{1}{4} \\
   \frac{1}{4} \\
   \frac{1}{4} 
  \end{matrix}
  \right\}  = 
 \left\{
 \begin{matrix}
   \frac{9}{24} \\
   \frac{5}{24} \\
   \frac{5}{24} \\
   \frac{5}{24} 
  \end{matrix}
  \right\}  \\
V_n = T*V_{n-1} = T^n * V_0
$$当迭代屡次以后,$V_n$这个矩阵会最终稳定下来,这个矩阵叫马尔科夫矩阵。
>>####矩阵T的条件
>>**对于转移矩阵T是存在条件的:
①T要是随机矩阵:全部元素大于0,并且要求列相加要是等于1的。
②T是不可约的:所谓的不可约就是强连通性,即图中任何一个节点能够到达其余任何一个节点。好比有某一个节点是不能到达任何一个节点的,在那个节点所在的列都是0,或者是只有回到本身,也就是自环路。这两种就是在后面提到的终止点和陷阱了。
③T要是非周期的:也就是符合马尔科夫链的周期性变化。**

周期性迭代以后最终结果会固定住,好比上面的结果就是$[\frac{3}{9},\frac{2}{9},\frac{2}{9},\frac{2}{9}]^T$,也就是说第一个页面访问的频率是很高的。
####终止点
终止点就是指没有任何出链的网页,好比:![](https://upload-images.jianshu.io/upload_images/10624272-5439bf4e5c46a338.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)节点C就是一个终止点。
$$\left\{
 \begin{matrix}
   0 & \frac{1}{2} & 0 & 0 \\
   \frac{1}{3} & 0 & 0 & \frac{1}{2}\\
   \frac{1}{3} & 0 & 0 & \frac{1}{2}\\
  \frac{1}{3} & \frac{1}{2} & 0 & 0
  \end{matrix}
  \right\}$$![](https://upload-images.jianshu.io/upload_images/10624272-5fdc5ec492c0edfd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)这样的结果显然是毫无心义的。可是这并不意味着凉了,任何的算法都是人想出来的,也就是模拟人的作法。若是你遇到一个直进不出的网站,那么确定是开溜去另一个网站,因此遇到这种状况,那直接就是退出去啊。因此遇到这种状况就是直接把这个图的终止点改为到每个节点都有一条边,由于退出从新选择网页就至关因而从这个网页转移到其余网页去了。成功改变结果。
####陷阱
![](https://upload-images.jianshu.io/upload_images/10624272-15188744ccff4224.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)只是指向了本身,屡次收敛以后:
![](https://upload-images.jianshu.io/upload_images/10624272-f7cc2947e6a3a3cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)这个结果也是毫无心义的。一样,一个用户是不可能老是在一个网页里面打转。刚刚的添加转移的边方法也是能够的,可是比较经常使用的一种方法是用一个几率转移的方法,没一次用户不是都是必定会按照状态转移矩阵走的,因此能够加上一个退出的几率。$$V_n = \alpha TV_{n-1}+ (1-\alpha)V_0$$这样就稳了。
####Summary
**首先一个就是连接的质量问题,有不少导航栏的连接是不能和正常的跳转连接相比较的,好比CSDN两边的那些博客,都是本身写的,这些很影响判断的。还有一个就是没有过滤广告的连接,这些也是影响判断的。还有一个是相似于冷启动的问题,一个新网友若是是刚刚加了进来,通常是没有什么入度的,出度可能有,可能还要花些时间适应。**
#####对于PageRank的公式化
每个节点咱们都给一个PR值给他们,也就是说会根据这个PR值来判断哪一个网站走的几率最大。![](https://upload-images.jianshu.io/upload_images/10624272-cff85b253087f921.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这个时候$PR(A) = PR(B)+PR(C)$,然而,图中的B是不止一条的,B到A有两条路径,那么就是二分之一了,因此$PR(A) = \frac{PR(B)}{2}+\frac{PR(C)}{1}$,不断往下迭代便可。因此一个网页的迭代公式:$$PR(i) = \alpha \sum_{j \in in(i)} \frac{PR(j)}{|L(P_j)|}+\frac{(1-\alpha)}{4}$$N表示网页总数,in(i)表示的是指向网页i的网页集合,out(j)表示的就是网页j指向的网页集合。后面代码实现就是按照这个。
####马尔科夫链的收敛性的证实
这个就有点牛逼了。
$$T = \alpha T + \frac{(1-\alpha)}{N}ee^T \\
P_{n+1} = TP_{n}

对于收敛性,有三个性质要证实的: #####存在一个特征值\lambda = 1 假设一个向量1_n = [1,1,...,1]^T,因为每一列相加都是1,因此$$1_n^TT = 1_n^T = A^T1_n = 1_n$$这个是直接根据性质证实的。 #####存在的特征值都是|\lambda| <= 1 假设一个函数s(x) = \sum_{i=1}^n x_i,假设T^2_i表明的就是列向量,那么有:$$s(T^2_l) = s(\sum_{i=1}^na_iali) = \sum_{i=1}^n s(a_i)a_{li} = \sum_{i=1}^na_{li} = 1$$因此,T^2就仍是马尔科夫矩阵,不管上面叠多少次方都仍是。根据特征值的幂还有特征向量有$$T^n x = \lambda^n x$$ ,若是\ambda > 1那么就是无限大了,可是马尔科夫矩阵是小于1的,因此和假设矛盾\lambda <= 1。 #####收敛性

T^n u_0 = c_11^nx_1 + c_2 \lambda^n_2x_2+c_3 \lambda^n_3x_3+......+c_n \lambda^n_nx_n$$由于只有一个特征值是1的,其余都是小于1的,n次方更不要说了,因此迭代屡次以后绝壁收敛,实锤。参考:http://blog.sina.com.cn/s/blog_80ce3a550102xas8.html
这样就完整证实了PageRank算法。
####代码实现
```
from pygraph.classes.digraph import digraph
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

class pageRank(object):

    def __init__(self, dg):
        self.alpha = 0.85
        self.maxCycles = 200
        self.min_delta = 0.0001
        self.graph = dg

    def page_rank(self):
        #没有出链的点先加上和全部点的边
        for node in self.graph.nodes():
            if len(self.graph.neighbors(node)) == 0:
                for node2 in self.graph.nodes():
                    digraph.add_edge(self.graph, (node, node2))
        nodes = self.graph.nodes()
        graphs_size = len(nodes)

        if graphs_size == 0:
            return 'nodes set is empty!'

        page_rank = dict.fromkeys(nodes, 1.0/graphs_size)
        runAway = (1.0 - self.alpha) / graphs_size
        flag = False
        for i in range(self.maxCycles):
            change = 0
            for node in nodes:
                rank = 0
                for incident_page in self.graph.incidents(node):
                    rank += self.alpha * (page_rank[incident_page] / len(self.graph.neighbors(incident_page)))
                rank += runAway
                change += abs(page_rank[node] - rank)
                page_rank[node] = rank

            print("NO.%s iteration" % (i + 1))
            print(page_rank)

            if change < self.min_delta:
                flag = True
                break
        return page_rank
```
导入数据代码我懒得写了,直接用了一个包里面的。
```
if __name__ == '__main__':
    dg = digraph()

    dg.add_nodes(["A", "B", "C", "D", "E"])

    dg.add_edge(("A", "B"))
    dg.add_edge(("A", "C"))
    dg.add_edge(("A", "D"))
    dg.add_edge(("B", "D"))
    dg.add_edge(("C", "E"))
    dg.add_edge(("D", "E"))
    dg.add_edge(("B", "E"))
    dg.add_edge(("E", "A"))

    pr = pageRank(dg)
    page_ranks = pr.page_rank()

    print("The final page rank is\n", page_ranks)
```
最后效果:
![](https://upload-images.jianshu.io/upload_images/10624272-6f314f0a68fca047.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
好像和推荐系统扯远了,可是这个算法要用到后面退出PersonalRank算法。
###PersonalRank Algorithm
在PageRank算法中,计算出的PR值是每个节点相对于全局的重要性程度,而在推荐问题里面要求的是商品对于用户的重要性了,而不是全局的重要性。好比,当这个用户U1计算的时候,就要遍历全部的商品进行计算,看看哪一个商品的重要性相对于这个用户是最高的就推荐便可。
PersonalRank Algorithm是PageRank的变形,用于计算商品节点$D_j$相对于用户节点U的重要程度。假设用户为$U_1$,则从节点$U_1$开始在用户——商品二部图中游走,游走到容易一个节点的时候,和PageRank算法同样,会按照必定的几率选择中止或者继续游走。直到每个节点访问的几率再也不变化位置。
首先引入二部图:![](https://upload-images.jianshu.io/upload_images/10624272-111cf7a284c521d9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)二部图是无向图的一种,若无向图$G=<V,E>$中,其中,V是无向图中的顶点的集合,E是无向图中边的集合。在无向图G中,边的集合V能够分红两个子集$V_1,V_2$,并且知足这两个子集交集为空。回到PersonalRank算法,在这个算法中不用区分用户和商品,上述节点的感兴趣程度就变成了对用户$U_1$计算各个节点$U_2,...,U_5,D_1,...,D_5$的重要程度。算法具体流程:
**①初始化:$PR(U_1) = 1,PR(U_2) = 0,...,PR(U_5) = 0,PR(D_1) = 0,...,PR(D_5) = 0$。
②开始在图上游走,每次选择PR值不为0的节点开始,沿着边往前的几率为$\alpha$,停在当前点的几率就是$1-\alpha$了。
③首先U1开始,从U1到D2,D3,D4的几率就是$\frac{1}{3}$,则此时D1,D2和D4的PR就是$\alpha*PR(U_1)*\frac{1}{3}$,U1的PR值就变成了$1-\alpha$。
④此时PR值不为0的节点为$U_1,D_1,D_2,D_4$,则此时从这三点出发,继续上述的过程直到收敛为止。因此更新公式为:$$PR(i) = (1-\alpha)r_i + \alpha \sum_{j \in in(i)} \frac{PR(j)}{|out(j)|} \\ 
r_i = 1:0?i = u$$若是是在当前节点那么就要加上一个停留的几率了。**
####代码实现
```
def generate_dict(dataTmp):
    m, n = np.shape(dataTmp)
    data_dict = {}
    for i in range(m):
        tmp_dict = {}
        for j in range(n):
            if dataTmp[i, j] != 0:
                tmp_dict["D_" + str(j)] = dataTmp[i, j]
        data_dict["U_" + str(i)] = tmp_dict
    for j in range(n):
        tmp_dict = {}
        for i in range(m):
            if dataTmp[i, j] != 0:
                tmp_dict["U_" + str(i)] = dataTmp[i, j]
        data_dict["D_" + str(j)] = tmp_dict
    return data_dict
```
转换成二部图。
```

def PersonalRank(data_dict, alpha, user, maxCycles):
    rank = {}
    for x in data_dict.keys():
        rank[x] = 0
    rank[user] = 1
    step = 0
    while step < maxCycles:
        tmp = {}
        for x in data_dict.keys():
            tmp[x] = 0
        for i, ri in data_dict.items():
            for j in ri.keys():
                if j not in tmp:
                    tmp[j] = 0
                tmp[j] += alpha * rank[i] / (1.0 * len(ri))
                if j == user:
                    tmp[j] += (1-alpha)
        check = []
        for k in tmp.keys():
            check.append(tmp[k] - rank[k])
        if sum(check) <= 0.0001:
            break
        rank = tmp
        if step % 20 == 0:
            print('NO: ', step)
        step += 1
    return rank

```
效果:![](https://upload-images.jianshu.io/upload_images/10624272-c992f5796c651cb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>>####最后附上GitHub代码:https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/Recmmended%20System
相关文章
相关标签/搜索