特征选择与特征工程初探
特征工程是机器学习的第一步,涉及清理现有数据集、提高信噪比和降低维数的所有技术。大多数演算法对输入数据有很强的假设,当使用原始数据集时,它们的性能可能会受到负面影响。
另外有些特征之间高度相关,在其中一个特征提供了足够的信息之后,与之相关的其他特征往往无法提供额外的信息。这时我们就需要了解如何减少特征数量或者仅选择最佳特征。
一、scikit-learn数据集
scikit-learn提供了一些用于测试的内置数据集,这些数据集包含在sklearn.datasets中,每个数据集都包含了输入集(特征集)X和标签(目标值)y。比如波士顿房价的数据集(用于回归问题):
from sklearn.datasets import load_boston
boston = load_boston()
X = boston.data
y = boston.target
print(特征集的shape:, X.shape)
print(目标集的shape:, y.shape)
特征集的shape: (506, 13)
目标集的shape: (506,)
可以看到,这个数据集包含了506个样本、13个特征,以及1个目标值。
假如我们不想使用scikit-learn提供的数据集,那么我们还可以使用scikit-learn提供的工具来手动创建特定的数据集。相关的方法有:
make_classification()
:用于创建适用于测试分类演算法的数据集;make_regression()
:用于创建适用于测试回归模型的数据集;make_blobs()
:用于创建适用于测试聚类演算法的数据集。
二、创建训练集和测试集
一般来说,我们要在正式应用我们训练的模型前对它进行测试。因此我们需要将数据集分为训练集和测试集,顾名思义,前者用于训练模型参数,后者用于测试模型性能。在某些情况下,我们甚至还会再分出一个数据集作为交叉验证集,这种处理方式适用于有多种模型可供选择的情况。
数据集的分割有一些注意事项:首先,两个数据集必须要能反映原始数据的分布,否则在数据集失真的情况下得到的模型对于真实样本的预测效力会比较差;其次,原始数据集必须在分割之前随机混合,以避免连续元素之间的相关性。
在scikit-learn中,我们可以使用train_test_split()
函数来快速实现数据集的分割。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=1000)
这里前两个位置参数分别是特征集和目标集,test_size
用于指定测试集大小占整个数据集的比例,random_state
则是指定一个随机种子,这样可以确保我们在重复试验时数据不会发生变化(数据集都变了,那模型效果的变化就不知道该归因于模型的优化还是归因于数据集的变化了。)
三、管理分类数据
在许多分类问题中,目标数据集由各种类别标签组成。但是很多演算法是不支持这种数据格式的,因此我们要对其进行必要的编码。
假设我们有一个由10个样本组成的数据集,每个样本有两个特征。
import numpy as np
X = np.random.uniform(0.0, 1.0, size=(10, 2))
y = np.random.choice((Male, Female), size=(10))
print(X:, X)
print(y:, y)
X: [[0.48463048 0.21682675]
[0.27987595 0.28061459]
[0.13723177 0.45159025]
[0.42727284 0.99834867]
[0.61113219 0.31892401]
[0.14985227 0.71565914]
[0.048201 0.49254257]
[0.54466226 0.8419817 ]
[0.94426201 0.78924785]
[0.36877342 0.53250431]]
y: [Female Female Male Female Female Female Male Male
Female Male]
1. 使用LabelEncoder
类
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
yt = le.fit_transform(y)
print(y)
print(yt)
print(le.classes_)
[Female Female Male Female Female Female Male Male
Female Male]
[0 0 1 0 0 0 1 1 0 1]
[Female Male]
获得逆变换的方法很简单:
output = [1, 0, 1, 1, 0, 0]
decoded_output = [le.classes_[i] for i in output]
print(decoded_output)
[Male, Female, Male, Male, Female, Female]
这种方法很简单,但是有个缺点:所有的标签都变成了数字,然后使用真实值的分类器会根据其距离考虑相似的数字,而忽略其代表的分类含义。因此我们通常优先选择独热编码(one-hot encoding,又称一次有效编码),将数据二进位化。
2. 使用LabelBinarizer
类
from sklearn.preprocessing import LabelBinarizer
lb = LabelBinarizer()
yb = lb.fit_transform(y)
print(y)
print(yb)
print(lb.inverse_transform(yb))
[Female Female Male Female Female Female Male Male
Female Male]
[[0]
[0]
[1]
[0]
[0]
[0]
[1]
[1]
[0]
[1]]
[Female Female Male Female Female Female Male Male
Female Male]
可以看到,这里我们可以使用LabelBinarizer
类的inverse_transform
方法进行逆转化。
当存在多个标签时,这种方法会将其中一个标签变换为1,其余标签全部为0。这可能会导致的问题显而易见,也就是我们将多分类问题转换成了二分类问题。
四、管理缺失特征
我们可能会经常碰见数据缺失的情况,有以下选项可以解决该问题:
- 删除整行:这个选项比较激进,一般只有当数据集足够大、缺少的特征值数量很多而且预测风险大时才会选择;
- 创建子模型来预测这些特征值:第二个选项实现起来比较困难,因为需要确定一个监督策略来训练每个特征的模型,最后预测它们的值;
- 使用自动策略根据其他已知值插入这些缺失的特征值:考虑到以上的利弊,这可能是最好的选项了。
from sklearn.preprocessing import Imputer
data = np.array([[1, np.nan, 2],
[2, 3, np.nan],
[-1, 4, 2]])
# 插入均值
imp = Imputer(strategy=mean)
print(Mean:
, imp.fit_transform(data))
# 插入中位数
imp = Imputer(strategy=median)
print(Median:
, imp.fit_transform(data))
# 插入众数
imp = Imputer(strategy=most_frequent)
print(Mode:
, imp.fit_transform(data))
Mean:
[[ 1. 3.5 2. ]
[ 2. 3. 2. ]
[-1. 4. 2. ]]
Median:
[[ 1. 3.5 2. ]
[ 2. 3. 2. ]
[-1. 4. 2. ]]
Mode:
[[ 1. 3. 2.]
[ 2. 3. 2.]
[-1. 4. 2.]]
五、数据缩放和归一化
一般的数据集是由不同的值组成的,可以从不同的分布得到且具有不同的尺度,有时还会有异常值。当不同特征的取值范围差异过大时,很可能会对模型产生不良影响。因此我们往往需要先规范数据集。
我们来对比一下原始数据集和经过缩放和中心化的数据集:
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
import seaborn as sns
import matplotlib.pyplot as plt
sns.set()
# 导入数据
iris = load_iris()
data = iris.data
# 绘制原始数据散点图
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
sns.scatterplot(x=data[:, 0], y=data[:, 1], ax=axes[0])
# 数据归一化
scaler = StandardScaler()
scaled_data = scaler.fit_transform(data)
# 绘制规范化数据散点图
sns.scatterplot(x=scaled_data[:, 0], y=scaled_data[:, 1], ax=axes[1])
plt.setp(axes, xlim=[-2, 8], ylim=[-3, 5]);