1.9 MiB
1.9 MiB
In [1]:
import torch
from d2l import torch as d2lIn [2]:
d2l.set_figsize()
img = d2l.plt.imread('../data/catdog.jpg')
d2l.plt.imshow(img);In [3]:
def box_corner_to_center(boxes):
"""从(左上,右下)转换到(中间,宽度,高度)"""
x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
cx = (x1 + x2) / 2
cy = (y1 + y2) / 2
w = x2 - x1
h = y2 - y1
boxes = torch.stack((cx, cy, w, h), axis=-1)
return boxes
def box_center_to_corner(boxes):
"""从(中间,宽度,高度)转换到(左上,右下)"""
cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
x1 = cx - 0.5 * w
y1 = cy - 0.5 * h
x2 = cx + 0.5 * w
y2 = cy + 0.5 * h
boxes = torch.stack((x1, y1, x2, y2), axis=-1)
return boxesIn [4]:
dog_bbox, cat_bbox = [60.0, 45.0, 378.0, 516.0], [400.0, 112.0, 655.0, 493.0]In [5]:
boxes = torch.tensor((dog_bbox, cat_bbox))
box_center_to_corner(box_corner_to_center(boxes)) == boxesOut [5]:
tensor([[True, True, True, True],
[True, True, True, True]])In [6]:
def bbox_to_rect(bbox, color):
# 将边界框(左上x,左上y,右下x,右下y)格式转换成matplotlib格式:
# ((左上x,左上y),宽,高)
return d2l.plt.Rectangle(
xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
fill=False, edgecolor=color, linewidth=2)In [7]:
fig = d2l.plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'blue'))
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'red'))Out [7]:
<matplotlib.patches.Rectangle at 0x7f64a187bf90>
In [8]:
#@save
def multibox_prior(data, sizes, ratios):
"""生成以每个像素为中心具有不同形状的锚框"""
in_height, in_width = data.shape[-2:]
device, num_sizes, num_ratios = data.device, len(sizes), len(ratios)
boxes_per_pixel = (num_sizes + num_ratios - 1)
size_tensor = torch.tensor(sizes, device=device)
ratio_tensor = torch.tensor(ratios, device=device)
# 为了将锚点移动到像素的中心,需要设置偏移量。
# 因为一个像素的高为1且宽为1,我们选择偏移我们的中心0.5
offset_h, offset_w = 0.5, 0.5
steps_h = 1.0 / in_height # 在y轴上缩放步长
steps_w = 1.0 / in_width # 在x轴上缩放步长
# 生成锚框的所有中心点
center_h = (torch.arange(in_height, device=device) + offset_h) * steps_h
center_w = (torch.arange(in_width, device=device) + offset_w) * steps_w
shift_y, shift_x = torch.meshgrid(center_h, center_w, indexing='ij')
shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)
# 生成 “boxes_per_pixel” 个高和宽,
# 之后用于创建锚框的四角坐标(xmin,xmax,ymin,ymax)
w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]),
sizes[0] * torch.sqrt(ratio_tensor[1:])))\
* in_height / in_width # 处理矩形输入
h = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]),
sizes[0] / torch.sqrt(ratio_tensor[1:])))
# 除以2来获得半高和半宽
anchor_manipulations = torch.stack((-w, -h, w, h)).T.repeat(
in_height * in_width, 1) / 2
# 每个中心点都将有 “boxes_per_pixel” 个锚框,
# 所以生成含所有锚框中心的网格,重复了 “boxes_per_pixel” 次
out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],
dim=1).repeat_interleave(boxes_per_pixel, dim=0)
output = out_grid + anchor_manipulations
return output.unsqueeze(0)In [9]:
img = d2l.plt.imread('../data/catdog.jpg')
h, w = img.shape[:2]In [10]:
print(h, w)
X = torch.rand(size=(1, 3, h, w))
Y = multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shapeOut [10]:
561 728
torch.Size([1, 2042040, 4])
In [11]:
def show_bboxes(axes, bboxes, labels=None, colors=None):
"""显示所有边界框"""
def _make_list(obj, default_values=None):
if obj is None:
obj = default_values
elif not isinstance(obj, (list, tuple)):
obj = [obj]
return obj
labels = _make_list(labels)
colors = _make_list(colors, ['b', 'g', 'r', 'm', 'c'])
for i, bbox in enumerate(bboxes):
color = colors[i % len(colors)]
rect = d2l.bbox_to_rect(bbox.detach().numpy(), color)
axes.add_patch(rect)
if labels and len(labels) > i:
text_color = 'k' if color == 'w' else 'w'
axes.text(rect.xy[0], rect.xy[1], labels[i],
va='center', ha='center', fontsize=9, color=text_color,
bbox=dict(facecolor=color, lw=0))In [12]:
boxes = Y.reshape(h, w, 5, 4)In [13]:
bbox_scale = torch.tensor((w, h, w, h))
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, boxes[250, 250, :, :] * bbox_scale,
['s=0.75, r=1', 's=0.5, r=1', 's=0.25, r=1', 's=0.75, r=2',
's=0.75, r=0.5'])In [14]:
def box_iou(boxes1, boxes2):
"""计算两个锚框或边界框列表中成对的交并比"""
box_area = lambda boxes: ((boxes[:, 2] - boxes[:, 0]) *
(boxes[:, 3] - boxes[:, 1]))
# boxes1,boxes2,areas1,areas2的形状:
# boxes1:(boxes1的数量,4),
# boxes2:(boxes2的数量,4),
# areas1:(boxes1的数量,),
# areas2:(boxes2的数量,)
areas1 = box_area(boxes1)
areas2 = box_area(boxes2)
# inter_upperlefts,inter_lowerrights,inters的形状:
# (boxes1的数量,boxes2的数量,2)
inter_upperlefts = torch.max(boxes1[:, None, :2], boxes2[:, :2])
inter_lowerrights = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])
inters = (inter_lowerrights - inter_upperlefts).clamp(min=0)
# inter_areasandunion_areas的形状:(boxes1的数量,boxes2的数量)
inter_areas = inters[:, :, 0] * inters[:, :, 1]
union_areas = areas1[:, None] + areas2 - inter_areas
return inter_areas / union_areasIn [15]:
#@save
def assign_anchor_to_bbox(ground_truth, anchors, device, iou_threshold=0.5):
"""将最接近的真实边界框分配给锚框"""
num_anchors, num_gt_boxes = anchors.shape[0], ground_truth.shape[0]
# 位于第i行和第j列的元素x_ij是锚框i和真实边界框j的IoU
jaccard = box_iou(anchors, ground_truth)
# 对于每个锚框,分配的真实边界框的张量
anchors_bbox_map = torch.full((num_anchors,), -1, dtype=torch.long,
device=device)
# 根据阈值,决定是否分配真实边界框
max_ious, indices = torch.max(jaccard, dim=1)
anc_i = torch.nonzero(max_ious >= iou_threshold).reshape(-1)
box_j = indices[max_ious >= iou_threshold]
anchors_bbox_map[anc_i] = box_j
col_discard = torch.full((num_anchors,), -1)
row_discard = torch.full((num_gt_boxes,), -1)
for _ in range(num_gt_boxes):
max_idx = torch.argmax(jaccard)
box_idx = (max_idx % num_gt_boxes).long()
anc_idx = (max_idx / num_gt_boxes).long()
anchors_bbox_map[anc_idx] = box_idx
jaccard[:, box_idx] = col_discard
jaccard[anc_idx, :] = row_discard
return anchors_bbox_map
In [16]:
def offset_boxes(anchors, assigned_bb, eps=1e-6):
"""对锚框偏移量的转换"""
c_anc = d2l.box_corner_to_center(anchors)
c_assigned_bb = d2l.box_corner_to_center(assigned_bb)
offset_xy = 10 * (c_assigned_bb[:, :2] - c_anc[:, :2]) / c_anc[:, 2:]
offset_wh = 5 * torch.log(eps + c_assigned_bb[:, 2:] / c_anc[:, 2:])
offset = torch.cat([offset_xy, offset_wh], axis=1)
return offsetIn [17]:
def multibox_target(anchors, labels):
"""使用真实边界框标记锚框"""
batch_size, anchors = labels.shape[0], anchors.squeeze(0) # 问题:这里赋值给 anchors 会覆盖,但原代码就是这样
batch_offset, batch_mask, batch_class_labels = [], [], []
device, num_anchors = anchors.device, anchors.shape[0]
for i in range(batch_size):
label = labels[i, :, :]
anchors_bbox_map = assign_anchor_to_bbox(
label[:, 1:], anchors, device)
bbox_mask = ((anchors_bbox_map >= 0).float().unsqueeze(-1)).repeat(
1, 4)
# 将类标签和分配的边界框坐标初始化为零
class_labels = torch.zeros(num_anchors, dtype=torch.long,
device=device)
assigned_bb = torch.zeros((num_anchors, 4), dtype=torch.float32,
device=device)
# 使用真实边界框来标记锚框的类别。
# 如果一个锚框没有被分配,标记其为背景(值为零)
indices_true = torch.nonzero(anchors_bbox_map >= 0)
bb_idx = anchors_bbox_map[indices_true]
class_labels[indices_true] = label[bb_idx, 0].long() + 1
assigned_bb[indices_true] = label[bb_idx, 1:]
# 偏移量转换
offset = offset_boxes(anchors, assigned_bb) * bbox_mask
batch_offset.append(offset.reshape(-1))
batch_mask.append(bbox_mask.reshape(-1))
batch_class_labels.append(class_labels)
bbox_offset = torch.stack(batch_offset)
bbox_mask = torch.stack(batch_mask)
class_labels = torch.stack(batch_class_labels)
return (bbox_offset, bbox_mask, class_labels)In [18]:
ground_truth = torch.tensor([[0, 0.1, 0.08, 0.52, 0.92],
[1, 0.55, 0.2, 0.9, 0.88]])
anchors = torch.tensor([[0, 0.1, 0.2, 0.3], [0.15, 0.2, 0.4, 0.4],
[0.63, 0.05, 0.88, 0.98], [0.66, 0.45, 0.8, 0.8],
[0.57, 0.3, 0.92, 0.9]])
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, ground_truth[:, 1:] * bbox_scale, ['dog', 'cat'], 'k')
show_bboxes(fig.axes, anchors * bbox_scale, ['0', '1', '2', '3', '4']);In [19]:
labels = multibox_target(anchors.unsqueeze(dim=0),
ground_truth.unsqueeze(dim=0))In [20]:
def offset_inverse(anchors, offset_preds):
"""根据带有预测偏移量的锚框来预测边界框"""
anc = d2l.box_corner_to_center(anchors)
pred_bbox_xy = (offset_preds[:, :2] * anc[:, 2:] / 10) + anc[:, :2]
pred_bbox_wh = torch.exp(offset_preds[:, 2:] / 5) * anc[:, 2:]
pred_bbox = torch.cat((pred_bbox_xy, pred_bbox_wh), axis=1)
predicted_bbox = d2l.box_center_to_corner(pred_bbox)
return predicted_bboxIn [21]:
def nms(boxes, scores, iou_threshold):
"""对预测边界框的置信度进行排序"""
B = torch.argsort(scores, dim=-1, descending=True)
keep = [] # 保留预测边界框的指标
while B.numel() > 0:
i = B[0]
keep.append(i)
if B.numel() == 1: break
iou = box_iou(boxes[i, :].reshape(-1, 4),
boxes[B[1:], :].reshape(-1, 4)).reshape(-1)
inds = torch.nonzero(iou <= iou_threshold).reshape(-1)
B = B[inds + 1]
return torch.tensor(keep, device=boxes.device)In [22]:
#@save
def multibox_detection(cls_probs, offset_preds, anchors, nms_threshold=0.5,
pos_threshold=0.009999999):
"""使用非极大值抑制来预测边界框"""
device, batch_size = cls_probs.device, cls_probs.shape[0]
anchors = anchors.squeeze(0)
num_classes, num_anchors = cls_probs.shape[1], cls_probs.shape[2]
out = []
for i in range(batch_size):
cls_prob, offset_pred = cls_probs[i], offset_preds[i].reshape(-1, 4)
conf, class_id = torch.max(cls_prob[1:], 0)
predicted_bb = offset_inverse(anchors, offset_pred)
keep = nms(predicted_bb, conf, nms_threshold)
# 找到所有的non_keep索引,并将类设置为背景
all_idx = torch.arange(num_anchors, dtype=torch.long, device=device)
combined = torch.cat((keep, all_idx))
uniques, counts = combined.unique(return_counts=True)
non_keep = uniques[counts == 1]
all_id_sorted = torch.cat((keep, non_keep))
class_id[non_keep] = -1
class_id = class_id[all_id_sorted]
conf, predicted_bb = conf[all_id_sorted], predicted_bb[all_id_sorted]
# pos_threshold是一个用于非背景预测的阈值
below_min_idx = (conf < pos_threshold)
class_id[below_min_idx] = -1
conf[below_min_idx] = 1 - conf[below_min_idx]
pred_info = torch.cat((class_id.unsqueeze(1),
conf.unsqueeze(1),
predicted_bb), dim=1)
out.append(pred_info)
return torch.stack(out)In [23]:
anchors = torch.tensor([[0.1, 0.08, 0.52, 0.92], [0.08, 0.2, 0.56, 0.95],
[0.15, 0.3, 0.62, 0.91], [0.55, 0.2, 0.9, 0.88]])
offset_preds = torch.tensor([0] * anchors.numel())
cls_probs = torch.tensor([[0] * 4, # 背景的预测概率
[0.9, 0.8, 0.7, 0.1], # 狗的预测概率
[0.1, 0.2, 0.3, 0.9]]) # 猫的预测概率In [24]:
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])In [25]:
output = multibox_detection(cls_probs.unsqueeze(dim=0),
offset_preds.unsqueeze(dim=0),
anchors.unsqueeze(dim=0),
nms_threshold=0.5)
outputOut [25]:
tensor([[[ 0.0000, 0.9000, 0.1000, 0.0800, 0.5200, 0.9200],
[ 1.0000, 0.9000, 0.5500, 0.2000, 0.9000, 0.8800],
[-1.0000, 0.8000, 0.0800, 0.2000, 0.5600, 0.9500],
[-1.0000, 0.7000, 0.1500, 0.3000, 0.6200, 0.9100]]])In [26]:
fig = d2l.plt.imshow(img)
for i in output[0].detach().numpy():
if i[0] == -1:
continue
label = ('dog=', 'cat=')[int(i[0])] + str(i[1])
show_bboxes(fig.axes, [torch.tensor(i[2:]) * bbox_scale], label)In [27]:
img = d2l.plt.imread('../Pictures/1.jpg')
h, w = img.shape[:2]
h, wOut [27]:
(640, 640)
In [28]:
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,0.3])
bbox_scale = torch.tensor((w, h, w, h))
d2l.show_bboxes(d2l.plt.imshow(img).axes,
anchors[0] * bbox_scale)
display_anchors(fmap_w=3, fmap_h=3, s=[0.15,0.2,0.3])In [29]:
display_anchors(fmap_w=2, fmap_h=2, s=[0.4])In [30]:
display_anchors(fmap_w=1, fmap_h=1, s=[0.8])In [31]:
import torchvision,os
import pandas as pd
def read_data_bananas(is_train=True):
"""读取香蕉检测数据集中的图像和标签"""
data_dir = d2l.download_extract('banana-detection')
csv_fname = os.path.join(data_dir, 'bananas_train' if is_train
else 'bananas_val', 'label.csv')
csv_data = pd.read_csv(csv_fname)
csv_data = csv_data.set_index('img_name')
images, targets = [], []
for img_name, target in csv_data.iterrows():
images.append(torchvision.io.read_image(
os.path.join(data_dir, 'bananas_train' if is_train else
'bananas_val', 'images', f'{img_name}')))
# 这里的target包含(类别,左上角x,左上角y,右下角x,右下角y),
# 其中所有图像都具有相同的香蕉类(索引为0)
targets.append(list(target))
return images, torch.tensor(targets).unsqueeze(1) / 256In [32]:
class BananasDataset(torch.utils.data.Dataset):
"""一个用于加载香蕉检测数据集的自定义数据集"""
def __init__(self, is_train):
self.features, self.labels = read_data_bananas(is_train)
print('read ' + str(len(self.features)) + (f' training examples' if
is_train else f' validation examples'))
def __getitem__(self, idx):
return (self.features[idx].float(), self.labels[idx])
def __len__(self):
return len(self.features)
def load_data_bananas(batch_size):
"""加载香蕉检测数据集"""
train_iter = torch.utils.data.DataLoader(BananasDataset(is_train=True),
batch_size, shuffle=True)
val_iter = torch.utils.data.DataLoader(BananasDataset(is_train=False),
batch_size)
return train_iter, val_iterIn [33]:
batch_size, edge_size = 32, 256
train_iter, _ = load_data_bananas(batch_size)
batch = next(iter(train_iter))
batch[0].shape, batch[1].shapeOut [33]:
read 1000 training examples read 100 validation examples
(torch.Size([32, 3, 256, 256]), torch.Size([32, 1, 5]))
In [34]:
imgs = (batch[0][0:10].permute(0, 2, 3, 1)) / 255
axes = d2l.show_images(imgs, 2, 5, scale=2)
for ax, label in zip(axes, batch[1][0:10]):
d2l.show_bboxes(ax, [label[0][1:5] * edge_size], colors=['b'])In [35]:
from torch import nn
from torch.nn import functional as F
def cls_predictor(num_inputs,num_anchors,num_classes):
return nn.Conv2d(num_inputs,num_anchors*(num_classes+1), kernel_size=3,padding=1)
# 不改变原来图像的面积,提升通道数,每一个通道对应的是每一种锚框的一种类别的可能性
def bbox_predictor(num_inputs, num_anchors):
return nn.Conv2d(num_inputs, num_anchors * 4, kernel_size=3, padding=1)
# 不改变原来图像的面积,提升通道数,每一个通道对应的是每一种锚框距离正确标记框的偏移量
In [36]:
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
#类别预测输出中的通道数分别为5 × (10 + 1) = 55和3 × (10 + 1) = 33,其中任一输出的形
#状是(批量大小,通道数,高度,宽度)Out [36]:
(torch.Size([2, 55, 20, 20]), torch.Size([2, 33, 10, 10]))
In [37]:
def flatten_pred(pred):
#要先调换维数才能拍平 0 2 3 1 (batch w h channel)
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)
concat_preds([Y1, Y2]).shape
Out [37]:
torch.Size([2, 25300])
In [38]:
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)) #不改变大小 提升通道数哈 传统VGG做法
blk.append(nn.BatchNorm2d(out_channels))
blk.append(nn.ReLU())
in_channels = out_channels
blk.append(nn.MaxPool2d(2)) #降维
return nn.Sequential(*blk)In [39]:
forward(torch.zeros((2, 3, 20, 20)), down_sample_blk(3, 10)).shape
#降维升channelOut [39]:
torch.Size([2, 10, 10, 10])
In [40]:
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()).shapeOut [40]:
torch.Size([2, 64, 32, 32])
In [41]:
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 blkIn [42]:
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)
"""
Y : CNN特征图
anchors : 在当前尺度下生成的锚框
cls_preds :每个锚框的类别
bbox_preds :每个锚框的偏移量
"""Out [42]:
'\nY : CNN特征图\nanchors : 在当前尺度下生成的锚框\ncls_preds :每个锚框的类别\nbbox_preds :每个锚框的偏移量\n'
In [43]:
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]) - 1In [44]:
class TinySSD(nn.Module):
def __init__(self,num_classes,**kwargs):
super(TinySSD,self).__init__(**kwargs)
self.num_classes = num_classes
idx_to_in_channels = [64,128,128,128,128]
for i in range(5):
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]*5
for i in range(5):
X,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_predsIn [45]:
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])
In [46]:
batch_size = 32
train_iter, _ = d2l.load_data_bananas(batch_size)read 1000 training examples read 100 validation examples
In [47]:
device, net = d2l.try_gpu(), TinySSD(num_classes=1)
trainer = torch.optim.SGD(net.parameters(), lr=0.2, weight_decay=5e-4)In [48]:
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 + bboxIn [49]:
# 验证 类别预测用acc 锚框偏移量预测用L1范数
def cls_eval(cls_preds,cls_labels)->float:
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())In [50]:
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 feature , target in train_iter:
# feature -> (batch,3,h,w) target (batch,num,5) 其中5维数据分别为 label h1 w1 h2 w2
timer.start()
trainer.zero_grad()
X,Y=feature.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正确率 分类任务总个数 box偏移量l1范数 框选任务总个数
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}')class err 3.31e-03, bbox mae 3.15e-03
In [51]:
X = torchvision.io.read_image('../data/banana.jpg').unsqueeze(0).float()
img = X.squeeze(0).permute(1,2,0).long()
In [52]:
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)In [53]:
outputOut [53]:
tensor([[ 0.0000, 0.9985, 0.0642, 0.7531, 0.2869, 0.9532],
[ 0.0000, 0.9973, 0.4608, 0.5666, 0.6662, 0.7837],
[ 0.0000, 0.9970, 0.5375, 0.0591, 0.7508, 0.2796],
[ 0.0000, 0.9902, 0.7037, 0.3652, 0.9121, 0.5747],
[ 0.0000, 0.4860, 0.5226, 0.0069, 0.7186, 0.2129],
[ 0.0000, 0.4443, 0.4504, 0.6233, 0.6205, 0.8134],
[ 0.0000, 0.2552, 0.5831, 0.0923, 0.8302, 0.3042],
[ 0.0000, 0.2356, 0.5915, -0.0019, 0.7679, 0.2016],
[ 0.0000, 0.2331, 0.4954, 0.6350, 0.7170, 0.8248],
[ 0.0000, 0.1502, 0.7096, 0.2895, 0.8824, 0.5102],
[ 0.0000, 0.1475, 0.1230, 0.7664, 0.3295, 0.9989],
[ 0.0000, 0.1150, 0.5567, -0.0957, 0.7636, 0.1090],
[ 0.0000, 0.1137, 0.1259, 0.6939, 0.3409, 0.9125],
[ 0.0000, 0.1115, 0.4028, 0.6006, 0.5825, 0.7848],
[ 0.0000, 0.1093, 0.4543, 0.5068, 0.7193, 0.7340],
[ 0.0000, 0.1016, 0.5291, 0.1110, 0.7004, 0.3227],
[ 0.0000, 0.0900, 0.4455, 0.4652, 0.6391, 0.6875],
[ 0.0000, 0.0780, 0.4589, 0.6589, 0.6681, 0.8913],
[ 0.0000, 0.0652, 0.5598, 0.1803, 0.7430, 0.3703],
[ 0.0000, 0.0605, 0.6292, 0.3817, 0.8251, 0.5817],
[ 0.0000, 0.0514, 0.4952, 0.7167, 0.6964, 0.9586],
[ 0.0000, 0.0469, 0.6734, 0.4222, 0.8798, 0.6158],
[ 0.0000, 0.0416, 0.4532, 0.0716, 0.6357, 0.2691],
[ 0.0000, 0.0391, 0.4798, -0.0191, 0.6770, 0.1747],
[ 0.0000, 0.0389, 0.0044, 0.7313, 0.1998, 0.9244],
[ 0.0000, 0.0389, 0.5262, 0.4644, 0.6896, 0.6895],
[ 0.0000, 0.0374, 0.6148, 0.1649, 0.7902, 0.3841],
[ 0.0000, 0.0359, 0.0650, 0.8109, 0.2880, 1.0482],
[ 0.0000, 0.0359, 0.0698, 0.6747, 0.2262, 0.9140],
[ 0.0000, 0.0319, 0.6276, -0.1184, 0.7822, 0.1588],
[ 0.0000, 0.0319, -0.1519, 0.2342, 1.2012, 0.8651],
[ 0.0000, 0.0285, 0.1491, 0.6391, 0.3408, 0.8407],
[ 0.0000, 0.0282, 0.6711, 0.2857, 0.8190, 0.5322],
[ 0.0000, 0.0281, 0.1795, -0.1376, 0.7880, 1.1077],
[ 0.0000, 0.0271, 0.4184, 0.6820, 0.6104, 0.8625],
[ 0.0000, 0.0266, 0.6531, 0.2491, 0.8567, 0.4469],
[ 0.0000, 0.0215, 0.3868, 0.5555, 0.6020, 0.7265],
[ 0.0000, 0.0164, 0.5367, -0.1162, 0.6877, 0.1575],
[ 0.0000, 0.0160, 0.3315, 0.6025, 0.5351, 0.7996],
[ 0.0000, 0.0156, 0.2120, 0.6672, 0.3861, 0.8899],
[ 0.0000, 0.0154, -0.3605, -0.1560, 0.6862, 0.3646],
[ 0.0000, 0.0149, 0.6149, 0.0149, 0.8310, 0.2521],
[ 0.0000, 0.0141, 0.1019, 0.6182, 0.2584, 0.8786],
[ 0.0000, 0.0131, -0.1225, -0.3201, 0.2735, 0.4565],
[ 0.0000, 0.0130, 0.4430, 0.5082, 1.2768, 1.2750],
[ 0.0000, 0.0125, 0.1837, 0.7573, 0.3872, 0.9679],
[ 0.0000, 0.0123, 0.4169, 0.4787, 0.5627, 0.7412],
[ 0.0000, 0.0122, 0.7223, 0.2193, 0.9493, 0.4536],
[ 0.0000, 0.0121, 0.4850, 0.4219, 0.7411, 0.6612],
[ 0.0000, 0.0112, 0.6911, 0.4813, 0.9275, 0.7128],
[ 0.0000, 0.0105, 0.5652, 0.7101, 0.7331, 0.9001],
[ 0.0000, 0.0102, 0.0054, 0.8066, 0.2270, 0.9863],
[ 0.0000, 0.0101, 0.6626, 0.0780, 0.8909, 0.3222]],
device='cuda:0', grad_fn=<IndexBackward0>)In [54]:
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:
continue
h, 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)tensor([0.0000, 0.9985, 0.0642, 0.7531, 0.2869, 0.9532],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.9973, 0.4608, 0.5666, 0.6662, 0.7837],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.9970, 0.5375, 0.0591, 0.7508, 0.2796],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.9902, 0.7037, 0.3652, 0.9121, 0.5747],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.4860, 0.5226, 0.0069, 0.7186, 0.2129],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.4443, 0.4504, 0.6233, 0.6205, 0.8134],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.2552, 0.5831, 0.0923, 0.8302, 0.3042],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.2356, 0.5915, -0.0019, 0.7679, 0.2016],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.2331, 0.4954, 0.6350, 0.7170, 0.8248],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.1502, 0.7096, 0.2895, 0.8824, 0.5102],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.1475, 0.1230, 0.7664, 0.3295, 0.9989],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.1150, 0.5567, -0.0957, 0.7636, 0.1090],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.1137, 0.1259, 0.6939, 0.3409, 0.9125],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.1115, 0.4028, 0.6006, 0.5825, 0.7848],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.1093, 0.4543, 0.5068, 0.7193, 0.7340],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.1016, 0.5291, 0.1110, 0.7004, 0.3227],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0900, 0.4455, 0.4652, 0.6391, 0.6875],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0780, 0.4589, 0.6589, 0.6681, 0.8913],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0652, 0.5598, 0.1803, 0.7430, 0.3703],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0605, 0.6292, 0.3817, 0.8251, 0.5817],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0514, 0.4952, 0.7167, 0.6964, 0.9586],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0469, 0.6734, 0.4222, 0.8798, 0.6158],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0416, 0.4532, 0.0716, 0.6357, 0.2691],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.0391, 0.4798, -0.0191, 0.6770, 0.1747],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0389, 0.0044, 0.7313, 0.1998, 0.9244],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0389, 0.5262, 0.4644, 0.6896, 0.6895],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0374, 0.6148, 0.1649, 0.7902, 0.3841],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0359, 0.0650, 0.8109, 0.2880, 1.0482],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0359, 0.0698, 0.6747, 0.2262, 0.9140],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.0319, 0.6276, -0.1184, 0.7822, 0.1588],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.0319, -0.1519, 0.2342, 1.2012, 0.8651],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0285, 0.1491, 0.6391, 0.3408, 0.8407],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0282, 0.6711, 0.2857, 0.8190, 0.5322],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.0281, 0.1795, -0.1376, 0.7880, 1.1077],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0271, 0.4184, 0.6820, 0.6104, 0.8625],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0266, 0.6531, 0.2491, 0.8567, 0.4469],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0215, 0.3868, 0.5555, 0.6020, 0.7265],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.0164, 0.5367, -0.1162, 0.6877, 0.1575],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0160, 0.3315, 0.6025, 0.5351, 0.7996],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0156, 0.2120, 0.6672, 0.3861, 0.8899],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.0154, -0.3605, -0.1560, 0.6862, 0.3646],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0149, 0.6149, 0.0149, 0.8310, 0.2521],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0141, 0.1019, 0.6182, 0.2584, 0.8786],
grad_fn=<UnbindBackward0>)
tensor([ 0.0000, 0.0131, -0.1225, -0.3201, 0.2735, 0.4565],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0130, 0.4430, 0.5082, 1.2768, 1.2750],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0125, 0.1837, 0.7573, 0.3872, 0.9679],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0123, 0.4169, 0.4787, 0.5627, 0.7412],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0122, 0.7223, 0.2193, 0.9493, 0.4536],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0121, 0.4850, 0.4219, 0.7411, 0.6612],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0112, 0.6911, 0.4813, 0.9275, 0.7128],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0105, 0.5652, 0.7101, 0.7331, 0.9001],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0102, 0.0054, 0.8066, 0.2270, 0.9863],
grad_fn=<UnbindBackward0>)
tensor([0.0000, 0.0101, 0.6626, 0.0780, 0.8909, 0.3222],
grad_fn=<UnbindBackward0>)
In [54]: