論文 FLAT: Chinese NER Using Flat-Lattice Transformer(ACL 2020)
首先介紹一下命名實(shí)體識別任務(wù)數(shù)據(jù)集的常見標(biāo)注格式:
-
BIO
: B(Begin)-X 實(shí)體X的開頭紊遵;I(Inside)-X 實(shí)體X的內(nèi)部;O(Outside) 不屬于實(shí)體 -
BIOES
: B(Begin)-X 實(shí)體X的開頭匀奏;I(Inside)-X 實(shí)體X的中間;O(Outside) 不屬于實(shí)體论衍;E(End)-X 實(shí)體X的結(jié)尾聚磺;S(Singleton)-the single word是實(shí)體 -
BMES
:B(Begin)-X 實(shí)體X的開頭瘫寝;M(Middle)-X 實(shí)體X的中間稠炬;E(End)-X 實(shí)體X的結(jié)尾首启;S(Singleton)-the single word是實(shí)體
比如:
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任務(wù)很有效毅桃,但是結(jié)合詞典的方法通常會使輸入變成一個動態(tài)的結(jié)構(gòu)准夷,導(dǎo)致無法有效利用GPU的并行計(jì)算衫嵌。FLAT模型通過采用一個特殊的位置編碼表征輸入結(jié)構(gòu),從而不需要在運(yùn)行時動態(tài)改變結(jié)構(gòu)來表征輸入渐扮。這里推薦去看作者的講解視頻 結(jié)合詞典的中文命名實(shí)體識別
數(shù)據(jù)處理
- 讀取數(shù)據(jù)集
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]+'<end>')
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
列為標(biāo)簽
bigrams
列為兩兩相鄰字符組成的雙字
seq_len
為chars列中字符的個數(shù)
- 提取vocab
from fastNLP import Vocabulary
char_vocab = Vocabulary()
bigram_vocab = Vocabulary()
label_vocab = Vocabulary()
# 根據(jù)列 field_name中的詞構(gòu)建詞典
char_vocab.from_dataset(datasets['train'], field_name='chars') # <pad> id:0, <unk> id:1, 公 id:3 ...
bigram_vocab.from_dataset(datasets['train'], field_name='bigrams') # <pad> id:0, <unk> id:1, 公司 id:2 ...
label_vocab.from_dataset(datasets['train'], field_name='target') # <pad> id:0, <unk> id:1, O id:2, M-ORG id:3 ...
根據(jù)數(shù)據(jù)集字段中的字符構(gòu)建對應(yīng)的詞典vocab。
- 加載預(yù)訓(xùn)練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為預(yù)訓(xùn)練的單個字符的embedding向量察纯。
- bigram_embedding_path為預(yù)訓(xùn)練的雙個字符的embedding向量针肥。
給定預(yù)訓(xùn)練embedding的路徑慰枕,StaticEmbdding
函數(shù)根據(jù)vocab
從embedding中抽取相應(yīng)的數(shù)據(jù)(只會將出現(xiàn)在vocab中的詞抽取出來,如果沒有找到博肋,則會隨機(jī)初始化一個值(但如果該word是被標(biāo)記為no_create_entry的話匪凡,則不會單獨(dú)創(chuàng)建一個值掘猿,而是會被指向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)
加載詞典數(shù)據(jù)买猖,得到w_list
,詞典中的詞匯可包含兩個政勃,三個等多個字符奸远。
equip_chinese_ner_with_lexicon
函數(shù)用來將詞匯信息lexicon寫入樣本中讽挟。
- 得到詞匯相關(guān)字段
for k, v in datasets.items():
# partial函數(shù)將一個函數(shù)的某些參數(shù)固定住,返回一個新函數(shù)
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
列為樣本匹配到的詞匯的個數(shù)
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
函數(shù)將詞典lexicon中匹配到的詞匯拼接到樣本末尾罢杉,得到lattice
列
pos_s
列記為論文中的Head
pos_e
列即為論文中的Tail
這三列均被設(shè)置為Input滩租。
- 字符轉(zhuǎn)成整數(shù)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中的對應(yīng)字符字段轉(zhuǎn)成數(shù)字(vocab中的id)律想。Vocabulary類的主要作用就是實(shí)現(xiàn)字符和對應(yīng)id之間的互相轉(zhuǎn)換技即。
最終即得到了FLAT整體框架圖中所需要的輸入數(shù)據(jù)晌柬。
參考:
FLAT: Chinese NER Using Flat-Lattice Transformer (github.com)
論文閱讀《FLAT:Chinese NER Using Flat-Lattice Transformer》
Flat-Lattice-Transformer模型源碼測試