今天,我们将探讨序列到序列 (seq2seq) 模型的复杂工作原理,特别关注编码器-解码器架构和注意力机制。这些概念是各种 NLP 应用的基础,从机器翻译到问答系统。
这是可以期待的:
请继续关注这些先进的 NLP 概念,将理论见解与实际应用相结合,丰富多彩的旅程。无论您是初学者还是经验丰富的从业者,这篇博文都旨在增强您在 NLP 动态领域的理解和技能。
序列到序列模型彻底改变了我们在 NLP 中处理语言任务的方式。核心思想是将输入序列(如句子中的单词)映射到输出序列(如另一种语言的翻译单词)。这种映射是通过两个主要组件实现的:编码器和解码器,通常使用长短期记忆 (LSTM) 网络或门控循环单元 (GRU) 实现。
编码器的工作是读取和处理输入序列。在 LSTM 的背景下,这涉及:
编码器的最终内部状态(我们称之为上下文向量)被认为封装了整个输入序列的信息,为解码器生成有意义的输出奠定了基础。
解码器是另一个 LSTM 网络,它接管编码器中断的地方。它使用编码器的最终状态作为其初始状态:
解码器通过对上下文向量及其先前的输出进行条件反射,有效地学习生成目标序列。
虽然编码器-解码器架构为序列映射提供了强大的框架,但它并非没有局限性。一个关键问题是依赖于固定长度的上下文向量来编码整个输入序列,这对于长序列来说可能是个问题。这就是注意力机制发挥作用的地方。
注意力机制允许解码器将注意力集中在编码器输出的不同部分,用于解码器自身输出的每一步。从本质上讲,它计算权重分布(或注意力分数),以确定每个输入元素对每个输出的重要性。
注意力机制提供了更动态的编码过程,使模型能够为更长的序列生成更准确和连贯的输出。
为简单起见,我们将使用一种非常基本的预处理形式。
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
import numpy as np
def data_preprocessor(source_sentences, target_sentences):
source_tokenizer = Tokenizer()
source_tokenizer.fit_on_texts(source_sentences)
source_sequences = source_tokenizer.texts_to_sequences(source_sentences)
source_padded = pad_sequences(source_sequences, padding='post')
target_tokenizer = Tokenizer()
target_tokenizer.fit_on_texts(target_sentences)
target_sequences = target_tokenizer.texts_to_sequences(target_sentences)
target_padded = pad_sequences(target_sequences, padding='post')
return source_padded, target_padded, source_tokenizer, target_tokenizer
english_sentences = ['hello', 'world', 'how are you', 'I am fine', 'have a good day']
spanish_sentences = ['hola', 'mundo', 'cómo estás', 'estoy bien', 'ten un buen día']
input_texts, target_texts, source_tokenizer, target_tokenizer = data_preprocessor(english_sentences, spanish_sentences)
接下来,我们用注意力层构建 seq2seq 模型。
from tensorflow.keras.layers import Input, LSTM, Dense, Embedding, Concatenate
from tensorflow.keras.layers import AdditiveAttention as Attention
from tensorflow.keras.models import Model
# Model parameters
embedding_dim = 256
latent_dim = 512
num_encoder_tokens = len(source_tokenizer.word_index) + 1
num_decoder_tokens = len(target_tokenizer.word_index) + 1
# Encoder
encoder_inputs = Input(shape=(None,))
encoder_embedding = Embedding(num_encoder_tokens, embedding_dim)(encoder_inputs)
encoder_lstm = LSTM(latent_dim, return_state=True)
encoder_outputs, state_h, state_c = encoder_lstm(encoder_embedding)
encoder_states = [state_h, state_c]
# Decoder
decoder_inputs = Input(shape=(None,))
decoder_embedding = Embedding(num_decoder_tokens, embedding_dim)(decoder_inputs)
decoder_lstm = LSTM(latent_dim, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_embedding, initial_state=encoder_states)
# Attention Layer
attention = Attention()
attention_output = attention([decoder_outputs, encoder_outputs])
# Concatenating attention output and decoder LSTM output
decoder_concat_input = Concatenate(axis=-1)([decoder_outputs, attention_output])
# Dense layer
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_concat_input)
# Define the model
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
我们将目标文本转换为分类数据进行训练。请注意,在实际场景中,您应该使用更多数据并执行训练-测试拆分。
from tensorflow.keras.utils import to_categorical
decoder_target_data = to_categorical(target_texts, num_decoder_tokens)
model.fit([input_texts, target_texts], decoder_target_data, batch_size=64, epochs=50, validation_split=0.2)
为编码器和解码器设置推理模型。
# Encoder Inference Model
encoder_model = Model(encoder_inputs, encoder_states)
# Decoder Inference Model
decoder_state_input_h = Input(shape=(latent_dim,))
decoder_state_input_c = Input(shape=(latent_dim,))
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]
decoder_outputs, state_h, state_c = decoder_lstm(decoder_embedding, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]
decoder_outputs = decoder_dense(decoder_outputs)
decoder_model = Model([decoder_inputs] + decoder_states_inputs, [decoder_outputs] + decoder_states)
最后,让我们为翻译过程创建一个函数。
def translate(input_text):
# Tokenize and pad the input sequence
input_seq = source_tokenizer.texts_to_sequences([input_text])
input_seq = pad_sequences(input_seq, maxlen=input_texts.shape[1], padding='post')
# Get the encoder states
states_value = encoder_model.predict(input_seq)
# Generate an empty target sequence of length 1
target_seq = np.zeros((1, 1))
# Populate the first character of the target sequence with the start character
target_seq[0, 0] = target_tokenizer.word_index['start'] # Assuming 'start' is the start token
stop_condition = False
decoded_sentence = ''
while not stop_condition:
output_tokens, h, c = decoder_model.predict([target_seq] + states_value)
# Sample a token
sampled_token_index = np.argmax(output_tokens[0, -1, :])
sampled_char = target_tokenizer.index_word[sampled_token_index]
decoded_sentence += ' ' + sampled_char
# Exit condition: either hit max length or find stop token.
if (sampled_char == 'end' or len(decoded_sentence) > 50): # Assuming 'end' is the end token
stop_condition = True
# Update the target sequence (of length 1).
target_seq = np.zeros((1, 1))
target_seq[0, 0] = sampled_token_index
# Update states
states_value = [h, c]
return decoded_sentence
# Example usage
translated_sentence = translate("hello")
print(translated_sentence)
此代码提供了一个基本框架来理解具有注意力的 seq2seq 模型的工作原理。请记住,这是一个简化的示例。对于实际应用,您需要更复杂的预处理、更大的数据集和模型参数的微调。
在 NLP 系列的中,我们深入研究了序列到序列模型的复杂性,特别关注编码器-解码器架构和注意力机制。这种探索提供了对它们在各种 NLP 应用(如机器翻译和文本摘要)中的重要作用的见解。我们通过一个实际的例子来说明这些概念,强调它们在复杂的语言处理任务中的有效性。
正如我们总结的那样,很明显,NLP领域的旅程是持续和动态的。展望未来,我们的下一章“NLP 中的变形金刚:解码游戏规则改变者”将深入探讨变形金刚模型。我们将探索开创性的“注意力就是你所需要的一切”论文,并了解 transformer 架构的具体细节,这标志着 NLP 领域的重大转变。