前言

本文的内容是来自教程视频的第十五集,个人感觉,这个教程是有点虎头蛇尾,就是前面开始的教程,是非常惊人的好,但到这里,就有点水了,可以说就是把代码一铺,然后简单介绍一遍,很多细节都没有讲,所以,我在学习的时候,也是没办法从视频中直接学习,要先自学了代码后,再看的视频教程。

概念

前面已经解释了神经网络和卷积神经网络,本文要介绍残差网络。在学习之前,我们要先学习几个概念。

迁移学习 Transfer learning

Transfer learning 迁移学习,是旨在利用在一个或多个源任务(source tasks)上已经学习到的知识(例如模型参数、特征表示等),来帮助在目标任务(target task)上进行更高效的学习,使得目标任务能够在较少的标注数据、较短的训练时间等情况下,达到较好的学习效果。

残差网络

残差网络(Residual Network,简称 ResNet)是2015年提出的[深度卷积神经网络(CNN)架构],旨在解决深层神经网络训练中的退化问题。ResNet引入了一个非常重要的概念——残差学习(Residual Learning),使得网络能够训练非常深的网络,同时避免了传统深度网络中梯度消失或爆炸等问题。

ResNet-18

ResNet-18(Residual Network 18)是一个深度卷积神经网络(CNN),属于 ResNet(残差网络) 系列的一种,具有 18 层。它是由微软研究院(Microsoft Research)提出的,主要解决深层神经网络训练中的退化问题,并且在很多计算机视觉任务中表现非常出色。
ResNet-18 是 ResNet 系列中的一个较小的模型,它包含 18 层(包括卷积层、批归一化层、ReLU 激活函数等)。与更深的 ResNet 模型(如 ResNet-34、ResNet-50、ResNet-101 等)相比,ResNet-18 相对较小,适用于计算资源有限或者需要较快速训练的任务。
ResNet-18 的架构大致如下:
输入层:通常输入为大小为 (224, 224, 3) 的 RGB 图像(比如 ImageNet 数据集中的图像),也可以根据任务调整输入尺寸。
卷积层:ResNet-18 包含初始卷积层和多个残差块(Residual Block)。每个残差块中使用了所谓的“跳跃连接”,即输入会绕过某些卷积层,直接与某些层的输出相加,从而帮助缓解训练中的梯度消失和梯度爆炸问题。
残差连接(Skip Connection):通过引入 跳跃连接,每个残差块的输入都会与通过一系列卷积层后的输出相加,这样的设计能有效解决网络深度增加带来的性能退化问题。
全连接层:经过卷积和池化操作后的特征图会展平,并通过全连接层进行分类。

python和tensor基础学习

python字典推导式:

{key_expression: value_expression for item in iterable if condition}
 names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]

# 使用字典推导式将列表转为字典
age_dict = {names[i]: ages[i] for i in range(len(names))}

print(age_dict)

python的文件夹

在python中【../】表示向上一级目录。

ImageFolder

ImageFolder 会自动产生标签,所以可以配合DataLoader,作为入参使用。

#==========================transforms对象===================================================
# 在 ../pytorchTutorial/data/hymenoptera_data 下有两个子文件夹 train 和 val,每个文件夹中包含不同类别的图像文件。
# 目录结构如下:
# hymenoptera_data/
# ├── train/
# │   ├── ants/
# │   └── bees/
# └── val/
#     ├── ants/
#     └── bees/
# 在这种结构下,ImageFolder 会自动为 ants 和 bees 文件夹分配标签(例如,ants 可能对应标签 0,bees 对应标签 1)。
# 每个图像会被转换为一个 PIL (Python Imaging Library)图像对象,经过预处理后转化为 Tensor。

data_dir = '../pytorchTutorial/data/hymenoptera_data'# ../表示向上一级目录
#导入图片
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}

image
上图中1=0就是这个图片的标签。

DataLoader使用ImageFolder

data_dir = '../pytorchTutorial/data/hymenoptera_data'# ../表示向上一级目录
#导入图片
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}
#图片导入进dataloader  shuffle=True启用了数据洗牌
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
                                              shuffle=True, num_workers=0)
               for x in ['train', 'val']}
#取出图片的train和val文件夹图片的数量
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
 

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 标签名
class_names = image_datasets['train'].classes
# 标签值
class_to_idx = image_datasets['train'].class_to_idx  
print('标签值',class_to_idx) # {'ants': 0, 'bees': 1}
print('标签名',class_names) #  ['ants', 'bees']

print('train-dataset_sizes',dataset_sizes['train']) #train-dataset_sizes 244
print('val-dataset_sizes',dataset_sizes['val']) #val-dataset_sizes 153

模型参数

model.state_dict()是参数模型,就是权重值和偏移量,就是前面讲到的y=wx+b的w和b。

print('查看模型的参数')
model = nn.Sequential(
    nn.Linear(2, 3),
    nn.ReLU(),
    nn.Linear(3, 1)
)

# 查看模型的参数
print(model.state_dict())

# 输出将是模型每一层的权重和偏置:
# OrderedDict([('0.weight', tensor([[ 0.3356,  0.5206], [-0.4083,  0.3680], [-0.4225, -0.5568]])), 
#             ('0.bias', tensor([-0.5880, -0.6807, -0.4809])), 
#             ('2.weight', tensor([[-0.2668,  0.5349,  0.2408]])), 
#             ('2.bias', tensor([-0.3686]))])

调整张量的列顺序

print('使用tensor调整表格顺序')
#========使用tensor调整表格顺序
# 假设 images 是一个形状为 (B, C, H, W) 的图像 Tensor
# B 是批量大小(即图像的数量)。
# C 是图像的通道数(例如,RGB 图像的通道数是 3)。
# H 和 W 分别是图像的高度和宽度。
# 创建一个假设的图像数据:6 张 3 通道(RGB),28x28 大小的图像
images = torch.randn(6, 3, 28, 28)

# 将图像们排列成网格,grid_img 就是将 6 张图片拼接后的网格图像
# torchvision.utils.make_grid() 函数生成的网格图像,它的形状是 (C, H, W)
grid_img = torchvision.utils.make_grid(images)
print("grid_img.shape",grid_img.shape) # grid_img.shape torch.Size([3, 32, 182])

# 显示这个网格图像
# PyTorch 张量默认的图像格式是 (C, H, W),也就是说:通道、图像高度和宽度。
# 这里传0 2 1是在调整grid_img的3列的顺序,让(C, H, W)调整为(H, W, C),这符合 Matplotlib 显示图像时的标准要求
plt.imshow(grid_img.permute(1, 2, 0))  # 由于 grid_img 的形状是 (C, H, W),需要将其转为 (H, W, C)
plt.show()


print('使用numpy调整表格顺序')
#========使用numpy调整表格顺序
def imshow(img):
    img = img / 2 + 0.5  # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0))) # 调整tensor列顺序
    plt.show()

imshow(torchvision.utils.make_grid(images))

torch.max使用

print('torch.max使用')
#======== torch.max

# 假设模型输出 (3个样本, 4个类别)
outputs = torch.tensor([[1.2, 2.5, 0.3, 4.1],
                        [3.1, 0.2, 1.8, 2.5],
                        [0.5, 0.1, 2.2, 1.3]])

# 找到每行的最大值及其索引
_, predicted = torch.max(outputs, 1)

print("Predicted class indices:", predicted)
# Predicted class indices: tensor([3, 0, 2])

损失函数直接使用

print('损失函数直接使用')
loss = nn.CrossEntropyLoss()
Y= torch.tensor([0]) #这y是一行一列矩阵,但值0表示类别,如0=猫,1=狗,2=兔子
#nsamples x nclasses=1x3  1行3列
Y_pred_good = torch.tensor([[2.0,1.0, 0.1]]) # 这个预测的y里,2最大,2的索引是0.所以,这个预测的y最可能是猫 
l1 = loss(Y_pred_good, Y) 
print('输出损失精度的值',l1.item()) # 输出损失精度的值

torch.sum使用

print('torch.sum使用')
preds = torch.tensor([0, 1, 2, 1])  # 模型的预测
labels = torch.tensor([0, 1, 1, 1])  # 实际标签
preds == labels.data  # 返回: tensor([True, True, False, True]) 
running_corrects = torch.sum(preds == labels.data)  # 返回: 3(表示有 3 个预测正确)
print('计算当前批次的正确预测数量',running_corrects)

残差模型训练和使用

残差模型训练和使用的代码如下。

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
import copy
print('===========启动==========')
mean = np.array([0.5, 0.5, 0.5])
std = np.array([0.25, 0.25, 0.25])

data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize(mean, std)
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize(mean, std)
    ]),
}

data_dir = 'data/hymenoptera_data'
#导入图片
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}
#图片导入进dataloader
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
                                             shuffle=True, num_workers=0)
              for x in ['train', 'val']}
#取出图片的train和val文件夹下图片的数量
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}


class_names = image_datasets['train'].classes

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('标签名',class_names) #class_names ['ants', 'bees']
print('train-dataset_sizes',dataset_sizes['train']) #train-dataset_sizes 244
print('val-dataset_sizes',dataset_sizes['val']) #val-dataset_sizes 153
def imshow(inp, title):
    """Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    plt.title(title)
    plt.show()


# Get a batch of training data
#next等价于 dataiter = iter(dataloaders['train']) inputs, classes =dataiter.__next__()
inputs, classes = next(iter(dataloaders['train'])) 
print("inputs.shape",inputs.shape)
# Make a grid from batch
out = torchvision.utils.make_grid(inputs)

imshow(out, title=[class_names[x] for x in classes])

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    since = time.time()
    # model.state_dict()返回的是 模型的参数(权重和偏置) 的集合
    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0

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

        # Each epoch has a training and validation phase
        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()  # Set model to training mode resnet18的模型开启【训练】模式
            else:
                model.eval()   # Set model to evaluate mode resnet18的模型开启【评估】模式

            running_loss = 0.0
            running_corrects = 0

            # Iterate over data.
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)
                print("inputs.shape",inputs.shape) # inputs.shape torch.Size([4, 3, 224, 224])
             
                # track history if only in train 
                # set_grad_enabled是个类,不是函数,构造函数入参是ture,所以使用phase == 'train'传参
                # 用途是开启梯度计算,是通过改变全局的tensor的requires_grad属性来实现的
                with torch.set_grad_enabled(phase == 'train'):
                    # forward——前向传播
                    outputs = model(inputs)
                    print("outputs.shape",outputs.shape)
                    # 设置model的最后的全连接层是model.fc = nn.Linear(num_ftrs, 2),outputs输出的2列
                    # outputs输出的2列是 logits(原始得分),它是指经过神经网络最后一层计算后得到的值,但这些值并没有经过任何激活函数(如 Softmax 或 Sigmoid)的归一化处理。 
                    # torch.max(outputs, 1) 会在 outputs 的每一行中找到最大值的索引。然后组层新的张量 
                    _, preds = torch.max(outputs, 1)
                    print("predictions.shape", preds.shape) #predictions.shape torch.Size([4]),因为1批是4个,所以这个是个4行1列的数组,没一行代表logits(原始得分)最高的索引,即他是哪个类别
                    
                    loss = criterion(outputs, labels)

                    # backward + optimize only if in training phase
                    if phase == 'train':
                        optimizer.zero_grad()
                        loss.backward()
                        optimizer.step()
                
                # loss.item()=损失值 
                # inputs.size(0)返回inputs 的第一个维度的大小,这里inputs.size(0)=4,因为dataloaders定义的时候batch=4,所以使用next取值的时候,一组(一批)就是4个(图片)
                # running_loss=批次的总损失
                running_loss += loss.item() * inputs.size(0)
                print('计算批次的总损失',running_loss)
                # 计算当前批次的正确预测数量
                running_corrects += torch.sum(preds == labels.data)
                print('计算当前批次的正确预测数量',running_corrects)
            if phase == 'train':
                scheduler.step()
                
            # epoch_loss=训练模型中的平均损失,epoch_loss反映了模型在整个训练或验证阶段的学习效果。通常,训练过程中的损失应该逐渐减小,表明模型在逐渐学习并减少错误。
            # dataset_sizes[phase]=样本总数(train的dataset_sizes=244,val的dataset_sizes=153)
            epoch_loss = running_loss / dataset_sizes[phase]  # 平均损失
            # 准确率越高,表示模型在分类任务中的表现越好
            epoch_acc = running_corrects.double() / dataset_sizes[phase] # 平均准确率

            print('{} Loss: {:.4f} Acc: {:.4f}'.format( phase, epoch_loss, epoch_acc))

            # 当验证准确率超过历史最优时,更新权重和偏置
            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())

        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))

    # load best model weights
    # 重新导入权重和偏置
    model.load_state_dict(best_model_wts)
    return model

# ==============================================================================================================================
# 完整地加载了预训练的 ResNet-18,并且 所有的参数都是可训练的。
#### Finetuning the convnet ####
# Load a pretrained model and reset final fully connected layer.

# 创建一个预训ResNet-18 模型  ImageNet 数据集 上预训练过的 ResNet-18 模型 
# ResNet-18 在 ImageNet 数据集上进行了训练,ImageNet 是一个包含 1000 个类别、约 120 万张图像的大型视觉分类数据集。(Microsoft Research 的研究团队训练的)
model = models.resnet18(pretrained=True)
#fc 是最后一个全连接层(Fully Connected Layer)。
#对于 ResNet-18 来说,fc 层的输入特征数是 512,这是因为在 ResNet-18 中,最后一个卷积层的输出经过全局平均池化(Global Average Pooling)后,输出了一个大小为 512 的特征向量。因此,in_features 是 512。

num_ftrs = model.fc.in_features 
# Here the size of each output sample is set to 2.
# Alternatively, it can be generalized to nn.Linear(num_ftrs, len(class_names)).
model.fc = nn.Linear(num_ftrs, 2)
# *** 上面几行是把resnet-18里最后一个全连接层重新定义一下,因为原来的输出列数,不是我们需要的,我们就需要蜜蜂和蚂蚁俩类别,所以,我们输出是2,输入我们不知道,所以要先取出来最后一个全连接层,然后读取他的输入列。

model = model.to(device) #移动到设备上计算,有gpu用gpu计算,没有用cpu计算

criterion = nn.CrossEntropyLoss() #交叉熵损失函数

# Observe that all parameters are being optimized
optimizer = optim.SGD(model.parameters(), lr=0.001)

# StepLR Decays the learning rate of each parameter group by gamma every step_size epochs
# Decay LR by a factor of 0.1 every 7 epochs
# Learning rate scheduling should be applied after optimizer’s update
# e.g., you should write your code this way:
# for epoch in range(100):
#     train(...)
#     validate(...)
#     scheduler.step()


# lr_scheduler.StepLR:是 PyTorch 中的一种学习率调度器,它可以在训练过程中动态调整学习率。
# optimizer:传入需要调节学习率的优化器。
# step_size=7:每训练 7 个 epoch 后,学习率将被调整。
# gamma=0.1:每次调整时,学习率将乘以 0.1,即每 7 个 epoch 后,学习率减少 10%。
# 就是在优化七次后,在将学习率乘以0.1
step_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)

# train_model是个函数,内部会读取图片训练

model = train_model(model, criterion, optimizer, step_lr_scheduler, num_epochs=25)

# ==============================================================================================================================
# 冻结卷积层通常是因为你希望利用 预训练的卷积特征(已经从大数据集上训练过),而只希望通过训练 全连接层 来适应你的特定任务。
#### ConvNet as fixed feature extractor ####
# Here, we need to freeze all the network except the final layer.
# We need to set requires_grad == False to freeze the parameters so that the gradients are not computed in backward()
model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
    param.requires_grad = False

# Parameters of newly constructed modules have requires_grad=True by default
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs, 2)

model_conv = model_conv.to(device)

criterion = nn.CrossEntropyLoss()

# Observe that only parameters of final layer are being optimized as
# opposed to before.
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)

model_conv = train_model(model_conv, criterion, optimizer_conv,
                         exp_lr_scheduler, num_epochs=25)

这段代码还是比较难理解,不过如果理解了,那就基本上证明,前面的神经网络和卷积神经网络你都掌握了。


这样我们迁移学习——残差网络就学完了。


传送门:
零基础学习人工智能—Python—Pytorch学习—全集


注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!



若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

https://www.cnblogs.com/kiba/p/18572640

标签: none

添加新评论