摘要
在很多机器学习场景中,需要我们对数据进行预处理,sklean提供的pipeline接口方便我们将数据预处理与模型训练等工作进行整合,方便对训练集、验证集、测试集做相同的转换操作,极大的提高了工作效率。但是在不同场景下往往预处理的方法会出现多样性,然而sklearn所提供的预处理接口(Transformers)数量有限,有的时候往往需要我们自己编写函数对数据进行预处理。为了让我们自定义的数据预处理函数能够放入sklearn的pipeline中,我们想到了自定义Transformer的方法,本文也将围绕自定义Transformer的具体步骤进行展开。
目录
创建项目
编写自定义sklearn标准转换器
制作python第三方库
库的安装与调用
创建项目
使用编译器:pycharm
创建一个空项目,记得选择好相应的python解释器
点击create后创建新项目完成,在空项目文件夹下创建一个python package和一个setup.py文件,python package创建之后会自带一个叫__init__.py的空文件,我们之后会对它进行编写,创建好这些之后整个项目目录会变成下面这样:
请注意,我们所创建的这个python package的名字将会作为我们调用这个第三方包时的名字,在这个例子中将是
import transformer
编写自定义sklearn标准转换器
首先,sklearn为了方便用户自定义预处理过程,提供了TransformerMixin、BaseEstimator等基类,我们可以直接继承过来。另外,pipeline的工作原理是在调用pipeline的fit()方法时逐一调用pipeline中转换器的fit()、transform()方法,再调用最后一步estimator的fit()方法。为此我们需要重载自定义转换器的fit()方法和transform()方法,基类TransformerMixin中包含fit()和transform()方法,基类BaseEstimator中包含获取和设置转换器参数的方法get_params()、set_params()。
本实例将编写一个去掉指定列的转换器,即该转化器接受一个参数,经过fit(),transform()之后得到一个去掉指定列的DataFrame:
我们在刚才创建好的python package下新建一个python文件‘DropColumns.py’,该文件用来定义我们的转换器,文件内容如下:
import pandas as pdimport numpy as npfrom sklearn.base import BaseEstimator, TransformerMixinclass DropColumns(BaseEstimator, TransformerMixin): def __init__(self, drop_list): self.drop_list = drop_list def fit(self, X, y=None): return self def transform(self, X, y=None): useful_columns = [x for x in list(X.columns) if (x not in self.drop_list)] df = X[useful_columns].copy() return df
该转换器没有参数需要通过fit()方法保存,所以直接在fit()中return self即可。
为了对比,我们将创建一个需要保存参数的转换器IVTransformer,用于根据label计算目标属性的woe映射并计算其信息量iv,我们接着创建一个python文件‘IVTransformer.py’,文件内容如下:
import pandas as pdimport numpy as npfrom sklearn.base import BaseEstimator, TransformerMixinclass IVTransformer(BaseEstimator, TransformerMixin): def __init__(self, target_column=' ', label_column=' ', positive_label=[0], iv=0, woe=0): self.target_column = target_column self.label_column = label_column self.positive_label = positive_label self.iv = iv self.woe = woe def fit(self, df_name, y=None): if len(df_name[self.label_column].unique()) == 2: pos_label, neg_label = df_name.loc[:, self.label_column].unique() pos_counts = df_name[df_name[self.label_column] == pos_label][self.target_column].value_counts() neg_counts = df_name[df_name[self.label_column] == neg_label][self.target_column].value_counts() pos_total = df_name[self.label_column].value_counts()[pos_label] neg_total = df_name[self.label_column].value_counts()[neg_label] pos_rate = pos_counts / pos_total neg_rate = neg_counts / neg_total self.woe = np.log(pos_rate / neg_rate) self.iv = np.sum((pos_rate - neg_rate) * self.woe) return self elif len(df_name[self.label_column].unique()) > 2: new_label = np.array(['pos' if x in self.positive_label else 'neg' for x in df_name[self.label_column]]) pos_total = (new_label == 'pos').sum() neg_total = (new_label == 'neg').sum() pos_counts = df_name.loc[new_label == 'pos', self.target_column].value_counts() neg_counts = df_name.loc[new_label == 'neg', self.target_column].value_counts() pos_rate = pos_counts / pos_total neg_rate = neg_counts / neg_total self.woe = np.log(pos_rate / neg_rate) self.iv = np.sum((pos_rate - neg_rate) * self.woe) return self elif len(df_name[self.label_column].unique()) <2: print("Label needs at least 2 classes. The calculation cannot be executed.") return self def transform(self, df_name): df_name.loc[:, self.target_column] = df_name.loc[:, self.target_column].map(self.woe) return df_name
需要注意的是,我们通过fit()计算出来的woe和iv将保存在当前transformer中,在调用transform()时将不会改变这两个参数,因此我们在自定义其他转换器时也要注意,不要在transform()方法中改变转换器的参数。
另外,为了能在载入模块时使用*符号来载入模块内所有的类,我们需要在__init__.py文件内加入以下内容:
from .DropColumns import DropColumnsfrom .IVTransformer import IVTransformer__all__ = ['IVTransformer','DropColumns']
至此我们完成了自定义转换器的工作,但是为了方便大家调用我们的转换器来制作自己的pipeline,我们可以将这些代码打成包,那么接下来我们来完成后续的打包工作。
制作python第三方库
这一步需要定义最开始创建的setup.py文件,内容如下:
from setuptools import setupVERSION = '1.0.1'setup(name = 'Transformer', version = VERSION, description = 'DIY Transformers', author='zhj', author_email='760007506@qq.com', packages=['transformer'], zip_safe=False)
这里需要注意的是,setup()函数的packages参数需要表明当前目录下包含__init__.py文件的目录,因此如果定义了子模块,需要将子模块包含__init__.py文件的路径添加到packages参数list里面。另外需要注意的是,这里的name参数是安装库时的名字,与调用库时的名字不一样,为了区别,这个参数我们设置成首字母大写的Transformer。
接下来打开终端,进入setup.py文件所在的目录下,输入
python3 setup.py bdist_wheel
回车之后将产生三个文件夹‘build’、‘dist’、‘Transformer.egg-info’,我们打开dist文件夹会看到一个.whl拓展名的文件,这就是我们接下来要用到的安装文件。至此打包工作完成~
库的安装与调用
在指定python环境下打开终端,输入以下内容来安装我们的转换器库
pip install .../dist/Transformer-1.0.1-py3-none-any.whl
这里需要输入之前生成的.whl文件的绝对路径,这样我们的库就被安装在环境中了,在终端里输入pip list就可以看到它了。
接下来需要注意的就是,在调用我们的库时需要键入
import transformer
这里如果transformer首字母大写的话会报错。也就是说,import的模块名字与我们项目文件夹中的python package的名字一致。
可以写一个测试脚本来测试我们的库:
from transformer import *import pandas as pdimport numpy as npfrom sklearn.pipeline import Pipelineimport xgboost as xgbfrom sklearn import metricsif __name__ == '__main__':###################### load data ###################### train_data = pd.read_csv('...') vali_data = pd.read_csv('...') print(train_data.shape, vali_data.shape) print('Train Model...') fea_to_drop = [...] clf = xgb.XGBClassifier( learning_rate =0.1, n_estimators=100, max_depth=2, min_child_weight=1, gamma=0, subsample=0.7, colsample_bytree=0.8, objective = 'binary:logistic') train_y = train_data.loc[:, 'label'] pipeline = Pipeline([ ('step_iv',IVTransformer(target_column = 'data_0', label_column = 'label')), ('step_drop',DropColumns(drop_list = fea_to_drop)), ('clf',clf)]) p = pipeline.fit(train_data,train_y) print(p.predict(vali_data.loc[0:10]))
参考:
sklearnAPI文档