基于PyTorch的深度学习基础课程之十三:循环神经网络

循环神经网络(RNN)是一种用于处理序列数据的深度学习模型,通过隐层状态的反馈机制实现对序列特征的记忆和学习。RNN可应用于序列回归、分类和标注任务,如股价预测、文本分类等场景,其核心优势在于能够捕捉序列数据中的时序依赖关系。本文详细讨论了RNN的基本结构以及LSTM的结构,以及如何使用它们来构建循环神经网络,并给出了简要的应用示例。RNN也是理解目前应用最为广泛的Transformer模型(下文将讨论)的基础。

循环神经网络(Recurrent Neural Network,RNN)是用于对序列的非线性特征进行学习的深度神经网络。循环神经网络的输入是有前后关联关系的序列。

循环神经网络可以用来解决与序列有关的问题,如序列回归、序列分类和序列标注等任务。序列的回归问题,如气温、股票价格的预测问题,它的输入是前几个气温、股票价格的值,输出的是连续的预测值。序列的分类问题,如影评的正负面分类、垃圾邮件的检测,它的输入是影评和邮件的文本,输出的是预定的有限的离散的标签值。序列的标注问题,如自然语言处理中的中文分词和词性标注,循环神经网络可处理传统机器学习中的隐马尔可夫模型、条件随机场等模型胜任的标注任务。

1 基本单元

循环神经网络的基本单元如图 13-1(a)所示。循环神经网络基本结构的中间部分称为隐层,向量 sss 标记了隐层的状态。隐层的输出有两个,一个是 yyy,另一个反馈到自身。到自身的反馈将与下一步的输入共同改变隐层的状态向量 sss。因此,隐层的输入也有两个,分别是当前输入 xxx 和来自自身的反馈(首步没有来自自身的反馈)。

图13-1 循环神经网络基本单元示意

循环神经网络的反馈机制使得它有了记忆功能,具备了处理序列的能力。将基本单元按每步输入展开,如图13-1(b)所示。x(1),x(2),...,x(n)x^{(1)}, x^{(2)}, ..., x^{(n)}x(1),x(2),...,x(n) 是每步的输入,s1,s2,...,sns_1, s_2, ..., s_ns1,s2,...,sn 是每步的状态,y(1),y(2),...,y(n)y^{(1)}, y^{(2)}, ..., y^{(n)}y(1),y(2),...,y(n) 是每步的输出。图中的 U,V,WU, V, WU,V,W 是矩阵,分别是从输入到隐层状态、隐层状态到输出、当前状态到后一步状态的变换参数,它们是要学习的内容。要注意的是,图13-1(b)只是图13-1(a)所示基本单元的展开,并不是有多个基本单元,也就是说,图13-1(b)中的 U,V,WU, V, WU,V,W 矩阵分别只是一个,并不是每一步都有一个不同的 U,V,WU, V, WU,V,W 矩阵。

下面用一个简单的前向传播示例来解释 U,V,WU, V, WU,V,W 矩阵。

设输入为长度仅为2的序列 x=(x(1),x(2))x = (x^{(1)}, x^{(2)})x=(x(1),x(2)),其中,x(1)x^{(1)}x(1) 和 x(2)x^{(2)}x(2) 是一个3维的向量(向量常用来数字化表示一个基本的输入单元,如自然语言处理中用来表示词的词编码向量)。该输出的标签序列为 y=(y(1),y(2))y = (y^{(1)}, y^{(2)})y=(y(1),y(2))。那么适应该输入和输出要求的由基本单元组成的循环网络结构如图13-2所示。

图13-2 循环神经网络前向传播示意

图 13-2 中的循环神经网络的输入样本的观测序列有两个分量 x(1),x(2)x^{(1)}, x^{(2)}x(1),x(2),即每次输入的步长数为 222。观测序列的分量是 333 维的向量。隐状态是一个 222 维的向量 sss。输出是 111 维的标量,分别是 y(1),y(2)y^{(1)}, y^{(2)}y(1),y(2)。U,V,WU, V, WU,V,W 的值如图中所示。

记由上一步状态转换而来的状态分量为 sss_sss,由输入转换而来的状态分量为 sxs_xsx,则状态向量 s=tanh⁡(ss+sx)s = \tanh (s_s + s_x)s=tanh(ss+sx),其中,tanh⁡(⋅)\tanh (\cdot)tanh(⋅) 是激活函数(见本专栏文章之八)。设初始隐状态为 s0=[0.00.0]s_0 = [0.0 \quad 0.0]s0=[0.00.0]。

对第 111 步来说:

ss=s0×W=[0.00.0]×[0.10.20.30.4]=[0.00.0]sx=x(1)×U=[0.10.20.3]×[0.10.20.20.30.30.1]=[0.140.11]s1=tanh⁡(ss+sx)=[tanh⁡(0.14)tanh⁡(0.11)]=[0.140.11]y(1)=tanh⁡(s×V)=tanh⁡([0.140.11]×[0.20.5])=0.08(13-1) s_s = s_0 \times W = [0.0 \quad 0.0] \times \begin{bmatrix} 0.1 & 0.2 \\ 0.3 & 0.4 \end{bmatrix} = \begin{bmatrix} 0.0 & 0.0 \end{bmatrix} \\ s_x = x^{(1)} \times U = \begin{bmatrix} 0.1 & 0.2 & 0.3 \end{bmatrix} \times \begin{bmatrix} 0.1 & 0.2 \\ 0.2 & 0.3 \\ 0.3 & 0.1 \end{bmatrix} = \begin{bmatrix} 0.14 & 0.11 \end{bmatrix} \\ s_1 = \tanh (s_s + s_x) = [\tanh (0.14) \quad \tanh (0.11)] = \begin{bmatrix} 0.14 & 0.11 \end{bmatrix}\\ y^{(1)} = \tanh (s \times V) = \tanh \left( \begin{bmatrix} 0.14 & 0.11 \end{bmatrix} \times \begin{bmatrix} 0.2 \\ 0.5 \end{bmatrix} \right) = 0.08 \tag{13-1} ss=s0×W=[0.00.0]×[0.10.30.20.4]=[0.00.0]sx=x(1)×U=[0.10.20.3]× 0.10.20.30.20.30.1 =[0.140.11]s1=tanh(ss+sx)=[tanh(0.14)tanh(0.11)]=[0.140.11]y(1)=tanh(s×V)=tanh([0.140.11]×[0.20.5])=0.08(13-1)

读者可以对照图计算下第 222 步的状态和输出。

在本示例中,为了简化起见,每个激活函数的输入都没有加阈值参数 θ\thetaθ,在实际应用时,该参数可根据需要添加。

PyTorch中nn模块的RNN实现了如图13-2所示的RNN基本单元(不包括隐层状态到输出部分),它的类原型如下所示。

torch.nn.RNN原型

python 复制代码
CLASStorch.nn.RNN(input_size, hidden_size, num_layers=1, nonlinearity='tanh', bias=True, 
batch_first=False, dropout=0.0, bidirectional=False, device=None, dtype=None)

参数 input_size 设定输入 xxx 的维数,参数 hidden_size 设定隐状态向量 sss 的维数,参数 bias 设定是否使用阈值参数 bbb。

RNN 来模拟图13-2所示的循环神经网络前向传播的代码见代码 13-1。

代码13-1 模拟循环神经网络前向传播

python 复制代码
import torch
import torch.nn as nn
import numpy as np

# 参数设置
hidden_dim = 2
input_dim = 3
batch_size = 1

# 使用内置RNN层来模拟
rnn = nn.RNN(input_size=input_dim, hidden_size=hidden_dim, batch_first=True)

# 设置权重值
kernel_weights = torch.tensor([[0.1, 0.2],
                              [0.2, 0.3],
                              [0.3, 0.1]], dtype=torch.float32)
recurrent_weights = torch.tensor([[0.1, 0.2],
                                 [0.3, 0.4]], dtype=torch.float32)
bias_weights = torch.tensor([0., 0.], dtype=torch.float32)

with torch.no_grad():
    rnn.weight_ih_l0.data = kernel_weights.T  # 输入到隐藏的权重
    rnn.weight_hh_l0.data = recurrent_weights.T  # 隐藏到隐藏的权重
    rnn.bias_ih_l0.data = bias_weights  # 输入偏置
    rnn.bias_hh_l0.data = torch.zeros(hidden_dim)  # 隐藏偏置

# 测试序列处理
x1 = torch.tensor([[[0.1, 0.2, 0.3]]])  # 形状(batch, seq_len, input_size)
x2 = torch.tensor([[[0.2, 0.3, 0.4]]])
s0 = torch.zeros(1, batch_size, hidden_dim)  # 初始隐状态

# 第一步处理
out1, s1 = rnn(x1, s0)
print("out1:", out1.squeeze())
print("s1:", s1.squeeze())

输出:out1: tensor([0.1391, 0.1096], grad_fn=)

s1: tensor([0.1391, 0.1096], grad_fn=)

python 复制代码
# 第二步处理(使用上一步的隐状态)
out2, s2 = rnn(x2, s1)
print("out2:", out2.squeeze())
print("s2:", s2.squeeze())

输出:out2: tensor([0.2419, 0.2370], grad_fn=)

s2: tensor([0.2419, 0.2370], grad_fn=)

python 复制代码
print(rnn)

输出:RNN(3, 2, batch_first=True)

python 复制代码
from torchinfo import summary
print("模型结构:")
summary(rnn, input_size=(batch_size, 1, input_dim))

输出:

python 复制代码
模型结构:
=================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
=================================================================================
RNN                                      [1, 1, 2]                 14
=================================================================================
Total params: 14
Trainable params: 14
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 0.00
=================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.00
Estimated Total Size (MB): 0.00
=================================================================================

在参数设置部分行定义了隐状态的维度为2,它的大小决定了 WWW 矩阵的维度。

接下来实例化了一个 RNN 单元。与图13-2所示的示例不同,这里为了全面演示,使用了阈值参数。要注意的是,RNN 不包括隐层状态到输出部分,因此它的输出与隐状态是相同的。

然后,按照图13-2 所示的示例设置了网络参数,为了与示例保持一样,将阈值参数都设为0。

随后,模拟了图13-2所示的示例的两步运行,读者可以对照一下运行结果。

因为循环神经网络中基本单元的状态不仅与输入有关,还与上一状态有关,因此,传统的反向传播算法不适用于 U,V,WU, V, WU,V,W 的更新。在循环神经网络中,参数更新的算法是所谓的通过时间反向传播(BackPropagation Through Time,BPTT)算法,该算法多了一个在时间上反向传递的梯度,不再赘述。

最后来计算一下循环神经网络基本单元的参数数量。在基本单元中,需要训练的有三个变换的 UUU, VVV, WWW 矩阵和阈值参数向量。记输入向量的维数为 iii,隐层状态向量的维数为 hhh,输出向量的维数为 ooo。从输入到隐层变换的矩阵 UUU 是 i×hi \times hi×h 大小的,其对应的阈值参数向量是 hhh 长的。从上一步隐层到当前隐层变换的矩阵 WWW 是 h×hh \times hh×h 大小的,其对应的阈值参数向量是 hhh 长的。从隐层到输入变换的矩阵 VVV 是 h×oh \times oh×o 大小的,其对应的阈值参数向量是 ooo 长的。因此总的参数数量为:i×h+h+h×h+h+h×o+oi \times h + h + h \times h + h + h \times o + oi×h+h+h×h+h+h×o+o。

图 13-2所示的示例中,i=3i = 3i=3,h=2h = 2h=2,o=1o = 1o=1,不设阈值参数,因此,参数数量为:
3×2+2×2+2×1=12. 3 \times 2 + 2 \times 2 + 2 \times 1 = 12. 3×2+2×2+2×1=12.

代码 13-1 所示的示例中,RNN 没有实现输出部分,因此参数数量为:i×h+h+h×h+h=14i \times h + h + h \times h + h = 14i×h+h+h×h+h=14。

2 网络结构

图13-1(b)所示结构的特点是每一个输入都有一个对应的输出,称为many to many结构,它适合完成标注类任务。除了many to many结构外,循环神经网络还有其他几类常用结构,如图13-2所示。

图13-2 循环神经网络常用结构示意

one to many结构是单输入多输出的结构,可用于输入图片给出文字说明。many to one结构是多输入单输出的结构,可用于文本分类任务,如影评情感分类、垃圾邮件分类等。many to many delay结构也是多输入多输出的结构,但它是有延迟的输出,该结构常用于机器翻译、机器问答等。

3 长短时记忆网络

长短时记忆网络(Long Short Term Memory,LSTM)是循环神经网络中最常用的一种,在实践中表现出了良好效果。

以基本单元为基础构建的循环神经网络虽然能够处理有关联的序列,但是因为梯度消散和梯度爆炸等问题,不能有效利用间距过长的信息,称之为长期依赖(Long-Term Dependencies)问题。

长短时记忆网络是在各步间传递数据时,通过几个可控门(遗忘门、输入门、候选门、输出门)控制先前信息和当前信息的记忆和遗忘程度,从而使循环神经网络具备了长期记忆功能,能够利用间距很长的信息来解决当前问题。

图13-1所示的基本单元可以用图13-3(a)简化表示(输入序列分量和输出序列分量的序号由上标表示改为下标表示),状态 sis_isi 和输出 yiy_iyi 可表示为:

si=tanh⁡(si−1×W+xi×U)=tanh⁡([si−1xi]×[WU])yi=tanh⁡(si×V)(13-2) s_i = \tanh(s_{i-1} \times W + x_i \times U) = \tanh\left( [s_{i-1} \quad x_i] \times \begin{bmatrix} W \\ U \end{bmatrix} \right)\\ y_i = \tanh(s_i \times V) \tag{13-2} si=tanh(si−1×W+xi×U)=tanh([si−1xi]×[WU])yi=tanh(si×V)(13-2)

长短时记忆网络的基本单元如图 13-3(b)所示,从单元外部看,它与基本单元最大的区别在于每步的输出 yiy_iyi 也要嵌入下一步运算。

图13-3循环神经网络基本单元与长短时记忆网络单元对比

图 13-3 (b) 中标记为①、②、③、④的分别称为遗忘门、输入门、候选门、输出门,σ\sigmaσ 表示 Sigmoid 激活函数,tanh 表示 tanh 激活函数。

遗忘门用来控制上一步的状态 si−1s_{i-1}si−1 输入到本步的量,也就是遗忘上一步的状态的程度,它的输入是上一步的输出和本步的输入 [yi−1xi][y_{i-1} \quad x_i][yi−1xi],它的输出为:

fi=σ([yi−1xi]⋅Wf+bf)(13-3) f_i = \sigma([y_{i-1} \quad x_i] \cdot W_f + b_f) \tag{13-3} fi=σ([yi−1xi]⋅Wf+bf)(13-3)

式中,Wf,bfW_f, b_fWf,bf 是要学习的参数,下同。

遗忘门的输出 fif_ifi 通过乘操作作用于上一步的状态 si−1s_{i-1}si−1。

输入门和候选门用来将新信息输入本步的状态。候选门通过 tanh 函数提供候选输入信息:

s^i=tanh⁡([yi−1xi]⋅Ws+bs)(13-4) \hat{s}i = \tanh([y{i-1} \quad x_i] \cdot W_s + b_s) \tag{13-4} s^i=tanh([yi−1xi]⋅Ws+bs)(13-4)

输入门通过 Sigmoid 函数来控制输入量:

ini=σ([yi−1xi]⋅Win+bin)(13-5) in_i = \sigma([y_{i-1} \quad x_i] \cdot W_{in} + b_{in}) \tag{13-5} ini=σ([yi−1xi]⋅Win+bin)(13-5)

输入门的输出 iniin_iini 通过乘操作作用于候选输入信息 s^i\hat{s}_is^i。

经过遗忘门和输入信息后得到本步的状态 sis_isi:

si=si−1×fi+s~i×ini(13-6) s_i = s_{i-1} \times f_i + \tilde{s}_i \times in_i \tag{13-6} si=si−1×fi+s~i×ini(13-6)

同样地,输出门用来控制本步状态 sis_isi 的输出 yiy_iyi:

oi=σ([yi−1xi]⋅Wo+bo)yi=tanh⁡(si)×oi(13-7) o_i = \sigma([y_{i-1} \quad x_i] \cdot W_o + b_o) \\ y_i = \tanh(s_i) \times o_i \tag{13-7} oi=σ([yi−1xi]⋅Wo+bo)yi=tanh(si)×oi(13-7)

本步产生的新状态 sis_isi 和输出 yiy_iyi 将馈入下一步的运算中。

长短时记忆网络比基本循环神经网络具备更长期的记忆功能,但结构比较复杂,对效率有一定的影响。2014年提出了简化实用的结构GRU(gate recurrent unit),提高了效率。

4 双向循环神经网络和深度循环神经网络

在某些非实时问题中,不仅要利用目标前面的信息,还需要利用目标后面的信息。双向循环神经网络(bidirectional RNN)可用来解决需要利用双向信息的问题。

双向循环神经网络是将两个循环神经网络上下叠加在一起,输出由它们的状态共同决定,如图13-4所示。

图13-4 双向循环神经网络

下面的循环神经网络先从左到右前向计算一遍,得到每步的状态值。然后,上面的循环神经网络再从右到左后向计算一遍,此时的输入次序正好是反过来的,得到每步的状态值。最后将两个循环神经网络对应步的输出相加后经过激活函数得到该步的输出。

深度循环神经网络(deepRNN)也是将多个循环神经网络上下叠加起来。与双向循环神经网络不同的是,它不是双向计算,而是所有层同时前向计算,下层的输出是上层的输入,如图 13-5所示。深度循环神经网络一般会比单层的循环神经网络取得更好的效果。

图13-5 深度循环神经网络

当然,也可以将深度循环神经网络和双向循环神经网络结合起来,构成深度双向循环神经网络。

PyTorch在RNN和LSTM等类中提供了双向循环和多层叠加的功能,只需要将输入参数bidirectional设为True和num_layers设为需要的层次值即可(见上面RNN类原型)。

5 循环神经网络中文分词应用示例

自然语言处理领域的很多问题都是序列标注问题。中文分词是将中文句子分解成有独立含义的字或词,如"我爱自然语言处理"可分解成"我 爱 自然 语言 处理"或"我 爱 自然语言 处理"。中文分词是中文自动处理最基础的步骤,它是后续词性标注和语义分析的前导任务。英文自动处理的任务中不存在分词问题。

当前,标注方法是比较成功的分词方法。标注分词方法给句子中的每个字标记一个能区分词的标签,如SBME四标注法中,"S"表示是该字是单字,"B"表示该字是一个词的首字,"M"表示该字是一个词的中间字,"E"表示该字是一个词的结尾字。"我爱自然语言处理"一句两种分词的标注如图13-6所示。

图13-6 标注法中文分词示例

对分词模型来说,就是要对输入序列产生正确的输出序列。

本小节的示例主要可分为四步,PyTorch框架下的实现代码见代码13-2。

1)提取训练语料中的所有字,形成字典

该步的主要目的是给训练语料中用到的字进行编号。

2)将语料中的句子转化为训练样本

模型对每个输入训练样本的长度要求一致,因此,定义了一个collate_fn()函数,用于在加载每批数据时自动对句子和标签进行填充,短的句子在后面填充0,相应标签填充"X"。通过字典将句子的汉字序列转换为数字序列。

3)搭建模型进行训练

采用深度双向循环神经网络模型。

nn.Embedding是词向量层。所谓词向量可以简单地理解为用指定维度的向量来对汉字进行编码。在本示例中,用一个的64维向量来表示一个汉字。词向量各维度的值是要学习的参数,假设有4684个汉字,每个汉字用64维的向量来表示,则词向量层需要学习的参数共4684×64=2997764684 \times 64 = 2997764684×64=299776个。词向量在自然语言处理领域是很重要的技术,得到了广泛应用,将在后面Transformer主题的文章中继续讨论。

nn.LSTM中,通过设置bidirectional=True和num_layers=2,定义了一个双层双向的LSTM层。

4)利用训练好的模型进行分词

先要将待分词的句子转换成适合模型输入的形式,再用模型进行分词。

对"中国首次火星探测任务天问一号探测器实施近火捕获制动"一句的分词结果为:"中国 首次 火星 探测 任务 天问 一 号 探测器 实施 近火 捕获 制动"。

代码13-2 RNN中文分词示例

python 复制代码
### 训练样本预处理
import numpy as np

file = open("traindata.txt", encoding='utf-8')
test_str = "中国首次火星探测任务天问一号探测器实施近火捕获制动"

new_sents = []
sents_labels = []
for line in file.readlines():
    line = line.split()
    new_sent = ''
    sent_labels = ''
    for word in line:
        if len(word) == 1:
            new_sent += word
            sent_labels += 'S'
        elif len(word) >= 2:
            new_sent += word
            sent_labels += 'B' + 'M'*(len(word)-2) + 'E'
    if new_sent != '':
        new_sents.append([new_sent])
        sents_labels.append([sent_labels])
print("训练样本准备完毕!")
print('共有数据 %d 条' % len(new_sents))
print('平均长度:', np.mean([len(d[0]) for d in new_sents]))

输出:训练样本准备完毕!

共有数据 62946 条

平均长度: 8.67100371747212

python 复制代码
import torch
import torch.nn as nn
from torchinfo import summary
from torch.utils.data import Dataset, DataLoader
from torch.nn.utils.rnn import pad_sequence

# 参数设置
tags = {'S': 0, 'B': 1, 'M': 2, 'E': 3, 'X': 4} # 标签
embedding_size = 32 # 词向量大小
maxlen = 32 # 序列长度,长则截断,短则填充0
hidden_size = 32
batch_size = 64
epochs = 3

# 1.提取出所有用到的字,形成字典
stat = {}
for i in range(len(new_sents)):
    for v in new_sents[i][0]:
        stat[v] = stat.get(v, 0) + 1
stat = sorted(stat.items(), key=lambda x:x[1], reverse=True)
vocab = [s[0] for s in stat]
char2id = {c: i+1 for i, c in enumerate(vocab)} # 编号0为填充值,因此从1开始编号
id2char = {i+1: c for i, c in enumerate(vocab)}
print("用到的所有字的个数:", len(vocab))

输出:用到的所有字的个数: 3878

python 复制代码
# 2.将训练语句转化为训练样本
trainX = []
trainY = []
for i in range(len(new_sents)):
    sent = new_sents[i][0]
    labe = sents_labels[i][0]
    rep_len = len(sent)
    x = [0] * rep_len
    y = [4] * rep_len
    for j in range(rep_len):
        x[j] = char2id[sent[j]]
        y[j] = tags[labe[j]]
    trainX.append(torch.LongTensor(x))
    trainY.append(torch.LongTensor(y))

# 自定义Dataset
class CWSDataset(Dataset):
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
        
    def __len__(self):
        return len(self.X)
    
    def __getitem__(self, idx):
        return self.X[idx], self.Y[idx]

# 创建DataLoader
def collate_fn(batch): # 批加载时填充
    X, Y = zip(*batch)
    X = pad_sequence(X, batch_first=True, padding_value=0)
    Y = pad_sequence(Y, batch_first=True, padding_value=4)
    return X, Y

dataset = CWSDataset(trainX, trainY)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)

# 3.定义双层双向LSTM模型,训练
class BiLSTM_CWS(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, num_tags):
        super(BiLSTM_CWS, self).__init__()
        self.embedding = nn.Embedding(vocab_size+1, embedding_dim, padding_idx=0)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim//2, 
                           num_layers=2, 
                           bidirectional=True, 
                           batch_first=True,
                           dropout=0.4)
        self.fc = nn.Linear(hidden_dim, num_tags)
        
    def forward(self, x):
        x = self.embedding(x)
        x, _ = self.lstm(x)
        x = self.fc(x)
        return x

# 初始化模型
model = BiLSTM_CWS(len(vocab), embedding_size, hidden_size, len(tags))

# 打印模型详细结构
print("\n模型结构:")
summary(model, input_size=(batch_size, maxlen), dtypes=[torch.long])

输出:

python 复制代码
模型结构:
=================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
=================================================================================
BiLSTM_CWS                               [64, 32, 5]               --
├─Embedding: 1-1                         [64, 32, 32]              124,128
├─LSTM: 1-2                              [64, 32, 32]              12,800
├─Linear: 1-3                            [64, 32, 5]               165
=================================================================================
Total params: 137,093
Trainable params: 137,093
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 34.17
=================================================================================
Input size (MB): 0.02
Forward/backward pass size (MB): 1.13
Params size (MB): 0.55
Estimated Total Size (MB): 1.70
=================================================================================
python 复制代码
# 训练设置
criterion = nn.CrossEntropyLoss(ignore_index=4)  # 忽略填充标签
optimizer = torch.optim.Adam(model.parameters())

# 训练循环
for epoch in range(epochs):
    total_loss = 0
    for batch_X, batch_Y in dataloader:
        optimizer.zero_grad()
        outputs = model(batch_X)
        loss = criterion(outputs.view(-1, len(tags)), batch_Y.view(-1))
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print(f'Epoch {epoch+1}, Loss: {total_loss/len(dataloader):.4f}')

# 保存模型
torch.save(model.state_dict(), 'bi_lstm_cws.pth')

输出:Epoch 1, Loss: 0.8789

Epoch 2, Loss: 0.5091

Epoch 3, Loss: 0.4288

python 复制代码
# 4.利用训练好的模型进行分词
def predict(model, testsent):
    model.eval()
    x = [0] * maxlen
    replace_len = min(len(testsent), maxlen)
    for j in range(replace_len):
        x[j] = char2id.get(testsent[j], 0)
    x = torch.LongTensor(x).unsqueeze(0)
    with torch.no_grad():
        label = model(x)
    label = torch.argmax(label, dim=2).squeeze(0)
    s = ''
    for i in range(min(len(testsent), maxlen)):
        tag = label[i].item()
        if tag == 0 or tag == 3:
            s += testsent[i] + ' '
        elif tag == 1 or tag == 2:
            s += testsent[i]
    print(s)

# 测试预测
predict(model, test_str)

输出:中国 首次 火星 探测 任务 天问 一 号 探测器 实施 近火 捕获 制动

相关推荐
爱吃泡芙的小白白9 小时前
神经网络压缩实战指南:让大模型“瘦身”跑得更快
人工智能·深度学习·神经网络·模型压缩
YelloooBlue9 小时前
深度学习 SOP: conda通过命令快速构建指定版本tensorflow gpu环境。
深度学习·conda·tensorflow
AI即插即用10 小时前
即插即用系列 | AAAI 2026 WaveFormer: 当视觉建模遇上波动方程,频率-时间解耦的新SOTA
图像处理·人工智能·深度学习·神经网络·计算机视觉·视觉检测
逄逄不是胖胖10 小时前
《动手学深度学习》-55-2RNN的简单实现
人工智能·深度学习
咚咚王者10 小时前
人工智能之核心技术 深度学习 第四章 循环神经网络(RNN)与序列模型
人工智能·rnn·深度学习
Dreaming_of_you11 小时前
pytorch/cv2/pil/torchvision处理图像缩小的最佳方案
人工智能·pytorch·python·opencv
机 _ 长11 小时前
YOLO26 改进 | 训练策略 | 知识蒸馏 (Response + Feature + Relation)
python·深度学习·yolo·目标检测·机器学习·计算机视觉
美狐美颜sdk12 小时前
抖动特效在直播美颜sdk中的实现方式与优化思路
前端·图像处理·人工智能·深度学习·美颜sdk·直播美颜sdk·美颜api
Yeats_Liao12 小时前
异步推理架构:CPU-NPU流水线设计与并发效率提升
python·深度学习·神经网络·架构·开源
哥布林学者13 小时前
吴恩达深度学习课程五:自然语言处理 第三周:序列模型与注意力机制(一)seq2seq 模型
深度学习·ai