본문으로 건너뛰기

벡터 기호 구조: 결합과 중첩

📍 현재 위치: V부 · 어텐션과 트랜스포머 — 16장. 어텐션은 결합 문제에서 끝났습니다: 가중 평균은 carol, p1, advising, authoring의 흔적을 간직하지만, 무엇이 무엇과 짝을 이루었는지를 말해 주는 것은 아무것도 남기지 않습니다. 이 장은 역할과 채움값이 짝지어진 채로 유지되게 하는 벡터 대수를 만듭니다.

이 권에서 지금까지 등장한 모든 모델은 각 개체에 저마다의 벡터를 부여하고 트리플의 점수 매기기를 기하학에 맡겼습니다: TransE는 평행이동했고, 박스는 포함했으며, 메시지 전달은 전파했습니다. 이 장은 더 낯선 내기를 겁니다. 벡터 기호 구조(Vector Symbolic Architecture, VSA)는 학술 지식 그래프 전체, 즉 13개 개체와 5개 관계 위의 18개 트리플 전부를 단 하나의 벡터 SRdS \in \mathbb{R}^{d}, 곧 dd개의 실수로 이루어진 목록(기호 \in은 "~에 속한다"로 읽고, Rd\mathbb{R}^{d}는 그러한 길이 dd의 목록 전체의 집합입니다) 안에 d=1024d = 1024로 중첩하여 저장하고, 훈련 루프가 전혀 없이 세 가지 대수 연산만으로 그 벡터에 질의하여 답합니다 [1]. 결합(binding)은 두 벡터를 같은 크기의, 어느 입력과도 닮지 않은 하나의 벡터로 합성합니다. 중첩(superposition)은 단순한 덧셈으로, 여러 결합 쌍이 하나의 벡터를 공유하게 해 줍니다. 결합 해제(unbinding)는 결합을 근사적으로 거꾸로 실행하여, 혼선(cross-talk) 잡음의 안개 아래에서 저장된 항목을 되살려 냅니다. 그리고 어휘 전체를 최근접 이웃으로 훑는 정리 메모리(cleanup memory)가 잡음 섞인 메아리를 정확한 기호로 되돌려 놓습니다. 어텐션이 관련성의 계산법을 가졌다면, 이 대수는 어텐션에게 없던 것, 즉 구조의 계산법을 가집니다. 모든 주장은 먼저 유도된 다음, 커밋된 examples/neural/vsa.py의 출력과 대조하여 검증됩니다.

쉽게 말하면

북적이는 방에서 아홉 쌍의 사람들이 동시에 아홉 개의 대화를 나누고 있다고 상상해 보세요. 방 안의 공기는 그 모두를 동시에 실어 나릅니다. 음파는 그저 더해지기 때문입니다. 그것이 중첩이고, 구석에 놓인 마이크가 아홉 개가 아니라 하나의 파형을 녹음하는 이유입니다. 이제 각 쌍이 저마다의 비밀 스크램블 코드로 말한다고 해 봅시다. 그것이 결합이고, 뒤섞인 대화를 엿들어도 아무것도 알 수 없는 이유입니다: 스크램블된 말소리는 코드와도 원래 단어와도 닮지 않은, 잡음처럼 들립니다. 당신은 정확히 한 쌍의 해독기를 쥐고 있습니다. 그것을 방의 단 하나뿐인 파형에 적용하면 그 쌍의 대화가 나머지 여덟 쌍의 웅성거림 아래에서 희미하게, 지직거리며 튀어나옵니다. 그것이 결합 해제이고, 그 지직거림은 결함이 아니라 다른 대화들입니다. 마지막으로, 들려오는 희미한 단어들을 이미 알고 있는 이름 목록과 대조해 가장 가까운 것을 고릅니다. 그것이 정리(cleanup)이고, 잡음 섞인 추측을 정확한 답으로 바꿔 놓습니다. 이 장의 모든 것이 그 방입니다: 벡터 하나가 공기이고, 그 안에서 누가 들릴 수 있는지는 대수가 결정합니다.

이 장에서 다루는 내용

  • 정직하게 서술한 결합 문제: 덧셈만으로는 왜 "alice가 bob을 지도한다"와 "bob이 carol을 지도한다"가 순서를 잃은 뒤범벅으로 뭉개지는지, 그리고 이름을 붙일 가치가 있으려면 어떤 결합 연산자든 먼저 충족해야 하는 세 가지 요구 조건(비유사성, 가역성, 덧셈에 대한 분배법칙)을 다룹니다.
  • 원재료로서의 무작위 벡터: 독립적인 N(0,1/d)\mathcal{N}(0, 1/d) 항목 벡터의 기대 제곱 노름이 왜 1이고 쌍별 코사인이 왜 ±1/d\pm 1/\sqrt{d} 규모인지를 1차 모멘트로부터 유도합니다.
  • 원형 합성곱, 두 번: O(d2)O(d^2) 인덱스 정의와, 합성곱 정리를 거치는 O(dlogd)O(d \log d) 고속 푸리에 변환 경로, 그리고 4.16×10174.16 \times 10^{-17}에서 커밋된 일치 검증을 다룹니다.
  • 잡음까지 포함해 유도한 결합 해제: 근사 역원으로서의 인볼루션, 임펄스 스파이크 항등식 (aa~)[0]=a2(\,\mathbf{a} \circledast \tilde{\mathbf{a}}\,)[0] = \lVert \mathbf{a} \rVert^2, 그리고 왕복 충실도 1/20.7071/\sqrt{2} \approx 0.707을 예측해 측정값 0.6632와 대조하는 모멘트 계산을 다룹니다.
  • 교환법칙의 유령과 그 수리: 소박한 트리플 인코딩이 왜 지도교수와 지도 학생을 구별하지 못하는지를 alice가 bob의 학생을 묻는 질의에 유령처럼 나타나는 실행 결과로 인용하고, 인코딩을 방향성 있게 만드는 고정 무작위 순열을 다룹니다.
  • 지식 그래프 전체를 벡터 하나에: 인코딩 합, 질의 파이프라인, 프로브 하나에서 bob의 두 지도 학생이 동시에 떠오르는 것, 그리고 18개 중 18개의 꼬리 질의가 정답으로 돌아오는 것을 다룹니다.
  • 중심 실험으로서의 용량 격자: 세 가지 차원과 네 가지 메모리 부하에 걸친 랭크 1 정확도, 표에서 읽어 내는 두 가지 단조성, 그리고 그 둘의 배후에 있는 신호 대 잡음 스케일링 논증 d/k\sqrt{d/k}를 다룹니다.
  • 가족과 프런티어: 홀로그램 축약 표현, 이진 스패터 코드, MAP(Multiply-Add-Permute), 그리고 그 조상인 텐서곱을 하나의 비교 표에 담고, 트랜스포머 내부의 중첩으로 이어지는 한 문장짜리 다리를 놓습니다.

요구 조건 목록으로 서술한 결합 문제

덧셈이 할 수 없는 것에서 시작합니다. 학술 세계의 모든 기호가 저마다 벡터를 가진다고 합시다: alice에는 ealice\mathbf{e}_{\text{alice}}, 지도 관계에는 wadvises\mathbf{w}_{\text{advises}} 하는 식입니다. "alice가 bob을 지도한다"라는 사실을 저장하는 가장 소박한 방법은 참여자들을 더하는 것, 즉 ealice+wadvises+ebob\mathbf{e}_{\text{alice}} + \mathbf{w}_{\text{advises}} + \mathbf{e}_{\text{bob}}이고, 가장 소박한 메모리는 모든 사실을 다 더해 버립니다. 이제 두 번째 사실 "bob이 carol을 지도한다"를 같은 방식으로 저장하고 그 합을 들여다봅시다:

(ealice+wadvises+ebob)+(ebob+wadvises+ecarol)  =  ealice+2ebob+ecarol+2wadvises.(\mathbf{e}_{\text{alice}} + \mathbf{w}_{\text{advises}} + \mathbf{e}_{\text{bob}}) + (\mathbf{e}_{\text{bob}} + \mathbf{w}_{\text{advises}} + \mathbf{e}_{\text{carol}}) \;=\; \mathbf{e}_{\text{alice}} + 2\,\mathbf{e}_{\text{bob}} + \mathbf{e}_{\text{carol}} + 2\,\mathbf{w}_{\text{advises}}.

덧셈은 교환법칙과 결합법칙을 만족하므로 이 합은 하나의 자루(bag)입니다: alice, bob, carol, 그리고 지도 관계가 관여했다는 것, 심지어 몇 번 관여했는지까지는 기억하지만, 누가 누구에게 무엇을 했는지는 돌이킬 수 없이 잃어버렸습니다. "carol이 bob을 지도한다"에 "bob이 alice를 지도한다"를 더해도 똑같은 자루가 나옵니다. 이것이 앞 장의 뭉개짐을 대수 두 줄로 적은 것이며, 벡터 표현의 결합 문제(binding problem)입니다: 중첩만으로는 존재는 저장해도 구조는 결코 저장하지 못합니다 [2].

빠져 있는 것은 두 번째 연산, 일단 \circledast라고 부를, 두 벡터를 하나로 합성하는 연산입니다. 역할과 그 채움값(filler)이 합 안에서 한 단위로 함께 다니게 하기 위해서입니다. 연산자를 고르기 전에 그것이 만족해야 할 조건부터 적어 둡시다. 요구 조건들이 연산자를 거의 유일하게 골라 주기 때문입니다.

  1. 비유사성(dissimilarity). 결합된 쌍 ab\mathbf{a} \circledast \mathbf{b}a\mathbf{a}와도 b\mathbf{b}와도 닮지 않아야 합니다: 둘 모두에 대한 코사인이 0에 가까워야 합니다. 이것이 뭉개짐을 막는 성질입니다. 평균처럼 입력을 그저 섞기만 하는 결합이라면, 같은 기호가 관여하는 다른 모든 것과 부분적으로 일치해 버릴 것입니다.
  2. 가역성(invertibility), 적어도 근사적으로. ab\mathbf{a} \circledast \mathbf{b}a\mathbf{a}에 대한 지식으로부터, b\mathbf{b}를 식별할 수 있을 만큼 가까운 무언가를 복원할 수 있어야 합니다. 되돌릴 수 없는 결합은 표현이 아니라 해시입니다.
  3. 덧셈에 대한 분배법칙(distributivity). a(b+c)=ab+ac\mathbf{a} \circledast (\mathbf{b} + \mathbf{c}) = \mathbf{a} \circledast \mathbf{b} + \mathbf{a} \circledast \mathbf{c}가 성립하여 결합과 중첩이 서로 맞물려야 합니다: 결합 쌍들의 합을 해제하면 각 쌍이 따로따로 해제되며, 바로 이것이 여러 사실을 담은 메모리를 프로브 하나로 심문할 수 있게 해 줍니다.

한 가지 제약이 조용히 가장 중요합니다: 출력이 입력과 같은 차원이어야 한다는 것입니다. 수학적으로 자명한 결합인 텐서곱(tensor product), 즉 iijj열의 성분이 aibja_i b_j인 외적(outer product) ab\mathbf{a}\mathbf{b}^{\top}은 비유사성, 정확한 가역성, 분배법칙을 모두 만족하며, 이 장에 나오는 모든 것의 조상입니다 [2]. 그러나 텐서곱은 두 개의 dd차원 벡터를 d×dd \times d 행렬로 보내므로, 중첩 수준이 한 단계 깊어질 때마다 표현 크기가 제곱이 됩니다. 홀로그램 축약 표현(Holographic Reduced Representation, HRR)은 정확히 텐서곱을 고정 차원으로 압축한 것으로 도입되었습니다: 다음 절의 연산자인 원형 합성곱은 텐서곱을 반대각선을 따라 합산한 것으로, d2d^2개 중 dd개의 수만 남기고 그 압축의 대가를 잡음으로 치릅니다 [1].

원재료: 무작위 항목 벡터

이 대수는 올바른 종류의 벡터 위에서만 작동하므로, 벡터부터 고정합니다. 모든 기호는 단 한 번 무작위로 뽑히고 결코 훈련되지 않는 항목 벡터(item vector)를 받습니다: 각 좌표 dd개는 평균 0, 분산 1/d1/d의 가우스 분포 N(0,1/d)\mathcal{N}(0, 1/d)에서 독립적으로 표본화되며, 여기서 dd는 표현 차원(본 실험에서는 1024)입니다. 동반 코드 파일은 코드북(codebook) 전체를 컴프리헨션 한 줄로 만듭니다(vsa.py 120–125행):

def make_codebook(names: list[str], rng: np.random.Generator,
d: int) -> dict[str, np.ndarray]:
"""One i.i.d. random item vector per symbol, components ~ N(0, 1/d), so
every vector has expected squared norm 1 and any two distinct items are
nearly orthogonal (cosine ~ ±1/√d) — the raw material of every VSA."""
return {name: rng.normal(0.0, 1.0 / np.sqrt(d), size=d) for name in names}

독스트링은 두 가지를 주장합니다; 둘 다 유도해 봅시다. 항목 벡터 하나를 a=(a0,,ad1)\mathbf{a} = (a_0, \ldots, a_{d-1})이라 쓰고, 각 좌표 aka_k는 독립이며 E[ak]=0\mathbb{E}[a_k] = 0, E[ak2]=1/d\mathbb{E}[a_k^2] = 1/d라고 합시다(기호 E\mathbb{E}는 기댓값, 곧 추출의 무작위성에 대한 평균을 뜻합니다). 첫째, 제곱 노름 a2=k=0d1ak2\lVert \mathbf{a} \rVert^2 = \sum_{k=0}^{d-1} a_k^2, 즉 좌표 제곱들의 합의 기댓값은

E[a2]  =  k=0d1E[ak2]  =  d1d  =  1,\mathbb{E}\big[\lVert \mathbf{a} \rVert^2\big] \;=\; \sum_{k=0}^{d-1} \mathbb{E}[a_k^2] \;=\; d \cdot \frac{1}{d} \;=\; 1,

이므로 항목 벡터는 단위 구면 위나 그 근처에 삽니다. 둘째, 독립인 두 번째 항목 벡터 b\mathbf{b}를 잡아 내적 ab=kakbk\mathbf{a} \cdot \mathbf{b} = \sum_k a_k b_k의 모멘트를 계산합니다. 독립성 덕분에 각 기댓값이 인수분해되므로 평균은 0입니다:

E[ab]  =  k=0d1E[akbk]  =  k=0d1E[ak]E[bk]  =  0.\mathbb{E}[\mathbf{a} \cdot \mathbf{b}] \;=\; \sum_{k=0}^{d-1} \mathbb{E}[a_k b_k] \;=\; \sum_{k=0}^{d-1} \mathbb{E}[a_k]\,\mathbb{E}[b_k] \;=\; 0.

내적의 분산은 독립인 항 akbka_k b_k들의 분산의 합입니다. 각 항의 평균이 0이므로(바로 위의 식이 그것입니다) 그 분산은 2차 모멘트와 같고, E[ak2bk2]=E[ak2]E[bk2]=(1/d)(1/d)\mathbb{E}[a_k^2 b_k^2] = \mathbb{E}[a_k^2]\,\mathbb{E}[b_k^2] = (1/d)(1/d)입니다:

Var(ab)  =  k=0d11d1d  =  d1d2  =  1d.\operatorname{Var}(\mathbf{a} \cdot \mathbf{b}) \;=\; \sum_{k=0}^{d-1} \frac{1}{d}\cdot\frac{1}{d} \;=\; d \cdot \frac{1}{d^2} \;=\; \frac{1}{d}.

따라서 두 무작위 항목의 내적은 평균 0에 표준편차 1/d1/\sqrt{d}입니다. 이 장의 모든 비교가 쓰는 잣대는 코사인 유사도(cosine similarity), 즉 방향만 측정되도록 양쪽 길이를 나눈 내적 cos(x,y)=xy/(xy)\cos(\mathbf{x}, \mathbf{y}) = \mathbf{x} \cdot \mathbf{y} \,/\, (\lVert \mathbf{x} \rVert\, \lVert \mathbf{y} \rVert)이며, 짧은 도우미 함수 cosine()으로 구현되어 있습니다(vsa.py 102–107행). 두 노름이 모두 1 근처에 집중하므로, 서로 다른 두 기호 사이의 코사인은 ±1/d\pm 1/\sqrt{d} 규모입니다: d=1024d = 1024에서 약 ±0.031\pm 0.031입니다. 다른 모든 것이 기대는 성질이 바로 이것입니다. 고차원에서 무작위 벡터들은 기본적으로 거의 직교(nearly orthogonal)하므로, "닮았다"(코사인이 1에 가까움)와 "무관하다"(코사인이 0에 가까움)가 넓고 정량화 가능한 간극으로 분리되며, 이 장의 모든 연산이 지출하는 잡음 여유가 바로 그 간극입니다 [3].

결합은 원형 합성곱이다

HRR의 결합 연산자는 \circledast로 쓰는 원형 합성곱(circular convolution)입니다. 기호보다 먼저 정의를 풀어 읽어 봅시다: ii번째 출력 좌표는 a\mathbf{a}와, 좌표 순서를 뒤집어 ii칸만큼 순환 회전시킨 b\mathbf{b}의 복사본 사이의 내적입니다. 그래서 모든 출력 좌표가 두 입력의 모든 좌표를 섞으며, 인덱스 산술은 dd로 나눈 나머지(인덱스를 0,,d10, \ldots, d-1 안에 머물게 하는 것)로 감아 돕니다. 형식적으로, 00부터 d1d-1까지의 각 출력 인덱스 ii에 대해

(ab)[i]  =  k=0d1a[k]  b[(ik)modd],(\mathbf{a} \circledast \mathbf{b})[i] \;=\; \sum_{k=0}^{d-1} a[k]\; b[(i - k) \bmod d],

이고, 여기서 kkdd개의 입력 위치 전부를 훑습니다. 이 방식으로 dd개 좌표를 모두 계산하면 좌표당 dd번의 곱셈, 총 O(d2)O(d^2)의 작업이 듭니다. 동반 코드 파일은 이 정의를 기준 진리로 보존합니다(vsa.py 59–71행):

def bind_direct(a: np.ndarray, b: np.ndarray) -> np.ndarray:
"""Binding by the O(d²) textbook definition of circular convolution:

(a ⊛ b)[i] = Σ_{k=0}^{d−1} a[k] · b[(i − k) mod d].

Kept as the ground truth the FFT version is checked against."""
d = a.shape[0]
ks = np.arange(d)
out = np.empty(d)
for i in range(d):
# (a ⊛ b)[i] = Σ_k a[k] · b[(i − k) mod d]
out[i] = float(a @ b[(i - ks) % d])
return out

원형 합성곱은 교환적이고(ab=ba\mathbf{a} \circledast \mathbf{b} = \mathbf{b} \circledast \mathbf{a}, 합에서 k=ikk' = i - k로 치환하면 됩니다), 결합적이며, 덧셈에 분배됩니다(각 출력 좌표가 b\mathbf{b}에 대해 선형이기 때문입니다). 따라서 요구 조건 3은 정확히 성립합니다. 요구 조건 1은 통계적으로 성립합니다: 각 출력 좌표는 독립인 평균 0 항들의 곱 dd개의 합이므로, ab\mathbf{a} \circledast \mathbf{b}a\mathbf{a}(또는 b\mathbf{b}, 또는 임의의 제3의 항목)의 기대 내적은 00이고, 늘 그렇듯 1/d1/\sqrt{d} 규모의 요동을 가집니다. 결합된 쌍은 자기 자신의 인수를 포함해 모든 것과 거의 직교하는, 새로운 유사 기호입니다.

O(d2)O(d^2) 비용은 규모가 커지면 결격 사유가 될 텐데, 여기서 합성곱 정리(convolution theorem)가 제 몫을 합니다: 인덱스 영역의 원형 합성곱은 푸리에 영역의 성분별 곱셈입니다. 벡터를 dd개의 복소 주파수 계수 a^j=kake2πijk/d\hat{a}_j = \sum_k a_k e^{-2\pi \mathrm{i} jk/d}(여기서 i\mathrm{i}는 제곱하면 1-1이 되는 수, 곧 허수 단위이며, 각 계수를 복소수로 만드는 것이 바로 이것입니다)로 보내는 선형 사상인 이산 푸리에 변환(discrete Fourier transform, DFT)을 F\mathcal{F}라 쓰면, 이 정리는 모든 주파수 jj에 대해 F(ab)j=a^jb^j\mathcal{F}(\mathbf{a} \circledast \mathbf{b})_j = \hat{a}_j \hat{b}_j라고 말합니다. 고속 푸리에 변환(Fast Fourier Transform, FFT)은 F\mathcal{F}와 그 역변환을 O(dlogd)O(d \log d)번의 연산으로 계산하므로, 결합은 변환하고, 점별로 곱하고, 되돌리는 절차가 됩니다(vsa.py 74–82행):

def bind(a: np.ndarray, b: np.ndarray) -> np.ndarray:
"""Binding in O(d log d) via the convolution theorem: circular convolution
in the index domain is elementwise multiplication in the Fourier domain,

a ⊛ b = ℱ⁻¹( ℱ(a) ⊙ ℱ(b) ).

The ifft output is real up to float rounding (a and b are real), so the
imaginary part is dropped. Commutative and associative, like ⊛ itself."""
return np.real(np.fft.ifft(np.fft.fft(a) * np.fft.fft(b)))

실행은 이 항등식을 신뢰하기 전에 먼저 인증합니다: d=1024d = 1024에서 시드로 새로 만든 벡터들에 대해, 두 구현 사이의 최대 절대 불일치는 1024개 좌표 전체에 걸쳐 부동소수점 반올림 바닥 수준입니다:

binding: FFT (convolution theorem) vs the O(d²) definition
max |direct − fft| = 4.16e-17 (< 1e-10: identical; FFT used from here on)

역량 검사 assert fft_maxdiff < 1e-10(vsa.py 266행)이 매 실행마다 이 동치성을 지킵니다; 이 모듈은 검증되지 않은 지름길이 만든 숫자를 결코 인용하지 않습니다.

결합 해제: 인볼루션과 그 내재적 잡음

요구 조건 2, 곧 되돌아오는 길이 없다면 결합은 쓸모가 없습니다. b\mathbf{b}에 의한 합성곱의 정확한 역원은 존재하지만(푸리에 영역에서 b^j\hat{b}_j로 나누면 됩니다), 수치적으로 위험합니다: 크기 b^j\lvert \hat{b}_j \rvert가 작은 주파수에서는 잡음이 폭발적으로 증폭됩니다(막대 기호 \lvert \cdot \rvert는 복소수의 크기, 곧 절댓값을 뜻합니다). HRR은 대신 안정적인 근사 역원(approximate inverse)인 인볼루션(involution) b~\tilde{\mathbf{b}}을 씁니다. 이는 인덱스 순서를 모듈로 dd로 뒤집어 정의됩니다(vsa.py 85–94행):

b~[i]  =  b[(i)modd].\tilde{\mathbf{b}}[i] \;=\; \mathbf{b}[(-i) \bmod d].

두 번 적용하면 b\mathbf{b}로 돌아오며, "인볼루션"이라는 이름이 뜻하는 바가 그것입니다. 그 힘은 항등식 하나에 있습니다. bb~\mathbf{b} \circledast \tilde{\mathbf{b}}의 위치 00 좌표를 정의로부터 직접 계산하되, b~[(0k)modd]=b[((0k))modd]=b[k]\tilde{\mathbf{b}}[(0-k) \bmod d] = \mathbf{b}[(-(0-k)) \bmod d] = \mathbf{b}[k]를 대입합니다:

(bb~)[0]  =  k=0d1b[k]  b~[(0k)modd]  =  k=0d1b[k]b[k]  =  b2    1.(\mathbf{b} \circledast \tilde{\mathbf{b}})[0] \;=\; \sum_{k=0}^{d-1} b[k]\;\tilde{b}[(0 - k) \bmod d] \;=\; \sum_{k=0}^{d-1} b[k]\, b[k] \;=\; \lVert \mathbf{b} \rVert^2 \;\approx\; 1.

i0i \neq 0인 다른 모든 좌표는 서로 다른 좌표들의 곱의 합 kb[k]b[(ki)modd]\sum_k b[k]\,b[(k - i) \bmod d]이며, 평균 0에 익숙한 O(1/d)O(1/\sqrt{d}) 규모의 요동을 가집니다. 따라서 bb~\mathbf{b} \circledast \tilde{\mathbf{b}}는 근사적으로 단위 임펄스(unit impulse) δ\boldsymbol{\delta}(위치 0에 1, 나머지는 0인 벡터)를 b21\lVert \mathbf{b} \rVert^2 \approx 1로 스케일한 것에 잡음을 더한 것입니다. 임펄스는 원형 합성곱의 항등원입니다: 정의에 대입하면 (δa)[i]=k=0d1δ[k]a[(ik)modd]=a[i](\boldsymbol{\delta} \circledast \mathbf{a})[i] = \sum_{k=0}^{d-1} \delta[k]\, a[(i - k) \bmod d] = a[i]가 되는데, δ[k]\delta[k]k=0k = 0을 제외하면 사라지고 살아남는 항이 δ[0]a[i]=a[i]\delta[0]\,a[i] = a[i]이기 때문입니다. 그러므로 결합 해제는 결합을 한 번 더 하는 것에 지나지 않습니다(vsa.py 97–99행):

def unbind(s: np.ndarray, a: np.ndarray) -> np.ndarray:
"""Retrieve whatever ``s`` binds with ``a``: unbind(s, a) = s ⊛ ã."""
return bind(s, involution(a))

교환법칙과 결합법칙으로 항을 다시 묶으며 항등식들을 연결합니다:

unbind(ab,b)  =  (ab)b~  =  a(bb~)  =  a(b2δ+noise)    a+anoise.\operatorname{unbind}(\mathbf{a} \circledast \mathbf{b},\, \mathbf{b}) \;=\; (\mathbf{a} \circledast \mathbf{b}) \circledast \tilde{\mathbf{b}} \;=\; \mathbf{a} \circledast (\mathbf{b} \circledast \tilde{\mathbf{b}}) \;=\; \mathbf{a} \circledast \big(\lVert \mathbf{b} \rVert^2 \boldsymbol{\delta} + \text{noise}\big) \;\approx\; \mathbf{a} + \mathbf{a} \circledast \text{noise}.

복원된 벡터는 a\mathbf{a} 더하기 내재적 잡음입니다. 메모리에 아무것도 더 저장하지 않았는데도 그렇습니다. 잡음은 얼마나 될까요? 푸리에 영역이 세 단계로 답을 주며, 그 결과는 실행이 반드시 재현해야 하는 숫자입니다.

1단계: 푸리에 영역에서의 복호 벡터. 실수 벡터에서 인덱스 반전은 DFT를 켤레화하므로, F(b~)j=b^j\mathcal{F}(\tilde{\mathbf{b}})_j = \overline{\hat{b}_j}(윗줄은 복소켤레)입니다. 따라서 복호 벡터 abb~\mathbf{a} \circledast \mathbf{b} \circledast \tilde{\mathbf{b}}의 푸리에 계수는 a^jb^jb^j=a^jb^j2\hat{a}_j \hat{b}_j \overline{\hat{b}_j} = \hat{a}_j \lvert \hat{b}_j \rvert^2입니다: a\mathbf{a}의 각 주파수가 살아남되, 음이 아닌 무작위 가중치 Wj=b^j2W_j = \lvert \hat{b}_j \rvert^2로 다시 스케일됩니다. 가우스 항목 벡터에 대해 각 WjW_j의 평균은 E[Wj]=kE[bk2]=1\mathbb{E}[W_j] = \sum_k \mathbb{E}[b_k^2] = 1입니다.

2단계: 가중치의 분포. 복소수에 관한 표준적 사실 두 가지가 이 단계를 이어 줍니다. 오일러 공식(Euler's formula)은 복소 지수를 코사인과 사인으로 전개하여 eiθ=cosθisinθe^{-\mathrm{i}\theta} = \cos\theta - \mathrm{i}\sin\theta이므로, 이를 b^j\hat{b}_j의 정의에 대입하면 계수가 실수부와 허수부로 갈라집니다: b^j=kbkcos(2πjk/d)ikbksin(2πjk/d)\hat{b}_j = \sum_k b_k \cos(2\pi jk/d) - \mathrm{i} \sum_k b_k \sin(2\pi jk/d). 그리고 임의의 복소수 zz의 크기 제곱은 그 두 부분의 제곱의 합, 곧 z2=(Rez)2+(Imz)2\lvert z \rvert^2 = (\operatorname{Re}\, z)^2 + (\operatorname{Im}\, z)^2이므로, Wj=b^j2W_j = \lvert \hat{b}_j \rvert^2는 코사인 합의 제곱 더하기 사인 합의 제곱입니다. 일반적인 주파수 jj에서 b^j\hat{b}_j의 실수부는 합 kbkcos(2πjk/d)\sum_k b_k \cos(2\pi jk/d), 곧 평균 0 가우스이고 그 분산은

k=0d11dcos2 ⁣(2πjkd)  =  1dd2  =  12\sum_{k=0}^{d-1} \frac{1}{d}\cos^2\!\Big(\frac{2\pi jk}{d}\Big) \;=\; \frac{1}{d}\cdot\frac{d}{2} \;=\; \frac{1}{2}

인데, 그 주파수가 그리는 온전한 주기들에 걸쳐 코사인 제곱의 평균이 절반이기 때문입니다. 허수부는 부호가 뒤집힌 사인 합 kbksin(2πjk/d)-\sum_k b_k \sin(2\pi jk/d)로(평균 0 가우스의 부호를 뒤집어도 분포는 그대로입니다), 같은 분산 12\tfrac{1}{2}의 평균 0 가우스이며, 두 부분은 독립입니다: 공분산이 k1dcos(2πjk/d)sin(2πjk/d)=12dksin(4πjk/d)=0-\sum_k \tfrac{1}{d}\cos(2\pi jk/d)\sin(2\pi jk/d) = -\tfrac{1}{2d}\sum_k \sin(4\pi jk/d) = 0인데, 배각 공식에 의해 각 곱이 온전한 주기에 걸쳐 합이 0이 되는 사인이 되기 때문이고, 결합 가우스(jointly Gaussian) 변수는 무상관이면 독립입니다. 두 분산을 다시 더하면 E[Wj]=12+12=1\mathbb{E}[W_j] = \tfrac{1}{2} + \tfrac{1}{2} = 1로, 방금 계산한 평균과 일치합니다. 그러므로 WjW_j는 분산 12\tfrac{1}{2}의 독립인 평균 0 가우스 두 개의 제곱의 합이고, 그 합은 평균 1의 지수분포를 따릅니다. 이를 보려면 두 가우스 밀도 1πex2\tfrac{1}{\sqrt{\pi}}e^{-x^2}의 곱, 즉 1πe(x2+y2)\tfrac{1}{\pi}e^{-(x^2+y^2)}을 결합 밀도로 쓰고, WjW_j가 문턱값 w0w \ge 0을 넘을 확률(기호 Pr\Pr는 "~일 확률"로 읽습니다)을 x2+y2=r2x^2 + y^2 = r^2이고 넓이 요소가 rdrdθr\,dr\,d\theta인 극좌표에서 계산합니다:

Pr[Wjw]  =  1π02π ⁣ ⁣wer2rdrdθ  =  2wrer2dr  =  [er2]r=w  =  ew,\Pr[W_j \ge w] \;=\; \frac{1}{\pi}\int_0^{2\pi}\!\!\int_{\sqrt{w}}^{\infty} e^{-r^2}\, r\,dr\,d\theta \;=\; 2\int_{\sqrt{w}}^{\infty} r\,e^{-r^2}\,dr \;=\; \Big[-e^{-r^2}\Big]_{r=\sqrt{w}}^{\infty} \;=\; e^{-w},

이는 정확히 평균 1 지수분포의 꼬리입니다. 평균 1 지수분포의 2차 모멘트는 부분적분을 두 번 적용하면 따라 나옵니다:

E[Wj2]  =  0w2ewdw  =  20wewdw  =  21  =  2.\mathbb{E}[W_j^2] \;=\; \int_0^\infty w^2 e^{-w}\,dw \;=\; 2\int_0^\infty w\,e^{-w}\,dw \;=\; 2 \cdot 1 \;=\; 2.

3단계: 파르세발 항등식을 거친 코사인. 파르세발 항등식(Parseval's identity)은 인덱스 영역의 내적을 주파수 영역의 내적으로 바꿔 주는 표준 DFT 정리, xy=1djx^jy^j\mathbf{x} \cdot \mathbf{y} = \tfrac{1}{d}\sum_j \hat{x}_j \overline{\hat{y}_j}입니다. 검증하려면 두 변환에 정의 x^j=kxke2πijk/d\hat{x}_j = \sum_k x_k e^{-2\pi \mathrm{i} jk/d}를 대입합니다: kkk \neq k'인 모든 교차항은 1의 거듭제곱근에 대한 기하급수 합인 인수 je2πij(kk)/d=0\sum_j e^{-2\pi \mathrm{i} j(k-k')/d} = 0을 달고 있고, 살아남는 k=kk = k' 항들은 dkxkykd\sum_k x_k y_k를 기여하며, 이를 1d\tfrac{1}{d}가 상쇄합니다. Uj=a^j2U_j = \lvert \hat{a}_j \rvert^2이라 쓰고 각 큰 합을 그 평균의 dd배로 대체하면(큰 수의 법칙):

cos  =  1djUjWj1djUjWj21djUj    E[U]E[W]E[U]E[W2]E[U]  =  11121  =  12    0.707.\cos \;=\; \frac{\tfrac{1}{d}\sum_j U_j W_j}{\sqrt{\tfrac{1}{d}\sum_j U_j W_j^2} \cdot \sqrt{\tfrac{1}{d}\sum_j U_j}} \;\approx\; \frac{\mathbb{E}[U]\,\mathbb{E}[W]}{\sqrt{\mathbb{E}[U]\,\mathbb{E}[W^2]}\cdot\sqrt{\mathbb{E}[U]}} \;=\; \frac{1 \cdot 1}{\sqrt{1 \cdot 2} \cdot \sqrt{1}} \;=\; \frac{1}{\sqrt{2}} \;\approx\; 0.707.

인볼루션은 a\mathbf{a}를 복원하지 않습니다; a\mathbf{a}와의 기대 코사인이 약 0.7070.707이고 그 점근값 둘레로 1/d1/\sqrt{d} 규모의 요동을 갖는 벡터를 복원합니다 [1]. 실행은 d=1024d = 1024에서 한 번의 결합/해제 왕복으로 정확히 이것을 측정하며, 중요한 대조군으로 같은 복호 벡터를 무관한 제3의 항목 c\mathbf{c}와도 비교합니다:

fidelity of one bind/unbind round trip at d=1024
cos( unbind(bind(a,b), b), a ) = 0.6632 (≈ 1/√2 ≈ 0.71: the involution is an APPROXIMATE inverse)
cos( unbind(bind(a,b), b), c ) = -0.0284 (an unrelated vector: ≈ 0)

측정값 0.6632는 역량 검사가 강제하는 구간 0.5cos0.90.5 \le \cos \le 0.9 안에 들어 있고(vsa.py 267–270행), 무관한 대조군은 0.0284-0.0284로, 0 둘레로 예측된 1/d1/\sqrt{d}의 몇 배 이내에 있습니다. 두 숫자를 나란히 붙들어 두세요. 그 간극이 메커니즘의 전부이기 때문입니다: 옳은 답은 코사인 0.66으로 자신을 알리고, 모든 틀린 답은 0.03으로 속삭입니다. 그만큼의 여유를 가진 신호가 정확해지려면 한 단계가 더 필요합니다. 정리 메모리(cleanup memory)는 복호된 벡터를 어휘의 모든 항목 벡터와 비교하고 순위를 돌려줍니다(vsa.py 110–115행):

def cleanup(x: np.ndarray, vocab: dict[str, np.ndarray]) -> list[tuple[str, float]]:
"""The cleanup memory: compare the noisy vector ``x`` against every item
in the codebook and return ``(name, cosine)`` pairs, best first (name
breaks the — never observed — exact ties, so the order is deterministic)."""
scored = [(name, cosine(x, v)) for name, v in vocab.items()]
return sorted(scored, key=lambda nc: (-nc[1], nc[0]))

정리는 이 아키텍처 안에서 신경적인 것과 기호적인 것이 악수하는 자리입니다: 대수는 연속적이고 잡음 섞인 매질에서 계산하고, 최근접 이웃 탐색이 그 결과를 다시 정확한 기호로 이산화합니다. 회상(recall)이 성공하는 것은 잡음이 0일 때가 아니라(결코 0이 아닙니다) 신호의 코사인이 최고의 사칭자를 이길 때이며, 연산의 어떤 정확성도 아닌 바로 그 부등식이, 이 장 끝의 용량 분석이 예산을 세울 대상입니다.

학술 지식 그래프 위 홀로그램 축약 표현 파이프라인의 3패널 도해. 코드북이라 이름 붙은 왼쪽 패널은 기호마다 하나씩, 무작위 잡음 질감의 작은 가로 띠들이 세로로 늘어선 모습을 보여 주며 옆에는 alice, bob, carol, dave, p1, mit, advises, authored라는 이름이 붙어 있고, 쌍별 코사인이 0에 가까운 1024차원 무작위 항목 벡터라는 주석이 달려 있습니다. 인코딩이라 이름 붙은 가운데 패널은 18개 트리플 중 3개를 보여 주는데, 각 트리플은 두 항목 띠가 동그라미 친 별표 결합으로 낯선 새 띠 하나로 융합되는 모습으로 그려지고, 머리가 관계와 결합된 뒤 문자 로(rho)로 표시된 순열 적용 꼬리 띠와 다시 결합됩니다; 18개의 결합된 띠는 큰 덧셈 기호를 지나 아래로 흘러 18개 트리플 전부를 담은 단일 메모리 벡터 S라 이름 붙은 하나의 넓은 띠가 됩니다. 질의라 이름 붙은 오른쪽 패널은 bob advises에 대한 프로브를 보여 줍니다: 메모리 띠 S를 bob-advises 결합의 인볼루션과 합성곱한 뒤 역순열을 적용해 잡음 섞인 메아리 띠를 만들고; 그 아래 13개 개체 이름에 대한 정리 막대그래프에서 carol이 코사인 0.2055로 가장 높고 dave가 0.1873으로 둘째이며, 둘 다 저장된 답으로서 초록색으로 강조되고, 나머지 11개 막대는 0 근처이며, 프로브 하나가 중첩된 두 답을 동시에 떠올린다는 캡션이 붙어 있습니다. 파이프라인 전체를 그림 하나로: 무작위 항목 벡터(왼쪽)가 트리플별로 결합되어 하나의 1024차원 메모리 S로 중첩되고(가운데); (bob, advises, ?)에 대한 해제-후-정리 프로브 하나가 저장된 두 지도 학생, carol을 0.2055로, dave를 0.1873으로 떠올립니다(오른쪽). 저자가 AI의 도움을 받아 직접 제작한 원본 도해.

교환법칙의 유령, 그리고 그것을 쫓아내는 순열

트리플은 세 부분으로 이루어지므로 자명한 인코딩은 셋 모두를 결합하는 것입니다: 머리 hh, 관계 rr, 꼬리 tt의 트리플에 대해 (ehwr)et(\mathbf{e}_h \circledast \mathbf{w}_r) \circledast \mathbf{e}_t. 이것은 틀렸고, 실행은 고치기 전에 먼저 그 실패를 시연합니다. 원형 합성곱은 교환적이고 결합적이므로 세 인수의 곱은 인수들의 자루입니다: (ealicewadvises)ebob(\mathbf{e}_{\text{alice}} \circledast \mathbf{w}_{\text{advises}}) \circledast \mathbf{e}_{\text{bob}}(ebobwadvises)ealice(\mathbf{e}_{\text{bob}} \circledast \mathbf{w}_{\text{advises}}) \circledast \mathbf{e}_{\text{alice}}같은 벡터입니다. 결합은 사실 수준에서 덧셈의 기호 자루 문제를 풀어 놓고, 슬롯 수준에서 그것을 다시 만들어 냈습니다.

실패를 기계적으로 지켜봅시다. 소박한 메모리는 18개 트리플 전체에 대해 (ehwr)et\sum (\mathbf{e}_h \circledast \mathbf{w}_r) \circledast \mathbf{e}_t를 저장하며, 그 안에 (alice, advises, bob)이 있습니다. bob의 지도 학생을 물으려고 쌍 ebobwadvises\mathbf{e}_{\text{bob}} \circledast \mathbf{w}_{\text{advises}}로 결합을 해제합니다. 인볼루션은 합성곱에 분배되므로(푸리에 영역에서 곱의 켤레는 켤레들의 곱입니다) 프로브는 e~bobw~advises\tilde{\mathbf{e}}_{\text{bob}} \circledast \tilde{\mathbf{w}}_{\text{advises}}이고, 저장된 alice 트리플은 다음 항을 기여합니다:

ealicewadvisesebobe~bobw~advises  =  ealice(wadvisesw~advises)(ebobe~bob)    ealice,\mathbf{e}_{\text{alice}} \circledast \mathbf{w}_{\text{advises}} \circledast \mathbf{e}_{\text{bob}} \circledast \tilde{\mathbf{e}}_{\text{bob}} \circledast \tilde{\mathbf{w}}_{\text{advises}} \;=\; \mathbf{e}_{\text{alice}} \circledast \big(\mathbf{w}_{\text{advises}} \circledast \tilde{\mathbf{w}}_{\text{advises}}\big) \circledast \big(\mathbf{e}_{\text{bob}} \circledast \tilde{\mathbf{e}}_{\text{bob}}\big) \;\approx\; \mathbf{e}_{\text{alice}},

괄호 친 각 인수가 근사적으로 임펄스 δ\boldsymbol{\delta}이기 때문입니다. 프로브는 bob과 advises라는 인수를 어느 자리에 있었든 상쇄해 버리고, bob의 지도교수인 alice가 진짜 지도 학생과 정확히 같은 강도로 메아리쳐 돌아옵니다. 실행은 유령이 두 진짜 답을 제치고 1위를 차지하는 모습을 보여 줍니다:

why the tail slot needs ρ: query (bob, advises, ?) on the NAIVE
memory Σ (e_h ⊛ w_r) ⊛ e_t — ⊛ is commutative, so the stored
triple (alice, advises, bob) echoes back alice at full strength:
rank entity cosine
1 alice 0.2271 * GHOST — bob's advisor, not his advisee
2 dave 0.2083 * stored tail
3 carol 0.1617 * stored tail

표준적인 수리는 슬롯 보호(slot protection)이며 [1], 초차원 컴퓨팅(hyperdimensional computing)이 순열 트릭(permutation trick)으로 대중화한 형태입니다 [3]: 결합하기 전에 꼬리를 좌표의 고정 무작위 순열 ρ\rho에 통과시키는 것, ρ(x)[i]=x[perm[i]]\rho(\mathbf{x})[i] = \mathbf{x}[\text{perm}[i]]입니다(vsa.py 128–133행). 무작위 순열은 그 자체로 합성곱이 아니므로(순환 시프트라면 합성곱이 되며, 그래서 무작위여야 합니다) \circledast와 교환하지 않고, 인코딩 (ehwr)ρ(et)(\mathbf{e}_h \circledast \mathbf{w}_r) \circledast \rho(\mathbf{e}_t)는 방향성을 갖게 됩니다: ρ\rho는 문법의 격조사가 문장의 목적어를 표시하듯 꼬리 슬롯을 표시합니다. 유령 계산을 다시 돌리면 유령은 녹아 없어집니다: alice 트리플은 이제 상쇄 후 ealiceρ(ebob)e~bob\mathbf{e}_{\text{alice}} \circledast \rho(\mathbf{e}_{\text{bob}}) \circledast \tilde{\mathbf{e}}_{\text{bob}}을 기여하는데, ρ(ebob)\rho(\mathbf{e}_{\text{bob}})ebob\mathbf{e}_{\text{bob}}은 통계적으로 무관한 무작위 벡터들이므로 ρ(ebob)e~bob\rho(\mathbf{e}_{\text{bob}}) \circledast \tilde{\mathbf{e}}_{\text{bob}}에는 임펄스 스파이크가 없습니다. 아래의 보호된 실행에서 유령은 코사인 0.22710.2271에서 0.0557-0.0557로 떨어집니다. 꼬리 검색은 해제 후 순열을 되돌립니다: 복원된 꼬리 추정치 t^\hat{\mathbf{t}}(굵은 벡터 전체 위의 모자 기호는 푸리에 계수가 아니라 추정치를 표시합니다)는 t^=ρ1(unbind(S,ehwr))\hat{\mathbf{t}} = \rho^{-1}\big(\operatorname{unbind}(S,\, \mathbf{e}_h \circledast \mathbf{w}_r)\big)입니다.

지식 그래프 전체를 벡터 하나에

이제 모든 것이 갖추어졌습니다. 메모리는 대수 한 줄, kg.py의 18개 트리플(이 권의 모든 모델이 훈련하는 바로 그 13개 개체의 학술 세계, kg.py 41–50행)에 대한 합이며, vsa.py 143–156행에 그대로 구현되어 있습니다:

def encode(triples: list[tuple[str, str, str]], cb: dict[str, np.ndarray],
perm: np.ndarray | None = None) -> np.ndarray:
"""The holographic memory: every triple bound into one vector, all triples
superposed by addition,

S = Σ_{(h,r,t)} (e_h ⊛ w_r) ⊛ ρ(e_t).

With ``perm=None`` the tail is left unprotected (ρ = identity) — the naive,
order-blind encoding kept only to demonstrate its ghost answers."""
S = np.zeros(next(iter(cb.values())).shape[0])
for h, r, t in triples:
tail = cb[t] if perm is None else permute(cb[t], perm)
S += bind(bind(cb[h], cb[r]), tail)
return S

이 한 줄이 주장하는 바를 곱씹어 봅시다. 특정 개인들에 관한 18개의 방향성 있는 사실이 10241024개의 부동소수점 수, 사실당 대략 5757개의 수 안에 살고 있으며, 인덱스도, 테이블도, 훈련 루프도, 그래디언트도 없습니다: 인코딩은 원샷 대수이며, TransE의 천 에폭과는 개념적으로 정반대입니다. 메모리를 질의 가능하게 만드는 것은 분배법칙입니다: 합의 결합을 해제하면 모든 항이 해제되므로, 프로브된 트리플은 신호를 내고 나머지 17개는 혼선 잡음을 냅니다. 질의 파이프라인(vsa.py 159–170행)은 결합 해제, 역순열, 정리입니다. bob의 지도 학생을 물으면, 보호된 메모리에 대한 프로브 하나가 다음을 돌려줍니다:

query (bob, advises, ?) on the protected memory — one probe,
then cleanup over the 13 entity vectors
rank entity cosine
1 carol 0.2055 * stored tail
2 dave 0.1873 * stored tail
3 logic 0.0371
(alice now scores -0.0557 — the ghost is gone;
both stored tails surface from a single unbinding: the
superposition holds carol AND dave at once)

이 표는 네 줄에 담긴 이 장의 논지입니다. 지식 그래프는 bob의 지도에 관한 사실 두 개, (bob, advises, carol)과 (bob, advises, dave)를 저장하고, 둘 다 단 한 번의 결합 해제에서 코사인 0.2055와 0.1873으로 떠오르며, 최고의 오답(주제 logic)은 한참 아래인 0.0371에, 쫓겨난 유령 alice는 0.0557-0.0557에 있습니다. 중첩은 답 하나를 저장하고 나머지를 잃는 타협이 아닙니다; 합은 두 결합 쌍을 진짜로 함께 담고 있고 프로브 하나가 둘 다 읽어 내며, vsa.py 273–274행의 역량 검사가 이 주장을 영구히 못 박아 둡니다. 승리한 코사인이 왜 쌍 왕복의 0.66이 아니라 0.2 근처에 앉아 있는지도 눈여겨보세요; 그 하락에는 두 원인이 있고, 둘 다 이미 유도할 수 있습니다. 첫째, 프로브 ehwr\mathbf{e}_h \circledast \mathbf{w}_r 자체가 두 인수의 결합이므로, 꼬리에 살아남는 푸리에 가중치는 Wj=e^h,j2w^r,j2W_j = \lvert \hat{e}_{h,j} \rvert^2 \lvert \hat{w}_{r,j} \rvert^2, 곧 두 개의 독립인 평균 1 지수분포의 곱입니다; 독립성이 2차 모멘트를 인수분해하여 E[Wj2]=22=4\mathbb{E}[W_j^2] = 2 \cdot 2 = 4가 되고, 충실도 유도의 3단계를 E[W2]=2\sqrt{\mathbb{E}[W^2]} = 2로 다시 돌리면 단일 트리플의 깨끗한 왕복은 쌍의 1/21/\sqrt{2}보다 낮은 1/4=1/21/\sqrt{4} = 1/2이 됩니다(순열 ρ\rho는 여기서 아무것도 바꾸지 않습니다. 두 벡터를 함께 순열해도 노름과 내적이 보존되기 때문입니다). 둘째, 복호 벡터의 노름에는 중첩된 나머지 17개 트리플의 혼선이 추가로 실려 있어 모든 코사인의 분모가 다시 커졌고, 그 깨끗한 0.50.5를 관측된 0.20.2까지 끌어내립니다. 신호가 줄어든 것이 아닙니다; 더 깊어진 결합과 차오른 혼선의 바다가 둘 다 분모에 사는 것입니다. 18개 꼬리 질의 (h,r,?)(h, r, ?) 전부를 저장된 꼬리들에 대고 돌리면 이 작은 그래프는 온전히 되돌아옵니다:

accuracy@1 = 18 / 18 (1.0000)

용량 격자: 이 계산법이 벽을 만나는 곳

1024차원에 트리플 18개는 가벼운 부하입니다. 중심 실험은 스케일링에서 유일하게 중요한 질문을 던집니다: 부하 kk(저장된 트리플 수)가 커지고 차원 dd가 줄어들 때 회상은 어떻게 행동하는가? 이 스윕(vsa.py 175–198행)은 각 칸마다 50개 개체, 5개 관계의 어휘를 새로 만들고, 균등 무작위 트리플 kk개를 차원 dd의 보호된 메모리 하나에 저장한 뒤, 저장된 모든 트리플을 꼬리 질의로 되물어 랭크 1 정확도를 매깁니다; 무작위 트리플은 머리-관계 쌍에서 충돌할 수 있으므로, bob의 두 지도 학생과 똑같이, 상위 1 정리 결과가 그 머리-관계 쌍의 저장된 꼬리 중 어느 것이든 되면 그 질의는 정답으로 칩니다. 커밋된 격자는 다음과 같습니다:

accuracy@1k=5k = 5k=18k = 18k=50k = 50k=200k = 200
d=64d = 640.60000.27780.16000.1250
d=256d = 2561.00000.66670.48000.1850
d=1024d = 10241.00001.00000.92000.6200

두 단조성부터 읽으세요. 역량 검사가 각 단조성의 사례 하나씩을 못 박아 두기 때문입니다: (d=1024,k=18)(d = 1024, k = 18)의 정확도는 (d=64,k=18)(d = 64, k = 18)의 값 아래로도, (d=1024,k=200)(d = 1024, k = 200)의 값 아래로도 떨어져서는 안 됩니다(vsa.py 276–279행). 어느 열이든 아래로 내려가면 정확도는 차원과 함께 올라갑니다: k=18k = 18에서 d=64d = 64의 0.2778, d=256d = 256의 0.6667을 지나 d=1024d = 1024에서 완벽한 1.0000까지. 어느 행이든 따라가면 정확도는 부하와 함께 내려갑니다: d=1024d = 1024에서 k=18k = 18의 1.0000이 k=200k = 200에서 0.6200까지. 두 기울기 모두 하나의 신호 대 잡음 논증에서 따라 나오며, 전부 적어도 될 만큼 짧습니다. 트리플 kk개의 메모리에서 그중 하나를 프로브합니다. 프로브된 트리플은 신호를 기여하는데, 그 벡터가 참 꼬리와 이루는 코사인은 kk와 무관한 고정 상수입니다: 앞 절에서 유도한 약 1/21/2의 단일 트리플 왕복 충실도이며, 프로브 자체가 결합된 인수 둘을 지니므로 쌍 충실도 1/21/\sqrt{2}보다 아래에 있습니다. 나머지 k1k - 1개의 각 트리플은 혼선 벡터를 기여합니다: 프로브와 무관한 항목들의 합성곱, 따라서 어떤 고정된 코드북 항목과의 내적도 평균 0에 표준편차 1/d1/\sqrt{d} 규모인 무작위처럼 보이는 벡터입니다. 독립인 잡음 항들은 분산으로 더해지므로, 어떤 후보 답에든 사영된 전체 혼선의 표준편차는 다음 규모입니다:

σnoise    k1d,\sigma_{\text{noise}} \;\sim\; \sqrt{\frac{k-1}{d}},

그리고 회상을 결정하는 양은 고정된 신호와 그 커져 가는 잡음의 비, 곧 d/k\sqrt{d/k}로 스케일하는 신호 대 잡음비(signal-to-noise ratio)입니다. 저장된 모든 쌍은 다른 모든 회상에 1/d1/\sqrt{d} 규모의 잡음을 보태며, 그 잡음의 값을 치르는 자원이 차원입니다. 랭크 1 회상은 나아가 50개 항목 어휘 전체에 걸친 잡음의 최댓값을 이겨야 하며, 그래서 정확도는 절벽에서 떨어지듯이 아니라 매끄럽게 침식됩니다: 트리플 하나가 더해질 때마다 잡음 분포가 위로 조금씩 밀려 올라가고, 질의 하나의 여유가 또 잠깁니다. 이 스케일링은 격자의 대각선까지 예측합니다: ddkk를 둘 다 4배로 늘리면 비가 대략 보존되어야 하고, 실제로 두 칸이 같은 영역에 놓입니다. d=256,k=50d = 256, k = 50의 0.4800 대 d=1024,k=200d = 1024, k = 200의 0.6200입니다; 둘째 숫자가 다소 높게 나오는 것은 k=200k = 200을 50개 개체 어휘에 채우면 많은 머리-관계 쌍이 저장된 꼬리를 여러 개 갖게 되고, 저장된 꼬리라면 무엇이든 인정하는 채점이 그 지점에서 더 관대하기 때문입니다. HRR의 원래 용량 분석은 이 논증을 정확하게 만들어, 목표 오류 확률로 kk개 쌍을 저장하는 데 필요한 차원을 유도합니다; 스케일링 법칙은 동일합니다 [1].

우아한 성능 저하: 특장점이자 대가

격자가 담고 있지 않은 것을 보세요: 붕괴가 없습니다. d=64d = 64에서 트리플 k=200k = 200개를 64차원 벡터에 욱여넣어도 메모리는 여전히 모든 질의에 답합니다; 다만 87.5퍼센트의 확률로 틀릴 뿐입니다. 어떤 예외도 던져지지 않았고, 어떤 거부도 없었으며, "메모리 가득 참"이라는 보고도 없었습니다. 이것이 우아한 성능 저하(graceful degradation)이고, 초차원 컴퓨팅 문헌이 이를 기리는 것은 옳습니다 [3]. 2권의 완결(completion) 엔진과 대조해 보세요: 모순으로 과부하를 걸면 그것은 ⊥(모순 기호, 2권의 만족 불가능한 바닥 개념)를 유도하고, 잘못된 개념의 이름을 대고, 책임 있는 공리들을 보여 줍니다. 기호 시스템은 부서지기 쉽고 그 사실에 대해 시끄럽습니다; 홀로그램 메모리는 강건하고 조용합니다. SS의 좌표 절반을 손상시키면 회상은 비례해서 저하됩니다; 기호 테이블의 항목 절반을 손상시키면 시스템은 멈춥니다. 하드웨어에게, 잡음 섞인 센서에게, 생물학에게 바람직한 쪽은 VSA의 행동입니다.

대가는 그 조용함입니다. 0.2055라는 정리 코사인에는 아무런 증명서도 딸려 오지 않습니다: d=64d = 64에서 메모리가 틀린 꼬리를 돌려줄 때, 그 오답은 모든 정답과 동일한 인터페이스, 곧 이름 하나와 점수 하나로 도착합니다. ⊥도 없고, 증명 흔적도 없고, 벡터에게 왜냐고 물을 방법도 없습니다. 실패는 집계 수준에서는 통계적으로 예측 가능하지만(격자가 바로 그것입니다) 개별적으로는 보이지 않습니다. 감사 가능성을 내주고 강건함을 사는 이 거래는 이 권의 모든 신경 표현에 되풀이되는 흥정이며, 여기서 가장 순수한 형태로 분리되어 있고, 이 권을 닫는 평결 장이 저울에 올릴 공소장입니다.

한 표에 담은 가족

원형 합성곱은 요구 조건 목록을 충족하는 여러 결합 연산자 중 하나의 선택이며, 그 결과로 생기는 가족이 바로 벡터 기호 구조라는 우산 용어가 가리키는 것입니다 [4]. 구성원들은 벡터 공간과 결합 대수는 다르지만 아키텍처는 공유합니다: 무작위 항목 벡터, 비유사성을 보존하는 결합, 덧셈에 의한 중첩, 근사적 결합 해제, 그리고 정리입니다.

아키텍처벡터 공간결합결합 해제트레이드오프
텐서곱 [2]Rd×d\mathbb{R}^{d \times d}, 수준마다 커짐외적 ab\mathbf{a}\mathbf{b}^{\top}내적 축약, 정확함정확한 복원, 그러나 중첩 수준마다 차원이 제곱됨
HRR [1]Rd\mathbb{R}^{d}, N(0,1/d)\mathcal{N}(0, 1/d) 항목원형 합성곱 \circledast, FFT로 O(dlogd)O(d \log d)인볼루션 a~\tilde{\mathbf{a}}, 근사적(cos1/2\cos \approx 1/\sqrt{2})어떤 깊이에서도 고정 차원, 내재적 잡음과 정리로 값을 치름
이진 스패터 코드 [3]밀집 이진 {0,1}d\lbrace 0,1 \rbrace^{d}좌표별 XOR다시 XOR, 자기 역원이고 정확함결합이 정확하고 저렴함; 잡음이 사는 곳은 중첩(다수결 투표)
MAP(Multiply-Add-Permute) [4]쌍극 {1,+1}d\lbrace -1,+1 \rbrace^{d}좌표별 곱셈다시 곱셈, 자기 역원엄밀히 쌍극인 벡터에서 결합은 변장한 XOR(비유사성 보존은 동등); 매력은 자명한 하드웨어 대응이고, 정확성은 벡터가 쌍극에 머무는 동안만 유지됨: 일단 중첩되어 정수 값이 된 벡터는 근사적으로만 해제됨

텐서곱 행은 기원 설화입니다: 외적으로서의 결합이 변수 결합에 대한 연결주의의 답으로 가장 먼저 나왔고 [2], 이후의 모든 행은 정보 손실을 어디에 지출할지 골라 그것을 고정 차원으로 되돌린 손실 압축입니다. 여기서 예고 한 문장을 빚지고 있습니다: 차원보다 훨씬 많은 특징이 하나의 활성 벡터 안에서 거의 직교하는 방향들로 공존하는 이 동일한 중첩 계산법이, 지금은 훈련된 트랜스포머가 내부 특징을 어떻게 눌러 담는지에 대한 작업 가설이며, 그래서 이 장의 잡음 산술은 앞 장의 모델들을 해석하는 데 뜻밖에도 유관해집니다 [5].

아직 풀리지 않은 부분

정리 메모리는 자기가 받은 기호만 돌려줄 수 있습니다. 이 장의 모든 복원은 저장된 항목들의 고정 코드북에 대한 최근접 이웃 탐색으로 끝났고, 코드북의 벡터들은 무작위입니다: wadvises\mathbf{w}_{\text{advises}}는 가상의 wsupervises\mathbf{w}_{\text{supervises}}ep2\mathbf{e}_{\text{p2}} 사이에서 어느 쪽과도 더 가깝지 않은데, 모든 것의 거의 직교성이 바로 그 설계이기 때문입니다. 그 설계가 회상을 깨끗하게 만드는 것이고, 동시에 벽이기도 합니다. 1권과 2권이 실천한 추론은 그저 비슷하기만 한 기호들을 맞추어 볼 일이 끊이지 않습니다: 지도(advising)에 관한 규칙이 감독(supervising)에 관한 사실에 대해서도 아마 발화해야 한다는 것을 알아차리는 일 말입니다. wsupervises\mathbf{w}_{\text{supervises}}로 질의된 VSA 메모리는 설계된 그대로 잡음을 돌려줍니다. 성분을 공유하는 항목 벡터를 구성하여 유사성을 주입할 수는 있고 HRR도 이를 예견했지만, 그러면 모든 곳에서 독립성을 가정했던 잡음 분석을 공유된 각 성분이 체계적 혼선으로 작용하는 상황에서 다시 해야 하며, 중첩된 메모리가 용량 논증이 무너지기 전까지 얼마나 많은 구조화된 유사성을 실어 나를 수 있는지에 대한 일반적 설명은 아무도 갖고 있지 않습니다. 정확한 구조의 이 대수를 지키면서 기호들 사이의 등급 있는 유사성을 받아들이는 법, 그리고 그런 등급 있는 일치를 의미 있는 합성 점수와 함께 다단계 규칙 사슬로 엮는 법은 진정으로 열린 문제이며, 그것이 정확히 다음 장으로 통하는 문입니다.

왜 중요한가

뉴로심볼릭 프로그램에게 이 장은 이빨 달린 존재 증명입니다. 벡터는 연상은 인코딩할 수 있어도 구조는 인코딩할 수 없다는, "누가 누구에게 무엇을 했는가"에는 본질적으로 포인터와 기호 테이블이 필요하다는 회의적 입장이 되풀이하여 제기됩니다. 커밋된 실행은 그 주장의 강한 형태를 1024개의 부동소수점 수로 반박합니다: 방향성 있는 지식 그래프 전체를 순수 대수로 벡터 하나에 저장하고, 순수 대수로 질의하여 18개 중 18개를 맞히며, 실패 양상은 숨기지 않고 격자 위에서 측정했습니다. 마찬가지로 중요한 것은 어떻게인가입니다: 훈련이 전혀 없습니다. 결합, 중첩, 결합 해제는 고정된 미분 가능 연산들이고(FFT는 선형이고 점별 곱은 쌍선형이므로, 결합·중첩·해제는 매끄러운 사상으로 합성됩니다), 미분 불가능한 유일한 단계는 정리의 코드북에 대한 argmax(argmax는 가장 큰 것을 고르는 연산으로, 코사인이 가장 높은 기호를 돌려줍니다)인데, 그래디언트가 흘러야 할 때는 이를 코드북 코사인들에 대한 소프트맥스로 대체합니다. 이는 어휘에 대한 어텐션, 곧 앞 장의 연산자가 새 역할로 돌아온 것입니다. 그 치환과 함께 파이프라인 전체가 학습된 모델 안에 들어앉아 그래디언트를 통과시킬 수 있으며, 이것이 정확히 4권의 미분 가능 논리 시스템에서 구조화된 벡터 연산이 맡는 역할입니다. 그리고 중첩에 담긴 구조는 실재하지만 예산이 정해져 있으며 차원이 통화이고 혼선이 세금이라는 용량 격자의 교훈은, 트랜스포머를 포함해 활성 공간에서 관계를 표현한다고 주장하는 모든 아키텍처에 들고 갈 정량적 틀입니다. 어텐션은 미분 가능한 어디를 볼 것인가를 주었고, 이 장은 미분 가능한 무엇이 무엇과 짝인가를 주었으며, 남은 간극인 미분 가능한 누구와 일치하는가는 한 장 앞에 있습니다.

핵심 용어

  • 벡터 기호 구조(Vector Symbolic Architecture, VSA): 고차원 무작위 벡터 위의 표현 체계 가족으로, 결합 연산, 덧셈 중첩, 근사적 결합 해제, 정리 메모리로 이루어집니다.
  • 결합(\circledast, binding): 두 벡터를 같은 차원의, 두 입력 모두와 거의 직교하는 벡터 하나로 합성하는 연산; HRR에서는 원형 합성곱 (ab)[i]=ka[k]b[(ik)modd](\mathbf{a} \circledast \mathbf{b})[i] = \sum_k a[k]\,b[(i-k) \bmod d]입니다.
  • 중첩(superposition): 저장 수단으로서의 단순한 벡터 덧셈; 여러 결합 쌍이 하나의 벡터에 공존하며, 각각 복원 가능하고, 각각 다른 쌍들에 혼선 잡음을 보탭니다.
  • 홀로그램 축약 표현(Holographic Reduced Representation, HRR): 원형 합성곱을 쓰는 실수 값 VSA로, 합성곱 정리를 통해 FFT로 O(dlogd)O(d \log d)에 계산됩니다.
  • 인볼루션(a~\tilde{\mathbf{a}}, involution): 모듈로 dd 인덱스 반전, HRR의 안정적 근사 역원; aa~\mathbf{a} \circledast \tilde{\mathbf{a}}는 임펄스 δ\boldsymbol{\delta}a2\lVert \mathbf{a} \rVert^2로 스케일한 것에 근사하며, 왕복 충실도는 1/21/\sqrt{2} 근처입니다.
  • 정리 메모리(cleanup memory): 잡음 섞인 복호 벡터를 정확한 저장 기호로 되돌리는, 항목 코드북에 대한 (코사인 기준) 최근접 이웃 탐색입니다.
  • 슬롯 보호(slot protection, 순열 트릭): 결합 전에 한쪽 인수에 적용하는 고정 무작위 좌표 순열 ρ\rho; ρ\rho는 합성곱과 교환하지 않으므로 인코딩을 방향성 있게 만들고 교환법칙의 유령을 없앱니다.
  • 용량 / 우아한 성능 저하(capacity / graceful degradation): dd차원에 kk개 항목을 저장할 때 d/k\sqrt{d/k}로 스케일하는 신호 대 잡음비가 지배하는 회상 정확도; 과부하는 시끄럽게 실패하는 대신 매끄럽고 조용하게 정확도를 떨어뜨립니다.

이 장이 이끄는 곳

벡터 기호 구조는 구성상 저장된 기호는 정확하게 맞추고, 그 밖의 모든 것은 전혀 맞추지 못합니다; 코드북은 동일성은 알아도 친족 관계는 모릅니다. 다음 장 소프트 단일화는 이 대수가 내디딜 수 없는 걸음을 내디딥니다: 기호적 증명의 심장에 있는 동등성 검사를 임베딩 벡터 위의 커널로 대체하여 advises와 그 유사어가 0이 아니라 0.98로 일치할 수 있게 하고, 그런 등급 있는 일치를 증명 사슬 전체에 꿰어 유도(derivation)를 가장 약한 고리로 채점합니다. 이 장이 기호를 정확하게 유지한 채 구조를 벡터화했다면, 다음 장은 기호 자체를 협상 가능하게 만들고, 새로운 의무를 물려받습니다: 어느 정도의 일치가 충분히 좋은 일치인지 결정하는 일입니다.


동반 코드: examples/neural/vsa.py는 파이프라인 전체를 구현합니다. 두 가지 결합 구현과 그 일치 검증, 충실도 왕복, 소박한 인코딩의 유령, 모든 꼬리 질의를 갖춘 보호된 18-트리플 메모리, 그리고 용량 스윕이, examples/neural/kg.py에 고정된 지식 그래프 위에서 시드가 지정된 생성기 하나로 구동됩니다. examples/neural/에서 python3 vsa.py를 실행하면 이 장의 모든 숫자를 재현할 수 있습니다; 수용 하니스 examples/neural/validate.py는 그 역량 단언들(1e-10 FFT 게이트, 충실도 구간, 무관 벡터 직교성 검사, 소박한 유령의 출현, carol과 dave의 상위 2, 18개 중 16개 하한, 그리고 두 용량 단조성 표본 검사)을 이 권의 평결의 일부로 다시 실행합니다.