神经网络
我们一般使用torch.nn
模块来构建一个神经网络。
1 | import torch |
1 | X, y = datasets.load_breast_cancer(return_X_y=True) |
Pytorch要求训练的数据必须是float类型,导入的X是np.float64也就是double类型。
1 | X = X.astype(np.float32) |
1 | X.shape |
torch.Size([569, 30])
1. 构建一个神经网络
1 | class MLPNet(nn.Module): |
1 | feature = X.shape[1] |
1 | output = net(X) |
1 | print(output[0:5, :]) |
tensor([[0.2746],
[0.2853],
[0.2849],
[0.2925],
[0.3158]], grad_fn=<SliceBackward>)
2. Loss 函数
1 | loss = nn.MSELoss() |
1 | lossVal = loss(output, y) |
tensor(0.2712, grad_fn=<MseLossBackward>)
3. 反向传播
实现了前向传播,有了损失函数,我们就可以进行反向传播了。其实很简单: 1
2
3
4net.zero_grad()
lossVal = loss(output, y)
lossVal.backward()
# 然后更新参数
要注意zero_grad
这一条,绝对不可以漏了。因为根据pytorch中的backward()函数的计算,当网络参量进行反馈时,梯度是被积累的而不是被替换掉;但是在每一个batch时毫无疑问并不需要将两个batch的梯度混合起来累积,因此这里就需要每个batch设置一遍zero_grad。
1 | net.zero_grad() |
1 | lossVal.backward() |
1 | print(net.hidLayer.bias.grad) |
tensor([ 0.0000e+00, 0.0000e+00, 0.0000e+00, 2.6967e-06, -8.7211e-05,
0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00,
0.0000e+00, 0.0000e+00, 8.8216e-06, 0.0000e+00, 0.0000e+00,
0.0000e+00, 0.0000e+00, 0.0000e+00, 4.9632e-12, 0.0000e+00,
-3.9861e-05, 3.5545e-04, 0.0000e+00, 0.0000e+00, -5.6574e-03])
4. 更新参数
有了grad,就可以更新参数了: \[
\theta = \theta - \alpha \cdot \frac{dJ}{d\theta}
\] 关于更新参数有很多种方法,最简单的当然是手动更新了: 1
2
3learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
但不建议这么做,在torch.optim
中实现了很多更新的算法如SGD, Nesterov-SGD, Adam, RMSProp等等。一般选择 Adam 是最好的。 使用的方法如下:
- 首先构建一个Optimizer对象:
opt = optim.XXX(net.parameters, lr=...)
- 在需要更新的时候调用
opt.step()
更新一次参数。
1 | import torch.optim as optim |
1 | # 更新参数 |
另外说明一下,opt有一个.zero_grad
方法,它的效用和net.zero_grad()
是一样的,在每次backward之前都必须调用一次zero_grad
。
5. 总流程
1 | model = MLPNet(feature, 25, 1) |
Iter: 100, Loss = 0.17146091163158417
Iter: 200, Loss = 0.10728909820318222
Iter: 300, Loss = 0.08248171955347061
Iter: 400, Loss = 0.06832777708768845
Iter: 500, Loss = 0.05633820965886116
Iter: 600, Loss = 0.0479607917368412
Iter: 700, Loss = 0.04309488832950592
Iter: 800, Loss = 0.03938402235507965
Iter: 900, Loss = 0.036379750818014145
Iter: 1000, Loss = 0.03386558219790459
看一下效果如何:
1 | with torch.no_grad(): |
准确率: 0.9525483304042179
注意到我们使用了: 1
2with torch.no_grad():
model.eval()
调用no_grad()
会构建一个上下文环境,在这个块当中进行计算时不会将梯度记录下来,从而大大提高运行速度,节省内存。
eval
则是对应了train
。在当你需要用到 dropout 或者 batch normalization 的时候,调用 train
方法可以在 forward 的时候打开 dropout 或归一化操作,而eval
则会关闭。