論文出處:SeqGAN: Sequence Generative Adversarial Nets with Policy Gradient
在這幾年,使用DL技術解決文字生成的應用非常多,包括NMT或是chatbot,或是文章的自動摘要等等。傳統上會使用一個Seq2seq模型,利用 Mamximize 每個 time step token 和 groundtruth token 的 Likelihood。而這會有許多問題。比方說,因為機制是依照上一個時間點的token,選取這個時間點機率最大的token,但是在testing的時候遇到的狀況可能在training時根本沒看過,這會導致訓練困難,也就是所謂 explosure bias 問題。為了解決這個問題,Samy Bengio在2015年提出了 Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks,即所謂的 Scheduled Sampling演算法,解決Training和Testing之間差異的問題。
然而這並未解決所有問題,因為當我們在衡量一個句子的好壞的時候,往往不是這個時間點一定要出現什麼字,而是內容是否對應以及文法是否通順。舉個李宏毅老師上課提過的例子:
假設現在在實作一個Chatbot,有一對句子是:
A: How are you ?
B: I'm fine .
這時候假設我們的model output出兩個句子:
1. I am John .
2. Not bad .
依照NLL Loss的計算方式,第一個答案的loss會比較低,但這句很明顯答非所問。而第二句完全是可以的回答,但在這個Loss的測量下他會被嚴重懲罰。這呼應了我前面提到的,一個句子的好壞不該只看單一一個字,而是應該觀察整句話。當然,有許多Evaluation是可以彌補這種NLL的缺點,像是bleu score,比較可以評測句子的內容是否比較相關。但是我們不能用這個分數當作loss,因為他是不可微分的。
綜合以上,也是我實際在做 generation 有遇過的問題,所以會思考往 GAN 以及 RL 的方式解決這些問題,而最經典利用這兩者解決語言生成模型的方法當然就屬這篇: SeqGAN
Sequence Generative Adversarial Nets
GAN 在這之前已經廣泛被利用在電腦視覺領域,像是style transfer的cycle-GAN、star-GAN等等。但是在NLP領域卻相對比較少聽到,最主要限制如下:
- seq2seq model 的輸出並非連續的,換句話說就是,在輸出的時候因為採取了sampling的動作,導致不可微分。
- Disciminator 只能輸入完整的句子,這會造成無法在生成的過程中初步校正,導致長句生成難以訓練。
以上第一個問題這邊舉個例子解釋不能微分在現實生活中的意義: 假設我們在選取某個time step的output時,選擇了id = 3的token,這時,假設 error 經過 back propagation 到這個地方時,使這個id = 3.1。但是3.1這個id現實上是不存在的。也就是說傳統我們使用的訓練方法只能用在連續的分佈上,反之若是離散的數據會導致如上述的狀況。
Seq-GAN 最大精髓就是解決以上兩個問題:
- 使用Reinforce Method解決不可微分的環節
- 以及使用Monte Carlo Search 使得我們可以在句子尚未完整生成時可以給予 Reward。
先解釋上圖左半部:給定一個真實世界的資料(真人說出的句子),我們目標是訓練一個生成器 $G_{\theta}$ 可以生成出句子 $Y_{1:T} = (y_1,…,y_t,…y_T), y_t \in \mathcal Y$。這邊的 $\mathcal Y$ 是代表候選字的來源集合(字典)。$\theta$ 是生成器的參數。
前面提到,本篇文使用 RL 設計架構:
- 在 time step $t$, state $s$ 就是到目前為止生成的部分句子 $(y_1,….,y_{t-1})$
- Action $a$ 是時間 $t$ 要選擇的 $y_t$
因此,這個生成器 $G_\theta$ 可以被視為是隨機的。不過文中有特別解釋,儘管在一個在一個action被選擇後他的state transition是固定的:若當前 state $s = Y_{1:t-1}$ , action $a = y_t$,下一個state $s^{\prime } = Y_{1:t}$,則 $\delta_{s,s^{\prime}}^{a} = 1$。而對於其他的state $s^{\prime \prime}$:$\delta_{s,s^{\prime \prime}}^{a} = 0$。而GAN當然不只有生成器,對於判別器 $D_{\phi}$,這邊 $\phi$就是判別器的參數。
接著解釋上圖右半部:判別器的最初預想的作用機制是:他必須分辨出句子是從真實世界中取得的,還是經由生成器生成出的,並且給出一的對應的分數。跟一般的 GAN 直接在這個環節直接對於二元分類的Cross Entropy做gradient descent/ascent 不同,因為對於生成器參數的訓練來說是不可微分的關係,這邊是把分數當成reward,使用 policy gradient 的 training。而為了要讓判別器可以對未完成的句子給出reward,這邊採用 Mote Carlo 的搜尋演算法。
SeqGAN via Policy Gradient
首先,先介紹沒有intermediate reward 的 policy:
\[J(\theta) = \mathbb E[R_T|s_0, \theta] = \sum_{y_1 \in \mathcal Y} G_{\theta}(y_1|s_0) \cdot Q_{D_{\phi}}^{G_{\theta}}(s_0, y_1)\]- $R_T$ 是對於完整句子的 rewrad
- $Q_{D_{\phi}}^{G_{\theta}}$ 為action-value function,意思是給定一個initial state $s_0$,若生成器 $G_{\theta}$ 生成 $y_1$ 之後,判別器 $D_{\phi}$ 給這個sequence $y_1$ 的 reward。
而整個式子的意義就是,給定一個初始條件,生成某個句子可以得到的 reward 期望值。接下來的問題就是,該如何產生 action-value (reward) ? 在這邊,我們利用 RL 演算法,將判別判別器判斷某句話為 real 的機率當作 real,以下數學表示:
\[Q_{D_{\phi}}^{G_{\theta}}(a = y_T, s = Y_{1:T-1}) = D_{\phi}(Y_{1:T})\]然而上式只考慮到整句話要完成時的 action value 。在每個時間點,不但應該考慮到在這時間點之前的生成是否適合,未來的考量也會影響我們這個時間點的決定。比方說下棋,可能以短期來看有一步是可以獲得即時的回饋,但縱觀長期(最後勝負)可能就會放棄這個眼前的機會。這解釋了為什麼只考慮整個句子給出 reward 並不完美。
為了要解決上述問題,我們引入 Monte Carlo Search 的演算法, 我們藉由 roll out 的生成器 $G_{\beta }$ sample 出當下時間 $t$ 點到生成結束的 $T-t$ 個 token。
\[\bigg\{ Y_{1:T}^{1}, ....., T_{1:T}^{N}\bigg\} = MC^{G_{\beta}}(Y_{1:t}, N)\]$N$可以看成sample出來的句子數目,而每個 $Y_{1:T}^{n}$ 是由 roll-out policy $G_{\beta}$ 基於 $t$ 時間點的 state $Y_{1:t}^{n} = (y_1,….,y_t)$ 以及 sample 出 的 $Y_{t+1:T}^{n}$ 所構成。
總結一下 action value 的算法得到
$Q_{D_{\phi}}^{G_{\theta}}(a = y_T, s = Y_{1:t-1})$ =
$ \frac 1 N \sum_{n=1}^{N} D_{\phi}(Y_{1:T}^n), Y_{1:T}^n \in MC^{G_{\beta}}(Y_{1:t}, N) \quad for \quad t<T $
$D_{\phi}(Y_{1:t}) \quad for \quad t = T$
到這邊我們知道了 Reward 是如何計算的。我們使用判別器來當作reward,輔助生成器的訓練。這代表當生成能力變好之後,這個判別器要重新更新參數,才能繼續擔任有效的判別器以及給出合理的 reward。Discriminator 更新參數方法如下:
\[\operatorname*{min}_{\phi} (- \mathbb E_{Y \sim pdata}[logD_{\phi}(Y)] - \mathbb E_{Y \sim G_{\theta}}[log(1 - D_{\phi}(Y))])\]每當 $D_{\phi}$ 參數更新後,緊接著就更新 $G_{\theta}$,交替進行。更新Generator的方式無法像傳統GAN使用上式直接做gradient ascent,我們是以 reward 的期望值當作生成器的 objective function,並對其計算gradient:
\[\nabla_{\theta} J(\theta) = \sum_{t=1}^{T} \mathbb E_{Y_{1:t-1 \sim G_{\theta}}} \big[ \sum_{y_t \in \mathcal Y} \nabla_{\theta}G_{\theta}(y_t|Y_{1:t-1})\cdot Q_{D_{\phi}}^{G_{\theta}}(Y_{1:t-1}, y_t) \big]\]跟以往一樣,我們無法確切計算出期望值,所以我們採用sample的方式來逼近期望值:
\[\nabla_{\theta} J(\theta) \simeq \sum_{t=1}^{T}\sum_{y_t\in \mathcal Y}\nabla_{\theta} G_{\theta}(y_t|Y_{1:t-1})\cdot Q_{D_{\phi}}^{G_{\theta}}(Y_{1:t-1}, y_t)\] \[= \sum_{t=1}^{T}\sum_{y_t\in \mathcal Y}\nabla_{\theta} G_{\theta}(y_t|Y_{1:t-1})\cdot \frac {G_{\theta}(y_t|Y_{1:t-1})} {G_{\theta}(y_t|Y_{1:t-1})} \cdot Q_{D_{\phi}}^{G_{\theta}}(Y_{1:t-1}, y_t)\] \[= \sum_{t=1}^{T}\sum_{y_t\in \mathcal Y}G_{\theta}(y_t|Y_{1:t-1}) \cdot \nabla_{\theta} log G_{\theta}(y_t|Y_{1:t-1}) \cdot Q_{D_{\phi}}^{G_{\theta}}(Y_{1:t-1}, y_t)\] \[= \sum_{t=1}^{T}\mathbb E_{y_t \sim G_{\theta}}\big[ \nabla_{\theta} log G_{\theta}(y_t|Y_{1:t-1}) \cdot Q_{D_{\phi}}^{G_{\theta}}(Y_{1:t-1}, y_t) \big]\]到這裡,大致簡述了這篇的方法,以下是較為詳細的演算法虛擬碼:
實作上,不會讓Generator從頭開始使用前述架構學起,初期會先用傳統的 MLE 方法 pre-train $G_{\theta}$ 。