.. raw:: html
.. _sec_anchor:
Khung neo
=========
.. raw:: html
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.
.. raw:: html
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.
.. code:: python
%matplotlib inline
from d2l import mxnet as d2l
from mxnet import gluon, image, np, npx
np.set_printoptions(2)
npx.set_np()
.. raw:: html
Sinh nhiều Khung neo
--------------------
.. raw:: html
Giả sử ảnh đầu vào có chiều cao :math:`h` và chiều rộng :math:`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 :math:`s\in (0, 1]`, tỷ lệ cạnh là :math:`r >0`,
chiều rộng và chiều cao của khung neo lần lượt là :math:`ws\sqrt{r}` và
:math:`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.
.. raw:: html
Dưới đây, ta thiết lập một tập kích thước :math:`s_1,\ldots, s_n` và một
tập tỷ lệ khung :math:`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 :math:`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 :math:`s_1` kích
thước hoặc :math:`r_1` tỷ lệ khung như sau:
.. math:: (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).
.. raw:: html
Ở trên, số khung neo có tâm trên cùng một điểm ảnh là :math:`n+m-1`. Đối
với toàn bộ bức ảnh đầu vào, ta sẽ sinh ra tổng cộng :math:`wh(n+m-1)`
khung neo.
.. raw:: html
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.
.. code:: python
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
.. parsed-literal::
:class: output
561 728
.. parsed-literal::
:class: output
(1, 2042040, 4)
.. raw:: html
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 :math:`x, y` ở góc trên bên trái và tọa độ trục :math:`x, y` ở góc
dưới bên phải của khung neo. Tọa độ của các trục :math:`x` và :math:`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.
.. code:: python
boxes = Y.reshape(h, w, 5, 4)
boxes[250, 250, 0, :]
.. parsed-literal::
:class: output
array([0.06, 0.07, 0.63, 0.82])
.. raw:: html
Để 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.
.. code:: python
#@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))
.. raw:: html
Như chúng ta vừa thấy, các giá trị tọa độ của trục :math:`x` và
:math:`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.
.. code:: python
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'])
.. figure:: output_anchor_vn_f30d52_9_0.svg
.. raw:: html
Giao trên Hợp
-------------
.. raw:: html
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 :math:`\mathcal{A}` và :math:`\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:
.. math:: J(\mathcal{A},\mathcal{B}) = \frac{\left|\mathcal{A} \cap \mathcal{B}\right|}{\left| \mathcal{A} \cup \mathcal{B}\right|}.
.. raw:: html
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 :numref:`fig_iou`. 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.
.. raw:: html
.. _fig_iou:
.. figure:: ../img/iou.svg
IoU là tỷ lệ giữa vùng giao trên vùng hợp của hai khung chứa.
.. raw:: html
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.
.. raw:: html
Gán nhãn Khung neo trong tập Huấn luyện
---------------------------------------
.. raw:: html
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.
.. raw:: html
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?
.. raw:: html
Giả sử rằng các khung neo trên ảnh là :math:`A_1, A_2, \ldots, A_{n_a}`
và các khung chứa nhãn gốc là :math:`B_1, B_2, \ldots, B_{n_b}` và
:math:`n_a \geq n_b`. Xây dựng ma trận
:math:`\mathbf{X} \in \mathbb{R}^{n_a \times n_b}`, trong đó mỗi phần tử
:math:`x_{ij}` trong hàng :math:`i^\mathrm{th}` và cột
:math:`j^\mathrm{th}` là hệ số IoU của khung neo :math:`A_i` so với
khung chứa nhãn gốc :math:`B_j`. Đầu tiên, ta tìm ra phần tử lớn nhất
trong ma trận :math:`\mathbf{X}` rồi lưu lại chỉ mục hàng và cột của
phần tử đó là :math:`i_1,j_1`, rồi gán khung chứa nhãn gốc
:math:`B_{j_1}` cho khung neo :math:`A_{i_1}`. Rõ ràng, khung neo
:math:`A_{i_1}` và khung chứa nhãn gốc :math:`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 :math:`i_1` và cột :math:`j_1`
trong ma trận :math:`\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 :math:`\mathbf{X}` rồi cũng lưu lại chỉ mục hàng
và cột của phần tử đó là :math:`i_2,j_2`. Chúng ta gán khung chứa nhãn
gốc :math:`B_{j_2}` cho khung neo :math:`A_{i_2}` và sau đó loại bỏ mọi
phần tử tại hàng :math:`i_2` và cột :math:`j_2` trong ma trận
:math:`\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 :math:`\mathbf{X}` đã bị loại bỏ.
.. raw:: html
Ta tiến hành việc này cho đến khi các phần tử ở cột :math:`n_b` trong ma
trận :math:`\mathbf{X}` đều bị loại bỏ. Tại thời điểm này, chúng ta đều
đã gán :math:`n_b` khung chứa nhãn gốc cho :math:`n_b` khung neo. Tiếp
đến, chỉ việc duyệt qua :math:`n_a - n_b` khung neo còn lại. Với khung
neo :math:`A_i`, ta cần tìm ra khung chứa nhãn gốc :math:`B_j` sao cho
khung chứa ấy có hệ số IoU so với :math:`A_i` là lớn nhất trên hàng
:math:`i` của ma trận :math:`\mathbf{X}`, và chỉ gán khung chứa nhãn gốc
:math:`B_j` cho khung neo :math:`A_i` khi mà hệ số IoU lớn hơn một
ngưỡng cho trước.
.. raw:: html
Như mô tả ở :numref:`fig_anchor_label` (trái), giả sử giá trị lớn nhất
của ma trận :math:`\mathbf{X}` là :math:`x_{23}`, ta gán khung chứa nhãn
gốc :math:`B_3` cho khung neo :math:`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
:math:`x_{71}` của phần ma trận còn lại và gán khung chứa nhãn gốc
:math:`B_1` cho khung neo :math:`A_7`. Sau đó, như trong
:numref:`fig_anchor_label` (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 :math:`x_{54}` của
phần ma trận còn lại và gán khung chứa nhãn gốc :math:`B_4` cho khung
neo :math:`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 :math:`x_{92}` của phần ma trận còn lại và gán khung chứa nhãn
gốc :math:`B_2` cho khung neo :math:`A_9`. Sau đó ta chỉ cần duyệt các
khung neo còn lại :math:`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.
.. raw:: html
.. _fig_anchor_label:
.. figure:: ../img/anchor-label.svg
Gán khung chứa nhãn gốc cho các khung neo.
.. raw:: html
Giờ ta có thể gán nhãn hạng mục và độ dời cho các khung neo. Nếu khung
neo :math:`A` được gán khung chứa nhãn gốc :math:`B` thì khung neo
:math:`A` sẽ có cùng hạng mục với :math:`B`. Độ dời của khung neo
:math:`A` được đặt dựa theo vị trí tương đối của tọa độ tâm của
:math:`B` và :math:`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 :math:`A` và khung chứa nhãn gốc
:math:`B` được gán cho nó là :math:`(x_a, y_a), (x_b, y_b)`, chiều rộng
của :math:`A` và :math:`B` lần lượt là :math:`w_a, w_b`, và chiều cao
lần lượt là :math:`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 :math:`A` như sau
.. math::
\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),
.. raw:: html
Giá trị mặc định của các hằng số là
:math:`\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.
.. raw:: html
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 độ :math:`x, y` của góc trên bên trái và tọa độ :math:`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à :math:`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.
.. code:: python
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']);
.. figure:: output_anchor_vn_f30d52_11_0.svg
.. raw:: html
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``.
.. code:: python
labels = npx.multibox_target(np.expand_dims(anchors, axis=0),
np.expand_dims(ground_truth, axis=0),
np.zeros((1, 3, 5)))
.. raw:: html
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.
.. code:: python
labels[2]
.. parsed-literal::
:class: output
array([[0., 1., 2., 0., 2.]])
.. raw:: html
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 :math:`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
:math:`A_4` được gán là mèo. Nếu ta không xét khung neo :math:`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 :math:`A_1` và khung
chứa nhãn gốc chó, vậy hạng mục của khung neo :math:`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
:math:`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
:math:`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 :math:`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.
.. raw:: html
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.
.. code:: python
labels[1]
.. parsed-literal::
:class: output
array([[0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0.,
1., 1., 1., 1.]])
.. raw:: html
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.
.. code:: python
labels[0]
.. parsed-literal::
:class: output
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]])
.. raw:: html
Khung chứa khi Dự đoán
----------------------
.. raw:: html
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*).
.. raw:: html
Hãy cùng xem cách NMS hoạt động. Đối với khung chứa dự đoán :math:`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à :math:`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 :math:`B`. Ta gọi :math:`p` là độ tin cậy
(*confidence level*) của khung chứa dự đoán :math:`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 :math:`L`. Ta chọn ra khung chứa
dự đoán có mức tin cậy cao nhất :math:`B_1` từ :math:`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 :math:`B_1` lớn hơn một ngưỡng nhất định khỏi danh sách
:math:`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, :math:`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ó.
.. raw:: html
Sau đó, ta chọn tiếp khung chứa dự đoán :math:`B_2` có độ tin cậy cao
thứ hai trong :math:`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
:math:`B_2` lớn hơn một ngưỡng nhất định khỏi :math:`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 :math:`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 :math:`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 :math:`L`.
.. raw:: html
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.
.. code:: python
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
.. raw:: html
In các khung chứa dự đoán cùng với độ tin cậy trên ảnh
.. code:: python
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])
.. figure:: output_anchor_vn_f30d52_23_0.svg
.. raw:: html
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 độ
:math:`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).
.. code:: python
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
.. parsed-literal::
:class: 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]]])
.. raw:: html
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.
.. code:: python
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)
.. figure:: output_anchor_vn_f30d52_27_0.svg
.. raw:: html
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.
Tóm tắt
-------
.. raw:: html
- 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ả.
Bài tập
-------
.. raw:: html
1. Hãy thay đổi giá trị ``size`` và ``ratios`` 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?
Thảo luận
---------
- `Tiếng Anh - MXNet `__
- `Tiếng Việt `__
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