中文自然语言处理--基于双向GRU 生成各种古诗

使用 LSTM 的变形 GRU 中文字符级建模,训练出一个能作诗的模型
数据集集为4000+古诗,一行一首,标题、作者已去掉:
https://download.csdn.net/download/fgg1234567890/16153813

import os
import random
import pandas as pd
import numpy as np
# 引入需要的模块
from keras.layers import Bidirectional
from keras.layers import GRU
import keras
from keras.callbacks import LambdaCallback
from sklearn.model_selection import train_test_split
from keras.layers import Dense, Input, Flatten, Dropout
from keras.layers import Embedding
from keras.models import Model
from keras.utils import plot_model
from keras.models import load_model
from keras.optimizers import Adam

# 步骤:
# 语料准备
# 语料预处理
# 模型参数配置
# 构建模型
# 训练模型
# 模型作诗
# 绘制模型网络结构图

puncs = [']', '[', '(', ')', '{', '}', ':', '《', '》']

# 预处理
def preprocess_file(Config):
    # 语料文本内容
    files_content = ''
    with open(Config.poetry_file, 'r', encoding='utf-8') as f:
        for line in f:
            # 每行的末尾加上"]"符号代表一首诗结束
            for char in puncs:
                line = line.replace(char, "")
            files_content += line.strip() + "]"

    words = sorted(list(files_content))
    words.remove(']')
    counted_words = {}
    for word in words:
        if word in counted_words:
            counted_words[word] += 1
        else:
            counted_words[word] = 1

    # 去掉低频的字
    erase = []
    for key in counted_words:
        if counted_words[key] <= 2:
            erase.append(key)
    for key in erase:
        del counted_words[key]
    del counted_words[']']
    wordPairs = sorted(counted_words.items(), key=lambda x: -x[1])

    words, _ = zip(*wordPairs)
    # word到id的映射
    word2num = dict((c, i+1) for i, c in enumerate(words))
    num2word = dict((i, c) for i, c in enumerate(words))
    word2numF = lambda x: word2num.get(x, 0)
    return word2numF, num2word, words, files_content

class Config(object):
    '''
    模型参数配置。预先定义模型参数和加载语料以及模型保存名称
    '''
    poetry_file = './qtais_tab2.txt'
    weight_file = 'poetry_model.h5'
    # 根据前六个字预测第七个字
    max_len = 6
    batch_size = 512
    learning_rate = 0.001

class PoetryModel(object):
    def __init__(self, config):
        '''
        init 函数定义,通过加载 Config 配置信息,进行语料预处理和模型加载,
        如果模型文件存在则直接加载模型,否则开始训练。
        '''
        self.model = None
        self.do_train = True
        self.loaded_model = False
        self.config = config

        # 文件预处理
        self.word2numF, self.num2word, self.words, self.files_content = preprocess_file(self.config)
        if os.path.exists(self.config.weight_file):
            self.model = load_model(self.config.weight_file)
            self.model.summary()
        else:
            self.train()
        self.do_train = False
        self.loaded_model = True

    def build_model(self):
        '''
        建立模型
        用 Keras 来构建网络模型,这里使用 LSTM 的 GRU 来实现,当然直接使用 LSTM 也没问题。
        '''
        input_tensor = Input(shape=(self.config.max_len,))
        embedd = Embedding(len(self.num2word)+1, 300, input_length=self.config.max_len)(input_tensor)
        lstm = Bidirectional(GRU(128, return_sequences=True))(embedd)
        dropout = Dropout(0.6)(lstm)
        # lstm = Bidirectional(GRU(128, return_sequences=True))(embedd)
        # dropout = Dropout(0.6)(lstm)
        flatten = Flatten()(dropout)
        dense = Dense(len(self.words), activation='softmax')(flatten)
        self.model = Model(inputs=input_tensor, outputs=dense)
        optimizer = Adam(lr=self.config.learning_rate)
        self.model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy'])

    def sample(self, preds, temperature=1.0):
        '''
        当temperature=1.0时,模型输出正常
        当temperature=0.5时,模型输出比较open
        当temperature=1.5时,模型输出比较保守
        在训练的过程中可以看到temperature不同,结果也不同
        '''
        preds = np.asarray(preds).astype('float64')
        preds = np.log(preds) / temperature
        exp_preds = np.exp(preds)
        preds = exp_preds / np.sum(exp_preds)
        probas = np.random.multinomial(1, preds, 1)
        return np.argmax(probas)

    def generate_sample_result(self, epoch, logs):
        '''
        训练过程中,每个 epoch 打印出当前的学习情况
        '''
        print("\n==================Epoch {}=====================".format(epoch))
        for diversity in [0.5, 1.0, 1.5]:
            print("------------Diversity {}--------------".format(diversity))
            start_index = random.randint(0, len(self.files_content) - self.config.max_len - 1)
            generated = ''
            sentence = self.files_content[start_index: start_index + self.config.max_len]
            generated += sentence
            for i in range(20):
                x_pred = np.zeros((1, self.config.max_len))
                for t, char in enumerate(sentence[-6:]):
                    x_pred[0, t] = self.word2numF(char)

                preds = self.model.predict(x_pred, verbose=0)[0]
                next_index = self.sample(preds, diversity)
                next_char = self.num2word[next_index]
                generated += next_char
                sentence = sentence + next_char
            print(sentence)

    def predict(self, text):
        '''
        根据给定的提示,来进行预测。
        根据给出的文字,生成诗句,如果给的 text 不到四个字,则随机补全。
        '''
        if not self.loaded_model:
            return
        with open(self.config.poetry_file, 'r', encoding='utf-8') as f:
            file_list = f.readlines()
        random_line = random.choice(file_list)
        # 如果给的text不到四个字,则随机补全
        if not text or len(text) != 4:
            for _ in range(4 - len(text)):
                random_str_index = random.randrange(0, len(self.words))
                text += self.num2word.get(random_str_index) if self.num2word.get(random_str_index) not in [',', '。',
                                                                                                           ','] else self.num2word.get(
                    random_str_index + 1)
        seed = random_line[-(self.config.max_len):-1]
        res = ''
        seed = 'c' + seed
        for c in text:
            seed = seed[1:] + c
            for j in range(5):
                x_pred = np.zeros((1, self.config.max_len))
                for t, char in enumerate(seed):
                    x_pred[0, t] = self.word2numF(char)
                preds = self.model.predict(x_pred, verbose=0)[0]
                next_index = self.sample(preds, 1.0)
                next_char = self.num2word[next_index]
                seed = seed[1:] + next_char
            res += seed
        return res

    def data_generator(self):
        '''
        用于生成数据,提供给模型训练时使用。
        给定前六个字,生成第七个字,所以在后面生成训练数据的时候,会以6的跨度,1的步长截取文字,生成语料。
        如果出现了 ] 符号,说明 ] 符号之前的语句和之后的语句是两首诗里面的内容,
        两首诗之间是没有关联关系的,所以我们后面会舍弃掉包含 ] 符号的训练数据。
        '''
        print("len(self.files_content):", len(self.files_content))
        i = 0
        while 1:
            x = self.files_content[i: i + self.config.max_len]
            y = self.files_content[i + self.config.max_len]
            puncs = [']', '[', '(', ')', '{', '}', ':', '《', '》', ':']
            if len([i for i in puncs if i in x]) != 0:
                i += 1
                continue
            if len([i for i in puncs if i in y]) != 0:
                i += 1
                continue
            y_vec = np.zeros(
                shape=(1, len(self.words)),
                dtype=np.bool
            )
            y_vec[0, self.word2numF(y)] = 1.0
            x_vec = np.zeros(
                shape=(1, self.config.max_len),
                dtype=np.int32
            )
            for t, char in enumerate(x):
                x_vec[0, t] = self.word2numF(char)
            yield x_vec, y_vec
            i += 1

    def train(self):
        '''
        用来进行模型训练。
        TensorFlow回调函数:tf.keras.callbacks.LambdaCallback:
        用于动态创建简单的自定义回调的回调。
        此回调是使用将在适当时间调用的匿名函数构造的。请注意,回调需要位置参数,如下所示:
        on_epoch_begin和on_epoch_end要求两个位置参数: epoch,logs
        on_batch_begin和on_batch_end要求两个位置参数: batch,logs
        on_train_begin并on_train_end要求一个位置参数: logs
        参数:
        on_epoch_begin:在每个epoch开始时调用。
        on_epoch_end:在每个epoch结束时调用。
        on_batch_begin:在每个批处理开始时调用。
        on_batch_end:在每个批处理结束时调用。
        on_train_begin:在模型训练开始时调用。
        on_train_end:在模型训练结束时调用。
        '''
        # number_of_epoch = len(self.files_content) // self.config.batch_size
        number_of_epoch = 10
        if not self.model:
            self.build_model()
        self.model.summary()
        self.model.fit_generator(
            generator=self.data_generator(),
            verbose=True,
            steps_per_epoch=self.config.batch_size,
            epochs=number_of_epoch,
            callbacks=[
                # 该回调函数将在每个epoch后保存模型到filepath
                keras.callbacks.ModelCheckpoint(self.config.weight_file, save_weights_only=False),
                LambdaCallback(on_epoch_end=self.generate_sample_result)])

model = PoetryModel(Config)

text = input("text:")
sentence = model.predict(text)
print(sentence)
# 绘制网络结构图
plot_model(model.model, to_file='model.png')

原文:
https://soyoger.blog.csdn.net/article/details/108729402

上一篇:surface book3屏幕背后发热问题解决(无法解决)


下一篇:吴恩达深度学习课程脉络(五)序列建模(更新ing)