Learner0x5a's Studio.

pytorch笔记

Word count: 1.2kReading time: 5 min
2021/04/07 Share

官方教程 Learn PyTorch with Examples

https://pytorch.org/tutorials/beginner/pytorch_with_examples.html

PyTorch Batch训练的一些笔记

根据可视化结果,batch size越小,得到的分类边界更精细,收敛更好,但收敛越慢,batch size过小,就会导致震荡甚至无法收敛。
batch size越大,收敛越快,但分类边界比较粗糙。

调用指定的GPU

直接终端中设定

1
CUDA_VISIBLE_DEVICES=1 python3 main.py

torch.nonzero

torch.nonzero() 找出tensor中非零的元素的索引.

PyTorch 梯度传播的笔记

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
tensor = torch.FloatTensor([[1,2],[3,4]])  
tensor.requires_grad=True
mean_out = torch.mean(tensor*tensor) # x^2
max_out = torch.max(tensor*tensor)

print(mean_out) # tensor(7.5000, grad_fn=<MeanBackward0>)
print(max_out) # tensor(16., grad_fn=<MaxBackward1>)


# 每次backward会自动销毁计算图
mean_out.backward() # 反向传播; 这里不用retain_graph=True,因为mean_out和max_out是不同的计算图;这里只销毁mean_out
print(tensor.grad)
'''
tensor([[0.5000, 1.0000],
[1.5000, 2.0000]])
'''

max_out.backward(retain_graph=True) # 自动累计梯度; 这里需要加retain_graph=True,因为下面还要用max_out
print(tensor.grad)
'''
tensor([[ 0.5000, 1.0000],
[ 1.5000, 10.0000]])
'''


tensor.grad = None # 如果想重新算梯度,需要将梯度清零;tensor.grad = None
max_out.backward()
print(tensor.grad)
'''
tensor([[0., 0.],
[0., 8.]])
'''

# mean_out = 1/4 * sum(tensor*tensor)
# mean_out 的梯度是d(mean_out)/d(tensor) = 1/4*2*tensor = tensor/2
# max_out = max(tensor*tensor)
# max_out 的梯度是d(max_out)/d(tensor) = max(2*tensor)
# mean,max之类的函数不参与梯度运算,原样保留
# max simply selects the greatest value and ignores the others, so max is the identity operation for that one element.
# Therefore the gradient can flow backwards through it for just that one element.


NN 花式求梯度

NN来自https://github.com/yunjey/pytorch-tutorial/blob/master/tutorials/01-basics/feedforward_neural_network/main.py

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# Fully connected neural network with one hidden layer
class NeuralNet(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super(NeuralNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, num_classes)

def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out


# 定义一个函数用来算梯度,这里其实和forward一样,用来算输出的梯度;
# 也可以改成别的,比如去掉fc2和relu,算一下第一层对输入的梯度;
# 也可以指定某个神经元的梯度,比如return out[:,2]就是只用第三个神经元算梯度
def pre_grad(self,x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out

model = NeuralNet(input_size, hidden_size, num_classes).to(device)

# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
# Move tensors to the configured device
images = images.reshape(-1, 28*28).to(device)
images.requires_grad = True
labels = labels.to(device)

# Forward pass
outputs = model(images)
loss = criterion(outputs, labels)

# Backward and optimize
optimizer.zero_grad()
# 这里计算的是loss对input的梯度; shape (100,784); loss是一个标量,不需要传参给backward()
loss.backward()
print(np.shape(images.grad),images.grad[0][:5])
optimizer.step()

# 计算输出对输入的梯度 d(output) / d(input); shape也是(100,784),原因参见下面backward的解析
model.zero_grad()
out = model.pre_grad(images)
# 求向量对向量的梯度,需要传一个grad_tensor进去,这个grad_tensor和out的shape要一样
# 这里的ones_like(out)就是产生一个和out的shape一样,元素全为1的tensor
# 这个grad_tensor的物理意义是权重;
# torch在算完Jacobian矩阵后,会用Jacobian矩阵乘上这个grad_tensor,对各列(即每个神经元对input所有字节的梯度)做一个加权和
# 所以得到的仍然是一个shape为(100,784)的张量
out.backward(torch.ones_like(out))
print(np.shape(images.grad),images.grad[0][:5])
images.grad = None # 记得清零

# 可以参考:https://blog.csdn.net/weixin_38314865/article/details/100423919
# https://mustafaghali11.medium.com/how-pytorch-backward-function-works-55669b3b7c62
# https://zhuanlan.zhihu.com/p/83172023
# 注意,如果input是一个batch,即使最后一层只有一个神经元,也要传grad_tensor进去
# 虽然out的shape是(batch_size,), 但其实后台直接按(batch_size,1)来处理了,
# 所以乘上一个ones_like(out)相当于对第二维求加权和,使得求导可以进行
# shape的变化:(batch_size,1) * (batch_size,) -> (1,);
# 所以本质是用某种方法让dy/dx的y变成一个标量


'''
# 如果想要得到原始的Jacobian矩阵,需要利用one-hot向量多次backward,再把结果拼起来; 例如
x = torch.randn(2, requires_grad=True)
y = x * 2
J = torch.zeros(x.shape[0],x.shape[0]) # 初始化空Jacobian矩阵 (2*2)

y.backward(torch.FloatTensor([[1,0]]),retain_graph=True) # 第一行
J[:,0] = x.grad
x.grad = None

y.backward(torch.FloatTensor([[0,1]]),retain_graph=True) # 第二行
J[:,1] = x.grad

print(J)

# 对于这个nn,Jacobian矩阵也可以通过修改计算图pre_grad,遍历神经元再拼起来得到
'''

# 也可以计算对单个样本的梯度; shape (1,784)
img = images[0].view(1,-1)
img.requires_grad = True # 注意,计算单个样本的时候,需要把上面对整体样本的设置注视掉,即注释掉images.requires_grad = True
model.zero_grad()
out = model.pre_grad(img)
out.backward(torch.ones_like(out)) # 同上,传入Jacobian矩阵加权向量
print(np.shape(img.grad),img.grad[:,:5])
img.grad = None

CATALOG
  1. 1. 官方教程 Learn PyTorch with Examples
  2. 2. PyTorch Batch训练的一些笔记
  3. 3. 调用指定的GPU
  4. 4. torch.nonzero
  5. 5. PyTorch 梯度传播的笔记
    1. 5.1. NN 花式求梯度