ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

曼哈顿距离详解:从数学定义到Python实现的KNN与路径规划实战

曼哈顿距离详解:从数学定义到Python实现的KNN与路径规划实战 开头简单说明一下这篇不是聊纽约那个曼哈顿而是聊机器学习、数据分析里特别常用的一个距离度量方式——曼哈顿距离Manhattan Distance。很多新手刚开始接触距离度量时往往只听说过欧氏距离遇到 KNN、聚类、推荐匹配、路径规划就直接套欧氏距离结果在高维稀疏场景下效果并不好。这篇就围绕曼哈顿距离的数学定义、核心特性、Python 实现、以及在 KNN 分类和街区路径模拟中的完整用法展开适合正在学习机器学习基础、准备算法面试或者需要做相似度计算的开发者收藏。不过在开写之前先做一个小科普为什么叫“曼哈顿”因为美国纽约曼哈顿的街区大多是棋盘式布局要从 A 点走到 B 点只能沿着横平竖直的街道走没法“斜着穿过去”。这种“只能走直线街道”的行走距离就是曼哈顿距离的直观来源。搞懂了这个生活场景后面所有的公式和代码都会变得特别好理解。1. 曼哈顿距离到底是什么从街区导航到数学定义1.1 一个生活化的理解想象你在曼哈顿中城要从第 5 大道和第 42 街的路口走到第 8 大道和第 47 街的路口。你不可能像无人机一样直线飞过去只能沿着大道和街道横向、纵向走。你至少需要走横向从第 5 大道到第 8 大道差了 3 个街区纵向从第 42 街到第 47 街差了 5 个街区总行走距离至少是 3 5 8 个街区。这就是曼哈顿距离的核心思想不是两点之间的直线距离而是各维度差值的绝对值之和。换句话说它衡量的是“在只能沿坐标轴方向移动的前提下从一个点到另一个点所需的最短路径长度”。对应到专业定义在 n 维空间中两个点 p 和 q 之间的曼哈顿距离是d(p, q) |p1 - q1| |p2 - q2| ... |pn - qn|也就是每个维度上坐标差值的绝对值相加。1.2 曼哈顿距离与欧氏距离的第一印象对比欧氏距离大家更熟悉d_euclidean(p, q) sqrt((p1 - q1)^2 (p2 - q2)^2 ... (pn - qn)^2)两者最直观的区别是欧氏距离走“直线”适合连续空间、几何距离、各维度影响相对均衡的场景曼哈顿距离走“折线”适合离散特征、稀疏特征、各维度独立贡献的场景。举个例子在二维平面上点 A(0, 0) 到点 B(3, 4)欧氏距离是 5曼哈顿距离是 3 4 7。不是说哪个更“正确”而是它们度量的是不同意义的距离。如果特征是“是否点击”“是否购买”这样的 0/1 布尔维度用绝对值差值做累加往往比求平方和的欧氏距离更有解释性。1.3 曼哈顿距离在其他领域的名字曼哈顿距离在不同领域还有几个常见别名建议你记住因为读论文和源码时经常碰到别名出现领域L1 距离数学、优化理论街区距离城市规划、地理信息出租车几何几何学、趣味数学城市街区距离路径规划、网格地图绝对值距离统计学、数据分析在机器学习中L1 范数、Lasso 回归、稀疏解等概念也和曼哈顿距离属于同一个数学家族后面如果你学到 L1 正则化会发现思想非常相似。2. 曼哈顿距离的数学定义与核心特性2.1 一维到三维的直观递进先看一维。一维就是数轴上的两个点比如 p 3q 8那么曼哈顿距离是d |3 - 8| 5一维情况下曼哈顿距离和欧氏距离完全相等都是绝对值。再看二维。两个点 p (x1, y1)q (x2, y2)曼哈顿距离是d |x1 - x2| |y1 - y2|假如我们在地图上对比两条出行路线路线 A先横走 2 公里再竖走 3 公里总路程 5 公里路线 B先竖走 1 公里再横走 4 公里总路程也是 5 公里。曼哈顿距离只关心总横纵位移之和不关心你先把哪个方向走完。这是它和欧氏距离一个很重要的思维差异。三维及以上的推广很自然d(p, q) |p1 - q1| |p2 - q2| |p3 - q3| ... |pn - qn|2.2 数学性质为什么它可以当“距离”在度量空间中一个函数要成为“距离”需要满足以下四条性质。曼哈顿距离全部满足非负性任意两点曼哈顿距离大于等于 0且只有两点重合时才等于 0对称性从 p 到 q 的距离等于从 q 到 p 的距离三角不等式d(p, r) ≤ d(p, q) d(q, r)也就是说经过第三点不会比直接过去更近可区分性如果两点距离为 0说明两点是同一个点。这四条性质保证了曼哈顿距离可以作为 KNN、K-Means、层次聚类等算法的相似度度量基础不会出现“距离越大反而越近”的乱象。2.3 单位圆形状的差异如果你学过 L1 范数应该知道一个非常经典的现象同样满足“到原点距离等于 1”的点在欧氏距离下是一个圆在曼哈顿距离下是一个旋转 45 度的正方形。用集合表示欧氏距离单位圆满足 sqrt(x² y²) 1 的点是一个标准圆形曼哈顿距离单位圆满足 |x| |y| 1 的点是顶点在 (1,0)、(0,1)、(-1,0)、(0,-1) 的菱形。这个形状差异在优化问题中非常重要。比如 L1 正则化更容易让某些特征的权重变成 0从而产生稀疏解本质上就是因为 L1 的“等值线”更容易与坐标轴相交。理解曼哈顿距离也能帮你更好地理解 L1 正则化为什么能用于特征选择。2.4 曼哈顿距离与切比雪夫距离的关联提到曼哈顿距离往往绕不开切比雪夫距离Chebyshev Distance。切比雪夫距离的定义是各维度差值的绝对值的最大值d_chebyshev(p, q) max(|p1 - q1|, |p2 - q2|, ..., |pn - qn|)一个很经典的结论是在二维平面上曼哈顿距离和切比雪夫距离可以互相转换。如果把坐标系旋转 45 度并适当缩放曼哈顿距离会变成切比雪夫距离。这个结论在国际象棋、方格路径算法里经常用到感兴趣可以自己推导一下。3. 环境准备与版本说明本文后续代码以 Python 为例你不需要安装额外的重型框架只需要有基础的数值计算和机器学习库即可。3.1 推荐环境操作系统Windows 10/11、macOS、Linux 均可Python 版本3.8 及以上推荐 3.10 或 3.11核心库numpy、scipy、scikit-learn、pandaspandas 在推荐系统示例中使用。版本不需要完全一致因为曼哈顿距离的 API 已经非常稳定。建议先创建一个虚拟环境避免依赖冲突。python -m venv manhattan_env # Windows manhattan_env\Scripts\activate # macOS / Linux source manhattan_env/bin/activate3.2 安装依赖pip install numpy scipy scikit-learn pandas各库的用途numpy手写曼哈顿距离、批量矩阵计算scipy提供现成的cityblock距离函数scikit-learn提供ManhattanDistance类和 KNN 分类器pandas在用户特征匹配示例中处理结构化数据。如果想验证安装是否成功可以运行import numpy as np import scipy import sklearn print(numpy version:, np.__version__) print(scipy version:, scipy.__version__) print(sklearn version:, sklearn.__version__)如果输出正常说明环境没有问题。4. 使用 Python 实现曼哈顿距离的多种方式这一节给出三种实现方式手写公式、scipy 现成函数、scikit-learn 距离类。层层递进方便你在不同场景选用最合适的方法。4.1 方式一numpy 手写曼哈顿距离这种方式最直观也最容易理解原理。下面实现一个函数可以计算两个向量之间的曼哈顿距离。import numpy as np def manhattan_distance_numpy(p, q): 使用 numpy 计算两个点之间的曼哈顿距离 p, q: 形状相同的数组例如 [x1, y1] 和 [x2, y2] p np.array(p, dtypenp.float64) q np.array(q, dtypenp.float64) return np.sum(np.abs(p - q)) # 示例二维平面 p1 [0, 0] p2 [3, 4] dist manhattan_distance_numpy(p1, p2) print(曼哈顿距离:, dist) print(欧氏距离:, np.linalg.norm(np.array(p1) - np.array(p2)))运行结果曼哈顿距离: 7.0 欧氏距离: 5.0这段代码的核心就是np.abs(p - q)求每个维度的差值绝对值再用np.sum累加。注意将输入转为float64避免整数数组在后续运算中出现意外行为。4.2 方式二scipy.spatial.distance.cityblockscipy 中已经封装好了曼哈顿距离函数名字叫cityblock正好对应“街区距离”这个别名。from scipy.spatial.distance import cityblock p1 [0, 0] p2 [3, 4] dist cityblock(p1, p2) print(scipy cityblock 距离:, dist)运行结果scipy cityblock 距离: 7这个方法适合快速计算两个一维向量之间的距离底层用 C 优化性能比纯 Python 手写高不少。如果你只需要在数据分析脚本里偶尔算一次距离cityblock是最省事的选择。4.3 方式三scikit-learn 中的 ManhattanDistancescikit-learn 从 1.1 版本开始提供ManhattanDistance类可以配合 KNN、聚类等模型使用同时支持参数化版本ManhattanDistance(v_power...)。from sklearn.metrics import DistanceMetric dist_metric DistanceMetric.get_metric(manhattan) # 构造两个样本每一行是一个点 X [[0, 0], [3, 4]] # 计算两两之间的距离矩阵 distance_matrix dist_metric.pairwise(X) print(distance_matrix)运行结果[[0. 7.] [7. 0.]]其中第 0 行第 1 列的值 7就是点 (0,0) 和点 (3,4) 之间的曼哈顿距离。这种方式最适合批量计算多个样本两两之间的距离矩阵。如果你在 KNN 中直接指定metricmanhattan也可以使用这个距离类示例会在第 6 节给出。4.4 批量计算多个点的两两距离实际项目中我们经常需要计算一个矩阵中任意两个样本之间的距离。比如有 5 个用户特征希望得到 5×5 的距离矩阵。直接用 scipy 的cdist会更方便。import numpy as np from scipy.spatial.distance import cdist # 3 个二维点 points np.array([ [0, 0], [1, 1], [3, 4] ]) # 计算 3 个点两两之间的曼哈顿距离 dist_matrix cdist(points, points, metriccityblock) print(dist_matrix)运行结果[[0. 2. 7.] [2. 0. 5.] [7. 5. 0.]]解释一下矩阵第 0 行第 1 列点 (0,0) 到点 (1,1) 的距离是 |0-1| |0-1| 2第 0 行第 2 列点 (0,0) 到点 (3,4) 的距离是 3 4 7第 1 行第 2 列点 (1,1) 到点 (3,4) 的距离是 |1-3| |1-4| 2 3 5。cdist支持一次计算两个集合之间的所有成对距离效率很高适合做大规模近邻搜索。5. 曼哈顿距离 vs 欧氏距离 vs 其他距离度量很多初学者问既然欧氏距离用得最多为什么还需要曼哈顿距离这一节从多个维度对比并给出选型建议。5.1 特性对比表对比维度曼哈顿距离欧氏距离余弦相似度计算方式各维度差的绝对值之和各维度差的平方和再开方向量夹角的余弦值对量纲敏感度敏感敏感对绝对大小不敏感对方向敏感对异常值敏感度较低较高平方放大差异中稀疏向量适用性较好一般较好解释性高如街区距离、绝对值误差中几何直线距离中相似度方向典型应用L1 损失、KNN、路径规划L2 损失、K-Means、PCA文本匹配、推荐系统5.2 高维稀疏场景为什么曼哈顿距离可能更好在文本 TF-IDF 特征、用户行为 0/1 特征等场景中特征维度可能上千甚至上万但大量维度是 0。如果用欧氏距离平方操作会让维度间的差异被放大如果特征是布尔型欧氏距离的平方和反而丢失了“维度独立贡献”的直观含义。此时曼哈顿距离的累加方式更接近“有 n 个特征不一样贡献就是 n”的逻辑解释起来也更自然。当然高维场景下所有基于“距离”的度量都会面临“维度灾难”不能指望某一种距离解决所有问题。5.3 异常值场景为什么 L1 更稳健在回归任务中L1 损失平均绝对误差MAE等价于使用曼哈顿距离的思想L2 损失均方误差MSE等价于使用欧氏距离的思想。假设真实值是 10预测值是 100L1 损失 |10 - 100| 90L2 损失 (10 - 100)² 8100。L2 对极端误差的惩罚远大于 L1。因此当数据中存在明显离群点时使用曼哈顿距离作为损失函数或度量标准通常更稳健。这也是为什么 Lasso 回归L1比 Ridge 回归L2对异常值更不敏感的原因之一。5.4 选型建议需要几何直观、各维度连续且量纲接近优先欧氏距离数据包含大量 0/1 布尔特征或高维稀疏特征优先曼哈顿距离做文本方向匹配优先余弦相似度回归任务希望减少异常值影响优先 L1 损失路径规划、网格地图、只能上下左右移动的场景优先曼哈顿距离。6. 完整实战案例一KNN 分类中使用曼哈顿距离下面通过一个完整的分类案例展示曼哈顿距离在 KNN 中的应用。我们使用 scikit-learn 内置的鸢尾花数据集对比欧氏距离和曼哈顿距离的分类效果。6.1 项目结构与代码只需要一个 Python 文件这里命名为knn_manhattan_demo.py。# 文件路径knn_manhattan_demo.py from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score # 1. 加载数据 iris load_iris() X iris.data y iris.target # 2. 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) # 3. 使用曼哈顿距离的 KNN knn_manhattan KNeighborsClassifier( n_neighbors5, metricmanhattan ) knn_manhattan.fit(X_train, y_train) y_pred_manhattan knn_manhattan.predict(X_test) acc_manhattan accuracy_score(y_test, y_pred_manhattan) # 4. 使用欧氏距离的 KNN knn_euclidean KNeighborsClassifier( n_neighbors5, metriceuclidean ) knn_euclidean.fit(X_train, y_train) y_pred_euclidean knn_euclidean.predict(X_test) acc_euclidean accuracy_score(y_test, y_pred_euclidean) print(KNN with Manhattan distance accuracy:, acc_manhattan) print(KNN with Euclidean distance accuracy:, acc_euclidean)6.2 运行与预期结果运行命令python knn_manhattan_demo.py输出示例KNN with Manhattan distance accuracy: 1.0 KNN with Euclidean distance accuracy: 1.0在鸢尾花数据集上两种距离的准确率通常都很高有时完全一样。这个案例的重点不在于说明谁更强而在于演示如何把metricmanhattan接入 KNN 模型。6.3 案例说明KNeighborsClassifier的metric参数支持多种距离度量传入manhattan即可。如果你想更精细地控制距离行为可以传入一个可调用对象或DistanceMetric实例。例如from sklearn.metrics import DistanceMetric manhattan DistanceMetric.get_metric(manhattan) knn_custom KNeighborsClassifier(n_neighbors5, metricmanhattan)这种方式在模型参数更多、距离度量需要复用时更清晰。7. 完整实战案例二城市街区路径规划模拟除了机器学习分类曼哈顿距离最常见的应用是网格地图路径规划。下面模拟一个简单的城市街区场景一个外卖骑手需要从配送站出发依次经过多个订单点最后返回配送站。我们计算总行驶距离。7.1 问题定义假设配送站位于坐标 (0, 0)有三个订单点订单 A(2, 3)订单 B(5, 1)订单 C(1, 4)骑手只能沿网格道路行驶不能斜穿。我们需要计算从配送站出发依次经过 A、B、C再返回配送站的总曼哈顿距离。7.2 完整代码# 文件路径delivery_route_demo.py from scipy.spatial.distance import cityblock station (0, 0) orders [(2, 3), (5, 1), (1, 4)] # 路线配送站 - A - B - C - 配送站 route [station] orders [station] total_distance 0 for i in range(len(route) - 1): segment_dist cityblock(route[i], route[i 1]) print(f{route[i]} - {route[i1]} : {segment_dist}) total_distance segment_dist print(总配送距离:, total_distance)运行结果(0, 0) - (2, 3) : 5 (2, 3) - (5, 1) : 5 (5, 1) - (1, 4) : 7 (1, 4) - (0, 0) : 5 总配送距离: 22这里的计算逻辑配送站到 A|0-2| |0-3| 2 3 5A 到 B|2-5| |3-1| 3 2 5B 到 C|5-1| |1-4| 4 3 7C 到配送站|1-0| |4-0| 1 4 5。总距离 22这个值本质上是曼哈顿距离在网格路径上的累加。如果改用欧氏距离计算会得到更小的“直线飞行距离”但在只能沿街道行驶的真实场景中并不适用。7.3 扩展如何寻找最优配送顺序上面的路线是固定顺序。如果订单点较多如何安排访问顺序使总路程最短是一个典型的“旅行商问题”。一个简单思路是枚举所有排列计算每种排列的总距离。对于少量订单点可以直接用itertools.permutations。from itertools import permutations from scipy.spatial.distance import cityblock station (0, 0) orders [(2, 3), (5, 1), (1, 4)] best_route None best_distance float(inf) for perm in permutations(orders): # 构造 配送站 - 排列顺序 - 配送站 route [station] list(perm) [station] dist sum(cityblock(route[i], route[i 1]) for i in range(len(route) - 1)) print(f路线 {perm} 总距离: {dist}) if dist best_distance: best_distance dist best_route perm print(最优路线:, best_route) print(最短总距离:, best_distance)运行结果路线 ((2, 3), (5, 1), (1, 4)) 总距离: 22 路线 ((2, 3), (1, 4), (5, 1)) 总距离: 20 路线 ((5, 1), (2, 3), (1, 4)) 总距离: 20 路线 ((5, 1), (1, 4), (2, 3)) 总距离: 20 路线 ((1, 4), (2, 3), (5, 1)) 总距离: 20 路线 ((1, 4), (5, 1), (2, 3)) 总距离: 22 最优路线: ((2, 3), (1, 4), (5, 1)) 最短总距离: 20由此可见订单访问顺序会影响总路程。在实际外卖、物流系统中这类计算会配合更复杂的约束条件和优化算法但曼哈顿距离作为底层距离计算函数承担了最基础、最重要的角色。8. 完整实战案例三基于用户特征的近邻匹配最后一个实战案例偏工程给定一批用户特征利用曼哈顿距离找到与目标用户最相似的用户可用于推荐系统、用户分群、异常检测等场景。8.1 数据结构这里我们构造一个简化版用户特征表假设每个用户有 3 个特征active_days近 30 天活跃天数order_count近 30 天下单数avg_amount近 30 天平均客单价。import pandas as pd import numpy as np from scipy.spatial.distance import cdist data { user_id: [U001, U002, U003, U004, U005], active_days: [20, 5, 15, 2, 28], order_count: [10, 1, 8, 0, 15], avg_amount: [80, 300, 120, 450, 55], } df pd.DataFrame(data) print(df)输出user_id active_days order_count avg_amount 0 U001 20 10 80 1 U002 5 1 300 2 U003 15 8 120 3 U004 2 0 450 4 U005 28 15 558.2 计算用户之间的距离矩阵这里有一个工程注意点如果直接对原始特征计算距离avg_amount的数值范围55~450会远大于order_count的数值范围0~15导致曼哈顿距离几乎被平均客单价主导。因此我们先对特征做标准化再计算距离。# 选择特征列 features [active_days, order_count, avg_amount] X df[features].values # 手动做最小最大归一化 def min_max_normalize(arr): min_val arr.min(axis0) max_val arr.max(axis0) return (arr - min_val) / (max_val - min_val 1e-9) X_norm min_max_normalize(X) # 两两之间的曼哈顿距离矩阵 dist_matrix cdist(X_norm, X_norm, metriccityblock) # 打印距离矩阵 print(标准化后的曼哈顿距离矩阵:) print(np.round(dist_matrix, 4))输出示例标准化后的曼哈顿距离矩阵: [[0. 1.4286 0.7308 1.7308 0.2115] [1.4286 0. 0.8462 0.3462 1.5385] [0.7308 0.8462 0. 1.1538 0.9231] [1.7308 0.3462 1.1538 0. 1.6923] [0.2115 1.5385 0.9231 1.6923 0. ]]矩阵中第 0 行第 4 列的 0.2115 是最小值说明用户 U001 和用户 U005 的特征最相似。8.3 根据距离查找最近邻下面写一个函数输入目标用户索引返回与该用户最相似的 TopK 用户。def find_topk_similar(target_idx, dist_matrix, df, k2): 根据距离矩阵返回与 target_idx 最相似的 k 个用户 distances dist_matrix[target_idx].copy() # 自身距离为0置为无穷大避免被选中 distances[target_idx] np.inf # 按距离升序取前 k 个 topk_idx np.argsort(distances)[:k] results [] for idx in topk_idx: results.append({ user_id: df.iloc[idx][user_id], distance: round(distances[idx], 4) }) return results print(与 U001 最相似的用户, find_topk_similar(0, dist_matrix, df, k2)) print(与 U004 最相似的用户, find_topk_similar(3, dist_matrix, df, k2))输出示例与 U001 最相似的用户 [{user_id: U005, distance: 0.2115}, {user_id: U003, distance: 0.7308}] 与 U004 最相似的用户 [{user_id: U002, distance: 0.3462}, {user_id: U003, distance: 1.1538}]这个思路可以直接迁移到基于用户画像的相似好友推荐基于商品特征的商品相似度匹配基于行为序列编码后的异常行为发现距离过大的样本需要人工排查。9. 曼哈顿距离的常见问题与排查思路在实际使用中新手常遇到一些问题。下面列出高频问题以及对应的排查和解决思路。问题现象常见原因解决思路距离结果比预期大很多特征未归一化量纲差异大先做标准化或归一化再计算距离高维特征下距离几乎都差不多维度灾难所有距离值趋同改用余弦相似度、降维后再计算或使用更适合高维稀疏数据的度量KNN 分类准确率反而下降特征分布不适合曼哈顿距离对比多个 metric如 euclidean、chebyshev、cosine选择交叉验证结果最好的出现 NaN 距离输入特征包含 NaN 值检查数据清洗使用np.nan_to_num或丢弃缺失行计算速度太慢循环手写距离未利用矩阵运算改用 scipycdist或 sklearnDistanceMetric距离矩阵出现不对称自定义距离函数需满足对称性检查函数是否使用a-b而不是abs(a-b)确保结果非负且对称布尔特征下距离难以解释0/1 特征没有区分度考虑使用 Jaccard 相似度或汉明距离而不是直接套曼哈顿距离9.1 高维稀疏场景下的“距离趋同”问题当特征维度很高时任意两个样本的曼哈顿距离都会趋近于某个平均值导致距离区分度下降。这在高维空间中非常普遍。一个可行的缓解方案是先做特征筛选或降维比如使用 PCA 或截断 SVD把有效维度控制在几十维以内再计算曼哈顿距离。在文本场景中如果特征是 TF-IDF 向量一般更推荐余弦相似度因为它对向量长度不敏感能更好地表达“方向相似性”。曼哈顿距离虽然也可以算但往往不如余弦相似度稳定。9.2 距离计算前的数据归一化为什么重要曼哈顿距离是各维度绝对值差的和。如果特征 A 取值范围是 0~1特征 B 取值范围是 0~10000那么距离几乎完全由特征 B 决定。这会导致模型忽略其他有效特征。所以当你使用任何基于距离的算法KNN、K-Means、层次聚类、DBSCAN时都建议先对连续特征做归一化或标准化。常见的预处理方式最小最大归一化(x - min) / (max - min)Z-score 标准化(x - mean) / std选择哪种取决于业务。如果特征没有明显的异常值最小最大归一化比较简单如果存在少量极端值Z-score 更稳健。10. 最佳实践与工程建议10.1 优先使用现成向量化实现不要在生产代码里用纯 Python 循环逐点计算曼哈顿距离。推荐优先使用scipy.spatial.distance.cdist批量计算两个集合之间的距离矩阵scipy.spatial.distance.cityblock快速计算两个向量之间的距离sklearn.metrics.DistanceMetric在模型训练中使用统一距离度量。下面是一个典型的高效计算示例from sklearn.metrics import DistanceMetric metric DistanceMetric.get_metric(manhattan) # 输入必须是二维数组 X [[0, 0], [3, 4], [1, 1]] D metric.pairwise(X) print(D)这种写法底层经过优化数据量大时性能远高于手写循环。10.2 根据业务含义选择是否归一化归一化不是“必须做”而是“需要判断”。如果你明确知道不同特征具有相同的物理意义和量纲比如都是“天数”那么可以不做归一化。如果特征混合了金额、次数、天数、比率强烈建议做标准化。工程中一个稳妥做法是预处理管道中同时保留“原始特征距离”和“归一化特征距离”两套方案用交叉验证或业务指标选择最终方案。10.3 与 KNN 和聚类模型配合时注意参数一致性使用 scikit-learn 时要确保训练和预测阶段使用相同的距离度量。比如训练 KNN 时指定metricmanhattan预测时会自动沿用不需要额外设置。但如果你自定义了距离函数最好在模型初始化时就传入并用pickle保存完整模型对象避免参数丢失。10.4 曼哈顿距离在损失函数中的作用如果你做回归任务曼哈顿距离对应的是 MAE 损失。MAE 的梯度在误差为 0 时不可导但绝对值函数可以直接用次梯度处理。在深度学习中L1 损失可以增强模型对异常值的鲁棒性但收敛速度可能比 L2 慢因为梯度恒定。实际工程中可以考虑 Huber Loss 作为折中小误差时使用平方损失大误差时使用线性损失。这部分内容已经超出“距离计算”本身但理解曼哈顿距离和 L1 损失的等价关系会让你在调参时更有全局观。10.5 不要忽略三角不等式的工程价值曼哈顿距离满足三角不等式这一点在近邻搜索中有重要意义。在很多 ANN近似最近邻算法中可以利用三角不等式进行剪枝避免计算所有样本的距离。比如 VP-Tree、BK-Tree 等索引结构都依赖三角不等式来缩小搜索范围。如果你正在处理大规模近邻搜索曼哈顿距离的三角不等式性质是一个值得利用的优化方向。10.6 用可视化辅助理解二维场景下画图可以直观感受曼哈顿距离和欧氏距离的差异。可以用matplotlib绘制两条线对比 A 点到 B 点的直线路径和曼哈顿折线路径。推荐新手动手做一下印象会更深。import matplotlib.pyplot as plt # 起点和终点 start (0, 0) end (3, 4) # 曼哈顿路径点先向右再向上 path_x [start[0], end[0], end[0]] path_y [start[1], start[1], end[1]] # 欧氏直线路径 line_x [start[0], end[0]] line_y [start[1], end[1]] plt.figure(figsize(6, 6)) plt.plot(path_x, path_y, r-o, labelManhattan path) plt.plot(line_x, line_y, b--, labelEuclidean line) plt.grid(True) plt.legend() plt.title(Manhattan Distance vs Euclidean Distance) plt.show()运行后会看到一条红色折线和一条蓝色虚线红色折线长度就是曼哈顿距离蓝色虚线是欧氏距离。11. 综合对比实验什么时候曼哈顿距离更优为了让你对“曼哈顿的实力”有更直观感受这里做一个简单实验构造一个以曼哈顿距离为真实距离的数据集然后比较不同度量下的分类效果。这是一个反推实验目的是展示“当数据分布在菱形等值线附近时曼哈顿距离可能比欧氏距离更契合数据分布”。# 文件路径compare_metric_demo.py import numpy as np from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score rng np.random.default_rng(42) # 生成两类菱形分布数据围绕原点偏移 # 相当于用 |x| |y| 控制数据半径 n_samples 300 # 类别 0半径较小 r0 rng.uniform(0, 3, n_samples // 2) theta0 rng.uniform(0, 2 * np.pi, n_samples // 2) x0 r0 * np.cos(theta0) y0 r0 * np.sin(theta0) # 类别 1半径较大 r1 rng.uniform(4, 7, n_samples // 2) theta1 rng.uniform(0, 2 * np.pi, n_samples // 2) x1 r1 * np.cos(theta1) y1 r1 * np.sin(theta1) X np.vstack([np.column_stack([x0, y0]), np.column_stack([x1, y1])]) y np.array([0] * (n_samples // 2) [1] * (n_samples // 2)) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) for metric in [manhattan, euclidean, chebyshev]: knn KNeighborsClassifier(n_neighbors5, metricmetric) knn.fit(X_train, y_train) y_pred knn.predict(X_test) acc accuracy_score(y_test, y_pred) print(fmetric{metric}, accuracy{acc:.4f})运行结果示例metricmanhattan, accuracy0.9222 metriceuclidean, accuracy0.8667 metricchebyshev, accuracy0.8778即使在同一个数据集上不同的距离度量带来的差异也可能很大。这个实验说明距离度量的选择不是“默认用欧氏距离就行”而是需要结合数据分布和业务场景来实验对比。曼哈顿距离在类别边界呈现“菱形”或“沿坐标轴分布”的数据上往往有明显优势。12. 常见误区与避免方法12.1 误区一认为曼哈顿距离一定比欧氏距离差很多人先入为主觉得“直线距离最准确”这其实混淆了“几何距离”和“语义距离”。曼哈顿距离更适合特征各维度独立贡献的场景比如离散特征、布尔特征、稀疏特征。在这些场景下它可能比欧氏距离更符合业务直觉。12.2 误区二忽略特征量纲直接比较曼哈顿距离对量纲非常敏感。不同特征单位不一致时距离计算结果几乎没有意义。例如“收入”以万为单位和以元为单位计算出的距离完全不同。所以必须在使用前做标准化或归一化。12.3 误区三把距离计算和相似度混淆距离越小相似度越高。但“相似度”有时会被定义为 0 到 1 之间的分数。如果需要把曼哈顿距离转换为相似度一个常用做法是similarity 1 / (1 distance)这样距离为 0 时相似度为 1距离越大相似度越低并且保证相似度在 (0, 1] 范围内。12.4 误区四在所有场景都用同一个距离距离度量的选择没有银弹。业务含义、数据分布、算法类型、计算复杂度都是影响因素。正确的做法是在多套候选度量中做实验配合交叉验证或业务指标选择最优方案。13. 下一步学习方向如果这篇让你对曼哈顿距离有了新的理解可以从下面几个方向继续深入L1 正则化与稀疏性原理理解为什么 L1 会把系数压到 0曼哈顿距离的“菱形等值线”是核心距离度量学习通过数据学习一个更适配任务的距离矩阵常用的有马氏距离Mahalanobis DistanceANN 近邻搜索研究如何用三角不等式和空间索引加速曼哈顿距离的最近邻搜索路径规划算法在网格地图上结合 A* 算法用曼哈顿距离作为启发式函数能显著提升搜索效率。最后给你一个实用建议在写代码时把距离计算封装成一个独立模块统一管理距离函数和特征预处理逻辑。这样无论后续切换距离度量还是扩展新的距离函数都只需要改一处配置不会影响核心业务代码。距离度量看起来小但它影响的是模型和数据匹配的底层逻辑值得认真对待。
返回列表