关注博主不迷路,不定期更行博文,看完记得一键三连哦!
在运行实现该算法之前,大家一定要安装好相关应用和工具
应用:IntelliJ IDEA 2023.3.2
JDK:1.8
scala: 2.11
spark:2.4.0,2.3.0
数据集:海伦约会数据集
一、创建Maven管理的scala项目
一般来说,大家会创建scala项目,而非maven管理的Scala项目,但由于idea版本的变化,添加Maven管理的方式也不同,博主在创建时也遇到了很多问题,参照了以下两篇博文,大家可以根据下面两篇博文将项目创建好,创建好的项目一定会有pom.xml文件,这样才可以添加依赖。
idea 2023版本创建maven管理的Scala项目教程_idea2023创建scala的maven工程-CSDN博客
idea 2023.3.2版本如何创建新的maven项目_idea2023创建maven项目-CSDN博客
二、KNN算法
KNN(K-Nearest Neighbors)是一种基本的分类和回归算法,它的工作原理非常直观:通过测量不同特征值之间的距离来进行预测。在分类问题中,KNN算法可以根据一个样本最近邻的K个样本的类别,通过投票的方式来预测该样本的类别。
如果你想要实现一个自定义的KNN算法,需要按照以下步骤进行:
-
数据转换: 将数据集转换为
RDD
或DataFrame
,并对其进行必要的预处理。 -
距离计算: 实现一个函数来计算两个数据点之间的距离。
-
寻找最近邻: 对于每个测试样本,使用
reduceByKey
和take
操作来找到最近的K个邻居。 -
投票/平均: 根据K个最近邻的标签,通过投票或平均来预测测试样本的标签。
-
模型评估: 使用测试集评估自定义KNN算法的性能。
KNN算法类实现如下:
case class DataPoint(features: Array[Double], label: Int)
class KNearestNeighbors(k: Int) {
private var trainingData: List[(DataPoint, Double)] = List()
// 加载训练数据
def loadTrainingData(data: List[DataPoint]): Unit = {
trainingData = data.map(dp => (dp, euclideanDistance(Array.fill(3)(0.0), dp.features)))
}
// 计算欧氏距离
private def euclideanDistance(point1: Array[Double], point2: Array[Double]): Double = {
math.sqrt(point1.zip(point2).map { case (x, y) => math.pow(x - y, 2) }.sum)
}
// 查找最近邻
private def findNearestNeighbors(testPoint: Array[Double]): List[(Int, Double)] = {
trainingData.map { case (dp, dist) => (dp.label, dist + euclideanDistance(testPoint, dp.features)) }
.sortBy(_._2).take(k)
}
// 投票机制
private def vote(neighbors: List[(Int, Double)]): Int = {
neighbors.groupBy(_._1).mapValues(_.size).maxBy(_._2)._1
}
// KNN分类
def classify(testPoint: Array[Double]): Int = {
val neighbors = findNearestNeighbors(testPoint)
vote(neighbors)
}
}
三、对训练集进行数据处理
val df = spark.read.textFile("E:\\scala\\Spark\\data\\date_train.txt")
import spark.implicits._
// 假设前三个字段是特征,最后一个字段是标签
val dataset = df.map(x => {
val fields = x.split("\t")
val fly = fields(0).toDouble
val play = fields(1).toDouble
val ice = fields(2).toDouble
val label = fields(3) // 直接使用字符串作为标签
(fly, play, ice, label)
})
// 使用StringIndexer来索引标签
val index = new StringIndexer().setInputCol("_4").setOutputCol("labelIndex")
// 训练StringIndexer模型并转换数据集
val indexed = index.fit(dataset).transform(dataset)
// indexed.show()
// 转换为DataPoint列表,注意:这里假设indexed已经是DataFrame类型
val traindata: List[DataPoint] = indexed.select("_1", "_2", "_3", "labelIndex")
.collect().toList.map(row => {
val features: Array[Double] = Array(row.getAs[Double](0), row.getAs[Double](1), row.getAs[Double](2))
// 确保正确地将索引转换为整数
val labelIndex: Double = row.getAs[Double](3) // 获取Double类型的索引
val label: Int = labelIndex.toInt // 转换为Int
DataPoint(features, label) // 构造DataPoint实例
})
// 显示转换后的数据
// traindata.foreach(println)
四、对测试集数据进行处理
val df2=spark.read.textFile("E:\\scala\\Spark\\data\\date_test.txt")
// 假设前三个字段是特征,最后一个字段是标签
val testPoints :Array[Array[Double]]= df2.map(x => {
val fields = x.split("\t")
val fly = fields(0).toDouble
val play = fields(1).toDouble
val ice = fields(2).toDouble
val label = fields(3) // 直接使用字符串作为标签
Array(fly, play, ice)
}).collect()
五、对训练集进行训练
val k = 3
val knn = new KNearestNeighbors(k)
knn.loadTrainingData(traindata)
六、对测试集进行测试
var label="UnKonwn"
testPoints.foreach { testPoint =>
val predictedLabel = knn.classify(testPoint)
if(predictedLabel==0){
label="didntLike"
}
if(predictedLabel==1){
label="smallDoses"
}
if(predictedLabel==2){
label="largeDoses"
}
println(s"The predicted persons' data : ${testPoint.mkString(", ")}" )
println(s"Hellon's attitude is $label")
}
七、总结
综上所述,即可实现spark对KNN算法的实现,有人会问为什么不直接调用spark的机器学习模块呢?当然是没有啦,哈哈,在这里博主给出了,但是该算法在模型评估上还存在较大问题,并没有实现类似于其他算法的模型评估指数,对于模型的正确率还有待考量,有厉害的大佬欢迎批评指正和完善,好啦,就这么多了。下面给出完整代码,数据集放在我的资源了(0积分):
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.ml.feature.StringIndexer
object KNN {
case class data(fly:Double,play:Double,ice:Double)
// 定义KNN算法
case class DataPoint(features: Array[Double], label: Int)
class KNearestNeighbors(k: Int) {
private var trainingData: List[(DataPoint, Double)] = List()
// 加载训练数据
def loadTrainingData(data: List[DataPoint]): Unit = {
trainingData = data.map(dp => (dp, euclideanDistance(Array.fill(3)(0.0), dp.features)))
}
// 计算欧氏距离
private def euclideanDistance(point1: Array[Double], point2: Array[Double]): Double = {
math.sqrt(point1.zip(point2).map { case (x, y) => math.pow(x - y, 2) }.sum)
}
// 查找最近邻
private def findNearestNeighbors(testPoint: Array[Double]): List[(Int, Double)] = {
trainingData.map { case (dp, dist) => (dp.label, dist + euclideanDistance(testPoint, dp.features)) }
.sortBy(_._2).take(k)
}
// 投票机制
private def vote(neighbors: List[(Int, Double)]): Int = {
neighbors.groupBy(_._1).mapValues(_.size).maxBy(_._2)._1
}
// KNN分类
def classify(testPoint: Array[Double]): Int = {
val neighbors = findNearestNeighbors(testPoint)
vote(neighbors)
}
}
def main(args:Array[String]):Unit= {
val spark = SparkSession.builder()
.appName("KNN Prediction with Spark")
.master("local[*]")
.getOrCreate()
val df = spark.read.textFile("E:\\scala\\Spark\\data\\date_train.txt")
import spark.implicits._
// 假设前三个字段是特征,最后一个字段是标签
val dataset = df.map(x => {
val fields = x.split("\t")
val fly = fields(0).toDouble
val play = fields(1).toDouble
val ice = fields(2).toDouble
val label = fields(3) // 直接使用字符串作为标签
(fly, play, ice, label)
})
// 使用StringIndexer来索引标签
val index = new StringIndexer().setInputCol("_4").setOutputCol("labelIndex")
// 训练StringIndexer模型并转换数据集
val indexed = index.fit(dataset).transform(dataset)
// indexed.show()
// 转换为DataPoint列表,注意:这里假设indexed已经是DataFrame类型
val traindata: List[DataPoint] = indexed.select("_1", "_2", "_3", "labelIndex")
.collect().toList.map(row => {
val features: Array[Double] = Array(row.getAs[Double](0), row.getAs[Double](1), row.getAs[Double](2))
// 确保正确地将索引转换为整数
val labelIndex: Double = row.getAs[Double](3) // 获取Double类型的索引
val label: Int = labelIndex.toInt // 转换为Int
DataPoint(features, label) // 构造DataPoint实例
})
// 显示转换后的数据
// traindata.foreach(println)
//设置K值,例如k=3
val k = 3
val knn = new KNearestNeighbors(k)
knn.loadTrainingData(traindata)
val df2=spark.read.textFile("E:\\scala\\Spark\\data\\date_test.txt")
// 假设前三个字段是特征,最后一个字段是标签
val testPoints :Array[Array[Double]]= df2.map(x => {
val fields = x.split("\t")
val fly = fields(0).toDouble
val play = fields(1).toDouble
val ice = fields(2).toDouble
val label = fields(3) // 直接使用字符串作为标签
Array(fly, play, ice)
}).collect()
var label="UnKonwn"
testPoints.foreach { testPoint =>
val predictedLabel = knn.classify(testPoint)
if(predictedLabel==0){
label="didntLike"
}
if(predictedLabel==1){
label="smallDoses"
}
if(predictedLabel==2){
label="largeDoses"
}
println(s"The predicted persons' data : ${testPoint.mkString(", ")}" )
println(s"Hellon's attitude is $label")
}
spark.stop()
}
}