该文介绍了java colt和commons-math3的一些矩阵计算API,并且使用colt库简单实现了基于法方程组法的最小二乘法,结构方程模型的梯度下降参数估计,广义混合效应模型(多层广义线性模型)的MCMC参数估计,实现和测试代码链接inuyasha11/stats
java矩阵计算概况
因为项目迁移需求,需要用java编写一些统计计算库。上网搜索了几个java矩阵库,找到了两个主流的,colt和commons-math3,colt库是CERN(欧洲核子研究组织)主导开发的,上次更新好像是10年前(?),所幸代码能支持java8,commons-math3一看名字就知道,系出apache软件基金会,里面除了矩阵库,还有其他的数学和统计方法,例如kmeans,遗传算法。这个两个库尝试了之后发现,真是难用,除了因为java缺乏操作符重载外,这两库API太少,很多轮子需要自己造,同时单线程速度也比底层调用blas,lpack的那些C++、python库慢,唯一的优点是纯java,可以很方便的多线程开发和后端系统对接,但是现在都搞微服务,谁还把计算模块嵌入业务模块啊。不过既然尝试,咱也写写使用介绍和心得吧,重点介绍下colt,稍微介绍下commons-math3。
矩阵构造
colt和commons-math3均支持两种形式的矩阵构造,dense矩阵和稀疏矩阵
对于dense矩阵,colt的实现是一维数组,commons-math3是二维数组,当矩阵元素大于4096的时候,commons-math3的dense矩阵工厂方法会创建BlockRealMatrix实例,BlockRealMatrix顾名思义,就是将矩阵分块存储,所以BlockRealMatrix虽然也是二维数组实现,但是数组的第一个索引仅仅是分块矩阵索引,第二个索引才是矩阵元素索引,本质上变成了和colt一样的一维数组实现。
对于稀疏矩阵,colt提供了稀疏矩阵类SparseDoubleMatrix2D,其存储矩阵元素的elements属性是一个hashmap,为了节省内存,colt自己实现了一个基于开放寻址方法的hashmap,在这个hashmap中,键为int类型,是矩阵的元素索引,值为double类型,是矩阵的元素值。当对稀疏矩阵set元素值的时候,如果元素值为0,则从hashmap中删除该元素的索引,若不为0,则在hashmap中存储键值对(索引,元素值),commons-math3的稀疏矩阵实现形式与colt类似,就不多赘述了。
colt和commoms-math3均支持通过传入矩阵的行数和列数构造初始化元素为0的矩阵
colt代码
import cern.colt.matrix.DoubleMatrix2D;
import cern.colt.matrix.impl.DenseDoubleMatrix2D;
DoubleMatrix2D mat = new DenseDoubleMatrix2D(3, 4);
System.out.println(mat.toString());
commons-math3代码
RealMatrix mat = new Array2DRowRealMatrix(4,4);
也可以通过传入二维数组构造矩阵
colt代码
double[][] matData = { {1d, 2d, 3d}, {4d, 5d, 6d}, {7d, 8d, 9d}};
DoubleMatrix2D mat = new DenseDoubleMatrix2D(matData);
System.out.println(mat.toString());
commons-math3代码
double[][] matData = { {1d, 2d, 3d}, {4d, 5d, 6d}, {7d, 8d, 9d}};
RealMatrix mat = new Array2DRowRealMatrix(matData);
也可以通过工厂模式创建矩阵实例
colt代码
DoubleFactory2D F = DoubleFactory2D.dense;
F.make(4,4);
这个工厂类还包含一些很有趣的静态方法,例如创建随机元素矩阵的方法random,合并两个矩阵的appendColumns方法和appendRows方法
DoubleFactory2D F = DoubleFactory2D.dense;
DoubleMatrix2D mat1 = F.random(10, 5);
DoubleMatrix2D mat2 = F.random(10, 3);
DoubleMatrix2D mat3 = F.appendColumns(mat1, mat2);
System.out.println(mat3);
commons-math3代码
RealMatrix mat = MatrixUtils.createRealMatrix(4, 4);
commons-math3的工厂方法会自动根据矩阵元素的大小创建Array2DRowRealMatrix类实例或BlockRealMatrix类实例,关于这两个类前面已经说明了一些情况,不多赘述了。
赋值和索引矩阵元素
colt
colt提供了get方法和getQuick方法读取矩阵中的单个元素值,还提供了set和setQuick方法设置矩阵中的单个元素值,get方法和getQuick方法的区别是get方法里加入了一些参数检查代码,set方法和setQuick方法同理,理论上getQuick和setQuick更快。
double[][] matData = { {1d, 2d, 3d}, {4d, 5d, 6d}, {7d, 8d, 9d}};
DoubleMatrix2D mat = new DenseDoubleMatrix2D(matData);
System.out.println(mat.get(0, 0));
mat.set(0, 0, 10d) ;
System.out.println(mat.get(0, 0));
commons-math3
commons-math3提供了getEntry和setEntry两个方法索引和赋值矩阵元素,内部实现和colt几乎一样
double[][] matData = { {1d, 2d, 3d}, {4d, 5d, 6d}, {7d, 8d, 9d}};
RealMatrix mat = new Array2DRowRealMatrix(matData);
double val = mat.getEntry(0, 0);
mat.setEntry(0, 0, val + 10d);
获得矩阵的行数和列数
colt代码
int rowSize = mat.rows();
int colSize = mat.rows();
commons-math3代码
int rowSize = mat.getRowDimension();
int colSize = mat.getColumnDimension();
复制矩阵
colt
在colt中,复制矩阵,只复制矩阵的形状,不复制元素,可以使用like方法
double[][] matData = { {1d, 2d, 3d}, {4d, 5d, 6d}};
DoubleMatrix2D mat1 = new DenseDoubleMatrix2D(matData);
DoubleMatrix2D mat2 = mat1.like();
System.out.println(mat2);
又复制矩阵的形状,又复制元素,可以使用copy方法
double[][] matData = { {1d, 2d, 3d}, {4d, 5d, 6d}};
DoubleMatrix2D mat1 = new DenseDoubleMatrix2D(matData);
DoubleMatrix2D mat2 = mat1.copy();
System.out.println(mat2);
commons-math3
commons-math3貌似只提供了copy方法,commons-math3的copy方法调用了System.arraycopy静态方法进行数组赋值
RealMatrix mat2 = mat1.copy();
矩阵赋值速度比较
运行100次取平均值,单位毫秒
矩阵乘法
colt