Создание минимального воспроизводимого примера для scikit-learn#

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

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

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

Рекомендации#

В этом разделе мы сосредоточимся на Шаги/Код для воспроизведения раздел Шаблон issue. Мы начнем с фрагмента кода, который уже предоставляет неудачный пример, но имеет возможности для улучшения читаемости. Затем мы создаем MCVE из него.

Пример

# I am currently working in a ML project and when I tried to fit a
# GradientBoostingRegressor instance to my_data.csv I get a UserWarning:
# "X has feature names, but DecisionTreeRegressor was fitted without
# feature names". You can get a copy of my dataset from
# https://example.com/my_data.csv and verify my features do have
# names. The problem seems to arise during fit when I pass an integer
# to the n_iter_no_change parameter.

df = pd.read_csv('my_data.csv')
X = df[["feature_name"]] # my features do have names
y = df["target"]

# We set random_state=42 for the train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.33, random_state=42
)

scaler = StandardScaler(with_mean=False)
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# An instance with default n_iter_no_change raises no error nor warnings
gbdt = GradientBoostingRegressor(random_state=0)
gbdt.fit(X_train, y_train)
default_score = gbdt.score(X_test, y_test)

# the bug appears when I change the value for n_iter_no_change
gbdt = GradientBoostingRegressor(random_state=0, n_iter_no_change=5)
gbdt.fit(X_train, y_train)
other_score = gbdt.score(X_test, y_test)

other_score = gbdt.score(X_test, y_test)

Предоставьте пример кода с ошибкой с минимальными комментариями#

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

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

  • он содержит все необходимые операторы импорта;

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

Улучшенный пример

import pandas as pd

df = pd.read_csv("https://example.com/my_data.csv")
X = df[["feature_name"]]
y = df["target"]

from sklearn.model_selection import train_test_split

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

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler(with_mean=False)
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

from sklearn.ensemble import GradientBoostingRegressor

gbdt = GradientBoostingRegressor(random_state=0)
gbdt.fit(X_train, y_train)  # no warning
default_score = gbdt.score(X_test, y_test)

gbdt = GradientBoostingRegressor(random_state=0, n_iter_no_change=5)
gbdt.fit(X_train, y_train)  # raises warning
other_score = gbdt.score(X_test, y_test)
other_score = gbdt.score(X_test, y_test)

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

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

В частности, для этого конкретного примера:

  • предупреждение не имеет ничего общего с train_test_split поскольку он уже появляется на этапе обучения, до использования тестового набора.

  • аналогично, строки, вычисляющие оценки на тестовом наборе, не обязательны;

  • ошибку можно воспроизвести для любого значения random_state поэтому оставьте его по умолчанию;

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

Улучшенный пример

import pandas as pd
df = pd.read_csv("https://example.com/my_data.csv")
X = df[["feature_name"]]
y = df["target"]

from sklearn.ensemble import GradientBoostingRegressor

gbdt = GradientBoostingRegressor()
gbdt.fit(X, y)  # no warning

gbdt = GradientBoostingRegressor(n_iter_no_change=5)
gbdt.fit(X, y)  # raises warning

НЕ сообщайте ваши данные, только если это крайне необходимо#

Идея в том, чтобы сделать код максимально самодостаточным. Для этого вы можете использовать Синтетический набор данныхЕго можно сгенерировать с помощью numpy, pandas или sklearn.datasets модуля. В большинстве случаев ошибка не связана с конкретной структурой ваших данных. Даже если это так, попробуйте найти доступный набор данных, который имеет схожие характеристики с вашим и воспроизводит проблему. В данном конкретном случае нас интересуют данные с помеченными именами признаков.

Улучшенный пример

import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor

df = pd.DataFrame(
    {
        "feature_name": [-12.32, 1.43, 30.01, 22.17],
        "target": [72, 55, 32, 43],
    }
)
X = df[["feature_name"]]
y = df["target"]

gbdt = GradientBoostingRegressor()
gbdt.fit(X, y) # no warning
gbdt = GradientBoostingRegressor(n_iter_no_change=5)
gbdt.fit(X, y) # raises warning

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

  • старайтесь ограничить все строки максимум 79 символами, чтобы избежать горизонтальных полос прокрутки в блоках фрагментов кода, отображаемых в issue на GitHub;

  • используйте пустые строки для разделения групп связанных функций;

  • разместите все импорты в отдельной группе в начале.

Шаги упрощения, представленные в этом руководстве, могут быть реализованы в порядке, отличном от показанного здесь. Важные моменты:

  • минимальный воспроизводимый пример должен быть запускаемым простым копированием и вставкой в терминале Python;

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

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

Используйте форматирование Markdown#

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

```python
from sklearn.datasets import make_blobs

n_samples = 100
n_components = 3
X, y = make_blobs(n_samples=n_samples, centers=n_components)
```

отобразит фрагмент кода в формате Python следующим образом

from sklearn.datasets import make_blobs

n_samples = 100
n_components = 3
X, y = make_blobs(n_samples=n_samples, centers=n_components)

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

В разделе под названием Фактические результаты из Шаблон issue вас просят предоставить сообщение об ошибке, включая полную трассировку стека исключения. В этом случае используйте python-traceback квалификатор. Например:

```python-traceback
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in 
    4 vectorizer = CountVectorizer(input=docs, analyzer='word')
    5 lda_features = vectorizer.fit_transform(docs)
----> 6 lda_model = LatentDirichletAllocation(
    7     n_topics=10,
    8     learning_method='online',

TypeError: __init__() got an unexpected keyword argument 'n_topics'
```

при отображении дает следующее:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-a674e682c281> in <module>
    4 vectorizer = CountVectorizer(input=docs, analyzer='word')
    5 lda_features = vectorizer.fit_transform(docs)
----> 6 lda_model = LatentDirichletAllocation(
    7     n_topics=10,
    8     learning_method='online',

TypeError: __init__() got an unexpected keyword argument 'n_topics'

Синтетический набор данных#

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

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

NumPy#

Инструменты NumPy, такие как numpy.random.randn и numpy.random.randint может использоваться для создания фиктивных числовых данных.

  • регрессия

    Регрессии используют непрерывные числовые данные в качестве признаков и целевой переменной.

    import numpy as np
    
    rng = np.random.RandomState(0)
    n_samples, n_features = 5, 5
    X = rng.randn(n_samples, n_features)
    y = rng.randn(n_samples)
    

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

  • классификация

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

    import numpy as np
    
    rng = np.random.RandomState(0)
    n_samples, n_features = 5, 5
    X = rng.randn(n_samples, n_features)
    y = rng.randint(0, 2, n_samples)  # binary target with values in {0, 1}
    

    Если ошибка возникает только с нечисловыми метками классов, вы можете сгенерировать случайную целевую переменную с помощью numpy.random.choice.

    import numpy as np
    
    rng = np.random.RandomState(0)
    n_samples, n_features = 50, 5
    X = rng.randn(n_samples, n_features)
    y = np.random.choice(
        ["male", "female", "other"], size=n_samples, p=[0.49, 0.49, 0.02]
    )
    

Pandas#

Некоторые объекты scikit-learn ожидают на входе pandas dataframes. В этом случае вы можете преобразовать numpy arrays в pandas объекты с помощью pandas.DataFrame, или pandas.Series.

import numpy as np
import pandas as pd

rng = np.random.RandomState(0)
n_samples, n_features = 5, 5
X = pd.DataFrame(
    {
        "continuous_feature": rng.randn(n_samples),
        "positive_feature": rng.uniform(low=0.0, high=100.0, size=n_samples),
        "categorical_feature": rng.choice(["a", "b", "c"], size=n_samples),
    }
)
y = pd.Series(rng.randn(n_samples))

Кроме того, scikit-learn включает различные Сгенерированные наборы данных которые можно использовать для создания искусственных наборов данных контролируемого размера и сложности.

make_regression#

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

from sklearn.datasets import make_regression

X, y = make_regression(n_samples=1000, n_features=20)

make_classification#

sklearn.datasets.make_classification создает многоклассовые наборы данных с несколькими гауссовыми кластерами на класс. Шум может быть введен с помощью коррелированных, избыточных или неинформативных признаков.

from sklearn.datasets import make_classification

X, y = make_classification(
    n_features=2, n_redundant=0, n_informative=2, n_clusters_per_class=1
)

make_blobs#

Аналогично make_classification, sklearn.datasets.make_blobs создаёт многоклассовые наборы данных, используя нормально распределённые кластеры точек. Он обеспечивает больший контроль над центрами и стандартными отклонениями каждого кластера, и поэтому полезен для демонстрации кластеризации.

from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=10, centers=3, n_features=2)

Утилиты загрузки наборов данных#

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

from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True)