Розмітка даних — найдорожча частина 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 етапів:
- Аналіз даних та бізнес-задачі — визначаємо метрики, структуру датасету, доступні типи розмітки.
- Вибір стратегії відбору — тестуємо Uncertainty Sampling, Committee та Core-Set на вашій задачі; обираємо найкращу за кривою навчання.
- Розробка пайплайну — реалізуємо цикл: навчити → відібрати → розмітити → донавчити. Інтегруємо з платформами Label Studio або Scale AI.
- Пілотний запуск — на невеликому датасеті (1000-5000 прикладів) перевіряємо ефективність.
- Продакшн — автоматизуємо процес, налаштовуємо моніторинг метрик (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. Отримайте консультацію інженера з машинного навчання.







