如何使用Scikit-Learn创建一个自定义数据转换器

192 阅读5分钟

使用Scikit-Learn创建一个自定义数据转换器

在机器学习中,数据转换器被用来使数据集适合训练过程。Scikit-Learn能够快速进行实验,以最小的时间实现涉及预处理、机器学习算法、评估和推理的数据管道,获得高质量的结果。

简介

Scikit-Learn提供了内置的数据准备方法,在数据被送入训练模型之前。然而,作为一个数据科学家,你可能需要执行更多的自定义清理过程,或添加更多的属性,以提高模型的性能。要做到这一点,你将需要为你的数据创建一个自定义的转化器。

在这篇文章中,我们将探讨如何做到这一点。

前提条件

要跟上本教程,你应该具备以下条件。

  1. Python编程语言有良好的理解。
  2. 熟悉NumpyPandas库。
  3. 有使用Jupyter笔记本或任何其他基于笔记本的技术的基本知识,例如Google Colab
  4. 安装了Python和上述的库。

让我们开始吧。

这些代码片段是为笔记本定制的,但你也可以使用普通的python文件。

开始使用

加载数据

我们将使用下面的脚本从这个资源库获得我们的数据集。

import os
import tarfile
from six.moves import urllib

OUR_ROOT_URL = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
OUR_PATH = "datasets/housing"
OUR_DATA_URL = OUR_ROOT_URL + OUR_PATH + "/housing.tgz"

def get_data(our_data_url=OUR_DATA_URL, our_path=OUR_PATH):
      if not os.path.isdir(our_path):
            os.makedirs(our_path)
      #setting the zip file path      
      zipfile_path = os.path.join(our_path, "housing.tgz")
      #getting the file from the url and extracting it
      urllib.request.urlretrieve(our_data_url, zipfile_path)
      our_zip_file = tarfile.open(zipfile_path)
      our_zip_file.extractall(path=our_path)
      our_zip_file.close()

get_data()

这段代码是为了从URL中下载数据,以不纠缠于此。首先,我们导入os 模块,与操作系统进行交互。之后,我们导入了tarfile 模块,用于访问和操作tar文件。最后,我们导入了urllib ,用于使用URL操作功能。

然后,我们适当地设置了我们的路径。在get_data() 函数中,我们为我们的数据建立了一个目录,从URL中检索它,然后提取并存储它。

因此,在你的工作目录中,你会注意到一个名为datasets的目录被创建。打开它,你会得到另一个名为housing的目录,里面有一个名为housing.csv的文件。我们将使用这个文件。

我们调用这个函数。然后,我们将加载CSV文件。

import pandas as pd

def load_our_data(our_path=OUR_PATH):
    #setting the csv file path
    our_file_path = os.path.join(our_path, "housing.csv")
    #reading it using Pandas
    return pd.read_csv(our_file_path)

our_dataset = load_our_data()

首先,我们导入pandas 库,从指定的路径加载CSV数据,our_file_path

你可以通过以下方式查看数据。

our_dataset.head()
our_dataset.info()

清理数据

我们在这里要做的清理操作是用中值来填充空的数字属性。我们将使用SimpleImputer ,一个估计器来做这件事。但是,首先,我们把strategy 设为median ,以计算每一列空数据的中值。

from sklearn.impute import SimpleImputer
'''setting the `strategy` to `median` so that it calculates the median value for each column's empty data'''
imputer = SimpleImputer(strategy="median")
#removing the ocean_proximity attribute for it is textual
our_dataset_num = our_dataset.drop("ocean_proximity", axis=1)
#estimation using the fit method
imputer.fit(our_dataset_num)
#transforming using the learnedparameters
X = imputer.transform(our_dataset_num)
#setting the transformed dataset to a DataFrame
our_dataset_numeric = pd.DataFrame(X, columns=our_dataset_num.columns)

我们放弃了ocean_proximity属性,因为它是一个文本属性,将在下一节处理。

产生的结果是一个数组,所以我们把它转换为一个DataFrame。

处理文本和分类属性

我们不能类似地处理文本和数字属性。因此,例如,我们不能计算文本的中位数。

我们将使用一个叫做OrdinalEncoder. 的转化器来处理这个问题。选择它是因为它对管道更友好。此外,它为相应的文本属性分配了数字,例如,1代表NEAR,2代表FAR。

from sklearn.preprocessing import OrdinalEncoder
#selecting the textual attribute
our_text_cats = our_dataset[['ocean_proximity']]
our_encoder = OrdinalEncoder()
#transforming it
our_encoded_dataset = our_encoder.fit_transform(our_text_cats)

我们的数据转换器

这就是我们要创建的自定义转换器。我们将添加这三个属性。

  • 每户的房间。
  • 每户人口。
  • 每户的卧室。

为了使我们的转化器能够顺利地与Scikit-Learn一起工作,我们应该有三个方法。

  1. fit()
  2. transform()
  3. fit_transform

我们包括这三个方法是因为Scikit-Learn是基于鸭子类型的。还使用了一个类,因为这使我们更容易包括所有的方法。

最后一个是通过使用TransformerMixin 作为基类自动得到的。BaseEstimator 让我们得到set_params()get_params() 方法,这些方法对超参数调整有帮助。

我们通过划分适当的属性来获得transform() 方法中的三个额外属性。一个例子是这样的。为了得到每户的房间数,我们用房间数除以户数。

import numpy as np
from sklearn.base import BaseEstimator, TransformerMixin
#initialising column numbers
rooms,  bedrooms, population, household = 3,4,5,6

class CustomTransformer(BaseEstimator, TransformerMixin):
    #the constructor
    '''setting the add_bedrooms_per_room to True helps us check if the hyperparameter is useful'''
    def __init__(self, add_bedrooms_per_room = True):
        self.add_bedrooms_per_room = add_bedrooms_per_room
    #estimator method
    def fit(self, X, y = None):
        return self
    #transfprmation
    def transform(self, X, y = None):
        #getting the three extra attributes by dividing appropriate attributes
        rooms_per_household = X[:, rooms] / X[:, household]
        population_per_household = X[:, population] / X[:, household]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:, bedrooms] / X[:, rooms]
            return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]

attrib_adder = CustomTransformer()
our_extra_attributes = attrib_adder.transform(our_dataset.values)            

我们的流水线

我们在一个管道中实现它们,使数据转换步骤以正确的顺序执行。

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
#the numeric attributes transformation pipeline
numeric_pipeline = Pipeline([
        ('imputer', SimpleImputer(strategy="median")),
        ('attribs_adder', CustomTransformer()),
    ])
numeric_attribs = list(our_dataset_numeric)
#the textual transformation pipeline
text_attribs = ["ocean_proximity"]
#setting the order of the two pipelines
our_full_pipeline = ColumnTransformer([
        ("numeric", numeric_pipeline, numeric_attribs),
        ("text", OrdinalEncoder(), text_attribs),
    ])
'''Finally, scaling the data and learning the scaled parameters from the pipeline
'''
our_dataset_prepared = full_pipeline.fit_transform(our_dataset)

ColumnTransformer ,用于分别转化列,并将每个转化器产生的特征结合起来,形成一个单一的特征空间。

总结

我们已经看到了获取数据、转换数据,然后在一个管道中实现所有步骤的各种步骤。所以我希望你能得到一些启发。