零基础⼊⻔CV赛事- 街景字符编码识别完整讲解

1 赛题理解

训练集数据包括3W张照片,验证集数据包括1W张照片,每张照片包括颜色图像和对应的编码类别和具体位置。

在这里插入图片描述

数据标签

对于训练数据每张图片将给出对于的编码标签,和具体的字符框的位置(训练集、测试集和验证集都给出字符位
置)。可用于模型训练:
在这里插入图片描述
字符的坐标具体如下所示:
在这里插入图片描述
在比赛数据(训练集、测试集和验证集)中,同一张图片中可能包括-一个或者多个字符,因此在比赛数据的JSON
标注中,会有两个字符的边框信息:
在这里插入图片描述

读取数据

JSON中标签的读取方式:
在这里插入图片描述
在这里插入图片描述

解题思路

赛题思路分析:赛题本质是分类问题,需要对图片的字符进行识别。但赛题给定的数据图片中不同图片中包含的字
符数量不等,如下图所示。有的图片的字符个数为2,有的图片字符个数为3,有的图片字符个数为4。在这里插入图片描述

简单入门思路:定长字符识别

可以将赛题抽象为一个定长字符识别问题,在赛题数据集中大部分图像中字符个数为2-4个,最多的字符个数为6
个。因此可以对于所有的图像都抽象为6个字符的识别问题,字符23填充为23XXXX,字符231填充为231XXX。在这里插入图片描述
经过填充之后,原始的赛题可以简化了6个字符的分类问题。在每个字符的分类中会进行11个类别的分类,假如分
类为填充字符,则表明该字符为空。

专业字符识别思路:不定长字符识别

在这里插入图片描述
在字符识别研究中,有特定的方法来解决此种不定长的字符识别问题,比较典型的有CRNN字符识别模型。
在本次赛题中给定的图像数据都比较规整,可以视为一个单词或者一个句子。

专业分类思路:检测再识别

在赛题数据中已经给出了训练集、验证集中所有图片中字符的位置,因此可以首先将字符的位置进行识别,利用物
体检测的思路完成。
在这里插入图片描述
此种思路需要构建字符检测模型,对测试集中的字符进行识别。可以参考物体检测模型SSD或者YOLO来完成。

2 数据读取与数据扩增

opencv

OpenCV是个跨平台的计算机视觉库, 最早由Inte1开源得来。OpenCV发展的非常早,拥有众多的计算机视觉、数
字图像处理和机器视觉等功能。OpenCV在功能上比Pillow更加强大很多。学习成本也高很多。在这里插入图片描述
cv2.imread(),返回的是个数组ndarray(dtype=uint8),其中数值我们也可以更改。
imread第二个参数有多种方式让我们读取:
cv2.IMREAD_COLOR (默认读取方式,也可以用1来代替): 读取彩色图片,不包括alpha值(透明度)
cv2.IMREAD_GRAYSCALE (也可以用0来代替): 图像灰度处理,这样压缩了了图片尺寸,但也会丢失特征。
cv2.IMREAD_UNCHANGED (也可以用-1来代替): 读取彩色图片,且包括alpha值(透明度)

图片存储:

img = cv2.imread(’./dataset/cats_and_dogs_filtered/train/cats/cat.0.jpg’, 1)
cv2.imwrite(‘cat0.png’, img)

OpenCV官网: htpt:/opencv.r/
OpenCV Github: htp/github.com/opencv/opencv
OpenCV扩展算法库: htps:/github.com/opencv/opencv contrib

数据扩增

在深度学习中数据扩增方法非常重要,数据扩增可以增加训练集的样本,同时也可以有效缓解模型过拟合的情况,
也可以给模型带来的更强的泛化能力。
在这里插入图片描述

数据扩增为什么有用?

在深度学习模型的训练过程中,数据扩增是必不可少的环节。现有深度学习的参数非常多,一般的模型可训练的参
数量基本上都是万到百万级别,而训练集样本的数量很难有这么多。
其次数据扩增可以扩展样本空间,假设现在的分类模型需要对汽车进行分类,左边的是汽车A,右边为汽车B.如.
果不使用任何数据扩增方法,深度学习模型会从汽车车头的角度来进行判别,而不是汽车具体的区别。
在这里插入图片描述

有哪些数据扩增方法?

数据扩增方法有很多:从颜色空间、尺度空间到样本空间,同时根据不同任务数据扩增都有相应的区别。
对于图像分类,数据扩增一般不会改变标签; 对于物体检测,数据扩增会改变物体坐标位置;对于图像分割,数据
扩增会改变像素标签。
在常见的数据扩增方法中,一般会从图像颜色、尺寸、形态、空间和像素等角度进行变换。当然不同的数据扩增方
法可以自由进行组合,得到更加丰富的数据扩增方法。以torchvision为例, 常见的数据扩增方法包括:

1. transforms.CenterCrop 对图片中心进行裁剪
2. transforms.Colorlitter 对图像颜色的对比度、饱和度和零度进行变换
3. transforms.FiveCrop 对图像四个角和中心进行裁剪得到五分图像
4. transforms.Grayscale 对图像进行灰度变换
5. transforms.Pad 使用固定值进行像素填充
6. transforms.RandomAffine 随机仿射变换
7. transforms.RandomCrop 随机区域裁剪
8. transforms.RandomHorizontalFlip 随机水平翻转
9. transforms.RandomRotation 随机旋转
10. transforms.RandomVertcalFlip 随机垂直翻转

常用的数据扩增库

torchvision
https://github.com/pytorch/vision
pytorch官方提供的数据扩增库,提供了基本的数据数据扩增方法,可以无缝与torch进行集成;但数据扩增方法种
类较少,且速度中等;
imgaug
https://github.com/aleju/imgaug
imgaug是常用的第三方数据扩增库,提供了多样的数据扩增方法,且组合起来非常方便,速度较快;
albumentations
https:/albumentations.readthedocs.io
是常用的第三方数据扩增库,提供了多样的数据扩增方法,对图像分类,语义分割、物体检测和关键点检测都支
持,速度较快。

在Pytorch中数据是通过Dataset进行封装, 并通过DataLoderi进行并行读取。 所以我们只需要重载一 下数据读取的逻辑就可以完成数据的读取。
Dataset封装:

class SVHNDataset(Dataset):
    def __init__(self, img_path, img_label, transform=None):
        self.img_path = img_path
        self.img_label = img_label 
        if transform is not None:
            self.transform = transform
        else:
            self.transform = None

    def __getitem__(self, index):
        img = Image.open(self.img_path[index]).convert('RGB')

        if self.transform is not None:
            img = self.transform(img)
        #标签构建 
        lbl = np.array(self.img_label[index], dtype=np.int)
        lbl = list(lbl)  + (5 - len(lbl)) * [10]#当原标签list(lbl)长度小于5的时候,此时通过拼接数字10达到长度为5目的
        return img, torch.from_numpy(np.array(lbl[:5]))#返回图像和标签

    def __len__(self):
        return len(self.img_path)#返回所有图片个数

通过上述代码,可以将赛题的图像数据和对应标签进行读取,在读取过程中的进行数据扩增,效果如下所示:
在这里插入图片描述
接下来我们将在定义好的Dataset基础上构建DataLoder,你可以会问有了Dataset为什么还要有DataLoder?其实这两个是两个不同的概念,是为了实现不同的功能。

Dataset:对数据集的封装,提供索引方式的对数据样本进行读取
DataLoder:对Dataset进行封装,提供批量读取的迭代读取
加入DataLoder后,数据读取代码改为如下:

train_path = glob.glob('input/train/*.png')
# print(train_path)
train_path.sort()# 返回所有照片路径
# print(train_path)
train_json = json.load(open('input/train.json'))#以字典方式全部返回
# print(train_json)
train_label = [train_json[x]['label'] for x in train_json]#提取所有照片的标签以列表形式存储,与照片顺序对应
# print(train_label)
print(len(train_path), len(train_label))

# train_loader[0]是数据,[1]标签,一共十张
train_loader = torch.utils.data.DataLoader(
    SVHNDataset(train_path, train_label,
                transforms.Compose([
                    #缩放到固定尺寸
                    transforms.Resize((64, 128)),
                    #随机区域裁剪
                    transforms.RandomCrop((60, 120)),
                    #对图像颜色的对比度、饱和度和零度进行变换
                    transforms.ColorJitter(0.3, 0.3, 0.2),
                    #随机旋转
                    transforms.RandomRotation(10),
                    #将图片转换为pytorch的tesntor
                    transforms.ToTensor(),
                    #对图像像素进行归一化
                    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])), 
    batch_size=10,#每个批次个数
    shuffle=True,#是否打乱顺序 
    num_workers=0,#读取线程个数
)

val_path = glob.glob('input/val/*.png')
val_path.sort()
val_json = json.load(open('input/val.json'))
val_label = [val_json[x]['label'] for x in val_json]
print(len(val_path), len(val_label))

val_loader = torch.utils.data.DataLoader(
    SVHNDataset(val_path, val_label,
                transforms.Compose([
                    transforms.Resize((60, 120)),
                    # 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=0,
)
#先看一下这个train_loadertorch.utils.data.DataLoaderf()的方法和属性
print(dir(train_loader))
#输出显示'__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__'等还有好多
#发现有__iter__魔术方法但是没有__next__()说明这个train_loader是一个可迭代的,并不是迭代器,采用iter()方法进行读取
a= iter(train_loader)#train_loader类中只定义了一个__inter__函数是一个可迭代的interable,所以用iter转成迭代器
data= next(a)#调用迭代器的next方法
print("data类型",type(data))
print(data[0].shape)
print('--------------------------------')
print(data[1])

输出结果:

30000 30000
10000 10000
['_DataLoader__initialized', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'batch_sampler', 'batch_size', 'collate_fn', 'dataset', 'drop_last', 'num_workers', 'pin_memory', 'sampler', 'timeout', 'worker_init_fn']
data类型 <class 'list'>
torch.Size([10, 3, 60, 120])
--------------------------------
tensor([[ 3, 10, 10, 10, 10],
        [ 2,  5, 10, 10, 10],
        [ 4,  4,  5, 10, 10],
        [ 1, 10, 10, 10, 10],
        [ 4, 10, 10, 10, 10],
        [ 2,  9, 10, 10, 10],
        [ 3,  2, 10, 10, 10],
        [ 4, 10, 10, 10, 10],
        [ 1,  9, 10, 10, 10],
        [ 6,  6, 10, 10, 10]], dtype=torch.int32)

在加入DataLoder后,数据按照批次获取,每批次调用Dataset读取单个样本进行拼接。此时data的格式为:
torch.Size([10, 3, 64, 128]), torch.Size([10, 5])
前者为图像文件,为batchsize * chanel * height * width次序;后者为字符标签

以上这节对数据读取进行了详细的讲解,并介绍了常见的数据扩增方法和使用,最后使用Pytorch框架对本次赛题的数据进行读取。

3 字符识别模型

CNN介绍
卷积神经网络(简称CNN)是一类特殊的人工神经网络,是深度学习中重要的一个分支。CNN在很多领域都表现优异,精度和速度比传统计算学习算法高很多。特别是在计算机视觉领域,CNN是解决图像分类、图像检索、物体检测和语义分割的主流模型。

CNN每一层由众多的卷积核组成,每个卷积核对输入的像素进行卷积操作,得到下一次的输入。随着网络层的增加卷积核会逐渐扩大感受野,并缩减图像的尺寸。
在这里插入图片描述
CNN是一种层次模型,输入的是原始的像素数据。CNN通过卷积(convolution)、池化(pooling)、非线性激活函数(non-linear activation function)和全连接层(fully connected layer)构成。
如下图所示为LeNet网络结构,是非常经典的字符识别模型。两个卷积层,两个池化层,两个全连接层组成。卷积核都是5×5,stride=1,池化层使用最大池化。
在这里插入图片描述
通过多次卷积和池化,CNN的最后一层将输入的图像像素映射为具体的输出。如在分类任务中会转换为不同类别的概率输出,然后计算真实标签与CNN模型的预测结果的差异,并通过反向传播更新每层的参数,并在更新完成后再次前向传播,如此反复直到训练完成 。

与传统机器学习模型相比,CNN具有一种端到端(End to End)的思路。在CNN训练的过程中是直接从图像像素到最终的输出,并不涉及到具体的特征提取和构建模型的过程,也不需要人工的参与。
CNN发展
随着网络结构的发展,研究人员最初发现网络模型结构越深、网络参数越多模型的精度更优。比较典型的是AlexNet、VGG、InceptionV3和ResNet的发展脉络。在这里插入图片描述

LeNet-5(1998)
在这里插入图片描述
AlexNet(2012)
在这里插入图片描述
VGG-16(2014)
在这里插入图片描述
Inception-v1 (2014)
在这里插入图片描述
ResNet-50 (2015)
在这里插入图片描述
Pytorch构建CNN模型
在上一章节我们讲解了如何使用Pytorch来读取赛题数据集,本节我们使用本章学习到的知识构件一个简单的CNN模型,完成字符识别功能。
在Pytorch中构建CNN模型非常简单,只需要定义好模型的参数和正向传播即可,Pytorch会根据正向传播自动计算反向传播。

在本章我们会构建一个非常简单的CNN,然后进行训练。这个CNN模型包括两个卷积层,最后并联6个全连接层进行分类。

import torch
torch.manual_seed(0)
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = True

import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data.dataset import Dataset

# 定义模型
class SVHN_Model1(nn.Module):
    def __init__(self):
        super(SVHN_Model1, self).__init__()
        # CNN提取特征模块
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(),  
            nn.MaxPool2d(2),
            nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(), 
            nn.MaxPool2d(2),
        )
        # 
        self.fc1 = nn.Linear(32*3*7, 11)
        self.fc2 = nn.Linear(32*3*7, 11)
        self.fc3 = nn.Linear(32*3*7, 11)
        self.fc4 = nn.Linear(32*3*7, 11)
        self.fc5 = nn.Linear(32*3*7, 11)
        self.fc6 = nn.Linear(32*3*7, 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
    
model = SVHN_Model1()

接下来是训练代码:

# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.Adam(model.parameters(), 0.005)

loss_plot, c0_plot = [], []
# 迭代10个Epoch
for epoch in range(10):
    for data in train_loader:
        c0, c1, c2, c3, c4, c5 = model(data[0])
        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
        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])
        
    print(epoch)

在训练完成后我们可以将训练过程中的损失和准确率进行绘制,如下图所示。从图中可以看出模型的损失在迭代过程中逐渐减小,字符预测的准确率逐渐升高。
在这里插入图片描述
当然为了追求精度,也可以使用在ImageNet数据集上的预训练模型,具体方法如下:

class SVHN_Model2(nn.Module):
    def __init__(self):
        super(SVHN_Model1, self).__init__()
                
        model_conv = models.resnet18(pretrained=True)
        model_conv.avgpool = nn.AdaptiveAvgPool2d(1)
        model_conv = nn.Sequential(*list(model_conv.children())[:-1])
        self.cnn = model_conv
        
        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)
    
    def forward(self, img):        
        feat = self.cnn(img)
        # print(feat.shape)
        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)
        return c1, c2, c3, c4, c5

4 模型训练与验证

深度学习训练流程:
1.在训练集上进行训练、并在验证集上进行验证:
2. 模型可以保存最优的权重、并读取权重:
3. 记录下训练集和验证集的精度,便于调参。

训练集验证集测试集作用:
1.训练集(TrainSet) :模型用于训练和调整模型参数;
2.验证集(Validation Set) :用来验证模型精度和调整模型超参数;
3.测试集(Test Set) :验证模型的泛化能力。

切分验证集方法:
1.留出法(Hold-Out)
直接将训练集划分成两部分,新的训练集和验证集。这种划分方式的优点是最为直接简单;缺点是只得到了一
份验证集,有可能导致模型在验证集上过拟合。留出法应用场景是数据量比较大的情况。
2.交叉验证法(Cross Validation, CV)
将训练集划分成K份,将其中的K- 1份作为训练集,剩余的1份作为验证集,循环K训练。这种划分方式是所有
的训练集都是验证集,最终模型验证精度是K份平均得到。这种方式的优点是验证集精度比较可靠,训练K次
可以得到K个有多样性差异的模型;CV验证的缺点是需要训练K次,不适合数据量很大的情况。
步骤:
将数据集无替换的随机分为k份,k-1份用来训练模型,剩下一份用来模型性能评估。重复k次,得到k个模型和性能评估结果。得到k个性能评估后,取平均求出最终性能评估。即:
第一步:不重复抽样将原始数据随机分为k份。
第二步:每一次挑选其中 1 份作为测试集,剩余k-1份作为训练集用于模型训练。
第三步:重复第二步k次,每个子集都有一次作为测试集,其余子集作为训练集。在每个训练集上训练后得到一个模型,用这个模型在相应测试集上测试,计算并保存模型的评估指标。
第四步:计算k组测试结果的平均值作为模型精度的估计,并作为当前k折交叉验证下模型的性能指标。
优点:分组后取平均减少方差,使得模型对数据划分不敏感。
缺点:k取值需要尝试。
3.自助采样法(BootStrap)
通过有放回的采样方式得到新的训练集和验证集,每次的训练集和验证集都是有区别的。这种划分方式一般适用于数据量较小的情况。

1.构造训练集和验证集;
2.每轮进行训练和验证,并根据最优验证集精度保存模型。

# 构建训练集
train_path = glob.glob('../input/train/*.png')
train_path.sort()
train_json = json.load(open('../input/train.json'))
train_label = [train_json[x]['label'] for x in train_json]
print(len(train_path), len(train_label))

train_loader = torch.utils.data.DataLoader(
    SVHNDataset(train_path, train_label,
                transforms.Compose([
                    transforms.Resize((64, 128)),
                    transforms.RandomCrop((60, 120)),
                    transforms.ColorJitter(0.3, 0.3, 0.2),
                    transforms.RandomRotation(10),
                    transforms.ToTensor(),
                    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])), 
    batch_size=40, 
    shuffle=True, 
    num_workers=10,
)
# 构建验证集
val_path = glob.glob('../input/val/*.png')
val_path.sort()
val_json = json.load(open('../input/val.json'))
val_label = [val_json[x]['label'] for x in val_json]
print(len(val_path), len(val_label))

val_loader = torch.utils.data.DataLoader(
    SVHNDataset(val_path, val_label,
                transforms.Compose([
                    transforms.Resize((60, 120)),
                    # 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=40, 
    shuffle=False, 
    num_workers=10,
)
# 训练与验证
model = SVHN_Model1()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), 0.001)
best_loss = 1000.0

use_cuda = True
if use_cuda:
    model = model.cuda()

for epoch in range(10):
    train_loss = train(train_loader, model, criterion, optimizer, epoch)
    val_loss = validate(val_loader, model, criterion)
    
    val_label = [''.join(map(str, x)) for x in val_loader.dataset.img_label]
    val_predict_label = predict(val_loader, model, 1)
    val_predict_label = np.vstack([
        val_predict_label[:, :11].argmax(1),
        val_predict_label[:, 11:22].argmax(1),
        val_predict_label[:, 22:33].argmax(1),
        val_predict_label[:, 33:44].argmax(1),
        val_predict_label[:, 44:55].argmax(1),
    ]).T
    val_label_pred = []
    for x in val_predict_label:
        val_label_pred.append(''.join(map(str, x[x!=10])))
    
    val_char_acc = np.mean(np.array(val_label_pred) == np.array(val_label))
    
    print('Epoch: {0}, Train loss: {1} \t Val loss: {2}'.format(epoch, train_loss, val_loss))
    print('Val Acc', val_char_acc)
    # 记录下验证集精度
    if val_loss < best_loss:
        best_loss = val_loss
        # print('Find better model in Epoch {0}, saving model.'.format(epoch))
        # 保存模型
        torch.save(model.state_dict(), './model.pt')

模型保存与加载
在Pytorch中模型的保存和加载非常简单,比较常见的做法是保存和加载模型参数:
torch.save(model_object.state_dict(), 'model.pt')
model.load_state_dict(torch.load(' model.pt'))

深度学习原理少但实践性非常强,基本上很多的模型的验证只能通过训练来完成。同时深度学习有众多的网络结构和超参数,因此需要反复尝试。训练深度学习模型需要GPU的硬件支持,也需要较多的训练时间,如何有效的训练深度学习模型逐渐成为了一门学问。
深度学习有众多的训练技巧,比较推荐的阅读链接有:

  • http://lamda.nju.edu.cn/weixs/project/CNNTricks/CNNTricks.html
  • http://karpathy.github.io/2019/04/25/recipe/

本章以深度学习模型的训练和验证为基础,讲解了验证集划分方法、模型训练与验证、模型保存和加载以及模型调参流程。
需要注意的是模型复杂度是相对的,并不一定模型越复杂越好。在有限设备和有限时间下,需要选择能够快速迭代训练的模型。

5 模型集成

本章讲解的知识点包括:集成学习方法、深度学习中的集成学习和结果后处理思路。

学习集成学习方法以及交叉验证情况下的模型集成
学会使用深度学习模型的集成学习

集成学习方法
在机器学习中的集成学习可以在一定程度上提高预测精度,常见的集成学习方法有Stacking、Bagging和Boosting,同时这些集成学习方法与具体验证集划分联系紧密。
由于深度学习模型一般需要较长的训练周期,如果硬件设备不允许建议选取留出法,如果需要追求精度可以使用交叉验证的方法。

下面假设构建了10折交叉验证,训练得到10个CNN模型。
在这里插入图片描述
Dropout
Dropout可以作为训练深度神经网络的一种技巧。在每个训练批次中,通过随机让一部分的节点停止工作。同时在预测的过程中让所有的节点都其作用。
在这里插入图片描述
Dropout经常出现在在先有的CNN网络中,可以有效的缓解模型过拟合的情况,也可以在预测时增加模型的精度。

class SVHN_Model1(nn.Module):
    def __init__(self):
        super(SVHN_Model1, self).__init__()
        # CNN提取特征模块
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 16, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(),
            nn.Dropout(0.25),
            nn.MaxPool2d(2),
            nn.Conv2d(16, 32, kernel_size=(3, 3), stride=(2, 2)),
            nn.ReLU(), 
            nn.Dropout(0.25),
            nn.MaxPool2d(2),
        )
        # 
        self.fc1 = nn.Linear(32*3*7, 11)
        self.fc2 = nn.Linear(32*3*7, 11)
        self.fc3 = nn.Linear(32*3*7, 11)
        self.fc4 = nn.Linear(32*3*7, 11)
        self.fc5 = nn.Linear(32*3*7, 11)
        self.fc6 = nn.Linear(32*3*7, 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

TTA
测试集数据扩增(Test Time Augmentation,简称TTA)也是常用的集成学习技巧,数据扩增不仅可以在训练时候用,而且可以同样在预测时候进行数据扩增,对同一个样本预测三次,然后对三次结果进行平均。
在这里插入图片描述 在这里插入图片描述在这里插入图片描述
代码如下:

def predict(test_loader, model, tta=10):
    model.eval()
    test_pred_tta = None
    
    # TTA 次数
    for _ in range(tta):
        test_pred = []
    
        with torch.no_grad():
            for i, (input, target) in enumerate(test_loader):
                if use_cuda:
                    input = input.cuda()
                
                c0, c1, c2, c3, c4 = model(input)
                target=target.long()
                if use_cuda:
                    output = np.concatenate([
                        c0.data.cpu().numpy(), 
                        c1.data.cpu().numpy(),
                        c2.data.cpu().numpy(), 
                        c3.data.cpu().numpy(),
                        c4.data.cpu().numpy()], axis=1)
                else:
                    output = np.concatenate([
                        c0.data.numpy(), 
                        c1.data.numpy(),
                        c2.data.numpy(), 
                        c3.data.numpy(),
                        c4.data.numpy()], axis=1)
                
                test_pred.append(output)
        
        test_pred = np.vstack(test_pred)
        if test_pred_tta is None:
            test_pred_tta = test_pred
        else:
            test_pred_tta += test_pred
    
    return test_pred_tta

本章的开头已经提到,假设我们训练了10个CNN则可以将多个模型的预测结果进行平均。但是加入只训练了一个CNN模型,如何做模型集成呢?

在论文Snapshot Ensembles中,作者提出使用cyclical learning rate进行训练模型,并保存精度比较好的一些checkopint,最后将多个checkpoint进行模型集成。 在这里插入图片描述
由于在cyclical learning rate中学习率的变化有周期性变大和减少的行为,因此CNN模型很有可能在跳出局部最优进入另一个局部最优。在Snapshot论文中作者通过使用表明,此种方法可以在一定程度上提高模型精度,但需要更长的训练时间。
在这里插入图片描述
集成学习只能在一定程度上提高精度,并需要耗费较大的训练时间,因此建议先使用提高单个模型的精度,再考虑集成学习过程;
具体的集成学习方法需要与验证集划分方法结合,Dropout和TTA在所有场景有可以起作用。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
天池是一个著名的数据科学竞赛平台,而datawhale是一家致力于数据科学教育和社群建设的组织。街景字符编码识别是指通过计算机视觉技术,对街道场景中的字符进行自动识别和分类。 街景字符编码识别是一项重要的研究领域,对于提高交通安全、城市管理和智能驾驶技术都具有重要意义。街道场景中的字符包括道路标志、车牌号码、店铺招牌等。通过对这些字符进行准确的识别,可以辅助交通管理人员进行交通监管、道路规划和交通流量分析。同时,在智能驾驶领域,街景字符编码识别也是一项关键技术,可以帮助自动驾驶系统准确地识别和理解道路上的各种标志和标识,为自动驾驶提供可靠的环境感知能力。 天池和datawhale联合举办街景字符编码识别竞赛,旨在吸引全球数据科学和计算机视觉领域的优秀人才,集思广益,共同推动该领域的研究和发展。通过这个竞赛,参赛选手可以使用各种机器学习和深度学习算法,基于提供的街景字符数据集,设计和训练模型,实现准确的字符编码识别。这个竞赛不仅有助于促进算法研发和技术创新,也为各参赛选手提供了一个学习、交流和展示自己技能的平台。 总之,天池datawhale街景字符编码识别是一个具有挑战性和实际应用需求的竞赛项目,旨在推动计算机视觉和智能交通领域的技术发展,同时也为数据科学爱好者提供了一个学习和展示自己能力的机会。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值