第十一章 文本生成

11.1 文本生成的原理

前面学习了文本的分类,那么如何产生新的文本?
把文本生成问题看成一个预测问题,例如我们有很多图片,我们训练了一个神经网络来对图片进行分类,本质上来说,它就是对图片像素所表示的内容进行预测。例如判断一张图片是猫还是狗,
文本预测和之前例子非常相似,我们从句子中抽取单词,然后创建一个数据集,将句子中的短语定义为X,而将下一个单词定义为Y,例如X为twinkle twinkle little ,Y为star。当神经网络看到X时就会预测Y,
因此,当有一个足够大的语料库时,神经网络能够对其中的短语进行训练,并预测对应的下一个单词,这样就可以产生一系列复杂的文本。
具体实现如下:
数据集为语段爱尔兰歌词:
第十一章 文本生成_第1张图片
代码的开头部分:

tokenizer = Tokenizer()
data = "In the town of Athy one Jeremy Lanigan \n Battered away ......."#将整首歌词放入data中,\n换行
corpus = data.lower().split("\n")#然后对\n调用split函数,对data创建一个python的列表,并将句子的内容都转化为小写,最终的内容将作为语料库corpus。
tokenizer.fit_on_texts(corpus)#使用分词器的fit_on_texts方法,针对这首歌创建词典,词典中的内容是一组键值对,其中关键字为单词,值为单词的编码。
total_words =len(tokenizer.word_index) +1#统计词典中单词的数量,考虑到未登录词,设定语料库中词汇的总量为单词的数量加1.

将语料库转化为训练数据

input_sequences = [] #将输入数据input_sequences定义为一个列表
for line in corpus:
	token_list = tokenizer.texts_to_sequences([line])[0] #使用texts_to_sequences方法,对语料库的每一行进行序列化,
	for i in range(1,len(token_list)):#迭代这个列表,对每个句子产生不同长度的序列,
	n_gram_sequence = token_list[:i+1]
	input_sequences.append(n_gram_sequence)

第十一章 文本生成_第2张图片
不同长度的序列:
第十一章 文本生成_第3张图片
针对当前序列,首先取前两个单词的编码,然后取前三个单词的编码,以此类推。

#找到语料库中最长句子的长度。

max_sequence_len = max([len(x) for x in input_sequences])
#遍历整个语料库,找到最长的序列长度。

#根据最长的序列长度,来填充所有序列,使他们长度相同。在句子前面填充0.

input_sequences = np.array(pad_sequences(input_sequences,maxlen=max_sequence_len,padding='pre'))

效果如下:
第十一章 文本生成_第4张图片
接下来,将序列转化为神经网络的输入X和标签Y,可以将除最后一个字符以外的所有字符作为输入X,而将最后一个字符作为标签Y。
第十一章 文本生成_第5张图片
前面字符都是输入标签X,最后一个字符是Y。

通过以下代码来产生神经网络的输入和标签。

xs = input_sequences[:,:-1] #将input_sequences中,除最后一个编码以外的序列作为输入xs
labels = input_sequences[:,-1] #将input_sequences中最后一个编码作为标签labels

对标签进行独热编码:
之所以进行独热编码,是因为我们将单词的预测问题,转化成了分类问题。需要根据给定的单词序列,来预测下一个单词,而每一个单词对应语料库中的一个类别,采用keras内置的函数,来实现独热编码。

ys = tf.keras.utils.to_categorical(labels,num_classes = total_words)

效果如下:
第十一章 文本生成_第6张图片

11.2 循环神经网络设计

创建一个神经网络,再进行学习和预测:

model = Sequential()
model.add(Embedding(total_words,64,input_length=max_sequence_len - 1))#嵌入层,设置嵌入层需要处理的单词数量,为语料库中所有单词。嵌入维度为64,输入序列的长度为最大序列长度减1(这是因为序列的最后一个单词是标签,所以输入序列的长度比最大序列长度小1
model.add(LSTM(20))#添加一个单向的LSTM层,LSTM的参数代表它的cell state所处理的上下文长度。20个单词
model.add(Dense(total_words,activation='softmax'))#全连接层,其结点的数量为语料库中所有单词的数量(因为使用了独热编码,因此每一个单词对应了一个神经元节点)。当预测某一个单词时,该单词对应的神经元就会被激活,
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accurcay'])#设置损失函数为分类交叉熵,并设置优化器为adam,
model.fit(xs,ys,epochs=500,verbose=1)#设置神经网络的训练,将训练周期设置为500.模型数据比较少,需要较长的周期才能训练。

网络的准确度曲线:
第十一章 文本生成_第7张图片
第十一章 文本生成_第8张图片
让他预测接下来产生的十个单词,以上就是神经网络产生的句子,但是有很多单词是重复的,这是因为单向的LSTM层只能够从前面的单词推断出后面的单词,所以可以将LSTM层改成双向的。如下所示:
双向的LSTM层:

model = Sequential()
model.add(Embedding(total_words,64,input_length=max_sequence_len - 1))#嵌入层,设置嵌入层需要处理的单词数量,为语料库中所有单词。嵌入维度为64,输入序列的长度为最大序列长度减1(这是因为序列的最后一个单词是标签,所以输入序列的长度比最大序列长度小1
model.add(Bidirectional(LSTM(20)))#添加一个双向的LSTM层,LSTM的参数代表它的cell state所处理的上下文长度。20个单词
model.add(Dense(total_words,activation='softmax'))#全连接层,其结点的数量为语料库中所有单词的数量(因为使用了独热编码,因此每一个单词对应了一个神经元节点)。当预测某一个单词时,该单词对应的神经元就会被激活,
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accurcay'])#设置损失函数为分类交叉熵,并设置优化器为adam,
model.fit(xs,ys,epochs=500,verbose=1)#设置神经网络的训练,将训练周期设置为500.模型数据比较少,需要较长的周期才能训练。

网络的准确度曲线:
第十一章 文本生成_第9张图片
第十一章 文本生成_第10张图片
输出开始有了一些具体的含义,但是仍然存在着单词的重复,因为这是一首押韵的歌词,所以有个别单词出现的频率比较高。

Laurence went to dublin这个句子怎么生成的?
将这句话定义成seed_text,如果我想预测这句话接下来的10个单词,首先将这句话转成序列:

token_list = tokenizer.texts_to_sequences([seed_text])[0]

使用pad_squences进行句子填充,使它的长度和训练集中的句子长度一致:

token_list = pad_sequences([token_list],maxlen =max_sequence_len -1 , padding='pre')

序列化结果如下:
第十一章 文本生成_第11张图片
将它传递给神经网络模型,得到预测结果:

predicted = model.predict_classes(token_list,verbose=0)

预测的是当前句子最有可能的下一个单词的索引。

接着我们对输出的单词索引进行反向的查询:

for word,index in tokenizer.word_index.items():
	if index == predicted:
		output_word = word
		break
seed_text +=" " +output_word

完整的代码:

seed_text = "Laurence went to dublin"
next_words = 10
  
for _ in range(next_words):
	token_list = tokenizer.texts_to_sequences([seed_text])[0]
	token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre')
	predicted = model.predict_classes(token_list, verbose=0)
	output_word = ""
	for word, index in tokenizer.word_index.items():
		if index == predicted:
			output_word = word
			break
	seed_text += " " + output_word
print(seed_text)

预测的次数越多,可能结果就越来越不准确。

循环神经网络设计2:
采用一首歌词进行训练:

import tensorflow as tf

from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.layers import Embedding, LSTM, Dense, Bidirectional
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam
import numpy as np 
import time

首先将歌词中的数据进行词条化,data就是歌词,用\n对数据进行分段,

#print(time.time())
tokenizer = Tokenizer()

data="In the town of Athy one Jeremy Lanigan \n Battered away til he hadnt a pound. \nHis father died and made him a man again \n Left him a farm and ten acres of ground. \nHe gave a grand party for friends and relations \nWho didnt forget him when come to the wall, \nAnd if youll but listen Ill make your eyes glisten \nOf the rows and the ructions of Lanigans Ball. \nMyself to be sure got free invitation, \nFor all the nice girls and boys I might ask, \nAnd just in a minute both friends and relations \nWere dancing round merry as bees round a cask. \nJudy ODaly, that nice little milliner, \nShe tipped me a wink for to give her a call, \nAnd I soon arrived with Peggy McGilligan \nJust in time for Lanigans Ball. \nThere were lashings of punch and wine for the ladies, \nPotatoes and cakes; there was bacon and tea, \nThere were the Nolans, Dolans, OGradys \nCourting the girls and dancing away. \nSongs they went round as plenty as water, \nThe harp that once sounded in Taras old hall,\nSweet Nelly Gray and The Rat Catchers Daughter,\nAll singing together at Lanigans Ball. \nThey were doing all kinds of nonsensical polkas \nAll round the room in a whirligig. \nJulia and I, we banished their nonsense \nAnd tipped them the twist of a reel and a jig. \nAch mavrone, how the girls got all mad at me \nDanced til youd think the ceiling would fall. \nFor I spent three weeks at Brooks Academy \nLearning new steps for Lanigans Ball. \nThree long weeks I spent up in Dublin, \nThree long weeks to learn nothing at all,\n Three long weeks I spent up in Dublin, \nLearning new steps for Lanigans Ball. \nShe stepped out and I stepped in again, \nI stepped out and she stepped in again, \nShe stepped out and I stepped in again, \nLearning new steps for Lanigans Ball. \nBoys were all merry and the girls they were hearty \nAnd danced all around in couples and groups, \nTil an accident happened, young Terrance McCarthy \nPut his right leg through miss Finnertys hoops. \nPoor creature fainted and cried Meelia murther, \nCalled for her brothers and gathered them all. \nCarmody swore that hed go no further \nTil he had satisfaction at Lanigans Ball. \nIn the midst of the row miss Kerrigan fainted, \nHer cheeks at the same time as red as a rose. \nSome of the lads declared she was painted, \nShe took a small drop too much, I suppose. \nHer sweetheart, Ned Morgan, so powerful and able, \nWhen he saw his fair colleen stretched out by the wall, \nTore the left leg from under the table \nAnd smashed all the Chaneys at Lanigans Ball. \nBoys, oh boys, twas then there were runctions. \nMyself got a lick from big Phelim McHugh. \nI soon replied to his introduction \nAnd kicked up a terrible hullabaloo. \nOld Casey, the piper, was near being strangled. \nThey squeezed up his pipes, bellows, chanters and all. \nThe girls, in their ribbons, they got all entangled \nAnd that put an end to Lanigans Ball."

corpus = data.lower().split("\n")

tokenizer.fit_on_texts(corpus)#根据\n分割的数据,使用fit_on_texts方法针对语料库创建的词典。
total_words = len(tokenizer.word_index) + 1

print(tokenizer.word_index)
print(total_words)

运行结果:
第十一章 文本生成_第12张图片
语料库中独立单词的数量是263个。

创建神经网络的输入序列:


input_sequences = []
for line in corpus:
	token_list = tokenizer.texts_to_sequences([line])[0]
	for i in range(1, len(token_list)):
		n_gram_sequence = token_list[:i+1]
		input_sequences.append(n_gram_sequence)

# pad sequences 
max_sequence_len = max([len(x) for x in input_sequences])
input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding='pre'))

# create predictors and label
xs, labels = input_sequences[:,:-1],input_sequences[:,-1]

ys = tf.keras.utils.to_categorical(labels, num_classes=total_words)

查看语料库中每个句子的组成,

print(tokenizer.word_index['in'])
print(tokenizer.word_index['the'])
print(tokenizer.word_index['town'])
print(tokenizer.word_index['of'])
print(tokenizer.word_index['athy'])
print(tokenizer.word_index['one'])
print(tokenizer.word_index['jeremy'])
print(tokenizer.word_index['lanigan'])

结果如下:
第十一章 文本生成_第13张图片
最重要的单词lanigan编码是70。

#查看创建的输入数据,
print(xs[6])

编码分别是[ 0 0 0 4 2 66 8 67 68 69]

#查看数据的标签
print(ys[6])

第十一章 文本生成_第14张图片
在这里标签是70,使用了独热编码对其进行编码,1是列表中的第70个元素。

#再来查看5的这一条数据
print(xs[5])
print(ys[5])

输入是4,2,66,8,67,68
输出是独热编码中的第69个元素
第十一章 文本生成_第15张图片
接下来建立一个神经网络模型来训练数据:

 model = Sequential()
model.add(Embedding(total_words,64,input_length=max_sequence_len - 1))#嵌入层,设置嵌入层需要处理的单词数量,为语料库中所有单词。嵌入维度为64,输入序列的长度为最大序列长度减1(这是因为序列的最后一个单词是标签,所以输入序列的长度比最大序列长度小1
model.add(Bidirectional(LSTM(20)))#添加一个双向的LSTM层,LSTM的参数代表它的cell state所处理的上下文长度。20个单词
model.add(Dense(total_words,activation='softmax'))#全连接层,其结点的数量为语料库中所有单词的数量(因为使用了独热编码,因此每一个单词对应了一个神经元节点)。当预测某一个单词时,该单词对应的神经元就会被激活,激活函数为softmax
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accurcay'])#设置损失函数为分类交叉熵,并设置优化器为adam,
history = model.fit(xs,ys,epochs=500,verbose=1)#设置神经网络的训练,将训练周期设置为500.模型数据比较少,需要较长的周期才能训练。

训练结果:
第十一章 文本生成_第16张图片
我们可以看到开始的训练准确率比较低,这是因为没有太多的数据,但是每个周期的训练很快,同时随着训练的进行,准确率在逐渐的增加。
第十一章 文本生成_第17张图片
最终的训练准确率大概为94.48%,

下面画一下准确率的曲线,代码如下:

import matplotlib.pyplot as plt


def plot_graphs(history, string):
  plt.plot(history.history[string])
  plt.xlabel("Epochs")
  plt.ylabel(string)
  plt.show()
plot_graphs(history, 'accuracy')

准确率曲线:
第十一章 文本生成_第18张图片
我们可以看到大概在200代左右就基本达到了目前的准确率水平,所以不用训练500代。

接下来用训练好的模型进行预测:

seed_text = "Laurence went to dublin"
next_words = 100
  
for _ in range(next_words):
	token_list = tokenizer.texts_to_sequences([seed_text])[0]
	token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre')
	predicted = model.predict_classes(token_list, verbose=0)
	output_word = ""
	for word, index in tokenizer.word_index.items():
		if index == predicted:
			output_word = word
			break
	seed_text += " " + output_word
print(seed_text) 
#print(time.time())

预测结果如下:
第十一章 文本生成_第19张图片
所有的输出结果都是从训练中得到的。

11.3 项目实战–生成优美的诗歌

本次实训是一个包含1692个句子的歌词文件,放在了C盘中tmp中,

import tensorflow as tf

from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.layers import Embedding, LSTM, Dense, Bidirectional
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam
import numpy as np 
import time
tokenizer = Tokenizer()
data = open('C:/tmp/archive/irish-lyrics-eof.txt').read()

corpus = data.lower().split("\n")

tokenizer.fit_on_texts(corpus)
total_words = len(tokenizer.word_index) + 1

print(tokenizer.word_index)
print(total_words)


input_sequences = []
for line in corpus:
	token_list = tokenizer.texts_to_sequences([line])[0]
	for i in range(1, len(token_list)):
		n_gram_sequence = token_list[:i+1]
		input_sequences.append(n_gram_sequence)

# pad sequences 
max_sequence_len = max([len(x) for x in input_sequences])
input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding='pre'))

# create predictors and label
xs, labels = input_sequences[:,:-1],input_sequences[:,-1]

ys = tf.keras.utils.to_categorical(labels, num_classes=total_words)
print(tokenizer.word_index['in'])
print(tokenizer.word_index['the'])
print(tokenizer.word_index['town'])
print(tokenizer.word_index['of'])
print(tokenizer.word_index['athy'])
print(tokenizer.word_index['one'])
print(tokenizer.word_index['jeremy'])
print(tokenizer.word_index['lanigan'])
print(xs[6])
print(ys[6])
print(xs[5])
print(ys[5])
print(tokenizer.word_index)

#t调整了神经网络的模型参数,使其更加适应大型的语料库训练

model = Sequential()
model.add(Embedding(total_words, 100, input_length=max_sequence_len-1))#嵌入维度调整成了100
model.add(Bidirectional(LSTM(150)))#LSTM的参数为150
model.add(Dense(total_words, activation='softmax'))
adam = Adam(lr=0.01)#调整优化器的类型,不再使用默认的Adam,而是设置了Adam优化器的学习速率。
model.compile(loss='categorical_crossentropy', optimizer=adam, metrics=['accuracy'])
#earlystop = EarlyStopping(monitor='val_loss', min_delta=0, patience=5, verbose=0, mode='auto')
history = model.fit(xs, ys, epochs=100, verbose=1)#设置训练周期为100
#print model.summary()
print(model)

import matplotlib.pyplot as plt


def plot_graphs(history, string):
  plt.plot(history.history[string])
  plt.xlabel("Epochs")
  plt.ylabel(string)
  plt.show()
plot_graphs(history, 'accuracy')

seed_text = "I've got a bad feeling about this"
next_words = 100
  
for _ in range(next_words):
	token_list = tokenizer.texts_to_sequences([seed_text])[0]
	token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre')
	predicted = model.predict_classes(token_list, verbose=0)
	output_word = ""
	for word, index in tokenizer.word_index.items():
		if index == predicted:
			output_word = word
			break
	seed_text += " " + output_word
print(seed_text)
print(time.time())

当有足够大的语料库时,这种方法非常有效.

语料库素材练习链接:https://www.tensorflow.org/text/tutorials/text_generation

你可能感兴趣的:(人工智能,tensorflow,分类)