240920深度学习笔记

本周主要进行了深度学习中卷积神经网络的学习

学习时间

2024.9.13—2024.9.20

学习内容

一、卷积神经网络知识点学习

二、卷积神经网络案例实践

三、残差网络ResNet

四、ResNet案例实践

主要内容

一、卷积神经网络知识点学习

1、卷积神经网络概述

卷积神经网络(Convolutional Neural Network, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络,是深度学习的典型网络结构之一。它主要用于图像、视频、语音等信号数据的分类和识别任务,并在计算机视觉、自然语言处理、语音识别等多个领域展现出卓越的性能。

2、输入层

输入层是CNN的第一层,负责接收原始数据。对于图像数据,输入层通常是一个二维或三维数组,表示图像的宽度、高度和颜色通道(如RGB)。

通道

输入通道:在卷积神经网络的输入层,通道数通常对应于输入数据的维度。对于图像数据,输入通道数通常与图像的颜色通道数相匹配。例如,RGB图像有三个输入通道,分别对应红色、绿色和蓝色。灰度图像则只有一个输入通道,表示灰度值。

输出通道:在卷积层中,每个卷积核会生成一个新的特征映射(feature map),这些特征映射作为输出特征图的通道。因此,输出通道的数量等于卷积核的数量。这些通道代表了网络从输入数据中提取的不同特征。

3、卷积层

卷积操作是通过一个称为卷积核(或滤波器)的小矩阵在输入数据(如图像)上滑动,并在每个位置执行元素级别的乘法后求和(有时加上一个偏置项),然后通过一个激活函数(如ReLU)来引入非线性,从而生成输出特征图(feature map)。这个过程可以看作是对输入数据的局部特征进行提取和增强的过程。

卷积层通过卷积操作来提取输入数据的特征。

卷积核是执行卷积操作时的核心元素。它是一个小的矩阵,其大小、步长和填充是卷积操作中的关键参数之一。卷积核在输入数据(如图像)上滑动,与输入数据的局部区域进行元素级别的乘法操作,然后将结果相加(可能还加上一个偏置项),得到输出特征图中的一个值。这个过程会在输入数据的整个空间范围内重复进行,从而生成完整的输出特征图。

卷积核的作用在于提取输入数据的局部特征。不同的卷积核可以学习到不同的特征

通过不同的卷积核学习同一张图片可以得到不同的结果。

填充和步幅

在应用多层卷积时,我们常常丢失边缘像素。 由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层,累积丢失的像素数就多了。 解决这个问题的简单方法即为填充(padding):在输入图像的边界填充元素(通常填充元素是0)。

在计算互相关时,卷积窗口从输入张量的左上角开始,向下、向右滑动。 一般默认每次滑动一个元素。 但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。我们将每次滑动元素的数量称为步幅(stride)

垂直步幅为 3,水平步幅为 2 的二维互相关运算

4、池化层

池化层的主要作用是对输入的特征图(feature map)进行下采样(downsampling),通过减少数据的空间尺寸(宽和高)来降低网络中的参数数量和计算量,同时提取出重要的特征信息,从而降低卷积层对位置的敏感性

常见池化操作包括:

最大池化层

在输入特征图的每个指定大小的区域内(如2x2、3x3等),选择该区域内的最大值作为输出。这种方式可以保留区域内最显著的特征,常用于捕捉纹理信息。

平均池化层

与最大池化类似,但在每个区域内计算平均值而不是最大值。这种方式有助于保留整体数据的特征,减少因邻域大小受限造成的估计值方差增大,常用于背景信息的保留。

5、全连接层

全连接层通常位于CNN的末端,负责将提取到的特征映射到样本标记空间,实现分类或回归等任务。

由于卷积层和池化层的输出是二维或三维的特征图,而全连接层需要一维的输入向量,因此需要对特征图进行展平操作。这一步骤将二维或三维的特征图转换为一维的特征向量,以便输入到全连接层中。

根据具体的任务需求(如分类、回归等),输出层会对全连接层的输出进行进一步的处理。例如,在分类任务中,输出层可能会使用softmax函数将全连接层的输出转换为类别概率分布;在回归任务中,输出层则可能直接输出预测值。

二、卷积神经网络案例实践

基于卷积神经网络对MNIST数据集进行训练

首先读取数据

分别构建训练集和测试集

DataLoader来迭代取数据(DataLoader可以将数据集分成多个小批次(batch),每次迭代只加载一个批次的数据,这样可以减少内存的使用,同时也有利于利用并行计算加速模型的训练。)

#定义超参
input_size = 28 #图像的总尺寸28*28
num_classes = 10 #标签的种类数
num_epochs = 3 #训练的总循环周期
batch_size = 64 #一个批次的大小(64张图片)

#训练集
train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)

#测试集
test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor())

#构建batch数据
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)

卷积网络模块构建

一般卷积层,relu层,池化层可以写成一个模块

由于卷积最后结果是一个特征图,需要把图转换成向量才能做分类或者回归任务

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(         #输入大小(1,28,28)
            nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),    #输出大小(16,14,14)
        )

        self.conv2 = nn.Sequential(         #下一层输入大小(16,14,14)
            nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),    #输出大小(32,7,7)
        )
        self.out = nn.Linear(32 * 7 * 7, num_classes)    #全连接层得到的结果

    def forward(self, x):          #前向传播
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(x.size(0), -1)           #特征图展开
        x = self.out(x)
        return x

准确率作为评估标准

def accuracy(predictions, labels):
    pred = torch.max(predictions, 1)[1]
    rights = pred.eq(labels.view_as(pred)).sum()
    return rights, len(labels)         #rights是某一批次中预测正确的个数,len为该批次中样本的数量

训练网络模型

#实例化
net = CNN()
#损失函数
criterion = nn.CrossEntropyLoss()
#优化器
optimizer = optim.Adam(net.parameters(), lr=0.001)   #定义优化器,普通的随机梯度下降算法

#开始训练循环
for epoch in range(num_epochs):
    train_rights = []   #当前epoch结果保存下来

    for batch_idx, (data, target) in enumerate(train_loader):
        net.train()
        output = net(data)
        loss = criterion(output, target)  #计算损失值
        optimizer.zero_grad()   #参数梯度清零
        loss.backward()    #计算损失函数相对于模型参数的梯度
        optimizer.step()   #根据得到的梯度优化参数
        right = accuracy(output, target)
        train_rights.append(right)

        if batch_idx % 100 == 0:

            net.eval()
            val_rights = []

            for(data, target) in test_loader:
                output = net(data)
                right = accuracy(output, target)
                val_rights.append(right)

            #准确率计算
            train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
            val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))

    print('训练正确率:' + str(train_r[0] / train_r[1]), '测试正确率:' + str(val_r[0] / val_r[1]))

训练结果

三、残差网络ResNet

在深度学习中,随着网络层数的增加,往往会出现梯度消失或梯度爆炸的问题,导致网络难以训练。此外,过深的网络还可能导致过拟合和低级特征丢失,从而引发性能退化。为了解决这些问题,研究者们提出了多种方法,其中最具代表性的是残差网络(ResNet)及其恒等映射机制。

残差块

残差块通常由两部分组成:主路径(Main Path)和跳跃连接(Skip Connection)。

主路径:这是残差块中的主要计算路径,通常包含多个卷积层、批量归一化层(Batch Normalization)和激活函数层。这些层负责提取输入数据的特征,并通过非线性变换产生新的特征表示。

跳跃连接:这是残差块中的关键部分,它将输入信号直接连接到残差块的输出上。具体来说,跳跃连接将输入信号(或经过某种变换后的输入信号)与主路径的输出相加,得到残差块的最终输出。这种设计允许输入信号直接传递到网络的更深层,有助于缓解梯度消失问题。

左正常块,右残差块

批量归一化层

批量归一化层是深度学习中一种非常流行且有效的技术,它能够持续加速深层网络的收敛速度,同时改善模型的性能。

批量归一化层的基本思想是在每次训练迭代中,通过规范化当前小批量的输入数据,使得网络的每一层输入都保持稳定的均值和方差。对于卷积层,批量归一化层则会对每个输出通道进行规范化,确保每个通道内的数据分布保持稳定。

Resnet模型

每个模块有4个卷积层(不包括恒等映射的1×1卷积层)。 加上第一个7×7卷积层和最后一个全连接层,共有18层。 因此,这种模型通常被称为ResNet-18。 通过配置不同的通道数和模块里的残差块数可以得到不同的ResNet模型,例如更深的含152层的ResNet-152。

四、ResNet案例实践

使用Resnet模型对花的图片进行分类

制作数据源

data_transforms中指定了所有图像预处理操作

ImageFolder假设所有的文件按文件夹保存好,每个文件夹下面储存同一类别的图片,文件夹的名字为分类的名字

data_dir = 'flower_data'
train_dir = data_dir + '/train'
valid_dir = data_dir + '/valid'

data_transforms = {
    'train': transforms.Compose([transforms.RandomRotation(30),
                                 transforms.CenterCrop(224),
                                 transforms.RandomHorizontalFlip(p=0.5),
                                 transforms.RandomVerticalFlip(p=0.5),
                                 transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
                                 transforms.ToTensor(),
                                 transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                                 ]),
    'valid': transforms.Compose([transforms.Resize(256),
                                 transforms.CenterCrop(224),
                                 transforms.ToTensor(),
                                 transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
                                 ]),
}

batch_size = 8

image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),data_transforms[x]) for x in ['train', 'valid']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
class_names = image_datasets['train'].classes

print(image_datasets)

打印数据集

读取标签对应的实际名字

with open('cat_to_name.json', 'r') as f:
    cat_to_name = json.load(f)

def im_convert(tensor):
    image = tensor.to("cpu").clone().detach()
    image = image.numpy().squeeze()
    image = image.transpose(1, 2, 0)
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))
    image = image.clip(0, 1)

    return image

fig = plt.figure(figsize=(20, 12))
colums = 4
rows = 2

dataiter = iter(dataloaders['valid'])
inputs,classes = next(dataiter)

for idx in range(colums*rows):
    ax = fig.add_subplot(rows, colums, idx + 1, xticks=[], yticks=[])
    ax.set_title(cat_to_name[str(int(classes[idx]))])
    plt.imshow(im_convert(inputs[idx]))
plt.show()

展示一个批次图片和其对应类别名

加载Resnet模型

加载models中提供的模型,并且直接用训练好权重当作初始化参数

def set_parameter_requires_grad(model, feature_extract):
    if feature_extract:
        for param in model.parameters():
            param.requires_grad = False         #这些参数的梯度将不会被计算,因此它们不会被更新


def model_resnet(num_classes, feature_extract, use_pretrained=True):
    model_ft = models.resnet152(pretrained=use_pretrained)
    set_parameter_requires_grad(model_ft, feature_extract)    #不改变resnet中的参数值
    num_ftrs = model_ft.fc.in_features
    model_ft.fc = nn.Linear(num_ftrs, num_classes)
    input_size = 224
    return model_ft, input_size

model_ft, input_size = model_resnet(102, feature_extract=True, use_pretrained=True)

使用GPU训练

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

优化器设置

optimizer_ft = optim.Adam(model_ft.parameters(), lr=0.01)
scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)# 学习率每七个epoch衰减成原来的1/10
criterion = nn.CrossEntropyLoss()

训练模块

def train_model(model, dataloader, criterion, optimizer, num_epoch=25, filename=filename):
    since = time.time()
    best_acc = 0

    model.to(device)

    val_acc_history = []
    train_acc_history = []
    train_losses = []
    val_losses = []
    LRs = [optimizer.param_groups[0]['lr']]

    best_model_wts = copy.deepcopy(model.state_dict())

    for epoch in range(num_epoch):
        print('Epoch {}/{}'.format(epoch, num_epoch-1))
        print('-' * 10)

        #训练和验证
        for phase in ['train', 'valid']:
            if phase == 'train':
                model.train()
            else:
                model.eval()

            running_loss = 0.0
            running_corrects = 0

            for inputs, labels in dataloader[phase]:
                inputs, labels = inputs.to(device), labels.to(device)

                # 清零
                optimizer.zero_grad()

                #只有训练的时候计算和更新梯度
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    loss = criterion(outputs, labels)
                    _, preds = torch.max(outputs, 1)

                    #训练阶段更新权重
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                #计算损失
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss / len(dataloader[phase].dataset)
            epoch_acc = running_corrects / len(dataloader[phase].dataset)

            time_elapsed = time.time() - since
            print('Time elapsed: {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
            print('{}, Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            #得到最好的那次模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())
                state = {
                    'state_dict': model.state_dict(),
                    'best_acc': best_acc,
                    'optimizer': optimizer.state_dict(),
                }
                torch.save(state, filename)
            if phase == 'valid':
                val_acc_history.append(epoch_acc)
                val_losses.append(epoch_loss)
                scheduler.step()
            if phase == 'train':
                train_acc_history.append(epoch_acc)
                train_losses.append(epoch_loss)

        print('Optimization learning rate : {:.7f}'.format(optimizer.param_groups[0]['lr']))
        LRs.append(optimizer.param_groups[0]['lr'])
        print()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    #训练完后用最好的一次当做模型的最终结果
    model.load_state_dict(best_model_wts)
    return model, val_acc_history, train_acc_history, val_losses, train_losses, LRs

开始训练

model_ft, val_acc_history, train_acc_history, val_losses, train_losses, LRs = train_model(model_ft, dataloaders, criterion, optimizer_ft, num_epoch=25, filename=filename)

训练结果

最后一次迭代

结果

总结

1、本周主要进行了卷积神经网络的知识点的学习

2、掌握了卷积神经网络的框架和执行流程

3、通过案例实际体会了卷积神经网络的执行过程

4、着重学习了卷积网络模型中的Resnet模型

5、初步了解数据增强和迁移学习相关技术

6、将数据增强和迁移学习融入实际案例,体会了Resnet152模型的执行过程和训练效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值