Данные предоставлены Maven Analytics
Часть: 2 из 3
Ссылка: https://github.com/DaDataGuy/Telecom-Churn-Analysis
Отток клиентов в сфере телекоммуникаций: https: //www.mavenanalytics.io/data-playground
Вам не приходило в голову, как можно предсказать, когда клиент уйдет, или какова вероятность того, что этот клиент уйдет? Это важный вопрос, на который может быть трудно ответить, но это не обязательно. Если у вас есть доступ к данным ваших клиентов и вы отметили, кто ушел, а кто нет; можно создать свою собственную модель и добиться реальных результатов.
Изображение ниже является окончательным результатом этого анализа прогноза оттока, и если вы заинтересованы в создании собственного, вы можете это сделать!
Я предлагаю иметь более 10 000 записей, но если вы этого не сделаете, это нормально. Имейте в виду, что ваша модель будет более чувствительна к изменениям ваших данных. В этом пошаговом руководстве я буду использовать набор данных, содержащий менее 10 000 записей.
В моем предыдущем сообщении в блоге я рассказал о том, как очистить и преобразовать ваши данные в рамках подготовки к этой модели машинного обучения. Если вы этого не сделали, нажмите ссылку и следуйте дальше. Если вы уже сделали это и ваши данные готовы, давайте приступим!
P.S. ссылка на этот набор данных и мой файл Jupyter Notebook указаны вверху. Не стесняйтесь скачивать и следовать.
Моя первая задача — создать новый блокнот Jupyter и начать загрузку библиотек и файла CleanedDF.csv.
import sys print("Python Version is: " + sys.version) output: Python Version is: 3.9.7 (default, Sep 16 2021, 16:59:28) [MSC v.1916 64 bit (AMD64)]
Я использую множество библиотек, и если у вас их нет, вы можете либо установить их с помощью anaconda, либо использовать «! pip install xgboost» в блоке ячеек. Если это невозможно, закомментируйте библиотеки, которых у вас нет, и вы сможете внести изменения позже.
import time import pandas as pd import itertools import numpy as np import seaborn as sns import matplotlib.pyplot as plt import joblib #load model import subprocess from sklearn import feature_selection # from sklearn.model_selection import train_test_split from sklearn.model_selection import cross_val_score from sklearn.model_selection import GridSearchCV # from sklearn.neighbors import KNeighborsClassifier from sklearn.linear_model import LogisticRegression,RidgeClassifier,SGDClassifier from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, BaggingClassifier from sklearn.preprocessing import OneHotEncoder , OrdinalEncoder,StandardScaler , MinMaxScaler, MaxAbsScaler from sklearn.naive_bayes import BernoulliNB from sklearn.ensemble import VotingClassifier from sklearn.svm import SVC from sklearn.decomposition import PCA from sklearn.tree import DecisionTreeClassifier,ExtraTreeClassifier from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.compose import ColumnTransformer from sklearn.feature_selection import RFE from lightgbm import LGBMClassifier from xgboost import XGBClassifier from sklearn.metrics import roc_auc_score, recall_score, confusion_matrix, classification_report,f1_score,accuracy_score from sklearn.dummy import DummyClassifier from catboost import CatBoostClassifier #imblen learn from imblearn.over_sampling import SMOTE from imblearn.pipeline import Pipeline as imbpipeline # Get multiple outputs in the same cell from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = "all" # Ignore all warnings import warnings warnings.filterwarnings('ignore') warnings.filterwarnings(action='ignore', category=DeprecationWarning) pd.set_option('display.max_columns', None) pd.set_option('display.float_format', lambda x: '%.2f' % x) # goes to two decimal places
Затем я загружу файл и распечатаю, сколько времени потребовалось для загрузки.
import time time_begin = time.time() df = pd.read_csv("CleanedDF.csv") # data = pd.read_csv("census.csv") print(f'Run time: {round(((time.time()-time_begin)/60), 3)} mins') Output: Run time: 0.001 mins
После загрузки всех библиотек и очищенного фрейма данных DF я назначаю фрейм данных, помеченный как df, X, а целевую переменную Churn — y.
X = df y = df['Churn']
Теперь я назначу тестовое и обучающее разделение моего фрейма данных и переменной оттока. Если вы делали этот сплит раньше, вы можете сказать: «Эй, подожди! вы не удалили целевую переменную (Churn) во фрейме данных X». Вы правы, но не волнуйтесь, дальше вы увидите, почему я это сделал.
В качестве дополнительного примечания, стратификация — это опция, которую я использовал для равномерного распределения целевой переменной между y_train и y_test. Затем я решил перетасовать данные, чтобы еще больше увеличить шансы рандомизации и предотвратить переоснащение.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y, shuffle = True )
Этот следующий шаг — ручной процесс, который, к сожалению, должен произойти. Мы будем разделять функции на числовые, категориальные или функции, которые необходимо удалить.
remove = ['CustomerID','ZipCode','City','ChurnCategory','ChurnReason','Churn'] # Numerical columns num_feats = [ 'Age', 'NumberofDependents', 'Population', 'NumberofReferrals', 'TenureinMonths', 'AvgMonthlyLongDistanceCharges', 'AvgMonthlyGBDownload', 'MonthlyCharge', 'TotalCharges', 'TotalRefunds', 'TotalExtraDataCharges', 'TotalLongDistanceCharges', 'TotalRevenue' ] # Categorical columns cat_feats = [ 'Gender', 'Offer', 'Married', 'PhoneService', 'MultipleLines', 'InternetService', 'InternetService', 'InternetType', 'OnlineSecurity', 'OnlineBackup', 'DeviceProtectionPlan', 'PremiumTechSupport', 'StreamingTV', 'StreamingMovies', 'StreamingMusic', 'UnlimitedData', 'Contract', 'PaperlessBilling', 'PaymentMethod', ]
Теперь я создам копию X_test, а затем удалю целевое значение. Причина создания копии перед ее удалением заключается в том, что мы сопоставим вероятность прогноза оттока с идентификатором клиента.
X_testcopy = X_test.copy() X_test.drop(remove, axis = 1, inplace = True) X_train.drop(remove, axis = 1, inplace = True)
Следующим шагом будет создание функции, которая действует как мой конвейер. Популярность конвейера возросла, и он позволяет выполнять ряд задач по обработке данных. Я прикрепил ссылку, которая, как мне кажется, объясняет, как правильно создать конвейер.
def get_pipeline(X, model): numeric_pipeline = SimpleImputer(strategy='mean') categorical_pipeline = OneHotEncoder(handle_unknown='ignore') preprocessor = ColumnTransformer( transformers=[ ('numeric', numeric_pipeline, num_feats), ('categorical', categorical_pipeline, cat_feats), ], remainder='passthrough' ) bundled_pipeline = imbpipeline(steps=[ ('preprocessor', preprocessor), ('smote', SMOTE(random_state=42)), ('scaler', MinMaxScaler()), ('model', model) ]) return bundled_pipeline
ПАУЗА
Давайте разберем это шаг за шагом.
- Создайте функцию, которая принимает входные данные X и модели.
- Внутри функции я использую созданные выше переменные и назначаю их методу преобразования под названием SimpleImputer. Simple Imputer обрабатывает отсутствующие данные, и я указал, что он заменит отсутствующие данные средним значением столбца в наборе данных X_test и X_train.
- То же самое относится и к следующему шагу, который использует OneHotEncoder. Один Hot Encoder берет категориальные данные, которые я специально создал, называемые cat_feats, и создает столбцы для каждой уникальной строки. Вы можете узнать больше, нажав на ссылку выше.
- Мой препроцессор берет два столбца, которые я создал, и назначает ряд задач по преобразованию данных. Два преобразования — это SimpleImputer, использующий «num_feats», и OneHotEncoder, использующий «cat_feats».
- Теперь я создам конвейер, который выполняет преобразования препроцессора, обрабатывает любые несбалансированные данные с помощью SMOTE, масштабирует числовые значения в каждом столбце, чтобы уменьшить влияние выбросов, и применяю его к моей модели.
Следующая часть будет длинной и, честно говоря, стоит потратить время, чтобы узнать, как это работает. Мой общий обзор заключается в том, что эта функция select_model возьмет ваши наборы данных X и Y и пропустит их через конвейер. Функция select_model работает с различными моделями, которые я указал, а затем возвращает точность и время выполнения во фрейме данных. Я заранее извиняюсь за то, как это отформатировано. В конце я предоставлю код Jupyter Notebook, и он будет представлять код в каждом блоке ячеек более понятным образом.
def select_model(X, y, pipeline=None): classifiers = {} classifiers.update({"DummyClassifier": DummyClassifier(strategy='most_frequent')}) classifiers.update({"XGBClassifier": XGBClassifier(use_label_encoder=False, eval_metric='logloss', objective='binary:logistic', )}) classifiers.update({"LGBMClassifier": LGBMClassifier()}) classifiers.update({"RandomForestClassifier": RandomForestClassifier()}) classifiers.update({"DecisionTreeClassifier": DecisionTreeClassifier()}) classifiers.update({"ExtraTreeClassifier": ExtraTreeClassifier()}) #classifiers.update({"ExtraTreesClassifier": ExtraTreeClassifier()}) classifiers.update({"AdaBoostClassifier": AdaBoostClassifier()}) classifiers.update({"KNeighborsClassifier": KNeighborsClassifier()}) classifiers.update({"RidgeClassifier": RidgeClassifier()}) classifiers.update({"SGDClassifier": SGDClassifier()}) classifiers.update({"BaggingClassifier": BaggingClassifier()}) classifiers.update({"BernoulliNB": BernoulliNB()}) classifiers.update({"SVC": SVC()}) classifiers.update({"CatBoostClassifier":CatBoostClassifier(silent=True)}) # Stacking models = [] models = [] models.append(('XGBClassifier', XGBClassifier(use_label_encoder=False, eval_metric='logloss', objective='binary:logistic'))) models.append(('CatBoostClassifier', CatBoostClassifier(silent=True))) models.append(('BaggingClassifier', BaggingClassifier())) classifiers.update({"VotingClassifier (XGBClassifier, CatBoostClassifier, BaggingClassifier)": VotingClassifier(models)}) models = [] models.append(('XGBClassifier', XGBClassifier(use_label_encoder=False, eval_metric='logloss', objective='binary:logistic'))) models.append(('LGBMClassifier', LGBMClassifier())) models.append(('CatBoostClassifier', CatBoostClassifier(silent=True))) classifiers.update({"VotingClassifier (XGBClassifier, LGBMClassifier, CatBoostClassifier)": VotingClassifier(models)}) models = [] models.append(('XGBClassifier', XGBClassifier(use_label_encoder=False, eval_metric='logloss', objective='binary:logistic'))) models.append(('RandomForestClassifier', RandomForestClassifier())) models.append(('DecisionTreeClassifier', DecisionTreeClassifier())) classifiers.update({"VotingClassifier (XGBClassifier, RandomForestClassifier, DecisionTreeClassifier)": VotingClassifier(models)}) models = [] models.append(('XGBClassifier', XGBClassifier(use_label_encoder=False, eval_metric='logloss', objective='binary:logistic'))) models.append(('AdaBoostClassifier', AdaBoostClassifier())) #models.append(('ExtraTreeClassifier', ExtraTreeClassifier())) classifiers.update({"VotingClassifier (XGBClassifier, AdaBoostClassifier)": VotingClassifier(models)}) models = [] models.append(('XGBClassifier', XGBClassifier(use_label_encoder=False, eval_metric='logloss', objective='binary:logistic'))) #models.append(('ExtraTreesClassifier', ExtraTreesClassifier())) classifiers.update({"VotingClassifier (XGBClassifier)": VotingClassifier(models)}) df_models = pd.DataFrame(columns=['model', 'run_time', 'accuracy']) for key in classifiers: start_time = time.time() pipeline = get_pipeline(X_train, classifiers[key]) cv = cross_val_score(pipeline, X, y, cv=5, scoring='accuracy') row = {'model': key, 'run_time': format(round((time.time() - start_time)/60,2)), 'accuracy': cv.mean(), } df_models = df_models.append(row, ignore_index=True) df_models = df_models.sort_values(by='accuracy', ascending=False) return df_models
Давайте посмотрим, как это было!
models = select_model(X_train, y_train) models.sort_values(by=['accuracy','run_time'], ascending=False)
Я вижу, что классификатор голосования, в котором несколько алгоритмов наложены друг на друга, работает лучше всего. Однако это заняло больше всего времени, и если бы мне пришлось применять ту же логику к большему набору данных, я бы выбрал другую модель.
Я выбираю алгоритм XGBClassifier, потому что он имеет самую высокую точность и самое низкое время выполнения. Теперь я начну строить свою начальную модель перед выполнением любой настройки гиперпараметров. Вы можете увидеть, что другие конвейеры включают параметр GridSearchCV для выполнения настройки гиперпараметров, и это хорошая привычка. Я этого не делал, потому что хотел сохранить эту ваниль.
Создание базовой модели для тестирования
Я сейчас собираюсь:
- построить XGBClassifier
- Используйте функцию get_pipeline для преобразования моего набора данных
- Подгонка преобразованного набора данных к X_train и y_train
- Возьмите связанный набор данных конвейера и предскажите, используя новый набор данных X_test, чтобы предотвратить переобучение.
basemodel = XGBClassifier(use_label_encoder = False, eval_metric='logloss', objective='binary:logistic') bundled_pipeline = get_pipeline(X_train, basemodel) bundled_pipeline.fit(X_train, y_train) basemodel_y_pred = bundled_pipeline.predict(X_test)
После того, как это будет завершено, я распечатаю матрицу классификации и путаницы.
print(classification_report(y_test, basemodel_y_pred)) print(confusion_matrix(y_test, basemodel_y_pred))
Матрица путаницы:
[1384] [153]
[ 185] [373]
Отчет о классификации:
Это последний отсчет!
Собрав все вместе, я также добавлю предсказания моделей. Большинство руководств пропускают эту часть или не показывают, как извлечь результаты прогнозов модели. На мой взгляд, возможность поделиться прогнозом модели для дальнейшего анализа данных является ключом к пониманию созданных классификаций.
time_begin = time.time() #starts timer # Loan Model model = XGBClassifier( use_label_encoder = False, eval_metric='logloss', objective='binary:logistic', learning_rate = 0.1, n_estimators = 1000, max_depth = 9, min_child_weight = 1, gamma = 0.4, colsample_bytree = 0.8, subsample = 0.9, reg_alpha = 1, scale_pos_weight = 1) model = get_pipeline(X_train,model) model.fit(X_train,y_train) y_pred = model.predict(X_test) # predict target probabilities test_prob = model.predict_proba(X_test)[:,1] test_pred = np.where(test_prob > 0.45, 1, 0) #sets the probability threshhold and can be tweaked # test set metrics roc_auc_score(y_test, test_pred) recall_score(y_test, test_pred) confusion_matrix(y_test, test_pred) print(classification_report(y_test,test_pred)) print(f'Run time: {round(((time.time()-time_begin)/60), 3)} mins') # adding predictions and their probabilities to the original test Data frame X_testcopy['predictions'] = test_pred X_testcopy['pred_probabilities'] = test_prob # Exporting the predictions to a new CSV labeled high_churn_list high_churn_list = X_testcopy[X_testcopy.pred_probabilities > 0.0].sort_values(by=['pred_probabilities'], ascending = False ).reset_index().drop(columns=['index'],axis=1)
Рассмотрение обновленной классификации и матрицы путаницы.
[1378] [159] [179] [379]]
Экспорт результатов:
high_churn_list.to_csv('high_churn_list_model.csv', index = False)
Модель готова! Что теперь?
Давайте разберем матрицу путаницы и узнаем, как она может помочь мне или бизнесу.
Модель предсказала:
- 1378 клиентов, которые не ушли и были правильно предсказаны
- 179 клиентов, которые ушли, но модель предсказала, что они не ушли
- 159 клиентов, которые не ушли, но модель предсказала, что они ушли
- 379 клиентов, которые действительно ушли и были правильно предсказаны
С помощью этой информации я могу создать маркетинговую программу, которая потенциально уменьшит отток 159 клиентов, поскольку они имеют наибольшую вероятность ухода. Это означает, что модель обнаружила, что их характеристики совпадают с теми, кто сбился, и я могу попытаться предотвратить это. Ниже приведен скриншот 12 лучших клиентов, которые, скорее всего, уйдут.
На этом заканчивается моя модель прогнозирования оттока клиентов с использованием Python и Scikit-Learn. Спасибо, что дошли до конца, и если у вас есть какие-либо комментарии, предложения или вы хотите сказать спасибо, оставьте комментарий ниже.
Хорошего дня! :)