@Grapes(
@Grab('org.apache.commons:commons-math3:3.6.1')
)
import org.apache.commons.math3.linear.*
def mat = new Array2DRowRealMatrix(4,3)
println mat
I have been reading Machine Learning in Action, which is a great book; however, all of the examples are in Python. While Python seems like a decent language, it is not one of my primary languages. With that in mind, I have been converting the Python examples into Groovy so that a few months from now when I come back and try to understand what I learned, I will be able to decipher the code.
What I have found is that, at least in the examples for this book, there are numerous Matrix-based operations. My Linear Algebra was a long time ago, but I think I remember some of the basics; however, it’s nice to have a Java-based implementation in the Apache Commons Math RealMatrix
implementations (there are others but this is what I have been focussing on). It took me a little time to get back up to speed, especially since the Python examples will have something like:
someMatrix = someMatrix * anotherMatrix + thirdMatrix * number
where the resulting matrix is the product of two matrices added to the product of a matrix and a scalar number. Conceptually, this boils down to:
Multiply someMatrix
by anotherMatrix
Multiply every element of thirdMatrix
by the scalar number
value
Add every element of the matrix in step 1 with the element at the same position of the matrix from step 2.
Not hard to grasp, and not even all that hard to code; however, this is something I’d rather push off to someone else’s implementation instead of writing it myself. That is where the Commons Math library comes into play. The RealMatrix
interface defines matrix support for double
values - there is also a more flexible FieldMatrix<T>
interface, but double
values work well as an example. Let’s start by setting up a simple Groovy script for playing with matrices. Create a file named matrix-math.groovy
and add the following to it:
@Grapes(
@Grab('org.apache.commons:commons-math3:3.6.1')
)
import org.apache.commons.math3.linear.*
def mat = new Array2DRowRealMatrix(4,3)
println mat
This script will download the artifacts for the Apache Commons Math library and create a simple RealMatrix
with 4 rows and 3 columns. It will then be printed to the console. When you run it, you should see
Array2DRowRealMatrix{{0.0,0.0,0.0},{0.0,0.0,0.0},{0.0,0.0,0.0},{0.0,0.0,0.0}}
which represents our empty 4x3 matrix. While this is not a bad representation, it would be nicer if we could get a better representation of the rows and columns of data for our poor human eyes. The library provides a RealMatrixFormat
for this. Add the following to the script:
def formatter = new RealMatrixFormat('{', '}', '{', '}', ',\n ', ',\t')
println formatter.format(mat)
Note that the println
line replaces the existing one. Now we get a better, more human-readable representation:
{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}
Interesting so far, but we would really like some data. With the existing matrix, you can add data in rows or columns by index, similar to an array:
mat.setRow(0, [1.0, 2.0, 3.0] as double[])
mat.setColumn(1, [9.0, 8.0, 7.0, 6.0] as double[])
Now when you run the code, notice that you get the first row and the second column populated with the provided data:
{{1, 9, 3}, {0, 8, 0}, {0, 7, 0}, {0, 6, 0}}
Also notice that the column data overwrote the row data we set for the second column (index 1). In our row data it was 2.0
, but the 9.0
value from the column was applied after and is the final value. The other main method of creating a matrix is by providing the data directly in the constructor. Say we want to create a matrix with the same dimensions, but with a sequential collection of values, such as:
1 2 3 4 5 6 7 8 9 10 11 12
You can do the following in the code:
def seqMat = new Array2DRowRealMatrix([
[1.0, 2.0, 3.0] as double[],
[4.0, 5.0, 6.0] as double[],
[7.0, 8.0, 9.0] as double[],
[10.0, 11.0, 12.0] as double[]
] as double[][])
println formatter.format(seqMat)
This code creates a matrix with an array of arrays, where the inner arrays are the rows of data. When printed out, you get the following:
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}
Now, let’s do some operations on our matrices. You can do common math operations on two matrices. Adding two matrices:
def sum = mat.add(seqMat)
println formatter.format(sum)
This gives you the element-by-element sum of the values and yields:
{{2, 11, 6}, {4, 13, 6}, {7, 15, 9}, {10, 17, 12}}
Subtracting one matrix from another:
def diff = seqMat.subtract(mat)
println formatter.format(diff)
Gives:
{{0, -7, 0}, {4, -3, 6}, {7, 1, 9}, {10, 5, 12}}
Multiplication of matrices is not what you might intuitively think it is, unless you are up on your Linear Algebra. Since there are whole wiki pages devoted to Matrix Multiplication, I won’t go into it here beyond stating that it can be done when you have square matrices (ours are not). Not being a tutorial on Linear Algebra, I am going to leave it at that. You can also multiply a matrix by a scalar number:
def prod = mat.scalarMultiply(2)
println formatter.format(prod)
Which multiplies every element by the given value and results in:
{{2, 18, 6}, {0, 16, 0}, {0, 14, 0}, {0, 12, 0}}
Similarly, there is a scalarAdd(double)
method.
Other useful operations may be performed on matrices. You can "transpose" the matrix:
def trans = seqMat.transpose()
println formatter.format(trans)
This rotates the values of the matrix to turn rows into columns, as in our example:
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}
becomes
{{1, 4, 7, 10}, {2, 5, 8, 11}, {3, 6, 9, 12}}
There are a handful of other built-in operations available to matrices that are probably useful if you know what you are doing, but at this point, I do not. Another useful construct is the set of "walker" methods that allow you to walk through the elements of the matrix in various ways, allowing you to modify the elements or simply read them. Let’s take our initial matrix as an example and multiply every element by 2.0
both in place and in an external collection.
For the in-place modification we need a RealMatrixChangingVisitor
class MultiplicationVisitor extends DefaultRealMatrixChangingVisitor {
double factor
double visit(int row, int column, double value){
value * factor
}
}
mat.walkInOptimizedOrder(new MultiplicationVisitor(factor:2.0))
println formatter.format(mat)
This visitor simply multiplies each value by the provided factor
and returns it, which will update the value in the matrix. The resulting matrix has the following:
{{2, 18, 6}, {0, 16, 0}, {0, 14, 0}, {0, 12, 0}}
You can also walk a matrix without the ability to change the internal values. This requires a RealMatrixPreservingVisitor
:
class CollectingVisitor extends DefaultRealMatrixPreservingVisitor {
List values = []
void visit(int row, int column, double value){
values << value
}
}
def collectingVisitor = new CollectingVisitor()
mat.walkInOptimizedOrder(collectingVisitor)
println collectingVisitor.values
In this case, the values are collected into a list and no matrix value is modified. You get the following result:
[2.0, 18.0, 6.0, 0.0, 16.0, 0.0, 0.0, 14.0, 0.0, 0.0, 12.0, 0.0]
This contains a list of all the values from our original matrix after the previous visitor has modified it.
Matrix operations can seem quite complicated; however, they are not bad with a helpful library. So far the Commons Math API seems pretty useful for these more advanced math concepts.
The entire script for this tutorial is provided below for completeness:
@Grapes(
@Grab('org.apache.commons:commons-math3:3.6.1')
)
import org.apache.commons.math3.linear.*
def formatter = new RealMatrixFormat('{', '}', '{', '}', ',\n ', ',\t')
def mat = new Array2DRowRealMatrix(4,3)
mat.setRow(0, [1.0, 2.0, 3.0] as double[])
mat.setColumn(1, [9.0, 8.0, 7.0, 6.0] as double[])
println formatter.format(mat)
println()
def seqMat = new Array2DRowRealMatrix([
[1.0, 2.0, 3.0] as double[],
[4.0, 5.0, 6.0] as double[],
[7.0, 8.0, 9.0] as double[],
[10.0, 11.0, 12.0] as double[]
] as double[][])
println formatter.format(seqMat)
println()
def sum = mat.add(seqMat)
println formatter.format(sum)
println()
def diff = seqMat.subtract(mat)
println formatter.format(diff)
println()
def prod = mat.scalarMultiply(2)
println formatter.format(prod)
println()
def trans = seqMat.transpose()
println formatter.format(trans)
println()
class MultiplicationVisitor extends DefaultRealMatrixChangingVisitor {
double factor
double visit(int row, int column, double value){
value * factor
}
}
mat.walkInOptimizedOrder(new MultiplicationVisitor(factor:2.0))
println formatter.format(mat)
println()
class CollectingVisitor extends DefaultRealMatrixPreservingVisitor {
List values = []
void visit(int row, int column, double value){
values << value
}
}
def collectingVisitor = new CollectingVisitor()
mat.walkInOptimizedOrder(collectingVisitor)
println collectingVisitor.values
println()