当前位置:   article > 正文

源码阅读-CVAE模型_cvae代码

cvae代码

数据处理

相关数据说明

1、dialog

list of list

每一个list,由N个tuple组成,第一个tuple是开始的,

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

其他的每个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]
  • 1
  • 2
  • 3

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`的语料
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
Returns:
vec_context: 一个np矩阵,第一维`batch_id`, 第二三维就是`context`向量,用0补齐
vec_context_lens:np数组,
vec_floors:np数组,第一维`batch_id`
  • 1
  • 2
  • 3
  • 4
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)])
  • 1
  • 2

后来重新看了一下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]
  • 1

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]]
  • 1

模型-KgRnnCVAE

相关参数

类型词向量长度矩阵方式
主题Topic30维67 * 30查表
对话行为DialogAct30维67 * 30查表
词库voca200维1000*200tf.nn.bidirectional_dynamic_rnn,初始化时还是查表

recogintionNetwork

[ μ log ⁡ ( σ 2 ) ] = W r [ x c ] + b r \left[

μlog(σ2)
\right]=W_{r}\left[
xc
\right]+b_{r} [μlog(σ2)]=Wr[xc]+br
对应的实现代码如下:

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得到训练后的结果。

  • 1
  • 2
  • 3
  • 4
  • 5

priorNetwork

[ μ ′ log ⁡ ( σ ′ 2 ) ] = MLP ⁡ p ( c ) \left[

μlog(σ2)
\right]=\operatorname{MLP}_{p}(c) [μlog(σ2)]=MLPp(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,见下面

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

一直提到的采样trick

其实很简单,就是我们要从 $p(Z|X_{k})$ 中采样一个 Z k Z_{k} Zk出来,尽管我们知道了 p ( Z ∣ X k ) p(Z|X_{k}) p(ZXk) 是正态分布,但是均值方差都是靠模型算出来的,我们要靠这个过程反过来优化均值方差的模型,但是“采样”这个操作是不可导的,而采样的结果是可导的,于是我们利用了一个事实:

N ( μ , σ 2 ) \mathcal{N}\left(\mu, \sigma^{2}\right) N(μ,σ2)中采样一个 Z Z Z,相当于从 N ( 0 , I ) \mathcal{N}\left(0, I\right) N(0,I)中采样一个 ϵ \epsilon ϵ,然后让 Z = μ + ε × σ Z=\mu+\varepsilon \times \sigma Z=μ+ε×σ

这样一来,“采样”这个操作就不用参与梯度下降了,改为采样的结果参与,使得整个模型可训练了

对应的代码实现

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
  • 1
  • 2
  • 3
  • 4
  • 5

KL loss

在前面的讨论中,我们希望 $X$ 经过编码后,$Z$ 的分布都具有零均值和单位方差,这个“希望”是通过加入了 KL loss 来实现的。

如果现在多了类别信息 $Y$,我们可以希望同一个类的样本都有一个专属的均值 $\mu^{Y}$(方差不变,还是单位方差),这个 $\mu^{Y}$ 让模型自己训练出来。

这样的话,有多少个类就有多少个正态分布,而在生成的时候,我们就可以通过控制均值来控制生成图像的类别。

事实上,这样可能也是在 VAE 的基础上加入最少的代码来实现 CVAE 的方案了,因为这个“新希望”也只需通过修改 KL loss 实现:

L μ , σ 2 = 1 2 ∑ i = 1 d [ ( μ ( i ) − μ ( i ) Y ) 2 + σ ( i ) 2 − log ⁡ σ ( i ) 2 − 1 ] \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] Lμ,σ2=21i=1d[(μ(i)μ(i)Y)2+σ(i)2logσ(i)21]

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
  • 1
  • 2
  • 3
  • 4
  • 5

存疑

模型训练中,计算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)
  • 1
  • 2
  • 3
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/40975
推荐阅读
相关标签
  

闽ICP备14008679号