基于MLlib的机器学习

1 概述

MLlib是Spark中提供机器学习函数的库,是专门为在集群上并行运行的情况而设计的。MLlib的设计理念非常简单:把数据以RDD的形式表示,然后在分布式数据集上调用各种算法。需要注意的是,MLlib中只包含能够在集群上运行良好的并行算法,这一点很重要。有 些经典的机器学习算法没有包含在其中,就是因为它们不能并行执行,而类似分布式随机森林算法 (distributed random forests)、K-means|| 聚类、交替最小二乘算法(alternating least squares) 等,这些算法就可以做分布式训练。MLlib 需要你的机器预装一些线性代数的库。首先,你需要为操作系统安装 gfortran 运行库,如果你要在 Python 中使用 MLlib, 你需要安装 NumPy。

2 分类例子

机器学习算法尝试根据训练数据(training data)使得表示算法行为的数学目标最大化,并以此来进行预测或作出决定。机器学习问题分为几种,包括分类、回归、聚类,每种都有不一样的目标。接下来我们举一个简单的分类算法例子,基于MLlib来训练模型,这个程序使用了 MLlib 中的两个函数:HashingTF 与 LogisticRegressionWithSGD,前者从文本数据构建词频(term frequency)特征向量,后者 使用随机梯度下降法(Stochastic Gradient Descent,简称 SGD)实现逻辑回归。假设我们 从两个文件 spam.txt 与 normal.txt 开始,两个文件分别包含垃圾邮件和非垃圾邮件的例子,每行一个。接下来我们就根据词频把每个文件中的文本转化为特征向量,然后训练出一个 可以把两类消息分开的逻辑回归模型。基于python代码如下:

from pyspark.mllib.regression import LablePoint
from pyspark.mllib.feature import HashingTF
from pyspark.mllib.classification import LogisticRegressionWithSGD
spam = sc.textFile("spam.txt")
normal = sc.textFile("normal.txt")
#创建一个HashingTF实例来把邮件文本映射为包含10000个特征的向量
tf = HashingTF(numFeatures = 10000)
#各邮件都被切分为单词,每个单词隐射为一个特征
spamFeatures = spam.map(lambda email: tf.transform(email.split(" ")))
normalFeatures = normal.map(lambda email: tf.transform(email.split(" ")))
#创建LabelPoint数据集分别存放阳性(垃圾邮件)和阴性(正常邮件)的例子
positiveExamples = spamFeatures.map(lambda features: LabeledPoint(1, features))
negativeExamples = normalFeatures.map(lambda features: LabeledPoint(0, features))
trainingData = positiveExamples.union(negativeExamples)
trainingData.chache()
#使用SGD算法运行逻辑回归
model = LogisticRegressionWithSGD.train(trainingData)
#predict
posTest = tf.transform("O M G GET cheap stuff by sending money to ...".split(" "))
negTest = tf.transform("Hi Dad, I started studying Spark the other ...".split(" "))
print "Prediction for positive test example: %g" % model.predict(posTest)
print "Prediction for negative test example: %g" % model.predict(negTest)

3 数据类型

MLlib 包含一些特有的数据类型,它们位于 org.apache.spark.mllib 包(Java/Scala)或
pyspark.mllib(Python)内。主要类型有如下:

  • Vector : 一个数学向量。MLlib 既支持稠密向量也支持稀疏向量,前者表示向量的每一位都存储下来,后者则只存储非零位以节约空间。向量可以通 过 mllib.linalg.Vectors 类创建出来。
  • LabeledPoint:在诸如分类和回归这样的监督式学习(supervised learning)算法中,LabeledPoint 用来表示带标签的数据点。它包含一个特征向量与一个标签(由一个浮点数表示),位置在 mllib.regression 包中。
  • Rating:用户对一个产品的评分,在 mllib.recommendation 包中,用于产品推荐。
  • 各种Model类:每个 Model 都是训练算法的结果,一般有一个 predict() 方法可以用来对新的数据点或数据点组成的RDD应用该模型进行预测。

大多数算法直接操作由 Vector、LabeledPoint 或 Rating 对象组成的 RDD。你可以用任意方式创建出这些对象,不过一般来说你需要通过对外部数据进行转化操作来构建出 RDD——例如,通过读取一个文本文件或者运行一条 Spark SQL 命令。接下来,使用 map() 将你的数据对象转为 MLlib 的数据类型。

3.1 操作向量

向量是 MLlib 中最常用的数据类型,Vector 类有一些需要注意的地方。

  • 第一,向量有两种:稠密向量与稀疏向量。稠密向量把所有维度的值存放在一个浮点数数组中。例如,一个100维的向量会存储 100个双精度浮点数。相比之下,稀疏向量只把各维度中的非零值存储下来。当最多只有 10% 的元素为非零元素时,我们通常更倾向于使用稀疏向量。许多特征提取技术都会生成非常稀疏的向量,所以这种方式常常是一种很关键的优化手段。
  • 第二,创建向量的方式在各种语言中有一些细微的差别,在 Python 中,你在 MLlib 中任意地方传递的 NumPy 数组都表示一个稠密向量,你也可以使用 mllib.linalg.Vectors 类创建其他类型的向量。而在 Java 和 Scala 中,都需要使用 mllib.linalg.Vectors 类。如下是在python中创建向量:
from numpy import array
from pyspark.mllib.linalg import Vectors
# 创建稠密向量
denseVec1 = array([1.0, 2.0, 3.0])  # Numpy数组可以直接传给MLlib
denseVec2 = Vectors.dense([1.0,2.0,3.0]) # 或者使用Vectors类来创建

#创建稀疏向量<1.0, 0.0, 2.0, 0.0>,改方法只接收向量的维度(4)以及非零位的位置和对应的值
#这些数据可以用一个dictionary来传递,或使用两个分别代表位置和值的list
sparseVec1 = Vectors.sparse(4, {
    
    0:1.0, 2: 2.0})
sparseVec2 = Vectors.sparse(4, [0, 2], [1.0, 2.0])

4 算法

接下来介绍MLlib 中主要的算法,以及它们的输入和输出类型。

4.1 特征提取

mllib.feature 包中包含一些用来进行常见特征转化的类。这些类中有从文本(或其他表示)创建特征向量的算法,也有对特征向量进行正规化和伸缩变换的方法。

TF-IDF
词频—逆文档频率(简称 TF-IDF)是一种用来从文本文档(例如网页)中生成特征向量的 简单方法。它为文档中的每个词计算两个统计值:一个是词频(TF),也就是每个词在文档中出现的次数,另一个是逆文档频率(IDF),用来衡量一个词在整个文档语料库中出现 的(逆)频繁程度。这些值的积,也就是 TF × IDF,展示了一个词与特定文档的相关程 度(比如这个词在某文档中很常见,但在整个语料库中却很少见,则认为该词在该文档中重要度较高)。MLlib 有两个算法可以用来计算 TF-IDF: HashingTF 和 IDF,都在 mllib.feature 包内。如下是基于python计算TF-IDF:

from pyspark.mllib.feature import HashingTF, IDF
#将若干文本文件读取为TF向量
rdd = sc.wholeTextFiles("data_path").map(lambda (name, text): text.split())
tf = HashingTF()
tfVectors = tf.transform(rdd).cache()

# 计算IDF, 然后计算TF-IDF向量
idf = IDF()
idfModel = idf.fit(tfVectors)
tfIdfVectors = idfModel.transform(tfVectors)

注意,我们对 RDDtfVectors 调用了 cache() 方法,因为它被使用了两次(一次是训练 IDF 模型时,一次是用 IDF 乘以 TF 向量时)。

缩放
大多数机器学习算法都要考虑特征向量中各元素的幅值,并且在特征缩放调整为平等对待时表现得最好(例如所有的特征平均值为 0,标准差为 1)。当构建好特征向量之后,你可以使用 MLlib 中的 StandardScaler 类来进行这样的缩放,同时控制均值和标准差。如下所示:

from pyspark.mllib.feature import StandardScaler
from pyspark.mllib.linalg import Vectors
vectors = [Vectors.dense([-2.0, 5.0, 1.0]), Vectors.dense([2.0, 0.0, 1.0])]
dataset = sc.parallelize(vectors)
scaler = StandardScaler(withMean=True, withStd=True)
model = scaler.fit(dataset)
result = model.transform(dataset)

运行结果为:{[-0.7071, 0.7071, 0.0], [0.7071, -0.7071, 0.0]}

正规划
在一些情况下,在准备输入数据时,把向量正规化为长度 1也是有用的。使用 Normalizer 类可以实现,只要使用 Normalizer.transform(rdd) 就可以了。默认情况下,Normalizer使用L2范式(也就是欧几里得距离),不过你可以给Normalizer传递一个参数p来使用 Lp范式。

Word2Vec
Word2Vec(https://code.google.com/p/word2vec/)是一个基于神经网络的文本特征化算法, 可以用来将数据传给许多下游算法。Spark 在 mllib.feature.Word2Vec 类中引入了该算法 的一个实现。当你训练好模型(通过 Word2Vec.fit(rdd)) 之后,你会得到一个 Word2VecModel,塔可以用来将每个单词通过 transform() 转为一个向 量。

4.2 统计

不论是在即时的探索中,还是在机器学习的数据理解中,基本的统计都是数据分析的重要部分。MLlib通过 mllib.stat.Statistics 类中的方法提供了几种广泛使用的统计函数,这些函数可以直接在 RDD上使用。一些常用的函数如下:

  • Statistics.colStats(rdd): 计算由向量组成的 RDD的统计性综述,保存着向量集合中每列的最小值、最大值、平均值和方差。这可以用来在一次执行中获取丰富的统计信息。
  • Statistics.corr(rdd, method): 计算由向量组成的 RDD 中的列间的相关矩阵,使用皮尔森相关(Pearson correlation) 或斯皮尔曼相关(Spearman correlation)中的一种(method 必须是 pearson 或 spearman 中的一个)。
  • Statistics.corr(rdd1, rdd2, method):计算两个由浮点值组成的 RDD 的相关矩阵,使用皮尔森相关或斯皮尔曼相关中的一种(method 必须是 pearson 或 spearman 中的一个)。
  • Statistics.chiSqTest(rdd):计算由 LabeledPoint 对象组成的 RDD 中每个特征与标签的皮尔森独立性测试
    (Pearson’s independence test) 结果。 返 回 一 个 ChiSqTestResult 对 象, 其中有p值 (p-value)、测试统计及每个特征的自由度。标签和特征值必须是分类的(即离散值)。
    除了这些算法以外,数值 RDD 还提供几个基本的统计函数,例如 mean()、stdev() 以及 sum()。除此以外,RDD 还支持 sample() 和 sampleByKey(),使用它们可以构建出简单而分层的数据样本。

4.3 分类与回归

分类与回归是监督式学习的两种主要形式。监督式学习指算法尝试使用有标签的训练数据 (也就是已知结果的数据点)根据对象的特征预测结果。分类和回归的区别在于预测的变量的类型:在分类中,预测出的变量是离散的(也就是一个在有限集中的值,叫作类别); 比如,分类可能是将邮件分为垃圾邮件和非垃圾邮件,也有可能是文本所使用的语言。在 回归中,预测出的变量是连续的(例如根据年龄和体重预测一个人的身高)。
分类和回归都会使用 MLlib中的== LabeledPoint类==,这个类在 mllib. regression 包中。一个 LabeledPoint 其实就是由一个label(label 总是一个 Double 值, 不过可以为分类算法设为离散整数)和一个 features 向量组成。

4.4 聚类

聚类算法是一种无监督学习任务,用于将对象分到具有高度相似性的聚类中。MLlib 包含聚类中流行的 K-means 算法,以及一个叫作 K-means|| 的变种,可以为并行环 境提供更好的初始化策略。K-means 中最重要的参数是生成的聚类中心的目标数量 K。事实上,你几乎不可能提前知 道聚类的“真实”数量,所以最佳实践是尝试几个不同的 K 值,直到聚类内部平均距离不 再显著下降为止。
在python中,调用 KMeans.train,它接收一个 Vector 组成的 RDD 作为参数。K-means 返回一个 KMeansModel 对象,该对象允许你访问其 clusterCenters 属性(聚类中心,是一个向量的数组)或者调用 predict() 来对一个新的 向量返回它所属的聚类。

4.5 协同过滤与推荐

协同过滤是一种根据用户对各种产品的交互与评分来推荐新产品的推荐系统技术。协同过滤吸引人的地方就在于它只需要输入一系列用户 / 产品的交互记录,仅仅根据这些交互,协同过滤算法就能够知道哪些产品之间 比较相似 (因为相同的用户与它们发生了交互)以及哪些用户之间比较相似,然后就可以作出新的推荐。
MLlib中包含交替最小二乘(简称 ALS)的一个实现,这是一个协同过滤的常用算法,可以很好地扩展到集群上。要使用 ALS 算法,你需要有一个由 mllib.recommendation.Rating 对象组成的 RDD,其 中每个包含一个用户 ID、一个产品 ID 和一个评分。我们推荐你直接在 ALS 中使用 ID 的哈希值;即 使有两个用户或者两个产品映射到同一个ID 上,总体结果依然会不错。还有一种办法是 broadcast() 一张从产品 ID 到整型值的表,来赋给每个产品独特的 ID。
ALS 返回一个== MatrixFactorizationModel 对象来表示结果==,可以调用 predict() 来对一个由 (userID, productID) 对组成的 RDD 进行预测评分。你也可以使用 model.recommendProducts (userId, numProducts) 来为一个给定用户找到最值得推荐的前 numProduct 个产品。注 意,和 MLlib 中的其他模型不同,MatrixFactorizationModel 对象很大,为每个用户和产品都存储了一个向量。这样我们就不能把它存到磁盘上,然后在另一个程序中读取回来。不过,你可以把模型中生成的特征向量 RDD,也就是 model.userFeatures 和 model. productFeatures 保存到分布式文件系统上。
最后,ALS 有两个变种:显式评分(默认情况)和隐式反馈(通过调用 ALS.trainImplicit()
而非 ALS.train() 来打开)
。用于显式评分时,每个用户对于一个产品的评分需要是一个得分 (例如 1 到 5 星),而预测出来的评分也是得分。而用于隐式反馈时,每个评分代表的是用户会和给定产品发生交互的置信度(比如随着用户访问一个网页次数的增加,评分也会提高), 预测出来的也是置信度。

4.6 降维

机器学习社区中使用的主要的降维技术是主成分分析PCA,在这种技术中,我们会把特征映射到低维空间,让数据在低维空间表示的方差最大化,从而忽略一些无用的维度。要计算出这种映射,我们要构建出正规化的相关矩阵,并使用这个矩阵的奇异向量和奇异值。与最大的一部分奇异值 相对应的奇异向量可以用来重建原始数据的主要成分。
MLlib 也提供了低层的奇异值分解(简称 SVD)原语。SVD 会把一个 m ×n 的矩阵 A 分 解成三个矩阵 A ≈ U Σ V T A ≈ UΣV^T AUΣVT, U 是一个正交矩阵,它的列被称为左奇异向量;Σ 是一个对角线上的值均为非负数并降序排列的对角矩阵,它的对角线上的值被称为奇异值;V 是一个正交矩阵,它的列被称为右奇异向量。

4.7 模型评估

无论机器学习任务使用的是何种算法,模型评估都是端到端机器学习流水线的一个重要环节。许多机器学习任务可以使用不同的模型来应对,而且即使使用的是同一个算法,参数设置也可以带来不同的结果。不仅如此,我们还要考虑模型对训练数据过度拟合的风险, 因此你最好通过在另一个数据集上测试模型来对模型进行评估,而不是使用训练数据集。你可以从由==(预测,事实)对组成的 RDD 上创建出一个 Metrics对象,然后计算诸如精确 率、召回率、接受者操作特性(ROC)曲线下的面积等指标==。

猜你喜欢

转载自blog.csdn.net/BGoodHabit/article/details/122629701