用Keras LSTM構建編碼器-解碼器模型

2021-02-13 python遇見NLP

轉載自公眾號:磐創AI

作者 | Nechu BM 

編譯 | VK 

來源 | Towards Data Science

基礎知識:了解本文之前最好擁有關於循環神經網絡(RNN)和編解碼器的知識。

本文是關於如何使用Python和Keras開發一個編解碼器模型的實用教程,更精確地說是一個序列到序列(Seq2Seq)。在上一個教程中,我們開發了一個多對多翻譯模型,如下圖所示:

這種結構有一個重要的限制,即序列長度。正如我們在圖像中看到的,輸入序列和輸出序列的長度必須相同。如果我們需要不同的長度呢?

例如,我們想實現一個接受不同序列長度的模型,它接收一個單詞序列並輸出一個數字,或者是圖像字幕模型,其中輸入是一個圖像,輸出是一個單詞序列。

如果我們要開發的模型是輸入和輸出長度不同,我們需要開發一個編解碼器模型。通過本教程,我們將了解如何開發模型,並將其應用於翻譯練習。模型的表示如下所示。

我們將模型分成兩部分,首先,我們有一個編碼器,輸入西班牙語句子並產生一個隱向量。編碼器是用一個嵌入層將單詞轉換成一個向量然後用一個循環神經網絡(RNN)來計算隱藏狀態,這裡我們將使用長短期記憶(LSTM)層。

然後編碼器的輸出將被用作解碼器的輸入。對於解碼器,我們將再次使用LSTM層,以及預測英語單詞的全連接層。

實現

示例數據來自manythings.org。它是由語言的句子對組成的。在我們的案例中,我們將使用西班牙語-英語對。

建立模型首先需要對數據進行預處理,得到西班牙語和英語句子的最大長度。

1-預處理

先決條件:了解Keras中的類「tokenizer」和「pad_sequences」。如果你想詳細回顧一下,我們在上一個教程中討論過這個主題。

首先,我們將導入庫,然後讀取下載的數據。

import string
import numpy as np

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Model
from keras.layers import LSTM, Input, TimeDistributed, Dense, Activation, RepeatVector, Embedding
from keras.optimizers import Adam
from keras.losses import sparse_categorical_crossentropy

# 翻譯文件的路徑
path_to_data = 'data/spa.txt'

# 讀文件
translation_file = open(path_to_data,"r", encoding='utf-8') 
raw_data = translation_file.read()
translation_file.close()

# 解析數據
raw_data = raw_data.split('\n')
pairs = [sentence.split('\t') for sentence in  raw_data]
pairs = pairs[1000:20000]

一旦我們閱讀了數據,我們將保留第一個例子,以便更快地進行訓練。如果我們想開發更高性能的模型,我們需要使用完整的數據集。然後我們必須通過刪除大寫字母和標點符號來清理數據。

def clean_sentence(sentence):
    # 把這個句子小寫
    lower_case_sent = sentence.lower()
    # 刪除標點
    string_punctuation = string.punctuation + "¡" + '¿'
    clean_sentence = lower_case_sent.translate(str.maketrans('', '', string_punctuation))
   
    return clean_sentence

接下來,我們將句子標識化並分析數據。

def tokenize(sentences):
    # 創建 tokenizer
    text_tokenizer = Tokenizer()
    # 應用到文本上
    text_tokenizer.fit_on_texts(sentences)
    return text_tokenizer.texts_to_sequences(sentences), text_tokenizer

創建完函數後,我們可以進行預處理:

# 清理句子
english_sentences = [clean_sentence(pair[0]) for pair in pairs]
spanish_sentences = [clean_sentence(pair[1]) for pair in pairs]

# 標識化單詞
spa_text_tokenized, spa_text_tokenizer = tokenize(spanish_sentences)
eng_text_tokenized, eng_text_tokenizer = tokenize(english_sentences)

print('Maximum length spanish sentence: {}'.format(len(max(spa_text_tokenized,key=len))))
print('Maximum length english sentence: {}'.format(len(max(eng_text_tokenized,key=len))))


# 檢查長度
spanish_vocab = len(spa_text_tokenizer.word_index) + 1
english_vocab = len(eng_text_tokenizer.word_index) + 1
print("Spanish vocabulary is of {} unique words".format(spanish_vocab))
print("English vocabulary is of {} unique words".format(english_vocab))

上面的代碼列印以下結果

根據之前的代碼,西班牙語句子的最大長度為12個單詞,英語句子的最大長度為6個單詞。在這裡我們可以看到使用編解碼器模型的優勢。以前我們處理等長句子有局限性,所以我們需要對英語句子應用填充到12,現在只需要一半。因此,更重要的是,它還減少了LSTM時間步數,減少了計算需求和複雜性。

我們使用填充來使每種語言中句子的最大長度相等。

max_spanish_len = int(len(max(spa_text_tokenized,key=len)))
max_english_len = int(len(max(eng_text_tokenized,key=len)))

spa_pad_sentence = pad_sequences(spa_text_tokenized, max_spanish_len, padding = "post")
eng_pad_sentence = pad_sequences(eng_text_tokenized, max_english_len, padding = "post")

# 重塑
spa_pad_sentence = spa_pad_sentence.reshape(*spa_pad_sentence.shape, 1)
eng_pad_sentence = eng_pad_sentence.reshape(*eng_pad_sentence.shape, 1)

現在我們已經準備好了數據,讓我們構建模型。

2.模型開發

在下一節中,我們將創建模型,並在python代碼中解釋添加的每一層。

2.1-編碼器

我們定義的第一層是圖像的嵌入層。為此,我們首先必須添加一個輸入層,這裡唯一要考慮的參數是「shape」,這是西班牙語句子的最大長度,在我們的例子中是12。

然後我們將其連接到嵌入層,這裡要考慮的參數是「input_dim」(西班牙語詞彙表的長度)和「output_dim」(嵌入向量的形狀)。此層將把西班牙語單詞轉換為輸出維度形狀的向量。

這背後的概念是以空間表示的形式提取單詞的含義,其中每個維度都是定義單詞的特徵。例如,「sol」將轉換為形狀為128的向量。輸出維越高,從每個單詞中提取的語義意義就越多,但所需的計算和處理時間也就越高。我們也需要在速度和性能之間找到平衡。

input_sequence = Input(shape=(max_spanish_len,))
embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)

接下來,我們將添加大小為64的LSTM層。即使LSTM的每一個時間步都輸出一個隱藏向量,我們會把注意力集中在最後一個,因此參數「return_sequences」 是'False'。我們將看到LSTM層如何在解碼器的return_sequences=True的情況下工作。

input_sequence = Input(shape=(max_spanish_len,))
embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)
encoder = LSTM(64, return_sequences=False)(embedding)

當返回序列為'False'時,輸出是最後一個隱藏狀態。

2.2-解碼器

編碼器層的輸出將是最後一個時間步的隱藏狀態。然後我們需要把這個向量輸入解碼器。讓我們更精確地看一下解碼器部分,並了解它是如何工作的。

正如我們在圖像中看到的,隱藏向量被重複n次,因此LSTM的每個時間步都接收相同的向量。為了使每個時間步都有相同的向量,我們需要使用層RepeatVector,因為它的名字意味著它的作用是重複它接收的向量,我們需要定義的唯一參數是n,重複次數。這個數字等於解碼器部分的時間步數,換句話說就是英語句子的最大長度6。

input_sequence = Input(shape=(max_spanish_len,))
embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)
encoder = LSTM(64, return_sequences=False)(embedding)
r_vec = RepeatVector(max_english_len)(encoder)

一旦我們準備好輸入,我們將繼續解碼器。這也是用LSTM層構建的,區別在於參數return_sequences,在本例中為'True'。這個參數是用來做什麼的?在編碼器部分,我們只期望在最後一個時間步中有一個向量,而忽略了其他所有的向量,這裡我們期望每個時間步都有一個輸出向量,這樣全連接層就可以進行預測。

input_sequence = Input(shape=(max_spanish_len,))
embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)
encoder = LSTM(64, return_sequences=False)(embedding)
r_vec = RepeatVector(max_english_len)(encoder)
decoder = LSTM(64, return_sequences=True, dropout=0.2)(r_vec)

我們還有最後一步,預測翻譯的單詞。為此,我們需要使用全連接層。我們需要定義的參數是單元數,這個單元數是輸出向量的形狀,它需要與英語詞彙的長度相同。為什麼?這個向量的值都接近於零,除了其中一個單位接近於1。然後我們需要將輸出1的單元的索引映射到字典中,在字典中我們將每個單元映射到一個單詞。

例如,如果輸入是單詞『sun』,而輸出是一個向量,其中所有都是零,然後單元472是1,那麼我們將該索引映射到包含英語單詞的字典上,並得到值『sun』。

我們剛剛看到了如何應用全連接層來預測一個單詞,但是我們如何對整個句子進行預測呢?因為我們使用return_sequence=True,所以LSTM層在每個時間步輸出一個向量,所以我們需要在每個時間步應用前面解釋過的全連接層層,讓其每次預測一個單詞。

為此,Keras開發了一個稱為TimeDistributed的特定層,它將相同的全連接層應用於每個時間步。

input_sequence = Input(shape=(max_spanish_len,))
embedding = Embedding(input_dim=spanish_vocab, output_dim=128,)(input_sequence)
encoder = LSTM(64, return_sequences=False)(embedding)
r_vec = RepeatVector(max_english_len)(encoder)
decoder = LSTM(64, return_sequences=True, dropout=0.2)(r_vec)
logits = TimeDistributed(Dense(english_vocab))(decoder)

最後,我們創建模型並添加一個損失函數。

enc_dec_model = Model(input_sequence, Activation('softmax')(logits))
enc_dec_model.compile(loss=sparse_categorical_crossentropy,
              optimizer=Adam(1e-3),
              metrics=['accuracy'])
enc_dec_model.summary()

一旦我們定義了模型,我們就可以訓練它了。

model_results = enc_dec_model.fit(spa_pad_sentence, eng_pad_sentence, batch_size=30, epochs=100)

當模型訓練好後,我們就可以進行第一次翻譯了。你還可以找到函數「logits_to_sentence」,它將全連接層的輸出與英語詞彙進行映射。

def logits_to_sentence(logits, tokenizer):

    index_to_words = {idx: word for word, idx in tokenizer.word_index.items()}
    index_to_words[0] = '<empty>' 

    return ' '.join([index_to_words[prediction] for prediction in np.argmax(logits, 1)])

index = 14
print("The english sentence is: {}".format(english_sentences[index]))
print("The spanish sentence is: {}".format(spanish_sentences[index]))
print('The predicted sentence is :')
print(logits_to_sentence(enc_dec_model.predict(spa_pad_sentence[index:index+1])[0], eng_text_tokenizer))

結論

編解碼器結構允許不同的輸入和輸出序列長度。首先,我們使用嵌入層來創建單詞的空間表示,並將其輸入LSTM層,因為我們只關注最後一個時間步的輸出,我們使用return_sequences=False。

這個輸出向量需要重複的次數與解碼器部分的時間步數相同,為此我們使用RepeatVector層。解碼器將使用LSTM,參數return_sequences=True,因此每個時間步的輸出都會傳遞到全連接層。

儘管這個模型已經是上一個教程的一個很好的改進,我們仍然可以提高準確性。我們可以在一層的編碼器和解碼器中增加一層。我們也可以使用預訓練的嵌入層,比如word2vec或Glove。最後,我們可以使用注意機制,這是自然語言處理領域的一個主要改進。我們將在下一個教程中介紹這個概念。

附錄:不使用重複向量的編解碼器

在本教程中,我們了解了如何使用RepeatVector層構建編碼器-解碼器。還有第二個選項,我們使用模型的輸出作為下一個時間步驟的輸入,而不是重複隱藏的向量,如圖所示。

實現這個模型的代碼可以在Keras文檔中找到,它需要對Keras庫有更深入的理解,並且開發要複雜得多:https://blog.keras.io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html

原文連結:https://towardsdatascience.com/how-to-build-an-encoder-decoder-translation-model-using-lstm-with-python-and-keras-a31e9d864b9b

相關焦點

  • 用Keras LSTM構建編碼器-解碼器模型
    例如,我們想實現一個接受不同序列長度的模型,它接收一個單詞序列並輸出一個數字,或者是圖像字幕模型,其中輸入是一個圖像,輸出是一個單詞序列。如果我們要開發的模型是輸入和輸出長度不同,我們需要開發一個編解碼器模型。通過本教程,我們將了解如何開發模型,並將其應用於翻譯練習。
  • 【乾貨】seq2seq模型實例:用Keras實現機器翻譯
    model使用編碼解碼器模型實現從英文翻譯成法語 這篇文章受啟發於keras的例子和關於編碼器- 解碼器網絡的論文。本文中我自己關於這個例子的實現可以在我個人的GitHub中找到 keras的例子連結:https://github.com/keras-team/keras/blob/master/examples/lstm_seq2seq.py編碼器- 解碼器網絡論文:https://arxiv.org/abs/1409.3215
  • python用於NLP的seq2seq模型實例:用Keras實現神經機器翻譯
    p=8438在本文中,我們將看到如何創建語言翻譯模型,這也是神經機器翻譯的非常著名的應用。我們將使用seq2seq通過Python的Keras庫創建我們的語言翻譯模型。假定您對循環神經網絡(尤其是LSTM)有很好的了解。本文中的代碼是使用Keras庫用Python編寫的。
  • 十分鐘掌握Keras實現RNN的seq2seq學習
    這需要一個更高級的設置,這就是人們在「序列到序列模型」時經常提及的沒有上下文。下面是它的工作原理:有一個RNN層(或其堆疊)作為「編碼器」:它負責處理輸入序列並返回其自身的內部狀態。注意,我們將丟棄編碼器RNN的輸出,只恢復狀態。該狀態將在下一步驟中用作解碼器的「上下文」或「環境」。
  • 入門 | 十分鐘搞定Keras序列到序列學習(附代碼實現)
    這就需要一個更高級的設置,尤其在沒有進一步語境的「序列到序列模型」時。下面是其工作原理:一個 RNN 層(或其中的堆棧)作為「編碼器」:它處理輸入序列並反饋其內部狀態。注意我們拋棄了編碼器 RNN 的輸出,只恢復其狀態。該狀態在下一步中充當解碼器的「語境」。另一個 RNN 層作為「解碼器」:在給定目標序列先前字母的情況下,它被訓練以預測目標序列的下一個字符。
  • CICC科普欄目|十分鐘搞定Keras序列到序列學習(附代碼實現)
    這就需要一個更高級的設置,尤其在沒有進一步語境的「序列到序列模型」時。下面是其工作原理:一個 RNN 層(或其中的堆棧)作為「編碼器」:它處理輸入序列並反饋其內部狀態。注意我們拋棄了編碼器 RNN 的輸出,只恢復其狀態。該狀態在下一步中充當解碼器的「語境」。另一個 RNN 層作為「解碼器」:在給定目標序列先前字母的情況下,它被訓練以預測目標序列的下一個字符。
  • 更深的編碼器+更淺的解碼器=更快的自回歸模型
    從另一個方面看,如果解碼器更小(即層數更少),那麼即使是自回歸模型,是不是也可以顯著提高生成效率呢?這就是本文要回答的問題:當模型解碼器變淺、編碼器變深時,模型是否還能在保持原來效果的前提下,提升生成效率。這可以用下面的圖表示:
  • 為文本摘要模型添加注意力機制:採用LSTM的編解碼器模型實現
    摘要文本摘要算法創建新的短語和句子,從原始文本中傳遞最有用的信息——就像人類一樣。在本文中,我們將重點研究抽象摘要技術,並將利用編解碼器架構來解決這一問題。什麼是編碼器-解碼器架構?常用的序列-序列模型(編碼器-解碼器)的整體結構如下圖所示該模型由編碼器、中間矢量和解碼器三部分組成。編碼器該編碼器基本上由一系列LSTM/GRU單元組成(請查看LSTM/GRU文檔以更好地理解架構)。
  • 手把手教你用 Keras 實現 LSTM 預測英語單詞發音
    出於這個原因,我們將創建一個帶有編碼器 (encoder) 和解碼器 (decoder) 兩個部分的 seq2seq [博客連結](https://blog.keras.io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras.html) 模型。
  • 如何Keras自動編碼器給極端罕見事件分類
    它還能通過使用不同的體系結構實現模型改進的靈活性。因此,我們選擇嘗試使用深度學習的方法。在本文中,我們將學習如何使用一個簡單的全連接層自動編碼器來構建罕見事件分類器。本文是為了演示如何使用自動編碼器來實現極端罕見事件分類器的構建。用戶可以自行探索自動編碼器的不同架構和配置。
  • 【Keras教程】用Encoder-Decoder模型自動撰寫文本摘要
    ▌教程概述:該教程分為5部分,包括:編碼器-解碼器結構文本摘要編碼器文本摘要解碼器讀取源文本實現模型  編碼器-解碼器結構編碼器-解碼器結構是一種組織循環神經網絡用於序列預測問題的方法,其具有輸入、輸出或輸入和輸出變量。該結構涉及兩個組件:一個編碼器和一個解碼器。
  • 在Keras中可視化LSTM
    在本文中,我們不僅將在Keras中構建文本生成模型,還將可視化生成文本時某些單元格正在查看的內容。就像CNN一樣,它學習圖像的一般特徵,例如水平和垂直邊緣,線條,斑塊等。類似,在「文本生成」中,LSTM則學習特徵(例如空格,大寫字母,標點符號等)。 LSTM層學習每個單元中的特徵。
  • 深度學習筆記 | 第17講:深度生成模型之自編碼器(AutoEncoder)
    所謂自編碼器(Autoencoder,AE),就是一種利用反向傳播算法使得輸出值等於輸入值的神經網絡,它現將輸入壓縮成潛在空間表徵,然後將這種表徵重構為輸出。所以,從本質上來講,自編碼器是一種數據壓縮算法,其壓縮和解壓縮算法都是通過神經網絡來實現的。自編碼器有如下三個特點:數據相關性。就是指自編碼器只能壓縮與自己此前訓練數據類似的數據,比如說我們使用mnist訓練出來的自編碼器用來壓縮人臉圖片,效果肯定會很差。數據有損性。自編碼器在解壓時得到的輸出與原始輸入相比會有信息損失,所以自編碼器是一種數據有損的壓縮算法。
  • 通過Keras 構建基於 LSTM 模型的故事生成器
    編程實現 LSTM本文將通過 LSTM 網絡開發一個故事生成器模型。主要使用自然語言處理(NLP)進行數據預處理,使用雙向LSTM進行模型構建。from tensorflow.keras.preprocessing.sequence import pad_sequencesfrom tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout, Bidirectionalfrom tensorflow.keras.preprocessing.text
  • 時間序列的LSTM模型預測——基於Keras
    那麼面對時間序列的預測問題,我們可以用傳統的ARIMA模型,也可以用基於時間序列分解的STL模型或者Facebook開源的Prophet模型。在機器學習或者人工智慧大熱的現在,深度學習等機器學習方法也可以用於時間序列的預測。今天介紹的就是如何基於Keras和Python,實現時間序列的LSTM模型預測。
  • 深度學習第51講:自編碼器(AutoEncoder)及其keras實現
    自動編碼器是從數據樣本中自動學習的,這意味著很容易對指定類的輸入訓練出一種特定的編碼器,而不需要完成任何新工作。     構建一個自編碼器需要兩部分:編碼器(Encoder)和解碼器(Decoder)。編碼器將輸入壓縮為潛在空間表徵,可以用函數f(x)來表示,解碼器將潛在空間表徵重構為輸出,可以用函數g(x)來表示,編碼函數f(x)和解碼函數g(x)都是神經網絡模型。
  • 10行模型代碼帶你完成Bi-LSTM+CRF的NLP任務!
    今天我們就手把手教大家如何用Keras優雅的搭建Bi-LSTM+CRF模型。我們假設大家已經有了可供訓練的數據(文末提供數據)。其數據格式應該為這樣:這裡我們以字為單元進行標註。「O」是other的縮寫,表示非實體詞。「B」表示「開頭」,即實體詞的開頭字符,「I」表示「中間」,實體詞的中間字符。
  • 基於 Keras 用 LSTM 網絡做時間序列預測
    在本篇文章中,將介紹如何在 R 中使用 keras 深度學習包構建 LSTM 神經網絡模型實現時間序列預測。文章的主要內容:問題描述「航班旅客數據」是一個常用的時間序列數據集,該數據包含了 1949 至 1960 年 12 年間的月度旅客數據,共有 144 個觀測值。
  • Keras構建模型的一般流程
    這些基本點弄清楚了,構建複雜模型和構建簡單模型沒任何區別。序列式(sequential)建模有兩種方式。用全連接網絡(fully-connected neural network, FCNN)來建模,代碼吐下: