
Привет! Меня зовут Кирилл Сергеев, я ML-инженер в Циане. В этой статье я расскажу, как мы решили задачу дедупликации объявлений о недвижимости, разработав систему на основе трёх моделей. Эта система автоматически находит и объединяет дублирующиеся объявления, помогая пользователям видеть только актуальную и уникальную информацию.
Материал будет полезен ML-инженерам и специалистам по обработке данных, которым интересно, как мы подошли к решению этой задачи: какие методы использовали, какие проблемы возникли и как мы их преодолели.
Почему мы вообще боремся с дублями
Рынок недвижимости растёт, и такие платформы, как Циан, ежедневно обрабатывают десятки тысяч новых объявлений от агентств, застройщиков и частных лиц. Однако одно и то же помещение может публиковаться несколько раз в разном виде: например, одновременно от собственника и риелтора или как несколько схожих лотов от застройщика. В результате появляются дубликаты, создающие иллюзию избыточного предложения.
Это проблема сразу на нескольких уровнях:
-
Для пользователей — поиск жилья превращается в хаос из повторяющихся объявлений, иногда с разной стоимостью.
-
Для бизнеса — аналитика становится неточной, что мешает оценивать спрос, цены и конкуренцию.
-
Для платформы — теряется доверие аудитории, растут расходы на обработку дублей и поддержку пользователей.
При этом некорректная дедупликация несёт не меньшую угрозу. Если алгоритм ошибочно объединит объявления, которые на самом деле описывают разные объекты (ошибка второго рода), пользователи могут потерять доступ к нужным вариантам.

Задачу усложняют несколько факторов:
-
Огромный поток данных — каждый день поступают десятки тысяч новых объявлений из разных источников (CRM, API партнёров).
-
Разнородные форматы — адреса и характеристики жилья могут быть указаны по-разному, с пробелами в данных.
-
Высокие требования к точности — пропущенные дубликаты создают шум, а ошибочная склейка может привести к финансовым потерям и падению репутации.
Например, в новостройках часто продаются квартиры с одинаковой планировкой, площадью и ценой за квадратный метр. Их описания тоже похожи из-за типовых маркетинговых текстов. Если система решит, что это одно и то же объявление, пользователи просто не увидят все доступные варианты квартир в ЖК.
Как научить алгоритм понимать, что объявления — про один и тот же объект
Матчинг объявлений в недвижимости — это процесс сопоставления разных публикаций, относящихся к одному и тому же реальному объекту. Грубо говоря, если одна и та же квартира размещена в нескольких объявлениях (например, от собственника и риелтора), система должна это распознать и объединить их в одну группу.
Формально задача матчинга выглядит так:
Пусть у нас есть множество объявлений:
То задача матчинга сводится к определению функции сходства:
Где указывает на вероятность того, что объявления
и
описывают один и тот же объект. Цель — разбить A на подмножества (кластеры):
где каждый содержит объявления про один реальный объект.
В реальных условиях точную функцию s найти невозможно — всегда есть шум, неточности в данных и разные способы подачи информации. Поэтому помимо основного алгоритма матчинга требуется пост-обработка результатов, чтобы минимизировать ошибки и избежать неправильного объединения объектов.
Матчинг объявлений — задача не из простых. Даже если два объявления описывают один и тот же объект, их данные могут заметно отличаться из-за человеческих ошибок, маркетинговых уловок или особенностей подачи информации. Разберём, какие параметры влияют на процесс сопоставления и с какими сложностями приходится сталкиваться.
Географическое расположение
-
Ошибка в адресе — вместо точной улицы указывают ближайшую станцию метро или крупный ориентир.
-
Неточность в номере дома — может быть указан соседний дом или корпус.
-
Проблемы с координатами — при ручном вводе риелторы иногда ставят метку «где-то рядом», а не в точном месте.
Физические характеристики
-
Площадь и количество комнат — ключевые параметры, но часто указываются с погрешностями. Например, в объявлении могут завышать или занижать площадь, чтобы привлечь больше внимания.
-
Маркетинговые трюки — в объявлении двухкомнатной квартиры иногда пишут «трешка», рассчитывая, что так её увидит больше потенциальных покупателей.
Текстовые описания
-
Субъективные эпитеты — «шикарная квартира в элитном доме» звучит красиво, но мало что говорит о реальных характеристиках объекта.
-
Отсутствие описания — некоторые объявления публикуются вообще без текста, что усложняет их анализ.
Фотографии
-
Разные фото одной квартиры — после переезда владельца мебель и интерьер меняются, из-за чего объявления могут выглядеть по-разному.
-
Использование стоковых изображений — некоторые риелторы добавляют «красивые» фото, которые не имеют отношения к реальному объекту.
Чтобы сопоставить объявления, нужно перевести их в удобный для машинного анализа формат — векторное представление. Это позволяет сравнивать объекты по ключевым характеристикам и находить совпадения.
Векторное представление:
Каждое объявление можно преобразовать в вектор признаков
, который включает:
-
Числовые данные: цена, площадь, количество комнат, этаж и т. д.
-
Категориальные данные: тип недвижимости (квартира, дом, комната), материал стен, наличие парковки и т. д.
-
Текстовые данные: закодированные описания, например, с помощью TF-IDF или эмбеддингов.
-
Изображения: представления фото, полученные с помощью сверточных нейросетей (CNN).
Чтобы определить, насколько два объявления похожи, используются разные метрики сходства. Они помогают понять, находятся ли векторы объявлений в многомерном пространстве рядом друг с другом.
Евклидово расстояние — измеряет геометрическое расстояние между точками в пространстве признаков:
Чем меньше значение тем ближе объекты друг к другу.
Косинусное сходство — измеряет угол между векторами объявлений, определяя их направленность, а не абсолютные значения:
Если объявления описывают один и тот же объект, их векторы будут практически параллельны, а косинусное сходство — близко к 1.
Обе метрики позволяют эффективно находить «похожие» объявления, особенно в сочетании с индексированными структурами данных, которые ускоряют поиск.
После предварительного фильтра по метрикам сходства можно применить модели машинного обучения для более точного матчинга.
-
Бинарная классификация (дубликат / не дубликат).
-
Для пары
считаем набор признаков и подаём в модель (CatBoost, Random Forest и т. д.).
-
-
Функция потерь:
-
где
— истинная метка (дубликат/нет),
— предсказанная вероятность.
-
-
Пост-процессинг:
-
Собираем вероятности и используем алгоритмы группировки пар в более крупные кластеры.
-
На практике для дедупликации объявлений не используется один метод — эффективнее сочетать несколько подходов:
-
Грубая фильтрация: сначала сужаем выборку с помощью векторных представлений и быстрых метрик (например, косинусного сходства).
-
Точный анализ: оставшиеся кандидаты передаются в ML-модель для финальной классификации.
Такой каскадный подход снижает вычислительную нагрузку и повышает точность.
Как устроена система дедупликации
Мы выбрали трёхэтапную архитектуру, которая позволяет эффективно находить дубликаты среди миллионов объявлений. Система состоит из трёх моделей, каждая из которых выполняет свою задачу:
-
Модель кандидатов — быстро отбирает потенциальные дубликаты.
-
Ранжирующая модель — оценивает, насколько объявления действительно похожи.
-
Модель группировки — объединяет их в кластеры реальных объектов.
1. Модель кандидатов: отсекаем лишнее
Проверять каждую пару объявлений в лоб невозможно — слишком большой объём данных. Поэтому мы используем векторную базу (Qdrant), чтобы для каждого нового или обновлённого объявления находить топ-100 самых похожих.
Основные признаки для векторизации:
-
Географическое положение
-
Цена
-
Площадь
-
Количество комнат
-
Категория недвижимости (квартира, дом и т. д.)
Этот этап значительно сокращает число возможных совпадений, позволяя дальше работать только с наиболее вероятными кандидатами.
2. Ранжирующая модель: оцениваем сходство
Теперь каждую из 100 пар дополнительно обогащаем более чем 200 признаками:
-
Полные текстовые описания
-
Фотографии
-
Дополнительные удобства (балкон, лифт, паркинг и т. д.)
Для предсказания вероятности дубликата используем CatBoost, который выдаёт оценку от 0 до 1. Если вероятность достаточно высока, пара идёт на следующий этап.
3. Модель группировки: собираем кластеры
После предсказания парных совпадений строим граф, где рёбра соединяют объявления, которые с высокой вероятностью являются дубликатами.
На этом этапе:
-
Применяем статистические фильтры, чтобы убрать случайные связи.
-
Объединяем объявления в кластеры — теперь каждое из них точно относится к конкретному объекту недвижимости.
Почему три модели?
-
Модель кандидатов резко сокращает количество проверяемых пар, иначе пришлось бы сравнивать миллионы объявлений.
-
Ранжирующая модель точно оценивает вероятность дубликатов.
-
Модель группировки не просто анализирует пары, а объединяет объявления в корректные группы.
Такой каскадный подход позволяет находить дубликаты быстро и с высокой точностью.
Оценка качества дедупликации
Важно не просто находить дубликаты, но и делать это с высокой точностью. Ошибки могут стоить дорого: если алгоритм слишком «жёсткий», он объединит разные объявления и скроет реальные варианты; если слишком «мягкий» — оставит дубли и засорит базу.
Чтобы оценить эффективность системы, мы используем три ключевые метрики: Precision, Recall и F1-score.
Основные метрики
Precision (Точность) — показывает, сколько из найденных дубликатов действительно являются таковыми:
где:
-
TP (True Positives) — пары объявлений, правильно определённые как дубликаты.
-
FP (False Positives) — пары, ошибочно помеченные как дубликаты.
Чем выше Precision, тем меньше ложных срабатываний.
Recall (Полнота) — показывает, какая доля всех существующих дубликатов была найдена моделью:
где:
-
FN (False Negatives) — реальные дубликаты, которые модель пропустила.
Чем выше Recall, тем меньше пропущенных дубликатов.
F1-score — гармоническое среднее Precision и Recall, балансирующая метрика:
Используется, когда важно одновременно минимизировать ложные срабатывания и не терять важные совпадения.
Precision и Recall всегда находятся в противоречии:
-
Если повысить Recall (делать алгоритм чувствительнее), система найдёт больше дубликатов, но увеличится число ложных срабатываний.
-
Если повысить Precision (сделать фильтры жёстче), ошибок станет меньше, но и пропущенных дубликатов станет больше.
Оптимальный баланс зависит от потребностей бизнеса. Например, если важно не раздражать пользователей ложными объединениями, делаем упор на Precision. Если же приоритет — очистка базы от дублей, увеличиваем Recall.
Полностью разметить миллионы объявлений вручную невозможно, поэтому мы используем комбинированный подход.
Разметка пар из модели кандидатов. Эта модель работает с высокой полнотой (Recall ≈ 100%), поэтому отбираем случайные пары из неё и размечаем вручную. Это помогает оценить Recall всей системы.
Разметка пар из модели группировки. Берём пары объявлений, которые уже были объединены системой, и проверяем, насколько они действительно являются дубликатами. Это помогает оценить Precision.
Пример стратегии отбора пар
-
Из модели кандидатов: пары (A, B), (A, C), (A, D) — случайная выборка из топ-100 похожих.
-
Из группировщика: пара (A, E) — если E не была кандидатом, но попала в тот же кластер.
Далее:
-
Передаём эти пары аннотаторам для ручной оценки.
-
Считаем метрики на размеченных данных.
-
Корректируем пороги модели, чтобы достичь нужного баланса Precision/Recall.
Модель кандидатов: как быстро найти потенциальные дубликаты

На этом этапе наша задача — выбрать топ-100 объявлений, которые могут быть дубликатами, чтобы передать их на дальнейший анализ. Проверять все возможные пары объявлений — нереально из-за огромного объёма данных, поэтому мы используем Qdrant, оптимизированную векторную базу данных для поиска ближайших соседей.
Qdrant оптимизирован для поиска в высокоразмерных пространствах — позволяет быстро находить ближайшие векторы среди миллионов записей.
Также он обновляется в реальном времени — новые объявления сразу попадают в индекс и могут участвовать в сравнении.
Еще один плюс Qdrant — его масштабируемость. Он поддерживает распределённую архитектуру, справляясь с огромными потоками запросов.
Каждое объявление кодируется вектором признаков, включающим:
-
Координаты (широта, долгота).
-
Числовые параметры (цена, этаж, количество комнат).
-
Категорию недвижимости (новостройка, вторичка, комната, дом).
Категории получают свои наборы весов, чтобы модель не путала, например, загородный дом и квартиру только из-за схожей площади.
Ранжирующая модель: как точно определить дубликаты

После отбора топ-100 кандидатов необходимо провести детальную оценку: действительно ли объявления описывают один и тот же объект или это просто похожие варианты? На этом этапе мы используем градиентный бустинг CatBoost, который анализирует множество признаков и выдаёт вероятность , что объявления
и
являются дубликатами. Какие признаки учитываются?
Базовые характеристики
-
Количество комнат, площадь, площадь кухни, этаж, высота потолков.
-
Год постройки, наличие лифта, тип дома.
-
Финансовые условия (цена, возможность ипотеки).
Текстовые признаки
-
Анализ описаний с помощью TF-IDF или эмбеддингов (например, BERT).
-
Оценка схожести ключевых фраз и структуры текста.
Географические признаки
-
Расстояние между координатами объявлений.
-
Учитывание типа района (центр, окраина, область).
Изображения:
-
Признаки, извлечённые из CNN-эмбеддингов (например, ResNet).
-
Проверка наличия совпадающих фото среди изображений объявлений (если хотя бы одно совпадает — это сильный сигнал).
Дополнительные признаки:
-
Категория недвижимости (новостройка, вторичка, коммерческая, загородная).
-
Тип сделки (аренда, продажа).
Модель группировки: как объединить дубликаты в кластеры

После того как ранжирующая модель оценила вероятность дубликатов, нужно не просто сравнить пары, а объединить объявления в кластеры, где каждое объявление относится к конкретному объекту недвижимости.
Для этого мы строим граф, где:
-
Вершины: объявления.
-
Рёбра: связываем
и
, если
, где
— порог (например, 0.8).
Однако не все связи одинаково надёжны. Некоторые могут быть случайными совпадениями, что приводит к ошибочному объединению разных объектов в один кластер. Чтобы этого избежать, мы применяем статистическую фильтрацию рёбер.
Ошибочное объединение нескольких реально разных объектов в один кластер критично. Например, если у нас есть два отдельных кластера по 7 объявлений, но модель случайно проводит одно ложное ребро между ними, в итоге они объединяются, и система считает их одним объектом.
Допустим, у нас:
-
91 возможное ребро между объявлениями в кластере.
-
42 истинных совпадения (TP).
-
1 ложное совпадение (FP).

По метрикам всё выглядит отлично (Precision ≈ 0.98, Recall = 1), но по факту — мы склеили два разных объекта в один, что недопустимо.
Поэтому фильтрация рёбер нужна, чтобы:
-
Повысить точность кластеризации — убираем случайные связи.
-
Снизить вероятность ложного объединения объектов.
Чтобы понять, является ли ребро между двумя объявлениями надёжным, мы анализируем пересечение их соседей.
Принцип:
-
Если у двух объявлений много общих соседей, они, вероятно, настоящие дубликаты.
-
Если у них подозрительно мало или слишком много общих соседей, связь может быть случайной.
Для проверки мы используем модель случайного графа, предполагая, что если два объявления не связаны, их пересечение соседей описывается биномиальным распределением:
где:
-
n — общее количество возможных соседей (сумма степеней вершин).
-
p — вероятность случайного совпадения соседа.
Затем ищем минимальное t, такое что:
Если фактическое пересечение соседей , ребро сохраняем; иначе — удаляем.
Алгоритм обнаружения статистически незначимых ребер
-
Определение параметров:
-
n – общее количество возможных соседей (в нашем случае, это сумма степеней вершин v и u ):
-
p – вероятность того, что случайно выбранное объявление является соседом как v , так и u . При отсутствии априорной информации p можно принять равным {Средняя_степень_вершины}/{Общее количество вершин}
-
-
Расчёт порогового значения пересечения
-
Используем биномиальную функцию распределения для определения минимального числа общих соседей t , при котором вероятность случайного совпадения пересечения меньше заданного порога
(обычно небольшого значения, например, 0.05).
-
Находим минимальное t , такое что
, где X – случайная величина, описывающая количество общих соседей
-
в свою очередь для больших n считается через нормальное
приближение:
, где
– стандартная нормальная с.в., тогда
, а для маленьких n (n < 1000) – явно, по формуле.
-
-
Сравнение фактического пересечения с порогом
-
Фактическое количество общих соседей между v и u обозначим как k.
-
Если
, то ребро между v и u считаем статистически значимым и сохраняем.
-
Иначе ребро считаем статистически незначимым и удаляем
-
Пример
Вершина v имеет 50 соседей, вершина u имеет 40 соседей. Общее количество вершин в графе: 10,000. Средняя степень вершины: допустим, 20.
-
-
-
Ожидаемое пересечение:
-
Выбираем уровень значимости
-
Находим минимальное t , при котором
Допустим, получаем t = 3
-
Если фактическое пересечение
, ребро сохраняем; иначе удаляем.
Механизм объединения объявлений в кластеры состоит из нескольких этапов:
1. Построение графа:
2. Фильтрация ребер:
3. Поиск компонент связности:
-
Используем алгоритм поиска в ширину (BFS) для определения компонент связности в графе.
-
Каждая компонента связности представляет собой кластер дубликатов.
-
Учитывается транзитивность связей: если A связано с B, а B с C, то A, B и C оказываются в одном кластере.
Архитектура системы дедупликации и матчинга

Для автоматизации процесса дедупликации и обеспечения стабильности работы мы используем Apache Airflow. Он управляет пайплайном и планирует ежедневные задачи, гарантируя, что новые данные обрабатываются своевременно.
Основные этапы работы системы
-
Обновление данных, при котором мы загружаем новые и изменённые объявления в систему.
-
Модель кандидатов
-
пересчитываем векторные представления в Qdrant;
-
добавляем новые объявления, удаляем устаревшие;
-
запускаем поиск ближайших 100 кандидатов для каждого нового объявления.
-
-
Ранжирующая модель
-
используем CatBoost, чтобы рассчитать вероятность дубликата для каждой пары из топ-100 кандидатов.
-
-
Группировка
-
строим граф объявлений;
-
применяем фильтрацию рёбер, чтобы убрать случайные совпадения;
-
выполняем поиск компонент связности, чтобы собрать объявления в кластеры.
-
-
Обновление базы
-
записываем обновлённые кластеры дубликатов в основную БД;
-
обновляем связи между объявлениями в индексе.
-
-
Отчёты и метрики
-
генерируем аналитику по качеству дедупликации;
-
создаём графики и отслеживаем изменения метрик;
-
включаем алерты, если качество модели начинает ухудшаться.
-
Благодаря Apache Airflow все этапы работают автоматически и регулярно, а дедупликация объявлений получается быстрой и точной.
Как выявлять дубликаты в реальном времени
Для почти онлайн-детекции дублей мы разработали микросервис «Стриминг дублей», который анализирует поток входящих объявлений в режиме реального времени. В его основе лежат Apache Kafka для обработки потоков данных, PostgreSQL для хранения результатов и Grafana для мониторинга работы системы.
Kafka-топики принимают все изменённые объявления, после чего микросервис обрабатывает входящие данные и выявляет потенциальные дубликаты. Процесс стриминга основан на той же трёхэтапной модели: кандидатная модель находит потенциальные совпадения, ранжирующая модель оценивает вероятность дубликата, а графовая модель объединяет объявления в кластеры.
Результаты сохраняются в базу данных PostgreSQL и передаются обратно в Kafka-топики: один содержит информацию о группах объявлений, другой — скоринговые оценки. Обработка данных идёт в нескольких потоках с использованием множества консьюмеров, что обеспечивает высокую производительность. Кроме того, в системе есть историческая очередь, которая позволяет пересчитывать и анализировать старые данные при изменении алгоритмов.
Работа микросервиса мониторится через Grafana, где визуализируются ключевые метрики: количество обработанных объявлений, производительность системы, состояние очереди и загрузка консьюмеров. Вся эта архитектура позволяет быстро и эффективно выявлять дубликаты, поддерживая высокое качество данных на платформе Циан.
Результаты и эффект от внедрения
Внедрение трёхэтапной системы дедупликации значительно улучшило работу платформы сразу на нескольких уровнях. Пользовательский опыт стал заметно лучше: уменьшилось количество повторяющихся объявлений в результатах поиска, пользователи быстрее находят нужные объекты и меньше раздражаются на дублирующийся контент. Это также повысило доверие к платформе — Циан воспринимается как источник качественной и актуальной информации.
Система оптимизировала внутренние бизнес-процессы. Аналитика по средним ценам, ликвидности и скорости продажи стала точнее, так как теперь учитываются только уникальные объекты. Снизились издержки на ручную модерацию и обработку дублей, а количество жалоб пользователей на повторяющиеся объявления заметно уменьшилось.
Внедрение системы также положительно сказалось на вовлечённости: снизился процент отказов, так как пользователи реже покидают платформу, не найдя нужного, а среднее время, проведённое на сайте, увеличилось. В целом, система дедупликации не только улучшила качество данных, но и положительно повлияла на ключевые метрики бизнеса и пользовательского опыта.
P.S. Хочу также выразить отдельную благодарность за помощь и разработку: @VD_CS и@maksim_ber
Автор: kirillsergeev0102