Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Компьютерное зрение. Практикум
.pdf
plt.imshow(result['mask'])
Результат можно посмотреть на рисунке 4.5.
Рисунок 4.5 – Результат сжатия изображения с помощью дополнения
изображения до квадратного размера
Опишем необходимую предобработку для валидации и обучающей выборки
IMG_SIZE = 256
INFER_HEIGHT = IMG_SIZE
INFER_WIDTH = IMG_SIZE
defget_training_augmentation():
train_transform = [
albu.LongestMaxSize(max_size=INFER_HEIGHT, always_apply=True),
albu.PadIfNeeded(min_height=int(INFER_HEIGHT*1.1),
min_width=int(INFER_WIDTH*1.1), border_mode=2, always_apply=True),
albu.RandomCrop(height=INFER_HEIGHT, width=INFER_WIDTH,
always_apply=True),
81

#Добивим небольшие размытия на изображении
albu.OneOf(
[
albu.Sharpen(alpha=(0.1, 0.2), lightness=(0.1, 0.2), p=0.5), # Уменьшение
вероятности и уменьшение интенсивности
albu.Blur(blur_limit=[1, 3], p=0.5), # Уменьшение вероятности и уменьшение
интенсивности
albu.GaussNoise(var_limit=(1, 5), p=0.5), # Уменьшение вероятности и
уменьшение интенсивности
],
p=0.7, # Уменьшение вероятности применения любой аугментации
),
#Сделаем изменения яркости и контрастности
albu.OneOf(
[
albu.RandomBrightnessContrast(brightness_limit=0.1, contrast_limit=0.1, p=0.5),
# Уменьшение вероятности и уменьшение интенсивности
albu.HueSaturationValue(hue_shift_limit=5, sat_shift_limit=10, val_shift_limit=5,
p=0.5), # Уменьшение вероятности и уменьшение интенсивности
albu.RGBShift(r_shift_limit=10, g_shift_limit=10, b_shift_limit=10, p=0.5), #
Уменьшение вероятности и уменьшение интенсивности
],
p=0.7, # Уменьшение вероятности применения любой аугментации цвета
),
]
return albu.Compose(train_transform)
defget_validation_augmentation():
82

#Для валидации сделаем лишь изменение размера изображения до 256
пикселей
test_transform = [albu.LongestMaxSize(max_size=INFER_HEIGHT,
always_apply=True),
albu.PadIfNeeded(min_height=INFER_HEIGHT, min_width=INFER_WIDTH,
border_mode=2, always_apply=True),
albu.CenterCrop(height=INFER_HEIGHT, width=INFER_WIDTH,
always_apply=True)]
return albu.Compose(test_transform)
defto_tensor(x, **kwargs):
x = x.transpose(2, 0, 1).astype('float32')
return torch.tensor(x)
defget_preprocessing(preprocessing_fn):
# Осуществит стартовую нормализацию данных согласно своим значениям
или готовым для imagenet
_transform = [
albu.Lambda(image=preprocessing_fn),
albu.Lambda(image=to_tensor, mask=to_tensor),
]
return albu.Compose(_transform)
Загрузим предобученную модель.
import segmentation_models_pytorch as smp
from segmentation_models_pytorch import utils
# Создадим нашу модель.
83

EPOCHS = 10
ENCODER = "resnet34"
ENCODER_WEIGHTS = "imagenet"
ACTIVATION = 'softmax2d'
DEVICE = torch.device('cuda'if torch.cuda.is_available() else'cpu')
BATCH_SIZE = 10
INIT_LR = 0.0005
LR_DECREASE_STEP = 4
LR_DECREASE_COEF = 2# LR будет разделен на этот коэфраз в
LR_DECREASE_STEP эпох
loss = utils.losses.DiceLoss()
model = smp.Unet(
encoder_name=ENCODER, # Архитектура кодировщика. В качестве примера
мы выбрали resnet34
encoder_weights="imagenet", # Чтобы не обучать кодировщик мы возьмем его
веса обученные на датасете ImageNet
in_channels=3, # Сколько каналов имеет изображение которое подается на
вход. Так как у нас RGB изображение то подается три канала
classes=len(classes), # Сколько каналов должно быть на выходе нейронной
сети. В данном случае их столько сколько классов у нас есть
activation=ACTIVATION# Так как у нас много классов, то на выходе мы
должны для каждого пикселя получить вероятность принадлежности элемента к
каждому классу
)
Получим параметры нормализации значений изображения. Возьмем
обыкновенное RGB изображение, возьмем один канал этого изображения. У нас
84

получится двумерная матрица размерами HxW в которой записаны значения
интенсивности одного из каналов. Теперь у нас получается, что некоторые пиксели
изображения могут быть слишком яркими их значение интесивности высокое, в то
же время на изображении могут быть и пиксели, у которых низкая интенсивность.
Во время обучения мы умножаем эти значения на подбираемые веса, но так как
разброс в значений интесивности велик процесс обучения может быть не
стабильным и долго сходится. Поэтому мы усредним значения интенсивности
каждого пикселя, то есть применим нормализациию. Мы можем получить
необходимые параметры нормализации для предобученной модели с помощью
функции представленной ниже
preprocessing_fn = smp.encoders.get_preprocessing_fn(ENCODER,
ENCODER_WEIGHTS)
train_dataset = SegmentationDataset(x_train, y_train,
num_of_classes=len(classes),
augmentation=get_training_augmentation(),
preprocessing=get_preprocessing(preprocessing_fn))
val_dataset = SegmentationDataset(x_val, y_val,
num_of_classes=len(classes),
augmentation=get_validation_augmentation(),
preprocessing=get_preprocessing(preprocessing_fn))
Обновление весов нейронной сети происходит не после вычисления
результатов одного конкретного изображения. Если бы это было так, то, во-первых,
это занимало бы слишком много времени на обучение, а во вторых сделало процесс
обучения менее стабильным. Поэтому сначала вычисляется результат работы
нейронной сети для группы изображений, потом полученные результаты на этой
группе изображений усредняются и на основе среднего результата корректируются
85

веса нашей сети.
Такие группы называются батчами. Библиотека PyTorch содержит в себе уже
готовый инструментарий для работы с батчами. Для создания батчей используется
объект DataLoader. DataLoader позволяет нам установить размер батча с помощью
параметра batch_size, параметр shuffle означает будет ли батч формироваться из
случайных изображений из выборки (если True) или будут поочередно извлекаться
из выборки изображения.
from torch.utils.data import DataLoader
n_cpu = os.cpu_count() #считает количество ядер в процессоре
train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True,
num_workers=n_cpu)
valid_loader = DataLoader(val_dataset, batch_size=5, shuffle=False,
num_workers=n_cpu)
В качестве метрики в задаче сегментации используется метрика IoU
(Intersection over Union).
Вычисляется она как площадь пересечения где значения классов пикселей
между маской сгенерированной моделью и маской, которая была в датасете
совпадают поделенное на общую площадь изображения. Диапазон значений у
метрики от 0 до 1. Чем ближе значение к нулю, тем менее точно нейросеть сумела
сегментировать изображение.
86

Рисунок 4.6 – Наглядное графическое представление формулы метрики
IoU(Intersection over Union)
metrics = [
utils.metrics.Fscore(),
utils.metrics.IoU()
]
optimizer = torch.optim.Adam([
dict(params=model.parameters(), lr=INIT_LR),
])
#Cоздадим описание как будет происходить эпоха обучения и валидации
train_epoch = utils.train.TrainEpoch(
model,
loss=loss,
metrics=metrics,
optimizer=optimizer,
device=DEVICE,
verbose=True, #выводить на экран информацию с процессом обучения
)
87

valid_epoch = utils.train.ValidEpoch(
model,
loss=loss,
metrics=metrics,
device=DEVICE,
verbose=True,
)
#Создадим директорию куда будем сохранять обученную модель
!mkdir models
max_score = 0
loss_logs = {"train": [], "val": []}
metric_logs = {"train": [], "val": []}
for i inrange(0, EPOCHS):
print('\nEpoch: {}'.format(i))
train_logs = train_epoch.run(train_loader)
train_loss, train_metric, train_metric_IOU = list(train_logs.values())
loss_logs["train"].append(train_loss)
metric_logs["train"].append(train_metric_IOU)
valid_logs = valid_epoch.run(valid_loader)
val_loss, val_metric, val_metric_IOU = list(valid_logs.values())
loss_logs["val"].append(val_loss)
metric_logs["val"].append(val_metric_IOU)
if max_score < valid_logs['iou_score']:
88

max_score = valid_logs['iou_score']
#Можно сохранить наилучшую модель в формате .pth но тогда вы сохраняете
только веса самой модели
torch.save(model, 'models/best_model_new.pth')
trace_image = torch.randn(BATCH_SIZE, 3, INFER_HEIGHT, INFER_WIDTH)
#Предпочтительный способ сохранения моделей
traced_model = torch.jit.trace(model, trace_image.to(DEVICE))
torch.jit.save(traced_model, 'models/best_model_new.pt')
print('Model saved!')
print("LR:", optimizer.param_groups[0]['lr'])
if i >0and i % LR_DECREASE_STEP == 0:
print('Decrease decoder learning rate')
optimizer.param_groups[0]['lr'] /= LR_DECREASE_COEF
Посмотрим графики обучения нейронной сети. Можно заметить, что Loss
функция.
Рисунок 4.7 – а) График значений функции потерь в зависимости от
количества эпох обучения модели. б) График значении метрики IoU в зависимости
от количества эпох обучения модели
89

Загрузим наилучше обученную модель.
best_model = torch.jit.load('models/best_model_new.pt', map_location=DEVICE)
Отобразим результат работы нашей обученной модели.
test_example = val_dataset[15] #произвольное изображение из валидационной
выборки
test_img = test_example[0].unsqueeze(0)
test_img = test_img.to(torch.float32).cuda()
#чтобызагрузитьвнейросетьнужноперевеститензорнаустройство cuda
gt_mask = test_example[1]
pred_mask = model(test_img)[0] #возвращает [9,256,256]
pred_mask = multichannel_to_matrix(pred_mask)
gt_mask = multichannel_to_matrix(gt_mask)
test_img = test_img.cpu()
print(test_img[0].shape)
plt.figure(figsize=(10, 4))
plt.subplot(1,3,1)
plt.imshow(test_img[0].permute(1,2,0))
plt.subplot(1,3,2)
visualize_mask(pred_mask.cpu(), title="Как предсказал анейросеть",
90
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
