编码器-解码器架构

一些编码器-解码器架构的思考

基本ai问题都可以归类为文字生文字(语言大模型),文字生图(midjourney),文字生视频(sora)。
输入和输出都是长度可变的序列,机器翻译语种问题就是这个架构的一个简单的应用。

图?

数学原理就是基本就是把输入序列映射为形状固定的编码状态S,然后映射成长度可变的序列。
编码器应该就是最基本的一个继承自nn.module的具有forward方法的类
解码器有点特殊,是将一个编码状态S,和前一个时间步生成的词元,映射成当前时间步的输出词元,所以初始化的时候需要把编码器的output state存起来

for example:
S(I love you !) 【】 -> Decoder -> 【我】 , t1时间步
S(I love you !) 【我】-> Decoder -> 【爱】,t2时间步
S(I love you!) 【我爱】-> Decoder -> 【我爱你】, t3时间步
S(I love you!) 【我爱你】-> Decoder -> 【我爱你 !】, t4时间步

Demo代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from torch import nn

class Encoder(nn.Module):
def __init__(self, **kwargs):
super(Encoder, self).__init__(**kwargs)
def forward(self, X, *args):
raise NotImplementedError

class Decoder(nn.Module):
def __init__(self,**kwargs):
super(Decoder, self).__init__(**kwargs)
def init_state(self, enc_output, *args):
raise NotImplementedError
def forward(self, X, state):
raise NotImplementedError

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, **kwargs):
enc_output=self.encoder(enc_X,*args)
dec_state=self.decoder.init_state(enc_output, *args)
return self.decoder(dec_X, dec_state)

落地encoder实现,就是序列的开始是以特殊词元 bos,和结束词元 eos 来完成。

  • 1 输入的X:
    示例:X = torch.zeros((4, 7), dtype=torch.long)
    可以理解为批量为4,长度为7的,数字组成的序列。
    【【3,4,5,1,5,7,8】【3,4,5,1,5,7,8】【3,4,5,1,5,7,8】【3,4,5,1,5,7,8】】

  • 2 经给embedding的X_emb会变成
    示例:
    【【【1,2,3,0,5,6】【1,3,4,0,5,6】…】【…】【…】【…】】

  • 3 经给 X_rnn = X = X_emb.permute(1, 0, 2)
    这里更换成形状 num_steps, batch_size, emb_size,因为始终要按照时间步 t1,t2,t3->…
    这样进行forward的,所以每个时间步可以处理多个不同批量的单个词元,这就是变换目的。
    说到底,rnn和其变种接受的X形状是(时间步,批量,词表大小)

  • 4 output, state=self.rnn(X_rnn)
    这个时候就是rnn的表演了。由于state的一开始在rnn里面规定的形状就是nxh,由于layer是多层,所以有多个隐藏状态,所以给出的形状(layer_num, n, h)
    而output这里没有添加dens线性层去映射到vocab范围,所以也是nxh,由于每个时间步都有output,所以给出的形状是(n_steps, n, h)

demo代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的形状:(num_layers,batch_size,num_hiddens)
return output, state
  • 1 上下文变量是如何来?
    通过取最后一个隐藏层(batch_size, num_hiddens),即然后赋值n份,以匹配n的批量值。

  • 2 怎么拼接?
    以下是模拟拼接的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    state=torch.ones(2,4,3) #模拟一个state, num_layer=2, batch_size=4, hiddens=3  
    context=state[-1].repeat(5,1,1) #取最后一个layer,repeat steps次,变成(5,4,3)
    print(context)
    X=torch.rand(4,5,5) #本来是batch_size=4, steps=5, emb=5
    X=X.permute(1,0,2) #变成(step=5,batch=4,emb=5)
    X_and_context = torch.cat((X, context), 2)#所以对于X变成steps次,和batch,然后emb,而隐藏层本来就是batch x h,最后复制steps次,然后就是emb+h
    print(X)
    X_and_context

    可以认为隐藏层的nxh,对每个时间步都跟着X进去了。
    落地decoder的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的形状:(num_layers,batch_size,num_hiddens)
return output, state

后面的loss构建,训练,和评估虽然很重要,但是我感觉现在我无法搞定。