$$ \newcommand{\floor}[1]{\left\lfloor{#1}\right\rfloor} \newcommand{\ceil}[1]{\left\lceil{#1}\right\rceil} \renewcommand{\mod}{\,\mathrm{mod}\,} \renewcommand{\div}{\,\mathrm{div}\,} \newcommand{\metar}{\,\mathrm{m}} \newcommand{\cm}{\,\mathrm{cm}} \newcommand{\dm}{\,\mathrm{dm}} \newcommand{\litar}{\,\mathrm{l}} \newcommand{\km}{\,\mathrm{km}} \newcommand{\s}{\,\mathrm{s}} \newcommand{\h}{\,\mathrm{h}} \newcommand{\minut}{\,\mathrm{min}} \newcommand{\kmh}{\,\mathrm{\frac{km}{h}}} \newcommand{\ms}{\,\mathrm{\frac{m}{s}}} \newcommand{\mss}{\,\mathrm{\frac{m}{s^2}}} \newcommand{\mmin}{\,\mathrm{\frac{m}{min}}} \newcommand{\smin}{\,\mathrm{\frac{s}{min}}} $$

Prijavi problem


Obeleži sve kategorije koje odgovaraju problemu

Još detalja - opišite nam problem


Uspešno ste prijavili problem!
Status problema i sve dodatne informacije možete pratiti klikom na link.
Nažalost nismo trenutno u mogućnosti da obradimo vaš zahtev.
Molimo vas da pokušate kasnije.

Припрема из претходних свезака

Све свеске се међусобно надограђују, тако да нам је неопходно да поновимо неке делове из претходних свески, што без додатног објашњења чинимо у овој секцији.

Овде нам је ради анализе резултата неопходан списак класа. Такође, да би смо могли да користимо модел поновићемо поступак из ранијих свезака - дакле, увеземо неопходне библиотеке и учитамо модел. Додатно нам је неопходно да имамо и учитану базу. Ово се наводи без објашњења, с обзиром да је поступак идентичан као у претходним свескама.

In [1]:
classes = [ 'AnnualCrop',
            'Forest',
            'HerbaceousVegetation',
            'Highway',
            'Industrial',
            'Pasture',
            'PermanentCrop',
            'Residential',
            'River',
            'SeaLake']
In [2]:
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
In [3]:
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("Модел учитан")
Модел учитан
In [4]:
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 који ограничава број хрпа над којим ће се извршити - што се ради уколико не желите да чекате да се изврши над свим подацима.

Функција ће као повратну вредност дати два низа. Први који представља све лабеле, а други који представља кореспондирајуће предикције.

In [5]:
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 ако имате времена или процесорске моћи за извршавање.

In [11]:
labels, preds = predict_on_dataset(model, val_loader, 20)
Процесирам хрпу 1 od 20
Процесирам хрпу 2 od 20
Процесирам хрпу 3 od 20
Процесирам хрпу 4 od 20
Процесирам хрпу 5 od 20
Процесирам хрпу 6 od 20
Процесирам хрпу 7 od 20
Процесирам хрпу 8 od 20
Процесирам хрпу 9 od 20
Процесирам хрпу 10 od 20
Процесирам хрпу 11 od 20
Процесирам хрпу 12 od 20
Процесирам хрпу 13 od 20
Процесирам хрпу 14 od 20
Процесирам хрпу 15 od 20
Процесирам хрпу 16 od 20
Процесирам хрпу 17 od 20
Процесирам хрпу 18 od 20
Процесирам хрпу 19 od 20
Процесирам хрпу 20 od 20

Напишите сада сами команде које штампају колико имамо обрађених лабела и предикција. Погледајте у ранијим ћелијама колики су то бројеви за цео валидациони скуп.

In [7]:
# Решење
# print(len(labels))
# print(len(preds))

Квантитативне метрике

Коначно, прелазимо на квантитативне метрике квалитета рада модела. Само израчунавање метрика је потпуно подржано ако користите пакет sklearn, и неопходно је само позвати адекватне функције.

Најпре ћемо позвати функцију classification_report која управо прима низове лабела и предикција које смо већ изгенерисали. Као што и само име функције наговештава, она ће као излаз дати стандардизован извештај са метрикама за један модел машинског учења.

In [8]:
print(metrics.classification_report(labels, preds))
              precision    recall  f1-score   support

           0       0.94      0.94      0.94        65
           1       0.97      1.00      0.99        73
           2       0.95      0.97      0.96        80
           3       0.97      1.00      0.98        57
           4       1.00      1.00      1.00        71
           5       1.00      0.94      0.97        53
           6       0.97      0.90      0.93        67
           7       1.00      0.99      0.99        68
           8       0.98      1.00      0.99        46
           9       0.97      1.00      0.98        60

    accuracy                           0.97       640
   macro avg       0.97      0.97      0.97       640
weighted avg       0.97      0.97      0.97       640

Ако гледамо редове излаза примећујемо да прво имамо метрике за сваку од класа (првих 10 редова), а онда и усредњене вредности (задња три реда). Две најпрепознатљивије метрике су прецизност (енг. precission) i одзив (енг. recall). Прецизност описује у којој мери је од свих датих предикција за неку класу модел тачно предвидео ту класу. Одзив описује колико је од свих постојећих примера неке класе модел дао предикција за ту класу (не улазећи у то да ли су оне тачне или не).

Кад се мало размисли постаје очигледно да су прецизност и одзив донекле супростављени, те повећање једног најчешће смањује друго - на шта указују и резултати које смо добили.

Матрица конфузије

Други, јако често коришћен, начин за представу резултата класификатора је матрица конфузије. Као што и само име наговештава у питању је алат који нам служи да добијемо увид у то шта "збуњује" наш модел. Конкретно, то је матрица где за сваку дату предикцију имамо која је тачна и која је предвиђена, па је лако увидети где долази до забуне.

За дате лабеле и предикције, матрица конфузије се једноставно добија позивом функције из пакета sklearn. Додуше, резултат је јако сувопарана матрица, па ћемо у следећој ћелији позвати функције које исту ту матрицу приказују мало лепше визуелно.

In [9]:
metrics.confusion_matrix(labels, preds)
Out[9]:
array([[61,  0,  1,  0,  0,  0,  1,  0,  1,  1],
       [ 0, 73,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  1, 78,  0,  0,  0,  1,  0,  0,  0],
       [ 0,  0,  0, 57,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0, 71,  0,  0,  0,  0,  0],
       [ 0,  1,  1,  0,  0, 50,  0,  0,  0,  1],
       [ 4,  0,  1,  2,  0,  0, 60,  0,  0,  0],
       [ 0,  0,  1,  0,  0,  0,  0, 67,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0, 46,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0, 60]], dtype=int64)
In [10]:
metrics.ConfusionMatrixDisplay(metrics.confusion_matrix(labels, preds), display_labels=classes).plot()
plt.xticks(rotation=90)
plt.show()

За сваку од класа је дата једна врста која представља тачне лабеле и једна колона која представља предикције. Тачне предикције се формирају на дијагонали матрице, јер тада је управо лабела врсте једнака лабели колоне. Све грешке модела видимо ван дијагонале матрице. Анализа постаје интересантна када уочавамо ситуације где модел често греши између неке две класе.

На примеру изнад, можете уочити да је највећи број ван дијагонале матрице у првој колони и седмој врсти. Уочавамо да је то ситуације када је тачна лабела "PermanentCrop" (стални усеви) а предвиђена лабела "АnnualCrop" (једногодишњи усеви). Ово је чак и потпуно очекивано јер заиста и јесте тешко разликовати два типа усева, док је рецимо много лашке разликовати насеље од усева.

Која је друга најчешћа грешка модела? Размислите зашто ту модел греши?