Python 将1.2GB边列表转换为稀疏矩阵

Python 将1.2GB边列表转换为稀疏矩阵,python,pandas,numpy,optimization,scipy,Python,Pandas,Numpy,Optimization,Scipy,我有一个1.2GB的文本文件中图形的边列表。我的ubuntu电脑有8GB内存。输入中的每一行看起来像 287111206 357850135 我想将其转换为稀疏邻接矩阵,并将其输出到文件中 我的数据的一些统计数据: Number of edges: around 62500000 Number of vertices: around 31250000 我以前也问过同样的问题,得到了很好的回答。问题是我不能让它工作 我第一次尝试在文件中加载np.loadtxt,但速度非常慢,占用了大量内存。因

我有一个1.2GB的文本文件中图形的边列表。我的ubuntu电脑有8GB内存。输入中的每一行看起来像

287111206 357850135
我想将其转换为稀疏邻接矩阵,并将其输出到文件中

我的数据的一些统计数据:

Number of edges: around 62500000
Number of vertices: around 31250000
我以前也问过同样的问题,得到了很好的回答。问题是我不能让它工作

我第一次尝试在文件中加载np.loadtxt,但速度非常慢,占用了大量内存。因此,我转而选择pandas.read_csv,它的速度非常快,但这也导致了它自身的问题。这是我当前的代码:

import pandas
import numpy as np
from scipy import sparse

data = pandas.read_csv("edges.txt", sep=" ", header= None, dtype=np.uint32)
A = data.as_matrix()
print type(A)
k1,k2,k3=np.unique(A,return_inverse=True,return_index=True)
rows,cols=k3.reshape(A.shape).T
M=sparse.coo_matrix((np.ones(rows.shape,int),(rows,cols)))
print type(M)
问题是熊猫数据帧<代码>数据非常庞大,我实际上是在一个低效率的环境中进行复制。然而,随着代码崩溃,情况更加糟糕

<type 'instancemethod'>
Traceback (most recent call last):
  File "make-sparse-matrix.py", line 13, in <module>
    rows,cols=k3.reshape(A.shape).T
AttributeError: 'function' object has no attribute 'shape'
raph@raph-desktop:~/python$ python make-sparse-matrix.py 
<type 'numpy.ndarray'>
Traceback (most recent call last):
  File "make-sparse-matrix.py", line 12, in <module>
    k1,k2,k3=np.unique(A,return_inverse=True,return_index=True)
  File "/usr/local/lib/python2.7/dist-packages/numpy/lib/arraysetops.py", line 209, in unique
    iflag = np.cumsum(flag) - 1
  File "/usr/local/lib/python2.7/dist-packages/numpy/core/fromnumeric.py", line 2115, in cumsum
    return cumsum(axis, dtype, out)
MemoryError
更新

我现在尝试了许多不同的方法,但都失败了。这里是一个总结

  • g=Graph.Read\u Ncol('edges.txt')
    一起使用。这使用了大量的RAM,使我的计算机崩溃
  • G=networkit.graphio.readGraph(“edges.txt”)一起使用,
    networkit.Format.EdgeList,分隔符=”,continuous=False)
    。这使用了大量的RAM,使我的计算机崩溃
  • 上面的代码在这个问题中使用了np.loadtxt(“edges.txt”)而不是pandas。这使用了大量的RAM,使我的计算机崩溃
  • 然后我编写了单独的代码,将所有顶点名称重新映射为1..V |的数字,其中| V |是顶点的总数。这将保存导入边列表的代码,使其不必构建映射顶点名称的表。我试着用这个:

  • 使用这个新的重新映射的边列表文件,我再次使用igraph和
    g=Graph.Read\u Edgelist(“edges contig.txt”)
    。尽管需要4GB的RAM(这比理论上应该的数量要多得多),但现在它仍然可以工作。然而,没有igraph函数可以从图中写出稀疏邻接矩阵。建议的解决方案是。不幸的是,这使用了大量的内存,使我的计算机崩溃
  • 使用重新映射的边缘列表文件,我使用networkit和
    G=networkit.readGraph(“edges contig.txt”,networkit.Format.EdgeListSpaceOne)
    。这也可以使用少于igraph所需4GB的内存。networkit还提供了一个编写Matlab文件的函数(这是scipy可以读取的稀疏邻接矩阵的一种形式)。然而,
    networkit.graphio.writeMat(G,“test.mat”)
    使用了大量RAM,使我的计算机崩溃
  • 最后,sascha的回答完成了,但大约需要40分钟。

    更新版本 正如评论中所指出的,该方法不适合您的用例。让我们做一些更改:

    • 使用pandas读取数据(而不是numpy:我很惊讶np.loadtxt的性能如此糟糕!)
    • 使用外部库实现更高效的内存方法(而不是字典)
    • 基本方法是相同的
    此方法将花费~45分钟(这很慢;但您可以对结果进行pickle/保存,因此只需执行一次)和~5 GB的内存来准备数据的稀疏矩阵,生成时使用:

    import random
    N = 62500000
    for i in xrange(N):
        print random.randint(10**8,10**9-1), random.randint(10**8,10**9-1)
    
    代码 第一版 这里有一个非常简单的非常低效的(关于时间和空间)代码来构建这个稀疏矩阵。我发布这段代码,因为我相信,如果在更大的应用中使用这些核心部分,理解它们是很重要的

    让我们看看,这段代码对于您的用例是否足够有效,或者它是否需要工作。从远处很难说,因为我们没有你的数据

    用于映射的字典部分可能会破坏您的记忆。但是,在不知道是否需要的情况下对其进行优化是毫无意义的。特别是因为代码的这一部分依赖于图中的顶点数(我对这个基数一无所知)

    edges-10.txt的输出:

    [[287111206 357850135]
     [512616930 441657273]
     [530905858 562056765]
     [524113870 320749289]
     [149911066 964526673]
     [169873523 631128793]
     [646151040 986572427]
     [105290138 382302570]
     [194873438 968653053]
     [912211115 195436728]]
    (10, 2)
    [[ 0 10]
     [ 1 11]
     [ 2 12]
     [ 3 13]
     [ 4 14]
     [ 5 15]
     [ 6 16]
     [ 7 17]
     [ 8 18]
     [ 9 19]]
      (0, 10)   True
      (1, 11)   True
      (2, 12)   True
      (3, 13)   True
      (4, 14)   True
      (5, 15)   True
      (6, 16)   True
      (7, 17)   True
      (8, 18)   True
      (9, 19)   True
    

    您可能想看看这个项目,这是一个C代码的GPL库,它是为这类事情而设计的,并且有一个很好的Python API。我认为在您的例子中,Python代码应该是

    from igraph import Graph
    g = Graph.Read_Edgelist('edges.txt')
    g.write_adjacency('adjacency_matrix.txt')
    
    以下是我的解决方案:

    import numpy as np
    import pandas as pd
    import scipy.sparse as ss
    
    def read_data_file_as_coo_matrix(filename='edges.txt'):
        "Read data file and return sparse matrix in coordinate format."
        data = pd.read_csv(filename, sep=' ', header=None, dtype=np.uint32)
        rows = data[0]  # Not a copy, just a reference.
        cols = data[1]
        ones = np.ones(len(rows), np.uint32)
        matrix = ss.coo_matrix((ones, (rows, cols)))
        return matrix
    
    熊猫使用
    read\u csv
    完成繁重的解析工作。熊猫已经在以列格式存储数据。
    数据[0]
    数据[1]
    只获取参考,没有副本。然后我把它们输入
    coo\u矩阵
    。本地基准:

    In [1]: %timeit -n1 -r5 read_data_file_as_coo_matrix()
    1 loop, best of 5: 14.2 s per loop
    
    In [3]: %timeit -n1 -r5 save_csr_matrix('edges.npz', matrix.tocsr())
    1 loop, best of 5: 13.4 s per loop
    
    In [4]: %timeit -n1 -r5 load_csr_matrix('edges.npz')
    1 loop, best of 5: 881 ms per loop
    
    然后将csr矩阵保存到文件中:

    def save_csr_matrix(filename, matrix):
        """Save compressed sparse row (csr) matrix to file.
    
        Based on http://stackoverflow.com/a/8980156/232571
    
        """
        assert filename.endswith('.npz')
        attributes = {
            'data': matrix.data,
            'indices': matrix.indices,
            'indptr': matrix.indptr,
            'shape': matrix.shape,
        }
        np.savez(filename, **attributes)
    
    本地基准:

    In [1]: %timeit -n1 -r5 read_data_file_as_coo_matrix()
    1 loop, best of 5: 14.2 s per loop
    
    In [3]: %timeit -n1 -r5 save_csr_matrix('edges.npz', matrix.tocsr())
    1 loop, best of 5: 13.4 s per loop
    
    In [4]: %timeit -n1 -r5 load_csr_matrix('edges.npz')
    1 loop, best of 5: 881 ms per loop
    
    然后从文件中重新加载:

    def load_csr_matrix(filename):
        """Load compressed sparse row (csr) matrix from file.
    
        Based on http://stackoverflow.com/a/8980156/232571
    
        """
        assert filename.endswith('.npz')
        loader = np.load(filename)
        args = (loader['data'], loader['indices'], loader['indptr'])
        matrix = ss.csr_matrix(args, shape=loader['shape'])
        return matrix
    
    本地基准:

    In [1]: %timeit -n1 -r5 read_data_file_as_coo_matrix()
    1 loop, best of 5: 14.2 s per loop
    
    In [3]: %timeit -n1 -r5 save_csr_matrix('edges.npz', matrix.tocsr())
    1 loop, best of 5: 13.4 s per loop
    
    In [4]: %timeit -n1 -r5 load_csr_matrix('edges.npz')
    1 loop, best of 5: 881 ms per loop
    
    最后,对其进行全面测试:

    def test():
        "Test data file parsing and matrix serialization."
        coo_matrix = read_data_file_as_coo_matrix()
        csr_matrix = coo_matrix.tocsr()
        save_csr_matrix('edges.npz', csr_matrix)
        loaded_csr_matrix = load_csr_matrix('edges.npz')
        # Comparison based on http://stackoverflow.com/a/30685839/232571
        assert (csr_matrix != loaded_csr_matrix).nnz == 0
    
    if __name__ == '__main__':
        test()
    
    运行
    test()
    时,大约需要30秒:

    $ time python so_38688062.py 
    real    0m30.401s
    user    0m27.257s
    sys     0m2.759s
    
    内存高水位线约为1.79GB


    注意,一旦你在CSR矩阵格式中转换了“边.txt”到“边.npz”,加载它将花费不到一秒钟。

    在我的回答中,我考虑节点的ID是由9个字符长字符串给出的,每个字符都来自<代码> [09A-ZA-Z] 。这些节点ID的code>n应映射到值
    [0,n-1]
    (这对于您的应用程序来说可能不是必需的,但仍然具有普遍意义)

    我相信大家都知道,为了完整起见,接下来的考虑事项如下:

  • 记忆是瓶颈
  • 文件中大约有
    10^8
    个字符串
  • 一个9个字符长的
    string+int32
    值对在字典中的开销约为
    120
    字节,导致文件的内存使用量为12GB
  • 文件中的字符串id可以映射到
    int64
    :有62个不同的字符->可以用6位编码,字符串中的9个字符->6*9=54 ca。1.2 GB就足够了,但是成本为
    from scipy import sparse
    def data_as_coo_matrix(filename, EDGE_CNT)
        node_cnt, nodes = maps_to_ids(filename, EDGE_CNT)    
        rows=nodes[::2]#it is only a view, not a copy
        cols=nodes[1::2]#it is only a view, not a copy
    
        return sparse.coo_matrix((np.ones(len(rows), dtype=bool), (rows, cols)), shape=(node_cnt, node_cnt))
    
    def toInt64(string):
        res=0L
        for ch in string:
            res*=62
            if ch <='9':
              res+=ord(ch)-ord('0')
            elif ch <='Z':
              res+=ord(ch)-ord('A')+10
            else:
              res+=ord(ch)-ord('a')+36
        return res
    
    import numpy as np
    import scipy.sparse as sparse
    
    def readEdges():
        with open('edges.txt') as f:
            data = f.read()  
        edges = np.fromstring(data, dtype=np.int32, sep=' ')
        edges = np.reshape(edges, (edges.shape[0]/2, 2))
        ones = np.ones(len(edges), np.uint32)
        cooMatrix = sparse.coo_matrix((ones, (edges[:,0], edges[:,1])))
    %timeit -n5 readEdges()
    
    5 loops, best of 3: 13.6 s per loop
    
    def readEdgesMmap():
        with open('edges.txt') as f:
            with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as m: 
                edges = np.fromstring(m, dtype=np.int32, sep=' ')
                edges = np.reshape(edges, (edges.shape[0]/2, 2))
                ones = np.ones(len(edges), np.uint32)
                cooMatrix = sparse.coo_matrix((ones, (edges[:,0], edges[:,1])))
    %timeit -n5 readEdgesMmap()
    
    5 loops, best of 3: 12.7 s per loop