Случайные леса (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. Естественная регуляризация
Случайность предотвращает переобучение, заставляя модель быть более общей
Преимущества случайного леса
- Высокая точность: Часто превосходит отдельные деревья и другие алгоритмы
- Устойчивость к переобучению: Благодаря ансамблированию
- Работа с пропущенными значениями: Может handle пропуски без предобработки
- Оценка важности признаков: Показывает, какие признаки наиболее значимы
- Быстрое обучение: Можно распараллелить построение деревьев
- Нет требований к масштабированию данных: Работает с признаками в разных масштабах
- Обработка нелинейных зависимостей: Естественно capture сложные взаимосвязи
Недостатки случайного леса
- Меньшая интерпретируемость: Сложнее понять логику, чем у одного дерева
- Большой объем памяти: Нужно хранить множество деревьев
- Медленное предсказание: Нужно пройти по всем деревьям
- Может быть избыточным для простых задач
- Склонен к переобучению на зашумленных данных (хотя меньше, чем отдельные деревья)
Реализация в 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)Краткий итог
- Случайный лес — ансамблевый алгоритм из множества деревьев решений
- Принцип работы: Бутстрэп-выборки + случайные признаки + голосование
- Преимущества: Высокая точность, устойчивость к переобучению, оценка важности признаков
- Недостатки: Меньшая интерпретируемость, больше памяти и времени
- Лучше всего подходит для табличных данных, когда важна точность
Случайный лес — это один из самых популярных и эффективных алгоритмов на практике, часто используемый как надежная базовая модель во многих задачах машинного обучения.