"&致密化#34;通过重新排列行/列来构建非常大的稀疏矩阵

时间:2016-09-29 07:59:22

标签: python numpy matrix scipy scikit-learn

我有一个非常大的稀疏矩阵(240k * 4.5k,≤1%非零元素),我希望" densify"通过重新排列其行和列,使左上区域尽可能地富集非零元素。 (为了使其更易于管理和可视化评估。)我希望scipy和相关工具来做到这一点。

  • 已经提出了一个很好的建议here来解决“手动”问题。交换稀疏矩阵的行/列,但它没有涵盖在左上角确定要交换哪些行/列以获得最佳浓缩(密集块)的挑战。
  • 请注意,基于非零元素的数量对行/列进行简单排序并不能解决问题。 (如果我使用例如这两行中元素最多,则它们之间不一定会有任何重叠 - ,其中列 - 是位于元素中的元素。)
  • 我也对scipy.sparse中此任务的最佳稀疏矩阵表示感到好奇。

欢迎任何建议或具体实施方案。

2 个答案:

答案 0 :(得分:1)

看起来您已经可以交换保留稀疏性的行,因此缺少的部分是对行进行排序的算法。所以你需要一个能给你一个"左右"得分了。可行的启发式方法如下:

  1. 首先获得非零元素的掩码(你不关心实际值,只关心它的非零元素)。
  2. 估算沿列轴的非零值的密度分布:

    def density(row, window):
        padded = np.insert(row, 0, 0)
        cumsum = np.cumsum(padded) 
        return (cumsum[window:] - cumsum[:-window]) / window
    
  3. 将左侧得分计算为具有最大左惩罚密度的列(从右侧看):

    def leftness_score(row):
        n = len(a)
        window = n / 10   # 10 is a tuneable hyper parameter
        smoothness = 1    # another parameter to play with
        d = density(row)
        penalization = np.exp(-smoothness * np.arange(n))
        return n - (penalization * d).argmax()
    
  4. 只要此密度的最大值不太靠右,此算法就会为具有高密度值的行提供更高的分数。进一步采取的一些想法:改进密度估计,使用不同的惩罚函数(而不是neg exp),将参数拟合到反映预期排序等的一些综合数据中。

答案 1 :(得分:0)

我最终得到了以下解决方案:
- 首先,基于scipy documentation,LiL(链表)格式似乎是这种操作的理想选择。 (但我从来没有做过任何实际比较!)
- 我使用了here描述的函数来交换行和列 - 在suggestion of elyase之后,我在矩阵的“左上角”定义了一个200 * 200'窗口'并实现了一个'窗口得分',它只是等于窗口内非零元素的数量。
- 为了识别要交换的列,我检查了哪个列包含窗口内最少的非零元素,哪个列包含窗口外最多的非零元素。如果出现平局,整列中非零元素的数量就是打破平局(如果这也是关联的,我随机选择)。
- 交换行的方法是相同的。

import numpy as np
import scipy.sparse
import operator

def swap_rows(mat, a, b):
    ''' See link in description'''

def swap_cols(mat, a, b) :
    ''' See link in description'''

def windowScore(lilmatrix,window):
    ''' Return no. of non-zero elements inside window. '''
    a=lilmatrix.nonzero()

    return sum([1 for i,j in list(zip(a[0],a[1])) if i<window and j<window])

def colsToSwap(lilmatrix,window):
    ''' Determine columns to be swapped.
    In: lil_matrix, window (to what col_no is it considered "left") 
    Out: (minColumnLeft,maxColumnRight) columns inside/outside of window w/ least/most NZ elements'''

    # Locate non-zero elements
    a=lilmatrix.nonzero()

    totalCols=lilmatrix.get_shape()[1]

    # Store no. of NZ elements for each column {in the window,in the whole table}, initialize with zeros
    colScoreWindow=np.zeros(totalCols)
    colScoreWhole=np.zeros(totalCols)

    ### Set colScoreWindow scores
    # Unique row indices
    rows_uniq={k for k in a[0] if k<window}
    for k in rows_uniq:
        # List of tuples w/ location of each NZ element in current row
        gen=((row,col) for row,col in list(zip(a[0],a[1])) if row==k)
        for row,col in gen:
           # Increment no. of NZ elements in current column in colScoreWindow
            colScoreWindow[col]+=1

    ### Set colScoreWhole scores
    # Unique row indices
    rows_uniq={k for k in a[0]}
    for k in rows_uniq:
        # List of tuples w/ location of each NZ element in current row
        gen=((row,col) for row,col in list(zip(a[0],a[1])) if row==k)
        for row,col in gen:
            # Increment no. of NZ elements in current column in colScoreWhole
            colScoreWhole[col]+=1


    # Column inside of window w/ least NZ elements
    minColumnLeft=sorted(list(zip(np.arange(totalCols),colScoreWindow,colScoreWhole,np.random.rand(totalCols)))[:window], key=operator.itemgetter(1,2,3))[0][0]
    # Column outside of window w/ most NZ elements
    maxColumnRight=sorted(list(zip(np.arange(totalCols),colScoreWindow,colScoreWhole,np.random.rand(totalCols)))[window:], key=operator.itemgetter(1,2,3))[-1][0]

    return (minColumnLeft,maxColumnRight)

def rowsToSwap(lilmatrix,window):
    ''' Same as colsToSwap, adjusted for rows.'''

在运行适当数量的colsToSwaprowsToSwap迭代以及实际交换函数后,窗口内非零元素的数量会收敛到最大值。请注意,该方法根本没有优化,并且还有很大的改进空间。例如,我怀疑减少稀疏矩阵类型转换和/或a=lilmatrix.nonzero()调用的数量会显着加快它的速度。