CRNN
项目实战
之前写过一篇文章利用CRNN
进行文字识别,当时重点讲的CRNN
网络结构和CNN部分的代码实现,因为缺少文字数据集没有进行真正的训练,这次正好有一批不定长的字符验证码,正好CRNN
主要就是用于端到端地对不定长的文本序列进行识别,当然是字符和文字都是可以用的,所以这里进行了一次实战。
主要是参考github
项目:https://github.com/meijieru/crnn.pytorch
关于lmdb
lmdb安装
首先关于lmdb这个数据库,python有两个包,一个是lmdb,另一个是python-lmdb。
使用pycharm的包安装功能可以看到关于lmdb的描述
Universal Python binding for the LMDB 'Lightning' Database Version 1.3.0
关于python-lmdb
的描述
simple lmdb bindings written using ctypes Version 1.0.0
所以理论上我们安装前者肯定是可以用的,但是经过亲身实践,
在pip环境中使用pip install lmdb
确实可以正常使用;
但是在conda
环境中,使用conda install lmdb
安装完成之后却无法导入包。
所以又使用:conda install python-lmdb
安装,安装完之后却可以使用,非常奇怪。
后发现原因大概率是版本问题,使用pip可以安装lmdb=1.3.0
的最新版本,而conda
只能安装lmdb=0.9.x
的版本,所以目前在conda
中只能使用python-lmdb
暂替使用。
制作适用CRNN
的lmdb
数据集
github
项目中关于如何训练自己的数据集写的不是很清楚,如果我们直接运行train.py
会遇到各种问题,首先第一个问题就是数据集的问题,lmdbDataset
中的初始化
1 | self.env = lmdb.open( |
这里会报错,因为这里读取的路径下需要有lmdb
格式的数据,所以在这之前我们需要生成lmdb
格式的数据集。
相关代码如下:
1 | # -*- coding: utf-8 -*- |
代码执行完成会在相应目录下生成data.mdb、lock.mdb两个文件。代码很简单,一看就懂,因为我的原始数据集,标签包含在图片名称中,不是另外存储在txt文件中,所以对相应代码进行了改动,另外把python2相关的东西改成了可以用python3运行。
另外有一点:
env = lmdb.open(outputPath, map_size=104857600) # 最大100MB
map_size需要根据自己的数据集设置大小(单位是B),运行完生成的data.mdb
的大小就是设置的大小,如果设置的比较大造成空间的浪费,设置的比较小可能会不够用(默认应该是10MB
)。
很多资料都写这里设置1T
,如果你的电脑硬盘空间不够,就会报错(报的错误是乱码)。
另外,如果你还遇到了其他乱码报错,大概率是路径错误。
参考文章:https://www.cnblogs.com/yanghailin/p/14519525.html
CTCLoss
在train.py中有这么一行代码,
from warpctc_pytorch import CTCLoss
初次使用的话一般是显示没有这个包的,而pytorch(version>=1.1)其实是有CTCLoss模块的
from torch.nn import CTCLoss
所以如果你的pytorch版本满足,就无需额外安装warp_ctc_pytorch了,替换一下导入代码即可。如果你的版本比较低,还是需要手动安装这个包的,如果是Windows环境下,比较麻烦的就是需要安装cmake来编译文件。不再赘述。
需要用到的warp_ctc_pytorch: https://github.com/SeanNaren/warp-ctc
参考文章:https://blog.csdn.net/weixin_40437821/article/details/105473032
然后简单介绍下,pytorch
中CTCLoss
的用法。
初始化
1 | ctc_loss = nn.CTCLoss(blank=len(CHARS)-1, reduction='mean') |
类初始化参数说明:
blank:空白标签所在的label值,默认为0,需要根据实际的标签定义进行设定;
reduction:处理output losses的方式,string类型,可选’none’ 、 ‘mean’ 及 ‘sum’,’none’表示对output losses不做任何处理,’mean’ 则对output losses取平均值处理,’sum’则是对output losses求和处理,默认为’mean’ 。
计算损失
1 | loss = ctc_loss(log_probs, targets, input_lengths, target_lengths) |
CTCLoss()
对象调用形参说明:
log_probs
:shape为(T, N, C)的模型输出张量,其中,T表示CTCLoss
的输入长度也即输出序列长度,N表示训练的batch size长度,C则表示包含有空白标签的所有要预测的字符集总长度,log_probs
一般需要经过torch.nn.functional.log_softmax
处理后再送入到CTCLoss
中;
targets:shape为(N, S) 或(sum(target_lengths))的张量,其中第一种类型,N表示训练的batch size长度,S则为标签长度,第二种类型,则为所有标签长度之和,但是需要注意的是targets不能包含有空白标签;
input_lengths:shape为(N)的张量或元组,但每一个元素的长度必须等于T即输出序列长度,一般来说模型输出序列固定后则该张量或元组的元素值均相同;
target_lengths:shape为(N)的张量或元组,其每一个元素指示每个训练输入序列的标签长度,但标签长度是可以变化的;
这里最重要的就是初始化blank参数的设置和计算损失时,log_probs
参数需要先进行log_softmax
,这也是我们在这个项目中需要调整的点,如果我们直接从warp_ctc_pytorch
更换为pytorch
内置的CTCLoss
,然后其他的不改动的话,是训练不出来结果的。
改动点:
1 | criterion = CTCLoss(blank=0, reduction='mean') # 初始化 |
比较巧合的是,在这个项目中,0的位置就是为空白字符预留的,而且blank的默认值也为0,所以不改动也是可以的。
训练
配置训练数据路径(trainroot)、验证数据路径(valroot)、预训练权重路径(pretrained),将lr(学习率)设置为0.001,nepoch=200。
训练的过程中会报很多错误,因为这个GitHub
项目可能部分代码写的比较粗糙,另一方面也是因为python2
和python3
,Linux和windows的环境问题。
我遇到了以下错误:
1、trainRoot,valRoot需要改下大小写
2、TypeError: Won't implicitly convert Unicode to bytes; use .encode()
按照错误提示加上encode
txn.get(‘num-samples’.encode())
label_byte = txn.get(label_key.encode())
imgbuf = txn.get(img_key.encode())
3、ValueError: sampler option is mutually exclusive with shuffle
1 | train_loader = torch.utils.data.DataLoader( |
因为本来shuffle参数为True,需要更改为False。
参考文章:https://blog.csdn.net/hjxu2016/article/details/111300972
4、TypeError: cannot pickle 'Environment' object
这个是因为在windows环境下,多进程训练有问题,将workers参数设置为0即可。
参考文章:https://blog.csdn.net/weixin_43272781/article/details/112757371
5、AttributeError: module 'torch' has no attribute 'longTensor'
在dataset.py脚本中,应该是torch.LongTensor。
6、TypeError: randint() takes 3 positional arguments but 4 were given
1 | random.randint(0, len(self), self.batch_size) |
我猜这里是笔误了,应该是len(self)-self.batch_size。
7、train.py中
image = torch.FloatTensor(opt.batchSize, 3, opt.imgH, opt.imgH)
推测最后一个参数应该是opt.imgW。
8、RuntimeError: The expanded size of the tensor (64) must match the existing size (63) at non-singleton dimension 0. Target sizes: [64]. Tensor sizes: [63]
嘿嘿,这个问题是因为我改了torch.range代码,因为pycharm提示说这个方法被废弃了,要求使用torch.arange
1 | batch_index = random_start + torch.arange(0, self.batch_size-1) |
torch.range(start=1, end=6)
的结果是会包含end的,而torch.arange(start=1, end=6)
的结果并不包含end。所以这里就不需要减1了。
1 | batch_index = random_start + torch.arange(0, self.batch_size) |
同样的,后面取末尾元素的时候也要去掉减1
1 | tail_index = random_start + torch.arange(0, tail) |
参考文章:https://blog.csdn.net/lunhuicnm/article/details/106712026
9、RuntimeError: set_sizes_contiguous is not allowed on a Tensor created from .data or .detach().
1 | v.data.resize_(data.size()).copy_(data) |
将上面的替换为下面的
参考文章:https://blog.csdn.net/weixin_45292103/article/details/102736742
10、还有一个关于utils.py中编码器(encode()方法的问题),
本来有一行代码是:_str = unicode(_str, 'utf-8')
这是python2中的语法,python3并不需要,但是因为前面制作lmdb数据集的时候,我们的标签进行了encode()处理,也就是这一步:txn.put(k.encode(), v.encode())
导致后面训练的时候,在对标签进行编码时,标签传过来是这样一种形式:”b’jdvfl0k’”
所以可以加上这行代码:_str = _str.replace("b'", "").replace("'", '')
。
11、在train.py的验证方法里,有这么一段代码:
1 | _, preds = preds.max(2) |
我在实际运行时发现preds.squeeze(2)会报错,然后调试发现preds此时的shape为(26, 64),所以应该无需squeeze,可以将这行代码注释。
另外,还要根据情况配置displayInterval、valInterval、saveInterval参数
displayInterval默认值是500,我这里训练不定长字符验证码,训练集总共4500张图片,batch_size=64,总共71个批次,所以设置displayInterval=10,每10个批次打印一次损失情况。其他两个参数同理,按照自己的情况调整。
终于把所有问题都解决了,可以正常训练了,但是训练的过程中打印的测试数据让我感觉不太对劲,我发现在对比预测标签和真实标签时,真实标签的形式为:”b’jdvfl0k’”
和上面同样的问题,需要处理下:
1 | target = target.replace("b'", "").replace("'", "") |
因为使用的预训练权重,训练的比较快,训练过程示例:
1 | [0/200][10/71] Loss: 0.07960973680019379 |
两轮训练结束之后准确率就达到了81.5%(200个验证图片)
经过35轮的训练,准确率可以稳定在97.5%左右。