메뉴
HN
Hacker News 26일 전

다항식 오토인코더, 트랜스포머 임베딩 압축에서 PCA 능가

IMP
7/10
핵심 요약

트랜스포머 임베딩 압축 시 기존 선형 방식인 PCA가 놓치는 비선형적 특성을 포착하기 위해 '다항식 오토인코더(Poly-AE)' 기법이 제안되었습니다. 복잡한 학습 과정 없이 단순한 수학적 계산만으로 적용할 수 있으며, 동일한 메모리 예산에서 기존 PCA 대비 검색 품질(NDCG)을 최대 4.4%p 가까이 끌어올렸습니다. 이는 정보 검색 시스템에서 저장 공간을 획기적으로 줄이면서도 성능 저하를 최소화할 수 있는 매우 실용적인 접근법입니다.

번역된 본문

02 블로그

(양자화를 제외하고) 임베딩을 압축하는 가장 직접적인 방법은 코퍼스(말뭉치)에 PCA(주성분 분석)를 적용하고 상위 d개의 고유 벡터를 유지하는 것입니다. 이 방법은 잘 작동하지만 PCA는 선형 투영(Linear projection)이며, 구(Sphere) 위의 신경망 임베딩은 구조적으로 비선형적입니다. 즉, 트랜스포머에서 잘 알려진 '원뿔 효과(Cone effect)'가 나타납니다. 분산의 일부는 선형 디코더가 도달할 수 없는 비선형 꼬리 영역에 존재합니다. 이 글은 이러한 비선형 꼬리의 일부를 포착하기 위해 PCA 위에 폐쇄형(Closed-form) 2차 디코더를 추가하는 방법에 대한 것입니다. 인코더는 일반적인 PCA로 유지됩니다. 디코더는 2차 다항식 리프트(Polynomial lift)와 릿지 최소제곱법(Ridge OLS, L2 정규화가 적용된 일반 선형 회귀)을 사용하며, 이 역시 폐쇄형으로 계산됩니다. SGD, 에포크, 하이퍼파라미터 탐색이 전혀 필요 없습니다. 코퍼스 통계량에 대해 단 한 번의 np.linalg.solve 연산만 수행하면 됩니다.

이 구조 자체는 제가 새로 만든 것이 아닙니다. "PCA 인코더 + 2차 디코더 + 최소제곱법 적합"은 동적 시스템(Dynamical-systems) 문헌에서 '2차 다양체(Quadratic manifold)'라는 이름으로 등장합니다 (Jain 2017, Geelen-Willcox 2022/2023, Schwerdtner-Peherstorfer 2024+ 등 참고, §9에서 자세히 다룹니다). 저는 실험을 진행하고 이 구조가 새롭다고 생각한 후에야 우연히 이 논문들을 발견했습니다. 다항식 리프트 기법은 현대 머신러닝 대화에서 자주 언급되지 않으며, 이 글은 인접 학문의 유용한 기법이 검색(Retrieval) 분야로 어떻게 넘어왔는지에 대한 기록입니다.

구체적인 결과는 다음과 같습니다. BEIR/FiQA 데이터셋, mxbai-embed-large-v1 (1024차원) 모델 기준으로 벡터당 512바이트의 예산을 사용했습니다. 평가 지표는 NDCG@10 (상위 10개의 정규화 할인 누적 이득, 표준 검색 순위 품질 측정값, [0, 1] 범위, 높을수록 좋음)입니다:

  • raw 1024d (4096 bytes): NDCG@10 0.4525 (—)
  • PCA top-256: 0.4168 (-3.58 p.p.)
  • poly-AE 256d: 0.4441 (-0.85 p.p.)
  • matryoshka top-256: 0.4039 (-4.86 p.p.)

PCA만으로도 벡터당 메모리를 4배 압축하면서 NDCG를 -3.58 p.p. 수준으로 유지합니다. 그러나 PCA 위에 2차 디코더를 얹은 방식(poly-AE)은 여기에 추가로 +2.73 p.p.를 끌어올립니다. 동일한 바이트 예산으로 원본(Raw)과의 성능 격차를 거의 전부 메꾼 셈입니다. 또 다른 친숙한 베이스라인인 Matryoshka는 표에 포함시켰으나 (여기서는 PCA보다 성능 하락이 더 큽니다. 이는 알려진 부가적인 관찰 사항일 뿐, 이 글의 핵심 주장은 아닙니다. §3 각주 및 §4 참고). 이 측정은 4개 모델(nomic-v1.5, mxbai-large, bge-base, e5-base)에서 진행되었습니다. PCA 대비 Poly-AE의 성능 향상은 d=128일 때 +1 ~ +4.4 p.p., d=256일 때 +0.03 ~ +2.7 p.p.입니다. 전체 표는 §3에 있습니다.

전체 구현은 약 150줄의 NumPy 코드와 MIT 라이선스를 사용하며, 저장소(github.com/IvanPleshkov/poly-autoencoder)에서 확인할 수 있습니다. 동일한 저장소의 beir_eval.py가 BEIR 평가 스크립트입니다. M 시리즈 맥북에서 3040분이면 재현 가능합니다 (코퍼스 인코딩에 1015분, d=256일 때 Ridge 계산에 약 15분 소요).

목차: §1 비교 대상 §2 실험 설정 §3 핵심 결과표 §4 2차 디코더가 도움이 되는 이유 §5 선형 투영이 실패하는 이유 §6 방법론 §7 소규모 코퍼스에 대한 주의사항 §8 잔차 압축 §9 방법론의 출처 §10 한계점 §11 다음으로 시도해 볼 것

  1. 비교 대상 모든 측정은 다음 4가지 라인을 통해 비교됩니다:
  • raw: 압축이 없는 전체 임베딩입니다. 품질의 상한선이며 벡터당 가장 많은 바이트를 차지합니다. 공정한 상한선을 제공하는 '비싼' 베이스라인입니다.
  • matryoshka: embedding[:d]에 L2 정규화를 적용한 형태입니다. Matryoshka 손실 함수로 학습된 모델(우리 테스트 샘플에서는 nomic, mxbai)에서 이는 유효한 Matryoshka 벡터입니다. 반면 Matryoshka 학습이 되지 않은 모델(bge-base, e5-base)에서는 MRL이 아닌 모델을 단순히 자르면 어떻게 되는지를 보여줍니다. 이는 bge 패밀리, e5 및 사용자 지정 미세 조정(Feature-tuned) 임베딩 사용자들이 실제로 겪는 시나리오입니다.
  • PCA: 코퍼스 공분산(Covariance)의 상위 d개 고유 벡터입니다. 벡터는 d차원의 PCA 좌표 공간에 존재하게 됩니다.
  • poly-AE: 우리가 제안하는 방법입니다. PCA를 사용하여 p ∈ ℝ^d로 인코딩한 다음, 2차 다항식을 사용하여 전체 D차원의 V̂로 디코딩하며, 이 V̂를 기반으로 검색을 수행합니다. 고정된 d 값에 대해 위 4가지 방법은 모두 벡터당 2d바이트(fp16 좌표 기준)의 메모리를 저장합니다.
  1. 실험 설정 BEIR은 표준 검색 데이터셋 모음(SciFact, FiQA, NFCorpus, TREC-COVID 등)입니다. 평가 지표는 NDCG@10입니다. 코퍼스와 쿼리(검색어)는 선택한 모델로 인코딩되고, 코사인 유사도를 통해 상위 10개가 검색된 뒤 레이블이 지정된 qrels(관련성 평가 데이터)와 비교하여 NDCG를 계산합니다. PCA와 poly-AE는 전랜덕티브(Transductive) 방식으로 학습됩니다. 즉, 통계량이 코퍼스 자체에서 계산됩니다.
원문 보기
원문 보기 (영어)
02 Blog The most direct way to compress an embedding (other than quantization) is to fit PCA on the corpus and keep the top-d eigenvectors. It works, but PCA is a linear projection, and neural-network embeddings on the sphere are structurally nonlinear — the well-known cone effect in transformers. Some of the variance lives in a nonlinear tail that a linear decoder can’t reach. This post is about a closed-form way to add a quadratic decoder on top of PCA, to capture part of that nonlinear tail. The encoder stays as plain PCA. The decoder is a degree-2 polynomial lift plus Ridge OLS (ordinary linear regression with L2 regularization), also closed-form. No SGD, no epochs, no hyperparameter search. One np.linalg.solve over corpus statistics. The construction itself isn’t mine. “PCA encoder + quadratic decoder + least-squares fit” appears in the dynamical-systems literature under the name quadratic manifold (see Jain 2017 , Geelen-Willcox 2022 / 2023 , Schwerdtner-Peherstorfer 2024+ — more in §9 ). I only stumbled onto these papers after running my experiments and believing the construction was new. The polynomial lift doesn’t come up much in modern ML conversations, and this post is a note about a useful trick from an adjacent discipline carrying over to retrieval. The concrete result. BEIR/FiQA, mxbai-embed-large-v1 (1024d), per-vector budget 512 bytes. Metric is NDCG@10 (Normalized Discounted Cumulative Gain over the top-10, the standard retrieval ranking-quality measure; range [0, 1], higher is better): method NDCG@10 Δ vs raw 1024d raw 1024d (4096 bytes) 0.4525 — PCA top-256 0.4168 -3.58 p.p. poly-AE 256d 0.4441 -0.85 p.p. matryoshka top-256 0.4039 -4.86 p.p. PCA already gives 4× per-vector memory compression at -3.58 p.p. NDCG. A quadratic decoder on top of PCA pulls another +2.73 p.p. — closing almost the entire gap to raw, at the same byte budget. Matryoshka is in the table as another familiar baseline (here it drops more than PCA — a known side observation, not the central claim of this post; see §3 footnote and §4 ). Measured on four models (nomic-v1.5, mxbai-large, bge-base, e5-base). Poly-AE over PCA: +1 to +4.4 p.p. at d=128, +0.03 to +2.7 p.p. at d=256. Full table in §3 . Full implementation — ~150 lines of numpy, MIT, repository github.com/IvanPleshkov/poly-autoencoder . The BEIR eval script is beir_eval.py in the same repo. Reproduces in 30–40 minutes on an M-series MacBook (10–15 min to encode the corpus, ~15 min for the Ridge solve at d=256). Contents: §1 what we’re comparing §2 setup §3 headline table §4 where the quadratic decoder helps §5 why linear projection loses §6 method §7 small-corpus caveat §8 residual compression §9 where the method came from §10 limits §11 what to try next 1. What we’re comparing Four lines in every measurement: raw — the full embedding, no compression. Quality ceiling and the most bytes per vector. The “expensive” baseline that gives a fair ceiling. matryoshka — embedding[:d] plus L2 normalization. On models trained with a matryoshka loss (nomic, mxbai in our sample), this is a valid matryoshka vector. On models without matryoshka training (bge-base, e5-base) it’s a test of what happens when you naively slice a non-MRL model — the scenario users of the bge family, e5, and custom-fine-tuned embeddings actually fall into. PCA — top-d eigenvectors of the corpus covariance. Vectors live in d-dimensional PCA coordinates. poly-AE — our method. Encode with PCA into p ∈ ℝ^d , decode with a quadratic polynomial back to a full D-dimensional V̂ , retrieve on V̂ . At a fixed d , all four methods store 2d bytes per vector (fp16 coordinates). 2. Experimental setup BEIR is the standard set of retrieval datasets (SciFact, FiQA, NFCorpus, TREC-COVID, etc.). The metric is NDCG@10. Corpus and queries are encoded with the chosen model, top-10 are retrieved by cosine similarity, and NDCG is computed against the labeled qrels. PCA and poly-AE are fit transductively : statistics are computed on the corpus we want to compress. Queries never participate in the fit — they hit a fixed encoder/decoder at inference time. This matches a production deployment: an index operator computes PCA + Ridge once on their data and then serves queries. For the main runs we use FiQA — 57K documents, 648 queries, 1706 qrels. 3. The headline table — four models NDCG@10 on FiQA at budgets of 256 fp16 (512 bytes/vector) and 128 fp16 (256 bytes): Model D d raw matryoshka† PCA poly-AE poly over matryoshka poly vs raw nomic-embed-text-v1.5 768 128 0.3746 0.3190 0.3273 0.3380 +1.90 p.p. -3.65 p.p. nomic-embed-text-v1.5 768 256 0.3746 0.3508 0.3670 0.3673 +1.65 p.p. -0.73 p.p. mxbai-embed-large-v1 1024 128 0.4525 0.3503 0.3689 0.4129 +6.26 p.p. -3.97 p.p. mxbai-embed-large-v1 1024 256 0.4525 0.4039 0.4168 0.4441 +4.02 p.p. -0.85 p.p. bge-base-en-v1.5* 768 128 0.4062 0.2914 0.3266 0.3654 +7.40 p.p. -4.09 p.p. bge-base-en-v1.5* 768 256 0.4062 0.3574 0.3688 0.3958 +3.84 p.p. -1.05 p.p. e5-base-v2* 768 128 0.3987 0.2498 0.3065 0.3317 +8.18 p.p. -6.70 p.p. e5-base-v2* 768 256 0.3987 0.3333 0.3618 0.3852 +5.19 p.p. -1.35 p.p. † The “matryoshka” column is embedding[:d] plus L2 normalization. On nomic and mxbai it’s a valid matryoshka vector. On models marked with * (bge-base, e5-base) the model wasn’t trained for matryoshka, and the slice here is a test of what happens when you naively slice a non-MRL model. This is the scenario that users of the bge family, e5, and custom-fine-tuned embeddings actually fall into — and we measure it here honestly. What this shows: Poly-AE is consistently ahead of PCA across all four models. Lift: +1 to +4.4 p.p. NDCG at d=128, +0.03 to +2.7 p.p. at d=256. Where the quadratic decoder actually helps and at what d — discussed in §4 . At d=256 , poly-AE loses 0.7–1.4 p.p. NDCG vs raw 768/1024 on all four models. 4× per-vector memory compression for less than 1.5 p.p. lost — the main number of the post. On non-matryoshka-trained models, the matryoshka column drops more than PCA — up to -15 p.p. NDCG at d=128. This is a side observation: the post compares PCA and poly-AE, not PCA vs matryoshka. If the matryoshka numbers in the table look surprising, there’s a short pointer in §4 . 4. Where the quadratic decoder actually helps PCA is the linear baseline. The quadratic decoder adds the nonlinear piece that the linear one can’t reach (mechanics in §5 ). How much does that actually help on retrieval, and at what d ? Poly-AE lift over PCA, by model and d : Model poly over PCA, d=128 poly over PCA, d=256 nomic-v1.5 +1.07 p.p. +0.03 p.p. mxbai-large +4.40 p.p. +2.73 p.p. bge-base +3.88 p.p. +2.70 p.p. e5-base-v2 +2.52 p.p. +2.34 p.p. The picture: At d=128 (8× compression) poly is consistently 1–4 p.p. ahead of PCA. This is the regime where the linear decoder starts dropping noticeable variance into the nonlinear tail, and the quadratic correction pulls it back. Sweet spot for the method. At d=256 (4× compression) the gap is uneven. On mxbai/bge/e5 — a stable +2.3–2.7 p.p. On nomic — close to zero (+0.03). Likely reason: nomic was carefully trained with multi-slice contrastive loss, its latent is more isotropic, and at d=256 the linear projection already takes most of what’s there. On non-MRL models the nonlinear tail is bigger → poly helps more. More anisotropy → bigger lift. The stronger the cone effect, the more variance lives in the nonlinear tail that PCA can’t reach but poly can. That’s the geometry §5 unpacks. Side: where matryoshka sits in the table In §3 you can see that on non-MRL models (bge, e5) the matryoshka column drops more than PCA — i.e. on a random non-MRL-trained model, naive slicing works worse than a corpus-side linear projection. This is a known result; the “MRL vs PCA on retrieval” question has been discussed independently of this post — see Matryoshka-Adaptor 2024 , SMEC “Rethinking MRL” 2025 , CoRECT 2025 , and the YouTube video literally titled «Is PCA enough?» . This post compares PCA and poly-AE; matryoshka is in