掌握動態規劃,助你成為優秀的演算法工程師

達觀資料發表於2019-10-08

1.導論

相信很多同學已經在為今年的校招做準備了,隨著AI的火熱,越來越多的同學湧入了演算法的行當之中。那去年校招的演算法崗是有多火熱?在知乎上看到這麼一條帖子,先不說內容哈,足足400w+的閱讀量啊。
0 (1)
不光是計算機或軟體專業的學生,很多電子,通訊,自動化等相關專業的同學也吸引了進來。當然,這應該是件好事。但是相當一部分同學,在學習的過程中,尤其是剛入門的時候,可能會有這樣一個疑問:演算法工程師的演算法,為什麼不是指《演算法導論》中的演算法(以下稱為經典演算法,用以區分),而是指機器學習裡的演算法。都叫演算法(Algorithm),但好像不是一回事啊,兩者有什麼關係,又有什麼區別呢?
本文試圖通過動態規劃這一經典演算法中的重要內容,同時又在機器學習演算法中有著廣泛的應用,來簡單探討一下這兩種“演算法”。

2.動態規劃

首先,動態規劃(Dynamic Programming)是《演算法導論》中的重要章節,同時也是在機器學習演算法中有著非常重要應用的一種優化演算法。可以說是,無論是否是演算法工程師都應該掌握的一種演算法。再功利一點說,動態規劃也是諸多面試官特別喜歡考的一種題型,下面就帶大家稍微溫故一下。

按照教材[1]的介紹,動態規劃通常需要按如下4個步驟來進行設計:
  1. 刻畫一個最優解的結構特徵。
  2. 遞迴地定義最優解的值。
  3. 計算最優解的值,通常採用自底向上的方法。
  4. 利用計算出資訊構造一個最優解。
簡單點說,動態規劃演算法的核心就是大問題拆成重複的小問題,同時記住已經解決了的小問題的解。那如果你去網上搜搞ACM或者OI大神的說法,他們都會說動態規劃是一個框,是一種解決問題的思想,而不是某種具體的演算法。下面我們詳細來看看,時髦的機器學習是怎麼往動態規劃這個框裡填的。

2.1 編輯距離

說到編輯距離(Edit distance),大家可能都比較熟悉。在自然語言處理(Natural Language Processing,又NLP)中,編輯距離是計算文字相似度的一種基本距離計算方式。簡單來說,就是隻能通過替換,刪除,插入操作,將一串字串變為另一串字串的運算元。而求最小編輯距離的過程,就是一個典型的動態規劃的過程。
如果計算兩個字串的編輯距離,我們可以看做是父問題,那他的子問題自然就是如何求更小字串之間的編輯距離。那上面提到,動態規劃不僅要將父問題拆分成子問題,更要記下子問題的解,以達到節省空間和時間的效果。
下圖可以看出,這個D矩陣,就是我們用來儲存這個子問題解的空間,假設兩個字串的長度分別為mn。而D(i, j)如圖所示,則是我們定義的最優解的結構特徵,實際表示的就是長度為i和長度為j的兩個子序列之間的最小編輯距離,我們把它從左至右,從下到上填到每個格子裡,一直到矩陣的右上角,就可以得到我們要的最小編輯距離。那麼最終我們的空間複雜度為O(mn),而時間複雜度同樣為O(mn),相比採用分治的思想遞迴地進行求解還是要快很多。(這裡篇幅有限,就不贅述程式碼了,有興趣的同學可以自行網上搜尋)
掌握動態規劃,助你成為優秀的演算法工程師
https://web.stanford.edu/class/cs124/lec/med.pdf

2.2 動態時間規整

說完NLP裡的相似度計算,我們們再來看看語音識別裡有沒有類似的演算法用到動態規劃呢?答案是肯定的。這個演算法就叫動態時間規整(Dynamic time warping),簡稱DTW,一聽名字是不是感覺就很動態規劃。DTW演算法是傳統語音識別中的重要方法,起初是用於孤立詞的語音識別,判斷兩段語音是否為同一個單詞。實際上,只要是時間序列,如下圖[2],都可以用來計算相似度,不侷限於語音識別當中,例如股市的交易策略,手勢識別等等場景都有應用。
0 (1)
在語音訊號中,有一個很大的問題就是,訊號長度並不相等,即使是同一個人說同一個單詞,也會有語速上的差別。那這個時候基於歐幾里得距離(Euclidean Distance)的方法就不奏效了,但是兩個時間序列形狀上又非常的相似,於是我們就希望可以通過某種對齊的方式來衡量這兩個時間序列的相似性。如上圖所示,箭頭代表了兩個時間序列中的相似點。我們用所有相似點之間的距離和來表示這種相似度,稱之為規整路徑距離。
0 (2)
大家看到這個“棋盤”好像和我們上面講到的編輯距離中的D矩陣很像。沒錯,假設n為序列A的長度,m為序列B的長度,D(m, n)就是上面這兩個序列的規則路徑距離。至於D(m, n)怎麼求?又到了我們的動態規劃發揮威力的時候。還是像求最小編輯距離一樣,D(i, j)如下式所求,並且由左到右,由下到上填入D矩陣中,最終求得的右上角的值就是我們的規整路徑距離。時間複雜度依然為O(mn)
0 (2)
說到這裡,好像動態規劃不就是畫個矩陣,按照D(i, j)的計算方法填滿矩陣,得到右上角的最終解。嗯,好像也有點道理。那我們接下來繼續看看,這樣說對不對。

2.3 維特比演算法

如果說前面兩種演算法和機器學習只能算沾邊的話,那我們現在要說的維特比演算法(Viterbi Algorithm)可以說是動態規劃機器學習當中的典範了,尤其如果是做自然語言處理方向的話,維特比演算法更是不可不知,哪怕在如今deep learning當道的時代。在自然語言處理中,像分詞、詞性標註命名實體識別、輸入法等等任務中都有非常重要的應用。
除了前面介紹的計算兩個序列之間的距離以外,動態規劃還有一個重要的應用場景,就是計算有向無環圖(DAG)中兩個節點間的最短路徑,維特比演算法就是針對籬笆網路(Lattice Network)這一特殊的有向無環圖而提出的。
0 (4)
如上圖所示,這是一個部分的籬笆網路,中間我們假設有N列,每列有4個節點,節點之間的權重我們暫時忽略。這個時候,網路的最左邊有一個節點為S,最右端有一個節點為E。如果我想求SE之間的最短路徑,理所當然,我們如果窮舉出所有的路徑進行比較,也就是4N條路徑,自然可以得到結果,但如果層數很多或者每層的節點數很多的時候,這種方法就顯得不夠友好了。
既然窮舉法太過暴力,自然我們想試試能不能用動態規劃來解決。首先,籬笆網路有這麼一個特點,就是假設我們從第一列走到最後一列,我們一定會經過其中的第i時刻的某個節點。這個當然是顯而易見的,但給我們帶來了一個好處,那就是當我們計算最終的最短路徑時,假設第i列有k個節點,如果我們已經計算了從開頭到第i列所有k個節點的最短路徑,那最終的最短路徑一定是經過其中之一。第二,如果說最短路徑P經過某個節點xij,那麼從起始節點S到節點xij的這段子路徑Q,一定是從起始節點Sxij的最短路徑,否則總路徑P也不再是最短路徑,這就自相矛盾了。
有了這兩個特性,終於可以試試動態規劃了。同樣我們從最左邊的S節點出發,到第1列的4個節點,因為各只有一段距離,那自然這4個距離d(S, x1i)為S節點到這4個節點的最短距離。當我們走到第2列時,根據之前的特性,一定會經過第1列的某個節點。此時的S節點到第2列某個節點的距離則為d(S, x2j)=d(S, x1i) + d(x1i, x2j)。而第1列有4個節點,所以d(S, x2j)應該是取4個距離中的最小值,當然在這一過程中,我們計算了4次,對於第2列的每個節點,我們都去進行如上的計算。所以在從第1列走到第2列的過程中,我們計算了4×4次,更關鍵的是我們把d(S, x2j)都要儲存下來,作為我們下一次計算的基礎。
而這個儲存中間結果的過程,很明顯地體現出了前文所述的動態規劃的特點。接下來,我們繼續走到第3列,同樣的,S節點到第3列某個節點的距離為d(S, x3k)=d(S, x2j) + d(x2j, x3k)。這個時候我們發現,等式右邊的第一項,可以直接取我們剛剛儲存的中間結果。對於d(S, x3k),我們依然是計算4次,取最小值儲存下來。同樣,需要遍歷第3列的4個節點,所以又是4×4次計算。也就是說,每往前走1列,我們就計算了4×4次。以此類推,到最右邊的節點E的時候,我們需要計算42次,相比於窮舉法的4N條路徑,這個效率已經是非常大的進步,把指數級的複雜度降低到了多項式級別!

2.4 CYK演算法

這個CYK演算法大家可能會有點陌生,全名為Cocke–Younger–Kasami演算法,是以三位作者的姓名共同命名的。這個演算法其實是句法分析方向的一個經典演算法,因為提出的時間是在基於規則的年代,所以即使是做NLP的同行,依然有很多同學並不瞭解。所以這裡給大家多交代一些背景知識。
首先,CYK演算法是基於喬姆斯基(Chomsky)正規化的上下文無關語法(Context Free Grammar)。感覺越解釋概念越多了哈。簡單點說,喬姆斯基正規化有兩種形式。
0 (9)
這裡,ABC都是非終結符,就是像名詞短語(NP),動詞短語(VP)等等,x是終結符,比如單詞就是終結符。對於A這個非終結符,要麼拆分成更小的2個非終結符,要麼就到此為止,右邊是一個單詞。例如:”吃藥“這個動詞短語,就可以按下面的方式進行句法分析。
0 (10)
好了,介紹完了背景知識,我們的任務就是給定一個句法規則的集合,對於任意一個句子,將它按照這個句法規則集合進行解析。下圖就是我們的一個句法解析樹,也就是最終的一個結果。
0 (4)
對於一次解析來說,如果嘗試去解析出所有的結果,那將是指數級的複雜度,而CYK演算法就是利用動態規劃的思想將複雜度降低到了多項式級。假設我們的句子的單詞數為n,我們先畫一個如下圖的下三角矩陣,橫座標為位置i,縱座標為跨度j
0 (3)
CYK演算法過程如下:
0 (3)
http://ccl.pku.edu.cn/doubtfire/Course/Computational%20Linguistics/contents/CYK_parsing.pdf

其中最關鍵的就是步驟2中的最內層迴圈,對於一個跨度內所有分割點k。簡單點說,就是在給定了位置和跨度的一個短語中,不斷調整中間的分割點位置,使得分割點兩邊的短語子串能夠符合給定的句法規則。當子串符合句法規則時,把對應的結果記錄下來,併為後續的解析所用,這就體現了動態規劃思想的核心!

2.5 分詞

上面我們介紹過維特比演算法,在分詞任務中有重要的應用,但現在我們又要來提分詞了,那這裡的動態規劃和維特比有什麼不一樣呢。首先,目前的分詞的主流演算法裡有這麼兩種,一種是基於字的模型分詞演算法,還有一種則是基於詞典的分詞方法。前者,主要有基於隱馬爾可夫(HMM)的生成式模型、基於條件隨機場(CRF)的判別式模型以及基於神經網路的分詞演算法,在這些演算法的解碼部分,都可以使用維特比演算法進行解碼。那這節要說的動態規劃則是基於詞典的分詞演算法裡用到的。
基於詞典的分詞演算法是如何工作的呢?舉一個簡單的例子,"我來到達觀資料"。首先,我們根據詞典將句子進行簡單的匹配,將句中匹配到的詞典詞和所有的單字組合起來,作為節點,構造成一個分詞的詞圖,如下圖所示(邊的權重假設都為1)。那這個圖裡從S節點到E節點的每條路徑,都代表這一種分詞的結果。我們希望正確的分詞結果,理應是一條概率最大的的路徑,這樣才能把一個語言學的問題轉化成一個數學的問題,從而讓計算機可以輔助我們得到正確的分詞結果。
0 (5)
因為句子是順序的,所以這個詞圖表現為一個有向無環圖。還記得前面講到的維特比演算法是針對特殊的有向無環圖來計算最短路徑,這裡的詞圖就顯得更為一般些。如何求得有向無環圖的最短路徑呢,這個時候,我們需要先對詞圖進行拓撲排序,然後再利用動態規劃求排序後的詞圖最大概率路徑。拓撲排序的結果如下圖:
0 (6)
有了拓撲排序的結果,我們就可以動態規劃了。上面我們講Viterbi演算法的時候,提到這麼一個性質,如果最終的最短路徑P經過某個節點i,那麼經過這個節點的子路徑Q一定是起始節點S到節點i的最短路徑,否則最短路徑P一定有比它更短的路徑存在。在這裡,我們是求最大概率路徑,其實原理是一樣的。對於到最終節點E的路徑Route(E),有:
0 (7)
實際上,我們可以一直寫下去,而這些子路徑就是動態規劃中的最優子結構,我們只需要再求解這些子結構中的最優解即可。相比於,列舉出所有的分詞路徑,這種計算方法是要快上很多。在計算最大概率的過程中,有不少小trick。期待後續的達觀技術分享為大家詳細介紹。

2.6 強化學習策略迭代

說到強化學習,這裡不得不首先引用一下Sutton在《Reinforcement Learning: An Introduction》關於動態規劃的一些表述。
DP provides an essential foundation for the understanding of the methods presented in the rest of this book. In fact, all of these methods can be viewed as attempts to achieve much the same effect as DP, only with less computation and without assuming a perfect model of the environment.
雖然動態規劃在實際的強化學習中,有諸多的限制,但動態規劃所具有的理論基礎是強化學習必不可少的,可見動態規劃之於強化學習的重要意義!
強化學習和大家熟悉的機器學習有著很大的不同,它沒有用到標註資料進行supervise,而且當前的動作可能會影響到後續的結果。既然是動作,肯定會考慮到很多因素。那麼在強化學習裡,我們需要考慮哪些變數呢?首先肯定是回報了,這裡用R來表示,不同的回報決定了不同的動作。回報又分為長期的和短期的,就好比做基礎演算法研究,短期看沒什麼回報,但長期來說就是技術的護城河。我們通過衰減係數?來表示,0≤?<1。除此以外,我們還要看當前所處的狀態S來決定所做的動作。有了狀態的集合,那自然要考慮狀態之間的轉移關係,就是我們的P,狀態轉移矩陣,但因為對於很多實際問題,狀態實在太多,所以每個狀態只能考慮前一個狀態的相關情況,這樣就大大簡化了問題的複雜度,這種性質我們也叫馬爾科夫性。最後,加上我們的一系列動作A,就有了馬爾科夫決策過程(Markov Decision Process, MDP)。
0 (5)
http://www0.cs.ucl.ac.uk/staff/d.silver/web/Teaching_files/MDP.pdf
好了,現在有了這些基本要素了,下面要用這些要素做什麼呢?比如機器學習裡,我們希望是最小化損失函式。而在強化學習裡,我們則是希望最大化的累積回報,比如下棋的時候,只要最終贏了即可,而不是要每步都要吃掉對方一些棋子。這時候,我們就先定義一下這個累積回報,假設從t時刻開始到最終時刻T,並且加上之前提到的衰減係數?,則有:
0 (8)
既然這個G~t~是從t時刻到最終狀態的累積回報,那就不光是一條路徑的累積,而是所有的路徑的總和,當狀態過多的時候,我們就沒法計算。這個時候需要引入一個狀態值函式,作為從某狀態開始的回報總和的期望。
0 (11)
這個狀態值函式就是用來衡量某一個狀態的好壞。除了我們上面提到的幾個要素以外,我們在某個狀態執行什麼動作,並不是固定的,那就需要引入新的概念策略?,作為動作關於狀態的分佈。則上式就變為:
0 (12)
問題又來了,這個期望其實也不太好算,因為其中一個狀態的價值函式需要計算後續所有狀態的回報。既然是馬爾科夫決策過程,那自然會想到用迭代的思想去計算。得到其遞迴的形式Bellman方程,如下式(詳細推導過程見7)
0 (13)
這樣,在給定策略的情況下,我們就可以迭代地去求每一個狀態的值函式了。從這個更新公式,也可以看出,強化學習裡的動態規劃將圍繞狀態值函式的更新這個最優子結構而做文章。
對於從v1到最終的v*的每次迭代vk,我們利用上式對值函式進行更新,直到收斂V*(證明略)。這一步就叫做策略評估。這時候,我們可以對於取得最大的動作值函式q而得到的新策略,對新的策略進行策略評估步驟。直到我們的策略不再變化,此時得到的策略即為最優策略。

3.總結

一下子說了那麼多涉及到動態規劃機器學習演算法,相信讀者朋友們還可以列舉出更多(歡迎評論區留言)。我們在動態規劃這個框裡填了那麼多機器學習演算法,而機器學習演算法也非常依賴動態規劃的高效率,可謂是你中有我,我中有你。
從上面舉的很多例子大家也可以看到,動態規劃更側重於對問題求解的優化,比如求最短距離,實際上是可以通過暴力方法進行求解的,而動態規劃則是提高了求解的效率。但機器學習演算法可不僅僅是對問題的求解進行優化,它要解決的問題往往沒有精確解,同時也沒法通過窮舉等暴力方法進行求解。需要對問題進行清晰地定義並建模,重點在於學習的過程,基於對訓練資料的擬合來預測新的樣本。
而說完動態規劃,大家很容易又聯想貪心演算法,同樣也是經典演算法中的一個重要的優化方法。比如word2vec中的霍夫曼樹(Huffman Tree),比如seq2seq中需要用到的集束搜尋(Beam Search),都是借鑑了貪心演算法的思想。前面我們提到了求最短路徑問題雖然是動態規劃,這實際上又涉及到經典演算法中圖論裡的內容,類似的還有一系列的概率圖模型(Probabilistic Graphical Model)。事實上,還有很多機器學習演算法運用到了經典演算法裡的內容,這裡就不一一列舉了。
總之,對於一名優秀的演算法工程師來說,經典演算法的掌握必不可少,對於機器學習演算法的理解也有很大的幫助。希望大家能夠認真掌握,"舉一反三",在接下來的秋招中好好發揮。

參考資料

1.Cormen, Thomas H., Charles E. Leiserson, and Ronald L. Rivest. "演算法導論." 第 2 版 機械工業出版社 (2006).
2.Salvador, Stan, and Philip Chan. "FastDTW: Toward Accurate Dynamic Time Warping in Linear Time and Space."
3.吳軍. 數學之美. 人民郵電出版社, 2012.
4.宗成慶. 統計自然語言處理. 清華大學出版社, 2013
5.黃海安. https://www.zybuluo.com/Team/note/1123968
6.Wiering, Marco, and Martijn Van Otterlo. "強化學習." 機械工業出版社.
7.http://www0.cs.ucl.ac.uk/staff/d.silver/web/Teaching_files/MDP.pdf

關於作者

楊慧宇:觀資料NLP技術專家,負責達觀資料底層NLP演算法效果的優化,以及財務稽核相關產品的研發工作。

相關文章