【Spark】用scala语言带你手撕KNN算法在海伦约会上的应用

关注博主不迷路,不定期更行博文,看完记得一键三连哦!

在运行实现该算法之前,大家一定要安装好相关应用和工具

应用: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算法,需要按照以下步骤进行:

  1. 数据转换: 将数据集转换为RDDDataFrame,并对其进行必要的预处理。

  2. 距离计算: 实现一个函数来计算两个数据点之间的距离。

  3. 寻找最近邻: 对于每个测试样本,使用reduceByKeytake操作来找到最近的K个邻居。

  4. 投票/平均: 根据K个最近邻的标签,通过投票或平均来预测测试样本的标签。

  5. 模型评估: 使用测试集评估自定义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()
  }
}

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,首先,我们需要加载数据集并进行预处理。以下是完整的代码: ```scala import org.apache.spark.ml.feature.{IndexToString, StringIndexer, VectorAssembler} import org.apache.spark.ml.linalg.Vectors import org.apache.spark.ml.classification.{KNNClassificationModel, KNNClassifier} import org.apache.spark.sql.{DataFrame, SparkSession} // 创建SparkSession val spark = SparkSession.builder() .appName("KNNExample") .master("local[*]") .getOrCreate() // 读取数据集 val data = spark.read.option("inferSchema", "true") .option("header", "false") .csv("path/to/adult.data") .toDF("age", "workclass", "fnlwgt", "education", "education-num", "marital-status", "occupation", "relationship", "race", "sex", "capital-gain", "capital-loss", "hours-per-week", "native-country", "label") // 将分类变量转换为数值变量 val categoricalCols = Array("workclass", "education", "marital-status", "occupation", "relationship", "race", "sex", "native-country") val indexers = categoricalCols.map { colName => new StringIndexer().setInputCol(colName).setOutputCol(colName + "_index").fit(data) } val pipeline = new Pipeline().setStages(indexers) val transformedData = pipeline.fit(data).transform(data) // 将所有特征合并到一个向量中 val assembler = new VectorAssembler() .setInputCols(Array("age", "workclass_index", "fnlwgt", "education_index", "education-num", "marital-status_index", "occupation_index", "relationship_index", "race_index", "sex_index", "capital-gain", "capital-loss", "hours-per-week", "native-country_index")) .setOutputCol("features") val dataWithFeatures = assembler.transform(transformedData) // 将标签列转换为数值类型 val labelIndexer = new StringIndexer().setInputCol("label").setOutputCol("label_index").fit(dataWithFeatures) val dataWithLabel = labelIndexer.transform(dataWithFeatures) // 将数据集拆分为训练集和测试集 val Array(trainingData, testData) = dataWithLabel.randomSplit(Array(0.7, 0.3)) // 创建KNN模型 val knn = new KNNClassifier().setFeaturesCol("features").setLabelCol("label_index").setTopTreeSize(10) // 训练模型 val model = knn.fit(trainingData) // 在测试集上进行预测 val predictions = model.transform(testData) // 将预测结果转换为原始标签 val labelConverter = new IndexToString().setInputCol("prediction").setOutputCol("predictedLabel").setLabels(labelIndexer.labels) val convertedPredictions = labelConverter.transform(predictions) // 计算模型的准确率 val correctPredictions = convertedPredictions.filter($"label" === $"predictedLabel").count() val totalPredictions = convertedPredictions.count() val accuracy = correctPredictions.toDouble / totalPredictions.toDouble println(s"Accuracy: $accuracy") ``` 在上面的代码中,我们首先加载数据集,并对分类变量进行数值化。然后,我们将所有特征合并为一个向量,并将标签列转换为数值类型。接下来,我们将数据集拆分为训练集和测试集,然后创建KNN模型并进行训练。最后,我们在测试集上进行预测,并计算模型的准确率。 请注意,上面的代码中使用的KNNClassifier是Spark MLlib中的一个实验性算法,可能在未来的版本中发生变化。如果您在生产环境中使用KNN算法,请注意选择稳定的算法实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值