2

I'm trying to use a KerasClassifier wrapper in order to make my workflow scikit-friendly. However, when I try to use it with the following function, it gives an error; training the model using native Keras model fit() works. (this is Tensorflow 2.2.0, running in a conda environment)

def model_arch(n_features: int):
    i = tf.keras.layers.Input(shape=(n_features,))

    hidden_dense = tf.keras.layers.Dense(64)(i)
    hidden_dense = tf.keras.layers.BatchNormalization()(hidden_dense)
    hidden_dense = tf.keras.layers.Activation(tf.nn.tanh)(hidden_dense)

    o = tf.keras.layers.Dense(1)(hidden_dense)
    o = tf.keras.layers.BatchNormalization()(o)
    o = tf.keras.layers.Activation("sigmoid")(o)

    classifier = tf.keras.models.Model(inputs=i, outputs=o)

    opt = tf.keras.optimizers.SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)
    classifier.compile(
        loss="binary_crossentropy",
        optimizer=opt,
        metrics=["accuracy"],
    )
    
    return classifier

The following works:

X = np.random.random((100,3))
y = np.random.random((100,)) # 'y' is a binary vector in reality

clf = model_arch(3)
clf.fit(X, y, epochs=10)

However, when I try to use KerasClassifier wrapper, I get an error:

clf = KerasClassifier(model_arch(3), epochs=10)
clf.fit(X, y)

# ValueError: The first argument to `Layer.call` must always be passed.

Every example I have seen on the internet seems to do the same as I: define a function that returns a compiled keras model, then pass it to the wrapper, and fit it or use in a pipeline. The only difference I notice is that most (if not all) examples use the Sequential API instead of the functional API, but afaik that should not be a problem, right?

Tensorflow documentation doesn't seem to give any example of what kind of function we should pass to the wrapper, but since every example uses one similar to mine, I think that's correct.

Can anyone shed some light? Thanks.

EDIT (after comments):

I import the KerasClassifier like this:

from tensorflow.keras.wrappers.scikit_learn import KerasClassifier

Error log:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/adrian/miniconda3/envs/kaggle/lib/python3.8/site-packages/tensorflow/python/keras/wrappers/scikit_learn.py", line 223, in fit
    return super(KerasClassifier, self).fit(x, y, **kwargs)
  File "/home/adrian/miniconda3/envs/kaggle/lib/python3.8/site-packages/tensorflow/python/keras/wrappers/scikit_learn.py", line 154, in fit
    self.model = self.build_fn(
  File "/home/adrian/miniconda3/envs/kaggle/lib/python3.8/site-packages/tensorflow/python/keras/engine/base_layer.py", line 799, in __call__
    raise ValueError(
ValueError: The first argument to `Layer.call` must always be passed.
desertnaut
  • 57,590
  • 26
  • 140
  • 166
Adrian
  • 755
  • 9
  • 17

1 Answers1

5

KerasClassifer expects a build function, not the model instance itself, which upon its call returns a compiled instance of a Keras model. Therefore, to resolve this with minimal changes, you must wrap it inside a function:

clf = KerasClassifier(lambda: model_arch(3), epochs=10)

Alternatively, a much better way is to pass the model's argument as a keyword argument:

clf = KerasClassifier(model_arch, n_features=3, epochs=10)
today
  • 32,602
  • 8
  • 95
  • 115
  • I actually slapped my forehead after reading this. Ofc I was passing an evaluated function so to speak, instead of the function itself. – Adrian Sep 28 '20 at 06:59
  • @today, can you please have a look here: https://stackoverflow.com/questions/65305864/understanding-weightedkappaloss-using-keras ? – Shlomi Schwartz Dec 15 '20 at 13:33