Эта статья возникла из личного публичного аккаунта:TechFlow, оригинальность это не просто, прошу внимания
Сегодня этоТемы машинного обученияВ 24-й статье поговорим о моделях дерева регрессии.
На самом деле так называемая модель дерева регрессииИспользование древовидных моделей для решения задач регрессии, наиболее классической древовидной моделью является модель дерева решений, которая также является основой почти всех древовидных моделей. Хотя базовая структура заключается в использовании деревьев решений, их можно разделить на два типа в соответствии с различными методами прогнозирования. Первое,Листовой узел на дереве соответствует предсказанному значениюВ соответствии с деревьями классификации этот метод называется деревьями регрессии. секунда,Листовые узлы дерева соответствуют линейной модели., окончательный результат дает линейная модель. Этот метод называется модельным деревом.
Сегодня давайте взглянем на дерево регрессии.
модель дерева регрессии
Основным алгоритмом модели дерева регрессии, то есть алгоритмом построения дерева решений, является алгоритм CART, о котором мы говорили в предыдущей статье. Если есть учащиеся, которые незнакомы или пропущены, вы можете просмотреть их через портал ниже:
Суть алгоритма CART заключается в том, что каждый раз, когда мы выбираем функции для разделения данных,Разделить набор данных навсегда. Различия между дискретными и непрерывными функциями не делается. Еще одна особенность CART заключается в том, что он использует индекс Джини вместо коэффициента прироста информации или коэффициента прироста информации для выбора функций разделения, но это не используется в задачах регрессии. Поскольку функция потерь в задаче регрессии представляет собой среднеквадратичную ошибку, а не перекрестную энтропию, трудно использовать энтропию для измерения точности непрерывных значений.
В дереве классификации листовой узел представляет прогнозируемое значение класса, а значение этого класса — моду класса обучающих выборок, попадающих в листовой узел, то есть класс с наибольшей частотой. В дереве регрессии соответствующий конечный узел, естественно, является непрерывным значением. Это непрерывное значение равносреднее значение обучающих выборок, попадающих на этот узел, а его ошибка представляет собой среднеквадратичную ошибку этих выборок.
Кроме того, когда мы выбирали порог разделения признаков, выбор порога был оптимизирован, и выбирались только те пороги, которые вызывали бы изменения в прогнозируемой категории. Но в задачах регрессии, поскольку прогнозируемое значение представляет собой число с плавающей запятой, такой оптимизации не существует. В целом, фактически, сложность реализации дерева регрессии ниже, чем у дерева классификации.
настоящий бой
Сначала мы загружаем данные, на этот раз мы используем классическую библиотеку scikit-learn.Прогноз цен на жилье в БостонеДанные. Что касается прогнозирования цен на жилье, в kaggle есть аналогичный конкурс, который называется: цены на дома, продвинутые методы регрессии. Тем не менее, есть больше функций, и есть такие случаи, как отсутствие, которые требуют от нас большой разработки функций. Заинтересованные студенты могут провести собственное исследование.
Во-первых, давайте получим данные, поскольку данные уже есть в библиотеке sklearn, мы можем напрямую вызвать API, чтобы получить их, что очень просто:
import numpy as np
import pandas as pd
from sklearn.datasets import load_boston
boston = load_boston()
X, y = boston.data, boston.target
Выведем первые несколько данных для проверки:
Эти данные имеют высокое качество, и библиотека sklearn имеетЗаконченный скрининг данных и разработка функций для нас, вы можете использовать его напрямую. Чтобы упростить передачу данных, мы объединяем X и Y вместе. Поскольку y — одномерный массив, его нельзя объединить с двумерным массивом X, поэтому нам нужно изменить форму y перед объединением.
y = y.reshape(-1, 1)
X = np.hstack((X, y))
hstackФункция может преобразовать два массива npГоризонтальная строчка, что соответствует vstack, то есть соединению двух массивов по вертикали, что также является нормальной операцией. После слияния y добавляется после X в качестве нового столбца. Теперь, когда данные на месте, пришло время реализовать модель.
Прежде чем реализовать основную часть дерева решений, давайте реализуем две вспомогательные функции. Первая вспомогательная функцияРассчитать сумму дисперсии партии образцов, вторая вспомогательная функцияполучить среднее значение выборки, что является прогнозируемым значением дочернего узла.
def node_mean(X):
return np.mean(X[:, -1])
def node_variance(X):
return np.var(X[:, -1]) * X.shape[0]
После этого продолжаем реализовывать функцию разделения данных по порогу. Это также может повторно использовать предыдущий код:
from collections import defaultdict
def split_dataset(X, idx, thred):
split_data = defaultdict(list)
for x in X:
split_data[x[idx] < thred].append(x)
return list(split_data.values()), list(split_data.keys())
Далее идут две очень важные функции, get_thresholds и split_variance. Как следует из названия, первая функция используется дляполучить порог, как упоминалось ранее, поскольку мы делаем регрессионную модель, теоретически каждое значение признака можно использовать в качестве основы для сегментации. Однако это не исключает, что могут быть случаи, когда собственные значения нескольких фрагментов данных совпадают, поэтому мы делаем дедупликацию. Вторая функцияРазделить данные на основе порога, который возвращает сумму отклонений после разделения.
def get_thresholds(X, i):
return set(X[:, i].tolist())
# 每次迭代方差优化的底线
MINIMUM_IMPROVE = 2.0
# 每个叶子节点最少样本数
MINIMUM_SAMPLES = 10
def split_variance(dataset, idx, threshold):
left, right = [], []
n = dataset.shape[0]
for data in dataset:
if data[idx] < threshold:
left.append(data)
else:
right.append(data)
left, right = np.array(left), np.array(right)
# 预剪枝
# 如果拆分结果有一边过少,则返回None,防止过拟合
if len(left) < MINIMUM_SAMPLES or len(right) < MINIMUM_SAMPLES:
return None
# 拆分之后的方差和等于左子树的方差和加上右子树的方差和
# 因为是方差和而不是均方差,所以可以累加
return node_variance(left) + node_variance(right)
Здесь мы используем параметр MINIMUM_SAMPLES, который используется дляПредварительная обрезкаиспользовать. Поскольку мы являемся регрессионной моделью, если мы не ограничиваем рост дерева решений, весьма вероятно, что результирующее дерево решений будет иметь столько же листовых узлов, сколько и количество обучающих выборок. Очевидно, что это приводит к переоснащению, что пагубно сказывается на эффекте модели. поэтому мы хотимОграничьте количество выборок на узел, это параметр, мы можем настроить его в соответствии с нашими потребностями.
Далее, это функция функции и пороговой фильтрации. Нам нужно разработать функцию для обхода всех разделяемых функций и порогов, разделения данных и поиска наилучшего возможного разделения из всех функций.
def choose_feature_to_split(dataset):
n = len(dataset[0])-1
m = len(dataset)
# 记录最佳方差,特征和阈值
var_ = node_variance(dataset)
bestVar = float('inf')
feature = -1
thred = None
for i in range(n):
threds = get_thresholds(dataset, i)
for t in threds:
# 遍历所有的阈值,计算每个阈值的variance
v = split_variance(dataset, i, t)
# 如果v等于None,说明拆分过拟合了,跳过
if v is None:
continue
if v < bestVar:
bestVar, feature, thred = v, i, t
# 如果最好的拆分效果达不到要求,那么就不拆分,控制子树的数量
if var_ - bestVar < MINIMUM_IMPROVE:
return None, None
return feature, thred
Как и выше, эта функция также использует предварительно сокращенный параметр MINIMUM_IMPROVE, который измеряетПреимущества каждого поколения поддеревьев. Когда доход, приносимый определенной генерацией поддеревьев, меньше определенного значения, это означает, что доход мал и нерентабелен, поэтому мы отказываемся от генерации этого поддерева. Это тоже своего рода предварительная обрезка.
После того, как все это сделано, пришло время построить дерево. Процесс построения дерева аналогичен предыдущему, за исключением того, что на этот раз в наших данных нет имени объекта, поэтому мы удаляем соответствующую логику имени объекта.
def create_decision_tree(dataset):
dataset = np.array(dataset)
# 如果当前数量小于10,那么就不再继续划分了
if dataset.shape[0] < MINIMUM_SAMPLES:
return node_mean(dataset)
# 记录最佳拆分的特征和阈值
fidx, th = choose_feature_to_split(dataset)
if fidx is None:
return th
node = {}
node['feature'] = fidx
node['threshold'] = th
# 递归建树
split_data, vals = split_dataset(dataset, fidx, th)
for data, val in zip(split_data, vals):
node[val] = create_decision_tree(data)
return node
Протестируем дерево полностью, во-первых, нам нужно разбить исходные данные.Разделите необработанные данные на обучающие данные и тестовые данные, поскольку наш сценарий относительно прост, мы не будем задавать данные проверки.
Разделение данных не нужно реализовывать самостоятельно, sklearn предоставляет соответствующие инструменты, мы можем вызвать их напрямую:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=23)
Обычно мы используем два параметра, один из которых — test_size, который может быть целым числом или числом с плавающей запятой. Если это целое число, оно представляетКоличество образцов в тестовом наборе. Если это число с плавающей запятой 0-1.0, оно представляетДоля тестового набора. random_state — это случайное начальное число, используемое при генерации случайных чисел.
Выведем сгенерированное дерево, из-за большого количества данных мы видим огромную древовидную структуру. После реализации части построения дерева последней оставшейся частью является часть предсказания.
Код части предсказания не сильно отличается от предыдущего дерева классификации, и общая логика точно такая же, за исключением того, что связанная логика feature_names удалена.
def classify(node, data):
key = node['feature']
pred = None
thred = node['threshold']
if isinstance(node[data[key] < thred], dict):
pred = classify(node[data[key] < thred], data)
else:
pred = node[data[key] < thred]
# 放置pred为空,挑选一个叶子节点作为替补
if pred is None:
for key in node:
if not isinstance(node[key], dict):
pred = node[key]
break
return pred
Поскольку эта функция может принимать только один фрагмент данных за раз, этого недостаточно, если мы хотим пакетного прогнозирования, поэтому лучше реализовать функцию прогнозирования для пакетного прогнозирования.
def predict(node, X):
y_pred = []
for x in X:
y = classify(node, x)
y_pred.append(y)
return np.array(y_pred)
после обрезки
Оригинальное английское слово, обозначающее пост-обрезку, — это пост-обрезка, но немного странно переводить его как пост-обрезка. В любом случае, давайте просто используем слово пост-обрезка.
В дереве регрессии идея, которую мы используем, очень проста, и при построении дерева мы строим максимально сложное и огромное дерево. затем вобрезать дерево через тестовый набор, логика обрезки также очень проста.Мы судим об ошибке, когда поддерево имеет разветвления и никакие разветвления не становятся только листовыми узлами.Если после обрезки ошибка меньше, то мы вычитаем это поддерево.
Весь процесс обрезки такой же, как и процесс построения дерева, рекурсивно сверху вниз.
Вся логика проста для понимания, давайте посмотрим непосредственно на код:
def is_dict(node):
return isinstance(node, dict)
def prune(node, testData):
testData = np.array(testData)
if testData.shape[0] == 0:
return node
# 拆分数据
split_data, _ = split_dataset(testData, node['feature'], node['threshold'])
# 对左右子树递归修剪
if is_dict(node[0]):
node[0] = prune(node[0], split_data[0])
if is_dict(node[1]) and len(split_data) > 1:
node[1] = prune(node[1], split_data[1])
# 如果左右都是叶子节点,那么判断当前子树是否需要修剪
if len(split_data) > 1 and not is_dict(node[0]) and not is_dict(node[1]):
# 计算修剪前的方差和
baseError = np.sum(np.power(np.array(split_data[0])[:, -1] - node[0], 2)) + np.sum(np.power(np.array(split_data[1])[:, -1] - node[1], 2))
# 计算修剪后的方差和
meanVal = (node[0] + node[1]) / 2
mergeError = np.sum(np.power(meanVal - testData[:, -1], 2))
if mergeError < baseError:
return meanVal
else:
return node
return node
Наконец, давайте проверим эффект после обрезки:
Как видно из рисунка, наша среднеквадратическая ошибка на тестовых данных до обрезки была 19,65, а после обрезки упала до 19,48. С числовой точки зрения это эффективно, но поскольку наши обучающие данные относительно малы, а предварительная обрезка выполняется одновременно, это влияет на эффект постобрезки. Но для реальной инженерии машинного обучения, если метод имеет явный эффект,в ценовом диапазоне, это ценно, вы не должны чувствовать, что улучшение не очевидно, и просто отрицать метод.
При расчете среднеквадратичной ошибки здесь используется библиотечная функция mean_square_error в sklearn.Из названия также видно ее назначение.Его можно использовать для двух массивов Numpy.Вычислить среднеквадратичную ошибку.
Суммировать
Актуальный контент о модели дерева регрессии закончился здесь Мы не только реализовали модель сами, но и провели эксперименты на реальных наборах данных. Если вы реализуете код модели самостоятельно, я думаю, вы многого выиграете.
Хотя на практике мыПочти никогдаИспользуйте модель дерева для задач регрессии, но сама модель дерева регрессии очень значима. Потому что на его основе мы разработали множество моделей с лучшими эффектами, например знаменитыйGBDT. Поэтому понимание дерева регрессии очень важно для нашего последующего углубленного обучения. Фактически, до популяризации глубокого обучения большинство высокоэффективных моделей основывались на древовидных моделях, таких как случайный лес, GBDT, Adaboost и т. д. Можно сказатьМодель дерева поддержала половину эры машинного обучения, поэтому я считаю, что каждый должен понимать важность этого.
Сегодняшняя статья здесь.Если вам нравится эта статья, если вы можете, пожалуйста,обращать внимание, подбодрите меня и облегчите доступ к другим статьям.
В этой статье используетсяmdniceнабор текста