Обучение перспетрона#
Обратное распространение ошибки#
Пусть имеем какую-то задачу регрессии. Т.е. какой-то конечный набор \(\left\{\left(x_i, y_i\right)\right\}_{i=1}^N\), функциональную зависимость которого нам надо примерно определить.
Т.к. работаем с нейронными сетями, то и пример будет кондовый, хоть и классический: \(x_i\) - матричка \(28\times28\), которая есть картинка какой-то цифры, а \(y_i\) - это та самая цифра, изображенная на соответсвующей картинке(матрице).
Нужно создать нейронную сеть, обучить её по заданному конечному набору и проверить, что она хорошо справляется с определением цифры по картинке для матричек не из обучающего набора.
Естественно, нам пригодятся все знания из темы регрессия. Т.е. надо определить функцию ошибки Loss, которую мы и хотим минимизировать градиентным спуском по весам модели.
Основная фишка нейронных сетей - это «аля» линейность по весам. Благодаря этому факту можно относительно несложно написать градиент в зависимости от ошибки выхода нейронных сетей - этот алгоритм называется обратное распространение ошибки (backpropagation). Подробнее о нём можно почитать тут: neerc.ifmo.ru/wiki/index.php?title=Обратное_распространение_ошибки
Dropout#
Это такая техника предотвращения переобучения и выучивания обучающей выборки.
Во время обучения случайным образом обнуляются некоторые элементы входного тензора с вероятностью \(p\), все остальные элементы при этом домнажаются на \(\frac{1}{1-p}\).
Это оказалось эффективным методом регуляризации и предотвращения совместной адаптации нейронов, как описано в статье https://arxiv.org/abs/1207.0580
Стохастический градиентный спуск#
Ествественно, надо его вспомнить. Не будем же мы всю обучающую выборку за раз сувать в вычисление градиента?
Напоминаю, что мы «нарезаем» обучающую выборку на батчи размера batch_size, а дальше делаем градиентный спуск только по нарезанным кусочкам поочередно. Как только прошли всё, считаем, что закончилась ЭпОхА.
Распознование рукописных цифр. Датасет MNIST. Реализация на torch#
torch - библиотека для удобного обращения с тензорами и нейросетями в python. Поддерживает GPU. Рекоммендую выполнять дальнейший код на коллабе, если вам жалко 5ГБ.
Загрузка датасета#
# It's better to use google collab for next code.
import torch # 5 GB! Plus we'll need GPU later (probably)
import numpy as np
from sklearn.model_selection import train_test_split
from torchvision import datasets
import torchvision.transforms as transforms
# number of subprocesses to use for data loading
num_workers = 0
# how many samples per batch to load
batch_size = 16
# convert data to torch.FloatTensor
transform = transforms.ToTensor()
# choose the training and test datasets
train_data = datasets.MNIST(root='data', train=True,
download=True, transform=transform)
test_data = datasets.MNIST(root='data', train=False,
download=True, transform=transform)
# prepare data loaders for stochastic gradient descent
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size,
num_workers=num_workers)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=batch_size,
num_workers=num_workers)
Беглый осмотр датасета#
import matplotlib.pyplot as plt
#%matplotlib inline
# obtain one batch of training images
dataiter = iter(train_loader)
images, labels = next(dataiter)
images = images.numpy()
# plot the images in the batch, along with the corresponding labels
fig = plt.figure(figsize=(25, 4))
for idx in np.arange(16):
ax = fig.add_subplot(2, 16//2, idx+1, xticks=[], yticks=[])
ax.imshow(np.squeeze(images[idx]), cmap='gray')
# print out the correct label for each image
# .item() gets the value contained in a Tensor
ax.set_title(str(labels[idx].item()))
img = np.squeeze(images[1])
fig = plt.figure(figsize = (12,12))
ax = fig.add_subplot(111)
ax.imshow(img, cmap='gray')
width, height = img.shape
thresh = img.max()/2.5
for x in range(width):
for y in range(height):
val = round(img[x][y],2) if img[x][y] !=0 else 0
ax.annotate(str(val), xy=(y,x),
horizontalalignment='center',
verticalalignment='center',
color='white' if img[x][y]<thresh else 'black')
Определение модели#
import torch.nn as nn
import torch.nn.functional as F
## TODO: Define the NN architecture
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# linear layer (784 -> 1 hidden node)
self.fc1 = nn.Linear(28 * 28, 512)
self.fc2 = nn.Linear(512, 512)
self.fc3 = nn.Linear(512, 10)
self.dropout = nn.Dropout(p=0.2) # google it
def forward(self, x):
# flatten image input
x = x.view(-1, 28 * 28)
# add hidden layer, with relu activation function
x = self.dropout(F.relu(self.fc1(x)))
x = self.dropout(F.relu(self.fc2(x)))
x = self.fc3(x)
return x
# initialize the NN
model = Net()
print(model)
Net(
(fc1): Linear(in_features=784, out_features=512, bias=True)
(fc2): Linear(in_features=512, out_features=512, bias=True)
(fc3): Linear(in_features=512, out_features=10, bias=True)
(dropout): Dropout(p=0.2, inplace=False)
)
Обучение#
Шаги для обучения также описаны в комментариях ниже:
Очистить градиенты всех оптимизированных переменных: torch за вас считает все градиенты и сохраняет их, в начале каждого шага обучения их необходимо обнулить
Передача вперед: вычисление прогноза модели на текущем батче
Вычислить ошибку
Обратный проход (backpropogation): вычислить градиент потерь для текущих весов модели
Выполните один шаг оптимизации (обновление весов)
Обновление средней ошибки на батче
Ну и дальше надо взять сколько-то эпох. У меня хватило 5 для хорошего результата. В дз вам надо будет добиться точности 99%, варьируя параметры модели и количество эпох.
## TODO: Specify loss and optimization functions
# specify loss function
criterion = nn.CrossEntropyLoss()
# specify optimizer
optimizer = torch.optim.SGD(model.parameters(), lr=0.05) # Try lr=0.05 and n_epochs=1
# number of epochs to train the model
n_epochs = 1 # suggest training between 20-50 epochs
model.train() # prep model for training (enables dropout)
from tqdm import tqdm
for epoch in range(n_epochs):
# monitor training loss
train_loss = 0.0
###################
# train the model #
###################
for data, target in tqdm(train_loader):
# clear the gradients of all optimized variables
optimizer.zero_grad()
# forward pass: compute predicted outputs by passing inputs to the model
output = model(data)
# calculate the loss
loss = criterion(output, target)
# backward pass: compute gradient of the loss with respect to model parameters
loss.backward()
# perform a single optimization step (parameter update)
optimizer.step()
# update running training loss
train_loss += loss.item()*data.size(0)
# print training statistics
# calculate average loss over an epoch
train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(
epoch+1,
train_loss
))
3%|██▋ | 124/3750 [00:16<07:49, 7.73it/s]
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[6], line 30
28 loss = criterion(output, target)
29 # backward pass: compute gradient of the loss with respect to model parameters
---> 30 loss.backward()
31 # perform a single optimization step (parameter update)
32 optimizer.step()
File ~/anaconda3/envs/book/lib/python3.11/site-packages/torch/_tensor.py:492, in Tensor.backward(self, gradient, retain_graph, create_graph, inputs)
482 if has_torch_function_unary(self):
483 return handle_torch_function(
484 Tensor.backward,
485 (self,),
(...)
490 inputs=inputs,
491 )
--> 492 torch.autograd.backward(
493 self, gradient, retain_graph, create_graph, inputs=inputs
494 )
File ~/anaconda3/envs/book/lib/python3.11/site-packages/torch/autograd/__init__.py:251, in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)
246 retain_graph = create_graph
248 # The reason we repeat the same comment below is that
249 # some Python versions print out the first line of a multi-line function
250 # calls in the traceback and some print out the last line
--> 251 Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass
252 tensors,
253 grad_tensors_,
254 retain_graph,
255 create_graph,
256 inputs,
257 allow_unreachable=True,
258 accumulate_grad=True,
259 )
KeyboardInterrupt:
Тестирование обученной модели#
Наконец, мы тестируем нашу полученную модель на ранее невиданных тестовых данных и оцениваем ее производительность.
model.eval()
#
model.eval(
) переведет все слои в вашей модели в режим вычисления (а не обучения). Мы же не хотим dropout на тесте.
# initialize lists to monitor test loss and accuracy
test_loss = 0.0
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
model.eval() # prep model for *evaluation*
for data, target in test_loader:
# forward pass: compute predicted outputs by passing inputs to the model
output = model(data)
# calculate the loss
loss = criterion(output, target)
# update test loss
test_loss += loss.item()*data.size(0)
# convert output probabilities to predicted class
_, pred = torch.max(output, 1)
# compare predictions to true label
correct = np.squeeze(pred.eq(target.data.view_as(pred)))
# calculate test accuracy for each object class
for i in range(batch_size):
label = target.data[i]
class_correct[label] += correct[i].item()
class_total[label] += 1
# calculate and print avg test loss
test_loss = test_loss/len(test_loader.dataset)
print('Test Loss: {:.6f}\n'.format(test_loss))
for i in range(10):
if class_total[i] > 0:
print('Test Accuracy of %5s: %2d%% (%2d/%2d)' % (
str(i), 100 * class_correct[i] / class_total[i],
np.sum(class_correct[i]), np.sum(class_total[i])))
else:
print('Test Accuracy of %5s: N/A (no training examples)' % (classes[i]))
print('\nTest Accuracy (Overall): %2d%% (%2d/%2d)' % (
100. * np.sum(class_correct) / np.sum(class_total),
np.sum(class_correct), np.sum(class_total)))
Visualize Sample Test Results#
This cell displays test images and their labels in this format: predicted (ground-truth)
. The text will be green for accurately classified examples and red for incorrect predictions.
# obtain one batch of test images
dataiter = iter(test_loader)
images, labels = next(dataiter)
# get sample outputs
output = model(images)
# convert output probabilities to predicted class
_, preds = torch.max(output, 1)
# prep images for display
images = images.numpy()
# plot the images in the batch, along with predicted and true labels
fig = plt.figure(figsize=(25, 4))
for idx in np.arange(16):
ax = fig.add_subplot(2, 16//2, idx+1, xticks=[], yticks=[])
ax.imshow(np.squeeze(images[idx]), cmap='gray')
ax.set_title("{} ({})".format(str(preds[idx].item()), str(labels[idx].item())),
color=("green" if preds[idx]==labels[idx] else "red"))