一、卷积介绍

卷积:用一个不同的线性操作替换神经网络单元中稠密的、全连接的仿射变换。
卷积作用:
实现局部模式对输出的影响,满足神经网络的平移不变性需求。
大大减少模型参数数量,参数数量取决于卷积核大小和使用的卷积核(输出通道)个数,而非图像像素数量。
卷积特性:
局部性:对图像局部像素进行操作,通过核函数与输入图像局部区域做标量积运算。
平移不变性:在图像不同位置使用相同核权重,保证权重在不同位置对局部模式响应一致。

二、卷积实战

卷积层设置:在 PyTorch 里用nn.Conv2d模块构建卷积层,说明参数如卷积核大小(kernel_size)、输入输出通道数、步长(stride)等的设置及权重张量形状计算。比如对于 3 通道输入、16 通道输出、3×3 卷积核的层,权重张量形状是 16×3×3×3 。
填充边界:输出图像尺寸变小,引入填充(padding)概念来解决此问题,保持输入输出图像大小一致。

conv = nn.Conv2d(3, 1, kernel_size=3, padding=1)
output = conv(img.unsqueeze(0))
img.unsqueeze(0).shape, output.shape 

(torch.Size([1, 3, 32, 32]), torch.Size([1, 1, 32, 32]))

卷积神经网络的工作是估计连续层中的一组过滤器的卷积核,这些过滤器将把一个多通道图像转换成另一个多通道图像,其中不同的通道对应不同的特征,例如一个通道代表平均值,一个通道代表垂直边缘。


下采样方法

1.取4个像素的平均值。这种平均池化在早期是一种常见的方法。
2.取4个像素的最大值。称为最大池化(max pooling ),有丢弃剩余四分之三的数据的缺点。
3.使用带步长的卷积。该方法只将每层第N个像素纳入计算。步长为2的3×4卷枳仍然包含来自前一层所有像素的输入。
网络整合实践:以区分鸟和飞机为目标,整合卷积层、激活函数(如 Tanh)、最大池化层和全连接层构建卷积神经网络。

三、子类化nn.Module

nn.Module 子类化:将网络编写为 nn.Module 子类,在构造函数__init__中初始化nn.Conv2d、nn.Linear等模块,在forward方法中按序调用模块实例完成前向传播。

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
        self.act1 = nn.Tanh()
        self.pool1 = nn.MaxPool2d(2)
        self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1)
        self.act2 = nn.Tanh()
        self.pool2 = nn.MaxPool2d(2)
        self.fc1 = nn.Linear(8 * 8 * 8, 32)
        self.act3 = nn.Tanh()
        self.fc2 = nn.Linear(32, 2)

    def forward(self, x):
        out = self.pool1(self.act1(self.conv1(x)))
        out = self.pool2(self.act2(self.conv2(out)))
        out = out.view(-1, 8 * 8 * 8) # <1>
        out = self.act3(self.fc1(out))
        out = self.fc2(out)
        return out

网络架构与信息压缩:分类网络常通过减少卷积通道数、池化减少像素数量、线性层使输出维度低于输入维度来压缩信息。
参数和子模块跟踪:给 nn.Module 属性分配 nn.Module 实例会自动注册为子模块,子模块须是顶级属性。调用parameters()方法可访问子模块参数,参数由自动求导填充,供优化器更新。
函数式 API:PyTorch 中每个 nn 模块都有对应函数式 API,无内部状态,输出取决于输入参数。如nn.Linear对应nn.functional.linear,使用函数式 API 可使模型定义更简洁。

import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(8 * 8 * 8, 32)
        self.fc2 = nn.Linear(32, 2)
        
    def forward(self, x):
        out = F.max_pool2d(torch.tanh(self.conv1(x)), 2)
        out = F.max_pool2d(torch.tanh(self.conv2(out)), 2)
        out = out.view(-1, 8 * 8 * 8)
        out = torch.tanh(self.fc1(out))
        out = self.fc2(out)
        return out

四、训练convnet

训练循环:ConvNet 训练核心是两个嵌套循环,外部循环跨迭代周期,内部循环从数据集生成批次 DataLoader。每次循环步骤包括

  • 正向传播提供输入
  • 计算损失
  • 将老梯度归零
  • 调用loss.backward()反向传播计算梯度
  • 让优化器朝更低损失迈进
  • 同时收集输出信息

测量精度:使用训练集和验证集上的准确率衡量模型,比损失更易解释。通过validate函数实现,在计算过程中不需要梯度更新,统计预测值和真实值一致的数量,进而计算准确率。
保存并加载模型:使用torch.save将模型参数保存到文件,文件仅含权重无结构。加载时先创建模型实例,再用load_state_dict加载参数,确保加载期间模型类定义不变。
在 GPU 上训练:为提高训练速度,将训练转移到 GPU。使用torch.cuda.is_available判断 GPU 可用性,通过Tensor.to方法将张量和模型参数移动到 GPU。修改训练循环和validate函数,将数据和模型移动到指定设备(GPU 或 CPU)。

五、模型设计

模型构建基础:将模型构建为nn.Module子类,用于图像分类。PyTorch 提供全面模块和损失函数,可实现多种架构。
增加内存容量(宽度):通过增加每层神经元数或卷积通道数使模型变宽,如在NetWidth类中指定更多输出通道并相应增加后续层。模型容量与通道和特征数量相关,容量越大可处理输入可变性越大,但过拟合风险也越高。
正则化:
权重惩罚:在损失中添加正则化项,如 L1 和 L2 正则化,L2 正则化更流行,也称为权重衰减,PyTorch 的 SGD 优化器有weight_decay参数可实现 L2 正则化。

def training_loop_l2reg(n_epochs, optimizer, model, loss_fn,
                        train_loader):
    for epoch in range(1, n_epochs + 1):
        loss_train = 0.0
        for imgs, labels in train_loader:
            imgs = imgs.to(device=device)
            labels = labels.to(device=device)
            outputs = model(imgs)
            loss = loss_fn(outputs, labels)

            l2_lambda = 0.001
            l2_norm = sum(p.pow(2.0).sum()
                          for p in model.parameters())  # <1>
            loss = loss + l2_lambda * l2_norm

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
            loss_train += loss.item()
        if epoch == 1 or epoch % 10 == 0:
            print('{} Epoch {}, Training loss {}'.format(
                datetime.datetime.now(), epoch,
                loss_train / len(train_loader)))

Dropout:将网络每轮训练迭代中的神经元随机部分清零,防止过拟合。在 PyTorch 中通过nn.Dropout2d或nn.Dropout3d模块实现,训练中活跃,生产评估时关闭。

class NetDropout(nn.Module):
    def __init__(self, n_chans1=32):
        super().__init__()
        self.n_chans1 = n_chans1
        self.conv1 = nn.Conv2d(3, n_chans1, kernel_size=3, padding=1)
        self.conv1_dropout = nn.Dropout2d(p=0.4)
        self.conv2 = nn.Conv2d(n_chans1, n_chans1 // 2, kernel_size=3,
                               padding=1)
        self.conv2_dropout = nn.Dropout2d(p=0.4)
        self.fc1 = nn.Linear(8 * 8 * n_chans1 // 2, 32)
        self.fc2 = nn.Linear(32, 2)
        
    def forward(self, x):
        out = F.max_pool2d(torch.tanh(self.conv1(x)), 2)
        out = self.conv1_dropout(out)
        out = F.max_pool2d(torch.tanh(self.conv2(out)), 2)
        out = self.conv2_dropout(out)
        out = out.view(-1, 8 * 8 * self.n_chans1 // 2)
        out = torch.tanh(self.fc1(out))
        out = self.fc2(out)
        return out

批量归一化:将输入重新调整到网络激活状态,减少训练对初始化依赖,加速收敛,通过nn.BatchNorm1d、nn.BatchNorm2d和nn.BatchNorm3d实现 。

class NetBatchNorm(nn.Module):
    def __init__(self, n_chans1=32):
        super().__init__()
        self.n_chans1 = n_chans1
        self.conv1 = nn.Conv2d(3, n_chans1, kernel_size=3, padding=1)
        self.conv1_batchnorm = nn.BatchNorm2d(num_features=n_chans1)
        self.conv2 = nn.Conv2d(n_chans1, n_chans1 // 2, kernel_size=3, 
                               padding=1)
        self.conv2_batchnorm = nn.BatchNorm2d(num_features=n_chans1 // 2)
        self.fc1 = nn.Linear(8 * 8 * n_chans1 // 2, 32)
        self.fc2 = nn.Linear(32, 2)
        
    def forward(self, x):
        out = self.conv1_batchnorm(self.conv1(x))
        out = F.max_pool2d(torch.tanh(out), 2)
        out = self.conv2_batchnorm(self.conv2(out))
        out = F.max_pool2d(torch.tanh(out), 2)
        out = out.view(-1, 8 * 8 * self.n_chans1 // 2)
        out = torch.tanh(self.fc1(out))
        out = self.fc2(out)
        return out

深度网络的挑战与解决:
跳跃连接:深度增加使训练更难收敛,2015 年提出的 ResNet 使用跳跃连接解决梯度消失问题,通过将前层输出添加到后层输入,使损失对深层参数导数更直接。
构建深度模型:在 PyTorch 中可定义构建块(如残差块)动态构建深度模型,如创建包含多个残差块的NetResDeep模型,nn.Sequential确保块输出作为下一块输入及参数可见。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐