5. pytorch第一个神经网络

pytorch第一个神经网络

文章目录

  • pytorch第一个神经网络
    • 前言
    • [1. 简单前馈网络二分类](#1. 简单前馈网络二分类)
      • [1.1 完整代码](#1.1 完整代码)
      • [1.2 代码讲解](#1.2 代码讲解)
        • [1. 任务与网络结构概述](#1. 任务与网络结构概述)
        • [2. 数据准备](#2. 数据准备)
        • [3. 定义模型(使用 nn.Sequential,最简单方式)](#3. 定义模型(使用 nn.Sequential,最简单方式))
        • [4. 损失函数与优化器](#4. 损失函数与优化器)
        • [5. 训练循环](#5. 训练循环)
    • [2. 简单前馈网络二分类(散点图版)](#2. 简单前馈网络二分类(散点图版))
      • [2.1 完整代码](#2.1 完整代码)
      • [2.2 代码讲解](#2.2 代码讲解)
        • [1. 数据准备(生成二维点云)](#1. 数据准备(生成二维点云))
        • [2. 定义神经网络(SimpleNN 类)](#2. 定义神经网络(SimpleNN 类))
        • [3. 定义损失函数和优化器](#3. 定义损失函数和优化器)
        • [4. 训练模型(循环迭代)](#4. 训练模型(循环迭代))
        • [5. 可视化](#5. 可视化)
    • 总结

前言

本文通过 PyTorch 实现第一个神经网络,从简单二分类任务入手,逐步展示前馈网络的构建和训练过程。

首先使用虚拟数据演示基本结构,包括数据准备、模型定义、损失函数、优化器和训练循环;其次通过散点图版示例,探索网络在非线性决策边界上的表现,如圆形分类,帮助小白理解网络的灵活性和局限性。

通过代码讲解和可视化,将掌握 PyTorch 的核心流程,为更复杂模型如 CNN 打下基础。

1. 简单前馈网络二分类

我们用 PyTorch 构建一个小型全连接前馈神经网络(Feedforward Neural Network),完成一个二分类任务(输出 0 或 1)。

虽然数据是虚拟的,但代码结构和训练流程与真实项目完全一致。

1.1 完整代码

python 复制代码
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# 参数
n_in, n_h, n_out, batch_size = 10, 5, 1, 10

# 数据
x = torch.randn(batch_size, n_in)
y = torch.tensor([[1.0], [0.0], [0.0], [1.0], [1.0], 
                  [1.0], [0.0], [0.0], [1.0], [1.0]])

# 模型
model = nn.Sequential(
    nn.Linear(n_in, n_h),
    nn.ReLU(),
    nn.Linear(n_h, n_out),
    nn.Sigmoid()
)

# 损失 + 优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 训练
losses = []
for epoch in range(50):
    y_pred = model(x)
    loss = criterion(y_pred, y)
    losses.append(loss.item())
    
    print(f'Epoch [{epoch+1}/50], Loss: {loss.item():.4f}')
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

# 可视化1:损失曲线
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(range(1, 51), losses, 'g-', label='Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss')
plt.grid()

# 可视化2:预测 vs 真实
plt.subplot(1, 2, 2)
y_pred_final = model(x).detach().numpy()
plt.plot(range(1, 11), y.numpy(), 'o-b', label='Actual')
plt.plot(range(1, 11), y_pred_final, 'x--r', label='Predicted')
plt.xlabel('Sample')
plt.ylabel('Value')
plt.title('Actual vs Predicted')
plt.legend()
plt.grid()

plt.tight_layout()
plt.show()

运行结果:

1.2 代码讲解

1. 任务与网络结构概述
  • 任务:二分类(判断样本属于类别 0 还是 1)
  • 网络结构:
    • 输入层:10 个特征(n_in = 10)
    • 隐藏层:5 个神经元(n_h = 5),使用 ReLU 激活函数(引入非线性)
    • 输出层:1 个神经元(n_out = 1),使用 Sigmoid 激活函数(输出概率 0~1)

网络示意图: 输入 (10维) → Linear → ReLU → Linear → Sigmoid → 输出 (0~1)
输出层 隐藏层 输入层 特征提取 (5维) Linear: 5 -> 1 Sigmoid 激活 Linear: 10 -> 5 ReLU 激活 输入特征: x1...x10 预测概率: y_pred

2. 数据准备
python 复制代码
n_in, n_h, n_out, batch_size = 10, 5, 1, 10

x = torch.randn(batch_size, n_in)  # shape: [10, 10],随机输入
y = torch.tensor([[1.0], [0.0], [0.0], [1.0], [1.0], 
                  [1.0], [0.0], [0.0], [1.0], [1.0]])  # shape: [10, 1]

x:10 个样本,每个样本 10 个随机特征(正态分布)

y:人为设定的标签(二分类目标),1 表示正类,0 表示负类

3. 定义模型(使用 nn.Sequential,最简单方式)
python 复制代码
model = nn.Sequential(
    nn.Linear(n_in, n_h),   # 第一层线性变换:10 → 5
    nn.ReLU(),              # ReLU 激活:max(0, x),增加非线性
    nn.Linear(n_h, n_out),  # 第二层线性变换:5 → 1
    nn.Sigmoid()            # Sigmoid:输出压缩到 (0,1),适合二分类概率
)

在 PyTorch 中,如果你想搭建一个模型,有两种主流方式:一种是像搭积木一样把层排好队的 nn.Sequential,另一种是更灵活但复杂的 nn.Module 类定义。

对于初学者来说,nn.Sequential 是最直观的,因为它就像一个容器,数据按顺序进去,一层层加工,最后出来结果。

虽然它很方便,但它只有一张"单程票"。如果你的网络结构包含以下情况,就不能用它:

  • 多输入/多输出:比如你的模型需要同时输入图片和文字。
  • 残差连接 (Residual Connections):比如 ResNet 中,数据需要跳过某一层直接传给后面。
  • 分支结构:数据走到一半需要兵分两路处理。
4. 损失函数与优化器
python 复制代码
criterion = nn.MSELoss()                    # 均方误差(回归常用)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
5. 训练循环
python 复制代码
losses = []  # 记录损失用于画图

for epoch in range(50):
    y_pred = model(x)               # 前向传播
    loss = criterion(y_pred, y)     # 计算损失
    losses.append(loss.item())

    print(f'Epoch [{epoch+1}/50], Loss: {loss.item():.4f}')

    optimizer.zero_grad()           # 清零梯度(必须!)
    loss.backward()                 # 反向传播计算梯度
    optimizer.step()                # 更新所有参数

每轮:前向 → 损失 → 反向 → 更新

损失会逐渐下降(从 ~0.26 降到 ~0.20 左右),说明模型在学习

2. 简单前馈网络二分类(散点图版)

据二维点的位置,判断它在单位圆内(标签1)还是圆外(标签0)。

这是一个非线性决策问题(圆形边界不是直线),普通线性回归办不到,但加隐藏层和激活函数的网络就能学到。

2.1 完整代码

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

# 数据
n_samples = 100
data = torch.randn(n_samples, 2)
labels = (data[:, 0]**2 + data[:, 1]**2 < 1).float().unsqueeze(1)

plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm')
plt.title("Generated Data")
plt.show()

# 模型
class SimpleNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(2, 4)
        self.fc2 = nn.Linear(4, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.sigmoid(self.fc2(x))
        return x

model = SimpleNN()

# 损失 + 优化
criterion = nn.BCELoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 训练
epochs = 100
for epoch in range(epochs):
    outputs = model(data)
    loss = criterion(outputs, labels)
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')

# 可视化边界
def plot_decision_boundary(model, data):
    x_min, x_max = data[:, 0].min() - 1, data[:, 0].max() + 1
    y_min, y_max = data[:, 1].min() - 1, data[:, 1].max() + 1
    xx, yy = torch.meshgrid(torch.arange(x_min, x_max, 0.1), 
                            torch.arange(y_min, y_max, 0.1), indexing='ij')
    grid = torch.cat([xx.reshape(-1, 1), yy.reshape(-1, 1)], dim=1)
    predictions = model(grid).detach().numpy().reshape(xx.shape)
    plt.contourf(xx, yy, predictions, levels=[0, 0.5, 1], cmap='coolwarm', alpha=0.7)
    plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm', edgecolors='k')
    plt.title("Decision Boundary")
    plt.show()

plot_decision_boundary(model, data)

运行结果:

2.2 代码讲解

1. 数据准备(生成二维点云)
python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

# 生成随机数据
n_samples = 100
data = torch.randn(n_samples, 2)  # 100 个二维点,服从正态分布
labels = (data[:, 0]**2 + data[:, 1]**2 < 1).float().unsqueeze(1)  # 圆内=1,圆外=0

# 可视化
plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm')
plt.title("Generated Data")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.show()

解释:

  • data:形状 100, 2,每个行是一个样本(点),两列是特征 x1 和 x2。从 torch.randn 生成,均值0,方差1,模拟随机散点。
  • labels:形状 100, 1。数学公式:如果 x1² + x2² < 1(单位圆内),标签=1(正类);否则=0。
  • .float():转浮点,
  • .unsqueeze(1): 加维度成列向量(PyTorch 损失函数要求)。
    • 为什么这样?创建非线性边界(圆形),测试网络捕捉曲线能力。如果是线性可分(如直线分隔),单层就够;这里需隐藏层。
  • 可视化:scatter 散点图,c=labels 用颜色区分(coolwarm 色图:蓝=0,红=1)。预期:点云围绕原点,圆内红点密集,圆外蓝点。

(上图:类似生成的散点数据,圆内/圆外分类。)

专业知识 :这是合成数据集,现实中用真实数据(如 Iris)。数学:标签基于欧氏距离,d = √(x1² + x2²) < 1。

这里生成的数据如图所示:

2. 定义神经网络(SimpleNN 类)
python 复制代码
class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(2, 4)  # 输入2维 → 隐藏4维
        self.fc2 = nn.Linear(4, 1)  # 隐藏4维 → 输出1维
        self.sigmoid = nn.Sigmoid()  # 输出激活

    def forward(self, x):
        x = torch.relu(self.fc1(x))  # 隐藏层:线性 + ReLU
        x = self.sigmoid(self.fc2(x))  # 输出层:线性 + Sigmoid
        return x

model = SimpleNN()

解释

  • 继承 nn.Module:PyTorch 所有模型的基类,提供参数管理、GPU 支持等。
  • init :定义层。
    • fc1:nn.Linear(2,4) 全连接层,权重矩阵 W1 4,2 + 偏置 b1 4。数学:隐藏输出 = W1 * x + b1
    • fc2:nn.Linear(4,1),W2 1,4 + b2 1。输出 = W2 * 隐藏 + b2
    • sigmoid:nn.Sigmoid(),公式 σ ( z ) = 1 / ( 1 + e − z ) σ(z) = 1 / (1 + e^{-z}) σ(z)=1/(1+e−z),输出 0,1 概率。
  • forward:前向传播路径。
    • torch.relu:隐藏激活,f(z) = max(0, z),引入非线性(否则多层还是线性)。
    • 为什么隐藏层 4 个神经元?小网络演示;大任务可加层/神经元。

专业知识 :这是 2-4-1 结构 MLP。参数总数:(24 +4) + (41 +1) = 17。ReLU 避免 Sigmoid 的梯度消失(Sigmoid 导数 max 0.25,易变0)。

3. 定义损失函数和优化器
python 复制代码
criterion = nn.BCELoss()  # 二元交叉熵
optimizer = optim.SGD(model.parameters(), lr=0.1)

解释

  • BCELoss:二元交叉熵,适合二分类。公式 L = - y log(ŷ) + (1-y) log(1-ŷ),平均所有样本。
    • 为什么 BCE?比 MSE 更好处理概率(预测 0.9 vs 0.1,BCE 惩罚更合理)。
  • SGD:随机梯度下降,lr=0.1(学习率)。model.parameters() 返回所有权重/偏置。
4. 训练模型(循环迭代)
python 复制代码
epochs = 100
for epoch in range(epochs):
    outputs = model(data)  # 前向
    loss = criterion(outputs, labels)  # 损失

    optimizer.zero_grad()  # 清梯度
    loss.backward()  # 反向
    optimizer.step()  # 更新

    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}')

解释

  • 循环:100 轮(epochs),每轮全数据训练(小数据集,无 batch)。
  • 前向:outputs = model(data),形状 100,1,概率值。
  • 损失:criterion 计算 BCE。
  • 反向:zero_grad 清零(梯度累加问题);backward 计算 ∂L/∂w;step 更新 w -= lr * ∇w。
  • 打印:损失从 ~0.5 降到 ~0.1,模型在学。
5. 可视化
python 复制代码
def plot_decision_boundary(model, data):
    x_min, x_max = data[:, 0].min() - 1, data[:, 0].max() + 1
    y_min, y_max = data[:, 1].min() - 1, data[:, 1].max() + 1
    xx, yy = torch.meshgrid(torch.arange(x_min, x_max, 0.1), 
                            torch.arange(y_min, y_max, 0.1), indexing='ij')
    grid = torch.cat([xx.reshape(-1, 1), yy.reshape(-1, 1)], dim=1)
    predictions = model(grid).detach().numpy().reshape(xx.shape)
    plt.contourf(xx, yy, predictions, levels=[0, 0.5, 1], cmap='coolwarm', alpha=0.7)
    plt.scatter(data[:, 0], data[:, 1], c=labels.squeeze(), cmap='coolwarm', edgecolors='k')
    plt.title("Decision Boundary")
    plt.show()

plot_decision_boundary(model, data)

解释

  • 网格生成:xx, yy 创建密集网格点(步长0.1),grid N,2 是所有点。
  • 预测:model(grid) 计算每个网格点的概率,reshape 回 2D。
  • 可视化:contourf 填充等高线(>0.5 红区,<0.5 蓝区);scatter 加原始点。
  • 预期:边界近似圆形,红区内点多为红,蓝区多为蓝。

(上图:类似决策边界,网络学到的圆形分界。)

专业知识:决策边界是 f(x)=0.5 的等值线。detach() 断开梯度,避免内存浪费。

注意:

为什么可视化结果是一条"直线"或"扇形",而不是圆形边界?

因为 SimpleNN 结构太简单:

  • self.fc1 = nn.Linear(2, 4):隐藏层只有 4个神经元
  • epochs = 100:只训练了 100轮

这就是问题所在:

  1. 神经元太少:每一个隐藏层的神经元本质上是在空间中画出一道"折痕"。4个神经元最多只能折出 4 条直线。
  2. 训练不足 :从输出日志看,Loss 还在 0.5954。对于二分类,如果模型完全随机猜,Loss 大约是 0.69。0.59 说明模型才刚刚开始摸到一点门道,还没来得及把那 4 条线围成一个圆,只画出了一个"尖角"(如图中那块橙色的区域)。

总结

通过两个简单前馈网络二分类示例,可以看到 PyTorch 如何实现模型定义、训练和可视化:从随机数据生成到决策边界绘制,网络通过隐藏层和激活函数捕捉非线性模式,但受限于神经元数量和训练轮次(如在圆形边界任务中可能仅学到近似折线)。

这凸显了神经网络的核心:参数优化依赖梯度下降,性能取决于结构设计和数据质量。掌握这些,已能扩展到真实任务,如图像分类;下一步建议尝试加深层数或用真实数据集实验,继续探索!

相关推荐
EMA2 分钟前
ERP结合多 Agent 项目技术解析文档
人工智能
世间一点尘3 分钟前
我让 Claude Code 修一个 Bug,它却重构了半个项目
人工智能
科技林总3 分钟前
大模型分类测评指标清单
人工智能·可用性测试
为码消得人憔悴3 分钟前
从零开始搭建 Obsidian 知识库
人工智能·aigc·agent
EMA7 分钟前
MaxKB 技术解析文档
人工智能
湘美书院--湘美谈教育7 分钟前
湘美谈教育AI赋能系列经验集锦:学好唐诗宋词的点滴心得体会
大数据·人工智能·深度学习·神经网络·机器学习
迦蓝叶13 分钟前
【开源自荐】JAiRouter:一个轻量级 AI 模型服务网关的开源实践
java·人工智能·spring·开源·llm-gateway·mass
Java知识技术分享21 分钟前
opencode安装ui-ux-pro-max和frontend-ui-ux技能
人工智能·ui·个人开发·ai编程·ux
苏映视官方账号27 分钟前
精品案例丨方寸之间,“微” 毫毕现 —— 圆刀机高精度检测工艺优化实例
人工智能·数码相机·视觉检测·制造