Ваши вкусы — это данные. Давайте их взломаем
Вы смотрите фильм "Бегущий по лезвию". Слушаете Radiohead. Читаете "Норвежский лес". Что это говорит о вас? Традиционно — ничего. Это просто список. Но если превратить эти названия в числа, а числа — в закономерности, картина меняется.
Эмбеддинги больших языковых моделей (LLM) — это не только про генерацию текста. Это про упаковку смысла в вектор. Фильм, книга, трек — это не просто название. Это сотни измерений настроения, стиля, нарратива. И эти измерения можно сравнить, сгруппировать, проанализировать.
Почему ваши рекомендации в стримингах такие тупые
Netflix советует вам фильмы потому, что другие люди, смотревшие "Игру в кальмара", тоже смотрели "Первому игроку приготовиться". Это коллаборативная фильтрация. Она работает, но примитивно.
Эмбеддинги LLM работают иначе. Они анализируют семантику. Модель читает описание фильма, рецензии, сюжет (если дать доступ) и создает его "отпечаток". Два фильма с разными актерами и бюджетом, но с одинаковой атмосферой безысходности и моральной двусмысленности окажутся рядом в векторном пространстве. Именно так можно найти то, что вы ищете, но не знаете, как сформулировать.
1 Соберите свой культурный след
Начните с данных. Экспортируйте списки из Letterboxd, Goodreads, Last.fm или просто составьте вручную в CSV. Формат простой: тип (movie/book/track), название, автор/режиссер/исполнитель. Можно добавить год.
import pandas as pd
# Пример данных
data = [
{"type": "movie", "title": "Blade Runner 2049", "creator": "Denis Villeneuve", "year": 2017},
{"type": "book", "title": "Neuromancer", "creator": "William Gibson", "year": 1984},
{"type": "track", "title": "Everything in Its Right Place", "creator": "Radiohead", "year": 2000},
# ... еще сотни записей
]
df = pd.DataFrame(data)
print(df.head())
Не пытайтесь собрать идеальный датасет. Начните с 50-100 объектов. Качество эмбеддингов от OpenAI или даже открытых моделей (например, через Sentence Transformers) настолько высоко, что даже по названию и автору модель уловит много смысла. Хотя, конечно, полное описание было бы лучше. Если хотите глубоко проработать тексты книг, посмотрите мой гайд про тренировку LLM на EPUB.
2 Превратите названия в векторы
Здесь два пути: платный API (OpenAI, Cohere) или бесплатные локальные модели. Для начала используйте OpenAI — это просто и эффективно. Создайте промпт, который описывает контекст.
import openai
from tenacity import retry, stop_after_attempt, wait_exponential
openai.api_key = 'your-key'
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def get_embedding(text, model="text-embedding-3-small"):
text = text.replace("\n", " ")
response = openai.embeddings.create(input=[text], model=model)
return response.data[0].embedding
# Создаем текстовое описание для эмбеддинга
df['text_for_embedding'] = df.apply(
lambda row: f"{row['title']} by {row['creator']}. Type: {row['type']}. Year: {row['year']}.", axis=1
)
# Генерируем эмбеддинги (осторожно, это может стоить денег при больших объемах)
# Рекомендую кэшировать результаты!
df['embedding'] = df['text_for_embedding'].apply(lambda x: get_embedding(x))
Почему мы добавляем тип и год? Чтобы модель знала контекст. Вектор для "Blade Runner" (фильм) будет отличаться от вектора "Blade Runner" (книга). Год помогает отделить оригинал от ремейка.
3 Упростите пространство и найдите кластеры
Эмбеддинги имеют 1536 измерений (для text-embedding-3-small). Чтобы это визуализировать, нужно уменьшить размерность. UMAP справляется с этим лучше, чем PCA, особенно для нелинейных данных.
import umap
import numpy as np
from sklearn.cluster import KMeans, DBSCAN
from sklearn.preprocessing import StandardScaler
# Преобразуем список эмбеддингов в массив
embeddings_array = np.vstack(df['embedding'].values)
# Нормализуем (важно для многих алгоритмов кластеризации)
scaler = StandardScaler()
embeddings_scaled = scaler.fit_transform(embeddings_array)
# Уменьшаем размерность до 2D для визуализации
reducer = umap.UMAP(n_components=2, random_state=42, n_neighbors=15, min_dist=0.1)
embeddings_2d = reducer.fit_transform(embeddings_scaled)
df['x'] = embeddings_2d[:, 0]
df['y'] = embeddings_2d[:, 1]
# Кластеризация K-Means (заранее задаем число кластеров)
kmeans = KMeans(n_clusters=5, random_state=42, n_init='auto')
df['cluster_kmeans'] = kmeans.fit_predict(embeddings_scaled)
# Или DBSCAN (находит кластеры автоматически, но сложнее настроить)
# dbscan = DBSCAN(eps=0.5, min_samples=3)
# df['cluster_dbscan'] = dbscan.fit_predict(embeddings_scaled)
4 Визуализируйте и интерпретируйте
Разбросайте точки по графику, раскрасьте по кластерам. Используйте Plotly для интерактивности — чтобы наводить курсор и видеть названия.
import plotly.express as px
fig = px.scatter(
df, x='x', y='y', color='cluster_kmeans',
hover_data=['title', 'creator', 'type'],
title='Ваша культурная вселенная в 2D',
width=800, height=600
)
fig.update_traces(marker=dict(size=10))
fig.show()
# Сохраняем для веба
fig.write_html("my_culture_map.html")
Теперь самое интересное — интерпретация. Посмотрите, что попало в один кластер. Вы увидите связи, которые не очевидны. Может, у вас есть кластер "ностальгическая меланхолия", где собраны indie-игры, пост-панк и японские мультфильмы 90-х. Или кластер "грандиозный пессимизм" с эпическим фэнтези и дроун-металом.
Где всё ломается: главные подводные камни
Теория гладкая. Практика — колючая. Вот что может пойти не так.
| Проблема | Почему возникает | Как исправить |
|---|---|---|
| Все точки сбились в кучу в центре графика | Параметр min_dist в UMAP слишком мал или эмбеддинги плохо нормализованы. |
Увеличьте min_dist до 0.5-1.0. Используйте StandardScaler перед UMAP. |
| Кластеры не соответствуют интуиции | Модель эмбеддингов уловила не те признаки (например, слишком много внимания году выпуска). | Измените промпт для эмбеддинга: уберите год, добавьте жанр или ключевые слова. Или попробуйте другую модель эмбеддингов. |
| Фильмы и книги не смешиваются | Модель слишком сильно разделяет типы медиа. | В промпте не акцентируйте тип (movie/book/track). Или кластеризуйте каждый тип отдельно, а потом сравните. |
Самая частая ошибка — ожидать, что алгоритм сам все поймет. Это не искусственный интеллект, это инструмент. Вы настраиваете его, чтобы он отражал ваше восприятие. Если кластер "драма и хеви-метал" кажется вам абсурдным, но модель упорно их объединяет — возможно, вы просто не замечали их общей агрессии и безысходности.
Не используйте этот метод как замену саморефлексии. Это зеркало, а не оракул. Если вы ненавидите классическую музыку, но алгоритм помещает вас в кластер с любителями Баха — может, стоит послушать Баха? Или пересмотреть промпт. Скорее второе.
Что дальше? За пределами кластеров
Кластеризация — только начало. С эмбеддингами можно:
- Строить рекомендации: находить ближайших соседей для любимого фильма не по жанру, а по вектору. Это даст более точные советы, чем любой стриминг. Технически это поиск ближайших соседей в векторном пространстве — тема, которую я детально разбирал в статье про гибридный поиск для RAG.
- Анализировать эволюцию вкусов: разбросать свои любимые альбомы по годам прослушивания и посмотреть, как ваш векторный вкус двигался в пространстве.
- Сравниваться с другими: вычислить "центроид" ваших вкусов (средний вектор) и сравнить его с центроидом друга. Косинусное сходство покажет, насколько вы близки культурно. (Спойлер: вы скорее всего не близки.)
Главный секрет: эмбеддинги LLM — это не про технологии. Это про новый язык описания культуры. Жанры умирают. На смену приходят многомерные паттерны, которые нельзя назвать словами, но можно увидеть на графике.
Следующий шаг — добавить временную ось. Как менялись ваши кластеры с 15 до 35 лет? Это уже не анализ вкусов, это анализ личности. И здесь уже можно подключить более сложные пайплайны, о которых я писал в гайде по семантическим пайплайнам.
Попробуйте. Создайте карту своего культурного ландшафта. Увидите себя со стороны. И, возможно, следующий фильм, который вы посмотрите, будет выбран не из "похожих", а из "векторно-близких". Это другой уровень.
Вопросы, которые вы хотели задать, но боялись
Это работает с любыми языками?
Да, современные мультиязычные модели эмбеддингов (как от OpenAI) хорошо работают с русскими, английскими и другими названиями. Но если у вас много контента на редком языке, возможно, стоит поискать специализированную модель или дообучить свою.
Сколько это стоит?
Использование OpenAI Embeddings для 1000 объектов обойдется примерно в $0.10-$0.50 в зависимости от модели. Локальные модели (например, через библиотеку sentence-transformers) — бесплатно, но требуют вычислительных ресурсов и немного больше кода.
Можно ли кластеризовать не по названиям, а по описаниям или рецензиям?
Конечно. Это даже лучше. Чем больше текстовой информации вы дадите модели, тем точнее будет эмбеддинг. Вы можете скрейпить описания с Kinopoisk или Goodreads, но проверяйте условия использования. Или используйте API, если есть.
А если я хочу проанализировать только музыку или только книги?
Принцип тот же. Более того, для музыки можно использовать аудио-эмбеддинги (например, от моделей вроде CLAP), но это сложнее. Для старта хватит текстовых описаний треков и альбомов. Кстати, у меня есть отдельная статья про анализ плейлистов ИИ, где этот подход разобран детально.