Кратко: изучено создание кодов причин для бинарной модели повышения градиента с использованием пакета Python SHAP. Использовали набор данных по раку молочной железы, рассчитали значения SHAP и построили сводную диаграмму для всего набора данных.
В машинном обучении часто сложно интерпретировать процесс принятия решений в сложных моделях. Вот тут-то и появляются коды причин. Коды причин дают представление о факторах, влияющих на процесс принятия решений в модели, что делает их мощным инструментом для интерпретации и понимания моделей машинного обучения. Одним из популярных методов создания кодов причин является использование значений SHAP (аддитивных пояснений Шэпли), которые измеряют влияние каждой функции на прогноз модели.
В этой статье мы рассмотрим, как генерировать коды причин для двоичной модели повышения градиента с использованием пакета Python SHAP. Мы будем использовать набор данных о раке молочной железы, который содержит функции, связанные с диагностикой рака молочной железы, для обучения и оценки нашей модели.
- Предварительные требования: Прежде чем продолжить, убедитесь, что в вашей системе установлен Python. Кроме того, вам потребуется установить следующие пакеты Python: scikit-learn, shap и numpy. Вы можете установить эти пакеты, используя pip/ или conda:
pip install shap conda install -c conda-forge shap
2. Импортируйте необходимые библиотеки. Сначала мы импортируем необходимые библиотеки для нашего анализа:
import numpy as np import shap from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split from sklearn.datasets import load_breast_cancer
3. Загрузите и предварительно обработайте данные. Затем мы загрузим набор данных о раке молочной железы и разделим его на наборы для обучения и тестирования:
# Load the dataset (in this example, we are using the breast cancer dataset) data = load_breast_cancer() X = data.data y = data.target # Split the data into training and testing sets X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
4. Подходящая модель: мы будем использовать классификатор повышения градиента для обучения нашей модели:
# Create and fit the Gradient Boosting Classifier gbm = GradientBoostingClassifier(n_estimators=100, random_state=42) gbm.fit(X_train, y_train)
5. Сгенерируйте код причины для каждой записи: мы будем использовать SHAP для расчета влияния каждой функции на прогнозы нашей модели:
# Create a TreeExplainer object for the Gradient Boosting Model explainer = shap.Explainer(gbm) # Calculate the SHAP values for the test dataset shap_values = explainer(X_test)
Функция generate_reason_codes принимает три входных данных: индекс записи, для которой создаются коды причин, значения SHAP, рассчитанные для всего набора данных, и имена функций. Затем он сортирует значения SHAP для указанной записи в порядке убывания и извлекает соответствующие имена и значения функций для создания словаря кодов причин.
# Create a function to generate reason codes def generate_reason_codes(instance, shap_values, feature_names): instance_shap_values = shap_values.values[instance] sorted_indices = np.argsort(np.abs(instance_shap_values))[::-1] sorted_values = instance_shap_values[sorted_indices] sorted_feature_names = [feature_names[i] for i in sorted_indices] reason_codes = { "reasons": [ {"feature": feature, "value": value, "impact": shap_value} for feature, value, shap_value in zip(sorted_feature_names, X_test[instance][sorted_indices], sorted_values) ] } return reason_codes # Choose an instance from the test dataset for which you want to generate reason codes instance = 0 # Generate the reason codes for the chosen instance reason_codes = generate_reason_codes(instance, shap_values, data.feature_names) # Display the reason codes print(reason_codes)
Например, мы можем сгенерировать коды причин для первой записи в нашем тестовом наборе следующим образом:
reason_codes = generate_reason_codes(0, shap_values, data.feature_names) print(reason_codes) #output {'reasons': [{'feature': 'mean concave points', 'value': 0.03821, 'impact': 1.141686222814163}, {'feature': 'worst concave points', 'value': 0.1015, 'impact': 1.1184778345560713}, {'feature': 'worst perimeter', 'value': 96.05, 'impact': 0.8204550414359093}, {'feature': 'worst radius', 'value': 14.97, 'impact': 0.5790399081464063}, {'feature': 'worst area', 'value': 677.9, 'impact': 0.5694348669874683}, {'feature': 'worst texture', 'value': 24.64, 'impact': 0.5303815545796332}, {'feature': 'worst concavity', 'value': 0.2671, 'impact': -0.37583321906063066}, {'feature': 'area error', 'value': 30.29, 'impact': 0.1693429393875545}, {'feature': 'mean texture', 'value': 18.6, 'impact': 0.16523431635099597}, {'feature': 'concave points error', 'value': 0.01037, 'impact': 0.14739714810086949}, {'feature': 'compactness error', 'value': 0.01911, 'impact': 0.12625250824628753}, {'feature': 'mean area', 'value': 481.9, 'impact': 0.11046554929205113}, {'feature': 'worst smoothness', 'value': 0.1426, 'impact': -0.0998261853314333}, {'feature': 'radius error', 'value': 0.3961, 'impact': 0.07462456955534748}, {'feature': 'worst symmetry', 'value': 0.3014, 'impact': 0.045209247408322197}, {'feature': 'mean perimeter', 'value': 81.09, 'impact': -0.03405795132824116}, {'feature': 'mean compactness', 'value': 0.1058, 'impact': 0.032791401757891886}, {'feature': 'mean concavity', 'value': 0.08005, 'impact': 0.018596441817801013}, {'feature': 'smoothness error', 'value': 0.006953, 'impact': 0.015290154162438207}, {'feature': 'mean radius', 'value': 12.47, 'impact': -0.012456545606318975}, {'feature': 'concavity error', 'value': 0.02701, 'impact': -0.011218565263938064}, {'feature': 'mean fractal dimension', 'value': 0.06373, 'impact': 0.007748485048966116}, {'feature': 'worst compactness', 'value': 0.2378, 'impact': 0.00738117485163141}, {'feature': 'fractal dimension error', 'value': 0.003586, 'impact': -0.0025826647471062607}, {'feature': 'perimeter error', 'value': 2.497, 'impact': 0.002458367139715965}, {'feature': 'texture error', 'value': 1.044, 'impact': 0.0012489303650987113}, {'feature': 'mean symmetry', 'value': 0.1925, 'impact': 0.0007953040626127801}, {'feature': 'symmetry error', 'value': 0.01782, 'impact': -0.0004817781327405499}, {'feature': 'worst fractal dimension', 'value': 0.0875, 'impact': -0.00010543410320955719}, {'feature': 'mean smoothness', 'value': 0.09965, 'impact': -3.488178749232912e-05}]}
6. Создайте сводку SHAP для всего набора данных: постройте сводку SHAP для всего набора данных, вы можете использовать функцию shap.summary_plot() после расчета значений SHAP для всего набора данных. Вот исправленный код:
# Create a SHAP explainer object for the Gradient Boosting Model explainer = shap.Explainer(gbm) # Calculate the SHAP values for the entire dataset (combining both the training and testing sets) shap_values = explainer(np.vstack((X_train, X_test))) # Plot the SHAP summary for the entire dataset shap.summary_plot(shap_values, np.vstack((X_train, X_test)), feature_names=data.feature_names) # Display the plot plt.show()
8. Чтобы сгенерировать коды причин для модели повышения градиента (GBM), запускаемой с обработанными переменными Weight of Evidence (WOE) с использованием OptBinning, вам необходимо сначала предварительно обработать переменные с помощью OptBinning, подогнать GBM к обработанным переменным WOE, а затем генерировать коды причин с помощью пакета SHAP. Вот код:
import numpy as np import pandas as pd import shap from optbinning import OptimalBinning from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split from sklearn.datasets import load_breast_cancer # Load the dataset (in this example, we are using the breast cancer dataset) data = load_breast_cancer() X = data.data y = data.target # Convert the dataset to a DataFrame df = pd.DataFrame(X, columns=data.feature_names) df['target'] = y # Process the dataset with OptBinning woe_df = pd.DataFrame() optb_objects = {} for feature in data.feature_names: optb = OptimalBinning(name=feature, dtype="numerical", solver="cp", monotonic_trend="auto", min_n_bins=2, max_n_bins=5) optb.fit(df[feature], df['target']) woe_df[feature] = optb.transform(df[feature], metric="woe") optb_objects[feature] = optb # Split the data into training and testing sets X_train, X_test, y_train, y_test = train_test_split(woe_df, y, test_size=0.2, random_state=42) # Create and fit the Gradient Boosting Classifier gbm = GradientBoostingClassifier(n_estimators=100, random_state=42) gbm.fit(X_train, y_train) # Create a SHAP explainer object for the Gradient Boosting Model explainer = shap.Explainer(gbm) # Calculate the SHAP values for the test dataset shap_values = explainer(X_test) # Helper function to find the corresponding WOE bucket for a given original value def get_woe_bucket(feature, original_value, optb_object): # print(feature,original_value) import re binning_table = optb_object.binning_table.build() return_value = None for index, row in binning_table.iterrows(): row["Bin"] = row["Bin"].replace('inf','1000000000.0') if (row["Bin"].startswith("Special") or row["Bin"].startswith("Missing")): continue interval_match = re.match(r'[\[\(]([^,]+), ([^\]\)]+)[\]\)]', row["Bin"]) if interval_match: left, right = float(interval_match.group(1)), float(interval_match.group(2)) if left <= original_value < right: return_value = str(row["Bin"]) break return return_value # return None # Create a function to generate reason codes def generate_reason_codes(instance, shap_values, feature_names, original_df, woe_df, optb_objects): instance_shap_values = shap_values.values[instance] sorted_indices = np.argsort(np.abs(instance_shap_values))[::-1] sorted_values = instance_shap_values[sorted_indices] sorted_feature_names = [feature_names[i] for i in sorted_indices] # print(sorted_feature_names) woe_buckets = [ optb_objects[feature].binning_table.build().loc[:, ["Bin", "Count", "Count (%)", "WoE"]] for feature in sorted_feature_names ] original_values_sorted = original_df.iloc[instance][sorted_indices] # print(original_values_sorted) corresponding_woe_buckets = [ get_woe_bucket(feature, original_value , optb_objects[feature]) for feature,original_value in zip(sorted_feature_names, original_values_sorted) ] # print(corresponding_woe_buckets) reason_codes = { "reasons": [ { "feature": feature, "original_value": original_value, "woe_value": woe_value, # "woe_bucket": woe_bucket, "corresponding_woe_bucket": corresponding_woe_buckets, "impact": shap_value } for feature, original_value, woe_value, corresponding_woe_buckets, shap_value in zip( sorted_feature_names, original_df.iloc[instance][sorted_indices], woe_df.iloc[instance][sorted_indices], # woe_buckets, corresponding_woe_buckets, sorted_values ) ] } return reason_codes # Process the dataset with OptBinning # Choose an instance from the test dataset for which you want to generate reason codes instance = 0 # Generate the reason codes for the chosen instance reason_codes = generate_reason_codes(instance, shap_values, data.feature_names, df.iloc[X_test.index], woe_df.iloc[X_test.index], optb_objects) # Display the reason codes print(reason_codes) {'reasons': [{'feature': 'mean concave points', 'original_value': 0.03821, 'woe_value': -1.1048177072776844, 'corresponding_woe_bucket': '[0.03, 0.05)', 'impact': 1.0140255442433102}, {'feature': 'worst perimeter', 'original_value': 96.05, 'woe_value': -2.491112068397574, 'corresponding_woe_bucket': '[87.37, 101.65)', 'impact': 1.002508592414365}, {'feature': 'worst concave points', 'original_value': 0.1015, 'woe_value': -1.906598728840426, 'corresponding_woe_bucket': '[0.09, 0.11)', 'impact': 0.7714232490527647}, {'feature': 'worst concavity', 'original_value': 0.2671, 'woe_value': 0.8030006592486145, 'corresponding_woe_bucket': '[0.26, 0.38)', 'impact': -0.7018065132671553}, {'feature': 'worst smoothness', 'original_value': 0.1426, 'woe_value': 0.5040550737483265, 'corresponding_woe_bucket': '[0.14, 0.15)', 'impact': -0.5958850659565252}, {'feature': 'worst area', 'original_value': 677.9, 'woe_value': -2.656904323240317, 'corresponding_woe_bucket': '[553.30, 691.75)', 'impact': 0.5273139784644225}, {'feature': 'worst radius', 'original_value': 14.97, 'woe_value': -2.656904323240317, 'corresponding_woe_bucket': '[13.22, 14.98)', 'impact': 0.3155327743268687}, {'feature': 'area error', 'original_value': 30.29, 'woe_value': -0.8759557701164795, 'corresponding_woe_bucket': '[22.12, 31.28)', 'impact': 0.2939706727482147}, {'feature': 'mean texture', 'original_value': 18.6, 'woe_value': 0.1884437532818903, 'corresponding_woe_bucket': '[18.46, 20.20)', 'impact': 0.2512034956533681}, {'feature': 'mean area', 'original_value': 481.9, 'woe_value': -2.180211705843788, 'corresponding_woe_bucket': '[390.60, 529.80)', 'impact': 0.23886420963043303}, {'feature': 'radius error', 'original_value': 0.3961, 'woe_value': 0.6998412958510026, 'corresponding_woe_bucket': '[0.38, 0.55)', 'impact': -0.1300811860157289}, {'feature': 'compactness error', 'original_value': 0.01911, 'woe_value': 0.07486240447920735, 'corresponding_woe_bucket': '[0.01, 0.02)', 'impact': 0.11771221918785298}, {'feature': 'worst texture', 'original_value': 24.64, 'woe_value': 0.2364129448856026, 'corresponding_woe_bucket': '[23.35, 29.30)', 'impact': 0.08330434898707008}, {'feature': 'mean radius', 'original_value': 12.47, 'woe_value': -1.6438142080103721, 'corresponding_woe_bucket': '[12.33, 13.09)', 'impact': -0.048831812029431945}, {'feature': 'worst fractal dimension', 'original_value': 0.0875, 'woe_value': 0.10129566154736286, 'corresponding_woe_bucket': '[0.08, 0.09)', 'impact': 0.02906002843415805}, {'feature': 'perimeter error', 'original_value': 2.497, 'woe_value': -0.6626205899007903, 'corresponding_woe_bucket': '[1.75, 2.76)', 'impact': 0.025539535517094845}, {'feature': 'concave points error', 'original_value': 0.01037, 'woe_value': -0.1600214824056033, 'corresponding_woe_bucket': '[0.01, 1000000000.0)', 'impact': 0.022289989505712746}, {'feature': 'mean smoothness', 'original_value': 0.09965, 'woe_value': 0.12615569886675773, 'corresponding_woe_bucket': '[0.09, 0.10)', 'impact': 0.02120193826867159}, {'feature': 'texture error', 'original_value': 1.044, 'woe_value': 0.45215663562067504, 'corresponding_woe_bucket': '[0.82, 1.08)', 'impact': -0.017274668655885797}, {'feature': 'fractal dimension error', 'original_value': 0.003586, 'woe_value': -0.04053453389185499, 'corresponding_woe_bucket': '[0.00, 0.01)', 'impact': -0.008231218777864029}, {'feature': 'concavity error', 'original_value': 0.02701, 'woe_value': 0.4746294914727335, 'corresponding_woe_bucket': '[0.02, 0.03)', 'impact': 0.006857779968393732}, {'feature': 'worst compactness', 'original_value': 0.2378, 'woe_value': 0.32907177536830745, 'corresponding_woe_bucket': '[0.20, 0.37)', 'impact': 0.006126590181754702}, {'feature': 'mean concavity', 'original_value': 0.08005, 'woe_value': 0.64129381894969, 'corresponding_woe_bucket': '[0.07, 0.12)', 'impact': -0.004604469863141343}, {'feature': 'symmetry error', 'original_value': 0.01782, 'woe_value': -0.09321679559920444, 'corresponding_woe_bucket': '(-1000000000.0, 0.02)', 'impact': -0.004145070495276751}, {'feature': 'worst symmetry', 'original_value': 0.3014, 'woe_value': 0.24630458141652653, 'corresponding_woe_bucket': '[0.28, 0.36)', 'impact': 0.0036792364983070187}, {'feature': 'mean symmetry', 'original_value': 0.1925, 'woe_value': 0.2109945788037871, 'corresponding_woe_bucket': '[0.17, 0.21)', 'impact': -0.002702521917813353}, {'feature': 'mean compactness', 'original_value': 0.1058, 'woe_value': 0.8449365842015237, 'corresponding_woe_bucket': '[0.10, 0.16)', 'impact': -0.0021190473675165513}, {'feature': 'mean perimeter', 'original_value': 81.09, 'woe_value': -2.203429995945793, 'corresponding_woe_bucket': '[75.01, 85.25)', 'impact': 0.0015084559097972466}, {'feature': 'smoothness error', 'original_value': 0.006953, 'woe_value': -0.03665632558255679, 'corresponding_woe_bucket': '[0.00, 0.01)', 'impact': 0.00030350262870593907}, {'feature': 'mean fractal dimension', 'original_value': 0.06373, 'woe_value': -0.2844756568790088, 'corresponding_woe_bucket': '[0.06, 0.07)', 'impact': 0.0}]}
В этом коде я использовал OptBinning для предварительной обработки переменных с кодировкой Weight of Evidence (WOE). Затем набор данных разделяется на наборы для обучения и тестирования, а классификатор повышения градиента применяется к обработанным переменным WOE. Наконец, пакет SHAP используется для генерации кодов причин для прогнозов модели.