稀疏矩阵

2021-01-01 17:19:00

稀疏矩阵是大多数元素的值为0的矩阵。如果非零元素(NNZ)的数量与大小的比值小于0.5,则该矩阵是稀疏的。对于仅包含NNZ元素的矩阵,将使用术语稀疏;对于包含所有元素的矩阵,将使用术语稠密。

存储有关所有0个元素的信息效率很低,因此我们假设未指定的元素为0.使用这种方案,稀疏矩阵可以比其对应的密集矩阵表示执行更快的操作并使用更少的内存,这在处理大数据时尤其重要设置数据科学。

今天,我们将研究SciPy稀疏包提供的所有不同实现,该实现以np.matrix与np.ndarray相反为模型,因此仅限于二维数组并且具有A * B之类的怪癖而不是矩阵乘法按元素乘法。

在[0]中:从scipy导入稀疏在[1]中:将numpy导入为np在[2]中:spmatrix = sparse。 random(10,10)In [3]:spmatrix Out [3]:< '< class'类型的10 x10稀疏矩阵numpy的。 float64''具有1个以COOrdinate格式>存储的元素在[4]中:spmatrix。 nnz / np。产品(形状形状)#稀疏度[4]:0.01

不同的稀疏格式各有优缺点,一个好的出发点是寻找可以有效地构造这些矩阵的格式,通常您会从其中一种形式开始,然后在准备进行计算时转换为另一种形式。

也许最简单的稀疏格式是COOrdinate(COO)格式。此变体使用三个子数组来存储元素值及其坐标位置。

在[5]中:行= [1,3,0,2,4]在[6]中:col = [1,4,2,3,3]在[7]中:数据= [2,5,9, 1,6]在[8]中:coo =稀疏。 coo_matrix((data,(row,col)),shape =(6,7))在[9]中:print(coo)#坐标值格式#(1,1)2#(3,4)5#( 0,2)9#(2,3)1#(4,3)6在[10]中:coo。 todense()#coo.toarray()代替nd [Out 10]:矩阵([[0,0,9,0,0,0,0 ,, [0,2,0,0,0,0,0 ],[0,0,0,1,0,0,0],[0,0,0,0,5,0,0],[0,0,0,6,0,0,0], [0,0,0,0,0,0,0]])

随着矩阵大小的增加,内存消耗的节省是相当可观的。稀疏结构中的数据管理是固定成本,这与密集矩阵的情况不同。随着数据的增长,管理子阵列所产生的开销可以忽略不计。一些数据集的选择。

请注意:只有在足够稀疏的情况下才使用稀疏数组;使用几个子数组来存储位置和数据的跟踪将导致存储大多数为非零的数组,这将适得其反。

在[11]中:def memory_usage(coo):...:#数据存储器和开销存储器...:coo_mem =(sum([[coo.data,coo.row,coo.col]中obj的obj.nbytes)) ...:+ sum([coo,coo。data,coo。row,coo。col]中obj的obj。__sizeof__()))::print(f'稀疏:{coo_mem}&# 39;)...:mtrx = coo。 todense()...:mtrx_mem = mtx。 nbytes + mtrx。 __sizeof__()...:print(f'密度:{mtrx_mem}')在[12]中:memory_usage(coo)#稀疏:480#密度:448在[13]中:coo。调整大小(100,100)在[14]中:memory_usage(coo)#稀疏:480#密集:80112

密钥字典(DOK)与COO非常相似,不同之处在于它将dict的子类存储为键-值对,因为它使用哈希表存储,因此在任何给定位置识别值的查询时间都是恒定的。如果您需要内置字典附带的功能,请使用“格式化”格式,但是请注意,哈希表比数组占用更多的内存。

在[15]中:dok = sparse。 dok_matrix((10,10))在[16]中:dok [(3,7)] = 42#将值42存储在坐标(3,7)在[17]中:dok [(9,5)]#零元素可访问Out [17]:0.0 In [18]:dok。键()| k转置()。 keys()#键视图的并集Out [18]:{(3,7),(7,3)} In [19]:isinstance(dok,dict)Out [19]:True

注意:使用从dict继承的方法来注意潜在的问题;他们并不总是守规矩。

在[20]中:out_of_bounds =(999,999)在[21]中:dok [out_of_bounds] = 1#按预期方式工作IndexError:索引超出范围。在[22]中:dok。 setdefault(out_of_bounds)#默默忽略...在[23]中:dok。 toarray()#...到现在为止ValueError:行索引超过[24]中的矩阵尺寸:dok。 pop(out_of_bounds)#通过消除[25]中的坏点来解决问题:稀疏。 dok_matrix。 fromkeys([...,...,...])#不要让我开始TypeError:__init__()缺少1个必需的位置参数:' arg1'

插入数据最灵活的格式是通过使用LInked List(LIL)矩阵。可以通过NumPy的索引和切片语法设置数据以快速填充矩阵。在我看来,LIL是用于从中构造稀疏矩阵的最酷的稀疏格式刮。

LIL将信息存储在lil.rows中,其中每个列表代表一个行索引,列表中的元素匹配列。在并行数组中,lil.data存储NNZ值,但是与其他稀疏格式不同,这些子数组无法显式传递给建设者; LIL矩阵必须由空状态或现有的密集或稀疏矩阵组成。下面是用于构建LIL矩阵的各种技术的说明。

在[26]中:lil = sparse。 lil_matrix((6,5),dtype = int)在[27]中:lil [(0,-1)] =-1#设置单个点In [28]:lil [3,(0,4)] = [ -2] * 2#在[29]中设置两个点:lil。 setdiag(8,k = 0)#设置主对角线In [30]:lil [:,2] = np。范围(lil。shape [0])。整形(-1,1)+ 1#设置整列In [31]:lil。 toarray()Out [31]:数组([[8,0,1,0,-1],[0,8,2,0,0],[0,0,3,0,0],[- 2,0,4,8,-2],[0,0,5,0,8],[0,0,6,0,0]])

那么缺点是什么?好吧,它在需要np.dtype(object)的情况下利用了锯齿状数组,这比矩形数组需要更多的内存,因此如果数据足够大,您可能会被迫使用COO而不是LIL。简而言之,LIL主要是为了提供便利,尽管那真是太棒了。

在[32]中:lil。 row Out [32]:数组([list([0,2,4]),list([1,2]),list([2]),list([0,2,3,4]),列表[[2,4]),list([2])],dtype = object)在[33]中:lil。数据[:,np。 newaxis]#暴露锯齿状结构Out [33]:数组([[list([8,1,-1])]],[list([8,2])],[list([3])],[list ([-2,4,8,-2])],[列表([5,8])],[列表([6])]],dtype = object)

顺便说一句,链接列表矩阵是一个错误的称呼,因为它不使用幕后链接列表!LIL实际上使用Python的列表,它是一个动态数组,因此尽管有什么文档说明,它也应该真正称为列表列表矩阵说。(错过了将它命名为LOL的机会……)

前面介绍的格式非常适合构建稀疏矩阵,但是它们的计算性能不及更专业的形式。压缩稀疏矩阵族的情况则相反,应将其视为只读而不是只写。较难理解,但稍稍耐心就可以看出它们的结构。

在[35]中:indptr = np。数组([0,2,3,3,3,6,6,7])在[36]中:索引= np。数组([0,2,2,2,3,4,3])在[37]中:data = np。数组([8,2,5,7,1,2,9])在[38]中:csr = sparse。 csr_matrix((数据,索引,indptr))在[39]中:csr。 todense()Out [39]:矩阵([[8,0,2,0,0],[0,0,5,0,0],[0,0,0,0,0],[0, 0,0,0,0],[0,0,7,1,2],[0,0,0,0,0],[0,0,0,9,0]])

一对相邻的索引指针决定了两件事:首先,它们在指针数组中的位置是行号;其次,这些值代表索引数组的[start:stop]切片,它们的区别是每行中的NNZ元素。使用指针查找索引,以确定数据中每个元素的列。

CSC的工作原理与CSR完全相同,但具有基于列的索引指针和行索引,下图是此格式的相同数据的图表。

在[40]中:csc = sparse。 csc_matrix(csr)#转换格式为[41]:csc。 indptr Out [41]:数组([0,1,1,1,4,6,7],dtype = int32)In [42]:csc。 index Out [42]:数组([0,0,1,4,4,6,4],dtype = int32)In [43]:csc。数据输出[43]:数组([8,2,5,7,1,9,2],dtype = int64)

如所承诺的,压缩格式的确比同等的COO更快。对于中等大小的矩阵,我们看到COO的速度提高了2倍,而COO的密度提高了60倍!

在[44]中:csr。调整大小(1000,1000)在[45]中:%timeit csr @ csr#111 µs±3.66 µs每个循环(平均值±标准偏差,共运行7次,每个循环10000个循环)In [46]:coo = csr。 tocoo()#另一种转换In [47]的方法:%timeit coo @ coo#每个循环251 µs±8.06 µs(平均±标准偏差,共运行7次,每个循环1000个)In [48]:arr = csr。 toarray()在[49]中:%timeit arr @ arr#数量级变慢!每个循环#632毫秒±2.02毫秒(平均±标准偏差,共7次运行,每个循环1次)

块稀疏行(BSR)与CSR类似,但在位置存储子矩阵而不是标量值。

在[50]中:1 = np。在[51]中为((2,3),dtype = int):data = np。数组([i + i for range in(4)]])在[52]中:索引= [1,2,2,0]在[53]中:indptr = [0,2,3,4]在[54中]:bsr = sparse。 bsr_matrix((数据,索引,indptr))在[55]中:bsr。 todense()Out [55]:矩阵([[0,0,0,1,1,1,2,2,2],[0,0,0,1,1,1,2,2,2] ,[0,0,0,0,0,0,3,3,3],[0,0,0,0,0,0,3,3,3],[4,4,4,0, 0,0,0,0,0],[4,4,4,0,0,0,0,0,0]])

此实现要求所有子矩阵都具有相同的形状,但是存在更多具有块矩阵的通用构造来放松此约束。这些矩阵在SciPy中没有其唯一的数据结构,但可以通过sparse.bmat间接创建构造函数。

在[56]中:A = np。范围(8)。 reshape(2,4)#可以使用[57]中的密集数组:T = np。在[58]中的tri(5,4):L = [[8] * 4] * 2#可以使用在[59]中的列表:I =稀疏。 identity(4)#可以使用[60]中的稀疏数组:Z = sparse。 coo_matrix((2,3))#零以创建列间隙In [61]:sp。 bmat([[A,Z,L],...:[无,无,I],...:[T,无,无]],dtype = int)Out [61]:< '< class'类型的11 x11稀疏矩阵numpy的。 int64''具有33个以COOrdinate格式存储的元素>在[62]中:_。 toarray()#ipython先前的输出Out [62]:array([[0,1,2,3,0,0,0,8,8,8,8],[4,5,6,7,0, 0,0,8,8,8,8],[0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0, 0,0,1,0,0],[0,0,0,0,0,0,0,0,0,1,0],[0,0,0,0,0,0,0, 0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0],[1,1,0,0,0,0,0,0, 0,0,0],[1,1,1,0,0,0,0,0,0,0,0],[1,1,1,1,0,0,0,0,0, 0,0],[1,1,1,1,0,0,0,0,0,0,0]]))

DIAgonal(DIA)变体也许是存储稀疏数据的最特殊格式,它最适合于沿矩阵对角线出现的数据。

在[63]中:data = np。范围(15)。重塑(3,-1)+ 1 In [64]:偏移= np。数组([0,-3,2])在[65]中:dia = sparse。 dia_matrix((data,offsets),shape =(7,5))在[66]中:dia。 toarray()Out [66]:数组([[1,0,13,0,0],[0,2,0,14,0],[0,0,3,0,15],[6, 0,0,4,0],[0,7,0,0,5],[0,0,8,0,0],[0,0,0,9,0]])

数据存储在形状(偏移量)x(宽度)的数组中,其中偏移量指示数据阵列中每行沿对角线的位置。当负数或正数时,偏移量分别位于主对角线以下或上方。请注意,如果数据矩阵中的一行被切除,则多余的元素可以采用任何值(但它们必须具有占位符)。

在[67]:dia中。数据。 ravel()[9:12] = 0#替换[68]:dia中的截止数据。数据输出[68]:数组[[[1、2、3、4、5],[6、7、8、9、0],[0、0、13、14、15]])数据[69] :直径toarray()#与更早的Out [69]相同的数组repr:array([[1,0,13,0,0],[0,2,0,14,0],[0,0,3,0, 15],[6、0、0、4、0],[0、7、0、0、5],[0、0、8、0、0],[0、0、0、9、0] ])

除了多种格式外,还有大量专门用于稀疏矩阵的函数,请尽可能使用这些函数而不是NumPy的对应函数,否则会影响速度性能。更糟糕的是,结果计算可能不正确!

对这些细节的研究留给了狂热的读者。

SciPy并不是在Python生态系统中处理稀疏结构的唯一资源,尽管大多数似乎在内部使用SciPy软件包,但它们都是自己使用的。我将介绍几个我认为最引人注目的库,但这并不是假定的成为一切的一切。

如今,如果没有Pandas,数据科学将不再是什么,因此它支持其数据结构的稀疏变体也就不足为奇了。一个真正整洁的功能是,推断元素不必是0的形式!

在[70]中:将熊猫作为pd导入在[71]中:ss = pd。稀疏系列。 from_coo(dia。tocoo())In [72]:ss#将MultiIndex用于坐标Out [72]:0 0 1 2 13 1 1 2 3 14 2 2 3 4 15 3 0 6 3 4 4 1 7 4 5 5 2 8 6 3 9 dtype:稀疏[int64,0] BlockIndex块位置:数组([0],dtype = int32)块长度:数组([12],dtype = int32)在[73]中:data = dict (A = [np.nan,1,2],B = [np.nan,3,np.nan])在[74]中:sdf = pd。 DataFrame(数据)。 to_sparse()在[75]中:键入(sdf)。 mro()#类继承层次结构Out [75]:[pandas。核心。稀疏的。框架。 SparseDataFrame,熊猫。核心。框架。 DataFrame,熊猫。核心。通用的。 NDFrame,熊猫。核心。基地。熊猫对象,熊猫。核心。基地。 StringMixin,熊猫。核心。访问器。 DirNamesMixin,熊猫。核心。基地。在[76]中的SelectionMixin,对象]:sdtype = pd。 SparseDtype(object,fill_value =' e')#不限于[77]:pd中的空值。 SparseArray(list(' abcdeeeeeeee'),dtype = sdtype)Out [77]:[a,b,c,d,e,e,e,e,e,e,e,e]填充: e IntIndex指数:数组([0,1,2,3],dtype = int32)

机器学习强国Scikit-Learn在很多领域都支持稀疏矩阵,这很重要,因为大数据在稀疏矩阵上蓬勃发展(假设有足够的稀疏性),毕竟谁不想从这些数字处理算法中获得性能提升不得不等待CPU密集型SVM,更不用说发现某些数据不适合工作内存了!

文本向量化器生成的Scikit-Learn术语文档矩阵会导致CSR矩阵。这对NLP至关重要,因为大多数单词都很少使用。天真地使用密集格式可能会导致速度瓶颈和大量内存浪费。

在[78]中:从sklearn.feature_extraction.text导入CountVectorizer在[79]中:bow = CountVectorizer()。 fit_transform([' demo'])在[80]中:稀疏。 isspmatrix(弓)出[80]:真入[81]:稀疏。 save_npz(' bag_of_words.npz',bow)#存储以备将来使用

此外,还有一些实用程序可以很好地处理稀疏矩阵,例如缩放器,少量分解,成对距离,训练测试拆分,并且许多估计器都可以预测和/或拟合稀疏矩阵。您的机器学习模型更加有效。

作为另一种实现方式,PyData的稀疏库提供了类似于np.ndarray的接口,而不是np.matrix,从而允许创建多维稀疏数组。需要注意的是,在撰写本文时,仅支持COO和DOK格式。

在[82]中:导入稀疏作为sp#避免使用scipy.sparse破坏名称。在[83]中:sarr = sp。随机((3,4,2,密度= 0.2)#3-D稀疏数组In [84]:sarr Out [84]:< COO:shape =(3、4、2),dtype = float64,nnz = 4,fill_value = 0.0>。在[85]中:sarr + = 1#在scipy.sparse中是不可能的。在[86]中:sarr在[86]中:< COO:shape =(3、4、2),dtype = float64,nnz = 4,fill_value = 1.0>。 #fill_value更新!在[87]中:sarr。 todense()Out [87]:数组([[[[1.,1.],[1.,1.],[1.,1.],[1.,1.]],[[1., 1.],[1.86024163,1.],[1.37233162,1.1114997],[1.,1.]],[[1.,1.],[1.,1.16850612],[1.,1.], [1.,1.]]])

在SciPy中,逻辑运算符不是直接实现的,但是可以通过将dtype约束为bool来模拟AND(&)和OR(|):

在[88]中:Class LogicalSparse(sparse。coo_matrix):#scipy COO ...:def __init__(self,* args,** kwargs):...:super()。 __init__(* args,dtype = bool,** kwargs)#利用现有的基类...:...:def __and__(self,other):#self&其他...:自我回报。乘(其他)...:... ...:def __or__(自我,其他):其他...:返回自我+其他

不幸的是,NOT(〜)是不可能的,因为它将稀疏矩阵变成一个密集的矩阵(理论上是自-1),直到现在,也就是如前所述,稀疏将动态更新填充值以适应当前状态。

在[89]中:mask = sp。 眼睛(100,dtype = bool)输入[90]:遮罩输出[90]:< COO:shape =(100,100),dtype = bool,nnz = 100,fill_value = False> 在[91]中:〜遮罩[91]:< COO:shape =(100,100),dtype = bool,nnz = 100,fill_value = True> 希望本文对如何正确使用稀疏数据结构有所启发,以便您可以放心地将其用于将来的项目中。了解每种格式(包括密集格式)的利弊有助于为特定任务选择最佳格式。 请注意,虽然稀疏矩阵是很好的工具,但它们不一定是阵列的替代品。如果矩阵不够稀疏,那么幕后的大量存储阵列实际上将比常规密集阵列占用更多的资源。 ......