創建測試集
在這個階段就分割數據,聽起來很奇怪。畢竟,你只是簡單快速地查看了數據而已,你需要再仔細調查下數據以決定使用什么算法。這么想是對的,但是人類的大腦是一個神奇的發現規律的系統,這意味著大腦非常容易發生過擬合:如果你查看了測試集,就會不經意地按照測試集中的規律來選擇某個特定的機器學習模型。再當你使用測試集來評估誤差率時,就會導致評估過于樂觀,而實際部署的系統表現就會差。這稱為數據透視偏差。
理論上,創建測試集很簡單:只要隨機挑選一些實例,一般是數據集的 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)。
但是如果數據集更新,這兩個方法都會失效。一個通常的解決辦法是使用每個實例的ID來判定這個實例是否應該放入測試集(假設每個實例都有唯一并且不變的ID)。例如,你可以計算出每個實例ID的哈希值,只保留其最后一個字節,如果該值小于等于 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這一列。最簡單的方法是使用行索引作為 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-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 對比了總數據集、分層采樣的測試集、純隨機采樣測試集的收入分類比例??梢钥吹?,分層采樣測試集的收入分類比例與總數據集幾乎相同,而隨機采樣數據集偏差嚴重。
圖 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()
地理數據可視化
housing.plot(kind="scatter", x="longitude", y="latitude")
因為存在地理信息(緯度和經度),創建一個所有街區的散點圖來數據可視化是一個不錯的主意(圖 2-11):
圖 2-11 數據的地理信息散點圖
這張圖看起來很像加州,但是看不出什么特別的規律。將alpha設為 0.1,可以更容易看出數據點的密度(圖 2-12):
圖 2-12 顯示高密度區域的散點圖
現在看起來好多了:可以非常清楚地看到高密度區域,灣區、洛杉磯和圣迭戈,以及中央谷,特別是從薩克拉門托和弗雷斯諾。
通常來講,人類的大腦非常善于發現圖片中的規律,但是需要調整可視化參數使規律顯現出來。
現在來看房價(圖 2-13)。每個圈的半徑表示街區的人口(選項s),顏色代表價格(選項c)。我們用預先定義的名為jet的顏色圖(選項cmap),它的范圍是從藍色(低價)到紅色(高價):
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()
圖 2-13 加州房價
這張圖說明房價和位置(比如,靠海)和人口密度聯系密切,這點你可能早就知道??梢允褂镁垲愃惴▉頇z測主要的聚集,用一個新的特征值測量聚集中心的距離。盡管北加州海岸區域的房價不是非常高,但離大海距離屬性也可能很有用,所以這不是用一個簡單的規則就可以定義的問題。
查找關聯
因為數據集并不是非常大,你可以很容易地使用corr()方法計算出每對屬性間的標準相關系數(standard correlation coefficient,也稱作皮爾遜相關系數):
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 展示了相關系數在橫軸和縱軸之間的不同圖形。
圖 2-14 不同數據集的標準相關系數(來源:Wikipedia;公共領域圖片)
警告:相關系數只測量線性關系(如果x上升,y則上升或下降)。相關系數可能會完全忽略非線性關系(例如,如果x接近 0,則y值會變高)。在上面圖片的最后一行中,他們的相關系數都接近于 0,盡管它們的軸并不獨立:這些就是非線性關系的例子。另外,第二行的相關系數等于 1 或 -1;這和斜率沒有任何關系。例如,你的身高(單位是英寸)與身高(單位是英尺或納米)的相關系數就是 1。
另一種檢測屬性間相關系數的方法是使用 Pandas 的scatter_matrix函數,它能畫出每個數值屬性對每個其它數值屬性的圖。因為現在共有 11 個數值屬性,你可以得到11 ** 2 = 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))
圖 2-15 散點矩陣
如果 pandas 將每個變量對自己作圖,主對角線(左上到右下)都會是直線圖。所以 Pandas 展示的是每個屬性的柱狀圖(也可以是其它的,請參考 Pandas 文檔)。
最有希望用來預測房價中位數的屬性是收入中位數,因此將這張圖放大(圖 2-16):
housing.plot(kind="scatter", x="median_income",y="median_house_value", alpha=0.1)
圖 2-16 收入中位數 vs 房價中位數
這張圖說明了幾點。首先,相關性非常高;可以清晰地看到向上的趨勢,并且數據點不是非常分散。第二,我們之前看到的最高價,清晰地呈現為一條位于 $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屬性與房價中位數的關聯更強。顯然,臥室數/總房間數的比例越低,房價就越高。每戶的房間數也比街區的總房間數的更有信息,很明顯,房屋越大,房價就越高。
這一步的數據探索不必非常完備,此處的目的是有一個正確的開始,快速發現規律,以得到一個合理的原型。但是這是一個交互過程:一旦你得到了一個原型,并運行起來,你就可以分析它的輸出,進而發現更多的規律,然后再回到數據探索這步。
-
機器學習
+關注
關注
66文章
8493瀏覽量
134178 -
數據可視化
+關注
關注
0文章
475瀏覽量
10717
原文標題:【翻譯】Sklearn 與 TensorFlow 機器學習實用指南 —— 第2章 一個完整的機器學習項目(中)
文章出處:【微信號:AI_shequ,微信公眾號:人工智能愛好者社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
如何使用python進行第一個機器學習項目(詳細教程篇)
創建一個邊緣機器學習系統
人工智能:你知道這八個超贊的機器學習項目嗎?
一個機器學習系統的需求建模與決策選擇

評論