本文为《Scikit-Learn 和 TensorFlow 机器学习指南》的第二章的第 3 讲:为机器学习算法准备数据。
1. 使用实际数据
2. 整体规划
3. 获取数据
4. 发现、可视化数据,增加直观印象
5. 为机器学习准备数据
6. 选择模型并进行训练
7. 调试模型
8. 部署、监控、维护系统
第二章前 2 讲的地址如下:
笔记尽量突出重点,提炼关键知识点。正文开始!
数据清洗(处理缺失值)
对于数据集中出现缺失值的情况,需要对其进行处理。对缺失值常用的三种方法是:
- 丢弃有缺失值的样本
- 丢弃有缺失值的整个特征
- 对缺失值进行填充(补零、均值填充或中位数填充等)
三种方法相应的代码如下:
housing.dropna(subset=["total_bedrooms"]) # option 1 housing.drop("total_bedrooms", axis=1) # option 2 median = housing["total_bedrooms"].median() housing["total_bedrooms"].fillna(median, inplace=True) # option 3
一般 option 3 应用更为广泛。值得注意的是,应该保留训练样本的 median 值,测试样本中的缺失值将以此 median 值进行填充。
在 Scikit-Learn 中提供了 Imputer 类,进行缺失值处理。示例代码如下:
from sklearn.preprocessing import Imputer imputer = Imputer(strategy="median") housing_num = housing.drop('ocean_proximity', axis=1) imputer.fit(housing_num) X = imputer.transform(housing_num) housing_tr = pd.DataFrame(X, columns=housing_num.columns)
处理文字或类别属性
本章的波士顿房价问题中,ocean_proximity 属性是非数值的字符属性,因此无法进行中位数填充。该属性如下所示:
['<1H OCEAN' 'INLAND' 'ISLAND' 'NEAR BAY' 'NEAR OCEAN']
你可以直接使用下面代码,将字符属性转换成数值属性:
from sklearn.preprocessing import OrdinalEncoder housing_cat = housing[['ocean_proximity']] ordinal_encoder = OrdinalEncoder() housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat) housing_cat_encoded[:10] housing_cat_encoded[:10]
array([[ 0.],
[ 0.],
[ 4.],
[ 1.],
[ 0.],
[ 1.],
[ 0.],
[ 1.],
[ 0.],
[ 0.]])
更方便地,还可以直接将字符属性转换为 one-hot 编码:
from sklearn.preprocessing import OneHotEncoder cat_encoder = OneHotEncoder(sparse=False) housing_cat_1hot = cat_encoder.fit_transform(housing_cat) housing_cat_1hot
array([[ 1., 0., 0., 0., 0.],
[ 1., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1.],
...,
[ 0., 1., 0., 0., 0.],
[ 1., 0., 0., 0., 0.],
[ 0., 0., 0., 1., 0.]])
自定义转换器
虽然 Scikit-Learn 已经提供了许多有用的转换器,但是你仍然可以编写自己的转换器,例如特定属性组合。自定义转换器很简单,只需要创建一个类,然后实现以下三个方法:fit()(返回自身)、transform()、fit_transform()。如果添加 TransformerMixin 作为基类,就可以直接得到最后一个方法。同时,如果添加 BaseEstimator 作为基类(并在构造函数中避免 *args 和 **kargs),你还能额外获得两个非常有用的自动调整超参数的方法 get_params()和 set_params()。
下面是自定义转换器,添加组合属性的例子:
from sklearn.base import BaseEstimator, TransformerMixin # column index rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6 class CombinedAttributesAdder(BaseEstimator, TransformerMixin): def __init__(self, add_bedrooms_per_room = True): # no *args or **kargs self.add_bedrooms_per_room = add_bedrooms_per_room def fit(self, X, y=None): return self # nothing else to do def transform(self, X, y=None): rooms_per_household = X[:, rooms_ix] / X[:, household_ix] population_per_household = X[:, population_ix] / X[:, household_ix] if self.add_bedrooms_per_room: bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix] return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room] else: return np.c_[X, rooms_per_household, population_per_household] attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False) housing_extra_attribs = attr_adder.transform(housing.values)
特征缩放
不同的特征属性范围不一,容易给训练造成困难,增加训练时间。因此,一般会对不同特征进行同尺度缩放。常用的两种方式是归一化和标准化。
归一化很简单:将值重新缩放于 0 到 1 之间。实现方法是将值减去最小值并除以最大值和最小值的差。对此,Scikit-Learn 提供了一个名为 MinMaxScaler 的转换器。如果希望范围不是 0~1,可以通过调整超参数 feature_range 进行更改。
标准化的做法是首先减去平均值(所以标准化值的均值总是零),然后除以方差。不同于归一化,标准化不将值绑定到特定范围,对某些算法而言,这可能是个问题(例如,神经网络期望的输入值范围通常是0到1)。但是标准化的方法受异常值的影响更小。Scikit-Learn 提供了一个标准化的转换器 StandadScaler。
管道 Pipeline
我们可以把机器学习算法中许多转换操作使用管道 pipeline 统一顺序进行。Scikit-Learn 正好提供了 Pipeline 来支持这样的转换。下面是一个数值属性的流水线例子:
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler num_pipeline = Pipeline([ ('imputer', Imputer(strategy="median")), ('attribs_adder', CombinedAttributesAdder()), ('std_scaler', StandardScaler()), ]) housing_num_tr = num_pipeline.fit_transform(housing_num)
以上是数值型的 Pipeline 处理过程。对于非数值型的字符属性,可以建立一个新的完整的 Pipeline,将上面的 num_pipeline 和字符属性的转换整合到一个 Pipeline 中,如下所示:
from sklearn.compose import ColumnTransformer num_attribs = list(housing_num) cat_attribs = ["ocean_proximity"] full_pipeline = ColumnTransformer([ ("num", num_pipeline, num_attribs), ("cat", OneHotEncoder(), cat_attribs), ]) housing_prepared = full_pipeline.fit_transform(housing)