前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >命名实体标注基于keras的BiLstm与CRF与算法封装

命名实体标注基于keras的BiLstm与CRF与算法封装

作者头像
学到老
发布2019-01-25 11:21:16
2.6K0
发布2019-01-25 11:21:16
举报
基本概述

众所周知,通过Bilstm已经可以实现分词或命名实体标注了,同样地单独的CRF也可以很好的实现。既然LSTM都已经可以预测了,为啥要搞一个LSTM+CRF的hybrid model? 因为单独LSTM预测出来的标注可能会出现(I-Organization->I-Person,B-Organization ->I-Person)这样的问题序列。

但这种错误在CRF中是不存在的,因为CRF的特征函数的存在就是为了对输入序列观察、学习各种特征,这些特征就是在限定窗口size下的各种词之间的关系。

将CRF接在LSTM网络的输出结果后,让LSTM负责在CRF的特征限定下,依照新的loss function,学习出新的模型

基于字的模型标注:

假定我们使用Bakeoff-3评测中所采用的的BIO标注集,即B-PER、I-PER代表人名首字、人名非首字,B-ORG、I-ORG代表组织机构名首字、组织机构名非首字,O代表该字不属于命名实体的一部分

B-Person I- Person B-Organization I-Organization O

加入CRF layer对LSTM网络输出结果的影响 为直观的看到加入后的区别我们可以借用网络中的图来表示:其中x表示输入的句子,包含5个字分别用w1,w2,w3,w4,w5表示

这里写图片描述
这里写图片描述

没有CRF layer的网络示意图

这里写图片描述
这里写图片描述

含有CRF layer的网络输出示意图 上图可以看到在没有CRF layer的情况下出现了 B-Person->I-Person 的序列,而在有CRF layer层的网络中,我们将 LSTM 的输出再次送入CRF layer中计算新的结果。而在CRF layer中会加入一些限制,以排除可能会出现上文所提及的不合法的情况

CRF loss function

完成随时函数请参考:https://createmomo.github.io/2017/11/11/CRF-Layer-on-the-Top-of-BiLSTM-5/ CRF loss function 如下: LossFunction=PRealPathP1+P2+…+PNLoss Function = \frac{P_{RealPath}}{P_1 + P_2 + … + P_N}LossFunction=P1​+P2​+…+PN​PRealPath​​

代码实现(keras版本)

1、搭建模型与编译 使用2.1.4版本的keras,在keras版本里面已经包含bilstm模型,但crf的loss function还没有,不过可以从keras contribute中获得,具体可参看:https://github.com/keras-team/keras-contrib(注意安装过程中一定要配置anaconda的环境变量)

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

构建网络模型与编译代码如下:

def create_model(train=True):
    if train:
        (train_x, train_y), (test_x, test_y), (vocab, chunk_tags) = load_data()
    else:
        with open('model/config.pkl', 'rb') as inp:
            (vocab, chunk_tags) = pickle.load(inp)
    model = Sequential()
    model.add(Embedding(len(vocab), EMBED_DIM, mask_zero=True))  # Random embedding
    model.add(Bidirectional(LSTM(BiRNN_UNITS // 2, return_sequences=True)))
    crf = CRF(len(chunk_tags), sparse_target=True)
    model.add(crf)
    model.summary()
    model.compile('adam', loss=crf.loss_function, metrics=[crf.accuracy])
    if train:
        return model, (train_x, train_y), (test_x, test_y)
    else:
        return model, (vocab, chunk_tags)

2、清洗数据 清晰数据是最麻烦的一步,首先我们采用网上开源的语料库作为训练和测试数据。语料库中已经做好了标记,其格式如下:

月 O 油 O 印 O 的 O 《 O 北 B-LOC 京 I-LOC 文 O 物 O 保 O 存 O 保 O 管 O

语料库中对每一个字分别进行标记,比较包括如下几种:

‘O’, ‘B-PER’, ‘I-PER’, ‘B-LOC’, ‘I-LOC’, “B-ORG”, “I-ORG” 分别表示,其他,人名第一个,人名非第一个,位置第一个,位置非第一个,组织第一个,非组织第一个

  train = _parse_data(open('data/train_data.data', 'rb'))
    test = _parse_data(open('data/test_data.data', 'rb'))

    word_counts = Counter(row[0].lower() for sample in train for row in sample)
    vocab = [w for w, f in iter(word_counts.items()) if f >= 2]
    chunk_tags = ['O', 'B-PER', 'I-PER', 'B-LOC', 'I-LOC', "B-ORG", "I-ORG"]

    # save initial config data
    with open('model/config.pkl', 'wb') as outp:
        pickle.dump((vocab, chunk_tags), outp)

    train = _process_data(train, vocab, chunk_tags)
    test = _process_data(test, vocab, chunk_tags)
    return train, test, (vocab, chunk_tags)

3、训练数据 在处理好数据后可以训练数据,本文中将batch-size=16获得较为高的accuracy(99%左右),进行了10个epoch的训练。

import bilsm_crf_model

EPOCHS = 10
model, (train_x, train_y), (test_x, test_y) = bilsm_crf_model.create_model()
# train model
model.fit(train_x, train_y,batch_size=16,epochs=EPOCHS, validation_data=[test_x, test_y])
model.save('model/crf.h5')
这里写图片描述
这里写图片描述

4、验证数据 import bilsm_crf_model import process_data import numpy as np

model, (vocab, chunk_tags) = bilsm_crf_model.create_model(train=False) predict_text = ‘中华人民共和国国务院总理周恩来在外交部长陈毅的陪同下,连续访问了埃塞俄比亚等非洲10国以及阿尔巴尼亚’ str, length = process_data.process_data(predict_text, vocab) model.load_weights(‘model/crf.h5’) raw = model.predict(str)[0][-length:] result = [np.argmax(row) for row in raw] result_tags = [chunk_tags[i] for i in result]

per, loc, org = ‘’, ‘’, ‘’

for s, t in zip(predict_text, result_tags): if t in (‘B-PER’, ‘I-PER’): per += ’ ’ + s if (t == ‘B-PER’) else s if t in (‘B-ORG’, ‘I-ORG’): org += ’ ’ + s if (t == ‘B-ORG’) else s if t in (‘B-LOC’, ‘I-LOC’): loc += ’ ’ + s if (t == ‘B-LOC’) else s

print([‘person:’ + per, ‘location:’ + loc, ‘organzation:’ + org]) 输出结果如下:

[‘person: 周恩来 陈毅, 王东’, ‘location: 埃塞俄比亚 非洲 阿尔巴尼亚’, ‘organzation: 中华人民共和国国务院 外交部’] 参考连接:https://github.com/stephen-v/zh-NER-keras

完整代码:
import numpy
from collections import Counter
from keras.preprocessing.sequence import pad_sequences
import pickle
import platform


def load_data():
    train = _parse_data(open('train_data.data', 'rb'))
    test = _parse_data(open('test_data.data', 'rb'))

    word_counts = Counter(row[0].lower() for sample in train for row in sample)
    vocab = [w for w, f in iter(word_counts.items()) if f >= 2]
    chunk_tags = ['O', 'B-PER', 'I-PER', 'B-LOC', 'I-LOC', "B-ORG", "I-ORG"]

    # save initial config data
    with open('config.pkl', 'wb') as outp:
        pickle.dump((vocab, chunk_tags), outp)

    train = _process_data(train, vocab, chunk_tags)
    test = _process_data(test, vocab, chunk_tags)
    return train, test, (vocab, chunk_tags)

def _parse_data(fh):
    #  in windows the new line is '\r\n\r\n' the space is '\r\n' . so if you use windows system,
    #  you have to use recorsponding instructions

    if platform.system() == 'Windows':
        # split_text = '\r\n'    #linux
        split_text = '\n'      #windows
    else:
        split_text = '\n'

    string = fh.read().decode('utf-8')
    data = [[row.split() for row in sample.split(split_text)] for
            sample in
            string.strip().split(split_text + split_text)]
    fh.close()
    return data

def _process_data(data, vocab, chunk_tags, maxlen=None, onehot=False):
    if maxlen is None:
        maxlen = max(len(s) for s in data)
    word2idx = dict((w, i) for i, w in enumerate(vocab))
    x = [[word2idx.get(w[0].lower(), 1) for w in s] for s in data]  # set to <unk> (index 1) if not in vocab

    y_chunk = [[chunk_tags.index(w[1]) for w in s] for s in data]

    x = pad_sequences(x, maxlen)  # left padding

    y_chunk = pad_sequences(y_chunk, maxlen, value=-1)

    if onehot:
        y_chunk = numpy.eye(len(chunk_tags), dtype='float32')[y_chunk]
    else:
        y_chunk = numpy.expand_dims(y_chunk, 2)
    return x, y_chunk


def process_data(data, vocab, maxlen=100):
    word2idx = dict((w, i) for i, w in enumerate(vocab))
    x = [word2idx.get(w[0].lower(), 1) for w in data]
    length = len(x)
    x = pad_sequences([x], maxlen)  # left padding
    return x, length



from keras.models import Sequential
from keras.layers import Embedding, Bidirectional, LSTM
from keras_contrib.layers import CRF

import pickle

EMBED_DIM = 200
BiRNN_UNITS = 200


def create_model(train=True):
    if train:
        (train_x, train_y), (test_x, test_y), (vocab, chunk_tags) = load_data()
    else:
        with open('model/config.pkl', 'rb') as inp:
            (vocab, chunk_tags) = pickle.load(inp)
    model = Sequential()
    model.add(Embedding(len(vocab), EMBED_DIM, mask_zero=True))  # Random embedding
    model.add(Bidirectional(LSTM(BiRNN_UNITS // 2, return_sequences=True)))
    crf = CRF(len(chunk_tags), sparse_target=True)
    model.add(crf)
    model.summary()
    model.compile('adam', loss=crf.loss_function, metrics=[crf.accuracy])
    if train:
        return model, (train_x, train_y), (test_x, test_y)
    else:
        return model, (vocab, chunk_tags)


if __name__=="__main__":
    EPOCHS = 10
    model, (train_x, train_y), (test_x, test_y) = create_model()
    # train model
    model.fit(train_x, train_y,batch_size=16,epochs=EPOCHS, validation_data=[test_x, test_y])
    model.save('model/crf.h5')

预测代码:

import numpy as np

model, (vocab, chunk_tags) = bilsm_crf_model.create_model(train=False)
predict_text = '中华人民共和国国务院总理周恩来在外交部长陈毅的陪同下,连续访问了埃塞俄比亚等非洲10国以及阿尔巴尼亚'
str, length = process_data.process_data(predict_text, vocab)
model.load_weights('model/crf.h5')
raw = model.predict(str)[0][-length:]
result = [np.argmax(row) for row in raw]
result_tags = [chunk_tags[i] for i in result]

per, loc, org = '', '', ''

for s, t in zip(predict_text, result_tags):
    if t in ('B-PER', 'I-PER'):
        per += ' ' + s if (t == 'B-PER') else s
    if t in ('B-ORG', 'I-ORG'):
        org += ' ' + s if (t == 'B-ORG') else s
    if t in ('B-LOC', 'I-LOC'):
        loc += ' ' + s if (t == 'B-LOC') else s

print(['person:' + per, 'location:' + loc, 'organzation:' + org])

可能出现的错误: ("‘Tensor’ object has no attribute ‘assign’",)。将process_data.py文件中的_parse_data函数windows平台下的分隔符也改为’\n’即可

参数解读

Batch_size: 定义:一次性读入多少批量的图片,不是样本。 Full Batch Learning:Batch_size=数据集大小,适用于小数据集。 Mini-batches Learning:Batch_size= N(自己设定),适用于大数据集。 Online Learning(在线学习):Batch_size=1, 如何选择Batch_size? A:一定范围内增大Batch_size: 1、 提高了内存的利用率,大矩阵乘法的并行化效率提高 2、 跑完一次epoch所需要的迭代次数减少,相同数据量的数据处理速度加快。 3、 Batch_size越大下降方向越准,引起的训练震荡越小。 4、 缺点:内存溢出、训练时间增加、收敛缓慢、局部最优,泛化性差 B:Batch_size过小:修正方向以各自样本的梯度方向修正难以达到收敛。 总结: 首先根据显存大小选择Batch_size;其次根据自己的实际情况调整Batch_size,调参重在“调”,根据实际情况选择合适的参数。设置好Batch_size后,每一次epoch记得shuffle一次,不要让网络通过相同的minibach。

内存报错方面

楼主在pyCharm上搭建深度神经网络做图像生成时, 运行代码, 报错:

Process finished with exit code -1073740791 (0xC0000409)

原因:显卡内存不足!

解决办法:将训练数据分成数量较小的batch

参考文献: http://www.cnblogs.com/lookfor404/p/9189429.html

关于算法封装参考:https://mp.weixin.qq.com/s/KBUoek-5TWQ3BklnsEx4nQ

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年09月07日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本概述
  • 基于字的模型标注:
  • CRF loss function
  • 代码实现(keras版本)
  • 完整代码:
  • 参数解读
  • 内存报错方面
相关产品与服务
批量计算
批量计算(BatchCompute,Batch)是为有大数据计算业务的企业、科研单位等提供高性价比且易用的计算服务。批量计算 Batch 可以根据用户提供的批处理规模,智能地管理作业和调动其所需的最佳资源。有了 Batch 的帮助,您可以将精力集中在如何分析和处理数据结果上。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档