如何在中文场景文字识别赛中赢取飞桨周边?
【飞桨开发者说】魏宏炜,福建省三明学院,本科三年级,研究方向为目标检测、OCR识别。
赛题背景
关于光学字符识别(Optical Character Recognition, 简称OCR),是指将图像上的文字转化为计算机可编辑的文字内容,众多的研究人员对相关的技术研究已久,也有不少成熟的OCR技术和产品产生,比如PaddleOCR。中文汉字识别是OCR的一个分支。因为汉语作为我们的母语,汉字主要在我国广泛使用,对汉字的种类、内涵、造字原理国内的掌握情况较透彻,所以关于汉字识别的深入研究主要集中在国内。
中文场景文字识别技术在人们的日常生活中受到广泛关注,具有丰富的应用场景,如:拍照翻译、图像检索、场景理解等。然而,中文场景中的文字面临着包括光照变化、低分辨率、字体以及排布多样性、中文字符种类多等复杂情况。如何解决上述问题成为一项极具挑战性的任务。
在本月中文场景文字识别赛中,笔者使用了飞桨开源深度学习框架,在百度学习与实训社区AI Studio上完成了数据处理、模型搭建、模型训练、模型预测等整个工作过程,拿到了6月份前十名,获得了800元京东卡。非常感谢AI Studio为参赛选手提供的GPU在线训练环境,以及丰厚GPU使用时长,对于在学习深度学习过程中硬件条件不足的学生党来说,提供了非常大的帮助。
下载安装命令 ## CPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu
比赛链接:
https://aistudio.baidu.com/aistudio/competition/detail/20
项目链接:
https://aistudio.baidu.com/aistudio/projectdetail/1136775
赛题数据分析
要求实现对街拍图片中的文字提取,并且提取出的文字序列还是长短不一的。考虑卷积神经网络在图像特征提取方面有很好的效果,输出可以采用CTC实现长度可变的预测。
1.图片信息:
(a) 标注:魅派集成吊顶
(b) 标注:母婴用品连锁
2.训练集标注文件:
h表示图片高,w表示图片宽,name为图片路径,value为图片对应的标注(如:母婴用品连锁)。
3.要求的预测结果格式为:
name为测试集图片名字,value为预测出的结果(如:母婴用品连锁)
模型原理
模型采用CRNN-CTC结构(CNN+RNN+CTC):先用CNN网络提取图像特征,转化为时间序列再传入RNN网络,最后输出使用CTC层(不同样本的标签序列长度可以不一致)。
结构图:
1. CNN层搭建:
卷积层的分量是通过从标准CNN模型中提取卷积层和最大池化层来构造的(全连接层被移除)。该模块用于从输入图像中提取序列特征表示。在被输入到网络之前,所有的图像都需要缩放到相同的高度。然后从卷积层分量产生的特征映射中提取一系列特征向量,这是递归层的输入。
#卷积层的Paddle实现 paddle.fluid.layers.conv2d(input, num_filters, filter_size, stride=1, padding=0, dilation=1, groups=None, param_attr=None, bias_attr=None, use_cudnn=True, act=None, name=None, data_format="NCHW") #最大池化层的Paddle实现 paddle.fluid.layers.pool2d(input, pool_size=-1, pool_type='max', pool_stride=1, pool_padding=0, global_pooling=False, use_cudnn=True, ceil_mode=False, name=None, exclusive=True, data_format="NCHW") #全连接层的Paddle实现 paddle.fluid.layers.fc(input, size, num_flatten_dims=1, param_attr=None, bias_attr=None, act=None, name=None)
2.LSTM层(递归层):
(a)图是传统的LSTM结构:一个LSTM由一个单元模块和三个门组成,即输入门、输出门和遗忘门。
(b)图是论文中使用的结构:深层双向LSTM的结构。将前向(从左到右)和后向(从右到左)LSTM相结合构成双向LSTM。堆叠2个双向LSTM构成深层双向LSTM。
本次比赛使用代码实现用的是双层GRU单元:
paddle.fluid.layers.dynamic_gru(input, size, param_attr=None, bias_attr=None, is_reverse=False, gate_activation='sigmoid', candidate_activation='tanh', h_0=None, origin_mode=False)
飞桨也提供了LSTM的实现方法
paddle.fluid.layers.dynamic_lstm(input, size, h_0=None, c_0=None, param_attr=None, bias_attr=None, use_peepholes=True, is_reverse=False, gate_activation='sigmoid', cell_activation='tanh', candidate_activation='tanh', dtype='float32', name=None)
3.CTC层(转录层):
转录是将RNN所做的每帧预测转换为标签序列的过程。从数学上讲,转录是找到基于每帧预测的概率最高的标签序列。在实践中,存在两种转录模式,即无词典转录和基于词典的转录。词汇是一组标签序列,预测是对的约束,例如。拼写检查字典。在无词汇模式下,预测是在没有任何词汇的情况下进行的。在基于词汇的模式下,预测是通过选择概率最高的标签序列来进行的。
我是使用第二种:预测通过选择概率最高的标签序列来进行。
#Paddle已经提供了代码实现 paddle.fluid.layers.ctc_greedy_decoder(input, blank, name=None)
4.现在来总结一下该模型的搭建吧!
论文中提供的网络层和参数的图片已经很直观了,稍微解释一下(从下往上看):
第一层(卷积层):图片(input)经过1层步长为1(s表示),填充为1(p表示)的3x3卷积,过滤器数量为64.
第二层(最大池化层):第一层的输出进行2x2的最大池化,步长为2,以此类推。BatchNormalization表示批归一化:用batch_norm实现
#batch_norm的Paddle实现 paddle.fluid.layers.batch_norm(input, act=None, is_test=False, momentum=0.9, epsilon=1e-05, param_attr=None, bias_attr=None, data_layout='NCHW', in_place=False, name=None, moving_mean_name=None, moving_variance_name=None, do_model_average_for_mean_and_var=False, use_global_stats=False)
Bidirectional-LSTM在论文中为2层的双向LSTM。实现代码中我使用的是2层的GRU单元,读者可以尝试使用LSTM。
回溯时间(BPTT)是在递归层的底部,将传播的差分序列连接成映射,将特征映射转换为特征序列的操作倒置,并反馈给卷积层。在实践中,我们创建了一个自定义网络层,称为“映射到等”,作为卷积层和递归层之间的桥梁。
完整步骤的代码实现为:
import paddle.fluid as fluid from paddle.fluid import ParamAttr from paddle.fluid.clip import GradientClipByNorm from paddle.fluid.regularizer import L2Decay from paddle.fluid.initializer import MSRA, Normal from paddle.fluid.layers import conv2d, conv2d_transpose, batch_norm, fc, dynamic_gru, im2sequence, elementwise_mul, \ pool2d, dropout, concat class CRNN(object): def __init__(self, num_classes, label_dict): self.outputs = None self.label_dict = label_dict self.num_classes = num_classes#类别数 def name(self): return 'crnn' def conv_bn_pool(self, x, n_filters, n_ConvBN, pool_stride, w_conv, is_test): w_bn = ParamAttr(regularizer=L2Decay(0.001))#设置L2正则化,初始化权重 b_bn = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0)) for _ in range(n_ConvBN): x = conv2d(x, n_filters, 3, 1, 1, param_attr=w_conv)#定义卷积层 #批归一化 x = batch_norm(x, act='relu', param_attr=w_bn, bias_attr=b_bn, is_test=is_test) assert pool_stride in [2, (2, 1), (3, 1)]#使用断言 if pool_stride == 2: x = pool2d(x, 2, 'max', pool_stride, 0, ceil_mode=True)#定义池化层,最大池化 elif pool_stride == (2, 1): x = pool2d(x, (2, 1), 'max', pool_stride, 0, ceil_mode=True) elif pool_stride == (3, 1): x = pool2d(x, (3, 1), 'max', pool_stride, 0, ceil_mode=True) return x def ocr_convs(self, x, is_test): w_conv1 = ParamAttr(regularizer=L2Decay(0.001)) w_conv2 = ParamAttr(regularizer=L2Decay(0.001)) w_conv3 = ParamAttr(regularizer=L2Decay(0.001)) x = self.conv_bn_pool(x, 128, 1, 2, w_conv1, is_test) x = self.conv_bn_pool(x, 256, 1, 2, w_conv2, is_test) x = self.conv_bn_pool(x, 512, 2, 2, w_conv2, is_test) x = self.conv_bn_pool(x, 1024, 2, (2, 1), w_conv3, is_test) return x def net(self, images, rnn_hidden_size=750, is_test=False): w_fc = ParamAttr(regularizer=L2Decay(0.001)) b_fc1 = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0)) b_fc2 = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0), learning_rate=2.0) b_fc3 = ParamAttr(regularizer=L2Decay(0.001), initializer=Normal(0.0, 0.0)) x = self.ocr_convs(images, is_test) x = im2sequence(x, (x.shape[2], 1), (1, 1))#用 filter 扫描输入的Tensor并将输入Tensor转换成序列 fc_1 = fc(x, rnn_hidden_size * 3, param_attr=w_fc, bias_attr=b_fc1)#定义全连接层,将cnn层输出处理成序列,用于代入RNN层 fc_2 = fc(x, rnn_hidden_size * 3, param_attr=w_fc, bias_attr=b_fc1) gru_forward = dynamic_gru(fc_1, rnn_hidden_size, param_attr=w_fc, bias_attr=b_fc2, candidate_activation='relu')#用于在完整序列上逐个时间步的进行单层Gated Recurrent Unit(GRU)的计算 gru_backward = dynamic_gru(fc_2, rnn_hidden_size, param_attr=w_fc, bias_attr=b_fc2, candidate_activation='relu', is_reverse=True)#使用2层结构 bigru = gru_forward + gru_backward bigru = dropout(bigru, 0.5, is_test)#使用随机丢弃单元的正则化方法 fc_out = fc(bigru, self.num_classes + 1, param_attr=w_fc, bias_attr=b_fc3)#全连接层 self.outputs = fc_out return fc_out def get_infer(self, images):#CTC转录层 return fluid.layers.ctc_greedy_decoder(input=self.outputs, blank=self.num_classes)
方法总结
该项目基于官方基线(baseline)上进行优化,在测试集上的分数大约为75.79,我在此基础上大约提高了5%。本项目主要是做了数据增强,加上必要的调参,网络部分基本上改动不大。原数据集是21万张训练集,我将他增加到了42万张(翻了2倍)。
本次使用的数据增强方法有:
-
对这42万张训练集图片都进行调整亮度、色度、对比度、饱和度,增加模型鲁棒性;
-
对其中21万张图片再进行随机的小幅度左右旋转(旋转角度为10到10之间)以及随机放大(1-2倍之间)。
在比赛之前,通常掌握的都是各方面各种课程的比较零散的知识,通过比赛,用这些知识,一步步构建出一个项目,优化模型,达到比较好的效果,并且还拿到了奖品,这是非常值得开心的。
下载安装命令 ## CPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
基于飞桨复现目标检测网络EfficientDet,感受CVPR2020的新SOTA算法的威力
【飞桨开发者说】武秉泓,国内一线互联网大厂工程师,计算机视觉技术爱好者,研究方向为目标检测、医疗影像 内容简介 下载安装命令 ## CPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安装命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu EfficientDet是由Google Brain于2019年末在目标检测领域所提出的当之无愧的新SOTA算法,并被收录于CVPR2020。本项目对目标检测算法EfficientDet进行了详细的解析,并介绍了基于官方目标检测开发套件PaddleDetection进行模型复现的细节。 EfficientDet源于CVPR2020年的一篇文章 https://arxiv.org/abs/1911.09070(源码: https://github.com/google/automl/tree/mas...
- 下一篇
使用消息队列扩展异步执行的实现方式
背景 你可能在你的项目中用过Spring的@Async注解,以此来将部分方法转化为异步执行,从而提高请求的响应效率 但在服务架构不断的演进之中,这种丢入线程池处理的方式带来的缺陷也愈发明显: 不利于监控 如果意外停机,尚未处理的任务会尽数丢失 在集群中的某个节点要处理大量异步任务时,无法将压力分担到集群中其他节点 项目中若集成了使用ThreadLocal特性的模块或第三方组件,需要注意上下文丢失的问题 思路 使用消息队列作为异步任务的实现方式,这样我们就可以: 大量成熟的MQ中间件都提供了可视化管理平台,监控更加方便 可以用消息队列Header来保存上下文,如用户信息、token等 消息队列的发布-订阅模式可以最大程度利用集群的业务处理能力 更容易保证任务的顺序性 如果有服务节点宕机,可以利用消息确认、消息重试等机制保证任务执行的正确性 实现 为了保证业务代码和实现方案解耦,类似于@Aync方案,我们同样采用注解+拦截器的方式进行逻辑注入 @Around("@annotation(org.springframework.amqp.rabbit.annotation.RabbitLi...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合Redis,开启缓存,提高访问速度