AlexNet网络学习笔记

一、网络的亮点

        1、首次利用GPU进行网络加速训练

        2、使用了ReLU激活函数,而不是传统的Sigmoid激活函数以及Tanh激活函数

        3、使用了LRN局部相应归一化

        4、在全连接层的前两层使用了Dropout随机失活神经元操作,以减少过拟合

二、Sigmoid、Tanh、ReLU激活函数介绍

        1、Sigmoid激活函数

       Sigmoid激活函数是一种常用的非线性激活函数,它将实数映射到区间(0, 1)上。其公式为

\sigma(x) = \frac{1}{1 + e^{-x}}

其中 e 是自然对数的底数。

        Sigmoid函数在深度学习中常用于二分类问题的输出层,也可以用于多分类问题的多个输出层。它的输出值介于0和1之间,可以被解释为概率值,使得模型输出更符合概率分布的特性。Sigmoid函数是一个自然的选择,因为它是一个平滑的、可微的阈值单元近似。如果我们将输出视为二分类概率问题时, Sigmoid仍然被⼴泛⽤作输出单元上的激活函数。

import numpy as np  
import matplotlib.pyplot as plt  
  
x = np.linspace(-10, 10, 1000)  
y = 1 / (1 + np.exp(-x))  
  
plt.figure(figsize=(8, 6))  
plt.plot(x, y)  
plt.title("Sigmoid Activation Function")  
plt.xlabel("Input")  
plt.ylabel("Output")  
plt.grid(True)  
plt.show()

         2、tanh激活函数

        tanh激活函数是一种非线性激活函数,它使用双曲正切函数将实数值压缩到-1到1的区间内。公式为:

tanh(x) = \frac{\exp(x) - \exp(-x)}{\exp(x) + \exp(-x)}

Sigmoid函数类似,tanh函数也可以用于预测概率的输出层,但它更适用于隐藏层。

import numpy as np
import matplotlib.pyplot as plt

# 生成x的取值范围
x = np.linspace(-10, 10, 1000)

# 计算tanh函数的值
y = np.tanh(x)

# 绘制图像
plt.plot(x, y)
plt.title("tanh activation function")
plt.xlabel("x")
plt.ylabel("tanh(x)")
plt.grid(True)
plt.show()

         3、ReLU激活函数

        ReLU(Rectified Linear Unit)是一种常用的激活函数,其主要特点是对输入的小于0的数值进行截断,保留大于0的数值。具体来说,ReLU函数的数学表达式为:

f(x)=max(0,x)

        这意味着,当输入x大于0时,ReLU函数的输出就是输入x;而当输入x小于0时,ReLU函数的输出为0。

        ReLU激活函数具有以下优点:

        (1)计算效率高:因为ReLU函数在大于0的区域内是线性的,所以计算相对简单快速。

        (2)缓解了梯度消失问题:相比Sigmoid和Tanh等激活函数,ReLU函数的导数在大于0的区域内始终为1,不会随着输入值变大而趋近于0,这在一定程度上缓解了深度神经网络中常见的梯度消失问题。

        (3)有利于模型学习:ReLU激活函数使得神经网络更倾向于学习那些在训练集中出现过的、大于0的输入,这可能有助于模型更好地捕捉训练数据中的一些特性。

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-10, 10, 1000)
y = np.maximum(0, x)

plt.plot(x, y)
plt.title("ReLU Activation Function")
plt.xlabel("Input")
plt.ylabel("Output")
plt.show()

 三、什么是过拟合?

        过拟合的根本原因是特征维度太多,模型假设过于复杂,参数过多,训练数据过少,噪声过多,导致拟合的函数完美的预测了训练集,但对于新数据的预测结果相对来说比较差。过度的拟合了训练数据,而没有考虑到模型的泛化能力。

四、网络层结构

layer_name

kernel_sizekernelspaddingstride
Conv11196[1,2]4
Maxpool13None02
Conv25256[2,2]1
Maxpool23None02
Conv33384[1,1]1
Conv43384[1,1]1
COnv53256[1,1]1
Maxpool33None02
FC2048NoneNoneNone
FC2048NoneNoneNone
FC1000NoneNoneNone

五、模型代码解读(Module)

模型的代码(含注释)如下:

import torch.nn as nn       # 导入Pytorch的神经网络模块
import torch        # 导入Pytorch库


# 定义AlexNet类,继承于nn.Module这个父类
class AlexNet(nn.Module):
    # 定义构造函数,接收两个参数:num_classes表示分类的类别数,默认为1000;init_weights表示是否初始化权重,默认为False。
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()     # 调用父类的构造函数
        # 定义特征提取部分,使用nn.Sequential容器来保存一系列的层
        self.features = nn.Sequential(
            # 卷积层,输入通道数为3,输入图像大小224×224,输出通道数为48,卷积核大小为11x11,步长为4,填充为2。输出特征图大小为55x55
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),      # ReLU激活函数
            # MaxPooling池化层,池化窗口大小为3x3,步长为2。输出特征图大小为27x27
            nn.MaxPool2d(kernel_size=3, stride=2),
            # 卷积层,输入通道数为48,输出通道数为128,卷积核大小为5x5,填充为2。输出特征图大小为27x27
            nn.Conv2d(48, 128, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            # MaxPooling池化层,输出特征图大小为13x13
            nn.MaxPool2d(kernel_size=3, stride=2),
            # 卷积层,输入通道数为128,输出通道数为192,卷积核大小为3x3,填充为1。输出特征图大小为13x13
            nn.Conv2d(128, 192, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            # 卷积层,输入通道数为192,输出通道数为192,卷积核大小为3x3,填充为1。输出特征图大小为13x13
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            # 卷积层,输入通道数为192,输出通道数为128,卷积核大小为3x3,填充为1。输出特征图大小为13x13
            nn.Conv2d(192, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            # MaxPooling池化层,输出特征图大小为6x6
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        # 定义分类器部分,使用nn.Sequential容器来保存一系列的层
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),                  # Dropout层,保留概率为0.5
            nn.Linear(128 * 6 * 6, 2048),       # 全连接层,输入特征维度为128*6*6,输出特征维度为2048
            nn.ReLU(inplace=True),              # ReLU激活函数
            nn.Dropout(p=0.5),                  # Dropout层,保留概率为0.5
            nn.Linear(2048, 2048),              # 全连接层,输入特征维度为2048,输出特征维度为2048
            nn.ReLU(inplace=True),              # ReLU激活函数
            nn.Linear(2048, num_classes),       # 全连接层,输入特征维度为2048,输出特征维度为num_classes(即分类的类别数)
        )
        # 如果参数init_weights为True,则初始化权重
        if init_weights:
            self._initialize_weights()          # 调用自定义的函数

    # 定义forward方法,模型的正向传播过程
    def forward(self, x):
        x = self.features(x)                    # 输入数据x进行特征提取层的处理
        x = torch.flatten(x, start_dim=1)       # 将特征提取处理过后的x进行展平操作
        x = self.classifier(x)                  # 通过分类器进行分类预测
        return x

    # 定义_initialize_weights方法,用来初始化模型权重
    def _initialize_weights(self):
        # 遍历模型的所有模块
        for m in self.modules():
            # 如果当前模块是2D卷积,则用kaiming正常分布来初始化权重
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                # 如果偏置存在,则将偏置初始化为0
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            # 如果模块是线性层(nn.Linear)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)      # 使用标准差为0.01的正态分布来初始化权重
                nn.init.constant_(m.bias, 0)            # 将偏置初始化为0

六、训练代码解读(train)

import os               # 导入操作系统相关的功能
import sys              # 导入系统相关的功能
import json             # 导入json相关的功能

import torch            # 导入Pytorch库,用于深度学习
import torch.nn as nn                   # 导入Pytorch的神经网络模块
from torchvision import transforms, datasets, utils     # 导入torchvision库,用于图像处理和数据加载
import matplotlib.pyplot as plt         # 导入matplotlib库,用于图像可视化
import numpy as np                      # 导入numpy库,用于数学计算
import torch.optim as optim             # 导入pytorch的优化器
from tqdm import tqdm                   # 导入进度条库,用于显示训练进度

from model import AlexNet               # 导入自定义的AlexNet模型

# 定义主函数
def main():
    # 选择设备,如果有可用的GPU则使用GPU,如果没有GPU则使用CPU
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

    # 定义数据预处理操作
    data_transform = {
        # 定义训练集的一系列转换操作
        "train": transforms.Compose([transforms.RandomResizedCrop(224),         # 随机裁剪,并缩放到224×224大小
                                     transforms.RandomHorizontalFlip(),         # 随机水平翻转
                                     transforms.ToTensor(),                     # 转换为tensor张量
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),     # 将像素值归一化
        # 定义验证集的一系列操作
        "val": transforms.Compose([transforms.Resize((224, 224)),       # 将图像调整为224×224
                                   transforms.ToTensor(),               # 转换为张量
                                   transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}        # 像素值归一化

    # 获得数据集根目录所在路径
    data_root = os.path.abspath(os.path.join(os.getcwd(), "F:\deep-learning-for-image-processing-master"))
    # 获取flower数据集的路径
    image_path = os.path.join(data_root, "data_set", "flower")
    # 判断路径是否存在
    assert os.path.exists(image_path), "{} path does not exist.".format(image_path)
    # 使用ImageFolder加载训练集,传入两个参数,路径和预处理的操作
    train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
                                         transform=data_transform["train"])
    # 获取数据集的数量
    train_num = len(train_dataset)

    # {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
    # 获取类别到索引的映射字典
    flower_list = train_dataset.class_to_idx
    # 将映射翻转,索引到类别的映射
    cla_dict = dict((val, key) for key, val in flower_list.items())
    # 将字典转换成接送字符串,并缩进4个空格方便阅读
    json_str = json.dumps(cla_dict, indent=4)
    # 以写入模式打开文件,将json字符串写入文件
    with open('class_indices.json', 'w') as json_file:
        json_file.write(json_str)

    batch_size = 16             # 设置每一个批次多少样本
    # 获取可用处理器的数量
    nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])
    print('Using {} dataloader workers every process'.format(nw))

    # 创建训练集数据加载器
    train_loader = torch.utils.data.DataLoader(train_dataset,
                                               batch_size=batch_size, shuffle=True,
                                               num_workers=nw)

    # 验证集数据路径
    validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),
                                            transform=data_transform["val"])
    val_num = len(validate_dataset)         # 获取验证集的大小
    # 创建验证集加载器
    validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                                  batch_size=4, shuffle=False,
                                                  num_workers=nw)

    print("using {} images for training, {} images for validation.".format(train_num,
                                                                           val_num))
    # 创建验证集数据迭代器
    test_data_iter = iter(validate_loader)
    # 获取一个批次的验证集样本数据和标签
    test_image, test_label = test_data_iter.next()

    # 显示图像函数
    def imshow(img):
        img = img / 2 + 0.5              # 将图像进行反标准化操作(逆归一化)
        npimg = img.numpy()              # 将图像转换为numpy数组
        plt.imshow(np.transpose(npimg, (1, 2, 0)))
        plt.show()

    # 打印测试集中前四个样本的类别标签,以便检查数据加载是否正确
    print(' '.join('%5s' % cla_dict[test_label[j].item()] for j in range(4)))
    # 显示测试图像:将测试集中的图像使用make_grid函数组合成一个大图
    imshow(utils.make_grid(test_image))

    # 创建一个AlexNet网络模型,设置类别数量为5,同时初始化权重
    net = AlexNet(num_classes=5, init_weights=True)

    # 将模型指认到设备上,有GPU指认GPU
    net.to(device)
    # 定义损失函数,使用交叉熵损失函数作为优化目标
    loss_function = nn.CrossEntropyLoss()
    # 使用Adam优化器来优化网络参数,设置学习率为0.0002
    optimizer = optim.Adam(net.parameters(), lr=0.0002)

    epochs = 10                     # 设置训练轮数
    save_path = './AlexNet.pth'     # 制定模型保存的路径
    best_acc = 0.0                  # 定义最佳准确率,用来保存性能最优的参数
    train_steps = len(train_loader)
    # 训练循环
    for epoch in range(epochs):

        net.train()                 # 将网络设置为训练模式train
        running_loss = 0.0          # 初始化训练损失为0
        # 遍历训练集,使用train_loader提供的数据
        train_bar = tqdm(train_loader, file=sys.stdout)
        for step, data in enumerate(train_bar):
            images, labels = data       # 从data数据中解包出images和labels
            optimizer.zero_grad()       # 清零优化器的梯度,以准备计算新的梯度
            outputs = net(images.to(device))        # 将输入图像传入网络模型,并在指定设备上计算
            loss = loss_function(outputs, labels.to(device))        # 使用交叉熵损失函数loss_function计算模型输出与真实标签的损失
            loss.backward()             # 根据损失计算参数的梯度,进行反向传播
            optimizer.step()            # 通过计算得到的梯度,通过优化器更新网络模型的参数

            # 累计当前批次的损失值,用于统计训练损失
            running_loss += loss.item()

            # 更新进度条train_bar的描述,显示当前训练周期、总周期数以及当前批次的损失值
            train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
                                                                     epochs,
                                                                     loss)


        net.eval()              # 将网络设置为验证模式
        acc = 0.0               # 定义一个变量acc,用于累计当前训练周期内正确分类的数量
        # 关闭梯度计算
        with torch.no_grad():
            # 创建一个进度条val_bar,用于可视化验证集的遍历进度
            val_bar = tqdm(validate_loader, file=sys.stdout)
            # 遍历验证数据集迭代器
            for val_data in val_bar:
                val_images, val_labels = val_data       # 从验证数据集中解包出验证图像和验证标签
                outputs = net(val_images.to(device))    # 将验证图像传入网络,并得到模型输出
                predict_y = torch.max(outputs, dim=1)[1]        # 使用torch.max函数在模型输出中获取每个样本最大值的索引,即预测的类别
                # 将预测的类别与真实标签进行比较,统计正确分类的数量,累加到acc中
                acc += torch.eq(predict_y, val_labels.to(device)).sum().item()

        # 计算验证准确率,用正确分类的数量除以验证集的总数
        val_accurate = acc / val_num
        # 打印训练周期结束后的训练损失和验证准确率
        print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f' %
              (epoch + 1, running_loss / train_steps, val_accurate))

        # 如果当前验证的准确率大于历史最佳准确率,则更新best_acc并保存模型参数到指定的路径save_path
        if val_accurate > best_acc:
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path)

    print('Finished Training')


if __name__ == '__main__':
    main()

 名词解释:

        1、反标准化(逆归一化)

        反标准化(De-normalization)操作是在对数据进行归一化(标准化)后,将其恢复到原始数据的操作。在机器学习和深度学习中,数据标准化是一种常见的预处理步骤,用于将数据的特征值缩放到一个较小的范围,以帮助模型的训练和优化。

        反标准化操作通常在预测阶段或结果展示阶段进行,以便将标准化后的结果转换回原始数据的范围。这是因为模型在训练过程中使用标准化后的数据进行训练,但最终的预测或展示需要在原始数据的尺度上进行。

        在图像处理中,像素值通常在0到255之间,而在训练神经网络时,可以将像素值标准化到[-1, 1]或[0, 1]的范围,以便更好地进行训练。在进行预测时,为了正确地展示图像或计算一些指标,可能需要将预测结果进行反标准化,使其回到原始像素值的范围。

        反标准化的具体方法取决于标准化时使用的方法,例如,如果在标准化时使用了均值和标准差来缩放数据,那么在反标准化时就需要使用相应的均值和标准差来还原数据。

        2、张量

        在计算机编程和数学领域,张量(Tensor)是一个广义的矩阵概念,它是一个多维数组,可以是一个标量(零维数组,即单个值)、向量(一维数组)、矩阵(二维数组)以及更高维度的数组。张量是一个非常通用的数据结构,它可以用来表示各种数据,例如图像、音频、文本和其他复杂的结构化数据。

        在深度学习和机器学习中,张量是非常重要的数据类型,因为神经网络模型的输入、输出和参数通常都是张量。PyTorch 和 TensorFlow 等深度学习框架中都有张量的概念,用于存储和处理模型的数据。

        以下是一些不同维度的张量示例:

        (1)标量(0维张量):单个值,如一个数字。

        (2)向量(1维张量):一列值,类似于列表或数组。

        (3)矩阵(2维张量):二维数组,如表格数据。

        (4)3维张量:例如彩色图像,具有高度、宽度和通道维度。

        (5)更高维度的张量:在深度学习中,常常处理具有多个特征维度的数据,如序列数据(时间序列、文本序列等)。

        在PyTorch中,张量是 torch.Tensor 类的实例,可以使用这个类创建、操作和处理张量。张量不仅可以包含数据,还可以包含用于自动求导(反向传播)的梯度信息。

七、预测代码解读(predict)

import os           # 导入操作系统相关的模块
import json         # 导入json模块,用于处理json格式的数据

import torch        # 导入torch模块,实现深度学习算法和计算
from PIL import Image           # 导入PIL模块中的Image模块,用于图像处理
from torchvision import transforms          # 导入torchvision模块中的transforms模块,用于图像的预处理
import matplotlib.pyplot as plt             # 导入matplotlib模块的pyplot模块,用于数据可视化

from model import AlexNet                   # 从model中导入AlexNet模块,图像分类


def main():
    # 判断是否有可用的GPU,如果有则将设备设置为GPU,如果没有则设置为CPU
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # 创建一个transform序列,这个序列包含以下三个操作
    # 1、将图像大小调整为224×244
    # 2、将图像数据转换为张量
    # 3、对图像的每一个通道进行归一化,将每一个通道的值从[0,1]缩放到[0.5,0.5]
    data_transform = transforms.Compose(
        [transforms.Resize((224, 224)),
         transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

    # 定义图像的路径,假设图像位于程序运行的当前目录的上一级目录中
    img_path = "../tulip.jpg"
    # 检查文件是否存在,如果不存在则抛出错误
    assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
    # 使用PIL的Image模块打开图像
    img = Image.open(img_path)

    # 使用matplotlib的pyplot模块显示原始图像
    plt.imshow(img)
    # 将图像数据转换为张量并且进行归一化处理
    # [N, C, H, W]是张量的维度,N为批量大小(这里只有一个样本),C是通道数,H是高度,W是宽度
    img = data_transform(img)
    # 在张量的维度上增加一个维度,以使其可以作为网络的输入
    img = torch.unsqueeze(img, dim=0)

    # 读取类别索引的json文件,在这里假设已经位于当前运行目录中
    json_path = './class_indices.json'
    # 判断json文件是否存在
    assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)

    # 打开json文件并加载其中的数据
    with open(json_path, "r") as f:
        class_indict = json.load(f)

    # 创建一个AlexNet模型,设置类别为5,并设置在指定的设备上运行
    model = AlexNet(num_classes=5).to(device)

    # 加载预训练权重
    weights_path = "./AlexNet.pth"
    # 判断文件是否存在
    assert os.path.exists(weights_path), "file: '{}' dose not exist.".format(weights_path)
    # 加载模型的权重到模型的state_dict中
    model.load_state_dict(torch.load(weights_path))

    # 设置模型为验证模式
    model.eval()
    # 不计算梯度,因为我们不训练,只是预测
    with torch.no_grad():
        # 将输入图像img转移到指定的设备(这里为CPU)并传递给模型model进行前向传播
        output = torch.squeeze(model(img.to(device))).cpu()
        # 使用torch.softmax函数将输出向量转换为概率分布,表示每个类别的概率
        predict = torch.softmax(output, dim=0)
        # 使用torch.argmax函数找到概率最高的类别索引,并将其转换为numpy数组
        predict_cla = torch.argmax(predict).numpy()

    print_res = "class: {}   prob: {:.3}".format(class_indict[str(predict_cla)],
                                                 predict[predict_cla].numpy())
    plt.title(print_res)
    for i in range(len(predict)):
        print("class: {:10}   prob: {:.3}".format(class_indict[str(i)],
                                                  predict[i].numpy()))
    plt.show()


if __name__ == '__main__':
    main()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸鱼翻身的路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值