【问题标题】:Handle large sized matrix in Java在 Java 中处理大型矩阵
【发布时间】:2012-01-30 11:12:12
【问题描述】:

我目前需要对大小为 48K x 50K 的矩阵进行奇异值分解。

我尝试过 JAMA,但它只适用于行 > 列。 我尝试过 PCOLT、JBLAS,但是当 rows*columns > MAX_INT

时它们返回错误

有什么建议吗?

对不起,如果我在上面的行中犯了任何错误。

提前非常感谢!

【问题讨论】:

  • 48k x 50k?!?!你知道那有多大吗?如果他们输入double,那至少是48k x 50k x 8 bytes = ~20 GB???
  • 看起来你已经开始了私人曼哈顿项目;)这是一个非常非常大的矩阵,我怀疑你是否找到了现成的东西甚至 OSS 来处理这个
  • 我使用浮点数,我的 RAM 大约是 30GB。我曾想过缩小尺寸,但不能。 :(
  • only works for rows > columns,为什么不镜像您的矩阵,使其大小为 50k x 48k?
  • 你可以使用 JAMA 并使用转置吗?

标签: java matrix


【解决方案1】:

对于非常大的内存块,我倾向于建议使用内存映射文件(也许这就是 R 为您所做的)您可以在 Java 中使用一些样板代码来执行此操作。不幸的是,Java 不直接支持一次映射超过 2 GB,因此您必须将其分成多个部分。

import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;

import java.io.Closeable;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

public class LargeDoubleMatrix implements Closeable {
    private static final int MAPPING_SIZE = 1 << 30;
    private final RandomAccessFile raf;
    private final int width;
    private final int height;
    private final List<MappedByteBuffer> mappings = new ArrayList<MappedByteBuffer>();

    public LargeDoubleMatrix(String filename, int width, int height) throws IOException {
        this.raf = new RandomAccessFile(filename, "rw");
        try {
            this.width = width;
            this.height = height;
            long size = 8L * width * height;
            for (long offset = 0; offset < size; offset += MAPPING_SIZE) {
                long size2 = Math.min(size - offset, MAPPING_SIZE);
                mappings.add(raf.getChannel().map(FileChannel.MapMode.READ_WRITE, offset, size2));
            }
        } catch (IOException e) {
            raf.close();
            throw e;
        }
    }

    protected long position(int x, int y) {
        return (long) y * width + x;
    }

    public int width() {
        return width;
    }

    public int height() {
        return height;
    }

    public double get(int x, int y) {
        assert x >= 0 && x < width;
        assert y >= 0 && y < height;
        long p = position(x, y) * 8;
        int mapN = (int) (p / MAPPING_SIZE);
        int offN = (int) (p % MAPPING_SIZE);
        return mappings.get(mapN).getDouble(offN);
    }

    public void set(int x, int y, double d) {
        assert x >= 0 && x < width;
        assert y >= 0 && y < height;
        long p = position(x, y) * 8;
        int mapN = (int) (p / MAPPING_SIZE);
        int offN = (int) (p % MAPPING_SIZE);
        mappings.get(mapN).putDouble(offN, d);
    }

    public void close() throws IOException {
        for (MappedByteBuffer mapping : mappings)
            clean(mapping);
        raf.close();
    }

    private void clean(MappedByteBuffer mapping) {
        if (mapping == null) return;
        Cleaner cleaner = ((DirectBuffer) mapping).cleaner();
        if (cleaner != null) cleaner.clean();
    }
}

有这个设置对角线值的测试。

@Test
public  void getSetMatrix() throws IOException {
    long start = System.nanoTime();
    final long used0 = usedMemory();
    LargeDoubleMatrix matrix = new LargeDoubleMatrix("/tmp/ldm.test", 48*1000, 50*1000);
    for(int i=0;i<matrix.width();i++)
        matrix.set(i,i,i);
    for(int i=0;i<matrix.width();i++)
        assertEquals(i, matrix.get(i,i), 0.0);
    long time = System.nanoTime() - start;
    final long used = usedMemory() - used0;
    if (used==0)
        System.err.println("You need to use -XX:-UsedTLAB to see small changes in memory usage.");
    System.out.printf("Setting the diagonal took %,d ms, Heap used is %,d KB%n", time/1000/1000, used/1024);
    matrix.close();
}

private long usedMemory() {
    return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}

打印(使用 -XX:-UseTLAB 运行时)

Setting the diagonal took 60 ms, Heap used is 55 KB

只创建实际使用的页面。这些文件看起来很大,但分配的空间是基于使用情况的。

$ ls -lh /tmp/ldm.test 
-rw-rw-r-- 1 peter peter 18G 2011-12-30 10:18 /tmp/ldm.test
$ du -sh /tmp/ldm.test 
222M    /tmp/ldm.test

【讨论】:

  • 对于正在尝试这个的人;当您尝试在 32 位 JRE 中运行测试时,测试将失败并出现 IOException(映射失败)。将尺寸更改为 10*1000、10*1000 或使用 64 位 JRE
  • @THelper 32 位 JVM 确实具有非常小的可用虚拟内存大小。我建议只在设备上使用 32 位 JVM。
【解决方案2】:

步骤 1. 使用数据库来保存它。
步骤 2. 使用多前端/并行算法。

This paper 调查大型 SVD 的 SOTA 方法。 3 个处理器上的 Lanzcos 算法在 32k X 32k 矩阵上只用了 10 多分钟,但只针对最小的奇异值。放气并重新提取连续的奇异值可能是可能的——我一直发现使用放气的 Power Iteration 对此很有用。

简而言之,使 M X M_T 和 M_T X M 并取特征向量和特征值来重构 SVD 矩阵。

如果您准备接受近似值,this other paper 只是处理近似算法的众多方法之一。许多是基于对列或具有最佳代表性的子矩阵的某种下采样,您可以在其中利用立方更小的部分的优势以及并行性。

显然,这些会带来一些失真,但也许您可以为您的结果平滑它。

最后,您确实需要使用 Strassen 的方法进行乘法运算。

【讨论】:

    【解决方案3】:

    我在执行 SVD 计算时遇到了类似的问题,我的经验是:不要在 Java 中这样做。有一些工具可以更有效地做到这一点。如果您真的需要 Java,您可能会考虑构建一个从代码内部调用该工具的接口。我最终使用了R。我通过将矩阵存储在一个文件中手动使用它,R 可以将其作为矩阵读取。

    顺便说一句,如果矩阵是sparse,则可以进行各种优化,以减少内存使用和输出文件的大小(如果您选择使用其中一个)。

    否则,请查看此线程以查看是否有帮助:Handle large data structure in Java

    【讨论】:

    • 非常感谢@Pieter。我会调查一下。
    • 顺便说一句,它一点也不稀疏,因为我之前应用了一些平滑,所以每个单元格都包含一个非零值
    猜你喜欢
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    • 2013-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-19
    相关资源
    最近更新 更多