[机器学习速成课程] 嵌套 (Embeddings):编程练习-学习笔记
稀疏数据和嵌入简介
学习目标:
- 将影评字符串数据转换为稀疏特征矢量
- 使用稀疏特征矢量实现情感分析线性模型
- 通过将数据投射到二维空间的嵌入来实现情感分析 DNN 模型
- 将嵌入可视化,以便查看模型学到的词语之间的关系
在此练习中,我们将探讨稀疏数据,并使用影评文本数据(来自 ACL 2011 IMDB 数据集)进行嵌入。这些数据已被处理成 tf.Example
格式。
(数据:
urls_pos.txt包含被判定为好评的评价;urls_neg.txt包含被判定为差评的评价;urls_unsep.txt包含既不是好评也不是差评的评价;
)
设置
我们导入依赖项并下载训练数据和测试数据。tf.keras
中包含一个文件下载和缓存工具,我们可以用它来检索数据集。
import math
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
from IPython import display
from sklearn import metrics
tf.logging.set_verbosity(tf.logging.ERROR)
train_url = 'https://storage.googleapis.com/mledu-datasets/sparse-data-embedding/train.tfrecord'
train_path = tf.keras.utils.get_file(train_url.split('/')[-1], train_url)
test_url = 'https://storage.googleapis.com/mledu-datasets/sparse-data-embedding/test.tfrecord'
test_path = tf.keras.utils.get_file(test_url.split('/')[-1], test_url)
构建情感分析模型
我们根据这些数据训练一个情感分析模型,以预测(目的)某条评价总体上是好评(标签为 1)还是差评(标签为 0)。
为此,我们会使用词汇表(即我们预计将在数据中看到的每个术语的列表),将字符串值 terms
转换为特征矢量。在本练习中,我们创建了侧重于一组有限术语的小型词汇表。其中的大多数术语明确表示是好评或差评,但有些只是因为有趣而被添加进来。
词汇表中的每个术语都与特征矢量中的一个坐标相对应。为了将样本的字符串值 terms
转换为这种矢量格式,我们按以下方式处理字符串值:如果该术语没有出现在样本字符串中,则坐标值将为 0;如果出现在样本字符串中,则值为 1。未出现在该词汇表中的样本中的术语将被弃用。
注意:我们当然可以使用更大的词汇表,而且有创建此类词汇表的专用工具。此外,我们可以添加少量的 OOV(未收录词汇)分桶,您可以在其中对词汇表中未包含的术语进行哈希处理,而不仅仅是弃用这些术语。我们还可以使用特征哈希法对每个术语进行哈希处理,而不是创建显式词汇表。这在实践中很有效,但却不具备可解读性(这对本练习非常实用)。如需了解处理此类词汇表的工具,请参阅 tf.feature_column 模块。
构建输入管道
首先,我们来配置输入管道,以将数据导入 TensorFlow 模型中。我们可以使用以下函数来解析训练数据和测试数据(格式为 TFRecord),然后返回一个由特征和相应标签组成的字典。
def _parse_function(record):
"""Extracts features and labels.
Args:
record: File path to a TFRecord file
Returns:
A `tuple` `(labels, features)`:
features: A dict of tensors representing the features
labels: A tensor with the corresponding labels.
"""
features = {
"terms": tf.VarLenFeature(dtype=tf.string), # terms are strings of varying lengths
"labels": tf.FixedLenFeature(shape=[1], dtype=tf.float32) # labels are 0 or 1
}
parsed_features = tf.parse_single_example(record, features)
terms = parsed_features['terms'].values
labels = parsed_features['labels']
return {'terms':terms}, labels
为了确认函数是否能正常运行,我们为训练数据构建一个 TFRecordDataset
,并使用上述函数将数据映射到特征和标签。
# Create the Dataset object
ds = tf.data.TFRecordDataset(train_path)
# Map features and labels with the parse function
ds = ds.map(_parse_function)
ds
运行以下单元,以从训练数据集中获取第一个样本。
n = ds.make_one_shot_iterator().get_next()
sess = tf.Session()
sess.run(n)
{'terms':terms}, labels
现在,我们构建一个正式的输入函数,可以将其传递给 TensorFlow Estimator 对象的 train()
方法。
# Create an input_fn that parses the tf.Examples from the given files,
# and split them into features and targets.
def _input_fn(input_filenames, num_epochs=None, shuffle=True):
# Same code as above; create a dataset and map features and labels
ds = tf.data.TFRecordDataset(input_filenames)
ds = ds.map(_parse_function)
if shuffle:
ds = ds.shuffle(10000)
# Our feature data is variable-length, so we pad and batch
# each field of the dataset structure to whatever size is necessary
ds = ds.padded_batch(25, ds.output_shapes)
ds = ds.repeat(num_epochs)
# Return the next batch of data
features, labels = ds.make_one_shot_iterator().get_next()
return features, labels
任务 1:使用具有稀疏输入和显式词汇表的线性模型
对于我们的第一个模型,我们将使用 54 个信息性术语来构建 LinearClassifier
模型;始终从简单入手!
以下代码将为我们的术语构建特征列。categorical_column_with_vocabulary_list
函数可使用“字符串-特征矢量”映射来创建特征列。
informative_terms = ("bad", "great", "best", "worst", "fun", "beautiful",
"excellent", "poor", "boring", "awful", "terrible",
"definitely", "perfect", "liked", "worse", "waste",
"entertaining", "loved", "unfortunately", "amazing",
"enjoyed", "favorite", "horrible", "brilliant", "highly",
"simple", "annoying", "today", "hilarious", "enjoyable",
"dull", "fantastic", "poorly", "fails", "disappointing",
"disappointment", "not", "him", "her", "good", "time",
"?", ".", "!", "movie", "film", "action", "comedy",
"drama", "family", "man", "woman", "boy", "girl")
terms_feature_column = tf.feature_column.categorical_column_with_vocabulary_list(key="terms", vocabulary_list=informative_terms)
terms_feature_column
输出:
_VocabularyListCategoricalColumn(key='terms', vocabulary_list=('bad', 'great', 'best', 'worst', 'fun', 'beautiful',
'excellent', 'poor', 'boring', 'awful', 'terrible',
'definitely', 'perfect', 'liked', 'worse', 'waste',
'entertaining', 'loved', 'unfortunately', 'amazing',
'enjoyed', 'favorite', 'horrible', 'brilliant', 'highly',
'simple', 'annoying', 'today', 'hilarious', 'enjoyable',
'dull', 'fantastic', 'poorly', 'fails', 'disappointing',
'disappointment', 'not', 'him', 'her', 'good',
'time', '?', '.', '!', 'movie', 'film', 'action',
'comedy', 'drama', 'family', 'man', 'woman', 'boy',
'girl'), dtype=tf.string, default_value=-1, num_oov_buckets=0)
LinearClassifier
,在训练集中训练该模型,并在评估集中对其进行评估。阅读上述代码后,运行该模型以了解其效果。my_optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
feature_columns = [ terms_feature_column ]
classifier = tf.estimator.LinearClassifier(
feature_columns=feature_columns,
optimizer=my_optimizer,
)
classifier.train(
input_fn=lambda: _input_fn([train_path]),
steps=1000)
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([train_path]),
steps=1000)
print "Training set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([test_path]),
steps=1000)
print "Test set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
任务 2:使用深度神经网络 (DNN) 模型
上述模型是一个线性模型,效果非常好。但是,我们可以使用 DNN 模型实现更好的效果吗?
我们将 LinearClassifier
切换为 DNNClassifier
。运行以下单元,看看您的模型效果如何。
##################### Here's what we changed ##################################
classifier = tf.estimator.DNNClassifier( #
feature_columns=[tf.feature_column.indicator_column(terms_feature_column)], #
hidden_units=[20,20], #
optimizer=my_optimizer, #
) #
###############################################################################
try:
classifier.train(
input_fn=lambda: _input_fn([train_path]),
steps=1000)
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([train_path]),
steps=1)
print "Training set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([test_path]),
steps=1)
print "Test set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
except ValueError as err:
print err
使用深度神经网络模型的结果明显比线性模型的好。
任务 3:在 DNN 模型中使用嵌入
在此任务中,我们将使用嵌入列来实现 DNN 模型。嵌入列会将稀疏数据作为输入,并返回一个低维度密集矢量作为输出。
注意:从计算方面而言,embedding_column(嵌入列) 通常是用于在稀疏数据中训练模型最有效的选项。在此练习末尾的可选部分,我们将更深入地讨论使用embedding_column
与 indicator_column
之间的实现差异,以及如何在这两者之间做出权衡。在下面的代码中,执行以下操作:
- 通过将数据投射到二维空间的
embedding_column
来为模型定义特征列(如需详细了解embedding_column
的函数签名,请参阅相关 TF 文档)。 - 定义符合以下规范的
DNNClassifier
:- 具有两个隐藏层,每个包含 20 个单元
- 采用学习速率为 0.1 的 AdaGrad 优化方法
gradient_clip_norm 值为 5.0
注意:在实践中,我们可能会将数据投射到 2 维以上(比如 50 或 100)的空间中。但就目前而言,2 维是比较容易可视化的维数。
解决方案
########################## SOLUTION CODE ########################################
# 没有使用嵌入的DNN模型: feature_columns = [ terms_feature_column ]
# 使用嵌入列来实现 DNN 模型。嵌入列会将稀疏数据作为输入,并返回一个低维度密集矢量作为输出。
terms_embedding_column = tf.feature_column.embedding_column(terms_feature_column, dimension=2)
feature_columns = [ terms_embedding_column ]
my_optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[10,10],
optimizer=my_optimizer
)
#################################################################################
classifier.train(
input_fn=lambda: _input_fn([train_path]),
steps=1000)
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([train_path]),
steps=1000)
print "Training set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([test_path]),
steps=1000)
print "Test set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
结果并没有比未使用嵌入的DNN模型好
任务 4:确信模型中确实存在嵌入
上述模型使用了 embedding_column
,而且似乎很有效,但这并没有让我们了解到内部发生的情形。我们如何检查该模型确实在内部使用了嵌入?
首先,我们来看看该模型中的张量:
classifier.get_variable_names()
好的,我们可以看到这里有一个嵌入层:'dnn/input_from_feature_columns/input_layer/terms_embedding/...'
。(顺便说一下,有趣的是,该层可以与模型的其他层一起训练,就像所有隐藏层一样。)
嵌入层的形状是否正确?请运行以下代码来查明。
注意:在我们的示例中,嵌入是一个矩阵,可让我们将一个 54 维矢量投射到 2 维空间。
classifier.get_variable_value('dnn/input_from_feature_columns/input_layer/terms_embedding/embedding_weights').shape
花些时间来手动检查各个层及其形状,以确保一切都按照您预期的方式互相连接。
任务 5:检查嵌入
现在,我们来看看实际嵌入空间,并了解术语最终所在的位置。请执行以下操作:
运行以下代码来查看我们在任务 3 中训练的嵌入。一切最终是否如您所预期的那样?
重新运行任务 3 中的代码来重新训练该模型,然后再次运行下面的嵌入可视化。哪些保持不变?哪些发生了变化?
最后,仅使用 10 步来重新训练该模型(这将产生一个糟糕的模型)。再次运行下面的嵌入可视化。您现在看到了什么?为什么?
import numpy as np
import matplotlib.pyplot as plt
embedding_matrix = classifier.get_variable_value('dnn/input_from_feature_columns/input_layer/terms_embedding/embedding_weights')
for term_index in range(len(informative_terms)):
# Create a one-hot encoding for our term. It has 0's everywhere, except for
# a single 1 in the coordinate that corresponds to that term.
term_vector = np.zeros(len(informative_terms))
term_vector[term_index] = 1
# We'll now project that one-hot vector into the embedding space.
embedding_xy = np.matmul(term_vector, embedding_matrix)
plt.text(embedding_xy[0],
embedding_xy[1],
informative_terms[term_index])
# Do a little set-up to make sure the plot displays nicely.
plt.rcParams["figure.figsize"] = (12, 12)
plt.xlim(1.2 * embedding_matrix.min(), 1.2 * embedding_matrix.max())
plt.ylim(1.2 * embedding_matrix.min(), 1.2 * embedding_matrix.max())
plt.show()
重新训练任务三模型,结果散的比较开,好拼(1)和差评(0)的界限还是很明显的:
仅使用10步来重新训练模型,结果很分散,也不准确,是个很糟糕的模型:
任务 6:尝试改进模型的效果
看看您能否优化该模型以改进其效果。您可以尝试以下几种做法:
- 更改超参数或使用其他优化工具,比如 Adam(通过遵循这些策略,您的准确率可能只会提高一两个百分点)。
-
向
informative_terms
中添加其他术语。此数据集有一个完整的词汇表文件,其中包含 30716 个术语,您可以在以下位置找到该文件:https://storage.googleapis.com/mledu-datasets/sparse-data-embedding/terms.txt 您可以从该词汇表文件中挑选出其他术语,也可以通过categorical_column_with_vocabulary_file
特征列使用整个词汇表文件。
!wget https://storage.googleapis.com/mledu-datasets/sparse-data-embedding/terms.txt -O /tmp/terms.txt
# Create a feature column from "terms", using a full vocabulary file.
# 向 informative_terms 中添加其他术语。
informative_terms = None
with open("/tmp/terms.txt", 'r') as f:
# Convert it to set first to remove duplicates.
informative_terms = list(set(f.read().split()))
terms_feature_column = tf.feature_column.categorical_column_with_vocabulary_list(key="terms",
vocabulary_list=informative_terms)
terms_embedding_column = tf.feature_column.embedding_column(terms_feature_column, dimension=2)
feature_columns = [ terms_embedding_column ]
my_optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)
my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
classifier = tf.estimator.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[10,10],
optimizer=my_optimizer
)
classifier.train(
input_fn=lambda: _input_fn([train_path]),
steps=1000)
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([train_path]),
steps=1000)
print "Training set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
evaluation_metrics = classifier.evaluate(
input_fn=lambda: _input_fn([test_path]),
steps=1000)
print "Test set metrics:"
for m in evaluation_metrics:
print m, evaluation_metrics[m]
print "---"
可是,结果依旧没有未使用嵌入的DNN模型好。为什么?
总结
我们可能获得了比我们原来的线性模型更好且具有嵌入的 DNN 解决方案,但线性模型也相当不错,而且训练速度快得多。线性模型的训练速度之所以更快,是因为它们没有太多要更新的参数或要反向传播的层。
在有些应用中,线性模型的速度可能非常关键,或者从质量的角度来看,线性模型可能完全够用。在其他领域,DNN 提供的额外模型复杂性和能力可能更重要。在定义模型架构时,请记得要充分探讨您的问题,以便知道自己所处的情形。
下一篇: HTML页面自定义网站favicon图标
推荐阅读
-
机器学习速成课程 笔记
-
[机器学习速成课程] 嵌套 (Embeddings):编程练习-学习笔记
-
机器学习速成课程 | 练习 | Google Development——编程练习:特征集
-
机器学习速成课程 | 练习 | Google Development——编程练习:(TensorFlow) Hello World
-
机器学习速成课程 | 练习 | Google Development——编程练习:使用 TensorFlow 的起始步骤
-
机器学习速成课程 | 练习 | Google Development——编程练习:特征组合
-
机器学习速成课程 | 练习 | Google Development——编程练习:合成特征和离群值
-
机器学习速成课程 | 练习 | Google Development——编程练习:逻辑回归
-
机器学习速成课程 | 练习 | Google Development——编程练习:稀疏数据和嵌套简介
-
google 机器学习速成课程 笔记1