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

Предположим, у мобильного оператора есть 4 тарифа: Базовый, Интернет-тариф, Продвинутый и Премиум. Маркетинговый отдел получил базу потенциальных клиентов с некоторыми данными о каждом клиенте.

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

Метод K-Nearest Neighbors (KNN), чтобы предсказать тариф нового пользователя.

K-Nearest Neighbours это алгоритм машинного обучения. Он поможет нам предсказать тариф пользователя на основании тренировочного набора данных. Для предсказания лучшего тарифа для нового пользователя алгоритм учитывает тарифы текущих клиентов, ближайших к новому пользователю по выбранным признакам.

Визуализация алгоритма K-Nearest Neighbours.

Из диаграммы, приведенной выше можно получить представление об алгоритме. Он учитывает «ближайших соседей» для прогноза тарифа нового пользователя.

В случае, приведенном на диаграмме, у нас есть клиенты с тарифами A и B. У этих клиентов есть некоторые признаки, например, возраст (X1) и уровень дохода (X2). Если мы рассмотрим значение K=3 (3 ближайшие точки данных, или ближайших соседа), мы получим в качестве прогноза тариф B. В то же время, если мы рассмотрим значение K=6, мы получим прогноз тариф A.

Для реализации алгоритма нам понадобятся библиотеки python sklearn, itertools, numpy, matplotlib, pandas.

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

df = pd.read_csv('Customers.csv')

Визуализация и анализ исходного набора данных

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

df['Тариф'].value_counts()

281 Продвинутый, 266 Базовый, 236 Премиум, and 217 Интернет-тариф

Можем исследовать наши данные, используя методы визуализации:

fig, ((ax1, ax2, ax3, ax4, ax5), (ax6, ax7, ax8, ax9, ax10)) = plt.subplots(nrows=2, ncols=5, figsize = (12,4))

df.hist(column='Регион проживания', bins=3, ax=ax1)
df.hist(column='Параметр1', bins=50, ax=ax2)
df.hist(column='Возраст', bins=50, ax=ax3)
df.hist(column='Семейное положение', bins=2, ax=ax4)
df.hist(column='Параметр2', bins=50, ax=ax5)
df.hist(column='Доход', bins=50, ax=ax6)
df.hist(column='Образование', bins=50, ax=ax7)
df.hist(column='Стаж', bins=50, ax=ax8)
df.hist(column='На пенсии', bins=2, ax=ax9)
df.hist(column='Гражданство', bins=2, ax=ax10) 
plt.tight_layout() 

Чтобы использовать библиотеку scikit-learn, мы должны преобразовать датафрейм Pandas в массив Numpy:

X = df[['Регион проживания', 'Параметр1', 'Возраст', 'Семейное положение', 'Параметр2', 'Доход', 'Образование', 'Стаж', 'На пенсии', 'Пол','Гражданство']].values  #.astype(float)
X[0:5]
array([[  2.,  13.,  44.,   1.,   9.,  64.,   4.,   5.,   0.,   0.,   2.],
[ 3., 11., 33., 1., 7., 136., 5., 5., 0., 0., 6.],
[ 3., 68., 52., 1., 24., 116., 1., 29., 0., 1., 2.],
[ 2., 33., 33., 0., 12., 33., 2., 0., 0., 1., 1.],
[ 2., 23., 30., 1., 9., 30., 1., 2., 0., 0., 4.]])

Нормализация данных

Нормализация данных дает нулевое среднее значение для данных и единичную дисперсию, это хорошая практика, особенно для таких алгоритмов, как KNN, которые основаны на расстоянии между точками:

y = df['Тариф'].values
X = preprocessing.StandardScaler().fit(X).transform(X.astype(float))
X[0:5]
 array([[-0.02696767, -1.055125  ,  0.18450456,  1.0100505 , -0.25303431,
         -0.12650641,  1.0877526 , -0.5941226 , -0.22207644, -1.03459817,
         -0.23065004],
        [ 1.19883553, -1.14880563, -0.69181243,  1.0100505 , -0.4514148 ,
          0.54644972,  1.9062271 , -0.5941226 , -0.22207644, -1.03459817,
          2.55666158],
        [ 1.19883553,  1.52109247,  0.82182601,  1.0100505 ,  1.23481934,
          0.35951747, -1.36767088,  1.78752803, -0.22207644,  0.96655883,
         -0.23065004],
        [-0.02696767, -0.11831864, -0.69181243, -0.9900495 ,  0.04453642,
         -0.41625141, -0.54919639, -1.09029981, -0.22207644,  0.96655883,
         -0.92747794],
        [-0.02696767, -0.58672182, -0.93080797,  1.0100505 , -0.25303431,
         -0.44429125, -1.36767088, -0.89182893, -0.22207644, -1.03459817,
          1.16300577]]) 

Выделение данных для тестирования

Цель любой модели — делать правильные прогнозы на неизвестных данных. Если мы будем проведить обучение и тестирование на одном и том же наборе данных, при тестировании на новых данных модель скорее всего будет иметь низкую точность. Как мы можем улучшить точность вне выборки?

Одним из способов является использование подхода под названием Train / Test Split — разделение исходных данных на обучающую и тестовую выборку. Таким образом мы сможем произвести обучение модели с обучающим набором и тестирование с тестовым набором, которого модель не видела.

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=4)
print ('Train set:', X_train.shape,  y_train.shape)
print ('Test set:', X_test.shape,  y_test.shape)
Train set: (800, 11) (800,)
Test set: (200, 11) (200,)

Мы разделили выборку на 2 части — 800 строк данных для тренировки модели и 200 строк — для тестирования модели на неизвестных данных

Обучение модели

Давайте начнем обучение! Начнем со значения k = 4, то есть количество ближайших соседей должно равняться 4:

# Импортируем библиотеку KNeighborsClassifier
from sklearn.neighbors import KNeighborsClassifier
k = 4  
# Train Model 
neigh = KNeighborsClassifier(n_neighbors = k).fit(X_train,y_train)
neigh

Предсказание

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

yhat_train = neigh.predict(X_train)
print("25 первых значений тренировочного набора в сравнении с реальными значениями:")
print(yhat_train[0:25])
print(y_train[0:25])
 25 первых значений тренировочного набора в сравнении с реальными значениями: 
[3 3 3 1 2 1 3 1 1 3 1 1 2 3 4 3 1 3 1 1 4 3 4 2 4] 
[4 4 3 1 3 1 1 1 1 3 3 1 2 4 4 3 1 1 3 1 4 4 4 2 1] 
yhat_test = neigh.predict(X_test)

print("25 первых значений тестового набора в сравнении с реальными значениями:")
print(yhat_test[0:25])
print(y_test[0:25])
25 первых значений тестового набора в сравнении с реальными значениями:
[1 1 3 2 4 3 3 2 2 4 1 4 2 1 3 1 3 4 3 4 3 3 3 1 1]
[4 1 1 2 4 4 3 1 3 4 4 2 4 1 4 3 4 2 3 4 1 1 1 3 4]

Оценка точности

Оценка точности производится функцией accuracy classification score. Она вычисляет, насколько близко в тестовом наборе находятся фактические и прогнозируемые метки.

from sklearn import metrics
print("Точность модели на обучающем наборе данных: ", metrics.accuracy_score(y_train, neigh.predict(X_train)))
print("Точность модели на тестовом наборе данных: ", metrics.accuracy_score(y_test, yhat))
Точность модели на обучающем наборе данных:  0.5475
Точность модели на тестовом наборе данных:  0.32

Это означает, что мы можем предсказать тариф каждого третьего пользователя из нашей новой базы данных. Неплохо, но можно ли лучше?

Что если взять 7 соседей (K=7)?

k = 7
neigh7 = KNeighborsClassifier(n_neighbors = k).fit(X_train,y_train)
yhat7 = neigh7.predict(X_test)
print("Train set Accuracy: ", metrics.accuracy_score(y_train, neigh7.predict(X_train)))
print("Test set Accuracy: ", metrics.accuracy_score(y_test, yhat7))
 Train set Accuracy:  0.5125
 Test set Accuracy:  0.335

Что будет, если применить другое значение K?

Самые внимательные наверняка задались вопросом, как выбрать наилучшее значение для K? Мы можем использовать следующий код для моделирования и расчета точности прогноза при различных значениях K:

Ks = 50
mean_acc = np.zeros((Ks-1))
std_acc = np.zeros((Ks-1))
ConfustionMx = [];
for n in range(1,Ks):
    
    #Train Model and Predict  
    neigh = KNeighborsClassifier(n_neighbors = n).fit(X_train,y_train)
    yhat=neigh.predict(X_test)
    mean_acc[n-1] = metrics.accuracy_score(y_test, yhat)

    
    std_acc[n-1]=np.std(yhat==y_test)/np.sqrt(yhat.shape[0])

mean_acc

Точность модели

plt.plot(range(1,Ks),mean_acc,'g')
plt.fill_between(range(1,Ks),mean_acc - 1 * std_acc,mean_acc + 1 * std_acc, alpha=0.10)
plt.legend(('Accuracy ', '+/- 3xstd'))
plt.ylabel('Accuracy ')
plt.xlabel('Number of Nabors (K)')
plt.tight_layout()
plt.show()

print( "Наивысшая точность модели ", mean_acc.max(), "достигается при K=", mean_acc.argmax()+1)
 Наивысшая точность модели  0.41 достигается при K= 38 
yhat_test = neigh38.predict(X_test)
print("25 первых значений тестового набора в сравнении с реальными значениями:")
print(yhat_test[0:25])
print(y_test[0:25])
 25 первых значений тестового набора в сравнении с реальными значениями: 
[3 1 2 4 4 4 3 1 3 2 1 2 3 1 3 1 3 4 3 2 3 1 1 1 4] 
[4 1 1 2 4 4 3 1 3 4 4 2 4 1 4 3 4 2 3 4 1 1 1 3 4] 

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

В реальных условиях такие модели строятся на большем количестве данных, и точность моделей может быть еще выше.