天天看點

機器學習|Pipeline

目錄

  • ​​前言​​
  • ​​Pipeline與ColumnTransformer​​
  • ​​Pipeline與FeatureUnion​​
  • ​​Pipeline與GridSearchCV​​
  • ​​Pipeline與pickle​​
  • ​​Pipeline的優缺點​​
  • ​​參考文獻​​

前言

很多資料科學家在做特征工程的時候都會遇到這樣困惑,當樣本的特征變量很多的時候,既有連續型的數值變量,或許其中還有空值,亦或許需要歸一化處理,又有離散型的類别變量,或許其中有些類别很多很多,有些類别又隻有幾個,假使這些問題都能被耐心的你一步一步處理好,但是在部署時,針對全新的預測樣本,又如何保證線上預測時候要做的特征處理與離線訓練所做的特征處理保持一緻性呢?這個時候就需要用到Pipeline了。

pipeline中文意思是管道,流水線,顧名思義就是讓機器學習像流水線作業一樣,一個典型的機器學習一般會包含如下幾個步驟

  • 擷取資料
  • 特征工程
  • 模型訓練與調參
  • 模型部署與預測
機器學習|Pipeline

擷取資料主要是指擷取真實的資料供後續訓練模型使用,特征工程是機器學習的重心所在,需要應對各種各樣的情形分情況進行處理,或篩選或降維或重新組合,選出一些重要的特征參與模型訓練,Pipeline與ColumnTransformer以及FeatureUnion結合能玩出什麼花樣呢?在訓練模型的時候有需要對超參進行調節標明,利用最佳的超參進行模型訓練,Pipeline與GridSearch結合能玩出什麼把戲呢?訓練評估好的模型,在後來的部署與線上預測時, Pipeline與pickle結合可以擦出什麼火花呢?首先,看Pipeline與ColumnTransformer結合。

Pipeline與ColumnTransformer

ColumnTransformer顧名思義就是對特征列進行變換,将其轉為模型可接受的形式,所有的特征列首先可以分為數值型列和類别型列兩類,針對數值型列,往往需要對缺失值用或常數或均值或其他統計量進行填充,處理了缺失值然後可能還需要進行歸一化處理;針對類别型列也需要缺失值填充,需要進行編碼處理等,類别型列又可以分少類别列和多類别列,為了防止類别多的列編碼時候次元爆炸需要用到OrdinaryEncoder編碼,類别少的列可以OnehotEncoder編碼,這樣,又可以把所有列一分為三。缺失值填充和編碼本是兩個獨立過程,是以可以用Pipeline将他們打包起來,按先後放入Pipeline的steps裡面,最後把這些處理都打包放入ColumnTransformer。

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import  OneHotEncoder, OrdinalEncoder
.........
.........
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, shuffle=True, random_state=0) #劃分訓練測試集
less_cat_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype=='object' and X_train[col_name].nunique()<10] #少類别型變量
more_cat_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype=='object' and X_train[col_name].nunique()>=10] #多類别型變量
num_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype in ['int64', 'float64']] #數值型特征

# print(less_cat_col, more_cat_col, num_col)

less_cat_transform = Pipeline(steps = [('imputer', SimpleImputer(strategy='most_frequent')),
                                    ('encoder', OneHotEncoder(handle_unknown='ignore'))]
                            ) #類别型變量先用衆數填充再獨熱編碼
more_cat_transform = Pipeline(steps = [('imputer', SimpleImputer(strategy='most_frequent')),
                                    ('encoder', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1))]
                            ) #類别型變量先用衆數填充再普通編碼

num_transform = SimpleImputer(strategy='mean') #數值型變量采用均值填充
preprocessor = ColumnTransformer(transformers = [('less_cat', less_cat_transform, less_cat_col),
                                            ('more_cat', more_cat_transform, more_cat_col),
                                        ('num', num_transform, num_col)]
                                ) #不同的預處理步驟打包到一起      

從上面的代碼可以看到我們對所有列分為三類less_cat_col,more_cat_col和num_col,針對這三類列,分别定義less_cat_transform ,more_cat_transform 和num_transform三種變換方式,少類别列less_cat_col前後分别采用最頻繁的值填充空值,然後采用OneHotEncoder編碼,多類别列more_cat_col先後分别采用最頻繁的值填充空值,然後采有OrdinalEncoder編碼,而數值型特征列按均值填充缺失值,最後将三類列和其對應的變換方式以三元組形式放入ColumnTransformer裡的transformers清單 ,這樣,該填充的填充了,該編碼的編碼了。

Pipeline與FeatureUnion

有時候我們會發現模型拟合的并不好,可能是因為目前使用的特征空間太單薄了,需要對特征列重構再組合,構造新的更加豐富的複合特征空間再參與模型訓練。

from sklearn.pipeline import FeatureUnion
from sklearn.decomposition import PCA,KernelPCA,TruncatedSVD

combined= FeatureUnion(transformer_list = [('linear_pca',PCA(n_components = 3)),
('kernel_pca',KernelPCA(n_components = 5)),
("svd", TruncatedSVD(n_components=2))])
combined_X = combined.fit_transform(X)      

從上面代碼可以看到FeatureUnion合并了一個名叫linear_pca,一個名叫kernel_pca,一個名叫svd一共3個轉換器,形成一個新的轉換器combined,然後将新的轉換器作用到原特征空間X上,相當于這3個轉換器分别獨立的作用在原來的特征空間X上,然後将他們的輸出進行合并,輸出的特征向量被橫向連接配接成更多元的特征向量,這裡由一個3維的特征向量,一個5維的特征向量和一個2維的特征向量進行合并成一個3+5+2=10維的特征向量,進而達到特征向量空間的擴維。

Pipeline與GridSearchCV

在訓練模型的時候往往需要對一些超參數進行調節設定,好的超參會讓模型事半功倍,超參的調節便成了很重要的一個環節,正因為Pipeline具有獨立連結性,可以與GridSearchCV結合來自動尋參。

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import  OneHotEncoder, OrdinalEncoder
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
.........
.........
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, shuffle=True, random_state=0) #劃分訓練測試集
less_cat_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype=='object' and X_train[col_name].nunique()<10] #少類别型變量
more_cat_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype=='object' and X_train[col_name].nunique()>=10] #多類别型變量
num_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype in ['int64', 'float64']] #數值型特征
    # print(less_cat_col, more_cat_col, num_col)

less_cat_transform = Pipeline(steps = [('imputer', SimpleImputer(strategy='most_frequent')),
                                    ('encoder', OneHotEncoder(handle_unknown='ignore'))]
                            ) #類别型變量先用衆數填充再獨熱編碼
more_cat_transform = Pipeline(steps = [('imputer', SimpleImputer(strategy='most_frequent')),
                                    ('encoder', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1))]
                            ) #類别型變量先用衆數填充再普通編碼

num_transform = SimpleImputer(strategy='mean') #數值型變量采用均值填充
preprocessor = ColumnTransformer(transformers = [('less_cat', less_cat_transform, less_cat_col),
                                            ('more_cat', more_cat_transform, more_cat_col),
                                        ('num', num_transform, num_col)]
                                ) #不同的預處理步驟打包到一起
 
model = GradientBoostingRegressor(random_state=0) # 
pipe = Pipeline(steps=[('preprocessing', preprocessor),
                    ('model', model)]
                )
params = {
        'model__n_estimators':[100, 200, 300],
        'model__learning_rate':[0.01, 0.05, 0.1],
        'model__max_depth': [3, 5, 7, 9,],
        'model__max_features':[5, 7, 11,  14],
        'model__min_samples_leaf': [1, 2, 3]
    }
gs = GridSearchCV(pipe, param_grid = params)
gs.fit(X_train, y_train)
print(gs.best_params_)
y_pred = gs.best_estimator_.predict(X_test)
MAE = mean_absolute_error(y_test, y_pred) #平均絕對誤差
score = gs.score(X_test, y_test)
print("平均絕對誤差和得分", MAE, score)      

從上面代碼可以看到,首先把預處理和模型放入一個Pipeline裡面,然後構造一個超參數網params ,這裡用到GradientBoostingRegressor模型,主要要對其中5個重要的超參進行設定,緊接着調用GridSearchCV,傳入超參數網,然後對模型進行訓練列印出模型最佳拟合的超參數清單如下

n_estimators : 300, learning_rate : 0.1, max_depth : 5, max_features : 14, min_samples_leaf : 3      

Pipeline與pickle

精心訓練好的模型如何永久儲存下來呢,比較簡單的就是對其進行序列化,儲存為本地的pickle檔案。

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import  OneHotEncoder, OrdinalEncoder
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error
import pickle
.........
.........
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, shuffle=True, random_state=0) #劃分訓練測試集
less_cat_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype=='object' and X_train[col_name].nunique()<10] #少類别型變量
more_cat_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype=='object' and X_train[col_name].nunique()>=10] #多類别型變量
num_col = [col_name for col_name in X_train.columns if X_train[col_name].dtype in ['int64', 'float64']] #數值型特征
    # print(less_cat_col, more_cat_col, num_col)

less_cat_transform = Pipeline(steps = [('imputer', SimpleImputer(strategy='most_frequent')),
                                    ('encoder', OneHotEncoder(handle_unknown='ignore'))]
                            ) #類别型變量先用衆數填充再獨熱編碼
more_cat_transform = Pipeline(steps = [('imputer', SimpleImputer(strategy='most_frequent')),
                                    ('encoder', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1))]
                            ) #類别型變量先用衆數填充再普通編碼

num_transform = SimpleImputer(strategy='mean') #數值型變量采用均值填充
preprocessor = ColumnTransformer(transformers = [('less_cat', less_cat_transform, less_cat_col),
                                            ('more_cat', more_cat_transform, more_cat_col),
                                        ('num', num_transform, num_col)]
                                ) #不同的預處理步驟打包到一起
model = GradientBoostingRegressor(n_estimators = 300, learning_rate = 0.1, max_depth = 5, min_samples_leaf= 3, random_state=0) # 模型初始化
pipe = Pipeline(steps=[('preprocessing', preprocessor),
                    ('model', model)]
                )
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
MAE = mean_absolute_error(y_test, y_pred) #平均絕對誤差
score = pipe.score(X_test, y_test)
print(pipe.named_steps['preprocessing']._feature_names_in)
print("mean_absolute_error: {}, and model score: {}".format(MAE, score))
with open(r'D:\項目\psg_melt_strategy.pickle', "wb") as model_file: #儲存模型
        pickle.dump(pipe, model_file)      

從上面的代碼可以看到,我們将訓練好的模型儲存為psg_melt_strategy.pickle,該序列化檔案不僅儲存了模型,而且還儲存了對應的特征工程處理,線上預測的時候隻需要将其加載起來進行預測即可,這段代碼可以作為模闆套到很多機器學習裡面去,囊括了從特征工程到模型的儲存,流水作業,行雲流水一氣呵成。

Pipeline的優缺點

  • 優點
  • 缺點

參考文獻

繼續閱讀