第14章 利用SVD簡(jiǎn)化數(shù)據(jù)
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script>

SVD 概述
奇異值分解(SVD, Singular Value Decomposition):
提取信息的一種方法,可以把 SVD 看成是從噪聲數(shù)據(jù)中抽取相關(guān)特征。從生物信息學(xué)到金融學(xué),SVD 是提取信息的強(qiáng)大工具。
SVD 場(chǎng)景
信息檢索-隱形語(yǔ)義檢索(Lstent Semantic Indexing, LSI)或 隱形語(yǔ)義分析(Latent Semantic Analysis, LSA)
隱性語(yǔ)義索引:矩陣 = 文檔 + 詞語(yǔ)
- 是最早的 SVD 應(yīng)用之一,我們稱利用 SVD 的方法為隱性語(yǔ)義索引(LSI)或隱性語(yǔ)義分析(LSA)。

推薦系統(tǒng)
- 利用 SVD 從數(shù)據(jù)中構(gòu)建一個(gè)主題空間。
- 再在該空間下計(jì)算其相似度。(從高維-低維空間的轉(zhuǎn)化,在低維空間來(lái)計(jì)算相似度,SVD 提升了推薦系統(tǒng)的效率。)

- 上圖右邊標(biāo)注的為一組共同特征,表示美式 BBQ 空間;另一組在上圖右邊未標(biāo)注的為日式食品 空間。
圖像壓縮
例如:32*32=1024 => 32*2+2*1+32*2=130(2*1表示去掉了除對(duì)角線的0), 幾乎獲得了10倍的壓縮比。

SVD 原理
SVD 工作原理
矩陣分解
- 矩陣分解是將數(shù)據(jù)矩陣分解為多個(gè)獨(dú)立部分的過(guò)程。
- 矩陣分解可以將原始矩陣表示成新的易于處理的形式,這種新形式是兩個(gè)或多個(gè)矩陣的乘積。(類似代數(shù)中的因數(shù)分解)
- 舉例:如何將12分解成兩個(gè)數(shù)的乘積?(1,12)、(2,6)、(3,4)都是合理的答案。
SVD 是矩陣分解的一種類型,也是矩陣分解最常見(jiàn)的技術(shù)
- SVD 將原始的數(shù)據(jù)集矩陣 Data 分解成三個(gè)矩陣 U、∑、V
- 舉例:如果原始矩陣 \(Data_{m*n}\) 是m行n列,
- \(U_{m*n}\) 表示m行n列
- \(∑_{m*k}\) 表示m行k列
- \(V_{k*n}\) 表示k行n列。
\(Data_{m*n} = U_{m*k} * ∑{k*k} * V{k*n}\)

具體的案例:(大家可以試著推導(dǎo)一下:https://wenku.baidu.com/view/b7641217866fb84ae45c8d17.html )

- 上述分解中會(huì)構(gòu)建出一個(gè)矩陣∑,該矩陣只有對(duì)角元素,其他元素均為0(近似于0)。另一個(gè)慣例就是,∑的對(duì)角元素是從大到小排列的。這些對(duì)角元素稱為奇異值。
- 奇異值與特征值(PCA 數(shù)據(jù)中重要特征)是有關(guān)系的。這里的奇異值就是矩陣 \(Data * Data^T\) 特征值的平方根。
- 普遍的事實(shí):在某個(gè)奇異值的數(shù)目(r 個(gè)=>奇異值的平方和累加到總值的90%以上)之后,其他的奇異值都置為0(近似于0)。這意味著數(shù)據(jù)集中僅有 r 個(gè)重要特征,而其余特征則都是噪聲或冗余特征。
SVD 算法特點(diǎn)
優(yōu)點(diǎn):簡(jiǎn)化數(shù)據(jù),去除噪聲,優(yōu)化算法的結(jié)果
缺點(diǎn):數(shù)據(jù)的轉(zhuǎn)換可能難以理解
使用的數(shù)據(jù)類型:數(shù)值型數(shù)據(jù)
推薦系統(tǒng)
推薦系統(tǒng) 概述
推薦系統(tǒng)是利用電子商務(wù)網(wǎng)站向客戶提供商品信息和建議,幫助用戶決定應(yīng)該購(gòu)買什么產(chǎn)品,模擬銷售人員幫助客戶完成購(gòu)買過(guò)程。
推薦系統(tǒng) 場(chǎng)景
- Amazon 會(huì)根據(jù)顧客的購(gòu)買歷史向他們推薦物品
- Netflix 會(huì)向其用戶推薦電影
- 新聞網(wǎng)站會(huì)對(duì)用戶推薦新聞?lì)l道
推薦系統(tǒng) 要點(diǎn)
基于協(xié)同過(guò)濾(collaborative filtering) 的推薦引擎
- 利用Python 實(shí)現(xiàn) SVD(Numpy 有一個(gè)稱為 linalg 的線性代數(shù)工具箱)
- 協(xié)同過(guò)濾:是通過(guò)將用戶和其他用戶的數(shù)據(jù)進(jìn)行對(duì)比來(lái)實(shí)現(xiàn)推薦的。
- 當(dāng)知道了兩個(gè)用戶或兩個(gè)物品之間的相似度,我們就可以利用已有的數(shù)據(jù)來(lái)預(yù)測(cè)未知用戶的喜好。
基于物品的相似度和基于用戶的相似度:物品比較少則選擇物品相似度,用戶比較少則選擇用戶相似度。【矩陣還是小一點(diǎn)好計(jì)算】
- 基于物品的相似度:計(jì)算物品之間的距離?!竞臅r(shí)會(huì)隨物品數(shù)量的增加而增加】
- 由于物品A和物品C 相似度(相關(guān)度)很高,所以給買A的人推薦C。

- 基于用戶的相似度:計(jì)算用戶之間的距離。【耗時(shí)會(huì)隨用戶數(shù)量的增加而增加】
- 由于用戶A和用戶C 相似度(相關(guān)度)很高,所以A和C是興趣相投的人,對(duì)于C買的物品就會(huì)推薦給A。

相似度計(jì)算
- inA, inB 對(duì)應(yīng)的是 列向量
- 歐氏距離:指在m維空間中兩個(gè)點(diǎn)之間的真實(shí)距離,或者向量的自然長(zhǎng)度(即改點(diǎn)到原點(diǎn)的距離)。二維或三維中的歐氏距離就是兩點(diǎn)之間的實(shí)際距離。
- 相似度= 1/(1+歐式距離)
相似度= 1.0/(1.0 + la.norm(inA - inB))- 物品對(duì)越相似,它們的相似度值就越大。
- 皮爾遜相關(guān)系數(shù):度量的是兩個(gè)向量之間的相似度。
- 相似度= 0.5 + 0.5*corrcoef() 【皮爾遜相關(guān)系數(shù)的取值范圍從 -1 到 +1,通過(guò)函數(shù)0.5 + 0.5*corrcoef()這個(gè)函數(shù)計(jì)算,把值歸一化到0到1之間】
相似度= 0.5 + 0.5 * corrcoef(inA, inB, rowvar = 0)[0][1]- 相對(duì)歐氏距離的優(yōu)勢(shì):它對(duì)用戶評(píng)級(jí)的量級(jí)并不敏感。
- 余弦相似度:計(jì)算的是兩個(gè)向量夾角的余弦值。
- 余弦值 = (A·B)/(||A||·||B||) 【余弦值的取值范圍也在-1到+1之間】
- 相似度= 0.5 + 0.5*余弦值
相似度= 0.5 + 0.5*( float(inA.T*inB) / la.norm(inA)*la.norm(inB))- 如果夾角為90度,則相似度為0;如果兩個(gè)向量的方向相同,則相似度為1.0。
推薦系統(tǒng)的評(píng)價(jià)
- 采用交叉測(cè)試的方法。【拆分?jǐn)?shù)據(jù)為訓(xùn)練集和測(cè)試集】
- 推薦引擎評(píng)價(jià)的指標(biāo): 最小均方根誤差(Root mean squared error, RMSE),也稱標(biāo)準(zhǔn)誤差(Standard error),就是計(jì)算均方誤差的平均值然后取其平方根。
- 如果RMSE=1, 表示相差1個(gè)星級(jí);如果RMSE=2.5, 表示相差2.5個(gè)星級(jí)。
推薦系統(tǒng) 原理
- 推薦系統(tǒng)的工作過(guò)程:給定一個(gè)用戶,系統(tǒng)會(huì)為此用戶返回N個(gè)最好的推薦菜。
- 實(shí)現(xiàn)流程大致如下:
- 尋找用戶沒(méi)有評(píng)級(jí)的菜肴,即在用戶-物品矩陣中的0值。
- 在用戶沒(méi)有評(píng)級(jí)的所有物品中,對(duì)每個(gè)物品預(yù)計(jì)一個(gè)可能的評(píng)級(jí)分?jǐn)?shù)。這就是說(shuō):我們認(rèn)為用戶可能會(huì)對(duì)物品的打分(這就是相似度計(jì)算的初衷)。
- 對(duì)這些物品的評(píng)分從高到低進(jìn)行排序,返回前N個(gè)物品。
項(xiàng)目案例: 餐館菜肴推薦系統(tǒng)
項(xiàng)目概述
假如一個(gè)人在家決定外出吃飯,但是他并不知道該到哪兒去吃飯,該點(diǎn)什么菜。推薦系統(tǒng)可以幫他做到這兩點(diǎn)。
開發(fā)流程
收集 并 準(zhǔn)備數(shù)據(jù)

def loadExData3():
# 利用SVD提高推薦效果,菜肴矩陣
"""
行:代表人
列:代表菜肴名詞
值:代表人對(duì)菜肴的評(píng)分,0表示未評(píng)分
"""
return[[2, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 0],
[3, 3, 4, 0, 3, 0, 0, 2, 2, 0, 0],
[5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0],
[4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5],
[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4],
[0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0],
[0, 0, 0, 3, 0, 0, 0, 0, 4, 5, 0],
[1, 1, 2, 1, 1, 2, 1, 0, 4, 5, 0]]
分析數(shù)據(jù): 這里不做過(guò)多的討論(當(dāng)然此處可以對(duì)比不同距離之間的差別)
訓(xùn)練算法: 通過(guò)調(diào)用 recommend() 函數(shù)進(jìn)行推薦
recommend() 會(huì)調(diào)用 基于物品相似度 或者是 基于SVD,得到推薦的物品評(píng)分。
- 1.基于物品相似度


# 基于物品相似度的推薦引擎
def standEst(dataMat, user, simMeas, item):
"""standEst(計(jì)算某用戶未評(píng)分物品中,以對(duì)該物品和其他物品評(píng)分的用戶的物品相似度,然后進(jìn)行綜合評(píng)分)
Args:
dataMat 訓(xùn)練數(shù)據(jù)集
user 用戶編號(hào)
simMeas 相似度計(jì)算方法
item 未評(píng)分的物品編號(hào)
Returns:
ratSimTotal/simTotal 評(píng)分(0~5之間的值)
"""
# 得到數(shù)據(jù)集中的物品數(shù)目
n = shape(dataMat)[1]
# 初始化兩個(gè)評(píng)分值
simTotal = 0.0
ratSimTotal = 0.0
# 遍歷行中的每個(gè)物品(對(duì)用戶評(píng)過(guò)分的物品進(jìn)行遍歷,并將它與其他物品進(jìn)行比較)
for j in range(n):
userRating = dataMat[user, j]
# 如果某個(gè)物品的評(píng)分值為0,則跳過(guò)這個(gè)物品
if userRating == 0:
continue
# 尋找兩個(gè)用戶都評(píng)級(jí)的物品
# 變量 overLap 給出的是兩個(gè)物品當(dāng)中已經(jīng)被評(píng)分的那個(gè)元素的索引ID
# logical_and 計(jì)算x1和x2元素的真值。
overLap = nonzero(logical_and(dataMat[:, item].A > 0, dataMat[:, j].A > 0))[0]
# 如果相似度為0,則兩著沒(méi)有任何重合元素,終止本次循環(huán)
if len(overLap) == 0:
similarity = 0
# 如果存在重合的物品,則基于這些重合物重新計(jì)算相似度。
else:
similarity = simMeas(dataMat[overLap, item], dataMat[overLap, j])
# print 'the %d and %d similarity is : %f'(iten,j,similarity)
# 相似度會(huì)不斷累加,每次計(jì)算時(shí)還考慮相似度和當(dāng)前用戶評(píng)分的乘積
# similarity 用戶相似度, userRating 用戶評(píng)分
simTotal += similarity
ratSimTotal += similarity * userRating
if simTotal == 0:
return 0
# 通過(guò)除以所有的評(píng)分總和,對(duì)上述相似度評(píng)分的乘積進(jìn)行歸一化,使得最后評(píng)分在0~5之間,這些評(píng)分用來(lái)對(duì)預(yù)測(cè)值進(jìn)行排序
else:
return ratSimTotal/simTotal
- 2.基于SVD(參考地址:http://www.codeweblog.com/svd-%E7%AC%94%E8%AE%B0/)

# 基于SVD的評(píng)分估計(jì)
# 在recommend() 中,這個(gè)函數(shù)用于替換對(duì)standEst()的調(diào)用,該函數(shù)對(duì)給定用戶給定物品構(gòu)建了一個(gè)評(píng)分估計(jì)值
def svdEst(dataMat, user, simMeas, item):
"""svdEst(計(jì)算某用戶未評(píng)分物品中,以對(duì)該物品和其他物品評(píng)分的用戶的物品相似度,然后進(jìn)行綜合評(píng)分)
Args:
dataMat 訓(xùn)練數(shù)據(jù)集
user 用戶編號(hào)
simMeas 相似度計(jì)算方法
item 未評(píng)分的物品編號(hào)
Returns:
ratSimTotal/simTotal 評(píng)分(0~5之間的值)
"""
# 物品數(shù)目
n = shape(dataMat)[1]
# 對(duì)數(shù)據(jù)集進(jìn)行SVD分解
simTotal = 0.0
ratSimTotal = 0.0
# 奇異值分解
# 在SVD分解之后,我們只利用包含了90%能量值的奇異值,這些奇異值會(huì)以NumPy數(shù)組的形式得以保存
U, Sigma, VT = la.svd(dataMat)
# # 分析 Sigma 的長(zhǎng)度取值
# analyse_data(Sigma, 20)
# 如果要進(jìn)行矩陣運(yùn)算,就必須要用這些奇異值構(gòu)建出一個(gè)對(duì)角矩陣
Sig4 = mat(eye(4) * Sigma[: 4])
# 利用U矩陣將物品轉(zhuǎn)換到低維空間中,構(gòu)建轉(zhuǎn)換后的物品(物品+4個(gè)主要的特征)
xformedItems = dataMat.T * U[:, :4] * Sig4.I
# 對(duì)于給定的用戶,for循環(huán)在用戶對(duì)應(yīng)行的元素上進(jìn)行遍歷,
# 這和standEst()函數(shù)中的for循環(huán)的目的一樣,只不過(guò)這里的相似度計(jì)算時(shí)在低維空間下進(jìn)行的。
for j in range(n):
userRating = dataMat[user, j]
if userRating == 0 or j == item:
continue
# 相似度的計(jì)算方法也會(huì)作為一個(gè)參數(shù)傳遞給該函數(shù)
similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T)
# for 循環(huán)中加入了一條print語(yǔ)句,以便了解相似度計(jì)算的進(jìn)展情況。如果覺(jué)得累贅,可以去掉
print 'the %d and %d similarity is: %f' % (item, j, similarity)
# 對(duì)相似度不斷累加求和
simTotal += similarity
# 對(duì)相似度及對(duì)應(yīng)評(píng)分值的乘積求和
ratSimTotal += similarity * userRating
if simTotal == 0:
return 0
else:
# 計(jì)算估計(jì)評(píng)分
return ratSimTotal/simTotal
排序獲取最后的推薦結(jié)果
# recommend()函數(shù),就是推薦引擎,它默認(rèn)調(diào)用standEst()函數(shù),產(chǎn)生了最高的N個(gè)推薦結(jié)果。
# 如果不指定N的大小,則默認(rèn)值為3。該函數(shù)另外的參數(shù)還包括相似度計(jì)算方法和估計(jì)方法
def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
# 尋找未評(píng)級(jí)的物品
# 對(duì)給定的用戶建立一個(gè)未評(píng)分的物品列表
unratedItems = nonzero(dataMat[user, :].A == 0)[1]
# 如果不存在未評(píng)分物品,那么就退出函數(shù)
if len(unratedItems) == 0:
return 'you rated everything'
# 物品的編號(hào)和評(píng)分值
itemScores = []
# 在未評(píng)分物品上進(jìn)行循環(huán)
for item in unratedItems:
estimatedScore = estMethod(dataMat, user, simMeas, item)
# 尋找前N個(gè)未評(píng)級(jí)物品,調(diào)用standEst()來(lái)產(chǎn)生該物品的預(yù)測(cè)得分,該物品的編號(hào)和估計(jì)值會(huì)放在一個(gè)元素列表itemScores中
itemScores.append((item, estimatedScore))
# 按照估計(jì)得分,對(duì)該列表進(jìn)行排序并返回。列表逆排序,第一個(gè)值就是最大值
return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[: N]
測(cè)試 和 項(xiàng)目調(diào)用,可直接參考我們的代碼
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/14.SVD/svdRecommend.py
要點(diǎn)補(bǔ)充
基于內(nèi)容(content-based)的推薦
- 通過(guò)各種標(biāo)簽來(lái)標(biāo)記菜肴
- 將這些屬性作為相似度計(jì)算所需要的數(shù)據(jù)
- 這就是:基于內(nèi)容的推薦。
構(gòu)建推薦引擎面臨的挑戰(zhàn)
問(wèn)題
- 1)在大規(guī)模的數(shù)據(jù)集上,SVD分解會(huì)降低程序的速度
- 2)存在其他很多規(guī)模擴(kuò)展性的挑戰(zhàn)性問(wèn)題,比如矩陣的表示方法和計(jì)算相似度得分消耗資源。
- 3)如何在缺乏數(shù)據(jù)時(shí)給出好的推薦-稱為冷啟動(dòng)【簡(jiǎn)單說(shuō):用戶不會(huì)喜歡一個(gè)無(wú)效的物品,而用戶不喜歡的物品又無(wú)效】
建議
- 1)在大型系統(tǒng)中,SVD分解(可以在程序調(diào)入時(shí)運(yùn)行一次)每天運(yùn)行一次或者其頻率更低,并且還要離線運(yùn)行。
- 2)在實(shí)際中,另一個(gè)普遍的做法就是離線計(jì)算并保存相似度得分。(物品相似度可能被用戶重復(fù)的調(diào)用)
- 3)冷啟動(dòng)問(wèn)題,解決方案就是將推薦看成是搜索問(wèn)題,通過(guò)各種標(biāo)簽/屬性特征進(jìn)行
基于內(nèi)容的推薦。
項(xiàng)目案例: 基于 SVD 的圖像壓縮
收集 并 準(zhǔn)備數(shù)據(jù)
將文本數(shù)據(jù)轉(zhuǎn)化為矩陣
# 加載并轉(zhuǎn)換數(shù)據(jù)
def imgLoadData(filename):
myl = []
# 打開文本文件,并從文件以數(shù)組方式讀入字符
for line in open(filename).readlines():
newRow = []
for i in range(32):
newRow.append(int(line[i]))
myl.append(newRow)
# 矩陣調(diào)入后,就可以在屏幕上輸出該矩陣
myMat = mat(myl)
return myMat
分析數(shù)據(jù): 分析 Sigma 的長(zhǎng)度個(gè)數(shù)
通常保留矩陣 80% ~ 90% 的能量,就可以得到重要的特征并去除噪聲。
def analyse_data(Sigma, loopNum=20):
"""analyse_data(分析 Sigma 的長(zhǎng)度取值)
Args:
Sigma Sigma的值
loopNum 循環(huán)次數(shù)
"""
# 總方差的集合(總能量值)
Sig2 = Sigma**2
SigmaSum = sum(Sig2)
for i in range(loopNum):
SigmaI = sum(Sig2[:i+1])
'''
根據(jù)自己的業(yè)務(wù)情況,就行處理,設(shè)置對(duì)應(yīng)的 Singma 次數(shù)
通常保留矩陣 80% ~ 90% 的能量,就可以得到重要的特征并取出噪聲。
'''
print '主成分:%s, 方差占比:%s%%' % (format(i+1, '2.0f'), format(SigmaI/SigmaSum*100, '4.2f'))
使用算法: 對(duì)比使用 SVD 前后的數(shù)據(jù)差異對(duì)比,對(duì)于存儲(chǔ)大家可以試著寫寫
例如:32*32=1024 => 32*2+2*1+32*2=130(2*1表示去掉了除對(duì)角線的0), 幾乎獲得了10倍的壓縮比。
# 打印矩陣
def printMat(inMat, thresh=0.8):
# 由于矩陣保護(hù)了浮點(diǎn)數(shù),因此定義淺色和深色,遍歷所有矩陣元素,當(dāng)元素大于閥值時(shí)打印1,否則打印0
for i in range(32):
for k in range(32):
if float(inMat[i, k]) > thresh:
print 1,
else:
print 0,
print ''
# 實(shí)現(xiàn)圖像壓縮,允許基于任意給定的奇異值數(shù)目來(lái)重構(gòu)圖像
def imgCompress(numSV=3, thresh=0.8):
"""imgCompress( )
Args:
numSV Sigma長(zhǎng)度
thresh 判斷的閾值
"""
# 構(gòu)建一個(gè)列表
myMat = imgLoadData('input/14.SVD/0_5.txt')
print "****original matrix****"
# 對(duì)原始圖像進(jìn)行SVD分解并重構(gòu)圖像e
printMat(myMat, thresh)
# 通過(guò)Sigma 重新構(gòu)成SigRecom來(lái)實(shí)現(xiàn)
# Sigma是一個(gè)對(duì)角矩陣,因此需要建立一個(gè)全0矩陣,然后將前面的那些奇異值填充到對(duì)角線上。
U, Sigma, VT = la.svd(myMat)
# SigRecon = mat(zeros((numSV, numSV)))
# for k in range(numSV):
# SigRecon[k, k] = Sigma[k]
# 分析插入的 Sigma 長(zhǎng)度
analyse_data(Sigma, 20)
SigRecon = mat(eye(numSV) * Sigma[: numSV])
reconMat = U[:, :numSV] * SigRecon * VT[:numSV, :]
print "****reconstructed matrix using %d singular values *****" % numSV
printMat(reconMat, thresh)
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/14.SVD/svdRecommend.py
- 作者:片刻 1988
- GitHub地址: https://github.com/apachecn/MachineLearning
- 版權(quán)聲明:歡迎轉(zhuǎn)載學(xué)習(xí) => 請(qǐng)標(biāo)注信息來(lái)源于 ApacheCN