文章目錄
數據處理
相關數據說明
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_profiles
和 ot_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
對應的實現代碼如下:
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
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
實現:
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)