一、torch.autograd
autograd—自动求导系统
深度学习的模型训练就是不断地更新权值,而权值的更新需要求解梯度,因此梯度十分重要。然而,求取梯度十分繁琐,因此,pytorch提供自动求导系统,不需手动计算梯度,只需要搭建好前向传播的计算图,然后根据pytorch中的autograd的方法就可以得到所有张量的梯度。
torch. autograd. backward
功能:自动求取梯度
- tensors:用于求导的张量,如loss
- retain_ graph:保存计算图(由于pytorch采用的是动态图机制,在每一次反向传播之后,计算图都会被释放掉,若要继续使用计算图,就要设置此参数为True)
- create_ graph:创建导数计算图,用于高阶求导
- grad_tensors:多梯度权重
回顾:对y进行backward方法就可以得到所有结点(x,w)梯度。
代码实现:
import torch
torch.manual_seed(10)
flag = True
# flag = False
if flag: # 创建叶子张量
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
# 执行运算并搭建动态图
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
y.backward()
# y.backward(retain_graph=True)
print(w.grad)
# y.backward()
对y.backward()设置断点,进入这个函数,可以看到:
从这里可以看出,张量中的backward方法实际直接调用了torch. autograd中的backward。
backward方法中有一个retain_graph参数,用来保存计算图,假设现在我们还要执行一次反向传播,即将程序的最后一行#去掉,程序会报错,因为计算图已被释放掉,为解决这一问题,我们需将retain_graph参数设置为True
对于参数grad_tensors(设置的权重),举例:在上述例子基础上加y1,Loss是一个向量,维度是2,而不再是一个标量,对Loss进行反向传播来查看w的梯度。
代码实现:
import torch
torch.manual_seed(10)
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
a = torch.add(w, x) # retain_grad()
b = torch.add(w, 1)
y0 = torch.mul(a, b) # y0 = (x+w) * (w+1)
y1 = torch.add(a, b) # y1 = (x+w) + (w+1) dy1/dw = 2
loss = torch.cat([y0, y1], dim=0) # [y0, y1]
grad_tensors = torch.tensor([1., 2.]) # 设置权重
loss.backward(gradient=grad_tensors) # gradient 传入 torch.autograd.backward()中的grad_tensors
print(w.grad)
结果:
tensor([9.])
结果为1* y0对应w的梯度+2*y1对应w的梯度=5+2 *2=9
torch. autograd. grad
功能:求取梯度
- outputs:用于求导的张量,如loss,y
- inputs:需要梯度的张量,如x,w
- create_ graph:创建导数计算图,用于高阶求导
- retain_ graph:保存计算图
- grad_ outputs:多梯度权重
代码实现:
import torch
torch.manual_seed(10)
x = torch.tensor([3.], requires_grad=True)
y = torch.pow(x, 2) # y = x**2
grad_1 = torch.autograd.grad(y, x, create_graph=True) # grad_1 = dy/dx = 2x = 2 * 3 = 6
print(grad_1)
grad_2 = torch.autograd.grad(grad_1[0], x) # grad_2 = d(dy/dx)/dx = d(2x)/dx = 2
print(grad_2)
结果:
(tensor([6.], grad_fn=<MulBackward0>),)
(tensor([2.]),)
autograd小贴士:
1.梯度不自动清零,每次都会叠加起来
举例:
import torch
torch.manual_seed(10)
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
for i in range(4):
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
y.backward()
print(w.grad)
# w.grad.zero_() # 下划线表示原地操作
结果:
tensor([5.])
tensor([10.])
tensor([15.])
tensor([20.])
循环两次,第一次梯度正常为5,但第二次之后就一直叠加。
所以,使用梯度后,需要对梯度手动的执行清0,w.grad.zero_()
2.依赖于叶子结点的结点,requires_grad默认为True
举例:回顾的计算图
a张量的requires_grad一定为True,因为a的梯度依赖于x的梯度。
代码:
import torch
torch.manual_seed(10)
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
a = torch.add(w, x)
b = torch.add(w, 1)
y = torch.mul(a, b)
print(a.requires_grad, b.requires_grad, y.requires_grad)
结果:
True True True
虽然只对w和x设置了requires_grad,a,b,y的这一属性都为True
3.叶子结点不可执行in-place
通过一个小例子理解in-place操作:
代码:
import torch
torch.manual_seed(10)
a = torch.ones((1, ))
print(id(a), a)
a = a + torch.ones((1, ))
print(id(a), a)
结果:
2877316649112 tensor([1.])
2877314704632 tensor([2.])
代码:
import torch
torch.manual_seed(10)
a = torch.ones((1, ))
print(id(a), a)
# a = a + torch.ones((1, ))
# print(id(a), a)
a += torch.ones((1, ))
print(id(a), a)
结果:
2446501335272 tensor([1.])
2446501335272 tensor([2.])
这个例子是a+1的操作,首先采用a=a+1操作实现,观察a内存地址变化,发现地址不一样,说明这一操作会开辟一个新的内存地址,在新的内存地址中赋予变量a,如果运算开辟了新的内存地址,也就不是in-place操作。
而+=操作,a+=1,运行后发现内存地址一样,这就是原地操作即in-place操作,在原始地址进行改变。
那么为什么叶子结点不可以执行in-place操作呢?这要从计算图求取梯度的过程来理解,求解w的梯度要用到b=w+1,也就是在我们反向传播的时候,是需要用到叶子张量w的,而在前向传播的时候,会记录w的地址,到反向传播时,再用w+1时是根据地址去取寻找w的数据,如果在反向传播之前改变了这一地址当中的数据,那么梯度求解会出错,这就是叶子张量不能执行in-place操作的原因。
二、逻辑回归
在前面,我们学习了数据的载体—张量,学习如何通过前向传播搭建计算图,通过计算图进行梯度的求解,有了数据、前向传播和梯度,我们就可以正式训练机器学习的模型。
逻辑回归是线性的二分类模型
对比逻辑回归和线性回归
逻辑回归:
线性回归:
线性回归是分析自变量x与因变量y(标量)之间关系的方法
逻辑回归是分析自变量x与因变量y(概率)之间关系的方法
逻辑回归还有一个别名:对数几率回归
圈起来的部分就是几率,它表示的是样本x为正样本的可能性,对几率取对数就得到了对数几率。
从下图推导可以知道对数几率回归等价于逻辑回归。
对数回归是通过wx+b来拟合y的对数。
二元逻辑回归模型的训练过程:
蓝色点是类别1,红色点是类别0,横轴和纵轴分别对应x0和x1两个自变量,y就是蓝色和红色的点,蓝色这条直线就是二元逻辑回归模型,随着迭代次数的增加,准确率不断提高,Loss值不断降低。
机器学习模型训练步骤:数据、模型、损失函数、优化器、迭代训练
首先获取数据,也称之为数据模块,在数据模块中涉及数据采集、数据清洗、数据划分和预处理。经过对数据的一系列处理使得可以直接输入到模型。
模型模块,可以根据任务的难易程度选择简单的线性模型或复杂的神经网络模型。
损失函数的选择,可以根据不同的任务选择不同的损失函数,比如在线性回归模型中,采用均方差损失函数;如果是分类任务,采用交叉熵。有了Loss就可以求取梯度,得到梯度后,会选择某一种优化方式。
优化器,采用某一种优化器更新权值。
最后进行反复的迭代训练过程。
这就是实现逻辑回归模型的五大模块
代码实现:
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np
torch.manual_seed(10)
# ============================ step 1/5 生成数据(随机生成样本方法) ============================
sample_nums = 100
mean_value = 1.7
bias = 1
n_data = torch.ones(sample_nums, 2)
x0 = torch.normal(mean_value * n_data, 1) + bias # 类别0 数据 shape=(100, 2)
y0 = torch.zeros(sample_nums) # 类别0 标签 shape=(100, 1)
x1 = torch.normal(-mean_value * n_data, 1) + bias # 类别1 数据 shape=(100, 2)
y1 = torch.ones(sample_nums) # 类别1 标签 shape=(100, 1)
train_x = torch.cat((x0, x1), 0)
train_y = torch.cat((y0, y1), 0)
# ============================ step 2/5 选择模型 ============================
class LR(nn.Module):
def __init__(self):
super(LR, self).__init__()
self.features = nn.Linear(2, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x): # 设定好这个逻辑回归的前向传播
x = self.features(x)
x = self.sigmoid(x)
return x
lr_net = LR() # 实例化逻辑回归模型
# ============================ step 3/5 选择损失函数 ============================
loss_fn = nn.BCELoss() # 采用pytorch中二分类的交叉熵函数
# ============================ step 4/5 选择优化器 ============================
lr = 0.01 # 学习率
optimizer = torch.optim.SGD(lr_net.parameters(), lr=lr, momentum=0.9) # 采用随机梯度下降法
# ============================ step 5/5 模型训练 ============================
for iteration in range(1000):
# 前向传播
y_pred = lr_net(train_x) # 把训练数据输入到模型,得到输出,有了输出就可以计算Loss
# 计算 loss
loss = loss_fn(y_pred.squeeze(), train_y) # 将标签和模型输出同时输入给损失函数,它会计算损失loss,loss计算梯度
# 反向传播
loss.backward()
# 更新参数
optimizer.step() # 有了梯度就可以采用优化器更新权值
# 清空梯度
optimizer.zero_grad()
# 绘图
if iteration % 20 == 0:
mask = y_pred.ge(0.5).float().squeeze() # 以0.5为阈值进行分类
correct = (mask == train_y).sum() # 计算正确预测的样本个数
acc = correct.item() / train_y.size(0) # 计算分类准确率
plt.scatter(x0.data.numpy()[:, 0], x0.data.numpy()[:, 1], c='r', label='class 0') # 绘制训练数据
plt.scatter(x1.data.numpy()[:, 0], x1.data.numpy()[:, 1], c='b', label='class 1')
w0, w1 = lr_net.features.weight[0]
w0, w1 = float(w0.item()), float(w1.item())
plot_b = float(lr_net.features.bias[0].item())
plot_x = np.arange(-6, 6, 0.1) # 绘制逻辑回归模型
plot_y = (-w0 * plot_x - plot_b) / w1
plt.xlim(-5, 7)
plt.ylim(-7, 7)
plt.plot(plot_x, plot_y)
plt.text(-5, 5, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.title("Iteration: {}\nw0:{:.2f} w1:{:.2f} b: {:.2f} accuracy:{:.2%}".format(iteration, w0, w1, plot_b, acc))
plt.legend()
plt.show()
plt.pause(0.5)
if acc > 0.99: # 模型训练停止条件 准确率大于99%停止
break
结果:
中间图略