[機器學習]協同過濾演算法的原理和基於Spark 例項

just-do-it-zzj發表於2020-12-30

目錄

 

協同過濾

協同過濾的型別

協同過濾的評價方法

冷啟動問題

Spark中協同過濾演算法的實現方式


協同過濾

協同過濾,簡稱CF演算法是一種藉助"集體計算"的途徑。它利用大量已有的使用者偏好來估計使用者對其未接觸過的物品的喜好程度。其內在思想是相似度的定義。

協同過濾常被應用於推薦系統。這些技術旨在補充使用者—商品關聯矩陣中所缺失的部分。其中使用者和物品由一小部分已知因素描述,用這些因素可以預測缺失值。

協同過濾的型別

  1. 在基於使用者的協同過濾(User CF )方法的中,如果兩個使用者表現出相似的偏好(即對相同物品的偏好大體相同),那就認為他們的興趣類似。要對他們中的一個使用者推薦一個未知物品。

如使用者A、B、C的偏好矩陣如下:

使用者/物品

物品A

物品B

物品C

物品D

物品E

使用者A

 

 

使用者B

 

 

 

 

使用者C

推薦

因為使用者A喜歡物品A、物品C、物品D;而使用者C喜歡物品A、物品C,演算法認為使用者A和使用者C相似,因此給使用者C推薦使用者A系統的物品D。

  1. 同樣也可以藉助基於物品(Item CF)的方法來做推薦。這種方法通常根據現有使用者對物品的偏好或是評級情況,來計算物品之間的某種相似度。 這時,相似使用者評級相同的那些物品會被認為更相近。一旦有了物品之間的相似度,便可用使用者接觸過的物品來表示這個使用者,然後找出和這些已知物品相似的那些物品,並將這些物品推薦給使用者。同樣,與已有物品相似的物品被用來生成一個綜合得分,而該得分用於評估未知物品的相似度。

如物品分類:

物品名

型別

物品A

型別A

物品B

型別B

物品C

型別C

物品D

型別A

物品E

型別B

使用者購買歷史矩陣:

使用者/物品

物品A

物品B

物品C

物品D

物品E

使用者A

 

 

使用者B

 

 

 

 

使用者C

推薦

物品A和物品D同屬於型別A,使用者A購買了物品D,所以把物品D推薦給使用者C。

協同過濾的評價方法

(1)平均絕對誤差(MAE)

統計精度度量方法中的平均絕對誤差(MAE)被廣泛用於評價協同過濾推薦系統的推薦質量。因此,推薦質量評價採用了常見的平均絕對誤差MAE在測試集上首先運用推薦系統預測出使用者的評分,然後根據測試集中使用者的實際評分,計算出兩者的偏差,即為MAE的值。

假設預測使用者評分值為{p1,p2…,pn }對應的實際評分值為 {q1,q2, …,qn },則MAE的計算公式為:

 

(2)均方根誤差RMSE的計算公式為:

 

冷啟動問題

在產品剛剛上線、新使用者到來的時候,如果沒有使用者在應用上的行為資料,也無法預測其興趣愛好。另外,當新商品上架也會遇到冷啟動的問題,沒有收集到任何一個使用者對其瀏覽,點選或者購買的行為,也無從對商品進行推薦。

Spark中協同過濾演算法的實現方式

Spark ML中的協同過濾演算法基於最小二乘法(Alternating Least Squares 即ALS)實現。

ALS演算法屬於User-Item CF,也叫做混合CF。它同時考慮了User和Item兩個方面,Spark用它學習那些未知的潛在因素。使用者和商品的關係,可以抽象為如下的三元組:<User,Item,Rating>。其中,Rating是使用者對商品的評分,表徵使用者對該商品的喜好程度。

假設有一批使用者資料,其中包含m個User和n個Item,則我們定義Rating矩陣,其中的元素表示第u個User對第i個Item的評分。

在實際使用中,由於n和m的數量都十分巨大,因此R矩陣的規模很容易就會突破億項級。傳統的矩陣分解方法對於這麼大的資料量就很難處理了。另一方面,一個使用者也不可能給所有商品評分,因此,R矩陣註定是個稀疏矩陣。

 

實際中,User和Item都有很多維度,比如User有姓名、性別、年齡等;Item有名稱、產地、類別等。計算時需要將R矩陣對映到這些維度上,對映方法參考“奇異值分解”。投影結果如下:

 

通常,K要比m和n小得多,從而達到降維的目的。我們給演算法喂資料時並沒有維度資訊,維度只是假設存在,因此這裡的關聯維度又被稱為Latent factor。

顯式反饋

 顯式反饋表現為Users對Items的直接評分。

隱式反饋

使用者給商品評分是個非常簡單的使用者行為。在實際中,還有大量的使用者行為,同樣能夠間接反映使用者的喜好,比如使用者的購買記錄、搜尋關鍵字,甚至是滑鼠的移動。我們將這些間接使用者行為稱之為隱式反饋(implicit feedback),以區別於評分這樣的顯式反饋(explicit feedback)。

隱式反饋有以下幾個特點:

1.沒有負面反饋(negative feedback)。使用者一般會直接忽略不喜歡的商品,而不是給予負面評價。

2.隱式反饋包含大量噪聲。比如,電視機在某一時間播放某一節目,然而使用者已經睡著了,或者忘了換臺。

3.顯式反饋表現的是使用者的喜好(preference),而隱式反饋表現的是使用者的信任(confidence)。比如使用者最喜歡的一般是電影,但觀看時間最長的卻是連續劇。大米購買的比較頻繁,量也大,但未必是使用者最想吃的食物。

4.隱式反饋非常難以量化。

1)Spark 協同過濾演算法

ALS的引數:

  1. numBlocks:users和items矩陣被分成的塊數,各塊之間可以平行計算,加快計算速度,預設值是10
  2. rank:模型中Latent factor 的大小,預設值為10
  3. maxIter:預設的最大迭代次數,模式是10
  4. regParam:指定正則化引數,預設是10
  5. implicitPrefs:指定是否使用隱式反饋,預設為顯式反饋;true為使用隱式反饋。
  6. alpha:當使用隱式反饋時該引數生效;對觀察值執行控制基線置信度,預設值為1。
  7. nonnegative:是否對ALS使用非0約束,預設為false,即不使用。

例項:

輸入資料請到 https://github.com/apache/spark/tree/master/data/mllib/als/sample_movielens_ratings.txt 下載

import java.io.File;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.ml.evaluation.RegressionEvaluator;
import org.apache.spark.ml.recommendation.ALS;
import org.apache.spark.ml.recommendation.ALSModel;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;

public class ALStest {
	public static void main(String[] args) {
		String rootDir = System.getProperty("user.dir") + File.separator;
		String fileResourcesDir = rootDir + "resources" + File.separator;

		// 訓練資料的路徑
		String filePath = fileResourcesDir + "sample_movielens_ratings.txt";
		
		SparkSession spark = SparkSession.builder().master("local[4]").appName("test").getOrCreate();
		JavaRDD<Rating> ratingsRDD = spark.read().textFile(filePath).javaRDD().map(Rating::parseRating);
		Dataset<Row> ratings = spark.createDataFrame(ratingsRDD, Rating.class);
		Dataset<Row>[] splits = ratings.randomSplit(new double[] { 0.8, 0.2 });
		Dataset<Row> training = splits[0];
		Dataset<Row> test = splits[1];
	
		//使用訓練資料通過ALS演算法訓練
		ALS als = new ALS().setMaxIter(5).setRegParam(0.01).setUserCol("userId").setItemCol("movieId")
				.setRatingCol("rating");
		ALSModel model = als.fit(training);		 
		 
		// evaluation metrics
		//刪除冷啟動資料,即NaN值的資料
		model.setColdStartStrategy("drop");
		//在測試資料上進行預測
		Dataset<Row> predictions = model.transform(test);
		//用均方根誤差評估模型
		RegressionEvaluator evaluator = new RegressionEvaluator().setMetricName("rmse").setLabelCol("rating")
				.setPredictionCol("prediction");
		double rmse = evaluator.evaluate(predictions);

		//給每個使用者推薦Top 10部電影
		Dataset<Row> userRecs = model.recommendForAllUsers(10);
		//給每部電影推薦Top 10 個使用者
		Dataset<Row> movieRecs = model.recommendForAllItems(10);

		// 給指定使用者推薦10部電影
		Dataset<Row> users = ratings.select(als.getUserCol()).distinct().limit(3);
		Dataset<Row> userSubsetRecs = model.recommendForUserSubset(users, 10);
		 
		//給指定電影推薦10個使用者
		Dataset<Row> movies = ratings.select(als.getItemCol()).distinct().limit(3);
		Dataset<Row> movieSubSetRecs = model.recommendForItemSubset(movies, 10);		
		training.show(false);
		predictions.show(false);
		userRecs.show(false);
		movieRecs.show(false);
		System.out.println("均方根誤差 = " + rmse);		
	}
}

 

相關文章