HYLOVEYOURSELF

小洋宝的进步史

导航

Datawhale组队学习 深入浅出PyTorch Task2❤

Datawhale组队学习

深入浅出PyTorch-PyTorch主要组成&基础实战

作者:博客园-岁月月宝贝

教程文档: datawhalechina/thorough-pytorch: PyTorch入门教程,在线阅读地址:https://datawhalechina.github.io/thorough-pytorch/

😉经过前面的PyTorch基础学习,相信大家已经掌握了“张量”和“自动求导”的使用方法,那下面我们就进入PyTorch项目实战!本章我们将从深度学习的核心概念与原理入手,深入剖析神经网络的结构与运作机制。接着,针对实际应用中遇到的问题,详细讲解数据读入、模型构建、训练与评估等一系列关键环节的实现方法。🙂


一、理论知识

在开始深度学习的旅程之前,我们需要先了解完成一项深度学习任务的整体流程。

深度学习和大家耳熟能详的机器学习在流程上很相似~👇它们通常都可分为以下几个步骤:

  1. 数据预处理:这是整个流程的基础,包括数据格式的统一、异常数据的消除以及必要的数据变换。同时,我们需要将数据划分为训练集、验证集和测试集。常见的划分方法有按比例随机选取KFold 方法,这些都可以通过 sklearn 提供的 train_test_split 函数和 KFold 实现。
  2. 模型选择与配置:选择合适的模型是关键。我们需要设定损失函数优化方法,并确定相应的超参数。对于机器学习任务,可以直接使用 sklearn 等库中模型自带的损失函数和优化器。
  3. 模型训练与评估:使用选定的模型对训练集数据进行拟合,并在验证集测试集上评估模型的表现。

但它们在代码实现上有较大差异👇:

  • 数据加载:深度学习通常需要处理大量数据,一次性加载所有数据可能会超出内存容量。因此,深度学习采用批(batch)训练的方式,每次读取固定数量的样本送入模型中训练。这需要专门的数据加载设计。
  • 模型构建:深度神经网络的层数较多,且包含一些特定功能的层(如卷积层、池化层、批正则化层、LSTM 层等)。因此,深度神经网络通常需要逐层搭建,或者预先定义好特定功能的模块,再将这些模块组装起来。这种“定制化”的模型构建方式既保证了模型的灵活性,也对代码实现提出了新的要求。
  • 损失函数和优化器:这部分与经典机器学习类似,但由于模型的灵活性,损失函数和优化器需要能够支持反向传播在用户自定义的模型结构上实现。

除此之外,我们深度学习是离不开“高性能引擎”GPU的,它不仅涉及到模型和数据的迁移,还包括损失函数和优化器的适配,以及多GPU训练时的模型和数据分配与整合。

🚗 GPU——加速计算的关键

在深度学习的训练过程中,GPU(图形处理器)的使用是加速计算的关键。然而,程序默认是在CPU(中央处理器)上运行的,这就需要我们将模型和数据“迁移”到GPU上进行运算。这一步骤至关重要,因为GPU拥有强大的并行计算能力,能够显著提升训练速度。

首先,我们需要确保模型和数据都被正确地放置在GPU上。这通常通过在代码中指定设备(device)为GPU来实现。例如,在PyTorch中,我们可以使用.to(device)方法将模型和数据移动到GPU上。同时,我们还需要确保损失函数和优化器也能够在GPU上正常工作,这通常意味着它们需要能够处理在GPU上的张量(tensor)运算。

当涉及到多张GPU进行训练时,情况会变得更加复杂。我们需要考虑如何在多个GPU之间分配模型和数据,以及如何整合它们的计算结果。这通常涉及到数据并行(data parallelism)模型并行(model parallelism)的策略。数据并行将数据分割成多个批次,每个批次在不同的GPU上进行计算,然后将结果合并。模型并行则是将模型的不同部分分配到不同的GPU上,每个GPU计算模型的一部分。

PS:But训练完成后一些性能指标,如准确率、损失值等的计算可能需要将数据从GPU“迁移”回CPU,因为某些操作在CPU上可能更高效,或者我们需要在CPU上进行结果的汇总和分析。

总的来说,GPU的配置和操作是深度学习训练过程中不可或缺的一部分😉。

最后,我们再来一个深度学习特点的小总结——

深度学习中训练和验证过程最大的特点在于读入数据是按批的,每次读入一个批次的数据,放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。

下面,我们就以案例的形式,把PyTorch掰开揉碎为大家讲!

二、案例一 FashionMNIST时装分类

img

FashionMNIST数据集(https://github.com/zalandoresearch/fashion-mnist/tree/master/data/fashion )包含已经预先划分好的训练集和测试集,其中训练集共60,000张图像,测试集共10,000张图像。每张图像均为单通道黑白图像,大小为32*32pixel,分属10个类别。上图给出了FashionMNIST中数据的若干样例图,其中每个小图对应一个样本。

我们的任务是对10个类别的“时装”图像进行分类,下面,我们开始吧!

1.导包

import os
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

torch、torch.nn、torch.utils.data.Dataset、torch.utils.data.DataLoader、torch.optimizer当然都是我们PyTorch自身的一些模块啦!另外,涉及到表格信息的读入很可能用到pandas,如果涉及可视化还会用到matplotlib、seaborn或更上层的cv2,涉及到下游分析和指标计算也常用到sklearn。

2.配置训练环境和超参数

## 配置GPU,这里有两种方式
# 方案一:使用os.environ,这种情况如果使用GPU则不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0'# 指明调用的GPU为0号
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
#device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")# 指明调用的GPU为1号(如果要与上行表示相同意思这里改为0就OK)

## 配置其他超参数,如batch_size, num_workers, learning rate, 以及总的epochs
batch_size = 256 # 批次大小
num_workers = 4   # 有多少个进程用于读取数据,对于Windows用户,这里应设置为0,否则会出现多线程错误
lr = 1e-4 # 优化器的学习率
epochs = 20 # 总训练次数
  • batch_size:样本是按“批”读入的,batch_size就是每次读入的样本数
  • num_workers:有多少个进程用于读取数据,Windows下该参数设置为0,Linux下常见的为4或者8,根据自己的电脑配置来设置
  • shuffle:是否将读入的数据打乱,一般在训练集中设置为True,验证集中设置为False
  • drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练

PS : 如果没有经过显式指明设备, 我们的数据和模型默认会存储在CPU上; 超参数除了写在训练的代码里,还可以使用yaml、json、dict等文件来存储❤

3.数据读入和加载

数据读入方式 :

  • 🍁使用 PyTorch 提供的内置数据集

    PyTorch 中自带了一些常用的数据集,如 MNIST、CIFAR10 等。这些数据集不仅包含了数据本身,而且还会自动下载相应的标签信息。使用内置数据集可以大大简化数据载入的流程,非常适合快速验证某些想法(例如在 MNIST 数据集上测试某个 idea 是否有效)。

  • 🍁从网站下载以 csv 格式存储的数据

    在实际项目中,数据来源可能更加多样化。例如,从网站下载以 csv 格式存储的数据,然后将其读入并转换成预期的格式。这种情况通常需要自己构建 Dataset。

数据变换 :

在数据载入之后,还需要对其进行必要的变换。例如,可能需要将图片统一为一致的大小,以便后续能够输入网络进行训练。此外,还需要将数据格式转换为 Tensor 类,这样才符合 PyTorch 的数据要求。当然,无论是使用内置数据集还是自己构建的数据集。

1️⃣ 下面我就定义自己的Dataset类并且读入自己的数据,最后完成了数据变换:

# 首先设置数据变换
from torchvision import transforms #torchvision:PyTorch官方用于图像处理的工具库

image_size = 28#因为我们套在了手写数字识别里,那里输入图像维度是28⭐
data_transform = transforms.Compose([
    transforms.ToPILImage(), #这一步取决于后续的数据读取方式,若使用内置数据集则不需要
    transforms.Resize(image_size),
    transforms.ToTensor()
])

## 读取方式一:使用torchvision自带数据集,下载可能需要一段时间
#from torchvision import datasets

#train_data = datasets.FashionMNIST(root='./', train=True, download=True, transform=data_transform)
#test_data = datasets.FashionMNIST(root='./', train=False, download=True, transform=data_transform)

## 读取方式二:读入csv格式的数据,自行构建Dataset类
# csv数据下载链接:https://www.kaggle.com/zalando-research/fashionmnist
class FMDataset(Dataset):
    def __init__(self, df, transform=None):
        self.df = df
        self.transform = transform
        self.images = df.iloc[:,1:].values.astype(np.uint8)#因为第一列是标签!这个需要输出train.df查看!
        self.labels = df.iloc[:, 0].values
        
    def __len__(self):
        return len(self.images)
    
    def __getitem__(self, idx):
        image = self.images[idx].reshape(28,28,1)#reshape可能会损失信息哦
        label = int(self.labels[idx])
        if self.transform is not None:
            image = self.transform(image)
        else:
            image = torch.tensor(image/255., dtype=torch.float)#除以255是归一化处理
        label = torch.tensor(label, dtype=torch.long)
        return image, label

train_df = pd.read_csv("./FashionMNIST/fashion-mnist_train.csv")
test_df = pd.read_csv("./FashionMNIST/fashion-mnist_test.csv")
train_data = FMDataset(train_df, data_transform)
test_data = FMDataset(test_df, data_transform)

我们定义的类需要继承PyTorch自身的Dataset类。主要包含三个函数:

  • __init__: 用于向类中传入外部参数,同时定义样本集
  • __getitem__: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据
  • __len__: 用于返回数据集的样本数

这是我的文件目录:

image-20250215021246545

PS :如果导入图片用ImageFolder类,如 “train_data = datasets.ImageFolder(train_path, transform=data_transform)”;Dataset的其他定制方法见Github

2️⃣ 在构建训练和测试数据集完成后,需要定义DataLoader类,以便在训练和测试时加载数据

train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers, drop_last=True)
test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)
  • drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练,因为shuffle=True,所以公平

PS :其他参数前面有介绍

3️⃣ 读入后,我们可以做一些数据可视化操作,主要是验证我们读入的数据是否正确

import matplotlib.pyplot as plt
image, label = next(iter(train_loader))#手动单个递进batch训练
print(image.shape, label.shape)
plt.imshow(image[0][0], cmap="gray")

image-20250215021659487

输出如上,但是用jupyter notebook应该还有照片如下一张(每次运行会变)

image-20250215021820080

4.模型设计
由于任务较为简单,这里我们手搭一个CNN,而不考虑当下各种模型的复杂结构:

#Module 类是 torch.nn 模块里提供的一个模型构造类,是所有神经网络模块的基类
class Net(nn.Module):#可以继承它来定义我们想要的模型
    #💗这里并没有将 Module 类命名为 Layer (层)或者 Model (模型)之类的名字,这是因为该类是一个可供⾃由组建的部件。它的子类既可以是⼀个层(如PyTorch提供的 Linear 类),⼜可以是一个模型(如这里定义的 CNN类),或者是模型的⼀个部分。
    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 32, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, stride=2),
            nn.Dropout(0.3),
            nn.Conv2d(32, 64, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, stride=2),
            nn.Dropout(0.3)
        )
        self.fc = nn.Sequential(
            nn.Linear(64*4*4, 512),
            nn.ReLU(),
            nn.Linear(512, 10)
        )
        
    def forward(self, x):
        x = self.conv(x)
        x = x.view(-1, 64*4*4)
        x = self.fc(x)
        # x = nn.functional.normalize(x)
        return x

#实例化 MLP 类得到模型变量 net
model = Net()
#模型构建完成后,将模型放到GPU上用于训练
model = model.cuda()
# model = nn.DataParallel(model).cuda()   # 多卡训练时的写法,之后的课程中会进一步讲解

下面两个表格用来辅助大家掌握:

PyTorch 中特殊层的构建

层类型 功能描述 PyTorch 实现代码示例
自定义无参数层 通过继承 nn.Module 类自定义一个将输入减掉均值后输出的层 class MyLayer(nn.Module):
def init(self, kwargs):
super(MyLayer, self).init(
kwargs)
def forward(self, x):
return x - x.mean()
自定义有参数层 自定义含模型参数的层,参数可以通过训练学出 class MyListDense(nn.Module):
def init(self):
super(MyListDense, self).init()
self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])
self.params.append(nn.Parameter(torch.randn(4, 1)))
def forward(self, x):
for i in range(len(self.params)):
x = torch.mm(x, self.params[i])
return x

LeNet 和 AlexNet 的 PyTorch 实现对比

特征 LeNet AlexNet
卷积层数量 2 个卷积层 5 个卷积层
卷积核大小 第一层 5x5,第二层 5x5 第一层 11x11,第二层 5x5,第三层 3x3,第四层 3x3,第五层 3x3
池化层 2 个最大池化层 3 个最大池化层
全连接层 3 个全连接层 3 个全连接层
激活函数 ReLU ReLU
Dropout 层
输入图像大小 32x32 227x227
输出类别数 10 10 (可根据需要调整为 1000)

PS:继承Module 类构造神经网络多层感知机(MLP),不含模型参数的将输入减掉均值后输出的层,ParameterParameterListParameterDict定义的含模型参数的层,常见的神经网络层(如卷积层、池化层,以及较为基础的 AlexNet,LeNet等),都于Github可见~

5.设定损失函数

我们使用torch.nn模块自带的CrossEntropy损失。PyTorch会自动把整数型的label转为one-hot型,用于计算CrossEntropyLoss(CE loss)。
⭐这里需要确保label是从0开始的,同时模型不加softmax层(因为要使用logits计算)

criterion = nn.CrossEntropyLoss()
# criterion = nn.CrossEntropyLoss(weight=[1,1,1,1,3,1,1,1,1,1])

(3的意思有加大力度惩罚的感觉,目的是让模型重视这个维度的属性)

这里可以看一下weighting等策略:

?nn.CrossEntropyLoss 

可以依据下图尝试设置更多如weight类的参数!

image-20250215022009436

这里整体说明了PyTorch训练中各个部分不是独立的,需要通盘考虑👌

那么我们是否有其他损失函数呢?以下是 PyTorch 中常见损失函数的对比表:

img

损失函数的代码实现与相关图像见Github 😊

6.设定优化器

这里我们使用Adam优化器

optimizer = optim.Adam(model.parameters(), lr=0.001)

PyTorch给我们提供了一个优化器的库torch.optim,下图图例就展现了常见的一些优化器(具体细节参见Github

img

优化器使用注意事项:

1.每个优化器都是一个类,我们一定要进行实例化才能使用

2.optimizer在一个神经网络的epoch中需要实现下面两个步骤:i 梯度置零optimizer.zero_grad(),ii 梯度更新optimizer.step()

3.可给网络不同的层赋予不同的优化器参数。

PS:

  • defaults:存储的是优化器的超参数,例子如下:
{'lr': 0.1, 'momentum': 0.9, 'dampening': 0, 'weight_decay': 0, 'nesterov': False}

7.训练和测试(验证)

我们这里把它们各自封装成函数,方便后续调用

训练过程:

def train(epoch):
    model.train()#训练状态,模型的参数应该支持反向传播的修改
    train_loss = 0
    for data, label in train_loader:
        data, label = data.cuda(), label.cuda()
        optimizer.zero_grad()#⭐开始用当前批次数据做训练时,应当先将优化器的梯度置零
        output = model(data)
        loss = criterion(output, label)#根据预先定义的criterion计算损失函数
        loss.backward()
        optimizer.step()#优化器更新模型参数
        train_loss += loss.item()*data.size(0)
    train_loss = train_loss/len(train_loader.dataset)
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))

验证过程:

与训练过程区别在于:

  • 需要预先设置torch.no_grad,以及将model调至eval模式
  • 不需要将优化器的梯度置零
  • 不需要将loss反向回传到网络
  • 不需要更新optimizer
def val(epoch):       
    model.eval()#验证/测试状态,则不应该修改模型参数
    val_loss = 0
    gt_labels = []
    pred_labels = []
    with torch.no_grad():
        for data, label in test_loader:
            data, label = data.cuda(), label.cuda()
            output = model(data)
            preds = torch.argmax(output, 1)
            gt_labels.append(label.cpu().data.numpy())
            pred_labels.append(preds.cpu().data.numpy())
            loss = criterion(output, label)
            val_loss += loss.item()*data.size(0)#总损失
    val_loss = val_loss/len(test_loader.dataset)
    gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)#标签连接
    acc = np.sum(gt_labels==pred_labels)/len(pred_labels)#计算分类准确率
    print('Epoch: {} \tValidation Loss: {:.6f}, Accuracy: {:6f}'.format(epoch, val_loss, acc))

组合:

for epoch in range(1, epochs+1):
    train(epoch)
    val(epoch)

输出如下:

image-20250215022136410

但是据李老师讲,只有验证loss不变/提高(过拟合)时才说明到了训练的边界,而我们还在下降,所以20个epoch还不够~

小贴士:

对于图像分类任务,我们还可以使用sklearn.metrics中的classification_report函数来计算模型的准确率、召回率、F1值等指标,如下所示:

from sklearn.metrics import classification_report
"""
将下方代码的labels和preds替换为模型预测出来的所有label和preds,
target_names替换为类别名称,
既可得到模型的分类报告
"""
print(classification_report(labels.cpu(), preds.cpu(), target_names=class_names))

除此之外,我们还可以使用torchevaltorchmetric来对模型进行评估。

PS :训练过程与验证过程的其他写法参见Github

8.模型保存
训练完成后,可以使用torch.save保存模型参数或者整个模型,也可以在训练过程中保存模型

save_path = "./FahionModel.pkl"
torch.save(model, save_path)

我们是存到了下面这里:

image-20250215022527673

这部分会在后面的课程中详细介绍!

+其他未来会介绍的部分:可视化(分类的ROC曲线,卷积网络中的卷积核,以及训练/验证过程的损失函数曲线)

三、 案例二 果蔬分类

数据集地址:kaggle 果蔬分类数据集

百度网盘:链接:https://pan.baidu.com/s/1q-fS2M97er1-769Ol-htSg 提取码:t0ww

大家首先需要从百度网盘上下载数据集到本地,并且将文件排布为以下的结构:

image-20250215024608039

接着windows用户粘入以下我修改后的代码(linux用户自力更生🐕):

# 导入必要的包
import os
import torch
import timm
from torchvision import transforms
import numpy as np
import torch.nn as nn
import warnings
import random


warnings.filterwarnings("ignore")
import matplotlib.pyplot as plt
#%matplotlib inline


# 设置随机种子,确保结果可以复现
def set_seed(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    random.seed(seed)
    np.random.seed(seed)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True


# 设置文件路径⭐
train_path = './train/'
val_path = './validation/'
test_path = './test/'

# 设置超参数(笔记本用户改为cuda0)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
batch_size = 32

# num_workers wins用户只能指定为0
num_workers = 0
lr = 3e-4
epochs = 10

# 输出类别
categories = os.listdir(train_path)
print(f"Categories: {len(categories)}")  #  Categories: 36

# 使用imgaug对图像进行数据增强
import imageio
import imgaug as ia
from imgaug import augmenters as iaa


class ImgAugTransform:
    def __init__(self):
        self.aug = iaa.Sequential([
            iaa.Scale((224, 224)),
            iaa.Sometimes(0.25, iaa.GaussianBlur(sigma=(0, 3.0))),
            iaa.Fliplr(0.5),
            iaa.Affine(rotate=(-20, 20), mode='symmetric'),
            iaa.Sometimes(0.25,
                          iaa.OneOf([iaa.Dropout(p=(0, 0.1)),
                                     iaa.CoarseDropout(0.1, size_percent=0.5)])),
            iaa.AddToHueAndSaturation(value=(-10, 10), per_channel=True)
        ])

    def __call__(self, img):
        img = np.array(img)
        return self.aug.augment_image(img).transpose(2, 1, 0)

import imgaug

#linux用户num_worker≠0时使用
# def worker_init_fn(worker_id):
#     imgaug.seed(np.random.get_state()[1][0] + worker_id)


tfs = ImgAugTransform()

from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
from PIL import Image

# 自定义数据集
train_dataset = ImageFolder(train_path, transform=tfs)
val_dataset = ImageFolder(val_path, transform=tfs)
test_dataset = ImageFolder(test_path, transform=tfs)

#假设上面代码适用于Linux用户
# train_loader = DataLoader(train_dataset, batch_size=batch_size, worker_init_fn=worker_init_fn, shuffle=True)
# val_loader = DataLoader(val_dataset, batch_size=batch_size, worker_init_fn=worker_init_fn, shuffle=False)
# test_loader = DataLoader(test_dataset, batch_size=batch_size, worker_init_fn=worker_init_fn, shuffle=False)

#那么windows用下边
# DataLoader 参数(num_workers=0 表示禁用多线程数据加载)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=0)

# 可视化图片
images, labels = next(iter(train_loader))
print(images.shape)  # torch.Size([32, 3, 224, 224])
print(type(images))  # <class 'torch.Tensor'>
print(images[1].shape)  # torch.Size([3, 224, 224])
plt.imshow(images[0].permute(1, 2, 0))
plt.show()  # 水果图片*1 输出

import timm

# 使用timm使用预训练模型
model = timm.create_model("resnet18", num_classes=36, pretrained=True).to(device)

avail_pretrained_models = timm.list_models(pretrained=True)
print(len(avail_pretrained_models))  # 1564

# 使用通配符查找resnet系列
all_resnent_models = timm.list_models("*resnet*")
print(all_resnent_models)

#这边输出固定:
#['cspresnet50', 'cspresnet50d', 'cspresnet50w', 'eca_resnet33ts', 'ecaresnet26t', 'ecaresnet50d', 'ecaresnet50d_pruned', 'ecaresnet50t', 'ecaresnet101d', 'ecaresnet101d_pruned', 'ecaresnet200d', 'ecaresnet269d', 'ecaresnetlight', 'gcresnet33ts', 'gcresnet50t', 'inception_resnet_v2', 'lambda_resnet26rpt_256', 'lambda_resnet26t', 'lambda_resnet50ts', 'legacy_seresnet18', 'legacy_seresnet34', 'legacy_seresnet50', 'legacy_seresnet101', 'legacy_seresnet152', 'nf_ecaresnet26', 'nf_ecaresnet50', 'nf_ecaresnet101', 'nf_resnet26', 'nf_resnet50', 'nf_resnet101', 'nf_seresnet26', 'nf_seresnet50', 'nf_seresnet101', 'resnet10t', 'resnet14t', 'resnet18', 'resnet18d', 'resnet26', 'resnet26d', 'resnet26t', 'resnet32ts', 'resnet33ts', 'resnet34', 'resnet34d', 'resnet50', 'resnet50_clip', 'resnet50_clip_gap', 'resnet50_gn', 'resnet50_mlp', 'resnet50c', 'resnet50d', 'resnet50s', 'resnet50t', 'resnet50x4_clip', 'resnet50x4_clip_gap', 'resnet50x16_clip', 'resnet50x16_clip_gap', 'resnet50x64_clip', 'resnet50x64_clip_gap', 'resnet51q', 'resnet61q', 'resnet101', 'resnet101_clip', 'resnet101_clip_gap', 'resnet101c', 'resnet101d', 'resnet101s', 'resnet152', 'resnet152c', 'resnet152d', 'resnet152s', 'resnet200', 'resnet200d', 'resnetaa34d', 'resnetaa50', 'resnetaa50d', 'resnetaa101d', 'resnetblur18', 'resnetblur50', 'resnetblur50d', 'resnetblur101d', 'resnetrs50', 'resnetrs101', 'resnetrs152', 'resnetrs200', 'resnetrs270', 'resnetrs350', 'resnetrs420', 'resnetv2_18', 'resnetv2_18d', 'resnetv2_34', 'resnetv2_34d', 'resnetv2_50', 'resnetv2_50d', 'resnetv2_50d_evos', 'resnetv2_50d_frn', 'resnetv2_50d_gn', 'resnetv2_50t', 'resnetv2_50x1_bit', 'resnetv2_50x3_bit', 'resnetv2_101', 'resnetv2_101d', 'resnetv2_101x1_bit', 'resnetv2_101x3_bit', 'resnetv2_152', 'resnetv2_152d', 'resnetv2_152x2_bit', 'resnetv2_152x4_bit', 'seresnet18', 'seresnet33ts', 'seresnet34', 'seresnet50', 'seresnet50t', 'seresnet101', 'seresnet152', 'seresnet152d', 'seresnet200d', 'seresnet269d', 'seresnetaa50d', 'skresnet18', 'skresnet34', 'skresnet50', 'skresnet50d', 'test_resnet', 'tresnet_l', 'tresnet_m', 'tresnet_v2_l', 'tresnet_xl', 'vit_base_resnet26d_224', 'vit_base_resnet50d_224', 'vit_small_resnet26d_224', 'vit_small_resnet50d_s16_224', 'wide_resnet50_2', 'wide_resnet101_2']


# 查看模型默认的cfg
print(model.default_cfg)

#这边的输出我和老师有些差别,我相较原来多了很多项
#{'url': 'https://github.com/huggingface/pytorch-image-models/releases/download/v0.1-rsb-weights/resnet18_a1_0-d63eafa0.pth', 'hf_hub_id': 'timm/resnet18.a1_in1k', 'architecture': 'resnet18', 'tag': 'a1_in1k', 'custom_load': False, 'input_size': (3, 224, 224), 'test_input_size': (3, 288, 288), 'fixed_input_size': False, 'interpolation': 'bicubic', 'crop_pct': 0.95, 'test_crop_pct': 1.0, 'crop_mode': 'center', 'mean': (0.485, 0.456, 0.406), 'std': (0.229, 0.224, 0.225), 'num_classes': 1000, 'pool_size': (7, 7), 'first_conv': 'conv1', 'classifier': 'fc', 'origin_url': 'https://github.com/huggingface/pytorch-image-models', 'paper_ids': 'arXiv:2110.00476'}


from tqdm import tqdm
from torch.optim import Adam
from torch.optim.lr_scheduler import CosineAnnealingLR

# 设置优化器和损失函数
criterion = nn.CrossEntropyLoss()
optimizer = Adam(model.parameters(), lr=3e-4, betas=[0.9, 0.99], eps=1e-08, weight_decay=0.0)
scheduler = CosineAnnealingLR(optimizer, T_max=100, eta_min=0)


# 模型训练
def train(epoch, model, train_loader):
    model.train()
    train_loss = 0
    for image, target in tqdm(train_loader):
        image = image.float()
        image = image.to(device)
        target = target.to(device)
        predict = model(image)
        loss = criterion(predict, target)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        scheduler.step()
        train_loss += loss.item() * image.size(0)
    train_loss = train_loss / len(train_loader.dataset)
    print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))


# 测试集验证
def val(epoch, model, val_dataloader):
    model.eval()
    val_loss = 0
    gt_labels = []
    pred_labels = []
    with torch.no_grad():
        for data, label in tqdm(val_dataloader):
            data, label = data.float().to(device), label.to(device)
            output = model(data)
            preds = torch.argmax(output, 1)
            gt_labels.append(label.cpu().data.numpy())
            pred_labels.append(preds.cpu().data.numpy())
            loss = criterion(output, label)
            val_loss += loss.item() * data.size(0)
    val_loss = val_loss / len(val_dataloader)
    gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)
    acc = np.sum(gt_labels == pred_labels) / len(pred_labels)
    print('Epoch: {} \tValidation Loss: {:.6f}, Accuracy: {:6f}'.format(epoch, val_loss, acc))


# 验证集验证
def test(epoch, model, test_dataloader):
    model.eval()
    test_loss = 0
    gt_labels = []
    pred_labels = []
    with torch.no_grad():
        for data, label in tqdm(test_dataloader):
            data, label = data.float().to(device), label.to(device)
            output = model(data)
            preds = torch.argmax(output, 1)
            gt_labels.append(label.cpu().data.numpy())
            pred_labels.append(preds.cpu().data.numpy())
    gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)
    acc = np.sum(gt_labels == pred_labels) / len(pred_labels)
    print('Epoch: {} \tTest  Accuracy: {:6f}'.format(epoch, acc))


# 以下仅展示训练和测试验证的结果。保存参数可以根据实际需求进行保存
for epoch in range(epochs):
    train(epoch, model, train_loader)
    val(epoch, model, val_loader)
    test(epoch, model, test_loader)

然后这段代码现在还因为版本冲突不能运行(想我这样修改的好处是所有包无脑下最新版就OK),所以大家需要进到自己这个项目的conda环境目录,像我就是D:\anaconda3\envs\fun-transformer\Lib\site-packages\imgaug\augmenters,然后修改里面的meta文件的第3368行,把bool修改为bool_ ,接着保存此文件就OK啦!(不过下次项目重启还需要这样做)

image-20250215143801348

(上面是我修改后的文件)

接下来关闭上面的文件,开启kexue上网(否则无法访问huggingface.co),运行我们这节课的guoshu.py,就可以发现以下的输出:

image-20250215144016838

image-20250215144048714

(因为运行10个epoch,所以是0-9)

并且每次运行还会弹出不同的果蔬图片(下面是两个例子😜):

image-20250215133028566

image-20250215133832183

通过本次任务的学习,我们深入掌握了 PyTorch 的主要组成和基础实战技巧。从深度学习的基本流程到具体的案例实现,我们逐步探索了数据预处理、模型构建、训练与评估等关键环节。希望这些内容能够帮助大家更好地理解和应用 PyTorch,为今后的深度学习项目打下坚实的基础。

如果你对本次学习有任何疑问或建议,欢迎在评论区留言,我们一起交流探讨!感谢你的阅读和支持,祝你在深度学习的道路上越走越远,收获满满!🎉

posted on 2025-02-15 02:52  岁月月宝贝  阅读(36)  评论(0)    收藏  举报