Tensorflow2.0之用循环神经网络生成周杰伦歌词


Tensorflow2.0之从零开始实现循环神经网络中,我们介绍了循环神经网络的原理以及如何使用低级API来实现循环神经网络,在这篇文章中,我们将使用 tensorflow2.0 来更简洁地实现循环神经网络。
在这里,我们将实现一个循环神经网络来自动生成周杰伦歌词。代码中的数据集加载以及采样函数在 Tensorflow2.0之语言模型数据集(周杰伦专辑歌词)预处理中已经介绍过了,在这里不再赘述。

1、导入需要的库

import tensorflow as tf
from tensorflow import keras
import numpy as np
import zipfile
import math

2、加载数据集

def load_data_jay_lyrics():
    """加载周杰伦歌词数据集"""
    with zipfile.ZipFile('./jaychou_lyrics.txt.zip') as zin:
        with zin.open('jaychou_lyrics.txt') as f:
            corpus_chars = f.read().decode('utf-8')
    corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
    corpus_chars = corpus_chars[0:10000]
    idx_to_char = list(set(corpus_chars))
    char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
    vocab_size = len(char_to_idx)
    corpus_indices = [char_to_idx[char] for char in corpus_chars]
    return corpus_indices, char_to_idx, idx_to_char, vocab_size

(corpus_indices, char_to_idx, idx_to_char, vocab_size) = load_data_jay_lyrics()

3、相邻采样

def data_iter_consecutive(corpus_indices, batch_size, num_steps, ctx=None):
    corpus_indices = np.array(corpus_indices)
    data_len = len(corpus_indices)
    batch_len = data_len // batch_size
    indices = corpus_indices[0: batch_size*batch_len].reshape((
        batch_size, batch_len))
    epoch_size = (batch_len - 1) // num_steps
    for i in range(epoch_size):
        i = i * num_steps
        X = indices[:, i: i + num_steps]
        Y = indices[:, i + 1: i + num_steps + 1]
        yield X, Y

4、定义模型

4.1 定义循环神经网络层

Keras 的 Rnn 模块提供了循环神经网络的实现。下面构造一个含单隐藏层、隐藏单元个数为256的循环神经网络层 rnn_layer,并对权重做初始化。

num_hiddens = 256
cell = keras.layers.SimpleRNNCell(num_hiddens, 
                                  kernel_initializer='glorot_uniform')
rnn_layer = keras.layers.RNN(cell,time_major=True,
                            return_sequences=True,return_state=True)

其中,rnn_layer 的输入形状为 (时间步数, 批量大小, 词典大小),在前向计算后会分别返回输出和隐藏状态。

其中输出指的是隐藏层在各个时间步上计算并输出的隐藏状态,它们通常作为后续输出层的输入。需要强调的是,该“输出”本身并不涉及输出层计算,形状为 (时间步数, 批量大小, 隐藏单元个数)

返回的隐藏状态指的是隐藏层在最后时间步的隐藏状态:当隐藏层有多层时,每一层的隐藏状态都会记录在该变量中。

举例来说:
我们要先初始化隐藏状态

batch_size = 2
state = rnn_layer.cell.get_initial_state(batch_size=batch_size,dtype=tf.float32)
state.shape
TensorShape([2, 256])

可见隐藏状态的形状为 (批量大小, 隐藏单元个数)

num_steps = 35
X = tf.random.uniform(shape=(num_steps, batch_size, vocab_size))
print(X.shape)
Y, state_new = rnn_layer(X, state)
print(Y.shape)
print(len(state_new))
print(state_new[0].shape)
(35, 2, 1027)
(35, 2, 256)
2
(256,)

4.2 定义循环神经网络

class RNNModel(tf.keras.Model):
    def __init__(self, rnn_layer, vocab_size):
        super().__init__()
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.dense = keras.layers.Dense(vocab_size)
        
    def call(self, inputs, state):
        # 将输入转置成(num_steps, batch_size)后获取one-hot向量表示
        X = tf.one_hot(tf.transpose(inputs), self.vocab_size)
        Y, state = self.rnn(X, state)
        # 全连接层会首先将Y的形状变成(num_steps * batch_size, num_hiddens),
        # 它的输出形状为(num_steps * batch_size, vocab_size)
        output = self.dense(tf.reshape(Y, (-1, Y.shape[-1])))
        return output, state
    
    def get_initial_state(self, *args, **kwargs):
        return self.rnn.cell.get_initial_state(*args, **kwargs)

model = RNNModel(rnn_layer, vocab_size)

将经过采样后的样本直接输入此网络中时,它经过的处理操作依次为:

  • 输入形状为 (批量大小, 时间步数)
  • 将输入转置成 (时间步数, 批量大小)
  • 利用独热编码得到 (时间步数, 批量大小, 词典大小)
  • 输入到 rnn 层,得到 (时间步数, 批量大小, 隐藏单元个数)
  • reshape(时间步数x批量大小, 隐藏单元个数)
  • 经过 Dense 层,得到 (时间步数x批量大小, 词典大小)

5、定义预测函数

def predict_rnn_keras(prefix, num_chars):
    # 使用model的成员函数来初始化隐藏状态
    state = model.get_initial_state(batch_size=1,dtype=tf.float32)
    output = [char_to_idx[prefix[0]]]
    for t in range(num_chars + len(prefix) - 1):
        X = np.array([output[-1]]).reshape((1, 1))
        Y, state = model(X, state)  # 前向计算不需要传入模型参数
        if t < len(prefix) - 1:
            output.append(char_to_idx[prefix[t + 1]])
        else:
            output.append(int(np.array(tf.argmax(Y,axis=-1))))

    return ''.join([idx_to_char[i] for i in output])

其中,num_chars 表示要预测多少个字符。
我们可以先试验一下:

predict_rnn_keras('分开', 10)

得到:

扫描二维码关注公众号,回复: 10045363 查看本文章
'分开缸池升脑霜陪身纳梦旋'

因为模型参数为随机值,所以预测结果也是随机的。

6、裁剪梯度

循环神经网络中较容易出现梯度衰减或梯度爆炸。为了应对梯度爆炸,我们可以裁剪梯度(clip gradient)。假设我们把所有模型参数梯度的元素拼接成一个向量 g \boldsymbol{g} ,并设裁剪的阈值是 θ \theta 。裁剪后的梯度

min ( θ g , 1 ) g \min\left(\frac{\theta}{|\boldsymbol{g}|}, 1\right)\boldsymbol{g}

L 2 L_2 范数不超过 θ \theta

# 计算裁剪后的梯度
def grad_clipping(grads,theta):
    norm = np.array([0])
    for i in range(len(grads)):
        norm+=tf.math.reduce_sum(grads[i] ** 2)
    norm = np.sqrt(norm).item()
    new_gradient=[]
    if norm > theta:
        for grad in grads:
            new_gradient.append(grad * theta / norm)
    else:
        for grad in grads:
            new_gradient.append(grad)  
    return new_gradient

7、定义模型训练函数

7.1 困惑度

我们通常使用困惑度(perplexity)来评价语言模型的好坏。困惑度是对交叉熵损失函数做指数运算后得到的值。特别地,

  • 最佳情况下,模型总是把标签类别的概率预测为1,此时困惑度为1;
  • 最坏情况下,模型总是把标签类别的概率预测为0,此时困惑度为正无穷;
  • 基线情况下,模型总是预测所有类别的概率都相同,此时困惑度为类别个数。

显然,任何一个有效模型的困惑度必须小于类别个数。在本例中,困惑度必须小于词典大小vocab_size。

7.2 初始化优化器

lr = 1e2
optimizer=tf.keras.optimizers.SGD(learning_rate=lr)

7.3 定义梯度下降函数

def train_step(X, state, Y, clipping_theta=1e-2):
    with tf.GradientTape(persistent=True) as tape:
        (outputs, state) = model(X, state)
        y = Y.T.reshape((-1,))
        l = loss_object(y,outputs)

    grads = tape.gradient(l, model.variables)
    # 梯度裁剪
    grads=grad_clipping(grads, clipping_theta)
    optimizer.apply_gradients(zip(grads, model.variables))  # 因为已经误差取过均值,梯度不用再做平均
    return l, y

7.4 定义训练函数

  • num_epochs:训练次数;
  • batch_size:批次大小;
  • pred_period:间隔多少次展示一次结果;
  • pred_len:要求预测的字符长度。
def train_and_predict_rnn_keras(num_epochs, batch_size, pred_period, pred_len, prefixes):
    
    for epoch in range(num_epochs):
        l_sum, n = 0.0, 0
        data_iter = data_iter_consecutive(
            corpus_indices, batch_size, num_steps)
        state = model.get_initial_state(batch_size=batch_size,dtype=tf.float32)
        for X, Y in data_iter:
            l, y = train_step(X, state, Y)
            l_sum += np.array(l).item() * len(y)
            n += len(y)

        if (epoch + 1) % pred_period == 0:
            print('epoch %d, perplexity %f' % (
                epoch + 1, math.exp(l_sum / n)))
            for prefix in prefixes:
                print(' -', predict_rnn_keras(prefix, pred_len))

7.5 训练

num_epochs, batch_size = 250, 32
pred_period, pred_len, prefixes = 50, 50, ['分开', '不分开']
train_and_predict_rnn_keras(num_epochs, batch_size, pred_period,
                            pred_len, prefixes)
发布了124 篇原创文章 · 获赞 16 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_36758914/article/details/105007428
今日推荐