当前位置:   article > 正文

NLP——姓氏分类与机器翻译探秘(前馈神经网络&Transformer)

NLP——姓氏分类与机器翻译探秘(前馈神经网络&Transformer)

目录

一、前馈神经网络探索:多层感知器与卷积神经网络

1.The Multilayer Perceptron(多层感知器)

2.卷积神经网络(CNN)

2.1卷积层

2.1.1卷积核

2.1.2卷积步长

2.2激活层

2.3池化层

2.4全连接层

二.姓氏分类

1.姓氏数据集

2 词汇表、向量化器和数据加载器

2.1 词汇表类

2.2 姓氏向量化器

3 姓氏分类器模型

三、机器翻译

2.2.1 编码器

2.2.2 注意力机制

2.2.3 含注意力机制的解码器

2.2.4 评价翻译结果

四、使用Transformer和PyTorch构建的日中机器翻译模型

1.获取平行语料库数据集

2.准备分词器

3.使用分词器和原始句子构建TorchText的词汇表对象,并将句子转换为Torch张量

4.创建一个数据加载器对象,用于在训练过程中迭代

5.Sequence-to-sequence Transformer

6.开始训练


一、前馈神经网络探索:多层感知器与卷积神经网络

在本文中,我们将深入挖掘前馈神经网络的核心,特别是多层感知器和卷积神经网络。多层感知器通过层叠感知器单元,提升了我们在前一次实验中学到的简单感知器的功能。卷积神经网络则通过窗口滤波器的灵感,擅长于学习输入数据的局部模式,成为计算机视觉和序列数据处理的重要工具。我们将通过具体案例来展示这些模型的应用,并与允许信息反馈的递归神经网络进行对比。同时,我们将关注数据张量在计算过程中的变化,以加深对神经网络模型的理解。

1.The Multilayer Perceptron(多层感知器)

多层感知器(MLP)作为神经网络的基本构建块,是在感知器的基础上发展而来的。与单个感知器不同,MLP通过组合多个感知器,形成多层结构,其中每一层的输出都是一个向量而非单一数值。在PyTorch框架中,实现MLP仅需设置线性层的输出特征数。此外,MLP的特点在于每层之间不仅包含线性变换,还引入了非线性激活函数。

图1 多层感知机

图2展示了最简单的MLP结构,它包含三个阶段:输入、隐藏和输出。输入阶段接收数据向量,例如在“餐馆评论情绪分类”示例中,输入是经过压缩的one-hot编码的评论。经过第一个线性层处理后,输入向量被转换为隐藏向量,这一阶段是连接输入和输出的桥梁。隐藏向量中的每个值代表该层中各个感知器的输出。随后,隐藏向量作为输入传递到第二个线性层,生成最终的输出向量。在二分类任务中,如Yelp评论分类,输出向量可以是简单的1或0。而在多分类任务中,输出向量的维度将与类别数量相匹配。虽然这里仅展示了一个隐藏向量,但MLP可以包含多个隐藏层,每个层生成自己的隐藏向量。最终,这些隐藏向量通过线性层和非线性激活的组合,映射到输出向量。

图2 一种具有两个线性层和三个表示阶段(输入向量、隐藏向量和输出向量)的MLP的可视化表示

多层感知器的强大之处在于引入了第二个线性层,并使模型能够学习到一种中间表示——这种表示能够捕捉到数据点是否位于某条直线(或更广义地说,某个超平面)的一侧。当分类任务的数据是线性可分的时候,这种能力成为神经网络应用的一个关键优势,也是其建模能力的高峰所在。

2.卷积神经网络(CNN)

2.1卷积层


2.1.1卷积核

卷积神经网络中的卷积核是一个小型矩阵,它能在输入数据上滑动以提取特定特征。这种操作相当于应用一个过滤器,能够捕捉输入数据中的多样化特征。卷积核通常是一个正方形的矩阵,其尺寸会根据网络的结构和任务难度来设定。在训练阶段,卷积核的权重会不断优化,以最大化地提取输入数据中的关键信息。通过使用多个卷积核,CNN能够同时学习并识别不同类型的特征,从而增强其特征提取的效率。卷积核不仅有宽度、高度,还有深度,通常表示为宽度x高度x深度。此外,卷积核的参数不仅包含核内的权重,还包含偏置项。

2.1.2卷积步长

在卷积神经网络中,卷积核在执行卷积操作时会按照一定的间距移动,这个间距被称为卷积步长。不同的步长设置会影响卷积操作后得到的结果的大小,即使是在使用相同尺寸的卷积核和相同的词汇表进行卷积时也是如此。

2.2激活层


在卷积神经网络中,激活层是紧接在卷积层之后的一个处理层,其主要目的是引入非线性元素,以此提升网络的表达能力。激活层的关键部分是激活函数,它负责在神经网络中引入非线性,使得网络能够识别并学习复杂的非线性关系。常见的激活函数有sigmoid、ReLU和tanh等。这些激活函数作用于每个神经元的输出,产生新的特征图,从而维持网络的非线性特性,并帮助网络学习到更高级别的抽象特征。

2.3池化层

池化层在卷积神经网络中用于对特征图进行池化处理。池化过程涉及在特征图的特定区域内选取一个值来代表该区域。这种操作是针对每个特征图独立进行的,它能够减小特征图的尺寸,包括宽度和高度,从而减少后续卷积层的参数数量,节省计算资源。此外,池化还有助于防止过拟合。常见的池化方法包括最大池化,它选取区域内最大的值作为代表,以及平均池化,它计算区域内所有值的平均值作为代表。

2.4全连接层

多层感知器是一种由完全连接的神经元构成的神经网络。在卷积神经网络(CNN)中,全连接层是一种普遍使用的层,其中每个神经元都与前一层的每一个神经元直接相连,也叫做密集层。这类层一般置于卷积层与输出层之间,其主要任务是转换卷积层所提取的特征,以便生成最终的输出。

全连接层通过执行非线性变换和特征的融合,增强了模型的表达性和准确性。它使网络能够理解和捕捉不同特征之间的复杂联系,进而执行更复杂的模式识别和分类操作。在模型训练时,全连接层的参数必须通过学习过程进行优化,以减少损失并提升模型的表现。为了引入非线性,全连接层常常使用激活函数,比如ReLU、Sigmoid或Tanh,这些函数有助于提升网络的表达力和学习效率。

二.姓氏分类

1.姓氏数据集

本姓氏数据集汇聚了来自18个国家的10,000个不同姓氏,这些姓氏由作者从互联网上的多个姓名资源中收集而来。数据集具有两个引人注目的特点。首先,其分布极不平衡,其中前三大语言占据数据的60%以上,具体为英语(27%)、俄语(21%)和阿拉伯语(14%),其余15个民族的姓氏频率逐渐降低,这一特性也反映了语言的独特性。其次,国籍与姓氏的拼写之间存在着一种清晰且直观的联系,某些拼写变体与特定国家有着紧密的关联(如“O’Neill”、“Antonopoulos”、“Nagasawa”或“Zhu”)。在本研究中,姓氏分类的任务便是将这些姓氏归类到它们各自的国籍类别中。

2 词汇表、向量化器和数据加载器

为了对姓氏进行字符级别的分类,我们使用了词汇表、向量化器和DataLoader,将这些姓氏字符串转换为向量化的mini-batches。这些数据结构它们展示了多态性的一个例子,将姓氏的字符标记与Yelp评论的单词标记同等对待。数据向量化不是通过将单词映射到整数,而是通过将字符映射到整数来完成的。

2.1 词汇表类

使用的词汇类与将Yelp评论中的单词映射到对应的整数。简要来说,词汇表是由两个Python字典组成的协调,这两个字典在令牌(在本例中是字符)和整数之间建立了一个双射;也就是说,第一个字典将字符映射到整数索引,第二个字典将整数索引映射回字符。与Yelp评论的词汇表不同,我们使用的是one-hot词汇表,不计算字符出现的频率,但会限制频繁出现的条目。这主要是因为数据集规模较小,而且大多数字符出现的频率足够高。

2.2 姓氏向量化器

尽管词汇表将单个令牌(字符)转换为整数,但SurnameVectorizer负责应用词汇表并将姓氏转换为向量。其实例化和使用方法与“示例:对餐馆评论的情绪进行分类”中的ReviewVectorizer类似,但有一个关键区别:字符串不是在空格上分割的。姓氏是一系列字符,每个字符在我们的词汇表中都是一个单独的标记。然而,我们将忽略序列信息,通过迭代字符串输入中的每个字符来创建输入的压缩one-hot向量表示。我们为之前未遇到的字符指定一个特殊的令牌,即UNK。由于我们仅从训练数据中实例化词汇表,而验证或测试数据中可能有唯一字符,因此在字符词汇表中仍然使用UNK符号。

3 姓氏分类器模型

SurnameClassifier是一个多层感知器(MLP)的具体实现。其核心功能包括两个线性层,第一个线性层将输入向量转换为一个中间向量,并对这个中间向量施加非线性变换,以引入非线性决策能力。第二个线性层负责将这个中间向量映射到最终的预测向量。

在模型的最后阶段,我们可以选择性地应用softmax操作。softmax操作确保了所有输出值的和为1,这相当于给出了“概率”的解释。之所以称之为可选,是因为这与我们选择的交叉熵损失函数的特性密切相关。在“损失函数”章节中我们已经讨论了交叉熵损失。重要的是要记住,交叉熵损失是针对多类分类任务而设计的理想损失函数。然而,在训练过程中计算softmax不仅计算量大,而且在某些情况下可能会导致不稳定性。

这种设计体现了以下优点:
1)灵活的模型架构:多层感知器能够灵活地适应不同的输入数据和分类任务。
2)非线性决策能力:通过非线性变换,模型能够捕捉输入数据中的复杂模式。
3)概率输出:可选的softmax操作提供了概率输出,有助于理解模型对每个类别的置信度。
4)高效的训练:通过避免不必要的softmax计算,模型在训练过程中更加高效且稳定。

三、机器翻译

2.2.1 编码器

在编码器部分,输入语言的词索引首先通过词嵌入层转换为词的表征。接着,这些表征被输入到多层门控循环单元(GRU)中。PyTorch的‘nn.GRU’在执行前向传播时会返回输出以及最终时间步的隐藏状态。这里的输出是指最后一层GRU在每个时间步的隐藏状态,而不包括输出层的计算。注意力机制利用这些隐藏状态作为键和值。

2.2.2 注意力机制

注意力机制是一种在深度学习中广泛使用的机制,它允许模型在处理输入序列时,能够根据输入的重要性动态地分配计算资源。通过捕捉序列中不同元素之间的相互关系,注意力机制能够有效地聚焦于序列的特定部分,从而提高模型对重要信息的敏感度和处理效率。在自然语言处理、计算机视觉等领域,注意力机制的应用极大地提升了模型在复杂任务中的表现。

注意力机制的输入包括查询项、键项和值项。设编码器和解码器的隐藏单元个数相同。这里的查询项为解码器在上一时间步的隐藏状态,形状为(批量大小, 隐藏单元个数);键项和值项均为编码器在所有时间步的隐藏状态,形状为(时间步数, 批量大小, 隐藏单元个数)。注意力机制返回当前时间步的背景变量,形状为(批量大小, 隐藏单元个数)。

2.2.3 含注意力机制的解码器

解码器的初始化隐藏状态直接取自编码器在最后一个时间步的隐藏状态,这要求编码器和解码器的循环神经网络在隐藏层数量和单元数量上保持一致。在解码器的正向传播过程中,我们首先利用注意力机制计算出当前时间步的背景向量。解码器的输入来源于输出语言的词索引,这些输入经过词嵌入层转换为表征形式,并与背景向量在特征维度上拼接。接着,我们利用拼接后的结果和前一个时间步的隐藏状态,通过门控循环单元计算出当前时间步的输出和隐藏状态。最终,通过一个全连接层,将输出转换成针对各个输出词的预测,输出形状为(批量大小,输出词典大小)。

2.2.4 评价翻译结果

机器翻译质量的评估通常采用BLEU(Bilingual Evaluation Understudy)指标。BLEU通过检查模型预测的序列中是否存在标签序列的任意子序列来进行评估。具体来说,对于一个长度为n的子序列,其精度定义为预测序列中与标签序列匹配的长度为n的子序列数量与预测序列中长度为n的子序列数量的比例。以标签序列为“A, B, C, D, E, F”和预测序列为“A, B, B, C, D”为例,p1=4/5, p2=3/4, p3=1/3, p4=0。BLEU的计算涉及标签序列和预测序列的词数,其公式为:

exp\left ( min\left ( 0,1-\frac{len_{label} }{len_{pred} } \right ) \right ) \prod_{n=1}^{k} p_{n}^{\frac{1}{2^{n} } }

其中,k是期望匹配的最大子序列长度。BLEU赋予匹配较长子序列更高的权重,因为匹配长子序列比匹配短子序列更具挑战性。当子序列的精度固定时,随着子序列长度的增加,其权重显著提升。为了惩罚较短输出带来的高精度值,公式中加入了惩罚系数。例如,当k=2时,标签序列为“A, B, C, D, E, F”,预测序列为“A, B”,尽管p1=p2=1,但由于惩罚系数的影响,BLEU值会接近0.14。

四、使用Transformer和PyTorch构建的日中机器翻译模型

1.获取平行语料库数据集


可以使用从JParaCrawl下载的日英平行语料库http://www.kecl.ntt.co.jp/icl/lirg/jparacrawl,该语料库被称为“由NTT创建的最大的公开可用英日平行语料库。它主要通过爬取网络并自动对齐平行句子而构建。”您还可以在此查阅相关论文。

2.准备分词器


与英语或其他字母文字语言不同,日语句子中没有空格来分隔单词。可以使用JParaCrawl提供的分词器,这些分词器是使用SentencePiece为日语和英语构建的。可以访问JParaCrawl网站下载它们。

在加载分词器后,您可以测试它们,例如,可以通过执行以下代码进行测试:

en_tokenizer.encode("All residents aged 20 to 59 years who live in Japan must enroll in public pension system.", out_type='str')

图3 en_tokenizer分词器分词处理

ja_tokenizer.encode("年金 日本に住んでいる20歳~60歳の全ての人は、公的年金制度に加入しなければなりません。", out_type='str')

图4  ja_tokenizer

3.使用分词器和原始句子构建TorchText的词汇表对象,并将句子转换为Torch张量


利用分词器和原始句子,我们接下来构建从TorchText导入的词汇表对象。这个过程可能需要几秒到几分钟,具体取决于数据集的大小和计算能力。不同的分词器也会影响构建词汇表所需的时间。

  1. def build_vocab(sentences, tokenizer):
  2. counter = Counter() # 创建一个Counter对象来计数词汇
  3. for sentence in sentences: # 遍历句子列表
  4. counter.update(tokenizer.encode(sentence, out_type=str)) # 将句子编码成单词序列,并更新计数器
  5. return Vocab(counter, specials=['<unk>', '<pad>', '<bos>', '<eos>']) # 创建一个Vocab对象,包含计数器和一个特殊词汇列表
  6. ja_vocab = build_vocab(trainja, ja_tokenizer) # 为日文数据构建词汇表
  7. en_vocab = build_vocab(trainen, en_tokenizer) # 为英文数据构建词汇表

在获得词汇表对象之后,就可以使用这些词汇表对象和分词器对象来构建训练数据对应的张量。

  1. def data_process(ja, en):
  2. # 初始化一个空列表,用于存储处理后的数据
  3. data = []
  4. # 使用zip函数遍历ja和en两个列表中对应的元素
  5. for (raw_ja, raw_en) in zip(ja, en):
  6. # 对每一对(ja, en)进行处理
  7. # 去除ja字符串末尾的换行符
  8. raw_ja = raw_ja.rstrip("\n")
  9. # 去除en字符串末尾的换行符
  10. raw_en = raw_en.rstrip("\n")
  11. # 将ja字符串编码为索引列表,使用ja_tokenizer进行编码,并转换为str类型
  12. ja_tokens = ja_tokenizer.encode(raw_ja, out_type=str)
  13. # 将索引列表转换为torch.long类型的tensor
  14. ja_tensor_ = torch.tensor([ja_vocab[token] for token in ja_tokens], dtype=torch.long)
  15. # 将en字符串编码为索引列表,使用en_tokenizer进行编码,并转换为str类型
  16. en_tokens = en_tokenizer.encode(raw_en, out_type=str)
  17. # 将索引列表转换为torch.long类型的tensor
  18. en_tensor_ = torch.tensor([en_vocab[token] for token in en_tokens], dtype=torch.long)
  19. # 将处理后的ja_tensor_和en_tensor_作为元组添加到data列表中
  20. data.append((ja_tensor_, en_tensor_))
  21. # 返回处理后的数据列表
  22. return data
  23. # 假设trainja和trainen是包含日文和英文文本对的列表
  24. train_data = data_process(trainja, trainen)

4.创建一个数据加载器对象,用于在训练过程中迭代


在这里,将批处理大小`BATCH_SIZE`设置为16,以防止出现“显存不足”的情况。但这取决于多种因素,比如机器的内存容量、数据的大小等。

  1. BATCH_SIZE = 8
  2. # 定义批量大小,这里设置为8,意味着每次处理8个样本
  3. PAD_IDX = ja_vocab['<pad>']
  4. # 定义填充索引,PAD_IDX是词汇表中用于填充的单词的索引,通常为"<pad>"
  5. BOS_IDX = ja_vocab['<bos>']
  6. # 定义开始索引,BOS_IDX是词汇表中表示句子开始的标记的索引,通常为"<bos>"
  7. EOS_IDX = ja_vocab['<eos>']
  8. # 定义结束索引,EOS_IDX是词汇表中表示句子结束的标记的索引,通常为"<eos>"
  9. def generate_batch(data_batch):
  10. # 定义一个函数,用于生成用于训练的数据批量
  11. ja_batch, en_batch = [], []
  12. # 初始化两个空列表,分别用于存放日语文本批量和英文文本批量
  13. for (ja_item, en_item) in data_batch:
  14. # 遍历每个数据批量中的日文和英文样本对
  15. ja_batch.append(torch.cat([torch.tensor([BOS_IDX]), ja_item, torch.tensor([EOS_IDX])], dim=0))
  16. # 将BOS索引添加到每个日文样本的开始,将EOS索引添加到每个日文样本的末尾
  17. # ja_item是原始的日文样本,通常是一个单词的索引列表
  18. # torch.cat用于将张量沿着指定的维度连接起来
  19. en_batch.append(torch.cat([torch.tensor([BOS_IDX]), en_item, torch.tensor([EOS_IDX])], dim=0))
  20. # 类似地,将BOS索引添加到每个英文样本的开始,将EOS索引添加到每个英文样本的末尾
  21. ja_batch = pad_sequence(ja_batch, padding_value=PAD_IDX)
  22. # 使用pad_sequence函数将日文批量的所有样本填充到相同长度,使用PAD_IDX作为填充值
  23. en_batch = pad_sequence(en_batch, padding_value=PAD_IDX)
  24. # 使用pad_sequence函数将英文批量的所有样本填充到相同长度,使用PAD_IDX作为填充值
  25. return ja_batch, en_batch
  26. # 返回填充后的日文和英文文本批量
  27. train_iter = DataLoader(train_data, batch_size=BATCH_SIZE,
  28. shuffle=True, collate_fn=generate_batch)

5.Sequence-to-sequence Transformer

Transformer是一种Seq2Seq模型,最早在《Attention is all you need》论文中提出,用于解决机器翻译任务。Transformer模型由编码器和解码器块组成,每个块包含固定数量的层。

编码器通过一系列多头注意力和前馈网络层处理输入序列。编码器的输出被称为记忆,与目标张量一起馈送给解码器。编码器和解码器通过教师强制技术进行端到端训练。

6.开始训练


经过准备必要的类和函数后,即可以开始训练模型。

  1. for epoch in tqdm.tqdm(range(1, NUM_EPOCHS+1)):
  2. # 使用tqdm来显示进度条
  3. start_time = time.time()
  4. # 调用train_epoch函数来训练模型一个epoch,并将transformer, train_iter, optimizer作为参数传入
  5. train_loss = train_epoch(transformer, train_iter, optimizer)
  6. end_time = time.time()
  7. # 打印训练信息
  8. print((f"Epoch: {epoch}, Train loss: {train_loss:.3f}, "
  9. f"Epoch time = {(end_time - start_time):.3f}s"))
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号