实体识别的领域是建筑领域,处理的文本是《地铁设计规范》,2014 年 3 月 1 日开始施行的。
参考的基准代码是https://github.com/Determined22/zh-NER-TF,该源码设计的模型,参考的论文是这两篇,Bidirectional LSTM-CRF Models for Sequence Tagging
和Neural Architectures for Named Entity Recognition
(注:但是目前写这一篇时,尚未将代码和论文模型进行比对)
现将所更改的一些内容,进行记录。
1.因为我的实体类别粗粒度分为6类,细粒度分为18类,因此默认写死的tag2lable显得不太灵活
并且对应于字编号,我将这一变量更名为tag2id,同时因为字编号的使用方式是写入文件,由使用方进行读取使用,所以tag2id也写入文件,分开两个文件写(TODO:可以写入同一个文件,参考ChineseNER这个代码的写法)
代码位置:data.py/vocab_build
def vocab_build(vocab_dir, corpus_path, min_count):
"""
:param vocab_dir:
:param corpus_path:
:param min_count:
:return:
"""
data = read_corpus(corpus_path)
# word2id筛选掉不满足字频的字,将其他字进行编号,并把英文用<ENG>,数字用<NUM>,然后再给未来没有在字典中的字留一个<UNK>
# 用<UNK>来代替,把不满足统一句子长度的句子用<PAD>进行填充,形成这样子的一个字典;其中<PAD>编号为0,<UNK>为最大编号
# word2id = {'<PAD>': 0, '': , '': , .. '<ENG>': , '': , .. '<NUM>': , '': , .. '<UNK>': }
word2id = {}
tag2id = {}
tag_id = 0
for sent_, tag_ in data:
for word in sent_:
if word.isdigit():
word = '<NUM>'
# A-Z, a-z
elif ('\u0041' <= word <='\u005a') or ('\u0061' <= word <='\u007a'):
word = '<ENG>'
if word not in word2id:
word2id[word] = [len(word2id)+1, 1]
else:
word2id[word][1] += 1
for tag in tag_:
if tag not in tag2id:
tag2id[tag] = tag_id
tag_id += 1
low_freq_words = []
for word, [word_id, word_freq] in word2id.items(): # 其实这个word_id真的没用到,表示字加入字典时的序号
if word_freq < min_count and word != '<NUM>' and word != '<ENG>':
low_freq_words.append(word)
for word in low_freq_words:
del word2id[word]
# 后面修改一下按照字频去编号,这个也是参考https://github.com/zjy-ucas/ChineseNER
new_id = 1
for word in word2id.keys():
word2id[word] = new_id
new_id += 1
word2id['<UNK>'] = new_id # 查找表中没有对应的自嵌入,被替换成UNK,使用UNK对应的嵌入
word2id['<PAD>'] = 0 # 为了保证每个batch句子长度一致,用PAD对应嵌入填充句子
# print(len(word2id))
word_vocab_path = os.path.join(vocab_dir, 'word2id.pkl')
with open(word_vocab_path, 'wb') as fw:
pickle.dump(word2id, fw) # 将对象写入打开的文件中,二进制;反序列化load()
tag_vocab_path = os.path.join(vocab_dir, 'tag2id.pkl')
with open(tag_vocab_path, 'wb') as fw:
pickle.dump(tag2id, fw)
2.适应tag2id和word2id的使用方式
原有main.py中,只读取word2id,因为tag2id是写死的,如下图
改为
对应这个参数是文件夹,因此,将data.py/read_dictionary修改如下
def read_dictionary(vocab_dir):
"""
读取字典文件
:param vocab_path:
:return:
"""
word_vocab_path = os.path.join(vocab_dir, 'word2id.pkl')
with open(word_vocab_path, 'rb') as fr:
word2id = pickle.load(fr)
print('vocab_size:', len(word2id))
tag_vocab_path = os.path.join(vocab_dir, 'tag2id.pkl')
with open(tag_vocab_path, 'rb') as fr:
tag2id = pickle.load(fr)
print('vocab_size:', len(tag2id))
return word2id, tag2id
3.在这个预处理得到字编号的过程中,将不满足字频的字进行了删除,具体逻辑在data.py/vocab_build,字频限制使用min_count这个变量,对了,在开始训练前,需要先得到字编号和标签编号,因此首先需要在data.py中,我首先设置的词频是3,这个后面也可以作为要调的一个参数
if __name__ == '__main__':
'''
vocab_dir = 'data_path/rail_data18'
corpus_path = 'data_path/rail_data18/train_data'
'''
vocab_dir = 'data_path/rail_data6'
corpus_path = 'data_path/rail_data6/train_data'
min_count = 3
vocab_build(vocab_dir, corpus_path, min_count)
word2id, tag2id = read_dictionary(vocab_dir)
print(word2id)
print(tag2id)
4.将结果写入部分,进行了改动,源码中是unicode了,改为不进行编码
5.main.py中将训练数据的默认路径进行修改,如图
源代码中是将测试数据用作验证用,但我有构造出验证数据,因此使用验证数据进行验证
6.添加perl语言的解析环境
windows
activeperl官网下载安装,命令行perl -v检查安装成功,但是需要重启电脑才能被识别到
linux
检查是否已经安装:perl -v,没有直接用rpm安装就可以了
7.将数据集放入相应的位置,就可以跑起来代码了
训练命令:
python main.py --mode=train
测试命令:
python main.py --mode=test --demo_model=1608281271
(后面那个数字为训练的模型)