Keras Tokenizer方法到底是做什么的?

118

有时,情况要求我们采取以下措施:

from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer(num_words=my_max)

然后,不可避免地,我们会诵唱这个口号:

tokenizer.fit_on_texts(text) 
sequences = tokenizer.texts_to_sequences(text)

虽然我(或多或少)理解总体效果是什么,但我无法弄清楚每个单独的效果是什么,无论我做了多少研究(包括文档)。我从未见过一个单独使用而没有另一个的情况。

那么每个效果具体是什么呢?有没有任何情况可以只使用其中一个而不使用另一个?如果没有,为什么它们不被合并成像这样的东西:

sequences = tokenizer.fit_on_texts_to_sequences(text)

如果我漏掉了什么显而易见的事情,那我很抱歉,因为我对这个还很新手。


1
我发现反直觉的是,Tokenizer 的输出是一系列整数,例如单词索引,而不是单独标记的列表。实际上,它可以接受标记化文本(每个内容的标记列表)并输出整数序列。https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/text/Tokenizer#fit_on_texts。 - flow2k
4个回答

178

源代码

  1. fit_on_texts基于文本列表更新内部词汇表。该方法根据单词出现的频率创建词汇索引。所以,如果你给它这样的东西,"The cat sat on the mat." 它将创建一个字典,例如word_index["the"] = 1; word_index["cat"] = 2,它是单词->索引的字典,因此每个单词都有一个唯一的整数值。0是用于填充的。所以较低的整数代表出现频率更高的单词(通常前几个单词是停用词,因为它们出现得很多)。
  2. texts_to_sequences 将texts中的每个文本转换为整数序列。它基本上是把文本中的每个单词替换为其在word_index字典中对应的整数值。没有什么魔法,仅此而已。

为什么不合并它们?因为您几乎总是需要适配一次,然后将其转换为序列多次。您将适配一次训练语料库,然后在训练/评估/测试/预测时使用完全相同的word_index字典将实际文本转换为序列,以将其馈送到网络中。因此,保持这些方法分开是有道理的。


3
所以这一切都在源代码中!我想我没有找得够仔细...现在我明白了:适合-一次,序列-多次!像往常一样,活到老学到老。谢谢。 - Jack Fleeting
2
@nuric 感谢您的回复。然而,有几点需要注意。 根据官方文档, “0是一个保留索引,不会分配给任何单词。” 所以在您的示例中,第一个单词的索引将为1。 https://keras.io/preprocessing/text/ 另一点是默认过滤器会删除大多数标点符号,因此只有在从过滤器中删除标点符号时,它们才会成为词汇表中最常见的符号。 - Nikita
1
@Nikita 谢谢你指出这个问题,我已经更新了答案来修复它。 - nuric
谢谢。那么,对于一个时间序列,我们需要使用分词器来进行标记化吗? - Avv
我们可以在进行fit_on_sequences操作后提取单词吗? - 10sha25

76

通过以下的例子进一步解释会有助于更好地理解:

示例1

t  = Tokenizer()
fit_text = "The earth is an awesome place live"
t.fit_on_texts(fit_text)
test_text = "The earth is an great place live"
sequences = t.texts_to_sequences(test_text)

print("sequences : ",sequences,'\n')

print("word_index : ",t.word_index)
#[] specifies : 1. space b/w the words in the test_text    2. letters that have not occured in fit_text

Output :

       sequences :  [[3], [4], [1], [], [1], [2], [8], [3], [4], [], [5], [6], [], [2], [9], [], [], [8], [1], [2], [3], [], [13], [7], [2], [14], [1], [], [7], [5], [15], [1]] 

       word_index :  {'e': 1, 'a': 2, 't': 3, 'h': 4, 'i': 5, 's': 6, 'l': 7, 'r': 8, 'n': 9, 'w': 10, 'o': 11, 'm': 12, 'p': 13, 'c': 14, 'v': 15}

例子2:

t  = Tokenizer()
fit_text = ["The earth is an awesome place live"]
t.fit_on_texts(fit_text)

#fit_on_texts fits on sentences when list of sentences is passed to fit_on_texts() function. 
#ie - fit_on_texts( [ sent1, sent2, sent3,....sentN ] )

#Similarly, list of sentences/single sentence in a list must be passed into texts_to_sequences.
test_text1 = "The earth is an great place live"
test_text2 = "The is my program"
sequences = t.texts_to_sequences([test_text1, test_text2])

print('sequences : ',sequences,'\n')

print('word_index : ',t.word_index)
#texts_to_sequences() returns list of list. ie - [ [] ]

Output:

        sequences :  [[1, 2, 3, 4, 6, 7], [1, 3]] 

        word_index :  {'the': 1, 'earth': 2, 'is': 3, 'an': 4, 'awesome': 5, 'place': 6, 'live': 7}

6
例子永远不会撒谎!感谢您的精彩回答。 - Chintan
我有一个训练数据集和一个测试数据集,两者都有两列:索引和已清理的文本。我想知道是否可以在两个数据框之间进行append(),然后在附加集上执行Tokenizer.fit_on_text(),而不仅仅是在训练集上执行。我认为,如果我想要将文本转换为整数向量,其中每个整数表示使用的所有词汇表中的一个单词,那么首先进行append()会更好,因为当我执行text_to_sequence()时,测试集中的向量将具有更多的元素。 - Nachengue
@Nachengue 请看这里:https://dev59.com/uFYN5IYBdhLWcg3wO14v。 - keramat

15

让我们看看这行代码做了什么。

tokenizer.fit_on_texts(text) 
例如,考虑句子" The earth is an awesome place live"

tokenizer.fit_on_texts("The earth is an awesome place live")[[1,2,3,4,5,6,7]]适配,其中3代表“is”,6代表“place”,等等。

sequences = tokenizer.texts_to_sequences("The earth is an great place live")

返回 [[1,2,3,4,6,7]]

你看到这里发生了什么。单词“great”最初不适合,因此它没有识别出单词“great”。意思是fit_on_text可以独立使用于训练数据,然后配好的词汇表索引可以用来表示全新的单词序列。这是两个不同的过程,因此需要这两行代码。


1
非常好,你发现了缺少“great”这个单词的问题。但是对于不在词汇表中的单词,我们该怎么处理呢? - kkgarg

3

nuric已经解答了问题,但我想补充一些内容。

请在这个例子中关注基于词频的编码和OOV:

from tensorflow.keras.preprocessing.text        import Tokenizer

corpus =['The', 'cat', 'is', 'on', 'the', 'table', 'a', 'very', 'long', 'table']

tok_obj = Tokenizer(num_words=10, oov_token='<OOV>')
tok_obj.fit_on_texts(corpus)

[TL;DR] 分词器将包含语料库中出现的前10个单词。这里有10个单词,但只有8个是唯一的。如果最常见的10个单词超过了此数量,则将对它们进行编码,并且它们将成为词汇表外的单词(OOV)。
建立字典:
请注意频率。
{'<OOV>': 1, 'the': 2, 'table': 3, 'cat': 4, 'is': 5, 'on': 6, 'a': 7, 'very': 8, 'long': 9}

句子处理:

processed_seq = tok_obj.texts_to_sequences(['The dog is on the bed'])

这将会给出:

>>> processed_seq
    [[2, 1, 5, 6, 2, 1]]

如何检索句子?

构建字典inv_map并使用它!下面可以使用列表推导式来压缩代码。

inv_map = {v: k for k, v in tok_obj.word_index.items()}

for seq in processed_seq:
    for tok in seq:
        print(inv_map[tok])

这将会给出:

>>> the
<OOV>
is
on
the
<OOV>

因为字典中没有单词“dog”和“bed”。 使用列表推导式可以压缩代码。这里输出一个列表作为结果。
[inv_map[tok] for seq in processed_seq for tok in seq]

这将会给出:

>>> ['the', '<OOV>', 'is', 'on', 'the', '<OOV>']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接