Графики частичной зависимости с моделью scikit-learn и wrapped keras в конвейере - PullRequest
1 голос
/ 22 апреля 2020

Я пытаюсь создать графики частичной зависимости, используя sklearn.inspection.plot_partial_dependence для модели, которую я успешно построил, используя keras и утилиту-оболочку keras sklearn (см. Блок кода ниже). Обернутая модель успешно строится, она может использовать метод подгонки, а после подгонки она может использовать метод прогнозирования с ожидаемыми результатами. Все признаки того, что это действительная оценка. Однако, когда я пытаюсь запустить plot_partial_dependence от sklearn.inspection, я получаю некоторый текст ошибки, подразумевающий, что это не допустимая оценка, хотя я могу продемонстрировать, что это так.

I Я отредактировал это так, чтобы его было легче воспроизвести с помощью примера склеарна с данными о жилье в Бостоне.

from sklearn.datasets import load_boston
from sklearn.inspection import plot_partial_dependence, partial_dependence
from keras.wrappers.scikit_learn import KerasRegressor
import keras
import tensorflow as tf
import pandas as pd

boston = load_boston()
feature_names = boston.feature_names
X = pd.DataFrame(boston.data, columns=boston.feature_names)
y = boston.target
mean = X.describe().transpose()['mean']
std = X.describe().transpose()['std']
X_norm = (X-mean)/std

def build_model_small():
    model = keras.Sequential([
        keras.layers.Dense(64, activation='relu', input_shape=[len(X.keys())]),
        keras.layers.Dense(64, activation='relu'),
        keras.layers.Dense(1)
        ])

    optimizer = keras.optimizers.RMSprop(0.0005)

    model.compile(loss='mse',
              optimizer=optimizer,
              metrics=['mae', 'mse', 'mape'])
    return model


kr = KerasRegressor(build_fn=build_model_small,verbose=0)
kr.fit(X_norm,y, epochs=100, validation_split = 0.2)
pdp_plot = plot_partial_dependence(kr,X_norm,feature_names)

Как я уже сказал, если я запускаю kr.predict(X.head(20)), я получаю 20 предсказаний значений y для первых 20 строки X, как и следовало ожидать от действительного оценщика.

Но текст ошибки, который я получаю от plot_partial_dependence, выглядит следующим образом:

Traceback (most recent call last):
  File "temp_ML_tf_sklearn_postproc.py", line 79, in <module>
    pdp_plot = plot_partial_dependence(kr,X,labels[:-1])
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/sklearn/inspection/_partial_dependence.py", line 678, in plot_partial_dependence
    for fxs in features)
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 921, in __call__
    if self.dispatch_one_batch(iterator):
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 759, in dispatch_one_batch
    self._dispatch(tasks)
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 716, in _dispatch
    job = self._backend.apply_async(batch, callback=cb)
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/joblib/_parallel_backends.py", line 182, in apply_async
    result = ImmediateResult(func)
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/joblib/_parallel_backends.py", line 549, in __init__
    self.results = batch()
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 225, in __call__
    for func, args, kwargs in self.items]
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 225, in <listcomp>
    for func, args, kwargs in self.items]
  File "/home/mymachine/anaconda3/lib/python3.7/site-packages/sklearn/inspection/_partial_dependence.py", line 307, in partial_dependence
    "'estimator' must be a fitted regressor or classifier."
ValueError: 'estimator' must be a fitted regressor or classifier.

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

  A fitted estimator object implementing :term:`predict`,
    :term:`predict_proba`, or :term:`decision_function`.
    Multioutput-multiclass classifiers are not supported.

Мой оценщик делает на самом деле реализацию .predict.

Второй строка, вызванная в трассировке errr, вызывает средство проверки, которое проверяет, является ли оно регрессором или классификатором:

if not (is_classifier(estimator) or is_regressor(estimator)):
    raise ValueError(
        "'estimator' must be a fitted regressor or classifier."
    )

Я посмотрел исходный код is_regressor (), и он похож на один строчный :

return getattr(estimator, "_estimator_type", None) == "regressor"

Поэтому я попытался взломать его, выполнив setattr(mp,'_estimator_type','regressor'), и он просто сказал Attribute Error: can't set attribute, так что это один дешевый обходной путь, который не сработал.

Я даже попробовал еще более хакерское исправление и временно закомментировал оскорбительную проверку в источнике _partial_dependence.py (оператор if, который я скопировал выше), и получил следующую ошибку:

Traceback (most recent call last):
  File "temp_ML_tf_sklearn_postproc.py", line 79, in <module>
    pdp_plot = plot_partial_dependence(kr,X,labels[:-1])
  File "/home/billy/anaconda3/lib/python3.7/site-packages/sklearn/inspection/_partial_dependence.py", line 678, in plot_partial_dependence
    for fxs in features)
  File "/home/billy/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 921, in __call__
    if self.dispatch_one_batch(iterator):
  File "/home/billy/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 759, in dispatch_one_batch
    self._dispatch(tasks)
  File "/home/billy/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 716, in _dispatch
    job = self._backend.apply_async(batch, callback=cb)
  File "/home/billy/anaconda3/lib/python3.7/site-packages/joblib/_parallel_backends.py", line 182, in apply_async
    result = ImmediateResult(func)
  File "/home/billy/anaconda3/lib/python3.7/site-packages/joblib/_parallel_backends.py", line 549, in __init__
    self.results = batch()
  File "/home/billy/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 225, in __call__
    for func, args, kwargs in self.items]
  File "/home/billy/anaconda3/lib/python3.7/site-packages/joblib/parallel.py", line 225, in <listcomp>
    for func, args, kwargs in self.items]
  File "/home/billy/anaconda3/lib/python3.7/site-packages/sklearn/inspection/_partial_dependence.py", line 317, in partial_dependence
    check_is_fitted(est)
  File "/home/billy/anaconda3/lib/python3.7/site-packages/sklearn/utils/validation.py", line 967, in check_is_fitted
    raise NotFittedError(msg % {'name': type(estimator).__name__})
sklearn.exceptions.NotFittedError: This KerasRegressor instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.

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

Я также попытался передать kr.fit(X,y,etc...) непосредственно в качестве первого аргумента plot_partial_dependence. Компьютер вращался в течение нескольких минут, показывая, что на самом деле выполнялась подгонка, но затем я получил ту же ошибку, когда попытался запустить график частичной зависимости.

Еще одна довольно запутанная подсказка. Я попытался полностью использовать конвейер keras / sklearn в другой функции sklearn, чтобы посмотреть, будет ли он работать с какими-либо утилитами sklearn. На этот раз я сделал:

from sklearn.model_selection import cross_validate
cv_scores = cross_validate(kr,X_norm,y, cv=4, return_train_score=True, n_jobs=-1)`

, и это сработало! Так что я не думаю, что с моим использованием keras.wrappers.scikit_learn.KerasRegressor.

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

Я использую sklearn 0.22.1 и Python 3.7 .3 (Анаконда) кстати. И чтобы было ясно, я использовал plot_partial_dependence на моделях, построенных на sklearn, и даже на конвейерах. Эта проблема возникает только с моделью на основе керас. Большое спасибо за любые комментарии, которые могут иметь люди.

Редактировать:

Предыдущая версия этого вопроса включала построение конвейера с помощью StandardScaler () и затем обернутого объекта KerasRegressor. С тех пор я обнаружил, что это происходит даже с одним объектом KerasRegressor, то есть я выделил проблему, а не конвейер. Поэтому, как предложил один из комментаторов, я исключил часть конвейера, чтобы сделать его проще и ближе к делу.

1 Ответ

0 голосов
/ 28 апреля 2020

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

Я просто скопировал исходный код (в моей установке anaconda, он был в ~/anaconda3/lib/python3.7/site-packages/sklearn/inspection/_partial_dependence.py) в файл с именем custom_pdp.py в каталоге моего проекта, в котором я закомментировал поврежденные части как я (и, где необходимо, жестко запрограммировал свои собственные замещающие значения).

В моем коде я использовал строку импорта import custom_pdp as cpdp вместо того, чтобы импортировать его из sklearn, и после этого вызывать plot_partial_dependence как cpdp.plot_partial_dependence(...)

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

Во-первых, вы должны изменить относительные строки импорта вверху следующим образом:

from sklearn.utils.extmath import cartesian
from sklearn.utils import check_array
from sklearn.utils import check_matplotlib_support  # noqa
from sklearn.utils import _safe_indexing
from sklearn.utils import _determine_key_type
from sklearn.utils import _get_column_indices
from sklearn.utils.validation import check_is_fitted
from sklearn.tree._tree import DTYPE
from sklearn.exceptions import NotFittedError
from sklearn.ensemble._gb import BaseGradientBoosting
from sklearn.ensemble._hist_gradient_boosting.gradient_boosting import (
    BaseHistGradientBoosting)

(они ранее были относительными путями, такими как from ..utils.extmath import cartesian et c.)

Затем изменяются только следующие функции:

From _partial_dependence_brute:

def _partial_dependence_brute(est, grid, features, X, response_method):

    ... (skipping docstring)

    averaged_predictions = []

    # define the prediction_method (predict, predict_proba, decision_function).
    # if is_regressor(est):
    #     prediction_method = est.predict
    # else:
    #     predict_proba = getattr(est, 'predict_proba', None)
    #     decision_function = getattr(est, 'decision_function', None)
    #     if response_method == 'auto':
    #         # try predict_proba, then decision_function if it doesn't exist
    #         prediction_method = predict_proba or decision_function
    #     else:
    #         prediction_method = (predict_proba if response_method ==
    #                              'predict_proba' else decision_function)
    #     if prediction_method is None:
    #         if response_method == 'auto':
    #             raise ValueError(
    #                 'The estimator has no predict_proba and no '
    #                 'decision_function method.'
    #             )
    #         elif response_method == 'predict_proba':
    #             raise ValueError('The estimator has no predict_proba method.')
    #         else:
    #             raise ValueError(
    #                 'The estimator has no decision_function method.')
    prediction_method = est.predict

    #the rest in this function are as they were before, beginning with:
    for new_values in grid:
        X_eval = X.copy()

        ....

Затем закомментируйте первые 20 строк определения partial_dependence:

def partial_dependence(estimator, X, features, response_method='auto',
                   percentiles=(0.05, 0.95), grid_resolution=100,
                   method='auto'):
    ... (skipping docstring)
    # if not (is_classifier(estimator) or is_regressor(estimator)):
    #     raise ValueError(
    #         "'estimator' must be a fitted regressor or classifier."
    #     )
    # 
    # if isinstance(estimator, Pipeline):
    #     # TODO: to be removed if/when pipeline get a `steps_` attributes
    #     # assuming Pipeline is the only estimator that does not store a new
    #     # attribute
    #     for est in estimator:
    #         # FIXME: remove the None option when it will be deprecated
    #         if est not in (None, 'drop'):
    #             check_is_fitted(est)
    # else:
    #     check_is_fitted(estimator)
    # 
    # if (is_classifier(estimator) and
    #         isinstance(estimator.classes_[0], np.ndarray)):
    #     raise ValueError(
    #         'Multiclass-multioutput estimators are not supported'
    #     )

    #The rest of the function continues as it was:
    # Use check_array only on lists and other non-array-likes / sparse. Do not
    # convert DataFrame into a NumPy array.
    if not(hasattr(X, '__array__') or sparse.issparse(X)):
        X = check_array(X, force_all_finite='allow-nan', dtype=np.object)

        ....

Если ваша модель другого сорта или вы используете другие параметры, возможно, вам придется внести другие изменения .

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

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