AI夏令营——CV(2)

文章详细介绍了神经网络的训练过程,包括反向传播和梯度下降算法,以及在处理图像时全连接网络的局限性和卷积神经网络(CNN)的优势。文中还展示了如何使用PyTorch构建和训练CNN模型,以及利用Albumentations库进行数据增强,以提高模型性能。最后,文章提供了代码示例,展示了一个简单的CNN模型训练流程。
摘要由CSDN通过智能技术生成

直播笔记:CNN原理与进阶技巧

        神经网络是由具有适应性的简单单元组成的广泛并行互连的网络,他的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。

 训练过程

        反向传播(BP, back propagation)

基本思想:

(1)计算每一层的状态和激活值,直到最后一层(即信号是前向传播的);

(2)计算每一层的误差,误差的计算过程是从最后一层向前推进的(即误差是反向传播的);

(3)计算每个神经元连接权重的梯度;

(4)根据梯度下降法则更新参数(目标是误差变小)。

迭代以上步骤,直到满足停止准则(比如相邻两次迭代的误差的差别很小)。

公式详解

所有训练数据的总体代价:

E_{total}=\frac{1}{N}\sum_{i=1}^{N}E_{(i)}

目标是调整权重和偏置使得总体代价(误差)变小,求得总体代价取最小值时对应各个神经元的参数(即权重和偏置项)。 由梯度下降优化算法,有:

\begin{aligned} &{\mathcal{W}}_{\pi ew} =w_{old}-\eta\nabla E \\ &w_{ji}^{r(l)} =w_{ji}^{(l)}-\eta\frac{\partial E}{\partial w_{ji}^{(l)}} \\ &=w_{ji}^{(l)}-\eta\frac{\partial E}{\partial z_{j}^{(l+1)}}\frac{\partial z_{j}^{(l+1)}}{\partial w_{ji}^{(l)}} \\ &=w_{ji}^{(l)}-\eta a_{i}^{(l)}\frac{\partial E}{\partial z_{j}^{(l+1)}} \end{aligned}

参考:深度学习 | 反向传播详解 - 知乎 (zhihu.com)

梯度下降

寻找目标函数最小化。利用梯度信息,通过不断迭代调整参数来寻找合适的目标值。

基本概念

步长(Learning rate):每一步梯度下降时向目标方向前行的长度

损失函数(loss function): 为了评估模型的好坏,通常用损失函数来度量拟合的程度。损失函数最小化,意味着拟合程度最好,对应的模型参数即为最优参数。

面临的问题

局部最小值和鞍点。

局部最小值

这是梯度下降法最常遇到的一个问题,当一个函数存在多个局部最小值,很可能梯度下降法只是找到其中的一个局部最小值而停止。

规避局部最小值:可以多次用不同的初始值执行算法,选择损失函数最小的初始值。

鞍点

数学含义是:目标函数在此点的梯度为0,但从该点出发的一个方向存在函数极大值点,而另一个方向是函数的极小值点。

常见梯度下降法

批量梯度下降(Batch Gradient Descent BGD)

上面所介绍的算法其实就是批量梯度下降。需要首先计算所有数据上的损失值,然后再进行梯度下降,具体的操作步骤是:遍历全部数据集算一次损失函数,然后算函数对各个参数的梯度,更新梯度。这种方法每更新一次参数,都要把数据集里的所有样本计算一遍,计算量大,计算速度慢,不支持在线学习。

随机梯度下降(Stochastic Gradient Descent SGD)

不使用全量的样本来计算梯度,而使用单一样本来近似估计梯度,可以极大地减少计算量,提高计算效率。具体的操作步骤是:每次从训练集中随机选择一个样本,计算其对应的损失和梯度,进行参数更新,反复迭代。

这种方式在数据规模比较大时可以减少计算复杂度,从概率意义上来说的单个样本的梯度是对整个数据集合梯度的无偏估计,但是它存在着一定的不确定性,因此收敛速率比批梯度下降得更慢。

小批量梯度下降(Mini-batch Gradient Descent)

为了克服上面两种方法的缺点,采用的一种折中手段:将数据分为若干批次,按批次更新参数,每一批次中的一组数据共同决定了本次梯度的方向,下降起来就不容易跑偏,减少了随机性,另一方面,因为批的样本数比整个数据集少了很多,计算量也不是很大。

参考:什么是梯度下降 - 知乎 (zhihu.com)

CNN讲解

全连接神经网络处理大尺寸图像具有三个明显的缺点:

        (1)首先将图像展开为向量会丢失空间信息

        (2)其次参数过多效率低下,训练困难

        (3)同时大量的参数也很快会导致网络过拟合

由此推出卷积神经网络。

结构:

卷积神经网络的各层中的神经元是3维排列的:宽度、高度和深度。

卷积是一个二维模板,但是在卷积神经网络中的深度指的是激活数据体的第三个维度,而不是整个网络的深度,整个网络的深度指的是网络的层数。

组成:

输入层

卷积层——核心

卷积层的参数是有一些可学习的滤波器集合构成的。每个滤波器在空间上(宽度和高度)都比较小,但是深度和输入数据一致。

可以被看做是神经元的一个输出。神经元只观察输入数据中的一小部分,并且和空间上左右两边的所有神经元共享参数

降低参数的数量。这个由于卷积具有“权值共享”这样的特性,可以降低参数数量,达到降低计算开销,防止由于参数过多而造成过拟合

感受野

ReLU层

池化(Pooling)层

全连接层

参考:卷积神经网络(CNN)详解 (zhihu.com)

   CNN赛题中的应用:三维卷积

代码解读

# step1 自定义数据集
import os, sys, glob, argparse
import pandas as pd
import numpy as np
from tqdm import tqdm

import cv2
from PIL import Image
from sklearn.model_selection import train_test_split, StratifiedKFold, KFold

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

import nibabel as nib
from nibabel.viewers import OrthoSlicer3D

train_path = glob.glob('./data/Train/*/*')
test_path = glob.glob('./data/Test/*')

np.random.shuffle(train_path)
np.random.shuffle(test_path)

DATA_CACHE = {}


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

    def __getitem__(self, index):
        if self.img_path[index] in DATA_CACHE:
            img = DATA_CACHE[self.img_path[index]]
        else:
            img = nib.load(self.img_path[index])
            img = img.dataobj[:, :, :, 0]
            DATA_CACHE[self.img_path[index]] = img

        # 随机选择一些通道
        idx = np.random.choice(range(img.shape[-1]), 50)
        img = img[:, :, idx]
        img = img.astype(np.float32)

        if self.transform is not None:
            img = self.transform(image=img)['image']

        img = img.transpose([2, 0, 1])
        return img, torch.from_numpy(np.array(int('NC' in self.img_path[index])))

    def __len__(self):
        return len(self.img_path)


import albumentations as A

train_loader = torch.utils.data.DataLoader(
    XunFeiDataset(train_path[:-10],
                  A.Compose([
                      A.RandomRotate90(),
                      A.RandomCrop(120, 120),
                      A.HorizontalFlip(p=0.5),
                      A.RandomContrast(p=0.5),
                      A.RandomBrightnessContrast(p=0.5),
                  ])
                  ), batch_size=2, shuffle=True, num_workers=0, pin_memory=False
)

val_loader = torch.utils.data.DataLoader(
    XunFeiDataset(train_path[-10:],
                  A.Compose([
                      A.RandomCrop(120, 120),
                  ])
                  ), batch_size=2, shuffle=False, num_workers=0, pin_memory=False
)

test_loader = torch.utils.data.DataLoader(
    XunFeiDataset(test_path,
                  A.Compose([
                      A.RandomCrop(128, 128),
                      A.HorizontalFlip(p=0.5),
                      A.RandomContrast(p=0.5),
                  ])
                  ), batch_size=2, shuffle=False, num_workers=0, pin_memory=False
)


# step2 自定义CNN模型

class XunFeiNet(nn.Module):
    def __init__(self):
        super(XunFeiNet, self).__init__()

        model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
        model.conv1 = torch.nn.Conv2d(50, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
        model.avgpool = nn.AdaptiveAvgPool2d(1)
        model.fc = nn.Linear(512, 2)
        self.resnet = model

    def forward(self, img):
        out = self.resnet(img)
        return out


model = XunFeiNet()
model = model.to('cuda')
criterion = nn.CrossEntropyLoss().cuda()
optimizer = torch.optim.AdamW(model.parameters(), 0.001)

# step3 模型训练与验证

def train(train_loader, model, criterion, optimizer):
    model.train()
    train_loss = 0.0
    for i, (input, target) in enumerate(train_loader):
        input = input.cuda(non_blocking=True)
        target = target.cuda(non_blocking=True)

        output = model(input)
        loss = criterion(output, target.long())

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if i % 20 == 0:
            print(loss.item())

        train_loss += loss.item()

    return train_loss / len(train_loader)


def validate(val_loader, model, criterion):
    model.eval()
    val_acc = 0.0

    with torch.no_grad():
        for i, (input, target) in enumerate(val_loader):
            input = input.cuda()
            target = target.cuda()

            # compute output
            output = model(input)
            loss = criterion(output, target.long())

            val_acc += (output.argmax(1) == target).sum().item()

    return val_acc / len(val_loader.dataset)


for _ in range(3):
    train_loss = train(train_loader, model, criterion, optimizer)
    val_acc = validate(val_loader, model, criterion)
    train_acc = validate(train_loader, model, criterion)

    print(train_loss, train_acc, val_acc)

# step4 模型预测与提交

def predict(test_loader, model, criterion):
    model.eval()
    val_acc = 0.0

    test_pred = []
    with torch.no_grad():
        for i, (input, target) in enumerate(test_loader):
            input = input.cuda()
            target = target.cuda()

            output = model(input)
            test_pred.append(output.data.cpu().numpy())

    return np.vstack(test_pred)


pred = None
for _ in range(10):
    if pred is None:
        pred = predict(test_loader, model, criterion)
    else:
        pred += predict(test_loader, model, criterion)

submit = pd.DataFrame(
    {
        'uuid': [int(x.split('\\')[-1][:-4]) for x in test_path],
        'label': pred.argmax(1)
    })
submit['label'] = submit['label'].map({1: 'NC', 0: 'MCI'})
submit = submit.sort_values(by='uuid')
submit.to_csv('submit2.csv', index=None)

导包

tqdm库:  显示循环的进度条的库

        两种运行模式

        1、基于迭代对象运行  tqdm(iterator)

        2、手动进行更新

PIL库:Python Imaging Library  图像处理库

        针对python3的版本  pillow

torch库:  基于 Python 的科学计算库

         Tensors (张量)相当于Numpy的多维数组,可以应用到 GPU 上加快计算速度

 Albumentations : 处理图像

        快

VerticalFlip 围绕X轴垂直翻转

HorizontalFlip 围绕Y轴水平翻转

Flip 垂直或水平和垂直翻转

compose()   ----  串联多个图片变换

更多及参考:(82条消息) Pytorch使用albumentations实现数据增强_padifneeded_zhangyuexiang123的博客-CSDN博客

数据增强方法:

        像素级变换:仅更改输入图像,并且将使所有其他目标(蒙版,边界框和关键点)保持不变

                包括:模糊,色彩抖动,图像压缩,高斯噪声,倒置,归一化,随机亮度对比,锐化,色相饱和度值

        空间级变换:同时更改输入图像以及其他目标,例如蒙版,边界框和关键点

深度神经网络需要大量的训练数据才能获得良好的结果并防止过拟合。但是,通常很难获得足够的训练样本,主要难点在于收集和标注数据集的成本过高。此时可以通过图像增强来制作新样本。

部分代码详解

torch.manual_seed(0)     -------- CPU生成随机数的种子。方便下次复现实验结果。

cuDNN是GPU加速库

在使用GPU的时候,PyTorch会默认使用cuDNN加速,但是,在使用 cuDNN 的时候,torch.backends.cudnn.deterministic模式是为False。设置这个 flag 为True,我们就可以在 PyTorch 中对模型里的卷积层进行预先的优化,也就是在每一个卷积层中测试 cuDNN 提供的所有卷积实现算法,然后选择最快的那个。

      torch.backends.cudnn.deterministic 将这个 flag 置为 True 的话,每次返回的卷积算法将是确定的,即默认算法。如果配合上设置 Torch 的随机种子为固定值的话,应该可以保证每次运行网络的时候相同输入的输出是固定的。

 设置torch.backends.cudnn.benchmark = True将会让程序在开始时花费一点额外时间,为整个网络的每个卷积层搜索最适合它的卷积实现算法,进而实现网络的加速。

torch.utils.data.DataLoader()    对数据进行batch的划分,可以快速的迭代数据。

        参数        

                batch_size=2  批训练

                shuffle=True     要不要打乱数据

                num_workers=1    多线程来读数据

                pin_memory=False   是否把把数据存放在锁页内存中

ResNet-18

        pretrained参数   表示是否载入在ImageNet上预训练的模型。 

 torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)

in_channels     输入图像通道数

out_channels   卷积产生的通道数

kernel_size   卷积核尺寸

stride       卷积步长

padding   填充操作

bias     为真,则在输出中添加一个可学习的偏差

nn.AdaptiveAvgPool2d(output_size)     自适应平均池化

nn.Linear(in_features, out_features)    设置网络中的全连接层

总结感悟:

通过直播的讲解以及在众多群友的帮助下,完成了CNN代码在windows本地部署。最后提交的csv文件中得到的结果全是NC,不过最终成绩比一开始的baseline高多了。 关于机器学习和深度学习的代码还是需要进一步的研究和学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值