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)