CNN——AlexNet

1.AlexNet概述

论文原文:ImageNet classification with deep convolutional neural networks (acm.org)

        在LeNet提出后,卷积神经网络在计算机视觉和机器学习领域中很有名气。但卷积神经网络并没有主导这些领域。这是因为虽然LeNet在小数据集上取得了很好的效果,但是在更大、更真实的数据集上训练卷积神经网络的性能和可行性还有待研究。事实上,在上世纪90年代初到2012年之间的大部分时间里,神经网络往往被其他机器学习方法超越,如支持向量机(support vector machines)。

        虽然上世纪90年代就有了一些神经网络加速卡,但仅靠它们还不足以开发出有大量参数的深层多通道多层卷积神经网络。此外,当时的数据集仍然相对较小,神经网络巨量的参数很容易导致过拟合。除了这些障碍,训练神经网络的一些关键技巧仍然缺失,包括参数初始化、随机梯度下降的变体、非挤压激活函数和有效的正则化技术。

        随着GPU性能的提升,和ImageNet数据集的出现。2012年,AlexNet横空出世。它首次证明了深度卷积神经网络学习到的特征可以超越手工设计的特征。它一举打破了计算机视觉研究的现状。 AlexNet使用了8层卷积神经网络,并以很大的优势赢得了2012年ImageNet图像识别挑战赛。

AlexNet和LeNet的设计理念非常相似,但也存在显著差异。

  1. AlexNet比相对较小的LeNet5要深得多。AlexNet由八层组成:五个卷积层、两个全连接隐藏层和一个全连接输出层。

  2. AlexNet使用ReLU而不是sigmoid作为其激活函数。ReLU 能够提高计算速度的同时,有效地解决了梯度消失问题,从而使得训练更加高效。

        3.局部响应归一化(Local response nomalization,LRN)。LRN层旨在模拟生物神经系统中的激励侧抑制机制,通过对局部神经元的活动进行归一化处理来增强模型的泛化能力。在后来VGG的论文中发现没什么用,还会增加参数。

                        

        通过验证集得到参数组合:k=2,n=5,α=1e-4,β=0.75

        4.重叠池化(Overlapping Pooling)。池化步长小于池化窗口的大小。现在也不再被采用。

        5.在训练阶段全连接层使用了Dropout降低过拟合,每次迭代随机使一半的神经元弃用

Dropout 是一种在神经网络训练过程中常用的正则化技术,通过在神经网络的训练过程中随机丢弃(或者说关闭)一些神经元节点来减少过拟合的发生。

1. 减少过拟合

        Dropout 强制让网络在训练时不能依赖于特定的神经元,因为它们在每次迭代中都有可能被随机丢弃。这样可以降低网络对某些特定特征的依赖性,使得网络更加泛化,从而减少了过拟合的风险。

2. 提升网络的鲁棒性

        通过随机丢弃节点,网络变得更加健壮。它鼓励网络中不同神经元之间学习到更加独立、更具有鲁棒性的特征表示,而不是过度依赖某些特定的神经元。

3. 防止神经元共适应

        神经元之间可能会相互适应,导致它们高度依赖彼此。Dropout 强制网络在训练过程中更加独立地学习,减少了神经元共适应的情况,从而使网络更加健壮。

4. 类似集成多个模型的效果

        每次训练迭代中,Dropout 随机地关闭一些神经元,相当于训练了许多不同的网络结构。最终,这些结构共同贡献了一个集成模型,提高了模型的表现力。

        6.分布式训练。在当时GPU性能并不高,内存比较小,AlexNet在使用GPU进行训练时,可将卷积层和全连接层分别放到不同的GPU上进行并行计算,从而大大加快了训练速度。

        7.数据增强。

                1.从256×256中随机裁剪出224×224再水平翻转。

                2.第二种数据增强方式是改变训练图像中RGB通道的强度。在整个ImageNet训练集中的RGB像素值集合上执行PCA,在每张训练图像中,我们都会添加所主成分的倍数

        整个网络结构图如下,网络结构被切割为两部分,每个GPU单独计算一半的通道数,其中会互相通信三次。

        输入尺寸是227,224是论文中写错了。如果是224,则第一次卷积后特征图尺寸((224-11)/4)+1=54.25会出现小数

        当然从现在的计算资源来看,AlexNet是一个非常简单的神经网络,已经不再需要分到2个GPU上并行计算了,于是网络结构可以简化如下

2.网络结构详解

1.输入层。227 × 227 × 3,三通道RGB图像

2.卷积C1

  1. 卷积。96个11×11×3的卷积核,padding = 0,stride = 4。特征图尺寸为((227-11)/4)+1=55,得到输出55×55×96的特征图
  2. ReLU激活
  3. 最大池化。核大小为3×3,padding = 0,stride = 2,特征图尺寸为((55-3)/2)+1=27,得到输出27×27×96的特征图

3.卷积C2

  1. 卷积。256个5×5×96的卷积核,padding = 2,stride = 1。特征图尺寸为((27-5+2×2)/1)+1=27,得到输出27×27×256的特征图
  2. ReLU激活
  3. 最大池化。核大小为3×3,padding = 0,stride = 2,特征图尺寸为((27-3)/2)+1=13,得到输出13×13×256的特征图

4.卷积C3

  1. 卷积。384个3×3×256的卷积核,padding = 1,stride = 1。特征图尺寸为((13-3+2×1)/1)+1=13,得到输出13×13×384的特征图
  2. ReLU激活

5.卷积C4

  1. 卷积。384个3×3×384的卷积核,padding = 1,stride = 1。特征图尺寸为((13-3+2×1)/1)+1=13,得到输出13×13×384的特征图
  2. ReLU激活

6.卷积C5

  1. 卷积。256个3×3×384的卷积核,padding = 1,stride = 1。特征图尺寸为((13-3+2×1)/1)+1=13,得到输出13×13×256的特征图
  2. ReLU激活
  3. 最大池化。核大小为3×3,padding = 0,stride = 2,特征图尺寸为((13-3)/2)+1=6,得到输出6×6×256的特征图

7.全连接层FC6

  1. 全连接,6×6×256–>>1×1×4096,并使用Dropout,随机50%神经元弃用
  2. ReLU激活

8.全连接层FC7

  1. 全连接,1×1×4096–>>1×1×4096,并使用Dropout,随机50%神经元弃用
  2. ReLU激活

9.全连接层FC8

        全连接softmax,1×1×4096–>>1×1×1000。1000是1000个分类类别

3.Pytorch实现AlexNet

class AlexNetWithLRN(nn.Module):
    def __init__(self, num_classes=1000):
        super(AlexNetWithLRN, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 96, kernel_size=11, stride=4),
            nn.ReLU(inplace=True),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),  # LRN层
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(96, 256, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),  # LRN层
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(256, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

# 打印模型结构
model = AlexNet().to(device)
summary(model, (3, 227, 227))

                

4.AlexNet在CIFAR-10数据集简单实践

        注:这里只是简单实践,训练轮数较少,且不涉及调参(所以没验证集)和各种提高模型性能的tricks

1.读取数据集

        CIFAR-10数据集是32*32尺寸的,但AlexNet网络结构是针对ImageNet大尺寸设计的,但ImageNet数据集作为简单实践的话又太大了。这里直接简单的将图片拉大,但实际上这并不是一个好的操作,这里只是简单实践,毕竟AlexNet现在并不常使用。

# 数据预处理
transform = transforms.Compose([
    transforms.Resize((227, 227)),  
    transforms.ToTensor(),
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
])

# 加载CIFAR-10数据集
train_dataset = datasets.CIFAR10(root='./dataset', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root='./dataset', train=False, download=True, transform=transform)

# 数据加载器
train_dataloader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=64, shuffle=False)

        这个归一化的数据来源于ImageNet数据集百万张统计得到,通常可以作为一般数据集的归一化标准。当然也可以针对自己数据集重新计算均值和标准差用于归一化。

2.使用GPU 

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

4.模型训练

def train(model, lr, epochs, train_dataloader, device, save_path):
    # 将模型放入GPU
    model = model.to(device)
    # 使用交叉熵损失函数
    loss_fn = nn.CrossEntropyLoss().to(device)
    # SGD
    optimizer = torch.optim.SGD(model.parameters(), lr=lr, weight_decay=5e-4, momentum=0.9)
    # 记录训练与验证数据
    train_losses = []
    train_accuracies = []
    # 开始迭代   
    for epoch in range(epochs):   
        # 切换训练模式
        model.train()  
        # 记录变量
        train_loss = 0.0
        correct_train = 0
        total_train = 0
        # 读取训练数据并使用 tqdm 显示进度条
        for i, (inputs, targets) in tqdm(enumerate(train_dataloader), total=len(train_dataloader), desc=f"Epoch {epoch+1}/{epochs}", unit='batch'):
            # 训练数据移入GPU
            inputs = inputs.to(device)
            targets = targets.to(device)
            # 模型预测
            outputs = model(inputs)
            # 计算损失
            loss = loss_fn(outputs, targets)
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播
            loss.backward()
            # 使用优化器优化参数
            optimizer.step()
            # 记录损失
            train_loss += loss.item()
            # 计算训练正确个数
            _, predicted = torch.max(outputs, 1)
            total_train += targets.size(0)
            correct_train += (predicted == targets).sum().item()
        # 计算训练正确率并记录
        train_loss /= len(train_dataloader)
        train_accuracy = correct_train / total_train
        train_losses.append(train_loss)
        train_accuracies.append(train_accuracy)
        # 输出训练信息
        print(f"Epoch [{epoch + 1}/{epochs}] - Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.4f}")
    # 绘制损失和正确率曲线
    plt.figure(figsize=(10, 5))
    plt.subplot(1, 2, 1)
    plt.plot(range(epochs), train_losses, label='Training Loss')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.legend()
    plt.subplot(1, 2, 2)
    plt.plot(range(epochs), train_accuracies, label='Accuracy')
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.tight_layout()
    plt.show()
    torch.save(model.state_dict(), save_path)
model = AlexNet(num_classes=10) # 十分类
lr = 0.01 
epochs = 10
save_path = './modelWeight/AlexNet_CIFAR10'
train(model,lr,epochs,train_dataloader,device,save_path)

        这里只训练了10个epoch,也没有使用验证集调参,仅仅是简单实践而已。可以看到损失还在不断降低,还没收敛。

5.模型测试

def test(model, test_dataloader, device, model_path):
    # 将模型设置为评估模式
    model.eval()
    # 将模型移动到指定设备上
    model.to(device)

    # 从给定路径加载模型的状态字典
    model.load_state_dict(torch.load(model_path))

    correct_test = 0
    total_test = 0
    # 不计算梯度
    with torch.no_grad():
        # 遍历测试数据加载器
        for inputs, targets in test_dataloader:  
            # 将输入数据和标签移动到指定设备上
            inputs = inputs.to(device)
            targets = targets.to(device)
            # 模型进行推理
            outputs = model(inputs)
            # 获取预测结果中的最大值
            _, predicted = torch.max(outputs, 1)
            total_test += targets.size(0)
            # 统计预测正确的数量
            correct_test += (predicted == targets).sum().item()
    
    # 计算并打印测试数据的准确率
    test_accuracy = correct_test / total_test
    print(f"Accuracy on Test: {test_accuracy:.4f}")
    return test_accuracy
model_path = './modelWeight/AlexNet_CIFAR10'
test(model, test_dataloader, device, save_path)

6.使用Pytorch自带的AlexNet

        Pytorch有官方实现的AlexNet以及它在ImageNet上预训练好的权重,如果数据集的分类类别都在ImageNet中存在,而且想快速训练,可以使用预训练好的权重。地址:alexnet — Torchvision main documentation (pytorch.org)

        此外还需要修改最后一层全连接层的输出数目

from torchvision import models
# 初始化预训练的AlexNet模型
modelPre = models.alexnet(weights='DEFAULT')
num_ftrs = modelPre.classifier[6].in_features
modelPre.classifier[6] = nn.Linear(num_ftrs, 10)  # CIFAR-10有10个类别
modelPre = modelPre.to(device)
summary(modelPre, (3, 224, 224))

        weights='DEFAULT'就是使用默认最新最好的预训练权重,直接指定weights='IMAGENET1K_V1'也是一样的,因为AlexNet在pytroch中只有一个权重,其他模型会可能有多个版本权重,可以在官方文档中看。如果只想使用他的模型而不使用预训练权重,直接不设定这个参数就可以了。

        Pytorch实现的AlexNet还是有些不同的,一般输入224×224,还使用了全局平均池化支持不同的尺寸输入,AlexNet出来的时候全局平均池化还没被提出。

                        

lr = 0.01 
epochs = 10
save_path = './modelWeight/AlexNetPreTrain_CIFAR10'
train(modelPre,lr,epochs,train_dataloader,device,save_path)

        可以看到收敛更快 

lr = 0.01 
epochs = 10
save_path = './modelWeight/AlexNetPreTrain_CIFAR10'
train(modelPre,lr,epochs,train_dataloader,device,save_path)

 

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AlexNet是深度学习中广为人知的卷积神经网络结构,是获得ImageNet比赛胜利的经典模型。AlexNet的代码主要用Python实现,使用的深度学习框架是TensorFlow。下面是AlexNet Python代码的一些重要部分: 1.定义模型 在TensorFlow中,定义一个卷积神经网络可以用类似如下代码创建: ```python class AlexNet(object): def __init__(self, x, keep_prob, num_classes, skip_layer, weights_path = 'DEFAULT'): # 输入 x 是一个 4D tensor : [batch_size, width, height, channel] # keep_prob 是 dropout 函数的 keep probability # num_classes 是分类的种类数量 # skip_layer 是否跳过某些层 # weights_path 预先训练好的模型路径 # 初始化各种权重和 bias # 定义各种 AlexNet 的网络层 # ... def conv(self, input, kernel, biases, k_h, k_w, c_o, s_h, s_w, padding="VALID", group=1): # 定义卷积操作,用于搭建卷积神经网络层 # ... def fc(self, input, num_out, name, relu=True): # 定义全连接层操作,用于搭建卷积神经网络层 # ... def max_pool(self, input, k_h, k_w, s_h, s_w, name, padding="VALID"): # 定义池化操作,用于搭建卷积神经网络层 # ... ``` 2.定义训练方法 训练神经网络需要指定损失函数、优化器和反向传播等参数。可以使用TensorFlow中的 `tf.nn.conv2d`、`tf.nn.softmax_cross_entropy_with_logits`、`tf.train.AdamOptimizer` 等函数,如下: ```python def train(self, x_train, y_train): # 预处理和训练数据 # 定义损失函数 logits = self.fc8 cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=self.y) loss = tf.reduce_mean(cross_entropy) # 定义优化器 optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate) # 定义反向传播 grads = optimizer.compute_gradients(loss) train_op = optimizer.apply_gradients(grads) # 训练过程 sess = tf.Session() sess.run(tf.initialize_all_variables()) for i in range(10000): sess.run(train_op, feed_dict={self.x: x_train, self.y: y_train}) # evaluate ``` 3.定义预测方法 预测的时候,传入模型的参数是像素点的数组,然后神经网络会输出一个向量,向量中每个分量代表该样本属于某一类别的概率。 ```python def predict(self, x_test): # 预处理测试数据 # 载入已训练好的模型 # 预测分类 sess = tf.Session() sess.run(tf.initialize_all_variables()) y_pred = sess.run(tf.argmax(self.fc8, 1), feed_dict={self.x: x_test, self.keep_prob: 1.}) return y_pred ``` 以上只是AlexNet Python代码的部分,要完整实现AlexNet还需要重新实现一些TensorFlow内置函数。此外,在写神经网络的代码时,我们要记住观察其预测结果和损失的变化,及时调整超参数,以提高模型的性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值