hankcs / HanLP

中文分词 词性标注 命名实体识别 依存句法分析 成分句法分析 语义依存分析 语义角色标注 指代消解 风格转换 语义相似度 新词发现 关键词短语提取 自动摘要 文本分类聚类 拼音简繁转换 自然语言处理
https://hanlp.hankcs.com/
Apache License 2.0
33.99k stars 10.18k forks source link

[非bug]transformer_encode进行gather index span时不合理 #1763

Closed geasyheart closed 2 years ago

geasyheart commented 2 years ago

Describe the bug A clear and concise description of what the bug is. 你好,我在看到transformer_tokenizer.py时,你对CLS和SEP也计算了token_token_span,接着你会调用pick_tensor_for_each_token这个函数来获取首字向量或者平均向量,随后你会在每一个任务里面获取句子向量时都是通过encoder_hidden[:, 1:-1:, ]来表示,其中-1在有padding的情况下不会是[SEP]。

Code to reproduce the issue Provide a reproducible test case that is the bare minimum necessary to generate the problem.

Describe the current behavior A clear and concise description of what happened.

Expected behavior A clear and concise description of what you expected to happen.

System information

Other info / logs Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached.

geasyheart commented 2 years ago

定义成非bug原因为模型本身不具有可解释性,只要效果不错,就不定义为bug~

hankcs commented 2 years ago

随后你会在每一个任务里面获取句子向量时都是通过encoder_hidden[:, 1:-1:, ]来表示

请问究竟哪个任务这样表示了呢?所有token level的任务都有相应的token level的mask,不会将padding纳入loss或prediction。

geasyheart commented 2 years ago

我debug的multitask,另外不会将padding纳入loss或prediction这里没问题,只是你对transformer encode取对应的index那里不合理。

geasyheart commented 2 years ago

除了tok,剩下的multi task learning都会走到h[:, 1:-1,:]这里

hankcs commented 2 years ago

我们有两个地方理解得可能不一样。

  1. 到了这一行h已经是每个token的hidden state了
  2. sep_is_eos意味着如果句子有n个token的话,h这一维的长度就是n+1(包含eos)。所以sep_is_eos=False时,为了下游任务正确地apply token mask,这里只有将最后一个元素去掉,但不意味着去掉的一定是sep。其实只要去掉一个无关紧要的元素就行,很明显去掉最后一个是效率最高的做法。

这种做法乍一看似乎很绕,其实HanLP的代码复用率是相当高的。所有的task都是继承了single task learning的component,这些single task在设计的时候是不需要考虑h的长度不一致的问题的。毕竟一个task不一定会做MTL,我希望写它们的时候不用考虑兼容性。只要需要的时候,为了将它们纳入MTL框架,才有上面这段检查bos和eos的代码。

geasyheart commented 2 years ago

那我举个例子,tok那里您也是一样的处理方式(即transformer_encoder_output[:, 1:-1, :])。比如有两个句子:

batch['token'] = [['我', '爱', '你', '们', '和', '北', '京', '。'], ['我', '爱', '你', '们', '。']]

# 您的token_token_span
batch['token_token_span'] = tensor([[[0],[1],[2], [3], [4], [5], [6],[7],[8],[9]],
                                   [[0], [1], [2],[3],[4],[5], [6], [0],[0],[0]]])

您经过transformer encoderpick_tensor_for_each_token处理后,维度h是(2, 10, 256),这里没问题,问题在下面decoder这一步

  def forward(self, contextualized_embeddings: torch.FloatTensor, batch: Dict[str, torch.Tensor], mask=None):
      return self.classifier(contextualized_embeddings[:, 1:-1, :])

这里的-1在有padding的情况下不会是[SEP]

geasyheart commented 2 years ago

嗯嗯,multi task learning代码写的相当nice,有很多值得学习的地方。

hankcs commented 2 years ago
```python

这里的-1在有padding的情况下不会是[SEP]。

去掉的究竟是sep还是pad其实对下游任务没有任何影响,因为总归是要再过一遍mask的,只要h的维度和mask一致就行了。

geasyheart commented 2 years ago

下游任务有没有影响,本质来讲是输入的形式是固定的,以及模型已经能够拟合的很好。 如果在每次pick_tensor_for_each_token对transformer_encoder结果进行操作时去掉[CLS][SEP],那么在decoder部分接收到的都是原tokens了。

hankcs commented 2 years ago

下游任务有没有影响,本质来讲是输入的形式是固定的,以及模型已经能够拟合的很好。 如果在每次pick_tensor_for_each_token对transformer_encoder结果进行操作时去掉[CLS][SEP],那么在decoder部分接收到的都是原tokens了。

无论去掉的是不是sep,decoder得到的h在mask后一模一样。

不能直接去掉。有些任务需要cls作为bos,需要sep作为eos。MTL框架会根据所有task取一个全集去encode,再根据每个task的需要裁剪。

geasyheart commented 2 years ago

好的,我知道您这边的做法了