성분별 곱을 하나의 점수로
길이가 같은 두 벡터 와 의 내적은 대응하는 성분을 곱해 모두 더한 값이야.
입력은 벡터 둘이고 출력은 스칼라 하나야. 행렬곱의 각 칸도 한 행과 한 열의 내적으로 만들어지므로, 선형층과 어텐션처럼 행렬곱을 많이 쓰는 모델에서는 내적이 반복해서 등장해.
각도와 크기를 함께 읽기
유클리드 공간에서는 야. 같은 방향이면 양수, 수직이면 0, 반대 방향이면 음수가 돼. 하지만 크기도 함께 곱해지므로 내적 자체를 곧바로 순수한 유사도라고 부르면 안 돼. 방향만 비교하려면 두 벡터를 단위 길이로 나눈 코사인 유사도를 써.
같은 식을 읽는 세 렌즈
| 렌즈 | 의 의미 |
|---|---|
| 대수 | 대응 성분의 곱을 합한 스칼라 |
| 기하 | 한 벡터를 다른 벡터 방향으로 투영한 길이에 기준 벡터의 길이를 곱한 값 |
| 통계 | 두 벡터를 중심화하면 공분산의 분자와 이어지고, 추가로 표준화하면 상관계수와 이어지는 합 |
AI에서 만나는 자리
- 어텐션 점수: query와 key의 내적을 차원 크기에 맞게 조정해 각 위치 사이의 호환성을 계산해.
- 검색과 추천: 단위 정규화한 임베딩의 내적은 코사인 유사도와 같아.
- 분류 logits: 선형 분류기는 은닉 벡터와 각 클래스 가중치 벡터의 내적에 편향을 더해 점수를 만들어.
- 행렬곱: 신경망의 선형변환은 내적을 여러 개 묶어 동시에 계산해.
import numpy as np
np.random.seed(42)
128차원 임의 벡터 두 개
a = np.random.randn(128) b = np.random.randn(128)
코사인 유사도 함수
def cosine_similarity(v1, v2): return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
첫 번째 비교
sim_ab = cosine_similarity(a, b) print(f"a와 b의 코사인 유사도: {sim_ab:.6f}")
a를 100배한 세 번째 벡터
c = a * 100
c와 b의 유사도
sim_cb = cosine_similarity(c, b) print(f"c와 b의 코사인 유사도: {sim_cb:.6f}")
두 결과 비교
print(f"두 유사도가 같은가? {np.isclose(sim_ab, sim_cb)}")
크기는 어떻게 변했나
print(f"\n||a|| = {np.linalg.norm(a):.4f}") print(f"||c|| = {np.linalg.norm(c):.4f}") print(f"c의 크기는 a의 {np.linalg.norm(c) / np.linalg.norm(a):.0f}배")
a와 b의 코사인 유사도: 0.029607 c와 b의 코사인 유사도: 0.029607 두 유사도가 같은가? True
크기만 변했지(스칼라) 방향(벡터)는 변하지 않았기 때문에 코사인 유사도는 같음, 두 백터에 어떤 값을 곱해도 동일하다