同傳翻譯的「前世此生」html
同聲傳譯,簡稱「同傳」,是指譯員在不打斷講話者講話的狀況下,不間斷地將內容口譯給聽衆的一種翻譯方式,同聲傳譯員經過專用的設備提供即時的翻譯,這種方式適用於大型的研討會和國際會議,同聲傳譯效率高,能保證演講或會議的流暢進行。git
同聲傳譯員通常收入較高,可是成爲同聲傳譯的門檻也很高。當前,世界上95%的國際高端會議都採用同聲傳譯的方式。第二次世界大戰結束後,設立在德國的紐倫堡國際軍事法庭在審判法西斯戰犯時,首次採用同聲傳譯,這也是世界上第一次在大型國際活動中採用同聲傳譯。github
不過目前人工同傳翻譯存在着如下侷限之處:網絡
-
精力體力的挑戰:與交替傳譯不一樣的是,同傳須要邊聽、邊記、邊翻,同步進行,對譯員的要求極高。因爲須要高度集中注意力,人類同傳通常兩人一組,且每隔20多分鐘就要換人休息,對人的精力、體力都是極大的挑戰。架構
-
譯出率不高:據統計,同傳譯員的譯出率通常在60%-70%左右。譯出率不高的緣由,通常因爲未聽清或者難翻譯,人類譯員一般會選擇性的忽略某些句子,保證整體上的準確率和實時性。app
-
全球同傳譯員稀缺:因爲苛刻的要求,全球同傳譯員稀缺,只有幾千人。與巨大的市場需求相比,人才嚴重短缺。框架
相比之下機器同聲傳譯的優點有:機器最大的優點是不會由於疲倦而致使譯出率降低,能將全部「聽到」的句子所有翻譯出來,這使得機器的「譯出率」能夠達到100%,遠高於人類譯員的60%-70%。同時,在價格上也佔有優點。dom
本期項目咱們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_size self.emb_dim = d_model self.bos_id = bos_id self.eos_id = eos_id self.dropout = dropout self.waitk = waitk self.n_layer = n_layer self.n_head = n_head self.d_model = d_model # 聲明WordEmbedding self.src_word_embedding = WordEmbedding( vocab_size=src_vocab_size, emb_dim=d_model, bos_id=self.bos_id) # 聲明PositionalEmbedding self.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_embedding self.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) # 聲明Encoder self.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) # 聲明Decoder self.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 mask src_slf_attn_bias = base_attn_bias src_slf_attn_bias.stop_gradient = True # 計算target端的attention mask trg_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 mask trg_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 embedding src_emb = self.src_word_embedding(src_word) # 計算source的position embedding src_pos_emb = self.src_pos_embedding(src_pos) # 獲得最終Embedding:word embedding + position embedding src_emb = src_emb + src_pos_emb enc_input = F.dropout( src_emb, p=self.dropout, training=self.training) if self.dropout else src_emb with 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): # 分別將子句送入encoder enc_output = self.encoder( enc_input[:, :i, :], src_mask=src_slf_attn_bias[:, :, :, :i]) enc_outputs.append(enc_output) # 計算target的word embedding trg_emb = self.trg_word_embedding(trg_word) # 計算target的position embedding trg_pos_emb = self.trg_pos_embedding(trg_pos) # 獲得最終Embedding:word embedding + position embedding trg_emb = trg_emb + trg_pos_emb dec_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) # 定義Loss criterion = 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) # 計算loss sum_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() # 打印log logger.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) # 計算loss sum_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
加入交流羣,一塊兒學習吧
若是你在學習過程當中遇到任何問題或疑問,歡迎加入PaddleNLP的QQ技術交流羣!
推薦閱讀: