news 2026/5/10 6:24:59

【Scala PyTorch深度学习】PyTorch On Scala 系列课程 第四章 08 :神经网络【AI Infra 3.0】[PyTorch Scala 硕士研一课程]

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Scala PyTorch深度学习】PyTorch On Scala 系列课程 第四章 08 :神经网络【AI Infra 3.0】[PyTorch Scala 硕士研一课程]

PyTorch Scala 高校计算机 硕士研一课程

章节 4: 使用torch.nn搭建模型

在熟悉了PyTorch张量和用于梯度计算的Autograd系统后,我们现在开始构建神经网络本身。本章主要介绍torch.nn包,它是PyTorch用于高效构建网络结构的专用库。

你将学习如何使用核心nn.Module类作为模型的设计蓝图。我们将使用PyTorch提供的常用构建块来组装网络,包括线性层(nn.Linear)、卷积层(nn.Conv2d)和循环层(nn.RNN)。我们还将整合像激活函数(例如ReLU、Sigmoid)这样的重要组成部分,以引入非线性处理能力。此外,你将学习如何使用torch.nn中的损失函数(如MSELossMSELoss或CrossEntropyLossCrossEntropyL**oss)来定义目标衡量标准,以及如何从torch.optim中选择合适的优化算法(如SGD或Adam)在训练期间迭代地优化模型参数。在本章结束时,你将能够在PyTorch中定义并实例化自己的基本神经网络。

torch.nn.Module基类

收藏

构建神经网络在PyTorch中围绕着一个主要的理念:torch.nn.Module。可以将nn.Module视为一个基础蓝图或基类,所有的神经网络模型、层,甚至复杂的复合结构都是以此为基础构建的。它提供了一种标准化的方式来封装模型参数、管理这些参数的辅助函数(例如在CPU和GPU之间移动它们),以及定义输入数据在网络中流动的逻辑。

无论何时,当你在PyTorch中定义自定义神经网络时,通常会通过创建一个继承自nn.Module的Python类来完成。这种继承为你的自定义类提供了大量内置功能,这些功能对于深度学习工作流程来说非常必要。

nn.Module的核心结构

本质上,使用nn.Module需要在你的自定义类中实现两个主要方法:

  1. __init__(self):构造函数。你在这里定义和初始化网络的组件,例如层(卷积层、线性层等)、激活函数,甚至其他nn.Module实例(子模块)。这些组件通常被作为类实例(self)的属性进行赋值。
  2. forward(self, input_data):此方法定义了网络的前向传播。它规定了输入数据(input_data)如何流经在__init__中定义的层和组件。forward方法接收一个或多个输入张量,并返回一个或多个输出张量。PyTorch的Autograd系统会根据此forward方法中执行的操作自动构建计算图,从而实现自动微分。

以下是一个自定义模块的骨架:

importtorchimporttorch.nnas nnimporttorch.nn.functionalas Fimporttorch.nnclassMySimpleNetwork[D<:]extendsnn.Module:def__init__(self):super(MySimpleNetwork,self).__init__()# 在 __init__ 中定义层或组件 # 示例:一个线性层self.layer1=nn.Linear(in_features=10,out_features=5)# 示例:一个激活函数实例self.activation=nn.ReLU()defforward(x:Tensor[D]):# 定义数据流经组件的方式 x=self.layer1(x)x=self.activation(x)returnx// 实例化网络valmodel=MySimpleNetwork()println(model)

执行此代码将打印出网络结构的表示,展示nn.Module如何帮助组织你的组件。

参数和子模块

nn.Module的一个重要特点是其自动注册和管理可学习参数的能力。当你在__init__方法中将一个PyTorch层(如nn.Linearnn.Conv2d等)的实例作为属性赋值时,nn.Module会识别该层的内部参数(权重和偏置)。

这些参数是torch.nn.Parameter类的实例,它是torch.Tensor的一个特殊子类。主要区别在于Parameter对象默认自动设置requires_grad=True,并且它们会注册到父nn.Module中。这种注册使得PyTorch可以轻松收集模型的所有可学习参数,这对于在训练期间将它们传递给优化器来说非常重要。

你也可以直接使用nn.Parameter定义你自己的自定义可学习参数:

classCustomModuleWithParameterextendsnn.Module:def__init__(self):super().__init__()# 一个可学习的参数张量valmy_weight=nn.Parameter(torch.randn(5,2))// 一个普通的张量属性(不会自动跟踪用于优化)valmy_info=torch.tensor([1.0,2.0])defforward(x:Tensor[D]):// 示例用法returntorch.matmul(x,my_weight)valmodule=CustomModuleWithParameter()# 访问模块跟踪的参数forname,param<-module.named_parameters():println(f"Parameter name: {name}, Shape: {param.shape}, Requires grad: {param.requires_grad}")

注意my_weight被列为参数,而my_info则没有。这种自动跟踪简化了管理深度网络中可能数千或数百万参数的过程。

nn.Module的主要功能

除了定义结构和管理参数之外,nn.Module还提供了一些有用的方法,可供你的自定义类继承:

  • parameters(): 返回模块内(包括子模块中的)所有nn.Parameter对象的迭代器。这通常用于将模型的参数提供给优化器。
  • named_parameters(): 类似于parameters(),但会生成(参数名,参数对象)的元组。这有助于检查或有选择地修改特定参数。
  • children(): 返回直接子模块(定义为属性的子模块)的迭代器。
  • modules(): 返回网络中所有模块的迭代器,从模块自身开始,然后递归遍历所有子模块。
  • state_dict(): 返回一个Python字典,该字典包含模块的完整状态,主要将每个参数和缓冲区名称映射到其对应的张量。这对于保存模型权重非常重要。
  • load_state_dict(): 将状态(通常来自保存的文件)加载回模块,恢复参数和缓冲区。
  • to(device): 将模块的所有参数和缓冲区移动到指定设备(例如,GPU的'cuda'或CPU的'cpu')。这对于硬件加速非常重要。
  • train(): 将模块及其子模块设置为训练模式。这会影响像Dropout和BatchNorm这样的层,它们在训练和评估期间表现不同。
  • eval(): 将模块及其子模块设置为评估模式。

理解nn.Module非常重要,因为它建立了在PyTorch中定义任何神经网络架构的标准模式。在接下来的章节中,我们将使用这个基类来构建包含各种层、激活函数和损失函数的网络。

定义自定义网络架构

许多网络架构需要比简单的线性层堆叠更复杂的设计。尽管torch.nn.Sequential对于线性模型很方便,但更复杂的设计常常是必需的。你可能需要跳跃连接(如在 ResNet 中)、多输入/输出路径,或以非顺序方式使用的层。在这种情况下,通过继承torch.nn.Module来定义你自己的自定义网络架构就变得必不可少。这种方法在指定数据如何通过模型流动方面提供了最大的灵活性。

这个基本过程包含两个主要步骤:

  1. 在构造函数 (__init__) 中定义层:创建一个继承自torch.nn.Module的 Python 类。在其__init__方法中,你必须首先调用父类的构造函数 (super().__init__())。然后,实例化网络所需的所有层(例如nn.Linearnn.Conv2dnn.ReLU等),并将它们作为类实例的属性(使用self)进行分配。这些层就成为你的自定义模块的子模块。
  2. forward方法中定义数据流:为你的类实现forward方法。此方法将输入张量作为参数,并定义输入数据如何通过你在__init__中定义的层进行传播。此方法的输出是你的网络对于给定输入的最终输出。PyTorch 的 Autograd 系统会根据此forward方法中执行的操作自动构建计算图。

我们从一个基本例子开始:一个实现为自定义模块的简单线性回归模型。

importtorchimporttorch.nnas nnclassSimpleLinearModelextendsnn.Module:def__init__(self,input_features:Int,output_features:Int):// 调用父类构造函数super().__init__()// 定义单个线性层vallinear_layer=nn.Linear(input_features,output_features)// 打印初始化信息println(f"已初始化 SimpleLinearModel,输入特征数={input_features},输出特征数={output_features}")println(f"已定义层: {linear_layer}")defforward(x:Tensor[D]):// 定义前向传播:将输入通过线性层println(f"前向传播输入形状: {x.shape}")valoutput=linear_layer(x)println(f"前向传播输出形状: {output.shape}")returnoutput// --- 使用示例 ---// 定义输入和输出维度valin_dim=10valout_dim=1// 实例化自定义模型valmodel=SimpleLinearModel(in_dim,out_dim)// 创建一些模拟输入数据(batch_size=5,特征数=10)valdummy_input=torch.randn(5,in_dim)println(f"\n模拟输入张量形状: {dummy_input.shape}")// 将数据通过模型valoutput=model(dummy_input)println(f"模型输出张量形状: {output.shape}")// 检查参数(自动注册)println("\n模型参数:")for(name,param)<-model.named_parameters():ifparam.requires_grad then println(s" 名称: {name}, 形状: {param.shape}")

在此示例中:

  • SimpleLinearModel继承自nn.Module
  • __init__调用super().__init__()并定义self.linear_layer = nn.Linear(...)。此层现在是一个已注册的子模块。
  • forward(self, x)接收输入x并将其通过self.linear_layer,然后返回结果。

PyTorch 会自动追踪nn.Linear层的参数(权重和偏置),因为它们被作为属性分配在nn.Module子类中。我们可以通过查看model.parameters()model.named_parameters()来验证这一点。

构建多层感知机 (MLP)

现在,我们来构建一个稍微复杂一点的模型,一个两层 MLP,在层之间带有一个 ReLU 激活函数。

importtorchimporttorch.nnas nnimporttorch.nn.functionalas F//通常用于函数式 API,如激活函数classSimpleMLPextendsnn.Module:def__init__(input_size:Int,hidden_size:Int,output_size:Int):super().__init__()# 定义层vallayer1=nn.Linear(input_size,hidden_size)valactivation=nn.ReLU()// 将激活函数定义为层vallayer2=nn.Linear(hidden_size,output_size)println(f"已初始化 SimpleMLP: 输入={input_size}, 隐藏层={hidden_size}, 输出={output_size}")println(f"层 1: {layer1}")println(f"激活函数: {activation}")println(f"层 2: {layer2}")defforward(x:Tensor[D]):// 定义前向传播序列println(f"前向传播输入形状: {x.shape}")valx=layer1(x)println(f"经过层 1 后的形状: {x.shape}")valx=activation(x)// 应用 ReLU 激活println(f"经过激活函数后的形状: {x.shape}")valx=layer2(x)println(f"经过层 2(输出)后的形状: {x.shape}")returnx// --- 使用示例 ---// 定义维度valin_size=784// 示例:展平的 28x28 图像valhidden_units=128valout_size=10// 示例:用于分类的 10 个类别// 实例化 MLPvalmlp_model=SimpleMLP(input_size=in_size,hidden_size=hidden_units,output_size=out_size)// 创建模拟输入(批大小=32)valdummy_mlp_input=torch.randn(32,in_size)println(f"\n模拟 MLP 输入形状: {dummy_mlp_input.shape}")// 前向传播valmlp_output=mlp_model(dummy_mlp_input)println(f"MLP 输出形状: {mlp_output.shape}")// 检查参数println("\nMLP 模型参数:")for(name,param)<-mlp_model.named_parameters():ifparam.requires_grad then println(f" 名称: {name}, 形状: {param.shape}")

这里,forward方法明确规定了序列:输入 ->layer1->activation->layer2-> 输出。请注意,nn.ReLU等激活函数通常也在__init__中定义为层,并在forward中调用。另外,你也可以直接在forward方法中使用其函数式等效项(例如,导入torch.nn.functional as F后使用F.relu(x)),特别是对于没有可学习参数的激活函数。

可视化 MLP 结构

我们可以可视化SimpleMLPforward方法中定义的数据流。

输入 (x)层1 (线性)激活函数 (ReLU)层2 (线性)输出

数据流经SimpleMLP模型,如其forward方法中所定义。

继承nn.Module的优点

  • 灵活性:这是主要优势。你可以实现任何架构,包括具有多个输入/输出、残差连接(其中输入被加回到后续层的输出)、共享层或forward传递中自定义操作的架构。nn.Sequential仅限于严格的线性层序列。
  • 可读性和组织性:复杂的架构通常在类结构中组织时更容易理解,其中层在__init__中定义,它们的交互方式在forward中定义。
  • 参数管理:PyTorch 会自动发现并注册在__init__方法中作为属性分配的任何nn.Module(例如self.layer1 = nn.Linear(...))。这意味着model.parameters()将正确地给出所有子模块的所有可学习参数(权重、偏置),使其可以轻松传递给优化器。
  • 嵌套:自定义模块可以包含其他模块(包括nn.Sequential或其他自定义模块),从而允许你构建分层和可重用的组件。

通过继承nn.Module,你可以完全控制网络的结构,从而实现针对特定任务的复杂深度学习模型。这种方法是构建更复杂的前馈网络的标准做法。

常见层:线性、卷积、循环

PyTorch 提供神经网络模型的基本构成单元,即层。torch.nn包提供了多种预构建层,它们执行神经网络中常见的操作。这些层将可学习参数(权重和偏置)和操作本身都包含在内。这里将介绍三种主要类型:线性层、卷积层和循环层。

线性层 (nn.Linear)

神经网络层最基本的类型是线性层,也称为全连接层或密集层。它对输入数据进行线性变换。如果输入张量的形状为 (∗,Hin)(∗,H**in),其中 ∗∗ 代表任意数量的前导维度(如批大小),HinH**in是输入特征的数量,那么nn.Linear层会将其转换为形状为 (∗,Hout)(∗,Hou**t) 的输出张量,其中 HoutHou**t是为该层指定的输出特征数量。

在数学上,此操作表示为 y=xWT+by=xWT+b,其中:

  • xx是输入
  • WW是权重矩阵
  • bb是偏置向量
  • yy是输出

您可以通过指定输入特征和输出特征的数量来创建线性层。

importtorchimporttorch.nnas nn// 示例:创建一个线性层,输入特征大小为 20,输出特征大小为 30vallinear_layer=nn.Linear(in_features=20,out_features=30)// 创建一个示例输入张量(批大小 64,20 个特征)valinput_tensor=torch.randn(64,20)// 将输入通过该层valoutput_tensor=linear_layer(input_tensor)println(f"Input shape: {input_tensor.shape}")println(f"Output shape: {output_tensor.shape}")// 预期输出:// Input shape: torch.Size([64, 20])// Output shape: torch.Size([64, 30])// 检查层的参数(自动创建)println(f"\nWeight shape: {linear_layer.weight.shape}")println(f"Bias shape: {linear_layer.bias.shape}")// 预期输出:// Weight shape: torch.Size([30, 20])// Bias shape: torch.Size([30])

线性层是许多架构中的基本组成部分,包括简单的多层感知机(MLP),并且在像 CNN 和 RNN 这样更复杂的模型中,它们通常作为最终的分类或回归头部。

卷积层 (nn.Conv2d)

卷积层是现代计算机视觉模型的核心。与对扁平特征向量进行操作的线性层不同,卷积层设计用于处理网格状数据(如图像),并保留空间关系。用于二维数据(如图像)的主要层是nn.Conv2d

它的工作原理是通过在输入空间维度(高和宽)上滑动小型滤波器(卷积核)。对于滤波器的每个位置,它计算滤波器权重与滤波器下输入图像块的点积,从而在输出特征图中生成一个元素。这个过程有助于检测边缘、角点和纹理等空间模式。

nn.Conv2d的主要参数包括:

  • in_channels:输入图像中的通道数量(例如,RGB 图像为 3)。
  • out_channels:要应用的滤波器数量。每个滤波器生成一个输出通道(特征图)。
  • kernel_size:滤波器的大小(高,宽)。可以是单个整数用于方形卷积核,或一个元组(H, W)
  • stride(可选,默认 1):滤波器在每一步移动的像素数。
  • padding(可选,默认 0):添加到输入边界的零填充量。
// 示例:处理一批 16 张图像,3 通道(RGB),32x32 像素// 应用 6 个滤波器(输出通道),每个大小为 5x5valconv_layer=nn.Conv2d(in_channels=3,out_channels=6,kernel_size=5)// 创建一个示例输入张量(批大小,通道,高,宽)// PyTorch 通常期望通道优先的格式 (N, C, H, W)valinput_image_batch=torch.randn(16,3,32,32)// 将输入通过卷积层valoutput_feature_maps=conv_layer(input_image_batch)println(f"Input shape: {input_image_batch.shape}")println(f"Output shape: {output_feature_maps.shape}")// 没有填充/步幅时,输出大小会减小:32 - 5 + 1 = 28// 预期输出:// Input shape: torch.Size([16, 3, 32, 32])// Output shape: torch.Size([16, 6, 28, 28])// 检查参数println(f"\nWeight (filter) shape: {conv_layer.weight.shape}")// (输出通道,输入通道,卷积核高,卷积核宽)println(f"Bias shape: {conv_layer.bias.shape}")// (输出通道)// 预期输出:// Weight (filter) shape: torch.Size([6, 3, 5, 5])// Bias shape: torch.Size([6])

nn.Conv2d及其变体(nn.Conv1dnn.Conv3d)对涉及空间层次的任务是不可或缺的,主要用于图像和视频分析,但有时也应用于序列数据。我们将在第 7 章更详细地了解如何构建 CNN。

循环层 (nn.RNN)

循环神经网络(RNN)设计用于处理序列数据,其中元素的顺序很重要。示例包括文本、时间序列数据或音频信号。RNN 层的核心思想是维护一个隐藏状态,该状态捕捉序列中先前元素的信息,并影响当前元素的处理。

PyTorch 中基本的nn.RNN层逐步处理输入序列。在每一步 tt,它接收输入 xtx**t和前一个隐藏状态 ht−1h**t−1,以计算输出 oto**t(可选,通常只使用最终隐藏状态)和新的隐藏状态 hth**t

nn.RNN的主要参数:

  • input_size:每个时间步输入中的特征数量。
  • hidden_size:隐藏状态中的特征数量。
  • num_layers(可选,默认 1):堆叠 RNN 层的数量。
  • batch_first(可选,默认 False):如果为 True,输入和输出张量将以(batch, seq_len, features)形式提供,而不是默认的(seq_len, batch, features)
// 示例:处理一批 10 个序列,每个序列长 20 步,每步有 5 个特征。// 使用大小为 30 的隐藏状态。// 设置 batch_first=True 以便更方便地处理数据。valrnn_layer=nn.RNN(input_size=5,hidden_size=30,batch_first=true)// 创建一个示例输入张量(批,序列长度,输入特征)valinput_sequence_batch=torch.randn(10,20,5)// 初始化隐藏状态(层数,批,隐藏状态大小)// 如果未提供,默认为零。valinitial_hidden_state=torch.randn(1,10,30)// 层数=1// 将输入序列和初始隐藏状态通过 RNN// 输出包含所有时间步的输出// final_hidden_state 包含最后一个时间步的隐藏状态val(output_sequence,final_hidden_state)=rnn_layer(input_sequence_batch,initial_hidden_state)println(f"Input shape: {input_sequence_batch.shape}")println(f"Initial hidden state shape: {initial_hidden_state.shape}")println(f"Output sequence shape: {output_sequence.shape}")// (批,序列长度,隐藏状态大小)println(f"Final hidden state shape: {final_hidden_state.shape}")// (层数,批,隐藏状态大小)// 预期输出:// Input shape: torch.Size([10, 20, 5])// Initial hidden state shape: torch.Size([1, 10, 30])println(f"Output sequence shape: {output_sequence.shape}")// (批,序列长度,隐藏状态大小)println(f"Final hidden state shape: {final_hidden_state.shape}")// (层数,批,隐藏状态大小)// 预期输出:// Output sequence shape: torch.Size([10, 20, 30])// Final hidden state shape: torch.Size([1, 10, 30])

虽然nn.RNN展示了基本思想,但简单的 RNN 通常因梯度消失而难以处理长序列。在实际应用中,通常更偏好nn.LSTM(长短期记忆)和nn.GRU(门控循环单元)等更高级的循环层,因为它们包含门控机制,能更好地管理长距离依赖中的信息流。这些将在第 7 章再次提及。

这三种层类型(线性层、卷积层、循环层)代表了针对不同数据和任务的基本操作。torch.nn提供了这些层以及许多其他层(如池化层、归一化层、dropout 层),它们可以在nn.Module子类中组合起来,以构建复杂的深度学习模型。在接下来的部分中,我们将看到如何将这些层与非线性激活函数结合,并定义使用损失函数和优化器训练它们的标准。

激活函数 (ReLU, Sigmoid, Tanh)

收藏

神经网络的表示能力很大程度上得益于在层之间引入非线性。如果只是简单地堆叠线性变换(如nn.Linear层)而没有任何介入函数,整个网络将简化为一个单一的等效线性变换。无论网络有多少层,它都只能学习输入与输出之间的线性关系。

激活函数是引入这些重要非线性的组成部分。它们逐元素应用于层的输出(常被称为预激活值或logit),在将值传递给下一层之前对其进行转换。PyTorch 在torch.nn模块中提供了各种各样的激活函数,通常通过将它们实例化为层在模型定义中使用。我们来看看其中最常见的三种:ReLU、Sigmoid 和 Tanh。

ReLU (修正线性单元)

修正线性单元,简称ReLU,可以说是现代深度学习中最受欢迎的激活函数,尤其是在卷积神经网络中。它的定义非常简单:如果输入为正,它直接输出输入值,否则输出零。

其数学定义为:

ReLU(x)=max⁡(0,x)ReLU(x)=max(0,x)

在 PyTorch 中,可以使用nn.ReLU

importtorchimporttorch.nnas nn// 示例用法valrelu_activation=nn.ReLU()valinput_tensor=torch.randn(4)// 示例输入张量valoutput_tensor=relu_activation(input_tensor)println(f"输入: {input_tensor}")println(f"ReLU 输出: {output_tensor}")// 在简单模型中的示例classSimpleNetextendsnn.Module:def__init__(self):super().__init__()vallayer1=nn.Linear(10,20)valactivation=nn.ReLU()vallayer2=nn.Linear(20,5)defforward(x:torch.Tensor):x=layer1(x)x=activation(x)// 应用 ReLUx=layer2(x)returnx// 示例模型valmodel=SimpleNet()

ReLU 函数对负输入为零,对正输入为线性。

优点:

  • 计算效率高:计算非常简单(max⁡(0,x)max(0,x))。
  • 减少梯度消失:对于正输入,梯度为1,这有助于在训练期间梯度反向传播,相比于 Sigmoid 或 Tanh 等饱和函数。
  • 引入稀疏性:由于负输入被映射到零,这可以导致网络中出现稀疏激活,有时可能是有益的。

缺点:

  • ReLU 死亡问题:输入始终落在负区间的神经元将输出零。因此,流经它们的梯度也将为零,这意味着它们的权重在反向传播期间不会被更新。这些神经元实际上“死亡”了,不再对学习有贡献。Leaky ReLU 或 Parametric ReLU (PReLU) 等变体试图解决此问题。
  • 非零中心:输出始终为非负值。

Sigmoid

Sigmoid 函数,有时也称为逻辑函数,将其输入压缩到 0 到 1 的范围内。它在历史上很受欢迎,尤其是在二元分类模型的输出层,其中输出代表一个概率。

其数学形式为:

σ(x)=11+e−xσ(x)=1+ex1

在 PyTorch 中,可以使用nn.Sigmoid

importtorchimporttorch.nnas nn// 示例用法valsigmoid_activation=nn.Sigmoid()valinput_tensor=torch.randn(4)// 示例输入张量valoutput_tensor=sigmoid_activation(input_tensor)println(f"输入: {input_tensor}")println(f"Sigmoid 输出: {output_tensor}")

Sigmoid 函数将任意实数平滑地映射到 (0, 1) 的范围内。

优点:

  • 输出易于理解:(0, 1) 的范围便于表示概率。
  • 梯度平滑:函数处处可微,提供平滑的梯度。

缺点:

  • 梯度消失:对于非常大或非常小的输入,函数会饱和(输出接近 1 或 0),梯度变得非常接近零。这会严重减缓或停止深度网络的学习,因为梯度难以通过多层反向传播。
  • 非零中心:输出始终为正,这有时会减缓收敛速度,相比于零中心激活函数。
  • 计算成本更高:指数函数比 ReLU 的简单比较成本更高。

由于梯度消失问题,Sigmoid 在今天的深度网络隐藏层中不如 ReLU 常用,但它在特定任务(例如二元分类或多标签分类)的输出层中仍然适用。

Tanh (双曲正切)

双曲正切函数,即 Tanh 函数,在数学上与 Sigmoid 相关,但将其输入压缩到 (-1, 1) 的范围内。

其定义为:

tanh⁡(x)=ex−e−xex+e−x=2σ(2x)−1tanh(x)=e**x+exexex=2σ(2x)−1

在 PyTorch 中,可以使用nn.Tanh

importtorchimporttorch.nnas nn// 示例用法valtanh_activation=nn.Tanh()valinput_tensor=torch.randn(4)// 示例输入张量valoutput_tensor=tanh_activation(input_tensor)println(f"输入: {input_tensor}")println(f"Tanh 输出: {output_tensor}")

Tanh 函数将任意实数平滑地映射到 (-1, 1) 的范围内。

优点:

  • 零中心输出:与 Sigmoid 不同,Tanh 的输出以零为中心,这通常有助于模型在训练期间的收敛。零中心数据通常与基于梯度的优化方法配合得更好。
  • 梯度平滑:与 Sigmoid 类似,它处处可微。

缺点:

  • 梯度消失:与 Sigmoid 类似,Tanh 也会在很大正值或负值输入时出现饱和,导致深度网络中梯度消失。虽然由于其零中心性质,在隐藏层中它通常比 Sigmoid 更受青睐,但它仍然容易受到此问题的影响。
  • 计算成本更高:涉及指数函数,使其比 ReLU 成本更高。

在 ReLU 兴起之前,Tanh 在隐藏层中通常比 Sigmoid 更受青睐,主要因为其零中心输出范围。它仍然常见于循环神经网络 (RNN) 和 LSTM 中。

选择激活函数

没有一个“最佳”激活函数适用于所有情况。然而,有一些通用指导原则:

  • ReLU通常是前馈网络和 CNN 中隐藏层的默认选择,因为它高效且能有效缓解正输入时的梯度消失问题。从 ReLU 开始,如果遇到诸如死亡神经元之类的问题,再考虑其他替代方案。
  • 如果怀疑存在“ReLU 死亡”问题,Leaky ReLUParametric ReLU (PReLU)是不错的替代方案。它们为负输入引入了一个小的非零斜率。
  • Tanh在隐藏层中可能很有效,尤其是在 RNN 中,因为它有零中心输出。
  • Sigmoid通常保留用于输出层,当你需要用于二元或多标签分类的概率时。因为梯度消失问题,避免在深度隐藏层中大量使用它。

通常需要进行实验,以找到适用于特定架构和数据集的最佳激活函数。在 PyTorch 中,更换激活函数很简单,通常只需更改一行代码,即激活模块实例化或在nn.Moduleforward方法中被调用的位置。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 1:57:38

LLM工程化实践——大模型部署与推理框架vLLM

vLLM&#xff1a;大语言模型推理与服务库vLLM 是由加州大学伯克利分校天空计算实验室最初研发、现由学术界和工业界共同贡献的社区驱动型大语言模型推理与服务库&#xff0c;核心定位为简单、高速、低成本的 LLM 服务工具&#xff0c;其核心特性围绕极致的推理性能和高度的灵活…

作者头像 李华
网站建设 2026/4/17 23:54:31

MP4视频修复终极指南:5分钟快速拯救损坏视频文件的完整方案

MP4视频修复终极指南&#xff1a;5分钟快速拯救损坏视频文件的完整方案 【免费下载链接】untrunc Restore a truncated mp4/mov. Improved version of ponchio/untrunc 项目地址: https://gitcode.com/gh_mirrors/un/untrunc 你是否曾因为相机突然断电、存储卡故障或文件…

作者头像 李华
网站建设 2026/4/17 18:20:48

3大实战场景:dnSpyEx .NET逆向调试与编辑的完整指南

3大实战场景&#xff1a;dnSpyEx .NET逆向调试与编辑的完整指南 【免费下载链接】dnSpy Unofficial revival of the well known .NET debugger and assembly editor, dnSpy 项目地址: https://gitcode.com/gh_mirrors/dns/dnSpy dnSpyEx是一款功能强大的.NET逆向工具&am…

作者头像 李华
网站建设 2026/4/18 0:52:44

【Claude Code 源码解析教程】总体大纲

教程概述本教程旨在深入解析 Anthropic Claude Code 项目的源码架构和实现细节。Claude Code 是一个基于 TypeScript Bun 构建的 AI 编程助手 CLI 工具&#xff0c;代号 "Tengu"&#xff0c;具有复杂的多模块架构和丰富的功能特性。教程目标读者&#xff1a;对 AI 编…

作者头像 李华
网站建设 2026/4/17 18:45:18

如何在3分钟内让Mac通过USB数据线获得Android手机的高速网络连接

如何在3分钟内让Mac通过USB数据线获得Android手机的高速网络连接 【免费下载链接】HoRNDIS Android USB tethering driver for Mac OS X 项目地址: https://gitcode.com/gh_mirrors/ho/HoRNDIS 还在为Mac的网络连接不稳定而烦恼吗&#xff1f;想要获得比WiFi热点更快、更…

作者头像 李华