Реализация AI-системы персонализации промо-акций
Промо-персонализация — определение, кому, какую скидку и в какой момент предложить. Массовые скидки "всем 15%" — деньги на ветер: 30-40% получателей купили бы без скидки. ML-система предлагает скидку только тем, кто в ней нуждается, и подбирает минимальный стимул, достаточный для конверсии.
Модель uplift для промо
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import cross_val_score
class PromoUpliftModel:
"""
Uplift modeling: предсказывает не вероятность покупки,
а ПРИРОСТ вероятности от скидки.
"""
def __init__(self):
# Two-model approach
self.model_treatment = GradientBoostingClassifier(
n_estimators=200, learning_rate=0.05, random_state=42
)
self.model_control = GradientBoostingClassifier(
n_estimators=200, learning_rate=0.05, random_state=42
)
def train(self, df: pd.DataFrame, feature_cols: list):
"""
df: user_id, received_promo (0/1), purchased (0/1), features...
"""
X = df[feature_cols].fillna(0)
y = df['purchased']
# Обучаем отдельно на тех, кто получил промо, и кто не получил
treatment_mask = df['received_promo'] == 1
control_mask = df['received_promo'] == 0
X_t, y_t = X[treatment_mask], y[treatment_mask]
X_c, y_c = X[control_mask], y[control_mask]
self.model_treatment.fit(X_t, y_t)
self.model_control.fit(X_c, y_c)
print(f"Treatment model AUC: {cross_val_score(self.model_treatment, X_t, y_t, scoring='roc_auc', cv=3).mean():.3f}")
print(f"Control model AUC: {cross_val_score(self.model_control, X_c, y_c, scoring='roc_auc', cv=3).mean():.3f}")
def predict_uplift(self, X: pd.DataFrame) -> pd.Series:
"""Предсказание uplift для каждого пользователя"""
p_treatment = self.model_treatment.predict_proba(X)[:, 1]
p_control = self.model_control.predict_proba(X)[:, 1]
return pd.Series(p_treatment - p_control, index=X.index)
class PromoPersonalizationEngine:
def __init__(self, uplift_model: PromoUpliftModel):
self.uplift_model = uplift_model
self.promo_tiers = [
{'discount': 5, 'min_uplift': 0.05},
{'discount': 10, 'min_uplift': 0.04},
{'discount': 15, 'min_uplift': 0.03},
{'discount': 20, 'min_uplift': 0.025},
{'discount': 25, 'min_uplift': 0.02},
]
def assign_promo(self, users_df: pd.DataFrame,
feature_cols: list,
budget_per_user: float = 50) -> pd.DataFrame:
"""Персональное назначение промо-скидок"""
X = users_df[feature_cols].fillna(0)
uplifts = self.uplift_model.predict_uplift(X)
result = users_df[['user_id']].copy()
result['predicted_uplift'] = uplifts.values
result['segment'] = 'no_promo'
result['discount_pct'] = 0
result['expected_roi'] = 0
for _, row in result.iterrows():
idx = row.name
uplift = result.at[idx, 'predicted_uplift']
avg_order = users_df.at[idx, 'avg_order_value'] if 'avg_order_value' in users_df.columns else 100
# Выбираем минимальную скидку с положительным ROI
for tier in self.promo_tiers:
if uplift >= tier['min_uplift']:
promo_cost = avg_order * tier['discount'] / 100
expected_revenue_lift = uplift * avg_order
roi = (expected_revenue_lift - promo_cost) / promo_cost
if roi > 0.5 and promo_cost <= budget_per_user:
result.at[idx, 'discount_pct'] = tier['discount']
result.at[idx, 'expected_roi'] = roi
# Сегментация
if uplift > 0.15:
result.at[idx, 'segment'] = 'persuadable_high'
elif uplift > 0.07:
result.at[idx, 'segment'] = 'persuadable_low'
else:
result.at[idx, 'segment'] = 'sure_thing'
break
return result
def calculate_promo_roi(self, results_df: pd.DataFrame) -> dict:
"""Расчёт ROI промо-кампании"""
with_promo = results_df[results_df['discount_pct'] > 0]
without_promo = results_df[results_df['discount_pct'] == 0]
return {
'total_users_targeted': len(with_promo),
'avg_discount': with_promo['discount_pct'].mean(),
'estimated_total_cost': (with_promo['discount_pct'] / 100 * 100).sum(),
'segment_breakdown': results_df['segment'].value_counts().to_dict(),
'expected_avg_roi': with_promo['expected_roi'].mean()
}
Uplift modeling экономит 25-40% промо-бюджета против "скидки всем". Ключевые сегменты:
- Sure Things (~20%) — купят без скидки: не трогаем
- Persuadables (~35%) — нужен правильный стимул: таргетируем
- Lost Causes (~25%) — не купят даже со скидкой: не тратим бюджет
- Sleeping Dogs (~20%) — скидка их раздражает: не трогаем
Правильная сегментация по uplift типично сохраняет 40-50% промо-бюджета при сохранении 85-90% объёма продаж.







