万字长文:深入理解各类型神经网络(简单神经网络,CNN,LSTM)的输入和输出

深度学习入门~神经网络的输出② 计算神经网络的输出 在Logistic回归中,通过特征量x1,x2和x3计算Z和α,如左图,只进行了以此计算,但如果把它放在神经网络中,则是进行多次计算。 ????神经网路的第一个结点值计算。它是神经网络中第一层的第一个结点,所以上标和小标都是1。 ????第二个结点也是一样的。 同理,第三个结点和第四个结点计算的也是相同的值(不是说它们计算的结果相同,而是它们四个要计算的值相同,都是Logistc回归的Z和α)。 显然,使用for循环实现这四行计算,效率是十分低下的,因此我们可以使用向量化对他们 阅读详情
AI摘要

本文深入浅出地探讨了简单神经网络、卷积神经网络(CNN)和循环神经网络(RNN,特别是LSTM)的结构、输入输出以及在处理图像和文本数据时的特征维度变化。对于神经网络,从标量输入到向量输入,再到考虑batch_size的影响,逐步解析了网络中权重参数和激活函数的作用。对于CNN,解释了如何将二维图像数据转化为适合网络处理的输入,并介绍了从原始图像特征到卷积层输出的维度变化。对于RNN,特别是LSTM,详细阐述了其输入输出的形状、隐藏状态和输出的获取方法,以及如何在文本分类任务中利用LSTM的输出。通过这些讲解,有助于读者更好地理解和应用深度学习模型。

目录

 

简单神经网络

当输入为标量

当输入为向量

考虑batch_size

总结以及易混淆的要点梳理

卷积神经网络

图片的在计算机中的表示

图片特征维度的转变

循环神经网络

使用LSTM的困惑

文本的表示

LSTM的输入和输出

输出的获取以及Pytorch实例


简单神经网络

当输入为标量

对于一个最简单的神经网络而言,它的一个神经元通常长这个样子:

假设我们有a1, a2, ... , an共n个输入,对于每个输入都给定一个权重w1, w2, ... , wn,再给定一个偏置b,将权重和输入相乘,加上偏置,就得到了一个神经元SUM,SUM的表达式为:SUM = w1 * x1 + w2 * x2 + ... + wn * xn + b,在这里由于各个项都是标量,得到的结果SUM也是一个标量。得到了SUM这个输出神经元后,我们通常会在后面加一个激活函数(上图中的f),增强其拟合数据的能力。通过相乘和相加的运算,并通过激活函数后,我们得到了最终的结果t(上图中最后的小圆圈)。

上图仅仅是一个神经元,在神经网络的不同层中,往往有不止一个神经元,所以在学习神经网络的过程中,我们往往会看到如下图所示的神经网络的示意图:

在这张图所表示的神经网络中,有一个输入层(input layer),三个隐层(hidden layer 1-3),最后是一个输出层(output layer),下面一一进行分析(在这步及其以后的分析中,我都会忽略偏置b,因为偏置对于形状的变化没有影响):

在input layer层,也就是输入层,我们先还是假设输入是简单的标量,即:a1, a2, ... , an,这时候我们看到input layer后的hidden layer 1层,就远远不止一个神经元了。第一步的分析中我们知道,每个输入都对应一组权重:w1, w2, ... , wn,有了这组权重之后,我们才能计算得到一个神经元的值,那么对于hidden layer 1中9个神经元的情况,我们自然也需要9组不同的权重,每组权重分别和输入计算得到一个神经元,每组权重都是独立的。分析完了hidden layer 1 之后,其余的两个隐层和最后的输出层也是同样的处理,比如要计算得到hidden layer 2,我们可以把hidden layer 1的每个神经元看成输入;要计算得到output layer,把hidden layer 3中的每个神经元看成输入。

补充一些小细节:

1.一般神经网络的示意图中,都是跟上图一样,只画一个圆圈表示神经元,但是不画激活函数,但其实激活函数已经包含在这个小圆圈里面了,便于理解,可以将神经元的这个小圆圈从中间切一刀,前半部分的状态是输入的各个量乘以权重,加上偏置得到的未经过激活函数之前的状态,然后把中间的这条竖线看成是激活函数,那么右半边的这个半圆就是代表通过激活函数之后的神经元了。

2.若神经网络要完成一个分类任务,输出层output layer通常使用softmax作为激活函数,想实现n分类,把输出层的神经元数量也设计为n即可,各个层的神经元的数量都是可以自己设计的。

当输入为向量

但是我们在用一些深度学习的框架完成一些任务的时候,往往会发现输入的形式往往不是一个数(标量),通常情况下都是一个多维的向量,这时候的情况要略微复杂一些,看下面这张神经网络的图:

在这张图中,只画了两层的网络:N-1层,N层,N-1层可以是输入层,也可以是任意的一个隐层,在前面中我们提到,隐层的神经元在计算得到下一层的网络时,可以看成输入。所以无论N-1层是输入层还是隐层,都可以看成输入层,图中N-1层有n个神经元(图中只标了8个,但是讨论中我们认为这一层的神经元数量是n,而第N层的神经元数量是k),那么我们可以认为对应n个标量的输入,观察这个式子,有了前面的讲解,这个式子应该非常容易理解:

这个式子的结果就是如图中箭头所示的神经元,下标的0是代表这个神经元在当前层中,它是第一个神经元,上标的(1)代表层的标号(N-1层中神经元的标号是0,那么N层就是1,以此类推),继续观察式子中的权重参数,w有两个下标,第一个下标都是0,代表它们都是第一组权重(也就是得到下一层第一个神经元的一组w),后面的下标是0-n,就是对应n个神经元的输入。那么对应的,要是我们想要计算得到第N层第二个神经元,那么这组w就应该为w_{1,0}w_{1,1} w_{1,2}...w_{1,n},这样的w共有n组,n取决于第N层到底有多少个神经元。

那么此时思考两个问题,1.怎么表示这个n组w?2.怎么表示权重w和输入的相乘呢?答案是矩阵相乘的形式:

首先为了避免混淆,再次说明下图中符号的含义:N-1层有n个输入神经元,第N层有k个神经元,也就是说:要通过运算,做一个形状变化,由N个神经元的层,变为k个神经元的层。

图中可以看到,我们把输入变为列向量的形式,形状为[n,1];把k组权重w变成了矩阵的形式,形状为[k,n]。由矩阵乘法的定义可知,前面矩阵的列,后面矩阵(列向量也是矩阵)的行相同,可以进行乘法运算,可以得到一个[k,1]的矩阵,恰好代表了第N层的k个神经元。最后仔细思考一下矩阵乘法的运算过程,这跟我们前面讨论的标量的情况是一模一样的。

 

考虑batch_size

以上,我们理解了标量的输入形式和向量的输入形式。但往往我们用数据的时候,会有一个维度叫做batch_size。换而言之,我们的输入往往不止一个数据,在把数据喂给神经网络时,通常情况下我们都有一个batch_size,也就是同时把一批数据作为输入。我们还是在上图矩阵相乘的图中考虑此时跟之前一个样本时候的差异。

1.之前一个样本的时候,输入是列向量的形式,此时样本数量变多,那么这个时候在列这个维度上,自然不然是1了,此时的输入应该是一个矩阵,其形状是[n,batch_size],即行不变,每一列代表一个样本的所有特征输入。

2.权重矩阵w不变,形状仍然是[k,n],样本量的变化并不会影响权重矩阵的形状。此时进行矩阵乘法发现,[k,n]*[n,batch_size]得到的结果为[k,batch_size],k代表计算得到结果的这一层的神经元数量。在过程中我们可以发现,无论通过几次矩阵运算,batch_size这个维度始终不会变化,因为权重矩阵都是针对一个样本的,样本间是“互相独立”的,但是权重矩阵是公用的。用深度学习框架设计网络结构时,也不会考虑batch_size这一维。

 

总结以及易混淆的要点梳理

以下是之前我在学习deep learning的一些困惑,可能看一遍会有所帮助:

1.上面几张神经网络的示意图,也包括我们在网上看到的所有神经网络的结构,都仅仅是“针对一个样本”的,也就是说,我们设计网络结构的时候,不会去考虑样本的数量,也就是说,不会去考虑batch_size这个维度,这一点相信在使用深度学习框架设计网络结构的时候,都会深有体会。

2.为什么要一次向网络中输入多个样本呢?因为这样才能发挥GPU并行计算的优势,GPU能够加速神经网络的收敛,其实现原理的底层就在于它能够加速张量(向量)的运算。GPU跟CPU相比,运算能力远远没有CPU那么强,但是GPU有很多小的运算核心,对于一些简单的运算任务,它能挥发“人多力量大”的优势,而CPU虽然运算能力很强,但是没有那么多的核心。就好比做极为简单的加减乘除数学题,100个小学生和一个博士都能做,但是两者数量存在差异,使得小学生做相同的题反而比博士快得多。恰巧神经网络能够充分发挥并行计算的优势(神经网络本质就是矩阵向量间的运算),所以GPU在深度学习中至关重要。

3.既然GPU能够对多个样本进行并行计算,为什么不一次性将所有样本输入呢?当数据量过大的时候,输入矩阵的维度也跟着膨胀,一次喂给神经网络的样本过多,会超过GPU的负载能力。因此batch_size的设计要合理,不能过大也不能过小。

4.考虑下batch_size这个维度,理一下神经网络的优化过程:对于一个样本,输入层的n个神经元就是其特征的维度,比如我们要进行房价的预测任务,房价高低有多个影响因素,比如地理位置,平方数等。也就是有多个特征,这些特征都需要转化成可以量化的数据进行表示。一个房价样本通过隐层到输出层,输入通过层与层之间一系列的权重得到了最终的输出,跟这个样本的目标值(比如该预测任务中,目标值就是房价)计算损失(分类和回归有不同的损失函数),最后反向传播,更新权重参数w,这就是一个完整的过程。当然一个样本肯定不能把w更新的很完美,那么所有数据中的样本都通过神经网络一遍,自然神经网络就能找出输入数据共性的规律,然后将这些规律反映在权重参数w中。还是那房价预测的例子直观感受下:房价高低必然是受地理位置,平方数等因素影响的,其中每个因素影响有大有小,将房价样本通过神经网络,假设就学习到了:房价=平方数*0.8+地理位置*0.2,0.8和0.2就是在大量样本中学习到的,也就是模型的参数,有了这些参数后,后续就能根据特征进行最终房价的预测。

5.各种优化算法(梯度下降BGD,随机梯度下降SGD等)是怎么体现的呢?在4中,我们说的是一个样本,但是实际中一次有batch_size个样本,事实上在进行反向传播更新参数的时候,我们通常也不是每次就根据一个样本来更新参数的,而是有着更多样化的选择。比如梯度下降算法(BGD):每次迭代都需要把所有样本都送入,这样的好处是每次迭代都顾及了全部的样本,做的是全局最优化,但是有可能达到局部最优。随机梯度下降(SGD):从样本中随机抽出一个,训练后按梯度更新一次,然后再抽取一个,再更新一次(看到一个样本就更新一次,类似4中的方法),优点在于在样本量极其大的情况下,可能不用训练完所有的样本就可以获得一个损失值在可接受范围之内的模型。缺点在于单个样本的训练可能会带来很多噪声,使得SGD并不是每次迭代都向着整体最优化方向,因此在刚开始训练时可能收敛得很快,但是训练一段时间后就会变得很慢。当然还有小批量梯度下降等:选取一个小批次的样本计算损失,然后进行参数更新,是大多数情况下一个较为常用,也是较为优秀的优化算法的选择。

6.怎么理解将数据按照batch_size分成好几个批次,还要设计各种SGD/BGD等优化算法,这两者不是矛盾了吗?首先,梯度下降的各种算法是在batch_size的基础上的,也就是说。即使你使用的是BGD,顾及所有样本进行梯度更新,这个所有样本也只是当前的batch_size的大小,并不是所有数据。当然如果batch_size比较小,直接兼顾所有样本用BGD也可以,但实际上我们用其他的优化算法用的比较多,很少用BGD。所以batch_size和各种优化算法并不矛盾。

 

卷积神经网络

图片的在计算机中的表示

我们花了很大的篇幅理清了普通神经网络的输入和输出,接下来考虑卷积神经网络CNN,有了上面的基础,卷积神经网络的理解是水到渠成的。

CNN的卷积、池化等一些概念就不再这里讨论了,网上相关的资料十分详细,这里主要是从输入输出和形状变化的角度,从宏观上理解CNN。

CNN通常用于CV领域,那么我们也以图片为例,分析CNN中输入和输出的形式,首先我们要先明白图片在计算机中是怎样表示的:图片一般用三维向量来表示(假定通道数在最后一维),即(H,W,C),意思为(高,宽,通道数),黑白图片的通道数只有1,其中每个像素点的取值为[0,255],彩色图片的通道数为(R,G,B),每个通道的每个像素点的取值为[0,255],三个通道的颜色相互叠加,形成了各种颜色。关于图片的像素表示,可以想象一下有一个矩阵,它的高和宽分别为H和W,这个矩阵中每个点的取值范围是[0,255],一个点代表一个像素,对于黑白图片,就只有一个这样的矩阵;而对于彩色图片,有三个这样的矩阵,分别表示三个通道(R,G,B),三层像素点叠加后形成彩色。

 

图片特征维度的转变

我们先不考虑CNN,如果我们把图片放到普通神经的网络中,该怎样进行分类呢?从上文的讨论中我们可以知道,除去样本数量batch_size这个维度,迄今为止样本的特征都是一维的列向量,比如一个样本有n个特征,那么输入为[n,batch_size],表示特征的只有n这个维度,无论特征数量有多少,它始终只是一个维度上的数量的拓展。而一张图片,即使不考虑样本数量这个维度,它也需要三个维度,即(H,W,C),事实上,全连接层也能实现图片的分类,只不过效果肯定不如CNN好,具体的做法是,我们需要将图片的三维特征“展平”,变成一维的特征,比如十分有名的Mnist数据集,每张图片的形状是[28,28,1],表示高和宽都为28,由于是黑白图片,通道数为1,我们将[28,28,1]展开,变成[28*28*1],这样暴力的将像素点铺平,就是其转变的方式。要是我们只用全连接层来实现Mnist数据集的分类,代码如下,会发现和普通神经网络是一模一样的,只是多了一步从三维变成一维的转变:

# 由于Mnist数据集本身比较简单,全连接层的效果也十分好,正确率在95%左右
class MnistNet(nn.Module):
    def __init__(self):
        super(MnistNet, self).__init__()
        self.fc1 = nn.Linear(28 * 28 * 1, 28)
        self.fc2 = nn.Linear(28, 10)

    def forward(self, x):
        x = x.view(-1, 28 * 28 * 1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        return F.log_softmax(x, dim=-1)

 

事实上,即使是CNN,在网络的最后几层,也一定是全连接层,也一定会进行“将多维的像素点映射到一维上”这个操作,比如在下面这个图中,在倒数第四层池化之后,最后三层都是全连接层,倒数第四层到倒数第三层的变化就是铺平像素点的操作。

CNN的拓扑结构图:图来自博客:https://www.zhihu.com/question/41949741

这下上图CNN的结果就更为清晰了,CNN的核心操作,是卷积和池化,而卷积和池化无非就是对输入的三维[H,W,C]进行变化,比如原始输入是[28,28,1],经过卷积后变成[28,28,6],池化后变成[14,14,6]。卷积和池化不改变原始的维度,保持图片原始三个维度的基础上,从图片中抽取特征,当然最后还是要变成一维,进行最后的工作,如图片分类。

 

循环神经网络

循环神经网络这一部分拿最常用的LSTM举例,其余的如传统RNN,GRU等都类似,只是内部机制不太相同,在输入输出的宏观层面是一样的。

过程中会拿pytorch的LSTM api结合起来分析,tensorflow等框架应该大体是类似的,理解了原理后应该也能很快入门。

本节内容有一部分参考知乎:https://zhuanlan.zhihu.com/p/79064602

 

使用LSTM的困惑

相比起传统神经网络和CNN,RNNs(LSTM、GRU)等网络的输入输出比较难理解,尤其是即使理解了LSTM的内部机制(输入门,遗忘门,输出门),到实际使用时还是会疑惑,比如pytorch中LSTM的官方API如下:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

torch.nn.LSTM(input_size,hidden_size,num_layers,batch_first,dropout,bidirectional)

  1. input_size:输入数据的形状,即embedding_dim,句子中一个词用embedding_dim长度的向量表示

  2. hidden_size:隐藏层神经元的数量,即每一层有多少个LSTM单元

  3. num_layer :即RNN的中LSTM单元的层数

  4. batch_first:默认值为False,输入的数据需要[seq_len,batch,feature],如果为True,则为[batch,seq_len,feature]

  5. dropout: dropout的比例,默认值为0。dropout是一种训练过程中让部分参数随机失活的一种方式,能够提高训练速度,同时能够解决过拟合的问题。这里是在LSTM的最后一层,对每个输出进行dropout

  6. bidirectional:是否使用双向LSTM,默认是False

实例化LSTM对象之后,不仅需要传入数据,还需要前一次的h_0(前一次的隐藏状态)和c_0(前一次memory)

即:lstm(input,(h_0,c_0))

LSTM的默认输入为input, (h_0, c_0)

  1. input:(seq_len, batch, input_size)--->batch_first=False

  2. h_0:(num_layers * num_directions, batch, hidden_size)

  3. c_0: (num_layers * num_directions, batch, hidden_size)

LSTM的默认输出为output, (h_n, c_n)

  1. output:(seq_len, batch, num_directions * hidden_size)--->batch_first=False

  2. h_n:(num_layers * num_directions, batch, hidden_size)

  3. c_n: (num_layers * num_directions, batch, hidden_size)

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

即使把输入输出的参数描述的很清楚,也经常会有以下困惑:

1.输入和输出为什么是这样的形状?

2.该怎么设计LSTM的输入?

3.该怎么用LSTM的输出,是用output、h_n,还是c_n,或者怎么拼接输出?

为了理清这些疑惑,先从文本的输入表示开始讲起。

 

文本的表示

对于一段文本,对它进行分词处理后,分成一个个词,在深度学习中,要想表示一个词,有两种形式:onehot encoding 和 word embedding,两者都是词的向量表示,不同的是前者是稀疏的向量,后者是稠密的向量。由于word embedding能够在一定程度上反映词和词之间的语义相似度,所以用途更为广泛。在这里定义一个分类任务,贯穿下面的分析过程:假设有一个IMDB情感分类数据集,每个样本都是一段评论文本,标签是文本的情感极性,共有10个分类,我们要做的是把这些文本通过LSTM进行情感分类,首先对该数据集进行如下的预处理:

1.在这个任务中,每段文本对应一条数据,数据的单位是一整段的文本,而不是单个句子或者句子中的每个词,因为分类的标签是建立在整段文本上的,这一点必须明确。

2.对每段文本进行分词处理,同时由于数据集中每段文本的长度不统一,需要指定一个固定长度max_len,使得所有的样本中分词得到的数量是相同的(比如每个样本的文本中都有50个词语),比max_len长的可以截去,比max_len短的可以进行补零操作。

3.对每个词进行word embedding处理,给定一个embedding dim,即词向量的维度,假定其值为300。假设在2中设定max_len的值是50,那么对于数据集的每个样本而言,它的形状被处理成[max_len, embedding_dim],即[50,300]。含义:每段文本有50个词,词用一个300维的向量表示。

 

LSTM的输入和输出

对数据进行了预处理之后,来观察LSTM的输入:

其中seq_len代表词的数量,这这里也就是50,batch是一次训练的样本数量,input_size就是词向量的维度,这里是300.

接下来分析其他的维度,在学习LSTM时,我们经常会看到这么一张图:

这张图很好的反映了LSTM单元的内部结构,但观察这张图,似乎很难和之前上文讲解的神经网络联系起来,这张图也一度让我懵圈了好久,实际上,从宏观上把握LSTM,它的结构长如下这样:

首先观察其中一个神经网络的“切片”,也就是下图所示的这个:

会发现这个单独的神经网络几乎和传统的神经网络一模一样,最下面绿色的是输入层,上面黄色的是隐层,最上面蓝色的是输出层,接下来就是重点:(为了方便叙述,我将样本中那一段文本简单的认为是一个句子,实际中一个样本可能不止一句话,同时讲解中会忽略细胞状态c,细胞状态跟隐藏状态h非常的像,而且一般常用的就是隐藏状态h和输出output)

在大图中的左侧可以看到,其中有一个时序:t,时序这个概念是LSTM的核心,在图中画出的4个神经网络的下方,也注明了T=1,2,3,4...这个时序实际上就是输入的seq_len,也就是表示句子中词的个数,LTSM的核心思想在于要“理解”前文的信息,句子中词的存在不是孤立的,那么该如何运用句子中上文的信息呢,答案是隐藏状态h_n是跟随着时间步来更新的,图中用红线连接4个神经网络来表示。也就是说,一开始输入的是h_0,随着时间步的推移,h_0这个参数会随着时间步更新。如果有n个时间步,那么最后的隐藏状态就是h_n,h_n蕴含了整句话的编码信息。

在LSTM中,有几点需要注意,关于隐层,图中只画了黄色的一层,但是实际上可能不止一层,层数由参数num_layers指定,num_layers中每层的神经元的数量由参数hidden_size指定,且因为这个参数只指定了一次,所以每个隐藏层,包括输出层的LSTM神经元都是相同的,都为hidden_size(这一点是我按照api的参数自己理解的,可能不太对,但是暂时可以这么理解)

我们继续来分析output和h_n的形状,先是output,其形状是:

output:(seq_len, batch, num_directions * hidden_size)

从图中可以看出,每个时间步都有一个输出,有seq_len个时间步,第一维自然是seq_len,batch是样本的数量,第三个维度较难理解,从图中我们可以看到,无论隐层有多少个,一个时间步上,输出都只有一个,因此输出和LSTM纵向的深度无关,稍微补充一点,LSTM横向的深度指的是seq_len,纵向的深度是隐层数量num_layers,一般num_layers不会设置的很大,seq_len一般还是挺大的。所以经常说LSTM是一个“长但是不深”的网络,它的深度的概念和CNN有点不同,一个是横向的深度,一个是纵向的深度。言归正传,第三维的hidden_size很好理解,是该层神经元的数量,而num_directions的含义是指它是不是双向的网络,双向的话,num_directions=2,单向的则为1。单向的情况下,output第三维就是hidden_size,双向的话就是2*hidden_size。双向情况下有一个较难理解的点,就是:双向LSTM中,output:按照正反计算的结果顺序在第2个维度(维度从0开始计数,其实也就是我们讨论的第三维)进行拼接,正向第一个拼接反向的最后一个输出。也就是说,比如seq_len=0,也就是第一个时间步,在正向的LSTM中,它对应了正向的第一个output,但是在反向的LSTM中,句子中的第一个词变成了最后一个词,自然就对应了反向的最后一个输出。理解了这个之后,output的形状就显而易见了。

分析h_n的形状,其形状是:

h_n:(num_layers * num_directions, batch, hidden_size)

明确一点,h_n是所有隐层(包括输出层!!!)的权重的集合,所以与output不同,它和纵向的深度有关,但是与横向的深度(时间步)无关,时间步越长,h_n随着时间步不断累积序列的信息,但是本身的形状不会变(有点绕,不理解的可以继续结合着图理解一下)。如果是双向的LSTM,相当于把句子倒过来,再喂给一个新的LSTM,h_n自然也和是否双向有关,所以第一维是num_layers * num_directions,二三维就很好理解了,分别是样本数量和每层LSTM的神经元。在双向LSTM的情况下,h_n的拼接和output不同,是按照得到的结果在第0个维度(维度从0开始计数,其实也就是我们讨论的第一维)进行拼接,正向第一个之后接着是反向第一个。这一点可以较为感性的去理解,输出output蕴含的是包括上文的编码信息,考虑第一个时间步的编码,正向的LSTM它能最先得到编码,而反向的LSTM中,这个词就是最后一个,得在最后一步才能得到全句的编码,而且output第一维是seq_len,正向第一个拼接反向的最后一个输出就合情合理了。而对于隐藏状态h_n,正向和反向的LSTM是同时进行编码的,一个从前往后,一个从后往前,而且它还没有seq_len的限制,不存在说需要拿到全局的编码才能拼接,这时候正向第一个之后接着是反向第一个就好了(感觉有点难讲清楚。。)

以上,输入输出总算是讲完了,细胞状态c_n就跟h_n基本是一样的,不过多赘述了。然后我们看下面这个画的十分好的图:

仔细看下,会发现有了知识储备后,这图十分好理解,蓝色方块是每个LSTM单元,横轴代表时间步,纵轴代表LSTM共有多少层,h0,c0经过时间步更新成h_n,c_n,每个时间步都对应一个output。

即:output就是最后一个layer上,序列中每个时刻(横向)状态h的集合(若为双向则按位置拼接,输出维度2*hidden_size),而hn实际上是每个layer最后一个状态(纵向)输出的拼接。(知乎:ymmy的回答)

 

输出的获取以及Pytorch实例

1.一个简单的使用实例(单向LSTM):

batch_size =10	# 数据量大小
seq_len = 20	# 每个句子有20个词
embedding_dim = 30	# 每个词用长度为30的向量表示
word_vocab = 100	# 词表的大小
hidden_size = 18	# 隐藏层神经元的数量
num_layer = 2		# LSTM单元的层数

#准备输入数据
input = torch.randint(low=0,high=100,size=(batch_size,seq_len))

#准备embedding
embedding  = torch.nn.Embedding(word_vocab,embedding_dim)
lstm = torch.nn.LSTM(embedding_dim,hidden_size,num_layer)

#进行embed操作
embed = embedding(input) #input:[10, 20]	embed:[10,20,30]

#转化数据为batch_first=False
embed = embed.permute(1,0,2) #[20,10,30]

#初始化状态, 如果不初始化,torch默认初始值为全0
h_0 = torch.rand(num_layer,batch_size,hidden_size)
c_0 = torch.rand(num_layer,batch_size,hidden_size)
output,(h_n,c_n) = lstm(embed,(h_0,c_0))
# 输出如下:
# ==========================================================================
In [122]: output.size()
Out[122]: torch.Size([20, 10, 18])

In [123]: h_n.size()
Out[123]: torch.Size([2, 10, 18])

In [124]: c_n.size()
Out[124]: torch.Size([2, 10, 18])

2.考虑一个问题,我们怎么在这么多的输出中获得我们想要的?如果我们是为了做文本分类任务,此时我们只要获得整个句子的编码即可,考虑最后一个时间步,它累积了上文所有的信息,所以我们取最后一个seq_len的output,再通过全连接层进行处理即可,对应到实际,只需要取output[-1,:,:],-1表示最后一个时间步。前面我们说过,h_n是所有隐层(包括输出层!!!)的权重的集合,最后一个隐层就是输出层,那么获取的方式也可以是:h_n[-1,:,:],即:最后一次的h_1应该和output的最后一个time step的输出是一样,output[-1,:,:]==h_n[-1,:,:]。

代码验证如下:

In [179]: a = output[-1,:,:]

In [180]: a.size()
Out[180]: torch.Size([10, 18])

In [183]: b = h_n[-1,:,:]

In [183]: b.size()
Out[183]: torch.Size([10, 18])
In [184]: a == b
Out[184]:
tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]],
       dtype=torch.uint8)

3.双向的LSTM的输出的获取,跟1稍稍不同。首先还是举个例子:

batch_size =10 #句子的数量
seq_len = 20  #每个句子的长度
embedding_dim = 30  #每个词语使用多长的向量表示
word_vocab = 100  #词典中词语的总数
hidden_size = 18  #隐层中lstm的个数
num_layer = 2  #多少个隐藏层

input = torch.randint(low=0,high=100,size=(batch_size,seq_len))
embedding  = torch.nn.Embedding(word_vocab,embedding_dim)
lstm = torch.nn.LSTM(embedding_dim,hidden_size,num_layer,bidirectional=True)

embed = embedding(input) #[10,20,30]

#转化数据为batch_first=False
embed = embed.permute(1,0,2) #[20,10,30]
h_0 = torch.rand(num_layer*2,batch_size,hidden_size)
c_0 = torch.rand(num_layer*2,batch_size,hidden_size)
output,(h_n,c_n) = lstm(embed,(h_0,c_0))

In [135]: output.size()
Out[135]: torch.Size([20, 10, 36])

In [136]: h_n.size()
Out[136]: torch.Size([4, 10, 18])

In [137]: c_n.size()
Out[137]: torch.Size([4, 10, 18])

前向输出的获取:前向的LSTM中,最后一个time step的输出的前hidden_size个,和最后一层向前传播h_n的输出相同:

#-1是前向LSTM的最后一个,前18是前hidden_size个
In [188]: a = output[-1,:,:18]  #前项LSTM中最后一个time step的output

In [189]: b = h_1[-2,:,:]  #拼接方式:[前向第一,后向第一,...,前向最后一个,后向最后一个].倒数第二个为前向最后一个

In [190]: a.size()
Out[190]: torch.Size([10, 18])

In [191]: b.size()
Out[191]: torch.Size([10, 18])

In [192]: a == b
Out[192]:
tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]],
       dtype=torch.uint8)

后向的获取:后向LSTM中,最后一个time step的输出的后hidden_size个,和最后一层后向传播的h_1的输出相同:

#0 是反向LSTM的最后一个,后18是后hidden_size个
In [196]: c = output[0,:,18:]  #后向LSTM中的最后一个输出

In [197]: d = h_1[-1,:,:] #后向LSTM中的最后一个隐藏层状态

In [198]: c == d
Out[198]:
tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]],
       dtype=torch.uint8)

4.使用注意点:

  1. 第一次调用之前LSTM api,需要初始化隐藏状态(一般规定形状,用rand函数即可),如果不初始化,默认创建全为0的隐藏状态

  2. 往往会使用LSTM or GRU 的输出的最后一维的结果,来代表LSTM、GRU对文本处理的结果,其形状为[batch, num_directions*hidden_size]。

    1. 并不是所有模型都会使用最后一维的结果

    2. 如果实例化LSTM的过程中,batch_first=False,则output[-1] or output[-1,:,:]可以获取最后一维

    3. 如果实例化LSTM的过程中,batch_first=True,则output[:,-1,:]可以获取最后一维

  3. 如果结果是(seq_len, batch_size, num_directions * hidden_size),需要把它转化为(batch_size,seq_len, num_directions * hidden_size)的形状,不能使用view等变形的方法,需要使用output.permute(1,0,2),即交换0和1轴,实现上述效果

  4. 使用双向LSTM的时候,往往会分别使用每个方向最后一次的output,进行拼接,作为当前数据经过双向LSTM的结果(!!!!!!)

    • 即:torch.cat([h_1[-2,:,:],h_1[-1,:,:]],dim=-1)

    • 最后的表示的size是[batch_size,hidden_size*2]

  5. 上述内容在GRU中同理,GRU的API和LSTM十分接近,有兴趣可以了解一下。

文本分类中CNN-LSTM融合原理 CNN-LSTM融合原理 阅读这篇文章需要较扎实的CNN和RNN基础。 怎么把CNN结合LSTM做文本分类,宏观的看,我以为有三种方法: CNN-> LSTM:将文本先经过CNN提取局部特征,再用LSTM提取这些局部特征的长距离特征,再经变换输入全连接层。 LSTM-> CNN:先对文本用LSTM提取长距离特征,得到融合了上下文的新文本,再用CNN提取新文本的局部特征(有点像先Word2Vec再CNN),再经变换输入全连接层。 CNN、LSTM同步:将CNN提取的局部特征和LSTM提取的长距离 阅读详情

相关推荐

融合句法树与主题注意力的层次化神经网络:社会情感分类新范式

自然语言处理中的情感分析旨在让计算机理解文本所表达的情感倾向。其核心原理在于将文本转化为机器可处理的向量表示,并通过分类器进行预测。传统方法如情感词典或主题模型,往往难以兼顾词语间的句法关联与上下文语义的微妙变化。随着深度学习的发展,循环神经网络(RNN)及其变体LSTM凭借序列建模能力,成为情感分析的主流技术。它们能捕捉词序信息,但标准LSTM将句子视为扁平序列,忽略了由主谓宾等关系构成的句法结构这一关键语义骨架。为此,树状长短期记忆网络(Tree-LSTM)应运而生,它能依据句法树结构整合信息,更精准地

weixin_30345577的博客 664

3.3 神经网络的输出-深度学习-Stanford吴恩达教授

多个例子中的向量化 (Vectorizing across Multiple Examples) 课程PPT

赵继超的笔记 9997

深入理解GLU(门控线性单元)及其在深度学习中的应用

本文深入解析了GLU(门控线性单元)的核心原理及其在深度学习中的应用。GLU通过门控机制动态调节信息流,相比LSTM等结构具有计算高效、易于并行的优势。文章详细介绍了其在PyTorch中的实现方法,并探讨了GLU在Transformer的FFN层变体(如GEGLU)以及计算机视觉、多模态融合等领域的应用潜力。

nn3456的博客 628

深度学习(神经网络的表示及输出)

首先拿以上这样一种神经网络模型来演示。

Darkness_01的博客 1936

多元回归分析:基于CNN-LSTM的多输入单输出预测模型及Matlab代码实现

我们将详细解释CNN-LSTM模型的原理,并提供一个示例,以便读者能够理解如何应用该模型进行预测任务。在这个示例中,我们使用了一个卷积层(Conv1D),其输出被最大池化层(MaxPooling1D)处理,然后输入到LSTM层中。通过合理选择模型架构、准备训练数据、训练模型并进行预测,我们可以利用CNN-LSTM模型对多输入变量进行预测。在这个示例中,我们使用训练集数据进行模型训练,并设置了训练的轮数(epochs)和批量大小(batch_size)。这些指标可以帮助我们评估模型的预测准确性。

CodeNexus的博客 814

自然语言处理:解密人类语言

自然语言处理(Natural Language Processing,NLP)是人工智能(Artificial Intelligence,AI)的一个重要分支,它旨在让计算机理解、生成和处理人类语言。自然语言是人类交流的主要方式,因此,自然语言处理的目标是使计算机能够理解和处理人类语言,从而实现人类与计算机之间的有效沟通。自然语言处理的历史可以追溯到1950年代,当时的研究主要集中在语言模型、语法分析和机器翻译等方面。

AI天才研究院 271

lstm 输入数据维度_理解Pytorch中LSTM的输入输出参数含义

本文不会介绍LSTM的原理,具体可看如下两篇文章Understanding LSTM NetworksDeepLearning.ai学习笔记(五)序列模型 -- week1 循环序列模型1、举个栗子在介绍LSTM各种参数含义之前我们还是需要先用一个例子(参考LSTM神经网络输入输出究竟是怎样的?Scofield的回答)来理解LSTM。Recurrent NNs,一般看的最多的图是这个:rnn但是...

weixin_28681379的博客 6481

STM32在六轴机械臂实时控制中的选型与分层架构设计

嵌入式实时控制系统中,微控制器的选型直接决定运动控制精度与系统可靠性。其核心在于匹配确定性中断响应、低延迟外设协同与工业协议栈支持等硬性指标。STM32凭借Cortex-M内核的NVIC调度能力、成熟HAL库及CAN FD/高级定时器等专用外设,在机电一体化场景中成为伺服控制中枢的首选。相比ESP32等通用MCU,STM32在硬实时性、EMC认证生态和工业总线兼容性上具备不可替代优势。本文围绕GTB六轴机械臂项目,详解基于STM32F407的硬件抽象层配置、FreeRTOS任务调度、事件队列解耦及闭环控制数

jupi8的博客 755

【深度学习-Day 43】解密LSTM:深入理解长短期记忆网络如何克服RNN的遗忘症

在上一篇文章中,我们深入探讨了标准循环神经网络(RNN)所面临的巨大挑战——长期依赖问题,以及随之而来的梯度消失与梯度爆炸。这些问题严重限制了RNN处理长序列数据的能力,使其难以捕捉间隔较远的信息。为了克服这一“健忘症”,一种强大的RNN变体应运而生:长短期记忆网络(Long Short-Term Memory, LSTM)。本文将深入剖析LSTM的核心结构,详细解读其精巧的“门控机制”和“细胞状态”,并通过图解方式揭示其如何智能地筛选、保留和输出信息,从而成为处理序列数据的王者。

吴师兄大模型的博客 2840

Zotero on Mac: 高效文献管理与引用指南

本文为Mac用户提供了一份全面的Zotero高效文献管理与引用指南。从安装配置、文献收集管理、PDF深度阅读与笔记,到在Word中无缝插入引用和生成参考文献,详细讲解了核心工作流。同时推荐了ZotFile、Better BibTeX等必备插件,并介绍了利用同步与WebDAV实现跨设备无缝衔接的方法,助力研究者构建个人数字知识库,极大提升学术研究效率。

weixin_29281941的博客 1178

PostgreSQL+Citus分布式集群实战:从零搭建到性能调优

本文详细介绍了如何从零开始搭建并优化PostgreSQL+Citus分布式数据库集群。内容涵盖集群规划、源码编译安装、Citus扩展部署、节点配置、分布式表创建以及核心性能调优策略,为应对海量数据增长提供了完整的部署指南和实战解决方案。

oo7890的博客 627

别再找了!关于RNN、LSTM、GRU,看这篇3万字“终结长文”就够了!

之前的文章中已经讲过CNN,GNN,transformer,还剩下一个循环神经网络没有讲过,这几种神经网络是目前最常用的连接主义的人工智能算法。

m0_59235945的博客 1182

保姆级避坑指南:用Keil和J-Flash搞定凌思微LE5010蓝牙芯片的首次程序下载

本文提供了一份详细的保姆级避坑指南,帮助开发者使用Keil和J-Flash工具完成凌思微LE5010蓝牙芯片的首次程序下载。文章重点解决了算法文件混淆、协议栈漏烧和仿真器连接异常等常见问题,并详细介绍了开发环境配置、协议栈烧录和下载接口的物理层奥秘,助力开发者高效完成蓝牙开发任务。

weixin_28739079的博客 467

KART-RERANK模型精讲:深入理解其卷积神经网络与LSTM混合架构

本文介绍了KART-RERANK模型如何通过融合CNN与LSTM的混合架构来提升信息检索的精准度。用户可在星图GPU平台上自动化部署🏎️ KART-RERANK: 繁荣山丘相关性大奖赛镜像,快速搭建环境,并将其应用于搜索引擎或推荐系统的相关性排序场景,以优化搜索结果。

weixin_30476025的博客 190

Ubuntu开发环境配置——持续更新

本文简要介绍了Ubuntu下的开发环境配置流程和问题分析与解决,更多内容持续更新中。

m0_53365889的博客 2225

uniapp: err = err.replace(this._UNI_INPUT_DIR_REG, ‘UNI_TypeError: err.replace is not a function问题修复

问题:uniapp的依赖包出现了问题,需要升级解决办法:升级uniapp依赖包,下载"semver"和”@dcloudio/uni-i18n“两个包,本地锁定postcss为7版本,就解决了。

py_boy的博客 1453

深度剖析CNN/RNN/LSTM等传统大模型与GPT/BERT等大模型的核心差异

传统序列模型(CNN/RNN/LSTM)与大语言模型(GPT/BERT)并非替代关系,而是互补关系:传统模型是序列数据处理的基础,解决了“从0到1”的序列特征提取问题,在低算力、小数据场景仍不可替代;大模型是语言理解的革命,通过Transformer和预训练范式解决了“从1到100”的通用语义建模问题,在复杂语言任务中展现出碾压性优势。作为开发者,需根据任务需求、数据规模、算力资源三者平衡选型:在满足业务效果的前提下,优先选择成本更低、部署更简单的方案;

1128

支付宝域名如何加入白名单(扫码老是弹窗)

支付宝扫码之后,遇到非支付宝官方网页,请确认是否继续访问弹窗,问题解决办法。本章教程提供解决办法,亲测有效。

猿小白的博客 9196

五大神经网络架构核心原理与实战指南:CNN、RNN、GAN、Transformer、GNN

神经网络作为深度学习的核心,其设计思想源于万能近似定理,理论上能逼近任意复杂函数。然而,面对图像、文本、图结构等具体数据时,全连接网络因参数量爆炸和无法有效利用数据内在结构而效率低下。因此,研究者引入了结构先验,将领域知识编码到网络设计中,诞生了针对不同数据结构的专用架构。卷积神经网络利用卷积核的局部连接和参数共享,高效提取图像的空间特征;循环神经网络通过循环结构处理序列依赖;生成对抗网络通过生成器与判别器的对抗博弈学习数据分布;Transformer凭借自注意力机制实现序列的全局建模和高效并行;图神经网络

weixin_30216561的博客 331

【Proteus仿真】基于AT89C51单片机的单片机双向通信

本文介绍了基于AT89C51单片机实现双向通信的系统设计。系统采用MAX232芯片实现TTL与RS-232电平转换,通过CONN-D9F接口建立串行通信连接。甲机程序通过按键控制发送不同指令字符,同时接收乙机数据驱动数码管显示;乙机程序根据接收指令控制LED显示状态,并可发送0-10数字至甲机。文中详细说明了MAX232电平转换原理、DB9接口定义,以及完整的单片机控制程序代码,包括串口初始化、数据收发处理等关键功能实现,为嵌入式系统串口通信开发提供了实用参考方案。

XU157303764的博客 1094

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

PyTorch深度学习工具箱:解锁文本分类的神经网络组合技

当你面对海量文本数据,需要快速构建高效的分类模型时,是否曾为选择哪种神经网络架构而犹豫不决?是传统的CNN,还是更擅长处理序列的LSTM?亦或是它们的组合?今天介绍的PyTorch神经网络模型库为你提供了完美的解决方案——一个集成了多种主流神经网络架构的深度学习工具箱,让你能够像搭积木一样自由组合模型组件,快速验证不同架构的性能表现。 这个基于PyTorch的开源项目专门针对文本分类任务,包含了

gitblog_00879的博客 831

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

为什么BiLSTM在NLP任务中表现优异?深入理解双向LSTM的工作原理

本文深入探讨了BiLSTM在NLP任务中表现优异的原因,详细解析了双向LSTM的工作原理及其在深度学习中的应用。通过对比传统RNN和单向LSTM的局限性,揭示了BiLSTM如何通过双向信息流动机制提升上下文感知能力,成为自然语言处理领域的核心技术之一。

sam99的博客 186

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

LSTM网络在文本处理中的核心原理与工程实践

长短期记忆网络(LSTM)作为循环神经网络(RNN)的重要变体,通过门控机制有效解决了传统RNN的梯度消失问题,在序列数据处理中展现出独特优势。其核心原理包含遗忘门、输入门和输出门的协同工作,能够动态调节信息流,特别适合处理文本这类具有前后依赖关系的序列数据。在工程实践中,LSTM与词嵌入技术(如Word2Vec、BERT)结合,显著提升了机器翻译、情感分析等NLP任务的性能。针对实际应用中的梯度爆炸、长文本记忆衰减等问题,可采用梯度裁剪、注意力机制等解决方案。当前LSTM的变体模型如GRU、SRU等,在保

weixin_29035405的博客 297

深入理解CRNN:OCR领域的主流模型架构解析

若需适配特定领域文本(如医疗报告、古籍文献),建议:1. 收集不少于500张领域相关图像2. 使用SynthText生成合成数据补充训练样本3. 在CRNN基础上微调最后几层网络4. 加入CRF层进一步优化字符连贯性本文深入剖析了CRNN模型的工作原理与工程实现细节,展示了其在通用OCR服务中的强大生命力。尽管深度学习技术日新月异,但CRNN凭借其结构简洁、推理高效、准确率高、易于部署的特点,依然是工业界广泛采用的OCR主干架构。特别是在CPU-only环境、低延迟要求、中文识别优先。

weixin_42581846的博客 847

【万字长文】2025年最新AI大模型算法工程师学习路线图:从零基础到精通,一篇文章全掌握

大模型在当今人工智能领域占据着核心地位,其强大的能力正不断推动各行业的变革与创新。无论是对人工智能充满好奇的初学者,还是希望在该领域深入发展的专业人士,掌握大模型相关知识和技能都至关重要。以下为你详细介绍 2025 年从零基础入门到精通大模型的学习路线。

2401_85343303的博客 2208
上一篇: 详解使用pytorch实现线性回归
下一篇: pytorch实现IMDB数据集情感分类(全连接层的网络、LSTM)
评论 14
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值