人工智能-深度学习-Pytorch与神经网络

一.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=XW+b,L=(YD)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+ex1
我们画一下 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+ex)2ex
函数图像如下:

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+exexex

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()

在这里插入图片描述

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯洁的小魔鬼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值