- 機器學習實戰:基于Scikit-Learn、Keras和TensorFlow(原書第3版)
- (法)奧雷利安·杰龍
- 6609字
- 2024-09-11 17:33:03
2.3 獲取數據
是時候動手了。不要猶豫,拿起你的筆記本計算機并瀏覽代碼示例。正如我在前言中提到的,本書中的所有代碼示例都是開源的,可以作為Jupyter notebook在線獲取(https://github.com/ageron/handson-ml3),它們是包含文本、圖像和可執行代碼片段的交互式文檔(在我們的示例中是Python)。在本書中,假設你在Google Colab上運行這些代碼,這是一項免費服務,可讓你直接在線運行任何Jupyter notebook,而無須在你的機器上安裝任何東西。如果你想使用其他在線平臺(例如Kaggle),或者如果你想在自己的機器上本地安裝所有內容,請參閱本書的GitHub頁面上的說明。
2.3.1 使用Google Colab運行代碼示例
首先,打開網絡瀏覽器并訪問https://homl.info/colab3:這將帶你進入Google Colab,它將顯示本書的Jupyter notebook列表(見圖2-3)。你會發現每章有一個notebook,外加一些額外的notebook以及NumPy、Matplotlib、Pandas、線性代數和微積分的教程。例如,如果你單擊02_end_to_end_machine_learning_project.ipynb,那么第2章中的notebook將在Google Colab中打開(見圖2-4)。

圖2-3:Google Colab中的notebook列表
Jupyter notebook由單元格列表組成。每個單元格包含可執行代碼或文本。嘗試雙擊第一個文本單元格(其中包含句子“Welcome to Machine Learning Housing Corp.!”)。這將打開單元格進行編輯。請注意,Jupyter notebook使用Markdown語法進行格式化(例如,**粗體**、*斜體*、#標題、[url](鏈接文本)等)。嘗試修改此文本,然后按Shift-Enter查看結果。

圖2-4:你在Google Colab中的notebook
接下來,通過從菜單中選擇Insert→“Code cell”來創建一個新的代碼單元格。或者,你可以單擊工具欄中的+Code按鈕,或將鼠標懸停在單元格底部,直到看到+Code和+Text出現,然后單擊+Code。在新的代碼單元格中,鍵入一些Python代碼,例如print(“Hello World”),然后按Shift-Enter運行此代碼(或單擊單元格左側的按鈕)。
如果你尚未登錄Google賬戶,則系統會要求你立即登錄(如果你還沒有Google賬戶,則需要創建一個)。登錄后,當你嘗試運行代碼時,你會看到一條安全警告,告訴你此notebook不是由Google創作的。一個惡意的人可能會創建一個notebook,試圖誘騙你輸入你的Google憑據,然后就可以訪問你的個人數據,因此在你運行notebook之前,請始終確保你信任其作者(在運行它之前,仔細檢查每個代碼單元格將執行的操作)。假設你相信我(或者你計劃檢查每個代碼單元格),你現在可以單擊“Run anyway”。
Colab然后會為你分配一個新的運行時:這是一個位于Google服務器上的免費虛擬機,其中包含一堆工具和Python庫,包括本書大多數章節所需的一切(在某些章節中,你需要運行安裝附加庫的命令)。這需要幾秒鐘。接下來,Colab將自動連接到此運行時并使用它來執行你的新代碼單元。重要的是,這些代碼在運行時運行,而不是在你的機器上運行。代碼的輸出將顯示在單元格下方。恭喜,你已經在Colab上運行了一些Python代碼!
要插入新的代碼單元格,你還可以鍵入Ctrl-M(或macOS上的Cmd-M),然后鍵入A(在活動單元格的上方插入)或B(在下方插入)。還有許多其他可用的鍵盤快捷鍵:你可以通過鍵入Ctrl-M(或Cmd-M)然后鍵入H來查看和編輯它們。如果你選擇在Kaggle或你自己的機器上使用JupyterLab或帶有Jupyter擴展的Visual Studio Code等IDE來運行notebook,你會看到一些細微差別——運行時稱為內核,用戶界面和鍵盤快捷鍵略有不同,等等——但是從一個Jupyter環境切換到另一個環境并不難。
2.3.2 保存你的代碼更改和數據
你可以對Colab notebook進行更改,只要你保持瀏覽器選項卡打開,它們就會一直存在。但是一旦關閉它,所做的更改就會丟失。為避免這種情況,請確保通過選擇File→“Save a copy in Drive”將notebook的副本保存到你的Google Drive。或者,你可以通過選擇File→Download→“Download.ipynb”將notebook下載到你的計算機。然后你可以稍后訪問https://colab.research.google.com并再次打開notebook(從Google Drive或從你的計算機上傳)。
Google Colab僅供交互使用:你可以在notebook中隨意調整代碼,但不能讓notebook長時間無人值守,否則運行時將關閉并且所有它的數據將丟失。
如果notebook生成了你關心的數據,請確保在運行時關閉之前下載此數據。為此,單擊文件圖標(見圖2-5中的步驟1),找到你要下載的文件,單擊它旁邊的垂直點(步驟2),然后單擊下載(步驟3)。或者,你可以將Google Drive掛載在運行時上,讓notebook可以直接將文件讀寫到Google Drive,就好像它是本地目錄一樣。為此,單擊文件圖標(第1步),然后單擊Google Drive圖標(在圖2-5中圈出)并按照屏幕上的說明進行操作。

圖2-5:從Google Colab運行時下載文件(第1步到第3步),或裝載你的Google Drive(帶圓圈的圖標)
默認情況下,你的Google Drive將安裝在/content/drive/MyDrive。如果要備份數據文件,只需運行!cp/content/my_great_model/content/drive/MyDrive將其復制到此目錄即可。任何以!開頭的命令都被視為shell命令,而不是Python代碼。cp是Linux shell命令,用于將文件從一個路徑復制到另一個路徑。請注意,Colab運行時在Linux(特別是Ubuntu)上運行。
2.3.3 交互性的力量和危險
Jupyter notebook是交互式的,這是一件很棒的事情:你可以一個一個地運行每個單元格、在任何時候停止、插入一個單元格研究代碼、返回并再次運行同一個單元格,等等。我強烈建議你這樣做。如果你只是一個一個地運行單元格而不去動手嘗試它們,你就不會學得那么快。然而,這種靈活性是有代價的:很容易以錯誤的順序運行單元格,或者忘記運行一個單元格。如果發生這種情況,后續的代碼單元格很可能會失敗。例如,每個notebook中的第一個代碼單元格包含設置代碼(例如導入),因此請確保先運行它,否則所有單元格將無法運行。
如果遇到奇怪的錯誤,請嘗試重新啟動運行時(通過從菜單中選擇Runtime→“Restart runtime”),然后從notebook的開頭再次運行所有單元格。這通常可以解決問題。如果問題未解決,則可能是你所做的其中一項更改破壞了notebook:只需恢復到原始notebook并重試。如果仍然失敗,請在GitHub上提交問題。
2.3.4 本書代碼與notebook代碼
有時你可能會注意到本書代碼與notebook代碼之間存在一些細微差別。發生這種情況可能有以下幾種原因:
· 當你閱讀這些內容時,代碼庫可能已經發生了細微的變化,或者盡管我盡了最大的努力,但我還是在書中犯了錯誤。可悲的是,我無法修復這本書中的代碼(除非你正在閱讀電子版并且你可以下載最新版本),但我可以修復notebook。所以,如果你是從本書中復制代碼后遇到錯誤,請在notebook中查找修復的代碼:我會努力保持它們沒有錯誤并與最新的庫版本保持同步。
· notebook中包含一些額外的代碼來美化圖形(添加標簽、設置字體大小等)并為本書以高分辨率來保存它們。如果你愿意,你可以安全地忽略這些額外的代碼。
我優化了代碼的可讀性和簡單性:我讓它盡可能的保持線性和扁平,只定義很少的函數或類。目標是確保你正在運行的代碼通常就在你面前,而不是嵌套在你必須搜索的多個抽象層中。這也使你可以更輕松地使用代碼。為簡單起見,錯誤處理的代碼很有限,我將一些最不常見的導入放在需要的地方(而不是按照PEP 8 Python樣式指南的建議將它們放在文件頂部)。也就是說,你的生產環境代碼不會有太大的不同:只是更加模塊化,并且具有額外的測試和錯誤處理。
好的!一旦你熟悉了Colab,就可以下載數據了。
2.3.5 下載數據
在典型的環境中,你的數據在關系數據庫或其他一些通用數據存儲中,并分布在多個表/文檔/文件中。要訪問它,你首先需要獲得你的憑據和訪問授權[4],并熟悉數據模式。然而,在這個項目中,事情要簡單得多:你只需下載一個壓縮文件housing.tgz,其中包含一個名為housing.csv的逗號分隔值(Comma-Separated Value,CSV)文件,其中包含所有數據。
與其手動下載和解壓縮數據,不如編寫一個函數來為你完成這些工作。這在數據定期更改時特別有用:你可以編寫一個小腳本,使用該函數來獲取最新數據(或者你可以設置一個計劃作業來定期自動執行此操作)。如果你需要在多臺機器上安裝數據集,自動化獲取數據的過程也很有用。
這是獲取和加載數據的函數:

調用load_housing_data()時,它會查找datasets/housing.tgz文件。如果找不到,它會在當前目錄(在Colab中默認為/content)中創建datasets目錄,從ageron/data GitHub存儲庫下載housing.tgz文件,并將其內容提取到datasets目錄中。這將創建datasets/housing目錄,其中包含housing.csv文件。最后,該函數將此CSV文件加載到包含所有數據的Pandas DataFrame對象中,并返回它。
2.3.6 快速瀏覽數據結構
首先使用DataFrame的head()方法查看前5行數據(見圖2-6)。

圖2-6:數據集中的前5行
每一行代表一個地區。有10個屬性(圖2-6中沒有全部顯示):longitude、latitude、housing_median_age、total_rooms、total_bedrooms、population、households、median_income、median_house_value和ocean_proximity。
info()方法對于獲取數據的快速描述很有用,特別是總行數、每個屬性的類型和非空值的數量:

在本書中,當代碼示例包含代碼和輸出的混合時,就像這里的情況一樣,它的格式與Python解釋器中的一樣,以提高可讀性:代碼行以>>>為前綴(或...縮進塊),并且輸出沒有前綴。
數據集中有20 640個實例,這意味著按照機器學習標準,它相當小,但非常適合入門。你注意到total_bedrooms屬性只有20 433個非空值,這意味著207個地區缺少此屬性。你稍后需要處理這個問題。
除了ocean_proximity之外,所有屬性都是數字的。它的類型是對象,所以它可以容納任何類型的Python對象。但是由于你是從CSV文件加載此數據的,所以你知道它一定是文本屬性。當你查看前五行時,你可能會注意到ocean_proximity列中的值是重復的,這意味著它可能是一個分類屬性。你可以使用value_counts()方法找出存在哪些類別以及每個類別有多少個地區:

讓我們看看其他領域。describe()方法顯示數字屬性的摘要(見圖2-7)。

圖2-7:每個數字屬性的摘要
count、mean、min和max行是不言自明的。請注意,空值將被忽略(因此,例如,total_bedrooms的計數是20 433,而不是20 640)。std行顯示標準差,標準差衡量值的分散程度[5]。25%、50%和75%行顯示相應的百分位數:百分位數表示一組觀察值中給定百分比的觀察值低于該值。例如,25%的地區housing_median_age低于18,50%低于29,75%低于37。這些通常稱為第25個百分位數(或第一個四分位數)、中位數和第75個百分位數(或第三個四分位數)。
另一種快速了解你正在處理的數據類型的方法是為每個數值屬性繪制直方圖。直方圖顯示具有給定值范圍(在水平軸上)的實例數(在垂直軸上)。你可以一次繪制一個屬性,也可以對整個數據集調用hist()方法(如下面的代碼示例所示),它會繪制每個數值屬性的直方圖(見圖2-8):


圖2-8:每個數值屬性的直方圖

圖2-8:每個數值屬性的直方圖(續)
查看這些直方圖,你會注意到一些事情:
· 首先,收入中位數屬性看起來不像是以美元表示的。在與收集數據的團隊核實后,你被告知數據已按比例縮放并上限為15(實際上是15.0001)以表示較高的收入中位數,以及0.5(實際上是0.4999)以表示較低的收入中位數。這些數字大約代表萬美元(例如,3實際上表示大約30 000美元)。使用預處理屬性在機器學習中很常見,這不一定是個問題,但你應該了解數據是如何計算的。
· 房屋年齡中位數和房價中位數也有上限。后者可能是一個嚴重的問題,因為它是你的目標屬性(你的標簽)。你的機器學習算法可能會學習到價格永遠不會超過該限制。你需要與你的客戶團隊(將使用你的系統輸出的團隊)確認這是否是一個問題。如果他們告訴你他們需要超過500 000美元的精確預測,那么你有兩個選擇:
◆ 為標簽被封頂的地區收集適當的標簽。
◆ 從訓練集中刪除這些地區(也從測試集中刪除這些地區,因為如果系統預測值超過500 000美元,則不應該評估它不好)。
· 這些屬性具有非常不同的尺度。我們將在本章稍后探討特征縮放時討論這個問題。
· 最后,許多直方圖向右傾斜:它們向中位數右側的延伸比向左延伸的遠。這可能會使某些機器學習算法更難檢測到正確模式。稍后,你將嘗試轉換這些屬性來獲得更對稱的鐘形分布。
你現在應該對正在處理的數據類型有了更好的理解。
等等!在你進一步查看數據之前,你需要創建一個測試集,把它放在一邊,永遠不要看它。
2.3.7 創建測試集
在這個階段自愿保留部分數據可能看起來很奇怪。畢竟,你只是快速瀏覽了數據,在決定使用什么算法之前,你肯定應該了解更多相關信息,對吧?這是事實,但你的大腦是一個驚人的模式檢測系統,這也意味著它極易過擬合:如果你查看測試集,你可能會偶然發現測試數據中一些看似有趣的模式,從而引導你選擇一種特殊的機器學習模型。當你使用測試集估計泛化誤差時,你的估計會過于樂觀,并且你將啟動一個性能不如預期的系統。這稱為數據窺探偏差。
創建測試集在理論上很簡單;隨機選擇一些實例,通常是數據集的20%(如果你的數據集非常大,則更少),然后將它們放在一邊:

然后你可以像這樣使用這個函數:

好吧,這可以工作,但并不完美:如果你再次運行該程序,它將生成不同的測試集!隨著時間的推移,你(或你的機器學習算法)將會看到整個數據集,這是你要避免的。
一種解決方案是在第一次運行時保存測試集,然后在后續運行中加載它。另一種選擇是在調用np.random.permutation()之前設置隨機數生成器的種子[例如,使用np.random.seed(42)][6],以便它始終生成相同的混淆索引。
但是,這兩種解決方案都會在下次獲取更新的數據集時失效。為了在更新數據集后也有穩定的訓練/測試拆分,一個常見的解決方案是使用每個實例的標識符來決定它是否應該進入測試集(假設實例具有唯一且不可變的標識符)。例如,你可以計算每個實例標識符的哈希值,如果哈希值低于或等于最大哈希值的20%,則將該實例放入測試集中。這可確保測試集在多次運行中保持一致,即使你刷新數據集也是如此。新的測試集將包含20%的新實例,但不會包含之前訓練集中的任何實例。
以下是一個可能的實現:

不幸的是,房屋數據集沒有標識符列。最簡單的解決方案是使用行索引作為ID:

如果使用行索引作為唯一標識符,則需要確保將新數據附加到數據集的末尾并且不會刪除任何行。如果這做不到,那么你可以嘗試使用最穩定的特征來構建唯一的標識符。例如,一個地區的經緯度保證在幾百萬年內保持穩定,因此你可以將它們組合成一個ID,如下所示[7]:

Scikit-Learn提供了一些函數以各種方式將數據集拆分為多個子集。最簡單的函數是train_test_split(),它所做的事情與我們之前定義的shuffle_and_split_data()函數幾乎相同,但有幾個附加功能。首先,有一個random_state參數允許你設置隨機生成器種子。其次,你可以將多個具有相同行數的數據集傳遞給它,并且它會在相同的索引上拆分它們(這非常有用,例如,如果你有一個單獨標簽的DataFrame):

到目前為止,我們已經考慮了純隨機的采樣方法。如果你的數據集足夠大(尤其是相對于屬性的數量),那么這通常沒問題。但如果不夠大,你就有引入顯著采樣偏差的風險。當一家調查公司的員工決定打電話給1000個人問他們幾個問題時,他們不會只是在電話簿中隨機挑選1000個人。就他們想問的問題而言,他們試圖確保這1000人代表全體人口。例如,美國人口中女性占51.1%,男性占48.9%,因此在美國進行一項良好的調查需要嘗試在樣本中保持這一比例:511名女性和489名男性(至少在答案可能因性別而異的情況下)。這稱為分層采樣:將總體分為稱為層的同質子組,并從每個層中抽取正確數量的實例以保證測試集能代表總體。如果進行調查的人使用純隨機采樣,則大約有10.7%的機會會抽取到女性參與者少于48.5%或超過53.5%的偏差測試集。無論采用哪種方式,調查結果都可能偏差非常大。
假設你與一些專家聊天,他們告訴你收入中位數是預測房價中位數的一個非常重要的屬性。你可能希望確保測試集能代表整個數據集中的各種收入類別。由于收入中位數是一個連續的數值屬性,你首先需要創建一個收入類別屬性。讓我們更仔細地看一下收入中位數直方圖(回到圖2-8):大多數收入中位數集中在1.5~6左右(即15 000~60 000美元),但一些收入中位數遠遠超過6。重要的是每個層的數據集中要有足夠數量的實例,否則對層重要性的估計可能有偏差。這意味著你不應該有太多的層,每個層應該足夠大。下面的代碼使用pd.cut()函數創建了一個收入類別屬性,有5個類別(標記為1到5);類別1的范圍從0到1.5(即低于15 000美元),類別2的范圍從1.5到3,以此類推:

這些收入類別如圖2-9所示:

圖2-9:收入類別直方圖

現在你可以根據收入類別進行分層采樣了。Scikit-Learn在sklearn.model_selection包中提供了許多拆分類,它們實現了各種策略,將數據集拆分為訓練集和測試集。每個拆分器都有一個split()方法,該方法返回對相同數據的不同訓練/測試拆分的迭代器。
準確地說,split()方法產生訓練和測試指標,而不是數據本身。如果你想更好地估計模型的性能,那么進行多次拆分會很有用,正如我們將在本章后面討論交叉驗證時所看到的。例如,以下代碼生成同一數據集的10個不同的分層拆分:

現在,你可以只使用第一個拆分:

或者,由于分層采樣相當普遍,因此有一種更簡潔的方法可以使用帶有stratify參數的train_test_split()函數來獲得單個拆分:

讓我們看看這是否按預期工作。可以從查看測試集中的收入類別比例開始:

使用類似的代碼,你可以測量完整數據集中的收入類別比例。圖2-10比較了完整數據集、分層采樣生成的測試集和純隨機采樣生成的測試集中的收入類別比例。如你所見,使用分層采樣生成的測試集的收入類別比例與完整數據集中的收入類別比例幾乎相同,而使用純隨機采樣生成的測試集則存在偏差。

圖2-10:分層采樣與純隨機采樣的采樣偏差比較
你不會再次使用income_cat列,因此你不妨刪除它,將數據恢復到其原始狀態:

我們在測試集生成上花費了大量時間是有充分理由的:這是機器學習項目中經常被忽視但至關重要的部分。此外,當我們討論交叉驗證時,其中的許多想法都會很有用。現在是時候進入下一階段了:探索數據。