• Mahout in action 中文版3.推荐器的数据表达3.1


    3

    推荐器的数据表达

    本章概要:

    Mahout的推荐数据如何呈现

    DataModel 的实现和使用

    布尔型的偏好数据处理

    推荐结果的好坏取决于数据的数量和质量。“巧妇难为无米之炊”用在这里再合适不过了。数据质量高本身是好事,而且数据量大也是好事。推荐算法天生就是数据密集型的,它们擅长处理大数据。算法运行的性能和数据的质量和呈现形式直接相关。一个好的数据结构可以影响算法的处理能力、处理规模以及很多其他性能。

    本章探索了一些关键的类,这些类主要负责呈现数据、访问推荐器相关数据。你将会了解为何Mahout提供的数据表达方式会影响整个框架的效率和可扩展性。本章将会详细介绍一个Mahout中关键的概念,它提供了数据访问功能:DataModel。

    最后,我们将会讨论布尔型的偏好数据,这里没有“偏好指数”这个概念,所以它需要特殊的处理方法。接下来我们将介绍推荐数据的基本单元:“用户-项目 偏好指数”(user-item preference)。

    3.1 偏好指数的数据表达

    我们向推荐器输入的偏好指数是一个数值,代表了“喜欢到什么程度”。数据的结果十分简单,就是用户-项目-偏好指数,它的数据量往往非常大,有时候还会省略偏好指数。

    3.1.1 Preference对象

    Preference是一个抽象的接口,包括user ID、item ID和一个偏好值。它表示一个用户对一个项目的喜好程度。它作为接口最常被GenericPreference去实现。举个例子,你要去表达用户123对项目456喜好程度是3.0,那么你应该这么定义:new GenericPreference(123, 456, 3.0f)。

    那么如何去表达一个偏好的集合呢?如果你给出的答案是:Collection<Preference> 或 Preference[],那么只能说你错了,而且很多Mahout中的API都不是这么做的。集合和数组的操作对于呈现大量的Preference对象效率是非常低的,如果你从来没有仔细调查过java高层对象,那么准备好承受打击吧!

    一个GenericPreference对象包含20个有用的字节:8个字节用来存储用户ID(java long),8个字节用来存储项目ID(long),最后4个字节存储偏好指数(float)。整个对象足足有28个字节,吃了一惊吧。而且它的大小会根据JVM的实现不同而变化。我说的这个数据是参照了苹果64位Java 6 VM forMac OS X 10.6,它还包括了8字节的对象引用,而且由于一些其他原因还有20字节需要占用。因此GenericPreference对象已经多消耗了140%的存储了,仅仅因为它被封装的太厉害了。

    那么接下来怎么办?在推荐器中调用这样的对象是十分平常的事情,但是我们不难看出,在一个偏好集合之中,用户的ID或者项目ID很多都是冗余的。

    3.1.2 PreferenceArray的实现

    PreferenceArray是偏好集合的一个接口,它提供了一些类似数组中的API。举个例子,GenericUserPreferenceArray代表了所有的偏好个一个用户关联。在内部它只包含一个用户ID和多个项目ID以及一个偏好指数的浮点型数组。边际内存要求每个偏好占12字节(项目ID8字节,偏好指数4字节)。现在和之前的那个48字节的Preference对象比一比,GC显示出了四重偏好不仅仅是更快了,而且分配的对象也更加少了。比较一下图3.1和3.2,理解这样的节省是如何实现的。

     

     

    图3.1 低效的偏好数组的实现。灰色代表高层类的内存消耗,白色代表有用的数据,也包括累的引用。

     

     

     

    图3.2 高效的存储方式—GenericUserPreferenceArray

    下面的代码展示了PreferenceArray的典型构造方法。

    清单3.1 PreferenceArray的偏好设置方法

    PreferenceArray user1Prefs = new GenericUserPreferenceArray(2);
    user1Prefs.setUserID(0, 1L); A
    user1Prefs.setItemID(0, 101L);
    user1Prefs.setValue(0, 2.0f); B
    user1Prefs.setItemID(1, 102L);
    user1Prefs.setValue(1, 3.0f); C
    Preference pref = user1Prefs.get(1); D
    

    A 设置用户ID为1

    B 用户1对项目101感兴趣,偏好指数2.0

    C 用户1对项目102感兴趣,偏好指数3.0

    D 为项目2实例化一个偏好对象

    另外还有一个对象GenericItemPreferenceArray,它是吧所有的偏好都关联到一个项目上去,它的用法和用户的很类似。

    3.1.3 加速收集

    你可能觉得万事大吉了,Mahout自己改造了一个对象数组。但是你高兴的太早了,这还没有完。你还记得我们之前提到过规模的重要性吗?但愿你已经说服自己你将要面临的问题都是非比寻常的大数据,所以我们需要一个非比寻常的答案。

    PreferenceArray为了减少占用内存使其实现变得复杂是值得的。减少75%的内存不仅仅是减少了百万字节。其实在适当的场合它可以减少万兆字节,这就是他是适合与不适合在你硬盘上处理的区别,或许,这就是你需不需要在多插几个内存条或者重新搞一台服务器的区别了。改动虽小,但是真的很低碳环保!

    3.1.4 FastByIDMap与FastIDSet

    当你听说Mahout用了很多经典数据结构,比如map和set,请不要吃惊,当然他没有用Java中的一些数据结构,比如TreeSet、HashMap。当你阅读它的API之后,可能你会发现FastByIDMap 和FastIDSet。它们两个非常像Map和Set。但是它们是专门为推荐器设计的,其目的是为了减少内存占用,而不是提升性能。

    我们之前所做的工作不是为了苛求Java关于集合的架构。相反,这些对象和方法是为了在更宽泛的情形保持高效。它们不可能假设很多特定的场景去设计。Mahout所要使用的更为特别、专门。它做了很多严格的假设,主要的区别在于:

    FastByIDMap 和HashMap一样,也是基于哈希的。它使用了线性散列而不是分离链接散列法去解决冲突。这就避免了额外的Map实体对象的引入,就像我们之前所讨论过的,对象往往会消耗掉很多内存空间。

    键和值都采用了long类型,而不是对象。使用long类型的键可以节约空间和提升性能。

    Set的底层实现并没用使用Map

    FastByIDMap可以被用作是缓存,因为它有一个最大容量的概念。当超过这个容量时,不经常用的单元将会被移除。

    另外,存储方面有很大的不同:FastIDSet每个成员需要大概14个字节,而HashSet需要84个字节。FastByIDMap每个实例消耗28字节,而HashMap为84字节。所以说,当你考虑了使用的特定情形,那么性能的提升空间是非常大的。它们为推荐器节约了很多空间,而这一点优化意义也超出了优化本身。那么,这样聪明的设计会在哪里大显神通呢?

    (翻译 By 花考拉)

  • 相关阅读:
    lua module
    lua require
    lua io
    lua table2
    lua table1
    【leetcode】魔术排列
    【leetcode】速算机器人
    【leetcode】黑白方格画
    【leetcode】根据数字二进制下 1 的数目排序
    【leetcode】插入区间
  • 原文地址:https://www.cnblogs.com/colorfulkoala/p/2604952.html
Copyright © 2020-2023  润新知