书中有一段代码,它的主要目的是计算测试样本集 X
与训练样本集 X_train
之间的欧氏距离,并将结果存储在一个距离矩阵 dists
中。
下面,我们逐行解释代码。
代码解释
def compute_distances(X, X_train):
'''
输入:
X:测试样本实例矩阵(形状为 (num_test, D))
X_train:训练样本实例矩阵(形状为 (num_train, D))
输出:
dists:欧氏距离矩阵(形状为 (num_test, num_train))
'''
# 获取测试和训练样本的数量
num_test = X.shape[0]
num_train = X_train.shape[0]
# 初始化距离矩阵,大小为 (num_test, num_train)
dists = np.zeros((num_test, num_train))
# 计算测试样本与训练样本的内积矩阵,形状为 (num_test, num_train)
M = np.dot(X, X_train.T)
# 计算测试样本的每个样本的平方和,结果是一个形状为 (num_test,) 的一维数组
te = np.square(X).sum(axis=1)
# 计算训练样本的每个样本的平方和,结果是一个形状为 (num_train,) 的一维数组
tr = np.square(X_train).sum(axis=1)
# 利用向量化公式计算欧氏距离矩阵
dists = np.sqrt(-2 * M + tr + np.matrix(te).T)
return dists
详细说明
-
获取样本数量
num_test = X.shape[0] num_train = X_train.shape[0]
X.shape[0]
返回测试样本的数量,即行数。X_train.shape[0]
返回训练样本的数量。
-
初始化距离矩阵
dists = np.zeros((num_test, num_train))
- 创建一个大小为
(num_test, num_train)
的零矩阵,用于存储每个测试样本与每个训练样本之间的距离。
- 创建一个大小为
-
计算内积矩阵
M = np.dot(X, X_train.T)
np.dot
计算矩阵乘法。X
的形状为(num_test, D)
,X_train.T
的形状为(D, num_train)
。- 结果
M
的形状为(num_test, num_train)
,其中M[i][j]
是第i
个测试样本与第j
个训练样本的内积。
-
计算测试样本的平方和
te = np.square(X).sum(axis=1)
np.square(X)
计算每个元素的平方。.sum(axis=1)
对每一行(即每个样本)的平方值求和。te
是一个长度为num_test
的数组,te[i]
是第i
个测试样本特征的平方和。
-
计算训练样本的平方和
tr = np.square(X_train).sum(axis=1)
- 类似地,计算训练样本的每个样本特征的平方和。
tr
是一个长度为num_train
的数组,tr[j]
是第j
个训练样本特征的平方和。
-
计算欧氏距离矩阵
dists = np.sqrt(-2 * M + tr + np.matrix(te).T)
-
解释公式:
欧氏距离的平方可以表示为:
dist ( X i , X j ) = ∑ k = 1 D ( X i k − X j k ) 2 \text{dist}(X_i, X_j) = \sqrt{\sum_{k=1}^{D} (X_{ik} - X_{jk})^2} dist(Xi,Xj)=k=1∑D(Xik−Xjk)2
展开后:
dist 2 ( X i , X j ) = ∑ k X i k 2 − 2 ∑ k X i k X j k + ∑ k X j k 2 \text{dist}^2(X_i, X_j) = \sum_{k} X_{ik}^2 - 2 \sum_{k} X_{ik} X_{jk} + \sum_{k} X_{jk}^2 dist2(Xi,Xj)=k∑Xik2−2k∑XikXjk+k∑Xjk2
用矩阵形式表示:
dists 2 = te − 2 ⋅ M + tr \text{dists}^2 = \text{te} - 2 \cdot M + \text{tr} dists2=te−2⋅M+tr
-
代码实现:
-2 * M
对应公式中的 − 2 ∑ k X i k X j k -2 \sum_{k} X_{ik} X_{jk} −2∑kXikXjk。tr
是训练样本平方和,需广播(broadcasting)到每一行。np.matrix(te).T
将te
转置为列向量,以便在矩阵加法中正确广播。np.sqrt()
计算平方根,得到最终的欧氏距离矩阵。
-
-
返回距离矩阵
- 最终返回的
dists
是一个大小为(num_test, num_train)
的矩阵,其中每个元素dists[i][j]
表示第i
个测试样本与第j
个训练样本之间的欧氏距离。
- 最终返回的
总结
-
高效计算
- 通过利用矩阵运算和广播机制,避免了使用嵌套循环,大大提高了计算效率。
-
向量化操作
- 使用
numpy
的向量化操作,使代码更加简洁、清晰,同时提升了性能。
- 使用
-
数学原理
- 理解欧氏距离公式的矩阵化表示,对理解这段代码至关重要。
补充说明
-
广播机制(Broadcasting)
- 当执行数组运算时,
numpy
会自动扩展数组的维度,以匹配操作数的形状。 - 在
dists = np.sqrt(-2 * M + tr + np.matrix(te).T)
中,tr
和np.matrix(te).T
会根据需要自动扩展,以进行矩阵加法。
- 当执行数组运算时,
-
为什么使用
np.matrix(te).T
te
是一维数组,形状为(num_test,)
。np.matrix(te).T
将其转置为列向量,形状为(num_test, 1)
,以便在加法中与-2 * M + tr
的形状兼容。