图像分类

本文最后更新于:2 年前

背景介绍

  • LeNet:Yan LeCun 等人于 1998 年第一次将卷积神经网络应用到图像分类任务上[1],在手写数字识别任务上取得了巨大成功。

  • AlexNet:Alex Krizhevsky 等人在 2012 年提出了 AlexNet[2], 并应用在大尺寸图片数据集 ImageNet 上,获得了 2012 年 ImageNet 比赛冠军(ImageNet Large Scale Visual Recognition Challenge,ILSVRC)。

  • VGG:Simonyan 和 Zisserman 于 2014 年提出了 VGG 网络结构[3],是当前最流行的卷积神经网络之一,由于其结构简单、应用性极强而深受广大研究者欢迎。

  • GoogLeNet:Christian Szegedy 等人在 2014 提出了 GoogLeNet[4],并取得了 2014 年 ImageNet 比赛冠军。

  • ResNet:Kaiming He 等人在 2015 年提出了 ResNet[5],通过引入残差模块加深网络层数,在 ImagNet 数据集上的错误率降低到 3.6%,超越了人眼识别水平。ResNet 的设计思想深刻地影响了后来的深度神经网络的设计。

LeNet


图1:LeNet模型网络结构示意图

【提示】:

[!NOTE] Tips
卷积层的输出特征图如何当作全连接层的输入使用呢?

卷积层的输出数据格式是 $[N, C, H, W]$,在输入全连接层的时候,会自动将数据拉平,

也就是对每个样本,自动将其转化为长度为 $K$ 的向量,

其中 $K = C \times H \times W$,一个 mini-batch 的数据维度变成了 $N\times K$ 的二维向量。


手写数字识别

定义网络结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 导入需要的包

import paddle

import numpy as np

from paddle.nn import Conv2D, MaxPool2D, Linear



## 组网

import paddle.nn.functional as F



# 定义 LeNet 网络结构

class LeNet(paddle.nn.Layer):

    def __init__(self, num_classes=1):

        super(LeNet, self).__init__()

        # 创建卷积和池化层
        # 创建第1个卷积层

        self.conv1 = Conv2D(in_channels=1, out_channels=6, kernel_size=5)

        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)

        # 尺寸的逻辑:池化层未改变通道数;当前通道数为6
        # 创建第2个卷积层

        self.conv2 = Conv2D(in_channels=6, out_channels=16, kernel_size=5)

        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)

        # 创建第3个卷积层

        self.conv3 = Conv2D(in_channels=16, out_channels=120, kernel_size=4)

        # 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W]
        # 输入size是[28,28],经过三次卷积和两次池化之后,C*H*W等于120

        self.fc1 = Linear(in_features=120, out_features=64)

        # 创建全连接层,第一个全连接层的输出神经元个数为64, 第二个全连接层输出神经元个数为分类标签的类别数

        self.fc2 = Linear(in_features=64, out_features=num_classes)

    # 网络的前向计算过程

    def forward(self, x):

        x = self.conv1(x)

        # 每个卷积层使用Sigmoid激活函数,后面跟着一个2x2的池化

        x = F.sigmoid(x)

        x = self.max_pool1(x)

        x = F.sigmoid(x)

        x = self.conv2(x)

        x = self.max_pool2(x)

        x = self.conv3(x)

        # 尺寸的逻辑:输入层将数据拉平[B,C,H,W] -> [B,C*H*W]

        x = paddle.reshape(x, [x.shape[0], -1])

        x = self.fc1(x)

        x = F.sigmoid(x)

        x = self.fc2(x)

        return x

查看网络各层形状

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 输入数据形状是 [N, 1, H, W]

# 这里用np.random创建一个随机数组作为输入数据

x = np.random.randn(*[3,1,28,28])

x = x.astype('float32')



# 创建LeNet类的实例,指定模型名称和分类的类别数目

model = LeNet(num_classes=10)

# 通过调用LeNet从基类继承的sublayers()函数,

# 查看LeNet中所包含的子层

print(model.sublayers())

x = paddle.to_tensor(x)

for item in model.sublayers():

    # item是LeNet类中的一个子层

    # 查看经过子层之后的输出数据形状

    try:

        x = item(x)

    except:

        x = paddle.reshape(x, [x.shape[0], -1])

        x = item(x)

    if len(item.parameters())==2:

        # 查看卷积和全连接层的数据和参数的形状,

        # 其中item.parameters()[0]是权重参数w,item.parameters()[1]是偏置参数b

        print(item.full_name(), x.shape, item.parameters()[0].shape, item.parameters()[1].shape)

    else:

        # 池化层没有参数

        print(item.full_name(), x.shape)

数据读取模型训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# -*- coding: utf-8 -*-

# LeNet 识别手写数字

import os

import random

import paddle

import numpy as np

import paddle

from paddle.vision.transforms import ToTensor

from paddle.vision.datasets import MNIST



# 定义训练过程

def train(model, opt, train_loader, valid_loader):

    # 开启0号GPU训练

    use_gpu = True

    paddle.device.set_device('gpu:0'if use_gpu else paddle.device.set_device('cpu')

    print('start training ... ')

    model.train()

    for epoch in range(EPOCH_NUM):

        for batch_id, data in enumerate(train_loader()):

            img = data[0]

            label = data[1

            # 计算模型输出

            logits = model(img)

            # 计算损失函数

            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')

            loss = loss_func(logits, label)

            avg_loss = paddle.mean(loss)



            if batch_id % 2000 == 0:

                print("epoch: {}, batch_id: {}, loss is: {:.4f}".format(epoch, batch_id, float(avg_loss.numpy())))

            avg_loss.backward()

            opt.step()

            opt.clear_grad()



        model.eval()

        accuracies = []

        losses = []

        for batch_id, data in enumerate(valid_loader()):

            img = data[0]

            label = data[1

            # 计算模型输出

            logits = model(img)

            pred = F.softmax(logits)

            # 计算损失函数

            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')

            loss = loss_func(logits, label)

            acc = paddle.metric.accuracy(pred, label)

            accuracies.append(acc.numpy())

            losses.append(loss.numpy())

        print("[validation] accuracy/loss: {:.4f}/{:.4f}".format(np.mean(accuracies), np.mean(losses)))

        model.train()



    # 保存模型参数

    paddle.save(model.state_dict(), 'mnist.pdparams')




# 创建模型

model = LeNet(num_classes=10)

# 设置迭代轮数

EPOCH_NUM = 5

# 设置优化器为Momentum,学习率为0.001

opt = paddle.optimizer.Momentum(learning_rate=0.001, momentum=0.9, parameters=model.parameters())

# 定义数据读取器

train_loader = paddle.io.DataLoader(MNIST(mode='train', transform=ToTensor()), batch_size=10, shuffle=True)

valid_loader = paddle.io.DataLoader(MNIST(mode='test', transform=ToTensor()), batch_size=10)

# 启动训练过程

train(model, opt, train_loader, valid_loader)


ResNet

解决网络层数加深后模型效果没有提升。

基础知识

残差块

实现方式:
image.png|250

一般残差块输出通道数为输入通道数的==四倍==。

ResNet-50

depth = [3,4,6,3]
表示 c2 有 3 个残差块,c 3 有 4 个,c 4 有 6 个,c 5 有 3 个。
image.png|525

飞浆高层 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#从paddle.vis ion.models模块中import残差网络,VGG网络,LeNet网络
from paddle.vision.models import resnet50, v9g16, LeNet
from paddle.vision.datasets import Cifar10
from paddle.optimizer import Momentum
from paddle.regularizer import L2Decay
from paddle.nn import CrossEnt ropyLoss
from paddle.metric import Accuracy
from paddle.vision. transforms import Transpose
#确保从paddle.vis ion.datasets. Cifar10中加载的图像数据是np. ndarray类型
paddle.vision.set_image_backend( 'cv2' )
#调用resnet50模型
model = padd le .Model( resnet50(pretrained=False, num_classes=10} )
#使用Cifar10数据集
train_dataset = Cifar10(mode='train', transform= =Transpose() )
val_dataset = Cifar10 (mode='test', transform=Transpose())
#定义优化器
optimizer = Momentum( learning_rate=0.01, momentum=0.9,
weight_decay = L2Decay(1e-4),
parameters = model. parameters())
#进行训练前准备
model.prepare(optimizer, CrossEntropyLoss(), Accuracy(topk=(1, 5)))
#启动训练
model.fit(train_dataset ,
val_dataset ,
epochs=50,
batch_size=64,
save_dir=*"./output",
num_workers=8 )


图像分类 ResNet 实战:眼疾识别分类

CV 任务研发流程

image.png|425

[!NOTE] Tips
其中,基本的计算机视觉任务研发全流程包含模型训练、模型预测和模型部署三大步骤。每个步骤又包含单独的流程:

  • 数据准备:根据网络接收的数据格式,完成相应的预处理和跑批量数据读取器操作,保证模型正常读取;
  • 模型构建:设计卷积网络结构;
  • 特征提取:使用构建的模型提取数据的特征信息;
  • 损失函数:通过损失函数衡量模型的预测值和真实值的不一致程度,通常损失函数越小,模型性能越好;
  • 模型评估:在模型训练中或训练结束后岁模型进行评估测试,观察准确率;
  • 模型预测:使用训练好的模型进行测试,也需要准备数据和模型特征提取,最后对结果进行解析。

数据预处理


图像分类
https://alleyf.github.io/2023/03/da98629042c9.html
作者
alleyf
发布于
2023年3月17日
更新于
2023年4月14日
许可协议