在pandas中快速稀疏向量相加
这是一个关于如何在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 个回答
创建一些测试数据
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