02 Блог
Полиномиальный автоэнкодер
Самый прямой способ сжать эмбеддинг (если не считать квантизации) — посчитать PCA на корпусе и оставить top-d собственных направлений. Это работает, но PCA — линейная проекция, а эмбеддинги нейросеток на сфере структурно нелинейны (известный cone effect у трансформеров). Часть дисперсии остаётся в нелинейном хвосте, который линейный декодер принципиально не достаёт.
Этот пост — про closed-form способ добавить квадратичный декодер
поверх PCA, чтобы захватить часть этого нелинейного хвоста. Encoder
остаётся обычным PCA. Decoder — полиномиальный лифт степени 2 плюс
Ridge OLS (обычная линейная регрессия с L2-регуляризацией), тоже
closed-form. Никакого SGD, никаких эпох, никакого
подбора гиперпараметров. Один np.linalg.solve поверх корпусной
статистики.
Сама конструкция — не моя. «PCA-encoder + квадратичный decoder + least-squares fit» встречается в литературе по динамическим системам под названием quadratic manifold (см. Jain 2017, Geelen-Willcox 2022/2023, Schwerdtner-Peherstorfer 2024+ — подробнее в §9). Сам я наткнулся на эти работы уже после того, как прогнал эксперименты и думал, что конструкция новая. В современном ML полиномиальный лифт встречается нечасто, и этот пост — заметка о том, что в соседней дисциплине есть полезный приём, который переносится на retrieval.
Конкретный результат. BEIR/FiQA, mxbai-embed-large-v1 (1024d),
бюджет 512 байт на вектор. Метрика — NDCG@10 (Normalized
Discounted Cumulative Gain в топ-10, стандартная retrieval-метрика
для качества ранжирования; диапазон [0, 1], чем выше тем лучше):
| метод | NDCG@10 | Δ vs raw 1024d |
|---|---|---|
| raw 1024d (4096 байт) | 0.4533 | — |
| PCA top-256 | 0.4175 | -3.58 п.п. |
| PCA-whitened top-256 | 0.3892 | -6.41 п.п. |
| PCA-half-whitened top-256 | 0.4311 | -2.22 п.п. |
| poly-AE 256d | 0.4445 | -0.88 п.п. |
| matryoshka top-256 | 0.4040 | -4.93 п.п. |
PCA даёт 4× сжатие per-vector памяти при -3.58 п.п. NDCG. Квадратичный декодер поверх PCA вытаскивает +1.34 п.п. над best-PCA-baseline (half-whitened, +2.70 п.п. над vanilla PCA) — почти полностью закрывает gap до raw, при том же байт-бюджете. Matryoshka в таблице как ещё один знакомый baseline (здесь проседает сильнее PCA — это известный побочный результат, не главный тезис поста; см. сноску в §3 и §4).
Замерено на четырёх моделях (nomic-v1.5, mxbai-large, bge-base, e5-base). Прирост poly-AE над PCA-half-whitened — от −0.59 до +2.10 п.п. при d=128 и от +0.57 до +2.65 п.п. при d=256. Полная таблица в §3.
Полная реализация — ~150 строк numpy, MIT, репозиторий
github.com/IvanPleshkov/poly-autoencoder.
Eval-скрипт против BEIR — beir_eval.py в том же репо. Воспроизводится
за 30–40 минут на M-серии MacBook (энкодинг корпуса 10–15 мин, Ridge
solve при d=256 — около 15 мин).
Содержание:
- §1 что сравниваем
- §2 постановка
- §3 главная таблица
- §4 где квадратичный декодер помогает
- §5 почему линейная проекция теряет
- §6 метод
- §7 маленький корпус
- §8 сжатие residual
- §9 откуда метод
- §10 пределы
- §11 что дальше
1. Что с чем сравниваем
Четыре линии в каждом замере:
- raw — полный эмбеддинг, без сжатия. Потолок качества и максимум байт на вектор. Это «дорогой» вариант, который даёт честный ceiling.
- matryoshka —
embedding[:d]плюс L2-нормировка. На моделях с matryoshka-обучением (nomic, mxbai в нашей выборке) — валидный matryoshka-вектор. На моделях без matryoshka-обучения (bge-base, e5-base) это тест на то, что произойдёт при наивной нарезке не-MRL-модели — сценарий, в который реально попадают пользователи bge / e5 / custom-fine-tuned эмбеддингов. - PCA — top-d собственных направлений ковариации, посчитанной по
корпусу. Векторы хранятся в d-мерной PCA-системе координат. В
таблицах сравниваем три варианта взвешивания осей: vanilla
(cosine на сырых PCA-координатах), whitened (на
p / sqrt(eigvals)— каждая ось весит одинаково), half-whitened (наp / eigvals^(1/4)— компромисс между двумя крайностями). - poly-AE — наш метод. Кодируем PCA-проекцией в
p ∈ ℝ^d, декодируем квадратичным полиномом обратно в полный D-мерныйV̂, retrieve поV̂.
Все четыре метода при выбранной d хранят 2d байт на вектор (fp16
координаты).
2. Постановка эксперимента
BEIR — стандартный набор retrieval-датасетов (SciFact, FiQA, NFCorpus, TREC-COVID и др.). Метрика — NDCG@10. Корпус и запросы кодируются выбранной моделью, потом по cosine similarity берутся top-10 для каждого запроса, NDCG считается против размеченного qrels.
Для PCA и poly-AE «фит» делается трансдуктивно: считаем статистики прямо на корпусе, который собираемся компрессировать. Запросы при этом никогда не участвуют в фите — они приходят на вход уже фиксированному энкодеру/декодеру. Это совпадает с production-сценарием: оператор индекса один раз считает PCA + Ridge на своих данных, потом обслуживает поток запросов.
Для основных замеров берём FiQA — 57K документов, 648 запросов, 1706 qrels.
3. Главная таблица — четыре модели
NDCG@10 на FiQA при бюджете 256 fp16 (512 байт/вектор) и 128 fp16 (256 байт):
| Модель | D | d | raw | matryoshka† | PCA | PCA-w | PCA-h-w | poly-AE |
|---|---|---|---|---|---|---|---|---|
| nomic-embed-text-v1.5 | 768 | 128 | 0.3746 | 0.3190 | 0.3273 | 0.3227 | 0.3439 | 0.3380 |
| nomic-embed-text-v1.5 | 768 | 256 | 0.3746 | 0.3508 | 0.3670 | 0.3298 | 0.3616 | 0.3673 |
| mxbai-embed-large-v1 | 1024 | 128 | 0.4533 | 0.3505 | 0.3691 | 0.3876 | 0.4002 | 0.4125 |
| mxbai-embed-large-v1 | 1024 | 256 | 0.4533 | 0.4040 | 0.4175 | 0.3892 | 0.4311 | 0.4445 |
| bge-base-en-v1.5* | 768 | 128 | 0.4062 | 0.2914 | 0.3268 | 0.3216 | 0.3443 | 0.3653 |
| bge-base-en-v1.5* | 768 | 256 | 0.4062 | 0.3574 | 0.3685 | 0.3357 | 0.3697 | 0.3962 |
| e5-base-v2* | 768 | 128 | 0.3987 | 0.2498 | 0.3062 | 0.3264 | 0.3358 | 0.3315 |
| e5-base-v2* | 768 | 256 | 0.3987 | 0.3333 | 0.3608 | 0.3316 | 0.3660 | 0.3848 |
† Колонка «matryoshka» — это embedding[:d] плюс L2-нормировка. На
nomic и mxbai это валидный matryoshka-вектор. На моделях, помеченных
* (bge-base, e5-base), модель не тренировали под matryoshka, и slice
здесь — это тест на то, что произойдёт при наивной нарезке
не-MRL-модели. Это сценарий, в который попадают пользователи
bge-семейства, e5, custom-fine-tuned моделей — и мы здесь его честно
замеряем.
Что отсюда видно:
- Poly-AE впереди vanilla PCA на всех восьми ячейках (+1.07 до
+4.34 п.п. при d=128, +0.03 до +2.77 п.п. при d=256). Над более
жёстким baseline’ом — half-whitened PCA — разница меньше: −0.59
до +2.10 п.п. при d=128, +0.57 до +2.65 п.п. при d=256. Где
квадратичный декодер реально помогает и при каком
d— разбираем в далее. - При
d=256poly теряет 0.7–1.4 п.п. NDCG vs raw 768/1024 на всех четырёх моделях. 4× сжатие per-vector памяти при потере меньше полутора пунктов — основная цифра поста. - На моделях без матрёшка-обучения колонка matryoshka проседает сильнее PCA — до -15 п.п. NDCG при d=128. Это побочное наблюдение: в посте мы сравниваем PCA и poly-AE, а не PCA с матрёшкой. Если цифры matryoshka в таблице кажутся странными — короткий комментарий в §4.
4. Где квадратичный декодер реально помогает
PCA — линейный baseline. Квадратичный декодер добавляет ту самую
нелинейную часть, которой линейный не достаёт (механика — в §5).
Насколько это реально помогает на retrieval, и при каком d?
Прирост poly-AE по моделям и d. Левые две колонки — над vanilla
PCA, правые — над half-whitened PCA (более жёсткий, apples-to-apples
baseline, потому что внутри poly-AE координаты p тоже whiten’ятся,
см. §6):
| Модель | poly над PCA, d=128 | poly над PCA-½w, d=128 | poly над PCA, d=256 | poly над PCA-½w, d=256 |
|---|---|---|---|---|
| nomic-v1.5 | +1.07 п.п. | −0.59 п.п. | +0.03 п.п. | +0.57 п.п. |
| mxbai-large | +4.34 п.п. | +1.23 п.п. | +2.70 п.п. | +1.34 п.п. |
| bge-base | +3.85 п.п. | +2.10 п.п. | +2.77 п.п. | +2.65 п.п. |
| e5-base-v2 | +2.53 п.п. | −0.43 п.п. | +2.40 п.п. | +1.88 п.п. |
Картина:
-
При
d=128(8× сжатие) poly над vanilla PCA стабильно +1–4 п.п. Это режим, где линейный декодер заметно роняет дисперсию в нелинейный хвост, и квадратичная коррекция её вытаскивает. Sweet spot для метода против vanilla PCA. Над PCA-half-whitened картина смешанная — на nomic/e5 poly проигрывает (−0.59/−0.43 п.п.), на mxbai/bge выигрывает (+1.23/+2.10). -
При
d=256(4× сжатие) разница над vanilla PCA переменчивая — на mxbai/bge/e5 стабильные +2.4–2.8 п.п., на nomic ноль (+0.03). Возможная причина: nomic тщательно тренировался с multi-slice contrastive loss, его latent более изотропный. Над PCA-half-whitened poly везде впереди (+0.57 до +2.65 п.п.) — на этомdквадратичная коррекция остаётся осмысленной даже после whitening’а. -
Анизотропия → больший прирост. Чем сильнее cone effect, тем больше дисперсии лежит в нелинейном хвосте, который не достаёт даже whitened PCA, а poly — достаёт. Это та самая геометрия, которую разбираем в §5.
Замечание про матрёшку в таблице
В §3 видно, что matryoshka column на не-MRL моделях (bge, e5) проседает сильнее PCA — то есть на случайной не-MRL модели наивная нарезка работает хуже линейной проекции на корпус. Это известный результат — вопрос «MRL vs PCA на retrieval» обсуждался независимо от этого поста, см. Matryoshka-Adaptor 2024, SMEC «Rethinking MRL» 2025, CoRECT 2025, и YouTube-видео «Is PCA enough?». В этом посте мы сравниваем PCA и poly-AE; matryoshka в §3-таблице как третий ориентир.
Где poly-AE не подходит
PCA-фит на корпусе — обязательная часть метода. Это значит poly-AE не работает там, где корпуса под рукой нет:
- multi-tenant SaaS: одна модель, тысячи клиентов с разными корпусами, считать PCA для каждого — operational headache;
- streaming-индексы: статистика плывёт со временем, PCA придётся переобучать;
- edge-инференс: телефон, браузер, embedded — не хочется таскать серверную PCA-матрицу клиента.
В этих сценариях нужна модель с матрёшка-обучением и embedding[:d],
poly-AE здесь не альтернатива — ему тоже нужна корпусная статистика.
Практический takeaway
В operator-fit-сценарии (фиксированный корпус, оператор фитит сжатие один раз) у вас два рабочих режима:
- d=256 даёт 4× сжатие при -0.7 до -1.4 п.п. NDCG vs raw. Poly над PCA-half-whitened — от +0.57 (nomic) до +2.65 п.п. (bge). Стабильно впереди best-PCA-baseline на любой модели.
- d=128 даёт 8× сжатие. Poly над vanilla PCA +1–4 п.п. на любой модели. Над PCA-half-whitened картина расколота: на mxbai/bge выигрывает (+1.23/+2.10 п.п.), на nomic/e5 проигрывает (−0.59/−0.43 п.п.) — т.е. на хорошо MRL-обученных моделях half-whitened PCA сама берёт ту же нелинейность дешевле.
5. Почему линейная проекция теряет информацию
PCA — это лучшая возможная линейная проекция данных в d-мерное подпространство. Но «лучшая линейная» не значит «достаточная»: если у данных есть нелинейная структура, линейный декодер её принципиально не достаёт.
У transformer-эмбеддингов такая структура есть и хорошо изучена — cone effect. Облако точек концентрировано в узком конусе на сфере и сильно нелинейно структурировано внутри.
тяни мышью чтобы повернуть
PCA ловит только проекции вдоль ортогональных собственных осей — то есть эллипсоид, в который облако не помещается. Часть структуры (curvature многообразия) для линейного декодера невидима. Чем сильнее анизотропия (чем уже конус), тем больше дисперсии остаётся в этом нелинейном хвосте, который PCA структурно не способен достать.
Из этого ясно, что хочется. Декодер, который умеет квадратичные комбинации координат — то есть умеет ловить локальную curvature многообразия. Часть информации, которую PCA теряет, мы тогда вытащим обратно.
6. Полиномиальный декодер через линейный лифт
Из §5 ясно, что нужен нелинейный декодер. Прямолинейный путь — обучить нейросетку. Но тогда мы теряем закрытость пайплайна: SGD, learning rate, batch size, сходимость, ранний останов. Хочется иметь нелинейный декодер, который решается одной формулой.
Стандартный приём из теории регрессии: полиномиальный лифт. Берём
вектор p и поднимаем его во все мономы степени до 2 — bias, линейные,
квадраты и парные произведения. На двумерном примере:
lift([p₁, p₂]) = [1, p₁, p₂, p₁², p₁·p₂, p₂²]
↑ ↑ ↑ ↑ ↑ ↑
bias линейные квадратичные
Любая линейная комбинация этих шести компонент = квадратичная функция от
исходных p₁, p₂. То есть линейная регрессия по лифту = квадратичная
регрессия в исходном пространстве. Никакой нелинейной оптимизации не
нужно — обычный Ridge OLS в закрытом виде.
Применяем к нашему случаю. Encoder — top-d PCA (p = (V − V̄) @ Q,
p ∈ ℝ^d). Поднимаем p во все мономы до степени 2:
def polynomial_lift(p, degree=2):
features = [1.0] # bias
features.extend(p) # degree 1
for i in range(len(p)):
for j in range(i, len(p)):
features.append(p[i] * p[j]) # degree 2
return np.array(features)
Размерность лифта — M = (d+1)(d+2)/2. Для d=128 это M = 8385,
для d=256 — M = 33153. Decoder — линейная регрессия от M-мерного
лифта обратно к D-мерному исходному вектору. Несмотря на «обычную
линейную регрессию», итоговое отображение p → V̂ — квадратичное.
Тренировка decoder’а — np.linalg.solve:
def fit_decoder(P, V, lam=1e-3):
L = polynomial_lift(P, degree=2) # (N, M)
G = L.T @ L + lam * np.trace(L.T @ L) / L.shape[1] * np.eye(L.shape[1])
W = np.linalg.solve(G, L.T @ V)
return W
Полиномиальный автоэнкодер собран:
- encoder — закрытый линейный (PCA), без обучения,
- decoder — закрытый квадратичный (Ridge OLS на полиномиальном лифте),
обучается одним
np.linalg.solve, - reconstruction —
V̂ = polynomial_lift(p) @ W, - residual —
V_resid = V − V̂(полезен в §8 для квантизации).
Никаких backprop, learning rate, batch size, эпох. На 100K векторах при
d=100 — пара минут на CPU. При d=256 — около 15 минут (Ridge решает
систему M³, кубически по M).
Технический штрих с нормировкой
Сырой p после PCA имеет огромную анизотропию по самим координатам
(первая ось ~50× по дисперсии, последняя ~0.5×) — это плохо для Ridge:
крупные оси доминируют решение, мелкие недополучают веса. Решается
покоординатной нормировкой на единичную дисперсию:
p_normed = p / np.sqrt(eigvals)
p = p_normed * (0.9 / np.linalg.norm(p_normed, axis=1).max())
Глобальный множитель 0.9 / max-norm даёт ‖p‖ ≤ 0.9 — численная
стабильность при возведении в квадрат. На NDCG не влияет, но численно
аккуратнее.
7. Замер на маленьком корпусе и in-sample magic
Один методологический штрих. Когда я гонял первый sanity-check на
SciFact (5183 документа, 300 запросов), poly-AE при d=256 показывал
NDCG@10 0.6980 vs raw 0.7032 — потеря всего 0.5 п.п. И прирост над
PCA там был +1.36 п.п.
На FiQA (57638 документов) тот же эксперимент дал:
- poly vs raw: -0.73 п.п. (стабильно, как на SciFact);
- poly над PCA: +0.03 п.п. (вместо +1.36 на SciFact).
Что произошло. На SciFact с d=256 Ridge решает регрессию с M=33153
фичей на N=5183 векторах — серьёзно overdetermined: in-sample V̂_corpus
почти точно совпадает с V_corpus, и retrieval по V̂_corpus
эквивалентен retrieval’у по полному 768-мерному V_corpus. То есть в
SciFact-замере poly-AE раздул разрыв с PCA за счёт того, что просто
запомнил корпус в весах Ridge, а не за счёт настоящего обобщения.
Что важно: poly vs raw просадка стабильна (-0.5–0.85 п.п.) на обоих
корпусах. А вот в poly vs PCA нужно держать в голове, что на маленьких
корпусах (N ≤ 10K при d=256) разница частично mythical. Безопасное
правило: N должно быть хотя бы в 5–10 раз больше M = (d+1)(d+2)/2.
Для d=256 это N ≳ 200K. Для d=128 — N ≳ 50K.
В headline-таблице (§3) FiQA с N = 57K достаточен для d=128, на
грани для d=256. Цифры для bge на FiQA при d=256 нужно читать с тем
же caveat’ом.
8. Сжатие — что делать с residual
Сам по себе автоэнкодер ничего не сжимает в строгом смысле слова. Он
перепаковывает вектор в пару (p, V_resid) — латентная координата плюс
остаток. Та же информация, в другой форме. Чтобы получить реальное
сжатие, residual ещё нужно квантовать.
И вот тут anisotropy-снимающее свойство poly-AE как раз в кассу. На
DBpedia-OpenAI при d=100 cond(V_resid) падает с 72 до 3.4 — почти
изотропный остаток. Линейный PCA того же d даёт cond=4.21 — заметно
менее изотропный.
Google Research недавно опубликовал TurboQuant — квантизатор с фиксированной случайной ротацией, который на изотропных распределениях подходит близко к теоретическому пределу сжатия (Shannon rate-distortion для нормальных). Композиция получается естественная: автоэнкодер снимает анизотропию, TurboQuant эффективно квантует уже изотропный остаток.
V ──► encoder (PCA) ──► p
│
decoder (poly+Ridge) ──► V̂
│
V − V̂ ──► V_resid ──► TurboQuant ──► компактный код
В Qdrant 1.18 я выпустил модификацию TurboQuant с компенсацией анизотропии. Она даёт примерно такой же recall, что и гибрид «poly-AE + квантизация остатка», но без необходимости хранить poly-AE латентные координаты — то есть экономит p-часть бюджета байт на вектор. Про трюки, которые я применил поверх стандартного TurboQuant — в следующем посте.
9. Откуда метод и где его применяют
С полиномиальным лифтом я знаком ещё со времён работы в ACD/Labs — там в хемоинформатике это типовой инструмент для построения признаков по молекулярной структуре (как вход для downstream-регрессии). Сейчас работаю в Qdrant. Когда делал там поддержку late-interaction retrieval (ColBERT-style multi-vectors с maxsim, v1.10), я вспомнил про этот приём — и так сложилась идея «PCA + квадратичный декодер». Мне казалось, что для нейросетевых эмбеддингов её никто не пробовал.
Когда черновик попал к Sava Kalbachou на ревью, он нашёл конкретные работы, в которых ровно такая же конструкция давно используется — в области численного моделирования физических систем, под названием quadratic manifold:
- Jain, Tiso, Rixen, Rutzmoser 2017 — «A quadratic manifold for model order reduction of nonlinear structural dynamics». Введение понятия: PCA top-d плюс квадратичная коррекция. У них квадратичная часть выводится из физики системы, не из data fit’а. Самая ранняя работа.
- Geelen, Wright, Willcox 2022 — «Operator inference for non-intrusive model reduction with quadratic manifolds». Точно наша конструкция: PCA-encoder, decoder на Kronecker-произведении латента, коэффициенты через regularized least squares. Код в открытом доступе.
- Geelen, Balzano, Willcox 2023 — «Learning latent representations in high-dimensional state spaces using polynomial manifold constructions». Уже прямо «polynomial manifold», fit через регрессию.
- Schwerdtner, Peherstorfer 2024–2025 — серия работ про варианты обучения и применения: greedy выбор PCA-направлений, sparse regression на манифольде, streaming online-learning, «Operator Inference Aware Quadratic Manifolds with Isotropic Reduced Coordinates» — последняя работа любопытно перекликается с residual-isotropy наблюдением для TurboQuant из §8.
В этих работах конструкция применяется к снапшотам траектории решения численной модели физической системы. Эмбеддинги — другая геометрия данных, и заранее неочевидно, что quadratic manifold там тоже будет работать; цифры в §3 показывают, что работает.
Почему этой связки, кажется, нет в mainstream ML
Дальше — мои домыслы. У этих сообществ, видимо, мало пересечений: работы по quadratic manifold выходят в инженерных журналах, в ML-исследованиях они редко всплывают.
Ещё в ML-памяти полиномиальные методы в основном ассоциируются с kernel trick’ом (SVM, kernel PCA) — это другая конструкция, и явный полиномиальный лифт как самостоятельный приём после этого почти не упоминается.
И, наверное, autoencoder в современном ML по умолчанию мыслится как нейросеть. Closed-form варианты могут просто не попадать в поле внимания.
То есть это applied-ML работа: приём, разработанный в соседней области, оказался полезен и для эмбеддингов. Содержание поста — перенос конструкции и эмпирическая проверка.
10. Пределы метода
Несколько честных оговорок:
- Cubic Ridge solve. При
d=256M = 33K, иnp.linalg.solveв закрытом виде стоитO(M³)≈ 5–15 минут на CPU. Дляd=384это ужеM = 74KиM³неподъёмное. Метод комфортен доd ≈ 200–256. Для большихdнужно либо рандомизированные методы (random feature approximation), либо переходить на нейросетевой декодер. - Трансдуктивный фит. PCA + Ridge считаются на корпусе, который собираемся компрессировать. Если корпус drift’ит со временем, декомпрессия деградирует. Для статичных индексов это не проблема, для streaming — нужно переобучать.
- Маленькие N. При
N < 5M(то естьN < 200Kдляd=256) Ridge начинает запоминать корпус, in-sample retrieval улучшается за счёт переобучения, а не за счёт структуры. Контрольная цифра — соотношение poly-vs-PCA: если оно слишком велико, скорее всего overfit. - Степень 3 — пока не работает. Лифт степени 3 даёт
M = O(d³)— дляd=100это уже 175K фичей. Помещается в память, но Ridge solve становится непрактичным, и переобучение растёт.
11. Что попробовать дальше
Очевидные ветки:
- Бóльший корпус, более тяжёлые модели. Все замеры в посте — на FiQA с моделями уровня 305–560M params. Имеет смысл прогнать на MS MARCO (8.8M passages) с моделями уровня e5-mistral-7b. Кубический Ridge становится узким местом, нужно random-feature approximation.
- Степень лифта. При каких
d/Nстепень 3 начинает реально помогать? На каких эмбеддингах есть тензорная структура 3-го порядка, которую мы сейчас теряем? - Режим, в котором матрёшку нельзя применить. Multi-tenant SaaS, edge-инференс — там poly-AE без PCA не сработает (нужны корпусные статистики). А вот гибрид: матрёшка в модели + полиномиальный декодер на клиенте — теоретически даёт лучшее из обоих миров.
Код и воспроизводство
Лоадеры BEIR (SciFact, FiQA, NFCorpus, TREC-COVID), энкодеры (nomic-v1.5, mxbai-large, bge-base, e5-base), PCA-init с per-axis std, polynomial lift, Ridge OLS, retrieval с NDCG@10/Recall@10 — всё в github.com/IvanPleshkov/poly-autoencoder.
Минимальный прогон:
git clone https://github.com/IvanPleshkov/poly-autoencoder.git
cd poly-autoencoder
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt pytrec_eval einops
python beir_eval.py --model nomic-embed-text-v1.5 --dataset fiqa --d 128,256
На M-серии MacBook первый прогон скачает модель + датасет, закодирует корпус (5–15 мин в зависимости от модели), и за следующие 5–20 минут выдаст таблицу с NDCG@10 для всех четырёх методов.