目录
1、YOLOv5骨干网络模型图:
2、C3模块介绍:
3、C3模块的主要代码:
4、完整的code
5、运行结果展示:
(1)使用SGD优化器
(2)使用Adam优化器
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊 | 接辅导、项目定制
今天这篇博客的主要内容是,实现Yolov5里面的C3模块。
1、YOLOv5骨干网络模型图:
更多详细的v5模型介绍:【目标检测】yolov5模型详解-CSDN博客
从上述YOLOv5的模型图中,我们可以看到,C3模块 主要出现在Backbone模块和Neck模块。
这篇文章,我们主要先来看下YOLOv5里面的C3模块。
2、C3模块介绍:
YOLOv5中的C3模块是目标检测算法中的一个关键组件,主要用于特征提取和融合。该模块在YOLOv5的骨干网络中扮演着重要角色,帮助算法更好地理解和分析图像。
具体来说,C3模块的结构较为复杂,它包含了多个Conv模块和一个Bottleneck模块。
Conv模块主要负责对输入的特征图进行卷积操作,以提取更高级别的特征。这种卷积操作可以通过任意的卷积核进行,但根据设计,采用1*1的卷积核可以起到降维或升维的作用,对于提取特征有重要意义。
Bottleneck模块是C3模块的另一个重要组成部分,其设计有利于增加网络的感受野,同时减少计算量。感受野的增加可以让网络更加关注物体的全局信息,从而提高特征提取的效果。具体来说,Bottleneck模块包含两个部分:一个(1,1)的卷积,用于将输入特征图的通道数减半;以及一个(3,3)的卷积,用于将通道数翻倍。
此外,C3模块还引入了一些创新性的技术,如在模块中引入自注意力机制,以加强对图像中重要区域的关注。这种机制在处理具有复杂背景和遮挡情况的图像时,能够提升模型对关键特征的提取能力。
总的来说,YOLOv5中的C3模块是一个设计精良的特征提取模块,它通过复杂的结构和先进的技术,提高了目标检测算法的性能和准确性。
我们先来看下C3模块的模型图(如下)。
这次的模型搭建就是C3模块。
3、C3模块的主要代码:
class C3(nn.Module):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
class Conv(nn.Module):
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
super(Conv, self).__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
def forward(self,x):
return self.act(self.bn(self.conv(x)))
def autopad(k, p=None): # kernel, padding
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
return p
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_, c2, 3, 1, g=g)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class model_K(nn.Module):
def __init__(self):
super(model_K, self).__init__()
# 卷积模块
self.Conv = Conv(3, 32, 3, 2)
# C3模块
self.C3_1 = C3(32, 64, 3, 2)
# 全连接网络层,用于分类
self.classifier = nn.Sequential(
nn.Linear(in_features=802816, out_features=100),
nn.ReLU(),
nn.Linear(in_features=100, out_features=4)
)
def forward(self,x):
x = self.Conv(x)
x = self.C3_1(x)
x = torch.flatten(x, start_dim=1)
x = self.classifier(x)
return x
model = model_K().to(device)
print(model)
4、完整的code
将一般的网络结构改成C3网络结构,完整代码如下:
import copy
import pathlib
import warnings
import matplotlib.pyplot as plt
import torch
from torch import nn
from torchvision import datasets
from torchvision.transforms import transforms
import matplotlib as mpl
mpl.use('Agg') # 在服务器上运行的时候,打开注释
'''
利用v5里面的C3来模块搭建网络
'''
# 1、设备检查
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
# 2、导入数据
data_dir = './data'
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*'))
classNames = [str(path).split('/')[1] for path in data_paths]
print(classNames) # ['cat', 'dog']
# 3、图像预处理
train_transforms = transforms.Compose([
transforms.Resize([224, 224]),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229,0.224,0.225]
)
])
test_transforms = transforms.Compose([
transforms.Resize([224,224]),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229,0.224,0.225]
)
])
total_data = datasets.ImageFolder('./data', transform=train_transforms)
# 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])
print(train_size, test_size) # 2720 680
# 5、加载数据
batch_size = 4
train_dl = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size, shuffle=True)
test_dl = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=True)
for X,y in test_dl:
print('Shape of X [N,C,H,W]:', X.shape) # torch.Size([4, 3, 224, 224])
print('Shape of y:',y.shape, y.dtype)
break
# (二)C3模块的模型搭建
def autopad(k, p=None): # kernel, padding
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
return p
class Conv(nn.Module):
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
super(Conv, self).__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
def forward(self,x):
return self.act(self.bn(self.conv(x)))
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_, c2, 3, 1, g=g)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class C3(nn.Module):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
class model_K(nn.Module):
def __init__(self):
super(model_K, self).__init__()
# 卷积模块
self.Conv = Conv(3, 32, 3, 2)
# C3模块
self.C3_1 = C3(32, 64, 3, 2)
# 全连接网络层,用于分类
self.classifier = nn.Sequential(
nn.Linear(in_features=802816, out_features=100),
nn.ReLU(),
nn.Linear(in_features=100, out_features=4)
)
def forward(self,x):
x = self.Conv(x)
x = self.C3_1(x)
x = torch.flatten(x, start_dim=1)
x = self.classifier(x)
return x
model = model_K().to(device)
print(model)
# (三)、编写训练函数
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
num_batches = len(dataloader)
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)
# 反向传播
optimizer.zero_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 test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, test_acc = 0, 0
# 当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs, target in dataloader:
imgs, target = imgs.to(device), target.to(device)
# 计算loss
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
# 正式训练
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
epochs = 20
train_loss = []
train_acc = []
test_loss = []
test_acc = []
best_acc = 0 # 设置一个最佳准确率,作为最佳模型的判别指标
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
# 保存最佳模型到best_model
if epoch_test_acc > best_acc:
best_acc = epoch_test_acc
best_model = copy.deepcopy(model)
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}')
print(template.format(epoch+1, epoch_train_acc, epoch_train_loss,epoch_test_acc,epoch_test_loss,lr))
# 保存最佳模型到文件中
PATH = './best_model.pth' # 保存的参数文件名
torch.save(model.state_dict(), PATH)
print('Done')
# (四)、结果可视化
warnings.filterwarnings('ignore')
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.savefig("/data/jupyter/deep_demo/p08_v5-C3/resultImg.jpg") # 保存图片在服务器的位置
plt.show()
# (五)、模型评估
best_model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, best_model, loss_fn)
print(epoch_test_acc, epoch_test_loss)
5、运行结果展示:
(1)使用SGD优化器
(2)使用Adam优化器
6、总结C3模块
C3模块主要由三个Conv模块和一个Bottleneck模块组成。
Conv模块负责对输入的特征图进行卷积操作,提取图像中的特征。
Bottleneck模块则进一步对特征进行处理,增加网络的感受野并减少计算量。