Практическое глубокое обучение 6.1. Реализация операций 2D-кроссовера и сверточных слоев

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

Примите участие в 22-м дне ноябрьского испытания обновлений и узнайте подробности события:Вызов последнего обновления 2021 г.


Просто глядя на сухой код точно не поймешь, что я делаю.Рекомендуется кушать с теорией.

Практическое глубокое обучение 6.1 Зачем нужны слои свертки | Вывод формулы свертки - самородки (juejin.cn)

2D крест

import torch
from torch import nn
from d2l import torch as d2l
# 实现二维交叉运算
def corr2d(x,k):
    h,w = k.shape
    Y = torch.zeros((x.shape[0]-h+1,x.shape[1]-w+1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i,j] = (x[i:i+h,j:j+w]*k).sum()
    return Y

Реализация - это формула:

hi,j=a,bva,bxi+a,j+bh_{i, j}=\sum_{a, b} v_{a, b} x_{i+a, j+b}
# 验证一下我们的二维交叉运算

X = torch.arange(40).reshape(5,8)
K = torch.ones(3,3)
K[:,1] = -1

print(X)
print(K)
>>
tensor([[ 0,  1,  2,  3,  4,  5,  6,  7],
        [ 8,  9, 10, 11, 12, 13, 14, 15],
        [16, 17, 18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29, 30, 31],
        [32, 33, 34, 35, 36, 37, 38, 39]])
tensor([[ 1., -1.,  1.],
        [ 1., -1.,  1.],
        [ 1., -1.,  1.]])

Используя те же данные, что и в предыдущей статье, вручную создайте тестовые данные для расчета. Взгляните на наше сгенерированное «изображение» и «ядро свертки».

# 验证一下我们的二维交叉运算

X = torch.arange(40).reshape(5,8)
K = torch.ones(3,3)
K[:,1] = -1

print(X)
print(K)
>>
tensor([[ 0,  1,  2,  3,  4,  5,  6,  7],
        [ 8,  9, 10, 11, 12, 13, 14, 15],
        [16, 17, 18, 19, 20, 21, 22, 23],
        [24, 25, 26, 27, 28, 29, 30, 31],
        [32, 33, 34, 35, 36, 37, 38, 39]])
tensor([[ 1., -1.,  1.],
        [ 1., -1.,  1.],
        [ 1., -1.,  1.]])
>>
tensor([[27., 30., 33., 36., 39., 42.],
        [51., 54., 57., 60., 63., 66.],
        [75., 78., 81., 84., 87., 90.]])

Проверьте результат.

[0128910161718][111111111]Элементы умножаются по положению=[0128918161718]\left[\begin{array}{ccc}0 & 1 & 2 \\ 8 & 9 & 10 \\ 16 & 17 & 18\end{array}\right]\left[\begin{array}{ccc}1 & -1 & 1 \\ 1 & -1 & 1 \\ 1 & -1 & 1\end{array}\right] элемент по позиции =\left[\begin{array}{ccc}0 & -1 & 2 \\ 8 & -9 & 18 \\ 16 & -17 & 18\end{массив}\right]

[0128918161718].sum()=27\left[\begin{array}{ccc}0 & -1 & 2 \\ 8 & -9 & 18 \\ 16 & -17 & 18\end{array}\right]. sum()=27

свертка

# 实现一个卷积
class Conv2D(nn.Module):
    def __init__(self,kernel_size):
        super().__init__()
        self.weight = nn.Parameter(torch.rand(kernel_size))
        self.bias = nn.Parameter(torch.zeros(1))
    def forward(self,x):
        return corr2d(x,self.weight) + self.bias

Сгенерируйте тестовые данные:

X = torch.ones(5,8)
X[:,2:6]=0
print(X)
>>
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.]])

После вывода X выглядит так, делая вид, что он картинка:

image.png


K = torch.tensor([[1,-1]])
# 检测图像中垂直边缘:
Y = corr2d(X,K)
print(Y)
>>
tensor([[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.]])

Посмотрите на вывод, чтобы узнать, для чего предназначено это ядро ​​свертки K, обнаружение вертикального края.

Из результатов видно, что изменение произошло от второго столбца к третьему столбцу исходного изображения, и изменение произошло от третьего к последнему столбцу к предпоследнему столбцу.

Зачем?

Поскольку ядро ​​свертки равно 1*2, второй столбец и предпоследний столбец результата вычисления генерируются, когда выбрасываются две позиции на следующем рисунке. Две позиции расчета — это второй-третий столбец и третий-последний столбец исходного изображения.

image.png

Теперь давайте протестируем сверточный слой.

код 1

Это исходный код из книги г-на Ли Му:

# 用一下pytorch的卷积层:
conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

X = X.reshape((1,1,5,8))
Y = Y.reshape((1,1,5,7))

epoch = 20
learning_rate = 0.03

for i in range(epoch):
    Y_hat = conv2d(X)
    l = ((Y_hat-Y)**2).sum()
    conv2d.zero_grad()
    l.backward()
    conv2d.weight.data[:] -= learning_rate * conv2d.weight.grad
    if (i+1)%5 == 0:
        print(f"batch {i+1},loss = {l.sum():.5f}")
        
print(conv2d.weight.data.reshape(1,2))
>>
batch 5,loss = 0.30810
batch 10,loss = 0.00828
batch 15,loss = 0.00023
batch 20,loss = 0.00001

Видно, что наши потери в это время уже очень низки.

Приведенный выше код реализован с использованием собственной двумерной свертки pytorch.

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

После этого от руки прописывается градиентный спуск.

print(conv2d.weight.data.reshape(1,2))
>>
tensor([[ 0.9994, -0.9994]])

Глядя на результаты после обучения, они очень близки к [1,-1].

код 2

# 用一下pytorch的卷积层:
conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)

X = X.reshape((1,1,5,8))
Y = Y.reshape((1,1,5,7))
loss = nn.MSELoss(reduction='sum')
trainer = torch.optim.SGD(conv2d.parameters(),learning_rate)

epoch = 20
learning_rate = 0.03

for i in range(epoch):
    Y_hat = conv2d(X)
    l = loss(Y_hat,Y)
    trainer.zero_grad()
    l.backward()
#     conv2d.weight.data[:] -= learning_rate*conv2d.weight.grad
    trainer.step()
    if (i+1)%5 == 0:
        print(f"batch {i+1},loss = {l:.5f}")
        

print(conv2d.weight.data.reshape(1,2))

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

Уведомлениеloss = nn.MSELoss(reduction='sum')Не забудьте изменить параметр сокращения на сумму, потому что по умолчанию возвращается среднее значение.Если вы используете среднее значение, для достижения того же эффекта за 20 шагов необходимо изменить значение параметра обучения примерно до 0,8.

код 3

# 再用一下我们自己写的卷积层
conv2d = Conv2D(kernel_size=(1,2))

epoch = 20
learning_rate = 0.03

for i in range(epoch):
    Y_hat = conv2d(X)
    l = (Y_hat-Y)**2
    conv2d.zero_grad()
    l.sum().backward()
    conv2d.weight.data[:] -= learning_rate * conv2d.weight.grad
    if (i+1)%5 == 0:
        print(f"batch {i+1},loss = {l.sum():.5f}")
        

print(conv2d.weight.data.reshape(1,2))
>>
batch 5,loss = 0.66233
batch 10,loss = 0.01583
batch 15,loss = 0.00044
batch 20,loss = 0.00001
tensor([[ 0.9992, -0.9992]])

Конечно, только потому, что мы сами написали сверточный слой, мы использовали его здесь. На самом деле все же быстрее использовать чужие.


  1. Подробнее о серии «Практическое глубокое обучение» см. здесь:Колонка «Практическое глубокое обучение» (juejin.cn)

  2. Примечания Адрес Github:DeepLearningNotes/d2l(github.com)

Все еще в процессе обновления......