官术网_书友最值得收藏!

1.1.2 編碼器-解碼器結(jié)構(gòu)

我們繼續(xù)深入探索Seq2Seq結(jié)構(gòu),如圖1-3所示,模型的最外層一般是由一個(gè)編碼器和一個(gè)解碼器組成的。編碼器依次對(duì)輸入序列中的每個(gè)元素進(jìn)行處理,然后將獲取的信息壓縮成一個(gè)上下文向量,之后將上下文向量發(fā)送給解碼器,解碼器根據(jù)輸入序列和上下文向量依次生成輸出序列。編碼器和解碼器在早期一般采用RNN模塊,后來(lái)變成了LSTM(長(zhǎng)短期記憶網(wǎng)絡(luò))或GRU(門控循環(huán)單元)模塊,不過(guò)不管內(nèi)部模塊是什么,其主要特點(diǎn)就是可以提取時(shí)序特征信息。

圖1-3 Seq2Seq結(jié)構(gòu)的模型一般由一個(gè)編碼器和一個(gè)解碼器組成

接下來(lái)我們用PyTorch實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Seq2Seq結(jié)構(gòu)模型,并用于1.1.4節(jié)的實(shí)戰(zhàn)案例。本次實(shí)現(xiàn)主要分為以下幾步:首先實(shí)現(xiàn)Seq2Seq結(jié)構(gòu)模型的編碼器和解碼器部分;然后在解碼器中加入注意力機(jī)制;之后定義該任務(wù)的損失函數(shù);最后進(jìn)行訓(xùn)練和評(píng)估。

1.編碼器

在編碼器中,一般會(huì)先使用嵌入層(Embedding Layer)將輸入序列中的每一個(gè)詞元轉(zhuǎn)換為特征向量,嵌入層的參數(shù)是一個(gè)vocab_size×embed_size的矩陣,其中vocab_size表示詞表的大小,embed_size表示特征向量的維度。如果有多個(gè)編碼器,則詞向量的轉(zhuǎn)換僅發(fā)生在第一個(gè)編碼器的處理過(guò)程中。在底層的編碼器中輸入的是詞向量列表,而在其他編碼器中,輸入的是上一層編碼器的輸出。所有編碼器都有相同的輸入來(lái)源,這些輸入通常是一個(gè)大小為512的向量列表。列表的大小是可以通過(guò)設(shè)置超參數(shù)來(lái)調(diào)整的,通常設(shè)置為訓(xùn)練數(shù)據(jù)集中最長(zhǎng)句子的長(zhǎng)度。

python

class EncoderRNN(nn.Module):

  def __init__(self, input_size, hidden_size, dropout_p=0.1):

    super(EncoderRNN, self).__init__()

    self.hidden_size=hidden_size

    self.embedding=nn.Embedding(input_size, hidden_size)

    self.rnn=nn.RNN(hidden_size, hidden_size, batch_first=True)

    self.dropout=nn.Dropout(dropout_p)


  def forward(self, x):

    x=self.embedding(x)

    x=self.dropout(x)

    output, hidden=self.rnn(x)

    return output, hidden

這段代碼定義了一個(gè)名為EncoderRNN的類,用于將輸入的序列數(shù)據(jù)編碼成一個(gè)向量,稱為隱藏狀態(tài)向量。

在該類的初始化方法中,有如下3個(gè)主要的成員變量。

?self.hidden_size代表了隱藏狀態(tài)的維度。

?self.embedding是一個(gè)嵌入層,用于將輸入序列的每個(gè)元素映射成對(duì)應(yīng)的稠密向量表示。

?self.rnn是一個(gè)循環(huán)神經(jīng)網(wǎng)絡(luò)層,其輸入和輸出的維度都是hidden_size,并且采用batch_first=True的方式進(jìn)行設(shè)置。

在前向傳播過(guò)程中,首先,將輸入序列進(jìn)行嵌入操作并使用dropout進(jìn)行隨機(jī)失活處理,得到嵌入后的序列。然后,將嵌入后的序列輸入到RNN層中進(jìn)行編碼操作。最后,返回編碼后的輸出序列以及最終的隱藏(hidden)狀態(tài)作為輸出。

下面我們實(shí)例化一個(gè)編碼器對(duì)象,然后使用玩具數(shù)據(jù)查看輸入向量、輸出向量和隱藏狀態(tài)的維度。

python

encoder=EncoderRNN(input_size=10, hidden_size=5) #詞表大小為10,隱藏狀態(tài)維度為5

input_vector=th.tensor([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])

output, hidden=encoder(input_vector)

print("輸入向量的維度:", input_vector.size()) #輸入向量的維度: torch.Size([1, 10])

print("輸出向量的維度:", output.size())    #輸出向量的維度: torch.Size([1, 10, 5])

print("最終隱藏狀態(tài)的維度:", hidden.size())  #最終隱藏狀態(tài)的維度: torch.Size([1, 1, 5])

2.解碼器

如果Seq2Seq結(jié)構(gòu)的編碼器中有多個(gè)RNN層,則在解碼器中僅使用編碼器最后一層的輸出,該輸出內(nèi)容也被稱為上下文向量,因?yàn)樗鼘?duì)整個(gè)輸入序列的上下文進(jìn)行編碼。解碼器將使用此上下文向量進(jìn)行初始化,這其中隱含了一個(gè)限定條件,就是解碼器的隱藏狀態(tài)維度和編碼器的隱藏狀態(tài)維度必須相同。解碼器最終的輸出應(yīng)該是詞元的概率分布,因此在解碼器的最后一層使用全連接層進(jìn)行變換。

解碼器在對(duì)目標(biāo)序列的單詞進(jìn)行嵌入之前,需要將整個(gè)序列右移一位,然后在序列的開(kāi)頭增加一個(gè)標(biāo)志位SOS_token來(lái)表示開(kāi)始解碼。例如,當(dāng)目標(biāo)序列是“Hello World!”時(shí),實(shí)際輸入的應(yīng)該是[SOS_token, Hello, World, !]。具體的解碼過(guò)程其實(shí)是:輸入SOS_token,然后解碼器輸出Hello,將SOS_token和Hello拼接起來(lái)輸入解碼器,然后輸出World,以此類推,直到解碼器輸出結(jié)束詞元EOS_token為止。

解碼器在前向傳播生成結(jié)果時(shí),采用了強(qiáng)制學(xué)習(xí)的方法。此方法是指在訓(xùn)練中使用真實(shí)的目標(biāo)輸出作為下一個(gè)輸入,而不是使用解碼器猜測(cè)的輸出作為下一個(gè)輸入。例如,在前面這個(gè)例子中,當(dāng)我們輸入SOS_token時(shí),期望模型輸出Hello,但是如果它實(shí)際輸出了Hi,那我們會(huì)將其先存下來(lái),然后在下一輪輸入的時(shí)候,將SOS_token和正確答案Hello拼接起來(lái)輸入解碼器。使用強(qiáng)制學(xué)習(xí)的方法可以加快網(wǎng)絡(luò)的收斂速度,但當(dāng)應(yīng)用訓(xùn)練好的網(wǎng)絡(luò)時(shí),可能會(huì)出現(xiàn)不穩(wěn)定的情況。

python

class DecoderRNN(nn.Module):

  def __init__(self, hidden_size, output_size):

    super(DecoderRNN, self).__init__()

    self.embedding=nn.Embedding(output_size, hidden_size)

    self.rnn=nn.RNN(hidden_size, hidden_size, batch_first=True)

    self.out=nn.Linear(hidden_size, output_size)


  def forward(self, encoder_outputs, encoder_hidden, target_tensor=None):

    batch_size=encoder_outputs.size(0)

    decoder_input=th.empty(batch_size, 1, dtype=th.long).fill_(SOS_token)  # Start of Sentence詞元,表示開(kāi)始生成一個(gè)句子

    decoder_hidden=encoder_hidden

    decoder_outputs=[]

    for i in range(MAX_LENGTH):

      decoder_output, decoder_hidden =self.forward_step(decoder_input, decoder_hidden)

      decoder_outputs.append(decoder_output)

      if target_tensor is not None:  # 強(qiáng)制學(xué)習(xí)

        decoder_input=target_tensor[:, i].unsqueeze(1)

      else:

        _, topi=decoder_output.topk(1)

      decoder_input=topi.squeeze(-1).detach()


    decoder_outputs=th.cat(decoder_outputs, dim=1)

    decoder_outputs=F.log_softmax(decoder_outputs, dim=-1)

    return decoder_outputs, decoder_hidden, None


  def forward_step(self, x, hidden):

    x=self.embedding(x)

    x=F.relu(x)

    x, hidden=self.rnn(x, hidden)

    output=self.out(x)

    return output, hidden

這段代碼定義了一個(gè)名為DecoderRNN的類,用于根據(jù)編碼器的輸出和隱藏狀態(tài)逐步生成目標(biāo)序列。

在該類的初始化方法中,有三個(gè)主要的成員變量。

其中self.embedding和self.rnn前面已經(jīng)介紹過(guò),不再贅述。self.out是一個(gè)線性層,用于將RNN的輸出映射到目標(biāo)序列的維度。

在前向傳播過(guò)程中,首先根據(jù)編碼器的輸出和隱藏狀態(tài)初始化解碼器的第一個(gè)輸入,然后進(jìn)入一個(gè)循環(huán),循環(huán)次數(shù)為最大序列長(zhǎng)度(MAX_LENGTH)。在每一次循環(huán)中,調(diào)用forward_step方法來(lái)逐步生成目標(biāo)序列。強(qiáng)制學(xué)習(xí)方法會(huì)將目標(biāo)序列作為下一步的輸入。循環(huán)過(guò)程中的每一個(gè)輸出都被存儲(chǔ)在decoder_outputs列表中。

最后,在前向傳播的末尾,將存儲(chǔ)的所有輸出進(jìn)行拼接,然后經(jīng)過(guò)log_softmax函數(shù)進(jìn)行標(biāo)準(zhǔn)化,得到最終的decoder_outputs。同時(shí),返回最終的隱藏狀態(tài)(decoder_hidden)以及“None”以保持在訓(xùn)練循環(huán)中的一致性。

forward_step方法用于執(zhí)行每一步的解碼過(guò)程。它首先對(duì)輸入序列進(jìn)行嵌入操作,然后通過(guò)ReLU激活函數(shù)激活,接著經(jīng)過(guò)RNN層進(jìn)行解碼計(jì)算,再通過(guò)線性層映射到目標(biāo)序列的維度。最后,返回解碼結(jié)果和隱藏狀態(tài)。

下面我們實(shí)例化一個(gè)解碼器對(duì)象,然后使用玩具數(shù)據(jù)看一下最終輸出向量的維度。

python

decoder=DecoderRNN(hidden_size=5, output_size=10)

target_vector=th.tensor([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])

encoder_outputs, encoder_hidden=encoder(input_vector)

output, hidden, _=decoder(encoder_outputs, encoder_hidden, input_vector)

print("輸出向量的維度:", output.size()) #輸出向量的維度:[1, 10, 10]

最終輸出向量的第一個(gè)維度表示批量大小,第二個(gè)維度表示最大輸出長(zhǎng)度,即MAX_LENGTH,第三個(gè)維度表示詞表大小。

主站蜘蛛池模板: 上林县| 栾城县| 进贤县| 外汇| 通江县| 灵武市| 台州市| 彭泽县| 罗山县| 平武县| 万州区| 栖霞市| 佳木斯市| 农安县| 图木舒克市| 唐河县| 太仆寺旗| 景宁| 双峰县| 明水县| 博客| 贺州市| 大田县| 西丰县| 耒阳市| 夏邑县| 特克斯县| 瑞丽市| 东山县| 忻城县| 尼勒克县| 石台县| 西充县| 耒阳市| 夏河县| 新昌县| 蒙阴县| 阿拉尔市| 广昌县| 兴宁市| 天峻县|