当前位置:   article > 正文

知识图谱-LSTM+CRF人物关系抽取实战

lstm+crf


一、引言

本文的idea主要来源于LSTM+CRF的命名实体识别,在命名实体识别中,可以通过BIO或者BIOSE等标注进行人名、地名、机构名或者其他专有名词的识别,那么把三元组的主语、谓语、宾语(也可理解为:实体-关系-实体)三个部分当成三个需要识别的专有名词,也就可以实现三元组的抽取了,基于此想法,具体实践看看效果。

二、实践简介

1、数据来源

本文主要基于历史文章中的人物关系抽取,数据来源于http://www.lishixinzhi.comhttp://www.uuqgs.com/

2、预测类别(7个)

主语开头:B-SUBJECT
主语非开头:I-SUBJECT
谓语开头:B-PREDICATE
谓语非开头:I-PREDICATE
宾语开头:B-OBJECT
宾语非开头:I-OBJECT
其他:O

3、框架

keras

4、模型结构

本次抽取本质上还是基于LSTM的一个分类问题,至于CRF层,完全是为了保证序列的输出严格性,因为CRF对于预测序列有较强的的限制性,比如B-PRESON后面只能为I-PERSON或者O之类的限制。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, 91, 100)           60000     
_________________________________________________________________
bidirectional_1 (Bidirection (None, 91, 100)           60400     
_________________________________________________________________
time_distributed_1 (TimeDist (None, 91, 7)             707       
_________________________________________________________________
crf_1 (CRF)                  (None, 91, 7)             119       
=================================================================
Total params: 121,226
Trainable params: 61,226
Non-trainable params: 60,000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

5、项目流程

    # 获取词典映射
    word2id, tag2id, id2word, id2tag = getWordAndTagId('train.txt')
    # 获取句子和标注
    sentences, tags=getSentencesAndTags('train.txt')
    # 将句子和标注转换为id
    sentencesIds, tagsIds = sentencesAndTags2id(sentences, tags,word2id, tag2id)
    # 将句子和标注进行填充,确保输入维度一致
    sentencesIds = pad_sequences(sentencesIds, padding='post')
    tagsIds = pad_sequences(tagsIds, padding='post')
    print(sentencesIds.shape)
    print(tagsIds.shape)
    # 载入模型
    model=model(len(word2id),100,sentencesIds.shape[1],len(tag2id))
   # 训练
    history = model.fit(sentencesIds, tagsIds.reshape([len(tagsIds),-1,1]), epochs=500)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

三、数据标注

关于训练数据,未找到合适的标注数据,只能自己标注了,如下:

B-SUBJECTI-SUBJECTI-SUBJECTI-SUBJECTOOB-PREDICATEI-PREDICATEB-OBJECTI-OBJECTOB-OBJECTI-OBJECTOOOOB-SUBJECTI-SUBJECTI-SUBJECTOOOOOB-PREDICATEI-PREDICATEOB-OBJECTI-OBJECTI-OBJECTI-OBJECTOB-OBJECTI-OBJECTI-OBJECTI-OBJECTO

...此处省略n多

李 B-SUBJECTI-SUBJECTOOOB-PREDICATEI-PREDICATEOOOOB-OBJECTI-OBJECTOOOOB-OBJECTI-OBJECTO
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

四、实战

1、数据预处理

1.1 词典映射

主要是低频词过滤字与id的映射(word2id)、预测类别与id的映射(lable2id),具体实现方式各有不同,不做重点讲解,但要特别注意未登录词的处理:

 word_size = len(words)
 word2id = {count[0]: index for index, count in enumerate(words,start=1)}  
 id2word = {index: count[0] for index, count in enumerate(words,start=1)}
 tag2id = {count[0]: index for index, count in enumerate(tags)}
 id2tag = {index: count[0] for index, count in enumerate(tags)}
 #  填充词
 word2id['<PAD>'] = 0
#  未登录词
 word2id['<UNK>'] = word_size + 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
1.2 从训练文件中获取句子和标签
def getSentencesAndTags(filePath):
    '''
    从文件里面获取句子和标注
    :param filePath:
    :return:
    '''
    with open(filePath,encoding='utf-8') as file:
        wordsAndtags=[line.split() for line in file]
        sentences=[]
        tags=[]
        sentence=[]
        tag=[]
        for wordAndTag in wordsAndtags:
            if len(wordAndTag)==2:
                sentence.append(wordAndTag[0])
                tag.append(wordAndTag[1])
            else:
                sentences.append(sentence)
                tags.append(tag)
                sentence=[]
                tag = []
    return sentences,tags
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
1.3 输入文本转id

将输入的文本,通过词典,转换成数字序列:

def sentencesAndTags2id(sentences,tags,word2id, tag2id):
    '''
    将句子和标注转换为id
    :param sentences:
    :param tags:
    :param word2id:
    :param tag2id:
    :return:
    '''
    sentencesIds = [[word2id.get(char,len(word2id)) for char in sentence] for sentence in sentences]
    tagsIds = [[tag2id[char] for char in tag] for tag in tags]
    return sentencesIds,tagsIds
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
1.4 数据填充

为了保证数据的维度一致,进行句子填充

from keras_preprocessing.sequence import pad_sequences
sentencesIds = pad_sequences(sentencesIds, padding='post')
tagsIds = pad_sequences(tagsIds, padding='post')
  • 1
  • 2
  • 3

2、模型构建

def model(vocabSize,embeddingDim,inputLength,tagSize):
    model = Sequential()
    model.add(Embedding(vocabSize + 1,embeddingDim,input_length=inputLength,mask_zero=True))
    model.add(Bidirectional(LSTM(50, return_sequences=True)))
    model.add(TimeDistributed(Dense(tagSize)))
    crf_layer = CRF(tagSize, sparse_target=True)
    model.add(crf_layer)
    model.compile('adam', loss=crf_layer.loss_function, metrics=[crf_layer.accuracy])
    model.summary()
    return model
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3、测试

通过简单的测试结果如下:
image.png
比较简单的句子上都能取得比较好的成果,但是由于训练数据不够,还是会出现无法抽取到结果或者抽取错误的情况,比如:
image.png

4、总结

本文主要针对历史故事的人物关系进行抽取,从数据获取,到数据标注,到模型训练。由于时间和人力关系,很多方面都采取了简单模式,比如数据标注,数据量远远没有达到一个量级,比如测试环节,主要还是通过人为观察抽取结果来验证是否准确,无法达到工业级别。但对于三元组的抽取,可以作为一种参考和借鉴。


  • 特别说明:鉴于好多童鞋使用代码,但是运行不起来,在这里说明一下代码的版本情况

tensorflow=1.14.0
keras=2.2.4
keras-contrib=2.0.8
keras-contrib安装参考:https://github.com/keras-team/keras-contrib

另外由于本人没能及时回复的原因,代码已放在
LSTM+CRF人物关系抽取实战下载,找到资源下载模块自取

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/374858
推荐阅读
相关标签
  

闽ICP备14008679号