aboutsummaryrefslogtreecommitdiff
path: root/mllib
diff options
context:
space:
mode:
authorBurak Yavuz <brkyvz@gmail.com>2014-12-29 13:24:26 -0800
committerXiangrui Meng <meng@databricks.com>2014-12-29 13:24:26 -0800
commit02b55de3dce9a1fef806be13e5cefa0f39ea2fcc (patch)
tree0e3e2a60779921eddf03e776442faa4ec5cc9ffa /mllib
parent8d72341ab75a7fb138b056cfb4e21db42aca55fb (diff)
downloadspark-02b55de3dce9a1fef806be13e5cefa0f39ea2fcc.tar.gz
spark-02b55de3dce9a1fef806be13e5cefa0f39ea2fcc.tar.bz2
spark-02b55de3dce9a1fef806be13e5cefa0f39ea2fcc.zip
[SPARK-4409][MLlib] Additional Linear Algebra Utils
Addition of a very limited number of local matrix manipulation and generation methods that would be helpful in the further development for algorithms on top of BlockMatrix (SPARK-3974), such as Randomized SVD, and Multi Model Training (SPARK-1486). The proposed methods for addition are: For `Matrix` - map: maps the values in the matrix with a given function. Produces a new matrix. - update: the values in the matrix are updated with a given function. Occurs in place. Factory methods for `DenseMatrix`: - *zeros: Generate a matrix consisting of zeros - *ones: Generate a matrix consisting of ones - *eye: Generate an identity matrix - *rand: Generate a matrix consisting of i.i.d. uniform random numbers - *randn: Generate a matrix consisting of i.i.d. gaussian random numbers - *diag: Generate a diagonal matrix from a supplied vector *These methods already exist in the factory methods for `Matrices`, however for cases where we require a `DenseMatrix`, you constantly have to add `.asInstanceOf[DenseMatrix]` everywhere, which makes the code "dirtier". I propose moving these functions to factory methods for `DenseMatrix` where the putput will be a `DenseMatrix` and the factory methods for `Matrices` will call these functions directly and output a generic `Matrix`. Factory methods for `SparseMatrix`: - speye: Identity matrix in sparse format. Saves a ton of memory when dimensions are large, especially in Multi Model Training, where each row requires being multiplied by a scalar. - sprand: Generate a sparse matrix with a given density consisting of i.i.d. uniform random numbers. - sprandn: Generate a sparse matrix with a given density consisting of i.i.d. gaussian random numbers. - diag: Generate a diagonal matrix from a supplied vector, but is memory efficient, because it just stores the diagonal. Again, very helpful in Multi Model Training. Factory methods for `Matrices`: - Include all the factory methods given above, but return a generic `Matrix` rather than `SparseMatrix` or `DenseMatrix`. - horzCat: Horizontally concatenate matrices to form one larger matrix. Very useful in both Multi Model Training, and for the repartitioning of BlockMatrix. - vertCat: Vertically concatenate matrices to form one larger matrix. Very useful for the repartitioning of BlockMatrix. The names for these methods were selected from MATLAB Author: Burak Yavuz <brkyvz@gmail.com> Author: Xiangrui Meng <meng@databricks.com> Closes #3319 from brkyvz/SPARK-4409 and squashes the following commits: b0354f6 [Burak Yavuz] [SPARK-4409] Incorporated mengxr's code 04c4829 [Burak Yavuz] Merge pull request #1 from mengxr/SPARK-4409 80cfa29 [Xiangrui Meng] minor changes ecc937a [Xiangrui Meng] update sprand 4e95e24 [Xiangrui Meng] simplify fromCOO implementation 10a63a6 [Burak Yavuz] [SPARK-4409] Fourth pass of code review f62d6c7 [Burak Yavuz] [SPARK-4409] Modified genRandMatrix 3971c93 [Burak Yavuz] [SPARK-4409] Third pass of code review 75239f8 [Burak Yavuz] [SPARK-4409] Second pass of code review e4bd0c0 [Burak Yavuz] [SPARK-4409] Modified horzcat and vertcat 65c562e [Burak Yavuz] [SPARK-4409] Hopefully fixed Java Test d8be7bc [Burak Yavuz] [SPARK-4409] Organized imports 065b531 [Burak Yavuz] [SPARK-4409] First pass after code review a8120d2 [Burak Yavuz] [SPARK-4409] Finished updates to API according to SPARK-4614 f798c82 [Burak Yavuz] [SPARK-4409] Updated API according to SPARK-4614 c75f3cd [Burak Yavuz] [SPARK-4409] Added JavaAPI Tests, and fixed a couple of bugs d662f9d [Burak Yavuz] [SPARK-4409] Modified according to remote repo 83dfe37 [Burak Yavuz] [SPARK-4409] Scalastyle error fixed a14c0da [Burak Yavuz] [SPARK-4409] Initial commit to add methods
Diffstat (limited to 'mllib')
-rw-r--r--mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala570
-rw-r--r--mllib/src/test/java/org/apache/spark/mllib/linalg/JavaMatricesSuite.java163
-rw-r--r--mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala172
-rw-r--r--mllib/src/test/scala/org/apache/spark/mllib/util/TestingUtils.scala6
4 files changed, 868 insertions, 43 deletions
diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala
index 327366a1a3..5a7281ec6d 100644
--- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala
+++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala
@@ -17,9 +17,11 @@
package org.apache.spark.mllib.linalg
-import java.util.{Random, Arrays}
+import java.util.{Arrays, Random}
-import breeze.linalg.{Matrix => BM, DenseMatrix => BDM, CSCMatrix => BSM}
+import scala.collection.mutable.{ArrayBuilder => MArrayBuilder, HashSet => MHashSet, ArrayBuffer}
+
+import breeze.linalg.{CSCMatrix => BSM, DenseMatrix => BDM, Matrix => BM}
/**
* Trait for a local matrix.
@@ -80,6 +82,16 @@ sealed trait Matrix extends Serializable {
/** A human readable representation of the matrix */
override def toString: String = toBreeze.toString()
+
+ /** Map the values of this matrix using a function. Generates a new matrix. Performs the
+ * function on only the backing array. For example, an operation such as addition or
+ * subtraction will only be performed on the non-zero values in a `SparseMatrix`. */
+ private[mllib] def map(f: Double => Double): Matrix
+
+ /** Update all the values of this matrix using the function f. Performed in-place on the
+ * backing array. For example, an operation such as addition or subtraction will only be
+ * performed on the non-zero values in a `SparseMatrix`. */
+ private[mllib] def update(f: Double => Double): Matrix
}
/**
@@ -123,6 +135,122 @@ class DenseMatrix(val numRows: Int, val numCols: Int, val values: Array[Double])
}
override def copy = new DenseMatrix(numRows, numCols, values.clone())
+
+ private[mllib] def map(f: Double => Double) = new DenseMatrix(numRows, numCols, values.map(f))
+
+ private[mllib] def update(f: Double => Double): DenseMatrix = {
+ val len = values.length
+ var i = 0
+ while (i < len) {
+ values(i) = f(values(i))
+ i += 1
+ }
+ this
+ }
+
+ /** Generate a `SparseMatrix` from the given `DenseMatrix`. */
+ def toSparse(): SparseMatrix = {
+ val spVals: MArrayBuilder[Double] = new MArrayBuilder.ofDouble
+ val colPtrs: Array[Int] = new Array[Int](numCols + 1)
+ val rowIndices: MArrayBuilder[Int] = new MArrayBuilder.ofInt
+ var nnz = 0
+ var j = 0
+ while (j < numCols) {
+ var i = 0
+ val indStart = j * numRows
+ while (i < numRows) {
+ val v = values(indStart + i)
+ if (v != 0.0) {
+ rowIndices += i
+ spVals += v
+ nnz += 1
+ }
+ i += 1
+ }
+ j += 1
+ colPtrs(j) = nnz
+ }
+ new SparseMatrix(numRows, numCols, colPtrs, rowIndices.result(), spVals.result())
+ }
+}
+
+/**
+ * Factory methods for [[org.apache.spark.mllib.linalg.DenseMatrix]].
+ */
+object DenseMatrix {
+
+ /**
+ * Generate a `DenseMatrix` consisting of zeros.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @return `DenseMatrix` with size `numRows` x `numCols` and values of zeros
+ */
+ def zeros(numRows: Int, numCols: Int): DenseMatrix =
+ new DenseMatrix(numRows, numCols, new Array[Double](numRows * numCols))
+
+ /**
+ * Generate a `DenseMatrix` consisting of ones.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @return `DenseMatrix` with size `numRows` x `numCols` and values of ones
+ */
+ def ones(numRows: Int, numCols: Int): DenseMatrix =
+ new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(1.0))
+
+ /**
+ * Generate an Identity Matrix in `DenseMatrix` format.
+ * @param n number of rows and columns of the matrix
+ * @return `DenseMatrix` with size `n` x `n` and values of ones on the diagonal
+ */
+ def eye(n: Int): DenseMatrix = {
+ val identity = DenseMatrix.zeros(n, n)
+ var i = 0
+ while (i < n) {
+ identity.update(i, i, 1.0)
+ i += 1
+ }
+ identity
+ }
+
+ /**
+ * Generate a `DenseMatrix` consisting of i.i.d. uniform random numbers.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @param rng a random number generator
+ * @return `DenseMatrix` with size `numRows` x `numCols` and values in U(0, 1)
+ */
+ def rand(numRows: Int, numCols: Int, rng: Random): DenseMatrix = {
+ new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rng.nextDouble()))
+ }
+
+ /**
+ * Generate a `DenseMatrix` consisting of i.i.d. gaussian random numbers.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @param rng a random number generator
+ * @return `DenseMatrix` with size `numRows` x `numCols` and values in N(0, 1)
+ */
+ def randn(numRows: Int, numCols: Int, rng: Random): DenseMatrix = {
+ new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rng.nextGaussian()))
+ }
+
+ /**
+ * Generate a diagonal matrix in `DenseMatrix` format from the supplied values.
+ * @param vector a `Vector` that will form the values on the diagonal of the matrix
+ * @return Square `DenseMatrix` with size `values.length` x `values.length` and `values`
+ * on the diagonal
+ */
+ def diag(vector: Vector): DenseMatrix = {
+ val n = vector.size
+ val matrix = DenseMatrix.zeros(n, n)
+ val values = vector.toArray
+ var i = 0
+ while (i < n) {
+ matrix.update(i, i, values(i))
+ i += 1
+ }
+ matrix
+ }
}
/**
@@ -156,6 +284,8 @@ class SparseMatrix(
require(colPtrs.length == numCols + 1, "The length of the column indices should be the " +
s"number of columns + 1. Currently, colPointers.length: ${colPtrs.length}, " +
s"numCols: $numCols")
+ require(values.length == colPtrs.last, "The last value of colPtrs must equal the number of " +
+ s"elements. values.length: ${values.length}, colPtrs.last: ${colPtrs.last}")
override def toArray: Array[Double] = {
val arr = new Array[Double](numRows * numCols)
@@ -188,7 +318,7 @@ class SparseMatrix(
private[mllib] def update(i: Int, j: Int, v: Double): Unit = {
val ind = index(i, j)
- if (ind == -1){
+ if (ind == -1) {
throw new NoSuchElementException("The given row and column indices correspond to a zero " +
"value. Only non-zero elements in Sparse Matrices can be updated.")
} else {
@@ -197,6 +327,192 @@ class SparseMatrix(
}
override def copy = new SparseMatrix(numRows, numCols, colPtrs, rowIndices, values.clone())
+
+ private[mllib] def map(f: Double => Double) =
+ new SparseMatrix(numRows, numCols, colPtrs, rowIndices, values.map(f))
+
+ private[mllib] def update(f: Double => Double): SparseMatrix = {
+ val len = values.length
+ var i = 0
+ while (i < len) {
+ values(i) = f(values(i))
+ i += 1
+ }
+ this
+ }
+
+ /** Generate a `DenseMatrix` from the given `SparseMatrix`. */
+ def toDense(): DenseMatrix = {
+ new DenseMatrix(numRows, numCols, toArray)
+ }
+}
+
+/**
+ * Factory methods for [[org.apache.spark.mllib.linalg.SparseMatrix]].
+ */
+object SparseMatrix {
+
+ /**
+ * Generate a `SparseMatrix` from Coordinate List (COO) format. Input must be an array of
+ * (i, j, value) tuples. Entries that have duplicate values of i and j are
+ * added together. Tuples where value is equal to zero will be omitted.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @param entries Array of (i, j, value) tuples
+ * @return The corresponding `SparseMatrix`
+ */
+ def fromCOO(numRows: Int, numCols: Int, entries: Iterable[(Int, Int, Double)]): SparseMatrix = {
+ val sortedEntries = entries.toSeq.sortBy(v => (v._2, v._1))
+ val numEntries = sortedEntries.size
+ if (sortedEntries.nonEmpty) {
+ // Since the entries are sorted by column index, we only need to check the first and the last.
+ for (col <- Seq(sortedEntries.head._2, sortedEntries.last._2)) {
+ require(col >= 0 && col < numCols, s"Column index out of range [0, $numCols): $col.")
+ }
+ }
+ val colPtrs = new Array[Int](numCols + 1)
+ val rowIndices = MArrayBuilder.make[Int]
+ rowIndices.sizeHint(numEntries)
+ val values = MArrayBuilder.make[Double]
+ values.sizeHint(numEntries)
+ var nnz = 0
+ var prevCol = 0
+ var prevRow = -1
+ var prevVal = 0.0
+ // Append a dummy entry to include the last one at the end of the loop.
+ (sortedEntries.view :+ (numRows, numCols, 1.0)).foreach { case (i, j, v) =>
+ if (v != 0) {
+ if (i == prevRow && j == prevCol) {
+ prevVal += v
+ } else {
+ if (prevVal != 0) {
+ require(prevRow >= 0 && prevRow < numRows,
+ s"Row index out of range [0, $numRows): $prevRow.")
+ nnz += 1
+ rowIndices += prevRow
+ values += prevVal
+ }
+ prevRow = i
+ prevVal = v
+ while (prevCol < j) {
+ colPtrs(prevCol + 1) = nnz
+ prevCol += 1
+ }
+ }
+ }
+ }
+ new SparseMatrix(numRows, numCols, colPtrs, rowIndices.result(), values.result())
+ }
+
+ /**
+ * Generate an Identity Matrix in `SparseMatrix` format.
+ * @param n number of rows and columns of the matrix
+ * @return `SparseMatrix` with size `n` x `n` and values of ones on the diagonal
+ */
+ def speye(n: Int): SparseMatrix = {
+ new SparseMatrix(n, n, (0 to n).toArray, (0 until n).toArray, Array.fill(n)(1.0))
+ }
+
+ /**
+ * Generates the skeleton of a random `SparseMatrix` with a given random number generator.
+ * The values of the matrix returned are undefined.
+ */
+ private def genRandMatrix(
+ numRows: Int,
+ numCols: Int,
+ density: Double,
+ rng: Random): SparseMatrix = {
+ require(numRows > 0, s"numRows must be greater than 0 but got $numRows")
+ require(numCols > 0, s"numCols must be greater than 0 but got $numCols")
+ require(density >= 0.0 && density <= 1.0,
+ s"density must be a double in the range 0.0 <= d <= 1.0. Currently, density: $density")
+ val size = numRows.toLong * numCols
+ val expected = size * density
+ assert(expected < Int.MaxValue,
+ "The expected number of nonzeros cannot be greater than Int.MaxValue.")
+ val nnz = math.ceil(expected).toInt
+ if (density == 0.0) {
+ new SparseMatrix(numRows, numCols, new Array[Int](numCols + 1), Array[Int](), Array[Double]())
+ } else if (density == 1.0) {
+ val colPtrs = Array.tabulate(numCols + 1)(j => j * numRows)
+ val rowIndices = Array.tabulate(size.toInt)(idx => idx % numRows)
+ new SparseMatrix(numRows, numCols, colPtrs, rowIndices, new Array[Double](numRows * numCols))
+ } else if (density < 0.34) {
+ // draw-by-draw, expected number of iterations is less than 1.5 * nnz
+ val entries = MHashSet[(Int, Int)]()
+ while (entries.size < nnz) {
+ entries += ((rng.nextInt(numRows), rng.nextInt(numCols)))
+ }
+ SparseMatrix.fromCOO(numRows, numCols, entries.map(v => (v._1, v._2, 1.0)))
+ } else {
+ // selection-rejection method
+ var idx = 0L
+ var numSelected = 0
+ var j = 0
+ val colPtrs = new Array[Int](numCols + 1)
+ val rowIndices = new Array[Int](nnz)
+ while (j < numCols && numSelected < nnz) {
+ var i = 0
+ while (i < numRows && numSelected < nnz) {
+ if (rng.nextDouble() < 1.0 * (nnz - numSelected) / (size - idx)) {
+ rowIndices(numSelected) = i
+ numSelected += 1
+ }
+ i += 1
+ idx += 1
+ }
+ colPtrs(j + 1) = numSelected
+ j += 1
+ }
+ new SparseMatrix(numRows, numCols, colPtrs, rowIndices, new Array[Double](nnz))
+ }
+ }
+
+ /**
+ * Generate a `SparseMatrix` consisting of i.i.d. uniform random numbers. The number of non-zero
+ * elements equal the ceiling of `numRows` x `numCols` x `density`
+ *
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @param density the desired density for the matrix
+ * @param rng a random number generator
+ * @return `SparseMatrix` with size `numRows` x `numCols` and values in U(0, 1)
+ */
+ def sprand(numRows: Int, numCols: Int, density: Double, rng: Random): SparseMatrix = {
+ val mat = genRandMatrix(numRows, numCols, density, rng)
+ mat.update(i => rng.nextDouble())
+ }
+
+ /**
+ * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @param density the desired density for the matrix
+ * @param rng a random number generator
+ * @return `SparseMatrix` with size `numRows` x `numCols` and values in N(0, 1)
+ */
+ def sprandn(numRows: Int, numCols: Int, density: Double, rng: Random): SparseMatrix = {
+ val mat = genRandMatrix(numRows, numCols, density, rng)
+ mat.update(i => rng.nextGaussian())
+ }
+
+ /**
+ * Generate a diagonal matrix in `SparseMatrix` format from the supplied values.
+ * @param vector a `Vector` that will form the values on the diagonal of the matrix
+ * @return Square `SparseMatrix` with size `values.length` x `values.length` and non-zero
+ * `values` on the diagonal
+ */
+ def diag(vector: Vector): SparseMatrix = {
+ val n = vector.size
+ vector match {
+ case sVec: SparseVector =>
+ SparseMatrix.fromCOO(n, n, sVec.indices.zip(sVec.values).map(v => (v._1, v._1, v._2)))
+ case dVec: DenseVector =>
+ val entries = dVec.values.zipWithIndex
+ val nnzVals = entries.filter(v => v._1 != 0.0)
+ SparseMatrix.fromCOO(n, n, nnzVals.map(v => (v._2, v._2, v._1)))
+ }
+ }
}
/**
@@ -256,72 +572,250 @@ object Matrices {
* Generate a `DenseMatrix` consisting of zeros.
* @param numRows number of rows of the matrix
* @param numCols number of columns of the matrix
- * @return `DenseMatrix` with size `numRows` x `numCols` and values of zeros
+ * @return `Matrix` with size `numRows` x `numCols` and values of zeros
*/
- def zeros(numRows: Int, numCols: Int): Matrix =
- new DenseMatrix(numRows, numCols, new Array[Double](numRows * numCols))
+ def zeros(numRows: Int, numCols: Int): Matrix = DenseMatrix.zeros(numRows, numCols)
/**
* Generate a `DenseMatrix` consisting of ones.
* @param numRows number of rows of the matrix
* @param numCols number of columns of the matrix
- * @return `DenseMatrix` with size `numRows` x `numCols` and values of ones
+ * @return `Matrix` with size `numRows` x `numCols` and values of ones
*/
- def ones(numRows: Int, numCols: Int): Matrix =
- new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(1.0))
+ def ones(numRows: Int, numCols: Int): Matrix = DenseMatrix.ones(numRows, numCols)
/**
- * Generate an Identity Matrix in `DenseMatrix` format.
+ * Generate a dense Identity Matrix in `Matrix` format.
* @param n number of rows and columns of the matrix
- * @return `DenseMatrix` with size `n` x `n` and values of ones on the diagonal
+ * @return `Matrix` with size `n` x `n` and values of ones on the diagonal
*/
- def eye(n: Int): Matrix = {
- val identity = Matrices.zeros(n, n)
- var i = 0
- while (i < n){
- identity.update(i, i, 1.0)
- i += 1
- }
- identity
- }
+ def eye(n: Int): Matrix = DenseMatrix.eye(n)
+
+ /**
+ * Generate a sparse Identity Matrix in `Matrix` format.
+ * @param n number of rows and columns of the matrix
+ * @return `Matrix` with size `n` x `n` and values of ones on the diagonal
+ */
+ def speye(n: Int): Matrix = SparseMatrix.speye(n)
/**
* Generate a `DenseMatrix` consisting of i.i.d. uniform random numbers.
* @param numRows number of rows of the matrix
* @param numCols number of columns of the matrix
* @param rng a random number generator
- * @return `DenseMatrix` with size `numRows` x `numCols` and values in U(0, 1)
+ * @return `Matrix` with size `numRows` x `numCols` and values in U(0, 1)
*/
- def rand(numRows: Int, numCols: Int, rng: Random): Matrix = {
- new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rng.nextDouble()))
- }
+ def rand(numRows: Int, numCols: Int, rng: Random): Matrix =
+ DenseMatrix.rand(numRows, numCols, rng)
+
+ /**
+ * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @param density the desired density for the matrix
+ * @param rng a random number generator
+ * @return `Matrix` with size `numRows` x `numCols` and values in U(0, 1)
+ */
+ def sprand(numRows: Int, numCols: Int, density: Double, rng: Random): Matrix =
+ SparseMatrix.sprand(numRows, numCols, density, rng)
/**
* Generate a `DenseMatrix` consisting of i.i.d. gaussian random numbers.
* @param numRows number of rows of the matrix
* @param numCols number of columns of the matrix
* @param rng a random number generator
- * @return `DenseMatrix` with size `numRows` x `numCols` and values in N(0, 1)
+ * @return `Matrix` with size `numRows` x `numCols` and values in N(0, 1)
*/
- def randn(numRows: Int, numCols: Int, rng: Random): Matrix = {
- new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rng.nextGaussian()))
- }
+ def randn(numRows: Int, numCols: Int, rng: Random): Matrix =
+ DenseMatrix.randn(numRows, numCols, rng)
+
+ /**
+ * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers.
+ * @param numRows number of rows of the matrix
+ * @param numCols number of columns of the matrix
+ * @param density the desired density for the matrix
+ * @param rng a random number generator
+ * @return `Matrix` with size `numRows` x `numCols` and values in N(0, 1)
+ */
+ def sprandn(numRows: Int, numCols: Int, density: Double, rng: Random): Matrix =
+ SparseMatrix.sprandn(numRows, numCols, density, rng)
/**
* Generate a diagonal matrix in `DenseMatrix` format from the supplied values.
* @param vector a `Vector` tat will form the values on the diagonal of the matrix
- * @return Square `DenseMatrix` with size `values.length` x `values.length` and `values`
+ * @return Square `Matrix` with size `values.length` x `values.length` and `values`
* on the diagonal
*/
- def diag(vector: Vector): Matrix = {
- val n = vector.size
- val matrix = Matrices.eye(n)
- val values = vector.toArray
- var i = 0
- while (i < n) {
- matrix.update(i, i, values(i))
- i += 1
+ def diag(vector: Vector): Matrix = DenseMatrix.diag(vector)
+
+ /**
+ * Horizontally concatenate a sequence of matrices. The returned matrix will be in the format
+ * the matrices are supplied in. Supplying a mix of dense and sparse matrices will result in
+ * a sparse matrix. If the Array is empty, an empty `DenseMatrix` will be returned.
+ * @param matrices array of matrices
+ * @return a single `Matrix` composed of the matrices that were horizontally concatenated
+ */
+ def horzcat(matrices: Array[Matrix]): Matrix = {
+ if (matrices.isEmpty) {
+ return new DenseMatrix(0, 0, Array[Double]())
+ } else if (matrices.size == 1) {
+ return matrices(0)
+ }
+ val numRows = matrices(0).numRows
+ var hasSparse = false
+ var numCols = 0
+ matrices.foreach { mat =>
+ require(numRows == mat.numRows, "The number of rows of the matrices in this sequence, " +
+ "don't match!")
+ mat match {
+ case sparse: SparseMatrix => hasSparse = true
+ case dense: DenseMatrix => // empty on purpose
+ case _ => throw new IllegalArgumentException("Unsupported matrix format. Expected " +
+ s"SparseMatrix or DenseMatrix. Instead got: ${mat.getClass}")
+ }
+ numCols += mat.numCols
+ }
+ if (!hasSparse) {
+ new DenseMatrix(numRows, numCols, matrices.flatMap(_.toArray))
+ } else {
+ var startCol = 0
+ val entries: Array[(Int, Int, Double)] = matrices.flatMap {
+ case spMat: SparseMatrix =>
+ var j = 0
+ val colPtrs = spMat.colPtrs
+ val rowIndices = spMat.rowIndices
+ val values = spMat.values
+ val data = new Array[(Int, Int, Double)](values.length)
+ val nCols = spMat.numCols
+ while (j < nCols) {
+ var idx = colPtrs(j)
+ while (idx < colPtrs(j + 1)) {
+ val i = rowIndices(idx)
+ val v = values(idx)
+ data(idx) = (i, j + startCol, v)
+ idx += 1
+ }
+ j += 1
+ }
+ startCol += nCols
+ data
+ case dnMat: DenseMatrix =>
+ val data = new ArrayBuffer[(Int, Int, Double)]()
+ var j = 0
+ val nCols = dnMat.numCols
+ val nRows = dnMat.numRows
+ val values = dnMat.values
+ while (j < nCols) {
+ var i = 0
+ val indStart = j * nRows
+ while (i < nRows) {
+ val v = values(indStart + i)
+ if (v != 0.0) {
+ data.append((i, j + startCol, v))
+ }
+ i += 1
+ }
+ j += 1
+ }
+ startCol += nCols
+ data
+ }
+ SparseMatrix.fromCOO(numRows, numCols, entries)
+ }
+ }
+
+ /**
+ * Vertically concatenate a sequence of matrices. The returned matrix will be in the format
+ * the matrices are supplied in. Supplying a mix of dense and sparse matrices will result in
+ * a sparse matrix. If the Array is empty, an empty `DenseMatrix` will be returned.
+ * @param matrices array of matrices
+ * @return a single `Matrix` composed of the matrices that were vertically concatenated
+ */
+ def vertcat(matrices: Array[Matrix]): Matrix = {
+ if (matrices.isEmpty) {
+ return new DenseMatrix(0, 0, Array[Double]())
+ } else if (matrices.size == 1) {
+ return matrices(0)
+ }
+ val numCols = matrices(0).numCols
+ var hasSparse = false
+ var numRows = 0
+ matrices.foreach { mat =>
+ require(numCols == mat.numCols, "The number of rows of the matrices in this sequence, " +
+ "don't match!")
+ mat match {
+ case sparse: SparseMatrix =>
+ hasSparse = true
+ case dense: DenseMatrix =>
+ case _ => throw new IllegalArgumentException("Unsupported matrix format. Expected " +
+ s"SparseMatrix or DenseMatrix. Instead got: ${mat.getClass}")
+ }
+ numRows += mat.numRows
+
+ }
+ if (!hasSparse) {
+ val allValues = new Array[Double](numRows * numCols)
+ var startRow = 0
+ matrices.foreach { mat =>
+ var j = 0
+ val nRows = mat.numRows
+ val values = mat.toArray
+ while (j < numCols) {
+ var i = 0
+ val indStart = j * numRows + startRow
+ val subMatStart = j * nRows
+ while (i < nRows) {
+ allValues(indStart + i) = values(subMatStart + i)
+ i += 1
+ }
+ j += 1
+ }
+ startRow += nRows
+ }
+ new DenseMatrix(numRows, numCols, allValues)
+ } else {
+ var startRow = 0
+ val entries: Array[(Int, Int, Double)] = matrices.flatMap {
+ case spMat: SparseMatrix =>
+ var j = 0
+ val colPtrs = spMat.colPtrs
+ val rowIndices = spMat.rowIndices
+ val values = spMat.values
+ val data = new Array[(Int, Int, Double)](values.length)
+ while (j < numCols) {
+ var idx = colPtrs(j)
+ while (idx < colPtrs(j + 1)) {
+ val i = rowIndices(idx)
+ val v = values(idx)
+ data(idx) = (i + startRow, j, v)
+ idx += 1
+ }
+ j += 1
+ }
+ startRow += spMat.numRows
+ data
+ case dnMat: DenseMatrix =>
+ val data = new ArrayBuffer[(Int, Int, Double)]()
+ var j = 0
+ val nCols = dnMat.numCols
+ val nRows = dnMat.numRows
+ val values = dnMat.values
+ while (j < nCols) {
+ var i = 0
+ val indStart = j * nRows
+ while (i < nRows) {
+ val v = values(indStart + i)
+ if (v != 0.0) {
+ data.append((i + startRow, j, v))
+ }
+ i += 1
+ }
+ j += 1
+ }
+ startRow += nRows
+ data
+ }
+ SparseMatrix.fromCOO(numRows, numCols, entries)
}
- matrix
}
}
diff --git a/mllib/src/test/java/org/apache/spark/mllib/linalg/JavaMatricesSuite.java b/mllib/src/test/java/org/apache/spark/mllib/linalg/JavaMatricesSuite.java
new file mode 100644
index 0000000000..704d484d0b
--- /dev/null
+++ b/mllib/src/test/java/org/apache/spark/mllib/linalg/JavaMatricesSuite.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.mllib.linalg;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.io.Serializable;
+import java.util.Random;
+
+public class JavaMatricesSuite implements Serializable {
+
+ @Test
+ public void randMatrixConstruction() {
+ Random rng = new Random(24);
+ Matrix r = Matrices.rand(3, 4, rng);
+ rng.setSeed(24);
+ DenseMatrix dr = DenseMatrix.rand(3, 4, rng);
+ assertArrayEquals(r.toArray(), dr.toArray(), 0.0);
+
+ rng.setSeed(24);
+ Matrix rn = Matrices.randn(3, 4, rng);
+ rng.setSeed(24);
+ DenseMatrix drn = DenseMatrix.randn(3, 4, rng);
+ assertArrayEquals(rn.toArray(), drn.toArray(), 0.0);
+
+ rng.setSeed(24);
+ Matrix s = Matrices.sprand(3, 4, 0.5, rng);
+ rng.setSeed(24);
+ SparseMatrix sr = SparseMatrix.sprand(3, 4, 0.5, rng);
+ assertArrayEquals(s.toArray(), sr.toArray(), 0.0);
+
+ rng.setSeed(24);
+ Matrix sn = Matrices.sprandn(3, 4, 0.5, rng);
+ rng.setSeed(24);
+ SparseMatrix srn = SparseMatrix.sprandn(3, 4, 0.5, rng);
+ assertArrayEquals(sn.toArray(), srn.toArray(), 0.0);
+ }
+
+ @Test
+ public void identityMatrixConstruction() {
+ Matrix r = Matrices.eye(2);
+ DenseMatrix dr = DenseMatrix.eye(2);
+ SparseMatrix sr = SparseMatrix.speye(2);
+ assertArrayEquals(r.toArray(), dr.toArray(), 0.0);
+ assertArrayEquals(sr.toArray(), dr.toArray(), 0.0);
+ assertArrayEquals(r.toArray(), new double[]{1.0, 0.0, 0.0, 1.0}, 0.0);
+ }
+
+ @Test
+ public void diagonalMatrixConstruction() {
+ Vector v = Vectors.dense(1.0, 0.0, 2.0);
+ Vector sv = Vectors.sparse(3, new int[]{0, 2}, new double[]{1.0, 2.0});
+
+ Matrix m = Matrices.diag(v);
+ Matrix sm = Matrices.diag(sv);
+ DenseMatrix d = DenseMatrix.diag(v);
+ DenseMatrix sd = DenseMatrix.diag(sv);
+ SparseMatrix s = SparseMatrix.diag(v);
+ SparseMatrix ss = SparseMatrix.diag(sv);
+
+ assertArrayEquals(m.toArray(), sm.toArray(), 0.0);
+ assertArrayEquals(d.toArray(), sm.toArray(), 0.0);
+ assertArrayEquals(d.toArray(), sd.toArray(), 0.0);
+ assertArrayEquals(sd.toArray(), s.toArray(), 0.0);
+ assertArrayEquals(s.toArray(), ss.toArray(), 0.0);
+ assertArrayEquals(s.values(), ss.values(), 0.0);
+ assert(s.values().length == 2);
+ assert(ss.values().length == 2);
+ assert(s.colPtrs().length == 4);
+ assert(ss.colPtrs().length == 4);
+ }
+
+ @Test
+ public void zerosMatrixConstruction() {
+ Matrix z = Matrices.zeros(2, 2);
+ Matrix one = Matrices.ones(2, 2);
+ DenseMatrix dz = DenseMatrix.zeros(2, 2);
+ DenseMatrix done = DenseMatrix.ones(2, 2);
+
+ assertArrayEquals(z.toArray(), new double[]{0.0, 0.0, 0.0, 0.0}, 0.0);
+ assertArrayEquals(dz.toArray(), new double[]{0.0, 0.0, 0.0, 0.0}, 0.0);
+ assertArrayEquals(one.toArray(), new double[]{1.0, 1.0, 1.0, 1.0}, 0.0);
+ assertArrayEquals(done.toArray(), new double[]{1.0, 1.0, 1.0, 1.0}, 0.0);
+ }
+
+ @Test
+ public void sparseDenseConversion() {
+ int m = 3;
+ int n = 2;
+ double[] values = new double[]{1.0, 2.0, 4.0, 5.0};
+ double[] allValues = new double[]{1.0, 2.0, 0.0, 0.0, 4.0, 5.0};
+ int[] colPtrs = new int[]{0, 2, 4};
+ int[] rowIndices = new int[]{0, 1, 1, 2};
+
+ SparseMatrix spMat1 = new SparseMatrix(m, n, colPtrs, rowIndices, values);
+ DenseMatrix deMat1 = new DenseMatrix(m, n, allValues);
+
+ SparseMatrix spMat2 = deMat1.toSparse();
+ DenseMatrix deMat2 = spMat1.toDense();
+
+ assertArrayEquals(spMat1.toArray(), spMat2.toArray(), 0.0);
+ assertArrayEquals(deMat1.toArray(), deMat2.toArray(), 0.0);
+ }
+
+ @Test
+ public void concatenateMatrices() {
+ int m = 3;
+ int n = 2;
+
+ Random rng = new Random(42);
+ SparseMatrix spMat1 = SparseMatrix.sprand(m, n, 0.5, rng);
+ rng.setSeed(42);
+ DenseMatrix deMat1 = DenseMatrix.rand(m, n, rng);
+ Matrix deMat2 = Matrices.eye(3);
+ Matrix spMat2 = Matrices.speye(3);
+ Matrix deMat3 = Matrices.eye(2);
+ Matrix spMat3 = Matrices.speye(2);
+
+ Matrix spHorz = Matrices.horzcat(new Matrix[]{spMat1, spMat2});
+ Matrix deHorz1 = Matrices.horzcat(new Matrix[]{deMat1, deMat2});
+ Matrix deHorz2 = Matrices.horzcat(new Matrix[]{spMat1, deMat2});
+ Matrix deHorz3 = Matrices.horzcat(new Matrix[]{deMat1, spMat2});
+
+ assert(deHorz1.numRows() == 3);
+ assert(deHorz2.numRows() == 3);
+ assert(deHorz3.numRows() == 3);
+ assert(spHorz.numRows() == 3);
+ assert(deHorz1.numCols() == 5);
+ assert(deHorz2.numCols() == 5);
+ assert(deHorz3.numCols() == 5);
+ assert(spHorz.numCols() == 5);
+
+ Matrix spVert = Matrices.vertcat(new Matrix[]{spMat1, spMat3});
+ Matrix deVert1 = Matrices.vertcat(new Matrix[]{deMat1, deMat3});
+ Matrix deVert2 = Matrices.vertcat(new Matrix[]{spMat1, deMat3});
+ Matrix deVert3 = Matrices.vertcat(new Matrix[]{deMat1, spMat3});
+
+ assert(deVert1.numRows() == 5);
+ assert(deVert2.numRows() == 5);
+ assert(deVert3.numRows() == 5);
+ assert(spVert.numRows() == 5);
+ assert(deVert1.numCols() == 2);
+ assert(deVert2.numCols() == 2);
+ assert(deVert3.numCols() == 2);
+ assert(spVert.numCols() == 2);
+ }
+}
diff --git a/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala b/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala
index 322a0e9242..a35d0fe389 100644
--- a/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala
+++ b/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala
@@ -43,9 +43,9 @@ class MatricesSuite extends FunSuite {
test("sparse matrix construction") {
val m = 3
- val n = 2
+ val n = 4
val values = Array(1.0, 2.0, 4.0, 5.0)
- val colPtrs = Array(0, 2, 4)
+ val colPtrs = Array(0, 2, 2, 4, 4)
val rowIndices = Array(1, 2, 1, 2)
val mat = Matrices.sparse(m, n, colPtrs, rowIndices, values).asInstanceOf[SparseMatrix]
assert(mat.numRows === m)
@@ -53,6 +53,13 @@ class MatricesSuite extends FunSuite {
assert(mat.values.eq(values), "should not copy data")
assert(mat.colPtrs.eq(colPtrs), "should not copy data")
assert(mat.rowIndices.eq(rowIndices), "should not copy data")
+
+ val entries: Array[(Int, Int, Double)] = Array((2, 2, 3.0), (1, 0, 1.0), (2, 0, 2.0),
+ (1, 2, 2.0), (2, 2, 2.0), (1, 2, 2.0), (0, 0, 0.0))
+
+ val mat2 = SparseMatrix.fromCOO(m, n, entries)
+ assert(mat.toBreeze === mat2.toBreeze)
+ assert(mat2.values.length == 4)
}
test("sparse matrix construction with wrong number of elements") {
@@ -117,6 +124,142 @@ class MatricesSuite extends FunSuite {
assert(sparseMat.values(2) === 10.0)
}
+ test("toSparse, toDense") {
+ val m = 3
+ val n = 2
+ val values = Array(1.0, 2.0, 4.0, 5.0)
+ val allValues = Array(1.0, 2.0, 0.0, 0.0, 4.0, 5.0)
+ val colPtrs = Array(0, 2, 4)
+ val rowIndices = Array(0, 1, 1, 2)
+
+ val spMat1 = new SparseMatrix(m, n, colPtrs, rowIndices, values)
+ val deMat1 = new DenseMatrix(m, n, allValues)
+
+ val spMat2 = deMat1.toSparse()
+ val deMat2 = spMat1.toDense()
+
+ assert(spMat1.toBreeze === spMat2.toBreeze)
+ assert(deMat1.toBreeze === deMat2.toBreeze)
+ }
+
+ test("map, update") {
+ val m = 3
+ val n = 2
+ val values = Array(1.0, 2.0, 4.0, 5.0)
+ val allValues = Array(1.0, 2.0, 0.0, 0.0, 4.0, 5.0)
+ val colPtrs = Array(0, 2, 4)
+ val rowIndices = Array(0, 1, 1, 2)
+
+ val spMat1 = new SparseMatrix(m, n, colPtrs, rowIndices, values)
+ val deMat1 = new DenseMatrix(m, n, allValues)
+ val deMat2 = deMat1.map(_ * 2)
+ val spMat2 = spMat1.map(_ * 2)
+ deMat1.update(_ * 2)
+ spMat1.update(_ * 2)
+
+ assert(spMat1.toArray === spMat2.toArray)
+ assert(deMat1.toArray === deMat2.toArray)
+ }
+
+ test("horzcat, vertcat, eye, speye") {
+ val m = 3
+ val n = 2
+ val values = Array(1.0, 2.0, 4.0, 5.0)
+ val allValues = Array(1.0, 2.0, 0.0, 0.0, 4.0, 5.0)
+ val colPtrs = Array(0, 2, 4)
+ val rowIndices = Array(0, 1, 1, 2)
+
+ val spMat1 = new SparseMatrix(m, n, colPtrs, rowIndices, values)
+ val deMat1 = new DenseMatrix(m, n, allValues)
+ val deMat2 = Matrices.eye(3)
+ val spMat2 = Matrices.speye(3)
+ val deMat3 = Matrices.eye(2)
+ val spMat3 = Matrices.speye(2)
+
+ val spHorz = Matrices.horzcat(Array(spMat1, spMat2))
+ val spHorz2 = Matrices.horzcat(Array(spMat1, deMat2))
+ val spHorz3 = Matrices.horzcat(Array(deMat1, spMat2))
+ val deHorz1 = Matrices.horzcat(Array(deMat1, deMat2))
+
+ val deHorz2 = Matrices.horzcat(Array[Matrix]())
+
+ assert(deHorz1.numRows === 3)
+ assert(spHorz2.numRows === 3)
+ assert(spHorz3.numRows === 3)
+ assert(spHorz.numRows === 3)
+ assert(deHorz1.numCols === 5)
+ assert(spHorz2.numCols === 5)
+ assert(spHorz3.numCols === 5)
+ assert(spHorz.numCols === 5)
+ assert(deHorz2.numRows === 0)
+ assert(deHorz2.numCols === 0)
+ assert(deHorz2.toArray.length === 0)
+
+ assert(deHorz1.toBreeze.toDenseMatrix === spHorz2.toBreeze.toDenseMatrix)
+ assert(spHorz2.toBreeze === spHorz3.toBreeze)
+ assert(spHorz(0, 0) === 1.0)
+ assert(spHorz(2, 1) === 5.0)
+ assert(spHorz(0, 2) === 1.0)
+ assert(spHorz(1, 2) === 0.0)
+ assert(spHorz(1, 3) === 1.0)
+ assert(spHorz(2, 4) === 1.0)
+ assert(spHorz(1, 4) === 0.0)
+ assert(deHorz1(0, 0) === 1.0)
+ assert(deHorz1(2, 1) === 5.0)
+ assert(deHorz1(0, 2) === 1.0)
+ assert(deHorz1(1, 2) == 0.0)
+ assert(deHorz1(1, 3) === 1.0)
+ assert(deHorz1(2, 4) === 1.0)
+ assert(deHorz1(1, 4) === 0.0)
+
+ intercept[IllegalArgumentException] {
+ Matrices.horzcat(Array(spMat1, spMat3))
+ }
+
+ intercept[IllegalArgumentException] {
+ Matrices.horzcat(Array(deMat1, spMat3))
+ }
+
+ val spVert = Matrices.vertcat(Array(spMat1, spMat3))
+ val deVert1 = Matrices.vertcat(Array(deMat1, deMat3))
+ val spVert2 = Matrices.vertcat(Array(spMat1, deMat3))
+ val spVert3 = Matrices.vertcat(Array(deMat1, spMat3))
+ val deVert2 = Matrices.vertcat(Array[Matrix]())
+
+ assert(deVert1.numRows === 5)
+ assert(spVert2.numRows === 5)
+ assert(spVert3.numRows === 5)
+ assert(spVert.numRows === 5)
+ assert(deVert1.numCols === 2)
+ assert(spVert2.numCols === 2)
+ assert(spVert3.numCols === 2)
+ assert(spVert.numCols === 2)
+ assert(deVert2.numRows === 0)
+ assert(deVert2.numCols === 0)
+ assert(deVert2.toArray.length === 0)
+
+ assert(deVert1.toBreeze.toDenseMatrix === spVert2.toBreeze.toDenseMatrix)
+ assert(spVert2.toBreeze === spVert3.toBreeze)
+ assert(spVert(0, 0) === 1.0)
+ assert(spVert(2, 1) === 5.0)
+ assert(spVert(3, 0) === 1.0)
+ assert(spVert(3, 1) === 0.0)
+ assert(spVert(4, 1) === 1.0)
+ assert(deVert1(0, 0) === 1.0)
+ assert(deVert1(2, 1) === 5.0)
+ assert(deVert1(3, 0) === 1.0)
+ assert(deVert1(3, 1) === 0.0)
+ assert(deVert1(4, 1) === 1.0)
+
+ intercept[IllegalArgumentException] {
+ Matrices.vertcat(Array(spMat1, spMat2))
+ }
+
+ intercept[IllegalArgumentException] {
+ Matrices.vertcat(Array(deMat1, spMat2))
+ }
+ }
+
test("zeros") {
val mat = Matrices.zeros(2, 3).asInstanceOf[DenseMatrix]
assert(mat.numRows === 2)
@@ -162,4 +305,29 @@ class MatricesSuite extends FunSuite {
assert(mat.numCols === 2)
assert(mat.values.toSeq === Seq(1.0, 0.0, 0.0, 2.0))
}
+
+ test("sprand") {
+ val rng = mock[Random]
+ when(rng.nextInt(4)).thenReturn(0, 1, 1, 3, 2, 2, 0, 1, 3, 0)
+ when(rng.nextDouble()).thenReturn(1.0, 2.0, 3.0, 4.0, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0)
+ val mat = SparseMatrix.sprand(4, 4, 0.25, rng)
+ assert(mat.numRows === 4)
+ assert(mat.numCols === 4)
+ assert(mat.rowIndices.toSeq === Seq(3, 0, 2, 1))
+ assert(mat.values.toSeq === Seq(1.0, 2.0, 3.0, 4.0))
+ val mat2 = SparseMatrix.sprand(2, 3, 1.0, rng)
+ assert(mat2.rowIndices.toSeq === Seq(0, 1, 0, 1, 0, 1))
+ assert(mat2.colPtrs.toSeq === Seq(0, 2, 4, 6))
+ }
+
+ test("sprandn") {
+ val rng = mock[Random]
+ when(rng.nextInt(4)).thenReturn(0, 1, 1, 3, 2, 2, 0, 1, 3, 0)
+ when(rng.nextGaussian()).thenReturn(1.0, 2.0, 3.0, 4.0)
+ val mat = SparseMatrix.sprandn(4, 4, 0.25, rng)
+ assert(mat.numRows === 4)
+ assert(mat.numCols === 4)
+ assert(mat.rowIndices.toSeq === Seq(3, 0, 2, 1))
+ assert(mat.values.toSeq === Seq(1.0, 2.0, 3.0, 4.0))
+ }
}
diff --git a/mllib/src/test/scala/org/apache/spark/mllib/util/TestingUtils.scala b/mllib/src/test/scala/org/apache/spark/mllib/util/TestingUtils.scala
index 30b906aaa3..e957fa5d25 100644
--- a/mllib/src/test/scala/org/apache/spark/mllib/util/TestingUtils.scala
+++ b/mllib/src/test/scala/org/apache/spark/mllib/util/TestingUtils.scala
@@ -178,17 +178,17 @@ object TestingUtils {
implicit class MatrixWithAlmostEquals(val x: Matrix) {
/**
- * When the difference of two vectors are within eps, returns true; otherwise, returns false.
+ * When the difference of two matrices are within eps, returns true; otherwise, returns false.
*/
def ~=(r: CompareMatrixRightSide): Boolean = r.fun(x, r.y, r.eps)
/**
- * When the difference of two vectors are within eps, returns false; otherwise, returns true.
+ * When the difference of two matrices are within eps, returns false; otherwise, returns true.
*/
def !~=(r: CompareMatrixRightSide): Boolean = !r.fun(x, r.y, r.eps)
/**
- * Throws exception when the difference of two vectors are NOT within eps;
+ * Throws exception when the difference of two matrices are NOT within eps;
* otherwise, returns true.
*/
def ~==(r: CompareMatrixRightSide): Boolean = {