SGDClassifier超参数调优后准确率下降、训练时长增加,求问题原因
我的数据集包含训练集、验证集和测试集三类。我先在训练集上运行以下代码训练结合BOW的SGDClassifier模型,随后在验证集上评估:
#model for SVM with BOW sgd_1 = Pipeline([('vect', CountVectorizer()), ('clf', SGDClassifier()), ]) #time to train the model stsgd_1 = time.time() sgd_1.fit(x_train, y_train) print("time to train the model", (time.time() - stsgd_1)) ypred_sgd1 = sgd_1.predict(x_val) print('accuracy %s' % accuracy_score(ypred_sgd1, y_val)) print(classification_report(y_val, ypred_sgd1,target_names=my_tags)) cf_matrix_sgd = confusion_matrix(y_val, ypred_sgd1) print_confusion_matrix(cf_matrix_sgd, my_tags)
该模型在验证集上达到了90.15%的准确率。之后我使用以下代码进行超参数调优:
#tuning both BOW and sgd resulted in lowering the accuracy param_sgd1 = { "vect__max_df": (0.5, 0.75, 1.0), 'vect__max_features': (None, 5000, 10000, 50000), "vect__ngram_range": ((1, 1), (1, 2)), # unigrams or bigrams "clf__alpha": (0.00001, 0.000001), "clf__penalty": ("l2", "elasticnet"), 'clf__max_iter': (10, 50, 80), } gs_sgd1 = GridSearchCV(sgd_1, param_sgd1, n_jobs=-1, verbose=1) print("performing grid search on SVM") print("pipeline:", [name for name, _ in sgd_1.steps]) print("parameters:") pprint(param_sgd1) #t0 = time() gs_sgd1.fit(x_val, y_val) #print("done in %0.3fs" % (time() - t0)) print() print("Best score: %0.3f" % gs_sgd1.best_score_) print("Best parameters set:") best_parameters = gs_sgd1.best_estimator_.get_params() for param_name in sorted(param_sgd1.keys()): print("\t%s: %r" % (param_name, best_parameters[param_name]))
调优得到了最优参数后,我构建了以下带有调优后超参数的模型:
#Refit the model for SVM with tuned hyperparameters sgd_final1 = Pipeline([('vect', CountVectorizer(max_df=1.0, max_features=5000, ngram_range=(1,1))), ('clf', SGDClassifier(alpha=1e-05, max_iter=80, penalty="elasticnet")), ])
我将该模型在同一验证集上拟合以对比之前的结果,但这次准确率下降,且训练时长有所增加。请问我哪里操作出错了?
核心错误分析
超参数调优的数据集误用
你调用gs_sgd1.fit(x_val, y_val)时,直接把验证集当作网格搜索的训练数据,这会导致严重的数据泄露:网格搜索会反复用验证集的数据训练和评估模型,最终选出的参数是专门适配验证集的,完全不具备泛化能力。当你用这些参数重新训练模型时,自然无法达到初始模型的泛化表现。
正确做法是用训练集做网格搜索,通过交叉验证(GridSearchCV默认的cv=5)在训练集内部拆分数据来寻找最优参数,绝对不能动用外部验证集。最终模型的训练逻辑错误
你应该把sgd_final1在训练集上拟合(sgd_final1.fit(x_train, y_train)),再用验证集评估,而不是直接在验证集上训练。直接在验证集上训练模型,相当于让模型“记住”了验证集的所有样本,完全失去了验证集用来评估泛化能力的意义,也和初始模型的训练逻辑完全不一致,根本无法做有效对比。
训练时长增加的原因
你最终模型使用了penalty="elasticnet"(弹性网惩罚),它的计算开销比SGDClassifier默认的l2惩罚更大;另外虽然max_iter=80比默认值小,但弹性网需要同时处理L1和L2正则项,整体训练耗时会有所上升。
修正后的操作流程
- 超参数调优阶段:将网格搜索的训练数据改为训练集
gs_sgd1.fit(x_train, y_train) - 最终模型训练:用最优参数构建模型后,在训练集上拟合,再用验证集评估
sgd_final1.fit(x_train, y_train) ypred_sgd_final = sgd_final1.predict(x_val) print('accuracy %s' % accuracy_score(ypred_sgd_final, y_val))
内容的提问来源于stack exchange,提问作者user13178113

