The Jerusalem PostTrump has a chance to unite Saudi Arabia, UAE, and Israel against Iran and the Houthis - opinionDaily MaverickBUSINESS REFLECTION: Crossed Wires: AI smashes the foundations of the global kingdom of mathematicsESPN DeportesArsenal triunfa y sigue a la caza del líder CityPunchEvening recap: Tinubu’s 2027 alliance, Eaglets’ AFCON setback, Tenia Abraham’s death, other top storiesESPNTransfer rumors, news: Could Viktor Gyökeres leave Arsenal in January?RTP DesportoArsenal vence em Nottingham e continua na perseguição ao líder Manchester CityInquirerHope, faith, family support fan breast cancer patients’ path to recoveryBollywood HungamaTiger Shroff safe after firing incident reported under Juhu Police StationZDF heuteEntdecken Sie das ZDF-NachrichtenstudioPremium TimesTwo killed, two injured in Lagos road crash – LASTMAالشرقيوم الغضب.. فرنسا تواجه تداعيات أسوأ اضطرابات منذ سنواتWirtualna Polska"Co oni mają w głowach?". Macron nie wytrzymał
The Daily Newsstand · Free, Always
Sunday, October 18, 2026

Насколько сильно Random State влияет на качество ML-модели? Проверяем на 100 экспериментах

Translate

Представим ситуацию: мы обучили модель, получили Accuracy 77% и решили немного изменить параметры. Запустили обучение повторно — теперь получилось 72%.

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

А потом замечаем, что вместе с параметрами изменился random_state.

Возникает вопрос: насколько вообще можно доверять результату одного запуска?

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

Спойлер: в моём эксперименте изменение только случайного разбиения позволяло получить Accuracy от 64% до 79,33% для одного и того же дерева решений. Причём сам алгоритм и его гиперпараметры оставались неизменными.

Что такое random_state и зачем он нужен?

В большинстве ML-проектов рано или поздно встречается что-то подобное:

model = RandomForestClassifier(random_state=42)

Или:

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42
)

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

Например, он может влиять на:

  • Разбиение датасета на обучающую и тестовую выборки.

  • Порядок перемешивания объектов.

  • Выбор признаков и подвыборок в некоторых алгоритмах.

  • Инициализацию параметров отдельных моделей.

  • Случайные операции внутри процесса обучения.

Фиксированный random_state нужен прежде всего для воспроизводимости эксперимента.

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

Но здесь есть важная деталь: random_state=42 не делает модель лучше, чем random_state=123.

Число 42 — просто популярная традиция, а не какое-то специальное значение.

При этом влияние random_state зависит от того, где именно мы его используем.

Например, изменение seed в train_test_split может привести к совершенно другому составу обучающей и тестовой выборок. А изменение seed в Random Forest при фиксированных данных изменит случайные решения внутри самого алгоритма.

Это два разных источника вариативности, и их стоит изучать отдельно.

Эксперимент №1. Сто разных разбиений одного датасета

Начнём с самого интересного.

Создадим искусственный датасет для бинарной классификации, состоящий из 1200 объектов и 20 признаков.

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

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

Генерация данных

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.metrics import accuracy_score

X, y = make_classification(
    n_samples=1200,
    n_features=20,
    n_informative=8,
    n_redundant=4,
    flip_y=0.08,
    class_sep=0.8,
    random_state=42
)

print(X.shape)

Получаем:

(1200, 20)

Для сравнения возьмём два алгоритма:

  1. Logistic Regression — относительно простая линейная модель.

  2. Decision Tree — нелинейная модель, чувствительная к изменениям обучающих данных.

Все гиперпараметры зафиксируем.

Обучаем модели 100 раз

Будем менять только random_state при разделении датасета.

Каждый раз модель будет получать новое разбиение, состоящее из 75% обучающих и 25% тестовых объектов.

models = {
    "Logistic Regression": lambda: make_pipeline(
        StandardScaler(),
        LogisticRegression(
            max_iter=2000,
            random_state=42
        )
    ),

    "Decision Tree": lambda: DecisionTreeClassifier(
        max_depth=6,
        random_state=42
    )
}

results = []

for seed in range(100):

    X_train, X_test, y_train, y_test = train_test_split(
        X,
        y,
        test_size=0.25,
        stratify=y,
        random_state=seed
    )

    for name, create_model in models.items():

        model = create_model()

        model.fit(X_train, y_train)

        predictions = model.predict(X_test)

        accuracy = accuracy_score(y_test, predictions)

        results.append({
            "seed": seed,
            "model": name,
            "accuracy": accuracy
        })

results = pd.DataFrame(results)

В этом эксперименте важно обратить внимание на две вещи.

Во-первых, сами модели не меняются. Все их параметры остаются одинаковыми.

Во-вторых, на каждом seed обе модели получают одно и то же разбиение. Это позволяет сравнивать их результаты в одинаковых условиях.

Теперь посмотрим на статистику.

summary = results.groupby("model")["accuracy"].agg(
    ["mean", "std", "min", "max"]
)

print(summary.round(4))

Я выполнил этот код на scikit-learn 1.8.0. Получились следующие результаты:

Модель

Средняя Accuracy

Std

Min

Max

Logistic Regression

0.7368

0.0218

0.6767

0.7767

Decision Tree

0.7248

0.0283

0.6400

0.7933

Для Logistic Regression значения Accuracy варьировались от 67,67% до 77,67%.

Для Decision Tree — от 64% до 79,33%.

То есть у дерева решений разница между худшим и лучшим результатом составила 15,33 процентного пункта.

plt.figure(figsize=(10, 5))

for name, group in results.groupby("model"):
    plt.hist(
        group["accuracy"],
        bins=15,
        alpha=0.5,
        label=name
    )

plt.xlabel("Accuracy")
plt.ylabel("Number of runs")
plt.title("Accuracy distribution across 100 random splits")
plt.legend()
plt.grid(alpha=0.2)

plt.tight_layout()
plt.show()

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

При этом разброс дерева заметно больше.

И самое важное: мы не меняли алгоритм, не проводили подбор гиперпараметров и не добавляли новые признаки.

Менялось только распределение объектов между train и test.

Почему разница оказалась настолько большой?

Здесь нет ничего мистического.

Наш датасет содержит всего 1200 объектов. При тестовой доле 25% качество оценивается на 300 примерах.

Поэтому одна дополнительная ошибка изменяет Accuracy примерно на 0,33 процентного пункта.

Если в одной тестовой выборке оказалось больше сложных объектов, модель получит более низкую метрику. Если больше простых — более высокую.

Даже стратификация не гарантирует одинаковую сложность тестовых выборок.

stratify=y сохраняет примерно одинаковое соотношение классов, но никак не гарантирует, что выбранные объекты будут одинаково предсказуемыми.

Кроме того, меняется не только test, но и train.

Это означает, что мы одновременно наблюдаем два эффекта:

  • Модель обучается на немного разных данных.

  • Качество оценивается на разных объектах.

Поэтому полученный разброс нельзя полностью приписывать нестабильности самого алгоритма.

Чтобы изучить именно её, понадобится второй эксперимент.

Эксперимент №2. Меняем seed внутри Random Forest

Теперь зафиксируем обучающую и тестовую выборки.

После этого обучим Random Forest 30 раз, каждый раз изменяя только random_state самой модели.

Так мы исключим изменение состава train и test.

from sklearn.ensemble import RandomForestClassifier

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.25,
    stratify=y,
    random_state=42
)

forest_results = []

for seed in range(30):

    model = RandomForestClassifier(
        n_estimators=100,
        max_features="sqrt",
        random_state=seed,
        n_jobs=-1
    )

    model.fit(X_train, y_train)

    predictions = model.predict(X_test)

    accuracy = accuracy_score(y_test, predictions)

    forest_results.append(accuracy)

forest_results = np.array(forest_results)

print("Mean:", forest_results.mean())
print("Std:", forest_results.std())
print("Min:", forest_results.min())
print("Max:", forest_results.max())

Результаты:

Метрика

Значение

Mean Accuracy

0.8230

Std

0.0094

Min Accuracy

0.7933

Max Accuracy

0.8367

Теперь разброс значительно меньше.

Разница между минимальной и максимальной Accuracy составляет около 4,33 процентного пункта.

Причём изменение seed уже не связано с изменением обучающей или тестовой выборки.

Влияют случайные операции самого Random Forest: bootstrap-выборки объектов, выбор подмножеств признаков и особенности построения отдельных деревьев.

В данном эксперименте случайность самого алгоритма привела к меньшему разбросу результатов, чем изменение train/test split в первом эксперименте.

Но делать из этого универсальное правило нельзя. Результат будет зависеть от датасета, архитектуры модели, её гиперпараметров и количества объектов.

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

А можно просто найти самый удачный random_state?

После первого эксперимента возникает соблазнительная идея.

Если на одном seed мы получаем Accuracy 64%, а на другом 79,33%, почему бы просто не выбрать второй?

Технически это очень легко:

best_result = results.loc[
    results["accuracy"].idxmax()
]

print(best_result)

Но здесь мы сталкиваемся с проблемой.

Выбирая seed по максимальной тестовой метрике, мы фактически начинаем подбирать тестовую выборку под желаемый результат.

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

Это разновидность утечки информации из тестовой выборки в процесс выбора модели.

Допустим, мы протестировали 100 вариантов и выбрали лучший.

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

Мы просто выбрали наиболее удачное наблюдение из множества попыток.

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

Random State не является гиперпараметром, который стоит оптимизировать по тестовой метрике.

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

Эксперимент №3. Используем кросс-валидацию

Один из способов получить более устойчивую оценку качества — кросс-валидация.

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

Например, при 5-Fold Cross-Validation:

  1. Разделяем датасет на пять частей.

  2. Обучаем модель на четырёх частях.

  3. Оцениваем на оставшейся.

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

  5. Усредняем результаты.

Используем StratifiedKFold, поскольку у нас задача бинарной классификации.

from sklearn.model_selection import (
    StratifiedKFold,
    cross_val_score
)

cv = StratifiedKFold(
    n_splits=5,
    shuffle=True,
    random_state=42
)

for name, create_model in models.items():

    model = create_model()

    scores = cross_val_score(
        model,
        X,
        y,
        cv=cv,
        scoring="accuracy"
    )

    print(name)
    print("Scores:", np.round(scores, 4))
    print("Mean:", round(scores.mean(), 4))
    print("Std:", round(scores.std(), 4))
    print()

Получилось следующее:

Logistic Regression

Scores: [0.7625 0.7417 0.7333 0.7167 0.7375]
Mean: 0.7383
Std: 0.0148

Decision Tree

Scores: [0.7417 0.7208 0.7167 0.7000 0.7000]
Mean: 0.7158
Std: 0.0155

Для Logistic Regression средняя Accuracy составила 73,83%, а для Decision Tree — 71,58%.

Это довольно близко к средним значениям из первого эксперимента.

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

Но у неё тоже есть ограничения.

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

Кроме того, стандартное отклонение Accuracy между фолдами — не то же самое, что доверительный интервал истинного качества модели.

Если нужна более подробная оценка устойчивости, можно использовать Repeated Stratified K-Fold с несколькими повторениями.

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

Может ли Random State поменять победителя?

Вернёмся к первому эксперименту.

В среднем Logistic Regression показала результат лучше, чем Decision Tree:

  • Logistic Regression: 73,68%.

  • Decision Tree: 72,48%.

Разница составляет всего 1,2 процентного пункта.

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

Посмотрим, как часто менялся победитель.

comparison = results.pivot(
    index="seed",
    columns="model",
    values="accuracy"
)

tree_wins = (
    comparison["Decision Tree"] >
    comparison["Logistic Regression"]
).sum()

logreg_wins = (
    comparison["Logistic Regression"] >
    comparison["Decision Tree"]
).sum()

draws = (
    comparison["Decision Tree"] ==
    comparison["Logistic Regression"]
).sum()

print("Decision Tree wins:", tree_wins)
print("Logistic Regression wins:", logreg_wins)
print("Draws:", draws)

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

Поэтому если разница между алгоритмами составляет пару десятых процента на одном случайном split, объявлять победителя рано.

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

Для наглядности можно построить график зависимости Accuracy от seed:

plt.figure(figsize=(12, 5))

for name, group in results.groupby("model"):

    plt.plot(
        group["seed"],
        group["accuracy"],
        label=name,
        alpha=0.75
    )

plt.xlabel("Random State")
plt.ylabel("Accuracy")
plt.title("Accuracy depending on random split")
plt.legend()
plt.grid(alpha=0.2)

plt.tight_layout()
plt.show()

Здесь хорошо видно ещё одну вещь: значение seed не имеет осмысленной числовой связи с качеством.

Например, seed 90 не должен давать результат лучше, чем seed 10, просто потому, что он больше.

Это всего лишь способ инициализации генератора случайных чисел.

А что будет на большом датасете?

Можно предположить, что с увеличением количества объектов разброс должен уменьшаться.

Во многих случаях так и происходит.

Если тестовая выборка содержит 300 объектов, ошибка на одном из них изменяет Accuracy на 1/300.

Если тестовая выборка содержит 30 000 объектов — уже на 1/30 000.

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

Но это не означает, что большой датасет полностью решает проблему.

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

Например, модель может хорошо работать на покупателях из одного региона и плохо — из другого.

Тогда важнее оказывается не столько размер выборки, сколько её репрезентативность.

Поэтому вопрос стоит формулировать не просто как «достаточно ли у нас данных?», а как «насколько хорошо наша проверочная выборка отражает реальные условия работы модели?».

Как я бы организовал сравнение моделей

После такого эксперимента становится очевидно, что один запуск с random_state=42 — довольно слабое основание для окончательных выводов.

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

1. Сначала определить правильную стратегию разделения данных.

Если объекты независимы — можно использовать Stratified K-Fold для классификации.

Если есть группы связанных объектов или временная зависимость — выбрать соответствующую групповую или временную валидацию.

2. Сравнивать модели на одинаковых разбиениях.

Это помогает избежать ситуации, когда один алгоритм получил сложный test, а другой — простой.

3. Смотреть не только на среднюю метрику, но и на её разброс.

Разница в 0,5 процентного пункта не выглядит убедительно, если значения между разными разбиениями колеблются на несколько процентов.

При этом разброс метрик сам по себе не заменяет статистическую проверку превосходства моделей.

4. Отдельно изучать случайность внутри алгоритма.

Для Random Forest, нейросетей и некоторых других моделей полезно проверять несколько seed при фиксированной процедуре оценки.

5. Оставлять отдельный тестовый набор для финальной проверки.

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

Тестовую выборку желательно использовать только после завершения этого процесса.

Если постоянно смотреть на тестовые результаты и принимать на их основе решения, со временем тест перестаёт быть независимым.

Несколько ограничений нашего эксперимента

Стоит отдельно проговорить, чего результаты не доказывают.

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

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

В-третьих, мы измеряли Accuracy на приблизительно сбалансированной выборке. Для других задач более подходящими могут быть ROC-AUC, PR-AUC, F1, MAE и другие метрики.

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

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

Итоги

В начале статьи мы хотели выяснить, насколько сильно Random State может влиять на качество ML-модели.

Получилось следующее:

Эксперимент

Минимальная Accuracy

Максимальная Accuracy

Разница

Logistic Regression, 100 разбиений

67,67%

77,67%

10 п.п.

Decision Tree, 100 разбиений

64,00%

79,33%

15,33 п.п.

Random Forest, 30 seed при фиксированном split

79,33%

83,67%

4,33 п.п.

Даже в таком небольшом эксперименте изменение случайного разбиения могло заметно повлиять на итоговую Accuracy.

Но главный вывод немного шире.

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

Пытаться подобрать seed с максимальной тестовой метрикой — плохая идея. Гораздо полезнее оценивать устойчивость модели к различным разбиениям и случайным инициализациям.

И если одна модель получила Accuracy 85,1%, а другая 85,4%, это ещё не обязательно означает, что вторая действительно лучше.

Возможно, стоит провести ещё несколько экспериментов, прежде чем делать выводы.

Полезные материалы:

Код экспериментов написан на Python с использованием NumPy, pandas, matplotlib и scikit-learn. Все значения метрик выше получены на scikit-learn 1.8.0.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.