基于pytorch的运动鞋识别实验
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
Ⅰ Ⅰ Ⅰ Introduction:
- 本文为机器学习素材来自imagenet的运动鞋品牌分类案例。
- 学习目标:
- 编写一个完整的深度学习程序
- 了解如何设置动态学习率
- 调整代码使测试集的acc到达84%(合格)或86%(拔高)
Ⅱ Ⅱ Ⅱ Experiment:
- 数据准备与任务分析:
数据通过网络下载完成,该试验任务是开发一个基于PyTorch的运动鞋识别系统,能够准确地识别不同品牌的运动鞋。
数据集是带标签的,分为nike和adidas两类。 - 配置环境:
语言环境:python 3.8
编译器: pycharm
深度学习环境:
torch2.11
cuda12.1
torchvision0.15.2a0 - 构建网络:
为了提高模型性能,选择输入为3通道,经过4层卷积2层池化以及两层全连接输出最终结果,同时训练中加入BN与dropout方法。
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self, config ,classNames):
super(Model, self).__init__()
self.conv1=nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, padding=1), # 12*220*220
nn.BatchNorm2d(64),
nn.ReLU())
self.conv2=nn.Sequential(
nn.Conv2d(64, 64, kernel_size=3, padding=1), # 12*216*216
nn.BatchNorm2d(64),
nn.ReLU())
self.pool3=nn.Sequential(
nn.MaxPool2d(2)) # 12*108*108
self.conv4=nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, padding=1), # 24*104*104
nn.BatchNorm2d(128),
nn.ReLU())
self.conv5=nn.Sequential(
nn.Conv2d(128, 256, kernel_size=3, padding=1), # 24*100*100
nn.BatchNorm2d(256), #256*56*56
nn.ReLU())
self.pool6=nn.Sequential(
nn.MaxPool2d(2)) # 24*50*50
self.dropout = nn.Sequential(
nn.Dropout(0.1))
self.fc=nn.Sequential(
nn.Linear(256*28*28*4, 1024))
self.fc2 = nn.Sequential(
nn.Linear(1024, len(classNames)))
def forward(self, x):
batch_size = x.size(0)
x = self.conv1(x) # 卷积-BN-激活
x = self.conv2(x) # 卷积-BN-激活
x = self.pool3(x) # 池化
x = self.conv4(x) # 卷积-BN-激活
x = self.conv5(x) # 卷积-BN-激活
x = self.pool6(x) # 池化
x = self.dropout(x)
x = x.view(batch_size, -1) # flatten 变成全连接网络需要的输入 (batch, 24*50*50) ==> (batch, -1), -1 此处自动算出的是24*50*50
x = self.fc(x)
x = self.fc2(x)
return x
- 训练模型:
模型的损失函数选用交叉熵,通过以下代码对模型进行更新:
def update_model(config, dataloader, model, loss_fn, optimizer):
device = config.device
loss_fn = nn.CrossEntropyLoss()
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_loss
- 测试模型:
通过以下代码完成评估:
def eval_model(config, testdl, model, loss_fn):
device = config.device
size = len(testdl.dataset)
num_batches = len(testdl)
test_loss, test_acc = 0, 0
with torch.no_grad():
for imgs, target in testdl:
imgs, target = imgs.to(device), target.to(device)
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_loss
- 实验结果及可视化:
训练代码执行如下:
def train(config, model, traindl, testdl):
device = config.device
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
epochs = config.epochs
lr = config.lr
optimizer = torch.optim.SGD(model.parameters(), lr=lr)
train_loss = []
train_acc = []
test_loss = []
test_acc = []
for epoch in range(epochs):
adjust_learning_rate(optimizer, epoch, lr)
model.train()
epoch_train_acc, epoch_train_loss = update_model(config, traindl, model, loss_fn, optimizer)
model.eval()
epoch_test_acc, epoch_test_loss = eval_model(config, testdl, model, loss_fn)
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))
print('Done')
pic(epochs, train_acc, train_loss, test_acc, test_loss)
绘图代码如下:
def pic(epochs, train_acc, train_loss, test_acc, test_loss):
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100 #分辨率
epochs_range = range(epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
结果如图:
选择使用以下代码对指定的adidas分类中的图片预测检查模型性能,可以发现模型成功预测。
def predict_one_image(config, model, image_path, classes, transform):
device = config.device
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'prediction result is: {pred_class}')
Ⅲ Ⅲ Ⅲ Conclusion:
-
知识点归纳:
- 动态调整学习率:
def adjust_learning_rate(optim, epoch, start_lr): lr = start_lr * (0.98 ** (epoch//2)) for param_group in optim.param_groups: param_group['lr'] = lr
2.为什么要进行标准化处理?
标准化处理图像数据是深度学习中特别是计算机视觉领域中的常见操作。这里我们详细解释一下为什么要进行标准化处理以及为什么选择特定的均值和标准差。为什么要标准化图像数据?
加速模型训练:标准化可以使数据的每个维度具有相同的尺度,从而加快梯度下降法的收敛速度。均值为 0 和标准差为 1 的标准正态分布(高斯分布)特别适用于许多优化算法,使得模型更容易训练。提高模型性能:未经标准化的数据可能导致不同特征值范围过大,从而影响模型的性能。标准化可以使模型更容易找到全局最优解。
减小数值不稳定性:在深度神经网络中,大数值输入可能导致数值不稳定,影响模型训练的稳定性。标准化可以有效减少这种风险。
为什么选择特定的均值和标准差?
通常这些值是基于数据集计算得到的。
ImageNet 数据集:这些均值和标准差是从 ImageNet 数据集中所有图像的每个通道(RGB)计算得到的。在实际应用中,很多预训练的深度学习模型(例如 ResNet、VGG 等)都是在 ImageNet 数据集上训练的。
保证一致性:使用与预训练模型相同的均值和标准差,可以确保你的数据和预训练数据具有相同的分布,从而提高模型在迁移学习中的性能和稳定性。