Files
nn/chapter13.ipynb
T
2026-07-27 17:57:55 +08:00

1.9 MiB
Raw Blame History

前面的13.1 13.2 因为不可抗力事件消失了(保存的时候乱码了)

In [1]:

import torch
from d2l import torch as d2l
In [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 boxes
In [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)) == boxes
Out [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.shape
Out [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_areas
In [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 offset
In [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_bbox
In [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)
output
Out [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, w
Out [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) / 256
In [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_iter
In [33]:
batch_size, edge_size = 32, 256
train_iter, _ = load_data_bananas(batch_size)
batch = next(iter(train_iter))
batch[0].shape, batch[1].shape
Out [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]))

为了方便不同类别的图像进行训练,我们要把张量拍平再cat

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
#降维升channel
Out [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()).shape
Out [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 blk
In [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]) - 1
In [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_preds

应该是生成(32^2 + 16^2 + 8^2 + 4^2 + 1) × 4 = 5444个锚框

In [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 + bbox
In [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]:
output
Out [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]: