我第一次尝试scikit-learn,用于多输出多类文本分类问题。为此,我尝试使用GridSearchCV
来优化MLPClassifier
的参数。
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)
当我尝试在没有任何参数优化的情况下为MultiOutputClassifier(MLPClassifier())
设置Pipeline
时,pipe.score
给出了~0.837的分数,这似乎表明上面的代码正在做一些事情。在一些测试字符串上运行pipe.predict()
似乎产生了相对足够的输出结果。
pipe = Pipeline(steps=[('cv', CountVectorizer()),
('mlpc', MultiOutputClassifier(MLPClassifier()))])
pipe.fit(x_train, y_train)
pipe.score(x_test, y_test)
然而,当试图单独使用GridSearchCV
使用下面的开始代码从另一个堆栈溢出答案,我遇到了一个问题:
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)
这个警告似乎与评分问题有关,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(
x_train
、x_test
、y_train
、y_test
的.shape
分别为(3036,)
、(1302,)
、(3036, 2)
、(1302, 2)
。内容都是字符串,正如您在上面的代码中看到的那样,我已经将NaN的许多出现替换为空字符串""
,这应该是模型的一个可行选项(基本上让它选择None作为类):
df.fillna('', inplace=True) #Replaces NaNs with "" in the DataFrame (which would be considered a viable choice in this multi-classification model)
我在GridSearchCV
中尝试了其他scoring=
方法:roc_auc_ovr
和f1_macro
,本质上是相同的问题。
如果我在GridSearchCV
中包含以下选项:error_score='raise'
,我得到错误: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
在寻找解决方案时,我发现了这个答案,这表明CountVectorizer
期望1D输入。我的.shape
中有两个功能用于多输出。我不知道这个答案是否正确,因为我是第一次评估scikit-learn。答案建议使用ColumnTransformer
来解决这个问题,但我需要一个代码示例来理解如果这确实是问题。
您的y
有多个列,而不是您的X
,因此CountVectorizer
不是问题。相反,问题在于你有两个目标,每个目标都有两个以上的可能结果。根据用户指南,MLPClassifier
支持多标签,但不支持多输出multiclass。在同一页面的后面,有一个关于评分的额外警告:
警告:目前,
sklearn.metrics
中没有指标支持多类多输出分类任务。
所以即使你将分类器包装在例如MultiOutputClassifier
中,网格搜索的评分也会失败。我认为你应该能够定义一个自定义的得分者来完成这个工作?您还可以考虑标签是否按照label_1和label_2正确排序,或者是否可以将其转换为多标签问题,对目标进行二值化?