当前位置: 首页 > ops >正文

8.4 打卡 DAY 33: 第一个神经网络 - MLP的构建与训练

DAY 33: 第一个神经网络 - MLP的构建与训练

核心概念回顾

  1. 多层感知机 (MLP):神经网络的基本形态。
  2. 梯度下降 (Gradient Descent):神经网络的学习方式。
  3. 激活函数 (Activation Function):为网络注入“非线性”。
  4. 损失函数 (Loss Function):衡量“预测”与“现实”的差距。
  5. 优化器 (Optimizer):更新网络参数的工具。

1. 什么是多层感知机 (MLP)?

多层感知机(Multi-Layer Perceptron),简称MLP,是神经网络最基本的一种结构。你可以把它想象成一个由多个“计算层”堆叠起来的系统:

  • 输入层 (Input Layer):接收最原始的数据。比如在我们的鸢尾花项目中,输入层就有4个神经元,分别对应4个特征(花萼长、宽等)。
  • 隐藏层 (Hidden Layers):位于输入层和输出层之间,负责从数据中提取更复杂、更抽象的特征。一个MLP可以没有隐藏层,也可以有一个或多个。正是这些隐藏层让神经网络拥有了强大的学习能力。
  • 输出层 (Output Layer):产生最终的预测结果。在我们的项目中,输出层有3个神经元,分别代表3种鸢尾花的类别。

数据从输入层开始,逐层向前传递并进行计算,直到输出层得出结果,这个过程被称为前向传播 (Forward Propagation)

2. 梯度下降:机器如何“学习”?

神经网络的学习过程,本质上是一个**寻找最优参数(权重和偏置)**的过程,目标是让模型的预测结果与真实标签尽可能地接近。梯度下降就是实现这个目标最核心的算法。

想象你在一个漆黑的山谷里,想要走到谷底(最低点)。你看不清路,但你能感知到脚下哪个方向是向下的。于是,你每走一步,都选择当前位置最陡峭的下坡方向迈出一小步。不断重复这个过程,最终你就能到达谷底。

在这个比喻中:

  • 山谷的形状:由损失函数定义,它描述了预测错误程度。
  • 你的位置:代表当前模型的参数
  • 谷底:代表能使预测错误最小化的最优参数
  • 最陡峭的下坡方向:这就是梯度 (Gradient),它指向损失增长最快的方向,因此它的反方向就是损失下降最快的方向。
  • 你迈出的一小步:由学习率 (Learning Rate) 控制,决定了每次参数更新的幅度。
3. 激活函数:赋予网络“拐弯”的能力

如果神经网络的每一层都只是简单的线性计算(如 y = wx + b),那么无论叠加多少层,整个网络最终也只能表示一个复杂的线性关系。这就像你只能画直线,无法画出曲线一样,表达能力非常有限。

激活函数的作用就是为网络引入非线性因素。它作用于每个神经元的输出上,将线性计算的结果进行一次非线性转换。最常用的激活函数之一是 ReLU (Rectified Linear Unit),它的规则非常简单:输入大于0时,输出等于输入;输入小于等于0时,输出为0。

正是这些非线性“拐弯”的能力,让神经网络可以拟合各种复杂的函数关系。

4. 损失函数:一把衡量“差距”的尺子

损失函数(也叫成本函数或目标函数)是用来量化模型预测值真实标签之间差距的工具。这个差距值(损失值)越小,说明模型预测得越准。

  • 回归问题:常用均方误差损失 (MSE Loss),计算预测值与真实值之差的平方和的平均值。
  • 分类问题:常用交叉熵损失 (Cross-Entropy Loss),它能量化两个概率分布之间的差异。在多分类任务中,它非常有效。

整个训练过程的目标,就是通过调整模型参数来最小化这个损失值。

5. 优化器:参数更新的“指挥官”

优化器是梯度下降算法的具体实现者。它接收损失函数计算出的梯度,并按照一定的规则来更新模型的参数。

  • 随机梯度下降 (SGD):最基础的优化器。它根据当前批次数据的梯度来更新参数。
  • Adam (Adaptive Moment Estimation):一种更高级的优化器。它能为每个参数自适应地调整学习率,通常能更快、更稳定地收敛,是目前非常流行的选择。

炼丹前的准备:安装PyTorch与CUDA

深度学习涉及大量的简单并行计算,这正是GPU(图形处理器)的优势所在。相较于一个能处理复杂任务的博士生(CPU),GPU更像是100个能飞快完成简单加减乘除的小学生,人多力量大。

我们主要使用支持NVIDIA显卡CUDA并行计算架构的PyTorch。

  • 硬件检查 (NVIDIA显卡用户)
    在你的电脑CMD(命令提示符)中输入 nvidia-smi 命令,可以查看显卡信息。

    重点关注两个信息:

    1. CUDA Version:显卡驱动支持的最高CUDA版本(如12.7)。
    2. 显存大小:例如12288 MiB (12 GB),这决定了你能训练多大的模型。
  • 环境安装
    强烈建议为深度学习项目创建一个独立的Conda新环境,例如命名为DL。请参考我们提供的安装教程或在B站搜索相关视频,完成PyTorch和(如果适用)CUDA、cuDNN的安装。

    • 怕麻烦的同学:可以直接安装CPU版本的PyTorch,代码同样可以跑通。
    • 追求性能的同学:请务必安装与你显卡兼容的GPU版本。
  • 代码验证CUDA
    安装完成后,可以用以下代码在Python中检查PyTorch是否能成功调用CUDA。

    import torch# 检查CUDA是否可用
    if torch.cuda.is_available():print("CUDA可用!")print(f"可用的CUDA设备数量: {torch.cuda.device_count()}")print(f"当前CUDA设备的名称: {torch.cuda.get_device_name(torch.cuda.current_device())}")print(f"PyTorch检测到的CUDA版本: {torch.version.cuda}")
    else:print("CUDA不可用,将使用CPU进行训练。")
    

    注意:这里显示的CUDA版本是PyTorch编译时所用的版本,它需要小于等于你显卡驱动支持的最高版本。


2. 神经网络的流程:一步一脚印

a. 数据预处理

我们依然使用经典的鸢尾花数据集(4个特征,3个类别)。神经网络对输入数据的尺度非常敏感,因此归一化是必不可少的步骤。此外,PyTorch使用一种名为**张量(Tensor)**的特殊数据结构进行计算。

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
import torch
import numpy as np# 1. 加载和划分数据
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 2. 归一化数据 (非常重要!)
scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
# 注意:测试集使用训练集学习到的scaler进行transform,保证数据尺度一致
X_test = scaler.transform(X_test) # 3. 转换为 PyTorch 张量
X_train = torch.FloatTensor(X_train)
X_test = torch.FloatTensor(X_test)
# 标签y是整数类别,需要转换为LongTensor
y_train = torch.LongTensor(y_train)
y_test = torch.LongTensor(y_test)print("训练数据尺寸:", X_train.shape)
print("训练标签尺寸:", y_train.shape)

预处理补充

  • 分类任务:标签通常是整数(0, 1, 2…),需要转换为 torch.LongTensor
  • 回归任务:标签通常是连续的浮点数,需要转换为 torch.FloatTensor
b. 模型的定义

我们来定义一个简单的MLP模型,它包含一个输入层、一个隐藏层和一个输出层。在PyTorch中,自定义模型通常需要:

  1. 继承 nn.Module 类。
  2. __init__ (初始化方法) 中定义好每一“层”的结构。
  3. forward (前向传播方法) 中定义数据是如何从输入层流经每一层最终到达输出层的。
import torch.nn as nn
import torch.optim as optimclass MLP(nn.Module):def __init__(self):# 调用父类的初始化函数 (八股文)super(MLP, self).__init__()# 定义网络层# 输入层(4个特征) -> 隐藏层(10个神经元)self.fc1 = nn.Linear(4, 10) # 激活函数self.relu = nn.ReLU()# 隐藏层(10个神经元) -> 输出层(3个类别)self.fc2 = nn.Linear(10, 3)def forward(self, x):# 定义数据前向传播的顺序out = self.fc1(x)out = self.relu(out)out = self.fc2(out)return out# 实例化我们定义的模型
model = MLP()
print(model)

注意:输出层通常不需要激活函数,因为像交叉熵这样的损失函数内部已经包含了Softmax操作,会将输出转换为概率。

c. 定义损失函数和优化器
  • 损失函数 (Criterion):衡量模型预测值与真实值之间的差距。对于多分类问题,我们使用交叉熵损失 (nn.CrossEntropyLoss)
  • 优化器 (Optimizer):根据损失函数计算出的梯度,来更新模型的参数(权重和偏置),以使损失最小化。我们这里使用经典的随机梯度下降 (optim.SGD)
# 定义损失函数
criterion = nn.CrossEntropyLoss()# 定义优化器,传入模型的所有参数(model.parameters())和学习率(lr)
optimizer = optim.SGD(model.parameters(), lr=0.01)
d. 定义训练流程 (CPU版本)

训练过程就是一个循环,在每一轮(Epoch)中:

  1. 前向传播:将训练数据输入模型,得到预测输出。
  2. 计算损失:用损失函数计算预测输出和真实标签的差距。
  3. 反向传播
    • optimizer.zero_grad():清除上一轮的梯度。
    • loss.backward():计算当前损失关于模型各参数的梯度。
    • optimizer.step():优化器根据梯度更新模型参数。
num_epochs = 20000 # 训练的总轮数
losses = [] # 用于存储每一轮的损失值for epoch in range(num_epochs):# 前向传播outputs = model(X_train) # 隐式调用forward方法loss = criterion(outputs, y_train)# 反向传播和优化optimizer.zero_grad()   # 梯度清零loss.backward()         # 计算梯度optimizer.step()        # 更新参数# 记录损失值losses.append(loss.item()) # .item()用于从张量中提取数值# 每100轮打印一次训练信息if (epoch + 1) % 100 == 0:print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
e. 可视化Loss过程

通过绘制损失曲线,我们可以直观地看到模型训练过程中损失是否在稳定下降,这是判断模型是否有效学习的重要依据。

import matplotlib.pyplot as pltplt.plot(range(num_epochs), losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss over Epochs')
plt.show()

从图中可以看到,损失值随着训练轮数的增加而平稳下降并最终收敛,说明我们的模型学到东西了!


@浙大疏锦行

http://www.xdnf.cn/news/16988.html

相关文章:

  • usr/bin/ld链接报错undefined reference `av_frame_free(AVFrame**)‘等ffmpeg库报错
  • VAE学习笔记
  • Visual Studio Code的下载,安装
  • 机器学习(11):岭回归Ridge
  • iOS混淆工具有哪些?功能测试与质量保障兼顾的混淆策略
  • OpenLayers 入门指南【五】:Map 容器
  • C语言的数组与字符串
  • 力扣热题100——双指针
  • Hadoop MapReduce 3.3.4 讲解~
  • SpringBoot自动装配原理
  • 36.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--缓存Token
  • 编程算法:技术创新与业务增长的核心驱动力
  • IDA9.1使用技巧(安装、中文字符串显示、IDA MCP服务器详细部署和MCP API函数修改开发经验)
  • 电商直播流量爆发式增长,华为云分布式流量治理与算力调度服务的应用场景剖析
  • 构建属于自己的第一个 MCP 服务器:初学者教程
  • 从零认识OpenFlow
  • 学习游戏制作记录(角色属性和状态脚本)8.4
  • 【Linux指南】软件安装全解析:从源码到包管理器的进阶之路
  • AI鉴伪技术鉴赏:“看不见”的伪造痕迹如何被AI识破
  • Java项目:基于SSM框架实现的电子病历管理系统【ssm+B/S架构+源码+数据库+毕业论文+远程部署】
  • Git如何同步本地与远程仓库并解决冲突
  • 【iOS】渲染原理离屏渲染
  • 打造个人数字图书馆:LeaNote+cpolar如何成为你的私有化知识中枢?
  • 时序数据库如何高效处理海量数据
  • Spring P1 | 创建你的第一个Spring MVC项目(IDEA图文详解版,社区版专业版都有~)
  • 【数据库】使用Sql Server创建索引优化查询速度,一般2万多数据后,通过非索引时间字段排序查询出现超时情况
  • Anthropic 禁止 OpenAI 访问 Claude API:商业竞争与行业规范的冲突
  • 接口重试方案,使用网络工具的内置重试机制,并发框架异步重试,Spring Retry,消息队列重试,Feign调用重试,监控与报警,避坑指南
  • Linux 系统启动原理
  • mac 技巧