Часть 1 Нейронная сеть Pytorch с нуля
Компьютерное зрение (CNN)
Компьютерное зрение относится к области искусственного интеллекта и информатики, которая направлена на то, чтобы позволить компьютерам понимать и интерпретировать визуальные данные. Он включает в себя разработку алгоритмов и методов, которые позволяют компьютерам анализировать, обрабатывать и извлекать значимую информацию из изображений или видео. Конечная цель компьютерного зрения — позволить машинам воспринимать и понимать визуальный мир так же, как люди.
Введение:
Этот проект будет вращаться вокруг предоставления всеобъемлющего пошагового руководства по созданию нейронной сети с нуля. Основная цель — вооружить читателей глубоким пониманием фундаментальных процессов, связанных с построением нейронной сети. Как только проект будет завершен и наша цель по распространению знаний будет достигнута, мы перейдем к следующему этапу, который включает преобразование кода в модульный стиль. Это преобразование обеспечит повторное использование кода и его адаптацию для будущих проектов. Кроме того, мы сосредоточимся на повышении производительности модели за счет внедрения методов трансферного обучения, использования предварительно обученных моделей и их знаний для повышения общей точности и эффективности нашей нейронной сети.
Содержание:
- О наборе данных
- Подготовка данных
- Исследование данных
- Преобразование данных в тензоры
4.1 torchvision.transforms
4.2 torch.utils.data.Dataset.ImageFolder
4.3 torch.utils.data.DataLoader - Model_0 (базовая модель)
- Model_V1 (улучшение модели)
6.1 TrivialAugmentation
6.2 Матрица путаницы - Сделайте прогноз в пользовательском изображении
1. О наборе данных
Для этого проекта мы будем использовать набор данных Food Images (Food-10) от Kaggle. Набор данных содержит ряд различных подмножеств полных данных о еде-101. Food101 предоставил нам метаданные как для тестов, так и для поездов в текстовом файле. Мы воспользуемся этой информацией и извлечем ее часть.

2. Подготовка данных
PyTorch предлагает множество встроенных функций загрузки данных для обработки распространенных типов данных. One useful function is ImageFolder, particularly when working with images in a standard image classification form
Этот проект был выполнен с использованием Google Colab.
Для начала данные получены из Kaggle по указанной выше ссылке и сохранены на моем Google Диске. Затем я подключаю свой Google Диск к Google Colab. Из загруженного набора данных извлекается подмножество 10% для построения модели и уменьшения ее размера. Этот подход способствует более быстрому обучению и позволяет многократно улучшать модель до тех пор, пока не будет достигнута желаемая производительность. Важно помнить, что машинное обучение включает в себя итеративный процесс, и рекомендуется начинать с меньшего подмножества. Как только желаемая производительность будет достигнута, модель можно масштабировать по мере необходимости.
Наш извлеченный набор данных должен быть организован в этом формате (стандартная форма классификации изображений)

Цель состоит в том, чтобы превратить эту структуру хранения данных в набор данных, который можно использовать с PyTorch.
Во-первых, давайте получим мои вспомогательные функции с моего Google Диска.
import shutil
from shutil import copy
# Specify the source and destination paths
source_file_path = "/content/drive/MyDrive/Deep_learning/helper_functions.py"
destination_file_path = "helper_functions.py"
# Copy the file
copy(source_file_path, destination_file_path)
# Print a success message
print("File copied successfully!")
# importing my helper_fucntions
from helper_functions import download_data_V2, walk_through_dir, plot_transformed_random_images, accuracy_fn, eval_model
Приведенная ниже функция кода извлекает 10% изображений для каждого из моих любимых продуктов: кексов, пончиков, картофеля фри и мороженого. Он извлекает образцы из указанной папки на Google Диске и сохраняет их во вновь созданных папках «train» и «test». Структура папок соответствует стандартному формату, требуемому набором данных ImageFolder PyTorch.
# test data
download_data_V2('/content/drive/MyDrive/Deep_learning/data2/meta/meta/test.txt',
'/content/drive/MyDrive/Deep_learning/data2/images',
'data/my_fav_foods/test',
percentage=0.1,
class_list=['cup_cakes', 'donuts', 'french_fries', 'ice_cream'])
# train data
download_data_V2('/content/drive/MyDrive/Deep_learning/data2/meta/meta/train.txt',
'/content/drive/MyDrive/Deep_learning/data2/images',
'data/my_fav_foods/train',
percentage=0.1,
class_list=['cup_cakes', 'donuts', 'french_fries', 'ice_cream'])
Создайте путь для каталога данных, обучения и тестирования
from pathlib import Path
# data preparation
# Setup path to data folder
data_path = Path("data/")
image_path = data_path / "my_fav_foods"
# Setup train and testing paths
train_dir = image_path / "train"
test_dir = image_path / "test"
Проверьте структуру папок
# check data folder structure walk_through_dir(data_path) Output: There are 1 directories and 0 images in 'data'. There are 2 directories and 0 images in 'data/my_fav_foods'. There are 4 directories and 0 images in 'data/my_fav_foods/train'. There are 0 directories and 75 images in 'data/my_fav_foods/train/ice_cream'. There are 0 directories and 75 images in 'data/my_fav_foods/train/french_fries'. There are 0 directories and 75 images in 'data/my_fav_foods/train/cup_cakes'. There are 0 directories and 75 images in 'data/my_fav_foods/train/donuts'. There are 4 directories and 0 images in 'data/my_fav_foods/test'. There are 0 directories and 25 images in 'data/my_fav_foods/test/ice_cream'. There are 0 directories and 25 images in 'data/my_fav_foods/test/french_fries'. There are 0 directories and 25 images in 'data/my_fav_foods/test/cup_cakes'. There are 0 directories and 25 images in 'data/my_fav_foods/test/donuts'.
3. Исследование данных
Станьте единым целым с данными!
Теперь, когда у нас есть набор данных, давайте изучим их.
- Случайная визуализация с использованием PIL
from PIL import Image
import random
random.seed(42)
# get all image path
all_image_pathlist = list(image_path.glob('*/*/*.jpg'))
# pick a random image from all_image_pathlist
random_image = random.choice(all_image_pathlist)
# get the image class(the image class is the directory name where the image is stored)
image_class = random_image.parent.stem
# open image using PIL
img = Image.open(random_image)
# print metadata
print(f'image path: {random_image}')
print(f'image class: {image_class}')
print(f'image height: {img.height}')
print(f'image width: {img.width}')
img
output:
image path: data/my_fav_foods/test/french_fries/457709.jpg
image class: french_fries
image height: 384
image width: 512

4. Преобразование данных в тензоры
Чтобы подготовить данные изображения для глубокого обучения с помощью PyTorch, мы выполняем следующие ключевые шаги:
- Преобразование изображений в тензоры с помощью
torchvision.transforms: это преобразует данные изображения в числовые тензоры, позволяя алгоритмам машинного обучения обрабатывать данные. Мы также можем применять методы увеличения данных во время этого преобразования, чтобы повысить производительность модели. - Преобразование преобразованных данных в
torch.utils.data.Dataset.ImageFolder: этот формат, специфичный для PyTorch, упрощает обработку и организацию данных. Он автоматически упорядочивает изображения в папках для конкретного класса для быстрого доступа во время обучения. - Создайте
torch.utils.data.DataLoader: этот загрузчик генерирует пакеты выборок из набора данных, повышая эффективность обучения за счет одновременной обработки нескольких выборок. Это ускоряет обучение и повышает производительность модели.
4.1 torchvision.transforms
torchvision.transforms содержит множество готовых методов для форматирования изображений, превращения их в тензоры и даже манипулирования ими для увеличения данных.
Примечание. Обычно мы не выполняем манипуляции с данными в тестовом наборе. Однако нам нужно убедиться, что изображения нашего тестового набора преобразованы в тензоры. Мы приводим тестовые изображения к тому же размеру, что и наши тренировочные изображения.
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# transform for train data
data_transform_train = transforms.Compose([
# resize to 64x64 just like in tinyVGG architecture (For faster training time)
transforms.Resize(size=(64,64)),
# flip the images randomly on the horizontal
transforms.RandomHorizontalFlip(p=0.5),
# trivial augmentation
# transforms.TrivialAugmentWide(num_magnitude_bins=5),
# turn image into a torch.Tensor
transforms.ToTensor()
])
# transform for test data
data_transform_test = transforms.Compose([
# resize to 64x64 just like in tinyVGG architecture (For faster training time)
transforms.Resize(size=(64,64)),
# turn image into a torch.Tensor
transforms.ToTensor()
])
NOTE:После преобразования данных изображения его форма станет первым форматом цветового канала. (CHW) Такое расположение обычно используется в PyTorch.
# the img shape after transform print(data_transform_train(img).shape) output: torch.Size([3, 64, 64])
Чтобы визуализировать наше преобразованное изображение с помощью matplotlib, нам нужно изменить форму изображения, чтобы она соответствовала формату, в котором цветовой канал является последним (HWC). Это можно сделать с помощью метода перестановки. Matlotlib принимает формат HWC.
import matplotlib.pyplot as plt import numpy as np plt.imshow(data_transform_train(img).permute(1,2,0)) plt.axis(False)

Теперь у нас есть набор преобразований, давайте вызовем нашу функцию, чтобы опробовать их на разных изображениях.
# NOTE: This function use matlotlib and has permuted the shape of each image for visulalization
# originaly the shape after transform is in CHW format
plot_transformed_random_images(all_image_pathlist,
transform=data_transform_train,
n=2)

4.2 torch.utils.data.Dataset.ImageFolder
Теперь пришло время преобразовать данные нашего изображения в набор данных, совместимый с PyTorch. Поскольку наши данные соответствуют стандартному формату классификации изображений, мы можем использовать класс torchvision.datasets.ImageFolder.
Указав путь к файлу целевого каталога изображений и указав желаемые преобразования, мы можем легко использовать этот класс. Давайте применим его к нашим папкам данных train_dir и test_dir.
from torchvision import datasets
# use train_data_transform
train_data = datasets.ImageFolder(root=train_dir, # target folder of images
transform=data_transform_train, # transforms to perform on data (images)
target_transform=None) # transforms to perform on labels (if necessary)
# use test_data_transform
test_data = datasets.ImageFolder(root=test_dir,
transform=data_transform_test)
print(f"Train data:\n{train_data}\nTest data:\n{test_data}")
output:
Train data:
Dataset ImageFolder
Number of datapoints: 300
Root location: data/my_fav_foods/train
StandardTransform
Transform: Compose(
Resize(size=(64, 64), interpolation=bilinear, max_size=None, antialias=warn)
RandomHorizontalFlip(p=0.5)
ToTensor()
)
Test data:
Dataset ImageFolder
Number of datapoints: 100
Root location: data/my_fav_foods/test
StandardTransform
Transform: Compose(
Resize(size=(64, 64), interpolation=bilinear, max_size=None, antialias=warn)
ToTensor()
)
Исследование и извлечение наиболее распространенных атрибутов нашего преобразованного набора данных.
# Check the lengths
print(len(train_data), len(test_data))
# Get class names as a list
class_names = train_data.classes
print(class_names)
# Can also get class names as a dict
class_dict = train_data.class_to_idx
print(class_dict)
output:
300 100
['cup_cakes', 'donuts', 'french_fries', 'ice_cream']
{'cup_cakes': 0, 'donuts': 1, 'french_fries': 2, 'ice_cream': 3}
Проверка метаданных первого изображения
img, label = train_data[0][0], train_data[0][1] # indexing the first img and label
print(f"Image tensor:\n{img}")
print(f"Image shape: {img.shape}")
print(f"Image datatype: {img.dtype}")
print(f"Image label: {label}")
print(f"Label datatype: {type(label)}")
output:
Image tensor:
tensor([[[0.3216, 0.2314, 0.1647, ..., 0.9020, 0.9059, 0.9059],
[0.5647, 0.5373, 0.4471, ..., 0.4353, 0.4471, 0.4471],
[0.5216, 0.5608, 0.6039, ..., 0.6275, 0.6275, 0.6235],
...,
[0.5255, 0.5255, 0.5216, ..., 0.4784, 0.4588, 0.4510],
[0.5098, 0.5098, 0.5059, ..., 0.4667, 0.4510, 0.4353],
[0.4941, 0.4980, 0.4941, ..., 0.4471, 0.4314, 0.4196]],
[[0.3020, 0.2196, 0.1608, ..., 0.9569, 0.9608, 0.9608],
[0.5412, 0.4941, 0.4235, ..., 0.6353, 0.6471, 0.6431],
[0.5176, 0.5451, 0.5843, ..., 0.8000, 0.8000, 0.7961],
...,
[0.4000, 0.3961, 0.3922, ..., 0.3451, 0.3255, 0.3176],
[0.3765, 0.3765, 0.3725, ..., 0.3294, 0.3137, 0.3020],
[0.3608, 0.3647, 0.3608, ..., 0.3098, 0.2941, 0.2863]],
[[0.2431, 0.1647, 0.1098, ..., 0.9725, 0.9725, 0.9725],
[0.4549, 0.4196, 0.3451, ..., 0.8745, 0.8863, 0.8863],
[0.4667, 0.4863, 0.4784, ..., 0.9451, 0.9412, 0.9373],
...,
[0.2510, 0.2471, 0.2431, ..., 0.3059, 0.2941, 0.2824],
[0.2588, 0.2627, 0.2627, ..., 0.3059, 0.2863, 0.2667],
[0.2549, 0.2588, 0.2627, ..., 0.2863, 0.2667, 0.2510]]])
Image shape: torch.Size([3, 64, 64])
Image datatype: torch.float32
Image label: 0
Label datatype: <class 'int'>
Визуализировать в Matplotlib
# Rearrange the order of dimensions
img_permute = img.permute(1, 2, 0)
# Print out different shapes (before and after permute)
print(f"Original shape: {img.shape} -> [color_channels, height, width]")
print(f"Image permute shape: {img_permute.shape} -> [height, width, color_channels]")
# Plot the image
plt.figure(figsize=(5, 3))
plt.imshow(img_permute)
plt.axis("off")
plt.title(class_names[label], fontsize=14);
output:
Original shape: torch.Size([3, 64, 64]) -> [color_channels, height, width]
Image permute shape: torch.Size([64, 64, 3]) -> [height, width, color_channels]

4.3 torch.utils.data.DataLoader
Превращение нашего набора данных в DataLoader делает их итерируемыми (в пакетах), чтобы модель могла пройти и изучить отношения между образцами и целями.
import multiprocessing
# Turn train and test Datasets into DataLoaders
from torch.utils.data import DataLoader
BATCHSIZE = 32
NUM_WORKERS = multiprocessing.cpu_count()
train_dataloader = DataLoader(dataset=train_data,
batch_size=BATCHSIZE, # how many samples per batch?
num_workers=NUM_WORKERS, # how many subprocesses to use for data loading? (higher = more)
shuffle=True) # shuffle the data?
test_dataloader = DataLoader(dataset=test_data,
batch_size=BATCHSIZE,
num_workers=NUM_WORKERS,
shuffle=False) # don't usually need to shuffle testing data
train_dataloader, test_dataloader
output:
(<torch.utils.data.dataloader.DataLoader at 0x7fd16c66fbe0>,
<torch.utils.data.dataloader.DataLoader at 0x7fd16c66e3e0>)
Проверьте форму
img, label = next(iter(train_dataloader))
print(f"Image shape: {img.shape} -> [batch_size, color_channels, height, width]")
print(f"Label shape: {label.shape}")
output:
Image shape: torch.Size([32, 3, 64, 64]) -> [batch_size, color_channels, height, width]
Label shape: torch.Size([32])
5. Model_0 (базовая модель)
В нейронных сетях построение базовой модели имеет решающее значение для исследования архитектуры, настройки гиперпараметров, использования предварительного обучения или передачи обучения, обнаружения переобучения и сравнения/выбора моделей. Он служит отправной точкой для создания более продвинутых и оптимизированных моделей нейронных сетей.
5.1 Архитектура построения модели, копирующая TinyVGG
from torch import nn
class tinyVGG(nn.Module):
"""
Model architecture copying TinyVGG from:
https://poloclub.github.io/cnn-explainer/
"""
def __init__(self, input_shape: int, hidden_units: int, output_shape: int):
super().__init__()
# Convolutional blocks 1
self.block_1 = nn.Sequential(
nn.Conv2d(in_channels=input_shape, out_channels=hidden_units, kernel_size=3, stride=1, padding=0),
nn.ReLU(),
nn.Conv2d(in_channels=hidden_units, out_channels=hidden_units, kernel_size=3, stride=1, padding=0),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2) # compresses our images i.e 60x60 image pass through maxpol kernel:2 = 30x30 shape, for kernel of 4 = 15x15 shape
)
# Convolutional blocks 2
self.block_2 = nn.Sequential(
nn.Conv2d(in_channels=hidden_units, out_channels=hidden_units, kernel_size=3, stride=1, padding=0),
nn.ReLU(),
nn.Conv2d(in_channels=hidden_units, out_channels=hidden_units, kernel_size=3, stride=1, padding=0),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2)
)
# Classifier
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(in_features=hidden_units*13*13, # each layer of our network compresses and changes the shape of our inputs data
out_features=output_shape)
)
def forward(self, x):
x = self.block_1(x)
# print(f'shape after convolutional block 1: {x.shape}')
x = self.block_2(x)
# print(f'shape after convolutional block 2: {x.shape}')
x = self.classifier(x)
# print(f'shape after classifier: {x.shape}')
return x
Создайте экземпляр модели
model_0 = tinyVGG(input_shape=3,
hidden_units=10,
output_shape=len(class_names))
Общие сведения о моделях
# Install torchinfo if it's not available, import it if it is
try:
import torchinfo
except:
!pip install torchinfo
import torchinfo
from torchinfo import summary
summary(model_0, input_size=[3, 3, 64, 64]) # do a test pass through of an example input size
output:
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting torchinfo
Downloading torchinfo-1.8.0-py3-none-any.whl (23 kB)
Installing collected packages: torchinfo
Successfully installed torchinfo-1.8.0
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
tinyVGG [3, 4] --
├─Sequential: 1-1 [3, 10, 30, 30] --
│ └─Conv2d: 2-1 [3, 10, 62, 62] 280
│ └─ReLU: 2-2 [3, 10, 62, 62] --
│ └─Conv2d: 2-3 [3, 10, 60, 60] 910
│ └─ReLU: 2-4 [3, 10, 60, 60] --
│ └─MaxPool2d: 2-5 [3, 10, 30, 30] --
├─Sequential: 1-2 [3, 10, 13, 13] --
│ └─Conv2d: 2-6 [3, 10, 28, 28] 910
│ └─ReLU: 2-7 [3, 10, 28, 28] --
│ └─Conv2d: 2-8 [3, 10, 26, 26] 910
│ └─ReLU: 2-9 [3, 10, 26, 26] --
│ └─MaxPool2d: 2-10 [3, 10, 13, 13] --
├─Sequential: 1-3 [3, 4] --
│ └─Flatten: 2-11 [3, 1690] --
│ └─Linear: 2-12 [3, 4] 6,764
==========================================================================================
Total params: 9,774
Trainable params: 9,774
Non-trainable params: 0
Total mult-adds (M): 17.06
==========================================================================================
Input size (MB): 0.15
Forward/backward pass size (MB): 2.14
Params size (MB): 0.04
Estimated Total Size (MB): 2.32
==========================================================================================
Настройте функцию потерь и оптимизатор
loss_fn = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(params=model_0.parameters(), lr=0.001)
Нам снова понадобится наша вспомогательная функция для этапа обучения и визуализации производительности модели.
from helper_functions import train_step, test_step, train, plot_loss_curves, pred_and_plot_image, eval_model, plot_model_performance
Обучение начинается здесь…
### START TRAINING ###
# Set random seeds
torch.manual_seed(42)
NUM_EPOCHS = 10
from timeit import default_timer as timer
start_time = timer()
# Train model_0
model_0_results = train(model=model_0,
train_dataloader=train_dataloader,
test_dataloader=test_dataloader,
optimizer=optimizer,
loss_fn=loss_fn,
epochs=NUM_EPOCHS)
# End the timer and print out how long it took
end_time = timer()
print(f"Total training time: {end_time-start_time:.3f} seconds")
output:
100%
10/10 [00:48<00:00, 3.95s/it]
Epoch: 1 | train_loss: 1.3883 | train_acc: 0.2094 | test_loss: 1.3810 | test_acc: 0.2422
Epoch: 2 | train_loss: 1.3679 | train_acc: 0.3302 | test_loss: 1.3757 | test_acc: 0.2812
Epoch: 3 | train_loss: 1.3302 | train_acc: 0.3312 | test_loss: 1.3318 | test_acc: 0.4062
Epoch: 4 | train_loss: 1.2576 | train_acc: 0.3521 | test_loss: 1.3115 | test_acc: 0.3984
Epoch: 5 | train_loss: 1.2249 | train_acc: 0.4125 | test_loss: 1.4605 | test_acc: 0.3750
Epoch: 6 | train_loss: 1.2448 | train_acc: 0.4198 | test_loss: 1.3601 | test_acc: 0.3516
Epoch: 7 | train_loss: 1.2283 | train_acc: 0.4385 | test_loss: 1.3194 | test_acc: 0.3594
Epoch: 8 | train_loss: 1.2523 | train_acc: 0.3667 | test_loss: 1.3298 | test_acc: 0.3906
Epoch: 9 | train_loss: 1.1973 | train_acc: 0.4552 | test_loss: 1.3669 | test_acc: 0.3672
Epoch: 10 | train_loss: 1.1929 | train_acc: 0.4500 | test_loss: 1.3539 | test_acc: 0.3984
Total training time: 48.788 seconds
print(model_0_results.keys())
model_0_results['test_acc'][-1]
output:
dict_keys(['train_loss', 'train_acc', 'test_loss', 'test_acc'])
0.3984375
Визуализируйте производительность model_0
plot_loss_curves(model_0_results)

Основываясь на данных настройках модели и гиперпараметрах, кажется, что model_0 не достигает удовлетворительной точности. Визуализация производительности показывает, что модель хорошо работает на данных обучения, но хуже на данных тестирования, что указывает на явный случай переобучения из-за заметного разрыва между потерями при обучении и тестировании.
Мы можем обратиться к следующей ссылке для руководства по решению проблемы:
Кроме того, мы можем:
Увеличение количества эпох на основе тенденции графика указывает на потенциал для улучшения. Кроме того, мы можем поэкспериментировать с преобразованием размера входных данных (например, увеличить масштаб с 64x64), хотя это может потребовать больше времени на обучение. Сбор большего количества образцов для нейронной сети также может привести к лучшим результатам.
Вот некоторые общие гиперпараметры, используемые в PyTorch:
- Скорость обучения: определяет размер шага для обновления весов модели во время обучения.
- Размер партии: количество образцов, обработанных на каждой итерации во время обучения.
- Количество эпох: общее время прохождения всего набора данных через модель во время обучения.
- Функция потерь: вычисляет ошибку между прогнозируемыми и целевыми значениями.
- Оптимизатор: алгоритм, используемый для обновления весов моделей на основе рассчитанных потерь.
- Импульс: параметр, который накапливает долю прошлых градиентов для ускорения сходимости.
- Затухание веса: термин регуляризации, который наказывает большие веса для предотвращения переобучения.
- Dropout: метод регуляризации, случайным образом удаляющий часть единиц нейронной сети во время обучения.
- Функция активации: нелинейная функция, применяемая к выходным данным слоя нейронной сети.
- Размер скрытого слоя: количество нейронов в каждом скрытом слое нейронной сети.
- kernel_size, шаг и отступы
Эти гиперпараметры существенно влияют на производительность модели и обучение в PyTorch. Правильная их настройка имеет решающее значение для достижения оптимальных результатов.
5.2 Баланс между переоснащением и недообучением
Ни один из рассмотренных выше методов не является серебряной пулей, то есть они не всегда работают.
И предотвращение переобучения и недообучения, возможно, является наиболее активной областью исследований в области машинного обучения.
Поскольку все хотят, чтобы их модели подходили лучше (меньше недообучения), но не настолько хорошо, они плохо обобщают и работают в реальном мире (меньше переобучения).
Между переоснащением и недообучением существует тонкая грань.
Потому что слишком много одного может вызвать другое.
Трансферное обучение, возможно, является одним из самых мощных методов, когда речь идет о том, чтобы справиться как с переоснащением, так и с недостаточным приспособлением к вашим собственным проблемам.
6. Model_V1 (улучшение модели)
Теперь, когда мы получили результаты нашей базовой модели и выявили переоснащение, мы планируем улучшить нашу модель (model_V1), включив тривиальные методы расширения.
6.1 Тривиальная аугментация
Увеличение данных — это метод, используемый для увеличения размера и разнообразия обучающего набора данных. Он включает в себя применение преобразований к существующим данным и создание новых образцов, которые похожи, но немного отличаются. Это помогает повысить производительность и обобщить модели машинного обучения, предоставляя им более широкий и разнообразный набор примеров. Общие преобразования включают геометрические изменения, корректировку цвета, искажение и рандомизацию. Увеличение данных применяется во время обучения, чтобы предотвратить переоснащение и повысить способность модели обрабатывать реальные изменения.
Внедряя эту модификацию, мы стремимся усложнить понимание данных моделью, одновременно снижая риск переобучения.
data_transform_train_V1 = transforms.Compose([
# resize to 64x64 just like in tinyVGG architecture
transforms.Resize(size=(64,64)),
# flip the images randomly on the horizontal
transforms.RandomHorizontalFlip(p=0.5),
# trivial augmentation
transforms.TrivialAugmentWide(num_magnitude_bins=5),
# turn image into a torch.Tensor
transforms.ToTensor()
])
data_transform_test_V1 = transforms.Compose([
# resize to 64x64 just like in tinyVGG architecture
transforms.Resize(size=(64,64)),
# turn image into a torch.Tensor
transforms.ToTensor()
])
# use data_transform_train_V1 here
train_data_V1 = datasets.ImageFolder(root=train_dir, # target folder of images
transform=data_transform_train_V1, # transforms to perform on data (images)
target_transform=None) # transforms to perform on labels (if necessary)
# use data_transform_test_V1 here
test_data_V1 = datasets.ImageFolder(root=test_dir,
transform=data_transform_test_V1)
BATCHSIZE = 32
NUM_WORKERS = multiprocessing.cpu_count()
train_dataloader_V1 = DataLoader(dataset=train_data_V1,
batch_size=BATCHSIZE, # how many samples per batch?
num_workers=NUM_WORKERS, # how many subprocesses to use for data loading? (higher = more)
shuffle=True) # shuffle the data?
test_dataloader_V1 = DataLoader(dataset=test_data_V1,
batch_size=BATCHSIZE,
num_workers=NUM_WORKERS,
shuffle=False) # don't usually need to shuffle testing data
# train_dataloader_V1, test_dataloader_V1
model_V1 = tinyVGG(
input_shape=3,
hidden_units=10,
output_shape=len(train_data_V1.classes))
### START TRAINING ###
# Set random seeds
torch.manual_seed(42)
NUM_EPOCHS = 10
from timeit import default_timer as timer
start_time = timer()
# Train model_1
modelOne useful function is ImageFolder, particularly when working with images in a standard image classification formresults = train(model=model_V1,
train_dataloader=train_dataloader_V1,
test_dataloader=test_dataloader_V1,
optimizer=optimizer,
loss_fn=loss_fn,
epochs=NUM_EPOCHS)
# End the timer and print out how long it took
end_time = timer()
print(f"Total training time: {end_time-start_time:.3f} seconds")
output:
100%
10/10 [00:53<00:00, 4.52s/it]
Epoch: 1 | train_loss: 1.3873 | train_acc: 0.2500 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 2 | train_loss: 1.3887 | train_acc: 0.2396 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 3 | train_loss: 1.3893 | train_acc: 0.2396 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 4 | train_loss: 1.3869 | train_acc: 0.2552 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 5 | train_loss: 1.3877 | train_acc: 0.2500 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 6 | train_loss: 1.3886 | train_acc: 0.2396 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 7 | train_loss: 1.3879 | train_acc: 0.2500 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 8 | train_loss: 1.3873 | train_acc: 0.2500 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 9 | train_loss: 1.3866 | train_acc: 0.2604 | test_loss: 1.3707 | test_acc: 0.4141
Epoch: 10 | train_loss: 1.3867 | train_acc: 0.2552 | test_loss: 1.3707 | test_acc: 0.4141
Total training time: 53.044 seconds
Визуализация
import pandas as pd model_0_df = pd.DataFrame(model_0_results) modelOne useful function is ImageFolder, particularly when working with images in a standard image classification formdf = pd.DataFrame(modelOne useful function is ImageFolder, particularly when working with images in a standard image classification formresults) plot_model_performance(model_0_df, modelOne useful function is ImageFolder, particularly when working with images in a standard image classification formdf)

Результаты model_V1 с бином TrivialAugmentWide, равным 5, показывают, что он изо всех сил пытается учиться на данных обучения, но хорошо работает на данных тестирования. Это говорит о том, что модель эффективно уменьшила переоснащение, поскольку лучше обобщает невидимые данные.
6.2 Матрица путаницы
# from torchmetrics import ConfusionMatrix
# from mlxtend.plotting import plot_confusion_matrix
from tqdm.auto import tqdm
def conft_matrix(model: torch.nn.Module,
dataset: torch.utils.data.dataset.Dataset,
dataloader: torch.utils.data.DataLoader,
):
y_preds = []
model.eval()
with torch.inference_mode():
# evaluate per batch using test_dataloader
for X_test, y_test in tqdm(dataloader, desc='Making predictions...'):
y_logit = model(X_test)
y_preds.append(torch.softmax(y_logit, dim=0).argmax(dim=1))
# convert into i demensional tensor
y_preds_tensor = torch.concat(y_preds)
# retreive the truth label
y_true_tensor = torch.tensor(dataset.targets)
# create confusion matrix
cm = ConfusionMatrix(task='multiclass', num_classes=len(class_names))
cm = cm(y_preds_tensor, y_true_tensor)
# Convert the confusion matrix to a NumPy array
cm = cm.numpy()
# Plot the confusion matrix
fig, ax = plot_confusion_matrix(conf_mat=cm, colorbar=True)
# Set the tick labels on the x and y axes
tick_marks = np.arange(len(class_names))
plt.xticks(tick_marks, class_names)
plt.yticks(tick_marks, class_names)
# Show the plot
return y_preds_tensor,y_true_tensor
Confusion_Matrix_V1 = conft_matrix(model_V1,
test_data_V1,
test_dataloader_V1)

y_pred, y_true = Confusion_Matrix_V1
y_pred, y_true
output:
(tensor([1, 2, 3, 0, 2, 2, 0, 1, 0, 3, 0, 2, 3, 3, 2, 3, 0, 3, 2, 1, 1, 0, 0, 2,
0, 0, 1, 2, 2, 3, 1, 2, 1, 3, 0, 3, 3, 1, 3, 0, 2, 0, 2, 2, 0, 1, 3, 0,
1, 0, 2, 3, 3, 0, 1, 1, 2, 2, 1, 2, 3, 3, 1, 0, 1, 3, 3, 1, 3, 3, 1, 2,
2, 3, 0, 0, 2, 0, 0, 3, 2, 3, 0, 0, 1, 2, 1, 1, 2, 2, 2, 0, 3, 3, 2, 3,
0, 1, 2, 3]),
tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3]))
Интерпретация:
- Из 100 выборок в test_data наша модель сделала 26 правильных прогнозов.
- В частности, он точно предсказал 8 из 25 кексов.
- Стоит сосредоточиться на случаях, когда модель делала существенные неверные прогнозы. Например, он неправильно пометил 9 образцов как мороженое, хотя их истинные этикетки были картофелем фри. Это может быть связано с изображениями, которые содержат как мороженое, так и картофель фри, неправильно помеченными образцами дизайна или другими факторами.
7. Сделайте прогноз на пользовательском изображении
Нам нужно будет преобразовать его в тот же формат, что и данные, на которых обучается наша модель, за исключением аугментации и случайного переворота.
Скопируйте файл изображения с моего диска Google
copy('/content/drive/MyDrive/Deep_learning/my_donut.jpg',
'/content/data')
# Print a success message
print("File copied successfully!")
Визуализируйте изображение
img = Image.open('/content/data/my_donut.jpg')
img

Настройка пользовательского пути к изображению
custom_image_path = data_path / "my_donut.jpg"
custom_image_path
output:
PosixPath('data/my_donut.jpg')
Мы будем читать наше изображение, используя torchvision.io.read_image().
Этот метод считывает изображение в формате JPEG или PNG и превращает его в трехмерный RGB или полутоновый torch.Tensor со значениями типа данных uint8 в диапазоне [0, 255].
import torchvision
# Read in custom image
custom_image_uint8 = torchvision.io.read_image(str(custom_image_path))
# Print out image data
print(f"Custom image tensor:\n{custom_image_uint8}\n")
print(f"Custom image shape: {custom_image_uint8.shape}\n")
print(f"Custom image dtype: {custom_image_uint8.dtype}")
output:
Custom image tensor:
tensor([[[213, 214, 214, ..., 250, 250, 250],
[213, 214, 214, ..., 250, 250, 250],
[213, 214, 214, ..., 250, 250, 250],
...,
[ 38, 37, 37, ..., 24, 12, 44],
[ 39, 38, 38, ..., 33, 14, 37],
[ 41, 40, 39, ..., 49, 27, 43]],
[[181, 182, 182, ..., 244, 244, 244],
[181, 182, 182, ..., 244, 244, 244],
[181, 182, 182, ..., 244, 244, 244],
...,
[ 20, 19, 19, ..., 10, 0, 30],
[ 21, 20, 20, ..., 15, 0, 19],
[ 23, 22, 21, ..., 29, 7, 23]],
[[168, 169, 169, ..., 244, 244, 244],
[168, 169, 169, ..., 244, 244, 244],
[168, 169, 169, ..., 244, 244, 244],
...,
[ 6, 5, 5, ..., 0, 0, 17],
[ 7, 6, 6, ..., 3, 0, 9],
[ 9, 8, 7, ..., 18, 0, 14]]], dtype=torch.uint8)
Custom image shape: torch.Size([3, 1020, 680])
Custom image dtype: torch.uint8
Наш тензор custom_image имеет тип данных torch.uint8, и его значения находятся в диапазоне [0, 255].
Но наша модель использует тензоры изображений типа torch.float32 со значениями между [0, 1].
Поэтому, прежде чем мы будем использовать наше собственное изображение с нашей моделью, нам нужно преобразовать его в тот же формат, что и данные, на которых обучается наша модель.
# Load in custom image and convert the tensor values to float32
custom_image = torchvision.io.read_image(str(custom_image_path)).type(torch.float32)
# Divide the image pixel values by 255 to get them between [0, 1]
custom_image = custom_image / 255.
# Print out image data
print(f"Custom image tensor:\n{custom_image}\n")
print(f"Custom image shape: {custom_image.shape}\n")
print(f"Custom image dtype: {custom_image.dtype}")
output:
Custom image tensor:
tensor([[[0.8353, 0.8392, 0.8392, ..., 0.9804, 0.9804, 0.9804],
[0.8353, 0.8392, 0.8392, ..., 0.9804, 0.9804, 0.9804],
[0.8353, 0.8392, 0.8392, ..., 0.9804, 0.9804, 0.9804],
...,
[0.1490, 0.1451, 0.1451, ..., 0.0941, 0.0471, 0.1725],
[0.1529, 0.1490, 0.1490, ..., 0.1294, 0.0549, 0.1451],
[0.1608, 0.1569, 0.1529, ..., 0.1922, 0.1059, 0.1686]],
[[0.7098, 0.7137, 0.7137, ..., 0.9569, 0.9569, 0.9569],
[0.7098, 0.7137, 0.7137, ..., 0.9569, 0.9569, 0.9569],
[0.7098, 0.7137, 0.7137, ..., 0.9569, 0.9569, 0.9569],
...,
[0.0784, 0.0745, 0.0745, ..., 0.0392, 0.0000, 0.1176],
[0.0824, 0.0784, 0.0784, ..., 0.0588, 0.0000, 0.0745],
[0.0902, 0.0863, 0.0824, ..., 0.1137, 0.0275, 0.0902]],
[[0.6588, 0.6627, 0.6627, ..., 0.9569, 0.9569, 0.9569],
[0.6588, 0.6627, 0.6627, ..., 0.9569, 0.9569, 0.9569],
[0.6588, 0.6627, 0.6627, ..., 0.9569, 0.9569, 0.9569],
...,
[0.0235, 0.0196, 0.0196, ..., 0.0000, 0.0000, 0.0667],
[0.0275, 0.0235, 0.0235, ..., 0.0118, 0.0000, 0.0353],
[0.0353, 0.0314, 0.0275, ..., 0.0706, 0.0000, 0.0549]]])
Custom image shape: torch.Size([3, 1020, 680])
Custom image dtype: torch.float32
Преобразуйте наше пользовательское изображение в 64x64.
# Create transform pipleine to resize image
custom_image_transform = transforms.Compose([
transforms.Resize((64, 64)),
])
# Transform target image to 61 x 64 and add batch size of 1 in index 0
custom_image_transformed = custom_image_transform(custom_image).unsqueeze(dim=0)
# add batch size to the shape
custom_image_transformed.shape
Прогноз
Наш вывод отслеживал градиентный спуск, который немного замедлил результаты. Мы планируем преобразовать этот код в функцию и перевести его в режим оценки (eval_mode) позже. Это позволит нам быстрее выполнять прогнозирование пользовательских изображений.
custom_image_pred = model_V1(custom_image_transformed) # results will be in logits form (raw output) custom_image_pred output: tensor([[-0.0307, -0.0029, 0.0206, 0.1004]], grad_fn=<AddmmBackward0>)
Преобразование из логитов -> вероятности прогноза -> метки прогноза.
# Print out prediction logits
print(f"Prediction logits: {custom_image_pred}")
# Convert logits -> prediction probabilities (using torch.softmax() for multi-class classification)
custom_image_pred_probs = torch.softmax(custom_image_pred, dim=1)
print(f"Prediction probabilities: {custom_image_pred_probs}")
# Convert prediction probabilities -> prediction labels
custom_image_pred_label = torch.argmax(custom_image_pred_probs, dim=1)
print(f'Class Distionary: {class_dict}')
print(f"Prediction label: {class_names[custom_image_pred_label]}")
output:
Prediction logits: tensor([[-0.0307, -0.0029, 0.0206, 0.1004]], grad_fn=<AddmmBackward0>)
Prediction probabilities: tensor([[0.2369, 0.2436, 0.2494, 0.2701]], grad_fn=<SoftmaxBackward0>)
Class Distionary: {'cup_cakes': 0, 'donuts': 1, 'french_fries': 2, 'ice_cream': 3}
Prediction label: ice_cream
Наша модель model_V1 ошибочно предсказывает пончик для ice_cream с очень низкой вероятностью. Проверяя custom_image_pred_probs, мы замечаем, что модель дает почти одинаковый вес (значения похожи) каждому классу.
Подобная вероятность предсказания может означать несколько вещей:
- Модель пытается предсказать все четыре класса одновременно (может быть изображение, содержащее все классы).
- Модель на самом деле не знает, что она хочет предсказать, и, в свою очередь, просто присваивает одинаковые значения каждому из классов.
Наш случай номер 2, так как наша модель плохо обучена, она в основном угадывает предсказание.
Функция прогнозирования
# Call out our helper function
pred_and_plot_image(model_V1,
custom_image_path,
class_names,
custom_image_transform)

Вкратце, процесс включает в себя следующие этапы:
1. Загрузка набора данных food101 с Kaggle и выполнение необходимой подготовки и исследования.
2. Преобразование набора данных в наборы для обучения и тестирования с помощью модуля torchvision.transforms для применения различных преобразований изображений и torch.utils.data.Dataset.ImageFolder для организации набора данных. Эти методы помогают предварительно обрабатывать и структурировать данные для обучения и оценки.
3. Создание пакетов данных с использованием класса torch.utils.data.DataLoader для облегчения эффективной загрузки и обработки данных во время обучение.
4. Разработка базовой модели в качестве отправной точки для создания более продвинутых и оптимизированных моделей нейронных сетей.
5. Решение проблем переобучения путем реализации тривиальных методов расширения.
6. Сравнение производительности базовой модели с улучшенной моделью с использованием Matplotlib для визуализации и анализа результатов.
7. Использование матрицы путаницы для более глубокого понимания производительности модели и потенциальные области для улучшения.
8. Прогнозирование пользовательских изображений с использованием обученной модели.
Эти этапы охватывают процесс работы с набором данных food101, подготовку данных, создание моделей, устранение переобучения, оценку производительности и прогнозирование.
Спасибо, что нашли время, чтобы прочитать мою статью. Надеюсь, вы нашли его познавательным и наводящим на размышления. Если вы хотите оставаться на связи и получать новости о моих будущих статьях, рассмотрите возможность подписаться на мою учетную запись. Кроме того, пожалуйста, не стесняйтесь, пишите мне сюда для любых запросов или дальнейших обсуждений. Я ценю вашу поддержку и с нетерпением жду возможности пообщаться с вами. Следите за следующей записью в блоге «Переход к модульному стилю».
А пока не стесняйтесь обращаться ко мне по адресу [email protected]
Ознакомьтесь с полным кодом этой статьи здесь: Github
Помощник по функциям можно увидеть здесь: Помощник по функциям