임베딩 모델의 문서를 보면 요즘 이런 옵션이 붙어 있습니다. 「3072차원 출력을 256차원으로 잘라 써도 성능이 크게 떨어지지 않습니다.」 벡터 데이터베이스 쪽에서도 저장 비용과 검색 속도 때문에 차원을 줄이는 것이 일상입니다.
줄여도 되는 근거가 뭘까요. 「중요한 정보가 앞쪽 좌표에 몰려 있어서」는 학습으로 그렇게 만든 경우의 설명입니다. 그런데 아무 학습도 없이, 무작위 행렬 하나를 곱하는 것만으로도 거리가 거의 그대로 보존된다는 정리가 있습니다. 그것도 원래 차원이 얼마든 상관없이 그렇습니다.
지난 글에서 고차원 공간이 얼마나 이상하게 생겼는지를 봤습니다. 이번 글은 그 이상함을 거꾸로 이용하는 결과입니다.
무작위 사영의 정의
난수 행렬 하나
차원 벡터 에 행렬 을 곱해 차원으로 보냅니다. 의 원소는 전부 평균 0, 분산 인 정규분포에서 독립으로 뽑습니다.
데이터를 전혀 안 봅니다. 주성분 분석처럼 공분산을 계산하지도 않고, 학습도 없습니다. 그냥 난수 행렬입니다.
분산에 를 넣는 것과 표준정규에서 뽑은 뒤 로 나누는 것은 같은 일입니다. 정규분포는 상수배를 하면 분산이 그 제곱만큼 줄어드므로 을 로 나누면 분산이 가 됩니다. 코드에서는 대개 뒤쪽으로 적습니다 — rng.standard_normal((d, k)) / np.sqrt(k) 가 그것입니다.
길이의 평균 보존
먼저 평균부터 봅니다. 의 각 좌표는 이고, 의 원소가 독립이므로
입니다. 개를 더하면
로 길이가 평균적으로 정확히 보존됩니다. 사영은 선형이므로 이고, 따라서 두 점 사이의 거리도 같은 성질을 갖습니다.
내적의 덧셈 오차
거리만 보존되면 내적은 저절로 따라옵니다. 편극 항등식이라고 부르는 한 줄이 그 다리입니다.
오른쪽에 길이 제곱 둘밖에 없으므로, 그 둘이 보존되면 내적도 보존됩니다. 코사인 유사도로 검색하는 자리에서 랜덤 사영을 써도 되는 근거가 이것입니다.
다만 오차가 붙는 방식이 다릅니다. 길이는 배 안에 든다는 상대오차인데, 위 식에 그것을 넣으면 내적 쪽 오차는 에 비례하는 덧셈 오차가 됩니다. 두 값이 다른 뜻입니다 — 내적 자체가 작은 쌍, 즉 거의 직교한 두 벡터에서는 그 덧셈 오차가 값 자체보다 클 수 있습니다. 가까운 이웃의 내적은 잘 보존되고 먼 쌍의 내적은 순서가 뒤집히기도 한다는 것이 여기서 나오는 실무적 결론입니다.
평균이 맞는 것과 매번 맞는 것은 다릅니다. 진짜 질문은 흔들림이 얼마나 되느냐입니다.
흔들림의 크기
카이제곱으로 적기
이고 서로 독립이므로
입니다. 오른쪽의 합은 자유도 인 카이제곱 분포입니다 — 표준정규분포 개를 제곱해 더한 분포입니다.
평균과 분산은 한 개짜리에서 세워 배 하면 끝입니다. 이면 이고, 정규분포의 네제곱 평균이 3이므로 입니다. 독립인 것 개를 더하면 평균도 분산도 배이므로 평균 , 분산 입니다.
제곱근을 취하면 절반
그러면 길이 제곱의 상대 표준편차는
입니다. 우리가 보는 것은 길이 제곱이 아니라 길이이므로 제곱근을 한 번 더 취해야 하는데, 여기서 흔들림이 절반으로 줄어듭니다. 이유는 한 줄입니다 — 1 근처에서 이므로, 제곱 쪽의 어긋남 가 길이 쪽에서는 로 전달됩니다.
실측과 맞대기
실제로 재 보겠습니다. 4096차원에 점 500개를 두고 모든 쌍의 거리를 사영 전후로 비교했습니다.
import numpy as np
rng = np.random.default_rng(0)
def pdist(X):
s = (X*X).sum(1)
D2 = np.maximum(s[:, None] + s[None, :] - 2*X@X.T, 0)
return np.sqrt(D2[np.triu_indices(X.shape[0], 1)])
d, n = 4096, 500
X = rng.standard_normal((n, d)).astype(np.float32)
D0 = pdist(X) # 쌍 124,750개
for k in [16, 32, 64, 128, 256, 512, 1024, 2048]:
R = (rng.standard_normal((d, k)) / np.sqrt(k)).astype(np.float32)
r = pdist(X @ R) / D0
print(f"k={k:5d} 최악 왜곡 {np.abs(r-1).max():.4f} 표준편차 {r.std():.4f} "
f" 이론 {np.sqrt(1/(2*k)):.4f} ±0.1 안에 든 비율 {np.mean(np.abs(r-1) <= 0.1):.4f}")
k= 16 최악 왜곡 0.8227 표준편차 0.1743 이론 0.1768 ±0.1 안에 든 비율 0.4281
k= 32 최악 왜곡 0.4921 표준편차 0.1224 이론 0.1250 ±0.1 안에 든 비율 0.5819
k= 64 최악 왜곡 0.4337 표준편차 0.0873 이론 0.0884 ±0.1 안에 든 비율 0.7452
k= 128 최악 왜곡 0.2857 표준편차 0.0624 이론 0.0625 ±0.1 안에 든 비율 0.8907
k= 256 최악 왜곡 0.1867 표준편차 0.0447 이론 0.0442 ±0.1 안에 든 비율 0.9749
k= 512 최악 왜곡 0.1439 표준편차 0.0306 이론 0.0312 ±0.1 안에 든 비율 0.9986
k= 1024 최악 왜곡 0.1185 표준편차 0.0224 이론 0.0221 ±0.1 안에 든 비율 1.0000
k= 2048 최악 왜곡 0.0710 표준편차 0.0153 이론 0.0156 ±0.1 안에 든 비율 1.0000
여덟 줄 전부 이론값과 소수 셋째 자리까지 맞습니다. 마지막 열까지 예측되는지도 확인해 둘 만합니다. 흔들림이 정규분포에 가깝다고 보면 안에 들 비율은 인데, 에 위의 이론값을 넣으면 이렇게 나옵니다.
| 16 | 32 | 64 | 128 | 256 | 512 | |
|---|---|---|---|---|---|---|
| 정규근사 예측 | 0.4284 | 0.5763 | 0.7421 | 0.8904 | 0.9763 | 0.9986 |
| 실측 | 0.4281 | 0.5819 | 0.7452 | 0.8907 | 0.9749 | 0.9986 |
여섯 줄 모두 소수 둘째 자리까지 맞습니다. 표준편차 하나만 알면 분포 전체가 예측된다는 뜻이고, 다음 절에서 꼬리까지 밀어붙일 수 있는 근거가 이것입니다.
여기서 이미 보이는 것이 있습니다. 이라는 값이 이론식 어디에도 없습니다. 원래 차원은 안에 삼켜져 사라졌고, 남은 것은 뿐입니다.
모든 쌍에 대한 보장
존슨–린덴스트라우스 보조정리
위 표에서 표준편차와 최악 왜곡이 다르게 움직입니다. 에서 표준편차는 0.062인데 가장 심하게 어긋난 쌍은 0.286이었습니다. 쌍이 12만 개나 되니 그중 하나쯤은 꼬리에 있기 마련입니다.
존슨–린덴스트라우스 보조정리는 그 「하나쯤」까지 막으려면 가 얼마나 커야 하는지를 답합니다. 정리는 이렇게 말합니다. 의 점 개와 에 대해
이면, 모든 쌍 에 대해
를 만족하는 사영이 존재합니다.
세 걸음 유도
첫째, 방금 본 대로 는 카이제곱을 로 나눈 것입니다.
둘째, 그 분포의 꼬리를 누릅니다. 꼬리 확률을 다루는 표준적인 길은 두 단계입니다 — 관심 있는 사건을 지수함수로 감싸 로 바꾸고, 여기에 마르코프 부등식을 씁니다. 음이 아닌 값의 평균이 이면 그 값이 를 넘을 확률이 이하라는 것이라, 오른쪽이 가 됩니다. 카이제곱은 이 지수의 평균이 닫힌 식으로 적히므로 를 가장 좋은 값으로 고르면
가 나옵니다. 에 대해 지수적으로 작아진다는 점이 핵심입니다.
지수 안의 에서 뒤 항이 어디서 오는지도 짚어 둡니다. 최적화한 결과에 이 남는데 이것을 2차까지 펴면 이고, 그 나머지를 버리지 않고 눌러 담은 것이 항입니다. 그래서 이 작을 때는 만 남고, 이 1에 가까우면 이 오히려 0으로 가 식이 못 쓰게 됩니다 — 이 식의 유효 범위가 인 이유입니다.
셋째, 쌍이 개이므로 하나라도 벗어날 확률은 그 수를 곱한 것 이하입니다(합집합 한계). 그 값이 1보다 작으려면
입니다. 로그는 여기서 나옵니다 — 쌍의 개수가 인데 확률이 지수라, 지수와 만나면 로그가 됩니다. 그리고 세 걸음 어디에도 가 나오지 않습니다.
「존재한다」와 「확률 1/2」
정리의 결론은 "그런 사영이 존재한다"인데 유도가 준 것은 "무작위 이 실패할 확률이 1/2 미만"입니다. 이 둘은 같은 말입니다 — 실패 확률이 1보다 작다는 것 자체가 성공하는 경우가 하나는 있다는 뜻이기 때문입니다. 존재를 확률로 증명하는 방식이라 만드는 방법까지 같이 주는 것이 이 유도의 이점입니다.
값을 치르는 쪽은 확인입니다. 한 번 뽑아 실패하면 다시 뽑으면 되는데, 실패했는지 알려면 모든 쌍을 재 봐야 하고 그 비용이 입니다. 이 100만이면 사영하는 것보다 확인하는 쪽이 훨씬 비쌉니다. 실무에서 이 확인을 건너뛰고 한 번 뽑은 을 그냥 쓰는 이유가 여기 있고, 다음 절에서 그렇게 해도 되는지를 실제로 세어 봅니다.
d와 n에 대한 의존성
실험의 점을 정규분포에서 뽑은 것이 결과를 유리하게 만들지는 않는지부터 짚고 갑니다. 정리에는 분포 가정이 없습니다 — 점 개가 어디서 왔든, 심지어 누가 최악으로 골라 놓았든 조건이 그대로 걸립니다. 그러니 실험의 역할은 보장을 확인하는 것이 아니라 보장이 얼마나 헐거운지를 재는 것입니다.
d는 조건에 없다
점 500개와 을 고정하고 만 64배 늘렸습니다.
| 256 | 1024 | 4096 | 16384 | |
|---|---|---|---|---|
| 최악 왜곡 | 0.270 | 0.271 | 0.295 | 0.280 |
| 표준편차 | 0.061 | 0.064 | 0.063 | 0.063 |
64배를 늘렸는데 아무 일도 안 일어납니다.
n에는 로그로만
다음은 점 개수 에는 로그로만 걸린다는 쪽입니다. , 을 고정하고 을 80배 늘렸습니다.
| 100 | 500 | 2000 | 8000 | |
|---|---|---|---|---|
| 쌍의 개수 | 4,950 | 124,750 | 1,999,000 | 31,996,000 |
| 최악 왜곡 | 0.259 | 0.281 | 0.319 | 0.362 |
쌍의 개수는 6천 개에서 3천만 개로 6천 배 늘었는데 최악 왜곡은 0.26에서 0.36까지만 올랐습니다. 왜곡이 을 따라가기 때문입니다 — 의 제곱근이 1.39이고, 실제 비는 0.362/0.259 = 1.40입니다.
R을 다시 뽑으면
앞 절이 남긴 물음 — 한 번 뽑은 이 운이 나빴을 가능성 — 을 재 봅니다. 같은 점 집합에 만 100번 다시 뽑아 매번 최악 왜곡을 셌습니다.
100번 전부 0.251에서 0.324 사이이고 중앙이 0.281입니다. 다시 뽑아서 얻을 수 있는 이득이 10% 남짓이라, 확인 비용을 무는 것이 거의 뜻이 없다는 결론이 나옵니다. 12만 쌍의 최댓값은 이미 평균적인 꼬리라 뽑기마다 크게 흔들리지 않습니다.
같은 그림이 정리가 얼마나 느슨한지도 보여 줍니다. 에 를 넣으면 정리가 보장하는 것은 까지인데, 실측 최악 왜곡은 제곱거리 기준 0.295였습니다. 과 에서는 정리가 아무것도 보장하지 못하는데 실측은 0.63과 0.44로 멀쩡합니다. 느슨함의 출처는 합집합 한계입니다 — 12만 쌍이 전부 독립으로 최악일 수 있다고 치고 확률을 그냥 더하는데, 실제 쌍들은 그렇게 어긋나지 않습니다.
필요한 차원의 크기
표로 읽기
식에 값을 넣어 보면 실감이 옵니다. 로 계산한 것입니다.
| 점 개수 | |||
|---|---|---|---|
| 1,000 | 5,527 | 1,382 | 615 |
| 10,000 | 7,369 | 1,843 | 819 |
| 1,000,000 | 11,053 | 2,764 | 1,229 |
| 1,000,000,000 | 16,579 | 4,145 | 1,843 |
읽는 법이 두 가지입니다.
좋은 소식은 세로 방향입니다. 점을 100만 개에서 10억 개로, 천 배 늘려도 필요한 차원은 1.5배밖에 안 늡니다. 데이터가 얼마나 많든 상관없이 몇천 차원이면 됩니다.
나쁜 소식은 가로 방향입니다. 이 분모에 제곱으로 들어가므로 정밀도를 두 배 올리려면 차원이 네 배 필요합니다. 을 요구하면 100만 점에 11,053차원이 나오는데, 이건 원래 차원 3072보다 큽니다. 정리가 보장하는 것을 곧이곧대로 받으면 차원이 줄기는커녕 늘어납니다.
상수 8의 출처
분자의 8은 어디서 왔을까요. 앞 절의 유도를 되짚으면 꼬리 한계의 지수에 4가 있었고 쌍의 개수 에서 2가 나와 둘이 곱해진 것입니다. 둘 다 증명을 세우기 편한 쪽으로 고른 값이지 최선을 계산한 값이 아닙니다. 실제로 다른 증명 기법을 쓰면 4나 2로도 적힌 문헌이 있습니다.
그러니 이 상수를 실무 설정에 그대로 옮기는 것은 뜻이 없습니다. 이 식에서 가져갈 것은 과 라는 모양이지 이라는 수가 아닙니다.
recall에서 k를 역산하기
그러면 실무에서는 를 어떻게 정할까요. 정리를 쓰는 대신 재서 정합니다.
- 데이터에서 쌍을 몇만 개 표본으로 뽑아 사영 전후의 거리 비를 잽니다.
- 목표를 왜곡이 아니라 검색 품질로 잡습니다 — 상위 10개를 몇 개나 맞히는가입니다.
- 를 두 배씩 올려 가며 목표를 넘는 자리를 찾고, 넘은 값과 그 직전 값 사이를 이분 탐색으로 좁힙니다.
왜곡이 로 단조롭게 줄어들므로 이분 탐색이 그대로 성립합니다. 위 실험에서 일 때 이미 97.5%의 쌍이 안에 있었는데, 검색에서 필요한 것은 「모든 쌍의 거리가 정확하다」가 아니라 「상위 몇 개의 순서가 대체로 맞는다」이므로 실무의 는 정리가 요구하는 것보다 훨씬 작습니다.
요구 조건을 쌍마다 다르게 걸 수도 있습니다. 가까운 쌍만 정확하면 되는 검색에서는 먼 쌍이 얼마나 어긋나든 상관없고, 조건이 걸리는 쌍의 수가 에서 「각 점의 이웃 몇 개」로 줄면 합집합 한계의 곱하는 수도 함께 줄어듭니다. 정리를 그대로 쓰는 것과 필요한 만큼만 요구하는 것 사이의 차이가 실무의 를 만듭니다.
주성분 분석과 희소 사영
주성분 분석과의 대비
| 랜덤 사영 | 주성분 분석 | |
|---|---|---|
| 데이터를 보는가 | 안 본다 | 공분산을 계산한다 |
| 만드는 비용 | 난수 생성만 | 또는 반복법 |
| 새 데이터 | 같은 을 그냥 곱한다 | 분포가 바뀌면 다시 계산 |
| 보존하는 것 | 모든 쌍의 거리 | 분산이 큰 방향 |
| 같은 에서 품질 | 보통 낮다 | 보통 높다 |
랜덤 사영이 이기는 자리는 품질이 아니라 조건입니다. 데이터를 한 번도 안 봐도 되므로 스트리밍으로 들어오는 벡터에 바로 쓸 수 있고, 데이터셋이 커져도 사영 행렬을 다시 만들 필요가 없습니다. 그리고 보장이 분포와 무관합니다 — 주성분 분석은 분산이 큰 방향에 정보가 있다는 가정이 필요한데, 랜덤 사영의 보장은 그런 가정 없이 모든 쌍에 걸립니다.
부호만 남긴 사영
의 원소가 꼭 정규분포일 필요는 없습니다. 평균 0, 분산 이고 꼬리가 너무 두껍지만 않으면 같은 보장이 성립합니다. 가장 간단한 것이 원소를 로만 두는 것입니다.
이러면 곱셈이 사라집니다. 면 더하고 면 빼는 일뿐이라 부호 뒤집기와 덧셈으로 사영이 끝나고, 마지막에 로 한 번 나누면 됩니다. 난수도 비트 하나면 충분해 을 저장하는 메모리가 32분의 1이 됩니다.
3분의 2를 0으로 비우기
한 걸음 더 갈 수 있습니다. 원소를 에서 각각 , , 의 확률로 뽑는 방식입니다. 평균은 0이고 분산은 이라 조건을 그대로 만족하는데, 원소의 3분의 2가 0이라 그 항은 계산 자체를 건너뜁니다.
사영 한 번의 비용은 이므로 가 클수록 이 절약이 커집니다. , 이면 한 벡터에 100만 번의 곱셈이 드는데 그중 3분의 2가 사라지는 셈입니다. 0의 비율을 더 올린 방식과, 난수 행렬 대신 빠른 변환을 써서 비용을 로 내리는 구조화 사영도 있습니다 — 이름만 적어 둡니다.
이 결과가 딛고 있는 자리도 여럿입니다. 다음 글의 무작위 초평면 해싱은 사영한 값의 부호만 남기는 방식이고, 거리가 보존되니 각도도 보존된다는 것이 그 근거입니다. Matryoshka 임베딩은 앞쪽 좌표만 잘라 써도 되게 학습시키는 방법인데, 「낮은 차원으로도 거리가 담긴다」는 사실 자체는 학습 없이도 성립한다는 것이 이 정리입니다.
정리
- 원소가 인 무작위 행렬 을 곱하면 로 길이가 평균적으로 정확히 보존된다. 사영이 선형이라 거리도 같고, 편극 항등식을 거치면 내적도 따라온다 — 다만 내적 쪽 오차는 에 비례하는 덧셈 오차라 거의 직교한 쌍에서는 값보다 클 수 있다.
- 은 카이제곱을 로 나눈 것이라 길이의 상대 표준편차가 다. 제곱근을 취하면서 절반이 되는 것은 한 줄이다. 4096차원 실험에서 여덟 개 전부 이론과 맞았고, 「±0.1 안에 든 비율」까지 정규근사로 예측됐다.
- 존슨–린덴스트라우스 보조정리: 이면 모든 쌍의 거리가 안에 든다. 마르코프 부등식으로 얻은 카이제곱 꼬리 한계에 쌍의 개수 를 곱하는 합집합 한계에서 로그가 나온다.
- 원래 차원 는 조건에 없다. 를 256에서 16384로 64배 늘려도 최악 왜곡이 0.27~0.30으로 그대로였다.
- 점 개수에는 으로만 걸린다. 쌍을 6천 개에서 3천만 개로 6천 배 늘렸을 때 최악 왜곡은 0.259에서 0.362로, 예측한 비 1.39와 맞는 1.40배만 올랐다.
- 을 다시 뽑아 얻을 것이 거의 없다. 100번 다시 뽑은 최악 왜곡이 0.251~0.324에 전부 모였다. 대신 정리는 느슨하다 — 에서 정리는 아무것도 보장하지 않는데 실측은 0.63이었고, 출처는 합집합 한계다.
- 은 제곱으로 들어간다. 정밀도를 두 배 올리려면 차원이 네 배다. 상수 8도 증명 기법에서 나온 값이라, 이 식에서 가져갈 것은 과 라는 모양이지 수가 아니다. 실무의 는 목표 recall에서 이분 탐색으로 역산한다.
- 원소를 로 두면 곱셈이 덧셈이 되고, 을 로 뽑으면 3분의 2가 0이 되어 비용이 그만큼 줄어든다.
읽어주셔서 감사합니다. 😊

