Впровадження Active Learning для оптимізації розмітки даних

Ручна розмітка даних з'їдає бюджет ML-проєкту, особливо коли модель уже впевнена в більшості прикладів. Ми впроваджуємо Active Learning, щоб алгоритм сам відбирав лише складні об'єкти — це скорочує витрати на анотацію в рази без втрати якості. Наша команда реалізує проєкт під ключ: від вибору стратегії до інтеграції з вашими пайплайнами та подальшою підтримкою.
Часті запитання

Розмітка даних — найдорожча частина ML-проекту. Для NLP із сотнями тисяч прикладів бюджет на анотацію часто перевищує бюджет на навчання. Ми стикалися з проектами, де замовник витратив 80% часу на ручну розмітку, а приріст метрик склав лише 2%. Причина: 90% прикладів модель розпізнає впевнено — розмічати їх безглуздо. Active Learning вирішує це: алгоритм сам відбирає 'важкі' об'єкти, де точність моделі низька. Розмічати потрібно лише їх. Результат: 5-10-кратна економія бюджету розмітки при збереженні якості на рівні 90% від повної розмітки. Наші інженери впроваджують Active Learning під ключ: від вибору стратегії до інтеграції з платформами. У цій статті — основні стратегії, код і досвід впровадження.

Active Learning — метод машинного навчання, де алгоритм обирає найбільш інформативні приклади для розмітки.

Які стратегії працюють краще?

На практиці ми використовуємо три основні стратегії відбору:

Стратегія Принцип Коли застосовувати
Uncertainty Sampling Обирає приклади з максимальною невизначеністю моделі (ентропія, margin, least confidence) Класифікація, регресія — будь-які задачі з ймовірнісним виходом
Query by Committee Ансамбль моделей голосує; відбираються приклади з максимальним розкидом голосів Немає даних для калібрування ймовірностей, потрібна робастність
Core-Set Sampling Обирає приклади, максимально віддалені від уже розмічених (геометричне покриття) Потрібна різноманітність у датасеті, уникнення дублювання невизначеності

Uncertainty Sampling

Класичний підхід — модель передбачає ймовірності, і ми відбираємо приклади з найменшою впевненістю. Реалізація зазвичай через ентропію:

import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.base import BaseEstimator

class UncertaintySampler:
    def __init__(self, model: BaseEstimator, strategy='entropy'):
        self.model = model
        self.strategy = strategy

    def query(self, X_unlabeled: np.ndarray, n_instances: int = 10) -> np.ndarray:
        proba = self.model.predict_proba(X_unlabeled)
        if self.strategy == 'entropy':
            scores = -np.sum(proba * np.log(proba + 1e-10), axis=1)
        elif self.strategy == 'margin':
            sorted_proba = np.sort(proba, axis=1)
            scores = 1 - (sorted_proba[:, -1] - sorted_proba[:, -2])
        elif self.strategy == 'least_confident':
            scores = 1 - proba.max(axis=1)
        return np.argsort(scores)[-n_instances:]

Плюс: проста реалізація, працює для будь-яких моделей з predict_proba. Мінус: не враховує різноманітність — може відбирати схожі невизначені приклади.

Query by Committee

Тут ми навчаємо не одну модель, а ансамбль (наприклад, на бутстреп-вибірках). Незгода учасників комітету — індикатор інформативності:

from sklearn.base import clone
class CommitteeSampler:
    def __init__(self, base_estimator, n_members=5):
        self.committee = [clone(base_estimator) for _ in range(n_members)]

    def fit_committee(self, X_labeled, y_labeled):
        n = len(X_labeled)
        for member in self.committee:
            bootstrap_idx = np.random.choice(n, n, replace=True)
            member.fit(X_labeled[bootstrap_idx], y_labeled[bootstrap_idx])

    def query(self, X_unlabeled, n_instances=10):
        predictions = np.array([
            member.predict(X_unlabeled) for member in self.committee
        ])
        vote_entropy = []
        for sample_idx in range(X_unlabeled.shape[0]):
            votes = predictions[:, sample_idx]
            unique, counts = np.unique(votes, return_counts=True)
            probs = counts / len(votes)
            entropy = -np.sum(probs * np.log(probs + 1e-10))
            vote_entropy.append(entropy)
        return np.argsort(vote_entropy)[-n_instances:]

Ця стратегія стійкіша до переучування і часто дає кращий приріст якості.

Core-Set Sampling

Якщо ми хочемо покрити весь ознаковий простір, а не лише границі рішень — використовуємо Core-Set. Він обирає точки, максимально далекі від уже розмічених:

from sklearn.metrics import pairwise_distances

def core_set_selection(X_labeled, X_unlabeled, n_instances):
    selected_indices = []
    labeled_pool = X_labeled.copy()
    for _ in range(n_instances):
        distances = pairwise_distances(X_unlabeled, labeled_pool)
        min_distances = distances.min(axis=1)
        best_idx = np.argmax(min_distances)
        selected_indices.append(best_idx)
        labeled_pool = np.vstack([labeled_pool, X_unlabeled[best_idx]])
    return np.array(selected_indices)

Чому Active Learning не завжди вигідний?

Якщо датасет уже збалансований і містить мало 'легких' прикладів — виграш може бути невеликим. Ми завжди проводимо попередній аналіз: будуємо криву навчання на вибірці, щоб зрозуміти потенціал економії. Наприклад, для одного з проектів попередній тест показав, що AL дасть лише 20% економії, і ми рекомендували не впроваджувати.

Коли варто використовувати Committee замість Uncertainty?

Committee краще працює із зашумленими даними та коли калібрування ймовірностей моделі неточне. На практиці ми часто комбінуємо обидві стратегії в рамках одного циклу: на перших ітераціях використовуємо Committee для різноманітності, потім перемикаємося на Uncertainty для точності.

Active Learning для NER

Для Sequence Labeling ми використовуємо невизначеність на рівні токенів. Агрегуємо ентропію за всіма токенами речення — обираємо найневпевненіші речення:

import torch
from transformers import AutoModelForTokenClassification, AutoTokenizer

def ner_uncertainty_sampling(texts, model, tokenizer, n_instances=20):
    sentence_uncertainties = []
    for i, text in enumerate(texts):
        inputs = tokenizer(text, return_tensors='pt', truncation=True, max_length=512)
        with torch.no_grad():
            outputs = model(**inputs)
        probs = torch.softmax(outputs.logits, dim=-1).squeeze()
        token_entropy = -(probs * torch.log(probs + 1e-10)).sum(dim=-1)
        sentence_uncertainty = token_entropy.max().item()
        sentence_uncertainties.append((i, sentence_uncertainty))
    sentence_uncertainties.sort(key=lambda x: x[1], reverse=True)
    return [idx for idx, _ in sentence_uncertainties[:n_instances]]

Цей підхід дає 3-5-кратну економію для NER-датасетів.

Повний цикл Active Learning

class ActiveLearningPipeline:
    def __init__(self, model, sampler, labeling_budget):
        self.model = model
        self.sampler = sampler
        self.budget = labeling_budget
        self.labeled_count = 0
        self.performance_history = []

    def run(self, X_initial, y_initial, X_pool, batch_size=20):
        X_labeled, y_labeled = X_initial.copy(), y_initial.copy()
        X_unlabeled = X_pool.copy()

        while self.labeled_count < self.budget and len(X_unlabeled) > 0:
            self.model.fit(X_labeled, y_labeled)
            current_metric = self.evaluate(X_labeled, y_labeled)
            self.performance_history.append({
                'n_labeled': len(X_labeled),
                'metric': current_metric
            })

            query_idx = self.sampler.query(X_unlabeled, n_instances=batch_size)
            new_y = get_labels_from_annotator(X_unlabeled[query_idx])
            X_labeled = np.vstack([X_labeled, X_unlinked[query_idx]])
            y_labeled = np.concatenate([y_labeled, new_y])
            X_unlabeled = np.delete(X_unlabeled, query_idx, axis=0)
            self.labeled_count += batch_size

        return self.performance_history

Цей код ілюструє повний цикл — його ми адаптуємо під вашу інфраструктуру.

Як ми впроваджуємо Active Learning у ваш проект

Типовий процес складається з 5 етапів:

  1. Аналіз даних та бізнес-задачі — визначаємо метрики, структуру датасету, доступні типи розмітки.
  2. Вибір стратегії відбору — тестуємо Uncertainty Sampling, Committee та Core-Set на вашій задачі; обираємо найкращу за кривою навчання.
  3. Розробка пайплайну — реалізуємо цикл: навчити → відібрати → розмітити → донавчити. Інтегруємо з платформами Label Studio або Scale AI.
  4. Пілотний запуск — на невеликому датасеті (1000-5000 прикладів) перевіряємо ефективність.
  5. Продакшн — автоматизуємо процес, налаштовуємо моніторинг метрик (latency p99, GPU utilization) та перезапуск циклів.

Строки та вартість

Етап Тривалість
Базовий пайплайн (Uncertainty Sampling + інтеграція з Label Studio) 2-3 тижні
Розширений (Committee + Core-Set + NLP/NER) 6-8 тижнів
Кастомна холодна стратегія під специфіку даних від 8 тижнів

Вартість визначається після аналізу вашого проекту. Оцінимо ваш проект.

Що входить в результат впровадження

  • Робочий пайплайн Active Learning, інтегрований з вашою системою розмітки
  • Дашборд метрик: динаміка якості моделі, економія бюджету, розподіл вибраних прикладів
  • Документація та керівництво з експлуатації
  • Підтримка 3 місяці після запуску

Ми реалізували Active Learning для 15+ проектів у NLP та Computer Vision. Один з останніх кейсів: задача класифікації відгуків — скоротили обсяг розмітки з 50 000 до 8 000 прикладів при збереженні F1 на рівні 0.94.

Active Learning — перевірена методологія, яка вже заощадила нашим клієнтам мільйони рублів. Гарантуємо якість впровадження та прозорий результат.

Зв'яжіться з нами: розкажіть про своє завдання — ми запропонуємо оптимальну стратегію Active Learning. Отримайте консультацію інженера з машинного навчання.

Напрямки AI-розробки

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    815
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139