学习记录
发现自己对pytorch的网络搭建还是停留在调包情况,准备学习深入一些,先从简单的拟合直线开始。
使用torch拟合直线
这里参考了知乎一个大佬的方法,由于在服务器上写的,好像不能画图。
import numpy as np
import torch
import torch.nn as nn
def linear_model(x):
return torch.mul(x, w) + b
def get_loss(my_pred, my_y_train):
return torch.mean((my_pred - my_y_train) ** 2)
torch.manual_seed(2)
x_train = np.array([[3.3], [4.4], [5.5], [6.71], [6.93], [4.168], [9.779], [6.182], [7.59], [2.167], [7.042], [10.791], [5.313], [7.997], [3.1]], dtype=np.float32)
y_train = np.array([[1.7], [2.76], [2.09], [3.19], [1.694], [1.573], [3.366], [2.596], [2.53], [1.221], [2.827], [3.465], [1.65], [2.904], [1.3]], dtype=np.float32)
input = torch.from_numpy(x_train)
gt = torch.from_numpy(y_train)
w = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)
epoch = 50
pred_loss, w_pred, b_pred = 1, 0, 0
lr = 1e-3
for i in range(epoch):
pred_v = linear_model(input)
loss = get_loss(pred_v, gt)
if w.grad:
w.grad.zero_()
if b.grad:
b.grad.zero_()
loss.backward()
w.data = w.data - lr * w.grad.data
b.data = b.data - lr * b.grad.data
if pred_loss > loss:
w_pred = w
b_pred = b
print(w_pred, b_pred)
最后的训练结果是
tensor([0.3976], requires_grad=True) tensor([-0.2102], requires_grad=True)
一些分析
loss.backward()的用法
查询相关知识后发现是获取参数本次epoch的梯度,对于直线,获取的是w和b的梯度。在梯度下降算法,梯度定义为函数上升最快的方向,自变量减去该方向可以获取函数下降的最快速度。 损失函数是
J
(
w
,
b
)
=
∑
i
=
0
15
(
V
p
r
e
d
_
i
?
V
g
t
_
i
)
2
15
J(w, b)=\frac{\sum_{i=0}^{15} (V_{pred\_i} -V_{gt\_i})^2}{15}
J(w,b)=15∑i=015?(Vpred_i??Vgt_i?)2? 其中
V
p
r
e
d
_
i
=
w
×
x
i
+
b
V_{pred\_i}=w \times x_i+b
Vpred_i?=w×xi?+b 损失函数分别对w和b求偏导数:
?
J
(
w
,
b
)
?
w
=
∑
i
=
0
15
2
×
(
V
p
r
e
d
_
i
?
V
g
t
_
i
)
×
x
i
15
\frac{\partial J(w, b)}{\partial w}=\frac{\sum_{i=0}^{15} 2\times(V_{pred\_i} -V_{gt\_i})\times x_i}{15}
?w?J(w,b)?=15∑i=015?2×(Vpred_i??Vgt_i?)×xi??
?
J
(
w
,
b
)
?
b
=
∑
i
=
0
15
2
×
(
V
p
r
e
d
_
i
?
V
g
t
_
i
)
×
1
15
\frac{\partial J(w, b)}{\partial b}=\frac{\sum_{i=0}^{15} 2\times(V_{pred\_i} -V_{gt\_i})\times 1}{15}
?b?J(w,b)?=15∑i=015?2×(Vpred_i??Vgt_i?)×1? 算出来的
?
J
(
w
,
b
)
?
w
\frac{\partial J(w, b)}{\partial w}
?w?J(w,b)?和
?
J
(
w
,
b
)
?
b
\frac{\partial J(w, b)}{\partial b}
?b?J(w,b)?就是w.grad.data 和b.grad.data
lr被称为步长或学习率。
为什么每个epoch都要有梯度置零
在每个epoch中都有以下步骤
if w.grad:
w.grad.zero_()
if b.grad:
b.grad.zero_()
因为每次梯度都是要从本次的loss处重新计算梯度。 如果参数的梯度不置零,epoch为3,如图
print('w={}, b={}'.format(w,b))
print('===========')
for i in range(epoch):
pred_v = linear_model(input)
loss, delta = get_loss(pred_v, gt)
grad = torch.mean(2 * torch.mul(delta, input))
loss.backward()
w.data = w.data - lr * w.grad.data
b.data = b.data - lr * b.grad.data
if pred_loss > loss:
w_pred = w
b_pred = b
print(w.grad, grad, w, b)
w=tensor([0.3923], requires_grad=True), b=tensor([-0.2236], requires_grad=True)
===========
tensor([-0.5801]) tensor(-0.5801, grad_fn=<MeanBackward0>) tensor([0.3929], requires_grad=True) tensor([-0.2232], requires_grad=True)
tensor([-1.1069]) tensor(-0.5269, grad_fn=<MeanBackward0>) tensor([0.3940], requires_grad=True) tensor([-0.2226], requires_grad=True)
tensor([-1.5320]) tensor(-0.4251, grad_fn=<MeanBackward0>) tensor([0.3955], requires_grad=True) tensor([-0.2216], requires_grad=True)
可以看出上一次计算出的梯度值在本次梯度的计算中是累加关系。
参数更新分析
在梯度反向传播时,反向传播这个过程不加入下一次的反向传播中。需要使用w.data 和b.data 去更新两个参数,只改变值,不改变参数的传递。如图:
w.data = w.data - lr * w.grad.data
b.data = b.data - lr * b.grad.data
print(w.grad_fn, b.grad_fn)
exit()
None None
两个都是None ?是因为系统将w和b的grad值初始化为None ,因此需要加if w.grad: 和if b.grad: 判断是否是第一个epoch,从第二次二者就有梯度值了。
如果使用w 和b 更新的话,w 和b 的更新计算也会加入到计算图中:
w = w - lr * w.grad
b = b - lr * b.grad
print(w.grad_fn, b.grad_fn)
exit()
<SubBackward0 object at 0x7f85cbc768d0> <SubBackward0 object at 0x7f858d088050>
w和b的的grad_fn 就不是None 。除此之外,w和b的grad 也丢了,在第二个epoch时,w.grad 会变成NoneType 。查阅相关资料,发现参数不再是叶子节点了。
print(w.is_leaf, b.is_leaf)
w = w - lr * w.grad
b = b - lr * b.grad
print(w.is_leaf, b.is_leaf)
True True
False False
查询相关的CSDN博客: 如果该张量的属性requires_grad=True ,而且是用于直接创建的,也即它的属性grad_fn=None ,那么它就是叶子节点。 如果该张量的属性requires_grad=True ,但是它不是用户直接创建的,而是由其他张量经过某些运算操作产生的,那么它就不是叶子张量,而是中间节点张量,并且它的属性grad_fn 不是None ,就出现上述的情况了,表示该张量是通过运算操作获取的。 pytorch的自动梯度机制不会为中间结果保存梯度,即只会为叶子节点计算的梯度保存起来,保存到该叶子节点张量的属性grad 中,不会在中间节点张量的属性grad 中保存这个张量的梯度。 出于对效率的考虑,中间节点张量的属性grad 是None .如果用户需要为中间节点保存梯度的话,可以让这个中间节点调用方法retain_grad() ,这样梯度就会保存在这个中间节点的grad 属性中。 这跟系统提示的warning 一样,就是说w和b变成了中间向量。
UserWarning: The .grad attribute of a Tensor that is not a leaf
Tensor is being accessed. Its .grad attribute won't be populated
during autograd.backward(). If you indeed want the gradient for a
non-leaf Tensor, use .retain_grad() on the non-leaf Tensor. If
you access the non-leaf Tensor by mistake, make sure you access
the leaf Tensor instead. See
github.com/pytorch/pytorch/pull/30531 for more informations.
需不需要使用with torch.no_grad()
使用了with torch.no_grad() 之后,梯度就会消失,不需要使用。但经过验证,在参数更新时加不加没关系,考虑原因可能是只更新的data值,和梯度没有关系。
总结
分析了简单的拟合直线中的原理及实现,下一步准备研究下多层网络的反馈和卷积网络的参数传递。
|