CH9_高斯混合型(GMM)及其Spark实现

Spark自编程实现高斯混合模型(GMM)

1. EM算法简介

EM算法是一种迭代算法,用于含有隐含变量的概率模型参数的极大似然估计,或极大后验概率估计,EM算法由两步组成:E步,求期望;M步,求极大。

2. EM 算法步骤

输入:观测变量数据Y,隐变量数据Z,联合分布P(Y,Z|θ),条件分布P(Z|Y,θ)

输出:模型参数θ

1. 选 择 参 数 的 初 始 值 θ ( 0 ) , 开 始 迭 代 1. 选择参数的初始值 \theta^{(0)},开始迭代 1.θ(0),`

$2.E步:记\theta^{(i)} 为第i次迭代参数 \theta 的估计值,在第i+1次迭代E步,计算:

Q ( θ , θ ( i ) ) = E Z [ l o g P ( Y , Z ∣ θ ) ∣ Y , θ ( i ) ] = ∑ Z l o g P ( Y , Z ∣ θ ) P ( Z ∣ Y , θ ( i ) ) Q(\theta,\theta^{(i)}) = E_Z[logP(Y,Z|\theta)|Y,\theta^{(i)}]=\sum_ZlogP(Y,Z|\theta)P(Z|Y,\theta^{(i)}) Q(θ,θ(i))=EZ[logP(Y,Zθ)Y,θ(i)]=ZlogP(Y,Zθ)P(ZY,θ(i))

这 里 P ( Z ∣ Y , θ ( i ) 是 在 给 定 观 测 数 据 Y 和 当 前 的 参 数 估 计 θ ( i ) 下 隐 变 量 数 据 Z 的 条 件 概 率 分 布 这里 P(Z|Y,\theta^{(i)} 是在给定观测数据Y和当前的参数估计 \theta^{(i)} 下隐变量数据Z的条件概率分布 P(ZY,θ(i)Yθ(i)Z

3. M 步 : 求 使 Q ( θ , θ ( i ) ) 极 大 化 的 θ , 确 定 第 i + 1 次 迭 代 的 参 数 的 估 计 值 θ ( i + 1 ) 3. M步:求使Q(\theta,\theta^{(i)}) 极大化的\theta ,确定第i+1次迭代的参数的估计值 \theta^{(i+1)} 3.M使Q(θ,θ(i))θ,i+1θ(i+1)

θ ( i + 1 ) = a r g m a x ⏟ θ Q ( θ , θ ( i ) ) \theta^{(i+1)} = \underbrace{argmax}_{\theta}Q(\theta,\theta^{(i)}) θ(i+1)=θ argmaxQ(θ,θ(i))
4. 重 复 第 二 步 和 第 三 步 , 直 到 收 敛 4. 重复第二步和第三步,直到收敛 4.

3.EM算法在高斯混合模型(Gaussian Nisture Model)中的应用
高斯混合模型

高斯混合模型是指具有如下形式的概率分布模型:

P ( y ∣ θ ) = ∑ k = 1 K α k ϕ ( y ∣ θ k ) 其 中 , α k 是 系 数 , α k ≥ 0 , ∑ k = 1 K α k = 1 ; ϕ ( y ∣ θ k ) 是 高 斯 分 布 密 度 , θ k = ( μ k , σ k 2 ) P(y|\theta) =\sum_{k=1}^K \alpha_k\phi(y|\theta_k)\\ 其中,\alpha_k 是系数,\alpha_k \geq 0,\sum_{k=1}^K\alpha_k = 1;\phi(y|\theta_k)是高斯分布密度,\theta_k=(\mu_k,\sigma_k^2) P(yθ)=k=1Kαkϕ(yθk)αkαk0,k=1Kαk=1;ϕ(yθk)θk=(μk,σk2)

ϕ ( y ∣ θ k ) = 1 2 π σ k − e x p ( − ( y − μ k ) 2 2 σ k 2 ) \phi(y|\theta_k) = \frac{1}{\sqrt{ 2\pi }\sigma_k}-exp(\frac{-(y-\mu_k)^2}{2\sigma_k^2}) ϕ(yθk)=2π σk1exp(2σk2(yμk)2)
称为第k个分模型

4. 高斯混合模型参数估计的EM算法

输 入 : 观 测 数 据 y 1 , y 2 . . . , y n , 高 斯 混 合 模 型 输入:观测数据 y_1,y_2 ...,y_n,高斯混合模型 y1,y2...,yn

输 出 : 高 斯 混 合 模 型 参 数 输出:高斯混合模型参数

( 1 ) 取 参 数 的 初 始 值 开 始 迭 代 (1) 取参数的初始值开始迭代 (1)

( 2 ) E 步 : 依 据 当 前 模 型 参 数 , 计 算 分 模 型 k 对 观 测 数 据 y i 的 响 应 度 (2) E步:依据当前模型参数,计算分模型k对观测数据y_i 的响应度 (2)Ekyi

γ ^ j k = α k ϕ ( y i ∣ θ k ) ∑ k = 1 K α k ϕ ( y i ∣ θ k ) j = 1 , 2 , . . . , N ; k = 1 , 2 , . . . , K \hat \gamma_{jk} = \frac{\alpha_k \phi(y_i|\theta_k)}{\sum_{k=1}^K \alpha_k \phi(y_i|\theta_k)} \qquad \qquad j=1,2,...,N ;\qquad k=1,2,...,K γ^jk=k=1Kαkϕ(yiθk)αkϕ(yiθk)j=1,2,...,N;k=1,2,...,K

( 3 ) M 步 : 计 算 新 一 轮 迭 代 的 模 型 参 数 (3) M步:计算新一轮迭代的模型参数 (3)M

μ ^ k = ∑ j = 1 N γ ^ j k y j ∑ j = 1 N γ ^ j k , k = 1 , 2 , . . . , k \hat \mu_k = \frac{\sum_{j=1}^N \hat \gamma_{jk} y_j}{\sum{j=1}^N \hat \gamma{jk}}, \qquad k=1,2,...,k \\ μ^k=j=1Nγ^jkj=1Nγ^jkyj,k=1,2,...,k

σ ^ k 2 = ∑ j = 1 N γ ^ j k ( y i − μ k ) 2 ∑ j = 1 N γ ^ j k , k = 1 , 2... , K \hat \sigma_k^2 = \frac{\sum_{j=1}^N \hat \gamma_{jk} (y_i-\mu_k)^2}{\sum_{j=1}^N \hat \gamma_{jk}} ,\qquad k=1,2...,K σ^k2=j=1Nγ^jkj=1Nγ^jk(yiμk)2,k=1,2...,K

α ^ k = ∑ j = 1 N γ ^ j k N , k = 1 , 2... , K \hat \alpha_k = \frac{\sum_{j=1}^N \hat \gamma_{jk}}{N} , \qquad k=1,2...,K α^k=Nj=1Nγ^jk,k=1,2...,K

( 4 ) 重 复 第 二 步 和 第 三 步 , 知 道 收 敛 或 达 到 最 大 迭 代 次 数 (4) 重复第二步和第三步,知道收敛或达到最大迭代次数 (4)


Spark自编程实现GMM
package CH9_EM

import org.apache.spark.sql.{DataFrame, Row, SparkSession}
import org.apache.spark.sql.functions._
import org.apache.spark.ml.linalg.{DenseMatrix, DenseVector, Vector}

import scala.beans.BeanProperty
import breeze.linalg.{
  *,
  diag,
  DenseMatrix => denseMatrix,
  DenseVector => densevector
}
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.stat.distribution.MultivariateGaussian
import org.apache.spark.ml.util.Identifiable
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{IntegerType, StructType}

/**
  * Created by WZZC on 2020/5/17
  **/
case class GMMModel(data: DataFrame) {

  @BeanProperty var maxIter: Int = 40
  @BeanProperty var tol: Double = 1e-5
  @BeanProperty var k: Int = _
  @BeanProperty var fts: Array[String] = data.columns

  //  private val spark: SparkSession = data.sparkSession

  private val numFeatures = fts.length
  private val dataSize = data.count()

  // 初始化 alpha、 mu和sigma向量

  var alpha: Array[Double] = _

  var mu: Array[densevector[Double]] = _

  var cov: Array[denseMatrix[Double]] = _

  private def matrixArr: densevector[Double] =
    densevector(densevector.rand(numFeatures).toArray)
      .asInstanceOf[densevector[Double]]

  // 结果表的Schema信息
  val schemaOfResult: StructType = data.schema
    .add("clusterId", IntegerType) //增加一列表示类id的字段

  private var clusterDf: DataFrame = _
//  var clusterDf  = spark.createDataFrame(spark.sparkContext.emptyRDD[Row], schemaOfResult)

  private val ftsName: String = Identifiable.randomUID("GMMModel")

  /**
    * 数据特征转换
    *
    * @param dataFrame
    * @return
    */
  def dataTransForm(dataFrame: DataFrame) = {
    new VectorAssembler()
      .setInputCols(fts)
      .setOutputCol(ftsName)
      .transform(dataFrame)
  }

  /**
    * 计算多元高斯分布的概率密度函数
    *
    * @param vec 待计算样本
    * @param mus 均值向量
    * @param covs 协方差矩阵
    * @return
    */
  private def pdf(vec: densevector[Double],
                  mus: densevector[Double],
                  covs: denseMatrix[Double]) = {

    val muvec: DenseVector = new DenseVector(mus.data)
    val yvec: DenseVector = new DenseVector(vec.data)
    val covMatrix =
      new DenseMatrix(covs.rows, covs.cols, covs.data, covs.isTranspose)

    val mgaussian: MultivariateGaussian =
      new MultivariateGaussian(muvec, covMatrix)
    //    val mgaussian: MultivariateGaussian = MultivariateGaussian(mus, covs)
    //    使用breeze包的多元高斯分布会出现以下错误
    //    breeze.linalg.NotConvergedException
    mgaussian.pdf(yvec)

  }

  /**
    *
    * @return
    */
  private def gamakudf(mus: Array[densevector[Double]],
                       covs: Array[denseMatrix[Double]],
                       alphas: Array[Double]) =
    udf((vec: Vector) => {

      val tuples: Array[((densevector[Double], denseMatrix[Double]), Double)] =
        mus.zip(covs).zip(alphas)

      val featureVec = new densevector(vec.toArray)

      val gammak: Array[Double] = tuples.map(tp => {
        pdf(featureVec, tp._1._1, tp._1._2) * tp._2
      })

      val s = gammak.sum

      new DenseVector(gammak.map(x => (x / s).formatted("%.4f").toDouble))
 
    })

 

  private def pdfudf(mus: Array[densevector[Double]],
                     covs: Array[denseMatrix[Double]]) =
    udf((vec: Vector) => {

      val tuples = mus.zip(covs)

      val featureVec = new densevector(vec.toArray)

      val gammak: Array[Double] = tuples.map(tp => {
        pdf(featureVec, tp._1, tp._2)
      })

      new DenseVector(gammak)

    })

  /**
    *
    * @param matrix
    * @param side
    */
  private def matrixToVectors(matrix: denseMatrix[Double], side: String) = {

    val rows: Int = matrix.rows
    val cols: Int = matrix.cols

    side match {
      case "row" => {
        (0 until rows).toArray
          .map(i => {
            densevector((0 until cols).toArray.map(j => {
              matrix.valueAt(i, j)
            }))
          })
      }

      case "col" => {
        (0 until cols).toArray
          .map(i => {
            densevector((0 until rows).toArray.map(j => {
              matrix.valueAt(j, i)
            }))
          })
      }
    }

  }

  def fit = {

    var alphas: Array[Double] = new Array[Double](k).map(_ => 1.0 / k)

    var mus: Array[densevector[Double]] =
      Array.fill(k)(densevector.rand(numFeatures))

    var covs: Array[denseMatrix[Double]] =
      Array.fill(k)(diag(matrixArr))

    var i = 0
//    var cost = 0.0
//    var convergence = false //判断收敛,即代价函数变化小于阈值tol

    while (i < maxIter) {

      //  E步:根据当前模型参数,计算分模型k对观测数据yi的响应度
      val edf: DataFrame = dataTransForm(data)
        .withColumn("gammajk", gamakudf(mus, covs, alphas)(col(ftsName)))

      val gammajk: RDD[densevector[Double]] = edf
        .select("gammajk")
        .rdd
        .map(x => densevector(x.getAs[Vector](0).toArray))

      //  M步:计算新一轮迭代的模型参数
      val gammaMatrix: denseMatrix[Double] = gammajk
        .map(_.toDenseMatrix)
        .reduce((v1, v2) => denseMatrix.vertcat(v1, v2))
        .t

      val sumgammak: Array[Double] = gammajk
        .map(_.toArray)
        .reduce((a1, a2) => a1.zip(a2).map(x => x._1 + x._2))

      val dataRdd = dataTransForm(data)
        .select(ftsName)
        .rdd
        .persist()

      val gammaVectors: Array[densevector[Double]] =
        matrixToVectors(gammaMatrix, "row")

      val dfmatrix: denseMatrix[Double] = dataRdd
        .map(x => densevector(x.getAs[Vector](0).toArray))
        .map(_.toDenseMatrix)
        .reduce((v1, v2) => denseMatrix.vertcat(v1, v2))

      //
      // 更新sigmas
      covs = mus
        .zip(gammaVectors)
        .map(tp => {

          val ymusMatrix: denseMatrix[Double] = dataRdd
            .map(x => {
              val ym = densevector(x.getAs[Vector](0).toArray)
              ym - tp._1
            })
            .map(_.toDenseMatrix)
            .reduce((v1, v2) => denseMatrix.vertcat(v1, v2))

          (ymusMatrix(::, *) *:* tp._2).t * ymusMatrix

        })
        .zip(sumgammak)
        .map(tp => {
          tp._1.map(x => (x / tp._2).formatted("%.4f").toDouble)
        })

      
      // 更新 mus
      mus = matrixToVectors(gammaMatrix, "row")
        .map(vec => {
          (vec.toDenseMatrix * dfmatrix).toDenseVector
        })
        .zip(sumgammak)
        .map(tp => {
          tp._1.map(_ / tp._2)
        })

      // 更新 alphas
      alphas = sumgammak.map(_ / dataSize).map(_.formatted("%.4f").toDouble)

      i += 1
      if (i >= maxIter) clusterDf = edf
    }

    alpha = alphas
    mu = mus
    cov = covs

  }

  //TODO   predict
  def predict = {

    val predictUdf = udf((probability: Vector) => {
      val array = probability.toArray
      array.indexOf(array.max)
    })

    clusterDf
      .withColumnRenamed("gammajk", "probability")
      .withColumn("prediction", predictUdf(col("probability")))
      .drop(ftsName)

  }

}

 
模型测试
bject GMMModelRunner {

  def main(args: Array[String]): Unit = {

    val spark = SparkSession
      .builder()
      .appName(s"${this.getClass.getSimpleName}")
      .master("local[*]")
      .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
      .config("spark.shuffle.consolidateFiles", "true")
      .config("spark.io.compression.codec", "snappy")
      .getOrCreate()

    val dataset: DataFrame = spark.read
      .option("header", true)
      .option("inferSchema", true)
      .csv("F:\\DataSource\\gmm.txt")

    val fts = Array("f1", "f2", "f3")

    val model: GMMModel = new GMMModel(dataset)
    model.setFts(fts)
    model.setK(2)
    model.setMaxIter(5)

    model.fit

    val alphas = model.alpha
    val mus = model.mu
    val covs = model.cov

    println("================")
    alphas.foreach(a => println("alpha: " + a))
    println("================")
    covs.foreach(println)
    println("================")
    mus.foreach(println)

    model.predict.show(false)
    spark.stop()

  }

}
参考资料

《统计学习方法》

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值