【AI with ML】第 8 章 :使用 TensorFlow 创建文本

       🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎

📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

​​

 🖍foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟👋

文章目录

将序列转换为输入序列

创建模型

生成文本

预测下一个词

复合预测以生成文本

扩展数据集

更改模型架构

改进数据

基于字符的编码

概括


  • You know nothing, Jon Snow
  • the place where he’s stationed

  • be it Cork or in the blue bird’s son

  • sailed out to summer

  • old sweet long and gladness rings

  • so i’ll wait for the wild colleen dying

这段文本是由一个在小型语料库上训练的非常简单的模型生成的。我通过添加换行符和标点符号对其进行了一些增强,但除了第一行,其余的都是由您将在本章中学习如何构建的模型生成的。提到野生科琳的死有点酷——如果你看过琼恩·雪诺出身的节目,你就会明白为什么!

在最后几章中,您了解了如何将 TensorFlow 用于基于文本的数据,首先将其标记为可以由神经网络处理的数字和序列,然后使用嵌入来使用向量模拟情绪,最后使用深度循环神经网络网络对文本进行分类。我们使用 Sarcasm 数据集,一个小而简单的数据集来说明这一切是如何工作的。在本章我们将换档:不是对现有文本进行分类,而是创建一个神经网络,它可以 预测文本。给定一个文本语料库,它会尝试理解其中的单词模式,这样它就可以,给定一段名为 seed,预测接下来应该出现什么词。一旦它有了,种子和预测的单词就变成了新的种子,并且可以预测下一个单词。因此,当在文本语料库上训练时,神经网络可以尝试以类似的风格编写新文本。为了创作上面的这首诗,我从一些传统的爱尔兰歌曲中收集了歌词,用它们训练了一个神经网络,并用它来预测单词。

我们将从简单开始,使用少量文本来说明如何构建预测模型,最后我们将创建包含更多文本的完整模型。之后你可以尝试一下,看看它能创作出什么样的诗篇!

开始之前,您必须以与目前为止所做的稍有不同的方式来处理文本。在前面的章节中,您将句子转换为序列,然后根据其中标记的嵌入对这些序列进行分类。

在创建可用于训练像这样的预测模型的数据时,还有一个额外的步骤,需要将序列转换为输入序列标签,其中输入序列是一组单词,标签是句子中的下一个词。然后,您可以训练模型以将输入序列与其标签相匹配,以便未来的预测可以选择接近输入序列的标签。

将序列转换为输入序列

什么时候预测文本,您需要使用具有关联标签的输入序列(特征)来训练神经网络。将序列与标签匹配是预测文本的关键。

因此,例如,如果在您的语料库中有一句话“今天有美丽的蓝天”,您可以将其拆分为“今天有美丽的蓝色”作为特征和“天空”作为标签。然后,如果你要预测文本“今天有美丽的蓝色”,它很可能是“天空”。如果在训练数据中你也有“昨天有美丽的蓝天”,以同样的方式分割,你要得到对文本“明天会有美丽的蓝天”的预测,那么下一个很可能是单词将是“天空”。

给定大量句子,训练以下一个单词为标签的单词序列,你可以快速建立一个预测模型,其中可以从现有的文本正文中预测句子中最有可能的下一个词。

我们将从一个非常小的文本语料库开始——摘自 1860 年代的一首传统爱尔兰歌曲,其中一些歌词如下:

  • In the town of Athy one Jeremy Lanigan

  • Battered away til he hadnt a pound.

  • His father died and made him a man again

  • Left him a farm and ten acres of ground.

  • He gave a grand party for friends and relations

  • Who didnt forget him when come to the wall,

  • And if youll but listen Ill make your eyes glisten

  • Of the rows and the ructions of Lanigan’s Ball.

  • Myself to be sure got free invitation,

  • For all the nice girls and boys I might ask,

  • And just in a minute both friends and relations

  • Were dancing round merry as bees round a cask.

  • Judy ODaly, that nice little milliner,

  • She tipped me a wink for to give her a call,

  • And I soon arrived with Peggy McGilligan

  • Just in time for Lanigans Ball.

创建一个包含所有文本的字符串,并将其设置为您的数据。用于\n换行符。然后可以像这样轻松加载和标记这个语料库:

tokenizer = Tokenizer()

data="In the town of Athy one Jeremy Lanigan \n Battered away ... ..."
corpus = data.lower().split("\n")

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

这个过程的结果是用它们的标记值替换单词,如图 8-1所示。

图 8-1。标记一个句子

为了训练预测模型,我们应该在这里采取进一步的步骤——将句子分成多个更小的序列,例如,我们可以有一个序列由前两个标记组成,另一个序列由前三个标记组成,等等。(图 8 -2)。

图 8-2。将一个序列转换为多个输入序列

 为此,您需要遍历语料库中的每一行并将其转换为使用的标记列表 texts_to_sequences. 然后,您可以通过遍历每个标记并列出所有标记来拆分每个列表。

这是代码:

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)

print(input_sequences[:5])

一旦你有了这些输入序列,你就可以将它们填充成规则的形状。我们将使用预填充(图 8-3)。

图 8-3。填充输入序列

为此,您需要在输入序列中找到最长的句子,并将所有内容填充到该长度。这是代码:

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'))

最后,一旦有了一组填充输入序列,就可以将它们拆分为特征和标签,其中标签只是输入序列中的最后一个标记(图 8-4)。

图 8-4。将填充序列转换为特征 (x) 和标签 (y)

训练神经网络时,您需要将每个特征与其对应的标签相匹配。因此,例如,[0 0 0 0 4 2 66 8 67 68 69] 的标签将是 [70]。

下面是将标签与输入序列分开的代码:

xs, labels = input_sequences[:,:-1],input_sequences[:,-1]

接下来,您需要对标签。现在它们只是标记——例如,图 8-4顶部的数字 2 。但是如果你想在分类器中使用一个标记作为标签,它必须被映射到一个输出神经元。因此,如果你要对n 个词进行分类,每个词都是一个类,你需要有n 个神经元。这就是控制词汇量很重要的地方,因为你拥有的单词越多,你需要的类就越多。记得回到第2章和第3章当您使用 Fashion MNIST 数据集对时尚商品进行分类时,您有 10 种类型的服装?这要求你在输出层中有 10 个神经元。在这种情况下,如果你想预测最多 10,000 个词汇怎么办?您需要一个包含 10,000 个神经元的输出层!

此外,您需要one-hot 对你的标签进行编码,使它们与神经网络的所需输出相匹配。考虑图 8-4。如果向神经网络输入由一系列 0 后跟一个 4 组成的输入 X,您会希望预测为 2,但网络如何传递它是通过具有 vocabulary_size 神经元的输出层,其中第二个神经元概率最高。

要将您的标签编码为一组 Y,然后您可以使用它来训练,您可以使用该to_categorical实用程序tf.keras

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

您可以在图 8-5中直观地看到这一点。

图 8-5。One-hot编码标签

这是一个非常稀疏的表示,如果你有很多训练数据和很多潜在的词,它会很快吃掉内存!假设您有 100,000 个训练句子,词汇量为 10,000 个单词——您需要 1,000,000,000 个字节来保存标签!但如果我们要对单词进行分类和预测,这就是我们必须设计网络的方式。

创建模型

让我们现在创建一个可以使用此输入数据进行训练的简单模型。它将仅包含一个嵌入层,然后是一个 LSTM,然后是一个致密层。

对于嵌入,您需要每个词一个向量,因此参数将是词的总数和您要嵌入的维数。在这种情况下,我们没有太多的词,所以八个维度应该足够了。

您可以使 LSTM 成为双向的,步数可以是序列的长度,即我们的最大长度减 1(因为我们从末尾取了一个标记来制作标签)。

最后,输出层将是一个密集层,以总词数为参数,由 softmax 激活。该层中的每个神经元将是下一个单词与该索引值的单词匹配的概率:

model = Sequential()
model.add(Embedding(total_words, 8))
model.add(Bidirectional(LSTM(max_sequence_len-1)))
model.add(Dense(total_words, activation='softmax'))

使用分类损失函数(如分类交叉熵)和像 Adam 这样的优化器编译模型。您还可以指定要捕获指标:

model.compile(loss='categorical_crossentropy', 
               optimizer='adam', metrics=['accuracy'])

这是一个非常简单的模型,没有大量数据,因此您可以训练很长时间——比如 1,500 个时期:

history = model.fit(xs, ys, epochs=1500, verbose=1)

在 1,500 个 epoch 之后,你会发现它已经达到了非常高的准确率(图 8-6)。

图 8-6。训练准确率

有了大约 95% 准确率的模型,我们可以确信,如果我们有一个它已经看到的文本字符串,它将在大约 95% 的时间内准确预测下一个单词。但是请注意,在生成文本时,它会不断地看到它以前没有看到过的单词,所以尽管这个数字很好,你会发现网络很快就会生成无意义的文本。我们将在下一节中对此进行探讨。

生成文本

现在您已经训练了一个可以预测序列中下一个单词的网络,下一步是给它一个文本序列并让它预测下一个单词。让我们来看看如何做到这一点。

预测下一个词

你会首先创建一个称为种子文本的短语。这是网络将基于其生成的所有内容的初始表达式。它将通过预测下一个词来做到这一点。

以网络已经看到的短语“in the town of athy”开头:

seed_text = "in the town of athy"

 接下来,您需要使用 texts_to_sequences. 这将返回一个数组,即使只有一个值,所以取该数组中的第一个元素:

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

然后您需要填充该序列以使其与用于训练的数据具有相同的形状:

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

现在您可以通过调用预测此标记列表的下一个单词 model.predict在令牌列表上。这将返回语料库中每个单词的概率,因此将结果传递给 np.argmax得到最有可能的:

predicted = np.argmax(model.predict(token_list), axis=-1)
print(predicted)

这应该给你价值68。如果您查看单词索引,您会发现这是单词“one”:

'town': 66, 'athy': 67, 'one': 68, 'jeremy': 69, 'lanigan': 70,

您可以通过搜索单词索引项在代码中查找它,直到找到predicted并打印出来:

for word, index in tokenizer.word_index.items():
    if index == predicted:
        print(word)
        break

因此,从文本“in the town of athy”开始,网络预测下一个单词应该是“one”——如果你查看训练数据,这是正确的,因为歌曲以以下行开头:

  • In the town of Athy one Jeremy Lanigan

  • Battered away til he hadnt a pound

现在您已经确认模型正在运行,您可以发挥创意并使用不同的种子文本。例如,当我使用种子文本“sweet jeremy saw dublin”时,它预测的下一个词是“then”。(选择此文本是因为所有这些词都在语料库中。对于这种情况下的预测词,您应该期望得到更准确的结果,至少在开始时是这样。)

复合预测以生成文本

在上一节中,您了解了如何使用模型预测给定种子文本的下一个单词。要让神经网络现在创建新文本,您只需重复预测,每次都添加新词。

例如,早些时候当我使用短语“sweet jeremy saw dublin”时,它预测下一个词是“then”。您可以在此基础上通过将“then”附加到种子文本以获得“sweet jeremy saw dublin then”并获得另一个预测。重复此过程将为您提供 AI 创建的文本字符串。

这是上一节中多次执行此循环的更新代码,次数由 next_words范围:

seed_text = "sweet jeremy saw 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)

这将最终创建一个像这样的字符串:

sweet jeremy saw dublin then got there as me me a call doing me

它迅速陷入胡言乱语。为什么?第一个原因是训练文本的主体非常小,因此可以使用的上下文很少。第二个是序列中下一个词的预测取决于序列中的前一个词,如果前一个词匹配不佳,即使是最好的“下一个”匹配的概率也很低。当你将它添加到序列中并预测其后的下一个单词时,它具有低概率的可能性甚至更高——因此,预测的单词将看起来是半随机的。

因此,例如,虽然短语“sweet jeremy saw dublin”中的所有单词都存在于语料库中,但它们从来没有按这个顺序存在。当第一次预测完成时,“then”这个词被选为最有可能的候选者,它的概率相当高 (89%)。当它被添加到种子中以获得“sweet jeremy saw dublin then”时,我们有另一个在训练数据中没有看到的短语,因此预测给出了“得到”这个词的最高概率,为 44%。继续向句子中添加单词会降低训练数据中匹配的可能性,因此预测准确性会受到影响——导致对被预测的单词产生更随机的“感觉”。

随着时间的推移,这导致人工智能生成的内容变得越来越荒谬的现象。例如,查看优秀的科幻短片Sunspring,它完全由基于 LSTM 的网络编写,就像您在这里构建的网络一样,受过科幻电影剧本的训练。该模型被赋予了种子内容,并负责生成新脚本。结果很搞笑,你会发现虽然最初的内容很有意义,但随着电影的进展,它变得越来越难以理解。

扩展数据集

您用于硬编码数据集的相同模式可以扩展为非常简单地使用文本文件。我托管了一个文本文件,其中包含大约 1,700 行文本,这些文本是从您可以用于实验的大量歌曲中收集而来的。稍微修改一下,您就可以使用它来代替单一的硬编码歌曲。

要在 Colab 中下载数据,请使用以下代码:

!wget --no-check-certificate \
    https://storage.googleapis.com/laurencemoroney-blog.appspot.com/ \
    irish-lyrics-eof.txt-O /tmp/irish-lyrics-eof.txt

然后你可以像这样简单地将文本从它加载到你的语料库中:

data = open('/tmp/irish-lyrics-eof.txt').read()
corpus = data.lower().split("\n")

您的其余代码将无需修改即可工作!

对其进行一千个时期的训练可以使您获得大约 60% 的准确度,并且曲线变平(图 8-7)。

图 8-7。在更大的数据集上训练

再次尝试“in the town of athy”这个短语会得到“one”的预测,但这次的概率只有 40%。

对于“sweet jeremy saw dublin”,预测的下一个词是“drawn”,概率为 59%。预测接下来的 10 个单词会产生:

sweet jeremy saw dublin drawn and fondly i am dead and the parting graceful

它看起来好多了!但是我们可以进一步改进它吗?

更改模型架构

改进模型的方法是使用多个堆叠 LSTM 来更改其架构。这非常简单 - 只需确保您设置return_sequencesTrue其中的第一个。这是代码:

model = Sequential()
model.add(Embedding(total_words, 8))
model.add(Bidirectional(LSTM(max_sequence_len-1, return_sequences='True')))
model.add(Bidirectional(LSTM(max_sequence_len-1)))
model.add(Dense(total_words, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', 
			  metrics=['accuracy'])
history = model.fit(xs, ys, epochs=1000, verbose=1)

您可以在图 8-8中看到这对训练一千个时期的影响。它与之前的曲线没有显着差异。

图 8-8。添加第二个 LSTM 层

 当使用与之前相同的短语进行测试时,这次我以 51% 的概率得到了“in the town of athy”之后的“more”作为下一个词,而在“sweet jeremy saw dublin”之后我得到了“cailín”(盖尔语“女孩”一词)的概率为 61%。同样,当预测更多单词时,输出很快就会变成乱码。

这里有些例子:

sweet jeremy saw dublin cailín loo ra fountain plundering that fulfill
you mccarthy you mccarthy down

you know nothing jon snow johnny cease and she danced that put to smother well
i must the wind flowers
dreams it love to laid ned the mossy and night i weirs

如果您得到不同的结果,请不要担心——您没有做错任何事情,但神经元的随机初始化会影响最终分数。

改进数据

您可以使用一个小技巧来扩展此数据集的大小而无需添加任何新歌曲,称为 开窗数据。现在,每首歌曲中的每一行都作为一行读取,然后转换为输入序列,如图 8-2 所示。虽然人类逐行阅读歌曲是为了听到押韵和韵律,但模型不必这样做,尤其是在使用双向 LSTM 时。

因此,与其采用“在 Athy 镇,一个 Jeremy Lanigan”这一行,而不是处理它,然后移动到下一行(“Battered away till he hadn't a pound”)并处理它,我们可以处理所有这些行是一个长的、连续的文本。然后,我们可以创建一个包含n 个单词的文本的“窗口”,对其进行处理,然后将窗口向前移动一个单词以获得下一个输入序列(图 8-9)。

图 8-9。一个移动的单词窗口

 在这种情况下,可以以增加输入序列数量的形式产生更多的训练数据。在整个文本语料库中移动窗口将为我们提供 (( number_of_words – window_size ) × window_size ) 个我们可以用来训练的输入序列。

代码非常简单——在加载数据时,我们可以根据语料库中的单词动态创建它们,而不是将每首歌的歌词分割成一个“句子”:

window_size=10
sentences=[]
alltext=[]
data = open('/tmp/irish-lyrics-eof.txt').read()
corpus = data.lower()
words = corpus.split(" ")
range_size = len(words)-max_sequence_len
for i in range(0, range_size):
    thissentence=""
    for word in range(0, window_size-1):
        word = words[i+word]
        thissentence = thissentence + word
        thissentence = thissentence + " "
    sentences.append(thissentence)

在这种情况下,因为我们不再有句子而且我们正在创建与移动窗口,max_sequence_len就是窗口的大小。读取完整文件,将其转换为小写,然后使用字符串拆分将其拆分为单词数组。然后代码循环遍历单词并从当前索引到当前索引加上窗口大小的每个单词造句,将每个新构造的句子添加到句子数组中。

训练时,您会注意到额外的数据使每个时期的速度慢得多,但结果得到了极大的改善,并且生成的文本变得乱码的速度也慢得多。

这是一个引起我注意的例子——尤其是最后一行!

  • you know nothing, jon snow is gone

  • and the young and the rose and wide

  • to where my love i will play

  • the heart of the kerry

  • the wall i watched a neat little town

有许多您可以尝试调整超参数。改变窗口大小会改变训练数据的数量——较小的窗口大小可以产生更多的数据,但是给标签的单词会更少,所以如果你把它设置得太小,你最终会得到无意义的诗歌。您还可以更改嵌入的维度、LSTM 的数量或用于训练的词汇的大小。鉴于百分比准确度并不是最好的衡量标准——你会想要对诗歌的“意义”进行更主观的检查——没有硬性规定可用于确定你的模型是否“好”或不。

例如,当我尝试使用 6 的窗口大小时,将嵌入的维数增加到 16,将 LSTM 的数量从窗口大小(应该是 6)更改为 32,并提高 Adam 的学习率优化器,我得到了一条漂亮、平滑的学习曲线(图 8-10),一些诗歌开始变得更有意义了。

图 8-10。调整超参数的学习曲线

当使用“sweet jeremy saw dublin”作为种子时(记住,种子中的所有词都在语料库中),我得到了这首诗:

  • sweet jeremy saw dublin

  • whack fol

  • all the watch came

  • and if ever you love get up from the stool

  • longs to go as i was passing my aged father

  • if you can visit new ross

  • gallant words i shall make

  • such powr of her goods

  • and her gear

  • and her calico blouse

  • she began the one night

  • rain from the morning so early

  • oer railroad ties and crossings

  • i made my weary way

  • through swamps and elevations

  • my tired feet

  • was the good heavens

虽然“whack fol”这个短语对很多读者来说可能没有意义,但它在一些爱尔兰歌曲中很常见,有点像“la la la”或“doobie-doobie-doo”。我真正喜欢的是后面的一些短语如何保持某种意义,比如“她的物品和装备以及她的印花布衬衫的力量”——但这可能是由于过度拟合已经存在的短语语料库中的歌曲。例如,从“oer railroad ties...”到“my tired feet”的句子直接取自语料库中一首名为“The Lakes of Pontchartrain”的歌曲。如果您遇到这样的问题,最好降低学习率并减少 LSTM 的数量。但最重要的是,尝试并享受乐趣!

基于字符的编码

为了前几章我们一直在研究使用基于词的编码的 NLP。我发现这更容易上手,但在生成文本时,您可能还需要考虑基于字符的编码,因为语料库中唯一字符的数量往往比唯一单词的数量少得多。因此,您的输出层中可以有更少的神经元,并且您的输出预测分布在更少的概率上。例如,在查看莎士比亚全集的数据集时,您会看到整个集合中只有 65 个不同的字符。因此,当您进行预测时,您不会像在爱尔兰歌曲数据集中那样查看 2,700 个单词中下一个单词的概率,而是只查看 65 个单词。这会使您的模型更简单一些!

字符编码的另一个好处是标点字符也包括在内,因此可以预测换行符等。例如,当我使用在莎士比亚语料库上训练的 RNN 来预测我最喜欢的《权力的游戏》台词之后的文本时,我得到:

  • YGRITTE:

  • You know nothing, Jon Snow.

  • Good night, we’ll prove those body’s servants to

  • The traitor be these mine:

  • So diswarl his body in hope in this resceins,

  • I cannot judg appeal’t.

  • MENENIUS:

  • Why, ’tis pompetsion.

  • KING RICHARD II:

  • I think he make her thought on mine;

  • She will not: suffer up thy bonds:

  • How doched it, I pray the gott,

  • We’ll no fame to this your love, and you were ends

 她认定他是叛徒并想把他绑起来(“驱散他的身体”)有点酷,但我不知道“resceins”是什么意思!如果你看过这部剧,就会发现这是情节的一部分,所以也许莎士比亚在没有意识到的情况下有所作为!

当然,我确实认为在使用像莎士比亚的文本作为我们的训练数据时我们往往会更宽容一点,因为这种语言已经有点陌生了。

与爱尔兰歌曲模型一样,输出确实会很快退化为无意义的文本,但玩起来仍然很有趣。要亲自尝试,您可以查看Colab

概括

在本章中,我们探讨了如何使用经过训练的基于 LSTM 的模型进行基本文本生成。您了解了如何将文本拆分为训练特征和标签,使用单词作为标签,并创建一个模型,在给定种子文本时,该模型可以预测下一个可能的单词。您对此进行了迭代以改进模型以获得更好的结果,探索了传统爱尔兰歌曲的数据集。您还通过使用莎士比亚文本的示例了解了如何通过基于字符的文本生成来潜在地改进这一点。希望这是对机器学习模型如何合成文本的有趣介绍!

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sonhhxg_柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值