Создание минимального воспроизводимого примера для 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)