李沐45_SSD实现——自学笔记

news/2024/5/17 11:45:24

主体思路:
1.生成一堆锚框
2.根据真实标签为每个锚框打标(类别、偏移、mask)
3.模型为每个锚框做一个预测(类别、偏移)
4.计算上述二者的差异损失,以更新模型weights

先读取一张图像。 它的高度和宽度分别为561和728像素。

%matplotlib inline
import torch
from d2l import torch as d2limg = d2l.plt.imread('catdog.jpg')
h, w = img.shape[:2]
h, w
(561, 728)

display_anchors函数定义如下。 我们在特征图(fmap)上生成锚框(anchors),每个单位(像素)作为锚框的中心。 由于锚框中的(x,y)轴坐标值(anchors)已经被除以特征图(fmap)的宽度和高度,因此这些值介于0和1之间,表示特征图中锚框的相对位置。

def display_anchors(fmap_w, fmap_h, s):d2l.set_figsize()# 前两个维度上的值不影响输出fmap = torch.zeros((1, 10, fmap_h, fmap_w))anchors = d2l.multibox_prior(fmap, sizes=s, ratios=[1, 2, 0.5])bbox_scale = torch.tensor((w, h, w, h))d2l.show_bboxes(d2l.plt.imshow(img).axes,anchors[0] * bbox_scale)

锚框的尺度设置为0.15,特征图的高度和宽度设置为4。图像上4行和4列的锚框的中心是均匀分布的。

display_anchors(fmap_w=4, fmap_h=4, s=[0.15])

在这里插入图片描述

将特征图的高度和宽度减小一半,然后使用较大的锚框来检测较大的目标。 当尺度设置为0.4时,一些锚框将彼此重叠。

display_anchors(fmap_w=2, fmap_h=2, s=[0.4])

在这里插入图片描述

进一步将特征图的高度和宽度减小一半,然后将锚框的尺度增加到0.8。 此时,锚框的中心即是图像的中心

display_anchors(fmap_w=1, fmap_h=1, s=[0.8])

在这里插入图片描述

SSD的实现 单发多框检测

定义了这样一个类别预测层,通过参数num_anchors和num_classes分别指定了a
和q。 该图层使用填充为1的3X3的卷积层。此卷积层的输入和输出的宽度和高度保持不变。

%matplotlib inline
import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2ldef cls_predictor(num_inputs, num_anchors, num_classes):return nn.Conv2d(num_inputs, num_anchors * (num_classes + 1),kernel_size=3, padding=1)

边界框预测层

每个锚框预测4个偏移量,而不是q+1个类别。

def bbox_predictor(num_inputs, num_anchors):return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)

连接多尺度的预测

def forward(x, block):return block(x)Y1 = forward(torch.zeros((2, 8, 20, 20)), cls_predictor(8, 5, 10))
Y2 = forward(torch.zeros((2, 16, 10, 10)), cls_predictor(16, 3, 10))
Y1.shape, Y2.shape
(torch.Size([2, 55, 20, 20]), torch.Size([2, 33, 10, 10]))

将通道维移到最后一维。 因为不同尺度下批量大小仍保持不变,我们可以将预测结果转成二维的(批量大小,高X宽X通道数)的格式,以方便之后在维度1上的连结

def flatten_pred(pred):return torch.flatten(pred.permute(0, 2, 3, 1), start_dim=1)def concat_preds(preds):return torch.cat([flatten_pred(p) for p in preds], dim=1)

尽管Y1和Y2在通道数、高度和宽度方面具有不同的大小,我们仍然可以在同一个小批量的两个不同尺度上连接这两个预测输出。

concat_preds([Y1, Y2]).shape
torch.Size([2, 25300])

高和宽减半块

高和宽减半块down_sample_blk,该模块将输入特征图的高度和宽度减半,可以扩大每个单元在其输出特征图中的感受野。

def down_sample_blk(in_channels, out_channels):blk = []for _ in range(2):blk.append(nn.Conv2d(in_channels, out_channels,kernel_size=3, padding=1))blk.append(nn.BatchNorm2d(out_channels))blk.append(nn.ReLU())in_channels = out_channelsblk.append(nn.MaxPool2d(2))return nn.Sequential(*blk)

在以下示例中,我们构建的高和宽减半块会更改输入通道的数量,并将输入特征图的高度和宽度减半。

forward(torch.zeros((2, 3, 20, 20)), down_sample_blk(3, 10)).shape
torch.Size([2, 10, 10, 10])

基本网络块

基本网络块用于从输入图像中抽取特征,输出特征形状32X32

def base_net():blk = []num_filters = [3, 16, 32, 64]for i in range(len(num_filters) - 1):blk.append(down_sample_blk(num_filters[i], num_filters[i+1]))return nn.Sequential(*blk)forward(torch.zeros((2, 3, 256, 256)), base_net()).shape
torch.Size([2, 64, 32, 32])

完整模型

完整的单发多框检测模型由五个模块组成。每个块生成的特征图既用于生成锚框,又用于预测这些锚框的类别和偏移量。在这五个模块中,第一个是基本网络块,第二个到第四个是高和宽减半块,最后一个模块使用全局最大池将高度和宽度都降到1。

def get_blk(i):if i == 0:blk = base_net()elif i == 1:blk = down_sample_blk(64, 128)elif i == 4:blk = nn.AdaptiveMaxPool2d((1,1))else:blk = down_sample_blk(128, 128)return blk

每个块定义前向传播。与图像分类任务不同,此处的输出包括:CNN特征图Y;在当前尺度下根据Y生成的锚框;预测的这些锚框的类别和偏移量(基于Y)。

def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):Y = blk(X)anchors = d2l.multibox_prior(Y, sizes=size, ratios=ratio)cls_preds = cls_predictor(Y)bbox_preds = bbox_predictor(Y)return (Y, anchors, cls_preds, bbox_preds)
sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], [0.71, 0.79],[0.88, 0.961]]
ratios = [[1, 2, 0.5]] * 5
num_anchors = len(sizes[0]) + len(ratios[0]) - 1

完整的模型TinySSD

class TinySSD(nn.Module):def __init__(self, num_classes, **kwargs):super(TinySSD, self).__init__(**kwargs)self.num_classes = num_classesidx_to_in_channels = [64, 128, 128, 128, 128]for i in range(5):# 即赋值语句self.blk_i=get_blk(i)setattr(self, f'blk_{i}', get_blk(i))setattr(self, f'cls_{i}', cls_predictor(idx_to_in_channels[i],num_anchors, num_classes))setattr(self, f'bbox_{i}', bbox_predictor(idx_to_in_channels[i],num_anchors))def forward(self, X):anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5for i in range(5):# getattr(self,'blk_%d'%i)即访问self.blk_iX, anchors[i], cls_preds[i], bbox_preds[i] = blk_forward(X, getattr(self, f'blk_{i}'), sizes[i], ratios[i],getattr(self, f'cls_{i}'), getattr(self, f'bbox_{i}'))anchors = torch.cat(anchors, dim=1)cls_preds = concat_preds(cls_preds)cls_preds = cls_preds.reshape(cls_preds.shape[0], -1, self.num_classes + 1)bbox_preds = concat_preds(bbox_preds)return anchors, cls_preds, bbox_preds

创建一个模型实例,然后使用它对一个256X256像素的小批量图像X执行前向传播.第一个模块输出特征图的形状为32X32。 回想一下,第二到第四个模块为高和宽减半块,第五个模块为全局汇聚层。 由于以特征图的每个单元为中心有4个锚框生成,因此在所有五个尺度下,每个图像总共生成5444

net = TinySSD(num_classes=1)
X = torch.zeros((32, 3, 256, 256))
anchors, cls_preds, bbox_preds = net(X)print('output anchors:', anchors.shape)
print('output class preds:', cls_preds.shape)
print('output bbox preds:', bbox_preds.shape)
output anchors: torch.Size([1, 5444, 4])
output class preds: torch.Size([32, 5444, 2])
output bbox preds: torch.Size([32, 21776])

训练模型

读取数据集和初始化

batch_size = 32
train_iter, _ = d2l.load_data_bananas(batch_size)
Downloading ../data/banana-detection.zip from http://d2l-data.s3-accelerate.amazonaws.com/banana-detection.zip...
read 1000 training examples
read 100 validation examples

香蕉检测数据集中,目标的类别数为1。 定义好模型后,我们需要初始化其参数并定义优化算法。

device, net = d2l.try_gpu(), TinySSD(num_classes=1)
trainer = torch.optim.SGD(net.parameters(), lr=0.2, weight_decay=5e-4)

定义损失和平均函数

使用L1范数损失,即预测值和真实值之差的绝对值。 掩码变量bbox_masks令负类锚框和填充锚框不参与损失的计算。 最后,我们将锚框类别和偏移量的损失相加,以获得模型的最终损失函数。

cls_loss = nn.CrossEntropyLoss(reduction='none')
bbox_loss = nn.L1Loss(reduction='none')def calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):batch_size, num_classes = cls_preds.shape[0], cls_preds.shape[2]cls = cls_loss(cls_preds.reshape(-1, num_classes),cls_labels.reshape(-1)).reshape(batch_size, -1).mean(dim=1)bbox = bbox_loss(bbox_preds * bbox_masks,bbox_labels * bbox_masks).mean(dim=1)return cls + bbox

沿用准确率平均结果,平均绝对误差来评价预测结果

def cls_eval(cls_preds, cls_labels):# 由于类别预测结果放在最后一维,argmax需要指定最后一维。return float((cls_preds.argmax(dim=-1).type(cls_labels.dtype) == cls_labels).sum())def bbox_eval(bbox_preds, bbox_labels, bbox_masks):return float((torch.abs((bbox_labels - bbox_preds) * bbox_masks)).sum())

训练模型

需要在模型的前向传播过程中生成多尺度锚框(anchors),并预测其类别(cls_preds)和偏移量(bbox_preds)。 然后,我们根据标签信息Y为生成的锚框标记类别(cls_labels)和偏移量(bbox_labels)。 最后,我们根据类别和偏移量的预测和标注值计算损失函数。

num_epochs, timer = 20, d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],legend=['class error', 'bbox mae'])
net = net.to(device)
for epoch in range(num_epochs):# 训练精确度的和,训练精确度的和中的示例数# 绝对误差的和,绝对误差的和中的示例数metric = d2l.Accumulator(4)net.train()for features, target in train_iter:timer.start()trainer.zero_grad()X, Y = features.to(device), target.to(device)# 生成多尺度的锚框,为每个锚框预测类别和偏移量anchors, cls_preds, bbox_preds = net(X)# 为每个锚框标注类别和偏移量bbox_labels, bbox_masks, cls_labels = d2l.multibox_target(anchors, Y)# 根据类别和偏移量的预测和标注值计算损失函数l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,bbox_masks)l.mean().backward()trainer.step()metric.add(cls_eval(cls_preds, cls_labels), cls_labels.numel(),bbox_eval(bbox_preds, bbox_labels, bbox_masks),bbox_labels.numel())cls_err, bbox_mae = 1 - metric[0] / metric[1], metric[2] / metric[3]animator.add(epoch + 1, (cls_err, bbox_mae))
print(f'class err {cls_err:.2e}, bbox mae {bbox_mae:.2e}')
print(f'{len(train_iter.dataset) / timer.stop():.1f} examples/sec on 'f'{str(device)}')
class err 3.32e-03, bbox mae 3.24e-03
4187.2 examples/sec on cuda:0

在这里插入图片描述

预测目标

将图像中感兴趣的目标检测出来,读取并调整测试图像的大小,然后将其转成卷积层需要的四维格式。

X = torchvision.io.read_image('banana.jpg').unsqueeze(0).float()
img = X.squeeze(0).permute(1, 2, 0).long()

使用下面的multibox_detection函数,我们可以根据锚框及其预测偏移量得到预测边界框。然后,通过非极大值抑制来移除相似的预测边界框

def predict(X):net.eval()anchors, cls_preds, bbox_preds = net(X.to(device))cls_probs = F.softmax(cls_preds, dim=2).permute(0, 2, 1)output = d2l.multibox_detection(cls_probs, bbox_preds, anchors)idx = [i for i, row in enumerate(output[0]) if row[0] != -1]return output[0, idx]output = predict(X)

筛选所有置信度不低于0.9的边界框,做为最终输出。

def display(img, output, threshold):d2l.set_figsize((5, 5))fig = d2l.plt.imshow(img)for row in output:score = float(row[1])if score < threshold:continueh, w = img.shape[0:2]bbox = [row[2:6] * torch.tensor((w, h, w, h), device=row.device)]d2l.show_bboxes(fig.axes, bbox, '%.2f' % score, 'w')display(img, output.cpu(), threshold=0.9)

在这里插入图片描述


http://www.mrgr.cn/p/85046584

相关文章

IO基础-IO多路复用基础

Java的Selector封装了底层epoll和poll的API&#xff0c;可以通过指定如下参数来调用执行的内核调用, 在Linux平台&#xff0c;如果指定 -Djava.nio.channels.spi.SelectorProvidersun.nio.ch.PollSelectorProvider 则底层调用poll&#xff0c; -Djava.nio.channels.spi.Selec…

HarmonyOS实战开发-自定义通知角标、如何设定应用的桌面图标角标的功能。

介绍 本示例主要展示了设定应用的桌面图标角标的功能&#xff0c;使用ohos.notificationManager 接口&#xff0c;进行桌面角标的设置&#xff0c;通知的发送&#xff0c;获取等。 效果预览 使用说明 在使用本应用时&#xff0c;需安装并启动仿桌面应用&#xff1b;在主界面…

Linux系统一键安装DataEase结合内网穿透实现公网访问本地WebUI界面

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

GPT国内怎么用

2022年11月&#xff0c;OpenAI发布了ChatGPT&#xff0c;这标志着大型语言模型在自然语言处理领域迈出了巨大的一步。ChatGPT不仅在生成文本方面表现出了惊人的流畅度和连贯性&#xff0c;更为人工智能应用开启了全新的可能性。 ChatGPT的推出促进了人工智能技术在多个领域的广…

【GUI软件】小红书按关键词采集笔记详情,支持多个关键词,含笔记正文、转评赞藏等,爬了1024w条!

小红书采集软件,根据关键词自动采集笔记详情。一、背景介绍 1.1 爬取目标 熟悉我的小伙伴都了解,我之前开发过2款软件:【GUI软件】小红书搜索结果批量采集,支持多个关键词同时抓取! 【GUI软件】小红书详情数据批量采集,含笔记内容、转评赞藏等,支持多笔记同时采集!现在…

提升法律文书起草效率:AlphaGPT 助力律师快速生成诉讼和仲裁文件

法律文书起草对于法律专业人士而言是一项基础而关键的任务。无论是民事、刑事还是行政诉讼&#xff0c;以及仲裁案件&#xff0c;精确的法律文书撰写对于案件的成功至关重要。然而&#xff0c;这一过程往往既耗时又复杂&#xff0c;尤其是在处理复杂的案情和面对当事人难以理解…

图片批量高效管理,支持批量转换图片格式并按比例进行放大150%,高效掌握图片

在这个数字化时代&#xff0c;图片已经成为我们生活和工作中不可或缺的一部分。然而&#xff0c;面对海量的图片文件&#xff0c;如何高效地进行管理、编辑和优化&#xff0c;成为了摆在我们面前的一大难题。现在&#xff0c;一款全新的图片批量管理工具来了&#xff0c;它能够…

进程控制

linux下进程控制,等待,退出,fork()函数,fork返回id的细节目录1.fork函数1.1如何理解fork函数有两个返回值问题1.2如何理解fork给父进程返回自己的pid,自己返回01.3如何理解同一个id值,有两个不同的值2.进程终止2.1进程退出码2.2进程退出2.2.1进程常见退出办法2.2.3exit()…

React脚手架的搭建与使用

React脚手架是开发现代Web应用的必备&#xff0c;其充分利用Webpack、Babel、ESlint等工具辅助项目的开发&#xff0c;当然这些工具也无需手动配置即可使用&#xff0c;脚手架的意义更多的是关注的是业务而不是工具的配置&#xff1b;项目的整体技术架构为&#xff1a;react w…

JS 中 reduce()方法及使用详解

reduce()方法可以搞定的东西特别多,就是循环遍历能做的,reduce都可以做,比如数组求和、数组求积、统计数组中元素出现的次数、数组去重等等。 reduce() 方法对数组中的每个元素执行一个由您提供的reduce函数(依次执行),将其结果汇总为单个返回值。 1、语法介绍// arr.reduc…

探索Java世界中的七大排序算法(上)

文章目录 排序的概念直接插入排序希尔排序( 缩小增量排序)选择排序堆排序冒泡排序 在计算机科学中&#xff0c;排序算法是一类重要的算法&#xff0c;它们用于将一组元素按照一定的顺序进行排列。在Java编程中&#xff0c;我们经常需要对数组或集合进行排序操作。本文将介绍Jav…

高并发(AQS)

AQS 抽象的队列同步器框架,主要通过程序来构建锁和同步器AQS 抽象的队列同步器框架,主要通过程序来构建锁和同步器 AQS 的全称为 AbstractQueuedSynchronizer ,翻译过来的意思就是抽象队列同步器,它和Java的Synchronized作用和一样,用来同步加锁; 特性对比ReentrantLock …

JAVA基础-流程控制、字符串

一、java基础 1、java主类结构 package com.study.again001; 包名public class helloword { 类名 static String s1 = "1"; 静态成员变量 public static void main(String[] args) { main方法 String s2 = "2"; 局部变量 …

C到二进制概述

此帖是记录视频内容,防止后续自己遗忘 一、预处理 1、库文件 预处理等于一个文本粘贴过程,编译后把库文件展开,展开本质也为粘贴内容include 本质是在其中特定路径搜索(usr/include...),而#include "xxx"是在当前路径下进行搜索 。其中#include" "高于…

Qt6连接MySQL

Qt6连接MySQL Qt6编译MySQL的过程太变态了&#xff0c;MinGW和MSVC都很费劲。 主要参考qt6.5.0MySQL驱动手动编译以及数据库连接详细教程以及注意事项附资源链接&#xff0c;这篇文章堪称保姆级教程&#xff0c;写的十分详细。 笔者这里踩了个坑&#xff0c;在按照上文中的过…

AliyunCTF 2024 - BadApple

文章目录 前言环境搭建漏洞分析漏洞利用参考 前言 本文首发于看雪论坛 https://bbs.kanxue.com/thread-281291.htm 依稀记得那晚被阿里CTF支配的恐惧&#xff0c;今年的阿里CTF笔者就做了一道签到PWN题&#xff0c;当时也是下定决心要学习 jsc pwn 然后复现这道 BadApple 题目…

UI——数据表和UI绑定

目的创建数据表 创建UI控件 玩家角色蓝图UI按键逻辑 UI与数据表绑定1.创建数据表2.创建UI控件3.玩家角色蓝图UI按键逻辑4.UI与数据表绑定本文来自博客园,作者:荒坂株式会社,博客内容均属学习笔记,只做交流之用

MBR20200FCT-ASEMI肖特基二极管MBR20200FCT

MBR20200FCT-ASEMI肖特基二极管MBR20200FCT编辑:ll MBR20200FCT-ASEMI肖特基二极管MBR20200FCT 型号:MBR20200FCT 品牌:ASEMI 封装:TO-220 最大平均正向电流(IF):20A 最大循环峰值反向电压(VRRM):200V 最大正向电压(VF):0.90V 工作温度:-65C~175C 反向恢复时间:…

ELK-Kibana 部署

目录 一、在 node1 节点上操作 1.1.安装 Kibana 1.2.设置 Kibana 的主配置文件 1.3.启动 Kibana 服务 1.4.验证 Kibana 1.5.将 Apache 服务器的日志&#xff08;访问的、错误的&#xff09;添加到 ES 并通过 Kibana 显示 1.6. 浏览器访问 二、部署FilebeatELK&…

App测试中,强制等待和隐式等待谁更强?

简介 添加等待是为了确保自动化脚本在执行过程中与应用程序之间的同步和稳定性。 应用程序的响应时间是不确定的,可能存在网络延迟、加载时间、动画效果等因素。如果在执行自动化脚本时没有适当的等待机制,脚本可能会在应用程序还未完成相应操作或加载完成之前继续执行下一步…