源碼閱讀-CVAE模型

數據處理

相關數據說明

1、dialog

list of list

每一個list,由N個tuple組成,第一個tuple是開始的,

(["<s>", "<d>", "</s>"] ,0, None) 

其他的每個tuple,

  • 第一項是list of str(就是一個句子的所有單詞)
  • 第二項是int,是否是B說的,
  • 第三項是 tuple, 第一個元素是str,消息類型(下面統稱爲dialog_act),第二項是list of float,向量(1*4的數組)

2、meta

meta = (vec_a_meta, vec_b_meta, l["topic"])

vec_{}_meta = [{}_age, {}_edu, 1, 0]  #{}.format(a or b) [1, 0] or [0, 1]

list of tuple

每一個tuple也有三項,

  • 第一項和第二項都是說話人的信息,一個list,年齡、教育、性別
  • 第三項是str,topic

3、utt

list of list,

就是所有的消息,和dialog不同的是沒有說話人和消息向量這兩個。

詞庫構建

統計訓練語料中所有單詞的詞頻,只保留詞頻在前10000個。OOV在0.008035

1、vocab及rev_vocab

vocab:list of str,就是所有的單詞,再加上兩個 <pad><unk>(未知單詞)

rev_vocab:dict,key是單詞,value是在詞表的位置

2、 topic_vocab及rev_topic_vocab

也是按照topic頻率建立,一共有67個topic

3、 dialog_act 及 rev_dialog_act_vocab

同上,一共有42個dialog_act

batch準備

1、batch初始化

  • epoch_init:
  • backward_size:
  • step_size:

算出一共可以分成幾個batch,記錄每個batch包含的訓練數據。

2、_prepare_batch

每一batch返回前,要做如下處理

每一個數據單元都是一輪會話,把最後一句作爲out,前面的都是 context

返回的是一批輸入context,一批out

相關變量:
context_lens, list of int 每個訓練樣本有幾句輸入
context_utts, list of list 每個訓練樣本中的輸入句子(補齊長度)
floors, list of [0/1]  每個訓練樣本中的輸入句子,和`out`是不是同一個人
out_utts, list of list 每個`out`
out_lens, list of int 每個`out` 長度
out_floors, list of int,每個`out`說話人
out_das, list of list,每個`out`的語料
Returns:
vec_context: 一個np矩陣,第一維`batch_id`, 第二三維就是`context`向量,用0補齊
vec_context_lens:np數組,
vec_floors:np數組,第一維`batch_id`
my_profiles = np.array([meta[out_floors[idx]] for idx, meta in enumerate(meta_rows)])
ot_profiles = np.array([meta[1-out_floors[idx]] for idx, meta in enumerate(meta_rows)])

後來重新看了一下meta的內容,my_profilesot_profiles 是說話兩方

  • out_floors[idx] 取值0,1,1-out_floors[idx] 取值1,0

訓練及測試數據

1、meta_corpus

每一個meta是一個list,

[m_meta, o_meta, topic_idx]

2、dialog_corpus

每一個dialog是一個list

  • 第一項:每一句話中,每個詞在詞表的位置utt_idx,
  • 第二項:每一句話是誰說的0或者1,floor
  • 第三項:[dialog_act_idx, x,x,x],比如
['statement-non-opinion', [0.149, 0.851, 0.0, -0.4215]]

模型-KgRnnCVAE

相關參數

類型 詞向量長度 矩陣 方式
主題Topic 30維 67 * 30 查表
對話行爲DialogAct 30維 67 * 30 查表
詞庫voca 200維 1000*200 tf.nn.bidirectional_dynamic_rnn,初始化時還是查表

recogintionNetwork

[μlog(σ2)]=Wr[xc]+br \left[\begin{array}{c}{\mu} \\ {\log \left(\sigma^{2}\right)}\end{array}\right]=W_{r}\left[\begin{array}{l}{x} \\ {c}\end{array}\right]+b_{r}
對應的實現代碼如下:

with variable_scope.variable_scope("recognitionNetwork"):
    recog_input = tf.concat([cond_embedding, output_embedding, attribute_fc1], 1)
    recog_mulogvar = layers.fully_connected(recog_input, config.latent_size * 2, activation_fn=None, scope="muvar")
    recog_mu, recog_logvar = tf.split(recog_mulogvar, 2, axis=1) # 從公式看出是將兩個參數一起訓練的,所以用tf.split得到訓練後的結果。

priorNetwork

[μlog(σ2)]=MLPp(c) \left[\begin{array}{c}{\mu^{\prime}} \\ {\log \left(\sigma^{\prime 2}\right)}\end{array}\right]=\operatorname{MLP}_{p}(c)

with variable_scope.variable_scope("priorNetwork"):
    # P(XYZ)=P(Z|X)P(X)P(Y|X,Z)
    prior_fc1 = layers.fully_connected(cond_embedding, np.maximum(config.latent_size * 2, 100),
                                       activation_fn=tf.tanh, scope="fc1")
    prior_mulogvar = layers.fully_connected(prior_fc1, config.latent_size * 2, activation_fn=None,
                                            scope="muvar")
    prior_mu, prior_logvar = tf.split(prior_mulogvar, 2, axis=1)
    
    
    # use sampled Z or posterior Z
    latent_sample = tf.cond(self.use_prior,
                            lambda: sample_gaussian(prior_mu, prior_logvar),
                            lambda: sample_gaussian(recog_mu, recog_logvar))
    # 這裏, self.use_prior 是bool的False,所以執行的後面那個函數,就是trick,見下面

一直提到的採樣trick

其實很簡單,就是我們要從 $p(Z|X_{k})$ 中採樣一個 $Z_{k}$ 出來,儘管我們知道了$p(Z|X_{k})$ 是正態分佈,但是均值方差都是靠模型算出來的,我們要靠這個過程反過來優化均值方差的模型,但是“採樣”這個操作是不可導的,而採樣的結果是可導的,於是我們利用了一個事實:

$\mathcal{N}\left(\mu, \sigma^{2}\right)$中採樣一個$Z$,相當於從$\mathcal{N}\left(0, I\right)$中採樣一個$\epsilon$,然後讓$Z=\mu+\varepsilon \times \sigma$

這樣一來,“採樣”這個操作就不用參與梯度下降了,改爲採樣的結果參與,使得整個模型可訓練了

對應的代碼實現

def sample_gaussian(mu, logvar):
    epsilon = tf.random_normal(tf.shape(logvar), name="epsilon")
    std = tf.exp(0.5 * logvar)
    z= mu + tf.multiply(std, epsilon)
return z

KL loss

在前面的討論中,我們希望 $X$ 經過編碼後,$Z$ 的分佈都具有零均值和單位方差,這個“希望”是通過加入了 KL loss 來實現的。

如果現在多了類別信息 $Y$,我們可以希望同一個類的樣本都有一個專屬的均值 $\mu^{Y}$(方差不變,還是單位方差),這個 $\mu^{Y}$ 讓模型自己訓練出來。

這樣的話,有多少個類就有多少個正態分佈,而在生成的時候,我們就可以通過控制均值來控制生成圖像的類別。

事實上,這樣可能也是在 VAE 的基礎上加入最少的代碼來實現 CVAE 的方案了,因爲這個“新希望”也只需通過修改 KL loss 實現:

Lμ,σ2=12i=1d[(μ(i)μ(i)Y)2+σ(i)2logσ(i)21] \mathcal{L}_{\mu, \sigma^{2}}=\frac{1}{2} \sum_{i=1}^{d}\left[\left(\mu_{(i)}-\mu_{(i)}^{Y}\right)^{2}+\sigma_{(i)}^{2}-\log \sigma_{(i)}^{2}-1\right]

def gaussian_kld(recog_mu, recog_logvar, prior_mu, prior_logvar):
    kld = -0.5 * tf.reduce_sum(1 + (recog_logvar - prior_logvar)
                               - tf.div(tf.pow(prior_mu - recog_mu, 2), tf.exp(prior_logvar))
                               - tf.div(tf.exp(recog_logvar), tf.exp(prior_logvar)), reduction_indices=1)
return kld

存疑

模型訓練中,計算loss時有一處不是很明白

self.log_p_z = norm_log_liklihood(latent_sample, prior_mu, prior_logvar)
self.log_q_z_xy = norm_log_liklihood(latent_sample, recog_mu, recog_logvar)
self.est_marginal = tf.reduce_mean(rc_loss + bow_loss - self.log_p_z + self.log_q_z_xy)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章