1. 程式人生 > >實戰 | 手把手教你搭一個機器翻譯模型

實戰 | 手把手教你搭一個機器翻譯模型

作者 | 李理

環信人工智慧研發中心 VP,十多年自然語言處理和人工智慧研發經驗。主持研發過多款智慧硬體的問答和對話系統,負責環信中文語義分析開放平臺和環信智慧機器人的設計與研發。

本示例會介紹使用 seq2seq 網路來實現機器翻譯,同時使用注意力機制來提高seq2seq的效果(尤其是長句)。

圖5.24: seq2seq 模型

sequence to sequence 模型

sequence to sequence 模型,或者說seq2seq 模型,由兩個RNN 組成。這兩個RNN分別叫做encoder 和decoder。encoder 會一個詞一個出的讀入輸入序列,每一步都有一個輸出,而最後的輸出叫做context 向量,我們可以認為是模型對源語言句子“語義”的一種表示。而decoder 用這個context 向量一步一步的生成目標語言的句子。

為什麼要兩個RNN 呢,如果我們使用一個RNN,輸入和輸出是一對一的關係(對於分類,我們可以只使用最後一個輸出),但是翻譯肯定不是一個詞對一個詞的翻譯。當然這只是使用兩個RNN 在形式上的方便,從“原理”上來說,人類翻譯也是類似的,首先仔細閱讀源語句,然後“理解”它,而所謂的“理解”在seq2seq 模型裡可以認為encoding 的過程,然後再根據理解,翻譯成目標語句。

注意力機制(Attention Mechanism)

用一個固定長度的向量來承載輸入序列的完整“語義”,不管向量能有多長,都是很困難的事情。

[Bahdanau et al. 等人引入的](https://arxiv.org/abs/1409.0473) ** 注意力機制**

試圖這樣來解決這個問題:我們不依賴於一個固定長度的向量,而是通過“注意”輸入的某些部分。在decoer 每一步解碼的時候都通過這個機制來選擇輸入的一部分來重點考慮。這似乎是合乎人類的翻譯過程的——我們首先通過一個encoder 大致理解句子的意思(編碼到一個定長向量),具體翻譯某個詞或者短語的時候我們會仔細推敲對應的源語言的詞(注意力機制)。

注意力是通過decoder 的另外一個神經網路層來計算的。它的輸入是當前輸入和上一個時刻的隱狀態,輸出是一個新的向量,這個向量的長度和輸入相同(因為輸入是變長的,我們會選擇一個最大的長度),這個向量會用softmax 變成“概率”,得到* 注意力權重*,這個權重可以認為需要花費多大的“注意力”到某個輸入上,因此我們會用這個權重加權平均encoder 的輸出,從而得到一個新的context 向量,這個向量會用來預測當前時刻的輸出。

圖5.25: 加入注意力機制的Encoder

依賴的lib

import unicodedata
import string
import re
import random
import time
import math

import torch
import torch.nn as nn
from torch.autograd import Variable
from torch import optim
import torch.nn.functional as F

是否使用CUDA,這個例子資料較少,所以CPU 也可以訓練。

#如果有GPU請改成True
USE_CUDA = False

圖5.26: 注意力的計算過程

載入資料

訓練資料是幾千個英語到法語的平行句對。我們這裡只是介紹演算法,所以使用一個很小的資料集來演示。資料在data/eng-fra.txt 裡,格式如下:

I am cold. Je suis froid.

我們會用one-hot 的方法來表示一個單詞。

1. 單詞變成數字

我們會建立一個Lang 物件來表示源/目標語言,它包含word2idx、idx2word 和word2count,分別表示單詞到id、id 到單詞和單詞的詞頻。word2count 的作用是用於過濾一些低頻詞(把它變成unknown)

SOS_token = 0
EOS_token = 1


class Lang:
   def __init__(self, name):
      self.name = name
      self.word2index = {}
      self.word2count = {}
      self.index2word = {0: "SOS", 1: "EOS"}
      self.n_words = 2

   def index_words(self, sentence):
      for word in sentence.split(' '):
      self.index_word(word)

   def index_word(self, word):
      if word not in self.word2index:
self.word2index[word] = self.n_words
self.word2count[word] = 1
self.index2word[self.n_words] = word
self.n_words += 1
     else:
self.word2count[word] += 1

  def unicode_to_ascii(s):
     return ''.join(
        c for c in unicodedata.normalize('NFD', s)
           if unicodedata.category(c) != 'Mn'
)

# 大小轉小寫,trim,去掉非字母。

  def normalize_string(s):
     s = unicode_to_ascii(s.lower().strip())
     s = re.sub(r"([.!?])", r" \1", s)
     s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
return s

2. 讀取檔案

檔案格式是每行一個句對,用tab 分隔,第一個是英語,第二個是法語,為了方便未來的複用,我們有一個reverse 引數,這樣如果我們需要法語到英語的翻譯就可以用到。

def read_langs(lang1, lang2, reverse=False):
   print("Reading lines...")
 
# 讀取檔案
   lines = open('../data/%s-%s.txt' % (lang1,lang2)).read().strip().split('\n')

# split

   pairs = [[normalize_string(s) for s in l.split('\t')] for l in lines]

if reverse:
      pairs = [list(reversed(p)) for p in pairs]
      input_lang = Lang(lang2)
      output_lang = Lang(lang1)
else:
    input_lang = Lang(lang1)
    output_lang = Lang(lang2)

return input_lang, output_lang, pairs

3. 過濾句子

作為演示,我們只挑選長度小於10 的句子,並且這保留”I am” 和”He is” 開頭的資料

MAX_LENGTH = 10
good_prefixes = (
   "i am ", "i m ",
   "he is", "he s ",
   "she is", "she s",
   "you are", "you re "
)
def filter_pair(p):
   return len(p[0].split(' ')) < MAX_LENGTH and len(p[1].split(' ')) < MAX_LENGTH and \
       p[1].startswith(good_prefixes)
def filter_pairs(pairs):
   return [pair for pair in pairs if filter_pair(pair)]

資料處理過程如下:

• 讀取檔案,split 成行,再split 成pair

• 文字歸一化,通過長度和內容過濾

• 通過pair 裡的句子得到單詞列表

圖5.27: 句子變成Tensor

def prepare_data(lang1_name, lang2_name, reverse=False):
   input_lang, output_lang, pairs = read_langs(lang1_name, lang2_name,
reverse)
   print
("Read %s sentence pairs" % len(pairs))

   pairs = filter_pairs(pairs)
   print("Trimmed to %s sentence pairs" % len(pairs))

   print("Indexing words...")
   for pair in pairs:
        input_lang.index_words(pair[0])
        output_lang.index_words(pair[1])
 
   return input_lang, output_lang, pairs

input_lang, output_lang, pairs = prepare_data('eng', 'fra', True)

print(random.choice(pairs))

把訓練資料變成Tensor/Variable

為了讓神經網路能夠處理,我們首先需要把句子變成Tensor。每個句子首先分成詞,每個詞被替換成對應的index。另外我們會增加一個特殊的EOS 來表示句子的結束。

在PyTorch 裡一個Tensor 是一個多維陣列,它的所有元素的資料型別都是一樣的。我們這裡使用LongTensor 來表示詞的index。

可以訓練的PyTorch 模組要求輸入是Variable 而不是Tensor。變數除了包含Tensor 的內容之外,它還會跟蹤計算圖的狀態,從而可以進行自動梯度的求值。

def indexes_from_sentence(lang, sentence):
  return [lang.word2index[word] for word in sentence.split(' ')]

def variable_from_sentence(lang, sentence):
indexes = indexes_from_sentence(lang, sentence)
indexes.append(EOS_token)
var = Variable(torch.LongTensor(indexes).view(-1, 1))

  if USE_CUDA: var = var.cuda()
  return var

def variables_from_pair(pair):
input_variable = variable_from_sentence(input_lang, pair[0])
target_variable = variable_from_sentence(output_lang, pair[1])
  return (input_variable, target_variable)

圖5.28: Encoder 定義

定義模型

1. Encoder

class EncoderRNN(nn.Module):
   def __init__(self, input_size, hidden_size, n_layers=1):
       super(EncoderRNN, self).__init__()
       
       self.input_size = input_size
       self.hidden_size = hidden_size
       self.n_layers = n_layers
       
       self.embedding = nn.Embedding(input_size, hidden_size)
       self.gru = nn.GRU(hidden_size, hidden_size, n_layers)
       
   def forward(self, word_inputs, hidden):
       # 注意:和名字分類不同,我們這裡一次處理完一個輸入的所有詞,而不是for迴圈每次處理一個詞。
       # 兩者的效果是一樣的,但是一次處理萬效率更高。
       seq_len = len(word_inputs)
       embedded = self.embedding(word_inputs).view(seq_len, 1, -1)
       output, hidden = self.gru(embedded, hidden)
       return output, hidden
       
   def init_hidden(self):
       hidden = Variable(torch.zeros(self.n_layers, 1, self.hidden_size))
       if USE_CUDA: hidden = hidden.cuda()
       return hidden

我們這裡的程式碼每次只處理一個訓練句對,這樣的實現不是最高效的,但是不要考慮padding,比較容易理解,後面我們會介紹batch 的例子。

2. Attention Decoder

2.1 Bahdanau 等人提出的模型

下面我們來學習一下這篇文章提出的[Neural Machine Translation by JointlyLearning to Align and Translate](https://arxiv.org/abs/1409.0473) Attention Decoder。

decoder 在每一個時刻的輸出依賴與前一個時刻的輸出和一個x,這個x 包括當前的隱狀態(它也會考慮前一個時刻的輸出)和一個注意力”context“,下文會介紹它。函式g 是一個帶非線性啟用的全連線層,它的輸入是yi−1, si 和ci 拼接起來的。

上式的意思是:我們在翻譯當前詞時只考慮上一個翻譯出來的詞以及當前的隱狀態和注意力context。

當前隱狀態si 是有RNN f 計算出來的,這個RNN 的輸入是上一個隱狀態si−1,decoder 的上一個輸出yi−1 和context 向量ci。

在程式碼實現中,我們使用的RNN 是‘nn.GRU‘,隱狀態si 是‘hidden‘,輸出yi是‘output‘,context ci 是‘context‘。

context 向量ci 是encoder 在每個時刻(詞) 的輸出的加權和,而權值aij 表示i時刻需要關注hj 的程度(概率)。

而權值aij 是” 能量” eij 的softmax。

而能量eij 是上個時刻的隱狀態si−1 和encoder 第j 個時刻的輸出hj 的函式:

2.2 實現Bahdanau 模型

總結一下,我們的decoder 有4 個主要的部分——一個embedding 層用於把輸入詞變成向量;一個用於計算注意力能量的層;一個RNN 層;一個輸出層。

decoder 的輸入是上一個時刻的隱狀態si−1,上一個時刻的輸出yi−1 和所有encoder 的輸出h∗。

• embedding 層其輸入是上一個時刻的輸出yi−1

embedded = embedding(last_rnn_output)

• attention 層首先根據函式a 計算e,其輸入是(si−1, hj) 輸出是eij,最後對e 用softmax 得到aij

attn_energies[j] = attn_layer(last_hidden, encoder_outputs[j])

attn_weights = normalize(attn_energies)

• context 向量ci 是encoder 的所有時刻的輸出的加權和

context = sum(attn_weights * encoder_outputs)

• RNN 層f 的輸入是(si−1, yi−1, ci) 和內部的隱狀態,輸出是si

rnn_input = concat(embedded, context)

rnn_output, rnn_hidden = rnn(rnn_input, last_hidden)

• 輸出層g 的輸入是(yi−1, si, ci),輸出是yi

output = out(embedded, rnn_output, context)

# 注意:我們後面並沒有用到這個模型,程式碼只是為了讓讀者更加理解前面的內容。
class BahdanauAttnDecoderRNN(nn.Module):
   def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1):
       super(AttnDecoderRNN, self).__init__()
       
       # 定義引數
       self.hidden_size = hidden_size
       self.output_size = output_size
       self.n_layers = n_layers
       self.dropout_p = dropout_p
       self.max_length = max_length
       
       # 定義網路
       self.embedding = nn.Embedding(output_size, hidden_size)
       self.dropout = nn.Dropout(dropout_p)
       self.attn = GeneralAttn(hidden_size)
       self.gru = nn.GRU(hidden_size * 2, hidden_size, n_layers, dropout=dropout_p)
       self.out = nn.Linear(hidden_size, output_size)
   def forward(self, word_input, last_hidden, encoder_outputs):
       # 每次decode我們只執行一個time step,但是會使用encoder的所有輸出
       
       # 得到當前詞(decoder的上一個輸出)的embedding
       word_embedded = self.embedding(word_input).view(1, 1, -1) # S=1 x B x N
       word_embedded = self.dropout(word_embedded)
       
       # 計算attention weights
       attn_weights = self.attn(last_hidden[-1], encoder_outputs)
       context = attn_weights.bmm(encoder_outputs.transpose(0, 1)) # B x 1 x N
     
       # 把word_embedded和context拼接起來作為rnn的輸入
       rnn_input = torch.cat((word_embedded, context), 2)
       output, hidden = self.gru(rnn_input, last_hidden)
       
       # 輸出層
       output = output.squeeze(0) # B x N
       output = F.log_softmax(self.out(torch.cat((output, context), 1)), 1)
       
       # 返回結果
       return output, hidden, attn_weights

2.3 Luong 等人提出的模型

Luong 等人在[Effective Approaches to Attention-based Neural Machine Translation](https://arxiv.org/abs/1508.04025) 提出了更多的提高和簡化。他們描述了”全局注意力“模型,其計算注意力得分的方法和之前不同。前面是通過si−1 和hj 計算aij,也就是當前的注意力權重依賴與前一個狀態,而這裡的注意力依賴與decoder 當前的隱狀態和encoder 所有隱狀態:

特點的”score” 函式會比較兩個隱狀態的”相似度“,可以是兩個向量的內積,也可以是hs′ 做一個線性變換之後和ht 的內積,也可以是把兩個向量拼接起來然後做一個線性變換,然後和一個引數va(這個引數是學習出來的)的內積:

scoring 函式的模組化定義使得我們可以隨意的修改而不影響其它地方的程式碼。

這個模組的輸入總是decoder 的隱狀態和encoder 的所有輸出。

class Attn(nn.Module):
   def __init__(self, method, hidden_size, max_length=MAX_LENGTH):
       super(Attn, self).__init__()
       
       self.method = method
       self.hidden_size = hidden_size
       
       if self.method == 'general':
           self.attn = nn.Linear(self.hidden_size, hidden_size)
           
       elif self.method == 'concat':
           self.attn = nn.Linear(self.hidden_size * 2, hidden_size)
           self.other = nn.Parameter(torch.FloatTensor(1, hidden_size))
           
   def forward(self, hidden, encoder_outputs):
       seq_len = len(encoder_outputs)
       
       # 建立變數來儲存注意力能量
       attn_energies = Variable(torch.zeros(seq_len))
       if USE_CUDA: attn_energies = attn_energies.cuda()
       
       # 計算
       for i in range(seq_len):
           attn_energies[i] = self.score(hidden, encoder_outputs[i])
           
       return F.softmax(attn_energies, 0).unsqueeze(0).unsqueeze(

相關推薦

實戰 | 手把手一個機器翻譯模型

作者 | 李理環信人工智慧研發中心 VP,十多年自然語言處理和人工智慧研發經驗。主

手把手一個吸引人的購物網站

網站建設 購物網站 自助建站 購物網站盈利能力相信很多用戶都是有目共睹的,因此不少的中小企業對購物網站的建設也是趨之若鶩,怎麽企業設計購物網站有什麽方法能夠為購物網站提高人氣呢?下面看看凡科網站建設帶來的一些分析。 要對用戶的跟隨心理進行分析。無論是實體銷售還是線上的銷售,用戶都會有一種莫名

手把手編寫一個簡單的PHP模塊形態的後門

cpp rest xtu job ring 事先 們的 original call 看到Freebuf 小編發表的用這個隱藏於PHP模塊中的rootkit,就能持久接管服務器文章,很感興趣,苦無作者沒留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一個非常流行

手把手實現一個完整的BST(超級詳細)

查找樹 str image isempty 使用 this 根據 數據 false 查找基本分類如下: 線性表的查找 順序查找 折半查找 分塊查找 樹表的查找 二叉排序樹 平衡二叉樹 B樹 B+樹 散列表的查找 今天介紹二叉排序樹。 二叉排序樹 ( Binary

大神手把手一個頁面模板引擎,只需20行Javascript代碼!

[1] 表達 最終 strong ice ali 開頭 syntax years 只用20行Javascript代碼就寫出一個頁面模板引擎的大神是AbsurdJS的作者,下面是他分享的全文,轉需。 不知道你有木有聽說過一個基於Javascript的Web頁面預處理器,叫做A

手把手搭建一個加密貨幣交易模擬器,不用投錢就能玩

box NPU nec idp reat 監控 最簡 data- 自己 手把手教你搭建一個加密貨幣交易模擬器,不用投錢就能玩 大數據文摘,編譯:汪小七、黃文暢、小魚 我雖然不是交易員,但對加密貨幣的交易非常感興趣。然而,我不會在自己什麽都不清楚的時候就盲目投

騰訊雲技術專家盧萌凱手把手Demo一個人臉識別程序!

方案設計 如果 簡介 同學會 分析 ref 視頻轉碼 頭像 根據 歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐幹貨哦~ 本文來自騰訊雲技術沙龍,本次沙龍主題為Serverless架構開發與SCF部署實踐 盧萌凱:畢業於東南大學,曾就職於華為,熟悉雲行業解決方案。

手把手構建一個音視訊小程式

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~ 本文由騰訊視訊雲終端團隊發表於雲+社群專欄 騰訊雲提供了全套技術文件和原始碼來幫助您快速構建一個音視訊小程式,但是再好的原始碼和文件也有學習成本,為了儘快的能除錯起來,我們還提供了一個免費的一鍵部署服務:您只需輕點幾下滑鼠,就可以在自己

WebRTC系列(1)-手把手實現一個瀏覽器拍照室Demo

1.WebRTC開發背景   由於業務需求,需要在專案中實現實時音視訊通話功能,之前基於瀏覽器開發的Web專案要進行音視訊通話,需要安裝flash外掛才能實現或者使用C/S客戶端進行通訊。隨著網際網路技術的驅動下,在很多場景下需要進行音視訊通訊,在生活中我們現在使用電話越來越少,使用微信和視訊越來越多。在一

Vue+ElementUI: 手把手一個audio元件

目的 本專案的目的是教你如何實現一個簡單的音樂播放器(這並不難) 本專案並不是一個可以用於生產環境的element播放器,所以並沒有考慮太多的相容性問題 本專案不是ElementUI的一個音訊外掛,只是一個教程,不過你可以自行擴充套件實現 本專案只是為了學習audio相關事件以及API

手把手實現一個 Vue 進度條組件!

內容 分享圖片 軟件 pen export padding eight 自己 自動完成 最近在個人的項目中,想對頁面之間跳轉的過程進行優化,想到了很多文檔或 npm 等都用到的頁面跳轉進度條,於是便想自己去實現一個,特此記錄。 來看下 npm 搜索組件時候的效果: so

手把手實現一個 Vue 進度條元件!

最近在個人的專案中,想對頁面之間跳轉的過程進行優化,想到了很多文件或 npm 等都用到的頁面跳轉進度條,於是便想自己去實現一個,特此記錄。 來看下 npm 搜尋元件時候的效果: so 下面咱們一起動手實現一下唄。 定義使用方式 想實現一個元件的前提,一定要想好你的需求是什麼,還要自己去定義一

手把手一個Loading

點選上面藍色字型關注 “IT大飛說” 置頂公眾號( ID:ITBigFly)第一時間收到推送 作為 Android 開發者,無奈經常會碰到各種各樣的奇葩需求,現在大多公司 UI 設計圖、標註都是按 IOS 來設計的,包括一個

手把手一個自己的chrome擴充套件程式

手把手教你做一個自己的chrome擴充套件程式 [目錄] first.效果 1.收藏夾修改 (1).滑鼠移動到收藏夾上的動作效果 (2).收藏夾框 (3)百度搜索框功能 2.右上文字修改 3.背景圖片修改 4

手把手設計一個百萬級的訊息推送系統

本文分享的內容不但可以滿足物聯網領域同時還支援以下場景: 基於 Web 的聊天系統(點對點、群聊)。 Web 應用中需求服務端推送的場景。 基於 SDK 的訊息推送平臺。 技術選型 要滿足大量的連線數、同時支援雙全工通訊,並且效能也得有保障。 在 Java 技術

手把手一個Java web學生資訊、選課、簽到考勤、成績管理系統附帶完整原始碼及視訊開發教程

四個階段的Java web學生資訊系統視訊教程終於錄製完成了,系統用到的知識點有:jsp+servlet+mysql+jquery+ajax,前端採用的是當下最流行的easyui管理框架,全部採用面向介面的MVC三層設計模式,是大家學習Java web實戰專案不可多得的入門專

手把手開發一個babel-plugin

需求 在最近的開發過程中,不同的專案、不同的頁面都需要用到某種UI控制元件,於是很自然的將這些UI控制元件拆出來,單獨建立了一個程式碼庫進行維護。下面是我的元件庫大致的目錄結構如下: ... - lib - components - comp

手把手APM之Skywalking搭建指南

手把手教你搭APM之Skywalking 前言 什麼是APM?全稱:Application Performance Management 可以參考這裡: 現代APM體系,基本都是參考Google的Dapper(大規模分散式系統的跟蹤系統)的體系來做的。通過跟蹤請求的處理過程,來對應用系統在前後端處理、服務端呼

手把手APM之Skywalking搭建指南(支援Java/C#/Node.js)

手把手教你搭APM之Skywalking 前言 什麼是APM?全稱:Application Performance Management 可以參考這裡: 現代APM體系,基本都是參考Google的Dapper(大規模分散式系統的跟蹤系統)的體系來做的。通過跟蹤請求的

阿里大牛手把手構建一個高效能、高可用的大型分散式網站

大型分散式網站架構技術 大型網站的特點 大型網站一般有如下特點: 1.使用者多,分佈廣泛 2.大流量,高併發 3.海量資料,服