接上篇。
应用文本回归尝试预测最畅销书排行
在实例“根据身高预测体重”中,我们介绍了线性回归的概念。然而,有时候需要将回归分析应用到像文本这类的非数字数据中去。
在本例中,我们将通过尝试预测最畅销的 100 本 O’Reilly 公司出版的图书,说明如何应用文本回归。此外,我们还介绍在本例的特殊情况下应用文本回归无法解决问题。原因仅仅是这些数据中不含有可以被我们的测试数据利用的信号。即使如此,本例也并非一无是处,因为在实践中,数据可能会含有实际信号,该信号可以被这里要介绍的文本回归检测到。
本例使用到的数文件可以在这里下载(http://xyclade.github.io/MachineLearning/Example%20Data/TextRegression_Example_1.csv)。除了 Smile 库,本例也会使用 Scala-csv 库,因为 csv 中包含带逗号的字符串。我们从获取需要的数据开始:
现在我们得到了 O’Reilly 出版社最畅销100部图书的书名、排序和详细说明。然而,当涉及某种回归分析时,我们需要数字数据。这就是问什么我们要建立一个文档词汇矩阵 (DTM)。注意这个 DTM 与我们在垃圾邮件分类实例中建立的词汇文档矩阵 (TDM) 是类似的。区别在于,DTM 存储的是文档记录,包含文档中的词汇,相反,TDM 存储的是词汇记录,包含这些词汇所在的一系列文档。
我们自己用如下代码生成 DTM:
观察这段代码,注意到这里面有一个方法 def getNumericRepresentationForRecords(): (Array[Array[Double]], Array[Double])。这一方法返回一个元组,该元组以一个矩阵作为第一个参数,该矩阵中每一行代表一个文档,每一列代表来自 DTM 文档的完备词汇集中的词汇。注意第一个列表中的浮点数表示词汇出现的次数。
第二个参数是一个数组,包含第一个列表中所有记录的排序值。
现在我们可以按如下方式扩展主程序,这样就可以得到所有文档的数值表示:
val documentTermMatrix = new DTM()
testData.foreach(x => documentTermMatrix.addDocumentToRecords(x._1,x._2,x._3))
有了这个从文本到数值的转换,现在我们可以利用回归分析工具箱了。我们在“基于身高预测体重”的实例中应用了普通最小二乘法 (OLS),不过这次我们要应用“最小绝对收缩与选择算子”(Lasso) 回归。这是因为我们可以给这种回归方法提供某个 λ 值,它代表一个惩罚值。该惩罚值可以帮助 LASSO 算法选择相关的特征(单词)而丢弃其他一些特征(单词)。
LASSO 执行的这一特征选择功能非常有用,因为在本例中,文档说明包含了大量的单词。LASSO 会设法找出那些单词的一个合适的子集作为特征,而要是应用 OLS,则所有单词都会被使用,那么运行时间将会变得极其漫长。此外,OLS 算法实现会检测非满秩。这是维数灾难的一种情形。
无论如何,我们需要找出一个最佳的 λ 值,因此,我们应该用交叉验证法尝试几个 λ 值,操作过程如下:
多次运行这段代码会给出一个在 36 和 51 之间变化的 RMSE 值。这表示我们排序的预测值会偏离至少 36 位。鉴于我们要尝试预测最高的 100 位,结果表明这个模型的效果非常差。在本例中,λ 值变化对模型的影响并不明显。然而,在实践中应用这种算法时,要小心地选取 λ 值: λ 值选得越大,算法选取的特征数就越少。 所以,交叉验证法对分析不同 λ 值对算法的影响很重要。
引述 John Tukey 的一句话来总结这个实例:
“数据中未必隐含答案。某些数据和对答案的迫切渴求的结合,无法保证人们从一堆给定数据中提取出一个合理的答案。”
应用无监督学习合并特征(PCA)
主成分分析 (PCA) 的基本思路是减少一个问题的维数。这是一个很好的方法,它可以避免维灾难,也可以帮助合并数据,避开无关数据的干扰,使其中的趋势更明显。
在本例中,我们打算应用 PCA 把 2002-2012 年这段时间内 24 只股票的股价合并为一只股票的股价。这个随时间变化的值就代表一个基于这 24 只股票数据的股票市场指数。把这24种股票价格合并为一种,明显地减少了处理过程中的数据量,并减少了数据维数,对于之后应用其他机器学习算法作预测,如回归分析来说,有很大的好处。为了看出特征数从 24 减少为 1 之后的效果,我们会将结果与同一时期的道琼斯指数 (DJI) 作比较。
随着工程的开始,下一步要做的是加载数据。为此,我们提供了两个文件:Data file 1 和 Data file 2.
有了训练数据,并且我们已经知道要将24个特征合并为一个单独的特征,现在我们可以进行主成分分析,并按如下方式为数据点检索数据。
这段代码不仅执行了 PCA,还将结果绘成图像,y 轴表示特征值,x 轴表示每日。
为了能看出 PCA 合并的效果,我们现在通过如下方式调整代码将道琼斯指数加入到图像中:
首先把下列代码添加到 def top 方法中:
//Verification against DJI
//用道琼斯指数验证
val verificationDataPath = basePath + "PCA_Example_2.csv"
val verificationData = getDJIFromFile(new File(verificationDataPath))
val DJIIndex = getDJIFromFile(new File(verificationDataPath))
canvas.line("Dow Jones Index", DJIIndex._2, Line.Style.DOT_DASH, Color.BLUE)
然后我们需要引入下列两个方法:
这段代码加载了 DJI 数据,并把它绘成图线添加到我们自己的股票指数图中。然而,当我们执行这段代码时,效果图有点无用.
如你所见,DJI 的取值范围与我们的计算特征的取值范围偏离很远。因此,现在我们要将数据标准化。办法就是根据数据的取值范围将数据进行缩放,这样,两个数据集就会落在同样的比例中。
用下列代码替换 getDJIFromFile 方法:
用下列代码替换 def top 方法中 plotData 的定义:
val maxDataValue = points.maxBy(x => x(0))
val minDataValue = points.minBy(x => x(0))
val rangeValue = maxDataValue(0) - minDataValue(0)
val plotData = points
.zipWithIndex
.map(x => Array(x._2.toDouble, -x._1(0) / rangeValue))
现在我们看到,虽然 DJI 的取值范围落在 0.8 与 1.8 之间,而我们的新特征的取值范围落在 -0.5 与 0.5 之间,但两条曲线的趋势符合得很好。学完这个实例,加上段落中对 PCA 的说明,现在你应该学会了 PCA 并能把它应用到你自己的数据中。
应用支持向量机(SVM)
在我们实际开始应用支持向量机 (SVM) 之前,我会稍微介绍一下 SVM。基本的 SVM 是一个二元分类器,它通过挑选出一个代表数据点之间最大距离的超平面,将数据集分为两部分。一个 SVM 就带有一个所谓的“校正率”值。如果不存在理想分割,则该校正率提供了一个误差范围,允许人们在该范围内找出一个仍尽可能合理分割的超平面。因此,即使仍存在一些令人不快的点,在校正率规定的误差范围内,超平面也是合适的。这意味着,我们无法为每种情形提出一个“标准的”校正率。不过,如果数据中没有重叠部分,则较低的校正率要优于较高的校正率。
我刚刚说明了作为一个二元分类器的基本 SVM,但是这些原理也适用于具有更多类别的情形。然而,现在我们要继续完成具有 2 种类别的实例,因为仅说明这种情况已经足够了。
在本例中,我们将完成几个小案例,其中,支持向量机 (SVM) 的表现都胜过其他分离算法如 KNN。这种方法与前几例中的不同,但它能帮你更容易学会怎么使用以及何时使用 SVM。
对于每个小案例,我们会提供代码、图像、不同参数时的 SVM 运行测试以及对测试结果的分析。这应该使你对输入 SVM 算法的参数有所了解。
在第一个小案例中,我们将应用高斯核函数,不过在 Smile 库中还有其他核函数。其他核函数可以在这里找到。紧接着高斯核函数,我们将讲述多项式核函数,因为这个核函数与前者有很大的不同。
我们会在每个小案例中用到下列的基本代码,其中只有构造函数 filePaths 和 svm 随每个小案例而改变。
案例1(高斯核函数)
在本案例中,我们介绍了最常用的 SVM 核函数,即高斯核函数。我们的想法是帮助读者寻找该核函数的最佳输入参数。本例中用到的数据可以在这里下载。
从该图中可以清楚看出,线性回归线在这里起不了作用。我们要使用一个 SVM 来作预测。在给出的第一段代码中,高斯核函数的 sigma 值为 0.01,边距惩罚系数为 1.0,类别总数为 2,并将其传递给了 SVM。那么,这些都代表什么意思呢?
我们从高斯核函数说起。这个核函数反映了 SVM 如何计算系统中成对数据的相似度。对于高斯核函数,用到了欧氏距离中的方差。我们特意挑选高斯核函数的原因是,数据中并不含有明显的结构如线性函数、多项式函数或者双曲线函数。相反地,数据聚集成了3组。
我们传递到高斯核中构造函数的参数是 sigma。这个 sigma 值反映了核函数的平滑程度。我们会演示改变这一取值如何影响预测效果。我们将边距惩罚系数取 1。这一参数定义了系统中向量的边距,因此,这一值越小,约束向量就越多。我们会执行一组运行测试,通过结果向读者说明这个参数在实践中的作用。注意其中 s: 代表 sigma,c: 代表校正惩罚系数。百分数表示预测效果的误差率, 它只不过是训练之后,对相同数据集的错误预测的百分数。
不幸的是,并不存在为每个数据集寻找正确 sigma 的黄金法则。不过,可能最好的方法就是计算数据的 sigma 值,即 √(variance),然后在这个值附近取值看看哪一个 sigma 值效果最好。因为本例数据的方差在 0.2 与 0.5 之间,我们把这区间作为中心并在中心的两边都选取一些值,以比较我们的案例中使用高斯核的 SVM 的表现。
看看表格中的结果和错误预测的百分比,它表明产生最佳效果的参数组合是一个非常低的 sigma (0.001) 和一个 1.0 及以上的校正率。不过,如果把这个模型应用到实际中的新数据上,可能会产生过拟合。因此,在用模型本身的训练数据测试模型时,你应该保持谨慎。一个更好的方法是使用交叉验证,或用新数据验证。
案例2(多项式核函数)
高斯核并不总是最佳选择,尽管在应用 SVM 时,它是最常用的核函数。因此,在本例中,我们将演示一个多项式核函数胜过高斯核函数的案例。注意,虽然本案例中的示例数据是构建好的,但在本领域内相似的数据(带有一点噪声)是可以找到的。本案例中的训练数据可以在这里下载,测试数据在这里下载。
对于本例数据,我们用一个三次多项式创建了两个类别,并生成了一个测试数据文件和一个训练数据文件。训练数据包含x轴上的前500个点,而测试数据则包含x轴上500到1000这些点。为了分析多项式核函数的工作原理,我们将数据汇成图。左图是训练数据的,右图是测试数据的。
考虑到本实例开头给出的基本代码,我们作如下的替换:
val trainingPath = "/users/.Example Data/SVM_Example_2.csv"
val testingPath = "/users/.Example Data/SVM_Example_2_Test_data.csv"
然后,如果我们使用高斯核并且运行代码,就可以得到如下结果:
从中我们可以看到,次数为 3 和 5 的情况得到了100%的准确率,这两种情况中测试数据与训练数据之间没有一个点是重叠的。与高斯核的最佳情况 27.4% 的错误率相比,这种表现令人惊喜。确实要注意本例这些数据是构建好的,因此没有什么噪声数据。所以才能出现所有的“校正率”都为 0% 错误率。如果添加了噪声,则需要对校正率进行微调。
以上就是对支持向量机这一部分的总结。
结论
在了解了机器学习的整体思想之后,你应该可以辨别出哪些情况分别属于分类问题、回归问题或是维数约化问题。此外,你应该理解机器学习的基本概念,什么是模型,并且知道机器学习中的一些常见陷阱。
在学完本文中的实例之后,你应该学会应用 K-NN、朴素贝叶斯算法以及线性回归分析了。此外,你也能够应用文本回归、使用 PCA 合并特征以及应用支持向量机。还有非常重要的一点,就是能够建立你自己的推荐系统。
查看更多关于面向开发人员的机器学习指南 (下)的详细内容...