从零开始的Pytorch【02】:构建你的第一个神经网络

从零开始的Pytorch【02】:构建你的第一个神经网络

前言

欢迎来到PyTorch学习系列的第二篇!在上一篇文章中,我们介绍了PyTorch的基本概念,包括张量、自动求导和Jupyter Notebook的使用。在这篇文章中,我们将继续深入,指导你如何使用PyTorch构建一个简单的神经网络并进行训练。这将是你迈向深度学习应用的第一步。

什么是神经网络?

神经网络(Neural Network)是深度学习的核心,它模仿了人类大脑的神经元结构来处理和分析数据。一个典型的神经网络由多个层(layers)组成,每层包含若干个神经元(neurons),通过权重(weights)和偏置(biases)相连接。神经网络的目的是通过调整这些权重和偏置,使得输入数据通过网络后得到的输出接近于预期结果。

在本教程中,我们将构建一个简单的前馈神经网络(Feedforward Neural Network),并使用它来处理一个二分类问题。

构建一个简单的神经网络

首先,我们需要导入PyTorch库,并定义我们要使用的网络模型。这里我们将使用PyTorch的torch.nn模块,该模块提供了许多构建神经网络的基础工具。

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

# 定义一个简单的神经网络类
class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        # 定义输入层到隐藏层的全连接层
        self.fc1 = nn.Linear(2, 10)
        # 定义隐藏层到输出层的全连接层
        self.fc2 = nn.Linear(10, 1)

    def forward(self, x):
        # 使用ReLU激活函数处理输入层到隐藏层
        x = F.relu(self.fc1(x))
        # 使用Sigmoid激活函数处理隐藏层到输出层
        x = torch.sigmoid(self.fc2(x))
        return x

# 创建模型实例
model = SimpleNN()

解析:

  1. 定义网络结构:我们使用nn.Module类定义了一个简单的神经网络模型。fc1是从输入层到隐藏层的全连接层,fc2是从隐藏层到输出层的全连接层。
  2. 前向传播:在forward方法中,我们定义了数据如何通过网络传播。这里我们使用了ReLU激活函数(用于隐藏层)和Sigmoid激活函数(用于输出层)。
  3. 创建模型实例:最后,我们创建了一个SimpleNN模型的实例。
准备训练数据

接下来,我们需要为模型准备训练数据。在这个示例中,我们使用一个简单的二分类数据集,其中每个输入有两个特征,输出为0或1。

# 准备训练数据
data = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
labels = torch.tensor([[0.0], [1.0], [1.0], [0.0]])

# 查看数据和标签
print("Data:", data)
print("Labels:", labels)

在这里插入图片描述

解析:

  • 数据集:这里的数据集data由四个二维数据点组成,每个点对应一个标签labels。这个数据集表示了一个简单的逻辑异或(XOR)问题。
定义损失函数和优化器

在训练神经网络时,我们需要定义一个损失函数来衡量模型的预测与实际标签之间的差异。我们还需要定义一个优化器来更新模型的权重,使损失最小化。

# 定义损失函数和优化器
criterion = nn.BCELoss()  # 二分类交叉熵损失函数
optimizer = optim.SGD(model.parameters(), lr=0.1)  # 随机梯度下降优化器,学习率为0.1

解析:

  • 损失函数:我们使用BCELoss,即二分类交叉熵损失函数,它非常适合处理二分类问题。
  • 优化器:我们使用随机梯度下降(SGD)优化器,并设置学习率lr为0.1。
训练神经网络

接下来,我们将训练神经网络。训练过程包括前向传播(计算预测值)、计算损失、反向传播(计算梯度),以及更新权重。我们将这个过程循环多次,直到模型的性能达到满意的水平。

# 训练神经网络
num_epochs = 1000  # 训练迭代次数

for epoch in range(num_epochs):
    # 前向传播
    outputs = model(data)
    loss = criterion(outputs, labels)
    
    # 反向传播和优化
    optimizer.zero_grad()  # 清除梯度缓存
    loss.backward()  # 计算梯度
    optimizer.step()  # 更新权重
    
    if (epoch+1) % 100 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')

在这里插入图片描述

在训练的1000个epoch中,模型的损失函数值从最初的0.5077下降到0.0122,显示出模型的预测误差在逐渐减少,性能逐步提高。以下是训练过程中的一些关键点:

  • Epoch [100/1000]: Loss = 0.5077
  • Epoch [200/1000]: Loss = 0.2628
  • Epoch [500/1000]: Loss = 0.0420
  • Epoch [1000/1000]: Loss = 0.0122

训练日志表明,随着训练的进行,损失值显著下降。这表明通过反复的训练迭代,模型成功学习到了数据的特征,并优化了权重,使得预测结果更加准确。

模型评估

训练完成后,我们可以使用模型对新数据进行预测,并评估其性能。

# 测试模型
with torch.no_grad():  # 关闭梯度计算
    test_data = torch.tensor([[1.0, 0.0], [0.0, 0.0]])
    predictions = model(test_data)
    print("Test Data:", test_data)
    print("Predictions:", predictions)

测试模型时,输入两个测试数据点:[1.0, 0.0][0.0, 0.0],模型给出的预测结果如下:

  • 对于输入 [1.0, 0.0],预测值为 0.9898,接近于1,表示模型认为该输入属于标签为1的类别。
  • 对于输入 [0.0, 0.0],预测值为 0.0169,接近于0,表示模型认为该输入属于标签为0的类别。

在这里插入图片描述

通过这些测试结果可以看出,模型能够对训练数据进行合理预测,说明模型在训练后能够拟合训练数据,出于数据集的问题,暂时不能有新数据来证明其泛化能力。

探索性分析:对数据集的调整

我们可以干一件有意思的事情,对原始数据集进行修改,去掉一个数据点,看看其对整体性能的影响如何,不妨去掉[1.0, 0.0]

# 准备训练数据
data = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 1.0]])
labels = torch.tensor([[0.0], [1.0], [0.0]])

# 查看数据和标签
print("Data:", data)
print("Labels:", labels)

其他因素均保持不变,我们看看效果如何:

在这里插入图片描述
在这里插入图片描述
训练日志显示,模型的损失函数值在1000个epoch内从0.0097下降到了0.0027,表现出稳定的训练过程。然而,由于数据集中移除了一个关键数据点,模型的表现能力有所下降。

在测试阶段,我们使用原本被移除的数据点 [1.0, 0.0] 以及其他数据进行评估,结果如下:

对于输入 [1.0, 0.0],预测值为 0.9289,虽然接近1,但与先前的模型相比稍微下降,表明模型的准确性受到了数据移除的影响。
对于输入 [0.0, 0.0],预测值为 0.0034,仍然接近于0,显示出模型在此数据点上的良好表现。

通过这次实验可以看到,尽管模型在训练集上的损失值继续降低,但在面对未见过的数据点时,预测结果有所偏差,说明模型的表现能力受到了影响,但是这个在新数据中的表现,往往能正确反应模型的泛化能力。这也提示我们,数据的完整性在模型训练中的重要性。

探索性的调整:增加隐藏层与神经元数量

在完成了基础网络的训练后,我们可以进行一些探索性的调整,看看这些调整对模型性能的影响。我们可以尝试:

  1. 增加隐藏层:在模型中引入更多的隐藏层。
  2. 改变神经元数量:在每一层中增加或减少神经元的数量。
增加隐藏层

我们可以增加一个新的隐藏层,看其对模型的影响:

class DeeperNN(nn.Module):
    def __init__(self):
        super(DeeperNN, self).__init__()
        # 第一层,全连接层,输入2个特征,输出10个神经元
        self.fc1 = nn.Linear(2, 10)
        # 第二层,新增的隐藏层,10个神经元输入,10个神经元输出
        self.fc2 = nn.Linear(10, 10)
        # 输出层,全连接层,10个神经元输入,1个输出
        self.fc3 = nn.Linear(10, 1)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))  # 新增隐藏层的激活函数
        x = torch.sigmoid(self.fc3(x))
        return x

训练这个更深的模型,你可能会发现:

  • 准确性提升:更多的隐藏层能够使模型捕获到更复杂的模式,从而提升预测准确性。
  • 训练时间增加:引入更多的层意味着更多的计算量,训练时间相应增加。
改变神经元数量

我们也可以通过调整每层中的神经元数量来优化模型的性能。例如,将隐藏层的神经元数量从10增加到50:

class WiderNN(nn

.Module):
    def __init__(self):
        super(WiderNN, self).__init__()
        self.fc1 = nn.Linear(2, 50)  # 增加隐藏层的神经元数量
        self.fc2 = nn.Linear(50, 1)

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

这种调整的效果可能包括:

  • 准确性提升:更多的神经元可以让模型拥有更大的表示能力。
  • 过拟合风险:如果神经元数量过多,模型可能会过拟合训练数据,对新数据的泛化能力下降。
  • 训练时间增加:更多的神经元需要更多的计算资源,训练时间会相应增加。

结果对比与总结

通过增加隐藏层和调整神经元数量,我们可以探索不同网络结构对模型性能的影响。

  • 增加隐藏层可以提升模型对复杂模式的捕获能力,但也增加了训练时间。
  • 增加每层的神经元数量可以提高模型的表示能力,但也可能导致过拟合,尤其是在数据量较小的情况下。

在实际应用中,选择网络结构时需要权衡模型的准确性和训练时间,并注意避免过拟合。通过这些探索,你将对神经网络的设计有更深的理解,并能更有效地应用PyTorch构建复杂的深度学习模型。

小结

在这篇文章中,我们构建了一个简单的神经网络,并通过实验探索了数据集的改变,增加隐藏层和调整神经元数量对模型性能的影响。希望这些探索能帮助你更好地理解神经网络结构设计的要点,为今后的深度学习实践打下坚实基础。

下一篇文章中,我们将深入探讨如何使用更高级的PyTorch功能来提升模型性能。敬请期待!

课后练习

为了巩固今天的学习内容,建议你尝试以下练习:

  1. 尝试不同数量的隐藏层,并观察对模型准确性和训练时间的影响。
  2. 尝试不同的数据集,调整每层的神经元数量,观察模型是否过拟合,以及如何调整模型以避免过拟合

如果在练习过程中遇到问题,欢迎随时留言讨论。希望你能继续享受PyTorch学习的过程!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值