CV深度学习基础

部署运行你感兴趣的模型镜像

CV深度学习基础

CNN卷积神经网络

卷积神经网络

卷积神经网络(Convolutional Neural Networks,CNN),CNN可以有效的降低反馈神经网络(传统神经网络/全连接神经网络)的复杂性,常见的CNN结构有LeNet-5、AlexNet、ZFNet、VGGNetGoogleNet、ResNet**、DenseNet、SENet、Residual Attention NetworksMobileNetShuffleNet等等;从这些结构来讲CNN发展的一个重要方向就是层次的增加,通过这种方式可以利用增加的非线性神经元得出目标函数的近似结构,同时得出更好的特征表达,但是这种方式导致了网络整体复杂性的增加,使网络更加难以优化,很容易模型过拟合/模型退化的情况。CNN的应用主要是在图像分类、目标检测以及图像分割等应用场景应用比较多。

CNN的应用主要是在图像分类、目标检测以及图像分割等应用场景应用比较多。

卷积神经网络-主要层次

数据输入层:Input Layer

卷积计算层:CONV Layer

ReLU激励层:ReLU Incentive Layer

池化层:Pooling Layer

全连接层:FC Layer

备注:Batch Normalization Layer(可能有)

卷积神经网络-Input Layer

和神经网络/机器学习一样,需要对输入的数据需要进行预处理操作,需要进行预处理的主要原因是:

输入数据单位不一样,可能会导致神经网络收敛速度慢,训练时间长数据范围大的输入在模式分类中的作用可能偏大,而数据范围小的作用就有可能偏小

由于神经网络中存在的激活函数是有值域限制的,因此需要将网络训练的目标数据映射到激活函数的值域

S形激活函数在(-4,4)区间以外区域很平缓,区分度太小。例如S形函数f(X),f(100)与f(5)只相差0.0067

常见3种数据预处理方式

去均值

将输入数据的各个维度中心化到0

标准化

将输入数据的各个维度的幅度标准化到同样的范围

PCA/白化

用PCA降维(去掉特征与特征之间的相关性)

白化是在PCA的基础上,对转换后的数据每个特征轴上的幅度进行标准化

建议:对数据特征进行预处理,去均值、归一化、标准化、区间缩放法

除了刚刚介绍的输入数据处理策略之外,图像领域中还经常使用Pillow或者

OpenCV实现更多的图像特征转换处理,主要包括以下几种:

几何变换、颜色处理、图像合成、图像增强

卷积神经网络-卷积理解

卷积计算层:CONV Layer

局部关联:每个神经元看做一个filter/kernal

窗口(receptive field)滑动,filter对局部数据进行计算

相关概念

深度:depth

步长:stride

填充值:zero-padding

CONV过程参考:http://cs231n.github.io/assets/conv- demo/index.html

卷积神经网络-CONV Layer

局部感知: 在进行计算的时候,将图片划分为一个个的区域进行计算/考虑;

参数共享机制:假设每个神经元连接数据窗的权重是固定的

滑动窗口重叠:降低窗口与窗口之间的边缘不平滑的特性。

固定每个神经元的连接权重,可以将神经元看成一个模板(卷积核);也就是每个卷积核只关注一个特性需要计算的权重个数会大大的减少

备注:一组固定的权重和窗口内数据做矩阵内积后求和的过程叫做卷积

卷积神经网络-Activation Function Layer

将卷积层的输出结果做一次非线性的映射, 也就是做一次“激活

常用非线性映射函数

Sigmoid(S形函数)、Tanh(双曲正切,双S形函数)、ReLU、Leaky ReLU、ELU、Maxout

激励层建议

CNN尽量不要使用sigmoid,如果要使用,建议只在全连接层使用

首先使用ReLU,因为迭代速度快,但是有可能效果不佳

如果使用ReLU失效的情况下,考虑使用Leaky ReLu或者Maxout,此时一般情况都可以解决啦

tanh激活函数在某些情况下有比较好的效果,但是应用场景比较少

卷积神经网络-Pooling Layer

在连续的卷积层中间存在的就是池化层,主要功能是:通过逐步减小表征的空间尺寸来减小参数量和网络中的计算,提取重要特征,删除冗余的噪音特征信息;池化层在每个特征图上独立操作。使用池化层可以压缩数据和参数的量,减小过拟合

在池化层中,进行压缩减少特征数量的时候一般采用两种策略:

Max Pooling:最大池化,一般采用该方式

Average Pooling:平均池化

卷积神经网络-FC

类似传统神经网络中的结构,FC层中的神经元连接着之前层次的所有激活输出;换一句话来讲的话,就是两层之间所有神经元都有权重连接;通常情况下,在CNN中,FC层只会在尾部出现

一般的CNN结构依次为:

INPUT

[[CONV -> RELU] * N -> POOL?]*M

[FC -> RELU] * K

FC

import torch
import torch.nn as nn


class Network(nn.Module):
    def __init__(self, n_class=10):
        super(Network, self).__init__()
        self.n_class = 10

        # # padding参数: 填充大小(h,w)这里给定的就是单边的填充,实际的填充需要乘以2, 当步长为1的时候,padding为'same'会自动进行填充,保证输出的feature map和输入大小一致,当padding为'valid'不进行填充
        # self.conv1 = nn.Conv2d(
        #     in_channels=3,  # 输入通道
        #     out_channels=20,  # 输出通道数目,也就是当前卷积操作对应的卷积核数目
        #     kernel_size=(3, 3),  # 卷积核大小(h,w)
        #     stride=(1, 1),  # 窗口滑动步长(h,w)
        #     padding=1
        # )
        # self.act1 = nn.PReLU(num_parameters=20)
        # self.pool1 = nn.MaxPool2d(2, 2)

        self.features = nn.Sequential(
            nn.Conv2d(3, 20, (3, 3), (1, 1), 1),
            nn.PReLU(20),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(20, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            nn.Conv2d(30, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(30, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            nn.Conv2d(30, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(30, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            nn.Conv2d(30, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(30, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            nn.Conv2d(30, 30, (3, 3), (1, 1), 1),
            nn.PReLU(30),
            # nn.MaxPool2d(2, 2)
            # nn.AdaptiveAvgPool2d(output_size=(7, 7))  # 自适应的平均池化,最终输出一定是7*7的feature map
        )
        self.pool = nn.AdaptiveAvgPool2d(output_size=(7, 7))  # 自适应的平均池化,最终输出一定是7*7的feature map

        self.classify = nn.Sequential(
            nn.Linear(1470, 512),
            nn.PReLU(1),
            nn.Linear(512, 128),
            nn.PReLU(1),
            nn.Linear(128, self.n_class)
        )

    def forward(self, x):
        """
        一个普通卷积神经网络的前向执行过程
        假定是一个图像分类的模型
        :param x: [N,C,H,W]
        :return: [N,n_class]表示N个样本,每个样本分别属于n_class个类别的置信度
        """
        # 1. 卷积 + 激活 + 池化 ---> 局部特征提取
        # z = self.conv1(x)
        # z = self.act1(z)
        # z = self.pool1(z)
        # 在当前情况下,x:[N,3,224,224],z:[N,30,7,7]
        # 表示总共有N个图像,每个图像有30个feature map,每个feature map是一个7*7的矩阵
        # ===> 每个图像使用30*7*7个特征进行描述
        z = self.features(x)  # [N,30,7,7]
        print(z.shape)
        z = self.pool(z)
        print(z.shape)

        # 2. 全连接 + 激活 ---> 全局特征提取
        # 结构转换[N,30,7,7] --> [N, 30*7*7]
        z = z.view(z.shape[0], -1)
        return self.classify(z)


if __name__ == '__main__':
    # img的shape为: [N,C,H,W]; N表示样本数目,C表示通道数目/Feature Map的数目,H和W表示图像的高度和宽度
    # N==8表示8个图像;C==3表示每个图像有3个通道,比如:R\G\B;H==W==244表示图像的大小就是224*224的
    # img = torch.rand(8, 3, 244, 244)
    # img = torch.rand(8, 3, 256, 256)
    img = torch.rand(8, 3, 56, 56)

    net = Network()
    r = net(img)
    print(r.shape)

卷积神经网络-Batch Normalization Layer

在神经网络的训练过程中,我们一般会将输入样本的特征进行标准化处理,使数据变成均值为0,标准差为1的高斯分布,或者范围在0附近的分布。因为如果数据没有进行该处理的话,由于样本特征分布比较散,可能会导致学习速度慢甚至难以学习。

Batch Normalization Layer(BN Layer)是期望我们的结果是服从高斯分布的,所以对神经元的输出进行一下修正。论文中建议放到卷积层/FC层后,激励层/池化层前,而实际应用的时候有时候会放到激励层后。

BN的训练步骤主要分为以下4步:

  1. 求解当前训练批次数据的均值
  2. 求解当前训练批次数据的方差
  3. 使用求得的均值和方差对该批次的数据做标准化处理,获得0-1分布。
  4. 尺度变换和偏移:使用标准化之后的x乘以γ调整数值大小,再加上β增加偏移后得到输出值y。这个γ是尺度因子,β是平移因子,属于BN的核心精髓,由于标准化后的x基本会被限制在正态分布下,会使得网络的表达能力下降,为了解决这个问题,引入两个模型参数γ、β进行平移变化。

Batch Normalization优点:

梯度传递(计算)更加顺畅,不容易导致神经元饱和(防止梯度消失(梯度弥散)/梯度爆

炸,允许使用饱和性激活函数<eg: sigmoid、tanh等>)

学习率可以设置的大一点,加快训练速度。

对于模型参数的初始化方式和模型参数取值不太敏感,使得网络学习更加稳定,提高模型训练精度。

具有一定的正则化效果,类似Dropout、L1、L2等正则化的效果。

Batch Normalization缺点:

如果网络层次比较深,加BN层的话,可能会导致模型训练速度很慢。

训练批次不能设置太小,一般建议批次大小16以上。

卷积神经网络-Layer Normalization Layer

在Layer Normalization中,是针对不同样本计算当前样本的所有神经元的均值和方差,也就是说在LN中,同层神经元输入拥有相同的均值和方差,不同的输入样本具有不同的均值和方差;而在BN中,同层的不同神经元输入的是不同的均值和方差,而同一个batch中的所有样本拥有相同的方差和均值。

主要优点:不受样本批次大小的影响。在RNN上效果比较明显,但是在CNN上,效果不及BN。

在BN中注重对于每个batch中的数据进行归一化操作,保证数据的一致性,是因为在判别模型中的结果一般取决于数据整体分布情况。但是在图像风格化中,生成的结果主要依赖于某个图像的实例,所以对整个batch做归一化不是特别的适合,比较适合对每个feature map特征图(HW)做归一化操作,能够保证各个图像实例之间的独立

卷积神经网络-Instance Normalization Layer

在BN中注重对于每个batch中的数据进行归一化操作,保证数据的一致性,是因为在判别模型中的结果一般取决于数据整体分布情况。但是在图像风格化中,生成的结果主要依赖于某个图像的实例,所以对整个batch做归一化不是特别的适合,比较适合对每个feature map特征图(HW)做归一化操作,能够保证各个图像实例之间的独立。

优点:不受样本批次大小影响,保证每个feature map的独立性。

卷积神经网络-Group Normalization Layer

主要针对于BN中对于小batchsize效果差的问题,在GN中将channel方向分为不同的group,然后每个group中计算归一化操作,计算(C//G)WH的均值、方差,然后进行归一化操作,这样计算出来的结果和batchsize没有关系,效果比较不错。
在这里插入图片描述

import torch
import torch.nn as nn
import numpy as np


if __name__ == '__main__':
    x = torch.rand(8, 6, 24, 32)

    bn2 = nn.BatchNorm2d(num_features=6, momentum=1.0)
    x1 = bn2(x)
    print(x1.shape)
    print(bn2.running_mean.shape)
    print(bn2.running_var.shape)

    x_mean = torch.mean(x, dim=(0, 2, 3), keepdim=True)
    x_std = torch.std(x, dim=(0, 2, 3), keepdim=True)
    x4  = (x - x_mean) / x_std
    print(x_mean.shape)
    print(x_std.shape)
    print(x4.shape)
    print(torch.abs(x1 - x4).max())
    print(bn2.running_mean - x_mean.view(-1))

    # 基于1d实现:BN
    bn1 = nn.BatchNorm1d(num_features=6)
    # 改变维度
    x_ = torch.transpose(x, 3, 1)
    print(x_.shape)
    x_ = torch.permute(x, (0, 2, 3, 1))
    print(x_.shape)
    x2 = bn1(x.reshape(-1, 6))
    print(x2)
    print(x2.shape)
    print(bn1.running_mean.shape)
    print(bn1.running_var.shape)

    # ln
    ln = nn.LayerNorm(normalized_shape=[6, 24, 32], eps=1e-16, elementwise_affine=False)
    x3 = ln(x)
    print('**' * 100)
    print(x3.shape)

    x_mean =  torch.mean(x, dim=(1, 2, 3), keepdim=True)
    x_std = torch.std(x, dim=(1, 2, 3), keepdim=True)
    x4 = (x - x_mean) / x_std
    print(x_mean.shape)
    print(x_std.shape)
    print(x4.shape)
    print(torch.abs(x3 - x4).max())

    # IN
    IN = nn.InstanceNorm2d(num_features=6, eps=1e-16)
    x3 = IN(x)
    # print('**' * 100)
    # print(x3.shape)

    IN2 = nn.LayerNorm(normalized_shape=[24, 32])
    x4 = IN2(x)
    print('**' * 100)
    print(x.shape)
    print(torch.abs(x3 - x4).max())

    x_mean = torch.mean(x, dim=(2, 3), keepdim=True)
    x_std = torch.std(x, dim=(2, 3), keepdim=True)
    x4 = (x - x_mean) / x_std
    print(x_mean.shape)
    print(x4.shape)
    print(torch.abs(x3 - x4).shape)

    # GN
    n, c, h, w = x.shape
    g = 2
    gn_x = x.reshape(n, g, c // g, h, w)
    print(gn_x.shape)

    x_mean = torch.mean(gn_x, dim=(2, 3, 4), keepdim=True)
    x_std = torch.std(gn_x, dim=(2, 3, 4), keepdim=True)
    gn_x1 = (gn_x - x_mean) / x_std
    gn_x1 = gn_x1.reshape(n, c, h, w)
    print(x_mean.shape)
    print(gn_x1.shape)

    gn1 = nn.LayerNorm(normalized_shape=[3, 24, 32], eps=1e-16)
    gn_x2 = gn1(gn_x)
    gn_x2 = gn_x2.reshape(n, c, h, w)
    print(gn_x2.reshape)
    print(torch.abs(gn_x2 - gn_x1).max())

卷积神经网络-Switchable Normalization Layer

Switchabel Normalization是针对于BN、LN、IN、GN是完全人工设计,并且没法通用,每个归一化层的设计需要涉及大量的实验,工作量比较大,SN的就是为了提出一个自适配归一化方法,会自动的为神经网络中的每个归一化层确定一个合适的归一化操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

卷积神经网络优缺点

优点

局部感知的共享卷积核(共享参数),对高维数据的处理没有压力

无需选择特征属性,只要训练好权重,即可得到特征值

深层次的网络抽取图像信息比较丰富,表达效果好

缺点

需要调参,需要大量样本,训练迭代次数比较多,最好使用GPU训练

物理含义不明确,从每层输出中很难看出含义来

卷积神经网络-参数初始化

在卷积神经网络中,可以看到神经元之间的连接是通过权重w以及偏置b实现的。并且w和b的取值会直接影响模

型的训练速度以及训练精度。

权重的初始化

建议方式:很小的随机数(对于多层深度神经网络,太小的值会导致回传的梯度非常小)服从均值为0,方差比较小(建议:2/n, n为权重数量,https://arxiv.org/pdf/1502.01852.pdf)的高斯分布随机数列。

Xavier:服从均匀分布的随机数列(http://proceedings.mlr.press/v9/glorot10a/glorot10a.pdf)。

错误方式:全部初始化为0,全部设置为0,在反向传播的时候是一样的梯度值,那么这个网络的权重是没有办法差异化的,也就没有办法学习到东西。

NOTE: Weight Standarization(权重w标准化)类似批归一化,也是对权重系数做一个标准化的操作,让模型效果会稍微好一点,在卷积/FC操作之前,对w做一个标准化操作。在卷积操作中,以每个卷积核为单位计算均值μ、标准差σ;在FC操作,

以当前层次的所有的权重为单位计算均值μ、标准差σ。论文:https://arxiv.org/pdf/1903.10520.pdf偏置项的初始化

一般直接设置为0,在存在ReLU激活函数的网络中,也可以考虑设置为一个很小的数字(大于0)

import torch
import torch.nn as nn
import torch.nn.init as init

if __name__ == '__main__':
    # lr = nn.Linear(in_features=6, out_features=10)  # 输出4个特征,输出10个特征
    # init.constant_(lr.bias, 0.0)
    # lr.weight.data.normal_(0.0, 0.1) # 将权重初始化为均值为0、标准差为0.1的正态分布。
    # lr.weight.data.add_(1.0) # 将所有权重的值加1
    # print(lr.weight)
    # print(lr.bias)

    conv = nn.Conv2d(in_channels=6, out_channels=10, kernel_size=(3, 3), stride=(1, 1), padding=1)
    print(conv.weight.shape)
    print(conv.bias.shape)
    print(init.xavier_normal_(conv.weight)) # 使用Xavier正态分布对卷积层的权重进行初始化。
    print(init.xavier_uniform_(conv.weight)) # 使用Xavier均匀分布对卷积层的权重进行初始化。
import torch
import torch.nn as nn
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import numpy as np
import matplotlib.pyplot as plt
import os


if __name__ == '__main__':
    # 定义一个数据集对象
    dataset = datasets.MNIST(
        root='../datas/MNIST',
        train=True,
        transform=transforms.ToTensor(), # 定义数据集转换方式,默认是numpy
        download=True
    )
    print(type(dataset))
    # 定义一个批次数据提取器
    data_loader = DataLoader(dataset, batch_size=4)
    print(type(data_loader))
    # 便利循环
    k = 0
    for batch_img, bacth_label in data_loader:
        print(batch_img.shape)
        print(bacth_label.shape)
        n, c, h, w = batch_img.shape
        for i in range(n):
            img = batch_img[i].detach().numpy()    # [c, h, w]
            gray_img = (img[0] * 256).astype(np.uint8)  # [h, w],转为灰度图
            label = bacth_label[i].item()
            output_path = f'../datas/MNIST/MNIST/images/{label}/{k}.png'
            k += 1
            if not os.path.exists(os.path.dirname(output_path)):
                os.makedirs(os.path.dirname(output_path))
            plt.imsave(output_path, gray_img, cmap='gray')


卷积神经网络过拟合

卷积神经网络正则化和Dropout

神经网络的学习能力受神经元数目以及神经网络层次的影响,神经元数目越大,神经网络层次越高,那么神经网络的学习能力越强,那么就有可能出现过拟合的问题;(通俗来讲:神经网络的空间表达能力变得更紧丰富了)

Regularization:正则化,通过降低模型的复杂度,通过在cost函数上添加一个正则项的方式来降低overfitting,主要有L1L2两种方式

Dropout:通过随机删除神经网络中的神经元来解决overfitting问题,在每次迭代的时候,只使用部分神经元训练模型获取W和d的值,参考:《Dropout: ASimple Way to Prevent Neural Networks from Overfitting》

http://www.cs.toronto.edu/~rsalakhu/papers/srivastava14a.pdf;预测的时候使用所有的神经元进行预测

一般情况下,对于同一组训练数据,利用不同的神经网络训练之后,求其输出的平均值可以减少overfitting。Dropout就是利用这个原理,每次丢掉一半左右的隐藏层神经元,相当于在不同的神经网络上进行训练,这样就减少了神经元之间的依赖性,即每个神经元不能依赖于某几个其它的神经元(指层与层之间相连接的神经元),使神经网络更加能学习到与其它神经元之间的更加健壮robust(鲁棒性)的特征。另外Dropout不仅减少overfitting,还能提高准确率

import torch
import torch.nn as nn

def t1():
    p = 0.5
    x = torch.rand(20)
    d1 = nn.Dropout(p=0.5)
    print('=' * 100)
    print(x)
    print(x / (1 - p))
    d1.train()
    print(d1(x))  # 训练阶段,占比p的重置为0,其他没有被重置的对应特征: x / (1 -p)
    d1.eval()
    print(d1(x))  # 推理阶段

def t2():
    x = torch.rand(1, 10, 2, 2)
    d2 = nn.Dropout2d(p=0.5) # 随机将整个通道的特征重置为0
    d2.train()
    x1 = d2(x)
    d2.eval()
    x2 = d2(x)
    print(x)
    print('=' * 100)
    print(x1)
    print('=' * 100)
    print(x2)
    print('=' * 100)
    d = nn.Dropout(p=0.5)
    d.train()
    print(d(x))

class DropBlock(nn.Module):
    # NOTE: 针对输入的[N,C,H,W]数据,在feature钟随机丢弃 m * n大小的块
    pass

if __name__ == '__main__':
    t2()

在这里插入图片描述

正则化

正则化是通过给cost函数添加正则项的方式来解决overfitting,Dropout是通过直接修改神经网络的结构来解决overfitting

import torch
import torch.nn as nn
import torch.optim as optim


class NetWork(nn.Module):
    def __init__(self):
        super(NetWork, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(19, 128),
            nn.ReLU(),
            nn.Linear(128, 256),
            nn.ReLU(),
            nn.Linear(256, 3)
        )

    def forwaard(self, x):
        return self.model(x)

class RegL1Loss(nn.Module):
    def __init__(self, lam, params):
        super(RegL1Loss, self).__init__()
        self.lam = lam
        self.params = list(params)
        self.n_parm = len(self.params)

    def forward(self):
        ls = 0.0
        for param in self.params:
            ls += torch.mean(torch.abs(param))
        return self.lam * ls / self.n_parm



if __name__ == '__main__':

    net = NetWork()
    loss_fn = nn.MSELoss()

    reg_l1_loss_fnn = RegL1Loss(lam=0.1, params=net.parameters())


    # _loss = loss_fn(None, None) # 基于预测值和实际值
    _l1_loss = reg_l1_loss_fnn() # 求解惩罚项损失
    print(_l1_loss)
    # _loss = _loss + _l1_loss

    opt = optim.SGD(params=net.parameters(), lr=0.005, weight_decay=0.5) #l2惩罚项系数

一般都可以使用Dropout解决过拟合问题

回归算法中使用L2范数相比于Softmax分类器,更加难以优化

对于回归问题,首先考虑是否可以转化为分类问题,比如:用户对于商品的评分,可以考虑将得分结果分成1~5分,这样就变成了一个分类问题。

如果实在没法转化为分类问题的,那么使用L2范数的时候要非常小心,比如在L2范数之前不要使用Dropout。

一般建议使用L2范数或者Dropout来减少神经网络的过拟合。

卷积神经网络训练算法

和一般的机器学习算法一样,需要先定义Loss Function,衡量预测值和实际值之间的误差,一般使用平方和误差损失函数或者交叉熵损失函数

找到最小损失函数的W和b的值,CNN中常使用的是SGD

其实就是一般深度学习中的BP算法;SGD需要计算W和b的偏导,BP算法就是计算偏导用的,BP算法的核心是求导链式法则。

在神经网络中一般采用Mini-batch SGD,主要包括以下四个步骤的循环:

  1. 采样一个batch的数据

  2. 前向计算损失loss

  3. 反向传播计算梯度(一个batch上所有样本的梯度和)

  4. 利用梯度更新权重参

    使用BP算法逐级求解出ΔW和Δd的值

    根据SGD(随机梯度下降),迭代更新W和b

池化层误差反向传播

Maxpool 池化层反向传播,除最大值处继承上层梯度外,其他位置置零

平均池化,我们需要把残差平均分成2*2=4份,传递到前边小区域的4个单元即可。

数据增强(Data augmentation)

增加训练数据,则能够提升算法的准确率,因为这样可以避免过拟合,而避免了过拟合你就可以增大你的网络结构了。当训练数据有限的时候,可以通过一些变换来从已有的训练数据集中生成一些新的数据,来扩大训练数据。数据增强的方法有:

1)水平翻转(旋转):当前业务是否支持翻转。

2)随机裁剪(crop采样):如原始图像大小为256256,随机裁剪出一些图像224224的图像。(AlexNet 训练时,对左上、右上、左下、右下、中间做了5次裁剪,然后翻转,得到一些剪切图片。防止大网络过拟合(under ubstantial overfitting)。)

3)fancy PCA:在训练集像素值的RGB颜色空间进行PCA, 得到RGB空间的3个主方向向量,3个特征值, p1, p2, p3, λ1, λ2, λ3. 对每幅图像的每个像素加上如下的变化:
$$
I_{xy}==[I_{xy}R,I_{xy}G,I_{xy}B]T \
其中:αi是满足均\
[p_1,p_2,p_3][\alpha_1\lambda_1,\alpha_2\lambda_2,\alpha_3\lambda_3]^T

$$
4)样本不均衡(解决方案:增加小众类别的图像数据):样本不均衡即有些类别图像特别多,有些特别少。类别不平衡数据的处理:Label shuffle。

5)其他:平移变换;旋转/仿射变换;高斯噪声、模糊处理对颜色的数据增强:图像亮度、饱和度、对比度变化。

6)训练和测试要协调:在训练的时候,我们通常都需要做数据增强,在测试的时候,我们通常很少去做数据增强。这其中似乎有些不协调,因为你训练和测试之间有些不一致。实验发现,训练的最后几个迭代,移除数据增强,和传统一样测试,可以提升一点性能。

如果训练的时候一直使用尺度和长宽比增强数据增强,在测试的时候也同样做这个变化,随机取32个裁剪图片来测试,也可以在最后的模型上提升一点性能。

总结:也就是多尺度的训练,多尺度的测试。

深度学习超参数

1)学习率(Learning Rate)

学习率被定义为每次迭代中成本函数中最小化的量。也即下降到成本函数的最小值的速率是学习率,它是可变的。从梯度下降算法的角度来说,通过选择合适的学习率,可以使梯度下降法得到更好的性能。

一般常用的学习率有0.00001,0.0001,0.001,0.003,0.01,0.03,0.1,0.3,1,3,10

2)学习率调整策略

  1. fixed固定策略:学习率始终是一个固定值。
  2. step均匀分步策略:如果设置为step,则还需要设置一个stepsize, 返回 base_lr * gamma (floor(iter /stepsize))其中iter表示当前的迭代次数。floor(9.9)=9, 其功能是“下取整”。
  3. 分步策略:base_lr * gamma iter , iter为当前迭代次数, gamma为小于1的值。
  4. multistep多分步或不均匀分步策略:刚开始训练网络时学习率一般设置较高,这样loss和accuracy下降很快,一般前200000次两者下降较快,后面可能就需要我们使用较小的学习率了。step策略由于过于平均,而loss和accuracy的下降率在整个训练过程中又是一个不平均的过程,因此有时不是很合适。fixed手工调节起来又很麻烦,这时multistep可能就会派上用场了。multistep还需要设置一个stepvalue。这个参数和step很相似,step是均匀等间隔变化,而multistep则是根据 stepvalue值变化
  5. poly指数下降策略: 学习率进行指数下降, 返回 base_lr *(1 - iter/max_iter) power
optim.lr_scheduler.CyclicLR

Boston房价预测项目

import os
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import torch.nn as nn
import torch
import torch.optim as optim
import numpy as np
from torch.utils.data import Dataset, DataLoader
from sklearn import datasets
from torch.utils.tensorboard import SummaryWriter



class Network(nn.Module):
    def __init__(self):
        super(Network, self).__init__()
        # 第一层全连接
        # self.w1 = nn.Parameter(torch.empty(13, 6))
        # self.b1 = nn.Parameter(torch.empty(6))
        # self.linear1 = nn.Linear(in_features=13, out_features=6)

        # 第二层全连接
        # self.w2 = nn.Parameter(torch.empty(6, 3))
        # self.b2 = nn.Parameter(torch.empty(3))
        # self.linear2 = nn.Linear(in_features=6, out_features=3)

        # 第三层输出层
        # self.w3 = nn.Parameter(torch.empty(3, 1))
        # self.b3 = nn.Parameter(torch.empty(1))
        # self.linear3 = nn.Linear(in_features=3, out_features=1)

        # 参数初始化
        # nn.init.kaiming_uniform_(self.w1)
        # nn.init.kaiming_uniform_(self.w2)
        # nn.init.kaiming_uniform_(self.w3)
        # nn.init.constant_(self.b1, 0.1)
        # nn.init.constant_(self.b2, 0.1)
        # nn.init.constant_(self.b3, 0.1)

        self.module = nn.Sequential(
            nn.Linear(in_features=13, out_features=6),
            nn.Sigmoid(),
            nn.Linear(in_features=6, out_features=3),
            nn.Sigmoid(),
            nn.Linear(in_features=3, out_features=1)
        )

    def forward(self, x):
        """
        定义network前向执行过程, forward方法的入参可以是任意的
        输入的形状是[N, 13]
        输出的形状是[N, 1]
        """
        # 第一层全连接
        # x = torch.matmul(x, self.w1) + self.b1
        # x = self.linear1(x)
        # x = torch.sigmoid(x)

        # 第二层全连接层
        # x = x @ self.w2 + self.b2
        # x = self.linear2(x)
        # x = torch.sigmoid(x)

        # 第三次全连接层
        # x = torch.matmul(x, self.w3) + self.b3
        # x = self.linear3(x)
        # return x

        return self.module(x)

class BostonDataset(Dataset):
    def __init__(self, x, y):
        super(BostonDataset, self).__init__()
        self.x = torch.from_numpy(x)
        self.y = torch.from_numpy(y)

    def __getitem__(self, index):
        # 返回index对应的样本数据
         return self.x[index], self.y[index]

    def __len__(self):
        # 返回当前数据集的样本条数
        return len(self.x)

def fetch_dataloader(batch_size):
    # 读取数据

    data = pd.read_csv('../datas/boston_housing.data', sep='\s+', header=None)
    # print(type(data))
    # 划分数据集
    X = data.iloc[:, :-1]
    Y = data.iloc[:, -1]
    # print(X.shape)
    # print(Y.shape)
    x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.1, random_state=24)
    x_train = x_train.values.astype('float32')
    x_test = x_test.values.astype('float32')
    y_train = y_train.values.astype('float32').reshape(-1, 1)
    y_test = y_test.values.astype('float32').reshape(-1, 1)

    # 预处理
    x_scale = StandardScaler()
    # y_scale = MinMaxScaler()
    x_train = x_scale.fit_transform(x_train)
    x_test = x_scale.transform(x_test)
    # 区间缩放
    # y_train = y_scale.fit_transform(y_train)
    # y_test = y_scale.transform(y_test)

    print(f"训练数据shape:{x_train.shape} - {y_train.shape}")
    print(f"测试数据shape:{x_test.shape} - {y_test.shape}")

    # 2.构建Dataset对象
    train_dataset = BostonDataset(x=x_train, y=y_train)
    test_dataset = BostonDataset(x=x_test, y=y_test)

    # 3.构建数据遍历器
    # 将dataset中的数据一条一条数据拿出来,合并在一起形成一个批次的数据集,并返回
    train_dataloader = DataLoader(
        dataset=train_dataset,  # 给定数据集对象,要求必须又__getitem__方法
        batch_size=batch_size,     # 批次大小
        shuffle=True,  # 从dataset中提取数据是否需要打乱顺序
        num_workers=0,  # 将数据加载形成batch的过程中是否需要多线程,0表示直接在当前主线程中执行
        collate_fn=None,    # 给定如何将n条数据合并成数据批次数据方法返回,默认不需要调整
        prefetch_factor=None,  # 当num_workers为0事,必须为默认值,其他情况给定的是预加载的样本数目,一般情况设置为batch_szie* num_workers
    )
    test_dataloader = DataLoader(
        dataset=test_dataset,
        batch_size=batch_size * 4,
        shuffle=False,
        num_workers=0,
        collate_fn=None
    )

    return train_dataloader, test_dataloader, x_test, y_test

def save_model(path, net, epoch, train_batch, test_batch):
    if not os.path.exists(os.path.dirname(path)):
        os.makedirs(os.path.dirname(path))
    torch.save(net, path)
    torch.save({'net':  net,
                "epoch": epoch,
                "train_batch": train_batch,
                "test_batch": test_batch,
              }, path)


def training(restore_path=None):
    # # 读取数据
    # data = pd.read_csv('../datas/boston_housing.data', sep='\s+', header=None)
    # # print(type(data))
    # # 划分数据集
    # X = data.iloc[:, :-1]
    # Y = data.iloc[:, -1]
    # # print(X.shape)
    # # print(Y.shape)
    # x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.1, random_state=24)
    # x_train = x_train.values.astype('float32')
    # x_test = x_test.values.astype('float32')
    # y_train = y_train.values.astype('float32').reshape(-1, 1)
    # y_test = y_test.values.astype('float32').reshape(-1, 1)
    #
    # # 预处理
    # x_scale = StandardScaler()
    # y_scale = MinMaxScaler()
    # x_train = x_scale.fit_transform(x_train)
    # x_test = x_scale.transform(x_test)
    # # 区间缩放
    # y_train = y_scale.fit_transform(y_train)
    # y_test = y_scale.transform(y_test)
    #
    # print(f"训练数据shape:{x_train.shape} - {y_train.shape}")
    # print(f"测试数据shape:{x_test.shape} - {y_test.shape}")

    train_dataloader, test_loader, x_test, y_test = fetch_dataloader(batch_size=16)

    # 网络结构
    net = Network()
    loss_fn = nn.MSELoss()
    opt = optim.SGD(params=net.parameters(), lr=0.01, weight_decay=0.001)

    # 运行状态可视化, 一般卸载网络结构定义之后
    # 借用tensorflow框架来实现可视化
    # 命令行执行 tensorboard --logdir D:\my_program\study_code\output\summary01
    writer = SummaryWriter(log_dir='./output/01/summary01')
    writer.add_graph(net, input_to_model=torch.randn(1, 13))    # 添加执行图

    # 模型构建
    # epoch 将整个数据集从头到尾遍历一次记一次epoch
    # batch: 一起前 + 一次反向 就要一个batch
    # net.train()
    total_epoch = 1000
    # batch_size = 8
    # total_batch = len(x_train) // batch_size
    trian_batch = 0
    test_batch = 0

    # 模型恢复
    if (restore_path is not None) and os.path.exists(restore_path):
        original_net = torch.load(restore_path, map_location='cpu')
        net.load_state_dict(state_dict=net['net'].state_dict()) # 参数恢复
        train_batch = original_net['train_batch']
        test_batch = original_net['test_batch']
        start_epoch = original_net['epoch'] + 1
        total_epoch = total_epoch + start_epoch
    else:
        trian_batch = 0
        test_batch = 0
        start_epoch = 0



    for epoch in range(start_epoch, total_epoch):
        # rnd_indexes = np.random.permutation(len(x_train))
        # for batch in range(total_batch):
        #     # 前向过程
        #     _idxes = rnd_indexes[batch * batch_size : (batch + 1) * batch_size]
        #     _x = torch.from_numpy(x_train[_idxes])
        #     _y = torch.from_numpy(y_train[_idxes])
        net.train()
        train_loss = []
        for _x, _y in train_dataloader:
            _py = net(_x)
            _loss = loss_fn(_py, _y)

            # 反向过程
            opt.zero_grad()     # 梯度清零
            _loss.backward()    # 反向传播求梯度值
            opt.step()  # 参数更新

            trian_batch += 1
            train_loss.append(_loss.item())

            print(f'train epoch:{epoch}, batch:{trian_batch}, loss:{_loss.item():.4f}')
            # print(f'epoch:{epoch}, batch:{batch}, loss:{_loss.item():.4f}')'
            writer.add_scalar('train_batch_loss', _loss.item(), global_step=trian_batch)



        net.eval()
        test_loss = []
        with torch.no_grad():
            for _x, _y in test_loader:
                _pred_y = net(_x)
                _loss = loss_fn(_pred_y, _y)
                test_batch += 1
                print(f'test epoch:{epoch}, batch:{test_batch}, loss:{_loss.item():.4f}')
                writer.add_scalar('test_batch_loss', _loss.item(), global_step=test_batch)
                test_loss.append(_loss.item())
        # 可视化
        writer.add_histogram('w1', net.module[0].weight, global_step=epoch)
        writer.add_histogram('b1', net.module[0].bias, global_step=epoch)
        writer.add_histogram('w3', net.module[4].weight, global_step=epoch)
        writer.add_histogram('b3', net.module[4].bias, global_step=epoch)
        writer.add_scalars('loss', {'train': np.mean(train_loss), 'test': np.mean(test_loss)}, global_step=epoch)

        if epoch % 100 == 0:
            save_model(
                f'./output/01/model/net_{epoch}.pkl',
                net, epoch, trian_batch, test_batch
            )
    # 模型评估
    net.eval() # 模型进入推理预测阶段
    with torch.no_grad(): # 以下状态不在求解梯度值
        predict_test_y = net(torch.from_numpy(x_test))
        predict_test_loss = loss_fn(predict_test_y, torch.from_numpy(y_test))
        print(np.hstack([predict_test_y.detach().numpy(), y_test]))
        print(predict_test_loss.item())


    # TODO:自己加入提前结束训练的逻辑判断
    writer.close()


    # 模型持久化
    # path = f'./output/01/model/net{epoch}.pkl'
    # if not os.path.exists(os.path.dirname(path)):
    #     os.makedirs(os.path.dirname(path))
    # torch.save(net, path)
    # torch.save({'net':net,
    #             "total_epoch": total_epoch,
    #             "lr": 0.01,
    #             "opt": opt}, path)
    save_model(
        f'./output/01/model/net_{epoch}.pkl',
        net, epoch, trian_batch, test_batch
    )
    # obj =  torch.load(path, map_location='cpu')
    # print(obj)




def t1():
    net = Network()
    _x = torch.randn(8, 13)
    _r = net(_x)
    print(_r)

if __name__ == '__main__':

    training(restore_path='D:\my_program\study_code\output\01\model\net_100.pkl')
    # t1()


loss_fn(predict_test_y, torch.from_numpy(y_test))
print(np.hstack([predict_test_y.detach().numpy(), y_test]))
print(predict_test_loss.item())

# TODO:自己加入提前结束训练的逻辑判断
writer.close()


# 模型持久化
# path = f'./output/01/model/net{epoch}.pkl'
# if not os.path.exists(os.path.dirname(path)):
#     os.makedirs(os.path.dirname(path))
# torch.save(net, path)
# torch.save({'net':net,
#             "total_epoch": total_epoch,
#             "lr": 0.01,
#             "opt": opt}, path)
save_model(
    f'./output/01/model/net_{epoch}.pkl',
    net, epoch, trian_batch, test_batch
)
# obj =  torch.load(path, map_location='cpu')
# print(obj)

def t1():
net = Network()
_x = torch.randn(8, 13)
_r = net(_x)
print(_r)

if name == ‘main’:

training(restore_path='D:\my_program\study_code\output\01\model\net_100.pkl')
# t1()

您可能感兴趣的与本文相关的镜像

Yolo-v8.3

Yolo-v8.3

Yolo

YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的Joseph Redmon 和Ali Farhadi 开发。 YOLO 于2015 年推出,因其高速和高精度而广受欢迎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值