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
相關文章
相關標籤/搜索