我有两个大矩阵(40000 * 4096),我想将第一个矩阵的每一行与第二个矩阵的所有行进行比较和匹配,结果输出的大小将为(40000 * 40000)。但是,由于我需要执行数千次,因此每次迭代都要花费26k秒的疯狂时间,因此需要5000次... 如果您能给我一些聪明的建议,我会很高兴。谢谢。 附言这是我到目前为止只进行了一次迭代(5000次中的1次)
def matcher(Antigens, Antibodies,ind):
temp = np.zeros((Antibodies.shape[0],Antibodies.shape[1]))
output = np.zeros((Antibodies.shape[0],1))
for i in range(len(Antibodies)):
temp[i] = np.int32(np.equal(Antigens[ind],Antibodies[i]))
output[i] = np.sum(temp[i])
return output
output = [matcher(gens,Antibodies) for gens in Antigens]
答案 0 :(得分:1)
好的,我想我了解您的目标是什么
计数行匹配数(抗原与抗体基质)。所得载体的每一行(40,000 x 1)代表1条抗原行与所有抗体行之间的精确匹配数(因此,值介于0-40_000之间)。
我做了一些假数据:
import numpy as np
import numba as nb
num_mat = 5 # number of matrices
num_row = 10_000 # number of rows per matrix
num_elm = 4_096 # number of elements per row
dim = (num_mat,num_row,num_elm)
Antigens = np.random.randint(0,256,dim,dtype=np.uint8)
Antibodies = np.random.randint(0,256,dim,dtype=np.uint8)
这里有一个重要的观点,我将矩阵缩小为可以表示数据的最小数据类型,以减少其内存占用量。我不确定您的数据是什么样子,但希望您也可以这样做。
此外,以下代码假定您的尺寸看起来是假数据:
(矩阵,行,元素的数量)
@nb.njit
def match_arr(arr1, arr2):
for i in range(arr1.shape[0]): #4096 vs 4096
if arr1[i] != arr2[i]:
return False
return True
@nb.njit
def match_mat_sum(ag, ab):
out = np.zeros((ag.shape[0])) # 40000
for i in range(ag.shape[0]):
tmp = 0
for j in range(ab.shape[0]):
tmp += match_arr(ag[i], ab[j])
out[i] = tmp
return out
@nb.njit(parallel=True)
def match_sets(Antigens, Antibodies):
out = np.empty((Antigens.shape[0] * Antibodies.shape[0], Antigens.shape[1])) # 5000 x 40000
# multiprocessing per antigen matrix, may want to move this as suits your data
for i in nb.prange(Antigens.shape[0]):
for j in range(Antibodies.shape[0]):
out[j+(5*i)] = match_mat_sum(Antigens[i], Antibodies[j]) # need to figure out the index to avoid race conditions
return out
我严重依赖Numba。一种重要的优化方法不是使用np.equal()
检查整个行的等效性,而是编写一个自定义函数match_arr()
,一旦发现不匹配的元素,该函数便会中断。希望这可以让我们跳过很多比较。
时间比较:
%timeit match_arr(arr1, arr2)
314 ns ± 0.361 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit np.equal(arr1, arr2)
1.07 µs ± 5.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
match_mat_sum
此函数仅计算表示两个矩阵之间精确匹配之和的中间步长(40,000 x 1矢量)。此步骤可简化两个矩阵,例如:(m x n),(o x n)->(m)
match_sets()
最后一个函数使用通过nb.prange
的显式并行循环将该操作并行化。您可能希望根据数据的外观将此功能移至其他循环(例如,如果您有一个抗原矩阵,但有5000种抗体矩阵,则应将prange
移至内部循环,否则将不会利用并行化)。假数据假设某些抗原和某些抗体基质。
这里要注意的另一重要事项是在out
数组上的索引。为了避免竞争条件,每个显式循环都需要写入唯一的空间。同样,根据您的数据,您需要索引正确的“位置”以放入结果。
在具有16 gigs RAM的Ryzen 1600(6核)上,使用此伪造数据,我在10.2秒内生成了结果。
您的数据大约是3200倍。假设线性缩放,假设您有足够的内存,全套将花费大约9个小时。
您也可以编写某种批处理加载器,而不是直接将5000个巨型矩阵加载到内存中。
答案 1 :(得分:0)
可以通过混合numpy广播和numexpr模块来解决此问题,该模块可以快速执行操作,同时最大程度地减少中间值的存储
import numexpr as ne
# expand arrays dimensions to support broadcasting when doing comparison
Antigens, Antibodies = Antigens[None, :, :], Antibodies[:, None, :]
output = ne.evaluate('sum((Antigens==Antibodies)*1, axis=2)')
# *1 is a hack because numexpr does not currently support sum on bool
这可能比您当前的解决方案要快,但是对于如此大的阵列,将需要一些时间。
numexpr在此操作上的表现有些乏味,但您至少可以在循环内使用广播:
output = np.zeros((Antibodies.shape[0],)*2, dtype=np.int32)
for row, out_row in zip(Antibodies, output):
(row[None,:]==Antigens).sum(1, out=out_row)