优化批量马氏距离计算的性能速度
批量特征马氏距离计算的性能优化问题
我有一段计算批量特征马氏距离的代码,在设备上运行耗时约100ms,其中大部分时间消耗在delta与inv_covariance的矩阵乘法上。delta维度为874×32×100,inv_covariance维度为874×100×100。
初始实现代码如下:
def compute_distance(embedding: np.ndarray, mean: np.ndarray, inv_covariance: np.ndarray) -> np.ndarray: batch, channel, height, width = embedding.shape embedding = embedding.reshape(batch, channel, height * width) # calculate mahalanobis distances delta = np.ascontiguousarray((embedding - mean).transpose(2, 0, 1)) distances = ((delta @ inv_covariance) * delta).sum(2).transpose(1, 0) distances = distances.reshape(batch, 1, height, width) distances = np.sqrt(distances.clip(0)) return distances
我尝试使用Numba的@njit装饰器改写代码,预分配中间矩阵并通过循环执行小矩阵乘法(因matmul不支持三维矩阵),改写后代码如下:
def compute_distance(embedding: np.ndarray, mean: np.ndarray, inv_covariance: np.ndarray) -> np.ndarray: batch, channel, height, width = embedding.shape embedding = embedding.reshape(batch, channel, height * width) # calculate mahalanobis distances delta = np.ascontiguousarray((embedding - mean).transpose(2, 0, 1)) inv_covariance = np.ascontiguousarray(inv_covariance) intermediate_matrix = np.zeros_like(delta) for i in range(intermediate_matrix.shape[0]): intermediate_matrix[i] = delta[i] @ inv_covariance[i] distances = (intermediate_matrix * delta).sum(2).transpose(1, 0) distances = np.ascontiguousarray(distances) distances = distances.reshape(batch, 1, height, width) distances = np.sqrt(distances.clip(0)) return distances
我添加了若干ascontiguousarray调用,最后一个是代码运行必需的,其余用于抑制@运算可提速的警告(实际提速效果不明显)。请问是否有方法进一步提升代码速度?无论是优化现有实现还是通过数学思路重构?
最终实现
基于Jérôme Richard的回答,我最终实现了如下代码:
@nb.njit() def matmul(delta: np.ndarray, inv_covariance: np.ndarray): """Computes distances = ((delta[i] @ inv_covariance[i]) * delta[i]).sum(2) using numba. Args: delta: Matrix of dimension BxD inv_covariance: Matrix of dimension DxD Returns: Matrix of dimension BxD """ si, sj, sk = delta.shape[0], inv_covariance.shape[1], delta.shape[1] assert sk == inv_covariance.shape[0] line = np.zeros(sj, dtype=delta.dtype) res = np.zeros(si, dtype=delta.dtype) for i in range(si): line.fill(0.0) for k in range(sk): factor = delta[i, k] for j in range(sj): line[j] += factor * inv_covariance[k, j] for j in range(sj): res[i] += line[j] * delta[i, j] return res @nb.njit def mean_subtraction(embeddings: np.ndarray, mean: np.ndarray): """Computes embeddings - mean using numba, this is required as I have errors with the default numpy implementation. Args: embeddings: Embedding matrix of dimension FxBxD mean: Mean matrix of dimension BxD Returns: Delta matrix of dimension FxBxD """ output_matrix = np.zeros_like(embeddings) for i in range(embeddings.shape[0]): output_matrix[i] = embeddings[i] - mean return output_matrix @nb.njit(parallel=True) def compute_distance_numba(embedding: np.ndarray, mean: np.ndarray, inv_covariance: np.ndarray) -> np.ndarray: """Compute distance score using numba. Args: embedding: Embedding Vector mean: Mean of the multivariate Gaussian distribution inv_covariance: Inverse Covariance matrix of the multivariate Gaussian distribution. """ batch, channel, height, width = embedding.shape embedding = embedding.reshape(batch, channel, height * width) delta = np.ascontiguousarray(mean_subtraction(embedding, mean).transpose(2, 0, 1)) inv_covariance = np.ascontiguousarray(inv_covariance) intermediate_matrix = np.zeros((delta.shape[0], delta.shape[1]), dtype=delta.dtype) for i in nb.prange(intermediate_matrix.shape[0]): intermediate_matrix[i] = matmul(delta[i], inv_covariance[i]) distances = intermediate_matrix.transpose(1, 0) distances = np.ascontiguousarray(distances) distances = distances.reshape(batch, 1, height, width) distances = np.sqrt(distances.clip(0)) return distances
与已采纳答案相比,改动包括自定义减法函数,以及为中间矩阵指定dtype以避免默认使用np.float64。
内容的提问来源于stack exchange,提问作者scorpion2k40
相关产品推荐
相关产品推荐

