機器學習實戰_一個完整的程序(一)

clipboard.png
使用housing.csv 訓練數據git

兩種經常使用性能評價指標:

  • 均方根偏差RSEM
  • 平均絕對偏差MAE

$$RESM = \sqrt{ \frac{1}{m}\sum_{i=1}^m(y'-y)^2}$$
$$MAE = \sqrt{ \frac{1}{m}\sum_{i=1}^m|y'-y|}$$算法

MSE 和 MAE 都是測量預測值和目標值兩個向量距離的方法。有多種測量距離的方法,或範數,更通常的K 階閔氏範數寫成。p=0時,ℓ0(漢明範數)只顯示了這個向量的基數(即,非零元素的個數),p趨於無窮時,ℓ∞(切比雪夫範數)是向量中最大的絕對值。
$$RESM = \sqrt{ \frac{1}{m}\sum_{i=1}^m|y'-y|^p}$$數組

拆分訓練集與測試集:

import numpy as np

def split_train_test(data, test_ratio):
    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:]
    return data.iloc[train_indices], data.iloc[test_indices]
    
train_set, test_set = split_train_test(housing, 0.2)    # housing數據二八拆分

這個方法可行,可是並不完美:若是再次運行程序,就會產生一個不一樣的測試集。屢次運行以後,你(或你的機器學習算法)就會獲得整個數據集,這是須要避免的。安全

一個一般的解決辦法是使用每一個實例的識別碼,以斷定是否這個實例是否應該放入測試集(假設實例有單一且不變的識別碼)。例如,你能夠計算出每一個實例識別碼的哈希值,只保留其最後一個字節,若是值小於等於 51(約爲 256 的 20%),就將其放入測試集。這樣能夠保證在屢次運行中,測試集保持不變,即便更新了數據集。新的測試集會包含新實例中的 20%,但不會有以前位於訓練集的實例。下面是一種可用的方法:app

import hashlib

# 參數identifier爲單一且不變的識別碼,能夠爲索引id
# hash(np.int64(identifier)).digest()[-1]返回識別碼的哈希摘要值的最後一個字節
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]

不過,房產數據集沒有識別碼這一列。最簡單的方法是使用行索引做爲 IDdom

housing_with_id = housing.reset_index()   # 增長一個索引列,放在數據的第一列
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

若是使用行索引做爲惟一識別碼,你須要保證新數據放到現有數據的尾部,且沒有行被深處。若是作不到,則能夠用最穩定的特徵來建立惟一識別碼。例如,一個區的維度和經度在幾百萬年以內是不變的,因此能夠將二者結合成一個 ID。機器學習

若是你想簡單地拆分數據作預測模型示例,使用split_train_test進行拆分便可。Scikit-Learn 提供了一些函數,能夠用多種方式將數據集分割成多個子集。最簡單的函數是train_test_split,它的做用和以前的函數split_train_test很像,並帶有其它一些功能。首先,它有一個random_state參數,能夠設定前面講過的隨機生成器種子;第二,你能夠將種子傳遞到多個行數相同的數據集,能夠在相同的索引上分割數據集(這個功能很是有用,好比你有另外一個DataFrame做爲標籤):ide

from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

另一種拆分形式:分層採樣
將人羣分紅均勻的子分組,稱爲分層,從每一個分層去除合適數量的實例,以保證測試集對總人數有表明性。例如,美國人口的 51.3% 是女性,48.7% 是男性。因此在美國,嚴謹的調查須要保證樣本也是這個比例:513 名女性,487 名男性做爲數據樣本。函數

數據集中的每一個分層都要有足夠的實例位於你的數據中,這點很重要。不然,對分層重要性的評估就會有誤差。這意味着,你不能有過多的分層,且每一個分層都要足夠大。後面的代碼經過將收入中位數除以 1.5(以限制收入分類的數量),建立了一個收入類別屬性,用ceil對值舍入(以產生離散的分類),而後將全部大於 5的分類納入到分類5 性能

# 預處理,建立"income_cat"屬性 
# 凡是會對原數組做出修改並返回一個新數組的,每每都有一個 inplace可選參數
# inplace=True,原數組名對應的內存值直接改變;inplace=False,原數組名對應的內存值並不改變,新的結果賦給一個新的數組.
housing["income_cat"] = np.ceil(housing["median_income"] / 1.5)
housing["income_cat"].where(housing["income_cat"] < 5, 5.0, inplace=True)

# 如今,就能夠根據收入分類,進行分層採樣。你可使用 Scikit-Learn 的StratifiedShuffleSplit類
from sklearn.model_selection import StratifiedShuffleSplit

# random_state爲隨機種子生成器,能夠獲得相同的隨機結果
# n_splits是將訓練數據分紅train/test對的組數,這裏彙總成一組數據
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]

# 如今,你須要刪除income_cat屬性,使數據回到初始狀態:    
for set in (strat_train_set, strat_test_set):
    set.drop(["income_cat"], axis=1, inplace=True)

可視化數據尋找規律:

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()

每一個圈的半徑表示街區的人口(選項s),顏色表明價格(選項c)。咱們用預先定義的名爲jet的顏色圖(選項cmap),它的範圍是從藍色(低價)到紅色(高價):

clipboard.png

這張圖說明房價和位置(好比,靠海)和人口密度聯繫密切。你還能夠很容易地使用corr()方法計算出每對屬性間的標準相關係數(也稱做皮爾遜相關係數):

corr_matrix = housing.corr()

如今來看下每一個屬性和房價中位數的關聯度:

>>> corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value    1.000000
median_income         0.687170
total_rooms           0.135231
housing_median_age    0.114220
households            0.064702
total_bedrooms        0.047865
population           -0.026699
longitude            -0.047279
latitude             -0.142826
Name: median_house_value, dtype: float64

相關係數的範圍是 -1 到 1。當接近 1 時,意味強正相關;例如,當收入中位數增長時,房價中位數也會增長。當相關係數接近 -1 時,意味強負相關;你能夠看到,緯度和房價中位數有輕微的負相關性(即,越往北,房價越可能下降)。最後,相關係數接近 0,意味沒有線性相關性。(沒有直接的線性關係,不是沒有關係)

另外一種檢測屬性間相關係數的方法是使用 Pandas 的scatter_matrix函數,它能畫出每一個數值屬性對每一個其它數值屬性的圖。由於如今共有 11 個數值屬性,你能夠獲得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=(12, 8))

獲得兩個屬性的散點圖
clipboard.png

爲機器學習算法準備數據

  • 函數可讓你在任何數據集上(好比,你下一次獲取的是一個新的數據集)方便地進行重複數據轉換。
  • 你能慢慢創建一個轉換函數庫,能夠在將來的項目中複用。
  • 在將數據傳給算法以前,你能夠在實時系統中使用這些函數。
  • 這可讓你方便地嘗試多種數據轉換,查看那些轉換方法結合起來效果最好。

數據清洗

大多機器學習算法不能處理特徵丟失,所以先建立一些函數來處理特徵丟失的問題。前面,你應該注意到了屬性total_bedrooms有一些缺失值。有三個解決選項:

  • 去掉對應的街區;
  • 去掉整個屬性;
  • 進行賦值(0、平均值、中位數等等)。

用DataFrame的dropna(),drop(),和fillna()方法,能夠方便地實現:

housing.dropna(subset=["total_bedrooms"])    # 選項1
housing.drop("total_bedrooms", axis=1)       # 選項2    axis=0對行操做,axis=1對列操做
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median)     # 選項3

若是選擇選項 3,你須要計算訓練集的中位數,用中位數填充訓練集的缺失值,不要忘記保存該中位數。後面用測試集評估系統時,須要替換測試集中的缺失值,也能夠用來實時替換新數據中的缺失值。

Scikit-Learn 提供了一個方便的類來處理缺失值:Imputer。下面是其使用方法:首先,須要建立一個Imputer實例,指定用該屬性的中位數替換它的每一個缺失值:

from sklearn.preprocessing import Imputer

imputer = Imputer(strategy="median")    # 進行中位數賦值

由於只有數值屬性才能算出中位數,咱們須要建立一份不包括文本屬性ocean_proximity的數據副本:

housing_num = housing.drop("ocean_proximity", axis=1)    # 去除ocean_proximity不爲數值屬性的特徵

如今,就能夠用fit()方法將imputer實例擬合到訓練數據:

imputer.fit(housing_num)

imputer計算出了每一個屬性的中位數,並將結果保存在了實例變量statistics_中。只有屬性total_bedrooms有缺失值,可是咱們確保一旦系統運行起來,新的數據中沒有缺失值,因此安全的作法是將imputer應用到每一個數值

>>> imputer.statistics_    # 實例變量statistics_和housing_num數值數據獲得的中位數是同樣的
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])

如今,你就可使用這個「訓練過的」imputer來對訓練集進行轉換,經過將缺失值替換爲中位數:

X = imputer.transform(housing_num)

結果是一個普通的 Numpy 數組,包含有轉換後的特徵。若是你想將其放回到 PandasDataFrame中,也很簡單:

housing_tr = pd.DataFrame(X, columns=housing_num.columns)     # 獲得處理缺失值後的DF數據

處理文本和類別屬性

前面,咱們丟棄了類別屬性ocean_proximity,由於它是一個文本屬性,不能計算出中位數。大多數機器學習算法跟喜歡和數字打交道,因此讓咱們把這些文本標籤轉換爲數字。Scikit-Learn 爲這個任務提供了一個轉換器LabelEncoder:

# 簡單來講 LabelEncoder 是對不連續的數字或者文本進行編號
# le.fit([1,5,67,100])
# le.transform([1,1,100,67,5])
# 輸出: array([0,0,3,2,1])

>>> 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])

處理離散特徵這還不夠,Scikit-Learn 提供了一個編碼器OneHotEncoder,用於將整書分類值轉變爲獨熱向量。注意fit_transform()用於 2D 數組,而housing_cat_encoded是一個 1D 數組,因此須要將其變形:

>>> 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>

注意輸出結果是一個 SciPy 稀疏矩陣,而不是 NumPy 數組。當類別屬性有數千個分類時,這樣很是有用。通過獨熱編碼,咱們獲得了一個有數千列的矩陣,這個矩陣每行只有一個 1,其他都是 0。使用大量內存來存儲這些 0 很是浪費,因此稀疏矩陣只存儲非零元素的位置。你能夠像一個 2D 數據那樣進行使用,可是若是你真的想將其轉變成一個(密集的)NumPy 數組,只需調用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,咱們能夠用一步執行這兩個轉換(從文本分類到整數分類,再從整數分類到獨熱向量):

>>> 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]])

注意默認返回的結果是一個密集 NumPy 數組。向構造器LabelBinarizer傳遞sparse_output=True,就能夠獲得一個稀疏矩陣。

相關文章
相關標籤/搜索