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