DL专栏7-CNN调优方法


动手构建第一个卷积神经网络模型

目录

  • 介绍

  • CNN 模型架构中的主要组成部分

    • 卷积层
    • 池化层
    • 全连接层
  • 代码实现

    • 步骤1:导入必要的库
    • 步骤2:下载训练和测试数据集
    • 步骤3:拆分训练集进行训练和验证
    • 步骤4:使用 Dataloader 将数据集加载到内存中
    • 步骤5:定义架构
    • 步骤6:定义损失函数
    • 步骤7:实施训练和验证算法
    • 步骤8:训练和评估阶段
    • 步骤9:测试阶段
    • 步骤 10 使用样本进行测试
  • 结论

介绍

卷积神经网络由 Yann LeCun 和 Yoshua Bengio 在 1995 年引入,后来证明在图像领域显示出非凡的结果。

那么,当应用于图像领域时,它们与普通神经网络相比有何特别之处?

我将用一个简单的例子来解释其中的一个原因。考虑到任务是对手写数字图像进行分类,下面给出了一些来自训练集的样本。

如果你正确观察,你会发现所有数字都出现在相应图像的中心。如果测试图像的类型相似,用这些图像训练一个正常的神经网络模型可能会得到很好的结果。

但是如果测试图像如下所示呢?

这里数字九出现在图像的角落。如果我们使用一个简单的神经网络模型来对这张图片进行分类,我们的模型可能会分类失败。

但是,如果将相同的测试图像提供给 CNN 模型,则它很可能会正确分类。它性能更好的原因是它在图像中寻找空间特征。

对于上述情况本身,即使数字 9 位于帧的左角,经过训练的 CNN 模型也会捕获图像中的特征,并且很可能预测该数字是数字 9。普通的神经网络无法做到这种程度。

现在让我们简要讨论一下 CNN 的主要构建块。

CNN 模型架构中的主要组成部分

这是一个简单的 CNN 模型,用于对图像是否包含猫进行分类。

因此,CNN 的主要组成部分是:

  1. 卷积层
  2. 池化层
  3. 全连接层

卷积层

卷积层帮助我们提取图像中存在的特征。这种提取是在滤波器的帮助下实现的。

请遵守以下操作。

在这里,我们可以看到一个窗口在整个图像上滑动,其中图像表示为网格。

现在让我们看看如何进行卷积运算。

假设输入特征图是我们的图像,卷积滤波器是我们要滑过的窗口。

现在让我们观察卷积运算的实例之一。

当卷积滤波器叠加在图像上时,相应的元素会相乘。然后将相乘的值相加得到一个填充在输出特征图中的值。

这个操作一直持续到我们在输入特征图上滑动窗口,直到填充完输出特征图为止。

池化层

使用池化层的想法是减少特征图的维度。对于下面给出的表示,我们使用了 2*2 最大池化层。每次窗口滑过图像时,我们取窗口内的最大值。

最后,经过最大池操作后,我们可以看到输入的维度即 4 * 4 已经缩小到 2*2。

全连接层

如前所述,该层位于 CNN 模型架构的尾部。全连接层的输入是使用卷积滤波器提取的丰富特征。然后向前传播直到输出层,在那里我们得到输入图像属于不同类别的概率。预测输出是模型预测的概率最高的类别。

代码实现

在这里,我们将Fashion MNIST 作为我们的问题数据集。

该数据集包含 T 恤、裤子、套头衫、连衣裙、外套、凉鞋、衬衫、运动鞋、包和踝靴。任务是在训练模型后将给定图像分类到上述类别中。

我们将在 Google Colab 中实现代码,因为它们会在固定时间段内提供免费 GPU 资源的使用。

如果你不熟悉 Colab 环境和 GPU,请查看此博客 (https://www.analyticsvidhya.com/blog/2021/05/a-complete-hands-on-guide-to-train-your-neural-network-model-on-google-colab-gpu/) 以获得更好的想法。

下面给出的是我们将要构建的 CNN 的架构。

步骤 1:导入必要的库

import os
import torch
import torchvision
import tarfile
from torchvision import transforms
from torch.utils.data import random_split
from torch.utils.data.dataloader import DataLoader
import torch.nn as nn
from torch.nn import functional as F
from itertools import chain

步骤2:下载训练和测试数据集

train_set = torchvision.datasets.FashionMNIST("/usr", download=True, transform=
                                                transforms.Compose([transforms.ToTensor()]))
test_set = torchvision.datasets.FashionMNIST("./data", download=True, train=False, transform=
                                               transforms.Compose([transforms.ToTensor()]))

步骤3 拆分训练集进行训练和验证

train_size = 48000
val_size = 60000 - train_size
train_ds,val_ds = random_split(train_set,[train_size,val_size])

步骤4使用 Dataloader 将数据集加载到内存中

train_dl = DataLoader(train_ds,batch_size=20,shuffle=True)
val_dl = DataLoader(val_ds,batch_size=20,shuffle=True)
classes = train_set.classes

现在让我们可视化加载的数据,

for imgs,labels in train_dl:
  for img in imgs:
    arr_ = np.squeeze(img) 
    plt.show()
    break
  break

步骤5 定义架构

import torch.nn as nn
import torch.nn.functional as F
#define the CNN architecture
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        #convolutional layer-1
        self.conv1 = nn.Conv2d(1,6,5, padding=0)
        #convolutional layer-2
        self.conv2 = nn.Conv2d(6,10,5,padding=0)
        # max pooling layer
        self.pool = nn.MaxPool2d(2, 2)
        # Fully connected layer 1
        self.ff1 = nn.Linear(4*4*10,56)
        # Fully connected layer 2
        self.ff2 = nn.Linear(56,10)
    def forward(self, x):
        # adding sequence of convolutional and max pooling layers
        #input dim-28*28*1
        x = self.conv1(x)
        # After convolution operation, output dim - 24*24*6
        x = self.pool(x)
        # After Max pool operation output dim - 12*12*6
        x = self.conv2(x)
        # After convolution operation  output dim - 8*8*10
        x = self.pool(x)
        # max pool output dim 4*4*10
        x = x.view(-1,4*4*10) # Reshaping the values to a shape appropriate to the input of fully connected layer
        x = F.relu(self.ff1(x)) # Applying Relu to the output of first layer
        x = F.sigmoid(self.ff2(x)) # Applying sigmoid to the output of second layer
        return x
# create a complete CNN
model_scratch = Net()
print(model)
# move tensors to GPU if CUDA is available
if use_cuda:
    model_scratch.cuda()

步骤 6 定义损失函数

# Loss function 
import torch.nn as nn
import torch.optim as optim
criterion_scratch = nn.CrossEntropyLoss()
def get_optimizer_scratch(model):
    optimizer = optim.SGD(model.parameters(),lr = 0.04)
    return optimizer

步骤 7 实施训练和验证算法

# Implementing the training algorithm
def train(n_epochs, loaders, model, optimizer, criterion, use_cuda, save_path):
    """returns trained model"""
    # initialize tracker for minimum validation loss
    valid_loss_min = np.Inf 
    for epoch in range(1, n_epochs+1):
        # initialize variables to monitor training and validation loss
        train_loss = 0.0
        valid_loss = 0.0
        # train phase #
        # setting the module to training mode
        model.train()
        for batch_idx, (data, target) in enumerate(loaders['train']):
            # move to GPU
            if use_cuda:
                data, target = data.cuda(), target.cuda()
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()
            train_loss = train_loss + ((1 / (batch_idx + 1)) * (loss.data.item() - train_loss))
        # validate the model #
        # set the model to evaluation mode
        model.eval()
        for batch_idx, (data, target) in enumerate(loaders['valid']):
            # move to GPU
            if use_cuda:
                data, target = data.cuda(), target.cuda()
            output = model(data)
            loss = criterion(output, target)
            valid_loss = valid_loss + ((1 / (batch_idx + 1)) * (loss.data.item() - valid_loss))
# print training/validation statistics 
        print('Epoch: {} tTraining Loss: {:.6f} tValidation Loss: {:.6f}'.format(
            epoch, 
            train_loss,
            valid_loss
            ))
## If the valiation loss has decreased, then saving the model
        if valid_loss <= valid_loss_min:
            print('Validation loss decreased ({:.6f} --> {:.6f}).  Saving model ...'.format(
            valid_loss_min,
            valid_loss))
            torch.save(model.state_dict(), save_path)
            valid_loss_min = valid_loss 
    return model

步骤 8:训练和评估阶段

num_epochs = 15
model_scratch = train(num_epochs, loaders_scratch, model_scratch, get_optimizer_scratch(model_scratch), 
                      criterion_scratch, use_cuda, 'model_scratch.pt')

请注意,每次验证损失减少时,我们都在保存模型的状态。

步骤 9 测试阶段

def test(loaders, model, criterion, use_cuda):
# monitor test loss and accuracy
    test_loss = 0.
    correct = 0.
    total = 0.
# set the module to evaluation mode
    model.eval()
    for batch_idx, (data, target) in enumerate(loaders['test']):
          # move to GPU
          if use_cuda:
            data, target = data.cuda(), target.cuda()
          # forward pass: compute predicted outputs by passing inputs to the model
          output = model(data)
          # calculate the loss
          loss = criterion(output, target)
          # update average test loss 
          test_loss = test_loss + ((1 / (batch_idx + 1)) * (loss.data.item() - test_loss))
          # convert output probabilities to predicted class
          pred = output.data.max(1, keepdim=True)[1]
          # compare predictions to true label
        correct += np.sum(np.squeeze(pred.eq(target.data.view_as(pred)),axis=1).cpu().numpy())
        total += data.size(0)
 print('Test Loss: {:.6f}n'.format(test_loss))
print('nTest Accuracy: %2d%% (%2d/%2d)' % (
        100. * correct / total, correct, total))
# load the model that got the best validation accuracy
model_scratch.load_state_dict(torch.load('model_scratch.pt'))
test(loaders_scratch, model_scratch, criterion_scratch, use_cuda)

步骤 10 使用样本进行测试

为使用单个图像测试模型而定义的函数

def predict_image(img, model):
    # Convert to a batch of 1
    xb = img.unsqueeze(0)
    # Get predictions from model
    yb = model(xb)
    # Pick index with highest probability
    _, preds  = torch.max(yb, dim=1)
    # printing the image
    plt.imshow(img.squeeze( ))
    #returning the class label related to the image
    return train_set.classes[preds[0].item()]


img,label = test_set[9]
predict_image(img,model_scratch)

结论

在这里,我们简要讨论了卷积神经网络中的主要操作及其架构。还实现了一个简单的卷积神经网络模型,以更好地了解实际用例。

代码地址

此外,你可以通过在架构的全连接层中使用正则化技术(例如批量归一化和 dropout)来扩充数据集来提高实现模型的性能。

请记住,也可以使用预先训练的 CNN 模型,这些模型已使用大型数据集进行训练。通过使用这些最先进的模型,你肯定会获得给定问题的最佳度量分数。

针对CNN优化的总结

Systematic evaluation of CNN advances on the ImageNet

  • 使用没有 batchnorm 的 ELU 非线性或者有 batchnorm 的 ReLU。
  • 用类似1*1的网络结构预训练RGB数据,能得到更好的效果。
  • 使用线性学习率衰退策略。
  • 使用平均和最大池化层的和。
  • 使用大约 128(0.005) 到 256 (0.01)的 mini-batch 大小。如果这对你的 GPU 而言太大,将学习率按比例降到这个大小就行。
  • 使用卷积层代替之前的MLP中的线性层,并用平均池化层预测。
  • 当研究增加训练集大小的时候,需要确定数据集对性能提升的平衡点。
  • 数据的质量要比数据大小更重要。
  • 如果你不能增加输入图像的大小,在随后的层上减少步幅(stride),这样做有同样的效果。
  • 如果你的网络有复杂和高度优化的架构,像是 GoogLeNet,那修改一定要谨慎。

有助于充分利用 DNN 的小技巧

  • 记得要 shuffle。不要让你的网络通过完全相同的 minibatch,如果框架允许,在每个 epoch 都 shuffle 一次。

  • 扩展数据集。DNN 需要大量的数据,而且模型在小的数据集上很容易过拟合。我强烈建议你要扩展原始的数据集。如果你的是一个视觉任务,可以增加噪点、增白,减少像素,旋转或色移,模糊,等等可以扩展的一切。有一点不好的是,假如你扩展得太大,可能训练的数据大多数是相同的。我创建了一个应用随机变换的层来解决这个问题,这样就不会有相同的样本。若果你用的是语音数据,可以进行移位和失真处理。

  • 在整个数据集上训练之前,先在非常小的子数据集上训练进行过拟合,这样你会知道你的网络可以收敛。这个 tip 来自 Karpathy。

  • 始终使用 dropout 将过拟合的几率最小化。在大小 > 256 (完全连接层或卷积层)之后就应该使用 dropout。关于这一点有一篇很好的论文:Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning [Gal Yarin & Zoubin Ghahramani,2015].

  • 避免 LRN 池化,MAX 池化会更快。

  • 避免 Sigmoid/TanH 的门,它们代价昂贵,容易饱和,而且可能会停止反向传播。实际上,你的网络越深,就越应该避免使用 Sigmoid 和 TanH。可以使用更便宜而且更有效的 ReLU 和 PreLU 的门,正如在 Yoshua Bengio 等人的论文 Deep Sparse Rectifier Neural Networks 中所提到的,这两者能够促进稀疏性,而且它们的反向传播更加鲁棒。

  • 在最大池化之前不要使用 ReLU 或 PreLU ,而是在保存计算之后使用它。

  • 不要使用 ReLU ,它们太旧了。虽然他们是非常有用的非线性函数,可以解决很多问题。但是,你可以试试用它微调一个新模型,由于 ReLU 阻碍反向传播,初始化不好,你没法得到任何微调效果。但是你应该用 PreLU 以及一个非常小的乘数,通常是0.1。使用 PreLU 的话收敛更快,而且不会像 ReLU 那样在初始阶段被卡住。ELU 也很好,但成本高。

  • 经常使用批标准化。参考论文:Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift [Sergey Ioffe & Christian Szegedy,2015]。这会很有效。批标准化允许更快的收敛(非常快)以及更小的数据集。这样你能够节省时间和资源。

  • 虽然大多数人喜欢删除平均值,不过我不喜欢。我喜欢压缩输入数据为[-1,+1]。这可以说是训练和部署方面的技巧,而不是针对提升性能的技巧。

  • 要能适用更小的模型。假如你是像我这样部署深度学习模型,你很快就会体会到把千兆字节规模的模型推给用户或地球另一端的服务器的痛苦。哪怕要牺牲一些准确度,也应该小型化。

  • 假如你使用比较小的模型,可以试试 ensemble。通常 ensemble 5个网络能够提升准确度约3%。

  • 尽可能使用 xavier 初始化。你可以只在大的完全连接层上使用它,然后避免在 CNN 层上使用。有关这点的解释可以阅读这篇文章:An Explanation of Xavier Initialization(by Andy Jones)

  • 如果你的输入数据有空间参数,可以试试端到端的 CNN。可以阅读这篇论文:SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <0.5MB model size [Forrest N. Iandola et. al. 2016],它介绍了一种新的方法,而且性能非常好,你可以尝试应用上面提到的tips。

  • 修改你的模型,只要可能就使用 1x1 的 CNN 层,它的位置对提高性能很有帮助。

  • 假如没有高端的 GPU,就不要尝试训练任何东西了。

  • 假如你要利用模型或你自己的层来制作模板,记得把所有东西参数化,否则你得重建所有二进制文件。

  • 最后,要明白你在做什么。深度学习就像是机器学习里的中子弹,它不是任何任务、任何时候都有效的。了解你正在使用的结构以及你试图达成的目的,才不至于盲目地复制模型。

  • 初始化十分关键,用对了超参都不用调;没用对,跑出来的结果就跟模型有bug一样不忍直视。卷积核权重初始化方式。对于weight的初始化我一般都是使用xavier初始化。当然也可以可以尝试何凯明大神的He初始化。对于bias的初始化全置于0。

  • Batch Normalization可以很大程度的加快收敛速度。建议搭建自己网络的时候尽量加上BN,如果有BN了全连接层就没必要加Dropout了。

  • 目标检测不能盲目去掉fpn结构。在针对自己的数据调检测任务如yolov3的时候不能盲目砍掉fpn结构,尽管你分析出某个分支的Anchor基本不可能会对你预测的目标起作用,但如果你直接去掉分支很可能会带来漏检。

  • 优化器的选择。我基本都是带动量的SGD。如果优化不动可以试试Adam。激活函数。可以先用ReLU做一版,如果想再提升精度可以将ReLU改成PReLU试试。我更倾向于直接使用ReLU。

  • 初始学习率。一般我是从0.01开始设置,我个人认为这个学习率和学习率衰减策略是相关的,但不宜设置的过大过小,0.01和0.1应该是比较常用的。学习率衰减策略我一般使用multistep方式,step_size的设置要看视你的的max_iter而定。

  • 残差结构和密集连接。resnet的残差结构和dense net密集连接结构,做工程的时候考虑到速度近乎不可能说完全使用完整版本的resnet和densenet的完整结构,但我们可以自己动手将我们网络的某些模块替换为残差结构和密集连接,替换的时候可以适当降低这俩结构的复杂度,类似于通道数减半,密集连接中只保留一半连接等等。这里需要做一些消融实验来验证改进后的精度。

  • 找到模型调参时的可靠评价指标。在调整参数训练模型时一定要找到正确的评价指标,没调整一个参数就要记录一下模型的评价指标如准确率,map值,miou值等。并且在调参时建议将调整的参数和在测试集上的精度组合成一个字符串给模型重命令,方便之后快速review。

  • 使用了带backbone的网络,如训练VGG16-SSD建议选择finetune的方式,从头训练不仅费时费力,甚至难以收敛。

  • 在做分割实验的时候我发现用upsamling 加1*1卷积代替反卷积做上采样得到的结果更平滑,并且miou差距不大,所以我认为这两者都是都可以使用的。

  • 一些Anchor-based目标检测算法为了提高精度,都是疯狂给框,ap值确实上去了,但也导致了fp会很多,并且这部分fp没有回归,在nms阶段也滤不掉。相比于ap提升而言,工程上减少fp更加重要。Gaussian yolov3的fp相比于yolov3会减少40%,Anchor-free算法暂时接触得不多,就不太了解了。

  • 刚开始, 先上小规模数据, 模型往大了放, 只要不爆显存, 能用256个filter你就别用128个. 直接奔着过拟合去. 没错, 就是训练过拟合网络, 连测试集验证集这些都可以不用。(1) 你要验证自己的训练脚本的流程对不对. 这一步小数据量, 生成速度快, 但是所有的脚本都是和未来大规模训练一致的(除了少跑点循环)(2)如果小数据量下, 你这么粗暴的大网络奔着过拟合去都没效果. 那么, 你要开始反思自己了, 模型的输入输出是不是有问题? 要不要检查自己的代码(永远不要怀疑工具库, 除非你动过代码)? 模型解决的问题定义是不是有问题? 你对应用场景的理解是不是有错? 不要怀疑NN的能力, 不要怀疑NN的能力, 不要怀疑NN的能力. 就我们调参狗能遇到的问题, NN没法拟合的, 这概率是有多小?(3)你可以不这么做, 但是等你数据准备了两天, 结果发现有问题要重新生成的时候, 你这周时间就酱油了.

  • 观察loss胜于观察准确率,记得”shuffle, shuffle, shuffle“.Dropout, Dropout, Dropout(不仅仅可以防止过拟合, 其实这相当于做人力成本最低的Ensemble, 当然, 训练起来会比没有Dropout的要慢一点, 同时网络参数你最好相应加一点, 对, 这会再慢一点).

  • 网络原理的理解最重要, CNN的conv这块, 你得明白sobel算子的边界检测.

  • 无脑用ReLU(CV领域). 无脑用3x3. 无脑用xavier. filter数量2^n. 第一层的filter, 数量不要太少. 否则根本学不出来(底层特征很重要).
    sgd adam 这些选择上, 看你个人选择. 一般对网络不是决定性的. 反正我无脑用sgd + momentum.

  • 新手刚上来调参的时候没什么经验,所以一定要低调!啥叫低调?低调就是一开始调的时候千万别考虑太远,在能承受的范围内,尽可能先把滤波器搞得多多的,把数据搞得少少的,直奔过拟合去!所谓小步试错,快速迭代,互联网公司都是这么干的。虽然过拟合也不好搞,但总归是有不少套路的,比起过拟合,欠拟合的问题远远可怕的多。毕竟结果都训练不出来还扯什么远方?

  • 有很多无脑的配置确实可以尝试,比如3x3的卷积核,relu激活函数,加shuffle,加数据增强,加BN,加Dropout等。dropout可以从0. 5往上加,optimizer可以用Adam或者SGD+0.8/0.9的Momentum。大部分时候这些经验比你自己辛苦挑出来的奇技淫巧要有价值的多,但不是绝对的。

提升算法性能思路

分为四个部分:

  1. 通过数据提升性能

  2. 通过算法提升性能

  3. 通过算法调参提升性能

  4. 通过嵌套模型提升性能

通常来讲,随着列表自上而下,性能的提升也将变小。例如,对问题进行新的架构或者获取更多的数据,通常比调整最优算法的参数能带来更好的效果。虽然并不总是这样,但是通常来讲是的。

我已经把相应的链接加入了博客的教程中,相应网站的问题中,以及经典的Neural Net FAQ中。

部分思路只适用于人工神经网络,但是大部分是通用的。通用到足够你用来配合其他技术来碰撞出提升模型性能的方法。

通过数据提升性能

对你的训练数据和问题定义进行适当改变,你能得到很大的性能提升。或许是最大的性能提升。

  1. 获取更多数据
  2. 创造更多数据
  3. 重放缩你的数据
  4. 转换你的数据
  5. 特征选取
  6. 重架构你的问题

1) 获取更多数据

你能获取更多训练数据吗?

你的模型的质量通常受到你的训练数据质量的限制。为了得到最好的模型,你首先应该想办法获得最好的数据。你也想尽可能多的获得那些最好的数据。

有更多的数据,深度学习和其他现代的非线性机器学习技术有更全的学习源,能学得更好,深度学习尤为如此。这也是机器学习对大家充满吸引力的很大一个原因(世界到处都是数据)。

更多的数据并不是总是有用,但是确实有帮助。于我而言,如果可以,我会选择获取更多的数据。

可以参见以下相关阅读: Datasets Over Algorithms(www.edge.org/response-detail/26587)

2) 创造更多数据

上一小节说到了有了更多数据,深度学习算法通常会变的更好。有些时候你可能无法合理地获取更多数据,那你可以试试创造更多数据。

  • 如果你的数据是数值型向量,可以随机构造已有向量的修改版本。
  • 如果你的数据是图片,可以随机构造已有图片的修改版本(平移、截取、旋转等)。
  • 如果你的数据是文本,类似的操作……

这通常被称作数据扩增(data augmentation)或者数据生成(data generation)。

你可以利用一个生成模型。你也可以用一些简单的技巧。例如,针对图片数据,你可以通过随机地平移或旋转已有图片获取性能的提升。如果新数据中包含了这种转换,则提升了模型的泛化能力。

这也与增加噪声是相关的,我们习惯称之为增加扰动。它起到了与正则化方法类似的作用,即抑制训练数据的过拟合。

以下是相关阅读:

3) 重缩放(rescale)你的数据

这是一个快速获得性能提升的方法。
当应用神经网络时,一个传统的经验法则是:重缩放(rescale)你的数据至激活函数的边界。

如果你在使用sigmoid激活函数,重缩放你的数据到0和1的区间里。如果你在使用双曲正切(tanh)激活函数,重缩放数据到-1和1的区间里。

这种方法可以被应用到输入数据(x)和输出数据(y)。例如,如果你在输出层使用sigmoid函数去预测二元分类的结果,应当标准化y值,使之成为二元的。如果你在使用softmax函数,你依旧可以通过标准化y值来获益。

这依旧是一个好的经验法则,但是我想更深入一点。我建议你可以参考下述方法来创造一些训练数据的不同的版本:

  • 归一化到0和1的区间。
  • 重放缩到-1和1的区间
  • 标准化(译者注:标准化数据使之成为零均值,单位标准差)

然后对每一种方法,评估你的模型的性能,选取最好的进行使用。如果你改变了你的激活函数,重复这一过程。

在神经网络中,大的数值累积效应(叠加叠乘)并不是好事,除上述方法之外,还有其他的方法来控制你的神经网络中数据的数值大小,譬如归一化激活函数和权重,我们会在以后讨论这些技术。

以下为相关阅读:

4) 数据变换

这里的数据变换与上述的重缩放方法类似,但需要更多工作。
你必须非常熟悉你的数据。通过可视化来考察离群点。

猜测每一列数据的单变量分布。

  • 列数据看起来像偏斜的高斯分布吗?考虑用Box-Cox变换调整偏态。
  • 列数据看起来像指数分布吗?考虑用对数变换。
  • 列数据看起来有一些特征,但是它们被一些明显的东西遮盖了,尝试取平方或者开平方根来转换数据
  • 你能离散化一个特征或者以某种方式组合特征,来更好地突出一些特征吗?

依靠你的直觉,尝试以下方法。

  • 你能利用类似PCA的投影方法来预处理数据吗?
  • 你能综合多维特征至一个单一数值(特征)吗?
  • 你能用一个新的布尔标签去发现问题中存在一些有趣的方面吗?
  • 你能用其他方法探索出目前场景下的其他特殊结构吗?

神经网层擅长特征学习(feature engineering)。它(自己)可以做到这件事。但是如果你能更好的发现问题到网络中的结构,神经网层会学习地更快。你可以对你的数据就不同的转换方式进行抽样调查,或者尝试特定的性质,来看哪些有用,哪些没用。

以下是相关阅读:

5) 特征选择

一般说来,神经网络对不相关的特征是具有鲁棒的(校对注:即不相关的特征不会很大影响神经网络的训练和效果)。它们会用近似于0的权重来弱化那些没有预测能力的特征的贡献。

尽管如此,这些无关的数据特征,在训练周期依旧要耗费大量的资源。所以你能去除数据里的一些特征吗?

有许多特征选择的方法和特征重要性的方法,这些方法能够给你提供思路,哪些特征该保留,哪些特征该剔除。最简单的方式就是对比所有特征和部分特征的效果。
同样的,如果你有时间,我建议在同一个网络中尝试选择不同的视角来看待你的问题,评估它们,来看看分别有怎样的性能。

  • 或许你利用更少的特征就能达到同等甚至更好的性能。而且,这将使模型变得更快!
  • 或许所有的特征选择方法都剔除了同样的特征子集。很好,这些方法在没用的特征上达成了一致。
  • 或许筛选过后的特征子集,能带给特征工程的新思路。

以下是相关阅读:

6) 重新架构你的问题

有时候要试试从你当前定义的问题中跳出来,想想你所收集到的观察值是定义你问题的唯一方式吗?或许存在其他方法。或许其他构建问题的方式能够更好地揭示待学习问题的结构。

我真的很喜欢这个尝试,因为它迫使你打开自己的思路。这确实很难,尤其是当你已经对当前的方法投入了大量的时间和金钱时。

但是咱们这么想想,即使你列出了3-5个可供替代的建构方案,而且最终还是放弃了它们,但这至少说明你对当前的方案更加自信了。

  • 看看能够在一个时间窗(时间周期)内对已有的特征/数据做一个合并。
  • 或许你的分类问题可以成为一个回归问题(有时候是回归到分类)。
  • 或许你的二元输出可以变成softmax输出?
  • 或许你可以转而对子问题进行建模。

仔细思考你的问题,最好在你选定工具之前就考虑用不同方法构建你的问题,因为此时你对解决方案并没有花费太多的投入。除此之外,如果你在某个问题上卡住了,这样一个简单的尝试能释放更多新的想法。

而且,这并不代表你之前的工作白干了,关于这点你可以看看后续的模型嵌套部分。

以下为相关阅读:

通过算法提升性能

机器学习当然是用算法解决问题。

所有的理论和数学都是描绘了应用不同的方法从数据中学习一个决策过程(如果我们这里只讨论预测模型)。

  1. 对算法进行抽样调查
  2. 借鉴已有文献
  3. 重采样方法

1) 对算法进行抽样调查

其实你事先无法知道,针对你的问题哪个算法是最优的。如果你知道,你可能就不需要机器学习了。那有没有什么数据(办法)可以证明你选择的方法是正确的?

让我们来解决这个难题。当从所有可能的问题中平均来看各算法的性能时,没有哪个算法能够永远胜过其他算法。所有的算法都是平等的,下面是在no free lunch theorem中的一个总结。

或许你选择的算法不是针对你的问题最优的那个

我们不是在尝试解决所有问题,算法世界中有很多新热的方法,可是它们可能并不是针对你数据集的最优算法。

我的建议是收集(证据)数据指标。接受更好的算法或许存在这一观点,并且给予其他算法在解决你的问题上“公平竞争”的机会。

抽样调查一系列可行的方法,来看看哪些还不错,哪些不理想。

  • 首先尝试评估一些线性方法,例如逻辑回归(logistic regression)和线性判别分析(linear discriminate analysis)。
  • 评估一些树类模型,例如CART, 随机森林(Random Forest)和Gradient Boosting。
  • 评估一些实例方法,例如支持向量机(SVM)和K-近邻(kNN)。
  • 评估一些其他的神经网络方法,例如LVQ, MLP, CNN, LSTM, hybrids等

选取性能最好的算法,然后通过进一步的调参和数据准备来提升。尤其注意对比一下深度学习和其他常规机器学习方法,对上述结果进行排名,比较他们的优劣。

很多时候你会发现在你的问题上可以不用深度学习,而是使用一些更简单,训练速度更快,甚至是更容易理解的算法。

以下为相关阅读:

2) 借鉴已有文献

方法选择的一个捷径是借鉴已有的文献资料。可能有人已经研究过与你的问题相关的问题,你可以看看他们用的什么方法。

你可以阅读论文,书籍,博客,问答网站,教程,以及任何能在谷歌搜索到的东西。

写下所有的想法,然后用你的方式把他们研究一遍。

这不是复制别人的研究,而是启发你想出新的想法,一些你从没想到但是却有可能带来性能提升的想法。

发表的研究通常都是非常赞的。世界上有非常多聪明的人,写了很多有趣的东西。你应当好好挖掘这个“图书馆”,找到你想要的东西。

以下为相关阅读:

3) 重采样方法

你必须知道你的模型效果如何。你对模型性能的估计可靠吗?

深度学习模型在训练阶段非常缓慢。这通常意味着,我们无法用一些常用的方法,例如k层交叉验证,去估计模型的性能。

  • 或许你在使用一个简单的训练集/测试集分割,这是常规套路。如果是这样,你需要确保这种分割针对你的问题具有代表性。单变量统计和可视化是一个好的开始。
  • 或许你能利用硬件来加速估计的过程。例如,如果你有集群或者AWS云端服务(Amazon Web Services)账号,你可以并行地训练n个模型,然后获取结果的均值和标准差来得到更鲁棒的估计。
  • 或许你可以利用hold-out验证方法来了解模型在训练后的性能(这在早停法(early stopping)中很有用,后面会讲到)。
  • 或许你可以先隐藏一个完全没用过的验证集,等到你已经完成模型选择之后再使用它。

而有时候另外的方式,或许你能够让数据集变得更小,以及使用更强的重采样方法。

  • 有些情况下你会发现在训练集的一部分样本上训练得到的模型的性能,和在整个数据集上训练得到的模型的性能有很强的相关性。也许你可以先在小数据集上完成模型选择和参数调优,然后再将最终的方法扩展到全部数据集上。
  • 或许你可以用某些方式限制数据集,只取一部分样本,然后用它进行全部的建模过程。

以下为相关阅读:

通过算法调参提升性能

这通常是工作的关键所在。你经常可以通过抽样调查快速地发现一个或两个性能优秀的算法。但是如果想得到最优的算法可能需要几天,几周,甚至几个月。

为了获得更优的模型,以下是对神经网络算法进行参数调优的几点思路:

  1. 诊断(Diagnostics)
  2. 权重初始化(Weight Initialization)
  3. 学习速率(Learning Rate)
  4. 激活函数
  5. 网络拓扑(Network Topology)
  6. 批次和周期(Batches and Epochs)
  7. 正则化
  8. 优化和损失
  9. 早停法

你可能需要训练一个给定“参数配置”的神经网络模型很多次(3-10次甚至更多),才能得到一个估计性能不错的参数配置。这一点几乎适用于这一节中你能够调参的所有方面。

关于超参数优化请参阅博文:

1) 诊断

如果你能知道为什么你的模型性能不再提高了,你就能获得拥有更好性能的模型。

你的模型是过拟合还是欠拟合?永远牢记这个问题。永远。

模型总是会遇到过拟合或者欠拟合,只是程度不同罢了。一个快速了解模型学习行为的方法是,在每个周期,评估模型在训练集和验证集上的表现,并作出图表。

  • 如果训练集上的模型总是优于验证集上的模型,你可能遇到了过拟合,你可以使用诸如正则化的方法。
  • 如果训练集和验证集上的模型都很差,你可能遇到了欠拟合,你可以提升网络的容量,以及训练更多或者更久。
  • 如果有一个拐点存在,在那之后训练集上的模型开始优于验证集上的模型,你可能需要使用早停法。

经常画一画这些图表,学习它们来了解不同的方法,你能够提升模型的性能。这些图表可能是你能创造的最有价值的(模型状态)诊断信息。

另一个有用的诊断是网络模型判定对和判定错的观察值。

  • 对于难以训练的样本,或许你需要更多的数据。
  • 或许你应该剔除训练集中易于建模的多余的样本。
  • 也许可以尝试对训练集划分不同的区域,在特定区域中用更专长的模型。

以下为相关阅读:

2) 权重初始化

经验法则通常是:用小的随机数进行初始化。

在实践中,这可能依旧效果不错,但是对于你的网络来说是最佳的吗?对于不同的激活函数也有一些启发式的初始化方法,但是在实践应用中并没有太多不同。

固定你的网络,然后尝试多种初始化方式。

记住,权重是你的模型真正的参数,你需要找到他们。有很多组权重都能有不错的性能表现,但我们要尽量找到最好的。

  • 尝试所有不同的初始化方法,考察是否有一种方法在其他情况不变的情况下(效果)更优。
  • 尝试用无监督的方法,例如自动编码(autoencoder),来进行预先学习。
  • 尝试使用一个已经存在的模型,只是针对你的问题重新训练输入层和输出层(迁移学习(transfer learning))

需要提醒的一点是,改变权重初始化方法和激活函数,甚至优化函数/损失函数紧密相关。

以下为相关阅读:

3) 学习率

调整学习率很多时候也是行之有效的时段。

以下是可供探索的一些想法:

  • 实验很大和很小的学习率
  • 格点搜索文献里常见的学习速率值,考察你能学习多深的网络。
  • 尝试随周期递减的学习率
  • 尝试经过固定周期数后按比例减小的学习率。
  • 尝试增加一个动量项(momentum term),然后对学习速率和动量同时进行格点搜索。

越大的网络需要越多的训练,反之亦然。如果你添加了太多的神经元和层数,适当提升你的学习速率。同时学习率需要和训练周期,batch size大小以及优化方法联系在一起考虑。

以下为相关阅读:

4) 激活函数

你或许应该使用修正激活函数(rectifier activation functions)。他们也许能提供更好的性能。

在这之前,最早的激活函数是sigmoid和tanh,之后是softmax, 线性激活函数,或者输出层上的sigmoid函数。我不建议尝试更多的激活函数,除非你知道你自己在干什么。

尝试全部三种激活函数,并且重缩放你的数据以满足激活函数的边界。

显然,你想要为输出的形式选择正确的传递函数,但是可以考虑一下探索不同表示。例如,把在二元分类问题上使用的sigmoid函数切换到回归问题上使用的线性函数,然后后置处理你的输出。这可能需要改变损失函数使之更合适。详情参阅数据转换那一节。

以下为相关阅读:

5) 网络拓扑

网络结构的改变能带来好处。

你需要多少层以及多少个神经元?抱歉没有人知道。不要问这种问题…

那怎么找到适用你的问题的配置呢?去实验吧。

  • 尝试一个隐藏层和许多神经元(广度模型)。
  • 尝试一个深的网络,但是每层只有很少的神经元(深度模型)。
  • 尝试上述两种方法的组合。
  • 借鉴研究问题与你的类似的论文里面的结构。
  • 尝试拓扑模式(扇出(fan out)然后扇入(fan in))和书籍论文里的经验法则(下有链接)

选择总是很困难的。通常说来越大的网络有越强的代表能力,或许你需要它。越多的层数可以提供更强的从数据中学到的抽象特征的能力。或许需要它。

深层的神经网络需要更多的训练,无论是训练周期还是学习率,都应该相应地进行调整。

以下为相关阅读:
这些链接会给你很多启发该尝试哪些事情,至少对我来说是的。

6) Batches和周期

batch size大小会决定最后的梯度,以及更新权重的频度。一个周期(epoch)指的是神经网络看一遍全部训练数据的过程。

你是否已经试验了不同的批次batch size和周期数?
之前,我们已经讨论了学习率,网络大小和周期之间的关系。

在很深的网络结构里你会经常看到:小的batch size配以大的训练周期。

下面这些或许能有助于你的问题,也或许不能。你要在自己的数据上尝试和观察。

  • 尝试选取与训练数据同大小的batch size,但注意一下内存(批次学习(batch learning))
  • 尝试选取1作为batch size(在线学习(online learning))
  • 尝试用格点搜索不同的小的batch size(8,16,32,…)
  • 分别尝试训练少量周期和大量周期。

考虑一个接近无穷的周期值(持续训练),去记录到目前为止能得到的最佳的模型。

一些网络结构对batch size更敏感。我知道多层感知器(Multilayer Perceptrons)通常对batch size是鲁棒的,而LSTM和CNNs比较敏感,但是这只是一个说法(仅供参考)。

以下为相关阅读:

7) 正则化

正则化是一个避免模型在训练集上过拟合的好方法。

神经网络里最新最热的正则化技术是dropout方法,你是否试过?dropout方法在训练阶段随机地跳过一些神经元,驱动这一层其他的神经元去捕捉松弛。简单而有效。你可以从dropout方法开始。

  • 格点搜索不同的丢失比例。
  • 分别在输入,隐藏层和输出层中试验dropout方法
  • dropout方法也有一些拓展,比如你也可以尝试drop connect方法。

也可以尝试其他更传统的神经网络正则化方法,例如:

  • 权重衰减(Weight decay)去惩罚大的权重
  • 激活约束(Activation constraint)去惩罚大的激活值

你也可以试验惩罚不同的方面,或者使用不同种类的惩罚/正则化(L1, L2, 或者二者同时)

以下是相关阅读:

8) 优化和损失

最常见是应用随机梯度下降法(stochastic gradient descent),但是现在有非常多的优化器。你试验过不同的优化(方法)过程吗?
随机梯度下降法是默认的选择。先好好利用它,配以不同的学习率和动量。

许多更高级的优化方法有更多的参数,更复杂,也有更快的收敛速度。好与坏,是不是需要用,取决于你的问题。

为了更好的利用好一个给定的(优化)方法,你真的需要弄明白每个参数的意义,然后针对你的问题通过格点搜索不同的的取值。困难,消耗时间,但是值得。

我发现了一些更新更流行的方法,它们可以收敛的更快,并且针对一个给定网络的容量提供了一个快速了解的方式,例如:

  • ADAM
  • RMSprop

你还可以探索其他优化算法,例如,更传统的(Levenberg-Marquardt)和不那么传统的(genetic algorithms)。其他方法能够为随机梯度下降法和其他类似方法提供好的出发点去改进。

要被优化的损失函数与你要解决的问题高度相关。然而,你通常还是有一些余地(可以做一些微调,例如回归问题中的均方误(MSE)和平均绝对误差(MAE)等),有时候变换损失函数还有可能获得小的性能提升,这取决于你输出数据的规模和使用的激活函数。

以下是相关阅读:

9) Early Stopping/早停法

一旦训练过程中出现(验证集)性能开始下降,你可以停止训练与学习。这可以节省很多时间,而且甚至可以让你使用更详尽的重采样方法来评估你的模型的性能。

早停法是一种用来避免模型在训练数据上的过拟合的正则化方式,它需要你监测模型在训练集以及验证集上每一轮的效果。一旦验证集上的模型性能开始下降,训练就可以停止。

如果某个条件满足(衡量准确率的损失),你还可以设置检查点(Checkpointing)来储存模型,使得模型能够继续学习。检查点使你能够早停而非真正的停止训练,因此在最后,你将有一些模型可供选择。

以下是相关阅读:

通过嵌套模型提升性能

你可以组合多个模型的预测能力。刚才提到了算法调参可以提高最后的性能,调参之后这是下一个可以提升的大领域。

事实上,你可以经常通过组合多个“足够好的”模型来得到优秀的预测能力,而不是通过组合多个高度调参的(脆弱的)模型。

你可以考虑以下三个方面的嵌套方式:

  1. 组合模型
  2. 组合视角
  3. 堆叠(Stacking)

1) 组合模型

有时候我们干脆不做模型选择,而是直接组合它们。

如果你有多个不同的深度学习模型,在你的研究问题上每一个都表现的还不错,你可以通过取它们预测的平均值来进行组合。

模型差异越大,最终效果越好。例如,你可以应用非常不同的网络拓扑或者不同的技术。

如果每个模型都效果不错但是不同的方法/方式,嵌套后的预测能力将更加鲁棒。

每一次你训练网络,你初始化不同的权重,然后它会收敛到不同的最终权重。你可以多次重复这一过程去得到很多网络,然后把这些网络的预测值组合在一起。

它们的预测将会高度相关,但是在那些难以预测的特征上,它会给你一个意外的小提升。

以下是相关阅读:

2) 组合视角

同上述类似,但是从不同视角重构你的问题,训练你的模型。

同样,目标得到的是效果不错但是不同的模型(例如,不相关的预测)。得到不同的模型的方法,你可以依赖我们在数据那一小节中罗列的那些非常不同的放缩和转换方法。

你用来训练模型的转换方法越不同,你构建问题的方式越不同,你的结果被提升的程度就越高。

简单使用预测的均值将会是一个好的开始。

3) stacking/堆叠

你还可以学习如何最佳地组合多个模型的预测。这称作堆叠泛化(stacked generalization),或者简短来说就叫堆叠。

通常上,你使用简单线性回归方法就可以得到比取预测平均更好的结果,像正则化的回归(regularized regression),就会学习如何给不同的预测模型赋权重。基线模型是通过取子模型的预测均值得到的,但是应用学习了权重的模型会提升性能。

其余的可参考资源

别的地方有很多很好的资源,但是几乎没有能将所有想法串联在一起的。如果你想深入研究,我列出了如下资源和相应的博客,你能发现很多有趣的东西。

提高计算机视觉任务的图像质量

当我们开始从事任何基于计算机视觉的任务时,我们所有人都面临的几个问题是,要么是缺乏数据,要么是数据质量的问题。

数据量较少仍然只有两种可能的解决方案,一种是尝试获取更多数据或继续使用不同的增强技术,但是当我们谈论数据质量时,数据质量差异很大,因为在某些限制性的指导原则下,你不会点击所有的图像。用户可以在不同的光照条件、不同的角度和 DPI 下单击图像,因此提出一种适用于所有这些的理想图像增强技术是一种 NP 难题。

因此,有一组方法主要用于增强计算机视觉任务的图像质量,例如对象检测、图像分类、OCR 等。我们将通过示例图像并应用各种增强技术来一一讨论它们.

  1. 二值化/阈值化
  2. 降噪
  3. 纠偏
  4. 重新缩放
  5. 形态学操作
  6. 为了尝试这些操作,我们将使用Python3语言及其两个库, PillowOpenCV

二值化

此技术用于将图像从 RGB 转换为单色(黑白),通常称为阈值处理。该技术主要用于需要白底黑字的 OCR 任务。

OCR 模型在白底黑字的图像上进行训练,以提高准确性,因此对图像进行二值化有助于提高 OCR 模型的质量。二值化图像还有助于节省空间并加快处理速度,因为与其他多通道图像格式相比,它只有一个颜色通道。

OpenCV 库提供了多种类型的二值化技术。

1. 二进制阈值:这是最简单的一个,我们必须定义一个阈值,低于该阈值,所有像素值都被转换为黑色,其余的像素值会转换为白色,从而得到二值化图像,你可以使用以下代码片段来对图像进行二进制阈值处理。

## import dependencies
import cv2
from PIL import Image
import matplotlib.pyplot as plt
## reading image
img = cv2.imread('text_document.jpg',0)
## apply binary thresholding
ret,thresh1 = cv2.threshold(img,170,255,cv2.THRESH_BINARY)
## plot original and binarised image 
titles = ['Original Image', 'Binary Thresholding']
images = [img, thresh1]
for i in range(2):
    plt.figure(figsize=(20,20))
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

上面的代码将产生以下图像:

2. 自适应阈值:与二元阈值法不同,该方法根据像素值的小周围区域来确定其阈值。这种方法也有两种类型:

  • 自适应阈值均值:阈值是平均值附近区域减去固定的Ç
  • 自适应高斯阈值:阈值是邻域值减去常数C的高斯加权总和。

该方法主要用于去除图像中的不同光照条件,因为我们根据其周围区域获得像素值。

## import dependencies

import cv2

from PIL import Image

import matplotlib.pyplot as plt

## reading image

img = cv2.imread('lighting_conditions.jpg', 0)

## apply adaptive thresholding 

## adaptive mean thresholding 

th1 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,

            cv2.THRESH_BINARY,11,2)

## adaptive gaussian thresholding

th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,

            cv2.THRESH_BINARY,11,2)

## plot original and binarised image 

titles = ['Original Image', 'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']

images = [img, th1, th2]

plt.figure(figsize=(20,20))

for i in range(3):

    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)

    plt.title(titles[i])

    plt.xticks([]),plt.yticks([])

上述代码的输出如下:

3. Otsu’s Binrisation:该方法不需要任何阈值参数,因为它会自动确定。此方法通过创建所有像素值的直方图,然后从中计算平均值来确定阈值。

## import dependencies

import cv2

from PIL import Image

import matplotlib.pyplot as plt

## reading image

img = cv2.imread('lighting_conditions.jpg', 0)

## apply Otru's thresholding

ret3,th1 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

## plot original and binarised image 

titles = ["Original Image", "Binary Otsu's Thresholding"]

images = [img, th1]

plt.figure(figsize=(20,20))

for i in range(2):

    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)

    plt.title(titles[i])

    plt.xticks([]),plt.yticks([])

上述代码的输出如下:

降噪

大多数计算机视觉任务失败的最重要因素是噪声。噪声可以是高斯噪声(由于不同的光照条件而产生)和椒盐噪声(稀疏的明暗干扰)。

有时图像对人眼看起来更好,但是当我们将这些图像传递给任何基于计算机视觉的模型时,例如分类和对象检测,结果都不理想,因为我们想要找到的噪声对象失真,并且可能与模型训练的对象不匹配,因此图像中的噪声会降低模型的准确性。

OpenCV 提供了一个名为 fastNIMeansDenoising() 的函数,它可以平滑图像以减少图像噪声。

## import dependencies

import cv2

from PIL import Image

import matplotlib.pyplot as plt

## reading image

img = cv2.imread('noisy_image.jpg')

## apply image denoising

dst = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21)

## plot original and denoised image 

titles = ["Original Image", "Denoised Image"]

images = [img, dst]

plt.figure(figsize=(20,20))

for i in range(2):

    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)

    plt.title(titles[i])

    plt.xticks([]),plt.yticks([])

上面的代码将产生以下图像:

纠偏

纠偏是从图像中去除偏斜(角度不同于 0)的过程。这个问题没有具体的解决方案。你可能会在网络上找到多种解决方案,但是当你在图像上尝试这些解决方案时,这可能对你没有意义。

所以我建议你训练你自己的基于计算机视觉的模型,创建一个对多角度图像进行分类的分类模型是最好的选择。

重新缩放

重新缩放是放大或缩小图像以改变图像分辨率的过程。当你重新缩放图像时,该图像中的不同像素值将被复制以使其更加像素化,并删除像素值以使其缩小。

此方法主要用于图像分类和对象检测,因为你需要将图像重新缩放到模型输入大小。

假设你正在为猫和狗的类型创建 VGG 分类器,你有一些可能大小不同的图像,因此你无法将这些图像按原样传递给模型,因为模型需要固定的图像大小. 在这种情况下,你需要根据模型的输入尺寸缩小或放大图像尺寸。

重新缩放图像的简单方法如下:

## import dependencies
import cv2
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
## reading image
img = Image.open('noisy_image.jpg')
## apply image rescaling and making image 300x300 (downscaling)
dst = img.resize((50,50))
## plot original and downscaled image
titles = ["Original Image", "Rescaled Image"]
images = [np.asarray(img), np.asarray(dst)]
plt.figure(figsize=(20,20))
for i in range(2):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

上面的代码将产生以下图像:

形态学操作

形态学操作是一些用于图像变换的简单操作形式。对于图像变换,输入图像数组与决定操作性质的核相乘。

当你有一些物体的边界看不清楚时,你可以使用形态学操作来扩大它的边界,这将有助于轻松找到物体,同样,如果边界很大,你可以用同样的方法缩小它。这些技术主要用于 ICR,因为它们的文本边界较小,需要放大以便 ICR 模型识别它们。

两种主要的形态学操作如下:

1. 腐蚀:此操作试图腐蚀图像的前景,从而使图像中的白色像素值最小化。腐蚀的程度完全取决于内核的大小以及你应用该内核的迭代次数。内核是一个正方形大小的矩阵,只有 1 和 0 值来生成腐蚀图像。

使用 5×5 矩阵应用于一次迭代的腐蚀如下所示:

## import dependencies
import cv2
from PIL import Image
import matplotlib.pyplot as plt
## reading image
img = cv2.imread('text_document.jpg', 0)
## apply erosion
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)
## plot original and eroded image
titles = ["Original Image", "Eroded Image"]
images = [img, erosion]
plt.figure(figsize=(20,20))
for i in range(2):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

结果图像如下:

2.膨胀:这个操作与腐蚀相反,它试图最大化图像中的白色区域。这也取决于内核大小和迭代次数。

使用 5×5 内核大小进行 1 次迭代的膨胀如下所示:

## import dependencies
import cv2
from PIL import Image
import matplotlib.pyplot as plt
## reading image
img = cv2.imread('text_document.jpg', 0)
## apply dilation
kernel = np.ones((5,5),np.uint8)
dilation = cv2.dilate(img,kernel,iterations = 1)
## plot original and dilated image
titles = ["Original Image", "Dilated Image"]
images = [img, dilation]
plt.figure(figsize=(20,20))
for i in range(2):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

这些操作取决于内核大小和值,主要用于为 OCR 模型准备图像。

结论

这些是一些最重要的技术,你可以使用它们来提高图像质量,从而提高基于计算机视觉的模型的准确性。没有选择算法的正确方法,它完全是基于尝试最适合你的数据的方法。

有时,选择可以是单独的,有时你必须使用这些算法的组合,以使你的数据更适合你的算法。总之,如果你的图像质量太差,并且你希望这些算法在这些图像上工作,你会有些不满,因为这些技术可以改善噪声和杂质较少的图像,但对于非常糟糕的图像,它可能不起作用。


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录