🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
自然语言处理 (NLP) 是人工智能中的一种技术,用于处理对基于人类的语言的理解。它涉及编程技术来创建一个模型,该模型可以理解语言、对内容进行分类,甚至可以用基于人类的语言生成和创建新的作品。我们将在接下来的几章中探索这些技术。还有许多使用 NLP 来创建聊天机器人等应用程序的服务,但这不在本书的范围内——相反,我们将研究 NLP 的基础以及如何建模语言以便您可以训练神经网络理解和分类文本。为了一点乐趣,您还将了解如何使用机器学习模型的预测元素来写诗!
本章开始时,我们将研究如何将语言分解为数字,以及这些数字如何用于神经网络。
将语言编码成数字
你可以用多种方式将语言编码成数字。最常见的是按字母编码,当字符串存储在程序中时自然会这样做。然而,在内存中,您存储的不是字母a,而是它的编码——可能是 ASCII 或 Unicode 值,或其他。例如,考虑单词listen。这可以用 ASCII 编码为数字 76、73、83、84、69 和 78。这很好,因为您现在可以使用数字来表示单词。但是再考虑一下silent这个词,它是listen的反字母。相同的数字代表该单词,尽管顺序不同,这可能会使构建模型来理解文本有点困难。
笔记
一个 antigram是一个词,它是另一个词的字谜,但具有相反的含义。例如,united和untied是 antigrams, restful和fluster是 antigrams ,Santa和Satan,45 岁和50 岁以上。我的职位以前是 Developer Evangelist,但后来改为 Developer Advocate——这是一件好事,因为Evangelist是Evil's Agent的反字母!
更好的替代方法可能是使用数字而不是其中的字母来编码整个单词。在那种情况下,silent可能是数字x和listen数字y,并且它们不会相互重叠。
使用这种技术,考虑像“我爱我的狗”这样的句子。您可以使用数字 [1, 2, 3, 4] 对其进行编码。如果你想编码“我爱我的猫”。它可能是 [1, 2, 3, 5]。你已经到了可以判断句子具有相似含义的地步,因为它们在数字上相似——[1, 2, 3, 4] 看起来很像 [1, 2, 3, 5]。
此过程称为标记化,接下来您将探索如何在代码中执行此操作。
令牌化入门
张量流Keras 包含一个名为的库preprocessing
,它提供了许多非常有用的工具来为机器学习准备数据。其中之一是Tokenizer
允许您将单词转换为标记。让我们用一个简单的例子来看看它的实际效果:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer
sentences = [
'Today is a sunny day',
'Today is a rainy day'
]
tokenizer = Tokenizer(num_words = 100)
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index
print(word_index)
在这种情况下,我们创建一个Tokenizer
对象并指定它可以标记化的单词数。这将是从单词语料库中生成的最大标记数。我们这里有一个非常小的语料库,只包含六个独特的单词,所以我们将远远低于指定的一百个。
一旦我们有了分词器,调用fit_on_texts
将创建分词词索引。打印出来将显示语料库中单词的一组键/值对,如下所示:
{'today': 1, 'is': 2, 'a': 3, 'day': 4, 'sunny': 5, 'rainy': 6}
分词器非常灵活。例如,如果我们用另一个包含单词“today”但后面有一个问号的句子来扩展语料库,结果表明它会足够聪明地过滤掉“today?” 就像“今天”一样:
sentences = [
'Today is a sunny day',
'Today is a rainy day',
'Is it sunny today?'
]
{'today': 1, 'is': 2, 'a': 3, 'sunny': 4, 'day': 5, 'rainy': 6, 'it': 7}
此行为由filters
tokenizer 的参数控制,默认情况下删除撇号字符以外的所有标点符号。因此,例如,“今天是晴天”将变成一个包含 [1, 2, 3, 4, 5] 和前面编码的序列,“今天是晴天吗?” 会变成 [2, 7, 4, 1]。一旦你将句子中的单词标记化,下一步就是将你的句子转换为数字列表,数字是单词作为键的值。
将句子变成序列
现在你已经了解了如何获取单词并将它们标记为数字,下一步是将句子编码为数字序列。tokenizer 有一个方法叫做 text_to_sequences
——你所要做的就是将你的句子列表传递给它,它会返回一个序列列表。因此,例如,如果您像这样修改前面的代码:
sentences = [
'Today is a sunny day',
'Today is a rainy day',
'Is it sunny today?'
]
tokenizer = Tokenizer(num_words = 100)
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index
sequences = tokenizer.texts_to_sequences(sentences)
print(sequences)
您将获得代表这三个句子的序列。记住单词索引是这样的:
{'today': 1, 'is': 2, 'a': 3, 'sunny': 4, 'day': 5, 'rainy': 6, 'it': 7}
输出将如下所示:
[[1, 2, 3, 4, 5], [1, 2, 3, 6, 5], [2, 7, 4, 1]]
然后你可以用单词代替数字,你会发现这些句子是有意义的。
现在考虑如果您在一组数据上训练神经网络会发生什么。典型的模式是您有一组用于训练的数据,您知道这些数据不会满足 100% 的需求,但您希望尽可能多地涵盖。在 NLP 的情况下,您的训练数据中可能有数千个单词,用于许多不同的上下文,但您不可能在每个可能的上下文中都有每个可能的单词。所以当你向你的神经网络展示一些新的、以前没见过的文本,包含以前没见过的词时,会发生什么?你猜对了——它会感到困惑,因为它根本没有这些词的上下文,因此,它给出的任何预测都会受到负面影响。
使用词汇外标记
一用于处理这些情况的工具是词汇外(OOV) 标记。这可以帮助您的神经网络理解包含以前看不见的文本的数据的上下文。例如,给定前面的小示例语料库,假设你想处理这样的句子:
test_data = [
'Today is a snowy day',
'Will it be rainy tomorrow?'
]
请记住,您并不是将此输入添加到现有文本的语料库(您可以将其视为训练数据),而是考虑预训练网络如何查看此文本。如果你用你已经使用过的词和你现有的分词器来分词,就像这样:
test_sequences = tokenizer.texts_to_sequences(test_data)
print(word_index)
print(test_sequences)
您的结果将如下所示:
{'today': 1, 'is': 2, 'a': 3, 'sunny': 4, 'day': 5, 'rainy': 6, 'it': 7}
[[1, 2, 3, 5], [7, 6]]
所以新的句子,将标记换回单词,将是“今天是一天”和“下雨了”。
如您所见,您几乎失去了所有上下文和意义。词汇表外的标记在这里可能会有所帮助,您可以在标记器中指定它。您可以通过添加一个名为 的参数来执行此操作oov_token
,如此处所示——您可以为其分配您喜欢的任何字符串,但要确保它不会出现在您的语料库中的其他地方:
tokenizer = Tokenizer(num_words = 100, oov_token="<OOV>")
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index
sequences = tokenizer.texts_to_sequences(sentences)
test_sequences = tokenizer.texts_to_sequences(test_data)
print(word_index)
print(test_sequences)
您会看到输出有所改进:
{'<OOV>': 1, 'today': 2, 'is': 3, 'a': 4, 'sunny': 5, 'day': 6, 'rainy': 7,
'it': 8}
[[2, 3, 4, 1, 6], [1, 8, 1, 7, 1]]
您的标记列表有一个新项目“<OOV>”,并且您的测试句子保持其长度。反向编码它们现在将给出“今天是 <OOV> 日”和“<OOV> it <OOV> rainy <OOV>”。
前者更接近本意。后者,因为它的大部分词不在语料库中,仍然缺乏很多上下文,但它是朝着正确方向迈出的一步。
了解填充
什么时候训练神经网络,您通常需要所有数据都具有相同的形状。回想一下前面的章节,当使用图像进行训练时,您将图像重新格式化为相同的宽度和高度。对于文本,你会面临同样的问题——一旦你标记了你的单词并将你的句子转换成序列,它们的长度都可能不同。要使它们具有相同的大小和形状,您可以使用padding。
为了探索填充,让我们在语料库中添加另一个更长的句子:
sentences = [
'Today is a sunny day',
'Today is a rainy day',
'Is it sunny today?',
'I really enjoyed walking in the snow today'
]
当你排序时,你会看到你的数字列表有不同的长度:
[
[2, 3, 4, 5, 6],
[2, 3, 4, 7, 6],
[3, 8, 5, 2],
[9, 10, 11, 12, 13, 14, 15, 2]
]
(当你打印序列时,它们都在一行上,但为了清楚起见,我在这里将它们分成不同的行。)
如果你想让这些长度相同,你可以使用 pad_sequences
应用程序接口。首先,您需要导入它:
from tensorflow.keras.preprocessing.sequence import pad_sequences
API 的使用非常简单。要将您的(未填充的)序列转换为填充集,您只需pad_sequences
这样调用:
padded = pad_sequences(sequences)
print(padded)
您将获得一组格式良好的序列。它们也将在不同的行上,如下所示:
[[ 0 0 0 2 3 4 5 6]
[ 0 0 0 2 3 4 7 6]
[ 0 0 0 0 3 8 5 2]
[ 9 10 11 12 13 14 15 2]]
序列被填充0
,这不是我们单词列表中的标记。如果你想知道为什么令牌列表从 1 开始,而通常程序员从 0 开始计数,现在你知道了!
您现在拥有可用于训练的规则形状的东西。但在开始之前,让我们稍微研究一下这个 API,因为它为您提供了许多选项,您可以使用这些选项来改进数据。
首先,您可能已经注意到,对于较短的句子,为了使它们与最长的句子形状相同,在开头添加了必要数量的零。这称为prepadding,这是默认行为。您可以使用padding
参数更改它。例如,如果你希望你的序列在最后用零填充,你可以使用:
padded = pad_sequences(sequences, padding='post')
输出将是:
[[ 2 3 4 5 6 0 0 0]
[ 2 3 4 7 6 0 0 0]
[ 3 8 5 2 0 0 0 0]
[ 9 10 11 12 13 14 15 2]]
你可以看到现在单词在填充序列的开头,字符0
在结尾。
您可能已经观察到的下一个默认行为是,所有句子的长度都与最长的句子相同。这是一个明智的默认设置,因为这意味着您不会丢失任何数据。权衡是你得到了很多填充。但是如果你不想要这个怎么办,也许是因为你有一个疯狂的长句子,这意味着你在填充序列中有太多的填充?要解决这个问题,您可以使用maxlen
参数,在调用 时指定所需的最大长度,pad_sequences
如下所示:
padded = pad_sequences(sequences, padding='post', maxlen=6)
输出将是:
[[ 2 3 4 5 6 0]
[ 2 3 4 7 6 0]
[ 3 8 5 2 0 0]
[11 12 13 14 15 2]]
现在你填充的序列都是相同的长度,并且没有太多的填充。但是,您从最长的句子中丢失了一些单词,并且它们从一开始就被截断了。如果您不想丢失开头的单词,而是希望它们从句子的末尾截断怎么办?您可以使用参数覆盖默认行为truncating
,如下所示:
padded = pad_sequences(sequences, padding='post', maxlen=6, truncating='post')
结果将显示最长的句子现在在末尾而不是开头被截断:
[[ 2 3 4 5 6 0]
[ 2 3 4 7 6 0]
[ 3 8 5 2 0 0]
[ 9 10 11 12 13 14]]
笔记
张量流支持使用“参差不齐”(形状各异)的张量进行训练,非常适合NLP的需求。使用它们比我们在本书中介绍的要高级一些,但是一旦您完成了接下来几章中提供的 NLP 介绍,您就可以浏览文档以了解更多信息。
删除停用词和清理文本
在在下一节中,您将查看一些真实世界的数据集,您会发现数据集中经常有您不需要的文本。你可能想过滤掉所谓的 太常见且不添加任何含义的停用词,例如“the”、“and”和“but”。你可能还会遇到很多文本中的 HTML 标记,最好有一种干净的方法来删除它们。您可能想要过滤掉的其他内容包括粗鲁的词语、标点符号或名称。稍后我们将探索推文数据集,其中通常包含某人的用户 ID,我们希望将其过滤掉。
虽然每个任务都根据您的文本语料库而有所不同,但您可以执行三项主要操作来以编程方式清理文本。
第一个是去除 HTML 标签。幸运的是,有一个调用的库BeautifulSoup
使这变得简单明了。例如,如果您的句子包含诸如 之类的 HTML 标记<br>
,它们将被以下代码删除:
from bs4 import BeautifulSoup
soup = BeautifulSoup(sentence)
sentence = soup.get_text()
删除停用词的一种常见方法是拥有一个停用词列表并预处理您的句子,从而删除停用词的实例。这是一个简化的例子:
stopwords = ["a", "about", "above", ... "yours", "yourself", "yourselves"]
可以在本章的一些在线示例中找到完整的停用词列表。
然后,当你遍历你的句子时,你可以使用这样的代码从你的句子中删除停用词:
words = sentence.split()
filtered_sentence = ""
for word in words:
if word not in stopwords:
filtered_sentence = filtered_sentence + word + " "
sentences.append(filtered_sentence)
其他你可能会考虑去掉标点符号,这可以欺骗停用词删除器。刚刚显示的查找被空格包围的单词,因此不会发现停用词后紧跟句号或逗号。
使用 提供的翻译功能很容易解决这个问题Pythonstring
库。它还带有一个常量,string.punctuation
它包含一个常用标点符号列表,因此要从单词中删除它们,您可以执行以下操作:
import string
table = str.maketrans('', '', string.punctuation)
words = sentence.split()
filtered_sentence = ""
for word in words:
word = word.translate(table)
if word not in stopwords:
filtered_sentence = filtered_sentence + word + " "
sentences.append(filtered_sentence)
在这里,在过滤停用词之前,句子中的每个单词都删除了标点符号。所以,如果拆分一个句子给你“它”这个词;它将被转换为“it”,然后作为停用词被删除。但是请注意,执行此操作时您可能必须更新停用词列表。这些列表通常包含缩写词和缩略词,例如“you'll”。翻译器会将“you'll”更改为“youll”,如果您想过滤掉它,则需要更新停用词列表以将其包含在内。
遵循这三个步骤将为您提供一组更清晰的文本供使用。但当然,每个数据集都会有您需要处理的特性。
使用真实数据源
现在您已经了解了获取句子、使用单词索引对它们进行编码以及对结果进行排序的基础知识,您可以通过获取一些著名的公共数据集并使用 Python 提供的工具将其放入下一个级别一种可以轻松对其进行排序的格式。我们将从 TensorFlow 数据集中已为您完成大量工作的一个开始:IMDb 数据集。之后,我们将进行更多动手操作,处理一个基于 JSON 的数据集和几个包含情感数据的逗号分隔值 (CSV) 数据集!
从 TensorFlow 数据集中获取文本
我们在第 4 章中探讨了 TFDS ,因此如果您对本节中的任何概念感到困惑,可以快速浏览一下。TFDS 背后的目标是以标准化方式尽可能轻松地访问数据。它提供对几个基于文本的数据集的访问;我们将探索imdb_reviews
,一个包含 50,000 条标记电影评论的数据集,来自互联网电影数据库 (IMDb),每一个都被确定为正面或负面的情绪。
此代码将从 IMDb 数据集加载训练拆分并遍历它,将包含评论的文本字段添加到名为imdb_sentences
. 评论是文本元组和包含评论观点的标签。请注意,通过将调用tfds.load
包装在tfds.as_numpy
您确保数据将作为字符串加载,而不是张量:
imdb_sentences = []
train_data = tfds.as_numpy(tfds.load('imdb_reviews', split="train"))
for item in train_data:
imdb_sentences.append(str(item['text']))
一旦你有了句子,你就可以创建一个分词器并像以前一样将其适合它们,以及创建一组序列:
tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words=5000)
tokenizer.fit_on_texts(imdb_sentences)
sequences = tokenizer.texts_to_sequences(imdb_sentences)
你也可以打印出你的单词索引来检查它:
print(tokenizer.word_index)
它太大,无法显示整个索引,但这里是前 20 个词。请注意,分词器按照数据集中的频率顺序列出了它们,因此对“the”、“and”和“a”等常用词进行了索引:
{'the': 1, 'and': 2, 'a': 3, 'of': 4, 'to': 5, 'is': 6, 'br': 7, 'in': 8,
'it': 9, 'i': 10, 'this': 11, 'that': 12, 'was': 13, 'as': 14, 'for': 15,
'with': 16, 'movie': 17, 'but': 18, 'film': 19, "'s": 20, ...}
如上一节所述,这些是停用词。存在这些词会影响您的训练准确性,因为它们是最常见的词,而且它们没有区别。
另请注意,“br”包含在此列表中,因为它在本语料库中通常用作<br>
HTML 标记。
您可以更新代码以用于BeautifulSoup
删除 HTML 标记,添加字符串翻译以删除标点符号,并从给定列表中删除停用词,如下所示:
from bs4 import BeautifulSoup
import string
stopwords = ["a", ... , "yourselves"]
table = str.maketrans('', '', string.punctuation)
imdb_sentences = []
train_data = tfds.as_numpy(tfds.load('imdb_reviews', split="train"))
for item in train_data:
sentence = str(item['text'].decode('UTF-8').lower())
soup = BeautifulSoup(sentence)
sentence = soup.get_text()
words = sentence.split()
filtered_sentence = ""
for word in words:
word = word.translate(table)
if word not in stopwords:
filtered_sentence = filtered_sentence + word + " "
imdb_sentences.append(filtered_sentence)
tokenizer = tf.keras.preprocessing.text.Tokenizer(num_words=25000)
tokenizer.fit_on_texts(imdb_sentences)
sequences = tokenizer.texts_to_sequences(imdb_sentences)
print(tokenizer.word_index)
请注意,在处理之前将句子转换为小写,因为所有停用词都以小写形式存储。当你现在打印出你的单词索引时,你会看到:
{'movie': 1, 'film': 2, 'not': 3, 'one': 4, 'like': 5, 'just': 6, 'good': 7,
'even': 8, 'no': 9, 'time': 10, 'really': 11, 'story': 12, 'see': 13,
'can': 14, 'much': 15, ...}
你可以看到这比以前干净多了。然而,总有改进的余地,我在查看完整索引时注意到的一件事是,最后一些不太常见的词是荒谬的。审稿人通常会将单词组合在一起,例如使用破折号(“烦人的结论”)或斜线(“他/她”),而去除标点符号会错误地将它们变成一个单词。您可以通过一些在这些字符周围添加空格的代码来避免这种情况,因此我在创建句子后立即添加了以下内容:
sentence = sentence.replace(",", " , ")
sentence = sentence.replace(".", " . ")
sentence = sentence.replace("-", " - ")
sentence = sentence.replace("/", " / ")
这将像“他/她”这样的组合词变成了“他/她”,然后将“/”去掉并标记为两个词。这可能会在以后带来更好的训练结果。
现在您有了语料库的分词器,您可以对句子进行编码。例如,我们在本章前面看到的简单句子会像这样:
sentences = [
'Today is a sunny day',
'Today is a rainy day',
'Is it sunny today?'
]
sequences = tokenizer.texts_to_sequences(sentences)
print(sequences)
[[516, 5229, 147], [516, 6489, 147], [5229, 516]]
如果你解码这些,你会看到停用词被删除,你得到的句子被编码为“今天晴天”、“今天雨天”和“今天晴天”。
如果您想在代码中执行此操作,您可以使用dict
颠倒的键和值创建一个新的(即,对于单词索引中的键/值对,将值作为键,将键作为值)并从中进行查找那。这是代码:
reverse_word_index = dict(
[(value, key) for (key, value) in tokenizer.word_index.items()])
decoded_review = ' '.join([reverse_word_index.get(i, '?') for i in sequences[0]])
print(decoded_review)
这将给出以下结果:
today sunny day
使用 IMDb 子词数据集
TFDS还包含一些使用子词的预处理 IMDb 数据集。在这里,您不必按单词分解句子;他们已经为你分成了子词。使用子词是将语料库拆分为单个字母(相对较少的低语义标记)和单个单词(许多具有高语义含义的标记)之间的中间方法,这种方法通常可以非常有效地用于训练语言分类器。这些数据集还包括用于拆分和编码语料库的编码器和解码器。
要访问它们,您可以调用tfds.load
并传递它imdb_reviews/subwords8k
或imdb_reviews/subwords32k
像这样:
(train_data, test_data), info = tfds.load(
'imdb_reviews/subwords8k',
split = (tfds.Split.TRAIN, tfds.Split.TEST),
as_supervised=True,
with_info=True
)
info
您可以像这样访问对象上的编码器。这将帮助您了解vocab_size
:
encoder = info.features['text'].encoder
print ('Vocabulary size: {}'.format(encoder.vocab_size))
这将输出8185
,因为此实例中的词汇表由 8,185 个标记组成。如果要查看子词列表,可以使用以下encoder.subwords
属性获取它:
print(encoder.subwords)
['the_', ', ', '. ', 'a_', 'and_', 'of_', 'to_', 's_', 'is_', 'br', 'in_', 'I_',
'that_',...]
您在这里可能会注意到的一些事情是停用词、标点符号和语法都在语料库中,HTML 标记也是如此,例如<br>
. 空格由下划线表示,因此第一个标记是单词“the”。
如果你想编码一个字符串,你可以像这样使用编码器:
sample_string = 'Today is a sunny day'
encoded_string = encoder.encode(sample_string)
print ('Encoded string is {}'.format(encoded_string))
其输出将是一个令牌列表:
Encoded string is [6427, 4869, 9, 4, 2365, 1361, 606]
所以,你的五个词被编码成七个标记。要查看标记,您可以使用subwords
编码器上的属性,它返回一个数组。它是从零开始的,因此虽然“Today”中的“Tod”被编码为6427
,但它是数组中的第 6,426 项:
print(encoder.subwords[6426])
Tod
如果要解码,可以使用decode
encoder的方法:
encoded_string = encoder.encode(sample_string)
original_string = encoder.decode(encoded_string)
test_string = encoder.decode([6427, 4869, 9, 4, 2365, 1361, 606])
从 CSV 文件中获取文本
尽管TFDS 有很多很棒的数据集,但它并没有包含所有内容,而且您通常需要自己管理加载数据。NLP 数据可用的最常见格式之一是 CSV 文件。在接下来的几章中,您将使用我改编自的 Twitter 数据的 CSV文本数据集中的开源情感分析。您将使用两个不同的数据集,一个将情绪降低为“积极”或“消极”以进行二元分类,另一个使用了所有情绪标签。每个的结构都是相同的,所以我在这里只显示二进制版本。
这Pythoncsv
库使处理 CSV 文件变得简单明了。在这种情况下,数据每行存储两个值。首先是一个数字(0 或 1),表示情绪是消极的还是积极的。第二个是包含文本的字符串。
以下代码将读取 CSV 并进行与我们在上一节中看到的类似的预处理。它在复合词的标点符号周围添加空格,使用 BeautifulSoup
去除 HTML 内容,然后去除所有标点符号:
import csv
sentences=[]
labels=[]
with open('/tmp/binary-emotion.csv', encoding='UTF-8') as csvfile:
reader = csv.reader(csvfile, delimiter=",")
for row in reader:
labels.append(int(row[0]))
sentence = row[1].lower()
sentence = sentence.replace(",", " , ")
sentence = sentence.replace(".", " . ")
sentence = sentence.replace("-", " - ")
sentence = sentence.replace("/", " / ")
soup = BeautifulSoup(sentence)
sentence = soup.get_text()
words = sentence.split()
filtered_sentence = ""
for word in words:
word = word.translate(table)
if word not in stopwords:
filtered_sentence = filtered_sentence + word + " "
sentences.append(filtered_sentence)
这将为您提供 35,327 个句子的列表。
创建训练和测试子集
现在文本语料库已被读入句子列表,您需要将其拆分为训练和测试子集以训练模型。例如,如果你想使用 28,000 个句子进行训练,其余的保留用于测试,你可以使用这样的代码:
training_size = 28000
training_sentences = sentences[0:training_size]
testing_sentences = sentences[training_size:]
training_labels = labels[0:training_size]
testing_labels = labels[training_size:]
现在您有了一个训练集,您需要从中创建单词索引。下面是使用分词器创建最多包含 20,000 个单词的词汇表的代码。我们将一个句子的最大长度设置为 10 个单词,通过截断末尾来截断较长的句子,在末尾填充较短的句子,并使用“<OOV>”:
vocab_size = 20000
max_length = 10
trunc_type='post'
padding_type='post'
oov_tok = "<OOV>"
tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_tok)
tokenizer.fit_on_texts(training_sentences)
word_index = tokenizer.word_index
training_sequences = tokenizer.texts_to_sequences(training_sentences)
training_padded = pad_sequences(training_sequences, maxlen=max_length,
padding=padding_type,
truncating=trunc_type)
training_sequences
您可以通过查看和来检查结果training_padded
。例如,这里我们打印训练序列中的第一项,您可以看到它是如何被填充到最大长度 10 的:
print(training_sequences[0])
print(training_padded[0])
[18, 3257, 47, 4770, 613, 508, 951, 423]
[ 18 3257 47 4770 613 508 951 423 0 0]
您还可以通过打印来检查单词索引:
{'<OOV>': 1, 'just': 2, 'not': 3, 'now': 4, 'day': 5, 'get': 6, 'no': 7,
'good': 8, 'like': 9, 'go': 10, 'dont': 11, ...}
从 JSON 文件中获取文本
其他文本文件非常常见的格式是 JavaScript 对象表示法 (JSON)。这是一种开放标准文件格式,通常用于数据交换,尤其是与 Web 应用程序。它是人类可读的,旨在使用名称/值对。因此,它特别适合标记文本。快速搜索 JSON 的 Kaggle 数据集会产生超过 2,500 个结果。流行的数据集,例如例如,斯坦福问答数据集 (SQuAD) 存储在 JSON 中。
JSON 具有非常简单的语法,其中对象作为名称/值对包含在大括号中,名称/值对由逗号分隔。例如,表示我的名字的 JSON 对象将是:
{"firstName" : "Laurence",
"lastName" : "Moroney"}
JSON 还支持数组,这很像 Python 列表,并用方括号语法表示。这是一个例子:
[
{"firstName" : "Laurence",
"lastName" : "Moroney"},
{"firstName" : "Sharon",
"lastName" : "Agathon"}
]
对象也可以包含数组,所以这是完全有效的 JSON:
[
{"firstName" : "Laurence",
"lastName" : "Moroney",
"emails": ["lmoroney@gmail.com", "lmoroney@galactica.net"]
},
{"firstName" : "Sharon",
"lastName" : "Agathon",
"emails": ["sharon@galactica.net", "boomer@cylon.org"]
}
]
一个存储在 JSON 中的较小数据集和很多使用乐趣是Rishabh Misra的讽刺检测新闻头条数据集,可在Kaggle上获得。该数据集从两个来源收集新闻标题:The Onion收集有趣或讽刺的新闻,HuffPost收集普通标题。
Sarcasm 数据集中的文件结构非常简单:
{"is_sarcastic": 1 or 0,
"headline": String containing headline,
"article_link": String Containing link}
该数据集包含大约 26,000 个项目,每行一个。为了使其在 Python 中更具可读性,我创建了一个版本,将这些包含在一个数组中,以便它可以作为一个列表来读取,这在本章的源代码中使用。
读取 JSON 文件
Python 的json
库使读取 JSON 文件变得简单。鉴于 JSON 使用名称/值对,您可以根据名称对内容进行索引。因此,例如,对于 Sarcasm 数据集,您可以创建 JSON 文件的文件句柄,使用库打开它json
,进行迭代,逐行读取每个字段,并使用字段名称获取数据项.
这是代码:
import json
with open("/tmp/sarcasm.json", 'r') as f:
datastore = json.load(f)
for item in datastore:
sentence = item['headline'].lower()
label= item['is_sarcastic']
link = item['article_link']
这使得创建句子和标签列表变得简单,就像您在本章中所做的那样,然后标记句子。您还可以在阅读句子时即时进行预处理,删除停用词、HTML 标签、标点符号等。下面是创建句子、标签和 URL 列表的完整代码,同时清除句子中不需要的单词和字符:
with open("/tmp/sarcasm.json", 'r') as f:
datastore = json.load(f)
sentences = []
labels = []
urls = []
for item in datastore:
sentence = item['headline'].lower()
sentence = sentence.replace(",", " , ")
sentence = sentence.replace(".", " . ")
sentence = sentence.replace("-", " - ")
sentence = sentence.replace("/", " / ")
soup = BeautifulSoup(sentence)
sentence = soup.get_text()
words = sentence.split()
filtered_sentence = ""
for word in words:
word = word.translate(table)
if word not in stopwords:
filtered_sentence = filtered_sentence + word + " "
sentences.append(filtered_sentence)
labels.append(item['is_sarcastic'])
urls.append(item['article_link'])
和以前一样,这些可以分为训练集和测试集。如果要使用数据集中 26,000 项中的 23,000 项进行训练,可以执行以下操作:
training_size = 23000
training_sentences = sentences[0:training_size]
testing_sentences = sentences[training_size:]
training_labels = labels[0:training_size]
testing_labels = labels[training_size:]
要标记数据并为训练做好准备,您可以采用与之前相同的方法。在这里,我们再次指定 20,000 个单词的词汇量,最大序列长度为 10,并在末尾进行截断和填充,以及“<OOV>”的 OOV 标记:
vocab_size = 20000
max_length = 10
trunc_type='post'
padding_type='post'
oov_tok = "<OOV>"
tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_tok)
tokenizer.fit_on_texts(training_sentences)
word_index = tokenizer.word_index
training_sequences = tokenizer.texts_to_sequences(training_sentences)
padded = pad_sequences(training_sequences, padding='post')
print(word_index)
输出将是整个索引,按词频顺序排列:
{'<OOV>': 1, 'new': 2, 'trump': 3, 'man': 4, 'not': 5, 'just': 6, 'will': 7,
'one': 8, 'year': 9, 'report': 10, 'area': 11, 'donald': 12, ... }
希望看起来相似的代码将帮助您了解在为神经网络准备文本以进行分类或生成时可以遵循的模式。在下一章中,您将看到如何使用嵌入为文本构建分类器,而在第 7 章中,您将更进一步,探索递归神经网络。然后,在第 8 章中,您将看到如何进一步增强序列数据以创建可以生成新文本的神经网络!
概括
在前面的章节中,您使用图像来构建分类器。根据定义,图像是高度结构化的。你知道他们的维度。你知道格式。另一方面,文本可能更难处理。它通常是非结构化的,可能包含格式说明等不需要的内容,并不总是包含您想要的内容,并且通常必须进行过滤以删除无意义或不相关的内容。在本章中,您了解了如何使用单词标记化将文本转换为数字,然后探索了如何读取和过滤各种格式的文本。有了这些技能,您现在就可以进行下一步,学习如何从单词中推断出意义——这是理解自然语言的第一步。