使用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 字符串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 长整型形式的物品ID@Overrideprotected 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 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:");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.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: 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;否则返回 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 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;否则返回 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(Collection<Refreshable> 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 FastByIDMap<String> itemIDMap;// 构造函数,初始化 itemIDMap,设置初始容量为 10000。public ItemMemIDMigrator() {this.itemIDMap = new FastByIDMap<String>(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 长整型形式的物品ID@Overrideprotected 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;否则返回 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 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 - GenericDataModelsFastByIDMap<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 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(Collection<Refreshable> collection) {// 刷新方法,可以根据需要实现}// 假设你有一个方法来查找 MyBook 对象private MyBook lookupMyBook(long itemID) {// 这里需要根据实际逻辑实现return new MyBook(); // 示例返回一个空的 MyBook 对象}// 假设你有一个 MyBook 类@Dataprivate static class MyBook {private String genre;private String author;}
}
相关文章:
使用Apache Mahout制作 推荐引擎
目录 创建工程 基本概念 关键概念 基于用户与基于项目的分析 计算相似度的方法 协同过滤 基于内容的过滤 混合方法 创建一个推荐引擎 图书评分数据集 加载数据 从文件加载数据 从数据库加载数据 内存数据库 协同过滤 基于用户的过滤 基于项目的过滤 添加自定…...
记录:导出功能:接收文件流数据进行导出(vue3)
请求接口:一定要加responseType: blob 后端返回数据: api.js export function export() {return request({url: dev/api/export,method: get,responseType: blob,//一定要加}) } vue: import {export} from /api// 导出 const exportTab…...
GraphRAG vs 传统 RAG:如何通过知识图谱提升 AI 检索能力
相比传统 RAG 仅能独立检索文本片段的局限性,GraphRAG通过构建实体关系图谱实现了信息间的连接,让 AI 能更完整地理解和检索复杂的关联信息,从而生成更准确和连贯的回答 问题背景: 想象有一本详细记录某人(X)成就的传记,每个章节都描述了他的…...
问题清除指南|关于num_classes与 BCELoss、BCEWithLogitsLoss 和 CrossEntropyLoss 的关系
前言:关于「 num_classes 1 」引发的探究。 2024年尾声,学弟问到一个问题:在研究工作 CNNDetection 的github开源代码 networks/trainer.py 文件的 line 27 self.model resnet50(num_classes1) 中,变量 num_classes 的值为1&…...
组网实训实现
小型单元网络实现 IP划分: 外网:172.1.1.0/24 172.1.2.0/24 内网:基于192.168.3.0/24的子网划分 综合办公楼:192.168.3.00 000000 /26(192.168.3.0-192.168.3.63) 综合一楼:192.168.3.0000 0000 /28&…...
【DevOps】Jenkins部署
Jenkins部署 文章目录 Jenkins部署资源列表基础环境一、部署Gilab1.1、安装Gitlab1.2、修改配置文件1.3、加载配置文件1.4、访问Gitlab1.5、修改root登录密码1.6、创建demo测试项目1.7、上传代码1.8、验证上传的代码 二、部署Jenkins所需软件2.1、部署JDK2.2、部署Tomcat2.3、部…...
HTML——38.Span标签和字符实体
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>span标签和字符实体</title><style type"text/css">h1{text-align: center;}p{text-indent: 2em;}span{color: red;}</style></head><…...
doris:基于 Arrow Flight SQL 的高速数据传输链路
Doris 基于 Arrow Flight SQL 协议实现了高速数据链路,支持多种语言使用 SQL 从 Doris 高速读取大批量数据。 用途 从 Doris 加载大批量数据到其他组件,如 Python/Java/Spark/Flink,可以使用基于 Arrow Flight SQL 的 ADBC/JDBC 替代过去…...
文献阅读 | B. S. Carmo 2010
目录 一、文献名称二、原文地址三、ABSTRACT主要发现详细观察分岔分析雷诺数依赖性比较见解意义结论 四、IINTRODUCTION历史研究回顾计算研究近期研究进展研究空白与目的论文结构 一、文献名称 二、原文地址 研究目的:研究串列排列双圆柱体周围流场中的次级不稳定性…...
GRAPE——RLAIF微调VLA模型:通过偏好对齐提升机器人策略的泛化能力(含24年具身模型汇总)
前言 24年具身前沿模型大汇总 过去的这两年,工作之余,我狂写大模型与具身的文章,加之具身大火,每周都有各种朋友通过CSDN私我及我司「七月在线」寻求帮助/指导(当然,也欢迎各大开发团队与我司合作共同交付)…...
超越YOLO11!DEIM:先进的实时DETR目标检测
DEIM: DETR with Improved Matching for Fast Convergence arXiv: https://arxiv.org/abs/2412.04234 Project webpage:https://www.shihuahuang.cn/DEIM/ GitHub:https://github.com/ShihuaHuang95/DEIM 1 背景:DETR目标检测框架 目标检…...
django vue3实现大文件分段续传(断点续传)
前端环境准备及目录结构: npm create vue 并取名为big-file-upload-fontend 通过 npm i 安装以下内容"dependencies": {"axios": "^1.7.9","element-plus": "^2.9.1","js-sha256": "^0.11.0&quo…...
用户注册模块(芒果头条项目进度4)
1 创建⽤户模块⼦应⽤ 1.1 在项⽬包⽬录下 创建apps的python包。 1.2 在apps包下 创建应⽤userapp $ cd 项⽬包⽬录/apps $ python ../../manage.py startapp userapp 1.3 配置导包路径 默认情况下导包路径指向项⽬根⽬录 # 通过下⾯语句可以打印当前导包路径 print(sys.pa…...
Java Map集合、集合的嵌套
一. 概述 1. Map集合称为双列集合,格式:{key1value1, key2value2,.....},一次需要存一对数据作为一个元素。 2. Map集合的每个元素"keyvalue"称为一个键值对/键值对对象/一个Entry对象,Map集合也被称为"键值对集合"。 3.…...
C#中使用系统默认应用程序打开文件
有时您可能希望程序使用默认应用程序打开文件。 例如,您可能希望显示 PDF 文件、网页或互联网上的 URL。 System.Diagnostics.Process类的Start方法启动系统与文件关联的应用程序。 例如,如果文件扩展名为.txt,则系统会在 NotePad、WordPa…...
论文泛读《LPFHE: Low-Complexity Polynomial CNNs for Secure Inference over FHE》
文章目录 1、摘要2、介绍3、文章结构4、总结 1、摘要 Machine learning as a service (MLaaS) 在客户中越来越受欢迎。为了解决 MLaaS 中的隐私问题,引入了 FHE 来保护客户端的数据。 然而,FHE 不能直接评估 卷积神经网络 (CNNs) 中的非算数激活函数。…...
基于Spring Boot的IT技术交流和分享平台的设计与实现源码
风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的IT技术交流和分享平台的设计与实现。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 基于S…...
力扣hot100——二分查找
35. 搜索插入位置 class Solution { public:int searchInsert(vector<int>& a, int x) {if (a[0] > x) return 0;int l 0, r a.size() - 1;while (l < r) {int mid (l r 1) / 2;if (a[mid] < x) l mid;else r mid - 1;}if (a[l] x) return l;else …...
1月第一讲:WxPython跨平台开发框架之前后端结合实现附件信息的上传及管理
1、功能描述和界面 前端(wxPython GUI): 提供文件选择、显示文件列表的界面。支持上传、删除和下载附件。展示上传状态和附件信息(如文件名、大小、上传时间)。后端(REST API 服务):…...
uniapp:跳转第三方地图
1.跳转第三方高德地图 //跳转地图 toMap(item){uni.navigateTo({url: (window.location.href https://uri.amap.com/navigation?to${item.lng},${item.lat},${item.shopName}&modecar&policy1&srchttps://gawl.gazhcs.com/wap/index.html&callnative0)}) },…...
源码理解 UE4中的 FCookStatsManager::FAutoRegisterCallback RegisterCookStats
官方文档:https://dev.epicgames.com/documentation/zh-cn/unreal-engine/API/Runtime/Core/ProfilingDebugging/FCookStatsManager文档中的注释: When a cook a complete that is configured to use stats (ENABLE_COOK_STATS), it will broadcast this…...
QML Text详解
1. 概述 Text 是 QML 中用来显示文本的基本组件。它可以用于创建静态的标签、标题、说明文字等,支持多种文本格式、样式、颜色、对齐方式等配置。Text 组件也支持动态文本内容的显示,可以通过绑定数据源来实时更新文本内容。 Text 组件非常灵活&#x…...
详细讲一下Prettier对我们日常开发的作用,以及详细用法
1.什么是 Prettier? // Prettier 是代码格式化工具,它可以自动调整代码格式 // 比如把这样的代码: function foo ( a, b ){ return ab; }// 自动格式化成这样: function foo(a, b) {return a b; } 2.基础配置详解 {// 控制…...
多模态论文笔记——Coca
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍多模态模型Coca,在DALLE 3中使用其作为captioner基准模型的原因和优势。 文章目录 ALBEF论文模型结构组成训练目标 CoCa论文模型结构CoCa…...
24年无人机行业资讯 | 12.23-12.29
24年无人机行业资讯 | 12.23-12.29 1、 国家发改委新设低空经济司,助力低空经济规范发展2、商务部支持无人机民用国际贸易,强调出口管制与安全并重3、滨州高新区首架无人机成功下线4、 2025第九届世界无人机大会筹备推进会顺利召开5、2024年世界无人机竞…...
我们公司只有3个人,一个前端,一个后端
在当今这个数字化时代,各行各业都离不开互联网技术的支撑,而在这股技术浪潮中,小而美的创业公司如同雨后春笋般涌现,它们凭借着灵活高效、创新不断的特点,在市场中占有一席之地。 今天,就让我带你走进这样一…...
golang后台框架总结
gin [golang]gin框架接收websocket通信 - 花酒锄作田 - 博客园 golang Gin实现websocket_golang gin websocket-CSDN博客 ws测试: Websocket测试-Websocket在线测试-Websocket模拟请求工具 vmware下载: VMware16安装包详细安装教程_vmware16下载-CSDN博客...
Elasticsearch分片数量是什么意思?
Elasticsearch中的分片(Shard)数量是一个重要概念,以下为你详细介绍它的含义及相关要点: ### 定义 分片是Elasticsearch将索引数据进行拆分的基本单元。简单来说,Elasticsearch会把一个索引的数据分割成多个较小的部分…...
BerOS 文件系统路径归一化问题及其 Python 实现
题目背景 本文将讨论一道与操作系统路径归一化有关的问题,该问题来自 BerOS 文件系统 的设计。BerOS 是一个新型操作系统,其文件路径系统允许路径中的分隔符 / 重复出现。例如,以下路径被视为等价的: /usr//local//nginx/sbin//…...
【微服务】1、引入;注册中心;OpenFeign
微服务技术学习引入 - 微服务自2016年起搜索指数持续增长,已成为企业开发大型项目的必备技术,中高级java工程师招聘多要求熟悉微服务相关技术。微服务架构介绍 概念:微服务是一种软件架构风格,以专注于单一职责的多个响应项目为基…...
SQL Server 数据库 忘记密码
1、先用windows 身份验证 连接 2、安全性--登录名 3、设置 身份验证 4、重启电脑 5、登录 登陆成功!!! ------------------------------------------------------------------ --1、查询登录账号信息 ------------------------------------------------------------------ -- …...
Tableau数据可视化与仪表盘搭建-安装教程
下载 tableau.com/zh-cn/support/releases 滚动到最下方的下载 在下载的同时 我们点击登录,去注册一个tableau的账号 下面点击我们下载好的tableau安装程序 不要自定义安装,会有路径问题 点击试用14天 点击激活 激活学生 tableau.com/zh-cn/academic…...
UGUI 优化DrawCall操作记录(基于Unity2021.3.18)
UGUI中相同材质相同Shader相同贴图的UI元素可以合并DrawCall。 1.使用图集 Unity性能优化---使用SpriteAtlas创建图集进行批次优化_unity2021.3.33 spriteatlas优化-CSDN博客 2.Canvas的子物体在场景树中的索引位置和不同图集不影响UI合批且UI网格没有重叠,如下图…...
DeepSeek v3为何爆火?如何用其集成Milvus搭建RAG?
最近,DeepSeek v3(一个MoE模型,拥有671B参数,其中37B参数被激活)模型全球爆火。 作为一款能与Claude 3.5 Sonnet,GPT-4o等模型匹敌的开源模型DeepSeek v3不仅将其算法开源,还放出一份扎实的技术…...
网络编程原理:回显服务器与客户端通信交互功能
文章目录 路由器及网络概念网络通信基础TCP/IP 五层协议封装和分用封装分用 网络编程(网络协议)UDP类 API使用实现回显通信程序回显服务器(UDP代码)回显客户端(UDP代码) TCP API使用回显服务器(TCP代码)回显客户端(TCP代码) 路由器及网络概念 网络发展是…...
#渗透测试#漏洞挖掘#WAF分类及绕过思路
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…...
西安电子科技大学初/复试笔试、面试、机试成绩占比
西安电子科技大学初/复试笔试、面试、机试成绩占比 01通信工程学院 02电子工程学院 03计算机科学与技术学院 04机电工程学院 06经济与管理学院 07数学与统计学院 08人文学院 09外国语学院 12生命科学与技术学院 13空间科学与技术学院 14先进材料与纳米科技学院 15网络与信息安…...
【Python学习(六)——While、for、循环控制、指数爆炸】
Python学习(六)——While、for、循环控制、指数爆炸 本文介绍了While、for、循环控制、指数爆炸,仅作为本人学习时记录,感兴趣的初学者可以一起看看,欢迎评论区讨论,一起加油鸭~~~ 心中默念:Py…...
中间件自动化测试框架cmdlinker
背景 作为一个中间件的测试工程师,如何对于中间件提供的命令进行自动化的回归,这一直是一个难题,市面上好像缺乏了对于命令进行自动化回归的合理解决方案。 常见方式有下面两种: 直接写字符串的命令,然后使用各种编程…...
写一个类模板三个模板参数K,V,M,参数是函数(函数参数、lambda传参、函数指针)
cal是类的成员函数。cal的3个入参是func1(K),func2(K,V),func3(K,V,M),请写出cal,并在main函数中调用cal 在您给出的要求中,cal成员函数并不直接…...
STM32-笔记35-DMA(直接存储器访问)
一、什么叫DMA? DMA(Direct Memory Access,直接存储器访问)提供在外设与内存、存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于…...
数势科技:解锁数据分析 Agent 的智能密码(14/30)
一、数势科技引领数据分析变革 在当今数字化浪潮中,数据已然成为企业的核心资产,而数据分析则是挖掘这一资产价值的关键钥匙。数势科技,作为数据智能领域的领军者,以其前沿的技术与创新的产品,为企业开启了高效数据分析…...
ES6中定义私有属性详解
在ES6中,定义私有属性的方式相对传统的JavaScript有所不同。ES6并没有提供直接的语法来定义私有属性,但可以通过几种方法间接实现私有属性。 1. 使用Symbol来模拟私有属性 Symbol是一种新的数据类型,可以作为对象的键,并且它的值…...
@Cacheable 注解爆红(不兼容的类型。实际为 java. lang. String‘,需要 ‘boolean‘)
文章目录 1、org.springframework.cache.annotation.Cacheable2、javax.persistence.Cacheable Cacheable(value "findPAUserById", key "#id")public Optional<PAUser> findById(Integer id) {return paUserRepository.findById(id);}我真的要笑死…...
离散数学 期末笔记
命题符号化 使用等值演算法证明 求公式范式 在自然推理体系中构造下列推理的证明 在一阶逻辑中将下列命题符号化 设A、B、C、D是 Z 的子集 证明下列集合恒等式 二元关系 性质 没有空的 没有漏的 没有重复 函数...
物联网控制期末复习
第3章 物联网控制系统的过程通道设计 3.1 模拟量输出通道 3.1.1单模拟量输出通道的构成 计算机控制系统的模拟量输出通道将计算机产生的数字控制信号转换为模拟信号(电压或电流)作用于执行机构,以实现对被控对象的控制。 多D/A结构&#…...
Hypium纯血鸿蒙系统 HarmonyOS NEXT自动化测试框架
1、什么是Hypium Hypium是华为官方为鸿蒙操作系统开发的一款以python为语言的自动化测试框架。 引用华为官网介绍如下: DevEco Testing Hypium(以下简称Hypium)是HarmonyOS平台的UI自动化测试框架,支持开发者使用python语言为应用编写UI自动化测试脚本…...
李宏毅机器学习课程笔记01 | 1.Introduction of Machine/Deep Learning
笔记是在语雀上面做的,粘贴在CSND上可能存在格式错误 机器学习的本质就是借助机器寻找一个转换函数 根据函数的输出类型,可以将机器学习进行分类 regression 回归任务:函数输出时一个数值classification 分类任务:人类设定好选项…...
探索Docker Compose:轻松管理多容器应用
探索Docker Compose:轻松管理多容器应用 在现代软件开发中,容器化已经成为构建、部署和扩展应用的主流方式。而Docker Compose作为Docker生态系统的重要组成部分,可以简化多容器应用的管理。本文将深入探讨Docker Compose的核心功能及应用场…...
java中static和const和final的区别
static 关键字 static 关键字用于声明类的成员(方法或变量)为静态成员。静态成员属于类本身,而不是类的实例。换句话说,静态成员可以通过类名直接访问,而不需要实例化对象。 静态变量:属于类的所有对象共…...