当前位置: 首页 > news >正文

深度学习 Pytorch图像分类步骤

 这篇就是讲用 Pytorch 进行图像分类的步骤,以下都是我自己总结的,有问题可以指出。

首先呢,项目文件夹可以是这样:

当然在写一些简单的网络结构和数据集比较简单时,除了测试文件其他都可以直接写在一个文件,那么下面开始:

一、数据预处理 Transform

也叫数据增强,是一种通过人工生成或修改训练数据来增加数据集多样性的技术,常用于解决过拟合问题。数据增强通过“模拟”更多训练数据,迫使模型学习泛化性更强的规律,而非训练集中的偶然性模式。其本质是一种低成本的正则化手段,尤其在数据稀缺时效果显著。常用的处理方法有以下几种:

而最常用的就是 Resize + ToTensor + Normalize,因为分类模型基本都要用到,代码如下:

transform = transforms.Compose([transforms.Resize((224, 224)), #统一图片大小transforms.RandomHorizontalFlip(), #随机水平翻转transforms.ToTensor(), #转为张量transforms.Normalize(mean, std) #进行标准化
])

二、加载数据集/自定义Dataset类

  Dataset是一个抽象类,是所有自定义数据集应该继承的基类。它定义了数据集必须实现的方法,一个是 __len__方法,还有一个是 __getitem__方法,其中 def __len__(self) 是返回的总数据的个数,def __getitem__(self, idx) 是根据索引 idx 返回数据集中的样本并提取,然后进行预处理或变换,总之 Dataset 是为了告诉 Pytorch 我这个数据集该怎么数,怎么取。

加载数据集时又有以下几种典型场景:

1、加载图片数据集

代码如下:

class FlowerDataset(Dataset):def __init__(self, root_dir, transform=None):self.transform = transformself.images = []self.labels = []self.classes = ['Rose', 'Sunflower', 'Hibiscus']for label, cls in enumerate(self.classes):class_dir = os.path.join(root_dir, cls)for img_name in os.listdir(class_dir):if img_name.endswith(('.jpg', '.png')):img_path = os.path.join(class_dir, img_name)self.images.append(img_path)self.labels.append(label)def __len__(self):return len(self.images) #返回数据集的大小def __getitem__(self, idx):image = Image.open(self.images[idx]).convert('RGB')label = self.labels[idx]if self.transform:image = self.transform(image)return image, label

  __init__() 方法的最终结果是两个平行的列表:  self.images 包含所有图像路径,self.labels 包含对应的数字标签,所以后面的 __len__() 方法返回的是数据集的大小,即有多少张图片,而 __getitem__() 就是根据给定索引 idx ,返回数据集中对应的图像和标签,图像会先转成 RGB ,再做 transform,最后打包成张量

2、加载csv数据集

比如图片都在一个文件夹,标签在csv文件中,这个时候代码如下:

import pandas as pdclass CSVImageDataset(Dataset):def __init__(self, csv_file, img_dir, transform=None):self.data = pd.read_csv(csv_file)self.img_dir = img_dirself.transform = transformdef __len__(self):return len(self.data)def __getitem__(self, idx):img_name = self.data.iloc[idx, 0]label = self.data.iloc[idx, 1]img_path = os.path.join(self.img_dir, img_name)image = Image.open(img_path).convert('RGB')if self.transform:image = self.transform(image)return image, label

逻辑与图片数据集差不多,就是要用到的是 panda库

3、加载混合数据集

 比如每张图片还有一段描述文字,将一起喂给模型,那就:

def __getitem__(self, idx):image = Image.open(...)text = self.texts[idx]label = self.labels[idx]return image, text, label

用哪种模型、要加载什么数据集,就改写对应的__getitem__() 就好惹。

但其实还有一种更简单的方法,如果数据是按train、test、valid 这么直接分好的,那么直接用 torchvision.datasets.ImageFolder 就好了,例如:

from torchvision.datasets import ImageFoldertrain_dataset = ImageFolder(root='Flowers/train', transform=transform)
test_dataset = ImageFolder(root='Flowers/test', transform=transform)

它会自动把子文件夹名当成类别,并且把每张图转成 (image, label)的格式,非常友好

下面是一套 Dataset 的写法通用模版:

class MyDataset(Dataset):def __init__(self, ..., transform=None):self.data = ... # 加载所有路径或数据行self.labels = ...self.transform = transformdef __len__(self):return len(self.data)def __getitem__(self, idx):# 根据idx取图像+标签image = ...label = ...if self.transform:image = self.transform(image)return image, label

总之呢,只要看数据集的图片和标签在哪,再套不同的模版就好了

4、加载官方数据集

最后还有一种就是使用官方自带的数据集,比如 MNIST:

train_dataset = MNIST(root=data_dir,train=True,transform=tfs,download=False
)

 MNIST 本身就是一个已经实现了__len__ 和__getitem__方法的Dataset类,我们只要调用它,就可以直接传给 DataLoader 用了,因为其内部已经定义了怎么读取图片和标签,所以不需要再手写来实现这些功能

三、数据加载器 DataLoader

 DataLoader 是一个迭代器,用于从 Dataset 中批量加载数据。它的主要功能包括:支持批量加载batch分组,将多个样本组合成一个批次,以及打乱数据,在每个 epoch 中随机打乱数据顺序防止局部收敛,代码如下:

    # 创建训练集和测试集对象train_dataset = Flowerimages(train_dir, transform = transform)test_dataset = Flowerimages(test_dir, transform = transform)val_dataset = Flowerimages(val_dir, transform=transform)#将数据放在数据加载器中train_loader = DataLoader(dataset=train_dataset, batch_size=10, shuffle=True)test_loader = DataLoader(dataset=test_dataset, batch_size=10, shuffle=False)val_loader = DataLoader(dataset=val_dataset, batch_size=10, shuffle=False)

在使用 DataLoader 之前要先创建 Dataset 对象,因为 DataLoader是取数据的工具,而 Dataset是装数据的容器,得先有了数据才能取

四、定义模型结构

 因为苯人现在还只是初级阶段,所以模型结构不会写得太复杂,我直接以老师上课讲的网络结构为例,基本包括:卷积层1-->激活函数-->池化层2-->卷积层3-->激活函数-->池化层4-->全连接层5-->激活函数-->全连接层6-->激活函数-->全连接层7(输出),具体代码如下:


class MyModel(nn.Module):def __init__(self):super(NumberModel, self).__init__()self.c1 = nn.Sequential( #卷积层1nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5,stride=1,),nn.ReLU(), #激活函数)self.s2 = nn.AdaptiveAvgPool2d(14) #自适应池化层2self.c3 = nn.Sequential( #卷积层3nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5,stride=1,),nn.ReLU(), #激活函数)self.s4 = nn.AdaptiveAvgPool2d(5) #自适应池化层4self.l5 = nn.Sequential( #全连接层5nn.Linear(16*5*5,120),nn.ReLU(), #激活函数)self.l6 = nn.Sequential( #全连接层6nn.Linear(120,84),nn.ReLU(), #激活函数) self.l7 = nn.Linear(84,10) #输出def forward(self, x): #前向传播函数x = self.c1(x)x = self.s2(x)x = self.c3(x)x = self.s4(x)x = x.view(x.size(0), -1)x = self.l5(x)x = self.l6(x)out = self.l7(x)return out

五、损失优化

就是配置训练细节,必要的有损失函数和优化器,代码示例如下:

    model = MyModel()device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #配置设备model.to(device)# 创建 CrossEntropyLoss 损失函数的实例化对象criterion = nn.CrossEntropyLoss()# 使用AdamW 优化器,增加权重衰减optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)

训练批次和学习率也可以单独在优化器外面设置

六、模型训练

下面是一个常用的模版(苯人综合了我几位老师写的):

# 假设你已经定义好了:
# model, train_loader, val_loader, criterion, optimizer, device
def train(model, train_loader, epochs):epochs = 5  # 想训练几轮自己定for epoch in range(epochs):model.train()#初始化下面三个参数running_loss = 0.0 #当前轮次中所有loss的总和total, correct = 0, 0 #样本总数和预测正确的样本数for images, labels in train_loader: #遍历所有训练数据images, labels = images.to(device), labels.to(device) #移到同个设备output = model(images) #得到预测值loss = criterion(output, labels) #计算损失,需传入预测值和真实标签optimizer.zero_grad() #梯度清零loss.backward() #反向传播optimizer.step() #根据梯度更新参数running_loss += loss.item() #把当前 batch 的 loss 加到总的 running_loss 里predicted = torch.argmax(output, 1)  #返回预测概率中的最大值对应的索引total += labels.size(0) #加上当前batch的样本数correct += (predicted == labels).sum().item() #加上预测对的个数(预测值和真实标签相等的数量)train_acc = 100 * correct / total #计算准确率print(f"[Epoch {epoch+1}/{num_epochs}] Loss: {running_loss:.4f}, Accuracy: {acc:.2f}%")

这里注意一下 predicted 那行也可以这么写:

_, predicted = torch.max(output, dim=1)  

 表示从每一行里找最大值的索引用predicted接收,_ 是为了接受返回的最大值的数值,也就是具体的概率,但是因为我们不关心具体数值只看类别,所以用_占位,而后面选择维度为1 是因为进入全连接层后也就是分类阶段,此时的输出就从图像特征变成了分类概率,所以 output 的输出形状(N, C),这里的 C 不再是通道数而是预测的类别,所以我们选择维度1。

调用训练函数:

train(model, train_loader, epochs)

七、模型验证

 模型验证是在训练过程中的 “体检”,目的是调整参数并判断模型是否过拟合,下面同样也是一个常用模版:

def evaluate(model, val_loader):model.eval()  # 进入验证模式val_loss = 0.0correct = 0total = 0with torch.no_grad():  # 关闭梯度计算for images, labels in val_loader:images, labels = images.to(device), labels.to(device)output = model(images)loss = criterion(output, labels)val_loss += loss.item()_, predicted = torch.max(output, dim=1)total += labels.size(0)correct += (predicted == labels).sum().item()avg_loss = running_loss / len(train_loader)acc = 100 * correct / totalprint(f"验证集 Loss: {avg_loss:.4f}, Accuracy: {acc:.2f}%")

基本逻辑与模型训练差不多,然后在封装了训练函数后,在每轮 epoch 结束训练后都可以验证一下:

def train(model, train_loader, val_loader, epochs):best_acc = 0.0 #初始化最高的准确率'''模型训练'''# 每个epoch后验证val_loss, val_acc = evaluate(model, val_loader)

如果要训练和验证分离,则可以:

for epoch in range(epochs):train_loss, train_acc = train(model, train_loader)print(f"[Epoch {epoch+1}] Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%")val_loss, val_acc = evaluate(model, val_loader)print(f"[Epoch {epoch+1}] Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%")

训练中验证适用于训练周期短,代码简单时,而分离验证则更适合比较大的项目。

八、模型保存

 模型应该是在训练+验证阶段保存,为什么不是在测试后保存是因为测试相当于最终考试,不能用来调参也不能回头修改。

保存模型有两种方式,第一种是保存整个模型的结构和权重参数 torch.save(model, "xxx.path") ,对跨平台和版本兼容性表现较差,所以一般采用第二种方式 torch.save(model.state_dict(), "xxx.path"),只保存权重参数,缺点就是加载时要重新定义模型结构。

而一般又会保存两种模型,一个是最后一次训练后的模型 last.pth,一个是正确率最高的模型 best.pth,具体代码如下:

保存最后一次训练后的模型参数:

#前面是验证代码torch.save(model.state_dict(), 'last.pth') 

保存最佳模型参数:

    # 前面是验证代码if val_acc > best_acc:best_acc = val_acc torch.save(model.state_dict(), 'best.pth') #得到最高的准确率的模型print("最佳模型已保存")

九、模型加载与测试

模型加载:

model = MyModel()
model.load_state_dict(torch.load("best.pth"))
model.eval()

模型测试就是用来评估模型的最终性能,相当于最终的体检报告,数据来源是完全没碰过的数据,不能拿训练集和验证集的数据跑,那就相当于考试漏题咯,代码如下:

import torch
from model import MyModel
from dataset import MyDataset
from torch.utils.data import DataLoader
from torchvision import transforms# 数据预处理
transform = transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),# ...看你的训练用啥归一化
])# 加载测试集
test_dataset = MyDataset('path/to/test', transform=transform)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)# 加载模型
model = MyModel()
model.load_state_dict(torch.load('best.pth'))
model.eval()# 测试
correct, total = 0, 0
with torch.no_grad():for images, labels in test_loader:images, labels = images.to(model.device), labels.to(model.device)output = model(images)_, predicted = torch.max(output, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f"测试准确率: {100 * correct / total:.2f}%")

这里没有再定义模型结构是因为:from model import MyModel,我直接从 model.py 文件中将模型结构引进来了,而 model.py 文件包含前面定义好的模型结构,也就是自己创建的 MyModel 类,同样 from dataset import MyDataset 也是一个道理,所以这段代码的前提是已经写好了模型结构并且加载好了数据集以及数据加载器。

这篇就到这里,有很多不足的地方,或许也有很多不对的地方,而且前面的好多东西我都还没写博客就直接来写总的步骤了[晕],总之也是我自己看,有问题可以指出(๑•̀ㅂ•́)و✧!下一篇我也没想好写啥

http://www.xdnf.cn/news/1128637.html

相关文章:

  • Linux部署Python服务
  • sglang笔记1: 后端详细阅读版
  • 文心一言4.5开源模型测评:ERNIE-4.5-0.3B超轻量模型部署指南
  • Halcon双相机单标定板标定实现拼图
  • Java线程池深度解析与Spring Boot实战指南
  • resources为什么是类的根目录
  • 策略设计模式分析
  • AI辅助Python编程30天速成
  • 死锁问题以及读写锁和自旋锁介绍【Linux操作系统】
  • LeetCode|Day13|88. 合并两个有序数组|Python刷题笔记
  • MySQL数学函数
  • HALCON+PCL混合编程
  • 从抽象函数到可计算导数 ——SymPy 中占位、求导、代入的完整闭环
  • JVM——编译执行于解释执行的区别是什么?JVM使用哪种方式?
  • K型热电偶电动势以及温度对照表
  • 从基础到进阶:MyBatis-Plus 分页查询封神指南
  • BPE(字节对编码)和WordPiece 是什么
  • [AI-video] Web UI | Streamlit(py to web) | 应用配置config.toml
  • Android 图片压缩
  • Spring应用抛出NoHandlerFoundException、全局异常处理、日志级别
  • 前端开发数据缓存方案详解
  • 1.easypan-登录注册
  • git起步
  • Jfinal+SQLite java工具类复制mysql表数据到 *.sqlite
  • 同济医院R语言训练营第三期开讲!上交大张维拓老师主讲
  • 2025最新国产用例管理工具评测:Gitee Test、禅道、蓝凌测试、TestOps 哪家更懂研发协同?
  • 希尔排序:突破传统排序的边界
  • 22.计算指定范围内数字的幂次和
  • StampedLock分析
  • 基于cornerstone3D的dicom影像浏览器 第二章,初始化页面结构