Pytorch拟合直线方法

学习记录

发现自己对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)#转换成tensor类型
gt = torch.from_numpy(y_train)
w = torch.randn(1, requires_grad=True)#初始化y = w * x + b的参数
b = torch.randn(1, requires_grad=True)
epoch = 50#设置迭代次数
pred_loss, w_pred, b_pred  = 1, 0, 0#保存最好的一次loss
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_()#每次w和b的梯度都要置零
    if b.grad:    
        b.grad.zero_()
    loss.backward()   
    #with torch.no_grad():
        #w.data = w.data - lr * w.grad.data
        #b.data = b.data - lr * b.grad.data
    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('loss = {}, epoch = {}'.format(loss, i))
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)=15i=015(Vpred_iVgt_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} wJ(w,b)=15i=0152×(Vpred_iVgt_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} bJ(w,b)=15i=0152×(Vpred_iVgt_i)×1
算出来的 ∂ J ( w , b ) ∂ w \frac{\partial J(w, b)}{\partial w} wJ(w,b) ∂ J ( w , b ) ∂ b \frac{\partial J(w, b)}{\partial b} bJ(w,b)就是w.grad.datab.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)) 
    # if w.grad:    
    #     w.grad.zero_()#每次w和b的梯度都要置零
    # 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.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)

可以看出上一次计算出的梯度值在本次梯度的计算中是累加关系。

#-0.5801 + -0.5269 = -1.1069
#-1.1069 + -0.4251 = -1.5320

参数更新分析

在梯度反向传播时,反向传播这个过程不加入下一次的反向传播中。需要使用w.datab.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,从第二次二者就有梯度值了。

如果使用wb更新的话,wb的更新计算也会加入到计算图中:

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中保存这个张量的梯度。
出于对效率的考虑,中间节点张量的属性gradNone.如果用户需要为中间节点保存梯度的话,可以让这个中间节点调用方法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值,和梯度没有关系。

总结

分析了简单的拟合直线中的原理及实现,下一步准备研究下多层网络的反馈和卷积网络的参数传递。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值