(第一部分 機器學習基礎(chǔ))
第01章 機器學習概覽
第02章 一個完整的機器學習項目(上)
第02章 一個完整的機器學習項目(下)
第03章 分類
第04章 訓練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學習和隨機森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學習)
第9章 啟動和運行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強化學習(上)
第16章 強化學習(下)
數(shù)據(jù)清洗
大多機器學習算法不能處理特征丟失副瀑,因此先創(chuàng)建一些函數(shù)來處理特征丟失的問題。前面跷叉,你應(yīng)該注意到了屬性total_bedrooms有一些缺失值忌警。有三個解決選項:
去掉對應(yīng)的分區(qū);
去掉整個屬性掺炭;
進行賦值(0辫诅、平均值、中位數(shù)等等)涧狮。
用DataFrame的dropna()
炕矮, drop()
,和 fillna()
方法者冤,可以方便地實現(xiàn):
housing.dropna(subset=["total_bedrooms"]) # 選項1
housing.drop("total_bedrooms", axis=1) # 選項2
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median) # 選項3
如果選擇選項3肤视,你需要計算訓練集的中位數(shù)吁讨,用中位數(shù)填充訓練集的缺失值滔悉,不要忘記保存該中位數(shù)亏吝。后面用測試集評估系統(tǒng)時芒珠,需要替換測試集中的缺失值争舞,也可以用來實時替換新數(shù)據(jù)中的缺失值裁眯。
Scikit-Learn提供了一個方便的類來處理缺失值:Imputer
豆胸。下面是其使用方法:首先常柄,需要創(chuàng)建一個Imputer
實例衬廷,指定用該屬性的中位數(shù)替換它的每個缺失值:
from sklearn.preprocessing import Imputer
imputer = Imputer(strategy="median")
因為只有數(shù)值屬性才能算出中位數(shù)摇予,我們需要創(chuàng)建一份不包括文本屬性ocean_proximity的數(shù)據(jù)副本:
housing_num = housing.drop("ocean_proximity", axis=1)
現(xiàn)在,就可以用fit()
方法將imputer
實例擬合到訓練數(shù)據(jù):
imputer.fit(housing_num)
imputer
計算出了每個屬性的中位數(shù)吗跋,并將結(jié)果保存在了實例變量statistics_
中侧戴。只有屬性total_bedrooms
有缺失值,但是我們要確保一旦系統(tǒng)運行起來跌宛,新的數(shù)據(jù)中沒有缺失值酗宋,所以安全的做法是將imputer
應(yīng)用到每個數(shù)值:
>>> imputer.statistics_
array([ -118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])
>>> housing_num.median().values
array([ -118.51 , 34.26 , 29. , 2119. , 433. , 1164. , 408. , 3.5414])
現(xiàn)在,你就可以使用這個“訓練過的”imputer
來對訓練集進行轉(zhuǎn)換秩冈,通過將缺失值替換為中位數(shù):
X = imputer.transform(housing_num)
結(jié)果是一個普通的Numpy數(shù)組本缠,包含有轉(zhuǎn)換后的特征。如果你想將其放回到Pandas DataFrame中入问,也很簡單:
housing_tr = pd.DataFrame(X, columns=housing_num.columns)
Scikit-Learn設(shè)計
Scikit-Learn設(shè)計的API設(shè)計的非常好丹锹。它的主要設(shè)計原則是:
一致性:所有對象的接口一致且簡單:
估計量(estimator)稀颁。任何可以基于數(shù)據(jù)集而對一些參數(shù)進行估計的對象都被設(shè)計成估計量(比如,imputer
就是個估計量)楣黍。估計本身是通過fit()
方法匾灶,只需要一個數(shù)據(jù)集作為參數(shù)(對于監(jiān)督學習算法,需要兩個數(shù)據(jù)集租漂;第二個數(shù)據(jù)集包含標簽)阶女。任何其它用來指導估計過程的參數(shù)都被當做超參數(shù)(比如imputer
的strategy
),并且超參數(shù)要被設(shè)置成實例變量(通常是通過構(gòu)造器參數(shù))哩治。
轉(zhuǎn)換量(transformer)秃踩。一些估計量(比如imputer
)也可以轉(zhuǎn)換數(shù)據(jù)集,這些估計量被稱為轉(zhuǎn)換量业筏。API也是相當簡單:轉(zhuǎn)換是通過transform()
方法憔杨,被轉(zhuǎn)換的數(shù)據(jù)集作為參數(shù)。返回的是經(jīng)過轉(zhuǎn)換的數(shù)據(jù)集蒜胖。轉(zhuǎn)換過程依賴學習到的參數(shù)消别,比如imputer
的例子。所有的轉(zhuǎn)換都有一個便捷的方法fit_transform()
台谢,等同于調(diào)用fit()
再transform()
(但有時fit_transform()
經(jīng)過優(yōu)化寻狂,運行的更快)。
預測量(predictor)朋沮。最后蛇券,一些估計量可以根據(jù)給出的數(shù)據(jù)集做預測,這些估計量稱為預測量樊拓。例如怀读,上一章的LinearRegression
模型就是一個預測量:它根據(jù)一個國家的人均GDP預測生活滿意度。預測量有一個predict()
方法骑脱,可以用新實例的數(shù)據(jù)集做出相應(yīng)的預測。預測量還有一個score()
方法苍糠,可以根據(jù)測試集(和相應(yīng)的標簽叁丧,如果是監(jiān)督學習算法的話)對預測進行衡量。可檢驗岳瞭。所有估計量的超參數(shù)都可以通過公共實例變量直接訪問(比如拥娄,
imputer.strategy
),并且所有估計量學習到的參數(shù)也可以通過公共實例變量添加下劃線后綴訪問(比如瞳筏,imputer.statistics_
)稚瘾。類不可擴散。數(shù)據(jù)集被表示成NumPy數(shù)組或SciPy稀疏矩陣姚炕,而不是自制的類摊欠。超參數(shù)只是普通的Python字符串或數(shù)字丢烘。
可組合。盡可能使用現(xiàn)存的模塊些椒。例如播瞳,用任意的轉(zhuǎn)換量序列加上一個估計量,就可以做成一個
Pipeline
免糕,后面會看到例子赢乓。合理的默認值。Scikit-Learn給大多數(shù)參數(shù)提供了合理的默認值石窑,很容易就能創(chuàng)建一個系統(tǒng)牌芋。
處理文本和分類屬性
前面,我們丟棄了分類屬性ocean_proximity
松逊,因為它是一個文本屬性躺屁,不能計算出中位數(shù)。大多數(shù)機器學習算法更喜歡和數(shù)字打交道棺棵,所以讓我們把這些文本標簽轉(zhuǎn)換為數(shù)字楼咳。
Scikit-Learn為這個任務(wù)提供了一個轉(zhuǎn)換量LabelEncoder
:
>>> from sklearn.preprocessing import LabelEncoder
>>> encoder = LabelEncoder()
>>> housing_cat = housing["ocean_proximity"]
>>> housing_cat_encoded = encoder.fit_transform(housing_cat)
>>> housing_cat_encoded
array([1, 1, 4, ..., 1, 0, 3])
好了一些,現(xiàn)在就可以在任何ML算法里用這個數(shù)值數(shù)據(jù)了烛恤。你可以查看映射表母怜,編碼器是通過屬性classes_
來學習的(“<1H OCEAN”被映射為0,“INLAND”被映射為1缚柏,等等):
>>> print(encoder.classes_)
['<1H OCEAN' 'INLAND' 'ISLAND' 'NEAR BAY' 'NEAR OCEAN']
這種做法的問題是苹熏,ML算法會認為兩個臨近的值比兩個疏遠的值要更相似。顯然這樣不對(比如币喧,分類0和4比0和1更相似)轨域。要解決這個問題,一個常見的方法是給每個分類創(chuàng)建一個二元屬性:當分類是“<1H OCEAN”杀餐,該屬性為1(否則為0)干发,當分類是“INLAND”,另一個屬性等于1(否則為0)史翘,以此類推枉长。這稱作獨熱編碼(One-Hot Encoding),因為只有一個屬性會等于1(熱)琼讽,其余會是0(冷)必峰。
Scikit-Learn提供了一個編碼器OneHotEncoder
,用于將整書分類值轉(zhuǎn)變?yōu)楠殶崾噶孔甑拧W⒁?code>fit_transform()用于2D數(shù)組吼蚁,而housing_cat_encoded
是一個1D數(shù)組,所以需要將其變形:
>>> from sklearn.preprocessing import OneHotEncoder
>>> encoder = OneHotEncoder()
>>> housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
>>> housing_cat_1hot
<16513x5 sparse matrix of type '<class 'numpy.float64'>'
with 16513 stored elements in Compressed Sparse Row format>
注意輸出結(jié)果是一個SciPy稀疏矩陣问欠,而不是NumPy數(shù)組肝匆。當分類屬性有數(shù)千個分類時粒蜈,這樣非常有用。經(jīng)過獨熱編碼术唬,我們得到了一個有數(shù)千列的矩陣薪伏,這個矩陣每行只有一個1,其余都是0粗仓。使用大量內(nèi)存來存儲這些0非常浪費嫁怀,所以稀疏矩陣只存儲非零元素的位置。你可以像一個2D數(shù)據(jù)那樣進行使用借浊,但是如果你真的想將其轉(zhuǎn)變成一個(密集的)NumPy數(shù)組塘淑,只需調(diào)用toarray()
方法:
>>> housing_cat_1hot.toarray()
array([[ 0., 1., 0., 0., 0.],
[ 0., 1., 0., 0., 0.],
[ 0., 0., 0., 0., 1.],
...,
[ 0., 1., 0., 0., 0.],
[ 1., 0., 0., 0., 0.],
[ 0., 0., 0., 1., 0.]])
使用類LabelBinarizer
,我們可以用一步執(zhí)行這兩個轉(zhuǎn)換(從文本分類到整數(shù)分類蚂斤,再從整數(shù)分類到獨熱矢量):
>>> from sklearn.preprocessing import LabelBinarizer
>>> encoder = LabelBinarizer()
>>> housing_cat_1hot = encoder.fit_transform(housing_cat)
>>> housing_cat_1hot
array([[0, 1, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 1],
...,
[0, 1, 0, 0, 0],
[1, 0, 0, 0, 0],
[0, 0, 0, 1, 0]])
注意默認返回的結(jié)果是一個密集NumPy數(shù)組存捺。向構(gòu)造器LabelBinarizer
傳遞sparse_output=True
,就可以得到一個稀疏矩陣曙蒸。
自定義轉(zhuǎn)換量
盡管Scikit-Learn提供了許多有用的轉(zhuǎn)換量捌治,你還是需要自己動手寫轉(zhuǎn)換量執(zhí)行任務(wù),比如自定義的清理操作纽窟,或?qū)傩越M合肖油。你需要讓自制的轉(zhuǎn)換量與Scikit-Learn組件(比如pipeline)無縫銜接工作,因為Scikit-Learn是依賴鴨子類型的(而不是繼承)臂港,你所需要做的是創(chuàng)建一個類并執(zhí)行三個方法:fit()
(返回self
)森枪,transform()
,和fit_transform()
审孽。通過添加TransformerMixin
作為基類县袱,可以很容易地得到最后一個。另外佑力,如果你添加BaseEstimator
作為基類(且構(gòu)造器中避免使用*args
和**kargs
)式散,你就能得到兩個額外的方法(get_params()
和set_params()
),二者可以方便地進行超參數(shù)自動微調(diào)打颤。例如杂数,一個小轉(zhuǎn)換量類添加了上面討論的屬性:
from sklearn.base import BaseEstimator, TransformerMixin
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)
在這個例子中,轉(zhuǎn)換量有一個超參數(shù)add_bedrooms_per_room
瘸洛,默認設(shè)為True
(提供一個合理的默認值很有幫助)。這個超參數(shù)可以讓你方便地發(fā)現(xiàn)添加了這個屬性是否對機器學習算法有幫助次和。更一般地反肋,你可以為每個不能完全確保的數(shù)據(jù)準備步驟添加一個超參數(shù)。數(shù)據(jù)準備步驟越自動化踏施,可以自動化的操作組合就越多石蔗,越容易發(fā)現(xiàn)更好用的組合(并能節(jié)省大量時間)罕邀。
特征縮放
數(shù)據(jù)要做的最重要的轉(zhuǎn)換之一是特征縮放。除了個別情況养距,當輸入的數(shù)值屬性量度不同時诉探,機器學習算法的性能都不會好。這個規(guī)律也適用于房產(chǎn)數(shù)據(jù):總房間數(shù)分布范圍是6到39320棍厌,而收入中位數(shù)只分布在0到15肾胯。不需要對目標值進行縮放。
有兩種常見的方法可以讓所有的屬性有相同的量度:線性函數(shù)歸一化(Min-Max scaling)和標準化(standardization)耘纱。
線性函數(shù)歸一化(許多人稱其為歸一化(normalization))很簡單:值被轉(zhuǎn)變敬肚、重新縮放,直到范圍變成0到1束析。我們通過減去最小值艳馒,然后再除以最大值與最小值的差值,來進行歸一化员寇。Scikit-Learn提供了一個轉(zhuǎn)換量MinMaxScaler
來實現(xiàn)這個功能弄慰。它有一個超參數(shù)feature_range
,可以讓你改變范圍蝶锋,如果不希望范圍是0到1陆爽。
標準化就很不同:首先減去平均值(所以標準化值的平均值總是0),然后除以方差牲览,使得到的分布具有單位方差墓陈。與歸一化不同,標準化不會限定值到某個特定的范圍第献,這對某些算法可能構(gòu)成問題(比如贡必,神經(jīng)網(wǎng)絡(luò)常需要輸入值得范圍是0到1)。但是庸毫,標準化受到異常值的影響很小仔拟。例如,假設(shè)一個分區(qū)的收入中位數(shù)是100飒赃。歸一化會將其它范圍是0到15的值變?yōu)?-0.15利花,但是標準化不會受什么影響。Scikit-Learn提供了一個轉(zhuǎn)換量StandardScaler
來進行標準化载佳。
警告:與所有的轉(zhuǎn)換一樣炒事,縮放器只能向訓練集擬合,而不是向完整的數(shù)據(jù)集(包括測試集)蔫慧。只有這樣挠乳,才能用縮放器轉(zhuǎn)換訓練集和測試集(和新數(shù)據(jù))。
轉(zhuǎn)換Pipeline
你已經(jīng)看到,存在許多數(shù)據(jù)轉(zhuǎn)換步驟睡扬,需要按一定的順序執(zhí)行盟蚣。幸運的是,Scikit-Learn提供了類Pipeline
卖怜,來進行這一系列的轉(zhuǎn)換屎开。下面是一個數(shù)值屬性的小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
構(gòu)造器需要一個定義步驟順序的名字/估計量對的列表。除了最后一個估計量马靠,其余都要是轉(zhuǎn)換量(即奄抽,它們都要有fit_transform()
方法)。名字可以隨意起虑粥。
當你調(diào)用pipeline的fit()
方法如孝,就會對所有轉(zhuǎn)換量順序調(diào)用fit_transform()
方法,將每次調(diào)用的輸出作為參數(shù)傳遞給下一個調(diào)用娩贷,一直到最后一個評估量第晰,它只執(zhí)行fit()
方法。
pipeline暴露相同的方法作為最終的評估量彬祖。在這個例子中茁瘦,最后的評估量是一個StandardScaler
,它是一個轉(zhuǎn)換量储笑,因此這個pipeline有一個transform()
方法甜熔,可以順序?qū)?shù)據(jù)做所有轉(zhuǎn)換(它還有一個fit_transform
方法可以使用,就不必先調(diào)用fit()
再進行transform()
)突倍。
你現(xiàn)在就有了一個對數(shù)值的pipeline腔稀,你還需要對分類值應(yīng)用LabelBinarizer
:如何將這些轉(zhuǎn)換寫成一個pipeline呢?Scikit-Learn提供了一個類FeatureUnion
實現(xiàn)這個功能羽历。你給它一列轉(zhuǎn)換量(可以是所有的轉(zhuǎn)換量)焊虏,當調(diào)用它的transform()
方法,每個轉(zhuǎn)換量的transform()
會被并行執(zhí)行秕磷,等待輸出诵闭,然后將輸出合并起來,并返回結(jié)果(當然澎嚣,調(diào)用它的fit()
方法就會調(diào)用每個轉(zhuǎn)換量的fit()
)疏尿。一個完整的處理數(shù)值和分類屬性的pipeline如下所示:
from sklearn.pipeline import FeatureUnion
num_attribs = list(housing_num)
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)),
('label_binarizer', LabelBinarizer()),
])
full_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline),
])
你可以很簡單地運行整個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)
每個子pipeline都以一個選擇轉(zhuǎn)換量開始:通過選擇對應(yīng)的屬性(數(shù)值或分類)、丟棄其它的易桃,來轉(zhuǎn)換數(shù)據(jù)褥琐,并將輸出DataFrame轉(zhuǎn)變成一個NumPy數(shù)組。Scikit-Learn沒有工具來處理Pandas DataFrame晤郑,因此我們需要寫一個簡單的自定義轉(zhuǎn)換量來做這項工作:
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
選擇并訓練模型
可到這一步了踩衩!你在前面限定了問題嚼鹉、獲得了數(shù)據(jù)、探索了數(shù)據(jù)驱富、采樣了一個測試集、寫了自動化的轉(zhuǎn)換pipeline來清理和為算法準備數(shù)據(jù)∑ノ瑁現(xiàn)在褐鸥,你已經(jīng)準備好選擇并訓練一個機器學習模型了。
在訓練集上訓練和評估
好消息是基于前面的工作赐稽,接下來要做的比你想的要簡單許多叫榕。像前一章那樣,我們先來訓練一個線性回歸模型:
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
完畢姊舵!你現(xiàn)在就有了一個可用的線性回歸模型晰绎。用一些訓練集中的實例做下驗證:
>>> 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
函數(shù)史飞,用全部訓練集來計算下這個回歸模型的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
okay尖昏,有總比沒有強,但顯然結(jié)果并不好:大多數(shù)分區(qū)的median_housing_values
位于120000美元到265000美元之間构资,因此預測誤差$68628不能讓人滿意抽诉。這是一個模型欠擬合訓練數(shù)據(jù)的例子。當這種情況發(fā)生時吐绵,意味著特征沒有提供足夠多的信息來做出一個好的預測迹淌,或者模型并不強大。就像前一章看到的己单,修復欠擬合的主要方法是選擇一個更強大的模型唉窃,給訓練算法提供更好的特征,或去掉模型上的限制荷鼠。這個模型還沒有正則化句携,所以排除了最后一個選項。你可以嘗試添加更多特征(比如允乐,人口的對數(shù)值)矮嫉,但是首先讓我們嘗試一個更為復雜的模型,看看效果牍疏。
來訓練一個DecisionTreeRegressor
蠢笋。這是一個強大的模型,可以發(fā)現(xiàn)數(shù)據(jù)中復雜的非線性關(guān)系(決策樹會在第6章詳細講解)鳞陨。代碼看起來很熟悉:
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared, housing_labels)
現(xiàn)在模型就訓練好了昨寞,用訓練集評估下:
>>> 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
等一下瞻惋,發(fā)生了什么?沒有誤差援岩?這個模型可能是絕對完美的嗎歼狼?當然,更大可能性是這個模型嚴重過擬合數(shù)據(jù)享怀。如何確定呢羽峰?如前所述,直到你準備運行一個具備足夠信心的模型添瓷,都不要碰測試集梅屉,因此你需要使用訓練集的部分數(shù)據(jù)來做訓練,用一部分來做模型驗證鳞贷。
使用交叉驗證做更佳的評估
評估決策樹模型的一種方法是用函數(shù)train_test_split
來分割訓練集坯汤,得到一個更小的訓練集和一個驗證集,然后用更小的訓練集來訓練模型搀愧,用驗證集來評估惰聂。這需要一定工作量,并不難而且也可行妈橄。
另一種更好的方法是使用Scikit-Learn的交叉驗證功能庶近。下面的代碼采用了K折交叉驗證(K-fold cross-validation):它隨機地將訓練集分成十個不同的子集,成為“折”眷蚓,然后訓練評估決策樹模型10次鼻种,每次選一個不用的折來做評估,用其它9個來做訓練沙热。結(jié)果是一個包含10個評分的數(shù)組:
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交叉驗證功能期望的是效用函數(shù)(越大越好)而不是成本函數(shù)(越低越好)叉钥,因此得分函數(shù)實際上與MSE相反(即負值),這就是為什么前面的代碼在計算平方根之前先計算
-scores
篙贸。
來看下結(jié)果:
>>> 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
現(xiàn)在決策樹就不像前面看起來那么好了投队。實際上,它看起來比線性回歸模型還糟爵川!注意到交叉驗證不僅可以讓你得到模型性能的評估敷鸦,還能測量評估的準確性(即,它的標準差)寝贡。決策樹的評分大約是71200扒披,通常波動有±3200。如果只有一個驗證集圃泡,就得不到這些信息碟案。但是交叉驗證的代價是訓練了模型多次,不可能總是這樣颇蜡。
讓我們計算下線性回歸模型的的相同分數(shù)价说,以做確保:
>>> 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
判斷沒錯:決策樹模型過擬合很嚴重辆亏,它的性能比線性回歸模型還差。
現(xiàn)在再嘗試最后一個模型:RandomForestRegressor
鳖目。第7章我們會看到扮叨,隨機森林是通過用特征的隨機子集訓練許多決策樹。在其它多個模型之上建立模型成為集成學習(Ensemble Learning)领迈,它是推進ML算法的一種好方法甫匹。我們會跳過大部分的代碼,因為代碼本質(zhì)上和其它模型一樣:
>>> 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
現(xiàn)在好多了:隨機森林看起來很有希望惦费。但是,訓練集的評分仍然比驗證集的評分低很多抢韭。解決過擬合可以通過簡化模型薪贫,給模型加限制(即,規(guī)整化)刻恭,或用更多的訓練數(shù)據(jù)瞧省。在深入隨機森林之前,你應(yīng)該嘗試下機器學習算法的其它類型模型(不同核心的支持向量機鳍贾,神經(jīng)網(wǎng)絡(luò)鞍匾,等等),不要在調(diào)節(jié)超參數(shù)上花費太多時間骑科。目標是列出一個可能模型的列表(兩到五個)橡淑。
提示:你要保存每個試驗過的模型,以便后續(xù)可以再用咆爽。要確保有超參數(shù)和訓練參數(shù)梁棠,以及交叉驗證評分,和實際的預測值斗埂。這可以讓你比較不同類型模型的評分符糊,還可以比較誤差種類。你可以用Python的模塊
pickle
呛凶,非常方便地保存Scikit-Learn模型男娄,或使用sklearn.externals.joblib
,后者序列化大NumPy數(shù)組更有效率:from sklearn.externals import joblib joblib.dump(my_model, "my_model.pkl") # 然后 my_model_loaded = joblib.load("my_model.pkl")
模型微調(diào)
假設(shè)你現(xiàn)在有了一個列表漾稀,列表里有幾個有希望的模型模闲。你現(xiàn)在需要對它們進行微調(diào)。讓我們來看幾種微調(diào)的方法县好。
網(wǎng)格搜索
微調(diào)的一種方法是手工調(diào)整超參數(shù)围橡,直到找到一個好的超參數(shù)組合。這么做的話會非常冗長缕贡,你也可能沒有時間探索多種組合翁授。
你應(yīng)該使用Scikit-Learn的GridSearchCV
來做這項搜索工作拣播。你所需要做的是告訴GridSearchCV
要試驗有哪些超參數(shù),要試驗什么值收擦,GridSearchCV
就能用交叉驗證試驗所有可能超參數(shù)值的組合贮配。例如,下面的代碼搜索了RandomForestRegressor
超參數(shù)值的最佳組合:
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)
當你不能確定超參數(shù)該有什么值塞赂,一個簡單的方法是嘗試連續(xù)的10的次方(如果想要一個粒度更小的搜尋泪勒,可以用更小的數(shù),就像在這個例子中對超參數(shù)
n_estimators
做的)宴猾。
param_grid
告訴Scikit-Learn首先評估所有的列在第一個dict
中的n_estimators
和max_features
的3 × 4 = 12種組合(不用擔心這些超參數(shù)的含義圆存,會在第7章中解釋)。然后嘗試第二個dict
中超參數(shù)的2 × 3 = 6種組合仇哆,這次會將超參數(shù)bootstrap
設(shè)為False
而不是True
(后者是該超參數(shù)的默認值)沦辙。
總之,網(wǎng)格搜索會探索12 + 6 = 18種RandomForestRegressor
的超參數(shù)組合讹剔,會訓練每個模型五次(因為用的是五折交叉驗證)油讯。換句話說,訓練總共有18 × 5 = 90輪延欠!折將要花費大量時間陌兑,完成后,你就能獲得參數(shù)的最佳組合由捎,如下所示:
>>> grid_search.best_params_
{'max_features': 6, 'n_estimators': 30}
提示:因為30是
n_estimators
的最大值兔综,你也應(yīng)該估計更高的值,因為這個值會持續(xù)提升隅俘。
你還能直接得到最佳的估計量:
>>> 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)
注意:如果
GridSearchCV
是以(默認值)refit=True
開始運行的邻奠,則一旦用交叉驗證找到了最佳的估計量,就會在整個訓練集上重新訓練为居。這是一個好方法碌宴,因為用更多數(shù)據(jù)訓練會提高性能。
當然蒙畴,也可以得到評估值:
>>> 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}
在這個例子中贰镣,我們通過設(shè)定超參數(shù)max_features
為6,n_estimators
為30膳凝,得到了最佳方案碑隆。對這個組合,RMSE的值是49959蹬音,這比之前使用默認的超參數(shù)的值(52634)要稍微好一些上煤。祝賀你,你成功地微調(diào)了最佳模型著淆!
提示:不要忘記劫狠,你可以像超參數(shù)一樣處理數(shù)據(jù)準備的步驟拴疤。例如,網(wǎng)格搜索可以自動判斷是否添加一個你不確定的特征(比如独泞,使用轉(zhuǎn)換量
CombinedAttributesAdder
的超參數(shù)add_bedrooms_per_room
)呐矾。它還能用相似的方法來自動找到處理異常值、缺失特征懦砂、特征選擇等任務(wù)的最佳方法蜒犯。
隨機搜索
當探索相對較少的組合時,就像前面的例子荞膘,網(wǎng)格搜索還可以罚随。但是當超參數(shù)的搜索空間很大時,最好使用RandomizedSearchCV
羽资。這個類的使用方法和類GridSearchCV
很相似毫炉,但它不是嘗試所有可能的組合,而是通過選擇每個超參數(shù)的一個隨機值的特定數(shù)量的隨機組合削罩。這個方法有兩個優(yōu)點:
如果你讓隨機搜索運行,比如1000次费奸,它會探索每個超參數(shù)的1000個不同的值(而不是像網(wǎng)格搜索那樣弥激,只搜索每個超參數(shù)的幾個值)。
你可以方便地通過設(shè)定搜索次數(shù)愿阐,控制超參數(shù)搜索的計算量微服。
集成方法
另一種微調(diào)系統(tǒng)的方法是將表現(xiàn)最好的模型組合起來。組合(集成)之后的性能通常要比單獨的模型要好(就像隨機森林要比單獨的決策樹要好)绢涡,特別是當單獨模型的誤差類型不同時掌动。我們會在第7章更深入地講解這點梯码。
分析最佳模型和它們的誤差
通過分析最佳模型,常炒园梗可以獲得對問題更深的了解。比如魄缚,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])
將重要性分數(shù)和屬性名放到一起:
>>> 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
就夠了冶匹,所以可以丟棄掉其它的)习劫。
你還應(yīng)該看一下系統(tǒng)犯的誤差,搞清為什么會有些誤差嚼隘,以及如何改正問題(添加更多的特征诽里,或相反,去掉沒有什么信息的特征飞蛹,清洗異常值等等)谤狡。
用測試集評估系統(tǒng)
調(diào)節(jié)完系統(tǒng)之后灸眼,你終于有了一個性能足夠好的系統(tǒng)。現(xiàn)在就可以用測試集評估最后的模型了豌汇。這個過程沒有什么特殊的:從測試集得到預測值和標簽幢炸,運行full_pipeline
轉(zhuǎn)換數(shù)據(jù)(調(diào)用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
評估結(jié)果通常要比交叉驗證的效果差一點,如果你之前做過很多超參數(shù)微調(diào)(因為你的系統(tǒng)在驗證集上微調(diào)逻澳,得到了不錯的性能闸天,通常不會在未知的數(shù)據(jù)集上有同樣好的效果)。這個例子不屬于這種情況斜做,但是當發(fā)生這種情況時苞氮,你一定要忍住不要調(diào)節(jié)超參數(shù),使測試集的效果變好瓤逼;這樣的提升不能推廣到新數(shù)據(jù)上笼吟。
然后就是項目的預上線階段:你需要展示你的方案(重點說明學到了什么、做了什么霸旗、沒做什么贷帮、做過什么假設(shè)、系統(tǒng)的限制是什么诱告,等等)撵枢,記錄下所有事情,用漂亮的圖表和容易記住的表達(比如精居,“收入中位數(shù)是房價最重要的預測量”)做一次精彩的展示锄禽。
啟動、監(jiān)控靴姿、維護系統(tǒng)
很好沃但,你被允許啟動系統(tǒng)了!你需要為實際生產(chǎn)做好準備佛吓,特別是接入輸入數(shù)據(jù)源绽慈,并編寫測試。
你還需要編寫監(jiān)控代碼辈毯,以固定間隔檢測系統(tǒng)的實時表現(xiàn)坝疼,當發(fā)生下降時觸發(fā)報警。這對于捕獲突然的系統(tǒng)崩潰和性能下降十分重要谆沃。做監(jiān)控很常見钝凶,是因為模型會隨著數(shù)據(jù)的演化而性能下降,除非模型用新數(shù)據(jù)定期訓練。
評估系統(tǒng)的表現(xiàn)需要對預測值采樣并進行評估耕陷。這通常需要人來分析掂名。分析者可能是領(lǐng)域?qū)<遥蛘呤潜姲脚_(比如Amazon Mechanical Turk 或 CrowdFlower)的工人哟沫。不管采用哪種方法饺蔑,你都需要將人工評估的pipeline植入系統(tǒng)。
你還要評估系統(tǒng)輸入數(shù)據(jù)的質(zhì)量嗜诀。有時因為低質(zhì)量的信號(比如失靈的傳感器發(fā)送隨機值猾警,或另一個團隊的輸出停滯),系統(tǒng)的表現(xiàn)會逐漸變差隆敢,但可能需要一段時間发皿,系統(tǒng)的表現(xiàn)才能下降到一定程度,觸發(fā)警報拂蝎。如果監(jiān)測了系統(tǒng)的輸入穴墅,你就可能盡量早的發(fā)現(xiàn)問題。對于線上學習系統(tǒng)温自,監(jiān)測輸入數(shù)據(jù)是非常重要的玄货。
最后,你可能想定期用新數(shù)據(jù)訓練模型悼泌。你應(yīng)該盡可能自動化這個過程誉结。如果不這么做,非常有可能你需要每隔至少六個月更新模型券躁,系統(tǒng)的表現(xiàn)就會產(chǎn)生嚴重波動。如果你的系統(tǒng)是一個線上學習系統(tǒng)掉盅,你需要定期保存系統(tǒng)狀態(tài)快照也拜,好能方便地回滾到之前的工作狀態(tài)。
實踐趾痘!
希望這一章能告訴你機器學習項目是什么樣的慢哈,你能用學到的工具訓練一個好系統(tǒng)。你已經(jīng)看到永票,大部分的工作是數(shù)據(jù)準備步驟卵贱、搭建監(jiān)測工具、建立人為評估pipeline和自動化定期模型訓練侣集,當然键俱,最好能了解整個過程、熟悉三或四種算法世分,而不是在探索高級算法上浪費全部時間编振,導致在全局上的時間不夠。
因此臭埋,如果你還沒這樣做踪央,現(xiàn)在最好拿起臺電腦臀玄,選擇一個感興趣的數(shù)據(jù)集,將整個流程從頭到尾完成一遍畅蹂。一個不錯的著手開始的地點是競賽網(wǎng)站健无,比如http://kaggle.com/:你會得到一個數(shù)據(jù)集,一個目標液斜,以及分享經(jīng)驗的人累贤。
練習
使用本章的房產(chǎn)數(shù)據(jù)集:
- 嘗試一個支持向量機回歸器(
sklearn.svm.SVR
),使用多個超參數(shù)旗唁,比如kernel="linear"
(多個超參數(shù)C
值)∑枧ǎ現(xiàn)在不用擔心這些超參數(shù)是什么含義。最佳的SVR
預測表現(xiàn)如何检疫? - 嘗試用
RandomizedSearchCV
替換GridSearchCV
讶请。 - 嘗試在準備pipeline中添加一個只選擇最重要屬性的轉(zhuǎn)換器。
- 嘗試創(chuàng)建一個單獨的可以完成數(shù)據(jù)準備和最終預測的pipeline屎媳。
- 使用
GridSearchCV
自動探索一些準備過程中的候選項夺溢。
練習題答案可以在線上的Jupyter notebooks(https://github.com/ageron/handson-ml)找到。
(第一部分 機器學習基礎(chǔ))
第01章 機器學習概覽
第02章 一個完整的機器學習項目(上)
第02章 一個完整的機器學習項目(下)
第03章 分類
第04章 訓練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學習和隨機森林
第08章 降維
(第二部分 神經(jīng)網(wǎng)絡(luò)和深度學習)
第9章 啟動和運行TensorFlow
第10章 人工神經(jīng)網(wǎng)絡(luò)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(上)
第11章 訓練深度神經(jīng)網(wǎng)絡(luò)(下)
第12章 設(shè)備和服務(wù)器上的分布式 TensorFlow
第13章 卷積神經(jīng)網(wǎng)絡(luò)
第14章 循環(huán)神經(jīng)網(wǎng)絡(luò)
第15章 自編碼器
第16章 強化學習(上)
第16章 強化學習(下)