2021年3月2日

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

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

Quick,Draw! 的识别操作由一个分类器执行,它接收用户输入(用(x,y)中的点笔画序列表示),然后识别用户尝试涂鸦的图形所属的类别。

该模型将结合使用卷积层,LSTM层和softmax输出层对涂鸦进行分类:

输入为一个涂鸦,用(x,y,n)中的点笔画序列表示,其中n表示点是否为新笔画的第一个点。

然后,模型将应用LSTM层,将所有应用LSTM步的输出之和传递到softmax层,请根据我们已知的涂鸦类别来决定涂鸦的分类。

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

运行教程代码

要尝试本教程的代码,请执行以下操作:

安装TensorFlow(如果尚未安装的话)

下载 教程代码(https://github.com/tensorflow/models/blob/master/tutorials/rnn/quickdraw/train_model.py)

如需详细了解如何获取原始文件,请快速查看并绘制! 数据以及如何将数据转换为TFRecord文件,请参见示意图

使用以下命令执行教程代码,以训练本教程中所述的基于RNN的模型。请调整路径,将指针指向第3步中下载的解压缩数据

python train_model.py \

–training_data = rnn_tutorial_data / training.tfrecord-?????-of-????? \

–eval_data = rnn_tutorial_data / eval.tfrecord-?????-of-????? \

–classes_file = rnn_tutorial_data / training.tfrecord.classes

教程详情

下载数据

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

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

任选:下载完整的QuickDraw数据

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

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

然后,使用以下命令检查gsutil安装是否成功以及您是否可以访问数据存储分区:

gsutil ls -r“ gs:// quickdraw_dataset / full / simplified / *”

系统会输出一长串文件,所示:

gs:// quickdraw_dataset / full / simplified /艾菲尔铁塔.ndjson

gs:// quickdraw_dataset / full / simplified /中国的长城.ndjson

gs:// quickdraw_dataset / full / simplified / Mona Lisa.ndjson

gs:/ / quickdraw_dataset / full / simplified / aircraft carrier.ndjson

之后,创建一个文件夹并在其中下载数据集。

mkdir rnn_tutorial_data

cd rnn_tutorial_data

gsutil -m cp“ gs:// quickdraw_dataset / full / simplified / *”。

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

任选:转换数据

转换为ndjson文件转换为TFRecord文件(包含tf.train.Example样本),请运行以下命令。

python create_dataset.py –ndjson_path rnn_tutorial_data \

–output_path rnn_tutorial_data

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

初步详细说明了该转换过程。

原始QuickDraw数据的格式为ndjson文件,其中每行包含一个所示的JSON对象:

{“ word”:“ cat”,

“ countrycode”:“ VE”,

“ timestamp”:“ 2017-03-02 23:25:10.07453 UTC”,

“ recognized”:true,

“ key_id”:“ 5201136883597312”,

“绘图”:[

[

[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],

[72,40,27,79,82,88,100,120,134,152,165,184,189,186,179,152,131,114,100,100,89 70]

,[

[76,28,7],

[136,128,128]

,[

[76,23,0],

[160,164,175]

],[

[87,52,37],

[175,191,204]

],[

[174,220,246,251] ,

[134,132,136,139]

],[

[175,255],

[147,168]

],[[

[171,208,215],

[164,198,210]

],[

[130,110,108,111,130,139,139,119],

[129,134,137,144,148,144,136,130]

],[

[107,106],

[96,113

]

]

}}

在解析我们的分类器时,我们只关注“ word”和“ drawing”行。在解析ndjson文件时,我们使用一个函数逐行处理它们,该函数可将drawing缩小中的笔画转换为大小为[数字of points,3](包含连续点的差异)的张量。此函数将会以字符串形式返回类别名称。

def parse_line(ndjson_line):

“”“解析ndjson行并返回墨水(作为np数组)和类名。”“”

sample = json.loads(ndjson_line)

class_name = sample [“ word”]

inkarray = sample [“ drawing” ]

stroke_lengths = [len(stroke [0])对于墨水

阵列中的笔画] total_points = sum(stroke_lengths)

np_ink = np.zeros((total_points,3),dtype = np.float32)

current_t = 0

对于墨水阵列中的笔画:

for i在[0,1]中:

np_ink [current_t:(current_t + len(stroke [0])),i] =笔画[i]

current_t + = len(stroke [0])

np_ink [current_t-1,2] = 1 #stroke_end

#预处理。

#1.大小归一化。

较低= np.min(np_ink [:, 0:

上= np.max(np_ink [:, 0:2],轴= 0)

比例尺=上-下

比例尺[scale == 0] = 1

np_ink [:, 0:2] =(np_ink [:, 0:2 ]-较低)/小数

位数2。计算增量。

np_ink = np_ink [1 :, 0:2]-np_ink [0:-1,0:2]

返回np_ink,类名

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

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

然后,将这些数据变形为[num_training_samples,max_length,3]形状的张量。

最后,我们计算连续点之间的差异,相互之间存储为VarLenFeature(位于tensorflow.Example中的ink键下)。另外,我们将class_index存储为单个的FixedLengthFeature,将ink的形状存储为长度为2的FixedLengthFeature。

定义模型

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

要打造模型,我们需要执行以下操作:

除了油墨数据之外,我们还拥有每个样本的长度和目标类别。这可通过函数_get_input_tensors实现

将输入传递给_add_conv_layers中的每一卷积层

最后,将每个时间步的输出相加,针对输入生成一个固定长度的细分嵌入

在_add_fc_layers中使用softmax层进行嵌入进行分类

代码所示:

墨水,长度,目标= _get_input_tensors(特征,目标)

卷积= _add_conv_layers(墨水)

final_state = _add_rnn_layers(被卷积,长度)

logits = _add_fc_layers(final_state)

_get_input_tensors

要获得输入特征,我们先从特征字典获得形状,然后创建大小为[batch_size](包含输入序列的长度)的一维张量。张量,然后变形为[batch_size,?,3]。最后,如果转移目标,我们需要确保其存储为大小为[batch_size]的一维张量。

代码所示:

形状= features [“ shape”]

长度= tf.squeeze(

tf.slice(shapes,begin = [0,0],size = [params [“ batch_size”],1]))

油墨= tf.reshape(

tf。 sparse_tensor_to_dense(features [“ ink”]),

[params [“ batch_size”],-1,3])

如果目标值不是None:

目标= tf.squeeze(目标)

_add_conv_layers

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

输入是一个每个点维数都是3的序列。我们将使用一维卷积,将3个输入特征观测通道。这意味着输入为[batch_size,length,3]张量,而输出为[ batch_size,length,number_of_filters]张量。

卷积=

i范围内的墨水(len(params.num_conv)):convolved_input

=

如果params.batch_norm

卷积:convolved_input = tf.layers.batch_normalization(convolved_input

training =(mode == tf.estimator.ModeKeys.TRAIN))

#如果已启用,则添加辍学层,而不是第一卷积层。

如果i> 0并且params.dropout:

convolved_input = tf.layers.dropout(convolved_input

rate = params.dropout,

training =(mode == tf.estimator.ModeKeys.TRAIN))

convolved = tf.layers.conv1d(convolved_input

filter = params.num_conv [i],

kernel_size = params.conv_len [i],

激活=无,

步幅= 1,

padding =“ same”,

name =“ conv1d_%d”%i)

返回卷积,长度

_add_rnn_layers

我们将卷积的输出传递给双向LSTM层,进行我们使用contrib的辅助函数。

输出,_,_ = contrib_rnn.stack_bidirectional_dynamic_rnn(

cells_fw = [range(params.num_layers)中_的cell(params.num_nodes)],

cells_bw = [range(params.num_layers)中_的cell(params.num_nodes)]

输入=卷积,

sequence_length =长度,

dtype = tf.float32,

范围=“ rnn_classification”)

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

要创建一个固定长度的嵌入,我们需要将LSTM的输出相加。

mask = tf.tile(tf.expand_dims(

tf.sequence_mask(lengths,tf.shape(outputs)[1]),2),

[1,1,tf.shape(outputs [2]])

zero_outside = tf。其中(mask,输出,tf.zeros_like(输出))

输出= tf.reduce_sum(zero_outside,轴= 1)

_add_fc_layers

将输入的嵌入传递至全连接层,之后合并层用作softmax层。

tf.layers.dense(final_state,params.num_classes)

损失,预测和优化器

最后,我们需要添加一个损失函数,一个训练操作和预测来创建

cross_entropy = tf.reduce_mean(

tf.nn.sparse_softmax_cross_entropy_with_logits(

labels = targets,logits = logits))

#添加优化程序。

train_op = tf.contrib.layers.optimize_loss(

loss = cross_entropy,

global_step = tf.train.get_global_step(),

learning_rate = params.learning_rate,

optimizer =“ Adam”,

#某些梯度削波可在开始时稳定训练

。 gradient_clipping_norm,

摘要= [“ learning_rate”,“ loss”,“ gradients”,“ gradient_norm”])

预测= tf.argmax(logits,axis = 1)

返回model_fn_lib.ModelFnOps(

mode = mode,

projections = {“ logits”: Logits,

“ predictions”:预测},

loss = cross_entropy,

train_op = train_op,

eval_metric_ops = {“ accuracy”:tf.metrics.accuracy(目标,预测)})

训练和评估模型

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

estimator = tf.estimator.Estimator(

model_fn = model_fn,

model_dir = output_dir,

config = config,

params = model_params)

#训练模型。

tf.contrib.learn.Experiment(

估计器估计=,

train_input_fn = get_input_fn(

模式= tf.contrib.learn.ModeKeys.TRAIN,

tfrecord_pattern = FLAGS.training_data,

的batch_size = FLAGS.batch_size),

train_steps = FLAGS.steps,

eval_input_fn = get_input_fn (

mode = tf.contrib.learn.ModeKeys.EVAL,

tfrecord_pattern = FLAGS.eval_data,

batch_size = FLAGS.batch_size),

min_eval_frequency = 1000)

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

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