8种距离度量 Python 实战:从欧式到马氏,3个维度对比代码实现
在数据科学和机器学习领域,距离度量是许多算法的核心基础。无论是KNN分类、K-Means聚类,还是推荐系统中的相似度计算,选择合适的距离度量方法直接影响模型效果。本文将带你用Python实现8种经典距离度量,并通过实际代码对比它们的特性。
1. 距离度量基础与Python环境准备
距离度量本质上是衡量两个对象相似程度的数学方法。在机器学习中,我们通常将数据表示为向量,然后通过计算向量之间的距离来判断它们的相似性。不同的距离度量方法适用于不同的场景和数据特性。
首先确保你的Python环境已安装必要的科学计算库:
import numpy as np from scipy.spatial import distance from sklearn.preprocessing import StandardScaler from sklearn.datasets import make_blobs import time我们创建一个简单的二维数据集用于演示:
# 生成示例数据 np.random.seed(42) data = np.random.rand(10, 2) * 10 # 10个二维点 point_a = data[0] # [6.965, 1.635] point_b = data[1] # [1.025, 1.857]2. 8种距离度量的Python实现
2.1 欧式距离 (Euclidean Distance)
欧式距离是最直观的距离度量,表示n维空间中两点间的直线距离。其数学定义为:
$$ d(p,q) = \sqrt{\sum_{i=1}^n (p_i - q_i)^2} $$
Python实现:
def euclidean_dist(a, b): return np.sqrt(np.sum((a - b)**2)) # 使用scipy验证 assert euclidean_dist(point_a, point_b) == distance.euclidean(point_a, point_b)适用场景:KNN、K-Means等算法中默认的距离度量,适用于各维度相关性较小且尺度相似的数据。
2.2 曼哈顿距离 (Manhattan Distance)
曼哈顿距离又称城市街区距离,得名于在规则街区网格中行走的实际距离。计算公式为:
$$ d(p,q) = \sum_{i=1}^n |p_i - q_i| $$
Python代码:
def manhattan_dist(a, b): return np.sum(np.abs(a - b)) # 验证 assert manhattan_dist(point_a, point_b) == distance.cityblock(point_a, point_b)典型应用:在路径规划和高维数据中表现良好,对异常值比欧式距离更鲁棒。
2.3 余弦相似度 (Cosine Similarity)
余弦相似度通过测量两个向量夹角的余弦值来判断相似性,忽略向量长度:
$$ \text{cosine}(p,q) = \frac{p \cdot q}{|p| |q|} $$
实现代码:
def cosine_sim(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) # 验证 assert np.allclose(cosine_sim(point_a, point_b), 1 - distance.cosine(point_a, point_b))优势场景:文本相似度计算、推荐系统等方向敏感的应用。
2.4 汉明距离 (Hamming Distance)
汉明距离衡量两个等长字符串在相同位置上不同字符的个数:
def hamming_dist(a, b): return np.sum(a != b) / len(a) # 示例 str_a = np.array([1, 0, 1, 1, 0]) str_b = np.array([1, 1, 0, 1, 1]) print(f"汉明距离: {hamming_dist(str_a, str_b):.2f}") # 输出0.60主要用途:错误检测与纠正、基因序列比对等离散数据场景。
2.5 切比雪夫距离 (Chebyshev Distance)
切比雪夫距离定义为各坐标数值差的最大值,源于国际象棋中国王的移动方式:
$$ d(p,q) = \max_i |p_i - q_i| $$
Python实现:
def chebyshev_dist(a, b): return np.max(np.abs(a - b)) # 验证 assert chebyshev_dist(point_a, point_b) == distance.chebyshev(point_a, point_b)适用情况:仓储物流路径规划、棋盘类游戏AI等场景。
2.6 闵可夫斯基距离 (Minkowski Distance)
闵可夫斯基距离是欧式距离和曼哈顿距离的推广形式:
$$ d(p,q) = \left( \sum_{i=1}^n |p_i - q_i|^p \right)^{1/p} $$
参数p控制距离类型:
- p=1:曼哈顿距离
- p=2:欧式距离
- p→∞:切比雪夫距离
代码实现:
def minkowski_dist(a, b, p): return np.sum(np.abs(a - b)**p)**(1/p) # 验证与欧式距离等价 assert np.isclose(minkowski_dist(point_a, point_b, 2), euclidean_dist(point_a, point_b))2.7 标准化欧式距离 (Standardized Euclidean)
针对各维度尺度差异大的数据,先标准化再计算欧式距离:
def standardized_euclidean(a, b, data): scaler = StandardScaler() scaled_data = scaler.fit_transform(data) a_scaled = scaler.transform([a])[0] b_scaled = scaler.transform([b])[0] return euclidean_dist(a_scaled, b_scaled)解决痛点:当特征量纲不一致时,避免某些维度主导距离计算。
2.8 马氏距离 (Mahalanobis Distance)
马氏距离考虑特征间的相关性,适用于非球形分布数据:
$$ d(p,q) = \sqrt{(p-q)^T S^{-1} (p-q)} $$
其中S是协方差矩阵。Python实现:
def mahalanobis_dist(a, b, data): cov = np.cov(data.T) inv_cov = np.linalg.inv(cov) diff = a - b return np.sqrt(diff.T @ inv_cov @ diff) # 生成相关数据验证 correlated_data = make_blobs(n_samples=100, centers=1, n_features=2, random_state=42)[0] point_c = correlated_data[0] point_d = correlated_data[1] print(f"马氏距离: {mahalanobis_dist(point_c, point_d, correlated_data):.2f}")核心优势:自动处理特征间的线性相关性,对尺度变化不敏感。
3. 三维度对比分析
我们从计算效率、适用场景和异常值敏感性三个维度系统对比这8种距离度量:
| 距离度量 | 计算速度 | 典型应用场景 | 异常值敏感性 |
|---|---|---|---|
| 欧式距离 | ⚡⚡⚡ | 空间距离、KNN、K-Means | 高 |
| 曼哈顿距离 | ⚡⚡⚡ | 高维数据、路径规划 | 中 |
| 余弦相似度 | ⚡⚡ | 文本相似度、推荐系统 | 低 |
| 汉明距离 | ⚡⚡⚡⚡ | 错误检测、基因序列 | 不适用 |
| 切比雪夫距离 | ⚡⚡⚡ | 棋盘游戏、仓储物流 | 高 |
| 闵可夫斯基距离 | ⚡⚡ | 可调参数适应不同场景 | 取决于p值 |
| 标准化欧式 | ⚡⚡ | 多尺度特征数据 | 中 |
| 马氏距离 | ⚡ | 非球形分布、相关特征 | 低 |
性能提示:在大型数据集上,欧式距离和曼哈顿距离计算效率最高,而马氏距离由于涉及矩阵求逆,计算成本较高。
4. 实战应用:KNN中的距离度量选择
我们通过一个完整的KNN分类示例展示不同距离度量的实际影响:
from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score # 准备数据 X, y = make_blobs(n_samples=1000, centers=3, n_features=5, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3) # 测试不同距离度量 metrics = ['euclidean', 'manhattan', 'cosine', 'mahalanobis'] results = {} for metric in metrics: start = time.time() if metric == 'mahalanobis': knn = KNeighborsClassifier(n_neighbors=5, metric=metric, metric_params={'V': np.cov(X_train.T)}) else: knn = KNeighborsClassifier(n_neighbors=5, metric=metric) knn.fit(X_train, y_train) preds = knn.predict(X_test) acc = accuracy_score(y_test, preds) time_cost = time.time() - start results[metric] = {'accuracy': acc, 'time': time_cost} # 打印结果 print("KNN性能对比:") for metric, res in results.items(): print(f"{metric:10} | 准确率: {res['accuracy']:.3f} | 耗时: {res['time']:.3f}s")典型输出结果可能显示:
- 欧式距离和曼哈顿距离准确率相近且计算最快
- 马氏距离可能在某些数据集上表现更好但耗时明显增加
- 余弦相似度在文本类数据上表现突出
5. 距离度量选择指南
根据数据特性选择距离度量:
数据尺度:
- 各维度尺度相似:欧式距离
- 尺度差异大:标准化欧式或马氏距离
特征相关性:
- 特征独立:欧式距离
- 存在相关性:马氏距离
数据类型:
- 连续数值:欧式/曼哈顿
- 二进制/分类:汉明距离
- 文本数据:余弦相似度
算法效率要求:
- 大规模数据:欧式或曼哈顿距离
- 小规模高维数据:可考虑马氏距离
# 完整距离计算模块 class DistanceMetrics: @staticmethod def euclidean(a, b): return np.sqrt(np.sum((a - b)**2)) @staticmethod def manhattan(a, b): return np.sum(np.abs(a - b)) @staticmethod def cosine(a, b): return 1 - np.dot(a, b)/(np.linalg.norm(a)*np.linalg.norm(b)) # 其他方法实现...在实际项目中,建议先通过数据探索分析特征分布和相关性,再选择2-3种候选距离度量进行实验比较,最终根据模型表现确定最优方案。