百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Pipeline 和 Transformer(pipeline销售)

toyiye 2024-07-08 23:07 10 浏览 0 评论

掌握sklearn必须知道这三个强大的工具。因此,在建立机器学习模型时,学习如何有效地使用这些方法是至关重要的。

在深入讨论之前,我们先从两个方面着手:

  • Transformer:Transformer是指具有fit()和transform()方法的对象,用于清理、减少、扩展或生成特征。简单地说,transformers帮助你将数据转换为机器学习模型所需的格式。OneHotEncoder和MinMaxScaler就是Transformer的例子。
  • Estimator:Estimator是指机器学习模型。它是一个具有fit()和predict()方法的对象。我们将交替使用模型和Estimator这2个术语。该链接是一些Estimator的例子:https://scikit-learn.org/stable/tutorial/machine_learning_map/index.html。

安装

如果你想在你电脑上运行代码,确保你已经安装了pandas,seaborn和sklearn。我在Jupyter notebook中在python3.7.1中编写脚本。

让我们导入所需的库和数据集。关于这个数据集(包括数据字典)的详细信息可以在这里找到(这个源实际上是针对R的,但是它似乎引用了相同的底层数据集):https://vincentarelbundock.github.io/Rdatasets/doc/reshape2/tips.html。

# 设置种子
seed = 123

# 为数据导入包/模块
import pandas as pd
from seaborn import load_dataset

# 为特征工程和建模导入模块
from sklearn.model_selection import train_test_split
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.preprocessing import OneHotEncoder, MinMaxScaler
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LinearRegression

# 加载数据集
df = load_dataset('tips').drop(columns=['tip', 'sex']).sample(n=5, random_state=seed)

# 添加缺失的值
df.iloc[[1, 2, 4], [2, 4]] = np.nan
df

使用少量的记录可以很容易地监控每个步骤的输入和输出。因此,我们将只使用数据集中5条记录的样本。

管道

假设我们想用smoker、day和time列来预测总的账单。我们将先删除size列并对数据进行划分:

# 划分数据
X_train, X_test, y_train, y_test = train_test_split(df.drop(columns=['total_bill', 'size']), 
                                                    df['total_bill'], 
                                                    test_size=.2, 
                                                    random_state=seed)

通常情况下,原始数据不是我们可以直接将其输入机器学习模型的状态。因此,将数据转换为可接受且对模型有用的状态成为建模的必要先决条件。让我们做以下转换作为准备:

  1. 用“missing”填充缺失值
  2. one-hot编码

以下完成这两个步骤:

# 输入训练数据
imputer = SimpleImputer(strategy='constant', fill_value='missing')
X_train_imputed = imputer.fit_transform(X_train)

# 编码训练数据
encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
X_train_encoded = encoder.fit_transform(X_train_imputed)

# 检查训练前后的数据
print("******************** Training data ********************")
display(X_train)
display(pd.DataFrame(X_train_imputed, columns=X_train.columns))
display(pd.DataFrame(X_train_encoded, columns=encoder.get_feature_names(X_train.columns)))

# 转换测试数据
X_test_imputed = imputer.transform(X_test)
X_test_encoded = encoder.transform(X_test_imputed)

# 检查测试前后的数据
print("******************** Test data ********************")
display(X_test)
display(pd.DataFrame(X_test_imputed, columns=X_train.columns))
display(pd.DataFrame(X_test_encoded, columns=encoder.get_feature_names(X_train.columns)))

你可能已经注意到,当映射回测试数据集的列名时,我们使用了来自训练数据集的列名。这是因为我更喜欢使用来自于训练Transformer的数据的列名。但是,如果我们使用测试数据集,它将给出相同的结果。

对于每个数据集,我们首先看到原始数据,然后是插补后的输出,最后是编码后的输出。

这种方法可以完成任务。但是,我们将上一步的输出作为输入手动输入到下一步,并且有多个临时输出。我们还必须在测试数据上重复每一步。随着步骤数的增加,维护将变得更加繁琐,更容易出错。

我们可以使用管道编写更精简和简洁的代码:

# 将管道与训练数据匹配
pipe = Pipeline([('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                 ('encoder', OneHotEncoder(handle_unknown='ignore', sparse=False))])
pipe.fit(X_train)

# 检查训练前后的数据
print("******************** Training data ********************")
display(X_train)
display(pd.DataFrame(pipe.transform(X_train), columns=pipe['encoder'].get_feature_names(X_train.columns)))

# 检查测试前后的数据
print("******************** Test data ********************")
display(X_test)
display(pd.DataFrame(pipe.transform(X_test), columns=pipe['encoder'].get_feature_names(X_train.columns)))

使用管道时,每个步骤都将其输出作为输入传递到下一个步骤。因此,我们不必手动跟踪数据的不同版本。这种方法为我们提供了完全相同的最终输出,但是使用了更优雅的代码。

在查看了转换后的数据之后,现在是在我们的示例中添加模型的时候了。让我们从为第一种方法添加一个简单模型:

# 输入训练数据
imputer = SimpleImputer(strategy='constant', fill_value='missing')
X_train_imputed = imputer.fit_transform(X_train)

# 编码训练数据
encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
X_train_encoded = encoder.fit_transform(X_train_imputed)

# 使模型拟合训练数据
model = LinearRegression()
model.fit(X_train_encoded, y_train)

# 预测训练数据
y_train_pred = model.predict(X_train_encoded)
print(f"Predictions on training data: {y_train_pred}")

# 转换测试数据
X_test_imputed = imputer.transform(X_test)
X_test_encoded = encoder.transform(X_test_imputed)

# 预测测试数据
y_test_pred = model.predict(X_test_encoded)
print(f"Predictions on test data: {y_test_pred}")

我们将对管道方法进行同样的处理:

# 将管道与训练数据匹配
pipe = Pipeline([('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                 ('encoder', OneHotEncoder(handle_unknown='ignore', sparse=False)), 
                 ('model', LinearRegression())])
pipe.fit(X_train, y_train)

# 预测训练数据
y_train_pred = pipe.predict(X_train)
print(f"Predictions on training data: {y_train_pred}")

# 预测测试数据
y_test_pred = pipe.predict(X_test)
print(f"Predictions on test data: {y_test_pred}")

你可能已经注意到,一旦我们训练了一条管道,进行预测是多么简单。pipe.predict(X)对原始数据进行转换,然后返回预测。也很容易看到步骤的顺序。让我们直观地总结一下这两种方法:

使用管道不仅可以组织和简化代码,而且还有许多其他好处,下面是其中一些好处:

  • 微调管道的能力:当构建一个模型时,你可能需要尝试不同的方法来预处理数据并再次运行模型,看看预处理步骤中的调整是否能提高模型的泛化能力。在优化模型时,微调不仅存在于模型的超参数中,而且存在于预处理步骤的实现中。考虑到这一点,当我们有一个统一了Transformer和Estimator的管道对象时,我们可以微调整个管道的超参数,包括使用GridSearchCV或RandomizedSearchCV的Estimator和两个Transformer。
  • 更容易部署:在训练模型时用于准备数据的所有转换步骤在进行预测时也可以应用于生产环境中的数据。当我们训练管道时,我们训练一个包含数据转换器和模型的对象。一旦经过训练,这个管道对象就可以用于更平滑的部署。

ColumnTransformer

在前面的例子中,我们以相同的方式对所有列进行插补和编码。但是,我们经常需要对不同的列组应用不同的transformer。例如,我们希望将OneHotEncoder仅应用于分类列,而不应用于数值列。这就是ColumnTransformer的用武之地。

这一次,我们将对保留所有列的数据集进行分区,以便同时具有数值和类别特征。

# 划分数据
X_train, X_test, y_train, y_test = train_test_split(df.drop(columns=['total_bill']), 
                                                    df['total_bill'], 
                                                    test_size=.2, 
                                                    random_state=seed)

# 定义分类列
categorical = list(X_train.select_dtypes('category').columns)
print(f"Categorical columns are: {categorical}")

# 定义数字列
numerical = list(X_train.select_dtypes('number').columns)
print(f"Numerical columns are: {numerical}")

我们根据数据类型将特征分为两组。列分组可以根据数据的适当情况进行。例如,如果不同的预处理管道更适合分类列,则可以将它们进一步拆分为多个组。

上一节的代码现在将不再工作,因为我们有多个数据类型。让我们看一个例子,其中我们使用ColumnTransformer和Pipeline在存在多个数据类型的情况下执行与之前相同的转换。

# 定义分类管道
cat_pipe = Pipeline([('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                     ('encoder', OneHotEncoder(handle_unknown='ignore', sparse=False))])

# 使ColumnTransformer拟合训练数据
preprocessor = ColumnTransformer(transformers=[('cat', cat_pipe, categorical)], 
                                 remainder='passthrough')
preprocessor.fit(X_train)

# 准备列名
cat_columns = preprocessor.named_transformers_['cat']['encoder'].get_feature_names(categorical)
columns = np.append(cat_columns, numerical)

# 检查训练前后的数据
print("******************** Training data ********************")
display(X_train)
display(pd.DataFrame(preprocessor.transform(X_train), columns=columns))

# 检查测试前后的数据
print("******************** Test data ********************")
display(X_test)
display(pd.DataFrame(preprocessor.transform(X_test), columns=columns))

分类列的输出与上一节的输出相同。唯一的区别是这个版本有一个额外的列:size。我们已经将cat_pipe(在上一节中称为pipe)传递给ColumnTransformer来转换分类列,并指定remainment='passthrough'以保持其余列不变。

让我们用中值填充缺失值,并将其缩放到0和1之间:

# 定义分类管道
cat_pipe = Pipeline([('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                     ('encoder', OneHotEncoder(handle_unknown='ignore', sparse=False))])

# 定义数值管道
num_pipe = Pipeline([('imputer', SimpleImputer(strategy='median')),
                     ('scaler', MinMaxScaler())])

# 使ColumnTransformer拟合训练数据
preprocessor = ColumnTransformer(transformers=[('cat', cat_pipe, categorical),
                                               ('num', num_pipe, numerical)])
preprocessor.fit(X_train)

# 准备列名
cat_columns = preprocessor.named_transformers_['cat']['encoder'].get_feature_names(categorical)
columns = np.append(cat_columns, numerical)

# 检查训练前后的数据
print("******************** Training data ********************")
display(X_train)
display(pd.DataFrame(preprocessor.transform(X_train), columns=columns))

# 检查测试前后的数据
print("******************** Test data ********************")
display(X_test)
display(pd.DataFrame(preprocessor.transform(X_test), columns=columns))

现在所有列都被插补,范围在0到1之间。使用ColumnTransformer和Pipeline,我们将数据分成两组,将不同的管道和不同的Transformer应用到每组,然后将结果粘贴在一起:

尽管在我们的示例中,数值管道和分类管道中的步骤数相同,但管道中可以有任意数量的步骤,并且不同列子集的步骤数不必相同。现在我们将一个模型添加到我们的示例中:

# 定义分类管道
cat_pipe = Pipeline([('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                     ('encoder', OneHotEncoder(handle_unknown='ignore', sparse=False))])

# 定义数值管道
num_pipe = Pipeline([('imputer', SimpleImputer(strategy='median')),
                     ('scaler', MinMaxScaler())])

# 组合分类管道和数值管道
preprocessor = ColumnTransformer(transformers=[('cat', cat_pipe, categorical),
                                               ('num', num_pipe, numerical)])

# 在管道上安装transformer和训练数据的estimator
pipe = Pipeline(steps=[('preprocessor', preprocessor),
                       ('model', LinearRegression())])
pipe.fit(X_train, y_train)

# 预测训练数据
y_train_pred = pipe.predict(X_train)
print(f"Predictions on training data: {y_train_pred}")

# 预测测试数据
y_test_pred = pipe.predict(X_test)
print(f"Predictions on test data: {y_test_pred}")

为了将ColumnTransformer中指定的预处理步骤与模型结合起来,我们在外部使用了一个管道。以下是它的视觉表现:

当我们需要对不同的列子集执行不同的操作时,ColumnTransformer很好地补充了管道。

FeatureUnion

以下代码的输出在本节中被省略,因为它们与ColumnTransformer章节的输出相同。

FeatureUnion是另一个有用的工具。它可以做ColumnTransformer刚刚做过的事情,但要做得更远:

# 自定义管道
class ColumnSelector(BaseEstimator, TransformerMixin):
    """Select only specified columns."""
    def __init__(self, columns):
        self.columns = columns

    def fit(self, X, y=None):
        return self

    def transform(self, X):
        return X[self.columns]

# 定义分类管道
cat_pipe = Pipeline([('selector', ColumnSelector(categorical)),
                     ('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                     ('encoder', OneHotEncoder(handle_unknown='ignore', sparse=False))])

# 定义数值管道
num_pipe = Pipeline([('selector', ColumnSelector(numerical)),
                     ('imputer', SimpleImputer(strategy='median')),
                     ('scaler', MinMaxScaler())])

# FeatureUnion拟合训练数据
preprocessor = FeatureUnion(transformer_list=[('cat', cat_pipe),
                                              ('num', num_pipe)])
preprocessor.fit(X_train)

# 准备列名
cat_columns = preprocessor.transformer_list[0][1][2].get_feature_names(categorical)
columns = np.append(cat_columns, numerical)

# 检查训练前后的数据
print("******************** Training data ********************")
display(X_train)
display(pd.DataFrame(preprocessor.transform(X_train), columns=columns))

# 检查测试前后的数据
print("******************** Test data ********************")
display(X_test)
display(pd.DataFrame(preprocessor.transform(X_test), columns=columns))

我们可以将FeatureUnion视为创建数据的副本,并行地转换这些副本,然后将结果粘贴在一起。这里的术语副本更像是一种辅助概念化的类比,而不是实际采用的技术。

在每个管道的开始,我们添加了一个额外的步骤,在这里我们使用一个定制的转换器来选择相关的列:第14行和第19行的ColumnSelector。下面是我们可视化上面的脚本的图:

现在,是时候向脚本添加模型了:

# 定义分类管道
cat_pipe = Pipeline([('selector', ColumnSelector(categorical)),
                     ('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
                     ('encoder', OneHotEncoder(handle_unknown='ignore', sparse=False))])

# 定义数值管道
num_pipe = Pipeline([('selector', ColumnSelector(numerical)),
                     ('imputer', SimpleImputer(strategy='median')),
                     ('scaler', MinMaxScaler())])

# 组合分类管道和数值管道
preprocessor = FeatureUnion(transformer_list=[('cat', cat_pipe),
                                              ('num', num_pipe)])

# 组合分类管道和数值管道
pipe = Pipeline(steps=[('preprocessor', preprocessor),
                       ('model', LinearRegression())])
pipe.fit(X_train, y_train)

# 预测训练数据
y_train_pred = pipe.predict(X_train)
print(f"Predictions on training data: {y_train_pred}")

# 预测测试数据
y_test_pred = pipe.predict(X_test)
print(f"Predictions on test data: {y_test_pred}")

它看起来很像我们用ColumnTransformer做的。

如本例所示,使用FeatureUnion比使用ColumnTransformer要复杂得多。因此,在我看来,在类似的情况下最好使用ColumnTransformer。

然而,FeatureUnion肯定有它的位置。如果你需要以不同的方式转换相同的输入数据并将它们用作特征,FeatureUnion就是其中之一。例如,如果你正在处理一个文本数据,并且希望对数据进行tf-idf矢量化以及提取文本长度,FeatureUnion是一个完美的工具。

总结

你可能已经注意到,Pipeline是超级明星。ColumnTransformer和FeatureUnion是用于管道的附加工具。ColumnTransformer更适合于并行划分,而FeatureUnion允许我们在同一个输入数据上并行应用多个转换器。下面是一个简单的总结:

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码