街景字符编码识别之定长字符识别模型

点赞再看,养成习惯!

前言

终于终于到了模型搭建、训练和测试的阶段了,哦不,太高兴了,这篇文章只讲六字符识别模型的搭建,会涉及到训练过程用来测试模型搭建是否正确。废话少说,上干货咯~

正文

在CV分类领域,模型搭建一般采取两种方法:自定义网络结构、迁移学习(继承预训练好的网络)。前者很适合小白入门,这一套走下去可以学到PyTorch卷积、池化等函数的使用不说,还会学习到如何去计算CNN每层的输出(这蛮重要的,如果不太明白输入图像经过卷积、池化后通道、尺寸会怎么改变,可以看看参考文献的CNN基础);后者呢,则是小编推荐对DL有基础过后,打比赛以及实际工程用到的方法,只需通过微调(一般做法是固定卷积、池化层的参数,只改变和优化全连接层)就可以利用上那些大型网络,这能节省不少时间呢!下面分别给出两种方法下的模型:

模型

SVHNModel1(自定义)

class SVHN_Model1(nn.Module):
    def __init__(self):
        super(SVHN_Model1, self).__init__()
        # CNN提取特征模块
        self.cnn = nn.Sequential(
            # 输入图像channels*height*width:(3,64,128)
            nn.Conv2d(3, 16, kernel_size=(3, 3), padding=1),
            # 卷积过后变成(16,64,128)
            nn.ReLU(),
            nn.MaxPool2d(2),
            # 池化过后变成(16,32,64)
            nn.Conv2d(16, 32, kernel_size=(3, 3), padding=1),
            # 卷积过后变成(32,32,64)
            nn.ReLU(),
            nn.MaxPool2d(2),
            # 池化过后变成(32,16,32)
            nn.Conv2d(32, 64, kernel_size=(3, 3), padding=1),
            # 卷积过后变成(64,16,32)
            nn.ReLU(),
            nn.MaxPool2d(2)
            # 池化过后变成(64,8,16)
        )
        # fc层,直接从向量64*8*16映射到0-10的标签输出上
        self.fc1 = nn.Linear(64 * 8 * 16, 11)
        self.fc2 = nn.Linear(64 * 8 * 16, 11)
        self.fc3 = nn.Linear(64 * 8 * 16, 11)
        self.fc4 = nn.Linear(64 * 8 * 16, 11)
        self.fc5 = nn.Linear(64 * 8 * 16, 11)
        self.fc6 = nn.Linear(64 * 8 * 16, 11)

    def forward(self, img):
        feat = self.cnn(img)
        # 拉伸为向量
        feat = feat.view(feat.shape[0], -1)
        c1 = self.fc1(feat)
        c2 = self.fc2(feat)
        c3 = self.fc3(feat)
        c4 = self.fc4(feat)
        c5 = self.fc5(feat)
        c6 = self.fc6(feat)
        return c1, c2, c3, c4, c5, c6

代码的注释已经很详细了,大家“自助取餐”。另外前面几篇文章已经说过,定长识别模型会输出6个字符标签,没有的填充10,所以CNN输出层是11个神经元。
SVHNModel2(继承)

class SVHN_Model2(nn.Module):
    def __init__(self):
        super(SVHN_Model2, self).__init__()
        
        # 继承resnet18
        model_conv = models.resnet18(pretrained=True)
        # 将resnet18的最后一个池化层修改为自适应的全局平均池化层
        model_conv.avgpool = nn.AdaptiveAvgPool2d(1)
        # 微调,把fc层删除
        model_conv = nn.Sequential(*list(model_conv.children())[:-1])
        self.cnn = model_conv
        # 自定义fc层
        self.fc1 = nn.Linear(512, 11)
        self.fc2 = nn.Linear(512, 11)
        self.fc3 = nn.Linear(512, 11)
        self.fc4 = nn.Linear(512, 11)
        self.fc5 = nn.Linear(512, 11)
        self.fc6 = nn.Linear(512, 11)

这里说明下,为什么修改池化层为全局平均池化层以及自定义层的输入向量为什么是512?全局平均池化层能够将任何输入都池化成只剩下通道层,也就是说前面卷积层、池化层的输出就是一个长度是通道数的向量,这样很方便微调(当然也有一些池化自身的意义在里面)。那么为什么通道数是512(fc层输入向量长度),这就需要大家去看论文了,继承什么预训练好的网络,就必须先得了解它,可以去参考文献处找到那篇论文。其实认真看了那篇论文以后,会对池化层的修改产生疑惑,因为原本resnet18就用到了全局池化层,这里修改成自适应的全局池化层,不知道有何好处?

测试

模型搭建完毕后,可以进行简单的测试,看看模型搭建有没有出错,这时候得用上前几篇文章对数据的预处理以及加载了,不过小编这里也有几个小技巧要与大家分享。先上代码:

# test
if __name__ == '__main__':
    train_path = glob.glob(r'E:\Datas\StreetCharsRecognition\mchar_train\*.png')
    train_path.sort()
    train_json = json.load(open(r'E:\Datas\StreetCharsRecognition\mchar_train.json'))
    train_label = [train_json[x]['label'] for x in train_json]

    print("扩增前数据集大小", ":", len(train_path))

    # Tip1:测试时,训练集可以调小点,这里是20个训练样本
    train_loader = torch.utils.data.DataLoader(
        SVHNDataset(train_path[:20], train_label[:20],
                    transforms.Compose([
                        transforms.Resize((64, 128)),
                        transforms.ColorJitter(0.3, 0.3, 0.2),
                        transforms.RandomRotation(5),
                        transforms.ToTensor(),
                        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                    ])),
        batch_size=10,  # 每批样本个数
        shuffle=False,  # 是否打乱顺序
        num_workers=5,  # 进程个数
    )
    # 模型生成
    model = SVHN_Model1()
    # 损失函数
    criterion = nn.CrossEntropyLoss()
    # 优化器,这里优化模型的所有参数
    optimizer = torch.optim.Adam(model.parameters(), 0.005)

    loss_plot, c0_plot = [], []
    # Tip2:epochs设置小点,这里是3
    for epoch in range(3):
        for data in train_loader:
            c0, c1, c2, c3, c4, c5 = model(data[0])
            data[1] = data[1].long()
            loss = criterion(c0, data[1][:, 0]) + \
                   criterion(c1, data[1][:, 1]) + \
                   criterion(c2, data[1][:, 2]) + \
                   criterion(c3, data[1][:, 3]) + \
                   criterion(c4, data[1][:, 4]) + \
                   criterion(c5, data[1][:, 5])
            loss /= 6 #计算loss
            # 反向传播,优化参数
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            loss_plot.append(loss.item())
            c0_plot.append((c0.argmax(1) == data[1][:, 0]).sum().item() * 1.0 / c0.shape[0])

        # Tips3:打印个东西,表示迭代过程没出问题
        # 一般都是打印epoch/train_loss/train_acc/val_loss/val_acc
        # 这里就打印epoch,凑合用
        print(epoch)

测试目标是为了验证模型搭建有无问题,所以首先可以采用cpu版本的代码测试,这可以充分利用PyTorch动态图的优势,喜欢IDE单点跟踪的童鞋,可以疯狂debug哟!!!过程中一些配置参数(比如这里的epochs)可以设置得小些,加速测试流程。

结语

这个定长字符识别模型并不难搭建,建议基础薄弱的童鞋去试试自定义模型的方法,自己设计网络结构,然后用PyTorch去实现,看看模型每层的输出是不是如你计算出的那般。

参考文献

  1. CNN基础:https://blog.csdn.net/gongsai20141004277/article/details/104379066
  2. ResNet:https://arxiv.org/pdf/1512.03385.pdf

童鞋们,让小编听见你们的声音,点赞评论,一起加油。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值