Practicalexamples
在本节中,我们为您介绍一组在实际环境中的机器学习算法。 这些例子的想法是让你开始使用机器学习算法,而不深入解释底层算法。我们只专注于这些算法的特征方面,如何验证您的实现,最后尝试让您意识到常见的陷阱。
下面是可以使用的样例:
• Labeling ISP's based on their down/upload speed (K-NN)
• Classifying email as spam or ham (Naive Bayes)
• Ranking emails based on their content (Recommendation system)
• Predicting weight based on height (Linear Regression OLS)
• An attempt at rank prediction for top selling books using textregression
• Using unsupervised learning to merge features (PCA)
• Using Support Vector Machines (SVMS)
对于每个这些例子,我们使用了Smile MachineLearning库。 我们同时使用了smile-core和smile-plot库。 这些库可用于Maven,Gradle,Ivy,SBT和Leiningen。
所以在开始工作之前,我假设你在你最喜欢的IDE中创建了一个新项目,并将smile-core和smile-plot库添加到你的项目中。 使用时的额外的库,以及如何获取示例数据按照示例进行处理。
Labeling ISPs based on their down/uploadspeed (K-NN using Smile in Scala)
本节的目标是使用K-Nearest Neighbors(K-NN)算法将下载/上传速度对分类为互联网服务提供商(ISP)Alpha(由0表示)或Beta(由1表示)。 K-NN背后的想法如下:给定一组被分类的点,您可以通过查看其K个邻居(K是正整数)来对新点进行分类。这个想法是,通过检查新点与其周围点之间的欧氏距离,找到K个邻居。 对于这些邻居,然后检查最大的代表类,并将该类分配给新点。
这儿是样本数据集
第一步是先下样本数据,下面是代码:
object KNNExample {
def main(args:Array[String]): Unit = {
val basePath ="/.../KNN_Example_1.csv"
val testData =getDataFromCSV(new File(basePath))
}
defgetDataFromCSV(file: File): (Array[Array[Double]], Array[Int]) = {
val source =scala.io.Source.fromFile(file)
val data =source
.getLines()
.drop(1)
.map(x=> getDataFromString(x))
.toArray
source.close()
val dataPoints= data.map(x => x._1)
valclassifierArray = data.map(x => x._2)
return(dataPoints, classifierArray)
}
defgetDataFromString(dataString: String): (Array[Double], Int) = {
//Split thecomma separated value string into an array of strings
val dataArray:Array[String] = dataString.split(',')
//Extract thevalues from the strings
valxCoordinate: Double = dataArray(0).toDouble
valyCoordinate: Double = dataArray(1).toDouble
val classifier:Int = dataArray(2).toInt
//And returnthe result in a format that can later
//easily beused to feed to Smile
return(Array(xCoordinate, yCoordinate), classifier)
}
}
首先你可能想知道为什么是数据格式化这种方式。嗯,dataPoints和它们的标签值之间的分隔是为了能轻易地分离测试和训练数据,并且因为API预想到数据是以这种方式执行K-NN算法和绘制数据地。 其次,作为Array [Array [Double]]存储的数据点被用来支持超过2维的数据点。
给定数据,接下来要做的第一件事是看看数据是什么样子地。对于此,Smile提供了一个很好的绘图库。但为了使用这个,应用程序应该更改为Swing。此外,数据应该被反馈到绘图库以获得具有实际绘图的JPane。 更改代码后,它应该如下所示:
object KNNExample extends SimpleSwingApplication {
def top = newMainFrame {
title ="KNN Example"
val basePath ="/.../KNN_Example_1.csv"
val testData =getDataFromCSV(new File(basePath))
val plot =ScatterPlot.plot(testData._1,
testData._2,
'@',
Array(Color.red, Color.blue)
)
peer.setContentPane(plot)
size = newDimension(400, 400)
}
...
绘制数据背后的想法是验证K-NN是否是针对该特定数据集的拟合机器学习算法。 在这种情况下,数据如下:
在此图中,您可以看到,蓝色和红色点似乎在区域(3 <x <5)和(5 <y <7.5)这个区域中混合了。由于这个组是混合的,所以K-NN算法是一个好的选择,因为拟合线性决策边界将在混合区域中导致大量假分类。
考虑到使用K-NN算法是适合这个问题的选择,让我们继续实际的机器学习部分。对于此,GUI是开源的,因为它没有真正添加任何值。回忆一下机器学习的全局概念,在机器学习中有两个关键部分:预测和验证。 首先我们来看看验证,因为使用没有任何验证的模型永远不是一个好主意。 这里验证模型的主要原因是防止过拟合。然而,即使我们可以在做验证之前,也应该选择正确的K.
这个算法地缺点是没有用于找到正确的K值的黄金规则。然而,找到允许大多数数据点被正确分类的好的K可以通过查看数据来完成。另外,应该仔细挑选K以防止算法的不可判定性。 说例如K = 2,问题有2个标签,那么当一个点在两个标签之间时,算法应该选择哪一个。 有一个经验法则,K应该是特征数目的平方根(或者说,维度的数目)。在我们的例子中,K = 1,但这不是一个好主意,因为这将导致在决策边界周围地更高的假分类。 挑选K = 2将导致我们两个标签的错误,因此目前挑选K = 3似乎是一个很好的拟合。
对于这个例子,我们做2折交叉验证。 一般来说,2折交叉验证是模型验证的一个相当弱的方法,因为它将数据集拆分为一半,仅验证两次,这仍然允许过拟合,但由于数据集只有100点,10折(这是一个更强的版本)压根没有意义,因为那里只有10个数据点用于测试,这将给出一个倾斜的错误率。
def main(args: Array[String]): Unit = {
val basePath ="/.../KNN_Example_1.csv"
val testData =getDataFromCSV(new File(basePath))
//Define theamount of rounds, in our case 2 and
//initialisethe cross validation
val cv = newCrossValidation(testData._2.length, validationRounds)
val testDataWithIndices = (testData
._1
.zipWithIndex,
testData
._2
.zipWithIndex)
val trainingDPSets= cv.train
.map(indexList => indexList
.map(index=> testDataWithIndices
._1.collectFirst { case (dp, `index`) => dp}.get))
valtrainingClassifierSets = cv.train
.map(indexList => indexList
.map(index=> testDataWithIndices
._2.collectFirst { case (dp, `index`) => dp}.get))
valtestingDPSets = cv.test
.map(indexList => indexList
.map(index=> testDataWithIndices
._1.collectFirst { case (dp, `index`) => dp}.get))
val testingClassifierSets= cv.test
.map(indexList => indexList
.map(index=> testDataWithIndices
._2.collectFirst { case (dp, `index`) => dp}.get))
valvalidationRoundRecords = trainingDPSets
.zipWithIndex.map(x => ( x._1,
trainingClassifierSets(x._2),
testingDPSets(x._2),
testingClassifierSets(x._2)
)
)
validationRoundRecords
.foreach {record =>
val knn =KNN.learn(record._1, record._2, 3)
//And foreach test data point make a prediction with the model
valpredictions = record
._3
.map(x=> knn.predict(x))
.zipWithIndex
//Finallyevaluate the predictions as correct or incorrect
//and countthe amount of wrongly classified data points.
val error =predictions
.map(x=> if (x._1 != record._4(x._2)) 1 else 0)
.sum
println("False prediction rate: " + error / predictions.length* 100 + "%")
}
}
如果你执行这个代码几次,你可能会注意到错误的预测率波动相当多。这是由于用于训练和测试的随机样本。 当这个随机采样有些不幸时,错误率变得更高,而当获取好的随机采样时,错误率可能非常低。
不幸的是,我不能为你提供一个黄金规则,即使你的模型有着最好的训练集去训练。有人会说,具有最小错误率的模型总是最好的,但是当你回想起过拟合这一术语时,选择这个特定的模型可能对未来的数据执行真的很糟糕。 这就是为什么有一个足够大和代表性的数据集是一个良好的机器学习应用程序的关键。然而,当意识到这个问题,你可以不断根据新的数据和已知正确的分类不断更新你的模型。
让我们回顾一下我们迄今为止做了什么。首先,你得到了训练和测试数据。 接下来你生成和验证几个模型,并选择给出最好的结果的模型。 然后我们现在有一个最后一步要做,这是使用这个模型进行预测:
val knn = KNN.learn(record._1, record._2, 3)
val unknownDataPoint = Array(5.3, 4.3)
val result = knn.predict(unknownDatapoint)
if (result == 0)
{
println("Internet Service Provider Alpha")
}
else if (result == 1)
{
println("Internet Service Provider Beta")
}
else
{
println("Unexpected prediction")
}
执行此代码的结果是将unknownDataPoint(5.3,4.3)标记为ISP Alpha。 这是更容易分类的点之一,因为它清楚地在图中的数据点的 Alpha字段中。 因为现在很清楚如何做这些预测,我不会介绍给你其他点,但随时可以尝试不同的点去进行预测。