人工智能--基于LSTM的文本生成

Posted Abro.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了人工智能--基于LSTM的文本生成相关的知识,希望对你有一定的参考价值。

学习目标:

  1. 理解文本生成的基本原理。
  2. 掌握利用LSTM生成唐诗宋词的方法。

学习内容:

利用如下代码和100首经典宋词的数据,基于LSTM生成新的词,并调整网络参数,提高生成的效果。

poetry5000.txt数据集下载

poetry_song.txt数据集下载


 

学习过程:

网络结构:

Batch_size改为64,学习率改为0.001后,迭代十次:

输入四字造词:

Batch_size改为64,学习率改为0.0001后,迭代十次:

输入四字造词:

显然,把学习率改为0.0001后,LSTM模型生成宋词的效果要好很多。


源码:

# 基于LSTM的唐诗生成

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

# In[1]: 数据预处理

# 配置信息
class Config(object):
    #poetry_file = 'poetry.txt' # 数据量太大,导致GPU内存溢出
    poetry_file = 'poetry_song.txt'  # 换成宋词的数据做训练
    weight_file = 'poetry_song_model.h5' 
    
    max_len = 6  # 根据前六个字预测第七个字
    batch_size = 64 #改为128
    learning_rate = 0.001 #可改为0.0001
    
# 在构建LSTM网络之前,需要标识诗句的结束点、去掉数据集中低频的字和构建word到id的映射
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

# In[2]: 定义一个类,包含 生成训练数据,构建网络模型,训练网络,根据给出的文字生成诗句
import random
import os
import keras
import numpy as np
from keras.callbacks import LambdaCallback
from keras.models import Input, Model, load_model
from keras.layers import LSTM, Dropout, Dense, Embedding
from keras.optimizers import Adam

class PoetryModel(object):
    def __init__(self, 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
        
    
    # 生成训练数据,x表示输入,y表示输出。
    # 输入信息为6个字,输出信息为第1个字。
    # 比如“我想要吃香蕉啊”,输入即为“我想要吃香蕉”,输出为“啊”。
    # 之后将文字转换成向量的形式  
    def data_generator(self):
        i = 0
        # 需要注意的是,此处的生成器是一个while True的无限循环过程。
        # 当输入字段长度大于文本语料字段的长度时,下标已经超过语料的长度,
        # 因此在训练网络时要限制网络学习的循环次数。
        while True:
            x = self.files_content[i: i + self.config.max_len]
            y = self.files_content[i + self.config.max_len]
             
            # 给定前六个字,生成第七个字。跨度为6的句子中,前后每个字都是有关联的。
            # 如果出现了“]”“[”等符号,说明“]”符号之前的和之后的语句是没有关联的内容,分别属于两首不同的诗。
            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


    # 建立基于LSTM的神经网络模型
    def build_model(self):
        input_tensor = Input(shape=(self.config.max_len,))
        embedd = Embedding(len(self.num2word) + 2, 300, 
                           input_length=self.config.max_len)(input_tensor)
        lstm = LSTM(512, return_sequences=True)(embedd)

        #flatten = Flatten()(lstm)
        dropout = Dropout(0.6)(lstm)
        lstm = LSTM(256)(dropout)
        flatten = Dropout(0.6)(lstm)
        
        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'])
        self.model.summary()



    # 查看学习情况
    # 对每个测试样本(6个汉字序列),self.model.predict的预测结果preds是固定的
    # 但是,为了生成随机的结果,在多项式概率分布条件下再随机选择
    # preds的某个分量越高,被选择的概率越大,但不是每次都选分量最高对应的汉字。
    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):
        if not self.loaded_model:
            return
         
        # 随机选择一行,截取最后5个字,接上用户输入的第一个字,作为生成第一句中的第二个字的随机种子 
        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:
            # 生成下一个字的种子,包含6个字
            seed = seed[1:] + c
            #print(seed) 
            # 经过这个循环后,c将变为生成句子的第一个字,生成的一行句子一共有6个字
            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 train(self):
        # 训练模型
        number_of_epoch = 10 #100

        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=[
                keras.callbacks.ModelCheckpoint(self.config.weight_file, 
                                                save_weights_only=False),
                LambdaCallback(on_epoch_end=self.generate_sample_result)
            ]
        )
                
# In[3]: 主函数,程序入口
if __name__ == '__main__':
    # 构造网络对象。并且会判断:如果训练好的权重.h5文件存在则直接加载,否则开始训练
    model = PoetryModel(Config)
    text = input('请输入4个汉字以上(每个汉字为首作一行诗):')
    while len(text)>0:
        sentence = model.predict(text)
        print(sentence)
        text = input('请输入4个汉字以上(每个汉字为首作一行诗):')

源码下载 


学习产出:

  1. 造出来的词有点瑕疵,有时候造出来的宋词,在人看来不像是一首宋词。

以上是关于人工智能--基于LSTM的文本生成的主要内容,如果未能解决你的问题,请参考以下文章

人工智能--基于LSTM的文本挖掘

人工智能--基于LSTM的文本挖掘

基于LSTM模型实现文本生成

基于LSTM模型实现文本生成

使用经过训练的字符级 LSTM 模型生成文本

使用 RNN/LSTM 连续生成文本