Попытаемся спрогнозировать запросы на обслуживание оборудования, по истории запросов в 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.
