[原]Mahout 对推荐数据的抽象表示(下部分)
这篇博客是延续上部分的补充: Mahout 对推荐数据的抽象表示(上部分)
处理无Preference values 数据
下面都是围绕Mahout对没有Preference values的数据的推荐。
有时进入推荐引擎的数据没有Preference values,而是只有相关联的一个userID、itemID,它们之间有多强的联系我们没有一个Preference values来量化衡量。
例如在新闻推荐中,新闻网站根据你的浏览日志来给你推荐新闻,相当于也就是给每个user 和不同类型新闻建立联系,但是这个联系强弱不是让用户去给新闻打分来建立的而是通过用户点击新闻行为进行学习而得来的。
顺便简单介绍下用户行为数据,用户行为数据最简单存在就是日志,网站运行过程记录了大量的原始日志,这些日志就是用户最原始数据存储。用户行为在个性化推荐中一般分为两种:显性反馈行为、隐性反馈行为,从字面意思就很好理解显性反馈就是用户明确表示对物品喜好的行为,比如最经典的5分评价机制和两档评分机制(喜欢/不喜欢),隐性反馈最具代表的就是页面浏览行为,通常对于这种隐性反馈系统需要学习得到user 和 item 强弱关联。
下图是摘取自Mahout in action第三章作者一个描述是否有Preference values示意图:
书中还描述了一个忽略Preference values音乐推荐场景,具体可以看第三章内容。
Mahout 中 无Preference values 代码实现
没有Preference values 可以简化我们对Preference 的表示(上部分我们详细描述了Preference 这个数据结构在Mahout中实现),这样我们可以有更好性能提升和更少的内存使用。
上部分我们用到了一个GenericDataModel类来实现DataModel,这里我们使用GenericBooleanPrefDataModel。
我们讨论类中一些方法如:getItemIDsFromUser方法会执行很快,因为实现中只需要返回已经存好的数据结构,源码如下:
@Override public FastIDSet getItemIDsFromUser(long userID) throws TasteException { FastIDSet itemIDs = preferenceFromUsers.get(userID); if (itemIDs == null) { throw new NoSuchUserException(userID); } return itemIDs; }
一些方法如getPreferencesFromUser会执行比较慢,因为执行时需要new 起来新的对象而不是上部分中讲到的直接存储Preference values在PreferenceArrays返回,参考源码:
@Override public PreferenceArray getPreferencesFromUser(long userID) throws NoSuchUserException { FastIDSet itemIDs = preferenceFromUsers.get(userID); if (itemIDs == null) { throw new NoSuchUserException(userID); } PreferenceArray prefArray = new BooleanUserPreferenceArray(itemIDs.size()); int i = 0; LongPrimitiveIterator it = itemIDs.iterator(); while (it.hasNext()) { prefArray.setUserID(i, userID); prefArray.setItemID(i, it.nextLong()); i++; } return prefArray; }
大家可能会有好奇getPreferenceValue方法返回值,因为这里根本就没有Preference values,设计者统一返回1.0f
@Override public Float getPreferenceValue(long userID, long itemID) throws NoSuchUserException { FastIDSet itemIDs = preferenceFromUsers.get(userID); if (itemIDs == null) { throw new NoSuchUserException(userID); } if (itemIDs.contains(itemID)) { return 1.0f; } return null; }
看一个简单使用GenericBooleanPrefDataModel示例程序:
package recommender; import org.apache.mahout.cf.taste.common.TasteException; import org.apache.mahout.cf.taste.eval.DataModelBuilder; import org.apache.mahout.cf.taste.eval.RecommenderBuilder; import org.apache.mahout.cf.taste.eval.RecommenderEvaluator; import org.apache.mahout.cf.taste.impl.common.FastByIDMap; import org.apache.mahout.cf.taste.impl.eval.AverageAbsoluteDifferenceRecommenderEvaluator; import org.apache.mahout.cf.taste.impl.model.GenericBooleanPrefDataModel; import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood; import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender; import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity; import org.apache.mahout.cf.taste.model.DataModel; import org.apache.mahout.cf.taste.model.PreferenceArray; import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood; import org.apache.mahout.cf.taste.recommender.Recommender; import org.apache.mahout.cf.taste.similarity.UserSimilarity; import java.io.File; class IREvaluatorBooleanPrefIntro1 { private IREvaluatorBooleanPrefIntro1() { } public static void main(String[] args) throws Exception { DataModel model = new GenericBooleanPrefDataModel( GenericBooleanPrefDataModel.toDataMap( new FileDataModel(new File("ua.base")))); RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator(); RecommenderBuilder recommenderBuilder = new RecommenderBuilder() { @Override public Recommender buildRecommender(DataModel model) throws TasteException { UserSimilarity similarity = new PearsonCorrelationSimilarity(model); UserNeighborhood neighborhood = new NearestNUserNeighborhood(10, similarity, model); return new GenericUserBasedRecommender(model, neighborhood, similarity); } }; DataModelBuilder modelBuilder = new DataModelBuilder() { @Override public DataModel buildDataModel(FastByIDMap<PreferenceArray> trainingData) { return new GenericBooleanPrefDataModel( GenericBooleanPrefDataModel.toDataMap(trainingData)); } }; double score = evaluator.evaluate( recommenderBuilder, modelBuilder, model, 0.9, 1.0); System.out.println(score); } }
运行结果如下:
Exception in thread "main" java.lang.IllegalArgumentException: DataModel doesn't have preference values at com.google.common.base.Preconditions.checkArgument(Preconditions.java:125) at org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity.<init>(PearsonCorrelationSimilarity.java:74) at org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity.<init>(PearsonCorrelationSimilarity.java:66) at recommender.IREvaluatorBooleanPrefIntro1$1.buildRecommender(IREvaluatorBooleanPrefIntro1.java:37) at org.apache.mahout.cf.taste.impl.eval.AbstractDifferenceRecommenderEvaluator.evaluate(AbstractDifferenceRecommenderEvaluator.java:125) at recommender.IREvaluatorBooleanPrefIntro1.main(IREvaluatorBooleanPrefIntro1.java:50)
通过抛出异常我想大家也差不多明白,一些 similarity metric 像pearson、欧式距离等在没有Preference values时是不能工作的,如果没有Preference values 这些相似性衡量标准将失去意义。但是没有Preference values同样可以计算类似IR中 precision 和 recall。
package recommender; import org.apache.mahout.cf.taste.common.TasteException; import org.apache.mahout.cf.taste.eval.DataModelBuilder; import org.apache.mahout.cf.taste.eval.IRStatistics; import org.apache.mahout.cf.taste.eval.RecommenderBuilder; import org.apache.mahout.cf.taste.eval.RecommenderIRStatsEvaluator; import org.apache.mahout.cf.taste.impl.common.FastByIDMap; import org.apache.mahout.cf.taste.impl.eval.GenericRecommenderIRStatsEvaluator; import org.apache.mahout.cf.taste.impl.model.GenericBooleanPrefDataModel; import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood; import org.apache.mahout.cf.taste.impl.recommender.GenericBooleanPrefUserBasedRecommender; import org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity; import org.apache.mahout.cf.taste.model.DataModel; import org.apache.mahout.cf.taste.model.PreferenceArray; import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood; import org.apache.mahout.cf.taste.recommender.Recommender; import org.apache.mahout.cf.taste.similarity.UserSimilarity; import java.io.File; class IREvaluatorBooleanPrefIntro2 { private IREvaluatorBooleanPrefIntro2() { } public static void main(String[] args) throws Exception { DataModel model = new GenericBooleanPrefDataModel( GenericBooleanPrefDataModel.toDataMap( new FileDataModel(new File("ua.base")))); RecommenderIRStatsEvaluator evaluator = new GenericRecommenderIRStatsEvaluator(); RecommenderBuilder recommenderBuilder = new RecommenderBuilder() { @Override public Recommender buildRecommender(DataModel model) throws TasteException { UserSimilarity similarity = new LogLikelihoodSimilarity(model); UserNeighborhood neighborhood = new NearestNUserNeighborhood(10, similarity, model); return new GenericBooleanPrefUserBasedRecommender(model, neighborhood, similarity); } }; DataModelBuilder modelBuilder = new DataModelBuilder() { @Override public DataModel buildDataModel(FastByIDMap<PreferenceArray> trainingData) { return new GenericBooleanPrefDataModel( GenericBooleanPrefDataModel.toDataMap(trainingData)); } }; IRStatistics stats = evaluator.evaluate( recommenderBuilder, modelBuilder, model, null, 10, GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD, 1.0); System.out.println(stats.getPrecision()); System.out.println(stats.getRecall()); } }
我们可以注意上面代码中的相似度衡量变成了:LogLikelihoodSimilarity 这个是一个不依赖具体数字来计算的相似度所以可以work。