TensorFlow 实战(四)(2)https://developer.aliyun.com/article/1522805
9.6 用词向量注入语义
你已经建立了一个可以以约 80%的准确率测量情绪的模型,但你希望进一步改进。你相信词嵌入将提供所需的优势以达到更高的准确率。词嵌入是将单词编码为特征向量的一种方法。词嵌入与模型训练一起学习词汇表中单词的特征向量。嵌入层引入了一个可训练矩阵,其中每一行表示词汇表中单词的一个可训练向量。这比独热编码要好得多,因为独热编码受到维度灾难的困扰,这意味着随着单词数量的增加,输入模型的维度也会增加。
你应该为拥有一个可以准确分类正面和负面情绪的合理模型感到自豪;80%的准确率是一个很好的起点。但让我们看看我们已经拥有的优秀模型中可以改进的地方。
正面对着我们的一个瓶颈是我们模型中的单热编码层。尽管单热编码很简单,但它是一种非常低效的单词表示。它是单词的局部表示,意味着表示中只有一个元素(设为值 1)携带信息。换句话说,这是一种非常稀疏的表示,其中有大量元素被设置为零且不贡献信息。单热编码还受到维度诅咒的影响。最后,单热编码完全忽略了文本中存在的宝贵语义。使用单热编码,你不能说一只猫和一只狗更相似还是更相似于一座火山。现在的问题是,是否有更好的表示单词的方法?
9.6.1 词嵌入
是时候迎来一个称为词嵌入的新时代的单词表示了。词嵌入,有时也称为词向量,是一种非常丰富和高效的单词表示。与像单热向量这样的局部表示相反,词向量提供了一种分布式表示。这意味着向量中的所有元素都在定义向量所表示的词中发挥作用。换句话说,词向量具有密集的表示,与单热向量的稀疏表示相反。词向量的维度不取决于词汇量的大小,这使你可以节省内存和计算时间。最重要的是,词向量捕捉到了词的语义或相似性。使用词向量,你知道猫更类似于狗而不是火山。在理解词向量之前,你必须理解上下文在单词周围扮演的重要角色。词向量在很大程度上依赖于单词的上下文来生成丰富的单词表示。上下文的重要性被一位英国语言学家 J.R.弗斯的一句名言潜移默化地捕捉到:
你可以通过一个词的周围环境来了解它。
再深入一点,一个词的上下文在定义该词的语义中起着重要作用。例如,看看下面这个句子:
我们的宠物托比是一只 ____;他喜欢玩球。
你认为这里应该填什么词?我们在上下文中看到了“宠物”、“玩耍”和“球”这样的词。很可能是猫或狗。这意味着只有某种类型的词(即某种宠物)会出现在这个上下文中。利用这个特性,词向量可以生成保留文本语义的向量。在这个例子中,词向量将捕捉到猫和狗非常相似(当然,不是生物学上的相似,而是我们与它们互动或感知的方式)。从更技术的角度来看,词向量算法的目标如下:如果词w[i]和w[j]出现在同样的上下文中,对于某个距离度量Dist(a,b),它测量两个向量a和b之间的距离:
Dist(w[i], w[j]) ~ 0
实际的词向量算法超出了本书的范围。一些值得注意的算法包括 Skip-gram、CBoW(Continuous Bag-of-Words)、GloVe(全局向量)和 ELMo(来自语言模型的嵌入)。您可以通过阅读 Tomas Mikolov 等人的论文“Efficient Estimation of Word Representations in Vector Space” (arxiv.org/pdf/1301.3781.pdf
) 了解更多关于 Skip-gram 和 CBoW 算法的细节。
展示给我词向量算法
词向量算法以无监督的方式训练。训练算法的具体细节会根据算法而异。Skip-gram 算法通过选择一个探针单词作为输入,上下文单词作为目标生成输入目标对。例如,从句子“I went to buy flowers”中,它将生成如下输入目标对:[(went, I), (went, to), (to, went), (to, buy), . . . ]。然后,它将解决预测探测单词的上下文的分类任务,这将导致识别出良好的词向量。然而,像 Skip-gram 这样的词向量算法遭受语料库全局视图的缺乏,因为该算法只考虑单词周围的小上下文。
另一方面,GloVe 使用局部和全局信息生成词向量。为了提取语料库的全局信息,它利用了一个共现矩阵,其中包含单词“i”在语料库中与“j”上下文中出现的次数。您可以在 Pennington 等人的论文“GloVe: Global Representations for Word Vectors” (nlp.stanford.edu/pubs/glove.pdf
) 中了解更多信息。GloVe 仍然没有解决模糊词的问题。所谓的模糊词是指根据上下文具有不同含义的词。例如,在句子“I went to the bank to deposit money”和“I walked on the river bank”中,“bank”这个词有完全不同的含义。GloVe 会为这两种情况给出相同的向量,这是不准确的。
进入 ELMo!ELMo 是由 Peters 等人在论文“深度上下文化的词表示”中介绍的 (arxiv.org/pdf/1802.05365.pdf
)。ELMo 使用双向 LSTM 模型生成词向量。双向 LSTM 类似于标准 LSTM,但同时正向和反向读取序列。
词向量算法的最终输出是一个 V × d 大小的嵌入矩阵。这个矩阵的第 i 行表示由 ID i 表示的单词的词向量。d 通常 < 300,并使用超参数算法选择。图 9.7 描述了词嵌入矩阵。
图 9.7 概述了如何使用嵌入矩阵获取词向量。使用输入单词 ID 进行查找以获取对应这些索引的向量。这些向量的实际值在模型训练期间学习。
现在,我们将用单词嵌入增强我们的情感分析器模型。
9.6.2 使用单词嵌入定义最终模型
一般来说,任何深度序列模型都可以从单词嵌入中受益。作为额外的好处,大多数情况下,您不需要担心单词向量算法本身,可以通过引入一个随机初始化的嵌入空间来获得良好的性能。然后,这些嵌入可以与模型的其他部分一起在解决特定 NLP 任务时进行联合训练。按照同样的模式,让我们向我们的情感分析器引入一个随机初始化的嵌入空间。
让我们回顾一下我们之前实现的模型。该模型由一个掩码层、一个独热编码器层和一个 LSTM 层,后面跟着两个 Dense 层(之间有丢弃)组成:
model = tf.keras.models.Sequential([ # Create a mask to mask out zero inputs tf.keras.layers.Masking(mask_value=0.0, input_shape=(None,1)), # After creating the mask, convert inputs to onehot encoded inputs OnehotEncoder(depth=n_vocab), # Defining an LSTM layer tf.keras.layers.LSTM(128, return_state=False, return_sequences=False), # Defining a Dense layer tf.keras.layers.Dense(512, activation='relu'), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(1, activation='sigmoid') ])
除了两个改变外,模型将保持不变:
- 我们将用 tf.keras.layers.Embedding 层替换独热编码器层。
- 通过在层中设置 mask_zero=True,掩码功能将被吸收到 tf.keras.layers.Embedding 层中。
tf.keras.layers.Embeddings 层将一个大的可训练矩阵引入模型。该矩阵是一个 (V+1) x d 大小的矩阵,其中 V 是词汇表的大小。额外的一个是因为我们使用了特殊的保留 ID 零。d 是通过超参数优化算法选择的。在下面的模型中,我们将 d = 128 设置为经验值。已经在列表中用粗体标出了已更改的行。
列表 9.7 使用单词嵌入实现情感分析器
model = tf.keras.models.Sequential([ ❶ tf.keras.layers.Embedding(input_dim=n_vocab+1, output_dim=128, mask_zero=True),❷ tf.keras.layers.LSTM(128, return_state=False, return_sequences=False), ❸ tf.keras.layers.Dense(512, activation='relu'), ❹ tf.keras.layers.Dropout(0.5), ❺ tf.keras.layers.Dense(1, activation='sigmoid') ❻ ]) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) ❼ model.summary()
❶ 创建一个掩码以屏蔽零输入。
❷ 添加一个嵌入层。它将查找作为输入传递的单词 ID 的单词向量。
❸ 定义一个 LSTM 层。
❹ 定义 Dense 层。
❺ 定义一个 Dropout 层。
❻ 使用 S 型激活函数定义最终的 Dense 层。
❼ 使用二元交叉熵编译模型。
空输入、掩码和 LSTM 层
我们通过引入一个过滤器来过滤掉数据集中的空评论,以确保我们的数据集中没有任何空评论。理解我们这样做的原因非常重要。除了起到数据清洗的作用外,它还具有重要的目的。在数据集中有空评论会导致我们的数据流水线中出现全零向量。例如,如果序列长度为 5,则空评论将返回 [0,0,0,0,0]。当使用掩码层时,所有输入将被忽略。这对于 LSTM 层是一个问题性的边缘情况,并将引发以下错误:
UnknownError: [_Derived_] CUDNN_STATUS_BAD_PARAM in tensorflow/stream_executor/cuda/cuda_dnn.cc(1496): ➥ 'cudnnSetRNNDataDescriptor( data_desc.get(), data_type, layout, ➥ max_seq_length, batch_size, data_size, seq_lengths_array, ➥ (void*)&padding_fill)' [[{{node cond_38/then/_0/CudnnRNNV3}}]] [[sequential/lstm/StatefulPartitionedCall]] [[gradient_tape/sequential/embedding/embedding_lookup/Reshape/_42]] ➥ [Op:__inference_train_function_8225] Function call stack: train_function -> train_function -> train_function
由于这个原因,在将数据提供给模型之前,您必须确保从数据中过滤掉空评论。
有了这个,我们将训练我们定义的模型。
9.6.3 训练和评估模型
训练和评估代码与我们之前讨论的实现相同。因此,我们将不再赘述讨论。当您训练新模型时,您将看到类似于以下的结果。
当你训练模型时,你将达到略高于我们以前体验到的验证准确性的验证准确性:
Epoch 1/25 2427/2427 [==============================] - 30s 12ms/step - loss: 0.7552 - ➥ accuracy: 0.7949 - val_loss: 0.3942 - val_accuracy: 0.8277 - lr: 0.0010 Epoch 2/25 ... Epoch 8/25 2427/2427 [==============================] - 29s 12ms/step - loss: 0.3059 - ➥ accuracy: 0.9312 - val_loss: 0.6839 - val_accuracy: 0.8130 - lr: 1.0000e-04
评估模型可以通过运行以下代码完成:
test_ds = get_tf_pipeline(ts_x, ts_y, batch_size=128) model.evaluate(test_ds)
看起来添加一个嵌入层也会导致稍微更高的测试性能:
87/87 [==============================] - 0s 5ms/step - loss: 0.7214 - accuracy: 0.8111
记住我们说过不应该单凭准确性来信任模型。现在让我们深入一点,看看我们的模型是否在做出合理的预测。一个简单的方法是检查测试集中前 k 个正面评价和前 k 个负面评价,并进行视觉检查。当我们完成评估时,我们耗尽了 tf.data 流水线。因此,我们需要重新定义数据流水线:
test_ds = get_tf_pipeline(ts_x, ts_y, batch_size=128)
然后,我们将逐批次进行,并且对于每个批次,我们将把输入、预测和目标分别存储在三个单独的列表中:test_x,test_pred 和 test_y:
test_x = [] test_pred = [] test_y = [] for x, y in test_ds: test_x.append(x) test_pred.append(model.predict(x)) test_y.append(y) test_x = [doc for t in test_x for doc in t.numpy().tolist()] test_pred = tf.concat(test_pred, axis=0).numpy() test_y = tf.concat(test_y, axis=0).numpy()
我们将使用 argsort 来获取排序预测数组的索引。这样,数组的开头将包含最负面的评价的索引,而数组的末尾将包含最正面的评价的索引。让我们取最上面的五个和最下面的五个评价来进行视觉检查:
sorted_pred = np.argsort(test_pred.flatten()) min_pred = sorted_pred[:5] max_pred = sorted_pred[-5:] print("Most negative reviews\n") print("="*50) for i in min_pred: print(" ".join(tokenizer.sequences_to_texts([test_x[i]])), '\n') print("\nMost positive reviews\n") print("="*50) for i in max_pred: print(" ".join(tokenizer.sequences_to_texts([test_x[i]])), '\n')
让我们来检查结果:
Most negative reviews ================================================== buy game high rating promise gameplay saw youtube story so-so graphic ➥ mediocre control terrible could not adjust control option preference ... attempt install game quad core windows 7 pc zero luck go back forth try ➥ every suggestion rockstar support absolutely useless game ... way product 5 star 28 review write tone lot review similar play 2 song ➥ expert drum say unless play tennis shoe fact screw not flush mean feel ➥ every kick specifically two screw leave plus pedal completely torn ➥ mount screw something actually go wrong pedal instal unscrew send back ➥ ea unk interactive stranger unk unk genre develop operation flashpoint various ➥ real-life computer sims military folk unk know come deliver good ➥ recreation ultra deadly unk modern combat engagement arma attempt ➥ simulate `` unk firepower '' soldier combine arm warfare set fictional ➥ sprawl island nation conveniently mirror terrain middle eastern country not cup tea Most positive reviews ================================================== find something love every final fantasy game play thus far ff13 different ➥ really appreciate square enix 's latest trend shake gameplay new ➥ release still hammer best look ... know little squad base game genre know game fun not unk fun obliterate ➥ enemy mobile suit satisfy blow zombie head resident evil graphic ➥ presentation solid best franchise yes ... okay hdtv monitor cause ps3 switch game movie widescreen fullscreen every 5 ➥ minute someone tell need get hd component cable look price saw brand ➥ name sony much money basically name brand pay fancy retail packaging ➥ generic cable get quality without fancy packaging name brand embed ➥ cable favor save money absolutely phenomenal gaming mouse love programmable size button mouse ➥ surprising ergonomic design ... first motorstorm come unk racing type one pioneer physic base race every ➥ track lot branch path every branch suitable different class vehicle ➥ take next level race much bigger apart mud also obstacles individual ➥ vehicle class small vehicle get stuck plant unk hazard time lot physic ➥ people complain vehicle slide
我们可以自信地说,我们的情感分析器取得了成功!模型识别出的负面和正面评价似乎都放在了正确的位置。我们不得不克服许多与数据质量、数据准备和模型设计有关的障碍。在所有这些困难中,我们都坚持了下来!在下一章中,我们将讨论另一个被称为语言建模的 NLP 任务。
练习 6
Skip-gram 等词向量算法使用直接连接到与词汇表大小相同的密集层的嵌入层。如果你有一个大小为 500 的词汇表,想要生成 32 维的词向量,并且有一个具有 500 个单元和 softmax 激活的密集层,你会如何实现一个模型?该模型接受大小为(None,500)的(批量维度为 None)one-hot 编码的单词向量。
摘要
- NLTK 库提供了一个 API 来执行各种文本预处理任务,例如将文本标记为单词、删除停用词、词形还原等等。
- 预处理任务需要谨慎处理。例如,在删除停用词时,单词“not”不应该被删除。这是因为在情感分析任务中,“not”这个词承载着非常重要的信息。
- tensorflow.keras.preprocessing.text.Tokenizer 可以用来将文本转换为数字。这是通过 Tokenizer 首先构建一个将每个唯一单词映射到唯一 ID 的字典。然后给定的文本可以被转换为一系列 ID。
- 填充是一种将可变长度文本转换为相同长度的技术。
- 填充工作是通过在给定文本语料库中将所有序列填充到固定长度,通过在末尾或开头插入零来完成的。
- 在处理文本等变长序列时,还有一种称为“桶装”(bucketing)的策略,用于将相似长度的文本序列一起进行批处理。这有助于模型保持内存占用小,同时不浪费在过多填充上的计算。
- 在 TensorFlow 中,你可以使用 tf.data.experimental.bucket_by_sequence_length() 来对文本序列进行桶装。
- LSTM(长短期记忆)模型在解决自然语言处理任务方面表现出卓越的性能。
- LSTM 模型通过从一个时间步到下一个时间步,同时处理该时间步的输入以产生每个时间步的输出来工作。
- LSTM 模型具有存储长期和短期记忆的机制。这是通过控制 LSTM 单元中信息流动的门控机制实现的。
- 单词嵌入是一种文本编码方法,优于一热编码,并且在生成单词的数值表示时具有保留单词语义的能力。
练习答案
练习 1
s = s.replace("-", ' ') tokens = word_tokenize(s) pos_tags = nltk.pos_tag(tokens) clean_text = [ lemmatizer.lemmatize(w, pos=p[0].lower()) if p[0]=='V' else w for (w, p) in pos_tags ]
练习 2
s = “a_b_B_c_d_a_D_b_d_d” tok = Tokenizer(num_words = 3, split=”_”, lower=True) tok.fit_on_texts([s]) Most common words get the lowest word ID (starting from 1). ➥ tok.texts_to_sequences([s]) will produce [[3,2,2,1,3,1,2,1,1]]
练习 3
bucket_fn = tf.data.experimental.bucket_by_sequence_length( lambda x: tf.cast(tf.shape(x)[0],'int32'), bucket_boundaries=[10, 25, 30], bucket_batch_sizes=[batch_size,batch_size,batch_size, batch_size], padded_shapes=None, padding_values=0, pad_to_bucket_boundary=True )
练习 4
inp = tf.keras.layers.Input(shape=(None, 30)) lstm_out = tf.keras.layers.LSTM(32, return_sequences=True)(inp) sum_out = tf.keras.layers.Add(axis=1)(lstm_out) dense_out = tf.keras.layers.Dense(10, activation=’softmax’)(sum_out)
练习 5
A - (25+50)/(10+25+50) B - (10+50)/(10+25+50) C - (10+25)/(10+25+50)
练习 6
tf.keras.models.Sequential( [ tf.keras.layers.Embedding(input_dim=500, output_dim=32, input_shape=(500,)), tf.keras.layers.Dense(500, activation=’softmax’) ])
第十章:用 TensorFlow 进行自然语言处理:语言建模
本章涵盖
- 使用 TensorFlow 构建自然语言处理数据管道
- 实现基于 GRU 的语言模型
- 使用困惑度指标评估语言模型
- 定义一个推断模型,从训练好的模型中生成新的文本
- 实现束搜索以提升生成文本的质量
在上一章中,我们讨论了一个重要的自然语言处理任务,称为情感分析。在那一章中,你使用了一个视频游戏评论数据集,并训练了一个模型来分析文本,预测评论是否带有负面或正面情感。你学习了各种预处理步骤,可以执行这些步骤以提高文本的质量,例如去除停用词和词形还原(即将单词转换为基本形式;例如,将复数转换为单数)。你使用了一种特殊类型的模型,称为长短期记忆(LSTM)。LSTM 模型可以处理诸如句子之类的序列,并学习它们之间的关系和依赖以产生结果。LSTM 模型通过保持一个包含有关过去信息的状态(或记忆)来执行这一任务,因为它逐个元素地处理序列。LSTM 模型可以使用它已经看到的过去输入的记忆以及当前输入,在任何给定时间产生一个输出。
在本章中,我们将讨论一项称为语言建模的新任务。语言建模一直是自然语言处理的核心。语言建模是指在给定一系列先前单词的情况下预测下一个单词的任务。例如,给定句子“I went swimming in the ____”,模型将预测单词“pool”。像 BERT(双向编码器表示来自 Transformers 的模型,这是一种基于 Transformer 的模型)这样的开创性模型是使用语言建模任务进行训练的。这是语言建模如何帮助实现创新模型,并在各种领域和用例中得到应用的一个典型例子。
在我看来,语言建模在自然语言处理领域是一个被忽视的角色。它没有得到足够的重视,主要是因为该任务本身的使用案例有限。然而,语言建模可以为解决其他下游使用案例(例如信息检索、问答、机器翻译等)提供急需的语言知识(例如语义、语法、依赖解析等)。因此,作为一个自然语言处理从业者,你必须理解语言建模任务。
在本章中,您将构建一个语言模型。您将学习有关各种预处理步骤的知识,例如使用 n-grams 而不是完整单词作为模型的特征来减小词汇量的大小。您可以通过每 n 个字符分割文本来将任何文本转换为 n-grams(例如,如果使用 bi-grams,则 aabbbccd 变为 aa、bb、bc 和 cd)。您将定义一个 tf.data 数据流水线,它将为我们完成大部分预处理工作。接下来,您将使用一种被称为门控循环单元(GRU)的 LSTM 模型的密切关联方法来执行语言建模任务。GRU 比 LSTM 模型简单得多,训练速度更快,同时保持与 LSTM 模型相似的性能。我们将使用一种称为困惑度的特殊度量标准来衡量模型的好坏。困惑度衡量了模型在给定前几个词的情况下看到下一个词时的惊讶程度。在本章后面的部分,您将更多地了解这个度量标准。最后,您将学习一种称为波束搜索的技术,可以显著提高模型生成的文本的质量。
10.1 处理数据
您一直在密切关注一批新一代的深度学习模型,被称为 Transformer。这些模型是使用语言建模进行训练的。这是一种可以用来训练自然语言处理模型以生成故事/Python 代码/电影剧本的技术,具体取决于使用的训练数据。其思想是当一个由 n 个单词组成的序列中,预测第 n+1 个单词。训练数据可以从文本语料库中轻松生成,只需将一个文本序列作为输入,将其向右移动 1 位以生成目标序列。这可以在字符级、单词级或 n-gram 级别上进行。我们将使用两个连续的单词作为语言建模任务的 n-gram。我们将使用 Facebook 的一个名为 bAbI 的儿童故事数据集(research.fb.com/downloads/babi/
)。您将创建一个 TensorFlow 数据流水线,用于执行这些转换以生成输入和目标序列。
10.1.1 什么是语言建模?
我们已经简要讨论了语言建模的任务。简而言之,语言建模对于文本w[1]、w[2],…、w[n][-1]、w[n],其中w[i]是文本中的第i个词,计算给定w[1]、w[2],…、w[n][-1]时,w[n]的概率。在数学上表示为:
P(w [n]|w[1], w[2], …, w[n][-1])
换句话说,它预测给定w[1]、w[2],…、w[n][-1]时的w[n]。在训练模型时,我们训练它最大化这个概率;换句话说:
argmax[W] P(w [n]|w[1], w[2], …, w[n][-1])
在这种概率计算中,使用了具有可训练权重/参数W
的模型。对于大文本来说,这种计算变得计算上不可行,因为我们需要从当前单词一直回溯到第一个单词。为了使其计算可行,让我们使用马尔科夫性质,它指出你可以使用有限的历史来近似这个序列;换句话说
P(w [n]|w[1], w[2], …, w[n][-1]) ≈ P(w [n]|w[k], w[k+1], …, w[n][-1]) for some k
如您所想象的那样,k越小,近似效果越好。
闭合任务
类似 BERT 的 Transformer 模型使用了一种称为masked language modeling的语言模型变体。蒙面语言建模受到了闭合*任务或者填空测试的启发。这个想法是在给出一个带有一个或多个空白处的句子时,要求学生填写空白处的单词。这在语言评估测试中被用来衡量学生的语言能力。在蒙面语言建模中,模型变成了学生。单词随机地从输入中删除,并且要求模型预测缺失的单词。这构成了像 BERT 这样的模型中使用的训练过程的基础。
10.1.2 下载和玩弄数据
作为第一步,让我们使用以下列表中的代码下载数据集。
列表 10.1 下载亚马逊评论数据集
import os import requests import tarfile import shutil # Retrieve the data if not os.path.exists(os.path.join('data', 'lm','CBTest.tgz')): ❶ url = "http:/ /www.thespermwhale.com/jaseweston/babi/CBTest.tgz" # Get the file from web r = requests.get(url) if not os.path.exists(os.path.join('data','lm')): os.mkdir(os.path.join('data','lm')) # Write to a file with open(os.path.join('data', 'lm', 'CBTest.tgz'), 'wb') as f: ❷ f.write(r.content) else: print("The tar file already exists.") if not os.path.exists(os.path.join('data', 'lm', 'CBTest')): ❸ # Write to a file tarf = tarfile.open(os.path.join("data","lm","CBTest.tgz")) tarf.extractall(os.path.join("data","lm")) else: print("The extracted data already exists")
❶ 如果尚未下载包含数据的tgz
文件,则下载数据。
❷ 将下载的数据写入磁盘。
❸ 如果tgz
文件可用但尚未解压缩,则将其解压缩到给定的目录。
如果数据不存在,列表 10.1 将会下载到本地文件夹并解压缩内容。如果您查看数据文件夹(具体地说,data/lm/CBTest/data),您会看到它有三个文本文件:cbt_train.txt,cbt_valid.txt 和 cbt_test.txt。每个文件包含一组故事。我们将在内存中读取这些文件。我们将在下一个列表中定义一个简单的函数来将这些文件读入内存。
列表 10.2 在 Python 中读取故事
def read_data(path): stories = [] ❶ with open(path, 'r') as f: s = [] ❷ for row in f: if row.startswith("_BOOK_TITLE_"): ❸ if len(s)>0: stories.append(' '.join(s).lower()) ❹ s = [] ❺ s.append(row) ❻ if len(s)>0: stories.append(' '.join(s).lower()) ❼ return stories
❶ 定义一个列表来保存所有的故事。
❷ 定义一个列表来保存一个故事。
❸ 当我们遇到以 _BOOK_TITLE 开头的行时,它是一个新的故事。
❹ 如果我们看到了一个新故事的开始,将已存在的故事添加到故事列表中。
❺ 重置包含当前故事的列表。
❻ 将当前文本行添加到列表s
中。
❼ 在循环结束后处理最后一个故事仍然存在于s
中的边界情况。
这段代码打开给定的文件并逐行读取它。我们还有一些额外的逻辑来将文本分割成单独的故事。正如前面所说,每个文件包含多个故事。我们想在最后创建一个字符串列表,其中每个字符串是一个单独的故事。前一个函数就是这样做的。接下来,我们可以读取文本文件并将它们存储在变量中,如下所示:
stories = read_data(os.path.join('data','lm','CBTest','data','cbt_train.txt')) val_stories = read_data(os.path.join('data','lm','CBTest','data','cbt_valid.txt')) test_stories = read_data(os.path.join('data','lm','CBTest','data','cbt_test.txt'))
这里,故事将包含训练数据,val_stories 将包含验证数据,最后,test_stories 将包含测试数据。让我们快速查看一些关于数据集的高级信息:
print("Collected {} stories (train)".format(len(stories))) print("Collected {} stories (valid)".format(len(val_stories))) print("Collected {} stories (test)".format(len(test_stories))) print(stories[0][:100]) print('\n', stories[10][:100])
此代码检查每个数据集中有多少个故事,并打印训练集中第 11 个故事的前 100 个字符:
Collected 98 stories (train) Collected 5 stories (valid) Collected 5 stories (test) chapter i. -lcb- chapter heading picture : p1.jpg -rcb- how the fairies ➥ were not invited to court . a tale of the tontlawald long , long ago there stood in the midst of a ➥ country covered with lakes a
出于好奇,让我们也分析一下我们要处理的词汇量。为了分析词汇量,我们将首先将我们的字符串列表转换为字符串列表的列表,其中每个字符串都是一个单词。然后,我们可以利用内置的 Counter 对象来获取文本语料库的单词频率。之后,我们将创建一个 pandas Series 对象,其中频率作为值,单词作为索引,并查看有多少单词出现超过 10 次:
from collections import Counter # Create a large list which contains all the words in all the reviews data_list = [w for doc in stories for w in doc.split(' ')] # Create a Counter object from that list # Counter returns a dictionary, where key is a word and the value is the frequency cnt = Counter(data_list) # Convert the result to a pd.Series freq_df = pd.Series( list(cnt.values()), index=list(cnt.keys()) ).sort_values(ascending=False) # Count of words >= n frequent n=10 print("Vocabulary size (>={} frequent): {}".format(n, (freq_df>=n).sum()))
这将返回
, 348650 the 242890 .\n 192549 and 179205 to 120821 a 101990 of 96748 i 79780 he 78129 was 66593 dtype: int64 Vocabulary size (>=10 frequent): 14473
TensorFlow 实战(四)(4)https://developer.aliyun.com/article/1522808