Линейная регрессия, простейшая из техник машинного обучения, прекрасна и работает очень хорошо. На основе непрерывного числового ввода это просто и понятно, как фундаментальная истина. Логистическая регрессия (классификация) так же проста и работает одинаково хорошо, просто заменяя категорию 1 или 0. Мы больше не можем моделировать данные с помощью линии, а вместо этого должны использовать сигмовидную функцию (которая выглядит как S на картезианской карте). система координат) и экстраполирует, что целью является любое из рассматриваемых чисел. Как очень компьютер этого сделать так. Это напоминает вам, что для компьютера все равно 1 или 0. Однако в тайне сфинкса должно быть нечто большее. Разве нам не нужна более сложная модель для решения чрезвычайно сложных проблем? По мере того, как мы погружаемся в более глубокие и сложные проблемы, наша модель приближается к пропасти сложности. Почему да сфинкс, я вижу тебя сейчас. Как мы разгадаем эту загадку?

Давайте отступим и пересмотрим. Линейная/логистическая регрессия предоставляет линию/плоскость/сигмоид для соответствия данным. В свою очередь, вы можете использовать эту линию/плоскость/сигмоид, чтобы предсказать, что будет (например, цена, время приготовления и т. д.) на основе того, что есть (например, размер дома, вид мяса). Это означает, что если бы у вас был кусок говядины весом 1 фунт и данные о времени приготовления различных видов мяса, вы могли бы предсказать, как долго его готовить. Для более сложных задач это соотношение 1:1 больше не будет в достаточной степени описывать реальный мир. Поскольку слишком много переменных (то, что мы называем функциями в машинном обучении) мешают обработке данных машиной, эта простая модель не работает. Давайте рассмотрим классическую проблему, которая слишком сложна для линейной регрессии и слишком важна, чтобы оставить ее без ответа. Если у человека есть опухоль, можем ли мы предсказать, является ли она злокачественной или доброкачественной. Я хотел бы немного отвлечься и поблагодарить доктора Савара за то, что он был одним из самых трудолюбивых профессоров, которых я встречал в колледже (я был одним из трех, получивших пятерку в его классе), а также за то, что он заявил, что если вы «действительно хотите помогать людям идти в инженерию». Я перефразирую, и я уверен, что он уже мертв, так что покойся с миром.

Со всеми характеристиками, такими как размер, плотность, возраст человека, пол человека, был ли он курильщиком и т. д., линия/плоскость/сигмовидная перестает быть предсказательной. Как все эти функции способствуют достижению конечной цели? Деревья решений — один из способов решить эту проблему, и они достаточно просты для понимания. С помощью бинарных деревьев решений вы делите данные на две группы на основе критерия до самого низа. Каждое разделение будет основываться на различных критериях. Это может быть мужчина/женщина, размер опухоли больше X, плотность указанной опухоли. Какое бы деление ни показало, что оно дает нам наилучшее начальное улучшение, мы начнем деление, и это будет нашим корневым узлом. Из этого логического значения вы разделяете данные на две части. Дерево будет иметь истинную ветвь и ложную ветвь. Теперь у нас есть два подмножества данных. Мы снова и снова разделяем данные из этих поддеревьев по разным критериям. Этот процесс происходит на всем пути вниз. Нам понадобится мера того, что является улучшением на каждом шаге вниз.

Классическим показателем является примесь Джини (это сообщение в блоге отлично подходит для Джини). Если коэффициент Джини подгруппы лучше, чем примесь Джини исходного набора данных, разделение было улучшением. Это очень похоже на банковскую систему на финансовых рынках, поскольку она необходима даже для существования. Без возможности измерить, полезно ли разделение данных на две подгруппы для прогнозирования, этот метод был бы невозможен.

Мы делаем много настройки в этом блоге. Я думаю, что это отражение того, что я заметил, что если вы сначала соберете свои идеи воедино, после того, как вы действительно начнете кодировать, будет меньше ошибок. Это также очень похоже на EDA, который необходим для машинного обучения и последующей очистки данных. Теперь, когда мы говорили о метафизике, давайте перейдем к хорошему. КАК?

Давайте импортируем набор данных о сердечных приступах из Irvine Learning Repository, очистим его от визуализации EDA, а затем разделим данные для тестирования и обучения.

Это набор данных о людях, перенесших сердечные приступы. Цель 1 указывает, что у них был один, а 0 — нет.

Первое дерево решений, созданное DecisionTreeClassifier sklearn, показало себя с точностью 74% предсказуемости. Однако 100% точность тренировочных данных говорит нам о многом.

Это говорит нам о том, что модель настолько плотно прилегает, что не подходит.

Давайте нарисуем дерево и просто посмотрим на него, прежде чем мы будем модулировать гиперпараметры.

fea = list(X.columns)
feature_names = fea
plt.figure(figsize=(20,30))
out = tree.plot_tree(myTree,feature_names=fea,filled=True,fontsize=9,node_ids=True,class_names=True)
for o in out:
     arrow = o.arrow_patch
     if arrow is not None:
        arrow.set_edgecolor('black')
        arrow.set_linewidth(1)
plt.show()

Это очень глубокое дерево решений и переоснащение, как мы можем догадаться по 100% точности обучающих данных. Так глубоко, что мы даже не смогли захватить все это здесь. Мы могли бы экспортировать его как собственный файл. Однако мы также можем просто напечатать его:

print(tree.export_text(myTree,feature_names=fea,show_weights=True))
#=>
|--- cp <= 0.50
|   |--- exang <= 0.50
|   |   |--- ca <= 0.50
|   |   |   |--- thal <= 2.50
|   |   |   |   |--- thalach <= 96.50
|   |   |   |   |   |--- weights: [1.00, 0.00] class: 0
|   |   |   |   |--- thalach >  96.50
|   |   |   |   |   |--- weights: [0.00, 17.00] class: 1
|   |   |   |--- thal >  2.50
|   |   |   |   |--- restecg <= 0.50
|   |   |   |   |   |--- weights: [2.00, 0.00] class: 0
|   |   |   |   |--- restecg >  0.50
|   |   |   |   |   |--- age <= 41.00
|   |   |   |   |   |   |--- weights: [1.00, 0.00] class: 0
|   |   |   |   |   |--- age >  41.00
|   |   |   |   |   |   |--- weights: [0.00, 3.00] class: 1
|   |   |--- ca >  0.50
|   |   |   |--- trestbps <= 109.00
|   |   |   |   |--- chol <= 233.50
|   |   |   |   |   |--- weights: [0.00, 2.00] class: 1
|   |   |   |   |--- chol >  233.50
|   |   |   |   |   |--- weights: [1.00, 0.00] class: 0
|   |   |   |--- trestbps >  109.00
|   |   |   |   |--- weights: [16.00, 0.00] class: 0
|   |--- exang >  0.50
|   |   |--- thalach <= 166.50
|   |   |   |--- thalach <= 106.50
|   |   |   |   |--- oldpeak <= 1.00
|   |   |   |   |   |--- weights: [0.00, 1.00] class: 1
|   |   |   |   |--- oldpeak >  1.00
|   |   |   |   |   |--- weights: [4.00, 0.00] class: 0
|   |   |   |--- thalach >  106.50
|   |   |   |   |--- weights: [48.00, 0.00] class: 0
|   |   |--- thalach >  166.50
|   |   |   |--- chol <= 238.00
|   |   |   |   |--- weights: [0.00, 1.00] class: 1
|   |   |   |--- chol >  238.00
|   |   |   |   |--- weights: [2.00, 0.00] class: 0
|--- cp >  0.50
|   |--- oldpeak <= 2.10
|   |   |--- trestbps <= 176.00
|   |   |   |--- age <= 56.50
|   |   |   |   |--- trestbps <= 119.00
|   |   |   |   |   |--- age <= 46.50
|   |   |   |   |   |   |--- weights: [0.00, 8.00] class: 1
|   |   |   |   |   |--- age >  46.50
|   |   |   |   |   |   |--- age <= 50.00
|   |   |   |   |   |   |   |--- weights: [3.00, 0.00] class: 0
|   |   |   |   |   |   |--- age >  50.00
|   |   |   |   |   |   |   |--- weights: [0.00, 3.00] class: 1
|   |   |   |   |--- trestbps >  119.00
|   |   |   |   |   |--- weights: [0.00, 52.00] class: 1
|   |   |   |--- age >  56.50
|   |   |   |   |--- sex <= 0.50
|   |   |   |   |   |--- thal <= 2.50
|   |   |   |   |   |   |--- weights: [0.00, 12.00] class: 1
|   |   |   |   |   |--- thal >  2.50
|   |   |   |   |   |   |--- weights: [1.00, 0.00] class: 0
|   |   |   |   |--- sex >  0.50
|   |   |   |   |   |--- chol <= 253.00
|   |   |   |   |   |   |--- ca <= 0.50
|   |   |   |   |   |   |   |--- age <= 65.50
|   |   |   |   |   |   |   |   |--- weights: [0.00, 9.00] class: 1
|   |   |   |   |   |   |   |--- age >  65.50
|   |   |   |   |   |   |   |   |--- trestbps <= 154.00
|   |   |   |   |   |   |   |   |   |--- weights: [1.00, 0.00] class: 0
|   |   |   |   |   |   |   |   |--- trestbps >  154.00
|   |   |   |   |   |   |   |   |   |--- weights: [0.00, 1.00] class: 1
|   |   |   |   |   |   |--- ca >  0.50
|   |   |   |   |   |   |   |--- thalach <= 148.00
|   |   |   |   |   |   |   |   |--- weights: [0.00, 2.00] class: 1
|   |   |   |   |   |   |   |--- thalach >  148.00
|   |   |   |   |   |   |   |   |--- weights: [2.00, 0.00] class: 0
|   |   |   |   |   |--- chol >  253.00
|   |   |   |   |   |   |--- trestbps <= 119.00
|   |   |   |   |   |   |   |--- weights: [0.00, 1.00] class: 1
|   |   |   |   |   |   |--- trestbps >  119.00
|   |   |   |   |   |   |   |--- weights: [6.00, 0.00] class: 0
|   |   |--- trestbps >  176.00
|   |   |   |--- weights: [2.00, 0.00] class: 0
|   |--- oldpeak >  2.10
|   |   |--- chol <= 239.50
|   |   |   |--- weights: [6.00, 0.00] class: 0
|   |   |--- chol >  239.50
|   |   |   |--- age <= 64.50
|   |   |   |   |--- weights: [0.00, 3.00] class: 1
|   |   |   |--- age >  64.50
|   |   |   |   |--- weights: [1.00, 0.00] class: 0

При каждом разделении данных мы разделяем разные функции этих данных. Мы можем взять это из нашей модели и проанализировать их важность.

importances = myTree.feature_importances_
indices = np.argsort(importances)
plt.figure(figsize=(12,12))
plt.title('Feature Importances')
plt.barh(range(len(indices)), importances[indices], color='violet', align='center')
plt.yticks(range(len(indices)), [feature_names[i] for i in indices])
plt.xlabel('Relative Importance')
plt.show()

Это классическая проблема с деревьями решений, и нам придется выполнять предварительную и последующую обрезку, чтобы улучшить ее предсказуемость.

Давайте предварительно обрежем наше дерево и добавим несколько гиперпараметров, чтобы ограничить его размер. Это предотвратит переоснащение и, следовательно, будет более предсказуемым, когда мы уберем тренировочные колеса.

Точность является мерой того, что он получил прямо из всего этого.

Точность = TP(что было на самом деле положительным)+ TN(что было на самом деле отрицательным)/TP+TN+FP(что прогнозировалось как положительное, но на самом деле было отрицательным)+FN(что прогнозировалось как отрицательное, но на самом деле было положительным)

Полнота — это процент того, что модель предсказывала как истину, от того, что было на самом деле верно. Отзыв = ТП/ТП+FN. В модели с предварительной обрезкой наша память также была лучше.

Наша точность была лучше как для обучения (вы можете видеть, что это не переоснащение), так и для тестирования (на 2% лучше).

Что приятно, так это то, что sklearn предоставляет встроенный метод, который позволит нам добавлять диапазоны гиперпараметров. С помощью GridSearch мы можем сравнить все гиперпараметры друг с другом и вернуть наилучшую комбинацию. Важно отметить, что мы перебираем все возможные деревья решений со всеми возможными гиперпараметрами. Я не знаю точное значение bigO, но оно не менее bigO(n²). Снизу мы можем посмотреть на «max_leaf_nodes» и узнать, что это bigO(n⁴).

parameters = {'max_depth': np.arange(4,10), 
              'min_samples_leaf': [ 7, 10,15 ],
              'max_leaf_nodes' : [10,15,20,25 ],
              'min_impurity_decrease': [ 0.0001,0.001,0.01], 
              'criterion': ['entropy','gini'],
              'splitter': ['best','random'],
               
             }
# Type of scoring used to compare parameter combinations
acc_scorer = metrics.make_scorer(metrics.recall_score)
# Run the grid search
grid_obj = GridSearchCV(myTree, parameters, scoring=acc_scorer,cv=5)
grid_obj = grid_obj.fit(Xtrain, ytrain)
# Set the clf to the best combination of parameters
estimator = grid_obj.best_estimator_
# Fit the best algorithm to the data. 
XX = estimator.fit(Xtrain, ytrain)

Это заняло около минуты, и теперь мы проверим это и распечатаем матрицу путаницы.

Важно отметить, что разные меры правильности имеют разные последствия. Вспомнить будет очень важно, когда вы предсказываете что-то очень важное, например, раковую опухоль или если насильственный преступник совершит повторное преступление в тюрьме. Это гарантирует, что мы помечаем значения True в основном как True.

print(tree.export_text(XX,feature_names=fea,show_weights=True)) 
|--- cp <= 0.36
|   |--- exang <= 0.92
|   |   |--- sex <= 0.53
|   |   |   |--- weights: [3.00, 11.00] class: 1
|   |   |--- sex >  0.53
|   |   |   |--- ca <= 0.15
|   |   |   |   |--- weights: [4.00, 10.00] class: 1
|   |   |   |--- ca >  0.15
|   |   |   |   |--- weights: [14.00, 1.00] class: 0
|   |--- exang >  0.92
|   |   |--- restecg <= 0.71
|   |   |   |--- weights: [31.00, 0.00] class: 0
|   |   |--- restecg >  0.71
|   |   |   |--- weights: [23.00, 2.00] class: 0
|--- cp >  0.36
|   |--- sex <= 0.62
|   |   |--- weights: [1.00, 35.00] class: 1
|   |--- sex >  0.62
|   |   |--- oldpeak <= 0.72
|   |   |   |--- chol <= 219.78
|   |   |   |   |--- weights: [0.00, 11.00] class: 1
|   |   |   |--- chol >  219.78
|   |   |   |   |--- weights: [6.00, 24.00] class: 1
|   |   |--- oldpeak >  0.72
|   |   |   |--- weights: [15.00, 21.00] class: 1

Мы предварительно обрезали наше дерево, чтобы получить минимальное увеличение точности/отзыва. Теперь мы попробуем пост-сокращение с сокращением стоимости-сложности.

У сокращения стоимости-сложности есть другой подход, когда мы намеренно построим дерево переобучения. Затем мы начнем удалять поддеревья внутри этого дерева. Для каждого удаляемого поддерева мы увеличиваем ошибку дерева, потому что дерево переобучения идеально соответствует обучающим данным. Мы найдем ошибку нового дерева с удаленным поддеревом по сравнению с самим исходным деревом. Мы делаем это каждый раз, когда удаляется поддерево. Как только мы удалим это поддерево, мы запишем то, что называется альфой, количество ошибок в дереве увеличилось. Затем мы удалим поддерево, которое добавляет наибольшую ценность (наименьшее увеличение ошибки). Рекурсивно начните сверху и сделайте все сначала.

Это позволит нам построить путь деревьев решений и их альфа-значение. Мы можем построить этот путь либо в зависимости от точности, либо против полноты. Затем мы можем найти оптимальную альпу, при которой увеличение ошибки дерева также приводит к лучшему увеличению точности или отзыва.

treeT = DecisionTreeClassifier(random_state=1)
path = treeT.cost_complexity_pruning_path(Xtrain, ytrain)
ccp_alphas, impurities = path.ccp_alphas, path.impurities
fig, ax = plt.subplots(figsize=(10,5))
ax.plot(ccp_alphas[:-1], impurities[:-1], marker='o', drawstyle="steps-post")
ax.set_xlabel("effective alpha")
ax.set_ylabel("total impurity of leaves")
ax.set_title("Total Impurity vs effective alpha for training set")
plt.show()

clfs = []
for ccp_alpha in ccp_alphas:
    clf = DecisionTreeClassifier(random_state=1, ccp_alpha=ccp_alpha)
    clf.fit(Xtrain, ytrain)
    clfs.append(clf)
print("Number of nodes in the last tree is: {} with ccp_alpha: {}".format(
      clfs[-1].tree_.node_count, ccp_alphas[-1]))
train_scores = [clf.score(Xtrain, ytrain) for clf in clfs]
test_scores = [clf.score(Xtest, ytest) for clf in clfs]  
index_best_model = np.argmax(test_scores)
best_model = clfs[index_best_model]
print(best_model)
print('Training accuracy of best model: ',best_model.score(Xtrain, ytrain))
print('Test accuracy of best model: ',best_model.score(Xtest, ytest)) 
recall_train=[]
for clf in clfs:
    pred_train3=clf.predict(Xtrain)
    values_train=metrics.recall_score(ytrain,pred_train3)
    recall_train.append(values_train) 
recall_test=[]
for clf in clfs:
    pred_test3=clf.predict(Xtest)
    values_test=metrics.recall_score(ytest,pred_test3)
    recall_test.append(values_test) 
fig, ax = plt.subplots(figsize=(15,5))
ax.set_xlabel("alpha")
ax.set_ylabel("Recall")
ax.set_title("Recall vs alpha for training and testing sets")
ax.plot(ccp_alphas, recall_train, marker='o', label="train",
        drawstyle="steps-post")
ax.plot(ccp_alphas, recall_test, marker='o', label="test",
        drawstyle="steps-post")
ax.legend()
plt.show()  
index_best_model = np.argmax(recall_test)
best_model = clfs[index_best_model]
print(best_model)

|--- cp <= 0.50
|   |--- exang <= 0.50
|   |   |--- ca <= 0.50
|   |   |   |--- weights: [4.00, 20.00] class: 1
|   |   |--- ca >  0.50
|   |   |   |--- weights: [17.00, 2.00] class: 0
|   |--- exang >  0.50
|   |   |--- weights: [54.00, 2.00] class: 0
|--- cp >  0.50
|   |--- weights: [22.00, 91.00] class: 1