Све свеске се међусобно надограђују, тако да нам је неопходно да поновимо неке делове из претходних свески, што без додатног објашњења чинимо у овој секцији.
Овде нам је ради анализе резултата неопходан списак класа. Такође, да би смо могли да користимо модел поновићемо поступак из ранијих свезака - дакле, увеземо неопходне библиотеке и учитамо модел. Додатно нам је неопходно да имамо и учитану базу. Ово се наводи без објашњења, с обзиром да је поступак идентичан као у претходним свескама.
classes = [ 'AnnualCrop',
'Forest',
'HerbaceousVegetation',
'Highway',
'Industrial',
'Pasture',
'PermanentCrop',
'Residential',
'River',
'SeaLake']
from os import environ
environ["OPENCV_IO_ENABLE_JASPER"] = "true"
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import cv2
import numpy as np
from skimage import exposure
from sklearn import metrics
from matplotlib import pyplot as plt
with torch.no_grad():
model = torchvision.models.resnet50(pretrained=True)
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, 10)
device = "cpu"
model.to(device)
model.load_state_dict(torch.load(r"mldata\resnet_50_land_use.pt", map_location=torch.device(device)))
model.eval()
print("Модел учитан")
def image_loader(path):
image = (cv2.imread(path).astype("float32") / 255.0)[:, :, ::-1].copy()
return torch.from_numpy(image.transpose(2,0,1))
transforms = torchvision.transforms.Compose([
torchvision.transforms.ToPILImage(),
torchvision.transforms.Resize(224),
torchvision.transforms.ToTensor(),
])
dataset = torchvision.datasets.DatasetFolder(root=r"mldata\EuroSAT\2750", loader=image_loader, transform=transforms, extensions="jpg")
train_ratio = 0.7
val_ratio = 0.15
test_ratio = 0.15
dataset_size = len(dataset)
train_samples = int(train_ratio * dataset_size)
val_samples = int(val_ratio * dataset_size)
test_samples = dataset_size - train_samples - val_samples # Еквивалентно са int(val_ratio * test_ratio) али избегава грешку заокруживања.
DATASET_SEED = 12345
train_dataset, val_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_samples, val_samples, test_samples], generator=torch.Generator().manual_seed(DATASET_SEED))
BATCH_SIZE = 32
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=0, drop_last=True, pin_memory=True)
Мерење квалитета модела¶
Извршавање модела над целим валидационим скупом¶
Ради квантитативног мерења квалитета рада модела неопходно је извршити модел на што већем узорку података - идеално на целом валидационом скупу података. Следећа фукнција управо служи за извршавање модела на већем скупу података.
Већину елемената ове функције смо већ видели у претходним ћелијама и у зависности од вашег предзнања вероватно је можете и у потпуности разумети. Функција пре свега као аргументе прима модел и скуп података над којим треба да се изврши, а као помоћни аргумент прима и max_batches који ограничава број хрпа над којим ће се извршити - што се ради уколико не желите да чекате да се изврши над свим подацима.
Функција ће као повратну вредност дати два низа. Први који представља све лабеле, а други који представља кореспондирајуће предикције.
def predict_on_dataset(model, dataset, max_batches=None):
model.eval()
iteration_cnt = 0
all_preds = list()
all_labels = list()
with torch.no_grad():
for i, data in enumerate(dataset):
if max_batches is not None and iteration_cnt == max_batches:
break
print(f"Процесирам хрпу {i + 1} od {max_batches}")
inputs, labels = data[0].to(device), data[1].to(device)
outputs = model(inputs)
_, pred = torch.max(outputs, 1)
all_preds += list(pred.data.cpu().numpy())
all_labels += list(labels.data.cpu().numpy())
iteration_cnt += 1
return all_labels, all_preds
Хајде да сада извршимо предикцију модела над валидационим подацима користећи наведену функцију. Ограничили смо да се изврши на укупно 20 хрпа, што је прихватљив број за већину процесора али ћете свакако морати да сачекате неколико минута да се заврши извршење. Идеално, желимо да извршимо на што више података да бисмо добили репрезентативније метрике. Слободно ставите већи број или None ако имате времена или процесорске моћи за извршавање.
labels, preds = predict_on_dataset(model, val_loader, 20)
Напишите сада сами команде које штампају колико имамо обрађених лабела и предикција. Погледајте у ранијим ћелијама колики су то бројеви за цео валидациони скуп.
# Решење
# print(len(labels))
# print(len(preds))
Квантитативне метрике¶
Коначно, прелазимо на квантитативне метрике квалитета рада модела. Само израчунавање метрика је потпуно подржано ако користите пакет sklearn, и неопходно је само позвати адекватне функције.
Најпре ћемо позвати функцију classification_report која управо прима низове лабела и предикција које смо већ изгенерисали. Као што и само име функције наговештава, она ће као излаз дати стандардизован извештај са метрикама за један модел машинског учења.
print(metrics.classification_report(labels, preds))
Ако гледамо редове излаза примећујемо да прво имамо метрике за сваку од класа (првих 10 редова), а онда и усредњене вредности (задња три реда). Две најпрепознатљивије метрике су прецизност (енг. precission) i одзив (енг. recall). Прецизност описује у којој мери је од свих датих предикција за неку класу модел тачно предвидео ту класу. Одзив описује колико је од свих постојећих примера неке класе модел дао предикција за ту класу (не улазећи у то да ли су оне тачне или не).
Кад се мало размисли постаје очигледно да су прецизност и одзив донекле супростављени, те повећање једног најчешће смањује друго - на шта указују и резултати које смо добили.
Матрица конфузије¶
Други, јако често коришћен, начин за представу резултата класификатора је матрица конфузије. Као што и само име наговештава у питању је алат који нам служи да добијемо увид у то шта "збуњује" наш модел. Конкретно, то је матрица где за сваку дату предикцију имамо која је тачна и која је предвиђена, па је лако увидети где долази до забуне.
За дате лабеле и предикције, матрица конфузије се једноставно добија позивом функције из пакета sklearn. Додуше, резултат је јако сувопарана матрица, па ћемо у следећој ћелији позвати функције које исту ту матрицу приказују мало лепше визуелно.
metrics.confusion_matrix(labels, preds)
metrics.ConfusionMatrixDisplay(metrics.confusion_matrix(labels, preds), display_labels=classes).plot()
plt.xticks(rotation=90)
plt.show()
За сваку од класа је дата једна врста која представља тачне лабеле и једна колона која представља предикције. Тачне предикције се формирају на дијагонали матрице, јер тада је управо лабела врсте једнака лабели колоне. Све грешке модела видимо ван дијагонале матрице. Анализа постаје интересантна када уочавамо ситуације где модел често греши између неке две класе.
На примеру изнад, можете уочити да је највећи број ван дијагонале матрице у првој колони и седмој врсти. Уочавамо да је то ситуације када је тачна лабела "PermanentCrop" (стални усеви) а предвиђена лабела "АnnualCrop" (једногодишњи усеви). Ово је чак и потпуно очекивано јер заиста и јесте тешко разликовати два типа усева, док је рецимо много лашке разликовати насеље од усева.
Која је друга најчешћа грешка модела? Размислите зашто ту модел греши?