Scikit-Learn 與 TensorFlow 機器學習實用指南學習筆記 5 —— 如何為機器學習演算法準備資料?

紅色石頭發表於2019-01-02

本文為《Scikit-Learn 和 TensorFlow 機器學習指南》的第二章的第 3 講:為機器學習演算法準備資料。

  1. 使用實際資料
  2. 整體規劃

  3. 獲取資料

  4. 發現、視覺化資料,增加直觀印象

  5. 為機器學習準備資料

  6. 選擇模型並進行訓練

  7. 除錯模型

  8. 部署、監控、維護系統

第二章前 2 講的地址如下:

如何入手第一個機器學習專案?

如何從資料視覺化中發現資料規律?

筆記儘量突出重點,提煉關鍵知識點。正文開始!

資料清洗(處理缺失值)

對於資料集中出現缺失值的情況,需要對其進行處理。對缺失值常用的三種方法是:

  • 丟棄有缺失值的樣本
  • 丟棄有缺失值的整個特徵

  • 對缺失值進行填充(補零、均值填充或中位數填充等)

三種方法相應的程式碼如下:

housing.dropna(subset=["total_bedrooms"])    # option 1
housing.drop("total_bedrooms", axis=1)       # option 2
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median, inplace=True) # option 3

一般 option 3 應用更為廣泛。值得注意的是,應該保留訓練樣本的 median 值,測試樣本中的缺失值將以此 median 值進行填充。

在 Scikit-Learn 中提供了 Imputer 類,進行缺失值處理。示例程式碼如下:

from sklearn.preprocessing import Imputer
imputer = Imputer(strategy="median")
housing_num = housing.drop('ocean_proximity', axis=1)
imputer.fit(housing_num)
X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X, columns=housing_num.columns)

處理文字或類別屬性

本章的波士頓房價問題中,ocean_proximity 屬性是非數值的字元屬性,因此無法進行中位數填充。該屬性如下所示:

[‘<1H OCEAN’ ‘INLAND’ ‘ISLAND’ ‘NEAR BAY’ ‘NEAR OCEAN’]

你可以直接使用下面程式碼,將字元屬性轉換成數值屬性:

from sklearn.preprocessing import OrdinalEncoder
housing_cat = housing[['ocean_proximity']]
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10]
housing_cat_encoded[:10]

array([[ 0.],
[ 0.],
[ 4.],
[ 1.],
[ 0.],
[ 1.],
[ 0.],
[ 1.],
[ 0.],
[ 0.]]) [ 0.]])

更方便地,還可以直接將字元屬性轉換為 one-hot 編碼:

from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder(sparse=False)
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot

array([[ 1., 0., 0., 0., 0.],
[ 1., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 1.],
…,
[ 0., 1., 0., 0., 0.],
[ 1., 0., 0., 0., 0.],
[ 0., 0., 0., 1., 0.]])

自定義轉換器

雖然 Scikit-Learn 已經提供了許多有用的轉換器,但是你仍然可以編寫自己的轉換器,例如特定屬性組合。自定義轉換器很簡單,只需要建立一個類,然後實現以下三個方法:fit()(返回自身)、transform()、fit_transform()。如果新增 TransformerMixin 作為基類,就可以直接得到最後一個方法。同時,如果新增 BaseEstimator 作為基類(並在建構函式中避免 * args 和 ** kargs),你還能額外獲得兩個非常有用的自動調整超引數的方法 get_params()和 set_params()。

下面是自定義轉換器,新增組合屬性的例子:

from sklearn.base import BaseEstimator, TransformerMixin

# column index
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
   def __init__(self, add_bedrooms_per_room = True): # no *args or **kargs
       self.add_bedrooms_per_room = add_bedrooms_per_room
   def fit(self, X, y=None):
       return self  # nothing else to do
   def transform(self, X, y=None):
       rooms_per_household = X[:, rooms_ix] / X[:, household_ix]
       population_per_household = X[:, population_ix] / X[:, household_ix]
       if self.add_bedrooms_per_room:
           bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
           return np.c_[X, rooms_per_household, population_per_household,
                        bedrooms_per_room]
       else:
           return np.c_[X, rooms_per_household, population_per_household]

attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)

特徵縮放

不同的特徵屬性範圍不一,容易給訓練造成困難,增加訓練時間。因此,一般會對不同特徵進行同尺度縮放。常用的兩種方式是歸一化和標準化。

歸一化很簡單:將值重新縮放於 0 到 1 之間。實現方法是將值減去最小值併除以最大值和最小值的差。對此,Scikit-Learn 提供了一個名為 MinMaxScaler 的轉換器。如果希望範圍不是 0~1,可以透過調整超引數 feature_range 進行更改。

標準化的做法是首先減去平均值(所以標準化值的均值總是零),然後除以方差。不同於歸一化,標準化不將值繫結到特定範圍,對某些演算法而言,這可能是個問題(例如,神經網路期望的輸入值範圍通常是0到1)。但是標準化的方法受異常值的影響更小。Scikit-Learn 提供了一個標準化的轉換器 StandadScaler。

管道 Pipeline

我們可以把機器學習演算法中許多轉換操作使用管道 pipeline 統一順序進行。Scikit-Learn 正好提供了 Pipeline 來支援這樣的轉換。下面是一個數值屬性的流水線例子:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([
       ('imputer', Imputer(strategy="median")),
       ('attribs_adder', CombinedAttributesAdder()),
       ('std_scaler', StandardScaler()),
   ])

housing_num_tr = num_pipeline.fit_transform(housing_num)

以上是數值型的 Pipeline 處理過程。對於非數值型的字元屬性,可以建立一個新的完整的 Pipeline,將上面的 num_pipeline 和字元屬性的轉換整合到一個 Pipeline 中,如下所示:

from sklearn.compose import ColumnTransformer

num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

full_pipeline = ColumnTransformer([
       ("num", num_pipeline, num_attribs),
       ("cat", OneHotEncoder(), cat_attribs),
   ])

housing_prepared = full_pipeline.fit_transform(housing)

相關文章