在大多数机器学习项目中,你要处理的数据不大可能恰好是生成最优模型的理想格式。有很多数据变换的步骤例如分类变量编码、特征缩放和归一化需要执行。Scikit-learn的预处理模块中包含了内建的函数来支持这些常用的变换。
但是,在一个典型的机器学习工作流中你将需要应用这些变换至少两次。一次是在训练时,另一次是在你要用模型预测新数据时。当然你可以写一个函数来重用这些变换,但是你还是需要首先运行这个函数,然后再调用模型。Scikit-learn的流水线/pipeline就是一个简化此操作的工具,具有如下优点:
在本文中,我将使用一个贷款预测方面的数据集,来介绍流水线的工作原理以及实现方法。
学编程,上汇智网,在线编程环境,一对一助教指导。
首先我将训练和测试文件导入jypyter notebook。我删除了Load_ID列,因为在训练和预测中并不需要它。我使用pandas的dtypes函数来获取数据集的简要信息:
import pandas as pd
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
train = train.drop('Loan_ID', axis=1)
train.dtypes
可以看到数据中既有分类变量也有数值变量,因此我至少需要应用one-hot编码变换以及某种尺度的缩放。我使用scikit-learn的流水线来执行这些变换,同时应用fit方法进行训练。
在构建流水线之前我将训练数据拆分为训练集和测试集,这样我可以验证模型的性能:
X = train.drop('Loan_Status', axis=1)
y = train['Loan_Status']
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
构造流水线的第一步是定义变换器类型。在下面的代码中,我创建了一个运用StandardScaler的数值变换器,它同时包含了一个SimpleImputer来填充丢失的值。这是scikit-learn中的一个相当出色的函数,它有很多选项来定义如何填充丢失值。我选择使用中位数据(median)但是也可能其他选项会有更好的效果。分类变换器也有一个支持各种填充方法的SimpleImputer,燃火利用OneHotEncoder将分类值转换为整数:
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncodernumeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())])categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))])
接下来我们使用ColumnTransformer变换数据帧中的列。在此之前已经使用pandas的dtype方法进行了列表排序:
numeric_features = train.select_dtypes(include=['int64', 'float64']).columns
categorical_features = train.select_dtypes(include=['object']).drop(['Loan_Status'], axis=1).columns
from sklearn.compose import ColumnTransformer
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)])
接下来的步骤是创建一个流水线将前面创建的预处理器与分类器整合在一起。在这里我使用一个简单的RandomForestClassifier:
from sklearn.ensemble import RandomForestClassifier
rf = Pipeline(steps=[('preprocessor', preprocessor),
('classifier', RandomForestClassifier())])
你可以简单地对原始数据调用fit方法,预处理步骤将会先执行,然后再训练分类器:
rf.fit(X_train, y_train)
要预测新数据也一样,流水线也会先进行预处理,然后再进行预测:
y_pred = rf.predict(X_test)
流水线可以用于模型选择过程。下面的示例代码对一组scikit-learn分类器逐个应用变换并训练模型。
from sklearn.metrics import accuracy_score, log_loss
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC, LinearSVC, NuSVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, GradientBoostingClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysisclassifiers = [
KNeighborsClassifier(3),
SVC(kernel="rbf", C=0.025, probability=True),
NuSVC(probability=True),
DecisionTreeClassifier(),
RandomForestClassifier(),
AdaBoostClassifier(),
GradientBoostingClassifier()
]for classifier in classifiers:
pipe = Pipeline(steps=[('preprocessor', preprocessor),
('classifier', classifier)])
pipe.fit(X_train, y_train)
print(classifier)
print("model score: %.3f" % pipe.score(X_test, y_test))
[外链图片转存失败(img-WAIAk0il-1569282274148)(scikit-learn-pipeline-guide/model-select.png)]
流水线也可以用于网格搜索(grid search)以找到模型的最佳参数。为此我们需要首先为模型创建一个参数网格。重要的一点是你需要给每个参数名添加分类器的名称。在上面的代码中我将分类器命名 为classifier,因此我给每个参数都添加了classifier__
。接下来我创建一个网格搜索对象,它包含了原始的流水线。当我调用fit方法时,就会在网格搜索交叉验证之前首先对数据执行变换。
param_grid = {
'classifier__n_estimators': [200, 500],
'classifier__max_features': ['auto', 'sqrt', 'log2'],
'classifier__max_depth' : [4,5,6,7,8],
'classifier__criterion' :['gini', 'entropy']}
from sklearn.model_selection import GridSearchCV
CV = GridSearchCV(rf, param_grid, n_jobs= 1)
CV.fit(X_train, y_train)
print(CV.best_params_)
print(CV.best_score_)
在我开始使用流水线之前,经常发现我看不懂以前某个项目的处理流程了。流水线让整个机器学习流程清晰易懂,容易维护。希望这教程对你学习scikit-learn的pipeline有所帮助。