任務(wù)描述
kaggle 案例 california-housing-prices
https://www.kaggle.com/camnugent/california-housing-prices
基于給定的數(shù)據(jù),訓(xùn)練模型預(yù)測(cè)某一區(qū)域的房?jī)r(jià)中位數(shù)
房?jī)r(jià)數(shù)據(jù)包括人口 . 收入中位數(shù) . 房?jī)r(jià)中位數(shù) 等對(duì)于每個(gè)街區(qū)的描述屬性
設(shè)計(jì)問題解決方案時(shí)應(yīng)該了解到的信息:
- 弄清楚模型的應(yīng)用目的.
- 大致弄明白當(dāng)前(非機(jī)器學(xué)習(xí)模型的condition下)是如何的解答該應(yīng)用問題.
接下來,你需要設(shè)計(jì)采用哪種模型解決問題.監(jiān)督學(xué)習(xí)?無監(jiān)督學(xué)習(xí)?增強(qiáng)學(xué)習(xí)?從問題類型上來分,是一個(gè)分類問題?還是一個(gè)回歸問題?亦或者是其他的問題?需要采用分批次學(xué)習(xí)還是在線學(xué)習(xí)?
挑選一個(gè)性能評(píng)價(jià)指標(biāo):
-
均方根誤差RMSE
RMSE.png
RMSE是一種常用的測(cè)量數(shù)值之間差異的度量,其代表預(yù)測(cè)的值和觀察到的值之差的樣本標(biāo)準(zhǔn)差.
比如 :根據(jù)正態(tài)分布的結(jié)論,RMSE=50,000,這意味著模型預(yù)測(cè)的68%的結(jié)果落在預(yù)測(cè)值左右(+ -)50,000的范圍之內(nèi).有95%的預(yù)測(cè)落在2倍標(biāo)準(zhǔn)差即100,000的范圍內(nèi).因此較好地描述了預(yù)測(cè)的性能.
-
均值絕對(duì)誤差MAE
MAE.png
RMSE和MAE都是一種衡量?jī)蓚€(gè)向量之間差值距離的方式,總的來說RMSE更優(yōu)也更常用.但是在樣本中存在很多離群點(diǎn)的時(shí)候,你就應(yīng)該考慮使用MAE對(duì)其進(jìn)行衡量,因?yàn)镸AE相對(duì)RMSE而言對(duì)離群值不那么敏感.
- RMSE對(duì)應(yīng)于歐幾里得范式,也稱之為二范式.
- MAE對(duì)應(yīng)于一范式.有時(shí)又稱為曼哈頓范式.
- 更一般的情況,也存在k范式.
快速了解數(shù)據(jù):
import pandas as pd
housing = pd.read_csv('housing.csv')
housing.head() # 查看DF的前5行

可以用info()方法簡(jiǎn)要查看數(shù)據(jù)的描述.特別是數(shù)據(jù)的行數(shù),每個(gè)特征的數(shù)據(jù)類型,以及非空值的數(shù)量. 可以了解到:
- 20000左右的數(shù)據(jù)量,對(duì)于機(jī)器學(xué)習(xí)模型來說算是一個(gè)小的數(shù)據(jù)集
可以看到total_bedrooms這一屬性只有20433個(gè)非空值,即有207個(gè)缺失值,這是值得格外注意的.
In: housing.info()
Out:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
longitude 20640 non-null float64
latitude 20640 non-null float64
housing_median_age 20640 non-null float64
total_rooms 20640 non-null float64
total_bedrooms 20433 non-null float64
population 20640 non-null float64
households 20640 non-null float64
median_income 20640 non-null float64
median_house_value 20640 non-null float64
ocean_proximity 20640 non-null object
dtypes: float64(9), object(1)
memory usage: 1.6+ MB
通過對(duì)于前5行的瀏覽,發(fā)現(xiàn)ocean_proximity這一屬性為object類型,并且值是重復(fù)的,這也意味著這可能是一個(gè)類別屬性.因此,我們可以用value_counts( )方法弄清白到底有哪些類,并且每個(gè)類中到底存在多少實(shí)例.
In: type(housing['ocean_proximity'])
Out: pandas.core.series.Series
In: housing['ocean_proximity'].value_counts()
Out:
<1H OCEAN 9136
INLAND 6551
NEAR OCEAN 2658
NEAR BAY 2290
ISLAND 5
Name: ocean_proximity, dtype: int64
housing.describe()`方法可以展示數(shù)值特征的概要
In: housing.describe()

另外一個(gè)快速整體認(rèn)知數(shù)據(jù)的方法是繪制每一個(gè)屬性的直方圖.
- hist()方法是依賴于matplotlib包的,因此在使用之前必須先申明基于matplotlib.這個(gè)操作可以使用Jupyter的魔法命令
%matplotlib inline.這能夠告知Jupyter啟用matplotlib.
%matplotlib inline
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))

從直方圖中得到的總結(jié):
- 特征median_income數(shù)值的單位并不是dollar.已經(jīng)被縮放并capped(設(shè)置閾值,這里是[0.5, 15])處理過了.在訓(xùn)練的時(shí)候這無關(guān)緊要,但是需要知道這些數(shù)據(jù)大概是如何計(jì)算得到.
- housing_median_age和median_house_value也是明顯被capped過(注意到在上界出現(xiàn)了峰值).這將是一個(gè)需要異常注意的問題,因?yàn)閙edian_house_value將是我們模型應(yīng)用的標(biāo)簽,即ML模型訓(xùn)練后得出的任何預(yù)測(cè)幾乎都不可能超過這個(gè)上界.
因此,在模型應(yīng)用的時(shí)候需要格外地注意,目標(biāo)應(yīng)用和訓(xùn)練集上的數(shù)值差別 - 這些屬性存在多個(gè)不同的規(guī)模量級(jí),這也就需要在后面進(jìn)行
特征歸一化. - 很多
直方圖顯示出的特征數(shù)據(jù)分布都是尾部大的分布,這對(duì)于機(jī)器學(xué)習(xí)模型來說是不太好訓(xùn)練的.因此我們需要將這些特征的分布修正為正態(tài)分布.
創(chuàng)建測(cè)試集
數(shù)據(jù)窺探誤差:目前為止我們只是對(duì)數(shù)據(jù)快速瞥了一眼,在挑選真正的模型之前我們還有很多信息需要學(xué)習(xí).如果在這之前我們只是對(duì)test_set進(jìn)行一個(gè)窺探,那么很容易造成認(rèn)識(shí)上的錯(cuò)覺,傾向于選擇某一個(gè)特定的模型進(jìn)行訓(xùn)練.然鵝,大部分情況下這種都是過于樂觀的操作.這就是叫做數(shù)據(jù)窺探誤差
import numpy as np
自己寫的一個(gè)拆分方法
def split_train_test(data,test_radio):
shuffled_indices = np.random.permutation(len(data)) # 產(chǎn)生一個(gè)目標(biāo)長(zhǎng)度的亂序索引
test_set_size = int(len(data) * test_radio) #指定測(cè)試集的數(shù)量
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
return data.iloc[train_indices],data.iloc[test_indices]
易混淆的iloc和loc的區(qū)分::
在pandas中l(wèi)oc和iloc都能實(shí)現(xiàn)對(duì)目標(biāo)數(shù)據(jù)的準(zhǔn)確索引.但實(shí)質(zhì)上是有區(qū)別的:
- loc['a','b' ]中填入的是行列位置標(biāo)簽
- iloc[a,b] 中填入的索引
train_set,test_set = split_train_test(housing,0.2)
上述方法當(dāng)然有效,但是并不完美.因?yàn)槿绻阒匦聄un這一程序,你將拿到不同的測(cè)試集.慢慢的,你的算法將可能看到整個(gè)數(shù)據(jù)集,而這是你應(yīng)該避免的.
# 查看拆分的效果
In: train_set.shape
Out: (16512, 10)
In: test_set.shape
Out: (4128, 10)
sklearn的拆分方法
from sklearn.model_selection import train_test_split
train_set,test_set = train_test_split(housing, test_size=0.2, random_state=42)
分層采樣
- 直接隨機(jī)采樣有什么弊端?
?當(dāng)你的數(shù)據(jù)集足夠大時(shí),一般來說隨機(jī)采樣都是可行的.但是如果數(shù)據(jù)量不夠大,那么隨機(jī)采樣則可能有樣本嚴(yán)重偏斜的風(fēng)險(xiǎn).
- 為什么要進(jìn)行分層采樣?
?分層抽樣比單純隨機(jī)抽樣所得到的結(jié)果準(zhǔn)確性更高,組織管理更方便,而且它能保證總體中每一層都有個(gè)體被抽到,從而樣本集對(duì)于總體來說會(huì)更加有代表性。這樣除了能估計(jì)總體的參數(shù)值,還可以分別估計(jì)各個(gè)層內(nèi)的情況,因此分層抽樣技術(shù)常被采用。
- 實(shí)例介紹
?例如,通過對(duì)包含1000個(gè)樣本的數(shù)據(jù)集D進(jìn)行分層抽樣而獲得70%樣本的訓(xùn)練集S和含30%樣本的測(cè)試集T,若D包含500個(gè)正例、500個(gè)反例,則分層采樣得到的S應(yīng)包含350個(gè)正例、350個(gè)反例,而T則包含150個(gè)正例、150個(gè)反例;
若S、T中樣本類別比例差別很大,則誤差估計(jì)將由于訓(xùn)練/測(cè)試數(shù)據(jù)分布的差異而產(chǎn)生偏差。
In: housing['median_income'].hist()
Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070de81dd8>

假設(shè)median_income屬性非常重要:
?通過直方圖可以看出,大多數(shù)的income集中在2-5 unit之間.但是也是有一些income和2-5的范圍差得很遠(yuǎn),比如tail部分.
在這種情況下,保證數(shù)據(jù)集中income衡量的特征中每一層都有足夠數(shù)量的實(shí)例是很有必要的,否則就會(huì)產(chǎn)生一些偏差.同時(shí)也這意味著不能有太多的分層,并且每個(gè)分層是需要足夠大的.
?后面的代碼通過將收入中位數(shù)除以 1.5(以限制收入分類的層次數(shù)量),創(chuàng)建了一個(gè)收入類別屬性,并且需要用ceil對(duì)值舍入(盡量產(chǎn)生離散的分類),然后將所有大于 5的分類歸入到分類5.
housing['income_cat'] = np.ceil(housing['median_income'] / 1.5) # 添加income_cat屬性,輔助分層
housing['income_cat'].where(housing['income_cat']<5, 5.0, inplace=True) # where方法操作
接下來準(zhǔn)備基于income類對(duì)數(shù)據(jù)進(jìn)行分層采樣.這個(gè)問題需要用到sklearn中的StratifiedShuffleSplit class.
from sklearn.model_selection import StratifiedShuffleSplit
# random_state 依然是隨機(jī)種子生成器
# n_split 是將訓(xùn)練數(shù)據(jù)分成train-test對(duì)的對(duì)數(shù).-->我們這個(gè)地方匯總為一組數(shù)據(jù)
split = StratifiedShuffleSplit(n_splits=1,test_size=0.2,random_state=42)
for train_index,test_index in split.split(housing, housing['income_cat']):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
查看在整個(gè)housing數(shù)據(jù)集中income 屬性類別的比例,瞥一眼看看是否成功分層采樣.
In: housing['income_cat'].value_counts() / len(housing)
Out:
3.0 0.350581
2.0 0.318847
4.0 0.176308
5.0 0.114438
1.0 0.039826
Name: income_cat, dtype: float64
完成分割后,接下來務(wù)必將數(shù)據(jù)集的特征還原,即丟掉income_cat屬性:
# 完成分割,刪除income_cat屬性
for set in (strat_train_set,strat_test_set):
set.drop(['income_cat'], axis=1,inplace=True)
客觀上來講,對(duì)于本例20,000個(gè)樣本的小集合來說,上述得到的訓(xùn)練集會(huì)有利于模型訓(xùn)練.
可視化數(shù)據(jù)尋找規(guī)律
接下來需要更加地了解數(shù)據(jù).
復(fù)制一份數(shù)據(jù),避免訓(xùn)練集被損壞.
housing = strat_train_set.copy()
看一下人口密度的特征:
In: import matplotlib.pyplot as plt
housing.plot(kind='scatter',x='longitude',y='latitude')
Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070b54c978>

可以看到California的輪廓.但是這個(gè)整個(gè)很難看出特點(diǎn).因此設(shè)置alpha = 0.1,使得視圖可以區(qū)分分布點(diǎn)的密度.
In: housing.plot(kind='scatter',x='longitude',y='latitude',alpha = 0.1)
Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070e36b390>

基于上述圖就可以清楚地看到高人口密度的區(qū)域了.大多集中在海灣和城市.
接下來看房?jī)r(jià)特征:
In: housing.plot(kind='scatter',x='longitude',y='latitude',alpha=0.4,
s=housing['population']/100,label='population',c='median_house_value',cmap=
plt.get_cmap('jet'),colorbar=True,figsize=(12,7),legend=True,use_index=True)
Out: <matplotlib.axes._subplots.AxesSubplot at 0x2070e3d7358>

每個(gè)圈的半徑表示街區(qū)的人口(選項(xiàng)s),顏色代表價(jià)格(選項(xiàng)c)。我們用預(yù)先定義的名為jet的顏色圖(選項(xiàng)cmap),它的范圍是從藍(lán)色(低價(jià))到紅色(高價(jià)):
這張圖說明房?jī)r(jià)和位置,還有人口密度聯(lián)系密切. 這對(duì)于使用聚類算法去分析主要的簇可能是有幫助的,并且能夠添加衡量是否鄰近'簇'中心的新特征.
搜索關(guān)聯(lián)性
數(shù)據(jù)集較小,可以輕易使用corr()方法計(jì)算出每對(duì)屬性間的標(biāo)準(zhǔn)相關(guān)系數(shù)(也稱作皮爾遜相關(guān)系數(shù))
In: corr_matrix = housing.corr()
In: corr_matrix.shape,type(corr_matrix)
Out: ((9, 9), pandas.core.frame.DataFrame)
In: corr_matrix

相關(guān)系數(shù)的范圍為-1到1. 越接近1,意味著強(qiáng)正相關(guān);例如收入中位數(shù)越大,大概率上房?jī)r(jià)中位數(shù)也會(huì)很大.當(dāng)相關(guān)系數(shù)越接近-1,意味著負(fù)相關(guān)越強(qiáng).相關(guān)系數(shù)越接近0,越是意味著兩種feature之間沒有直接的線性關(guān)系.

上圖表述了不同的分布圖所描述的兩種特征的相關(guān)程度.
需要注意的是:關(guān)聯(lián)系數(shù)僅僅衡量了線性的關(guān)聯(lián),它可能完全無法刻畫出非線性的關(guān)系.比如第3行,其實(shí)能看出特征之間還是有明顯的關(guān)系的,只不過不是線性關(guān)系罷了.
In: corr_matrix['median_house_value'].sort_values(ascending=False)
Out:
median_house_value 1.000000
median_income 0.687160
total_rooms 0.135097
housing_median_age 0.114110
households 0.064506
total_bedrooms 0.047689
population -0.026920
longitude -0.047432
latitude -0.142724
Name: median_house_value, dtype: float64
可以看到median_house_value 與median_income 的相關(guān)性是非常強(qiáng)的.
另一種檢測(cè)屬性間相關(guān)系數(shù)的方法是使用 Pandas 的scatter_matrix函數(shù),它能畫出每個(gè)數(shù)值屬性對(duì)每個(gè)其它數(shù)值屬性的圖。因?yàn)楝F(xiàn)在共有 11 個(gè)數(shù)值屬性,你可以得到11 ** 2 = 121張圖。

from pandas.tools.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
"housing_median_age"]
scatter_matrix(housing[attributes],figsize=(10,10))
seaborn中的pairplot有更好的相關(guān)系數(shù)圖繪圖效果
import seaborn as sns
attributes = ["median_house_value", "median_income", "total_rooms",
"housing_median_age"]
f1 = sns.pairplot(housing[attributes],diag_kind="kde")

其實(shí)對(duì)于預(yù)測(cè)median_house_value最優(yōu)希望的特征就是median_income.
# 繪圖查看median_house_value和median_income的相關(guān)程度
In: housing.plot(kind='scatter',x='median_income',y='median_house_value',alpha=0.1)
Out: <matplotlib.axes._subplots.AxesSubplot at 0x20710c25160>

圖中揭示了:
- median_income和median_house_value的關(guān)聯(lián)確實(shí)非常強(qiáng),可以看到明顯的集中和上升變化趨勢(shì).
- 在圖中可以看到不止一條value分層線,50000是預(yù)先設(shè)定,同時(shí)還能看到45000 . 35000等等. 這些對(duì)應(yīng)的區(qū)域其實(shí)
可能是需要被去掉的,否則可能會(huì)造成錯(cuò)誤的分布特點(diǎn).
嘗試特征組合
在為算法準(zhǔn)備數(shù)據(jù)之前,還需要做的一個(gè)步驟就是嘗試組合出各種各樣的特征.將一些沒有多大用處的特征利用起來,通過運(yùn)算組合得到一些新的有意義的特征,這一步也是比較重要的,能夠有效提升模型效果.
比如:
- 在本例中total_rooms這一屬性直接利用是沒有多少價(jià)值的,知道一個(gè)區(qū)域的總房間數(shù)似乎無用.
- 我們真正要用的上是每個(gè)家庭有多少間屋子.
- 同理,total_bedrooms也是沒有意義的,可以構(gòu)造出bedrooms_per_room屬性.
- 同時(shí)population_per_household似乎也是一個(gè)有意思的屬性.
housing['rooms_per_household'] = housing['total_rooms']/housing['households']
housing['bedrooms_per_room'] = housing['total_bedrooms']/housing['total_rooms']
housing['population_per_household'] = housing['population']/housing['households']
housing.head()

完成組合,查看一下皮爾遜相關(guān)系數(shù):
In: corr_matrix = housing.corr()
Out:
corr_matrix['median_house_value'].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687160
rooms_per_household 0.146285
total_rooms 0.135097
housing_median_age 0.114110
households 0.064506
total_bedrooms 0.047689
population_per_household -0.021985
population -0.026920
longitude -0.047432
latitude -0.142724
bedrooms_per_room -0.259984
Name: median_house_value, dtype: float64
可以發(fā)現(xiàn)我們構(gòu)造的bedrooms_per_room屬性,還有rooms_per_household屬性與目標(biāo)的相關(guān)性是明顯高于total_rooms和households/total_rooms的.說明構(gòu)造還是比較成功的.
添加特征是一個(gè)不斷迭代循環(huán)的過程,一旦模型得到提升,就可以再次分析其輸出,拿到更多信息**并且返回這一探索的步驟.
為機(jī)器學(xué)習(xí)算法準(zhǔn)備數(shù)據(jù)
數(shù)據(jù)清洗
數(shù)據(jù)清洗之前,需要再將strat_train_set復(fù)制一份.
并且需要將數(shù)據(jù)源和標(biāo)簽分開,因?yàn)閮烧哌M(jìn)行的是不一樣的操作.
housing = strat_train_set.drop('median_house_value',axis=1)
housing_labels = strat_train_set['median_house_value'].copy()
drop()方法創(chuàng)建了一個(gè)data的副本,同時(shí)還并不會(huì)影響strat_train_set
大多機(jī)器學(xué)習(xí)算法不能處理特征丟失,因此先創(chuàng)建一些函數(shù)來處理特征丟失的問題。前面,注意到屬性total_bedrooms有一些缺失值。
針對(duì)這個(gè)問題有三個(gè)解決選項(xiàng):
- 去掉對(duì)應(yīng)的街區(qū);
- 去掉整個(gè)屬性;
- 進(jìn)行賦值(0、平均值、中位數(shù)等等)
用DataFrame的dropna(),drop(),和fillna()方法,可以方便地實(shí)現(xiàn):
housing.dropna(subset=['total_bedrooms']) #dropna丟棄該行數(shù)據(jù),指定到 total_bedrooms列去尋找缺失數(shù)據(jù),axis默認(rèn)為0-->行丟棄
housing.drop('total_bedrooms',axis=1) # 去掉整個(gè)屬性
median = housing['total_bedrooms'].median()
housing['total_bedrooms'].fillna(median) # 用中位數(shù)進(jìn)行填充
scikit-learn 也提供了一個(gè)方便的類來處理缺失值:Imputer.用法如下:
- 首先需要?jiǎng)?chuàng)建一個(gè)Imputer實(shí)例,指定用中位數(shù)替換它的每個(gè)缺失值
In: from sklearn.preprocessing import Imputer
In: imputer = Imputer(strategy='median')
In: imputer # imputer實(shí)例
Out: Imputer(axis=0, copy=True, missing_values='NaN', strategy='median', verbose=0)
只有數(shù)值屬性才能計(jì)算出中位數(shù),因此也需要?jiǎng)?chuàng)建一份不包括文本屬性ocean_proximity的數(shù)據(jù)副本:
housing_num = housing.drop('ocean_proximity',axis=1).copy() # 只有這樣的樣本才不會(huì)計(jì)算報(bào)錯(cuò)
現(xiàn)在,就可以用fit()方法將imputer實(shí)例擬合到訓(xùn)練數(shù)據(jù):
In: imputer.fit(housing_num)
Out: Imputer(axis=0, copy=True, missing_values='NaN', strategy='median', verbose=0)
imputer實(shí)例在這個(gè)過程中簡(jiǎn)單計(jì)算了每一個(gè)屬性的median value,并且存在了statistics_屬性當(dāng)中.
In: imputer.statistics_
Out: array([-118.51 , 34.26 , 29. , 2119.5 , 433. , 1164. ,408. , 3.5409])
In: housing_num.median().values
Out: array([-118.51 , 34.26 , 29. , 2119.5 , 433. , 1164. , 408. , 3.5409])
使用訓(xùn)練過的imputer對(duì)訓(xùn)練集進(jìn)行轉(zhuǎn)換變形
In: X = imputer.transform(housing_num)
In: X.shape,type(X)
Out: ((16512, 8), numpy.ndarray)
housing_num轉(zhuǎn)換后變成了ndarray的格式,當(dāng)然也可以放回DF中
housing_tr = pd.DataFrame(X,columns=housing_num.columns)
housing_tr.head()

處理文本和類別屬性
在前面,為了處理缺失的數(shù)據(jù)值,我們?nèi)サ袅藢傩詏cean_proximity,因?yàn)槲谋緦傩圆]有中位數(shù).
為了讓機(jī)器學(xué)習(xí)算法能利用文本標(biāo)簽,我們需要把文本轉(zhuǎn)換為數(shù)字.
scikit-learn提供了LabelEncoder:
In: from sklearn.preprocessing import LabelEncoder
In: encoder = LabelEncoder()
In: housing_cat = housing['ocean_proximity']
In: housing_cat_encoded= encoder.fit_transform(housing_cat)
In: housing_cat_encoded
Out: array([0, 0, 4, ..., 1, 0, 3])
轉(zhuǎn)換后的數(shù)字量就可以用于任何ML模型的學(xué)習(xí).同時(shí)還可以通過encoder.classes_查看數(shù)值和類別的mapping映射關(guān)系
In: encoder.classes_ # 查看分出的類別
Out: array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],dtype=object)
LabelEncoder還不夠好 ! 為什么?
對(duì)于LabelEncoder來說,其生成的標(biāo)簽數(shù)值并非是二值,本例中出現(xiàn)了0,1,2,3,4.將這樣的數(shù)據(jù)喂給ML模型,模型會(huì)誤認(rèn)為類別的相似度由數(shù)值的大小影響.為了解決這樣的問題,引入了one-hot解決方案.
One-Hot:
要更優(yōu)地處理離散特征這還不夠,Scikit-Learn 提供了一個(gè)編碼器OneHotEncoder,用于將整數(shù)分類值轉(zhuǎn)變?yōu)楠?dú)熱向量。結(jié)果是為每一個(gè)類別都創(chuàng)建了一個(gè)二值屬性,值為'1'時(shí),代表屬于該類(hot),否則均為'0'(cold).
利用encoder.fit_transform()之前需要注意該方法用于處理2d數(shù)組,因此需要先將housing_cat_encoded變形.
In: from sklearn.preprocessing import OneHotEncoder
In: encoder = OneHotEncoder()
In: housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1)) # 變形操作
In: housing_cat_1hot
Out: <16512x5 sparse matrix of type '<class 'numpy.float64'>'with 16512 stored elements in Compressed Sparse Row format>
注意到上述的housing_cat_1hot結(jié)果是一個(gè)SciPy系數(shù)矩陣,而非一個(gè)Nunmpy array.在有成千上萬的類別屬性的時(shí)候這是非常有用的,可以節(jié)省很多空間,因?yàn)椴挥么鎯?chǔ)0.
以上的步驟也都可以用LabelBinarizer一步搞定,即從文本到整數(shù)分類,再轉(zhuǎn)換成one-hot類
In: from sklearn.preprocessing import LabelBinarizer
In: encoder = LabelBinarizer(sparse_output = False)
In: housing_cat_1hot = encoder.fit_transform(housing_cat)
In: housing_cat_1hot
Out:
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]])
自定義轉(zhuǎn)換器
盡管scikit-learn提供了很多有用的轉(zhuǎn)換器,你依然需要手動(dòng)編寫,以滿足自定義數(shù)據(jù)清洗操作和特征組合的需要.你想要你的轉(zhuǎn)換器和scikit-learn庫運(yùn)行時(shí)可以無縫連接(比如管道),因?yàn)閟cikit-learn依賴于鴨子類型(而非繼承),你需要做的所有就是創(chuàng)造一個(gè)類并執(zhí)行fit()(返回self) . transform() 和 fit_transform()這三個(gè)方法. 如果你只添加TransformerMixin作為基類,你就可以不用最后一個(gè)方法.如果你添加BaseEstimator作為基類(且構(gòu)造器中避免使用args和kargs),你就能得到兩個(gè)額外的方法(get_params()和set_params()),二者可以方便地進(jìn)行超參數(shù)自動(dòng)微調(diào)。
例如,下面這個(gè)小轉(zhuǎn)換器類添加了上面討論的屬性:
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6
# 這里的示例沒有定義fit_transform(), 因?yàn)榭梢灾苯佑胒it 和 transform步驟達(dá)到同樣的效果
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): # 創(chuàng)造新的特征
rooms_per_household = X[:, rooms_ix] / X[:, household_ix] # X[:,3]表示的是第4列所有數(shù)據(jù)
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_表示的是拼接數(shù)組。
bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]
In: attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
In: housing_extra_attribs = attr_adder.transform(housing.values) # 返回一個(gè)加入新特征的數(shù)據(jù)集合
In: housing.shape
Out: (16512, 9)
In: housing_extra_attribs.shape
Out: (16512, 11)
在這個(gè)例子中,轉(zhuǎn)換器只有一個(gè)超參數(shù),即add_bedrooms_per_room這個(gè)布爾量,默認(rèn)設(shè)置為True(提供一個(gè)明智的默認(rèn)設(shè)置通常是很有幫助的).這個(gè)超參數(shù)會(huì)讓你輕易地發(fā)現(xiàn)添加這個(gè)特征是否對(duì)ML算法的有好處.更一般的,你可以往gate中添加一個(gè)你在數(shù)據(jù)準(zhǔn)備階段不能完全確定其作用的超參數(shù).數(shù)據(jù)準(zhǔn)備步驟越是自動(dòng)化,可以自動(dòng)化解析出的特征組合就越多,這也使得你更可能找到一個(gè)非常好的特征組合(這將節(jié)省你大量時(shí)間)
這里需要區(qū)分一下fit() . transform(). 和fit_transform():
- fit()是很好理解的,就是訓(xùn)練擬合,學(xué)習(xí)到相關(guān)參數(shù).
- transform()和fit_transform()看起來是非常相似,也是很容易混淆的,實(shí)質(zhì)上兩種有重大區(qū)別:
- fit_transform()用在訓(xùn)練集當(dāng)中.是fit()和transform()的組合,可以直接完成兩個(gè)步驟.
- transform()如上所述,只有轉(zhuǎn)化的功能,用于測(cè)試集當(dāng)中.但是這時(shí)不需要fit的原因是轉(zhuǎn)換相關(guān)的參數(shù)都已經(jīng)拿到了.
- fit()是后續(xù)所有api的先決條件
- fit_transform和transform的效果其實(shí)是沒有區(qū)別的(正確使用的前提下).
此外,scikit-learn無法直接處理DataFrame,因此需要自定義一個(gè)方法能夠?qū)崿F(xiàn)向numpy的轉(zhuǎn)換
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 # .values實(shí)現(xiàn)取出所有的值, 為數(shù)組的形式
數(shù)據(jù)歸一化
你需要對(duì)你數(shù)據(jù)進(jìn)行的最重要的轉(zhuǎn)化之一就是特征歸一化.在輸入特征有不同的量級(jí)的時(shí)候ML模型的表現(xiàn)通常不好.對(duì)于本數(shù)據(jù)集來說,同樣存在這一的情況.
需要注意的是目標(biāo)值value通常是不需要進(jìn)行歸一化的
有兩種常見的方法可以讓所有的屬性有相同的量度:線性函數(shù)歸一化(Min-Max scaling)和標(biāo)準(zhǔn)化(standardization)。
Min-Max scaling:

Min-Max scaling是一種非常簡(jiǎn)單的概念,可以將所有的值重新縮放至0,1的范圍內(nèi).Scikit-Learn 提供了一個(gè)轉(zhuǎn)換器MinMaxScaler來實(shí)現(xiàn)這個(gè)功能。它有一個(gè)超參數(shù)feature_range,可以讓你改變范圍,如果不希望范圍是 0 到 1;Scikit-Learn 提供了一個(gè)轉(zhuǎn)換器StandardScaler來進(jìn)行標(biāo)準(zhǔn)化
Standardization:
Standardization就是另外一種完全不同的方法.

- 標(biāo)準(zhǔn)化不會(huì)將數(shù)值約束到一個(gè)特定的范圍內(nèi),這對(duì)于一些特定的算法(比如神經(jīng)網(wǎng)絡(luò))是不太合適的
- 標(biāo)準(zhǔn)化受到離群值的影響較小.相對(duì)而言,Min-Max scaling會(huì)受到更大的影響.
- 標(biāo)準(zhǔn)化后的結(jié)果分布不會(huì)有單位偏差
和所有的變換一樣,scaler只能用訓(xùn)練數(shù)據(jù)進(jìn)行訓(xùn)練,而不能整個(gè)數(shù)據(jù)集擬合,這是需要格外注意的地方.只有這樣我們才能用它來對(duì)訓(xùn)練集和測(cè)試集進(jìn)行變形.
from sklearn.preprocessing import MinMaxScaler,StandardScaler
轉(zhuǎn)換管道
有很多數(shù)據(jù)轉(zhuǎn)換的步驟需要以正確的順序被執(zhí)行.scikit-learn中就提供了Pipeline類去解決順序轉(zhuǎn)換的問題.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer',Imputer(strategy='median')), # 缺失值填充
('attribs_adder',CombinedAttributesAdder()), # 合成新屬性
('std_scaler',StandardScaler()) # 數(shù)據(jù)歸一化
])
In: housing_num_tr = num_pipeline.fit_transform(housing_num)
Pipeline構(gòu)造器需要傳入定義一系列步驟的名字/預(yù)測(cè)器對(duì)的列表.除開最后一個(gè)預(yù)測(cè)器,所有的預(yù)測(cè)器都需要是一個(gè)轉(zhuǎn)換器(必須要有fit_transform()方法).管道的名字可以隨意取.
當(dāng)對(duì)pipeline使用fit()方法時(shí),他會(huì)自動(dòng)順序調(diào)用所有轉(zhuǎn)換器的fit_transform()方法,將每一個(gè)調(diào)用方法的輸出結(jié)果作為參數(shù)傳給下一個(gè)方法調(diào)用,直到run到了最后一個(gè)預(yù)測(cè)器,這個(gè)預(yù)測(cè)器只有fit()方法.
pipeline公開了和最終預(yù)測(cè)器相同的方法.在本例中最后一個(gè)預(yù)測(cè)器是StandardScaler,這是一個(gè)轉(zhuǎn)換器,因此pipeline有一個(gè)順序地將所有轉(zhuǎn)換器運(yùn)用到data上的transform()方法.(它還有一個(gè)fit_transform方法,這個(gè)方法可以代替fit()和transform()的順序執(zhí)行)
注意,pipeline最后一步如果有predict()方法我們才可以對(duì)pipeline使用fit_predict(),同理,最后一步如果有transform()方法我們才可以對(duì)pipeline使用fit_transform()方法。
為了將處理類別值的LabelBinary方法和先前處理numerical的pipeline進(jìn)行組合,將這些變換組合到單個(gè)的管道中:Scikit-Learn提供了FeatureUnion類解決這個(gè)問題.
- 你可以傳入一個(gè)轉(zhuǎn)換器的列表(還可以是一整個(gè)轉(zhuǎn)換器管道)
- 運(yùn)行的時(shí)候管道會(huì)將每一個(gè)轉(zhuǎn)換器的輸出融合并返回.
from sklearn.pipeline import FeatureUnion
實(shí)際編碼中,筆者遇到pipeline傳參個(gè)數(shù)出錯(cuò)的問題,查閱資料后得出以下解決方案:
- The pipeline is assuming LabelBinarizer's fit_transform method is defined to take three positional arguments,This can be solved by making a custom transformer that can handle 3 positional arguments.
- Since LabelBinarizer doesn't allow more than 2 positional arguments you should create your custom binarizer like,solution2 is following
solution_1:
from sklearn.base import TransformerMixin # gives fit_transform method for free
class MyLabelBinarizer(TransformerMixin):
def __init__(self, *args, **kwargs):
self.encoder = LabelBinarizer(*args, **kwargs)
def fit(self, x, y=0):
self.encoder.fit(x)
return self
def transform(self, x, y=0):
return self.encoder.transform(x)
solution2:
class CustomLabelBinarizer(BaseEstimator, TransformerMixin):
def __init__(self, sparse_output=False):
self.sparse_output = sparse_output
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
enc = LabelBinarizer(sparse_output=self.sparse_output)
return enc.fit_transform(X)
管道組合
In: num_attribs = list(housing_num)
In: cat_attribs = ['ocean_proximity']
In: num_pipeline = Pipeline([
('selector',DataFrameSelector(num_attribs)),
('imputer',Imputer(strategy = 'median')),
('attribs_adder',CombinedAttributesAdder()),
('std_scaler',StandardScaler()),
])
In: cat_pipeline = Pipeline([
('selector',DataFrameSelector(cat_attribs)),
('label_binarizer',CustomLabelBinarizer()),
])
In: full_pipeline = FeatureUnion(transformer_list=[
('num_pipeline',num_pipeline),
('cat_pipeline',cat_pipeline),
])
In: housing.shape
Out: (16512, 9)
In: housing_prepared = full_pipeline.fit_transform(housing)
In: housing_prepared.shape
Out: (16512, 16) # 已經(jīng)在管道中完成轉(zhuǎn)換
In: type(housing_prepared) #housing_prepared是一個(gè)ndarray的形式
Out: numpy.ndarray
每一個(gè)子管道都從選擇轉(zhuǎn)換器開始:完成的任務(wù)是挑選需要的目標(biāo)屬性,拋棄其他的屬性,將DataFrame轉(zhuǎn)換成Numpy array.在scikit-learn中沒有任何處理DataFrame的函數(shù),因此我們需要自己寫一個(gè)簡(jiǎn)單的轉(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 # 保證返回的是一個(gè)array
注釋以下概念:
每一個(gè)scikit-learn中的擴(kuò)展都需要繼承特定的sklearn.base下的包的類:
- BaseEstimator 估計(jì)器的基類
- ClassifierMixin 分類器的混合類
- ClusterMixin 聚類器的混合類
- RegressorMixin 回歸器的混合類
- TransformerMixin 轉(zhuǎn)換器的混合類
創(chuàng)建了DataFrameSelector類繼承了BaseEstimator,TransformerMixin意味著它的基本功能就是單一估計(jì)器和轉(zhuǎn)換器的組合
模型選擇與訓(xùn)練
設(shè)計(jì)問題 , 拿到數(shù)據(jù)+瞥一眼 , 取出測(cè)試集與訓(xùn)練集 , 運(yùn)用轉(zhuǎn)換管道清洗數(shù)據(jù) . 接下來就該挑選并訓(xùn)練ML模型
首先訓(xùn)練一個(gè)線性回歸的模型.
In: from sklearn.linear_model import LinearRegression
In: lin_reg = LinearRegression()
In: lin_reg.fit(housing_prepared, housing_labels)
Out: LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,normalize=False)
In: housing_prepared.shape
Out: (16512, 16)
現(xiàn)在已經(jīng)拿到一個(gè)有效的線性回歸模型了.
為了瞄一眼效果,可以從訓(xùn)練集中取一些數(shù)據(jù)拿來嘗試
In: some_data = housing_prepared[:5]
In: some_data.shape
Out: (5, 16)
In: some_labels = housing_labels.iloc[:5]
In: 'Predictions:\t',lin_reg.predict(some_data)
Out: ('Predictions:\t', array([210644.60459286, 317768.80697211, 210956.43331178, 59218.98886849,189747.55849879]))
In: 'Predictions:\t',list(some_labels)
Out: ('Predictions:\t', [286600.0, 340600.0, 196900.0, 46300.0, 254500.0])
iloc和loc的區(qū)分::
在pandas中l(wèi)oc和iloc都能實(shí)現(xiàn)對(duì)目標(biāo)數(shù)據(jù)的準(zhǔn)確索引.但實(shí)質(zhì)上是有區(qū)別的:
- loc['a','b' ]中填入的是行列位置標(biāo)簽
- iloc[a,b] 中填入的索引
我們拿到了我們的預(yù)測(cè)值,盡管偏差還是蠻大的.
我們可以用RMSE指標(biāo)來對(duì)整個(gè)訓(xùn)練集的效果進(jìn)行一個(gè)評(píng)價(jià)
In: from sklearn.metrics import mean_squared_error
In: housing_predictions = lin_reg.predict(housing_prepared)
In: lin_mse = mean_squared_error(housing_labels,housing_predictions)
In: lin_rmse = np.sqrt(lin_mse)
In: lin_rmse
Out: 68628.19819848922

由統(tǒng)計(jì)直方圖看出,median_housing_values大多數(shù)都分布在12000 - 265000之間,因此目前這個(gè)lin_reg模型的RMSE達(dá)到了68628,這是很不理想的.
這是欠擬合的情況發(fā)生了(在訓(xùn)練集上查看出來擬合的效果):
- 發(fā)生這種情況告訴我們特征并沒有提供足夠多的信息.
- 模型并不足夠好
面對(duì)這種情況時(shí),我們解決欠擬合的主要方法是:
- 挑選一個(gè)更加強(qiáng)大的模型,并喂更好的特征
- 減少對(duì)于模型的約束(比如調(diào)整正則項(xiàng)系數(shù)還有放寬對(duì)于樹模型生長(zhǎng)的約束等等,都有利于模型變得更加復(fù)雜).
但是我們這個(gè)模型并沒有正則化,因此只能采取第一種方法進(jìn)行優(yōu)化.
我們可以嘗試添加更多的特征,比如取人口的對(duì)數(shù).
下面嘗試訓(xùn)練一種更加強(qiáng)大的回歸模型,決策樹回歸模型DecisionTreeRegressor.這種模型能夠發(fā)現(xiàn)復(fù)雜的非線性關(guān)系.
In: from sklearn.tree import DecisionTreeRegressor
In: tree_reg = DecisionTreeRegressor()
In: tree_reg.fit(housing_prepared,housing_labels)
Out: DecisionTreeRegressor(criterion='mse', max_depth=None, max_features=None,
max_leaf_nodes=None, min_impurity_decrease=0.0,
min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
presort=False, random_state=None, splitter='best')
In: housing_predicions = tree_reg.predict(housing_prepared)
In: housing_predicions[:5]
Out: array([286600., 340600., 196900., 46300., 254500.])
In: tree_mse = mean_squared_error(housing_labels,housing_predicions)
In: tree_rmse = np.sqrt(tree_mse)
In: tree_rmse
Out: 0.0
可以發(fā)現(xiàn)運(yùn)用DT模型的時(shí)候,這個(gè)模型竟然沒有error,模型幾乎是完美的.但是在訓(xùn)練集上有這樣的表現(xiàn),這往往是嚴(yán)重過擬合的表現(xiàn).
因此就如前述,對(duì)于一個(gè)模型而言,千萬不能讓它去碰測(cè)試集.你需要用訓(xùn)練集的一部分去做訓(xùn)練,另一部分做模型驗(yàn)證.因?yàn)橛行?qiáng)大的模型能夠完美地直接擬合已經(jīng)訓(xùn)練的數(shù)據(jù).
使用交叉驗(yàn)證做出更好的評(píng)估
一種評(píng)估DT模型的方法就是使用train_test_split方法將訓(xùn)練集分為更小的訓(xùn)練集和驗(yàn)證集,然后用小的訓(xùn)練集和驗(yàn)證集去訓(xùn)練并驗(yàn)證模型.
一種很好的替代方法就是使用scikit-learn的交叉驗(yàn)證功能,這樣的驗(yàn)證方法更加可靠.下面的代碼演示了K折交叉驗(yàn)證:
- 它隨機(jī)將訓(xùn)練集拆分為10個(gè)獨(dú)立的子集,稱之為'折'
- 然后利用10個(gè)子集訓(xùn)練并評(píng)估10個(gè)不同的DT模型
- 每次都選用一個(gè)不同折用于評(píng)估,利用剩下的9個(gè)折進(jìn)行訓(xùn)練.
- 結(jié)果是包含10個(gè)評(píng)估結(jié)果的array
In: from sklearn.model_selection import cross_val_score
In: scores =cross_val_score(tree_reg,housing_prepared,housing_labels,scoring='neg_mean_squared_error'
,cv=10)
In: rmse_scores = np.sqrt(-scores)
In: rmse_scores
Out: array([69166.55059074, 66543.54599875, 71015.98771317, 69322.34433533,
70674.92662341, 75050.13558149, 70625.34940601, 70823.47513762,
76224.34231131, 70124.08527976])
注意:scikit-learn交叉驗(yàn)證期望的是一個(gè)效用函數(shù)(越大越好)而不是損失函數(shù)(越小越好),因此得分函數(shù)通常是MSE值的負(fù)值.這也是為什么在開根之前要先取-scores.
def display_scores(scores):
print('Scores:',scores)
print('Mean:',scores.mean())
print('Standard deviation:',scores.std())
In: display_scores(rmse_scores)
Out:
Scores: [69166.55059074 66543.54599875 71015.98771317 69322.34433533,
70674.92662341 75050.13558149 70625.34940601 70823.47513762,76224.34231131 70124.08527976]
Mean: 70957.07429775785
Standard deviation: 2660.0686304080364
利用了更加科學(xué)的方法:'交叉驗(yàn)證'(能真正看出效果),看上去似乎DT的表現(xiàn)并不如先前那么好.事實(shí)上,看起來比線性回歸模型還要糟糕!
注意到交叉驗(yàn)證不僅讓你得到模型性能的評(píng)估,并且還能看到模型有多么準(zhǔn)確(標(biāo)準(zhǔn)差). 決策樹的平均分大概為71400,波動(dòng)通常在3200上下.如果你只有一個(gè)驗(yàn)證集你將得不到這些信息.
但是交叉驗(yàn)證的消耗在于多次訓(xùn)練模型,這樣的代價(jià)并不是在任何情況下都能接受.
下面計(jì)算一下線性回歸模型的分?jǐn)?shù):
In: lin_scores = cross_val_score(lin_reg,housing_prepared,housing_labels,scoring=
'neg_mean_squared_error',cv=10)
In: lin_rmse_scores = np.sqrt(-lin_scores)
In: display_scores(lin_rmse_scores)
Out: Scores: [66782.73843989 66960.118071 70347.95244419 74739.57052552
68031.13388938 71193.84183426 64969.63056405 68281.61137997 71552.91566558 67665.10082067]
Mean: 69052.46136345083
Standard deviation: 2731.674001798348
可以看到DT模型過擬合驗(yàn)證集,表現(xiàn)甚至是要比線性回歸模型更差的
接下來嘗試最后一個(gè)模型:隨機(jī)森林.
隨機(jī)森林的原理是用隨機(jī)的特征子集訓(xùn)練大量的DT模型.然后基于大量的DT模型做出決策.這樣的思路是提升ML算法性能的一個(gè)重要方法.
In: from sklearn.ensemble import RandomForestRegressor
In: forest_reg = RandomForestRegressor()
In: forest_reg.fit(housing_prepared,housing_labels)
In: forest_score = cross_val_score(forest_reg,housing_prepared,housing_labels,scoring=
'neg_mean_squared_error',cv=10)
In: forest_rmse_scores = np.sqrt(-forest_score)
In: display_scores(forest_rmse_scores)
Out: Scores: [52139.11827562 49499.22947406 52429.83310745 55613.43640446
52186.19472032 56245.76235768 51450.03759208 51103.18141115
56223.7137723 53716.20236911]
Mean: 53060.67094842296
Standard deviation: 2195.8523393951145
計(jì)算隨機(jī)森林的rmse得分:
In: housing_predicions = forest_reg.predict(housing_prepared)
In: forest_rmse_scores = np.sqrt(mean_squared_error(housing_labels,housing_predicions))
In: forest_rmse_scores
Out: 22723.911950422345
我們可以看到隨機(jī)森林的效果提升明顯,是一個(gè)很有希望的方法.但是訓(xùn)練集上的結(jié)果依然比驗(yàn)證集上的得分小非常多,這說明模型依然是過擬合的.
為了解決過擬合,可以采取以下的措施:
- 簡(jiǎn)化模型,采用正則化的方式限制模型
- 使用更多的訓(xùn)練數(shù)據(jù)(防止過擬合,被部分?jǐn)?shù)據(jù)帶偏)
在深入隨機(jī)森林之前,你應(yīng)該嘗試下機(jī)器學(xué)習(xí)算法的其它類型模型(不同核心的支持向量機(jī),神經(jīng)網(wǎng)絡(luò),等等),不要在調(diào)節(jié)超參數(shù)上花費(fèi)太多時(shí)間。目標(biāo)是列出一個(gè)可能模型的列表(兩到五個(gè))。
模型微調(diào)
假定你已經(jīng)有了好幾個(gè)有希望的模型,那么你需要如何去調(diào)整優(yōu)化它們呢?
<簡(jiǎn)書不支持html這種標(biāo)記方式>網(wǎng)格搜索(調(diào)參神器) <我能怎么辦,我只能手動(dòng)標(biāo)注重點(diǎn)了 :-) >
- 手動(dòng)調(diào)參將是不現(xiàn)實(shí)也將是非常耗時(shí)且乏味的.
- scikit-learn提供了網(wǎng)格搜索GrideSearchCV方法為我們做參數(shù)搜索的工作.我們需要做的就是傳入我們想要試驗(yàn)的超參數(shù),參數(shù)值,該方法會(huì)使用交叉驗(yàn)證的方法評(píng)估所有可能的超參數(shù)的組合.
下面測(cè)試使用網(wǎng)格搜索為隨機(jī)森林搜尋最佳的超參數(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')
注: bootstrap 超參數(shù)指定了是否是有放回的采樣,默認(rèn)值是True即有放回.
注意:隨機(jī)采樣(bootsrap)就是從我們的訓(xùn)練集里面采集固定個(gè)數(shù)的樣本,但是每采集一個(gè)樣本后,都將樣本放回。也就是說,之前采集到的樣本在放回后有可能繼續(xù)被采集到。
param_grid傳入的grid_search是需要測(cè)定18種參數(shù)組合的,而每一種參數(shù)組合模型是會(huì)訓(xùn)練5次的(用了5折交叉驗(yàn)證,cv=5),換句話說將會(huì)有18 * 5 = 90次訓(xùn)練,也就是將有90個(gè)模型被生成評(píng)估.這是比較耗時(shí)的,但是完成的時(shí)候你可以拿到最好的超參數(shù)組合.
In: grid_search.fit(housing_prepared,housing_labels)
Out: GridSearchCV(cv=5, error_score='raise-deprecating',
estimator=RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators='warn', n_jobs=None,
oob_score=False, random_state=None, verbose=0, warm_start=False),
fit_params=None, iid='warn', n_jobs=None,
param_grid=[{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]}, {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]}],
pre_dispatch='2*n_jobs', refit=True, return_train_score='warn',
scoring='neg_mean_squared_error', verbose=0)
如果你不能確定超參數(shù)該用什么比較好,一個(gè)簡(jiǎn)單的方法就是使用連續(xù)的10的冪的數(shù)(如果想要進(jìn)行更小粒度的搜索,也可以采用更小的數(shù),例如本例中的n_estimators)
In: grid_search.best_params_
Out: {'max_features': 6, 'n_estimators': 30}
可以看到n_estimators的最佳參數(shù)是30(我們列舉的最大的值),因此此時(shí)我們可以嘗試將n_estimators更大的值列入搜尋的范圍,可能會(huì)有更好的效果.
我們也已經(jīng)拿到了最好的預(yù)測(cè)器
In: grid_search.best_estimator_
Out: RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
max_features=6, max_leaf_nodes=None, min_impurity_decrease=0.0,
min_impurity_split=None, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
n_estimators=30, n_jobs=None, oob_score=False,
random_state=None, verbose=0, warm_start=False)
查看每個(gè)參數(shù)組合下的模型的得分
cvres = grid_search.cv_results_
for mean_score,params in zip(cvres['mean_test_score'],cvres['params']):
print(np.sqrt(-mean_score), params)
Out:
63612.103000118135 {'max_features': 2, 'n_estimators': 3}
55711.8198772516 {'max_features': 2, 'n_estimators': 10}
52854.67439984241 {'max_features': 2, 'n_estimators': 30}
59625.31171120096 {'max_features': 4, 'n_estimators': 3}
53215.95533215571 {'max_features': 4, 'n_estimators': 10}
50325.738110031445 {'max_features': 4, 'n_estimators': 30}
59266.62134036455 {'max_features': 6, 'n_estimators': 3}
52476.67607546813 {'max_features': 6, 'n_estimators': 10}
49804.8632562366 {'max_features': 6, 'n_estimators': 30}
58402.64862222165 {'max_features': 8, 'n_estimators': 3}
52316.6376868916 {'max_features': 8, 'n_estimators': 10}
50030.72214217435 {'max_features': 8, 'n_estimators': 30}
62645.29839193287 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}
54350.05441732232 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}
59667.78209721833 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}
52949.122929664874 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}
58383.95829180255 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}
51925.63083312882 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}
使用{'max_features': 6, 'n_estimators': 30}參數(shù)組合能夠取得49804的成績(jī),相比之前的53060已經(jīng)有了不小的提升.模型微調(diào)是成功的.
<手動(dòng)強(qiáng)調(diào)>
當(dāng)然不能忘記:
- 可以把某些數(shù)據(jù)準(zhǔn)備步驟做的調(diào)整當(dāng)做是超參數(shù).可以在網(wǎng)格搜索中自動(dòng)地搜尋判斷最佳的特征組合方案,測(cè)試一些你不太清楚效果的特征.
- 同樣也可以用來自動(dòng)搜尋處理離群值,缺失值和特征選擇等繁瑣的操作.
<手動(dòng)強(qiáng)調(diào)>
隨機(jī)搜索(其實(shí)是一種更加推薦的方法)
(https://blog.csdn.net/juezhanangle/article/details/80051256) 還不錯(cuò)介紹的文章
當(dāng)你搜尋相對(duì)少的組合的時(shí)候,grid search還是比較ok的,就像前述的例子一樣. 但是超參數(shù)的搜尋域是非常大的,也就是不可能實(shí)現(xiàn)大范圍的搜索遍歷.
這種時(shí)候就需要隨機(jī)搜索RandomizedSearchCV的支持.
- 不再是搜尋所有可能組合的思路
- 在每一次迭代時(shí)都通過為超參數(shù)挑選隨機(jī)數(shù)值的方式來評(píng)估給定數(shù)量個(gè)隨機(jī)參數(shù)組合模型.
- 優(yōu)勢(shì)在于:
- 隨機(jī)搜索時(shí),如果運(yùn)行1000次,它會(huì)為每個(gè)超參數(shù)探索1000個(gè)不同值,而不像grid search那樣只針對(duì)特定的幾個(gè)超參數(shù)進(jìn)行搜尋.
- 你可以更好地通過設(shè)定迭代次數(shù)來控制計(jì)算開銷.
集成方法
另外一個(gè)調(diào)整系統(tǒng)的方法就是講表現(xiàn)較好的模型進(jìn)行組合,從而期望得到一個(gè)表現(xiàn)更好的模型.這種'ensemble'思路下的模型通常要比最好的單個(gè)模型表現(xiàn)都要好.
分析最佳模型以及它們的誤差
通過分析最佳模型,我們通常能夠?qū)栴}有更深入的了解.
比如RandomForestRegressor能夠指示出每個(gè)參數(shù)對(duì)做出最準(zhǔn)確預(yù)測(cè)的相對(duì)重要性:
In: feature_importance = grid_search.best_estimator_.feature_importances_
In: feature_importance
Out: array([7.41664388e-02, 7.07907175e-02, 4.28625371e-02, 1.79719493e-02,
1.62705597e-02, 1.86339608e-02, 1.61629091e-02, 3.75001961e-01,
4.39887397e-02, 1.07949019e-01, 5.90690293e-02, 1.08288098e-02,
1.38722570e-01, 8.04572063e-05, 2.69477214e-03, 4.80556934e-03])
我們可以將這些重要的分?jǐn)?shù)和對(duì)應(yīng)的屬性名稱放到一起:
extra_attribs = ['room_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_importance, attributes),reverse=True)
Out:
[(0.3750019612559819, 'median_income'),
(0.13872256966756555, 'INLAND'),
(0.1079490192331523, 'pop_per_hhold'),
(0.07416643880806785, 'longitude'),
(0.07079071748646316, 'latitude'),
(0.05906902934048595, 'bedrooms_per_room'),
(0.04398873969400665, 'room_per_hhold'),
(0.04286253709600471, 'housing_median_age'),
(0.01863396082921661, 'population'),
(0.017971949317441203, 'total_rooms'),
(0.016270559681538607, 'total_bedrooms'),
(0.016162909131536056, 'households'),
(0.010828809777764731, '<1H OCEAN'),
(0.004805569337738232, 'NEAR OCEAN'),
(0.002694772136742528, 'NEAR BAY'),
(8.045720629387519e-05, 'ISLAND')]
手動(dòng)強(qiáng)調(diào)
有了這個(gè)信息,你就可以丟棄一些不那么有用的特征(比如,顯然只要一個(gè)ocean_proximity分類就夠了,即OCEAN與否,所以可以丟棄掉其它的分類諸如NEAR OCEAN / NEAR BAY / ISLAND)。你還應(yīng)該看一下系統(tǒng)犯的誤差,搞清為什么會(huì)有些誤差,以及如何改正問題(添加更多的特征,或相反,去掉沒有什么信息的特征,清洗異常值等等)。
同時(shí)你還可以查看你的系統(tǒng)犯的一些具體的錯(cuò)誤,然后嘗試去理解為什么會(huì)犯錯(cuò)并嘗試去修復(fù)這些問題(比如添加額外的特征,取反,去掉一些沒有用的特征,清除離群值等等)
:-)
在測(cè)試集上評(píng)估你的模型
在微調(diào)模型后,我們已經(jīng)拿到了一個(gè)表現(xiàn)足夠不錯(cuò)的模型.
現(xiàn)在可以將這個(gè)最終模型用到測(cè)試集上進(jìn)行評(píng)估.
在這一步需要對(duì)測(cè)試集進(jìn)行一定的拆分 + 變形處理.但是要注意的是:這里的變形處理用的是transform(),而非fit_transform(),具體的緣由在前面已經(jīng)講過了
指定網(wǎng)格搜索的結(jié)果為最終的模型
In: final_model = grid_search.best_estimator_
In: X_test = strat_test_set.drop('median_house_value',axis=1)
In: y_test = strat_test_set['median_house_value'].copy()
In: X_test_prepared = full_pipeline.transform(X_test)
In: final_prediciton = final_model.predict(X_test_prepared)
In: final_mse = mean_squared_error(y_test,final_prediciton)
In: final_rmse = np.sqrt(final_mse)
In: final_rmse
Out: 48249.82755755361
可以看到我們的模型利用隨機(jī)森林達(dá)到的最終效果為 48249.8
手動(dòng)強(qiáng)調(diào):
這個(gè)final_rmse的結(jié)果通常要比我們交叉驗(yàn)證的結(jié)果要差一點(diǎn)點(diǎn)(因?yàn)槲覀兊哪P驮隍?yàn)證集上經(jīng)過調(diào)整,它會(huì)在陌生的集合上表現(xiàn)的不如在驗(yàn)證集上那么好).這種情況是比較正常的,你不能因?yàn)樵撃P驮跍y(cè)試集上表演稍差一點(diǎn)就根據(jù)測(cè)試集對(duì)模型進(jìn)行參數(shù)調(diào)整,這樣的調(diào)參依據(jù)通常是不現(xiàn)實(shí)的,因?yàn)榧词褂兴嵘膊粫?huì)體現(xiàn)到新的數(shù)據(jù)上去.
嘗試使用Xgboost
In: from xgboost import XGBRegressor
In: xgboost_reg = XGBRegressor()
In: xgboost_reg.fit(housing_prepared,housing_labels)
Out: XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bytree=1, gamma=0, learning_rate=0.1, max_delta_step=0,
max_depth=3, min_child_weight=1, missing=None, n_estimators=100,
n_jobs=1, nthread=None, objective='reg:linear', random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,
silent=True, subsample=1)
In: xgboost_scores = cross_val_score(xgboost_reg,housing_prepared,housing_labels,
scoring = 'neg_mean_squared_error',cv=10)
In: xgboost_rmse_scores = np.sqrt(-xgboost_scores)
In: display_scores(xgboost_rmse_scores)
Out:
Scores: [52056.36777099 49897.25202193 52875.58279806 55219.63304289
53215.38148905 56019.44318581 50929.49959547 50775.14967982 56506.79613124 53597.20486028]
Mean: 53109.231057552926
Standard deviation: 2153.951090593369
In: xgboost_prediciton = xgboost_reg.predict(housing_prepared)
In: xgboost_rmse = np.sqrt(mean_squared_error(housing_labels,xgboost_prediciton))
In: xgboost_rmse
Out: 50467.191745053715
可以看到Xgboost在未調(diào)參的情況下已經(jīng)有了不錯(cuò)的性能,并且由xgboost_rmse看出并沒有嚴(yán)重地過擬合現(xiàn)象發(fā)生
下面嘗試?yán)肦andomizedSearchCV對(duì)xgboost模型優(yōu)化參數(shù)
from sklearn.model_selection import RandomizedSearchCV
param_dist={
'n_estimators':range(80,200,4),
'max_depth':range(2,15,1),
'learning_rate':np.linspace(0.01,2,20),
'subsample':np.linspace(0.7,0.9,20),
'colsample_bytree':np.linspace(0.5,0.98,10),
'min_child_weight':range(1,9,1)
}
grid_search = RandomizedSearchCV(xgboost_reg,param_dist,n_iter=300,cv=10,scoring='neg_mean_squared_error',n_jobs = -1)
進(jìn)行了對(duì)xgboost的隨機(jī)搜索,迭代周期較長(zhǎng),下面選擇略去,模型保存在了ML_model中
In: grid_search.fit(housing_prepared,housing_labels)
In: grid_search.best_estimator_.feature_importances_
Out: array([0.14419934, 0.13962418, 0.08169935, 0.06437909, 0.04607843,
0.05400327, 0.03316994, 0.11683007, 0.09428105, 0.11503268,
0.08480392, 0.00833333, 0.01004902, 0. , 0.00277778,
0.00473856], dtype=float32)
In: grid_search.best_params_
Out:{'subsample': 0.8368421052631578,
'n_estimators': 156,
'min_child_weight': 3,
'max_depth': 7,
'learning_rate': 0.11473684210526315,
'colsample_bytree': 0.9266666666666666}
In: grid_search.best_estimator_
Out: XGBRegressor(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bytree=0.9266666666666666, gamma=0,
learning_rate=0.11473684210526315, max_delta_step=0, max_depth=7,
min_child_weight=3, missing=None, n_estimators=156, n_jobs=1,
nthread=None, objective='reg:linear', random_state=0, reg_alpha=0,
reg_lambda=1, scale_pos_weight=1, seed=None, silent=True,
subsample=0.8368421052631578)
In: xgboost_reg_final = grid_search.best_estimator_
為了重復(fù)訓(xùn)練耗費(fèi)時(shí)間,我們將訓(xùn)練好的模型存儲(chǔ)起來
In: from sklearn.externals import joblib
In: import os
In: os.chdir('C:/Users/Administrator/ML_model')
In: joblib.dump(xgboost_reg_final,'Canifornia_house_price_xgboostModel.pkl')
Out:['Canifornia_house_price_xgboostModel.pkl']
In: xgboost_reg_final_predictions = xgboost_reg_final.predict(X_test_prepared)
In: final_mse_xgboost = mean_squared_error(y_test,xgboost_reg_final_predictions)
In: final_rmse_xgboost = np.sqrt(final_mse_xgboost)
In: final_rmse_xgboost
Out: 44216.3972564512 # 這樣的成績(jī)已經(jīng)比較不錯(cuò)了
我們可以看到運(yùn)用Xgboost進(jìn)行建模,對(duì)結(jié)果有了較大的提升.
注:對(duì)于這個(gè)問題做到這樣的效果其實(shí)是不堪用的,RMSE依然維持在一個(gè)比較高的水平.不過,建模解決一個(gè)問題時(shí),到底能做到什么程度主要還是取決于問題的難度和數(shù)據(jù)的質(zhì)量.這個(gè)題目數(shù)據(jù)樣本數(shù)量和特征都很少,且特征沒有太大的操作空間.題目旨在讓大家熟悉一套建模流程,雖然各個(gè)環(huán)節(jié)都比較粗糙但也都多少有點(diǎn)意思.
最后附上一張思維導(dǎo)圖(放大可以清楚查看):


