Community Engee

Ансамбль деревьев

Author
avatar-artpgchartpgch
Notebook

Определение влиятельных признаков для модели случайного леса

Введение

В современном анализе данных задача прогнозирования непрерывных величин занимает центральное место во множестве прикладных областей — от экономики и биоинформатики до автостроения и энергетики. Одним из наиболее широко применяемых методов решения подобных задач является случайный лес (Random Forest) — ансамблевый алгоритм машинного обучения, предложенный Лео Брейманом в 2001 году.

Случайный лес представляет собой ансамбль регрессионных деревьев, каждое из которых строится на основе независимой выборки из исходных данных. Базовым элементом здесь выступает регрессионное дерево — иерархическая структура типа «дерево решений», в узлах которой происходит последовательное разбиение пространства признаков на более однородные области. Ансамблирование основано на идее, что объединение множества простых моделей в единую композицию позволяет достичь более высокой точности и устойчивости прогнозов, чем использование любой из них по отдельности.

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

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

Исходные данные

Присоединим необходимые файлы и библиотеки.

In [ ]:
#EngeePkg.purge()
#import Pkg
#Pkg.add(["DataFrames", "XLSX", "CategoricalArrays", "MLJ", "MLJDecisionTreeInterface", "StableRNGs", "EvoTrees", "DecisionTree", "Statistics", "Random", "PyPlot"])
using DataFrames, XLSX, CategoricalArrays, MLJ, MLJDecisionTreeInterface, StableRNGs, EvoTrees, DecisionTree, Statistics, Random, PyPlot
foreach(include, filter(contains(r"\.jl$"), readdir()))

Для анализа используется набор данных, содержащий характеристики легковых автомобилей. В рамках исследования строится регрессионная модель, прогнозирующая расход топлива на основе следующих параметров:

  • количество цилиндров;
  • рабочий объём двигателя;
  • мощность;
  • масса автомобиля;
  • время разгона;
  • год выпуска;
  • страна происхождения.
In [ ]:
X = XLSX.readdata("автомобили.xlsx", "Sheet1", "A:G")
X = DataFrame(X[2:end, :], Symbol.(X[1, :])) 
Out[0]:
406×7 DataFrame
381 rows omitted
RowЦилиндровОбъёмМощностьМассаРазгонГодСтрана
AnyAnyAnyAnyAnyAnyAny
1830713035041270USA
28350165369311.570USA
3831815034361170USA
4830415034331270USA
58302140344910.570USA
6842919843411070USA
784542204354970USA
8844021543128.570USA
9845522544251070USA
10839019038508.570USA
114133115309017.570France
128350165414211.570USA
13835115340341170USA
3956181110294516.482USA
39662628530151782USA
397415692258514.582USA
3986232112283514.782USA
399414496266513.982Japan
40041358423701382USA
401415190295017.382USA
402414086279015.682USA
40349752213024.682Germany
404413584229511.682USA
405412079262518.682USA
406411982272019.482USA

Загрузим данные расхода топлива.

In [ ]:
расход_ = XLSX.readdata("расход.xlsx", "Sheet1", "A:A") 
Расход = parse.(Float64, расход_[2:end])
Out[0]:
406-element Vector{Float64}:
  18.0
  15.0
  18.0
  16.0
  17.0
  15.0
  14.0
  14.0
  14.0
  15.0
 NaN
 NaN
 NaN
   ⋮
  25.0
  38.0
  26.0
  22.0
  32.0
  36.0
  27.0
  27.0
  44.0
  32.0
  28.0
  31.0

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

Определение количества уникальных значений признаков

Определим количество уникальных значений каждого признака из набора данных.

In [ ]:
for колонка in names(X)
    try
        X[!, колонка] = [getdata(val) for val in X[!, колонка]]
    catch e
        X[!, колонка] = categorical(X[!, колонка])
    end
end
уникальных = [length(unique(skipmissing(X[!, колонка]))) for колонка in names(X)]
Out[0]:
7-element Vector{Int64}:
   5
  83
  94
 356
  96
  13
   7

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

In [ ]:
график1 = Plots.bar(1:length(уникальных), уникальных, 
    title = "Количество уникальных значений",
    ylabel = "Уникальных значений",
    xticks = (1:length(уникальных), names(X)[1:end]),
    ylims = (0, maximum(уникальных) * 1.1),
    xrotation = 45,
    legend = false,
    bar_width = 0.7,
    color = :steelblue)
display(график1)

На диаграмме видно, что имеются значительные различия количестве уникальных значений признаков. Подобная диспропорция создаёт риск смещения оценок при использовании стандартного алгоритма для выбора переменных расщепления в узлах деревьев случайного леса, поэтому для формирования ансамбля регрессионных деревьев необходимо учитывать взаимосвязь между признаками.

Формирование ансамбля регрессионных деревьев

Для оценки показателей важности признаков, необходимо выполнить обучение ансамбля, состоящего из регрессионных деревьев, с учётом взаимосвязи между признаками. Создадим обучающую выборку.

In [ ]:
X_matrix = zeros(Float64, nrow(X), ncol(X))
col_names = names(X)

for (j, col) in enumerate(eachcol(X))
    if eltype(col) <: String || eltype(col) <: CategoricalValue
        unique_vals = unique(col)
        val_to_num = Dict(val => i for (i, val) in enumerate(unique_vals))
        X_matrix[:, j] = [Float64(val_to_num[x]) for x in col]
    else
        X_matrix[:, j] = Float64.(col)
    end
end

train_idx = .!isnan.(Расход)
X_train = X_matrix[train_idx, :]
y_train = Расход[train_idx]

println("Размер обучающей выборки: $(size(X_train, 1)) строк")
println("Количество признаков: $(size(X_train, 2))")
Размер обучающей выборки: 398 строк
Количество признаков: 7

Выполним обучение ансамбля.

In [ ]:
Random.seed!(1)
деревьев = 200
деревья, yHat_train = build_forest(y_train, X_train, 0, деревьев, 0.632, -1, 5, 2)
valid_pred_idx = .!isnan.(yHat_train)
if sum(valid_pred_idx) > 0
    R2 = cor(y_train[valid_pred_idx], yHat_train[valid_pred_idx])^2
    println("R² = ", R2)
end
R² = 0.8713331235365577

Значение коэффициента детерминации свидетельствует о том, что модель объясняет 87% разброса целевой переменной относительно среднего значения.

Оценка влияния признака

Оценка влияния признаков выполняется путём перестановки вневыборочных наблюдений между деревьями ансамбля.

In [ ]:
важность = permutation_importance(деревья, X_train, y_train, 5);

Значение важность представляет собой вектор размера 1×7, содержащий оценки влияния исходных признаков. Особенностью полученных оценок является отсутствие смещения в сторону признаков с большим количеством уникальных значений. Сравним полученные показателей влияния признаков.

In [ ]:
график2 = Plots.bar(важность, 
    title = "Показатели влияния признаков",
    xlabel = "Признаки",
    ylabel = "Влияние",
    xticks = (1:length(уникальных), names(X)[1:end]),
    ylims = (0, maximum(важность) * 1.1),
    xrotation = 45,
    legend = false,
    bar_width = 0.7,
    color = :steelblue)
display(график2)

Бóльшие значения оценок соответствуют более влиятельным предсказателям. Согласно столбчатой диаграмме, наибольшей прогностической значимостью обладает год выпуска автомобиля, за которой следует масса автомобиля.

Отобразим оценки взаимосвязи признаков, в виде цветовой матрицы.

In [ ]:
график3 = imshow(predAssociation, cmap="viridis", aspect="auto", interpolation="nearest")
title("Оценки взаимосвязи признаков")
colorbar(label="Взаимосвязь")
PyPlot.xticks(0:length(col_names)-1, col_names, rotation=45, ha="right")
PyPlot.yticks(0:length(col_names)-1, col_names)
display(график3)
PyObject <matplotlib.image.AxesImage object at 0x7f317892a990>

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

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

Заключение

В данном примере продемонстрирован подход к построению регрессионного случайного леса с акцентом на корректный отбор влиятельных признаков. На примере набора характеристик автомобилей решена задача прогнозирования расхода топлива — типичная для автомобилестроения задача, где точность модели влияет на инженерные решения.
Механизм суррогатных расщеплений позволил корректно обработать пропуски данных и построить матрицу взаимосвязей признаков, дающую содержательную интерпретацию структуры зависимостей в данных.

Представленная методология универсальна и применима в широком спектре областей: от машиностроения до биоинформатики. Главный практический вывод: корректный учёт природы переменных при выборе критерия разбиения позволяет повысить точность прогнозов и получать содержательные выводы о значимости факторов, свободные от статистических смещений.