1. 程式人生 > >資料探勘之_SVD的python實現和分析pin

資料探勘之_SVD的python實現和分析pin

前言

今日實現第一個推薦演算法,在”機器學習實戰“一書中找到了SVD方法一章練習。這裡總結下筆記經驗,與大家分享 。

簡介

對於一個簡單的推薦系統,例如電影推薦,我們知道N個使用者對M個電影的評分。這時候對於一個新的使用者,我們應該如何給他推薦新的電影呢?一個最簡單的方法,根據使用者已看的電影,找出與他相似的使用者,然後推薦其他未看的高得分的電影。SVD提供了一個更加準確的解決方案。其基本思想是,降維!      對於電影,一般的推薦演算法是將每個電影的評分作為一個維度,對於Xi使用者,就有一個矩陣行  Xi = [xi1,xi2,xi3....xij]。 但是電影如此之多,每個人又不可能看過所有的電影,這將造成矩陣非常巨大,然後非常稀疏。SVD的具體思想是提取電影的引數Q1, ..., Qj。這個引數可以表達為電影的動作,搞笑,恐怖等程度的描述。因此後面的推薦演算法中,我們不需要對每個電影的口味進行分析,當給定新使用者的時候直接推薦適合他口味,即基於電影引數Qj的一個評分相符,的電影即可。

實現

實現SVD,只需要python中的numpy,其中有一個線性代數工具箱linalg。
import numpy as np
data  = [[1,2,3],[2,2,3],[3,3,3]]
U,S,V = np.linalg.svd(data)


>>> U
array([[-0.48273945,  0.76567677,  0.42509024],
       [-0.54696309,  0.11548497, -0.82915294],
       [-0.68395468, -0.63277351,  0.36304778]])
>>> S
array([ 7.51653849,  1.17761678,  0.33892171])
>>> V
array([[-0.48273945, -0.54696309, -0.68395468],
       [-0.76567677, -0.11548497,  0.63277351],
       [-0.42509024,  0.82915294, -0.36304778]])
其中S向量只儲存了對角元素的成分,可以大大節省儲存空間。接下來我們就需要保留部分奇異值,對於保留的數量,一個典型的方法就是保留矩陣中90%的能量資訊。能量資訊為奇異值的平和總和。 看到這裡是否想起了PCA分析,同樣的分解矩陣,同樣的計算能量資訊。下面討論章節我會進行一些個人總結。 那麼演算法實現了,具體應用到推薦中該如何做呢?對於使用者沒有看過的電影,我們只需要計算與使用者看過的電影中的高評分電影的相似度,然後推薦相似度高的電影即可。你或許聽過協同過濾(collaborative filtering),沒聽過沒關係,我們要做得就是協同過濾。也就是基於使用者與其他使用者的資料進行對比來實現精確推薦。另外還有可以基於內容的推薦,不在本文考慮範圍。 計算相似度的集中方法,有: 歐式距離,資料差的的平方和 相關係數,一般為皮爾遜相關係數(Pearson correlation) 餘弦相似性,兩個向量的餘弦夾角的大小 注意,計算相似度後,最好把資料歸一化,一般歸一化到0-1的範圍。計算相似度中我們一般考慮使用基於物品的相似度的分析。原因是由實際考慮的。設想你有一個商店,商品種類可能不太會變動,但是使用者不斷進進出出,那麼計算那個方便呢! 對,由於物品的穩定性更高,計算量小,那麼就基於物品的推薦即可。

事例

假設你有一家餐館,然後有資料(自己想想資料是什麼吧)。怎麼給使用者推薦他沒有嘗試過的菜品呢。一般我們看到是直接上我們的特色菜,這個就沒有針對性了。我們試試基於SVD的推薦演算法。具體步驟為: (1)收集資料! (2)建立使用者評價矩陣 (3)針對特定使用者,找到其沒有評價的物品,計算其評分 (4)將預測的物品評分從大到小排序,然後推薦給使用者。 結合最後的程式,執行下面的推薦命令,即可完成給使用者0推薦3個產品的任務
user = 0
dataMat = mat(loadExData2())
# 直接實現
rec1 = recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst)
# 採用SVD方法實現
rec2 = recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=svdEst)
(Pdb) rec1
[(0, 5.0), (1, 5.0), (2, 5.0)]
(Pdb) rec2
[(7, 4.5148349067003304), (8, 4.514365463123859), (0, 4.5142831096323039)]

討論

(1)SVD全程為Singular Value Decomposition, 即奇異值分解。假設原始資料為m*n的矩陣,SVD會將矩陣分解為三個矩陣的乘積,中間的矩陣為一個對角不為零其餘元素為0的矩陣。那些部位0的對角元素即為奇異值。

(2)SVD其實是一種矩陣分解技術,不同的矩陣分解技術可能適合不同的應用。其他技術,例如PCA。同樣可以用於影象壓縮等其他應用。 (3)其他推薦演算法。 試想另外一種情形,假設一個商家想看怎麼捆綁銷售可以獲得利益最大化怎麼辦。這時候要考慮在所有銷售商品中,哪兩種或幾種商品銷售趨勢很一致。這其實也是一種推薦過程。這個過程中使用者的評價暫時忽略,我們嘗試找銷售量相似的產品。這裡提供一下大致的思路。 a, 計算兩兩產品的相關係數 b, 對某一產品,按照相關係數排序 c, 對排序結果,挑選相關係數最大的物品做捆綁銷售。 (4)SVD演算法其實會消耗大量的網路資源。因此實際操作的時候考慮到效率問題,大規模計算一般會是在離線情況下一天計算一次相似性,並且儲存相似性得分。加入網站剛開始建立,資料量不足(冷啟動問題),可以考慮用搜索問題來解決推薦。 理論上有很多推薦的演算法,按內容推薦, 協同過濾(包括item-based, user-based, SVD分解等),上下文推薦,Constraint-based推薦,圖關係挖掘等。很多比較牛的單個演算法, 就能在某個指標上取得較好效果, 例如MAE,RMSE。。。不過有自己的優點, 每種演算法也有自己的缺點, 例如按內容推薦主要推薦和使用者歷史結果相似的item,一般的item-based容易推薦熱門item(被更多人投票過)。。。。   所以在工業界,例如各網際網路公司, 都會使用多種演算法進行互相配合, 取長補短, 配合產品提升效果。而且在完整的推薦系統中,不僅有傳統的Rating推薦, 還需要輔以非常多的挖掘, Ranking來達到預期效果。
圖片來自引用參考2
#!/usr/bin/env python
# -*- coding: UTF-8
from numpy import *
from numpy import linalg as la
import numpy as np

    
def loadExData2():
    return[[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
           [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
           [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
           [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
           [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
           [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
           [4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
           [0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
           [0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
           [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
           [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]]
    
def ecludSim(inA,inB):
    return 1.0/(1.0 + la.norm(inA - inB))


def pearsSim(inA,inB):
    if len(inA) < 3 : return 1.0
    return 0.5+0.5*corrcoef(inA, inB, rowvar = 0)[0][1]


def cosSim(inA,inB):
    num = float(inA.T*inB)
    denom = la.norm(inA)*la.norm(inB)
    return 0.5+0.5*(num/denom)


def standEst(dataMat, user, simMeas, item):
    '''    
    計算相似性
    資料矩陣,使用者ID, 相似度方法, 物品ID
    simMeas: ecludSim, pearsSim, cosSim
    '''
    n = shape(dataMat)[1]
    simTotal = 0.0; ratSimTotal = 0.0
    # 不同的物品
    for j in range(n):
        userRating = dataMat[user,j]
        if userRating == 0: continue
        # 對兩個物品,item和j,考慮都有評價的部分
        overLap = nonzero(logical_and(dataMat[:,item].A>0, \
                                      dataMat[:,j].A>0))[0]
        if len(overLap) == 0: 
            similarity = 0
        else: 
            similarity = simMeas(dataMat[overLap,item], \
                                   dataMat[overLap,j])
        print 'the %d and %d similarity is: %f' % (item, j, similarity)
        simTotal += similarity
        ratSimTotal += similarity * userRating
    # 返回歸一化的結果    
    if simTotal == 0: 
        return 0
    else: 
        return ratSimTotal/simTotal
    
def svdEst(dataMat, user, simMeas, item):
    '''
    基於SVD的相似性計算
    '''
    n = shape(dataMat)[1]
    simTotal = 0.0; ratSimTotal = 0.0
    U,Sigma,VT = la.svd(dataMat)
    # 只考慮前四個元素,轉化成矩陣形式
    Sig4 = mat(eye(4)*Sigma[:4]) #arrange Sig4 into a diagonal matrix
    # 將資料降維轉化
    xformedItems = dataMat.T * U[:,:4] * Sig4.I  #create transformed items
    for j in range(n):
        userRating = dataMat[user,j]
        if userRating == 0 or j==item: continue
        similarity = simMeas(xformedItems[item,:].T,\
                             xformedItems[j,:].T)
        print 'the %d and %d similarity is: %f' % (item, j, similarity)
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0: return 0
    else: return ratSimTotal/simTotal


def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
    '''
    針對某一使用者,user,進行推薦。返回dictionary
    N           : 推薦物品個數
    simMeas     : 相似性計算方法
    estMethod   : 針對某一使用者的某一物品,計算相似性的程式,返回向量。
    '''
    unratedItems = nonzero(dataMat[user,:].A==0)[1]#find unrated items 
    if len(unratedItems) == 0: return 'you rated everything'
    itemScores = []
    for item in unratedItems:
        estimatedScore = estMethod(dataMat, user, simMeas, item)
        itemScores.append((item, estimatedScore))
    return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N]


def printMat(inMat, thresh=0.8):
    for i in range(32):
        for k in range(32):
            if float(inMat[i,k]) > thresh:
                print 1,
            else: print 0,
        print ''


def imgCompress(numSV=3, thresh=0.8):
    # 用svd方法進行影象壓縮
    myl = []
    for line in open('0_5.txt').readlines():
        newRow = []
        for i in range(32):
            newRow.append(int(line[i]))
        myl.append(newRow)
    myMat = mat(myl)
    print "****original matrix******"
    printMat(myMat, thresh)
    U,Sigma,VT = la.svd(myMat) # svd
    SigRecon = mat(zeros((numSV, numSV)))
    for k in range(numSV):#construct diagonal matrix from vector
        SigRecon[k,k] = Sigma[k]
    # reconstruct with the first numSV feature
    reconMat = U[:,:numSV]*SigRecon*VT[:numSV,:]
    print "****reconstructed matrix using %d singular values******" % numSV
    printMat(reconMat, thresh)
    


參考 機器學習實戰