背景:
如今已经进入了一个数据爆炸的时代,随着 Web 2.0 的发展, Web 已经变成数据分享的平台,那么,如何让人们在海量的数据中想要找到他们需要的信息将变得越来越难。
在这样的情形下,搜索引擎(Google,Bing,百度等等)成为大家快速找到目标信息的最好途径。在用户对自己需求相对明确的时候,用搜索引擎很方便的通过关键字搜索很快的找到自己需要的信息。但搜索引擎并不能完全满足用户对信息发现的需求,那是因为在很多情况下,用户其实并不明确自己的需要,或者他们的需求很难用简单的关键字来表述。又或者他们需要更加符合他们个人口味和喜好的结果,因此出现了推荐系统,与搜索引擎对应,大家也习惯称它为推荐引擎。
推荐引擎:
推荐引擎利用特殊的信息过滤技术,将不同的物品或内容推荐给可能对它们感兴趣的用户。
工作原理图如下
1-1
原理图如上,接受对应的数据源,然后分析推荐信息给用户,一般的推荐源包括隐式和显式
一般总结如图下:
1-2
通过这些信息分析出对应用户之间的相似度,通过对应协同过滤算法进行推荐。
根据不同数据源推荐方法可以分为以下几大类:
- A 根据系统用户的基本信息发现用户的相关程度,这种被称为基于人口统计学的推荐(Demographic-based Recommendation)
- B 根据推荐物品或内容的元数据,发现物品或者内容的相关性,这种被称为基于内容的推荐(Content-based Recommendation)
- C 根据用户对物品或者信息的偏好,发现物品或者内容本身的相关性,或者是发现用户的相关性,这种被称为基于协同过滤的推荐(Collaborative Filtering-based Recommendation)。
解释一下,
A 例如不同用户之间年龄相仿,或者同是女人又或者都是老师等等这些基本上可以定义为用户本身属性,基于这些属性大体上可以得出一定的相似度。
B 例如不同的电影更具不同的标签判断同是恐怖片,或者同是爱情片,动作片等也可以大体分析出不同商品之间的相关性
C 前两者明显人性化角度来说精确度欠佳,用户对物品的偏好很多时候和物品本身没有关系,有可能和用户习惯等等相关,所以协同过滤可以很好的优化人性学推荐。
协同过滤
协同过滤业界分为两种:
1 基于用户的协同过滤推荐(base-usered Collaborative fliter)
1-3
基于用户的 CF 的基本思想相当简单,基于用户对物品的偏好找到相邻邻居用户,然后将邻居用户喜欢的推荐给当前用户。计算上,就是将一个用户对所有物品的偏好作为一个向量来计算用户之间的相似度,找到 K 邻居后,根据邻居的相似度权重以及他们对物品的偏好,预测当前用户没有偏好的未涉及物品,计算得到一个排序的物品列表作为推荐。图 1-3 给出了一个例子,对于用户 A,根据用户的历史偏好,这里只计算得到一个邻居 - 用户 C,然后将用户 C 喜欢的物品 D 推荐给用户 A。
2 基于项目的协同过滤推荐(base-item Collaborative fliter)
1-4
基于物品的 CF 的原理和基于用户的 CF 类似,只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据用户的历史偏好,推荐相似的物品给他。从计算的角度看,就是将所有用户对某个物品的偏好作为一个向量来计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到一个排序的物品列表作为推荐。图 1-4给出了一个例子,对于物品 A,根据所有用户的历史偏好,喜欢物品 A 的用户都喜欢物品 C,得出物品 A 和物品 C 比较相似,而用户 C 喜欢物品 A,那么可以推断出用户 C 可能也喜欢物品 C。
3 基于模型的协同过滤推荐(base-model Collaborative fliter)
也是深度学习(ML)在推荐系统中的应用,前两种统称为Memory-Recommendation(内存推荐),那么模型推荐又是什么?
基于模型的协同过滤作为目前最主流的协同过滤类型,其相关算法可以写一本书了,当然我们这里主要是对其思想做有一个归类概括。我们的问题是这样的m个物品,m个用户的数据,只有部分用户和部分数据之间是有评分数据的,其它部分评分是空白,此时我们要用已有的部分稀疏数据来预测那些空白的物品和数据之间的评分关系,找到最高评分的物品推荐给用户。
换句化说大数据时代协同过滤算法已经层出不穷,相关ML算法简要列举如下:
K-means (聚类协同),Baye(分类协同) ,ALS(最小交叉二乘法的矩阵分解协同),SVD(奇异值的矩阵分解协同等)
1-0
对于上图,我们可以看到,也可以想到不同的用户不可能会对每个商品都有评分记录
现阶段实现的为ALS算法矩阵分解的模型协同推荐,也很容易看出来也是未来深度学习,海量数据建模的优势所在。
PS:简单分析下这两种算法的利弊,Base-userd-CF(基于用户的协同过滤)简称UCF,和Based-items-CF(基于商品的协同过滤)简称ICF 来说,UCF基于大多数用户都会买的心里倾向于推荐热门商品,而对长尾商品推荐足,换句话说系统覆盖率不好。ICF基于用户对商品本身的偏好推荐相似商品来说即便推荐精度略小于UCF,但是系统多样性要好与UCF,所以很明显两者基本互补,业界对该推荐算法的选择也是以混合,分区等综合推荐为主,例如Amazon 的分区推荐等。
单机协同过滤实现与设计(mahout):
Mahout 为apache ASF 旗下的子项目,原生支持云数据处理,整合HADOOP。内至丰富的协同过滤算法实现,可以很方便的迁移至云环境,进行分布式计算
适合实时在线小数据量的计算,代码如下
Create a data source from the CSV file
boolean printCommonalities = Boolean.parseBoolean("false");
String ROOT = System.getProperty("user.home");
File userPreferencesFile = new File(ROOT + "/recommendations.txt");
FileDataModel dataModel = new FileDataModel(userPreferencesFile);
System.out.println("Data Model: Users: " + dataModel.getNumUsers() + " Items: " + dataModel.getNumItems());
Long userId = 955L;
UserSimilarity userSimilarity1 = new EuclideanDistanceSimilarity(dataModel);
// Optional:
final CachingUserSimilarity userSimilarity = new CachingUserSimilarity(userSimilarity1, dataModel);
userSimilarity.setPreferenceInferrer(new AveragingPreferenceInferrer(dataModel));
//Get a neighborhood of users
UserNeighborhood neighborhood =
new NearestNUserNeighborhood(3, userSimilarity, dataModel);
//Create the recommender
Recommender recommender1 =
new GenericUserBasedRecommender(dataModel, neighborhood, userSimilarity);
final CachingRecommender recommender = new CachingRecommender(recommender1);
System.out.println("-----");
System.out.println("User: " + userId);
WikiContentHandler handler = new WikiContentHandler();
//Print out the users own preferences first
if (printCommonalities) {
long[] users = neighborhood.getUserNeighborhood(userId);
for (int i = 0; i < users.length; i++) {
long neighbor = users[i];
System.out.println("Neighbor: " + neighbor);
TasteUtils.printCommonalities(dataModel, userId, neighbor, handler.map);
}
System.out.println("");
}
long start = System.currentTimeMillis();
//Get the top 5 recommendations
List<RecommendedItem> recommendations = recommender.recommend(userId, 5);
TasteUtils.printRecs(recommendations, handler.map);
long end = System.currentTimeMillis();
System.out.println(end - start);
//recommendations/
// Make all the randomness in the project predictable and repeatable, so you can compare scores as you make changes to the recommender components.
RandomUtils.useTestSeed();
RecommenderIRStatsEvaluator evaluator = new GenericRecommenderIRStatsEvaluator();
// Building a recommendation engine with RecommenderBuilder
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
@Override
public Recommender buildRecommender(DataModel model) throws TasteException {
UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.7, similarity, model);
return new GenericBooleanPrefUserBasedRecommender(model, neighborhood, similarity);
}
};
// Use 70% of the data to train; test using the other 30%.
IRStatistics score = evaluator.evaluate(recommenderBuilder, null, dataModel, new IDRescorer() {
@Override
public double rescore(long id, double originalScore) {
return 0;
}
@Override
public boolean isFiltered(long id) {
return false;
}
}, 5, 0.7, 1.0);
System.out.println("user-based as score \t " + score);
RecommenderIRStatsEvaluator evaluator1 = new GenericRecommenderIRStatsEvaluator();
// Building a recommendation engine with RecommenderBuilder
RecommenderBuilder recommenderBuilder1 = new RecommenderBuilder() {
@Override
public Recommender buildRecommender(DataModel model) throws TasteException {
ItemSimilarity similarityCommmon = new LogLikelihoodSimilarity(model);
ItemSimilarity similarity = new CachingItemSimilarity(similarityCommmon, model);
return new CachingRecommender(
new GenericBooleanPrefItemBasedRecommender(
model, similarity
));
}
};
// Use 70% of the data to train; test using the other 30%.
IRStatistics score1 = evaluator1.evaluate(recommenderBuilder1, null, dataModel, new IDRescorer() {
@Override
public double rescore(long id, double originalScore) {
return 0;
}
@Override
public boolean isFiltered(long id) {
return false;
}
}, 5, 0.7, 1.0);
System.out.println("item-based as score \t " + score1);
测试数据
350W条数据量,大小100M 左右,单机mahout 运行单线程会造成线程大范围的Z | T评级。
PS:以上代码简单解释下部分流程
1 首先需要计算模型用户之间的相似度,选择不同的相似度算法
我这里选择的是欧几里德距离(Euclidean Distance)
2 选择用户邻居的原则
这里有两种,固定阈值邻居和固定用户邻居
我用的是NearestNUserNeighborhood
3 创建基于用户的推荐
4 缓存推荐
5 开始基于用户的推荐recommender.recommend(userId, nums);
分布式协同过滤实现与设计(ML):
由于spark 已经实现了ALS的矩阵分解模型推荐算法做为离线计算维度,那么我们可以直接小试牛刀
进行之前我没有必要先了解下spark的工作流程图
1-5
总结一下:
客户端应用启动作为Driver把整个引用通过action 算子,划分为不同的job ,在根据内部DAG(有向无环图)scheduler 划分为多个stage and task ,task 作为任务执行的最小维度,然后由中间的 cluster Manager 调度不同work node 生成对应executor 的JVM (单独不能跨APPLICATION)去执行 完毕后通过不同的算子(例如collect 和shuffle等等)合并存储结果
处理原理图如下:
1-6
ALS模型推荐:
思路如下:
1 首先我们需要拿到用户对商品偏好的2 维3元矩阵数组
类似如下格式
Userid itemid ref
1 101 0.9090909
2 102 0.12121212
3 103 0.13223131
.
.
2 我们需要创建用户商品偏好的稀疏矩阵
3 根据最小交叉二乘法进行矩阵降维为用户特征矩阵和商品特征矩阵(特征例如一张电影票是恐怖,其恐怖可以做为它的特征)
4 通过ALS训练得出不同特征的用户或者商品的一般矩阵值(需要加入误差函数得到最小误差值)
5 两矩阵相乘即可得出用户对商品的偏好估值例如图1-7
1-7
小试牛刀spark:
现在我们调用spark的mllib的算法库API作为driver连接spark集群(standalone)
代码片段截取如下:
测试数据如下:
700W条数据
160M
1-8
如上图我们可以看到整个数据被分成了很多TASK由不同的进程执行(我现在是本地执行还没有提交,自然是多进程执行)
现在开始启动spark集群如下
现在还没有任何application runing
bin/spark-submit \
--class com.example.test.spark_als.JavaAlsExample2 \
--master spark://localhost:7077 --deploy-mode cluster \
--driver-memory 2048M \
--executor-cores 8 \
--total-executor-cores 8 \
--deploy-mode cluster \
--executor-memory 1G \
/home/jackliang/spark_als-0.0.1-SNAPSHOT-jar-with-dependencies.jar
我们提交但是看到UI界面好像没有反映(纯属意外操作)
我们看下日志
很明显我的9000端口的hadoop没有启动
Hadoop 已启动(可能是底层需要用到HDFS)
PS:hadoop有个坑,如果没有对应配置的化每次start-all.sh启动hadoop的时候都不会启动namenode结点,需要注意(
解决方案:
1 每次启动 hadoop namenode -format 一下
2 配置对应配置(网上有,这不是重点
)
我们在最后提交JOB的时候检查一下我们的测试文件
日常报错 _ _ .
调整参数,重启我们会看到如下
虽然有一点点的延迟处理但是时间差不多,9点2分24提交到2分25开始处理至5分零7秒结束。
虽然只有一个WORK 但是八个核16线程同时处理从数据上还是比较快的,大概2分多钟的处理时间。
到这里就结束了。
测试数据在贴一下
700W line
160M 的二维三元矩阵数据
PS:以上是大数据分布式机器学习需要用到的点,但这些远远不够,大数据处理应该是一个生态而不是仅仅只是某个处理框架,我们应该有所认识,结合本身系统业务(首页推荐用户历史,商品详情推荐热门商品)个人认为是一个比较大的尝试和创新。