我的环境:
- 语言环境:Python 3.12.7
- 编译器:jupyter notebook
- 深度学习环境:TensorFlow 2.18.0
PyTorch version: 2.5.1+cu118
Torchvision version: 0.20.1+cu118
一、前期准备
import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision from torchvision import transforms, datasets import os,PIL,pathlib,warnings warnings.filterwarnings("ignore") #忽略警告信息 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") deviceimport os,PIL,random,pathlib data_dir = './PotatoPlants/' data_dir = pathlib.Path(data_dir) data_paths = list(data_dir.glob('*')) classeNames = [str(path).split("\\")[1] for path in data_paths] classeNames二、使用步骤
1.处理图片
transforms.Resize([224, 224]):将输入图像调整到224x224像素的大小。这通常是为了适配预训练模型的输入尺寸。transforms.ToTensor():将PIL图像或NumPy数组转换为PyTorch张量(Tensor),并且自动将像素值从[0, 255]缩放到[0, 1]。transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]):对图像进行标准化处理,使其符合标准正态分布。这里的均值(mean)和标准差(std)是根据ImageNet数据集计算得出的,通常用于预训练模型。
# 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863 train_transforms = transforms.Compose([ transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸 # transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间 transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛 mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。 ]) test_transform = transforms.Compose([ transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸 transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间 transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛 mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。 ]) total_data = datasets.ImageFolder("E:/Jupyter/MR_K/7/PotatoPlants",transform=train_transforms) total_data2.划分数据集
train_size = int(0.8 * len(total_data)) test_size = len(total_data) - train_size train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size]) train_dataset, test_dataset3.处理数据集
train_dataset和test_dataset:这些是包含训练和测试数据的数据集对象。你需要确保这些变量已经被正确定义并包含了相应的数据。batch_size=32:指定每个批次中包含的样本数量。在这个例子中,每个批次包含32个样本。shuffle=True:在每个epoch开始时,是否对数据进行随机打乱。这有助于模型训练的泛化能力。num_workers=1:指定加载数据时使用的子进程数量。这里设置为1,意味着数据加载将在主进程中进行。增加num_workers可以提高数据加载的效率,特别是在处理大型数据集时。
batch_size = 32 train_dl = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=1) test_dl = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=True, num_workers=1) for X, y in test_dl: print("Shape of X [N, C, H, W]: ", X.shape) print("Shape of y: ", y.shape, y.dtype) break4.配置模型
卷积块定义
每个卷积块由若干个卷积层、ReLU激活函数和最大池化层组成。这些层的配置如下:
- 卷积层(
nn.Conv2d):用于提取图像特征。每个卷积层的参数包括输入通道数、输出通道数、卷积核大小、步长和填充。 - ReLU激活函数(
nn.ReLU):用于引入非线性,帮助模型学习更复杂的特征。 - 最大池化层(
nn.MaxPool2d):用于降低特征图的空间维度,减少参数数量和计算量。
全连接层分类器
在卷积块之后,你使用了一个全连接层分类器,它由三个全连接层组成:
- 第一个全连接层(
nn.Linear):将特征图展平后映射到4096维。 - 第二个全连接层:将4096维映射到4096维。
- 第三个全连接层:将4096维映射到输出类别数(这里假设为3)。
前向传播
在forward方法中,你定义了数据通过网络的流程:
- 输入数据
x依次通过五个卷积块。 - 然后通过
torch.flatten将多维的特征图展平成一维。 - 最后,展平后的数据通过全连接层分类器。
设备选择
你检查了是否有可用的CUDA设备(GPU),如果有,则使用GPU进行计算,否则使用CPU。这是为了加速训练和推理过程。
模型实例化和设备分配
最后,你实例化了vgg16模型,并且将其移动到了选择的设备(GPU或CPU)上。
import torch.nn.functional as F class vgg16(nn.Module): def __init__(self): super(vgg16, self).__init__() # 卷积块1 self.block1 = nn.Sequential( nn.Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块2 self.block2 = nn.Sequential( nn.Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块3 self.block3 = nn.Sequential( nn.Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块4 self.block4 = nn.Sequential( nn.Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 卷积块5 self.block5 = nn.Sequential( nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), nn.ReLU(), nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)) ) # 全连接网络层,用于分类 self.classifier = nn.Sequential( nn.Linear(in_features=512*7*7, out_features=4096), nn.ReLU(), nn.Linear(in_features=4096, out_features=4096), nn.ReLU(), nn.Linear(in_features=4096, out_features=3) ) def forward(self, x): x = self.block1(x) x = self.block2(x) x = self.block3(x) x = self.block4(x) x = self.block5(x) x = torch.flatten(x, start_dim=1) x = self.classifier(x) return x device = "cuda" if torch.cuda.is_available() else "cpu" print("Using {} device".format(device)) model = vgg16().to(device) model# 统计模型参数量以及其他指标 import torchsummary as summary summary.summary(model, (3, 224, 224))5.循环训练
# 训练循环 def train(dataloader, model, loss_fn, optimizer): size = len(dataloader.dataset) # 训练集的大小 num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整) train_loss, train_acc = 0, 0 # 初始化训练损失和正确率 for X, y in dataloader: # 获取图片及其标签 X, y = X.to(device), y.to(device) # 计算预测误差 pred = model(X) # 网络输出 loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失 # 反向传播 optimizer.zero_grad() # grad属性归零 loss.backward() # 反向传播 optimizer.step() # 每一步自动更新 # 记录acc与loss train_acc += (pred.argmax(1) == y).type(torch.float).sum().item() train_loss += loss.item() train_acc /= size train_loss /= num_batches return train_acc, train_lossdef test (dataloader, model, loss_fn): size = len(dataloader.dataset) # 测试集的大小 num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整) test_loss, test_acc = 0, 0 # 当不进行训练时,停止梯度更新,节省计算内存消耗 with torch.no_grad(): for imgs, target in dataloader: imgs, target = imgs.to(device), target.to(device) # 计算loss target_pred = model(imgs) loss = loss_fn(target_pred, target) test_loss += loss.item() test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item() test_acc /= size test_loss /= num_batches return test_acc, test_loss6.训练和测试循环
初始化优化器和损失函数:
- 使用 Adam 优化器,学习率设置为
1e-4。 - 使用交叉熵损失函数
nn.CrossEntropyLoss。
- 使用 Adam 优化器,学习率设置为
设置训练周期:
epochs设置为 40,即模型将训练 40 个周期。
初始化记录列表:
train_loss、train_acc、test_loss、test_acc用于记录每个周期的训练损失、训练准确率、测试损失和测试准确率。
最佳准确率初始化:
best_acc初始化为 0,用于记录测试准确率最高的模型。
训练和测试周期:
- 对于每个周期,执行以下步骤:
- 将模型设置为训练模式
model.train()。 - 调用
train函数进行训练,并记录训练准确率和损失。 - 将模型设置为评估模式
model.eval()。 - 调用
test函数进行测试,并记录测试准确率和损失。 - 如果当前周期的测试准确率超过
best_acc,则更新best_acc并深拷贝当前模型到best_model。
- 将模型设置为训练模式
- 对于每个周期,执行以下步骤:
记录和打印信息:
- 将每个周期的准确率和损失添加到相应的列表中。
- 打印当前周期的信息,包括周期编号、训练准确率、训练损失、测试准确率、测试损失和当前学习率。
保存最佳模型:
- 训练结束后,将最佳模型的参数保存到文件
best_model.pth中。
- 训练结束后,将最佳模型的参数保存到文件
注意事项和建议
- 确保
train_dl和test_dl已经正确初始化,并且包含正确的数据。 - 确保
device变量已经定义,并且模型和数据已经移动到正确的设备上。 - 在保存模型时,你保存的是
model的状态字典,而不是整个模型对象。这是推荐的做法,因为它只保存模型参数,不包含模型架构。 - 在打印信息时,你使用了格式化字符串来清晰地展示每个周期的结果。
- 你可以考虑添加一个学习率调度器,例如
torch.optim.lr_scheduler.StepLR,以在训练过程中调整学习率。
import copy optimizer = torch.optim.Adam(model.parameters(), lr= 1e-4) loss_fn = nn.CrossEntropyLoss() # 创建损失函数 epochs = 40 train_loss = [] train_acc = [] test_loss = [] test_acc = [] best_acc = 0 # 设置一个最佳准确率,作为最佳模型的判别指标 for epoch in range(epochs): model.train() epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer) model.eval() epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn) # 保存最佳模型到 best_model if epoch_test_acc > best_acc: best_acc = epoch_test_acc best_model = copy.deepcopy(model) train_acc.append(epoch_train_acc) train_loss.append(epoch_train_loss) test_acc.append(epoch_test_acc) test_loss.append(epoch_test_loss) # 获取当前的学习率 lr = optimizer.state_dict()['param_groups'][0]['lr'] template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}') print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss, lr)) # 保存最佳模型到文件中 PATH = './best_model.pth' # 保存的参数文件名 torch.save(model.state_dict(), PATH) print('Done')7.检测图像
函数参数
image_path:要预测的图片的文件路径。model:已经训练好的 PyTorch 模型。transform:用于将图片转换为模型输入格式的转换序列。classes:类别名称列表,通常从数据集的class_to_idx属性中获取。
预测过程
加载图片:
- 使用
PIL.Image.open加载图片,并将其转换为 RGB 格式。
- 使用
展示图片:
- 使用
matplotlib.pyplot.imshow展示图片。注意,这里需要导入matplotlib库。
- 使用
应用转换:
- 使用
transform将图片转换为模型可以接受的格式。
- 使用
数据移动到设备:
- 将转换后的图片移动到指定的设备(GPU或CPU)。
模型评估模式:
- 将模型设置为评估模式
model.eval(),以关闭 dropout 和 batch normalization 等仅在训练时使用的层。
- 将模型设置为评估模式
模型预测:
- 使用模型对图片进行预测,得到输出
output。
- 使用模型对图片进行预测,得到输出
获取预测结果:
- 使用
torch.max获取预测结果中概率最高的类别索引。 - 根据索引从
classes列表中获取对应的类别名称。
- 使用
打印预测结果:
- 打印预测的类别名称。
from PIL import Image classes = list(total_data.class_to_idx) def predict_one_image(image_path, model, transform, classes): test_img = Image.open(image_path).convert('RGB') plt.imshow(test_img) # 展示预测的图片 test_img = transform(test_img) img = test_img.to(device).unsqueeze(0) model.eval() output = model(img) _,pred = torch.max(output,1) pred_class = classes[pred] print(f'预测结果是:{pred_class}')总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。