Попытаемся спрогнозировать запросы на обслуживание оборудования, по истории запросов в Service Desk.

Имеется однотипное оборудование компании, в разных регионах, например, станки, или сервера. Имеется департамент сервиса, который выполняет заявки на обслуживание серверов: почистить, заменить деталь, обновить софт. Имеется Service Desk система, в которой ведется история этих заявок, за несколько лет. Специалист, выполнивший обслуживание сервера, заполняет и закрывает заявку в системе Service Desk.

Исходные данные: датасет со следующими полями:

  • номер заявки Service Desk;

  • дата начала и дата завершения работ;

  • регион, и локация сервера;

  • сервер, производитель, модель, серийный номер;

  • тип выполненных работ (справочник);

Какие проблемы в исходных данных:

  • нет списания запчастей на обслуживание, что за запчасти, и на какой сервер списано. Хотя пристегнуть эти данные было очень полезно;

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

  • датасет очень короткий, всего навсего за полтора года, с ноября 2019 года по август 2021 года. Это уже очень плохо;

Анонимизированный датасет выложен на Kaggle. В датасете оборудование условно называется «Ноутбук».

Задача минимум: спрогнозировать, какие сервера в следующем месяце потребуют обслуживание.

Задача максимум: к задаче минимум еще и спрогнозировать тип обслуживания, то есть какой сервер в следующем месяце потребует какого типа обслуживания.

В данной статье описаны основные моменты решения задачи минимум. Задачу максимум пока не осилил.

Добавляем параметр time_diff – «Обслуживался ли ноутбук в прошлом месяце»:

def get_timediff(df):
  temp = []
  for i in df['NB_SerialNumber'].unique():
      df = data[data["NB_SerialNumber"] == i].sort_values(by = ["YearMonth"])
      df['Datetime'] = pd.to_datetime(df[['Day','Month','Year',]]
                     .astype(str).apply(' '.join, 1), format='%d %m %Y')
      df['time_diff'] = df['Datetime'].diff()
      df['time_diff'] = df['time_diff'].apply(lambda x: x.days/30)
      df['time_diff'] = df['time_diff'].fillna(0)
      df['time_diff'] = df['time_diff'].astype(int)
      temp.append(df)
  return(temp)

my_data = get_timediff(data)
my_data_df = pd.concat(my_data)

Для обучения берем период с начала и до мая:

df_train = my_data_df[(my_data_df['YearMonth'].isin(     
  [201911, 201912,      
   202001, 202002, 202003, 202004, 202005, 202006, 202007, 202008, 202009, 202010, 202011, 202012,       
   202101, 202102, 202103, 202104, 202105      
  ]      
) )]

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

df_train.time_diff.value_counts() 
df_train = df_train[df_train.time_diff != -1] 
df_train = df_train[df_train.time_diff != 2] 
df_train = df_train[df_train.time_diff != 3] 
df_train = df_train[df_train.time_diff != 4] 
df_train = df_train[df_train.time_diff != 5] 
df_train = df_train[df_train.time_diff != 6] 
df_train = df_train[df_train.time_diff != 7] 
df_train = df_train[df_train.time_diff != 8] 
df_train = df_train[df_train.time_diff != 9] 
df_train = df_train[df_train.time_diff != 10] 
df_train = df_train[df_train.time_diff != 11] 
df_train = df_train[df_train.time_diff != 12] 
df_train = df_train[df_train.time_diff != 14]

Указываем только категориальные признаки:

categorical_features_indices = np.where(X_train.dtypes != np.float)[0]

Далее обучаем:

model = CatBoostRegressor(iterations=100,
                          depth=15,
                          learning_rate=0.01,
                          loss_function='RMSE') 
cat_features=[0,1,2,3,4,5,6] 
model.fit(X_train, y_train, cat_features)

За пороговое значение берем 0.2:

preds_raw = model.predict(X_test, prediction_type='RawFormulaVal') 
preds_raw_df=pd.DataFrame(preds_raw) 

lst=[] 
for i in preds_raw_df[0]:
	if i>0.2:
		lst.append(1)
	else :
		lst.append(0)

Как бы приходим к точности предсказания 88%:

from sklearn.metrics import accuracy_score 
accuracy_score(y_test,lst) 
# 0.8863444895458374

Слабое место в модели понятно, при 10-12 тысяч заявок на обслуживание в месяц, и порядка 4000 серверов, не очень сложно угадать 88% тех серверов, которые в следующем месяце потребует сервиса.

Юпитер книжка тоже выложена на Kaggle. Прошу не сильно бросаться помидорами, это мое пятое задание по ML.

Комментарии (4)


  1. lair
    14.10.2021 00:25

    Как бы приходим к точности предсказания 88%:

    Во-первых, использовать accuracy — плохо, надо смотреть на confusion matrix (и вообще, что у вас с балансом классов?).


    А во-вторых, смотреть надо не на одном последнем периоде, а на нескольких (см. TimeSeriesSplit).


    А в-третьих, неплохо бы сравнить с бейзлайном, основанным на простой вероятности отказа.


    1. camunar Автор
      14.10.2021 05:18

      Спасибо, попробую. С балансом классов все плохо.


      1. lair
        14.10.2021 09:43

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


        1. camunar Автор
          14.10.2021 11:35

          Ну не совсем так, в явной форме. Я сначала сделал, в потом понял, что это большая проблема.

          Спасибо, сделаю вторую версию модели