13.4. Khung neo

Các giải thuật phát hiện vật thể thường lấy mẫu ở rất nhiều vùng của ảnh đầu vào, rồi xác định xem các vùng đó có chứa đối tượng cần quan tâm hay không, và điều chỉnh biên của vùng lấy mẫu này để dự đoán khung chứa nhãn gốc của đối tượng một cách chính xác hơn. Các mô hình khác nhau có thể dùng các phương pháp lấy mẫu vùng ảnh khác nhau. Ở đây, chúng tôi sẽ giới thiệu một phương pháp đó là: tạo ra nhiều khung chứa với kích thước và tỷ lệ cạnh khác nhau với tâm trên từng điểm ảnh. Các khung chứa đó được gọi là các khung neo. Chúng ta sẽ thực hành phát hiện vật thể dựa trên các khung neo ở các phần sau.

Trước tiên, hãy nhập các gói và mô-đun cần thiết cho mục này. Tại đây, ta đã chỉnh sửa độ chính xác khi in số thực của Numpy. Vì ta đang gọi hàm in của Numpy khi in các tensor, nên các tensor số thực dấu phẩy động sẽ được in ra dưới dạng súc tích hơn.

%matplotlib inline
from d2l import mxnet as d2l
from mxnet import gluon, image, np, npx

np.set_printoptions(2)
npx.set_np()

13.4.1. Sinh nhiều Khung neo

Giả sử ảnh đầu vào có chiều cao \(h\) và chiều rộng \(w\). Ta sinh ra các khung neo với kích thước khác nhau có tâm tại mỗi điểm ảnh. Giả sử kích thước này \(s\in (0, 1]\), tỷ lệ cạnh là \(r >0\), chiều rộng và chiều cao của khung neo lần lượt là \(ws\sqrt{r}\)\(hs/\sqrt{r}\). Với một vị trí tâm cho trước, ta xác định được khung neo với chiều cao và chiều rộng như trên.

Dưới đây, ta thiết lập một tập kích thước \(s_1,\ldots, s_n\) và một tập tỷ lệ khung \(r_1,\ldots, r_m\). Nếu ta dùng tổ hợp tất cả các kích thước và tỷ lệ khung với mỗi điểm ảnh làm một tâm, ảnh đầu vào sẽ có tổng cộng \(whnm\) khung neo. Mặc dù các khung chứa chuẩn đối tượng có thể sẽ nằm trong số đó, nhưng độ phức tạp tính toán này thường quá cao. Do đó, ta thường chỉ chú ý tới tổ hợp chứa \(s_1\) kích thước hoặc \(r_1\) tỷ lệ khung như sau:

(13.4.1)\[(s_1, r_1), (s_1, r_2), \ldots, (s_1, r_m), (s_2, r_1), (s_3, r_1), \ldots, (s_n, r_1).\]

Ở trên, số khung neo có tâm trên cùng một điểm ảnh là \(n+m-1\). Đối với toàn bộ bức ảnh đầu vào, ta sẽ sinh ra tổng cộng \(wh(n+m-1)\) khung neo.

Phương pháp sinh khung neo ở trên được lập trình sẵn trong hàm multibox_prior. Ta chỉ cần thiết lập đầu vào, tập các kích thước và tập các tỉ số cạnh, rồi hàm này sẽ trả về tất cả các khung neo như mong muốn.

img = image.imread('../img/catdog.jpg').asnumpy()
h, w = img.shape[0:2]

print(h, w)
X = np.random.uniform(size=(1, 3, h, w))  # Construct input data
Y = npx.multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape
561 728
(1, 2042040, 4)

Ta có thể thấy rằng kích thước của khung neo được trả về ở biến y là (kích thước batch, số khung neo, 4). Sau khi thay đổi kích thước của y thành (chiều cao ảnh, chiều rộng ảnh, số khung neo có tâm trên cùng một điểm ảnh, 4), ta sẽ thu được tất cả các khung neo với tâm ở một vị trí điểm ảnh nhất định. Trong phần ví dụ dưới đây, ta truy xuất khung neo đầu tiên có tâm tại vị trí (250, 250). Nó có bốn phần tử: tọa độ trục \(x, y\) ở góc trên bên trái và tọa độ trục \(x, y\) ở góc dưới bên phải của khung neo. Tọa độ của các trục \(x\)\(y\) được chia lần lượt cho chiều rộng và độ cao của ảnh, do đó giá trị của chúng sẽ nằm trong khoảng 0 và 1.

boxes = Y.reshape(h, w, 5, 4)
boxes[250, 250, 0, :]
array([0.06, 0.07, 0.63, 0.82])

Để mô tả tất cả các khung neo có tâm trên cùng một điểm của bức ảnh, trước hết ta sẽ định nghĩa hàm show_bboxes để vẽ nhóm khung chứa trên ảnh này.

#@save
def show_bboxes(axes, bboxes, labels=None, colors=None):
    """Show bounding boxes."""
    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.asnumpy(), 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))

Như chúng ta vừa thấy, các giá trị tọa độ của trục \(x\)\(y\) trong biến boxes đã được chia lần lượt cho chiều rộng và chiều cao của ảnh. Khi vẽ ảnh, ta cần khôi phục các giá trị tọa độ gốc của các khung neo và xác định biến bbox_scale. Lúc này, ta có thể vẽ tất cả các khung neo có tâm tại vị trí (250, 250) của bức ảnh này. Như bạn có thể thấy, khung neo màu xanh dương với kích thước 0.75 và tỉ số cạnh 1 sẽ bao quanh khá tốt chú chó trong hình này.

d2l.set_figsize()
bbox_scale = np.array((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'])
../_images/output_anchor_vn_f30d52_9_0.svg

13.4.2. Giao trên Hợp

Chúng ta chỉ mới đề cập rằng khung neo đó bao quanh tốt chú chó trong ảnh. Nếu ta biết khung chứa nhãn gốc của đối tượng, làm thế nào để định lượng được “mức độ tốt” ở đây? Một phương pháp đơn giản là đo điểm tương đồng giữa các khung neo và khung chứa nhãn gốc. Và ta biết rằng hệ số Jaccard có thể đo lường sự tương đồng giữa hai tập dữ liệu. Với hai tập hợp \(\mathcal{A}\)\(\mathcal{B}\), chỉ số Jaccard của chúng là kích thước của phần giao trên kích thước của phần hợp:

(13.4.2)\[J(\mathcal{A},\mathcal{B}) = \frac{\left|\mathcal{A} \cap \mathcal{B}\right|}{\left| \mathcal{A} \cup \mathcal{B}\right|}.\]

Trong thực tế, chúng ta có thể coi vùng điểm ảnh trong khung chứa là một tập hợp các điểm ảnh. Theo cách này, ta có thể đo lường được tính tương đồng của hai khung chứa bằng hệ số Jaccard của các tập điểm ảnh tương ứng. Khi đo sự tương đồng giữa hai khung chứa, hệ số Jaccard thường được gọi là Giao trên Hợp (Intersection over Union - IoU), tức tỷ lệ giữa vùng giao nhau và vùng kết hợp của hai khung chứa ảnh, được thể hiện trong Fig. 13.4.1. Miền giá trị của IoU nằm trong khoảng từ 0 đến 1: giá trị 0 có nghĩa là không có điểm ảnh nào giao nhau giữa hai khung chứa, trong khi đó giá trị 1 chỉ ra rằng hai khung chứa ấy hoàn toàn trùng nhau.

../_images/iou.svg

Fig. 13.4.1 IoU là tỷ lệ giữa vùng giao trên vùng hợp của hai khung chứa.

Trong phần còn lại, chúng ta sẽ dùng IoU để đo sự tương đồng giữa các khung neo với khung chứa nhãn gốc và giữa các khung neo với nhau.

13.4.3. Gán nhãn Khung neo trong tập Huấn luyện

Trong tập huấn luyện, chúng ta xem mỗi khung neo là một mẫu huấn luyện. Để huấn luyện mô hình phát hiện đối tượng, chúng ta cần đánh dấu hai loại nhãn cho mỗi khung neo: thứ nhất là hạng mục (category) của đối tượng trong khung neo, thứ hai là độ dời tương đối (offset) của khung chứa nhãn gốc so với khung neo. Trong việc phát hiện đối tượng, trước tiên ta tạo ra nhiều khung neo, dự đoán các hạng mục và độ dời cho từng khung neo, hiệu chỉnh vị trí của chúng dựa theo độ lệch dự kiến để có được những khung chứa và sau cùng là lọc ra các khung chứa mà cần được dự đoán.

Chúng ta biết rằng, trong tập huấn luyện phát hiện đối tượng, mỗi hình ảnh được gán nhãn với vị trí của khung chứa nhãn gốc và hạng mục của đối tượng. Ta gán nhãn cho các khung neo sau khi tạo chủ yếu dựa vào thông tin vị trí và hạng mục của các khung chứa nhãn gốc tương đồng với các khung neo đó. Vậy làm thế nào để gán các khung chứa nhãn gốc cho những khung neo tương đồng với chúng?

Giả sử rằng các khung neo trên ảnh là \(A_1, A_2, \ldots, A_{n_a}\) và các khung chứa nhãn gốc là \(B_1, B_2, \ldots, B_{n_b}\)\(n_a \geq n_b\). Xây dựng ma trận \(\mathbf{X} \in \mathbb{R}^{n_a \times n_b}\), trong đó mỗi phần tử \(x_{ij}\) trong hàng \(i^\mathrm{th}\) và cột \(j^\mathrm{th}\) là hệ số IoU của khung neo \(A_i\) so với khung chứa nhãn gốc \(B_j\). Đầu tiên, ta tìm ra phần tử lớn nhất trong ma trận \(\mathbf{X}\) rồi lưu lại chỉ mục hàng và cột của phần tử đó là \(i_1,j_1\), rồi gán khung chứa nhãn gốc \(B_{j_1}\) cho khung neo \(A_{i_1}\). Rõ ràng, khung neo \(A_{i_1}\) và khung chứa nhãn gốc \(B_{j_1}\) có độ tương đồng cao nhất trong số tất cả các cặp “khung neo–khung chứa nhãn gốc”. Tiếp theo, loại bỏ các phần tử trong hàng \(i_1\) và cột \(j_1\) trong ma trận \(\mathbf{X}\). Tìm phần tử lớn nhất trong các phần tử còn lại trong ma trận \(\mathbf{X}\) rồi cũng lưu lại chỉ mục hàng và cột của phần tử đó là \(i_2,j_2\). Chúng ta gán khung chứa nhãn gốc \(B_{j_2}\) cho khung neo \(A_{i_2}\) và sau đó loại bỏ mọi phần tử tại hàng \(i_2\) và cột \(j_2\) trong ma trận \(\mathbf{X}\). Như vậy, tại thời điểm này thì các phần tử trong hai hàng và hai cột của ma trận \(\mathbf{X}\) đã bị loại bỏ.

Ta tiến hành việc này cho đến khi các phần tử ở cột \(n_b\) trong ma trận \(\mathbf{X}\) đều bị loại bỏ. Tại thời điểm này, chúng ta đều đã gán \(n_b\) khung chứa nhãn gốc cho \(n_b\) khung neo. Tiếp đến, chỉ việc duyệt qua \(n_a - n_b\) khung neo còn lại. Với khung neo \(A_i\), ta cần tìm ra khung chứa nhãn gốc \(B_j\) sao cho khung chứa ấy có hệ số IoU so với \(A_i\) là lớn nhất trên hàng \(i\) của ma trận \(\mathbf{X}\), và chỉ gán khung chứa nhãn gốc \(B_j\) cho khung neo \(A_i\) khi mà hệ số IoU lớn hơn một ngưỡng cho trước.

Như mô tả ở Fig. 13.4.2 (trái), giả sử giá trị lớn nhất của ma trận \(\mathbf{X}\)\(x_{23}\), ta gán khung chứa nhãn gốc \(B_3\) cho khung neo \(A_2\). Tiếp theo ta loại bỏ tất cả các giá trị ở hàng 2 và cột 3 của ma trận, tìm phần tử lớn nhất \(x_{71}\) của phần ma trận còn lại và gán khung chứa nhãn gốc \(B_1\) cho khung neo \(A_7\). Sau đó, như trong Fig. 13.4.2 (giữa), ta loại bỏ tất cả các giá trị ở hàng 7 và cột 1 của ma trận, tìm phần tử lớn nhất \(x_{54}\) của phần ma trận còn lại và gán khung chứa nhãn gốc \(B_4\) cho khung neo \(A_5\). Cuối cùng, trong :numref:fig_anchor_label (phải), ta loại bỏ tất cả các giá trị ở hàng 5 và cột 4 của ma trận, tìm phần tử lớn nhất \(x_{92}\) của phần ma trận còn lại và gán khung chứa nhãn gốc \(B_2\) cho khung neo \(A_9\). Sau đó ta chỉ cần duyệt các khung neo còn lại \(A_1, A_3, A_4, A_6, A_8\) và dựa vào mức ngưỡng để quyết định có gán khung chứa nhãn gốc cho các khung neo này không.

../_images/anchor-label.svg

Fig. 13.4.2 Gán khung chứa nhãn gốc cho các khung neo.

Giờ ta có thể gán nhãn hạng mục và độ dời cho các khung neo. Nếu khung neo \(A\) được gán khung chứa nhãn gốc \(B\) thì khung neo \(A\) sẽ có cùng hạng mục với \(B\). Độ dời của khung neo \(A\) được đặt dựa theo vị trí tương đối của tọa độ tâm của \(B\)\(A\) cũng như kích thước tương đối của hai khung. Do vị trí và kích thước của các khung trong tập dữ liệu thường khá đa dạng, các vị trí và kích thước tương đối này thường yêu cầu một số phép biến đổi đặc biệt sao cho phân phối của giá trị độ dời trở nên đều và dễ khớp hơn. Giả sử tọa độ tâm của khung neo \(A\) và khung chứa nhãn gốc \(B\) được gán cho nó là \((x_a, y_a), (x_b, y_b)\), chiều rộng của \(A\)\(B\) lần lượt là \(w_a, w_b\), và chiều cao lần lượt là \(h_a, h_b\). Đối với trường hợp này, một kỹ thuật phổ biến là gán nhãn độ dời của \(A\) như sau

(13.4.3)\[\left( \frac{ \frac{x_b - x_a}{w_a} - \mu_x }{\sigma_x}, \frac{ \frac{y_b - y_a}{h_a} - \mu_y }{\sigma_y}, \frac{ \log \frac{w_b}{w_a} - \mu_w }{\sigma_w}, \frac{ \log \frac{h_b}{h_a} - \mu_h }{\sigma_h}\right),\]

Giá trị mặc định của các hằng số là \(\mu_x = \mu_y = \mu_w = \mu_h = 0, \sigma_x=\sigma_y=0.1, và \sigma_w=\sigma_h=0.2\). Nếu một khung neo không được gán cho một khung chứa nhãn gốc, ta chỉ cần gán hạng mục của khung neo này là nền. Các khung neo có hạng mục là nền thường được gọi là khung neo âm, và tất cả các khung neo còn lại được gọi là khung neo dương.

Dưới đây chúng tôi sẽ giải thích chi tiết một ví dụ. Ta định nghĩa các khung chứa nhãn gốc cho con mèo và con chó trong ảnh đã đọc, trong đó phần tử đầu tiên là hạng mục (0 là chó, 1 là mèo) và bốn phần tử còn lại là các tọa độ \(x, y\) của góc trên bên trái và tọa độ \(x, y\) của góc dưới bên phải (dải giá trị nằm trong khoảng từ 0 đến 1). Ở đây ta khởi tạo năm khung neo bằng tọa độ của góc trên bên trái và góc dưới bên phải để gán nhãn, được kí hiệu lần lượt là \(A_0, \ldots, A_4\) (chỉ số trong chương trình bắt đầu từ 0). Đầu tiên, ta vẽ vị trí của các khung neo này và các khung chứa nhãn gốc vào ảnh.

ground_truth = np.array([[0, 0.1, 0.08, 0.52, 0.92],
                         [1, 0.55, 0.2, 0.9, 0.88]])
anchors = np.array([[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']);
../_images/output_anchor_vn_f30d52_11_0.svg

Ta có thể gán hạng mục và độ dời cho các khung neo này bằng cách sử dụng hàm multibox_target. Hàm này đặt hạng mục nền bằng 0 và tăng chỉ số lên 1 với mỗi hạng mục mục tiêu (1 là chó và 2 là mèo). Ta thêm chiều mẫu vào các tensor chứa khung neo và khung chứa nhãn gốc ở ví dụ trên và khởi tạo kết quả dự đoán ngẫu nhiên với kích thước (kích thước batch, số hạng mục tính cả nền, số khung neo) bằng cách sử dụng hàm expand_dims.

labels = npx.multibox_target(np.expand_dims(anchors, axis=0),
                             np.expand_dims(ground_truth, axis=0),
                             np.zeros((1, 3, 5)))

Có ba phần tử trong kết quả trả về, tất cả đều theo định dạng tensor. Phần tử thứ ba là hạng mục được gán nhãn cho khung neo.

labels[2]
array([[0., 1., 2., 0., 2.]])

Ta phân tích các hạng mục được gán nhãn này dựa theo vị trí của khung neo và khung chứa nhãn gốc trong ảnh. Đầu tiên, trong tất cả các cặp “khung neo—khung chứa nhãn gốc”, giá trị IoU của khung neo \(A_4\) đối với khung chứa nhãn gốc mèo là lớn nhất, vậy hạng mục của khung neo \(A_4\) được gán là mèo. Nếu ta không xét khung neo \(A_4\) hoặc khung chứa nhãn gốc mèo, trong các cặp “khung neo—khung chứa nhãn gốc” còn lại, cặp với giá trị IoU lớn nhất là khung neo \(A_1\) và khung chứa nhãn gốc chó, vậy hạng mục của khung neo \(A_1\) được gán là chó. Tiếp theo ta xét ba khung neo còn lại chưa được gán nhãn. Hạng mục của khung chứa nhãn gốc có giá trị IoU lớn nhất với khung neo \(A_0\) là chó, tuy nhiên giá trị IoU này lại nhỏ hơn mức ngưỡng (mặc định là 0.5), do đó khung neo này được gán nhãn là nền; hạng mục của khung chứa nhãn gốc có giá trị IoU lớn nhất với khung neo \(A_2\) là mèo và giá trị IoU này lớn hơn mức ngưỡng, do đó khung neo này được gán nhãn là mèo; hạng mục của khung chứa nhãn gốc có giá trị IoU lớn nhất với khung neo \(A_3\) là mèo, tuy nhiên giá trị IoU này lại nhỏ hơn mức ngưỡng, do đó khung neo này được gán nhãn là nền.

Phần tử thứ hai trong giá trị trả về là một biến mặt nạ (mask variable), với kích thước (kích thước batch, số khung neo nhân bốn). Các phần tử trong biến mặt nạ tương ứng một - một với bốn giá trị độ dời của mỗi khung neo. Do ta không cần quan tâm đến việc nhận diện nền nên độ dời thuộc lớp âm không ảnh hướng đến hàm mục tiêu. Qua phép nhân theo từng phần tử, các giá trị 0 trong biến mặt nạ có thể lọc ra các độ dời thuộc lớp âm trước khi tính hàm mục tiêu.

labels[1]
array([[0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0.,
        1., 1., 1., 1.]])

Phần tử đầu tiên trong giá trị trả về là bốn giá trị độ dời được gán nhãn cho mỗi khung neo, với giá trị độ dời của các khung neo thuộc lớp âm được gán nhãn là 0.

labels[0]
array([[ 0.00e+00,  0.00e+00,  0.00e+00,  0.00e+00,  1.40e+00,  1.00e+01,
         2.59e+00,  7.18e+00, -1.20e+00,  2.69e-01,  1.68e+00, -1.57e+00,
         0.00e+00,  0.00e+00,  0.00e+00,  0.00e+00, -5.71e-01, -1.00e+00,
        -8.94e-07,  6.26e-01]])

13.4.4. Khung chứa khi Dự đoán

Trong giai đoạn dự đoán, đầu tiên ta tạo ra nhiều khung neo cho bức ảnh, sau đó dự đoán hạng mục và độ dời của từng khung neo. Tiếp theo, ta thu được những khung chứa dự đoán dựa trên các khung neo và độ dời dự đoán của chúng. Khi tồn tại nhiều khung neo, nhiều khung chứa dự đoán tương tự nhau có thể được tạo ra cho cùng một mục tiêu. Để đơn giản hóa kết quả, ta có thể loại bỏ những khung chứa dự đoán giống nhau. Một phương pháp thường được sử dụng là triệt phi cực đại (non-maximum suppression - NMS).

Hãy cùng xem cách NMS hoạt động. Đối với khung chứa dự đoán \(B\), mô hình sẽ dự đoán xác suất cho từng hạng mục. Giả sử rằng xác suất dự đoán lớn nhất là \(p\), hạng mục tương ứng với xác suất này sẽ là hạng mục dự đoán của \(B\). Ta gọi \(p\) là độ tin cậy (confidence level) của khung chứa dự đoán \(B\). Trên cùng một bức ảnh, ta sắp xếp các khung chứa dự đoán không phải là nền theo thứ tự giảm dần độ tin cậy, thu được danh sách \(L\). Ta chọn ra khung chứa dự đoán có mức tin cậy cao nhất \(B_1\) từ \(L\) để làm chuẩn so sánh và loại bỏ tất cả khung chứa dự đoán “không chuẩn” có hệ số IoU với khung chứa \(B_1\) lớn hơn một ngưỡng nhất định khỏi danh sách \(L\). Mức ngưỡng này là một siêu tham số được định trước. Tại thời điểm này, \(L\) chỉ còn khung chứa dự đoán có độ tin cậy cao nhất sau khi đã loại bỏ những khung chứa giống nó.

Sau đó, ta chọn tiếp khung chứa dự đoán \(B_2\) có độ tin cậy cao thứ hai trong \(L\) để làm chuẩn so sánh, và loại bỏ tất cả khung chứa dự đoán “không chuẩn” khác có hệ số IoU so với khung chứa \(B_2\) lớn hơn một ngưỡng nhất định khỏi \(L\). Ta sẽ lặp lại quy trình này cho đến khi tất cả khung chứa dự đoán trong \(L\) đã được sử dụng làm chuẩn so sánh. Lúc này, IoU của bất cứ cặp khung chứa dự đoán nào trong \(L\) đều nhỏ hơn ngưỡng cho trước. Cuối cùng, đầu ra sẽ là mọi khung chứa dự đoán còn lại trong \(L\).

Tiếp theo, hãy xem xét một ví dụ chi tiết. Trước tiên, ta tạo bốn khung neo. Để đơn giản hóa vấn đề, ta giả định rằng độ dời dự đoán đều bằng 0, nghĩa là các khung chứa dự đoán đều là các khung neo. Cuối cùng, ta định ra một xác suất dự đoán cho từng lớp.

anchors = np.array([[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 = np.array([0] * anchors.size)
cls_probs = np.array([[0] * 4,  # Predicted probability for background
                      [0.9, 0.8, 0.7, 0.1],  # Predicted probability for dog
                      [0.1, 0.2, 0.3, 0.9]])  # Predicted probability for cat

In các khung chứa dự đoán cùng với độ tin cậy trên ảnh

fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
            ['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])
../_images/output_anchor_vn_f30d52_23_0.svg

Ta dùng hàm multibox_detection để thực hiện triệt phi cực đại và đặt ngưỡng là 0.5. Hàm này tạo thêm chiều mẫu trong tensor đầu vào. Ta có thể thấy kích thước của kết quả trả về là (kích thước batch, số lượng khung neo, 6). 6 phần tử của từng hàng biểu diễn thông tin đầu ra của một khung chứa dự đoán. Phần tử đầu tiên là chỉ số của hạng mục dự đoán, bắt đầu từ 0 (0 là chó, 1 là mèo). Giá trị -1 cho biết đó là nền hoặc khung bị loại bỏ bởi triệt phi cực đại. Phần tử thứ hai chính là độ tin cậy của khung chứa dự đoán. Bốn phần tử còn lại là các tọa độ \(x, y\) của góc trên bên trái và góc dưới bên phải của khung chứa dự đoán (miền giá trị nằm trong khoảng từ 0 đến 1).

output = npx.multibox_detection(
    np.expand_dims(cls_probs, axis=0),
    np.expand_dims(offset_preds, axis=0),
    np.expand_dims(anchors, axis=0),
    nms_threshold=0.5)
output
array([[[ 0.  ,  0.9 ,  0.1 ,  0.08,  0.52,  0.92],
        [ 1.  ,  0.9 ,  0.55,  0.2 ,  0.9 ,  0.88],
        [-1.  ,  0.8 ,  0.08,  0.2 ,  0.56,  0.95],
        [-1.  ,  0.7 ,  0.15,  0.3 ,  0.62,  0.91]]])

Ta loại bỏ các khung chứa dự đoán có giá trị -1 rồi trực quan hóa các kết quả còn được giữ lại sau khi triệt phi cực đại.

fig = d2l.plt.imshow(img)
for i in output[0].asnumpy():
    if i[0] == -1:
        continue
    label = ('dog=', 'cat=')[int(i[0])] + str(i[1])
    show_bboxes(fig.axes, [np.array(i[2:]) * bbox_scale], label)
../_images/output_anchor_vn_f30d52_27_0.svg

Trong thực tế, ta có thể loại bỏ các khung chứa dự đoán có mức độ tin cậy thấp trước khi thực hiện triệt phi cực đại để giảm bớt chi phí tính toán. Ta cũng có thể lọc các đầu ra sau khi triệt phi cực đại, ví dụ, bằng cách chỉ giữ lại những kết quả có độ tin cậy cao để làm đầu ra cuối cùng.

13.4.5. Tóm tắt

  • Chúng ta tạo ra nhiều khung neo với nhiều kích thước và tỷ lệ khác nhau, bao quanh từng điểm ảnh.
  • IoU, còn được gọi là hệ số Jaccard, đo lường độ tương đồng giữa hai khung chứa. Đó là tỷ lệ của phần giao trên phần hợp của hai khung chứa.
  • Trong tập huấn luyện, ta đánh dấu hai loại nhãn cho mỗi khung neo: hạng mục của đối tượng trong khung neo và độ dời của khung chứa chuẩn so với khung neo.
  • Khi dự đoán, ta có thể dùng triệt phi cực đại để loại bỏ các khung chứa dự đoán tương tự nhau, từ đó đơn giản hóa kết quả.

13.4.6. Bài tập

  1. Hãy thay đổi giá trị sizeratios trong hàm multibox_prior và quan sát sự thay đổi của các khung neo được tạo.
  2. Tạo hai khung chứa với giá trị IoU là 0.5 và quan sát sự chồng nhau giữa chúng.
  3. Xác thực kết quả độ dời labels[0] bằng cách đánh dấu các độ dời của khung neo như định nghĩa trong phần này (hằng số là một giá trị mặc định).
  4. Thay đổi biến anchors trong phần “Gán nhãn Khung neo ở tập Huấn luyện” và “Khung chứa khi Dự đoán”. Kết quả thay đổi như thế nào?

13.4.7. Thảo luận

13.4.8. 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
  • Nguyễn Mai Hoàng Long
  • Phạm Minh Đức
  • Phạm Đăng Khoa
  • Đỗ Trường Giang
  • Lê Khắc Hồng Phúc
  • Nguyễn Văn Cường