同傳翻譯的“前世今生”
同聲傳譯,簡稱“同傳”,是指譯員在不打斷講話者講話的情況下,不間斷地將內容口譯給聽眾的一種翻譯方式,同聲傳譯員通過專用的設備提供即時的翻譯,這種方式適用于大型的研討會和國際會議,同聲傳譯效率高,能保證演講或會議的流暢進行。
同聲傳譯員一般收入較高,但是成為同聲傳譯的門檻也很高。當前,世界上95%的國際高端會議都采用同聲傳譯的方式。第二次世界大戰結束后,設立在德國的紐倫堡國際軍事法庭在審判法西斯戰犯時,首次采用同聲傳譯,這也是世界上第一次在大型國際活動中采用同聲傳譯。
不過目前人工同傳翻譯存在著以下局限之處:
精力體力的挑戰:與交替傳譯不同的是,同傳需要邊聽、邊記、邊翻,同步進行,對譯員的要求極高。由于需要高度集中注意力,人類同傳一般兩人一組,且每隔20多分鐘就要換人休息,對人的精力、體力都是極大的挑戰。 譯出率不高:據統計,同傳譯員的譯出率一般在60%-70%左右。譯出率不高的原因,一般由于未聽清或者難翻譯,人類譯員通常會選擇性的忽略某些句子,保證總體上的準確率和實時性。 全球同傳譯員稀缺:由于苛刻的要求,全球同傳譯員稀缺,只有幾千人。與巨大的市場需求相比,人才嚴重短缺。
相比之下機器同聲傳譯的優勢有:機器最大的優勢是不會因為疲倦而導致譯出率下降,能將所有“聽到”的句子全部翻譯出來,這使得機器的“譯出率”可以達到100%,遠高于人類譯員的60%-70%。同時,在價格上也占有優勢。
本期項目我們PaddleNLP團隊為大家帶來一個機器同傳翻譯demo,它的翻譯效果如何呢?讓我們先睹為快吧!
語音同傳Demo
文本同傳Demo
是不是看起來效果很不錯!或許大家會問,實現起來復雜嗎?這里小編隆重給大家推薦一個好用的工具——PaddleNLP!即使是零基礎課程學員,通過PaddleNLP,只需經過簡單的一些操作也能夠輕松將它實現,如果你也感興趣,那就趕快來試試吧!
機器同傳demo教程: https://github.com/PaddlePaddle/PaddleNLP/blob/develop/education/day09.md
本項目是基于機器翻譯領域主流模型 Transformer網絡結構的同傳模型STACL的PaddlePaddle 實現,包含模型訓練,預測以及使用自定義數據等內容。用戶可以基于發布的內容搭建自己的同傳翻譯模型。
《STACL: Simultaneous Translation with Implicit Anticipation and Controllable Latency using Prefix-to-Prefix Framework》 提出適用于同傳場景的翻譯架構,該架構基于Transformer實現。
STACL 主要具有以下優勢:
Implicit Anticipation(隱式的預測能力): Prefix-to-Prefix架構擁有預測能力,即在未看到源詞的情況下仍然可以翻譯出對應的目標詞,克服了SOV→SVO等詞序差異;
圖1:Implicit Anticipation
Controllable Latency(可控的延遲): Wait-k策略可以不需要全句的源句,直接預測目標句,可以實現任意的字級延遲,同時保持較高的翻譯質量。
圖2:Controllable Latency (Wait-k)
Wait-k策略首先等待源端讀入k個詞后開始進行翻譯。上圖2中,k=1,第一個目標詞在讀入第一個1個源詞后翻譯,第二個目標詞在讀入前2個源詞后翻譯,以此類推,所以當源端讀入“他 還 說”3個詞后,目標端就已經翻譯出“he also said”。當k=3,第一個目標詞在讀入前3個源詞后翻譯,所以當源端讀入“他 還 說”后,目標端翻譯出第一個詞”he“。
快速實踐
本項目基于飛槳PaddleNLP完成,記得給PaddleNLP點個小小的Star? 開源不易,希望大家多多支持~
GitHub地址: https://github.com/PaddlePaddle/PaddleNLP
PaddleNLP文檔: https://paddlenlp.readthedocs.io
完整代碼請戳: https://github.com/PaddlePaddle/PaddleNLP/tree/develop/examples/simultaneous_translation/stacl
深度學習任務Pipeline
圖3:深度學習任務Pipeline
2.1 數據預處理
本項目展示的訓練數據為NIST的中英demo數據(1000條中英文本對),同時提供基于全量NIST中英數據訓練的預訓練模型下載。
中文需要Jieba+BPE,英文需要BPE。
BPE(Byte Pair Encoding)
BPE優勢:
壓縮詞表; 一定程度上緩解OOV(out of vocabulary)問題
圖4:learn BPE
圖5:Apply BPE
圖6:Jieba+BPE
數據格式
兵營 是 雙@@ 槍 老@@ 大@@ 爺 的 前提 建筑 之一 。it serves as a prerequisite for Re@@ apers to be built at the Bar@@ rac@@ ks .
2.2 構造Dataloader
構造DataLoader過程,與上一篇項目類似:越學越有趣:『手把手帶你學NLP』系列項目07 ——機器翻譯的那些事兒。
同樣使用paddlenlp.data和paddle.io.DataLoader進行數據處理和Dataloder的構造。
圖7:構造Dataloader的流程
圖8:Dataloader細節
2.3 搭建模型
基于飛槳框架API,包括:
paddle.nn.TransformerEncoderLayer:Transformer編碼器層 paddle.nn.TransformerEncoder:Transformer編碼器 paddle.nn.TransformerDecoderLayer:Transformer解碼器層 paddle.nn.TransformerDecoder:Transformer解碼器
圖9:模型搭建
Encoder層 采用Transformer的編碼結構。
Decoder層 基于paddle.nn.TransformerDecoderLayer加入Wait-k策略。
模型主結構 與Transformer基本一致,具體細節可參考: paddlenlp.transformers.TransformerModel SimultaneousTransformer:Encoder+Decoder(wait-k 策略)
圖10:wait-k策略示例
# 定義SimultaneousTransformer,這里給出和nn
. TransformerDecoderLayer不一致地方的注釋
class SimultaneousTransformer ( nn
. Layer
) : def
__init__ ( self
, src_vocab_size
, trg_vocab_size
, max_length
, n_layer
, n_head
, d_model
, d_inner_hid
, dropout
, weight_sharing
, bos_id
= 0 , eos_id
= 1 , waitk
= - 1 ) : super ( SimultaneousTransformer
, self
) . __init__ ( ) self
. trg_vocab_size
= trg_vocab_sizeself
. emb_dim
= d_modelself
. bos_id
= bos_idself
. eos_id
= eos_idself
. dropout
= dropoutself
. waitk
= waitkself
. n_layer
= n_layerself
. n_head
= n_headself
. d_model
= d_model# 聲明WordEmbeddingself
. src_word_embedding
= WordEmbedding ( vocab_size
= src_vocab_size
, emb_dim
= d_model
, bos_id
= self
. bos_id
) # 聲明PositionalEmbeddingself
. src_pos_embedding
= PositionalEmbedding ( emb_dim
= d_model
, max_length
= max_length
) # 判斷target是否要和source共享WordEmbedding
if weight_sharing
: assert src_vocab_size
== trg_vocab_size
, ( "Vocabularies in source and target should be same for weight sharing." ) self
. trg_word_embedding
= self
. src_word_embeddingself
. trg_pos_embedding
= self
. src_pos_embedding
else : self
. trg_word_embedding
= WordEmbedding ( vocab_size
= trg_vocab_size
, emb_dim
= d_model
, bos_id
= self
. bos_id
) self
. trg_pos_embedding
= PositionalEmbedding ( emb_dim
= d_model
, max_length
= max_length
) # 聲明Encoder層encoder_layer
= nn
. TransformerEncoderLayer ( d_model
= d_model
, nhead
= n_head
, dim_feedforward
= d_inner_hid
, dropout
= dropout
, activation
= 'relu' , normalize_before
= True
, bias_attr
= [ False
, True
] ) encoder_norm
= nn
. LayerNorm ( d_model
) # 聲明Encoderself
. encoder
= nn
. TransformerEncoder ( encoder_layer
= encoder_layer
, num_layers
= n_layer
, norm
= encoder_norm
) # 聲明Decoder層decoder_layer
= DecoderLayer ( d_model
= d_model
, nhead
= n_head
, dim_feedforward
= d_inner_hid
, dropout
= dropout
, activation
= 'relu' , normalize_before
= True
, bias_attr
= [ False
, False
, True
] ) decoder_norm
= nn
. LayerNorm ( d_model
) # 聲明Decoderself
. decoder
= Decoder ( decoder_layer
= decoder_layer
, num_layers
= n_layer
, norm
= decoder_norm
) if weight_sharing
: self
. linear
= lambda x
: paddle
. matmul ( x
= x
, y
= self
. trg_word_embedding
. word_embedding
. weight
, transpose_y
= True
) else : self
. linear
= nn
. Linear ( in_features
= d_model
, out_features
= trg_vocab_size
, bias_attr
= False
) def
forward ( self
, src_word
, trg_word
) : src_max_len
= paddle
. shape ( src_word
) [ - 1 ] trg_max_len
= paddle
. shape ( trg_word
) [ - 1 ] base_attn_bias
= paddle
. cast ( src_word
== self
. bos_id
, dtype
= paddle
. get_default_dtype ( ) ) . unsqueeze ( [ 1 , 2 ] ) * - 1e9 # 計算source端的attention masksrc_slf_attn_bias
= base_attn_biassrc_slf_attn_bias
. stop_gradient
= True# 計算target端的attention masktrg_slf_attn_bias
= paddle
. tensor
. triu ( ( paddle
. ones ( ( trg_max_len
, trg_max_len
) , dtype
= paddle
. get_default_dtype ( ) ) * - np
. inf
) , 1 ) trg_slf_attn_bias
. stop_gradient
= True# 計算encoder
- decoder的attention masktrg_src_attn_bias
= paddle
. tile ( base_attn_bias
, [ 1 , 1 , trg_max_len
, 1 ] ) src_pos
= paddle
. cast ( src_word
!= self
. bos_id
, dtype
= "int64" ) * paddle
. arange ( start
= 0 , end
= src_max_len
) trg_pos
= paddle
. cast ( trg_word
!= self
. bos_id
, dtype
= "int64" ) * paddle
. arange ( start
= 0 , end
= trg_max_len
) # 計算source的word embeddingsrc_emb
= self
. src_word_embedding ( src_word
) # 計算source的position embeddingsrc_pos_emb
= self
. src_pos_embedding ( src_pos
) # 得到最終Embedding:word embedding
+ position embeddingsrc_emb
= src_emb
+ src_pos_embenc_input
= F
. dropout ( src_emb
, p
= self
. dropout
, training
= self
. training
) if self
. dropout
else src_embwith paddle
. static . amp
. fp16_guard ( ) : # 下面是添加了waitk策略的部分
if self
. waitk
>= src_max_len
or self
. waitk
== - 1 : # 整句模型,和API一致enc_outputs
= [ self
. encoder ( enc_input
, src_mask
= src_slf_attn_bias
) ] else : # Wait- k策略 enc_outputs
= [ ] for i in
range ( self
. waitk
, src_max_len
+ 1 ) : # 分別將子句送入encoderenc_output
= self
. encoder ( enc_input
[ : , : i
, : ] , src_mask
= src_slf_attn_bias
[ : , : , : , : i
] ) enc_outputs
. append ( enc_output
) # 計算target的word embeddingtrg_emb
= self
. trg_word_embedding ( trg_word
) # 計算target的position embeddingtrg_pos_emb
= self
. trg_pos_embedding ( trg_pos
) # 得到最終Embedding:word embedding
+ position embeddingtrg_emb
= trg_emb
+ trg_pos_embdec_input
= F
. dropout ( trg_emb
, p
= self
. dropout
, training
= self
. training
) if self
. dropout
else trg_emb# 送入Decoder,拿到輸出dec_output
= self
. decoder ( dec_input
, enc_outputs
, tgt_mask
= trg_slf_attn_bias
, memory_mask
= trg_src_attn_bias
) # 經過全連接層拿到最終輸出predict
= self
. linear ( dec_output
) return predict
2.4 訓練模型
配置優化器、損失函數,以及評價指標(Perplexity,即困惑度,常用來衡量語言模型優劣,也可用于機器翻譯、文本生成等任務)。
圖11:訓練模型
def
do_train ( args
) : # 設置在GPU
/ CPU
/ XPU上運行paddle
. set_device ( args
. device
) # 設置隨機種子random_seed
= eval ( str ( args
. random_seed
) ) if random_seed is
not None
: paddle
. seed ( random_seed
) # 獲取
Dataloader ( train_loader
) , ( eval_loader
) = create_data_loader ( args
, places
= paddle
. get_device ( ) ) # 聲明模型transformer
= SimultaneousTransformer ( args
. src_vocab_size
, args
. trg_vocab_size
, args
. max_length
+ 1 , args
. n_layer
, args
. n_head
, args
. d_model
, args
. d_inner_hid
, args
. dropout
, args
. weight_sharing
, args
. bos_idx
, args
. eos_idx
, args
. waitk
) print ( 'waitk=' , args
. waitk
) # 定義Losscriterion
= CrossEntropyCriterion ( args
. label_smooth_eps
, args
. bos_idx
) # 定義學習率的衰減策略scheduler
= paddle
. optimizer
. lr
. NoamDecay ( args
. d_model
, args
. warmup_steps
, args
. learning_rate
) # 定義優化器optimizer
= paddle
. optimizer
. Adam ( learning_rate
= scheduler
, beta1
= args
. beta1
, beta2
= args
. beta2
, epsilon
= float ( args
. eps
) , parameters
= transformer
. parameters ( ) ) step_idx
= 0 # 按epoch迭代訓練
for pass_id in
range ( args
. epoch
) : batch_id
= 0 for input_data in train_loader
: # 從訓練集Dataloader按batch取數據
( src_word
, trg_word
, lbl_word
) = input_data# 獲得模型輸出的logits logits
= transformer ( src_word
= src_word
, trg_word
= trg_word
) # 計算losssum_cost
, avg_cost
, token_num
= criterion ( logits
, lbl_word
) # 計算梯度avg_cost
. backward ( ) # 更新參數optimizer
. step ( ) # 梯度清零optimizer
. clear_grad ( ) if ( step_idx
+ 1 ) % args
. print_step
== 0 or step_idx
== 0 : total_avg_cost
= avg_cost
. numpy ( ) # 打印loglogger
. info ( "step_idx: %d, epoch: %d, batch: %d, avg loss: %f, " " ppl: %f " % ( step_idx
, pass_id
, batch_id
, total_avg_cost
, np
. exp ( [ min ( total_avg_cost
, 100 ) ] ) ) ) if ( step_idx
+ 1 ) % args
. save_step
== 0 : # 驗證transformer
. eval ( ) total_sum_cost
= 0 total_token_num
= 0 with paddle
. no_grad ( ) : for input_data in eval_loader
: # 從驗證集Dataloader按batch取數據
( src_word
, trg_word
, lbl_word
) = input_data# 獲得模型輸出的logits logits
= transformer ( src_word
= src_word
, trg_word
= trg_word
) # 計算losssum_cost
, avg_cost
, token_num
= criterion ( logits
, lbl_word
) total_sum_cost
+= sum_cost
. numpy ( ) total_token_num
+= token_num
. numpy ( ) total_avg_cost
= total_sum_cost
/ total_token_num
[ 2021 - 06 - 17 22 : 03 : 51 , 772 ] [ INFO
] - step_idx
: 0 , epoch
: 0 , batch
: 0 , avg loss
: 9.260654 , ppl
: 10516.013672
[ 2021 - 06 - 17 22 : 04 : 14 , 491 ] [ INFO
] - step_idx
: 9 , epoch
: 0 , batch
: 9 , avg loss
: 9.239330 , ppl
: 10294.142578
[ 2021 - 06 - 17 22 : 04 : 40 , 302 ] [ INFO
] - step_idx
: 19 , epoch
: 0 , batch
: 19 , avg loss
: 9.196883 , ppl
: 9866.330078
[ 2021 - 06 - 17 22 : 04 : 53 , 412 ] [ INFO
] - validation
, step_idx
: 19 , avg loss
: 9.171905 , ppl
: 9622.934570
2.5 預測和評估
模型最終訓練的效果一般可通過測試集來進行測試,同傳類似機器翻譯場景,一般計算BLEU值。
預測結果中每行輸出是對應行輸入的得分最高的翻譯,對于使用 BPE 的數據,預測出的翻譯結果也將是 BPE 表示的數據,要還原成原始的數據(這里指 tokenize 后的數據)才能進行正確的評估。
圖12:預測和評估
動手試一試
是不是覺得很有趣呀。小編強烈建議初學者參考上面的代碼親手敲一遍,因為只有這樣,才能加深你對代碼的理解呦。
本次項目對應的代碼: https://aistudio.baidu.com/aistudio/projectdetail/1926754
更多PaddleNLP信息,歡迎訪問GitHub點star收藏后體驗: https://github.com/PaddlePaddle/PaddleNLP
推薦閱讀 : 全新的安全漏洞掃描解決方案,百度MTC讓APP安全防護能力更強
總結
以上是生活随笔 為你收集整理的零基础也可以实现“机器同传翻译”! 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。