The @matrix
module contains a single class @matrix, for doing matrix math.
A matrix is a 2D collection of numbers with a variety of practical applications:
representing systems of linear equations, representing the parameters of a neural network,
and projecting 3D coordinates onto 2D space for computer graphics.
Note that in matrix notation, rows come before columns. So a RxC matrix has R rows and C columns, and the position (r,c)
is row r and column c.
Using the @matrix
class has several advantages over using arrays of numbers.
First, common matrix operations are implemented. Second, the matrix class is more efficient for several reasons:
The matrix class operates on 32-bit floating point numbers instead of 64-bit numbers/arbitrary variants.
The matrix class is copy-on-modify, meaning that copying or slicing a matrix does not copy the underlying data until it is modified.
Lastly, the matrix class will use the GPU to accelerate large matrix multiplications.
This requires OpenGL with shaders, which is supported by default in Windows and MacOS,
and is supported in Linux if X is installed.
Create a new matrix.
If array is a 1D array, a matrix of size Rx1 is created, where R is the length of the array. If array is a 2D array, a matrix of size RxC is created, where R is the length of the array, and C is the length of each sub-array. It is assumed that all sub-arrays have the same length as the first sub-array.
The maximum matrix size is is 32,767 by 32,767.
If no parameters are specified, an invalid matrix is created.
#create the same matrix two different ways
m = matrix('[ 1 2 3, 4 5 6, 7 8 9]')
m = matrix([[1,2,3],[4,5,6],[7,8,9]])
Determine whether or not this matrix is valid.
Within this module, an invalid matrix is a 0x0 matrix used to pass errors.
#create an invalid matrix
m = matrix()
print 'valid =',m.valid() #false
Get the number of rows in this matrix.
Get the number of columns in this matrix.
Get the number of rows and columns in this matrix.
Convert this matrix into a string.
This is the same format taken by constructor.
Convert this matrix into an array.
If this is an RxC matrix, a 2D array is returned with length R, where each sub-array has length C. If this is an Rx1 matrix, a 1D array is returned with length R. This is the same format taken by constructor.
Slice this matrix.
Either rows or cols can be 0 to include all rows after i or j, respectively.
Slicing a matrix is efficient because the underlying data is not copied until it is modified.
m = matrix('[ 1 2 3, 4 5 6, 7 8 9]')
print m.slice(1,2,1,2).string() #[ 5 6, 8 9 ]
Get the ith row.
Uses :slice() under the hood.
m = matrix('[ 1 2 3, 4 5 6, 7 8 9]')
print m.row(1).string() #[ 4 5 6 ]
Get the jth column.
Uses :slice() under the hood.
m = matrix('[ 1 2 3, 4 5 6, 7 8 9]')
print m.col(1).string() #[ 2, 5, 8 ]
Get the value at row i column j.
Set the value at row i column j to value f.
Add this matrix to other.
Matrices of different sizes are processed in the following way:
Subtract other from this matrix.
Matrices of different sizes are processed in the following way:
Dot product this with other.
Matrices of different sizes are processed in the following way:
To multiply by a scalar, use :mul().
Divide the elements of this with the elements of other.
Matrices of different sizes are processed in the following way:
Fill this matrix with f.
Transpose this matrix.
Multiply this matrix with other.
It is an error to multiply two matrices where this.cols() != other.rows(). See Matrix multiplication on Wikipedia for details.
This will call :mul_cpu() or :mul_gpu() under the hood depending on the size of the matrices. In general, it is more efficient to multiply large matrices on the GPU and small matrices on the CPU.
The exception is that :mul_cpu() is always used on Raspberry Pi. On the Pi 0 and 3 systems, matrix multiplication is actually slower on the GPU than on the CPU for all sizes. However, :mul_gpu() remains enabled for benchmarking purposes.
Explicitly multiply this matrix with other on the CPU.
Can be used for benchmarks against :mul_gpu().
Explicitly multiply this matrix with other on the GPU.
Can be used for benchmarks against :mul_cpu().
if matrix.gpu(true)
p = matrix.random(1000,1000)
q = matrix.random(1000,1000)
t = os.ticks()
s = p.mul_gpu(q)
print 'mul_gpu():',(os.ticks()-t),'ms'
t = os.ticks()
r = p.mul_cpu(q)
print 'mul_cpu():',(os.ticks()-t),'ms'
print 'verification:',r.eq(s)
else
print 'GPU not supported!'
##
on Intel Macbook:
GL_VENDOR = Intel Inc.
GL_RENDERER = Intel(R) UHD Graphics 617
GL_VERSION = 2.1 INTEL-12.10.22
GLSL_VERSION = 1.20
mul_gpu(): 437 ms
mul_cpu(): 1308 ms
verification: true
on Raspberry Pi 3:
GL_VENDOR = VMware, Inc.
GL_RENDERER = llvmpipe (LLVM 9.0.1, 128 bits)
GL_VERSION = 3.1 Mesa 19.3.2
GLSL_VERSION = 1.40
mul_gpu(): 33910 ms
mul_cpu(): 9008 ms
verification: true
##
description
Floating-point mathematics is not perfectly precise, and so you may need to adjust tolerance to suit your needs.
Solve the system of linear equations represented by this matrix by using Gauss-Jordan elimination to turn this matrix into reduced row-echelon form.
Floating-point mathematics is not perfectly precise, and so you may need to adjust tolerance to suit your needs.
Find the inverse of this matrix, if any.
Floating-point mathematics is not perfectly precise, and so you may need to adjust tolerance to suit your needs.
Append other to the bottom of this matrix.
Adjoin other to the right side of this matrix.
Standardize the elements of this matrix such that for each column, the mean is 0 and the standard deviation is 1.
Normalize the elements of this matrix such that all values are within range [0,1). This is accomplished by finding the min/max values for each column, and scaling each value f to become (f-min)/(max-min).
Logarithmically scale all elements of this matrix.
This applies the following function to all elements of the array:
Compared to simply doing a logarithm of each element, this technique can handle values less than 1.
Apply the function fn to all elements of this matrix.
:abs(f)
return f.abs()
m = matrix('[ 1, -1, 2, -2]')
print m.apply(abs).string()
#[ 1, 1, 2, 2 ]
Sum the values in each column.
Get statistics for a certain column.
The returned object has the following fields:
m = matrix('[ 1 2 3, 4 5 6, 7 8 9]')
print m.stats(0)
##
{
"avg" : 4,
"std" : 2.44949,
"min" : 1,
"max" : 7
}
##
Adjoin a column of 1's to the left side of this matrix.
This is useful for certain mathematical calculations.
Fit this matrix to labels, assuming this matrix a matrix of samples, one per row. Specifically, solve the linear equation Xb*W = Y, where Xb is this.bias(), Y is labels, and W is an unknown matrix of weights.
This performs linear regression by linear least squares using the ordinary least squares method. For underdetermined systems, the pseudo-inverse formula is used.
Run this matrix using the weights from :fit(), assuming this matrix a matrix of samples, one per row. Specifically, run the linear equation Xb*W = P, where Xb is this.bias(), W is weights, and P is a matrix of predictions.
If this matrix is the same matrix called with :fit(), then the matrix P is a prediction of the labels Y, and Y-P is the prediction error.
#simple linear regression
x = matrix([ 1, 2, 3, 4])
y = matrix([ 3.1, 4.9, 7.1, 8.9]) #y = 2x+1 with some deviation
w = x.fit(y)
print w.string() #[ 1.1, 1.96 ] (close to [1, 2])
print x.run(w).string() #[ 3.06, 5.02, 6.98, 8.94 ] (close to y)
Save this matrix to path in a binary format.
The binary format is:
The binary format is designed to be portable and easy to load in other languages, like C++. The reason the row/column sizes appear at the end of the file is to discourage using them for buffer allocation; doing so would be a security risk (buffer underflow/overflow) when parsing a malformed file. Instead, parsers should first read the elements and then verify that the element count maches the given row/column sizes.
Load a matrix from a binary file generated by :save().
Whether or not a GPU is available.
If show_info is true, information about the GPU is printed to stdout, which is useful for debugging GPU support. See example for details.
print matrix.gpu(true)
##
GL_VENDOR = Intel Inc.
GL_RENDERER = Intel(R) UHD Graphics 617
GL_VERSION = 2.1 INTEL-12.10.22
GLSL_VERSION = 1.20
true
##
Vertically stack matrices.
Every matrix in a should have the same number of columns, otherwise it is an error.
Horizontally stack matrices.
Every matrix in a should have the same number of rows, otherwise it is an error.
Create an identity matrix.
An identity matrix identity matrix is just a square matrix where the diagonal is 1's and the rest of the elements are 0's.
Create a matrix of random values in range [0,1).