自然语言处理入门(三)--Keras实现BiLSTM+CRF中文命名实体识别
原标题:自然语言处理入门(三)--Keras实现BiLSTM+CRF中文命名实体识别
原文来自:CSDN 原文链接:https://blog.csdn.net/huanghaocs/article/details/96568992
本文主要是利用Keras框架搭建BiLSTM+CRF的序列标注模型,完成中文的命名实体识别任务。这里使用的数据集是提前处理过的,已经转成命名实体识别需要的“BIO”标注格式。
详细代码和数据:https://github.com/huanghao128/zh-nlp-demo
本文使用的数据是已经预处理过的,所以直接加载数据就好了,首先我们要加载字符词典文件,还有BIO标记类别的索引化。其中BIO标记中B-PER和I-PER表示人名,B-LOC和I-LOC表示地名,B-ORG和I-ORG表示机构名。
char_vocab_path = "./data/char_vocabs.txt" # 字典文件 train_data_path = "./data/train_data" # 训练数据 test_data_path = "./data/test_data" # 测试数据 special_words = ['<PAD>', '<UNK>'] # 特殊词表示 # "BIO"标记的标签 label2idx = {"O": 0, "B-PER": 1, "I-PER": 2, "B-LOC": 3, "I-LOC": 4, "B-ORG": 5, "I-ORG": 6 } # 索引和BIO标签对应 idx2label = {idx: label for label, idx in label2idx.items()} # 读取字符词典文件 with open(char_vocab_path, "r", encoding="utf8") as fo: char_vocabs = [line.strip() for line in fo] char_vocabs = special_words + char_vocabs # 字符和索引编号对应 idx2vocab = {idx: char for idx, char in enumerate(char_vocabs)} vocab2idx = {char: idx for idx, char in idx2vocab.items()}
然后加载训练和测试集,并把原始数据和BIO标记转成索引和类别编号。
# 读取数据集语料 def read_corpus(corpus_path, vocab2idx, label2idx): datas, labels = [], [] with open(corpus_path, encoding='utf-8') as fr: lines = fr.readlines() sent_, tag_ = [], [] for line in lines: if line != 'n': [char, label] = line.strip().split() sent_.append(char) tag_.append(label) else: sent_ids = [vocab2idx[char] if char in vocab2idx else vocab2idx['<UNK>'] for char in sent_] tag_ids = [label2idx[label] if label in label2idx else 0 for label in tag_] datas.append(sent_ids) labels.append(tag_ids) sent_, tag_ = [], [] return datas, labels # 加载训练集 train_datas, train_labels = read_corpus(train_data_path, vocab2idx, label2idx) # 加载测试集 test_datas, test_labels = read_corpus(test_data_path, vocab2idx, label2idx)
数据的填充,以及类别的one-hot编码。
import keras from keras.preprocessing import sequence MAX_LEN = 100 VOCAB_SIZE = len(vocab2idx) CLASS_NUMS = len(label2idx) # padding data train_datas = sequence.pad_sequences(train_datas, maxlen=MAX_LEN) train_labels = sequence.pad_sequences(train_labels, maxlen=MAX_LEN) test_datas = sequence.pad_sequences(test_datas, maxlen=MAX_LEN) test_labels = sequence.pad_sequences(test_labels, maxlen=MAX_LEN) print('x_train shape:', train_datas.shape) print('x_test shape:', test_datas.shape) # encoder one-hot train_labels = keras.utils.to_categorical(train_labels, CLASS_NUMS) test_labels = keras.utils.to_categorical(test_labels, CLASS_NUMS) print('trainlabels shape:', train_labels.shape) print('testlabels shape:', test_labels.shape)
模型构建主要使用keras自带的基础模型组装,首先是双向LSTM模型,然后输出接CRF模型,输出对每个时刻的分类。
## BiLSTM+CRF模型构建 from keras.models import Sequential from keras.models import Model from keras.layers import Masking, Embedding, Bidirectional, LSTM, Dense, Input, TimeDistributed, Activation from keras_contrib.layers import CRF from keras_contrib.losses import crf_loss from keras_contrib.metrics import crf_viterbi_accuracy from keras import backend as K EPOCHS = 20 BATCH_SIZE = 64 EMBED_DIM = 128 HIDDEN_SIZE = 64 MAX_LEN = 100 VOCAB_SIZE = len(vocab2idx) CLASS_NUMS = len(label2idx) # Input输入层 inputs = Input(shape=(MAX_LEN,), dtype='int32') # masking屏蔽层 x = Masking(mask_value=0)(inputs) # Embedding层 x = Embedding(VOCAB_SIZE, EMBED_DIM, mask_zero=True)(x) # Bi-LSTM层 x = Bidirectional(LSTM(HIDDEN_SIZE, return_sequences=True))(x) # Bi-LSTM展开输出 x = TimeDistributed(Dense(CLASS_NUMS))(x) # CRF模型层 outputs = CRF(CLASS_NUMS)(x) model = Model(inputs=inputs, outputs=outputs) model.summary() model.compile(loss=crf_loss, optimizer='adam', metrics=[crf_viterbi_accuracy]) # 训练模型 model.fit(train_datas, train_labels, epochs=EPOCHS, verbose=1, validation_split=0.1) score = model.evaluate(test_datas, test_labels, batch_size=BATCH_SIZE) print(model.metrics_names) print(score) # 保存模型 model.save("./model/ch_ner_model.h5")
结果预测是我们训练好模型后,重新加载模型,输入新的要预测文本,然后识别出文本中的命名实体。这里首先要加载字符词典,然后加载模型,之后对输入文本预处理成字符序列,然后模型预测每个时刻的输出类别,最后把类别转成BIO标记,BIO标记组合成正确的命名实体。
char_vocab_path = "./data/char_vocabs.txt" # 字典文件 model_path = "./model/ch_ner_model.h5" # 模型文件 ner_labels = {"O": 0, "B-PER": 1, "I-PER": 2, "B-LOC": 3, "I-LOC": 4, "B-ORG": 5, "I-ORG": 6} special_words = ['<PAD>', '<UNK>'] MAX_LEN = 100 with open(char_vocab_path, "r", encoding="utf8") as fo: char_vocabs = [line.strip() for line in fo] char_vocabs = special_words + char_vocabs idx2vocab = {idx: char for idx, char in enumerate(char_vocabs)} vocab2idx = {char: idx for idx, char in idx2vocab.items()} idx2label = {idx: label for label, idx in ner_labels.items()} sentence = "中华人民共和国国务院总理周恩来在外交部长陈毅的陪同下,连续访问了埃塞俄比亚等非洲10国以及阿尔巴尼亚。" sent2id = [vocab2idx[word] if word in vocab2idx else vocab2idx['<UNK>'] for word in sentence] sent2input = np.array([sent2id[:MAX_LEN] + [0] * (MAX_LEN-len(sent2id))]) model = load_model(model_path, custom_objects={'CRF': CRF}, compile=False) y_pred = model.predict(sent2input) y_label = np.argmax(y_pred, axis=2) y_label = y_label.reshape(1, -1)[0] y_ner = [index2label[i] for i in y_label][-MAX_LEN:] print(idx2label) print(sent_chars) print(sent2id) print(y_ner)
从BIO标记的结果,解析成具体的人名、地名、机构名还需要一些操作,具体的解析过程如下:
# 对预测结果进行命名实体解析和提取 def get_valid_nertag(input_data, result_tags): result_words = [] start, end =0, 1 # 实体开始结束位置标识 tag_label = "O" # 实体类型标识 for i, tag in enumerate(result_tags): if tag.startswith("B"): if tag_label != "O": # 当前实体tag之前有其他实体 result_words.append((input_data[start: end], tag_label)) # 获取实体 tag_label = tag.split("-")[1] # 获取当前实体类型 start, end = i, i+1 # 开始和结束位置变更 elif tag.startswith("I"): temp_label = tag.split("-")[1] if temp_label == tag_label: # 当前实体tag是之前实体的一部分 end += 1 # 结束位置end扩展 elif tag == "O": if tag_label != "O": # 当前位置非实体 但是之前有实体 result_words.append((input_data[start: end], tag_label)) # 获取实体 tag_label = "O" # 实体类型置"O" start, end = i, i+1 # 开始和结束位置变更 if tag_label != "O": # 最后结尾还有实体 result_words.append((input_data[start: end], tag_label)) # 获取结尾的实体 return result_words result_words = get_valid_nertag(sent_chars, y_ner) for (word, tag) in result_words: print("".join(word), tag)
贴出来的代码应该不完善,详细情况和数据集的下载可以关注github。
免责声明:本文来自互联网新闻客户端自媒体,不代表本网的观点和立场。
合作及投稿邮箱:E-mail:editor@tusaishared.com
热门资源
Python 爬虫(二)...
所谓爬虫就是模拟客户端发送网络请求,获取网络响...
TensorFlow从1到2...
原文第四篇中,我们介绍了官方的入门案例MNIST,功...
TensorFlow从1到2...
“回归”这个词,既是Regression算法的名称,也代表...
机器学习中的熵、...
熵 (entropy) 这一词最初来源于热力学。1948年,克...
TensorFlow2.0(10...
前面的博客中我们说过,在加载数据和预处理数据时...
智能在线
400-630-6780
聆听.建议反馈
E-mail: support@tusaishared.com