推荐系统简介
Mahout关注的三个核心问题: 聚类,分类,推荐(协同过滤).
聚类: 聚类技术试图将大量的事物组合为拥有类似属性的簇(cluster),借以在一些规模较大或难于理解的数据集上发现层次结构和顺序,以揭示一些有用的模式或让数据更易于理解.
分类: 分类技术决定了一个事物多大程度上从属于某些类型或类别,或者多大程度上具有或不具有某些属性.
第一个推荐引擎
public class RecommenderIntro {
public static void main(String[] args) throws Exception {
String basePath = "/Users/zhange/work/projects/TestMahout/src/main/java/";
DataModel model = new FileDataModel(new File(basePath + "Chapter02/intro.csv")); // 加载数据文件
UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model);
Recommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity); // 生成推荐引擎
List<RecommendedItem> recommendations = recommender.recommend(1, 1); // 为第一个用户推荐一本书
for (RecommendedItem recommendation : recommendations) {
System.out.println(recommendation);
}
}
}
上面用到了多个Mahout提供的组件:
- DataModel: 实现存储并未计算提供其所需的所有偏好,用户,物品数据
- UserSimilarity: 实现给出连个用户之间的相似度,可以从多种可能度量或计算中选用一种来作为依据
- UserNeighborhood: 实现明确了与给定用户最相似的一组用户
- Recommender: 实现合并这些组件,为用户推荐物品
各个组件之间的关系:
上面例子中的输出结果为: RecommendedItem[item:104, value:4.257081]
表示的意思是,将物品104推荐给用户1,并且给出用户1对104物品的估计评分为4.257081.
评估一个推荐引擎
一般的评估方法是,将真实的偏好数据与估计的偏好数据进行对比,当没有真实的偏好数据可用时,从需要评估的数据中取出一小部分作为真实数据,剩余的部分用作推荐引擎的训练计算数据,然后将二者进行对比,得到推荐引擎的效果评估结果.
简单的评估方式:
- 平均差值: 计算出估计和实际偏好的平均差值,差值越低越好
- 均方根: 计算出实际偏好值和估计值之间的差值之后,先进性平方再求其平均值的平方根,值越低越好.
评估过程
public class EvaluatorIntro {
private EvaluatorIntro() {
}
public static void main(String[] args) throws Exception {
String basePath = "/Users/zhange/work/projects/TestMahout/src/main/java/";
// 生成可重复的结果
RandomUtils.useTestSeed();
DataModel model = new FileDataModel(new File(basePath + "Chapter02/intro.csv"));
// 使用平均差值进行评估,或者可以使用均方根进行评估: RMSRecommenderEvaluator()
RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator();
// 跟上个例子一样,根据现有的数据创建一个recommender,返回一个RecommenderBuilder:
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
//@Override
public Recommender buildRecommender(DataModel model) throws TasteException {
UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
UserNeighborhood neighborhood =
new NearestNUserNeighborhood(2, similarity, model);
return new GenericUserBasedRecommender(model, neighborhood, similarity);
}
};
// 使用70%的数据进行训练,30%的数据进行测试
double score = evaluator.evaluate(recommenderBuilder, // 上面创建的RecommenderBuilder
null, // DataModelBuilder的实例,用于控制如何从训练数据中生成DataModel,通常为null,除非需要控制如何从训练数据中生成DataModel
model, //
0.7,
1.0); // 用于控制使用多少输入数据,1.0即当前数据的100%
System.out.println(score);
}
}
如上所示,大部分计算行为发生在evaluate()中,EvaluatorIntro会将数据按照参数中提供的比例,将model数据分为训练集合测试集,将训练集与传入的RecommenderBuilder重新构建一个Recommender,然后根据训练结果与测试数据进行比较,得到评估结果.
评估的查准率(精度)和查全率(召回率)
使用经典的信息检索度量标准来评估推荐程序:查准率和查全率,其目的是尽量避免在搜索结果中返回无关信息,而应竭力返回相关的信息.
在推荐系统中的解释:
查准率: 指top结果中相关结果的比例,”Precision at 10(推荐10个结果时德尔查准率)”,是指这个比例来自对前10个top结果的判定.
查全率: 是指所有相关结果包含在top结果中的比例.
用在推荐中的说法就是: 查准率是top推荐的10个结果中有几个是真正相关的,查全率是所有相关的项出现在top中的比例.
Mahout中提供的相关评估:
public class IREvaluatorIntro {
private IREvaluatorIntro() {
}
public static void main(String[] args) throws Exception {
String basePath = "/Users/zhange/work/projects/TestMahout/src/main/java/";
RandomUtils.useTestSeed();
DataModel model = new FileDataModel(new File(basePath + "Chapter02/intro.csv"));
RecommenderIRStatsEvaluator evaluator = new GenericRecommenderIRStatsEvaluator(); // 新建一个RecommenderEvaluator
// 跟上个例子一样,根据现有的数据创建一个recommender,返回一个RecommenderBuilder:
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
//@Override
public Recommender buildRecommender(DataModel model) throws TasteException {
UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model);
return new GenericUserBasedRecommender(model, neighborhood, similarity);
}
};
// 推荐两个结果时的评估:
IRStatistics stats = evaluator.evaluate(recommenderBuilder,
null, model, null, 2,
GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD,
1.0);
System.out.println(stats.getPrecision());
System.out.println(stats.getRecall());
}
}
运行结果解析:
0.75
1.0
0.75: 表示退浆两个结果时的查准率为0.75,即75%的推荐结果是真正相关的
1.0: 表示推荐两个结果时的查全率为1.0, 即所有真正相关的项都包含在推荐结果中
偏好数据的表示
推荐引擎的输入为偏好数据,即: 用户ID,物品ID,偏好值.而时候偏好值会被忽略.
Preference
Preference是一个最基本的抽象,表示用户ID,物品ID,偏好值.Perference是一个接口,最常用的实现是GeneriPreference.比如:
new Preference(123, 145, 3.0f)
一个Preference中共包含20字节数据,(long,long,float),整个对象占用28字节,因此资源消耗很大.
而PreferenceArray表示一个聚合的偏好,具有类似数组的API.比如,GeneriUserPreferenceArray,表示与某个用户关联的偏好集合,包含一个单一用户ID,一个物品ID数组,一个偏好值数组,存储因而更加高效.
设置PreferenceArray中的偏好值:
PreferenceArrayy user1Prefs = new GeneriUserPreferenceArray(2);
user1Prefs.setUserID(0, 1L);
user1Prefs.setItemID(0, 122L);
user1Prefs.setValue(0,2.0f);
user1Prefs.setItemID(1, 103L);
user1Prefs.setValue(1, 3.0f);
而对应于物品的GenericItemPreferenceArray与其原理实现一致.
FastByIDMap和FastIDSet
Mahout中使用了大量Map和Set数据结构,但并不是JDK中的TreeSet和HashMap,而是FastMap,FastByIDMap,FastIDSet,以降低内存占用.
数据结构对比:
- 与HashMao类似,FastByIDMap是基于散列的.但是它在处理列冲突时使用的是线性探测,而非分离链接,这样便不必为每个条目都增加一个额外的Map.Entry对象.如前所述,Object对内存的消耗是惊人的.
- 在Mahout推荐程序中,键(key)和成员(member)通常采用原始类型long,而非object,使用long型的键能够节省内存并提升性能.
- Set实现的内部没有使用Map.
- FastByIDMap可以作为高速缓存,因为他有一个最大空间的概念,超过这个大小时,若要新加入条目则会把不常用的移走.
这些实现不是单纯为了提升性能,而是为了减少内存占用.
内存级DataModel
基于文件的数据
可刷新组件
Refresh接口,只公开了一个方法: refresh(Collection
FileDataModel支持刷新文件,在读取主数据文件之后额外生成的数据文件,并可以覆盖任何以前度去过对的数据.需要更新的文件必须目录相同,前缀相同,foo.tx -> foo.1.txt .
JDBCDataModel
JDBCDataModel中数据库的设置:
- 用户ID和物品ID列应为非空,而且必须被索引
- 主键必须为用户ID和物品ID的组合主键
- 列的数据类型根据Java中对应long和float的类型来选择.在MySQ中,应该为BIGING和FLOAT
- 注意调节缓冲区和查询告诉缓存,见 MySQLJDBCDataModel的javadoc
- 当使用MySQL的Connector/J驱动时,将驱动的参数设为True
无偏好值的处理
Mahout中没有偏好值的关联称为布尔型偏好.在内存中的表示使用GerericBooleanPrefDataModel.