不離不棄
芳齡永繼
概述
第二章假設(shè)我們將親自實踐一個端到端的機器學習項目寡润,體會真正的機器學習工程項目的過程吆玖。
實驗流程
實驗數(shù)據(jù)
從 StatLib 庫中選擇了加州住房價格的數(shù)據(jù)集闸与,該數(shù)據(jù)集基于1990年加州人口普查的數(shù)據(jù)。數(shù)據(jù)集下載地址:Dataset
下載并預覽數(shù)據(jù)
下載并解壓 housing.tgz
預覽數(shù)據(jù)
每一行代表一個區(qū)域眶熬,每個區(qū)域有10個相關(guān)屬性: longitude, latitude, housing_median_age, total_rooms, total_bed rooms, population, households, median_income, median_house_value, ocean_proximity
讀取并初步分析數(shù)據(jù)
- 讀取數(shù)據(jù)
- 查看數(shù)據(jù)結(jié)構(gòu)和描述
可知大部分屬性是float64類型的亡笑,ocean_proximity則是object们镜,該數(shù)據(jù)集中包含20640個實例,total_bedrooms只有20433個實例的妖,即207個區(qū)域缺失了這個特征 - 查看數(shù)據(jù)基本情況
由上一步可知,ocean_proximity則是object足陨,預覽數(shù)據(jù)時可知嫂粟,其是文本類型數(shù)據(jù),查看其存在多少種分類
可知其存在5種分類
繪制各屬性的頻數(shù)分布直方圖觀察數(shù)據(jù)分布情況
發(fā)現(xiàn)房價墨缘、房齡兩個屬性被設(shè)置了上限星虹,大量超過限制的實例被壓縮到末尾區(qū)間內(nèi),以及部分屬性重尾(右側(cè)延伸大于其左側(cè)延伸)镊讼,之后需要注意這些問題
import pandas as pd
import matplotlib.pyplot as plt
def load_housing_data(housing_path):
return pd.read_csv(housing_path)
if __name__ == '__main__':
housing = load_housing_data('housing.csv')
housing.head()
housing.info()
housing['ocean_proximity'].value_counts()
housing.describe()
housing.hist(bins=50, figsize=(20,15))
plt.savefig('housing_distribution.png')
創(chuàng)建測試集
選取數(shù)據(jù)集的20%作為測試集宽涌,首先我們?yōu)榕懦藶橛绊懀S機選取數(shù)據(jù)的20%作為測試集狠毯,其分布如下
思索一下护糖,發(fā)現(xiàn)問題似乎沒有這么簡單,隨機抽樣固然排除了人為因素嚼松,但是容易造成抽樣偏差嫡良,假設(shè)一個地區(qū)有70%的男生,30%的女生献酗,完全隨機抽樣有可能全部抽出男生寝受,那么此時測試集就不能代表整體了。因而我們?yōu)楸WC測試集具有代表性罕偎,會選擇分層抽樣很澄。此例中我們按照地區(qū)收入中位數(shù)進行分層抽樣:
- 首先觀察收入中位數(shù)的分布,發(fā)現(xiàn)大部分人處于2~3w左右
- 收入數(shù)據(jù)為連續(xù)值颜及,我們四舍五入對其取整甩苛,將其離散化
- 為減少離散值類數(shù)目,將大于5w的部分歸于5w類目中
- 按此比例對原數(shù)據(jù)進行分層抽樣
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
housing['income_cat'] = np.ceil(housing['median_income'] / 1.5)
housing['income_cat'].where(housing['income_cat'] < 5, 5.0, inplace=True)
spliter = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in spliter.split(housing,housing['income_cat']):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
for data_set in (strat_train_set, strat_test_set):
data_set.drop(['income_cat'], axis=1 ,inplace=True)
PS:測試集的構(gòu)建很重要俏站,卻往往被忽視讯蒲,因此機器學習項目中需要注意
數(shù)據(jù)探索和可視化
首先將測試集放在一邊,我們只能夠在訓練集上進行數(shù)據(jù)探索(如果數(shù)據(jù)量比較大肄扎,可以先提取出一個較小的探索集)墨林,這樣才不會損壞訓練集。
-
將地理數(shù)據(jù)可視化
可見與加州地圖輪廓類似犯祠,但也看不出什么了旭等,因而改變alpha參數(shù),觀察實例分布密度
可見有明顯的高密度區(qū)域衡载,就是灣區(qū)搔耕、洛杉磯等地。 -
將人口月劈、房價信息可視化
圖中每個圓的半徑代表人口數(shù)量度迂,顏色代表房價高低(藍色為低藤乙,紅色為高),可見人口與房價的關(guān)系惭墓。 -
尋找特征之間的相關(guān)性
查看各屬性與房價之間的皮爾森相關(guān)性系數(shù)
相關(guān)性從-1~1坛梁。越接近1,就有越強的正相關(guān)腊凶;越接近-1划咐,就有越強的負相關(guān)。從圖中可知钧萍,一個地區(qū)收入中位數(shù)越高褐缠,房價越高;而越往北风瘦,房價越低队魏。而與房價相關(guān)性最高的特征是收入。 -
繪制收入万搔、房價散點圖
二者相關(guān)性的確很強胡桨,但是50w以上的部分是一條直線,因為我們將50w以上的實例全部歸類于50w這一類了瞬雹。仔細觀察昧谊,發(fā)現(xiàn)45w、35w酗捌、23w附近貌似也有這樣的直線呢诬,值得注意。 -
嘗試不同屬性的組合
比如目前知道每個地區(qū)的房間數(shù)胖缤,但并沒有什么用尚镰,如果知道了每個家庭的房間數(shù),可能就大大不同了哪廓,因此可以對屬性進行組合钓猬。
發(fā)現(xiàn)每個家庭的房間數(shù)、每個房間的臥室數(shù)與房價也很有相關(guān)性housing = strat_train_set.copy() housing.plot(kind='scatter', x='longitude', y='latitude') plt.savefig('gregrophy.png') housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.1) plt.savefig('gregrophy_more.png') fig = plt.scatter(x=housing['longitude'], y=housing['latitude'], alpha=0.4, \ s=housing['population']/100, label='population', \ c=housing['median_house_value'], cmap=plt.get_cmap('jet')) plt.colorbar(fig) plt.legend() plt.savefig('gregrophy_population_value.png') corr_matrix = housing.corr() corr_matrix['median_house_value'].sort_values(ascending=False) housing.plot(kind='scatter', x='median_income', y='median_house_value', alpha=0.1) plt.savefig('income_value.png') 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'] corr_matrix = housing.corr() corr_matrix['median_house_value'].sort_values(ascending=False)
數(shù)據(jù)準備
準備干凈的數(shù)據(jù)集撩独,將預測器與標簽分開
-
數(shù)據(jù)清洗
- 放棄這些相應(yīng)的地區(qū)
- 放棄這個屬性
- 將缺失的值設(shè)為某個值(0、平均數(shù)或中位數(shù)等)
我選擇用中位數(shù)填補缺失值
填補之后账月,可見數(shù)據(jù)集已經(jīng)沒有缺失部分了
housing = strat_train_set.drop('median_house_value', axis=1) housing_labels = strat_train_set['median_house_value'].copy() imputer = Imputer(strategy='median') housing_num = housing.drop('ocean_proximity', axis=1) imputer.fit(housing_num) imputer.statistics_ X = imputer.transform(housing_num) housing_tr = pd.DataFrame(X, columns=housing_num.columns)
-
處理文本與分類屬性
之前ocean_proximity為文本屬性综膀,無法進行數(shù)值計算,因此我們需要將其轉(zhuǎn)化為數(shù)字局齿,但是此時算法會將其理解為純數(shù)值進行運算剧劝,這是不對的,我們的原始數(shù)據(jù)是分類數(shù)據(jù)抓歼,是類別讥此,不能用于加減乘除等運算拢锹,因此我們需要將其轉(zhuǎn)換成獨熱編碼。
encoder = LabelEncoder() housing_cat = housing['ocean_proximity'] housing_cat_encoded = encoder.fit_transform(housing_cat) encoder = OneHotEncoder() housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1)) encoder = LabelBinarizer() housing_cat_1hot = encoder.fit_transform(housing_cat)
-
自定義轉(zhuǎn)換器
之前討論過組合屬性形成新的屬性萄喳,我們也希望可以引入新的屬性卒稳,但是又希望這個過程可以與Scikit-Learn的流水線無縫銜接,因此可以自定義轉(zhuǎn)換器他巨。所需要的只是新創(chuàng)建一個類充坑,應(yīng)用fit()、transform()染突、fit_transform()就可以了捻爷。如果添加TransformerMixin作為基類,可以直接獲得最后一個方法份企;添加BaseEstimator作為基類也榄,則可以獲得兩個自動調(diào)整超參數(shù)的方法get_params()和set_params()。
增加前:
增加后:
新增加了每間房房間數(shù)量和每戶家庭人口數(shù)兩個組合屬性class CombinedAttributesAdder(BaseEstimator, TransformerMixin): """docstring for CombinedAttributesAdder""" def __init__(self, add_bedrooms_per_room=True): self.add_bedrooms_per_room = add_bedrooms_per_room def fit(self, X, y=None): return self 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]
-
轉(zhuǎn)換流水線
許多數(shù)據(jù)轉(zhuǎn)換需要正確的步驟來運行司志,因此需要設(shè)置流水線工作甜紫。我仿照書上寫這段代碼時,發(fā)現(xiàn)由于版本更新俐芯,這里失效了:
經(jīng)檢查是LabelBinarizer更新后只接收兩個參數(shù)棵介,因此重寫了一個自己的MyLabelBinarizerclass MyLabelBinarizer(BaseEstimator, TransformerMixin): """docstring for DataFrameSelector""" def __init__(self): self.encoder = LabelBinarizer() def fit(self, X, y=None): return encoder.fit(X) def transform(self, X, y=None): return encoder.transform(X)
準備數(shù)據(jù)成功
選擇和訓練模型
-
培訓和評估訓練集
使用線性回歸模型,看看預測情況如何#train the model 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: ", lin_reg.predict(some_data_prepared)) print("Labels: ", list(some_labels)) housing_predictions = lin_reg.predict(housing_prepared) lin_mse = mean_squared_error(housing_labels, housing_predictions) lin_rmse = np.sqrt(lin_mse)
部分預測結(jié)果吧史,效果不盡如人意邮辽,利用RMSE進行評估
效果確實不行!使用決策樹模型贸营,看看情況如何
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)
```
情況喜人6质觥!但是轉(zhuǎn)念一想钞脂,100%正確揣云,這不是過擬合了吧?進行模型驗證評估1小邓夕!
-
驗證模型
選擇交叉驗證方法驗證模型
效果真的爛,保險起見阎毅,檢查一下線性回歸模型
也很差勁焚刚,換模型!扇调!#valid models tree_scores = cross_val_score(tree_reg, housing_prepared, housing_labels, scoring='neg_mean_squared_error', cv=10) tree_rmse_scores = np.sqrt(-tree_scores) display_scores(tree_rmse_scores) 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)
-
使用隨機森林
情況爛到家了矿咕,理論上來說,不應(yīng)該,選其他模型或者調(diào)參數(shù) -
調(diào)參
利用網(wǎng)格搜索使用不同參數(shù)組合碳柱,找出最好參數(shù)結(jié)果
查看整體情況
此時隨機森林的最佳參數(shù)是max_features為6捡絮,n_estimators為30時,RMSE=50019#improve the models 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) grid_search.best_params_ cvres = grid_search.cv_results_ for mean_score, params in zip(cvres['mean_test_score'], cvres['params']): print(np.sqrt(-mean_score), params)
-
分析最佳模型及其錯誤
查看每個特征的重要性
可見收入是最重要的特征#analyse the model feature_importances = grid_search.best_estimator_.feature_importances_ 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)
-
通過測試集評估系統(tǒng)
最終誤差可以接受#final model final_model = grid_search.best_estimator_ X_test = strat_test_set.drop('median_house_value', axis=1) y_teat = strat_test_set['median_house_value'].copy() X_test_prepared = full_pipeline.fit_transform(X_test) final_predictons = final_model.predict(X_test_prepared) final_mse = mean_squared_error(y_teat, final_predictons) final_rmse = np.sqrt(final_mse)
啟動莲镣、監(jiān)控和維護系統(tǒng)
后記
性能指標
回歸問題的典型性能衡量指標是均方根誤差(RMSE)福稳,它測量的是預測過程中預測錯誤的標準偏差。
當數(shù)據(jù)離群點很多時剥悟,可以考慮使用平均絕對誤差(MAE)
特征縮放
如果輸入的數(shù)據(jù)有較大的比例差異灵寺,可以考慮對特征數(shù)值進行縮放,通常有兩種方法:最大-最小縮放区岗、標準化
最大-最小縮放:歸一化略板,將數(shù)值縮放到0~1之間,實現(xiàn)方法是將值減去最小值并除以最大值和最小值的差
標準化:首先減去平均值慈缔,再除以方差