Reputation: 5922
I am trying out scikit-learn for the first time, for a Multi-Output Multi-Class text classification problem. I am attempting to use GridSearchCV
to optimize the parameters of MLPClassifier
for this purpose.
Below is the code I have so far:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.multioutput import MultiOutputClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score
df = pd.read_csv('data.csv')
df.fillna('', inplace=True) #Replaces NaNs with "" in the DataFrame (which would be considered a viable choice in this multi-classification model)
x_features = df['input_text']
y_labels = df[['output_text_label_1', 'output_text_label_2']]
x_train, x_test, y_train, y_test = train_test_split(x_features, y_labels, test_size=0.3, random_state=7)
When I attempt to set up a Pipeline
for MultiOutputClassifier(MLPClassifier())
without any parameter optimization, pipe.score
gives a score of ~0.837, which seems to suggest that the above code is doing something. Running pipe.predict()
on some test strings seems to yield relatively adequate output results.
pipe = Pipeline(steps=[('cv', CountVectorizer()),
('mlpc', MultiOutputClassifier(MLPClassifier()))])
pipe.fit(x_train, y_train)
pipe.score(x_test, y_test)
However, when attempting to separately use GridSearchCV
using the below starting code from another Stack Overflow answer, I run into a problem:
mlpc = MLPClassifier(solver='adam',
learning_rate_init=0.01,
max_iter=300,
activation='relu',
early_stopping=True)
pipe = Pipeline(steps=[('cv', CountVectorizer(ngram_range=(1, 1))),
('scale', StandardScaler(with_mean=False)),
('mlpc', MultiOutputClassifier(mlpc))])
search_space = {
'cv__max_df': (0.9, 0.95, 0.99),
'cv__min_df': (0.01, 0.05, 0.1),
'mlpc__estimator__alpha': 10.0 ** -np.arange(1, 5),
'mlpc__estimator__hidden_layer_sizes': ((64, 32), (128, 64),
(64, 32, 16), (128, 64, 32)),
'mlpc__estimator__tol': (1e-3, 5e-3, 1e-4),
}
grid_search = GridSearchCV(pipe, search_space, scoring='accuracy', error_score='raise', n_jobs=-1)
grid_search.fit(x_train, y_train)
This warning seems to be related to a scoring problem, UserWarning: One or more of the test scores are non-finite
:
UserWarning: One or more of the test scores are non-finite: [nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan
nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan nan]
warnings.warn(
The .shape
of x_train
, x_test
, y_train
, y_test
are (3036,)
(1302,)
(3036, 2)
(1302, 2)
respectively. The contents are all strings, and as you can see in the code above, I have replaced many occurrences of NaN into empty strings ""
, which should be a viable option for the model (basically having it select None as a class):
df.fillna('', inplace=True) #Replaces NaNs with "" in the DataFrame (which would be considered a viable choice in this multi-classification model)
I have tried other scoring=
methods in GridSearchCV
: roc_auc_ovr
and f1_macro
, with essentially the same problem.
If I include in GridSearchCV
the following option: error_score='raise'
, I get the error: ValueError: multiclass-multioutput is not supported
.
ValueError Traceback (most recent call last)
<ipython-input-10-14bccd802d09> in <module>
21 #https://stackoverflow.com/questions/31265110/does-gridsearchcv-not-support-multi-class
22
---> 23 grid_search.fit(x_train, y_train)
~/condaenv/lib/python3.9/site-packages/sklearn/model_selection/_search.py in fit(self, X, y, groups, **fit_params)
889 return results
890
--> 891 self._run_search(evaluate_candidates)
892
893 # multimetric is determined here because in the case of a callable
~/condaenv/lib/python3.9/site-packages/sklearn/model_selection/_search.py in _run_search(self, evaluate_candidates)
1390 def _run_search(self, evaluate_candidates):
1391 """Search all candidates in param_grid"""
-> 1392 evaluate_candidates(ParameterGrid(self.param_grid))
1393
1394
~/condaenv/lib/python3.9/site-packages/sklearn/model_selection/_search.py in evaluate_candidates(candidate_params, cv, more_results)
836 )
837
--> 838 out = parallel(
839 delayed(_fit_and_score)(
840 clone(base_estimator),
~/condaenv/lib/python3.9/site-packages/joblib/parallel.py in __call__(self, iterable)
1059
1060 with self._backend.retrieval_context():
-> 1061 self.retrieve()
1062 # Make sure that we get a last message telling us we are done
1063 elapsed_time = time.time() - self._start_time
~/condaenv/lib/python3.9/site-packages/joblib/parallel.py in retrieve(self)
938 try:
939 if getattr(self._backend, 'supports_timeout', False):
--> 940 self._output.extend(job.get(timeout=self.timeout))
941 else:
942 self._output.extend(job.get())
~/condaenv/lib/python3.9/site-packages/joblib/_parallel_backends.py in wrap_future_result(future, timeout)
540 AsyncResults.get from multiprocessing."""
541 try:
--> 542 return future.result(timeout=timeout)
543 except CfTimeoutError as e:
544 raise TimeoutError from e
~/condaenv/lib/python3.9/concurrent/futures/_base.py in result(self, timeout)
444 raise CancelledError()
445 elif self._state == FINISHED:
--> 446 return self.__get_result()
447 else:
448 raise TimeoutError()
~/condaenv/lib/python3.9/concurrent/futures/_base.py in __get_result(self)
389 if self._exception:
390 try:
--> 391 raise self._exception
392 finally:
393 # Break a reference cycle with the exception in self._exception
ValueError: multiclass-multioutput is not supported
When looking around for solutions, I spotted this answer which suggests that CountVectorizer
expects 1D inputs. I have two features in my .shape
for Multi-Output. I have no idea if this answer is correct, since I am evaluating scikit-learn for the first time. The answer proposes to use ColumnTransformer
to solve this, but I need a code example to understand how if this is indeed the issue.
Upvotes: 1
Views: 1759
Reputation: 12582
Your y
has multiple columns, not your X
, so the CountVectorizer
isn't the issue. Rather, the problem is that you have two targets each with more than two possible outcomes. From the User Guide, MLPClassifier
supports multilabel, but not multi-output multiclass. Later in the same page, there is an additional warning about scoring:
Warning: At present, no metric in
sklearn.metrics
supports the multiclass-multioutput classification task.
so even if you wrapped your classifier in e.g. MultiOutputClassifier
the scoring for the grid search would fail. I think you should be able to define a custom scorer to make this work? You might also consider whether your labels are really properly ordered as label_1 and label_2, or whether this can be cast as a multilabel problem, binarizing the targets?
Upvotes: 1