Spark MLlib
- 7.1 概述
- 7.2 系统要求
- 7.3 机器学习基础
- 7.4 数据类型
- 7.4.1 操作向量
- 7.5 算法
- 7.5.1 特征提取
- 7.5.2 统计
- 7.5.3 分类与回归
- 7.5.4 聚类
- 7.5.5 协同过滤与推荐
- 7.5.6 降维
- 7.5.7 模型评估
- 7.6 一些提示与性能考量
- 7.6.1 准备特征
- 7.6.2 配置算法
- 7.6.3 缓存RDD以重复使用
- 7.6.4 识别稀疏程度
- 7.6.5 并行度
- 7.8 总结
7.1 概述
- 设计理念:把数据以RDD的形式表示,然后再分布式数据集上调用各种算法;即RDD上一系列可供调用的函数的集合。
- MLlib完成文本分类的流程
- 首先使用字符串RDD来表示你的消息
- 运行MLlib中的一个特征提取算法来把文本数据转换为数值特征,该操作会返回一个向量RDD
- 对向量RDD调用分类算法;这步会返回一个模型对象,可以使用该对象对新的数据点进行分类
- 使用MLlib的评估函数在测试数据上评估模型
- MLlib只包含能够在集群上运行良好的并行算法,比如 分布式随机森林算法、K-means||聚类、交替最小二乘算法等
- 在Spark中,可以通过把参数列表传给parallelize()来在不同的节点上分别运行不同的参数,而在每个节点上则使用单节点的机器学习库来实现
7.2 系统要求
- 预装gfortran运行库。
7.3 机器学习基础
- 机器学习算法:尝试根据训练数据 使得表示算法行为的数学目标最大化,并以此来进行预测或作出决定。包括 分类、回归和聚类这三种学习问题。
- 机器学习大多数算法都只是专为数值特征定义的,因此 提取特征并转化为特征向量 是非常重要的。
- 机器学习流水线步骤
- 垃圾邮件分类
- 读取垃圾邮件 、非垃圾邮件 文件
- 根据词频把每个文件中的文本转化为特征向量
- 训练出一个可以把两类消息分开的逻辑回归模型
- 预测测试
// Scala版垃圾分类器
//创建Spark配置,设置应用程序名字
val conf = new SparkConf().setAppName("test11_2").setMaster("local[*]")
//创建Spark执行的入口
val sc=new SparkContext(conf)
//读取文件
val spam = sc.textFile(args(0))
println(spam)
val normal = sc.textFile(args(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() //因为逻辑回归是迭代算法,所以缓存训练数据RDD
// 使用梯度下降算法运行逻辑回归
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("Hi Dad, I started studying Spark the other....".split(" "))
println("positive test example: "+ model.predict(posTest))
println("negative test example: "+ model.predict(negTest))
7.4 数据类型
- MLlib包含特有的数据类型,位于mllib包内
- Vector:一个数学向量。
- 稠密向量:表示向量的每一位都存储下来
- 稀疏向量:值存储非零位以节约空间
- LabeledPoint:用来表示带标签的数据点,包含一个特征向量和一个标签(由一个浮点数表示)
- Rating:用户对一个产品的评分,用于产品推荐
- 各种model类:每个Model都是训练算法的结果,一般会有一个predict()方法可以用来对新的数据点或者数据点组成的RDD应用该模型进行预测。
7.4.1 操作向量
- 稠密向量:把所有维度的值都存放在一个浮点数数组中。
- 稀疏矩阵:把各维度中的非零值存储下来,优先使用。
// 创建稠密向量<1.0, 2.0, 3.0> ;Vectors.dense 接收一串值或一个数组
val denseVec1 = Vectors.dense(1.0,2.0,3.0)
val denseVec2 = Vectors.dense(Array(1.0,2.0,3.0))
//创建稀疏向量<1.0,0.0,2.0,0.0>;该方法只接收向量的维度(这里为4)以及非零位的位置和对应的值
val sparseVec1 = Vectors.sparse(4, Array(0,2),Array(1.0,2.0))
- MLlib的Vector类只是用来为数据表示服务的,而没有在用户API中提供加法或减法这样的向量的算术操作。
7.5 算法
7.5.1 特征提取
- TF-IDF
- 词频-逆文档频率:用来从文本文档中生成特征向量的简单方法。
- 为文档中的每个词计算两个统计值:
- 词频:每个词在文档中出现的次数
- 逆文档频率:用来衡量一个词在整个文档语料中出现的(逆)频繁程度
- 可以展示一个词与特定文档的相关程度。
- 两个算法计算TF-IDF
- 构建词频向量------HashingTF:从一个文档中计算出给定大小的词频向量,使用每个单词对所需向量的长度S取模得出的哈希值,把所有单词都映射到一个0到S-1之间的数字上,从而保证生成一个S维的向量(推荐将S设置在2^18~20 之间);它可以一次只运行于一个文档中,也可以运行于整个RDD中,要求每个“文档”都使用对象的可迭代系列来表示
- 计算逆文档频率-----IDF
- 首先对IDF对象调用fit()方法获取一个IDFModel,代表语料库中的逆文档频率
- 接着对模型调用transform()来把TF向量转为IDF向量
- 以上在真实流水线中,需要在把文档传给TF之前,对文档进行预处理并提炼单词(去除标点符号,所有单词统一小写等)
- 缩放
- 大部分机器学习算法都要考虑特征向量中各元素的幅值,并且在特征缩放调整为平等对待时表现最好,在构建完特征向量之后,对特征向量进行缩放
- StandardScaler:通过这个函数进行缩放,同时控制好均值和标准差。
- 正规化
- Normalizer:通过使用Normalizer.transform(rdd)把向量正规化为长度1.默认是,也可以传递一个参数p来使用
- Word2Vec
- 是一个基于神经网络的文本特征化算法,用来将数据传给许多下游算法。
- Word2Vec算法的模型大小等于 你在词库中的单词数 乘以 向量的大小
7.5.2 统计
- Statistics.colStats(rdd):计算由向量组成的RDD的统计性综述,保存着向量集合中每列的最小值,最大值,平均值和方差
- Statistics.corr(rdd,method):计算由向量组成的RDD中的列间的相关矩阵,使用皮尔森相关或者斯皮尔曼相关中的一种
- Statistics.corr(rdd1,rdd2,method):计算两个由浮点值组成的RDD的相关矩阵,使用皮尔森相关或斯皮尔曼相关中的一种
- Statistics.chiSqTest(rdd):计算由LabeledPoint对象组成的RDD中的每个特征与标签的皮尔森独立性测试结果,返回一个ChiSqTestResult对象,其中有p值、测试统计及每个特征的自由度。标签和特征值必须是分类。
7.5.3 分类与回归
- 监督式学习:尝试使用有标签的训练数据 根据对象的特征预测结果。
- 分类与回归的区别:在于预测的变量的类型,在分类中,预测的变量是离散值,而回归是连续值。
- 线性回归
- 指用特征的线性组合来预测输出值。MLlib也支持和的正则的回归,通常称为Lasso和ridge回归。
- 包括:mllib.regression.LinearRegressionWithSGD、LassoWithSGD以及RidgeRegressionWithSGD。
- numIterations:要运行的迭代次数(默认值:100)
- stepSize:梯度下降的步长(默认值:1.0)
- intercept:是否给数据加上一个干扰特征或者偏差特征—也就是一个值始终为1的特征(默认值:False)
- regParam:Lasso和ridge的正规化参数(默认值:1.0)
- 调用算法的方式:
- 创建一个LinearRegressionWithSGD对象
- 调用setter方法来设置参数
- 调用run()来训练模型
//Scala中的线性回归
val points: RDD[LabeledPoint]= //...
val lr = new LinearRegressionWithSGD().setNumIterations(200).setIntercept(true)
val model = lr.run(points)
println("weights: %s, intercept: %s".format(model.weights,model.intercept))
- 逻辑回归
- 逻辑回归是一种二元分类方法,用来寻找一个分隔阴性和阳性示例的线性分隔平面。
- 在MLlib中,它接收一组标签为0或1的LabeledPoint,返回可以预测新点的分类的LogisticRegressionModel对象
- 两种解决逻辑回归问题的算法:SGD和LBFGS
- 这两种算法得出的LogisticRegressionModel可以为每个店求出一个0到1之间的得分,再基于一个阈值(默认0.5)返回0或1
- 通过setThreshold()改变阈值、clearThreshold()去除阈值设置,得到原始分数
- 支持向量机
- 支持向量机(SVM)算法是另一种使用线性分隔平面的二元分类算法,同样只预期0或者1的标签
- 朴素贝叶斯
- 朴素贝叶斯(Naive Bayes)算法是一种多元分类算法,使用基于特征的线性函数计算将一个点分到各类中的得分。
- 这种算法常用使用TF-IDF特征的文本分类
- 通过使用mllib.classification.NaiveBsyes类来使用朴素贝叶斯算法,支持一个参数lambda来进行平滑化处理。
- 返回的NaiveBayesModel可以使用predict()预测对某点最合适的分类,也可以访问训练好的模型的两个参数:
- 各特征与各分类的可能性矩阵theta(对于C个分类和D个特征的矩阵大小为C X D)
- 表示先验概率的C维向量pi
- 决策树与随机森林
- 决策树既可以用于分类也可以用于回归。
- 决策树以节点树的形式表示,每个节点基于数据的特征做出一个二元决定、而每个叶节点则包含一种预测结果
- 决策树模型本身容易检查,而且决策树既支持分类的特征,也支持连续的特征
- 一个预测用户是否会购买一件产品的决策树示例
- 通过使用trainClassifier()和trainRegressor()来训练决策树,参数如下:
- data:由LabeledPoint组成的RDD
- numClasses(仅用于分类):要使用的类别数量
- impurity:节点的不纯净度测量,对于分类可以为gini或entropy,而回归则必须为variance
- maxDepth:树的最大深度
- maxBins:在构建各节点时将数据分到多少个箱子中
- categoricalFeaturesInfo:一个映射表,用来指定哪些特征时分类的,以及它们各有多个分类;如果没有特征时分类的,就传递一个空的映射表
- train()会返回一个DecisionTreeModel对象,可以使用其的predict()对一个新的特征向量预测对应的值,或预测一个向量RDD.也可以使用toDebugString()来输出这棵树。
- 随机森林
- 通过RandomForest.trainClassifier和trainRegressor使用
- numTrees:构建树的数量。提高numTrees可以降低对训练数据过度拟合的可能性
- featureSubsetStrategy:在每个节点上做决定时需要考虑的特征数量,可以是auto、all、sqrt、log2以及onethird
- seed:所使用的随机数种子
- 随机森林算法返回一个WeightedEnsembleModel对象,其中包含几个决策树,可以对RDD或vector调用predict(),也可以通过toDebugString方法打印出其中所有的树。
7.5.4 聚类
- 聚类算法是一种无监督学习任务,用于将对象分到具有高度相似度的聚类中。该算法主要用于数据探索以及异常检测,用于无标签的数据。
- KMeans
- MLlib包含聚类中流行的K-means算法以及一个叫做K-means||的变种,可以为并行环境提供更好的初始化策略。
- K-means||的初始化过程与K-means++在配置单节点时所进行的初始化过程非常相似。
- K-means参数
- K: 是 生成的聚类中心的目标数量.
- initializationMode:用于初始化聚类中心的方法,可以是 k-means|| 或 random;k-means||默认会带来更好的结果
- maxIteration:运行的最大迭代次数(默认值:100)
- runs:算法并发运行的数目。 K-means算法支持从多个起点并发执行,然后选择最佳结果。
- Keams算法接收一个Vector组成的RDD作为参数,返回一个KMeansModel对象,该对象允许你访问其clusterCenters属性或者调用predict()来对一个新的向量返回它所属的聚类。predict()总是返回和改点距离最近的聚类中心。
7.5.5 协同过滤与推荐
- 协同过滤是一种根据用户对各种产品的交互与评分来推荐新产品的推荐系统技术。
- 它只需要输入一系列 用户/产品 的交互记录(无论“显性”还是“隐性”),协同过滤算法就可以推断出哪些产品之间比较相似以及哪些用户之间比较相似,然后作出推荐。
- 交替最小二乘
- ALS是协同过滤最常用的算法,可以很好地扩展到集群上;ALS会为每个用户和产品都设一个特征向量,这样用户向量与产品向量的点积就接近于它们的得分。
- 有以下参数
- rank:使用的特征向量的大小,更大的特征向量会产生更好的模型(默认是 10)
- iterations:要执行的迭代次数(默认值:10)
- lambda:正则化参数(默认值:0.01)
- alpha:用来在隐式ALS中计算置信度的常量(默认值:1.0)
- numUserBlocks,numProductBlocks:切分用户和产品数据的块的数目,用来控制并行度,(默认值:-1,表示自动决定)
- 如何使用ALS算法:
- 首先需要一个由mllib.recommendation.Rating对象组成的RDD,其中每个包含一个用户ID,一个产品ID和一个评分。
- ALS返回一个MatrixFactorizationModel对象来表示结果,调用predict()来对一个由(userID,productID)对组成的RDD进行预测评分;也可以使用model.recommendProducts(userID,numProducts)来为一个给定用户找到最值得推荐的前numProduct个产品。
- 注意一点:MatrixFactorizationModel对象很大,为每个用户和产品都存储了一个向量,因此不能将其存到磁盘上,而是应该将模型中生成的特征向量RDD,也就是model.userFeatures和model.productFeatures保存到分布式文件系统中。
- ALS两个变种:显式评分(默认情况)和隐式反馈(ALS.trainImplicit()打开)。
- 显式评分:每个用户对于一个产品的评分需要是一个得分,而预测出来的评分也是得分。
- 隐式反馈:每个评分代表的是用户会和给定产品发生交互的置信度,预测出来的也是置信度。
7.5.6 降维
- 主成分分析
- 将特征映射到低维空间,让数据在低维空间表示的方差最大化,从而忽略一些无用的维度。
- 首先构建出正规化的相关矩阵,并使用这个矩阵的奇异向量和奇异值,与最大的一部分奇异值相对应的奇异向量可以用来重建原始数据的主要成分。
- 通过使用mllib.linalg.distributed.RowMatrix类来表示矩阵,然后存储一个由Vector组成的RDD,每行一个后就可以调用PCA算法
val points: RDD[Vector] = //...
val mat:RowMatrix = new RowMatrix(points)
val pc:Matrix = mat.computePrincipalComponents(2)
//将点投射到低维空间中
val projected = mat.multiply(pc).rows
//在投影出的二维数据上训练k-means模型
val model = KMeans.train(projected,10)
- 奇异值分解
- MLlib提供低层的奇异值分解(SVD),SVD会将一个m X n的矩阵A分解为三个矩阵 ,其中:
- U 是一个正交矩阵,它的列被称为左奇异向量
- ∑ 是一个对角线上的值均为非负数并降序排列的对角矩阵,它的对角线上的值被称为奇异值
- V 是一个正交矩阵,它的列被称为右奇异向量
- 对于大型矩阵,通常不需要进行完全分解,只需要分解出靠前的奇异值和与之对应的奇异向量即可。这样可以节省存储空间、降噪,并有利于恢复低秩矩阵。
// Scala中的SVD
//计算RowMatrix矩阵的前20个奇异值及其对应的奇异向量
val svd: SingularValueDecomposition[RowMatrix, Matrix] = mat.computeSVD(20,computeU=true)
val U: RowMatrix = svd.U //U是一个分布式RowMatrix
val s: Vector = svd.s //奇异值用一个局部稠密向量表示
val V: Matrix = svd.V //V是一个局部稠密矩阵
7.5.7 模型评估
- 通过测试数据对模型进行评估,通过使用BinaryClassificationMetrics和MulticlassMetrics等不同的类从由(预测,事实)对组成的RDD上创建出一个Metrics对象,然后计算诸如精确率、召回率、接受者操作特性曲线(ROC)下的面积等指标。
7.6 一些提示与性能考量
7.6.1 准备特征
- 特征选择是机器学习最重要的一步。
7.6.2 配置算法
- 正规化选项可用时,都需要打开。
- 尝试迭代次数的增减来调试达到最优
7.6.3 缓存RDD以重复使用
- MLlib 的大多数算法都是迭代的,对数据进行重复操作的。因此需要在数据集传给MLlib前 用cache()将数据集缓存起来。
7.6.4 识别稀疏程度
- 优先选择稀疏表示
7.6.5 并行度
- 输入RDD的分区数至少应该和集群的CPU核心数相当。
- 通过以下方法改变分区数
- 向SparkContext.textFile()这样的函数传递分区数的最小值来改变默认行为
- 对RDD调用repartition(numPartitions)来将RDD分区成numPartitions个分区。
7.8 总结