目录
依赖环境
代码
导入依赖包
定义数据集路径:
创建训练集、验证集和测试集的文件夹:
代码的作用:
设置新的数据集路径与类别名称
代码的作用:
定义数据预处理和增强变换:
代码的作用:
定义数据集评估划分与batch大小
代码的作用:
可视化
代码的作用:
评估可视化
代码的作用:
网络结构定义
代码的作用:
定义损失函数和优化器,并训练模型
模型可视化评估
代码的作用:
下载地址:
python深度学习pytorch水稻图像分类完整案例
依赖环境
!pip install split-folders
!pip install torch-summary
!pip install torch matplotplib
代码
导入依赖包
import os
import pathlib
import numpy as np
import splitfolders
import itertools
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from termcolor import colored
from datetime import datetime
import warnings
from tqdm.notebook import tqdm
from sklearn.metrics import confusion_matrix, classification_report
from torchsummary import summary
from numpy import asarray
from PIL import Image
os
和pathlib
用于文件和目录操作。numpy
用于数组和数值计算。splitfolders
用于分割数据集。itertools
提供迭代生成器。matplotlib.pyplot
用于绘图和数据可视化。torch
、torch.nn
、torch.nn.functional
、torch.optim
、torchvision
和torchvision.transforms
用于构建和训练神经网络模型。termcolor
用于终端中的彩色输出。datetime
用于时间操作。warnings
用于忽略警告信息。tqdm.notebook
用于显示进度条。sklearn.metrics
提供评估指标,包括混淆矩阵和分类报告。torchsummary
用于总结模型结构。numpy.asarray
和PIL.Image
用于图像处理。
定义数据集路径:
data = './Rice_Image_Dataset'
data = pathlib.Path(data)
创建训练集、验证集和测试集的文件夹:
splitfolders.ratio(input=data, output='rice_imgs', seed=42, ratio=(0.7, 0.15, 0.15))
- 使用
splitfolders.ratio
函数将数据集按照 7:1.5:1.5 的比例划分为训练集、验证集和测试集。 input
参数指定输入数据集的路径。output
参数指定输出文件夹的名称'rice_imgs'
,划分后的数据集将保存在这个文件夹中。seed
参数设置随机种子,以确保划分结果的可重复性。ratio
参数指定训练集、验证集和测试集的比例,分别为 70%、15% 和 15%。
代码的作用:
这段代码通过 splitfolders
库将原始的水稻图像数据集划分为训练集、验证集和测试集,以便在模型训练、验证和测试过程中使用不同的数据子集,从而提高模型的泛化能力和评估准确性。
设置新的数据集路径与类别名称
root_dir = './rice_imgs'
root_dir = pathlib.Path(root_dir)
Arborio='./Rice_Image_Dataset/Arborio'
Arborio_classes=os.listdir(Arborio)
Rice_classes = os.listdir(root_dir)
batchsize=8
from colorama import Fore, Style
print(Fore.GREEN +str(Rice_classes))
print(Fore.YELLOW +"\nTotal number of classes are: ", len(Rice_classes))
root_dir
定义为之前划分后的数据集路径'./rice_imgs'
,并转换为路径对象。Arborio
定义为原始数据集中某个类别的路径。- 使用
os.listdir(Arborio)
获取Arborio
类别中的所有文件和文件夹名称。 - 使用
os.listdir(root_dir)
获取新的数据集路径中的所有类别名称。 - 导入
colorama
库中的Fore
和Style
,用于终端输出的颜色设置。
代码的作用:
这段代码的主要作用是设置新的数据集路径,并获取数据集中各个类别的名称,以便在后续的数据加载和处理过程中使用。此外,代码还打印出了类别名称和总数,以便进行检查和验证。
定义数据预处理和增强变换:
transform = transforms.Compose(
[
transforms.Resize((250,250)),
transforms.ToTensor(),
transforms.Normalize((0),(1)),
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(30),
]
)
transforms.Compose
:将多个变换操作组合在一起。transforms.Resize((250,250))
:将图像调整为 250x250 的固定尺寸。transforms.ToTensor()
:将图像转换为 PyTorch 的张量格式,并将像素值归一化到 [0,1] 的范围。transforms.Normalize((0),(1))
:标准化图像,使图像的每个通道均值为 0,标准差为 1。transforms.RandomHorizontalFlip()
:随机水平翻转图像,用于数据增强,以增加模型的泛化能力。transforms.RandomRotation(30)
:随机旋转图像最多 30 度,用于数据增强。
代码的作用:
这段代码通过定义一个数据预处理和增强的变换流水线,在加载图像数据时自动对图像进行调整大小、转换为张量、标准化、随机水平翻转和随机旋转等操作。这些预处理和增强操作有助于提高模型的训练效果和泛化能力。
定义数据集评估划分与batch大小
import torch.utils.data
batch_size = 32
# Read train images as a dataset
train_set = torchvision.datasets.ImageFolder(
os.path.join(root_dir, 'train'), transform=transform
)
# Create a Data Loader
train_loader = torch.utils.data.DataLoader(
train_set, batch_size = batch_size, shuffle=True
)
print(colored(f'Train Folder :\n ', 'green', attrs=['bold']))
print(train_set)
print('_'*100)
#############################################################
# Read validation images as a dataset
val_set = torchvision.datasets.ImageFolder(
os.path.join(root_dir, 'val'), transform=transform
)
# Create a Data Loader
val_loader = torch.utils.data.DataLoader(
val_set, batch_size = batch_size, shuffle=True
)
print(colored(f'Validation Folder :\n ', 'red', attrs=['bold']))
print(val_set)
print('_'*100)
#############################################################
# Read test images as a dataset
test_set = torchvision.datasets.ImageFolder(
os.path.join(root_dir, 'test'), transform=transform
)
# Create a Data Loader
test_loader = torch.utils.data.DataLoader(
test_set, batch_size = batch_size, shuffle=True
)
print(colored(f'Test Folder :\n ', 'yellow', attrs=['bold']))
print(test_set)
-
设置批量大小:
- 首先导入
torch.utils.data
,并设置批量大小为 32,用于后续的数据加载器中。
- 首先导入
-
加载训练集数据并创建数据加载器:
- 使用
torchvision.datasets.ImageFolder
函数加载训练集图像数据,并应用之前定义的变换transform
。os.path.join(root_dir, 'train')
指定了训练集数据的路径。 - 创建一个数据加载器
train_loader
,它从train_set
中以批量的形式读取数据,batch_size
设置为 32,并启用了随机打乱shuffle=True
。
- 使用
-
打印训练集信息:
- 使用
colored
函数将输出文本设置为绿色,并打印训练集文件夹的信息和训练集数据集对象。
- 使用
-
加载验证集数据并创建数据加载器:
- 类似于加载训练集,使用
torchvision.datasets.ImageFolder
函数加载验证集图像数据,并应用相同的变换transform
。os.path.join(root_dir, 'val')
指定了验证集数据的路径。 - 创建一个数据加载器
val_loader
,从val_set
中以批量的形式读取数据,batch_size
设置为 32,并启用了随机打乱shuffle=True
。
- 类似于加载训练集,使用
-
打印验证集信息:
- 使用
colored
函数将输出文本设置为红色,并打印验证集文件夹的信息和验证集数据集对象。
- 使用
-
加载测试集数据并创建数据加载器:
- 类似于加载训练集和验证集,使用
torchvision.datasets.ImageFolder
函数加载测试集图像数据,并应用相同的变换transform
。os.path.join(root_dir, 'test')
指定了测试集数据的路径。 - 创建一个数据加载器
test_loader
,从test_set
中以批量的形式读取数据,batch_size
设置为 32,并启用了随机打乱shuffle=True
。
- 类似于加载训练集和验证集,使用
-
打印测试集信息:
- 使用
colored
函数将输出文本设置为黄色,并打印测试集文件夹的信息和测试集数据集对象。
- 使用
代码的作用:
这段代码通过加载和处理训练集、验证集和测试集的数据,创建了相应的数据加载器。这些加载器将在模型训练和评估过程中使用,以批量的形式高效地读取和处理图像数据,从而提高模型训练和评估的效率。
可视化
# 可视化数据集以进行检查
# 首先创建一个包含标签名称的字典
labels_map = {
0: "Arborio",
1: "Basmati",
2: "Ipsala",
3: "Jasmine",
4: "Karacadag",
}
figure = plt.figure(figsize=(10, 10))
cols, rows = 5, 5
for i in range(1, cols * rows + 1):
sample_idx = torch.randint(len(train_set), size=(1,)).item()
img, label = train_set[sample_idx]
figure.add_subplot(rows, cols, i)
plt.title(labels_map[label])
plt.axis("off")
img_np = img.numpy().transpose((1, 2, 0))
# 将像素值剪辑到 [0, 1] 范围内
img_valid_range = np.clip(img_np, 0, 1)
plt.imshow(img_valid_range)
plt.suptitle('Rice Images', y=0.95)
plt.show()
-
创建标签字典:
- 创建一个字典
labels_map
,将类别索引映射到类别名称,方便后续的可视化和检查。
- 创建一个字典
-
创建可视化图形:
- 使用
plt.figure
创建一个图形对象,设置图形大小为 10x10。 - 设置图形的列数和行数为 5,意味着将显示 25 个图像样本。
- 使用
-
可视化图像样本:
- 使用
torch.randint
随机选择训练集中图像样本的索引。 - 从
train_set
中获取图像和对应的标签。 - 将图像添加到图形的子图中,并设置子图的标题为对应的类别名称。
- 关闭子图的坐标轴显示。
- 将图像从张量格式转换为 NumPy 数组格式,并进行转置以匹配
plt.imshow
的输入格式。 - 使用
np.clip
将图像的像素值限制在 [0, 1] 范围内,以确保显示的图像颜色正常。 - 显示图像样本,并设置图形的整体标题为 "Rice Images"。
- 使用
代码的作用:
这段代码通过随机选择并显示训练集中的图像样本,以及相应的类别名称,帮助检查数据是否正确加载,并提供对数据分布的直观理解。可视化的图像可以帮助确认图像增强和预处理步骤是否按照预期执行。
评估可视化
def train(model, train_loader, validation_loader, device,
loss_fn, optimizer, num_epochs, patience=3):
"""
训练模型:
model: 创建的模型
train_loader: 使用DataLoader加载的训练集
validation_loader: 使用DataLoader加载的验证集
device: 训练模型可用的设备(CPU或CUDA)
loss_fn: 定义的损失函数
optimizer: 定义的优化器
num_epochs (int): 训练的轮数
patience (int): 用于早停的耐心参数
"""
history = {
'train_loss': [],
'val_loss': [],
'train_acc': [],
'val_acc': []
}
epoch = 0
best_val_loss = float('inf')
best_model_weights = None
early_stopping_counter = 0
while epoch < num_epochs and early_stopping_counter < patience:
model.train()
train_loss = 0.0
correct = 0
total = 0
pbar = tqdm(enumerate(train_loader), ncols=600, total=len(train_loader))
for batch_idx, (inputs, labels) in pbar:
pbar.set_description(f'Epoch {epoch+1}/{num_epochs} ')
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = loss_fn(outputs, labels)
loss.backward()
optimizer.step()
train_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
train_loss /= len(train_loader)
train_acc = correct / total
history['train_loss'].append(train_loss)
history['train_acc'].append(train_acc)
val_loss, val_acc = evaluate(model, validation_loader, device, loss_fn)
history['val_loss'].append(val_loss)
history['val_acc'].append(val_acc)
# 打印进度
print(f'train_loss: {train_loss:.4f} | '
f'train_acc: {train_acc:.4f} | ' +
f'val_loss: {val_loss:.4f} | ' +
f'val_acc: {val_acc:.4f}', '\n')
if history['val_loss'][-1] < best_val_loss:
best_model_weights = model.state_dict()
early_stopping_counter = 0
else:
early_stopping_counter += 1
best_val_loss = history['val_loss'][-1]
epoch += 1
return history, best_model_weights
# ---------------------------------------------------------------
def evaluate(model, data_loader, device, loss_fn):
"""
评估模型并返回损失和准确率:
model: 创建的模型
data_loader: 测试或验证集加载器
device: 训练模型可用的设备(CPU或CUDA)
loss_fn: 定义的损失函数
"""
model.eval()
total_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in data_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
loss = loss_fn(outputs, labels)
total_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
val_loss = total_loss / len(data_loader)
val_acc = correct / total
return val_loss, val_acc
# -------------------------------------------------------------------
def plot_comparision_result(model):
"""
绘制训练和验证集准确率和损失的对比图:
model: 创建的模型
"""
fig, axs = plt.subplots(2, 1, figsize=(10, 12))
# 绘制训练和验证集的准确率
axs[0].plot(model['history']['train_acc'], color="red", marker="o")
axs[0].plot(model['history']['val_acc'], color="blue", marker="h")
axs[0].set_title('训练集和验证集准确率对比')
axs[0].set_ylabel('准确率')
axs[0].set_xlabel('轮次')
axs[0].legend(['训练集', '验证集'], loc="lower right")
# 绘制训练和验证集的损失
axs[1].plot(model['history']['train_loss'], color="red", marker="o")
axs[1].plot(model['history']['val_loss'], color="blue", marker="h")
axs[1].set_title('训练集和验证集损失对比')
axs[1].set_ylabel('损失')
axs[1].set_xlabel('轮次')
axs[1].legend(['训练集', '验证集'], loc="upper right")
plt.tight_layout()
plt.show()
# -------------------------------------------------------------------
# 定义函数以创建包含实际标签和预测标签的两个列表
def get_ture_and_pred_labels(dataloader, model):
"""
获取包含实际标签和预测标签的两个列表,用于混淆矩阵:
dataloader: 数据加载器
model: 创建的模型
"""
i = 0
y_true = []
y_pred = []
for images, labels in dataloader:
images = images.to(device)
labels = labels.numpy()
outputs = model(images)
_, pred = torch.max(outputs.data, 1)
pred = pred.detach().cpu().numpy()
y_true = np.append(y_true, labels)
y_pred = np.append(y_pred, pred)
return y_true, y_pred
# ------------------------------------------------------------------
def plot_confusion_matrix(cm, classes,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
绘制混淆矩阵:
cm(array): 混淆矩阵
classes(dictionary): 目标类别(key=分类类型,value=数值类型)
"""
plt.figure(figsize=(10,7))
plt.grid(False)
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, [f"{value}={key}" for key , value in classes.items()], rotation=45)
plt.yticks(tick_marks, [f"{value}={key}" for key , value in classes.items()])
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, f"{cm[i,j]}\n{cm[i,j]/np.sum(cm)*100:.2f}%",
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.ylabel('实际值')
plt.xlabel('预测值')
plt.tight_layout()
plt.show()
代码的作用:
-
train
函数:- 训练模型并记录训练和验证集的损失和准确率。
- 实现早停机制以防止过拟合。
-
evaluate
函数:- 评估模型在给定数据集上的表现,返回损失和准确率。
-
plot_comparision_result
函数:- 绘制训练和验证集的准确率和损失随训练轮次变化的对比图。
-
get_ture_and_pred_labels
函数:- 获取实际标签和预测标签的列表,用于计算混淆矩阵。
-
plot_confusion_matrix
函数:- 绘制混淆矩阵以评估分类模型的性能,显示分类结果的准确性。
网络结构定义
# 定义第二个卷积神经网络模型:包含两个卷积层和两个池化层
class Model(nn.Module):
def __init__(self, dim_output):
super().__init__()
self.conv1 = nn.Conv2d(3, 6, 5) # 第一个卷积层,输入通道数为3(RGB图像),输出通道数为6,卷积核大小为5x5
self.pool = nn.MaxPool2d(2, 2) # 最大池化层,窗口大小为2x2
self.conv2 = nn.Conv2d(6, 16, 5) # 第二个卷积层,输入通道数为6,输出通道数为16,卷积核大小为5x5
self.fc1 = nn.Linear(16 * 59 * 59, 120) # 第一个全连接层,输入维度为16*59*59,输出维度为120
self.fc2 = nn.Linear(120, 84) # 第二个全连接层,输入维度为120,输出维度为84
self.fc3 = nn.Linear(84, dim_output) # 第三个全连接层,输入维度为84,输出维度为类别数
def forward(self, x):
x = self.pool(F.relu(self.conv1(x))) # 第一个卷积层后接ReLU激活函数和池化层
x = self.pool(F.relu(self.conv2(x))) # 第二个卷积层后接ReLU激活函数和池化层
x = torch.flatten(x, 1) # 展平操作,展平所有维度除了批量维度
x = F.relu(self.fc1(x)) # 第一个全连接层后接ReLU激活函数
x = F.relu(self.fc2(x)) # 第二个全连接层后接ReLU激活函数
x = self.fc3(x) # 第三个全连接层
return x
model_ = Model(5) # 实例化模型,类别数为5
summary(model_, (3, 250, 250)) # 打印模型结构和参数信息,输入图像尺寸为3x250x250
-
定义卷积神经网络模型:
__init__
方法中定义了两个卷积层、两个池化层和三个全连接层。conv1
:第一个卷积层,输入通道数为3,输出通道数为6,卷积核大小为5x5。pool
:最大池化层,窗口大小为2x2。conv2
:第二个卷积层,输入通道数为6,输出通道数为16,卷积核大小为5x5。fc1
:第一个全连接层,输入维度为165959,输出维度为120。fc2
:第二个全连接层,输入维度为120,输出维度为84。fc3
:第三个全连接层,输入维度为84,输出维度为类别数(即输出维度)。
-
定义前向传播:
forward
方法定义了前向传播过程。- 输入图像依次通过卷积层、激活函数、池化层、展平操作和全连接层。
- 最后输出的结果用于分类任务。
-
实例化模型:
model_ = Model(5)
:实例化模型,类别数为5。
-
显示模型结构和参数信息:
summary(model_, (3, 250, 250))
:使用torchsummary
显示模型的结构和参数信息,输入图像尺寸为3x250x250。
代码的作用:
这段代码定义了一个用于图像分类的卷积神经网络模型,并显示了模型的结构和参数信息。这有助于了解模型的层次结构和参数量,为后续的模型训练和评估做准备。
定义损失函数和优化器,并训练模型
# define a Loss function and optimizer for model_2
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(model_.parameters(), lr=0.001)
# train model_
history_2, best_model_weights_2 = train(model_, train_loader, val_loader,
device, loss_fn, optimizer, num_epochs=5, patience=5)
模型可视化评估
size_histories = {}
# 存储训练结果
size_histories['Model_'] = {'history': history_2, 'weights': best_model_weights_2}
# 绘制模型在每个训练轮次的准确率和损失图
plot_comparision_result(size_histories['Model_'])
# 检查混淆矩阵进行错误分析
y_true, y_pred = get_ture_and_pred_labels(val_loader, model_)
print(classification_report(y_true, y_pred), '\n\n')
cm = confusion_matrix(y_true, y_pred)
classes = {
"Arborio": 0,
"Basmati": 1,
"Ipsala": 2,
"Jasmine": 3,
"Karacadag": 4,
}
plot_confusion_matrix(cm, classes, title='Confusion matrix', cmap=plt.cm.Blues)
-
存储训练结果:
- 初始化一个字典
size_histories
用于存储不同模型的训练结果。 - 将
Model_
模型的训练历史记录和最佳模型权重存储在size_histories['Model_']
中。
- 初始化一个字典
-
绘制模型的准确率和损失图:
- 调用
plot_comparision_result
函数,绘制模型在每个训练轮次的准确率和损失图。该函数将绘制训练集和验证集的准确率和损失随训练轮次变化的对比图,以便直观地评估模型的性能。
- 调用
-
检查混淆矩阵进行错误分析:
- 使用
get_ture_and_pred_labels
函数获取验证集中真实标签和预测标签的列表y_true
和y_pred
。 - 打印分类报告
classification_report(y_true, y_pred)
,显示每个类别的精度、召回率和F1分数。 - 计算混淆矩阵
cm
,显示模型在验证集上的分类错误情况。 - 定义类别名称和对应的数值标签字典
classes
。 - 调用
plot_confusion_matrix
函数,绘制混淆矩阵图,显示各类别的分类结果。
- 使用
代码的作用:
这段代码将模型的训练结果进行存储,并通过绘制准确率和损失图帮助评估模型在训练过程中的表现。通过分类报告和混淆矩阵,可以详细分析模型在验证集上的分类效果,识别模型在不同类别上的分类准确性以及存在的错误,从而为模型的优化提供依据。这种详细的错误分析对于提高模型的性能和泛化能力具有重要意义。