前向传播和反向传播

之前听过前向传播和反向传播,也看到项目中有这两个步骤,但是不知道具体是干什么的,在看PyTorch官方教程的时候看到一个用numpy实现的2层神经网络,里面实现了前向传播和反向传播,这里记录一下,同时也记录一下我的PyTorch学习过程。

Numpy

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
#coding:utf-8
import numpy as np

# N是batch大小;D_in是输入维度,H是隐藏维度;D_out是输出维度
N, D_in, H, D_out = 64, 1000, 100, 10

# 创建任意输入和输出数据
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)
# 随机初始化weight
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)

learning_rate = 1e-6
for t in range(500):
# 前向传播:计算预测的y
h = x.dot(w1) # x:64*1000 w1:1000*100 h:64*100
h_relu = np.maximum(h, 0) # 激活函数ReLU
y_pred = h_relu.dot(w2) # h_relu:64*100 w2:100*10 y_pred:64*10

# 计算打印loss
loss = np.square(y_pred - y).sum()
print t, loss

# 反向传播,根据loss计算w1和w2的gradient
grad_y_pred = 2.0 * (y_pred - y) # grad_y_pred:64*10
grad_w2 = h_relu.T.dot(grad_y_pred) # h_relu.T:100*64 grad_w2:100*10
grad_h_relu = grad_y_pred.dot(w2.T) # w2.T:10*100 grad_h_relu:64*100
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0 # grad_h:64*100
grad_w1 = x.T.dot(grad_h) # x.T:1000*64 grad_w1:1000*100
# 更新weight
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2

PyTorch:Tensor

numpy是一个很棒的框架,但是它没有利用GPU加速计算,对现代的深度神经网络,GPU经常提供50倍以上的加速,所以numpy不适用于现在的深度学习。
PyTorch里一个基本的概念是Tensor,他和numpy里的array概念上是一样的,一个Tensor就是一个n维数组,PyTorch提供了大量的函数来操作这些Tensor。和numpy不同的是,PyTorch的Tensor可以利用GPU来加速计算。
下面用PyTorch的Tensor来实现刚才的2层神经网络:

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
#coding:utf-8
import torch

dtype = torch.float
device = torch.device("cpu")
# dtype = torch.device("cuda:0") # 用GPU加速计算
# N是batch大小
N, D_in, H, D_out = 64, 1000, 100, 10
# 创建随机的输入和输出数据
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 随机初始化weight
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)

learning_rate = 1e-6
for t in range(500):
# 前向传播:计算预测的y
h = x.mm(w1)
h_relu = h.clamp(min=0) # 激活函数ReLU
y_pred = h_relu.mm(w2)

# 计算打印loss
loss = (y_pred - y).pow(2).sum().item()
print t, loss

# 反向传播
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
# 用梯度下降更新weight
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2

PyTorch:autograd

上面我们手动实现了反向传播,对于2层的神经网络还可以,但是对更大的复杂网络就会非常困难,PyTorch提供了自动微分(automatic differentiation)功能来自动计算神经网络中的反向传播。这个功能在PyTorch的autograd包中,当使用autograd,网络的前向传播会定义一个计算图,图中的节点为Tensor,边是从输入Tensor产生输出Tensor的函数。这个图中的反向传播可以让我们很容易地计算梯度。

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
#coding:utf-8
import torch

dtype = torch.float

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in, dtype=dtype)
y = torch.randn(N, D_out, dtype=dtype)

w1 = torch.randn(D_in, H, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
# 这里没有记录中间的隐藏层数据
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# loss是一个shape(1,)的Tensor
loss = (y_pred - y).pow(2).sum()
print t, loss.item()
# 用autograd计算反向传播,这里会根据所有设置了requires_grad=True的Tensor计算loss的梯度,
# w1.grad和w2.grad将会保存loss对于w1和w2的梯度
loss.backward()
# 手动更新weight,需要用torch.no_grad(),因为weight有required_grad=True,但我们不需要在
# autograd中跟踪这个操作
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 在更新weight后,手动将梯度归零
w1.grad.zero_()
w2.grad.zero_()

PyTorch:forward backward

PyTorch中我们可以定义自己的autograd操作符,定义torch.autograd.Function的子类,并实现forwardbackward函数,虽然还不是很明白,但是也先记录一下:

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
40
41
42
43
44
45
46
47
48
49
50
51
#coding:utf-8
import torch

class MyReLU(torch.autograd.Function):
"""
通过继承Function来实现自定义autograd函数
"""

@staticmethod
def forward(ctx, input):
"""
在前向传播中,我们接收一个Tensor包含输入,返回一个Tensor包含输出。ctx是一个上下文对象,
可以用来存放反向计算的信息,可以用ctx.save_for_backward方法缓存任意在反向传播中用到的对象
"""

ctx.save_for_backward(input) # 这里input是x.mm(w1)
return input.clamp(min=0)

@staticmethod
def backward(ctx, grad_output):
"""
反向传播中,我们接收一个Tensor包含loss对于输出的梯度,需要计算loss对于输入的梯度
"""

input, = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[input < 0] = 0
return grad_input

dtype = torch.float

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in, dtype=dtype)
y = torch.randn(N, D_out, dtype=dtype)

w1 = torch.randn(D_in, H, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
relu = MyReLU.apply

y_pred = relu(x.mm(w1)).mm(w2)
# loss是一个shape(1,)的Tensor
loss = (y_pred - y).pow(2).sum()
# print t, loss.item()
loss.backward()
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 在更新weight后,手动将梯度归零
w1.grad.zero_()
w2.grad.zero_()