CART (деревья классификации и регрессии) — популярный алгоритм машинного обучения, используемый как для задач классификации, так и для регрессии. Это тип алгоритма дерева решений, который рекурсивно разбивает данные на подмножества на основе значений входных функций, в конечном итоге создавая древовидную структуру, которую можно использовать для прогнозирования.
Преимущества CART:
- Интерпретируемость. Деревья решений, включая CART, представляют собой легко интерпретируемые модели. Вы можете легко понять и визуализировать процесс принятия решений в модели, что делает ее ценным инструментом для объяснения прогнозов заинтересованным сторонам.
- Никаких предположений относительно данных. CART не делает никаких предположений о распределении данных или взаимосвязях между функциями. Он может обрабатывать как категориальные, так и числовые функции, не требуя масштабирования функций.
- Управляет нелинейностью. CART может фиксировать сложные нелинейные связи в данных, что делает его пригодным для решения широкого круга задач.
- Важность функций. CART может предоставить информацию о важности функций, помогая вам определить, какие функции наиболее актуальны для прогнозирования.
- Универсальность. Его можно использовать как для задач классификации, так и для регрессии, что делает его универсальным алгоритмом.
Недостатки CART:
- Переоснащение. Деревья решений CART склонны к переоснащению, особенно когда им позволяют расти слишком глубоко. Переобучение происходит, когда дерево становится слишком сложным и соответствует шуму обучающих данных, что приводит к плохому обобщению на новые, невидимые данные.
- Нестабильность. Небольшие изменения в обучающих данных могут привести к существенному изменению древовидных структур. Это может привести к нестабильности, что сделает модель чувствительной к изменениям данных.
- Склонность к доминирующим классам: В задачах классификации с несбалансированными классами CART может создавать смещенные деревья, отдающие предпочтение большинству классов.
- Жадный алгоритм: CART использует жадный подход, что означает, что он принимает локально оптимальные решения при каждом разделении. Это не всегда может привести к созданию глобально оптимальных деревьев.
Некоторые гиперпараметры для CART:
max_depth
: Этот параметр управляет максимальной глубиной или пределом глубины дерева решений. Это может помочь предотвратить переобучение за счет ограничения сложности дерева.min_samples_split
: определяет минимальное количество выборок, необходимое для разделения внутреннего узла. Увеличение этого значения может предотвратить слишком раннее разбиение дерева, что снижает вероятность переобучения.min_samples_leaf
: Этот параметр задает минимальное количество выборок, необходимое для присутствия в конечном узле. Его можно использовать для управления размером дерева и предотвращения появления мелких конечных узлов, улавливающих шум.max_features
: определяет максимальное количество объектов, рассматриваемых для разделения на каждом узле. Это может помочь уменьшить сложность модели и улучшить обобщение.criterion
: Этот параметр определяет меру примеси, используемую для разделения узлов. Для классификации распространенными вариантами являются «джини» для примеси Джини и «энтропия» для получения информации. Для регрессии обычно используется mse (среднеквадратическая ошибка).min_impurity_decrease
: Определяет порог разделения узлов на основе уменьшения примесей. Разделение будет производиться только в том случае, если оно приведет к уменьшению примесей, превышающему этот порог.class_weight
(для классификации): позволяет присваивать веса классам, решая проблемы несбалансированности классов.
Настройка гиперпараметров имеет решающее значение для оптимизации производительности моделей CART и часто включает в себя такие методы, как перекрестная проверка, чтобы найти лучшую комбинацию гиперпараметров для вашей конкретной проблемы. max_depth
и min_samples_split
часто являются хорошей отправной точкой для настройки гиперпараметров, поскольку настройка этих двух гиперпараметров позволяет найти правильный баланс между сложностью модели и обобщением для вашей конкретной проблемы.
Теперь давайте рассмотрим эти два параметра немного подробнее:
max_depth
Гиперпараметр:
max_depth
управляет максимальной глубиной или высотой дерева решений.- Меньшее значение
max_depth
создает неглубокое дерево с меньшим количеством разбиений. - Большее значение
max_depth
позволяет сделать дерево более глубоким и сложным.
Влияние max_depth
:
a) Маленький max_depth
(например, 1 или 2):
- Создаёт простую границу принятия решения.
- Данные могут не соответствовать данным, поскольку они не могут улавливать сложные закономерности.
- Менее склонны к переобучению.
b) Средний max_depth
(например, 3 или 4):
- Уравновешивает сложность и способность фиксировать закономерности.
- Часто хорошая отправная точка для настройки.
c) Большое max_depth
(например, 10 или более):
- Может привести к переобучению данных за счет улавливания шума.
- Более склонен к переобучению.
min_samples_split
Гиперпараметр:
min_samples_split
контролирует минимальное количество выборок, необходимое для разделения внутреннего узла.- Меньшее значение
min_samples_split
позволяет узлам разделяться с меньшим количеством выборок. - Большее значение
min_samples_split
требует большего количества выборок для разделения узла.
Влияние min_samples_split
:
a) Маленький min_samples_split
(например, 2):
- позволяет узлам разделяться даже при очень небольшом количестве выборок.
- Создает более сложное дерево с множеством разбиений.
- Более склонно к переоснащению.
b) Средний min_samples_split
(например, 10 или 20):
- Баланс между сложностью и обобщением.
- Часто хороший выбор для предотвращения переобучения.
c) Большие min_samples_split
(например, 100 или более):
- для разделения узлов требуется значительное количество выборок.
- Создает более простое дерево с меньшим количеством разбиений.
- Помогает предотвратить переобучение.
Какова функция потерь в CART?
Для задач классификации обычно используется «джини» или «энтропия», а для регрессии обычно используется «mse». Это определяется параметром criterion
. Значение критерия по умолчанию для классификации — «джини», значение по умолчанию для регрессии — «squared_error».
Джини и энтропия являются мерами примеси. Итак, чем разнообразнее результаты выборок в узлах, тем выше Джини и энтропия. Например, предположим, что у нас есть два отдельных узла, и в каждом узле есть образец. Предположим, что выборка в первом узле равна (1 0 1 0 1), а выборка во втором узле — (1 1 1 1 1). В этом случае, поскольку второй узел имеет меньшую примесь, то есть меньшую неоднородность, значения Джини и энтропии будут рассчитываться ниже по сравнению с первым узлом.
Кодирование
Давайте немного запрограммируем, используя набор данных о диабете!
ПРИМЕЧАНИЕ: Поскольку здесь мы будем моделировать дерево решений классификации, мы импортируем DecisionTreeClassifier
из SciKit Learn. Однако для задач регрессии мы должны импортировать DecisionTreeRegressor
################################################ # Decision Tree Classification: CART ################################################ import warnings import joblib import pydotplus import numpy as np import pandas as pd import seaborn as sns from matplotlib import pyplot as plt from sklearn.tree import DecisionTreeClassifier, export_graphviz, export_text from sklearn.metrics import classification_report, roc_auc_score from sklearn.model_selection import train_test_split, GridSearchCV, cross_validate, validation_curve from sklearn.preprocessing import MinMaxScaler, RobustScaler from sklearn.impute import KNNImputer from skompiler import skompile import graphviz pd.set_option('display.max_columns', None) pd.set_option('display.width', 500) warnings.simplefilter(action='ignore', category=Warning) df = pd.read_csv("datasets/diabetes.csv") #################################### # FUNCTIONS #################################### def outlier_thresholds(dataframe ,col ,q1=.05 , q3=.95, decimal=3): quartile1=dataframe[col].quantile(q1) quartile3=dataframe[col].quantile(q3) iqr=quartile3-quartile1 low_limit= round(quartile1 - (iqr*1.5) , decimal) up_limit= round(quartile3 + (iqr*1.5), decimal) return low_limit , up_limit def replace_with_thresholds(dataframe, col_name, q1=.05, q3=.95, lower_limit = None, upper_limit = None): low_limit, up_limit = outlier_thresholds(dataframe, col_name, q1, q3) if lower_limit != None: dataframe.loc[(dataframe[col_name] < lower_limit), col_name] = lower_limit else: dataframe.loc[(dataframe[col_name] < low_limit), col_name] = low_limit if upper_limit != None: dataframe.loc[(dataframe[col_name] > upper_limit), col_name] = upper_limit else: dataframe.loc[(dataframe[col_name] > up_limit), col_name] = up_limit def plot_importance(model, features, num:int = 0, save=False): if num <= 0: num = X.shape[1] feature_imp = pd.DataFrame({'Value': model.feature_importances_, 'Feature': features.columns}) plt.figure(figsize=(10, 10)) sns.set(font_scale=1) sns.barplot(x="Value", y="Feature", data=feature_imp.sort_values(by="Value", ascending=False)[0:num]) plt.title('Features') plt.tight_layout() plt.show() if save: plt.savefig('importances.png') def val_curve_params(model, X, y, param_name, param_range, scoring="accuracy", cv=10): train_score, test_score = validation_curve( model, X=X, y=y, param_name=param_name, param_range=param_range, scoring=scoring, cv=cv) mean_train_score = np.mean(train_score, axis=1) mean_test_score = np.mean(test_score, axis=1) plt.plot(param_range, mean_train_score, label="Training Score", color='b') plt.plot(param_range, mean_test_score, label="Validation Score", color='g') plt.title(f"Validation Curve for {type(model).__name__}") plt.xlabel(f"Number of {param_name}") plt.ylabel(f"{scoring}") plt.tight_layout() plt.legend(loc='best') plt.show(block=True) def tree_graph(model, col_names, file_name): tree_str = export_graphviz(model, feature_names=col_names, filled=True, out_file=None) graph = pydotplus.graph_from_dot_data(tree_str) graph.write_png(file_name) ########################################### # DATA PREPROCESSING ########################################### #Replacing outliers with these values. cols = [col for col in df.columns if col != "Outcome"] for col in cols: replace_with_thresholds(df, col) #Columns that cannot contain zero problematic_cols = [col for col in df.columns if col not in ["Pregnancies",'DiabetesPedigreeFunction','Outcome']] #Now replace these zeros with NaN for col in problematic_cols: df[col]=df[col].replace(0,np.nan) # Filling NaN values by using KNN Imputer scaler=MinMaxScaler() df=pd.DataFrame(scaler.fit_transform(df), columns=df.columns) imputer=KNNImputer(n_neighbors=5) df=pd.DataFrame(imputer.fit_transform(df), columns=df.columns) df=pd.DataFrame(scaler.inverse_transform(df), columns=df.columns) ################################################## # FEATURE ENGINEERING ################################################## df.loc[(df["Age"] <= 18 ), "NEW_AGE"] = "young" df.loc[(df["Age"] > 18 ) & (df["Age"] <= 24), "NEW_AGE"] = "adult" df.loc[(df["Age"] > 24 ) & (df["Age"] <= 59), "NEW_AGE"] = "mid_adult" df.loc[(df["Age"] > 59), "NEW_AGE"] = "senior" df.loc[(df["BMI"] < 18.5) , "BMI_CAT"] ="underweight" df.loc[(df["BMI"] >= 18.5) & (df["BMI"] < 24.9) , "BMI_CAT"] ="normal" df.loc[(df["BMI"] >= 24.9) & (df["BMI"] < 29.9) , "BMI_CAT"]="overweight" df.loc[(df["BMI"] >= 29.9) , "BMI_CAT"] ="obese" df.loc[(df["Insulin"] < 15) , "INSULIN_CAT"] ="low" df.loc[(df["Insulin"] >= 15) & (df["Insulin"] < 166) , "INSULIN_CAT"] ="normal" df.loc[(df["Insulin"] >= 166) , "INSULIN_CAT"] ="high" # One Hot Encoding ohe_cols = [col for col in df.columns if 10 >= df[col].nunique() > 2] df= pd.get_dummies(df,columns= ohe_cols, drop_first=True) X = df.drop(["Outcome"], axis=1) y = df["Outcome"]
У нас есть некоторые дополнительные функции кандидата. Мы выберем некоторые из них с помощью функции Feature_selecter(),
def feature_selecter(input_x, y, candidate_features_dict:dict, candidate_features_id:list, best_features:list, best_accuracy=0, verbose=True): if not candidate_features_id: return best_accuracy, best_features best_x = input_x best_feature= -1 if best_accuracy == 0: cart_model = DecisionTreeClassifier(random_state=17).fit(input_x, y) cv_results = cross_validate(cart_model, input_x, y, cv=5, scoring="accuracy") best_accuracy = cv_results["test_score"].mean() if verbose: print(f"best accuracy(old) = {best_accuracy}") #print(candidate_features_id) for feature in candidate_features_id: X = input_x.copy(deep=True) # define your candidate feature here! if feature == 0: X[candidate_features_dict[feature]] = X["Insulin"]*X["Glucose"] elif feature == 1: X[candidate_features_dict[feature]] = X["Glucose"]/(X["Insulin"]+0.0001) elif feature == 2: X[candidate_features_dict[feature]] = X["Age"]*X["Pregnancies"] elif feature == 3: X[candidate_features_dict[feature]] = X["Age"]/(X["Pregnancies"]+0.0001) elif feature == 4: X[candidate_features_dict[feature]] = X["Age"]*X["Pregnancies"]*X["Glucose"] elif feature == 5: X[candidate_features_dict[feature]] = X["Glucose"]/(X["Age"]+0.0001) elif feature == 6: X[candidate_features_dict[feature]] = X["Insulin"]/(X["Age"]+0.0001) elif feature == 7: X[candidate_features_dict[feature]] = X["BMI"]*X["Pregnancies"] elif feature == 8: X[candidate_features_dict[feature]] = X["BMI"]*X["Age"] elif feature == 9: X[candidate_features_dict[feature]] = X["BMI"]*(X["Age"])*X["Pregnancies"] elif feature == 10: X[candidate_features_dict[feature]] = X["BMI"]*(X["Glucose"]) elif feature == 11: X[candidate_features_dict[feature]] = X["DiabetesPedigreeFunction"]*(X["Insulin"]) elif feature == 12: X[candidate_features_dict[feature]] = X["SkinThickness"]*(X["Insulin"]) elif feature == 13: X[candidate_features_dict[feature]] = X["Pregnancies"]/(X["Age"]+0.0001) elif feature == 14: X[candidate_features_dict[feature]] = X["Glucose"]+X["Insulin"]+X["SkinThickness"] elif feature == 15: X[candidate_features_dict[feature]] = X["BloodPressure"]/(X["Glucose"]+0.0001) cart_model = DecisionTreeClassifier(random_state=17).fit(X, y) cv_results = cross_validate(cart_model, X, y, cv=5, scoring="accuracy") accuracy = cv_results["test_score"].mean() if accuracy > best_accuracy: best_accuracy = accuracy best_feature = feature best_x = X if best_feature == -1: return best_accuracy, best_features best_features.append(best_feature) candidate_features_id.remove(best_feature) if verbose: print(f"best accuracy(new) = {best_accuracy}") print(f"added feature = {best_feature}", end = '\n\n') #print(best_features) return feature_selecter(best_x, y, candidate_features_dict, candidate_features_id, best_features, best_accuracy, verbose)
Затем запустите функцию Feature_selecter(),
candidate_features = {0:"new_glucoseXinsulin", 1:"new_glucose/insulin", 2:"new_ageXpreg", 3:"new_age/preg", 4:"new_ageXpregXglucose", 5:"new_glucose/age", 6:"new_insulin/age", 7:"new_bmiXpreg", 8:"new_bmiXage", 9:"new_bmiXageXpreg", 10:"new_bmiXglucose", 11:"new_degreeXinsulin", 12:"new_skinXinsulin", 13:"new_preg/age", 14:"new_glucose+insulin+skin", 15:"new_blood/glucose"} accuracy, new_features = feature_selecter(X,y,candidate_features, list(candidate_features.keys()), best_features=[]) ''' best accuracy(old) = 0.7291401409048468 best accuracy(new) = 0.7539003480179951 added feature = 15 best accuracy(old) = 0.7539003480179951 ''' #See which feature is selected. for feature in new_features: print(candidate_features[feature]) #new_blood/glucose #Add this newly created feature. X["new_blood/glucose"] = X["BloodPressure"]/(X["Glucose"]+0.0001) ################################################## # MODEL BUILDING AND EVALUATION ################################################## #Build the CART model. cart_model = DecisionTreeClassifier(random_state=17).fit(X, y) #Evaluate with 5-Fold CV cv_results = cross_validate(cart_model, X, y, cv=5, scoring=["accuracy", "precision", "recall", "f1", "roc_auc"]) print(f"Accuracy : {cv_results['test_accuracy'].mean()}") # 0.7539 print(f"Precision : {cv_results['test_precision'].mean()}") # 0.6467 print(f"Recall : {cv_results['test_recall'].mean()}") # 0.6605 print(f"F1 Score : {cv_results['test_f1'].mean()}") # 0.6515 print(f"ROC AUC : {cv_results['test_roc_auc'].mean()}") # 0.7322 ################################################## # HYPERPARAMETER OPTIMIZATION ################################################## #We will use GridSearchCV to find the optimal hyperparameters. #First, see the current parameters. cart_model.get_params() ''' {'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': None, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'random_state': 17, 'splitter': 'best'} ''' #Let's use GridSearchCV for the max_depth and min_samples_split parameters. #Initially, max_depth = None and min_samples_split = 2. We will try max_depth #from 1 to 10, min_samples_split from 2 to 19 and find the best parameter #values that give the highest accuracy. cart_params = {'max_depth': range(1, 11), "min_samples_split": range(2, 20)} cart_best_grid = GridSearchCV(cart_model, cart_params, cv=5, n_jobs=-1, verbose=True, scoring="accuracy").fit(X, y) ''' Fitting 5 folds for each of 180 candidates, totalling 900 fits ''' print(cart_best_grid.best_params_) #{'max_depth': 7, 'min_samples_split': 4} #So it seems when max_depth = 7 and min_samples_split = 4, we obtain the #highest accuracy. Let's the accuracy after this hyperparameter tuning print(cart_best_grid.best_score_) # accuracy : 0.7629 ################################################ # FINAL MODEL ################################################ #Build the final model cart_final = DecisionTreeClassifier(**cart_best_grid.best_params_, random_state=17).fit(X, y) #See our newly updated parameters, max_depth = 7, min_samples_split = 4 cart_final.get_params() ''' {'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': 7, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 4, 'min_weight_fraction_leaf': 0.0, 'random_state': 17, 'splitter': 'best'} ''' cv_results = cross_validate(cart_final, X, y, cv=5, scoring=["accuracy", "precision", "recall", "f1", "roc_auc"]) print(f"Accuracy : {cv_results['test_accuracy'].mean()}") # 0.7629 print(f"Precision : {cv_results['test_precision'].mean()}") # 0.6674 print(f"Recall : {cv_results['test_recall'].mean()}") # 0.6681 print(f"F1 Score : {cv_results['test_f1'].mean()}") # 0.6621 print(f"ROC AUC : {cv_results['test_roc_auc'].mean()}") # 0.7667
В результате окончательные показатели производительности выглядят следующим образом: ??? ??? Точность: 0.6674
Вспомнить > ... 0.6681 Оценка F1 : 0,6621
ROC AUC : 0,7667
################################################ # FEATURE IMPORTANCE ################################################ #See the most important features. plot_importance(cart_final, X, save=False) #IMAGE IS BELOW (importance.png)
################################################ # ANALYZING MODEL COMPLEXITY WITH LEARNING CURVES ################################################ val_curve_params(cart_final, X, y, "max_depth", range(1, 11), scoring="accuracy") #IMAGE IS BELOW (validation_curve.png)
С помощью GridSearchCV мы обнаружили, что наиболее оптимальное значение max_глубины равно 7. Глядя на кривую проверки выше, вы можете задаться вопросом, почему оптимальное значение max_глубины не равно 8. Это связано с тем, что приведенный выше график зависит только от параметра max_глубина, но мы использовали GridSearchCV с параметрами max_length и min_samples_split. Итак, по своей сути алгоритм GridSearchCV получил таблицу, отличную от приведенной выше, и лучшие значения гиперпараметров для таблицы GridSearchCV — max_length = 7 и min_samples_split = 4.
Теперь продолжим.
################################################ # VISUALIZING THE DECISION TREE ################################################ #We can visualize the decision tree we obtained using the CART algorithm. #The tree_graph function will create an image named "cart_final.png" in #the directory. tree_graph(model=cart_final, col_names=X.columns, file_name="cart_final.png") # IMAGE IS SAVED AS "cart_final.png" (look at the directory)
################################################ # EXTRACTING DECISION RULES ################################################ #We can also express the decision tree in the "cart_final.png" picture above #with text or code. #Text tree_rules = export_text(cart_final, feature_names=list(X.columns)) print(tree_rules) ''' |--- Insulin <= 127.80 | |--- Glucose <= 123.50 | | |--- BMI <= 50.90 | | | |--- Insulin <= 110.10 | | | | |--- DiabetesPedigreeFunction <= 0.75 | | | | | |--- new_blood/glucose <= 0.64 | | | | | | |--- new_blood/glucose <= 0.64 | | | | | | | |--- class: 0.0 | | | | | | |--- new_blood/glucose > 0.64 | | | | | | | |--- class: 1.0 | | | | | |--- new_blood/glucose > 0.64 | | | | | | |--- Insulin <= 36.50 | | | | | | | |--- class: 0.0 | | | | | | |--- Insulin > 36.50 | | | | | | | |--- class: 0.0 | | | | |--- DiabetesPedigreeFunction > 0.75 | | | | | |--- BMI <= 32.70 | | | | | | |--- class: 0.0 | | | | | |--- BMI > 32.70 | | | | | | |--- DiabetesPedigreeFunction <= 0.88 | | | | | | | |--- class: 1.0 | | | | | | |--- DiabetesPedigreeFunction > 0.88 | | | | | | | |--- class: 0.0 | | | |--- Insulin > 110.10 | | | | |--- Insulin <= 110.30 | | | | | |--- class: 1.0 | | | | |--- Insulin > 110.30 | | | | | |--- BMI <= 45.40 | | | | | | |--- BMI <= 33.85 | | | | | | | |--- class: 0.0 | | | | | | |--- BMI > 33.85 | | | | | | | |--- class: 0.0 | | | | | |--- BMI > 45.40 | | | | | | |--- class: 1.0 | | |--- BMI > 50.90 | | | |--- class: 1.0 | |--- Glucose > 123.50 | | |--- BMI <= 26.30 | | | |--- class: 0.0 | | |--- BMI > 26.30 | | | |--- DiabetesPedigreeFunction <= 0.71 | | | | |--- new_blood/glucose <= 0.62 | | | | | |--- BMI <= 27.65 | | | | | | |--- class: 1.0 | | | | | |--- BMI > 27.65 | | | | | | |--- BMI <= 36.20 | | | | | | | |--- class: 0.0 | | | | | | |--- BMI > 36.20 | | | | | | | |--- class: 1.0 | | | | |--- new_blood/glucose > 0.62 | | | | | |--- class: 0.0 | | | |--- DiabetesPedigreeFunction > 0.71 | | | | |--- class: 1.0 |--- Insulin > 127.80 | |--- Glucose <= 154.50 | | |--- NEW_AGE_mid_adult <= 0.50 | | | |--- SkinThickness <= 25.60 | | | | |--- Age <= 69.50 | | | | | |--- class: 0.0 | | | | |--- Age > 69.50 | | | | | |--- class: 0.0 | | | |--- SkinThickness > 25.60 | | | | |--- Insulin <= 154.50 | | | | | |--- BMI <= 42.05 | | | | | | |--- Pregnancies <= 7.00 | | | | | | | |--- class: 0.0 | | | | | | |--- Pregnancies > 7.00 | | | | | | | |--- class: 1.0 | | | | | |--- BMI > 42.05 | | | | | | |--- class: 1.0 | | | | |--- Insulin > 154.50 | | | | | |--- Insulin <= 281.80 | | | | | | |--- Glucose <= 127.50 | | | | | | | |--- class: 0.0 | | | | | | |--- Glucose > 127.50 | | | | | | | |--- class: 1.0 | | | | | |--- Insulin > 281.80 | | | | | | |--- class: 0.0 | | |--- NEW_AGE_mid_adult > 0.50 | | | |--- BMI <= 26.25 | | | | |--- class: 0.0 | | | |--- BMI > 26.25 | | | | |--- Age <= 42.50 | | | | | |--- Insulin <= 133.80 | | | | | | |--- Pregnancies <= 4.00 | | | | | | | |--- class: 1.0 | | | | | | |--- Pregnancies > 4.00 | | | | | | | |--- class: 1.0 | | | | | |--- Insulin > 133.80 | | | | | | |--- Insulin <= 146.30 | | | | | | | |--- class: 0.0 | | | | | | |--- Insulin > 146.30 | | | | | | | |--- class: 1.0 | | | | |--- Age > 42.50 | | | | | |--- Insulin <= 143.80 | | | | | | |--- BloodPressure <= 74.00 | | | | | | | |--- class: 1.0 | | | | | | |--- BloodPressure > 74.00 | | | | | | | |--- class: 0.0 | | | | | |--- Insulin > 143.80 | | | | | | |--- Pregnancies <= 0.50 | | | | | | | |--- class: 0.0 | | | | | | |--- Pregnancies > 0.50 | | | | | | | |--- class: 1.0 | |--- Glucose > 154.50 | | |--- BMI_CAT_overweight <= 0.50 | | | |--- Insulin <= 544.00 | | | | |--- SkinThickness <= 13.50 | | | | | |--- class: 0.0 | | | | |--- SkinThickness > 13.50 | | | | | |--- BMI <= 22.60 | | | | | | |--- class: 0.0 | | | | | |--- BMI > 22.60 | | | | | | |--- BMI <= 46.10 | | | | | | | |--- class: 1.0 | | | | | | |--- BMI > 46.10 | | | | | | | |--- class: 1.0 | | | |--- Insulin > 544.00 | | | | |--- BloodPressure <= 69.00 | | | | | |--- class: 1.0 | | | | |--- BloodPressure > 69.00 | | | | | |--- class: 0.0 | | |--- BMI_CAT_overweight > 0.50 | | | |--- NEW_AGE_mid_adult <= 0.50 | | | | |--- class: 0.0 | | | |--- NEW_AGE_mid_adult > 0.50 | | | | |--- SkinThickness <= 28.90 | | | | | |--- class: 1.0 | | | | |--- SkinThickness > 28.90 | | | | | |--- class: 0.0 ''' #Python print(skompile(cart_final.predict).to('python/code')) ''' (((((((0 if x[15] <= 0.6409004628658295 else 1) if x[15] <= 0.6422942876815796 else 0 if x[4] <= 36.5 else 0) if x[6] <= 0.7525000274181366 else 0 if x[5] <= 32.70000076293945 else 1 if x[6] <= 0.8784999847412109 else 0) if x[4] <= 110.0999984741211 else 1 if x[4] <= 110.29999923706055 else (0 if x[5] <= 33.849998474121094 else 0) if x[5 ] <= 45.39999961853027 else 1) if x[5] <= 50.89999961853027 else 1) if x[1] <= 123.5 else 0 if x[5] <= 26.300000190734863 else ((1 if x[5] <= 27.65000057220459 else 0 if x[5] <= 36.20000076293945 else 1) if x[15] <= 0.6183468401432037 else 0) if x[6] <= 0.7114999890327454 else 1) if x[4 ] <= 127.79999923706055 else (((0 if x[7] <= 69.5 else 0) if x[3] <= 25.59999942779541 else ((0 if x[0] <= 7.0 else 1) if x[5] <= 42.04999923706055 else 1) if x[4] <= 154.5 else (0 if x[1] <= 127.5 else 1) if x[4] <= 281.8000030517578 else 0) if x[8] <= 0.5 else 0 if x[5] <= 26.25 else ((1 if x[0] <= 4.0 else 1) if x[4] <= 133.8000030517578 else 0 if x[4] <= 146.29999542236328 else 1) if x[7] <= 42.5 else (1 if x[2] <= 74.0 else 0) if x[4] <= 143.8000030517578 else 0 if x[0] <= 0.5 else 1) if x[1] <= 154.5 else ((0 if x[3] <= 13.5 else 0 if x[5] <= 22.59999942779541 else 1 if x[5] <= 46.10000038146973 else 1) if x[4] <= 544.0 else 1 if x[2] <= 69.0 else 0) if x[11] <= 0.5 else 0 if x[8] <= 0.5 else 1 if x[3] <= 28.899999618530273 else 0) ''' #SQL print(skompile(cart_final.predict).to('sqlalchemy/sqlite')) ''' SELECT CASE WHEN (x5 <= 127.79999923706055) THEN CASE WHEN (x2 <= 123.5) THEN CASE WHEN (x6 <= 50.89999961853027) THEN CASE WHEN (x5 <= 110.0999984741211) THEN CASE WHEN (x7 <= 0.7525000274181366) THEN CASE WHEN (x16 <= 0.6422942876815796) THEN CASE WHEN (x16 <= 0.6409004628658295) THEN 0 ELSE 1 END ELSE 0 END ELSE CASE WHEN (x6 <= 32.70000076293945) THEN 0 ELSE CASE WHEN (x7 <= 0.8784999847412109) THEN 1 ELSE 0 END END END ELSE CASE WHEN (x5 <= 110.29999923706055) THEN 1 ELSE CASE WHEN (x6 <= 45.39999961853027) THEN 0 ELSE 1 END END END ELSE 1 END ELSE CASE WHEN (x6 <= 26.300000190734863) THEN 0 ELSE CASE WHEN (x7 <= 0.7114999890327454) THEN CASE WHEN (x16 <= 0.6183468401432037) THEN CASE WHEN (x6 <= 27.65000057220459) THEN 1 ELSE CASE WHEN (x6 <= 36.20000076293945) THEN 0 ELSE 1 END END ELSE 0 END ELSE 1 END END END ELSE CASE WHEN (x2 <= 154.5) THEN CASE WHEN (x9 <= 0.5) THEN CASE WHEN (x4 <= 25.59999942779541) THEN 0 ELSE CASE WHEN (x5 <= 154.5) THEN CASE WHEN (x6 <= 42.04999923706055) THEN CASE WHEN (x1 <= 7.0) THEN 0 ELSE 1 END ELSE 1 END ELSE CASE WHEN (x5 <= 281.8000030517578) THEN CASE WHEN (x2 <= 127.5) THEN 0 ELSE 1 END ELSE 0 END END END ELSE CASE WHEN (x6 <= 26.25) THEN 0 ELSE CASE WHEN (x8 <= 42.5) THEN CASE WHEN (x5 <= 133.8000030517578) THEN 1 ELSE CASE WHEN (x5 <= 146.29999542236328) THEN 0 ELSE 1 END END ELSE CASE WHEN (x5 <= 143.8000030517578) THEN CASE WHEN (x3 <= 74.0) THEN 1 ELSE 0 END ELSE CASE WHEN (x1 <= 0.5) THEN 0 ELSE 1 END END END END END ELSE CASE WHEN (x12 <= 0.5) THEN CASE WHEN (x5 <= 544.0) THEN CASE WHEN (x4 <= 13.5) THEN 0 ELSE CASE WHEN (x6 <= 22.59999942779541) THEN 0 ELSE 1 END END ELSE CASE WHEN (x3 <= 69.0) THEN 1 ELSE 0 END END ELSE CASE WHEN (x9 <= 0.5) THEN 0 ELSE CASE WHEN (x4 <= 28.899999618530273) THEN 1 ELSE 0 END END END END END AS y FROM data ''' #MS Excel print(skompile(cart_final.predict).to('excel')) #A1=IF((x5<=127.79999923 ...922 chars skipped... 9618530273),1,0))))) ################################################ # PREDICTION USING PYTHON CODES ################################################ #We can directly predict an X value we enter by the tree rules we obtain. #The X value here can be a list, it does not need to be a pandas dataframe. def predict_with_rules(x): return (((((((0 if x[15] <= 0.6409004628658295 else 1) if x[15] <= 0.6422942876815796 else 0 if x[4] <= 36.5 else 0) if x[6] <= 0.7525000274181366 else 0 if x[5] <= 32.70000076293945 else 1 if x[6] <= 0.8784999847412109 else 0) if x[4] <= 110.0999984741211 else 1 if x[4] <= 110.29999923706055 else (0 if x[5] <= 33.849998474121094 else 0) if x[5 ] <= 45.39999961853027 else 1) if x[5] <= 50.89999961853027 else 1) if x[1] <= 123.5 else 0 if x[5] <= 26.300000190734863 else ((1 if x[5] <= 27.65000057220459 else 0 if x[5] <= 36.20000076293945 else 1) if x[15] <= 0.6183468401432037 else 0) if x[6] <= 0.7114999890327454 else 1) if x[4 ] <= 127.79999923706055 else (((0 if x[7] <= 69.5 else 0) if x[3] <= 25.59999942779541 else ((0 if x[0] <= 7.0 else 1) if x[5] <= 42.04999923706055 else 1) if x[4] <= 154.5 else (0 if x[1] <= 127.5 else 1) if x[4] <= 281.8000030517578 else 0) if x[8] <= 0.5 else 0 if x[5] <= 26.25 else ((1 if x[0] <= 4.0 else 1) if x[4] <= 133.8000030517578 else 0 if x[4] <= 146.29999542236328 else 1) if x[7] <= 42.5 else (1 if x[2] <= 74.0 else 0) if x[4] <= 143.8000030517578 else 0 if x[0] <= 0.5 else 1) if x[1] <= 154.5 else ((0 if x[3] <= 13.5 else 0 if x[5] <= 22.59999942779541 else 1 if x[5] <= 46.10000038146973 else 1) if x[4] <= 544.0 else 1 if x[2] <= 69.0 else 0) if x[11] <= 0.5 else 0 if x[8] <= 0.5 else 1 if x[3] <= 28.899999618530273 else 0) #However, we added some new columns during feature engineering. For example #NEW_AGE, new_blood/glucose. While we originally had 8 independent columns, #now we have 16 independent columns. Therefore, an X value that we enter as #input must have these new features. That's why we write a function called #converter() and this function converts the original 8 feature sample #into 16 features. def converter(inp): if inp[7] <= 18: inp += [1,1] elif (inp[7] > 18) & (inp[7] <= 24): inp += [0,0] elif (inp[7] > 24) & (inp[7] <= 59): inp += [1,0] elif inp[7] >= 60: inp += [0,1] if inp[5] < 18.5: inp += [0,0,1] elif (inp[5] >= 18.5) & (inp[5] < 24.9): inp += [0,0,0] elif (inp[5] >= 24.9) & (inp[5] < 29.9): inp += [0,1,0] elif inp[5] >= 29.9: inp += [1,0,0] if inp[4] < 15: inp += [1,0] elif (inp[4] >= 15) & (inp[4] < 166): inp += [0,1] elif inp[4] >= 166: inp += [0,0] inp.append(inp[2]/(inp[1]+0.0001)) return inp #Let's make an example. x = [12, 13, 20, 23, 4, 55, 12, 7] x = converter(x) print(x) # [12, 13, 20, 23, 4, 55, 12, 7, 1, 1, 1, 0, 0, 1, 0, 1.5384497042330443] print(predict_with_rules(x)) # 1 #Another example. x = [6, 148, 70, 35, 0, 30, 0.62, 50] x = converter(x) print(x) # [6, 148, 70, 35, 0, 30, 0.62, 50, 1, 0, 1, 0, 0, 1, 0, 0.4729726533968558] print(predict_with_rules(x)) # 0 ################################################ # SAVING AND LOADING MODEL ################################################ #Let's say you created a good model and you want to share that model with #other people. In this case, you can save the final model as a pkl file and #share with other people. #Save model as pkl file joblib.dump(cart_final, "cart_final.pkl") #You can also load a model with a pkl extension in your directory. #Load model from pkl file cart_model_from_disc = joblib.load("cart_final.pkl") #And you can use the model.. x = [12, 13, 20, 23, 4, 55, 12, 7] x = converter(x) #since you are using a sklearn model, you also need to convert it pandas df. x = pd.DataFrame(x).T print(cart_model_from_disc.predict(x)) # [1.]