Mercari Price 比賽分享——語言不僅是演算法和公式而已
最近半年一直在忙於各種nlp比賽,除夕因為kaggle的price寫到凌晨3點,最後靠rp爬回季軍,也算圓了一個solo gold的夢想。這應該是我2017下半年玩到的最有意思的一場比賽了,賽內賽外都學到很多。kernel上有很多優秀的解決方案,有同學覺得我注釋太少(源碼在此https://www.kaggle.com/whitebird/mercari-price-3rd-0-3905-cv-at-pb-in-3300-s ),這裡聊一下這個比賽,正好也說說nlp這個領域的二三事。
price題目非常簡單,是一個通過二手商品的名字類型及用戶描述來預測一個定價的場景。有趣的地方在於主辦方為比賽做了一個很大的限制,讓參賽者所有的方案必須在線上的docker(16gb內存+1g硬碟+4 core cpu)中60分鐘內完成預處理訓練及預測。所以以往瘋狂融合模型的套路走不通了,大家在同一起跑線拼的就是對數據和機器學習模型的理解。比賽的ab榜幾乎沒有shake,可以說是非常良心的比賽。
思路:
既然是限時賽,那麼我們要做的事情就是通過人的先驗知識去為模型鋪好道路讓它沿著最優的梯度一路滑下去。商品定價回歸不同於文本分類,並不是簡單的截取單個關鍵字就可以進行判斷,而是由關鍵詞之間會有強烈的互作用力:比如蘋果+手機產生的價格遠遠高於他們各自價格相加,所以奠定了FM會是隱層中一個非常有效的回歸工具。而且二手市場大部分都是女性用戶,冗長的文本擁有大量信息,那麼輸入端提取特徵時nn的embedding也是一個很好的選擇。
預處理部分:
預處理佔據了代碼的70%,但卻是最不提分的部分...一個優秀的模型奠定了你的baseline,預處理只能給你錦上添花。如果不是參加比賽的選手其實可以忽略冗長的預處理:基本就是給拼寫錯誤的單詞糾正,幹掉奇怪的符號,把法語字母翻譯成英文字母等。
提一句英文單詞有個很有意思的點:詞的多態。顯然apple和apples、text和txt是相近的意思,但大部分tokenizer都會把它分成兩個。這個問題值得細細琢磨,我個人傾向通過詞根和詞源演化來解決這個問題,所以用了注重子串復現的翻譯評價指標bleu去歸併了詞語,有興趣可以自行了解下這個原理。
NN系列模型
最早我採用的是一個nn模型,模型結構如下:
la= [] def gauss_init(): return RandomNormal(mean=0.0, stddev=0.005)def conv1d_maxpool_flatten(filters, kernel_size, pool_size, input): return Flatten()(MaxPooling1D(pool_size)(Conv1D(filters, kernel_size, activation=sigmoid, padding=same)(input))input1 = Input(shape=(sub_featlen.shape[1],), dtype=float32)input2 = Input(shape=(1,), dtype=int32)input3 = Input(shape=(FEAT_LENGTH-FIX_LEN,), dtype=int32)input4 = Input(shape=(FEAT_LENGTH2,), dtype=int32)la.append(Flatten()(Embedding(index3, 30, init=gauss_init(), input_length=1, trainable=True)(input2)))x31 = Cropping1D(cropping=(0,40))(Embedding(wordnum, 40, init=gauss_init(), trainable=True)(input3))la.append(conv1d_maxpool_flatten(15, 3, FEAT_LENGTH-FIX_LEN, x31))la.append(conv1d_maxpool_flatten(50, 2, FEAT_LENGTH-FIX_LEN, x31))embedding_layer1 = Embedding(wordnum, 160, init=gauss_init(), trainable=True)la.append(Attention(50)(Cropping1D(cropping=(0,80))(embedding_layer1(input3))))la.append(Attention(50)(embedding_layer1(input4)))la.append(conv1d_maxpool_flatten(55, 2, FEAT_LENGTH2, Cropping1D(cropping=(0,50))(Embedding(wordnum, 50, init=gauss_init(), trainable=True)(input4))))x1 = BatchNormalization()(merge(la+[input1],mode = concat))x1 = Dropout(0.02)(merge([Dense(30, activation=sigmoid)(x1), PReLU()(Dense(470)(x1))], mode=concat))x1 = Dropout(0.02)(merge([Dense(256, activation=sigmoid)(x1), Dense(11, activation=linear)(x1), PReLU()(Dense(11)(x1))], mode=concat))out = merge([Dense(1, activation=linear)(x1), Dense(1, activation=relu)(x1), MaxoutDense(1, 30)(x1)], mode=sum)model = Model(input=[input1,input2,input3,input4], output=out)
四個input分別代表一些普通數值特徵、商品分類embedding、商品名稱+商標的短文本以及商品詳細信息長文本。普通特徵用Dense處理、文本使用cnn+attention。
為了保證效率,有幾個高光的細節:
1.文本的處理:首先放棄了lstm使用快速的cnn和attention,讓模型訓練在cpu下也可以非常快的處理embedding。特徵方面把商品名字和商品商標放在一起作為一個短文本,我之前有一篇博客(https://zhuanlan.zhihu.com/p/29394867)專門談過這個加速訓練的方案,比賽後我驚訝地發現這個思路很多top選手也用了,殊途同歸。
2.模型訓練為了極致優化時間,我甚至將每個ep的batchsize和optimizer手動控制,讓前期的batchsize小lr大——小步迅速迭代更新,後期batchsize大lr小——finetune。為了讓參數最優,幾乎所有參數都是靠不斷猜想嘗試+盯著loss的每一跳感受模型的梯度下降調整的...養成盯進度條的習慣有好處的。
3.大量運用了concat不同激活函數的方式。本來一個relu的結構,我習慣用一個relu和sigmoid拼接,甚至再加一個linear。我認為每一層的隱層特徵裡面既有表達是與否的二分類特徵也有表達程度的量特徵,用任何一個激活函數都是會有信息丟失,最合適的方式是讓模型的梯度自行選擇合適的激活函數。
另外優秀的NN模型還有第四名的XNN以及第一名的無embedding的文本處理NN。
第四名的XNN結構很複雜這裡就不細說,各位可以去閱讀他的github(https://github.com/ChenglongChen/tensorflow-XNN ),雜揉了cnn、attention、attend、fm等思路,是一個驚艷的nlp解決方案。
第一名的NN模型完全捨棄了cnn,用ngram先提取了所有關鍵詞然後tfidf直接扔給模型,效果非常驚人,僅僅用半個小時就遠高於現在第二名的成績,只能說state of the art。精緻的80行代碼,裡面滿滿的都是對於數據的理解。所謂藝術品對於新手來說如果看不到他雕刻成型的過程,那是很難學習到東西的,不建議新手學習。。。
FM系列模型:
一個NN對這個比賽來說是不夠的,當我把我的NN壓在40分鐘左右時,我開始做第二個模型。這時剛巧anttip開源了他的wordbatch庫(https://github.com/anttttti/Wordbatch ),我把它稍稍做了修改(將部分特徵tfidf),作為次模型和NN加權。學會FM的使用是這個比賽給我最大的收穫,以前從未想過FM可以在文本處理任務上這麼優秀。
不得不說anttip的ftrl-fm寫的非常漂亮,建議大家有興趣的去拜讀一下他的cython代碼。FM系列演算法在文本處理場景的好處是可以處理上百萬維的特徵,而embedding+cnn最多能提取上千到上萬個關鍵詞。相比之下FM更加迅速暴力,但缺點是不像NN一樣可以有3-5個隱層處理特徵關聯,潛力不足。但個人認為在一些簡單的工作生產環境,如商品短文本識別推薦等,高效率的ftrl-fm比NN更加適合。
lgb系列模型:
這個是我沒有嘗試的,因為在文本場景lgb表達力操作感上來說遠弱於NN,速度上又遜於FM。。。
--------------分割線---------------
比賽說到這裡,最後隨便說點演算法之外的事吧。2017年對我來說無論是在工作還是比賽上寫代碼,像極了wow那幾年打raid的日子。
最大的感觸是:Things change. 工作、比賽與遊戲一樣充斥著版本的更替。不斷有更加優秀的方案和模型湧現,就像一個個小資料片,瞬間就把以前的方案踩在腳下。
版本伊始,早期玩家帶著任務裝備開荒,戰場牌子副本牌子每個cd拚命的刷,種族聲望日常任務沒有斷過。
新版本的一切如公眾號上熱門的那些技術文:計算機視覺、自然語言、自動駕駛、強化學習;各個平台的比賽:天池的科學家積分、kaggle的master成就。日常論文要讀,比賽牌子要刷。
就在休閑玩家剛剛意識到版本的稀缺職業和材料的時候,高玩已經開著g團賺夠了g。老闆終於砸鍋賣鐵刷出畢業裝的時候,新版本預告片出來了。晚期的紫裝貶值,屬性還不如下個版本的入門藍綠。暴雪大手一揮,辛辛苦苦刷了幾個月的畢業裝和貨幣化為廢土。
人生並沒有在你艱辛努力後考上大學、買房、成家、打敗lich king的那一刻走到幸福的終點,而是從零開始了另一個篇章。NN神經網路和lgb梯度森林已經快把傳統的LR、SVM的價值擠壓殆盡了。轉瞬即逝的比特幣已經成為版本棄兒,而這個版本極度稀有的房產和演算法科學,兩年後的下個版本會不會爛在拍賣行?
沒有什麼事物抵擋得住時代變遷,除了人。知識會過時,想像力和好奇心不會;演算法會被淘汰,工程和學術能力不會。並沒有人告訴我們神經網路或者梯度森林什麼時候會過時,但我想如果有心把coding做到極致,不妨在提升這些演算法熟練度的同時,多做一些演算法之外的東西。從在研究Word2vec的時候想起閱讀古老的詞源學演化開始,從看到埃舍爾的版畫能聯想到CNN的kernel之間互相影響開始,從看到壯麗的建築感受到美妙的結構優化開始。
nlp是個非常有意思的領域,至今為止我常常在閱讀或者與人交談時,思考人為什麼如此組織語言,辭彙在我腦海中以怎樣的方式形成話語。正是因為這種對抽象和感性的迷惑促使了近年來這個領域不斷出現新的想法。人類擁有動物的獸性,又在長遠的進化路上獲得了理智。獸性本是天生,生物學可以解釋。現在是時候嘗試了解陌生的理智了。
最後祝各位狗年運勢昌隆,競賽有成。
推薦閱讀:
※嶺回歸-嶺回歸
※AI+互聯網金融--入職半年總結
※為何讀不懂你的那個TA
※Learning Explanatory Rules from Noisy Data 閱讀筆記4
※python學習之文章數據分析