机器学习这个词是让人疑惑的,首先它是英文名称Machine Learning(简称ML)的直译,在计算界Machine一般指计算机。这个名字使用了拟人的手法,说明了这门技术是让机器“学习”的技术。但是计算机是死的,怎么可能像人类一样“学习”呢?
传统上如果我们想让计算机工作,我们给它一串指令,然后它遵照这个指令一步步执行下去。有因有果,非常明确。但这样的方式在机器学习中行不通。机器学习根本不接受你输入的指令,相反,它接受你输入的数据! 也就是说,机器学习是一种让计算机利用数据而不是指令来进行各种工作的方法。这听起来非常不可思议,但结果上却是非常可行的。“统计”思想将在你学习“机器学习”相关理念时无时无刻不伴随,相关而不是因果的概念将是支撑机器学习能够工作的核心概念。你会颠覆对你以前所有程序中建立的因果无处不在的根本理念。
[外链图片转存失败(img-WB4oinHN-1563260413834)(assets/221227224214301.png)]
机器学习方法是计算机利用已有的数据(经验),得出了某种模型(迟到的规律),并利用此模型预测未来(是否迟到)的一种方法。
机器学习(Machine Learning, ML)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。
机器学习是人工智能的核心,是使计算机具有智能的根本途径,其应用遍及人工智能的各个领域。
[外链图片转存失败(img-MzuRQCcO-1563260413836)(assets/2018052522431460.png)]
1、数据收集与探索性分析
业界有一句非常著名的话:“数据决定了机器学习的上界,而模型和算法只是逼近这个上界。”由此可见,数据对于整个机器学习项目至关重要。
通常,我们拿到一个具体的领域问题后,可以使用网上一些具有代表性的、大众经常会用到的公开数据集。相较于自己整理的数据集,显然大众的数据集更具有代表性,数据处理的结果也更容易得到大家的认可。此外,大众的数据集在数据过拟合、数据偏差、数值缺失等问题上也会处理的更好。但如果在网上找不到现成的数据,那我们只好收集原始数据,再去一步步进行加工、整理。
得到数据集后应首先进行探索性数据分析,了解数据的分布情况。
对于分类问题,数据偏斜不能过于严重,不同类别的数据数量不要有数个数量级的差距。 而且还要对数据的量级有一个评估,多少个样本,多少个特征,可以估算出其对内存的消耗程度,判断训练过程中内存是否能够放得下。如果放不下就得考虑改进算法或者使用一些降维的技巧了。如果数据量实在太大,那就要考虑分布式了。
2、数据清洗
数据集或多或少都会存在数据缺失、分布不均衡、存在异常数据、混有无关紧要的数据等诸多数据不规范的问题。这就需要我们对收集到的数据进行进一步的处理,包括处理缺失值、处理偏离值、数据规范化、数据的转换等,这样的步骤叫做数据清洗或数据预处理。
3、特征工程
特征工程就是一个把原始数据转变成特征的过程,这些特征可以很好的描述这些数据,并且利用它们建立的模型在未知数据上的表现性能可以达到最优(或者接近最佳性能)。从数学的角度来看,特征工程就是人工地去设计输入变量X。
特征越好,灵活性越强;特征越好,构建的模型越简单;特征越好,模型的性能越出色。
特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功的关键。纵观Kaggle、KDD等国内外大大小小的比赛,每个竞赛的冠军其实并没有用到很高深的算法,大多数都是在特征工程这个环节做出了出色的工作,然后使用一些常见的算法,比如LR(线性回归),就能得到出色的性能。遗憾的是,市面上并没有系统的介绍特征工程的书籍;实践中特征工程更多依赖分析师的经验;
4、模型选择、训练与评估
可供选择的机器学习模型有很多,每个模型都有自己的适用场景,那么如何选择合适的模型呢?
首先我们要对处理好的数据进行分析,判断训练数据有没有类标,若是有类标则应该考虑监督学习的模型,否则可以划分为非监督学习问题。其次分析问题的类型是属于分类问题还是回归问题,当我们确定好问题的类型之后再去选择具体的模型。
在模型的实际选择时,通常会考虑尝试不同的模型对数据进行训练,然后比较输出的结果,选择最佳的那个。此外,我们还会考虑到数据集的大小。若是数据集样本较少,训练的时间较短,通常考虑朴素贝叶斯等一些轻量级的算法,否则的话就要考虑SVM等一些重量级算法。
选好模型后是调优问题,可以采用交差验证,观察损失曲线,测试结果曲线等分析原因,调节参数。
[外链图片转存失败(img-S0xlG4Br-1563260413837)(assets/无标题.png)]
**标签:**对于机器学习,我们经常说要训练机器,让它达到一个最佳的状态,然后用它来预测一些事情。
预测的信息,叫作标签。不预测,通过人工建立的信息,也叫作标签。标签就是一个信息。
信息多种多样,所以标签也就多种多样。标签可以表示一类物体,比如标签是“苹果”、“香蕉”,比如标签是“红色”、“橙色”。
**特征:**标签是一个信息,至于是什么信息,跟特征有直接关系。特征就是一系列的信息,用来表征事物,映射出标签。特征应该是具体可量化的信息,不包括主观感受。
说一个人“稳重”,是根据这个人的特征来评定的,比如TA再三确认问题、及时反馈进度、结束后回顾总结,等等,具备这些特征,我们就给TA打上“稳重”的标签。
样本:
样本是数据实例,是特征的封装。样本可以带标签,或不带标签。
[外链图片转存失败(img-v9sK4iNg-1563260413838)(assets/1562940991196.png)]
监督式学习:训练数据集有标注;线性回归、逻辑回归、决策树算法;
非监督式学习:训练集目标无标注;如聚类算法、关联规则算法;
增强学习:智能体不断与环境进行交互,通过试错的方式来获得最佳策略;
如果预测的值是离散值,此类学习任务称为 分类;
如果预测的值是连续值,此类学习任务称为 回归;
学习过程中使用的训练样本不具备标记信息,将训练集分成若干组,这些自动形成的组可能对应一些潜在的规律,此类学习任务称为 聚类;
**过拟合:**过分依赖训练数据;
**欠拟合:**未能学习训练数据中的关系;
[外链图片转存失败(img-LPqbXd89-1563260413839)(assets/u=1743534219,2796932966&fm=173&app=49&f=JPEG.jpg)]
谈起机器学习,真是让我们心向往之同时又令人头痛不已。
心生向往是因为机器学习在很多方面都已经展现出其魅力,在人工智能的领域比如说AlphaGo、无人驾驶、语音识别、人脸识别、车牌识别;靠近生活的有推荐系统、用户画像、情感分析、商品推荐等等,都或多或少用到机器学习的知识。其中大部分应用是相当能满足程序员心中的极客精神的(极客精神的本质:好奇与行动,或者说是好奇之心与改变之力,但这两者必须建立在能力的基础上)。
但令人头不痛不已的当你去涉足机器学习这个领域的时候,你会发现其中涉及大量的数学知识(高等数学、概率统计、线性代数【矩阵论】、最优化、二次规划),这对很多程序员来说都很不友好。
但没关系,程序员应该是工程师,而不是科学家,我们要做的是学会把理论落实成为生产力。
因此我们将尽可能降低数学的描述(避免一长串的数学证明)来描述机器学习算法的基本原理。
如果需要对算法进行深入了解和学习,那么还是应该认真学习算法背后的数学原理。
MLlib是Spark的机器学习(ML)库。旨在简化机器学习的工程实践工作,并方便扩展到更大规模。MLlib由一些通用的学习算法和工具组成,包括分类、回归、聚类、协同过滤、降维机器学习算法,同时还包括底层的优化和高层的管道API。具体来说,其主要包括以下几方面的内容:
Spark 机器学习库从 1.2 版本以后被分为两个包:
spark.mllib 包含基于RDD的原始算法API。Spark MLlib 历史比较长,在1.0 以前的版本即已经包含了,提供的算法实现都是基于原始的 RDD。
spark.ml 则提供了基于DataFrames 高层次的API,可以用来构建机器学习工作流(PipeLine)。ML Pipeline 弥补了原始 MLlib 库的不足,向用户提供了一个基于 DataFrame 的机器学习工作流式 API 套件。
Spark在机器学习方面的发展非常快,目前已经支持了主流的统计和机器学习算法。纵观所有基于分布式架构的开源机器学习库,MLlib可以算是计算效率比较高的。MLlib目前支持常见的机器学习问题: 分类、回归、聚类和协同过滤等。
MLlib底层基础部分主要包括向量接口和矩阵接口,这两种接口都会使用Scala语言基于 Netlib 和 BLAS / LAPACK开发的线性代数库Breeze。
MLlib支持本地的密集向量和稀疏向量,并且支持标量向量。疏矩阵在含有大量非零元素的向量Vector计算中会节省大量的空间并大幅度提高计算速度。MLlib同时支持本地矩阵和分布式矩阵。
KNN(k-NearestNeighbor)又被称为近邻算法,它的核心思想是:物以类聚,人以群分。KNN算法是机器学习中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。KNN是一种分类算法,KNN没有显式的学习过程,也就是说没有训练阶段,待收到新样本后直接进行处理。
KNN的思路是:如果一个样本在特征空间中的k个最邻近的样本中的大多数属于某一个类别,则该样本也划分为这个类别。KNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。
提到KNN,网上最常见的就是下面这个图,可以帮助大家理解。我们要确定绿点属于哪个颜色(红色或者蓝色),要做的就是选出距离目标点距离最近的k个点,看这k个点的大多数颜色是什么颜色。当k取3的时候,我们可以看出距离最近的三个,分别是红色、红色、蓝色,因此判定目标点为红色。
[外链图片转存失败(img-vWGIFTwY-1563260413840)(assets/1378215-20180805232806939-472376897.png)]
0)分别读取测试数据、训练数据集;
1)计算测试数据与训练数据之间的距离;
2)选取距离最小的K个点;
3)确定前K个点所在类别的出现频率;
4)返回前K个点中出现频率最高的类别作为测试数据的预测分类
可以简化为:找邻居 + 投票
距离的计算:欧式距离
目标:用 spark 实现 KNN 算法(KNN算法在MLlib中没有实现)
数据集:鸢尾花数据集
Iris 鸢尾花数据集是一个经典数据集,在统计学习和机器学习领域都经常被用作示例。数据集内包含 3 类共 150 条记录,每类各 50 个数据,每条记录都有 4 项特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度,可以通过这4个特征预测鸢尾花卉属于(iris-setosa, iris-versicolour, iris-virginica)中的哪一品种。
KNN算法实现:
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.math.{pow, sqrt}
object SimpleKNN {
case class LabelPoint(label: String, point: Array[Double])
def main(args: Array[String]): Unit = {
// 1、初始化
val conf = new SparkConf()
.setAppName(s"${this.getClass.getCanonicalName}")
.setMaster("local[4]")
val sc = new SparkContext(conf)
val K = 15
sc.setLogLevel("WARN")
// 2、读数据,封装数据
val lines = sc.textFile("file:///C:\\Users\\wangsu\\Desktop\\Spark MLlib\\iris1.dat")
.map(line => {
val arr = line.split(",")
if (arr.length==4)
LabelPoint("", arr.map(_.toDouble))
else
LabelPoint(arr.last, arr.init.map(_.toDouble))
})
// 3、将数据分为样本数据、测试数据
val sampleRDD: RDD[LabelPoint] = lines.filter(_.label != "")
val testData: Array[Array[Double]] = lines.filter(_.label == "").collect().map(_.point)
def getDistance(x: Array[Double], y: Array[Double]): Double = {
sqrt(x.zip(y).map(z => pow(z._1 - z._2, 2)).sum)
}
// 4、求最近的K个点;对这K个点的label做wordcount,得到最终结果
testData.foreach(elem => {
val dists: RDD[(Double, String)] = sampleRDD.map(labelpoint => (getDistance(elem, labelpoint.point), labelpoint.label))
val minDists: Array[(Double, String)] = dists.sortBy(_._1).take(K)
val labels = minDists.map(_._2)
print(s"${elem.toBuffer} : ")
labels.groupBy(x=>x).mapValues(_.length).foreach(print)
println()
})
sc.stop()
}
}
备注:
1、数据要做合理封装
2、规避RDD嵌套
3、程序并不复杂,如果能自己实现能更好的理解算法
4、程序中的问题,在步骤4中需要多少次遍历数据集,可以优化
优化算法:
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.immutable
import scala.collection.immutable.TreeSet
import scala.math.{pow, sqrt}
object SuperKNN {
case class LabelPoint(label: String, point: Array[Double])
def main(args: Array[String]): Unit = {
// 1、初始化
val conf = new SparkConf()
.setAppName(s"${this.getClass.getCanonicalName}")
.setMaster("local[4]")
val sc = new SparkContext(conf)
val K = 15
sc.setLogLevel("WARN")
// 2、读数据,封装数据
val lines = sc.textFile("file:///C:\\Users\\wangsu\\Desktop\\Spark MLlib\\iris1.dat")
.map(line => {
val arr = line.split(",")
if (arr.length==4)
LabelPoint("", arr.map(_.toDouble))
else
LabelPoint(arr.last, arr.init.map(_.toDouble))
})
// 3、将数据分为样本数据、测试数据
val sampleRDD = lines.filter(_.label != "")
val testData = lines.filter(_.label == "").collect().map(_.point)
val bc_testData = sc.broadcast(testData)
def getDistance(x: Array[Double], y: Array[Double]): Double = {
sqrt(x.zip(y).map(z => pow(z._1 - z._2, 2)).sum)
}
// 4、计算距离
// 减少对数据集的扫描(mapPartitions、广播变量),以应对大数据集(优化)
val distanceRDD: RDD[(String, (Double, String))] = sampleRDD.mapPartitions(iter => {
val bcpoints = bc_testData.value
// 这里要用flatMap
iter.flatMap { case LabelPoint(label, point1) =>
bcpoints.map(point2 => (point2.mkString(","), (getDistance(point2, point1), label)))
}
})
// 5、求距离最小的K个点
// 转换为了一个典型的 TopK 问题 (求分区内的topK,再求总的topK)
val topKRDD: RDD[(String, Map[String, Int])] = distanceRDD.aggregateByKey(immutable.TreeSet[(Double, String)]())(
(splitSet: TreeSet[(Double, String)], elem: (Double, String)) => {
val newSet = splitSet + elem
newSet.take(K)
},
(splitSet1: TreeSet[(Double, String)], splitSet2) =>
(splitSet1 ++ splitSet2).take(K)
).map { case (point, set) =>
(point, set.toArray.map(_._2).groupBy(x => x).map(x => (x._1, x._2.length)))
}
// 6、打印结果
topKRDD.collect().foreach(println)
sc.stop()
}
}
**KD树 与 球树。**KNN暴力求解,计算预测样本与所有训练集中的样本的距离,选出最小的k个距离所对应的样本,然后采用多数表决的方法进行预测。这个方法对于少量样本来说简单有效,但是对于大量样本来说效率太低。实践中会采用kd树和球树进行改进。kd树 与 球树 的基本思想是将样本点做分类,当计算预测样本与所有训练集中的样本的距离不必遍历全部的训练样本,从而提高效率。
限定半径最近邻算法。样本中某系类别的样本非常的少,甚至少于K,这导致稀有类别样本在找K个最近邻的时候,会把距离其实较远的其他样本考虑进来,而导致预测不准确。为了解决这个问题,我们限定最近邻的一个最大距离,也就是说,我们只在一个距离范围内搜索所有的最近邻,这避免了上述问题。这个距离我们一般称为限定半径。
最近质心算法。它首先把样本按输出类别归类。对于第i类的Ci个样本。它会对这Ci个样本的n维特征中每一维特征求平均值,最终该类别所有维度的n个平均值形成所谓的质心点。对于样本中的所有出现的类别,每个类别会最终得到一个质心点。当我们做预测时,仅仅需要比较预测样本和这些质心的距离,最小的距离对于的质心类别即为预测的类别。
KNN算法的优点:
(1)理论成熟,思想简单,既可以用来做分类也可以用来做回归;
(2)可用于非线性分类;
(3)训练时间复杂度低,为O(n);
(4) 和朴素贝叶斯之类的算法比,对数据没有假设,准确度高,对异常点不敏感;
KNN算法的缺点:
(1)计算量大,尤其是特征数非常多的时候;
(2)样本不平衡的时候,对稀有类别的预测准确率低;
(3)kd树,球树之类的模型建立需要大量的内存;
(4)使用懒散学习方法,基本上不学习,导致预测时速度比起逻辑回归之类的算法慢;
(5)KNN模型可解释性不强。
聚类就是对大量未标注的数据集,按数据的内在相似性将数据集划分为多个类别,使类别内的数据相似度较大,而类别间的数据相似度较小。
kmeans是最简单的聚类算法之一,但是运用十分广泛。最近在工作中也经常遇到这个算法。kmeans一般在数据分析前期使用,选取适当的k,将数据分类后,然后分类研究不同聚类下数据的特点。
k均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,其步骤是随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
[外链图片转存失败(img-szz7s0EL-1563260413840)(assets/KMeans过程.png)]
1、读取、封装数据;
2、随机选取K个中心点;
3、计算所有点 与 K个中心点的距离,离哪个中心点近就属于那个分类;
4、计算 K个分类的中心点;
5、计算中心点是否发生移动;
6、中心点没有移动,迭代结束;否则转步骤3
目标:用 spark 实现KMeans 算法
数据集:鸢尾花数据集
import scala.math.{pow, sqrt}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* kmeans实现步骤:
* 1、读数据、封装数据
* 2、随机选择K个点
* 3、计算所有点到K个点的距离
* 4、遍历所有点,对每个点找距离最小的点,离谁最近就属于哪个分类
* 5、计算K个分类的中心点
* 6、计算新旧中心是否发生移动
* 7、没有移动结束循环,否则转步骤3
*/
object KMeansImpl {
case class LablePoint(label: String, point: Array[Double])
def main(args: Array[String]) {
// K : 分类的个数;minDist : 中心点移动的最小距离,迭代终止条件;两个参数最好从命令行传进来
val K = 3
val minDist = 0.001
val conf = new SparkConf().setMaster("local[*]").setAppName("KMeans")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
// 1、读文件,封装数据
val lines = sc.textFile("file:///C:\\Users\\wangsu\\Desktop\\Spark MLlib\\iris.dat")
val data: RDD[LablePoint] = lines.filter(_.trim.size != 0)
.map(line => {
val arr = line.split(",")
LablePoint(arr.last, arr.init.map(_.toDouble))
})
data.cache()
val doubleses: Array[Array[Double]] = data.takeSample(withReplacement = false, K).map(_.point)
// 2、获得K个随机的中心点
val centerPoints = doubleses
var tempDist = 1.0
while(tempDist > minDist) {
// 3、计算所有点到K个点的距离;
// 得到每个点的分类 [分类编号, (特征, 1.0)];1.0 在后面计算中心点时用于计数
val indexRDD: RDD[(Int, (Array[Double], Double))] = data.map(p => (getIndex(p.point, centerPoints), (p.point, 1.0)))
// 计算新的中心点
def arrayAdd(x: Array[Double], y: Array[Double]): Array[Double] = x.zip(y).map(elem => elem._1 + elem._2)
// 将所用的点按照计算出的分类计算
val catalogRDD: RDD[(Int, (Array[Double], Double))] = indexRDD.reduceByKey((x, y) =>
(arrayAdd(x._1, y._1), x._2 + y._2)
)
// 计算新的中心点
val newCenterPoints: collection.Map[Int, Array[Double]] =
catalogRDD.map{ case (index, (point, count)) => (index, point.map(_ / count)) }
.collectAsMap()
// 计算中心点移动的距离
val dist = for (i <- 0 until K) yield {
getDistance(centerPoints(i), newCenterPoints(i))
}
tempDist = dist.sum
// 重新定义中心点
for ((key, value) <- newCenterPoints) {
centerPoints(key) = value
}
println("distSum = " + tempDist + "")
}
// 打印结果
println("Final centers:")
centerPoints.foreach(x => println(x.toBuffer))
sc.stop()
}
private def getDistance(x: Array[Double], y: Array[Double]): Double = {
sqrt(x.zip(y).map(elem => pow(elem._1 - elem._2, 2)).sum)
}
private def getIndex(p: Array[Double], centers: Array[Array[Double]]): Int = {
val dist = centers.map(point => getDistance(point, p))
dist.indexOf(dist.min)
}
}
KMeans算法的缺点:
1、K值必须给定。进行k-means算法时,必须指定聚类数量。但是有时候我们并不知道应该聚成多少个类,而是希望算法可以给出一个合理的聚类数量,往往一开始K值很难预先估计并给定。
2、随机的K个中心点影响结果。KMeans算法对初值的选择是敏感的,一开始的k个中心点是随机选定的,在后面的迭代中再进行重算,直到收敛。但是根据算法的步骤不难看出,这样一来最后所生成的结果往往很大程度上取决于一开始K个中心点的位置。这样一来,也就意味着结果具备很大的随机性,每次计算结果都会因为初始随机选择的中心质点不一样而导致结果不一样。
[外链图片转存失败(img-JVwnKqjc-1563260413841)(assets/Kmeans算法对初值敏感.png)]
Spark MLlib 实现 KMeans II 算法,主要解决第二个缺点。
(1)从输入的数据点集合中随机选择一个点作为第一个聚类中心;
(2)对于数据集中的每一个点x,计算它与最近聚类中心(指已选择的聚类中心)的距离D(x),并以较大的概率选择离初始聚类中心之间远的点作为下一个聚类中心点;
并根据以下概率选择新的聚类中心。
(3)重复过程(2)直到找到k个聚类中心。
调用Spark MLlib KMeans算法,对批发商的数据进行分类;
pom.xml文件中要增加:
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-mllib_2.11artifactId>
<version>${spark.version}version>
dependency>
数据集说明:数据来自于分销商,分销商将不同类别的商品卖给不同的客户;客户有可能是饭店、咖啡馆、便利店、大型超市等。商品分为6类,包括新鲜食物、牛奶、杂货、冷冻食品、洗涤剂和纸类产品、熟食等。
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.mllib.clustering.{KMeans, KMeansModel}
import org.apache.spark.mllib.linalg
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.rdd.RDD
object KMeansApp {
def main(args: Array[String]) {
// 初始化
val conf = new SparkConf().setAppName("KMeansApp").setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val filePath = "file:///C:\\Users\\wangsu\\Downloads\\Wholesale customers data.csv"
// 准备数据集
val rawTrainingData = sc.textFile(filePath)
val parsedTrainingData: RDD[linalg.Vector] = rawTrainingData.filter(!isColumnNameLine(_)).map(line => {
val data: Array[Double] = line.split(",")
.slice(2, 8)
.map(_.trim)
.filter(!"".equals(_))
.map(_.toDouble)
Vectors.dense(data)
})
parsedTrainingData.cache()
parsedTrainingData.foreach(println)
// 定义参数,供 KMeans 算法使用
val numClusters = 8
val numIterations = 60
// 模型训练
val clusters: KMeansModel = KMeans.train(parsedTrainingData, numClusters, numIterations, "k-means||")
// 输出模型的信息(聚类的个数;中心点的坐标)
println("Cluster Number:" + clusters.clusterCenters.length)
println("Cluster Centers Information Overview:")
var clusterIndex: Int = 0
clusters.clusterCenters.foreach(x => {
println(s"Center Point of Cluster $clusterIndex : $x")
clusterIndex += 1
})
// 计算数据的分类情况
//begin to check which cluster each test data belongs to based on the clustering result
val rawTestData = sc.textFile(filePath)
val parsedTestData = rawTestData.filter(!isColumnNameLine(_)).map(line => {
val data: Array[Double] = line.split(",")
.slice(2, 8)
.map(_.trim)
.filter(!"".equals(_))
.map(_.toDouble)
Vectors.dense(data)
})
parsedTestData.collect().foreach(testDataLine => {
val predictedClusterIndex: Int = clusters.predict(testDataLine)
println(s"The data $testDataLine belongs to cluster $predictedClusterIndex")
})
println("Spark MLlib K-means clustering test finished.")
// ks 中的值为K值,下面测试不同K值,聚类的
val ks:Array[Int] = Array(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 300)
ks.foreach(cluster => {
val model:KMeansModel = KMeans.train(parsedTrainingData, cluster,30,1)
val ssd = model.computeCost(parsedTrainingData)
println("sum of squared distances of points to their nearest center when k=" + cluster + " -> "+ ssd)
})
}
private def isColumnNameLine(line: String): Boolean = {
if (line != null && line.contains("Channel")) true
else false
}
}
决策树(decision tree)是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
决策树由结点和有向边组成,结点有两种类型:
[外链图片转存失败(img-ayLWdbWl-1563260413842)(assets/决策树模型.png)]
决策树优点:
1)决策树模型可以读性好,具有描述性,有助于人工分析;
2)效率高,决策树只需要一次构建,反复使用,每一次预测的最大计算次数不超过决策树的深度。
想象一个女孩的母亲要给这个女孩介绍男朋友,于是有了下面的对话:
女儿:多大年纪了?
母亲:26。
女儿:长的帅不帅?
母亲:挺帅的。
女儿:收入高不?
母亲:不算很高,中等情况。
女儿:是公务员不?
母亲:是,在税务局上班呢。
女儿:那好,我去见见。
这个女孩的决策过程就是典型的分类树决策。相当于通过年龄、长相、收入和是否公务员对将男人分为两个类别:见和不见。假设这个女孩对男人的要求是:30岁以下、长相中等以上并且是高收入者或中等以上收入的公务员,那么这个可以用下图表示女孩的决策逻辑。
[外链图片转存失败(img-DUnazbXt-1563260413843)(assets/决策树示例.jpg)]
假如双十一我要剁手买一件衣服,但是我一直犹豫着要不要买,我决定买这件事的不确定性(熵)为2.6。
我在看了这件衣服的评价后,我决定买衣服这件事的不确定性是1.2。
我在线下实体店试穿衣服后,我决定买衣服这件事的不确定性是0.9。
上面条件熵给出了两个:
一个是看了网上的评价,此时的信息增益是:Gain1=2.6−1.2=1.4
另一个是线下试穿了衣服,此时的信息增益:Gain2=2.6−0.9=1.7
很显然我在线下试穿衣服之后对于决定买这件衣服的不确定度下降更多,更通俗的说就是我试穿衣服之后买这件衣服的可能性更大了。所以如果有看买家评价和线下试穿两个属性,首先应该选择线下试穿来构建内部节点。
由于 MarkDown 文档排版功能的限制,这部分引用以下网页的内容
https://blog.csdn.net/PIPIXIU/article/details/82980740
(信息、熵、条件熵、信息增益)
信息增益 = 信息熵 – 条件熵
信息增益在决策树算法中是用来选择特征的指标,信息增益越大,则这个特征的选择性越好。
下表是一个由15个样本组成的贷款申请训练数据。数据包括贷款申请人的四个特征。表的最后一列是类别,是否同意贷款,取2个值:是、否。
[外链图片转存失败(img-8Kqqi9Q0-1563260413843)(assets/决策树案例数据-1563003295967.png)]
计算以上数据集的熵:
[外链图片转存失败(img-g5FdZm6l-1563260413844)(assets/1563087014478.png)]
g(D,A3)=0.420 【选择信息增益最大的属性,作为分裂点】
g(D,A4)=0.363
import scala.io.Source
/*
不是完整的决策树算法
使用测试数据集,仅计算第一个分裂点* */
object EntropyCalc {
case class LabeledPoint(feature: Array[String], Label: String)
// 计算以2为底的对数
def log2(x:Double):Double = {
assert(x>=0)
if (x==0) 0
else math.log(x)/math.log(2)
}
// 计算熵
def getEntropy(prob: Array[Int]) = {
val p1 = prob.map(_.toDouble/prob.sum)
val p2 = p1.map(x=>log2(x))
-p1.zip(p2).map(x=>x._1*x._2).sum
}
// 计算数据集Label的分类情况
def getProp(data: Array[LabeledPoint]): Array[Int] = {
data.map(elem => elem.Label).groupBy(x=>x).map(x=>x._2.length).toArray
}
// 计算对于不同的特征,数据集Label的分类情况
// 返回值类型:Array(List(Array(Int)))
// 一个List代表一个属性的分类情况,(下面的数据中有4个List,代表有4个属性)
// Array(
// List(Array(3, 2), Array(2, 3), Array(4, 1)),
// List(Array(6, 4), Array(5)),
// List(Array(6, 3), Array(6)),
// List(Array(4, 1), Array(4, 2), Array(4)))
def getConditionProp(data: Array[LabeledPoint], features: Int): Array[List[Array[Int]]] = {
(0 until features).map(index => {
data.map(elem=>(elem.feature(index), elem.Label)).
groupBy(x=>x).toArray. // 按照(属性,Label)进行聚组; groupBy之后返回的是Map,
map(x=>(x._1._1, x._2.length)). // 得到(属性,出现次数)
groupBy(x=>x._1). // 按照 属性 进行聚组
map(x=>x._2.map(y=>y._2)). // 得到最终结果
toList
}).toArray
}
// 计算条件熵
def getConditionEntroy(typePorb: Array[List[Array[Int]]]): Array[Double] = {
typePorb.map(list => {
//List(Array(3, 2), Array(2, 3), Array(4, 1))
val sums = list.flatten(x=>x).sum.toDouble
// 每个类别出现的概率
val prob = list.map(x=>x.sum/sums)
// 计算熵
val entropy = list.map(x=>getEntropy(x))
// 计算条件熵
prob.zip(entropy).map(x=>x._1*x._2).sum
})
}
def main(args: Array[String]): Unit = {
val lines = Source.fromFile("C:\\Users\\wangsu\\Desktop\\Spark MLlib\\EntropyCalc.dat").getLines.toArray
if (lines.length == 0) System.exit(1)
val featureNums = lines(0).split("\\s+").length - 1
val data: Array[LabeledPoint] = lines.map(line => {
val value = line.split("\\s+")
LabeledPoint(value.init, value.last)
})
// 1、计算数据集Label的分类情况:(6,9)
val typeProb = getProp(data)
// 2、计算选定某一特征,数据集Label的分类情况:
val typeConditionProb = getConditionProp(data, featureNums)
// 3、计算信息熵
val entropy = getEntropy(typeProb)
// 4、计算条件熵
val conditionEntropy = getConditionEntroy(typeConditionProb)
// 5、计算信息增益
val infoGain = conditionEntropy.map(x=> entropy - x)
println(infoGain.toBuffer)
}
}
数据集(EntropyCalc.dat):
青年 否 否 一般 否
青年 否 否 好 否
青年 是 否 好 是
青年 是 是 一般 是
青年 否 否 一般 否
中年 否 否 一般 否
中年 否 否 好 否
中年 是 是 好 是
中年 否 是 非常好 是
中年 否 是 非常好 是
老年 否 是 非常好 是
老年 否 是 好 是
老年 是 否 好 是
老年 是 否 非常好 是
老年 否 否 一般 否
[外链图片转存失败(img-QWvmJKnd-1563260413845)(assets/构造决策树.png)]
ID3算法递归地构建决策树:
1、从根节点开始,对所有特征计算信息增益
2、选择信息增益最大的特征作为节点的特征,由该特征的不同取值建立子节点;
3、再对子节点递归地调用以上方法构建决策树;
4、直到所有特征的信息增益均很小或者没有特征可以选择为止。最后得到一个决策树。
三种情形导致递归返回:
1、当前节点包含的样本全属于同一类别,无需划分;
2、当前属性集为空,或是所有样本在所有属性上取值相同,无法划分;
3、当前节点包含的样本集合为空,不能划分。(将其父节点中样本最多的类别设置为该叶子节点的类别)
C4.5算法,在ID3算法的基础做了改进:
1、能处理连续的特征;
2、采用信息增益比,选取分裂点;
3、能够处理缺失值
信息增益比
[外链图片转存失败(img-LFV8tSx2-1563260413846)(assets/1563090347204.png)]
基尼系数
[外链图片转存失败(img-2jPp7zk9-1563260413848)(assets/1563090374953.png)]
数据集说明:
案例使用 covtype 数据集合。下载后是一个压缩的 csv 文件,同时下载数据集的描述文件。
covtype.info 数据集记录的是美国 Colorado 植被覆盖类型数据,是一个真实森林的数据。每条记录都包含很多指标描述每一块土地。例如:高度、坡度、到水的距离、树荫下的面积、土壤的类型等等。森林的覆盖类型是需要根据其他54个特征进行预测的特征。
数据集包括分类和数值特征。总共有581012条记录。每条记录有55列,其中一列是土壤的类型,其他54列是输入特征。
covtype 数据集中有很多类型的特征,它已经帮我们转换成 one-hot 形式,具体来说:
• 11到14列,其实表示的是 Wilderness_Area,Wilderness_Area 本身有 4 个类别
• 15到54列,其实表示的是 Soil_Type,Soil_Type 本身有 40个属性值
• 55列是表示目标值(即label)
1、调用MLlib的决策树算法
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.mllib.tree.DecisionTree
object DecisionTree1 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("DececisionTree1").setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val lines = sc.textFile("C:\\Users\\wangsu\\Desktop\\Spark MLlib\\covtype.data")
val data= lines.map(line => {
val values = line.split(",").map(_.toDouble)
val feature = Vectors.dense(values.init)
val label = values.last - 1
LabeledPoint(label, feature)
})
val model = DecisionTree.trainClassifier(data, 7, Map[Int, Int](), "gini", 6, 32)
println(model.toDebugString)
sc.stop()
}
}
2、模型评价
import org.apache.spark.mllib.evaluation.MulticlassMetrics
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.mllib.tree.model.DecisionTreeModel
import org.apache.spark.rdd.RDD
object DecisionTree2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("DececisionTree1").setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val lines = sc.textFile("C:\\Users\\wangsu\\Desktop\\Spark MLlib\\covtype.data")
val data= lines.map(line => {
val values = line.split(",").map(_.toDouble)
val feature = Vectors.dense(values.init)
val label = values.last - 1
LabeledPoint(label, feature)
})
val Array(trainData, cvDatva, testData) = data.randomSplit(Array(0.3, 0.3, 0.4))
val model = DecisionTree.trainClassifier(trainData, 7, Map[Int, Int](), "gini", 17, 50)
def getMetrics(model:DecisionTreeModel, dataRDD:RDD[LabeledPoint]): MulticlassMetrics = {
val predictLabel = dataRDD.map(item =>{
val value = model.predict(item.features)
(value, item.label)
})
new MulticlassMetrics(predictLabel)
}
val metrics1 = getMetrics(model, trainData)
println("accuracy1 = " + metrics1.accuracy)
println("precision1 = " + metrics1.precision)
println("confusionMatrix1 = " + metrics1.confusionMatrix)
println("recall = " + metrics1.recall)
println("****************************************************************************")
val metrics2 = getMetrics(model, testData)
println("accuracy1 = " + metrics2.accuracy)
println("precision1 = " + metrics1.precision)
println("confusionMatrix2 = " + metrics2.confusionMatrix)
sc.stop()
}
}
3、超参数选择
import org.apache.spark.mllib.evaluation.MulticlassMetrics
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.{SparkConf, SparkContext}
object DecisionTree3 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("DecisionTree3")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val data = sc.textFile("C:\\Users\\wangsu\\Desktop\\Spark MLlib\\covtype.data")
.map(line => {
val arr = line.split(",").map(_.toDouble)
val label = arr.last - 1
val features = arr.init
LabeledPoint(label, Vectors.dense(features))
})
val Array(trainRDD, cvRDD) = data.randomSplit(Array(0.7, 0.4), 100)
trainRDD.cache()
cvRDD.cache()
for (impurity <- Array("gini", "entropy");
maxDepth <- Array(10, 20, 25, 30);
maxBins <- Array(32, 100, 200)){
val model = DecisionTree.trainClassifier(trainRDD, 7, Map[Int, Int](), impurity, maxDepth, maxBins)
val predictRDD = trainRDD.map{ case LabeledPoint(label, feature) =>
(model.predict(feature), label)
}
val a = new MulticlassMetrics(predictRDD)
println(a.accuracy, impurity, maxDepth, maxBins)
}
sc.stop()
}
}
4、不使用onehot编码
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.mllib.evaluation.MulticlassMetrics
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.mllib.tree.model.DecisionTreeModel
import org.apache.spark.rdd.RDD
object DecisionTree4 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("DececisionTree1").setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val lines = sc.textFile("C:\\Users\\wangsu\\Desktop\\Spark MLlib\\covtype.data")
val data= lines.map(line => {
val values = line.split(",").map(_.toDouble)
// 对应的4个特征中哪个取值为1
val wilderness = values.slice(10, 14).indexOf(1.0).toDouble
val soil = values.slice(14, 54).indexOf(1.0).toDouble
val featureVector = Vectors.dense(values.slice(0, 10) :+ wilderness :+ soil)
val label = values.last - 1
LabeledPoint(label, featureVector)
})
val model = DecisionTree.trainClassifier(data, 7, Map(10->4, 11->40), "gini", 17, 50)
def getMetrics(model:DecisionTreeModel, dataRDD:RDD[LabeledPoint]): MulticlassMetrics = {
val predictLabel = dataRDD.map(item =>{
val value = model.predict(item.features)
(value, item.label)
})
new MulticlassMetrics(predictLabel)
}
sc.stop()
}
}
回归这种现象最早由英国生物统计学家高尔顿在研究父母亲和子女的遗传特性时所发现的一种有趣的现象:身高这种遗传特性表现出“高个子父母,其子代身高也高于平均身高;但不见得比其父母更高,到一定程度后会往平均身高方向发生“回归”。这种效应被称为“趋中回归”。
现在的回归分析则多半指源于高尔顿工作的那样一整套建立变量间数量关系模型的方法和程序。
对大量的观测数据进行处理,从而得到比较符合事物内部规律的数学表达式。也就是说寻找到数据与数据之间的规律所在,从而就可以模拟出结果,也就是对结果进行预测。解决的就是通过已知的数据得到未知的结果。例如:对房价的预测、判断信用评价、电影票房预估等。
[外链图片转存失败(img-mdRghU10-1563260413850)(assets/13876065-7766b978beec2a61.png)]
图片上有很多个小点点,通过这些小点点我们很难预测当x值=某个值时,y的值是多少,我们无法得知,所以,数学家是很聪明的,是否能够找到一条直线来描述这些点的趋势或者分布呢?答案是肯定的。相信大家在学校的时候都学过这样的直线,只是当时不知道这个方程在现实中是可以用来预测很多事物的。
[外链图片转存失败(img-WDxHNdU6-1563260413857)(assets/1563099409731.png)]
可以用方程做拟合:[外链图片转存失败(img-AEN5TGkI-1563260413858)(assets/1238778-20171121174546149-1652553649.png)]
变量 Xi 是 i 个变量或者说属性 ;
参数 ai 是模型训练的目的就是计算出这些参数的值;
这样的方程可以有很多很多,哪个效果最好?
[外链图片转存失败(img-FwB9mnjK-1563260413860)(assets/20150506184443150.jpg)]
不要看公式很复杂,其实就是一句话,(预测值-真实值)的平法和的平均值,换句话说就是点到直线距离和最小。用一幅图来表示:
[外链图片转存失败(img-Zhdl10rg-1563260413861)(assets/13876065-556858ea2edf5a9f.png)]
一开始损失函数是比较大的,但随着直线的不断变化(模型不断训练),损失函数会越来越小,从而达到极小值点,也就是我们要得到的最终模型。
这种方法我们称为梯度下降法。随着模型的不断训练,损失函数的梯度越来越平,直至极小值点,点到直线的距离和最小,所以这条直线就会经过所有的点,这就是我们要求的模型(函数)。
以此类推,高维的线性回归模型也是一样的,利用梯度下降法优化模型,寻找极值点,这就是模型训练的过程。
[外链图片转存失败(img-dw6pt0i5-1563260413863)(assets/495808268-5aa08feed157b_articlex.png)]
随机梯度下降(GSD):
[外链图片转存失败(img-CzTNbFnc-1563260413868)(assets/1563110828353.png)]
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.{LabeledPoint, LinearRegressionModel, LinearRegressionWithSGD}
object LinearRegressionDemo {
def main(args: Array[String]): Unit = {
//1.构建spark对象
val conf: SparkConf = new SparkConf().setAppName("LinearRegressionDemo").setMaster("local[2]")
val sc = new SparkContext(conf)
sc.setLogLevel("ERROR")
//2.读取样本数据
val filePath = "C:\\Users\\wangsu\\Desktop\\Spark MLlib\\lpsa.dat"
val lines = sc.textFile(filePath)
val examplesRDD = lines.map { line =>
val parts = line.split(",")
LabeledPoint(parts(0).toDouble, Vectors.dense(parts(1).split(' ').map(_.toDouble)))
}.cache()
val numExamples: Long = examplesRDD.count()
//3.新建线性回归模型、并设置训练参数
val numIterations = 100
val stepSize = 1
val miniBatchFraction = 1.0
val model: LinearRegressionModel = LinearRegressionWithSGD.train(examplesRDD, numIterations, stepSize, miniBatchFraction)
println(s"model.weights = ${model.weights}")
println(s"model.intercept = ${model.intercept}")
//4.对样本进行测试
val prediction: RDD[Double] = model.predict(examplesRDD.map(_.features))
val predictionAndLabel: RDD[(Double, Double)] = prediction.zip(examplesRDD.map(_.label))
val print_predict: Array[(Double, Double)] = predictionAndLabel.take(50)
println("prediction" + "\t" + "label")
for (i <- 0 to print_predict.length - 1) {
println(print_predict(i)._1 + "\t" + print_predict(i)._2)
}
//5.计算测试误差
val loss: Double = predictionAndLabel.map {
x => (x._1 - 1) * (x._1 - 1)
}.reduce(_ + _)
val rmse: Double = math.sqrt(loss / numExamples)
println(s"Test RMSE =$rmse")
//6.保存模型
val mode_path = "C:\\Users\\wangsu\\Desktop\\Spark MLlib\\linearRegression"
model.save(sc, mode_path)
//7.加载模型
LinearRegressionModel.load(sc,mode_path)
sc.stop()
}
}
朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。朴素贝叶斯原理简单,也很容易实现,多用于文本分类,比如垃圾邮件过滤。
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。而朴素朴素贝叶斯分类是贝叶斯分类中最简单,也是常见的一种分类方法。
http://bbs.elecfans.com/jishu_1659159_1_1.html
https://blog.csdn.net/qq_42267603/article/details/88111742#4.%E5%AD%A6%E4%B9%A0%E4%B8%8E%E5%88%86%E7%B1%BB%E7%AE%97%E6%B3%95
plesRDD.map(.features))
val predictionAndLabel: RDD[(Double, Double)] = prediction.zip(examplesRDD.map(.label))
val print_predict: Array[(Double, Double)] = predictionAndLabel.take(50)
println(“prediction” + “\t” + “label”)
for (i <- 0 to print_predict.length - 1) {
println(print_predict(i)._1 + “\t” + print_predict(i)._2)
}
//5.计算测试误差
val loss: Double = predictionAndLabel.map {
x => (x._1 - 1) * (x._1 - 1)
}.reduce(_ + _)
val rmse: Double = math.sqrt(loss / numExamples)
println(s"Test RMSE =$rmse")
//6.保存模型
val mode_path = "C:\\Users\\wangsu\\Desktop\\Spark MLlib\\linearRegression"
model.save(sc, mode_path)
//7.加载模型
LinearRegressionModel.load(sc,mode_path)
sc.stop()
}
}
#### 7、朴素贝叶斯(监督学习、分类算法)
朴素贝叶斯是经典的机器学习算法之一,也是为数不多的基于概率论的分类算法。朴素贝叶斯原理简单,也很容易实现,多用于文本分类,比如垃圾邮件过滤。
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。而朴素朴素贝叶斯分类是贝叶斯分类中最简单,也是常见的一种分类方法。
http://bbs.elecfans.com/jishu_1659159_1_1.html
https://blog.csdn.net/qq_42267603/article/details/88111742#4.%E5%AD%A6%E4%B9%A0%E4%B8%8E%E5%88%86%E7%B1%BB%E7%AE%97%E6%B3%95