Нейронные сети могут быть построены с использованием пакета torch.nn
.
torch.nn
зависит от autograd
в определении моделей и их дифференцировании.
nn.Module
содержит слои и метод forward(input)
, который возвращает output
.
Например, посмотрите на эту сеть, которая классифицирует цифровые изображения:
Это простая сеть прямой связи. Она принимает вход, передает его через несколько слоев один за другим, а затем, наконец, возвращает вывод.
Типичная процедура обучения для нейронной сети следующая:
- Определение нейронной сети, которая имеет некоторые изучаемые параметры (или веса)
- Итерация по набору входных данных
- Обработка ввода через сеть
- Рассчет потери (насколько далеки результаты от правильных)
- Распространение градиентов обратно на параметры сети
- Обновление весов сети, как правило, используя простое правило обновления:
weight = weight - learning_rate * gradient
Определение сети
Давайте определим сеть:
# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def init(self):
super(Net, self).init()
# 1 канал ввода изображения,
# 6 каналов вывода,
# 5x5 квадратное сверточное ядро
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# аффинная операция: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# Максимальное объединение через (2, 2) окно
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# Если размер - это квадрат,
# тогда вы можете задать только одно число
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
# все измерения, исключая измерение пакета
size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
Вывод:
Net(
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
Вам просто нужно определить функцию forward
, а функция backward
(где вычисляются градиенты) автоматически определена для вас, используя autograd
. Вы можете использовать любую из операций Tensor в функции forward
.
Изучаемые параметры модели возвращаются net.parameters()
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
Вывод:
10
torch.Size([6, 1, 5, 5])
Попробуем случайный 32x32 ввод (Ожидаемый размер ввода для этой net(LeNet) - 32x32.) Для использования этой сети на MNIST наборе следует изменить размер изображений в наборе на 32x32.
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
Вывод:
tensor([[-0.0233, 0.0159, -0.0249, 0.1413,
0.0663, 0.0297, -0.0940, -0.0135,
0.1003, -0.0559]], grad_fn=<AddmmBackward>)
Обнулим градиентные буферы всех параметров и backprops со случайными градиентами:
net.zero_grad()
out.backward(torch.randn(1, 10))
Примечание
torch.nn
поддерживает только мини-пакеты. Весь torch.nn
пакет поддерживает только вводы, которые являются мини-пакетами примеров, а не отдельным примером.
Например, nn.Conv2d
будет принимать 4D тензор
nSamples x nChannels x Height x Width
.
Если у вас есть отдельный пример, просто используйте input.unsqueeze(0)
для того чтобы добавить поддельное измерение пакета.
Рассмотрим все классы, которые встрчались ранее.
torch.Tensor
- многомерный массив с поддержкой autograd операций какbackward()
. Также содержит градиенты тензора.nn.Module
- модуль нейронной сети. Удобный способ инкапсуляции параметров с вспомогательными интрументами для их перемещения в GPU, экспорта, загрузки и т. д.nn.Parameter
- это разновидность Tensor, который автоматически регистрируется как параметр при назначении в качестве атрибутаModule
.autograd.Function
- реализует прямое и обратное определения (forward and backward) autograd операции. КаждаяTensor
операция создает как минимум одинFunction
узел, который подключается к функциям, которые создалиTensor
и кодирует его историю.
На данный момент мы рассмотрели:
- Определение нейронной сети
- Обработка входных данных и обратный вызов
Еще осталось:
- Вычисление потери
- Обновление весов сети
Функция потери
Функция потери принимает (выходную, целевую) пару входных данных и вычисляет значение, определяющее, насколько далеко вывод от цели.
Существует несколько разных функций потери в пакете torch.nn. Простая потеря: nn.MSELoss
, которая вычисляет среднеквадратичную ошибку между вводом и целью.
Например:
output = net(input)
target = torch.randn(10) # случайный target, например
target = target.view(1, -1) # сделаем его такой же формы как вывод
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
Вывод:
tensor(1.3389, grad_fn=<MseLossBackward>)
Теперь, если вы следуете потере в обратном направлении, используя .grad_fn атрибут, вы увидите граф вычислений, который выглядит схожим образом:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
Таким образом, когда мы вызываем loss.backward(), весь граф дифференцирован в соотвествии с потерей, и все тензоры в графе, которые имеют requires_grad=True будут иметь их .grad тензор накопленный с градиентом.
Для иллюстрации проследуем по нескольким шагам в обратном направлении:
print(loss.grad_fn) # MSELoss
print(loss.grad_fn.next_functions[0][0]) # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU
Вывод:
<MseLossBackward object at 0x7fab77615278>
<AddmmBackward object at 0x7fab77615940>
<AccumulateGrad object at 0x7fab77615940>
Backprop (Обратное распространение)
Для обратного распространения ошибки все, что нам нужно сделать, это loss.backward()
. Вы должны очистить существующие градиенты, иначе градиенты будут
накапливается к существующим градиентам.
Теперь мы вызовем loss.backward()
и посмотрим на смещение conv1 градиентов до и после backward.
# обнуляем градиентные буферы всех параметров
net.zero_grad()
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
Вывод:
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0054, 0.0011, 0.0012, 0.0148, -0.0186, 0.0087])
Обновление весов
Самым простым правилом обновления, используемым на практике, является стохастический градиентный спуск (Stochastic Gradient Descent (SGD)):
weight = weight - learning_rate * gradient
Мы можем реализовать это с помощью простого Python кода:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
Однако, когда вы используете нейронные сети, вы хотите использовать различные правила обновления, такие как SGD, Nesterov-SGD, Adam, RMSProp и др. Чтобы включить их, существует небольшой пакет: torch.optim
, который реализует все эти методы. Использовать его очень просто:
import torch.optim as optim
# создайте ваш оптимизатор
optimizer = optim.SGD(net.parameters(), lr=0.01)
# в вашем тренировочном цикле:
optimizer.zero_grad() # обнуляем градиентные буфферы
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # Выполняем обновление
Читайте также другие статьи в блоге: