使用Apache Mahout制作 推荐引擎

目录

创建工程

基本概念

关键概念

基于用户与基于项目的分析

计算相似度的方法

协同过滤

基于内容的过滤

混合方法

创建一个推荐引擎

图书评分数据集

加载数据

从文件加载数据

从数据库加载数据

内存数据库

协同过滤

基于用户的过滤

基于项目的过滤

添加自定义规则到推荐引擎

评估

在线学习引擎

基于内容的过滤

完整代码


推荐引擎可能是当今初创公司应用最多的一种数据科学方法。有两项主要技术用来创建一个 推系统:基于内容的过滤与协同过滤。基于内容的过滤算法使用项目属性寻找带有相似属性的 项目协同过滤算法关注的是用户的评分或者其他用户的行为,它基于拥有类似行为的用户喜好 与购买物品进行推荐。 本章先讲解基本概念,它们是理解推荐引擎原理必需的内容;然后演示如何使用Apache Mahout中的各种算法实现快速创建一个可扩展的推荐引擎。本章内容涵盖如下主题:

 如何创建一个推荐引擎

 准备Apache Mahout

 基于内容的方法

 协同过滤方法

到本章结束时,你将知道我们的问题适合使用哪种推荐引擎进行解决,以及如何快速创建这 样个推荐引擎。

创建工程

接着上一篇文章工程,pom添加:

        <dependency>
            <groupId>org.apache.mahout</groupId>
            <artifactId>mahout-mr</artifactId>
            <version>0.10.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.mahout</groupId>
            <artifactId>mahout-integration</artifactId>
            <version>0.7</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.16.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

基本概念

推荐引擎的目标是向用户展示他们感兴趣的项目。与搜索引擎不同的是,相关内容通常出现 在一个网站中,用户不必创建查询来请求它。因为推荐引擎会观察用户的行为,并且在用户不知 情的情况下为他们创建查询。

可以这么说,推荐引擎最著名的例子就是www.amazon.com,它使用多种方法为用户做个性 化推荐。

向我们展示了一种商品推荐的例子——“购买这件商品的顾客还买了……”。这是 一个基于项目的协同推荐的例子,在这种推荐方式下,与特定项目相似的项目都会得到推荐。相 关内容稍后讲解。

关键概念

推荐引擎需要如下4个输入做出推荐:
 使用属性描述的项目信息;

 用户资料,比如年龄范围、性别、位置、朋友等;

 用户交互,比如评级、浏览、标记、比较、保存、电邮;

 显示项目上下文,比如项目的分类与地理位置。
获得这些输入后,推荐引擎将其组合在一起,帮助我们回答如下问题:
 购买、观看、浏览、收藏过这个项目的用户还买了、看了、浏览了、收藏了……

 与这个项目类似的项目

 你可能认识的其他用户

 和你类似的其他用户
下面详细介绍这种组合是如何工作的。

基于用户与基于项目的分析

创建推荐引擎时要搞清楚,推荐引擎尝试推荐一个特定项目时,搜索的是相关项目还是相关 用户。

基于项目的分析中,引擎的主要任务是找出那些与特定项目类似的项目;而基于用户的分 析中,引擎首先要找出那些与特定用户类似的用户。比如,先找出那些带有相同资料信息(年 龄、性别等)或行为历史(买了、看了、浏览了等)的用户,然后将相同项目推荐给其他类似 用户。 这两种方法都要求计算一个相似矩阵(similarity matrix),具体取决于分析的是项目属性还 是用户行为。

下面深入了解具体应该如何做。

计算相似度的方法

计算相似度(similarity)的基本方法有3种:
 协同过滤算法关注的是用户评分或其他用户的行为,并且基于拥有类似行为的用户喜好 与购买的物品进行推荐;

 基于内容的过滤算法使用项目属性寻找带有相似属性的项目;

 组合了协同过滤与基于内容的过滤的混合方法。
接下来,详细学习每一种方法。

协同过滤

协同过滤只基于用户评级或其他用户的行为,基于拥有相似行为的用户喜好与购买的物品进 行推荐。
协同过滤的主要优点是不依赖于项目内容,因此可以准确推荐复杂项目,而无需了解项目本 身,比如电影。它基于的假设是“人们过去认可的将来也会认可”“他们喜欢与过去喜欢的项目 相似的项目”。

这个方法的主要缺点就是所谓的冷启动(cold start),也就是说,如果想创建一个精确的协 同过滤系统,算法往往需要先有大量用户评分。因此,产品的第一个版本中通常不会使用协同过 滤,直到有了相当数量的数据积累之后才会使用。

基于内容的过滤

另一方面,基于内容的过滤建立在项目的描述与用户偏好资料之上,按照如下步骤进行组合: 首先,使用属性描述项目并找出相似项目,我们选用一个距离测度(比如余弦距离或皮尔逊相关 系数,详细内容请参考第1章有关距离测度的内容)测量项目之间的距离。接着,将用户资料输 入方程式。鉴于用户喜欢的项目类型的反馈,我们引入权重指示特定项目属性的重要程度。比如, “潘多拉电台流媒体服务应用”基于内容的过滤技术使用400多个属性创建电台。起初,一个用户 通过特定属性挑选了一支歌曲,并通过提供反馈突出重要的歌曲属性。

这个方法最初只需要很少的用户反馈信息,因此它能有效避免冷启动问题。

混合方法

那么,应该如何选择协同过滤方法与基于内容的过滤方法呢?借助协同过滤方法可以从用户 对一个内容源的行为了解用户喜好,并通过用户偏好找出其他类型的内容。基于内容的过滤方法 仅限于推荐同类型的内容,并且用户已经在使用这种类型。这对于不同的使用案例是有价值的, 比如基于用户正在浏览的新闻推荐新闻文章是有用的。但如果能够再进一步,基于正在浏览的新 闻推荐其他类型的资源(比如图书、电影),这将会更有用。

协同过滤与基于内容的过滤不是相互排斥的,某些情况下,我们可以将二者组合以产生更有 效的结果。比如,Netflix使用协同过滤分析相似用户的搜索与观看模式。此外,还使用基于内容 的过滤向用户推荐高评分影片。

混合技术有很多,比如加权混合、切换混合、分区混合、特征组合、特征扩充、级联混合、 分层混合等。机器学习与数据挖掘社区中,推荐系统一直是个活跃版块,数据科学会议上也会专 门为它设立分会场。Adomavicius 与 Tuzhilin ( 2005 )的 Toward the next generation of recommender systems: a survey of the state-of-the-art and possible extensions论文,你可以很好地了 解这些技术。论文中,作者讨论了不同方法与基本算法,并且提供了更多有价值的参考论文。需 要认真了解某个特定方法时,为了获取更多技术细节,你可以阅读弗朗西斯科·里奇等人合著的 《推荐系统:技术、评估及高效算法》。

创建一个推荐引擎

为了演示基于内容的过滤与协同过滤方法,下面创建一个图书推荐引擎。

图书评分数据集

将使用图书评分数据集(Ziegler等,2005),它由一个爬虫程序收集了4周而得到。该数 据集包含的数据涉及Book-Crossing网站的278 858个会员与1 157 112个评分,这些评分既有隐含 的也有明确的,涵盖271 379个不同的ISBN。用户数据经过匿名化处理,但含有人口统计信息。

Book-Crossing数据集包含3个文件,网站中对于这3个文件的描述如下。
 BX-Users:包含用户。请注意,用户ID(User-ID)被匿名化处理,由字符串换为整数。 如果存在统计数据则给出(位置与年龄),否则这些字段就是NULL值。

 BX-Books:图书通过各自不同的ISBN号码加以识别。无效ISBN已经被从数据集中移走。 而且,给出了一些基于内容的信息(图书名称、图书作者、出版年份、出版商),这些信 息是从Amazon Web Service那里得到的。请注意,如果图书有多个作者,则只提供第一作 者。此外还给出了指向封面图片的 URL 连接,有三种不同形式: Image-URL-S 、 Image-URL-M、Image-URL-L,即小、中、大。这些URL指向Amazon网站。

 BX-Book-Ratings:包含图书的评分信息。有些评分(Book-Rating)很明确,使用数字1~10 表示(分值越高表示越值得阅读);有些评分不明,使用0表示。

加载数据

根据数据的存储位置(文件或数据库),有两种不同的数据加载方法可以选用。首先详细讲 解如何从文件加载数据,包括如何处理自定义格式。之后快速了解如何从数据库加载数据。

从文件加载数据

可以使用FileDataModel类从文件加载数据,文件中的数据以逗号进行分隔,每一行顺序包含userID、itemID、preference(可选)、timestamp(可选),格式如下:

userID, itemID[, preference[, timestamp]]

可选项preference是一个二元偏好值,也就是说,对于某本书,用户要么“喜欢”,要么“不 喜欢”,没有喜爱程度的差别。

以#开始的行或空行都会被忽略。数据行也可以包含其他字段,但这些字段会被忽略。

DataModel类可以接受如下类型:
 userID、itemID是long类型

 preference是double类型

 timestamp是long类型
如果你能提供上面这种格式的数据集,那就可以简单地使用如下代码加载数据:

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

这个类不适合用于加载大量数据,比如几千万行数据。需要加载大量数据时,使用带有JDBC 支持的DataModel与数据库会更合适。

现实情况下,我们无法保证提供给我们的输入数据中,userID与itemID总为整型值。比如, 示例中,itemID对应于ISBN书号,它可以唯一地标识一本图书,那么就不是整型值。默认情况 下,FileDataModel不适合处理这种数据。

下面 考虑 itemID 是 字 符串 时应 该如何 处理 。我们 要定 义自己 的数 据模型 ,对 FileDataModel做扩展,重载readItemIDFromString(String)方法,读入字符串形式的 itemID值,而后将其转换为long型值并返回。为了将String转化为long,我们要对Mahout中 的AbstractIDMigrator辅助类做扩展,这个类的设计初衷就是为了完成这个任务。

对AbstractIDMIgrator类做扩展:

// ItemMemIDMigrator 类继承自 AbstractIDMigrator,用于管理长整型ID和字符串ID之间的映射关系。
public class ItemMemIDMigrator extends AbstractIDMigrator {
    // 使用 FastByIDMap 来存储长整型ID和字符串ID之间的映射关系。
    private FastByIDMap<String> itemIDMap;

    // 构造函数,初始化 itemIDMap,设置初始容量为 10000。
    public ItemMemIDMigrator() {
        this.itemIDMap = new FastByIDMap<String>(10000);
    }

    // 存储长整型ID和字符串ID之间的映射关系。
    // @param longID 长整型ID
    // @param stringID 字符串ID
    public void storeMapping(long longID, String stringID) {
        itemIDMap.put(longID, stringID);
    }

    // 初始化单个字符串ID的映射关系。
    // @param stringID 字符串ID
    // @throws TasteException 如果发生异常
    public void singleInit(String stringID) throws TasteException {
        // 将字符串ID转换为长整型ID,并存储映射关系。
        storeMapping(toLongID(stringID), stringID);
    }

    // 根据长整型ID获取对应的字符串ID。
    // @param l 长整型ID
    // @return 对应的字符串ID
    // @throws TasteException 如果发生异常
    @Override
    public String toStringID(long l) throws TasteException {
        // 从 itemIDMap 中获取长整型ID对应的字符串ID。
        return this.itemIDMap.get(l);
    }
}

对FileDataModel做扩展:

// StringItemIdFileDataModel 类继承自 FileDataModel,用于处理文件数据模型,特别是处理字符串形式的物品ID。
public class StringItemIdFileDataModel extends FileDataModel {
    // 用于管理长整型ID和字符串ID之间映射关系的 ItemMemIDMigrator 实例。
    public ItemMemIDMigrator itemMemIDMigrator;

    // 构造函数,初始化文件数据模型,并指定分隔符。
    // @param dataFile 数据文件
    // @param delimiterRegex 分隔符的正则表达式
    // @throws IOException 如果文件读取失败
    public StringItemIdFileDataModel(File dataFile, String delimiterRegex) throws IOException {
        super(dataFile, delimiterRegex);
    }

    // 从字符串中读取物品ID,并将其转换为长整型ID。
    // @param value 字符串形式的物品ID
    // @return 长整型形式的物品ID
    @Override
    protected long readItemIDFromString(String value) {
        // 如果 itemMemIDMigrator 为空,则初始化一个新的 ItemMemIDMigrator 实例。
        if (Objects.isNull(itemMemIDMigrator)) {
            itemMemIDMigrator = new ItemMemIDMigrator();
        }
        // 将字符串形式的物品ID转换为长整型ID。
        long readValue = itemMemIDMigrator.toLongID(value);
        try {
            // 检查长整型ID是否已经存在于 itemMemIDMigrator 中。
            // 如果不存在,则调用 singleInit 方法初始化映射关系。
            if (Objects.isNull(itemMemIDMigrator.toStringID(readValue))) {
                itemMemIDMigrator.singleInit(value);
            }
        } catch (Exception e) {
            // 捕获并打印异常。
            e.printStackTrace();
        }
        // 返回长整型形式的物品ID。
        return readValue;
    }

    // 根据长整型ID获取对应的字符串形式的物品ID。
    // @param key 长整型ID
    // @return 字符串形式的物品ID
    // @throws TasteException 如果发生异常
    public String getItemIdAsString(long key) throws TasteException {
        // 通过 itemMemIDMigrator 获取长整型ID对应的字符串形式的物品ID。
        return itemMemIDMigrator.toStringID(key);
    }
}

以上就是所有准备工作。

从数据库加载数据

除了从文件加载数据之外,还可以从数据库中加载数据,这需要用到一个JDBC数据模型。 本章不会详细讲解安装数据库、连接数据库等内容,只大致了解应该怎样做。 由于数据库连接器存在于一个单独的包——mahout-integration中,所以首先要把这个 包添加到项目的依赖列表。打开pom.xml文件,添加如下依赖关系:

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

由于要连接MySQL数据库,所以还需要向项目添加一个用于处理数据库连接的包。

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

现在,我们已经把需要的所有包都准备好了,接下来创建连接。

    public static DataModel loadFromDB() throws Exception {
        MysqlDataSource dbsource = new MysqlDataSource();
        dbsource.setUser("user");
        dbsource.setPassword("pass");
        dbsource.setServerName("localhost");
        dbsource.setDatabaseName("my_db");
        DataModel dataModelDB = new MySQLJDBCDataModel(dbsource,
                "taste_preferences", "user_id", "item_id", "preference",
                "timestamp");
        return dataModelDB;
    }

Mahout集成了针对各种数据库的JDBCDataModel实现,使得我们可以通过JDBC访问这些数 据库。默认情况下,这个类假定在JNDI名称jdbc/taste之下有DataSource可用,允许我们使 用一个taste_preferences表访问数据库,格式如下:

CREATE TABLE taste_preferences ( user_id BIGINT NOT NULL,
item_id BIGINT NOT NULL, preference REAL NOT NULL, PRIMARY KEY (user_id, item_id) ) CREATE INDEX taste_preferences_user_id_index ON taste_preferences (user_id); CREATE INDEX taste_preferences_item_id_index ON taste_preferences (item_id);

内存数据库

最后,数据模型也可以在内存中动态创建并保存。可以从一个用户偏好数组创建数据库,这 个偏好数组保存着用户对一组项目的评分。 整个创建过程如下:首先,创建一个FastByIdMap散列表,它映射到存储一组用户偏好的 数组PreferenceArray。

接下来,为用户新建一个偏好数组,存储用户评分。初始化这个数组时,必须给出占用内存 大小的参数。

接下来,为当前偏好(0号位置)设置用户ID。其实,这将为所有偏好设置用户ID。

为当前偏好(0号位置)设置项目ID。

为当前偏好(0号位置)设置偏好值。

继续为用户设置其他项目.

最后,添加用户偏好到散列映射.

接着,使用偏好散列映射初始化GenericDataModel

	public DataModel loadInMemory() {
		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);
		preferences.put(1L, prefsForUser1); // use userID as the key
		//TODO: add others users
		DataModel dataModel = new GenericDataModel(preferences);
		return dataModel;
	}

协同过滤

可以使用Mahout中的org.apache.mahout.cf.taste包创建推荐引擎,这个包之前是一个 名叫Taste的单独项目,现已并入Mahout中被继续开发。

基于Mahout的协同过滤引擎接收用户对某些项目的偏好(嗜好),返回用户可能喜欢的其他 项目。比如,一个销售图书或CD的网站使用Mahout后,可以很轻松地根据顾客以往的购物数据 找出他们可能感兴趣的CD。

下面这几个关键抽象在顶级包中都定义有相应的Mahout接口。
 DataModel:表示与用户及其项目偏好相关的信息仓库。

 UserSimilarity:定义两个用户之间的相似度。

 ItemSimilarity:定义两个项目之间的相似度。

 UserNeighborhood:为给定用户计算邻近用户。

 Recommender:为用户推荐项目。

基于用户的过滤

通过初始化前面提到的组件,可以实现一个最基本的基于用户的协同过滤器,具体步骤如下。

首先,加载数据模型.

接着,定义计算用户关联性的方法,比如使用皮尔逊相关系数

然后,定义如何指出哪些用户是相似的,即评分彼此相近的用户。

接下来,使用数据模型、邻居、相似对象初始化GenericUserBasedRecommender默认引 擎,代码如下

        String filePath = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
        StringItemIdFileDataModel dataModel = new StringItemIdFileDataModel(new File(filePath), ";");
        ItemSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
        ItemBasedRecommender recommender = new GenericItemBasedRecommender(dataModel, similarity);

以上就是全部代码,至此,第一个最基本的推荐引擎就做好了.

    public static void userBased() throws Exception {
        String filePath = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
        StringItemIdFileDataModel model = new StringItemIdFileDataModel(new File(filePath), ";");
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
        UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.1, similarity, model);
        UserBasedRecommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);
        IDRescorer rescorer = new MyRescorer();
        // List recommendations = recommender.recommend(2, 3, rescorer);
        long userID = 276704;// 276704;//212124;//277157;
        int noItems = 10;
        System.out.println("Rated items:");
        for (Preference preference : model.getPreferencesFromUser(userID)) {
            String itemISBN = model.getItemIdAsString(preference.getItemID());
            System.out.println("Item: " + books.get(itemISBN) + " | Item id: " + itemISBN + " | Value: " + preference.getValue());
        }
        System.out.println("\nRecommended items:");
        List<RecommendedItem> recommendations = recommender.recommend(userID,
                noItems);
        for (RecommendedItem item : recommendations) {
            String itemISBN = model.getItemIdAsString(item.getItemID());
            System.out.println("Item: " + books.get(itemISBN) + " | Item id: " + itemISBN + " | Value: " + item.getValue());
        }
    }

下面讨论应该如何调用推荐 引擎。首先,打印用户已经评分的项目,以及提供给这位用户的10个推荐项目。

        IDRescorer rescorer = new MyRescorer();
        String itemISBN = "042513976X";
        long itemID = dataModel.readItemIDFromString(itemISBN);
        int noItems = 10;
        System.out.println("Recommendations for item: " + books.get(itemISBN));
        System.out.println("Most similar items:");
        List<RecommendedItem> recommendations = recommender.mostSimilarItems(itemID, noItems);
        for (RecommendedItem item : recommendations) {
            itemISBN = dataModel.getItemIdAsString(item.getItemID());
            System.out.println("Item: " + books.get(itemISBN) + " | Item id: " + itemISBN + " | Value: " + item.getValue());
        }

上面代码输入如下推荐项目及其分数:

Rated items:
Item: The Handmaid's Tale | Item id: 0395404258 | Value: 0.0
Item: Get Clark Smart : The Ultimate Guide for the Savvy Consumer | Item id: 1563526298 | Value: 9.0
Item: Plum Island | Item id: 0446605409 | Value: 0.0
Item: Blessings | Item id: 0440206529 | Value: 0.0
Item: Edgar Cayce on the Akashic Records: The Book of Life | Item id: 0876044011 | Value: 0.0
Item: Winter Moon | Item id: 0345386108 | Value: 6.0
Item: Sarah Bishop | Item id: 059032120X | Value: 0.0
Item: Case of Lucy Bending | Item id: 0425060772 | Value: 0.0
Item: A Desert of Pure Feeling (Vintage Contemporaries) | Item id: 0679752714 | Value: 0.0
Item: White Abacus | Item id: 0380796155 | Value: 5.0
Item: The Land of Laughs : A Novel | Item id: 0312873115 | Value: 0.0
Item: Nobody's Son | Item id: 0152022597 | Value: 0.0
Item: Mirror Image | Item id: 0446353957 | Value: 0.0
Item: All I Really Need to Know | Item id: 080410526X | Value: 0.0
Item: Dreamcatcher | Item id: 0743211383 | Value: 7.0
Item: Perplexing Lateral Thinking Puzzles: Scholastic Edition | Item id: 0806917695 | Value: 5.0
Item: Obsidian Butterfly | Item id: 0441007813 | Value: 0.0

Recommended items:
23:13:56.438 [main] DEBUG org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender - Recommending items for user ID '276704'
23:13:56.996 [main] DEBUG org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender - Recommendations are: [RecommendedItem[item:185076364212086120, value:10.0], RecommendedItem[item:-591612556174026052, value:10.0], RecommendedItem[item:8244501488765029498, value:10.0], RecommendedItem[item:-6934465359805634419, value:10.0], RecommendedItem[item:2057628558693255423, value:10.0], RecommendedItem[item:-2687357165449897826, value:10.0], RecommendedItem[item:-3143413626485789003, value:10.0], RecommendedItem[item:-7096625993756755430, value:9.86375], RecommendedItem[item:3619295664714735210, value:9.708363], RecommendedItem[item:-6076948342511691177, value:9.708363]]
Item: Keeper of the Heart | Item id: 0380774933 | Value: 10.0
Item: Bleachers | Item id: 0385511612 | Value: 10.0
Item: Salem's Lot | Item id: 0451125452 | Value: 10.0
Item: The Girl Who Loved Tom Gordon | Item id: 0671042858 | Value: 10.0
Item: Mind Prey | Item id: 0425152898 | Value: 10.0
Item: It Came From The Far Side | Item id: 0836220730 | Value: 10.0
Item: Faith of the Fallen (Sword of Truth, Book 6) | Item id: 081257639X | Value: 10.0
Item: The Talisman | Item id: 0345444884 | Value: 9.86375
Item: Hamlet | Item id: 067172262X | Value: 9.708363
Item: Untamed | Item id: 0380769530 | Value: 9.708363

基于项目的过滤

项目相似性(ItemSimilarity)是接下来要讨论的重点。基于项目的推荐器很有用,因为它 们能够充分利用项目本身之间的关系:它们将计算建立在项目的相似性而非用户的相似性之上, 项目的相似性是相对稳定的。项目的相似性可以预先计算,不需要实时重新计算。

因此,如果打算使用ItemSimilarity这个类,强烈建议使用GenericItemSimilarity类,这个类可以预先计算项目的相似性。也可以使用PearsonCorrelationSimilarity类,这个类会实时计算相似性。但对于大量数据,你会发现它的计算速度慢得让人难以忍受。

    public static ItemBasedRecommender getRecommender() throws Exception {
        String filePath = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
        StringItemIdFileDataModel dataModel = new StringItemIdFileDataModel(new File(filePath), ";");
        ItemSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
        ItemBasedRecommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
        IDRescorer rescorer = new MyRescorer();
        String itemISBN = "042513976X";
        long itemID = dataModel.readItemIDFromString(itemISBN);
        int noItems = 10;
        System.out.println("Recommendations for item: " + books.get(itemISBN));
        System.out.println("Most similar items:");
        List<RecommendedItem> recommendations = recommender.mostSimilarItems(itemID, noItems);
        for (RecommendedItem item : recommendations) {
            itemISBN = dataModel.getItemIdAsString(item.getItemID());
            System.out.println("Item: " + books.get(itemISBN) + " | Item id: " + itemISBN + " | Value: " + item.getValue());
        }
        return recommender;
    }
Recommendations for item: Close to the Bone
Most similar items:
Item: Private Screening | Item id: 0345311396 | Value: 1.0
Item: Heartstone | Item id: 0553569783 | Value: 1.0
Item: Clockers / Movie Tie In | Item id: 0380720817 | Value: 1.0
Item: Rules of Prey | Item id: 0425121631 | Value: 1.0
Item: The Next President | Item id: 0553576666 | Value: 1.0
Item: Orchid Beach (Holly Barker Novels (Paperback)) | Item id: 0061013412 | Value: 1.0
Item: Winter Prey | Item id: 0425141233 | Value: 1.0
Item: Night Prey | Item id: 0425146413 | Value: 1.0
Item: Presumed Innocent | Item id: 0446359866 | Value: 1.0
Item: Dirty Work (Stone Barrington Novels (Paperback)) | Item id: 0451210158 | Value: 1.0

上述输出结果中,可以看到输出的一组项目与我们选择的特定项目是相似的。

添加自定义规则到推荐引擎

我们经常会遇到这样的问题:一些业务规则需要提高所选项目的分数。比如,图书数据集中新 到了一本书,我们想给它一个更高的分数。对此,可以使用IDRescorer接口实现完成这个任务。  rescore(long, double):从参数接收itemID与原始分数,返回调整后的分数。  isFiltered(long):若推荐不包含指定项目,则返回true,否则返回false。

// 自定义的重新评分器类,实现 IDRescorer 接口,用于对推荐结果进行重新评分。
class MyRescorer implements IDRescorer {
    // 判断某个项目是否被过滤,如果返回 true,则该项目将被过滤掉,不参与推荐。
    // @param itemId 项目ID
    // @return 如果项目被过滤,返回 true;否则返回 false
    public boolean isFiltered(long itemId) {
        return false;
    }

    // 对原始评分进行重新评分,可以基于某些条件调整评分。
    // @param itemId 项目ID
    // @param originalScore 原始评分
    // @return 重新评分后的值
    public double rescore(long itemId, double originalScore) {
        if (bookIsNew(itemId)) {
            originalScore *= 1.3;
        }
        return Math.random();
    }

    // 判断书籍是否是新的,根据项目ID返回一个布尔值。
    // @param itemId 项目ID
    // @return 如果书籍是新的,返回 true;否则返回 false
    private boolean bookIsNew(long itemId) {
        // TODO Auto-generated method stub
        return false;
    }
}

评估

你可能想知道,如何才能保证推荐引擎返回的推荐项目是靠谱的。准确检测推荐有效程度的 唯一方法就是,在拥有实际用户的真实系统中做A/B测试。比如,A组收到一个随机推荐的项目, 而B组收到我们的推荐引擎推荐的项目。

由于这并非总是可行的,也不实际,所以可以使用脱机统计评估进行估计。一种方法是使用 第1章介绍的k折交叉验证。将数据集分成多个子集,其中一些用来训练我们的推荐引擎,其余的 测试它对未知用户的推荐效果。

Mahout实现了RecommenderEvaluator类,这个类将一个数据集划分成两部分,第一部分 默认为数据的90%,用于生成推荐;其余部分则与评估的偏好值做比较,以测试匹配效果。这个 类不直接接受recommender对象,需要创建一个类实现RecommenderBuilder接口,它为一个 给定的DataModel对象(稍后用于测试)创建一个recommender对象。接下来,让我们看看如 何实现。

首先,创建一个实现RecommenderBuilder接口的类。需要实现buildRecommender方法, 它会返回一个recommender.

recommender对象的类后,我们就可以对RecommenderEvaluator的实例做初始 化。这个类的默认实现是AverageAbsoluteDifferenceRecommenderEvaluator类,用于在 用户的预测评分与实际评分之间计算平均绝对差值。下面代码显示了如何将上面这些内容组合在 一起,以进行Hold-Out测试。

首先,加载数据模型.

接着,初始化evaluator实例.

初始化BookRecommender对象,实现RecommenderBuilder接口

最后,调用evaluate()方法,该方法接收如下参数。

RecommenderBuilder : 该 对象实现了 RecommenderBuilder , 用 于创建待测 试的 recommender。

 DataModelBuilder:要使用的DataModelBuilder,若为null,将使用默认的DataModel 实现。

 DataModel:用于测试的数据集。

 trainingPercentage:表示生成推荐的每个用户偏好所占的比例,其他的则与估计的 偏好值做比较,以评估recommender的性能。

 evaluationPercentage:评估中参与的用户在数据模型中所占的比例。

evaluate()方法返回一个double值, 一般来说,值越小,匹配得越好。

    // 评估推荐器
    public static void evaluateRecommender() throws Exception {
        String filePath = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
        StringItemIdFileDataModel dataModel = new StringItemIdFileDataModel(new File(filePath), ";");
        RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator();
        RecommenderBuilder builder = new BookRecommender();
        double result = evaluator.evaluate(builder, null, dataModel, 0.9, 1.0);
        System.out.println(result);
    }

在线学习引擎

在线学习引擎中的“在线”两字是什么意思呢?上面讲到的推荐引擎对于已有的用户有很好 的工作效果,但对于那些新注册的用户推荐效果不佳。我们肯定也想为这些新用户做一些合理的 推荐。创建一个推荐实例代价很大(它肯定比一个普通的网络请求需要花更长时间),因此不能 每次都创建一个新推荐。
幸运的是,Mahout允许我们向数据模型添加临时用户。一般设置如下:
 使用当前数据定期重建整个推荐,比如每天或每小时,具体取决于耗费多长时间。

 做推荐时,检查系统中是否有这个用户。

 若有,像往常一样结束推荐。

 若没有,则创建临时用户,填入偏好,并做推荐。

如果你的内存有限,第一部分(定期重建推荐器)其实很难办到:创建新推荐器时,你需要 在内存中保留数据的两个副本(为了可以处理来自旧推荐器的请求)。然而,由于这对推荐真的 没什么用,所以就不细讲了。

对于临时用户,可以使用一个PlusAnonymousConcurrentUserDataModel类实例包装我 们的数据模型。这个类允许获得一个临时用户ID,以后必须释放这个ID,以便可以重用(这样的 ID数目是有限制的)。得到ID后,必须填写偏好,然后可以像以前一样开始推荐:

// OnlineRecommendation 类是一个在线推荐系统的实现,负责为用户提供个性化的推荐。
public class OnlineRecommendation {
    // 定义数据文件的路径,使用 ClassUtils 获取默认类加载器的资源路径。
    public static String PATH = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
    // 推荐器对象,用于执行推荐操作。
    Recommender recommender;
    // 并发用户的数量,默认为 100。
    int concurrentUsers = 100;
    // 每次推荐的物品数量,默认为 10。
    int noItems = 10;

    // 构造函数,初始化推荐系统。
    // @throws IOException 如果文件读取失败
    public OnlineRecommendation() throws IOException {
        // 创建数据模型对象,使用 StringItemIdFileDataModel 类来处理字符串形式的物品ID。
        DataModel model = new StringItemIdFileDataModel(new File(PATH), ";");
        // 创建支持并发和匿名用户的数据模型对象,允许指定并发用户的数量。
        PlusAnonymousConcurrentUserDataModel plusModel = new PlusAnonymousConcurrentUserDataModel(model, concurrentUsers);
        // 此处未实现推荐器的初始化,需要在实际使用时完成。
        // recommender = ...;
    }

    // 为指定用户生成推荐列表。
    // @param userId 用户ID
    // @param preferences 用户的首选项数组
    // @return 推荐的项目列表
    // @throws Exception 如果推荐过程中发生异常
    public List<RecommendedItem> recommend(long userId, PreferenceArray preferences) throws Exception {
        // 检查用户是否存在于数据模型中。
        if (userExistsInDataModel(userId)) {
            // 如果用户存在,直接调用推荐器的推荐方法,返回推荐结果。
            return recommender.recommend(userId, noItems);
        } else {
            // 如果用户不存在,则将其视为匿名用户。
            // 获取支持匿名用户的数据模型对象。
            PlusAnonymousConcurrentUserDataModel plusModel = (PlusAnonymousConcurrentUserDataModel) recommender.getDataModel();
            // 从数据模型中获取一个可用的匿名用户ID。
            Long anonymousUserId = plusModel.takeAvailableUser();
            // 将首选项数组的用户ID设置为匿名用户ID。
            PreferenceArray temp = preferences;
            temp.setUserID(0, anonymousUserId);
            // 将匿名用户的首选项设置到数据模型中。
            plusModel.setTempPrefs(temp, anonymousUserId);
            // 为匿名用户生成推荐列表。
            List<RecommendedItem> results = recommender.recommend(userId, noItems);
            // 释放匿名用户ID,使其可以被其他请求重用。
            plusModel.releaseUser(anonymousUserId);
            // 返回推荐结果。
            return results;
        }
    }

    // 检查用户是否存在于数据模型中。
    // 注意:此方法尚未实现,默认返回 false。
    // @param userId 用户ID
    // @return 如果用户存在,返回 true;否则返回 false
    private boolean userExistsInDataModel(long userId) {
        // TODO Auto-generated method stub
        return false;
    }
}

基于内容的过滤

Mahout框架不包含基于内容的过滤,主要是因为,如何定义相似项目是由你自己决定的。如 果想定义一个基于内容的项目到项目相似度,需要实现自己的ItemSimilarity。比如,我们的 图书数据集中,针对图书相似度,可能制定如下规则:  若类型相同,则将相似度加0.15;  若作者相同,则将相似度加0.50。
下面实现我们自己的相似度测度,如下

public class MyItemSimilarity implements ItemSimilarity {
    @Override
    public double itemSimilarity(long itemID1, long itemID2) {
        // 假设 lookupMyBook 方法会返回 MyBook 对象
        MyBook book1 = lookupMyBook(itemID1);
        MyBook book2 = lookupMyBook(itemID2);
        double similarity = 0.0;
        // 根据相同类型增加相似度
        if (book1.getGenre().equals(book2.getGenre())) {
            similarity += 0.15;
        }
        // 根据相同作者增加相似度
        if (book1.getAuthor().equals(book2.getAuthor())) {
            similarity += 0.50;
        }
        // 你可以根据其他属性进一步调整相似度计算
        return similarity;
    }

    @Override
    public double[] itemSimilarities(long itemID, long[] itemIDs) throws TasteException {
        // 这里可以根据 itemID 和 itemIDs 数组计算多个相似度
        double[] similarities = new double[itemIDs.length];
        for (int i = 0; i < itemIDs.length; i++) {
            similarities[i] = itemSimilarity(itemID, itemIDs[i]);
        }
        return similarities;
    }

    @Override
    public long[] allSimilarItemIDs(long itemID) throws TasteException {
        // 这里可以根据 itemID 找出所有相似的 itemID
        // 例如,找到所有相似度大于某个阈值的 itemID
        return new long[0]; // 需要根据实际逻辑实现
    }

    @Override
    public void refresh(Collection<Refreshable> collection) {
        // 刷新方法,可以根据需要实现
    }

    // 假设你有一个方法来查找 MyBook 对象
    private MyBook lookupMyBook(long itemID) {
        // 这里需要根据实际逻辑实现
        return new MyBook(); // 示例返回一个空的 MyBook 对象
    }

    // 假设你有一个 MyBook 类
    @Data
    private static class MyBook {
        private String genre;
        private String author;
    }
}

然后,使用这个ItemSimilarity替换LogLikelihoodSimilarity或其他GenericItem- Based Recommender的实现。以上就是在Mahout框架中做基于内容的推荐。

此处给出的示例是基于内容推荐的一种最简单的形式。还有一种方法可以用来创建基于内容 的用户画像(content-based profile of users),它建立在项目特征的加权向量基础之上。权重表示 每个特征对于用户的重要程度,这可以从单独评分的内容向量计算出来。

完整代码

// ItemMemIDMigrator 类继承自 AbstractIDMigrator,用于管理长整型ID和字符串ID之间的映射关系。
public class ItemMemIDMigrator extends AbstractIDMigrator {
    // 使用 FastByIDMap 来存储长整型ID和字符串ID之间的映射关系。
    private FastByIDMap<String> itemIDMap;

    // 构造函数,初始化 itemIDMap,设置初始容量为 10000。
    public ItemMemIDMigrator() {
        this.itemIDMap = new FastByIDMap<String>(10000);
    }

    // 存储长整型ID和字符串ID之间的映射关系。
    // @param longID 长整型ID
    // @param stringID 字符串ID
    public void storeMapping(long longID, String stringID) {
        itemIDMap.put(longID, stringID);
    }

    // 初始化单个字符串ID的映射关系。
    // @param stringID 字符串ID
    // @throws TasteException 如果发生异常
    public void singleInit(String stringID) throws TasteException {
        // 将字符串ID转换为长整型ID,并存储映射关系。
        storeMapping(toLongID(stringID), stringID);
    }

    // 根据长整型ID获取对应的字符串ID。
    // @param l 长整型ID
    // @return 对应的字符串ID
    // @throws TasteException 如果发生异常
    @Override
    public String toStringID(long l) throws TasteException {
        // 从 itemIDMap 中获取长整型ID对应的字符串ID。
        return this.itemIDMap.get(l);
    }
}
// StringItemIdFileDataModel 类继承自 FileDataModel,用于处理文件数据模型,特别是处理字符串形式的物品ID。
public class StringItemIdFileDataModel extends FileDataModel {
    // 用于管理长整型ID和字符串ID之间映射关系的 ItemMemIDMigrator 实例。
    public ItemMemIDMigrator itemMemIDMigrator;

    // 构造函数,初始化文件数据模型,并指定分隔符。
    // @param dataFile 数据文件
    // @param delimiterRegex 分隔符的正则表达式
    // @throws IOException 如果文件读取失败
    public StringItemIdFileDataModel(File dataFile, String delimiterRegex) throws IOException {
        super(dataFile, delimiterRegex);
    }

    // 从字符串中读取物品ID,并将其转换为长整型ID。
    // @param value 字符串形式的物品ID
    // @return 长整型形式的物品ID
    @Override
    protected long readItemIDFromString(String value) {
        // 如果 itemMemIDMigrator 为空,则初始化一个新的 ItemMemIDMigrator 实例。
        if (Objects.isNull(itemMemIDMigrator)) {
            itemMemIDMigrator = new ItemMemIDMigrator();
        }
        // 将字符串形式的物品ID转换为长整型ID。
        long readValue = itemMemIDMigrator.toLongID(value);
        try {
            // 检查长整型ID是否已经存在于 itemMemIDMigrator 中。
            // 如果不存在,则调用 singleInit 方法初始化映射关系。
            if (Objects.isNull(itemMemIDMigrator.toStringID(readValue))) {
                itemMemIDMigrator.singleInit(value);
            }
        } catch (Exception e) {
            // 捕获并打印异常。
            e.printStackTrace();
        }
        // 返回长整型形式的物品ID。
        return readValue;
    }

    // 根据长整型ID获取对应的字符串形式的物品ID。
    // @param key 长整型ID
    // @return 字符串形式的物品ID
    // @throws TasteException 如果发生异常
    public String getItemIdAsString(long key) throws TasteException {
        // 通过 itemMemIDMigrator 获取长整型ID对应的字符串形式的物品ID。
        return itemMemIDMigrator.toStringID(key);
    }
}
// OnlineRecommendation 类是一个在线推荐系统的实现,负责为用户提供个性化的推荐。
public class OnlineRecommendation {
    // 定义数据文件的路径,使用 ClassUtils 获取默认类加载器的资源路径。
    public static String PATH = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
    // 推荐器对象,用于执行推荐操作。
    Recommender recommender;
    // 并发用户的数量,默认为 100。
    int concurrentUsers = 100;
    // 每次推荐的物品数量,默认为 10。
    int noItems = 10;

    // 构造函数,初始化推荐系统。
    // @throws IOException 如果文件读取失败
    public OnlineRecommendation() throws IOException {
        // 创建数据模型对象,使用 StringItemIdFileDataModel 类来处理字符串形式的物品ID。
        DataModel model = new StringItemIdFileDataModel(new File(PATH), ";");
        // 创建支持并发和匿名用户的数据模型对象,允许指定并发用户的数量。
        PlusAnonymousConcurrentUserDataModel plusModel = new PlusAnonymousConcurrentUserDataModel(model, concurrentUsers);
        // 此处未实现推荐器的初始化,需要在实际使用时完成。
        // recommender = ...;
    }

    // 为指定用户生成推荐列表。
    // @param userId 用户ID
    // @param preferences 用户的首选项数组
    // @return 推荐的项目列表
    // @throws Exception 如果推荐过程中发生异常
    public List<RecommendedItem> recommend(long userId, PreferenceArray preferences) throws Exception {
        // 检查用户是否存在于数据模型中。
        if (userExistsInDataModel(userId)) {
            // 如果用户存在,直接调用推荐器的推荐方法,返回推荐结果。
            return recommender.recommend(userId, noItems);
        } else {
            // 如果用户不存在,则将其视为匿名用户。
            // 获取支持匿名用户的数据模型对象。
            PlusAnonymousConcurrentUserDataModel plusModel = (PlusAnonymousConcurrentUserDataModel) recommender.getDataModel();
            // 从数据模型中获取一个可用的匿名用户ID。
            Long anonymousUserId = plusModel.takeAvailableUser();
            // 将首选项数组的用户ID设置为匿名用户ID。
            PreferenceArray temp = preferences;
            temp.setUserID(0, anonymousUserId);
            // 将匿名用户的首选项设置到数据模型中。
            plusModel.setTempPrefs(temp, anonymousUserId);
            // 为匿名用户生成推荐列表。
            List<RecommendedItem> results = recommender.recommend(userId, noItems);
            // 释放匿名用户ID,使其可以被其他请求重用。
            plusModel.releaseUser(anonymousUserId);
            // 返回推荐结果。
            return results;
        }
    }

    // 检查用户是否存在于数据模型中。
    // 注意:此方法尚未实现,默认返回 false。
    // @param userId 用户ID
    // @return 如果用户存在,返回 true;否则返回 false
    private boolean userExistsInDataModel(long userId) {
        // TODO Auto-generated method stub
        return false;
    }
}
// 自定义的重新评分器类,实现 IDRescorer 接口,用于对推荐结果进行重新评分。
class MyRescorer implements IDRescorer {
    // 判断某个项目是否被过滤,如果返回 true,则该项目将被过滤掉,不参与推荐。
    // @param itemId 项目ID
    // @return 如果项目被过滤,返回 true;否则返回 false
    public boolean isFiltered(long itemId) {
        return false;
    }

    // 对原始评分进行重新评分,可以基于某些条件调整评分。
    // @param itemId 项目ID
    // @param originalScore 原始评分
    // @return 重新评分后的值
    public double rescore(long itemId, double originalScore) {
        if (bookIsNew(itemId)) {
            originalScore *= 1.3;
        }
        return Math.random();
    }

    // 判断书籍是否是新的,根据项目ID返回一个布尔值。
    // @param itemId 项目ID
    // @return 如果书籍是新的,返回 true;否则返回 false
    private boolean bookIsNew(long itemId) {
        // TODO Auto-generated method stub
        return false;
    }
}
public class BookRecommender implements RecommenderBuilder {
    public static Map<String, String> books;
    public static String PATH = ClassUtils.getDefaultClassLoader().getResource("BX-Books.csv").getPath();

    public static void main(String[] args) throws Exception {
        books = loadBooks(PATH);
        //userBased();
        getRecommender();
        evaluateRecommender();
    }

    // 加载书籍信息到Map中
    public static Map<String, String> loadBooks(String path) throws Exception {
        Map<String, String> map = new HashMap<String, String>();
        BufferedReader br = new BufferedReader(new FileReader(path));
        String line = "";
        while ((line = br.readLine()) != null) {
            String[] str = line.replace("\"", "").split(";");
            map.put(str[0], str[1]);
        }
        br.close();
        System.out.println("加载图书信息成功:" + map.size());
        return map;
    }

    // 获取推荐器
    public static ItemBasedRecommender getRecommender() throws Exception {
        String filePath = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
        StringItemIdFileDataModel dataModel = new StringItemIdFileDataModel(new File(filePath), ";");
        ItemSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
        ItemBasedRecommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
        IDRescorer rescorer = new MyRescorer();
        String itemISBN = "042513976X";
        long itemID = dataModel.readItemIDFromString(itemISBN);
        int noItems = 10;
        System.out.println("Recommendations for item: " + books.get(itemISBN));
        System.out.println("Most similar items:");
        List<RecommendedItem> recommendations = recommender.mostSimilarItems(itemID, noItems);
        for (RecommendedItem item : recommendations) {
            itemISBN = dataModel.getItemIdAsString(item.getItemID());
            System.out.println("Item: " + books.get(itemISBN) + " | Item id: " + itemISBN + " | Value: " + item.getValue());
        }
        return recommender;
    }

    public static void userBased() throws Exception {
        String filePath = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
        StringItemIdFileDataModel model = new StringItemIdFileDataModel(new File(filePath), ";");
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
        UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.1, similarity, model);
        UserBasedRecommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);
        IDRescorer rescorer = new MyRescorer();
        // List recommendations = recommender.recommend(2, 3, rescorer);
        long userID = 276704;// 276704;//212124;//277157;
        int noItems = 10;
        System.out.println("Rated items:");
        for (Preference preference : model.getPreferencesFromUser(userID)) {
            String itemISBN = model.getItemIdAsString(preference.getItemID());
            System.out.println("Item: " + books.get(itemISBN) + " | Item id: " + itemISBN + " | Value: " + preference.getValue());
        }
        System.out.println("\nRecommended items:");
        List<RecommendedItem> recommendations = recommender.recommend(userID,
                noItems);
        for (RecommendedItem item : recommendations) {
            String itemISBN = model.getItemIdAsString(item.getItemID());
            System.out.println("Item: " + books.get(itemISBN) + " | Item id: " + itemISBN + " | Value: " + item.getValue());
        }
    }

    // 评估推荐器
    public static void evaluateRecommender() throws Exception {
        String filePath = ClassUtils.getDefaultClassLoader().getResource("BX-Book-Ratings.csv").getPath();
        StringItemIdFileDataModel dataModel = new StringItemIdFileDataModel(new File(filePath), ";");
        RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator();
        RecommenderBuilder builder = new BookRecommender();
        double result = evaluator.evaluate(builder, null, dataModel, 0.9, 1.0);
        System.out.println(result);
    }

    public static DataModel loadFromDB() throws Exception {
        MysqlDataSource dbsource = new MysqlDataSource();
        dbsource.setUser("user");
        dbsource.setPassword("pass");
        dbsource.setServerName("localhost");
        dbsource.setDatabaseName("my_db");
        DataModel dataModelDB = new MySQLJDBCDataModel(dbsource,
                "taste_preferences", "user_id", "item_id", "preference",
                "timestamp");
        return dataModelDB;
    }

    public DataModel loadInMemory() {
        // In-memory DataModel - GenericDataModels
        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);
        preferences.put(1L, prefsForUser1); // use userID as the key
        //TODO: add others users
        // Return preferences as new data model
        DataModel dataModel = new GenericDataModel(preferences);
        return dataModel;
    }

    @Override
    public Recommender buildRecommender(DataModel dataModel) throws TasteException {
        try {
            return BookRecommender.getRecommender();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class MyItemSimilarity implements ItemSimilarity {
    @Override
    public double itemSimilarity(long itemID1, long itemID2) {
        // 假设 lookupMyBook 方法会返回 MyBook 对象
        MyBook book1 = lookupMyBook(itemID1);
        MyBook book2 = lookupMyBook(itemID2);
        double similarity = 0.0;
        // 根据相同类型增加相似度
        if (book1.getGenre().equals(book2.getGenre())) {
            similarity += 0.15;
        }
        // 根据相同作者增加相似度
        if (book1.getAuthor().equals(book2.getAuthor())) {
            similarity += 0.50;
        }
        // 你可以根据其他属性进一步调整相似度计算
        return similarity;
    }

    @Override
    public double[] itemSimilarities(long itemID, long[] itemIDs) throws TasteException {
        // 这里可以根据 itemID 和 itemIDs 数组计算多个相似度
        double[] similarities = new double[itemIDs.length];
        for (int i = 0; i < itemIDs.length; i++) {
            similarities[i] = itemSimilarity(itemID, itemIDs[i]);
        }
        return similarities;
    }

    @Override
    public long[] allSimilarItemIDs(long itemID) throws TasteException {
        // 这里可以根据 itemID 找出所有相似的 itemID
        // 例如,找到所有相似度大于某个阈值的 itemID
        return new long[0]; // 需要根据实际逻辑实现
    }

    @Override
    public void refresh(Collection<Refreshable> collection) {
        // 刷新方法,可以根据需要实现
    }

    // 假设你有一个方法来查找 MyBook 对象
    private MyBook lookupMyBook(long itemID) {
        // 这里需要根据实际逻辑实现
        return new MyBook(); // 示例返回一个空的 MyBook 对象
    }

    // 假设你有一个 MyBook 类
    @Data
    private static class MyBook {
        private String genre;
        private String author;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948863.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC(六)拦截器

目录 1.什么是拦截器 2.拦截器和过滤器有哪些区别 3.拦截器方法 4.单个拦截器的执行流程 5.使用拦截器实现用户登录权限验证&#xff08;实例&#xff09; 1.先在html目录下写一个login.html文件 2.在controller包下写一个LoginController文件 3.加拦截器 1.创建一个conf…

【FlutterDart】 拖动边界线改变列宽并且有边界高亮和鼠标效果(12 /100)

【Flutter&Dart】 拖动改变 widget 的窗口尺寸大小GestureDetector&#xff5e;简单实现&#xff08;10 /100&#xff09; 【Flutter&Dart】 拖动边界线改变列宽类似 vscode 那种拖动改变编辑框窗口大小&#xff08;11 /100&#xff09; 上效果 对比一下vscode的效果&…

4.1.2 栈和队列(一)

文章目录 栈的定义栈的基本运算栈的存储结构栈的应用表达式求值 栈和队列的逻辑结构与线性表相同&#xff0c;但是其运算受到限制&#xff0c;统称为运算受限的线性表。 栈&#xff0c; 先进后出 队列&#xff0c;先进先出 栈的定义 栈顶&#xff0c;唯一能操作端 栈底&#xf…

如何理解RDD,以及RDD的五大特性和五大特点。

RDD&#xff1a;英文全称Resilient Distributed Dataset&#xff0c;叫做弹性分布式数据集&#xff0c;代表一个不可变、可分区、里面的元素可并行计算的分布式的抽象的数据集合。 Resilient弹性&#xff1a;RDD的数据可以存储在内存或者磁盘当中&#xff0c;RDD的数据可以分区…

JavaWeb开发(六)XML介绍

1. XML介绍 1.1. 什么是XML &#xff08;1&#xff09;XML 指可扩展标记语言(EXtensible Markup Language)XML 是一种很像HTML的标记语言。   &#xff08;2&#xff09;XML 的设计宗旨是传输数据(目前主要是作为配置文件)&#xff0c;而不是显示数据。   &#xff08;3&a…

2000-2020年各省地区生产总值数据/各省gdp数据

2000-2020年各省地区生产总值数据/各省gdp数据 1、时间&#xff1a;2000-2020年 2、来源&#xff1a;国家统计局 3、指标&#xff1a;行政区划代码、地区、年份、地区生产总值 4、范围&#xff1a;31省 指标解释&#xff1a;地区生产总值&#xff08;Regional GDP&#xf…

鸿蒙HarmonyOS开发:基于Swiper组件和自定义指示器实现多图片进度条轮播功能

文章目录 一、概述1、场景介绍2、技术选型 二、实现方案1、图片区域实现2、底部导航点设计3、手动切换 三、所有代码1、设置沉浸式2、外层Tabs效果3、ImageSwiper组件 四、效果展示 一、概述 在短视频平台上&#xff0c;经常可以见到多图片合集。它的特点是&#xff1a;由多张…

第二十八周学习周报

目录 摘要Abstract1 GFPGAN1.1 总体结构1.2 实验研究1.3 代码分析 总结 摘要 本周主要的学习内容是GFPGAN模型。GFPGAN是一种基于生成对抗网络(GAN)的模型&#xff0c;其利用封装在预训练的人脸GAN中的丰富多样的先验进行人脸图像的修复。这种生成面部先验&#xff08;GFP&…

MCP(Model Context Protocol)模型上下文协议 进阶篇3 - 传输

MCP 目前定义了两种标准的客户端-服务端通信传输机制&#xff1a; stdio&#xff08;标准输入输出通信&#xff09;HTTP with Server-Sent Events (SSE)&#xff08;HTTP 服务端发送事件&#xff09; 客户端应尽可能支持 stdio。此外&#xff0c;客户端和服务端也可以以插件方…

NVIDIA DLI课程《NVIDIA NIM入门》——学习笔记

先看老师给的资料&#xff1a; NVIDIA NIM是 NVIDIA AI Enterprise 的一部分&#xff0c;是一套易于使用的预构建容器工具&#xff0c;目的是帮助企业客户在云、数据中心和工作站上安全、可靠地部署高性能的 AI 模型推理。这些预构建的容器支持从开源社区模型到 NVIDIA AI 基础…

物联网云平台:构建物联网生态的核心

我们常说的物联网&#xff0c;简称是IoT&#xff0c; 全称 Internet of Things。 用通俗的语言理解物联网&#xff0c;其实就是万事万物的互联网络。物联网概念也已经传播很多年了&#xff0c; 目前正在各行各业发挥力量。 要构建一个物联网生态&#xff0c; 我们首先想到的是智…

VS2022引入sqlite数据库交互

法一&#xff1a;用官网编译好的动态库(推荐) 下载所需文件 sqlite官网地址&#xff1a;https://www.sqlite.org/howtocompile.html 下载以下的2个压缩包 第一个压缩包 sqlite-amalgamation-xxxx.zip&#xff0c;xxxx是版本号,保持一致即可&#xff0c;这里面有sqite3.h 第…

设计模式学习[15]---适配器模式

文章目录 前言1.引例2.适配器模式2.1 对象适配器2.2 类适配器 总结 前言 这个模式其实在日常生活中有点常见&#xff0c;比如我们的手机取消了 3.5 m m 3.5mm 3.5mm的接口&#xff0c;只留下了一个 T y p e − C Type-C Type−C的接口&#xff0c;但是我现在有一个 3.5 m m 3.…

Markdown如何导出Html文件Markdown文件

Markdown如何导出Html文件Markdown文件 前言语法详解小结其他文章快来试试吧☺️ Markdown 导出 HTML &#x1f448;点击这里也可查看 前言 Markdown的源文件以md为后缀。Markdown是HTML语法的简化版本&#xff0c;它本身不带有任何样式信息。我们所看到的Markdown网页(如&…

Python安装(新手详细版)

前言 第一次接触Python&#xff0c;可能是爬虫或者是信息AI开发的小朋友&#xff0c;都说Python 语言简单&#xff0c;那么多学一些总是有好处的&#xff0c;下面从一个完全不懂的Python 的小白来安装Python 等一系列工作的记录&#xff0c;并且遇到的问题也会写出&#xff0c…

JMeter + Grafana +InfluxDB性能监控 (二)

您可以通过JMeter、Grafana 和 InfluxDB来搭建一个炫酷的基于JMeter测试数据的性能测试监控平台。 下面&#xff0c;笔者详细介绍具体的搭建过程。 安装并配置InfluxDB 您可以从清华大学开源软件镜像站等获得InfluxDB的RPM包&#xff0c;这里笔者下载的是influxdb-1.8.0.x86_…

STL常用容器总结

1.Vector容器特性 vector 容器是一个长度动态改变的动态数组&#xff0c;既然也是数组&#xff0c;那么其内存是一段连续的内存&#xff0c;具有数组的随机存取的优点。 / 1.1.vector特性总结: 1.vector 是动态数组&#xff0c;连续内存空间&#xff0c;具有随机存取效率高的…

BBP飞控板中的坐标系变换

一般飞控板中至少存在以下坐标系&#xff1a; 陀螺Gyro坐标系加速度计Acc坐标系磁强计Mag坐标系飞控板坐标系 在BBP飞控板采用的IMU为同时包含了陀螺&#xff08;Gyro&#xff09;及加速度计&#xff08;Acc&#xff09;的6轴传感器&#xff0c;故Gyro及Acc为同一坐标系。同时…

【OAuth2系列】如何使用OAuth 2.0实现安全授权?详解四种授权方式

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 【OAuth2系列】集成微…

鸿蒙MPChart图表自定义(六)在图表中绘制游标

在鸿蒙开发中&#xff0c;MPChart 是一个非常强大的图表库&#xff0c;它可以帮助我们创建各种精美的图表。今天&#xff0c;我们将继续探索鸿蒙MPChart的自定义功能&#xff0c;重点介绍如何在图表中绘制游标。 OpenHarmony三方库中心仓 一、效果演示 以下是效果演示图&…