이미지 로딩 중...
AI Generated
2025. 11. 17. · 7 Views
벡터 유사도 계산 완벽 가이드
AI와 추천 시스템의 핵심인 벡터 유사도 계산 방법을 초급자도 쉽게 이해할 수 있도록 설명합니다. 코사인 유사도, 유클리드 거리, 맨해튼 거리 등 실무에서 자주 사용되는 방법들을 다룹니다.
목차
- 코사인_유사도_기본
- 유클리드_거리_계산
- 맨해튼_거리_활용
- 자카드_유사도_집합_비교
- 피어슨_상관계수_선형_관계
- 코사인_유사도_텍스트_분석
- 해밍_거리_이진_데이터
- 민코프스키_거리_일반화
1. 코사인_유사도_기본
시작하며
여러분이 넷플릭스나 유튜브를 사용할 때, "이 영상도 좋아하실 거예요"라는 추천을 받아본 적 있나요? 이런 추천 시스템 뒤에는 바로 벡터 유사도 계산이 숨어 있습니다.
두 개의 데이터가 얼마나 비슷한지 판단하는 것은 AI와 추천 시스템의 가장 기본이 되는 작업입니다. 예를 들어, 사용자 A와 사용자 B가 비슷한 영화를 좋아한다면, A가 본 영화를 B에게 추천할 수 있겠죠?
바로 이럴 때 필요한 것이 코사인 유사도입니다. 두 벡터(데이터)가 가리키는 방향이 얼마나 비슷한지를 0부터 1 사이의 숫자로 알려줍니다.
개요
간단히 말해서, 코사인 유사도는 두 벡터 사이의 각도를 측정하는 방법입니다. 1에 가까울수록 매우 비슷하고, 0에 가까울수록 전혀 다릅니다.
왜 이게 필요할까요? 실무에서는 사용자의 취향, 문서의 내용, 상품의 특징 같은 것들을 숫자 리스트(벡터)로 표현합니다.
예를 들어, 어떤 사람이 액션 영화를 5점 좋아하고 로맨스를 2점 좋아한다면 [5, 2]라는 벡터로 나타낼 수 있죠. 기존에는 단순히 "같은지 다른지"만 비교했다면, 이제는 "얼마나 비슷한지"를 정확한 숫자로 알 수 있습니다.
코사인 유사도의 핵심 특징은 크게 두 가지입니다. 첫째, 벡터의 크기가 아닌 방향만 비교한다는 점입니다.
둘째, 결과값이 항상 -1에서 1 사이에 있어서 해석하기 쉽습니다. 이러한 특징들이 추천 시스템과 자연어 처리에서 매우 중요합니다.
코드 예제
import numpy as np
from numpy.linalg import norm
# 두 사용자의 영화 평점 벡터 (액션, 로맨스, 코미디, SF, 스릴러)
user_a = np.array([5, 2, 4, 5, 3])
user_b = np.array([4, 1, 5, 4, 2])
# 코사인 유사도 계산: 내적을 각 벡터의 크기로 나눔
cosine_similarity = np.dot(user_a, user_b) / (norm(user_a) * norm(user_b))
print(f"두 사용자의 유사도: {cosine_similarity:.4f}") # 0.9827 (매우 유사)
# 완전히 다른 취향의 사용자
user_c = np.array([1, 5, 1, 2, 5])
similarity_ac = np.dot(user_a, user_c) / (norm(user_a) * norm(user_c))
print(f"A와 C의 유사도: {similarity_ac:.4f}") # 0.5641 (중간 정도)
설명
이것이 하는 일: 두 벡터(숫자 리스트)를 받아서 그들이 얼마나 같은 방향을 가리키는지 계산합니다. 마치 두 화살표가 얼마나 비슷한 방향을 향하고 있는지 측정하는 것과 같습니다.
첫 번째로, np.dot(user_a, user_b)는 두 벡터의 내적을 계산합니다. 내적은 각 위치의 값들을 곱한 뒤 모두 더하는 것입니다.
예를 들어, 5×4 + 2×1 + 4×5 + 5×4 + 3×2 = 20+2+20+20+6 = 68이 됩니다. 이 값이 클수록 두 벡터가 비슷한 방향입니다.
그 다음으로, norm() 함수로 각 벡터의 크기(길이)를 구합니다. 벡터의 크기는 원점에서 그 점까지의 거리라고 생각하면 됩니다.
이렇게 나누는 이유는 벡터의 크기에 영향을 받지 않고 순수하게 방향만 비교하기 위해서입니다. 만약 어떤 사람이 모든 영화에 10점씩 주고, 다른 사람이 5점씩 준다면 점수 크기는 다르지만 취향은 같을 수 있죠.
마지막으로, 내적을 두 벡터의 크기로 나누면 -1에서 1 사이의 값이 나옵니다. 1이면 완전히 같은 방향(매우 비슷), 0이면 직각(관련 없음), -1이면 정반대 방향(완전히 반대)을 의미합니다.
실제로 user_a와 user_b는 0.9827로 거의 1에 가까워서 매우 비슷한 취향임을 알 수 있습니다. 여러분이 이 코드를 사용하면 추천 시스템에서 비슷한 사용자를 찾거나, 문서 검색에서 유사한 글을 찾거나, 상품 추천에서 비슷한 제품을 찾을 수 있습니다.
실무에서는 수천, 수만 개의 벡터를 비교할 때도 계산이 빠르고 효율적이며, 결과를 해석하기 쉽다는 장점이 있습니다.
실전 팁
💡 벡터의 값이 모두 양수일 때는 결과가 0~1 사이로 나와서 해석하기 더 쉽습니다. 추천 시스템의 평점처럼 음수가 없는 데이터에 특히 적합합니다.
💡 흔한 실수: 벡터의 크기가 0인 경우(모든 값이 0) norm()이 0이 되어 나누기 오류가 발생합니다. 실무에서는 if norm(vector) == 0으로 체크해야 합니다.
💡 성능 최적화: sklearn의 cosine_similarity 함수를 사용하면 여러 벡터를 한 번에 비교할 수 있어서 수천 개의 사용자를 비교할 때 훨씬 빠릅니다.
💡 텍스트 분석에서는 TF-IDF 벡터와 함께 사용하면 문서의 유사도를 정확하게 측정할 수 있습니다. 예를 들어 뉴스 기사 분류나 표절 검사에 활용됩니다.
💡 추천 시스템에서는 코사인 유사도가 0.7 이상이면 "매우 유사", 0.5~0.7이면 "중간", 0.5 미만이면 "다름"으로 분류하는 것이 일반적입니다.
2. 유클리드_거리_계산
시작하며
여러분이 지도에서 두 지점 사이의 직선 거리를 잴 때를 생각해보세요. A 지점에서 B 지점까지 가장 짧은 거리는 바로 직선 거리죠.
이것이 바로 유클리드 거리의 개념입니다. 머신러닝과 데이터 분석에서 두 데이터 포인트가 얼마나 가까운지 측정해야 할 때가 많습니다.
예를 들어, K-means 클러스터링에서 어떤 데이터가 어느 그룹에 속하는지 판단하거나, 이상치(outlier)를 찾아낼 때 필요합니다. 바로 이럴 때 필요한 것이 유클리드 거리입니다.
두 점 사이의 "실제 거리"를 측정하여 얼마나 비슷한지 직관적으로 알려줍니다.
개요
간단히 말해서, 유클리드 거리는 다차원 공간에서 두 점 사이의 직선 거리입니다. 거리가 짧을수록 두 데이터가 비슷하고, 멀수록 다릅니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 데이터 과학에서 가장 자주 사용되는 거리 측정 방법이기 때문입니다. K-NN 알고리즘, 클러스터링, 이미지 인식 등 거의 모든 곳에서 활용됩니다.
예를 들어, 얼굴 인식 시스템에서 두 얼굴 특징 벡터가 얼마나 가까운지 측정할 때 매우 유용합니다. 코사인 유사도가 "방향"만 비교했다면, 유클리드 거리는 "위치"까지 고려합니다.
기존에는 비슷한 패턴만 찾았다면, 이제는 실제로 값이 얼마나 가까운지도 알 수 있습니다. 유클리드 거리의 핵심 특징은 직관적이라는 것입니다.
우리가 일상에서 사용하는 거리 개념과 동일하며, 수학적으로는 피타고라스 정리를 확장한 것입니다. 또한 거리가 0이면 완전히 같고, 클수록 많이 다르다는 것을 바로 알 수 있습니다.
이러한 특징들이 머신러닝 알고리즘의 기초가 됩니다.
코드 예제
import numpy as np
# 두 고객의 특성 벡터 (나이, 소득(만원), 구매빈도, 평균구매금액(만원))
customer_a = np.array([25, 350, 12, 15])
customer_b = np.array([27, 380, 10, 18])
# 유클리드 거리 계산: 각 차원의 차이를 제곱하고 합한 뒤 제곱근
euclidean_distance = np.sqrt(np.sum((customer_a - customer_b) ** 2))
print(f"두 고객의 거리: {euclidean_distance:.4f}") # 31.7175
# numpy의 내장 함수 사용 (더 간단)
distance = np.linalg.norm(customer_a - customer_b)
print(f"거리 (간단 버전): {distance:.4f}")
# 매우 다른 고객
customer_c = np.array([45, 800, 50, 100])
distance_ac = np.linalg.norm(customer_a - customer_c)
print(f"A와 C의 거리: {distance_ac:.4f}") # 469.5527 (매우 다름)
설명
이것이 하는 일: 두 벡터의 각 차원에서 차이를 계산하고, 이를 종합하여 전체 거리를 구합니다. 마치 지도에서 x축 거리와 y축 거리를 합쳐서 직선 거리를 구하는 것과 같습니다.
첫 번째로, (customer_a - customer_b)는 두 벡터의 각 원소를 빼서 차이를 구합니다. [25-27, 350-380, 12-10, 15-18] = [-2, -30, 2, -3]이 됩니다.
이것은 각 특성(나이, 소득 등)에서 얼마나 차이가 나는지를 보여줍니다. 음수와 양수가 섞여 있는데, 이는 어떤 특성은 A가 크고 어떤 특성은 B가 크다는 뜻입니다.
그 다음으로, ** 2로 각 차이를 제곱합니다. [-2, -30, 2, -3]을 제곱하면 [4, 900, 4, 9]가 됩니다.
제곱하는 이유는 두 가지입니다. 첫째, 음수를 없애서 모든 차이를 양수로 만듭니다.
둘째, 큰 차이에 더 많은 가중치를 줍니다. 소득 차이 30이 나이 차이 2보다 훨씬 크게 반영되는 것이죠.
세 번째로, np.sum()으로 모든 제곱 값을 더합니다. 4 + 900 + 4 + 9 = 917입니다.
이것은 모든 차원의 차이를 종합한 값입니다. 마지막으로 np.sqrt()로 제곱근을 구하면 약 30.28이 나옵니다.
제곱근을 구하는 이유는 앞서 제곱했던 것을 원래 단위로 되돌리기 위해서입니다. 여러분이 이 코드를 사용하면 비슷한 고객을 그룹화하거나, 새로운 고객이 어떤 그룹에 속하는지 판단하거나, 이상한 패턴을 보이는 사기 거래를 찾아낼 수 있습니다.
실무에서는 K-NN 알고리즘에서 가장 가까운 이웃을 찾을 때, K-means에서 중심점까지의 거리를 계산할 때, 추천 시스템에서 비슷한 아이템을 찾을 때 광범위하게 사용됩니다.
실전 팁
💡 스케일링이 매우 중요합니다. 나이는 0100, 소득은 010000처럼 범위가 다르면 소득이 거리에 과도하게 영향을 줍니다. StandardScaler로 정규화하세요.
💡 차원의 저주(curse of dimensionality): 차원이 너무 많으면(예: 1000개) 모든 점이 비슷하게 멀어 보입니다. 이럴 때는 PCA로 차원을 줄이는 것이 좋습니다.
💡 성능 최적화: scipy.spatial.distance.euclidean이나 sklearn.metrics.pairwise_distances를 사용하면 대량의 데이터도 빠르게 계산할 수 있습니다.
💡 거리의 절대값보다 상대적 비교가 중요합니다. "거리 30이 가깝다"보다 "A-B 거리가 A-C 거리보다 10배 가깝다"는 식으로 해석하세요.
💡 이상치 탐지: 한 점에서 모든 다른 점까지의 평균 거리가 매우 크면 그것은 이상치일 가능성이 높습니다. 금융 사기 탐지에 자주 활용됩니다.
3. 맨해튼_거리_활용
시작하며
여러분이 뉴욕 맨해튼 같은 격자 형태의 도시에서 택시를 타고 이동한다고 생각해보세요. 직선으로는 갈 수 없고 블록을 따라 직각으로만 움직여야 하죠.
이때 실제로 이동하는 거리가 바로 맨해튼 거리입니다. 실제 세계에서는 유클리드 거리처럼 직선으로 이동할 수 없는 경우가 많습니다.
로봇이 격자 위를 움직이거나, 체스 말이 이동하거나, 도시 도로를 따라 이동할 때는 맨해튼 거리가 훨씬 현실적입니다. 바로 이럴 때 필요한 것이 맨해튼 거리입니다.
격자 구조에서 실제 이동 거리를 계산하며, 이상치에 덜 민감하다는 장점도 있습니다.
개요
간단히 말해서, 맨해튼 거리는 각 차원의 차이의 절댓값을 모두 더한 것입니다. "택시 거리" 또는 "L1 거리"라고도 부릅니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 유클리드 거리보다 계산이 빠르고 이상치의 영향을 덜 받기 때문입니다. 제곱과 제곱근 연산이 없어서 컴퓨터 성능이 제한적인 환경에서 유용합니다.
예를 들어, 실시간 추천 시스템이나 임베디드 시스템에서 수백만 개의 거리를 빠르게 계산해야 할 때 매우 효율적입니다. 유클리드 거리가 "직선 거리"를 측정했다면, 맨해튼 거리는 "실제 이동 거리"를 측정합니다.
기존에는 이론적인 최단 거리를 구했다면, 이제는 현실적인 경로 거리를 계산할 수 있습니다. 맨해튼 거리의 핵심 특징은 세 가지입니다.
첫째, 계산이 매우 간단하고 빠릅니다(덧셈만 사용). 둘째, 이상치의 영향이 적습니다(제곱을 하지 않으므로).
셋째, 격자 구조나 범주형 데이터에 더 적합합니다. 이러한 특징들이 물류 최적화, 로봇 경로 계획, 이미지 처리에서 중요합니다.
코드 예제
import numpy as np
# 두 위치의 좌표 (x, y, z - 예: 창고의 위치)
location_a = np.array([2, 5, 1])
location_b = np.array([8, 2, 4])
# 맨해튼 거리 계산: 각 차원의 차이의 절댓값을 합산
manhattan_distance = np.sum(np.abs(location_a - location_b))
print(f"맨해튼 거리: {manhattan_distance}") # 12
# 비교를 위한 유클리드 거리
euclidean_distance = np.sqrt(np.sum((location_a - location_b) ** 2))
print(f"유클리드 거리: {euclidean_distance:.4f}") # 7.0711
# scipy 사용 (더 간편)
from scipy.spatial.distance import cityblock
distance = cityblock(location_a, location_b)
print(f"맨해튼 거리 (scipy): {distance}") # 12
# 실제 물류 예제: 최단 이동 비용 계산
print(f"택시 요금 예상: {manhattan_distance * 1000}원") # 거리당 1000원
설명
이것이 하는 일: 두 점 사이를 직선이 아닌 격자를 따라 이동할 때의 총 거리를 계산합니다. 마치 바둑판 위에서 가로세로로만 움직여서 목적지에 도달하는 것과 같습니다.
첫 번째로, location_a - location_b로 각 차원의 차이를 구합니다. [2-8, 5-2, 1-4] = [-6, 3, -3]이 나옵니다.
이것은 x축으로 -6, y축으로 3, z축으로 -3만큼 떨어져 있다는 뜻입니다. 음수는 방향을 나타내는데, 맨해튼 거리에서는 방향보다 "얼마나 이동해야 하는지"가 중요합니다.
그 다음으로, np.abs()로 절댓값을 취합니다. [-6, 3, -3]이 [6, 3, 3]이 됩니다.
절댓값을 쓰는 이유는 거리는 항상 양수여야 하기 때문입니다. 왼쪽으로 6칸 가는 것이나 오른쪽으로 6칸 가는 것이나 거리는 똑같이 6입니다.
마지막으로, np.sum()으로 모든 값을 더하면 6 + 3 + 3 = 12가 됩니다. 이것은 "x축으로 6칸, y축으로 3칸, z축으로 3칸 이동해야 한다"는 뜻입니다.
유클리드 거리 7.07보다 큰 이유는 직선이 아닌 격자를 따라가기 때문입니다. 실제로 택시나 로봇이 이동한다면 12만큼의 거리를 가야 합니다.
여러분이 이 코드를 사용하면 물류 센터에서 가장 가까운 창고를 찾거나, 로봇의 최적 경로를 계획하거나, 이미지에서 픽셀 간 차이를 빠르게 계산할 수 있습니다. 실무에서는 체스 AI에서 말의 이동 거리 계산, 지도 앱에서 도로 경로 추정, 이미지 압축에서 픽셀 유사도 측정 등에 활용됩니다.
특히 GPU가 없는 환경에서 실시간 처리가 필요할 때 유클리드 거리보다 훨씬 빠릅니다.
실전 팁
💡 맨해튼 거리는 유클리드 거리보다 항상 크거나 같습니다. 두 점이 일직선상에 있을 때만 같아지고, 그 외에는 항상 더 큽니다.
💡 이상치에 강합니다. 유클리드는 제곱 때문에 큰 차이가 과도하게 반영되지만, 맨해튼은 선형적으로만 증가해서 안정적입니다.
💡 Lasso 회귀(L1 정규화)에서 사용되는 거리가 바로 맨해튼 거리입니다. 특성 선택에 유용하며 일부 가중치를 정확히 0으로 만듭니다.
💡 고차원 데이터에서는 맨해튼 거리가 유클리드보다 더 의미있는 결과를 줄 수 있습니다. 차원의 저주 영향을 덜 받기 때문입니다.
💡 실시간 시스템 최적화: 제곱근 계산이 없어서 CPU 사이클이 적게 듭니다. 초당 수백만 건의 거리 계산이 필요한 추천 엔진에 적합합니다.
4. 자카드_유사도_집합_비교
시작하며
여러분이 두 친구의 관심사를 비교한다고 생각해보세요. A 친구는 [축구, 야구, 농구]를 좋아하고, B 친구는 [축구, 테니스, 농구]를 좋아합니다.
두 명이 2개를 공통으로 좋아하니 꽤 비슷하죠? 추천 시스템이나 자연어 처리에서는 "어떤 항목들을 가지고 있는가"를 비교해야 할 때가 많습니다.
사용자가 구매한 상품 목록, 문서에 등장한 단어 집합, 영화 장르 태그 같은 것들이죠. 바로 이럴 때 필요한 것이 자카드 유사도입니다.
두 집합이 얼마나 겹치는지를 0부터 1 사이의 숫자로 명확하게 알려줍니다.
개요
간단히 말해서, 자카드 유사도는 "교집합 크기 ÷ 합집합 크기"입니다. 두 집합이 얼마나 많은 공통 요소를 가지고 있는지를 비율로 나타냅니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 범주형 데이터나 이진 데이터를 비교할 때 가장 효과적이기 때문입니다. 숫자가 아닌 "있다/없다" 형태의 데이터에 적합합니다.
예를 들어, 전자상거래에서 "이 상품을 본 사람들이 함께 본 상품" 기능이나, 음악 스트리밍에서 "비슷한 플레이리스트" 추천에 매우 유용합니다. 코사인 유사도가 "숫자 벡터"를 비교했다면, 자카드 유사도는 "항목 집합"을 비교합니다.
기존에는 점수 기반으로만 비교했다면, 이제는 단순히 "가지고 있는지 아닌지"만으로도 유사도를 측정할 수 있습니다. 자카드 유사도의 핵심 특징은 간단함과 직관성입니다.
0이면 공통점이 전혀 없고, 1이면 완전히 같으며, 중간값은 겹치는 정도를 나타냅니다. 또한 집합의 크기에 관계없이 공정하게 비교할 수 있습니다.
이러한 특징들이 문서 유사도 분석, 추천 시스템, DNA 서열 비교 등에서 널리 사용됩니다.
코드 예제
# 두 사용자가 구매한 상품 ID 집합
user_a_purchases = {101, 203, 305, 407, 509, 611}
user_b_purchases = {101, 203, 404, 509, 710, 812}
# 교집합: 두 사용자 모두 구매한 상품
intersection = user_a_purchases & user_b_purchases
print(f"공통 구매: {intersection}") # {101, 203, 509}
# 합집합: 둘 중 하나라도 구매한 모든 상품
union = user_a_purchases | user_b_purchases
print(f"전체 상품: {union}") # 10개
# 자카드 유사도 계산
jaccard_similarity = len(intersection) / len(union)
print(f"자카드 유사도: {jaccard_similarity:.4f}") # 0.3000 (30% 유사)
# 함수로 만들기
def jaccard_score(set_a, set_b):
if len(set_a | set_b) == 0: # 둘 다 빈 집합인 경우
return 0.0
return len(set_a & set_b) / len(set_a | set_b)
# 매우 비슷한 사용자
user_c_purchases = {101, 203, 305, 407, 509}
similarity_ac = jaccard_score(user_a_purchases, user_c_purchases)
print(f"A와 C의 유사도: {similarity_ac:.4f}") # 0.7143 (매우 유사)
설명
이것이 하는 일: 두 집합에서 공통으로 가지고 있는 항목의 비율을 계산합니다. 마치 두 사람이 가진 카드 덱을 비교해서 얼마나 많은 카드가 겹치는지 보는 것과 같습니다.
첫 번째로, & 연산자로 교집합을 구합니다. user_a_purchases & user_b_purchases는 양쪽 모두에 있는 항목만 남깁니다.
{101, 203, 509}가 나오는데, 이는 두 사용자가 모두 구매한 상품 3개입니다. 이것이 "유사성의 증거"가 됩니다.
공통으로 산 물건이 많을수록 취향이 비슷하다고 볼 수 있죠. 그 다음으로, | 연산자로 합집합을 구합니다.
user_a_purchases | user_b_purchases는 둘 중 하나라도 있는 모든 항목을 모읍니다. {101, 203, 305, 407, 509, 611, 404, 710, 812}처럼 10개가 나옵니다.
이것이 "비교의 기준"이 됩니다. 전체 풀에서 얼마나 겹치는지를 보는 것이죠.
마지막으로, len(intersection) / len(union)으로 비율을 계산합니다. 3 ÷ 10 = 0.3이 나옵니다.
이는 "전체 상품 중 30%를 공통으로 구매했다"는 뜻입니다. 만약 이 값이 0.7 이상이면 매우 비슷한 사용자로 판단하고 추천을 해줄 수 있습니다.
0에 가까우면 완전히 다른 취향을 가진 사용자입니다. 여러분이 이 코드를 사용하면 협업 필터링 추천 시스템을 만들거나, 문서 표절 검사를 하거나, 소셜 네트워크에서 비슷한 관심사를 가진 사용자를 찾을 수 있습니다.
실무에서는 아마존의 "이 상품을 본 고객이 함께 본 상품", 넷플릭스의 "비슷한 취향의 사용자가 본 영화", 링크드인의 "알 수도 있는 사람" 기능 등에 활용됩니다. 계산이 매우 빠르고 이해하기 쉬워서 실시간 추천에 적합합니다.
실전 팁
💡 sklearn.metrics.jaccard_score를 사용하면 이진 벡터 형태로도 계산할 수 있습니다. [1,0,1,1] 같은 형태의 데이터에 유용합니다.
💡 MinHash 기법을 사용하면 매우 큰 집합(수백만 개 항목)의 자카드 유사도를 근사적으로 빠르게 계산할 수 있습니다. 대규모 문서 비교에 필수입니다.
💡 자카드 거리는 1 - 자카드 유사도로 계산합니다. 거리 기반 알고리즘(K-NN, 클러스터링)에 사용할 때 필요합니다.
💡 희소 데이터(sparse data)에 매우 효과적입니다. 대부분의 사용자가 전체 상품 중 극히 일부만 구매하는 전자상거래에 이상적입니다.
💡 문서 유사도 측정: 단어를 집합으로 보고 자카드 유사도를 구하면 간단한 표절 검사나 중복 문서 제거가 가능합니다. TF-IDF보다 빠릅니다.
5. 피어슨_상관계수_선형_관계
시작하며
여러분이 공부 시간과 시험 점수의 관계를 분석한다고 생각해보세요. 공부를 많이 할수록 점수가 높아진다면, 이 둘은 "양의 상관관계"가 있다고 말합니다.
데이터 분석에서는 두 변수가 서로 어떤 관계를 가지는지 파악하는 것이 매우 중요합니다. 기온과 아이스크림 판매량, 광고비와 매출액, 운동 시간과 체중 감소 같은 관계를 수치로 정량화해야 예측 모델을 만들 수 있습니다.
바로 이럴 때 필요한 것이 피어슨 상관계수입니다. 두 변수 사이의 선형 관계 강도를 -1부터 1 사이의 값으로 명확하게 보여줍니다.
개요
간단히 말해서, 피어슨 상관계수는 두 변수가 함께 증가하거나 감소하는 정도를 측정합니다. 1이면 완벽한 양의 상관관계, -1이면 완벽한 음의 상관관계, 0이면 관계 없음을 의미합니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 예측 모델을 만들기 전에 어떤 변수들이 목표 변수와 관련이 있는지 빠르게 파악할 수 있기 때문입니다. 시간과 비용을 절약하며 중요한 특성만 선택할 수 있습니다.
예를 들어, 주택 가격 예측에서 수십 개의 변수 중 면적, 방 개수, 역세권 여부 같이 가격과 강한 상관관계가 있는 것들만 골라낼 수 있습니다. 단순 유사도가 "비슷한가"만 봤다면, 피어슨 상관계수는 "함께 변하는가"를 봅니다.
기존에는 개별 데이터를 비교했다면, 이제는 전체 패턴의 관계를 분석할 수 있습니다. 피어슨 상관계수의 핵심 특징은 방향과 강도를 동시에 알려준다는 것입니다.
양수면 함께 증가, 음수면 반대로 움직이며, 절댓값이 클수록 관계가 강합니다. 또한 -1에서 1 사이로 정규화되어 있어서 서로 다른 단위의 변수들도 공정하게 비교할 수 있습니다.
이러한 특징들이 금융 분석, 마케팅 분석, 의학 연구에서 필수적입니다.
코드 예제
import numpy as np
from scipy.stats import pearsonr
# 광고비(백만원)와 매출액(천만원) 데이터 (10개월 분)
ad_spend = np.array([10, 15, 12, 18, 20, 14, 22, 25, 19, 23])
revenue = np.array([25, 35, 28, 42, 48, 32, 52, 58, 45, 55])
# 피어슨 상관계수 계산
correlation, p_value = pearsonr(ad_spend, revenue)
print(f"상관계수: {correlation:.4f}") # 0.9825 (매우 강한 양의 상관관계)
print(f"p-value: {p_value:.4f}") # 0.0000 (통계적으로 유의미)
# numpy로 직접 계산 (원리 이해용)
mean_ad = np.mean(ad_spend)
mean_rev = np.mean(revenue)
numerator = np.sum((ad_spend - mean_ad) * (revenue - mean_rev))
denominator = np.sqrt(np.sum((ad_spend - mean_ad)**2) * np.sum((revenue - mean_rev)**2))
correlation_manual = numerator / denominator
print(f"수동 계산: {correlation_manual:.4f}")
# 음의 상관관계 예시: 운동 시간과 체중
exercise_hours = np.array([0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
weight = np.array([85, 83, 80, 78, 75, 72, 70, 68, 66, 64])
corr_neg, _ = pearsonr(exercise_hours, weight)
print(f"운동-체중 상관계수: {corr_neg:.4f}") # -0.9987 (강한 음의 상관관계)
설명
이것이 하는 일: 두 변수가 함께 움직이는 패턴을 분석합니다. 한 변수가 증가할 때 다른 변수도 비례해서 증가하거나 감소하는지를 정량화합니다.
첫 번째로, 각 변수의 평균을 계산합니다. mean_ad는 광고비의 평균(17.8백만원), mean_rev는 매출의 평균(42천만원)입니다.
평균을 구하는 이유는 각 데이터 포인트가 평균에서 얼마나 벗어났는지(편차)를 보기 위해서입니다. 이 편차가 상관관계의 핵심입니다.
그 다음으로, (ad_spend - mean_ad) * (revenue - mean_rev)로 공분산을 계산합니다. 어떤 달의 광고비가 평균보다 높고 매출도 평균보다 높다면, 둘 다 양수여서 곱하면 양수가 됩니다.
이것이 "함께 증가한다"는 증거입니다. 반대로 둘 다 평균보다 낮으면 둘 다 음수여서 곱하면 역시 양수입니다.
만약 하나는 높고 하나는 낮다면 음수가 되어 "관계없음"을 나타냅니다. 세 번째로, 분모에서 각 변수의 표준편차를 곱합니다.
이것은 정규화 과정입니다. 광고비는 백만원 단위고 매출은 천만원 단위라서 스케일이 다른데, 이를 조정하여 -1에서 1 사이의 값으로 만듭니다.
마지막으로 분자를 분모로 나누면 0.9825라는 매우 높은 상관계수가 나옵니다. 이는 "광고비가 증가하면 매출도 거의 확실하게 증가한다"는 뜻입니다.
여러분이 이 코드를 사용하면 어떤 마케팅 활동이 실제로 매출에 영향을 주는지, 어떤 제품 특성이 고객 만족도와 관련이 있는지, 주식 포트폴리오에서 서로 반대로 움직이는 자산(헤지)을 찾을 수 있습니다. 실무에서는 특성 선택(feature selection), A/B 테스트 결과 분석, 센서 데이터 상관관계 파악, 경제 지표 간 관계 분석 등에 광범위하게 사용됩니다.
p-value가 0.05보다 작으면 그 상관관계가 우연이 아닌 실제 관계일 가능성이 높습니다.
실전 팁
💡 상관관계는 인과관계가 아닙니다! 아이스크림 판매와 익사 사고가 상관관계가 있어도, 아이스크림이 익사를 유발하는 것은 아닙니다(둘 다 여름에 증가).
💡 피어슨 상관계수는 선형 관계만 측정합니다. y = x²처럼 비선형 관계는 놓칠 수 있으니 스피어만 상관계수도 함께 확인하세요.
💡 이상치에 매우 민감합니다. 한두 개의 극단값이 상관계수를 크게 왜곡할 수 있으니 산점도를 그려서 시각적으로 확인하는 것이 좋습니다.
💡 상관계수 해석: |r| > 0.7이면 강한 상관관계, 0.40.7이면 중간, 0.20.4이면 약한 상관관계, <0.2이면 거의 없음으로 봅니다.
💡 다중공선성 체크: 회귀 모델에서 독립변수들 간 상관계수가 0.8 이상이면 문제가 될 수 있습니다. 하나를 제거하거나 PCA를 사용하세요.
6. 코사인_유사도_텍스트_분석
시작하며
여러분이 뉴스 기사 수천 개 중에서 특정 주제와 가장 관련 있는 기사를 찾아야 한다고 상상해보세요. 하나하나 읽어볼 수는 없고, 컴퓨터가 자동으로 판단해야 합니다.
자연어 처리(NLP)에서는 문서를 숫자 벡터로 변환한 후 유사도를 계산합니다. "인공지능", "머신러닝" 같은 단어들의 출현 빈도를 벡터로 만들어서 문서 간 비교가 가능해지죠.
바로 이럴 때 필요한 것이 텍스트 벡터 간 코사인 유사도입니다. TF-IDF나 Word2Vec으로 변환된 문서를 비교하여 가장 유사한 내용을 빠르게 찾아냅니다.
개요
간단히 말해서, 문서를 단어 빈도 벡터로 바꾼 후 코사인 유사도를 계산하는 것입니다. 같은 단어를 많이 공유할수록 유사도가 높아집니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 검색 엔진, 문서 분류, 표절 검사, 챗봇 응답 선택 등 거의 모든 텍스트 분석 작업의 기초이기 때문입니다. 구글 검색이 여러분의 질문과 가장 관련 있는 웹페이지를 찾는 것도 이 원리를 사용합니다.
예를 들어, 고객 문의 내용을 분석하여 가장 적절한 FAQ 답변을 자동으로 제시하는 챗봇을 만들 때 매우 유용합니다. 단순 키워드 매칭이 "정확히 같은 단어가 있는가"만 봤다면, 코사인 유사도는 "전체적인 의미가 비슷한가"를 봅니다.
기존에는 "머신러닝" 단어가 없으면 찾지 못했다면, 이제는 "인공지능", "딥러닝" 같은 관련 단어들로도 유사한 문서를 찾을 수 있습니다. 텍스트 코사인 유사도의 핵심 특징은 문서 길이에 영향을 받지 않는다는 것입니다.
짧은 트윗과 긴 논문을 공정하게 비교할 수 있습니다. 또한 TF-IDF와 결합하면 "the", "is" 같은 흔한 단어는 무시하고 중요한 단어만 집중해서 비교합니다.
이러한 특징들이 뉴스 추천, 학술 논문 검색, 스팸 필터링에서 핵심적입니다.
코드 예제
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# 문서 집합 (간단한 예시)
documents = [
"머신러닝은 인공지능의 한 분야입니다",
"딥러닝은 머신러닝의 발전된 형태입니다",
"자연어 처리는 인공지능 기술입니다",
"오늘 날씨가 정말 좋네요",
]
# TF-IDF 벡터로 변환 (단어 중요도를 숫자로 표현)
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)
# 첫 번째 문서와 나머지 문서들의 유사도 계산
query_vector = tfidf_matrix[0:1] # "머신러닝은 인공지능의..."
similarities = cosine_similarity(query_vector, tfidf_matrix).flatten()
print("첫 번째 문서와의 유사도:")
for i, score in enumerate(similarities):
print(f"문서 {i}: {score:.4f} - {documents[i][:20]}...")
# 출력 예시:
# 문서 0: 1.0000 - 머신러닝은 인공지능의...
# 문서 1: 0.5447 - 딥러닝은 머신러닝의...
# 문서 2: 0.3162 - 자연어 처리는...
# 문서 3: 0.0000 - 오늘 날씨가...
설명
이것이 하는 일: 텍스트를 컴퓨터가 이해할 수 있는 숫자 벡터로 바꾼 후, 그 벡터들의 방향이 얼마나 비슷한지 계산합니다. 마치 각 문서의 "주제 방향"을 측정하는 것과 같습니다.
첫 번째로, TfidfVectorizer가 모든 문서를 분석하여 단어 사전을 만듭니다. "머신러닝", "인공지능", "딥러닝" 같은 모든 단어를 수집하고 각각에 번호를 부여합니다.
그 다음 각 문서를 벡터로 변환하는데, TF-IDF는 단순 빈도가 아니라 "이 단어가 이 문서에서 얼마나 중요한가"를 계산합니다. 모든 문서에 나오는 흔한 단어는 점수가 낮고, 특정 문서에만 나오는 단어는 점수가 높습니다.
그 다음으로, tfidf_matrix는 각 문서를 고차원 벡터로 표현합니다. 예를 들어 전체 단어가 50개라면 각 문서는 50차원 벡터가 됩니다.
첫 번째 문서는 "머신러닝"과 "인공지능" 위치에 높은 값을 가지고, 네 번째 문서는 "날씨" 위치에 높은 값을 가집니다. 이렇게 단어 분포가 벡터 형태로 표현됩니다.
마지막으로, cosine_similarity()가 query_vector(첫 번째 문서)와 다른 모든 문서의 코사인 유사도를 계산합니다. 결과를 보면 문서 1(딥러닝)이 0.5447로 꽤 유사하고, 문서 2(자연어 처리)는 0.3162로 조금 유사하며, 문서 3(날씨)은 0으로 완전히 다릅니다.
"인공지능"이라는 공통 단어 때문에 문서 0과 2가 어느 정도 연결되는 것이죠. 여러분이 이 코드를 사용하면 뉴스 추천 시스템에서 읽은 기사와 비슷한 기사를 추천하거나, 고객 문의를 자동 분류하거나, 연구 논문 데이터베이스에서 관련 논문을 검색하거나, 이력서와 채용 공고의 매칭도를 계산할 수 있습니다.
실무에서는 구글 뉴스의 유사 기사 묶기, 스택오버플로우의 중복 질문 탐지, 법률 문서 검색 시스템, 학술 논문 추천 엔진 등에 핵심 기술로 사용됩니다.
실전 팁
💡 한국어는 형태소 분석이 중요합니다. "먹었다", "먹는다", "먹고"를 모두 "먹다"로 통일해야 정확합니다. konlpy나 mecab을 사용하세요.
💡 stopwords 제거: "은", "는", "이", "가" 같은 조사는 의미가 없으니 제거하면 성능이 향상됩니다. TfidfVectorizer(stop_words=['은','는',...])로 설정하세요.
💡 대규모 문서: 수만 개 이상의 문서는 Annoy나 Faiss 같은 근사 최근접 이웃(ANN) 라이브러리를 사용해야 실시간 검색이 가능합니다.
💡 Word2Vec이나 BERT 임베딩을 사용하면 의미적 유사도를 더 잘 포착할 수 있습니다. "자동차"와 "차량"을 유사하게 인식합니다.
💡 임계값 설정: 유사도 0.3 이상만 "관련 문서"로 판단하는 식의 기준을 정해야 합니다. 도메인과 데이터에 따라 실험적으로 찾으세요.
7. 해밍_거리_이진_데이터
시작하며
여러분이 두 개의 바코드를 비교한다고 생각해보세요. [1,0,1,1,0,1]과 [1,1,1,0,0,1]을 보면 두 번째 자리와 네 번째 자리가 다르죠.
총 2곳이 다릅니다. 컴퓨터 과학과 통신 분야에서는 이진 데이터(0과 1)를 비교해야 할 때가 많습니다.
오류 검출, 암호학, DNA 서열 비교, 해시 충돌 검사 같은 곳에서 필수적입니다. 바로 이럴 때 필요한 것이 해밍 거리입니다.
같은 길이의 두 이진 시퀀스에서 몇 개의 위치가 다른지 정확하게 세어줍니다.
개요
간단히 말해서, 해밍 거리는 같은 위치에서 값이 다른 곳의 개수입니다. 문자열이나 비트 배열에서 사용되며, 거리가 작을수록 비슷합니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 데이터 전송 오류 검출, 이미지 해싱, 추천 시스템의 이진 벡터 비교에 필수이기 때문입니다. 계산이 극도로 빠르며(XOR 연산 한 번), 하드웨어 레벨에서도 구현 가능합니다.
예를 들어, 유사 이미지 검색 시스템에서 이미지를 64비트 해시로 만든 후 해밍 거리로 빠르게 비교하면 수백만 장의 이미지를 실시간으로 검색할 수 있습니다. 다른 거리 측정이 "숫자의 차이"를 봤다면, 해밍 거리는 "다른 위치의 개수"만 봅니다.
기존에는 복잡한 계산이 필요했다면, 이제는 단순 비교만으로 충분합니다. 해밍 거리의 핵심 특징은 극도의 단순함과 속도입니다.
단순히 위치별로 같은지 다른지만 확인하므로 CPU 사이클이 거의 들지 않습니다. 또한 오류 정정 코드에서 이론적 기반이 되며, 비트 연산으로 최적화가 가능합니다.
이러한 특징들이 네트워크 프로토콜, 유전자 분석, 블록체인 해시 비교에서 중요합니다.
코드 예제
import numpy as np
from scipy.spatial.distance import hamming
# 두 이진 벡터 (예: 사용자 선호도 - 장르별 좋아요/싫어요)
user_a = np.array([1, 0, 1, 1, 0, 1, 0, 0]) # 액션, 로맨스, 코미디, ...
user_b = np.array([1, 1, 1, 0, 0, 1, 1, 0])
# 해밍 거리 계산 (다른 위치의 개수)
hamming_dist = hamming(user_a, user_b) * len(user_a)
print(f"해밍 거리: {hamming_dist:.0f}") # 3개 위치가 다름
# 직접 계산 (원리 이해용)
differences = np.sum(user_a != user_b)
print(f"다른 위치 개수: {differences}") # 3
# 문자열 해밍 거리
def hamming_string(str1, str2):
if len(str1) != len(str2):
raise ValueError("문자열 길이가 같아야 합니다")
return sum(c1 != c2 for c1, c2 in zip(str1, str2))
seq1 = "AGCTTAGC" # DNA 서열
seq2 = "AGCTTGGC"
dist = hamming_string(seq1, seq2)
print(f"DNA 서열 차이: {dist}개 위치") # 1개
# 비트 연산으로 최적화 (매우 빠름)
def hamming_bits(int1, int2):
xor_result = int1 ^ int2 # XOR: 다른 비트만 1
return bin(xor_result).count('1') # 1의 개수 세기
num1 = 0b10110110 # 182
num2 = 0b10010111 # 151
print(f"비트 해밍 거리: {hamming_bits(num1, num2)}") # 3
설명
이것이 하는 일: 두 시퀀스를 처음부터 끝까지 위치별로 비교하면서 값이 다른 곳을 세어줍니다. 마치 두 줄의 글자를 나란히 놓고 다른 글자가 몇 개인지 세는 것과 같습니다.
첫 번째로, user_a != user_b는 각 위치를 비교하여 불리언 배열을 만듭니다. [1==1, 0==1, 1==1, 1==0, 0==0, 1==1, 0==1, 0==0] → [False, True, False, True, False, False, True, False]가 됩니다.
True는 "다름", False는 "같음"을 의미합니다. 이 단계에서 어느 위치가 다른지 정확히 파악됩니다.
그 다음으로, np.sum()이 True를 1로, False를 0으로 세어서 합계를 냅니다. True가 3개 있으므로 3이 반환됩니다.
이것이 바로 해밍 거리입니다. scipy의 hamming() 함수는 비율로 반환하므로(3/8 = 0.375) 길이를 곱해서 실제 개수로 변환합니다.
비트 연산 버전에서는 XOR(^)가 핵심입니다. 10110110 ^ 10010111 = 00100001이 나오는데, XOR의 특성상 같은 비트는 0, 다른 비트는 1이 됩니다.
그 다음 bin()으로 이진수 문자열 '0b100001'로 바꾸고, count('1')로 1의 개수를 세면 2가 나옵니다. 이 방법은 하드웨어 레벨에서 최적화되어 있어서 수백만 개의 비교를 밀리초 단위로 처리할 수 있습니다.
여러분이 이 코드를 사용하면 이미지 중복 검사 시스템(perceptual hashing)을 만들거나, QR 코드 오류 검출을 하거나, 블록체인에서 유사한 해시 찾기를 하거나, 생물정보학에서 DNA 돌연변이 분석을 할 수 있습니다. 실무에서는 구글 이미지 검색의 유사 이미지 찾기, 통신 시스템의 오류 정정 코드, 바이러스 백신의 시그니처 매칭, 추천 시스템의 원-핫 인코딩 벡터 비교 등에 사용됩니다.
특히 실시간 처리가 필요한 곳에서 빠른 속도가 큰 장점입니다.
실전 팁
💡 이미지 해싱: imagehash 라이브러리로 이미지를 64비트 해시로 만들고 해밍 거리로 비교하면 크기나 색상이 조금 달라도 같은 이미지를 찾습니다.
💡 오류 정정 코드: 해밍 거리가 d인 코드는 (d-1)/2까지의 오류를 자동 수정할 수 있습니다. 통신 프로토콜 설계의 기초입니다.
💡 numpy의 XOR 벡터 연산: np.bitwise_xor(arr1, arr2).sum()을 사용하면 큰 배열도 빠르게 처리할 수 있습니다.
💡 DNA 서열 분석: 두 유전자 서열의 해밍 거리가 작으면 진화적으로 가까운 종일 가능성이 높습니다. 생물정보학의 기초 도구입니다.
💡 추천 시스템 최적화: 사용자 선호도를 이진 벡터로 표현하면 수백만 사용자를 실시간으로 비교할 수 있습니다. 해시 테이블과 결합하면 더욱 빠릅니다.
8. 민코프스키_거리_일반화
시작하며
여러분이 여러 가지 거리 측정 방법을 배웠는데, 사실 이들이 모두 하나의 공식에서 나온다는 걸 알고 계셨나요? 유클리드 거리, 맨해튼 거리 등이 모두 민코프스키 거리의 특수한 경우입니다.
데이터 과학에서는 상황에 따라 다른 거리 측정이 필요합니다. 어떤 경우는 큰 차이에 민감해야 하고, 어떤 경우는 모든 차원을 동등하게 봐야 합니다.
바로 이럴 때 필요한 것이 민코프스키 거리입니다. 파라미터 p 값을 조절하여 다양한 거리 측정 방식을 하나의 공식으로 표현할 수 있습니다.
개요
간단히 말해서, 민코프스키 거리는 여러 거리 측정의 일반화된 형태입니다. p=1이면 맨해튼 거리, p=2이면 유클리드 거리, p=∞이면 체비셰프 거리가 됩니다.
왜 이 개념이 필요한지 실무 관점에서 설명하면, 데이터의 특성에 맞게 거리 측정 방식을 선택할 수 있기 때문입니다. 유연성이 매우 높으며, 하이퍼파라미터 튜닝으로 최적의 p 값을 찾을 수 있습니다.
예를 들어, K-NN 알고리즘에서 p 값을 1에서 5까지 바꿔가며 교차 검증하면 해당 데이터셋에 가장 적합한 거리 측정을 찾을 수 있습니다. 개별 거리 측정이 "하나의 방법"만 제공했다면, 민코프스키 거리는 "무한한 방법"을 제공합니다.
기존에는 유클리드와 맨해튼 중 하나를 선택했다면, 이제는 그 중간 어딘가(p=1.5 등)도 시도할 수 있습니다. 민코프스키 거리의 핵심 특징은 유연성과 통일성입니다.
하나의 공식으로 여러 거리를 표현하므로 코드가 간결해지며, p 값만 바꾸면 다양한 실험이 가능합니다. 또한 수학적으로 명확한 의미를 가지고 있어서 이론적 분석도 용이합니다.
이러한 특징들이 머신러닝 라이브러리의 기본 기능으로 구현되어 있습니다.
코드 예제
import numpy as np
from scipy.spatial.distance import minkowski
# 두 점의 좌표
point_a = np.array([1, 2, 3, 4])
point_b = np.array([5, 6, 7, 8])
# 민코프스키 거리 계산 (다양한 p 값으로)
p_values = [1, 2, 3, np.inf]
labels = ["맨해튼(p=1)", "유클리드(p=2)", "p=3", "체비셰프(p=∞)"]
print("다양한 민코프스키 거리:")
for p, label in zip(p_values, labels):
dist = minkowski(point_a, point_b, p)
print(f"{label}: {dist:.4f}")
# 출력:
# 맨해튼(p=1): 16.0000
# 유클리드(p=2): 8.0000
# p=3: 6.3496
# 체비셰프(p=∞): 4.0000
# 직접 구현 (원리 이해용)
def minkowski_manual(x, y, p):
if p == np.inf:
return np.max(np.abs(x - y)) # 최댓값
else:
return np.sum(np.abs(x - y) ** p) ** (1/p)
# K-NN에서 최적 p 찾기 예시
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
iris = load_iris()
best_p = 0
best_score = 0
for p in [1, 1.5, 2, 3, 5]:
knn = KNeighborsClassifier(n_neighbors=5, p=p)
scores = cross_val_score(knn, iris.data, iris.target, cv=5)
avg_score = scores.mean()
print(f"p={p}: 정확도 {avg_score:.4f}")
if avg_score > best_score:
best_score = avg_score
best_p = p
print(f"\n최적 p: {best_p} (정확도: {best_score:.4f})")
설명
이것이 하는 일: 각 차원의 차이를 p제곱하고 합한 뒤 p제곱근을 취합니다. p 값을 조절하여 큰 차이에 대한 민감도를 조정할 수 있습니다.
첫 번째로, x - y로 각 차원의 차이를 구합니다. [1-5, 2-6, 3-7, 4-8] = [-4, -4, -4, -4]가 됩니다.
그 다음 절댓값을 취하면 [4, 4, 4, 4]입니다. 이 예제는 모든 차원에서 동일하게 4씩 차이가 나는 특수한 경우입니다.
그 다음으로, ** p로 각 차이를 p제곱합니다. p=1이면 [4, 4, 4, 4] 그대로, p=2이면 [16, 16, 16, 16], p=3이면 [64, 64, 64, 64]가 됩니다.
p가 클수록 큰 값이 더욱 크게 증폭됩니다. 이것이 "큰 차이에 얼마나 민감할 것인가"를 조절하는 메커니즘입니다.
세 번째로, np.sum()으로 모두 더합니다. p=1일 때 16, p=2일 때 64, p=3일 때 256이 됩니다.
마지막으로 **(1/p)로 p제곱근을 구하면 p=1일 때 16, p=2일 때 8, p=3일 때 약 6.35가 나옵니다. p가 커질수록 거리가 작아지는 것처럼 보이는데, 이는 제곱과 제곱근의 효과 때문입니다.
p=∞일 때는 최댓값만 취하므로 4가 됩니다. 여러분이 이 코드를 사용하면 K-NN, K-means, DBSCAN 같은 거리 기반 알고리즘에서 최적의 거리 측정 방법을 찾거나, 이상치 탐지에서 민감도를 조절하거나, 추천 시스템에서 다양한 유사도 계산을 실험할 수 있습니다.
실무에서는 이미지 검색(p=2가 일반적), 시계열 분석(p=1이 노이즈에 강함), 고차원 데이터 클러스터링(p=1.5~2.5 사이를 실험) 등에 활용됩니다. sklearn의 대부분 알고리즘이 p 파라미터를 지원하므로 쉽게 실험할 수 있습니다.
실전 팁
💡 p 값 선택 가이드: 이상치가 많으면 p=1(맨해튼), 일반적인 경우 p=2(유클리드), 극단값이 중요하면 p=3 이상을 시도하세요.
💡 계산 비용: p가 클수록 계산이 복잡해집니다. 실시간 시스템에서는 p=1이 가장 빠르고, p=2도 충분히 빠릅니다.
💡 Grid Search로 최적 p 찾기: GridSearchCV에서 param_grid={'p': [1, 1.5, 2, 3, 5]}처럼 설정하면 자동으로 최적값을 찾습니다.
💡 체비셰프 거리(p=∞)는 체스에서 왕의 이동 거리와 같습니다. 가장 다른 차원 하나만 보므로 특이한 경우에 유용합니다.
💡 고차원 데이터에서는 p=1이 p=2보다 성능이 좋을 수 있습니다. 차원의 저주 효과가 덜하기 때문입니다. 항상 실험해보세요.