translation.py
- 在
train
的时候如果不指定sourse language
和target language
,那么,language pair
必须是这样的形式
,. - .(...).idx split
是train
valid
test
- 也就是说
language pair
要在第一个.
后面,以及用-
隔开两个语言对
parts = filename.split('.')
if len(parts) >= 3 and len(parts[1].split('-')) == 2:
return parts[1].split('-')
- 加载的词典的命名必须是
dict.{lang1}.txt
src_dict = cls.load_dictionary(os.path.join(args.data[0], 'dict.{}.txt'.format(args.source_lang)))
tgt_dict = cls.load_dictionary(os.path.join(args.data[0], 'dict.{}.txt'.format(args.target_lang)))
- 其实发现
translaion task
其实没有什么东西,全是一些如何加载预训练模型,以及如何加载数据,如何将数据处理成翻译需要的形式,因为主要是继承fairseq_task
的功能,而fairseq_task
本身就是一个seq2seq
,因此只用translation.py
实现加载数据什么的就行了。
fairseq_task.py
- 首先可以看到一个比较有意思的点,是创建词典的时候说词典的大小必须为
8
的倍数这样nvidia
能够加速。
def build_dictionary(cls, filenames, workers=1, threshold=-1, nwords=-1, padding_factor=8):
"""Build the dictionary
Args:
filenames (list): list of filenames
workers (int): number of concurrent workers
threshold (int): defines the minimum word count
nwords (int): defines the total number of words in the final dictionary,
including special symbols
padding_factor (int): can be used to pad the dictionary size to be a
multiple of 8, which is important on some hardware (e.g., Nvidia
Tensor Cores).
"""
d = Dictionary()
for filename in filenames:
Dictionary.add_file_to_dictionary(filename, d, tokenizer.tokenize_line, workers)
d.finalize(threshold=threshold, nwords=nwords, padding_factor=padding_factor)
return d
- 里面的
train_step
,可以看到task
还承担了调用train valid test
,以及inference(decode)
的过程,里面获得loss
等等是通过criterion
来获得的。 -
sample
是一个minibatch
,就是fairseq
的translation task
类里面实现的的读取数据的操作。
def train_step(self, sample, model, criterion, optimizer, ignore_grad=False):
"""
Do forward and backward, and return the loss as computed by *criterion*
for the given *model* and *sample*.
Args:
sample (dict): the mini-batch. The format is defined by the
:class:`~fairseq.data.FairseqDataset`.
model (~fairseq.models.BaseFairseqModel): the model
criterion (~fairseq.criterions.FairseqCriterion): the criterion
optimizer (~fairseq.optim.FairseqOptimizer): the optimizer
ignore_grad (bool): multiply loss by 0 if this is set to True
Returns:
tuple:
- the loss
- the sample size, which is used as the denominator for the
gradient
- logging outputs to display while training
"""
model.train()
loss, sample_size, logging_output = criterion(model, sample)
if ignore_grad:
loss *= 0
optimizer.backward(loss)
return loss, sample_size, logging_output
-
inference_step
的时候是通过generator.generate
来产生数据的。
def inference_step(self, generator, models, sample, prefix_tokens=None):
with torch.no_grad():
return generator.generate(models, sample, prefix_tokens=prefix_tokens)
-
criterion
是在FairseqCriterion
中定义的,但是这个task
对应的criterion
是在哪里呢? -
train_step
,test_step
,valid_step
是在task
中定义的,然后通过train.py
中创建trainer
来调用的。 -
inference_step
调用的是generatre
,这个位于sequence_generator.py
里面。
sequence_generator.py
- 可以选择使用
sampling
或者是beam search
或者是sample topk
,temperature
控制采样的概率>1
更趋近于均匀采样,<1
更趋近于尖锐的采样。
sampling (bool, optional): sample outputs instead of beam search
(default: False)
sampling_topk (int, optional): only sample among the top-k choices
at each step (default: -1)
sampling_temperature (float, optional): temperature for sampling,
where values >1.0 produces more uniform sampling and values
<1.0 produces sharper sampling (default: 1.0)
-
generate
的时候可以传入一个models
然后获得EnsembleModel
。 - 要搞清楚
encoder_input
是什么,首先要搞清楚sample
是什么。
encoder_input = {
k: v for k, v in sample['net_input'].items()
if k != 'prev_output_tokens'
}
- 没有在循环里面说明是一次得到所有的
encoder
输出。
encoder_outs = model.forward_encoder(encoder_input)
- 还有这个
encoder_outs = model.reorder_encoder_out(encoder_outs, new_order)
是干啥的?是根据new_order
顺序对encdoer_outs
重新排序的 - 最为重要的函数是
finalize_hypos
,有一个参数是step
,是当前的时间步,具体功能是? -
model.forward_decoder
是最为关键的一步,根据之前所生成的tokens
和encoder
的输出得到当前步每个单词的概率,以及atten_scores
,这里应该写了teacher forcing
。 - 在这里可以看到
forward_decoder
是一个step
一个step进行的,而encoder
的时候是全部的encoder
出来。 - 所以说控制是否生成
unk
只用decoder
的时候输出概率的时候将lprobs[:,self.unk] -= float('inf')
即可。
reorder_state = None
batch_idxs = None
for step in range(max_len + 1): # one extra step for EOS marker
# reorder decoder internal states based on the prev choice of beams
if reorder_state is not None:
if batch_idxs is not None:
# update beam indices to take into account removed sentences
corr = batch_idxs - torch.arange(batch_idxs.numel()).type_as(batch_idxs)
reorder_state.view(-1, beam_size).add_(corr.unsqueeze(-1) * beam_size)
model.reorder_incremental_state(reorder_state)
model.reorder_encoder_out(encoder_outs, reorder_state)
lprobs, avg_attn_scores = model.forward_decoder(tokens[:, :step + 1], encoder_outs)
lprobs[:, self.pad] = -math.inf # never select pad
lprobs[:, self.unk] -= self.unk_penalty # apply unk penalty
- 在
search_step
这一行得到具体的分数,indices
和beams
,search_step
就是根据本次decoder
出现的概率在inference
的时候使用beam search
得到的词以及得到的累计的分数score
。
- 最后
generator
函数返回的是根据句子的score
由大到小排序的一个列表,列表里面是一个字典,字典中有一项是score
是这个生成出来句子的得分,就是beam search
得到每个位置词概率相加的分数。
# bsz是batch size
# list of completed sentences
finalized = [[] for i in range(bsz)]
finished = [False for i in range(bsz)]
worst_finalized = [{'idx': None, 'score': -math.inf} for i in range(bsz)]
...
# the return of generator in sequence_generator.py
# sort by score descending
for sent in range(len(finalized)):
finalized[sent] = sorted(finalized[sent], key=lambda r: r['score'], reverse=True)
return finalized
- 找到了生成的概率,所以要看一下
search.step
生成的是个什么东西了。
-
score
是每个source word
截止当前步总的分数。
-
token
起初的时候填充的都是pad
,第0
个位置是eos
。
-
generate
还有一个比较有意思的参数是prefix_tokens
用来强制开始时候产生开始的word
。 - 往
token
里面填充新的单词是通过swap buffers
来实现的,不知道这么实现是为了什么??
- 想知道
scores
是干啥的,首先要看search.step
里面
- 从这里我们可以看到
lprobs
是bsz ,beam_size
,每个句子产生的self.vocab_size
的对应于每个次的概率。score
是根据cand_scores
而来的,而cand_scores
是search.step
得到的分数。
search.step
首先厘清trainer.py,task和model和train的关系
task
-
translation task
里面定义了加载数据load_dataset
,加载模型的方法load_pretrained_model
,setup_task
初始化task
(就是加载词典)。 -
fairseq_task
里面定义了build_generator
,train_step
,valid_step
,inference_step
,get_batch_iterator
。
train.py
- 在
train.py
里面,使用epoch_itr = task.get_batch_iterator
得到训练数据,model = task.build_model(args)
得到模型,criterion = task.build_criterion(args)
得到训练器。 - 然后将每次更新交给函数
train
来做,valid
和lr
调度的时候由具体的函数来做。原来还可以通过args.max_epoch
设置训练最多的epoch
。
max_epoch = args.max_epoch or math.inf
max_update = args.max_update or math.inf
- 原来
subset
是这么用的用,
隔开测试多个文件,valid_subsets = args.valid_subset.split(',')
- 在
train
函数里面首先获得训练的数据,train
函数是一次训练一个epoch
,里面的每个batch
的训练是使用trainer来调用的。
- 很好奇梯度累计是哪里实现的,
trainer.train_step
是一次实现一个batch
。 -
valide
的结构和train
差不多
trainer.py
-
在这初始化时候将
model.half
以及model.cuda
-
trainer
里面是通过调用task.train_step
来进行前向和后向传递的。
-
valid
也是调用task.valid_step
。
-
所以说
trainer
就是task.train_step
的一个皮,而task.train_step
里面主要是由criterion
和optimizer
来实现的,梯度累计是在optimizer
里面实现的
criterion
里面的实现也是先将数据通过model forward
以后得到net_output
这个应该是logit
,然后得到log probs
-
以及得到的
net_output = model(**sample['net_input'])
是否里面有teacher forcing
,有的,得到的net_output
直接是decoder
每个位置数据的单词以及所有单词的概率,以及一些其他信息比如decoder
句子的得分。
-
所以说这个
target
是干啥的target = model.get_targets(sample, net_output).view(-1)
因此所有的问题现在回到
model
上来了。
model
fairseq_model
BaseFairseqModel
- 从
BaseFairseqModel
首先看一下fairseq_model
有什么功能。 - 从
FairseqModel
,可以看到基础的架构是一个encoder
一个decoder
。
- 所以现在的
prev_output_tokens
是啥,根据criterion
里面的net_output = model(**sample['net_input'])
可以看到看就是teacher forcing
需要的正确答案,或者是推断的时候之前步的decoder
输出。
FairseqMultiModel
- 就是多个
encoder_decoder
结合起来,这里有share-embeddings
。
-
forward
也是得到了多个decoder
的output
。
- 可以看到
LanguageModel
只有一个decoder
。
-
src_tokens
是训练的数据[[1,3,5,3],[123,2,2,3],[12,2,3,1]]
,得到的是一个句子序列,只不过每个单词的位置是所有词表的概率。
TransformerModel
- 可以通过参数
args.decoder_embed_path
和args.encoder_embed_path
加载预训练好的embedding
具体是通过args.decoder_embed_path
。
-
seq2seq
transformerEncoder
-
forward
中搭建多层的layer
,只用nn.ModuleList
然后加入layer
的子组件。
- 原始的
embedding
加上位置的embedding
然后做dropout
。
-
transformerEncoderLayer
,layer_norm
加的也太频繁了吧,基本上每做一个操作都要加一个layer_norm
。
- 根据新的
index
对encoder_out
进行重新排列
transformerEncoderLayer
- 关于先
residual
后layernorm
,原始的论文和t2t
的不一样,fairseq
中的实现是原始论文,但是可以通过参数args.encoder_normalize_before=True
来切换到t2t
的模式。
- 每一层有两个
layernorm
,layernorm
和batchnorm
一样是有参数的。
- 原来画了两个真是两层
fead_forward
,fead_forward
后面必跟一个dropout
,而且两层fead_forward
只有一个有relu
,self.relu_dropout
和self.dropout
数值上有什么不同么? - 所以说一层有两个子层,第一个子层主要是
multi-head attention
,第二个子层是两层feed_forward
,encoder
返回的是出来好的x
,就是embedding
矩阵。
-
mult-head attention
为了避免乘性attention
出现极端值因此进行了scale
- 实现
qkv
的一点细节
-
都是来自一个大的矩阵,然后取出自己想要的部分
- 原来
torch
里面的parameter
可以向操作numpy
一样。
self.in_proj_weight = Parameter(torch.Tensor(3 * embed_dim, embed_dim))
-
multi-head attention
也不是每个头乘以不同的矩阵,而是乘以一个大的矩阵,然后reshape
成最后一维度是head_dim
,然后乘以同样reshape
后的k
,
- 通过
masked_fill
将有些位置的attention
填充成-inf
这样就概率就为0
-
torch.bmm
以后reshape
成原始attention
的大小,然后过一个linear
- 就是一系列的
reshape
操作,先加大bs
维度,得到bs
维度很大,但是最后一维是head_dim
的矩阵,然后reshape
成原始的最后一维是embed_dim
矩阵。
TransformerDecoderLayer
-
transformerDecoder
的输入是pre_output_tokens+ position embedding
- 而训练中的
pre_output_tokens
是真实的数据,采用了teacher forcing
的技术。
-
transformerDecoderLayer
的输入,x
是已经embedding
化的 -
decoder
第一部分是一个self attention
layer norm
和residual
- 第二个
attention
是以自己为query
,以encoder
的output
作为k和v
进行attention
- 剩下的操作,可以看到和
encoder
一样也是一个linear
加上了relu
一个没有加relu
。