在机器学习领域,特征离散化是一种将连续特征值分割成有限数量的区间(或称为“桶”)的技术。这种技术通过将特征值转换为一组离散的标签,使得原本线性不可分的数据集在经过转换后,有可能通过线性分类器进行有效分类。本文将探讨特征离散化如何影响线性分类器在不同数据集上的性能表现。
特征离散化的过程包括将每个特征划分为等宽的区间,并将这些区间的值进行独热编码(one-hot encoding)。经过这样的预处理,即使是线性分类器也能表现出非线性的分类行为。例如,在本文中展示的三个数据集中,前两个数据集(环形和同心圆)在没有进行特征离散化时,线性分类器难以实现良好的分类效果。然而,当应用特征离散化后,线性分类器的性能得到了显著提升。相比之下,第三个数据集在没有进行特征离散化时,已经接近线性可分,因此特征离散化对其性能的提升作用有限。
值得注意的是,虽然特征离散化能够提升线性分类器在某些数据集上的性能,但这种技术并不总是适用。特别是在高维空间中,数据更容易通过线性方式进行分离。此外,特征离散化和独热编码会增加特征的数量,当样本数量较少时,容易导致过拟合。在本文的实验中,通过绘制训练点和测试点的分类准确率,直观地展示了不同分类器在不同数据集上的表现。
在实验中,使用了三种不同的数据集:环形数据集、同心圆数据集和一个近似线性可分的数据集。对于每个数据集,都应用了多种分类器,包括逻辑回归、线性支持向量机(LinearSVC)、结合了KBinsDiscretizer的逻辑回归和LinearSVC,以及梯度提升分类器和支持向量机(SVC)。实验结果表明,在环形和同心圆这两个线性不可分的数据集上,特征离散化显著提高了线性分类器的性能;而在近似线性可分的数据集上,特征离散化对线性分类器性能的提升作用有限。
此外,还比较了两种非线性分类器——梯度提升分类器和SVC——的性能。在环形和同心圆数据集上,这两种非线性分类器的性能优于线性分类器。然而,在近似线性可分的数据集上,非线性分类器的性能与线性分类器相当。这些结果表明,选择合适的分类器对于提高模型性能至关重要。
在进行特征离散化时,需要注意选择合适的桶数。桶数的选择会影响模型的性能,过多的桶数可能会导致过拟合,而过少的桶数可能无法捕捉数据的复杂性。在本文的实验中,通过网格搜索(GridSearchCV)来寻找最佳的桶数和分类器参数。这种方法虽然计算量较大,但能够找到最优的模型参数,从而提高分类器的性能。
在实际应用中,特征离散化和独热编码是一种有效的数据预处理技术,尤其适用于那些线性分类器难以处理的非线性数据集。然而,也需要意识到这种技术可能带来的问题,如过拟合和计算复杂度的增加。因此,在应用特征离散化时,需要根据具体的数据集和问题背景,权衡其利弊,选择最合适的方法。
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap
from sklearn.datasets import make_circles, make_classification, make_moons
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.exceptions import ConvergenceWarning
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import KBinsDiscretizer, StandardScaler
from sklearn.svm import SVC, LinearSVC
from sklearn.utils._testing import ignore_warnings
h = 0.02 # step size in the mesh
def get_name(estimator):
name = estimator.__class__.__name__
if name == "Pipeline":
name = [get_name(est[1]) for est in estimator.steps]
name = " + ".join(name)
return name
# list of (estimator, param_grid), where param_grid is used in GridSearchCV
classifiers = [
(make_pipeline(StandardScaler(), LogisticRegression(random_state=0)), {
"logisticregression__C": np.logspace(-1, 1, 3)
}),
(make_pipeline(StandardScaler(), LinearSVC(random_state=0)), {
"linearsvc__C": np.logspace(-1, 1, 3)
}),
(make_pipeline(StandardScaler(), KBinsDiscretizer(encode="onehot", random_state=0), LogisticRegression(random_state=0)), {
"kbinsdiscretizer__n_bins": np.arange(5, 8),
"logisticregression__C": np.logspace(-1, 1, 3)
}),
(make_pipeline(StandardScaler(), KBinsDiscretizer(encode="onehot", random_state=0), LinearSVC(random_state=0)), {
"kbinsdiscretizer__n_bins": np.arange(5, 8),
"linearsvc__C": np.logspace(-1, 1, 3)
}),
(make_pipeline(StandardScaler(), GradientBoostingClassifier(n_estimators=5, random_state=0)), {
"gradientboostingclassifier__learning_rate": np.logspace(-2, 0, 5)
}),
(make_pipeline(StandardScaler(), SVC(random_state=0)), {
"svc__C": np.logspace(-1, 1, 3)
}),
]
names = [get_name(e).replace("StandardScaler + ", "") for e, _ in classifiers]
n_samples = 100
datasets = [
make_moons(n_samples=n_samples, noise=0.2, random_state=0),
make_circles(n_samples=n_samples, noise=0.2, factor=0.5, random_state=1),
make_classification(n_samples=n_samples, n_features=2, n_redundant=0, n_informative=2, random_state=2, n_clusters_per_class=1),
]
fig, axes = plt.subplots(nrows=len(datasets), ncols=len(classifiers) + 1, figsize=(21, 9))
cm_piyg = plt.cm.PiYG
cm_bright = ListedColormap(["#b30065", "#178000"])
# iterate over datasets
for ds_cnt, (X, y) in enumerate(datasets):
print(f"\ndataset {ds_cnt}\n---------")
# split into training and test part
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=42)
# create the grid for background colors
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
# plot the dataset first
ax = axes[ds_cnt, 0]
if ds_cnt == 0:
ax.set_title("Input data")
# plot the training points
ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=cm_bright, edgecolors="k")
# and testing points
ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=cm_bright, alpha=0.6, edgecolors="k")
ax.set_xlim(xx.min(), xx.max())
ax.set_ylim(yy.min(), yy.max())
ax.set_xticks(())
ax.set_yticks(())
# iterate over classifiers
for est_idx, (name, (estimator, param_grid)) in enumerate(zip(names, classifiers)):
ax = axes[ds_cnt, est_idx + 1]
clf = GridSearchCV(estimator=estimator, param_grid=param_grid)
with ignore_warnings(category=ConvergenceWarning):
clf.fit(X_train, y_train)
score = clf.score(X_test, y_test)
print(f"{name}: {score:.2f}")
# plot the decision boundary. For that, we will assign a color to each point in the mesh [x_min, x_max]*[y_min, y_max].
if hasattr(clf, "decision_function"):
Z = clf.decision_function(np.column_stack([xx.ravel(), yy.ravel()]))
else:
Z = clf.predict_proba(np.column_stack([xx.ravel(), yy.ravel()]))[:, 1]
# put the result into a color plot
Z = Z.reshape(xx.shape)
ax.contourf(xx, yy, Z, cmap=cm_piyg, alpha=0.8)
# plot the training points
ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=cm_bright, edgecolors="k")
# and testing points
ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap=cm_bright, edgecolors="k", alpha=0.6)
ax.set_xlim(xx.min(), xx.max())
ax.set_ylim(yy.min(), yy.max())
ax.set_xticks(())
ax.set_yticks(())
if ds_cnt == 0:
ax.set_title(name.replace(" + ", "\n"))
ax.text(0.95, 0.06, (f"{score:.2f}").lstrip("0"), size=15, bbox=dict(boxstyle="round", alpha=0.8, facecolor="white"), transform=ax.transAxes, horizontalalignment="right")
plt.tight_layout()
# Add suptitles above the figure
plt.subplots_adjust(top=0.90)
suptitles = ["Linear classifiers", "Feature discretization and linear classifiers", "Non-linear classifiers"]
for i, suptitle in zip([1, 3, 5], suptitles):
ax = axes[0, i]
ax.text(1.05, 1.25, suptitle, transform=ax.transAxes, horizontalalignment="center", size="x-large")
plt.show()