Baseline代码相关解读
是否需要使用self判定的标准是是否在类中
在 Python 中,self
是一个类的实例方法中的参数,用于指代调用该方法的实例本身。在代码示例中,self
并不是必需的,因为这个示例代码不是类的一部分,只是一个独立的函数调用。让我们逐步解释这些区别。
类中的方法与独立的函数
-
独立的函数调用:
from torchtext.data.utils import get_tokenizer # 获取 'basic_english' 分词器 tokenizer = get_tokenizer('basic_english') # 示例文本 text = "Hello, world! This is an example sentence." # 使用分词器分割文本 tokens = tokenizer(text) print(tokens)
在这个例子中,
tokenizer
是通过调用get_tokenizer('basic_english')
函数得到的。这个代码片段没有涉及到类和实例,所以没有self
的概念。它只是简单地调用函数并使用返回的分词器进行文本分割。 -
类中的方法:
如果我们在一个类中实现同样的功能,就需要使用
self
来指代实例的属性或方法。例如:from torchtext.data.utils import get_tokenizer class TextProcessor: def __init__(self): self.tokenizer = get_tokenizer('basic_english') # 初始化实例的分词器属性 def tokenize(self, text): return self.tokenizer(text) # 使用实例的分词器属性进行分词 # 创建 TextProcessor 的实例 processor = TextProcessor() # 示例文本 text = "Hello, world! This is an example sentence." # 使用实例的方法分割文本 tokens = processor.tokenize(text) print(tokens)
在这个例子中:
__init__
方法:类的构造方法,用于初始化实例。这里用self.tokenizer
来存储分词器。self.tokenizer
:指代实例的分词器属性。tokenize
方法:定义了一个实例方法,用于调用实例的分词器属性进行分词。
为什么这里没有 self
在独立的函数调用中,如下面的例子,不涉及到类和实例,所以不需要使用 self
:
from torchtext.data.utils import get_tokenizer
# 获取 'basic_english' 分词器
tokenizer = get_tokenizer('basic_english')
# 示例文本
text = "Hello, world! This is an example sentence."
# 使用分词器分割文本
tokens = tokenizer(text)
print(tokens)
这个代码片段直接使用函数调用来获得分词器,并使用分词器对文本进行分割,没有涉及到类和实例,所以没有 self
的概念。
类与实例中的 self
当代码被封装到一个类中时,self
用于引用类的实例,并访问实例的属性和方法。例如:
class TextProcessor:
def __init__(self):
self.tokenizer = get_tokenizer('basic_english')
def tokenize(self, text):
return self.tokenizer(text)
在这里,self.tokenizer
是一个实例属性,它存储了分词器。tokenize
方法通过 self.tokenizer
来调用这个实例属性。
总结
- 独立的函数调用:直接调用函数,返回分词器并使用它,不涉及
self
。 - 类中的方法:通过类和实例来封装功能,需要使用
self
来引用实例的属性和方法。
希望这些解释能帮助你理解为什么在独立的函数调用中不需要使用 self
,而在类的方法中需要使用 self
。
列表推导式
[word for word, _ in zh_vocab.most_common(10000)]
这种语法叫做列表推导式(List Comprehension),是 Python 中创建列表的一种简洁方式。
详细解释
列表推导式的一般形式是:
[expression for item in iterable if condition]
在你的例子中:
[word for word, _ in zh_vocab.most_common(10000)]
具体解释如下:
-
zh_vocab.most_common(10000):
- 这个方法返回一个包含最多 10000 个
(word, count)
元组的列表,其中word
是词汇,count
是该词汇在数据集中出现的次数。这些元组按count
的降序排列。
- 这个方法返回一个包含最多 10000 个
-
for word, _ in zh_vocab.most_common(10000):
- 这是一个 for 循环,它遍历
zh_vocab.most_common(10000)
返回的所有(word, count)
元组。 - 在这个循环中,每个元组会被解包为两个变量:
word
和_
。word
是词汇,_
是计数(在 Python 中,_
通常表示一个不需要使用的变量)。
- 这是一个 for 循环,它遍历
-
[word for word, _ in zh_vocab.most_common(10000)]:
- 这个列表推导式的作用是创建一个新列表,其中包含
zh_vocab.most_common(10000)
返回的每个元组中的word
部分,忽略count
部分。
- 这个列表推导式的作用是创建一个新列表,其中包含
作用总结
[word for word, _ in zh_vocab.most_common(10000)]
创建了一个包含 zh_vocab
中最常见的前 10000 个词汇的列表,忽略每个词汇的计数。
具体例子
假设 zh_vocab.most_common(5)
返回的结果如下:
[('你', 1000), ('好', 800), ('我', 600), ('是', 400), ('的', 200)]
那么列表推导式:
[word for word, _ in zh_vocab.most_common(5)]
将创建以下列表:
['你', '好', '我', '是', '的']
这种方式非常简洁且易于理解,特别是在处理数据时,可以快速地从一个复杂的数据结构中提取出所需的部分。
字典的get方法:
self.en_word2idx.get(word, self.en_word2idx['<sos>'])
这个部分将每个单词转换为对应的索引。让我们详细拆解这个方法:
self.en_word2idx
是一个字典,字典的键是单词,值是该单词对应的索引。.get(word, self.en_word2idx['<sos>'])
是字典的get
方法,它尝试获取键word
对应的值(索引)。如果word
不在字典中,它将返回第二个参数self.en_word2idx['<sos>']
,即<sos>
(开始标记)的索引。
这样,代码确保了即使某个单词不在词汇表中,也会有一个默认的索引(<sos>
)作为替代。
nn.Embedding
nn.Embedding
默认情况下并不是预训练的。它在初始化时会随机生成一个嵌入矩阵,然后在训练过程中学习每个单词的嵌入向量。让我们详细解释它的工作原理和训练过程。
nn.Embedding
的工作原理
初始化嵌入矩阵
当你创建一个 nn.Embedding
对象时,会初始化一个嵌入矩阵。这个矩阵的每一行表示一个单词的嵌入向量,矩阵的行数是词汇表大小,列数是嵌入向量的维度。
import torch
import torch.nn as nn
# 假设 input_dim = 10, emb_dim = 4
embedding = nn.Embedding(10, 4)
嵌入矩阵在初始化时通常使用均匀分布或正态分布进行随机填充。例如,初始化后的嵌入矩阵可能如下所示:
[
[ 0.1, -0.2, 0.3, 0.4],
[-0.1, 0.2, -0.3, -0.4],
[ 0.5, 0.6, 0.7, 0.8],
...
]
索引查找
给定一个包含单词索引的序列,nn.Embedding
会查找嵌入矩阵中对应的行,并返回这些行作为嵌入向量序列。例如:
# 输入序列
input_seq = torch.tensor([2, 5, 7])
# 获取嵌入向量
embedded_seq = embedding(input_seq)
print(embedded_seq)
如果 input_seq
是 [2, 5, 7]
,nn.Embedding
会返回嵌入矩阵的第 2、5、7 行。
如何转换整数序列
当调用 embedding(input_seq)
时,PyTorch 会在内部执行以下操作:
- 输入:
input_seq
是一个包含单词索引的整数张量,形状为[sequence_length]
,例如[2, 5, 7]
。 - 索引查找:PyTorch 在嵌入矩阵中查找这些索引对应的行。
- 返回嵌入向量:查找到的行组成一个新的张量,形状为
[sequence_length, emb_dim]
。
训练过程
嵌入矩阵的值在训练过程中通过反向传播算法进行更新。具体步骤如下:
-
前向传播:
- 输入序列通过嵌入层转换为嵌入向量序列。
- 嵌入向量序列作为神经网络的输入进行前向传播,计算出模型的预测输出。
-
计算损失:
- 使用模型的预测输出和真实标签计算损失。
-
反向传播:
- 通过反向传播算法计算损失相对于模型参数的梯度。
- 梯度会传递回嵌入层,更新嵌入矩阵的权重。
-
更新权重:
- 使用优化器更新模型参数,包括嵌入矩阵。
总结
nn.Embedding
不是预训练的:它在初始化时随机生成嵌入矩阵。- 索引查找:给定一个包含单词索引的序列,
nn.Embedding
会查找嵌入矩阵中对应的行,并返回这些行作为嵌入向量序列。 - 训练过程:嵌入矩阵的权重在训练过程中通过反向传播进行更新,逐步学习每个单词的嵌入向量。
这种方式允许模型在训练过程中根据任务的具体需求学习到最适合的词嵌入,从而提高模型的性能。
nn.GRU
GRU(Gated Recurrent Unit)是一种改进的循环神经网络(RNN),旨在解决传统 RNN 的梯度消失和梯度爆炸问题。GRU 通过引入门控机制来捕捉序列中的长期依赖关系。
GRU 层的基本概念
GRU 是一种特殊的 RNN,它通过更新门(update gate)和重置门(reset gate)来控制信息的流动。与 LSTM 不同的是,GRU 只有两个门,比 LSTM 更简单但效果相近。
GRU 结构
- 重置门(Reset Gate):控制前一时刻的隐藏状态有多少信息需要遗忘。
- 更新门(Update Gate):控制当前时刻的隐藏状态有多少信息需要保留。
具体代码解释
self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)
参数解释
-
emb_dim
(输入特征的维度)- 这是 GRU 层的输入维度,即输入序列的每个时间步的特征向量的维度。在这个上下文中,
emb_dim
是嵌入层的输出维度。
- 这是 GRU 层的输入维度,即输入序列的每个时间步的特征向量的维度。在这个上下文中,
-
hid_dim
(隐藏层的维度)- 这是 GRU 层的隐藏状态的维度。每个时间步的隐藏状态会有
hid_dim
个特征。
- 这是 GRU 层的隐藏状态的维度。每个时间步的隐藏状态会有
-
n_layers
(GRU 层的数量)- 这是堆叠的 GRU 层的数量。多层 GRU 允许模型捕捉更复杂的序列模式。
-
dropout
(Dropout 比率)- 在各层之间应用 Dropout,用于防止过拟合。
-
batch_first=True
(批次维度放在第一位)- 指定输入和输出张量的形状,批次大小放在第一位。这意味着输入和输出张量的形状为
[batch_size, seq_len, feature_dim]
。
- 指定输入和输出张量的形状,批次大小放在第一位。这意味着输入和输出张量的形状为
GRU 的前向传播过程
当你将输入序列传递给 GRU 层时,它会进行以下步骤:
-
输入处理
- 输入序列的形状为
[batch_size, seq_len, emb_dim]
。
- 输入序列的形状为
-
计算每个时间步的隐藏状态
- GRU 使用当前时间步的输入和前一时间步的隐藏状态来计算当前时间步的隐藏状态。
-
输出和隐藏状态
- 输出:每个时间步的输出序列,形状为
[batch_size, seq_len, hid_dim]
。 - 隐藏状态:最后一个时间步的隐藏状态,形状为
[n_layers, batch_size, hid_dim]
。
- 输出:每个时间步的输出序列,形状为
具体实现步骤
1. 初始化 GRU 层
self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout, batch_first=True)
2. 前向传播方法
def forward(self, src):
# src shape: [batch_size, src_len]
embedded = self.dropout(self.embedding(src))
# embedded shape: [batch_size, src_len, emb_dim]
outputs, hidden = self.rnn(embedded)
# outputs shape: [batch_size, src_len, hid_dim]
# hidden shape: [n_layers, batch_size, hid_dim]
return outputs, hidden
-
输入序列
src
src
的形状为[batch_size, src_len]
。- 经过嵌入层和 Dropout 后,得到
embedded
,形状为[batch_size, src_len, emb_dim]
。
-
传递给 GRU 层
embedded
作为输入传递给 GRU 层,得到outputs
和hidden
。outputs
包含每个时间步的输出,形状为[batch_size, src_len, hid_dim]
。hidden
是最后一个时间步的隐藏状态,形状为[n_layers, batch_size, hid_dim]
。
总结
- GRU 层的作用:GRU 层通过更新门和重置门控制信息流动,有效捕捉序列中的长期依赖关系,解决传统 RNN 的梯度消失问题。
- 参数:
emb_dim
:输入特征的维度,即嵌入向量的维度。hid_dim
:隐藏状态的维度。n_layers
:GRU 层的数量。dropout
:Dropout 比率,用于防止过拟合。batch_first=True
:指定输入和输出的张量形状。
通过这种方式,GRU 层能够处理序列数据,捕捉其中的时序信息,为后续的解码或其他处理提供有用的特征表示。
Baseline流程
环境配置
-
torchtext :是一个用于自然语言处理(NLP)任务的库,它提供了丰富的功能,包括数据预处理、词汇构建、序列化和批处理等,特别适合于文本分类、情感分析、机器翻译等任务
-
jieba:是一个中文分词库,用于将中文文本切分成有意义的词语
-
sacrebleu:用于评估机器翻译质量的工具,主要通过计算BLEU(Bilingual Evaluation Understudy)得分来衡量生成文本与参考译文之间的相似度.
-
spacy:是一个强大的自然语言处理库,支持70+语言的分词与训练
在此我们使用其用于英文的 tokenizer(分词,就是将句子、段落、文章这种长文本,分解为以字词为单位的数据结构,方便后续的处理分析工作)
!pip install torchtext
!pip install jieba
!pip install sacrebleu
!python -m spacy download en_core_web_trf
数据预处理
预处理阶段通常包括多个步骤,旨在清理、标准化和转换数据,使之适合模型训练
清洗和规范化数据
- 去除无关信息:删除HTML标签、特殊字符、非文本内容等,确保文本的纯净性(本赛题的训练集中出现了非常多的脏数据,如“Joey. (掌声) (掌声) 乔伊”、“Thank you. (马嘶声) 谢谢你们”等这种声音词)
- 统一格式:转换所有文本为小写,确保一致性;标准化日期、数字等格式。
- 分句和分段:将长文本分割成句子或段落,便于处理和训练。
分词
将句子分解成单词或词素(构成单词的基本组成部分,一个词素可以是一个完整的单词,也可以是单词的一部分,但每一个词素都至少携带一部分语义或语法信息),这是NLP中最基本的步骤之一。我们这里使用了使用jieba 对中文进行分词,使用spaCy对英文进行分词。
构建词汇表和词向量
- 词汇表构建:从训练数据中收集所有出现过的词汇,构建词汇表,并为每个词分配一个唯一的索引。
- 词向量:使用预训练的词向量或自己训练词向量,将词汇表中的词映射到高维空间中的向量,以捕捉语义信息(当前大模型领域训练的 embedding 模型就是用来完成此任务的)。
序列截断和填充
- 序列截断:限制输入序列的长度,过长的序列可能增加计算成本,同时也可能包含冗余信息。
- 序列填充:将所有序列填充至相同的长度,便于批量处理。通常使用
标记填充。
添加特殊标记
- 序列开始和结束标记:在序列两端添加
(Sequence Start)和 (Sequence End)标记,帮助模型识别序列的起始和结束。 - 未知词标记:为不在词汇表中的词添加
(Unknown)标记,使模型能够处理未见过的词汇。
数据增强
- 随机替换或删除词:在训练数据中随机替换或删除一些词,增强模型的鲁棒性。
- 同义词替换:使用同义词替换原文中的词,增加训练数据的多样性。
数据分割
将数据划分为训练集、验证集和测试集,分别用于模型训练、参数调整和最终性能评估
编码器-解码器模型
在日常生活中,针对编码器解码器这个概念我们可以看到有:
- 在电视系统上为了便于视频的传播,会使用各种编码器将视频编码成数字信号,在客户端,相应的解码器组件会把收到的数字信号解码为视频。
- 电话通过对声波和电信号进行相互转换,达到传递声音的目的。
针对神经网络的翻译过程,我们可以这样子理解:
针对给定的中文句子“我/对/你/感到/满意”,编码器会将这句话编码成一个实数向量(0.2, −1, 6, 5, 0.7, −2),这个向量就是源语言句子的“表示”结果。虽然有些不可思议,但是神经机器翻译模型把这个向量等同于输入序列。向量中的数字并没有实际的意义,然而解码器却能从中提取到源语言句子中所包含的信息。也有研究人员把向量的每一个维度看作是一个“特征”,这样源语言句子就被表示成多个“特征”的联合,而且这些特征可以被自动学习。有了这样的源语言句子的“表示”,解码器可以把这个实数向量作为输入,然后逐词生成目标语言句子“I am satisfied with you”。
编码器
编码器由词嵌入层和中间网络层组成:
- 当输入一串单词序列时,词嵌入层(embedding)会将每个单词映射到多维实数表示空间,这个过程也被称为词嵌入。
- 之后中间层会对词嵌入向量进行更深层的抽象,得到输入单词序列的中间表示。中间层的实现方式有很多,比如:循环神经网络、卷积神经网络、自注意力机制等都是模型常用的结构。
解码器
解码器的结构基本上和编码器是一致的,在基于循环神经网络的翻译模型中,解码器只比编码器多了输出层,用于输出每个目标语言位置的单词生成概率,而在基于自注意力机制的翻译模型中,除了输出层,解码器还比编码器多一个编码解码注意力子层,用于帮助模型更好地利用源语言信息。
RNN编码器-解码器
以基于循环神经网络的机器翻译模型为例。
左侧为编码器部分,源语言单词按照其在文本序列中的先后顺序被依次送入到循环神经网络(RNN)当中。在每个时间步 t 中,模型依据送入的源语言单词$$x_{t} $$对应修改并维护其模型内部的隐状态 $$h_{t}$$,这个隐状态编码了输入的源语言序列前 t 个时刻的所有必要信息。按照这种方式当 m 个输入全部被送入到编码器之后,所对应的 $$h_{m}$$可以认为包含了源语言序列的所有信息。
blob:https://datawhaler.feishu.cn/734451ef-196c-48b7-b501-3042b2b37fc4
右半部分是 RNN 解码器部分,它接收编码器输出的编码源语言句子信息的向量 $$h_{m}$$作为初始隐状态 $$s_{0}$$。由于 RNN 的循环过程在每个时间步都要求一个输入单词,为了启动解码过程,一般会使用一个保留的特殊符号 “[Start]” 作为翻译开始的标记送入到 RNN 解码器当中并解码出目标语言序列的第一个单词 $$z_{1}$$。接下来,$$z_{1}$$ 会作为下一个时刻的输入被送入到循环神经网络当中,并按照不断迭代产生后续的预测。由于目标语言序列的长度无法被提前预知,因此使用另一个保留符号 “[Stop]” 作为预测结束的标志。当某一个时刻 t 预测出的目标语言单词为 zt =“[Stop]” 时,解码过程动态地停止。在上述过程当中,主要涉及到两步运算,第一步是 RNN 接收前一时刻隐状态 $$s_{t-1}$$ 并依据当前时刻输入 $$z_{t-1}$$(目标语言单词 $$z_{t-1}$$ 对应的语义嵌入)对隐状态进行维护并生成$$s_{t}$$的运算过程,第二步是依据当前时刻隐状态生成目标语言单词的过程:
blob:https://datawhaler.feishu.cn/2ef84fa6-4299-42ff-aed0-ae058cb773cd
但是仅仅使用一个定长的向量 $$h_{m}$$ 编码整个源语言序列。这对于较短的源语言文本没有什么问题,但随着文本序列长度的逐渐加长,单一的一个向量 hm 可能不足以承载源语言序列当中的所有信息。
当文本长度在 20 个单词以内时,单一向量能够承载源语言文本中的必要信息。随着文本序列的进一步增加,翻译性能的评价指标 BLEU 的值就开始出现明显地下降。因此,这就启发我们使用更加有效地机制从编码器向解码器传递源语言信息,这就是接下来要讲到的注意力机制。
Attention Mechanism
注意力机制的引入使得不再需要把原始文本中的所有必要信息压缩到一个向量当中
blob:https://datawhaler.feishu.cn/eb85be7f-e0b8-46d5-b3a5-596ac4d9433a
注意力机制允许解码器在生成每个输出词时,关注编码器产生的所有中间状态,从而更好地利用源序列的信息。具体来说,给定源语言序列经过编码器输出的向量序列 $$h_{1},h_{2},h_{3},...,h_{m}$$,注意力机制旨在依据解码端翻译的需要,自适应地从这个向量序列中查找对应的信息。
翻译质量评价
人们在使用机器翻译系统时需要评估系统输出结果的质量。这个过程也被称作机器翻译译文质量评价,简称为译文质量评价(Quality Evaluation of Translation)。在机器翻译的发展进程中,译文质量评价有着非常重要的作用。不论在系统研发的反复迭代中,还是在诸多的机器翻译应用场景中,都存在大量的译文质量评价环节。从某种意义上说,没有译文质量评价,机器翻译也不会发展成今天的样子。比如,本世纪初研究人员提出了译文质量自动评价方法 BLEU(Bilingual Evaluation Understudy)(Task 1知识文档已详细介绍过)。该方法使得机器翻译系统的评价变得自动、快速、便捷,而且评价过程可以重复。正是由于 BLEU 等自动评价方法的提出,机器翻译研究人员可以在更短的时间内得到译文质量的评价结果,加速系统研发的进程。
blob:https://datawhaler.feishu.cn/4357d981-60e1-411b-a384-5d5be9901a59
标签:dim,嵌入,AI,self,Datawhale,GRU,机器翻译,序列,向量 From: https://www.cnblogs.com/cauwj/p/18308407