如何用深度學(xué)習(xí)處理結(jié)構(gòu)化數(shù)據(jù)?
這篇文章主要關(guān)注的是深度學(xué)習(xí)領(lǐng)域一個(gè)并不非常廣為人知的應(yīng)用領(lǐng)域:結(jié)構(gòu)化數(shù)據(jù)。本文作者為舊金山大學(xué)(USF)在讀研究生 Kerem Turgutlu。
使用深度學(xué)習(xí)方法按照本文所介紹的步驟處理結(jié)構(gòu)化數(shù)據(jù)有這樣的好處:
- 快
- 無需領(lǐng)域知識(shí)
- 表現(xiàn)優(yōu)良
在機(jī)器學(xué)習(xí)/深度學(xué)習(xí)或任何類型的預(yù)測(cè)建模任務(wù)中,都是先有數(shù)據(jù)然后再做算法/方法。這也是某些機(jī)器學(xué)習(xí)方法在解決某些特定任務(wù)之前需要做大量特征工程的主要原因,這些特定任務(wù)包括圖像分類、NLP 和許多其它「非常規(guī)的」數(shù)據(jù)的處理——這些數(shù)據(jù)不能直接送入 logistic 回歸模型或隨機(jī)森林模型進(jìn)行處理。相反,深度學(xué)習(xí)無需任何繁雜和耗時(shí)的特征工程也能在這些類型的任務(wù)取得良好的表現(xiàn)。大多數(shù)時(shí)候,這些特征需要領(lǐng)域知識(shí)、創(chuàng)造力和大量的試錯(cuò)。當(dāng)然,領(lǐng)域?qū)I(yè)知識(shí)和精巧的特征工程仍然非常有價(jià)值,但這篇文章將提及的技術(shù)足以讓你在沒有任何領(lǐng)域知識(shí)的前提下向 Kaggle 競(jìng)賽的前三名看齊,參閱:http://blog.kaggle.com/2016/01/22/rossmann-store-sales-winners-interview-3rd-place-cheng-gui/
圖 1:一只萌狗和一只怒貓
由于特征生成(比如 CNN 的卷積層)的本質(zhì)和能力很復(fù)雜,所以深度學(xué)習(xí)在各種各樣的圖像、文本和音頻數(shù)據(jù)問題上得到了廣泛的應(yīng)用。這些問題無疑對(duì)人工智能的發(fā)展非常重要,而且這一領(lǐng)域的頂級(jí)研究者每年都在分類貓、狗和船等任務(wù)上你追我趕,每年的成績(jī)也都優(yōu)于前一年。但在實(shí)際行業(yè)應(yīng)用方面我們卻很少看到這種情況。這是為什么呢?公司企業(yè)的數(shù)據(jù)庫(kù)涉及到結(jié)構(gòu)化數(shù)據(jù),這些才是塑造了我們的日常生活的領(lǐng)域。
首先,讓我們先定義一下結(jié)構(gòu)化數(shù)據(jù)。在結(jié)構(gòu)化數(shù)據(jù)中,你可以將行看作是收集到的數(shù)據(jù)點(diǎn)或觀察,將列看作是表示每個(gè)觀察的單個(gè)屬性的字段。比如說,來自在線零售商店的數(shù)據(jù)有表示客戶交易事件的列和包含所買商品、數(shù)量、價(jià)格、時(shí)間戳等信息的列。
下面我們給出了一些賣家數(shù)據(jù),行表示每個(gè)獨(dú)立的銷售事件,列中給出了這些銷售事件的信息。
圖 2:結(jié)構(gòu)化數(shù)據(jù)的 pandas dataframe 示例
接下來我們談?wù)勅绾螌⑸窠?jīng)網(wǎng)絡(luò)用于結(jié)構(gòu)化數(shù)據(jù)任務(wù)。實(shí)際上,在理論層面上,創(chuàng)建帶有任何所需架構(gòu)的全連接網(wǎng)絡(luò)都很簡(jiǎn)單,然后使用「列」作為輸入即可。在損失函數(shù)經(jīng)歷過一些點(diǎn)積和反向傳播之后,我們將得到一個(gè)訓(xùn)練好的網(wǎng)絡(luò),然后就可以進(jìn)行預(yù)測(cè)了。
盡管看起來非常簡(jiǎn)單直接,但在處理結(jié)構(gòu)化數(shù)據(jù)時(shí),人們往往更偏愛基于樹的方法,而不是神經(jīng)網(wǎng)絡(luò)。原因?yàn)楹?這可以從算法的角度理解——算法究竟是如何對(duì)待和處理我們的數(shù)據(jù)的。
人們對(duì)結(jié)構(gòu)化數(shù)據(jù)和非結(jié)構(gòu)化數(shù)據(jù)的處理方式是不同的。非結(jié)構(gòu)化數(shù)據(jù)雖然是「非常規(guī)的」,但我們通常處理的是單位量的單個(gè)實(shí)體,比如像素、體素、音頻頻率、雷達(dá)反向散射、傳感器測(cè)量結(jié)果等等。而對(duì)于結(jié)構(gòu)化數(shù)據(jù),我們往往需要處理多種不同的數(shù)據(jù)類型;這些數(shù)據(jù)類型分為兩大類:數(shù)值數(shù)據(jù)和類別數(shù)據(jù)。類別數(shù)據(jù)需要在訓(xùn)練之前進(jìn)行預(yù)處理,因?yàn)榘窠?jīng)網(wǎng)絡(luò)在內(nèi)的大多數(shù)算法都還不能直接處理它們。
編碼變量有很多可選的方法,比如標(biāo)簽/數(shù)值編碼和 one-hot 編碼。但在內(nèi)存方面和類別層次的真實(shí)表示方面,這些技術(shù)還存在問題。內(nèi)存方面的問題可能更為顯著,我們通過一個(gè)例子來說明一下。
假設(shè)我們列中的信息是一個(gè)星期中的某一天。如果我們使用 one-hot 或任意標(biāo)簽編碼這個(gè)變量,那么我們就要假設(shè)各個(gè)層次之間都分別有相等和任意的距離/差別。
但這兩種方法都假設(shè)每?jī)商熘g的差別是相等的,但我們很明顯知道實(shí)際上并不是這樣,我們的算法也應(yīng)該知道這一點(diǎn)!
「神經(jīng)網(wǎng)絡(luò)的連續(xù)性本質(zhì)限制了它們?cè)陬悇e變量上的應(yīng)用。因此,用整型數(shù)表示類別變量然后就直接應(yīng)用神經(jīng)網(wǎng)絡(luò),不能得到好的結(jié)果。」[1]
基于樹的算法不需要假設(shè)類別變量是連續(xù)的,因?yàn)樗鼈兛梢园葱枰M(jìn)行分支來找到各個(gè)狀態(tài),但神經(jīng)網(wǎng)絡(luò)不是這樣的。實(shí)體嵌入(entity embedding)可以幫助解決這個(gè)問題。實(shí)體嵌入可用于將離散值映射到多維空間中,其中具有相似函數(shù)輸出的值彼此靠得更近。比如說,如果你要為一個(gè)銷售問題將各個(gè)省份嵌入到國(guó)家這個(gè)空間中,那么相似省份的銷售就會(huì)在這個(gè)投射的空間相距更近。
因?yàn)槲覀儾幌朐谖覀兊念悇e變量的層次上做任何假設(shè),所以我們將在歐幾里得空間中學(xué)習(xí)到每個(gè)類別的更好表示。這個(gè)表示很簡(jiǎn)單,就等于 one-hot 編碼與可學(xué)習(xí)的權(quán)重的點(diǎn)積。
嵌入在 NLP 領(lǐng)域有非常廣泛的應(yīng)用,其中每個(gè)詞都可表示為一個(gè)向量。Glove 和 word2vec 是其中兩種著名的嵌入方法。我們可以從圖 4 看到嵌入的強(qiáng)大之處 [2]。只要這些向量符合你的目標(biāo),你隨時(shí)可以下載和使用它們;這實(shí)際上是一種表示它們所包含的信息的好方法。
盡管嵌入可以在不同的語(yǔ)境中使用(不管是監(jiān)督式方法還是無監(jiān)督式方法),但我們的主要目標(biāo)是了解如何為類別變量執(zhí)行這種映射。
實(shí)體嵌入
盡管人們對(duì)「實(shí)體嵌入」有不同的說法,但它們與我們?cè)谠~嵌入上看到的用例并沒有太大的差異。畢竟,我們只關(guān)心我們的分組數(shù)據(jù)有更高維度的向量表示;這些數(shù)據(jù)可能是詞、每星期的天數(shù)、國(guó)家等等。這種從詞嵌入到元數(shù)據(jù)嵌入(在我們情況中是類別)的轉(zhuǎn)換使用讓 Yoshua Bengio 等人使用一種簡(jiǎn)單的自動(dòng)方法就贏得了 2015 年的一場(chǎng) Kaggle 競(jìng)賽——通常這樣做是無法贏得比賽的。參閱:https://www.kaggle.com/c/pkdd-15-predict-taxi-service-trajectory-i
「為了處理由客戶 ID、出租車 ID、日期和時(shí)間信息組成的離散的元數(shù)據(jù),我們使用該模型為這些信息中的每種信息聯(lián)合學(xué)習(xí)了嵌入。這種方法的靈感來自于自然語(yǔ)言建模方法 [2],其中每個(gè)詞都映射到了一個(gè)固定大小的向量空間(這種向量被稱為詞嵌入)。[3]
我們將一步步探索如何在神經(jīng)網(wǎng)絡(luò)中學(xué)習(xí)這些特征。定義一個(gè)全連接的神經(jīng)網(wǎng)絡(luò),然后將數(shù)值變量和類別變量分開處理。
對(duì)于每個(gè)類別變量:
1. 初始化一個(gè)隨機(jī)的嵌入矩陣 mxD:
- m:類別變量的不同層次(星期一、星期二……)的數(shù)量
- D:用于表示的所需的維度,這是一個(gè)可以取值 1 到 m-1 的超參數(shù)(取 1 就是標(biāo)簽編碼,取 m 就是 one-hot 編碼)

圖 6:嵌入矩陣
2. 然后,對(duì)于神經(jīng)網(wǎng)絡(luò)中的每一次前向通過,我們都在該嵌入矩陣中查詢一次給定的標(biāo)簽(比如為「dow」查詢星期一),這會(huì)得到一個(gè) 1xD 的向量。

圖 7:查找后的嵌入向量
3. 將這個(gè) 1×D 的向量附加到我們的輸入向量(數(shù)值向量)上。你可以把這個(gè)過程看作是矩陣增強(qiáng),其中我們?yōu)槊恳粋€(gè)類別都增加一個(gè)嵌入向量,這是通過為每一特定行執(zhí)行查找而得到的。
4. 在執(zhí)行反向傳播的同時(shí),我們也以梯度的方式來更新這些嵌入向量,以最小化我們的損失函數(shù)。
輸入一般不會(huì)更新,但對(duì)嵌入矩陣而言有一種特殊情況,其中我們?cè)试S我們的梯度反向流回這些映射的特征,從而優(yōu)化它們。
我們可以將其看作是一個(gè)讓類別嵌入在每次迭代后都能進(jìn)行更好的表示的過程。
注意:根據(jù)經(jīng)驗(yàn),應(yīng)該保留沒有非常高的基數(shù)的類別。因?yàn)槿绻粋€(gè)變量的某個(gè)特定層次占到了 90% 的觀察,那么它就是一個(gè)沒有很好的預(yù)測(cè)價(jià)值的變量,我們可能最好還是避開它。
好消息
通過在我們的嵌入向量中執(zhí)行查找并允許 requires_grad=True 并且學(xué)習(xí)它們,我們可以很好地在我們最喜歡的框架(最好是動(dòng)態(tài)框架)中實(shí)現(xiàn)上面提到的架構(gòu)。但 Fast.ai 已經(jīng)實(shí)現(xiàn)了所有這些步驟并且還做了更多。除了使結(jié)構(gòu)化的深度學(xué)習(xí)更簡(jiǎn)單,這個(gè)庫(kù)還提供了很多當(dāng)前最先進(jìn)的功能,比如差異學(xué)習(xí)率、SGDR、周期性學(xué)習(xí)率、學(xué)習(xí)率查找等等。這些都是我們可以利用的功能。你可以在以下博客進(jìn)一步了解這些主題:
- 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 實(shí)現(xiàn)
在這一部分,我們將介紹如何實(shí)現(xiàn)上述步驟并構(gòu)建一個(gè)能更有效處理結(jié)構(gòu)化數(shù)據(jù)的神經(jīng)網(wǎng)絡(luò)。
為此我們要看看一個(gè)熱門的 Kaggle 競(jìng)賽:https://www.kaggle.com/c/mercari-price-suggestion-challenge/。對(duì)于實(shí)體嵌入來說,這是一個(gè)非常合適的例子,因?yàn)槠鋽?shù)據(jù)基本上都是類別數(shù)據(jù),而且有相當(dāng)高的基數(shù)(也不是過高),另外也沒有太多其它東西。
數(shù)據(jù):
約 140 萬(wàn)行
- item_condition_id:商品的情況(基數(shù):5)
- category_name:類別名稱(基數(shù):1287)
- brand_name:品牌名稱(基數(shù):4809)
- shipping:價(jià)格中是否包含運(yùn)費(fèi)(基數(shù):2)
重要說明:因?yàn)槲乙呀?jīng)找到了最好的模型參數(shù),所以我不會(huì)在這個(gè)例子包含驗(yàn)證集,但是你應(yīng)該使用驗(yàn)證集來調(diào)整超參數(shù)。
第 1 步:
將缺失值作為一個(gè)層次加上去,因?yàn)槿笔П旧硪彩且粋€(gè)重要信息。
- 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 步:
預(yù)處理數(shù)據(jù),對(duì)數(shù)值列進(jìn)行等比例的縮放調(diào)整,因?yàn)樯窠?jīng)網(wǎng)絡(luò)喜歡歸一化的數(shù)據(jù)。如果你不縮放你的數(shù)據(jù),網(wǎng)絡(luò)就可能格外重點(diǎn)關(guān)注一個(gè)特征,因?yàn)檫@不過都是點(diǎn)積和梯度。如果我們根據(jù)訓(xùn)練統(tǒng)計(jì)對(duì)訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)都進(jìn)行縮放,效果會(huì)更好,但這應(yīng)該影響不大。這就像是把每個(gè)像素的值都除以 255,一樣的道理。
因?yàn)槲覀兿M嗤膶哟斡邢嗤木幋a,所以我將訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)結(jié)合了起來。
- combined_x, combined_y, nas, _ = proc_df(combined, 'price', do_scale=True)
第 3 步:
創(chuàng)建模型數(shù)據(jù)對(duì)象。路徑是 Fast.ai 存儲(chǔ)模型和激活的地方。
- path = '../data/'
- md = ColumnarModelData.from_data_frame(path, test_idx, combined_x, combined_y, cat_flds=cats, bs= 128
第 4 步:
確定 D(嵌入的維度),cat_sz 是每個(gè)類別列的元組 (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 步:
創(chuàng)建一個(gè) learner,這是 Fast.ai 庫(kù)的核心對(duì)象。
- 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),
第 6 步:
這部分在我前面提及的其它文章中有更加詳細(xì)的解釋。
要充分利用 Fast.ai 的優(yōu)勢(shì)。
在損失開始增大之前的某個(gè)時(shí)候,我們要選擇我們的學(xué)習(xí)率……
- # find best lrm.lr_find()# find best lrm.sched.plot()
圖 9:學(xué)習(xí)率與損失圖
擬合
我們可以看到,僅僅過了 3 epoch,就得到:
- lr = 0.0001m.fit(lr, 3, metrics=[lrmse])
更多擬合
- m.fit(lr, 3, metrics=[lrmse], cycle_len=1)
還有更多……
- m.fit(lr, 2, metrics=[lrmse], cycle_len=1)
所以,在短短幾分鐘之內(nèi),無需進(jìn)一步的其它操作,這些簡(jiǎn)單卻有效的步驟就能讓你進(jìn)入大約前 10% 的位置。如果你真的有更高的目標(biāo),我建議你使用 item_description 列并將其作為多個(gè)類別變量使用。然后把工作交給實(shí)體嵌入完成,當(dāng)然不要忘記堆疊和組合。
參考文獻(xiàn)
- [1] Cheng Guo, Felix Berkhahn (2016, April, 22) Entity Embeddings of Categorical Variables. Retrieved from https://arxiv.org/abs/1604.06737.
- [2] TensorFlow Tutorials: https://www.tensorflow.org/tutorials/word2vec
- [3] Yoshua Bengio, et al. Artificial Neural Networks Applied to Taxi Destination Prediction. Retrieved from https://arxiv.org/pdf/1508.00021.pdf.