# pytorch_Gesture_Recognition **Repository Path**: nibo0021/pytorch_gesture_recognition ## Basic Information - **Project Name**: pytorch_Gesture_Recognition - **Description**: 这是一个基于pytorch的使用CNN的手势识别模型。是一个入门级别的训练模型。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2022-02-06 - **Last Updated**: 2022-02-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # pytorch_Gesture_Recognition 这是一个基于pytorch的使用CNN的手势识别模型。是一个入门级别的训练模型。 ## pytorch 进行模型训练测试的四大步骤 从代码的角度来看,我们的所做的事可以总结为一下四步 1. 数据加载 2. 模型设计 3. 训练模型 4. 加载测试 ## 数据加载 建立自己的数据集,有两点需要我们保证。 > 1. 创建的数据集要继承 torch.utils.data.Dataset > 2. 我们必须完成 __init__, __len__, 和 __getitem__ 三个方法 ```python import os import torch from torch.utils.data import Dataset from torchvision.transforms import ToTensor, Lambda from PIL import Image # 定义使用常量 ANNOTATIONS_FILE = "./images/train.txt" IMG_DAR = "./images/train" # 对于一个数据集来说,我们必须重写他的__init__, __len__, 和 __getitem__ 三个方法 class CustomImageDataset(Dataset): # 在__init__方法里所初始化的这些参数其实都没有参与到后记数据的加载中,关键还是__len__ 和 __getitem__ def __init__(self): with open(ANNOTATIONS_FILE, "r") as f: # 读取标签文件 读取一行,去掉结尾的\n 然后根据空格分割为图片地址和标签 self.labels = [line.strip('\n').split(" ") for line in f.readlines()] self.img_dir = IMG_DAR # 图片地址 self.transform = ToTensor() # 图片转化方法 self.target_transform = Lambda( lambda y: int(y) ) # 标签转换方法 # 我们可以像这样使用lambda表达式来出处理数据,以下被注释的代码可以将label转换成onehot型 # self.target_transform = Lambda( # lambda y: torch.zeros(6, dtype=torch.float).scatter_(0, torch.tensor(int(y)), value=1) # ) # __len__ 方法返回数据集的总长度 def __len__(self): return len(self.labels) # __getitem__ 方法使数据集可以使用下表索引,返回值为一个样本 def __getitem__(self, idx): image = Image.open(self.labels[idx][0]) label = self.labels[idx][1] if self.transform: image = self.transform(image) if self.target_transform: label = self.target_transform(label) sample = {'image': image, 'label': label} return sample ``` ## 模型设计 设计自己的模型,我们需要完成两点内容 > 1. 我们的模型要继承 torch.nn.Model > 2. 我们会在__init__方法里定义使用到的模型结构,在forward 方法是模型的关键,主要完成的就是把我们定义的每一次都串联起来 ```python # 模型文件 from torch import nn class CnnNet(nn.Module): # 在初始化里定义了模型的结构,是一个两次卷积池化 + 三层的全连接网络 def __init__(self): super(CnnNet, self).__init__() self.conve1 = nn.Sequential( # Conv2d 卷积神经网络, # 第一个参数是输入图片的位深,应为我们使用了rgb色彩,所以是3 # 第二个参数是输出图片的位深,可以自己设定,表达了你从图片中所收集的不同特征 # 第三个参数是卷积核的大小 5*5 # 设置padding属性 = (卷积核的大小 - 移动步长)/2 可以让输出图像的长宽属性保持不变 nn.Conv2d(3, 24, 5, padding=2), # 归一化层:数据经过处理之后就会变成均值为零方差为一的正态分布,防止梯度消失 nn.BatchNorm2d(24), # 激活函数 nn.ReLU() ) # 池化层:2*2 的池化会让你的图片长宽都变为原来的一半, 作用是防止过拟合和减小训练参数 # 原来的图片是64*64 的 现在已经变成了32 * 32的 self.pool1 = nn.MaxPool2d(2, 2) self.conve2 = nn.Sequential( nn.Conv2d(24, 48, 3, padding=1), nn.BatchNorm2d(48), nn.ReLU() ) self.pool2 = nn.MaxPool2d(2, 2) self.fc = nn.Sequential( # 经过上面的操作,输出的“图片”,已经变成了一个 16 * 16 * 48 的大小,所以全连接层开始时有16*16*48 nn.Linear(48 * 16 * 16, 1024), nn.ReLU(inplace=True), nn.Linear(1024, 128), nn.ReLU(inplace=True), nn.Linear(128, 6) ) # forward 方法是模型的关键,主要完成的就是把我们定义的每一次都串联起来 def forward(self, x): x = self.conve1(x) x = self.pool1(x) x = self.conve2(x) x = self.pool2(x) # 这里是把原先tensor中的数据按照行优先的顺序排成一个一维的数据,不让输入不到全连接层 x = x.view(x.size(0), -1) out = self.fc(x) return out ``` ## 训练模型 在训练模型阶段,首先要做到把数据加载到模型,计算出结果,在根据结果计算损失函数,设计优化方法,循环优化。总结下来的步骤也就是。 > 1. 导入模型 (直接实例化我们设计的模型) > 2. 导入数据 (使用dataloader 来加载) > 3. 计算损失 (pytorch 有常用的损失函数) > 4. 设计优化方法 (pytorch 有常用的优化方法) > 5. 循环运行 (一次训练的效果不会很好,我们往往会让一个数据集进行多次训练) ```python import torch from torch import nn from torch.utils.data import DataLoader from dataset import CustomImageDataset from model import CnnNet ## 训练方法 def train_loop(dataloader, model, loss_fn, optimizer, device): size = len(dataloader.dataset) for i in range(0, 100): for batch, data in enumerate(dataloader): # 计算 预期 and 损失 # 这里的to(device) 将训练模型的输入数据转化成了GPU可以识别的形式 pred = model(data['image'].to(device)) loss = loss_fn(pred, data['label'].to(device)) # 清空一下梯度 optimizer.zero_grad() # 进行反向传播和模型优化 loss.backward() optimizer.step() # 每隔一段时间输出一下过程 if batch % 100 == 0: loss, current = loss.item(), batch * len(data['image']) print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") # # 通过以下代码可以查看通过datalorder 加载的数据 # def train_loop(dataloader, model, loss_fn, optimizer): # size = len(dataloader.dataset) # for batch, data in enumerate(dataloader): # print(batch) # 打印batch编号 # print(data['image'].size()) # 打印该batch里面图片的大小 # print(data['label']) # 打印该batch里面图片的标签 if __name__ == '__main__': # 使用我们在model.py 创建的模型 model = CnnNet() # 使用GPU运算,将模型的参数变成GUP的 device = torch.device('cuda:0') model.to(device) # 使用DataLoader方法来调用我们写好的数据集 train_dataloader = DataLoader(CustomImageDataset(), batch_size=32) # 交叉熵函数作为损失函数,一般分类问题会用到交叉熵 loss_fn = nn.CrossEntropyLoss() # 设置学习率, 定义我们使用梯度优化器 learning_rate = 1e-3 optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # 调用训练方法 train_loop(train_dataloader, model, loss_fn, optimizer, device) ``` ## 测试模型 在测试模型的阶段,可以主要分为两个阶段,即加载模型和记载图片,具体步骤如下 > 1. 模型的加载 > 2. 加载图片并且转化为tensor > 3. 将图片扔到模型里得到输出 ```python import torch from PIL import Image from torchvision.transforms import ToTensor, Lambda # 1. 模型的加载 model = torch.load('./model.pkl') print(model) # 2. 加载图片并且转化为tensor transform = ToTensor() img_in = Image.open("./images/test/signs/img_0009.png") device = torch.device('cuda:0') # # 这里注意,要使用 unsqueeze(0)将序列扩充一维,在训练时我们使用的是一组图片,这里的一张成组 img_in = transform(img_in).unsqueeze(0).to(device) # 3. 将图片扔到模型里得到输出 out = model(img_in) print(out) print(torch.max(out, 1)) ``` ## 致谢 特别感谢来自 [hugh_sun](https://gitee.com/hugh_sun/Gesture_Recognition) 的数据