一.Pytorch基础操作
注:详细说明在代码注释中, 为保证代码完整性, 以下代码会有重复部分, 重点会在题目外叙述
1.numpy建立矩阵
import numpy as np
# numpy中建立4 * 4的 全为1的矩阵
a_numpy = np.ones([4, 4])
b_numpy = np.ones([4, 4])
# 矩阵的点乘
c_numpy = a_numpy @ b_numpy
# 使用 numpy 建立矩阵的类型是 numpy.ndarray
print("c_numpy的数据类型: %s, 值: %s" % (c_numpy.dtype, c_numpy))
c_numpy的数据类型: float64,
值:
[[4. 4. 4. 4.]
[4. 4. 4. 4.]
[4. 4. 4. 4.]
[4. 4. 4. 4.]]
2.torch建立矩阵
import torch
# torch中建立4 * 4的 全为1的矩阵
a_torch = torch.ones([4, 4])
b_torch = torch.ones([4, 4])
# torch中矩阵的点乘
c_torch = a_torch @ b_torch
# 使用 torch 建立的矩阵类型是 torch.Tensor(张量)
print("c_torch的数据类型: %s, 值: %s" % (c_torch.dtype, c_torch))
c_torch的数据类型: torch.float32, 值:
tensor([[4., 4., 4., 4.],
[4., 4., 4., 4.],
[4., 4., 4., 4.],
[4., 4., 4., 4.]])
3.将 numpy 所建立的矩阵转换为 torch 所用的矩阵类型 Tensor(张量)
import torch
import numpy as np
# 使用 torch.from_numpy() 方法进行转换
d_numpy = np.ones([4, 4])
print("d_numpy的数据类型: %s, 形状: %s" % (d_numpy.dtype, d_numpy.shape))
# numpy 中的矩阵是什么类型, 转换之后就是什么类型
d_torch = torch.from_numpy(d_numpy)
print("d_torch的数据类型: %s, 形状: %s" % (d_torch.dtype, d_torch.shape))
# 使用 torch.Tensor()方法进行转换
e_numpy = np.ones([4, 4])
print("e_numpy的数据类型: %s, 形状: %s" % (e_numpy.dtype, e_numpy.shape))
# 需要显示的转换为双精度类型
e_torch = torch.Tensor(e_numpy).double()
print("e_torch的数据类型: %s, 形状: %s" % (e_torch.dtype, e_torch.shape))
d_numpy的数据类型: float64, 形状: (4, 4)
d_torch的数据类型: torch.float64, 形状: torch.Size([4, 4])
e_numpy的数据类型: float64, 形状: (4, 4)
e_torch的数据类型: torch.float64, 形状: torch.Size([4, 4])
4.将 torch 所用的矩阵类型 Tensor(张量)转换为 numpy 所建立的矩阵
import torch
f_torch = torch.ones([4, 4])
print("f_torch的数据类型: %s, 形状: %s" % (f_torch.dtype, f_torch.shape))
# 将torch建立的Tensor类型转换为numpy需要的array类型
g_numpy = f_torch.numpy()
print("g_numpy的数据类型: %s, 形状: %s" % (g_numpy.dtype, g_numpy.shape))
f_torch的数据类型: torch.float32, 形状: torch.Size([4, 4])
g_numpy的数据类型: float32, 形状: (4, 4)
5.矩阵的内存问题
torch 和 numpy 共享内存
5.1 生成新的Tensor矩阵
import torch
import numpy as np
h_numpy = np.ones([4, 4])
h_torch = torch.from_numpy(h_numpy)
# 生成新的Tensor矩阵
h_torch = h_torch + h_torch
print("原始h_numpy: %s, h_torch: %s" % (h_numpy, h_torch))
原始h_numpy:
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]],
h_torch:
tensor([[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.]], dtype=torch.float64)
5.2 影响原始的 n_numpy
import torch
import numpy as np
i_numpy = np.ones([4, 4])
i_torch = torch.from_numpy(i_numpy)
# 影响原始的 n_numpy
i_torch += i_torch
print("原始i_numpy: %s, i_torch: %s" % (i_numpy, i_torch))
原始i_numpy:
[[2. 2. 2. 2.]
[2. 2. 2. 2.]
[2. 2. 2. 2.]
[2. 2. 2. 2.]],
i_torch:
tensor(
[[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.]], dtype=torch.float64)
5.3 将原始的 j_numpy 和 j_torch都设置为 0
import torch
import numpy as np
j_numpy = np.ones([4, 4])
j_torch = torch.from_numpy(j_numpy)
# 将原始的 j_numpy 和 j_torch都设置为 0
j_torch.zero_()
print("原始j_numpy: %s, j_torch: %s" % (j_numpy, j_torch))
原始j_numpy:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]],
j_torch:
tensor([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]], dtype=torch.float64)
二.Pytorch自动求导
pytorch的自动求导功能建立在计算图上, 我们先看下计算图的概念, 比如
对
于
一
个
线
性
模
型
Y
=
X
⋅
W
+
b
,
损
失
函
数
L
=
(
Y
−
D
)
2
对于一个线性模型 Y = X\cdot W + b ,损失函数 L= (Y-D)^2
对于一个线性模型Y=X⋅W+b,损失函数L=(Y−D)2
计算图如下
计算图是一种有向图, torch会记录下运算的每一步, 以便求导的时候反向推演, 这边是自动求导的原理。
2.1 Tensor类型矩阵和反向传递
进行反向求导需要建立torch专用的Tensor类型矩阵, requires_grad 参数设置为True,使用 backward()方法进行反向传递。
注意:backward()反向传递时默认为标量(单值)。
import torch
# 建立 Tensor类型矩阵 [1, 2]
x = torch.tensor([1.0, 2.0], requires_grad=True)
print("x值 : %s" % x)
# 构建 y 函数 y = x^3 + x^2 + x +1
y = x ** 3 + x ** 2 + x + 1
print("y值 : %s" % y)
# 对 y 求均值, 类似于 y = 0.5 * y, 但是 z.backward() 只有标量(即单值)才能反向求导, 所以采用 mean() 函数
# 注意: torch在利用计算图求导的过程中根节点都是一个标量, 当根节点即函数的因变量为一个向量的时候
# 会构建多个计算图对该向量中的每一个元素分别进行求导
z = torch.mean(y)
print("z值 : %s" % z)
# 进行反向传播, 计算梯度
z.backward()
# 取出对x 求导的梯度
x_grad = x.grad
print(x_grad)
x值 : tensor([1., 2.], requires_grad=True)
y值 : tensor([ 4., 15.], grad_fn=<AddBackward0>)
z值 : tensor(9.5000, grad_fn=<MeanBackward0>)
tensor([3.0000, 8.5000])
2.2 对向量进行反向传递
backward() 函数传入参数的梯度的系数即可对向量进行反向传递。
import torch
# 建立 Tensor类型矩阵 [1, 2]
x = torch.tensor([1.0, 2.0], requires_grad=True)
print("x值 : %s" % x)
# 构建 y 函数 y = x^3 + x^2 + x +1
y = x ** 3 + x ** 2 + x + 1
print("y值 : %s" % y)
# 分别计算
z = y * 0.5
print("z值 : %s" % z)
# 进行反向传播, 计算梯度(参数 torch.tensor([1, 1]), 这个是求得 x 的梯度的系数,
# 不然只能对标量进行反向求导, 也可以直接写为 torch.ones_like(y) )
z.backward(torch.tensor([1, 1]))
# 取出对x 求导的梯度
x_grad = x.grad
print(x_grad)
x值 : tensor([1., 2.], requires_grad=True)
y值 : tensor([ 4., 15.], grad_fn=<AddBackward0>)
z值 : tensor([2.0000, 7.5000], grad_fn=<MulBackward0>)
tensor([3.0000, 8.5000])
2.3 梯度的累计和grad.zero_()方法
在迭代过程当中, 如果不对梯度进行操作, 则每迭代一次, 梯度便会进行累加, grad.zero_() 方法可以将梯度置零。
import torch
# 建立 Tensor类型矩阵 [1, 2]
x = torch.tensor([1.0, 2.0], requires_grad=True)
print("x值 : %s" % x)
for step in range(3):
print(" ------------ ")
y = x ** 3 + x ** 2 + x + 1
print("y值 : %s" % y)
z = torch.mean(y)
print("z值 : %s" % z)
z.backward()
x_grad = x.grad
print("梯度 : %s" % x_grad)
# 将 x 的梯度置零, 否则会累加
x.grad.zero_()
x值 : tensor([1., 2.], requires_grad=True)
------------
y值 : tensor([ 4., 15.], grad_fn=<AddBackward0>)
z值 : tensor(9.5000, grad_fn=<MeanBackward0>)
梯度 : tensor([3.0000, 8.5000])
------------
y值 : tensor([ 4., 15.], grad_fn=<AddBackward0>)
z值 : tensor(9.5000, grad_fn=<MeanBackward0>)
梯度 : tensor([3.0000, 8.5000])
------------
y值 : tensor([ 4., 15.], grad_fn=<AddBackward0>)
z值 : tensor(9.5000, grad_fn=<MeanBackward0>)
梯度 : tensor([3.0000, 8.5000])
三.Pytorch机器学习
接下来我们尝试用 PyTorch 进行对如下数据进行回归分析:
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 制造特征, 以x=0为对称轴, 标准差为1的正态分布生成1000个点
# 注意: 要以 矩阵的方式生成点, 即 1000行 1列的矩阵
x = np.random.normal(0, 1, [1000, 1])
# 生成标签, 大概遵从 y = x^2 + 2x + 1 的曲线分布
d = x ** 2 + 2 * x + 1 + np.random.normal(0, 0.3, [1000, 1])
# 绘制散点图
plt.scatter(x, d, c="k")
plt.show()
3.1 利用Pytorch的自动求导功能进行多项式回归
"""
多项式回归( 反向传播和自动求导 )
"""
import torch
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 制造特征, 以x=0为对称轴, 标准差为1的正态分布生成1000个点
# 注意: 要以 矩阵的方式生成点, 即 1000行 1列的矩阵
x = np.random.normal(0, 1, [1000, 1])
# 生成标签, 大概遵从 y = x^2 + 2x + 1 的曲线分布
d = x ** 2 + 2 * x + 1 + np.random.normal(0, 0.3, [1000, 1])
# 二. 构建数学模型
# 建立通用线性回归模型, y = x @ w +b, w, b为可训练参数向量
def model(x, w, b):
return x @ w + b
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
# 拼接训练集特征 [x^2, x]
xin = torch.cat((xin ** 2, xin), dim=1)
din = torch.from_numpy(d).float()
# 四. 为可训练参数赋予初始值
# 按标准正态分布随机生成一个 2行1列的Tensor张量类型矩阵作为特征系数
# 将两个可训练参数设置为 需要计算梯度
# requires_grad参数说明: 比如 y = x^2, z = 2 * y, 将 x 参数的 requires_grad 设置为 True,
# 单独执行一些类似于 forward 正向传播操作是不会对最终结果有影响的。但会增加内存或显存占用量, 因为整个计算图都会保留着。
# 但是如果需要进行 backward 反向传播操作, 比如 z.backward(), torch 会自动根据计算图反向计算梯度(只计算不更新)
# 当执行 optimizer.step() 操作时,计算图上的参数都会根据 backward 计算的梯度来更新。
w = torch.randn([2, 1], requires_grad=True)
b = torch.randn([1], requires_grad=True)
# 五. 开始训练
# 设置学习率为 0.1
eta = 0.1
# 开始迭代
for step in range(100):
# 代入模型进行计算
y = model(xin, w, b)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = (y - din) ** 2
# 为进行反向传播, loss应该是一个标量
loss = loss.mean()
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# loss为计算图最终节点, 以下步骤已经不需要再加入到计算图中
# torch.no_grad() 会关闭自动求导引擎的, 因此能节省显存并加速。
# 注意: 如果不进行 torch.no_grad()
# 1. 计算图的叶子节点不能使用 w -= eta * w.grad 形式的 in-place 操作, 会报错:
# RuntimeError: a leaf Variable that requires grad is being used in an in-place operation.
# 但是当采用 w = w - eta * w.grad 的形式时, 又会导致非 in-place, 在新的内存地址 会产生新的 w,
# 此时 w变成了中间节点, 而中间节点的 grad 会因为节约内存而被删除, 所以此时的 w.grad会报错
# 'NoneType' object has no attribute 'zero_'
with torch.no_grad():
# 进行梯度下降
w -= eta * w.grad
b -= eta * b.grad
# inplace操作, 将计算的梯度置0, 否则会累加
w.grad.zero_()
b.grad.zero_()
# 六. 训练结果绘制
# 生成-3 到 3之间 100个数, 并变为 100 x 1 的矩阵
x_plt = np.linspace(-3, 3, 100)[..., np.newaxis]
# 将 x_plt 转为 Tensor 类型
xp = torch.from_numpy(x_plt).float()
# 拼接特征 [x^2, x]
xp = torch.cat((xp ** 2, xp), dim=1)
# 代入模型
yp = model(xp, w, b)
# 由于 y_plt 为计算图的一部分, 所以进行一下切分, 只要 y 节点
y_plt = yp.detach().numpy()
# 绘制散点图
plt.scatter(x, d, c="k")
# 绘制曲线图
plt.plot(x_plt, y_plt, c="r", lw=2)
plt.show()
3.2 利用Pytorch内置的 optim 优化器进行机器学习
torch.optim提供了一些内置的优化优化算法, 我们这里采用 SGD 随机梯度下降法(Stochastic Gradient Descent), optimizer.step()可以直接进行迭代。
"""
多项式回归( 构建优化器 )
"""
import torch
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 制造特征, 以x=0为对称轴, 标准差为1的正态分布生成1000个点
# 注意: 要以 矩阵的方式生成点, 即 1000行 1列的矩阵
x = np.random.normal(0, 1, [1000, 1])
# 生成标签, 大概遵从 y = x^2 + 2x + 1 的曲线分布
d = x ** 2 + 2 * x + 1 + np.random.normal(0, 0.3, [1000, 1])
# 二. 构建数学模型
# 建立通用线性回归模型, y = x @ w +b, w, b为可训练参数向量
def model(x, w, b):
return x @ w + b
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
# 拼接训练集特征 [x^2, x]
xin = torch.cat((xin ** 2, xin), dim=1)
din = torch.from_numpy(d).float()
# 四. 为可训练参数赋予初始值
# 按标准正态分布随机生成一个 2行1列的Tensor张量类型矩阵作为特征系数
# 将两个可训练参数设置为 需要计算梯度
# requires_grad参数说明: 比如 y = x^2, z = 2 * y, 将 x 参数的 requires_grad 设置为 True,
# 单独执行一些类似于 forward 正向传播操作是不会对最终结果有影响的。但会增加内存或显存占用量, 因为整个计算图都会保留着。
# 但是如果需要进行 backward 反向传播操作, 比如 z.backward(), torch 会自动根据计算图反向计算梯度(只计算不更新)
# 当执行 optimizer.step() 操作时,计算图上的参数都会根据 backward 计算的梯度来更新。
w = torch.randn([2, 1], requires_grad=True)
b = torch.randn([1], requires_grad=True)
# 五. 开始训练
# 设置学习率为 0.1
eta = 0.1
# 构建优化器, 采用 随机梯度下降法(Stochastic Gradient Descent), 传入 可训练参数 w, b 组成的矩阵和学习率
optimizer = torch.optim.SGD([w, b], eta)
# 开始迭代
for step in range(100):
# 代入模型进行计算
y = model(xin, w, b)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = (y - din) ** 2
# 为进行反向传播, loss应该是一个标量
loss = loss.mean()
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# 进行迭代, 与 传统的 w -= eta * w.grad 相比, 简化了一些步骤
optimizer.step()
# 将优化器已计算的梯度置0, 否则会累加
optimizer.zero_grad()
# 六. 训练结果绘制
# 生成-3 到 3之间 100个数, 并变为 100 x 1 的矩阵
x_plt = np.linspace(-3, 3, 100)[..., np.newaxis]
# 将 x_plt 转为 Tensor 类型
xp = torch.from_numpy(x_plt).float()
# 拼接特征 [x^2, x]
xp = torch.cat((xp ** 2, xp), dim=1)
# 代入模型
yp = model(xp, w, b)
# 由于 y_plt 为计算图的一部分, 所以进行一下切分, 只要 y 节点
y_plt = yp.detach().numpy()
# 绘制散点图
plt.scatter(x, d, c="k")
# 绘制曲线图
plt.plot(x_plt, y_plt, c="r", lw=2)
plt.show()
3.3 利用 Model 类进一步优化
我们可以将 目标函数 和 需要进行训练的参数封装在 Python 类中, 以进行结构优化
"""
多项式回归( 构建模型类, 优化算法结构 )
"""
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 制造特征, 以x=0为对称轴, 标准差为1的正态分布生成1000个点
# 注意: 要以 矩阵的方式生成点, 即 1000行 1列的矩阵
x = np.random.normal(0, 1, [1000, 1])
# 生成标签, 大概遵从 y = x^2 + 2x + 1 的曲线分布
d = x ** 2 + 2 * x + 1 + np.random.normal(0, 0.3, [1000, 1])
# 二. 构建数学模型
# 将整个数学模型和参数进行封装
# 继承 nn.Module
class Model(nn.Module):
# 初始化
def __init__(self):
# 将父类初始化
super().__init__()
# 定义可训练参数并赋予初始值,并封装注册到nn.parameter.Parameter类中,
# Parameter封装好的参数可以通过 model.parameters() 调用
self.w = nn.parameter.Parameter(torch.randn([2, 1]))
self.b = nn.parameter.Parameter(torch.randn([1]))
def forward(self, x):
# 正向计算过程, 即我们定义的数学模型, y = x @ w +b
return x @ self.w + self.b
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生
# 的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
# 拼接训练集特征 [x^2, x]
xin = torch.cat((xin ** 2, xin), dim=1)
din = torch.from_numpy(d).float()
# 四. 开始训练
# 设置学习率为 0.1
eta = 0.1
# 调用封装好的模型
model = Model()
# 开始进行训练
model.train()
# 构建优化器, 采用 随机梯度下降法(Stochastic Gradient Descent)
# 调用 model.parameters() 传入参数和学习率
optimizer = torch.optim.SGD(model.parameters(), eta)
# 开始迭代
for step in range(100):
# 代入模型进行计算
y = model(xin)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = (y - din) ** 2
# 为进行反向传播, loss应该是一个标量
loss = loss.mean()
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# 进行迭代
optimizer.step()
# 将优化器已计算的梯度置0, 否则会累加
optimizer.zero_grad()
# 五. 训练结果绘制
# 生成-3 到 3之间 100个数, 并变为 100 x 1 的矩阵
x_plt = np.linspace(-3, 3, 100)[..., np.newaxis]
# 将 x_plt 转为 Tensor 类型
xp = torch.from_numpy(x_plt).float()
# 拼接特征 [x^2, x]
xp = torch.cat((xp ** 2, xp), dim=1)
# 代入模型
yp = model(xp)
# 由于 y_plt 为计算图的一部分, 所以进行一下截断 detach() , 只要 y 节点
y_plt = yp.detach().numpy()
# 绘制散点图
plt.scatter(x, d, c="k")
# 绘制曲线图
plt.plot(x_plt, y_plt, c="r", lw=2)
plt.show()
3.4 将训练好的模型保存到本地
torch.save()方法可以将模型以文件的形式保存到本地,model.state_dict() 方法为可训练参数信息。
"""
多项式回归( 模型的保存 )
"""
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 制造特征, 以x=0为对称轴, 标准差为1的正态分布生成1000个点
# 注意: 要以 矩阵的方式生成点, 即 1000行 1列的矩阵
x = np.random.normal(0, 1, [1000, 1])
# 生成标签, 大概遵从 y = x^2 + 2x + 1 的曲线分布
d = x ** 2 + 2 * x + 1 + np.random.normal(0, 0.3, [1000, 1])
# 二. 构建数学模型
# 将整个数学模型和参数进行封装
# 继承 nn.Module
class Model(nn.Module):
# 初始化
def __init__(self):
# 将父类初始化
super().__init__()
# 定义可训练参数并赋予初始值,并封装注册到nn.parameter.Parameter类中,
# Parameter封装好的参数可以通过 model.parameters() 调用
self.w = nn.parameter.Parameter(torch.randn([2, 1]))
self.b = nn.parameter.Parameter(torch.randn([1]))
def forward(self, x):
# 正向计算过程, 即我们定义的数学模型, y = x @ w +b
return x @ self.w + self.b
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
# 拼接训练集特征 [x^2, x]
xin = torch.cat((xin ** 2, xin), dim=1)
din = torch.from_numpy(d).float()
# 四. 为可训练参数赋予初始值.
# 设置学习率为 0.1
eta = 0.1
# 调用封装好的模型
model = Model()
# 开始进行训练
model.train()
# 构建优化器, 采用 随机梯度下降法(Stochastic Gradient Descent)
# 调用 model.parameters() 传入参数和学习率
optimizer = torch.optim.SGD(model.parameters(), eta)
# 开始迭代
for step in range(100):
# 代入模型进行计算
y = model(xin)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = (y - din) ** 2
# 为进行反向传播, loss应该是一个标量
loss = loss.mean()
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# 进行迭代
optimizer.step()
# 将优化器已计算的梯度置0, 否则会累加
optimizer.zero_grad()
# 四. 训练结果绘制
# 将训练好的模型保存到本地, model.state_dict()中为可训练参数信息
torch.save(model.state_dict(), "regression.pt")
3.5 加载本地模型进行预测
model.load_state_dict(torch.load(“regression.pt”)) 方法可以对模型进行加载复原
"""
多项式回归( 本地模型的加载 )
"""
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 一. 我们保存的模型信息
# 将整个数学模型和参数进行封装
# 继承 nn.Module
class Model(nn.Module):
# 初始化
def __init__(self):
# 将父类初始化
super().__init__()
# 定义可训练参数并赋予初始值,并封装注册到nn.parameter.Parameter类中,
# Parameter封装好的参数可以通过 model.parameters() 调用
self.w = nn.parameter.Parameter(torch.randn([2, 1]))
self.b = nn.parameter.Parameter(torch.randn([1]))
def forward(self, x):
# 正向计算过程, 即我们定义的数学模型, y = x @ w +b
return x @ self.w + self.b
# 二. 加载模型
model = Model()
model.load_state_dict(torch.load("regression.pt"))
# 三. 绘制测试集和预测曲线
# 3.1 绘制测试集
# 测试集的特征, 以x=0为对称轴, 标准差为1的正态分布生成1000个点
# 注意: 要以 矩阵的方式生成点, 即 1000行 1列的矩阵
x = np.random.normal(0, 1, [1000, 1])
# 测试集标签, 大概遵从 y = x^2 + 2x + 1 的曲线分布
d = x ** 2 + 2 * x + 1 + np.random.normal(0, 0.3, [1000, 1])
# 绘制散点图
plt.scatter(x, d, c="k")
# 3.2 绘制预测曲线
# 生成-3 到 3之间 100个数, 并变为 100 x 1 的矩阵
x_plt = np.linspace(-3, 3, 100)[..., np.newaxis]
# 将 x_plt 转为 Tensor 类型
xp = torch.from_numpy(x_plt).float()
# 拼接特征 [x^2, x]
xp = torch.cat((xp ** 2, xp), dim=1)
# 代入模型
yp = model(xp)
# 由于 y_plt 为计算图的一部分, 所以进行一下截断 detach() , 只要 y 节点
y_plt = yp.detach().numpy()
# 绘制曲线图
plt.plot(x_plt, y_plt, c="r", lw=2)
plt.show()
3.6 使用Pytorch 内置的数学模型进行预测
"""
多项式回归( 使用内置的线性模型 )
"""
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 制造特征, 以x=0为对称轴, 标准差为1的正态分布生成1000个点
# 注意: 要以 矩阵的方式生成点, 即 1000行 1列的矩阵
x = np.random.normal(0, 1, [1000, 1])
# 生成标签, 大概遵从 y = x^2 + 2x + 1 的曲线分布
d = x ** 2 + 2 * x + 1 + np.random.normal(0, 0.3, [1000, 1])
# 二. 构建数学模型
# 将整个数学模型和参数进行封装
# 继承 nn.Module
class Model(nn.Module):
# 初始化
def __init__(self):
# 将父类初始化
super().__init__()
# 定义线性模型, 并设特征为 2 个, 输出为 1个, 即标签
self.layers = nn.Linear(2, 1)
def forward(self, x):
# 返回线性模型
return self.layers(x)
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生
# 的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
# 拼接训练集特征 [x^2, x]
xin = torch.cat((xin ** 2, xin), dim=1)
din = torch.from_numpy(d).float()
# 四. 进行训练.
# 设置学习率为 0.1
eta = 0.1
# 调用封装好的模型
model = Model()
# 开始进行训练
model.train()
# 损失函数采用 MSE(Mean Square Error 均方误差)作为损失函数
loss_fn = nn.MSELoss()
# 构建优化器, 采用 随机梯度下降法(Stochastic Gradient Descent)
# 调用 model.parameters() 传入参数和学习率
optimizer = torch.optim.SGD(model.parameters(), eta)
# 开始迭代
for step in range(100):
# 代入模型进行计算
y = model(xin)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = loss_fn(y, din)
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# 进行迭代
optimizer.step()
# 将优化器已计算的梯度置0, 否则会累加
optimizer.zero_grad()
# 五. 训练结果绘制
# 生成-3 到 3之间 100个数, 并变为 100 x 1 的矩阵
x_plt = np.linspace(-3, 3, 100)[..., np.newaxis]
# 将 x_plt 转为 Tensor 类型
xp = torch.from_numpy(x_plt).float()
# 拼接特征 [x^2, x]
xp = torch.cat((xp ** 2, xp), dim=1)
# 代入模型
yp = model(xp)
# 由于 y_plt 为计算图的一部分, 所以进行一下截断 detach() , 只要 y 节点
y_plt = yp.detach().numpy()
# 绘制散点图
plt.scatter(x, d, c="k")
# 绘制曲线图
plt.plot(x_plt, y_plt, c="r", lw=2)
plt.show()
以上结果基本相同, ps:话说写回归分析真的是快吐了, 所以接下来, 我们还是回归分析T^T
四.Pytorch 解决二分类问题
4.1 线性可分的二分类问题
我们看一下如下数据:
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 制造数据
# 数据类型1: 以x=-2为对称轴, 标准差为1的正态分布生成 500 * 2 的矩阵, 即 500个点
# 数据类型2: 以x=2位对称轴, 标准差为1的正态分布生成 500 * 2 的矩阵, 也是 500个点
# 拼接两个矩阵, 生成 1000 个点, 横纵坐标可以看成两个特征
x = np.concatenate([np.random.normal(-2, 1, [500, 2]), np.random.normal(2, 1, [500, 2])], axis=0)
# 生成500个全为0的 500 * 1的矩阵和 500个全为1的 500 * 1 的矩阵,
# 并进行行方向上的拼接组成 1000 * 1的矩阵作为标签
d = np.concatenate([np.zeros([500, 1]), np.ones([500, 1])], axis=0)
# 绘制散点图, 观察下生成的点
plt.scatter(x[:, 0], x[:, 1], c=d[:, 0])
plt.show()
面对这种数据, 我们可以很容易的画一条斜线就直接分开了两种类型的数据, 例如:
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as grid_spec
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 制造数据
# 数据类型1: 以x=-2为对称轴, 标准差为1的正态分布生成 500 * 2 的矩阵, 即 500个点
# 数据类型2: 以x=2位对称轴, 标准差为1的正态分布生成 500 * 2 的矩阵, 也是 500个点
# 拼接两个矩阵, 生成 1000 个点, 横纵坐标可以看成两个特征
x = np.concatenate([np.random.normal(-2, 1, [500, 2]), np.random.normal(2, 1, [500, 2])], axis=0)
# 生成500个全为0的 500 * 1的矩阵和 500个全为1的 500 * 1 的矩阵,
# 并进行行方向上的拼接组成 1000 * 1的矩阵作为标签
d = np.concatenate([np.zeros([500, 1]), np.ones([500, 1])], axis=0)
# 二. 构建数学模型
# 将整个数学模型和参数进行封装
# 继承 nn.Module
class Model(nn.Module):
def __init__(self):
super().__init__()
# 定义线性模型, 并设特征为 2 个, 输出为 1个
self.linear = nn.Linear(2, 1)
# 采用 Sigmoid ( y = 1/(1 + e^x) )作为激活函数, 使输出值在 0 - 1 之间, 同时也可以当成预测是否值为 1 的置信度
self.sigmoid = nn.Sigmoid()
# 正向传播过程
def forward(self, x):
# 先是建立线性模型
y = self.linear(x)
# 再采用 sigmoid 整理数据在 0-1之间
y = self.sigmoid(y)
return y
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生
# 的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
din = torch.from_numpy(d).float()
# 三. 开始训练
# 设置学习率为 0.1
eta = 0.1
# 调用封装好的模型
model = Model()
# 开始进行训练
model.train()
# 损失函数采用 MSE(Mean Square Error 均方误差)作为损失函数
loss_fn = nn.MSELoss()
# 构建优化器, 采用 随机梯度下降法(Stochastic Gradient Descent)
# 调用 model.parameters() 传入参数和学习率
optimizer = torch.optim.SGD(model.parameters(), eta)
for step in range(500):
# 代入模型进行计算
y = model(xin)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = loss_fn(y, din)
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# 进行迭代
optimizer.step()
# 将优化器已计算的梯度置0, 否则会累加
optimizer.zero_grad()
# 四. 绘制训练结果
# 建立编号为1, 大小为 12 * 8 的画图窗口 figure
fig = plt.figure(1, figsize=(12, 8))
# 指定放置子图的网格的几何形状, 为 1行 2列
gs = grid_spec.GridSpec(1, 2)
# 在第一行第一个位置的图像绘制 原始的散点
ax = fig.add_subplot(gs[0, 0])
# 绘制散点, 散点的颜色用标签 d 来区分
ax.scatter(x[:, 0], x[:, 1], c=d[:, 0])
# 利用我们训练出的模型, 绘制等值面
# 生成两组 30 * 30 的坐标矩阵, z1, z2分别对应着横坐标矩阵和纵坐标矩阵, 相同位置的元素组成一个完整的坐标
# 方法: X, Y = np.meshgrid(x, y), x, y为网格点的横纵坐标列向量, X,Y为坐标矩阵
z1, z2 = np.meshgrid(np.linspace(-6, 6, 30), np.linspace(-6, 6, 30))
# 将 z1,z2转换为 900行1列的矩阵, 并进行列向的拼接, 组成一个个的坐标
Z = np.concatenate([z1.reshape(-1, 1), z2.reshape(-1, 1)], axis=1)
# 计算各个网格点的预测值, 以便用 Y 来做插值分析(等值面)
# 以下计算不需要梯度
with torch.no_grad():
Zt = torch.from_numpy(Z).float()
Y = model(Zt)
Y = Y.numpy()
print(np.shape(Y))
# 将 Y 从 900 * 1 的矩阵转换为 30 * 30 的矩阵
Y = np.reshape(Y, [30, 30])
# 在第一行第二个位置的图像绘制 图像
ax = fig.add_subplot(gs[0, 1])
# 以 z1, z2 为横纵坐标, Y为权重绘制(体现在颜色上)
ax.contourf(z1, z2, Y)
# 绘制原始散点
ax.scatter(x[:, 0], x[:, 1], c=d[:, 0])
plt.show()
4.2 线性不可分问题
我们再看一下如下数据:
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 以x=0为对称轴, 标准差为1的正态分布, 随机生成四份点数据, 然后分别向 右上、左下,右下、左上平移[2, 2]
# 拼接四个矩阵, 生成 2000 个点, 横纵坐标可以看成两个特征
x = np.concatenate([
np.random.normal(0, 1, [500, 2]) + np.array([2, 2]),
np.random.normal(0, 1, [500, 2]) + np.array([-2, -2]),
np.random.normal(0, 1, [500, 2]) + np.array([2, -2]),
np.random.normal(0, 1, [500, 2]) + np.array([-2, 2])],
axis=0)
# 生成1000个全为0的 1000 * 1的矩阵和 1000个全为1的 1000 * 1 的矩阵,
# 并进行行方向上的拼接组成 2000 * 1的矩阵作为标签
d = np.concatenate([np.zeros([1000, 1]), np.ones([1000, 1])], axis=0)
# 绘制散点图, 观察下生成的点
plt.scatter(x[:, 0], x[:, 1], c=d[:, 0])
plt.show()
很显然, 这种数据类型, 是无法用普通的线性模型去分类的, 现在的数据特征只有两种, 一个是横坐标, 另一个就是纵坐标, 我们可以尝试对特征进行一下扩充。
import numpy as np
import torch.nn as nn
import matplotlib.pyplot as plt
import matplotlib.gridspec as grid_spec
import torch
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 以x=0为对称轴, 标准差为1的正态分布, 随机生成四份点数据, 然后分别向 右上、左下,右下、左上平移[2, 2]
# 拼接四个矩阵, 生成 2000 个点, 横纵坐标可以看成两个特征
x = np.concatenate([
np.random.normal(0, 1, [500, 2]) + np.array([2, 2]),
np.random.normal(0, 1, [500, 2]) + np.array([-2, -2]),
np.random.normal(0, 1, [500, 2]) + np.array([2, -2]),
np.random.normal(0, 1, [500, 2]) + np.array([-2, 2])],
axis=0)
# 生成1000个全为0的 1000 * 1的矩阵和 1000个全为1的 1000 * 1 的矩阵,
# 并进行行方向上的拼接组成 2000 * 1的矩阵作为标签
d = np.concatenate([np.zeros([1000, 1]), np.ones([1000, 1])], axis=0)
# 二. 构建数学模型
# 将整个数学模型和参数进行封装
# 继承 nn.Module
class Model(nn.Module):
def __init__(self):
super().__init__()
# 定义线性模型, 并设特征为 5 个, 标签为 1个
self.linear = nn.Linear(5, 1)
# 采用 Sigmoid ( y = 1/(1 + e^x) )作为激活函数, 使输出值在 0 - 1 之间, 同时也可以当成预测是否值为 1 的置信度
self.sigmoid = nn.Sigmoid()
# 正向传播过程
def forward(self, x):
# 我们将x扩大为5个特征, 分别为 x 横纵坐标, x横纵坐标的平方, x的横坐标乘以x的纵坐标
x = torch.cat([x, x ** 2, x[:, 0:1] * x[:, 1:2]], dim=1)
# 先是建立线性模型
y = self.linear(x)
# 再采用 sigmoid 整理数据在 0-1之间
y = self.sigmoid(y)
return y
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生
# 的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
din = torch.from_numpy(d).float()
# 三. 开始训练
# 设置学习率为 0.1
eta = 0.1
# 调用封装好的模型
model = Model()
# 开始进行训练
model.train()
# 损失函数采用 MSE(Mean Square Error 均方误差)作为损失函数
loss_fn = nn.MSELoss()
# 构建优化器, 采用 随机梯度下降法(Stochastic Gradient Descent)
# 调用 model.parameters() 传入参数和学习率
optimizer = torch.optim.SGD(model.parameters(), eta)
for step in range(500):
# 代入模型进行计算
y = model(xin)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = loss_fn(y, din)
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# 进行迭代
optimizer.step()
# 将优化器已计算的梯度置0, 否则会累加
optimizer.zero_grad()
# 四. 绘制训练结果
# 建立编号为1, 大小为 12 * 8 的画图窗口 figure
fig = plt.figure(1, figsize=(12, 8))
# 指定放置子图的网格的几何形状, 为 1行 2列
gs = grid_spec.GridSpec(1, 2)
# 在第一行第一个位置的图像绘制 原始的散点
ax = fig.add_subplot(gs[0, 0])
# 绘制散点, 散点的颜色用标签 d 来区分
ax.scatter(x[:, 0], x[:, 1], c=d[:, 0])
# 利用我们训练出的模型, 绘制等值面
# 生成两组 30 * 30 的坐标矩阵, z1, z2分别对应着横坐标矩阵和纵坐标矩阵, 相同位置的元素组成一个完整的坐标
# 方法: X, Y = np.meshgrid(x, y), x, y为网格点的横纵坐标列向量, X,Y为坐标矩阵
z1, z2 = np.meshgrid(np.linspace(-6, 6, 30), np.linspace(-6, 6, 30))
# 将 z1,z2转换为 900行1列的矩阵, 并进行列向的拼接, 组成一个个的坐标
Z = np.concatenate([z1.reshape(-1, 1), z2.reshape(-1, 1)], axis=1)
# 计算各个网格点的预测值, 以便用 Y 来做插值分析(等值面)
# 以下计算不需要梯度
with torch.no_grad():
Zt = torch.from_numpy(Z).float()
Y = model(Zt)
Y = Y.numpy()
print(np.shape(Y))
# 将 Y 从 900 * 1 的矩阵转换为 30 * 30 的矩阵
Y = np.reshape(Y, [30, 30])
# 在第一行第二个位置的图像绘制 图像
ax = fig.add_subplot(gs[0, 1])
# 以 z1, z2 为横纵坐标, Y为权重绘制(体现在颜色上)
ax.contourf(z1, z2, Y)
# 绘制原始散点
ax.scatter(x[:, 0], x[:, 1], c=d[:, 0])
plt.show()
可以看到, 当我们将两个特征值,扩充到了 5 个的时候,此种数据类型已经得到了较好的区分,但问题是,对于扩充的特征类型,该如何选择呢,如果靠人力盲目的去尝试不同的特征组合,是不是不太现实,接下来我们介绍下 PyTorch提供的神经网络模型。
4.3 多层神经网络解决线性不可分问题
我们采用如下图的神经网络模型进行训练, 输入两个特征, 然后让 Linear()模型自动组合特征, 采用激活函数进行非线性变换后再传入第二层神经网络, 再进行非线性变换然后输出结果:
由于每一层的输出都会对下一层的输入产生影响, 我们管这种神经网络也叫全链接神经网络。
import numpy as np
import torch.nn as nn
import matplotlib.pyplot as plt
import matplotlib.gridspec as grid_spec
import torch
plt.switch_backend("TkAgg")
# 一. 准备训练数据集
# 以x=0为对称轴, 标准差为1的正态分布, 随机生成四份点数据, 然后分别向 右上、左下,右下、左上平移[2, 2]
# 拼接四个矩阵, 生成 2000 个点, 横纵坐标可以看成两个特征
x = np.concatenate([
np.random.normal(0, 1, [500, 2]) + np.array([2, 2]),
np.random.normal(0, 1, [500, 2]) + np.array([-2, -2]),
np.random.normal(0, 1, [500, 2]) + np.array([2, -2]),
np.random.normal(0, 1, [500, 2]) + np.array([-2, 2])],
axis=0)
# 生成1000个全为0的 1000 * 1的矩阵和 1000个全为1的 1000 * 1 的矩阵,
# 并进行行方向上的拼接组成 2000 * 1的矩阵作为标签
d = np.concatenate([np.zeros([1000, 1]), np.ones([1000, 1])], axis=0)
# 二. 构建数学模型
# 将整个数学模型和参数进行封装
# 继承 nn.Module
class Model(nn.Module):
def __init__(self):
super().__init__()
# 定义线性模型, 并设特征为 2 个, 输出为 5个(此为第一层神经网络)
self.linear1 = nn.Linear(2, 5)
# 采用 Sigmoid ( y = 1/(1 + e^x) )作为激活函数, 进行非线性变换
self.sigmoid1 = nn.Sigmoid()
# 定义线性模型, 并设特征为 5 个, 输出为 1个(此为第二层神经网络)
self.linear2 = nn.Linear(5, 1)
# 采用 Sigmoid ( y = 1/(1 + e^x) )作为激活函数, 进行非线性变换
self.sigmoid2 = nn.Sigmoid()
# 正向传播过程
def forward(self, x):
# 将x输入到第一层神经网络中
y = self.linear1(x)
# 进行 sigmoid 非线性变换
z = self.sigmoid1(y)
# 继续传递到第二层神经网络中
m = self.linear2(z)
# 进行 sigmoid 非线性变换
n = self.sigmoid2(m)
return n
# 三. 整理训练集的特征和标签
# 将numpy的array类型转换为torch.Tensor类型, 所有numpy产生
# 的数据类型均为64bit浮点类型, 这里将元素设置为32bit浮点类型
xin = torch.from_numpy(x).float()
din = torch.from_numpy(d).float()
# 三. 开始训练
# 设置学习率为 0.1
eta = 0.3
# 调用封装好的模型
model = Model()
# 开始进行训练
model.train()
# 损失函数采用 MSE(Mean Square Error 均方误差)作为损失函数
loss_fn = nn.MSELoss()
# 构建优化器, 采用 随机梯度下降法(Stochastic Gradient Descent)
# 调用 model.parameters() 传入参数和学习率
optimizer = torch.optim.SGD(model.parameters(), eta)
for step in range(10000):
# 代入模型进行计算
y = model(xin)
# 计算损失函数, 然后从损失函数开始进行反向传播
# 损失函数, 这个是计算图的最终节点
loss = loss_fn(y, din)
# 反向传播, 计算梯度, 这个张量的所有梯度将会自动积累到.grad属性
loss.backward()
# 进行迭代
optimizer.step()
# 将优化器已计算的梯度置0, 否则会累加
optimizer.zero_grad()
# 四. 绘制训练结果
# 建立编号为1, 大小为 12 * 8 的画图窗口 figure
fig = plt.figure(1, figsize=(12, 8))
# 指定放置子图的网格的几何形状, 为 1行 2列
gs = grid_spec.GridSpec(1, 2)
# 在第一行第一个位置的图像绘制 原始的散点
ax = fig.add_subplot(gs[0, 0])
# 绘制散点, 散点的颜色用标签 d 来区分
ax.scatter(x[:, 0], x[:, 1], c=d[:, 0])
# 利用我们训练出的模型, 绘制等值面
# 生成两组 30 * 30 的坐标矩阵, z1, z2分别对应着横坐标矩阵和纵坐标矩阵, 相同位置的元素组成一个完整的坐标
# 方法: X, Y = np.meshgrid(x, y), x, y为网格点的横纵坐标列向量, X,Y为坐标矩阵
z1, z2 = np.meshgrid(np.linspace(-6, 6, 30), np.linspace(-6, 6, 30))
# 将 z1,z2转换为 900行1列的矩阵, 并进行列向的拼接, 组成一个个的坐标
Z = np.concatenate([z1.reshape(-1, 1), z2.reshape(-1, 1)], axis=1)
# 计算各个网格点的预测值, 以便用 Y 来做插值分析(等值面)
# 以下计算不需要梯度
with torch.no_grad():
Zt = torch.from_numpy(Z).float()
Y = model(Zt)
Y = Y.numpy()
print(np.shape(Y))
# 将 Y 从 900 * 1 的矩阵转换为 30 * 30 的矩阵
Y = np.reshape(Y, [30, 30])
# 在第一行第二个位置的图像绘制 图像
ax = fig.add_subplot(gs[0, 1])
# 以 z1, z2 为横纵坐标, Y为权重绘制(体现在颜色上)
ax.contourf(z1, z2, Y)
# 绘制原始散点
ax.scatter(x[:, 0], x[:, 1], c=d[:, 0])
plt.show()
看效果, 比我们直接组织特征更加有层次感
五.激活函数
我们在上面进行构建神经网络的时候, 采用了激活函数, 激活函数可以看成一个非线性函数, 我们先看下激活函数的概念:
1.激活函数的定义
激活函数(Activation functions)对于人工神经网络模型去学习、理解非常复杂和非线性的函数来说具有十分重要的作用。它们将非线性特性引入到我们的网络中。在神经元中,输入的 inputs 通过加权,求和后,还被作用了一个函数,这个函数就是激活函数。引入激活函数是为了增加神经网络模型的非线性。没有激活函数的每层都相当于矩阵相乘。就算叠加了若干层之后,无非还是个矩阵相乘罢了。
如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机(Perceptron)。
如果使用激活函数的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。
2.常用的激活函数
2.1 Sigmoid函数
f
(
x
)
=
1
1
+
e
−
x
f(x) = \frac {1}{1 + e^{-x}}
f(x)=1+e−x1
我们画一下 Sigmoid 的函数曲线:
"""
Sigmoid函数
f(x) = 1/ ( 1+ e^(-x)))
"""
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
def model(x):
return 1 / (1 + np.e ** (-x))
# 准备 -5 到 5 均匀排列的 1000 个点
x = np.linspace(-5, 5, 1000)
y = model(x)
# get current axis 获得坐标轴对象
ax = plt.gca()
# 将右边 边沿线颜色设置为空 其实就相当于抹掉这条边
ax.spines['right'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# 设置中心的为(0,0)的坐标轴
# 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))
# x轴数值设置
plt.xlim(-5, 5)
plt.ylim(0, 1)
# 绘制曲线图
plt.plot(x, y, c="r", lw=2)
plt.show()
Sigmoid函数也叫Logistic函数,Sigmoid单调平滑,易于求导,用于隐层神经元输出,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间,可以用作神经网络的阈值函数,。
Sigmoid的缺点:
我们可以看一下 Sigmoid 的导数:
f
(
x
)
=
e
−
x
(
1
+
e
−
x
)
2
f(x) = \frac{e^{-x}}{(1 + e^{-x})^2}
f(x)=(1+e−x)2e−x
函数图像如下:
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
def model(x):
return 1 / ((1 + np.e ** (-x)) ** 2) * (np.e**(-x))
# 准备 -5 到 5 均匀排列的 1000 个点
x = np.linspace(-5, 5, 1000)
y = model(x)
# 绘制曲线图
plt.plot(x, y, c="r", lw=2)
plt.show()
梯度消失现象: 可以看到Sigmoid导数值区间为(0, 0.25), 如果我们初始化神经网络的权值为 [0, 1]之间的随机值,在反向传播时,由于需要从后向前求导,每传递一层梯度值都会至少减小为原来的0.25倍,如果神经网络隐层特别多,那么梯度在穿过多层后将变得非常小接近于0,即出现梯度消失现象。
梯度爆炸: 当网络权值初始化时比较大,而 x 比较小的时候(对于线性 y = wx +b 的模型, w比较大, 而x的变化比较小时, 导致x的导数较小)导致反向求导每层大于 1, 每层累计相乘则会出现梯度爆炸情况。
2.2 Tanh函数
tanh = e x − e − x e x + e − x \tanh = \frac {e^{x}-e^{-x}}{e^{x}+e^{-x}} tanh=ex+e−xex−e−x
y=tanh(x) 为为双曲正切函数。它是一个奇函数,以 (0, 0) 为中心点, 阈值在 (-1, 1)区间, 它的收敛速度相对于Sigmoid更快, 但是 tanh 并没有解决sigmoid梯度消失的问题。
函数图像如下:
"""
Tanh函数
f(x) = ( e^x - e^-x))/ ( e^x+ e^-x))
"""
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
def model(x):
return (np.e ** x - np.e ** (-x))/(np.e ** x + np.e ** (-x))
# 准备 -5 到 5 均匀排列的 1000 个点
x = np.linspace(-5, 5, 1000)
y = model(x)
# get current axis 获得坐标轴对象
ax = plt.gca()
# 将右边 边沿线颜色设置为空 其实就相当于抹掉这条边
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# 设置中心的为(0,0)的坐标轴
# 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))
# x轴数值设置
plt.xlim(-5, 5)
plt.ylim(-1, 1)
# 绘制曲线图
plt.plot(x, y, c="r", lw=2)
plt.show()
2.3 ReLU函数
f ( x ) = m a x ( 0 , x ) f(x) = max(0, x) f(x)=max(0,x)
ReLU是目前最常用的激活函数,在搭建人工神经网络时,如果不确定采用哪种激活函数,可以优先选择ReLU函数。
1.更加有效率的梯度下降以及反向传播,避免了梯度爆炸和梯度消失问题。
2.ReLU函数的收敛速度远快于sigmoid函数和tanh函数。
3.ReLU函数计算速度非常快,没有其他复杂激活函数中诸如指数函数的影响,同时活跃度的分散性使得神经网络整体计算成本下降。
"""
ReLU函数
f(x) = max(0, x)
"""
import numpy as np
import matplotlib.pyplot as plt
plt.switch_backend("TkAgg")
def model_0(x):
return np.zeros(len(x))
def model_x(x):
return x
# 准备 -5 到 5 均匀排列的 1000 个点
x = np.linspace(-5, 5, 1000)
y_0 = model_0(x[0:500])
print(y_0)
y_x = model_x(x[500:])
print(y_x)
y = np.concatenate((y_0, y_x), axis=0)
# get current axis 获得坐标轴对象
ax = plt.gca()
# 将右边 边沿线颜色设置为空 其实就相当于抹掉这条边
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# 设置中心的为(0,0)的坐标轴
# 指定 data 设置的bottom(也就是指定的x轴)绑定到y轴的0这个点上
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))
# x轴数值设置
plt.xlim(-5, 5)
plt.ylim(-5, 5)
# 绘制曲线图
plt.plot(x, y, c="r", lw=2)
plt.show()