You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@systemds.apache.org by mb...@apache.org on 2020/10/10 14:14:09 UTC

[systemds] branch master updated: [SYSTEMDS-2680] New ALS built-in functions (als, alsCG, alsDS)

This is an automated email from the ASF dual-hosted git repository.

mboehm7 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/systemds.git


The following commit(s) were added to refs/heads/master by this push:
     new f2f6dbd  [SYSTEMDS-2680] New ALS built-in functions (als, alsCG, alsDS)
f2f6dbd is described below

commit f2f6dbdab9fd451cfe64093b1f3b8738c314fce7
Author: gabrielaozegovic <ga...@gmail.com>
AuthorDate: Sat Oct 10 14:55:29 2020 +0200

    [SYSTEMDS-2680] New ALS built-in functions (als, alsCG, alsDS)
    
    AMLS project SS2020.
    Closes #1070.
---
 scripts/builtin/als.dml                            |  63 ++++++++
 scripts/builtin/alsCG.dml                          | 167 +++++++++++++++++++++
 scripts/builtin/alsDS.dml                          | 155 +++++++++++++++++++
 .../java/org/apache/sysds/common/Builtins.java     |   3 +
 src/test/java/org/apache/sysds/test/TestUtils.java |  13 +-
 .../test/functions/builtin/BuiltinALSTest.java     |  91 +++++++++++
 src/test/scripts/functions/builtin/als.dml         |  33 ++++
 7 files changed, 519 insertions(+), 6 deletions(-)

diff --git a/scripts/builtin/als.dml b/scripts/builtin/als.dml
new file mode 100644
index 0000000..f332e62
--- /dev/null
+++ b/scripts/builtin/als.dml
@@ -0,0 +1,63 @@
+#-------------------------------------------------------------
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+#  
+# This script computes an approximate factorization of a low-rank matrix X into two matrices U and V
+# using different implementations of the Alternating-Least-Squares (ALS) algorithm.
+# Matrices U and V are computed by minimizing a loss function (with regularization).
+#
+# INPUT   PARAMETERS:
+# ---------------------------------------------------------------------------------------------
+# NAME    TYPE     DEFAULT  MEANING
+# ---------------------------------------------------------------------------------------------
+# X       String   ---      Location to read the input matrix X to be factorized
+# rank    Int      10       Rank of the factorization
+# reg     String   "L2"	    Regularization: 
+#                           "L2" = L2 regularization;
+#                              f (U, V) = 0.5 * sum (W * (U %*% V - X) ^ 2)
+#                                       + 0.5 * lambda * (sum (U ^ 2) + sum (V ^ 2))
+#                           "wL2" = weighted L2 regularization
+#                              f (U, V) = 0.5 * sum (W * (U %*% V - X) ^ 2)
+#                                       + 0.5 * lambda * (sum (U ^ 2 * row_nonzeros) 
+#                                       + sum (V ^ 2 * col_nonzeros))
+# lambda  Double   0.000001 Regularization parameter, no regularization if 0.0
+# maxi    Int      50       Maximum number of iterations
+# check   Boolean  TRUE     Check for convergence after every iteration, i.e., updating U and V once
+# thr     Double   0.0001   Assuming check is set to TRUE, the algorithm stops and convergence is declared 
+#                           if the decrease in loss in any two consecutive iterations falls below this threshold; 
+#                           if check is FALSE thr is ignored
+# ---------------------------------------------------------------------------------------------
+# OUTPUT:
+# 1- An m x r matrix U, where r is the factorization rank 
+# 2- An r x n matrix V
+
+m_als = function(Matrix[Double] X, Integer rank = 10, String reg = "L2", Double lambda = 0.000001,
+  Integer maxi = 50, Boolean check = TRUE, Double thr = 0.0001, Boolean verbose = TRUE)
+  return (Matrix[Double] U, Matrix[Double] V)
+{
+  N = 10000; # for large problems, use scalable alsCG
+  if( reg != "L2" | nrow(X) > N | ncol(X) > N )
+    [U, V] = alsCG(X=X, rank=rank, reg=reg, lambda=lambda,
+                   maxi=maxi, check=check, thr=thr, verbose=verbose);
+  else
+    [U, V] = alsDS(X=X, rank=rank, lambda=lambda, maxi=maxi,
+                   check=check, thr=thr, verbose=verbose);
+}
diff --git a/scripts/builtin/alsCG.dml b/scripts/builtin/alsCG.dml
new file mode 100644
index 0000000..b713b93
--- /dev/null
+++ b/scripts/builtin/alsCG.dml
@@ -0,0 +1,167 @@
+#-------------------------------------------------------------
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+#  
+# This script computes an approximate factorization of a low-rank matrix X into two matrices U and V
+# using the Alternating-Least-Squares (ALS) algorithm with conjugate gradient.
+# Matrices U and V are computed by minimizing a loss function (with regularization).
+#
+# INPUT   PARAMETERS:
+# ---------------------------------------------------------------------------------------------
+# NAME    TYPE     DEFAULT  MEANING
+# ---------------------------------------------------------------------------------------------
+# X       String   ---      Location to read the input matrix X to be factorized
+# rank    Int      10       Rank of the factorization
+# reg     String   "L2"     Regularization:
+#                           "L2" = L2 regularization;
+#                              f (U, V) = 0.5 * sum (W * (U %*% V - X) ^ 2)
+#                                       + 0.5 * lambda * (sum (U ^ 2) + sum (V ^ 2))
+#                           "wL2" = weighted L2 regularization
+#                              f (U, V) = 0.5 * sum (W * (U %*% V - X) ^ 2)
+#                                       + 0.5 * lambda * (sum (U ^ 2 * row_nonzeros)
+#                                       + sum (V ^ 2 * col_nonzeros))
+# lambda  Double   0.000001 Regularization parameter, no regularization if 0.0
+# maxi    Int      50       Maximum number of iterations
+# check   Boolean  TRUE     Check for convergence after every iteration, i.e., updating U and V once
+# thr     Double   0.0001   Assuming check is set to TRUE, the algorithm stops and convergence is declared
+#                           if the decrease in loss in any two consecutive iterations falls below this threshold;
+#                           if check is FALSE thr is ignored
+# ---------------------------------------------------------------------------------------------
+# OUTPUT: 
+# 1- An m x r matrix U, where r is the factorization rank
+# 2- An r x n matrix V
+
+
+m_alsCG = function(Matrix[Double] X, Integer rank = 10, String reg = "L2", Double lambda = 0.000001, Integer maxi = 50, Boolean check = TRUE, Double thr = 0.0001, Boolean verbose = TRUE)
+    return (Matrix[Double] U, Matrix[Double] V)
+{
+  r = rank;
+  max_iter = maxi;
+
+  ###### MAIN PART ######
+  m = nrow (X);
+  n = ncol (X);
+
+  # initializing factor matrices
+  U = rand (rows = m, cols = r, min = -0.5, max = 0.5); # mxr
+  V = rand (rows = n, cols = r, min = -0.5, max = 0.5); # nxr
+
+  W = (X != 0);
+
+  # check for regularization
+  row_nonzeros = matrix(0,rows=1,cols=1);
+  col_nonzeros = matrix(0,rows=1,cols=1);
+  if( reg == "L2" ) {
+    # Loss Function with L2:
+    # f (U, V) = 0.5 * sum (W * (U %*% V - X) ^ 2)
+    #          + 0.5 * lambda * (sum (U ^ 2) + sum (V ^ 2))
+    if( verbose )
+      print ("BEGIN ALS-CG SCRIPT WITH NONZERO SQUARED LOSS + L2 WITH LAMBDA - " + lambda);
+    row_nonzeros = matrix(1, nrow(W), 1);
+    col_nonzeros = matrix(1, ncol(W), 1);
+  }
+  else if( reg == "wL2" ) {
+    # Loss Function with weighted L2:
+    # f (U, V) = 0.5 * sum (W * (U %*% V - X) ^ 2)
+    #          + 0.5 * lambda * (sum (U ^ 2 * row_nonzeros) + sum (V ^ 2 * col_nonzeros))
+    if( verbose )
+      print ("BEGIN ALS-CG SCRIPT WITH NONZERO SQUARED LOSS + WEIGHTED L2 WITH LAMBDA - " + lambda);
+    row_nonzeros = rowSums(W);
+    col_nonzeros = t(colSums(W));
+  }
+  else {
+    stop ("wrong regularization! " + reg);
+  }
+
+  is_U = TRUE; # start optimizing U, alternated
+  maxinneriter = r ; # min (ncol (U), 15);
+
+  loss_init = 0.0; # only used if check is TRUE
+  if( check ) {
+    loss_init = 0.5 * sum( (X != 0) * (U %*% t(V) - X) ^ 2);
+    loss_init = loss_init + 0.5 * lambda * (sum (U ^ 2 * row_nonzeros) + sum (V ^ 2 * col_nonzeros));
+    if( verbose )
+      print ("-----   Initial train loss: " + loss_init + " -----");
+  }
+
+  it = 0;
+  converged = FALSE;
+  while( as.integer(it/2) < max_iter & ! converged ) {
+    it = it + 1;
+    if( is_U )
+      G = ((X != 0) * (U %*% t(V) - X)) %*% V + lambda * U * row_nonzeros;
+    else
+      G = t(t(U) %*% ((X != 0) * (U %*% t(V) - X))) + lambda * V * col_nonzeros;
+
+    R = -G;
+    S = R;
+    norm_G2 = sum (G ^ 2);
+    norm_R2 = norm_G2;
+
+    inneriter = 1;
+    tt = 0.000000001;
+    while( norm_R2 > tt * norm_G2 & inneriter <= maxinneriter ) {
+      if( is_U ) {
+        HS = (W * (S %*% t(V))) %*% V + lambda * S * row_nonzeros;
+        alpha = norm_R2 / sum (S * HS);
+        U = U + alpha * S;  # OK since U is not used in HS
+      }
+      else {
+        HS = t(t(U) %*% (W * (U %*% t(S)))) + lambda * S * col_nonzeros;
+        alpha = norm_R2 / sum (S * HS);
+        V = V + alpha * S;  # OK since V is not used in HS
+      }
+
+      R = R - alpha * HS;
+      old_norm_R2 = norm_R2;
+      norm_R2 = sum (R ^ 2);
+      S = R + (norm_R2 / old_norm_R2) * S;
+      inneriter = inneriter + 1;
+    }
+
+    is_U = ! is_U;
+
+    # check for convergence
+    if( check & (it%%2 == 0) ) {
+      loss_cur = 0.5 * sum( (X != 0) * (U %*% t(V) - X) ^ 2);
+      loss_cur = loss_cur + 0.5 * lambda * (sum (U ^ 2 * row_nonzeros) + sum (V ^ 2 * col_nonzeros));
+
+      loss_dec = (loss_init - loss_cur) / loss_init;
+      if( verbose )
+        print ("Train loss at iteration (" + as.integer(it/2) + "): " + loss_cur + " loss-dec " + loss_dec);
+      if( loss_dec >= 0 & loss_dec < thr | loss_init == 0 ) {
+        if( verbose )
+          print ("----- ALS-CG converged after " + as.integer(it/2) + " iterations!");
+        converged = TRUE;
+      }
+      loss_init = loss_cur;
+    }
+  }
+
+  if(verbose) {
+    if(check)
+      print ("----- Final train loss: " + loss_init + " -----");
+    if(!converged )
+      print ("Max iteration achieved but not converged!");
+  }
+
+  V = t(V);
+}
diff --git a/scripts/builtin/alsDS.dml b/scripts/builtin/alsDS.dml
new file mode 100644
index 0000000..dfb88de
--- /dev/null
+++ b/scripts/builtin/alsDS.dml
@@ -0,0 +1,155 @@
+#-------------------------------------------------------------
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+#
+# Alternating-Least-Squares (ALS) algorithm using a direct solve method for 
+# individual least squares problems (reg="L2"). This script computes an 
+# approximate factorization of a low-rank matrix V into two matrices L and R.
+# Matrices L and R are computed by minimizing a loss function (with regularization).
+#
+# INPUT   PARAMETERS:
+# ---------------------------------------------------------------------------------------------
+# NAME    TYPE     DEFAULT  MEANING
+# ---------------------------------------------------------------------------------------------
+# V       String   ---      Location to read the input matrix V to be factorized
+# L       String   ---      Location to write the factor matrix L
+# R       String   ---      Location to write the factor matrix R
+# rank    Int      10       Rank of the factorization
+# lambda  Double   0.000001 Regularization parameter, no regularization if 0.0
+# maxi    Int      50       Maximum number of iterations
+# check   Boolean  FALSE    Check for convergence after every iteration, i.e., updating L and R once
+# thr     Double   0.0001   Assuming check is set to TRUE, the algorithm stops and convergence is declared
+#                           if the decrease in loss in any two consecutive iterations falls below this threshold;
+#                           if check is FALSE thr is ignored
+# ---------------------------------------------------------------------------------------------
+# OUTPUT:
+# 1- An m x r matrix L, where r is the factorization rank
+# 2- An r x n matrix R
+#
+
+m_alsDS = function(Matrix[Double] X, Integer rank = 10, Double lambda = 0.000001, 
+  Integer maxi = 50, Boolean check = FALSE, Double thr = 0.0001, Boolean verbose = TRUE)
+  return (Matrix[Double] U, Matrix[Double] V)
+{
+  r = rank;
+  max_iter = maxi;
+
+  # check the input matrix V, if some rows or columns contain only zeros remove them from V
+  X_nonzero_ind = X != 0;
+  row_nonzeros = rowSums (X_nonzero_ind);
+  col_nonzeros = t (colSums (X_nonzero_ind));
+  orig_nonzero_rows_ind = row_nonzeros != 0;
+  orig_nonzero_cols_ind = col_nonzeros != 0;
+  num_zero_rows = nrow (X) - sum (orig_nonzero_rows_ind);
+  num_zero_cols = ncol (X) - sum (orig_nonzero_cols_ind);
+  if (num_zero_rows > 0) {
+    if( verbose )
+      print ("Matrix X contains empty rows! These rows will be removed.");
+    X = removeEmpty (target = X, margin = "rows");
+  }
+  if (num_zero_cols > 0) {
+    if( verbose )
+      print ("Matrix X contains empty columns! These columns will be removed.");
+    X = removeEmpty (target = X, margin = "cols");
+  }
+  if (num_zero_rows > 0 | num_zero_cols > 0) {
+    if( verbose )
+      print ("Recomputing nonzero rows and columns!");
+    X_nonzero_ind = X != 0;
+    row_nonzeros = rowSums (X_nonzero_ind);
+    col_nonzeros = t (colSums (X_nonzero_ind));
+  }
+
+  ###### MAIN PART ######
+  m = nrow (X);
+  n = ncol (X);
+
+  # initializing factor matrices
+  U = rand (rows = m, cols = r, min = -0.5, max = 0.5);
+  V = rand (rows = n, cols = r, min = -0.5, max = 0.5);
+
+  # initializing transformed matrices
+  Xt = t(X);
+
+  # check for regularization
+  if ( verbose )
+    print ("BEGIN ALS SCRIPT WITH NONZERO SQUARED LOSS + L2 WITH LAMBDA - " + lambda);
+
+  loss_init = 0.0; # only used if check is TRUE
+  if (check) {
+    loss_init = sum (X_nonzero_ind * (X - (U %*% t(V)))^2) 
+      + lambda * (sum ((U^2) * row_nonzeros) + sum ((V^2) * col_nonzeros));
+    if( verbose )
+      print ("----- Initial train loss: " + loss_init + " -----");
+  }
+
+  lambda_I = diag (matrix (lambda, rows = r, cols = 1));
+  it = 0;
+  converged = FALSE;
+  while ((it < max_iter) & (!converged)) {
+    it = it + 1;
+    # keep V fixed and update U
+    parfor (i in 1:m) {
+      V_nonzero_ind = t(X[i,] != 0);
+      V_nonzero = removeEmpty (target=V * V_nonzero_ind, margin="rows");
+      A1 = (t(V_nonzero) %*% V_nonzero) + (as.scalar(row_nonzeros[i,1]) * lambda_I); # coefficient matrix
+      U[i,] = t(solve (A1, t(X[i,] %*% V)));
+    }
+
+    # keep U fixed and update V
+    parfor (j in 1:n) {
+      U_nonzero_ind = t(Xt[j,] != 0)
+      U_nonzero = removeEmpty (target=U * U_nonzero_ind, margin="rows");
+      A2 = (t(U_nonzero) %*% U_nonzero) + (as.scalar(col_nonzeros[j,1]) * lambda_I); # coefficient matrix
+      V[j,] = t(solve (A2, t(Xt[j,] %*% U)));
+    }
+
+    # check for convergence
+    if (check) {
+      loss_cur = sum (X_nonzero_ind * (X - (U %*% t(V)))^2) 
+        + lambda * (sum ((U^2) * row_nonzeros) + sum ((V^2) * col_nonzeros));
+      loss_dec = (loss_init - loss_cur) / loss_init;
+      if( verbose )
+        print ("Train loss at iteration (X) " + it + ": " + loss_cur + " loss-dec " + loss_dec);
+      if (loss_dec >= 0 & loss_dec < thr | loss_init == 0) {
+        if( verbose )
+          print ("----- ALS converged after " + it + " iterations!");
+        converged = TRUE;
+      }
+      loss_init = loss_cur;
+    }
+  } # end of while loop
+
+  if(verbose) {
+    if(check)
+      print ("----- Final train loss: " + loss_init + " -----");
+    if(!converged )
+      print ("Max iteration achieved but not converged!");
+  }
+
+  # inject 0s in U if original X had empty rows
+  if (num_zero_rows > 0)
+    U = removeEmpty (target = diag (orig_nonzero_rows_ind), margin = "cols") %*% U;
+  # inject 0s in V if original X had empty rows
+  if (num_zero_cols > 0)
+    V = removeEmpty (target = diag (orig_nonzero_cols_ind), margin = "cols") %*% V;
+  V = t(V);
+}
diff --git a/src/main/java/org/apache/sysds/common/Builtins.java b/src/main/java/org/apache/sysds/common/Builtins.java
index f565124..92f6e48 100644
--- a/src/main/java/org/apache/sysds/common/Builtins.java
+++ b/src/main/java/org/apache/sysds/common/Builtins.java
@@ -40,6 +40,9 @@ public enum Builtins {
 	//builtin functions
 	ABS("abs", false),
 	ACOS("acos", false),
+	ALS("als", true),
+	ALS_CG("alsCG", true),
+	ALS_DS("alsDS", true),
 	ASIN("asin", false),
 	ATAN("atan", false),
 	AVG_POOL("avg_pool", false),
diff --git a/src/test/java/org/apache/sysds/test/TestUtils.java b/src/test/java/org/apache/sysds/test/TestUtils.java
index 6c10b01..d491df3 100644
--- a/src/test/java/org/apache/sysds/test/TestUtils.java
+++ b/src/test/java/org/apache/sysds/test/TestUtils.java
@@ -2339,14 +2339,15 @@ public class TestUtils
 	 * @return computed result
 	 */
 	public static double[][] performMatrixMultiplication(double[][] a, double[][] b) {
-		int rows = a.length;
-		int cols = b[0].length;
-		double[][] result = new double[rows][cols];
+		int m = a.length;
+		int n = a[0].length;
+		int l = b[0].length;
+		double[][] result = new double[m][l];
 
-		for (int i = 0; i < rows; i++) {
-			for (int j = 0; j < cols; j++) {
+		for (int i = 0; i < m; i++) {
+			for (int j = 0; j < l; j++) {
 				double value = 0;
-				for (int k = 0; k < a[i].length; k++) {
+				for (int k = 0; k < n; k++) {
 					value += (a[i][k] * b[k][j]);
 				}
 				result[i][j] = value;
diff --git a/src/test/java/org/apache/sysds/test/functions/builtin/BuiltinALSTest.java b/src/test/java/org/apache/sysds/test/functions/builtin/BuiltinALSTest.java
new file mode 100644
index 0000000..778813d
--- /dev/null
+++ b/src/test/java/org/apache/sysds/test/functions/builtin/BuiltinALSTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.sysds.test.functions.builtin;
+
+import org.apache.sysds.runtime.matrix.data.MatrixValue;
+import org.apache.sysds.test.AutomatedTestBase;
+import org.apache.sysds.test.TestConfiguration;
+import org.apache.sysds.test.TestUtils;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class BuiltinALSTest extends AutomatedTestBase {
+
+	private final static String TEST_NAME = "als";
+	private final static String TEST_DIR = "functions/builtin/";
+	private static final String TEST_CLASS_DIR = TEST_DIR + BuiltinALSTest.class.getSimpleName() + "/";
+
+	private final static double eps = 0.00001;
+	private final static int rows = 6;
+	private final static int cols = 6;
+
+	@Override
+	public void setUp() {
+		addTestConfiguration(TEST_NAME,new TestConfiguration(TEST_CLASS_DIR, TEST_NAME,new String[]{"B"}));
+	}
+
+	@Test
+	public void testALSCG() {
+		runtestALS("alsCG");
+	}
+	
+	@Test
+	public void testALSDS() {
+		runtestALS("alsDS");
+	}
+	
+	@Test
+	public void testALS() {
+		runtestALS("als");
+	}
+
+	private void runtestALS(String alg) {
+		loadTestConfiguration(getTestConfiguration(TEST_NAME));
+		String HOME = SCRIPT_DIR + TEST_DIR;
+		fullDMLScriptName = HOME + TEST_NAME + ".dml";
+		List<String> proArgs = new ArrayList<>();
+		proArgs.add("-args");
+		proArgs.add(input("X"));
+		proArgs.add(alg);
+		proArgs.add(output("U"));
+		proArgs.add(output("V"));
+		programArgs = proArgs.toArray(new String[proArgs.size()]);
+
+		double[][] X = {
+			{7,1,1,2,2,1},{7,2,2,3,2,1},
+			{7,3,1,4,1,1},{7,4,2,5,3,1},
+			{7,5,3,6,5,1}, {7,6,5,1,4,1}};
+		writeInputMatrixWithMTD("X", X, true);
+
+		runTest(true, EXCEPTION_NOT_EXPECTED, null, -1);
+
+		//compare expected results
+		HashMap<MatrixValue.CellIndex, Double> matrixV = readDMLMatrixFromHDFS("V");
+		HashMap<MatrixValue.CellIndex, Double> matrixU = readDMLMatrixFromHDFS("U");
+		double[][] doubleV = TestUtils.convertHashMapToDoubleArray(matrixV);
+		double[][] doubleU = TestUtils.convertHashMapToDoubleArray(matrixU);
+		double[][] result = TestUtils.performMatrixMultiplication(doubleU, doubleV);
+
+		TestUtils.compareMatrices(X, result, rows, cols, eps);
+	}
+}
diff --git a/src/test/scripts/functions/builtin/als.dml b/src/test/scripts/functions/builtin/als.dml
new file mode 100644
index 0000000..1e546d8
--- /dev/null
+++ b/src/test/scripts/functions/builtin/als.dml
@@ -0,0 +1,33 @@
+#-------------------------------------------------------------
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+X = read($1)
+alg = $2
+
+if( alg == "alsCG" )
+  [U, V] = alsCG(X=X)
+else if( alg == "alsDS" )
+  [U, V] = alsDS(X=X)
+else if( alg == "als" )
+  [U, V] = als(X=X)
+
+write(U, $3)
+write(V, $4)