CWYAlpha

Just another WordPress.com site

Thought this was cool: 评分数据的存储——Mahout笔记之二

leave a comment »


趁热打铁~
这是Mahout in action一书的第三章。

推荐系统的质量很大程度上依赖于数据,所以先讨论数据的存储。
如下代码生成一个打分数据:

1
2
new GenericPreference(123, 456, 3.0f)
//用户123对物品456的偏好为3.0

但是表示一组打分的时候通常不会使用 Collection 或者 Preference[] 这种方式。
然后我们讨论一些原因。
一个GenericPreference占用了20字节,这包括8字节的User ID (long),8字节的Item ID (long),4字节的打分(float)。
但在实际代码中它会占用28字节。这里包含了一些数据对齐之类的原因。所以实际使用内存是真正需要的28/20=140%,浪费非常严重。(实际数据对齐还和操作系统有关)
另外数据在大多数算法中其实都是需要以“某一个用户的所有打分”或者“某一个物品的所有打分”的这种形式列出来。在这种情况下,User ID和Item ID都只需要存储一次。因此,Mahout给了其他的更合适的选择。
PreferenceArray就是一个类似数组的形式存储。比如GenericUserPreferenceArray表示的是和一个用户有关的所有打分,它存储了一个User ID,一个Item ID数组还有一个打分数组。示例代码:

1
2
3
4
5
6
7
PreferenceArray user1Prefs = new GenericUserPreferenceArray(2);
user1Prefs.setUserID(0, 1L);
user1Prefs.setItemID(0, 101L);
user1Prefs.setValue(0, 2.0f);
user1Prefs.setItemID(1, 102L);
user1Prefs.setValue(1, 3.0f);
Preference pref = user1Prefs.get(1);

类似的,还有GenericItemPreferenceArray存储和某一个物品相关的所有打分信息。
除了数组以外,Mahout还有类似map和set的实现。这包括FastMap, FastByIDMap, FastIDSet。他们主要是为了提高速度,同时兼顾内存占用。直接用Java的HashSet占用内存较大。
FastByIDMap是基于hash的,使用线性探查法(linear probing)解决冲突,他就像是一个Cache,因为超过大小的话最不常用的一个会被替换掉。看一个示例:

1
2
3
4
5
6
7
8
9
10
11
FastByIDMap<PreferenceArray> preferences = 
  new FastByIDMap<PreferenceArray>();
PreferenceArray prefsForUser1 = new GenericUserPreferenceArray(10);
prefsForUser1.setUserID(0, 1L);
prefsForUser1.setItemID(0, 101L);
prefsForUser1.setValue(0, 3.0f);
prefsForUser1.setItemID(1, 102L);
prefsForUser1.setValue(1, 4.5f);
//...(8 more)
preferences.put(1L, prefsForUser1);
DataModel model = new GenericDataModel(preferences);

在GenericDataModel中,每个评分大约占用28字节内存,这除了数据还包括数据结构的需要(索引等)。
除了这种形式以外,数据的建立还可以基于文件。文件除了上一章说的可以是csv那种用逗号分隔的,也可以是tab分割的。
refresh()这个方法用来在读取文件后更新一下,使得数据结构按照需要的顺序存储。

1
2
3
4
DataModel dataModel = new FileDataModel(new File("input.csv");
Recommender recommender = new SlopeOneRecommender(dataModel);
...
recommender.refresh(null);

FileDataModel还支持更新文件,允许读取一个更新文件来修改之前读过的数据。比如:

1
2
1,108,3.0
1,103,

第一行表示添加(或者更新)用户1对物品108的打分为3.0,第二行表示删除用户1对物品103的打分。
更新文件必须和主文件在同一个目录下,他们在第一个.之前的文件名必须一致,例如主文件是foo.txt.gz,更新文件是 foo.1.txt.gz和 foo.2.txt.gz(这也说明数据文件是可以使用压缩文件的)。
由于数据量可能很大,Mahout提供了数据库接口,需要进行配置。由于不详细研究如何使用Mahout,所以这部分跳过。
然后讨论如何处理无打分的数据。例如一个网站统计用户浏览文章的记录,用于推荐其他文章。网站只有用户浏览的记录,一般不会让用户打分。另外,即使有打分,很多时候忽略打分的取值是更好的选择。比如用户1给电影103打了4.5分,我们可以只考虑用户1和电影103有关系。
在Mahout中,这种情况成为布尔偏好(Boolean preferences)。文中称缺少合适的词汇来表达这个意思,因为实际上这不代表只有yes和no两种偏好,应当是三种情况:喜欢,不喜欢,不详。
如果改为使用布尔偏好,则能大大减少内存消耗。在Mahout中使用GenericBooleanPrefDataModel存储布尔偏好的数据(代替GenericDataModel),它只存了FastIDSet,没有打分。要注意的是,getPreferenceValue()这个方法依然是可以用的,不会抛出异常。他返回的总是同一个数值:1.0
下面是使用GenericBooleanPrefDataModel的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DataModel model = new GenericBooleanPrefDataModel(
    new FileDataModel(new File("ua.base")));
RecommenderIRStatsEvaluator evaluator =
  new GenericRecommenderIRStatsEvaluator();
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
  @Override
  public Recommender buildRecommender(DataModel model) {
    UserSimilarity similarity = new LogLikelihoodSimilarity(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));
//GenericBooleanPrefDataModel使用FastIDSet,
//而不是PreferenceArray,所以,用toDataMap进行转换
  }
};
IRStatistics stats = evaluator.evaluate(
    recommenderBuilder, modelBuilder, model, null, 10,
    GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD,
    1.0);
System.out.println(stats.getPrecision());
System.out.println(stats.getRecall());

原文先卖关子给了个有bug的程序,这里就不卖关子了……
但是那个bug是要注意的:PearsonCorrelationSimilarity是不能使用的,因为没有打分。类似的,EuclideanDistanceSimilarity也不能用。
这里计算相似度用的是LogLikelihoodSimilarity,以后再详细讨论这些相似度。
上面的代码还是不太好。GenericUserBasedRecommender虽然是可以用的,但是因为相似度计算的问题,在布尔偏好的时候不好使。应当换用 GenericBooleanPrefUserBasedRecommender
FileDataModel还是可以用的,只要文件每行只有两个数值User ID和Item ID,他会正确处理。
布尔和非布尔偏好是不能混用的,要么删掉所有打分,要么把缺失的打分用某种平均值估计出来。

from Dora Blog: http://diaorui.net/?p=321

Written by cwyalpha

七月 2, 2012 在 8:39 上午

发表在 Uncategorized

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: