Попытаемся спрогнозировать запросы на обслуживание оборудования, по истории запросов в 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.
lair
Во-первых, использовать accuracy — плохо, надо смотреть на confusion matrix (и вообще, что у вас с балансом классов?).
А во-вторых, смотреть надо не на одном последнем периоде, а на нескольких (см.
TimeSeriesSplit
).А в-третьих, неплохо бы сравнить с бейзлайном, основанным на простой вероятности отказа.
camunar Автор
Спасибо, попробую. С балансом классов все плохо.
lair
Вы понимаете, да, что если "с балансом классов все плохо", то получить метрику, подобную вашей, можно предиктором, который всегда предсказывает доминирующий класс?
camunar Автор
Ну не совсем так, в явной форме. Я сначала сделал, в потом понял, что это большая проблема.
Спасибо, сделаю вторую версию модели