1. 程式人生 > >【NLP】【十一】基於RNN和tf.keras 實現文字生成

【NLP】【十一】基於RNN和tf.keras 實現文字生成

【一】宣告

本文源自TensorFlow官方指導(https://tensorflow.google.cn/tutorials/sequences/text_generation),增加了部分細節說明。

【二】綜述

1. tf.keras與keras有如下三個較大的不同點

1):opt必須是tf.train模組下的opt,不能是keras下的opt

2):tf.keras模型預設的儲存格式時check-point,不是h5.

3):  tf.keras進行模型訓練、推理時,input_data可以直接傳遞tf.data.Dataset

2. TensorFlow的官方案例,是依據字元進行文字生成的,基本流程是,給定一個句子,預測其下一個字元。因此該模型不知道單詞是怎麼拼寫的,不知道字成詞,因為他是字元級的,它只知道預測下一個字元。因此,可能生成了不存在的單詞或者詞語。

3. 該模型只有三層(char-embedding、GRU、FC),但引數巨大,訓練十分緩慢(i7CPU訓練一個epoch差不多半個小時)。而且這裡,char-embedding是直接訓練出來了,而不是通過fasttext或者gensim訓練出來,然後在做fine-tuning的。

【三】程式碼如下:

# -*- coding:utf-8 -*-
import tensorflow as tf
import numpy as np
import os
import time

tf.enable_eager_execution()
# 1. 資料下載
path = tf.keras.utils.get_file('shakespeare.txt',
                                'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')

#2. 資料預處理
with open(path) as f:
    # text 是個字串
    text = f.read()
# 3. 將組成文字的字元全部提取出來,注意 vocab是個list
vocab = sorted(set(text))

# 4. 建立text-->int的對映關係
char2idx = {u:i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)
text_as_int = np.array([char2idx[c] for c in text])

# 5. 借用dataset的batch方法,將text劃分為定長的句子
seq_length = 100
examples_per_epoch = len(text)//seq_length
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
# 這裡batch_size 加1的原因在於,下面對inputs和labels的生成。labels比inputs多一個字元
sequences = char_dataset.batch(seq_length+1, drop_remainder=True)
# 6. 將每個句子劃分為inputs和labels。例如:hello,inputs = hell,label=ello
def split_input_target(chunk):
    input_text = chunk[:-1]
    target_text = chunk[1:]
    return input_text, target_text
dataset = sequences.map(split_input_target)

# 7. 將句子劃分為一個個batch
BATCH_SIZE = 64
steps_per_epoch = examples_per_epoch//BATCH_SIZE
BUFFER_SIZE = 10000
# drop_remainder 一般需要設定為true,表示當最後一組資料不夠劃分為一個batch時,將這組資料丟棄
dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)

# 8. 模型搭建
# Length of the vocabulary in chars
vocab_size = len(vocab)
# The embedding dimension
embedding_dim = 256
# Number of RNN units
rnn_units = 1024
model = tf.keras.Sequential()
# 這裡是字元embedding,所以是字符集大小*embedding_dim
model.add(tf.keras.layers.Embedding(input_dim=vocab_size,output_dim=embedding_dim,
                                    batch_input_shape=[BATCH_SIZE,None]))
model.add(tf.keras.layers.GRU(units=rnn_units,
                              return_sequences=True,
                              recurrent_initializer='glorot_uniform',
                              stateful=True))
model.add(tf.keras.layers.Dense(units=vocab_size))

model.summary()

# 9. 模型配置
# optimizer 必須為 tf.train 下的opt,不能是keras下的opt
model.compile(optimizer=tf.train.AdamOptimizer(),loss=tf.losses.sparse_softmax_cross_entropy)

# 10 .設定回撥函式
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt_{epoch}")
checkpoint_callback=tf.keras.callbacks.ModelCheckpoint(
    filepath=checkpoint_prefix,
    save_weights_only=True)

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir='./logs/')

# 11. 訓練模型,repeat() 表示dataset無限迴圈,不然這裡資料可能不夠30個epochs
model.fit(dataset.repeat(),epochs=30,
          steps_per_epoch=steps_per_epoch,
          callbacks=[checkpoint_callback,tensorboard_callback])

# 12 .模型儲存
# 儲存為keras模型格式
model.save_weights(filepath='./models/gen_text_with_char_on_rnn.h5',save_format='h5')
# 儲存為TensorFlow的格式
model.save_weights(filepath='./models/gen_text_with_char_on_rnn_check_point')

# 13. 模型生成文字
def generate_text(model, start_string):
    # Evaluation step (generating text using the learned model)

    # Number of characters to generate
    num_generate = 1000

    # You can change the start string to experiment
    start_string = 'ROMEO'

    # Converting our start string to numbers (vectorizing)
    input_eval = [char2idx[s] for s in start_string]
    input_eval = tf.expand_dims(input_eval, 0)

    # Empty string to store our results
    text_generated = []

    # Low temperatures results in more predictable text.
    # Higher temperatures results in more surprising text.
    # Experiment to find the best setting.
    temperature = 1.0

    # Here batch size == 1
    model.reset_states()
    for i in range(num_generate):
        predictions = model(input_eval)
        # remove the batch dimension
        predictions = tf.squeeze(predictions, 0)

        # using a multinomial distribution to predict the word returned by the model
        predictions = predictions / temperature
        predicted_id = tf.multinomial(predictions, num_samples=1)[-1, 0].numpy()

        # We pass the predicted word as the next input to the model
        # along with the previous hidden state
        input_eval = tf.expand_dims([predicted_id], 0)

        text_generated.append(idx2char[predicted_id])

    return (start_string + ''.join(text_generated))

print(generate_text(model, start_string="ROMEO: "))



【四】總結

1. 關於tf.keras更多的內容,可以參考官方網站(https://tensorflow.google.cn/guide/keras)

2. 關於tf.dataset的更多內容,可以參考官方網站(https://tensorflow.google.cn/guide/datasets)和另外一篇部落格(https://my.oschina.net/u/3800567/blog/1637798)

3. 可以完全使用tf.keras,不再使用keras。二者功能與介面一致,tf.keras提供了更多的對TensorFlow的支援