LawsonAbs的认知与思考,还请各位读者批判阅读。

总结


  • flat的源码你确定你能看懂??各种调库的操作对小白也太不友好了吧~
  • 持续更新~



本博客分成两部分,第一部分(part1)主要描述了一下复现flat可以参考的文档,数据集等;第二部分(part2)主要讲了论文的核心内容


part 1. 预热知识


1 可参考文档

本文介绍一种FLAT方法用于做中文NER操作。


  • 论文​​地址​​。
  • 论文的Github:https://github.com/LeeSureman/Flat-Lattice-Transformer
    大家需要多根据issues 发现问题和解决问题,原作者对大多数问题进行了比较完整的回答。很多时候,不是因为解决不了问题,而是因为GitHub用的不熟悉。
  • 作者讲演视频:https://www.bilibili.com/video/BV1Z54y1y766?from=search&seid=16334174024820448071

2 数据集


  • WeiboNER:
    flat这份工作中,作者对原微博数据集进行了稍微的修改,然后得到了下面这个网盘中的数据集。具体的修改方式我清楚。
    链接:https://pan.baidu.com/s/1SxsvSY1L446hb4zToRb1sA 提取码:7v5y
  • 其它数据集可以在链接 https://github.com/RowitZou/LGN 中获取具体的对应数据集(如下图所示)。
    论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集


part 2. 论文内容


1.任务

中文NER

1.1中文NER和英文NER的区别

1.1.1 英文NER

英文NER 是基于单词(在我心中,其实这也是字哇),但为什么英文NER的任务就较轻松于中文NER 呢?是因为英文单词多种组合的情况很少,上下词确定时,其含义就比较唯一固定了。

1.1.2 中文NER

不同于英文NER(英文NER是以单词为单位),中文NER通常以字符为单位进行序列标注建模。但是我觉得中文NER较英文NER复杂的根本原因有两个,分别是:

第一:英文中的一个单词就约等于中文某个词组的效果。比如说:​​beautiful​​就相当于中文中的​​漂亮的​​,英文一个单词就可以解决,但是中文却需要使用三个字符才可以表示。

第二:中文NER中,按照字为单位后,其后续过程中遇到的组合种类更多,具有的不确定性更大。比如:​​重庆人和药店​​就可以理解成如下两种方式:


  • ​重庆人​​​,​​和​​​,​​药店​
  • ​重庆​​​,​​人和药店​

而且这两种拆分方式没有固定的答案,需要通过上下含义才能得到。

1.2为什么需要这个方法?

基于分词的方法导致的误差使得中文NER只能基于字符的方法来做。但是将整句话分词字,就无法利用词的信息了。​​重庆人和药店​​​这句话,将其作为单个字符来训练模型时,就不能利用上​​重庆​​​,​​人和药店​​这两个词语。我们都知道,在深度学习中,该利用上的数据都应该使用,从而使得模型具备更好的拟合效果。于是便提出了基于词汇增强方式的FLAT 算法诞生了。

2.相关工作

2.1 ​​Lattice LSTM​

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_github_02

这个工作我目前尚不是很清楚。

3.主要思想

文章的contribution就只有一个:

使用一种位置编码方式能够还原出 ​​flat lattice​​ 的信息,从而能够可以更好的得到训练效果。

3.1 什么是span?

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_NLP_03上面图中的每个绿色框框就是一个span。例如:​​​重​​​,​​人​​​,​​人和药店​​等都是span。

3.2 什么是​​flat lattice​​?

lattice 本意为栅栏,栅格。下图就是一个形象的lattice结构:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据_04

3.3 将 lattice 式的DAG 转为flat lattice时,如何保持原序列结构?

在NER 问题中,位置信息是很重要的。flat的作者在transformer 的positional embedding的设计下,想到了能否使用一种各个span的相对编码方式从而保证原序列的结构信息?

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_05

  • 可以看到整个图被压平了,所以称之为FLAT

3.4 如何利用flat之后的信息?

lattice 给我们flaten了,但是该怎么利用这些信息呢?有两个处理:


  • 将序列分成了head 和 tail 两种
  • 利用这个head 和 tail 信息做一个相对位置操作。也就是文中说的:


To encode the interactions among spans, we propose the relative position encoding of spans.For two spans xi and xj in the lattice, there are three kinds of relations between them: intersection, inclusion and separation, determined by their heads and tails.


也就得到了如下的信息:

d i j ( h h ) = h e a d [ i ] − h e a d [ j ] d i j ( h t ) = h e a d [ i ] − t a i l [ j ] d i j ( t h ) = t a i l [ i ] − h e a d [ j ] d i j ( t t ) = t a i l [ i ] − t a i l [ j ] d_{ij}^{(hh)} = head[i] - head[j] \\ d_{ij}^{(ht)} = head[i] - tail[j]\\ d_{ij}^{(th)} = tail[i] - head[j] \\ d_{ij}^{(tt)} = tail[i] - tail[j] dij(hh)​=head[i]−head[j]dij(ht)​=head[i]−tail[j]dij(th)​=tail[i]−head[j]dij(tt)​=tail[i]−tail[j]

然后根据上面的这些数据,可以得到一个相对位置编码数据。

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_深度学习_06

这里的 W r W_r Wr​是可学习参数,其它的参数与attention中原有的参数很像~ 【我不是很清楚】

4.源码阅读

4.1 可以参考我之前写的文章如何读源码?

4.2 如何跑通这份代码?

跑通这份代码可以用WeiboNER数据集(也就是原作者的数据集。)示例效果还是很贴合论文中的数据的,(水分还是比较少哇~)。但是下面我的讲解示例是用的是自己的一份数据集(TianchiNER)。

运行代码的时候需要注意配置fitlog,同时最好在Linux机器上跑(是因为有小伙伴在windows上跑的满是bug~)。

4.3 数据集

关于原作者的数据集,我在原作者的GitHub中的issues中说的很清楚了,这里再补充一下吧。


  • 微博有份原始数据集是叫 WeiboNER ,可以在链接https://github.com/hltcoe/golden-horse/tree/master/data ,但是作者把这个数据集做了一定的修改,以便方便代码使用,修改后的文件如果需要,我私发给大家(网盘操作稍微麻烦,就不搞了~)
  • 其它的数据集像 onetonotes,msra 我就不提供了,对于学习这份代码应该没有太大的影响 ~

4.1 宏观架构

先看一下宏观的过程,也就是对应论文中的那个花花绿绿的图:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_07

下图中的红框的1,2,3,4 就代表上述的​​Embedding​​,​​slef-attention​​,​​Add & Norm​​,​​FFN​​,​​Add & Norm​​,​​Linear &CRF​​ 中的一个或某几个步骤。下面仔细讲讲:


  • 1代表的就是将embedding ,位置信息交由attention 处理
    其中的​​self-Attention, Add&Norm , FFN, Add&Norm​​ 等操作都被封装到encoder 中了,所以只需要使用一个encoder()就行了。
  • 2代表的是将attention处理的结果放到最后一层的Linear 中操作
  • 3.代表的就是上步的结果做CRF 操作。

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_github_08其中embedding 是由 embed_char 和 embed_lex 两者合并得到的。

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_github_09

4.2 微观调试

这一部分主要是仔细的查看整个过程变量的变化,以及处理的细节问题。

4.2.1 数据准备

为了方便调试,这里就用了几个字作为数据用于训练和测试。如下数据作为本次调试中的数据。

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_深度学习_10

4.3 类的使用

在这个代码中,数据集的加载并非是pytorch 中的dataset 作为数据集的封装,而是用的fastNLP中自带的类。主要涉及到​​DataSet, Vocabulary,Embedding​​ 三个部分。其功能分别是:封装数据集;词典中词到下标的映射;将词映射成embedding。

4.4 数据生成的概括

主要的加载过程如下:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_NLP_11- load_tianchi_ner() 方法是用于加载我自己的一个数据集,返回值是datasets,vocab, embeddings


  • load_yangjie_rich_pretrain_word_list() 方法是用于加载词典信息,这个涉及到后面的datasets中lexicon的使用
  • equip_chinese_ner_with_lexicon() 方法是用于将词典信息写入datasets中

4.5 数据生成的详细过程

下面讲一下数据生成的详细过程:

​chars​​:是原生的文本

​target​​:就是序列标注的结果,也就是label。但是通常会将其写成target

​bigram​​: 这个是文本的连续的分割结果。分割方法是连续两两分割,比如对于上面的dev数据集,得到的 ​​bigram​​ 就是下面这样:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据_12

字典中存在的词语有:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_13

​[0,1,'本品']​​ ,前两个数字代表起止下标,后面代表匹配的词语。

下面这个就是遍历每一个 ​​datasets​​,然后交由concat

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_14

​concat()​​ 的功能就是把找到的词语拼接到之前的话中,得到ins[‘lattice’],结果如下:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_15

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_16

各个字段的含义是什么?

seq_len:语句长度

lexicons:加入的词典信息

raw_chars: 源char

lex_num:表示raw char匹配词典中词的个数

lex_s: 各个匹配词中匹配的起始下标

lex_e: 各个匹配词中匹配的终止下标

lattice:源字符串匹配词典添加在源字符串之后得到的结果

在经过下面三个 apply操作,

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_github_17数据就变成如下的样子了:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_NLP_18

由 ​​datasets['train']['lattice']​​ 可以得到 lattice_vocab ,其值如下:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据_19其长度为:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据_20

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_深度学习_21

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_22

可以看到datasets 这个变量的值的变化。变化的原则是:把datasets中每个汉字改为对应vocab中的值的下标,​​Vocabulary​​这个类的作用就是实现字到下标的转换。(详细知识可以在Vocabulary的源码中看到)

至此datasets这个变量值的设置就完成了,接下来就需要操作 vocabs 这个变量了。

StaticEmbedding 这个实现自 TokenEmbedding,但是TokenEmbedding实现自nn.Module,所以前二者都有 forward()方法用于将一个tensor处理。这里以 StaticEmbedding 为例,介绍其forward的方法:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_NLP_23

其方法中做了三件事儿:


  • drop_word
  • 得到words 的 embedding。这个embedding是 pytorch 自身模块中的 nn.Embedding的一个实例。

经过所有的处理之后,datasets 的值形式如下:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_深度学习_24传入模型中训练的数据就是 datasets[‘train’] ,如下:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_NLP_25

真正训练时传入 forward 的是batch_x,

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_深度学习_26

其值如下:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据_27

那么 lexicon 是怎么生成的呢?在add_lattice.py 下面的代码中:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据_28

其中get_skip_path() 如下:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_github_29

其中chars是datasets[‘train’]中的chars值,也就是原生的字符串。w_trie是通过 词典得到的一个word_list,然后将其放入到了一个Trie树中。 get_lexicon()方法是用一个二重循环的方法去寻找一个匹配的result。得到的结果就是上面注释的这种,也如下面这个图所示:

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据集_30

论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》_数据_31

难点


  • fastNLP,fitlog 等开源的学习
  • 源码阅读过程遇到的问题,解决问题的过程?

参考文章