基于RNN的中文古诗词生成神经网络实现

一、前言

1、图解RNN

2、Tensorflow中RNN实现的正确打开方式

二、实战

1、训练数据处理

(1)文字转为向量

    def _get_poetry(self):
        with open(self.poetry_file, "r", encoding='utf-8') as f:
            poetry_list = [line for line in f]
        return poetry_list

    def _gen_poetry_vectors(self):
        words = sorted(set(''.join(self.poetry_list)+' '))
        # 每一个字符分配一个索引 为后续诗词向量化做准备
        int_to_word = {i: word for i, word in enumerate(words)}
        word_to_int = {v: k for k, v in int_to_word.items()}
        to_int = lambda word: word_to_int.get(word)
        poetry_vectors = [list(map(to_int, poetry)) for poetry in self.poetry_list]
        return poetry_vectors, word_to_int, int_to_word
在这里将训练数据中所有的字生成了一个"文字==>数字"的词袋,并将所有的诗词按行分割转化为数字表示。

(2)生成器

    def batch(self):
        # 生成器
        start = 0
        end = self.batch_size
        for _ in range(self.chunk_size):
            batches = self.poetry_vectors[start:end]
            # 输入数据 按每块数据中诗句最大长度初始化数组,缺失数据补全
            x_batch = np.full((self.batch_size, max(map(len, batches))), self.word_to_int[' '], np.int32)
            for row in range(self.batch_size): x_batch[row, :len(batches[row])] = batches[row]
            # 标签数据 根据上一个字符预测下一个字符 所以这里y_batch数据应为x_batch数据向后移一位
            y_batch = np.copy(x_batch)
            y_batch[:, :-1], y_batch[:, -1] = x_batch[:, 1:], x_batch[:, 0]
            yield x_batch, y_batch
            start += self.batch_size
            end += self.batch_size
x_batch作为输入,y_batch为标签。诗词生成模型根据上一个字符生成下一个字符,所以这里的标签数据应该是和输入数据的shape一致,但序列字符后移一位。y_batch的最后一位,理论上来说应该是本行诗词的下一行的第一个字,简单起见,这里用本行的第一个字代替。

2、RNN模型

(1)模型初始化

class PoetryModel:

    def __init__(self):
        # 诗歌生成
        self.poetry = Poetry()
        # 单个cell训练序列个数
        self.batch_size = self.poetry.batch_size
        # 所有出现字符的数量
        self.word_len = len(self.poetry.word_to_int)
        # 隐层的数量
        self.rnn_size = 128
rnn_size代表RNN模型中隐层的数量,这个概念要和RNN模型中time_step的数量分开,这里time_step数量指的是每一行诗词的文字数量,即x。batch_size数量指每次训练时,使用的诗词行数。

(2)变量定义

        # 输入句子长短不一致 用None自适应
        inputs = tf.placeholder(tf.int32, shape=(self.batch_size, None), name='inputs')
        # 输出为预测某个字后续字符 故输出也不一致
        targets = tf.placeholder(tf.int32, shape=(self.batch_size, None), name='targets')
        # 防止过拟合
        keep_prob = tf.placeholder(tf.float32, name='keep_prob')
(3)embedding定义

    def embedding_variable(inputs, rnn_size, word_len):
        with tf.variable_scope('embedding'):
            # 这里选择使用cpu进行embedding
            with tf.device("/cpu:0"):
                # 默认使用'glorot_uniform_initializer'初始化,来自源码说明:
                # If initializer is `None` (the default), the default initializer passed in
                # the variable scope will be used. If that one is `None` too, a
                # `glorot_uniform_initializer` will be used.
                # 这里实际上是根据字符数量分别生成state_size长度的向量
                embedding = tf.get_variable('embedding', [word_len, rnn_size])
                # 根据inputs序列中每一个字符对应索引 在embedding中寻找对应向量,即字符转为连续向量:[字]==>[1]==>[0,1,0]
                lstm_inputs = tf.nn.embedding_lookup(embedding, inputs)
        return lstm_inputs
将诗词的文字对应索引分别转化为变量,引入另一个新概念,input_size,即每一个字所对应的变量长度,这里是训练数据中所有字符的数量,所以实际上我们每次训练使用的数据shape应该是(batch_size,time_step,input_size)。

(4)模型计算图定义

    def rnn_graph(self, batch_size, rnn_size, word_len, lstm_inputs, keep_prob):
        # cell.state_size ==> 128
        # 基础cell 也可以选择其他基本cell类型
        lstm = tf.nn.rnn_cell.BasicLSTMCell(num_units=rnn_size)
        drop = tf.nn.rnn_cell.DropoutWrapper(lstm, output_keep_prob=keep_prob)
        # 多层cell 前一层cell作为后一层cell的输入
        cell = tf.nn.rnn_cell.MultiRNNCell([drop] * 2)
        # 初始状态生成(h0) 默认为0
        # initial_state.shape ==> (64, 128)
        initial_state = cell.zero_state(batch_size, tf.float32)
        # 使用dynamic_rnn自动进行时间维度推进 且 可以使用不同长度的时间维度
        # 因为我们使用的句子长度不一致
        lstm_outputs, final_state = tf.nn.dynamic_rnn(cell, lstm_inputs, initial_state=initial_state)
        seq_output = tf.concat(lstm_outputs, 1)
        x = tf.reshape(seq_output, [-1, rnn_size])
        # softmax计算概率
        w, b = self.soft_max_variable(rnn_size, word_len)
        logits = tf.matmul(x, w) + b
        prediction = tf.nn.softmax(logits, name='predictions')
        return logits, prediction, initial_state, final_state
(5)权重及偏置定义

    def soft_max_variable(rnn_size, word_len):
        # 共享变量
        with tf.variable_scope('soft_max'):
            w = tf.get_variable("w", [rnn_size, word_len])
            b = tf.get_variable("b", [word_len])
        return w, b
RNN与CNN不同的一点在于,RNN的权重及偏置在所有cell中是一样的,这里使用了共享变量。
(6)损失及优化图定义

    @staticmethod
    def loss_graph(word_len, targets, logits):
        # 将y序列按序列值转为one_hot向量
        y_one_hot = tf.one_hot(targets, word_len)
        y_reshaped = tf.reshape(y_one_hot, [-1, word_len])
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y_reshaped))
        return loss

    @staticmethod
    def optimizer_graph(loss, learning_rate):
        grad_clip = 5
        # 使用clipping gradients
        tvars = tf.trainable_variables()
        grads, _ = tf.clip_by_global_norm(tf.gradients(loss, tvars), grad_clip)
        train_op = tf.train.AdamOptimizer(learning_rate)
        optimizer = train_op.apply_gradients(zip(grads, tvars))
        return optimizer

RNN会遇到梯度爆炸(gradients exploding)梯度弥散(gradients disappearing)的问题。LSTM解决了梯度弥散的问题,但是gradients仍然可能会爆炸,因此我们采用gradient clippling的方式来防止梯度爆炸。即通过设置一个阈值,当gradients超过这个阈值时,就将它重置为阈值大小,这就保证了梯度不会变得很大。

(7)开始训练

        # 开始训练
        saver = tf.train.Saver()
        sess = tf.Session()
        sess.run(tf.global_variables_initializer())
        step = 0
        new_state = sess.run(initial_state)
        for i in range(epoch):
            # 训练数据生成器
            batches = self.poetry.batch()
            # 随模型进行训练 降低学习率
            sess.run(tf.assign(learning_rate, 0.001 * (0.97 ** i)))
            for batch_x, batch_y in batches:
                feed = {inputs: batch_x, targets: batch_y, initial_state: new_state, keep_prob: 0.5}
                batch_loss, _, new_state = sess.run([loss, optimizer, final_state], feed_dict=feed)
                print(datetime.datetime.now().strftime('%c'), ' i:', i, 'step:', step, ' batch_loss:', batch_loss)
                step += 1
        model_path = os.getcwd() + os.sep + "poetry.model"
        saver.save(sess, model_path, global_step=step)
        sess.close()
随着模型的训练,逐步降低学习率。
(8)生成古诗词
    def gen(self, poem_len):
        def to_word(weights):
            t = np.cumsum(weights)
            s = np.sum(weights)
            sample = int(np.searchsorted(t, np.random.rand(1) * s))
            return self.poetry.int_to_word[sample]

        # 输入
        # 句子长短不一致 用None自适应
        self.batch_size = 1
        inputs = tf.placeholder(tf.int32, shape=(self.batch_size, 1), name='inputs')
        # 防止过拟合
        keep_prob = tf.placeholder(tf.float32, name='keep_prob')
        lstm_inputs = self.embedding_variable(inputs, self.rnn_size, self.word_len)
        # rnn模型
        _, prediction, initial_state, final_state = self.rnn_graph(self.batch_size, self.rnn_size, self.word_len, lstm_inputs, keep_prob)

        saver = tf.train.Saver()
        with tf.Session() as sess:
            sess.run(tf.global_variables_initializer())
            saver.restore(sess, tf.train.latest_checkpoint('.'))
            new_state = sess.run(initial_state)

            # 在所有字中随机选择一个作为开始
            x = np.zeros((1, 1))
            x[0, 0] = self.poetry.word_to_int[self.poetry.int_to_word[random.randint(1, self.word_len-1)]]
            feed = {inputs: x, initial_state: new_state, keep_prob: 1}

            predict, new_state = sess.run([prediction, final_state], feed_dict=feed)
            word = to_word(predict)
            poem = ''
            while len(poem) < poem_len:
                poem += word
                x = np.zeros((1, 1))
                x[0, 0] = self.poetry.word_to_int[word]
                feed = {inputs: x, initial_state: new_state, keep_prob: 1}
                predict, new_state = sess.run([prediction, final_state], feed_dict=feed)
                word = to_word(predict)
            return poem
在sep-sep模型中,上一个网络的状态输出应该作为下一个网络的状态输入,所以初始状态为零,后续输入状态都是上一次的输出状态。

(9)结果

筋烛升玄净,云光理片溪。
美子相离处,离来下路疏。
策变因坚薛,灵行一更颇。
卖上狎城天,穿洄欹笔软。
授名时依寂,快举即芳眉。
吊此之当主,长期动世迟。
看起来有模有样的,基本模型是成功了。在这个模型的基础上进行改动,还可以生成小说等。

三、其他

具体源码可以在我的github上找到: https://github.com/lpty/tensorflow_tutorial






  • 15
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
### 回答1: 好的,在设计并实现古诗词生成模型之前,我们需要先了解一些基本的知识。 首先,我们需要了解古诗词的基本结构和规则,包括它们的格律(例如五言绝句和七言律诗)、押韵方式(例如平仄和韵脚)和语言特点(例如修辞手法和笔画)。 其次,我们需要准备一个古诗词语料库,其中包含足够多的古诗词样本,这样我们就可以通过训练模型来学习这些样本的特点,并使用这些信息来生成新的古诗词。 接下来,我们可以使用机器学习技术来训练古诗词生成模型。常用的方法包括使用循环神经网络RNN)、自动编码器(AE)或注意力机制(AM)。在训练过程中,我们可以使用带有标签的古诗词样本来训练模型,让模型学习语言模型和格律规则。 最后,我们可以使用训练好的模型来生成新的古诗词。为了生成质量较高的古诗词,我们可以使用一些技巧来控制生成过程,例如设置 ### 回答2: 设计并实现古诗词生成模型是一项复杂而有挑战性的任务,需要综合运用自然语言处理技术和机器学习方法。下面我将简要介绍一种可能的实现方法。 首先,我们需要建立一个庞大的古诗词语料库,包含大量的古诗词样本。这些样本可以从古代诗词文集、古代文学作品或者在线资源中获取。 接下来,我们可以使用分词技术将每首古诗词切分成一个个词语,并建立一个词语表。词语表将作为我们模型的输入。 基于词语表,我们可以构建一个基于循环神经网络RNN)的生成模型。在这个模型中,RNN将学习古诗词的语法和韵律特点。我们可以采用LSTM(长短期记忆)作为RNN模型的一种常用变体,来捕捉长期的依赖关系。 为了训练这个生成模型,我们可以使用一种叫做“Teacher Forcing”的技术。即将每个时间步的输入设置为目标输出的前一个时间步,以增加训练的稳定性和速度。 在模型训练完成后,我们可以使用这个生成模型来自动生成古诗词。我们可以输入一些初始词语或者句子,然后通过模型得到下一个词语的概率分布。根据该分布,我们可以使用采样方法来选择下一个词语,不断重复这个过程,直到生成一个完整的古诗词。 当然,为了提高生成古诗词的质量,我们还可以采用一些技巧。例如,可以引入注意力机制来更好地捕捉句子中不同词语之间的依赖关系。还可以通过引入一些约束条件,例如韵律、句法和意境要求,来筛选生成古诗词。 总之,设计并实现古诗词生成模型是一项具有挑战性的任务,需要综合应用自然语言处理和机器学习技术。通过建立庞大的古诗词语料库和使用RNN模型,我们可以实现古诗词的自动生成,并通过一些技巧来提高生成质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值