Spec-Zone.ru › scikit-learn

Примечание

Перейти к концу, чтобы загрузить полный пример кода. Или запустить этот пример в браузере через JupyterLite или Binder

Статистическое сравнение моделей с использованием поиска по сетке

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

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

Начнем с моделирования данных в форме луны (где идеальное разделение между классами является нелинейным), добавив к ним умеренную степень шума. Точки данных будут принадлежать одному из двух возможных классов, которые предсказываются двумя признаками. Будем моделировать 50 выборок для каждого класса:

import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.datasets import make_moons

X, y = make_moons(noise=0.352, random_state=1, n_samples=100)

sns.scatterplot(
    x=X[:, 0], y=X[:, 1], hue=y, marker="o", s=25, edgecolor="k", legend=False
).set_title("Data")
plt.show()
Data

Сравним производительность SVC оценщиков, которые различаются по параметру kernel, чтобы определить, какой выбор этого гиперпараметра лучше всего предсказывает наши смоделированные данные. Оценим производительность моделей, используя RepeatedStratifiedKFold, повторяя 10 раз 10-кратную стратифицированную перекрестную проверку с использованием разных случайных перестановок данных в каждом повторении. Производительность будет оцениваться с помощью roc_auc_score.

from sklearn.model_selection import GridSearchCV, RepeatedStratifiedKFold
from sklearn.svm import SVC

param_grid = [
    {"kernel": ["linear"]},
    {"kernel": ["poly"], "degree": [2, 3]},
    {"kernel": ["rbf"]},
]

svc = SVC(random_state=0)

cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=10, random_state=0)

search = GridSearchCV(estimator=svc, param_grid=param_grid, scoring="roc_auc", cv=cv)
search.fit(X, y)
GridSearchCV(cv=RepeatedStratifiedKFold(n_repeats=10, n_splits=10, random_state=0),
             estimator=SVC(random_state=0),
             param_grid=[{'kernel': ['linear']},
                         {'degree': [2, 3], 'kernel': ['poly']},
                         {'kernel': ['rbf']}],
             scoring='roc_auc')
В среде Jupyter, пожалуйста, перезапустите этот ячейку, чтобы отобразить HTML-представление, или доверьтесь блокноту.
На GitHub HTML-представление не может быть рендерировано, попробуйте загрузить эту страницу с nbviewer.org.
GridSearchCV(cv=RepeatedStratifiedKFold(n_repeats=10, n_splits=10, random_state=0),
             estimator=SVC(random_state=0),
             param_grid=[{'kernel': ['linear']},
                         {'degree': [2, 3], 'kernel': ['poly']},
                         {'kernel': ['rbf']}],
             scoring='roc_auc')
SVC(random_state=0)
SVC(random_state=0)


Теперь можно просмотреть результаты поиска, отсортированные по mean_test_score:

import pandas as pd

results_df = pd.DataFrame(search.cv_results_)
results_df = results_df.sort_values(by=["rank_test_score"])
results_df = results_df.set_index(
    results_df["params"].apply(lambda x: "_".join(str(val) for val in x.values()))
).rename_axis("kernel")
results_df[["params", "rank_test_score", "mean_test_score", "std_test_score"]]
params rank_test_score mean_test_score std_test_score
kernel
rbf {'kernel': 'rbf'} 1 0.9400 0.079297
linear {'kernel': 'linear'} 2 0.9300 0.077846
3_poly {'degree': 3, 'kernel': 'poly'} 3 0.9044 0.098776
2_poly {'degree': 2, 'kernel': 'poly'} 4 0.6852 0.169106


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

Обычно анализ заканчивается на этом, но половина истории упущена. Выход GridSearchCV не содержит информации о достоверности различий между моделями. Мы не знаем, являются ли эти различия статистически значимыми. Для оценки этого необходимо провести статистический тест. В частности, для сравнения производительности двух моделей следует статистически сравнить их значения AUC. Есть 100 выборок (значения AUC) для каждой модели, так как мы повторили 10 раз 10-кратную перекрестную проверку.

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

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

# create df of model scores ordered by performance
model_scores = results_df.filter(regex=r"split\d*_test_score")

# plot 30 examples of dependency between cv fold and AUC scores
fig, ax = plt.subplots()
sns.lineplot(
    data=model_scores.transpose().iloc[:30],
    dashes=False,
    palette="Set1",
    marker="o",
    alpha=0.5,
    ax=ax,
)
ax.set_xlabel("CV test fold", size=12, labelpad=10)
ax.set_ylabel("Model AUC", size=12)
ax.tick_params(bottom=True, labelbottom=False)
plt.show()

# print correlation of AUC scores across folds
print(f"Correlation of models:\n {model_scores.transpose().corr()}")
plot grid search stats
Correlation of models:
 kernel       rbf    linear    3_poly    2_poly
kernel
rbf     1.000000  0.882561  0.783392  0.351390
linear  0.882561  1.000000  0.746492  0.298688
3_poly  0.783392  0.746492  1.000000  0.355440
2_poly  0.351390  0.298688  0.355440  1.000000

Можно заметить, что производительность моделей сильно зависит от итерации.

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

Для таких случаев разработаны несколько статистических тестов, учитывающих корреляцию. В этом примере мы покажем, как реализовать один из них (так называемый скорректированный t-тест Надьо и Бенжио) в двух статистических рамках: частотной и байесовской.

Сравнение двух моделей: частотный подход

Можно начать с вопроса: «Является ли первая модель статистически значимо лучше второй модели (при ранжировании по mean_test_score)?»

Для ответа на этот вопрос с помощью частотного подхода можно выполнить парный t-тест и вычислить p-значение. Это также известно как тест Диболда-Мариано в литературе по прогнозированию [5]. Было разработано много вариантов такого t-теста, чтобы учесть проблему «независимости выборок», описанную в предыдущем разделе. Мы будем использовать тот, который показал лучшие результаты по воспроизводимости (что оценивает насколько похожа производительность модели при оценке её на разных случайных разбиениях того же набора данных), сохраняя низкий уровень ложноположительных и ложноотрицательных результатов: скорректированный t-тест Надьо и Бенжио [2], который использует 10-кратную перекрёстную проверку, повторяемую 10 раз [3].

Этот скорректированный парный t-тест вычисляется как:

\[t=\frac{\frac{1}{k \cdot r}\sum_{i=1}^{k}\sum_{j=1}^{r}x_{ij}} {\sqrt{(\frac{1}{k \cdot r}+\frac{n_{test}}{n_{train}})\hat{\sigma}^2}}\]

где \(k\) — число итераций, \(r\) — число повторений в перекрестной проверке, \(x\) — разность в производительности моделей, \(n_{test}\) — число выборок, используемых для тестирования, \(n_{train}\) — число выборок, используемых для обучения, и \(\hat{\sigma}^2\) представляет собой дисперсию наблюдаемых различий.

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

import numpy as np
from scipy.stats import t


def corrected_std(differences, n_train, n_test):
    """Corrects standard deviation using Nadeau and Bengio's approach.

    Parameters
    ----------
    differences : ndarray of shape (n_samples,)
        Vector containing the differences in the score metrics of two models.
    n_train : int
        Number of samples in the training set.
    n_test : int
        Number of samples in the testing set.

    Returns
    -------
    corrected_std : float
        Variance-corrected standard deviation of the set of differences.
    """
    # kr = k times r, r times repeated k-fold crossvalidation,
    # kr equals the number of times the model was evaluated
    kr = len(differences)
    corrected_var = np.var(differences, ddof=1) * (1 / kr + n_test / n_train)
    corrected_std = np.sqrt(corrected_var)
    return corrected_std


def compute_corrected_ttest(differences, df, n_train, n_test):
    """Computes right-tailed paired t-test with corrected variance.

    Parameters
    ----------
    differences : array-like of shape (n_samples,)
        Vector containing the differences in the score metrics of two models.
    df : int
        Degrees of freedom.
    n_train : int
        Number of samples in the training set.
    n_test : int
        Number of samples in the testing set.

    Returns
    -------
    t_stat : float
        Variance-corrected t-statistic.
    p_val : float
        Variance-corrected p-value.
    """
    mean = np.mean(differences)
    std = corrected_std(differences, n_train, n_test)
    t_stat = mean / std
    p_val = t.sf(np.abs(t_stat), df)  # right-tailed t-test
    return t_stat, p_val
model_1_scores = model_scores.iloc[0].values  # scores of the best model
model_2_scores = model_scores.iloc[1].values  # scores of the second-best model

differences = model_1_scores - model_2_scores

n = differences.shape[0]  # number of test sets
df = n - 1
n_train = len(list(cv.split(X, y))[0][0])
n_test = len(list(cv.split(X, y))[0][1])

t_stat, p_val = compute_corrected_ttest(differences, df, n_train, n_test)
print(f"Corrected t-value: {t_stat:.3f}\nCorrected p-value: {p_val:.3f}")
Corrected t-value: 0.750
Corrected p-value: 0.227

Давайте сравним скорректированные значения t и p с нескорректированными:

t_stat_uncorrected = np.mean(differences) / np.sqrt(np.var(differences, ddof=1) / n)
p_val_uncorrected = t.sf(np.abs(t_stat_uncorrected), df)

print(
    f"Uncorrected t-value: {t_stat_uncorrected:.3f}\n"
    f"Uncorrected p-value: {p_val_uncorrected:.3f}"
)
Uncorrected t-value: 2.611
Uncorrected p-value: 0.005

Используя обычный уровень значимости альфа в p=0.05, мы видим, что нескорректированный t-тест говорит о том, что первая модель значимо лучше второй.

В отличие от этого, скорректированный подход не позволяет сделать этого вывода.

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

Сравнение двух моделей: байесовский подход

Мы можем использовать байесовскую оценку для расчета вероятности того, что первая модель лучше второй. Байесовская оценка выведет распределение, за которым следует среднее значение \(\mu\) разницы в производительности двух моделей.

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

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

Один из способов определения нашего апостериорного распределения с помощью замкнутой формы — выбрать априорное распределение, сопряженное с функцией правдоподобия. Бенаволи и коллеги [4] показывают, что при сравнении производительности двух классификаторов мы можем смоделировать априорное распределение как нормально-гамма-распределение (с неизвестным средним значением и дисперсией), сопряженное с нормальным правдоподобием, чтобы выразить апостериорное распределение как нормальное распределение. Выполняя интегрирование по дисперсии из этого нормального апостериорного распределения, мы можем определить апостериорное распределение параметра среднего значения как распределение Стьюдента. В частности:

\[St(\mu;n-1,\overline{x},(\frac{1}{n}+\frac{n_{test}}{n_{train}}) \hat{\sigma}^2)\]

где \(n\) — общее количество образцов, \(\overline{x}\) представляет собой среднее значение разницы в баллах, \(n_{test}\) — количество образцов, используемых для тестирования, \(n_{train}\) — количество образцов, используемых для обучения, а \(\hat{\sigma}^2\) — дисперсия наблюдаемых различий.

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

Давайте вычислим и построим апостериорное распределение:

# initialize random variable
t_post = t(
    df, loc=np.mean(differences), scale=corrected_std(differences, n_train, n_test)
)

Давайте построим график апостериорного распределения:

x = np.linspace(t_post.ppf(0.001), t_post.ppf(0.999), 100)

plt.plot(x, t_post.pdf(x))
plt.xticks(np.arange(-0.04, 0.06, 0.01))
plt.fill_between(x, t_post.pdf(x), 0, facecolor="blue", alpha=0.2)
plt.ylabel("Probability density")
plt.xlabel(r"Mean difference ($\mu$)")
plt.title("Posterior distribution")
plt.show()
Posterior distribution

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

better_prob = 1 - t_post.cdf(0)

print(
    f"Probability of {model_scores.index[0]} being more accurate than "
    f"{model_scores.index[1]}: {better_prob:.3f}"
)
print(
    f"Probability of {model_scores.index[1]} being more accurate than "
    f"{model_scores.index[0]}: {1 - better_prob:.3f}"
)
Probability of rbf being more accurate than linear: 0.773
Probability of linear being more accurate than rbf: 0.227

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

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

Область практического эквивалента

Иногда мы заинтересованы в определении вероятностей того, что наши модели имеют эквивалентную производительность, где «эквивалентность» определяется практическим способом. Примитивный подход [4] заключался бы в определении оценщиков как практически эквивалентных, если они отличаются менее чем на 1% по точности. Но мы также можем определить эту практическую эквивалентность, учитывая задачу, которую мы пытаемся решить. Например, различие в 5% по точности означало бы увеличение продаж на 1000 долларов, и мы считаем любые значения выше этого значения релевантными для нашего бизнеса.

В этом примере мы определим область практического эквивалента (ROPE) как \([-0.01, 0.01]\). То есть мы будем считать две модели практически эквивалентными, если они отличаются менее чем на 1% по производительности.

Для вычисления вероятностей практической эквивалентности классификаторов мы вычисляем площадь под кривой апостериорного распределения на интервале ROPE:

rope_interval = [-0.01, 0.01]
rope_prob = t_post.cdf(rope_interval[1]) - t_post.cdf(rope_interval[0])

print(
    f"Probability of {model_scores.index[0]} and {model_scores.index[1]} "
    f"being practically equivalent: {rope_prob:.3f}"
)
Probability of rbf and linear being practically equivalent: 0.432

Мы можем построить график распределения апостериорного распределения по интервалу ROPE:

x_rope = np.linspace(rope_interval[0], rope_interval[1], 100)

plt.plot(x, t_post.pdf(x))
plt.xticks(np.arange(-0.04, 0.06, 0.01))
plt.vlines([-0.01, 0.01], ymin=0, ymax=(np.max(t_post.pdf(x)) + 1))
plt.fill_between(x_rope, t_post.pdf(x_rope), 0, facecolor="blue", alpha=0.2)
plt.ylabel("Probability density")
plt.xlabel(r"Mean difference ($\mu$)")
plt.title("Posterior distribution under the ROPE")
plt.show()
Posterior distribution under the ROPE

Как предлагается в [4], мы можем далее интерпретировать эти вероятности, используя те же критерии, что и частотный подход: является ли вероятность попадания в ROPE больше 95% (значение альфа 5%)? В этом случае мы можем заключить, что обе модели практически эквивалентны.

Байесовский подход к оценке также позволяет нам вычислить, насколько не уверены мы в нашей оценке разницы. Это можно вычислить, используя доверительные интервалы. Для заданной вероятности они показывают диапазон значений, которые может принимать оцениваемая величина, в нашем случае среднее значение разницы в производительности. Например, 50%-ный доверительный интервал [x, y] говорит нам о том, что существует 50%-ная вероятность того, что истинное (среднее) различие в производительности между моделями находится между x и y.

Давайте определим доверительные интервалы наших данных, используя 50%, 75% и 95%:

cred_intervals = []
intervals = [0.5, 0.75, 0.95]

for interval in intervals:
    cred_interval = list(t_post.interval(interval))
    cred_intervals.append([interval, cred_interval[0], cred_interval[1]])

cred_int_df = pd.DataFrame(
    cred_intervals, columns=["interval", "lower value", "upper value"]
).set_index("interval")
cred_int_df
lower value upper value
interval
0.50 0.000977 0.019023
0.75 -0.005422 0.025422
0.95 -0.016445 0.036445


Как показано в таблице, существует 50%-ная вероятность того, что истинное среднее различие между моделями будет находиться между 0,000977 и 0,019023, 70%-ная вероятность того, что оно будет находиться между -0,005422 и 0,025422, и 95%-ная вероятность того, что оно будет находиться между -0,016445 и 0,036445.

Парное сравнение всех моделей: частотный подход

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

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

Давайте сравним производительность моделей, используя скорректированный t-тест:

from itertools import combinations
from math import factorial

n_comparisons = factorial(len(model_scores)) / (
    factorial(2) * factorial(len(model_scores) - 2)
)
pairwise_t_test = []

for model_i, model_k in combinations(range(len(model_scores)), 2):
    model_i_scores = model_scores.iloc[model_i].values
    model_k_scores = model_scores.iloc[model_k].values
    differences = model_i_scores - model_k_scores
    t_stat, p_val = compute_corrected_ttest(differences, df, n_train, n_test)
    p_val *= n_comparisons  # implement Bonferroni correction
    # Bonferroni can output p-values higher than 1
    p_val = 1 if p_val > 1 else p_val
    pairwise_t_test.append(
        [model_scores.index[model_i], model_scores.index[model_k], t_stat, p_val]
    )

pairwise_comp_df = pd.DataFrame(
    pairwise_t_test, columns=["model_1", "model_2", "t_stat", "p_val"]
).round(3)
pairwise_comp_df
model_1 model_2 t_stat p_val
0 rbf linear 0.750 1.000
1 rbf 3_poly 1.657 0.302
2 rbf 2_poly 4.565 0.000
3 linear 3_poly 1.111 0.807
4 linear 2_poly 4.276 0.000
5 3_poly 2_poly 3.851 0.001


Мы наблюдаем, что после поправки на множественные сравнения единственной моделью, которая значительно отличается от других, является '2_poly'. 'rbf', модель, занявшая первое место по результатам GridSearchCV, не имеет значимых различий с 'linear' или '3_poly'.

Парное сравнение всех моделей: байесовский подход

При использовании байесовской оценки для сравнения нескольких моделей нам не нужно вносить поправки на множественные сравнения (причины, по которым см. [4]).

Мы можем провести наши парные сравнения так же, как и в первой части:

pairwise_bayesian = []

for model_i, model_k in combinations(range(len(model_scores)), 2):
    model_i_scores = model_scores.iloc[model_i].values
    model_k_scores = model_scores.iloc[model_k].values
    differences = model_i_scores - model_k_scores
    t_post = t(
        df, loc=np.mean(differences), scale=corrected_std(differences, n_train, n_test)
    )
    worse_prob = t_post.cdf(rope_interval[0])
    better_prob = 1 - t_post.cdf(rope_interval[1])
    rope_prob = t_post.cdf(rope_interval[1]) - t_post.cdf(rope_interval[0])

    pairwise_bayesian.append([worse_prob, better_prob, rope_prob])

pairwise_bayesian_df = pd.DataFrame(
    pairwise_bayesian, columns=["worse_prob", "better_prob", "rope_prob"]
).round(3)

pairwise_comp_df = pairwise_comp_df.join(pairwise_bayesian_df)
pairwise_comp_df
model_1 model_2 t_stat p_val worse_prob better_prob rope_prob
0 rbf linear 0.750 1.000 0.068 0.500 0.432
1 rbf 3_poly 1.657 0.302 0.018 0.882 0.100
2 rbf 2_poly 4.565 0.000 0.000 1.000 0.000
3 linear 3_poly 1.111 0.807 0.063 0.750 0.187
4 linear 2_poly 4.276 0.000 0.000 1.000 0.000
5 3_poly 2_poly 3.851 0.001 0.000 1.000 0.000


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

Результаты показывают, что модель, занявшая первое место по результатам GridSearchCV 'rbf', имеет примерно 6,8% шанс быть хуже, чем 'linear', и 1,8% шанс быть хуже, чем '3_poly'. 'rbf' и 'linear' имеют 43% вероятность быть практически эквивалентными, а 'rbf' и '3_poly' имеют 10% шанс на это.

Аналогично выводам, полученным с помощью частотного подхода, все модели имеют 100% вероятность быть лучше, чем '2_poly', и ни одна из них не имеет практически эквивалентной производительности с последней.

Основные выводы

  • Небольшие различия в показателях производительности могут быть случайными, а не потому, что одна модель систематически предсказывает лучше, чем другая. Как показано в этом примере, статистика может показать, насколько это вероятно.
  • При статистическом сравнении производительности двух моделей, оцененных с помощью GridSearchCV, необходимо скорректировать рассчитанную дисперсию, которая может быть занижена, так как оценки моделей не являются независимыми друг от друга.
  • Частотный подход, использующий (исправленный на дисперсию) парный t-тест, может показать, лучше ли производительность одной модели, чем другой, с уверенностью, превышающей случайность.
  • Баесовский подход может предоставить вероятности того, что одна модель лучше, хуже или практически эквивалентна другой. Он также может показать, насколько мы уверены в том, что истинные различия в наших моделях попадают в определенный диапазон значений.
  • Если сравнивается несколько моделей, при использовании частотного подхода требуется корректировка по множественному сравниванию.

Ссылки

[1]

Dietterich, T. G. (1998). Approximate statistical tests for comparing supervised classification learning algorithms. Neural computation, 10(7).

[2]

Nadeau, C., & Bengio, Y. (2000). Inference for the generalization error. In Advances in neural information processing systems.

[3]

Bouckaert, R. R., & Frank, E. (2004). Evaluating the replicability of significance tests for comparing learning algorithms. In Pacific-Asia Conference on Knowledge Discovery and Data Mining.

[4](1,2,3,4,5)

Benavoli, A., Corani, G., Demšar, J., & Zaffalon, M. (2017). Time for a change: a tutorial for comparing multiple classifiers through Bayesian analysis. The Journal of Machine Learning Research, 18(1). See the Python library that accompanies this paper here.

[5]

Diebold, F.X. & Mariano R.S. (1995). Comparing predictive accuracy Journal of Business & economic statistics, 20(1), 134-144.

Общее время выполнения скрипта: (0 минут 2.032 секунды)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_grid_search_stats.ipynb

Download Python source code: plot_grid_search_stats.py

Download zipped: plot_grid_search_stats.zip

Связанные примеры

Тестирование с перестановками значимости классификационной оценки

Сравнение калибровки классификаторов

Общие ловушки при интерпретации коэффициентов линейных моделей

Пример конвейера для извлечения и оценки признаков текста

© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/model_selection/plot_grid_search_stats.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API