一、神经网络是什么?
神经网络(Neural Network, NN)是机器学习中模仿生物神经系统结构的计算模型。它由大量人工神经元 通过权重连接构成,能够通过数据学习复杂的非线性关系。
-
生物类比:神经元接收输入信号(树突),加权求和后通过激活函数(轴突)产生输出,传递给其他神经元。
-
与深度学习的区别:传统NN通常指全连接网络(如多层感知机),而深度学习包含更复杂的架构(如CNN、RNN),但NN是它们的理论基础。
二、神经元模型
每个神经元的计算可表示为:

我们使用多个神经元来构建神经网络,相邻层之间的神经元相互连接,并给每一个连接分配一个强度,如下图所示:
神经网络中信息只向一个方向移动,即从输入节点向前移动,通过隐藏节点,再向输出节点移动。其中的基本部分是 :
- 输入层: 即输入 x 的那一层
- 输出层: 即输出 y 的那一层
- 隐藏层: 输入层和输出层之间都是隐藏层
特点:
- 同一层的神经元之间没有连接。
- 第 N 层的每个神经元和第 N-1层 的所有神经元相连(这就是full connected的含义),这就是全连接神经网络。
- 第N-1层神经元的输出就是第N层神经元的输入。
- 每个连接都有一个权重值(w系数和b系数)。
根据上图,实现一个简单的神经网络。如下:
python
import torch
from torch import nn # nn模块中有平方损失函数和假设函数
from torch import optim # optim模块中有优化器函数
import matplotlib.pyplot as plt
#定义神经网络模型
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
# 定义各层
self.fc1 = nn.Linear(3, 5)
self.relu1 = nn.ReLU()
self.fc2 = nn.Linear(5, 5)
self.relu2 = nn.ReLU()
self.fc3 = nn.Linear(5, 2)
def forward(self, x):
# 前向传播
out = self.fc1(x)
out = self.relu1(out)
out = self.fc2(out)
out = self.relu2(out)
out = self.fc3(out)
return out
#初始化模型、损失函数和优化器
# 初始化模型
model = SimpleNN()
# 定义损失函数(这里使用均方误差)
criterion = nn.MSELoss()
# 定义优化器(这里使用随机梯度下降)
optimizer = optim.SGD(model.parameters(), lr=0.01)
#训练模型
# 示例训练数据
inputs = torch.randn(100, 3) # 生成100个样本,每个样本有3个特征
targets = torch.randn(100, 2) # 对应的目标值
num_epochs = 1000 # 训练轮数
for epoch in range(num_epochs):
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 反向传播和优化
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新权重
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 预测
model.eval()
with torch.no_grad():
predictions = model(inputs.clone().detach()).numpy()
# 绘图
plt.scatter(inputs[:, 0].numpy(), targets[:, 0].numpy(), label='Targets') # 使用第一个特征和第一个目标值
plt.scatter(inputs[:, 0].numpy(), predictions[:, 0], label='Predictions', color='red') # 使用第一个特征和第一个预测值
plt.xlabel('Feature 1')
plt.ylabel('Target 1')
plt.legend()
plt.show()
代码中引入了很多神经网络相关的技术,接下来,会逐一说明。
三、神经网络组成
1.激活函数
激活函数用于对每层的 输出数据进行变换 , 进而为整个网络注入了 非线性因素 。此时, 神经网络就 可以拟合各种曲线。
- 没有引入非线性因素的网络等价于使用一个线性模型来拟合
- 通过给网络输出增加激活函数, 实现引入非线性因素, 使得网络模型可以逼近任意函数, 提升网络对复杂问题的拟合能力.
如果不使用激活函数,整个网络虽然看起来复杂,其本质还相当于一种线性模型,如下公式所示:

常见的激活函数**-sigmoid****激活函数**
激活函数公式:
激活函数求导公式:
sigmoid 激活函数的函数图像如下 :
- sigmoid 函数可以将任意的输入映射到 **(0, 1)**之间,当输入的值大致在 <-6或者>6时,意味着输入任何值得到的激 活值都是差不多的,这样会丢失部分的信息。比如:输入 100 和输出 10000 经过 sigmoid 的激活值几乎都是等于 1 的,但是输入的数据之间相差 100 倍的信息就丢失了。
- 对于 sigmoid 函数而言,输入值在 [-6, 6]之间输出值才会有明显差异,输入值在 [-3, 3]之间才会有比较好的效果。
- 通过上述导数图像,我们发现导数数值范围是****(0, 0.25),当输入 <-6 或者 >6 时,sigmoid 激活函数图像的导数接近为0,此时网络参数将更新极其缓慢,或者无法更新。
- 一般来说, sigmoid 网络在 5****层之内就会产生梯度消失现象。而且,该激活函数并不是以 0 为中心的,所以在实践中这种激活函数使用的很少。sigmoid****函数一般只用于二分类的输出层。
python
import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 创建画布和坐标轴
_, axes = plt.subplots(1, 2)
"""
函数图像
"""
x = torch.linspace(-20, 20, 1000)
# 输入值x通过sigmoid函数转换成激活值y
y = torch.sigmoid(x)
axes[0].plot(x, y)
axes[0].grid()
axes[0].set_title('Sigmoid 函数图像')
"""
导数图像
"""
x = torch.linspace(-20, 20, 1000, requires_grad=True)
x__sum = torch.sigmoid(x).sum()
x__sum.backward()
# x.detach():输入值x的数值
# x.grad:计算梯度,求导
print('x__sum->',x__sum)
print('x->',x)
print('x.detach()->',x.detach())
print('x.grad->',x.grad)
axes[1].plot(x.detach(), x.grad)
axes[1].grid()
axes[1].set_title('Sigmoid 导数图像')
plt.show()
常见的激活函数**-tanh****激活函数**
Tanh 的公式如下:

激活函数求导公式:
Tanh 的函数图像、导数图像如下:
- Tanh 函数将输入映射到 (-1, 1) 之间,图像以 0 为中心,在 0 点对称,当输入 大概<-3 或者>3 时将被映射为 -1 或者 1。其导数值范围 (0, 1),当输入的值大概 <-3 或者 > 3 时,其导数近似 0。
- 与 Sigmoid 相比,它是以 0 为中心的,且梯度相对于sigmoid大,使得其收敛速度要比Sigmoid 快,减少迭代次数。然而,从图中可以看出,Tanh 两侧的导数也为 0,同样会造成 梯度消失。
- 若使用时可在隐藏层使用tanh函数,在输出层使用sigmoid函数。
python
import torch
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 创建画布和坐标轴
_, 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()
常用的激活函数**-ReLU****激活函数**
ReLU 公式如下:
激活函数求导公式 :
ReLU 的函数图像如下:
- ReLU 激活函数将小于 0 的值映射为 0,而大于 0 的值则保持不变,它更加重视正信号,而忽 略负信号,这种激活函数运算更为简单,能够提高模型的训练效率。
- 当x<0时,ReLU导数为0,而当x>0时,则不存在饱和问题。所以,ReLU 能够在x>0时保持梯 度不衰减,从而缓解梯度消失问题。然而,随着训练的推进,部分输入会落入小于0区域,导 致对应权重无法更新。这种现象被称为"神经元死亡"。
- ReLU是目前最常用的激活函数。与sigmoid相比,RELU的优势是:
采用sigmoid函数,计算量大(指数运算),反向传播求误差梯度时,计算量相对大,而采用 Relu激活函数,整个过程的计算量节省很多。 sigmoid函数反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。 Relu会使一部分神经元的输出为0,这样就造成了 网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
python
# 创建画布和坐标轴
import torch
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
_, 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()
常用的激活函数**-SoftMax****激活函数**
softmax 用于 多分类 过程中,它是二分类函数 sigmoid 在多分类上的推广,目的是 将多分类的结果以概率的形式展现出来。计算方法如下图所示:
Softmax就是将网络输出的logits 通过softmax函数,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们将它理解成概率,选取概率最大(也就是值对应最大的)节点,作为我们的预测目标类别。
python
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=0)
print(probabilities.sum())
print(probabilities.max())
print(probabilities)
其他常见的激活函数
激活函数的选择方法
对于 隐藏层 :
- 优先选择ReLU激活函数
- 如果ReLu效果不好,那么尝试其他激活,如Leaky ReLu等。
- 如果你使用了ReLU, 需要注意一下Dead ReLU问题, 避免出现大的梯度从而导致过多的神经元死亡。
- 少用使用sigmoid激活函数,可以尝试使用tanh激活函数
对于 输出层 :
- 二分类问题选择sigmoid激活函数
- 多分类问题选择softmax激活函数
- 回归问题选择identity激活函数
2. 权重初始化方法
- 均匀分布初始化
- 权重参数初始化从区间均匀随机取值。即在(-1/√d,1/√d)均匀分布中生成当前神经元的权重,其中d为每个神经元的输入数量
- 正态分布初始化
- 随机初始化从均值为0,标准差是1的高斯分布中取样,使用一些很小的值对参数W进行初始化
- 全0初始化
- 将神经网络中的所有权重参数初始化为 0
- 全1初始化
- 将神经网络中的所有权重参数初始化为 1.
- 固定值初始化
- 将神经网络中的所有权重参数初始化为某个固定值.
- kaiming 初始化,也叫做 HE****初始化
- HE 初始化分为正态分布的 HE 初始化、均匀分布的 HE 初始化.
- 正态化的he初始化
- stddev = sqrt(2 / fan_in)
- 均匀分布的he初始化
- 它从 [-limit,limit] 中的均匀分布中抽取样本, limit是 sqrt(6 / fan_in)
- fan_in 输入神经元的个数
- 正态化的he初始化
- HE 初始化分为正态分布的 HE 初始化、均匀分布的 HE 初始化.
- xavier 初始化,也叫做 Glorot****初始化
- 该方法也有两种,一种是正态分布的 xavier 初始化、一种是均匀分布的 xavier 初始化.
- 正态化的Xavier初始化
- stddev = sqrt(2 / (fan_in + fan_out))
- 均匀分布的Xavier初始化
-
-limit,limit\] 中的均匀分布中抽取样本, limit 是 sqrt(6 / (fan_in + fan_out))
-
- 正态化的Xavier初始化
- 该方法也有两种,一种是正态分布的 xavier 初始化、一种是均匀分布的 xavier 初始化.
python
import torch.nn as nn
# 1. 均匀分布随机初始化
def test01():
linear = nn.Linear(5, 3)
# 从0-1均匀分布产生参数
nn.init.uniform_(linear.weight)
print(linear.weight.data)
# test01()
# 2.固定初始化
def test02():
linear = nn.Linear(5, 3)
nn.init.constant_(linear.weight, 5)
print(linear.weight.data)
test02()
# 3. 全0初始化
def test03():
linear = nn.Linear(5, 3)
nn.init.zeros_(linear.weight)
print(linear.weight.data)
# test03()
# 4. 全1初始化
def test04():
linear = nn.Linear(5, 3)
nn.init.ones_(linear.weight)
print(linear.weight.data)
# test04()
# 5. 正态分布随机初始化
def test05():
linear = nn.Linear(5, 3)
nn.init.normal_(linear.weight, mean=0, std=1)
print(linear.weight.data)
# test05()
# 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)
# test06()
# 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)
# test07()
3. 网络搭建及参数计算
在pytorch中定义深度神经网络其实就是层堆叠的过程,继承自nn.Module,实现两个方法:
- __init__方法中定义网络中的层结构,主要是全连接层,并进行初始化
- forward方法,在实例化模型的时候,底层会自动调用该函数。该函数中可以定义学习率, 为初始化定义的layer传入数据等。
我们来构建如下图所示的神经网络模型:
编码设计如下:
- 第1个隐藏层:权重初始化采用标准化的xavier初始化 激活函数使用sigmoid
- 第2个隐藏层:权重初始化采用标准化的He初始化 激活函数采用relu
- out输出层线性层 假若二分类,采用softmax做数据归一化
python
import torch
import torch.nn as nn
from torchsummary import summary # 计算模型参数,查看模型结构, pip install torchsummary
# 创建神经网络模型类
class Model(nn.Module):
# 初始化属性值
def __init__(self):
super(Model, self).__init__() # 调用父类的初始化属性值
self.linear1 = nn.Linear(3, 3) # 创建第一个隐藏层模型, 3个输入特征,3个输出特征
nn.init.xavier_normal_(self.linear1.weight) # 初始化权
# 创建第二个隐藏层模型, 3个输入特征(上一层的输出特征),2个输出特征
self.linear2 = nn.Linear(3, 2)
# 初始化权重
nn.init.kaiming_normal_(self.linear2.weight)
# 创建输出层模型
self.out = nn.Linear(2, 2)
# 创建前向传播方法,自动执行forward()方法
def forward(self, x):
# 数据经过第一个线性层
x = self.linear1(x)
# 使用sigmoid激活函数
x = torch.sigmoid(x)
# 数据经过第二个线性层
x = self.linear2(x)
# 使用relu激活函数
x = torch.relu(x)
# 数据经过输出层
x = self.out(x)
# 使用softmax激活函数
# dim=-1:每一维度行数据相加为1
x = torch.softmax(x, dim=-1)
return x
if __name__ == "__main__":
# 实例化model对象
my_model = Model()
# 随机产生数据
my_data = torch.randn(5, 3)
print("mydata shape", my_data.shape)
# 数据经过神经网络模型训练
output = my_model(my_data)
print("output shape-->", output.shape)
# 计算模型参数
# 计算每层每个神经元的w和b个数总和
summary(my_model, input_size=(3,), batch_size=5)
# 查看模型参数
print("======查看模型参数w和b======")
for name, parameter in my_model.named_parameters():
print(name, parameter)
- 神经网络的输入数据是为[batch_size, in_features]的张量经过网络处理后获取了[batch_size, out_features]的输出张量。
- 在上述例子中,batchsize=5, infeatures=3,out_features=2,结果如下所示:
- mydata.shape---> torch.Size([5, 3])
- output.shape---> torch.Size([5, 2])
mydata shape torch.Size([5, 3])
output shape--> torch.Size([5, 2])
Layer (type) Output Shape Param #
================================================================
Linear-1 [5, 3] 12
Linear-2 [5, 2] 8
Linear-3 [5, 2] 6
================================================================
Total params: 26
Trainable params: 26
Non-trainable params: 0
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.00
Estimated Total Size (MB): 0.00
======查看模型参数w和b======
linear1.weight Parameter containing:
tensor([[-0.3720, -0.2167, -1.3236],
0.0348, 0.5131, 1.0614\], \[ 0.4597, 0.1922, -0.5263\]\], requires_grad=True) linear1.bias Parameter containing: tensor(\[ 0.3941, -0.4513, 0.4701\], requires_grad=True) linear2.weight Parameter containing: tensor(\[\[-0.1719, -0.3171, -0.8403\], \[-0.5550, 0.3276, 0.2605\]\], requires_grad=True) linear2.bias Parameter containing: tensor(\[0.0586, 0.5025\], requires_grad=True) out.weight Parameter containing: tensor(\[\[-0.6090, -0.5812\], \[-0.4905, 0.6005\]\], requires_grad=True) out.bias Parameter containing:
模型参数的计算 :
- 以第一个隐层为例:该隐层有3个神经元,每个神经元的参数为:4个(w1,w2,w3,b1),所以一共用3x4=12个参数。
- 输入数据和网络权重是两个不同的事儿!对于初学者理解这一点十分重要,要分得清。

四、损失函数
什么是损失函数?
在深度学习中, 损失函数是用来衡量模型参数的质量的函数 , 衡量的方式是比较网络输出和真实输 出的差异。
1. 回归任务常用损失函数
回归任务损失函数**-MAE****损失函数**
**Mean absolute loss(MAE)**也被称为L1 Loss,是以绝对误差作为距离。损失函数公式:
曲线如下图所示:
特点是:
- 由于L1 loss具有稀疏性,为了惩罚较大的值,因此常常将其作为正则项添加到其他loss中作为约束。
- L1 loss的最大问题是梯度在零点不平滑,导致会跳过极小值。
在 pytorch 中使用 nn.L1Loss() 实现,如下所示:
python
# 回归任务损失,MAE损失,使用nn.L1Loss()实现
# 计算算inputs与target之差的绝对值
def test3():
# 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)
回归任务损失函数**-MSE****损失函数**
**Mean Squared Loss/ Quadratic Loss(MSE loss)**也被称为L2 loss,或欧氏距离,它以误差的平方和的均值作为距离
损失函数公式:
曲线如下图所示:
特点是:
- L2 loss也常常作为正则项。
- 当预测值与目标值相差很大时, 梯度容易爆炸。
在 pytorch 中使用 nn.MSELoss() 实现,如下所示:
python
# 回归任务损失,MSE损失,使用nn.MSELoss()实现
def test4():
# 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('myloss:', my_loss)
回归任务损失函数**-smooth L1****损失函数**
smooth L1说的是光滑之后的 L1 。损失函数公式 :
其中: 𝑥 =f(x)−y 为真实值和预测值的差值。
曲线如下图所示:
从右图中可以看出,该函数实际上就是一个分段函数
- 在[-1,1]之间实际上就是L2损失,这样解决了L1的不光滑问题
- 在[-1,1]区间外,实际上就是L1损失,这样就解决了离群点梯度爆炸的问题
在 pytorch 中使用 nn.SmoothL1Loss() 实现,如下所示:
python
# 回归任务损失,SmoothL1损失,使用nn.SmoothL1Loss()实现
def test5():
# 1 设置真实值和预测值
y_true = torch.tensor([0, 3])
y_pred = torch.tensor([0.6, 0.4], requires_grad=True)
# 2 实例化smoothL1损失对象
loss = nn.SmoothL1Loss()
# 3 计算损失
my_loss = loss(y_pred, y_true).detach().numpy()
print('loss:', my_loss)
2. 分类任务常用损失函数
二分类任务损失函数
在处理二分类任务时,我们不再使用softmax激活函数,而是使用sigmoid激活函数,那损失函数也相应的进行调整, 使用二分类的交叉熵损失函数:
其中:
- y是样本x属于某一个类别的真实概率
- 而y^是样本属于某一类别的预测概率
- L用来衡量真实值y与预测值y^之间差异性的损失结果。
在pytorch中实现时使用nn.BCELoss() ,如下所示:
python
# 二分类交叉熵损失,使用nn.BCELoss()实现
def test2():
# 1 设置真实值和预测值
# 预测值是sigmoid输出的结果
y_pred = torch.tensor([0.6901, 0.5459, 0.2469], requires_grad=True)
y_true = torch.tensor([0, 1, 0], dtype=torch.float32)
# 2 实例化二分类交叉熵损失
criterion = nn.BCELoss()
# 3 计算损失
my_loss = criterion(y_pred, y_true).detach().numpy()
print('loss:', my_loss)
多分类任务损失函数
在多分类任务通常使用softmax将logits转换为概率的形式,所以多分类的交叉熵损失也叫做softmax****损失,它的计算方法是:
其中 :
- y是样本x属于某一个类别的真实概率
- 而f(x)是样本属于某一类别的预测分数
- S是softmax激活函数,将属于某一类别的预测分数转换成概率
- L用来衡量真实值y和预测值f(x)之间差异性的损失结果
上图中的交叉熵损失为:
从概率角度理解,我们的目的是最小化正确类别所对应的预测概率的对数的负值 ( 损失值最小 ) ,如下图所示:
在 pytorch 中使用 nn.CrossEntropyLoss() 实现,如下所示:
python
# 多分类交叉熵损失,使用nn.CrossEntropyLoss()实现。nn.CrossEntropyLoss()=softmax + 损失计算
def test1():
# 设置真实值: 可以是热编码后的结果也可以不进行热编码
# 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]], dtype=torch.float32)
# 实例化交叉熵损失
loss = nn.CrossEntropyLoss()
# 计算损失结果
my_loss = loss(y_pred, y_true).numpy()
print('loss:', my_loss)
五、网络优化方法
什么是梯度下降?
梯度下降法是一种 寻找使损失函数最小化的方法 。从数学上的角度来看, 梯度的方向是函数增长速度最快的方向,那么梯度的反方向就是函数减少最快的方向 ,所以有:
其中, η 是学习率,如果学习率太小,那么每次训练之后得到的效果都太小,增大训练的时间成本。如果,学习率太大,那就有可能直接跳过最优解,进入无限的训练中。解决的方法就是,学习率也需要随着训练的进行而变化。
梯度下降的三大变体
类型 | 更新公式 | 特点 | 适用场景 |
---|---|---|---|
批量梯度下降(BGD) | 使用全量数据计算梯度 | 稳定但内存消耗大、计算慢 | 小型数据集 |
随机梯度下降(SGD) | 每个样本更新一次参数 | 波动大、可能跳出局部最优 | 在线学习 |
小批量梯度下降(MBGD) | 使用mini-batch(如32/64样本) | 平衡速度与稳定性(主流选择) | 绝大多数深度学习任务 |
在进行模型训练时,有三个基础的概念:
- Epoch: 使用全部数据对模型进行以此完整训练,训练轮次
- Batch_size: 使用训练集中的小部分样本对模型权重进行以此反向传播的参数更新,每次训练每批次样本数量
- Iteration: 使用一个 Batch 数据对模型进行一次参数更新的过程
假设数据集有 50000 个训练样本,现在选择 Batch Size = 256 对模型进行训练。
每个 Epoch 要训练的图片数量: 50000
训练集具有的 Batch 个数: 50000/256+1=196
每个 Epoch 具有的 Iteration 个数: 196
10 个 Epoch 具有的 Iteration 个数: 1960
反向传播
前向传播 :指的是数据输入的神经网络中,逐层向前传输,一直到运算到输出层为止。
反向传播( Back Propagation ): 利用损失函数 ERROR,从后往前,结合梯度下降算法,依次求各个参数的偏导,并进行参数更新。
反向传播对神经网络中的各个节点的权重进行更新 。一个简单的神经网络用来举例:激活函数为 sigmoid。
前向传播运算过程 :
接下来是 反向传播, 我们先来求最简单的,求误差 E 对 w5 的导数。要求误差 E 对 w5 的导数,需要先求误差 E 对 out o1 的导数,再求 out o1 对 net o1 的导数,最后再求 net o1 对 w5 的导数,经过这个处理,我们就可以求出误差 E 对 w5 的导数(偏 导),如下图所示:
导数(梯度)已经计算出来了,下面就是 反向传播与参数更新过程 :
如果要想求 误差 E 对 w1 的导数 ,误差 E 对 w1 的求导路径不止一条,这会稍微复杂一点,但换汤不换药,计算过程如下所 示:
python
import torch
from torch import nn
from torch import optim
# 创建神经网络类
class Model(nn.Module):
# 初始化参数
def __init__(self):
# 调用父类方法
super(Model, self).__init__()
# 创建网络层
self.linear1 = nn.Linear(2, 2)
self.linear2 = nn.Linear(2, 2)
# 初始化神经网络参数
self.linear1.weight.data = torch.tensor([[0.15, 0.20], [0.25, 0.30]])
self.linear2.weight.data = torch.tensor([[0.40, 0.45], [0.50, 0.55]])
self.linear1.bias.data = torch.tensor([0.35, 0.35])
self.linear2.bias.data = torch.tensor([0.60, 0.60])
# 前向传播方法
def forward(self, x):
# 数据经过第一层隐藏层
x = self.linear1(x)
# 计算第一层激活值
x = torch.sigmoid(x)
# 数据经过第二层隐藏层
x = self.linear2(x)
# 计算第二层激活值
x = torch.sigmoid(x)
return x
if __name__ == '__main__':
# 定义网络输入值和目标值
inputs = torch.tensor([[0.05, 0.10]])
target = torch.tensor([[0.01, 0.99]])
# 实例化神经网络对象
model = Model()
output = model(inputs)
print("output-->", output)
# 计算误差
loss = torch.sum((output - target) ** 2) / 2
print("loss-->", loss)
# 优化方法和反向传播算法
optimizer = optim.SGD(model.parameters(), lr=0.5)
optimizer.zero_grad()
loss.backward()
print("w1,w2,w3,w4-->", model.linear1.weight.grad.data)
print("w5,w6,w7,w8-->", model.linear2.weight.grad.data)
optimizer.step()
# 打印神经网络参数
print(model.state_dict())
梯度下降优化方法
梯度下降优化算法中,可能会碰到以下情况:
- 碰到平缓区域,梯度值较小,参数优化变慢
- 碰到 "鞍点" ,梯度为 0,参数无法优化
- 碰到局部最小值,参数不是最优
对于这些问题, 出现了一些对梯度下降算法的优化方法, 例如:Momentum、AdaGrad、RMSprop、Adam 等。
梯度下降的优化方法**-**指数加权平均
指数移动加权平均则是参考各数值,并且各数值的权重都不同,距离越远的数字对平均数计算的贡献就越小(权重较小),距离越近则对平均数的计算贡献就越大(权重越大)。
比如:明天气温怎么样,和昨天气温有很大关系,而和一个月前的气温关系就小一些。
计算公式可以用下面的式子来表示:
- St 表示指数加权平均值;
- Yt 表示 t 时刻的值;
- β 调节权重系数,该值越大平均数越平缓。
下面通过代码来看结果,随机产生 30 天的气温数据:
python
"""
梯度下降的优化方法-指数加权平均
"""
ELEMENT_NUMBER = 30
# 1. 实际平均温度
def test01():
# 固定随机数种子
torch.manual_seed(0)
# 产生30天的随机温度
temperature = torch.randn(size=[ELEMENT_NUMBER,]) * 10
print(temperature)
# 绘制平均温度
days = torch.arange(1, ELEMENT_NUMBER + 1, 1)
plt.plot(days, temperature, color='r')
#
#
#
plt.scatter(days, temperature)
plt.show()
# test01()
# 2. 指数加权平均温度
def test02(beta=0.9):
torch.manual_seed(0) # 固定随机数种子
temperature = torch.randn(size=[ELEMENT_NUMBER,]) * 10 # 产生30天的随机温度
exp_weight_avg = []
for idx, temp in enumerate(temperature, 1): # 从下标1开始
# 第一个元素的的 EWA 值等于自身
if idx == 1:
exp_weight_avg.append(temp)
continue
# 第二个元素的 EWA 值等于上一个 EWA 乘以 β + 当前气温乘以 (1-β)
new_temp = exp_weight_avg[idx - 2] * beta + (1 - beta) * temp
exp_weight_avg.append(new_temp)
days = torch.arange(1, ELEMENT_NUMBER + 1, 1)
plt.plot(days, exp_weight_avg, color='r')
plt.scatter(days, temperature)
plt.show()
# test02()
上图是 β 为 0.5 和 0.9 时的结果,从中可以看出:
- 指数加权平均绘制出的气氛变化曲线更加平缓,
- β 的值越大,则绘制出的折线越加平缓,波动越小。
基础优化器 - 梯度下降的优化方法 -动量算法- Momentum
Monmentum 优化方法是如何一定程度上克服 "平缓"、"鞍点" 的问题呢?

- 当处于鞍点位置时,由于当前的梯度为 0,参数无法更新。但是 Momentum 动量梯度下降算法已经在先前积累了一些梯度值,很有可能使得跨过鞍点。
- 由于 mini-batch 普通的梯度下降算法,每次选取少数的样本梯度确定前进方向,可能会出现震荡,使得训练时间变长。Momentum 使用移动加权平均,平滑了梯度的变化,使得前进方向更加平缓,有利于加快训练过程。
核心思想:引入动量项加速收敛,抑制震荡。
优势:在山谷方向加速,减少横向震荡(类比滚下山坡的球)。
python
"""
梯度下降的优化方法-动量算法Momentum
"""
def test03():
# 1 初始化权重参数
w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
y = ((w ** 2) / 2.0).sum()
# 2 实例化优化方法:SGD 指定参数beta=0.9
optimizer = torch.optim.SGD([w], lr=0.01, momentum=0.9)
# 3 第1次更新 计算梯度,并对参数进行更新
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
# 4 第2次更新 计算梯度,并对参数进行更新
# 使用更新后的参数机选输出结果
y = ((w ** 2) / 2.0).sum()
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
test03()
基础优化器 - 梯度下降的优化方法 - adaGrad
AdaGrad 通过对不同的参数分量使用不同的学习率, AdaGrad 的学习率总体会逐渐减小 。
其计算步骤如下:
- 初始化学习率 α、初始化参数 θ、小常数 σ = 1e-6
- 初始化梯度累积变量 s = 0
- 从训练集中采样 m 个样本的小批量,计算梯度 g
- 累积平方梯度s = s + g ⊙ g,⊙ 表示各个分量相乘
学习率 α 的计算公式如下:
参数更新公式如下:
重复 2-4 步骤 , 即可完成网络训练。
自适应学习率:为每个参数分配独立的学习率。
缺点 : 可能会使得学习率过早、过量的降低,导致模型训练后期学习率太小,较难找到最优解。
python
"""
梯度下降的优化方法-AdaGrad
"""
def test04():
# 1 初始化权重参数
w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
y = ((w ** 2) / 2.0).sum()
# 2 实例化优化方法:adagrad优化方法
optimizer = torch.optim.Adagrad([w], lr=0.01)
# 3 第1次更新 计算梯度,并对参数进行更新
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
# 4 第2次更新 计算梯度,并对参数进行更新
# 使用更新后的参数机选输出结果
y = ((w ** 2) / 2.0).sum()
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
自适应优化器 - 梯度下降的优化方法 - RMSProp
RMSProp 优化算法是对 AdaGrad 的优化 . 最主要的不同是,其使用 指数移动加权平均梯度 替换历史梯度的平方和。其计算过程如下:
- 初始化学习率 α、初始化参数 θ、小常数 σ = 1e-6
- 初始化参数 θ
- 初始化梯度累计变量 s
- 从训练集中采样 m 个样本的小批量,计算梯度 g
- 使用指数移动平均累积历史梯度,公式如下:
学习率 α 的计算公式如下:
参数更新公式如下:
改进AdaGrad:引入指数移动平均,缓解学习率衰减。
python
def test05():
# 1 初始化权重参数
w = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
y = ((w ** 2) / 2.0).sum()
# 2 实例化优化方法:RMSprop算法,其中alpha对应这beta
optimizer = torch.optim.RMSprop([w], lr=0.01, alpha=0.9)
# 3 第1次更新 计算梯度,并对参数进行更新
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
# 4 第2次更新 计算梯度,并对参数进行更新
# 使用更新后的参数机选输出结果
y = ((w ** 2) / 2.0).sum()
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
自适应优化器 - 梯度下降的优化方法 - Adam
Momentum 使用指数加权平均计算当前的梯度值
- AdaGrad、RMSProp 使用自适应的学习率
- Adam优化算法(Adaptive Moment Estimation,自适应矩估计)将 Momentum 和 RMSProp 算法结合在一起。
- 修正梯度: 使⽤梯度的指数加权平均。
- 修正学习率: 使用梯度平方的指数加权平均。
融合动量与自适应学习率:主流推荐优化器。
python
"""
梯度下降的优化方法-Adam
"""
def test06():
# 1 初始化权重参数
w = torch.tensor([1.0], requires_grad=True)
y = ((w ** 2) / 2.0).sum()
# 2 实例化优化方法:Adam算法,其中betas是指数加权的系数
optimizer = torch.optim.Adam([w], lr=0.01, betas=[0.9, 0.99])
# 3 第1次更新 计算梯度,并对参数进行更新
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第1次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
# 4 第2次更新 计算梯度,并对参数进行更新
# 使用更新后的参数机选输出结果
y = ((w ** 2) / 2.0).sum()
optimizer.zero_grad()
y.backward()
optimizer.step()
print('第2次: 梯度w.grad: %f, 更新后的权重:%f' % (w.grad.numpy(), w.detach().numpy()))
自适应优化器 - **梯度下降的优化方法 -**新型优化器:Lion(2023)
-
符号动量:仅保留梯度方向,减少内存占用。
-
公式:
-
优势:在语言模型和扩散模型中表现优异(如Stable Diffusion XL)。
优化器对比与选型指南
优化器 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
SGD | 简单、调参直观 | 依赖学习率调度、收敛慢 | 图像分类(需精细调参) |
Adam | 自适应学习率、收敛快 | 可能陷入局部最优、内存占用高 | 推荐系统、自然语言处理 |
Lion | 内存高效、适合大模型 | 超参数敏感、理论支持不足 | 大规模预训练模型 |
性能对比结果
优化器 | 训练损失(5 Epoch) | 收敛速度 | 测试准确率 |
---|---|---|---|
SGD | 0.32 | 慢 | 92.1% |
Adam | 0.18 | 快 | 95.6% |
RMSprop | 0.21 | 中等 | 94.3% |
六、学习率优化策略
学习率衰减(Learning Rate Decay) 是深度学习中优化模型训练的关键技术,目的是在训练过程中动态调整学习率,从而平衡收敛速度与模型精度。
固定学习率的弊端:
-
太大:参数更新步长过大,在最优解附近震荡。
-
太小:收敛速度慢,易陷入局部最优。
学习率衰减的优势
-
自适应调整:初期大学习率快速收敛,后期小学习率精细调优。
-
提升泛化:避免后期因步长过大跳过平坦最小值(泛化性能更好)。
学习率选择
-
试探法:从较大值(如0.1)开始,若损失爆炸则逐步降低(如0.01→0.001)。
-
经验值:
-
SGD:0.1~0.01
-
Adam:0.001~0.0001
-
Lion:0.0001~0.00001
-
**学习率优化方法-**等间隔学习率衰减
等间隔学习率衰减方式如下所示:
python
lr_scheduler.StepLR(optimizer, step_size, gamma=0.1)
# 功能:等间隔-调整学习率
# 参数:
# step_size:调整间隔数=50
# gamma:调整系数=0.5
# 调整方式:lr = lr * gamma

python
# 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()
# test_StepLR()
学习率优化方法**-**指定间隔学习率衰减
指定间隔学习率衰减的效果如下:
python
lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1)
# 功能:指定间隔-调整学习率
# 主要参数:
# milestones:设定调整轮次:[50, 125, 160]
# gamma:调整系数
# 调整方式:lr = lr * gamma

python
# 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()
# test_MultiStepLR()
学习率优化方法**-**按指数学习率衰减
按指数学习率衰减的效果如下:
python
lr_scheduler.ExponentialLR(optimizer, gamma)
# 功能:按指数衰减-调整学习率
# 主要参数:
# gamma:指数的底
# 调整方式
# lr= lr∗ gamma^epoch

python
# 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()
# test_ExponentialLR()
七、正则化方法
神经网络正则化:防止模型"过拟合"的关键技术
什么是正则化?为什么需要它?
-
核心定义 :正则化(Regularization)是一类通过约束模型复杂度 来减少过拟合(Overfitting)的技术。在设计机器学习算法时希望在新样本上的泛化能力强。许多机器学习算法都采用相关的策略来减小测试误差,这些策 略被统称为正则化。
-
过拟合现象 :模型在训练集上表现极佳,但在测试集上性能骤降(记忆噪声而非学习规律)。神经网络的强大的表示能力经常遇到过拟合,所以需要使用不同形式的正则化策略。
-
生活类比:学生死记硬背考题(训练集)但无法解答变型题(测试集) → 正则化相当于要求学生理解原理而非死记硬背。
目前在深度学习中使用较多的策略有 范数惩罚, DropOut ,特殊的网络层等 ,接下来我们对其进行详细的介绍。
Dropout****正则化
在练神经网络中模型参数较多,在数据量不足的情况下,很容易过拟合。 Dropout (随机失活)是一个简单有效的正则化方法。
- 在训练过程中,Dropout的实现是让神经元以超参数p的概率停止工作或者激活被置为0,未被置为0的进行缩放,缩放比例为****1/(1-p)。训练过程可以认为是对完整的神经网络的一些子集进行训练,每次基于输入数据只更新子网络的参数。
- 在测试过程中,随机失活不起作用。
python
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)
上述代码将 Dropout 层的概率 p 设置为 0.4 ,此时经过 Dropout 层计算的张量中就出现了很多 0 , 未变为 0 的按照( 1/(1-0.4) )进行处理。
批量归一化**(BN层)**

先对数据标准化,再对数据重构(缩放+平移),如下所示:

- λ 和 β 是可学习的参数,它相当于对标准化后的值做了一个线性变换,λ****为系数,β为偏置;
- eps 通常指为 1e-5,避免分母为 0;
- E(x) 表示变量的均值;
- Var(x) 表示变量的方差;
间接正则化:通过规范化层输入分布,允许使用更大学习率,隐含正则效果。
python
self.bn = nn.BatchNorm1d(256) # 添加在激活函数前
x = self.bn(F.relu(self.fc1(x)))
L1/L2正则化(权重衰减)
-
数学形式:
-
L1正则化:损失函数增加权重绝对值之和 → L′=L+λ∑∣wi∣L′=L+λ∑∣wi∣
-
L2正则化:损失函数增加权重平方和 → L′=L+λ∑wi2L′=L+λ∑wi2
-
-
作用机制:
-
L1:产生稀疏权重(部分权重归零),适合特征选择。
-
L2:约束权重趋向较小值,平滑模型输出。
-
-
代码示例(PyTorch):
python# L2正则化(通过优化器的weight_decay参数实现) optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
早停(Early Stopping)
-
原理:监控验证集损失,当连续NN个epoch未改善时提前终止训练。
-
优势:简单有效,避免无效训练。
-
实现工具 :PyTorch的
torch.utils.data
或Keras的EarlyStopping
回调。
数据增强(Data Augmentation)
-
思想:通过变换训练数据(旋转、裁剪、噪声等)增加数据多样性。
-
示例(图像分类):
pythontransform = transforms.Compose([ transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.RandomRotation(10), # 随机旋转±10度 transforms.ToTensor(), ])
八 、案例一手机价格分类
使用神经网络(NN)构建手机价格分类模型:从数据到预测
案例目标
根据手机特征(如品牌、屏幕尺寸、内存等),预测其价格区间(低端、中端、高端)。
一、数据准备与预处理
假设我们构造了一个模拟数据集,包含以下特征:
品牌(分类) | 屏幕尺寸(数值) | 内存(GB) | 存储(GB) | 摄像头像素(MP) | 价格区间(标签) |
---|---|---|---|---|---|
苹果 | 6.1 | 4 | 64 | 12 | 高端 |
小米 | 6.5 | 6 | 128 | 48 | 中端 |
华为 | 5.8 | 8 | 256 | 50 | 高端 |
... | ... | ... | ... | ... | ... |
1. 数据预处理步骤
-
分类特征编码:使用独热编码(One-Hot Encoding)处理品牌。
-
数值特征标准化:将屏幕尺寸、内存等缩放到[0,1]区间。
-
标签编码:将价格区间转换为数值标签(0=低端,1=中端,2=高端)。
python
import pandas as pd
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import train_test_split
# 构造模拟数据(示例)
data = {
'品牌': ['苹果', '小米', '华为', '三星', 'OPPO', 'vivo'],
'屏幕尺寸': [6.1, 6.5, 5.8, 6.4, 6.3, 6.2],
'内存': [4, 6, 8, 6, 8, 6],
'存储': [64, 128, 256, 128, 256, 128],
'摄像头像素': [12, 48, 50, 64, 48, 32],
'价格区间': ['高端', '中端', '高端', '中端', '高端', '低端']
}
df = pd.DataFrame(data)
# 特征与标签分离
X = df.drop('价格区间', axis=1)
y = df['价格区间'].map({'低端': 0, '中端': 1, '高端': 2})
# 预处理流水线
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), ['屏幕尺寸', '内存', '存储', '摄像头像素']),
('cat', OneHotEncoder(), ['品牌'])
])
X_processed = preprocessor.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X_processed, y, test_size=0.2, random_state=42)
二、构建神经网络模型
使用Keras构建一个全连接网络:
-
输入层:对应预处理后的特征维度。
-
隐藏层:两个全连接层,激活函数为ReLU。
-
输出层:3个神经元(对应3个类别),激活函数为Softmax。
-
损失函数:分类交叉熵(Categorical Crossentropy)。
python
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.utils import to_categorical
# 标签转为One-Hot编码
y_train_onehot = to_categorical(y_train)
y_test_onehot = to_categorical(y_test)
# 定义模型
model = Sequential([
Dense(64, activation='relu', input_shape=(X_train.shape[1],)),
Dense(32, activation='relu'),
Dense(3, activation='softmax')
])
# 编译模型
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy']
)
# 查看模型结构
model.summary()
三、训练与评估
1. 训练模型
python
# 训练参数
history = model.fit(
X_train, y_train_onehot,
epochs=50,
batch_size=32,
validation_split=0.2,
verbose=1
)
# 绘制训练曲线
import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'], label='训练集准确率')
plt.plot(history.history['val_accuracy'], label='验证集准确率')
plt.xlabel('Epoch')
plt.ylabel('准确率')
plt.legend()
plt.show()
2. 评估模型
python
# 测试集评估
loss, accuracy = model.evaluate(X_test, y_test_onehot)
print(f'测试集准确率: {accuracy:.2f}')
# 预测示例
sample = preprocessor.transform(pd.DataFrame({
'品牌': ['华为'],
'屏幕尺寸': [6.7],
'内存': [12],
'存储': [512],
'摄像头像素': [108]
}))
pred = model.predict(sample)
print(f'预测概率分布: {pred}') # 输出各类别概率
四、优化与调参
1. 处理类别不平衡
若数据中类别分布不均,可添加类别权重:
python
class_weights = {0: 1.5, 1: 1.0, 2: 0.8} # 低端样本较少,权重更高
history = model.fit(
X_train, y_train_onehot,
class_weight=class_weights,
epochs=50,
batch_size=32,
validation_split=0.2
)
2. 添加正则化
python
from tensorflow.keras.regularizers import l2
model = Sequential([
Dense(64, activation='relu', kernel_regularizer=l2(0.01),
Dense(32, activation='relu', kernel_regularizer=l2(0.01)),
Dense(3, activation='softmax')
])
3. 早停法(Early Stopping)
python
from tensorflow.keras.callbacks import EarlyStopping
early_stop = EarlyStopping(
monitor='val_loss',
patience=5,
restore_best_weights=True
)
history = model.fit(
X_train, y_train_onehot,
epochs=100,
callbacks=[early_stop]
)
五、模型部署与应用
1. 保存与加载模型
python
model.save('phone_price_classifier.h5') # 保存模型
loaded_model = tf.keras.models.load_model('phone_price_classifier.h5') # 加载模型
2. 新数据预测
python
def predict_price(brand, screen_size, ram, storage, camera):
input_data = pd.DataFrame({
'品牌': [brand],
'屏幕尺寸': [screen_size],
'内存': [ram],
'存储': [storage],
'摄像头像素': [camera]
})
processed_data = preprocessor.transform(input_data)
prob = loaded_model.predict(processed_data)
class_id = prob.argmax()
return {0: '低端', 1: '中端', 2: '高端'}[class_id]
# 示例预测
result = predict_price('苹果', 6.7, 8, 256, 12)
print(f'预测价格区间: {result}') # 输出:高端
六、总结与扩展
-
模型扩展方向:
-
增加更多特征(如电池容量、5G支持、发布日期)。
-
使用更复杂的架构(如自动编码器提取特征)。
-
-
实际应用建议:
-
收集真实市场数据(如京东、天猫商品信息)。
-
部署为API服务,供电商平台实时调用。
-