我的预测任务如下:
使用名称来预测人们的种族(分为 4 类:“英语”、“法语”、“中国人”和“所有其他人”)作为一个多类分类问题。在特征工程期间,名称变量被进一步分解为 3 个字母和 4 个字母的子串。例如,名称“Robert”被分解为“rob”、“obe”、“ber”、“ert”、“robe”、“ober”和“bert”。
我有大约 500 万行,每行代表一个人。我将整个数据分成 80:20 的训练:测试集。在训练集中,我挖出 10% 用作开发集,我运行 5 倍交叉验证以获得每个 ML 算法的最佳(超)参数集。我使用的 ML 算法包括正则化逻辑回归 (LR)、线性 SVC、非线性 SVC、决策树 (DT) 和随机森林 (RF)。
我使用了最优参数集(来自超参数集,从 5 折交叉验证中给出了最佳 F 分数)并应用到使用整个训练集来训练模型,然后我使用训练后的模型来预测和评估它在测试集中的表现。
我看到的奇怪现象是 LR 和 linear-SVC 的准确率和 F1 分数高达 80%,而 DT 和 RF 极差,约为 50%,这与没有预测值的基准虚拟预测器大致相同.
我知道一些 ML 算法在不同的问题空间中应该比其他算法表现更好(没有免费午餐定理),并且 LR 和线性 SVC 优于非线性 SVC 的事实,而非线性 SVC 反过来又非常优于 DT 和 RF,这表明问题是线性可分,但边界不整齐地平行于特征轴。
然而,DT/RF 根本无法学习任何东西(与随机预测器的性能相似)这一事实让我感到震惊,尤其是在有这么多数据以及 LR/linear-SVC 做得很好的情况下。我在训练集上应用并评估了经过训练的模型,即使在我发现奇怪的训练集中进行评估时,DT/RF 要么学得非常少,要么根本学不到(因为我认为测试集的表现不佳是由于训练中的过度拟合放)。
既然 DT/RF 是灵活的模型,它们至少应该能够学习一些东西吗?这是否可能真的发生在现实生活中(也就是我看到的是真实的自然现象),还是我可能错误地使用了 DT/RF?我在下面包含了代码,这些代码指示了我允许在 5 折交叉验证期间随机搜索超参数空间的空间。
# Partial code to specify hyperparameter space to be searched
'LR_V1': { 'clf': LogisticRegression(),
'param': {
'logisticregression__solver': ['liblinear'],
'logisticregression__penalty': ['l1', 'l2'],
'logisticregression__C': np.logspace(-4, 4, 20),
'logisticregression__tol': np.logspace(-5, 5, 20),
'logisticregression__class_weight': [None, 'balanced'],
'logisticregression__multi_class': ['ovr', 'auto'],
'logisticregression__max_iter': [50, 1000, 4000, 20000],
}},
'LR_V2': { 'clf': LogisticRegression(),
'param': {
'logisticregression__solver': ['newton-cg', 'lbfgs', 'sag', 'saga'],
'logisticregression__penalty': ['none', 'l2'],
'logisticregression__C': np.logspace(-4, 4, 20),
'logisticregression__tol': np.logspace(-5, 5, 20),
'logisticregression__class_weight': [None, 'balanced'],
'logisticregression__multi_class': ['ovr', 'multinomial', 'auto'],
'logisticregression__max_iter': [50, 1000, 4000, 20000],
}},
'SVC_LINEAR': { 'clf': OneVsRestClassifier(LinearSVC()),
'param': {
'onevsrestclassifier__estimator__penalty': ['l2'],
'onevsrestclassifier__estimator__loss': ['hinge', 'squared_hinge'],
'onevsrestclassifier__estimator__C': np.logspace(-4, 4, 20),
'onevsrestclassifier__estimator__tol': [0.00001, 0.0001, 0.001, 0.01, 0.1, 1],
'onevsrestclassifier__estimator__class_weight': [None, 'balanced'],
'onevsrestclassifier__estimator__multi_class': ['ovr', 'crammer_singer'],
'onevsrestclassifier__estimator__max_iter': [50, 1000, 4000, 20000],
}},
'SVC_NONLINEAR': { 'clf': OneVsRestClassifier(SVC()),
'param': {
'onevsrestclassifier__estimator__kernel': ['poly', 'rbf', 'sigmoid'],
'onevsrestclassifier__estimator__C': np.logspace(-4, 4, 20),
'onevsrestclassifier__estimator__tol': [0.00001, 0.0001, 0.001, 0.01, 0.1, 1],
'onevsrestclassifier__estimator__class_weight': [None, 'balanced'],
'onevsrestclassifier__estimator__decision_function_shape': ['ovo', 'ovr'],
'onevsrestclassifier__estimator__max_iter': [50, 1000, 4000, 20000],
}},
'RF': { 'clf': RandomForestClassifier(),
'param': {
'randomforestclassifier__n_estimators': [1, 8, 16, 32, 64, 100, 200, 500, 1000],
'randomforestclassifier__criterion': ['gini', 'entropy'],
'randomforestclassifier__class_weight': [None, 'balanced', 'balanced_subsample'],
'randomforestclassifier__max_depth': [None, 5, 10, 20, 40, 80],
'randomforestclassifier__min_samples_split': np.linspace(0.01, 1.0, 100, endpoint=True),
'randomforestclassifier__min_samples_leaf': np.linspace(0.01, 0.5, 100, endpoint=True),
'randomforestclassifier__max_leaf_nodes': [None, 10, 50, 100, 200, 400],
'randomforestclassifier__max_features': [None, 'auto', 'sqrt', 'log2'],
}},
'DT': { 'clf': DecisionTreeClassifier(),
'param': {
'decisiontreeclassifier__splitter': ['random', 'best'],
'decisiontreeclassifier__criterion': ['gini', 'entropy'],
'decisiontreeclassifier__class_weight': [None, 'balanced'],
'decisiontreeclassifier__max_depth': [None, 5, 10, 20, 40, 80],
'decisiontreeclassifier__min_samples_split': np.linspace(0.01, 1.0, 100, endpoint=True),
'decisiontreeclassifier__min_samples_leaf': np.linspace(0.01, 0.5, 100, endpoint=True),
'decisiontreeclassifier__max_leaf_nodes': [None, 10, 50, 100, 150, 200],
'decisiontreeclassifier__max_features': [None, 'auto', 'sqrt', 'log2'],
}},
# Partial code to show the random search in 5-fold cross-validation
from sklearn.model_selection import RandomizedSearchCV
pipe = make_pipeline(preprocessor, control_panel['ml_algo_param_grid'][1]['clf'])
grid = RandomizedSearchCV(pipe, param_distributions=control_panel['ml_algo_param_grid'][1]['param'],
n_jobs=-1, cv=5, scoring=f1_score)
grid.fit(X_dev, y_dev)