让我们将本章的理念付诸实践,通过搭建一个简单的神经网络。我们将构建一个小型的前馈网络,专为二分类任务而设计。假设我们有包含两个特征的输入数据,并想将每个数据点归入两个类别(0或1)中的一个。任何PyTorch模型的基础都是torch.nn.Module类。通过继承nn.Module可以创建自定义网络,并在__init__方法中定义层,在forward方法中定义数据流。定义网络架构我们将创建一个具有以下特点的网络:一个接受2个特征的输入层。一个包含10个神经元和ReLU激活函数的隐藏层。一个包含1个神经元的输出层,生成适合二分类的单个logit值。以下是定义此架构的Python代码:import torch import torch.nn as nn import torch.optim as optim # 定义网络结构 class SimpleNet(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleNet, self).__init__() # 初始化父类 self.layer_1 = nn.Linear(input_size, hidden_size) self.relu = nn.ReLU() self.layer_2 = nn.Linear(hidden_size, output_size) def forward(self, x): # 定义前向传播 out = self.layer_1(x) out = self.relu(out) out = self.layer_2(out) # 注意:如果后续使用BCEWithLogitsLoss,这里不应用Sigmoid return out # 定义网络参数 input_features = 2 hidden_units = 10 output_classes = 1 # 二分类logit的单个输出 # 实例化网络 model = SimpleNet(input_features, hidden_units, output_classes) # 打印模型结构 print(model)运行此代码将打印我们新定义网络的结构,显示层及其顺序:SimpleNet( (layer_1): Linear(in_features=2, out_features=10, bias=True) (relu): ReLU() (layer_2): Linear(in_features=10, out_features=1, bias=True) )此输出证实我们有一个线性层,将2个输入特征映射到10个隐藏单元,接着是ReLU激活,最后是另一个线性层,将10个隐藏单元映射到单个输出值。准备训练组件在我们开始训练之前(训练的详细内容将在后面介绍),我们需要实例化模型,定义损失函数,并选择一个优化器。让我们来设置这些。# --- 数据准备(示例占位符)--- # 假设我们有一些输入数据(X)和目标标签(y) # 对于此示例,我们创建一些虚拟张量 # 包含5个样本的迷你批次,每个样本有2个特征 dummy_input = torch.randn(5, input_features) # 对应的虚拟标签(0或1)- BCEWithLogitsLoss需要浮点类型 dummy_labels = torch.randint(0, 2, (5, 1)).float() # --- 实例化模型、损失和优化器 --- # 模型已在上面实例化:model = SimpleNet(...) # 损失函数:带Logits的二元交叉熵 # 此损失函数适用于二分类,并期望接收原始logits作为输入 criterion = nn.BCEWithLogitsLoss() # 优化器:Adam是一个常用的选择 # 我们将模型的参数传递给优化器 learning_rate = 0.01 optimizer = optim.Adam(model.parameters(), lr=learning_rate) print(f"\n使用的损失函数: {criterion}") print(f"使用的优化器: {optimizer}")模拟前向和反向传播现在我们已经准备好模型、损失函数和优化器,让我们模拟训练过程中的单个步骤,来查看这些组件如何协同工作。这包括:将输入数据通过模型(前向传播)。计算模型输出与真实标签之间的损失。使用反向传播计算梯度。使用优化器更新模型的权重。# --- 模拟单个训练步骤 --- # 1. 前向传播:获取模型预测(logits) outputs = model(dummy_input) print(f"\n模型输出(logits)形状:{outputs.shape}") # print(f"Sample outputs: {outputs.detach().numpy().flatten()}") # 可选:查看输出 # 2. 计算损失 loss = criterion(outputs, dummy_labels) print(f"计算的损失:{loss.item():.4f}") # .item()获取标量值 # 3. 反向传播:计算梯度 # 首先,确保梯度已从上一步归零(在实际循环中很重要) optimizer.zero_grad() loss.backward() # 计算损失相对于模型参数的梯度 # 4. 优化器步骤:更新模型权重 optimizer.step() # 根据计算出的梯度更新参数 # --- 检查参数(可选)--- # 您可以在反向传播后(在optimizer.step()之前)检查梯度 # print("\nlayer_1权重的梯度(示例):") # print(model.layer_1.weight.grad[0, :]) # 访问特定参数的梯度 # 或者在步骤后检查参数值 # print("\n更新后的layer_1权重(示例):") # print(model.layer_1.weight[0, :])在此步骤中,我们执行了前向传播,从我们的SimpleNet获取原始输出(logits)。然后我们使用BCEWithLogitsLoss计算这些输出和我们的dummy_labels之间的差异。调用loss.backward()触发了Autograd计算所有requires_grad=True参数的梯度(默认包括我们nn.Linear层的权重和偏置)。最后,optimizer.step()使用计算出的梯度和Adam优化算法更新了模型的参数。请记住在实际训练循环中的下一次反向传播之前调用optimizer.zero_grad(),以防止梯度累积。您现在已成功使用torch.nn构建了一个简单的神经网络,定义了其层和前向传播,实例化了它,并将其与损失函数和优化器连接起来,为下一阶段:训练做好了准备。