거리 지표: 유클리드, 맨해튼, 민코프스키
거리 지표를 비교하고, 맨해튼 거리가 유클리드 거리보다 뛰어난 경우를 이해하며, 사용자 지정 지표를 KNeighborsClassifier에 전달합니다.
거리 지표: 유클리드, 맨해튼, 민코프스키은(는) CoddyKit의 무료 Machine Learning Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Machine Learning Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Machine Learning Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Why Distance Metrics Matter in KNN
KNN defines nearest neighbors using a distance metric — a mathematical function that quantifies how far apart two points are in feature space. The choice of metric directly affects which neighbors are selected, and therefore what the model predicts. Different metrics make different assumptions about the geometry of the data. Euclidean distance assumes diagonal movement is valid; Manhattan distance only allows axis-aligned movement; cosine similarity ignores magnitude and focuses on direction. No single metric is universally best — the right choice depends on the problem structure.
import numpy as np
A = np.array([0, 0])
B = np.array([3, 4])
# Euclidean: straight-line distance
euclidean = np.sqrt(np.sum((A - B)**2))
print('Euclidean:', euclidean) # 5.0
# Manhattan: sum of absolute differences
manhattan = np.sum(np.abs(A - B))
print('Manhattan:', manhattan) # 7
# Chebyshev: maximum single-axis difference
chebyshev = np.max(np.abs(A - B))
print('Chebyshev:', chebyshev) # 4Euclidean Distance: L2 Norm
Euclidean distance (also called L2 distance or L2 norm) measures the straight-line distance between two points. In 2D it follows the Pythagorean theorem: sqrt(dx^2 + dy^2). In n dimensions: sqrt(sum of squared differences). It is the most intuitive metric and is the default in KNeighborsClassifier. Euclidean distance works well when features are continuous, on a similar scale, and when the notion of diagonal proximity makes physical sense — for instance, geographic coordinates or sensor readings.
import numpy as np
def euclidean(a, b):
return np.sqrt(np.sum((np.array(a) - np.array(b))**2))
# 2D example
print('2D:', euclidean([0, 0], [3, 4])) # 5.0
# 3D example
print('3D:', euclidean([1, 2, 3], [4, 6, 3]).round(2)) # 5.0
# Using scipy for efficiency
from scipy.spatial.distance import euclidean as sp_euclidean
print('scipy:', sp_euclidean([0, 0], [3, 4]))Manhattan Distance: L1 Norm
Manhattan distance (L1 norm, city-block distance, taxicab distance) sums the absolute differences along each axis: sum(|a_i - b_i|). The name comes from the grid layout of Manhattan streets — you can only travel along blocks, not diagonally. Manhattan distance is more robust to outliers than Euclidean because it uses absolute values instead of squares. It is often preferred for high-dimensional data and for features that represent counts, ratings, or other quantities where diagonal movement is not physically meaningful.
import numpy as np
def manhattan(a, b):
return np.sum(np.abs(np.array(a) - np.array(b)))
print('Manhattan (0,0)-(3,4):', manhattan([0,0], [3,4])) # 7
print('Euclidean (0,0)-(3,4):', np.linalg.norm([3,4])) # 5.0
# Manhattan treats 3+4=7 units of travel
# Euclidean takes the diagonal shortcut = 5.0
# In a grid city, only Manhattan is physically achievable
from scipy.spatial.distance import cityblock
print('scipy cityblock:', cityblock([0,0], [3,4]))Minkowski Distance: Generalising L1 and L2
Minkowski distance is a generalisation that unifies Euclidean and Manhattan under a single formula: (sum(|a_i - b_i|^p))^(1/p). When p=1, it equals Manhattan distance. When p=2, it equals Euclidean distance. When p → infinity, it approaches Chebyshev distance (maximum single-axis difference). In scikit-learn, KNeighborsClassifier uses Minkowski with p=2 by default. You can explore other values of p as a hyperparameter — though p values other than 1 and 2 are rarely used in practice.
import numpy as np
def minkowski(a, b, p):
a, b = np.array(a), np.array(b)
return np.sum(np.abs(a - b)**p)**(1/p)
a, b = [0, 0], [3, 4]
for p in [1, 2, 3, 10, 100]:
d = minkowski(a, b, p)
print(f'p={p}: {d:.4f}')
# p=1 -> 7.0 (Manhattan)
# p=2 -> 5.0 (Euclidean)
# p->inf -> 4.0 (Chebyshev = max(3,4))Passing Metrics to KNeighborsClassifier
Scikit-learn lets you specify the distance metric via the metric parameter. Common string options include 'euclidean', 'manhattan', 'minkowski' (with additional p parameter), 'chebyshev', and 'cosine'. You can also pass a callable Python function as a custom metric. When using non-standard metrics, set algorithm='ball_tree' or algorithm='kd_tree' for efficient neighbor lookup, or algorithm='brute' for a guaranteed-correct but slower exhaustive search.
from sklearn.neighbors import KNeighborsClassifier
# Euclidean (default)
knn_l2 = KNeighborsClassifier(n_neighbors=5, metric='euclidean')
# Manhattan
knn_l1 = KNeighborsClassifier(n_neighbors=5, metric='manhattan')
# Minkowski with p=1.5
knn_mk = KNeighborsClassifier(n_neighbors=5, metric='minkowski', p=1.5)
# Chebyshev
knn_ch = KNeighborsClassifier(n_neighbors=5, metric='chebyshev')
# Cosine similarity (for text/angle-based)
knn_cos = KNeighborsClassifier(n_neighbors=5, metric='cosine',
algorithm='brute')Euclidean vs Manhattan: A Practical Comparison
When comparing Euclidean and Manhattan empirically, the difference appears most clearly in the presence of outlier features. Euclidean squares differences, making one large deviation dominate the total distance. Manhattan sums absolute values, treating all deviations proportionally. In practice, for image data or continuous physical measurements, Euclidean often wins. For high-dimensional sparse data (text, user-item ratings, counts), Manhattan tends to be more stable because it does not amplify the effect of any single dimension.
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_wine
X, y = load_wine(return_X_y=True)
for metric in ['euclidean', 'manhattan', 'chebyshev']:
pipe = Pipeline([
('sc', StandardScaler()),
('knn', KNeighborsClassifier(n_neighbors=5, metric=metric))
])
score = cross_val_score(pipe, X, y, cv=10).mean()
print(f'{metric:12}: {score:.3f}')Cosine Similarity for Text Data
Cosine similarity measures the angle between two vectors rather than their magnitude. Two documents are considered similar if they point in the same direction in feature space, regardless of document length. Cosine distance = 1 - cosine similarity. This is the preferred metric for text classification with TF-IDF vectors, where two documents can be very different in length but use the same vocabulary in similar proportions. Note that cosine distance is not a true metric (violates triangle inequality) but works well in practice for KNN on text.
import numpy as np
def cosine_distance(a, b):
a, b = np.array(a, dtype=float), np.array(b, dtype=float)
cos_sim = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
return 1 - cos_sim
# Long and short documents with same topic should be close
doc1 = [2, 1, 0, 3] # counts of words: 'python', 'ml', 'java', 'data'
doc2 = [4, 2, 0, 6] # same proportions, longer document
doc3 = [0, 0, 5, 1] # different topic
print('doc1 vs doc2 (same topic):', cosine_distance(doc1, doc2).round(3)) # near 0
print('doc1 vs doc3 (diff topic):', cosine_distance(doc1, doc3).round(3)) # largerHamming Distance for Categorical and Binary Features
Hamming distance counts the number of positions where two vectors differ. It is ideal for binary or categorical features where the concept of magnitude difference is meaningless. For example, comparing two patient records encoded as binary symptom vectors (1=present, 0=absent), Hamming distance counts how many symptoms differ. In scikit-learn, pass metric='hamming' to KNeighborsClassifier. Hamming distance is also used for comparing DNA sequences, error detection in binary codes, and genetic fingerprinting.
import numpy as np
def hamming(a, b):
a, b = np.array(a), np.array(b)
return np.sum(a != b) / len(a)
# Binary symptom vectors: [fever, cough, headache, fatigue]
patient1 = [1, 1, 0, 1]
patient2 = [1, 1, 1, 1] # only headache differs
patient3 = [0, 0, 1, 0] # very different
print('p1 vs p2:', hamming(patient1, patient2)) # 0.25
print('p1 vs p3:', hamming(patient1, patient3)) # 0.75
# sklearn usage
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=3, metric='hamming')The Curse of Dimensionality and Distance
As the number of features (dimensions) grows, all distance-based methods suffer from the curse of dimensionality: in high dimensions, the distance between any two random points converges to the same value, making all points appear equally distant. When distances become indistinguishable, the concept of 'nearest neighbor' loses meaning. This is why KNN typically performs best on datasets with fewer than 20-50 features and why dimensionality reduction (PCA, feature selection) is often applied before KNN in high-dimensional settings.
import numpy as np
np.random.seed(42)
for d in [2, 10, 50, 100, 500]:
# Random points in d-dimensional unit hypercube
X = np.random.rand(1000, d)
query = np.random.rand(d)
dists = np.linalg.norm(X - query, axis=1)
# High-dimensional: max/min ratio -> 1 (all distances similar)
ratio = dists.max() / dists.min()
print(f'd={d:3}: min={dists.min():.2f}, max={dists.max():.2f}, ratio={ratio:.2f}')
# As d grows, ratio approaches 1: distances become indistinguishableChoosing a Metric: A Practical Guide
Here is a decision guide for choosing a distance metric: use Euclidean (L2) for continuous features on similar scales (after StandardScaler); use Manhattan (L1) for sparse or high-dimensional data and when outlier robustness is needed; use Cosine for text/TF-IDF vectors where magnitude should not matter; use Hamming for binary or categorical features; use Minkowski with custom p only if you have domain knowledge suggesting a specific geometry. In practice, try Euclidean and Manhattan first using cross-validation and pick the winner.
def recommend_metric(data_type, is_sparse, has_outliers):
if data_type == 'text':
return 'cosine'
elif data_type == 'binary' or data_type == 'categorical':
return 'hamming'
elif is_sparse or has_outliers:
return 'manhattan'
else:
return 'euclidean' # default, safe choice
print(recommend_metric('text', False, False)) # cosine
print(recommend_metric('binary', False, False)) # hamming
print(recommend_metric('continuous', True, False)) # manhattan
print(recommend_metric('continuous', False, False)) # euclideanIncluding Metric in Grid Search
You can include the metric parameter in your GridSearchCV to find the best combination of k and distance metric simultaneously. This avoids manual trial-and-error across metrics. When searching over metrics that require additional parameters (like Minkowski's p), include those in the param grid too. The best metric is data-dependent and often not obvious from domain knowledge alone — letting cross-validation decide is both principled and practical.
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
pipe = Pipeline([('sc', StandardScaler()), ('knn', KNeighborsClassifier())])
param_grid = [
{'knn__n_neighbors': [3, 5, 7, 11],
'knn__metric': ['euclidean', 'manhattan'],
'knn__weights': ['uniform', 'distance']},
{'knn__n_neighbors': [3, 5, 7],
'knn__metric': ['minkowski'],
'knn__p': [1, 1.5, 2, 3]}
]
grid = GridSearchCV(pipe, param_grid, cv=10, scoring='accuracy', n_jobs=-1)
grid.fit(X_train, y_train)
print('Best params:', grid.best_params_)Quick Check
Test your understanding of Machine Learning with Python concepts from this lesson.
Lesson Recap
In this lesson you learned: how Euclidean, Manhattan, and Minkowski distances differ mathematically and when each is appropriate, cosine similarity for text data and Hamming for binary features, and the curse of dimensionality that makes all distances converge in high-dimensional spaces. Next up we explore KNN for regression tasks and its scalability limitations on large datasets.
AI 튜터와 함께 Python을(를) 배우세요 — 무료
브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.
- 코스
- 30
- 레슨
- 120
자주 묻는 질문
“거리 지표: 유클리드, 맨해튼, 민코프스키” 강의는 무료인가요?
네 — “거리 지표: 유클리드, 맨해튼, 민코프스키” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Machine Learning Academy 강의 전체를 잠금 해제할 수 있습니다. Machine Learning Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“거리 지표: 유클리드, 맨해튼, 민코프스키”에서 뭘 배우나요?
거리 지표를 비교하고, 맨해튼 거리가 유클리드 거리보다 뛰어난 경우를 이해하며, 사용자 지정 지표를 KNeighborsClassifier에 전달합니다. 브라우저에서 직접 실행하는 실습 코드로 Machine Learning Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Machine Learning Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Machine Learning Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“거리 지표: 유클리드, 맨해튼, 민코프스키” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Machine Learning Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Machine Learning Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- KNN 작동 방식: 거리, 이웃, 투표
- k 선택: 엘보 방법과 검증 곡선
- 거리 지표: 유클리드, 맨해튼, 민코프스키
- 회귀를 위한 KNN과 확장성의 한계