Encoder和Decoder
其实现在发现其实LSTM,GRU就是一个Encoder和Decoder的例子。我们把最后的全链接层想象成解码前,前面的RNN网络想象成编码器。是不是有那味儿了。
class Encoder(nn.Module):
"""编码器-解码器结构的基本编码器接口。"""
def __init__(self, **kwargs):
super(Encoder, self).__init__(**kwargs)
def forward(self, X, *args):
raise NotImplementedError
#编码器你看,其实跟之前的网络是相似的,没什么太大的差别,给一个X输出一个东西
Encoder,其实乍一看,你会发现和我们之前的网络结构没有什么本质上的区别
class Decoder(nn.Module):
"""编码器-解码器结构的基本解码器接口。"""
def __init__(self, **kwargs):
super(Decoder, self).__init__(**kwargs)
def init_state(self, enc_outputs, *args):
raise NotImplementedError
def forward(self, X, state):
raise NotImplementedError
# 解码器就有点不同了,首先他会根据encoder传过来的输入,更新自己的状态
# 其次,他也有自己的输入X,这个就是自己很奇妙的地方,就是他有自己的输入
Decoder也只是略有不同。首先,他需要初始化一个隐藏状态,这个隐藏状态是来自于Encoder的输出的。其次,对于Deocder,他是有一个自己的输入的。
class EncoderDecoder(nn.Module):
def __init__(self,encoder,decoder,**kwargs):
super(EncoderDecoder,self).__init__(**kwargs)
self.encoder = encoder
self.decoder = decoder
def forward(self,enc_X,dec_X,*args):
enc_outputs = self.encoder(enc_X,*args)
dec_state = self.decoder.init_state(enc_outputs,*args)
return self.decoder(dec_X,dec_state)
再写一个类包装它的话,就是这样。
现在我们真正来实现一个Seq2Seq的模型
class Seq2SeqEncoder(d2l.Encoder):
"""用于序列到序列学习的循环神经网络编码器。"""
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqEncoder, self).__init__(**kwargs)
# 嵌入层
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,
dropout=dropout)
def forward(self, X, *args):
# 输出'X'的形状:(`batch_size`, `num_steps`, `embed_size`)
X = self.embedding(X)
# 在循环神经网络模型中,第一个轴对应于时间步
X = X.permute(1, 0, 2)
# 如果未提及状态,则默认为0
output, state = self.rnn(X)
# `output`的形状: (`num_steps`, `batch_size`, `num_hiddens`)
# `state[0]`的形状: (`num_layers`, `batch_size`, `num_hiddens`)
return output, state
在这个Encoder部分。首先我们会用nn.Embedding去嵌入我们的输入 再用一个rnn去获得我们的输出 记得我们的X输入,需要调整它的维度,让时间步在前面
class Seq2SeqDecoder(d2l.Decoder):
"""用于序列到序列学习的循环神经网络解码器。"""
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqDecoder, self).__init__(**kwargs)
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,
dropout=dropout)
self.dense = nn.Linear(num_hiddens, vocab_size)
def init_state(self, enc_outputs, *args):
return enc_outputs[1]
def forward(self, X, state):
# 输出'X'的形状:(`batch_size`, `num_steps`, `embed_size`)
X = self.embedding(X).permute(1, 0, 2)
# 广播`context`,使其具有与`X`相同的`num_steps`
context = state[-1].repeat(X.shape[0], 1, 1)
X_and_context = torch.cat((X, context), 2)
output, state = self.rnn(X_and_context, state)
output = self.dense(output).permute(1, 0, 2)
# `output`的形状: (`batch_size`, `num_steps`, `vocab_size`)
# `state[0]`的形状: (`num_layers`, `batch_size`, `num_hiddens`)
return output, state
其实真正有意思的代码都在decoder部分,来看我分析。 首先我们要明确,H是一个声明东西,之前我误解的把他理解为一个 hiddenshiddens的矩阵,不是这样的,它是一个batch_size * hiddens的矩阵。对应到每一个batch(可以理解为每一句话),都是一个向量。这个向量怎么来的。看我分析,首先对于我们的输入 比如 4 * 7,4表示batch_size 7表示表示timestep 首先我们会进行一个embedding ,假设我们的embedding层是一个 vocab_size为10,embed_size为8的一个层。 那么它就会变成4 * 7 8的一个3d矩阵。 如果我们要把这个东西输入到rnn的网络中 我们会先进行维度的置换,因为我们想要的是每一个时间步的迭代更新,而不是,一个batch_size的迭代更新对吗 变成一个748的矩阵后,在一个rnn模型中(不是GRU或者LSTM) 会有Whh 一个 embedding * hiddens的矩阵(或略Bias) 在这里就是一个816(假设hiddens是16)的。经过点乘变成一个(每一次迭代,只会进去 28) 216这样一个矩阵(用T表示)(这是一个时间步的迭代) 与此同时,会初始化一个H ,这个H的维数是一个 batchsizehidddens的矩阵,也就是216的矩阵用H表示 H + T就是我们想要新的隐藏层H,(如果根据这个H 216 的H,再加上一个输出层 16*8,这里为什么是8,这个输出层跟着什么东西,全部看你想要获得一个什么东西,比如在这里,我想做一个文本的预测,做个文本的纠错问题,我希望它的输出还是一个词,那这个词只会来自于vocab字典中,所以我们映射成embedding的形状,到最后我们知道它是一个什么词对吗!
分析完了这么多,我们正式来看一下decoder,其实对于前面的理解并不难,难的是GRU的这里变成了embed_size + num_hiddens为什么是这样的,下面来看我分析。 我们定位到forward函数中,之前的循环神经网络,decoder部分我们直接套了一个hiddens * embedding的一个全连接层,但是我们不满足于H带出来的输出,我们希望是H + X进行输出,怎么做,进行维度上的concat,在这里,你可以看我们的X 是一个 4 * 7 8的矩阵 4表示我们的batchsize 7表示我们的timestep 8 表示我们的embeddingsize,我在embeddingsize这个维度concat了,其实是在embedding的维数上进行一个concat,让x的信息,让两个隐藏层的信息进去,这样就相当于带了两个隐藏层的信息和自己的embedding,。然后再进入一个rnn,然后出来的向量是一个batchsziehiddien,在进入一个全连接层,返回自己的vocab
|