#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Explanation: Copyright 2019 The TensorFlow Authors.
End of explanation
"""


import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers

import tensorflow_datasets as tfds
tfds.disable_progress_bar()

"""
Explanation: 单词嵌入向量
<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https://tensorflow.google.cn/tutorials/text/word_embeddings" class="">     <img src="https://tensorflow.google.cn/images/tf_logo_32px.png" class="">     在 TensorFlow.org 上查看</a></td>
  <td><a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/zh-cn/tutorials/text/word_embeddings.ipynb" class="">     <img src="https://tensorflow.google.cn/images/colab_logo_32px.png" class="">     在 Google Colab 中运行</a></td>
  <td><a target="_blank" href="https://github.com/tensorflow/docs-l10n/blob/master/site/zh-cn/tutorials/text/word_embeddings.ipynb" class="">     <img src="https://tensorflow.google.cn/images/GitHub-Mark-32px.png" class="">     在 GitHub 上查看源代码</a></td>
  <td><a href="https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/zh-cn/tutorials/text/word_embeddings.ipynb" class=""><img src="https://tensorflow.google.cn/images/download_logo_32px.png" class="">下载笔记本</a></td>
</table>

本教程将介绍单词嵌入向量。包含完整的代码，可在小型数据集上从头开始训练单词嵌入向量，并使用 Embedding Projector（如下图所示）可视化这些嵌入向量。
<img alt="Screenshot of the embedding projector" width="400" src="https://github.com/tensorflow/docs/blob/master/site/en/tutorials/text/images/embedding.jpg?raw=1">
用数字表示文本
机器学习模型将向量（数字数组）作为输入。在处理文本时，我们必须先想出一种策略，将字符串转换为数字（或将文本“向量化”），然后再其馈入模型。在本部分中，我们将探究实现这一目标的三种策略。
独热编码
作为第一个想法，我们可以对词汇表中的每个单词进行“独热”编码。考虑这样一句话：“The cat sat on the mat”。这句话中的词汇（或唯一单词）是（cat、mat、on、sat、the）。为了表示每个单词，我们将创建一个长度等于词汇量的零向量，然后在与该单词对应的索引中放置一个 1。下图显示了这种方法。
<img alt="Diagram of one-hot encodings" width="400" src="https://github.com/tensorflow/docs/blob/master/site/en/tutorials/text/images/one-hot.png?raw=1">
为了创建一个包含句子编码的向量，我们可以将每个单词的独热向量连接起来。
要点：这种方法效率低下。一个独热编码向量十分稀疏（这意味着大多数索引为零）。假设我们的词汇表中有 10,000 个单词。为了对每个单词进行独热编码，我们将创建一个其中 99.99% 的元素都为零的向量。
用一个唯一的数字编码每个单词
我们可以尝试的第二种方法是使用唯一的数字来编码每个单词。继续上面的示例，我们可以将 1 分配给“cat”，将 2 分配给“mat”，依此类推。然后，我们可以将句子“The cat sat on the mat”编码为一个密集向量，例如 [5, 1, 4, 3, 5, 2]。这种方法是高效的。现在，我们有了一个密集向量（所有元素均已满），而不是稀疏向量。
但是，这种方法有两个缺点：


整数编码是任意的（它不会捕获单词之间的任何关系）。


对于要解释的模型而言，整数编码颇具挑战。例如，线性分类器针对每个特征学习一个权重。由于任何两个单词的相似性与其编码的相似性之间都没有关系，因此这种特征权重组合没有意义。


单词嵌入向量
单词嵌入向量为我们提供了一种使用高效、密集表示的方法，其中相似的单词具有相似的编码。重要的是，我们不必手动指定此编码。嵌入向量是浮点值的密集向量（向量的长度是您指定的参数）。它们是可以训练的参数（模型在训练过程中学习的权重，与模型学习密集层权重的方法相同），无需手动为嵌入向量指定值。8 维的单词嵌入向量（对于小型数据集）比较常见，而在处理大型数据集时最多可达 1024 维。维度更高的嵌入向量可以捕获单词之间的细粒度关系，但需要更多的数据来学习。
<img alt="Diagram of an embedding" width="400" src="https://github.com/tensorflow/docs/blob/master/site/en/tutorials/text/images/embedding2.png?raw=1">
上面是一个单词嵌入向量的示意图。每个单词都表示为浮点值的 4 维向量。还可以将嵌入向量视为“查找表”。学习完这些权重后，我们可以通过在表中查找对应的密集向量来编码每个单词。
设置
End of explanation
"""


embedding_layer = layers.Embedding(1000, 5)

"""
Explanation: 使用嵌入向量层
Keras 让使用单词嵌入向量变得轻而易举。我们来看一下嵌入向量层。
可以将嵌入向量层理解为一个从整数索引（代表特定单词）映射到密集向量（其嵌入向量）的查找表。嵌入向量的维数（或宽度）是一个参数，您可以试验它的数值，以了解多少维度适合您的问题，这与您试验密集层中神经元数量的方式非常相似。
End of explanation
"""


result = embedding_layer(tf.constant([1,2,3]))
result.numpy()

"""
Explanation: 创建嵌入向量层时，嵌入向量的权重会随机初始化（就像其他任何层一样）。在训练过程中，通过反向传播来逐渐调整这些权重。训练后，学习到的单词嵌入向量将粗略地编码单词之间的相似性（因为它们是针对训练模型的特定问题而学习的）。
如果将整数传递给嵌入向量层，结果会将每个整数替换为嵌入向量表中的向量：
End of explanation
"""


result = embedding_layer(tf.constant([[0,1,2],[3,4,5]]))
result.shape

"""
Explanation: 对于文本或序列问题，嵌入向量层采用整数组成的 2D 张量，其形状为 (samples, sequence_length)，其中每个条目都是一个整数序列。它可以嵌入可变长度的序列。您可以在形状为 (32, 10)（32 个长度为 10 的序列组成的批次）或 (64, 15)（64 个长度为 15 的序列组成的批次）的批次上方馈入嵌入向量层。
返回的张量比输入多一个轴，嵌入向量沿新的最后一个轴对齐。向其传递 (2, 3) 输入批次，输出为 (2, 3, N)
End of explanation
"""


(train_data, test_data), info = tfds.load(
    'imdb_reviews/subwords8k', 
    split = (tfds.Split.TRAIN, tfds.Split.TEST), 
    with_info=True, as_supervised=True)

"""
Explanation: 当给定一个序列批次作为输入时，嵌入向量层将返回形状为 (samples, sequence_length, embedding_dimensionality) 的 3D 浮点张量。为了从可变长度的序列转换为固定表示，有多种标准方法。您可以先使用 RNN、注意力或池化层，然后再将其传递给密集层。本教程使用池化，因为它最简单。接下来，学习使用 RNN 进行文本分类教程是一个不错的选择。
从头开始学习嵌入向量
在本教程中，您将基于 IMDB 电影评论来训练情感分类器。在此过程中，模型将从头开始学习嵌入向量。我们将使用经过预处理的数据集。
要从头开始加载文本数据集，请参阅加载文本教程。
End of explanation
"""


encoder = info.features['text'].encoder
encoder.subwords[:20]

"""
Explanation: 获取编码器 (tfds.features.text.SubwordTextEncoder)，并快速浏览词汇表。
词汇表中的“”代表空格。请注意词汇表如何包含完整单词（以“”结尾）以及可用于构建更大单词的部分单词：
End of explanation
"""


train_batches = train_data.shuffle(1000).padded_batch(10)
test_batches = test_data.shuffle(1000).padded_batch(10)

"""
Explanation: 电影评论的长度可以不同。我们将使用 padded_batch 方法来标准化评论的长度。
End of explanation
"""


train_batch, train_labels = next(iter(train_batches))
train_batch.numpy()

"""
Explanation: 导入时，评论的文本是整数编码的（每个整数代表词汇表中的特定单词或单词部分）。
请注意尾随零，因为批次会填充为最长的示例。
End of explanation
"""


embedding_dim=16

model = keras.Sequential([
  layers.Embedding(encoder.vocab_size, embedding_dim),
  layers.GlobalAveragePooling1D(),
  layers.Dense(16, activation='relu'),
  layers.Dense(1)
])

model.summary()

"""
Explanation: 创建一个简单模型
我们将使用 Keras 序列式 API 定义模型。在这种情况下，它是一个“连续词袋”样式的模型。


接下来，嵌入向量层将采用整数编码的词汇表，并查找每个单词索引的嵌入向量。在模型训练时会学习这些向量。向量会向输出数组添加维度。得到的维度为：(batch, sequence, embedding)。


接下来，通过对序列维度求平均值，GlobalAveragePooling1D 层会返回每个样本的固定长度输出向量。这让模型能够以最简单的方式处理可变长度的输入。


此固定长度输出向量通过一个包含 16 个隐藏单元的完全连接（密集）层进行流水线传输。


最后一层与单个输出节点密集连接。利用 Sigmoid 激活函数，得出此值是 0 到 1 之间的浮点数，表示评论为正面的概率（或置信度）。


小心：此模型不使用遮盖，而是使用零填充作为输入的一部分，因此填充长度可能会影响输出。要解决此问题，请参阅遮盖和填充指南。
End of explanation
"""


model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'])

history = model.fit(
    train_batches,
    epochs=10,
    validation_data=test_batches, validation_steps=20)

"""
Explanation: 编译和训练模型
End of explanation
"""


import matplotlib.pyplot as plt

history_dict = history.history

acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
loss=history_dict['loss']
val_loss=history_dict['val_loss']

epochs = range(1, len(acc) + 1)

plt.figure(figsize=(12,9))
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

plt.figure(figsize=(12,9))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')
plt.ylim((0.5,1))
plt.show()

"""
Explanation: 通过这种方法，我们的模型可以达到约 88% 的验证准确率（请注意，该模型过度拟合，因此训练准确率要高得多）。
End of explanation
"""


e = model.layers[0]
weights = e.get_weights()[0]
print(weights.shape) # shape: (vocab_size, embedding_dim)

"""
Explanation: 检索学习的嵌入向量
接下来，我们检索在训练期间学习的单词嵌入向量。这将是一个形状为 (vocab_size, embedding-dimension) 的矩阵。
End of explanation
"""


import io

encoder = info.features['text'].encoder

out_v = io.open('vecs.tsv', 'w', encoding='utf-8')
out_m = io.open('meta.tsv', 'w', encoding='utf-8')

for num, word in enumerate(encoder.subwords):
  vec = weights[num+1] # skip 0, it's padding.
  out_m.write(word + "\n")
  out_v.write('\t'.join([str(x) for x in vec]) + "\n")
out_v.close()
out_m.close()

"""
Explanation: 现在，我们将权重写入磁盘。要使用 Embedding Projector，我们将以制表符分隔的格式上传两个文件：一个向量文件（包含嵌入向量）和一个元数据文件（包含单词）。
End of explanation
"""


try:
  from google.colab import files
except ImportError:
   pass
else:
  files.download('vecs.tsv')
  files.download('meta.tsv')

"""
Explanation: 如果您正在 Colaboratory 中运行本教程，则可以使用以下代码段将这些文件下载到本地计算机上（或使用文件浏览器，View -> Table of contents -> File browser）。
End of explanation
"""


