如何用深度學習處理結構化資料?機器之心Pro2017-11-28 15:05:11

這篇部落格主要關注的是深度學習領域一個並不非常廣為人知的應用領域:結構化資料。本文作者為舊金山大學(USF)在讀研究生 Kerem Turgutlu。

使用深度學習方法按照本文所介紹的步驟處理結構化資料有這樣的好處:

無需領域知識

表現優良

在機器學習/深度學習或任何型別的預測建模任務中,都是先有資料然後再做演算法/方法。這也是某些機器學習方法在解決某些特定任務之前需要做大量特徵工程的主要原因,這些特定任務包括影象分類、NLP 和許多其它「非常規的」資料的處理——這些資料不能直接送入 logistic 迴歸模型或隨機森林模型進行處理。相反,深度學習無需任何繁雜和耗時的特徵工程也能在這些型別的任務取得良好的表現。大多數時候,這些特徵需要領域知識、創造力和大量的試錯。當然,領域專業知識和精巧的特徵工程仍然非常有價值,但這篇文章將提及的技術足以讓你在沒有任何領域知識的前提下向 Kaggle 競賽的前三名看齊,參閱:http://blog。kaggle。com/2016/01/22/rossmann-store-sales-winners-interview-3rd-place-cheng-gui/

如何用深度學習處理結構化資料?

圖 1:一隻萌狗和一隻怒貓(我標註的)

由於特徵生成(比如 CNN 的卷積層)的本質和能力很複雜,所以深度學習在各種各樣的影象、文字和音訊資料問題上得到了廣泛的應用。這些問題無疑對人工智慧的發展非常重要,而且這一領域的頂級研究者每年都在分類貓、狗和船等任務上你追我趕,每年的成績也都優於前一年。但在實際行業應用方面我們卻很少看到這種情況。這是為什麼呢?公司企業的資料庫涉及到結構化資料,這些才是塑造了我們的日常生活的領域。

首先,讓我們先定義一下結構化資料。在結構化資料中,你可以將行看作是收集到的資料點或觀察,將列看作是表示每個觀察的單個屬性的欄位。比如說,來自線上零售商店的資料有表示客戶交易事件的列和包含所買商品、數量、價格、時間戳等資訊的列。

下面我們給出了一些賣家資料,行表示每個獨立的銷售事件,列中給出了這些銷售事件的資訊。

如何用深度學習處理結構化資料?

圖 2:結構化資料的 pandas dataframe 示例

接下來我們談談如何將神經網路用於結構化資料任務。實際上,在理論層面上,建立帶有任何所需架構的全連線網路都很簡單,然後使用「列」作為輸入即可。在損失函式經歷過一些點積和反向傳播之後,我們將得到一個訓練好的網路,然後就可以進行預測了。

儘管看起來非常簡單直接,但在處理結構化資料時,人們往往更偏愛基於樹的方法,而不是神經網路。原因為何?這可以從演算法的角度理解——演算法究竟是如何對待和處理我們的資料的。

人們對結構化資料和非結構化資料的處理方式是不同的。非結構化資料雖然是「非常規的」,但我們通常處理的是單位量的單個實體,比如畫素、體素、音訊頻率、雷達反向散射、感測器測量結果等等。而對於結構化資料,我們往往需要處理多種不同的資料型別;這些資料型別分為兩大類:數值資料和類別資料。類別資料需要在訓練之前進行預處理,因為包含神經網路在內的大多數演算法都還不能直接處理它們。

編碼變數有很多可選的方法,比如標籤/數值編碼和 one-hot 編碼。但在記憶體方面和類別層次的真實表示方面,這些技術還存在問題。記憶體方面的問題可能更為顯著,我們透過一個例子來說明一下。

假設我們列中的資訊是一個星期中的某一天。如果我們使用 one-hot 或任意標籤編碼這個變數,那麼我們就要假設各個層次之間都分別有相等和任意的距離/差別。

如何用深度學習處理結構化資料?

圖 3:one-hot 編碼和標籤編碼

但這兩種方法都假設每兩天之間的差別是相等的,但我們很明顯知道實際上並不是這樣,我們的演算法也應該知道這一點!

「神經網路的連續性本質限制了它們在類別變數上的應用。因此,用整型數表示類別變數然後就直接應用神經網路,不能得到好的結果。」[1]

基於樹的演算法不需要假設類別變數是連續的,因為它們可以按需要進行分支來找到各個狀態,但神經網路不是這樣的。實體嵌入(entity embedding)可以幫助解決這個問題。實體嵌入可用於將離散值對映到多維空間中,其中具有相似函式輸出的值彼此靠得更近。比如說,如果你要為一個銷售問題將各個省份嵌入到國家這個空間中,那麼相似省份的銷售就會在這個投射的空間相距更近。

因為我們不想在我們的類別變數的層次上做任何假設,所以我們將在歐幾里得空間中學習到每個類別的更好表示。這個表示很簡單,就等於 one-hot 編碼與可學習的權重的點積。

嵌入在 NLP 領域有非常廣泛的應用,其中每個詞都可表示為一個向量。Glove 和 word2vec 是其中兩種著名的嵌入方法。我們可以從圖 4 看到嵌入的強大之處 [2]。只要這些向量符合你的目標,你隨時可以下載和使用它們;這實際上是一種表示它們所包含的資訊的好方法。

如何用深度學習處理結構化資料?

圖 4:來自 TensorFlow 教程的 word2vec

儘管嵌入可以在不同的語境中使用(不管是監督式方法還是無監督式方法),但我們的主要目標是瞭解如何為類別變數執行這種對映。

實體嵌入

儘管人們對「實體嵌入」有不同的說法,但它們與我們在詞嵌入上看到的用例並沒有太大的差異。畢竟,我們只關心我們的分組資料有更高維度的向量表示;這些資料可能是詞、每星期的天數、國家等等。這種從詞嵌入到元資料嵌入(在我們情況中是類別)的轉換使用讓 Yoshua Bengio 等人使用一種簡單的自動方法就贏得了 2015 年的一場 Kaggle 競賽——通常這樣做是無法贏得比賽的。參閱:https://www。kaggle。com/c/pkdd-15-predict-taxi-service-trajectory-i

「為了處理由客戶 ID、計程車 ID、日期和時間資訊組成的離散的元資料,我們使用該模型為這些資訊中的每種資訊聯合學習了嵌入。這種方法的靈感來自於自然語言建模方法 [2],其中每個詞都對映到了一個固定大小的向量空間(這種向量被稱為詞嵌入)。[3]

如何用深度學習處理結構化資料?

圖 5:使用 t-SNE 2D 投影得到的計程車元資料嵌入視覺化

我們將一步步探索如何在神經網路中學習這些特徵。定義一個全連線的神經網路,然後將數值變數和類別變數分開處理。

對於每個類別變數:

1。 初始化一個隨機的嵌入矩陣 mxD:

m:類別變數的不同層次(星期一、星期二……)的數量

D:用於表示的所需的維度,這是一個可以取值 1 到 m-1 的超引數(取 1 就是標籤編碼,取 m 就是 one-hot 編碼)

如何用深度學習處理結構化資料?

圖 6:嵌入矩陣

2。 然後,對於神經網路中的每一次前向透過,我們都在該嵌入矩陣中查詢一次給定的標籤(比如為「dow」查詢星期一),這會得到一個 1xD 的向量。

如何用深度學習處理結構化資料?

圖 7:查詢後的嵌入向量

3。 將這個 1×D 的向量附加到我們的輸入向量(數值向量)上。你可以把這個過程看作是矩陣增強,其中我們為每一個類別都增加一個嵌入向量,這是透過為每一特定行執行查詢而得到的。

如何用深度學習處理結構化資料?

圖 8:添加了嵌入向量後

4。 在執行反向傳播的同時,我們也以梯度的方式來更新這些嵌入向量,以最小化我們的損失函式。

輸入一般不會更新,但對嵌入矩陣而言有一種特殊情況,其中我們允許我們的梯度反向流回這些對映的特徵,從而最佳化它們。

我們可以將其看作是一個讓類別嵌入在每次迭代後都能進行更好的表示的過程。

注意:根據經驗,應該保留沒有非常高的基數的類別。因為如果一個變數的某個特定層次佔到了 90% 的觀察,那麼它就是一個沒有很好的預測價值的變數,我們可能最好還是避開它。

好訊息

透過在我們的嵌入向量中執行查詢並允許 requires_grad=True 並且學習它們,我們可以很好地在我們最喜歡的框架(最好是動態框架)中實現上面提到的架構。但 Fast。ai 已經實現了所有這些步驟並且還做了更多。除了使結構化的深度學習更簡單,這個庫還提供了很多當前最先進的功能,比如差異學習率、SGDR、週期性學習率、學習率查詢等等。這些都是我們可以利用的功能。你可以在以下部落格進一步瞭解這些主題:

https://medium。com/@bushaev/improving-the-way-we-work-with-learning-rate-5e99554f163b

https://medium。com/@surmenok/estimating-optimal-learning-rate-for-a-deep-neural-network-ce32f2556ce0

https://medium。com/@markkhoffmann/exploring-stochastic-gradient-descent-with-restarts-sgdr-fa206c38a74e

使用 Fast。ai 實現

在這一部分,我們將介紹如何實現上述步驟並構建一個能更有效處理結構化資料的神經網路。

為此我們要看看一個熱門的 Kaggle 競賽:https://www。kaggle。com/c/mercari-price-suggestion-challenge/。對於實體嵌入來說,這是一個非常合適的例子,因為其資料基本上都是類別資料,而且有相當高的基數(也不是過高),另外也沒有太多其它東西。

資料:

約 140 萬行

item_condition_id:商品的情況(基數:5)

category_name:類別名稱(基數:1287)

brand_name:品牌名稱(基數:4809)

shipping:價格中是否包含運費(基數:2)

重要說明:因為我已經找到了最好的模型引數,所以我不會在這個例子包含驗證集,但是你應該使用驗證集來調整超引數。

第 1 步:

將缺失值作為一個層次加上去,因為缺失本身也是一個重要資訊。

train。category_name = train。category_name。fillna(‘missing’)。astype(‘category’)

train。brand_name = train。brand_name。fillna(‘missing’)。astype(‘category’)

train。item_condition_id = train。item_condition_id。astype(‘category’)

test。category_name = test。category_name。fillna(‘missing’)。astype(‘category’)

test。brand_name = test。brand_name。fillna(‘missing’)。astype(‘category’)

test。item_condition_id = test。item_condition_id。astype(‘category’)

第 2 步:

預處理資料,對數值列進行等比例的縮放調整,因為神經網路喜歡歸一化的資料。如果你不縮放你的資料,網路就可能格外重點關注一個特徵,因為這不過都是點積和梯度。如果我們根據訓練統計對訓練資料和測試資料都進行縮放,效果會更好,但這應該影響不大。這就像是把每個畫素的值都除以 255,一樣的道理。

因為我們希望相同的層次有相同的編碼,所以我將訓練資料和測試資料結合了起來。

combined_x, combined_y, nas, _ = proc_df(combined, ‘price’, do_scale=True)

第 3 步:

建立模型資料物件。路徑是 Fast。ai 儲存模型和啟用的地方。

path = ‘。。/data/’

md = ColumnarModelData。from_data_frame(path, test_idx, combined_x, combined_y, cat_flds=cats, bs= 128

第 4 步:

確定 D(嵌入的維度),cat_sz 是每個類別列的元組 (col_name, cardinality+1) 的列表。

# We said that D (dimension of embedding) is an hyperparameter

# But here is Jeremy Howard‘s rule of thumb

emb_szs = [(c, min(50, (c+1)//2)) for _,c in cat_sz]

# [(6, 3), (1312, 50), (5291, 50), (3, 2)]

第 5 步:

建立一個 learner,這是 Fast。ai 庫的核心物件。

# params: embedding sizes, number of numerical cols, embedding dropout, output, layer sizes, layer dropouts

m = md。get_learner(emb_szs, len(combined_x。columns)-len(cats),

0。04, 1, [1000,500], [0。001,0。01], y_range=y_range)

第 6 步:

這部分在我前面提及的其它文章中有更加詳細的解釋。

要充分利用 Fast。ai 的優勢。

在損失開始增大之前的某個時候,我們要選擇我們的學習率……

# find best lr

m。lr_find()

# find best lr

m。sched。plot()

如何用深度學習處理結構化資料?

圖 9:學習率與損失圖

擬合

我們可以看到,僅僅過了 3 epoch,就得到:

we can see that with just 3 epochs we have

lr = 0。0001

m。fit(lr, 3, metrics=[lrmse])

如何用深度學習處理結構化資料?

更多擬合

m。fit(lr, 3, metrics=[lrmse], cycle_len=1)

如何用深度學習處理結構化資料?

還有更多……

m。fit(lr, 2, metrics=[lrmse], cycle_len=1)

如何用深度學習處理結構化資料?

所以,在短短几分鐘之內,無需進一步的其它操作,這些簡單卻有效的步驟就能讓你進入大約前 10% 的位置。如果你真的有更高的目標,我建議你使用 item_description 列並將其作為多個類別變數使用。然後把工作交給實體嵌入完成,當然不要忘記堆疊和組合。