一、算法分類
基于模型的協同過濾作為目前最主流的協同過濾類型,當隻有部分使用者和部分物品之間是有評分資料的,其他部分評分是空白,此時我們要用已有的部分稀疏資料來預測那些空白的使用者和物品之間的評分關系,找到最高評分的物品推薦給使用者,實作使用者對未評價過的物品的預測評分。
基于模型協同過濾的方法包括:用關聯算法、聚類算法、分類算法、回歸算法、矩陣分解、神經網絡,圖模型以及隐語義模型來解決。
本文講矩陣分解的推薦。
二、基本思想
矩陣分解,直覺上來說就是把原來的大矩陣,近似分解成兩個小矩陣的乘積,在實際推薦計算時不再使用大矩陣,而是使用分解得到的兩個小矩陣。按照矩陣分解的原理,把原來的m*n 的大矩陣會分解成 m*k和k*n的兩個小矩陣。
k:隐式向量的次元數,是一個超參需要提前制定,k的取值遠遠小于m和n。
三、優缺點
優點 | 缺點 |
比較容易程式設計實作,随機梯度下降方法依次疊代即可訓練出模型。 比較低的時間和空間複雜度,高維矩陣映射為兩個低維矩陣節省了存儲空間, 訓練過程比較費時,但是可以離線完成; 評分預測一般線上計算,直接使用離線訓練得到的參數,可以實時推薦。 | 模型訓練比較費時。 |
預測的精度比較高,預測準确率要高于基于領域的協同過濾以及内容過濾等方法。 | 推薦結果不具有很好的解釋性,分解出來的使用者和物品 矩陣的每個次元無法和現實生活中概念來解釋, 無法用現實概念給每個次元命名,隻能了解為在語義空間。 |
四、實作算法
1、交替最小二乘法(ALS)
ALS優勢:
(1) 在交替的其中一步,也就是假設已知其中一個矩陣求解另一個矩陣時,要優化的參數是很容易并行化的;
(2) 在不那麼稀疏的資料集合上,交替最小二乘通常比随機梯度下降要更快的得到結果。
ALS過程:
(1) 初始化随機矩陣U裡面的元素值;
(2) 把U矩陣當做已知的,直接用線性代數的方法求得矩陣V;
(3) 得到的矩陣V後,把V當做已知的,求解矩陣U;
(4) 上面兩個過程交替進行,一直到誤差可以接受為止。
測試資料
1,1,7.0
1,2,6.0
1,3,7.0
1,4,4.0
1,5,5.0
1,6,4.0
2,1,6.0
2,2,7.0
2,4,4.0
2,5,3.0
2,6,4.0
3,2,3.0
3,3,3.0
3,4,1.0
3,5,1.0
4,1,1.0
4,2,2.0
4,3,2.0
4,4,3.0
4,5,3.0
4,6,4.0
5,1,1.0
5,3,1.0
5,4,2.0
5,5,3.0
5,6,3.0
View Code
spark代碼:
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.recommendation.ALS
import org.apache.spark.ml.recommendation.ALS.Rating
import org.apache.spark.sql.SparkSession
object ALSRecommend_test {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.master("local[5]")
.getOrCreate()
import spark.implicits._
//1.準備源資料集
val movieRatings = spark.read.textFile("E:\\tmp\\aikfka\\movieLess\\utou_main.csv")
.map(line => {
val fields = line.split(",")
Rating(fields(0).toInt , fields(1).toInt , fields(2).toFloat)
})
//2.從源資料集中拆分出一部分作為訓練集
val Array(traing,test) = movieRatings.randomSplit(Array(0.8 , 0.2))
//3.用訓練集來訓練一個模型
val als = new ALS()
.setMaxIter(15)
.setUserCol("user")
.setItemCol("item")
.setRatingCol("rating")
.setRegParam(0.15) //避免過拟合
.setRank(5)
val model = als.fit(traing)
model.itemFactors.show(false)
model.userFactors.show(false)
//
// //在打分資料裡面,有這樣的情況,就是資料集切割之後,使用者對電影的評分有不完整的情況,就是測試集裡面可能會出現訓練集當中
// //沒有出現過的電影或者使用者
// //這個叫做冷啟動
// //ALS有兩種政策,一種是辨別為NAN,還有一種是丢棄掉。我們這裡選擇丢掉
// model.setColdStartStrategy("drop")
//
// //4.通過模型來預測我們的測試集(源資料中的另一部分)
// val prediction = model.transform(test)
//
// //5.通過名額來評估模型的好壞(也就是預測的評分結果和測試集中的使用者的評分結果的誤內插補點大小)
// //我們使用RMSE名額來判斷模型的好壞,其實就是真實評分和預測評分的評分和
// //RMSE的結果越小,實際的模型的效果就越好
// //這裡要資料真實值的一列和預測值的一列
// val evaluator = new RegressionEvaluator()
// .setMetricName("rmse")
// .setLabelCol("rating")
// .setPredictionCol("prediction")
//
// val rmse = evaluator.evaluate(prediction)
//
// println("評估值:" + rmse)
//
// //6.基于模型完成物品的推薦
//
//// Returns top `numItems` items recommended for each user, for all users.
// //為每一個使用者推薦10個商品
// val forUserRecommendItem = model.recommendForAllUsers(6)
// forUserRecommendItem.show(false)
//
//
// //Returns top `numUsers` users recommended for each item, for all items.
// //為每一個商品推薦10個使用者
// val forItemRecommendUser = model.recommendForAllItems(10)
//
//
// //為指定的使用者推薦商品
// val users = movieRatings.select(als.getUserCol).distinct().limit(5)
//
// val userSubset = model.recommendForUserSubset(users , 10)
//
//// val items = movieRatings.select(als.getItemCol).distinct().limit(5)
////
//// val itemSubset = model.recommendForItemSubset(items , 10)
//
// userSubset.show(false)
//// itemSubset.show(false)
}
}
2、随機梯度下降
(1) 确定優化目标
優化目标是讓,Mij和Sij越接近越好,誤差越小越好。(Mij是使用者物品真實評分矩陣,Sij是預測評分矩陣)
loss = sum(i-->m,j--->n(Mij-Sij)^2)
(2) 确定loss裡面的未知數是什麼,進而優化他
loss=(Mij-Ui*Vj)^2
梯度下降,來找到最優的U、V;核心是找到函數的最小值點(疊代的方式)
(3) 梯度下降的過程
step1:loss對要下降的參數求導
dloss/dUi=2(Mij-Ui*Vj)*Vj
dloss/dVj=2(Mij-Ui*Vj)*Ui
step2:初始化一個Ui,Vj
for t in range(iter_numm):
Vt+1=Vt-lr(學習率)*dloss/dVj|Vj=Vt
python代碼:
'''
BiasSvd Model
'''
import math
import random
import pandas as pd
import numpy as np
class BiasSvd(object):
def __init__(self, alpha, reg_p, reg_q, reg_bu, reg_bi, number_LatentFactors=100, number_epochs=10, columns=["userId", "movieId", "rating"]):
self.alpha = alpha # 學習率
self.reg_p = reg_p
self.reg_q = reg_q
self.reg_bu = reg_bu
self.reg_bi = reg_bi
self.number_LatentFactors = number_LatentFactors # 隐式類别數量 k 隐矩陣的次元
self.number_epochs = number_epochs # 疊代次數 梯度下降
self.columns = columns
def fit(self, dataset):
'''
fit dataset
:param dataset: uid, iid, rating
:return:
'''
self.dataset = pd.DataFrame(dataset)
self.users_ratings = dataset.groupby(self.columns[0]).agg([list])[[self.columns[1], self.columns[2]]]
self.items_ratings = dataset.groupby(self.columns[1]).agg([list])[[self.columns[0], self.columns[2]]]
self.globalMean = self.dataset[self.columns[2]].mean()
self.P, self.Q, self.bu, self.bi = self.sgd()
def _init_matrix(self):
'''
初始化P和Q矩陣,同時為設定0,1之間的随機值作為初始值
:return:
'''
# User-LF
P = dict(zip(
self.users_ratings.index,
np.random.rand(len(self.users_ratings), self.number_LatentFactors).astype(np.float32)
))
# Item-LF
Q = dict(zip(
self.items_ratings.index,
np.random.rand(len(self.items_ratings), self.number_LatentFactors).astype(np.float32)
))
return P, Q
def sgd(self):
'''
使用随機梯度下降,優化結果
:return:
'''
#P ,Q 代表 P使用者矩陣 Q 物品矩陣 參數矩陣
P, Q = self._init_matrix()
# 初始化bu、bi的值,全部設為0
bu = dict(zip(self.users_ratings.index, np.zeros(len(self.users_ratings))))
bi = dict(zip(self.items_ratings.index, np.zeros(len(self.items_ratings))))
for i in range(self.number_epochs):
print("iter%d"%i)
error_list = []
for uid, iid, r_ui in self.dataset.itertuples(index=False):
v_pu = P[uid]
v_qi = Q[iid]
# 梯度
err = np.float32(r_ui - self.globalMean - bu[uid] - bi[iid] - np.dot(v_pu, v_qi))
#得到最新的 v_pu v_qi 參數,去更新 以前的
v_pu += self.alpha * (err * v_qi - self.reg_p * v_pu)
v_qi += self.alpha * (err * v_pu - self.reg_q * v_qi)
P[uid] = v_pu
Q[iid] = v_qi
bu[uid] += self.alpha * (err - self.reg_bu * bu[uid])
bi[iid] += self.alpha * (err - self.reg_bi * bi[iid])
error_list.append(err ** 2)
print(np.sqrt(np.mean(error_list)))
return P, Q, bu, bi
def predict(self, uid, iid):
if uid not in self.users_ratings.index or iid not in self.items_ratings.index:
return self.globalMean
p_u = self.P[uid]
q_i = self.Q[iid]
return self.globalMean + self.bu[uid] + self.bi[iid] + np.dot(p_u, q_i)
if __name__ == '__main__':
dataset = pd.read_csv("../data/ua.base", sep="\t", header=None, names=["userId", "movieId", "rating", "ts"])
print(dataset[["userId", "movieId", "rating"]])
dataset.groupby("userId").agg([list])
bsvd = BiasSvd(0.02, 0.01, 0.01, 0.01, 0.01, 10, 20)
bsvd.fit(dataset[["userId", "movieId", "rating"]])
s = bsvd.Q
pass
while True:
uid = input("uid: ")
iid = input("iid: ")
print(bsvd.predict(int(uid), int(iid)))