SVM + TF-IDF от Karpathy: классификация статей без нейросетей | AiManual
AiManual Logo Ai / Manual.
28 Янв 2026 Гайд

SVM + TF-IDF от Карпати: классифицируйте научные статьи на дешёвом VPS, пока все играются с LLM

Почему Андрей Карпати использует SVM и TF-IDF в arXiv Sanity. Рабочий код на 50 строк, который экономит тысячи на GPU.

Все помешались на трансформерах. А Карпати - нет

2026 год. Кажется, любая задача по обработке текста требует как минимум 7-миллиардную модель, три A100 и молитву на удачную квантизацию. BERT, RoBERTa, DeBERTa - алфавитный суп из архитектур, где каждая следующая буква обещает +0.5% accuracy ценой удвоения вычислительных затрат.

А потом ты заходишь на arXiv Sanity - сервис Андрея Карпати для поиска научных статей, которым пользуются десятки тысяч исследователей. И обнаруживаешь, что классификация по темам там работает на... SVM с TF-IDF. Да, на той самой технологии, которую все считали устаревшей ещё в 2018-м.

Вот ирония: один из главных проповедников глубокого обучения в мире использует для своего рабочего инструмента «старомодные» методы. И они работают. Отлично работают.

Зачем это вам? Потому что деньги - не фантики

Представьте два сценария:

  • Сценарий BERT: Разворачиваете модель на GPU-сервере ($200/месяц). Ждёте 30 секунд на инференс одной статьи. Обучение на вашем датасете требует отдельного инженера и неделю экспериментов.
  • Сценарий SVM+TF-IDF: Запускаете на самом дешёвом VPS за $5. Классификация занимает миллисекунды. Весь код умещается в 50 строк Python.

Разница в стоимости - 40x. Разница во времени инференса - 1000x. А в accuracy? Часто - 1-3% в пользу BERT на идеальных данных. На реальных, зашумленных - иногда SVM даже выигрывает.

💡
TF-IDF (Term Frequency-Inverse Document Frequency) - это не «просто подсчёт слов». Это взвешенная статистика, которая показывает, насколько слово важно для конкретного документа относительно всей коллекции. Слово «квантовый» в статье по физике получает высокий вес, а слово «методология» - низкий, потому что оно встречается везде.

Как работает классификатор arXiv Sanity (без магии)

Карпати не публиковал точный код, но по косвенным признакам и стандартным практикам можно восстановить архитектуру:

  1. Сбор текстов: Берутся заголовки и аннотации статей с arXiv. Только текст, без формул и специальной разметки.
  2. Предобработка: Токенизация, лемматизация (приведение слов к начальной форме), удаление стоп-слов. Для научных текстов часто оставляют специальные термины.
  3. TF-IDF векторизация: Каждый документ превращается в вектор из 10-50 тысяч измерений (в зависимости от размера словаря).
  4. SVM с линейным ядром: Обучается классификатор, который ищет оптимальную разделяющую гиперплоскость между классами.

Вся прелесть в том, что SVM с линейным ядром идеально сочетается с TF-IDF. Высокая размерность? Не проблема. Разреженные данные? Именно то, что нужно. Линейные SVM масштабируются на миллионы примеров и работают в режиме реального времени.

1 Собираем данные: не только arXiv

Первая ошибка - думать, что метод работает только для научных статей. Я применял ту же связку для:

  • Классификации обращений в техподдержку (20 категорий, 95% точность)
  • Автоматической тегизации постов в блоге
  • Определения тональности отзывов на товары

Главное - достаточное количество размеченных данных. SVM любит 1000+ примеров на класс, но может работать и с меньшим, если признаки хорошие.

2 Пишем код: 50 строк, которые заменяют нейросеть

Вот минимальный рабочий пример. Не нужно устанавливать torch, transformers или что-то тяжёлое:

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

# 1. Загружаем данные
# Предполагаем CSV с колонками 'text' и 'label'
df = pd.read_csv('articles.csv')

# 2. Создаём pipeline: TF-IDF -> SVM
# ngram_range=(1, 2) означает униграммы и биграммы
# max_features ограничивает размер словаря (ускоряет работу)
tfidf = TfidfVectorizer(
    max_features=10000,
    ngram_range=(1, 2),
    stop_words='english',
    min_df=2  # слово должно встречаться минимум в 2 документах
)

# LinearSVC быстрее SVC с linear kernel и часто точнее
svm = LinearSVC(
    C=1.0,
    class_weight='balanced',  # важно при несбалансированных классах
    max_iter=2000
)

pipeline = Pipeline([
    ('tfidf', tfidf),
    ('svm', svm)
])

# 3. Делим на train/test
X_train, X_test, y_train, y_test = train_test_split(
    df['text'], df['label'], test_size=0.2, random_state=42
)

# 4. Обучаем (это займёт секунды, даже на 10к документов)
pipeline.fit(X_train, y_train)

# 5. Предсказываем и оцениваем
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred))

# 6. Сохраняем модель для продакшена
import joblib
joblib.dump(pipeline, 'tfidf_svm_model.joblib')

# Для инференса:
# loaded_model = joblib.load('tfidf_svm_model.joblib')
# prediction = loaded_model.predict(["Новый метод квантового машинного обучения"])

Видите? Никаких GPU, никаких сложных архитектур. Весь файл с моделью весит мегабайты, а не гигабайты.

Параметр C в SVM - это регуляризация. Меньше C = больше регуляризация, модель проще. Начинайте с C=1.0 и меняйте на 0.1 или 10, если видите переобучение или недообучение.

Почему это всё ещё работает в 2026 году?

Три причины, которые все игнорируют:

Фактор SVM+TF-IDF BERT/трансформеры
Требования к данным 1000+ размеченных примеров 10000+ для fine-tuning
Скорость инференса ~1 мс на документ 100-1000 мс
Потребление памяти 10-100 МБ 1-10 ГБ
Интерпретируемость Можно посмотреть важные слова Чёрный ящик

Но главное - закон убывающей отдачи. Первые 95% точности SVM добирает легко. Следующие 4% BERT получает ценой 100x ресурсов. Последний 1% - это уже игры с ансамблями и тонкой настройкой, которая в продакшене часто не нужна.

Когда НЕ использовать этот подход

Честно о недостатках:

  • Контекстная неоднозначность: Слова «яблоко» (фрукт) и «Apple» (компания) получат одинаковые векторы. BERT справится лучше.
  • Многоязычные данные: TF-IDF нужно обучать отдельно для каждого языка или использовать сложные трюки.
  • Очень короткие тексты: Твиты, поисковые запросы - там, где контекст критичен, трансформеры выигрывают.
  • Transfer learning: Хотите дообучить модель на новых данных без полного переобучения? С SVM это сложнее.

Для этих случаев посмотрите на гибридный поиск с BM25 или более лёгкие трансформеры типа DistilBERT.

Продвинутые трюки, которые поднимут точность на 5-10%

Базовая версия работает. Но если хочется выжать максимум:

1. Свои стоп-слова для научных текстов

Встроенный список стоп-слов в sklearn удаляет «the», «and», «is». Но в научных статьях свои мусорные слова: «paper», «propose», «method», «results». Создайте custom_stopwords и добавьте их:

custom_stopwords = set(stopwords.words('english')) | {
    'paper', 'propose', 'method', 'approach', 'result',
    'show', 'demonstrate', 'experiment', 'study'
}
tfidf = TfidfVectorizer(stop_words=custom_stopwords, max_features=15000)

2. Взвешивание классов

В научных статьях по computer science может быть 100 статей по ML и 10 по теории баз данных. LinearSVC с class_weight='balanced' автоматически скорректирует веса.

3. Feature engineering для научного текста

Добавьте в признаки не только слова, но и:

  • Количество математических формул (по количеству знаков $ в LaTeX)
  • Присутствие определённых шаблонов («et al.», «Figure 1», «Theorem 2»)
  • Длину библиографии

Это превращает SVM в гибридную модель, которая учитывает не только текст, но и структуру.

4. Ансамбли с другими моделями

SVM + логистическая регрессия + наивный байес. Простой голосующий ансамбль часто даёт прирост в 1-2%, а обучается ненамного дольше.

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB

# Каждый классификатор получает свои гиперпараметры
estimators = [
    ('svm', LinearSVC(C=1.0, class_weight='balanced')),
    ('lr', LogisticRegression(max_iter=1000)),
    ('nb', MultinomialNB())
]

ensemble = VotingClassifier(estimators, voting='hard')
# Используйте тот же pipeline с TF-IDF

Развёртывание в продакшене: дешёво и сердито

Вот как выглядит продакшен-архитектура:

# app.py - минимальный FastAPI сервис
from fastapi import FastAPI
import joblib
import numpy as np

app = FastAPI()
model = joblib.load('tfidf_svm_model.joblib')

@app.post("/predict")
async def predict(texts: list[str]):
    predictions = model.predict(texts)
    # Можно добавить вероятности (у LinearSVC нет predict_proba)
    # Используйте decision_function для псевдовероятностей
    scores = model.decision_function(texts)
    return {"predictions": predictions.tolist(), "scores": scores.tolist()}

Этот сервис будет работать на самом слабом VPS. 1 ГБ RAM, 1 CPU ядро - достаточно для тысяч запросов в минуту. Попробуйте добиться такого же с BERT-large.

💡
Для батч-обработки больших объёмов данных (например, классификация всего arXiv) используйте Spark MLlib с теми же алгоритмами. TF-IDF и линейный SVM отлично параллелятся на кластере.

Что делать, когда SVM уже недостаточно?

Бывает. Данные стали сложнее, требования к точности выросли. Вот путь миграции:

  1. Сначала попробуйте Sentence Transformers: Всепрощенные эмбеддинги (например, all-MiniLM-L6-v2) + тот же SVM. Это даёт семантическое понимание при умеренных затратах.
  2. Затем - лёгкие трансформеры: DistilBERT, TinyBERT. В 5-10 раз меньше оригинала, но сохраняют 95% качества.
  3. И только потом - полноценные модели: Когда доказано, что более простые методы не справляются.

Кстати, для тематического моделирования больших коллекций документов посмотрите новые методы topic modeling 2026 - некоторые из них тоже обходятся без тяжёлых нейросетей.

Мой совет: начните с простого

Следующий раз, когда будете строить классификатор текстов:

  1. Соберите 100-200 размеченных примеров (можно даже вручную)
  2. Запустите код из этой статьи
  3. Оцените accuracy

Если получилось 85%+ - скорее всего, вам не нужны трансформеры. Сэкономленные $500/месяц на GPU лучше потратить на сбор более качественных данных.

Андрей Карпати не дурак. Если бы SVM+TF-IDF не работал для arXiv Sanity, он бы давно перешёл на что-то другое. Но система работает с 2015 года, обрабатывает сотни тысяч статей и не требует апгрейда железа каждые полгода.

Иногда лучшее решение - не самое модное, а самое подходящее. Особенно когда счёт идёт на реальные деньги, а не на хайп в твиттере.

P.S. Если интересно, как применять похожие принципы для поиска - почитайте про HashIndex как альтернативу векторному поиску. Там та же философия: простота, скорость, минимальные ресурсы.