Spark特征工程-one-hot 和 multi-hot

该博客介绍了Spark中用于特征工程的数据预处理技术,包括One-hot编码和Multi-hot编码。One-hot编码将离散特征转换为稀疏向量,每个类别对应一个维度;Multi-hot编码则适用于类别标签较多的情况,每个样本可以有多个类别标签。示例代码展示了如何使用Spark的OneHotEncoderEstimator和StringIndexer进行这两种编码操作,并给出了编码后的数据结构。

One-hot

  /**
   * One-hot encoding example function
   * @param samples movie samples dataframe
   */
  def oneHotEncoderExample(samples:DataFrame): Unit ={
    val samplesWithIdNumber = samples.withColumn("movieIdNumber", col("movieId").cast(sql.types.IntegerType))

    val oneHotEncoder = new OneHotEncoderEstimator()
      .setInputCols(Array("movieIdNumber"))
      .setOutputCols(Array("movieIdVector"))
      .setDropLast(false)

    val oneHotEncoderSamples = oneHotEncoder.fit(samplesWithIdNumber).transform(samplesWithIdNumber)
    oneHotEncoderSamples.printSchema()
    oneHotEncoderSamples.show(10)
  }

Multi-hot


  val array2vec: UserDefinedFunction = udf { (a: Seq[Int], length: Int) => org.apache.spark.ml.linalg.Vectors.sparse(length, a.sortWith(_ < _).toArray, Array.fill[Double](a.length)(1.0)) }

  /**
   * Multi-hot encoding example function
   * @param samples movie samples dataframe
   */
  def multiHotEncoderExample(samples:DataFrame): Unit ={
    val samplesWithGenre = samples.select(col("movieId"), col("title"),explode(split(col("genres"), "\\|").cast("array<string>")).as("genre"))
    val genreIndexer = new StringIndexer().setInputCol("genre").setOutputCol("genreIndex")

    val stringIndexerModel : StringIndexerModel = genreIndexer.fit(samplesWithGenre)

    val genreIndexSamples = stringIndexerModel.transform(samplesWithGenre)
      .withColumn("genreIndexInt", col("genreIndex").cast(sql.types.IntegerType))

/*    println("genreIndexSamples:")
    genreIndexSamples.printSchema()
    genreIndexSamples.show(10,false)
    println("genreIndexSamples.agg:")
    genreIndexSamples.agg(max(col("genreIndexInt"))).show(10,false)*/
   
    val indexSize = genreIndexSamples.agg(max(col("genreIndexInt"))).head().getAs[Int](0) + 1

    val processedSamples =  genreIndexSamples
      .groupBy(col("movieId")).agg(collect_list("genreIndexInt").as("genreIndexes"))
        .withColumn("indexSize", typedLit(indexSize))

    val finalSample = processedSamples.withColumn("vector", array2vec(col("genreIndexes"),col("indexSize")))
    finalSample.printSchema()
    finalSample.show(10,false)
  }

注释:
StringIndexer的使用
lit和typeLit
collect_list
agg使用
spark的聚合函数

输出样例:
one-hot
Raw Movie Samples:
root
 |-- movieId: string (nullable = true)
 |-- title: string (nullable = true)
 |-- genres: string (nullable = true)

+-------+--------------------+--------------------+
|movieId|               title|              genres|
+-------+--------------------+--------------------+
|      1|    Toy Story (1995)|Adventure|Animati...|
|      2|      Jumanji (1995)|Adventure|Childre...|
|      3|Grumpier Old Men ...|      Comedy|Romance|
|      4|Waiting to Exhale...|Comedy|Drama|Romance|
|      5|Father of the Bri...|              Comedy|
|      6|         Heat (1995)|Action|Crime|Thri...|
|      7|      Sabrina (1995)|      Comedy|Romance|
|      8| Tom and Huck (1995)|  Adventure|Children|
|      9| Sudden Death (1995)|              Action|
|     10|    GoldenEye (1995)|Action|Adventure|...|
+-------+--------------------+--------------------+
only showing top 10 rows

OneHotEncoder Example:
root
 |-- movieId: string (nullable = true)
 |-- title: string (nullable = true)
 |-- genres: string (nullable = true)
 |-- movieIdNumber: integer (nullable = true)
 |-- movieIdVector: vector (nullable = true)

+-------+--------------------+--------------------+-------------+-----------------+
|movieId|               title|              genres|movieIdNumber|    movieIdVector|
+-------+--------------------+--------------------+-------------+-----------------+
|      1|    Toy Story (1995)|Adventure|Animati...|            1| (1001,[1],[1.0])|
|      2|      Jumanji (1995)|Adventure|Childre...|            2| (1001,[2],[1.0])|
|      3|Grumpier Old Men ...|      Comedy|Romance|            3| (1001,[3],[1.0])|
|      4|Waiting to Exhale...|Comedy|Drama|Romance|            4| (1001,[4],[1.0])|
|      5|Father of the Bri...|              Comedy|            5| (1001,[5],[1.0])|
|      6|         Heat (1995)|Action|Crime|Thri...|            6| (1001,[6],[1.0])|
|      7|      Sabrina (1995)|      Comedy|Romance|            7| (1001,[7],[1.0])|
|      8| Tom and Huck (1995)|  Adventure|Children|            8| (1001,[8],[1.0])|
|      9| Sudden Death (1995)|              Action|            9| (1001,[9],[1.0])|
|     10|    GoldenEye (1995)|Action|Adventure|...|           10|(1001,[10],[1.0])|
+-------+--------------------+--------------------+-------------+-----------------+

multi-hot
MultiHotEncoder Example:
genreIndexSamples:
root
 |-- movieId: string (nullable = true)
 |-- title: string (nullable = true)
 |-- genre: string (nullable = true)
 |-- genreIndex: double (nullable = false)
 |-- genreIndexInt: integer (nullable = true)

+-------+-----------------------+---------+----------+-------------+
|movieId|title                  |genre    |genreIndex|genreIndexInt|
+-------+-----------------------+---------+----------+-------------+
|1      |Toy Story (1995)       |Adventure|6.0       |6            |
|1      |Toy Story (1995)       |Animation|15.0      |15           |
|1      |Toy Story (1995)       |Children |7.0       |7            |
|1      |Toy Story (1995)       |Comedy   |1.0       |1            |
|1      |Toy Story (1995)       |Fantasy  |10.0      |10           |
|2      |Jumanji (1995)         |Adventure|6.0       |6            |
|2      |Jumanji (1995)         |Children |7.0       |7            |
|2      |Jumanji (1995)         |Fantasy  |10.0      |10           |
|3      |Grumpier Old Men (1995)|Comedy   |1.0       |1            |
|3      |Grumpier Old Men (1995)|Romance  |2.0       |2            |
+-------+-----------------------+---------+----------+-------------+
 
 
genreIndexSamples.agg:
+------------------+
|max(genreIndexInt)|
+------------------+
|18                |
+------------------+

finalSample:
root
 |-- movieId: string (nullable = true)
 |-- genreIndexes: array (nullable = true)
 |    |-- element: integer (containsNull = true)
 |-- indexSize: integer (nullable = false)
 |-- vector: vector (nullable = true)

+-------+------------+---------+--------------------------------+
|movieId|genreIndexes|indexSize|vector                          |
+-------+------------+---------+--------------------------------+
|296    |[1, 5, 0, 3]|19       |(19,[0,1,3,5],[1.0,1.0,1.0,1.0])|
|467    |[1]         |19       |(19,[1],[1.0])                  |
|675    |[4, 0, 3]   |19       |(19,[0,3,4],[1.0,1.0,1.0])      |
|691    |[1, 2]      |19       |(19,[1,2],[1.0,1.0])            |
|829    |[1, 10, 14] |19       |(19,[1,10,14],[1.0,1.0,1.0])    |
|125    |[1]         |19       |(19,[1],[1.0])                  |
|451    |[0, 8, 2]   |19       |(19,[0,2,8],[1.0,1.0,1.0])      |
|800    |[0, 8, 16]  |19       |(19,[0,8,16],[1.0,1.0,1.0])     |
|853    |[0]         |19       |(19,[0],[1.0])                  |
|944    |[0]         |19       |(19,[0],[1.0])                  |
+-------+------------+---------+--------------------------------+

另一种multi-hot方法(适合标签量不大的情况)
主要是靠获取getWordsIndexMap,然后做映射


  def getWordsIndexMap(rdd: RDD[Set[String]], ss: SparkSession): Broadcast[Map[String, Int]] = {
    val allWords = rdd.map { x => (1, x) }.reduceByKey((x, y) => x ++ y).collect().head._2.toArray.sorted
    val wordsMapbt = ss.sparkContext.broadcast(allWords.zip(0.until(allWords.length)).toMap)
    wordsMapbt
  }
 
  def transformVec(rdd: RDD[(String, Set[String], String)], ss: SparkSession, mp: Broadcast[Map[String, Int]]) = {
    import ss.sqlContext.implicits._
    val indexDF = rdd.map { x => x._1 }.distinct().zipWithUniqueId().toDF("id", "index")
    val outRDD = rdd.toDF("id", "keywords", "from")
      .join(indexDF, "id")
      .select("index", "keywords", "from")
      .rdd
      .map {
        case Row(index: Long, keywords: collection.mutable.WrappedArray[String], from: String) =>
 
          val len = mp.value.size
 
          val arr1 = keywords.toArray.sorted.map {
            x =>
              mp.value(x)
          }
          val arr2 = arr1.map { x => 1.0 }
          (index, Vectors.sparse(len, arr1, arr2), from)
      }
    (indexDF, outRDD)
  }
<think>好的,用户想用Spark进行电影数据分析,并应用聚类算法。首先,我需要确定他们可能使用的数据类型。电影数据通常包括用户评分、电影属性(如类型、导演、演员)、时间戳等。聚类算法可能用于用户分群或电影分类,比如根据评分行为对用户进行分组,或者根据电影特征将电影分成不同的簇。 接下来,用户需要具体的实现方法。我应该考虑Spark的MLlib库,里面支持K-Means、Bisecting K-Means、Gaussian Mixture Model(GMM)等聚类算法。可能需要先进行数据预处理,比如特征提取、标准化,然后选择适合的算法。 数据预处理是关键步骤。电影评分数据可能需要处理成用户-电影评分矩阵,或者进行降维处理(如PCA)以减少维度。对于用户聚类,可能需要构建用户特征向量,如平均评分、偏好的电影类型等。对于电影聚类,可能需要基于类型、演员、导演等属性进行特征编码,或者使用协同过滤生成潜在特征。 然后,选择合适的聚类算法。K-Means是最常用的,但对初始中心敏感,且需要指定簇数。Bisecting K-Means是层次化的K-Means,可能更适合不同规模的簇。GMM假设数据符合高斯分布,适合更复杂的数据分布。DBSCAN可以自动确定簇数,适合噪声数据任意形状的簇,但Spark MLlib没有内置DBSCAN,可能需要第三方库或自己实现。 接下来,实现步骤需要详细说明。比如加载数据、特征处理、模型训练、评估优化。评估指标包括轮廓系数、Davies-Bouldin指数等。可能需要调整参数,如簇数、迭代次数、种子等。 用户可能还关心如何应用结果,比如推荐系统优化、市场细分等。要给出实际应用场景的例子,比如根据用户聚类结果进行个性化推荐,或者分析电影聚类发现潜在类型。 需要提醒用户注意的问题包括数据预处理的重要性,选择合适的特征算法,以及参数调优。例如,高维数据可能需要降维,类别特征需要编码,连续特征需要标准化。 可能还需要推荐一些优化方法,比如使用更高效的算法变种,或者调整Spark的配置参数以提高性能。例如,增加分区数、缓存中间数据等。 最后,生成相关问题,帮助用户深入思考,比如如何选择聚类算法、如何处理高维数据、如何评估聚类效果等。这些问题能引导用户进一步探索优化他们的分析流程。</think>### Spark电影数据分析聚类算法实现方法 #### 一、数据准备阶段 1. **数据加载** 使用Spark SQL或DataFrame API加载电影数据集,常见格式包括CSV/JSON/Parquet。示例数据字段: - 用户ID、电影ID、评分(1-5)、时间戳 - 电影元数据:标题、类型、上映年份 ```python from pyspark.sql import SparkSession spark = SparkSession.builder.appName("MovieClustering").getOrCreate() ratings_df = spark.read.csv("ratings.csv", header=True, inferSchema=True) movies_df = spark.read.csv("movies.csv", header=True, inferSchema=True) ``` 2. **特征工程** 将分类特征转换为数值型特征- 对电影类型进行多热编码(Multi-hot Encoding) - 对用户评分进行聚合统计(平均分、评分次数) - 使用TF-IDF处理文本特征(如电影标题中的关键词) ```python from pyspark.ml.feature import StringIndexer, OneHotEncoder genre_encoder = OneHotEncoder(inputCol="genreIndex", outputCol="genreVec") ``` #### 二、算法选择与实现 1. **K-Means聚类** 适合用户行为分析场景,如根据评分模式划分用户群体: ```python from pyspark.ml.clustering import KMeans kmeans = KMeans(featuresCol='scaledFeatures', k=5, seed=42) model = kmeans.fit(preprocessed_data) ``` 2. **Bisecting K-Means** 层次化聚类算法,适合处理大规模数据: ```python from pyspark.ml.clustering import BisectingKMeans bkm = BisectingKMeans(featuresCol='features', k=4, minDivisibleClusterSize=2.0) ``` 3. **混合模型** 使用GMM处理复杂分布特征: ```python from pyspark.ml.clustering import GaussianMixture gmm = GaussianMixture(featuresCol="features", k=3) ``` #### 三、关键实现步骤 1. **特征标准化** 使用`StandardScaler`处理数值型特征: ```python from pyspark.ml.feature import StandardScaler scaler = StandardScaler(inputCol="features", outputCol="scaledFeatures") ``` 2. **降维处理** 对高维特征使用PCA: ```python from pyspark.ml.feature import PCA pca = PCA(k=10, inputCol="scaledFeatures", outputCol="pcaFeatures") ``` 3. **模型评估** 计算轮廓系数评估聚类质量: ```python from pyspark.ml.evaluation import ClusteringEvaluator evaluator = ClusteringEvaluator() silhouette = evaluator.evaluate(predictions) ``` #### 四、应用场景示例 1. **用户群体细分** 根据用户的评分模式(评分方差、偏好类型、活跃时段)划分用户群体,用于精准营销。 2. **电影内容聚类** 结合元数据特征(类型、演员、导演)隐含特征(协同过滤生成的潜在因子),发现潜在的电影类别。 3. **异常检测** 通过离群点检测识别异常评分行为或数据质量问题。 #### 五、注意事项 1. **特征选择**优先选择与业务目标强相关的特征 2. **数据预处理**需处理缺失值异常值 3. **参数调优**使用`CrossValidator`进行超参数优化 4. **计算资源**合理设置`numPartitions`参数控制并行度
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值