【人工智能学习】【十三】注意力机制与Seq2Seq模型

问题来源

Encoder-Decoder模型可以根据Encoder产生的信息 c c c来作为Decoder的input来进行机器翻译, c c c是通过Encoder计算出来的,包含了被翻译内容的所有信息。但是通常某个词的翻译只和被翻译内容的一部分信息有关,比如“我爱做饭。”,翻译成"I love cooking.",cooking的翻译只和"做饭"有关。还有其他的例子,比如某个词的翻译依赖于前面某些信息,让机器做一个阅读理解题之类的。对于短句来讲,Attention的有点不明显,但如果句子比较长,语义编码完全依赖于一个 c c c向量会丢失很多信息,这也是为什么会有Attention机制的原因。
Attention机制是2016年谷歌一篇机器翻译的论文带火的,虽然在199几年就有这个思想。

Attention原理

之前Decoder的预测模型是这样
y 1 = f ( C ) y_1=f(C) y1=f(C)
y 2 = f ( C , H y − 1 ) y_2=f(C,H_{y-1}) y2=f(C,Hy1)
y 3 = f ( C , H y − 1 ) y_3=f(C,H_{y-1}) y3=f(C,Hy1)
… … ……
这意味着每个 y y y都依赖相同的信息 C C C,现在要想每个 y i y_i yi依赖的信息不同,将上式做一个变化
y 1 = f ( C 1 ) y_1=f(C_1) y1=f(C1)
y 2 = f ( C 2 , H y − 1 ) y_2=f(C_2,H_{y-1}) y2=f(C2,Hy1)
y 3 = f ( C 3 , H y − 1 ) y_3=f(C_3,H_{y-1}) y3=f(C3,Hy1)
… … ……

比如我现在要翻译“我爱做饭。”,对于这句话来讲,对“cooking”的翻译贡献大小如下:
(我,0.3)(爱,0.2) (做饭,0.5)
可见“做饭”对“cooking”的翻译贡献最大。这意味着翻译出来的结果对原来信息的每个词的关注量是不一样的,计算 y i y_i yi对每个单词的关注量会对翻译结果产生帮助。增加注意力机制的Encoder-Decoder模型就变成:
在这里插入图片描述
那么现在的问题变成了如何求 C i C_i Ci
这张图的 h 1 h_1 h1 h 2 h_2 h2 h 3 h_3 h3 h 4 h_4 h4是Encoder的输入序列, z 0 z_0 z0是Decoder初始化的语义编码向量(代码里在Decoder做的初始化),看箭头 z 0 z_0 z0 h 1 h_1 h1经过一个match function,变成了 α 0 1 \alpha_0^1 α01,这个 α 0 1 \alpha_0^1 α01首先是个标量,这个值包含了 z 0 z_0 z0 h 1 h_1 h1的信息,称作匹配程度。
在这里插入图片描述
match function可以自己来定义,常用的有如下几种:

  1. 余弦相似度
  2. 全连接神经网络
  3. 点积

我们以点积为例,计算公式如下:
α = h T W z \alpha=h^TWz α=hTWz
W W W是我们需要训练的权重矩阵。
那么
α j i = h i T W z j \alpha_j^i=h_i^TWz_j αji=hiTWzj
j j j是Decoder里的序列号
这样计算我们可以得到 z 0 z_0 z0对应的 α 0 1 \alpha_0^1 α01 α 0 2 \alpha_0^2 α02 α 0 3 \alpha_0^3 α03 α 0 4 \alpha_0^4 α04
在这里插入图片描述
然后对 α 0 1 \alpha_0^1 α01 α 0 2 \alpha_0^2 α02 α 0 3 \alpha_0^3 α03 α 0 4 \alpha_0^4 α04做了一步可能不是那么必要的softmax(李宏毅说的),把 α 0 1 ˊ \acute{\alpha_0^1} α01ˊ α 0 2 ˊ \acute{\alpha_0^2} α02ˊ α 0 3 ˊ \acute{\alpha_0^3} α03ˊ α 0 4 ˊ \acute{\alpha_0^4} α04ˊ的和变成1(看起来是标准化一下)
在这里插入图片描述
接着把这四个词通过下面公式计算出 c 0 c_0 c0
c 0 = ∑ a 0 i ˊ h i c_0=\sum\acute{a^i_0}h^i c0=a0iˊhi
在这里插入图片描述
这个 c 0 c_0 c0就是要得到的注意力信息了,是个和 h h h尺寸一样的向量,代表着 z 0 z_0 z0和输入向量序列的关系度。然后把 c 0 c_0 c0作为Decoder下一次序列的输入进行预测,把hidden state z 1 z_1 z1继续安装上面的方式计算。关于 z 1 z_1 z1是如何计算得出的,现在没有公认做好的做法,怎么做都可以。
在这里插入图片描述
z 1 z_1 z1继续Encoder的 h i h^i hi计算,得到 a 1 i a^i_1 a1i
在这里插入图片描述
这样就计算出了 c i c_i ci(由于和图片用的字母不一样,图里的 z i z_i zi就是这里的 c i c_i ci

Seq2Seq

Seq2Seq模型1是说序列对序列,是Encoder-Decoder思想的一种应用实现,我觉得没啥大的区别,提到Seq2Seq就想是NLP领域的Encoder-Decoder就好,毕竟Encoder-Decoder还可以做图像。这里介绍的是加入Attention的Seq2Seq模型。在这里插入图片描述
训练阶段,保存了Encoder侧所有的hidden state,Key是时间步,Value是state向量值。在Decoder侧,每次按照上面Attention里介绍的,去计算相关性。
解码器部分

class Seq2SeqAttentionDecoder(d2l.Decoder):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
                 dropout=0, **kwargs):
        super(Seq2SeqAttentionDecoder, self).__init__(**kwargs)
        self.attention_cell = MLPAttention(num_hiddens,num_hiddens, dropout)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.LSTM(embed_size+ num_hiddens,num_hiddens, num_layers, dropout=dropout)
        self.dense = nn.Linear(num_hiddens,vocab_size)

    def init_state(self, enc_outputs, enc_valid_len, *args):
        outputs, hidden_state = enc_outputs
#         print("first:",outputs.size(),hidden_state[0].size(),hidden_state[1].size())
        # Transpose outputs to (batch_size, seq_len, hidden_size)
        return (outputs.permute(1,0,-1), hidden_state, enc_valid_len)
        #outputs.swapaxes(0, 1)
        
    def forward(self, X, state):
        enc_outputs, hidden_state, enc_valid_len = state
        #("X.size",X.size())
        X = self.embedding(X).transpose(0,1)
#         print("Xembeding.size2",X.size())
        outputs = []
        for l, x in enumerate(X):
#             print(f"\n{l}-th token")
#             print("x.first.size()",x.size())
            # query shape: (batch_size, 1, hidden_size)
            # select hidden state of the last rnn layer as query
            query = hidden_state[0][-1].unsqueeze(1) # np.expand_dims(hidden_state[0][-1], axis=1)
            # context has same shape as query
#             print("query enc_outputs, enc_outputs:\n",query.size(), enc_outputs.size(), enc_outputs.size())
            context = self.attention_cell(query, enc_outputs, enc_outputs, enc_valid_len)
            # Concatenate on the feature dimension
#             print("context.size:",context.size())
            x = torch.cat((context, x.unsqueeze(1)), dim=-1)
            # Reshape x to (1, batch_size, embed_size+hidden_size)
#             print("rnn",x.size(), len(hidden_state))
            out, hidden_state = self.rnn(x.transpose(0,1), hidden_state)
            outputs.append(out)
        outputs = self.dense(torch.cat(outputs, dim=0))
        return outputs.transpose(0, 1), [enc_outputs, hidden_state,
                                        enc_valid_len]

训练

import zipfile
import torch
import requests
from io import BytesIO
from torch.utils import data
import sys
import collections

class Vocab(object): # This class is saved in d2l.
  def __init__(self, tokens, min_freq=0, use_special_tokens=False):
    # sort by frequency and token
    counter = collections.Counter(tokens)
    token_freqs = sorted(counter.items(), key=lambda x: x[0])
    token_freqs.sort(key=lambda x: x[1], reverse=True)
    if use_special_tokens:
      # padding, begin of sentence, end of sentence, unknown
      self.pad, self.bos, self.eos, self.unk = (0, 1, 2, 3)
      tokens = ['', '', '', '']
    else:
      self.unk = 0
      tokens = ['']
    tokens += [token for token, freq in token_freqs if freq >= min_freq]
    self.idx_to_token = []
    self.token_to_idx = dict()
    for token in tokens:
      self.idx_to_token.append(token)
      self.token_to_idx[token] = len(self.idx_to_token) - 1
      
  def __len__(self):
    return len(self.idx_to_token)
  
  def __getitem__(self, tokens):
    if not isinstance(tokens, (list, tuple)):
      return self.token_to_idx.get(tokens, self.unk)
    else:
      return [self.__getitem__(token) for token in tokens]
    
  def to_tokens(self, indices):
    if not isinstance(indices, (list, tuple)):
      return self.idx_to_token[indices]
    else:
      return [self.idx_to_token[index] for index in indices]

def load_data_nmt(batch_size, max_len, num_examples=1000):
    """Download an NMT dataset, return its vocabulary and data iterator."""
    # Download and preprocess
    def preprocess_raw(text):
        text = text.replace('\u202f', ' ').replace('\xa0', ' ')
        out = ''
        for i, char in enumerate(text.lower()):
            if char in (',', '!', '.') and text[i-1] != ' ':
                out += ' '
            out += char
        return out 


    with open('/home/kesci/input/fraeng6506/fra.txt', 'r') as f:
      raw_text = f.read()


    text = preprocess_raw(raw_text)

    # Tokenize
    source, target = [], []
    for i, line in enumerate(text.split('\n')):
        if i >= num_examples:
            break
        parts = line.split('\t')
        if len(parts) >= 2:
            source.append(parts[0].split(' '))
            target.append(parts[1].split(' '))

    # Build vocab
    def build_vocab(tokens):
        tokens = [token for line in tokens for token in line]
        return Vocab(tokens, min_freq=3, use_special_tokens=True)
    src_vocab, tgt_vocab = build_vocab(source), build_vocab(target)

    # Convert to index arrays
    def pad(line, max_len, padding_token):
        if len(line) > max_len:
            return line[:max_len]
        return line + [padding_token] * (max_len - len(line))

    def build_array(lines, vocab, max_len, is_source):
        lines = [vocab[line] for line in lines]
        if not is_source:
            lines = [[vocab.bos] + line + [vocab.eos] for line in lines]
        array = torch.tensor([pad(line, max_len, vocab.pad) for line in lines])
        valid_len = (array != vocab.pad).sum(1)
        return array, valid_len

    src_vocab, tgt_vocab = build_vocab(source), build_vocab(target)
    src_array, src_valid_len = build_array(source, src_vocab, max_len, True)
    tgt_array, tgt_valid_len = build_array(target, tgt_vocab, max_len, False)
    train_data = data.TensorDataset(src_array, src_valid_len, tgt_array, tgt_valid_len)
    train_iter = data.DataLoader(train_data, batch_size, shuffle=True)
    return src_vocab, tgt_vocab, train_iter

训练调用

embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.0
batch_size, num_steps = 64, 10
lr, num_epochs, ctx = 0.005, 500, d2l.try_gpu()

src_vocab, tgt_vocab, train_iter = load_data_nmt(batch_size, num_steps)
encoder = d2l.Seq2SeqEncoder(
    len(src_vocab), embed_size, num_hiddens, num_layers, dropout)
decoder = Seq2SeqAttentionDecoder(
    len(tgt_vocab), embed_size, num_hiddens, num_layers, dropout)
model = d2l.EncoderDecoder(encoder, decoder)

参考资料

深度学习中的注意力机制(2017版)
台湾李宏毅老师-机器学习


  1. Sutskever, I., Vinyals, O., & Le, Q. V. (2014). Sequence to sequence learning with neural networks. In Advances in neural information processing systems (pp. 3104-3112). ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值