機器學習實戰_一個完整的程序(二)

自定義轉換器

儘管 Scikit-Learn 提供了許多有用的轉換器,你仍是須要本身動手寫轉換器執行任務,好比自定義的清理操做,或屬性組合。你須要讓自制的轉換器與 Scikit-Learn 組件(好比流水線)無縫銜接工做,由於 Scikit-Learn 是依賴鴨子類型的(而不是繼承),你所須要作的是建立一個類並執行三個方法:fit()(返回self),transform(),和fit_transform()經過添加TransformerMixin做爲基類,能夠很容易地獲得最後一個。另外,若是你添加BaseEstimator做爲基類(且構造器中避免使用args和kargs),你就能獲得兩個額外的方法(get_params()和set_params()),兩者能夠方便地進行超參數自動微調*。例如,一個小轉換器類添加了上面討論的屬性:node

# 添加一個特徵組合的裝換器
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6

# 這裏的示例沒有定義fit_transform(),多是由於fit()沒有作任何動做(我猜的
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]  # X[:,3]表示的是第4列全部數據
        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, # np.c_表示的是拼接數組。
                         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)    # 返回一個加入新特徵的數據

在這個例子中,轉換器有一個超參數add_bedrooms_per_room,默認設爲True(提供一個合理的默認值頗有幫助)。這個超參數可讓你方便地發現添加了這個屬性是否對機器學習算法有幫助。更通常地,你能夠爲每一個不能徹底確保的數據準備步驟添加一個超參數。數據準備步驟越自動化,能夠自動化的操做組合就越多,越容易發現更好用的組合(並能節省大量時間)。git

另外sklearn是不能直接處理DataFrames的,那麼咱們須要自定義一個處理的方法將之轉化爲numpy類型算法

class DataFrameSelector(BaseEstimator,TransformerMixin):
    def __init__(self,attribute_names): #能夠爲列表
        self.attribute_names = attribute_names
    def fit(self,X,y=None):
        return self
    def transform(self,X):
        return X[self.attribute_names].values #返回的爲numpy array

數據縮放

有兩種常見的方法可讓全部的屬性有相同的量度:線性函數歸一化(Min-Max scaling)和標準化(standardization)。Scikit-Learn 提供了一個轉換器MinMaxScaler來實現這個功能。它有一個超參數feature_range,可讓你改變範圍,若是不但願範圍是 0 到 1;Scikit-Learn 提供了一個轉換器StandardScaler來進行標準化bootstrap

min-max方式,對應的方法爲數組

MinMaxScaler(self, feature_range=(0, 1), copy=True)

standardization 標準化數據,對應的方法爲網絡

StandardScaler(self, copy=True, with_mean=True, with_std=True)

轉換流水線

目前在數據預處理階段,咱們須要對缺失值進行處理、特徵組合和特徵縮放。每一步的執行都有着前後順序,存在許多數據轉換步驟,須要按必定的順序執行。sklearn提供了Pipeline幫助順序完成轉換幸運的是,Scikit-Learn 提供了類Pipeline,來進行這一系列的轉換。下面是一個數值屬性的小流水線:dom

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構造器須要一個定義步驟順序的名字/估計器對的列表。除了最後一個估計器,其他都要是轉換器(即,它們都要有fit_transform()方法)。名字能夠隨意起。機器學習

當你調用流水線的fit()方法,就會對全部轉換器順序調用fit_transform()方法,將每次調用的輸出做爲參數傳遞給下一個調用,一直到最後一個估計器,它只執行fit()方法。函數

估計器(Estimator):不少時候能夠直接理解成分類器,主要包含兩個函數:fit()和predict()
裝換器(Transformer):轉換器用於數據預處理和數據轉換,主要是三個方法:fit(),transform()和fit_transform()工具

最後的估計器是一個StandardScaler,它是一個轉換器,所以這個流水線有一個transform()方法,能夠順序對數據作全部轉換(它還有一個fit_transform方法可使用,就沒必要先調用fit()再進行transform())。

num_attribs = list(housing_num) # 返回的爲列名[col1,col2,....]
cat_attribs = ["ocean_proximity"]

num_pipeline = Pipeline([ # 數值類型
        ('selector', DataFrameSelector(num_attribs)),
        ('imputer', Imputer(strategy="median")),
        ('attribs_adder', CombinedAttributesAdder()),
        ('std_scaler', StandardScaler()),
    ])

cat_pipeline = Pipeline([ # 標籤類型
        ('selector', DataFrameSelector(cat_attribs)),
        ('cat_encoder', CategoricalEncoder(encoding="onehot-dense")),
    ])

上面定義的爲分別處理數值類型和標籤類型的轉換流程,housing_num爲DataFrame類型,list(DataFrame)的結果返回的爲列名字。上面着兩個流程還能夠再整合一塊兒。

from sklearn.pipeline import FeatureUnion
full_pipeline = FeatureUnion(transformer_list=[
        ("num_pipeline", num_pipeline),
        ("cat_pipeline", cat_pipeline),
    ])
housing_prepared = full_pipeline.fit_transform(housing) # 最終的結果
>>> housing_prepared
array([[ 0.73225807, -0.67331551,  0.58426443, ...,  0.        ,
         0.        ,  0.        ],
       [-0.99102923,  1.63234656, -0.92655887, ...,  0.        ,
         0.        ,  0.        ],
       [...]
>>> housing_prepared.shape
(16513, 17)

每一個子流水線都以一個選擇轉換器開始:經過選擇對應的屬性(數值或分類)、丟棄其它的,來轉換數據,並將輸出DataFrame轉變成一個 NumPy 數組。Scikit-Learn 沒有工具來處理 PandasDataFrame,所以咱們須要寫一個簡單的自定義轉換器來作這項工做:

from sklearn.base import BaseEstimator, TransformerMixin

class DataFrameSelector(BaseEstimator, TransformerMixin):
    def __init__(self, attribute_names):
        self.attribute_names = attribute_names
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return X[self.attribute_names].values

選擇並訓練模型

咱們先來訓練一個線性迴歸模型:

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)    # 利用預處理好的數據進行訓練模型

完畢!你如今就有了一個可用的線性迴歸模型。用一些訓練集中的實例作下驗證:

>>> some_data = housing.iloc[:5]    # 前五個做爲預測數據
>>> some_labels = housing_labels.iloc[:5]
>>> some_data_prepared = full_pipeline.transform(some_data)
>>> print("Predictions:\t", lin_reg.predict(some_data_prepared))    # 預測結果
Predictions:     [ 303104.   44800.  308928.  294208.  368704.]
>>> print("Labels:\t\t", list(some_labels))
Labels:         [359400.0, 69700.0, 302100.0, 301300.0, 351900.0]    # 實際結果

行的通,儘管預測並不怎麼準確(好比,第二個預測偏離了 50%!)。讓咱們使用 Scikit-Learn 的mean_squared_error函數,用所有訓練集來計算下這個迴歸模型的 RMSE:

>>> from sklearn.metrics import mean_squared_error
>>> housing_predictions = lin_reg.predict(housing_prepared)
>>> lin_mse = mean_squared_error(housing_labels, housing_predictions)
>>> lin_rmse = np.sqrt(lin_mse)
>>> lin_rmse
68628.413493824875

OK,有總比沒有強,但顯然結果並很差,這是一個模型欠擬合訓練數據的例子。當這種狀況發生時,意味着特徵沒有提供足夠多的信息來作出一個好的預測,或者模型並不強大,修復欠擬合的主要方法是選擇一個更強大的模型,給訓練算法提供更好的特徵,或去掉模型上的限制,你能夠嘗試添加更多特徵(好比,人口的對數值),可是首先讓咱們嘗試一個更爲複雜的模型,看看效果。訓練一個決策樹模型DecisionTreeRegressor。這是一個強大的模型,能夠發現數據中複雜的非線性關係。

from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)
>>> housing_predictions = tree_reg.predict(housing_prepared)
>>> tree_mse = mean_squared_error(housing_labels, housing_predictions)
>>> tree_rmse = np.sqrt(tree_mse)
>>> tree_rmse
0.0

等一下,發生了什麼?沒有偏差?這個模型多是絕對完美的嗎?固然,更大可能性是這個模型嚴重過擬合數據。如何肯定呢?如前所述,直到你準備運行一個具有足夠信心的模型,都不要碰測試集,所以你須要使用訓練集的部分數據來作訓練,用一部分來作模型驗證。

用交叉驗證作更佳的評估

使用 Scikit-Learn 的交叉驗證功能。下面的代碼採用了 K 折交叉驗證(K-fold cross-validation):它隨機地將訓練集分紅十個不一樣的子集,成爲「折」,而後訓練評估決策樹模型 10 次,每次選一個不用的折來作評估,用其它 9 個來作訓練。結果是一個包含 10 個評分的數組:

from sklearn.model_selection import cross_val_score
scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
                         scoring="neg_mean_squared_error", cv=10)
rmse_scores = np.sqrt(-scores)
警告:Scikit-Learn 交叉驗證功能指望的是效用函數(越大越好)而不是損失函數(越低越好),所以得分函數實際上與 MSE 相反(即負值),這就是爲何前面的代碼在計算平方根以前先計算-scores。

來看下結果

>>> def display_scores(scores):
...     print("Scores:", scores)
...     print("Mean:", scores.mean())
...     print("Standard deviation:", scores.std())
...
>>> display_scores(tree_rmse_scores)
Scores: [ 74678.4916885   64766.2398337   69632.86942005  69166.67693232
          71486.76507766  73321.65695983  71860.04741226  71086.32691692
          76934.2726093   69060.93319262]
Mean: 71199.4280043
Standard deviation: 3202.70522793

如今決策樹就不像前面看起來那麼好了。實際上,它看起來比線性迴歸模型還糟!注意到交叉驗證不只可讓你獲得模型性能的評估,還能測量評估的準確性(即,它的標準差)。決策樹的評分大約是 71200,一般波動有 ±3200。若是隻有一個驗證集,就得不到這些信息。可是交叉驗證的代價是訓練了模型屢次,不可能老是這樣。

讓咱們計算下線性迴歸模型的的相同分數,以作確保:

>>> lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
...                              scoring="neg_mean_squared_error", cv=10)
...
>>> lin_rmse_scores = np.sqrt(-lin_scores)
>>> display_scores(lin_rmse_scores)
Scores: [ 70423.5893262   65804.84913139  66620.84314068  72510.11362141
          66414.74423281  71958.89083606  67624.90198297  67825.36117664
          72512.36533141  68028.11688067]
Mean: 68972.377566
Standard deviation: 2493.98819069

判斷沒錯:決策樹模型過擬合很嚴重,它的性能比線性迴歸模型還差

如今再嘗試最後一個模型:RandomForestRegressor(隨機森林),隨機森林是經過用特徵的隨機子集訓練許多決策樹。在其它多個模型之上創建模型成爲集成學習(Ensemble Learning),它是推動 ML 算法的一種好方法。咱們會跳過大部分的代碼,由於代碼本質上和其它模型同樣:

>>> from sklearn.ensemble import RandomForestRegressor
>>> forest_reg = RandomForestRegressor()
>>> forest_reg.fit(housing_prepared, housing_labels)
>>> [...]
>>> forest_rmse
22542.396440343684
>>> display_scores(forest_rmse_scores)
Scores: [ 53789.2879722   50256.19806622  52521.55342602  53237.44937943
          52428.82176158  55854.61222549  52158.02291609  50093.66125649
          53240.80406125  52761.50852822]
Mean: 52634.1919593
Standard deviation: 1576.20472269

如今好多了:隨機森林看起來頗有但願。可是,訓練集的評分仍然比驗證集的評分低不少。解決過擬合能夠經過簡化模型,給模型加限制(即,正則化),或用更多的訓練數據。在深刻隨機森林以前,你應該嘗試下機器學習算法的其它類型模型(不一樣核心的支持向量機,神經網絡,等等),不要在調節超參數上花費太多時間。目標是列出一個可能模型的列表(兩到五個)。

提示:你要保存每一個試驗過的模型,以便後續能夠再用。要確保有超參數和訓練參數,以及交叉驗證評分,和實際的預測值。這可讓你比較不一樣類型模型的評分,還能夠比較偏差種類。你能夠用 Python 的模塊pickle,很是方便地保存 Scikit-Learn 模型,或使用sklearn.externals.joblib,後者序列化大 NumPy 數組更有效率:
from sklearn.externals import joblib

joblib.dump(my_model, "my_model.pkl")
# 而後
my_model_loaded = joblib.load("my_model.pkl")

模型微調

網格搜索:使用 Scikit-Learn 的GridSearchCV來作這項搜索工做。你所須要作的是告訴GridSearchCV要試驗有哪些超參數,要試驗什麼值,GridSearchCV就能用交叉驗證試驗全部可能超參數值的組合。例如,下面的代碼搜索了RandomForestRegressor超參數值的最佳組合(很費時間):

from sklearn.model_selection import GridSearchCV

param_grid = [
    {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
    {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
  ]

forest_reg = RandomForestRegressor()

grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                           scoring='neg_mean_squared_error')

grid_search.fit(housing_prepared, housing_labels)
當你不能肯定超參數該有什麼值,一個簡單的方法是嘗試連續的 10 的冪(若是想要一個粒度更小的搜尋,能夠用更小的數,就像在這個例子中對超參數n_estimators作的)。

param_grid告訴 Scikit-Learn 首先評估全部的列在第一個dict中的n_estimators和max_features的3 × 4 = 12種組合(不用擔憂這些超參數的含義,會在第 7 章中解釋)。而後嘗試第二個dict中超參數的2 × 3 = 6種組合,此次會將超參數bootstrap設爲False而不是True(後者是該超參數的默認值)。完成後,你就能得到參數的最佳組合,以下所示:

>>> grid_search.best_params_
{'max_features': 6, 'n_estimators': 30}

你還能直接獲得最佳的估計器:

>>> grid_search.best_estimator_
RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
           max_features=6, max_leaf_nodes=None, min_samples_leaf=1,
           min_samples_split=2, min_weight_fraction_leaf=0.0,
           n_estimators=30, n_jobs=1, oob_score=False, random_state=None,
           verbose=0, warm_start=False)

固然,也能夠獲得評估得分:

>>> cvres = grid_search.cv_results_
... for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
...     print(np.sqrt(-mean_score), params)
...
64912.0351358 {'max_features': 2, 'n_estimators': 3}
55535.2786524 {'max_features': 2, 'n_estimators': 10}
52940.2696165 {'max_features': 2, 'n_estimators': 30}
60384.0908354 {'max_features': 4, 'n_estimators': 3}
52709.9199934 {'max_features': 4, 'n_estimators': 10}
50503.5985321 {'max_features': 4, 'n_estimators': 30}
59058.1153485 {'max_features': 6, 'n_estimators': 3}
52172.0292957 {'max_features': 6, 'n_estimators': 10}
49958.9555932 {'max_features': 6, 'n_estimators': 30}
59122.260006 {'max_features': 8, 'n_estimators': 3}
52441.5896087 {'max_features': 8, 'n_estimators': 10}
50041.4899416 {'max_features': 8, 'n_estimators': 30}
62371.1221202 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
54572.2557534 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
59634.0533132 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
52456.0883904 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
58825.665239 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
52012.9945396 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

在這個例子中,咱們經過設定超參數max_features爲 6,n_estimators爲 30,獲得了最佳方案。對這個組合,RMSE 的值是 49959,這比以前使用默認的超參數的值(52634)要稍微好一些。祝賀你,你成功地微調了最佳模型!

隨機搜索:當探索相對較少的組合時,就像前面的例子,網格搜索還能夠。可是當超參數的搜索空間很大時,最好使用RandomizedSearchCV。這個類的使用方法和類GridSearchCV很類似,但它不是嘗試全部可能的組合,而是經過選擇每一個超參數的一個隨機值的特定數量的隨機組合。這個方法有兩個優勢:

  • 若是你讓隨機搜索運行,好比 1000 次,它會探索每一個超參數的 1000 個不一樣的值(而不是像網格搜索那樣,只搜索每一個超參數的幾個值)
  • 你能夠方便地經過設定搜索次數,控制超參數搜索的計算量。

分析最佳模型和它們的偏差

經過分析最佳模型,經常能夠得到對問題更深的瞭解。好比,RandomForestRegressor能夠指出每一個屬性對於作出準確預測的相對重要性

>>> feature_importances = grid_search.best_estimator_.feature_importances_
>>> feature_importances
array([  7.14156423e-02,   6.76139189e-02,   4.44260894e-02,
         1.66308583e-02,   1.66076861e-02,   1.82402545e-02,
         1.63458761e-02,   3.26497987e-01,   6.04365775e-02,
         1.13055290e-01,   7.79324766e-02,   1.12166442e-02,
         1.53344918e-01,   8.41308969e-05,   2.68483884e-03,
         3.46681181e-03])

將重要性分數和屬性名放到一塊兒:

>>> extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
>>> cat_one_hot_attribs = list(encoder.classes_)
>>> attributes = num_attribs + extra_attribs + cat_one_hot_attribs
>>> sorted(zip(feature_importances,attributes), reverse=True)
[(0.32649798665134971, 'median_income'),
 (0.15334491760305854, 'INLAND'),
 (0.11305529021187399, 'pop_per_hhold'),
 (0.07793247662544775, 'bedrooms_per_room'),
 (0.071415642259275158, 'longitude'),
 (0.067613918945568688, 'latitude'),
 (0.060436577499703222, 'rooms_per_hhold'),
 (0.04442608939578685, 'housing_median_age'),
 (0.018240254462909437, 'population'),
 (0.01663085833886218, 'total_rooms'),
 (0.016607686091288865, 'total_bedrooms'),
 (0.016345876147580776, 'households'),
 (0.011216644219017424, '<1H OCEAN'),
 (0.0034668118081117387, 'NEAR OCEAN'),
 (0.0026848388432755429, 'NEAR BAY'),
 (8.4130896890070617e-05, 'ISLAND')]

有了這個信息,你就能夠丟棄一些不那麼重要的特徵(好比,顯然只要一個分類ocean_proximity就夠了,因此能夠丟棄掉其它的)。你還應該看一下系統犯的偏差,搞清爲何會有些偏差,以及如何改正問題(添加更多的特徵,或相反,去掉沒有什麼信息的特徵,清洗異常值等等)。

用測試集評估系統

調節完系統以後,你終於有了一個性能足夠好的系統。如今就能夠用測試集評估最後的模型了。這個過程沒有什麼特殊的:從測試集獲得預測值和標籤,運行full_pipeline轉換數據(調用transform(),而不是fit_transform()!),再用測試集評估最終模型:

final_model = grid_search.best_estimator_

X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()

X_test_prepared = full_pipeline.transform(X_test)

final_predictions = final_model.predict(X_test_prepared)

final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)   # => evaluates to 48,209.6

評估結果一般要比交叉驗證的效果差一點,若是你以前作過不少超參數微調(由於你的系統在驗證集上微調,獲得了不錯的性能,一般不會在未知的數據集上有一樣好的效果)。這個例子不屬於這種狀況,可是當發生這種狀況時,你必定要忍住不要調節超參數,使測試集的效果變好;這樣的提高不能推廣到新數據上。

相關文章
相關標籤/搜索