Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Компьютерное зрение. Практикум

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
Для обучения нейронной сети необходимо, чтобы значения в тензорах были
числами с плавающей точкой. Чтобы это сделать разделим все значения на 255. В результате значения пикселей будут изменяться в диапазоне от 0.0 до 1.0.
import copy
x_train = copy.deepcopy(train_data.data)[:, :, :, None] / 255.
print(x_train.shape, x_train.min(), x_train.max(), x_train.dtype, sep="\n")
classes = train_data.classes
classes_ru = [
"0 - ноль",
"1 - один",
"2 - два",
"3 - три",
"4 - четыре",
"5 - пять",
"6 - шесть",
"7 - семь",
"8 - восемь",
"9 - девять",]
# Аналогично записываем x и y для валидационной выборки
x_val = copy.deepcopy(val_data.data)[:, :, :, None] / 255.
y_val = copy.deepcopy(val_data.targets)
print(x_val.shape, y_val.shape, sep="\n")
Создание сверточной нейронной сети. Спроектируем и обучим сверточную нейронную сеть. В качестве примера воссоздадим модель LeNet.
from torch.nn import Linear, Conv2d, ReLU, Softmax, MaxPool2d, Flatten,
41
Sequential
#Сверточные слои, служит для нахождения признаков, которые наиболее удачно позволят классифицировать изображения
conv_layers = [
Conv2d(
in_channels = 1,
out_channels = 6,
kernel_size = 5,
padding='same',
padding_mode='zeros'),
ReLU(),
MaxPool2d(kernel_size=2),
Conv2d(
in_channels = 6,
out_channels = 16,
kernel_size = 5),
ReLU(),
MaxPool2d(kernel_size=2),
Conv2d(
in_channels = 16,
out_channels = 120,
kernel_size = 5),
ReLU()
]
#Линейные слои. На основе признаков, извлеченных сверточными слоями, линейные слои должны уметь классифицировать изображения.
linear_layers=[
42
Linear(
in_features=120,
out_features=84, ), ReLU(), Linear(
in_features=84, out_features=10, ), Softmax(dim = -1), ]
#Flatten - сжимает многомерный тензор в одномерный тензор layers = conv_layers + [Flatten()] + linear_layers
#Sequential - объект который позволяет записать модель как набор слоев le_net = Sequential(
*layers
)
class LeNet(torch.nn.Module): def __init__(self): super().__init__()
self.le_net = le_net
def forward(self, x): return self.le_net(x.permute((0, 3, 1, 2))).log()
Теперь необходимо настроить градиентный спуск и обучить модель. На
43
рисунке 2.8 представлен график изменения значения ошибок работы модели в
процессе обучения.
# Реализуем цикл обучения модели.
from IPython.display import clear_output
# Для ускорения обучения используем видеокарту компьютера # или виртуальной машины, если есть такая возможность.
device = torch.device('cuda:0') if torch.cuda.is_available() else torch.device('cpu')
num_epochs = 1000 batch_size = 1000 val_every = 5
model = LeNet().to(dtype=x_train.dtype, device=device) # Важно, чтобы все
тензоры были одного типа и на одном устройстве.
optimizer = torch.optim.SGD( model.parameters(), lr=1e-2, # Дополнительные параметры оптимизатора, # ускоряющие процесс обучения. momentum=0.9, nesterov=True, )
#Функция потерь loss_fn = torch.nn.NLLLoss()
losses = {"train": [], "val": []}
44
for epoch in range(1, num_epochs + 1): optimizer.zero_grad()
# Будем на каждой итерации обучения брать не все 50 000 картинок, # а лишь случайные batch_size из них. batch_pos = torch.randint(high=x_train.shape[0], size=[batch_size])
print(epoch)
pred = model(x_train[batch_pos].to(device)) # Также переносим тензоры на
устройство.
loss = loss_fn(pred, y_train[batch_pos].to(device))
loss.backward() optimizer.step()
losses["train"].append(loss.item())
# Каждые val_every итераций считаем значение loss на валидации. if epoch % val_every == 0: with torch.no_grad():
val_batch_pos = torch.randint(high=x_val.shape[0], size=[batch_size])
val_pred = model(x_val[val_batch_pos].to(device))
val_loss = loss_fn(val_pred, y_val[val_batch_pos].to(device))
losses["val"].append(val_loss.item())
# Каждые 100 итераций рисуем графики loss. if epoch % 10 == 0: clear_output(True) fig, ax = plt.subplots(figsize=(30, 10))
45
plt.title("График ошибки") plt.plot(losses["train"], ".-", label="Ошибка на обучении") plt.plot(torch.arange(0, epoch, val_every), losses["val"], ".-", label="Ошибка
на валидации")
plt.xlabel("Итерация обучения")
plt.ylabel("Значение ошибки") plt.legend() plt.grid() plt.show()
Рисунок 2.8 – Изменение значения ошибки модели во время обучения
Как можно заметить график на валидации движется вниз вместе с графиком
обучения. Это означает, что модель приобрела способность "обобщать" то есть
классифицировать произвольно нарисованную цифру, а не только те которые
видела.
Результат работы модели представлен на рисунке 2.9.
46
Рисунок 2.9 – Результат работы модели
Оценим качество работы модели с помощью метрики точности (accuracy). Эта
метрика вычисляется как отношение количества правильно распознанных цифр к
общему числу цифр в выборке.
with torch.no_grad(): pred_sub = model(x_val.to(device)).max(1).indices acc_score = (y_val.to(device)
== pred_sub).to(dtype=torch.float).mean().item()
print('Доля правильно классифицированных изображений', round(acc_score, 2))
Код выше выведет следующее Доля правильно классифицированных
изображений 0.98. То есть в среднем 98 из 100 изображений наша сеть способна
верно распознать.
Fine-Tunning и Transfer-Learning. Для решения более сложных задач
потребуется использование огромного количества данных и более сложных
архитектур. Для того чтобы обучить модель может потребоваться большое
количество ресурсов и времени.
47
Однако можно переиспользовать некоторые части нейронных сетей.
Например, есть сверточная нейронная сеть ResNet которая была обучена на 150 000
изображений и классифицирует на 1000 классов. Ее первые сверточные слои были
обучены выделять признаки на изображениях, а её последние линейные слои были
обучены для решения задачи классификации.
Вполне возможно что первые несколько слоев могут пригодится при решении
некоторой вашей задачи классификации. Тогда мы можем просто отбросить из
модели слои которые мы использовали для классификации и заменить их на те
линейные слои, которые нужны нам.
После замены мы заново начнем обучать сеть, однако шаг обучения выставим
не большим, чтобы веса, которые были взяты из готовой модели не изменялись очень сильно. Этот подход называется fine-tuning и тоже является довольно затратным, но лучше чем обучение сети с нуля
Можно пойти дальше, и вообще запретить изменять веса из готовой модели
(заморозить веса). Во время дообучения изменяться будут только веса в слоях, которые добавили вы. Такой подход называется Transfer-Learning
Transfer - learning модели ResNet для классификации на STL-10.
Рассмотрим датасет CIFAR-10 который представляет собой набор из 60 тысяч
фотографий каждая из которых относится к одному из 10 классов (самолет,
автомобиль, птица, кошка, олень, собака, лягушка, лошадь, корабль и грузовик).
from torchvision import datasets download_dir = "./dataset" train_data = datasets.STL10(root=download_dir, download=True, split="train") val_data = datasets.STL10(root=download_dir, download=True, split="test")
classes = train_data.classes classes_ru = ('самолет' , 'птица', 'автомобиль', 'кошка', 'олень', 'собака', 'лошадь',
'обезьяна', 'корабль', 'грузовик')
48
Каждое изображение состоит из пикселей, а каждый пиксель представляет
собой комбинацию интенсивностей красного, зеленого и синего цветов. Эти
интенсивности могут принимать значения от 0 до 255. Однако для обучения
нейронной сети предпочтительно, чтобы данные были представлены в виде чисел с
плавающей точкой в диапазоне от 0 до 1. Чтобы преобразовать значения пикселей в
этот диапазон, необходимо разделить все значения на 255.
# Объекты данных из обучающей выборки (5 000 цветных картинок размеров
96 x 96).
x_train = torch.tensor(train_data.data) / 255. # Цели предсказания для обучающей выборки (5 000 чисел от 0 до 9). y_train = torch.tensor(train_data.labels)
# Аналогично записываем x и y для валидационной выборки (8 000 примеров). x_val = torch.tensor(val_data.data) / 255. y_val = torch.tensor(val_data.labels)
Пример изображений из набора данных представлен на рисунке 2.10.
Рисунок 2.10 – Пример изображений из набора данных STL
from torchvision import models
49
weights = models.ResNet18_Weights.DEFAULT # Скачаем предобученные веса
для модели.
model = models.resnet18(weights=weights) # Выберем архитектуру модели и
зададим в ней веса.
model #отобразим архитектуру модели
Получим параметры нормализации значений изображения. Возьмем
обыкновенное RGB изображение, возьмем один канал этого изображения. У нас
получится двумерная матрица размерами HxW в которой записаны значения
интенсивности одного из каналов. Теперь у нас получается что некоторые пиксели
изображения могут быть слишком яркими их значение интенсивности высокое, в то
же время на изображении могут быть и пиксели у которых низкая интенсивность.
Во время обучения мы умножаем эти значения на подбираемые веса, но так как
разброс в значений интенсивности велик процесс обучения может быть
нестабильным и долго сходится. Поэтому мы усредним значения интенсивности
каждого пикселя, то есть применим нормализацию.
weights.transforms() # Каким образом данные необходимо предобработать для
работы с моделью
for param in model.parameters(): param.requires_grad = False # Укажем, что не хотим считать градиенты ни для
одного из предобученных параметров.
Применим аугментации к нашим данным. Выполним отражение по
горизонтали, изменим размер до 256 пикселей, выполним случайный поворот,
изменим яркость и контрастность, и нормализуем изображение.
from torchvision import transforms # В библиотеке можно найти много
различных аугментаций
50
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]