深度学习100例 | 第3天:交通标志识别 - PyTorch实现
Posted K同学啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习100例 | 第3天:交通标志识别 - PyTorch实现相关的知识,希望对你有一定的参考价值。
文章目录
大家好,我是K同学啊,今天讲《深度学习100例》PyTorch版的第3个例子,前面一些例子主要还是以带大家了解PyTorch为主,建议手动敲一下代码,只有自己动手了,才能真正体会到里面的内容,光看不练是没有用的。今天的重点是在PyTorch调用VGG-16算法模型。先来了解一下PyTorch与TensorFlow的区别
PyTorch VS TensorFlow:
TensorFlow
:简单,模块封装比较好,容易上手,对新手比较友好。在工业界最重要的是模型落地,目前国内的大部分企业支持TensorFlow模型在线部署,不支持Pytorch。PyTorch
:前沿算法多为PyTorch版本,如果是你高校学生or研究人员,建议学这个。相对于TensorFlow,Pytorch在易用性上更有优势,更加方便调试。
当然如果你时间充足,我建议两个模型都是需要了解一下的,这两者都还是很重要的。
🍨 本文的重点:对函数训练过程中用到的model.train()、model.eval()、optimizer.zero_grad()、loss.backward()、optimizer.step()等多个函数展开详细介绍,并在代码中增加了数据可视化板块
🍖 我的环境:
- 语言环境:Python3.8
- 编译器:Jupyter Lab
- 深度学习环境:
- torch==1.10.0+cu113
- torchvision==0.11.1+cu113
- 创作平台:🔗 极链AI云
- 创作教程:🔎 操作手册
深度学习环境配置教程:小白入门深度学习 | 第四篇:配置PyTorch环境
👉 往期精彩内容
- 🔥 选自专栏:《深度学习100例》Pytorch版
- ✨镜像专栏:《深度学习100例》TensorFlow版
我们的代码流程图如下所示:
一、导入数据
from torchvision.transforms import transforms
from torch.utils.data import DataLoader
from torchvision import datasets
import torchvision.models as models
import torch.nn.functional as F
import torch.nn as nn
import torch,torchvision
1. 获取类别名
import os,PIL,random,pathlib
import matplotlib.pyplot as plt
# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
data_dir = './03_traffic_sign/'
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*'))
classeNames = [str(path).split("\\\\")[1] for path in data_paths]
print(classeNames)
['15', '16', '17', '20', '22', '23', '24', '26', '27', '28', '29', '30', '31', '32']
2. 数据可视化
pictures_paths = list(data_dir.glob('*/*'))
pictures_paths = [str(path) for path in pictures_paths]
pictures_paths[:3]
['03_traffic_sign\\\\15\\\\015_0001.png',
'03_traffic_sign\\\\15\\\\015_0002.png',
'03_traffic_sign\\\\15\\\\015_0003.png']
plt.figure(figsize=(14,5))
plt.suptitle("数据示例(微信公众号:K同学啊)",fontsize=15)
for i in range(18):
plt.subplot(3,6,i+1)
# plt.xticks([])
# plt.yticks([])
# plt.grid(False)
# 显示图片
images = plt.imread(pictures_paths[i])
plt.imshow(images)
plt.show()
3. 加载数据文件
total_datadir = './03_traffic_sign/'
# 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863
train_transforms = transforms.Compose([
transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
mean=[0.485, 0.456, 0.406],
std =[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
total_data = datasets.ImageFolder(total_datadir,transform=train_transforms)
total_data
Dataset ImageFolder
Number of datapoints: 1308
Root location: ./03_traffic_sign/
StandardTransform
Transform: Compose(
Resize(size=[224, 224], interpolation=bilinear, max_size=None, antialias=None)
ToTensor()
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
)
4. 划分数据
train_size = int(0.8 * len(total_data))
test_size = len(total_data) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
train_dataset, test_dataset
(<torch.utils.data.dataset.Subset at 0x2046d102700>,
<torch.utils.data.dataset.Subset at 0x2046d102340>)
train_size,test_size
(1046, 262)
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size=16,
shuffle=True,
num_workers=1)
test_loader = torch.utils.data.DataLoader(test_dataset,
batch_size=16,
shuffle=True,
num_workers=1)
print("The number of images in a training set is: ", len(train_loader)*16)
print("The number of images in a test set is: ", len(test_loader)*16)
print("The number of batches per epoch is: ", len(train_loader))
The number of images in a training set is: 1056
The number of images in a test set is: 272
The number of batches per epoch is: 66
for X, y in test_loader:
print("Shape of X [N, C, H, W]: ", X.shape)
print("Shape of y: ", y.shape, y.dtype)
break
Shape of X [N, C, H, W]: torch.Size([16, 3, 224, 224])
Shape of y: torch.Size([16]) torch.int64
二、自建模型
nn.Conv2d()
函数:
- 第一个参数(in_channels)是输入的channel数量,彩色图片为3,黑白图片为1。
- 第二个参数(out_channels)是输出的channel数量
- 第三个参数(kernel_size)是卷积核大小
- 第四个参数(stride)是步长,就是卷积操作时每次移动的格子数,默认为1
- 第五个参数(padding)是填充大小,默认为0
这里大家最难理解的可能就是nn.Linear(24*50*50, len(classeNames))
这行代码了,在理解它之前你需要先补习一下👉卷积计算 的相关知识,然后可参照下面的网络结构图来进行理解
class Network_bn(nn.Module):
def __init__(self):
super(Network_bn, self).__init__()
"""
nn.Conv2d()函数:
第一个参数(in_channels)是输入的channel数量
第二个参数(out_channels)是输出的channel数量
第三个参数(kernel_size)是卷积核大小
第四个参数(stride)是步长,默认为1
第五个参数(padding)是填充大小,默认为0
"""
self.conv1 = nn.Conv2d(in_channels=3, out_channels=12, kernel_size=5, stride=1, padding=0)
self.bn1 = nn.BatchNorm2d(12)
self.conv2 = nn.Conv2d(in_channels=12, out_channels=12, kernel_size=5, stride=1, padding=0)
self.bn2 = nn.BatchNorm2d(12)
self.pool = nn.MaxPool2d(2,2)
self.conv4 = nn.Conv2d(in_channels=12, out_channels=24, kernel_size=5, stride=1, padding=0)
self.bn4 = nn.BatchNorm2d(24)
self.conv5 = nn.Conv2d(in_channels=24, out_channels=24, kernel_size=5, stride=1, padding=0)
self.bn5 = nn.BatchNorm2d(24)
self.fc1 = nn.Linear(24*50*50, len(classeNames))
def forward(self, x):
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn2(self.conv2(x)))
x = self.pool(x)
x = F.relu(self.bn4(self.conv4(x)))
x = F.relu(self.bn5(self.conv5(x)))
x = self.pool(x)
x = x.view(-1, 24*50*50)
x = self.fc1(x)
return x
device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using device".format(device))
model = Network_bn().to(device)
model
Using cuda device
Network_bn(
(conv1): Conv2d(3, 12, kernel_size=(5, 5), stride=(1, 1))
(bn1): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(12, 12, kernel_size=(5, 5), stride=(1, 1))
(bn2): BatchNorm2d(12, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(conv4): Conv2d(12, 24, kernel_size=(5, 5), stride=(1, 1))
(bn4): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv5): Conv2d(24, 24, kernel_size=(5, 5), stride=(1, 1))
(bn5): BatchNorm2d(24, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(fc1): Linear(in_features=60000, out_features=14, bias=True)
)
三、模型训练
1. 优化器与损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=0.0001)
loss_model = nn.CrossEntropyLoss()
1. model.train()
model.train()
的作用是启用 Batch Normalization 和 Dropout。
如果模型中有BN层(Batch Normalization)和Dropout,需要在训练时添加model.train()。model.train()是保证BN层能够用到每一批数据的均值和方差。对于Dropout,model.train()是随机取一部分网络连接来训练更新参数。
2. model.eval()
model.eval()
的作用是不启用 Batch Normalization 和 Dropout。
如果模型中有BN层(Batch Normalization)和Dropout,在测试时添加model.eval()。model.eval()是保证BN层能够用全部训练数据的均值和方差,即测试过程中要保证BN层的均值和方差不变。对于Dropout,model.eval()是利用到了所有网络连接,即不进行随机舍弃神经元。
训练完train样本后,生成的模型model要用来测试样本。在model(test)之前,需要加上model.eval(),否则的话,有输入数据,即使不训练,它也会改变权值。这是model中含有BN层和Dropout所带来的的性质。
在做one classification的时候,训练集和测试集的样本分布是不一样的,尤其需要注意这一点。
3. optimizer.zero_grad()
函数会遍历模型的所有参数,通过内置方法截断反向传播的梯度流,再将每个参数的梯度值设为0,即上一次的梯度记录被清空。
4. loss.backward()
PyTorch的反向传播(即tensor.backward()
)是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。
具体来说,torch.tensor是autograd包的基础类,如果你设置tensor的requires_grads为True,就会开始跟踪这个tensor上面的所有运算,如果你做完运算后使用tensor.backward()
,所有的梯度就会自动运算,tensor的梯度将会累加到它的.grad属性里面去。
更具体地说,损失函数loss是由模型的所有权重w经过一系列运算得到的,若某个w的requires_grads为True,则w的所有上层参数(后面层的权重w)的.grad_fn属性中就保存了对应的运算,然后在使用loss.backward()
后,会一层层的反向传播计算每个w的梯度值,并保存到该w的.grad属性中。
如果没有进行tensor.backward()
的话,梯度值将会是None,因此loss.backward()
要写在optimizer.step()
之前。
5. optimizer.step()
step()函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。因为梯度下降是基于梯度的,所以在执行optimizer.step()
函数前应先执行loss.backward()
函数来计算梯度。
注意:optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()
方法产生的。
更多详细介绍请参考文章:https://blog.csdn.net/qq_38251616/article/details/125178716
from torch.autograd import Variable
def test(model, test_loader, loss_model):
size = len(test_loader.dataset)
num_batches = len(test_loader)
model.eval()
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in test_loader:
X, y = X.to(device), y.to(device)
pred = model(X)
test_loss += loss_model(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \\n Accuracy: (100*correct):>0.1f%, Avg loss: test_loss:>8f \\n")
return correct,test_loss
def train(model,train_loader,loss_model,optimizer):
model=model.to(device)
model.train()
for i, (images, labels) in enumerate(train_loader, 0): #0是标起始位置的值。
images = Variable(images.to(device))
labels = Variable(labels.to(device))
optimizer.zero_grad()
outputs = model(images)
loss = loss_model(outputs, labels)
loss.backward()
optimizer.step()
if i % 1000 == 0:
print('[%5d] loss: %.3f' % (i, loss))
2. 模型的训练
test_acc_list = []
epochs = 30
for t in range(epochs):
print(f"Epoch t+1\\n-------------------------------")
train(model,train_loader,loss_model,optimizer)
test_acc,test_loss = test(model, test_loader, loss_model)
test_acc_list.append(test_acc)
print("Done!")
Epoch 1
-------------------------------
[ 0] loss: 2.670
Test Error:
Accuracy: 97.7%, Avg loss: 0.128858
Epoch 2
-------------------------------
[ 0] loss: 0.029
Test Error:
Accuracy: 99.2%, Avg loss: 0.039848
......
Epoch 29
-------------------------------
[ 0] loss: 0.000
Test Error:
Accuracy: 99.2%, Avg loss: 0.016334
Epoch 30
-------------------------------
[ 0] loss: 0.000
Test Error:
Accuracy: 99.2%, Avg loss: 0.016901
Done!
四、结果分析
import numpy as np
import matplotlib.pyplot as plt
x = [i for i in range(1,31)]
plt.plot(x, test_acc_list, label="Accuracy", alpha=0.8)
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend()
plt.show()
以上是关于深度学习100例 | 第3天:交通标志识别 - PyTorch实现的主要内容,如果未能解决你的问题,请参考以下文章
深度学习100例-卷积神经网络(Inception-ResNet-v2)识别交通标志 | 第14天
深度学习100例-卷积神经网络(CNN)花朵识别 | 第4天
深度学习100例 - 卷积神经网络(CNN)实现车牌识别 | 第15天
深度学习100例-卷积神经网络(CNN)实现mnist手写数字识别 | 第1天