使用Mahout搭建推薦系統之入門篇4-Mahout實戰

用意: 結合上篇博客,寫寫代碼熟悉一下Mahout。不少地方想法都比較粗糙,亟待指正。

1、基本內容

    1. 加載數據: 判斷userID和itemID的大小關係
    2. 過濾數據: 評分較少的用戶直接過濾掉, 那些評分均一致且評分數量多的用戶過濾掉. 計算過濾百分比, 若是過濾過多, 則須要考慮其它方法了.
    3. DataModel選擇: 選擇數據庫存儲仍是文件存儲; 選擇GenericDataModel仍是GenericBooleanDataModel
    4. 選擇類似矩陣和參數, 如N值和門限值; 可視化(可選).

2、運行環境

     JAVA MYSQL等配置參考"最美的詞" 基於mahout的電影推薦系統
Mahout環境搭建
     本篇使用mahout 0.8的taste等相關jar包進行開發, jar包能夠從  http://mirror.bit.edu.cn/apache/mahout/mahout-distribution-0.8.tar.gz中摘取,也能夠在百度網盤上下載 http://pan.baidu.com/s/1iSOWk.
     與上次不一樣, 0.8版本的distribution合併了兩個包, 上次漏了兩個log包, 最終只須要引入7個包便可.
mahout核心類不變: 提供推薦Model等核心類
    mahout-core-0.8.jar
    mahout-math-0.8.jar
輔助類: 提供Log和部分數學公式類.
    slf4j-api-1.7.5.jar commons-logging-1.1.1.jar slf4j-jcl-1.7.5.jar提供Log服務
    guava-14.1.0.jar合併了兩個google相關的數學類google-collections.jar和guava.jar
    commons-math3-3.2.jar包取代了uncommons-maths-1.2.jar類   

3、程序運行

搭建基本框架並進行簡單測試
     我在博文1的框架下作了一點小改動, 從而說明推薦算法算法的 結果不穩定性以及調參的重要性. 推薦系統不像通常的業務邏輯, 搭建好系統只完成了極小的一部分, 重點在於 調參和響應速度
相似於博客1中敘述所述, 搭建基本的框架, 並引入movielens 100K中的u.data數據,運行成功. 
工程目錄結構:


[ 數據格式說明: movielens u.data數據格式爲"244     51     2 880606923", 以tab隔開. 表示ID爲244的用戶對ID爲51的物品打分爲2分, 時間爲880606923, 猜想相似於從1970年1月1日開始記的秒數, 數量級差很少, 暫時不使用此參數.]

首先介紹User-based和Item-based的方法.
     以User-based爲例, 將每個物品表示爲一個維度, 那麼每一個用戶均可以表示爲一個向量. 若是一個有{101, 102, 103, 104, 105}五個物品, 用戶1對101評分爲2.0, 對105評分爲3.0, 那麼用戶1能夠表示爲[2.0, 0, 0, 0, 3.0]. 那麼用戶之間就有距離, 距離由Similarity類似性決定, 常見的如歐拉距離. 若是咱們肯定了全部用戶間的距離, 那麼可使用N近鄰法或者門限法肯定每一個人的相鄰圈子, 以下所示. 

     如何選擇每一個item或者user響鈴圈子:
    常見的有N近鄰法和門限值法. 以下面2圖所示:

此圖表示N = 3時,選擇與1最近的前三位2, 4, 5而排除3.  1的圈子由2, 4, 5組成.

此圖表示門限(Threshold)選擇法, 4, 5 在門限以內, 而2. 3在門限以外. 1的圈子由4, 5組成.
總結: 那麼接下來的問題就是如何定義類似性, 即計算距離了.

3.1 調整N值和Threshold值對推薦結果的影響:

重要代碼片斷以下: 
public static void main(String[] args) throws Exception {


        int userId = 1;
        int rankNum = 2;
        
        QingRS qingRS = new QingRS();
        for(int neighberNum = 2; neighberNum < 10; neighberNum++) {
                System.out.println("neigherNum=" + neighberNum);
                qingRS.initRecommenderIntro(filename, neighberNum);
                String resultStr = qingRS.getRecommender(userId, rankNum);
                System.out.println(resultStr);
        }


}


運行結果:
A. 當neigherhood從2到9變化時, 推薦的物品前期在變化, 後期趨於穩定.
neigherNum=2

Recommend=313 4.5
neigherNum=3
Recommend=286 5.0
neigherNum=4
Recommend=286 5.0
neigherNum=5
Recommend=990 5.0
neigherNum=6
Recommend=990 5.0
neigherNum=7
Recommend=990 5.0
neigherNum=8
Recommend=990 5.0
neigherNum=9
Recommend=990 5.0java

解釋: neigherhood一開始變化時, 參考的人數增多了, 所謂三個臭皮匠頂過一個諸葛亮, 推薦將會變化, 可是隨着neigherhood的變大, 加再多的人進來也只是湊人數而已沒有多大的決定能力.python

B. 當rankNum從2到10變化時, 感受上rankNum的改變不該該影響推薦結果.

List<RecommendedItem> recommendations = recommender.recommend(userid,
rankNum);git

可是: 咱們發現除了neigherNum = 2之外, 推薦結果均發生了變化, 並且數據開始震盪, 若是將neigherNum放大到30, 推薦結果依舊不停地震盪. github

neigherNum=2

Recommend=313 4.5
neigherNum=3
Recommend=323 5.0
neigherNum=4
Recommend=898 5.0
neigherNum=5
Recommend=323 5.0
neigherNum=6
Recommend=323 5.0
neigherNum=7
Recommend=898 5.0
neigherNum=8
Recommend=326 5.0
neigherNum=9
Recommend=326 5.0算法

解釋???: 問題應該出在排序算法上, Mahout爲了節約內存使用了qSort, 所以排序算法不穩定. 可是我去查看Mahout源代碼發現GenericUserBasedRecommender中使用了Collections.sort(), sort默認使用的是MergeSort, 因此排序應該是穩定的. 依舊存在着疑問.sql

3.2. 針對DataModel作一些數據分析, 

相似於博文2, 判斷item和user數量, value範圍, 方差等.

代碼以下:數據庫

package com.qingfeng.rs.test;

import java.io.File;
import java.io.IOException;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.model.DataModel;

public class QingDataModelTest {
	private final static String filename = "data/u.data";

	public static void main(String[] args) throws IOException, TasteException {
		DataModel dataModel = new FileDataModel(new File(filename));
		// compute the max and min value
		// 計算最大最小值
		float maxValue = dataModel.getMaxPreference();
		float minValue = dataModel.getMinPreference();

		// compute the number of usersNum and itemsNum
		// 計算用戶和物品總數
		int usersNum = dataModel.getNumUsers();
		int itemsNum = dataModel.getNumItems();

		int[] itemsNumForUsers = new int[usersNum];
		int[] usersNumForItems = new int[itemsNum];

		LongPrimitiveIterator userIDs = dataModel.getUserIDs();
		int i = 0;
		while (userIDs.hasNext()) {
			itemsNumForUsers[i++] = dataModel.getPreferencesFromUser(
					userIDs.next()).length();
		}
		assert (i == usersNum);

		LongPrimitiveIterator itemIDs = dataModel.getItemIDs();
		i = 0;
		while (itemIDs.hasNext()) {
			usersNumForItems[i++] = dataModel.getPreferencesForItem(
					itemIDs.next()).length();
		}
		assert (i == itemsNum);

		// compute mean and variance
		// 計算平均值和方差
		double usersMean;
		double usersVar;

		int sum = 0;
		int sqSum = 0;
		for (int num : itemsNumForUsers) {
			sum += num;
			sqSum += num * num;
		}
		usersMean = (double) sum / usersNum;
		double userSqMean = (double) sqSum / usersNum;
		usersVar = Math.sqrt(userSqMean - usersMean * usersMean);

		double itemsMean;
		double itemsVar;
		sum = 0;
		sqSum = 0;
		for (int num : usersNumForItems) {
			sum += num;
			sqSum += num * num;
		}
		itemsMean = (double) sum / itemsNum;
		double itemsSqMean = (double) sqSum / itemsNum;
		itemsVar = Math.sqrt(itemsSqMean - itemsMean * itemsMean);

		System.out.println("Preference=(" + minValue + ", " + maxValue + ")");
		System.out.println("usersNum=" + usersNum + ", userMean=" + usersMean
				+ ", userVar=" + usersVar);
		System.out.println("itemsNum=" + itemsNum + ", itemsMean=" + itemsMean
				+ ", itemsVar=" + itemsVar);
	}
}


設置門限過濾數據apache

在代碼中加入過濾模塊
for (int num : itemsNumForUsers) {
	sum += num;
	if (num < 20) {
		countLower++;
		// System.out.println("user warning(" + countLower + ")=" + num);
	}
	sqSum += num * num;
}
System.out.println("user warning(" + countLower + ")");


for (int num : usersNumForItems) {
	sum += num;
	if (num < 20) {
		countLower++;
		//System.out.println("item warning(" + countLower + ")=" + num);
	}
	sqSum += num * num;
}
System.out.println("item warning(" + countLower + ")");


運行結果以下
user warning(0)
item warning(743)
Preference=(1.0, 5.0)
usersNum=943, userMean=106.04453870625663, userVar=100.87821227051644
itemsNum=1682, itemsMean=59.45303210463734, itemsVar=80.3599467406018

分析:與官方的1000個用戶, 1700部電影的說法一致http://www.grouplens.org/datasets/movielens/
user warning(0)
item warning(743) 表示有743個item評分個數小於20.

     物品評分較爲稀疏程度和物品總數大小是一致的. 使用user-based則用戶少,節約內存, 且矩陣緻密。api

設置門限爲20時, 發現物品矩陣稀疏、方差大和過濾器的統計結果item warning(743)大是一致, 此處先不過濾數據, 後期再說.緩存

注:固然優秀的過濾器須要改變門限值來不停的調試


3.3 選擇DataModel, 並計算內存使用狀況

     因爲數據有rate, 因此不使用Boolean形式的存儲. 
預估內存開銷:
     由上文分析可知: Preference ~= usersNum * userMean ~= 100K, 每一個Preference消耗28bytes,
預估內存開銷= 28bytes * 100K = 2.8 Mbytes. 此外類似矩陣若是使用鄰接矩陣方式存儲, max{usersNum, itemsNum}**2 * 4bytes(float) = 8Mbytes左右. 所以內存總結開銷在10M左右.
    [可是查看Mahout源代碼org.apache.mahout.cf.taste.impl中相關文件發現, 類似矩陣是臨時計算的, 每次recommend時經過重寫Estimator接口的estimate方法來具體實現. 能夠mahout仍是考慮到內存開銷, 犧牲了計算速度吧. 因此估計程序運行內存開銷依舊在2.8Mbytes左右. 究竟哪一個是正確的理解呢?]

     所以我使用in-memory形式的GenericDataModel將數據直接加載到內存中. 

實驗測試內存開銷:
經過屢次調用System.gc()來回收內存, 經過Rumtime.totalMemory和Runtime.freeMemory()查看內存使用狀態.
代碼以下: 

public class QingMemoryTest {
	private static final String filename = "data/u.data";

	public static void main(String[] args) throws Exception {
		DataModel dataModel = new FileDataModel(new File(filename));
		UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
		UserNeighborhood neighborhood = new NearestNUserNeighborhood(5,
				similarity, dataModel);

		Recommender recommender = new GenericUserBasedRecommender(dataModel,
				neighborhood, similarity);
		System.out.println("1: jvm free-memory= "
				+ Runtime.getRuntime().freeMemory() + "Bytes");
		System.gc();
		System.out.println("2: jvm free-memory= "
				+ Runtime.getRuntime().freeMemory() + "Bytes");

		// dataModel被回收, 因此推薦結果錯誤.
		System.out.println(recommender.recommend(1, 2).get(1).getValue());
	}
}


運行結果以下:
start: jvm used-memory= 0.5967178344726562MB

after dataModel: jvm used-memory= 19.2872314453125MB
after similarity: jvm used-memory= 19.2872314453125MB
after neighborhood: jvm used-memory= 19.58240509033203MB
after recommender: jvm used-memory= 19.58240509033203MB
recommend=340
after recommend first: jvm used-memory= 19.877883911132812MB
after gc: jvm used-memory= 9.829483032226562MB
recommend=340
after recommend second: jvm used-memory= 9.829483032226562MB

分析: 由上述數據可見,gc回收內存後, JVM內存消耗回收了10Mbytes, 與猜想一致.

問題: 回收完數據後, 爲何recommender還能夠進行推薦, 並且沒有額外的內存開銷???

數據增加10倍, 即便用1M數據進行測試

簡單統計分析結果:

user warning(0)
item warning(663)
Preference=(1.0, 5.0)
usersNum=6040, userMean=165.5975165562914, userVar=192.73107252940773
itemsNum=3706, itemsMean=269.88909875876953, itemsVar=383.9960197430679

估計內存消耗: usersNum和itemsNum增加了3到6倍, 而類似矩陣消耗內存爲平方級別, 那麼內存消耗上線爲9到36倍; 此外數據增加10倍, DataModel內存消耗爲線性增加, 增加10倍內存消耗. 那麼估計內存消耗= 2.8M * 10 + (9~36)*8M = 100M ~ 316M內存之間. 若是不存儲類似矩陣, 那麼內存消耗爲28M左右. 

因爲數據以"::"做爲分割符, 用python簡單處理一下,替換爲\t

f = open("result.dat", "w")
for line in open("ratings.dat", "r"):
	newLine = line.replace("::", "\t")
	f.write(newLine)

運行結果以下

start: jvm used-memory= 0.5967178344726562MB

after dataModel: jvm used-memory= 204.9770050048828MB
after similarity: jvm used-memory= 204.9770050048828MB
after neighborhood: jvm used-memory= 204.9770050048828MB
after recommender: jvm used-memory= 204.9770050048828MB
recommend=2908
after recommend first: jvm used-memory= 208.10643768310547MB
after gc: jvm used-memory= 76.12030029296875MB
recommend=2908
after recommend second: jvm used-memory= 76.12030029296875MB

分析: 由上述數據能夠: 數據回收了132Mbytes, 76M爲運行開銷. 與估計內存消耗移植. DataModel線性增加, 類似矩陣平方級別增加.

結論: 若是評分數增長到10M級別, 用戶或者物品數增加3~10倍, 那麼須要4G到40G的內存才能快速的計算出推薦結果, 須要增長內存條, 設置JVM配置以及使用hadoop來實現. 另外真實的數據用戶數達到GB級別, 總數達到TB級別, 須要的內存數量和運算量是十分恐怖的. 傳統地算法已經沒法知足要求, 須要藉助Hadoop這種分佈式來實現運算.

     固然內存不夠大, 硬盤能夠很大, 處理10M級別以上的推薦數據時, 選擇使用MysqlJDBCDataModel來實現存儲.     

另外: 據數盟的一位Q友說, "淘寶有8kw的商品(記憶也許有出入),用戶2億,多大的矩陣啊". 每次想到這裏, 都會默默地閉上雙眼, 遙想遠方的宇宙, 數據又是多麼地浩淼. 在上帝眼中, 咱們也許還只是玩過家家, 學1+1的小孩子吧.  

3.4. 選擇類似性矩陣和調參

此外, 後期但願考慮user-based, item-based, slope-one算法的比較, 同時參考運行時間.

類似矩陣選擇下面4種

PearsonCorrelationSimilarity   EuclideanDistanceSimilarity  TanimotoCoefficientSimilarity  LogLikeLihoodSimilarity

[注:其中EuclideanDistanceSimilarity比較特殊, 它沒有實現UserSimilarity接口, 因此不能放到一個Collection<UserSimilarity>容器中]

[注: 勿看了org.apache.mahout.math.hadoop.similarity.cooccurrence.measures文件]

參數調整隻選擇近鄰N和threashold

這裏給出代碼原型, 可是在普通PC上跑100K的數據集都太慢了, 使用intro.csv這個toy數據跑一跑.

N選擇[2, 4, 8, ... 64], Threshold選擇[0.9, 0.85, ... 0.7]; 

代碼以下:

public class QingParaTest {

	private final String filename = "data/intro.csv";
	private double threshold = 0.95;
	private int neighborNum = 2;
	private ArrayList<UserSimilarity> userSims;
	private final int SIM_NUM = 4;
	private final int NEIGHBOR_NUM = 64;
	private final double THRESHOLD_LOW = 0.7;

	public static void main(String[] args) throws IOException, TasteException {

		new QingParaTest().valuate();

	}

	public QingParaTest() {
		super();
		this.userSims = new ArrayList<UserSimilarity>();
	}

	private void valuate() throws IOException, TasteException {
		DataModel dataModel = new FileDataModel(new File(filename));

		RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator();

		// populate Similarity
		populateUserSims(dataModel);

		int simBest = -1;
		double scoreBest = 5.0;
		int neighborBest = -1;
		double thresholdBest = -1;
		System.out.println("SIM\tNeighborNum\t\tThreshold\tscore");
		for (int i = 0; i < SIM_NUM; i++) {
			for (neighborNum = 2; neighborNum <= NEIGHBOR_NUM; neighborNum *= 2) {

				for (threshold = 0.75; threshold >= THRESHOLD_LOW; threshold -= 0.05) {
					double score = 5.0;
					QingRecommenderBuilder qRcommenderBuilder = new QingRecommenderBuilder(
							userSims.get(i), neighborNum, threshold);

					// Use 70% of the data to train; test using the other 30%.
					score = evaluator.evaluate(qRcommenderBuilder, null,
							dataModel, 0.7, 1.0);
					System.out.println((i + 1) + "\t" + neighborNum + "\t"
							+ threshold + "\t" + score);

					if (score < scoreBest) {
						scoreBest = score;
						simBest = i + 1;
						neighborBest = neighborNum;
						thresholdBest = threshold;
					}
				}
			}
		}
		System.out.println("The best parameter");
		System.out.println(simBest + "\t" + neighborBest + "\t" + thresholdBest
				+ "\t" + scoreBest);
	}

	private void populateUserSims(DataModel dataModel) throws TasteException {
		UserSimilarity userSimilarity = new PearsonCorrelationSimilarity(
				dataModel);
		userSims.add(userSimilarity);
		userSimilarity = new TanimotoCoefficientSimilarity(dataModel);
		userSims.add(userSimilarity);
		userSimilarity = new LogLikelihoodSimilarity(dataModel);
		userSims.add(userSimilarity);

		userSimilarity = new EuclideanDistanceSimilarity(dataModel);
		userSims.add(userSimilarity);

	}

}

class QingRecommenderBuilder implements RecommenderBuilder {

	private UserSimilarity userSimilarity;
	private int neighborNum;
	private double threshold;

	public QingRecommenderBuilder(UserSimilarity userSimilarity,
			int neighborNum, double threshold) {
		super();
		this.userSimilarity = userSimilarity;
		this.neighborNum = neighborNum;
		this.threshold = threshold;
	}

	@Override
	public Recommender buildRecommender(DataModel dataModel)
			throws TasteException {
		UserNeighborhood neighborhood = new NearestNUserNeighborhood(
				neighborNum, threshold, userSimilarity, dataModel);
		return new GenericUserBasedRecommender(dataModel, neighborhood,
				userSimilarity);
	}

}


運行結果以下:

SIM NeighborNum Threshold score
1 2 0.75 0.4858379364013672
1 2 0.7 NaN
1 4 0.75 0.4676065444946289
1 4 0.7 NaN
1 8 0.75 0.8704338073730469
1 8 0.7 0.014162302017211914
1 16 0.75 NaN
1 16 0.7 0.7338032722473145
1 32 0.75 0.7338032722473145
1 32 0.7 0.4858379364013672
1 64 0.75 NaN
1 64 0.7 1.0

The best parameter

1 8 0.7 0.014162302017211914

分析: 運行最佳的結果爲N = 8, Threshold = 0.7 固然, 這個方法, 十分的粗糙, 可是也說明了參數的重要性, 畢竟推薦系統上線了必須有優秀的A\B Test結果, 要否則還不如使用打折, 優惠券來的簡單實在.

順便截一張Mahout in Action上一個真實案例的數據, 以下圖所示


 item-based與user_based一致, 基本上就是就Similarity, Neighborhood和Recommender的User換成Item便可.

3.5 slope-one

public class SlopeOne {
	public static void main(String[] args) throws IOException, TasteException {
		DataModel dataModel = new FileDataModel(new File("data/intro.csv"));

		RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator();
		double score = evaluator.evaluate(new SlopeOneNoWeighting(), null,
				dataModel, 0.7, 1.0);
		System.out.println(score);
	}

}

class SlopeOneNoWeighting implements RecommenderBuilder {
	public Recommender buildRecommender(DataModel model) throws TasteException {
		DiffStorage diffStorage = new MemoryDiffStorage(model,
				Weighting.UNWEIGHTED, Long.MAX_VALUE);
		return new SlopeOneRecommender(model, Weighting.UNWEIGHTED,
				Weighting.UNWEIGHTED, diffStorage);
	}
}


運行結果爲: 1.3571428571428572 固然這個結果意義不大, 由於數據集很小. 

4、總結

     推薦系統的難點在於各類參數、算法的選擇,以及推薦系統總體架構的測試;若是但願搭建商業級別的應用,在數據和架構上所花的時間要比算法調參多一些。

5、Similarity和Algorithm相關總結

如何計算類似性:
    常見的方法以下表所示: Similarity只是描述計算方法, 並不計算並保存類似矩陣.
    類似性的基本思路就是不適用歐式距離的, 都得加上權重或者門限來防止交集較小的類似距離.
類似距離(距離越小值越大)
優勢 缺點 取值範圍
PearsonCorrelation
相似於計算兩個矩陣的協方差
不受用戶評分偏高
或者偏低習慣影響的影響
1. 若是兩個item類似個數小於2時
沒法計算類似距離.
[可使用item類似個數門限來解決.]
沒有考慮兩個用戶之間的交集大小[使用 weight參數來解決]
2. 沒法計算兩個徹底相同的items
[-1, 1]
EuclideanDistanceSimilarity
計算歐氏距離, 使用1/(1+d)
使用與評分大小較
重要的場合
若是評分不重要則須要歸一化,
計算量大
同時每次有數據更新時麻煩
[-1, 1] 
CosineMeasureSimilarity
計算角度
與PearsonCorrelation一致
[-1, 1]
SpearmanCorrelationSimilarity
使用ranking來取代評分的
PearsonCorrelation
徹底依賴評分和徹底放棄評分之間的平衡
計算rank消耗時間過大
不利於數據更新
[-1, 1]
CacheUserSimilarity
保存了一些tag, reference
緩存常常查詢的user-similarity 額外的內存開銷
TanimotoCoefficientSimilarity
統計兩個向量的交集佔並集的比例
同時並集個數越多, 越相近.
適合只有相關性
而沒有評分的狀況
沒有考慮評分,信息丟失了 [-1,1]
LogLikeLihoodSimilarity
是TanimoteCoefficientSimilarity
的一種基於機率論改進
計算二者重合的偶然性
考慮了兩個item相鄰的獨特性
計算複雜 [-1,1]
    
     如何選擇推薦算法:    
    user-based算法: 最古老的算法, 計算類似的人羣, 最大的問題是存儲類似矩陣, 因爲每一個用戶喜歡的物品在變化, 致使類似矩陣不停的變化. 更新類似矩陣計算量可能較大. 針對搜索引擎來講, 搜索詞若是比用戶數目多的話,能夠考慮user-based. 
    item-based算法: 與user-based相似, 每一個物品被喜歡的用戶個數不停地變化, 類似矩陣持續地更新. 在互聯網時代,商品上百萬, 用戶上億. 那麼使用item-based比較靠譜, 物品類似矩陣變化較小, Amazon的推薦算法就是使用item-based爲基礎的.
    SVD: 如今比較流行的算法, 由於能夠進行降維. 發掘有價值的特徵維度來取代用戶維度或者商品維度. 舉個例子: 例如兩我的分別喜歡保時捷和法拉利, user-based和item-based計算的類似性都很低, 可是SVD引入跑車或者奢侈品這種潛在的特徵後, 二者就有類似性了. 固然缺點在於, SVD須要將整個矩陣加載到內存進行矩陣分解, 對內存消耗大, 不知道SVD的矩陣分解有沒有Map-Reduce實現方法. 
    Slope-One算法: 上述三種算法都不太適合做爲在線算法和更新數據, 可是Slope-One能夠. 舉個例子, 假設全部用戶評價電影A比電影B高1.0分, 評價電影C和電影A一致. 若是一個用戶評價電影B爲2.0分, 評價電影C爲4.0分, 那麼用戶評價電影A爲3.0分或者4.0分, 最佳的方法的取二者的加權平均值, 權重由同時出現次數決定. Slope-One能夠離線計算全部的n*(n-1)/2中相關性, 當一個用戶更新了電影時, 相關性更新快捷; 經過遍歷一遍電影便可得到全部電影的評分,從而排序給出推薦. 缺點是相關性計算複雜.  [我的以爲這個計算量也不小, 取決於電影個數以及用戶評分電影個數]

6、參考資料


[1] Sean Owen "Mahout in Action"  http://book.douban.com/subject/4893547/
相關文章
相關標籤/搜索