【PyTorch 卷积】实战自定义的图片归类
ztj100 2024-10-31 16:13 32 浏览 0 评论
前言
卷积神经网络是一类包含卷积计算且具有深度结构的前馈神经网络,是深度学习的代表算法之一,它通过卷积层、池化层、全连接层等结构,可以有效地处理如时间序列和图片数据等。关于卷积的概念网络上也比较多,这里就不一一描述了。实战为主当然要从实际问题出发,用代码的方式加深印象。在写代码前,我先说一下为什么我要写这篇文章?
之前我也用 Tensorflow.js 跟着别人试过图片分类,虽然结果是有了,但是对代码的理解和印象并不深刻。后来由于工作业务原因才接触 PyTorch,发现这个框架更好上手,整一圈后就想用这个把之前用得图片也实现一下分类。开始也是看文章实现,但是网上大部分都是用 MNIST 数据集实现的手写字识别,而业务中有时就是一些指定的不规则小众图片识别,所以下面就简单实现一个自定义的图片集归类。
流程
- 根据自己的定义,收集图片并归类
- 读取图片数据和归类标签,保存数据集
- 固定图片大小 (会变形),归一化转张量
- 定义超参数,损失函数和优化器等
- 炼丹,重复查看损失值准确率等指标
- 保存模型参数,加载测试图片分类效果
环境
- Python 3.8
- Torch 1.9.0
- Pillow 10.0
- Torchvision
- Numpy
- Pandas
- Matplotlib
编码
写代码前已经把需要的图片做好了分类,上面的依赖包也已经安装完毕。由于只是演示这里没有用预训练模型(ResNet、VGG),因为训练时要用的是 Tensor,所以需要先读取文件夹内的图片先转化为 PIL 的对象数据或 Numpy 数据,然后可以对图片进行调整,最后全都转成 Tensor(也可以跳过 PIL 直接转张量)。这里需要注意的是对灰彩图片通道,不同尺寸图的统一处理,就是灰色图的单通道要通过复制的方式创建三个通道,所以图片设置一样的像素大小。因为在卷积网络中,输入的通道数和输入大小要一致,不然可能在训练中报错。
图片数据生成
这里就是遍历各个分类文件夹的图片转换为对象信息数据,和提取所有分类,分别保存到指定位置,当然也可以在这里划分训练数据,校验数据,测试数据,需要的可以扩展这里就跳过了。
# -*- coding: utf-8 -*-
import os
import pickle as pkl
import pandas as pd
from PIL import Image
all_cate = []
data_set = []
directory = "./data/train"
for index, data in enumerate(os.walk(directory)):
root, dirs, files = data
if index == 0:
all_cate += dirs
else:
sorted(all_cate)
root_names = root.split("\\")
dir_name = root_names[-1]
for img in files:
img_path = root + "\\" + img
img_np = Image.open(img_path)
dict = {}
dict['img_np'] = img_np
dict['label'] = all_cate.index(dir_name) + 1
data_set.append(dict)
# 字典转DataFrame
df = pd.DataFrame(data_set)
pkl.dump(df, open('data/train_dataset.p', 'wb'))
open("data/all_cate.txt", encoding="utf-8", mode="w+").write("\n".join(all_cate))
print("存档数据成功~")
批量数据集标准化
这里是读取序列化的图片信息,对所有图片统一像素 (一般配置电脑最好在 100px 以内,不然会很卡) 并标准归一化后,转换为 Tensor。然后判断图片通道数,如果是灰色图,可以复制张量三次以创建三个通道,最后通过 torch 的 DataLoader 在训练前完成数据集的加载。
# -*- coding: utf-8 -*-
import torch
from torchvision import transforms
import pickle as pkl
from torch.utils.data import Dataset
class DataSet(Dataset):
def __init__(self, pkl_file):
df = pkl.load(open(pkl_file, 'rb'))
self.dataFrame = df
def __len__(self):
return len(self.dataFrame)
def __getitem__(self, item):
img_np = self.dataFrame.iloc[item, 0]
label = self.dataFrame.iloc[item, 1]
transform = transforms.Compose([
transforms.Resize((100, 100)), # 根据需要调整图像大小
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5]) # 标准归一化, p1.均值 p2.方差
])
image_tensor = transform(img_np)
if image_tensor.shape[0] == 1:
image_tensor = image_tensor.repeat(3, 1, 1)
res = {
'img_tensor': image_tensor,
'label': torch.LongTensor([label-1]) # 需要实际的索引值
}
return res
神经网络模型
这里创建的是卷积神经网络,接收 3 通道,第一层卷积层卷积核 3x3,输出 25 维张量,通过批标准化(BatchNorm2d)进行归一化处理,最后通过 ReLU 激活函数进行非线性变换。第一层池化使用 2x2 的最大池化操作对卷积后的特征图进行下采样。第二层也是卷积和对应的池化,最后是全连接层。将经过池化的特征图展平,然后通过一个有 1024 个神经元的全连接层,再通过 ReLU 激活函数进行非线性变换。之后是一个有 128 个神经元的全连接层,最后再通过 ReLU 激活函数进行非线性变换,输出 5 个神经元代表分类的概率分布。
# -*- coding: utf-8 -*-
import torch.nn as nn
import torch
import math
import torch.functional as F
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 25, kernel_size=3),
nn.BatchNorm2d(25),
nn.ReLU(inplace=True)
)
self.layer2 = nn.Sequential(
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.layer3 = nn.Sequential(
nn.Conv2d(25, 50, kernel_size=3),
nn.BatchNorm2d(50),
nn.ReLU(inplace=True)
)
self.layer4 = nn.Sequential(
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.fc = nn.Sequential(
nn.Linear(50 * 23 * 23, 1024),
nn.ReLU(inplace=True),
nn.Linear(1024, 128),
nn.ReLU(inplace=True),
nn.Linear(128, 5)
)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
开始训练
# -*- coding:utf-8 -*-
import torch
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
from data_set import DataSet
from torch.autograd import Variable
from utils import *
import cnn
import torch.nn as nn
import numpy as np
import torch.optim as optim
# 定义超参数
batch_size = 1
learning_rate = 0.02
num_epoches = 1
# 加载图片tensor训练集
tain_dataset = DataSet("data/train_dataset.p")
train_loader = DataLoader(tain_dataset, batch_size=batch_size, shuffle=True)
model = cnn.CNN()
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
# 训练模型
train_loses = []
records = []
for i in range(num_epoches):
for ii, data in enumerate(train_loader):
img = data['img_tensor']
label = data['label'].view(-1)
optimizer.zero_grad()
out = model(img)
loss = criterion(out, label)
train_loses.append(loss.data.item())
loss.backward()
optimizer.step()
if ii % 50 == 0:
print('epoch: {}, loop: {}, loss: {:.4}'.format(i, ii, np.mean(train_loses)))
records.append([np.mean(train_loses)])
# 绘制模型的损失,准确率走势图
train_loss = [data[0] for data in records]
plt.plot(train_loss, label = 'Train Loss')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.legend()
plt.show()
# 模型评估(略)
# model.eval()
# 模型保存
torch.save(model, 'params/cnn_imgs_02.pkl')
模型检测
训练完成保存参数到本地,下面就是将加载进的参数来测试其他图片的分类效果,同样的也是将指定图片和训练时一样的转换操作,最后将预测结果取出最大分布索引值,根据索引就可以匹配出分类名称了。另一个是工具函数,将 tensor 格式的图片在预测结果后显示在 pyplot 中。
# -*- coding:utf-8 -*-
import torch
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
from data_set import DataSet
from utils import *
import torchvision
from PIL import Image
from torchvision import transforms
import cnn
def imshow(img):
img = img / 2 + 0.5
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
model = torch.load("params/cnn_imgs_02.pkl")
img_path= "imgs/05.jpg"
img_np = Image.open(img_path)
transform = transforms.Compose([
transforms.Resize((100, 100)),
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5])
])
image_tensor = transform(img_np)
# 如果是灰度图片
if image_tensor.shape[0] == 1:
image_tensor = image_tensor.repeat(3, 1, 1)
image_tensor = image_tensor.view(-1, 3, 100, 100)
predict = model(image_tensor)
indices = torch.max(predict, 1)[1].item()
all_cate = []
for line in open("data/all_cate.txt", encoding="utf-8", mode="r"):
all_cate.append(line.strip())
cate_name = ""
try:
cate_name = all_cate[indices]
except ValueError:
cate_name = "未知"
print("识别结果是:", cate_name)
# imshow(torchvision.utils.make_grid(image_tensor))
# 原图显示
img_np.show()
exit()
相关推荐
- 这个 JavaScript Api 已被废弃!请慎用!
-
在开发过程中,我们可能会不自觉地使用一些已经被标记为废弃的JavaScriptAPI。这些...
- JavaScript中10个“过时”的API,你的代码里还在用吗?
-
JavaScript作为一门不断发展的语言,其API也在持续进化。新的、更安全、更高效的API不断涌现,而一些旧的API则因为各种原因(如安全问题、性能瓶颈、设计缺陷或有了更好的替代品)被标记为“废...
- 几大开源免费的 JavaScript 富文本编辑器测评
-
MarkDown编辑器用的时间长了,发现发现富文本编辑器用起来是真的舒服。...
- 比较好的网页里面的 html 编辑器 推荐
-
如果您正在寻找嵌入到网页中的HTML编辑器,以便用户可以直接在网页上编辑HTML内容,以下是几个备受推荐的:CKEditor:CKEditor是一个功能强大的、开源的富文本编辑器,可以嵌入到...
- Luckysheet 实现excel多人在线协同编辑
-
前言前些天看到Luckysheet支持协同编辑Excel,正符合我们协同项目的一部分,故而想进一步完善协同文章,但是遇到了一下困难,特此做声明哈,若侵权,请联系我删除文章!若侵犯版权、个人隐私,请联系...
- 从 Element UI 源码的构建流程来看前端 UI 库设计
-
作者:前端森林转发链接:https://mp.weixin.qq.com/s/ziDMLDJcvx07aM6xoEyWHQ引言...
- 手把手教你如何用 Decorator 装饰你的 Typescript?「实践」
-
作者:Nealyang转发连接:https://mp.weixin.qq.com/s/PFgc8xD7gT40-9qXNTpk7A...
- 推荐五个优秀的富文本编辑器
-
富文本编辑器是一种可嵌入浏览器网页中,所见即所得的文本编辑器。对于许多从事前端开发的小伙伴来说并不算陌生,它的应用场景非常广泛,平时发个评论、写篇博客文章等都能见到它的身影。...
- 基于vue + element的后台管理系统解决方案
-
作者:林鑫转发链接:https://github.com/lin-xin前言该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统(WebManagementSystem)开发。基于v...
- 开源富文本编辑器Quill 2.0重磅发布
-
开源富文本编辑器Quill正式发布2.0版本。官方TypeScript声明...
- Python之Web开发框架学习 Django-表单处理
-
在Django中创建表单实际上类似于创建模型。同样,我们只需要从Django类继承,则类属性将是表单字段。让我们在myapp文件夹中添加一个forms.py文件以包含我们的应用程序表单。我们将创建一个...
- Django测试入门:打造坚实代码基础的钥匙
-
这一篇说一下django框架的自动化测试,...
- Django ORM vs SQLAlchemy:到底谁更香?从入门到上头的选择指南
-
阅读文章前辛苦您点下“关注”,方便讨论和分享,为了回馈您的支持,我将每日更新优质内容。...
- 超详细的Django 框架介绍,它来了!
-
时光荏苒,一晃小编的Tornado框架系列也结束了。这个框架虽然没有之前的FastAPI高流量,但是,它也是小编的心血呀。总共16篇博文,从入门到进阶,包含了框架的方方面面。虽然小编有些方面介绍得不是...
- 20《Nginx 入门教程》使用 Nginx 部署 Python 项目
-
今天的目标是完成一个PythonWeb项目的线上部署,我们使用最新的Django项目搭建一个简易的Web工程,然后基于Nginx服务部署该PythonWeb项目。1.前期准备...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)