0%

PyTorch入门-3

PyTorch入门-3

建立神经网络

每个Module都是nn.Module的子类

1
2
3
4
5
6
7
8
9
import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

#检查GPU是否可以使用
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Using {device} device')

定义类即神经网络模块

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
class NeuralNetwork(nn.Module):		#nn.Module的子类
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
)

def forward(self, x): #在forward方法中实现对输入数据的操作
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits

#我们创建一个NeuralNetwork的实例,并将其移动到设备上(GPU/CPU),然后打印其结构
model = NeuralNetwork().to(device)
print(model)

output:
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)

######
X = torch.rand(1, 28, 28, device=device) #输入数据X,输出数据logits
logits = model(X) #不能直接调用model.forward()
pred_probab = nn.Softmax(dim=1)(logits) #使用softmax获取概率
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")

各层详解

1
2
input_image = torch.rand(3,28,28)	#模拟3张28*28的图片
print(input_image.size()) #output:torch.Size([3, 28, 28])
nn.Flatten
1
2
3
flatten = nn.Flatten()	#展平,将28*28的矩阵转化为784的向量
flat_image = flatten(input_image)
print(flat_image.size()) #output: torch.Size([3, 784])
nn.Linear
1
2
3
layer1 = nn.Linear(in_features=28*28, out_features=20) #使用存储的权重和偏差对输入应用线性转换。
hidden1 = layer1(flat_image)
print(hidden1.size()) #output: torch.Size([3, 20])
nn.Relu
1
hidden1 = nn.ReLU()(hidden1)	#激活函数
nn.Sequential

Sequential是模块的有序容器,数据按照定义的顺序传递给所有模块

1
2
3
4
5
6
7
8
seq_modules = nn.Sequential(
flatten,
layer1,
nn.ReLU(),
nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)
nn.Softmax
1
2
softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)

模型参数

nn.Module自动跟踪参数,可以使用parameters() or named_parameters()访问参数

1
2
3
4
print("Model structure: ", model, "\n\n")

for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")

Autograd

在训练神经网络时,最常用的算法是反向传播。在该算法中,参数(模型权值)根据损失函数相对于给定参数的梯度进行调整。为了计算这些梯度,PyTorch内置了一个名为torch.autograd的差异化引擎。它支持自动计算梯度的任何计算图

1
2
3
4
5
6
7
8
import torch

x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True) #等价:b = torch.randn(3); b.requires_grad_(True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

w,b是参数,需要loss函数对这两个变量求梯度,所以requires_grad=True,反向传播函数存储在grad_fn

1
2
3
4
5
6
print('Gradient function for z =', z.grad_fn)
print('Gradient function for loss =', loss.grad_fn)

loss.backward() #只能使用一次,如果想多次使用,使得retain_graph=True
print(w.grad)
print(b.grad)

停止梯度追踪

1
2
3
4
5
6
7
8
9
10
z = torch.matmul(x, w)+b
print(z.requires_grad) #output: True

with torch.no_grad():
z = torch.matmul(x, w)+b
print(z.requires_grad) #output: False

z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad) #output: False

停止计算的原因:

  • 将神经网络中的一些参数标记为冻结参数。这是对预先训练的网络进行微调的一个非常常见的场景
  • 在只进行正向传递的情况下加快计算速度,因为在不跟踪梯度的张量上的计算将更加有效

关于计算图的信息

从概念上讲,autograd在由Function对象组成的有向无环图(DAG)中保存数据(张量)和所有执行的操作(以及产生的新张量)的记录。在这个DAG中,叶是输入张量,根是输出张量。通过跟踪这个从根到叶的图形,您可以使用链式规则自动计算梯度。

  • forward
    • 运行请求的操作来计算结果张量
    • 在DAG中保持操作的梯度函数。
  • backward:.backward()在DAG根目录上被调用,开始autograd()
    • 计算每个.grad_fn的梯度
    • 将它们累加到各自张量的.grad属性中
    • 利用链式法则,一直传播到叶张量
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
inp = torch.eye(5, requires_grad=True)
out = (inp+1).pow(2)
out.backward(torch.ones_like(inp), retain_graph=True)
print("First call\n", inp.grad)
out.backward(torch.ones_like(inp), retain_graph=True)
print("\nSecond call\n", inp.grad)
inp.grad.zero_()
out.backward(torch.ones_like(inp), retain_graph=True)
print("\nCall after zeroing gradients\n", inp.grad)

###output
First call
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.],
[2., 2., 2., 2., 4.]])

Second call
tensor([[8., 4., 4., 4., 4.],
[4., 8., 4., 4., 4.],
[4., 4., 8., 4., 4.],
[4., 4., 4., 8., 4.],
[4., 4., 4., 4., 8.]])

Call after zeroing gradients
tensor([[4., 2., 2., 2., 2.],
[2., 4., 2., 2., 2.],
[2., 2., 4., 2., 2.],
[2., 2., 2., 4., 2.],
[2., 2., 2., 2., 4.]])