基于MLlib的机器学习

《Spark快速大数据分析》
11.1 概述

MLlib的设计理念非常简单:把数据以RDD的形式表示,
然后在分布式数据集上调用各种算法。MLlib引入了一些数据类型,
比如点和向量,不过归根结底,MLlib就是RDD上一系列可供调用的函数的集合。
比如,如果要用MLlib来完成文本分类的任务,例如识别垃圾邮件,
你只需要按如下步骤操作:
(1) 首先用字符串RDD来表示你的文本数据。
(2) 运行MLlib中的特征提取(feature extraction)算法来把文本数据
    转换为数值特征(适合机器学习算法处理);该操作会产生一个向量RDD。
(3) 对向量RDD调用分类算法,比如逻辑回归;这步会产生一个模型对象,
    可以使用该对象来对新的待分类的数据点进行分类。
(4) 使用MLlib的评估函数在测试数据集上评估模型。
 
MLlib中只包含能够在集群上运行良好的并行算法。
有些经典的机器学习算法并没有包含在其中,就是因为它们不能并行执行。
一些心的研究得出的算法因为适用于集群,也被包含在MLlib中,例如
分布式随机森林算法(distributed random forests),K-means||聚类,
交替最小二乘法(alternating least squares)等。
如果要在小规模数据集上训练各机器学习模型,最好还是在各个节点上
使用单节点的机器学习方法库实现,例如Weka,SciKit-Learn。
例如可以用Spark的map()操作在各个节点上并行使用。
类似的,我们在机器学习流水线中也常常用同一算法的不同参数对小规模数据集分别训练,
来选出最好的一组参数。在Spark中,你可以通过把参数列表传给parelleize()来在不同的
节点上分别运行不同的参数,而在每个节点上则使用单节点的机器学习库来实现。
只有当你需要在一个大规模分布式数据集上训练模型时,MLlib的优势才能突显出来。
 
 
11.3 机器学习基础
机器学习算法尝试根据训练数据(training data),使得表示算法行为的数学目标最大化,
并以此来进行预测或做出决定。机器学习问题分为几种,包括分类,回归,聚类,每种
都有不一样的目标。以分类(classification)为例来说明,分类是基于已经被标记的
其他数据点(比如一些已经分别被标记为垃圾邮件和非垃圾邮件的邮件)作为例子来识别
哪些尚未被标记的数据属于已知类型的哪一种。
 
所有的学习算法都需要定义每个数据点的特征集(feature),也就是传给学习函数的值。
举个栗子,对于一封邮件来说,一些特征可能包括来源机器,提到free这个单词的次数,
字体颜色等。在很多情况下,正确定义特征才是机器学习中最有挑战性的部分。
例如,在产品推荐的任务中,仅仅加上一个额外的特征(例如我们意识到推荐给用户的书籍
可能取决于用户看过的电影),就有可能极大的改进推荐效果。
 
大多数算法都只是为数值特征(具体来说,就是一个代表各个特征值的数字向量)定义的,
因此提取特征并转化为特征向量是机器学习过程中很重要的一步。
例如在文本分类中(比如垃圾邮件和非垃圾邮件的例子),有好几个提取文本特征的方法,
比如对各个单词出现的频率进行计数。
当数据已经成为特征向量的形式后,大多数机器学习算法都会
根据这些向量优化一个定义好的数学函数。例如,某个分类算法可能会在
特征向量的空间中定义出一个平面,使得这个平面能"最好"的分隔垃圾邮件和非垃圾邮件。
这里需要为"最好"给出定义,比如大多数据点都能被正确分类。
算法会在运行结束时返回一个代表学习决定的模型(比如这个选定的平面),而这个模型就
可以用来对新的数据点进行预测。
 
最后,大多数机器学习算法都有多个会影响结果的参数,所以现实中的机器学习流水线
会训练出多个不同版本的模型,然后分别对其进行评估。
所以通常需要把数据分为"训练数据集"和"测试数据集",并且之使用前者进行训练,
这样就可以使用后者来检验模型是否会过度拟合(overfit)了训练数据。
 
def testLogisticRegressionWithSGD = {
    val spam = sc.textFile("src/main/resources/mllib/spam.txt", 1)
    val normal = sc.textFile("src/main/resources/mllib/normal.txt", 1)
    
    //创建一个HashingTF实例来把邮件文本映射为包含一个10000个特征的向量
    val tf = new HashingTF(numFeatures = 10000)
    //各邮件都被切分为单词,每个单词被映射为一个特征
    val spamFeatures = spam.map { email => tf.transform(email.split(" ")) }
    val normalFeatures = normal.map { email => tf.transform(email.split(" ")) }
    
    //创建LabeledPoint数据集分别存放阳性(垃圾邮件)和阴性(正常邮件)的例子
    val positiveExamples = spamFeatures.map { features => LabeledPoint(1, features) }
    val negativeExamples = normalFeatures.map { features => LabeledPoint(0, features) }
    val trainingData = positiveExamples.union(negativeExamples)
    trainingData.cache()
    println(trainingData.toDebugString)
    
    //使用SGD算法运行逻辑回归
    val model = new LogisticRegressionWithSGD().run(trainingData)
    //以阳性(垃圾邮件)和阴性(正常邮件)的例子分别进行测试
    val posTest = tf.transform("O M G get cheap stuff by sending money to .".split(" "))
    val negTest = tf.transform("hello, i started studying Spark ".split(" "))
    println(s"prediction for positive tset example: ${model.predict(posTest)}")
    println(s"prediction for negitive tset example: ${model.predict(negTest)}")
    
    Thread.sleep(Int.MaxValue)
  }

  

 
11.4 数据类型
MLlib包含一些特有的数据类型,它们位于org.apache.spark.mllib包。
    Vector
        一个数学向量。MLlib既支持稠密向量也支持稀疏向量,前者表示向量
        的每一位都存储下来,后者则只存储非零位以节约空间,
    LabeledPoint
        在诸如分类和回归这样的监督式学习(supervised learning)算法中,
        LabeledPoint用来表示带标签的数据点。它包含一个特征向量和
        一个标签(由一个浮点数表示)
    Rating
        用户对一个产品的评分,用于产品推荐。
    各种Model类
        每个Model都是训练算法的结果,一般有一个predict()方法可以用来对新的数据点或
        数据点组成的RDD应用该模型进行预测。
 
大多数算法直接操作由Vector,LabeledPoint或Rating对象组成的RDD。可以用任意方式来创建
出这些对象,不过一般来说需要通过外部数据进行转化操作来构建出RDD。
例如,通过读取一个文本文件或者运行一条SparkSQL命令。
 
 
操作向量
第一,向量有两种,稠密向量和稀疏向量。稠密向量把所有维度的值存放在一个浮点数
    数组中。例如,一个100维的向量存储100个双精度浮点数。
    相比之下,稀疏向量只是把各维度中的非零值存储下来。当最多只有10%的元素为非零元素时,
    我们通常倾向于使用稀疏向量。许多特征向量提取技术都会生成非常稀疏的向量。
第二,创建向量的方式在各种语言中有一些细微的差别。
 
 
11.5 算法
11.5.1 特征提取
mllib.feature包中包含一些用来进行常见特征转化的类。
这些类中有从文本(或者其他表示)创建特征向量的算法,
也有特征向量进行正规化和伸缩变换的方法。
 
TF-IDF
词频-逆文档频率是一种用来从文本文档中生成特征向量的简单方法。
它为文档中的每个词计算两个统计值:
一个是词频(TF),也就是每个词在文档中出现的次数,
另一个是逆文档频率(IDF),用来衡量一个词在整个文档语料库中出现的(逆)频繁程度。
这些值的积,也就是TF * IDF,展示了一个词与特定文档的相关程度(比如
这个词在某个文档中很常见,但是在整个语料库中却很少见)。
MLlib有两个算法可以用来计算TF-IDF:HashingTF和IDF,都在mllib.feature包内。
HashingTF从一个文档中计算给定大小的词频向量。为了将词与向量顺序对应起来,
它使用了哈希法。在类似英语这样的语言中,有几十万个单词,
因此将每个单词映射到向量中的一个独立的维度上需要付出很大代价。
而HashingTF使用每个单词对所需向量的长度s取模得出的哈希值,把所有单词
映射到一个0到s-1直接的数字上。由此我们可以保证生成一个s维的向量。
在实践中,即使有多个单词被映射到同一个哈希值上,算法依然适用。
MLlib开发者推荐将s设置在2^18到2^20之间。
HashingTF可以一次只运行与一个文档中,也可以运行与整个RDD中。
它要求每个"文档"都使用对象的可迭代序列来表示。
 
当构建好词频向量后,就可以使用IDF来计算逆文档频率,然后将它们
与词频相乘来计算TF-IDF。首先要对IDF对象调用fit()方法来获取一个IDFModel,
它代表语料库中的逆文档频率。接下来对模型调用transform()来把TF向量转为IDF向量。
 
1.缩放
大多数机器学习算法都要考虑特征向量中各元素的幅值,并且在特征缩放调整为
平等对待时表现得最好。例如所有的特征平均值为0,标准差为1。当构建好特征向量后,
可以使用MLlib中的StandardScaler类来进行的缩放,同时控制均值和标准差。
需要创建一个StandardScaler,对数据集调用fit()函数来获取一个StandardScalerModel(
也就是为每一列计算平均值和标准差),然后使用这个模型对象的transform()方法来缩放一个数据集。
 
2.正规化
在一些情况下,在准备输入数据时,把向量正规化为长度1也是有用的。
是用Normalizer类可以实现,只要使用Normalizer.transform(rdd)就可以了。
默认情况下,Normalizer使用L^2范式(也即是欧几里德距离),不过可以给Normalizer
传递一个参数p来使用L^2范式。
 
11.5.2 统计
 
11.5.3 分类与回归
分类与回归是监督式学习的两种主要形式。监督式学习指算法尝试使用有
标签的训练数据(也就是已知结果的数据点)根据对象的特征预测结果。
分类和回归的区别在于预测的变量的类型:
在分类中,预测出的变量是离散的;也就是一个在有限集中的值,叫做类别,
比如分类可能是将邮件分为垃圾邮件和非垃圾邮件,也有可能是文本所使用的语言。
在回归中,预测出的变量是连续的,例如根据年龄和体重预测一个人的身高。
 
分类和回归都会使用MLlib的LabeledPoint类。一个LabeledPoint对象其实就是由
一个label(label总是一个Double值,不过可以分为分类算法设为离散整数)和
一个features向量组成。
 
对于一个二元分类,MLlib预期的标签为0或1。在某些情况下可能会使用-1和1,
但是这回导致错误的结果。对于多元分类,MLlib预期标签范围是从0到C-1,
其中C表示类别数量。
MLlib包含多种分类与回归的算法,其中包含简单的线性算法以及决策树和森林算法。
 
1 线性回归
线性回归是回归中最常用的方法之一,是指用特征的线性组合来预测输出值。
MLlib也支持L^1和L^2的正则的回归,通常称为Lasso和ridge回归。
线性回归算法可以使用的类包括mllib.regression.LinearRegressionWithSGD,
LassoWithSGD以及RidgeRegressionWithSGD。这遵循了MLlib中常用的命名模式,
即对于涉及多个算法的问题,在类名中使用With来表明所使用的算法。
SGD代表的是随即梯度下降法。
 
有几个可以对算法进行调优的参数:
    numIterations
        要运行的迭代次数(默认值100)
    stepSize
        梯度下降的步长(默认值1.0)
    intercept
        是否给数据加上一个干扰特征或者偏差特征--也就是一个值始终为1的特征(默认值false)
    regParam
        Lasso和ridge的正规化参数(默认值1.0)
 
一旦训练完成,所有语言返回的LinearRegressionModel都会包含一个predict()函数,
可以用来对单个特征向量预测一个值。RidgeRegressionWithSGD和LassonWithSGD的
行为类似,并且会返回一个类似的模型类。
 
2 逻辑回归
逻辑回归是一种二元分类方法,用来寻找一个分隔阴性和阳性示例的线性分割平面。
在MLlib中,它接收一组标签为0或1的LabeledPoint,
返回可以预测新点的分类的LogisticRegressionModel对象。
 
逻辑回归算法的API和线性回归相似。两个算法:SGD,LBFGS。
这两个算法中得出的LogisticRegressionModel可以为每个点求出一个
在0到1之间的得分,之后会基于一个阈值返回0或1:默认情况下,对于0.5
它会返回1。可以通过setThreshold()改变阈值,也可以通过clearThreshold()
去除阈值,这样的话predict()就会返回原始得分。对于阴性阳性示例各半的均衡数据集,
推荐保留0.5作为阈值。对于不均衡的数据集,可以通过提升阈值来
减少假阳性数据的数据量(也就是提高精准率,但是也降低了召回率),
也可以通过降低阈值来减少假阴性数据的数量。
 
3 支持向量机
支持向量机(SVM)算法是另一种使用线性分割平面的二元分类算法,
同样只预期0或1的标签。通过SVMWithSGD类,可以访问这种算法,
它的参数与线性回归和逻辑回归的参数差不多。
返回的SVMModel与LogisticRegressionModel一样使用阈值的方式进行预测。
 
4 朴素贝叶斯
朴素贝叶斯(Naive Bayes)算法是一种多元分类算法,它使用基于
特征的线性函数计算将一个点分到分类中的得分。这种算法通常用于
使用TF-IDF特征的文本分类,以及其他一些应用。
MLlib实现了多项朴素贝叶斯算法,需要非负的频次(比如词频)作为输入特征。
在MLlib中,可以通过mllib.classification.NaiveBayes类来使用朴素贝叶斯算法。
它支持一个参数(lamda),用来进行平滑化。可以对一个由LabeledPoint组成的RDD
调用朴素贝叶斯算法,对于C个分类,标签值在0到C-1之间。
返回的NavieBayesModel可以使用predict()预测对某个点最合适的分类,也可以访问
训练好的模型的两个参数:各特征与各分类的可能性矩阵theta(对于C个分类和D个特征
的情况,矩阵大小为C*D),以及表示先验概率的C维向量pi。
 
 

posted on 2015-12-03 01:24  develooop  阅读(2081)  评论(0编辑  收藏  举报

导航

AmazingCounters.com