week10(2021.11.20-2021.11.26)

这周看的关于ResNet(残差网络)的内容

Step1

首先来复习一下CNN的发展历史

1998年LeNet处理手写字体识别CNN领域的果蝇
2012年AlexNet8层神经网络
2014年GoogleNet比赛第一名(应用局限,网络复杂)
2014年VGG比赛第二名 VGG(应用广泛)
2015年ResNet层数非常多,错误率低

        当VGG出现时,大家觉得网络层数越多,最后的效果越好。但发现网络层数越多会导致1.反向传播容易梯度消失2.错误率也变高。ResNet的出现就解决了这个问题。从此开始,神经网络的层数越来越多。

1.LeNet(CNN领域的果蝇,最初应用在MNIST的手写字符识别)

        5个卷积层,3个全连接层

2.AlexNet 

        5个卷积层,3个全连接层    其中用到11×11,7×7,5×5的卷积核

3.VGG(AlexNet的改进) 

        将11×11,7×7,5×5的卷积核分别改称4个3×3,3个3×3,2个3×3,这样显著的减少了参数的应用。但是全连接层中耗费的参数太多

4.ResNet 

        以往的神经网络如左边所示,因此网络层数过多就会导致识别率变低,因此ResNet中应用了右侧的结构,其中右侧的曲线我们有时候会叫作捷径(高速公路),这样就允许原始输入信息直接传输到后面的层当中。捷径跨三层,四层也都是可以的,在实际中还有好多是这样的(如下图就是三层残差学习单元:可以减小参数)

                                                  

        为什么叫残差网络:如下图所示,在线性拟合中的残差说的是数据点距离拟合直线的函数值的差,那么这里我们可以类比,这里的X就是我们的拟合的函数,而H(x)的就是具体的数据点,那么我通过训练使的拟合的值加上F(x)的就得到具体数据点的值,因此这 F(x)的就是残差了

                 

下图的最右边就是残差网络的样子(34层)

                  

 然后就用程序实现Resnet吧

Step2 Pytorch实现Resnet50

参考:用Pytorch手工实现ResNet50 - 知乎 (zhihu.com)

这个是吴恩达的课程作业

        数据集来自这里的 下载1 

        完整代码如下

#该程序必须用gpu运行,否则会报错

import math
import numpy as np
import h5py
import matplotlib.pyplot as plt
import scipy
from PIL import Image
from scipy import ndimage
import torch
import torch.nn as nn
from cnn_utils import *
from torch import nn, optim
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms

np.random.seed(1)
torch.manual_seed(1)
batch_size = 24
learning_rate = 0.009
num_epocher = 100

X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()

# Normalize image vectors
X_train = X_train_orig/255.
X_test = X_test_orig/255.

# Reshape
Y_train = Y_train_orig.T
Y_test = Y_test_orig.T

print ("number of training examples = " + str(X_train.shape[0]))
print ("number of test examples = " + str(X_test.shape[0]))
print ("X_train shape: " + str(X_train.shape))
print ("Y_train shape: " + str(Y_train.shape))
print ("X_test shape: " + str(X_test.shape))
print ("Y_test shape: " + str(Y_test.shape))



class MyData(Dataset):  # 继承Dataset
    def __init__(self, data, y, transform=None):  # __init__是初始化该类的一些基础参数
        self.transform = transform  # 变换
        self.data = data
        self.y = y

    def __len__(self):  # 返回整个数据集的大小
        return len(self.data)

    def __getitem__(self, index):  # 根据索引index返回dataset[index]
        sample = self.data[index]
        if self.transform:
            sample = self.transform(sample)  # 对样本进行变换
        return sample, self.y[index]  # 返回该样本


train_dataset = MyData(X_train, Y_train_orig[0],
                       transform=transforms.ToTensor())
test_dataset = MyData(X_test, Y_test_orig[0],
                      transform=transforms.ToTensor())
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)


class ConvBlock(nn.Module):
    def __init__(self, in_channel, f, filters, s):
        super(ConvBlock, self).__init__()
        F1, F2, F3 = filters
        self.stage = nn.Sequential(
            nn.Conv2d(in_channel, F1, 1, stride=s, padding=0, bias=False),
            nn.BatchNorm2d(F1),
            nn.ReLU(True),
            nn.Conv2d(F1, F2, f, stride=1, padding=True, bias=False),
            nn.BatchNorm2d(F2),
            nn.ReLU(True),
            nn.Conv2d(F2, F3, 1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F3),
        )
        self.shortcut_1 = nn.Conv2d(in_channel, F3, 1, stride=s, padding=0, bias=False)
        self.batch_1 = nn.BatchNorm2d(F3)
        self.relu_1 = nn.ReLU(True)

    def forward(self, X):
        X_shortcut = self.shortcut_1(X)
        X_shortcut = self.batch_1(X_shortcut)
        X = self.stage(X)
        X = X + X_shortcut
        X = self.relu_1(X)
        return X


class IndentityBlock(nn.Module):
    def __init__(self, in_channel, f, filters):
        super(IndentityBlock, self).__init__()
        F1, F2, F3 = filters
        self.stage = nn.Sequential(
            nn.Conv2d(in_channel, F1, 1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F1),
            nn.ReLU(True),
            nn.Conv2d(F1, F2, f, stride=1, padding=True, bias=False),
            nn.BatchNorm2d(F2),
            nn.ReLU(True),
            nn.Conv2d(F2, F3, 1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F3),
        )
        self.relu_1 = nn.ReLU(True)

    def forward(self, X):
        X_shortcut = X
        X = self.stage(X)
        X = X + X_shortcut
        X = self.relu_1(X)
        return X


class ResModel(nn.Module):
    def __init__(self, n_class):
        super(ResModel, self).__init__()
        self.stage1 = nn.Sequential(
            nn.Conv2d(3, 64, 7, stride=2, padding=3, bias=False), #3代表彩图
            nn.BatchNorm2d(64),            #BatchNorm就是在深度神经网络训练过程中使得每一层神经网络的输入保持相同分布的。
            nn.ReLU(True),
            nn.MaxPool2d(3, 2, padding=1),
        )
        self.stage2 = nn.Sequential(
            ConvBlock(64, f=3, filters=[64, 64, 256], s=1),
            IndentityBlock(256, 3, [64, 64, 256]),
            IndentityBlock(256, 3, [64, 64, 256]),
        )
        self.stage3 = nn.Sequential(
            ConvBlock(256, f=3, filters=[128, 128, 512], s=2),
            IndentityBlock(512, 3, [128, 128, 512]),
            IndentityBlock(512, 3, [128, 128, 512]),
            IndentityBlock(512, 3, [128, 128, 512]),
        )
        self.stage4 = nn.Sequential(
            ConvBlock(512, f=3, filters=[256, 256, 1024], s=2),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
        )
        self.stage5 = nn.Sequential(
            ConvBlock(1024, f=3, filters=[512, 512, 2048], s=2),
            IndentityBlock(2048, 3, [512, 512, 2048]),
            IndentityBlock(2048, 3, [512, 512, 2048]),
        )
        self.pool = nn.AvgPool2d(2, 2, padding=1)
        self.fc = nn.Sequential(
            nn.Linear(8192, n_class)       #n_class下边规定是6 它是Resnet中的参数
        )

    def forward(self, X):
        out = self.stage1(X)
        out = self.stage2(out)
        out = self.stage3(out)
        out = self.stage4(out)
        out = self.stage5(out)
        out = self.pool(out)
        out = out.view(out.size(0), 8192)       #转换成全连接层需要的样子 8192可以写成-1么?
        out = self.fc(out)
        return out


device = 'cuda'


def test():
    model.eval()  # 需要说明是否模型测试
    eval_loss = 0
    eval_acc = 0
    for data in test_loader:
        img, label = data
        img = img.float().to(device)
        label = label.long().to(device)
        out = model(img)  # 前向算法
        loss = criterion(out, label)  # 计算loss
        eval_loss += loss.item() * label.size(0)  # total loss
        _, pred = torch.max(out, 1)  # 预测结果
        num_correct = (pred == label).sum()  # 正确结果
        eval_acc += num_correct.item()  # 正确结果总数

    print('Test Loss:{:.6f},Acc: {:.6f}'
          .format(eval_loss / (len(test_dataset)), eval_acc * 1.0 / (len(test_dataset))))


model = ResModel(6)
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.8)

# 开始训练
for epoch in range(num_epocher):      #100
    model.train()                #
    running_loss = 0.0
    running_acc = 0.0
    for i, data in enumerate(train_loader, 1):
        img, label = data
        img = img.float().to(device)
        label = label.long().to(device)
        # 前向传播
        out = model(img)
        loss = criterion(out, label)  # loss
        running_loss += loss.item() * label.size(0)
        _, pred = torch.max(out, 1)  # 预测结果       输出所在行的最大值,最后可以输出一列!
        num_correct = (pred == label).sum()  # 正确结果的数量
        running_acc += num_correct.item()  # 正确结果的总数

        optimizer.zero_grad()  # 梯度清零
        loss.backward()  # 后向传播计算梯度
        optimizer.step()  # 利用梯度更新W,b参数
    # 打印一个循环后,训练集合上的loss和正确率
    if (epoch + 1) % 1 == 0:
        print('Train{} epoch, Loss: {:.6f},Acc: {:.6f}'.format(epoch + 1, running_loss / (len(train_dataset)),
                                                               running_acc / (len(train_dataset))))
        test()

代码实现的功能是这里的 二-残差网络的搭建,共有6种图片,如下图所示

 

分析一下完整版程序,大致分为几个部分

一.数据加载等

        包括训练集1080张64×64的彩色图片,测试集120张64×64的彩色图片

二.神经网络

        1.ConvBlock(in_channel,f,filters,s)

 ConvBlock就是ResNet中包含的一个小的部分,将ConvBlock单独拿出来了

 又叫卷积块,它的主要作用是仅仅应用(学习后的)线性函数来减少输入的维度,以便在后面的 加法步骤中的维度相匹配。适用于维度不同的时候

 ConvBlock对应的代码从上方的完整代码copy一下:

class ConvBlock(nn.Module):
    def __init__(self, in_channel, f, filters, s):
        super(ConvBlock, self).__init__()
        F1, F2, F3 = filters
        self.stage = nn.Sequential(
            nn.Conv2d(in_channel, F1, 1, stride=s, padding=0, bias=False),
            nn.BatchNorm2d(F1),
            nn.ReLU(True),
            nn.Conv2d(F1, F2, f, stride=1, padding=True, bias=False),
            nn.BatchNorm2d(F2),
            nn.ReLU(True),
            nn.Conv2d(F2, F3, 1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F3),
        )
        self.shortcut_1 = nn.Conv2d(in_channel, F3, 1, stride=s, padding=0, bias=False)
        self.batch_1 = nn.BatchNorm2d(F3)
        self.relu_1 = nn.ReLU(True)

    def forward(self, X):
        X_shortcut = self.shortcut_1(X)
        X_shortcut = self.batch_1(X_shortcut)
        X = self.stage(X)
        X = X + X_shortcut
        X = self.relu_1(X)
        return X

画了一个小流程图:(以后就叫做 圈1 了) 

                         

        2.IndentityBlock(in_channel,f,filters)

 IndentityBlock就是ResNet中包含的一个小的部分,将IndentityBlock单独拿出来了

 又叫恒等块,恒等块是残差网络使用的的标准块,适用于输入输出维度相同的时候

 IndentityBlock对应的代码从上方的完整代码copy一下:

class IndentityBlock(nn.Module):
    def __init__(self, in_channel, f, filters):
        super(IndentityBlock, self).__init__()
        F1, F2, F3 = filters
        self.stage = nn.Sequential(
            nn.Conv2d(in_channel, F1, 1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F1),
            nn.ReLU(True),
            nn.Conv2d(F1, F2, f, stride=1, padding=True, bias=False),
            nn.BatchNorm2d(F2),
            nn.ReLU(True),
            nn.Conv2d(F2, F3, 1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F3),
        )
        self.relu_1 = nn.ReLU(True)

    def forward(self, X):
        X_shortcut = X
        X = self.stage(X)
        X = X + X_shortcut
        X = self.relu_1(X)
        return X

画了一个小流程图:(以后就叫做 圈2 了)  

                            

 最后画完流程图发现 二者就是差在捷径那里

        3.ResModel(n_class)

流程图有些过分繁琐,我就用 圈1 圈2 代替了(每个花括号就代表一个stage)

              

如果我画的ResNet看不懂,那也可以看这个:

三.训练+测试

代码中每训练一次就测试一次,结果如下(部分):

 完整的结果如下(后边其实没有太大的差别了)

链接:https://pan.baidu.com/s/1gdNlZG9fMdRUZKE2M5X9SQ 
提取码:430z

注意!这个完整版代码设置的是用GPU运行!

参考文章:

(4条消息) 深度学习 --- 深度残差网络详解ResNet_进击的菜鸟-CSDN博客_残差网络

残差神经网络(ResNet) - 知乎 (zhihu.com)

(4条消息) pytorch中max函数的一点使用记录 max(-1)[1]_开飞机的小毛驴儿-CSDN博客

​​​​​​pytorch中net.eval() 和net.train()的使用 - 简书 

Pytorch——torch.nn.Sequential()详解 - 希望每天涨粉 - 博客园

深度学习中,偏置(bias)在什么情况下可以要,可以不要?_修行之路-CSDN博客

nn.Conv2d(3, 64, 7, stride=2, padding=3, bias=False), - 国内版 Bing

pytorch的函数中的dilation参数的作用 - 慢行厚积 - 博客园

图解卷积层stride,padding,kernel_size 和卷积前后特征图尺寸之间的关系 - 知乎

BatchNorm的原理及代码实现 - 知乎

PyTorch实现ResNet50、ResNet101和ResNet152示例_-互联网文档类资源-CSDN下载

ResNet网络详解及Pytorch代码实现(超详细帮助你掌握ResNet原理及实现)_wuzhuoxi7116的博客-CSDN博客_resnet代码pytorch
 


VGG16和VGG19网络结构图_刀刀鱼-CSDN博客_vgg19网络结构

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值