Масштабирование признаков в ML: StandardScaler, MinMaxScaler, RobustScaler и другие методы

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

Признаки в датасете часто имеют разные единицы измерения и диапазоны значений: цена акции может варьироваться от $10 до $500, объем торгов — от сотен тысяч до миллиардов, а волатильность измеряется в процентах от 5% до 80%. Без масштабирования ML-алгоритмы, основанные на расстояниях или градиентах, будут некорректно оценивать важность признаков.

Библиотека scikit-learn предоставляет несколько методов масштабирования (скейлеров), каждый из которых решает специфические задачи:

  • StandardScaler применяет z-score нормализацию для приведения признаков к нулевому среднему и единичной дисперсии (масштабирование к среднему 0 и стандартному отклонению 1);
  • MinMaxScaler линейно масштабирует данные в заданный диапазон (обычно [0, 1] или [-1, 1]), сохраняя исходную форму распределения;
  • RobustScaler использует медиану и межквартильный размах (приведение к медиане 0 и IQR 1), что делает его устойчивым к выбросам.

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

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

Зачем ML-моделям требуется масштабирование данных?

Влияние масштабирования на качество и устойчивость моделей определяется особенностями используемого алгоритма.

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

Алгоритмы, чувствительные к масштабу

Градиентные методы оптимизации

Градиентный спуск и его вариации (SGD, Adam и др.) вычисляют производные функции потерь по каждому параметру модели. Если признаки имеют несопоставимые масштабы, параметры, связанные с признаками большого диапазона, то они получают значительно более крупные градиенты.

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

Линейные модели с регуляризацией

В линейной регрессии и логистической регрессии с L1/L2 регуляризацией масштабы признаков напрямую влияют на величину штрафов.

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

Модели, основанные на расстояниях

Модели SVM (Support Vector Machine — Метод опорных векторов), KNN (k-Nearest Neighbors — Метод k ближайших соседей) и другие алгоритмы, использующие евклидово расстояние, крайне чувствительны к масштабу.

Признаки с большими значениями начинают доминировать в метрике расстояния, подавляя вклад маломасштабных признаков:

  • в SVM это приводит к смещению разделяющей гиперплоскости и ухудшению качества классификации или регрессии;
  • в KNN это приводит к некорректному поиску ближайших объектов: модель фактически ориентируется только на признаки с большим масштабом и игнорирует остальные.

Поэтому для моделей этого типа обязательны процедуры масштабирования данных — стандартизация (StandardScaler) или нормализация (MinMaxScaler).

Нейронные сети

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

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

Алгоритмы, не чувствительные к масштабу признаков

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

Деревья решений и ансамбли деревьев

Алгоритмы, основанные на разбиении пространства признаков: Decision Tree, Random Forest, Extra Trees, Gradient Boosting, XGBoost, LightGBM, CatBoost — не зависят от масштаба данных. Разбиения строятся по условиям вида feature ≤ threshold, и сами пороги подстраиваются под диапазон каждого признака.

Поэтому разные масштабы признаков не влияют:

  • на форму дерева;
  • на важности признаков;
  • на итоговое качество модели.

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

Наивный байесовский классификатор

Наивный Байес (Naive Bayes) опирается на условные вероятности и плотности распределений. Масштаб признаков влияет только на параметры распределений, но не нарушает логику работы классификатора, поскольку сравнение вероятностей производится в общем масштабе.

Правило ближайшего центра (Nearest Centroid)

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

Влияние на скорость обучения и качество предсказаний

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

Градиентный спуск в таких оврагах делает много мелких шагов поперек направления минимума и медленно продвигается вдоль оврага. Это увеличивает количество итераций до достижения сходимости в 5-10 раз.

Влияние масштабирования на форму поверхности функции потерь. Левая панель показывает вытянутую поверхность без масштабирования — узкий овраг вдоль оси признака с малым масштабом. Средняя панель демонстрирует изотропную (симметричную) поверхность после StandardScaler. Правая панель сравнивает контуры: вытянутые эллипсы (красный) требуют zigzag траектории градиентного спуска, круговые контуры (синий) позволяют прямое движение к минимуму

Рис. 1: Влияние масштабирования на форму поверхности функции потерь. Левая панель показывает вытянутую поверхность без масштабирования — узкий овраг вдоль оси признака с малым масштабом. Средняя панель демонстрирует изотропную (симметричную) поверхность после StandardScaler. Правая панель сравнивает контуры: вытянутые эллипсы (красный) требуют zigzag траектории градиентного спуска, круговые контуры (синий) позволяют прямое движение к минимуму

Корректный выбор learning rate в условиях неотмасштабированных признаков превращается в чрезвычайно трудную задачу:

  • Слишком маленькое значение приводит к чрезмерно медленному обновлению всех параметров;
  • Слишком большое — вызывает нестабильность в направлении признаков с крупным масштабом: шаги становятся «рывками», модель осциллирует вокруг минимума или вовсе выходит за пределы области устойчивости.
👉🏻  Прогнозирование конверсии посетителей интернет-магазина в покупателей с помощью машинного обучения

Масштабирование выравнивает поверхность потерь, делая ее ближе к изотропной. Это позволяет использовать единый и более крупный learning rate, ускоряя и стабилизируя обучение.

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

В регуляризованных моделях (L1/L2) отсутствие масштабирования деформирует процесс отбора признаков. Так, к примеру, L1-регуляризация (Lasso), стремясь занулять неинформативные коэффициенты, опирается на их абсолютные значения.

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

StandardScaler: масштабирование до нормального распределения

StandardScaler выполняет z-score стандартизацию, преобразуя признаки к нулевому среднему и единичной дисперсии. Метод предполагает, что данные имеют нормальное или близкое к нормальному распределение.

Стандартизация через скейлер StandardScaler сохраняет информацию о выбросах, но делает признаки с выбросами менее стабильными.

Математический аппарат

Преобразование каждого значения признака выполняется по формуле z-score:

z = (x — μ) / σ

где:

  • x — исходное значение признака;
  • μ — среднее арифметическое признака по обучающей выборке;
  • σ — стандартное отклонение признака по обучающей выборке;
  • z — стандартизованное значение.

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

После преобразования признак имеет среднее 0 и стандартное отклонение 1. Значения интерпретируются как количество стандартных отклонений от среднего:

  • z = 2 означает, что значение на 2σ выше среднего;
  • z = -1.5 — на 1.5σ ниже среднего.

Свойства и особенности применения

StandardScaler оптимален для признаков с распределением, близким к нормальному. Если данные имеют гауссово распределение, после стандартизации около 68% значений попадают в диапазон [-1, 1], 95% — в [-2, 2], 99.7% — в [-3, 3]. Это свойство используют для детекции аномалий: значения |z| > 3 считаются выбросами.

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

Важно также отметить, что StandardScaler не ограничивает диапазон значений. После преобразования признак может принимать любые значения от -∞ до +∞. Это подходит для большинства алгоритмов, но создает проблемы для нейросетей с функциями активации, чувствительными к диапазону входов (sigmoid требует входы в диапазоне примерно [-6, 6] для эффективной работы).

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

Практическая реализация

Давайте рассмотрим как будет вести себя StandardScaler на синтетических данных, имитирующих финансовые временные ряды.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

np.random.seed(42)

# Генерация синтетических данных
n_samples = 252  # торговый год
dates = pd.date_range('2024-11-01', periods=n_samples, freq='D')

# Цена закрытия: случайное блуждание с трендом
price_trend = np.linspace(100, 110, n_samples)
price_noise = np.random.normal(0, 5, n_samples)
prices = price_trend + price_noise

# Объем торгов: логнормальное распределение
volumes = np.random.lognormal(13.5, 0.3, n_samples)

# Создание датафрейма
df = pd.DataFrame({
    'Date': dates,
    'Close': prices,
    'Volume': volumes
})

# Применение StandardScaler
scaler = StandardScaler()
df[['Close_scaled', 'Volume_scaled']] = scaler.fit_transform(df[['Close', 'Volume']])

# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Исходные данные
axes[0, 0].plot(df['Date'], df['Close'], color='black', linewidth=1)
axes[0, 0].set_title('Цена закрытия (исходная)', fontsize=11, fontweight='bold')
axes[0, 0].set_ylabel('Цена, $')
axes[0, 0].grid(True, alpha=0.3)

axes[0, 1].plot(df['Date'], df['Volume'], color='black', linewidth=1)
axes[0, 1].set_title('Объем торгов (исходный)', fontsize=11, fontweight='bold')
axes[0, 1].set_ylabel('Объем')
axes[0, 1].grid(True, alpha=0.3)

# Стандартизованные данные
axes[1, 0].plot(df['Date'], df['Close_scaled'], color='#2E86AB', linewidth=1)
axes[1, 0].axhline(y=0, color='red', linestyle='--', linewidth=0.8, alpha=0.7)
axes[1, 0].axhline(y=1, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)
axes[1, 0].axhline(y=-1, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)
axes[1, 0].set_title('Цена закрытия (StandardScaler)', fontsize=11, fontweight='bold')
axes[1, 0].set_ylabel('z-score')
axes[1, 0].set_xlabel('Дата')
axes[1, 0].grid(True, alpha=0.3)

axes[1, 1].plot(df['Date'], df['Volume_scaled'], color='#2E86AB', linewidth=1)
axes[1, 1].axhline(y=0, color='red', linestyle='--', linewidth=0.8, alpha=0.7)
axes[1, 1].axhline(y=1, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)
axes[1, 1].axhline(y=-1, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)
axes[1, 1].set_title('Объем торгов (StandardScaler)', fontsize=11, fontweight='bold')
axes[1, 1].set_ylabel('z-score')
axes[1, 1].set_xlabel('Дата')
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Статистика преобразования
print("Статистика до стандартизации:")
print(df[['Close', 'Volume']].describe())
print("\nСтатистика после стандартизации:")
print(df[['Close_scaled', 'Volume_scaled']].describe())
print(f"\nПараметры StandardScaler:")
print(f"Средние значения: {scaler.mean_}")
print(f"Стандартные отклонения: {scaler.scale_}")

Влияние стандартизации (StandardScaler) на временные ряды цены и объема: верхний ряд — исходные данные, нижний ряд — стандартизованные данные (среднее = 0, σ = 1)

Рис. 2: Влияние стандартизации (StandardScaler) на временные ряды цены и объема: верхний ряд — исходные данные, нижний ряд — стандартизованные данные (среднее = 0, σ = 1)

Обратите внимание на шкалу значений по оси y для верхних графиков и нижних. Заметно как StandardScaler отцентровывает данные по 0 и уже относительно них определяет амплитуды.

Статистика до стандартизации:
            Close        Volume
count  252.000000  2.520000e+02
mean   104.981176  7.690148e+05
std      5.853713  2.378962e+05
min     89.849482  2.758520e+05
25%    101.040716  5.909502e+05
50%    105.061273  7.311119e+05
75%    108.658175  8.952815e+05
max    127.590351  1.837037e+06

Статистика после стандартизации:
       Close_scaled  Volume_scaled
count  2.520000e+02   2.520000e+02
mean  -5.639228e-17   7.577713e-17
std    1.001990e+00   1.001990e+00
min   -2.590118e+00  -2.077142e+00
25%   -6.744951e-01  -7.499863e-01
50%    1.371036e-02  -1.596424e-01
75%    6.293983e-01   5.318199e-01
max    3.870051e+00   4.498378e+00

Параметры StandardScaler:
Средние значения: [1.04981176e+02 7.69014786e+05]
Стандартные отклонения: [5.84208725e+00 2.37423742e+05]

В статистике выше отдельно стоит обратить внимание на показатель std (стандартное отклонение): в исходных данных он составляет 5.853713, в то время как после работы StandardScaler — это 1.

👉🏻  Случайный лес (Random Forest): механика алгоритма, Бутстрэп-агрегирование, out-of-bag оценка

Отдельно стоит отметить, что отмасштабированные данные сохраняют относительные расстояния между точками. Если в исходных данных две цены отличались на 2σ, после стандартизации разница останется той же — 2. Это свойство особенно важно для алгоритмов, основанных на расстояниях, таких как KNN, SVM и методы кластеризации.

После преобразования параметры scaler.mean_ и scaler.scale_ сохраняются. Они используются для преобразования новых данных методом transform(), что гарантирует применение тех же параметров масштабирования к тестовой выборке. Такой подход предотвращает утечку данных (data leakage) и обеспечивает корректную оценку качества модели на новых данных.

MinMaxScaler: масштабирование в фиксированный диапазон

Метод MinMaxScaler выполняет линейное масштабирование признаков в заданный диапазон, по умолчанию [0, 1].

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

Математический аппарат

Преобразование значения признака в диапазон [0, 1] выполняется по формуле:

x_scaled = (x — x_min) / (x_max — x_min)

где:

  • x — исходное значение признака;
  • x_min — минимальное значение признака в обучающей выборке;
  • x_max — максимальное значение признака в обучающей выборке;
  • x_scaled — масштабированное значение в диапазоне [0, 1].

Числитель (x — x_min) сдвигает все значения так, что минимум становится равным нулю. Деление на диапазон (x_max — x_min) нормализует масштаб: максимальное значение преобразуется в 1, промежуточные значения распределяются пропорционально между 0 и 1.

Для масштабирования в произвольный диапазон [a, b] формула расширяется:

x_scaled = a + (x — x_min) × (b — a) / (x_max — x_min)

Где параметр a определяет нижнюю границу диапазона, b — верхнюю. В sklearn это задается через параметр feature_range=(a, b). Распространенные варианты: [-1, 1] для данных с отрицательными значениями, [0, 255] для изображений.

Когда использовать MinMaxScaler?

Метод MinMaxScaler особенно полезен для признаков с известными границами. Если значение по определению ограничено диапазоном (проценты 0–100%, рейтинги 1–5, вероятности 0–1). Для таких данных нормализация в диапазон [0, 1] выглядит естественно и легко интерпретируема. После преобразования 0 соответствует минимальному значению признака, а 1 — максимальному.

Нейронные сети с сигмоидальными функциями активации (sigmoid, tanh) обучаются эффективнее при входах в ограниченном диапазоне. Большие по модулю значения попадают в области насыщения функций, где градиенты близки к нулю. Масштабирование в [0, 1] или [-1, 1] удерживает активации в рабочем диапазоне, ускоряя и стабилизируя обучение.

Кроме того, MinMaxScaler сохраняет нули в разреженных данных, если минимальное значение равно нулю. Это особенно важно для преобразований типа «мешок слов» (bag-of-words) или one-hot кодирования, где большинство значений исходно нулевые. В отличие от этого, StandardScaler превратил бы нули в отрицательные значения, нарушив структуру разреженной матрицы.

Однако, нужно учитывать, что метод MinMaxScaler плохо подходит для данных с выбросами. Один экстремальный выброс расширяет диапазон (x_max — x_min), сжимая основную массу данных в узкий интервал. Например, если большинство цен акции находится в диапазоне $90–110, но есть один день с ценой $200 (например, из-за сплита, не учтенного в данных, либо спайка в котировкой в фиде от брокера), остальные значения окажутся в диапазоне [0, 0.18], теряя вариативность и информативность признака.

Кроме того, MinMaxScaler не гарантирует нормальность распределения. Если исходные данные имеют правостороннюю асимметрию (длинный хвост справа), после нормализации асимметрия сохраняется. Для алгоритмов, которые предполагают нормальное распределение признаков, таких как линейная регрессия или LDA (Linear Discriminant Analysis — Линейный дискриминантный анализ), может потребоваться дополнительное преобразование данных.

Практическая реализация

Давайте рассмотрим как будет вести себя MinMaxScaler на синтетических данных, похожих на финансовые временные ряды.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler

np.random.seed(42)

# Генерация синтетических данных
n_samples = 252  # торговый год
dates = pd.date_range('2024-11-01', periods=n_samples, freq='D')

# Цена закрытия: случайное блуждание с трендом
price_trend = np.linspace(100, 110, n_samples)
price_noise = np.random.normal(0, 5, n_samples)
prices = price_trend + price_noise

# Объем торгов: логнормальное распределение
volumes = np.random.lognormal(13.5, 0.3, n_samples)

# Создание датафрейма
df = pd.DataFrame({
    'Date': dates,
    'Close': prices,
    'Volume': volumes
})

# Применение MinMaxScaler
scaler = MinMaxScaler()
df[['Close_scaled', 'Volume_scaled']] = scaler.fit_transform(df[['Close', 'Volume']])

# Визуализация
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Исходные данные
axes[0, 0].plot(df['Date'], df['Close'], color='black', linewidth=1)
axes[0, 0].set_title('Цена закрытия (исходная)', fontsize=11, fontweight='bold')
axes[0, 0].set_ylabel('Цена, $')
axes[0, 0].grid(True, alpha=0.3)

axes[0, 1].plot(df['Date'], df['Volume'], color='black', linewidth=1)
axes[0, 1].set_title('Объем торгов (исходный)', fontsize=11, fontweight='bold')
axes[0, 1].set_ylabel('Объем')
axes[0, 1].grid(True, alpha=0.3)

# Масштабированные данные
axes[1, 0].plot(df['Date'], df['Close_scaled'], color='#2E86AB', linewidth=1)
axes[1, 0].axhline(y=0.5, color='red', linestyle='--', linewidth=0.8, alpha=0.7)  # центр диапазона
axes[1, 0].axhline(y=0, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)   # нижняя граница
axes[1, 0].axhline(y=1, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)   # верхняя граница
axes[1, 0].set_title('Цена закрытия (MinMaxScaler)', fontsize=11, fontweight='bold')
axes[1, 0].set_ylabel('Масштаб [0, 1]')
axes[1, 0].set_xlabel('Дата')
axes[1, 0].grid(True, alpha=0.3)

axes[1, 1].plot(df['Date'], df['Volume_scaled'], color='#2E86AB', linewidth=1)
axes[1, 1].axhline(y=0.5, color='red', linestyle='--', linewidth=0.8, alpha=0.7)  # центр диапазона
axes[1, 1].axhline(y=0, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)   # нижняя граница
axes[1, 1].axhline(y=1, color='gray', linestyle=':', linewidth=0.8, alpha=0.5)   # верхняя граница
axes[1, 1].set_title('Объем торгов (MinMaxScaler)', fontsize=11, fontweight='bold')
axes[1, 1].set_ylabel('Масштаб [0, 1]')
axes[1, 1].set_xlabel('Дата')
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Статистика преобразования
print("Статистика до масштабирования:")
print(df[['Close', 'Volume']].describe())
print("\nСтатистика после масштабирования MinMaxScaler:")
print(df[['Close_scaled', 'Volume_scaled']].describe())
print(f"\nПараметры MinMaxScaler:")
print(f"Минимальные значения: {scaler.data_min_}")
print(f"Максимальные значения: {scaler.data_max_}")
print(f"Диапазон (data_range_): {scaler.data_range_}")

Масштабирование признаков MinMaxScaler для финансовых данных. Верхний ряд — исходные временные ряды цены и объема с разными масштабами. Нижний ряд — масштабированные данные с диапазоном [0, 1], красная пунктирная линия показывает центр диапазона 0.5. Масштабирование сохраняет относительные различия между точками и делает признаки сопоставимыми для алгоритмов, чувствительных к масштабам

Рис. 3: Масштабирование признаков MinMaxScaler для финансовых данных. Верхний ряд — исходные временные ряды цены и объема с разными масштабами. Нижний ряд — масштабированные данные с диапазоном [0, 1], красная пунктирная линия показывает центр диапазона 0.5. Масштабирование сохраняет относительные различия между точками и делает признаки сопоставимыми для алгоритмов, чувствительных к масштабам

Обратите внимание на шкалу значений по оси y для верхних графиков и нижних. Заметно как MinMaxScaler отцентровывает данные по 0.5 и уже относительно них определяет амплитуды.

Статистика до масштабирования:
            Close        Volume
count  252.000000  2.520000e+02
mean   104.981176  7.690148e+05
std      5.853713  2.378962e+05
min     89.849482  2.758520e+05
25%    101.040716  5.909502e+05
50%    105.061273  7.311119e+05
75%    108.658175  8.952815e+05
max    127.590351  1.837037e+06

Статистика после масштабирования MinMaxScaler:
       Close_scaled  Volume_scaled
count    252.000000     252.000000
mean       0.400937       0.315890
std        0.155103       0.152382
min        0.000000       0.000000
25%        0.296528       0.201833
50%        0.403059       0.291612
75%        0.498364       0.396769
max        1.000000       1.000000

Параметры MinMaxScaler:
Минимальные значения: [8.98494817e+01 2.75852008e+05]
Максимальные значения: [1.27590351e+02 1.83703652e+06]
Диапазон (data_range_): [3.77408690e+01 1.56118451e+06]

В статистике выше отдельно стоит обратить внимание на показатели min и max: в исходных данных они составляют 89.849482 и 127.590351 соответственно, в то время как после преобразования через MinMaxScaler — это 0 и 1.

👉🏻  Как предсказать отток клиентов с помощью машинного обучения

Представленный выше пример код масштабирует признаки по шкале от 0 до 1. Но метод поддерживает и другие опции. Вот в чем разница:

  • Вариант [0, 1] подходит для признаков, где нулевое значение естественно соответствует минимуму (например, объем торгов не может быть отрицательным);
  • Вариант [-1, 1] удобен для признаков, у которых середина диапазона имеет смысловое значение (например, индекс относительной силы RSI = 50, либо индекс предпринимательской уверенности равный 50 — все они отражают нейтральное состояние рынка).

Сохраненные параметры data_min_, data_max_ и data_range_ используются для обратного преобразования через метод inverse_transform(). Это важно, когда модель предсказывает масштабированные значения, а для анализа или визуализации нужны исходные единицы измерения.

Ключевое свойство MinMaxScaler — линейность. Если значение x в исходных данных было в два раза больше y, после масштабирования это соотношение сохраняется. Линейность гарантирует, что расстояния между точками изменяются пропорционально, что особенно важно для алгоритмов, использующих метрики расстояний, таких как KNN, SVM или кластеризация.

RobustScaler: устойчивость к выбросам

Метод масштабирования RobustScaler использует статистики, устойчивые к выбросам: медиану вместо среднего, межквартильный размах (IQR) вместо стандартного отклонения.

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

Математический аппарат

Преобразование выполняется по формуле:

x_scaled = (x — Q₂) / IQR

где:

  • x — исходное значение признака;
  • Q₂ — медиана (второй квартиль, 50-й процентиль);
  • IQR — межквартильный размах (Interquartile Range);
  • x_scaled — масштабированное значение.

Медиана Q₂ — значение, которое делит упорядоченные данные пополам: 50% наблюдений меньше медианы, 50% больше. В отличие от среднего, медиана не чувствительна к выбросам. Если в данных есть экстремальное значение, оно не смещает медиану, тогда как среднее может измениться существенно.

Межквартильный размах IQR вычисляется как разность третьего и первого квартилей:

IQR = Q₃ — Q₁

  • где:Q₁ (первый квартиль, 25-й процентиль) — значение, ниже которого 25% данных;
  • Q₃ (третий квартиль, 75-й процентиль) — значение, ниже которого 75% данных.

IQR измеряет разброс центральных 50% данных, игнорируя крайние 25% с каждой стороны. Это делает IQR устойчивым к выбросам в хвостах распределения.

Преимущества при работе с зашумленными данными

Финансовые рынки часто демонстрируют экстремальные события: резкие распродажи, новостные шоки, корпоративные события (сплиты, дивиденды). Эти события создают выбросы в данных о доходностях, объемах и спредах. Выбросы несут информацию о рыночных режимах и не должны удаляться, но одновременно они не должны искажать масштабирование основной массы данных.

Использование StandardScaler в присутствии выбросов приводит к увеличению стандартного отклонения, что сжимает значения основной массы к нулю. Например, если доходность акции обычно находится в диапазоне [-3%, +3%], но один день показал +50% (эффект новостного объявления), стандартное отклонение σ будет рассчитано с учетом этого выброса. В результате обычные колебания ±3% превратятся в z-score около ±0.1, теряя вариативность.

Метод RobustScaler решает эту проблему, игнорируя выбросы при вычислении параметров масштабирования. Медиана и межквартильный размах (IQR) рассчитываются по центральной части распределения, поэтому экстремальные значения, например +50%, не влияют на масштабирование. Обычные колебания ±3% сохраняют свою вариативность после преобразования.

Метод RobustScaler не ограничивает диапазон значений, как это делает MinMaxScaler. Центральные 50% данных (между Q₁ и Q₃) масштабируются примерно в диапазон [-0.5, 0.5], но выбросы могут принимать значения ±5, ±10 и более. Это позволяет моделям учитывать экстремальные события, не искажая масштаб основных данных.

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

Практическая реализация

Создадим данные дневных доходностей с выбросами: основная масса в диапазоне [-2%, +2%], но 5% дней имеют экстремальные доходности до ±15%.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import RobustScaler, StandardScaler, MinMaxScaler

np.random.seed(42)
n_samples = 500

# Основная масса доходностей
returns_base = np.random.normal(0, 1.5, n_samples)

# Добавление выбросов: 5% экстремальных значений
n_outliers = int(n_samples * 0.05)
outlier_indices = np.random.choice(n_samples, n_outliers, replace=False)
outliers = np.random.choice([-1, 1], n_outliers) * np.random.uniform(8, 15, n_outliers)
returns_base[outlier_indices] = outliers

df = pd.DataFrame({
    'Returns': returns_base
})

# Масштабирование
robust_scaler = RobustScaler()
standard_scaler = StandardScaler()
minmax_scaler = MinMaxScaler(feature_range=(0, 1))

df['Returns_robust'] = robust_scaler.fit_transform(df[['Returns']])
df['Returns_standard'] = standard_scaler.fit_transform(df[['Returns']])
df['Returns_minmax'] = minmax_scaler.fit_transform(df[['Returns']])

# Визуализация распределений (2 ряда)
fig, axes = plt.subplots(2, 1, figsize=(14, 8))

# Верхний ряд: гистограммы
axes[0].hist(df['Returns_robust'], bins=50, alpha=0.6, label='RobustScaler', color='#2E86AB', edgecolor='black')
axes[0].hist(df['Returns_standard'], bins=50, alpha=0.5, label='StandardScaler', color='#A23B72', edgecolor='black')
axes[0].hist(df['Returns_minmax'], bins=50, alpha=0.5, label='MinMaxScaler', color='#F18F01', edgecolor='black')
axes[0].axvline(0, color='red', linestyle='--', linewidth=1)
axes[0].set_title('Гистограммы масштабированных доходностей', fontsize=12, fontweight='bold')
axes[0].set_xlabel('Масштабированные значения')
axes[0].set_ylabel('Частота')
axes[0].grid(True, alpha=0.3)
axes[0].legend()

# Нижний ряд: временные ряды
axes[1].plot(df.index, df['Returns_robust'], label='RobustScaler', color='#2E86AB', linewidth=1)
axes[1].plot(df.index, df['Returns_standard'], label='StandardScaler', color='#A23B72', linewidth=1)
axes[1].plot(df.index, df['Returns_minmax'], label='MinMaxScaler', color='#F18F01', linewidth=1)
axes[1].axhline(0, color='red', linestyle='--', linewidth=1)
axes[1].set_title('Временные ряды масштабированных доходностей', fontsize=12, fontweight='bold')
axes[1].set_xlabel('День')
axes[1].set_ylabel('Масштабированное значение')
axes[1].grid(True, alpha=0.3)
axes[1].legend()

plt.tight_layout()
plt.show()

# Отдельный график для сравнения распределений
plt.figure(figsize=(12, 6))
box_data = [df['Returns_robust'], df['Returns_standard'], df['Returns_minmax']]
labels = ['RobustScaler', 'StandardScaler', 'MinMaxScaler']

plt.boxplot(box_data, labels=labels, patch_artist=True,
            boxprops=dict(facecolor='#D3D3D3', color='black'),
            medianprops=dict(color='red', linewidth=2),
            whiskerprops=dict(color='black'),
            capprops=dict(color='black'),
            flierprops=dict(marker='o', markerfacecolor='orange', markersize=5, linestyle='none', markeredgecolor='black'))

plt.title('Сравнение масштабированных распределений', fontsize=14, fontweight='bold')
plt.ylabel('Масштабированные значения')
plt.grid(True, alpha=0.3)
plt.show()

# Статистика сравнения
print("Параметры RobustScaler:")
print(f"Медиана: {robust_scaler.center_}")
print(f"IQR: {robust_scaler.scale_}")

print("\nПараметры StandardScaler:")
print(f"Среднее: {standard_scaler.mean_}")
print(f"Стандартное отклонение: {standard_scaler.scale_}")

print("\nПараметры MinMaxScaler:")
print(f"Минимум: {minmax_scaler.data_min_}")
print(f"Максимум: {minmax_scaler.data_max_}")

# Диапазон центральных 50%
print("\nВлияние выбросов на масштабирование (Q1-Q3):")
print(f"RobustScaler: [{df['Returns_robust'].quantile(0.25):.2f}, {df['Returns_robust'].quantile(0.75):.2f}]")
print(f"StandardScaler: [{df['Returns_standard'].quantile(0.25):.2f}, {df['Returns_standard'].quantile(0.75):.2f}]")
print(f"MinMaxScaler: [{df['Returns_minmax'].quantile(0.25):.2f}, {df['Returns_minmax'].quantile(0.75):.2f}]")

Сравнение распределений доходностей после масштабирования различными методами: RobustScaler, StandardScaler и MinMaxScaler. На верхнем графике: гистограммы распределений, красная пунктирная линия показывает нулевое значение. На нижнем графике - динамика временных рядов масштабированных доходностей по всем трем методам

Рис. 4: Сравнение распределений доходностей после масштабирования различными методами: RobustScaler, StandardScaler и MinMaxScaler. На верхнем графике: гистограммы распределений, красная пунктирная линия показывает нулевое значение. На нижнем графике — динамика временных рядов масштабированных доходностей по всем трем методам

Сравнение масштабированных распределений доходностей с помощью графиков boxplot. Центральная линия в коробке — медиана, края коробки — квартильный размах (Q1–Q3), выбросы показаны точками

Рис. 5: Сравнение масштабированных распределений доходностей с помощью графиков boxplot. Центральная линия в коробке — медиана, края коробки — квартильный размах (Q1–Q3), выбросы показаны точками

По графику выше мы видим ключевое отличие методов при работе с данными с выбросами:

  • RobustScaler сохраняет вариативность центральных данных, выбросы остаются видимыми и не искажают масштаб;
  • StandardScaler сжимает основную массу данных, потому что экстремальные значения стремяться расширить стандартное отклонение;
  • MinMaxScaler сжимает практически все данные в узкий диапазон около нуля, сильно теряя вариативность.
Параметры RobustScaler:
Медиана: [0.0255043]
IQR: [2.07063403]

Параметры StandardScaler:
Среднее: [0.00838531]
Стандартное отклонение: [3.0210029]

Параметры MinMaxScaler:
Минимум: [-14.60986542]
Максимум: [14.79740772]

Влияние выбросов на масштабирование (Q1-Q3):
RobustScaler: [-0.54, 0.46]
StandardScaler: [-0.36, 0.32]
MinMaxScaler: [0.46, 0.53]

Интерпретация показателей:

  1. RobustScaler (медиана 0.0255, IQR 2.07) — центральные 50% данных остаются хорошо различимыми, выбросы почти не влияют. Идеально для финансовых рядов с экстремальными событиями;
  2. StandardScaler (среднее 0.0084, σ 3.02) — выбросы раздувают стандартное отклонение, поэтому обычные колебания сильно сжаты. А это значит модель «теряет» детали основной массы данных;
  3. MinMaxScaler (min -14.61, max 14.80) — экстремальные значения растягивают диапазон, а вся основная масса данных сжимается в узкий интервал около [0.46, 0.53]; чувствительность к выбросам высокая;
  4. Сравнение Q1–Q3: Robust [-0.54, 0.46] показывает хорошую вариативность центральных данных, Standard [-0.36, 0.32] слегка сжимает, MinMax [0.46, 0.53] почти нивелирует всякое распределение.
👉🏻  Что такое регрессионный анализ и как он работает?

Вывод: для зашумленных финансовых данных RobustScaler лучше сохраняет структуру основной массы, тогда как Standard и MinMax искажают распределение из-за влияния экстремальных значений.

Параметры center_ и scale_ в RobustScaler соответствуют медиане и межквартильному размаху (IQR). Эти значения используются для масштабирования новых данных через метод transform().

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

Сравнение методов масштабирования

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

Матрица выбора метода

Характеристика данных StandardScaler MinMaxScaler RobustScaler
Распределение Нормальное или близкое Любое Любое, особенно с тяжелыми хвостами
Наличие выбросов Отсутствуют или удалены Отсутствуют или удалены Присутствуют, несут информацию
Диапазон значений Неограничен Bounded [0, 1] или custom Неограничен
Оптимальные алгоритмы Линейная регрессия, логистическая регрессия, SVM, нейросети Нейросети с sigmoid/tanh, алгоритмы на основе расстояний Все gradient-based методы при зашумленных данных
Интерпретируемость Z-score: количество σ от среднего Позиция в диапазоне min-max Отклонение от медианы в единицах IQR
Чувствительность к новым данным Средняя (новые выбросы искажают μ, σ) Высокая (новый max/min меняет диапазон) Низкая (медиана, IQR устойчивы)
Типичные задачи Классификация, регрессия на чистых данных Computer vision, bounded признаки (проценты, рейтинги) Финансовые данные, сенсорные измерения, данные с артефактами

Таблица демонстрирует, что методы не взаимозаменяемы. StandardScaler предполагает нормальность и чистоту данных. MinMaxScaler требует известных границ и отсутствия выбросов. RobustScaler жертвует некоторой эффективностью на чистых данных ради устойчивости к аномалиям.

Влияние масштабирования на работу моделей

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

  • StandardScaler ускоряет сходимость градиентных методов и нейросетей на данных без выбросов, выравнивая градиенты всех признаков и сокращая количество итераций до сходимости в 2–3 раза;
  • MinMaxScaler полезен для нейросетей с функциями активации, которые работают в ограниченном диапазоне, такими как Sigmoid и tanh, обеспечивая корректную работу первого слоя и предотвращая затухание или накопление сигналов;
  • RobustScaler делает обучение стабильнее на данных с выбросами, так как градиенты, вычисленные на экстремальных значениях, не доминируют, что позволяет использовать более высокий шаг обучения без риска расхождения;
  • Алгоритмы на основе деревьев, такие как Random Forest и XGBoost, почти не зависят от масштабирования с точки зрения качества предсказаний, но масштабирование может ускорить обучение за счет улучшенной числовой стабильности при разбиениях;
  • Модели K-Nearest Neighbors критически зависят от масштаба признаков, потому что признаки с большими значениями доминируют при вычислении расстояний, и применение StandardScaler или RobustScaler выравнивает вклад всех признаков и снижает влияние выбросов;
  • Модели SVM с RBF kernel требуют масштабирования для корректной оценки сходства точек, и StandardScaler тут обычно работает оптимально, тогда как RobustScaler может быть полезен на зашумленных данных.

Другие методы масштабирования

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

👉🏻  Поиск аномалий в данных с Python

MaxAbsScaler

MaxAbsScaler масштабирует каждый признак, деля его на максимальное абсолютное значение. В результате все значения попадают в диапазон [-1, 1], при этом нули остаются нулями. Преобразование выполняется по формуле:

x_scaled = x / max(|x|)

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

  • «Мешок слов» (Bag-of-Words) для текстов;
  • TF-IDF векторы;
  • One-hot кодирование категориальных признаков.

MaxAbsScaler сохраняет разреженность: нули остаются нулями, а ненулевые значения масштабируются. В отличие от него, StandardScaler и MinMaxScaler превращают нули в ненулевые значения, что разрушает разреженную структуру и увеличивает использование памяти.

MaxAbsScaler не центрирует данные, то есть не вычитает среднее или медиану. Это важно, когда значение 0 имеет особое значение. Например, в TF-IDF ноль означает отсутствие слова, и это информативный сигнал, который нельзя менять.

Метод чувствителен к выбросам так же, как MinMaxScaler: одно экстремальное значение определяет максимум, сжимая остальные данные. Для данных с выбросами лучше использовать RobustScaler.

PowerTransformer

Метод PowerTransformer применяет степенные преобразования для приведения распределения признака к нормальному. Метод использует два варианта преобразований:

  • Box-Cox (только для положительных данных);
  • Yeo-Johnson (для любых данных, включая отрицательные и нулевые).

Box-Cox преобразование параметризуется λ (lambda) и определяется как:

x_transformed = (x^λ — 1) / λ, если λ ≠ 0

x_transformed = log(x), если λ = 0

Параметр λ подбирается автоматически методом максимального правдоподобия для максимизации нормальности распределения:

  • при λ = 1 преобразование линейно (не меняет данные);
  • при λ = 0.5 соответствует квадратному корню;
  • при λ = 0 — логарифму.

Yeo-Johnson расширяет метод Box-Cox на все числовые значения. Он использует разные формулы для положительных и отрицательных чисел. Это делает его удобным для финансовых данных, где признаки могут быть отрицательными, например доходности или P&L.

PowerTransformer помогает исправлять асимметрию распределений. Финансовые данные часто имеют правостороннюю асимметрию: большинство значений слева, длинный хвост справа (например, объем торгов или рыночная капитализация). Линейные модели и многие алгоритмы машинного обучения лучше работают с нормальными признаками. PowerTransformer преобразует асимметричное распределение в более симметричное, повышая точность моделей.

После преобразования PowerTransformer автоматически применяет StandardScaler, приводя данные к нулевому среднему и единичной дисперсии. В итоге признаки приближаются к стандартному нормальному распределению N(0, 1).

QuantileTransformer

Метод QuantileTransformer выполняет нелинейное преобразование, отображая распределение признака на равномерное или нормальное распределение. Метод основан на квантильной функции: значения заменяются их процентилями, затем процентили отображаются на целевое распределение.

QuantileTransformer работает в два этапа:

  1. Сначала вычисляется квантильная функция исходного распределения — для каждого значения x определяется его процентиль p;
  2. Процентиль p заменяется на соответствующее значение в целевом распределении — равномерном или нормальном.

Для равномерного распределения значения распределяются в диапазоне [0, 1]: минимальное становится 0, максимальное — 1, а промежуточные — пропорционально их рангу. Для нормального распределения процентили отображаются на квантили стандартного нормального распределения N(0, 1).

Преимущества метода:

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

Особенности:

  • Нелинейность преобразования изменяет расстояния между точками;
  • Близкие значения в областях высокой плотности могут стать более удаленными, если их процентили различаются;
  • Линейные зависимости разрушаются, что может ухудшить работу линейных моделей, но улучшает работу нелинейных алгоритмов, таких как деревья и нейросети.

Корректное применение масштабирования в ML-пайплайнах

Правильное масштабирование в процессе обучения модели важно для предотвращения утечки данных (data leakage) и обеспечения корректных предсказаний на новых данных.

Частые ошибки:

  1. Обучение скейлера на полном датасете, включая тестовую выборку;
  2. Использование разных параметров масштабирования для обучающей (train) и тестовой (test) выборок;
  3. Игнорирование обратного преобразования (inverse transform) при интерпретации предсказаний.

Разделение на train/test и предотвращение data leakage

Утечка данных (Data leakage) возникает, когда информация из тестовой выборки попадает в процесс обучения или предобработки.

Для масштабирования это значит: параметры скейлера (μ и σ для StandardScaler, min и max для MinMaxScaler, медиана и IQR для RobustScaler) должны вычисляться только на обучающей выборке.

Неправильный подход:

# НЕПРАВИЛЬНО: scaler обучается на всех данных
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # Leakage: используются данные из будущего test split
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2)

Этот код вычисляет среднее и стандартное отклонение по всему датасету, включая данные, которые позже попадут в тестовую выборку. Результат: модель косвенно «видит» статистики тестовых данных, что завышает оценку качества и приводит к переобучению на конкретном разбиении.

Правильный подход:

# ПРАВИЛЬНО: сначала split, потом fit scaler только на train
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)  # Вычисляем параметры только по train
X_test_scaled = scaler.transform(X_test)  # Применяем те же параметры к test

Метод fit_transform() одновременно обучает скейлер и применяет преобразование. Его используют только на обучающих данных.

Метод transform() применяет уже вычисленные параметры к новым данным без повторного обучения. Так гарантируется, что тестовые данные масштабируются с теми же μ и σ (для StandardScaler), min и max (для MinMaxScaler) или медианой и IQR (для RobustScaler), что и обучающие.

Важно учитывать: значения в X_test_scaled могут выходить за ожидаемый диапазон:

  • Если в тестовых данных есть значения вне диапазона [min_train, max_train], MinMaxScaler может дать значения вне [0, 1];
  • Если в тесте есть выбросы, отсутствовавшие в обучающем наборе, StandardScaler может дать |z| > 3.
👉🏻  Поиск аномалий в данных с Python

Это нормальное поведение, которое отражает отличие распределения test выборки от train выборки.

Интеграция в Sklearn Pipeline

Класс Pipeline от scikit-learn позволяет автоматизировать последовательность предобработки и обучения модели. Он гарантирует правильное использование fit и transform:

  1. fit вызывается только на обучающих данных;
  2. transform применяется автоматически к новым данным при предсказании.

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

import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import RobustScaler
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score, TimeSeriesSplit
from sklearn.metrics import mean_squared_error, r2_score

np.random.seed(42)

# Синтетические данные: предсказание доходности акции
n_samples = 1000

# Признаки: исторические доходности, объемы, волатильность
lag_returns_1 = np.random.normal(0, 2, n_samples)
lag_returns_5 = np.random.normal(0, 1.5, n_samples)
volume_change = np.random.normal(0, 10, n_samples)
volatility = np.abs(np.random.normal(20, 8, n_samples))

# Добавление выбросов в 3% наблюдений
outlier_idx = np.random.choice(n_samples, int(n_samples * 0.03), replace=False)
lag_returns_1[outlier_idx] *= np.random.uniform(3, 6, len(outlier_idx))
volume_change[outlier_idx] *= np.random.uniform(5, 10, len(outlier_idx))

# Целевая переменная: будущая доходность (с зависимостью от признаков + шум)
target = 0.3 * lag_returns_1 + 0.15 * lag_returns_5 - 0.02 * volatility + np.random.normal(0, 1, n_samples)

X = pd.DataFrame({
    'lag_returns_1d': lag_returns_1,
    'lag_returns_5d': lag_returns_5,
    'volume_change_pct': volume_change,
    'volatility': volatility
})
y = target

# Разделение на train/test (последние 20% для теста, имитация временного ряда)
split_idx = int(len(X) * 0.8)
X_train, X_test = X[:split_idx], X[split_idx:]
y_train, y_test = y[:split_idx], y[split_idx:]

# Создание Pipeline с RobustScaler и Ridge регрессией
pipeline = Pipeline([
    ('scaler', RobustScaler()),
    ('regressor', Ridge(alpha=1.0))
])

# Обучение pipeline
pipeline.fit(X_train, y_train)

# Предсказания
y_pred_train = pipeline.predict(X_train)
y_pred_test = pipeline.predict(X_test)

# Оценка качества
train_r2 = r2_score(y_train, y_pred_train)
test_r2 = r2_score(y_test, y_pred_test)
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
test_rmse = np.sqrt(mean_squared_error(y_test, y_pred_test))

print(f"Train R²: {train_r2:.4f} | Test R²: {test_r2:.4f}")
print(f"Train RMSE: {train_rmse:.4f} | Test RMSE: {test_rmse:.4f}")

# Кросс-валидация с TimeSeriesSplit (для временных рядов)
tscv = TimeSeriesSplit(n_splits=5)
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=tscv, 
                             scoring='neg_mean_squared_error')
cv_rmse = np.sqrt(-cv_scores)

print(f"\nКросс-валидация RMSE: {cv_rmse.mean():.4f} ± {cv_rmse.std():.4f}")

# Доступ к компонентам pipeline
scaler = pipeline.named_steps['scaler']
regressor = pipeline.named_steps['regressor']

print(f"\nПараметры RobustScaler:")
print(f"Медианы: {scaler.center_}")
print(f"IQR: {scaler.scale_}")
print(f"\nКоэффициенты Ridge:")
for feature, coef in zip(X.columns, regressor.coef_):
    print(f"{feature}: {coef:.4f}")
Train R²: 0.3594 | Test R²: 0.3937
Train RMSE: 0.9926 | Test RMSE: 1.0300

Кросс-валидация RMSE: 0.9936 ± 0.0563

Параметры RobustScaler:
Медианы: [ 0.02559429  0.10343076  0.05247758 20.08151803]
IQR: [ 2.69661915  2.01790813 13.27108432 11.37738439]

Коэффициенты Ridge:
lag_returns_1d: 0.7555
lag_returns_5d: 0.3328
volume_change_pct: -0.0044
volatility: -0.2966

Метод Pipeline обеспечивает атомарность операций:

  • fit() обучает все шаги последовательно на одних и тех же обучающих данных;
  • predict() автоматически применяет transform() каждого шага перед передачей данных в модель;

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

TimeSeriesSplit используется для кросс-валидации временных рядов. В отличие от обычного K-Fold, который перемешивает данные случайным образом, TimeSeriesSplit сохраняет временной порядок. Каждый фолд использует более ранние данные для обучения и более поздние для валидации. Это предотвращает утечку данных из будущего.

RobustScaler в Pipeline корректно обрабатывает выбросы, не искажая масштаб основных данных. Ridge-регрессия с L2-регуляризацией снижает переобучение на зашумленных данных. Комбинация RobustScaler + Ridge часто оптимальна для финансовых задач с регулярными аномалиями.

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

Типичные ошибки при масштабировании и как их избежать

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

  1. Утечка данных из будущего (data leakage): использование скейлера на полном датасете, включая тестовую выборку, завышает оценку качества. Решение: fit scaler только на train, transform — к test;
  2. Масштабирование целевой переменной без обратного преобразования: нейросети могут требовать масштабирования y, но предсказания будут в масштабированном виде. Решение: применять inverse_transform() для возвращения предсказаний в исходные единицы;
  3. Разные скейлеры для train и test: несопоставимые масштабы ухудшают качество модели. Решение: использовать один и тот же fitted scaler для всех данных;
  4. Игнорирование масштабирования в продакшене: новые данные подаются без transform(), предсказания некорректны. Решение: сохранять fitted scaler вместе с моделью и применять его ко всем новым наблюдениям;
  5. Масштабирование категориальных признаков после one-hot encoding: StandardScaler превращает бинарные значения в дробные, теряя интерпретацию. Решение: не масштабировать бинарные признаки;
  6. MinMaxScaler для данных с неизвестными границами: новые значения могут выходить за [0, 1], нарушая работу алгоритмов. Решение: использовать clip=True или выбирать RobustScaler;
  7. Забытое масштабирование при feature engineering: новые признаки создаются в исходном масштабе, старые — в масштабированном, создавая дисбаланс. Решение: делать feature engineering до масштабирования или повторно масштабировать все признаки;
  8. Предположение о стационарности параметров: распределения данных меняются (concept drift). Решение: периодически переобучать scaler на скользящем окне или использовать онлайн-методы для потоковых данных.

Заключение

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

  • StandardScaler ускоряет сходимость градиентных методов на чистых данных с нормальным распределением;
  • MinMaxScaler сохраняет форму распределения и ограничивает диапазон, что важно для нейросетей;
  • RobustScaler устойчив к выбросам, сохраняя информативные аномалии, что особенно полезно для финансовых данных.

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

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