Nous devons maintenant commencer à préparer les données pour l'algorithme d'apprentissage automatique
1. Préparation des données pour l'apprentissage automatique
Nous devons d'abord écrire des fonctions pour préparer les données, ne le faites pas manuellement. Les raisons sont :
- Ces transformations peuvent être reproduites sur n'importe quel jeu de données.
- Une bibliothèque de fonctions de conversion peut être construite progressivement
- Ces fonctions peuvent être utilisées dans des systèmes en temps réel pour transformer de nouvelles données puis saisir un algorithme
- Il est facile d'essayer plusieurs méthodes de transformation pour voir quelle combinaison de transformations fonctionne le mieux
Tout d'abord, nous avons toujours besoin d'un ensemble de données propre, et nous devons séparer les prédicteurs et les étiquettes
housing = start_train_set.drop("median_house_value", axis=1)
housing_labels = start_train_set["median_house_value"].copy()
复制代码
1.1 Nettoyage des données
Figure 1.1 Total_chambres manquantes
# 1.放弃这些相应的区域
housing.dropna(subset=["total_bedrooms"])
# 2.放弃整个属性
housing.drop("total_bedrooms", axis=1)
# 3.将缺失的值设置为某一个数(0,平均数或中位数等)
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median, inplace=True)
复制代码
Un bref aperçu des API utilisées ci-dessus :
Supprimer les lignes/colonnes vides DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
paramètre | illustrer |
---|---|
axe | par défaut 0 fait référence à la ligne, 1 fait référence à la colonne |
comment | Facultatif "tout" (supprimer tant que la ligne/colonne a des valeurs nulles) ou "tous" (supprimer si l'attribut est tout nul) |
battre | Entier, supprimer uniquement lorsque les valeurs nulles de seuil sont atteintes |
sous-ensemble | Spécifiez le nom de la colonne/le nom de l'index à supprimer |
en place | True pour écraser le DataFrame appelé |
Remplir les données manquantes DataFrame.fillna(value=None, method=None, axis=None, inplace=False, limit=None, downcast=None, **kwargs)
paramètre | illustrer |
---|---|
valeur | Valeur de remplissage, peut être scalaire, dict, série ou DataFrame |
méthode | 可选{‘backfill’, ‘bfill’, ‘pad’, ‘ffill’, None}, default None,pad/ffill:用前一个非缺失值去填充该缺失值,backfill/bfill:用下一个非缺失值填充该缺失,None:指定一个值去替换缺失值 |
limit | 限制填充个数 |
如果是选择方法三,我们还能够先训练出中位值,然后填充缺失值,当然我们也可以保存下来中位值,以备后面需要。当重新评估系统时,需要更换测试集中的缺失值;或者在系统上线时,需要新数据替换缺失值。我们可以使用Scikit-Learn提供的类:SimpleImputer来处理缺失值。
# 1.先创建一个SimpleImputer的实例
imputer = SimpleImputer(strategy="median")
# 2.创建一个无文本的数据副本
housing_num = housing.drop("ocean_proximity", axis=1)
# 3.使用fit()适配训练数据,这里适配了所有属性,以防其他原因导致别的属性可能也有缺失值
imputer.fit(housing_num)
# 当使用fit()之后,我们可以通过实例变量statistic_查看所有属性的中位数
imputer.statistics_
# imputer.statistics_输出
[-118.51 34.26 29. 2119. 433. 1164. 408. 3.54155]
# 将训练好的imputer的中位值替换housing_num的缺失值
X = imputer.transform(housing_num)
# transform返回的是NumPy数组,我们使用下面方法将其转换成DateFrame类型
housing_tr = pd.DataFrame(X, columns=housing_num.columns, index=housing_num.index)
复制代码
我们需要了解Scikit-Learn的API设计原则,它包含了如下方面:
- 一致性(Consistency):所有对象共享一个简单一致的界面。
- 估算器(Estimators):能够根据数据对某些参数进行估算的任意对象都可以称为估算器(例如我们使用过的imputer就是一个估算器)。估算由fit()方法执行,它只需要一个数据集作为参数(或者两个-对于有监督学习算法,第二个数据集包含标签)。引导过程的任何其他参数都被视为超参数(例如:imputer’s strategy),他必须被设置为一个实例变量(一般通过构造函数参数)。
- 转换器(Transformers):有些估算器(如imputer)也可转换数据集,这些称为转换器。由transform()方法和作为参数的但转换数据集一起执行转换,返回的结果就是转换后的数据集。
- 预测器(Predictors):还有些估算器能够基于一个给定的数据集进行预测,这称为预测器。例如之前的LinearRegression模型就是一个预测器,它基于一个国家的人均GDP预测该国家的生活满意度。预测器的predict()方法会接受一个新实例的数据集,然后返回一个包含相应预测的数据集。
- 检查(Inspection):所有估算器的超参数都可以通过公共实例变量(例如:imputer.strayegy)直接访问,并且所有估算器的学习参数可以通过有下划线后缀的公共实例变量(例如。imputer.stratics_)访问。
- 防止类扩散(Nonproliferation of classes):数据集被表示成NumPy数组或SciPy稀疏矩阵,超参数只能是Python字符串或数字
- 构成(Composition):现有的构件块尽最大可能重用。例如,任意序列的转换器最后加一个预测器就可以轻松创建一个Pipeline估算器。
- 合理的默认值(Sensible defaults):Scikit-Learn为大多数参数提供了合理的默认值,从而快速搭建一个基本的工作系统。
1.2 处理文本和分类属性
上面我们没有处理ocean_proximity属性,因为他是文本属性,需要我们先转换一下。
图1.2 housing["ocean_proximity"].value_counts()
之前我们通过housing["ocean_proximity"].value_counts()可以看到它的分类是有限的范围。因此我们可以通过Scikit-Learn的OrdinaryEncoder类把这些文字转成数字:
from sklearn.preprocessing import OrdinalEncoder
# 同样先创建OrdinalEncoder实例
ordinal_encoder = OrdinalEncoder()
# 这里将fit和transform通过fit_transform()方法一起执行
housing_cat_encode = ordinal_encoder.fit_transform(housing_cat)
# 查看一下housing_cat_encode转换后的前10行,使用housing_cat_encode[:10]输出如下:
[[1.]
[4.]
[1.]
[4.]
[0.]
[3.]
[0.]
[0.]
[0.]
[0.]]
# 可以看到分类的文字已经变成了全数字
# 我们还能通过Categories_实例变量获取分类列表:
ordinal_encoder.categories_
# 输出
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
复制代码
但是这种表征方式产生一个问题,机器学习算法会认为两个相近的值比离得远的两个值更为相似。这再某些情况下是正确的(例如:序列为“差”、“平均”、“好”、“优秀”),但是在ocean_proximity属性下这是有问题(明显类别0'<1H OCEAN'和类别4'NEAR BAY'之间比类别0'<1H OCEAN'和类别1'INLAND'之间的相似度更高)。
为了解决这种问题,常用的方法是创建一个二进制的属性:比如当类别是'<1H OCEAN',属性为[1,0,0,0,0](其中一个属性为1,其余为0),则'INLAND'可以表示为[0,1,0,0,0],以此类推。这就是独热编码。只有一个属性为1(热),其余是0(冷)。新的属性有时候也称为哑(dummy)属性。
我们可以使用Scikit-Learn的OneHotEncoder编码器,将类别编码为独热向量:
from sklearn.preprocessing import OneHotEncoder
# 定义实例
cat_encode = OneHotEncoder()
# 匹配和转换
housing_cat_1hot = cat_encode.fit_transform(housing_cat)
# 默认OneHotEncoder输出为SciPy稀疏矩阵,可以通过toarray()方法转化成(密集的)NumPy数组
# 也可以通过设置OneHotEncoder(sparse=False)直接输出NumPy数组
housing_cat_1hot.toarray()
# 输出
[[0. 1. 0. 0. 0.]
[0. 0. 0. 0. 1.]
[0. 1. 0. 0. 0.]
...
[1. 0. 0. 0. 0.]
[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]]
# cat_encode.categories_依然可以查看分类类别
cat_encode.categories_
# 输出
[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],
dtype=object)]
复制代码
从ocean_proximity的独热编码可以看出,如果属性过多的那么编码就会变得很长,也就会因为大量的输入反而会减慢训练并降低性能。如果发生了,这种情况就需要相关的数字特征代替类别输入。比如使用与海洋的距离代替ocean_proximity。或者也可以使用可学习的低维向量(称为嵌入)来替换每个类别。每个类别的表征可以在训练期间学习。这是表征学习。
1.3 自定义转换器
我们还可以根据自定义一些清理操作或组合特定属性等任务编写自己的转换器。我们希望能够与Scikit-Learn的功能(如流水线)想衔接,因此自定义转换器的需要应用以下三种方法:fit()(返回self)、transform()、fit_transform()。
fit_transform()可以通过继承TransformMixin类,同时我们还可以使用BaseEstimator作为基类(并在构造函数中避免*args和**kargs),同时还能够额外获得两种非常有用的自动调整超参数的方法(get_params()和set_params())。
我们定义一个可以添加之前讨论过的组合属性的转换器:
from sklearn.base import TransformerMixin
from sklearn.base import BaseEstimator
# 获取属性的列号[3,4,5,6]
col_names = "total_rooms", "total_bedrooms", "population", "households"
rooms_ix, bedrooms_ix, population_ix, households_ix = [
housing.columns.get_loc(c) for c in col_names]
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
# 无*args和**kargs
def __init__(self, add_bedrooms_per_room=True):
self.add_bedrooms_per_room = add_bedrooms_per_room
# 直接返回self
def fit(self, X, y=None):
return self
# 增加组合属性
def transform(self, X):
rooms_per_houshold = X[:, rooms_ix] / X[:, households_ix]
population_per_houshold = X[:, population_ix] / X[:, households_ix]
if self.add_bedrooms_per_room:
bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
return np.c_[X, rooms_per_houshold, population_per_houshold, bedrooms_per_room]
else:
return np.c_[X, rooms_per_houshold, population_per_houshold]
# 通过自定义的transformer给housing添加组合属性
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_addr = attr_adder.transform(housing.values)
复制代码
在CombinedAttributesAdder()我们还有一个超参数add_bedrooms_per_room,并且设置了默认值为True。有了这个超参数,可以让我们轻松知晓是否有助于机器学习算法。
1.4 特征缩放
在数据的转换中最重要的就是特征缩放。如果输入的属性具有非常大的比例差,往往会导致机器学习算法的性能不佳。目标值通常不需要缩放。本例中我们的房间总数范围在6~39320,收入中位数的范围是0~15,需要处理一下。
同比例缩放所有属性的两种常用方法是最小-最大缩放和标准化
最小-最大缩放(又叫做归一化) :将值重新缩放使其最终范围归于0~1之间将值减去最小值并除以最大值和最小值的差(可以使用Scikit-Learn的MinMaxScaler转换器,如果范围不想要0~1,可以调整feature_range) 。容易受到异常值影响:例如有一个在异常值为100,原本的0~15的范围就会被降低到成0~0.15。
公式 1-1:归一化
标准化 :首先减去平均值(所以标准化值的均值为0),然后除以方差(标准化值的方差为1),从而使得结果的分布具备单位方差(我们可以通过Scikit-Learn的StandadScaler转换器实现标准化)。标准化后没有把值绑定在某个范围内,但是受异常值的影响更小。
公式 1-2:标准化
需要注意的是,缩放器也是只用来拟合训练集而不是完整数据集。
1.5 转换流水线
我们可以是使用Scikit-Learn的Pipeline来创建固定流程的数据转换的流水线。
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# 创建一个数值属性的流水线
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler())
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
复制代码
Pipeline构造器会通过一系列的名称/构造器的配对来定义步骤序列。除了最后一个是估算器,其余都必须是转换器。流水线的命名随意,但是不含双下划线。
当我们调用流水线的fit()方法时,会在所有的转换器上按顺序依次调用fit_transform(),将一个调用的输出作为参数传递给下一个调用方法,直到传递到最后的估算器,则只有fit()。本例子的最后一个也是在转换器所以可以直接全部调用fit_transform()。
前面我们单独通过OneHotEncoder处理了字符列,num_pipeline处理了数字列。那么Scikit-Learn是否可以一起处理?哈哈!当然有,在0.20版Scikit-learn,我们可以通过ColumnTransformor,它能够根据将我们选择,对不同的列使用不同的处理方法。
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)
复制代码
导入ColumnTransformer后,我们需要先获得数值列名称列表和类别列名称列表,然后构造一个ColumnTransformer。我们从构造函数可以看出,我们需要一个元组列表,主要含有三个部分,第一个是名字、第二个是转换器、第三个是转换器所应用的列名列表。最后ColumnTransformer会将所有转换器处理的数据进行合并输出。
在合并的时候我们需要注意,OneHotEncoder()返回一个稀疏矩阵,而num_pipeline返回一个密集矩阵。当存在稀疏矩阵和密集矩阵合并时,ColumnTransformer会先估计最终矩阵的密度,当低于一个阈值(spare_threshold,默认为0.3),则返回稀疏矩阵。