Въведение

Машинното обучение се превърна в решение на много проблеми, управлявани от данни. Независимо дали става дума за идентифициране на измамни транзакции в банкова система, прогнозиране на кредити, прогнозиране на ранни стадии на определено заболяване или прогнозиране на отлив на клиенти.

С автоматизацията, която замества много от повтарящите се процеси, и използването на ML модели повдига редица въпроси.

  1. Как можем да разчитаме на производителността на конкретен модел само като разгледаме показатели като RMSE, резултат на R-квадрат, MAPE, точност, AUC, прецизност и припомняне и т.н.?
  2. Как моделът достига до определено решение или стойност?
  3. Какви характеристики карат модела да вземе определено решение?

Тази статия обяснява, че се занимава с областта на интерпретируемостта на ML, като обяснява чрез случая на използване на „предсказане на диабет“. До края на тази статия ще разберете защо е важно да интерпретирате всеки отделен ML модел след обучение и по време на извод. Фокусът ще бъде повече върху обяснението как да се прилагат различните методи за интерпретация на модела и по-малко върху обучението по модел.

Сложност срещу интерпретируемост

За да разберем по-добре компромиса между сложност и интерпретируемост, нека първо разберем процеса на обучение на ML модел.

Всеки процес на обучение по ML модел се състои от следното -

  1. Решаване на уравнение
  2. Функция загуба
  3. Оптимизатор
  4. Актуализация на обратно разпространение/тегла

За да постигнем по-голяма точност, често се опитваме да приложим по-сложно уравнение (компонент 1), за да се учим от данните за обучение. Това не означава, че не трябва да използваме сложен модел със сложни данни. Можем да опитаме различни методи като групиране и дори да обучим модели за дълбоко обучение, за да получим по-добър модел за оценяване. Но имайте предвид, че много сложното може да е много точно, но ще бъде по-малко интерпретируемо.

Обърнете се към графиката, предоставена по-горе, можем да видим, че линейната регресия може да е по-малко точна от дървото на решенията, но нейните силно интерпретируеми и дълбоки невронни мрежи може да са много точни, но по-малко интерпретируеми.

Но как да използваме сложни техники за моделиране и все пак да можем да интерпретираме тези модели?

Точно това е обсъдено в следващия раздел.

Употреба — Прогноза за диабет

Наборът от данни, използван за това упражнение, е База данни за диабета на индианците Pima, която съдържа важна информация за пациенти с диабет. Наборът от данни има 8 функции и целевата променлива в набора от данни е Резултат, обозначен в двоичен формат като 0 за липса на диабет и 1 за диабет.

Започвайки с проучвателен анализ на данни, последван от инженеринг на функции и експериментиране с различни модели на ансамбъл. Използван е и усъвършенстван метод за моделиране като подреждане, за да се увеличи сложността на модела за демонстрация.

dia_df = pd.read_csv(‘/content/diabetes.csv’)
dia_df.head()

dia_df.info()

dia_df.isna().sum()

dia_df[‘Outcome’].value_counts(normalize=True)

Това показва, че целевата променлива не е балансирана. Ние използваме свръхсемплиране, за да направим данните балансирани.

# train test split
X_train, X_test, y_train, y_test = train_test_split(dia_df.drop(‘Outcome’, axis=1), dia_df[‘Outcome’], test_size=0.3, random_state=1234)
# oversampling
over_sampler = RandomOverSampler(random_state=1234)
X_train, y_train = over_sampler.fit_resample(X_train, y_train)

След това извършваме инженеринг на функции като мащабиране на числовите характеристики.

scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

След като подготовката на данните приключи, можем да започнем с частта за моделиране. Нека започнем да експериментираме с различни модели.

  1. K-най-близките съседи
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
def show_metrics(y_true, y_pred):
    print('Classification Report: \n')
    print(classification_report(y_true, y_pred))
    print('\nConfusion Matrix: \n')
    print(confusion_matrix(y_true, y_pred))
y_pred_knn = knn.predict(X_test)
show_metrics(y_test, y_pred_knn)

2. Модел за усилване на градиента

gb_ensemble_model = GradientBoostingClassifier(n_estimators=500, learning_rate=0.001, max_depth=4, min_samples_split=4)
gb_ensemble_model.fit(X_train, y_train)
y_pred_gb = gb_ensemble_model.predict(X_test)
show_metrics(y_test, y_pred_gb)

3. Сложен модел на ансамбъл

clf1 = GradientBoostingClassifier(n_estimators=1000, learning_rate=0.001, max_depth=4, min_samples_split=4)
clf2 = KNeighborsClassifier(n_neighbors=20)
meta_clf = LogisticRegression()
stacked_ensemble = StackingClassifier([clf1, clf2], meta_classifier=meta_clf, use_probas=True)
stacked_ensemble.fit(X_train, y_train)
y_pred_se = stacked_ensemble.predict(X_test)
show_metrics(y_test, y_pred_se)

Опитахме различни техники за моделиране, започвайки от прост KNN модел и различен модел с нарастваща сложност. Да предположим, че подреденият ансамбъл модел даде най-добрите резултати за прогнозиране и искаме да го използваме за извод. В следващия раздел демонстрираме използването на възможност за интерпретиране на машинно обучение за разбиране на моделите на ансамбъла.

Тълкуване на модела

  1. График за важност на характеристиките

В случай, че използвате ансамблови модели, Scikit-Learn предоставя променлива в класа на модела, известна като „feature_importances_“. След като обучението на модела приключи, можем да получим значението на функцията и да начертаем графика, както е показано по-долу.

rf_model = RandomForestClassifier()
rf_model.fit(X_train, y_train)
features = dia_df.drop('Outcome', axis=1).columns.tolist()
importances = rf_model.feature_importances_
feature_imp_df = pd.DataFrame({'feature': features, 'importance': importances}).sort_values('importance', ascending=False)
plt.figure(figsize=(10,8))
plt.title('Random Forest Feature Importance')
sns.barplot(y='feature', x='importance', data=feature_imp_df, color='skyblue')

Разглеждайки горния график, можем да имаме някакво основно разбиране за характеристиките, които управляват модела при вземането на решения. Можем да добавим/изхвърлим някои функции според нашите познания за домейна и след това да разгледаме графиката отново.

2.Сурогат на дървото на решенията

Друг метод за интерпретация на модела са сурогатните модели. Сурогатен модел може да се използва, когато вземането на решение на текущия модел е сложно, ние използваме резултата, осигурен от нашия модел, за да обучим друг модел с висока степен на интерпретация. В този случай ние избираме дърво на решенията, тъй като е по-лесно за тълкуване чрез начертаване на дървото. Едно нещо, което трябва да се отбележи, е, че целевата променлива, използвана за обучение на дървото на решенията, ще бъде прогнозният изход на нашия подреден ансамбъл модел, а не действителните цели.

dt_model = DecisionTreeClassifier()
# stacked ensemble predictions
y_pred_train_se = stacked_ensemble.predict(X_train)
# training on predicted targets
dt_model.fit(X_train, y_pred_train_se)
fig, axes = plt.subplots(nrows = 1,ncols = 1,figsize = (5,3), dpi=300)
out = plot_tree(dt_model, feature_names=features, class_names=['Not Diabetic', 'Diabetic'], max_depth=2, filled=True, fontsize=4)
for o in out:
     arrow = o.arrow_patch
     if arrow is not None:
        arrow.set_edgecolor('black')
        arrow.set_linewidth(0.2)
plt.show()

3.Обяснител на LIME

LIME означава Locally Interpretable Model Agnostic Explanations. Както подсказва името, това обяснение извежда диапазон от стойности, дадени на един ред от данни като вход към модела. Помага ни да разберем характеристиките, които водят модела към вземане на конкретно решение. LIME е агностик на модела, което означава, че може да се приложи към всеки възможен вид ML модел.

def predict_proba(data):
    return np.array(list(zip(1-stacked_ensemble.predict(data),     stacked_ensemble.predict(data))))
explainer = lime.lime_tabular.LimeTabularExplainer(X_train, mode='classification', training_labels=y_train.values, feature_names=features)
def explain_instance(data):
    exp = explainer.explain_instance(data, predict_proba,  num_features=len(features))
    exp.show_in_notebook(show_table=True)
explain_instance(X_train[0])

explain_instance(X_train[100])

4.Обяснител на SHAP

SHAP означава обяснение на добавките на SHapley. Той дава обяснения на модела въз основа на стойностите на Shapley на всяка характеристика. Той предоставя както локално, така и глобално обяснение.

def local_explanation(data, i):
    shap.initjs()
    explainer = shap.TreeExplainer(gb_ensemble_model)
    shap_values = explainer.shap_values(data)
    return shap.force_plot(explainer.expected_value, shap_values[i], features=data[i], feature_names=features)
local_explanation(X_train, 0)

local_explanation(X_train, 100)

# global explanation
shap.initjs()
explainer = shap.TreeExplainer(gb_ensemble_model)
shap_values = explainer.shap_values(X_train)
shap.summary_plot(shap_values, features=X_train, feature_names=features, plot_size=(12,8))

Заключение

Тази статия ни дава основната идея зад интерпретируемостта на машинното обучение и обяснява как да използваме различни методи за интерпретиране на ML модели.

Бих ви препоръчал да прочетете тази електронна книга, ако се интересувате от дълбоко потапяне в интерпретацията на модела.

Кодът, използван в тази статия, може да бъде достъпен чрез тази „връзка“.