pytorch入门:自动求导,不同方式实现简单的机器学习

Tensor 与 Autograd

神经网络中重要的内容就是进行参数学习,参数学习就离不开求导。torch.autograd 包来进行自动求导。

自动求导要点

看不懂。。。。为啥别人都说这本书好呢。有好多错别字啊,,

梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。
在这里插入图片描述

看不懂的可以看看下面的在回来看看。。

  1. 创建叶子节点(Leaf Node) 的Tensor,使用requires_grad参数指定是否记录对其的操作,以便之后利用 backward() 方法进行导数求解,requires_grad 参数默认False,需要对其求导就设置为True,然后与之有关系的节点会自动变为True。

  2. 可以使用 requires_grad_(bool) 方法修改Tensor 的requires_grad 属性,调用 .detach() or with torch.no_grad(): ,就不会再计算张量的梯度,跟踪张量的历史记录。这点在评估模型,测试模型阶段常用。

  3. 同运算创建的非叶子节点 的Tensor ,会自动赋予 grad_fn 属性,表示梯度函数。叶子节点的这个属性是None。

  4. 最后得到的Tensor执行backward()函数,此时自动计算各变量的梯 度,并将累加结果保存到grad属性中。计算完成后,非叶子节点的梯度自动 释放。

  5. backward()函数接收参数,该参数应和调用backward()函数的Tensor 的维度相同,或者是可broadcast(广播)的维度。如果求导的Tensor为标量(即一个 数字),则backward中的参数可省略

  6. 反向传播的中间缓存会被清空,如果需要进行多次反向传播,需要 指定backward中的参数retain_graph=True。多次反向传播时,梯度是累加 的。

  7. 非叶子节点的梯度backward调用后即被清空。

  8. 可以通过用torch.no_grad()包裹代码块的形式来阻止autograd去跟踪 那些标记为.requesgrad=True的张量的历史记录。这步在测试阶段经常使 用。

计算图,有向无环图(DAG),表示算子与变量的关系,就是 z = wx + b。其中用户创建的变量就是叶子节点,这里就是 w b x 。为了计算叶子节点的梯度,将 requires_grad 设置为True,然后就可以自动跟踪记录。y z 是计算来的变量,是非叶子节点,z 是根节点。mul add 就是算子(函数)。
在这里插入图片描述
我们的目标是更新个叶子节点的梯度。下面就是各叶子节点的梯度的计算:

还记得吴恩达课程里的神经网络嘛,这个反向传播计算的就是代价函数的导数部分,每一个权重的Δ。。然后就可以梯度下降啦,,进行优化,,这里不需要对 x 的导。

在这里插入图片描述
使用 backward() 方法反向传播,自动计算个节点的梯度。利用导数的链式法则,计算叶子节点的梯度,梯度值累加到 grad 属性中,对于非叶子节点的函数操作 记录在grad_fn 属性中,叶子节点的grad_fn 值为None。
在这里插入图片描述
具体的过程可以看看吴恩达的神经网络的反向传播,感觉那个还是很好理解的。。。

标量的反向传播

# 看着可能很乱,但思路还是很清晰的。。。

x=torch.Tensor([2])
print('输入张量x:',x)
w=torch.randn(1,requires_grad=True)  # 可以使用w.requires_grad_(False) 修改这个属性
b=torch.randn(1,requires_grad=True) # 记录求导,并且与之有依赖关系的节点会自动变成True
y=torch.mul(w,x)  #等价于w*x
z=torch.add(y,b)  #等价于y+b
print('随机的权重参数w,偏移量b:,结果z:\n',w,b,z)
print("x,w,b的require_grad属性分别为:{},{},{}".format(x.requires_grad,w.requires_grad,b.requires_grad))
>>>
输入张量x: tensor([2.])
随机的权重参数w,偏移量b:,结果z:
 tensor([0.8357], requires_grad=True) tensor([0.4299], requires_grad=True) tensor([2.1012], grad_fn=<AddBackward0>)
x,w,b的require_grad属性分别为:False,True,True


#查看非叶子节点的requres_grad属性,
print("y,z的requires_grad属性分别为:{},{}".format(y.requires_grad,z.requires_grad))
#因与w,b有依赖关系,故y,z的requires_grad属性也是:True,True
>>> y,z的requires_grad属性分别为:True,True

#查看各节点是否为叶子节点
print("x,w,b,y,z的是否为叶子节点:{},{},{},{},{}".format(x.is_leaf,w.is_leaf,b.is_leaf,y.is_leaf,z.is_leaf))
>>> x,w,b,y,z的是否为叶子节点:True,True,True,False,False

#查看叶子节点的grad_fn属性,这个属性就是梯度函数。
print("x,w,b的grad_fn属性:{},{},{}".format(x.grad_fn,w.grad_fn,b.grad_fn))
#因x,w,b为用户创建的,为通过其他张量计算得到,故x,w,b的grad_fn属性:None,None,None
>>> x,w,b的grad_fn属性:None,None,None

#查看非叶子节点的grad_fn属性
print("y,z的是否为叶子节点:{},{}".format(y.grad_fn,z.grad_fn))
#y,z的是否为叶子节点:<MulBackward0 object at 0x7f923e85dda0>,<AddBackward0 object at 0x7f923e85d9b0>


#基于z张量进行梯度反向传播,执行backward之后计算图会自动清空,
#如果需要多次使用backward,需要修改参数retain_graph为True,此时梯度是累加的
#z.backward(retain_graph=True)
z.backward()
#查看叶子节点的梯度,x是叶子节点但它无需求导,故其梯度为None
print("参数w,b,x的梯度分别为:{},{},{}".format(w.grad,b.grad,x.grad))    # 应该是 z 对 w,b 的导
>>> 参数w,b,x的梯度分别为:tensor([2.]),tensor([1.]),None

#非叶子节点的梯度,执行backward之后,会自动清空
print("非叶子节点y,z的梯度分别为:{},{}".format(y.grad,z.grad))
>>>非叶子节点y,z的梯度分别为:None,None


z.backward(retain_graph=True)
print("参数w,b的梯度分别为:{},{},{}".format(w.grad,b.grad,x.grad))
print("非叶子节点y,z的梯度分别为:{},{}".format(y.retain_grad(),z.retain_grad()))
参数w,b的梯度分别为:tensor([4.]),tensor([2.]),None    # 这里经过了累加。应该就是简单的一次两次的累加
非叶子节点y,z的梯度分别为:None,None  

脑部链接。。更复杂的。加深对反向传播的理解,,,

非标量的反向传播

一般我们的目标都是标量,比如损失值,但也会碰到非标量的情况,这是就需要对非标量进行反向传播。pytorch 规定只能进行标量与张量的求导。这是就需要给backward() 传入一个grandient。将张量对张量的求导转换为标量对张量的求导。

意思就是:目标 loss=(y1,y2,ym) , 传入的grandient参数v =(v1,vm) , 对Loss的求导就可以转化为对 loss*v^T 标量的求导。

backward(gradient=None, retain_graph=None, create_graph=False)

#定义叶子节点张量x,形状为1x2
x= torch.tensor([[2, 3]], dtype=torch.float, requires_grad=True)
#初始化Jacobian矩阵  雅克比矩阵 就是y 对 x的梯度
J= torch.zeros(2 ,2)
#初始化目标张量,形状为1x2
y = torch.zeros(1, 2)
#定义y与x之间的映射关系:
y[0, 0] = x[0, 0] ** 2 + 3 * x[0 ,1]
y[0, 1] = x[0, 1] ** 2 + 2 * x[0, 0]

手算一下:y 对 x的梯度是一个雅可比矩阵。
在这里插入图片描述这个 J 就是 y 对 x 的梯度。

自动算一下:要分成两步进行,一次 v=(1,0) 得到 y1 对 x 的梯度,然后使 v=(0,1) 得到 y2 对 x 的梯度。这里 x 就是那个张量,y1,y2 就是那个标量嘛。。。感觉就是分开进行。

#生成y1对x的梯度
y.backward(torch.Tensor([[1, 0]]),retain_graph=True)  
J[0]=x.grad
#梯度是累加的,故需要对x的梯度清零
x.grad = torch.zeros_like(x.grad)
#生成y2对x的梯度
y.backward(torch.Tensor([[0, 1]]))             # 填入 v 
J[1]=x.grad
#显示jacobian矩阵的值
print(J)

使用 Numpy 实现机器学习

import numpy as np
from matplotlib import pyplot as plt

np.random.seed(100) 
x = np.linspace(-1, 1, 100).reshape(100,1)   # y=3x**2 + 2 随便加一些噪声
y = 3*np.power(x, 2) +2+ 0.2*np.random.rand(x.size).reshape(100,1)  
plt.scatter(x, y)
plt.show()

在这里插入图片描述

# 随机初始化 参数 w,b 
w1 = np.random.rand(1,1)
b1 = np.random.rand(1,1) 

lr =0.001 # 学习率
for i in range(800):
    # 前向传播
    y_pred = np.power(x,2)*w1 + b1
    # 定义损失函数
    loss = 0.5 * (y_pred - y) ** 2
    loss = loss.sum()
    #计算梯度
    grad_w=np.sum((y_pred - y)*np.power(x,2))   # 对 w 的导
    grad_b=np.sum((y_pred - y))
    #使用梯度下降法,是loss最小
    w1 -= lr * grad_w
    b1 -= lr * grad_b

plt.plot(x, y_pred,'r-',label='predict')
plt.scatter(x, y,color='blue',marker='o',label='true') # true data
plt.xlim(-1,1)
plt.ylim(2,6)  
plt.legend()
plt.show()
print(w1,b1)
>>> [[2.98927619]] [[2.09818307]]

在这里插入图片描述

使用 Tensor 及 Antograd 实现机器学习

import torch as t
import matplotlib.pyplot as plt
 
t.manual_seed(100)
dtype = t.float
x=t.unsqueeze(t.linspace(-1,1,100),dim=1)
y=3*x.pow(2) + 2+0.2 * t.rand(x.size())

w = t.randn(1,1,dtype=dtype,requires_grad = True)    # w , b 需要进行学习,属性设置为True
b = t.randn(1,1,dtype=dtype, requeires_grad = True)
lr = 0.001
for ii in range(800):
	y_pred = x.pow(2).mm(w) + b
	loss = 0.5 * (y_pred -y) **2
	loss = loss.sum()   
	loss.backward()           # 自动求得 loss 对 w,b 的梯度
	with t.no_grad():  # 手动更新参数,是上下文环境中切断自动求导的计算。
		w -= lr * w.grad  # 不在计算张量的梯度,跟踪张量的历史记录
		b -= lr * b.grad
		w.grad.zero_()    # 梯度清零,每次下降都要更新的
		b.grad.zero_()

plt.plot(x.numpy(), y_pred.detach().numpy(),'r-',label='predict')#predict
plt.scatter(x.numpy(), y.numpy(),color='blue',marker='o',label='true') # true data
plt.xlim(-1,1)
plt.ylim(2,6)  
plt.legend()
plt.show()
        
print(w, b)
>>>
(tensor([[2.9648]], requires_grad=True),
 tensor([[2.1145]], requires_grad=True))

这个 with t.no_grad(): 感觉好难理解啊。一般来说对 tensor 的操作会默认生成计算图的,以便来反向传播。但是有些操作我们是不需要反向(不生成计算图),可以打印过程中的 y, loss看看他们的 grad_fn 属性,MulBackward0 啥的。如果你不加这个 no_grad() ,后面的 w,b 的减法操作就会生成计算图,然后在反向中就会进行计算。(虽然不会生成计算图,但实际的计算还是会进行的),这个有节约显存的作用吧。。没试过。验证集,我们只是想看一下训练的效果,并不是想通过验证集来更新网络时,就可以使用with torch.no_grad()。

按理来说加不加这句的结果应该相同,但针对这个例子就会报错。。因为进行了一个 w-= 的赋值操作,好像就是为了防止给梯度赋值一个无穷大吧,,好难啊。。。

使用TensorFlow 框架

import tensorflow.compat.v1 as tf    # 我的是2.3.1,用第一版的API 就这么搞
tf.disable_v2_behavior()
import numpy as np

np.random.seed(100)
x=np.linspace(-1,1,100).reshape(100,1)
y=3*np.power(x,2) + 2+ 0.2*np.random.rand(x.size).reshape(100,1)
x1 = tf.placeholder(tf.float32,shape = (None,1))
y1 = tf.placeholder(tf.float32,shape = (None,1))
# 创建两个占位符,用来存放输入数据x,目标值 y ,在运行计算图是导入数据
w = tf.Variable(tf.random_uniform([1],0,1.0))
b = tf.Variable(tf.zeros([1]))
# 创建权重变量 w,b 并随机化,tf 的变量在整个计算图中保存其值。

y_pred = np.power(x,2) *w +b # 前向传播,计算预测值
loss = tf.reduce_mean(tf.square(y-y_pred)) # 计算损失值
grad_w,grad_b = tf.gradients(loss,[w,b]) # 计算有关参数 w,b 关于损失函数的梯度

#使用梯度下降法更新参数,执行计算图是给 new_w1, new_w2 赋值,
# 对 tf 来说更新参数是计算图的一部分内容,在pytroch 中这部分属于计算图之外
learning_rate = 0.01
new_w = w.assign(w - learning_rate * grad_w)
new_b = b.assign(b - learning_rate * grad_b)

# 已构建计算图,创建 session 执行计算
with tf.Session() as sess:
    # 执行前初始化变量 w,b 
    sess.run(tf.global_variables_initializer())
    
    for step in range(2000):
        # 循环执行计算图,每次把 x1, y1 赋值给 x,y 
        # 每次执行计算图,需要计算关于 new_w new_b 的损失值,返回numpy的数组
        loss_value, v_w,v_b = sess.run([loss,new_w,new_b],feed_dict = {x1:x,y1:y})
        
        if step%200 == 0:  # 200次输出一次结果
            print("损失值、权重、偏移量分别为{:.4f},{},{}".format(loss_value,v_w,v_b))

>>>
损失值、权重、偏移量分别为9.5711,[0.39555296],[0.05977444]
损失值、权重、偏移量分别为0.1316,[1.816024],[2.5013514]
损失值、权重、偏移量分别为0.0692,[2.1526716],[2.4078143]
损失值、权重、偏移量分别为0.0375,[2.387772],[2.321037]
损失值、权重、偏移量分别为0.0210,[2.556913],[2.2583735]
损失值、权重、偏移量分别为0.0125,[2.6786542],[2.2132683]
损失值、权重、偏移量分别为0.0081,[2.7662807],[2.1808016]
损失值、权重、偏移量分别为0.0058,[2.8293512],[2.1574354]
损失值、权重、偏移量分别为0.0046,[2.8747463],[2.1406155]
损失值、权重、偏移量分别为0.0040,[2.9074223],[2.1285102]

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值