Случайные леса (Random Forest) — это мощный ансамблевый алгоритм машинного обучения, который строит множество деревьев решений и объединяет их результаты для получения более точного и устойчивого прогноза.

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


Основная идея

Представьте, что вы хотите купить автомобиль и советуетесь с несколькими экспертами:

  • Один эксперт смотрит на марку
  • Другой — на год выпуска
  • Третий — на техническое состояние
  • Четвертый — на цену

Каждый эксперт дает свое мнение, а вы принимаете окончательное решение на основе большинства голосов. Это и есть принцип случайного леса!


Как работает случайный лес?

1. Бутстрэп-выборки (Bootstrap Aggregating / Bagging)

  • Из исходного набора данных случайно отбираются подвыборки с возвращением
  • Каждое дерево обучается на своей уникальной подвыборке
  • Некоторые наблюдения могут повторяться, некоторые — отсутствовать

2. Случайность признаков (Feature Randomness)

  • При построении каждого дерева на каждом разделении рассматривается только случайное подмножество признаков
  • Обычно: √p признаков для классификации, p/3 для регрессии (где p — общее число признаков)

3. Голосование (для классификации) или усреднение (для регрессии)

  • Каждое дерево дает свой прогноз
  • Классификация: выбирается класс с большинством голосов
  • Регрессия: берется среднее значение всех предсказаний

Процесс построения случайного леса

# Псевдокод алгоритма
def random_forest(training_data, n_trees):
    forest = []
    
    for i in range(n_trees):
        # 1. Создаем бутстрэп-выборку
        bootstrap_sample = sample_with_replacement(training_data)
        
        # 2. Строим дерево на этой выборке
        tree = build_decision_tree(
            bootstrap_sample,
            max_features='sqrt'  # Рассматриваем только случайное подмножество признаков
        )
        
        forest.append(tree)
    
    return forest
 
def predict(forest, new_sample):
    predictions = []
    for tree in forest:
        predictions.append(tree.predict(new_sample))
    
    # Для классификации - большинство голосов
    # Для регрессии - среднее значение
    return majority_vote(predictions)  # или average(predictions)

Почему случайный лес работает так хорошо?

1. Снижение дисперсии

Деревья решений имеют высокую дисперсию — небольшие изменения в данных могут сильно менять дерево. Объединение многих деревьев усредняет эту дисперсию.

2. Разнообразие деревьев

За счет случайности в данных и признаках, деревья получаются разными и “ошибаются в разных местах”

3. Естественная регуляризация

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


Преимущества случайного леса

  1. Высокая точность: Часто превосходит отдельные деревья и другие алгоритмы
  2. Устойчивость к переобучению: Благодаря ансамблированию
  3. Работа с пропущенными значениями: Может handle пропуски без предобработки
  4. Оценка важности признаков: Показывает, какие признаки наиболее значимы
  5. Быстрое обучение: Можно распараллелить построение деревьев
  6. Нет требований к масштабированию данных: Работает с признаками в разных масштабах
  7. Обработка нелинейных зависимостей: Естественно capture сложные взаимосвязи

Недостатки случайного леса

  1. Меньшая интерпретируемость: Сложнее понять логику, чем у одного дерева
  2. Большой объем памяти: Нужно хранить множество деревьев
  3. Медленное предсказание: Нужно пройти по всем деревьям
  4. Может быть избыточным для простых задач
  5. Склонен к переобучению на зашумленных данных (хотя меньше, чем отдельные деревья)

Реализация в Python

from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.datasets import make_classification, make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, mean_squared_error
import matplotlib.pyplot as plt
import numpy as np
 
# ДАННЫЕ ДЛЯ КЛАССИФИКАЦИИ
print("=== КЛАССИФИКАЦИЯ ===")
X_clf, y_clf = make_classification(
    n_samples=1000, n_features=20, n_informative=15, 
    n_redundant=5, random_state=42
)
 
X_train_clf, X_test_clf, y_train_clf, y_test_clf = train_test_split(
    X_clf, y_clf, test_size=0.3, random_state=42
)
 
# Создаем и обучаем случайный лес для классификации
rf_clf = RandomForestClassifier(
    n_estimators=100,      # Количество деревьев
    max_depth=10,          # Максимальная глубина деревьев
    min_samples_split=5,   # Минимальное количество samples для разделения
    min_samples_leaf=2,    # Минимальное количество samples в листе
    max_features='sqrt',   # Количество признаков для рассмотрения на split
    random_state=42,
    n_jobs=-1             # Использовать все ядра процессора
)
 
rf_clf.fit(X_train_clf, y_train_clf)
 
# Предсказания и оценка
y_pred_clf = rf_clf.predict(X_test_clf)
accuracy = accuracy_score(y_test_clf, y_pred_clf)
print(f"Точность классификации: {accuracy:.3f}")
 
# ДАННЫЕ ДЛЯ РЕГРЕССИИ
print("\n=== РЕГРЕССИЯ ===")
X_reg, y_reg = make_regression(
    n_samples=1000, n_features=10, noise=0.1, random_state=42
)
 
X_train_reg, X_test_reg, y_train_reg, y_test_reg = train_test_split(
    X_reg, y_reg, test_size=0.3, random_state=42
)
 
# Создаем и обучаем случайный лес для регрессии
rf_reg = RandomForestRegressor(
    n_estimators=100,
    max_depth=10,
    min_samples_split=5,
    max_features=1.0,      # Все признаки для регрессии
    random_state=42,
    n_jobs=-1
)
 
rf_reg.fit(X_train_reg, y_train_reg)
 
# Предсказания и оценка
y_pred_reg = rf_reg.predict(X_test_reg)
mse = mean_squared_error(y_test_reg, y_pred_reg)
print(f"MSE регрессии: {mse:.3f}")

Анализ важности признаков

Одна из самых полезных особенностей случайного леса:

# Важность признаков
feature_importance = rf_clf.feature_importances_
feature_names = [f'Feature_{i}' for i in range(X_clf.shape[1])]
 
# Сортируем по важности
indices = np.argsort(feature_importance)[::-1]
 
# Визуализация
plt.figure(figsize=(10, 6))
plt.title("Важность признаков в случайном лесе")
plt.bar(range(len(feature_importance)), feature_importance[indices])
plt.xticks(range(len(feature_importance)), [feature_names[i] for i in indices], rotation=45)
plt.xlabel("Признаки")
plt.ylabel("Важность")
plt.tight_layout()
plt.show()
 
# Вывод самых важных признаков
print("\nТоп-5 самых важных признаков:")
for i in range(5):
    print(f"{i+1}. {feature_names[indices[i]]}: {feature_importance[indices[i]]:.3f}")

Настройка гиперпараметров

from sklearn.model_selection import GridSearchCV
 
# Параметры для поиска
param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [5, 10, 15, None],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
    'max_features': ['sqrt', 'log2', None]
}
 
# Поиск по сетке
rf = RandomForestClassifier(random_state=42)
grid_search = GridSearchCV(
    estimator=rf,
    param_grid=param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    verbose=1
)
 
grid_search.fit(X_train_clf, y_train_clf)
 
print("Лучшие параметры:", grid_search.best_params_)
print("Лучшая точность:", grid_search.best_score_)
 
# Используем лучшую модель
best_rf = grid_search.best_estimator_

Сравнение с одиночным деревом

from sklearn.tree import DecisionTreeClassifier
 
# Одиночное дерево
single_tree = DecisionTreeClassifier(max_depth=10, random_state=42)
single_tree.fit(X_train_clf, y_train_clf)
y_pred_tree = single_tree.predict(X_test_clf)
accuracy_tree = accuracy_score(y_test_clf, y_pred_tree)
 
print(f"Точность одного дерева: {accuracy_tree:.3f}")
print(f"Точность случайного леса: {accuracy:.3f}")
print(f"Улучшение: {accuracy - accuracy_tree:.3f}")

Out-of-Bag (OOB) оценка

Случайный лес может оценивать качество без отдельного тестового набора:

# С OOB-оценкой
rf_oob = RandomForestClassifier(
    n_estimators=100,
    max_depth=10,
    oob_score=True,  # Включить OOB-оценку
    random_state=42
)
 
rf_oob.fit(X_clf, y_clf)
print(f"OOB-оценка точности: {rf_oob.oob_score_:.3f}")

Когда использовать случайный лес?

Идеальные сценарии:

  • Табличные данные с混合ными типами признаков
  • Когда важна точность, а не интерпретируемость
  • Для быстрого прототипирования и бейзлайна
  • Когда нужно оценить важность признаков
  • При наличии пропущенных значений

Не идеальные сценарии:

  • Требуется полная интерпретируемость модели
  • Очень большие данные (миллионы строк)
  • Временные ряды (есть временная зависимость)
  • Текстные или изображенческие данные (лучше нейросети)

Практический пример: Прогнозирование выживаемости на Титанике

import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
 
# Загрузка данных
titanic = pd.read_csv('https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv')
 
# Предобработка
titanic['Age'].fillna(titanic['Age'].median(), inplace=True)
titanic['Sex'] = titanic['Sex'].map({'male': 0, 'female': 1})
 
# Признаки и целевая переменная
features = ['Pclass', 'Sex', 'Age', 'Siblings/Spouses Aboard', 'Parents/Children Aboard']
X = titanic[features]
y = titanic['Survived']
 
# Разделение и обучение
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
 
rf_titanic = RandomForestClassifier(n_estimators=100, random_state=42)
rf_titanic.fit(X_train, y_train)
 
# Важность признаков
importance_df = pd.DataFrame({
    'feature': features,
    'importance': rf_titanic.feature_importances_
}).sort_values('importance', ascending=False)
 
print("Важность признаков для прогноза выживаемости:")
print(importance_df)

Краткий итог

  • Случайный лес — ансамблевый алгоритм из множества деревьев решений
  • Принцип работы: Бутстрэп-выборки + случайные признаки + голосование
  • Преимущества: Высокая точность, устойчивость к переобучению, оценка важности признаков
  • Недостатки: Меньшая интерпретируемость, больше памяти и времени
  • Лучше всего подходит для табличных данных, когда важна точность

Случайный лес — это один из самых популярных и эффективных алгоритмов на практике, часто используемый как надежная базовая модель во многих задачах машинного обучения.