Удержание клиентов является первоочередной задачей для всех компаний, независимо от отрасли или бизнес-модели, будь то большие, средние или малые компании. Конкуренция еще выше в SaaS или других отраслях, основанных на подписке.

Сохранение клиентов становится более важным там, где стоимость привлечения новых клиентов (CAC) высока, например, в сфере технологий, телекоммуникаций и финансов.

Для начала мы можем определить показатель оттока клиентов как процент клиентов, которые отменили свои подписки и решили больше не использовать ваш продукт.

Поскольку мы можем согласиться с тем, насколько важен уровень оттока для всех компаний, его прогнозирование является очень сложной задачей. Особенно, если вы хотите заблаговременно определить клиентов с высоким риском оттока (в этом весь смысл!). Выявление таких рисковых клиентов будет представлять собой дополнительный потенциальный источник дохода для компаний. К счастью, есть много способов выполнить такую ​​задачу с помощью машинного обучения.

Основная цель «модели прогнозирования оттока клиентов» состоит в том, чтобы удержать клиентов с высоким риском оттока путем активного взаимодействия с ними. Здесь я представлю вымышленный бизнес-кейс, в котором компания предложит 10% скидку на стоимость пожизненного контракта тем, кто выявление рисковых клиентов.

Будем считать:

  • Стоимость пожизненного контракта = 1000$
  • Значение скидки = 100$
  • предоставляется клиентам с высоким риском оттока

Основополагающие концепции, с которыми нам необходимо сначала договориться:

- Мы хотим прогнозировать отток клиентов или выявлять рискованных клиентов заранее, т. е. до того, как они уйдут (будь то месяц, квартал, полгода или какое у вас бизнес-кейс). для этого нам нужно быть осторожными, когда дело доходит до даты отсечки. Потому что включение информации после даты отсечения вызовет проблему утечки данных в нашей модели машинного обучения.

- В реальной жизни вам придется немного больше поработать, чтобы создать «столбец состояния оттока» или («целевой» столбец в терминах ML) — обычно это сложная, но очень важная задача.

- Мы обучаем нашу модель/модели на исторических данных. Основываясь на историческом поведении клиента, мы хотим определить схему оттока.

- После того, как модель будет создана и развернута, мы будем периодически вводить в нее данные клиента, чтобы извлечь «вероятности взбалтывания».

- Клиенты с высоким риском оттока будут привлечены и им будет предложена скидка 10%, чтобы зафиксировать их для другого контракта (или, по крайней мере, для продолжения их контракта).

- Клиенты с низкой вероятностью оттока в соответствии с моделью, никаких действий не требуется.

Теперь, когда бизнес-разговор вышел за рамки, давайте запачкаем руки технической реализацией. Пайплайн нашего проекта будет следующим:

  1. получить данные
  2. Очистите и подготовьте его
  3. Разделите наш подход на 4 подхода (из-за несбалансированного целевого класса)
  • Без обработки несбалансированного класса данных
  • обработка несбалансированного класса данных посредством передискретизации (SMOTE)
  • обработка несбалансированного класса данных посредством недостаточной выборки

Используйте библиотеку AutoML, например #PyCaret или #H2O, для построения базовой модели для сравнения.

Разработать 8–9 моделей для каждого подхода

  1. Настройте эти модели во всех 4 подходах
  2. оценивать и извлекать информацию из этих моделей
  3. проверить экономическое обоснование в каждой из моделей во всех 4 подходах

Из-за нехватки времени я расскажу о реализации SMOTE (передискретизация) здесь, чтобы получить представление о реализации всех 4 подходов и получить коды, пожалуйста, не стесняйтесь посетить мой GitHub: «https://github.com/Hussam1/ отток клиентов"



Приступаем к кодированию:

Сначала загружаем все необходимые библиотеки:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.metrics import confusion_matrix, classification_report, f1_score
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import LinearSVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from xgboost import XGBClassifier
from imblearn.over_sampling import SMOTENC

примечание: при появлении ошибок, скорее всего, вам нужно загрузить библиотеку в свою среду — попробуйте !pip install [library_name]

Импорт набора данных:

df= pd.read_csv(path_to_your_datasets)

чтобы иметь некоторую информацию о типе и доступности нулевых/пустых строк:

df.info()

После некоторого исследования качество данных относительно хорошее. Требуется некоторая корректировка:

  1. Измените тип [‘TotalCharges’] на числовой
  2. переназначить [‘SeniorCitizen’] на «Да» и «Нет» вместо 0 и 1
  3. Некоторые значения в столбце [‘tenure’] были равны 0, что могло указывать на очень нового клиента или просто на проблему с качеством данных — в любом случае, для простоты мы опустим их здесь. в реальной жизни вам нужно очень хорошо все изучить, прежде чем что-то уронить.

Разработка модели:

как указано, мы будем тестировать несколько моделей:

  • Логистическая регрессия
  • КНН
  • Метод опорных векторов (SVM)
  • Древо решений
  • RandomForest
  • АдаБуст
  • Градиент Достойный
  • XGBoost

первый вопрос: как корреляция между столбцами/функциями?

plt.figure(figsize=(25, 10))

corr = df.apply(lambda x: pd.factorize(x)[0]).corr()

mask = np.triu(np.ones_like(corr, dtype=bool))

ax = sns.heatmap(corr, mask=mask, xticklabels=corr.columns, yticklabels=corr.columns, 
                 annot=True, linewidths=.2, cmap='coolwarm', vmin=-1, vmax=1)

Мы видим много функций с сильной корреляцией, хотя и не идеальной, но все же я бы рекомендовал исследовать некоторые из них — все, что выше 0,8, на мой взгляд, стоит проверить.

сначала разделим данные на обучение и тестирование:

X = df.drop(columns=['Churn'])
y = df['Churn']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

Отдельные категориальные и числовые признаки:

categorical_feature_idxs = np.where(X_train.dtypes == "object")[0]
smtnc = SMOTENC(categorical_features=categorical_feature_idxs)
X_train_smote, y_train_smote = smtnc.fit_resample(X_train, y_train)
y_train_smote.value_counts()

мы можем видеть, что «SMOTE» помог нам справиться с несбалансированным классом в целевом столбце за счет передискретизации недопредставленного класса. Мы начали:

у нас было 3614 строк со значением класса «НЕТ», т. е. не взбито, и 1308 строк со значением класса «Да». Однако теперь, после использования SMOTE, мы имеем одинаковое представление обоих значений в наших наборах данных:

Создайте конвейер для преобразования:

# Separate categorical from numerical columns
categorical_features = X_train.select_dtypes(include=['object']).columns.tolist()
numeric_features = X_train.select_dtypes(exclude=['object']).columns.tolist()


# Create different transformation for each
categorical_transformer = Pipeline(steps=[("onehot", OneHotEncoder(handle_unknown="ignore"))])
numeric_transformer = Pipeline(steps=[("scaler", StandardScaler())])


# Create pre-processer which will be fed into each and every model
preprocessor = ColumnTransformer(transformers=[("num", numeric_transformer,numeric_features),
                                               ("cat", categorical_transformer,categorical_features)])

Создание моделей, начиная с логистической регрессии

# initiate model and the preprocessing pipeline we created earlier
lr_model = LogisticRegression(random_state=42, solver='liblinear')
pipeline_lr = Pipeline(steps=[("pre_process", preprocessor),
                              ("model", lr_model)])


# Extract the results


pipeline_lr.fit(X_train_smote, y_train_smote)
y_pred = pipeline_lr.predict(X_test)


class_labels = pipeline_lr.named_steps['model'].classes_


print(classification_report(y_test, y_pred))


cf = confusion_matrix(y_test, y_pred)
sns.heatmap(cf, annot=True, fmt='.0f');

Хорошие первые результаты! смогли отозвать 73% ушедших клиентов. т. е. из тех, что в итоге вышли (412 + 149), наша модель предсказала/выделила 412

Давайте продолжим и попробуем: KNN

# as usual, initiate the model and its preprocessing pipeline we created earlier
knn = KNeighborsClassifier()
pipeline_knn = Pipeline([("pre_process", preprocessor),
                         ("model", knn)])


# Running the model and extracting insights from it
pipeline_knn.fit(X_train_smote, y_train_smote)
y_pred = pipeline_knn.predict(X_test)


class_labels = pipeline_knn.named_steps['model'].classes_


print(classification_report(y_test, y_pred))


cf = confusion_matrix(y_test, y_pred)
sns.heatmap(cf, annot=True, fmt='.0f');

Эта модель привела к немного менее точным результатам. Модель KNN напомнила только 67% класса «ДА», а также дала нам более низкий F1-Score 73% (подумайте о F1 Score как о хорошем балансе между показателями точности и отзыва)

Теперь попробуем наш любимый: SVM

# as usual, initiate the model and its preprocessing pipeline we created earlier
svc = LinearSVC(random_state=42)
pipeline_svc = Pipeline([("pre_process", preprocessor),
                         ("model", svc)])


# Running the model and extracting insights from it
pipeline_svc.fit(X_train_smote, y_train_smote)
y_pred = pipeline_svc.predict(X_test)


class_labels = pipeline_svc.named_steps['model'].classes_


print(classification_report(y_test, y_pred))


cf = confusion_matrix(y_test, y_pred)
sns.heatmap(cf, annot=True, fmt='.0f');

Более того, 74% вспоминают о классе «ДА» и 75% F1-Score.

попробуем AdaBoost:

# as usual, initiate the model and its preprocessing pipeline we created earlier
ada_boost = AdaBoostClassifier(random_state=42)
pipeline_ada_boost = Pipeline([("pre_process", preprocessor_new),
                         ("model", ada_boost)])


# Running the model and extracting insights from it
pipeline_ada_boost.fit(X_train_smote, y_train_smote)
y_pred = pipeline_ada_boost.predict(X_test)


class_labels = pipeline_ada_boost.named_steps['model'].classes_


print(classification_report(y_test, y_pred))


cf = confusion_matrix(y_test, y_pred)
sns.heatmap(cf, annot=True, fmt='.0f');

Неплохие результаты, правда! 76% вспоминают о классе «Да» и 76% F1-Score. Последнее, что я буду запускать XGBoost:

# as usual, initiate the model and its preprocessing pipeline we created earlier
xgb = XGBClassifier(random_state=42)
pipeline_xgb = Pipeline([("pre_process", preprocessor_new),
                         ("model", xgb)])


# Running the model and extracting insights from it
pipeline_xgb.fit(X_train_smote, y_train_2)
y_pred = pipeline_xgb.predict(X_test)


class_labels = pipeline_xgb.named_steps['model'].classes_


print(classification_report(y_test_2, y_pred))


cf = confusion_matrix(y_test_2, y_pred)
sns.heatmap(cf, annot=True, fmt='.0f');

С той же концепцией мы можем продолжить работу с остальными моделями. для подробного кода, пожалуйста, посетите мой GitHub и не стесняйтесь использовать все, что хотите.

Помните, мы говорили о проверке результатов с помощью бизнес-кейса? Напоминаем, что наше экономическое обоснование выглядит следующим образом:

  • Компания свяжется со всеми, кто определен как «высокий риск» оттока
  • Предложите им 10% скидку на стоимость их пожизненного контракта, чтобы обеспечить их обязательство продолжать свой контракт до конца или подпишитесь на новый (предположим, что стоимость контракта составляет 1000 долларов США).

Столбец «Net_business_win» в приведенной ниже таблице представляет расчет для такого случая. Мы видим, что модель AdaBoost принесла самые высокие успехи в бизнесе, а модель XGBoost обеспечила самый высокий балл F1. Здесь важно подчеркнуть, что мы не должны принимать какую-либо метрику за чистую монету и предполагать, что она полезна для нас. нам нужно углубиться в каждую метрику и убедиться, что мы выбираем правильную для нашего случая и ту, которая дает нам наибольшую добавленную стоимость в зависимости от проблемы, которую мы пытаемся решить.

Эти цифры не самые лучшие, мы можем улучшить наши модели, попробовав:

  • Исследуйте коррелированные столбцы
  • Запустите настройку гиперпараметров, чтобы найти наилучшие параметры для выбранной модели/моделей.
  • Сотрудничайте с экспертами в области бизнеса, чтобы попытаться создать больше функций (столбцов), которые могли бы помочь нам определить закономерности оттока клиентов.

Если этот пост был полезен, пожалуйста, несколько раз нажмите кнопку аплодисментов 👏, чтобы выразить свою поддержку автору 👇

🚀Разработчики: учитесь и развивайтесь, не отставая от того, что важно, ПРИСОЕДИНЯЙТЕСЬ К FAUN.