精通 TensorFlow 1.x:6~10(1)https://developer.aliyun.com/article/1426819
GRU 模型的完整代码在 Jupyter 笔记本中提供。GRU 模型的结果如下:
train mse = 0.0019633215852081776 test mse = 0.014307591132819653 test rmse = 0.11961434334066987
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckbMoSmQ-1681566456879)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/2d9ac55a-7f30-406b-934d-3cba2d9c6a91.png)]
我们鼓励您探索 TensorFlow 中可用的其他选项来创建循环神经网络。现在让我们在 TensorFlow 的一个高级库中尝试相同的示例。
对于下一节,您可以按照 Jupyter 笔记本中的代码ch-07b_RNN_TimeSeries_Keras
。
用于 Keras RNN 模型的数据集预处理
与使用较低级别 TensorFlow 类和方法构建相比,在 Keras 中构建 RNN 网络要简单得多。对于 Keras,我们预先处理数据,如前面部分所述,以获得受监督的机器学习时间序列数据集:X_train, Y_train, X_test, Y_test
。
从这里开始,预处理有所不同。对于 Keras,输入必须是(samples, time steps, features)
形状。当我们将数据转换为监督机器学习格式时,在重塑数据时,我们可以将时间步长设置为 1,从而将所有输入时间步长作为特征,或者我们可以设置时间步长为实际的时间步数,从而为每个时间步长提供特征集。换句话说,我们之前获得的X_train
和X_test
数据集可以重新整形为以下方法之一:
方法 1:n
时间步长与1
特征:
X_train.reshape(X_train.shape[0], X_train.shape[1],1)
方法 2:1
时间步长n
特征:
X_train.reshape(X_train.shape[0], 1, X_train.shape[1])
在本章中,我们将对特征大小为 1 的数据集进行整形,因为我们只使用一个变量作为输入:
# reshape input to be [samples, time steps, features] X_train = X_train.reshape(X_train.shape[0], X_train.shape[1],1) X_test = X_test.reshape(X_test.shape[0], X_train.shape[1], 1)
Keras 中的简单 RNN
通过添加具有内部神经元数量和输入张量形状的SimpleRNN
层,可以在 Keras 中轻松构建 RNN 模型,不包括样本维数。以下代码创建,编译和拟合SimpleRNN
:
# create and fit the SimpleRNN model model = Sequential() model.add(SimpleRNN(units=4, input_shape=(X_train.shape[1], X_train.shape[2]))) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') model.fit(X_train, Y_train, epochs=20, batch_size=1)
由于我们的数据集很小,我们使用batch_size
为 1 并训练 20 次迭代,但对于较大的数据集,您需要调整这些和其他超参数的值。
该模型的结构如下:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= simple_rnn_1 (SimpleRNN) (None, 4) 24 _________________________________________________________________ dense_1 (Dense) (None, 1) 5 ================================================================= Total params: 29 Trainable params: 29 Non-trainable params: 0
训练的结果如下:
Epoch 1/20 95/95 [==============================] - 0s - loss: 0.0161 Epoch 2/20 95/95 [==============================] - 0s - loss: 0.0074 Epoch 3/20 95/95 [==============================] - 0s - loss: 0.0063 Epoch 4/20 95/95 [==============================] - 0s - loss: 0.0051 -- epoch 5 to 14 removed for the sake of brevity -- Epoch 14/20 95/95 [==============================] - 0s - loss: 0.0021 Epoch 15/20 95/95 [==============================] - 0s - loss: 0.0020 Epoch 16/20 95/95 [==============================] - 0s - loss: 0.0020 Epoch 17/20 95/95 [==============================] - 0s - loss: 0.0020 Epoch 18/20 95/95 [==============================] - 0s - loss: 0.0020 Epoch 19/20 95/95 [==============================] - 0s - loss: 0.0020 Epoch 20/20 95/95 [==============================] - 0s - loss: 0.0020
损失从 0.0161 开始,平稳在 0.0020。让我们做出预测并重新调整预测和原件。我们使用 Keras 提供的函数来计算均方根误差:
from keras.losses import mean_squared_error as k_mse from keras.backend import sqrt as k_sqrt import keras.backend as K # make predictions y_train_pred = model.predict(X_train) y_test_pred = model.predict(X_test) # invert predictions y_train_pred = scaler.inverse_transform(y_train_pred) y_test_pred = scaler.inverse_transform(y_test_pred) #invert originals y_train_orig = scaler.inverse_transform(Y_train) y_test_orig = scaler.inverse_transform(Y_test) # calculate root mean squared error trainScore = k_sqrt(k_mse(y_train_orig[:,0], y_train_pred[:,0]) ).eval(session=K.get_session()) print('Train Score: {0:.2f} RMSE'.format(trainScore)) testScore = k_sqrt(k_mse(y_test_orig[:,0], y_test_pred[:,0]) ).eval(session=K.get_session()) print('Test Score: {0:.2f} RMSE'.format(testScore))
我们得到以下结果:
Train Score: 23.27 RMSE Test Score: 54.13 RMSE
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-21Swd4gs-1681566456879)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/93bcf886-fc39-4e77-8fa0-d72af7ac310b.png)]
我们可以看到,这不像我们在 TensorFlow 部分得到的那样完美;但是,这种差异是因为超参数值。我们留给您尝试不同的超参数值来调整此 Keras 模型以获得更好的结果。
Keras 中的 LSTM
创建 LSTM 模型只需添加 LSTM 层而不是SimpleRNN
层,如下所示:
model.add(LSTM(units=4, input_shape=(X_train.shape[1], X_train.shape[2])))
模型结构如下所示:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= lstm_1 (LSTM) (None, 4) 96 _________________________________________________________________ dense_1 (Dense) (None, 1) 5 ================================================================= Total params: 101 Trainable params: 101 Non-trainable params: 0 _________________________________________________________________
笔记本ch-07b_RNN_TimeSeries_Keras
中提供了 LSTM 模型的完整代码。
由于 LSTM 模型具有更多需要训练的参数,对于相同数量的迭代(20 个周期),我们得到更高的误差分数。我们留给您探索周期和其他超参数的各种值,以获得更好的结果:
Train Score: 32.21 RMSE Test Score: 84.68 RMSE
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ybVvBfm-1681566456879)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/b64ddef8-5b68-44b8-842e-779177ef1557.png)]
Keras 中的 GRU
使用 TensorFlow 和 Keras 的一个优点是它们可以轻松创建模型。与 LSTM 一样,创建 GRU 模型只需添加 GRU 层而不是 LSTM 或SimpleRNN
层,如下所示:
model.add(GRU(units=4, input_shape=(X_train.shape[1], X_train.shape[2])))
模型结构如下:
Layer (type) Output Shape Param # ================================================================= gru_1 (GRU) (None, 4) 72 _________________________________________________________________ dense_1 (Dense) (None, 1) 5 ================================================================= Total params: 77 Trainable params: 77 Non-trainable params: 0
笔记本ch-07b_RNN_TimeSeries_Keras
中提供了 GRU 模型的完整代码。
正如预期的那样,GRU 模型显示出与 LSTM 几乎相同的表现,我们让您尝试使用不同的超参数值来优化此模型:
Train Score: 31.49 RMSE Test Score: 92.75 RMSE
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VuVFWQeq-1681566456880)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/f5e8871e-1511-408f-ab58-0b3e3b79644d.png)]
总结
时间序列数据是基于序列的数据,因此 RNN 模型是从时间序列数据中学习的相关架构。在本章中,您学习了如何使用 TensorFlow(一个低级库)和 Keras(一个高级库)创建不同类型的 RNN 模型。我们只介绍了SimpleRNN
,LSTM 和 GRU,但您应该探索可以使用 TensorFlow 和 Keras 创建的许多其他 RNN 变体。
在下一章中,我们将使用当前章节和前几章中构建的基础为各种自然语言处理(NLP)任务创建文本数据的 RNN 模型。
八、TensorFlow 和 Keras 中的用于文本数据的 RNN
文本数据可以被视为一系列字符,单词,句子或段落。 循环神经网络(RNN)已被证明是非常有用的序列神经网络结构。为了将神经网络模型应用于自然语言处理(NLP)任务,文本被视为单词序列。事实证明,这对于 NLP 任务非常成功,例如:
- 问题回答
- 会话智能体或聊天机器人
- 文本分类
- 情感分析
- 图像标题或描述文本生成
- 命名实体识别
- 语音识别和标记
NLP 与 TensorFlow 深度学习技术是一个广阔的领域,很难在一章中捕捉到。因此,我们尝试使用 Tensorflow 和 Keras 为您提供该领域中最流行和最重要的示例。一旦掌握了本章的内容,不要忘记探索和试验 NLP 的其他领域。
在本章中,我们将了解以下主题:
- 词向量表示
- 为 word2vec 模型准备数据
- TensorFlow 和 Keras 中的 SkipGram 模型
- 使用 t-SNE 可视化单词嵌入
- TensorFlow 和 Keras 中使用 LSTM 模型的文本生成示例
词向量表示
为了从文本数据中学习神经网络模型的参数,首先,我们必须将文本或自然语言数据转换为可由神经网络摄取的格式。神经网络通常以数字向量的形式摄取文本。将原始文本数据转换为数字向量的算法称为字嵌入算法。
一种流行的字嵌入方法是我们在 MNIST 图像分类中看到的单热编码。假设我们的文本数据集由 60,000 个字典单词组成。然后,每个单词可以由具有 60,000 个元素的单热编码向量表示,其中除了表示具有值 1 的该单词的一个元素之外,所有其他元素具有零值。
然而,单热编码方法有其缺点。首先,对于具有大量单词的词汇,单热词向量的维数变得非常大。其次,人们无法找到与单热编码向量的单词相似性。例如,假设猫和小猫的向量分别为[1 0 0 0 0 0]
和[0 0 0 0 0 1]
。这些向量没有相似之处。
还有其他基于语料库的方法,用于将基于文本的语料库转换为数字向量,例如:
- 单词频率 - 反向文档频率(TF-IDF)
- 潜在语义分析(LSA)
- 主题建模
最近,用数值向量表示单词的焦点已转移到基于分布假设的方法,这意味着具有相似语义含义的单词倾向于出现在类似的上下文中。
两种最广泛使用的方法称为 word2vec 和 GloVe。我们将在本章中使用 word2vec 进行练习。正如我们在前一段中所了解到的,单热编码给出了语料库字典中单词总数大小的维数。使用 word2vec 创建的单词向量的维度要低得多。
word2vec 系列模型使用两种架构构建:
- CBOW:训练模型以学习给定上下文词的中心词的概率分布。因此,给定一组上下文单词,模型以您在高中语言课程中所做的填空方式预测中心单词。 CBOW 架构最适用于具有较小词汇表的数据集。
- SkipGram:训练模型以学习给定中心词的上下文词的概率分布。因此,给定一个中心词,模型以您在高中语言课程中完成的句子方式预测语境词。
例如,让我们考虑一下这句话:
Vets2data.org is a non-profit for educating the US Military Veterans Community on Artificial Intelligence and Data Science.
在 CBOW 架构中,给出单词Military
和Community
,模型学习单词Veterans
的概率,并在 SkipGram 架构中,给出单词Veterans
,模型学习单词Military
和Community
的概率。
word2vec 模型以无监督的方式从文本语料库中学习单词向量。文本语料库分为成对的上下文单词和目标单词。虽然这些对是真正的对,但是伪对是用随机配对的上下文词和上下文词生成的,因此在数据中产生噪声。训练分类器以学习用于区分真对和假对的参数。该分类器的参数成为 word2vec 模型或单词向量。
关于 word2vec 理论背后的数学和理论的更多信息可以从以下论文中学到:
Mikolov, T., I. Sutskever, K. Chen, G. Corrado, and J. Dean. Distributed Representations of Words and Phrases and Their Compositionality. _Advances in Neural Information Processing Systems_, 2013, pp. 3111–3119. Mikolov, T., K. Chen, G. Corrado, and J. Dean. Efficient Estimation of Word Representations in Vector Space. _arXiv_, 2013, pp. 1–12. Rong, X. word2vec Parameter Learning Explained. _arXiv:1411.2738_, 2014, pp. 1–19. Baroni, M., G. Dinu, and G. Kruszewski. Don’t Count, Predict! A Systematic Comparison of Context-Counting vs. Context-Predicting Semantic Vectors. 2014.
您应该使用 GloVe 和 word2vec 练习并应用适用于您的文本数据的方法。
有关 GLoVe 算法的更多信息可以从以下文章中学习:
Pennington, J., R. Socher, and C. Manning. GloVe: Global Vectors for Word Representation. 2014.
让我们通过在 TensorFlow 和 Keras 中创建单词向量来理解 word2vec 模型。
您可以按照 Jupyter 笔记本中的下几节的代码ch-08a_Embeddings_in_TensorFlow_and_Keras
。
用于 word2vec 模型的数据准备
我们将使用流行的 PTB 和 text8 数据集进行演示。
PennTreebank(PTB)数据集是在 UPenn 进行的 Penn Treebank 项目的副产品。 PTB 项目团队在华尔街日报三年的故事中提取了大约一百万字,并以 Treebank II 风格对其进行了标注。 PTB 数据集有两种形式: 基本示例,大小约为 35 MB, 高级示例,大小约为 235 MB。我们将使用由 929K 字组成的简单数据集进行训练,73K 字用于验证,82K 字用于测试。建议您浏览高级数据集。有关 PTB 数据集的更多详细信息,请访问此链接 。
text8 数据集是一个较短的清理版本的大型维基百科数据转储,大小约为 1GB。有关如何创建 text8 数据集的过程,请参见此链接。
使用我们的自定义库datasetslib
中的load_data
代码加载数据集:
load_data()
函数执行以下操作:
- 如果数据集的 URL 在本地不可用,它将从数据集的 URL 下载数据存档。
- 由于
PTB
数据有三个文件,它首先从训练文件中读取文本,而对于text8
,它从归档中读取第一个文件。 - 它将训练文件中的单词转换为词汇表,并为每个词汇单词分配一个唯一的数字,单词 ID,将其存储在集合
word2id
中,并准备反向词典,这样我们就可以从 ID 中查找单词,并将其存储在集合id2word
中。 - 它使用集合
word2id
将文本文件转换为 ID 序列。 - 因此,在
load_data
的末尾,我们在训练数据集中有一系列数字,在集合id2word
中有一个 ID 到字的映射。
让我们看一下从 text8 和 PTB 数据集加载的数据:
加载和准备 PTB 数据集
首先导入模块并加载数据如下::
from datasetslib.ptb import PTBSimple ptb = PTBSimple() # downloads data, converts words to ids, converts files to a list of ids ptb.load_data() print('Train :',ptb.part['train'][0:5]) print('Test: ',ptb.part['test'][0:5]) print('Valid: ',ptb.part['valid'][0:5]) print('Vocabulary Length = ',ptb.vocab_len)
每个数据集的前五个元素以及词汇长度打印如下:
Train : [9970, 9971, 9972, 9974, 9975] Test: [102, 14, 24, 32, 752] Valid: [1132, 93, 358, 5, 329] Vocabulary Length = 10000
我们将上下文窗口设置为两个单词并获得 CBOW 对:
ptb.skip_window=2 ptb.reset_index_in_epoch() # in CBOW input is the context word and output is the target word y_batch, x_batch = ptb.next_batch_cbow() print('The CBOW pairs : context,target') for i in range(5 * ptb.skip_window): print('(', [ptb.id2word[x_i] for x_i in x_batch[i]], ',', y_batch[i], ptb.id2word[y_batch[i]], ')')
输出是:
The CBOW pairs : context,target ( ['aer', 'banknote', 'calloway', 'centrust'] , 9972 berlitz ) ( ['banknote', 'berlitz', 'centrust', 'cluett'] , 9974 calloway ) ( ['berlitz', 'calloway', 'cluett', 'fromstein'] , 9975 centrust ) ( ['calloway', 'centrust', 'fromstein', 'gitano'] , 9976 cluett ) ( ['centrust', 'cluett', 'gitano', 'guterman'] , 9980 fromstein ) ( ['cluett', 'fromstein', 'guterman', 'hydro-quebec'] , 9981 gitano ) ( ['fromstein', 'gitano', 'hydro-quebec', 'ipo'] , 9982 guterman ) ( ['gitano', 'guterman', 'ipo', 'kia'] , 9983 hydro-quebec ) ( ['guterman', 'hydro-quebec', 'kia', 'memotec'] , 9984 ipo ) ( ['hydro-quebec', 'ipo', 'memotec', 'mlx'] , 9986 kia )
现在让我们看看 SkipGram 对:
ptb.skip_window=2 ptb.reset_index_in_epoch() # in SkipGram input is the target word and output is the context word x_batch, y_batch = ptb.next_batch() print('The SkipGram pairs : target,context') for i in range(5 * ptb.skip_window): print('(',x_batch[i], ptb.id2word[x_batch[i]], ',', y_batch[i], ptb.id2word[y_batch[i]],')')
输出为:
The SkipGram pairs : target,context ( 9972 berlitz , 9970 aer ) ( 9972 berlitz , 9971 banknote ) ( 9972 berlitz , 9974 calloway ) ( 9972 berlitz , 9975 centrust ) ( 9974 calloway , 9971 banknote ) ( 9974 calloway , 9972 berlitz ) ( 9974 calloway , 9975 centrust ) ( 9974 calloway , 9976 cluett ) ( 9975 centrust , 9972 berlitz ) ( 9975 centrust , 9974 calloway )
加载和准备 text8 数据集
现在我们使用 text8 数据集执行相同的加载和预处理步骤:
from datasetslib.text8 import Text8 text8 = Text8() text8.load_data() # downloads data, converts words to ids, converts files to a list of ids print('Train:', text8.part['train'][0:5]) print('Vocabulary Length = ',text8.vocab_len)
我们发现词汇长度大约是 254,000 字:
Train: [5233, 3083, 11, 5, 194] Vocabulary Length = 253854
一些教程通过查找最常用的单词或将词汇量大小截断为 10,000 个单词来操纵此数据。 但是,我们使用了 text8 数据集的第一个文件中的完整数据集和完整词汇表。
准备 CBOW 对:
text8.skip_window=2 text8.reset_index_in_epoch() # in CBOW input is the context word and output is the target word y_batch, x_batch = text8.next_batch_cbow() print('The CBOW pairs : context,target') for i in range(5 * text8.skip_window): print('(', [text8.id2word[x_i] for x_i in x_batch[i]], ',', y_batch[i], text8.id2word[y_batch[i]], ')')
输出是:
The CBOW pairs : context,target ( ['anarchism', 'originated', 'a', 'term'] , 11 as ) ( ['originated', 'as', 'term', 'of'] , 5 a ) ( ['as', 'a', 'of', 'abuse'] , 194 term ) ( ['a', 'term', 'abuse', 'first'] , 1 of ) ( ['term', 'of', 'first', 'used'] , 3133 abuse ) ( ['of', 'abuse', 'used', 'against'] , 45 first ) ( ['abuse', 'first', 'against', 'early'] , 58 used ) ( ['first', 'used', 'early', 'working'] , 155 against ) ( ['used', 'against', 'working', 'class'] , 127 early ) ( ['against', 'early', 'class', 'radicals'] , 741 working )
准备 SkipGram 对:
text8.skip_window=2 text8.reset_index_in_epoch() # in SkipGram input is the target word and output is the context word x_batch, y_batch = text8.next_batch() print('The SkipGram pairs : target,context') for i in range(5 * text8.skip_window): print('(',x_batch[i], text8.id2word[x_batch[i]], ',', y_batch[i], text8.id2word[y_batch[i]],')')
输出为:
The SkipGram pairs : target,context ( 11 as , 5233 anarchism ) ( 11 as , 3083 originated ) ( 11 as , 5 a ) ( 11 as , 194 term ) ( 5 a , 3083 originated ) ( 5 a , 11 as ) ( 5 a , 194 term ) ( 5 a , 1 of ) ( 194 term , 11 as ) ( 194 term , 5 a )
准备小验证集
为了演示该示例,我们创建了一个包含 8 个单词的小型验证集,每个单词是从单词中随机选择的,其中单词 ID 在 0 到10 x 8
之间。
valid_size = 8 x_valid = np.random.choice(valid_size * 10, valid_size, replace=False) print(x_valid)
作为示例,我们将以下内容作为验证集:
valid: [64 58 59 4 69 53 31 77]
我们将使用此验证集通过打印五个最接近的单词来演示嵌入一词的结果。
TensorFlow 中的 SkipGram 模型
现在我们已经准备好了训练和验证数据,让我们在 TensorFlow 中创建一个 SkipGram 模型。
我们首先定义超参数:
batch_size = 128 embedding_size = 128 skip_window = 2 n_negative_samples = 64 ptb.skip_window=2 learning_rate = 1.0
batch_size
是要在单个批次中输入算法的目标和上下文单词对的数量embedding_size
是每个单词的单词向量或嵌入的维度ptb.skip_window
是在两个方向上的目标词的上下文中要考虑的词的数量n_negative_samples
是由 NCE 损失函数生成的负样本数,本章将进一步说明
在一些教程中,包括 TensorFlow 文档中的一个教程,还使用了一个参数num_skips
。在这样的教程中,作者选择了num_skips
(目标,上下文)对。例如,如果skip_window
是 2,那么对的总数将是 4,如果num_skips
被设置为 2,则只有两对将被随机选择用于训练。但是,我们考虑了所有的对以便保持训练练习简单。
定义训练数据的输入和输出占位符以及验证数据的张量:
inputs = tf.placeholder(dtype=tf.int32, shape=[batch_size]) outputs = tf.placeholder(dtype=tf.int32, shape=[batch_size,1]) inputs_valid = tf.constant(x_valid, dtype=tf.int32)
定义一个嵌入矩阵,其行数等于词汇长度,列等于嵌入维度。该矩阵中的每一行将表示词汇表中一个单词的单词向量。使用在 -1.0 到 1.0 之间均匀采样的值填充此嵌入矩阵。
# define embeddings matrix with vocab_len rows and embedding_size columns # each row represents vectore representation or embedding of a word # in the vocbulary embed_dist = tf.random_uniform(shape=[ptb.vocab_len, embedding_size], minval=-1.0,maxval=1.0) embed_matrix = tf.Variable(embed_dist,name='embed_matrix')
使用此矩阵,定义使用tf.nn.embedding_lookup()
实现的嵌入查找表。tf.nn.embedding_lookup()
有两个参数:嵌入矩阵和输入占位符。 查找函数返回inputs
占位符中单词的单词向量。
# define the embedding lookup table # provides the embeddings of the word ids in the input tensor embed_ltable = tf.nn.embedding_lookup(embed_matrix, inputs)
embed_ltable
也可以解释为输入层顶部的嵌入层。接下来,将嵌入层的输出馈送到 softmax 或噪声对比估计(NCE)层。 NCE 基于一个非常简单的想法,即训练基于逻辑回归的二分类器,以便从真实和嘈杂数据的混合中学习参数。
总之,基于 softmax 损失的模型在计算上是昂贵的,因为在整个词汇表中计算概率分布并对其进行归一化。基于 NCE 损耗的模型将其减少为二分类问题,即从噪声样本中识别真实样本。
NCE 的基本数学细节可以在以下 NIPS 论文中找到:使用噪声对比估计高效学习词嵌入,作者 Andriy Mnih 和 Koray Kavukcuoglu。该论文可从此链接获得。
tf.nn.nce_loss()
函数在求值计算损耗时自动生成负样本:参数num_sampled
设置为等于负样本数(n_negative_samples
)。此参数指定要绘制的负样本数。
# define noise-contrastive estimation (NCE) loss layer nce_dist = tf.truncated_normal(shape=[ptb.vocab_len, embedding_size], stddev=1.0 / tf.sqrt(embedding_size * 1.0) ) nce_w = tf.Variable(nce_dist) nce_b = tf.Variable(tf.zeros(shape=[ptb.vocab_len])) loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_w, biases=nce_b, inputs=embed_ltable, labels=outputs, num_sampled=n_negative_samples, num_classes=ptb.vocab_len ) )
接下来,计算验证集中的样本与嵌入矩阵之间的余弦相似度:
- 为了计算相似性得分,首先,计算嵌入矩阵中每个单词向量的 L2 范数。
# Compute the cosine similarity between validation set samples # and all embeddings. norm = tf.sqrt(tf.reduce_sum(tf.square(embed_matrix), 1, keep_dims=True)) normalized_embeddings = embed_matrix / norm
- 查找验证集中的样本的嵌入或单词向量:
embed_valid = tf.nn.embedding_lookup(normalized_embeddings, inputs_valid)
- 通过将验证集的嵌入与嵌入矩阵相乘来计算相似性得分。
similarity = tf.matmul( embed_valid, normalized_embeddings, transpose_b=True)
这给出了具有(valid_size
,vocab_len
)形状的张量。张量中的每一行指的是验证词和词汇单词之间的相似性得分。
接下来,定义 SGD 优化器,学习率为 0.9,历时 50 个周期。
n_epochs = 10 learning_rate = 0.9 n_batches = ptb.n_batches(batch_size) optimizer = tf.train.GradientDescentOptimizer(learning_rate) .minimize(loss)
对于每个周期:
- 逐批在整个数据集上运行优化器。
ptb.reset_index_in_epoch() for step in range(n_batches): x_batch, y_batch = ptb.next_batch() y_batch = dsu.to2d(y_batch,unit_axis=1) feed_dict = {inputs: x_batch, outputs: y_batch} _, batch_loss = tfs.run([optimizer, loss], feed_dict=feed_dict) epoch_loss += batch_loss
- 计算并打印周期的平均损失。
epoch_loss = epoch_loss / n_batches print('\n','Average loss after epoch ', epoch, ': ', epoch_loss)
- 在周期结束时,计算相似性得分。
similarity_scores = tfs.run(similarity)
- 对于验证集中的每个单词,打印具有最高相似性得分的五个单词。
top_k = 5 for i in range(valid_size): similar_words = (-similarity_scores[i,:]) .argsort()[1:top_k + 1] similar_str = 'Similar to {0:}:' .format(ptb.id2word[x_valid[i]]) for k in range(top_k): similar_str = '{0:} {1:},'.format(similar_str, ptb.id2word[similar_words[k]]) print(similar_str)
最后,在完成所有周期之后,计算可在学习过程中进一步利用的嵌入向量:
final_embeddings = tfs.run(normalized_embeddings)
完整的训练代码如下:
n_epochs = 10 learning_rate = 0.9 n_batches = ptb.n_batches_wv() optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss) with tf.Session() as tfs: tf.global_variables_initializer().run() for epoch in range(n_epochs): epoch_loss = 0 ptb.reset_index() for step in range(n_batches): x_batch, y_batch = ptb.next_batch_sg() y_batch = nputil.to2d(y_batch, unit_axis=1) feed_dict = {inputs: x_batch, outputs: y_batch} _, batch_loss = tfs.run([optimizer, loss], feed_dict=feed_dict) epoch_loss += batch_loss epoch_loss = epoch_loss / n_batches print('\nAverage loss after epoch ', epoch, ': ', epoch_loss) # print closest words to validation set at end of every epoch similarity_scores = tfs.run(similarity) top_k = 5 for i in range(valid_size): similar_words = (-similarity_scores[i, :] ).argsort()[1:top_k + 1] similar_str = 'Similar to {0:}:'.format( ptb.id2word[x_valid[i]]) for k in range(top_k): similar_str = '{0:} {1:},'.format( similar_str, ptb.id2word[similar_words[k]]) print(similar_str) final_embeddings = tfs.run(normalized_embeddings)
这是我们分别在第 1 和第 10 周期之后得到的输出:
Average loss after epoch 0 : 115.644006802 Similar to we: types, downturn, internal, by, introduce, Similar to been: said, funds, mcgraw-hill, street, have, Similar to also: will, she, next, computer, 's, Similar to of: was, and, milk, dollars, $, Similar to last: be, october, acknowledging, requested, computer, Similar to u.s.: plant, increase, many, down, recent, Similar to an: commerce, you, some, american, a, Similar to trading: increased, describes, state, companies, in, Average loss after epoch 9 : 5.56538496033 Similar to we: types, downturn, introduce, internal, claims, Similar to been: exxon, said, problem, mcgraw-hill, street, Similar to also: will, she, ssangyong, audit, screens, Similar to of: seasonal, dollars, motor, none, deaths, Similar to last: acknowledging, allow, incorporated, joint, requested, Similar to u.s.: undersecretary, typically, maxwell, recent, increase, Similar to an: banking, officials, imbalances, americans, manager, Similar to trading: describes, increased, owners, committee, else,
最后,我们运行 5000 个周期的模型并获得以下结果:
Average loss after epoch 4999 : 2.74216903135 Similar to we: matter, noted, here, classified, orders, Similar to been: good, precedent, medium-sized, gradual, useful, Similar to also: introduce, england, index, able, then, Similar to of: indicator, cleveland, theory, the, load, Similar to last: dec., office, chrysler, march, receiving, Similar to u.s.: label, fannie, pressures, squeezed, reflection, Similar to an: knowing, outlawed, milestones, doubled, base, Similar to trading: associates, downturn, money, portfolios, go,
尝试进一步运行,最多 50,000 个周期,以获得更好的结果。
同样,我们在 50 个周期之后使用 text8 模型得到以下结果:
Average loss after epoch 49 : 5.74381046423 Similar to four: five, three, six, seven, eight, Similar to all: many, both, some, various, these, Similar to between: with, through, thus, among, within, Similar to a: another, the, any, each, tpvgames, Similar to that: which, however, although, but, when, Similar to zero: five, three, six, eight, four, Similar to is: was, are, has, being, busan, Similar to no: any, only, the, another, trinomial,
t-SNE 和单词嵌入可视化
让我们可视化我们在上一节中生成的单词嵌入。 t-SNE 是在二维空间中显示高维数据的最流行的方法。我们将使用 scikit-learn 库中的方法,并重用 TensorFlow 文档中给出的代码,来绘制我们刚学过的词嵌入的图形。
以下是我们如何实现该程序:
- 创建
tsne
模型:
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000, method='exact')
- 将要显示的嵌入数限制为 500,否则,图形变得非常难以理解:
n_embeddings = 500
- 通过调用
tsne
模型上的fit_transform()
方法并将final_embeddings
的第一个n_embeddings
作为输入来创建低维表示。
low_dim_embeddings = tsne.fit_transform( final_embeddings[:n_embeddings, :])
- 找到我们为图表选择的单词向量的文本表示:
labels = [ptb.id2word[i] for i in range(n_embeddings)]
- 最后,绘制嵌入图:
plot_with_labels(low_dim_embeddings, labels)
我们得到以下绘图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yn5sZmOZ-1681566456880)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/16116250-d5d1-4e33-943a-f9c465a7edbf.png)]t-SNE visualization of embeddings for PTB data set
同样,从 text8 模型中,我们得到以下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Isd1Q9Zw-1681566456880)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/mastering-tf-1x-zh/img/652fb621-e9a3-48bb-8270-ee1073f69df3.png)]t-SNE visualization of embeddings for text8 data set
Keras 中的 SkipGram 模型
使用 Keras 的嵌入模型的流程与 TensorFlow 保持一致。
- 在 Keras 函数式或顺序模型中创建网络架构
- 将目标和上下文单词的真实性对提供给网络
- 查找目标和上下文单词的单词向量
- 执行单词向量的点积来获得相似性得分
- 将相似性得分通过 sigmoid 层以将输出作为真或假对
现在让我们使用 Keras 函数式 API 实现这些步骤:
- 导入所需的库:
from keras.models import Model from keras.layers.embeddings import Embedding from keras.preprocessing import sequence from keras.preprocessing.sequence import skipgrams from keras.layers import Input, Dense, Reshape, Dot, merge import keras
重置图,以便清除以前在 Jupyter 笔记本中运行的任何后续效果:
# reset the jupyter buffers tf.reset_default_graph() keras.backend.clear_session()
- 创建一个验证集,我们将用它来打印我们的模型在训练结束时找到的相似单词:
valid_size = 8 x_valid = np.random.choice(valid_size * 10, valid_size, replace=False) print('valid: ',x_valid)
- 定义所需的超参数:
batch_size = 1024 embedding_size = 512 n_negative_samples = 64 ptb.skip_window=2
- 使用
keras.preprocessing.sequence
中的make_sampling_table()
函数创建一个大小等于词汇长度的样本表。接下来,使用keras.preprocessing.sequence
中的函数skipgrams()
生成上下文和目标词对以及表示它们是真对还是假对的标签。
sample_table = sequence.make_sampling_table(ptb.vocab_len) pairs, labels= sequence.skipgrams(ptb.part['train'], ptb.vocab_len,window_size=ptb.skip_window, sampling_table=sample_table)
- 让我们打印一些使用以下代码生成的伪造和真实对:
print('The SkipGram pairs : target,context') for i in range(5 * ptb.skip_window): print(['{} {}'.format(id,ptb.id2word[id]) \ for id in pairs[i]],':',labels[i])
对配对如下:
The SkipGram pairs : target,context ['547 trying', '5 to'] : 1 ['4845 bargain', '2 <eos>'] : 1 ['1705 election', '198 during'] : 1 ['4704 flows', '8117 gun'] : 0 ['13 is', '37 company'] : 1 ['625 above', '132 three'] : 1 ['5768 pessimistic', '1934 immediate'] : 0 ['637 china', '2 <eos>'] : 1 ['258 five', '1345 pence'] : 1 ['1956 chrysler', '8928 exercises'] : 0
- 从上面生成的对中拆分目标和上下文单词,以便将它们输入模型。将目标和上下文单词转换为二维数组。
x,y=zip(*pairs) x=np.array(x,dtype=np.int32) x=dsu.to2d(x,unit_axis=1) y=np.array(y,dtype=np.int32) y=dsu.to2d(y,unit_axis=1) labels=np.array(labels,dtype=np.int32) labels=dsu.to2d(labels,unit_axis=1)
- 定义网络的架构。正如我们所讨论的,必须将目标和上下文单词输入网络,并且需要从嵌入层中查找它们的向量。因此,首先我们分别为目标和上下文单词定义输入,嵌入和重塑层:
# build the target word model target_in = Input(shape=(1,),name='target_in') target = Embedding(ptb.vocab_len,embedding_size,input_length=1, name='target_em')(target_in) target = Reshape((embedding_size,1),name='target_re')(target) # build the context word model context_in = Input((1,),name='context_in') context = Embedding(ptb.vocab_len,embedding_size,input_length=1, name='context_em')(context_in) context = Reshape((embedding_size,1),name='context_re')(context)
- 接下来,构建这两个模型的点积,将其输入 sigmoid 层以生成输出标签:
# merge the models with the dot product to check for # similarity and add sigmoid layer output = Dot(axes=1,name='output_dot')([target,context]) output = Reshape((1,),name='output_re')(output) output = Dense(1, activation='sigmoid',name='output_sig')(output)
- 从我们刚刚创建的输入和输出模型构建函数式模型:
# create the functional model for finding word vectors model = Model(inputs=[target_in,context_in],outputs=output) model.compile(loss='binary_crossentropy', optimizer='adam')
- 此外,在给定输入目标词的情况下,构建一个模型,用于预测与所有单词的相似性:
# merge the models and create model to check for cosine similarity similarity = Dot(axes=0,normalize=True, name='sim_dot')([target,context]) similarity_model = Model(inputs=[target_in,context_in], outputs=similarity)
让我们打印模型摘要:
__________________________________________________________________________ Layer (type) Output Shape Param # Connected to ========================================================================== target_in (InputLayer) (None, 1) 0 __________________________________________________________________________ context_in (InputLayer) (None, 1) 0 __________________________________________________________________________ target_em (Embedding) (None, 1, 512) 5120000 target_in[0][0] __________________________________________________________________________ context_em (Embedding) (None, 1, 512) 5120000 context_in[0][0] __________________________________________________________________________ target_re (Reshape) (None, 512, 1) 0 target_em[0][0] __________________________________________________________________________ context_re (Reshape) (None, 512, 1) 0 context_em[0][0] __________________________________________________________________________ output_dot (Dot) (None, 1, 1) 0 target_re[0][0] context_re[0][0] __________________________________________________________________________ output_re (Reshape) (None, 1) 0 output_dot[0][0] __________________________________________________________________________ output_sig (Dense) (None, 1) 2 output_re[0][0] ========================================================================== Total params: 10,240,002 Trainable params: 10,240,002 Non-trainable params: 0 __________________________________________________________________________
- 接下来,训练模型。我们只训练了 5 个周期,但你应该尝试更多的周期,至少 1000 或 10,000 个周期。
请记住,这将需要几个小时,因为这不是最优化的代码。 欢迎您使用本书和其他来源的提示和技巧进一步优化代码。
n_epochs = 5 batch_size = 1024 model.fit([x,y],labels,batch_size=batch_size, epochs=n_epochs)
让我们根据这个模型发现的单词向量打印单词的相似度:
# print closest words to validation set at end of training top_k = 5 y_val = np.arange(ptb.vocab_len, dtype=np.int32) y_val = dsu.to2d(y_val,unit_axis=1) for i in range(valid_size): x_val = np.full(shape=(ptb.vocab_len,1),fill_value=x_valid[i], dtype=np.int32) similarity_scores = similarity_model.predict([x_val,y_val]) similarity_scores=similarity_scores.flatten() similar_words = (-similarity_scores).argsort()[1:top_k + 1] similar_str = 'Similar to {0:}:'.format(ptb.id2word[x_valid[i]]) for k in range(top_k): similar_str = '{0:} {1:},'.format(similar_str, ptb.id2word[similar_words[k]]) print(similar_str)
我们得到以下输出:
Similar to we: rake, kia, sim, ssangyong, memotec, Similar to been: nahb, sim, rake, punts, rubens, Similar to also: photography, snack-food, rubens, nahb, ssangyong, Similar to of: isi, rake, memotec, kia, mlx, Similar to last: rubens, punts, memotec, sim, photography, Similar to u.s.: mlx, memotec, punts, rubens, kia, Similar to an: memotec, isi, ssangyong, rake, sim, Similar to trading: rake, rubens, swapo, mlx, nahb,
到目前为止,我们已经看到了如何使用 TensorFlow 及其高级库 Keras 创建单词向量或嵌入。现在让我们看看如何使用 TensorFlow 和 Keras 来学习模型并将模型应用于一些与 NLP 相关的任务的预测。
TensorFlow 和 Keras 中的 RNN 模型和文本生成
文本生成是 NLP 中 RNN 模型的主要应用之一。针对文本序列训练 RNN 模型,然后通过提供种子文本作为输入来生成文本序列。让我们试试 text8 数据集。
让我们加载 text8 数据集并打印前 100 个单词:
from datasetslib.text8 import Text8 text8 = Text8() # downloads data, converts words to ids, converts files to a list of ids text8.load_data() print(' '.join([text8.id2word[x_i] for x_i in text8.part['train'][0:100]]))
我们得到以下输出:
anarchism originated as a term of abuse first used against early working class radicals including the diggers of the english revolution and the sans culottes of the french revolution whilst the term is still used in a pejorative way to describe any act that used violent means to destroy the organization of society it has also been taken up as a positive label by self defined anarchists the word anarchism is derived from the greek without archons ruler chief king anarchism as a political philosophy is the belief that rulers are unnecessary and should be abolished although there are differing
在我们的笔记本示例中,我们将数据加载剪切为 5,000 字的文本,因为较大的文本需要高级技术,例如分布式或批量,我们希望保持示例简单。
from datasetslib.text8 import Text8 text8 = Text8() text8.load_data(clip_at=5000) print('Train:', text8.part['train'][0:5]) print('Vocabulary Length = ',text8.vocab_len)
我们看到词汇量现在减少到 1,457 个单词。
Train: [ 8 497 7 5 116] Vocabulary Length = 1457
在我们的示例中,我们构造了一个非常简单的单层 LSTM。为了训练模型,我们使用 5 个单词作为输入来学习第六个单词的参数。输入层是 5 个字,隐藏层是具有 128 个单元的 LSTM 单元,最后一层是完全连接的层,其输出等于词汇量大小。由于我们正在演示这个例子,我们没有使用单词向量,而是使用非常简单的单热编码输出向量。
精通 TensorFlow 1.x:6~10(3)https://developer.aliyun.com/article/1426821