13.7. Phát hiện Nhiều khung Một lượt (SSD)

Ở một số phần trước, chúng tôi đã giới thiệu về khung chứa, khung neo, phát hiện vật thể đa tỷ lệ và tập dữ liệu. Giờ ta sẽ sử dụng phần kiến thức nền tảng này để xây dựng một mô hình phát hiện vật thể: phát hiện nhiều khung trong một lần thực hiện (Single Shot Multibox Detection - SSD) [Liu et al., 2016]. Mô hình này đang được sử dụng rộng rãi nhờ tốc độ và tính đơn giản của nó. Một số khái niệm thiết kế và chi tiết lập trình của mô hình này cũng có thể được áp dụng cho các mô hình phát hiện vật thể khác.

13.7.1. Mô hình

Fig. 13.7.1 mô tả thiết kế của một mô hình SSD. Các thành phần chính của mô hình gồm có một khối mạng cơ sở và vài khối đặc trưng đa tỷ lệ được liên kết thành chuỗi. Trong đó khối mạng cơ sở được sử dụng để trích xuất đặc trưng từ ảnh gốc, thường có dạng một mạng nơ-ron tích chập sâu. Bài báo về SSD dùng mạng VGG-16 cụt đặt trước tầng phân loại [Liu et al., 2016], tuy nhiên bây giờ nó thường được thay bằng ResNet. Ta có thể thiết kế mạng cơ sở để đầu ra có chiều cao và chiều rộng lớn hơn. Bằng cách này, ánh xạ đặc trưng này sẽ sinh ra nhiều khung neo hơn, cho phép ta phát hiện các vật thể nhỏ hơn. Tiếp theo, mỗi khối đặc trưng đa tỷ lệ sẽ giảm chiều cao và chiều rộng của ánh xạ đặc trưng ở tầng trước (giảm kích thước đi còn một nửa chẳng hạn). Các khối này sau đó sử dụng từng phần tử trong ánh xạ đặc trưng để mở rộng vùng tiếp nhận trên ảnh đầu vào. Bằng cách này, khối đặc trưng đa tỷ lệ càng gần đỉnh mô hình trong Fig. 13.7.1 thì trả về ánh xạ đặc trưng càng nhỏ, và số khung neo được sinh ra bởi ánh xạ đặc trưng đó càng ít. Hơn nữa, khối đặc trưng càng gần đỉnh mô hình thì vùng tiếp nhận của mỗi phần tử trong ánh xạ đặc trưng càng lớn và càng phù hợp để phát hiện những vật thể lớn. Vì SSD sinh ra các tập khung neo với số lượng và kích thước khác nhau dựa trên khối mạng cơ sở và từng khối đặc trưng đa tỷ lệ, rồi sau đó dự đoán hạng mục và độ dời (tức là dự đoán khung chứa) cho các khung neo để phát hiện các vật thể với kích cỡ khác nhau, có thể nói SSD là một mô hình phát hiện vật thể đa tỷ lệ.

../_images/ssd.svg

Fig. 13.7.1 SSD được cấu thành bởi một khối mạng cơ sở và nhiều khối đặc trưng đa tỷ lệ được liên kết thành một chuỗi.

Tiếp theo, ta sẽ mô tả chi tiết lập trình cho các mô-đun trong Fig. 13.7.1. Đầu tiên, ta cần phải thảo luận về cách lập trình chức năng dự đoán hạng mục và khung chứa.

13.7.1.1. Tầng Dự đoán Hạng mục

Đặt số hạng mục của vật thể là \(q\). Trong trường hợp này, số hạng mục của khung neo là \(q+1\), với 0 kí hiệu khung neo chỉ là nền hậu cảnh. Ở một tỷ lệ nhất định, đặt chiều cao và chiều rộng của ánh xạ đặc trưng lần lượt là \(h\)\(w\). Nếu ta sử dụng từng phần tử làm tâm để sinh \(a\) khung neo, ta cần phân loại tổng cộng \(hwa\) khung neo. Nếu ta sử dụng một tầng kết nối đầy đủ (FCN) tại đầu ra thì khả năng cao là số lượng tham số mô hình sẽ quá lớn. Hãy nhớ lại cách ta sử dụng các kênh trong tầng tích chập để đưa ra dự đoán hạng mục trong Section 7.3. SSD sử dụng phương pháp tương tự để giảm độ phức tạp của mô hình.

Cụ thể, tầng dự đoán hạng mục sử dụng một tầng tích chập giữ nguyên chiều cao và chiều rộng của đầu vào. Do đó, tọa độ trong không gian của đầu ra và đầu vào tương quan một-một với nhau dọc theo cả chiều cao và chiều rộng của ánh xạ đặc trưng. Giả sử rằng đầu ra và đầu vào này có cùng tọa độ không gian \((x, y)\), các kênh của ánh xạ đặc trưng đầu ra tại tọa độ \((x, y)\) đại diện cho các dự đoán hạng mục của tất cả các khung neo được sinh ra khi sử dụng tọa độ \((x, y)\) của ánh xạ đặc trưng đầu vào làm trung tâm. Bởi lẽ đó, có tất cả \(a(q+1)\) kênh đầu ra, với các kênh đầu ra được đánh chỉ số theo \(i(q+1) + j\) (\(0 \leq j \leq q\)) biểu diễn dự đoán hạng mục thứ \(j\) cho khung neo thứ \(i\).

Bây giờ, ta định nghĩa một tầng dự đoán hạng mục theo dạng này. Sau khi ta xác định các tham số \(a\)\(q\), tầng này sử dụng một tầng tích chập \(3\times3\) với đệm bằng 1. Chiều cao và chiều rộng của đầu ra và đầu vào của tầng tích chập này không đổi.

%matplotlib inline
from d2l import mxnet as d2l
from mxnet import autograd, gluon, image, init, np, npx
from mxnet.gluon import nn

npx.set_np()

def cls_predictor(num_anchors, num_classes):
    return nn.Conv2D(num_anchors * (num_classes + 1), kernel_size=3,
                     padding=1)

13.7.1.2. Tầng Dự đoán Khung chứa

Thiết kế của tầng dự đoán khung chứa cũng tương tự như tầng dự đoán hạng mục. Điểm khác biệt duy nhất đó là ta cần dự đoán 4 giá trị độ dời cho từng khung neo, thay vì \(q+1\) hạng mục.

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

13.7.1.3. Ghép nối Dự đoán Đa Tỷ lệ

Như đã đề cập, SSD sử dụng các ánh xạ đặc trưng trên nhiều tỷ lệ để sinh các khung neo rồi dự đoán hạng mục và độ dời. Vì kích thước và số lượng các khung neo có tâm tại cùng một phần tử là khác nhau đối với ánh xạ đặc trưng có tỷ lệ khác nhau, các đầu ra dự đoán tại các tỷ lệ khác nhau có thể sẽ có kích thước khác nhau.

Trong ví dụ dưới đây, ta sử dụng cùng một batch dữ liệu để xây dựng ánh xạ đặc trưng Y1Y2 của hai tỷ lệ khác nhau. Trong đó, Y2 có chiều cao và chiều rộng bằng một nửa Y1. Ví dụ khi dự đoán hạng mục, giả sử mỗi phần tử trong ánh xạ đặc trưng Y1Y2 sinh 5 (với Y1) và 3 (với Y2) khung neo tương ứng. Với 10 hạng mục vật thể, số lượng kênh đầu ra của tầng dự đoán hạng mục sẽ là \(5\times(10+1)=55\) hoặc \(3\times(10+1)=33\) tương ứng. Định dạng đầu ra dự đoán là (kích thước batch, số lượng kênh, chiều cao, chiều rộng). Ta thấy, ngoại trừ kích thước batch, kích thước của các chiều còn lại là khác nhau. Do đó, ta phải biến đổi chúng về cùng một định dạng và ghép nối dự đoán đa tỷ lệ để dễ tính toán về sau.

def forward(x, block):
    block.initialize()
    return block(x)

Y1 = forward(np.zeros((2, 8, 20, 20)), cls_predictor(5, 10))
Y2 = forward(np.zeros((2, 16, 10, 10)), cls_predictor(3, 10))
(Y1.shape, Y2.shape)
((2, 55, 20, 20), (2, 33, 10, 10))

Chiều kênh chứa dự đoán cho tất cả các khung neo có cùng tâm. Đầu tiên, ta sẽ chuyển chiều kênh thành chiều cuối cùng. Do kích thước batch là giống nhau với mọi tỷ lệ, ta có thể chuyển đổi kết quả dự đoán thành định dạng 2D (kích thước batch, chiều cao \(\times\) chiều rộng \(\times\) số lượng kênh) để việc ghép nối trên chiều thứ nhất dễ dàng hơn.

def flatten_pred(pred):
    return npx.batch_flatten(pred.transpose(0, 2, 3, 1))

def concat_preds(preds):
    return np.concatenate([flatten_pred(p) for p in preds], axis=1)

Do đó, ta có thể ghép nối kết quả dự đoán cho hai tỷ lệ khác nhau trên cùng một batch dù Y1Y2 có kích thước khác nhau.

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

13.7.1.4. Khối giảm Chiều cao và Chiều rộng

Với bài toán phát hiện vật thể đa tỷ lệ, ta định nghĩa khối down_sample_blk sau đây để giảm 50% chiều cao và chiều rộng. Khối này bao gồm 2 tầng tích chập \(3\times3\) với đệm bằng 1 và tầng gộp cực đại \(2\times2\) cùng sải bước bằng 2 được kết nối tuần tự. Như ta đã biết, tầng tích chập \(3\times3\) với đệm bằng 1 sẽ không thay đổi kích thước của ánh xạ đặc trưng. Tuy nhiên, tầng gộp cực đại tiếp theo sẽ giảm một nửa kích thước của ánh xạ đặc trưng. Do \(1\times 2+(3-1)+(3-1)=6\), mỗi phần tử trong ánh xạ đặc trưng đầu ra sẽ có vùng tiếp nhận với kích thước \(6\times6\) trên ánh xạ đặc trưng đầu vào. Ta có thể thấy, khối giảm mẫu trên chiều cao và chiều rộng mở rộng vùng tiếp nhận của mỗi phần tử trong ánh xạ đặc trưng đầu ra.

def down_sample_blk(num_channels):
    blk = nn.Sequential()
    for _ in range(2):
        blk.add(nn.Conv2D(num_channels, kernel_size=3, padding=1),
                nn.BatchNorm(in_channels=num_channels),
                nn.Activation('relu'))
    blk.add(nn.MaxPool2D(2))
    return blk

Kiểm tra tính toán của lượt truyền xuôi trong khối giảm chiều cao và chiều rộng, ta có thể thấy khối này thay đổi số kênh đầu vào và giảm một nửa chiều cao và chiều rộng.

forward(np.zeros((2, 3, 20, 20)), down_sample_blk(10)).shape
(2, 10, 10, 10)

13.7.1.5. Khối Mạng Cơ sở

Khối mạng cơ sở được sử dụng để trích xuất đặc trưng từ ảnh gốc ban đầu. Để đơn giản hóa phép tính, ta sẽ xây dựng một mạng cơ sở nhỏ, bao gồm các khối giảm chiều cao và chiều rộng được kết nối tuần tự sao cho số lượng kênh tăng gấp đôi sau mỗi bước. Khi ta truyền ảnh đầu vào có kích thước \(256\times256\), khối mạng cơ sở sẽ cho ra ánh xạ đặc trưng có kích thước \(32 \times 32\).

def base_net():
    blk = nn.Sequential()
    for num_filters in [16, 32, 64]:
        blk.add(down_sample_blk(num_filters))
    return blk

forward(np.zeros((2, 3, 256, 256)), base_net()).shape
(2, 64, 32, 32)

13.7.1.6. Mô hình hoàn chỉnh

Mô hình SSD chứa tất cả năm mô-đun. Mỗi mô-đun tạo ra một ánh xạ đặc trưng dùng để sinh các khung neo, dự đoán hạng mục và độ dời của các khung neo đó. Mô-đun đầu tiên là khối mạng cơ sở, các mô-đun từ thứ hai tới thứ tư là các khối giảm chiều cao và chiều rộng, và mô-đun thứ năm là tầng gộp cực đại toàn cục nhằm giảm chiều cao và chiều rộng xuống còn 1. Do đó, mô-đun thứ hai tới thứ năm đều là các khối đặc trưng đa tỷ lệ như mô tả trong Fig. 13.7.1.

def get_blk(i):
    if i == 0:
        blk = base_net()
    elif i == 4:
        blk = nn.GlobalMaxPool2D()
    else:
        blk = down_sample_blk(128)
    return blk

Bây giờ, ta sẽ định nghĩa luợt truyền xuôi cho từng mô-đun. Khác với các mạng nơ-ron tích chập đã mô tả trước đây, mô-đun này không chỉ trả về ánh xạ đặc trưng Y xuất ra từ phép tích chập, mà còn sinh ra từ Y cả các khung neo của tỷ lệ hiện tại cùng với các dự đoán hạng mục và độ dời.

def blk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):
    Y = blk(X)
    anchors = npx.multibox_prior(Y, sizes=size, ratios=ratio)
    cls_preds = cls_predictor(Y)
    bbox_preds = bbox_predictor(Y)
    return (Y, anchors, cls_preds, bbox_preds)

Như đã đề cập trong Fig. 13.7.1, khối đặc trưng đa tỷ lệ càng gần đỉnh, các vật thể được phát hiện và các khung neo được tạo ra càng lớn. Ở đây, trước hết ta chia khoảng từ 0.2 tới 1.05 thành năm phần bằng nhau để xác định các kích thước của các khung neo nhỏ hơn ở các tỷ lệ: 0.2, 0.37, 0.54, v.v. Kế đến, theo \(\sqrt{0.2 \times 0.37} = 0.272\), \(\sqrt{0.37 \times 0.54} = 0.447\), và các công thức tương tự; ta xác định kích thước của các khung neo lớn hơn ở các tỷ lệ khác nhau.

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

Bây giờ, ta có thể định nghĩa mô hình hoàn chỉnh, TinySSD.

class TinySSD(nn.Block):
    def __init__(self, num_classes, **kwargs):
        super(TinySSD, self).__init__(**kwargs)
        self.num_classes = num_classes
        for i in range(5):
            # The assignment statement is self.blk_i = get_blk(i)
            setattr(self, f'blk_{i}', get_blk(i))
            setattr(self, f'cls_{i}', cls_predictor(num_anchors, num_classes))
            setattr(self, f'bbox_{i}', bbox_predictor(num_anchors))

    def forward(self, X):
        anchors, cls_preds, bbox_preds = [None] * 5, [None] * 5, [None] * 5
        for i in range(5):
            # getattr(self, 'blk_%d' % i) accesses self.blk_i
            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}'))
        # In the reshape function, 0 indicates that the batch size remains
        # unchanged
        anchors = np.concatenate(anchors, axis=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

Bây giờ ta thử tạo một mô hình SSD và sử dụng nó để thực hiện lượt truyền xuôi trên minibatch ảnh X có chiều rộng và chiều cao là 256 pixel. Như đã kiểm nghiệm trước đó, mô-đun đầu tiên xuất ánh xạ đặc trưng với kích thước \(32 \times 32\). Bởi vì các mô-đun từ thứ hai tới thứ tư là các khối giảm chiều cao và chiều rộng, còn mô-đun thứ năm là tầng gộp toàn cục, và mỗi phần tử trong ánh xạ đặc trưng này được dùng làm tâm cho bốn khung neo, tổng cộng \((32^2 + 16^2 + 8^2 + 4^2 + 1)\times 4 = 5444\) khung neo được tạo ra cho mỗi ảnh ở năm tỷ lệ đó.

net = TinySSD(num_classes=1)
net.initialize()
X = np.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: (1, 5444, 4)
output class preds: (32, 5444, 2)
output bbox preds: (32, 21776)

13.7.2. Huấn luyện

Ở bước này chúng tôi sẽ giải thích từng bước cách huấn luyện mô hình SSD để phát hiện vật thể.

13.7.2.1. Đọc Dữ liệu và Khởi tạo

Ta đọc tập dữ liệu Pikachu được tạo ở phần trước.

batch_size = 32
train_iter, _ = d2l.load_data_pikachu(batch_size)

Có 1 hạng mục trong tập dữ liệu Pikachu. Sau khi khai báo mô hình và thiết bị, ta khởi tạo các tham số của mô hình và định nghĩa thuật toán tối ưu.

device, net = d2l.try_gpu(), TinySSD(num_classes=1)
net.initialize(init=init.Xavier(), ctx=device)
trainer = gluon.Trainer(net.collect_params(), 'sgd',
                        {'learning_rate': 0.2, 'wd': 5e-4})

13.7.2.2. Định nghĩa Hàm mất mát và Hàm đánh giá

Phát hiện vật thể có hai loại mất mát. Thứ nhất là mất mát khi phân loại hạng mục của khung neo. Đối với mất mát này, ta hoàn toàn có thể sử dụng lại hàm mất mát entropy chéo trong phân loại ảnh. Loại mất mát thứ hai là mất mát của độ dời khung neo dương. Dự đoán độ dời là một bài toán chuẩn hóa. Tuy nhiên, ở đây ta không sử dụng hàm mất mát bình phương như trước. Thay vào đó, ta sử dụng mất mát chuẩn \(L_1\), tức là trị tuyệt đối hiệu của giá trị dự đoán và giá trị nhãn gốc. Biến mặt nạ bbox_masks loại bỏ các khung neo âm và khung neo đệm khỏi phép tính mất mát. Cuối cùng, ta cộng mất mát hạng mục và mất mát độ dời của khung neo để có hàm mất mát cuối cùng cho mô hình.

cls_loss = gluon.loss.SoftmaxCrossEntropyLoss()
bbox_loss = gluon.loss.L1Loss()

def calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):
    cls = cls_loss(cls_preds, cls_labels)
    bbox = bbox_loss(bbox_preds * bbox_masks, bbox_labels * bbox_masks)
    return cls + bbox

Ta có thể sử dụng độ chính xác để đánh giá kết quả phân loại. Do ta sử dụng mất mát chuẩn \(L_1\) khi huấn luyện, ta sẽ sử dụng trung bình sai số tuyệt đối (average absolute error) để đánh giá kết quả dự đoán khung chứa.

def cls_eval(cls_preds, cls_labels):
    # Because the category prediction results are placed in the final
    # dimension, argmax must specify this dimension
    return float((cls_preds.argmax(axis=-1).astype(
        cls_labels.dtype) == cls_labels).sum())

def bbox_eval(bbox_preds, bbox_labels, bbox_masks):
    return float((np.abs((bbox_labels - bbox_preds) * bbox_masks)).sum())

13.7.2.3. Huấn luyện Mô hình

Trong suốt quá trình huấn luyện, ta phải tạo ra các khung neo đa tỷ lệ (anchors) khi tính toán lượt truyền xuôi rồi dự đoán hạng mục (cls_preds) và độ dời (bbox_preds) cho mỗi khung neo. Sau đó, ta gán nhãn hạng mục (cls_labels) và độ dời (bbox_labels) cho từng khung neo được tạo ở trên dựa vào thông tin nhãn Y. Cuối cùng, ta tính toán hàm mất mát sử dụng giá trị hạng mục/độ dời nhãn gốc và dự đoán. Để đơn giản hóa mã nguồn, ta sẽ không đánh giá tập huấn luyện ở đây.

num_epochs, timer = 20, d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                        legend=['class error', 'bbox mae'])
for epoch in range(num_epochs):
    # accuracy_sum, mae_sum, num_examples, num_labels
    metric = d2l.Accumulator(4)
    train_iter.reset()  # Read data from the start.
    for batch in train_iter:
        timer.start()
        X = batch.data[0].as_in_ctx(device)
        Y = batch.label[0].as_in_ctx(device)
        with autograd.record():
            # Generate multiscale anchor boxes and predict the category and
            # offset of each
            anchors, cls_preds, bbox_preds = net(X)
            # Label the category and offset of each anchor box
            bbox_labels, bbox_masks, cls_labels = npx.multibox_target(
                anchors, Y, cls_preds.transpose(0, 2, 1))
            # Calculate the loss function using the predicted and labeled
            # category and offset values
            l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,
                          bbox_masks)
        l.backward()
        trainer.step(batch_size)
        metric.add(cls_eval(cls_preds, cls_labels), cls_labels.size,
                   bbox_eval(bbox_preds, bbox_labels, bbox_masks),
                   bbox_labels.size)
    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'{train_iter.num_image/timer.stop():.1f} examples/sec on '
      f'{str(device)}')
class err 2.35e-03, bbox mae 2.61e-03
5949.3 examples/sec on gpu(0)
../_images/output_ssd_vn_5d7329_35_1.svg

13.7.3. Dự đoán

Trong bước dự đoán, ta muốn phát hiện tất cả các vật thể đáng quan tâm trong ảnh. Ở đoạn mã dưới, ta đọc và biến đổi kích thước của ảnh kiểm tra, rồi chuyển thành dạng tensor bốn chiều mà tầng tích chập yêu cầu.

img = image.imread('../img/pikachu.jpg')
feature = image.imresize(img, 256, 256).astype('float32')
X = np.expand_dims(feature.transpose(2, 0, 1), axis=0)

Ta sử dụng hàm MultiBoxDetection để dự đoán các khung chứa dựa theo các khung neo và giá trị độ dời dự đoán của chúng. Sau đó ta sử dụng triệt phi cực đại (non-maximum suppression) để loại bỏ các khung chứa giống nhau.

def predict(X):
    anchors, cls_preds, bbox_preds = net(X.as_in_ctx(device))
    cls_probs = npx.softmax(cls_preds).transpose(0, 2, 1)
    output = npx.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)

Cuối cùng, ta lấy toàn bộ khung chứa có độ tin cậy tối thiểu là 0.3 và hiển thị chúng làm kết quả cuối cùng.

def display(img, output, threshold):
    d2l.set_figsize((5, 5))
    fig = d2l.plt.imshow(img.asnumpy())
    for row in output:
        score = float(row[1])
        if score < threshold:
            continue
        h, w = img.shape[0:2]
        bbox = [row[2:6] * np.array((w, h, w, h), ctx=row.ctx)]
        d2l.show_bboxes(fig.axes, bbox, '%.2f' % score, 'w')

display(img, output, threshold=0.3)
../_images/output_ssd_vn_5d7329_41_0.svg

13.7.4. Tóm tắt

  • SSD là một mô hình phát hiện vật thể đa tỷ lệ. Mô hình này sinh ra các tập khung neo với số lượng và kích thước khác nhau dựa trên khối mạng cơ sở và từng khối đặc trưng đa tỷ lệ, rồi dự đoán hạng mục và độ dời cho các khung neo để phát hiện các vật thể có kích thước khác nhau.
  • Trong suốt quá trình huấn luyện, hàm mất mát được tính bằng giá trị dự đoán và nhãn gốc của hạng mục và độ dời.

13.7.5. Bài tập

Do nhiều giới hạn, chúng tôi đã bỏ qua một số chi tiết phần lập trình cho mô hình SSD trong thí nghiệm này. Liệu bạn có thể cải thiện mô hình hơn nữa theo các hướng sau?

13.7.5.1. Hàm mất mát

A. Để dự đoán độ dời, thay thế mất mát chuẩn \(L_1\) bằng mất mát điều chuẩn \(L_1\). Hàm mất mát này sử dụng hàm bình phương xung quanh giá trị không để tăng độ mượt. Đây chính là vùng được điều chuẩn và được xác định bởi siêu tham số \(\sigma\):

(13.7.1)\[\begin{split}f(x) = \begin{cases} (\sigma x)^2/2,& \text{nếu }|x| < 1/\sigma^2\\ |x|-0.5/\sigma^2,& \text{mặt~khác} \end{cases}\end{split}\]

Khi \(\sigma\) lớn, mất mát này tương đương với mất mát chuẩn \(L_1\). Khi giá trị này nhỏ, hàm mất mát sẽ mượt hơn.

sigmas = [10, 1, 0.5]
lines = ['-', '--', '-.']
x = np.arange(-2, 2, 0.1)
d2l.set_figsize()

for l, s in zip(lines, sigmas):
    y = npx.smooth_l1(x, scalar=s)
    d2l.plt.plot(x.asnumpy(), y.asnumpy(), l, label='sigma=%.1f' % s)
d2l.plt.legend();
../_images/output_ssd_vn_5d7329_43_0.svg

Trong thí nghiệm ở phần này, ta sử dụng hàm mất mát entropy chéo để dự đoán hạng mục. Còn giờ, giả sử rằng xác suất dự đoán được đúng hạng mục \(j\)\(p_j\) và mất mát entropy chéo là \(-\log p_j\). Ta cũng có thể sử dụng mất mát tiêu điểm (focal loss) [Lin et al., 2017]. Cho siêu tham số \(\gamma\) and \(\alpha\) dương, mất mát này được định nghĩa như sau:

(13.7.2)\[- \alpha (1-p_j)^{\gamma} \log p_j.\]

Như bạn có thể thấy, bằng cách tăng \(\gamma\), ta thực chất có thể giảm giá trị mất mát đi khi khả năng dự đoán đúng hạng mục là lớn.

def focal_loss(gamma, x):
    return -(1 - x) ** gamma * np.log(x)

x = np.arange(0.01, 1, 0.01)
for l, gamma in zip(lines, [0, 1, 5]):
    y = d2l.plt.plot(x.asnumpy(), focal_loss(gamma, x).asnumpy(), l,
                     label='gamma=%.1f' % gamma)
d2l.plt.legend();
../_images/output_ssd_vn_5d7329_45_0.svg

13.7.5.2. Huấn luyện và Dự đoán

B. Khi một vật thể có kích thước khá lớn so với ảnh, mô hình thường chấp nhận kích thước ảnh đầu vào lớn hơn.

C. Điều này thường sản sinh lượng lớn các khung neo âm khi gán nhãn hạng mục cho khung neo. Ta có thể lấy mẫu các khung neo âm để cân bằng các hạng mục trong dữ liệu tốt hơn. Để thực hiện điều này, ta có thể đặt tham số negative_mining_ratio của hàm MultiBoxTarget.

D. Trong hàm mất mát, sử dụng các trọng số khác nhau cho mất mát hạng mục của các khung neo và mất mát độ dời của các khung neo dương.

E. Tham khảo bài báo SSD. Phương pháp nào có thể được sử dụng để đánh giá giá trị precision của các mô hình phát hiện vật thể [Liu et al., 2016]?

13.7.6. Thảo luận

13.7.7. Những người thực hiện

Bản dịch trong trang này được thực hiện bởi:

  • Đoàn Võ Duy Thanh
  • Lê Khắc Hồng Phúc
  • Đỗ Trường Giang
  • Phạm Hồng Vinh
  • Nguyễn Văn Quang
  • Nguyễn Mai Hoàng Long
  • Lê Khắc Hồng Phúc
  • Nguyễn Văn Cường
  • Nguyễn Lê Quang Nhật
  • Phạm Minh Đức