序列到序列学习(seq2seq)


ValueError Traceback (most recent call last)
Cell In[14], line 11
8 decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
9 dropout)
10 net = d2l.EncoderDecoder(encoder, decoder)
—> 11 train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)

Cell In[13], line 28, in train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device)
25 bos = torch.tensor([tgt_vocab[‘’]] * Y.shape[0],
26 device=device).reshape(-1, 1)
27 dec_input = torch.cat([bos, Y[:, :-1]], 1) # 强制教学
—> 28 Y_hat, _ = net(X, dec_input, X_valid_len)
29 l = loss(Y_hat, Y, Y_valid_len)
30 l.sum().backward() # 损失函数的标量进行“反向传播”

ValueError: too many values to unpack (expected 2)

2024.3.13
跑transformer时出现了这个错误,回来看9.7这个函数时也不能运行。有人遇到吗?

我觉得上面说的预测和训练的问题其实并没有,之所以训练时仅输入了一次X,而预测时要for num_steps,进行了时间步次输入,是因为其内部不同。
回到Decoder的代码中,有self.rnn(X,state),这里的X的形状是batch_size,num_steps,输入的state是Encoder获得的编码状态。同时,我们在之前学过GRU的内部代码,在GRU的内部其实对X的转置进行了for操作,也就是在GRU的内部进行了num_steps个时间步的推理,同时更新了隐状态state。也就是说在Decoder的GRU的内部,仅仅只有第一个时间步的状态输入来自Encoder的编码状态,后面是在更新的。
而预测时仅输入了一个单词,并不是一个时间序列,所以在Decoder的内部并没有时间步次的推理,只有一次,才需要在预测时进行时间步数次的推理和状态更新。
总结一下:就是在训练时Decoder的输入是包含时间步维度的X(会在内部进行时间步次的迭代),而预测时Decoder的输入只有一个单词,并不是num_steps个一起输入,这因为只有训练时我们才知道翻译的目标法语序列,而预测时我们只知道最开始的状态是bos,所以需要进行num_steps次推理,也就是把内部本来自动进行的时间步次的推理放到了外面一次一次地推理。
所以应该是不存在上面所说的训练和预测不一致的情况。
但还是存在与原文不一的情况,因为原文中的图是把Encoder的state作为编码信息输入到Decoder的每一时间步,但代码实现时由于GRU内部会自动进行状态更新,使得本应不变的state输入变成了随时间步变化。
如果有不同的理解请回复我,其中我不太确定的就是nn.GRU输入X和state后是否会固定state,因为之前学的GRU实现那一章内部是会变的,而pytorch的GRU我不是很确定。

是的,我也修改了,效果也从原来的:
go . => va !, bleu 1.000
i lost . => j’ai compris ., bleu 0.000
he’s calm . => il court ., bleu 0.000
i’m home . => je suis chez gagné ?, bleu 0.651

变为了:
go . => va !, bleu 1.000
i lost . => j’ai perdu ., bleu 1.000
he’s calm . => il est riche ., bleu 0.658
i’m home . => je suis chez moi ., bleu 1.000

我实现的做法就是在init中加入一个enc_state来记住。
context直接通过这个enc_state来构造

1 Like

但是这样你的解码器的RNN状态就丢失了,肯定会变差了

你好!我也发现了,原因是d2l的EncoderDecoder(参考上一章框架的代码)改了(英文版里只return1个),我的解决方案是把上一个框架代码的EncoderDecoder导入到这一章节代码里,以下代码可用于导入别的ipynb(在CSDN上找的):

在这一章节开头导入
import ipynb_importer
import 编码器解码器框架代码名

新建一个ipynb_importer.py
import io, os,sys,types
from IPython import get_ipython
from nbformat import read
from IPython.core.interactiveshell import InteractiveShell

class NotebookFinder(object):
“”“Module finder that locates Jupyter Notebooks”“”
def init(self):
self.loaders = {}

def find_module(self, fullname, path=None):
    nb_path = find_notebook(fullname, path)
    if not nb_path:
        return

    key = path
    if path:
        # lists aren't hashable
        key = os.path.sep.join(path)

    if key not in self.loaders:
        self.loaders[key] = NotebookLoader(path)
    return self.loaders[key]

def find_notebook(fullname, path=None):
“”"find a notebook, given its fully qualified name and an optional path

This turns "foo.bar" into "foo/bar.ipynb"
and tries turning "Foo_Bar" into "Foo Bar" if Foo_Bar
does not exist.
"""
name = fullname.rsplit('.', 1)[-1]
if not path:
    path = ['']
for d in path:
    nb_path = os.path.join(d, name + ".ipynb")
    if os.path.isfile(nb_path):
        return nb_path
    # let import Notebook_Name find "Notebook Name.ipynb"
    nb_path = nb_path.replace("_", " ")
    if os.path.isfile(nb_path):
        return nb_path

class NotebookLoader(object):
“”“Module Loader for Jupyter Notebooks”“”
def init(self, path=None):
self.shell = InteractiveShell.instance()
self.path = path

def load_module(self, fullname):
    """import a notebook as a module"""
    path = find_notebook(fullname, self.path)

    print ("importing Jupyter notebook from %s" % path)

    # load the notebook object
    with io.open(path, 'r', encoding='utf-8') as f:
        nb = read(f, 4)


    # create the module and add it to sys.modules
    # if name in sys.modules:
    #    return sys.modules[name]
    mod = types.ModuleType(fullname)
    mod.__file__ = path
    mod.__loader__ = self
    mod.__dict__['get_ipython'] = get_ipython
    sys.modules[fullname] = mod

    # extra work to ensure that magics that would affect the user_ns
    # actually affect the notebook module's ns
    save_user_ns = self.shell.user_ns
    self.shell.user_ns = mod.__dict__

    try:
      for cell in nb.cells:
        if cell.cell_type == 'code':
            # transform the input to executable Python
            code = self.shell.input_transformer_manager.transform_cell(cell.source)
            # run the code in themodule
            exec(code, mod.__dict__)
    finally:
        self.shell.user_ns = save_user_ns
    return mod

sys.meta_path.append(NotebookFinder())

哥们太牛了,这个逻辑!预测中,state会随时间步更新,encode不会随时间步变化,所以一直是encoder的信息。而又因为encode放在[-1]的位置不影响训练。太强了 :+1: :+1: :+1:

train_seq2seq 函数中,在把 “<bos>” 与 Y[:,:-1] cat 到一起的时候,解释说是为了原始的输出序列不包括序列结束词元 “<eos>”,但是在 9.5.4 节中,是对序列先加了 “<eos>”,再 padding 到 num_steps,这样 Y[:,:-1] 就不是在剔除 “<eos>” 了

def build_array_nmt(lines, vocab, num_steps):
    """将机器翻译的文本序列转换成小批量"""
    lines = [vocab[l] for l in lines]
    lines = [l + [vocab['<eos>']] for l in lines]
    array = torch.tensor([truncate_pad(
        l, num_steps, vocab['<pad>']) for l in lines])
    valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)
    return array, valid_len

def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
    """训练序列到序列模型"""
    ... ...
    for epoch in range(num_epochs):
        ... ...
        for batch in data_iter:
            ... ...
            dec_input = torch.cat([bos, Y[:, :-1]], 1)  # 强制教学

Snipaste_2024-04-22_17-35-13
请问为什么对第一个句子进行翻译的时侯,会多出一个空格呢,换成别的词也总有这个问题,求大佬解答~

关于大伙说的那个“预测时context在不断变化”的问题,我也试着简单修改了下解码器和预测的函数,然后发现bleu提升了。

(post deleted by author)