Andrej Karpathy — один из ведущих специалистов в области глубокого обучения и компьютерного зрения. Он ранее занимал должность Director of AI в Tesla, где руководил командой Autopilot Vision, и является соавтором и основным преподавателем курса Stanford CS231n (Convolutional Neural Networks for Visual Recognition) — одного из самых влиятельных академических курсов по нейросетям. Его опыт охватывает как фундаментальные исследования, так и практическое применение современных нейросетевых моделей в промышленности.
И вот этот человек для своего личного инструмента поиска научных статей выбрал... SVM. Не BERT. Не GPT. Не какие-нибудь модные эмбеддинги. Старый добрый Support Vector Machine.
Звучит как шутка? Это не шутка.
Его проект arXiv Sanity появился ещё в 2016 году и до сих пор помогает тысячам исследователей находить релевантные статьи. Причём работает на минимальном VPS за 5 долларов в месяц — без GPU, без облачных API, без ежемесячных счетов на сотни долларов.
Почему такой "примитивный" подход работает? И когда вам тоже стоит выбрать SVM вместо трансформера? Давайте разбираться.
TF-IDF — это способ превратить текст в набор чисел. Название расшифровывается как Term Frequency — Inverse Document Frequency, но запоминать это не обязательно. Важнее ухватить саму идею.
Представьте: вы читаете abstract научной статьи и пытаетесь понять, о чём она. Какие слова реально помогут?
Слово "the" встречается в каждой статье. Бесполезно.
Слово "neural" встречается в половине статей по ML. Чуть полезнее, но не сильно.
Слово "transformer" встречается в 5% статей. Вот это уже интересно.
Слово "attention mechanism" встречается в 2% статей. Очень информативно.
TF-IDF делает именно это: взвешивает слова по их информативности. Чем реже слово встречается в корпусе — тем выше его вес. Чем чаще слово встречается в конкретном документе — тем выше его вклад.
На выходе получаем вектор: каждый документ превращается в точку в многомерном пространстве, где каждая размерность — это отдельное слово (или пара слов). Если в корпусе 5000 уникальных слов, пространство будет 5000-мерным. Координата по каждой оси — это TF-IDF вес соответствующего слова в документе. Статьи про трансформеры оказываются рядом друг с другом (у них высокие значения по осям "attention", "transformer"), статьи про reinforcement learning — где-то в другом углу (высокие значения по "reward", "policy").
SVM (Support Vector Machine) — алгоритм классификации. Представьте график с точками двух цветов: красные и синие. Задача SVM — провести линию (а в многомерном пространстве — гиперплоскость), которая разделит эти точки. Причём провести так, чтобы линия была максимально далеко от ближайших точек обоих классов. Это как провести дорогу между двумя деревнями так, чтобы она шла ровно посередине — с максимальным отступом от каждой.
Зачем максимизировать отступ? Чтобы модель лучше обобщалась на новые данные. Если граница проходит впритык к точкам, любой шум сместит её не туда.
Но погодите. Мы же хотим рекомендовать статьи, а не классифицировать их. Зачем тут SVM?
А вот в чём трюк Karpathy:
Пользователь отмечает несколько статей как "интересные" (положительные примеры).
Все остальные статьи автоматически становятся "неинтересными" (отрицательные .примеры).
SVM учится отличать интересное от неинтересного.
Для каждой новой статьи SVM выдаёт score — насколько она похожа на те, что понравились.
По сути, SVM учится понимать ваши предпочтения на основе буквально нескольких примеров. И делает это на удивление хорошо.
Научные тексты — идеальный случай для TF-IDF + SVM. Вот почему:
1. Ключевые слова работают буквально. В научных статьях термины однозначны. Если в abstract есть "attention mechanism" и "transformer architecture" — это почти гарантированно статья про трансформеры. Глубокая семантика здесь не нужна.
2. Высокая размерность — не проблема. TF-IDF создаёт вектора с тысячами признаков (по одному на каждое слово). Обычные алгоритмы на таких данных тонут. Но SVM с линейным ядром создавался именно для этого — он прекрасно работает в высокоразмерных пространствах.
3. Мало положительных примеров. У пользователя обычно 5-20 "понравившихся" статей против 30 000 в базе. Такой дисбаланс убил бы большинство моделей. Но SVM с параметром class_weight='balanced' справляется на ура.
4. Скорость. LinearSVC обучается за миллисекунды. Можно пересчитывать рекомендации при каждом запросе — без кэширования, без очередей.
Математика (для любопытных)TF-IDF:
Term Frequency — как часто термин встречается в документе
:
Inverse Document Frequency — насколько термин редкий в корпусе:
где — общее число документов,
— число документов, содержащих термин
.
Итоговый вес:
В arXiv Sanity используется sublinear_tf=True, что означает сублинейное масштабирование:
Это снижает влияние слов, которые встречаются в документе много раз.
SVM:
Для линейного SVM ищем гиперплоскость , которая максимизирует margin. Задача оптимизации:
при условии:
После обучения decision function даёт score для ранжирования.
В 2016 году Karpathy устал тонуть в потоке статей на arXiv и написал arXiv Sanity Preserver. Название говорит само за себя: инструмент для сохранения рассудка при работе с arXiv. Позже появилась облегчённая версия — arXiv Sanity Lite, которую Karpathy переписал с нуля и которая работает до сих пор.
Принцип работы простой:
Система ежедневно скачивает новые статьи с arXiv
Для каждой статьи вычисляется TF-IDF вектор (title + abstract + authors)
Пользователь тегирует интересные статьи ("transformers", "rl", "diffusion")
SVM обучается на лету: тегированные статьи = положительные примеры
Все статьи ранжируются по decision_function SVM
Топ-N показываются как рекомендации
Красота в том, что всё это крутится на минимальном VPS. Karpathy прямо пишет в README: "I am running this code currently on the smallest Nanode 1 GB instance indexing about 30K papers, which costs $5/month." Тридцать тысяч статей, пять баксов в месяц, никакого GPU.
В апреле 2024 года исследователи из Fudan University опубликовали статью SurveyAgent — систему для работы с научной литературой. И что они взяли за основу модуля рекомендаций? Правильно, arXiv Sanity.
Из статьи:
Авторы SurveyAgent называют подход "widely recognized for its effectiveness and efficiency" (широко признанный за эффективность и производительность) и строят на нём свою систему. Поверх они добавили LLM-фильтрацию для улучшения семантической релевантности, но базовый алгоритм остался тем же. SVM + TF-IDF как фундамент, нейросети как опциональный бустер.
Karpathy честно признаёт: TF-IDF работает на уровне слов, а не смыслов. Если вы ищете статьи про "character role-playing in LLMs", система вполне может вернуть статьи про "character recognition in computer vision" — просто потому что слово "character" совпадает.
Это не баг, это осознанный компромисс. За простоту и скорость приходится платить отсутствием глубокого семантического понимания. Для большинства задач — вполне приемлемая цена.
Хватит теории. Давайте напишем свой мини-arXiv Sanity.
Python 3.8+
scikit-learn >= 1.0
numpy >= 1.19
Jupyter Notebook (для интерактивной работы)
pip install scikit-learn numpy
Если pip уже есть — этого достаточно.
import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.svm import LinearSVC from sklearn.metrics.pairwise import cosine_similarity
Всё, что нужно — scikit-learn и numpy. Никаких GPU, никаких API-ключей, никаких внешних сервисов.
Для демонстрации используем 10 статей по ML — достаточно, чтобы увидеть кластеризацию по темам. В реальном проекте это были бы тысячи статей с arXiv.
PAPERS = [ # NLP / Transformers {"id": "2017.vaswani", "title": "Attention Is All You Need", "abstract": "The dominant sequence transduction models are based on complex recurrent or convolutional neural networks. We propose a new simple network architecture, the Transformer, based solely on attention mechanisms.", "authors": "Vaswani, Shazeer, Parmar"}, {"id": "2018.bert", "title": "BERT: Pre-training of Deep Bidirectional Transformers", "abstract": "We introduce BERT, designed to pre-train deep bidirectional representations from unlabeled text by jointly conditioning on both left and right context.", "authors": "Devlin, Chang, Lee, Toutanova"}, {"id": "2018.elmo", "title": "Deep contextualized word representations", "abstract": "We introduce a new type of deep contextualized word representation that models complex characteristics of word use and how these uses vary across linguistic contexts.", "authors": "Peters, Neumann, Iyyer"}, {"id": "2019.xlnet", "title": "XLNet: Generalized Autoregressive Pretraining", "abstract": "We propose XLNet, a generalized autoregressive pretraining method that enables learning bidirectional contexts by maximizing the expected likelihood over all permutations.", "authors": "Yang, Dai, Yang, Carbonell"}, # Reinforcement Learning {"id": "2015.dqn", "title": "Human-level control through deep reinforcement learning", "abstract": "We develop a novel agent, the deep Q-network, that learns successful policies directly from high-dimensional sensory input using end-to-end reinforcement learning.", "authors": "Mnih, Kavukcuoglu, Silver"}, {"id": "2017.ppo", "title": "Proximal Policy Optimization Algorithms", "abstract": "We propose a new family of policy gradient methods for reinforcement learning, alternating between sampling data and optimizing a surrogate objective function.", "authors": "Schulman, Wolski, Dhariwal"}, # Computer Vision {"id": "2015.resnet", "title": "Deep Residual Learning for Image Recognition", "abstract": "We present a residual learning framework to ease the training of networks that are substantially deeper than those used previously.", "authors": "He, Zhang, Ren, Sun"}, {"id": "2020.vit", "title": "An Image is Worth 16x16 Words: Transformers for Image Recognition", "abstract": "We show that a pure transformer applied directly to sequences of image patches can perform very well on image classification tasks.", "authors": "Dosovitskiy, Beyer, Kolesnikov"}, # Generative Models & Optimization {"id": "2014.gan", "title": "Generative Adversarial Networks", "abstract": "We propose a framework for estimating generative models via an adversarial process, training a generative model G and a discriminative model D simultaneously.", "authors": "Goodfellow, Pouget-Abadie, Mirza"}, {"id": "2014.adam", "title": "Adam: A Method for Stochastic Optimization", "abstract": "We introduce Adam, an algorithm for first-order gradient-based optimization of stochastic objective functions, based on adaptive estimates of lower-order moments.", "authors": "Kingma, Ba"}, ]
Каждая статья — это словарь с id, title, abstract и authors. Именно эти поля использует arXiv Sanity.
def create_corpus(papers): """Создаём корпус: title + abstract + authors для каждой статьи.""" return [f"{p['title']} {p['abstract']} {p['authors']}" for p in papers] def build_tfidf_matrix(corpus): """Строим TF-IDF матрицу с параметрами как в arXiv Sanity.""" vectorizer = TfidfVectorizer( lowercase=True, stop_words='english', # убираем "the", "a", "is" ngram_range=(1, 2), # слова и пары слов max_features=5000, # топ-5000 признаков min_df=1, # минимум 1 документ max_df=0.9, # максимум 90% документов sublinear_tf=True, # 1 + log(tf) вместо tf norm='l2', # нормализация векторов ) tfidf_matrix = vectorizer.fit_transform(corpus) return vectorizer, tfidf_matrix
Ключевые параметры:
ngram_range=(1, 2) — учитываем не только слова, но и пары ("neural network", "attention mechanism")
sublinear_tf=True — если слово встретилось 10 раз или 100 раз, разница не так важна
max_df=0.9 — слова, которые есть в 90%+ документов, неинформативны
Начнём с простого. Cosine similarity измеряет угол между двумя векторами. Чем меньше угол — тем более похожи документы.
def recommend_by_cosine(query_idx, tfidf_matrix, papers, top_n=5): """Находим статьи, похожие на заданную, через cosine similarity.""" query_vector = tfidf_matrix[query_idx] similarities = cosine_similarity(query_vector, tfidf_matrix).flatten() # Сортируем по убыванию, исключаем саму статью ranked_indices = np.argsort(-similarities) results = [] for idx in ranked_indices: if idx != query_idx: results.append({ 'paper': papers[idx], 'score': similarities[idx] }) if len(results) >= top_n: break return results
Пример использования:
corpus = create_corpus(PAPERS) vectorizer, tfidf_matrix = build_tfidf_matrix(corpus) # Найдём статьи, похожие на "Attention Is All You Need" results = recommend_by_cosine(0, tfidf_matrix, PAPERS, top_n=5) for r in results: print(f"[{r['score']:.3f}] {r['paper']['title']}")
Вывод:
[0.053] Generative Adversarial Networks [0.045] Adam: A Method for Stochastic Optimization [0.039] Deep contextualized word representations [0.039] Proximal Policy Optimization Algorithms [0.017] An Image is Worth 16x16 Words: Transformers for Image Recognition
Scores низкие — датасет маленький и статьи разнородные. В реальном корпусе из тысяч статей похожие документы будут намного ближе друг к другу. Но даже здесь видно: baseline работает. Переходим к SVM.
А вот тут начинается самое интересное. Вместо поиска похожих документов мы обучаем классификатор: "что нравится пользователю?"
def recommend_by_svm(liked_indices, tfidf_matrix, papers, top_n=5, C=0.01): """SVM-based рекомендации, как в arXiv Sanity.""" n_papers = tfidf_matrix.shape[0] # Binary labels: 1 = понравилось, 0 = остальные y = np.zeros(n_papers, dtype=np.float32) for idx in liked_indices: y[idx] = 1.0 # Обучаем LinearSVC clf = LinearSVC( class_weight='balanced', # балансируем классы max_iter=10000, tol=1e-6, C=C, # регуляризация ) clf.fit(tfidf_matrix, y) # Ранжируем по decision_function scores = clf.decision_function(tfidf_matrix) ranked_indices = np.argsort(-scores) # Возвращаем топ-N (исключая liked) results = [] for idx in ranked_indices: if idx not in liked_indices: results.append({ 'paper': papers[idx], 'score': scores[idx] }) if len(results) >= top_n: break return results, clf
Важные моменты:
C=0.01 — сильная регуляризация. Это критично, потому что у нас 5-10 положительных примеров против тысяч отрицательных. Без регуляризации модель переобучится.
class_weight='balanced' — автоматически увеличивает вес редкого класса (положительных примеров).
decision_function — не вероятность, а "уверенность" модели. Чем выше score, тем больше статья похожа на понравившиеся.
Пример:
# Пользователю понравились статьи про трансформеры liked = [0, 1] # "Attention Is All You Need", "BERT" results, clf = recommend_by_svm(liked, tfidf_matrix, PAPERS, top_n=5) for r in results: print(f"[{r['score']:.2f}] {r['paper']['title']}")
Вывод:
[-0.01] Deep contextualized word representations [-0.01] XLNet: Generalized Autoregressive Pretraining [-0.01] An Image is Worth 16x16 Words [-0.01] Generative Adversarial Networks [-0.01] Adam: A Method for Stochastic Optimization
На маленьком демо-датасете (10 статей) scores близки к нулю — модели банально не хватает данных для уверенного разделения. Но порядок правильный: NLP-статьи (ELMo, XLNet) выше, чем RL и CV. На реальном корпусе из 30 000 статей разброс scores будет от -1 до +1.
Одно из главных преимуществ линейных моделей — интерпретируемость. Можно посмотреть, какие слова SVM считает "позитивными" (указывают на интерес) и "негативными" (указывают на НЕ-интерес).
def get_top_features(clf, vectorizer, top_n=10): """Топ позитивных и негативных признаков SVM.""" feature_names = np.array(vectorizer.get_feature_names_out()) weights = clf.coef_[0] # Топ позитивных top_pos = np.argsort(-weights)[:top_n] positive = [(feature_names[i], weights[i]) for i in top_pos] # Топ негативных top_neg = np.argsort(weights)[:top_n] negative = [(feature_names[i], weights[i]) for i in top_neg] return positive, negative
Для примера с трансформерами:
positive, negative = get_top_features(clf, vectorizer, top_n=5) print("Позитивные (указывают на интерес):") for word, weight in positive: print(f" + {word}: {weight:.3f}") print("\nНегативные (указывают на НЕ-интерес):") for word, weight in negative: print(f" - {word}: {weight:.3f}")
Вывод:
Позитивные (указывают на интерес): + attention: 0.012 + bert: 0.011 + bidirectional: 0.009 + pre: 0.008 + deep bidirectional: 0.007 Негативные (указывают на НЕ-интерес): - reinforcement: -0.008 - learning: -0.007 - image: -0.006 - optimization: -0.005 - generative: -0.004
SVM буквально говорит: "Этому пользователю интересны attention и bidirectional (трансформеры), не интересен reinforcement learning и image (CV)". Веса небольшие из-за размера датасета, но направление верное. Это не чёрный ящик — логика модели как на ладони.
""" Рекомендательная система для научных статей. По мотивам arXiv Sanity Lite (Andrej Karpathy). Требования: Python 3.8+, scikit-learn>=1.0, numpy """ import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.svm import LinearSVC from sklearn.metrics.pairwise import cosine_similarity # === ДАТАСЕТ (10 статей для демонстрации) === PAPERS = [ # NLP / Transformers {"id": "2017.vaswani", "title": "Attention Is All You Need", "abstract": "The dominant sequence transduction models are based on complex recurrent or convolutional neural networks. We propose a new simple network architecture, the Transformer, based solely on attention mechanisms.", "authors": "Vaswani, Shazeer, Parmar"}, {"id": "2018.bert", "title": "BERT: Pre-training of Deep Bidirectional Transformers", "abstract": "We introduce BERT, designed to pre-train deep bidirectional representations from unlabeled text by jointly conditioning on both left and right context.", "authors": "Devlin, Chang, Lee, Toutanova"}, {"id": "2018.elmo", "title": "Deep contextualized word representations", "abstract": "We introduce a new type of deep contextualized word representation that models complex characteristics of word use and how these uses vary across linguistic contexts.", "authors": "Peters, Neumann, Iyyer"}, {"id": "2019.xlnet", "title": "XLNet: Generalized Autoregressive Pretraining", "abstract": "We propose XLNet, a generalized autoregressive pretraining method that enables learning bidirectional contexts by maximizing the expected likelihood over all permutations.", "authors": "Yang, Dai, Yang, Carbonell"}, # Reinforcement Learning {"id": "2015.dqn", "title": "Human-level control through deep reinforcement learning", "abstract": "We develop a novel agent, the deep Q-network, that learns successful policies directly from high-dimensional sensory input using end-to-end reinforcement learning.", "authors": "Mnih, Kavukcuoglu, Silver"}, {"id": "2017.ppo", "title": "Proximal Policy Optimization Algorithms", "abstract": "We propose a new family of policy gradient methods for reinforcement learning, alternating between sampling data and optimizing a surrogate objective function.", "authors": "Schulman, Wolski, Dhariwal"}, # Computer Vision {"id": "2015.resnet", "title": "Deep Residual Learning for Image Recognition", "abstract": "We present a residual learning framework to ease the training of networks that are substantially deeper than those used previously.", "authors": "He, Zhang, Ren, Sun"}, {"id": "2020.vit", "title": "An Image is Worth 16x16 Words: Transformers for Image Recognition", "abstract": "We show that a pure transformer applied directly to sequences of image patches can perform very well on image classification tasks.", "authors": "Dosovitskiy, Beyer, Kolesnikov"}, # Generative Models & Optimization {"id": "2014.gan", "title": "Generative Adversarial Networks", "abstract": "We propose a framework for estimating generative models via an adversarial process, training a generative model G and a discriminative model D simultaneously.", "authors": "Goodfellow, Pouget-Abadie, Mirza"}, {"id": "2014.adam", "title": "Adam: A Method for Stochastic Optimization", "abstract": "We introduce Adam, an algorithm for first-order gradient-based optimization of stochastic objective functions, based on adaptive estimates of lower-order moments.", "authors": "Kingma, Ba"}, ] def create_corpus(papers): """Создаём корпус: title + abstract + authors для каждой статьи.""" return [f"{p['title']} {p['abstract']} {p['authors']}" for p in papers] def build_tfidf_matrix(corpus): """Строим TF-IDF матрицу с параметрами как в arXiv Sanity.""" vectorizer = TfidfVectorizer( lowercase=True, stop_words='english', ngram_range=(1, 2), max_features=5000, min_df=1, max_df=0.9, sublinear_tf=True, norm='l2', ) return vectorizer, vectorizer.fit_transform(corpus) def recommend_by_cosine(query_idx, tfidf_matrix, papers, top_n=5): """Находим статьи, похожие на заданную, через cosine similarity.""" similarities = cosine_similarity(tfidf_matrix[query_idx], tfidf_matrix).flatten() ranked = np.argsort(-similarities) return [{'paper': papers[i], 'score': similarities[i]} for i in ranked if i != query_idx][:top_n] def recommend_by_svm(liked_indices, tfidf_matrix, papers, top_n=5, C=0.01): """SVM-based рекомендации: обучаем классификатор на лайках пользователя.""" n = tfidf_matrix.shape[0] y = np.zeros(n, dtype=np.float32) for idx in liked_indices: y[idx] = 1.0 clf = LinearSVC(class_weight='balanced', max_iter=10000, tol=1e-6, C=C) clf.fit(tfidf_matrix, y) scores = clf.decision_function(tfidf_matrix) ranked = np.argsort(-scores) results = [{'paper': papers[i], 'score': scores[i]} for i in ranked if i not in liked_indices][:top_n] return results, clf def get_top_features(clf, vectorizer, top_n=5): """Топ позитивных и негативных признаков SVM.""" names = np.array(vectorizer.get_feature_names_out()) w = clf.coef_[0] pos = [(names[i], w[i]) for i in np.argsort(-w)[:top_n]] neg = [(names[i], w[i]) for i in np.argsort(w)[:top_n]] return pos, neg if __name__ == "__main__": corpus = create_corpus(PAPERS) vectorizer, tfidf = build_tfidf_matrix(corpus) print("=== Cosine Similarity ===") print(f"Query: {PAPERS[0]['title']}\n") for r in recommend_by_cosine(0, tfidf, PAPERS): print(f" [{r['score']:.3f}] {r['paper']['title']}") print("\n=== SVM (liked: Attention + BERT) ===") results, clf = recommend_by_svm([0, 1], tfidf, PAPERS) for r in results: print(f" [{r['score']:.2f}] {r['paper']['title']}") print("\n=== Top Features ===") pos, neg = get_top_features(clf, vectorizer) print("Positive:", [w for w, _ in pos]) print("Negative:", [w for w, _ in neg])
Производительность
На стандартном оборудовании (2 CPU, 4GB RAM) для корпуса из 10 000 научных статей:
|
Операция |
Время |
Память |
|---|---|---|
|
TF-IDF векторизация |
200-300 мс |
~200 МБ |
|
LinearSVC обучение (50 лайков) |
20-50 мс |
~50 МБ |
|
Prediction (100 документов) |
5 мс |
— |
|
Итого на запрос |
~300 мс |
~250 МБ |
Масштабирование:
100K документов: добавляет ~2 сек на TF-IDF векторизацию
1M документов: требует специализированного сервера или batch-обработки
Оптимизация: TF-IDF матрицу можно вычислить один раз и кэшировать. Тогда на каждый запрос уходит только время обучения SVM (~50 мс).
Этот подход отлично работает в следующих случаях:
1. Научные и технические тексты. Терминология в таких текстах однозначна: "transformer", "attention mechanism", "reinforcement learning" — это конкретные понятия. TF-IDF ловит их идеально.
2. Достаточный объём текста. Если есть title + abstract (100-300 слов), статистики хватит. Для коротких текстов (только заголовки) лучше взять эмбеддинги.
3. Мало положительных примеров. 5-50 лайков — идеальный диапазон для SVM. При тысячах лайков стоит смотреть на другие методы.
4. Нужна интерпретируемость. Можно посмотреть, какие слова SVM считает важными. Никакого чёрного ящика.
5. Ограниченный бюджет. Никаких GPU, никаких API-ключей, никаких ежемесячных счетов. Только CPU и несколько мегабайт памяти.
6. Offline-система. Всё работает локально, без интернета, без зависимости от внешних сервисов.
Когда НЕ использовать SVM + TF-IDF
Этот подход — не серебряная пуля. Вот когда стоит посмотреть на альтернативы:
1. Нужен семантический поиск. Если пользователь ищет "papers about making neural networks smaller" — TF-IDF не поймёт, что речь идёт о pruning, quantization и knowledge distillation. Тут нужны эмбеддинги.
2. Мало текста. TF-IDF работает на статистике слов. Если есть только заголовки без abstract — статистики не хватит.
3. Мультиязычность. "Attention" на английском и "внимание" на русском — разные токены. Для мультиязычных корпусов нужны multilingual embeddings.
4. Динамические интересы. SVM обучается с нуля при каждом запросе. Если пользователь лайкнул 1000 статей и хочет рекомендации за миллисекунды — нужен другой подход.
Сравнение подходов
|
Подход |
Скорость |
Качество |
Стоимость |
Когда использовать |
|---|---|---|---|---|
|
TF-IDF + SVM |
Мгновенно |
Хорошее для term-based |
$5/мес |
Рекомендации по ключевым словам, небольшие корпуса |
|
Sentence Embeddings |
Быстро |
Отличное |
$50-200/мес |
Семантический поиск, когда слова не совпадают |
|
LLM (GPT-4, Claude) |
Медленно |
Лучшее |
$0.01-0.10 за запрос |
Сложные запросы, нужны объяснения |
|
Гибрид (SVM + LLM) |
Средне |
Лучшее |
$0.005 за запрос |
Production-системы с высокими требованиями |
Karpathy в arXiv Sanity использует чистый SVM + TF-IDF. SurveyAgent добавляет LLM-фильтрацию поверх. Выбор зависит от ваших требований и бюджета.
Так почему Karpathy использует SVM в 2026 году?
Потому что для его задачи — персонализированные рекомендации научных статей — это оптимальное решение:
Работает на дешёвом VPS без GPU
Обучается за миллисекунды
Даёт интерпретируемые результаты
Не требует API-ключей и внешних сервисов
Мораль простая: не всегда нужен трансформер. Иногда старый добрый SVM решает задачу лучше, быстрее и дешевле.
Попробуйте код из этой статьи на своих данных. Адаптируйте под свои задачи. И помните: лучшая модель — та, которая решает вашу проблему, а не та, которая набрала больше всего звёзд на GitHub.
arXiv Sanity Lite — облегчённая версия проекта Karpathy
arXiv Sanity Preserver — оригинальный проект 2016 года
arxiv-sanity-lite.com — рабочий инстанс (часто не открывается из-за отсутствия поддержки)
SurveyAgent (arXiv:2404.06364) — академическая статья, использующая этот подход
scikit-learn TfidfVectorizer — документация
scikit-learn LinearSVC — документация
Источник


