傳統編程的關注點是代碼。在機器學習項目中,關注點變成了特徵表示。也就是說,開發者通過添加和改善特徵來調整模型。「Garbage in, garbage out」。對於一個機器學習問題,數據和特徵往往決定了結果的上限,而模型、算法的選擇及優化則是在逐步接近這個上限。特徵工程,顧名思義,是指從原始數據創建特徵的過程。
將原始數據映射到特徵許多機器學習模型都必須將特徵表示為實數向量,因為特徵值必須與模型權重相乘。
圖 1. 特徵工程將原始數據映射到機器學習特徵圖 1 左側表示來自輸入數據源的原始數據,右側表示特徵矢量,也就是組成數據集中樣本的浮點值集。特徵工程指的是將原始數據轉換為特徵矢量。進行特徵工程預計需要大量時間。
映射數值整數和浮點數據不需要特殊編碼,因為它們可以與數字權重相乘。如圖 2 所示,將原始整數值 6 轉換為特徵值 6.0 並沒有多大的意義:
圖 2. 將整數值映射到浮點值映射分類值分類特徵具有一組離散的可能值。例如,可能有一個名為 street_name 的特徵,其中的選項包括:
{'Charleston Road', 'North Shoreline Boulevard', 'Shorebird Way','Rengstorff Avenue'}
由於模型不能將字符串與學習到的權重相乘,因此我們使用特徵工程將字符串轉換為數字值。
要實現這一點,我們可以定義一個從特徵值(我們將其稱為可能值的詞彙表)到整數的映射。世界上的每條街道並非都會出現在我們的數據集中,因此我們可以將所有其他街道分組為一個全部包羅的「其他」類別,稱為 OOV(out-of-vocabulary)分桶。
通過這種方法,我們可以按照以下方式將街道名稱映射到數字:
將 North Shoreline Boulevard 映射到 1將 Rengstorff Avenue 映射到 3不過,如果我們將這些索引數字直接納入到模型中,將會造成一些可能存在問題的限制:
我們將學習適用於所有街道的單一權重。例如,如果我們學習到 street_name 的權重為 6,那麼對於 Charleston Road,我們會將其乘以 0,對於 North Shoreline Boulevard 則乘以 1,對於 Shorebird Way 則乘以 2,依此類推。以某個使用 street_name 作為特徵來預測房價的模型為例。根據街道名稱對房價進行線性調整的可能性不大,此外,這會假設你已根據平均房價對街道排序。我們的模型需要靈活地為每條街道學習不同的權重,這些權重將添加到利用其他特徵估算的房價中。
我們沒有將 street_name 可能有多個值的情況考慮在內。例如,許多房屋位於兩條街道的拐角處,因此如果模型包含單個索引,則無法在 street_name 值中對該信息進行編碼。
要去除這兩個限制,我們可以為模型中的每個分類特徵創建一個二元向量來表示這些值,如下所述:
該向量的長度等於詞彙表中的元素數。當只有一個值為 1 時,這種表示法稱為獨熱編碼;當有多個值為 1 時,這種表示法稱為多熱編碼。
圖 3 所示為街道 Shorebird Way 的獨熱編碼。在此二元矢量中,代表 Shorebird Way 的元素的值為 1,而代表所有其他街道的元素的值為 0。
圖 3. 通過獨熱編碼映射街道地址該方法能夠有效地為每個特徵值(例如,街道名稱)創建布爾變量。採用這種方法時,如果房屋位於 Shorebird Way 街道上,則只有 Shorebird Way 的二元值為 1。因此,該模型僅使用 Shorebird Way 的權重。同樣,如果房屋位於兩條街道的拐角處,則將兩個二元值設為 1,並且模型將使用它們各自的權重。
稀疏表示法假設數據集中有 100 萬個不同的街道名稱,你希望將其包含為 street_name 的值。如果直接創建一個包含 100 萬個元素的二元向量,其中只有 1 或 2 個元素為 ture,則是一種非常低效的表示法,在處理這些向量時會佔用大量的存儲空間並耗費很長的計算時間。在這種情況下,一種常用的方法是使用稀疏表示法,其中僅存儲非零值。在稀疏表示法中,仍然為每個特徵值學習獨立的模型權重,如上所述。
良好特徵的特點避免很少使用的離散特徵值良好的特徵值應該在數據集中出現大約 5 次以上。這樣一來,模型就可以學習該特徵值與標籤是如何關聯的。也就是說,大量離散值相同的樣本可讓模型有機會了解不同設置中的特徵,從而判斷何時可以對標籤很好地做出預測。
例如:house_type 特徵可能包含大量樣本,其中它的值為 victorian:house_type: victorian;相反,如果某個特徵的值僅出現一次或者很少出現,則模型就無法根據該特徵進行預測。例如,unique_house_id 就不適合作為特徵,因為每個值只使用一次,模型無法從中學習任何規律:
unique_house_id: 8SK982ZZ1242Z
最好具有清晰明確的含義每個特徵對於項目中的任何人來說都應該具有清晰明確的含義。例如,下面的房齡適合作為特徵,可立即識別是以年為單位的房齡:
house_age: 27
相反,對於下方特徵值的含義,除了創建它的工程師,其他人恐怕辨識不出:
house_age: 851472000
在某些情況下,混亂的數據(而不是糟糕的工程選擇)會導致含義不清晰的值。例如,以下 user_age 的來源沒有檢查值恰當與否:
user_age: 277
實際數據內不要摻入特殊值良好的浮點特徵不包含超出範圍的異常斷點或特殊的值。例如,假設一個特徵具有 0 到 1 之間的浮點值。那麼,如下值是可以接受的:
quality_rating: 0.82
quality_rating: 0.37
不過,如果用戶沒有輸入 quality_rating,則數據集可能使用如下特殊值來表示不存在該值:
quality_rating: -1
為解決特殊值的問題,需將該特徵轉換為兩個特徵:
一個特徵存儲布爾值,表示是否提供了 quality_rating。為該布爾值特徵指定一個名稱,例如 is_quality_rating_defined。考慮上遊不穩定性特徵的定義不應隨時間發生變化。例如,下列值是有用的,因為城市名稱一般不會改變。(注意,我們仍然需要將「br/sao_paulo」這樣的字符串轉換為獨熱矢量。)
city_id: "br/sao_paulo"
但收集由其他模型推理的值會產生額外成本。可能值「219」目前代表聖保羅,但這種表示在未來運行其他模型時可能輕易發生變化:
inferred_city_cluster: "219"
表示 (Representation):清理數據蘋果樹結出的果子有品相上乘的,也有蟲蛀壞果。而高端便利店出售的蘋果是 100% 完美的水果。從果園到水果店之間,專門有人花費大量時間將壞蘋果剔除或給可以挽救的蘋果塗上一層薄薄的蠟。作為一名機器學習工程師,你將花費大量的時間挑出壞樣本並加工可以挽救的樣本。即使是非常少量的「壞蘋果」也會破壞掉一個大規模數據集。
縮放特徵值縮放是指將浮點特徵值從自然範圍(例如 100 到 900)轉換為標準範圍(例如 0 到 1 或 -1 到 +1)。如果某個特徵集只包含一個特徵,則縮放可以提供的實際好處微乎其微或根本沒有。不過,如果特徵集包含多個特徵,則縮放特徵可以帶來以下優勢:
幫助避免「NaN 陷阱」。在這種陷阱中,模型中的一個數值變成 NaN(例如,當某個值在訓練期間超出浮點精確率限制時),並且模型中的所有其他數值最終也會因數學運算而變成 NaN。幫助模型為每個特徵確定合適的權重。如果沒有進行特徵縮放,則模型會對範圍較大的特徵投入過多精力。你不需要對每個浮點特徵進行完全相同的縮放。即使特徵 A 的範圍是 -1 到 +1,同時特徵 B 的範圍是 -3 到 +3,也不會產生什麼惡劣的影響。不過,如果特徵 B 的範圍是 5000 到 100000,你的模型會出現糟糕的響應。
要縮放數值數據,一種顯而易見的方法是將 [最小值,最大值] 以線性方式映射到較小的範圍,例如 [-1,+1]。另一種熱門的縮放策略是計算每個值的 Z 得分。Z 得分與距離均值的標準偏差相關。換言之:
scaledvalue "=("value"-"mean")/"stddev.
例如,給定以下條件:
則:
scaled_value = (130 - 100) / 20
scaled_value = 1.5
使用 Z 得分進行縮放意味著,大多數縮放後的值將介於 -3 和 +3 之間,而少量值將略高於或低於該範圍。
處理極端離群值下面的曲線圖表示的是加利福尼亞州住房數據集中稱為 roomsPerPerson 的特徵。roomsPerPerson 值的計算方法是相應地區的房間總數除以相應地區的人口總數。該曲線圖顯示,在加利福尼亞州的絕大部分地區,人均房間數為 1 到 2 間。不過,請看一下 x 軸。
圖 4. 一個非常非常長的尾巴圖 5. 對數縮放仍然留有尾巴對數縮放可稍稍緩解這種影響,但仍然存在離群值這個大尾巴。我們來採用另一種方法。如果我們只是簡單地將 roomsPerPerson 的最大值「限制」為某個任意值(比如 4.0),會發生什麼情況呢?
圖 6. 將特徵值限制到 4.0將特徵值限制到 4.0 並不意味著我們會忽略所有大於 4.0 的值。而是說,所有大於 4.0 的值都將變成 4.0。這就解釋了 4.0 處的那個有趣的小峰值。儘管存在這個小峰值,但是縮放後的特徵集現在依然比原始數據有用。
分箱下面的曲線圖顯示了加利福尼亞州不同緯度的房屋相對普及率。注意集群 - 洛杉磯大致在緯度 34 處,舊金山大致在緯度 38 處。
圖 7. 每個緯度的房屋數在數據集中,latitude 是一個浮點值。不過,在我們的模型中將 latitude 表示為浮點特徵沒有意義。這是因為緯度和房屋價值之間不存在線性關係。例如,緯度 35 處的房屋並不比緯度 34 處的房屋貴 35/34(或更便宜)。但是,緯度或許能很好地預測房屋價值。為了將緯度變為一項實用的預測指標,我們對緯度「分箱」,如下圖所示:
圖 8. 分箱值我們現在擁有 11 個不同的布爾值特徵(LatitudeBin1、LatitudeBin2、…、LatitudeBin11),而不是一個浮點特徵。擁有 11 個不同的特徵有點不方便,因此我們將它們統一成一個 11 元素矢量。這樣做之後,我們可以將緯度 37.4 表示為:[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
分箱之後,我們的模型現在可以為每個緯度學習完全不同的權重。
為了簡單起見,我們在緯度樣本中使用整數作為分箱邊界。如果我們需要更精細的解決方案,我們可以每隔 1/10 個緯度拆分一次分箱邊界。添加更多箱可讓模型從緯度 37.4 處學習和維度 37.5 處不一樣的行為,但前提是每 1/10 個緯度均有充足的樣本可供學習。
另一種方法是按分位數分箱,這種方法可以確保每個桶內的樣本數量是相等的。按分位數分箱完全無需擔心離群值。
清查截至目前,我們假定用於訓練和測試的所有數據都是值得信賴的。在現實生活中,數據集中的很多樣本是不可靠的,原因有以下一種或多種:
重複樣本。例如,伺服器錯誤地將同一條記錄上傳了兩次。不良標籤。例如,有人錯誤地將一顆橡樹的圖片標記為楓樹。不良特徵值。例如,有人輸入了多餘的位數,或者溫度計被遺落在太陽底下。一旦檢測到存在這些問題,你通常需要將相應樣本從數據集中移除,從而「修正」不良樣本。要檢測缺失值或重複樣本,你可以編寫一個簡單的程序。檢測不良特徵值或標籤可能會比較棘手。
除了檢測各個不良樣本之外,你還必須檢測集合中的不良數據。直方圖是一種用於可視化集合中數據的很好機制。此外,收集如下統計信息也會有所幫助:
標準偏差考慮生成離散特徵的最常見值列表。例如,country:uk 的樣本數是否符合你的預期?language:jp 是否真的應該作為你數據集中的最常用語言?了解數據遵循以下規則:
確認數據是否滿足這些預期(或者你可以解釋為何數據不滿足預期)。仔細檢查訓練數據是否與其他來源(例如信息中心)的數據一致。像處理任何任務關鍵型代碼一樣謹慎處理你的數據。良好的機器學習依賴於良好的數據。
特徵組合:對非線性規律進行編碼在圖 9 和圖 10 中,我們做出如下假設:
圖 9. 這是線性問題嗎?你可以畫一條線將生病的樹與健康的樹清晰地分開嗎?當然可以。這是個線性問題。這條線並不完美。有一兩棵生病的樹可能位於「健康」一側,但你畫的這條線可以很好地做出預測。
現在,我們來看看下圖:
圖 10. 這是線性問題嗎?你可以畫一條直線將生病的樹與健康的樹清晰地分開嗎?不,你做不到。這是個非線性問題。你畫的任何一條線都不能很好地預測樹的健康狀況。
圖 11. 一條線無法分開兩類數據要想解決圖 10 所示的非線性問題,可以創建一個特徵組合。特徵組合是指通過將兩個或多個輸入特徵相乘來對特徵空間中的非線性規律進行編碼的合成特徵。「cross」(組合)這一術語來自 cross product(向量積)。我們通過將x1與 x2組合來創建一個名為 x3的特徵組合:
我們像處理任何其他特徵一樣來處理這個新建的x3特徵組合。線性公式變為:
線性算法可以算出 w3的權重,就像算出 w1 和 w2 的權重一樣。換言之,雖然 w3 表示非線性信息,但你不需要改變線性模型的訓練方式來確定 w3的值。
特徵組合的種類我們可以創建很多不同種類的特徵組合。例如:
[A X B]:將兩個特徵的值相乘形成的特徵組合。[A x B x C x D x E]:將五個特徵的值相乘形成的特徵組合。[A x A]:對單個特徵的值求平方形成的特徵組合。通過採用隨機梯度下降法,可以有效地訓練線性模型。因此,在使用擴展的線性模型時輔以特徵組合一直都是訓練大規模數據集的有效方法。
特徵組合:組合獨熱矢量到目前為止,我們已經重點介紹了如何對兩個單獨的浮點特徵進行特徵組合。在實踐中,機器學習模型很少會組合連續特徵。不過,機器學習模型卻經常組合獨熱特徵矢量,將獨熱特徵矢量的特徵組合視為邏輯連接。例如,假設我們具有以下兩個特徵:國家/地區和語言。對每個特徵進行獨熱編碼會生成具有二元特徵的矢量,這些二元特徵可解讀為 country=USA, country=France 或 language=English, language=Spanish。然後,如果你對這些獨熱編碼進行特徵組合,則會得到可解讀為邏輯連接的二元特徵,如下所示:
country:usa AND language:spanish
再舉一個例子,假設你對緯度和經度進行分箱,獲得單獨的 5 元素特徵矢量。例如,指定的緯度和經度可以表示如下:
binned_latitude = [0, 0, 0, 1, 0]binned_longitude = [0, 1, 0, 0, 0]假設你對這兩個特徵矢量創建了特徵組合:
binned_latitude X binned_longitude此特徵組合是一個 25 元素獨熱矢量(24 個 0 和 1 個 1)。該組合中的單個 1 表示緯度與經度的特定連接。然後,你的模型就可以了解到有關這種連接的特定關聯性。
假設我們更粗略地對緯度和經度進行分箱,如下所示:
binned_latitude(lat) = [
0 < lat <= 10
10 < lat <= 20
20 < lat <= 30
]
binned_longitude(lon) = [
0 < lon <= 15
15 < lon <= 30
]
針對這些粗略分箱創建特徵組合會生成具有以下含義的合成特徵:
binned_latitude_X_longitude(lat, lon) = [
0 < lat <= 10 AND 0 < lon <= 15
0 < lat <= 10 AND 15 < lon <= 30
10 < lat <= 20 AND 0 < lon <= 15
10 < lat <= 20 AND 15 < lon <= 30
20 < lat <= 30 AND 0 < lon <= 15
20 < lat <= 30 AND 15 < lon <= 30
]現在,假設我們的模型需要根據以下兩個特徵來預測狗主人對狗狗的滿意程度:
行為類型behavior type(吠叫、啜泣、依偎等)如果我們根據這兩個特徵構建以下特徵組合:
[behavior type X time of day]
我們最終獲得的預測能力將遠遠超過任一特徵單獨的預測能力。例如,如果狗狗在下午 5 點主人下班回來時(快樂地)叫喊,可能表示對主人滿意度的正面預測結果。如果狗狗在凌晨 3 點主人熟睡時(也許痛苦地)哀叫,可能表示對主人滿意度的強烈負面預測結果。
線性學習器可以很好地擴展到大量數據。對大規模數據集使用特徵組合是學習高度複雜模型的一種有效策略。神經網絡可提供另一種策略。