一、实验目的
1.简要介绍 Vision Transformer(ViT)模型及其在图像分类任务中的应用。
基本概念
- Vision Transformer (ViT) 是一种基于Transformer架构的模型,最初在自然语言处理领域获得成功,后被引入到计算机视觉领域。
- 核心思想:ViT将图像分割成一系列小块(称为patches),这些小块被处理成序列数据,类似于在NLP中处理单词序列。
- 自注意力机制:ViT通过自注意力机制学习图像中不同部分之间的关系,这与传统的CNN使用卷积核在空间上聚焦局部区域形成鲜明对比。
与CNN的演化
- 从CNN到ViT:传统的卷积神经网络(CNN)依赖于局部卷积操作和池化来处理图像,这导致它们在处理图像的全局信息时存在局限性。
- 全局信息处理:ViT通过自注意力机制克服了这一限制,能够在整个图像序列上建立长距离依赖关系。
- 高效性:与CNN相比,ViT在处理高分辨率图像时更为高效,因为它避免了逐层卷积操作中的重复计算
2. 阐述实验目的和重要性。
Vision Transformer在图像分类任务中的应用标志着从局部聚焦的CNN到全局信息处理的转变,这在图像理解和分析中打开了新的可能性。由于其独特的结构和处理方式,ViT在处理复杂和多样化的图像数据方面显示出显著的优势。
二、实验设计:
1. 实验的整体框架和步骤
- 步骤一:数据准备
- 选择猫狗图像数据集:使用专门的猫狗图像数据集,如Kaggle上的猫狗分类挑战数据集。
- 数据预处理:包括调整图像大小、归一化等。
- 步骤二:模型设计与实现
- 设计Vision Transformer模型:根据ViT的架构设置模型参数,调整以适应猫狗图像特性。
- 编写实验代码:实现模型训练、验证和测试的过程。
- 步骤三:模型训练
- 在训练集上训练模型:调整超参数以优化猫狗图像分类的性能。
- 在验证集上进行性能评估。
- 步骤四:模型测试与评估
- 在测试集上评估模型性能。
- 分析结果:分析模型在猫狗分类任务中的表现。
2. Vision Transformer模型的结构和原理
- 针对猫狗分类调整模型结构
- 输入处理:根据猫狗图像的特点调整patch大小。
- 位置嵌入和Transformer编码器:同标准ViT模型。
- 分类头:设计为二分类输出。
- 模型原理
- 自注意力机制:学习猫和狗图像中关键特征的相关性。
- 层序连接和分类头:适用于猫狗分类任务。
3. 图像数据的预处理与划分
- 预处理
- 调整大小:统一图像尺寸,如将所有图像调整为256x256像素。
- 归一化:将像素值归一化到0-1范围。
- 数据增强:应用如随机裁剪、旋转等技术增加数据多样性。
- 数据划分
- 训练集:选取数据集的大部分(例如70%)用于模型训练。
- 验证集:约15%的数据用于模型的性能调优。
- 测试集:剩余的15%用于最终的性能评估。
三、实验过程:
1. 软件和硬件环境
- 软件环境
- 编程语言:Python。
- 深度学习框架:PyTorc
2. 模型训练过程
- 超参数的选择
- 学习率:开始时可以设置较大的学习率(例如0.001),并在训练过程中逐步降低。
- 批处理大小:根据GPU内存容量调整(例如32或64)。
- Epoch数量:设置足够多的epoch以确保充分训练(例如100个epoch)。
- 优化器的设置
- 优化器选择:通常使用Adam优化器,因其在深度学习模型中表现良好。
- 正则化技术:如权重衰减(L2正则化)以防止过拟合。
3. 训练和调优策略
- 损失函数的选择
- 对于猫狗二分类任务,使用二元交叉熵损失(Binary Cross-Entropy Loss)。
- 训练策略
- 早期停止:监控验证集的损失,如果在连续几个epoch中没有明显改善,则提前终止训练。
- 学习率调整:使用学习率衰减策略,如在验证集损失停止改善时降低学习率。
- 数据增强:在训练过程中应用数据增强技术,增加模型的泛化能力。
- 调优过程
- 超参数调优:通过实验不同的超参数组合来找到最佳配置。
- 性能监控:定期检查模型在训练集和验证集上的性能,确保模型正在学习并且没有过拟合。
- 结果记录:记录不同超参数设置下的训练结果,以便进行比较和分析。
四、实验结果:
1. 验证集上的模型表现
Loss-Acc图:
ROC曲线和AUC值:
- 性能指标
- 准确率:表示模型正确分类图像的比例。
- 召回率:特别针对每个类别(猫或狗),召回率显示了模型正确识别该类别图像的能力。
- 精确度:精确度衡量的是在预测为特定类别的图像中,实际上属于该类别的比例。
- F1分数:F1分数是精确度和召回率的调和平均,用于评估模型的整体性能。
- 性能图表
- 使用混淆矩阵来可视化模型在不同类别上的性能。
- 绘制ROC曲线和AUC值,以评估模型在不同阈值下的分类能力。
2. 实验结果分析
- 优点分析
- 全局特征学习:Vision Transformer因其自注意力机制能够捕捉图像的全局特征,这可能导致在特定类型的图像上表现出色。
- 泛化能力:如果模型在多种类型的猫狗图像(如不同品种、背景等)上表现良好,这表明它具有良好的泛化能力。
- 高准确率:一个高准确率表明模型在大多数情况下能够正确分类图像。
- 不足之处
- 对特定特征的敏感性:如果模型在某些特定类型的图像(如特定背景或照明条件下的图像)上表现不佳,这可能暗示模型对于某些特征过于敏感。
- 计算资源需求:ViT模型可能需要较多的计算资源,这在实际应用中可能是一个限制因素。
- 训练时间:与某些传统CNN模型相比,ViT可能需要更长的训练时间,特别是在缺乏优化时。
五、结论:
实验的局限性和可改进之处
- 数据集局限性:指出如果实验只用了特定类型的猫狗图像数据集,可能限制了模型泛化到更广泛场景的能力。
- 计算资源需求:讨论实验中ViT模型相对较高的计算资源需求,以及这可能对实际应用造成的限制。
- 训练时间和成本:指出模型训练所需的时间和成本,特别是在资源受限的环境中,这可能是一个重要考虑因素。
- 改进建议:
- 数据多样性:增加数据集的多样性,包括不同的图像质量、光照条件、背景等,以进一步测试和提高模型的泛化能力。
- 模型优化:探索模型架构和训练过程的优化,以减少资源消耗和提高训练效率。
- 后续研究方向:建议未来研究可以探索将ViT与其他技术(如CNN)结合,以利用各自的优点,或开发更轻量级的ViT变体。
总体而言,这项实验展示了Vision Transformer在猫狗图像分类任务上的有效性和潜力,同时也揭示了其在数据和计算资源方面的一些局限性。未来的研究可以在这些发现的基础上进行,以实现更广泛的应用和更优的性能。
实验代码:
import copy
import torch
import torchvision
import torchvision.transforms as transforms
from torchvision import models
from torch.utils.data import DataLoader
from torch import optim, nn
from torch.optim import lr_scheduler
import os
import matplotlib.pyplot as plt
import warnings
import numpy as np
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 设置GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
import torch
from torchvision import datasets, transforms
import os
# 数据集路径
data_dir = "E:\深度学习\Vision Transformer\cats_and_dogs_small"
# 图像的大小
img_height = 224
img_width = 224
# 数据预处理
data_transforms = {
'train': transforms.Compose([
transforms.RandomResizedCrop(img_height),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.RandomRotation(0.2),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'val': transforms.Compose([
transforms.Resize((img_height, img_width)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}
# 加载数据集
full_dataset = datasets.ImageFolder(data_dir)
# 获取数据集的大小
full_size = len(full_dataset)
train_size = int(0.7 * full_size) # 假设训练集占80%
val_size = full_size - train_size # 验证集的大小
# 随机分割数据集
torch.manual_seed(0) # 设置随机种子以确保结果可重复
train_dataset, val_dataset = torch.utils.data.random_split(full_dataset, [train_size, val_size])
# 将数据增强应用到训练集
train_dataset.dataset.transform = data_transforms['train']
# 创建数据加载器
batch_size = 32
train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
val_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
dataloaders = {'train': train_dataloader, 'val': val_dataloader}
dataset_sizes = {'train': len(train_dataset), 'val': len(val_dataset)}
class_names = full_dataset.classes
# 定义Vision Transformer模型
import timm
model = timm.create_model('vit_base_patch16_224',
pretrained=True) # 你可以选择适合你需求的Vision Transformer版本,这里以vit_base_patch16_224为例
num_ftrs = model.head.in_features
# 根据分类任务修改最后一层
model.head = nn.Linear(num_ftrs, len(class_names))
model = model.to(device)
# 打印模型摘要
print(model)
# 定义损失函数
criterion = nn.CrossEntropyLoss()
# 定义优化器
optimizer = optim.Adam(model.parameters())
# 定义学习率调度器
exp_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)
# 开始训练模型
num_epochs = 10
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
# 初始化记录器
train_loss_history = []
train_acc_history = []
val_loss_history = []
val_acc_history = []
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch, num_epochs - 1))
print('-' * 10)
# 每个epoch都有一个训练和验证阶段
for phase in ['train', 'val']:
if phase == 'train':
model.train() # Set model to training mode
else:
model.eval() # Set model to evaluate mode
running_loss = 0.0
running_corrects = 0
# 遍历数据
for inputs, labels in dataloaders[phase]:
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)
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = (running_corrects.double() / dataset_sizes[phase]).item()
# 记录每个epoch的loss和accuracy
if phase == 'train':
train_loss_history.append(epoch_loss)
train_acc_history.append(epoch_acc)
else:
val_loss_history.append(epoch_loss)
val_acc_history.append(epoch_acc)
print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
# 深拷贝模型
if phase == 'val' and epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
print()
print('Best val Acc: {:4f}'.format(best_acc))
epoch = range(1, len(train_loss_history) + 1)
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
ax[0].plot(epoch, train_loss_history, label='Train loss')
ax[0].plot(epoch, val_loss_history, label='Validation loss')
ax[0].set_xlabel('Epochs')
ax[0].set_ylabel('Loss')
ax[0].legend()
ax[1].plot(epoch, train_acc_history, label='Train acc')
ax[1].plot(epoch, val_acc_history, label='Validation acc')
ax[1].set_xlabel('Epochs')
ax[1].set_ylabel('Accuracy')
ax[1].legend()
plt.savefig("loss-acc.pdf", dpi=300,format="pdf")
from sklearn.metrics import classification_report, confusion_matrix
import math
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib.pyplot import imshow
# 定义一个绘制混淆矩阵图的函数
def plot_cm(labels, predictions):
# 生成混淆矩阵
conf_numpy = confusion_matrix(labels, predictions)
# 将矩阵转化为 DataFrame
conf_df = pd.DataFrame(conf_numpy, index=class_names, columns=class_names)
plt.figure(figsize=(8, 7))
sns.heatmap(conf_df, annot=True, fmt="d", cmap="BuPu")
plt.title('Confusion matrix', fontsize=15)
plt.ylabel('Actual value', fontsize=14)
plt.xlabel('Predictive value', fontsize=14)
def evaluate_model(model, dataloader, device):
model.eval() # 设置模型为评估模式
true_labels = []
pred_labels = []
# 遍历数据
for inputs, labels in dataloader:
inputs = inputs.to(device)
labels = labels.to(device)
# 前向
with torch.no_grad():
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
true_labels.extend(labels.cpu().numpy())
pred_labels.extend(preds.cpu().numpy())
return true_labels, pred_labels
# 获取预测和真实标签
true_labels, pred_labels = evaluate_model(model, dataloaders['val'], device)
# 计算混淆矩阵
cm_val = confusion_matrix(true_labels, pred_labels)
a_val = cm_val[0, 0]
b_val = cm_val[0, 1]
c_val = cm_val[1, 0]
d_val = cm_val[1, 1]
# 计算各种性能指标
acc_val = (a_val + d_val) / (a_val + b_val + c_val + d_val) # 准确率
error_rate_val = 1 - acc_val # 错误率
sen_val = d_val / (d_val + c_val) # 灵敏度
sep_val = a_val / (a_val + b_val) # 特异度
precision_val = d_val / (b_val + d_val) # 精确度
F1_val = (2 * precision_val * sen_val) / (precision_val + sen_val) # F1值
MCC_val = (d_val * a_val - b_val * c_val) / (
np.sqrt((d_val + b_val) * (d_val + c_val) * (a_val + b_val) * (a_val + c_val))) # 马修斯相关系数
# 打印出性能指标
print("验证集的灵敏度为:", sen_val,
"验证集的特异度为:", sep_val,
"验证集的准确率为:", acc_val,
"验证集的错误率为:", error_rate_val,
"验证集的精确度为:", precision_val,
"验证集的F1为:", F1_val,
"验证集的MCC为:", MCC_val)
# 绘制混淆矩阵
plot_cm(true_labels, pred_labels)
# 获取预测和真实标签
train_true_labels, train_pred_labels = evaluate_model(model, dataloaders['train'], device)
# 计算混淆矩阵
cm_train = confusion_matrix(train_true_labels, train_pred_labels)
a_train = cm_train[0, 0]
b_train = cm_train[0, 1]
c_train = cm_train[1, 0]
d_train = cm_train[1, 1]
acc_train = (a_train + d_train) / (a_train + b_train + c_train + d_train)
error_rate_train = 1 - acc_train
sen_train = d_train / (d_train + c_train)
sep_train = a_train / (a_train + b_train)
precision_train = d_train / (b_train + d_train)
F1_train = (2 * precision_train * sen_train) / (precision_train + sen_train)
MCC_train = (d_train * a_train - b_train * c_train) / (
math.sqrt((d_train + b_train) * (d_train + c_train) * (a_train + b_train) * (a_train + c_train)))
print("训练集的灵敏度为:", sen_train,
"训练集的特异度为:", sep_train,
"训练集的准确率为:", acc_train,
"训练集的错误率为:", error_rate_train,
"训练集的精确度为:", precision_train,
"训练集的F1为:", F1_train,
"训练集的MCC为:", MCC_train)
# 绘制混淆矩阵
plot_cm(train_true_labels, train_pred_labels)
from sklearn import metrics
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns
import pandas as pd
import math
def plot_roc(name, labels, predictions, **kwargs):
fp, tp, _ = metrics.roc_curve(labels, predictions)
plt.plot(fp, tp, label=name, linewidth=2, **kwargs)
plt.plot([0, 1], [0, 1], color='orange', linestyle='--')
plt.xlabel('False positives rate')
plt.ylabel('True positives rate')
ax = plt.gca()
ax.set_aspect('equal')
# 确保模型处于评估模式
model.eval()
train_ds = dataloaders['train']
val_ds = dataloaders['val']
val_pre_auc = []
val_label_auc = []
for images, labels in val_ds:
for image, label in zip(images, labels):
img_array = image.unsqueeze(0).to(device) # 在第0维增加一个维度并将图像转移到适当的设备上
prediction_auc = model(img_array) # 使用模型进行预测
val_pre_auc.append(prediction_auc.detach().cpu().numpy()[:, 1])
val_label_auc.append(label.item()) # 使用Tensor.item()获取Tensor的值
auc_score_val = metrics.roc_auc_score(val_label_auc, val_pre_auc)
train_pre_auc = []
train_label_auc = []
for images, labels in train_ds:
for image, label in zip(images, labels):
img_array_train = image.unsqueeze(0).to(device)
prediction_auc = model(img_array_train)
train_pre_auc.append(prediction_auc.detach().cpu().numpy()[:, 1]) # 输出概率而不是标签!
train_label_auc.append(label.item())
auc_score_train = metrics.roc_auc_score(train_label_auc, train_pre_auc)
plot_roc('validation AUC: {0:.4f}'.format(auc_score_val), val_label_auc, val_pre_auc, color="red", linestyle='--')
plot_roc('training AUC: {0:.4f}'.format(auc_score_train), train_label_auc, train_pre_auc, color="blue", linestyle='--')
plt.legend(loc='lower right')
# plt.savefig("roc.pdf", dpi=300,format="pdf")
print("训练集的AUC值为:", auc_score_train, "验证集的AUC值为:", auc_score_val)