
推荐引擎可以分为两类:
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-评分之差/总分”?