- 機器學習實戰:基于Scikit-Learn、Keras和TensorFlow(原書第3版)
- (法)奧雷利安·杰龍
- 9058字
- 2024-09-11 17:33:04
2.5 為機器學習算法準備數據
是時候為你的機器學習算法準備數據了。這里你應該為這個目的編寫函數,而不是手動執行此操作,原因如下:
· 你可以在任何數據集上輕松地重現這些轉換(例如,下次你獲得新的數據集時)。
· 你可以逐步構建一個可在未來項目中重復使用的轉換函數庫。
· 你可以在實時系統中使用這些函數來轉換新數據,再將其提供給你的算法。
· 你可以輕松嘗試各種轉換并查看哪種轉換組合效果最好。
但首先,要恢復到干凈的訓練集(通過再次復制strat_train_set)。你還應該將預測變量和標簽分開,因為你不一定要對預測變量和目標值應用相同的轉換(請注意drop()創建數據的副本并且不影響strat_train_set):

2.5.1 清洗數據
大多數機器學習算法無法處理缺失的特征,因此你需要處理這些問題。例如,你之前注意到total_bedrooms屬性有一些缺失值。你可以通過三個選項來解決此問題:
1.去掉相應的地區。
2.去掉整個屬性。
3.將缺失值設置為某個值(零、均值、中位數等)。這稱為歸責。
你可以使用Pandas DataFrame的dropna()、drop()和fillna()方法輕松完成這些操作:

你決定選擇選項3,因為它破壞性最小,但你將使用一個方便的Scikit-Learn類:SimpleImputer,而不是前面的代碼。它的好處是將存儲每個特征的中位數值:這不僅可以在訓練集上估算缺失值,而且可以在驗證集、測試集和任何提供給模型的新數據上估算缺失值。要使用它,首先你需要創建一個SimpleImputer實例,指定你要用該屬性的中位數替換每個屬性的缺失值:

由于只能根據數值屬性來計算中位數,因此你需要創建僅包含數值屬性的數據副本(這將排除文本屬性ocean_proximity):

現在你可以使用fit()方法將imputer實例擬合到訓練數據中:

imputer簡單地計算了每個屬性的中位數并將結果存儲在它的statistics_實例變量中。只有total_bedrooms屬性有缺失值,但你不能確定系統上線后新數據中不會有任何缺失值,所以對所有數值屬性應用imputer更安全:

現在你可以使用這個“訓練有素”的imputer來通過用學習到的中位數替換缺失值來轉換訓練集:

缺失值也可以替換為平均值(strategy="mean"),或替換為最頻繁的值(strategy="most_frequent"),或替換為常數值(strategy="constant",fill_value=...)。最后兩種策略支持非數值數據。
sklearn.impute包中還有更強大的imputers(兩者都僅用于數值特征):
· KNNImputer將每個缺失值替換為該特征的k近鄰的平均值。該距離基于所有可用的特征。
· IterativeImputer為每個特征訓練一個回歸模型,以根據所有其他可用特征預測缺失值。然后,它會根據更新后的數據再次訓練模型,并多次重復該過程,在每次迭代中改進模型和替換值。
Scikit-Learn的設計
Scikit-Learn的API設計得非常好。這些是主要設計原則(https://homl.info/11)[9]:
一致性
所有對象共享一個一致且簡單的接口。
估計器
任何可以根據數據集估計某些參數的對象都稱為估計器(例如,SimpleImputer是估計器)。估計本身由fit()方法執行,它將一個數據集作為參數(或兩個數據集用于監督學習算法,第二個數據集包含標簽)。指導估計過程所需的任何其他參數都被視為超參數(例如SimpleImputer的strategy),并且必須將其設置為實例變量(通常通過構造函數參數)。
轉換器
一些估計器(例如SimpleImputer)也可以轉換數據集,這些被稱為轉換器。同樣,API很簡單:轉換由transform()方法執行,將要轉換的數據集作為參數。它返回轉換后的數據集。這種轉換通常依賴于學習到的參數,就像SimpleImputer的情況一樣。所有的轉換器還有一個名為fit_transform()的便捷方法,相當于先調用fit()再調用transform()(但有時fit_transform()會經過優化,運行速度更快)。
預測器
最后,一些估計器在給定數據集的情況下能夠進行預測,這些被稱為預測器。例如,第1章中的LinearRegression模型是一個預測器,給定一個國家的人均GDP,它預測生活滿意度。預測器有一個predict()方法,它獲取新實例的數據集并返回相應預測的數據集。它還有一個score()方法,可以在給定測試集(以及,在監督學習算法中對應的標簽)的情況下測量預測的質量[10]。
檢查
所有估計器的超參數都可以通過公開實例變量(例如,imputer.strategy)直接訪問,并且所有估計器的學習參數可以通過帶有下劃線后綴的公共實例變量訪問(例如,imputer.statistics_)。
防止類擴散
數據集表示為NumPy數組或SciPy稀疏矩陣,而不是自定義類。超參數只是常規的Python字符串或數字。
構成
盡可能重用現有的構建塊。例如,正如你看到的,很容易從任意序列的轉換器來創建一個Pipeline估計器,然后是最終估計器。
合理的默認值
Scikit-Learn為大多數參數提供了合理的默認值,可以輕松快速地創建基本工作系統。
Scikit-Learn轉換器輸出NumPy數組(或有時是SciPy稀疏矩陣),即使將Pandas DataFrame作為輸入[11]。因此,imputer.transform(housing_num)的輸出是一個 NumPy數組:X既沒有列名也沒有索引。幸運的是,將X包裝在DataFrame中并從housing_num中恢復列名和索引并不難:

2.5.2 處理文本和類別屬性
到目前為止,我們只處理了數字屬性,但你的數據也可能包含文本屬性。在這個數據集中,只有一個:ocean_proximity屬性。讓我們看看它在前幾個實例中的值:

它不是任意文本:可能的值數量有限,每個值代表一個類別。所以這個屬性是一個類別屬性。大多數機器學習算法更喜歡處理數字,所以讓我們將這些類別從文本轉換為數字。為此,我們可以使用Scikit-Learn的OrdinalEncoder類:

這是housing_cat_encoded中前幾個編碼值的樣子:

你可以使用categories_實例變量來獲取類別列表。它是一個包含每個分類屬性的一維類別數組的列表(在本例中,列表包含一個數組,因為只有一個分類屬性):

這種表示的一個問題是ML算法會假設兩個距離較近的值比兩個距離較遠的值更相似。這在某些情況下可能沒問題(例如,對于有序類別,如“壞”“平均”“好”和“優秀”),但ocean_proximity列顯然不是情況(例如,類別0和4顯然比類別0和1更相似)。要解決此問題,一種常見的解決方案是為每個類別創建一個二進制屬性:當類別為"<1H OCEAN"時,這個屬性等于1(否則為0),當類別為"INLAND"時,另一個屬性等于1 (否則為0),以此類推。這稱為獨熱編碼,因為只有一個屬性會等于1(熱),而其他屬性將為0(冷)。新屬性有時稱為虛擬屬性。Scikit-Learn提供了一個OneHotEncoder類來將類別值轉換為獨熱向量:

默認情況下,OneHotEncoder的輸出是SciPy稀疏矩陣,而不是NumPy數組:

對于主要包含零的矩陣,稀疏矩陣是一種非常有效的表示。實際上,它在內部僅存儲非零值及其位置。當一個類別屬性有成百上千個類別時,獨熱編碼會產生一個非常大的矩陣,除了每行一個1之外其他元素全是0。在這種情況下,稀疏矩陣正是你所需要的:它將節省大量內存并加快計算速度。你可以像普通二維數組一樣使用稀疏矩陣[12],但如果你想將其轉換為(密集)NumPy數組,只需調用toarray()方法:

或者,你可以在創建OneHotEncoder時設置sparse=False,在這種情況下,transform()方法將直接返回常規的(密集)NumPy數組。
與OrdinalEncoder一樣,你可以使用編碼器的categories_實例變量獲取類別列表:

Pandas有一個名為get_dummies()的函數,它將每個分類特征轉換為獨熱表示,每個類別有一個二元特征:

它看起來既漂亮又簡單,那么為什么不使用它來代替OneHotEncoder呢?好吧,OneHotEncoder的優勢在于它會記住經過了哪些類別的訓練。這非常重要,因為一旦你的模型投入生產,它應該被提供與訓練期間完全相同的特征:不多也不少。看看經過訓練的cat_encoder在我們轉換相同的df_test時會輸出什么[使用transform(),而不是fit_transform()]:

看到不同了嗎?get_dummies()只看到兩個類別,因此它輸出兩列,而OneHotEncoder以正確的順序為每個學習到的類別輸出一列。此外,如果你向get_dummies()提供一個包含未知類別(例如,"<2H OCEAN")的DataFrame,它會為其生成一列:

但OneHotEncoder更聰明:它會檢測未知類別并引發異常。如果你愿意,可以將handle_unknown超參數設置為“忽略”,在這種情況下,它將只用零表示未知類別:

如果分類屬性有大量可能的類別(例如,國家代碼、職業、物種),則獨熱編碼將導致大量輸入特征。這可能會減慢訓練速度并降低性能。如果發生這種情況,你可能希望用與類別相關的有用數值特征來替換分類輸入。例如,你可以將ocean_proximity特征替換為到海洋的距離(類似地,國家代碼可以替換為國家的人口數量和人均GDP)。你也可以使用GitHub(https://github.com/scikit-learn-contrib/category_encoders)上的category_encoders包提供的編碼器。或者,在處理神經網絡時,你可以用稱為嵌入的可學習的低維向量替換每個類別。這是表示的一個示例(更多細節參見第13章和第17章)。
當你使用DataFrame擬合任何Scikit-Learn估計器時,估計器會將列名稱存儲在feature_names_in_屬性中。然后,Scikit-Learn確保之后饋送到該估計器的任何DataFrame [例如,到transform()或predict()]具有相同的列名。Transformer還提供了一個get_feature_names_out()方法,你可以使用該方法圍繞Transformer的輸出構建DataFrame:

2.5.3 特征縮放和轉換
你需要應用于數據的最重要的轉換之一是特征縮放。除了少數例外,機器學習算法在輸入數值屬性具有非常不同的尺度時表現不佳。房屋數據就是這種情況:房間總數大約在6~39 320之間,而收入中位數僅在0~15之間。如果不進行任何縮放,大多數模型將偏向于忽略收入中位數并更多地關注于房間的數量。
有兩種常用的方法可以使所有屬性具有相同的尺度:最小-最大縮放和標準化。
與所有估計器一樣,重要的是僅把縮放器擬合到訓練數據:永遠不要對訓練集以外的任何其他對象使用fit()或fit_transform()。一旦你有了一個訓練好的縮放器,你就可以用它來transform()任何其他集合,包括驗證集、測試集和新數據。請注意,雖然訓練集值將始終縮放到指定范圍,如果新數據包含異常值,這些值可能最終會縮放到范圍之外。如果你想避免這種情況,只需將clip超參數設置為True。
最小-最大縮放(很多人稱之為歸一化)是最簡單的:對于每個屬性,值被移動和重新縮放,這樣它們最終值在0~1之間。這是通過減去最小值并除以最小值和最大值之間的差值來執行的。Scikit-Learn為此提供了一個名為MinMaxScaler的轉換器。它有一個feature_range超參數,如果出于某種原因你不想要0~1,則允許你更改范圍(例如,神經網絡在零均值輸入下效果最好,因此-1~1的范圍更可取)。它很容易使用:

標準化是不同的:首先它減去平均值(因此標準化值的均值為零),然后將結果除以標準差(因此標準化值的標準差等于1)。與最小-最大縮放不同,標準化不會將值限制在特定范圍內。但是,標準化受異常值的影響要小得多。例如,假設一個地區的收入中位數等于100(錯誤數據),而不是通常的0~15。最小-最大縮放到0~1范圍會將此異常值映射到1,并將所有其他值壓縮到0~0.15,而標準化不會受到太大影響。Scikit-Learn提供了一個名為StandardScaler的轉換器用于標準化:

如果你想縮放稀疏矩陣而不先將其轉換為密集矩陣,則可以使用StandardScaler,并將其with_mean超參數設置為False:它只會將數據除以標準差,而不減去均值(因為這會破壞稀疏性)。
當一個特征的分布有一個重尾(heavy tail)時(即當遠離平均值的值不是指數級稀有時),最小-最大縮放和標準化都會將大多數值壓縮到一個小范圍內。機器學習模型通常不喜歡這樣,正如你將在第4章中看到的那樣。因此,在縮放特征之前,你應該首先對其進行變換來縮小重尾,并盡可能使分布大致對稱。例如,對于右側有重尾的正特征,一種常見的方法是用它的平方根來替換特征(或將特征提升到0~1之間的冪)。如果該特征有一個很長很重的尾巴,例如冪律(power law)分布,那么用對數替換該特征可能會有幫助。例如,population特征大致遵循冪律:擁有10 000名居民的地區出現頻率僅比擁有1000名居民的地區低10倍,而不是呈指數級降低。圖2-17展示了當你計算它的對數時這個特征看起來有多好:它非常接近高斯分布(即鐘形)。

圖2-17:變換特征使其更接近高斯分布
處理重尾特征的另一種方法是對特征進行分桶(bucketizing)。這意味著將其分布分成大致相等大小的桶,并用它所屬的桶的索引替換每個特征值,就像我們創建income_cat特征時所做的一樣(盡管我們只將其用于分層采樣)。例如,你可以將每個值替換為其百分位數。使用大小相等的桶進行分桶會產生幾乎均勻分布的特征,因此無須進一步縮放,或者你可以僅除以桶的數量來強制值在0~1范圍內。
當一個特征具有多峰分布(即具有兩個或更多清晰的峰,稱為模式)時,例如housing_median_age特征,將其分桶也很有幫助,但這次將桶的ID視為類別,而不是數值。這意味著必須對桶索引進行編碼,例如使用OneHotEncoder(所以你通常不想使用太多的桶)。這種方法將使回歸模型更容易學習針對該特征值的不同范圍的不同規則。例如,大約35年前建造的房屋可能具有一種過時的奇特風格,因此它們的價格比單憑其年齡所表明的要便宜。
另一種轉換多峰分布的方法是為每個模式(至少是主要模式)添加一個特征,表示房屋年齡中位數與該特定模式之間的相似性。相似性度量通常使用徑向基函數(Radial Basis Function,RBF)計算——任何一個僅取決于輸入值和固定點之間距離的函數。最常用的RBF是高斯RBF,其輸出值隨著輸入值遠離固定點而呈指數衰減。例如,房屋年齡x和35之間的高斯RBF相似性由方程exp(-γ(x-35)2)給出。超參數γ(gamma)決定了當x遠離35時相似性度量衰減的速度。使用Scikit-Learn的rbf_kernel()函數,可以創建一個新的高斯RBF特征來測量房屋年齡中位數與35之間的相似性:

圖2-18展示了這個作為房屋年齡中位數(實線)的函數的新特征。它還展示了如果你使用較小的gamma值,該函數會是什么樣子。如圖2-18所示,新的年齡相似性特征在35處達到峰值,正好在房屋年齡中位數分布的峰值附近:如果這個特定年齡組與較低的價格密切相關,那么這個新特征很有可能會有所幫助。

圖2-18:高斯RBF特征測量房屋年齡中位數與35之間的相似性
到目前為止,我們只查看了輸入特征,但目標值可能還需要轉換。例如,如果目標分布有一條重尾,你可以選擇用其對數替換目標。但如果這樣做,現在回歸模型將預測房價中位數的對數,而不是房價中位數本身。如果你想要已預測的房屋中位數,則需要計算模型預測的指數。
幸運的是,大多數Scikit-Learn的轉換器都有一個inverse_transform()方法,這使得計算它們的逆轉換變得容易。例如,下面的代碼展示了如何使用StandardScaler來縮放標簽(就像我們對輸入所做的那樣),然后在生成的縮放標簽上訓練一個簡單的線性回歸模型,并使用它對一些新數據進行預測,我們使用經過訓練的縮放器的inverse_transform()方法轉換回原始尺度。請注意,我們將標簽從Pandas Series轉換為DataFrame,因為StandardScaler需要2D輸入。此外,在此示例中,為簡單起見,我們僅在單個原始輸入特征(收入中位數)上訓練模型:

這很好用,但更簡單的選擇是使用TransformedTargetRegressor。我們只需要構造它,給它回歸模型和標簽轉換器,然后使用原始的未縮放標簽,將它擬合到訓練集上。它將自動使用轉換器來縮放標簽并在縮放后的標簽上訓練回歸模型,就像我們之前所做的那樣。然后,當我們想要進行預測時,它會調用回歸模型的predict()方法并使用縮放器的inverse_transform()方法來產生預測:

2.5.4 定制轉換器
盡管Scikit-Learn提供了許多有用的轉換器,但你需要編寫自己的轉換器來執行自定義轉換、清洗操作或組合一些特定的屬性等任務。
對于不需要任何訓練的轉換,你只需編寫一個函數,將NumPy數組作為輸入并輸出轉換后的數組。例如,如上一節所述,通過將重尾分布的特征替換為它們的對數(假設特征為正且尾部在右側)來轉換具有重尾分布的特征通常是個好主意。讓我們創建一個對數轉換器并將其應用于population特征:

inverse_func參數是可選的。它允許你指定一個逆變換函數,例如,如果你計劃在TransformedTargetRegressor中使用你的轉換器。
你的轉換函數可以將超參數作為附加參數。例如,下面是如何創建一個轉換器來計算與之前相同的高斯RBF相似性度量:

請注意,RBF內核沒有反函數,因為在距離固定點的給定距離處總是有兩個值(距離0除外)。另請注意,rbf_kernel()不會單獨處理這些特征。如果你向它傳遞一個具有兩個特征的數組,它會測量2D距離(歐幾里得)來測量相似性。例如,以下是如何添加一個特征來測量每個地區與舊金山之間的地理相似性:

自定義轉換器也可用于組合特征。例如,這里有一個計算輸入特征0和1之間比率的FunctionTransformer:

FunctionTransformer非常方便,但是如果你希望你的轉換器是可訓練的,可以在fit()方法中學習一些參數并稍后在transform()方法中使用它們,你該怎么辦?為此,你需要編寫一個自定義類。Scikit-Learn依賴鴨子類型,因此此類不必繼承自任何特定的基類。它只需要三個方法:fit()(必須返回self)、transform()和fit_transform()。
你只需將TransformerMixin添加為基類即可以得到fit_transform():默認實現將只調用fit(),然后調用transform()。如果將BaseEstimator添加為基類(避免在構造函數中使用*args和**kwargs),你還將獲得兩個額外的方法:get_params()和set_params()。這些對于自動超參數調整很有用。
例如,這里有一個與StandardScaler非常相似的自定義轉換器:

這里有幾點需要注意:
· sklearn.utils.validation包包含幾個我們可以用來驗證輸入的函數。為簡單起見,我們將在本書的其余部分跳過此類測試,但生產環境代碼應該有它們。
· Scikit-Learn流水線要求fit()方法有兩個參數X和y,這就是為什么我們需要y=None參數,即使我們不使用y。
· 所有Scikit-Learn估計器都在fit()方法中設置n_features_in_,它們確保傳遞給transform()或predict()的數據具有這個數量的特征。
· fit()方法必須返回self。
· 此實現并沒有100%完成:所有估計器在傳遞DataFrame時都應在fit()方法中設置feature_names_in_。此外,所有的轉換器都應該提供一個get_feature_names_out()方法,以及一個inverse_transform()方法,當它們的轉換可以被逆轉時。有關詳細信息,請參閱本章末尾的最后一個練習。
自定義轉換器可以(并且經常)在其實現中使用其他估計器。例如,以下代碼演示了使用KMeans的自定義轉換器在fit()方法中識別出訓練數據中的主要集群,然后在transform()方法中使用rbf_kernel()來測量每個樣本與每個集群中心的相似程度:

你可以通過將實例傳遞給sklearn.utils.estimator_checks包中的check_estimator()來檢查你的自定義估算器是否遵循Scikit-Learn的API。有關完整的API,請查看https://scikit-learn.org/stable/developers。
正如你將在第9章中看到的,k均值是一種聚類算法,用于在數據中定位集群。它的搜索數量是由n_clusters超參數控制的。訓練后,集群中心可通過cluster_centers_屬性獲得。KMeans的fit()方法支持可選參數sample_weight,它允許用戶指定樣本的相對權重。k均值是一種隨機算法,意味著它依賴于隨機性來定位集群,所以如果你想要可重現的結果,你必須設置random_state參數。如你所見,盡管任務很復雜,但代碼相當簡單。現在讓我們使用這個自定義轉換器:

此代碼創建一個ClusterSimilarity轉換器,將集群數設置為10。然后用訓練集中每個地區的經緯度調用fit_transform(),用每個地區的房價中位數加權。Transformer使用k均值來定位集群,然后測量每個地區與所有10個集群中心之間的高斯RBF相似性。結果是一個矩陣,每個地區一行,每個集群一列。讓我們看一下前三行,四舍五入到小數點后兩位:

圖2-19展示了k均值找到的10個集群中心。這些地區根據其與其最近的集群中心的地理相似性進行著色。如你所見,大多數集群位于人口稠密和昂貴的地區。

圖2-19:高斯RBF與最近的集群中心的相似性
2.5.5 轉換流水線
如你所見,我們需要以正確的順序執行許多數據轉換步驟。幸運的是,Scikit-Learn提供了Pipeline類來幫助處理此類轉換序列。以下是一個用于數值屬性的小流水線,它首先估算然后縮放輸入特征:

Pipeline構造函數采用定義一系列步驟的名稱/估計器對(二元組)列表。名稱可以是你喜歡的任何名稱,只要它們是唯一的并且不包含雙下劃線(__)。稍后,當我們討論超參數調整時,它們會很有用。估計器必須都是轉換器[即它們必須有一個fit_transform()方法],除了最后一個,它可以是任何東西:轉換器、預測器或任何其他類型的估計器。
在Jupyter notebook中,如果你導入sklearn并運行sklearn .set_config(display="diagram"),所有Scikit-Learn估計器都將呈現為交互式圖表。這對于可視化流水線特別有用。要可視化num_pipeline,運行一個以num_pipeline作為最后一行的單元格。單擊估計器會顯示更多細節。
如果不想給轉換器命名,則可以使用make_pipeline()函數來代替。它以轉換器作為位置參數,并使用轉換器類的名稱創建流水線,小寫且不帶下劃線(例如"simpleimputer"):

如果多個轉換器具有相同的名稱,則在它們的名稱后附加一個索引(例如,"foo-1""foo-2"等)。
當你調用流水線的fit()方法時,它會在所有轉換器上依次調用fit_transform(),將每次調用的輸出作為參數傳遞給下一次調用,直到它到達最終的估計器,它只調用fit()方法。
流水線公開了與最終估計器相同的方法。在這個示例中,最后一個估計器是一個StandardScaler,它是一個轉換器,所以流水線也像一個轉換器。如果你調用流水線的transform()方法,它將順序地將所有轉換應用于數據。如果最后一個估計器是預測器而不是轉換器,那么流水線有一個predict()方法而不是transform()方法。調用它會按順序將所有轉換應用于數據并將結果傳遞給預測器的predict()方法。
讓我們調用流水線的fit_transform()方法并查看輸出的前兩行,四舍五入到小數點后兩位:

正如你之前看到的,如果你想恢復一個好的DataFrame,則你可以使用流水線的get_feature_names_out()方法:

流水線還支持索引;例如,pipeline[1]返回流水線中的第二個估計器,而pipeline[:-1]返回一個Pipeline對象,其中包含除最后一個估計器之外的所有估計器。你還可以通過steps屬性(名稱/估計器對列表)或通過named_steps字典屬性(將名稱映射到估計器)訪問估計器。例如,num_pipeline["simpleimputer"]返回名為"simpleimputer"的估計器。
到目前為止,我們已經分別處理了類別列和數值列。擁有一個能夠處理所有列的轉換器,對每一列應用適當的轉換會更方便。為此,你可以使用ColumnTransformer。例如,以下的ColumnTransformer會將num_pipeline(我們剛剛定義的)應用于數值屬性,將cat_pipeline應用于分類屬性:

首先我們導入ColumnTransformer類,然后我們定義數字和類別列名稱的列表,并為分類屬性構建一個簡單的流水線。最后,我們構造一個ColumnTransformer。它的構造函數需要一個三元組的列表,每個包含一個名稱(必須是唯一的并且不包含雙下劃線)、一個轉換器,以及一個轉換器應該應用到的列的名稱(或索引)列表。
如果你希望刪除列,則可以指定字符串"drop"而不是使用轉換器,或者如果你希望列保持不變,則可以指定"passthrough"。默認情況下,剩余的列(即未列出的列)將被刪除,但如果你希望以不同方式處理這些列,則可以將remainder超參數設置為任何轉換器(或"passthrough")。
由于列出所有列名不是很方便,Scikit-Learn提供了一個make_column_selector()函數,它返回一個選擇器函數,你可以使用它來自動選擇給定類型的所有特征,例如數值或類別。你可以將此選擇器函數傳遞給ColumnTransformer,而不是列名或索引。此外,如果你不關心轉換器的命名,你可以使用make_column_transformer(),它會為你選擇名稱,就像make_pipeline()所做的那樣。例如,以下代碼創建與之前相同的ColumnTransformer,只是轉換器被自動命名為"pipeline-1"和"pipeline-2",而不是"num"和"cat":

現在我們已準備好將此ColumnTransformer應用于房屋數據:

很好!我們有一個預處理流水線,它獲取整個訓練數據集并將每個轉換器應用于適當的列,然后水平連接轉換后的列(轉換器絕不能更改行數)。這再一次返回一個NumPy數組,但你可以使用preprocessing.get_feature_names_out()來獲取列名,并將數據包裝在一個漂亮的DataFrame中,就像我們之前所做的那樣。
OneHotEncoder返回一個稀疏矩陣,而num_pipeline返回一個密集矩陣。當稀疏矩陣和密集矩陣混合存在時,ColumnTransformer會估計最終矩陣的密度(即非零單元的比率),如果密度低于給定閾值(默認情況下,sparse_threshold=0.3)。在此示例中,它返回一個密集矩陣。
你的項目進展很順利,你幾乎可以訓練一些模型了!你現在想要創建一個單一的流水線來執行到目前為止實驗過的所有轉換。讓我們回顧一下流水線將做什么以及為什么做:
· 數字特征中的缺失值將通過用中位數替換它們來估算,因為大多數ML算法不期望缺失值。在分類特征中,缺失值將被最常見的類別替換。
· 類別特征將被獨熱編碼,因為大多數ML算法只接受數字輸入。
· 計算并添加一些比率特征:bedrooms_ratio、rooms_per_house和people_per_house。希望這些能更好地與房價中位數相關聯,從而幫助ML模型。
· 還添加了一些集群相似性特征。這些可能比緯度和經度對模型更有用。
· 長尾特征被它們的對數取代,因為大多數模型更喜歡具有大致均勻分布或高斯分布的特征。
· 所有數值特征都將被標準化,因為大多數ML算法更喜歡所有特征具有大致相同的尺度。
構建流水線來執行所有這些操作的代碼現在看起來應該很熟悉:

如果你運行此ColumnTransformer,它會執行所有轉換并輸出具有24個特征的NumPy數組:
