网站备案进程查询,专业的建设网站,电商网站建设内容,建立网站可行性目录
创建工程
基本概念
关键概念
基于用户与基于项目的分析
计算相似度的方法
协同过滤
基于内容的过滤
混合方法
创建一个推荐引擎
图书评分数据集
加载数据
从文件加载数据
从数据库加载数据
内存数据库
协同过滤
基于用户的过滤
基于项目的过滤
添加自定…目录
创建工程
基本概念
关键概念
基于用户与基于项目的分析
计算相似度的方法
协同过滤
基于内容的过滤
混合方法
创建一个推荐引擎
图书评分数据集
加载数据
从文件加载数据
从数据库加载数据
内存数据库
协同过滤
基于用户的过滤
基于项目的过滤
添加自定义规则到推荐引擎
评估
在线学习引擎
基于内容的过滤
完整代码 推荐引擎可能是当今初创公司应用最多的一种数据科学方法。有两项主要技术用来创建一个 推系统基于内容的过滤与协同过滤。基于内容的过滤算法使用项目属性寻找带有相似属性的 项目协同过滤算法关注的是用户的评分或者其他用户的行为它基于拥有类似行为的用户喜好 与购买物品进行推荐。 本章先讲解基本概念它们是理解推荐引擎原理必需的内容然后演示如何使用Apache Mahout中的各种算法实现快速创建一个可扩展的推荐引擎。本章内容涵盖如下主题
如何创建一个推荐引擎
准备Apache Mahout
基于内容的方法
协同过滤方法
到本章结束时你将知道我们的问题适合使用哪种推荐引擎进行解决以及如何快速创建这 样个推荐引擎。
创建工程
接着上一篇文章工程pom添加 dependencygroupIdorg.apache.mahout/groupIdartifactIdmahout-mr/artifactIdversion0.10.0/version/dependencydependencygroupIdorg.apache.mahout/groupIdartifactIdmahout-integration/artifactIdversion0.7/version/dependencydependencygroupIdcommons-io/groupIdartifactIdcommons-io/artifactIdversion2.16.1/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.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包含用户。请注意用户IDUser-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 FastByIDMapString itemIDMap;// 构造函数初始化 itemIDMap设置初始容量为 10000。public ItemMemIDMigrator() {this.itemIDMap new FastByIDMapString(10000);}// 存储长整型ID和字符串ID之间的映射关系。// param longID 长整型ID// param stringID 字符串IDpublic 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 如果发生异常Overridepublic 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 长整型形式的物品IDOverrideprotected 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文件添加如下依赖关系 dependencygroupIdorg.apache.mahout/groupIdartifactIdmahout-integration/artifactIdversion0.7/version/dependency
由于要连接MySQL数据库所以还需要向项目添加一个用于处理数据库连接的包。 dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.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() {FastByIDMapPreferenceArray preferences new FastByIDMapPreferenceArray();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 usersDataModel 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:);ListRecommendedItem 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:);ListRecommendedItem 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 Handmaids 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: Nobodys 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.0Recommended 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: Salems 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:);ListRecommendedItem 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否则返回 falsepublic 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否则返回 falseprivate boolean bookIsNew(long itemId) {// TODO Auto-generated method stubreturn 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 ListRecommendedItem 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);// 为匿名用户生成推荐列表。ListRecommendedItem results recommender.recommend(userId, noItems);// 释放匿名用户ID使其可以被其他请求重用。plusModel.releaseUser(anonymousUserId);// 返回推荐结果。return results;}}// 检查用户是否存在于数据模型中。// 注意此方法尚未实现默认返回 false。// param userId 用户ID// return 如果用户存在返回 true否则返回 falseprivate boolean userExistsInDataModel(long userId) {// TODO Auto-generated method stubreturn false;}
}
基于内容的过滤
Mahout框架不包含基于内容的过滤主要是因为如何定义相似项目是由你自己决定的。如 果想定义一个基于内容的项目到项目相似度需要实现自己的ItemSimilarity。比如我们的 图书数据集中针对图书相似度可能制定如下规则 若类型相同则将相似度加0.15 若作者相同则将相似度加0.50。 下面实现我们自己的相似度测度如下
public class MyItemSimilarity implements ItemSimilarity {Overridepublic 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;}Overridepublic 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;}Overridepublic long[] allSimilarItemIDs(long itemID) throws TasteException {// 这里可以根据 itemID 找出所有相似的 itemID// 例如找到所有相似度大于某个阈值的 itemIDreturn new long[0]; // 需要根据实际逻辑实现}Overridepublic void refresh(CollectionRefreshable collection) {// 刷新方法可以根据需要实现}// 假设你有一个方法来查找 MyBook 对象private MyBook lookupMyBook(long itemID) {// 这里需要根据实际逻辑实现return new MyBook(); // 示例返回一个空的 MyBook 对象}// 假设你有一个 MyBook 类Dataprivate 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 FastByIDMapString itemIDMap;// 构造函数初始化 itemIDMap设置初始容量为 10000。public ItemMemIDMigrator() {this.itemIDMap new FastByIDMapString(10000);}// 存储长整型ID和字符串ID之间的映射关系。// param longID 长整型ID// param stringID 字符串IDpublic 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 如果发生异常Overridepublic 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 长整型形式的物品IDOverrideprotected 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 ListRecommendedItem 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);// 为匿名用户生成推荐列表。ListRecommendedItem results recommender.recommend(userId, noItems);// 释放匿名用户ID使其可以被其他请求重用。plusModel.releaseUser(anonymousUserId);// 返回推荐结果。return results;}}// 检查用户是否存在于数据模型中。// 注意此方法尚未实现默认返回 false。// param userId 用户ID// return 如果用户存在返回 true否则返回 falseprivate boolean userExistsInDataModel(long userId) {// TODO Auto-generated method stubreturn false;}
}
// 自定义的重新评分器类实现 IDRescorer 接口用于对推荐结果进行重新评分。
class MyRescorer implements IDRescorer {// 判断某个项目是否被过滤如果返回 true则该项目将被过滤掉不参与推荐。// param itemId 项目ID// return 如果项目被过滤返回 true否则返回 falsepublic 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否则返回 falseprivate boolean bookIsNew(long itemId) {// TODO Auto-generated method stubreturn false;}
}
public class BookRecommender implements RecommenderBuilder {public static MapString, 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 MapString, String loadBooks(String path) throws Exception {MapString, String map new HashMapString, 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:);ListRecommendedItem 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:);ListRecommendedItem 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 - GenericDataModelsFastByIDMapPreferenceArray preferences new FastByIDMapPreferenceArray();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 modelDataModel dataModel new GenericDataModel(preferences);return dataModel;}Overridepublic Recommender buildRecommender(DataModel dataModel) throws TasteException {try {return BookRecommender.getRecommender();} catch (Exception e) {e.printStackTrace();}return null;}
}
public class MyItemSimilarity implements ItemSimilarity {Overridepublic 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;}Overridepublic 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;}Overridepublic long[] allSimilarItemIDs(long itemID) throws TasteException {// 这里可以根据 itemID 找出所有相似的 itemID// 例如找到所有相似度大于某个阈值的 itemIDreturn new long[0]; // 需要根据实际逻辑实现}Overridepublic void refresh(CollectionRefreshable collection) {// 刷新方法可以根据需要实现}// 假设你有一个方法来查找 MyBook 对象private MyBook lookupMyBook(long itemID) {// 这里需要根据实际逻辑实现return new MyBook(); // 示例返回一个空的 MyBook 对象}// 假设你有一个 MyBook 类Dataprivate static class MyBook {private String genre;private String author;}
}