推荐引擎可以分为两类:
1、协同过滤CF引擎,基于用户相似度或基于条目。
2、基于内容,内容指的分析问卷调查等。
3.2.1 基于相似用户的推荐
找到与用户相似的用户,将他们的评分乘以相似度得出加权评分,这是基本原理。
package com.hankcs; import iweb2.ch3.collaborative.data.BaseDataset; import iweb2.ch3.collaborative.data.MusicData; import iweb2.ch3.collaborative.data.MusicUser; import iweb2.ch3.collaborative.recommender.Delphi; import iweb2.ch3.collaborative.similarity.RecommendationType; public class ch3_2_UserBasedSimilarity { public static void main(String[] args) throws Exception { // The static method createDataset will create random ratings // for all the users, pick 75% of all the songs and assign ratings. // // Users whose name starts from A to D should have ratings between 3 and 5 // Users whose name starts from E to Z should have ratings between 1 and 3 // 创建测试数据 BaseDataset ds = MusicData.createDataset(); // // Serialize the dataset, so that we can load it in the next script // // 序列化测试数据 ds.save("C:/iWeb2/deploy/data/ch3_2_dataset.ser"); // // use Delphi with USER_BASED similarity // // 创建基于用户的推荐系统 Delphi delphi = new Delphi(ds, RecommendationType.USER_BASED); // 输出详情 delphi.setVerbose(true); // // Show me users like X (top 5) // // 找到Bob和John的相似用户 MusicUser mu1 = (MusicUser) ds.pickUser("Bob"); delphi.findSimilarUsers(mu1); MusicUser mu2 = (MusicUser) ds.pickUser("John"); delphi.findSimilarUsers(mu2); // // Show me recommendations for user X (top 5) // delphi.recommend(mu1); //------------------------------------------------------------------------ //BaseDataset ds = MusicData.createDataset(); //ds.save("C:/iWeb2/deploy/data/ch3_2_dataset.ser"); //Delphi delphi = new Delphi(ds,RecommendationType.USER_BASED); //delphi.setVerbose(true); //MusicUser mu1 = ds.pickUser("Bob"); //delphi.findSimilarUsers(mu1); //MusicUser mu2 = ds.pickUser("John"); //delphi.findSimilarUsers(mu2); //delphi.recommend(mu1); //------------------------------------------------------------------------ } }
计算用户相似度
相似性使用Jacard度量,两个用户评分相同的次数 / 他们评分条目的并集的元素个数。
iweb2/ch3/collaborative/similarity/UserBasedSimilarity.java
protected void calculate(Dataset dataSet) { int nUsers = dataSet.getUserCount(); // 定义相似度矩阵的规模 int nRatingValues = 5; // 定义评分计算矩阵的规模 similarityValues = new double[nUsers][nUsers]; if (keepRatingCountMatrix) { ratingCountMatrix = new RatingCountMatrix[nUsers][nUsers]; } // if we want to use mapping from userId to index then generate // index for every userId // 如果需要从用户Id到下标索引的映射,则为每个用户生成索引 if (useObjIdToIndexMapping) { for (User u : dataSet.getUsers()) { idMapping.getIndex(String.valueOf(u.getId())); } } for (int u = 0; u < nUsers; u++) { int userAId = getObjIdFromIndex(u); User userA = dataSet.getUser(userAId); for (int v = u + 1; v < nUsers; v++) { // 计算相似度矩阵 //每个user只与id比他大的计算一次,保证不重不漏 int userBId = getObjIdFromIndex(v); User userB = dataSet.getUser(userBId); // 两个用户评分的一致性矩阵 RatingCountMatrix rcm = new RatingCountMatrix(userA, userB, nRatingValues); int totalCount = rcm.getTotalCount(); int agreementCount = rcm.getAgreementCount(); if (agreementCount > 0) { // Jaccard相似度,交集除以并集 similarityValues[u][v] = (double) agreementCount / (double) totalCount; } else { similarityValues[u][v] = 0.0; } // For large datasets if (keepRatingCountMatrix) { ratingCountMatrix[u][v] = rcm; } } // for u == v assign 1. // RatingCountMatrix wasn't created for this case similarityValues[u][u] = 1.0; } }
Delphi 内部工作机制
基于用户间相似度预测评分
某个条目的用户A预测评分 = ∑A与X的相似度 * X对该条目的评分 / ∑A与X的相似度 (X为除了A之外的任意用户)
iweb2/ch3/collaborative/recommender/Delphi.java
public List<PredictedItemRating> recommend(User user, int topN) { List<PredictedItemRating> recommendations = new ArrayList<PredictedItemRating>(); double maxRating = -1.0d; // 遍历所有条目 for (Item item : dataSet.getItems()) { // only consider items that user hasn't rated yet or doesn't own the content // 只考虑该用户还未评分的条目 if (!skipItem(user, item)) { // 只考虑该用户还未评分的条目 double predictedRating = predictRating(user, item); // 为这名用户预测评分 if (maxRating < predictedRating) { maxRating = predictedRating; } if (!Double.isNaN(predictedRating)) { // 把候选推荐的预测分值累加起来 recommendations.add(new PredictedItemRating(user.getId(), item.getId(), predictedRating)); } } else { if (verbose) { System.out.println("Skipping item:" + item.getName()); } } } this.maxPredictedRating.put(user.getId(), maxRating); List<PredictedItemRating> topNRecommendations = // 对预测分值排序,返回大小不大于topN的列表 PredictedItemRating.getTopNRecommendations(recommendations, topN); if (verbose) { PredictedItemRating.printUserRecommendations(user, dataSet, topNRecommendations); } return topNRecommendations; }
3.2.2 基于相似条目的推荐
基于条目和基于用户类似,只是将用户相似度换成条目相似度。
相似性依然使用Jacard度量,两个条目被评分相同的次数 / 被评分次数
某个用户的条目A预测评分 = ∑A与X的相似度 * 该用户对A的评分 / ∑A与X的相似度 (X为除了A之外的任意条目)
package com.hankcs; import iweb2.ch3.collaborative.data.BaseDataset; import iweb2.ch3.collaborative.data.MusicData; import iweb2.ch3.collaborative.data.MusicItem; import iweb2.ch3.collaborative.data.MusicUser; import iweb2.ch3.collaborative.recommender.Delphi; import iweb2.ch3.collaborative.similarity.RecommendationType; public class ch3_3_ItemBasedSimilarity { public static void main(String[] args) throws Exception { // // Load the dataset that we created before // // 载入上次的数据库 BaseDataset ds = BaseDataset.load("C:/iWeb2/deploy/data/ch3_2_dataset.ser"); // // use Delphi with ITEM_BASED similarity // // 创建基于条目的推荐引擎 Delphi delphi = new Delphi(ds,RecommendationType.ITEM_BASED); delphi.setVerbose(true); // // Show me recommendations for user X (top 5) // // 给Bob推荐一些条目 MusicUser mu1 = (MusicUser) ds.pickUser("Bob"); delphi.recommend(mu1); // // Show me items like X (top 5) // // 找出与这个条目相似的条目 MusicItem mi = (MusicItem) ds.pickItem("La Bamba"); delphi.findSimilarItems(mi); } }
此时对相似度的计算不同:
protected void calculate(Dataset dataSet) { // 定义相似度矩阵的规模 int nItems = dataSet.getItemCount(); // 定义评分计数矩阵的规模 int nRatingValues = 5; similarityValues = new double[nItems][nItems]; if (keepRatingCountMatrix) { ratingCountMatrix = new RatingCountMatrix[nItems][nItems]; } // if we want to use mapping from itemId to index then generate // index for every itemId // 如果需要生成从条目id到下标索引的映射,则为每个条目生成索引 if (useObjIdToIndexMapping) { for (Item item : dataSet.getItems()) { idMapping.getIndex(String.valueOf(item.getId())); } } // By using these variables we reduce the number of method calls // inside the double loop. int totalCount = 0; int agreementCount = 0; for (int u = 0; u < nItems; u++) { int itemAId = getObjIdFromIndex(u); Item itemA = dataSet.getItem(itemAId); // we only need to calculate elements above the main diagonal. // 只需要计算主对角线上方的元素 for (int v = u + 1; v < nItems; v++) { int itemBId = getObjIdFromIndex(v); Item itemB = dataSet.getItem(itemBId); RatingCountMatrix rcm = new RatingCountMatrix(itemA, itemB, nRatingValues); // 所有打分次数 totalCount = rcm.getTotalCount(); // 打分相同的次数 agreementCount = rcm.getAgreementCount(); if (agreementCount > 0) { similarityValues[u][v] = (double) agreementCount / (double) totalCount; } else { similarityValues[u][v] = 0.0; } if (keepRatingCountMatrix) { ratingCountMatrix[u][v] = rcm; } } // for u == v assign 1 // 对于uv相同的情况赋值1 similarityValues[u][u] = 1.0; } }
3.2.3 基于内容的推荐
在没有评分数据的情况下判断两个文档有多接近。
package com.hankcs; import iweb2.ch3.collaborative.data.BaseDataset; import iweb2.ch3.collaborative.data.MusicData; import iweb2.ch3.collaborative.data.MusicItem; import iweb2.ch3.collaborative.data.MusicUser; import iweb2.ch3.collaborative.recommender.Delphi; import iweb2.ch3.collaborative.similarity.RecommendationType; public class ch3_3_ItemBasedSimilarity { public static void main(String[] args) throws Exception { // // Load the dataset that we created before // // 载入上次的数据库 BaseDataset ds = BaseDataset.load("C:/iWeb2/deploy/data/ch3_2_dataset.ser"); // // use Delphi with ITEM_BASED similarity // // 创建基于条目的推荐引擎 Delphi delphi = new Delphi(ds,RecommendationType.ITEM_BASED); delphi.setVerbose(true); // // Show me recommendations for user X (top 5) // // 给Bob推荐一些条目 MusicUser mu1 = (MusicUser) ds.pickUser("Bob"); delphi.recommend(mu1); // // Show me items like X (top 5) // // 找出与这个条目相似的条目 MusicItem mi = (MusicItem) ds.pickItem("La Bamba"); delphi.findSimilarItems(mi); } }
相似度计算:
/** * 基于文本计算用户相似度 */ protected void calculate(Dataset dataSet) { int nUsers = dataSet.getUserCount(); similarityValues = new double[nUsers][nUsers]; // if we want to use mapping from userId to index then generate // index for every userId if (useObjIdToIndexMapping) { for (User u : dataSet.getUsers()) { idMapping.getIndex(String.valueOf(u.getId())); } } // 生成余弦相似性度量 CosineSimilarityMeasure cosineMeasure = new CosineSimilarityMeasure(); String[] allTerms = dataSet.getAllTerms(); for (int u = 0; u < nUsers; u++) { int userAId = getObjIdFromIndex(u); User userA = dataSet.getUser(userAId); for (int v = u + 1; v < nUsers; v++) { int userBId = getObjIdFromIndex(v); User userB = dataSet.getUser(userBId); double similarity = 0.0; for (Content userAContent : userA.getUserContent()) // 对用户A的所有评分条目进行迭代 { double bestCosineSimValue = 0.0; for (Content userBContent : userB.getUserContent()) // 对用户B的所有评分条目进行迭代 { double cosineSimValue = cosineMeasure.calculate( userAContent.getTermVector(allTerms), userBContent.getTermVector(allTerms)); bestCosineSimValue = Math.max(bestCosineSimValue, cosineSimValue); } similarity += bestCosineSimValue; // 累加所有文档的最佳相似度 } //System.out.println("Similarity user[" + u + "][" + v + "]=" + similarity); similarityValues[u][v] = similarity / userA.getUserContent().size(); // 通过简单的取平均得到相似度 } // for u == v assign 1. similarityValues[u][u] = 1.0; } }
样例设置
用户名A-D的用户偏好bz和sport
其他用户偏好usa和world
基于内容的相似度的要点
找出每个文档前N个出现最频繁的单词,求并集。该并集大小为M,则每个文档可以表示为一个1 * M的向量。
文档A、B的相似度等于 两个向量的点积 / 两个向量的长度的积
package iweb2.ch3.collaborative.similarity; /** * 计算词向量间的余弦相似度 */ public class CosineSimilarityMeasure { public double calculate(double[] v1, double[] v2) { double a = getDotProduct(v1, v2); double b = getNorm(v1) * getNorm(v2); // 标准化两个向量并计算它们的乘积 return a / b; // 得到余弦相似度 } private double getDotProduct(double[] v1, double[] v2) { double sum = 0.0; for (int i = 0, n = v1.length; i < n; i++) { sum += v1[i] * v2[i]; } return sum; } /** * 计算一个向量的欧几里得模 * @param v * @return */ private double getNorm(double[] v) { double sum = 0.0; for (int i = 0, n = v.length; i < n; i++) { sum += v[i] * v[i]; } return Math.sqrt(sum); } }
三类基于内容的推荐引擎
相似用户的寻找算法是成功的,相似条目的寻找则稍有模糊。这是由自然语言处理(NLP)制约的。两者杂交形成的用户–条目推荐器则是一个折中。
博主你好,我有一个异议。“相似性使用Jacard度量,两个用户评分相同的次数 / 他们评分条目的并集的元素个数。”我觉得这里除以“他们评分条目的并集”不太科学,应该是除以“他们都做出评价的条目”。这是我的主要异议。另外,单纯的“评分相同的次数”是不是不如“1-评分之差/总分”?