在pandas中快速稀疏向量相加

0 投票
1 回答
1129 浏览
提问于 2025-04-18 05:36

这是一个关于如何在Pandas中进行大量表连接以进行向量数学运算的问题。

通过一个非常非常长的处理过程,我把大量的数据(以HDF5表格的形式表示)处理成了大约20个稀疏向量,这些向量用Pandas的DataFrame表示,并且有基于字符串的多重索引。这些向量所在的空间非常复杂且维度很高(这是自然语言数据),但它们之间有一些重叠。每个向量大约有5K到60K个维度,而重叠的维度总数(根据我调用的20个向量可能会有所不同)大约是20万。(实际上,整个空间的维度远远超过20万!)

到这里为止,处理速度非常快,只需要一次性将表格处理成合适的向量。

但现在我想对这些向量进行对齐和求和。我找到的所有解决方案都比较慢。我正在使用Python 2.7上的Pandas 0.12.0。

A为我获取向量的存储位置/磁盘。

In [106]: nounlist = ["fish-n", "bird-n", "ship-n", "terror-n", "daughter-n", "harm-n", "growth-n", "reception-n", "antenna-n", "bank-n", "friend-n", "city-n", "woman-n", "weapon-n", "politician-n", "money-n", "greed-n", "law-n", "sympathy-n", "wound-n"]

In [107]: matrices = [A[x] for x in nounlist]

(我意识到matrices这个词有点误导。除了多重索引,它们其实只有一列。)

到目前为止一切顺利。但现在我想把它们连接起来,以便可以求和:

In [108]: %timeit matrices[0].join(matrices[1:], how="outer")
1 loops, best of 3: 18.2 s per loop

这是在一个相对较新的处理器上(2.7 GHz AMD Opteron)。对于理想情况下在语音处理系统中使用的高维度数据来说,这个速度太慢了。

我用reduce稍微好了一点:

In [109]: %timeit reduce(lambda x, y: x.join(y, how="outer"), matrices[1:], matrices[0])
1 loops, best of 3: 10.8 s per loop

这些在多次运行中保持相对一致。一旦返回,求和的速度就可以接受了:

In [112]: vec = reduce(lambda x, y: x.join(y, how="outer"), matrices[1:], matrices[0])

In [113]: %timeit vec.T.sum()
1 loops, best of 3: 262 ms per loop

我接近将时间缩短到合理范围的结果是这个:

def dictcutter(mlist):
    rlist = [x.to_dict()[x.columns[0]] for x in mlist]
    mdict = {}
    for r in rlist:
        for item in r:
            mdict[item] = mdict.get(item, 0.0) + r[item]
    index = pd.MultiIndex.from_tuples(mdict.keys())
    return pd.DataFrame(mdict.values(), index=index)

这个运行起来是:

In [114]: %timeit dictcutter(matrices)
1 loops, best of 3: 3.13 s per loop

但每一秒都很重要!有没有办法进一步缩短时间?有没有更聪明的方法按维度来加这些向量?

编辑补充 Jeff在评论中请求的细节:

关于“fish-n”向量的一些细节:

In [14]: vector = A['fish-n']

In [15]: vector.head()
Out[15]: 
                   fish-n
link   word1             
A2     give-v  140.954675
A4     go-v    256.313976
AM-CAU go-v      0.916041
AM-DIR go-v     29.022072
AM-MNR go-v     21.941577

In [16]: vector.info()
<class 'pandas.core.frame.DataFrame'>
MultiIndex: 5424 entries, (A2, give-v) to (A1, gotta-v)
Data columns (total 1 columns):
fish-n    5424  non-null values
dtypes: float64(1)

深入挖掘:

In [17]: vector.loc['A0']
Out[17]: 
<class 'pandas.core.frame.DataFrame'>
Index: 1058 entries, isolate-v to overdo-v
Data columns (total 1 columns):
fish-n    1058  non-null values
dtypes: float64(1)

In [18]: vector.loc['A0'][500:520]
Out[18]: 
                 fish-n
word1                  
whip-v         3.907307
fake-v         0.117985
sip-v          0.579624
impregnate-v   0.885079
flavor-v       5.583664
inspire-v      2.251709
pepper-v       0.967941
overrun-v      1.435597
clutch-v       0.140110
intercept-v   20.513823
refined-v      0.738980
gut-v          7.570856
ascend-v      12.686698
submerge-v     1.761342
catapult-v     0.577075
cleaning-v     1.492284
floating-v     5.318519
incline-v      2.270102
plummet-v      0.243116
propel-v       3.957041

现在把这个乘以20,然后尝试把它们全部求和……

1 个回答

0

创建一些测试数据

In [66]: def mklbl(prefix,n):
   ....:         return ["%s%s" % (prefix,i)  for i in range(n)]
   ....: 

In [67]: mi_total = pd.MultiIndex.from_product([mklbl('A',1000),mklbl('B',200)])

# note that these are random consecutive slices; that's just for illustration
In [68]: ms = [ pd.Series(1,index=mi_total.take(np.arange(50000)+np.random.randint(0,150000,size=1))) for i in range(20) ]

In [69]: ms[0]
Out[69]: 
A417  B112    1
      B113    1
      B114    1
      B115    1
      B116    1
      B117    1
      B118    1
      B119    1
      B120    1
      B121    1
      B122    1
      B123    1
      B124    1
      B125    1
      B126    1
...
A667  B97     1
      B98     1
      B99     1
      B100    1
      B101    1
      B102    1
      B103    1
      B104    1
      B105    1
      B106    1
      B107    1
      B108    1
      B109    1
      B110    1
      B111    1
Length: 50000, dtype: int64

把所有东西放进一个很长的序列里,然后转换成一个数据框(此时索引是重复的),接着在索引层级上进行求和(这样就去掉了重复的部分)

这相当于执行 concat(ms).groupby(level=[0,1]).sum()。最后的 sort 只是为了说明,并不是必须的。不过,如果你在后面要进行任何类型的索引,可能需要用 sortlevel() 来对索引进行排序。

 In [103]: concat(ms).to_frame(name='value').sum(level=[0,1]).sort('value',ascending=False)
Out[103]: 
           value
A596 B109     14
A598 B120     14
     B108     14
     B109     14
     B11      14
     B110     14
     B111     14
     B112     14
     B113     14
     B114     14
     B115     14
     B116     14
     B117     14
     B118     14
     B119     14
     B12      14
     B121     14
     B106     14
     B122     14
     B123     14
     B124     14
     B125     14
     B126     14
     B127     14
     B128     14
     B129     14
     B13      14
     B130     14
     B131     14
     B132     14
     B133     14
     B134     14
     B107     14
     B105     14
     B136     14
A597 B91      14
     B79      14
     B8       14
     B80      14
     B81      14
     B82      14
     B83      14
     B84      14
     B85      14
     B86      14
     B87      14
     B88      14
     B89      14
     B9       14
     B90      14
     B92      14
A598 B104     14
A597 B93      14
     B94      14
     B95      14
     B96      14
     B97      14
     B98      14
     B99      14
A598 B0       14
             ...

[180558 rows x 1 columns]

现在速度很快

In [104]: %timeit concat(ms).to_frame(name='value').sum(level=[0,1]).sort('value',ascending=False)
1 loops, best of 3: 342 ms per loop

撰写回答