Cấu trúc của mạng học sâu YOLOv3 được xây dựng trên mạng cơ bản Darknet-53 [16]. YOLOv3 có tổng cộng 106 lớp, 62.573.334 tham số học, riêng phần mạng cơ bản Darknet-53 có 51 lớp tích chập, và 24 lớp kết nối dư (residual) hay shortcut. Mạng Darknet-53 đóng vai trò là bộ phận phân tích ra bản đồ đặc tính trừu tượng của hình ảnh đầu vào trong khi thu nhỏ dần kích thước của ảnh. Có thể nói là qua đó mạng đã phân tách được dữ liệu đặc trưng của vật nhỏ khi hình ảnh kích thước còn lớn và cả của vật to khi đã thu gọn kích thước của ảnh.
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
Tiếp đó, việc dự đoán đối tượng được thực hiện bằng việc sử dụng một loạt các lớp tích chập có kích thước 1x1 và 3x3, cuối cùng là lớp có tích chập kích thước 1x1 với số lượng bộ lọc bằng filter = (B x (5 + C)). Nên đầu ra dự đoán có kích thước bằng với kích thước bản đồ đặc tính trừu tượng trước đó. Nhưng ở đây bản đồ dự đoán được diễn giải ở mỗi ô và chỉ dự đoán với số lượng khung cố định. Sau các lớp dự đoán [yolo] cho tỉ lệ kích thước 13x13 đầu tiên tại lớp 82. Sau đó, trước khi được nhân đôi lên thành kích thước 26x26 thì tại lớp 83 sẽ tạo kết nối lấy đặc tính trừu tượng từ lớp 79 trước lớp [yolo] đầu tiên, sau đó đưa qua lớp 84 để lấy thêm đặc tính đặc trưng. Sau đó bản đồ đặc định đặc trưng này được “cô đặc” (concatenate) với đặc tính trừu tượng ở lớp 61 có output 26 x 26 x 512, như vậy ta có được bản đồ đặc tính trừu tượng ở lớp “sâu hơn” mới được bổ sung thông tin từ các lớp trước đó “nông hơn”.
layer filter Size/stride input output
83 Route 79 ➔ 13x13x512
84 Conv 256 1x1/1 13x13x512 ➔ 13x13x256
85 Upsample 2x 13x13x256 ➔ 26x26x256
86 Route 85, 61 ➔ 26x26x768
Để chuẩn bị cho dự đoán vật thể ở lớp thứ 94, bản đồ đặc tính trừu tượng lại được đưa qua một loạt các lớp tích chập đã được thực hiện như ở lớp 75 đến lớp 81. Sau đó ta lại lấy đặc tính trừu tượng từ lớp 36 lên lớp 91, và thực hiện tương tự như ở trên để thực hiện dự đoán vật thể cuối ở lớp 106 cho kích thước bản đồ đặc tính trừu tượng 52 x 52 x 18.
Điều mà ta mong muốn là với mỗi ô của đặc tính trừu tượng sẽ dự đoán đối tượng thông qua một trong những hộp giới hạn nếu điểm giữa của vật nằm trong vùng của ô đặc tính trừu tượng đó. Để xác định được ô nào ta sẽ chia nhỏ dữ liệu hình ảnh đầu vào bằng với kích thước của bản đồ đặc tính trừu tượng của lớp cuối cùng. Ví dụ ta có ảnh đầu vào hình sau có kích cỡ 416x416 như hình 2.3 ở phía dưới đây. Stride của mạng tích chập là 32, ta sẽ có đầu ra đặc tính trừu tượng có kích thước 13x13. Ta chia dữ liệu ảnh đầu vào thành 13x13 ô.
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
HÌNH 2.3. Ví dụ ảnh dữ liệu đầu vào.
Sau đó, chính ô màu vàng (trong ảnh 2.4) nơi mà chứa điểm chính giữa maud đỏ của hộp giới hạn dán nhãn màu đỏ (trong hình 2.3) của đối tượng sẽ được chọn trở thành ô phụ trách việc dự đoán đối tượng. Tại mỗi ô đặc tính trừu tượng này có thể sử dụng đến ba hộp giới hạn.
HÌNH 2.4. Các thuộc tính của hộp giới hạn dự đoán của YOLO v3.
Hộp1 Hộp2 Hộp3 Bản đồ đặc tính trừu tượng Tọa độ hộp giới hạn Điểm vật thể Độ tự tin Số hộp
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
Đặc tính trừu tượng của mạng có (B x (5 + C)) đầu vào. Trong đó B là số lượng hộp giới hạn mà mỗi ô có thể dự đoán. Mỗi hộp dự đoán có 5 + C đặc tính bao gồm tọa độ lệch so với điểm giữa, kích thước, điểm số đánh giá có phải vật hay không và C là độ tự tin của các lớp đối tượng trong mỗi hộp dự đoán.
Độ lệch của hai hộp dãn nhãn và hộp anchor ở đây chính là độ lệch giữa hai điểm chính giữa của hai hộp theo như hình 2.5 dưới đây. Hộp màu đỏ, tâm màu đỏ là của hộp dán nhãn và hộp màu vàng và tâm màu vàng là của hộp anchor tại ô cùng chứa tâm màu đỏ. Ta sẽ tính được độ sai lệch 𝜎(𝑡𝑥) và 𝜎(𝑡𝑦) của hộp anchor so với hộp dán nhãn đó.
HÌNH 2.5. Vị trí của hộp dán nhãn và hộp anchor.
Kết quả của hộp giới hạn dự đoán được tính theo các công thức bên dưới đây:
𝑏𝑥 = 𝜎(𝑡𝑥) + 𝑐𝑥 (9)
𝑏𝑦 = 𝜎(𝑡𝑦) + 𝑐𝑦 (10)
𝑏𝑤 = 𝑝𝑤𝑒𝑡𝑤 (11)
𝑏ℎ = 𝑝ℎ𝑒𝑡ℎ (12)
Trong đó:
𝑏𝑥, 𝑏𝑦, 𝑏𝑤, 𝑏ℎ lần lượt là tọa độ điểm chính giữa x, y, chiều dài và chiều cao của hộp giới hạn dự đoán,
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
𝑡𝑥, 𝑡𝑦, 𝑡𝑤, 𝑡ℎ là kết quả tọa độ của hộp giới hạn đầu ra của mạng,
𝑐𝑥, 𝑐𝑦 là tọa độ điểm chính giữa của hộp giới hạn gốc (hộp giới hạn đã được dán nhãn),
𝑝𝑤, 𝑝ℎ là kích thước của hộp giới hạn định trước anchor box của hộp.
Mạng YOLOv3 không dự đoán chính xác vị trí tọa độ điểm chính giữa của hộp giới hạn mà chỉ dự đoán độ lệch so với tọa độ của hộp dán nhãn của ô dự đoán đối tượng, và chuẩn hóa kích thước của ô từ bản đồ đặc tính trừu tượng thành giá trị từ 0 đến 1 bằng hàm kích hoạt sigmoid. Ví dụ trong hình 2.5 ở trên, dự đoán điểm chính giữa cho ra kết quả cuối của hộp dự đoán là (0.6, 0.7) tức là trong ảnh đặc tính trừu tượng 13x13 tọa độ của điểm đó sẽ là ở (7.8, 9.1). Kích thước của hộp giới hạn được dự đoán bằng biến đổi logarit kết quả đầu ra của mạng [16], rồi nhân với kích thước của anchor box. Theo như công thức (11) và (12), kết quả hộp giới hạn dự đoán được biến đổi như theo hình dưới đây [17].
HÌNH 2.6. Kết quả hộp anchor được biến đổi thành kết quả dự đoán cuối cùng.
Đối với chiều rộng và chiều cao của hộp giới hạn dự đoán cũng được chuẩn hóa theo kích thước của dự liễu ảnh đầu vào. Nên nếu kết quả dự đoán của (𝑏𝑤, 𝑏ℎ) là (0.3, 0.8) thì thực tế trong ảnh đặc tính trừu tượng kích thước 13x13 hộp giới hạn đó sẽ có chiều rộng và độ cao lần lượt là 3.9 và 10.4.
Mạng YOLOv3 thực hiện dự đoán qua 3 tỷ lệ kích thước ảnh khách nhau. Lớp dự đoán [16] dự đoán theo bản đồ đặc tính trừu tượng tại 3 kích thước ảnh khác nhai có stride lần lượt là 32,16 và 8. Điều này có nghĩa là với dữ liệu ảnh đầu vào có kích thước 416x416, mạng sẽ thực hiện dự đoán nhận dạng đối tượng ở 3 tỷ
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
lệ hình ảnh lần lượt là 13x13, 26x26 và 52x52. Quá trình đó được thể hiện trong hình 2.6.
HÌNH 2.7. Mạng YOLOv3 thực hiện nhận dạng dữ liệu hình ảnh trên các tỷ lệ kích thước ảnh khác nhau.
Cuối cùng theo [16] ta có công thức sau để đánh giá độ chính xác trung bình của tọa độ vị trí dự đoán so với gái trị dán nhãn thực của dữ liệu ảnh đầu vào qua hai công thức: Precision được định nghĩa là tỉ lệ số điểm true positive trong số những điểm được phân loại là positive. Recall được định nghĩa là tỉ lệ số điểm true positive trong số những điểm thực sự là positive.
𝑃𝑟𝑒𝑐𝑖𝑠𝑖𝑜𝑛 = 𝑇𝑟𝑢𝑒 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑒 𝑇𝑟𝑢𝑒 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑒𝑠+𝐹𝑎𝑙𝑠𝑒 𝑃𝑜𝑠𝑖𝑡𝑖𝑣𝑒= 𝑐𝑜𝑢𝑛𝑡(𝑇𝑟𝑢𝑒 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑒) 𝑐𝑜𝑢𝑛𝑡(𝑃𝑟𝑒𝑑𝑖𝑐𝑡𝑒𝑑 𝑏𝑏𝑜𝑥𝑒𝑠) (13) 𝑅𝑒𝑐𝑎𝑙𝑙 = 𝑇𝑟𝑢𝑒 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑒 𝑇𝑟𝑢𝑒 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑒𝑠+𝐹𝑎𝑙𝑠𝑒 𝑁𝑒𝑔𝑎𝑡𝑖𝑣𝑒 = 𝑐𝑜𝑢𝑛𝑡(𝑇𝑟𝑢𝑒 𝑝𝑜𝑠𝑖𝑡𝑖𝑣𝑒) 𝑐𝑜𝑢𝑛𝑡(𝐺𝑟𝑜𝑢𝑛𝑑𝑡𝑟𝑢𝑡ℎ 𝑏𝑏𝑜𝑥𝑒𝑠) (14)
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
Trong hình 2.8 thể hiện các ô màu đỏ là các hộp giới hạn dự đoán và các ô màu xanh là hộp giới hạn được dán nhãn. Trong đó True positive khi có giá trị Iou lớn hơn ngưỡng 0.5 và độ tự tin lớn hơn ngưỡng conf_thres. False positive khi IoU dưới ngưỡng nhưng độ tự tin lại lớn hơn ngưỡng, còn False negative là cả hai giá trị IoU và độ tự tin đều nhỏ hơn ngưỡng đặt. Precision cao đồng nghĩa với việc độ chính xác của các ô giới tìm được đối tượng là cao. Recall cao đồng nghĩa với việc True Positive Rate cao, tức tỉ lệ bỏ sót các ô ground-truth là thấp.
2.2.TRIỂN KHAI MẠNG YOLOv3 SỬ DỤNG THƯ VIỆN HỌC SÂU PYTORCH
Vấn đề lớn nhất khi khai triển thuật toán nhận dạng đó chính là làm sao để xử lý và tính toán dữ liệu hình ảnh sử dụng mạng học sâu YOLOv3 bằng cách xử lý song song các gói ảnh bằng bộ xử lý hình ảnh GPU. Đó chính là lý mà em lựa chọn sử dụng công cụ hỗ trợ thư viện tuệ nhân tạo Pytorch. Để triển khai kiến trúc của mạng YOLOv3, theo như ở mã nguồn của tác giả [16], họ sử dụng file cfg để miêu tả các các lớp của mạng theo từng khối. Tất cả có 5 loại lớp được sử dụng trong cấu trúc của YOLOv3.
Lớp tích chập (Convolutional)
Lớp sử dụng tích chập để tìm đặc tính trừu tượng của hình ảnh, hoặc co không gian ảnh khi tham số stride = 2. Filters là số lượng bộ lọc, stride là bước trượt, pad là số lượng ta thêm padding không vào xung quanh viền để đảm bảo kích thước tại đầu ra sau tích chập. activation là hàm kích hoạt, ở đây YOLOv3 sử dụng hàm kích họa Leaky ReLU.
Ví du: [convolutional] batch_normalize=1 filters=32 size=3 stride=1 pad=1 activation=leaky
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
Lớp nối tắt (Shortcut)
Là điểm nối tắt như được sử dụng trong mạng hồi quy Resnet. Tham số from = - n để chỉ đầu ra của lớp nối tắt có được cộng thêm đặc tính trừu tượng của lớp thứ n trước đó tình từ lớp nối tắt.
Ví dụ:
[shortcut]
from=-3
activation=linear
Lớp tăng kích thước (Upsampling)
Lớp này có chức năng tang kích thước lên n lần theo tham số stride, tham số này cho biết khoảng cách của dịch chuyển của ô tích chập ở mỗi bước dịch chuyển qua các pixel trên ảnh.
Ví dụ:
[upsample]
stride=2
Lớp đường dẫn (Route)
Lớp này tham số layer có thể có 1 gái trị hoặc 2 giá trị. Khi layer có một giá trị, nó xuất ra đặc tính trừu tượng của lớp được chỉ định bởi giá trị. Ví dụ, ta có tham số layer = - 4, có nghĩa là lớp này sẽ xuất ra kết quả đặc tính đặc trưng từ lớp thứ 4th nằm ở trước đó, kể từ lớp đường dẫn.
[route]
layers = -4
Khi layer có hai giá trị, nó sẽ trả về giá trị ghép nối từ hai lớp được chỉ định bới giá trị. Ví dụ, layer = -1, 61 sẽ trả về giá trị ghép nối từ lớp trước đó (-1) và từ lớp thứ 61th sau nó.
[route]
layers = -1, 61
Lớp nhận dạng (YOLO)
Đây chính là lớp nhận dạng đối tượng. Tham số anchors miêu tả 9 hộp giới hạn định trước, nhưng chỉ có các hộp được chỉ định bởi tham số mask mới được sử dụng.
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56 [yolo] mask = 3,4,5 anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156, 198, 373,326 classes=80 num=9 jitter=.3 ignore_thresh = .5 truth_thresh = 1 random=1
Khi có file cấu hình mạng rồi ta đưa vào các khối block và lưu chúng như từ điển của các khối chức năng của mạng
def parse_cfg(cfgfile):
file = open(cfgfile, 'r')
lines = file.read().split('\n') #store the lines in a list
lines = [x for x in lines if len(x) > 0] get read of the empty lines
lines = [x for x in lines if x[0] != '#']
lines = [x.rstrip().lstrip() for x in lines]
block = {} blocks = []
for line in lines:
if line[0] == "[": #This marks the start of a new block
if len(block) != 0:
blocks.append(block) block = {}
block["type"] = line[1:-1].rstrip()
else:
key,value = line.split("=")
block[key.rstrip()] = value.lstrip() blocks.append(block)
return blocks
2.2.1.1. Xây dựng mô hình mạng học sâu trong Pytorch
Các lớp của mạng học sâu trong Pytorch hoạt động dưới dạng như các mô- đun. Từ danh sách các khối được miêu tả trong file cfg. Ta triển khai các mô-đun theo hàm create_module, hàm này sẽ trả danh sách các mô-đun nn.ModuleList, bên trong chưa các đối tượng là các khối mô-đun nn.Module.
Khi tạo một lớp tích chập mới, phải xác định kích thước của kernel. Mặc dù chiều cao và chiều rộng của kernel được cung cấp bởi tệp cfg, độ sâu của kernel chính xác là số lượng bộ lọc (hoặc độ sâu của bản đồ đặc tính trừu tượng) có
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
trong lớp trước. Điều này có nghĩa là cần theo dõi số lượng bộ lọc trong lớp mà lớp chập đang được áp dụng.
def create_modules(blocks):
#Captures the information about the input and pre-processing
net_info = blocks[0]
module_list = nn.ModuleList()
#indexing blocks helps with implementing route layers (skip connect ions) index = 0 prev_filters = 3 output_filters = [] for x in blocks: module = nn.Sequential() if (x["type"] == "net"): continue
#If it's a convolutional layer
if (x["type"] == "convolutional"):
#Get the info about the layer
activation = x["activation"]
try:
batch_normalize = int(x["batch_normalize"])
bias = False
except:
batch_normalize = 0
bias = True
filters= int(x["filters"])
padding = int(x["pad"])
kernel_size = int(x["size"])
stride = int(x["stride"])
if padding: pad = (kernel_size - 1) // 2 else: pad = 0
#Add the convolutional layer
conv = nn.Conv2d(prev_filters, filters, kernel_size, stride,
pad, bias = bias)
module.add_module("conv_{0}".format(index), conv)
#Add the Batch Norm Layer
if batch_normalize:
bn = nn.BatchNorm2d(filters)
module.add_module("batch_norm_{0}".format(index), bn)
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
#It is either Linear or a Leaky ReLU for YOLO
if activation == "leaky":
activn = nn.LeakyReLU(0.1, inplace = True)
module.add_module("leaky_{0}".format(index), activn)
Lớp lối tắt / Lớp đường dẫn
#If it is a route layer
elif (x["type"] == "route"):
x["layers"] = x["layers"].split(',')
#Start of a route
start = int(x["layers"][0])
#end, if there exists one.
try:
end = int(x["layers"][1])
except:
end = 0
#Positive anotation
if start > 0:
start = start - index
if end > 0:
end = end - index route = EmptyLayer()
module.add_module("route_{0}".format(index), route)
if end < 0:
filters = output_filters[index + start] + output_filters [index + end]
else:
filters= output_filters[index + start]
#shortcut corresponds to skip connection
elif x["type"] == "shortcut": from_ = int(x["from"]) shortcut = EmptyLayer()
module.add_module("shortcut_{}".format(index), shortcut)
elif x["type"] == "maxpool":
stride = int(x["stride"])
size = int(x["size"])
if stride != 1:
maxpool = nn.MaxPool2d(size, stride)
else:
maxpool = MaxPoolStride1(size)
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
Cuối cùng, là lớp YOLO dùng để nhận dạng đối tượng
#Yolo is the detection layer
elif x["type"] == "yolo":
mask = x["mask"].split(",")
mask = [int(x) for x in mask]
anchors = x["anchors"].split(",")
anchors = [int(a) for a in anchors]
anchors = [(anchors[i], anchors[i+1]) for i in range(0, len(
anchors),2)]
anchors = [anchors[i] for i in mask]
detection = DetectionLayer(anchors)
module.add_module("Detection_{}".format(index), detection)
Ta định nghĩa một lớp mới DetectionLayer để chứa các anchor box được dùng để dự đoán hộp giới hạn.
class DetectionLayer(nn.Module):
def __init__(self, anchors):
super(DetectionLayer, self).__init__()
self.anchors = anchors
def forward(self, x, inp_dim, num_classes, confidence): x = x.data
global CUDA
prediction = x
prediction = predict_transform(prediction, inp_dim, self.anchors
, num_classes, confidence, CUDA)
return prediction
2.2.1.1. Xây dựng đường truyền thẳng của mạng
Đường truyền thẳng của mạng được triển khai bằng cách ghi đè phương thức forward của đối tượng nn.Module. forward đóng vai trò tính toán đầu ra của mạng và biến đổi đầu ra của bản đồ đặc tính đặc trưng của ảnh để có thể xử lý dễ dàng hơn.
Hàm biến đổi đầu ra predict_transform được sử dụng trong forward có chức năng như đã nêu ở trên, được dùng để biến đổi kết quả đầu ra thành định dạng dễ xử lý hơn bằng việc biến đổi bản đồ đặc tính trừu tượng thành tensor hai chiều chứa tọa độ x và y, trong đó mỗi hàng của tensor tương ứng với các thuộc tính của hộp giới hạn bounding box theo thứ tự trong hình dưới đây.
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
HÌNH 2.9. Biến đổi định dạng kết quả đầu ra.
Dưới đây là đoạn mã để thực hiện việc biến đổi
def predict_transform(prediction, inp_dim, anchors, num_classes,
CUDA = True):
batch_size = prediction.size(0)
stride = inp_dim // prediction.size(2)
grid_size = inp_dim // stride
bbox_attrs = 5 + num_classes
num_anchors = len(anchors)
anchors = [(a[0]/stride, a[1]/stride) for a in anchors]
prediction = prediction.view(batch_size, bbox_attrs*num_anchors, gri d_size*grid_size)
prediction = prediction.transpose(1,2).contiguous()
prediction = prediction.view(batch_size, grid_size*grid_size*num_anc hors, bbox_attrs)
Áp dụng hàm kích hoạt sigmoid lên tọa độ tọa độ trung tâm x,y và điểm số tự tin theo như công thức (8), (9):
#Sigmoid centroid X, centroid Y. và object confidencce
prediction[:,:,0] = torch.sigmoid(prediction[:,:,0])
prediction[:,:,1] = torch.sigmoid(prediction[:,:,1])
Thêm phần bù vào dự đoán tọa độ trung tâm
#Tính offset
SVTH: Nguyễn Nhật Anh Lớp: KTDT & THCN K56
a,b = np.meshgrid(grid_len, grid_len)
x_offset = torch.FloatTensor(a).view(-1,1)
y_offset = torch.FloatTensor(b).view(-1,1)
if CUDA:
x_offset = x_offset.cuda() y_offset = y_offset.cuda()
x_y_offset = torch.cat((x_offset, y_offset), 1).repeat(1,num_anchors
).view(-1,2).unsqueeze(0)
prediction[:,:,:2] += x_y_offset
Áp dụng anchor box vào kích thước của các hộp giới hạn, tức biến đổi anchor