(第一部分 機器學習基礎)
第01章 機器學習概覽
第02章 一個完整的機器學習項目(上)
第02章 一個完整的機器學習項目(下)
第03章 分類
第04章 訓練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學習和隨機森林
第08章 降維
(第二部分 神經網絡和深度學習)
第9章 啟動和運行TensorFlow
第10章 人工神經網絡
第11章 訓練深度神經網絡(上)
第11章 訓練深度神經網絡(下)
第12章 設備和服務器上的分布式 TensorFlow
第13章 卷積神經網絡
第14章 循環神經網絡
第15章 自編碼器
第16章 強化學習(上)
第16章 強化學習(下)
本章中,你會作為被一家地產公司雇傭的數據科學家,完整地學習一個項目。下面是主要步驟:
- 項目概述。
- 獲取數據。
- 發現并可視化數據,發現規律。
- 為機器學習算法準備數據。
- 選擇模型,進行訓練。
- 微調模型。
- 給出解決方案。
- 部署、監控、維護系統。
使用真實數據
學習機器學習時,最好使用真實數據,而不是人工數據集。幸運的是,有上千個開源數據集可以進行選擇,涵蓋多個領域。以下是一些可以查找數據的資源:
流行的開源數據倉庫:
UC Irvine Machine Learning Repository
Kaggle datasets
Amazon’s AWS datasets準入口(提供開源數據列表)
http://dataportals.org/
http://opendatamonitor.eu/
http://quandl.com/
- 其它列出流行開源數據倉庫的網頁:
Wikipedia’s list of Machine Learning datasets
Quora.com question
Datasets subreddit
本章,我們選擇的是StatLib的加州房產價格數據集(見圖2-1)。這個數據集是基于1990年加州普查的數據。數據已經有點老(1990年還能買一個灣區不錯的房子),但是它有許多優點,利于學習,所以假設這個數據為最新的。為了便于教學,我們添加了一個分類屬性,并除去了一些屬性。
項目概覽
歡迎來到機器學習房地產公司!你的第一個任務是利用加州普查數據,建立一個加州房價模型。這個數據包含每個分區組的人口、收入中位數、房價中位數等指標。
分區組是美國調查局發布樣本數據的最小地理單位(一個分區通常有600到3000人)。我們將其簡稱為“分區”。
你的模型要利用這個數據進行學習,然后根據其它指標,預測任何分區的的房價中位數。
提示:你是一個有條理的數據科學家,你要做的第一件事是拿出你的機器學習項目清單。你可以使用附錄B中的清單;這個清單適用于大多數的機器學習項目,但是你還是要確認它是否滿足需求。在本章中,我們會檢查許多清單上的項目,但是也會跳過一些簡單的,有些會在后面的章節再討論。
劃定問題
詢問老板的第一個問題應該是商業目標是什么?建立模型可能不是最終目標。公司要如何使用、并從模型受益?這非常重要,因為它決定了如何劃定問題,要選擇什么算法,評估模型性能的指標是什么,要花多少精力進行微調。
老板告訴你,你的模型的輸出(一個區的房價中位數)會傳給另一個機器學習系統(見圖2-2),也有其它信號會傳入后面的系統。這一整套系統可以確定對某個區投資值不值。確定投資與否非常重要,它直接影響利潤。
管道
一系列的數據處理組件被稱為數據管道。管道在機器學習系統中很常見,因為有許多數據要處理和轉換。
組件通常是異步運行的。每個組件吸納進大量數據,進行處理,然后將數據傳輸到另一個數據容器中,而后管道中的另一個組件收入這個數據,然后輸出,這個過程依次進行下去。每個組件都是獨立的:組件間的接口只是數據容器。這樣可以讓系統更便于理解(記住數據流的圖),不同的項目組可以關注于不同的組件。進而,如果一個組件失效了,下游的組件使用失效組件最后生產的數據,通常可以正常運行(一段時間)。這樣就使整個架構相當健壯。
另一方面,如果沒有監控,失效的組件會在不被注意的情況下運行一段時間。數據會受到污染,整個系統的性能就會下降。
下一個要問的問題是,現在的解決方案效果如何。老板通常會給一個參考性能,以及如何解決問題。老板說,現在分區的房價是靠專家手工估計的,專家隊伍收集最新的關于一個區的信息(不包括房價中位數),他們使用復雜的規則進行估計。這種方法費錢費時間,而且估計結果不理想,誤差率大概有15%。
Okay,有了這些信息,你就可以開始設計系統了。首先,你需要劃定問題:監督或非監督,還是強化學習?這是個分類任務、回歸任務,還是其它的?要使用批量學習還是線上學習?繼續閱讀之前,請暫停一下,嘗試自己回答下這些問題。
你能回答出來嗎?一起看下答案:很明顯,這是一個典型的監督學習任務,因為你要使用的是有標簽的訓練樣本(每個實例都有預定的產出,即分區的房價中位數)。并且,這是一個典型的回歸任務,因為你要預測一個值。講的更細些,這是一個多變量回歸問題,因為系統要使用多個變量進行預測(要使用分區的人口,收入中位數等等)。在第一章中,你只是根據人均GDP來預測生活滿意度,因此這是一個單變量回歸問題。最后,沒有連續的數據流進入系統,沒有特別需求需要對數據變動作出快速適應。數據量不大可以放到內存中,因此批量學習就夠了。
提示:如果數據量很大,你可以要么在多個服務器上對批量學習做拆分(使用MapReduce技術,后面會看到),或是使用線上學習。
選擇性能指標
下一步是選擇性能指標。回歸問題的典型指標是均方根誤差(RMSE)。均方根誤差測量的是系統預測誤差的標準差。例如,RMSE等于50000,意味著,68%的系統預測值位于實際值的50000以內。95%的預測值位于實際值的100000以內。等式2-1展示了計算RMSE的方法。
符號的含義
這個方程引入了一些常見的貫穿本書的機器學習符號:
- m是測量RMSE的數據集中的實例數量。
例如,如果用一個含有2000個分區的驗證集求RMSE,則m = 2000。- x(i)是數據集第ith個實例的所有特征值(不包含標簽)的矢量,y(i)是它的標簽(這個實例的輸出值)。
例如,如果數據集中的第一個分區位于經度–118.29°,緯度33.91°,有1416名居民,收入中位數是38372美元,房價中位數是$156400(不考慮其它特征),則有:
和,
- X是包含數據集中所有實例的所有特征值(不包含標簽)的矩陣。每一行是一個實例,第ith行是x(i)的轉置,標記為(x(i))T。
例如,仍然是前面的第一區,矩陣X就是:
- h是系統的預測函數,也稱為假設(hypothesis)。當系統收到一個實例的特征矢量x(i),就會輸出這個實例的一個預測值?(i) = h(x(i))(?讀作y-hat)。
例如,如果系統預測第一區的房價中位數是$158400,則?(1) = h(x(1)) = 158400。預測誤差是?(1) – y(1) = 2000。
RMSE(X,h)是使用假設h在樣本集上測量的損失函數。我們使用小寫斜體表示標量值(例如m或y(1))和函數名(例如h),小寫粗體表示矢量(例如x(i)),大寫粗體表示矩陣(例如X)。(譯者注:MarkDown表示粗體斜體太麻煩了,忽略字體。)
雖然大多數時候RMSE是回歸任務可靠的性能指標,在有些情況下,你可能需要另外的函數。例如,假設存在許多異常的分區。此時,你可能需要使用絕對平均誤差(Mean Absolute Error,也稱作平均絕對偏差,見等式2-2):
RMSE和MAE都是測量預測值和目標值兩個矢量距離的方法。有多種測量距離的方法,或范數:
計算對應歐幾里得范數的平方和的根(RMSE):這個距離介紹過。它也稱作?2范數,標記為// · //2(或只是// · //)。
計算對應于?1(標記為// · //1)范數的絕對值之和(MAE)。有時,也稱其為曼哈頓范數,因為它測量了城市中的兩點,沿著矩形的邊行走的距離。
-
更一般的,包含n個元素的矢量v的?k范數,定義成
?0只顯示了這個矢量的基數(即,元素的個數),?∞是矢量中最大的絕對值。 范數的指數越高,就越關注大的值而忽略小的值。這就是為什么RMSE比MAE對異常值更敏感。但是當異常值是指數分布的(類似正態曲線),RMSE就會表現很好。
核實假設
最后,最好列出并核對迄今(你或其他人)作出的假設。這樣可以盡早發現嚴重的問題。例如,你的系統輸出的分區房價,會傳入到下游的機器學習系統,我們假設這些價格確實會被當做分區房價使用。但是如果下游系統實際上將價格轉化成了分類(例如,便宜、中等、昂貴),然后使用這些分類,而不是使用價格。這樣的話,獲得準確的價格就不那么重要了,你只需要得到合適的分類。問題相應地就變成了一個分類問題,而不是回歸任務。你可不想在一個回歸系統上工作了數月,最后才發現真相。
幸運的是,在與下游系統主管探討之后,你很確信他們需要的就是實際的價格,而不是分類。很好!整裝待發,可以開始寫代碼了。
獲取數據
開始動手。最后用Jupyter notebook完整地敲一遍示例代碼。完整的代碼位于https://github.com/ageron/handson-ml。
創建工作空間
首先,你需要安裝Python。可能已經安裝過了,沒有的話,可以從官網下載https://www.python.org/。
接下來,需要為你的機器學習代碼和數據集創建工作空間目錄。打開一個終端,輸入以下命令(在提示符$之后):
$ export ML_PATH="$HOME/ml" # 可以更改路徑
$ mkdir -p $ML_PATH
還需要一些Python模塊:Jupyter、NumPy、Pandas、Matplotlib和Scikit-Learn。如果所有這些模塊都已經在Jupyter中運行了,你可以直接跳到下一節“下載數據”。如果還沒安裝,有多種方法可以進行安裝(包括它們的依賴)。你可以使用系統的包管理系統(比如Ubuntu上的apt-get,或macOS上的MacPorts或HomeBrew),安裝一個Python科學計算環境比如Anaconda,使用Anaconda的包管理系統,或者使用Python自己的包管理器pip,它是Python安裝包(自從2.7.9版本)自帶的。可以用下面的命令檢測是否安裝pip:
$ pip3 --version
pip 9.0.1 from [...]/lib/python3.5/site-packages (python 3.5)
你需要保證pip是近期的版本,至少高于1.4,以保障二進制模塊文件的安裝(也成為wheels)。要升級pip,可以使用下面的命令:
$ pip3 install --upgrade pip
Collecting pip
[...]
Successfully installed pip-9.0.1
創建獨立環境
如果你希望在一個獨立環境中工作(強烈推薦這么做,不同項目的庫的版本不會沖突),用下面的pip命令安裝virtualenv:
$ pip3 install --user --upgrade virtualenv Collecting virtualenv [...] Successfully installed virtualenv
現在可以通過下面命令創建一個獨立的Python環境:
$ cd $ML_PATH $ virtualenv env Using base prefix '[...]' New python executable in [...]/ml/env/bin/python3.5 Also creating executable in [...]/ml/env/bin/python Installing setuptools, pip, wheel...done.
以后每次想要激活這個環境,只需打開一個終端然后哦輸入:
$ cd $ML_PATH $ source env/bin/activate
啟動該環境時,使用pip安裝的任何包都只安裝于這個獨立環境中,Python只會訪問這些包(如果你希望Python能訪問系統的包,創建環境時要使用包選項--system-site-)。更多信息,請查看virtualenv文檔。
現在,你可以使用pip命令安裝所有必需的模塊和它們的依賴:
$ pip3 install --upgrade jupyter matplotlib numpy pandas scipy scikit-learn
Collecting jupyter
Downloading jupyter-1.0.0-py2.py3-none-any.whl
Collecting matplotlib
[...]
要檢查安裝,可以用下面的命令引入每個模塊:
$ python3 -c "import jupyter, matplotlib, numpy, pandas, scipy, sklearn"
這個命令不應該有任何輸出和錯誤。現在你可以用下面的命令打開Jupyter:
$ jupyter notebook
[I 15:24 NotebookApp] Serving notebooks from local directory: [...]/ml
[I 15:24 NotebookApp] 0 active kernels
[I 15:24 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/
[I 15:24 NotebookApp] Use Control-C to stop this server and shut down all
kernels (twice to skip confirmation).
Jupyter服務器現在運行在終端上,監聽8888端口。你可以用瀏覽器打開http://localhost:8888/,以訪問這個服務器(服務器啟動時,通常就自動打開了)。你可以看到一個空的工作空間目錄(如果按照先前的virtualenv步驟,只包含env目錄)。
現在點擊按鈕New創建一個新的Python筆記本,選擇合適的Python版本(見圖2-3)。
這一步做了三件事:首先,在工作空間中創建了一個新的notebook文件Untitled.ipynb;第二,它啟動了一個Jupyter的Python內核來運行這個notebook;第三,在一個新欄中打開這個notebook。接下來,點擊Untitled,將這個notebook重命名為Housing(這會將ipynb文件自動命名為Housing.ipynb)。
notebook包含一組代碼框。每個代碼框可以放入可執行代碼或格式化文本。現在,notebook只有一個空的代碼框,標簽是“In [1]:”。在框中輸入print("Hello world!")
,點擊運行按鈕(見圖2-4)或按Shift+Enter。這會將當前的代碼框發送到Python內核,運行之后會返回輸出。結果顯示在代碼框下方。由于抵達了notebook的底部,一個新的代碼框會被自動創建出來。從Jupyter的Help菜單中的User Interface Tour可以學習Jupyter的基本操作。
下載數據
一般情況下,數據是存儲于關系型數據庫(或其它常見數據庫)中的多個表、文檔、文件。要訪問數據,你首先要有密碼和登錄權限,并要了解數據結構。但是在這個項目中,這一切要簡單些:只要下載一個壓縮文件,housing.tgz,它包含一個CSV文件housing.csv,含有所有數據。
你可以使用瀏覽器下載,運行tar xzf housing.tgz解壓提取出csv文件,但是更好的辦法是寫一個小函數來做這件事。如果數據變動頻繁,這么做是非常好的,因為可以讓你寫一個小腳本隨時獲取最新的數據(或者創建一個定時任務來做)。如果你想在多臺機器上安裝數據集,獲取數據自動化也是非常好的。
下面是獲取數據的函數:
import os
import tarfile
from six.moves import urllib
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, housing_path=HOUSING_PATH):
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(),就會在工作空間創建一個datasets/housing目錄,下載housing.tgz文件,提取出housing.csv。
然后使用Pandas加載數據。還是用一個小函數來加載數據:
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)
這個函數會返回一個包含所有數據的Pandas DataFrame對象。
快速查看數據結構
使用DataFrame的head()方法查看該數據集的頂部5行(見圖2-5)。
每一行都表示一個分區。共有10個屬性(截圖中可以看到6個):經度、維度、房屋年齡中位數、總房間數、臥室數量、人口數、家庭數、收入中位數、房屋價值中位數、大海距離。
info()方法可以快速查看數據的描述,包括總行數、每個屬性的類型和非空值的數量(見圖2-6)。
數據集中共有20640個實例,按照機器學習的標準這個數據量很小,但是非常適合入門。總房間數只有20433個非空值,意味著207個分區缺少這個值。后面要對它進行處理。
所有的屬性都是數值的,除了大海距離這項。它的類型是對象,因此可以包含任意Python對象,但是因為是從CSV文件加載的,所以必然是文本。當查看頂部的五行時,你可能注意到那一列的值是重復的,意味著它可能是一個分類屬性。可以使用value_counts()方法查看都有什么類型,每個類都有多少分區:
>>> housing["ocean_proximity"].value_counts()
<1H OCEAN 9136
INLAND 6551
NEAR OCEAN 2658
NEAR BAY 2290
ISLAND 5
Name: ocean_proximity, dtype: int64
再來看其它字段。describe()方法展示了數值屬性的概括(見圖2-7)。
count、mean、min和max幾行的意思很明了。注意,空值被忽略了(所以,臥室總數是20433而不是20640)。std是標準差(揭示數值的分散度)。25%、50%、75%展示了對應的分位數:每個分位數指明小于這個值,且指定分組的百分比。例如,25%的分區的房屋年齡中位數小于18,而50%的小于29,75%的小于37。這些值通常稱為25th分位數(或1st四分位數),中位數,75th分位數(3rd四分位數)。
另一種快速了解數據類型的方法是畫出每個數值屬性的柱狀圖。柱狀圖(的縱軸)展示了特定范圍的實例的個數。你還可以一次給一個屬性畫圖,或對完整數據集調用hist()方法,后者會畫出每個數值屬性的柱狀圖(見圖2-8)。例如,你可以看到略微超過800個分區的median_house_value值差不多等于$500000。
%matplotlib inline # only in a Jupyter notebook
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
plt.show()
筆記:hist()方法依賴于Matplotlib,后者依賴于用戶指定的圖形后端以打印到屏幕上。因此在畫圖之前,你要指定Matplotlib要使用的后端。最簡單的方法是使用Jupyter的魔術命令%matplotlib inline。它會告訴Jupyter設定好Matplotlib,以使用Jupyter自己的后端。繪圖就會在notebook中渲染了。注意在Jupyter中調用show()不是必要的,因為代碼框執行后Jupyter會自動展示圖像。
注意柱狀圖中的一些點:
首先,收入中位數貌似不是美元(USD)。與數據采集團隊交流之后,你被告知數據是經過縮放調整的,過高收入中位數的會變為15(實際為15.0001),過低的會變為5(實際為0.4999)。在機器學習中對數據進行預處理很正常,不一定是問題,但你要明白數據是如何計算出來的。
房屋年齡中位數和房屋價值中位數也被設了上線。后者可能是個嚴重的問題,因為它是你的目標屬性(你的標簽)。你的機器學習算法可能學習到價格不會超出這個界限。你需要與下游團隊核實,這是否會成為問題。如果他們告訴你他們需要明確的預測值,即價格可以超過500000美元,你則有兩個選項:
a. 對于設了上限的標簽,重新收集合適的標簽;
b. 將這些分區從訓練集移除(也從測試集移除,因為若房價超出$500000,你的系統就會被差評)。這些屬性值有不同的量度。我們會在本章后面討論特征縮放。
最后,許多柱狀圖的尾巴很長:相較于左邊,它們在中位數的右邊延伸過遠。對于某些機器學習算法,這會使檢測規律變得更難些。我們會在后面嘗試轉換處理這些屬性,使其變為正太分布。
希望你現在對要處理的數據有一定了解了。
警告:稍等!再你進一步查看數據之前,你需要創建一個測試集,放在一旁,再也不看。
創建測試集
在這個階段就分割數據,聽起來很奇怪。畢竟,你只是簡單快速地查看了數據而已,你需要再仔細調查下數據以決定使用什么算法。這么想是對的,但是人類的大腦是一個神奇的發現規律的系統,這意味著大腦非常容易發生過擬合:如果你查看了測試集,就會不經意地按照測試集中的規律來選擇某個特定的機器學習模型。再當你使用測試集來評估誤差率時,就會導致評估過于樂觀,而實際部署的系統表現就會差。這稱為數據透視偏差。
理論上,創建測試集很簡單:只要隨機挑選一些實例,一般是數據集的20%,放到一邊:
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)
>>> print(len(train_set), "train +", len(test_set), "test")
16512 train + 4128 test
這個方法可行,但是并不完美:如果再次運行程序,就會產生一個不同的測試集!多次運行之后,你(或你的機器學習算法)就會得到整個數據集,這是需要避免的。
解決的辦法之一是保存第一次運行得到的測試集,并在隨后的過程加載。另一種方法是在調用np.random.permutation()之前,設置隨機數生成器的種子(比如np.random.seed(42)),以產生總是相同的混合指數(shuffled indices)。
但是如果獲取更新后的數據集,這兩個方法都會失效。一個通常的解決辦法是使用每個實例的識別碼,以判定是否這個實例是否應該放入測試集(假設實例有單一且不變的識別碼)。例如,你可以每個實例識別碼的哈希值,只保留其最后一個字節,如果值小于等于51(約為256的20%),就將其放入測試集。這樣可以保證在多次運行中,測試集保持不變,即使更新了數據集。新的測試集會包含新實例中的20%,但不會有之前位于訓練集的實例。下面是一種可用的方法:
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]
不過,房產數據集沒有識別碼這一列。最簡單的方法是使用行索引作為ID:
housing_with_id = housing.reset_index() # adds an `index` column
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")
如果使用行索引作為唯一識別碼,你需要保證新數據放到現有數據的尾部,且沒有行被深處。如果做不到,則可以用最穩定的特征來創建唯一識別碼。例如,一個區的維度和經度在幾百萬年之內是不變的,所以可以將兩者結合成一個ID:
housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")
Scikit-Learn提供了一些函數,可以用多種方式將數據集分割成多個子集。最簡單的函數是train_test_split,它的作用和之前的函數split_train_test很像,并帶有其它一些功能。首先,它有一個random_state參數,可以設定前面講過的隨機生成器種子;第二,你可以將種子傳遞到多個行數相同的數據集,可以在相同的索引上分割數據集(這個功能非常有用,比如你有另一個DataFrame作為標簽):
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
目前為止,我們采用的都是純隨機的取樣方法。當你的數據集很大時(尤其是和屬性數相比),這通常可行;但如果數據集不大,就會有采樣偏差的風險。當一個調查公司想要對1000個人進行調查,它們不是在電話亭里隨機選1000個人出來。調查公司要保證這1000個人對人群整體有代表性。例如,美國人口的51.3%是女性,48.7%是男性。所以在美國,嚴謹的調查需要保證樣本也是這個比例:513名女性,487名男性。這稱作分層采樣(stratified sampling):將人群分成均勻的子分組,稱為分層,從每個分層取出合適數量的實例,以保證測試集對總人數有代表性。如果調查公司采用純隨機采樣,會有12%的概率導致采樣偏差:女性人數少于49%,或多余54%。不管發生那種情況,調查結果都會嚴重偏差。
假設專家告訴你,收入中位數是預測房價中位數非常重要的屬性。你可能想要保證測試集可以代表整體數據集中的多種收入分類。因為收入中位數是一個連續的數值屬性,你首先需要創建一個收入分類屬性。再仔細地看一下收入中位數的柱狀圖(圖2-9):
大多數的收入中位數的值聚集在2-5(一萬美元),但是一些收入中位數會超過6。數據集中的每個分層都要有足夠的實例位于你的數據中,這點很重要。否則,對分層重要性的評估就會有偏差。這意味著,你不能有過多的分層,且每個分層都要足夠大。后面的代碼通過將收入中位數除以1.5(以限制收入分類的數量),創建了一個收入分類屬性,用ceil對值舍入(以產生離散的分類),然后將所有大于5的分類歸入到分類5:
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
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]
檢查下結果是否符合預期。你可以在完整的房產數據集中查看收入分類比例:
>>> housing["income_cat"].value_counts() / len(housing)
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
使用相似的代碼,還可以測量測試集中收入分類的比例。圖2-10對比了總數據集、分層采樣的測試集、純隨機采樣測試集的收入分類比例。可以看到,分層采樣測試集的收入分類比例與總數據集幾乎相同,而隨機采樣數據集偏差嚴重。
現在,你需要刪除income_cat屬性,使數據回到初始狀態:
for set in (strat_train_set, strat_test_set):
set.drop(["income_cat"], axis=1, inplace=True)
我們用了大量時間來生成測試集的原因是:測試集通常被忽略,但實際是機器學習非常重要的一部分。還有,生成測試集過程中的許多思路對于后面的交叉驗證討論是非常有幫助的。接下來進入下一階段:數據探索。
數據探索和可視化、發現規律
目前為止,你只是快速查看了數據,對要處理的數據有了整體了解。現在的目標是更深的探索數據。
首先,保證你將測試集放在了一旁,只是研究訓練集。另外,如果訓練集非常大,你可能需要再采樣一個探索集,保證操作方便快速。在我們的案例中,數據集很小,所以可以在全集上直接工作。創建一個副本,以免損傷訓練集:
housing = strat_train_set.copy()
地理數據可視化
因為存在地理信息(緯度和經度),創建一個所有分區的散點圖來數據可視化是一個不錯的主意(圖2-11):
housing.plot(kind="scatter", x="longitude", y="latitude")
這張圖看起來很像加州,但是看不出什么特別的規律。將alpha設為0.1,可以更容易看出數據點的密度(圖2-12):
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
現在看起來好多了:可以非常清楚地看到高密度區域,灣區、洛杉磯和圣迭戈,以及中央谷,特別是從薩克拉門托到弗雷斯諾。
通常來講,人類的大腦非常善于發現圖片中的規律,但是需要調整可視化參數使規律顯現出來。
現在來看房價(圖2-13)。每個圈的半徑表示分區的人口(選項s),顏色代表價格(選項c)。我們用預先定義的顏色圖(選項cmap)jet,它的范圍是從藍色(低價)到紅色(高價):
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()
這張圖說明房價和位置(比如,靠海)和人口密度聯系密切,這點你可能早就知道。可以使用聚類算法來檢測主要的聚集,用一個新的特征值測量聚集中心的距離。海洋距離屬性也可能有用,盡管北加州海岸區域的房價并不高,所以這不是一個簡單的規則。
查找關聯
因為數據集并不是非常大,你可以很容易地使用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,意味沒有線性相關性。圖2-14展示了相關系數在橫軸和縱軸之間的不同圖形。
警告:相關系數只測量線性關系(如果x上升,y則上升或下降)。相關系數可能會完全忽略非線性關系(即,如果x接近0,則y值會變高)。在前面的計算結果中,底部的許多行的相關系數接近于0,盡管它們的軸并不獨立:這些就是非線性關系的例子。另外,第二行的相關系數等于1或-1;這和斜率沒有任何關系。例如,你的身高(單位是英寸)與身高(單位是英尺或納米)的相關系數就是1。
另一種檢測屬性間相關系數的方法是使用Pandas的scatter_matrix函數,它能畫出每個數值屬性對每個其它數值屬性的圖。因為現在共有11個數值屬性,你可以得到112=121張圖,在一頁上畫不下,所以只關注幾個和房價中位數最有可能相關的屬性(圖2-15):
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))
如果pandas將每個變量對自己作圖,主對角線(左上到右下)都會是直線圖。所以Pandas展示的是每個屬性的柱狀圖(也可以是其它的,請參考Pandas文檔)。
最有希望用來預測房價中位數的屬性是收入中位數,因此將這張圖放大(圖2-16):
housing.plot(kind="scatter", x="median_income",y="median_house_value",
alpha=0.1)
這張圖說明了幾點。首先,相關性非常高;可以清晰地看到向上的趨勢,并且數據點不是非常分散。第二,我們之前看到的最高價,清晰地呈現為一條位于500000美元的水平線。這張圖也呈現了一些不是那么明顯的直線:一條位于450000美元的直線,一條位于350000美元的直線,一條在$280000的線,和一些更靠下的線。你可能希望去除對應的分區,以防止算法重復這些巧合。
屬性組合試驗
希望前面的一節能教給你一些探索數據、發現規律的方法。你發現了一些數據的巧合,需要在給算法提供數據之前,將其去除。你還發現了一些屬性間有趣的關聯,特別是目標屬性。你還注意到一些屬性具有長尾分布,因此你可能要將其進行轉換(例如,計算其log對數)。當然,不同項目的處理方法各不相同,但大體思路是相似的。
給算法準備數據之前,你需要做的最后一件事是嘗試多種屬性組合。例如,如果你不知道某個分區有多少戶,該分區的總房間數就沒什么用。你真正需要的是每戶有幾個房間。相似的,總臥室數也不重要:你可能需要將其與房間數進行比較。每戶的人口數也是一個有趣的屬性組合。讓我們來創建這些新的屬性:
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)
median_house_value 1.000000
median_income 0.687170
rooms_per_household 0.199343
total_rooms 0.135231
housing_median_age 0.114220
households 0.064702
total_bedrooms 0.047865
population_per_household -0.021984
population -0.026699
longitude -0.047279
latitude -0.142826
bedrooms_per_room -0.260070
Name: median_house_value, dtype: float64
看起來不錯!與總房間數或臥室數相比,新的bedrooms_per_room屬性與房價中位數的關聯更強。顯然,臥室數/總房間數的比例越低,房價就越高。每戶的房間數也比分區的總房間數的更有信息,很明顯,房屋越大,房價就越高。
這一步的數據探索不必非常完備,此處的目的是有一個正確的開始,快速發現規律,以得到一個合理的原型。但是這是一個交互過程:一旦你得到了一個原型,并運行起來,你就可以分析它的輸出,進而發現更多的規律,然后再回到數據探索這步。
為機器學習算法準備數據
現在來為機器學習算法準備數據。不要手工來做,你需要寫一些函數,理由如下:
函數可以讓你在任何數據集上(比如,你下一次獲取的是一個新的數據集)方便地進行重復數據轉換。
你能慢慢建立一個轉換函數庫,可以在未來的項目中復用。
在將數據傳給算法之前,你可以在實時系統中使用這些函數。
這可以讓你方便地嘗試多種數據轉換,查看那些轉換方法結合起來效果最好。
但是,還是先回到清洗訓練集(通過再次復制strat_train_set),將預測量和標簽分開,因為我們不想對預測量和目標值應用相同的轉換(注意drop()創建了一份數據的備份,而不影響strat_train_set):
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()
(第一部分 機器學習基礎)
第01章 機器學習概覽
第02章 一個完整的機器學習項目(上)
第02章 一個完整的機器學習項目(下)
第03章 分類
第04章 訓練模型
第05章 支持向量機
第06章 決策樹
第07章 集成學習和隨機森林
第08章 降維
(第二部分 神經網絡和深度學習)
第9章 啟動和運行TensorFlow
第10章 人工神經網絡
第11章 訓練深度神經網絡(上)
第11章 訓練深度神經網絡(下)
第12章 設備和服務器上的分布式 TensorFlow
第13章 卷積神經網絡
第14章 循環神經網絡
第15章 自編碼器
第16章 強化學習(上)
第16章 強化學習(下)