人工神经网络知识点讲解
人工神经网络知识点讲解
知识导图
人工神经网络
├── 基础认知
│ ├── 神经网络的核心概念
│ ├── 神经元的工作机制
│ └── 网络的层级结构
├── 激活函数
│ ├── 激活函数的作用
│ ├── 常见激活函数:sigmoid/tanh/ReLU/Softmax
│ └── 激活函数的选择
├── 参数初始化
│ ├── 7种初始化方法
│ └── 初始化方法的选择
├── 损失函数
│ ├── 分类任务损失
│ └── 回归任务损失
├── 网络优化
│ ├── 优化算法
│ └── 学习率衰减
├── 正则化方法
│ ├── 正则化的作用
│ ├── Dropout 随机失活
│ └── 批量归一化
└── 实战案例
└── 手机价格分类案例
核心名词解释
-
人工神经网络 (ANN):也简称为神经网络 (NN),是一种模仿生物神经网络结构和功能的计算模型,用来拟合复杂的非线性关系。
-
神经元:神经网络的基本单元,接收输入后进行加权求和,再通过激活函数输出结果,模拟生物神经元的工作方式。
-
输入层 (Input Layer):神经网络的输入层,负责接收原始的输入数据,每个输入特征对应一个神经元。
-
隐藏层 (Hidden Layers):输入层和输出层之间的层,负责对输入数据进行特征提取和变换,神经网络的深度由隐藏层的数量决定。
-
输出层 (Output Layer):神经网络的输出层,负责生成最终的预测结果,根据任务的不同(分类 / 回归)有不同的设计。
-
全连接层:相邻层的所有神经元都相互连接的层,也叫稠密层,每个连接都有对应的权重和偏置。
-
内部状态值:神经元的加权求和结果,公式为
z = W⋅x\+b,是激活函数的输入。 -
激活值:激活函数对内部状态值进行非线性变换后得到的结果,是神经元的最终输出。
-
激活函数:为神经网络引入非线性因素的函数,让网络可以拟合复杂的非线性函数。
-
sigmoid:将输入映射到 (0,1) 区间的激活函数,常用于二分类的输出层。
-
tanh:将输入映射到 (-1,1) 区间的激活函数,是 sigmoid 的平移版本,解决了 sigmoid 的输出非零均值的问题。
-
ReLU:修正线性单元,是目前最常用的隐藏层激活函数,计算简单,缓解了梯度消失问题。
-
Softmax:多分类任务的输出激活函数,将输出转换为概率,所有输出的概率和为 1。
-
参数初始化:对神经网络的权重和偏置进行初始赋值的过程,合适的初始化可以让网络更快收敛,避免梯度消失 / 爆炸。
-
Xavier 初始化:也叫 Glorot 初始化,用来平衡输入和输出的方差,适合 sigmoid、tanh 这类激活函数。
-
Kaiming 初始化:也叫 HE 初始化,针对 ReLU 这类激活函数设计,考虑了 ReLU 的半正则特性,让深层网络更容易训练。
-
损失函数:用来衡量模型预测值和真实值之间差异的函数,是网络训练的优化目标。
-
交叉熵损失:分类任务中常用的损失函数,衡量预测概率分布和真实分布的差异。
-
MAE 损失:也叫 L1 损失,回归任务的损失,计算预测和真实的绝对误差。
-
MSE 损失:也叫 L2 损失,回归任务的损失,计算预测和真实的均方误差。
-
优化器:用来更新神经网络参数,最小化损失函数的算法。
-
学习率衰减:随着训练进行逐渐降低学习率的策略,让模型训练更稳定。
-
Dropout:随机失活正则化,训练时随机让部分神经元失活,防止过拟合。
-
批量归一化 (BN):对批量数据进行标准化和重构的层,缓解梯度消失,加速训练。
-
正则化:用来防止模型过拟合的方法,通过限制模型的复杂度,提升模型的泛化能力。
一、神经网络基础认知
本章节学习目标:
知道什么是人工神经网络
了解神经网络的构成
理解内部状态和激活值的概念
1.1 什么是人工神经网络
人工神经网络( Artificial Neural Network, 简写为 ANN)也简称为神经网络(NN),是一种模仿生物神经网络结构和功能的计算模型。
人脑可以看做是一个生物神经网络,由众多的神经元连接而成。各个神经元传递复杂的电信号,树突接收到输入信号,然后对信号进行处理,通过轴突输出信号。当电信号通过树突进入到细胞核时,会逐渐聚集电荷。达到一定的电位后,细胞就会被激活,通过轴突发出电信号。
1.2 神经元的工作机制
神经网络是由多个神经元组成,构建神经网络就是在构建神经元。
这个过程就像,来源不同树突 (树突都会有不同的权重) 的信息,进行的加权计算,输入到细胞中做加和,再通过激活函数输出细胞值。

1.3 神经网络的层级结构
神经网络中信息只向一个方向移动,即从输入节点向前移动,通过隐藏节点,再向输出节点移动。其中的基本部分是:
-
输入层(Input Layer):即输入 x 的那一层(如图像、文本、声音等)。每个输入特征对应一个神经元。输入层将数据传递给下一层的神经元。
-
输出层(Output Layer):即输出 y 的那一层。输出层的神经元根据网络的任务(回归、分类等)生成最终的预测结果。
-
隐藏层(Hidden Layers):输入层和输出层之间的层,负责对输入数据进行特征提取和变换,神经网络的深度由隐藏层的数量决定。
网络的特点:
-
同一层的神经元之间没有连接
-
第 N 层的每个神经元和第 N-1 层 的所有神经元相连,这就是全连接神经网络
-
全连接神经网络接收的样本数据是二维的,数据在每一层之间需要以二维的形式传递
-
第 N-1 层神经元的输出就是第 N 层神经元的输入
-
每个连接都有一个权重值(w 系数和 b 系数)

1.4 内部状态值和激活值
每一个神经元工作时,会产生 4 个值:
前向传播会产生内部状态值(加权求和值)和激活值;反向传播时会产生激活值梯度和内部状态值梯度。
-
内部状态值:
z = W⋅x\+b-
W:权重矩阵
-
x:输入值
-
b:偏置
-
-
激活值:
a=f\(z\)-
f:激活函数
-
z:内部状态值
-

二、激活函数
本章节学习目标:
知道常见的激活函数
了解激活函数的作用
掌握激活函数的选择方法
2.1 激活函数的作用
激活函数用于对每层的输出数据进行变换,进而为整个网络注入了非线性因素。此时,神经网络就可以拟合各种曲线。
没有引入非线性因素的网络等价于使用一个线性模型来拟合。通过给网络输出增加激活函数,实现引入非线性因素,使得网络模型可以逼近任意函数,提升网络对复杂问题的拟合能力。

2.2 常见的激活函数
2.2.1 sigmoid 激活函数
sigmoid 激活函数可以将输入映射到 (0,1) 的区间,它的公式和导数公式如下:

对应的绘制代码:
# 解决OMP冲突的代码
import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'
# 加这行!切换Matplotlib的后端,绕过PyCharm的兼容bug
import matplotlib
matplotlib.use('TkAgg')
# 导包
import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 指定用Windows自带的微软雅黑,支持中文
plt.rcParams['axes.unicode_minus'] = False # 顺便解决负号显示成方框的问题
# 准备数据
# 准备 -20,20的等差张量,1000点 --> x
x = torch.linspace(start=-20,end=20,steps=1000,requires_grad=False)
print(x.shape,x.ndim)
y = torch.sigmoid(x)
print(y.shape,y.ndim)
# 绘制图像
# **todo 为了把值域图像和导数图像画到一张画布上,创建一个一行两列的画布**
fig,axs = plt.subplots(1,2)
axs[0].plot(x,y)
axs[0].grid()
axs[0].set_title('sigmoid 值域图像')
# plt.show()
print('=======1=======')
# 准备数据
# # 准备x,y数据 -> **todo 注意此处的x开启自动微分,y的本质是梯度**
x = torch.linspace(start=-20,end=20,steps=1000,requires_grad=True)
print(x.shape,x.ndim)
torch.sigmoid(x).sum().backward() # -> # backward():触发反向传播,PyTorch自动帮你计算Sigmoid对x的导数,结果会自动存在 x.grad 里
y = x.grad
print(y.shape,y.ndim)
# 绘制图像
axs[1].plot(x.detach(),x.grad)
axs[1].grid()
axs[1].set_title('Sigmoid 导数图像')
plt.show()
print('=======2=======')
2.2.2 tanh 激活函数
tanh 激活函数可以将输入映射到 (-1,1) 的区间,解决了 sigmoid 输出非零均值的问题,它的公式和导数公式如下:

对应的绘制代码:
# 创建画布和坐标轴
_, axes = plt.subplots(1, 2)
# 函数图像
x = torch.linspace(-20, 20, 1000)
y = torch.tanh(x)
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('Tanh 函数图像')
# 导数图像
x = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.tanh(x).sum().backward()
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('Tanh 导数图像')
plt.show()
2.2.3 ReLU 激活函数
ReLU 是修正线性单元,是目前最常用的隐藏层激活函数,它的公式和导数公式如下:



对应的绘制代码:
# 创建画布和坐标轴
_, axes = plt.subplots(1, 2)
# 函数图像
x = torch.linspace(-20, 20, 1000)
y = torch.relu(x)
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('ReLU 函数图像')
# 导数图像
x = torch.linspace(-20, 20, 1000, requires_grad=True)
torch.relu(x).sum().backward()
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('ReLU 导数图像')
plt.show()
2.2.4 SoftMax 激活函数
softmax 用于多分类过程中,它是二分类函数 sigmoid 在多分类上的推广,目的是将多分类的结果以概率的形式展现出来。
Softmax 就是将网络输出的 logits 通过 softmax 函数,就映射成为 (0,1) 的值,而这些值的累和为 1(满足概率的性质),那么我们将它理解成概率,选取概率最大(也就是值对应最大的)节点,作为我们的预测目标类别。

对应的使用代码:
import torch
scores = torch.tensor([0.2, 0.02, 0.15, 0.15, 1.3, 0.5, 0.06, 1.1, 0.05, 3.75])
# dim = 0,按行计算
probabilities = torch.softmax(scores, dim=-1)
print(probabilities)
# 输出结果:
# tensor([0.0212, 0.0177, 0.0202, 0.0202, 0.0638, 0.0287, 0.0185, 0.0522, 0.0183,0.7392])
2.3 其他激活函数
还有其他的常见激活函数,比如 Leaky ReLU、PReLU、ELU 等:

2.4 激活函数的选择方法
隐藏层的选择
-
优先选择 ReLU 激活函数
-
如果 ReLu 效果不好,那么尝试其他激活,如 Leaky ReLu 等。
-
如果你使用了 ReLU, 需要注意一下 Dead ReLU 问题, 避免出现 0 梯度从而导致过多的神经元死亡。
-
少用使用 sigmoid 激活函数,可以尝试使用 tanh 激活函数
输出层的选择
-
二分类问题选择 sigmoid 激活函数
-
多分类问题选择 softmax 激活函数
-
回归问题选择 identity 激活函数
激活函数对比辨析
| 激活函数 | 输出区间 | 特点 | 适用场景 |
|---|---|---|---|
| sigmoid | (0, 1) | 输出非零均值,容易梯度消失 | 二分类输出层 |
| tanh | (-1, 1) | 输出零均值,缓解了 sigmoid 的部分问题 | 隐藏层(早期) |
| ReLU | [0, +∞) | 计算简单,缓解梯度消失,可能出现 Dead ReLU | 隐藏层(主流) |
| Softmax | (0, 1),总和为 1 | 输出概率分布 | 多分类输出层 |
三、参数初始化
本章节学习目标:
了解常见的参数初始化方法
掌握初始化方法的选择
3.1 常见的初始化方法
参数初始化是对神经网络的权重和偏置进行初始赋值的过程,常见的 7 种初始化方法如下:
基础初始化方法
-
均匀分布初始化:权重参数初始化从区间均匀随机取值,默认区间为(0,1)。可以设置为在 (-1/√d,1/√d) 均匀分布中生成当前神经元的权重,其中 d 为神经元的输入数量
-
正态分布初始化:随机初始化从均值为 0,标准差是 1 的高斯分布中取样,使用一些很小的值对参数 W 进行初始化
-
全 0 初始化:将神经网络中的所有权重参数初始化为 0
-
全 1 初始化:将神经网络中的所有权重参数初始化为 1
-
固定值初始化:将神经网络中的所有权重参数初始化为某个固定值
进阶初始化方法
-
kaiming 初始化(HE 初始化)
HE 初始化分为正态分布的 HE 初始化、均匀分布的 HE 初始化.-
正态分布的 he 初始化:它是从 [0, std] 中抽取样本的,
std = sqrt\(2 / fan\_in\) -
均匀分布的 he 初始化:它从 [-limit,limit] 中的均匀分布中抽取样本,
limit是 sqrt\(6 / fan\_in\) -
fan_in:输入层神经元的个数
-
-
xavier 初始化(Glorot 初始化)
该方法也有两种,一种是正态分布的 xavier 初始化、一种是均匀分布的 xavier 初始化.-
正态化的 Xavier 初始化:它是从 [0, std] 中抽取样本的,
std = sqrt\(2 / \(fan\_in \+ fan\_out\)\) -
均匀分布的 Xavier 初始化:[-limit,limit] 中的均匀分布中抽取样本,
limit 是 sqrt\(6 / \(fan\_in \+ fan\_out\)\) -
fan_in:输入层神经元的个数, fan_out:输出层神经元个数
-
3.2 初始化的代码示例
import torch
import torch.nn.functional as F
import torch.nn as nn
# 1. 均匀分布随机初始化
def test01():
linear = nn.Linear(5, 3)
# 从0-1均匀分布产生参数
nn.init.uniform_(linear.weight)
print(linear.weight.data)
# 2.固定初始化
def test02():
linear = nn.Linear(5, 3)
nn.init.constant_(linear.weight, 5)
print(linear.weight.data)
# 3. 全0初始化
def test03():
linear = nn.Linear(5, 3)
nn.init.zeros_(linear.weight)
print(linear.weight.data)
# 4. 全1初始化
def test04():
linear = nn.Linear(5, 3)
nn.init.ones_(linear.weight)
print(linear.weight.data)
# 5. 正态分布随机初始化
def test05():
linear = nn.Linear(5, 3)
nn.init.normal_(linear.weight, mean=0, std=1)
print(linear.weight.data)
# 6. kaiming 初始化
def test06():
# kaiming 正态分布初始化
linear = nn.Linear(5, 3)
nn.init.kaiming_normal_(linear.weight)
print(linear.weight.data)
# kaiming 均匀分布初始化
linear = nn.Linear(5, 3)
nn.init.kaiming_uniform_(linear.weight)
print(linear.weight.data)
# 7. xavier 初始化
def test07():
# xavier 正态分布初始化
linear = nn.Linear(5, 3)
nn.init.xavier_normal_(linear.weight)
print(linear.weight.data)
# xavier 均匀分布初始化
linear = nn.Linear(5, 3)
nn.init.xavier_uniform_(linear.weight)
print(linear.weight.data)
3.3 初始化方法的选择
根据激活函数选择
-
Sigmoid/Tanh:选择 Xavier 初始化
-
ReLU/Leaky ReLU:选择 kaiming 初始化
根据网络深度选择
-
浅层网络:随机初始化即可
-
深层网络:需要考虑方差平衡,如 Xavier 或 kaiming 初始化
初始化方法对比辨析
| 初始化方法 | 核心特点 | 适用激活函数 | 适用场景 |
|---|---|---|---|
| 均匀 / 正态随机 | 简单随机初始化 | 浅层网络的激活函数 | 简单的浅层网络 |
| 全 0 / 全 1 / 固定值 | 固定值初始化 | 不推荐,容易导致对称问题 | 仅偏置可以使用,权重不推荐 |
| Xavier 初始化 | 平衡输入输出方差 | Sigmoid、Tanh | 早期的深层网络 |
| Kaiming 初始化 | 适配 ReLU 的半正则特性 | ReLU、Leaky ReLU | 现代深层网络 |
四、神经网络搭建实战
我们来构建一个简单的神经网络模型,结合我们学到的激活函数和参数初始化:

搭建步骤
在 pytorch 中定义深度神经网络其实就是层堆叠的过程,继承自 nn.Module,实现两个方法:
-
\_\_init\_\_方法中定义网络中的层结构,主要是全连接层,并进行初始化 -
forward方法,在实例化模型的时候,底层会自动调用该函数。该函数中为初始化定义的 layer 传入数据,进行前向传播等。
代码实现
from torch.nn import Module, Linear
import torch
# TODO 1.自定义模型类继承Module类
class My_Model(Module):
# TODO 2.重写init魔法方法和forward前向传播方法
def __init__(self, *args, **kwargs):
# 1.调用父类的init初始化方法
super().__init__(*args, **kwargs)
# TODO 定义神经网络结构
self.linear1 = Linear(3, 3)
self.linear2 = Linear(3, 2)
self.out = Linear(2, 2)
# 3.参数初始化(生成权重矩阵)
# 隐藏层初始化权重矩阵
torch.nn.init.xavier_normal_(self.linear1.weight)
torch.nn.init.kaiming_normal_(self.linear2.weight)
def forward(self, x):
# TODO 前向传播计算(每层都是加权求和+激活函数)
x = torch.sigmoid(self.linear1(x))
x = torch.relu(self.linear2(x))
# 下面-1代表最后一维, 也就是按照每个样本概率和为1.
x = torch.softmax(self.out(x), dim=-1)
return x
# TODO 3.创建模型对象并使用模型对象
# 创建模型对象
model = My_Model() # 自动调用init魔法方法
# 准备数据集(正态分布数据)
torch.manual_seed(66)
data = torch.randn(5, 3) # 5个样本,3个特征
# 把数据传入模型对象
output = model(data) # 自动调用forward方法
print(output)
五、损失函数
本章节学习目标:
知道分类任务的损失函数
知道回归任务的损失函数
5.1 什么是损失函数
在深度学习中,损失函数是用来衡量模型参数的质量的函数,衡量的方式是比较网络输出和真实输出的差异。
损失函数在不同的文献中名称是不一样的,主要有以下几种命名方式:
-
损失函数(Loss Function)
-
代价函数(Cost Function)
-
目标函数(Objective Function)

5.2 分类任务的损失函数
5.2.1 多分类任务:交叉熵损失
在多分类任务通常使用 softmax 将 logits 转换为概率的形式,所以多分类的交叉熵损失也叫做 softmax 损失,它的计算方法是:

其中:
-
y 是样本 x 属于某一个类别的真实概率
-
而 f (x) 是样本属于某一类别的预测分数
-
S 是 softmax 激活函数,将属于某一类别的预测分数转换成概率
-
L 用来衡量真实值 y 和预测值 f (x) 之间差异性的损失结果
从概率角度理解,我们的目的是最小化正确类别所对应的预测概率的对数的负值 (损失值最小):

上图中的交叉熵损失为:

从概率角度理解,我们的目的是最小化正确类别所对应的预测概率的对数的负值(损失值最小),如下图所示:

在 pytorch 中使用nn\.CrossEntropyLoss\(\)实现,代码如下:
# 分类损失函数:交叉熵损失使用nn.CrossEntropyLoss()实现。nn.CrossEntropyLoss()=softmax + 损失计算
def test01():
# 设置真实值: 可以是热编码后的结果也可以不进行热编码
# y_true = torch.tensor([[0, 1, 0], [0, 0, 1]], dtype=torch.float32)
# 注意的类型必须是64位整型数据
y_true = torch.tensor([1, 2], dtype=torch.int64)
y_pred = torch.tensor([[0.2, 0.6, 0.2], [0.1, 0.8, 0.1]], requires_grad=True, dtype=torch.float32)
# 实例化交叉熵损失
loss = nn.CrossEntropyLoss()
# 计算损失结果
my_loss = loss(y_pred, y_true).detach().numpy()
print('loss:', my_loss)
5.2.2 二分类任务:二分类交叉熵损失
在处理二分类任务时,我们不再使用 softmax 激活函数,而是使用 sigmoid 激活函数,那损失函数也相应的进行调整,使用二分类的交叉熵损失函数:

其中:
-
y 是样本 x 属于某一个类别的真实概率
-
而 y^ 是样本属于某一类别的预测概率
-
L 用来衡量真实值 y 与预测值 y^ 之间差异性的损失结果。
在 pytorch 中实现时使用nn\.BCELoss\(\),代码如下:
def test02():
# 1 设置真实值和预测值
# 预测值是sigmoid输出的结果
y_pred = torch.tensor([0.6901, 0.5459], requires_grad=True)
y_true = torch.tensor([0, 1], dtype=torch.float32)
# 2 实例化二分类交叉熵损失
loss = nn.BCELoss()
# 3 计算损失
my_loss = loss(y_pred, y_true).detach().numpy()
print('loss:', my_loss)
5.3 回归任务的损失函数
5.3.1 MAE 损失函数(L1 Loss)
Mean absolute loss (MAE) 也被称为 L1 Loss,是以绝对误差作为距离。损失函数公式:


特点是:
-
由于 L1 loss 具有稀疏性,为了惩罚较大的值,因此常常将其作为正则项添加到其他 loss 中作为约束。
-
L1 loss 的最大问题是梯度在零点不平滑,导致会跳过极小值。
在 pytorch 中使用nn\.L1Loss\(\)实现,代码如下:
# 计算inputs与target之差的绝对值
def test03():
# 1 设置真实值和预测值
y_pred = torch.tensor([1.0, 1.0, 1.9], requires_grad=True)
y_true = torch.tensor([2.0, 2.0, 2.0], dtype=torch.float32)
# 2 实例化MAE损失
loss = nn.L1Loss()
# 3 计算损失
my_loss = loss(y_pred, y_true).detach().numpy()
print('loss:', my_loss)
5.3.2 MSE 损失函数(L2 Loss)
Mean Squared Loss (MSE) 也被称为 L2 Loss,是以均方误差作为距离。损失函数公式:


特点是:
-
L2 loss 的梯度会随着误差减小而减小,因此更容易收敛到最小值。
-
对异常值更敏感,因为它会对大的误差进行平方惩罚。
在 pytorch 中使用nn\.MSELoss\(\)实现,代码如下:
# 计算inputs与target之差的平方
def test04():
# 1 设置真实值和预测值
y_pred = torch.tensor([1.0, 1.0, 1.9], requires_grad=True)
y_true = torch.tensor([2.0, 2.0, 2.0], dtype=torch.float32)
# 2 实例化MSE损失
loss = nn.MSELoss()
# 3 计算损失
my_loss = loss(y_pred, y_true).detach().numpy()
print('loss:', my_loss)
损失函数对比辨析
| 损失函数 | 适用任务 | 特点 | 适用场景 |
|---|---|---|---|
| 交叉熵损失(CrossEntropy) | 多分类 | 衡量预测概率分布和真实分布的差异,梯度稳定 | 多分类任务 |
| 二分类交叉熵损失(BCELoss) | 二分类 | 针对二分类的交叉熵,配合 sigmoid 使用 | 二分类任务 |
| MAE 损失(L1 Loss) | 回归 | 对异常值不敏感,零点梯度不平滑 | 回归任务,对异常值鲁棒的场景 |
| MSE 损失(L2 Loss) | 回归 | 梯度随误差减小,更容易收敛,对异常值敏感 | 回归任务,数据质量较好的场景 |
六、网络优化方法
本章节学习目标:
了解常见的优化算法
掌握学习率的调整方法
6.1 梯度下降优化算法
梯度下降是神经网络优化的基础算法,常见的梯度下降变种有:
-
批量梯度下降(BGD):使用全部样本计算梯度,更新稳定但速度慢
-
随机梯度下降(SGD):使用单个样本计算梯度,速度快但波动大
-
小批量梯度下降(MBGD):使用小批量样本,平衡速度和稳定性,是目前的主流

6.2 带动量的梯度下降
动量(Momentum)是模拟物理中的动量概念,积累之前的梯度方向,加速收敛,减少震荡:

6.3 自适应学习率算法
6.3.1 AdaGrad
自适应调整每个参数的学习率,对稀疏数据效果好,但学习率会单调递减:

6.3.2 RMSProp
解决 AdaGrad 学习率递减的问题,使用指数移动平均来累积梯度:

6.3.3 Adam
结合了动量和 RMSProp 的优点,是目前最常用的优化算法,自适应调整学习率,收敛速度快:

6.4 学习率衰减
随着训练的进行,逐渐降低学习率,让模型在训练后期更稳定,避免震荡:
6.4.1 等间隔学习率衰减
每经过固定的轮次,就将学习率乘以一个衰减系数:
def test_StepLR():
# 0.参数初始化
LR = 0.1 # 设置学习率初始化值为0.1
iteration = 10
max_epoch = 200
# 1 初始化参数
y_true = torch.tensor([0])
x = torch.tensor([1.0])
w = torch.tensor([1.0], requires_grad=True)
# 2.优化器
optimizer = optim.SGD([w], lr=LR, momentum=0.9)
# 3.设置学习率下降策略
scheduler_lr = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.5)
# 4.获取学习率的值和当前的epoch
lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):
lr_list.append(scheduler_lr.get_last_lr()) # 获取当前lr
epoch_list.append(epoch) # 获取当前的epoch
for i in range(iteration): # 遍历每一个batch数据
loss = ((w*x-y_true)**2)/2.0 # 目标函数
optimizer.zero_grad()
# 反向传播
loss.backward()
optimizer.step()
# 更新下一个epoch的学习率
scheduler_lr.step()
# 5.绘制学习率变化的曲线
plt.plot(epoch_list, lr_list, label="Step LR Scheduler")
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()
6.4.2 指定间隔学习率衰减
在指定的轮次进行学习率衰减,更灵活:

def test_MultiStepLR():
torch.manual_seed(1)
LR = 0.1
iteration = 10
max_epoch = 200
weights = torch.randn((1), requires_grad=True)
target = torch.zeros((1))
print('weights--->', weights, 'target--->', target)
optimizer = optim.SGD([weights], lr=LR, momentum=0.9)
# 设定调整时刻数
milestones = [50, 125, 160]
# 设置学习率下降策略
scheduler_lr = optim.lr_scheduler.MultiStepLR(optimizer, milestones=milestones, gamma=0.5)
lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):
lr_list.append(scheduler_lr.get_last_lr())
epoch_list.append(epoch)
for i in range(iteration):
loss = torch.pow((weights - target), 2)
optimizer.zero_grad()
# 反向传播
loss.backward()
# 参数更新
optimizer.step()
# 更新下一个epoch的学习率
scheduler_lr.step()
plt.plot(epoch_list, lr_list, label="Multi Step LR Scheduler\nmilestones:{}".format(milestones))
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()
6.4.3 按指数学习率衰减
学习率按指数规律衰减:

def test_ExponentialLR():
# 0.参数初始化
LR = 0.1 # 设置学习率初始化值为0.1
iteration = 10
max_epoch = 200
# 1 初始化参数
y_true = torch.tensor([0])
x = torch.tensor([1.0])
w = torch.tensor([1.0], requires_grad=True)
# 2.优化器
optimizer = optim.SGD([w], lr=LR, momentum=0.9)
# 3.设置学习率下降策略
gamma = 0.95
scheduler_lr = optim.lr_scheduler.ExponentialLR(optimizer, gamma=gamma)
# 4.获取学习率的值和当前的epoch
lr_list, epoch_list = list(), list()
for epoch in range(max_epoch):
lr_list.append(scheduler_lr.get_last_lr())
epoch_list.append(epoch)
for i in range(iteration): # 遍历每一个batch数据
loss = ((w*x-y_true)**2)/2.0
optimizer.zero_grad()
# 反向传播
loss.backward()
optimizer.step()
# 更新下一个epoch的学习率
scheduler_lr.step()
# 5.绘制学习率变化的曲线
plt.plot(epoch_list, lr_list, label="Multi Step LR Scheduler")
plt.xlabel("Epoch")
plt.ylabel("Learning rate")
plt.legend()
plt.show()
优化方法对比辨析
| 优化算法 | 核心特点 | 适用场景 |
|---|---|---|
| SGD | 简单的随机梯度下降,收敛慢,容易震荡 | 简单任务,数据量小 |
| SGD+Momentum | 加入动量,加速收敛,减少震荡 | 大部分任务,稳定训练 |
| AdaGrad | 自适应学习率,适合稀疏数据 | 稀疏数据,NLP 任务 |
| RMSProp | 解决 AdaGrad 学习率递减问题 | 深度学习任务 |
| Adam | 结合动量和自适应学习率,收敛快 | 主流深度学习任务,默认首选 |
七、正则化方法
本章节学习目标:
知道正则化的作用
掌握随机失活 DropOut 策略
知道 BN 层的作用
7.1 正则化的作用
在设计机器学习算法时希望在新样本上的泛化能力强。许多机器学习算法都采用相关的策略来减小测试误差,这些策略被统称为正则化。
神经网络强大的表示能力经常遇到过拟合,所以需要使用不同形式的正则化策略。

7.2 Dropout 正则化
在练神经网络中模型参数较多,在数据量不足的情况下,很容易过拟合。Dropout(随机失活)是一个简单有效的正则化方法。
在训练过程中,Dropout 的实现是让神经元以超参数 p 的概率停止工作或者激活被置为 0, 未被置为 0 的进行缩放,缩放比例为 1/(1-p)。训练过程可以认为是对完整的神经网络的一些子集进行训练,每次基于输入数据只更新子网络的参数。
在测试过程中,随机失活不起作用。

代码示例:
import torch
import torch.nn as nn
def test():
# 初始化随机失活层
dropout = nn.Dropout(p=0.4)
# 初始化输入数据:表示某一层的weight信息
inputs = torch.randint(0, 10, size=[1, 4]).float()
layer = nn.Linear(4,5)
y = layer(inputs)
print("未失活FC层的输出结果:\n", y)
y = dropout(y)
print("失活后FC层的输出结果:\n", y)
输出结果:
未失活FC层的输出结果:
tensor([[-0.8369, -5.5720, 0.2258, 3.4256, 2.1919]],
grad_fn=<AddmmBackward0>)
失活后FC层的输出结果:
tensor([[-1.3949, -9.2866, 0.3763, 0.0000, 3.6531]], grad_fn=<MulBackward0>)
7.3 批量归一化(Batch Normalization)
批量归一化是对数据标准化,再对数据重构(缩放 + 平移),如下所示:

其中:
-
λ 和 β 是可学习的参数,它相当于对标准化后的值做了一个线性变换,λ 为系数,β 为偏置;
-
eps 通常指为 1e-5,避免分母为 0;
-
E (x) 表示变量的均值;
-
Var (x) 表示变量的方差;
批量归一化层在计算机视觉领域使用较多,它可以:
-
缓解梯度消失 / 爆炸问题
-
允许使用更高的学习率
-
降低对初始化的敏感程度
-
有轻微的正则化效果
代码示例:
import torch
import torch.nn as nn
m = nn.BatchNorm2d(2, eps=1e-05, momentum=0.1, affine=True) # affine参数设为True表示weight和bias将被使用
input = torch.randn(1, 2, 3, 4)
print("input-->", input)
output = m(input)
print("output-->", output)
print(output.size())
print(m.weight)
print(m.bias)
输出结果:
input--> tensor([[[[-0.2751, -1.2183, -0.5106, -0.1540],
[-0.4585, -0.5989, -0.6063, 0.5986],
[-0.4745, 0.1496, -1.1266, -1.2377]],
[[ 0.2580, 1.2065, 1.4598, 0.8387],
[-0.4586, 0.8938, -0.3328, 0.1192],
[-0.3265, -0.6263, 0.0419, -1.2231]]]])
output--> tensor([[[[ 0.4164, -1.3889, -0.0343, 0.6484],
[ 0.0655, -0.2032, -0.2175, 2.0889],
[ 0.0349, 1.2294, -1.2134, -1.4262]],
[[ 0.1340, 1.3582, 1.6853, 0.8835],
[-0.7910, 0.9546, -0.6287, -0.0452],
[-0.6205, -1.0075, -0.1449, -1.7779]]]],
grad_fn=<NativeBatchNormBackward0>)
torch.Size([1, 2, 3, 4])
Parameter containing:
tensor([1., 1.], requires_grad=True)
Parameter containing:
tensor([0., 0.], requires_grad=True)
八、实战案例:手机价格分类
本章节学习目标:
掌握构建分类模型的完整流程
动手实践整个训练和评估过程
8.1 需求分析
小明创办了一家手机公司,他不知道如何估算手机产品的价格。为了解决这个问题,他收集了多家公司的手机销售数据。该数据为二手手机的各个性能的数据,最后根据这些性能得到 4 个价格区间,作为这些二手手机售出的价格区间。
我们需要帮助小明找出手机的功能(例如:RAM 等)与其售价之间的某种关系。我们可以使用机器学习的方法来解决这个问题,也可以构建一个全连接的网络。
需要注意的是:在这个问题中,我们不需要预测实际价格,而是一个价格范围,它的范围使用 0、1、2、3 来表示,所以该问题也是一个分类问题。
I在这里插入图片描述
8.2 完整流程
我们按照四个步骤来完成这个任务:
-
准备训练集数据
-
构建要使用的模型
-
模型训练
-
模型预测评估
8.2.1 导入工具包
# 导入相关模块
import torch
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import time
8.2.2 构建数据集
数据共有 2000 条,其中 1600 条数据作为训练集,400 条数据用作测试集。 我们使用 sklearn 的数据集划分工作来完成。并使用 PyTorch 的 TensorDataset 来将数据集构建为 Dataset 对象,方便构造数据集加载对象。
# 构建数据集
def create_dataset():
# 使用pandas读取数据
data = pd.read_csv('data/手机价格预测.csv')
# 特征值和目标值
x, y = data.iloc[:, :-1], data.iloc[:, -1]
# 类型转换:特征值,目标值
x = x.astype(np.float32)
y = y.astype(np.int64)
# 数据集划分
x_train, x_valid, y_train, y_valid = train_test_split(x, y, train_size=0.8, random_state=88)
# 构建数据集,转换为pytorch的形式
train_dataset = TensorDataset(torch.from_numpy(x_train.values), torch.tensor(y_train.values))
valid_dataset = TensorDataset(torch.from_numpy(x_valid.values), torch.tensor(y_valid.values))
# 返回结果
return train_dataset, valid_dataset, x_train.shape[1], len(np.unique(y))
输出结果:
输入特征数: 20
分类个数: 4
8.2.3 构建分类网络模型
构建全连接神经网络来进行手机价格分类,该网络主要由三个线性层来构建,使用 relu 激活函数。
网络共有 3 个全连接层,具体信息如下:
-
第一层:输入为维度为 20, 输出维度为: 128
-
第二层:输入为维度为 128, 输出维度为: 256
-
第三层:输入为维度为 256, 输出维度为: 4
# 构建网络模型
class PhonePriceModel(nn.Module):
def __init__(self, input_dim, output_dim):
super(PhonePriceModel, self).__init__()
# 1. 第一层: 输入为维度为 20, 输出维度为: 128
self.linear1 = nn.Linear(input_dim, 128)
# 2. 第二层: 输入为维度为 128, 输出维度为: 256
self.linear2 = nn.Linear(128, 256)
# 3. 第三层: 输入为维度为 256, 输出维度为: 4
self.linear3 = nn.Linear(256, output_dim)
def forward(self, x):
# 前向传播过程
x = torch.relu(self.linear1(x))
x = torch.relu(self.linear2(x))
output = self.linear3(x)
# 获取数据结果
return output
模型实例化的输出:

8.2.4 模型训练
网络编写完成之后,我们需要编写训练函数。所谓的训练函数,指的是输入数据读取、送入网络、计算损失、更新参数的流程,该流程较为固定。我们使用的是多分类交叉生损失函数、使用 SGD 优化方法。最终,将训练好的模型持久化到磁盘中。
# 模型训练过程
def train(train_dataset,input_dim,class_num,):
# 固定随机数种子
torch.manual_seed(0)
# 初始化数据加载器
dataloader = DataLoader(train_dataset, shuffle=True, batch_size=8)
# 初始化模型
model = PhonePriceModel(input_dim, class_num)
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化方法
optimizer = optim.SGD(model.parameters(), lr=1e-3)
# 训练轮数
num_epoch = 50
# 遍历每个轮次的数据
for epoch_idx in range(num_epoch):
# 训练时间
start = time.time()
# 计算损失
total_loss = 0.0
total_num = 0
# 遍历每个batch数据进行处理
for x, y in dataloader:
# 将数据送入网络中进行预测
output = model(x)
# 计算损失
loss = criterion(output, y)
# 梯度清零
optimizer.zero_grad()
# 反向传播
loss.backward()
# 参数更新
optimizer.step()
# 损失计算
total_num += 1
total_loss += loss.item()
# 打印损失变换结果
print('epoch: %4s loss: %.2f, time: %.2fs' %(epoch_idx + 1, total_loss / total_num, time.time() - start))
# 模型保存
torch.save(model.state_dict(), 'model/phone.pth')
训练的输出结果:

8.2.5 模型评估
使用训练好的模型,对未知的样本的进行预测的过程。我们这里使用前面单独划分出来的验证集来进行评估。
def test(valid_dataset,input_dim,class_num):
# 加载模型和训练好的网络参数
model = PhonePriceModel(input_dim, class_num)
model.load_state_dict(torch.load('model/phone.pth'))
# 构建加载器
dataloader = DataLoader(valid_dataset, batch_size=8, shuffle=False)
# 评估测试集
correct = 0
# 遍历测试集中的数据
for x, y in dataloader:
# 将其送入网络中
output = model(x)
# 获取类别结果
y_pred = torch.argmax(output, dim=1)
# 获取预测正确的个数
correct += (y_pred == y).sum()
# 求预测精度
print('Acc: %.5f' % (correct.item() / len(valid_dataset)))
输出结果:
Acc: 0.64250
8.3 调优方向
我们前面的网络模型在测试集的准确率为: 0.64250, 我们可以通过以下方面进行调优:
-
优化方法由 SGD 调整为 Adam
-
学习率由 1e-3 调整为 1e-4
-
对数据进行标准化
-
增加网络深度,即:增加网络参数量
-
调整训练轮次
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)