问题描述:样本维度为n,测试样本数量为m1,训练样本数量为m2。求出描述测试样本与训练样本之间的距离的矩阵dists,其中dists.shape为(m1, m2)。测试集为矩阵X,维度为(m1, d);训练集为矩阵X_train,维度为(m2, d)。
问题难点:如何不使用循环计算出dists?
- 使用两层循环显而易见非常简单,最内层循环直接计算测试样本i与训练样本j之间的距离。
- 使用一层训练也不难想。每一次循环计算出测试样本i分别与每个训练样本之间的距离。使用Python的broadcast机制即可完成。
- 不使用循环计算出距离矩阵dists就挺难想的了,那么应该如何解决呢?应该利用numpy的矩阵乘法以及(x - y)^2 = x^2 + y^2 - 2*x*y解决。
两层循环
X[i] - self.X_train[j]计算出X[i]与self.X_train[j]之间的距离,计算结果为一维矩阵(维度为n)。使用np.square或者**2计算出每个维度的平方然后再np.sum一波即可计算出平方和,再使用np.sqrt即可计算出L2距离。代码如下
def compute_distances_one_loop(self, X):
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
diff = np.square(X[i] - self.X_train)
dists[i] = np.sqrt(np.sum(diff, axis=1))
return dists
- 一层循环
一层循环主要利用了Python的broadcast机制。X[i]为一个行向量,X_train为一个二维矩阵,两者直接相减,所得结果的元素(j, k)为X[i,k]-X_train[j, k]。实现代码如下
def compute_distances_one_loop(self, X):
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
for i in range(num_test):
diff = np.square(X[i] - self.X_train)
dists[i] = np.sqrt(np.sum(diff, axis=1))
return dists
- 无循环
首先展示实现代码
M = np.dot(X, self.X_train.T)计算出了(x - y)^2 = x^2 + y^2 - 2*x*y中的x*y。其中矩阵维度为(m1, m2)。
te = np.diag(np.dot(X, X.T))取出了X * X.T的对角线,元素为每个测试样本的各个维度平方的和。tr亦然,只是主体为训练样本。te、tr是一个行向量,te维度为m1,tr维度为m2
te = np.reshape(np.repeat(te, ncol), M.shape)这行代码,首先将te的每个元素重复m2次,并reshape为(m1, m2)。每一行元素都是相等的,第i行的元素为第i个测试样本的平方和。te计算出了x^2。
tr = np.reshape(np.repeat(tr, nrow), M.T.shape)亦然,只是重复了m1次。tr的shape为(m2, m1)。tr.T的每行元素的第j个元素为第j个训练样本的平方和。tr.T各行内容重复。tr.T计算出了y^2。
sq = -2 * M + te + tr.T计算出了(x - y)^2 = x^2 + y^2 - 2*x*y
def compute_distances_no_loops(self, X):
num_test = X.shape[0]
num_train = self.X_train.shape[0]
dists = np.zeros((num_test, num_train))
M = np.dot(X, self.X_train.T)
nrow = M.shape[0]
ncol = M.shape[1]
te = np.diag(np.dot(X, X.T))
tr = np.diag(np.dot(self.X_train, self.X_train.T))
te = np.reshape(np.repeat(te, ncol), M.shape)
tr = np.reshape(np.repeat(tr, nrow), M.T.shape)
sq = -2 * M + te + tr.T
dists = np.sqrt(sq)
return dists
- 三种计算方式的时间对比
Two loop version took 23.252080 seconds
One loop version took 51.822145 seconds
No loop version took 0.592002 seconds
显而易见,循环数越少,运行时间越短,利用了numpy向量化计算的快速。