论文 FLAT: Chinese NER Using Flat-Lattice Transformer(ACL 2020)
首先介绍一下命名实体识别任务数据集的常见标注格式:
-
BIO
: B(Begin)-X 实体X的开头;I(Inside)-X 实体X的内部;O(Outside) 不属于实体 -
BIOES
: B(Begin)-X 实体X的开头;I(Inside)-X 实体X的中间;O(Outside) 不属于实体;E(End)-X 实体X的结尾;S(Singleton)-the single word是实体 -
BMES
:B(Begin)-X 实体X的开头;M(Middle)-X 实体X的中间;E(End)-X 实体X的结尾;S(Singleton)-the single word是实体
比如:
Michael Jeffrey Jordan was born in Brooklyn , New York .
B-PER I-PER E-PER O O O S-LOC O B-LOC E-LOC O
FALT整体框架图
将词典信息加入模型被证明对中文NER任务很有效,但是结合词典的方法通常会使输入变成一个动态的结构,导致无法有效利用GPU的并行计算。FLAT模型通过采用一个特殊的位置编码表征输入结构,从而不需要在运行时动态改变结构来表征输入。这里推荐去看作者的讲解视频 结合词典的中文命名实体识别
数据处理
- 读取数据集
from fastNLP.io.loader import ConllLoader
def get_bigrams(words):
result = []
for i, w in enumerate(words):
if i != len(words)-1:
result.append(words[i]+words[i+1])
else:
result.append(words[i]+'')
return result
train_path = os.path.join('/input/resume-ner', 'train.char.bmes')
loader = ConllLoader(['chars', 'target']) # header
train_bundle = loader.load(train_path)
datasets = dict()
datasets['train'] = train_bundle.datasets['train']
# 添加列 bigrams
datasets['train'].apply_field(get_bigrams, field_name='chars', new_field_name='bigrams')
# 添加列 seq_len
datasets['train'].add_seq_len('chars')
print(datasets['train'])
得到的输出格式为:
chars
列为样本中的字符
target
列为标签
bigrams
列为两两相邻字符组成的双字
seq_len
为chars列中字符的个数
- 提取vocab
from fastNLP import Vocabulary
char_vocab = Vocabulary()
bigram_vocab = Vocabulary()
label_vocab = Vocabulary()
# 根据列 field_name中的词构建词典
char_vocab.from_dataset(datasets['train'], field_name='chars') # id:0, id:1, 公 id:3 ...
bigram_vocab.from_dataset(datasets['train'], field_name='bigrams') # id:0, id:1, 公司 id:2 ...
label_vocab.from_dataset(datasets['train'], field_name='target') # id:0, id:1, O id:2, M-ORG id:3 ...
根据数据集字段中的字符构建对应的词典vocab。
- 加载预训练embedding
embeddings = {}
if char_embedding_path is not None:
char_embedding = StaticEmbedding(char_vocab, char_embedding_path, word_dropout=0.01,
min_freq=char_min_freq, only_train_min_freq=only_train_min_freq)
embeddings['char'] = char_embedding
if bigram_embedding_path is not None:
bigram_embedding = StaticEmbedding(bigram_vocab, bigram_embedding_path, word_dropout=0.01,
min_freq=bigram_min_freq, only_train_min_freq=only_train_min_freq)
embeddings['bigram'] = bigram_embedding
return datasets, vocabs, embeddings
- char_embedding_path为预训练的单个字符的embedding向量。
- bigram_embedding_path为预训练的双个字符的embedding向量。
给定预训练embedding的路径,StaticEmbdding
函数根据vocab
从embedding中抽取相应的数据(只会将出现在vocab中的词抽取出来,如果没有找到,则会随机初始化一个值(但如果该word是被标记为no_create_entry的话,则不会单独创建一个值,而是会被指向unk的index))。
- 融入词汇信息
w_list = load_yangjie_rich_pretrain_word_list(yangjie_rich_pretrain_word_path,
_refresh=refresh_data,
_cache_fp='cache/{}'.format(args.lexicon_name))
datasets,vocabs,embeddings = equip_chinese_ner_with_lexicon(datasets,vocabs,embeddings,
w_list,yangjie_rich_pretrain_word_path,
_refresh=refresh_data,_cache_fp=cache_name,
only_lexicon_in_train=args.only_lexicon_in_train,
word_char_mix_embedding_path=output_char_and_word_path,
number_normalized=args.number_normalized,
lattice_min_freq=args.lattice_min_freq,
only_train_min_freq=args.only_train_min_freq)
加载词典数据,得到w_list
,词典中的词汇可包含两个,三个等多个字符。
equip_chinese_ner_with_lexicon
函数用来将词汇信息lexicon写入样本中。
- 得到词汇相关字段
for k, v in datasets.items():
# partial函数将一个函数的某些参数固定住,返回一个新函数
v.apply_field(partial(get_skip_path, w_trie=w_trie), 'chars', 'lexicons')
v.apply_field(copy.copy, 'chars', 'raw_chars')
v.add_seq_len('lexicons', 'lex_num')
v.apply_field(lambda x: list(map(lambda y: y[0], x)), 'lexicons', 'lex_s')
v.apply_field(lambda x: list(map(lambda y: y[1], x)), 'lexicons', 'lex_e')
lexicons
列为样本中匹配到的词汇信息,如[[0, 1, '中国'],[3, 5, '天安门']]
lex_num
列为样本匹配到的词汇的个数
lex_s
列为各个匹配词的起始索引列表,如[0, 3]
lex_e
列为各个匹配词的终止索引列表,如[1, 5]
- 拼接原始字符串和词汇
def concat(ins):
chars = ins['chars']
lexicons = ins['lexicons']
result = chars + list(map(lambda x: x[2], lexicons))
return result
def get_pos_s(ins):
lex_s = ins['lex_s']
seq_len = ins['seq_len']
pos_s = list(range(seq_len)) + lex_s
return pos_s
def get_pos_e(ins):
lex_e = ins['lex_e']
seq_len = ins['seq_len']
pos_e = list(range(seq_len)) + lex_e
return pos_e
for k, v in datasets.items():
v.apply(concat, new_field_name='lattice')
v.set_input('lattice')
v.apply(get_pos_s, new_field_name='pos_s')
v.apply(get_pos_e, new_field_name='pos_e')
v.set_input('pos_s', 'pos_e')
concat
函数将词典lexicon中匹配到的词汇拼接到样本末尾,得到lattice
列
pos_s
列记为论文中的Head
pos_e
列即为论文中的Tail
这三列均被设置为Input。
- 字符转成整数id
vocabs['char'].index_dataset(* (datasets.values()),
field_name='chars', new_field_name='chars')
vocabs['bigram'].index_dataset(* (datasets.values()),
field_name='bigrams', new_field_name='bigrams')
vocabs['label'].index_dataset(* (datasets.values()),
field_name='target', new_field_name='target')
vocabs['lattice'].index_dataset(* (datasets.values()),
field_name='lattice', new_field_name='lattice')
return datasets, vocabs, embeddings
最后通过各个vocab
将datasets中的对应字符字段转成数字(vocab中的id)。Vocabulary类的主要作用就是实现字符和对应id之间的互相转换。
最终即得到了FLAT整体框架图中所需要的输入数据。
参考:
FLAT: Chinese NER Using Flat-Lattice Transformer (github.com)
论文阅读《FLAT:Chinese NER Using Flat-Lattice Transformer》
Flat-Lattice-Transformer模型源码测试