Быстрый старт PyTorch(2) — как построить нейронную сеть

глубокое обучение PyTorch

Оригинальная ссылка:Tickets.WeChat.QQ.com/Yes/Q8 Тоже NX SDH6…

Вторая часть учебника Quick Start PyTorch, в котором описывается, как построить нейронную сеть. Предыдущая статья:

Содержание этой статьи:


3. Нейронные сети

в PyTorchtorch.nnВ частности, используется для реализации нейронных сетей. вnn.ModuleВключая построение сетевого уровня и метод...forward(input), и возвращает вывод сетиoutptu .

Ниже представлена ​​классическая сеть LeNet для классификации персонажей.

Для нейронной сети стандартный процесс обучения выглядит так:

  • Определить многослойную нейронную сеть
  • Предварительная обработка набора данных и подготовка его к вводу в сеть
  • Введите данные в сеть
  • Рассчитать потери в сети
  • Обратное распространение, вычисление градиентов
  • Чтобы обновить градиент сети, простое правило обновленияweight = weight - learning_rate * gradient

3.1 Определение сети

Сначала определите нейронную сеть, ниже представлена ​​5-слойная сверточная нейронная сеть, включающая два сверточных слоя и три полносвязных слоя:

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    
    def __init__(self):
        super(Net, self).__init__()
        # 输入图像是单通道,conv1 kenrnel size=5*5,输出通道 6
        self.conv1 = nn.Conv2d(1, 6, 5)
        # conv2 kernel size=5*5, 输出通道 16
        self.conv2 = nn.Conv2d(6, 16, 5)
        # 全连接层
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        
    def forward(self, x):
        # max-pooling 采用一个 (2,2) 的滑动窗口
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 核(kernel)大小是方形的话,可仅定义一个数字,如 (2,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):
        # 除了 batch 维度外的所有维度
        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автоматически определяется, когдаforwardМетод может принимать любую тензорную операцию.

net.parameters()Параметры обучения сети могут быть возвращены.Примеры использования следующие:

params = list(net.parameters())
print('参数数量: ', len(params))
# conv1.weight
print('第一个参数大小: ', params[0].size())

вывод:

参数数量:  10
第一个参数大小:  torch.Size([6, 1, 5, 5])

Затем просто протестируйте сеть и случайным образом сгенерируйте ввод 32*32:

# 随机定义一个变量输入网络
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

Выходной результат:

tensor([[ 0.1005,  0.0263,  0.0013, -0.1157, -0.1197, -0.0141,  0.1425, -0.0521,
          0.0689,  0.0220]], grad_fn=<ThAddmmBackward>)

Затем обратное распространение должно сначала очистить кеш градиента и выполнить обратное распространение стохастического градиента:

# 清空所有参数的梯度缓存,然后计算随机梯度进行反向传播
net.zero_grad()
out.backward(torch.randn(1, 10))

Уведомление:

torch.nnПоддерживает только **мини-пакеты** данных, то есть ввод не может быть одним образцом, например, дляnn.Conv2dПолученный вход представляет собой 4-мерный тензор --nSamples * nChannels * Height * Width.

Итак, если ваш ввод представляет собой один образец,нужно принятьinput.unsqueeze(0)увеличить размер поддельной партии, т. е. с 3-мерного на 4-мерный.

3.2 Функция потерь

Вход в функцию потерь(output, target), то есть данные пары выходных данных сети и истинной метки, а затем возвращает числовое значение, представляющее разрыв между выходными данными сети и истинной меткой.

PyTorch на самом деле определил множествофункция потерь, здесь используется только простая среднеквадратическая ошибка:nn.MSELoss, пример следующий:

output = net(input)
# 定义伪标签
target = torch.randn(10)
# 调整大小,使得和 output 一样的 size
target = target.view(1, -1)
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

Результат выглядит следующим образом:

tensor(0.6524, grad_fn=<MseLossBackward>)

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

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

если звонишьloss.backward(), то весь граф дифференцируем, т. е. включаяloss, все тензорные переменные в графе, если их свойстваrequires_grad=True, то его градиент.gradТензоры всегда накапливаются вместе с градиентом.

Чтобы проиллюстрировать код:

# MSELoss
print(loss.grad_fn)
# Linear layer
print(loss.grad_fn.next_functions[0][0])
# Relu
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])

вывод:

<MseLossBackward object at 0x0000019C0C349908>

<ThAddmmBackward object at 0x0000019C0C365A58>

<ExpandBackward object at 0x0000019C0C3659E8>

3.3 Обратное распространение

Для реализации обратного распространения нужно только вызватьloss.backward()То есть конечно сначала нужно очистить текущий кеш градиента, т.е..zero_grad()метода, иначе предыдущий градиент будет накапливаться до текущего градиента, что повлияет на обновление параметров веса.

Вот простой пример дляconv1Параметр смещения для слояbiasПример результатов до и после обратного распространения:

# 清空所有参数的梯度缓存
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.0069,  0.0021,  0.0090, -0.0060, -0.0008, -0.0073])

Узнать больше оtorch.nnбиблиотеку, вы можете просмотреть официальную документацию:

py torch.org/docs/stable…

3.4 Обновление весов

Простейшее правило обновления веса с использованием метода стохастического градиентного спуска (SGD) выглядит следующим образом:

weight = weight - learning_rate * gradient

Следуя этому правилу, реализация кода выглядит так:

# 简单实现权重的更新例子
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

Но это всего лишь самое простое правило, глубокое обучение имеет множество алгоритмов оптимизации, не толькоSGDNesterov-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()

Уведомление, тоже надо позвонитьoptimizer.zero_grad()метод очистки буфера градиента.

Учебник в этом разделе:

py torch.org/tutorials/ нет…

Код для этого подраздела:

GitHub.com/CCC013/глубокий…


резюме

Вторая часть в основном знакомит с построением нейронной сети, включая определение сети, выбор функции потерь, обратное распространение для вычисления градиента и обновление параметров веса.

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat — машинное обучение и компьютерное зрение, или отсканируйте QR-код ниже, давайте общаться, учиться и прогрессировать вместе!

Прекрасная рекомендация в прошлом

Серия машинного обучения
Рекомендация руководства по проектам и ресурсам Github