手把手教你使用TensorFlow2实现RNN

1. 什么是RNN

Recurrent Neural Network(循环神经网络)是一种特殊的神经网络,与前馈神经网络或卷积神经网络不同的是它含有一个或多个循环层。因此它可以处理序列数据,例如文本、语音、时间序列等。RNN在处理时,在每个时间步都会收到一个输入和一个隐藏状态,输出与隐藏状态有关。隐藏状态除了作为输出,还会在下一个时间步作为输入传入下一层RNN,这样就实现了信息的“循环”,在处理序列数据时表现出很好的优势。

1.1 RNN结构

RNN结构可以用下面的图示表示:

其中,$x_t$是当前时间步的输入,$h_t$是当前时间步的隐藏状态,同时也是下一个时间步的输入。经过变换得到的向量$y_t$是当前时间步的输出。权重$W_{hh}$、$W_{hx}$、$W_{yh}$和偏置$b_h$、$b_y$是模型参数。

1.2 RNN原理

在一个简单的RNN中,隐藏状态$h_t$的计算方式为:

$$h_t=tanh(W_{hh}h_{t-1}+W_{hx}x_t+b_h)$$

其中$tanh$是双曲正切函数,将输入转换为[-1,1]之间的值,将非线性映射到了一条s曲线上。我们可以通过这种方式增加模型的拟合能力。若我们在最后一层加一个线性变换,就可以将输出映射到我们需要的维度上。

$$y_t=W_{yh}h_{t}+b_y$$

2. 用TensorFlow2实现RNN

接下来,我们就来看看如何用TensorFlow2实现一个简单的RNN,用于生成文本。我们先来看看模型的基本架构:

model = keras.Sequential([

keras.layers.SimpleRNN(units=64, input_shape=[None, char_size],

activation='tanh'),

keras.layers.Dense(char_size, activation='softmax')

])

这里我们使用了一个SimpleRNN层,将一个隐藏状态$h_t$在每个时间步传递给下一个时间步。我们使用Softmax作为输出层的激活函数,并且将模型的输入形状设置成[input_length,char_size],其中input_length是需要生成的文本的长度。

3. 文本预处理和处理数据集

在训练模型之前,我们需要将文本进行预处理并处理成能够用于训练的数据集。

3.1 文本预处理

在我们的文本预处理中,我们需要做以下几个步骤:

将所有大写字母转换为小写字母

删除所有非字母字符(包括标点符号和数字)

将所有行进行合并

将文本转换为字符集

def preprocess(text):

text = text.lower()

text = re.sub(r'[^a-z]', ' ', text)

text = ' '.join(text.split())

chars = set(text)

return text, chars

text, chars = preprocess(text)

char_size = len(chars)

# 生成文本和字符集的映射

char2idx = {char: idx for idx, char in enumerate(chars)}

idx2char = np.array(list(chars))

3.2 处理数据集

我们需要创建一个数据集,它包含了所有可用于学习的训练序列。我们将文本划分成N个序列,每个序列的长度为input_length+1,即模型的输入和输出都是一个字符偏移量大小的滑动窗口。这意味着,模型可以在窗口中提取序列,并预测下一个字符。

def prepare_data(text, input_length):

inputs = []

targets = []

for i in range(len(text) - input_length):

inputs.append(text[i:i+input_length])

targets.append(text[i+input_length])

inputs_onehot = np.array([text2onehot(seq) for seq in inputs], np.float32)

targets_idx = np.array([char2idx[char] for char in targets], np.int32)

dataset = tf.data.Dataset.from_tensor_slices((inputs_onehot, targets_idx))

dataset = dataset.shuffle(1000).batch(batch_size).repeat()

return dataset

def text2onehot(text):

onehot = np.zeros((len(text), char_size))

for idx, char in enumerate(text):

onehot[idx, char2idx[char]] = 1.0

return onehot

input_length = 100

batch_size = 128

dataset = prepare_data(text, input_length)

4. 训练模型并生成文本

现在可以开始训练模型并使用该模型生成新文本了。我们将使用负对数似然来作为损失函数(加权交叉熵会更好,但是我们以这种方式进行的简单RNN可能没有足够的表达能力),并且使用优化器Adam。

# 编译模型

model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')

# 训练模型

steps_per_epoch = len(text) // input_length // batch_size

history = model.fit(dataset, steps_per_epoch=steps_per_epoch, epochs=epochs)

# 使用模型生成文本

def generate_text(start_string, temperature=1.0):

generated = start_string

for i in range(num_generate):

# 将起始字符串编码为一个onehot向量

input_eval = [char2idx[char] for char in generated]

input_eval = np.expand_dims(text2onehot(generated)[-1], axis=0)

# 模型预测一下一个字符的概率分布

predictions = model.predict(input_eval)

predictions = predictions.squeeze().astype('float64')

# 使用温度调整给预测概率分布赋值

predictions = np.log(predictions) / temperature

predictions = np.exp(predictions) / np.sum(np.exp(predictions))

# 选择预测概率最高的字符

predicted_id = np.random.choice(len(predictions), p=predictions)

# 最后将该字符添加到生成文本中

generated += idx2char[predicted_id]

return generated

4.1 生成文本的相关参数

在生成新文本之前,可以先设置一些相关参数。

num_generate:生成文本的长度

start_string:可以任意设置一个起始字符串

temperature(温度):用于调整下一次字符的采样

num_generate = 1000

start_string = 'hello'

temperature = 0.6

generated_text = generate_text(start_string, temperature)

print(generated_text)

5. 总结

在本文中,我们介绍了什么是RNN并且使用TensorFlow2代码实现了一个简单的RNN,用于生成文本。我们通过设置预训练文本、文本预处理、数据集处理、训练模型和生成新文本等多个步骤讲解了模型的训练和使用方法。

正如您所看到的,训练模型和使用模型生成文本并不难。通过调整模型结构和训练参数,我们可以获得更好的效果,例如增加训练数据、增加训练轮数、增加训练样本等。

后端开发标签