一個(gè)過(guò)程完整的機(jī)器學(xué)習(xí)項(xiàng)目

基本步驟

  1. 縱覽全局
  2. 獲取數(shù)據(jù)
  3. 數(shù)據(jù)可視化、找規(guī)律
  4. 準(zhǔn)備用于機(jī)器學(xué)習(xí)算法的數(shù)據(jù)
  5. 選擇模型并進(jìn)行訓(xùn)練
  6. 模型微調(diào)
  7. 展示解決方案
  8. 系統(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ù)集,如下圖。

加州房產(chǎn)價(jià)格

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)


RMSE

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)


MAE

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。


Pycharm中設(shè)置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()
raw_data.head()展示數(shù)據(jù)前五行

每行數(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()
raw_data.info()展示數(shù)據(jù)類(lèi)型及基本信息

可以看到total_bedroom不是所有數(shù)據(jù)都有值(20433/20640),處理的時(shí)候需要小心。
ocean_proximity顯然是個(gè)枚舉值,可以通過(guò)下面的方法查詢(xún)所有的枚舉值。

raw_data["ocean_proximity"].value_counts()
ocean_proximty的枚舉值
查看所有屬性的基本信息
raw_data.describe()
raw_data各屬性的基本統(tǒng)計(jì)信息
用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()
房?jī)r(jià)、人口散列圖

目測(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)
各屬性與median_house_value的相關(guān)系數(shù)
方式二
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))
pandas.plotting.scatter_matrix可視化相關(guān)性

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)
median_income與median_house_value關(guān)系圖

屬性組合試驗(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)
新屬性與房?jī)r(jià)的關(guān)聯(lián)系數(shù)
小結(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容