本章中,你會假裝作為被一家地產公司剛剛僱傭的數據科學家,完整地學習一個案例項目。下面是主要步驟:
項目概述。
獲取數據。
發現並可視化數據,發現規律。
為機器學習算法準備數據。
選擇模型,進行訓練。
微調模型。
給出解決方案。
部署、監控、維護系統。
學習機器學習時,最好使用真實數據,而不是人工數據集。幸運的是,有上千個開源數據集可以進行選擇,涵蓋多個領域。以下是一些可以查找的數據的地方:
流行的開源數據倉庫:
準入口(提供開源數據列表)
其它列出流行開源數據倉庫的網頁:
本章,我們選擇的是 StatLib 的加州房產價格數據集(見圖 2-1)。這個數據集是基於 1990 年加州普查的數據。數據已經有點老(1990 年還能買一個灣區不錯的房子),但是它有許多優點,利於學習,所以假設這個數據為最近的。為了便於教學,我們添加了一個類別屬性,並除去了一些。
圖 2-1 加州房產價格
歡迎來到機器學習房地產公司!你的第一個任務是利用加州普查數據,建立一個加州房價模型。這個數據包含每個街區組的人口、收入中位數、房價中位數等指標。
街區組是美國調查局發布樣本數據的最小地理單位(一個街區通常有 600 到 3000 人)。我們將其簡稱為「街區」。
你的模型要利用這個數據進行學習,然後根據其它指標,預測任何街區的的房價中位數。
提示:你是一個有條理的數據科學家,你要做的第一件事是拿出你的機器學習項目清單。你可以使用附錄 B 中的清單;這個清單適用於大多數的機器學習項目,但是你還是要確認它是否滿足需求。在本章中,我們會檢查許多清單上的項目,但是也會跳過一些簡單的,有些會在後面的章節再討論。
問老闆的第一個問題應該是商業目標是什麼?建立模型可能不是最終目標。公司要如何使用、並從模型受益?這非常重要,因為它決定了如何劃定問題,要選擇什麼算法,評估模型性能的指標是什麼,要花多少精力進行微調。
老闆告訴你你的模型的輸出(一個區的房價中位數)會傳給另一個機器學習系統(見圖 2-2),也有其它信號會傳入後面的系統。這一整套系統可以確定某個區進行投資值不值。確定值不值得投資非常重要,它直接影響利潤。
圖 2-2 房地產投資的機器學習流水線
流水線
一系列的數據處理組件被稱為數據流水線。流水線在機器學習系統中很常見,因為有許多數據要處理和轉換。
組件通常是異步運行的。每個組件吸納進大量數據,進行處理,然後將數據傳輸到另一個數據容器中,而後流水線中的另一個組件收入這個數據,然後輸出,這個過程依次進行下去。每個組件都是獨立的:組件間的接口只是數據容器。這樣可以讓系統更便於理解(記住數據流的圖),不同的項目組可以關注於不同的組件。進而,如果一個組件失效了,下遊的組件使用失效組件最後生產的數據,通常可以正常運行(一段時間)。這樣就使整個架構相當健壯。
另一方面,如果沒有監控,失效的組件會在不被注意的情況下運行一段時間。數據會受到汙染,整個系統的性能就會下降。
下一個要問的問題是,現在的解決方案效果如何。老闆通常會給一個參考性能,以及如何解決問題。老闆說,現在街區的房價是靠專家手工估計的,專家隊伍收集最新的關於一個區的信息(不包括房價中位數),他們使用複雜的規則進行估計。這種方法費錢費時間,而且估計結果不理想,誤差率大概有 15%。
OK,有了這些信息,你就可以開始設計系統了。首先,你需要劃定問題:監督或非監督,還是強化學習?這是個分類任務、回歸任務,還是其它的?要使用批量學習還是線上學習?繼續閱讀之前,請暫停一下,嘗試自己回答下這些問題。
你能回答出來嗎?一起看下答案:很明顯,這是一個典型的監督學習任務,因為你要使用的是有標籤的訓練樣本(每個實例都有預定的產出,即街區的房價中位數)。並且,這是一個典型的回歸任務,因為你要預測一個值。講的更細些,這是一個多變量回歸問題,因為系統要使用多個變量進行預測(要使用街區的人口,收入中位數等等)。在第一章中,你只是根據人均 GDP 來預測生活滿意度,因此這是一個單變量回歸問題。最後,沒有連續的數據流進入系統,沒有特別需求需要對數據變動作出快速適應。數據量不大可以放到內存中,因此批量學習就夠了。
提示:如果數據量很大,你可以要麼在多個伺服器上對批量學習做拆分(使用 MapReduce 技術,後面會看到),或是使用線上學習。
下一步是選擇性能指標。回歸問題的典型指標是均方根誤差(RMSE)。均方根誤差測量的是系統預測誤差的標準差。例如,RMSE 等於 50000,意味著,68% 的系統預測值位於實際值的 $50000 以內,95% 的預測值位於實際值的 $100000 以內(一個特徵通常都符合高斯分布,即滿足 「68-95-99.7」規則:大約68%的值落在1σ內,95% 的值落在2σ內,99.7%的值落在3σ內,這裡的σ等於50000)。公式 2-1 展示了計算 RMSE 的方法。
公式 2-1 均方根誤差(RMSE)
符號的含義
這個方程引入了一些常見的貫穿本書的機器學習符號:
m是測量 RMSE 的數據集中的實例數量。
例如,如果用一個含有 2000 個街區的驗證集求 RMSE,則m = 2000。
$x^{(i)}$ 是數據集第i個實例的所有特徵值(不包含標籤)的向量,$y^{(i)}$ 是它的標籤(這個實例的輸出值)。
例如,如果數據集中的第一個街區位於經度 –118.29°,緯度 33.91°,有 1416 名居民,收入中位數是 $38372,房價中位數是 $156400(忽略掉其它的特徵),則有:
和,
X是包含數據集中所有實例的所有特徵值(不包含標籤)的矩陣。每一行是一個實例,第i行是 $x^{(i)}$ 的轉置,記為 $x^{(i)T}$。
例如,仍然是前面提到的第一區,矩陣X就是:
h是系統的預測函數,也稱為假設(hypothesis)。當系統收到一個實例的特徵向量 $x^{(i)}$,就會輸出這個實例的一個預測值 $hat y^{(i)} = h(x^{(i)})$($hat y$ 讀作y-hat)。
例如,如果系統預測第一區的房價中位數是 $158400,則 $hat y^{(1)} = h(x^{(1)}) = 158400$。預測誤差是 $hat y^{(1)} – y^{(1)} = 2000$。
RMSE(X,h)是使用假設h在樣本集上測量的損失函數。
我們使用小寫斜體表示標量值(例如 $it m$ 或 $it{y^{(i)}}$)和函數名(例如 $it h$),小寫粗體表示向量(例如 $b{x^{(i)}}$),大寫粗體表示矩陣(例如 $b{X}$)。
雖然大多數時候 RMSE 是回歸任務可靠的性能指標,在有些情況下,你可能需要另外的函數。例如,假設存在許多異常的街區。此時,你可能需要使用平均絕對誤差(Mean Absolute Error,也稱作平均絕對偏差),見公式 2-2:
公式2-2 平均絕對誤差
RMSE 和 MAE 都是測量預測值和目標值兩個向量距離的方法。有多種測量距離的方法,或範數:
計算對應歐幾裡得範數的平方和的根(RMSE):這個距離介紹過。它也稱作ℓ2範數,標記為 $| cdot |_2$(或只是 $| cdot |$)。
計算對應於ℓ1(標記為 $| cdot |_1$)範數的絕對值和(MAE)。有時,也稱其為曼哈頓範數,因為它測量了城市中的兩點,沿著矩形的邊行走的距離。
更一般的,包含n個元素的向量v的ℓk範數(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 --versionpip 9.0.1 from [...]/lib/python3.5/site-packages (python 3.5)
你需要保證pip是近期的版本,至少高於 1.4,以保障二進位模塊文件的安裝(也稱為 wheel)。要升級pip,可以使用下面的命令:
$ pip3 install --upgrade pipCollecting pip[...]Successfully installed pip-9.0.1
創建獨立環境
如果你希望在一個獨立環境中工作(強烈推薦這麼做,不同項目的庫的版本不會衝突),用下面的pip命令安裝virtualenv:
$ pip3 install --user --upgrade virtualenvCollecting virtualenv[...]Successfully installed virtualenv
現在可以通過下面命令創建一個獨立的 Python 環境:
$ cd $ML_PATH$ virtualenv envUsing base prefix '[...]'New python executable in [...]/ml/env/bin/python3.5Also creating executable in [...]/ml/env/bin/pythonInstalling 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-learnCollecting jupyter Downloading jupyter-1.0.0-py2.py3-none-any.whlCollecting 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 allkernels (twice to skip confirmation).
Jupyter 伺服器現在運行在終端上,監聽 888 8埠。你可以用瀏覽器打開http://localhost:8888/,以訪問這個伺服器(伺服器啟動時,通常就自動打開了)。你可以看到一個空的工作空間目錄(如果按照先前的virtualenv步驟,只包含env目錄)。
現在點擊按鈕 New 創建一個新的 Python 注本,選擇合適的 Python 版本(見圖 2-3)。
圖 2-3 Jupyter 的工作空間
這一步做了三件事:首先,在工作空間中創建了一個新的 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 的基本操作。
圖 2-4 在 notebook 中列印Hello world!
一般情況下,數據是存儲於關係型資料庫(或其它常見資料庫)中的多個表、文檔、文件。要訪問數據,你首先要有密碼和登錄權限,並要了解數據模式。但是在這個項目中,這一切要簡單些:只要下載一個壓縮文件,housing.tgz,它包含一個 CSV 文件housing.csv,含有所有數據。
你可以使用瀏覽器下載,運行tar xzf housing.tgz解壓出csv文件,但是更好的辦法是寫一個小函數來做這件事。如果數據變動頻繁,這麼做是非常好的,因為可以讓你寫一個小腳本隨時獲取最新的數據(或者創建一個定時任務來做)。如果你想在多臺機器上安裝數據集,獲取數據自動化也是非常好的。
下面是獲取數據的函數:
import osimport tarfilefrom six.moves import urllibDOWNLOAD_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 pddef 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)。
圖 2-5 數據集的前五行
每一行都表示一個街區。共有 10 個屬性(截圖中可以看到 6 個):經度、維度、房屋年齡中位數、總房間數、總臥室數、人口數、家庭數、收入中位數、房屋價值中位數、離大海距離。
info()方法可以快速查看數據的描述,特別是總行數、每個屬性的類型和非空值的數量(見圖 2-6)。
圖 2-6 房屋信息
數據集中共有 20640 個實例,按照機器學習的標準這個數據量很小,但是非常適合入門。我們注意到總房間數只有 20433 個非空值,這意味著有 207 個街區缺少這個值。我們將在後面對它進行處理。
所有的屬性都是數值的,除了離大海距離這項。它的類型是對象,因此可以包含任意 Python 對象,但是因為該項是從 CSV 文件加載的,所以必然是文本類型。在剛才查看數據前五項時,你可能注意到那一列的值是重複的,意味著它可能是一項表示類別的屬性。可以使用value_counts()方法查看該項中都有哪些類別,每個類別中都包含有多少個街區:
>>> housing["ocean_proximity"].value_counts()<1H OCEAN 9136INLAND 6551NEAR OCEAN 2658NEAR BAY 2290ISLAND 5Name: ocean_proximity, dtype: int64
再來看其它欄位。describe()方法展示了數值屬性的概括(見圖 2-7)。
圖 2-7 每個數值屬性的概括
count、mean、min和max幾行的意思很明顯了。注意,空值被忽略了(所以,臥室總數是 20433 而不是 20640)。std是標準差(揭示數值的分散度)。25%、50%、75% 展示了對應的分位數:每個分位數指明小於這個值,且指定分組的百分比。例如,25% 的街區的房屋年齡中位數小於 18,而 50% 的小於 29,75% 的小於 37。這些值通常稱為第 25 個百分位數(或第一個四分位數),中位數,第 75 個百分位數(第三個四分位數)。
另一種快速了解數據類型的方法是畫出每個數值屬性的柱狀圖。柱狀圖(的縱軸)展示了特定範圍的實例的個數。你還可以一次給一個屬性畫圖,或對完整數據集調用hist()方法,後者會畫出每個數值屬性的柱狀圖(見圖 2-8)。例如,你可以看到略微超過 800 個街區的median_house_value值差不多等於 $500000。
%matplotlib inline # only in a Jupyter notebookimport matplotlib.pyplot as plthousing.hist(bins=50, figsize=(20,15))plt.show()
圖 2-8 每個數值屬性的柱狀圖
註:hist()方法依賴於 Matplotlib,後者依賴於用戶指定的圖形後端以列印到屏幕上。因此在畫圖之前,你要指定 Matplotlib 要使用的後端。最簡單的方法是使用 Jupyter 的魔術命令%matplotlib inline。它會告訴 Jupyter 設定好 Matplotlib,以使用 Jupyter 自己的後端。繪圖就會在 notebook 中渲染了。注意在 Jupyter 中調用show()不是必要的,因為代碼框執行後 Jupyter 會自動展示圖像。
注意柱狀圖中的一些點:
首先,收入中位數貌似不是美元(USD)。與數據採集團隊交流之後,你被告知數據是經過縮放調整的,過高收入中位數的會變為 15(實際為 15.0001),過低的會變為 5(實際為 0.4999)。在機器學習中對數據進行預處理很正常,這不一定是個問題,但你要明白數據是如何計算出來的。
房屋年齡中位數和房屋價值中位數也被設了上限。後者可能是個嚴重的問題,因為它是你的目標屬性(你的標籤)。你的機器學習算法可能學習到價格不會超出這個界限。你需要與下遊團隊核實,這是否會成為問題。如果他們告訴你他們需要明確的預測值,即使超過 $500000,你則有兩個選項:
對於設了上限的標籤,重新收集合適的標籤;
將這些街區從訓練集移除(也從測試集移除,因為若房價超出 $500000,你的系統就會被差評)。
這些屬性值有不同的量度。我們會在本章後面討論特徵縮放。
最後,許多柱狀圖的尾巴很長:相較於左邊,它們在中位數的右邊延伸過遠。對於某些機器學習算法,這會使檢測規律變得更難些。我們會在後面嘗試變換處理這些屬性,使其變為正態分布。
希望你現在對要處理的數據有一定了解了。
警告:稍等!在你進一步查看數據之前,你需要創建一個測試集,將它放在一旁,千萬不要再看它。
想要了解更多資訊,請掃描下方二維碼,關注機器學習研究會
轉自:人工智慧愛好者社區