Отзывы пользователей — один из самых ценных источников информации о продукте, при этом часто клиенты описывают одну и ту же тему или проблему десятками разных слов. Раньше работать с фидбэком было долго и ресурсоемко, но с появлением Embeddings и LLM это изменилось.

Привет, Хабр! Меня зовут Вова Михайлов, я маркетинговый аналитик в Garage Eight. Мы развиваем международные финтех-продукты и стараемся использовать всю информацию для улучшения наших сервисов, в том числе отзывы.
В этой статье я расскажу, как мы смогли автоматизировать и ускорить аналитику фидбэков с помощью Embeddings и GPT. Наш опыт будет релевантен не только финтеху, но и любым компаниям, которые хотят научиться в реальном времени получать данные из отзывов и сравнивать себя с конкурентами.
Предыстория: в чём проблема
I completed the action, but nothing changed on my side.
The information I expected is missing.
Still waiting for everything to update.
The expected update hasn’t come through.
Все эти отзывы относятся к одной теме, при этом различаются по лексике и тональности. Для человека очевидно, что их связывает, а для компьютера это совершенно разные наборы слов.
В существующих приложениях, которые мы используем для сбора отзывов (YouScan, AppTweak), аналитика так и собирается по ключевым словам: найти общие темы можно, но это распределение будет некачественным. Чтобы корректно разобрать их по ключевым категориям или фичам, специалисту понадобятся недели или месяцы. Именно поэтому мы решили собрать свой инструмент и автоматизировать работу с отзывами.
Чтобы разобраться в доступных методах анализа фидбэка, поделюсь ретроспективой — как они менялись с развитием технологий:
Ручной анализ. Сначала аналитики читали фидбэк вручную и раскладывали его по категориям в Excel. Это работало на десятках и сотнях отзывов, но масштабировать дальше было очень ресурсоемко.
Опросы и NPS. Далее компании начали измерять NPS, CSAT и другие метрики удовлетворенности. Это позволило понять уровень лояльности, но не дало объяснений причин проблем.
Keyword Analytics. Потом появился поиск по ключевым словам и Word Cloud. Можно было собирать облака тегов, определять ключевые темы, но проблема была в том, что алгоритм выявлял слова, а не смысл. Отзывы на одну тему, написанные разными словами, оказывались в разных категориях.
Classical NLP. С появлением NLP стали использовать TF-IDF, LDA и Topic Modeling для автоматического поиска тем. Это усовершенствовало группировку, но всё еще не помогало справиться с ключевой проблемой.
Embeddings. Здесь произошла революция: вместо слов начали использовать многомерное векторное представление смысла. Например:
My funds are missing → [0,14, −0,92, 0,33, ...].
Deposit never arrived → [0,15, −0,90, 0,31, ...].
Благодаря этому в многомерном векторном пространстве отзывы оказываются рядом, хотя общих слов почти нет. Так мы наконец научились искать отзывы из одной темы, написанные в разных выражениях.
LLM. Большие языковые модели стали отличным дополнением: Embeddings хорошо находят группы похожих отзывов, а LLM помогают интерпретировать их и превращать в понятные бизнес-темы.
В своем решении я использовал связку инструментов:
Google Play Scraper → OpenAI Embeddings API → KMeans Clustering → GPT Naming → Meta Clustering → GPT Naming → Business Topics
То есть система сначала понимает смысл отзывов, затем группирует их по смысловой близости и в конце переводит результат на привычный нам язык. Далее расскажу, что происходит на каждом из этапов.
Используем Embeddings: level 1
Перед началом кластеризации отзывов мы обрабатываем данные. С появлением современных Embeddings большая часть этой подготовки стала необязательна: теперь модели достаточно хорошо работают самостоятельно с разными языками, опечатками, разговорными выражениями, эмодзи и сокращениями. Именно поэтому в нашем решении предобработка минимальная.
Фактически я почти не делаю классическую NLP-нормализацию: не перевожу отзывы на английский, не делаю лемматизацию, когда все слова приводятся в начальную форму, и не вычищаю агрессивно текст. Изначально мне хотелось проверить, насколько современные Embeddings смогут работать с сырыми данными из стора. Честно говоря, результат оказался лучше ожидаемого: модель достаточно хорошо группирует отзывы по смыслу, даже на смеси разных языков и с опечатками.
Итак, переходим к основной части. В основе Embeddings лежит идея представления текста в виде многомерного вектора признаков. Для этого я использую OpenAI Embeddings API, который преобразует каждый отзыв в вектор размерностью 1536 признаков.
from openai import OpenAI client = OpenAI(api_key=config.API_KEY) def get_embedding(text): response = client.embeddings.create( model="text-embedding-3-small", input=text ) return response.data[0].embedding df["embedding"] = df["Review"].apply(get_embedding)
После построения Embeddings я использую алгоритм KMeans для поиска плотных областей в семантическом пространстве. KMeans я выбрал по нескольким причинам:
это простой и хорошо изученный алгоритм;
быстро работает на десятках тысяч записей и легко масштабируется;
дает предсказуемый результат;
позволяет контролировать количество тем через параметр K.
Единственная сложность — KMeans требует заранее знать количество кластеров. Именно поэтому пришлось экспериментировать с разными значениями K и смотреть на качество получаемых тем.
Кроме KMeans, я рассматривал еще два метода:
DBSCAN/HDBSCAN. Они не требуют заранее задавать число кластеров, хорошо находят выбросы и часто используются для embedding-space. Но в моих тестах они либо давали слишком много мелких кластеров, либо сильно зависели от параметров.
Agglomerative Clustering. Он хорошо показывает иерархию тем и удобен для последующего объединения похожих кластеров. В итоге именно его я использовал на втором уровне пайплайна для формирования MetaTopic.
В результате получилось так, что KMeans отвечает за поиск локальных смыслов внутри отзывов, а Agglomerative Clustering помогает находить более крупные бизнес-темы поверх уже найденных кластеров.
Интересный момент — появление LLM сильно изменило требования к кластеризации. Раньше нужно было получить «идеальные» кластеры, потому что именно они были конечным результатом анализа. Теперь кластеризация стала промежуточным этапом.
Главное — не идеальная математическая точность, а быстрое объединение семантически похожих отзывов, которые затем LLM может качественно проанализировать, назвать темы, выделить проблемы и сформулировать выводы. Это позволяет использовать более простые и быстрые алгоритмы кластеризации, сохраняя высокое качество итоговой аналитики.
Технически кластеризация реализуется следующими строками:
import numpy as np from sklearn.cluster import KMeans X = np.vstack(df["embedding"].values) kmeans = KMeans( n_clusters=15, random_state=42, n_init="auto" ) df["ClusterID"] = kmeans.fit_predict(X)
Если на вход KMeans дать 1000 отзывов, то вместо тысячи отдельных комментариев можно получить примерно 10–20 устойчивых тематических кластеров. Например, в нашем случае могут быть такие области:
Verification
Localization
Trust & Security
Product Features
На этом этапе мы уже переходим от текста к смыслу, но сами кластеры пока остаются просто математическими группами похожих векторов.
Собираем данные с помощью GPT
Чтобы сделать результаты кластеризации понятными человеку, поверх подключается ChatGPT. Модель анализирует примеры отзывов внутри каждого кластера и формирует базу данных с названием темы, описанием проблемы, тональностью отзывов, их количеством и средним рейтингом. Дополнительно для каждого отзыва в базе данных сохраняются дата публикации, рейтинг и тональность, что позволяет анализировать не только сами темы, но и их динамику во времени.
Для обработки данных используем промпт:
prompt = f""" You are a senior product researcher. Analyze the customer reviews below. Identify the single dominant customer topic inside this cluster. Return ONLY valid JSON: {{ "cluster_name": "Topic name", "description": "Short description", "sentiment": "Positive" }} Rules: - Return exactly one topic. - Choose the dominant theme. - Sentiment must be only: Positive, Negative, Mixed. Reviews: {reviews_text} """ response = client.chat.completions.create( model=config.MODEL_TOPICS, response_format={"type": "json_object"}, messages=[ {"role": "user", "content": prompt} ], temperature=0 ) result = json.loads(response.choices[0].message.content)
Таким образом тысячи комментариев превращаются в полноценную модель Voice of Customer. Анализируя модель, мы поняли, что можем использовать ее не только на собственных отзывах, но и на отзывах конкурентов. Так мы смогли выявить их сильные и слабые стороны, а также определить наше положение на рынке с точки зрения клиентов.

Всё получилось: Embeddings определили векторы отзывов, KMeans кластеризовал их, а GPT собрал понятную БД. И тут появилась новая проблема: оказалось, что одинаковые темы могут иметь разные названия:
Need English Language
English Language Request
Lack of English Support
По сути это та же проблема, которую мы решали с помощью векторов: бизнес понимает, что это одна сущность, а модель — нет. Именно поэтому я добавил второй уровень анализа и кластеризации.
Используем Embeddings и GPT: level 2
На этом этапе для уже найденных GPT тем мы повторно строим Embeddings и с помощью Agglomerative Clustering применяем иерархическую кластеризацию. Это позволяет объединять похожие темы разных компаний в общие группы.
from sklearn.cluster import AgglomerativeClustering from sklearn.metrics.pairwise import cosine_distances topic_texts = ( topics["ClusterName"] + "\n" + topics["Description"] ).tolist() topic_embeddings = [ get_embedding(text) for text in topic_texts ] X_topics = np.vstack(topic_embeddings) distance_matrix = cosine_distances(X_topics) clustering = AgglomerativeClustering( metric="precomputed", linkage="average", distance_threshold=0.35, n_clusters=None ) topics["MetaClusterID"] = clustering.fit_predict(distance_matrix)
Затем GPT снова обрабатывает данные и формирует более высокий уровень абстракции — MetaTopic. После этого похожие MetaTopic дополнительно консолидируются в бизнес-категории.
prompt = f""" You are a product analytics expert. Below are similar customer topics from different apps. Create one concise MetaTopic name that represents them. Return ONLY valid JSON: {{ "meta_topic": "Meta topic name" }} Topics: {topics_text} """ response = client.chat.completions.create( model=config.MODEL_TOPICS, response_format={"type": "json_object"}, messages=[ {"role": "user", "content": prompt} ], temperature=0 ) result = json.loads(response.choices[0].message.content)
В результате получается многоуровневая карта смыслов:
Review (Отзыв) → Cluster (Общая тема) → MetaTopic (Более крупная тема) → BusinessTopic (Бизнес-задача).
Например: Need English Language → English Language Support → Multilingual App Support → Localization.

Фактически система перестает работать со словами и начинает работать со смыслами: на входе — тысяча неструктурированных отзывов, на выходе — десяток бизнес-категорий, их тональность, объем обсуждения и динамика изменений во времени. Именно это позволяет использовать отзывы не как набор комментариев, а как источник знаний о продукте, клиентах и рынке.
Вместо итогов
С помощью нашего решения мы обошли встроенные инструменты аналитики в приложениях для отзывов и смогли автоматизировать работу с ними — то, на что раньше у специалиста ушли бы недели, сейчас обрабатывается за десять минут.
Сейчас мы активно пользуемся внутри инструментом и планируем внедрить чат-бота, который будет дополнительно анализировать и комментировать результаты в модели Voice of Customer.
Огромное преимущество современных возможностей в том, что стоимость такого решения минимальна: мы используем только OpenAI Embeddings API. Адаптировать наше решение под себя можете и вы — я буду рад обсудить фидбэк и ответить на ваши вопросы, задавайте их в комментариях.
