DAY33 简单神经网络
你需要自行了解下MLP的概念。
你需要知道
- 梯度下降的思想
- 激活函数的作用
- 损失函数的作用
- 优化器
- 神经网络的概念
神经网络由于内部比较灵活,所以封装的比较浅,可以对模型做非常多的改进,而不像机器学习三行代码固定。
1. 神经网络的概念 (The concept of neural networks)
您可以把神经网络想象成一个由许多相互连接的“神经元”(neurons)组成的系统,它模仿了人类大脑处理信息的方式。这些神经元组织成层(layers):
- 输入层 (Input Layer): 接收原始数据,比如一张图片的像素值,或者鸢尾花数据集中的花瓣、花萼的长度和宽度。
- 隐藏层 (Hidden Layers): 在输入层和输出层之间,负责进行大部分的计算和特征提取。一个神经网络可以有多个隐藏层。层数越多,网络通常能学习更复杂的模式。
- 输出层 (Output Layer): 输出最终结果,比如图片的分类(猫或狗),或者鸢尾花的种类。
每个连接都有一个相关的“权重”(weight),这个权重决定了前一个神经元对后一个神经元的影响程度。训练神经网络的过程,就是不断调整这些权重,使得网络能够对输入数据做出正确的预测。
2. 梯度下降的思想 (The idea of gradient descent)
梯度下降是一种优化算法,用于寻找函数(比如损失函数,后面会讲到)的最小值。想象一下你在一座山上,想要走到山谷的最低点,但周围有大雾,你看不清路。梯度下降就像你每走一步都选择当前位置最陡峭的下坡方向,这样就能最快到达谷底。
在神经网络中,“山”就是损失函数,我们要找的“谷底”就是损失函数的最小值点,对应的神经网络参数(主要是权重)就是最优的参数。梯度下降通过计算损失函数对于每个参数的“梯度”(可以理解为斜率或变化率),然后沿着梯度的反方向更新参数,从而逐渐减小损失。学习率(learning rate)是梯度下降中的一个重要超参数,它控制了每一步更新参数的幅度。
3. 损失函数的作用 (The role of loss functions)
损失函数(Loss Function),也叫代价函数(Cost Function),用来衡量神经网络模型预测结果与真实标签之间的差异。它的输出值(损失值)越大,表示模型的预测越不准确;损失值越小,表示模型预测越准确。
训练神经网络的目标就是最小化损失函数。通过梯度下降等优化算法,我们不断调整网络的权重,使得损失值越来越小。
常见的损失函数有:
- 均方误差 (Mean Squared Error, MSE): 常用于回归问题(预测连续值)。
- 交叉熵损失 (Cross-Entropy Loss): 常用于分类问题(预测离散类别)。您在代码中看到的
nn.CrossEntropyLoss()
就是这个。
4. 激活函数的作用 (The role of activation functions)
激活函数(Activation Function)被应用于神经网络中每个神经元的输出。它们的主要作用是给神经网络引入非线性(non-linearity)。
如果不用激活函数,或者只用线性激活函数(比如f(x)=x),那么无论神经网络有多少层,它本质上都只是一个线性模型,无法学习复杂的数据模式。而非线性激活函数使得神经网络能够拟合各种复杂的非线性关系。
常见的激活函数有:
- Sigmoid: 将输出压缩到0和1之间,常用于二分类问题的输出层。
- Tanh (双曲正切): 将输出压缩到-1和1之间。
- ReLU (Rectified Linear Unit): 计算公式是
f(x) = max(0, x)
。它非常简单高效,是目前最常用的激活函数之一。您在代码中看到的nn.ReLU()
就是这个。 - Softmax: 常用于多分类问题的输出层,它能将输出层的原始分数转换成概率分布,使得所有输出类别的概率和为1。
5. 优化器 (Optimizers)
优化器(Optimizer)是实现梯度下降思想的具体算法。它根据损失函数计算出的梯度来更新神经网络的权重,目标是最小化损失函数。
除了基本的随机梯度下降(Stochastic Gradient Descent, SGD,您代码中用的 optim.SGD
),还有许多更高级的优化器,它们在SGD的基础上做了一些改进,试图更快、更稳定地找到损失函数的最小值。
常见的优化器有:
- SGD (Stochastic Gradient Descent): 基本的梯度下降。
- Momentum: 在SGD的基础上引入了动量,可以加速收敛并减少震荡。
- AdaGrad (Adaptive Gradient): 对不同参数使用不同的学习率。
- RMSprop (Root Mean Square Propagation): 也是自适应学习率的一种。
- Adam (Adaptive Moment Estimation): 结合了Momentum和RMSprop的优点,是目前非常流行和常用的优化器之一。您代码中注释掉的
optim.Adam
就是这个。
简单来说,神经网络通过损失函数来衡量预测的好坏,通过梯度下降的思想,在优化器的帮助下,不断调整神经元之间的连接权重,而激活函数则赋予了网络学习复杂模式的能力。
数据的准备
# 仍然用4特征,3分类的鸢尾花数据集作为我们今天的数据集
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import numpy as np# 加载鸢尾花数据集
iris = load_iris()
X = iris.data # 特征数据
y = iris.target # 标签数据
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 打印下尺寸
print(X_train.shape)
print(y_train.shape)
print(X_test.shape)
print(y_test.shape)
归一化数据
# 归一化数据,神经网络对于输入数据的尺寸敏感,归一化是最常见的处理方式
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test) #确保训练集和测试集是相同的缩放
改变成张量
# 将数据转换为 PyTorch 张量,因为 PyTorch 使用张量进行训练
# y_train和y_test是整数,所以需要转化为long类型,如果是float32,会输出1.0 0.0
X_train = torch.FloatTensor(X_train)
y_train = torch.LongTensor(y_train)
X_test = torch.FloatTensor(X_test)
y_test = torch.LongTensor(y_test)
模型架构定义
定义一个简单的全连接神经网络模型,包含一个输入层、一个隐藏层和一个输出层。
定义层数+定义前向传播顺序
class MLP(nn.Module): # 定义一个多层感知机(MLP)模型,继承父类nn.Moduledef __init__(self): # 初始化函数super(MLP, self).__init__() # 调用父类的初始化函数# 前三行是八股文,后面的是自定义的self.fc1 = nn.Linear(4, 10) # 输入层到隐藏层self.relu = nn.ReLU()self.fc2 = nn.Linear(10, 3) # 隐藏层到输出层
# 输出层不需要激活函数,因为后面会用到交叉熵函数cross_entropy,交叉熵函数内部有softmax函数,会把输出转化为概率def forward(self, x):out = self.fc1(x)out = self.relu(out)out = self.fc2(out)return out# 实例化模型
model = MLP()
或者
# def forward(self,x): #前向传播# x=torch.relu(self.fc1(x)) #激活函数# x=self.fc2(x) #输出层不需要激活函数,因为后面会用到交叉熵函数cross_entropy# return x
定义损失函数和优化器
# 分类问题使用交叉熵损失函数
criterion = nn.CrossEntropyLoss()# 使用随机梯度下降优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)# # 使用自适应学习率的化器
# optimizer = optim.Adam(model.parameters(), lr=0.001)
开始循环训练
实际上在训练的时候,可以同时观察每个epoch训练完后测试集的表现:测试集的loss和准确度
# 训练模型
num_epochs = 20000 # 训练的轮数# 用于存储每个 epoch 的损失值
losses = []for epoch in range(num_epochs): # range是从0开始,所以epoch是从0开始# 前向传播outputs = model.forward(X_train) # 显式调用forward函数# outputs = model(X_train) # 常见写法隐式调用forward函数,其实是用了model类的__call__方法loss = criterion(outputs, y_train) # output是模型预测值,y_train是真实标签# 反向传播和优化optimizer.zero_grad() #梯度清零,因为PyTorch会累积梯度,所以每次迭代需要清零,梯度累计是那种小的bitchsize模拟大的bitchsizeloss.backward() # 反向传播计算梯度optimizer.step() # 更新参数# 记录损失值losses.append(loss.item())# 打印训练信息if (epoch + 1) % 100 == 0: # range是从0开始,所以epoch+1是从当前epoch开始,每100个epoch打印一次print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
如果你重新运行上面这段训练循环,模型参数、优化器状态和梯度会继续保留,导致训练结果叠加,模型参数和优化器状态(如动量、学习率等)不会被重置。这会导致训练从之前的状态继续,而不是从头开始
不会重置
import matplotlib.pyplot as plt
# 可视化损失曲线
plt.plot(range(num_epochs), losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss over Epochs')
plt.show()
浙大疏锦行-CSDN博客