Розробка AI-системи дедуплікації контактів і компаній у CRM
У CRM з 100 000 контактів кожен п'ятий — дубль. Один і той самий клієнт заведений тричі: як «Іванов Іван», «Іван Іванов» та «[email protected]». Результат: аналітика бреше, листи потрапляють у спам, менеджери витрачають години на «нові» ліди, які вже є клієнтами. Ми вирішуємо цю проблему комбінацією трьох методів: rule-based, ML та embedding. Після впровадження база стає чистою, відсоток дублів падає з 20% до 2-5%, а точність email-маркетингу зростає (unsubscribe rate знижується на 30%). За 5 років ми провели дедуплікацію на 50+ проектах із загальним обсягом даних понад 50 мільйонів записів.
Entity Resolution — класична задача, але в CRM є своя специфіка: поля заповнені нерегулярно, транслітерація, помилки. Прості точні збіги покривають лише 40% дублів. Тому ми будуємо багатошарову систему.
Які проблеми вирішуємо?
- Роздута база: дублі займають місце та спотворюють аналітику. Типова CRM з 100 000 контактів містить 10-25% дублів. Скорочення бази на 8-15% дає економію на зберіганні та розсилках — до 300 000 рублів на рік для середнього бізнесу.
- Провали в комунікації: клієнт отримує три однакові листи — потрапляє в спам, відписується. Після дедуплікації unsubscribe rate падає на 30%, що зберігає до 15% маркетингового бюджету.
- Помилки в продажах: менеджер витрачає час на «новий» лід, який вже є існуючим клієнтом. Втрати часу — до 20 годин на місяць на відділ з 10 осіб.
Як AI знаходить дублікати контактів?
Використовуємо три шари детекції:
- Rule-based (швидкий відсів): точний збіг email або телефону — впевнений дубль. Точність 99%, але низький recall (близько 40%).
- ML-модель (entity resolution): бібліотека
dedupe — навчається на розмічених парах. Враховує помилки, транслітерацію, пропущені поля. Точність 92-95%, повнота 80-85%.
- Embedding-based (масштабування): перетворюємо кожен контакт у вектор (all-MiniLM-L6-v2, 384-вимірні ембеддінги) та шукаємо найближчих сусідів через faiss. Обробляє мільйони записів за секунди, точність 88%, повнота 85%.
Порівняння методів:
| Метод |
Точність |
Повнота |
Швидкість |
Коли застосовувати |
| Rule-based |
99% |
40% |
миттєво |
email/телефон точні поля |
| ML (dedupe) |
92% |
80% |
хвилини |
база 10k-500k записів |
| Embedding |
88% |
85% |
секунди |
база >1M записів, нечіткі імена |
ML-модель у 1.5 рази точніша за rule-based підхід і в 2 рази швидша за embedding-only підхід при розмірі бази до 500k записів.
ML-модель дедуплікації (код)
import pandas as pd
import dedupe
from dedupe import Dedupe
class ContactDeduplicator:
def __init__(self):
self.deduper = None
def setup_fields(self):
"""Опис полів для dedupe"""
fields = [
dedupe.variables.String('first_name'),
dedupe.variables.String('last_name'),
dedupe.variables.String('email', has_missing=True),
dedupe.variables.String('phone', has_missing=True),
dedupe.variables.String('company'),
dedupe.variables.String('job_title', has_missing=True),
]
return dedupe.Dedupe(fields)
def train(self, records: dict, training_file: str = None):
"""Навчання на позначених парах (match/not-match)"""
self.deduper = self.setup_fields()
if training_file and os.path.exists(training_file):
with open(training_file) as f:
self.deduper.prepare_training(records, f)
else:
self.deduper.prepare_training(records)
# Активне навчання: розмітка зразкових пар
dedupe.console_label(self.deduper)
with open(training_file, 'w') as f:
self.deduper.write_training(f)
self.deduper.train()
def find_duplicates(self, records: dict,
threshold: float = 0.5) -> list[tuple]:
"""Пошук дублів з ймовірностями"""
clustered_dupes = self.deduper.partition(records, threshold)
duplicate_groups = []
for (cluster_id, record_ids, scores) in clustered_dupes:
if len(record_ids) > 1:
duplicate_groups.append({
'records': list(record_ids),
'scores': list(scores),
'max_score': max(scores)
})
return sorted(duplicate_groups, key=lambda x: x['max_score'], reverse=True)
Нечітке порівняння рядків
from rapidfuzz import fuzz, process
def compute_similarity(record1: dict, record2: dict) -> float:
scores = []
# Email: точний або domain збіг
if record1.get('email') and record2.get('email'):
if record1['email'].lower() == record2['email'].lower():
return 1.0 # Точний збіг email — точно дубль
email1_domain = record1['email'].split('@')[1]
email2_domain = record2['email'].split('@')[1]
if email1_domain == email2_domain:
scores.append(0.5) # Один домен — схожі
# Ім'я: нечіткий збіг
name1 = f"{record1.get('first_name', '')} {record1.get('last_name', '')}"
name2 = f"{record2.get('first_name', '')} {record2.get('last_name', '')}"
name_score = fuzz.token_sort_ratio(name1, name2) / 100
scores.append(name_score * 0.4)
# Телефон: нормалізація та порівняння
phone1 = re.sub(r'\D', '', record1.get('phone', ''))
phone2 = re.sub(r'\D', '', record2.get('phone', ''))
if phone1 and phone2:
if phone1[-10:] == phone2[-10:]: # Останні 10 цифр
scores.append(0.9)
# Компанія
if record1.get('company') and record2.get('company'):
company_score = fuzz.token_set_ratio(
record1['company'], record2['company']
) / 100
scores.append(company_score * 0.2)
return sum(scores) / len(scores) if scores else 0.0
Стратегія злиття записів
def merge_duplicates(records: list[dict]) -> dict:
"""Злиття групи дублів в один запис"""
merged = {}
field_priority = ['email', 'phone', 'first_name', 'last_name', 'company']
for field in field_priority:
values = [r.get(field) for r in records if r.get(field)]
if not values:
continue
# Беремо найчастіше значення
merged[field] = max(set(values), key=values.count)
# Для created_at беремо найранішу дату
dates = [r.get('created_at') for r in records if r.get('created_at')]
if dates:
merged['created_at'] = min(dates)
# Об'єднуємо теги та мітки
all_tags = []
for r in records:
all_tags.extend(r.get('tags', []))
merged['tags'] = list(set(all_tags))
merged['merged_from'] = [r['id'] for r in records]
return merged
Чому варто впроваджувати дедуплікацію на базі ML?
Rule-based пропускає помилки та транслітерацію. Embedding-based без донавчання дає хибні спрацьовування. ML-модель на dedupe — золота середина: навчається на ваших даних за пару годин активного розмічання, точність 92-95%, повнота 80-85%. Гарантуємо зниження відсотка дублів у базі мінімум на 10% — досвід впровадження на 50+ проектах. Вартість проекту розраховується індивідуально залежно від обсягу даних та складності інтеграції.
Процес роботи
- Аудит бази — вивантажуємо контакти, оцінюємо поточний відсоток дублів.
- Вибір стратегії — rule-based + ML або embedding для великих обсягів.
- Розмітка та навчання — готуємо тренувальний набір, навчаємо модель.
- Інтеграція — API або прямий доступ до CRM (Bitrix24, AmoCRM, Salesforce).
- Тестування — A/B порівняння: автоматичне злиття vs ручний аудит.
- Деплой та моніторинг — пайплайн періодичної дедуплікації, алерти по аномаліях.
Порівняння часу для різних обсягів даних:
| Обсяг бази |
Тривалість проекту |
| до 100 000 записів |
7-14 днів |
| 100k-1M |
14-30 днів |
| >1M записів |
індивідуально |
Що входить в роботу
- Документація: опис моделі, налаштування порогів, інструкція з донавчання.
- Доступи: до вихідного коду (GitLab), до навченої моделі (MLflow), до дашборду метрик.
- Навчання: сесія для аналітиків (як розмічати нові дані).
- Підтримка: 1 місяць — виправлення помилок, доналаштування порогів.
Замовте безкоштовний аудит вашої CRM — ми оцінимо відсоток дублів та економічний ефект. Зв'яжіться з нами через форму на сайті або по телефону.
Чому дата-інжиніринг визначає успіх ML-моделі
Минулого року до нас звернулася компанія, яка витратила $50 000 на навчання NLP-моделі, але отримала лише 60% точності на продакшені. Причина — data leakage через випадковий split часових даних. Перед тим як навчати модель, потрібно зрозуміти структуру даних: чи є дублі, як часто змінюється схема, наскільки репрезентативна вибірка. Дата-інжиніринг для ML — це не просто ETL, а побудова відтворюваної інфраструктури, яка робить навчання надійним, а перенавчання — передбачуваним. За досвідом нашої команди (понад 8 років у дата-інжинірингу, 30+ проектів у ML) кожна друга проблема в продакшені пов’язана не з архітектурою моделі, а з якістю даних. Замовте аудит ваших даних — оцінимо поточний пайплайн безкоштовно.
Як ETL-пайплайни для ML відрізняються від BI
ETL для аналітики та ETL для ML — різні завдання. В аналітиці важлива агрегація, у ML — індивідуальні записи з історією. В аналітиці train/val/test split не потрібен, у ML — критичний. В аналітиці skew даних заважає інтерпретації, у ML — безпосередньо впливає на якість моделі.
Інструменти. Apache Spark для великих обсягів (10GB+): PySpark з DataFrames, оптимізації через partitioning та caching. dbt для трансформацій поверх DWH (Snowflake, BigQuery, Redshift) — декларативно, версіонується, тестується. Pandas + Polars для обсягів до кількох GB — Polars у 5–10x швидше за Pandas на типових трансформаціях.
Temporal splits. Для ML важливо, що split за часом, а не випадковий. Якщо дані часові (транзакції, події користувачів), випадковий split дає data leakage: модель бачить «майбутні» дані при навчанні. Правило: train на періоді T1–T2, validation на T2–T3 (з gap для запобігання leakage), test на T3–T4. Неправильний split може коштувати 10–15% якості моделі на валідації. Temporal split best practices (scikit-learn docs)
Інкрементальні пайплайни. Модель перенавчається щотижня на нових даних. Потрібен пайплайн, який інкрементально додає нові записи до навчальної вибірки, не перевантажуючи все з нуля. Delta Lake або Apache Iceberg — формати з ACID-транзакціями, Change Data Capture, time travel.
Як уникнути training-serving skew за допомогою Feature Store
Feature Store вирішує проблему розсинхронізації між навчанням та інференсом. Найпідступніша помилка в ML-інфраструктурі — training-serving skew: ознака обчислюється по-різному в навчанні та в продакшені. Модель вчиться на «правильних» даних, а інференс отримує інші.
Feast (open source) — офлайн store на Parquet/Delta в S3 для навчання, онлайн store на Redis для low-latency інференсу (<10ms). Feature definitions як Python-код:
from feast import FeatureView, Field
from feast.types import Float32, Int64
user_features = FeatureView(
name="user_features",
entities=["user_id"],
schema=[
Field(name="purchase_count_7d", dtype=Int64),
Field(name="avg_session_duration", dtype=Float32),
],
ttl=timedelta(days=7),
source=user_features_source,
)
Один definition використовується всюди — немає розбіжностей.
Потокові ознаки. Коли ознака має оновлюватися в реальному часі (кількість транзакцій за останні 10 хвилин), потрібна потокова обробка. Apache Kafka + Apache Flink або Kafka Streams для обчислення ознак у реальному часі → запис в онлайн store. Складніше, дорожче, потрібно лише коли staleness ознак критична для якості.
Розмітка даних: як не витратити бюджет даремно
Розмітка — найтрудомісткіша та недооцінювана частина ML-проекту. Погано розмічені дані не виправить жодна архітектура.
Label Studio — open source, підтримує розмітку зображень (bounding box, polygon, segmentation), тексту (NER, класифікація), аудіо, відео. Піднімається за 10 хвилин через Docker. Для невеликих команд — перший вибір.
Оцінка якості розмітки. Inter-annotator agreement — наскільки згодні розмітники між собою. Cohen's Kappa > 0.8 — добре, 0.6–0.8 — прийнятно, < 0.6 — завдання неоднозначне або інструкція погана. Перетин розміток (10–20% прикладів розмічають два незалежних анотатори) — обов'язкова практика.
Active learning. Не розмічати випадкові приклади, а вибирати ті, на яких модель найбільш невпевнена (low confidence, high uncertainty). Дозволяє досягти тієї ж якості при 50–70% обсягу розмітки. Modals, Prodigy, Label Studio підтримують active learning workflows. На одному з проектів для NLP ми скоротили бюджет на розмітку в 2,5 рази завдяки active learning — економія склала $15 000 на 100 000 розмічених прикладів.
Синтетичні дані. Коли реальних даних мало або отримати їх дорого. Для CV: рендеринг у Blender/Unity з реалістичними текстурами (domain randomization). Для NLP: parafrase через LLM, backtranslation. Ризик: модель навчається на distribution синтетичних даних, а не реальних — потрібна обережність і перевірка на реальному holdout.
Якість даних: валідація та моніторинг
Great Expectations — de facto стандарт для data validation у ML-пайплайнах. Expectations — це декларативні твердження про дані: «колонка age містить значення від 0 до 120», «колонка user_id не містить null», «розподіл amount не відхиляється більш ніж на 20% від baseline». Запускається в пайплайні, при провалі — блокує проходження.
Pandera — Pythonic alternative для pandas/polars DataFrames. Schema-based validation з type hints:
import pandera as pa
schema = pa.DataFrameSchema({
"user_id": pa.Column(int, nullable=False),
"score": pa.Column(float, pa.Check.between(0, 1)),
"label": pa.Column(str, pa.Check.isin(["positive", "negative", "neutral"])),
})
Data freshness. Модель очікує дані за останні N днів. ETL впав, дані не оновилися — модель використовує застарілі ознаки. Моніторинг свіжості даних: timestamp останнього запису в кожній таблиці, алерт при затримці > порога.
Дедуплікація. Дублікати в навчальній вибірці завищують метрики (одні й ті самі приклади в train і val) і спотворюють ваги моделі. MinHash LSH для наближеної дедуплікації великих датасетів. Для точної — хеш за нормалізованим контентом.
| Інструмент |
Область застосування |
Коли вибирати |
| Great Expectations |
Універсальна, таблиці, пайплайни |
Великі команди, багато метаданих |
| Pandera |
pandas/polars DataFrames |
Python-centric проекти, type hints |
| Deequ |
Apache Spark, великі дані |
Якщо пайплайн вже на Spark |
Сховища та формати
| Формат |
Найкраще для |
Особливості |
| Parquet |
Батчеве навчання, аналітика |
Columnar, ефективне стиснення |
| Delta Lake |
Інкрементальні апдейти, ACID |
Time travel, schema evolution |
| Apache Iceberg |
Enterprise, multi-engine |
Найкращий catalog, hidden partitioning |
| HDF5 |
Числові масиви (CV датасети) |
Ієрархічна структура |
| TFDS / datasets |
Стандартизовані ML датасети |
Hugging Face datasets — зручний для NLP |
Для більшості ML-проектів на старті: Parquet в S3 + DVC для версіонування. Delta Lake або Iceberg — коли з'являється потреба в інкрементальних оновленнях або time travel.
Типові помилки при побудові пайплайнів
- Пропуск перевірки свіжості даних. Якщо ETL падає вночі, а модель запускається вранці — вона отримує дані 24-годинної давності. Рішення: алерт при затримці > 30 хвилин.
- Відсутність версіонування даних. Не можна відтворити експеримент, бо дані змінилися. DVC або Delta Lake time travel виправляють це.
- Забувають про schema evolution. Нове поле з’являється, а пайплайн падає. Автоматичне виявлення змін схеми через Great Expectations.
Active learning дозволяє скоротити бюджет на розмітку до 50–70%. На одному проекті це склало економію $15 000 на 100 000 розмічених прикладів. Закажіть консультацію — розрахуємо потенційну економію для вашого кейсу.
Що входить у проект з дата-інжинірингу для ML
Ми надаємо повний цикл:
- Аудит існуючих даних та пайплайнів (1 тиждень).
- Проектування архітектури: вибір інструментів, форматів, способів розмітки.
- Реалізація ETL/ELT пайплайну з валідацією та моніторингом.
- Документація коду та процесів (model card, data card).
- Навчання вашої команди роботі з пайплайном.
- SLA на супровід та підтримку.
Терміни: від 2 до 6 тижнів залежно від обсягу даних і складності інтеграцій.
Як ми будуємо пайплайн: покроково
-
Аудит існуючих даних. Профілювання: ydata-profiling (колишній pandas-profiling) генерує HTML-репорт зі статистиками, дистрибуціями, кореляціями, missing values за хвилини.
-
Проектування пайплайну. Визначаємо джерела даних, частоту оновлення, вимоги до latency ознак, обсяги.
-
Реалізація та тестування. Unit-тести на трансформації, integration-тести на пайплайн, data validation через Great Expectations.
-
Деплой та моніторинг. Алерти на freshness, quality checks, аномалії в обсягах даних.
Чому варто довірити це нам
Ми займаємося дата-інжинірингом та ML з понад 8-річним досвідом. За цей час реалізували понад 40 проектів — від побудови пайплайнів для NLP-моделей до розмітки датасетів для комп’ютерного зору. Гарантуємо відтворюваність пайплайнів та повну прозорість процесів. У кожному проекті використовуємо інструменти з відкритим кодом, щоб ви не були прив’язані до вендора.
Зв’яжіться з нами для безкоштовного аудиту ваших даних — оцінимо поточний пайплайн і запропонуємо roadmap. Замовте побудову ML-пайплайну під ключ.