Почему семантика убивает стиль (и как это исправить)
Вы ищете соавтора для проекта. Нашли человека, который пишет на нужную тему. Открываете его текст — и понимаете: стиль не ваш. Слишком академично. Или слишком разговорно. Или слишком... не ваше.
Проблема в том, что все современные поисковые системы ищут по смыслу. Они понимают, что вы пишете о DevOps, но не понимают, как вы это делаете. Короткими рублеными фразами? Длинными философскими рассуждениями? С техническим сленгом или академическим языком?
Стиль — это не про «что». Это про «как». И вот здесь эмбеддинги показывают свою настоящую силу.
Стилевые эмбеддинги игнорируют содержание. Они смотрят на структуру предложений, выбор слов, пунктуационные привычки, ритм текста. Два автора могут писать о разных вещах, но иметь идентичный стиль письма.
Что такое стилевой эмбеддинг (и чем он отличается от обычного)
Представьте стандартный текстовый эмбеддинг. Это вектор, который кодирует смысл. «Кошка» и «собака» будут ближе, чем «кошка» и «программирование».
Стилевой эмбеддинг работает иначе. Он смотрит на:
- Среднюю длину предложения
- Соотношение существительных и глаголов
- Частоту использования определенных союзов
- Лексическое разнообразие (сколько уникальных слов на 1000 символов)
- Использование пассивного залога
- Пунктуационные паттерны (любите ли вы тире, точки с запятой, многоточия)
Два текста про кошек и про микросервисы могут иметь одинаковые стилевые эмбеддинги, если их авторы пишут похоже.
Готовые инструменты: что работает прямо сейчас
1 FastText с стилевыми признаками
Самый простой способ — дообучить FastText на ваших текстах. Но не на содержании, а на стилевых метках.
Как НЕ надо делать:
# ПЛОХО: так вы получите семантические эмбеддинги
from gensim.models import FastText
model = FastText(sentences=all_texts, vector_size=100)Как надо делать:
# ХОРОШО: создаем стилевые признаки
import re
from collections import Counter
def extract_style_features(text):
features = []
sentences = re.split(r'[.!?]+', text)
# Средняя длина предложения
avg_sentence_len = sum(len(s.split()) for s in sentences) / len(sentences)
features.append(f"SENT_LEN_{int(avg_sentence_len)}")
# Соотношение существительных к глаголам (упрощенно)
nouns = len([w for w in text.split() if w.endswith(('ость', 'ие', 'ация'))])
verbs = len([w for w in text.split() if w.endswith(('ть', 'ться', 'ил', 'ал'))])
ratio = nouns / max(verbs, 1)
features.append(f"NOUN_VERB_{int(ratio*10)}")
# Любимая пунктуация
punct = Counter(c for c in text if c in ',;:-—')
most_common = punct.most_common(1)[0] if punct else ('none', 0)
features.append(f"PUNCT_{most_common[0]}")
return features
# Собираем стилевые "слова" для FastText
style_corpus = []
for text in all_texts:
style_features = extract_style_features(text)
style_corpus.append(style_features)
# Обучаем на стилевых признаках
model = FastText(sentences=style_corpus, vector_size=50)2 Sentence-BERT с fine-tuning на стиле
Более продвинутый вариант — взять Sentence-BERT и дообучить его различать стили.
Собираете пары текстов:
- Положительные пары: тексты одного автора (даже на разные темы)
- Отрицательные пары: тексты разных авторов (даже на одну тему)
from sentence_transformers import SentenceTransformer, losses, InputExample
from torch.utils.data import DataLoader
import torch
# Берем предобученную модель
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# Готовим данные для обучения
# Каждая пара - два текста и метка (1 - один стиль, 0 - разные стили)
train_examples = []
for author_id, texts in author_texts.items():
# Положительные пары: случайные тексты одного автора
for i in range(len(texts)):
for j in range(i+1, min(i+3, len(texts))):
train_examples.append(InputExample(
texts=[texts[i], texts[j]],
label=1.0 # одинаковый стиль
))
# Отрицательные пары: тексты этого автора и случайного другого
other_authors = [aid for aid in author_texts.keys() if aid != author_id]
for other_id in other_authors[:2]:
other_text = author_texts[other_id][0]
train_examples.append(InputExample(
texts=[texts[0], other_text],
label=0.0 # разные стили
))
# Обучаем с контрастивной потерей
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)
model.fit(
train_objectives=[(train_dataloader, train_loss)],
epochs=3,
warmup_steps=100
)Важно: не используйте слишком много отрицательных пар. Если взять все возможные комбинации, модель начнет переобучаться. Достаточно 2-3 отрицательных примеров на каждого автора.
Как искать авторов с похожим стилем
Допустим, у вас есть ваш текст. Вы хотите найти авторов, которые пишут похоже.
Шаг 1: Собираете корпус текстов потенциальных коллабораторов. Блоги, статьи на Хабре, посты в телеграме.
Шаг 2: Генерируете стилевые эмбеддинги для всех текстов (включая ваш).
Шаг 3: Ищете ближайших соседей в векторном пространстве.
import numpy as np
from sklearn.neighbors import NearestNeighbors
# Ваш текст
your_text = "Ваш текст здесь"
your_embedding = model.encode([your_text])[0]
# Все тексты потенциальных авторов
all_texts = [...] # список текстов
all_embeddings = model.encode(all_texts)
# Ищем 10 ближайших соседей
nn = NearestNeighbors(n_neighbors=10, metric='cosine')
nn.fit(all_embeddings)
distances, indices = nn.kneighbors([your_embedding])
print("Ближайшие по стилю авторы:")
for i, (dist, idx) in enumerate(zip(distances[0], indices[0])):
print(f"{i+1}. Текст #{idx}, расстояние: {dist:.3f}")Но здесь есть проблема: близкий текст может быть близким по стилю, но его автор может быть недоступен для коллаборации.
Решение: кластеризуем авторов, а не отдельные тексты.
3 Кластеризация авторов по стилю
Берем несколько текстов каждого автора, усредняем их эмбеддинги, получаем «стилевой профиль» автора.
from sklearn.cluster import DBSCAN
import pandas as pd
# Словарь: автор -> список его текстов
author_to_texts = {
"author1": ["текст1", "текст2", ...],
"author2": ["текст3", "текст4", ...],
# ...
}
# Считаем средний эмбеддинг для каждого автора
author_embeddings = {}
for author, texts in author_to_texts.items():
embeddings = model.encode(texts)
avg_embedding = np.mean(embeddings, axis=0)
author_embeddings[author] = avg_embedding
# Кластеризуем авторов
embeddings_matrix = np.array(list(author_embeddings.values()))
clustering = DBSCAN(eps=0.3, min_samples=2, metric='cosine').fit(embeddings_matrix)
# Смотрим, кто в одном кластере с вами
authors = list(author_embeddings.keys())
your_index = authors.index("ваш_авторский_ник")
your_cluster = clustering.labels_[your_index]
print(f"Ваш кластер: {your_cluster}")
print(f"Авторы в вашем кластере:")
for i, label in enumerate(clustering.labels_):
if label == your_cluster and i != your_index:
print(f"- {authors[i]}")Теперь у вас есть список авторов, которые пишут в похожем стиле. Можно писать им предложения о коллаборации.
Четыре ошибки, которые все совершают
| Ошибка | Почему это проблема | Как исправить |
|---|---|---|
| Слишком короткие тексты | Стиль проявляется на 500+ словах. Короткие твиты не дают статистики | Используйте тексты от 1000 символов. Или объединяйте несколько постов одного автора |
| Смешивание жанров | Техническая документация и личный блог — разный стиль даже у одного автора | Обучайте отдельные модели для каждого жанра. Или добавляйте жанр как признак |
| Игнорирование темы | Специфическая лексика (например, DevOps термины) влияет на статистику | Используйте лемматизацию или удаляйте доменные слова перед анализом |
| Только косинусная близость | Разные стили могут быть равноудалены от вашего, но в разных направлениях | Смотрите на распределение расстояний. Используйте t-SNE для визуализации |
Практический кейс: поиск соавтора для технического блога
У меня есть DevOps-блог. Я пишу короткими предложениями, с минимумом прилагательных, с примерами кода в каждом посте. Хочу найти соавтора, который пишет так же.
Что я делаю:
- Собираю 50 своих постов (это мой «стилевой эталон»)
- Скачиваю 1000 постов с DevOps-тематики с Хабра и Medium
- Обучаю Sentence-BERT на парах: мои посты vs чужие посты
- Кластеризую всех авторов
- Нахожу 3-х авторов в моем кластере
- Пишу им: «Привет, я заметил, что мы пишем в очень похожем стиле. Хочешь сделать совместный пост?»
Результат: из 3-х писем — 2 положительных ответа. Один автор сказал: «Да, я тоже заметил, что мы используем похожие конструкции. Думал, это только мне кажется».
Магия? Нет. Просто математика.
Что делать, если нет своих текстов для обучения
Используйте предобученные модели с вниманием к стилевым признакам.
Вариант 1: Анализируйте внутренние представления моделей. Возьмите Llama или Qwen, подайте текст и посмотрите на активации определенных слоев. Стилевая информация часто кодируется в средних слоях.
Вариант 2: Используйте EmergentFlow или подобные инструменты для быстрого прототипирования. Соберите пайплайн: текст -> извлечение стилевых признаков -> сравнение.
Вариант 3: Генерируйте синтетические тексты в разных стилях. Как в методе DocuLite для финансовых данных, но для стилей. Попросите ChatGPT написать один и тот же контент в 10 разных стилях. Используйте эти пары для обучения.
Стиль vs качество: важное различие
Стилевые эмбеддинги не измеряют качество письма. Плохо написанный текст и хорошо написанный текст могут иметь одинаковый стиль.
Если вы ищете не просто похожий стиль, а еще и качество — добавляйте второй этап фильтрации.
- Сначала находите авторов с похожим стилем
- Потом оцениваете качество их текстов (читаемость, грамотность, структура)
- Только потом предлагаете коллаборацию
Для оценки качества можно использовать те же LLM. Попросите GPT-4 оценить текст по шкале от 1 до 10 по критериям: ясность, структура, аргументация.
Этический момент: стиль как отпечаток пальцев
Стиль письма — это почти как биометрические данные. Уникальный. Узнаваемый.
Когда вы анализируете чужой стиль без разрешения — это серый этический район. Особенно если вы используете это для коммерческих целей.
Мое правило: если вы собираетесь связаться с автором — анализируйте. Если нет — не храните его стилевые эмбеддинги. Или хотя бы анонимизируйте данные.
Технически можно определить автора по стилю с точностью до 85-95%. Это мощно. И немного страшно.
Что будет дальше: стилевые рынки и коллаборационные платформы
Через год-два появятся платформы, где авторы будут регистрироваться со своими стилевыми эмбеддингами.
Вы загружаете свой текст. Система находит 10 авторов с максимально похожим стилем. Показывает их рейтинг, темы, на которых они специализируются. Вы выбираете одного, система предлагает шаблон письма для коллаборации.
Или еще интереснее: стилевые рекомендательные системы. «Вам понравился автор X. Вот авторы Y и Z, которые пишут в похожем стиле».
Это уже происходит с фильмами и музыкой. Почему бы не с текстами?
Следующий шаг — стилевая трансфер. «Напиши этот текст в стиле автора X». Но это уже другая история.
А пока — берите код выше, адаптируйте под свои нужды. Ищите своих стилевых двойников. Пишите им.
Самый неочевидный совет в конце: иногда лучшая коллаборация — с автором, чей стиль НЕ похож на ваш. Контраст создает интерес. Но чтобы это понять, нужно сначала найти тех, кто похож. А потом — сознательно выбрать того, кто отличается.
Математика дает варианты. Выбор — за вами.