← Назад к вопросам

Какие знаешь подходы uplift моделирования?

2.7 Senior🔥 102 комментариев
#Машинное обучение#Статистика и A/B тестирование

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Подходы к Uplift Моделированию

Uplift моделирование (также известное как Heterogeneous Treatment Effect моделирование) — это задача предсказания индивидуального эффекта лечения (treatment effect) на каждого пользователя. Это критично для оптимизации маркетинговых кампаний, персонализированной медицины и других областей, где решение должно быть адаптировано к конкретному человеку.

Основная концепция

Уплифт моделирование отвечает на вопрос: "На какого пользователя оказать воздействие (treatment), чтобы максимизировать эффект?"

# Базовая идея
# Для каждого пользователя мы хотим узнать UPLIFT:
# Uplift = P(conversion | treatment=1) - P(conversion | treatment=0)
#        = Вероятность конверсии с лечением минус без лечения

# Простой подход (НЕПРАВИЛЬНЫЙ):
# Посмотреть, кто конвертировался после treatment
# Проблема: не знаем, что было бы без treatment (контрфактический мир)

1. T-Learner (Two-Model Approach)

Самый простой и интуитивный подход:

from sklearn.ensemble import RandomForestClassifier
import numpy as np

class TLearner:
    """T-Learner для оценки uplift"""
    
    def __init__(self):
        self.model_treatment = RandomForestClassifier(random_state=42)
        self.model_control = RandomForestClassifier(random_state=42)
    
    def fit(self, X, treatment, y):
        """
        X: признаки пользователей
        treatment: 0 или 1 (контроль или лечение)
        y: целевой метрик (конверсия, покупка и т.д.)
        """
        
        # Разделяем данные на две группы
        treatment_mask = treatment == 1
        control_mask = treatment == 0
        
        # Обучаем отдельную модель для каждой группы
        self.model_treatment.fit(X[treatment_mask], y[treatment_mask])
        self.model_control.fit(X[control_mask], y[control_mask])
    
    def predict_uplift(self, X):
        """
        Предсказываем uplift: эффект лечения для каждого пользователя
        """
        # P(y=1 | treatment=1) - P(y=1 | treatment=0)
        prob_treatment = self.model_treatment.predict_proba(X)[:, 1]
        prob_control = self.model_control.predict_proba(X)[:, 1]
        
        uplift = prob_treatment - prob_control
        return uplift

# Использование
X_train = features_train
treatment_train = assignment  # 0 для контроля, 1 для лечения
y_train = conversions

model = TLearner()
model.fit(X_train, treatment_train, y_train)

# Прогноз uplift для новых пользователей
uplift_scores = model.predict_uplift(X_new)

# Отправить лечение тем, у кого большой положительный uplift
treatment_recommendation = uplift_scores > 0.1

Преимущества:

  • Простота реализации
  • Интерпретируемость
  • Работает с любым базовым алгоритмом

Недостатки:

  • Требует хорошо сбалансированной обработки между группами
  • Может быть нестабильным с малым количеством примеров в одной группе

2. S-Learner (Single-Model Approach)

Однозадачный подход: одна модель, но с treatment как признак

class SLearner:
    """S-Learner"""
    
    def __init__(self):
        # Одна модель для всех
        self.model = RandomForestClassifier(random_state=42)
    
    def fit(self, X, treatment, y):
        # Добавляем treatment как дополнительный признак
        X_augmented = np.hstack([X, treatment.reshape(-1, 1)])
        self.model.fit(X_augmented, y)
        self.feature_names_with_treatment = list(range(X.shape[1])) + ['treatment']
    
    def predict_uplift(self, X):
        # Предсказываем для treatment=1
        X_treatment = np.hstack([X, np.ones((X.shape[0], 1))])
        pred_treatment = self.model.predict_proba(X_treatment)[:, 1]
        
        # Предсказываем для treatment=0
        X_control = np.hstack([X, np.zeros((X.shape[0], 1))])
        pred_control = self.model.predict_proba(X_control)[:, 1]
        
        # Uplift = разница
        uplift = pred_treatment - pred_control
        return uplift

Преимущества:

  • Использует все данные одновременно
  • Более стабилен с малыми выборками

Недостатки:

  • Может пропустить эффекты взаимодействия
  • Менее интерпретируем

3. X-Learner (Cross-Fitting Approach)

Более продвинутый метод, использующий кросс-подгонку:

class XLearner:
    """X-Learner для оценки гетерогенного эффекта лечения"""
    
    def __init__(self, base_model=None):
        if base_model is None:
            base_model = RandomForestClassifier(random_state=42)
        self.base_model = base_model
    
    def fit(self, X, treatment, y):
        treatment_mask = treatment == 1
        control_mask = treatment == 0
        
        X_t, X_c = X[treatment_mask], X[control_mask]
        y_t, y_c = y[treatment_mask], y[control_mask]
        
        # Шаг 1: Обучаем модели на каждой группе
        self.model_t = clone(self.base_model).fit(X_t, y_t)
        self.model_c = clone(self.base_model).fit(X_c, y_c)
        
        # Шаг 2: Получаем остатки (residuals) — разница между истиной и предсказанием другой модели
        # Остатки в группе лечения: что не объяснила модель контроля
        residuals_t = y_t - self.model_c.predict(X_t)
        # Остатки в контроле: что не объяснила модель лечения
        residuals_c = y_c - self.model_t.predict(X_c)
        
        # Шаг 3: Обучаем модели UPLIFT на остатках
        self.uplift_model_t = clone(self.base_model).fit(X_t, residuals_t)
        self.uplift_model_c = clone(self.base_model).fit(X_c, residuals_c)
    
    def predict_uplift(self, X):
        # Усредняем предсказания обеих моделей uplift
        uplift_t = self.uplift_model_t.predict(X)
        uplift_c = self.uplift_model_c.predict(X)
        
        # Комбинируем с весами (можно использовать вероятности лечения)
        uplift = 0.5 * (uplift_t + uplift_c)
        return uplift

from sklearn.base import clone

Преимущества:

  • Более надежен, чем T-Learner
  • Хорошо работает с дисбалансом групп
  • Теоретически обоснован

Недостатки:

  • Сложнее в реализации
  • Требует больше вычислений

4. Causal Forest (Generalized Random Forests)

Вероятностный подход с доверительными интервалами:

from causalml.inference.tree_methods import CausalForestDML
from causalml.inference.meta import XGBTLearner

# Causal Forest
model = CausalForestDML(
    criterion='mse',
    n_estimators=200,
    max_depth=20,
    min_samples_leaf=5,
    random_state=42
)

model.fit(
    X=X_train,
    treatment=treatment_train,
    y=y_train
)

# Предсказание CATE (Conditional Average Treatment Effect)
uplift = model.predict(X_test)
errors = model.predict_std(X_test)  # Доверительные интервалы!

# Результат: не только uplift, но и неуверенность в предсказании
for i in range(5):
    print(f"Пользователь {i}: uplift={uplift[i]:.3f} +/- {errors[i]:.3f}")

5. RLearner (Residualized Learning)

Оптимизированный вариант для линейных моделей:

from causalml.inference.meta import BaseRLearner
from sklearn.linear_model import LinearRegression
import numpy as np

class SimplifiedRLearner:
    def __init__(self):
        self.propensity_model = LogisticRegression()  # P(T=1|X)
        self.outcome_model = LinearRegression()       # E[Y|X]
        self.treatment_model = LinearRegression()     # Финальная модель
    
    def fit(self, X, treatment, y):
        # Шаг 1: Предсказываем вероятность treatment (propensity score)
        propensity_scores = self.propensity_model.fit_predict_proba(X)[:, 1]
        
        # Шаг 2: Предсказываем исход без treatment
        outcome_pred = self.outcome_model.fit_predict(X)
        
        # Шаг 3: Вычисляем остатки
        y_residual = y - outcome_pred
        treatment_residual = treatment - propensity_scores
        
        # Шаг 4: Обучаем модель на остатках
        self.treatment_model.fit(
            treatment_residual.reshape(-1, 1),
            y_residual
        )
    
    def predict_uplift(self, X):
        # Uplift = коэффициент лечения
        return np.ones(X.shape[0]) * self.treatment_model.coef_[0]

6. Методы на основе машин опорных векторов и нейросетей

# Нейросеть для uplift моделирования
class NeuralUpliftModel(nn.Module):
    def __init__(self, input_dim, hidden_dim):
        super().__init__()
        
        # Общий слой
        self.shared = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU()
        )
        
        # Отдельные головы для treatment и control
        self.treatment_head = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim // 2),
            nn.ReLU(),
            nn.Linear(hidden_dim // 2, 1),
            nn.Sigmoid()
        )
        
        self.control_head = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim // 2),
            nn.ReLU(),
            nn.Linear(hidden_dim // 2, 1),
            nn.Sigmoid()
        )
    
    def forward(self, x, treatment):
        shared_repr = self.shared(x)
        
        if treatment == 1:
            output = self.treatment_head(shared_repr)
        else:
            output = self.control_head(shared_repr)
        
        return output

7. Практические рекомендации

Выбор метода:

МетодСложностьДанныеИнтерпретируемостьКогда использовать
T-LearnerНизкаяТребует балансаВысокаяНачальный анализ
S-LearnerНизкаяГибкийСредняяДисбаланс групп
X-LearnerСредняяГибкийСредняяСтандартное применение
Causal ForestВысокаяГибкийСредняяНужны доверительные интервалы
НейросетиОчень высокаяБольшие данныеНизкаяСложные взаимодействия

Оценка качества uplift модели:

from causalml.metrics import get_treatment_effect_curve

# Кривая QINI — показывает, насколько хорошо модель ранжирует пользователей
qini_curve = get_treatment_effect_curve(
    y_true=y_test,
    propensity_true=true_propensity,
    treatment_true=treatment_test,
    pred_uplift=predicted_uplift,
    outcome_col=0
)

# Важно: обычные метрики (AUC, accuracy) НЕ подходят для uplift
# Нужно использовать специальные метрики:
# - QINI коэффициент
# - Cumulative Gain
# - Treatment Rate

8. Валидация Uplift моделей

Основной вызов: контрфактический мир недоступен

# Лучшая практика: A/B тестирование

# 1. Разделить пользователей на группы:
#    - Control (40%): без treatment
#    - Treatment (40%): с treatment
#    - Hold-out (20%): для оценки только

# 2. На hold-out группе:
#    - Использовать модель для предсказания uplift
#    - Разделить на quartiles по predicted uplift
#    - Сравнить средний эффект в каждом quartile
#    - Если модель правильна: высокий uplift в top quartiles

def evaluate_uplift_model(y_control, y_treatment, pred_uplift):
    """
    Оценка качества uplift предсказаний
    """
    # Разделяем на quartiles по predicted uplift
    quartiles = pd.qcut(pred_uplift, q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
    
    # Считаем средний uplift в каждом quartile
    for q in ['Q1', 'Q2', 'Q3', 'Q4']:
        mask = quartiles == q
        actual_uplift = (y_treatment[mask].mean() - y_control[mask].mean())
        print(f"{q}: {actual_uplift:.3f}")
    
    # Ideal: Q4 (top quartile) имеет максимальный uplift

Заключение

Uplift моделирование — это мощный инструмент для:

  • Персонализированного маркетинга (кому отправить предложение)
  • Медицины (какому пациенту прописать лечение)
  • Оптимизации политик (какому пользователю дать скидку)

Ключевой принцип: индивидуальные эффекты лечения отличаются, и хороший uplift модель это учитывает. Выбор метода зависит от баланса между сложностью, интерпретируемостью и доступными данными.