基本步驟
- 縱覽全局
- 獲取數(shù)據(jù)
- 數(shù)據(jù)可視化、找規(guī)律
- 準(zhǔn)備用于機(jī)器學(xué)習(xí)算法的數(shù)據(jù)
- 選擇模型并進(jìn)行訓(xùn)練
- 模型微調(diào)
- 展示解決方案
- 系統(tǒng)維護(hù)
真實(shí)數(shù)據(jù)來(lái)源
UC Irvine Machine Learning Repository
Kaggle datasets
AWS datasets
Data Portals
opendatamonitor.eu
quandl
本文選取StatLib 的加州房產(chǎn)價(jià)格數(shù)據(jù)集,如下圖。
1. 縱覽全局
任務(wù)是利用加州普查數(shù)據(jù),建立一個(gè)加州房?jī)r(jià)模型。這個(gè)數(shù)據(jù)包含每個(gè)街區(qū)組的人口、收入中位數(shù)、房?jī)r(jià)中位數(shù)等指標(biāo)。
我們的模型要利用這個(gè)數(shù)據(jù)進(jìn)行學(xué)習(xí),然后根據(jù)其它指標(biāo),預(yù)測(cè)任何街區(qū)的的房?jī)r(jià)中位數(shù)。
劃定問(wèn)題
商業(yè)目標(biāo)是什么?設(shè)計(jì)的系統(tǒng)將如何被使用?
老板告訴你你的模型的輸出(一個(gè)區(qū)的房?jī)r(jià)中位數(shù))會(huì)傳給另一個(gè)機(jī)器學(xué)習(xí)系統(tǒng),也有其它信號(hào)會(huì)傳入后面的系統(tǒng)。這一整套系統(tǒng)可以確定某個(gè)區(qū)進(jìn)行投資值不值。確定值不值得投資非常重要,它直接影響利潤(rùn)。
如果有,現(xiàn)在的解決方案效果如何?
老板說(shuō),現(xiàn)在街區(qū)的房?jī)r(jià)是靠專(zhuān)家手工估計(jì)的,專(zhuān)家隊(duì)伍收集最新的關(guān)于一個(gè)區(qū)的信息(不包括房?jī)r(jià)中位數(shù)),他們使用復(fù)雜的規(guī)則進(jìn)行估計(jì)。這種方法費(fèi)錢(qián)費(fèi)時(shí)間,而且估計(jì)結(jié)果不理想。
確定是哪種機(jī)器學(xué)習(xí)問(wèn)題
這個(gè)問(wèn)題是典型監(jiān)督學(xué)習(xí)的問(wèn)題,每個(gè)實(shí)例都有標(biāo)簽,即街區(qū)房?jī)r(jià)的中位數(shù)。
這個(gè)問(wèn)題也是典型的回歸問(wèn)題,是一個(gè)多變量回歸問(wèn)題(人口、收入等),來(lái)預(yù)測(cè)一個(gè)值。
最后,這是一個(gè)批量學(xué)習(xí)問(wèn)題,因?yàn)閿?shù)據(jù)量完全可以放到內(nèi)存中。
選擇性能指標(biāo) Performance Measurement
回歸問(wèn)題的典型指標(biāo)是均方根誤差(Root Mean Square Error)
m: 實(shí)例數(shù)量
x(i): 實(shí)例i的特征向量
y(i): 實(shí)例i的標(biāo)簽
h: 系統(tǒng)預(yù)測(cè)函數(shù),也成為假設(shè)(hypothesis)
另外一種性能指標(biāo)是平均絕對(duì)誤差(Mean Absolute Error,Average Absolute Deviation)
2. 獲取數(shù)據(jù)
https://github.com/ageron/handson-ml/tree/master/datasets
創(chuàng)建workspace
如果需要獨(dú)立的工作環(huán)境,請(qǐng)自行搜索virtualenv的用法。大致方法如下:
# 安裝virtualenv
pip install --user --upgrade virtualenv
# 創(chuàng)建獨(dú)立的python環(huán)境,為了在不同的工作環(huán)境中的庫(kù)的版本不沖突
virtualenv myenv
# 使用myenv(source .sh 或者 .bat)
myenv/Scripts/activate
pycharm中在選擇python interpreter的時(shí)候也可以創(chuàng)建和指定virtualenv。
python環(huán)境配置參考另一篇簡(jiǎn)書(shū),需要安裝的庫(kù)包括numpy,pandas,matplotlib,scikit-learn
JupyerLab的使用請(qǐng)參考這篇簡(jiǎn)書(shū)。
修改pip源,參考這篇文章
pip install --upgrade matplotlib numpy pandas scipy scikit-learn jupyter jupyterlab
下載數(shù)據(jù)
下載數(shù)據(jù)壓縮包并解壓
import os
import tarfile
import urllib.request
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
HOUSING_PATH = "datasets/housing"
HOUSING_URL = DOWNLOAD_ROOT + HOUSING_PATH + "/housing.tgz"
def fetch_housing_data(housing_url=HOUSING_URL):
if not os.path.isdir(HOUSING_PATH):
os.makedirs(HOUSING_PATH)
tgz_path = os.path.join(HOUSING_PATH, "housing.tgz")
urllib.request.urlretrieve(housing_url, tgz_path)
housing_tgz = tarfile.open(tgz_path)
housing_tgz.extractall(path=HOUSING_PATH)
housing_tgz.close()
fetch_housing_data()
查看數(shù)據(jù)
目測(cè)數(shù)據(jù)
import pandas as pd
def load_housing_data(housing_path=HOUSING_PATH):
csv_path = os.path.join(housing_path, "housing.csv")
return pd.read_csv(csv_path)
raw_data = load_housing_data()
raw_data.head()
每行數(shù)據(jù)表示一個(gè)街區(qū)。共十個(gè)屬性longitude, latitude, housing_median_age, total_rooms, total_bedrooms, population, households, median_income, median_house_value, ocean_proximity。
查看整體數(shù)據(jù)結(jié)構(gòu)
raw_data.info()
可以看到total_bedroom不是所有數(shù)據(jù)都有值(20433/20640),處理的時(shí)候需要小心。
ocean_proximity顯然是個(gè)枚舉值,可以通過(guò)下面的方法查詢(xún)所有的枚舉值。
raw_data["ocean_proximity"].value_counts()
查看所有屬性的基本信息
raw_data.describe()
用matplotlib可視化數(shù)據(jù)
import matplotlib.pyplot as plt
# 直方圖50個(gè)桶, 2000*1000像素
raw_data.hist(bins=50, figsize=(20, 10))
plt.show()
創(chuàng)建測(cè)試集
隨機(jī)從數(shù)據(jù)中抽取20%作為測(cè)試數(shù)據(jù)。
import numpy as np
def split_train_test(data, test_ratio):
# seed 方法保證相同的種子每次隨機(jī)生成的數(shù)組一致,即保證了測(cè)試集的一致。
np.random.seed(714)
'''
numpy.random中函數(shù)shuffle與permutation都是對(duì)原來(lái)的數(shù)組進(jìn)行重新洗牌(即隨機(jī)打亂原來(lái)的元素順序);
區(qū)別在于shuffle直接在原來(lái)的數(shù)組上進(jìn)行操作,改變?cè)瓉?lái)數(shù)組的順序,無(wú)返回值。
而permutation不直接在原來(lái)的數(shù)組上進(jìn)行操作,而是返回一個(gè)新的打亂順序的數(shù)組,并不改變?cè)瓉?lái)的數(shù)組。
當(dāng)然,這里只是數(shù)組下標(biāo)的打亂。
'''
shuffled_indices = np.random.permutation(len(data))
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
# iloc: 根據(jù)標(biāo)簽的所在位置,從0開(kāi)始計(jì)數(shù),選取列
return data.iloc[train_indices], data.iloc[test_indices]
train_set, test_set = split_train_test(raw_data, 0.2)
print(len(train_set), "train +", len(test_set), "test")
#16512 train + 4128 test
那么問(wèn)題來(lái)了,如果源數(shù)據(jù)有更新,如何保證測(cè)試集不變?
一個(gè)通常的解決辦法是使用每個(gè)實(shí)例的ID來(lái)判定這個(gè)實(shí)例是否應(yīng)該放入測(cè)試集(假設(shè)每個(gè)實(shí)例都有唯一并且不變的ID)。例如,你可以計(jì)算出每個(gè)實(shí)例ID的哈希值,只保留其最后一個(gè)字節(jié),如果該值小于等于 51(約為 256 的20%),就將其放入測(cè)試集。
import hashlib
def test_set_check(identifier, test_ratio, hash):
return hash(np.int64(identifier)).digest()[-1] < 256 * test_ratio
def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5):
ids = data[id_column]
in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio, hash))
return data.loc[~in_test_set], data.loc[in_test_set]
# 添加一列index,從0開(kāi)始
raw_data_with_id = raw_data.reset_index()
train_set, test_set = split_train_test_by_id(raw_data_with_id, 0.2, "index")
'''
更好的辦法是選擇永遠(yuǎn)不會(huì)變的index:
raw_data_with_id["index"] = raw_data["longitude"] * 1000 + housing["latitude"]
因?yàn)榻?jīng)緯度是永遠(yuǎn)不會(huì)變的
hashlib基本用法:
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?')
print(md5.hexdigest())
'''
Scikit-Learn中提供了分割數(shù)據(jù)集的函數(shù),最簡(jiǎn)單的是train_test_split
from sklearn.model_selection import train_test_split train_set, test_set = train_test_split(raw_data, test_size=0.2, random_state=714) print(len(train_set), "train +", len(test_set), "test") # 16512 train + 4128 test
分層采樣(得用這個(gè))
目前為止的采樣都是隨機(jī)采樣,在數(shù)據(jù)集非常大的時(shí)候沒(méi)有問(wèn)題,但如果數(shù)據(jù)集不大,就需要分層采樣(stratified sampling),從每個(gè)分層取合適數(shù)量的實(shí)例,以保證測(cè)試集具有代表性。
import numpy as np
'''
根據(jù)原始數(shù)據(jù)直方圖中median_income的分布,新增一列income_cat,將數(shù)據(jù)映射到1-5之間
'''
raw_data["income_cat"] = np.ceil(raw_data["median_income"] / 1.5) # ceil 向上取整
raw_data["income_cat"].where(raw_data["income_cat"] < 5, 5.0, inplace=True) # where(condition, other=NAN), 滿(mǎn)足condition,則保留,不滿(mǎn)足取other
raw_data["income_cat"].value_counts() / len(raw_data) # 查看不同收入分類(lèi)的比例
# 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
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42) # n_splites 將訓(xùn)練數(shù)據(jù)分成train/test對(duì)的組數(shù)
for train_index, test_index in split.split(raw_data, raw_data["income_cat"]): # split.split(X, y, groups=None) 根據(jù)y對(duì)X進(jìn)行分割
strat_train_set = raw_data.loc[train_index]
strat_test_set = raw_data.loc[test_index]
'''
pandas中iloc和loc的區(qū)別:
iloc主要使用數(shù)字來(lái)索引數(shù)據(jù),而不能使用字符型的標(biāo)簽來(lái)索引數(shù)據(jù)。而loc則剛好相反,只能使用字符型標(biāo)簽來(lái)索引數(shù)據(jù),不能使用數(shù)字來(lái)索引數(shù)據(jù)
'''
# 最后在數(shù)據(jù)中刪除添加的income_cat列
for set in (strat_train_set, strat_test_set):
set.drop(["income_cat"], axis=1, inplace=True)
# drop函數(shù)默認(rèn)刪除行,列需要加axis = 1, 它不改變?cè)械膁f中的數(shù)據(jù),而是返回另一個(gè)dataframe來(lái)存放刪除后的數(shù)據(jù)。
3. 數(shù)據(jù)可視化、探索規(guī)律
創(chuàng)建數(shù)據(jù)副本
housing = strat_train_set.copy()
可視化
首先很直觀的,看下經(jīng)緯度的散點(diǎn)圖。
housing.plot(kind="scatter", x="longitude", y="latitude")
將alpha設(shè)為0.1可以看出地理位置信息的密度分布。
再加入人口和房?jī)r(jià)信息,每個(gè)圈的半徑表示人口(population),圈的顏色表示房?jī)r(jià)(median_house_value)。
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)
plt.legend()
目測(cè)的規(guī)律:房?jī)r(jià)與人口密度密切相關(guān),離大海的距離也是一個(gè)很有用的屬性。
查找關(guān)聯(lián)
相關(guān)系數(shù)的直觀形態(tài)
方式一
# 計(jì)算每個(gè)屬性之間的標(biāo)準(zhǔn)相關(guān)系數(shù)(也稱(chēng)作皮爾遜相關(guān)系數(shù))
corr_matrix = housing.corr()
# 查看每個(gè)屬性和median_house_value的相關(guān)系數(shù),數(shù)值在[-1,1]之間。
corr_matrix["median_house_value"].sort_values(ascending=False)
方式二
from pandas.plotting import scatter_matrix
# 計(jì)算一下四個(gè)屬性之間的關(guān)聯(lián)性
attributes = ["median_house_value", "median_income", "total_rooms", "housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12, 8))
median_income與房?jī)r(jià)的相關(guān)性最大,把這個(gè)圖單獨(dú)拿出來(lái)。
housing.plot(kind="scatter", x="median_income",y="median_house_value", alpha=0.1)
屬性組合試驗(yàn)
給算法準(zhǔn)備數(shù)據(jù)之前,你需要做的最后一件事是嘗試多種屬性組合。例如,如果你不知道某個(gè)街區(qū)有多少戶(hù),該街區(qū)的總房間數(shù)就沒(méi)什么用。你真正需要的是每戶(hù)有幾個(gè)房間。相似的,總臥室數(shù)也不重要:你可能需要將其與房間數(shù)進(jìn)行比較。每戶(hù)的人口數(shù)也是一個(gè)有趣的屬性組合。讓我們來(lái)創(chuàng)建這些新的屬性:
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)
小結(jié)
這一步的數(shù)據(jù)探索不必非常完備,此處的目的是有一個(gè)正確的開(kāi)始,快速發(fā)現(xiàn)規(guī)律,以得到一個(gè)合理的原型。但是這是一個(gè)交互過(guò)程:一旦你得到了一個(gè)原型,并運(yùn)行起來(lái),你就可以分析它的輸出,進(jìn)而發(fā)現(xiàn)更多的規(guī)律,然后再回到數(shù)據(jù)探索這步。
4. 準(zhǔn)備用于機(jī)器學(xué)習(xí)算法的數(shù)據(jù)
先把屬性和標(biāo)簽分開(kāi)
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()
數(shù)據(jù)清洗
之前注意到有一些街區(qū)的total_bedrooms屬性缺失
三種處理方法
# 去掉對(duì)應(yīng)的街區(qū)
housing.dropna(subset=["total_bedrooms"])
# 去掉整個(gè)屬性
housing.drop("total_bedrooms", axis=1)
# 進(jìn)行賦值(0、平均值、中位數(shù)等等)
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median)
Scikit-Learn 提供了一個(gè)方便的類(lèi)來(lái)處理缺失值: Imputer
from sklearn.preprocessing import Imputer imputer = Imputer(strategy="median") # 因?yàn)橹挥袛?shù)值屬性才能算出中位數(shù),我們需要?jiǎng)?chuàng)建一份不包括文本屬性 ocean_proximity 的數(shù)據(jù)副本 housing_num = housing.drop("ocean_proximity", axis=1) # imputer 計(jì)算出了每個(gè)屬性的中位數(shù),并將結(jié)果保存在了實(shí)例變量 statistics_ 中。 imputer.fit(housing_num) # 使用這個(gè)“訓(xùn)練過(guò)的” imputer 來(lái)對(duì)訓(xùn)練集進(jìn)行轉(zhuǎn)換,將缺失值替換為中位數(shù) X = imputer.transform(housing_num) # 結(jié)果是一個(gè)包含轉(zhuǎn)換后特征的普通的 Numpy 數(shù)組。將其放回到Pandas DataFrame 中。 housing_tr = pd.DataFrame(X, columns=housing_num.columns)
處理文本和類(lèi)別屬性 Categorical Attributes
數(shù)機(jī)器學(xué)習(xí)算法喜歡和數(shù)字打交道,所以將文本轉(zhuǎn)換為數(shù)字
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]
'''array([[0.],
[0.],
[4.],
[1.],
[0.],
[1.],
[0.],
[1.],
[0.],
[0.]])'''
ordinal_encoder.categories_
# [array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'], dtype=object)]
'''
這種做法的問(wèn)題是,機(jī)器學(xué)習(xí)算法會(huì)認(rèn)為兩個(gè)臨近的值比兩個(gè)疏遠(yuǎn)的值要更相似,顯然這樣不對(duì)。
要解決這個(gè)問(wèn)題,一個(gè)常見(jiàn)的方法是給每個(gè)分類(lèi)創(chuàng)建一個(gè)二元屬性:
當(dāng)分類(lèi)是 <1H OCEAN ,該屬性為 1(否則為 0),當(dāng)分類(lèi)是 INLAND ,另一個(gè)屬性等于 1(否則為 0),以此類(lèi)推。
這稱(chēng)作獨(dú)熱編碼(One-Hot Encoding),因?yàn)橹挥幸粋€(gè)屬性會(huì)等于 1(熱),其余會(huì)是 0(冷)。
'''
# OneHotEncoder ,用于將整數(shù)分類(lèi)值轉(zhuǎn)變?yōu)楠?dú)熱向量。注意 fit_transform() 用于 2D 數(shù)組,而 housing_cat_encoded 是一個(gè) 1D 數(shù)組,所以需要將其變形
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder()
housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
housing_cat_1hot # 輸出是系數(shù)矩陣
#<16512x5 sparse matrix of type '<class 'numpy.float64'>'
# with 16512 stored elements in Compressed Sparse Row format>
housing_cat_1hot.toarray() # 轉(zhuǎn)換成密集矩陣,或者在初始化的時(shí)候 cat_encoder = OneHotEncoder(sparse=False)
'''
array([[1., 0., 0., 0., 0.],
[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
...,
[0., 0., 1., 0., 0.],
[1., 0., 0., 0., 0.],
[0., 0., 0., 1., 0.]])
'''
自定義轉(zhuǎn)換器
盡管 Scikit-Learn 提供了許多有用的轉(zhuǎn)換器,你還是需要自己動(dòng)手寫(xiě)轉(zhuǎn)換器執(zhí)行任務(wù),比如自定義的清理操作,或?qū)傩越M合。你需要讓自制的轉(zhuǎn)換器與 Scikit-Learn 組件(比如流水線(xiàn))無(wú)縫銜接工作,因?yàn)?Scikit-Learn 是依賴(lài)?guó)喿宇?lèi)型的(而不是繼承),你所需要做的是創(chuàng)建一個(gè)類(lèi)并執(zhí)行三個(gè)方法: fit() (返回 self ), transform() ,和 fit_transform() 。通過(guò)添加 TransformerMixin 作為基類(lèi),可以很容易地得到最后一個(gè)。另外,如果你添加 BaseEstimator 作為基類(lèi)(且構(gòu)造器中避免使用 *args 和 **kargs ),你就能得到兩個(gè)額外的方法( get_params() 和 set_params() ),二者可以方便地進(jìn)行超參數(shù)自動(dòng)微調(diào)。例如,一個(gè)小轉(zhuǎn)換器類(lèi)添加了上面討論的屬性:
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)
特征縮放
線(xiàn)性歸一化(Min-Max scaling、normalization)
通過(guò)減去最小值,然后再除以最大值與最小值的差值,來(lái)進(jìn)行歸一化。Scikit-Learn 提供了一個(gè)轉(zhuǎn)換器 MinMaxScaler 來(lái)實(shí)現(xiàn)這個(gè)功能。它有一個(gè)超參數(shù) feature_range ,可以讓你改變范圍,如果不希望范圍是 0 到 1。
標(biāo)準(zhǔn)化(standardization)
首先減去平均值(所以標(biāo)準(zhǔn)化值的平均值總是 0),然后除以方差,使得到的分布具有單位方差。標(biāo)準(zhǔn)化受到異常值的影響很小。Scikit-Learn 提供了一個(gè)轉(zhuǎn)換器 StandardScaler 來(lái)進(jìn)行標(biāo)準(zhǔn)化。
轉(zhuǎn)換流水線(xiàn)
數(shù)據(jù)處理過(guò)程存在許多數(shù)據(jù)轉(zhuǎn)換步驟,需要按一定的順序執(zhí)行。幸運(yùn)的是,Scikit-Learn 提供了類(lèi) Pipeline ,來(lái)進(jìn)行這一系列的轉(zhuǎn)換。下面是一個(gè)數(shù)值屬性的小流水線(xiàn):
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import Imputer
from sklearn.compose import ColumnTransformer
'''
構(gòu)造器需要一個(gè)定義步驟順序的名字/估計(jì)器對(duì)的列表。除了最后一個(gè)估計(jì)器,其余都要是轉(zhuǎn)換器(即,它們都要有 fit_transform() 方法)。
當(dāng)你調(diào)用流水線(xiàn)的 fit() 方法,就會(huì)對(duì)所有轉(zhuǎn)換器順序調(diào)用 fit_transform() 方法,將每次調(diào)用的輸出作為參數(shù)傳遞給下一個(gè)調(diào)用,一直到最后一個(gè)估計(jì)器,它只執(zhí)行 fit() 方法。
'''
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
num_pipeline = Pipeline([
('imputer', Imputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])
full_pipeline = ColumnTransformer([
("num", num_pipeline, num_attribs),
("cat", OneHotEncoder(), cat_attribs),
])
housing_prepared = full_pipeline.fit_transform(housing)
'''
array([[-1.15604281, 0.77194962, 0.74333089, ..., 0. ,
0. , 0. ],
[-1.17602483, 0.6596948 , -1.1653172 , ..., 0. ,
0. , 0. ],
[ 1.18684903, -1.34218285, 0.18664186, ..., 0. ,
0. , 1. ],
...,
[ 1.58648943, -0.72478134, -1.56295222, ..., 0. ,
0. , 0. ],
[ 0.78221312, -0.85106801, 0.18664186, ..., 0. ,
0. , 0. ],
[-1.43579109, 0.99645926, 1.85670895, ..., 0. ,
1. , 0. ]])
'''
housing_prepared.shape
# (16512, 16)
5. 選擇模型并進(jìn)行訓(xùn)練
先訓(xùn)練一個(gè)線(xiàn)性回歸模型
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
計(jì)算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.19819848922
因此預(yù)測(cè)誤差 68628 不能讓人滿(mǎn)意。這是一個(gè)模型欠擬合訓(xùn)練數(shù)據(jù)的例子。當(dāng)這種情況發(fā)生時(shí),意味著特征沒(méi)有提供足夠多的信息來(lái)做出一個(gè)好的預(yù)測(cè),或者模型并不強(qiáng)大。
換一個(gè)決策樹(shù)模型
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 大概率過(guò)擬合了
交叉驗(yàn)證
'''
隨機(jī)地將訓(xùn)練集分成十個(gè)不同的子集,然后訓(xùn)練評(píng)估決策樹(shù)模型 10 次,每次選一個(gè)不用的折來(lái)做評(píng)估,用其它 9 個(gè)來(lái)做訓(xùn)練。結(jié)果是一個(gè)包含10 個(gè)評(píng)分的數(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)
tree_rmse_scores = np.sqrt(-scores)
tree_rmse_scores
# array([68274.11882883, 66569.14495813, 72556.31339841, 68235.85607159,
# 70706.44616166, 73298.7766776 , 70404.07783425, 71858.98228216,
# 77435.9399421 , 71396.89318558])
"""
Scikit-Learn 交叉驗(yàn)證功能期望的是效用函數(shù)(越大越好)
"""
def display_scores(scores):
print("Scores:", scores)
print("Mean:", scores.mean())
print("Standard deviation:", scores.std())
display_scores(tree_rmse_scores)
'''Scores: [70194.33680785 66855.16363941 72432.58244769 70758.73896782
71115.88230639 75585.14172901 70262.86139133 70273.6325285
75366.87952553 71231.65726027]
Mean: 71407.68766037929
Standard deviation: 2439.4345041191004'''
再換一個(gè)隨機(jī)森林
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor(random_state=42)
forest_reg.fit(housing_prepared, housing_labels)
housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse
# 21933.31414779769
forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels,
scoring="neg_mean_squared_error", cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)
'''
Scores: [51646.44545909 48940.60114882 53050.86323649 54408.98730149
50922.14870785 56482.50703987 51864.52025526 49760.85037653
55434.21627933 53326.10093303]
Mean: 52583.72407377466
Standard deviation: 2298.353351147122
'''
保存模型
from sklearn.externals import joblib
joblib.dump(my_model, "my_model.pkl")
# 然后
my_model_loaded = joblib.load("my_model.pkl")
6. 模型微調(diào)
網(wǎng)絡(luò)搜索 Grid Search
下面的代碼搜索了 RandomForestRegressor 超參數(shù)值的最佳組合:
from sklearn.model_selection import GridSearchCV
param_grid = [
# try 12 (3×4) combinations of hyperparameters
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
# then try 6 (2×3) combinations with bootstrap set as False
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor(random_state=42)
# train across 5 folds, that's a total of (12+6)*5=90 rounds of training
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
scoring='neg_mean_squared_error', return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)
grid_search.best_params_
# {'max_features': 8, 'n_estimators': 30}
# 因?yàn)?30 是 n_estimators 的最大值,你也應(yīng)該估計(jì)更高的值,因?yàn)樵u(píng)估的分?jǐn)?shù)可能會(huì)隨 n_estimators 的增大而持續(xù)提升。
隨機(jī)搜索
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
param_distribs = {
'n_estimators': randint(low=1, high=200),
'max_features': randint(low=1, high=8),
}
forest_reg = RandomForestRegressor(random_state=42)
rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs,
n_iter=10, cv=5, scoring='neg_mean_squared_error', random_state=42)
rnd_search.fit(housing_prepared, housing_labels)
集成方法
另一種微調(diào)系統(tǒng)的方法是將表現(xiàn)最好的模型組合起來(lái)。組合(集成)之后的性能通常要比單獨(dú)的模型要好(就像隨機(jī)森林要比單獨(dú)的決策樹(shù)要好),特別是當(dāng)單獨(dú)模型的誤差類(lèi)型不同時(shí)。待續(xù)
分析最佳模型和它們的誤差
用測(cè)試集評(píng)估系統(tǒng)
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)
# 47730.22690385927