tensorflow代码全解析 -3- seq2seq 自动生成文本

模型概述

序列建模seq2seq,给定一个序列A,模型生产另一个序列B,然后模型再由序列B生成C,以此一直持续下去。

基本工作流程如下:
序列A中的每一个单词通过word_embedding操作以后,作为input进seq2seq入模型,模型生成同样维度的序列A_out
训练的时候,模型的输出序列A_out与序列B之间的交叉熵作为模型的目标函数,采用clip控制过的梯度进行收敛;
生成的时候,首先给定一个种子序列作为模型输入,初始化模型,让模型可以自循环,并生产指定长度的序列

模型数据流

自己手写的,凑合看下,注意向量和tensor列表之间的区别

第一部分:生成数据

数据源 JayLyrics.txt

dataGenerator.py
定义参数

log_dir = './logs'
seq_length = 20 # 每一个序列的长度
batch_size = 32 #每一个batch的长度
datafiles = 'JayLyrics.txt'

读取文本,进行词频统计,将文本与数字进行一一对应,方便进行处理

with open(datafiles, encoding='utf-8') as f:
    data = f.read()
total_len = len(data)
words = list(set(data))
words.sort()
vocab_size = len(words)
char2id_dict = {w: i for i, w in enumerate(words)}
id2char_dict = {i: w for i, w in enumerate(words)}

进行这一步之后可以得到

将上面的数据对应表进行存储,注意tsv与csv格式不同,是以tab进行分隔的

_pointer = 0
def char2id( c):
    return char2id_dict[c]

def id2char(id):
    return id2char_dict[id]

metadata = 'metadata2.tsv'
def save_metadata(file):
    with open(file, 'w') as f:
        f.write('id\tchar\n')
        for i in range(vocab_size):
            c = id2char(i)
            f.write('{}\t{}\n'.format(i, c))
save_metadata(metadata)

下面进行关键的将原始文本转换为输入数据序列和输入标签
我们可以看到,文本数据与训练数据集是如何进行转换的。

def next_batch():
    _pointer = 0
    x_batches = []
    y_batches = []
    for i in range(batch_size):
        if _pointer + seq_length + 1 >= total_len:
            _pointer = 0
        bx = data[_pointer: _pointer + seq_length]
        by = data[_pointer +
                       1: _pointer + seq_length + 1]
        _pointer += seq_length  # update pointer position

        # convert to idss
        bx = [char2id(c) for c in bx]
        by = [char2id(c) for c in by]
        x_batches.append(bx)
        y_batches.append(by)
    return x_batches, y_batches
data[_pointer: _pointer + seq_length]
Out[22]: '作词:黄俊郎 \n作曲:周杰伦\n编曲:黄雨'
# 以上是 bx
data[_pointer +1: _pointer + seq_length + 1]
Out[23]: '词:黄俊郎 \n作曲:周杰伦\n编曲:黄雨勛'
# 以上是 by

再将上面的数据转化为数字

eg = next_batch()
# 一个batch 就这样生成了
#应注意到输入数据是一个32*20维的数据矩阵
#输入标签同样是一个32*20维的数据矩阵

[char2id(c) for c in bx]


第二部分 构建模型逻辑

构建seq2seq模型

3层LSTMCell 每一层100个神经元

    state_size = 100
    num_layers = 3
    #定义神经网络
    cell = rnn_cell.BasicLSTMCell(state_size)
    cell = rnn_cell.MultiRNNCell([cell] * num_layers)
    # 神经元的初始状态,将神经元设置为全零状态
    initial_state = cell.zero_state(
        batch_size, tf.float32)

将输入数据传递的seq2seq模型中

采用tf.nn.dynamic_rnn
inputs: The RNN inputs.
Tensor of shape: [batch_size, max_time, ...]

dynamic_rnn 返回
outputs: The RNN output Tensor.
If time_major == False (default), this will be a Tensor shaped:
[batch_size, max_time, cell.output_size].
state: The final state. If cell.state_size is an int, this

will be shaped [batch_size, cell.state_size].

   with tf.variable_scope('rnnlm'):
        with tf.device("/cpu:0"):
            embedding = tf.get_variable(
                'embedding', [vocab_size, state_size])
            inputs = tf.nn.embedding_lookup(embedding, input_data)
    # 将输入数据传递的seq2seq模型中
    # 采用tf.nn.dynamic_rnn
    # inputs: The RNN inputs.
    # Tensor of shape: [batch_size, max_time, ...]
    outputs, last_state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state)

embedding input 的详细解释 下面是看到的比较好的解释

引用自http://blog.csdn.net/mydear_11000/article/details/52414342

我们预处理了数据之后得到的是一个二维array,每个位置的元素表示这个word在vocabulary中的index。但是传入graph的数据不能讲word用index来表示,这样词和词之间的关系就没法刻画了。我们需要将word用dense vector表示,这也就是广为人知的word embedding。paper中并没有使用预训练的word embedding,所有的embedding都是随机初始化,然后在训练过程中不断更新embedding矩阵的值。
123

with tf.device("/cpu:0"): 
    embedding = tf.get_variable("embedding", vocab_size, state_size]) 
    inputs = tf.nn.embedding_lookup(embedding, self._input_data)

首先要明确几点:
既然我们要在训练过程中不断更新embedding矩阵,那么embedding必须是tf.Variable并且trainable=True(default)
目前tensorflow对于lookup embedding的操作只能再cpu上进行
embedding矩阵的大小是多少:每个word都需要有对应的embedding vector,总共就是vocab_size那么多个embedding,每个word embed成多少维的vector呢?因为我们input embedding后的结果就直接输入给了第一层cell,刚才我们知道cell的hidden units size,因此这个embedding dim要和hidden units size对应上(这也才能和内部的各种门的W和b完美相乘)。因此,我们就确定下来
embedding matrix shape=[vocab_size, hidden_units_size]

最后生成真正的inputs节点,也就是从embedding_lookup之后得到的结果,这个tensor的shape=[batch_size, num_stemps, size]

第三部分 构建模型损失函数和收敛方法

先定义参数w(1002636)和b(2636)
再将output 展开成[-1
state_size]维即是[-1100]
再用(output
w)+b获得输出序列logits
下面便可以使用sequence_loss_by_example将模型输出logits和训练标签targets计算序列交叉熵

with tf.name_scope('model'):
    with tf.variable_scope('rnnlm'):
        w = tf.get_variable( 'softmax_w', [state_size, vocab_size])
        b = tf.get_variable('softmax_b', [vocab_size])

with tf.name_scope('loss'):
    output = tf.reshape(outputs, [-1, state_size])
    logits = tf.matmul(output, w) + b
    probs = tf.nn.softmax(logits)
    last_state = last_state

    targets = tf.reshape(target_data, [-1])
# 将targets 展平 维度从(32*20)转化为640
# pass '[-1]' to flatten 't'
#reshape(t, [-1]) ==> [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6]

    loss = seq2seq.sequence_loss_by_example([logits],
                                            [targets],
                                            [tf.ones_like(targets, dtype=tf.float32)])
    cost = tf.reduce_sum(loss) / batch_size
  #下面采用scalar 是用来在tensorboard 显示数据的
    tf.summary.scalar('loss', cost)

第四部分 优化器设定

首先 定义lr 是不可训练的
关键地方 clip_by_global_norm的作用
设置梯度的最大范数
Gradient Clipping的方法,控制梯度的最大范数。
可以防止梯度爆炸的问题,如果梯度不加限制,则可能因为迭代中梯度过大导致训练难以收敛。

optimizer.apply_gradients则将前面clip过的梯度应用到所有可以训练的tvars上

with tf.name_scope('optimize'):
    lr = tf.placeholder(tf.float32, [])
    tf.summary.scalar('learning_rate', lr)

    optimizer = tf.train.AdamOptimizer(lr)
  #获取全部可以训练的参数tvars
    tvars = tf.trainable_variables()
# 提前计算梯度
    grads = tf.gradients(cost, tvars)

#显示在tensorboard
    for g in grads:
        tf.summary.histogram(g.name, g)

    # 由它们的范数之和之比求多个张量的值
    grads, _ = tf.clip_by_global_norm(grads, grad_clip)
 # 将前面clip过的梯度应用到可训练的参数上
    train_op = optimizer.apply_gradients(zip(grads, tvars))
    merged_op = tf.summary.merge_all()

第五部分 训练

启动模型,设定tensorboard,导入数据,记录数据

with tf.Session() as sess:

    #启动模型
    sess.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    writer = tf.summary.FileWriter(log_dir, sess.graph)

    # 设定tensorboard
    # Add embedding tensorboard visualization. Need tensorflow version
    # >= 0.12.0RC0
    config = projector.ProjectorConfig()
    embed = config.embeddings.add()
    embed.tensor_name = 'rnnlm/embedding:0'
    embed.metadata_path = metadata
    projector.visualize_embeddings(writer, config)

# 导入数据
    max_iter = n_epoch * \
               (data.total_len // seq_length) // batch_size
    for i in range(max_iter):
        learning_rate = learning_rate * \
                        (decay_rate ** (i // decay_steps))
        x_batch, y_batch = data.next_batch()
        feed_dict = {model.input_data: x_batch,
                     model.target_data: y_batch, model.lr: learning_rate}
        train_loss, summary, _, _ = sess.run([model.cost, model.merged_op, model.last_state, model.train_op],
                                             feed_dict)
# 记录数据
        if i % 10 == 0:
            writer.add_summary(summary, global_step=i)
            print('Step:{}/{}, training_loss:{:4f}'.format(i,
                                                           max_iter, train_loss))
        if i % 2000 == 0 or (i + 1) == max_iter:
            saver.save(sess, os.path.join(
                log_dir, 'lyrics_model.ckpt'), global_step=i)

第五部分 生成文本

首先从保存的模型中取出参数,初始化模型
再设定种子
将种子进行处理
初始化模型
按照设定的生成数量生成文本

#首先从保存的模型中取出参数,初始化模型
saver = tf.train.Saver()
with tf.Session() as sess:
    ckpt = tf.train.latest_checkpoint(args.log_dir)
    print(ckpt)
    saver.restore(sess, ckpt)
    # 再设定种子
    # initial phrase to warm RNN
    prime = u'你要离开我知道很简单'
    state = sess.run(cell.zero_state(1, tf.float32))

 # 将种子进行处理
# 初始化模型
    for word in prime[:-1]:
        x = np.zeros((1, 1))
        x[0, 0] = char2id(word)
        feed = {input_data: x, initial_state: state}
        state = sess.run(last_state, feed)

# 按照设定的生成数量生成文本
    word = prime[-1]
    lyrics = prime
    for i in range(args.gen_num):
        x = np.zeros([1, 1])
        x[0, 0] = char2id(word)
        feed_dict = {input_data: x, initial_state: state}
        probs, state = sess.run([probs, last_state], feed_dict)
        p = probs[0]
        word = id2char(np.argmax(p))
        print(word, end='')
        sys.stdout.flush()
        time.sleep(0.05)
        lyrics += word
        return lyrics

代码还是很复杂很复杂的,看了好几天,还是有些不明白的,
有些只能等以后再慢慢专研,现在主干是抓住了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容