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

计算机视觉:神经网络实战之手势识别(附代码)

第一章:计算机视觉中图像的基础认知
第二章:计算机视觉:卷积神经网络(CNN)基本概念(一)
第三章:计算机视觉:卷积神经网络(CNN)基本概念(二)
第四章:搭建一个经典的LeNet5神经网络
第五章:计算机视觉:神经网络实战之手势识别(附代码)

一、简介

手势识别作为计算机视觉领域的一个重要应用,通过分析图像或视频序列来识别特定的手势动作。下面以0-9手势数字识别为案例,从数据加载、预处理到模型训练的手势识别全流程,基于PyTorch框架构建经典卷积神经网络。

数字图片的存放的目录结构:
在这里插入图片描述
gesture是图片的根目录,二级子目录分为训练集 train和测试集test。三级目录就是每个数字的英文单词作为目录名字,这也是每张图片的归属标签,三级目录里面存放的是具体的数字图片,比如 one 目录下,存放166 张不同的数字 1 手势图片

在这里插入图片描述在这里插入图片描述

二、数据读取与预处理

2.1 数据加载策略

首先定义一个函数 load_dataset,用于动态加载一个图像数据集的路径和对应的标签。适用于以文件夹结构组织的数据集,其中每个子文件夹的名字代表一个类别(或标签),并且该子文件夹内包含了属于这个类别的所有图像文件。

import osdef load_dataset(root_path):"""动态加载数据集路径:param root_path: 数据集根目录(需包含以标签命名的子目录):return: 路径列表, 标签列表"""paths, labels = [], []for label in os.listdir(root_path):# 忽略隐藏文件或目录if label.startswith('.'):continue# 获取每个标签对应的子目录路径label_path = os.path.join(root_path, label)# 遍历子目录下的所有文件for file in os.listdir(label_path):# 忽略隐藏文件或目录if file.startswith('.'):continue# 构造完整的文件路径file_path = os.path.join(label_path, file)# 将文件路径添加到路径列表中paths.append(file_path)# 将对应的标签添加到标签列表中labels.append(label)return paths, labels

加载数据整体思路:

  • 初始化两个空列表 paths 和 labels,分别用于存储图像文件的完整路径和对应的类别标签。

  • 使用 os.listdir(root_path) 列出根目录下的所有项目(文件或文件夹)。

  • 对于每一个子目录,即类别标签,构造其完整路径 label_path。

  • 遍历子目录中的所有文件,对于每个文件构造其完整路径,并将这些路径添加到 paths 列表中;同时,将对应的类别标签添加到 labels 列表中。

  • 最后返回 paths 和 labels 列表。

实际调用示例

train_root = "gesture/train"
test_root = "gesture/test"
train_paths, train_labels = load_dataset(train_root)
test_paths, test_labels = load_dataset(test_root)
  • 指定训练集(train)和测试集(test)的根目录分别为 gesture/traingesture/test
  • 调用load_dataset函数,分别对训练集和测试集进行处理,获取它们的文件路径和标签列表。
  • train_paths 和 train_labels 分别保存了训练集中所有图像的路径及其对应的类别标签;同样地,test_paths 和 test_labels 保存了测试集的相关信息。

2.2 标签编码映射

建立标签与数字ID的双向映射:

labels = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
label2idx = {label:idx for idx, label in enumerate(labels)}
idx2label = {idx:label for label, idx in label2idx.items()}# 转换示例
print(label2idx["three"])  # 输出: 3
print(idx2label[5])        # 输出: "five"

三、数据加载

3.1 自定义数据集类

定义一个名为 GestureDataset 的自定义数据集类,该类继承自 PyTorch 的 torch.utils.data.Dataset 类。这个类主要用于手势识别任务中处理图像数据集,包括图像的读取、转换以及标签的处理。

# 引入必要的工具类
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from PIL import Image
from torchvision import transforms
import torchclass GestureDataset(Dataset):"""自定义手势识别数据集"""def __init__(self, X, y):"""初始化"""# 图像文件路径列表self.X = X# 对应的标签列表self.y = ydef __getitem__(self, idx):"""实现:- 按下标来索引一个样本"""# 获取图像路径img_path = self.X[idx]# 读取图像img = Image.open(fp=img_path)# 统一大小img = img.resize((32, 32))# 转张量 [C, H, W]# 转换为PyTorch张量(范围[0,1])img = transforms.ToTensor()(img)# 标准化到[-1,1]范围img = transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])(img)# 读取标签img_label = self.y[idx]# 标签转 idimg_idx = label2idx.get(img_label)# 转张量label = torch.tensor(data=img_idx, dtype=torch.long)return img, labeldef __len__(self):"""返回该数据集的样本个数"""return len(self.X)
  • 引入必要的工具和库,用于创建自定义的数据集 (Dataset) 和数据加载器 (DataLoader),处理图像 (PIL.Image),应用图像变换 (transforms) 以及张量操作 (torch)。
  • GestureDataset 类接受两个参数:X 和 y。
    • X 是一个包含所有图像文件路径的列表。
    • y 是与这些图像对应的标签列表。
  • 实现 __getitem__ 方法:
    • 根据给定的索引 idx,返回一个样本(即一张图像及其对应标签)。
    • 首先,通过提供的索引从 X 列表中获取图像的路径,并使用 Image.open() 打开图像。
    • 使用 .resize((32, 32)) 将图像调整为统一大小。
    • 使用 transforms.ToTensor() 将图像转换为PyTorch张量,并将像素值缩放到[0, 1]范围内。
    • 使用 transforms.Normalize() 对张量进行标准化处理,使每个通道的像素值均值为0.5,标准差也为0.5,从而将像素值映射到[-1, 1]范围内。
    • 获取该图像的标签 img_label,并将其转换为整数索引 img_idx。
    • 最后,将标签转换为PyTorch张量类型,并返回图像张量和标签张量。
  • 实现 __len__ 方法:此方法返回数据集中样本的数量,即图像路径列表 X 的长度。

为什么要将图像转换为PyTorch张量?

从图像到张量:深度学习模型通常接受张量作为输入,而不是原始的图像格式(如PIL Image或NumPy数组)。因此,需要将读取的图像转换为张量格式。

为什么要将像素值缩放到[0, 1]范围内?

数值范围调整:原始图像的像素值通常位于[0, 255]范围内(对于8位图像),这意味着每个像素的值是一个介于0到255之间的整数。然而,大多数现代神经网络期望输入数据的数值范围在[0, 1]或[-1, 1]之间。通过使用ToTensor(),每个像素值除以255,从而将其缩放到[0, 1]范围。

为什么要对张量进行标准化处理?

transforms.Normalize(mean, std)归一化是另一个重要的预处理步骤,它涉及到对数据进行均值和标准差的标准化处理。这一步骤有助于加速模型的收敛过程,并可能提高模型的性能。

  • 加快收敛速度:通过对输入数据进行标准化处理,可以使得损失函数表面更加平滑,从而帮助梯度下降算法更快地找到最优解。这是因为当输入特征具有相似的尺度时,优化算法能够更有效地更新权重。
  • 提升模型表现:标准化后的输入可以使不同特征(在这个场景中指的是图像的不同通道)具有相同的尺度,避免某些特征因为尺度过大或过小而对模型的学习产生不成比例的影响。这对于深层网络尤为重要,因为它有助于防止梯度消失或爆炸的问题。
  • 具体参数说明:
    mean=[0.5, 0.5, 0.5] 和 std=[0.5, 0.5, 0.5] 分别表示三个通道(通常是RGB图像的红、绿、蓝通道)的平均值和标准差。这些值用于将输入张量的每个元素标准化到接近正态分布的形式,即均值为0,方差为1。具体来说,这里的设置会将像素值从[0, 1]范围映射到[-1, 1]范围。
  • 计算公式:对于每个像素值 x,经过标准化后的新值为 (x - mean) / std。根据上面的例子,如果一个像素值原来是0.75(已经通过ToTensor()转换到了[0, 1]范围),那么标准化后的值将是 (0.75 - 0.5) / 0.5 = 0.5。

transforms.ToTensor()transforms.Normalize() 这两个操作是为了确保输入到神经网络的数据具有一致的尺度和分布,这样不仅有助于提高模型训练的效率,还可能改善最终模型的表现。这是因为在良好的初始化条件下,模型能够更稳定和高效地学习输入数据中的模式。

3.2 数据加载器配置

使用自定义的数据集类 GestureDataset 来创建训练集和测试集的数据加载器 DataLoader,以便于后续的模型训练和评估。

# 训练集加载器
train_dataset = GestureDataset(X=train_paths, y=train_labels)
train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=16)
# 测试集加载器
test_dataset = GestureDataset(X=test_paths, y=test_labels)
test_dataloader = DataLoader(dataset=test_dataset, shuffle=False, batch_size=32)# 测试
for X, y in test_dataloader:print(X.shape)print(y.shape)break
  • GestureDataset(X=train_paths, y=train_labels):
    • 这里实例化了 GestureDataset 类,传入两个参数:X=train_paths 和 y=train_labels。
    • train_paths 是一个包含所有训练图像路径的列表。
    • train_labels 是与这些图像对应的标签列表。
    • 通过这种方式,train_dataset 对象可以访问每个样本(图像及其标签),并对其进行必要的预处理(如调整大小、转换为张量等)。
  • DataLoader(dataset=train_dataset, shuffle=True, batch_size=16):
    • 使用 PyTorch 的 DataLoader 创建了一个数据加载器 train_dataloader。
    • dataset=train_dataset: 指定了要加载的数据集对象。
    • shuffle=True: 表示在每个epoch开始时,将数据集中的样本顺序打乱。这对于训练集来说非常重要,因为它可以帮助模型更好地泛化,避免模型学习到数据顺序的相关性。对于测试集,通常不需要打乱顺序,因为我们更关注模型在未见过的数据上的表现,而不是训练过程中的随机性。
    • batch_size=16: 设置了每个批次的大小为16,即每次迭代时从数据集中取出16个样本进行训练。选择合适的批量大小对于训练效率和模型性能都很关键,但也需要考虑内存限制。

四、卷积神经网络构建

4.1 网络架构设计

基于改进版LeNet-5卷积神经网络实现,LeNet-5网络在上一篇《搭建一个经典的LeNet5神经网络》中有详细介绍。

定义一个名为 GestureNet 的神经网络类,该类继承自 PyTorch 的 nn.Module。这个网络设计用于手势识别任务。

import torch.nn as nnclass GestureNet(nn.Module):# 定义了 GestureNet 类并初始化了父类 nn.Module。# 参数 in_channels 默认设置为3,通常代表输入图像的通道数(如RGB图像)。# num_classes 参数指定了模型最终输出的类别数量,默认设置为10,适用于一个包含10个类别的分类任务。def __init__(self, in_channels=3, num_classes=10):super().__init__()# 特征提取器self.features = nn.Sequential(nn.Conv2d(in_channels, 6, kernel_size=5),nn.ReLU(inplace=True),nn.MaxPool2d(2, 2),nn.Conv2d(6, 16, kernel_size=5),nn.ReLU(inplace=True),nn.MaxPool2d(2, 2))# 分类器self.classifier = nn.Sequential(nn.Flatten(),nn.Linear(16*5*5, 120),nn.ReLU(inplace=True),nn.Dropout(0.5),nn.Linear(120, 84),nn.ReLU(inplace=True),nn.Dropout(0.5),nn.Linear(84, num_classes))def forward(self, x):x = self.features(x)x = self.classifier(x)return x

代码解释:

  • self.features = nn.Sequential(...): 功能:这部分负责从输入图像中提取有用的特征。
    • 第一层是一个卷积层 (nn.Conv2d),它将输入通道数转换为6个输出通道,使用5x5大小的卷积核。
    • 接着是一个ReLU激活函数 (nn.ReLU),这里使用了 inplace=True 来节省内存,直接在原地修改输入张量而不是创建新的张量。
    • 然后是最大池化层 (nn.MaxPool2d),它使用2x2的窗口大小进行下采样,这有助于减少数据维度同时保留主要特征。
    • 接下来的第二层卷积层 (nn.Conv2d) 将通道数从6增加到16,同样使用5x5的卷积核。
    • 最后再经过ReLU激活函数和最大池化层处理。
  • self.classifier = nn.Sequential(...):功能:这部分将特征提取器提取的特征映射到具体的类别上,完成分类任务。
    • 首先通过 nn.Flatten() 层将多维的特征图展平成一维向量。假设输入图像大小为32x32,经过两次池化操作后,特征图大小变为8x8(考虑边缘丢失),再乘以16个通道,因此这里的 16 x 5 x 5 可能需要根据实际的输入尺寸调整为正确的值(可能是 16 x 8 x 8 或者其他值,取决于你的输入尺寸和网络的具体配置)。
    • 然后是一个全连接层 (nn.Linear),将展平后的特征映射到120维的空间。
    • 使用ReLU激活函数和Dropout (nn.Dropout),后者用于防止过拟合,此处的丢弃概率为0.5。
    • 接下来是另一个全连接层,将特征维度减少到84。
    • 再次应用ReLU激活函数和Dropout。
    • 最终,最后一个全连接层将特征映射到指定的类别数量 (num_classes)。
  • forward(self, x):这个方法定义了数据如何从前向后流经网络。
    • 输入 x 首先通过特征提取器 self.features 提取特征。
    • 然后这些特征被送入分类器 self.classifier 进行分类处理。
    • 最终返回分类结果。

上面是构建了一个简单的卷积神经网络用于手势识别,包括特征提取和分类两个阶段,并且在分类阶段引入了Dropout来提高模型的泛化能力。

4.2 网络维度变化

输入图像32x32的完整计算过程:

输出尺寸参数计算
Input3×32×32-
Conv1(5x5)6×28×28(5×5×3+1)×6 = 456
MaxPool16×14×14-
Conv2(5x5)16×10×10(5×5×6+1)×16 = 2416
MaxPool216×5×5-
Flatten400-
FC1120(400+1)×120 = 48120
FC284(120+1)×84 = 10164
Output10(84+1)×10 = 850

总参数量: 456 + 2416 + 48120 + 10164 + 850 = 61,706

五、模型训练与优化

5.1 训练配置

设置一个用于训练神经网络的基本环境,包括设备选择、模型实例化、优化器配置、损失函数定义以及学习率调度器的配置。

# 这行代码检查系统是否支持CUDA(即是否有可用的NVIDIA GPU),如果有,则将device变量设置为 "cuda",否则设置为 "cpu"。
# 确保模型的参数和运算都在选定的设备上进行,以充分利用硬件资源。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = GestureNet().to(device)
# 使用Adam优化算法来更新模型参数。Adam是一种常用的自适应学习率优化算法,适用于大多数深度学习任务。
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
# 定义交叉熵损失函数,这是分类问题中最常用的损失函数之一,特别适合于多分类任务。
loss_fn = nn.CrossEntropyLoss()
# 配置了一个学习率调度器 ReduceLROnPlateau,它可以根据某个监控指标的表现动态调整学习率。
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=3)

参数解释:

  • model.parameters():提供模型的所有可学习参数给优化器。
  • lr=0.001:设定初始学习率为0.001。
  • weight_decay=1e-4:加入L2正则化,防止过拟合。这里的权重衰减系数设为0.0001。
  • optimizer:要调整的学习率所属的优化器。
  • 'min':模式设定为最小化监控指标(如验证损失)。如果监控指标停止下降,就会触发学习率的降低。
  • patience=3:容忍度设为3,意味着如果连续3个epoch监控指标都没有改善,就会降低学习率。

利用Adam优化器和交叉熵损失函数进行有效的训练,并通过学习率调度器根据训练情况自动调整学习率,从而可能提高模型的最终性能。

5.2 训练循环实现

定义一个名为 train_epoch 的函数,用于执行一个epoch的模型训练。通过遍历提供的数据加载器(loader),对模型进行前向传播、计算损失、反向传播以及参数更新。

def train_epoch(model, loader, optimizer, loss_fn):# 设置模型为训练模式model.train()# 初始化变量 total_loss 用于累积整个epoch中的所有批次损失。这有助于最后计算平均损失。total_loss = 0# 使用 for 循环遍历数据加载器 loader 提供的所有批次数据 (inputs, labels)。for inputs, labels in loader:# 将输入数据和标签都移动到指定的设备(CPU或GPU)上,以便与模型保持一致。inputs, labels = inputs.to(device), labels.to(device)"""清空梯度:optimizer.zero_grad() 清除之前的梯度信息,因为PyTorch默认会累加梯度。前向传播:outputs = model(inputs) 执行一次前向传播,获取模型的预测输出。计算损失:loss = loss_fn(outputs, labels) 根据预测输出和真实标签计算损失。反向传播:loss.backward() 执行反向传播,计算每个参数的梯度。更新权重:optimizer.step() 使用计算出的梯度来更新模型的权重。"""optimizer.zero_grad()outputs = model(inputs)loss = loss_fn(outputs, labels)loss.backward()optimizer.step()# loss.item() 获取当前批次的标量损失值。# inputs.size(0) 获取当前批次的样本数量。# 将当前批次的损失乘以批次大小并累加到 total_loss 中,这样做是为了计算整个数据集上的平均损失。total_loss += loss.item() * inputs.size(0)# 返回整个epoch的平均损失,即总损失除以数据集中样本的数量。return total_loss / len(loader.dataset)

该函数接受四个参数:

  • model: 要训练的神经网络模型。
  • loader: 数据加载器,提供批次的数据和标签。
  • optimizer: 优化器,用于更新模型的权重。
  • loss_fn: 损失函数,用于评估模型预测值与真实标签之间的误差。

定义一个名为 validate 的函数,用于在验证集或测试集上评估模型的性能。通过计算平均损失和分类准确率来衡量模型的表现。

def validate(model, loader, loss_fn):# 设置模型为推理模式model.eval()# total_loss: 用于累积整个数据集上的所有批次损失。total_loss = 0# correct: 记录预测正确的样本数,用于计算准确率。correct = 0# 使用 torch.no_grad() 上下文管理器,在这个块内的所有操作都不会被追踪用于自动求导。# 这对于推理阶段非常有用,因为它减少了内存消耗并加快了计算速度,因为不需要计算梯度。with torch.no_grad():# 遍历数据加载器提供的所有批次数据 (inputs, labels)。for inputs, labels in loader:# 将输入数据和标签都移动到指定的设备(CPU或GPU)上,以便与模型保持一致。inputs, labels = inputs.to(device), labels.to(device)# 执行一次前向传播,获取模型的预测输出 outputs。outputs = model(inputs)# 根据预测输出和真实标签计算损失 loss。loss = loss_fn(outputs, labels)# 将当前批次的损失乘以批次大小并累加到 total_loss 中,这样做是为了最后计算整个数据集上的平均损失。total_loss += loss.item() * inputs.size(0)# 获取每个输入的最大得分对应的类别索引(即模型的预测类别)。_, predicted = torch.max(outputs, 1)# 比较预测类别 predicted 和真实标签 labels,统计预测正确的样本数,并将其累加到 correct 变量中。correct += (predicted == labels).sum().item()# 计算整个数据集上的平均损失:total_loss 除以数据集中的样本总数 len(loader.dataset)。# 计算分类准确率:正确预测的样本数 correct 除以数据集中的样本总数 len(loader.dataset)。# 最终返回这两个指标作为函数的结果,分别是平均损失和准确率。return total_loss / len(loader.dataset), correct / len(loader.dataset)

该函数接受三个参数:

  • model: 要评估的神经网络模型。
  • loader: 数据加载器,提供批次的数据和标签(通常为验证集或测试集)。
  • loss_fn: 损失函数,用于评估模型预测值与真实标签之间的误差。

validate方法评估模型在验证集或测试集上的表现,通过计算平均损失和准确率来量化模型的性能。这种评估对于监控模型的学习进度、调优超参数以及决定何时停止训练非常重要。使用 torch.no_grad() 和 model.eval() 确保了在推理过程中不会进行不必要的梯度计算和参数更新,从而提高了效率和准确性。

5.3 完整训练流程

这段代码实现一个简单的训练循环,用于在一个数据集上训练神经网络模型,并在每个epoch后使用验证集评估模型性能。同时保存最佳模型。

# 初始化一个变量 best_acc,用于记录目前为止在验证集上获得的最佳准确率。初始值设为0。
best_acc = 0
# 循环执行总共50个epoch的训练。你可以根据需要调整这个数字。
for epoch in range(50):# 执行一个epoch的训练和验证train_loss = train_epoch(model, train_dataloader, optimizer, loss_fn)val_loss, val_acc = validate(model, test_dataloader, loss_fn)# 学习率调度scheduler.step(val_loss)print(f"Epoch {epoch+1:02d}: "f"Train Loss: {train_loss:.4f} | "f"Val Loss: {val_loss:.4f} | "f"Accuracy: {val_acc:.2%}")# 保存最佳模型参数if val_acc > best_acc:best_acc = val_acctorch.save(model.state_dict(), "best_model.pth")

输出内容:

Epoch 01: Train Loss: 2.2930 | Val Loss: 2.1288 | Accuracy: 36.00%
Epoch 02: Train Loss: 1.6239 | Val Loss: 0.9157 | Accuracy: 73.75%
Epoch 03: Train Loss: 1.0185 | Val Loss: 0.5172 | Accuracy: 86.00%
Epoch 04: Train Loss: 0.7497 | Val Loss: 0.3426 | Accuracy: 88.25%
Epoch 05: Train Loss: 0.5869 | Val Loss: 0.2387 | Accuracy: 93.00%
Epoch 06: Train Loss: 0.4685 | Val Loss: 0.2408 | Accuracy: 91.75%
Epoch 07: Train Loss: 0.3958 | Val Loss: 0.1606 | Accuracy: 94.75%
Epoch 08: Train Loss: 0.3526 | Val Loss: 0.1398 | Accuracy: 96.25%
Epoch 09: Train Loss: 0.2660 | Val Loss: 0.1457 | Accuracy: 95.00%
Epoch 10: Train Loss: 0.2468 | Val Loss: 0.1102 | Accuracy: 96.25%
Epoch 11: Train Loss: 0.2453 | Val Loss: 0.1406 | Accuracy: 94.75%
Epoch 12: Train Loss: 0.2144 | Val Loss: 0.1584 | Accuracy: 93.75%
Epoch 13: Train Loss: 0.2132 | Val Loss: 0.1025 | Accuracy: 96.00%
Epoch 14: Train Loss: 0.1924 | Val Loss: 0.0857 | Accuracy: 97.75%
Epoch 15: Train Loss: 0.1701 | Val Loss: 0.1847 | Accuracy: 93.50%
Epoch 16: Train Loss: 0.1622 | Val Loss: 0.0780 | Accuracy: 97.25%
Epoch 17: Train Loss: 0.1204 | Val Loss: 0.1193 | Accuracy: 95.00%
Epoch 18: Train Loss: 0.1181 | Val Loss: 0.0955 | Accuracy: 96.75%
Epoch 19: Train Loss: 0.1312 | Val Loss: 0.0799 | Accuracy: 97.75%
Epoch 20: Train Loss: 0.1139 | Val Loss: 0.1064 | Accuracy: 96.50%
Epoch 21: Train Loss: 0.0857 | Val Loss: 0.0972 | Accuracy: 96.75%
Epoch 22: Train Loss: 0.0808 | Val Loss: 0.0980 | Accuracy: 97.00%
Epoch 23: Train Loss: 0.0966 | Val Loss: 0.0870 | Accuracy: 96.75%
Epoch 24: Train Loss: 0.0744 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 25: Train Loss: 0.0722 | Val Loss: 0.0854 | Accuracy: 96.50%
Epoch 26: Train Loss: 0.0947 | Val Loss: 0.0844 | Accuracy: 96.50%
Epoch 27: Train Loss: 0.0698 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 28: Train Loss: 0.0873 | Val Loss: 0.0858 | Accuracy: 96.50%
Epoch 29: Train Loss: 0.0797 | Val Loss: 0.0857 | Accuracy: 96.50%
Epoch 30: Train Loss: 0.0622 | Val Loss: 0.0857 | Accuracy: 96.50%
Epoch 31: Train Loss: 0.0728 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 32: Train Loss: 0.0841 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 33: Train Loss: 0.0644 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 34: Train Loss: 0.0755 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 35: Train Loss: 0.0797 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 36: Train Loss: 0.0677 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 37: Train Loss: 0.0667 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 38: Train Loss: 0.0902 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 39: Train Loss: 0.0715 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 40: Train Loss: 0.0727 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 41: Train Loss: 0.0750 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 42: Train Loss: 0.0813 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 43: Train Loss: 0.0780 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 44: Train Loss: 0.0847 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 45: Train Loss: 0.0767 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 46: Train Loss: 0.0732 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 47: Train Loss: 0.0634 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 48: Train Loss: 0.0750 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 49: Train Loss: 0.0665 | Val Loss: 0.0856 | Accuracy: 96.50%
Epoch 50: Train Loss: 0.0736 | Val Loss: 0.0856 | Accuracy: 96.50%

训练阶段:调用 train_epoch 函数对模型进行一个epoch的训练,并返回该epoch的平均训练损失 train_loss。

验证阶段:调用 validate 函数评估模型在验证集上的表现,返回验证损失 val_loss 和分类准确率 val_acc。注意这里使用的是 test_loader,通常应使用 val_loader 来代表验证集加载器,以避免直接在测试集上进行验证。

scheduler.step(val_loss)根据验证集上的损失 val_loss 调整学习率。这里使用的是 ReduceLROnPlateau 调度器,当监测的指标(这里是验证损失)停止改善时,会自动降低学习率,帮助模型跳出局部最优解或加速收敛。

打印出当前epoch的信息,包括:

  • 当前是第几个epoch ({epoch+1:02d} 确保输出两位数,例如01, 02…50)。
  • 训练损失 train_loss,保留四位小数。
  • 验证损失 val_loss,保留四位小数。
  • 分类准确率 val_acc,以百分比形式显示,并保留两位小数。

六、图像预测

定义一个名为 predict 的函数,用于对单个图像进行预测。

加载一个预训练的模型,对输入图像进行必要的预处理,然后使用该模型预测图像的类别,并返回预测的标签。

predict函数接受两个参数:

  • image_path: 输入图像的路径。
  • model_path: 预训练模型的路径,默认为 “best_model.pth”。
def predict(image_path, model_path="best_model.pth"):# 加载模型model = GestureNet()# 加载保存的最佳模型参数(权重)到模型中。model.load_state_dict(torch.load(model_path))# 将模型设置为评估模式,确保在推理时正确处理如 Dropout 和 Batch Normalization 等层的行为。model.eval()# 打开指定路径的图像,并通过 .convert('RGB') 确保其格式为 RGB。image = Image.open(image_path).convert('RGB')# 创建一系列图像变换操作,包括调整大小、转换为张量以及标准化处理。transform = transforms.Compose([transforms.Resize((32, 32)), # 将图像调整为 32x32 像素大小。transforms.ToTensor(), # 将图像转换为 PyTorch 张量,并将像素值从 [0, 255] 缩放到 [0, 1]。transforms.Normalize([0.5]*3, [0.5]*3) # 对每个通道应用均值和标准差为 0.5 的标准化处理,使数据分布接近正态分布。])# 通过 transform(image) 应用上述变换,# 并使用 .unsqueeze(0) 在维度0处增加一个新的维度,以适应模型输入的要求(即模拟一个批次的输入)。# 这一步是因为大多数深度学习模型期望输入是一个四维张量(批次大小,通道数,高度,宽度),即使只有一个样本也需要指定批次大小。tensor_img = transform(image).unsqueeze(0)# 禁用梯度计算:使用 torch.no_grad() 上下文管理器,在推理过程中禁用梯度计算,减少内存占用并加快计算速度。with torch.no_grad():# 前向传播:执行一次前向传播,获取模型输出 output。output = model(tensor_img)# 获取预测结果:使用 torch.max(output, 1) 获取输出中最大值的索引(即预测的类别),_ 用来忽略最大值本身,只保留索引。_, predicted = torch.max(output, 1)# 返回预测标签:通过 idx2label[predicted.item()] 将预测的类别索引转换为对应的标签名称。return idx2label[predicted.item()]# 使用示例
print(predict("../gesture/train/four/IMG_1122.JPG"))  # 输出: "four"

七、 训练模式和推理模式

模型为训练模式和推理模式,有什么区别?

在深度学习中,模型通常有两种运行模式:训练模式(Training Mode)推理模式(Inference Mode 或 Evaluation Mode)。这两种模式在行为上有一些关键的区别,主要是因为某些层或操作在训练和推理时需要不同的处理方式。以下是它们之间的主要区别:

7.1 训练模式

  1. Dropout 层

    • 在训练模式下,Dropout 层会随机“丢弃”一些神经元(即设置其输出为零),以防止过拟合并增强模型的泛化能力。
    • 这种机制有助于避免模型对特定特征过度依赖。
  2. Batch Normalization

    • 在训练过程中,Batch Normalization 会使用当前批次的数据来计算均值和方差,并对输入进行归一化。
    • 同时,它还会更新运行时的平均均值和方差,这些值会在推理阶段使用。
  3. 梯度计算与参数更新

    • 在训练模式下,模型不仅执行前向传播,还会通过反向传播计算损失函数关于模型参数的梯度,并根据优化算法更新这些参数。
  4. 数据增强

    • 在训练模式下,通常会对输入数据应用数据增强技术(如随机裁剪、翻转等),以增加数据集的多样性,从而提高模型的泛化能力。

7.2 推理模式

  1. Dropout 层

    • 在推理模式下,Dropout 层不会丢弃任何神经元;相反,所有神经元都会参与计算,但它们的输出会被缩放(通常是乘以保留概率)。这是因为我们希望模型能够利用全部的信息来做预测。
  2. Batch Normalization

    • 在推理模式下,Batch Normalization 使用的是在整个训练过程中累积得到的运行时均值和方差来进行归一化,而不是当前批次的统计数据。
    • 这样做的目的是为了保证推理时的一致性,因为推理时可能每次只处理少量样本甚至单个样本。
  3. 梯度计算与参数更新

    • 在推理模式下,不涉及梯度计算和参数更新。模型只是简单地执行前向传播以生成预测结果。
  4. 数据增强

    • 在推理模式下,通常不会应用数据增强技术,因为我们希望基于原始输入数据做出最准确的预测。

7.3 如何切换模式

  • 在 PyTorch 中,可以通过调用 model.train() 将模型设置为训练模式,通过调用 model.eval() 将模型设置为推理模式(也称为评估模式)。

  • 切换到相应模式非常重要,特别是在验证集或测试集上评估模型性能时,确保模型处于 eval 模式可以避免不必要的 Dropout 或 Batch Normalization 行为影响结果。

理解这两种模式的区别对于正确地训练和评估深度学习模型至关重要,尤其是在涉及到 Dropout、Batch Normalization 等技术的应用场景中。正确设置模型的工作模式可以显著影响最终模型的性能和稳定性。

八、结语

以上展现手势识别系统的构建过程,涵盖数据管理、模型设计、训练优化等关键环节。可根据实际需求调整网络深度、数据增强策略等参数。


计算机视觉:计算机视觉之手势识别图片


http://www.mrgr.cn/news/91519.html

相关文章:

  • Node-Red
  • 探讨如何加快 C# 双循环的速度效率
  • 服务器装机可用的基本操作
  • 【Linux Redis】关于用docker拉取Redis后,让虚拟机运行起来redis,并使得其可以连接到虚拟机外的navicat。
  • 银行IT治理——安全架构定义
  • Vue 3 生命周期和生命周期函数
  • spring cloud gateway限流常见算法
  • 一.数据治理理论架构
  • cs224w课程学习笔记-第2课
  • 从零搭建微服务项目Base(第5章——SpringBoot项目LogBack日志配置+Feign使用)
  • 3. 导入官方dashboard
  • 小爱音箱控制手机和电视听歌的尝试
  • 【笔记】LLM|Ubuntu22服务器极简本地部署DeepSeek+联网使用方式
  • TRELLIS 部署笔记
  • 线程的多种创建方式和使用
  • java八股文-mysql
  • spring boot知识点3
  • 解锁机器学习核心算法 | K-平均:揭开K-平均算法的神秘面纱
  • PHP本地商家卡券管理系统源码
  • MySQL的基本使用