自6月底申请项目到现在9月初撰写项目结题报告,眨眼一瞬间两个半月很快成为过去,在这两个半月的时间里,在不断的看文章和调试代码,首先我得感谢和我一起合作的赖百胜同学与我并肩作战,是他给了我一次次战胜bug的勇气,是他让我感觉到整个项目做下来而不觉得孤单。当然还要感谢intel中国研究院的尹老师,是他指导我们一步步由浅入深地学习spark程序设计,给我们指明研究的方向。最后,必须要感谢优快云能提供此次实习机会,让我们在暑假还能收获这么一段珍贵的实习经历。经历过这次开源实习后,我们团队成员都深深的感受到开源项目的重要性和巨大的意义。
下面我们通过三个部分来介绍整个项目执行过程中的情况并进行总结。
一、项目实施情况
在这两个半月的时间里面,我们牢牢按照项目初期安排实施项目的工作。
1、项目背景
Scala语言是近10年来逐渐发展起来的一种函数式的面向对象语言,语法类似于java,其良好的并发特性受到很多开发者的喜爱。另外目前国外的大型网站如Twitter,Amazon等也宣布其后端程序都用的是Scala语言。可以说Scala语言有巨大的潜力等待着人们发掘。
Spark是UCBerkeley开发的基于mapreduce算法实现的通用并行计算框架,其主要优点在于job的中间输出结果可以保存在内存中,从而不一定需要读写HDFS,因而spark能更好地适用于数据挖掘与及机器学习等算法。
Spark streaming是构建在spark上的一个处理流数据的框架,接收到的数据流分成小的时间片,以类似批量处理的方式来处理这部分的数据,非常适合与一些需要历史数据和实时数据联合分析的场合使用。
MLlib是spark中常用的机器学习算法的实现库,支持四种常见的机器学习问题:二元分类、回归、聚类以及协同过滤。但是spark自带的机器学习算法都是基于已有的数据进行模型训练然后对训练的模型进行预测和评估。如果需要对新的训练数据进行训练, MLlib中的模型需要重新训练,必将消耗很多的较多时间和CPU计算资源。
随着机器学习领域的发展,近些年来一些在线的机器学习算法也不断涌现,这些在线的机器学习算法在能以较低的计算复杂度对模型中新进入的数据进行学习和改进模型,使得新得到的模型具有更高的预测准确性,在目前基于地理位置的带有推荐功能的软件(如大众点评、淘点点等)中有巨大的应用。我们的项目也即基于这个背景出发,希望能改进MLlib库中的现有的机器学习算法,使其能成为在线的机器学习算法,为开源社区做出一点贡献。
2、熟悉scala和spark开发环境和编程语言
俗话说的好“工欲善其事,必先利其器”。我们在接到项目后花了大约一周左右的时间搭建开发环境,并购置了由瑞士洛桑理工(EPFL)的Martin Odersky撰写的scala语言的经典教科书《Programmingin Scala》。
spark官方网站建议在linux平台上运行spark程序,我们借助scala和spark官方网站及其他一些网站的相关操作介绍在实验室的linux服务器上搭建好可以运行spark程序的环境。详见博文《基于linux的spark与scala开发环境搭建》和《基于spark运行scala程序》。在试着运行spark自带的计算π和单词统计的例程时候发现spark的独特的基于内存的分布式计算的特性,这个特性可以使程序的运行速度成倍的提高。为了更好地理解spark底层工作原理,我们还学习了项目导师(尹老师)在小象学院开设的公开课《Spark上机器学习方法的模式与性能》,受益匪浅。
另外,由于linux上一般是用命令行或者脚本进行操作,执行程序很方便并且效率高,但是写程序显得并不是那么方便,我们在coursa上的《Functional ProgrammingPrinciples in Scala》课程中学习到了在windows环境下用scala-IDE这个平台编辑代码非常的好用,这个软件是基于eclipse开发的scala语言的编程环境。我们只需要在scala-IDE中将scala代码可以打包成jar文件,然后上传到linux服务器上,然后就可以欢快地跑起来了,这样编辑程序和运行程序两不误。
3、广义线性线性模型的流式机器学习
我们实现的基于sparkStreaming的广义线性模型包含最小线性二乘、逻辑斯特回归、岭回归和Lasso。
假设每个数据为a_i(i=1,2,…,n),所有数据按行排列成数据矩阵为A,所有数据的观测为向量y,模型参数为x,最小线性二乘的模型如下:
逻辑斯特回归的模型如下:
岭回归的模型如下:
λ是正则参数。
上述三种模型中的目标优化函数都可以对x求梯度,因此可以直接用梯度下降法对它们进行求解。我们只需要求上述三种模型的梯度即可。其中最小线性二乘关于x的梯度为:
逻辑斯特回归模型关于x的梯度为:
岭回归模型关于x的梯度为:
而对于Lasso,它的模型为
注意到正则项不能对x求梯度,所以不能直接用梯度下降法,为了解决这个问题,我们参照spark中的方法,使用HuberFunction对正则项 进行逼近。HuberFunction的定义为:
使用HuberFunction替换L1项后,我们可以对目标优化函数求关于x的梯度,它是:
其中:
有了四种模型的目标优化函数关于求解变量x的梯度后,梯度下降更新模型参数x的方法是:
其中,η是更新的步长。
上述四种广义线性模型还不是流式的学习算法,为了用流式的数据对模型进行训练,我们将上述提到的梯度下降法改为随机梯度下降法,即每次计算梯度只用当前数据进行计算。为了减小调用函数的频率,实际的实现中,我们使用一组(batch)数据进行梯度的计算,这一点和spark中的随机梯度下降法也是一致的。
我们利用sparkstreaming,从网络端口或文件夹中读取DStream的流式数据,先对数据进行解析,然后输入模型进行流式训练。
我们使用上述模型在数据集breast-cancer-wisconsin(详见我们的Code库)上进行测试,其中共有700个数据,将其中560个数据用来流式地训练,即每次只接收一个batch的数据,每次一个batch的数据进行训练后,我们都对测试集上140个数据进行测试,计算其准确率,可以看到测试误差会呈现下降的趋势。以下是逻辑斯特回归的训练结果:
每个算法我们有两种实现,第一种实现中,每个算法单独使用,并且训练过程是我们编写的;第二种实现,我们借助了spark中的非基于Streaming的广义线性模型的实现,新写了StreamingGeneralizedLinearAlgorithm类,这个类调指定了模型和算法后,会用spark自带的各种线性模型和随机梯度下降算法,因此细节是被封装了的。
4、基于ALS算法的简易在线推荐系统
在前期实现了广义线性模型的流式机器学习算法后,我们转向了对推荐系统的研究,因为网络上充满着各种各样的信息,而且数量相当庞大,并且还在以相当惊人的速度增长,如何在海量的信息中快速的找到我们所需要的信息并快速的反馈给用户,是互联网推荐系统需要考虑的一个十分重要的问题。
推荐系统中最核心和关键的部分就是使用的推荐算法,其在很大程度上决定了推荐系统性能的优劣。推荐算法大致可以分为以下几类:①基于内容的推荐算法(content-basedrecommendation)②协同过滤推荐算法(collaborativefiltering recommendation)③基于知识的推荐算法(knowledge-basedrecommendation)。
最初我们想基于协同过滤推荐算法在目前广泛应用的movielens数据集上计算和推荐用户电影根据推荐结果和已有的样本数据进行比较在模型不断学习过程中推荐的准确性。后面我们发现在spark的MLlib库中自带有基于ALS(迭代最小二乘法)算法的推荐程序,虽然ALS算法由于其计算复杂度较大,在实际应用中并不是很多,但是在movielens这个数据集上却显示出其优越性。
因此我们改写了spark中的ALS模型训练的程序,使其可以对新的数据进行学习,详见博文《基于ALS算法的简易在线推荐系统》。但是在博文中我们也提到了,由于目前的spark框架对于矩阵的运算操作支持的并不是特别好,因此将ALS算法改进为可以流式学习的算法有较大的难度,我们仅仅加入了将TCP流获得的数据合并到以前的数据集中,然后再进行模型训练,但是这样做的话由于元素过多和算法复杂度过高的关系,在小内存的机器上难以完美地运行,有待改进为算法复杂度更低的ALS模型更新算法。
在movielens数据集上,我们测试的效果如下,由于初始接收的数据不够多,无法对测试集中的所有的数据进行预测,因此RMSE偏小,随着到达的数据样本不断增多,学习后的模型在测试集上的的误差也在不断的减小。
5、在线的PCA算法
PCA是大规模机器学习中常用的工具,可以大量降低数据的维度,减少运算量。我们找到了在线PCA的具体算法,但是由于Spark中分布式的矩阵操作,特别是把多个矩阵拼接在一起,没有现成的函数,而实现这样的功能有比较复杂,我们在最后不得已放弃了在线PCA算法的实现。但是我们在Matlab上进行了如下的仿真。
在线PCA算法通过流式的数据训练一个映射矩阵,将高维的数据降到低维。可以想象,用来训练的数据越多,映射矩阵也就越高,即主成分的选取越准确。我们在MIT的人脸识别数据集CBCL上,对数据分成三个部分,PCA训练数据,SVM训练数据,SVM测试数据。其中,将PCA训练数据等分成20份,用不同数量的PCA训练数据进行PCA训练,然后对这些不同数据量训练出来的映射矩阵对同样的SVM训练数据进行降维,使用降维后的数据进行SVM训练,然后在SVM测试数据上进行测试,计算错误率。得到以下结果:
这个仿真说明,随着PCA训练数据的增加,其得到的主成分也越好。
二、项目的一些遗憾
前面第一部分主要阐述了我们在实施项目过程中所获得的一些小的阶段性成果,但是回首过去,在执行项目过程中总有一些遗憾的事情没能很好的完成,这里也做一个简要的回顾。
1、Scala编程规范
尽管我们尽力按照scala和spark的编程规范去写代码,但是第一次写这种代码,心里总是心有余悸,得先把算法实现了再去改代码规范,因此写出来的代码并不是那么的清楚易懂,另外,scala语言那种良好的封装特性,也让我们理解算法遇到了一定的障碍。
2、基于ALS模型的在线算法改进
其实我们挺想将ALS模型的在线学习改进为算法复杂度更低的算法,但是真的受制于spark的发展,让我们难以像在matlab一样对矩阵进行灵活的操作,因此改进ALS模型遇到了极大的阻力,希望后续更新spark的程序员能加强spark对矩阵操作的支持,这样改进其他的相关算法就容易多了。
3、在线PCA算法未能实现
同样是受限于spark对矩阵操作的支持,我们没能实现在线的PCA算法。还有一个原因是我们对spark中运用到的breeze库不是很熟悉,这个库可以用来做local矩阵和向量的操作。
三、项目结语
在这个临近中秋的夜晚写这份结题报告心中真是悲喜交加,喜的是两个月多月的实习期终于结束了,悲的是在执行的项目中总会留下那么一些遗憾感觉难以实现,并且感觉还有很多等待我们去实现的地方没有完成。
但是无论如何在我们的项目过程中,我们充分认识到了开源工作真的是非常的有意义,但是是一种长期漫长的工作,需要很多感兴趣写代码的人的不断的努力才能完成一个比较伟大的project。最后再次感谢优快云提供的这次开源项目实习机会,让我们受益匪浅。
附:项目代码见下面链接
https://code.youkuaiyun.com/zhangyuming010/zju_spark_2014