0 шт.
Выбрать страницу

Как обучается нейронная сеть?

Основные компоненты нейронной сети

Общая картина работы нейронной сети очень простая. Вы подаете данные на вход нейронной сети x и с каждой итерации подстраиваете выходные данные из модели под target. Например, выход из модели 0.9, а target 1, тогда лосс будет 0.1 и используя правило цепи и частные деривативы модель перестраивается так, чтобы на выходе у вас было 1.

Предлагаю разобрать конкретный пример нейронной сети и рассмотреть каждую ее часть по отдельности.

In [4]:
import torch

x = torch.randn([1, 100,3])
target = torch.randn([1, 100,64])

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = torch.nn.Linear(3,64)
    
    def forward(self, x):
        x = self.fc1(x)
        return x


model = Net()
criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0003)
for epoch in range(10):
    output = model(x)
    loss = criterion(output, target)
    print('Loss: ', loss.item())
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
Loss:  1.4574213027954102
Loss:  1.456715703010559
Loss:  1.4560108184814453
Loss:  1.4553066492080688
Loss:  1.4546031951904297
Loss:  1.4539005756378174
Loss:  1.4531985521316528
Loss:  1.4524974822998047
Loss:  1.4517971277236938
Loss:  1.4510974884033203
  1. Импортируем библиотеку torch.
import torch
  1. Определяем x и target.
x = torch.randn([1, 100,3])
target = torch.randn([1, 100,64])
  1. Декларируем модель. Для этого мы используем класс, а внутри класса в __init__ инстанциируем слои, которые мы будем использовать. В forward мы последовательно выполняем операции, используя инстансы слоев в __init__ и возможно другие функции, которые не требуют инстанциирования. Хочу обратить ваше внимание, что мы не делаем логику в __call__ как должно быть в соответствии с Python потому, что torch.nn.Module от которого мы унаследовали наш класс уже имеет __call__, который ссылается на forward.
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = torch.nn.Linear(3,64)

    def forward(self, x):
        x = self.fc1(x)
        return x
  1. Инстанциируем модель.
model = Net()
  1. Инстациируем функцию потерь. MSELoss - это самый просто лосс, формула которого (output-target) ** 2/n. MSELoss решает проблемы регрессии.
criterion = torch.nn.MSELoss()
  1. Инстанциируем оптимизатор. Adam является лучшим оптимизатором для многих задач. В классической теории вы, возможно, встречали SGD(Stochastic Gradient Descent), но Adam работает лучше.
optimizer = torch.optim.Adam(model.parameters(), lr=0.0003)
  1. Определяем тренировочный луп.
for epoch in range(10):
    output = model(x)
    loss = criterion(output, target)
    print('Loss: ', loss.item())
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
  1. Делаем прямое распространение. Прямое распространение - это когда мы пропускаем данные через модель. Данные идут через наш класс Net, в частности через torch.nn.Linear, который у нас в единственно числе. Формула torch.nn.Linear wx+b. Заметьте мы не используем функцию активации и много слоев для упрощения нашего примера.
    output = model(x)
  1. Вычисляем потери в соответствии с формулой (output-target) ** 2/n.
    loss = criterion(output, target)
  1. Обнуляем оптимизатор.
    optimizer.zero_grad()

У каждого слоя есть параметры. Прошу не путать с количеством параметров. Я имею в виду torch.nn.Parameter. Это специальный класс, которые отображает число или тензор, который оптимизируются. При обратном распространении оптимизируются именно torch.nn.Paramater, чтобы уменьшить лосс. Возьмем наш пример, в котором мы имеем torch.nn.Linear формула которого wx+b, гдe w и b являются torch.nn.Parameter и относительно данных переменных берутся частные деривативы. И w и b имеет атрибут grad, w.grad и b.grad, в которые сохраняются частные деривативы. Также хочу обратить внимание, что параметры регистрируются в модели и доступные через model.parameters(). optimizer.zero_grad() обнуляется w.grad и b.grad.

  1. Делаем обратное распространение.
    loss.backward()

loss - это переменная через, которую начинается обратное распространение. Это делается под капотом PyTorch. То есть, если мы проследим операции в обратном порядке, то мы получаем loss>>wx+b>>x, где w и b являются torch.nn.Parameter, относительно каждого параметра вычисляется частный дериватив и сохранятся в w.grad и b.grad. С точки зрения пользователя установка w.grad и b.grad - это, что делает loss.backward(). На самом деле происходит обычная ситуация для любого серьезного программного обеспечения, строится граф операций. В случае PyTorch строится направленный ациклический граф, и loss.backward перемещается по этому графу, делает traversal и устанавливает значения градиентов.

  1. Производим оптимизацию.
    optimizer.step()

Как мы видели в шаге 6, оптимизатор принимает model.parameters(), который содержит те самые w.grad и b.grad и оптимизирует их.

Для понимания, привожу ключевую часть исходного кода оптимизатора, который вы можете найти на сайте PyTorch:

p.data.addcdiv_(-step_size, exp_avg, denom)

p - это w или b. Возьмем w в качестве примера. Подставляя в формулу мы получаем w + (exp_avg/denom), exp_avg - это трансформированный w.grad. В SGD все гораздо проще.

Мы разберем каждый шаг в моем курсе, пройдем через каждую формулу, и вы до конца поймете весь процесс. Запишитесь на курс сегодня. Нейронные сети - это очень просто.

what_is_happening_when_a_neural_network_learns.png

Обучение

Понимание как работает нейронная сеть - это путь к профессионализму.

Записывайтесь на мой курс, где я расскажу все более детально и подробно, а главное простыми словами. Для меня нет глупых вопросов, для меня нет начинающих, для меня есть желающие познать и я помогаю им в этом.

In [ ]: