使用Mahout實現協同過濾

摘要: Mahout算法框架自帶的推薦器有下面這些: GenericUserBasedRecommender:基於用戶的推薦器,用戶數量少時速度快; GenericItemBasedRecommender:基於商品推薦器,商品數量少時速度快,尤其當外部提供了商品相似度數據後效率更好; SlopeOneRe...

Mahout算法框架自帶的推薦器有下面這些:

  • GenericUserBasedRecommender:基於用戶的推薦器,用戶數量少時速度快;
  • GenericItemBasedRecommender:基於商品推薦器,商品數量少時速度快,尤其當外部提供了商品相似度數據後效率更好;
  • SlopeOneRecommender:基於slope-one算法的推薦器,在線推薦或更新較快,需要事先大量預處理運算,物品數量少時較好;
  • SVDRecommender:奇異值分解,推薦效果較好,但之前需要大量預處理運算;
  • KnnRecommender:基於k近鄰算法(KNN),適合於物品數量較小時;
  • TreeClusteringRecommender:基於聚類的推薦器,在線推薦較快,之前需要大量預處理運算,用戶數量較少時效果好;

Mahout最常用的三個推薦器是上述的前三個,本文主要討論前兩種的使用。

接口相關介紹

基於用戶或物品的推薦器主要包括以下幾個接口:

  • DataModel 是用戶喜好信息的抽象接口,它的具體實現支持從任意類型的數據源抽取用戶喜好信息。Taste 默認提供 JDBCDataModel 和 FileDataModel,分別支持從數據庫和文件中讀取用戶的喜好信息。
  • UserSimilarity  ItemSimilarity。UserSimilarity 用於定義兩個用戶間的相似度,它是基於協同過濾的推薦引擎的核心部分,可以用來計算用戶的“鄰居”,這裏我們將與當前用戶口味相似的用戶稱爲他的鄰居。ItemSimilarity 類似的,計算內容之間的相似度。
  • UserNeighborhood 用於基於用戶相似度的推薦方法中,推薦的內容是基於找到與當前用戶喜好相似的鄰居用戶的方式產生的。UserNeighborhood 定義了確定鄰居用戶的方法,具體實現一般是基於 UserSimilarity 計算得到的。
  • Recommender 是推薦引擎的抽象接口,Taste 中的核心組件。程序中,爲它提供一個 DataModel,它可以計算出對不同用戶的推薦內容。實際應用中,主要使用它的實現類 GenericUserBasedRecommender 或者 GenericItemBasedRecommender,分別實現基於用戶相似度的推薦引擎或者基於內容的推薦引擎。
  • RecommenderEvaluator:評分器。
  • RecommenderIRStatsEvaluator:蒐集推薦性能相關的指標,包括準確率、召回率等等。

目前,Mahout爲DataModel提供了以下幾種實現:

  • org.apache.mahout.cf.taste.impl.model.GenericDataModel
  • org.apache.mahout.cf.taste.impl.model.GenericBooleanPrefDataModel
  • org.apache.mahout.cf.taste.impl.model.PlusAnonymousUserDataModel
  • org.apache.mahout.cf.taste.impl.model.file.FileDataModel
  • org.apache.mahout.cf.taste.impl.model.hbase.HBaseDataModel
  • org.apache.mahout.cf.taste.impl.model.cassandra.CassandraDataModel
  • org.apache.mahout.cf.taste.impl.model.mongodb.MongoDBDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.SQL92JDBCDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.MySQLJDBCDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.PostgreSQLJDBCDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.GenericJDBCDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.SQL92BooleanPrefJDBCDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.MySQLBooleanPrefJDBCDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.PostgreBooleanPrefSQLJDBCDataModel
  • org.apache.mahout.cf.taste.impl.model.jdbc.ReloadFromJDBCDataModel

從類名上就可以大概猜出來每個DataModel的用途,奇怪的是竟然沒有HDFS的DataModel,有人實現了一個,請參考MAHOUT-1579

UserSimilarity  ItemSimilarity 相似度實現有以下幾種:

  • CityBlockSimilarity:基於Manhattan距離相似度
  • EuclideanDistanceSimilarity:基於歐幾里德距離計算相似度
  • LogLikelihoodSimilarity:基於對數似然比的相似度
  • PearsonCorrelationSimilarity:基於皮爾遜相關係數計算相似度
  • SpearmanCorrelationSimilarity:基於皮爾斯曼相關係數相似度
  • TanimotoCoefficientSimilarity:基於谷本系數計算相似度
  • UncenteredCosineSimilarity:計算 Cosine 相似度

以上相似度的說明,請參考Mahout推薦引擎介紹

UserNeighborhood 主要實現有兩種:

  • NearestNUserNeighborhood:對每個用戶取固定數量N個最近鄰居
  • ThresholdUserNeighborhood:對每個用戶基於一定的限制,取落在相似度限制以內的所有用戶爲鄰居

Recommender分爲以下幾種實現:

  • GenericUserBasedRecommender:基於用戶的推薦引擎
  • GenericBooleanPrefUserBasedRecommender:基於用戶的無偏好值推薦引擎
  • GenericItemBasedRecommender:基於物品的推薦引擎
  • GenericBooleanPrefItemBasedRecommender:基於物品的無偏好值推薦引擎

RecommenderEvaluator有以下幾種實現:

  • AverageAbsoluteDifferenceRecommenderEvaluator:計算平均差值
  • RMSRecommenderEvaluator:計算均方根差

RecommenderIRStatsEvaluator的實現類是GenericRecommenderIRStatsEvaluator。

單機運行

首先,需要在maven中加入對mahout的依賴:

<dependency>
    <groupId>org.apache.mahout</groupId>
    <artifactId>mahout-core</artifactId>
    <version>0.13.0</version>
</dependency>

<dependency>
    <groupId>org.apache.mahout</groupId>
    <artifactId>mahout-integration</artifactId>
    <version>0.13.0</version>
</dependency>

<dependency>
    <groupId>org.apache.mahout</groupId>
    <artifactId>mahout-math</artifactId>
    <version>0.13.0</version>
</dependency>

<dependency>
    <groupId>org.apache.mahout</groupId>
    <artifactId>mahout-examples</artifactId>
    <version>0.13.0</version>
</dependency>

基於用戶的推薦,以FileDataModel爲例:

File modelFile modelFile = new File("intro.csv");

DataModel model = new FileDataModel(modelFile);

//用戶相似度,使用基於皮爾遜相關係數計算相似度
UserSimilarity similarity = new PearsonCorrelationSimilarity(model);

//選擇鄰居用戶,使用NearestNUserNeighborhood實現UserNeighborhood接口,選擇鄰近的4個用戶
UserNeighborhood neighborhood = new NearestNUserNeighborhood(4, similarity, model);

Recommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);

//給用戶1推薦4個物品
List<RecommendedItem> recommendations = recommender.recommend(1, 4);

for (RecommendedItem recommendation : recommendations) {
    System.out.println(recommendation);
}

注意: FileDataModel要求輸入文件中的字段分隔符爲逗號或者製表符,如果你想使用其他分隔符,你可以擴展一個FileDataModel的實現,例如,mahout中已經提供了一個解析MoiveLens的數據集(分隔符爲::)的實現GroupLensDataModel。

GenericUserBasedRecommender是基於用戶的簡單推薦器實現類,推薦主要參照傳入的DataModel和UserNeighborhood,總體是三個步驟:

  • (1) 從UserNeighborhood獲取當前用戶Ui最相似的K個用戶集合{U1, U2, …Uk};
  • (2) 從這K個用戶集合排除Ui的偏好商品,剩下的Item集合爲{Item0, Item1, …Itemm};
  • (3) 對Item集合裏每個Itemj計算Ui可能偏好程度值pref(Ui, Itemj),並把Item按此數值從高到低排序,前N個item推薦給用戶Ui。

對相同用戶重複獲得推薦結果,我們可以改用CachingRecommender來包裝GenericUserBasedRecommender對象,將推薦結果緩存起來:

Recommender cachingRecommender = new CachingRecommender(recommender);

上面代碼可以在main方法中直接運行,然後,我們可以獲取推薦模型的評分:

//使用平均絕對差值獲得評分
RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator();
// 用RecommenderBuilder構建推薦引擎
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
    @Override
    public Recommender buildRecommender(DataModel model) throws TasteException {
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
        UserNeighborhood neighborhood = new NearestNUserNeighborhood(4, similarity, model);
        return new GenericUserBasedRecommender(model, neighborhood, similarity);
    }
};
// Use 70% of the data to train; test using the other 30%.
double score = evaluator.evaluate(recommenderBuilder, null, model, 0.7, 1.0);
System.out.println(score);

接下來,可以獲取推薦結果的查準率和召回率:

RecommenderIRStatsEvaluator statsEvaluator = new GenericRecommenderIRStatsEvaluator();
// Build the same recommender for testing that we did last time:
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
    @Override
    public Recommender buildRecommender(DataModel model) throws TasteException {
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
        UserNeighborhood neighborhood = new NearestNUserNeighborhood(4, similarity, model);
        return new GenericUserBasedRecommender(model, neighborhood, similarity);
    }
};
// 計算推薦4個結果時的查準率和召回率
IRStatistics stats = statsEvaluator.evaluate(recommenderBuilder,null, model, null, 4,
        GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD,1.0);
System.out.println(stats.getPrecision());
System.out.println(stats.getRecall());

如果是基於物品的推薦,代碼大體相似,只是沒有了UserNeighborhood,然後將上面代碼中的User換成Item即可,完整代碼如下:

File modelFile modelFile = new File("intro.csv");

DataModel model = new FileDataModel(new File(file));

// Build the same recommender for testing that we did last time:
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
    @Override
    public Recommender buildRecommender(DataModel model) throws TasteException {
        ItemSimilarity similarity = new PearsonCorrelationSimilarity(model);
        return new GenericItemBasedRecommender(model, similarity);
    }
};

//獲取推薦結果
List<RecommendedItem> recommendations = recommenderBuilder.buildRecommender(model).recommend(1, 4);

for (RecommendedItem recommendation : recommendations) {
    System.out.println(recommendation);
}

//計算評分
RecommenderEvaluator evaluator =
        new AverageAbsoluteDifferenceRecommenderEvaluator();
// Use 70% of the data to train; test using the other 30%.
double score = evaluator.evaluate(recommenderBuilder, null, model, 0.7, 1.0);
System.out.println(score);

//計算查全率和查準率
RecommenderIRStatsEvaluator statsEvaluator = new GenericRecommenderIRStatsEvaluator();

// Evaluate precision and recall "at 2":
IRStatistics stats = statsEvaluator.evaluate(recommenderBuilder,
        null, model, null, 4,
        GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD,
        1.0);
System.out.println(stats.getPrecision());
System.out.println(stats.getRecall());

在Spark中運行

在Spark中運行,需要將Mahout相關的jar添加到Spark的classpath中,修改/etc/spark/conf/spark-env.sh,添加下面兩行代碼:

SPARK_DIST_CLASSPATH="$SPARK_DIST_CLASSPATH:/usr/lib/mahout/lib/*"
SPARK_DIST_CLASSPATH="$SPARK_DIST_CLASSPATH:/usr/lib/mahout/*"

然後,以本地模式在spark-shell中運行下面代碼交互測試:

//注意:這裏是本地目錄
val model = new FileDataModel(new File("intro.csv"))

val evaluator = new RMSRecommenderEvaluator()
val recommenderBuilder = new RecommenderBuilder {
  override def buildRecommender(dataModel: DataModel): Recommender = {
    val similarity = new LogLikelihoodSimilarity(dataModel)
    new GenericItemBasedRecommender(dataModel, similarity)
  }
}

val score = evaluator.evaluate(recommenderBuilder, null, model, 0.95, 0.05)
println(s"Score=$score")

val recommender=recommenderBuilder.buildRecommender(model)
val users=trainingRatings.map(_.user).distinct().take(20)

import scala.collection.JavaConversions._

val result=users.par.map{user=>
  user+","+recommender.recommend(user,40).map(_.getItemID).mkString(",")
}

https://github.com/sujitpal/mia-scala-examples上面有一個評估基於物品或是用戶的各種相似度下的評分的類,叫做 RecommenderEvaluator,供大家學習參考。

分佈式運行

Mahout提供了org.apache.mahout.cf.taste.hadoop.item.RecommenderJob類以MapReduce的方式來實現基於物品的協同過濾,查看該類的使用說明:

$ hadoop jar /usr/lib/mahout/mahout-examples-0.9-cdh5.4.0-job.jar org.apache.mahout.cf.taste.hadoop.item.RecommenderJob
15/06/10 16:19:34 ERROR common.AbstractJob: Missing required option --similarityClassname
Missing required option --similarityClassname
Usage:
 [--input <input> --output <output> --numRecommendations <numRecommendations>
--usersFile <usersFile> --itemsFile <itemsFile> --filterFile <filterFile>
--booleanData <booleanData> --maxPrefsPerUser <maxPrefsPerUser>
--minPrefsPerUser <minPrefsPerUser> --maxSimilaritiesPerItem
<maxSimilaritiesPerItem> --maxPrefsInItemSimilarity <maxPrefsInItemSimilarity>
--similarityClassname <similarityClassname> --threshold <threshold>
--outputPathForSimilarityMatrix <outputPathForSimilarityMatrix> --randomSeed
<randomSeed> --sequencefileOutput --help --tempDir <tempDir> --startPhase
<startPhase> --endPhase <endPhase>]
--similarityClassname (-s) similarityClassname    Name of distributed
                                                  similarity measures class to
                                                  instantiate, alternatively
                                                  use one of the predefined
                                                  similarities
                                                  ([SIMILARITY_COOCCURRENCE,
                                                  SIMILARITY_LOGLIKELIHOOD,
                                                  SIMILARITY_TANIMOTO_COEFFICIEN
                                                  T, SIMILARITY_CITY_BLOCK,
                                                  SIMILARITY_COSINE,
                                                  SIMILARITY_PEARSON_CORRELATION
                                                  ,
                                                  SIMILARITY_EUCLIDEAN_DISTANCE]
                                                  )

可見,該類可以接收的命令行參數如下:

  • --input(path): 存儲用戶偏好數據的目錄,該目錄下可以包含一個或多個存儲用戶偏好數據的文本文件;
  • --output(path): 結算結果的輸出目錄
  • --numRecommendations (integer): 爲每個用戶推薦的item數量,默認爲10
  • --usersFile (path): 指定一個包含了一個或多個存儲userID的文件路徑,僅爲該路徑下所有文件包含的userID做推薦計算 (該選項可選)
  • --itemsFile (path): 指定一個包含了一個或多個存儲itemID的文件路徑,僅爲該路徑下所有文件包含的itemID做推薦計算 (該選項可選)
  • --filterFile (path): 指定一個路徑,該路徑下的文件包含了[userID,itemID]值對,userID和itemID用逗號分隔。計算結果將不會爲user推薦[userID,itemID]值對中包含的item (該選項可選)
  • --booleanData (boolean): 如果輸入數據不包含偏好數值,則將該參數設置爲true,默認爲false
  • --maxPrefsPerUser (integer): 在最後計算推薦結果的階段,針對每一個user使用的偏好數據的最大數量,默認爲10
  • --minPrefsPerUser (integer): 在相似度計算中,忽略所有偏好數據量少於該值的用戶,默認爲1
  • --maxSimilaritiesPerItem (integer): 針對每個item的相似度最大值,默認爲100
  • --maxPrefsPerUserInItemSimilarity (integer): 在item相似度計算階段,針對每個用戶考慮的偏好數據最大數量,默認爲1000
  • --similarityClassname (classname): 向量相似度計算類
  • outputPathForSimilarityMatrix:SimilarityMatrix輸出目錄
  • --randomSeed:隨機種子 –sequencefileOutput:序列文件輸出路徑
  • --tempDir (path): 存儲臨時文件的目錄,默認爲當前用戶的home目錄下的temp目錄
  • --startPhase
  • --endPhase
  • --threshold (double): 忽略相似度低於該閥值的item對

一個例子如下,使用SIMILARITY_LOGLIKELIHOOD相似度推薦物品:

$ hadoop jar /usr/lib/mahout/mahout-examples-0.9-cdh5.4.0-job.jar org.apache.mahout.cf.taste.hadoop.item.RecommenderJob --input /tmp/mahout/part-00000 --output /tmp/mahout-out  -s SIMILARITY_LOGLIKELIHOOD

默認情況下,mahout使用的reduce數目爲1,這樣造成大數據處理時效率較低,可以通過參數mahout執行腳本中的MAHOUT_OPTS中的-Dmapred.reduce.tasks參數指定reduce數目。

上面命令運行完成之後,會在當前用戶的hdfs主目錄生成temp目錄,該目錄可由--tempDir (path)參數設置:

$ hadoop fs -ls temp
Found 10 items
-rw-r--r--   3 root hadoop          7 2015-06-10 14:42 temp/maxValues.bin
-rw-r--r--   3 root hadoop    5522717 2015-06-10 14:42 temp/norms.bin
drwxr-xr-x   - root hadoop          0 2015-06-10 14:41 temp/notUsed
-rw-r--r--   3 root hadoop          7 2015-06-10 14:42 temp/numNonZeroEntries.bin
-rw-r--r--   3 root hadoop    3452222 2015-06-10 14:41 temp/observationsPerColumn.bin
drwxr-xr-x   - root hadoop          0 2015-06-10 14:47 temp/pairwiseSimilarity
drwxr-xr-x   - root hadoop          0 2015-06-10 14:52 temp/partialMultiply
drwxr-xr-x   - root hadoop          0 2015-06-10 14:39 temp/preparePreferenceMatrix
drwxr-xr-x   - root hadoop          0 2015-06-10 14:50 temp/similarityMatrix
drwxr-xr-x   - root hadoop          0 2015-06-10 14:42 temp/weights

觀察yarn的管理界面,該命令會生成9個任務,任務名稱依次是:

  • PreparePreferenceMatrixJob-ItemIDIndexMapper-Reducer
  • PreparePreferenceMatrixJob-ToItemPrefsMapper-Reducer
  • PreparePreferenceMatrixJob-ToItemVectorsMapper-Reducer
  • RowSimilarityJob-CountObservationsMapper-Reducer
  • RowSimilarityJob-VectorNormMapper-Reducer
  • RowSimilarityJob-CooccurrencesMapper-Reducer
  • RowSimilarityJob-UnsymmetrifyMapper-Reducer
  • partialMultiply
  • RecommenderJob-PartialMultiplyMapper-Reducer

從任務名稱,大概可以知道每個任務在做什麼,如果你的輸入參數不一樣,生成的任務數可能不一樣,這個需要測試一下才能確認。

在hdfs上查看輸出的結果,用戶和推薦結果用\t分隔,推薦結果中物品之間用逗號分隔,物品後面通過冒號連接評分:

843 [10709679:4.8334665,8389878:4.833426,9133835:4.7503786,10366169:4.7503185,9007487:4.750272,8149253:4.7501993,10366165:4.750115,9780049:4.750108,8581254:4.750071,10456307:4.7500467]
6253    [10117445:3.0375953,10340299:3.0340924,8321090:3.0340924,10086615:3.032164,10436801:3.0187714,9668385:3.0141575,8502110:3.013954,10476325:3.0074399,10318667:3.0004222,8320987:3.0003839]

使用Java API方式執行,請參考Mahout分步式程序開發 基於物品的協同過濾ItemCF

在Scala或者Spark中,可以以Java API或者命令方式運行,最後還可以通過Spark來處理推薦的結果,例如:過濾、去重、補足數據,這部分內容不做介紹。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章