请选择 进入手机版 | 继续访问电脑版
 找回密码
 立即注册

QQ登录

只需一步,快速开始

TensorFlow:Quick, Draw! 涂鸦分类递归神经网络

1
回复
429
查看
[复制链接]

4

主题

6

帖子

24

积分

新手上路

Rank: 1

积分
24
来源: 2018-11-26 16:20:22 显示全部楼层 |阅读模式
本帖最后由 多吃蔬菜 于 2018-11-26 16:34 编辑

Quick, Draw! 是一款游戏;在这个游戏中,玩家要接受一项挑战:绘制几个图形,看看计算机能否识别玩家绘制的是什么。

Quick, Draw! 的识别操作 由一个分类器执行,它接收用户输入(用 (x, y) 中的点笔画序列表示),然后识别用户尝试涂鸦的图形所属的类别。在本教程中,我们将展示如何针对此问题构建基于 RNN 的识别器。该模型将结合使用卷积层、LSTM 层和 softmax 输出层对涂鸦进行分类:


上图显示了我们将在本教程中构建的模型的结构。输入为一个涂鸦,用 (x, y, n) 中的点笔画序列表示,其中 n 表示点是否为新笔画的第一个点。

然后,模型将应用一系列一维卷积,接下来,会应用 LSTM 层,并将所有 LSTM 步的输出之和馈送到 softmax 层,以便根据我们已知的涂鸦类别来决定涂鸦的分类。

本教程使用的数据来自真实的 Quick, Draw! 游戏, 这些数据是公开提供的。此数据集包含 5000 万幅涂鸦,涵盖 345 个类别(https://quickdraw.withgoogle.com/)。


运行教程代码
要尝试本教程的代码,请执行以下操作:
  • 安装 TensorFlow(如果尚未安装的话)
  • 下载数据(TFRecord 格式),然后解压缩。如需详细了解如何获取原始 Quick, Draw! 数据以及如何将数据转换为 TFRecord 文件,请参阅下文
  • 使用以下命令执行教程代码,以训练本教程中所述的基于 RNN 的模型。请务必调整路径,使其指向第 3 步中下载的解压缩数据


  1. python train_model.py \
  2.     --training_data=rnn_tutorial_data/training.tfrecord-?????-of-????? \
  3.     --eval_data=rnn_tutorial_data/eval.tfrecord-?????-of-????? \
  4.     --classes_file=rnn_tutorial_data/training.tfrecord.classes
复制代码



教程详情
下载数据
我们将本教程中要使用的数据放在了包含 TFExamples 的 TFRecord 文件中。您可以从以下位置下载这些数据:http://download.tensorflow.org/d ... l_dataset_v1.tar.gz(大约 1GB)。

或者,您也可以从 Google Cloud 下载 ndjson 格式的原始数据,并将这些数据转换为包含 TFExamples 的 TFRecord 文件,如下一部分中所述。

可选:下载完整的 QuickDraw 数据
完整的 Quick, Draw! 数据集可在 Google Cloud Storage 上找到,此数据集是按类别划分的 ndjson 文件。您可以在 Cloud Console 中浏览文件列表。

要下载数据,我们建议使用 gsutil 下载整个数据集。请注意,原始 .ndjson 文件需要下载约 22GB 的数据。

然后,使用以下命令检查 gsutil 安装是否成功以及您是否可以访问数据存储分区:
  1. <div align="left">gsutil ls -r "gs://quickdraw_dataset/full/simplified/*"
  2. </div>
复制代码

系统会输出一长串文件,如下所示:
  1. gs://quickdraw_dataset/full/simplified/The Eiffel Tower.ndjson
  2. gs://quickdraw_dataset/full/simplified/The Great Wall of China.ndjson
  3. gs://quickdraw_dataset/full/simplified/The Mona Lisa.ndjson
  4. gs://quickdraw_dataset/full/simplified/aircraft carrier.ndjson
  5. ...
复制代码


之后,创建一个文件夹并在其中下载数据集。
  1. mkdir rnn_tutorial_data
  2. cd rnn_tutorial_data
  3. gsutil -m cp "gs://quickdraw_dataset/full/simplified/*" .
复制代码


下载过程需要花费一段时间,且下载的数据量略超 23GB。

可选:转换数据
要将 ndjson 文件转换为 TFRecord 文件(包含 tf.train.Example 样本),请运行以下命令。

  1. python create_dataset.py --ndjson_path rnn_tutorial_data \
  2.       --output_path rnn_tutorial_data
复制代码


此命令会将数据存储在 TFRecord 文件的 10 个分片中,每个类别有 10000 项用于训练数据,有 1000 项用于评估数据。

下文详细说明了该转换过程。

原始 QuickDraw 数据的格式为 ndjson 文件,其中每行包含一个如下所示的 JSON 对象:
  1. <div align="left">{"word":"cat",
  2. "countrycode":"VE",
  3. "timestamp":"2017-03-02 23:25:10.07453 UTC",
  4. "recognized":true,
  5. "key_id":"5201136883597312",
  6. "drawing":[
  7.    [
  8.      [130,113,99,109,76,64,55,48,48,51,59,86,133,154,170,203,214,217,215,208,186,176,162,157,132],
  9.      [72,40,27,79,82,88,100,120,134,152,165,184,189,186,179,152,131,114,100,89,76,0,31,65,70]
  10.    ],[
  11.      [76,28,7],
  12.      [136,128,128]
  13.    ],[
  14.      [76,23,0],
  15.      [160,164,175]
  16.    ],[
  17.      [87,52,37],
  18.      [175,191,204]
  19.    ],[
  20.      [174,220,246,251],
  21.      [134,132,136,139]
  22.    ],[
  23.      [175,255],
  24.      [147,168]
  25.    ],[
  26.      [171,208,215],
  27.      [164,198,210]
  28.    ],[
  29.      [130,110,108,111,130,139,139,119],
  30.      [129,134,137,144,148,144,136,130]
  31.    ],[
  32.      [107,106],
  33.      [96,113]
  34.    ]
  35. ]
  36. }</div>
复制代码

在构建我们的分类器时,我们只关注 “word” 和 “drawing” 字段。在解析 ndjson 文件时,我们使用一个函数逐行处理它们,该函数可将 drawing 字段中的笔画转换为大小为 [number of points, 3](包含连续点的差异)的张量。此函数还会以字符串形式返回类别名称。
  1. <div align="left">def parse_line(ndjson_line):
  2.   """Parse an ndjson line and return ink (as np array) and classname."""
  3.   sample = json.loads(ndjson_line)
  4.   class_name = sample["word"]
  5.   inkarray = sample["drawing"]
  6.   stroke_lengths = [len(stroke[0]) for stroke in inkarray]
  7.   total_points = sum(stroke_lengths)
  8.   np_ink = np.zeros((total_points, 3), dtype=np.float32)
  9.   current_t = 0
  10.   for stroke in inkarray:
  11.     for i in [0, 1]:
  12.       np_ink[current_t:( current_t + len(stroke[0])), i] = stroke
  13.     current_t += len(stroke[0])
  14.     np_ink[current_t - 1, 2] = 1  # stroke_end
  15.   # Preprocessing.
  16.   # 1. Size normalization.
  17.   lower = np.min(np_ink[:, 0:2], axis=0)
  18.   upper = np.max(np_ink[:, 0:2], axis=0)
  19.   scale = upper - lower
  20.   scale[scale == 0] = 1
  21.   np_ink[:, 0:2] = (np_ink[:, 0:2] - lower) / scale
  22.   # 2. Compute deltas.
  23.   np_ink = np_ink[1:, 0:2] - np_ink[0:-1, 0:2]
  24.   return np_ink, class_name</div>
复制代码

由于我们希望数据在写入时进行随机处理,因此我们以随机顺序从每个类别文件中读取数据并写入随机分片。

对于训练数据,我们读取每个类别的前 10000 项;对于评估数据,我们读取每个类别接下来的 1000 项。

然后,将这些数据变形为 [num_training_samples, max_length, 3] 形状的张量。接下来,我们用屏幕坐标确定原始涂鸦的边界框并标准化涂鸦的尺寸,使涂鸦具有单位高度。


最后,我们计算连续点之间的差异,并将它们存储为 VarLenFeature(位于 tensorflow.Example 中的 ink 键下)。另外,我们将 class_index 存储为单一条目 FixedLengthFeature,将 ink 的 shape 存储为长度为 2 的 FixedLengthFeature。

定义模型
要定义模型,我们需要创建一个新的 Estimator。如需详细了解 Estimator,建议您阅读此教程。

要构建模型,我们需要执行以下操作:
  • 将输入调整回原始形状,其中小批次通过填充达到其内容的最大长度。除了 ink 数据之外,我们还拥有每个样本的长度和目标类别。这可通过函数 _get_input_tensors 实现
  • 将输入传递给 _add_conv_layers 中的一系列卷积层
  • 将卷积的输出传递到 _add_rnn_layers 中的一系列双向 LSTM 层。最后,将每个时间步的输出相加,针对输入生成一个固定长度的紧凑嵌入
  • 在 _add_fc_layers 中使用 softmax 层对此嵌入进行分类


代码如下所示:
  1. <div align="left">inks, lengths, targets = _get_input_tensors(features, targets)
  2. convolved = _add_conv_layers(inks)
  3. final_state = _add_rnn_layers(convolved, lengths)
  4. logits =_add_fc_layers(final_state)</div>
复制代码



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

4

主题

6

帖子

24

积分

新手上路

Rank: 1

积分
24
2018-11-26 16:21:02 显示全部楼层
本帖最后由 多吃蔬菜 于 2018-11-26 16:35 编辑

_get_input_tensors
要获得输入特征,我们先从特征字典获得形状,然后创建大小为 [batch_size](包含输入序列的长度)的一维张量。ink 作为稀疏张量存储在特征字典中,我们将其转换为密集张量,然后变形为 [batch_size, ?, 3]。最后,如果传入目标,我们需要确保它们存储为大小为 [batch_size] 的一维张量。

代码如下所示:
  1. shapes = features["shape"]
  2. lengths = tf.squeeze(
  3.     tf.slice(shapes, begin=[0, 0], size=[params["batch_size"], 1]))
  4. inks = tf.reshape(
  5.     tf.sparse_tensor_to_dense(features["ink"]),
  6.     [params["batch_size"], -1, 3])
  7. if targets is not None:
  8.   targets = tf.squeeze(targets)
复制代码


_add_conv_layers
您可以通过 params 字典中的参数 num_conv 和 conv_len 配置所需的卷积层数量和过滤器长度。

输入是一个每个点维数都是 3 的序列。我们将使用一维卷积,将 3 个输入特征视为通道。这意味着输入为 [batch_size, length, 3] 张量,而输出为 [batch_size, length, number_of_filters] 张量。
  1. convolved = inks
  2. for i in range(len(params.num_conv)):
  3.   convolved_input = convolved
  4.   if params.batch_norm:
  5.     convolved_input = tf.layers.batch_normalization(
  6.         convolved_input,
  7.         training=(mode == tf.estimator.ModeKeys.TRAIN))
  8.   # Add dropout layer if enabled and not first convolution layer.
  9.   if i > 0 and params.dropout:
  10.     convolved_input = tf.layers.dropout(
  11.         convolved_input,
  12.         rate=params.dropout,
  13.         training=(mode == tf.estimator.ModeKeys.TRAIN))
  14.   convolved = tf.layers.conv1d(
  15.       convolved_input,
  16.       filters=params.num_conv,
  17.       kernel_size=params.conv_len,
  18.       activation=None,
  19.       strides=1,
  20.       padding="same",
  21.       name="conv1d_%d" % i)
  22. return convolved, lengths
复制代码


_add_rnn_layers
我们将卷积的输出传递给双向 LSTM 层,对此我们使用 contrib 的辅助函数。
  1. outputs, _, _ = contrib_rnn.stack_bidirectional_dynamic_rnn(
  2.     cells_fw=[cell(params.num_nodes) for _ in range(params.num_layers)],
  3.     cells_bw=[cell(params.num_nodes) for _ in range(params.num_layers)],
  4.     inputs=convolved,
  5.     sequence_length=lengths,
  6.     dtype=tf.float32,
  7.     scope="rnn_classification")
复制代码


请参阅代码以了解详情以及如何使用 CUDA 加速实现。

要创建一个固定长度的紧凑嵌入,我们需要将 LSTM 的输出相加。我们首先将其中的序列不含数据的批次区域设为 0。
  1. mask = tf.tile(
  2.     tf.expand_dims(tf.sequence_mask(lengths, tf.shape(outputs)[1]), 2),
  3.     [1, 1, tf.shape(outputs)[2]])
  4. zero_outside = tf.where(mask, outputs, tf.zeros_like(outputs))
  5. outputs = tf.reduce_sum(zero_outside, axis=1)
复制代码


_add_fc_layers
将输入的嵌入传递至全连接层,之后将此层用作 softmax 层。
  1. tf.layers.dense(final_state, params.num_classes)
复制代码


损失、预测和优化器
最后,我们需要添加一个损失函数、一个训练操作和预测来创建 ModelFn:
  1. cross_entropy = tf.reduce_mean(
  2.     tf.nn.sparse_softmax_cross_entropy_with_logits(
  3.         labels=targets, logits=logits))
  4. # Add the optimizer.
  5. train_op = tf.contrib.layers.optimize_loss(
  6.     loss=cross_entropy,
  7.     global_step=tf.train.get_global_step(),
  8.     learning_rate=params.learning_rate,
  9.     optimizer="Adam",
  10.     # some gradient clipping stabilizes training in the beginning.
  11.     clip_gradients=params.gradient_clipping_norm,
  12.     summaries=["learning_rate", "loss", "gradients", "gradient_norm"])
  13. predictions = tf.argmax(logits, axis=1)
  14. return model_fn_lib.ModelFnOps(
  15.     mode=mode,
  16.     predictions={"logits": logits,
  17.                  "predictions": predictions},
  18.     loss=cross_entropy,
  19.     train_op=train_op,
  20.     eval_metric_ops={"accuracy": tf.metrics.accuracy(targets, predictions)})
复制代码


训练和评估模型
要训练和评估模型,我们可以借助 Estimator API 的功能,并使用 Experiment API 轻松运行训练和评估操作:

  1. estimator = tf.estimator.Estimator(
  2.       model_fn=model_fn,
  3.       model_dir=output_dir,
  4.       config=config,
  5.       params=model_params)
  6.   # Train the model.
  7.   tf.contrib.learn.Experiment(
  8.       estimator=estimator,
  9.       train_input_fn=get_input_fn(
  10.           mode=tf.contrib.learn.ModeKeys.TRAIN,
  11.           tfrecord_pattern=FLAGS.training_data,
  12.           batch_size=FLAGS.batch_size),
  13.       train_steps=FLAGS.steps,
  14.       eval_input_fn=get_input_fn(
  15.           mode=tf.contrib.learn.ModeKeys.EVAL,
  16.           tfrecord_pattern=FLAGS.eval_data,
  17.           batch_size=FLAGS.batch_size),
  18.       min_eval_frequency=1000)
复制代码


请注意,本教程只是用一个相对较小的数据集进行简单演示,目的是让您熟悉递归神经网络和 Estimator 的 API。如果在大型数据集上尝试,这些模型可能会更强大。

当模型完成 100 万个训练步后,分数最高的候选项的准确率预计会达到 70% 左右。请注意,这种程度的准确率足以构建 Quick, Draw! 游戏,由于该游戏的动态特性,用户可以在系统准备好识别之前调整涂鸦。此外,如果目标类别显示的分数高于固定阈值,该游戏不会仅使用分数最高的候选项,而且会将某个涂鸦视为正确的涂鸦。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册