ВВЕДЕНИЕ

Часть 1 читайте здесь: https://medium.com/@alidu143/unveiling-the-drivers-of-customer-churn-an-analytical-journey-to-improve-retention-part-1-20fbfd602841

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

Обработка функций и разработка

Удалить дубликаты

# Check for duplicate rows in churn_data
duplicate_rows = Telco_churn.duplicated()
print("Number of duplicate rows:", duplicate_rows.sum())
Number of duplicate rows: 0

Группировка набора данных (функции (X) и целевая переменная (y))

# Split the columns into numerical and categorical data
df = Telco_churn.copy()
df.drop(['customerID'], axis=1, inplace=True)


# split the data into features (X) and target variable (y)
X = df.drop('Churn', axis=1)
y = df['Churn']

Вмените пропущенные значения

from sklearn.impute import SimpleImputer

# Create an instance of the SimpleImputer class with mean strategy
imputer = SimpleImputer(strategy='mean')

# Impute missing values in TotalCharges column with the mean value of that column
X['TotalCharges'] = imputer.fit_transform(X[['TotalCharges']])

Создание новых функций

Основываясь на информации, предоставленной в наборе данных, есть некоторые атрибуты, которые потенциально могут быть объединены для создания новых функций:

«OnlineSecurity» и «DeviceProtection»:

Эти два атрибута указывают, есть ли у клиента определенные виды услуг защиты. Объединение их в одну функцию, такую ​​как «SecurityServices», может упростить набор данных и потенциально повысить точность модели.

# Combine "OnlineSecurity" and "DeviceProtection" into a new feature called "SecurityServices"
X['SecurityServices'] = X['OnlineSecurity'] + X['DeviceProtection']

# Replace values with new labels based on whether the customer has no, one, or both security services
X['SecurityServices'] = X['SecurityServices'].replace({'NoNo': 'NoneSecurityServices',
                                                       'YesNo': 'OnlyOnlineSecurity',
                                                       'NoYes': 'OnlyDeviceProtection',
                                                       'YesYes': 'BothSecurityServices'})

# Drop the original "OnlineSecurity" and "DeviceProtection" features
X = X.drop(['OnlineSecurity', 'DeviceProtection'], axis=1)

X['SecurityServices'].head()

"StreamingTV" и "StreamingMovies":

Оба эти атрибута относятся к тому, использует ли клиент потоковые сервисы. Объединение их в одну функцию, например «StreamingServices», также может помочь упростить набор данных.

# Create a new feature called "StreamingServices"
X['StreamingServices'] = X['StreamingTV'] + X['StreamingMovies']

# Replace values with new labels based on whether the customer has no, one, or both Streaming Services
X['StreamingServices'] = X['StreamingServices'].replace({'NoNo': 'NoneStreamingServices', 
                                                         'YesNo': 'OnlyStreamingTV', 
                                                         'NoYes': 'OnlyStreamingMovies', 
                                                         'YesYes': 'BothStreamingServices'})

# Drop the original "StreamingTV" and "StreamingMovies" features
X = X.drop(['StreamingTV', 'StreamingMovies'], axis=1)

X['StreamingServices'].head()

"PhoneService" и "MultipleLines":

Оба этих атрибута относятся к телефонной службе клиента. Объединение их в одну функцию, например «PhoneServices», может помочь уменьшить количество функций в наборе данных.

# Combine PhoneService and MultipleLines into a single feature, PhoneServices
X['PhoneServices'] = X.apply(lambda x: 'MultipleLines' if x['MultipleLines'] == 'Yes' else 'SingleLine', axis=1)

# Drop PhoneService and MultipleLines columns
X.drop(['PhoneService', 'MultipleLines'], axis=1, inplace=True)

X['PhoneServices'].head(100)

"InternetService" и "OnlineBackup":

Оба этих атрибута относятся к интернет-сервису клиента. Объединение их в одну функцию, например «InternetServices», может помочь упростить набор данных.

# Create a new feature called "InternetServices"
X['InternetServices'] = X.apply(lambda row: 'DSL Only' if row['InternetService'] == 'DSL' and row['OnlineBackup'] == 'No' else
                                          'Fiber Optic Only' if row['InternetService'] == 'Fiber optic' and row['OnlineBackup'] == 'No' else
                                          'Internet and Backup' if (row['InternetService'] == 'DSL' or row['InternetService'] == 'Fiber optic') and row['OnlineBackup'] == 'Yes' else
                                          'No Internet Service', axis=1)

# Drop the original "InternetService" and "OnlineBackup" columns
X = X.drop(['InternetService', 'OnlineBackup'], axis=1)

X['InternetServices'].head()

Особенности кодирования

# Import the necessary libraries
from sklearn.preprocessing import OneHotEncoder

columns_to_encode = ['Partner', 'Dependents', 'PaperlessBilling', 'gender']


# Create a OneHotEncoder object
ohe = OneHotEncoder()

# Fit and transform the columns using the OneHotEncoder
encoded_columns = ohe.fit_transform(X[columns_to_encode])

# Create a new DataFrame with the encoded columns
encoded_df = pd.DataFrame(encoded_columns.toarray(), columns=ohe.get_feature_names_out(columns_to_encode))

# Drop the original columns from the original DataFrame
X.drop(columns_to_encode, axis=1, inplace=True)

# Concatenate the original DataFrame with the new encoded DataFrame
X = pd.concat([X, encoded_df], axis=1)
y = y.replace({'Yes': 1, 'No': 0})
columns_to_encode = ['TechSupport', 'Contract','PaymentMethod', 'PhoneServices',
                     'SecurityServices','StreamingServices', 
                     'InternetServices']


# Create a OneHotEncoder object
ohe = OneHotEncoder()

# Fit and transform the columns using the OneHotEncoder
encoded_columns = ohe.fit_transform(X[columns_to_encode])

# Create a new DataFrame with the encoded columns
encoded_df = pd.DataFrame(encoded_columns.toarray(), columns=ohe.get_feature_names_out(columns_to_encode))

# Drop the original columns from the original DataFrame
X.drop(columns_to_encode, axis=1, inplace=True)

# Concatenate the original DataFrame with the new encoded DataFrame
X = pd.concat([X, encoded_df], axis=1)

Масштабирование функций

from sklearn.preprocessing import MinMaxScaler

# Create an instance of MinMaxScaler
scaler = MinMaxScaler()

# Fit and transform the MonthlyCharges and TotalCharges columns
X[['MonthlyCharges', 'TotalCharges', 'tenure']] = scaler.fit_transform(X[['MonthlyCharges', 'TotalCharges', 'tenure']])

#X[['TotalCharges']] = scaler.fit_transform(X[['TotalCharges']])

Окончательные характеристики

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 38 columns):
 #   Column                                                    Non-Null Count  Dtype  
---  ------                                                    --------------  -----  
 0   SeniorCitizen                                             7043 non-null   int64  
 1   tenure                                                    7043 non-null   int64  
 2   MonthlyCharges                                            7043 non-null   float64
 3   TotalCharges                                              7043 non-null   float64
 4   Partner_No                                                7043 non-null   float64
 5   Partner_Yes                                               7043 non-null   float64
 6   Dependents_No                                             7043 non-null   float64
 7   Dependents_Yes                                            7043 non-null   float64
 8   PaperlessBilling_No                                       7043 non-null   float64
 9   PaperlessBilling_Yes                                      7043 non-null   float64
 10  gender_Female                                             7043 non-null   float64
 11  gender_Male                                               7043 non-null   float64
 12  TechSupport_No                                            7043 non-null   float64
 13  TechSupport_No internet service                           7043 non-null   float64
 14  TechSupport_Yes                                           7043 non-null   float64
 15  Contract_Month-to-month                                   7043 non-null   float64
 16  Contract_One year                                         7043 non-null   float64
 17  Contract_Two year                                         7043 non-null   float64
 18  PaymentMethod_Bank transfer (automatic)                   7043 non-null   float64
 19  PaymentMethod_Credit card (automatic)                     7043 non-null   float64
 20  PaymentMethod_Electronic check                            7043 non-null   float64
 21  PaymentMethod_Mailed check                                7043 non-null   float64
 22  PhoneServices_MultipleLines                               7043 non-null   float64
 23  PhoneServices_SingleLine                                  7043 non-null   float64
 24  SecurityServices_BothSecurityServices                     7043 non-null   float64
 25  SecurityServices_No internet serviceNo internet service   7043 non-null   float64
 26  SecurityServices_NoneSecurityServices                     7043 non-null   float64
 27  SecurityServices_OnlyDeviceProtection                     7043 non-null   float64
 28  SecurityServices_OnlyOnlineSecurity                       7043 non-null   float64
 29  StreamingServices_BothStreamingServices                   7043 non-null   float64
 30  StreamingServices_No internet serviceNo internet service  7043 non-null   float64
 31  StreamingServices_NoneStreamingServices                   7043 non-null   float64
 32  StreamingServices_OnlyStreamingMovies                     7043 non-null   float64
 33  StreamingServices_OnlyStreamingTV                         7043 non-null   float64
 34  InternetServices_DSL Only                                 7043 non-null   float64
 35  InternetServices_Fiber Optic Only                         7043 non-null   float64
 36  InternetServices_Internet and Backup                      7043 non-null   float64
 37  InternetServices_No Internet Service                      7043 non-null   float64
dtypes: float64(36), int64(2)
memory usage: 2.0 MB

Тестирование на мультиколлинеарность

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

Первый шаг – измерение корреляции:

Корреляция — это распространенный метод, используемый для обнаружения мультиколлинеарности в регрессионном анализе. Коэффициент корреляции измеряет силу и направление линейной зависимости между двумя переменными. В контексте мультиколлинеарности нас интересует корреляция между независимыми переменными.

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

corr_matrix = X.corr()
# Create a heatmap of the correlation matrix
fig, ax = plt.subplots(figsize=(15, 15))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', ax=ax)
plt.show()

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

# Create correlation matrix
corr_matrix = churn_data.corr().abs()

# Get pairs of columns with high correlation coefficients
high_corr = [(i, j) for i in range(corr_matrix.shape[0]) for j in range(i+1, corr_matrix.shape[1]) if corr_matrix.iloc[i,j] > 0.5]

# Print pairs of columns with high correlation coefficients
for i, j in high_corr:
    print(f"{corr_matrix.index[i]} - {corr_matrix.columns[j]}: {corr_matrix.iloc[i,j]}")

Результаты указывают на следующие высокие корреляции.

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

# Calculate eigenvalues
eigvals, eigvecs = np.linalg.eig(corr_matrix)

# Print eigenvalues
print(eigvals)

# Check for eigenvalues close to 0
eigen_tol = 1e-4
cols_to_drop = []
for i, val in enumerate(eigvals):
    if val < eigen_tol:
        cols_to_drop.append(corr_matrix.columns[i])

# Drop columns with high levels of multicollinearity
if len(cols_to_drop) > 0:
    churn_data.drop(cols_to_drop, axis=1, inplace=True)
    print(f"Columns with high levels of multicollinearity: {cols_to_drop}")
else:
    print("No columns with high levels of multicollinearity.")

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

Тренировочный тестовый сплит

# Use train_test_split with a random_state, and add stratify for Classification

from sklearn.model_selection import train_test_split

# split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

Модель логистической регрессии со штрафом L1 (для Lasso) или штрафом L2 (для Ridge)

Логистическая регрессия с регуляризацией L1 или L2 может помочь улучшить производительность модели на несбалансированных наборах данных за счет уменьшения переобучения и повышения способности модели к обобщению.

Регуляризация L1 (лассо) также может выполнять выбор признаков, уменьшая коэффициенты менее важных признаков до нуля. Это может помочь улучшить производительность модели за счет уменьшения влияния зашумленных или нерелевантных функций, что может быть особенно важно в несбалансированных наборах данных, где класс меньшинства может быть связан с небольшим количеством важных функций. Я реализовал обе логистические регрессии с регуляризацией L1 или L2.

Для получения более подробной информации о реализации ознакомьтесь с моей записной книжкой на github. (Ссылка ниже).

И классификаторы Lasso, и Ridge имеют одинаковую точность, точность, полноту и оценку F1. Это может означать, что модель не переоснащает и не недообучает и хорошо обобщает тестовые данные. Однако значение точности довольно низкое (0,50), что указывает на то, что модель имеет высокий уровень ложных срабатываний, то есть она неверно предсказывает, что клиенты будут уходить, хотя на самом деле этого не произойдет.

Значение отзыва выше (0,77), что указывает на то, что модель имеет более низкий уровень ложноотрицательных результатов, т. е. она правильно предсказывает, что клиенты будут уходить, когда они действительно уйдут.

Показатель F1, который является гармоническим средним значением точности и полноты, также низок (0,61), что указывает на то, что модель может быть не лучшей с точки зрения баланса между точностью и полнотой.

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

Моделирование машинного обучения

Модель № 001

Классификатор RandomForest по несбалансированному набору данных

Для первой модели мы обучаем классификатор RandomForest на несбалансированном наборе данных. Для древовидных алгоритмов, таких как случайный лес, повышение градиента и деревья решений, масштабирование не требуется, поскольку на них не влияет масштаб входных объектов.

Модель №002

Дерево решений для несбалансированного набора данных

Для 2-й модели мы обучаем алгоритм дерева решений.

Модель №003

Адаптивное повышение (AdaBoost) по сравнению с несбалансированным набором данных

Для 3-й модели мы обучаем алгоритм Adaptive Boosting (AdaBoost).

Модель №004

Наивный байесовский анализ данных Balanced Train

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

Один из способов решить эту проблему — передискретизировать примеры в миноритарном классе. Этого можно достичь, просто продублировав примеры из класса меньшинства в наборе обучающих данных перед подгонкой модели. Это может сбалансировать распределение классов, но не дает модели никакой дополнительной информации.

from imblearn.over_sampling import SMOTE

# Create the SMOTE object
smote = SMOTE()

# Apply SMOTE to the training data only
X_train_resampled, y_train_resampled = smote.fit_resample(X_train, y_train)

Модель №005

SVM по данным сбалансированного поезда

Для 5-й модели мы обучаем SVM. Для других алгоритмов, таких как SVM и K-ближайших соседей, может потребоваться масштабирование, чтобы гарантировать, что все функции находятся в одинаковом масштабе. Горячее кодирование может привести к большому количеству разреженных функций, что может затруднить масштабирование данных. В таких случаях методы выбора признаков или уменьшения размерности, такие как PCA, могут использоваться для уменьшения количества признаков перед масштабированием.

Модель №006

К ближайших соседей для сбалансированных данных поезда

Для 6-й модели мы обучаем K-ближайших соседей по сбалансированным данным обучения.

Модель №007

Классификатор повышения градиента по сравнению со сбалансированными данными поезда

Для 7-й модели мы обучаем классификатор повышения градиента по сбалансированным данным обучения.

Модель №008

Catboost по сравнению со сбалансированными данными о поездах

Для 8-й модели мы тренируем catboost по сбалансированным данным поезда.

Модель №009

xgboost по сравнению со сбалансированными данными о поездах

Для 9-й модели мы тренируем xgboost по сбалансированным данным поезда.

Модель №010

Адаптивное повышение (AdaBoost) по сравнению со сбалансированным набором данных

Для 10-й модели мы обучаем алгоритм Adaptive Boosting (AdaBoost). Этот алгоритм строит серию деревьев решений, где каждое последующее дерево пытается исправить ошибки, допущенные предыдущим деревом.

Сравнение моделей

В таблице показаны метрики оценки для различных моделей, используемых для прогнозирования оттока. Используемые метрики оценки: точность, воспроизводимость, полнота и оценка F1.

Точность моделей колеблется от 69,34% до 79,56%. Модель с наибольшей точностью — AdaBoost со сбалансированными данными.

Точность — это отношение истинных срабатываний к сумме истинных срабатываний и ложных срабатываний. Точность моделей колеблется от 44,97% до 63,69%. Модель с наивысшей точностью — AdaBoost со сбалансированными данными.

Отзыв — это отношение истинных положительных результатов к сумме истинных положительных и ложных отрицательных результатов. Отзыв моделей колеблется от 46,52% до 81,02%. Модель с самым высоким отзывом — это Наивный Байес со сбалансированными данными.

Оценка F1 представляет собой гармоническое среднее точности и полноты. Оценка F1 моделей колеблется от 47,15% до 62,60%. Модель с наивысшей оценкой F1 — Gradient Boosting со сбалансированными данными.

В целом представляется, что наиболее эффективными моделями являются модели, в которых используются сбалансированные данные и методы ансамбля, такие как AdaBoost, Gradient Boosting и XGBoost. Наивный Байес также хорошо работает с точки зрения отзыва, но имеет более низкую точность и оценку F1 по сравнению с другими моделями. Наихудшей моделью является KNN, которая имеет низкую точность, точность и оценку F1.

Настройка гиперпараметров.

GradientBoostingClassifier

Мой код будет выполнять поиск по сетке для настройки гиперпараметров в классификаторе Gradient Boosting с использованием библиотеки sklearn в Python. Настраиваемые гиперпараметры — это количество оценок, скорость обучения и максимальная глубина дерева. Оценка F1 используется в качестве метрики оценки для поиска по сетке. Поиск по сетке выполняется с использованием 5-кратной перекрестной проверки и будет использовать все доступные ядра ЦП.

# Fit the GridSearchCV object to the preprocessed training data
grid_search.fit(X_train, y_train)

# Print the best hyperparameters and the F1 score
print("Best hyperparameters:", grid_search.best_params_)
print("F1 score:", grid_search.best_score_)

# Predict on the test data
y_pred_gboost_H = grid_search.predict(X_test)

# Print the classification report
print(classification_report(y_test, y_pred_gboost_H))

# Fit the GridSearchCV object to the training data
grid_search.fit(X_train_resampled, y_train_resampled)
# Print the best hyperparameters and the F1 score
print("Best hyperparameters:", grid_search.best_params_)
print("F1 score:", grid_search.best_score_)

Этот результат предполагает, что после настройки гиперпараметров с помощью GridSearchCV наилучшей комбинацией гиперпараметров для модели классификатора GradientBoosting являются скорость обучения 0,01, максимальная глубина 5 и 500 оценок. Оценка F1, полученная моделью с этими гиперпараметрами, составляет 0,8346, что является мерой точности модели, при этом более высокая оценка указывает на лучшую производительность. Это указывает на то, что производительность модели была улучшена за счет настройки гиперпараметров по сравнению с гиперпараметрами по умолчанию, используемыми в исходной модели.

# Predict on the test data
y_pred_gboost_H_resampled = grid_search.predict(X_test)

# Print the classification report
print(classification_report(y_test, y_pred_gboost_H_resampled))

Основываясь на результатах, общая точность модели повышения градиента после настройки гиперпараметров составляет 76%, что означает, что модель правильно предсказала 76% случаев. Модель имеет более высокую точность для класса 0 (клиенты, которые не ушли) на 89%, а точность для класса 1 (клиенты, которые ушли) ниже на 53%. Отзыв, который измеряет способность модели правильно идентифицировать все положительные случаи, выше для класса 1 на 74%, а отзыв для класса 0 составляет 76%. Показатель F1, представляющий собой средневзвешенное значение точности и полноты, составляет 0,62 для класса 1 и 0,82 для класса 0. Макросредний показатель F1 составляет 0,72, что является средним значением показателей F1 для обоих классов. Средневзвешенная оценка F1 составляет 0,77, что учитывает дисбаланс количества случаев для каждого класса. В целом, модель, по-видимому, лучше прогнозирует клиентов, которые не ушли, по сравнению с теми, кто ушёл.

ОКОНЧАТЕЛЬНЫЙ ВЫБОР МОДЕЛИ

Первая модель имеет более высокую общую точность (0,80 против 0,77) и более высокий балл F1 для положительного класса (0,57 против 0,61), что указывает на то, что она лучше правильно прогнозирует случаи оттока. Однако вторая модель имеет более высокий балл F1 для отрицательного класса (0,84 против 0,87), что указывает на то, что она лучше правильно прогнозирует случаи отсутствия оттока.

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

Ознакомьтесь с Частью 3, где я разрабатываю стратегии удержания на основе демографических данных и сегментации K-средних.

https://medium.com/@alidu143/unveiling-the-drivers-of-customer-churn-an-analytical-journey-to-improve-retention-part-3-26b3665d1996

Наконец, проверьте мой github для полного кода.

https://github.com/aliduabubakari/Telco-Customer-Churn/blob/main/ML_LP3-Classifcation-main_WEEK4.ipynb