電影推薦系統---協同過濾算法(SVD,NMF)


SVD

參考 https://www.zybuluo.com/rianusr/note/1195225

1 推薦系統概述

01推薦系統概述.png-26.4kB

 

1.1 項目安排

02項目安排.png-42.9kB 
03數據觀察.png-30.1kB

 

1.2 三大協同過濾

04基於物品的協同過濾推薦.png-40.5kB05基於用戶的協同過濾推薦.png-40.4kB06基於SVD協同過濾推薦.png-32.1kB

 

1.3 項目開發工具

基於SVD協同過濾推薦系統.png-38.1kB

 

2 Movielens數據集簡介

  • MovieLens是推薦系統常用的數據集; 
    MovieLens數據集中,用戶對自己看過的電影進行評分,分值為1~5; 
    MovieLens包括兩個不同大小的庫,適用於不同規模的算法; 
    ·小規模的庫事943個獨立用戶對1682部電影做的10000次評分的數據; 
    ·大規模的庫事6040個獨立用戶對3900部電影做的100萬次評分的數據;

數據集下載地址:http://files.grouplens.org/datasets/movielens/ml-100k.zip

 

3 數據探索

 

3.1 導入小規模的庫數據

 
  1. import numpy as np
  2. import pandas as pd
  3. import matplotlib.pyplot as plt
  4. data=pd.read_csv('ml-100k/u.data',sep='\t',names=['user_id','item_id','rating','timestamp'])
  5. data.head()

image_1ch29t0md1oee1nbi1ls8jua1g0nl.png-11.6kB

 

3.2 數據探索及發現

 
  1. # 數據信息查看
  2. data.info()

image_1ch29ug7468d11hp13uf1k2c1dl612.png-12.5kB

 
  1. # 數據描述
  2. data.describe()

image_1ch2a07k11v2o1osk11o4mij1r9s1f.png-24.4kB

 
  1. data.user_id.nunique() # nunique() --> 返回不重復user_id的個數,統計用戶的個數
  2. data.item_id.nunique() # 統計被評價電影的個數
>> 943
>> 1682
 
  1. data.duplicated(subset=['user_id','item_id']).sum() # 查看user_id與item_id是否有重復的情況
>> 0

從導入的數據可以看出,user_id共有943個,item_id共有1682個,與數據對於的user_id及item_id的編號剛好是1~943和1~1682,可見數據已經清洗好的,不需要重新處理 
然后將數據集拆分為訓練集和測試集,分別進行處理 
且user_id與item_id均不存在重復的情況,數據可以直接使用。

 

3.3 數據檢查

 

3.3.1 查看每個物品對應用戶的數量

 
a. 根據item_id分類聚合
 
  1. # 統計每個物品對應的用戶數
  2. item_id_usercnt = train_data.groupby('item_id').count().user_id
  3. item_id_usercnt[:5]

image_1ch2ae5eamae1s89pmt1oq51q2v1s.png-5.1kB

 
b. 直方圖展示
 
  1. # 展示分類聚合結果
  2. plt.hist(item_id_usercnt.values)
  3. plt.show()

image_1ch2ag9v5aiht0d1fd713gscl329.png-13.8kB

 
c. 查看十分位數
 
  1. # 分別查看每一物品對應的用戶的十分位數(十分位數、二十分位數...一百分位數)
  2. item_id_usercnt.quantile(q=np.arange(0,1.1,0.1))

image_1ch2ai6blqin1kiudd5p65vum2m.png-8.4kB

 
d. 物品對應用戶數量數據查看發現

約有30%左右的物品對應的用戶數少於10個,對這部分物品計算與其他物品的相似度不會太准確

 

3.3.2 查看每個用戶對應物品的數量

 
a. 根據user_id分類聚合
 
  1. # 統計每個用戶對應的物品數
  2. user_id_itemcnt = train_data.groupby('user_id').count().item_id
  3. user_id_itemcnt[:5]

image_1ch2chhe811241a288qi3fmjbc3g.png-5.1kB

 
b. 直方圖展示
 
  1. # 展示分類聚合結果
  2. plt.hist(user_id_itemcnt.values)
  3. plt.show()

image_1ch2aqhaifp31nhc1llj1djmb1j33.png-12.2kB

 
c. 查看十分位數
 
  1. # 分別查看每一用戶對應的物品的十分位數(十分位數、二十分位數...一百分位數)
  2. user_id_itemcnt.quantile(q=np.arange(0,1.1,0.1))

image_1ch2cjcu6hsq1879ll898eem3t.png-8.5kB

 
d. 物品對應用戶數量數據查看發現

從每個用戶對應的物品數量至少為20個的情況來看,基於用戶相似度的准確度會比基於物品要好

 

3.4 構建用戶-物品矩陣

 

3.4.1 獲取矩陣行數m、列數n

 
  1. # 通過nunique()方法分別獲得user_id、item_id的去重計數
  2. m_users = train_data.user_id.nunique() #
  3. n_items = train_data.item_id.nunique()
 

3.4.2 創建一個全是0的m*n的矩陣並向矩陣中填充對應數據

 
  1. user_item_matrix = np.zeros((m_users,n_items)) # 創建一個全是0的m*n的矩陣
  2. '''
  3. itertuples() 將每一行轉換為對應的元組,且數據一一對應
  4. for line in data.head().itertuples():
  5. print(line)
  6. >> Pandas(Index=0, user_id=196, item_id=242, rating=3, timestamp=881250949)
  7. >> Pandas(Index=1, user_id=186, item_id=302, rating=3, timestamp=891717742)
  8. >> Pandas(Index=2, user_id=22, item_id=377, rating=1, timestamp=878887116)
  9. >> Pandas(Index=3, user_id=244, item_id=51, rating=2, timestamp=880606923)
  10. >> Pandas(Index=4, user_id=166, item_id=346, rating=1, timestamp=886397596)
  11. '''
  12. for line in data.itertuples():
  13. user_item_matrix[line[1]-1,line[2]-1]=line[3]
  14. '''
  15. 因為user_id 和 item_id都是從1開始編號的,而矩陣的索引是從零開始
  16. data數據的第二列為user_id,第三列為item_id,第三列則為對應user對item的評分
  17. '''
  18. user_item_matrix #展示一下用戶物品矩陣

image_1ch2d1hvj1rfj2vv8o911pa1o0s4a.png-5.3kB

 

3.4.3 查看用戶-物品矩陣的稀疏性

 
  1. # 統計矩陣中非0值的個數與矩陣總元素個數的比值,保留3位小數
  2. sparsity = round(len(user_item_matrix.nonzero()[1])/float(m_users*n_items),3)
  3. sparsity
>> 0.063
 
發現:用戶-物品 矩陣非常稀疏,只有6%的用戶物品有互動記錄
 

4 基於item的協同過濾推薦系統

 

4.1 物品相似度矩陣

07基於item的協同過濾推薦 - 物品相似度矩陣.png-161.3kB

 

4.2 基於item的協同過濾推薦 - 預測原理

07基於item的協同過濾推薦 - 預測原理.png-202.5kB

 

4.3 代碼實現

 
  1. import numpy as np
  2. import pandas as pd
  3. # 導入數據
  4. data=pd.read_csv('ml-100k/u.data',sep='\t',names=['user_id','item_id','rating','timestamp'])
  5. # 用戶物品統計
  6. n_users = data.user_id.nunique()
  7. n_items = data.item_id.nunique()
  8. # 拆分數據集
  9. from sklearn.model_selection import train_test_split
  10. train_data,test_data =train_test_split(data,test_size=0.3) #按照訓練集70%,測試集30%的比例對數據進行拆分
  11. # 訓練集 用戶-物品 矩陣
  12. user_item_matrix = np.zeros((n_users,n_items))
  13. for line in train_data.itertuples():
  14. user_item_matrix[line[1]-1,line[2]-1] = line[3]
  15. # 構建物品相似矩陣 - 使用sklearn.metrics.pairwise中的cosine計算余弦距離
  16. '''
  17. 采用余弦距離計算相似度
  18. 如果兩個物品在同一條水平線上,則其夾角為零,對應的余弦值為1,代表完全相似
  19. 如果兩個物品處於垂直的方向上,其夾角為90度,那么其余弦值為0,代表毫不相干
  20. '''
  21. from sklearn.metrics.pairwise import pairwise_distances
  22. # 相似度計算定義為余弦距離
  23. item_similarity_m = pairwise_distances(user_item_matrix.T,metric='cosine')
  24. # 物品相似矩陣探索
  25. '''
  26. item_similarity_m.shape >> (1682, 1682)
  27. item_similarity_m[0:5,0:5].round(2) # 取5*5的矩陣查看其保留兩位小數的數據
  28. # pairwise_distances模塊在計算物品相似性時,不會計算自己與自己的相似性,所以所以對角線的值都為0
  29. >> array([[0. , 0.67, 0.73, 0.7 , 0.81],
  30. [0.67, 0. , 0.84, 0.64, 0.82],
  31. [0.73, 0.84, 0. , 0.8 , 0.85],
  32. [0.7 , 0.64, 0.8 , 0. , 0.76],
  33. [0.81, 0.82, 0.85, 0.76, 0. ]])
  34. '''
  35. # 現在我們只分析上三角,得到等分位數
  36. item_similarity_m_triu = np.triu(item_similarity_m,k=1) # 取得上三角數據
  37. item_sim_nonzero = np.round(item_similarity_m_triu[item_similarity_m_triu.nonzero()],3)
  38. '''
  39. # 上三角矩陣
  40. arr=np.linspace(1,9,9).reshape(3,3)
  41. arr
  42. >> array([[1., 2., 3.],
  43. [4., 5., 6.],
  44. [7., 8., 9.]])
  45. np.triu(arr,k=1) # 默認k=0,k的值正數表示向右上角移對應個單位,把對應位置全部變為0
  46. >> array([[0., 2., 3.],
  47. [0., 0., 6.],
  48. [0., 0., 0.]])
  49. '''
  50. # 查看十分位數
  51. np.percentile(item_sim_nonzero,np.arange(0,101,10))
>> array([0. , 0.829, 0.884, 0.919, 0.948, 0.976, 1., 1.,1. , 1. , 1. ])

可以看出相似性得分普遍偏大,相似度沒有比較好的可區分性。

 

4.4 訓練集預測

 
  1. user_item_precdiction = user_item_matrix.dot(item_similarity_m) / np.array([np.abs(item_similarity_m).sum(axis=1)])
  2. # 除以np.array([np.abs(item_similarity_m).sum(axis=1)]是為了可以使評分在1~5之間,使1~5的標准化
  3. # 只取數據集中有評分的數據集進行評估
  4. from sklearn.metrics import mean_squared_error
  5. from math import sqrt
  6. prediction_flatten = user_item_precdiction[train_item_matrix.nonzero()]
  7. user_item_matrix_flatten = train_item_matrix[train_item_matrix.nonzero()]
  8. error_train = sqrt(mean_squared_error(prediction_flatten,user_item_matrix_flatten)) # 均方根誤差計算
  9. print('訓練集預測均方根誤差:'error_train)
>> 訓練集預測均方根誤差:3.4714925320107684
 

4.5 測試集預測

 
  1. test_data_matrix = np.zeros((n_users,n_items))
  2. for line in test_data.itertuples():
  3. test_data_matrix[line[1]-1,line[2]-1]=line[3]
  4. # 預測矩陣
  5. item_prediction = test_data_matrix.dot(item_similarity_m) / np.array(np.abs(item_similarity_m).sum(axis=1))
  6. # 只取數據集中有評分的數據集進行評估
  7. prediction_flatten = user_item_precdiction[test_data_matrix.nonzero()]
  8. test_data_matrix_flatten = test_data_matrix[test_data_matrix.nonzero()]
  9. error_test = sqrt(mean_squared_error(prediction_flatten,test_data_matrix_flatten)) # 均方根誤差計算
  10. print('測試集預測均方根誤差:'error_test)
>> 測試集預測均方根誤差:3.4645810277607487
 

4.6 單模型結果提示思路

 

4.6.1 改變相似度算法 - 采用歐式距離

 
  1. # 相似度計算定義為歐式距離
  2. item_similarity_m = pairwise_distances(user_item_matrix.T,metric='euclidean')
>> 訓練集預測均方根誤差:3.3818902386408056
>> 測試集預測均方根誤差:3.3763275676001396
 

4.6.2 增加訓練集比例

 
  1. from sklearn.model_selection import train_test_split
  2. train_data,test_data =train_test_split(data,test_size=0.2)
>> 訓練集預測均方根誤差:3.4464124130045506
>> 測試集預測均方根誤差:3.4247175440782516
 

4.6.3 增加訓練集的同時采用歐式距離

>> 訓練集預測均方根誤差:3.3395618010919823
>> 測試集預測均方根誤差:3.339569787071282
 

4.7 基於item協同過濾推薦系統結果分析

  • 1、通過改變物品矩陣相似度(采用歐式距離)的計算方法可以看出預測效果略有提升;
  • 2、通過增加訓練集的方法對預測結果略有提升,但並不明顯;
  • 3、在增加訓練集的同時采用歐式距離計算相似度發現預測效果提升最好,但均方根誤差依然很大,與之前預測(物品是分位數查看結果,其區分性並不是很好)相符;
  • 4、因而在此例中使用基於item的協同過濾推薦系統並不理想。
 

5 基於user的協同過濾的推薦系統

 

5.1 用戶相似矩陣

08基於user的協同過濾推薦 - 用戶相似度矩陣.png-181.9kB

 

5.2 基於user的協同過濾的推薦系統 - 預測原理

08基於user的協同過濾推薦預測原理.png-284.7kB

 

5.3 代碼實現

 
  1. # 導入數據
  2. import numpy as np
  3. import pandas as pd
  4. data=pd.read_csv('ml-100k/u.data',sep='\t',names=['user_id','item_id','rating','timestamp'])
  5. # 用戶物品統計
  6. n_users = data.user_id.nunique()
  7. n_items = data.item_id.nunique()
  8. # 拆分數據集
  9. from sklearn.model_selection import train_test_split
  10. # 按照訓練集70%,測試集30%的比例對數據進行拆分
  11. train_data,test_data =train_test_split(data,test_size=0.3)
  12. # 訓練集 用戶-物品 矩陣
  13. user_item_matrix = np.zeros((n_users,n_items))
  14. for line in train_data.itertuples():
  15. user_item_matrix[line[1]-1,line[2]-1] = line[3]
  16. # 構建用戶相似矩陣 - 采用余弦距離
  17. from sklearn.metrics.pairwise import pairwise_distances
  18. # 相似度計算定義為余弦距離
  19. user_similarity_m = pairwise_distances(user_item_matrix,metric='cosine') # 每個用戶數據為一行,此處不需要再進行轉置
  20. user_similarity_m[0:5,0:5].round(2) # 取5*5的矩陣查看其保留兩位小數的數據
  21. '''
  22. >> array([[0. , 0.85, 0.96, 0.96, 0.74],
  23. [0.85, 0. , 0.99, 0.84, 0.93],
  24. [0.96, 0.99, 0. , 0.77, 0.97],
  25. [0.96, 0.84, 0.77, 0. , 0.97],
  26. [0.74, 0.93, 0.97, 0.97, 0. ]])
  27. '''
  28. # 現在我們只分析上三角,得到等分位數
  29. user_similarity_m_triu = np.triu(user_similarity_m,k=1) # 取得上三角數據
  30. user_sim_nonzero = np.round(user_similarity_m_triu[user_similarity_m_triu.nonzero()],3)
  31. np.percentile(user_sim_nonzero,np.arange(0,101,10))
>> array([0.266,0.752,0.804,0.842,0.871,0.896,0.919,0.941,0.962,0.991, 1. ])

可以看出用戶矩陣的相似性區分性還是比較好的

 

5.4 訓練集預測

 
  1. mean_user_rating = user_item_matrix.mean(axis=1)
  2. rating_diff = (user_item_matrix - mean_user_rating[:,np.newaxis]) # np.newaxis作用:為mean_user_rating增加一個維度,實現加減操作
  3. user_precdiction = mean_user_rating[:,np.newaxis] + user_similarity_m.dot(rating_diff) / np.array([np.abs(user_similarity_m).sum(axis=1)]).T
  4. # 處以np.array([np.abs(item_similarity_m).sum(axis=1)]是為了可以使評分在1~5之間,使1~5的標准化
  5. # 只取數據集中有評分的數據集進行評估
  6. from sklearn.metrics import mean_squared_error
  7. from math import sqrt
  8. prediction_flatten = user_precdiction[user_item_matrix.nonzero()]
  9. user_item_matrix_flatten = user_item_matrix[user_item_matrix.nonzero()]
  10. error_train = sqrt(mean_squared_error(prediction_flatten,user_item_matrix_flatten)) # 均方根誤差計算
  11. print('訓練集預測均方根誤差:'error_train)
>> 訓練集預測均方根誤差:3.165938175006113
 

5.5 測試集預測

 
  1. test_data_matrix = np.zeros((n_users,n_items))
  2. for line in test_data.itertuples():
  3. test_data_matrix[line[1]-1,line[2]-1]=line[3]
  4. # 預測矩陣
  5. rating_diff = (test_data_matrix - mean_user_rating[:,np.newaxis]) # np.newaxis作用:為mean_user_rating增加一個維度,實現加減操作
  6. user_precdiction = mean_user_rating[:,np.newaxis] + user_similarity_m.dot(rating_diff) / np.array([np.abs(user_similarity_m).sum(axis=1)]).T
  7. # 只取數據集中有評分的數據集進行評估
  8. prediction_flatten = user_precdiction[user_item_matrix.nonzero()]
  9. user_item_matrix_flatten = user_item_matrix[user_item_matrix.nonzero()]
  10. error_test = sqrt(mean_squared_error(prediction_flatten,user_item_matrix_flatten)) # 均方根誤差計算
  11. print('測試集預測均方根誤差:'error_test)
>> 測試集預測均方根誤差:3.393103348518984
 

5.6 單模型結果提示思路

 

5.6.1 改變相似度算法 - 采用歐式距離

 
  1. # 相似度計算定義為歐式距離
  2. item_similarity_m = pairwise_distances(user_item_matrix.T,metric='euclidean')
>> 訓練集預測均方根誤差:3.1190848133071603
>> 測試集預測均方根誤差:3.3913121798056123
 

5.6.2 減少訓練集比例 / 增加測試集比例

 
  1. from sklearn.model_selection import train_test_split
  2. train_data,test_data =train_test_split(data,test_size=0.4)
>> 訓練集預測均方根誤差:3.237884760612846
>> 測試集預測均方根誤差:3.34890617988761
 

5.6.2 增加訓練集比例

 
  1. from sklearn.model_selection import train_test_split
  2. train_data,test_data =train_test_split(data,test_size=0.2)
>> 訓練集預測均方根誤差:3.094954182470391
>> 測試集預測均方根誤差:3.435958471375406
 

5.6.3 增加測試集的同時采用歐式距離

>> 訓練集預測均方根誤差:3.1925775976328934
>> 測試集預測均方根誤差:3.330738557937318
 

5.7 基於user協同過濾推薦系統結果分析

  • 1、采用歐式距離的情況下,訓練集數據預測效果提升較測試集明顯;
  • 2、運行結果顯示基於user的預測結果在測試集上普遍不如在訓練集上的預測結果。分析其原因:a.user相似矩陣本身太小(943*943),遠小於item相似矩陣的(1682*1682);b.在原因a的基礎上,測試集的矩陣就更小;
  • 2、因而基於user協同過濾系統中,分別采用了減小/增大訓練集兩種優化方法對模型進行了測試,發現只要數據集增大,其預測效果就有提升;
  • 3、在減小訓練集並采用歐式距離的情況下,模型在測試集的預測效果有所提升,但依然不理想;
  • 4、與基於item的協同過濾系統相比,基於user協同過濾系統模型預測效果明顯略微優秀。
 

6 基於SVD協同過濾推薦系統

 

6.1 SVD協同推薦系統原理

09基於SVD協同過濾推薦 - 預測原理.png-264.9kB

 

6.2 代碼實現

 
  1. # 導入數據
  2. import numpy as np
  3. import pandas as pd
  4. data=pd.read_csv('ml-100k/u.data',sep='\t',names=['user_id','item_id','rating','timestamp'])
  5. # 拆分數據集並分別構建用戶-物品矩陣
  6. # 用戶物品統計
  7. n_users = data.user_id.nunique()
  8. n_items = data.item_id.nunique()
  9. from sklearn.model_selection import train_test_split
  10. # 按照訓練集70%,測試集30%的比例對數據進行拆分
  11. train_data,test_data =train_test_split(data,test_size=0.3)
  12. # 訓練集 用戶-物品 矩陣
  13. train_data_matrix = np.zeros((n_users,n_items))
  14. for line in train_data.itertuples():
  15. train_data_matrix[line[1]-1,line[2]-1] = line[3]
  16. # 測試集 用戶-物品 矩陣
  17. test_data_matrix = np.zeros((n_users,n_items))
  18. for line in train_data.itertuples():
  19. test_data_matrix[line[1]-1,line[2]-1] = line[3]
  20. # SVD矩陣
  21. import scipy.sparse as sp
  22. from scipy.sparse.linalg import svds
  23. # 奇異值分解,超參數k的值就是設定要留下的特征值的數量
  24. u, s, vt = svds(train_data_matrix,k=20)
  25. s_diag_matrix = np.diag(s)
  26. svd_prediction = np.dot(np.dot(u,s_diag_matrix),vt)
  27. '''
  28. print(u.shape) >> (943, 20)
  29. print(s.shape) >> (20,)
  30. print(vt.shape) >> (20, 1682)
  31. print(s_diag_matrix.shape) >> (20, 20)
  32. print(svd_prediction.shape) >> (943, 1682)
  33. '''
  34. # 預測值限定最小值和最大值
  35. # 預測值小於0的均設置為0,大於5的均設置為5
  36. svd_prediction[svd_prediction < 0] =0
  37. svd_prediction[svd_prediction > 5] =5
 

6.3 訓練集預測

 
  1. # 只取預測數據中有評分的數據,進行評估
  2. from sklearn.metrics import mean_squared_error
  3. from math import sqrt
  4. prediction_flatten = svd_prediction[train_data_matrix.nonzero()]
  5. train_data_matrix_flatten = train_data_matrix[train_data_matrix.nonzero()]
  6. error_train = sqrt(mean_squared_error(prediction_flatten,train_data_matrix_flatten))
  7. print('訓練集預測均方根誤差:'error_train)
>> 訓練集預測均方根誤差:2.440629842312816
 

6.4 測試集預測

 
  1. prediction_flatten = svd_prediction[test_data_matrix.nonzero()]
  2. test_data_matrix_flatten = test_data_matrix[test_data_matrix.nonzero()]
  3. error_test = sqrt(mean_squared_error(prediction_flatten,test_data_matrix_flatten))
  4. print('測試集預測均方根誤差:'error_test)
>> 測試集預測均方根誤差:2.440629842312816
 

7 三大協同過濾推薦系統總結分析

10三大協同過濾系統結果對比分析.png-75.6kB

  • 1、總體而言,基於小規模MovieLens數據集的本案例中的三大協同過濾推薦系統,其預測的效果總體上可以表示為:推薦系統預測效果:SVD > user > item。
  • 2、根據基於user系統過濾推薦系統的情況可以看出,協同過濾推薦系統的數據越多,且數據之間互動越多,則推薦效果越好,然而一般情況下正是因為互動不夠充分才會需要推薦系統,所以,收集盡可能多的數據,基於大數據的分析就顯的相對比較重要;
  • 3 在三大推薦系統中,SCD奇異值推薦系統表現尤為突出,預測的結果相對於其他兩個推薦系統而言有非常大的提升,唯一的缺點就是其難以加以解釋,但不失為是一種非常好的推薦系統。

 

NMF

參考

https://blog.csdn.net/qq_26225295/article/details/51165858

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM