原理
工业应用上的奇异值分解是近似的奇异值分解,而严格数学意义上的奇异值分解并非这样定义。
严格数学意义上的奇异值分解:
Mm×n=Xm×m∗Σm×n∗V′n×n工业应用上的奇异值分解SVD就是把一个矩阵A做如下转换:
Am×n≈Um×k∗Sk×k∗V′n×k
Um×k :在MLlib中叫右奇异矩阵(很疑惑,但在MLlib里确实如此),里边包含右奇异向量。由Spark源码:U, the matrix storing the right singular vectors.可知。Sk×k :奇异值对角方阵,MLlib中奇异值按降序排序,取top k,除奇异值所在的对角线,其他位置全为0。
V′n×k :在MLlib中叫左奇异矩阵,里边包含左奇异向量。
举例:
设矩阵 A2∗3
A=(4.05.02.06.03.01.0)则矩阵A的转置:列变行 AT3∗2
AT=⎛⎝⎜4.02.03.05.06.01.0⎞⎠⎟则 A∗AT
A∗AT=(29.035.035.062.0)则 A∗AT 的特征值和特征向量:由 (A∗AT)Vi=λiRi 求得。
A∗AT 的特征值:
84.1943148278917,6.805685172108291
A∗AT 每一特征值对应的特征向量(列向):
-0.5355281357229256 0.8445173863510019
-0.8445173863510022,-0.5355281357229257
奇异值s:由 si=λi−−√ 求得奇异值对角方阵
S2∗2=(9.1757460093385160.00.02.608770816324863)右奇异向量:由 ui=Ri 求得右奇异矩阵U。 ui:U矩阵的第i列
U2∗2=(−0.5355281357229256−0.84451738635100220.8445173863510019−0.5355281357229257)左奇异向量:由 vi=1si∗AT∗ui 求得左奇异矩阵V。 vi:V矩阵的第i列
V3∗2=⎛⎝⎜−0.6936438157910113−0.6689549365582719−0.26712833932251350.26848999628726217−0.58423454912098840.7658871414947904⎞⎠⎟如:
1s1=19.175746009338516=0.10898296432598079
u1=DenseVector(−0.5355281357229256,−0.8445173863510022)
则由 v1=1s1∗AT∗u1 即可求得。
实战
import org.apache.spark.mllib.linalg.distributed.RowMatrix
import org.apache.spark.mllib.linalg.{Matrix, SingularValueDecomposition, Vector, Vectors}
import org.apache.spark.sql.{Row, SQLContext}
import org.apache.spark.{SparkContext, SparkConf}
/**
* A ~= U * S * V'
* 降低A的储存和运算空间,提高效率
*/
object SVDExample {
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("PCAExample").setMaster("local[8]")
val sc = new SparkContext(conf)
val sqlContext = new SQLContext(sc)
val data =Array(
Vectors.dense(4.0 , 2.0 , 3.0),
Vectors.dense(5.0 , 6.0 , 1.0))
// Array[Vector]转换成DataFrame
val df = sqlContext.createDataFrame(data.map(Tuple1.apply)).toDF("features")
// DataFrame转换成RDD
val df_To_rdd=df.select("features").map { case Row(v: Vector) => v}
// RDD转换成矩阵
// 矩阵的每一行分布式存储
val mat: RowMatrix = new RowMatrix(df_To_rdd)
// 奇异值分解
// def computeSVD(k: Int,computeU: Boolean = false,rCond: Double = 1e-9)
//k:取top k个奇异值
//computeU:是否计算矩阵U
//rCond:小于1.0E-9d的奇异值会被抛弃
val svd: SingularValueDecomposition[RowMatrix, Matrix] = mat.computeSVD(2,true)
// s奇异值向量
println(svd.s)
//[9.175746009338516,2.608770816324863]
// U右奇异矩阵
svd.U.rows.foreach(println)
// [-0.5355281357229256,0.8445173863510019]
// [-0.8445173863510022,-0.5355281357229257]
// V左奇异矩阵
println(svd.V)
// -0.6936438157910113 0.26848999628726217
// -0.6689549365582719 -0.5842345491209884
// -0.2671283393225135 0.7658871414947904
}
}
SVD的现实意义
以下部分来自吴军老师的数学之美。
如矩阵 A100万∗50万 100万篇文章,每篇文章50万个特征,该矩阵的总元素有5000亿个,储存量和计算量非常大。如果用SVD做矩阵分解, A100万∗50万≈U100万∗100∗S100∗100∗V′50万×100 ,既把A近似的表示为3个矩阵 U、S、V′ ,总元素不超过1.5亿,大大减少了储存量和计算量。
也达到了降维的目的。