对应《Deep Learning for Vision System》书中的“Convolutional neural networks”一章

大纲脉络与书中一致,后附上个人编写torch框架下代码。


1、MLP用于图像分类

        当我们预计使用MLP模型来对图像数据进行分析处理时,如分类任务,由于模型结构的原因,我们需要将结构化的图像数据拉伸(flatten)为一维数据,例如将一张28x28的数据(矩阵)拉成784维度的一维数据(向量),随后再将其喂入神经网络,最后得到预测结果。

        此类做法有两大致命缺陷:

        (1)参数量过大由于MLP的结构为全连接,因此其参数量会非常大。

        (2)损坏结构化数据图像数据中形状、结构类特征对图片理解至关重要,拉扯会损失结构化信息。

        相应地,卷积神经网络对应的提出权值共享感受野来解决对应问题。

2、卷积神经网络(CNN)结构

2.1 总览

        回顾机器学习(包括深度学习)的整个架构,其包括:输入数据——预处理——特征提取——解释模型。显然,MLP在图像任务中的特征提取表现较差(事实上MLP作为最后的分类解释模型表现很好)。因此现在需要替换的是流程中的特征提取模块,也即使用CNN来进行特征提取,使用MLP来进行最终的分类。

         特征提取的最终的结果是特征向量,该特征向量可以作为MLP模型的输入,用于最后的模型分类

        特征提取过程中每经过一层则维度较小,但特征图的数量上升,也即维度减小,深度增加。(把每一个特征图理解为一个特征或一个属性,特征越多表示描述的角度和方面就越多,每一个特征图是聚焦某一个特征,因此其数据维度自然会降低)

        特征图:特征图(feature map)是卷积核对图像进行卷积所得到的结果。

2.2 特征提取直观认识

        如上图所示,一张一维的图片通过(四次)卷积操作获得了四个特征图片,也就是特征图(feature map), 所以原始数据从 28x28x1 变成了 20x20x4 (举例)。这20x20x4就是四个特征图(feature map)

2.3 图像分类问题直观认识

        以人脸识别为例,卷积神经网络通过识别局部特征来最终实现人脸识别。

3、卷积网络基本成分——卷积、池化、全连接

3.1 卷积层

        卷积层通常被用于进行特征学习或特征提取。全连接层通常被用于进行分类。

        卷积:卷积层作为一个特征发现窗口、滑过整个图像的像素值,并提取有图像对象种有意义的特征。

        卷积核(convolution filter):卷积核也即CNN网络中的权重, 用来操作图像的算子。其长度通常为方形,随机初始化,并随网络训练而调整(学习)。卷积方式是对应位置相乘相加,最后加上偏差。如下:

        在数字图像处理中,卷积核被称为算子,不同类型的算子能对图像进行不同类型的操作,如:边缘锐化算子、边缘提取算子、图像增强算子等。 如下图:

         因此,可以理解为,每一个卷积核(算子)能得到一个图像的特征,现在网络需要自主通过学习了解如何提取有用的特征,也即通过学习获得卷积核中的参数,不同的卷积核(算子)所提取的特征不同,这也是为什么将提取的结果称为特征图,因为每一个图代表谋一个特征的提取结果。

        感受野:卷积核所操作的区域被称为感受野(receptive field)。

        特征图(feature map):卷积核操作过后的图像被称为特征图。

        卷积中四个超参数卷积核个数(通道数,也即每一层的卷积核的个数)、卷积核尺寸、步长、填充

        (1)卷积核个数。如果在这一层使用N个卷积核,那个下一层的feature map的深度或者通道数也即为N。

        (2)卷积核尺寸。通常由2x2、3x3、5x5。小的卷积核用于提取图像中更加精细的特征,较大的卷积核会忽略细节而提取更大维度的特征。(经验主义而言:卷积核的大小最小2x2最大5x5).

        (3)步长。滑动的时候跳跃的像素个数。

        (4)填充。对卷积后的图像的填充。

        步长和填充的作用:能保证卷积后的图像大小和原始图像的大小一致,方便后续的池化操作计算图像输出大小。

3.2 池化层或降采样

        如上所述,每一个卷积核都会生成一幅feature map,那么在卷积模型中数据量就会出现较快的增长,因此需要对数据进行降采样,以及对图像数据进行压缩,以期降低数据量。

        池化层主要包括两类,最大池化和平均池化。最大池化操作与卷积操作一致,有一个一定大小的“卷积核”对输入图像进行滑动,同时也伴有步长,只不过卷积核中没有参数,且输出为卷积核所对应区域中元素最大的值,这也即最大池化。易知,若池化的卷积核尺寸为2x2,那么最终的数据量将缩减为原来的1/4。

        (全局)平均池化是一个更加极端的池化方式,其计算整个通道(channel)或说整张特征图数字的平均值。如下:

池化层主要用于减少数据的维度降低计算量等

(注:现有研究也表明,省去池化层,仅仅使用卷积层一直对图像数据进行数据提取,也能得到较好的效果。另一个方面,随着硬件存储等的发展,降低数据量的急迫性没有这么大,因此现在由很多研究舍弃池化层)

        卷积被用于在原始数据中提取图像特征,因此在卷积神经网络中,卷积层结构通常是多个卷积模块(包括卷积和池化)的堆叠,用于连续不断的提取图像中的特征,直到提取到足够小的大小,将其拉伸为一个向量,用作最优预测模型(多为MLP-based)的输出。如下:

3.3 全连接层

        全连接层也即多层神经网络,卷积模块所获得的结果传入神经网络中,进行图像分类。

        如上图所示,将5x5x40压缩成1000维度的向量,再将这个向量传入MLP中进行分类。 

4、使用CNN进行图像分类

4.1 组件一个卷积神经网络

        本文给出了一个keras框架下的基于CNN网络的图像分类。

        将模型结输出在屏幕如下:

        卷积: 模型的输入为灰度图像,其维度为(28,28,1)。第一个卷积模块 conv2d_1,其拥有32个卷积核,其输出维度为(28,28,32)。(下同)

        池化:然后经过一个池化层,该池化为尺寸为2的最大池化,无参数,其输出的数据维度(14,14,32),通道数没变,但数据维度标为之前的1/2,总数据变为之前的1/4。(下同)

        全连接:首先用flatten将提取好的特征图拉伸,将(7,7,64)拉伸为向量(3136)       

4.2 参数量分析

        关于卷积中参数量的计算:conv2d_1 层共有32个卷积核,size为3,其处理的图像的通道为1,每个卷积核有一个偏执参数。因此每个卷积核的参数量为:(3x3x1+1)x32=320。

        又如,conv2d_2的参数应为:(3x3x32+1)x64=18496。卷积核的通道数与处理对象特征图的通道数一致,如处理(14,14,32)的图像(特征图),则单个卷积核的形状为(3,3,32)。

        可训练参数与不可训练参数:在后续的计算机视觉中,或者后续的视觉大模型中,经常会使用预训练大模型。预训练大模型是指在大数据集上训练好的模块,如在ImageNet数据集上训练的特征提取模型,我们可以将其视作图像特征提取的工具,这个工具不会参与我们的模型训练,因为他是训练好的,因此在实际使用时将其视为一个特征提取工具冻结其参数。

5、增加dropout以防止过拟合

5.1 什么是过拟合

        欠拟合:模型没能拟合训练数据,通常是模型过于简单。 

        过拟合:对于训练数据拟合太过,记住了训练数据但没有真的学习到特征,通常是建立了一个比较复杂的网络模型(或者数据量太少)。

5.2 什么是dropout层

        dropout层能平衡我们的神经网络,让我们每个神经元节点能被平等的对待,使得预测结果不会过度的依赖于某个特征或某个神经元节点。其工作方式如下:

        在实际过程中,每个神经元都有一定的概率(dropout的超参数)被失活(被out),如上图所示,而每个神经元表示一个特征,以达到训练结果不会过度依赖某一个特征的目的。

        dropout减少了神经元之间的相互依赖,在这种情况下,模型一定程度上可能看作多个若分类模型的组合模型。

5.3 为什么需要dropout层

        防止过拟合!!!

6、使用卷积神经网络对彩色图像进行分类

        彩色图像为三通道图像,因此在数据输入部分需要采用三通道的卷积核。

        使用卷积神经网络对彩色图像进行分类的步骤如下

        (1)加载数据集

        (2)图像预处理:

                rescale:将图像/255转化为0-1之间数值

                准备标签:one-hot编码

                数据集分割

                定义框架

                组建模型:将数据集、模型、优化器等合并

                训练模型

                加载模型(从最优的记录中)

                评估模型

附:个人编写基于torch实现书中示例

所有代码均上传至(数据太大,自行网站下载):

https://github.com/xurongtang/CV_practiceicon-default.png?t=O83Ahttps://github.com/xurongtang/CV_practice

附1、手写数字识别项目

脚本1(dataset_load.py):加载MINIST数据集,参考博客:Fashion MNIST数据集的处理——“...-idx3-ubyte”文件解析_t10k-images-idx3-ubyte-CSDN博客文章浏览阅读8.4k次,点赞6次,收藏39次。MNIST数据集可能是计算机视觉所接触的第一个图片数据集。而 Fashion MNIST 是在遵循 MNIST 的格式和大小的基础上,提升了一定的难度,在比较算法的性能时可以有更好的区分度。Fashion MNIST 数据集包含 60000 张图片的训练集和 10000 张图片的测试集。图片的大小为 28×28,共784个像素。像素的灰度值介于0~255之间的整数。_t10k-images-idx3-ubytehttps://blog.csdn.net/weixin_43276033/article/details/124163762如下:

import torch
import cv2,os
import numpy as np
import struct
import matplotlib.pyplot as plt

def __decode_idx3_ubyte(file):
    """
    解析数据文件
    """
    # 读取二进制数据
    with open(file, 'rb') as fp:
        bin_data = fp.read()
    
    # 解析文件中的头信息
    # 从文件头部依次读取四个32位,分别为:
    # magic,numImgs, numRows, numCols
    # 偏置
    offset = 0
    # 读取格式: 大端
    fmt_header = '>iiii'
    magic, numImgs, numRows, numCols = struct.unpack_from(fmt_header, bin_data, offset)
    # print(magic,numImgs,numRows,numCols)
    
    # 解析图片数据
    # 偏置掉头文件信息
    offset = struct.calcsize(fmt_header)
    # 读取格式
    fmt_image = '>'+str(numImgs*numRows*numCols)+'B'
    data = torch.tensor(struct.unpack_from(fmt_image, bin_data, offset)).reshape(numImgs, numRows, numCols)
    return data

def __decode_idx1_ubyte(file):
    """
    解析标签文件
    """
    # 读取二进制数据
    with open(file, 'rb') as fp:
        bin_data = fp.read()
    
    # 解析文件中的头信息
    # 从文件头部依次读取两个个32位,分别为:
    # magic,numImgs
    # 偏置
    offset = 0
    # 读取格式: 大端
    fmt_header = '>ii'
    magic, numImgs = struct.unpack_from(fmt_header, bin_data, offset)
    # print(magic,numImgs)
    # 解析图片数据
    # 偏置掉头文件信息
    offset = struct.calcsize(fmt_header)
    # 读取格式
    fmt_image = '>'+str(numImgs)+'B'
    data = torch.tensor(struct.unpack_from(fmt_image, bin_data, offset))
    return data

def get_MINIST():
    # 文件路径
    data_path = 'E:/ComputerVision_Proj/dataset/MINIST/DATA/'
    file_names = ['t10k-images-idx3-ubyte',
                    't10k-labels-idx1-ubyte',
                    'train-images-idx3-ubyte',
                    'train-labels-idx1-ubyte']
    test_set = (__decode_idx3_ubyte(os.path.join(data_path, file_names[0])),
                __decode_idx1_ubyte(os.path.join(data_path, file_names[1])))
    train_set = (__decode_idx3_ubyte(os.path.join(data_path, file_names[2])),
                __decode_idx1_ubyte(os.path.join(data_path, file_names[3])))
    return train_set, test_set

if __name__ == '__main__':
    train_set, test_set = get_MINIST()
    print(train_set[0].shape,train_set[1].shape)
    print(test_set[0].shape,test_set[1].shape)
    test = np.array(train_set[0][0])
    plt.imshow(test)
    plt.show()

脚本2(cnn_digital_recognize):完全复现下列结构。

代码如下:

## Author: 撞南墙者 ##
## Date: 2024-12-7 ##
## CopyRight: 随意使用,欢迎交流 ## 
import torch,random
import copy
import numpy as np
import torch.nn as nn
import matplotlib.pyplot as plt
from dataset_load import get_MINIST
from torch.utils.data import DataLoader
from tqdm import tqdm

random.seed(2021)
np.random.seed(2021)
torch.manual_seed(2021)

class Custom_dataset(torch.utils.data.Dataset):
    def __init__(self,dataset):
        self.input_dataset,self.label_dataset = dataset
        self.one_hot_code_flag = True
        self.num_classes = 10

    def __getitem__(self,index):
        input_img = self.input_dataset[index]
        if self.one_hot_code_flag:
            label_img = self.one_hot_code(self.label_dataset[index],self.num_classes)
        else:
            label_img = self.label_dataset[index]
        return input_img,label_img
    
    def __len__(self):
        length = self.input_dataset.shape[0]
        assert length == self.label_dataset.shape[0]
        return length

    @staticmethod
    def one_hot_code(input_data: torch.Tensor,num_classes) -> torch.Tensor:
        one_hot = np.zeros((num_classes),dtype=np.int8)
        for i in range(num_classes):
            if input_data == i:
                one_hot[i] = 1
                break
        one_hot = torch.Tensor(one_hot)
        return one_hot

# 定义模型
class CNN_to_digital_number_recognize(nn.Module):
    
    def __init__(self,input_dim):
        super().__init__()
        self.input_dim = input_dim
        ksize1 = 5
        ksize2 = 5
        padding1 = 0    # ksize1//2
        padding2 = 0    # ksize2//2
        final_channels = 20
        pooling_size = 2
        # 如果想保持图像大小,padding = kernel_size//2
        self.conv1 = nn.Conv2d(in_channels=1,out_channels=10,kernel_size=ksize1,padding=padding1)
        self.pool1 = nn.MaxPool2d(kernel_size=pooling_size)
        self.conv2 = nn.Conv2d(in_channels=10,out_channels=final_channels,kernel_size=ksize2,padding=padding2)
        self.pool2 = nn.MaxPool2d(kernel_size=pooling_size)
        # 计算最后的元素总数
        final_elements = final_channels * ((((input_dim[1]-(ksize1//2)*2) // pooling_size) - (ksize2//2)*2) // pooling_size)**2
        self.flatten = nn.Flatten()
        self.linear1 = nn.Linear(final_elements,512)
        self.linear2 = nn.Linear(512,10)
        # 构建
        self.ConvBlock1 = nn.Sequential(
            self.conv1,
            nn.ReLU(),
            self.pool1)
        self.ConvBlock2 = nn.Sequential(
            self.conv2,
            nn.ReLU(),
            self.pool2)
        self.LinearBlock = nn.Sequential(
            self.flatten,
            self.linear1,
            nn.ReLU(),
            self.linear2,
            nn.Softmax(dim=1))

    def forward(self,x):
        # 卷积输入的数据维度为 (Batch,channel,height,weight)
        x = x.view(-1,self.input_dim[0],self.input_dim[1],self.input_dim[2]).to(torch.float)
        x = self.ConvBlock1(x)
        x = self.ConvBlock2(x)
        x = self.LinearBlock(x)
        # print(x.shape)
        return x

def training_process(train_set,model,params_dict: dict):
    ## 参数设置 ##
    BATCHSIZE = params_dict['batchsize']
    LEARNING_RATE = params_dict['learning_rate']
    EPOCHS = params_dict['epochs']
    device = torch.device(params_dict['device'])
    val_rate = 0.15
    ## 模型准备 ## 
    model.to(device)
    optimizer = torch.optim.SGD(model.parameters(),lr=LEARNING_RATE)
    loss_func = nn.CrossEntropyLoss()  # 对于分类问题
    ## 数据准备 ##
    length = train_set[0].shape[0]
    train_dataset = Custom_dataset((train_set[0][:int(length*(1-val_rate))],train_set[1][:int(length*(1-val_rate))]))
    train_loader = DataLoader(train_dataset,batch_size=BATCHSIZE,shuffle=True)
    val_dataset = Custom_dataset((train_set[0][:int(length*val_rate)],train_set[1][:int(length*val_rate)]))
    val_loader = DataLoader(val_dataset,batch_size=BATCHSIZE,shuffle=True)
    ## 定义模型验证/测试函数 ##
    def val(model,val_loader):
        val_loss = []
        model.eval()
        for _,(batch_x,batch_y) in enumerate(val_loader):
            batch_x = batch_x.float().to(device)
            batch_y = batch_y.float().to(device)
            batch_out = model(batch_x)
            # 计算误差
            loss = loss_func(batch_out,batch_y)
            val_loss.append(loss.item())
        model.train()
        return np.mean(val_loss)
    ## 记录训练过程 ##
    train_loss_ls = []
    val_loss_ls = []
    early_stopping_count = 0
    ## 开始训练 ## 
    print("Start training...")
    for epo in tqdm(range(EPOCHS),colour='yellow'):
        batch_loss = []
        for _,(batch_x,batch_y) in enumerate(train_loader):
            optimizer.zero_grad()
            batch_x = batch_x.float().to(device)
            batch_y = batch_y.float().to(device)
            batch_out = model(batch_x)
            # 计算误差
            loss = loss_func(batch_out,batch_y)
            batch_loss.append(loss.item())
            loss.backward()
            optimizer.step()
            if params_dict['device'] == 'cuda':
                torch.cuda.empty_cache()
        train_loss_ls.append(np.mean(batch_loss))
        val_loss_ls.append(val(model,val_loader))
        if epo == 0:
            best_val_loss = val_loss_ls[-1]
        # print(f"Iters {epo+1:>3}/{EPOCHS} is end, Train_loss:{train_loss_ls[-1]:>8.4f}, Valid_loss:{val_loss_ls[-1]:>8.4f}.")
        if val_loss_ls[-1] <= best_val_loss:
            best_val_loss = val_loss_ls[-1]
            best_model = copy.deepcopy(model)
            early_stopping_count = 0
            # print("Best model has been saved.")
        else:
            early_stopping_count += 1
        if early_stopping_count >= 5:
            print("Early stopping by 5 epochs...")
            break
    print("Training finished.")
    return best_model.eval()

def main():
    train_set,test_set = get_MINIST()
    print("train dataset size: ",train_set[0].shape)
    print("test dataset size: ",test_set[0].shape)
    # print(np.unique(np.array(train_set[1]),return_counts=True))
    Model = CNN_to_digital_number_recognize(input_dim=(1,28,28))
    trained_model = training_process(train_set,
                                     Model,
                                     params_dict={
                                            'batchsize':8,
                                            'learning_rate':0.001,
                                            'epochs':100,
                                            'device':'cuda'
                                            })
    # 简单测试,计算预测准确率
    count = 0
    trained_model.cpu()
    test_input_data,test_label_data = test_set
    print("Testing...")
    for i,input_test in enumerate(tqdm(test_input_data,colour='yellow')):
        res = trained_model(input_test.unsqueeze(0))
        pred_label = torch.argmax(res).item()
        if pred_label == test_label_data[i]:
            count += 1
    print(f"Accuracy:{(count/len(test_input_data))*100:>6.2f}%")
    
if __name__ == '__main__':
    main()

 代码运行结果:测试集精度约为98.95%

附2、十类别彩色图像分类(CIFAR-10 and CIFAR-100 datasets

 # 首先数据读取

在附1的数据处理代码中加入以下函数即可

## Author: 撞南墙者 ##
## Date: 2024-12-7 ##
## CopyRight: 随意使用,欢迎交流 ## 

def get_CIFAR10():
    root_path = 'E:/ComputerVision_Proj/dataset/cifar-10-batches-py/'
    train_path = ['data_batch_1', 'data_batch_2', 'data_batch_3', 'data_batch_4', 'data_batch_5']
    import pickle
    res_data = []
    res_labels = []
    for i in range(5):
        with open(os.path.join(root_path, root_path+train_path[i]), 'rb') as fp:
            res = pickle.load(fp, encoding='bytes')
        labels = res[b'labels']
        data = res[b'data']
        assert len(labels) == len(data)
        dataset_input = np.zeros((len(labels), 3, 32, 32))
        dataset_labels = np.array(labels,dtype=np.uint8)
        for j in range(len(labels)):
            dataset_input[j] = data[j].reshape(3, 32, 32)
        res_data.append(dataset_input)
        res_labels.append(dataset_labels)
    all_train_dataset_input = np.concatenate(res_data, axis=0)
    all_train_dataset_labels = np.concatenate(res_labels, axis=0)
    # test dataset
    with open(os.path.join(root_path, 'test_batch'), 'rb') as fp:
        res = pickle.load(fp, encoding='bytes')
    labels = res[b'labels']
    data = res[b'data']
    assert len(labels) == len(data)
    test_input = np.zeros((len(labels), 3, 32, 32))
    test_labels = np.array(labels,dtype=np.uint8)
    for j in range(len(labels)):
        test_input[j] = data[j].reshape(3, 32, 32)
    # 转化
    train_input = torch.tensor(all_train_dataset_input)
    train_labels = torch.tensor(all_train_dataset_labels)
    test_input = torch.tensor(test_input)
    test_labels = torch.tensor(test_labels)
    return (train_input,train_labels),(test_input,test_labels)

其结果为:

 只需在原来代码中改动部分参数即可。

## Author: 撞南墙者 ##
## Date: 2024-12-7 ##
## CopyRight: 随意使用,欢迎交流 ## 
import torch,random
import copy
import numpy as np
import torch.nn as nn
import matplotlib.pyplot as plt
from dataset_load import get_CIFAR10
from torch.utils.data import DataLoader
from tqdm import tqdm

random.seed(2021)
np.random.seed(2021)
torch.manual_seed(2021)

class Custom_dataset(torch.utils.data.Dataset):
    def __init__(self,dataset):
        self.input_dataset,self.label_dataset = dataset
        self.one_hot_code_flag = True
        self.num_classes = 10

    def __getitem__(self,index):
        input_img = self.input_dataset[index]
        if self.one_hot_code_flag:
            label_img = self.one_hot_code(self.label_dataset[index],self.num_classes)
        else:
            label_img = self.label_dataset[index]
        return input_img,label_img
    
    def __len__(self):
        length = self.input_dataset.shape[0]
        assert length == self.label_dataset.shape[0]
        return length

    @staticmethod
    def one_hot_code(input_data: torch.Tensor,num_classes) -> torch.Tensor:
        one_hot = np.zeros((num_classes),dtype=np.int8)
        for i in range(num_classes):
            if input_data == i:
                one_hot[i] = 1
                break
        one_hot = torch.Tensor(one_hot)
        return one_hot

# 定义模型
class CNN_to_digital_number_recognize(nn.Module):
    
    def __init__(self,input_dim):
        super().__init__()
        self.input_dim = input_dim
        ksize1 = 5
        ksize2 = 5
        padding1 = 0    # ksize1//2
        padding2 = 0    # ksize2//2
        final_channels = 20
        pooling_size = 2
        # 如果想保持图像大小,padding = kernel_size//2
        self.conv1 = nn.Conv2d(in_channels=3,out_channels=10,kernel_size=ksize1,padding=padding1)
        self.pool1 = nn.MaxPool2d(kernel_size=pooling_size)
        self.conv2 = nn.Conv2d(in_channels=10,out_channels=final_channels,kernel_size=ksize2,padding=padding2)
        self.pool2 = nn.MaxPool2d(kernel_size=pooling_size)
        # 计算最后的元素总数
        final_elements = final_channels * ((((input_dim[1]-(ksize1//2)*2) // pooling_size) - (ksize2//2)*2) // pooling_size)**2
        self.flatten = nn.Flatten()
        self.linear1 = nn.Linear(final_elements,512)
        self.linear2 = nn.Linear(512,10)
        # 构建
        self.ConvBlock1 = nn.Sequential(
            self.conv1,
            nn.ReLU(),
            nn.Dropout(0.5),
            self.pool1)
        self.ConvBlock2 = nn.Sequential(
            self.conv2,
            nn.ReLU(),
            nn.Dropout(0.5),
            self.pool2)
        self.LinearBlock = nn.Sequential(
            self.flatten,
            self.linear1,
            nn.ReLU(),
            self.linear2,
            nn.Softmax(dim=1))

    def forward(self,x):
        # 卷积输入的数据维度为 (Batch,channel,height,weight)
        x = x.view(-1,self.input_dim[0],self.input_dim[1],self.input_dim[2]).to(torch.float)
        x = self.ConvBlock1(x)
        x = self.ConvBlock2(x)
        x = self.LinearBlock(x)
        # print(x.shape)
        return x

def training_process(train_set,model,params_dict: dict):
    ## 参数设置 ##
    BATCHSIZE = params_dict['batchsize']
    LEARNING_RATE = params_dict['learning_rate']
    EPOCHS = params_dict['epochs']
    device = torch.device(params_dict['device'])
    val_rate = 0.15
    ## 模型准备 ## 
    model.to(device)
    optimizer = torch.optim.SGD(model.parameters(),lr=LEARNING_RATE)
    loss_func = nn.CrossEntropyLoss()  # 对于分类问题
    ## 数据准备 ##
    length = train_set[0].shape[0]
    train_dataset = Custom_dataset((train_set[0][:int(length*(1-val_rate))],train_set[1][:int(length*(1-val_rate))]))
    train_loader = DataLoader(train_dataset,batch_size=BATCHSIZE,shuffle=True)
    val_dataset = Custom_dataset((train_set[0][:int(length*val_rate)],train_set[1][:int(length*val_rate)]))
    val_loader = DataLoader(val_dataset,batch_size=BATCHSIZE,shuffle=True)
    ## 定义模型验证/测试函数 ##
    def val(model,val_loader):
        val_loss = []
        model.eval()
        for _,(batch_x,batch_y) in enumerate(val_loader):
            batch_x = batch_x.float().to(device)
            batch_y = batch_y.float().to(device)
            batch_out = model(batch_x)
            # 计算误差
            loss = loss_func(batch_out,batch_y)
            val_loss.append(loss.item())
        model.train()
        return np.mean(val_loss)
    ## 记录训练过程 ##
    train_loss_ls = []
    val_loss_ls = []
    early_stopping_count = 0
    ## 开始训练 ## 
    print("Start training...")
    for epo in tqdm(range(EPOCHS),colour='yellow'):
        batch_loss = []
        for _,(batch_x,batch_y) in enumerate(train_loader):
            optimizer.zero_grad()
            batch_x = batch_x.float().to(device)
            batch_y = batch_y.float().to(device)
            batch_out = model(batch_x)
            # 计算误差
            loss = loss_func(batch_out,batch_y)
            batch_loss.append(loss.item())
            loss.backward()
            optimizer.step()
            if params_dict['device'] == 'cuda':
                torch.cuda.empty_cache()
        train_loss_ls.append(np.mean(batch_loss))
        val_loss_ls.append(val(model,val_loader))
        if epo == 0:
            best_val_loss = val_loss_ls[-1]
        # print(f"Iters {epo+1:>3}/{EPOCHS} is end, Train_loss:{train_loss_ls[-1]:>8.4f}, Valid_loss:{val_loss_ls[-1]:>8.4f}.")
        if val_loss_ls[-1] <= best_val_loss:
            best_val_loss = val_loss_ls[-1]
            best_model = copy.deepcopy(model)
            early_stopping_count = 0
            # print("Best model has been saved.")
        else:
            early_stopping_count += 1
        if early_stopping_count >= 5:
            print("Early stopping by 5 epochs...")
            break
    print("Training finished.")
    return best_model.eval()

def main():
    train_set,test_set = get_CIFAR10()
    print("train dataset size: ",train_set[0].shape)
    print("test dataset size: ",test_set[0].shape)
    # print(np.unique(np.array(train_set[1]),return_counts=True))
    Model = CNN_to_digital_number_recognize(input_dim=(3,32,32))
    trained_model = training_process(train_set,
                                     Model,
                                     params_dict={
                                            'batchsize':32,
                                            'learning_rate':0.001,
                                            'epochs':100,
                                            'device':'cuda'
                                            })
    # 简单测试,计算预测准确率
    count = 0
    trained_model.cpu()
    test_input_data,test_label_data = test_set
    print("Testing...")
    for i,input_test in enumerate(tqdm(test_input_data,colour='yellow')):
        res = trained_model(input_test.unsqueeze(0))
        pred_label = torch.argmax(res).item()
        if pred_label == test_label_data[i]:
            count += 1
    print(f"Accuracy:{(count/len(test_input_data))*100:>6.2f}%")
    
if __name__ == '__main__':
    main()

结果为,精度在46.10%,可以选择调整参数或者网络架构等提升其精度:


欢迎批评指导,共勉!

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐