1. 程式人生 > >機器學習算法原理解析——協同過濾推薦

機器學習算法原理解析——協同過濾推薦

3.6 新用戶 準確率 用戶偏好 tab tag 相同 pty cin

1. CF協同過濾推薦算法原理及應用

1.1 概述

什麽是協同過濾(Collaborative Filtering,簡稱CF)?

首先想一個簡單的問題,如果你現在想看個電影,但你不知道具體看哪部,你會怎麽做?

大部分的人會問問周圍id朋友,看看最近有什麽好看的電影推薦,而我們一般更傾向於從口味比較類似的朋友那裏得到推薦。這就是協同過濾的核心思想。

協同過濾算法又分為基於用戶的協同過濾算法和基於物品的協同過濾算法。

1.2 案例需求

如下數據是各用戶對各文檔的偏好:

用戶/文檔

文檔A

文檔B

文檔C

文檔D

用戶A

推薦?

推薦?

用戶B

用戶C

用戶D

現在需要基於上述數據,給A用戶推薦一篇文檔。

1.3 算法分析

1.3.1 基於用戶相似度的分析

直覺分析:“用戶A/B”都喜歡物品A和物品B,從而“用戶A/B”的口味最為相近

。因此,為“用戶A”推薦物品時可參考“用戶B”的偏好,從而推薦D。

技術分享圖片

這種就是基於用戶的協同過濾算法UserCF指導思想。

1.3.2 基於物品相似度的分析

直覺分析:物品組合(A,D)被同時偏好出現的次數最多,因而可以認為A/D兩件物品的相似度最高,從而,可以為選擇了A物品的用戶推薦D物品

技術分享圖片

這種就是基於物品的協同過濾算法ItemCF指導思想

1.4 算法要點

1.4.1 指導思想

這種過濾算法的有效性基礎在於:

1.用戶偏好具有相似性,即用戶可分類。這種分類的特征越明顯,推薦準確率越高。

2.物品之間具有相似性,即偏好某物品的人,都很可能也同時偏好另一件相似物品。

1.4.2 兩種CF算法適用的場景

什麽情況下使用哪種算法推薦效果會更好?

不同環境下這兩種理論的有效性也不同,應用時需做相應調整。

a. 如豆瓣上的文藝作品,用戶對其的偏好程度與用戶自身的品位關聯性較強;適合UserCF;

b. 而對於電子商務網站來說,商品之間的內在聯系對用戶的購買行為影響更為顯著。

1.5 算法實現

總的來說,要實現協同過濾,需要以下幾個步驟:

1.收集用戶偏好

2.找到相似的用戶或物品

3.計算推薦

1.5.1 收集用戶偏好

用戶有很多方式向系統提供自己的偏好信息,而且不同的應用也可能不大相同,下面舉例進行介紹:

用戶行為

類型

特征

作用

評分

顯式

整數量化值[0,n]

可以得到精確偏好

投票

顯式

布爾量化值0|1

可以得到精確偏好

轉發

顯式

布爾量化值0|1

可以得到精確偏好

保存書簽

顯式

布爾量化值0|1

可以得到精確偏好

標記書簽Tag

顯式

一些單詞

需要進一步分析得到偏好

評論

顯式

一些文字

需要進一步分析得到偏好

點擊流

隱式

一組點擊記錄

需要進一步分析得到偏好

頁面停留時間

隱式

一組時間信息

噪音偏大,不好利用

購買

隱式

布爾量化值0|1

可以得到精確偏好

1.5.2 原始偏好數據的預處理

  • 用戶行為識別/組合

在一般應用中,我們提取的用戶行為一般都多於一種,關於如何組合這些不同的用戶行為,比如,可以將用戶行為分為“查看”和“購買”等等,然後基於不同的行為,計算不同的用戶/物品相似度。

類似於當當網或者京東給出的“購買了該圖書的人還購買了...”,“查看了圖書的人還查看了...”

  • 喜好程度加權

根據不同行為反映用戶喜好的程度將它們進行加權,得到用戶對於物品的總體喜好。

一般來說,顯式的用戶反饋比隱式的權值大,但比較稀疏,畢竟進行顯示反饋的用戶是少數:同時相對於“查看”,“購買”行為反映用戶喜好的程度更大,但這也因應用而異。

  • 數據減噪和歸一化

① 減噪:用戶行為數據是用戶在使用應用過程中產生的,它可能存在大量的噪音和用戶的誤操作,我們可以通過經典的數據挖掘算法過濾掉行為數據中的噪音,這樣可以是我們的分析更加精確。

② 歸一化:如前面講到的,在計算用戶對物品的喜好程度時,可能需要對不同的行為數據進行加權。但可以想象,不同行為的數據取值可能相差很大,比如,用戶的查看數據必然比購買數據大的多,如何將各個行為的數據統一在一個相同的取值範圍中,從而使得加權求和得到的總體喜好更加精確,就需要我們進行歸一化處理。最簡單的歸一化處理,就是將各類數據除以此類中的最大值,以保證歸一化後的數據取值在[0,1]範圍中。

  • 形成用戶偏好矩陣

一般是二維矩陣,一維是用戶列表,另一維是物品列表,值是用戶對物品的偏好,一般是[0,1]或者[-1,1]的浮點數值。

1.5.3 找到相似用戶或物品

當已經對用戶行為進行分析得到用戶喜好後,我們可以根據用戶喜好計算相似用戶和物品,然後基於相似用戶或者物品進行推薦,這就是最典型的CF的兩個分支:基於用戶的CF和基於物品的CF。這兩種方法都需要計算相似度,下面我們先看看最基本的幾種計算相似度的方法。

1.5.4 相似度的計算

相似度的計算,現有的幾種基本方法都是基於向量(Vector)的,其實也就是計算兩個向量的距離,距離越近相似度越大。

在推薦的場景中,在用戶——物品偏好的二維矩陣中,我們可以將一個用戶對所有物品的偏好作為一個向量來計算用戶之間的相似度,或者將所有用戶對某個物品的偏好作為一個向量來計算物品之間的相似度。

CF的常用方法有三種,分別是歐式距離算法、皮爾遜相關系數法、余弦相似度法。

為了測試算法,給出以下簡單的用戶偏好數據矩陣:

行表示三名用戶,列表示三個品牌,對品牌的喜愛度按照1~5增加。

用戶

蘋果

小米

魅族

zhangsan

5

5

2

Lisi

3

5

4

wangwu

1

2

5

(1) 歐式距離法

就是計算每兩個點的距離,比如Nike和Sony的相似度。

數值越小,表示相似度越高。

def OsDistance(vector1, vector2):
     sqDiffVector = vector1 - vector2
     sqDiffVector = sqDiffVector ** 2
     sqDistances = sqDiffVector.sum()
     distance = sqDistances ** 0.5
     return distance

(2) 皮爾遜相關系數

兩個變量之間的相關系數越高,從一個變量去預測另一個變量的精確度就越高,這是因為相關系數越高,就意味著這兩個變量的共變部分越多,所以從其中一個變量的變化就可越多地獲知另一個變量的變化。如果兩個變量之間的相關系數為1或-1,那麽你完全可由變量X去獲知變量Y的值。

  • 當相關系數為0時,X和Y兩變量無關系。
  • 當X的值增大,Y也增大,正相關關系,相關系數在0.00與1.00之間
  • 當X的值減小,Y也減小,正相關關系,相關系數在0.00與1.00之間
  • 當X的值增大,Y減小,負相關關系,相關系數在-1.00與0.00之間
  • 當X的值減小,Y增大,負相關關系,相關系數在-1.00與0.00之間

相關系數的絕對值越大,相關性越強,相關系數越接近於1和-1,相關度越強,相關系數越接近於0,相關度越弱。

在python中用函數corrcoef實現,具體方法見參考資料

(3) 余弦相似度

通過測量兩個向量內積空間的夾角的余弦值來度量它們之間的相似度。0度角的余弦值是1,而其他任何角度的余弦值都不大於1;並且其最小值是-1。從而兩個向量之間的角度的余弦值確定兩個向量是否大致指向相同的方向。兩個向量有相同的指向時,余弦相似度的值為1;兩個向量夾角為90%時,余弦相似度的值為0;兩個向量指向完全相反的方向時,余弦相似度的值為-1。在比較過程中,向量的規模大小不予考慮,僅僅考慮到向量的指向方向。余弦相似度通常用於兩個向量的夾角小於90%之內,因此余弦相似度的值為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)

1.5.5 計算推薦

UserCF基於用戶相似度的推薦

計算推薦的過程其實就是KNN算法的計算過程

ItemCF基於物品相似度的推薦

算法思路:

1.構建物品的同現矩陣;

2.構建用戶對物品的評分矩陣;

3.通過矩陣計算得出腿腳結果;

推薦結果=用戶評分矩陣*同現矩陣

實質:計算各種物品組合的出現次數

技術分享圖片

1.6 協同過濾算法Python實戰

1.6.1 電影推薦需求

根據一個用戶對電影評分的數據集來實現基於用戶相似度的協同過濾算法推薦,相似度的算法采用皮爾遜相關系數法

數據樣例如下:

用戶ID:電影ID:評分:時間

1::1193::5::978300760
1::661::3::978302109
1::914::3::978301968
1::3408::4::978300275
1::2355::5::978824291
1::1197::3::978302268
1::1287::5::978302039
1::2804::5::978300719
1::594::4::978302268
1::919::4::978301368

1.6.2 算法實現

本案例使用的數據分析包圍pandas,Numpy和matplotlib

1.6.2.1 數據規整

首先將評分數據從ratings.dat中讀出到一個DataFrame裏:

>>>import pandas as pd
>>>from pandas import Series,DataFrame
>>>rnames = [user id, movie id, rating, timestamp]
>>>ratings = pd.read_table(rratings.dat, sep=::, header=None, names=rnames)
>>>ratings[:3]

   user_id  movie_id  rating  timestamp
0        1      1193       5  978300760
1        1       661       3  978302109
2        1       914       3  978301968
 
[3 rows x 4 columns]

ratings表中對我們有用的僅是user_id,movie_id和rating這三列,因此我們將這三列取出,放到一個以user為行,movie為列,rating為值的表data裏面。

>>> data = ratings.pivot(index=user_id,columns=movie_id,values=rating)  #形成一個透視表
>>> data[:5]

技術分享圖片

可以看到這個表相當得稀疏,填充率大約只有5%,接下來要實現推薦的第一步是計算user之間的相關系數。

1.6.2.2 相關度測算

DataFrame對象有一個很親切的方法:

.corr(method=‘person‘, min_periods=1)方法,可以對所有列互相計算相關系數。

其中:

method 默認為皮爾遜相關系數,min_periods參數,這個參數的作用是設定計算相關系數時的最小樣本量,低於此值的一對列將不進行運算。這個值的取舍關系到相關系數計算的準確性,因此有必要先來確定一下這個參數。

1.6.2.3 min_periods參數測定

測定這樣一個參數的基本方法:

  • 統計在min_periods取不同值時,相關系數的標準差大小,越小越好

但同時又要考慮到,我們的樣本空間十分稀疏,min_periods定得太高會導致出來的結果集太小,所以只能選定一個折中的值。

這裏我們測定評分系統標準差的方法為:

  • 在data中挑選一對重疊評分最多的用戶,用他們之間的相關系數的標準差去對整體標準差做點評估。

在此前提下對這一對用戶在不同樣本量下的相關系數進行統計,觀察其標準差變化。

首先,要找出重疊評分最多的一對用戶。我們新建一個以user為行列的方針foo,然後挨個填充不同用戶間重疊評分的個數:

>>> foo = DataFrame(np.empty((len(data.index),len(data.index)),dtype=int),index=data.index,columns=data.index)
#print(empt.shape): (6040, 6040)
>>> for i in foo.index:
        for j in foo.columns:
            foo.ix[i,j] = data.ix[i][data.ix[j].notnull()].dropna().count()

這段代碼特別費時間,因為最後一行語句要執行4000*4000=1600萬遍;

找到的最大值所對應的行列分別為424和4169,這兩位用戶之間的重疊評分數為998:

>>> for i in foo.index:
        foo.ix[i,i]=0   #先把對角線的值設為 0
>>> ser = Series(np.zeros(len(foo.index)))
>>> for i in foo.index:
        ser[i]=foo[i].max()   #計算每行中的最大值
 
>>> ser.idxmax()   #返回ser的最大值所在的行號
4169
 
>>> ser[4169]    #取得最大值
998
 
>>> foo[foo==998][4169].dropna()   #取得另一個 user_id
424     4169
Name: user_id, dtype: float64

把424和4169的評分單獨拿出來,放到一個名為test的表裏,另外計算了一下這兩個用戶之間的相關系數為0.456,還算不錯,另外通過柱狀圖了解一下他們的評分分布情況:

>>> data.ix[4169].corr(data.ix[424])
0.45663851303413217
>>> test = data.reindex([424,4169],columns=data.ix[4169][data.ix[424].notnull()].dropna().index)
>>> test
movie_id  2   6   10  11  12  17 ...
424        4   4   4   4   1   5 ...
4169       3   4   4   4   2   5 ...
 
>>> test.ix[424].value_counts(sort=False).plot(kind=bar)
>>> test.ix[4169].value_counts(sort=False).plot(kind=bar)

技術分享圖片

技術分享圖片

對這倆用戶的相關系數統計,我們分別隨機抽取20、50、100、200、500和998個樣本值,各抽20次。並統計結果:

>>> periods_test = DataFrame(np.zeros((20,7)),columns=[10,20,50,100,200,500,998])
>>> for i in periods_test.index:
        for j in periods_test.columns:
            sample = test.reindex(columns=np.random.permutation(test.columns)[:j])
            periods_test.ix[i,j] = sample.iloc[0].corr(sample.iloc[1])
 
 
>>> periods_test[:5]
        10        20        50        100       200       500       998
0 -0.306719  0.709073  0.504374  0.376921  0.477140  0.426938  0.456639
1  0.386658  0.607569  0.434761  0.471930  0.437222  0.430765  0.456639
2  0.507415  0.585808  0.440619  0.634782  0.490574  0.436799  0.456639
3  0.628112  0.628281  0.452331  0.380073  0.472045  0.444222  0.456639
4  0.792533  0.641503  0.444989  0.499253  0.426420  0.441292  0.456639
 
[5 rows x 7 columns]
>>> periods_test.describe()
             10         20         50         100        200        500  #998略
count  20.000000  20.000000  20.000000  20.000000  20.000000  20.000000  
mean    0.346810   0.464726   0.458866   0.450155   0.467559   0.452448  
std     0.398553   0.181743   0.103820   0.093663   0.036439   0.029758  
min    -0.444302   0.087370   0.192391   0.242112   0.412291   0.399875  
25%     0.174531   0.320941   0.434744   0.375643   0.439228   0.435290  
50%     0.487157   0.525217   0.476653   0.468850   0.472562   0.443772  
75%     0.638685   0.616643   0.519827   0.500825   0.487389   0.465787  
max     0.850963   0.709073   0.592040   0.634782   0.546001   0.513486  
 
[8 rows x 7 columns]

從std這一行來看,理想的min_periods參數值應當為200左右(標準差和均值、極值最接近)。

1.6.2.4 算法檢驗

為了確認在min_periods=200下本推薦算法的靠譜程度,最好還是先做個檢驗。

具體方法:在評價數大於200的用戶中隨機抽取1000位用戶,每人隨機提取一個評價,另存到一個數組裏,並在數據表中刪除這個評價。然後基於閹割過的數據表計算被提取出的1000個評分的期望值,最後與真實評價數組進行相關性比較,看結果如何。

>>> check_size = 1000
>>> check = {}
>>> check_data = data.copy()   #復制一份 data 用於檢驗,以免篡改原數據
>>> check_data = check_data.ix[check_data.count(axis=1)>200]  #濾除評價數小於200的用戶
>>> for user in np.random.permutation(check_data.index):
        movie = np.random.permutation(check_data.ix[user].dropna().index)[0]
        check[(user,movie)] = check_data.ix[user,movie]
        check_data.ix[user,movie] = np.nan
        check_size -= 1
        if not check_size:
            break
 
 
>>> corr = check_data.T.corr(min_periods=200)
>>> corr_clean = corr.dropna(how=all)
>>> corr_clean = corr_clean.dropna(axis=1,how=all)  #刪除全空的行和列
>>> check_ser = Series(check)   #這裏是被提取出來的 1000 個真實評分
>>> check_ser[:5]
(15, 593)     4
(23, 555)     3
(33, 3363)    4
(36, 2355)    5
(53, 3605)    4
dtype: float64

接下來要基於corr_clean給check_ser中的1000個用戶-影片對計算評分期望。

計算方法:對與用戶相關系數大於0.1的其他評分進行加權平均,權值為相關系數

>>> result = Series(np.nan,index=check_ser.index)
>>> for user,movie in result.index:   #這個循環看著很亂,實際內容就是加權平均而已
        prediction = []
        if user in corr_clean.index:
            corr_set = corr_clean[user][corr_clean[user]>0.1].dropna()   #僅限大於 0.1 的用戶
        else:continue
        for other in corr_set.index:
            if  not np.isnan(data.ix[other,movie]) and other != user:#註意bool(np.nan)==True
                prediction.append((data.ix[other,movie],corr_set[other]))
        if prediction:
            result[(user,movie)] = sum([value*weight for value,weight in prediction])/sum([pair[1] for pair in prediction])
 
 
>>> result.dropna(inplace=True)
>>> len(result)#隨機抽取的 1000 個用戶中也有被 min_periods=200 刷掉的
862
>>> result[:5]
(23, 555)     3.967617
(33, 3363)    4.073205
(36, 2355)    3.903497
(53, 3605)    2.948003
(62, 1488)    2.606582
dtype: float64
>>> result.corr(check_ser.reindex(result.index))
0.436227437429696
>>> (result-check_ser.reindex(result.index)).abs().describe()#推薦期望與實際評價之差的絕對值
count    862.000000
mean       0.785337
std        0.605865
min        0.000000
25%        0.290384
50%        0.686033
75%        1.132256
max        3.629720
dtype: float64

862的樣本量能達到0.436的相關系數,應該說結果還不錯。如果一開始沒有過濾掉評價數小於200的用戶的話,那麽首先在計算corr時會明顯感覺時間變長,其次result中的樣本量會很小,大約200+個。但因為樣本量變小的緣故,相關系數可以提升到0.5~0.6。

另外從期望與實際評價的差的絕對值的統計量上看,數據也比較理想。

1.6.2.5 實現推薦

在上面的校驗,尤其是平均加權的部分做完後,推薦的實現就沒有什麽新東西了。

首先在原始未閹割的data數據上重做一份corr表:

>>> corr = data.T.corr(min_periods=200)
>>> corr_clean = corr.dropna(how=all)
>>> corr_clean = corr_clean.dropna(axis=1,how=all)

我們在corr_clean中隨機挑選一位用戶為他做一個推薦列表:

>>> lucky = np.random.permutation(corr_clean.index)[0]
>>> gift = data.ix[lucky]
>>> gift = gift[gift.isnull()]    #現在 gift 是一個全空的序列

最後的任務就是填充這個gift:

>>> corr_lucky = corr_clean[lucky].drop(lucky)#lucky 與其他用戶的相關系數 Series,不包含 lucky 自身
>>> corr_lucky = corr_lucky[corr_lucky>0.1].dropna()   #篩選相關系數大於 0.1 的用戶
>>> for movie in gift.index:   #遍歷所有lucky沒看過的電影
        prediction = []
        for other in corr_lucky.index:  #遍歷所有與lucky 相關系數大於 0.1 的用戶
            if not np.isnan(data.ix[other,movie]):
                prediction.append((data.ix[other,movie],corr_clean[lucky][other]))
        if prediction:
            gift[movie] = sum([value*weight for value,weight in prediction])/sum([pair[1] for pair in prediction])
 
 
>>> gift.dropna().order(ascending=False)   #將 gift 的非空元素按降序排列
movie_id
3245        5.000000
2930        5.000000
2830        5.000000
2569        5.000000
1795        5.000000
981         5.000000
696         5.000000
682         5.000000
666         5.000000
572         5.000000
1420        5.000000
3338        4.845331
669         4.660464
214         4.655798
3410        4.624088
...
2833        1
2777        1
2039        1
1773        1
1720        1
1692        1
1538        1
1430        1
1311        1
1164        1
843         1
660         1
634         1
591         1
56          1
Name: 3945, Length: 2991, dtype: float64

1.7 CF協同過濾算法補充

1.7.1 計算距離的數學公式

歐幾裏德距離(Euclidean Distance)

最初用於計算歐幾裏德空間中兩個點的距離,假設x,y是n維空間的兩個點,它們之間的歐幾裏德距離是:

技術分享圖片

可以看出,當n=2時,歐幾裏德距離就是平面上兩個點的距離。

當用歐幾裏德距離表示相似度,一般采用以下公式進行轉換:距離越小,相似度越大

技術分享圖片

皮爾遜相關系數(Pearson Correlation Coefficient)

皮爾遜相關系數一般用於計算兩個定距變量間聯系的緊密程度,它的取值在[-1, +1]之間。

技術分享圖片

sx,sy是x和y的樣品標準偏差。

Cosine相似度(Cosine Similarity)

Cosine相似度被廣泛應用於計算文檔數據的相似度:

技術分享圖片

Tanimoto系數(Tanimoto Coefficient)

Tanimoto系數也稱為Jaccard系數,是Cosine相似度的擴展,也多用於計算文檔數據的相似度:

技術分享圖片

相似鄰居的計算

介紹完相似度的計算方法,下面我們看看如何根據相似度找到用戶——物品的鄰居,常用的挑選鄰居的原則可以分為兩類:

1) 固定數量的鄰居:K-neighborhoods或者Fix-size neighborhoods

不論鄰居的“遠近”,只取最近的K個,作為其鄰居。

如下圖中的A,假設要計算點1的5-鄰居,那麽根據點之間的距離, 我們取最近的5個點,分別是點2,點3,點4,點7和點5。但很明顯我們可以看出,這種方法對於孤立點的計算效果不好,因為要取固定個數的鄰居,當它附近沒有足夠多比較相似的點,就被迫取一些不太相似的點作為鄰居,這樣就影響了鄰居相似的程度,比如下圖中,點1和點5其實並不是很相似。

2) 基於相似度門檻的鄰居:Threshold-based neighborhoods

與計算固定數量的鄰居的原則不同,基於相似度門檻的鄰居計算是對鄰居的遠近進行最大值的限制,落在以當前點為中心,距離為K的區域中的所有點都作為當前點的鄰居,這種方法計算得到的鄰居個數不確定,但相似度不會出現較大的誤差。

如下圖中的B,從點1出發,計算相似度在K內的鄰居,得到點2,點3,點4和點7,這種方法計算出的鄰居的相似度程度比前一種優,尤其是對孤立點的處理。

圖:相似度鄰居計算示意圖

技術分享圖片

1.7.2 協同過濾算法常見問題

雖然協同過濾是一種比較省事的推薦方法,但在某些場合下並不如利用元信息推薦好用。協同過濾會遇到的兩個常見問題是

1) 稀疏性問題——因用戶做出評價過少,導致算出的相關系數不準確;

2)冷啟動問題——因物品獲得評價過少,導致無“權”進入推薦列表中;

都是樣本量太少導致的(上例中也使用了至少200的有效重疊評價數)。

因此在對於新用戶和新物品進行推薦時,使用一些更一般性的方法效果可能會更好。比如:

  • 給新用戶推薦更多平均得分超高的電影;
  • 把新電影推薦給喜歡類似電影(如具有相同導演或演員)的人。

後面這種做法需要維護一個物品分類表,這個表既可以是基於物品元信息劃分的,也可是通過聚類得到的。

機器學習算法原理解析——協同過濾推薦