1. 程式人生 > >PyTorch使用seq2seq+attention實現時間格式轉換

PyTorch使用seq2seq+attention實現時間格式轉換

pytorch實現seq2seq+attention轉換日期


使用keras實現加入注意力機制的seq2seq比較麻煩,所以這裡我嘗試使用機器翻譯的seq2seq+attention模型實現人造日期對標準日期格式的轉換。

所copy的程式碼來自practical-pytorch教程,以及pytorch-seq2seq教程

所用的資料來自注意力機制keras實現
python3
pytorch版本 0.4.0
可能需要GPU

import json
from matplotlib import ticker
from collections import Counter
import
matplotlib.pyplot as plt import torch from torch import nn import torch.nn.functional as F import torch.optim as optim import numpy as np device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') device
device(type='cuda')

預處理


這裡先生成字元和數字相互轉換的字典,如果是句子也可以按照詞為單位。我在字典的開頭添加了4種表示。

def
build_vocab(texts, n=None):
counter = Counter(''.join(texts)) # char level char2index = {w: i for i, (w, c) in enumerate(counter.most_common(n), start=4)} char2index['~'] = 0 # pad 不足長度的文字在後邊填充0 char2index['^'] = 1 # sos 表示句子的開頭 char2index['$'] = 2 # eos 表示句子的結尾 char2index['#'
] = 3 # unk 表示句子中出現的字典中沒有的未知詞 index2char = {i: w for w, i in char2index.items()} return char2index, index2char

先看一下資料的格式。

pairs = json.load(open('Time Dataset.json', 'rt', encoding='utf-8'))
print(pairs[:1])
[['six hours and fifty five am', '06:55']]

我們將目標文字和原文字分開,求出兩邊句子的最大長度,然後建立兩邊各自的字典。

data = array(pairs)
src_texts = data[:, 0]
trg_texts = data[:, 1]
src_c2ix, src_ix2c = build_vocab(src_texts)
trg_c2ix, trg_ix2c = build_vocab(trg_texts)

這裡按批量跟新,定義一個隨機批量生成的函式,它能夠將文字轉換成字典中的數字表示,並同時返回batch_size個樣本和它們的長度,這些樣本按照長度降序排序。pad的長度以batch中最長的為準。這主要是為了適應pack_padded_sequence這個函式,因為輸入RNN的序列不需要將pad標誌也輸入RNN中計算,RNN只需要迴圈計算到其真實長度即可。

def indexes_from_text(text, char2index):
    return [1] + [char2index[c] for c in text] + [2]  # 手動新增開始結束標誌
def pad_seq(seq, max_length):
    seq += [0 for _ in range(max_length - len(seq))]
    return seq

max_src_len = max(list(map(len, src_texts))) + 2
max_trg_len = max(list(map(len, trg_texts))) + 2
max_src_len, max_trg_len
(43, 7)
def random_batch(batch_size, pairs, src_c2ix, trg_c2ix):
    input_seqs, target_seqs = [], []

    for i in random.choice(len(pairs), batch_size):
        input_seqs.append(indexes_from_text(pairs[i][0], src_c2ix))
        target_seqs.append(indexes_from_text(pairs[i][1], trg_c2ix))

    seq_pairs = sorted(zip(input_seqs, target_seqs), key=lambda p: len(p[0]), reverse=True)
    input_seqs, target_seqs = zip(*seq_pairs)
    input_lengths = [len(s) for s in input_seqs]
    input_padded = [pad_seq(s, max(input_lengths)) for s in input_seqs]
    target_lengths = [len(s) for s in target_seqs]
    target_padded = [pad_seq(s, max(target_lengths)) for s in target_seqs]

    input_var = torch.LongTensor(input_padded).transpose(0, 1)  
    # seq_len x batch_size
    target_var = torch.LongTensor(target_padded).transpose(0, 1)
    input_var = input_var.to(device)
    target_var = target_var.to(device)

    return input_var, input_lengths, target_var, target_lengths

可以先列印一下,batch_size=3時的返回結果。注意這裡batch經過了轉置。

random_batch(3, data, src_c2ix, trg_c2ix)
(tensor([[  1,   1,   1],
         [ 12,  23,   6],
         [  7,   9,  18],
         [ 27,  26,  21],
         [ 10,  23,  23],
         [  4,   4,  25],
         [ 16,  17,   2],
         [  7,   9,   0],
         [ 27,  11,   0],
         [ 10,   9,   0],
         [ 19,   2,   0],
         [  4,   0,   0],
         [ 13,   0,   0],
         [  8,   0,   0],
         [ 32,   0,   0],
         [  4,   0,   0],
         [  6,   0,   0],
         [ 31,   0,   0],
         [  5,   0,   0],
         [  8,   0,   0],
         [  6,   0,   0],
         [ 20,   0,   0],
         [  4,   0,   0],
         [ 12,   0,   0],
         [ 14,   0,   0],
         [ 28,   0,   0],
         [  5,   0,   0],
         [  4,   0,   0],
         [ 13,   0,   0],
         [ 12,   0,   0],
         [  6,   0,   0],
         [  5,   0,   0],
         [ 10,   0,   0],
         [  4,   0,   0],
         [  8,   0,   0],
         [  7,   0,   0],
         [  7,   0,   0],
         [  8,   0,   0],
         [  2,   0,   0]], device='cuda:0'),
 [39, 11, 7],
 tensor([[  1,   1,   1],
         [  6,   6,   5],
         [ 13,   9,   7],
         [  4,   4,   4],
         [  7,   5,   8],
         [  9,   8,  10],
         [  2,   2,   2]], device='cuda:0'),
 [7, 7, 7])

模型定義


模型分為encoder和decoder兩個部分,decoder部分比較簡單,就是一層Embedding層加上兩層GRU。之前處理的batch的格式主要是為了使用pack_padded_sequence和pad_packed_sequence這兩個類對GRU輸入輸出批量處理。一定要注意各個變數的shape。

class Encoder(nn.Module):
    def __init__(self, input_dim, embedding_dim, hidden_dim, num_layers=2, dropout=0.2):
        super().__init__()

        self.input_dim = input_dim
        self.embedding_dim = embedding_dim
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        self.dropout = dropout
        # input_dim = vocab_size + 1
        self.embedding = nn.Embedding(input_dim, embedding_dim)

        self.rnn = nn.GRU(embedding_dim, hidden_dim,
                          num_layers=num_layers, dropout=dropout)

        self.dropout = nn.Dropout(dropout)

    def forward(self, input_seqs, input_lengths, hidden=None):
        # src = [sent len, batch size]
        embedded = self.dropout(self.embedding(input_seqs))
        # embedded = [sent len, batch size, emb dim]
        packed = torch.nn.utils.rnn.pack_padded_sequence(embedded, input_lengths)
        outputs, hidden = self.rnn(packed, hidden)
        outputs, output_lengths = torch.nn.utils.rnn.pad_packed_sequence(outputs)
        # outputs, hidden = self.rnn(embedded, hidden)
        # outputs = [sent len, batch size, hid dim * n directions]
        # hidden = [n layers, batch size, hid dim]
        # outputs are always from the last layer
        return outputs, hidden

首先定義一下Attention層,這裡主要是對encoder的輸出進行attention操作,也可以直接對embedding層的輸出進行attention。
論文Neural Machine Translation by Jointly Learning to Align and Translate中定義了attention的計算公式。

decoder的輸出取決於decoder先前的輸出和 x , 這裡 x 包括當前GRU輸出的hidden state(這部分已經考慮了先前的輸出) 以及attention(上下文向量,由encoder的輸出求得)。 計算公式如下:函式 g 非線性啟用的全連線層,輸入是 y i 1 , s i , and c i 三者的拼接。

p ( y i { y 1 , . . . , y i 1 } , x ) = g ( y i 1 , s i , c i )

所謂的上下文向量就是對encoder的所有輸出進行加權求和, a i j 表示輸出的第 i 個詞對encoder第 j 個輸出 h j 的權重。

c i = j = 1 T x a i j h j

每個 a i j 通過對所有 e i j 進行softmax,而每個 e i j 是decoder的上一個hidden state s i 1 和指定的encoder的輸出 h j 經過某些線性操作 a 計算得分。

a i j = e x p ( e i j ) k = 1 T e x p ( e i k ) e i j = a ( s i 1 , h j )

此外,論文Effective Approaches to Attention-based Neural Machine Translation中提出了計算分值的不同方式。這裡用到的是第三種。

s c o r e ( h t , h ¯