В главе 4 мы построили модель обнаружения медицинских масок, используя RetinaNet, модель одноэтапного детектора. В этой главе мы попытаемся обнаружить объекты с помощью Faster R-CNN, двухэтапного детектора.
В разделах с 5.1 по 5.3 мы определим класс набора данных после загрузки данных на основе содержимого, подтвержденного в главах 2 и 3, и разделив их на обучающие и тестовые данные. В разделе 5.4 мы будем использовать API torchvision для загрузки предварительно обученной модели. В разделе 5.5 мы обучим модель с помощью трансферного обучения, а затем проверим расчет прогнозируемого значения и производительность модели в разделе 5.6.
Проверяем достаточно ли памяти на нашем устройстве
import torch if torch.cuda.is_available(): device = torch.device("cuda") print('There are %d GPU(s) available.' % torch.cuda.device_count()) print('We will use the GPU:', torch.cuda.get_device_name(0)) else: print('No GPU available, using the CPU instead.') device = torch.device("cpu")
There are 1 GPU(s) available. We will use the GPU: Tesla T4
5.1 Импорт данных
В упражнении по моделированию мы будем использовать код из раздела 2.1 для загрузки данных.
5.2 Разделение данных
Давайте разделим набор данных, как в разделе 3.3. И также загрузим пакеты, необходимые для моделирования. Torchvision используется для обработки изображений и имеет встроенные пакеты для наборов данных и пакеты для моделей.
import os import numpy as np import matplotlib.patches as patches import matplotlib.pyplot as plt from bs4 import BeautifulSoup from PIL import Image import torchvision from torchvision import transforms, datasets, models from torchvision.models.detection.faster_rcnn import FastRCNNPredictor import time
5.3 Определение класса набора данных
Определяем функции для ограничивающей рамки, как в разделе 2.3.
def generate_box(obj): xmin = float(obj.find('xmin').text) ymin = float(obj.find('ymin').text) xmax = float(obj.find('xmax').text) ymax = float(obj.find('ymax').text) return [xmin, ymin, xmax, ymax] adjust_label = 1 def generate_label(obj): if obj.find('name').text == "with_mask": return 1 + adjust_label elif obj.find('name').text == "mask_weared_incorrect": return 2 + adjust_label return 0 + adjust_label def generate_target(file): with open(file) as f: data = f.read() soup = BeautifulSoup(data, "html.parser") objects = soup.find_all("object") num_objs = len(objects) boxes = [] labels = [] for i in objects: boxes.append(generate_box(i)) labels.append(generate_label(i)) boxes = torch.as_tensor(boxes, dtype=torch.float32) labels = torch.as_tensor(labels, dtype=torch.int64) target = {} target["boxes"] = boxes target["labels"] = labels return target def plot_image_from_output(img, annotation): img = img.cpu().permute(1,2,0) fig,ax = plt.subplots(1) ax.imshow(img) for idx in range(len(annotation["boxes"])): xmin, ymin, xmax, ymax = annotation["boxes"][idx] if annotation['labels'][idx] == 1 : rect = patches.Rectangle((xmin,ymin),(xmax-xmin),(ymax-ymin),linewidth=1,edgecolor='r',facecolor='none') elif annotation['labels'][idx] == 2 : rect = patches.Rectangle((xmin,ymin),(xmax-xmin),(ymax-ymin),linewidth=1,edgecolor='g',facecolor='none') else : rect = patches.Rectangle((xmin,ymin),(xmax-xmin),(ymax-ymin),linewidth=1,edgecolor='orange',facecolor='none') ax.add_patch(rect) plt.show()
Также определяем класс набора данных и загрузчик данных, как в разделе 4.3. Набор данных будет загружен с помощью функции torch.utils.data.DataLoader с размером пакета 4. Вы можете свободно установить размер пакета в соответствии с объемом вашей личной памяти.
class MaskDataset(object): def __init__(self, transforms, path): ''' path: path to train folder or test folder ''' # transform module과 img path 경로를 정의 self.transforms = transforms self.path = path self.imgs = list(sorted(os.listdir(self.path))) def __getitem__(self, idx): #special method # load images ad masks file_image = self.imgs[idx] file_label = self.imgs[idx][:-3] + 'xml' img_path = os.path.join(self.path, file_image) if 'test' in self.path: label_path = os.path.join("test_annotations/", file_label) else: label_path = os.path.join("annotations/", file_label) img = Image.open(img_path).convert("RGB") #Generate Label target = generate_target(label_path) if self.transforms is not None: img = self.transforms(img) return img, target def __len__(self): return len(self.imgs) data_transform = transforms.Compose([ # transforms.Compose : list 내의 작업을 연달아 할 수 있게 호출하는 클래스 transforms.ToTensor() # ToTensor : numpy 이미지에서 torch 이미지로 변경 ]) def collate_fn(batch): return tuple(zip(*batch)) dataset = MaskDataset(data_transform, 'images/') test_dataset = MaskDataset(data_transform, 'test_images/') data_loader = torch.utils.data.DataLoader(dataset, batch_size=4, collate_fn=collate_fn) test_data_loader = torch.utils.data.DataLoader(test_dataset, batch_size=2, collate_fn=collate_fn)
5.4 Модель импорта
torchvision.models.detection предоставляет более быстрый API R-CNN (torchvision.models.detection.fasterrcnn_resnet50_fpn) для упрощения внедрения. Она предоставляет модель, предварительно обученную с помощью ResNet50 в наборе данных COCO, и вы можете установить pretrained=True/False.
Когда вы позже вызовете модель, вы можете установить желаемое количество классов в num_classes и использовать модель. Одна вещь, которую следует отметить при использовании Faster R-CNN, — указать число, включая фоновые классы, в num_classes. То есть фоновый класс следует добавить, увеличив количество классов в фактическом наборе данных на один.
def get_model_instance_segmentation(num_classes): model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True) in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) return model
5.5 Перенос обучения
Давайте проведем перенос обучения по распознаванию лицевых масок. Набор данных Face Mask Detection состоит из 3 классов, включая фоновый класс, установите для num_classes значение 4 и загрузите модель.
Если это среда, которая может использовать GPU, она отправляет загруженную модель в GPU, назначая его как устройство.
model = get_model_instance_segmentation(4) device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu') model.to(device)
FasterRCNN( (transform): GeneralizedRCNNTransform( Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) Resize(min_size=(800,), max_size=1333, mode='bilinear') ) (backbone): BackboneWithFPN( (body): IntermediateLayerGetter( (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) (bn1): FrozenBatchNorm2d(64) (relu): ReLU(inplace=True) (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False) (layer1): Sequential( (0): Bottleneck( (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(64) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(64) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(256) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (1): FrozenBatchNorm2d(256) ) ) (1): Bottleneck( (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(64) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(64) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(256) (relu): ReLU(inplace=True) ) (2): Bottleneck( (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(64) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(64) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(256) (relu): ReLU(inplace=True) ) ) (layer2): Sequential( (0): Bottleneck( (conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(128) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(128) (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(512) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): FrozenBatchNorm2d(512) ) ) (1): Bottleneck( (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(128) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(128) (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(512) (relu): ReLU(inplace=True) ) (2): Bottleneck( (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(128) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(128) (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(512) (relu): ReLU(inplace=True) ) (3): Bottleneck( (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(128) (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(128) (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(512) (relu): ReLU(inplace=True) ) ) (layer3): Sequential( (0): Bottleneck( (conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(256) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(256) (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(1024) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): FrozenBatchNorm2d(1024) ) ) (1): Bottleneck( (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(256) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(256) (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(1024) (relu): ReLU(inplace=True) ) (2): Bottleneck( (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(256) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(256) (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(1024) (relu): ReLU(inplace=True) ) (3): Bottleneck( (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(256) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(256) (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(1024) (relu): ReLU(inplace=True) ) (4): Bottleneck( (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(256) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(256) (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(1024) (relu): ReLU(inplace=True) ) (5): Bottleneck( (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(256) (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(256) (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(1024) (relu): ReLU(inplace=True) ) ) (layer4): Sequential( (0): Bottleneck( (conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(512) (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(512) (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(2048) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): FrozenBatchNorm2d(2048) ) ) (1): Bottleneck( (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(512) (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(512) (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(2048) (relu): ReLU(inplace=True) ) (2): Bottleneck( (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): FrozenBatchNorm2d(512) (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): FrozenBatchNorm2d(512) (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): FrozenBatchNorm2d(2048) (relu): ReLU(inplace=True) ) ) ) (fpn): FeaturePyramidNetwork( (inner_blocks): ModuleList( (0): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1)) (1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1)) (2): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1)) (3): Conv2d(2048, 256, kernel_size=(1, 1), stride=(1, 1)) ) (layer_blocks): ModuleList( (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) ) (extra_blocks): LastLevelMaxPool() ) ) (rpn): RegionProposalNetwork( (anchor_generator): AnchorGenerator() (head): RPNHead( (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (cls_logits): Conv2d(256, 3, kernel_size=(1, 1), stride=(1, 1)) (bbox_pred): Conv2d(256, 12, kernel_size=(1, 1), stride=(1, 1)) ) ) (roi_heads): RoIHeads( (box_roi_pool): MultiScaleRoIAlign() (box_head): TwoMLPHead( (fc6): Linear(in_features=12544, out_features=1024, bias=True) (fc7): Linear(in_features=1024, out_features=1024, bias=True) ) (box_predictor): FastRCNNPredictor( (cls_score): Linear(in_features=1024, out_features=4, bias=True) (bbox_pred): Linear(in_features=1024, out_features=16, bias=True) ) ) )
В приведенном выше выводе вы можете увидеть, из каких слоев состоит Fastser R-CNN. В это время вы можете проверить, доступен ли GPU, с помощью torch.cuda.is_available().
torch.cuda.is_available()
True
Теперь, когда модель создана, давайте обучим ее. Давайте установим количество обучения (num_epochs) равным 10 и оптимизируем его с помощью метода SGD. Каждый гиперпараметр можно свободно изменять .
num_epochs = 10 params = [p for p in model.parameters() if p.requires_grad] optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
Теперь давайте учиться. В созданном выше data_loader используется один батч для модели по порядку, а затем выполняется оптимизация через расчет потерь. Вы можете проверить ход обучения через вывод потерь для каждой эпохи.
print('----------------------train start--------------------------') for epoch in range(num_epochs): start = time.time() model.train() i = 0 epoch_loss = 0 for imgs, annotations in data_loader: i += 1 imgs = list(img.to(device) for img in imgs) annotations = [{k: v.to(device) for k, v in t.items()} for t in annotations] loss_dict = model(imgs, annotations) losses = sum(loss for loss in loss_dict.values()) optimizer.zero_grad() losses.backward() optimizer.step() epoch_loss += losses print(f'epoch : {epoch+1}, Loss : {epoch_loss}, time : {time.time() - start}')
----------------------train start-------------------------- epoch : 1, Loss : 77.14759063720703, time : 252.42370867729187 epoch : 2, Loss : 48.91315460205078, time : 263.22984743118286 epoch : 3, Loss : 43.18947982788086, time : 264.4591932296753 epoch : 4, Loss : 36.07373046875, time : 265.2568733692169 epoch : 5, Loss : 31.8864688873291, time : 265.57766008377075 epoch : 6, Loss : 31.76308250427246, time : 265.0076003074646 epoch : 7, Loss : 31.24744415283203, time : 265.16882514953613 epoch : 8, Loss : 29.340274810791016, time : 265.73448038101196 epoch : 9, Loss : 25.922008514404297, time : 267.91367626190186 epoch : 10, Loss : 23.59230613708496, time : 266.9004054069519
Если вы хотите сохранить обученные веса, вы можете сохранить их с помощью torch.save и вызвать их в любое время позже.
torch.save(model.state_dict(),f'model_{num_epochs}.pt')
model.load_state_dict(torch.load(f'model_{num_epochs}.pt'))
<All keys matched successfully>
5.6 Прогноз
После обучения модели давайте проверим результат прогноза, чтобы убедиться, что она хорошо обучена. Результаты прогнозирования включают координаты (прямоугольники) ограничивающей рамки, классы (метки) и оценки (оценки) . Значение достоверности класса хранится в оценках, и мы определим функцию make_prediction для извлечения только тех, которые равны 0,5 или выше в качестве порога. Далее выводятся результаты только для первой партии test_data_loader.
def make_prediction(model, img, threshold): model.eval() preds = model(img) for id in range(len(preds)) : idx_list = [] for idx, score in enumerate(preds[id]['scores']) : if score > threshold : idx_list.append(idx) preds[id]['boxes'] = preds[id]['boxes'][idx_list] preds[id]['labels'] = preds[id]['labels'][idx_list] preds[id]['scores'] = preds[id]['scores'][idx_list] return preds
with torch.no_grad(): # размер пакета тестового набора = 2 for imgs, annotations in test_data_loader: imgs = list(img.to(device) for img in imgs) pred = make_prediction(model, imgs, 0.5) print(pred) break
[{'boxes': tensor([[117.7811, 1.4936, 132.9596, 18.4192], [214.8204, 59.8669, 249.7893, 97.6275]], device='cuda:0'), 'labels': tensor([2, 2], device='cuda:0'), 'scores': tensor([0.9430, 0.9414], device='cuda:0')}, {'boxes': tensor([[218.8598, 99.3362, 260.0332, 138.8516], [130.5172, 109.1189, 179.2908, 152.5566], [ 29.2499, 88.7732, 45.5664, 104.5635], [ 40.9168, 109.1093, 67.3653, 140.0567], [165.5889, 90.0294, 179.4471, 109.1606], [ 83.7276, 84.3918, 94.5928, 96.4693], [302.4648, 130.4534, 332.0580, 158.8674], [258.4624, 90.7134, 269.2498, 102.2883], [ 2.8419, 103.6409, 21.9580, 125.5492]], device='cuda:0'), 'labels': tensor([2, 2, 1, 1, 1, 1, 1, 1, 1], device='cuda:0'), 'scores': tensor([0.9962, 0.9918, 0.9900, 0.9894, 0.9891, 0.9653, 0.9652, 0.9573, 0.9046], device='cuda:0')}]
Давайте нарисуем ограничивающую рамку на изображении, используя предсказанный результат. Изображение выводится с помощью функции plot_image_from_output, определенной выше. Цель — это фактическое местоположение ограничивающей рамки, а Прогноз — результат предсказания модели. Вы можете видеть, что модель хорошо определила фактическую ограничивающую рамку.
_idx = 1 print("Target : ", annotations[_idx]['labels']) plot_image_from_output(imgs[_idx], annotations[_idx]) print("Prediction : ", pred[_idx]['labels']) plot_image_from_output(imgs[_idx], pred[_idx])
Target : tensor([1, 1, 1, 2, 2, 1, 1, 1])
Prediction : tensor([2, 2, 1, 1, 1, 1, 1, 1, 1], device='cuda:0')
На этот раз мы оценим результаты прогноза на всех тестовых данных. Во-первых, прогнозируемые результаты и фактические метки для всех тестовых данных сохраняются в preds_adj_all и annot_all соответственно.
from tqdm import tqdm
labels = []
preds_adj_all = []
annot_all = []
for im, annot in tqdm(test_data_loader, position = 0, leave = True):
im = list(img.to(device) for img in im)
#annot = [{k: v.to(device) for k, v in t.items()} for t in annot]
for t in annot:
labels += t['labels']
with torch.no_grad():
preds_adj = make_prediction(model, im, 0.5)
preds_adj = [{k: v.to(torch.device('cpu')) for k, v in t.items()} for t in preds_adj]
preds_adj_all.append(preds_adj)
annot_all.append(annot)
100%|██████████| 85/85 [00:25<00:00, 3.34it/s]
Вычислим значение mAP через файл utils_ObjectDetection.py в папке Tutorial-Book-Utils. С помощью функции get_batch_statistics вычисляется совокупное значение между ограничивающими прямоугольниками, которые удовлетворяют условию IoU (пересечение союза), а затем вычисляется значение AP для каждого класса с помощью функции ap_per_class.
%cd Tutorial-Book-Utils/
import utils_ObjectDetection as utils
/content/Tutorial-Book-Utils
sample_metrics = []
for batch_i in range(len(preds_adj_all)):
sample_metrics += utils.get_batch_statistics(preds_adj_all[batch_i], annot_all[batch_i], iou_threshold=0.5)
true_positives, pred_scores, pred_labels = [torch.cat(x, 0) for x in list(zip(*sample_metrics))] # 배치가 전부 합쳐짐
precision, recall, AP, f1, ap_class = utils.ap_per_class(true_positives, pred_scores, pred_labels, torch.tensor(labels))
mAP = torch.mean(AP)
print(f'mAP : {mAP}')
print(f'AP : {AP}')
mAP : 0.7182363990382057 AP : tensor([0.8694, 0.9189, 0.3664], dtype=torch.float64)
Значения AP показаны только для фактических 3 классов, исключая фоновый класс. Несмотря на то, что мы тренировались всего 10 раз, мы можем увидеть улучшение по сравнению с результатом RetinaNet в главе 4. В частности, он показывает точность, эквивалентную 0,9189 AP для объекта с маской класса 1 и 0,3664 AP для объекта без маски класса 2 должным образом. Общеизвестно, что RetinaNet показывает высокую производительность даже в качестве одноэтапного метода с FPN и фокальными потерями. Конечно, производительность RetinaNet можно оптимизировать за счет настройки гиперпараметров, но, исходя из текущих экспериментальных результатов, Faster-RCNN показывает лучшую производительность на этом наборе данных.
На этом урок по обнаружению медицинских масок завершен. В этом руководстве мы перешли от предварительной обработки набора данных к обучению модели и составлению прогнозов. Чтобы повысить производительность, вы можете увеличить количество сеансов обучения или попробовать настройку гиперпараметров. Используйте модель обнаружения объектов для любых данных, которые вы хотите.