Внимание - это все, что вам нужно: Хотите узнать, Берт? Начать с «головы»

искусственный интеллект NLP

Эта статья одновременно публикуется в публичном аккаунте WeChat: А Ли голосует за то, чтобы накормить дом

Управляемое чтение

Это следует рассматривать как обзорную статью.Когда Трансформер почти правил всеми полями, вспоминать Трансформера было немного расплывчато. Тем не менее, статус Трансформера в области глубокого обучения должен быть бесспорным, будь то Берт в области НЛП или Визуальный Трансформер в области CV, мы это видим. Поэтому необходимо иметь глубокое понимание Transformer.В этой статье мы представим структуру Transformer и некоторые детали его реализации в виде обзора.

Рекомендуемые статьи, связанные с серией Берта

Чему учит BERT: узнайте, почему BERT такой сильный

TinyBert: Модель дистилляции для сверхдетализированных приложений, просто посмотрите на него, если у вас возникнут вопросы о дистилляции.

Технология квантования и динамическое квантование Альберта

DistillBert: Берт слишком дорого? Я дешевый и простой в использовании

[Обмен документами] | RoBERTa: Привет, XLNet здесь? Меня избили

Введение в статью XLNet - за спиной Берта

Механизм внимания

Преобразователь представляет собой структуру кодирования-декодирования Структура кодировщика и декодера аналогична, и они объединены несколькими идентичными слоями. Каждый уровень кодировщика разделен на два подуровня: уровень внимания и уровень полной связи. Каждый уровень в декодере разделен на три подуровня, которые представляют собой два уровня внимания и один полносвязный уровень.

  • Однослойное внимание

Структура слоя внимания показана на рисунке ниже, а метод расчетаAttention(Q,K,V)=softmax(QKTdk)VAttention(Q,K,V)=softmax(\frac {QK^T}{\sqrt{d_k}})V, этот шаг заключается в вычислении корреляции между Q и K. Благодаря этому вычислению слова, которые более связаны между контекстами, будут вычисляться в соответствии с весом корреляции. В Само-внимании матричный источник QKV является одним и тем же входом.

Использование факела для реализации однослойного внимания происходит следующим образом.

class ScaledDotProductAttention(nn.Module):
    ''' Scaled Dot-Product Attention '''
    de __init__(self, temperature, attn_dropout=0.1):
        super().__init__()
        self.temperature = temperature
        self.dropout = nn.Dropout(attn_dropout)

    def forward(self, q, k, v, mask=None):

        attn = torch.matmul(q / self.temperature, k.transpose(2, 3))

        if mask is not None:
            attn = attn.masked_fill(mask == 0, -1e9)

        attn = self.dropout(F.softmax(attn, dim=-1))
        output = torch.matmul(attn, v)

        return output, attn
  • MultiHead Attention

Структура многоголового внимания показана на рисунке ниже и представляет собой объединение результатов одноуровневого внимания, которое здесь подробно объясняться не будет.

Подробное объяснение структуры трансформатора

Вышеупомянутая структура внимания В этой главе мы познакомимся со структурой Трансформера. Прежде всего, мы введем встраивание позиций.

Выше мы представили, что Transformer представляет собой структуру кодировщик-декодер, и вход кодировщика включает не только встраивание слов, но и встраивание позиций. Причина добавления встраивания позиции заключается в использовании информации о последовательности входного текста. Расчет встраивания позиций выглядит следующим образом: вложение позиции

class PositionalEncoding(nn.Module):

    def __init__(self, d_hid, n_position=200):
        super(PositionalEncoding, self).__init__()

        # Not a parameter
        self.register_buffer('pos_table', self._get_sinusoid_encoding_table(n_position, d_hid))

    def _get_sinusoid_encoding_table(self, n_position, d_hid):
        ''' Sinusoid position encoding table '''
        # TODO: make it with torch instead of numpy

        def get_position_angle_vec(position):
            return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]

        sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
        sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])  # dim 2i
        sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])  # dim 2i+1

        return torch.FloatTensor(sinusoid_table).unsqueeze(0)

    def forward(self, x):
        return x + self.pos_table[:, :x.size(1)].clone().detach()

1. Encoder

Вход кодировщика - встраивание позиций и встраивание слов.После многоголового внимания и полностью подключенной сети результат выводится в декодер.

class EncoderLayer(nn.Module):
    ''' Compose with two layers '''

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(EncoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

    def forward(self, enc_input, slf_attn_mask=None):
        enc_output, enc_slf_attn = self.slf_attn(
            enc_input, enc_input, enc_input, mask=slf_attn_mask)
        enc_output = self.pos_ffn(enc_output)
        return enc_output, enc_slf_attn

2. Decoder

Декодер имеет двухуровневую структуру внимания, первый уровень — это внимание к себе, а вход второго уровня — это выход кодировщика и выход собственного внимания. Чтобы декодер не мог узнать что-то после текущей позиции, ввод должен быть замаскирован на этапе самоконтроля. Маска второго слоя внимания такая же, как маска кодировщика.

class DecoderLayer(nn.Module):
    ''' Compose with three layers '''

    def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
        super(DecoderLayer, self).__init__()
        self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
        self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)

    def forward(
            self, dec_input, enc_output,
            slf_attn_mask=None, dec_enc_attn_mask=None):
        dec_output, dec_slf_attn = self.slf_attn(
            dec_input, dec_input, dec_input, mask=slf_attn_mask)
        dec_output, dec_enc_attn = self.enc_attn(
            dec_output, enc_output, enc_output, mask=dec_enc_attn_mask)
        dec_output = self.pos_ffn(dec_output)
        return dec_output, dec_slf_attn, dec_enc_attn

Выводы и размышления

Эта статья представляет собой не столько интерпретацию Transformer, сколько интерпретацию исходного кода Transformer.При чтении статьи есть некоторые места, на которые я только что посмотрел, но когда я действительно смотрю на исходный код, я обнаруживаю, что есть много деталей, которые не были учтены в то время. Может быть, это просто поверхностность на бумаге. Вот некоторые мысли

  1. Почему ввод декодера должен быть добавлен к исходному вводу?
  2. Зачем добавлять маску, можно ли ее не указывать?

Reference

  1. Rush A . The Annotated Transformer[C]// Proceedings of Workshop for NLP Open Source Software (NLP-OSS). 2018.
  2. Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need[J]. arXiv preprint arXiv:1706.03762, 2017.