mac深度学习pytorch:ResNet50网络图像分类篇——花数据集图像分类(5类)保姆级详细过程及可视化

原代码及数据集下载:【度学习pytorch实战六:ResNet50网络图像分类篇自建花数据集图像分类(5类)超详细代码 -  CSDN App】/windows度学习pytorch实战六:ResNet50网络图像分类篇自建花数据集图像分类(5类)超详细代码_resnet50图像分类-CSDN博客

1.数据集

下载数据集至本地(点击原文链接进行下载)

2.代码

2.1数据预处理

**windows:**
import os
from shutil import copy
import random

def mkfile(file):
    if not os.path.exists(file):
        os.makedirs(file)

# 获取 photos 文件夹下除 .txt 文件以外所有文件夹名(即3种分类的类名)
file_path = 'yourdataroad/flower_photos'
flower_class = [cla for cla in os.listdir(file_path) if ".txt" not in cla]

# 创建 训练集train 文件夹,并由3种类名在其目录下创建3个子目录
mkfile('flower_data/train')
for cla in flower_class:
    mkfile('flower_data/train/' + cla)

# 创建 验证集val 文件夹,并由3种类名在其目录下创建3个子目录
mkfile('flower_data/val')
for cla in flower_class:
    mkfile('flower_data/val/' + cla)

# 划分比例,训练集 : 验证集 = 9 : 1
split_rate = 0.1

# 遍历3种花的全部图像并按比例分成训练集和验证集
for cla in flower_class:
    cla_path = file_path + '/' + cla + '/'  # 某一类别动作的子目录
    images = os.listdir(cla_path)  # iamges 列表存储了该目录下所有图像的名称
    num = len(images)
    eval_index = random.sample(images, k=int(num * split_rate))  # 从images列表中随机抽取 k 个图像名称
    for index, image in enumerate(images):
        # eval_index 中保存验证集val的图像名称
        if image in eval_index:
            image_path = cla_path + image
            new_path = 'flower_data/val/' + cla
            copy(image_path, new_path)  # 将选中的图像复制到新路径

        # 其余的图像保存在训练集train中
        else:
            image_path = cla_path + image
            new_path = 'flower_data/train/' + cla
            copy(image_path, new_path)
        print("\\r[{}] processing [{}/{}]".format(cla, index + 1, num), end="")  # processing bar
    print()

print("processing done!")

mac设备报错:

 File "/*/pytorch/resnet/data预处理.py", line 32, in <module>
    images = os.listdir(cla_path)  # iamges 列表存储了该目录下所有图像的名称
NotADirectoryError: [Errno 20] Not a directory: '/yourdataroad/flower_data/.DS_Store/'

错误信息表明在尝试列出目录内容时,cla_path 指向了一个不是目录的文件,具体来说是 /yourdataroad/flower_data/.DS_Store/.DS_Store 文件是 macOS 系统用来存储文件夹的自定义属性的隐藏文件,例如文件的图标位置和背景色等,它不是一个目录。

要修正这个错误,你需要在获取目录列表时排除 .DS_Store 文件

**macos**
import os
from shutil import copy
import random

def mkfile(file):
    if not os.path.exists(file):
        os.makedirs(file)

# 获取 photos 文件夹下除 .txt 文件以外所有文件夹名(即3种分类的类名)
file_path = '/yourdataroad/flower_data'
flower_class = [cla for cla in os.listdir(file_path) if os.path.isdir(os.path.join(file_path, cla)) and ".txt" not in cla]

# 创建 训练集train 文件夹,并由3种类名在其目录下创建3个子目录
mkfile('flower_data/train')
for cla in flower_class:
    mkfile('flower_data/train/' + cla)

# 创建 验证集val 文件夹,并由3种类名在其目录下创建3个子目录
mkfile('flower_data/val')
for cla in flower_class:
    mkfile('flower_data/val/' + cla)

# 划分比例,训练集 : 验证集 = 9 : 1
split_rate = 0.1

# 遍历3种花的全部图像并按比例分成训练集和验证集
for cla in flower_class:
    cla_path = os.path.join(file_path, cla)  # 某一类别动作的子目录
    images = [img for img in os.listdir(cla_path) if os.path.isfile(os.path.join(cla_path, img))]  # 确保是文件
    num = len(images)
    eval_index = random.sample(images, k=int(num * split_rate))  # 从images列表中随机抽取 k 个图像名称
    for index, image in enumerate(images):
        # eval_index 中保存验证集val的图像名称
        if image in eval_index:
            image_path = os.path.join(cla_path, image)
            new_path = os.path.join('flower_data/val', cla)
            copy(image_path, new_path)  # 将选中的图像复制到新路径

        # 其余的图像保存在训练集train中
        else:
            image_path = os.path.join(cla_path, image)
            new_path = os.path.join('flower_data/train', cla)
            copy(image_path, new_path)
        print("\\r[{}] processing [{}/{}]".format(cla, index + 1, num), end="")  # processing bar
    print()

print("processing done!")

代码运行结果:

2.2训练模型

假设你已知restnet网络原理,不知道也没关系。以下是在macos上运行的完整代码,windows或其他版本可以去看本文开头链接。

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms, models
import numpy as np
import matplotlib.pyplot as plt
import os

# 定义数据转换
data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

# 加载数据集
data_dir = '/Users/xiaqizai/Downloads/flower_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}
dataloaders = {x: DataLoader(image_datasets[x], batch_size=4,
                             shuffle=True, num_workers=4)
               for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes

# 加载预训练的ResNet-50模型
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)

# 替换最后的全连接层以适配我们的分类问题
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, len(class_names))

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

# 训练模型
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)

from tqdm import tqdm

def train_model(model, criterion, optimizer, num_epochs=25):
    # 初始化字典来存储每个epoch的损失和准确率
    epoch_losses = {'train': [], 'val': []}
    epoch_accs = {'train': [], 'val': []}

    # 开始训练
    for epoch in range(num_epochs):
        print(f'Epoch {epoch}/{num_epochs - 1}')
        print('-' * 10)

        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()  # 设置模型为训练模式
            else:
                model.eval()  # 设置模型为评估模式

            running_loss = 0.0
            running_corrects = 0

            # 使用tqdm包装数据加载器
            data_loader = tqdm(dataloaders[phase], desc=f'Epoch {epoch+1}/{num_epochs} - {phase}', leave=True)
            # 遍历数据
            for inputs, labels in data_loader:
                inputs = inputs.to(device)
                labels = labels.to(device)

                # 清零参数梯度
                optimizer.zero_grad()

                # 前向传播
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)

                    # 反向传播和优化
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

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

                # 更新进度条的后缀,显示损失和准确率
                data_loader.set_postfix(loss=running_loss / len(dataloaders[phase]), accuracy=running_corrects.double() / len(dataloaders[phase]))

            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]

            epoch_losses[phase].append(epoch_loss)
            epoch_accs[phase].append(epoch_acc)

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

    print('Training complete')
    return epoch_losses, epoch_accs

# 可视化一些预测结果
def visualize_model(model, num_images=6):
    was_training = model.training
    model.eval()
    images_so_far = 0
    fig = plt.figure()

    with torch.no_grad():
        for i, (inputs, labels) in enumerate(dataloaders['val']):
            inputs = inputs.to(device)
            labels = labels.to(device)

            outputs = model(inputs)
            _, preds = torch.max(outputs, 1)

            for j in range(inputs.size()[0]):
                images_so_far += 1
                ax = plt.subplot(num_images // 2, 2, images_so_far)
                ax.axis('off')
                ax.set_title('predicted: {}'.format(class_names[preds[j]]))
                imshow(inputs.cpu().data[j])

                if images_so_far == num_images:
                    model.train(mode=was_training)
                    return
        model.train(mode=was_training)

def imshow(inp, title=None):
    """Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # pause a bit so that plots are updated

# 可视化训练和验证损失和准确率
def visualize_training(epoch_losses, epoch_accs):
    plt.figure(figsize=(12, 5))
    plt.subplot(1, 2, 1)
    plt.plot(epoch_losses['train'], label='Training Loss')
    plt.plot(epoch_losses['val'], label='Validation Loss')
    plt.title('Training and Validation Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()

    plt.subplot(1, 2, 2)
    plt.plot(epoch_accs['train'], label='Training Accuracy')
    plt.plot(epoch_accs['val'], label='Validation Accuracy')
    plt.title('Training and Validation Accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()

    plt.tight_layout()
    plt.show()

# 在 if __name__ == '__main__': 块中运行训练和可视化:
if __name__ == '__main__':
    num_epochs = 10
    epoch_losses, epoch_accs = train_model(model, criterion, optimizer, num_epochs=num_epochs)
    print(epoch_losses)
    visualize_model(model)
    visualize_training(epoch_losses, epoch_accs)

plt.ioff()
plt.show()

代码运行结果:

2.2.1导入必要的库 

1.import torch

2.import torch.nn as nn

3.import torch.optim as optim

4.from torch.utils.data import DataLoader

5.from torchvision import datasets, transforms, models

6.import numpy as np

7.import matplotlib.pyplot as plt

8.import os

1•导入PyTorch库,这是一个开源的机器学习库,广泛用于计算机视觉和自然语言处理

2•从PyTorch库中导入nn模块,这个模块包含了构建神经网络所需的类和函数。

3•从PyTorch库中导入optim模块,这个模块包含了多种优化算法,用于训练神经网络时更新权重。

4•从PyTorch的utils.data模块中导入DataLoader类,它用于加载数据集,支持自动批处理、打乱数据顺序和多线程加载。

5•从torchvision库中导入datasetstransformsmodels模块。datasets用于加载和处理数据集,transforms用于数据预处理,models包含了预训练的模型和构建模型的组件。

6•导入NumPy库,这是一个用于科学计算的库,提供了大量的数学函数和对多维数组的支持。

7•导入matplotlib.pyplot模块,这是一个用于绘制图表的库,常用于数据可视化。

8•导入os模块,这个模块提供了与操作系统交互的功能,如文件和目录操作。

这些导入语句是深度学习项目中常见的,它们为后续的数据处理、模型构建、训练和可视化提供了必要的工具和函数

2.2.2定义数据转换

处理图像尺寸,转换数据类型

1.data_transforms = {
    'train': transforms.Compose([

      2.transforms.RandomResizedCrop(224),

      3.transforms.RandomHorizontalFlip(),

      4.transforms.ToTensor(),

      5.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([

      6.transforms.Resize(256),

      7.transforms.CenterCrop(224),

      8.transforms.ToTensor(),

      9.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

1• data_transforms是一个字典,它有两个键:'train''val',分别对应训练集和验证集的数据转换流程。

对于训练集('train'):

2• 随机裁剪图像,然后调整大小到224x224像素。这种转换有助于模型学习从不同位置和大小的图像中识别特征,增加模型的泛化能力。

3• 以一定的概率随机水平翻转图像。这也是一种数据增强技术,可以提高模型对图像翻转的鲁棒性。

4• 将PIL图像或NumPy数组转换为torch.Tensor,并将图像像素值从[0, 255]缩放到[0, 1]。

5• 归一化图像。使用ImageNet数据集的均值和标准差对图像的每个通道进行标准化。这有助于模型更快地收敛。

对于验证集('val'):

6• 将图像大小调整为256x256像素。这是因为验证集的图像将被裁剪到中心的224x224像素区域,首先调整大小可以确保裁剪后的图像不会失真。

7• 从图像的中心裁剪224x224像素的区域。这与训练集的随机裁剪不同,验证集使用固定的中心裁剪,以确保评估时的一致性。

8• 与训练集相同,将图像转换为torch.Tensor并缩放像素值。

9• 与训练集相同,使用相同的均值和标准差进行归一化。

训练集和验证集的数据转换流程被定义为两个不同的流程,以适应不同的处理需求。这种区分有助于提高模型的性能和泛化能力。

2.2.3加载和准备图像数据集

你可以在这里导入你的数据

1.data_dir = '/yourdataroad/flower_data'

2.image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}

3.dataloaders = {x: DataLoader(image_datasets[x], batch_size=4,
                             shuffle=True, num_workers=4)
               for x in ['train', 'val']}

4.dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}

5.class_names = image_datasets['train'].classes

1• 定义了一个变量data_dir,它存储了数据集的路径。这个路径是相对于当前工作目录的,指向存放训练和验证数据集的文件夹。

2• 创建一个名为image_datasets的字典,它将包含两个键:'train''val'。对于每个键,使用datasets.ImageFolder创建一个数据集对象。ImageFolder是PyTorch中的一个类,用于从文件夹中加载图像数据。os.path.join(data_dir, x)构造了每个数据集的完整路径。data_transforms[x]应用了之前定义的数据转换流程。

3• 创建一个名为dataloaders的字典,它将包含两个键:'train''val'。对于每个键,使用DataLoader创建一个数据加载器对象。DataLoader是PyTorch中用于封装数据集并提供批量加载、打乱和多线程加载等功能的类。这里设置了批量大小为4(batch_size=4),数据在每个epoch开始时打乱(shuffle=True),并使用4个工作线程来并行加载数据(num_workers=4)。

4• 创建一个名为dataset_sizes的字典,它将包含两个键:'train''val'。对于每个键,计算对应数据集的大小(即图像的数量)。

5• 从训练数据集中获取类别名称,并将其存储在变量class_names中。这通常用于后续的模型评估和结果可视化,以便于将类别索引映射回类别名称。

设置数据管道,使得数据可以被模型在训练和验证过程中使用。确保data_dir变量中的路径正确,并且该路径下有按照PyTorch ImageFolder预期的格式组织的图像数据集。

2.2.4加载预训练的Resnet-50模型

model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
  • models:这是从torchvision.models导入的模块,它包含了多种预训练的神经网络模型,如ResNet、VGG、AlexNet等。
  • resnet50():这是models模块中的一个函数,用于创建一个ResNet-50模型的实例。ResNet-50是一种深度残差网络,它在计算机视觉任务中表现优异,特别是在图像分类任务中。
  • weights=models.ResNet50_Weights.IMAGENET1K_V1:这是传递给resnet50()函数的一个参数,指定了模型的预训练权重。models.ResNet50_Weights.IMAGENET1K_V1是一个枚举成员,它指向在ImageNet数据集上预训练的ResNet-50模型的权重。ImageNet是一个大型的图像数据库,广泛用于视觉对象识别软件研究。IMAGENET1K_V1表示这是在ImageNet 1K数据集上训练的第一个版本的权重。

加载预训练模型的好处是,你可以利用在大规模数据集上训练得到的模型权重来提高模型的性能,特别是在数据量较少的情况下。此外,预训练模型通常可以更快地收敛,并且可以在迁移学习中作为特征提取器使用。

在实际应用中,你可能会根据自己的任务需求对模型的最后几层进行修改,以适应新的分类任务。例如,如果原始的ResNet-50模型是为1000类ImageNet任务训练的,而你的任务只有10个类别,你可能需要替换最后的全连接层,使其输出10个类别的预测。这种修改通常涉及到调整模型的fc(全连接层)层,以匹配新的类别数量。(见2.2.5)

2.2.5 transferlearning微调

*替换最后的全连接层以适配我们的分类问题*

1.num_ftrs = model.fc.in_features

2.model.fc = nn.Linear(num_ftrs, len(class_names))

利用在大型数据集(如ImageNet)上预训练的模型的知识,以提高在新任务上的性能。

1• model.fc:这是ResNet-50模型中的全连接层(也称为最后的分类层)。在原始的ResNet-50模型中,这个全连接层通常用于输出1000个类别的预测,对应于ImageNet数据集的类别数。

  • in_features:这是全连接层的一个属性,表示该层输入特征的数量。在ResNet-50模型中,这个值通常是2048。
  • num_ftrs:这是一个变量,用于存储model.fc.in_features的值。这个值将用于定义新的全连接层,以匹配你的特定任务。

2• nn.Linear:这是PyTorch中的一个类,用于创建一个全连接层。它的两个参数分别是输入特征的数量和输出特征的数量。

  • num_ftrs:这是新全连接层的输入特征数量,与原始全连接层的输入特征数量相同。
  • len(class_names):这是新全连接层的输出特征数量,等于你的数据集中的类别数。class_names是一个列表,包含了所有类别的名称,len(class_names)计算了类别的总数。
  • model.fc:这行代码将原始的全连接层替换为新的全连接层。新的全连接层将输出与你的数据集类别数相匹配的预测。

2.2.6定义损失函数和优化器

1.criterion = nn.CrossEntropyLoss()

2.optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

随机梯度下降SGD

动量法

1• nn.CrossEntropyLoss():这是PyTorch中的一个损失函数类,用于多分类问题。它计算输入(模型预测)和目标(真实标签)之间的交叉熵损失。交叉熵损失是评估模型预测与实际标签之间差异的常用方法,特别适用于分类任务。

  • criterion:这是一个变量,用于存储nn.CrossEntropyLoss()实例。在模型训练过程中,这个损失函数将被用来计算预测和实际标签之间的误差,以便进行梯度下降。

2• optim.SGD:这是PyTorch中的一个优化器类,代表随机梯度下降(Stochastic Gradient Descent)。SGD是一种优化算法,用于调整模型的参数以最小化损失函数。

  • model.parameters():这是一个生成器,它提供了模型中所有可训练的参数(权重和偏置)。
  • lr=0.001:这是学习率的设置,它决定了在梯度下降过程中参数更新的步长。较小的学习率可能导致训练速度较慢,但有助于找到更精确的最小值;较大的学习率可能导致训练不稳定,甚至发散。
  • momentum=0.9:这是动量的设置,它是一种加速梯度下降的技术,可以帮助优化器在相关方向上加速收敛,并在不相关方向上抑制振荡。动量项利用了之前梯度的信息,以平滑梯度的波动。
  • optimizer:这是一个变量,用于存储optim.SGD实例。在模型训练过程中,这个优化器将根据损失函数计算的梯度来更新模型的参数。

2.2.7训练模型

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

2.model = model.to(device)

3.from tqdm import tqdm

4.def train_model(model, criterion, optimizer, num_epochs=25):
   

 # 初始化字典来存储每个epoch的损失和准确率
  5.epoch_losses = {'train': [], 'val': []}
    epoch_accs = {'train': [], 'val': []}

    # 开始训练
  6.for epoch in range(num_epochs):
        print(f'Epoch {epoch}/{num_epochs - 1}')
        print('-' * 10)

      7.for phase in ['train', 'val']:

          8.if phase == 'train':
                model.train()  # 设置模型为训练模式
            else:
                model.eval()  # 设置模型为评估模式

          9.running_loss = 0.0
            running_corrects = 0

            # 使用tqdm包装数据加载器
         10.data_loader = tqdm(dataloaders[phase], desc=f'Epoch {epoch+1}/{num_epochs} - {phase}', leave=True)
            # 遍历数据

         11.for inputs, labels in data_loader:

             12.inputs = inputs.to(device)
                labels = labels.to(device)

                # 清零参数梯度
             13.optimizer.zero_grad()

                # 前向传播
             14.with torch.set_grad_enabled(phase == 'train'):

                 15.outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)

                    # 反向传播和优化
                 16.if phase == 'train':
                        loss.backward()
                        optimizer.step()

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

                # 更新进度条的后缀,显示损失和准确率
             18.data_loader.set_postfix(loss=running_loss / len(dataloaders[phase]), accuracy=running_corrects.double() / len(dataloaders[phase]))

         19.epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]

         20.epoch_losses[phase].append(epoch_loss)

         21.epoch_accs[phase].append(epoch_acc)

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

 23.print('Training complete')

 24.return epoch_losses, epoch_accs

1•这行代码创建了一个 torch.device 对象,用于指定模型和数据应该在哪个设备上运行。如果系统中有可用的 GPU,device 将被设置为 "cuda:0",否则将使用 CPU。

2•这行代码将模型移动到上面定义的设备(GPU或CPU)上。

3•导入 tqdm 库,这是一个快速、可扩展的 Python 进度条库,用于在命令行界面或 Jupyter 笔记本中显示循环的进度。

4• 定义了一个名为 train_model 的函数,它接受模型、损失函数、优化器和训练周期数作为参数。

5•初始化两个字典,用于存储每个训练周期的训练和验证损失以及准确率。

6•开始训练循环,循环次数由 num_epochs 参数指定。

7•对于每个训练周期,分别对训练集和验证集进行迭代。

8•根据当前阶段(训练或验证),将模型设置为相应的模式。model.train() 启用模型的训练模式,启用 Dropout 等;model.eval() 将模型设置为评估模式,禁用 Dropout。

9•初始化用于累积损失和正确预测数的变量。

10•使用 tqdm 包装数据加载器,以便在训练或验证时显示进度条。

11•遍历当前阶段的数据集。

12•将输入数据和标签移动到指定的设备上。

13•在每次迭代开始时,清除优化器中的梯度信息。

14•一个上下文管理器,用于根据当前阶段启用或禁用梯度计算。

15•进行前向传播,计算模型的输出,然后找到预测类别,并计算损失。

16•如果当前阶段是训练阶段,则进行反向传播计算梯度,并更新模型的参数。

17•累积损失和正确预测的数量。

18•更新进度条的后缀,打印当前阶段的损失和准确率。

19•计算整个训练周期的平均损失和准确率。

20•epoch_losses 是一个字典,它的键是 'train' 和 'val',分别对应训练和验证阶段。 phase 是一个变量,它的值在 'train' 和 'val' 之间切换,表示当前正在处理的是训练集还是验证集。epoch_loss 是一个变量,存储了当前周期内计算出的平均损失值。 append 方法用于将 epoch_loss 添加到 epoch_losses 字典中对应 phase 键的列表末尾。这样,随着训练的进行,每个周期的损失值都会被记录下来。

21•epoch_accs 是一个字典,它的结构与 epoch_losses 相同,也是用于记录训练和验证阶段的性能指标。epoch_acc 是一个变量,存储了当前周期内计算出的平均准确率值。 append 方法用于将 epoch_acc 添加到 epoch_accs 字典中对应 phase 键的列表末尾。这样,随着训练的进行,每个周期的准确率值都会被记录下来。

22• 打印当前阶段的损失和准确率。

23•训练完成后打印消息。

24•return 关键字用于将函数的输出结果返回给调用者。epoch_losses 和 epoch_accs 是在函数内部定义的两个字典,它们分别存储了每个训练周期(epoch)的训练和验证损失以及准确率。 当函数被调用时,这两个字典将被返回给调用者,调用者可以进一步使用这些数据,例如进行分析、绘图或保存到文件中。

这个函数是训练神经网络的核心,它处理了训练和验证过程,并在每个周期结束时输出性能指标。在实际使用时,你需要调用这个函数并传入之前定义的模型、损失函数、优化器以及训练周期数。

20.21 这两行代码的目的是为了在训练结束后,能够分析模型在训练和验证过程中的性能变化。通过查看这些记录的损失和准确率,可以判断模型是否正在学习(损失是否在减少),是否出现过拟合(训练损失在减少而验证损失在增加),以及模型的总体性能如何。这些信息对于调整模型参数、改进模型结构和训练策略都是非常有用的。

2.2.8可视化预测结果

1.def visualize_model(model, num_images=6):

  2.was_training = model.training

  3.model.eval()

  4.images_so_far = 0

  5.fig = plt.figure()

  6.with torch.no_grad():

      7.for i, (inputs, labels) in enumerate(dataloaders['val']):

          8.inputs = inputs.to(device)
            labels = labels.to(device)

          9.outputs = model(inputs)

         10._, preds = torch.max(outputs, 1)

         11.for j in range(inputs.size()[0]):

             12.images_so_far += 1

             13.ax = plt.subplot(num_images // 2, 2, images_so_far)

             14.ax.axis('off')

             15.ax.set_title('predicted: {}'.format(class_names[preds[j]]))

             16.imshow(inputs.cpu().data[j])

             17.if images_so_far == num_images:
                    model.train(mode=was_training)
                    return

     18.model.train(mode=was_training)

1• 定义了一个函数 visualize_model,它接受一个模型和一个可选参数 num_images,后者指定了要可视化的图像数量,默认值为6。

2• 记录模型当前的训练状态(True 或 False),以便在函数结束时恢复模型的原始状态。

3• 将模型设置为评估模式,这会禁用 Dropout 和 Batch Normalization 等在训练时使用的特定层的行为。

4• 初始化一个计数器,用于跟踪已经可视化的图像数量。

5• 创建一个新的 Matplotlib 图像。

6• 使用 torch.no_grad() 上下文管理器,以避免在预测时计算梯度,这有助于减少内存消耗并加速计算。

7• 遍历验证数据加载器中的批次。

8• 将输入数据和标签移动到指定的设备(GPU或CPU)。

9• 使用模型对输入数据进行预测。

10• 从模型输出中获取预测结果。torch.max 返回每个样本最高概率的预测类别的索引。

11• 遍历当前批次中的每个图像。

12• 更新已可视化图像的计数器。

13• 创建一个子图,用于显示图像和预测结果。

14• 关闭子图的坐标轴。

15• 设置子图的标题,显示模型对当前图像的预测类别。

16• 使用 imshow 函数显示图像。注意,这里假设你已经定义了一个名为 imshow 的函数,用于将 Tensor 转换为图像并显示。

17• 如果已经可视化了指定数量的图像,则恢复模型的训练状态(如果之前是训练状态)并返回。

18• 在遍历完所有验证图像后,无论是否达到了指定数量,都恢复模型的原始训练状态。

这个函数的目的是直观地展示模型在验证数据集上的表现,通过显示图像及其预测类别,可以帮助理解模型的行为和性能。

将 PyTorch Tensor 转换为图像并显示:

1.def imshow(inp, title=None):

  2."""Imshow for Tensor."""

  3.inp = inp.numpy().transpose((1, 2, 0))

  4.mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])

  5.inp = std * inp + mean

  6.inp = np.clip(inp, 0, 1)

  7.plt.imshow(inp)

  8.if title is not None:
        plt.title(title)

  9.plt.pause(0.001)  # pause a bit so that plots are update

1• 定义了一个函数 imshow,它接受一个输入参数 inp(一个图像 Tensor)和一个可选的标题参数 title

2• 这是一个多行字符串,用作函数的文档字符串,说明这个函数是用于 Tensor 的图像显示函数。

3• 将输入的 Tensor 转换为 NumPy 数组,并使用 transpose 方法将通道顺序从 CHW(通道、高度、宽度)转换为 HWC(高度、宽度、通道),因为 Matplotlib 期望图像数据是以 HWC 格式提供的。

4• 定义了两个 NumPy 数组 mean 和 std,分别存储了用于图像归一化的均值和标准差。这些值通常与用于数据预处理时的均值和标准差相同,用于将图像数据恢复到原始的 [0, 1] 范围。

5• 使用广播机制将归一化的图像数据恢复到原始的像素值。这是通过将每个通道的数据乘以标准差然后加上均值来实现的。

6• 使用 np.clip 函数确保图像数据在 [0, 1] 范围内。这是必要的,因为像素值必须在这个范围内才能正确显示。

7• 使用 Matplotlib 的 imshow 函数显示处理后的图像数据。

8• 如果提供了标题参数,使用 Matplotlib 的 title 函数为图像设置标题。

9• 调用 plt.pause 函数,暂停一段时间(0.001秒),这允许 Matplotlib 更新显示的图像。这在交互式环境中非常有用,例如 Jupyter Notebook,但在脚本中通常不需要。

绘制训练和验证过程中损失和准确率的变化

1.def visualize_training(epoch_losses, epoch_accs):

  2.plt.figure(figsize=(12, 5))

  3.plt.subplot(1, 2, 1)

  4.plt.plot(epoch_losses['train'], label='Training Loss')
    plt.plot(epoch_losses['val'], label='Validation Loss')

  5.plt.title('Training and Validation Loss')
  6.plt.xlabel('Epochs')
  7.plt.ylabel('Loss')

  8.plt.legend()

  9.plt.subplot(1, 2, 2)

 10.plt.plot(epoch_accs['train'], label='Training Accuracy')
    plt.plot(epoch_accs['val'], label='Validation Accuracy')

 11.plt.title('Training and Validation Accuracy')

 12.plt.xlabel('Epochs')
 13.plt.ylabel('Accuracy')

 14.plt.legend()

 15.plt.tight_layout()

 16.plt.show()

1• 定义了一个函数 visualize_training,它接受两个参数:epoch_losses 和 epoch_accs。这两个参数分别存储了训练过程中每个周期的训练和验证损失以及准确率。

2• 创建一个新的 Matplotlib 图像,并设置图像的大小为宽12英寸、高5英寸。

3• 在图像中创建一个子图,布局为1行2列,这是第一个子图。

4• 使用 plt.plot 绘制训练损失和验证损失。label 参数用于设置图例的标签。

5• 设置子图的标题。

6• 设置子图的 x 轴标签。

7• 设置子图的 y 轴标签。

8• 显示图例,图例中的条目由 label 参数指定。

9• 在图像中创建另一个子图,布局为1行2列,这是第二个子图。

10• 使用 plt.plot 绘制训练准确率和验证准确率。

11• 设置子图的标题。

12• 设置子图的 x 轴标签。

13• 设置子图的 y 轴标签。

14• 显示图例。

15• 使用 plt.tight_layout 自动调整子图参数,以确保子图之间有充足的空间,并且标签、标题等不会重叠。

16• 使用 plt.show 显示图像。这将打开一个窗口,显示绘制的图表。

这个函数通过绘制损失和准确率的变化曲线,帮助监控模型的训练过程,检查是否存在过拟合(例如,训损失持续下降而验证损失上升)或欠拟合(训练和验证损失都很高)。这种可视化是调整模型参数和训练策略的重要工具。

2.2.9在*if __name__ == '__main__'*: 块中运行训练和可视化:

1.if __name__ == '__main__':

  2.num_epochs = 10

  3.epoch_losses, epoch_accs = train_model(model, criterion, optimizer, num_epochs=num_epochs)

  4.visualize_model(model)

  5.visualize_training(epoch_losses, epoch_accs)

6.plt.ioff()

7.plt.show()

1• 这是一个常用的Python模式,用于判断当前脚本是否作为主程序运行。如果是,那么在该条件下的代码块将被执行。如果脚本是被其他脚本作为模块导入的,则不会执行这部分代码。

2• 设置训练周期的数量为10。这意味着模型将完整地遍历数据集10次。

3• 调用之前定义的 train_model 函数,并传入模型、损失函数、优化器以及训练周期数。函数返回两个字典:epoch_losses 存储每个周期的训练和验证损失,epoch_accs 存储每个周期的训练和验证准确率。

4• 调用之前定义的 visualize_model 函数,用于可视化模型在验证数据集上的预测结果。

5• 调用之前定义的 visualize_training 函数,用于绘制训练和验证过程中损失和准确率的变化曲线。

6• 这行代码通常放在脚本的末尾,用于关闭 Matplotlib 的交互模式。在交互模式下,每次调用 plt.show() 之前都需要调用 plt.pause()。关闭交互模式后,可以直接调用 plt.show() 来显示图像,而不需要暂停。

7• 这行代码通常用于显示所有已经创建的图像。在脚本中,它通常放在最后,以确保所有图像都被绘制并显示。由于 plt.ioff() 已经关闭了交互模式,所以这个调用将显示所有待显示的图像,并保持窗口打开,直到用户关闭它。 需要注意的是,plt.show() 应该只在脚本的末尾调用一次,以显示所有图像。如果在循环或函数中多次调用,可能会导致图像显示不正确或重复显示相同的图像。此外,由于 plt.show() 通常是一个阻塞调用,它会暂停程序的执行,直到用户关闭图像窗口,因此在脚本中通常只在所有图像都准备好后调用一次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值