From c034bac2bb3ab7c03250fec02e4cef166b806b75 Mon Sep 17 00:00:00 2001 From: whs Date: Mon, 20 Aug 2018 20:55:07 +0800 Subject: [PATCH] Add attention training model for ocr. (#1034) * Add attention training model for ocr. * Add beam search for infer. * Fix data reader. * Fix inference. * Prune result of inference. * Fix README * update README * Rsize figure. * Resize image and fix format. --- fluid/ocr_recognition/.run_ce.sh | 2 +- fluid/ocr_recognition/README.md | 26 +- fluid/ocr_recognition/attention_model.py | 368 ++++++++++++++++++ fluid/ocr_recognition/crnn_ctc_model.py | 10 +- .../{ctc_reader.py => data_reader.py} | 36 +- fluid/ocr_recognition/eval.py | 30 +- .../images/train_attention.jpg | Bin 0 -> 74088 bytes fluid/ocr_recognition/infer.py | 47 ++- .../{ctc_train.py => train.py} | 42 +- fluid/ocr_recognition/utility.py | 47 ++- 10 files changed, 548 insertions(+), 60 deletions(-) create mode 100755 fluid/ocr_recognition/attention_model.py mode change 100644 => 100755 fluid/ocr_recognition/crnn_ctc_model.py rename fluid/ocr_recognition/{ctc_reader.py => data_reader.py} (89%) create mode 100644 fluid/ocr_recognition/images/train_attention.jpg mode change 100644 => 100755 fluid/ocr_recognition/infer.py rename fluid/ocr_recognition/{ctc_train.py => train.py} (89%) mode change 100644 => 100755 mode change 100644 => 100755 fluid/ocr_recognition/utility.py diff --git a/fluid/ocr_recognition/.run_ce.sh b/fluid/ocr_recognition/.run_ce.sh index c306f310..90abc143 100755 --- a/fluid/ocr_recognition/.run_ce.sh +++ b/fluid/ocr_recognition/.run_ce.sh @@ -1,4 +1,4 @@ export ce_mode=1 -python ctc_train.py --batch_size=32 --total_step=1 --eval_period=1 --log_period=1 --use_gpu=True 1> ./tmp.log +python train.py --batch_size=32 --total_step=1 --eval_period=1 --log_period=1 --use_gpu=True 1> ./tmp.log cat tmp.log | python _ce.py rm tmp.log diff --git a/fluid/ocr_recognition/README.md b/fluid/ocr_recognition/README.md index 50b72440..ad70aa0c 100644 --- a/fluid/ocr_recognition/README.md +++ b/fluid/ocr_recognition/README.md @@ -5,8 +5,9 @@ ## 代码结构 ``` ├── ctc_reader.py # 下载、读取、处理数据。 -├── crnn_ctc_model.py # 定义了训练网络、预测网络和evaluate网络。 -├── ctc_train.py # 用于模型的训练。 +├── crnn_ctc_model.py # 定义了OCR CTC model的网络结构。 +├── attention_model.py # 定义了OCR attention model的网络结构。 +├── train.py # 用于模型的训练。 ├── infer.py # 加载训练好的模型文件,对新数据进行预测。 ├── eval.py # 评估模型在指定数据集上的效果。 └── utils.py # 定义通用的函数。 @@ -15,9 +16,16 @@ ## 简介 -本章的任务是识别含有单行汉语字符图片,首先采用卷积将图片转为特征图, 然后使用`im2sequence op`将特征图转为序列,通过`双向GRU`学习到序列特征。训练过程选用的损失函数为CTC(Connectionist Temporal Classification) loss,最终的评估指标为样本级别的错误率。 +本章的任务是识别图片中单行英文字符,这里我们分别使用CTC model和attention model两种不同的模型来完成该任务。 +这两种模型的有相同的编码部分,首先采用卷积将图片转为特征图, 然后使用`im2sequence op`将特征图转为序列,通过`双向GRU`学习到序列特征。 +两种模型的解码部分和使用的损失函数区别如下: + +- CTC model: 训练过程选用的损失函数为CTC(Connectionist Temporal Classification) loss, 预测阶段采用的是贪婪策略和CTC解码策略。 +- Attention model: 训练过程选用的是带注意力机制的解码策略和交叉信息熵损失函数,预测阶段采用的是柱搜索策略。 + +训练以上两种模型的评估指标为样本级别的错误率。 ## 数据 @@ -124,15 +132,23 @@ env OMP_NUM_THREADS= python ctc_train.py --use_gpu False env CUDA_VISIABLE_DEVICES=0,1,2,3 python ctc_train.py --parallel=True ``` +默认使用的是`CTC model`, 可以通过选项`--model="attention"`切换为`attention model`。 + 执行`python ctc_train.py --help`可查看更多使用方式和参数详细说明。 -图2为使用默认参数和默认数据集训练的收敛曲线,其中横坐标轴为训练迭代次数,纵轴为样本级错误率。其中,蓝线为训练集上的样本错误率,红线为测试集上的样本错误率。在60轮迭代训练中,测试集上最低错误率为第32轮的22.0%. +图2为使用默认参数在默认数据集上训练`CTC model`的收敛曲线,其中横坐标轴为训练迭代次数,纵轴为样本级错误率。其中,蓝线为训练集上的样本错误率,红线为测试集上的样本错误率。测试集上最低错误率为22.0%.

-
+
图 2

+图3为使用默认参数在默认数据集上训练`attention model`的收敛曲线,其中横坐标轴为训练迭代次数,纵轴为样本级错误率。其中,蓝线为训练集上的样本错误率,红线为测试集上的样本错误率。测试集上最低错误率为16.25%. + +

+
+图 3 +

## 测试 diff --git a/fluid/ocr_recognition/attention_model.py b/fluid/ocr_recognition/attention_model.py new file mode 100755 index 00000000..0f24da99 --- /dev/null +++ b/fluid/ocr_recognition/attention_model.py @@ -0,0 +1,368 @@ +import paddle.fluid as fluid + +decoder_size = 128 +word_vector_dim = 128 +max_length = 100 +sos = 0 +eos = 1 +gradient_clip = 10 +LR = 1.0 +beam_size = 2 +learning_rate_decay = None + + +def conv_bn_pool(input, + group, + out_ch, + act="relu", + is_test=False, + pool=True, + use_cudnn=True): + tmp = input + for i in xrange(group): + filter_size = 3 + conv_std = (2.0 / (filter_size**2 * tmp.shape[1]))**0.5 + conv_param = fluid.ParamAttr( + initializer=fluid.initializer.Normal(0.0, conv_std)) + tmp = fluid.layers.conv2d( + input=tmp, + num_filters=out_ch[i], + filter_size=3, + padding=1, + bias_attr=False, + param_attr=conv_param, + act=None, # LinearActivation + use_cudnn=use_cudnn) + + tmp = fluid.layers.batch_norm(input=tmp, act=act, is_test=is_test) + if pool == True: + tmp = fluid.layers.pool2d( + input=tmp, + pool_size=2, + pool_type='max', + pool_stride=2, + use_cudnn=use_cudnn, + ceil_mode=True) + + return tmp + + +def ocr_convs(input, is_test=False, use_cudnn=True): + tmp = input + tmp = conv_bn_pool(tmp, 2, [16, 16], is_test=is_test, use_cudnn=use_cudnn) + tmp = conv_bn_pool(tmp, 2, [32, 32], is_test=is_test, use_cudnn=use_cudnn) + tmp = conv_bn_pool(tmp, 2, [64, 64], is_test=is_test, use_cudnn=use_cudnn) + tmp = conv_bn_pool( + tmp, 2, [128, 128], is_test=is_test, pool=False, use_cudnn=use_cudnn) + return tmp + + +def encoder_net(images, rnn_hidden_size=200, is_test=False, use_cudnn=True): + + conv_features = ocr_convs(images, is_test=is_test, use_cudnn=use_cudnn) + + sliced_feature = fluid.layers.im2sequence( + input=conv_features, + stride=[1, 1], + filter_size=[conv_features.shape[2], 1]) + + para_attr = fluid.ParamAttr(initializer=fluid.initializer.Normal(0.0, 0.02)) + bias_attr = fluid.ParamAttr( + initializer=fluid.initializer.Normal(0.0, 0.02), learning_rate=2.0) + + fc_1 = fluid.layers.fc(input=sliced_feature, + size=rnn_hidden_size * 3, + param_attr=para_attr, + bias_attr=False) + fc_2 = fluid.layers.fc(input=sliced_feature, + size=rnn_hidden_size * 3, + param_attr=para_attr, + bias_attr=False) + + gru_forward = fluid.layers.dynamic_gru( + input=fc_1, + size=rnn_hidden_size, + param_attr=para_attr, + bias_attr=bias_attr, + candidate_activation='relu') + gru_backward = fluid.layers.dynamic_gru( + input=fc_2, + size=rnn_hidden_size, + is_reverse=True, + param_attr=para_attr, + bias_attr=bias_attr, + candidate_activation='relu') + + encoded_vector = fluid.layers.concat( + input=[gru_forward, gru_backward], axis=1) + encoded_proj = fluid.layers.fc(input=encoded_vector, + size=decoder_size, + bias_attr=False) + + return gru_backward, encoded_vector, encoded_proj + + +def gru_decoder_with_attention(target_embedding, encoder_vec, encoder_proj, + decoder_boot, decoder_size, num_classes): + def simple_attention(encoder_vec, encoder_proj, decoder_state): + decoder_state_proj = fluid.layers.fc(input=decoder_state, + size=decoder_size, + bias_attr=False) + decoder_state_expand = fluid.layers.sequence_expand( + x=decoder_state_proj, y=encoder_proj) + concated = encoder_proj + decoder_state_expand + concated = fluid.layers.tanh(x=concated) + attention_weights = fluid.layers.fc(input=concated, + size=1, + act=None, + bias_attr=False) + attention_weights = fluid.layers.sequence_softmax( + input=attention_weights) + weigths_reshape = fluid.layers.reshape(x=attention_weights, shape=[-1]) + scaled = fluid.layers.elementwise_mul( + x=encoder_vec, y=weigths_reshape, axis=0) + context = fluid.layers.sequence_pool(input=scaled, pool_type='sum') + return context + + rnn = fluid.layers.DynamicRNN() + + with rnn.block(): + current_word = rnn.step_input(target_embedding) + encoder_vec = rnn.static_input(encoder_vec) + encoder_proj = rnn.static_input(encoder_proj) + hidden_mem = rnn.memory(init=decoder_boot, need_reorder=True) + context = simple_attention(encoder_vec, encoder_proj, hidden_mem) + fc_1 = fluid.layers.fc(input=context, + size=decoder_size * 3, + bias_attr=False) + fc_2 = fluid.layers.fc(input=current_word, + size=decoder_size * 3, + bias_attr=False) + decoder_inputs = fc_1 + fc_2 + h, _, _ = fluid.layers.gru_unit( + input=decoder_inputs, hidden=hidden_mem, size=decoder_size * 3) + rnn.update_memory(hidden_mem, h) + out = fluid.layers.fc(input=h, + size=num_classes + 2, + bias_attr=True, + act='softmax') + rnn.output(out) + return rnn() + + +def attention_train_net(args, data_shape, num_classes): + + images = fluid.layers.data(name='pixel', shape=data_shape, dtype='float32') + label_in = fluid.layers.data( + name='label_in', shape=[1], dtype='int32', lod_level=1) + label_out = fluid.layers.data( + name='label_out', shape=[1], dtype='int32', lod_level=1) + + gru_backward, encoded_vector, encoded_proj = encoder_net(images) + + backward_first = fluid.layers.sequence_pool( + input=gru_backward, pool_type='first') + decoder_boot = fluid.layers.fc(input=backward_first, + size=decoder_size, + bias_attr=False, + act="relu") + + label_in = fluid.layers.cast(x=label_in, dtype='int64') + trg_embedding = fluid.layers.embedding( + input=label_in, + size=[num_classes + 2, word_vector_dim], + dtype='float32') + prediction = gru_decoder_with_attention(trg_embedding, encoded_vector, + encoded_proj, decoder_boot, + decoder_size, num_classes) + fluid.clip.set_gradient_clip(fluid.clip.GradientClipByValue(gradient_clip)) + label_out = fluid.layers.cast(x=label_out, dtype='int64') + + _, maxid = fluid.layers.topk(input=prediction, k=1) + error_evaluator = fluid.evaluator.EditDistance( + input=maxid, label=label_out, ignored_tokens=[sos, eos]) + + inference_program = fluid.default_main_program().clone(for_test=True) + + cost = fluid.layers.cross_entropy(input=prediction, label=label_out) + sum_cost = fluid.layers.reduce_sum(cost) + + if learning_rate_decay == "piecewise_decay": + learning_rate = fluid.layers.piecewise_decay([50000], [LR, LR * 0.01]) + else: + learning_rate = LR + + optimizer = fluid.optimizer.Adadelta( + learning_rate=learning_rate, epsilon=1.0e-6, rho=0.9) + optimizer.minimize(sum_cost) + + model_average = None + if args.average_window > 0: + model_average = fluid.optimizer.ModelAverage( + args.average_window, + min_average_window=args.min_average_window, + max_average_window=args.max_average_window) + + return sum_cost, error_evaluator, inference_program, model_average + + +def simple_attention(encoder_vec, encoder_proj, decoder_state, decoder_size): + decoder_state_proj = fluid.layers.fc(input=decoder_state, + size=decoder_size, + bias_attr=False) + decoder_state_expand = fluid.layers.sequence_expand( + x=decoder_state_proj, y=encoder_proj) + concated = fluid.layers.elementwise_add(encoder_proj, decoder_state_expand) + concated = fluid.layers.tanh(x=concated) + attention_weights = fluid.layers.fc(input=concated, + size=1, + act=None, + bias_attr=False) + attention_weights = fluid.layers.sequence_softmax(input=attention_weights) + weigths_reshape = fluid.layers.reshape(x=attention_weights, shape=[-1]) + scaled = fluid.layers.elementwise_mul( + x=encoder_vec, y=weigths_reshape, axis=0) + context = fluid.layers.sequence_pool(input=scaled, pool_type='sum') + return context + + +def attention_infer(images, num_classes, use_cudnn=True): + + max_length = 20 + gru_backward, encoded_vector, encoded_proj = encoder_net( + images, is_test=True, use_cudnn=use_cudnn) + + backward_first = fluid.layers.sequence_pool( + input=gru_backward, pool_type='first') + decoder_boot = fluid.layers.fc(input=backward_first, + size=decoder_size, + bias_attr=False, + act="relu") + init_state = decoder_boot + array_len = fluid.layers.fill_constant( + shape=[1], dtype='int64', value=max_length) + counter = fluid.layers.zeros(shape=[1], dtype='int64', force_cpu=True) + + # fill the first element with init_state + state_array = fluid.layers.create_array('float32') + fluid.layers.array_write(init_state, array=state_array, i=counter) + + # ids, scores as memory + ids_array = fluid.layers.create_array('int64') + scores_array = fluid.layers.create_array('float32') + + init_ids = fluid.layers.data( + name="init_ids", shape=[1], dtype="int64", lod_level=2) + init_scores = fluid.layers.data( + name="init_scores", shape=[1], dtype="float32", lod_level=2) + + fluid.layers.array_write(init_ids, array=ids_array, i=counter) + fluid.layers.array_write(init_scores, array=scores_array, i=counter) + + cond = fluid.layers.less_than(x=counter, y=array_len) + while_op = fluid.layers.While(cond=cond) + with while_op.block(): + pre_ids = fluid.layers.array_read(array=ids_array, i=counter) + pre_state = fluid.layers.array_read(array=state_array, i=counter) + pre_score = fluid.layers.array_read(array=scores_array, i=counter) + + pre_ids_emb = fluid.layers.embedding( + input=pre_ids, + size=[num_classes + 2, word_vector_dim], + dtype='float32') + + context = simple_attention(encoded_vector, encoded_proj, pre_state, + decoder_size) + + # expand the recursive_sequence_lengths of pre_state to be the same with pre_score + pre_state_expanded = fluid.layers.sequence_expand(pre_state, pre_score) + context_expanded = fluid.layers.sequence_expand(context, pre_score) + fc_1 = fluid.layers.fc(input=context_expanded, + size=decoder_size * 3, + bias_attr=False) + fc_2 = fluid.layers.fc(input=pre_ids_emb, + size=decoder_size * 3, + bias_attr=False) + + decoder_inputs = fc_1 + fc_2 + current_state, _, _ = fluid.layers.gru_unit( + input=decoder_inputs, + hidden=pre_state_expanded, + size=decoder_size * 3) + + current_state_with_lod = fluid.layers.lod_reset( + x=current_state, y=pre_score) + # use score to do beam search + current_score = fluid.layers.fc(input=current_state_with_lod, + size=num_classes + 2, + bias_attr=True, + act='softmax') + topk_scores, topk_indices = fluid.layers.topk( + current_score, k=beam_size) + + # calculate accumulated scores after topk to reduce computation cost + accu_scores = fluid.layers.elementwise_add( + x=fluid.layers.log(topk_scores), + y=fluid.layers.reshape( + pre_score, shape=[-1]), + axis=0) + selected_ids, selected_scores = fluid.layers.beam_search( + pre_ids, + pre_score, + topk_indices, + accu_scores, + beam_size, + 1, # end_id + #level=0 + ) + + fluid.layers.increment(x=counter, value=1, in_place=True) + + # update the memories + fluid.layers.array_write(current_state, array=state_array, i=counter) + fluid.layers.array_write(selected_ids, array=ids_array, i=counter) + fluid.layers.array_write(selected_scores, array=scores_array, i=counter) + + # update the break condition: up to the max length or all candidates of + # source sentences have ended. + length_cond = fluid.layers.less_than(x=counter, y=array_len) + finish_cond = fluid.layers.logical_not( + fluid.layers.is_empty(x=selected_ids)) + fluid.layers.logical_and(x=length_cond, y=finish_cond, out=cond) + + ids, scores = fluid.layers.beam_search_decode(ids_array, scores_array, + beam_size, eos) + return ids + + +def attention_eval(data_shape, num_classes): + images = fluid.layers.data(name='pixel', shape=data_shape, dtype='float32') + label_in = fluid.layers.data( + name='label_in', shape=[1], dtype='int32', lod_level=1) + label_out = fluid.layers.data( + name='label_out', shape=[1], dtype='int32', lod_level=1) + label_out = fluid.layers.cast(x=label_out, dtype='int64') + label_in = fluid.layers.cast(x=label_in, dtype='int64') + + gru_backward, encoded_vector, encoded_proj = encoder_net( + images, is_test=True) + + backward_first = fluid.layers.sequence_pool( + input=gru_backward, pool_type='first') + decoder_boot = fluid.layers.fc(input=backward_first, + size=decoder_size, + bias_attr=False, + act="relu") + trg_embedding = fluid.layers.embedding( + input=label_in, + size=[num_classes + 2, word_vector_dim], + dtype='float32') + prediction = gru_decoder_with_attention(trg_embedding, encoded_vector, + encoded_proj, decoder_boot, + decoder_size, num_classes) + _, maxid = fluid.layers.topk(input=prediction, k=1) + error_evaluator = fluid.evaluator.EditDistance( + input=maxid, label=label_out, ignored_tokens=[sos, eos]) + cost = fluid.layers.cross_entropy(input=prediction, label=label_out) + sum_cost = fluid.layers.reduce_sum(cost) + return error_evaluator, sum_cost diff --git a/fluid/ocr_recognition/crnn_ctc_model.py b/fluid/ocr_recognition/crnn_ctc_model.py old mode 100644 new mode 100755 index a5d4c70f..82d5c9ff --- a/fluid/ocr_recognition/crnn_ctc_model.py +++ b/fluid/ocr_recognition/crnn_ctc_model.py @@ -166,13 +166,16 @@ def encoder_net(images, return fc_out -def ctc_train_net(images, label, args, num_classes): +def ctc_train_net(args, data_shape, num_classes): L2_RATE = 0.0004 LR = 1.0e-3 MOMENTUM = 0.9 learning_rate_decay = None regularizer = fluid.regularizer.L2Decay(L2_RATE) + images = fluid.layers.data(name='pixel', shape=data_shape, dtype='float32') + label = fluid.layers.data( + name='label', shape=[1], dtype='int32', lod_level=1) fc_out = encoder_net( images, num_classes, @@ -211,7 +214,10 @@ def ctc_infer(images, num_classes, use_cudnn): return fluid.layers.ctc_greedy_decoder(input=fc_out, blank=num_classes) -def ctc_eval(images, label, num_classes, use_cudnn): +def ctc_eval(data_shape, num_classes, use_cudnn): + images = fluid.layers.data(name='pixel', shape=data_shape, dtype='float32') + label = fluid.layers.data( + name='label', shape=[1], dtype='int32', lod_level=1) fc_out = encoder_net(images, num_classes, is_test=True, use_cudnn=use_cudnn) decoded_out = fluid.layers.ctc_greedy_decoder( input=fc_out, blank=num_classes) diff --git a/fluid/ocr_recognition/ctc_reader.py b/fluid/ocr_recognition/data_reader.py similarity index 89% rename from fluid/ocr_recognition/ctc_reader.py rename to fluid/ocr_recognition/data_reader.py index fcf9bd8c..e537914e 100644 --- a/fluid/ocr_recognition/ctc_reader.py +++ b/fluid/ocr_recognition/data_reader.py @@ -7,6 +7,8 @@ from os import path from paddle.dataset.image import load_image import paddle +SOS = 0 +EOS = 1 NUM_CLASSES = 95 DATA_SHAPE = [1, 48, 512] @@ -22,8 +24,8 @@ TEST_LIST_FILE_NAME = "test.list" class DataGenerator(object): - def __init__(self): - pass + def __init__(self, model="crnn_ctc"): + self.model = model def train_reader(self, img_root_dir, @@ -89,7 +91,10 @@ class DataGenerator(object): img = img.resize((sz[0], sz[1])) img = np.array(img) - 127.5 img = img[np.newaxis, ...] - result.append([img, label]) + if self.model == "crnn_ctc": + result.append([img, label]) + else: + result.append([img, [SOS] + label, label + [EOS]]) yield result if not cycle: break @@ -117,7 +122,10 @@ class DataGenerator(object): 'L') img = np.array(img) - 127.5 img = img[np.newaxis, ...] - yield img, label + if self.model == "crnn_ctc": + yield img, label + else: + yield img, [SOS] + label, label + [EOS] return reader @@ -185,8 +193,12 @@ def data_shape(): return DATA_SHAPE -def train(batch_size, train_images_dir=None, train_list_file=None, cycle=False): - generator = DataGenerator() +def train(batch_size, + train_images_dir=None, + train_list_file=None, + cycle=False, + model="crnn_ctc"): + generator = DataGenerator(model) if train_images_dir is None: data_dir = download_data() train_images_dir = path.join(data_dir, TRAIN_DATA_DIR_NAME) @@ -199,8 +211,11 @@ def train(batch_size, train_images_dir=None, train_list_file=None, cycle=False): train_images_dir, train_list_file, batch_size, cycle, shuffle=shuffle) -def test(batch_size=1, test_images_dir=None, test_list_file=None): - generator = DataGenerator() +def test(batch_size=1, + test_images_dir=None, + test_list_file=None, + model="crnn_ctc"): + generator = DataGenerator(model) if test_images_dir is None: data_dir = download_data() test_images_dir = path.join(data_dir, TEST_DATA_DIR_NAME) @@ -213,8 +228,9 @@ def test(batch_size=1, test_images_dir=None, test_list_file=None): def inference(batch_size=1, infer_images_dir=None, infer_list_file=None, - cycle=False): - generator = DataGenerator() + cycle=False, + model="crnn_ctc"): + generator = DataGenerator(model) return paddle.batch( generator.infer_reader(infer_images_dir, infer_list_file, cycle), batch_size) diff --git a/fluid/ocr_recognition/eval.py b/fluid/ocr_recognition/eval.py index 69241316..35603757 100644 --- a/fluid/ocr_recognition/eval.py +++ b/fluid/ocr_recognition/eval.py @@ -1,9 +1,9 @@ import paddle.v2 as paddle import paddle.fluid as fluid -from utility import add_arguments, print_arguments, to_lodtensor, get_feeder_data -from crnn_ctc_model import ctc_infer +from utility import add_arguments, print_arguments, to_lodtensor, get_ctc_feeder_data, get_attention_feeder_data +from attention_model import attention_eval from crnn_ctc_model import ctc_eval -import ctc_reader +import data_reader import argparse import functools import os @@ -11,27 +11,34 @@ import os parser = argparse.ArgumentParser(description=__doc__) add_arg = functools.partial(add_arguments, argparser=parser) # yapf: disable -add_arg('model_path', str, None, "The model path to be used for inference.") +add_arg('model', str, "crnn_ctc", "Which type of network to be used. 'crnn_ctc' or 'attention'") +add_arg('model_path', str, "", "The model path to be used for inference.") add_arg('input_images_dir', str, None, "The directory of images.") add_arg('input_images_list', str, None, "The list file of images.") add_arg('use_gpu', bool, True, "Whether use GPU to eval.") # yapf: enable -def evaluate(args, eval=ctc_eval, data_reader=ctc_reader): +def evaluate(args): """OCR inference""" + + if args.model == "crnn_ctc": + eval = ctc_eval + get_feeder_data = get_ctc_feeder_data + else: + eval = attention_eval + get_feeder_data = get_attention_feeder_data + num_classes = data_reader.num_classes() data_shape = data_reader.data_shape() # define network - images = fluid.layers.data(name='pixel', shape=data_shape, dtype='float32') - label = fluid.layers.data( - name='label', shape=[1], dtype='int32', lod_level=1) - evaluator, cost = eval(images, label, num_classes) + evaluator, cost = eval(data_shape, num_classes) # data reader test_reader = data_reader.test( test_images_dir=args.input_images_dir, - test_list_file=args.input_images_list) + test_list_file=args.input_images_list, + model=args.model) # prepare environment place = fluid.CPUPlace() @@ -55,6 +62,7 @@ def evaluate(args, eval=ctc_eval, data_reader=ctc_reader): for data in test_reader(): count += 1 exe.run(fluid.default_main_program(), feed=get_feeder_data(data, place)) + print "Read %d samples;\r" % count, avg_distance, avg_seq_error = evaluator.eval(exe) print "Read %d samples; avg_distance: %s; avg_seq_error: %s" % ( count, avg_distance, avg_seq_error) @@ -63,7 +71,7 @@ def evaluate(args, eval=ctc_eval, data_reader=ctc_reader): def main(): args = parser.parse_args() print_arguments(args) - evaluate(args, data_reader=ctc_reader) + evaluate(args) if __name__ == "__main__": diff --git a/fluid/ocr_recognition/images/train_attention.jpg b/fluid/ocr_recognition/images/train_attention.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f9c1ce30bb8f5e9e704255e31c896f355727e2fd GIT binary patch literal 74088 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 k`xmJ;4EMc!4LoMosv}&pl(c%1H^Dy@C5 0 else False) + cycle=True if args.iterations > 0 else False, + model=args.model) # prepare environment place = fluid.CPUPlace() if args.use_gpu: @@ -68,6 +78,7 @@ def inference(args, infer=ctc_infer, data_reader=ctc_reader): batch_times = [] iters = 0 for data in infer_reader(): + feed_dict = get_feeder_data(data, place) if args.iterations > 0 and iters == args.iterations + args.skip_batch_num: break if iters < args.skip_batch_num: @@ -77,14 +88,13 @@ def inference(args, infer=ctc_infer, data_reader=ctc_reader): start = time.time() result = exe.run(fluid.default_main_program(), - feed=get_feeder_data( - data, place, need_label=False), - fetch_list=[sequence], + feed=feed_dict, + fetch_list=[ids], return_numpy=False) + indexes = prune(np.array(result[0]).flatten(), 0, 1) batch_time = time.time() - start fps = args.batch_size / batch_time batch_times.append(batch_time) - indexes = np.array(result[0]).flatten() if dict_map is not None: print "Iteration %d, latency: %.5f s, fps: %f, result: %s" % ( iters, @@ -114,18 +124,29 @@ def inference(args, infer=ctc_infer, data_reader=ctc_reader): print('average fps: %.5f, fps for 99pc latency: %.5f' % (fps_avg, fps_pc99)) +def prune(words, sos, eos): + """Remove unused tokens in prediction result.""" + start_index = 0 + end_index = len(words) + if sos in words: + start_index = np.where(words == sos)[0][0] + 1 + if eos in words: + end_index = np.where(words == eos)[0][0] + return words[start_index:end_index] + + def main(): args = parser.parse_args() print_arguments(args) if args.profile: if args.use_gpu: with profiler.cuda_profiler("cuda_profiler.txt", 'csv') as nvprof: - inference(args, data_reader=ctc_reader) + inference(args) else: with profiler.profiler("CPU", sorted_key='total') as cpuprof: - inference(args, data_reader=ctc_reader) + inference(args) else: - inference(args, data_reader=ctc_reader) + inference(args) if __name__ == "__main__": diff --git a/fluid/ocr_recognition/ctc_train.py b/fluid/ocr_recognition/train.py old mode 100644 new mode 100755 similarity index 89% rename from fluid/ocr_recognition/ctc_train.py rename to fluid/ocr_recognition/train.py index a1cb5299..12c89456 --- a/fluid/ocr_recognition/ctc_train.py +++ b/fluid/ocr_recognition/train.py @@ -1,9 +1,10 @@ -"""Trainer for OCR CTC model.""" +"""Trainer for OCR CTC or attention model.""" import paddle.fluid as fluid +from utility import add_arguments, print_arguments, to_lodtensor, get_ctc_feeder_data, get_attention_feeder_data import paddle.fluid.profiler as profiler -from utility import add_arguments, print_arguments, to_lodtensor, get_feeder_data from crnn_ctc_model import ctc_train_net -import ctc_reader +from attention_model import attention_train_net +import data_reader import argparse import functools import sys @@ -20,6 +21,7 @@ add_arg('log_period', int, 1000, "Log period.") add_arg('save_model_period', int, 15000, "Save model period. '-1' means never saving the model.") add_arg('eval_period', int, 15000, "Evaluate period. '-1' means never evaluating the model.") add_arg('save_model_dir', str, "./models", "The directory the model to be saved to.") +add_arg('model', str, "crnn_ctc", "Which type of network to be used. 'crnn_ctc' or 'attention'") add_arg('init_model', str, None, "The init model file of directory.") add_arg('use_gpu', bool, True, "Whether use GPU to train.") add_arg('min_average_window',int, 10000, "Min average window.") @@ -32,8 +34,16 @@ add_arg('skip_test', bool, False, "Whether to skip test phase.") # yapf: enable -def train(args, data_reader=ctc_reader): - """OCR CTC training""" +def train(args): + """OCR training""" + + if args.model == "crnn_ctc": + train_net = ctc_train_net + get_feeder_data = get_ctc_feeder_data + else: + train_net = attention_train_net + get_feeder_data = get_attention_feeder_data + num_classes = None train_images = None train_list = None @@ -43,20 +53,18 @@ def train(args, data_reader=ctc_reader): ) if num_classes is None else num_classes data_shape = data_reader.data_shape() # define network - images = fluid.layers.data(name='pixel', shape=data_shape, dtype='float32') - label = fluid.layers.data( - name='label', shape=[1], dtype='int32', lod_level=1) - sum_cost, error_evaluator, inference_program, model_average = ctc_train_net( - images, label, args, num_classes) + sum_cost, error_evaluator, inference_program, model_average = train_net( + args, data_shape, num_classes) # data reader train_reader = data_reader.train( args.batch_size, train_images_dir=train_images, train_list_file=train_list, - cycle=args.total_step > 0) + cycle=args.total_step > 0, + model=args.model) test_reader = data_reader.test( - test_images_dir=test_images, test_list_file=test_list) + test_images_dir=test_images, test_list_file=test_list, model=args.model) # prepare environment place = fluid.CPUPlace() @@ -144,7 +152,7 @@ def train(args, data_reader=ctc_reader): iter_num += 1 # training log if iter_num % args.log_period == 0: - print "\nTime: %s; Iter[%d]; Avg Warp-CTC loss: %.3f; Avg seq err: %.3f" % ( + print "\nTime: %s; Iter[%d]; Avg loss: %.3f; Avg seq err: %.3f" % ( time.time(), iter_num, total_loss / (args.log_period * args.batch_size), total_seq_error / (args.log_period * args.batch_size)) @@ -155,7 +163,7 @@ def train(args, data_reader=ctc_reader): total_loss = 0.0 total_seq_error = 0.0 -# evaluate + # evaluate if not args.skip_test and iter_num % args.eval_period == 0: if model_average: with model_average.apply(exe): @@ -195,12 +203,12 @@ def main(): if args.profile: if args.use_gpu: with profiler.cuda_profiler("cuda_profiler.txt", 'csv') as nvprof: - train(args, data_reader=ctc_reader) + train(args) else: with profiler.profiler("CPU", sorted_key='total') as cpuprof: - train(args, data_reader=ctc_reader) + train(args) else: - train(args, data_reader=ctc_reader) + train(args) if __name__ == "__main__": diff --git a/fluid/ocr_recognition/utility.py b/fluid/ocr_recognition/utility.py old mode 100644 new mode 100755 index 67a5bfa0..d401b225 --- a/fluid/ocr_recognition/utility.py +++ b/fluid/ocr_recognition/utility.py @@ -19,6 +19,7 @@ from __future__ import print_function import distutils.util import numpy as np from paddle.fluid import core +import paddle.fluid as fluid def print_arguments(args): @@ -77,7 +78,7 @@ def to_lodtensor(data, place): return res -def get_feeder_data(data, place, need_label=True): +def get_ctc_feeder_data(data, place, need_label=True): pixel_tensor = core.LoDTensor() pixel_data = None pixel_data = np.concatenate( @@ -88,3 +89,47 @@ def get_feeder_data(data, place, need_label=True): return {"pixel": pixel_tensor, "label": label_tensor} else: return {"pixel": pixel_tensor} + + +def get_attention_feeder_data(data, place, need_label=True): + pixel_tensor = core.LoDTensor() + pixel_data = None + pixel_data = np.concatenate( + map(lambda x: x[0][np.newaxis, :], data), axis=0).astype("float32") + pixel_tensor.set(pixel_data, place) + label_in_tensor = to_lodtensor(map(lambda x: x[1], data), place) + label_out_tensor = to_lodtensor(map(lambda x: x[2], data), place) + if need_label: + return { + "pixel": pixel_tensor, + "label_in": label_in_tensor, + "label_out": label_out_tensor + } + else: + return {"pixel": pixel_tensor} + + +def get_attention_feeder_for_infer(data, place): + batch_size = len(data) + init_ids_data = np.array([0 for _ in range(batch_size)], dtype='int64') + init_scores_data = np.array( + [1. for _ in range(batch_size)], dtype='float32') + init_ids_data = init_ids_data.reshape((batch_size, 1)) + init_scores_data = init_scores_data.reshape((batch_size, 1)) + init_recursive_seq_lens = [1] * batch_size + init_recursive_seq_lens = [init_recursive_seq_lens, init_recursive_seq_lens] + init_ids = fluid.create_lod_tensor(init_ids_data, init_recursive_seq_lens, + place) + init_scores = fluid.create_lod_tensor(init_scores_data, + init_recursive_seq_lens, place) + + pixel_tensor = core.LoDTensor() + pixel_data = None + pixel_data = np.concatenate( + map(lambda x: x[0][np.newaxis, :], data), axis=0).astype("float32") + pixel_tensor.set(pixel_data, place) + return { + "pixel": pixel_tensor, + "init_ids": init_ids, + "init_scores": init_scores + } -- GitLab