如何使用Keras为自定义NER构建深度神经网络
在这篇文章中,我们将学习如何使用Keras创建一个简单的神经网络来从非结构化文本数据中提取信息(NER)。
模型架构
在这里,我们将使用BILSTM + CRF层。LSTM层用于过滤不需要的信息,将仅保留重要的特征/信息,而CRF层用于处理序列数据。
BI-LSTM层
BI-LSTM用于为我们的单词生成向量表示。 它以句子中的每个单词作为输入,并在两个方向(即正向和反向)上生成每个单词的向量表示,其中正向访问过去的信息而反向访问将来的信息。 然后将其与CRF层合并。
CRF层
CRF层是BI-LSTM层之上的优化。它可用于基于过去的属性标签有效地预测当前标签。
数据预处理
加载数据
在本文中,我使用机器学习数据集(https://www.kaggle.com/abhinavwalia95/entity-annotated-corpus)。对于我们的机器学习模型,我们需要一个包含“ Sentence_id” /“ Sentence”列,“ word”列和“ tag”列的data frame。Python代码如下:
import pandas as pddf = pd.read_csv("/kaggle/input/entity-annotated-corpus/ner.csv", encoding = "ISO-8859-1", error_bad_lines=False)data = df[['sentence_idx','word','tag']]data.head()
在SentenceGetter中包装输入数据
加载数据之后,我们将使用SentenceGetter类来检索带有标签的句子。Python实现如下:
class SentenceGetter(object): def __init__(self, dataset): self.n_sent = 1 self.dataset = dataset self.empty = False agg_func = lambda s: [(w, t) for w,t in zip(s["word"].values.tolist(), s["tag"].values.tolist())] self.grouped = self.dataset.groupby("sentence_idx").apply(agg_func) self.sentences = [s for s in self.grouped] def get_next(self): try: s = self.grouped["Sentence: {}".format(self.n_sent)] self.n_sent += 1 return s except: return None getter = SentenceGetter(data)sentences = getter.sentencesprint(sentences[1:3])
这是三个句子的样子:
单词和标签词典
Keras(和大多数其他机器学习模型)期望所有id都是数字,这是节省内存的优化。我们将使用word2idx字典将每个单词转换为相应的整数ID,并使用tag2idx将tag转换为整数ID。
from math import nanwords = list(set(data["word"].values))n_words = len(words)tags = []for tag in set(data["tag"].values): if tag is nan or isinstance(tag, float): tags.append('unk') else: tags.append(tag)n_tags = len(tags)from future.utils import iteritemsword2idx = {w: i for i, w in enumerate(words)}tag2idx = {t: i for i, t in enumerate(tags)}idx2tag = {v: k for k, v in iteritems(tag2idx)}
Pad Sequence
BI-LSTM层期望所有文本/句子的长度相同。我们将填充大小选择为最长句子的长度。Python代码如下:
from keras.preprocessing.sequence import pad_sequencesfrom keras.utils import to_categoricalfrom sklearn.model_selection import train_test_splitmaxlen = max([len(s) for s in sentences])X = [[word2idx[w[0]] for w in s] for s in sentences]X = pad_sequences(maxlen=maxlen, sequences=X, padding="post",value=n_words - 1)y = [[tag2idx[w[1]] for w in s] for s in sentences]y = pad_sequences(maxlen=maxlen, sequences=y, padding="post", value=tag2idx["O"])y = [to_categorical(i, num_classes=n_tags) for i in y]# Split train and test dataX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
创建模型(并了解层参数)
输入层
输入层采用形状参数,该参数是表示输入数据维数的元组。
嵌入层
基本上,它是一个字典查找,它以整数作为输入并返回关联的向量。它包含三个参数:
- input_dim:文本数据中词汇的大小,即n_words + 1
- output_dim:嵌入的维数
- input_length:输入序列的长度,即最长句子的长度
BI-LSTM层
它包含五个参数:
- units:输出空间的维数
- return_sequences:如果return_sequence = True,则返回完整的输出序列,否则,返回输出序列中的最后一个输出。
- dropout:输入线性转换要下降的单位的分数。它介于0和1之间。
- recurrent_dropout:recurrent状态的线性转换要下降的单位的分数。它介于0和1之间。
- kernel_initializer:核权重矩阵的初始化程序,用于输入的线性转换。
TimeDistributed层
它是一个包装器,允许我们对序列中的每个元素独立地应用一个层。它用于序列分类,以保持输入和输出的一对一关系。
CRF层
我们没有应用任何自定义的CRF层。我们已经将输出类的数量传递给了CRF层。
机器学习模型Python代码
from keras.models import Model, Inputfrom keras.layers import LSTM, Embedding, Dense, TimeDistributed, Dropout, Bidirectionalimport keras as kfrom keras_contrib.layers import CRFinput = Input(shape=(140,))word_embedding_size = 150# Embedding Layermodel = Embedding(input_dim=n_words, output_dim=word_embedding_size, input_length=140)(input)# BI-LSTM Layermodel = Bidirectional(LSTM(units=word_embedding_size, return_sequences=True, dropout=0.5, recurrent_dropout=0.5, kernel_initializer=k.initializers.he_normal()))(model)model = LSTM(units=word_embedding_size * 2, return_sequences=True, dropout=0.5, recurrent_dropout=0.5, kernel_initializer=k.initializers.he_normal())(model)# TimeDistributed Layermodel = TimeDistributed(Dense(n_tags, activation="relu"))(model) # CRF Layercrf = CRF(n_tags)out = crf(model) # outputmodel = Model(input, out)
拟合和评估模型
编译模型
在训练模型之前,我们需要配置学习过程。它包含三个参数:
- 优化器:它将根据看到的数据及其损失函数进行自我更新
- 损失:它将能够根据训练数据衡量其性能。
- 指标:机器学习模型在训练和测试期间要评估的指标列表。
回调列表
当且仅当验证精度提高时,它才用于将模型权重更新/保存到模型文件。它包含五个参数:
- filepath:目标模型文件的路径
- monitor:监视模型的验证准确性
- verbose:如果verbose = 1,它将显示进度条和每个epoch一行,如果verbose = 0,它将不显示任何内容,如果verbose = 2,它将只显示每个epoch一行。
- save_best_only:如果save_best_only = True,则根据监视数量的最新最佳模型将不会被覆盖。
- mode:如果我们希望将其最小化,则将监视值设为val_loss,将mode ='min'设置;如果我们要将其最大化,将set _ ='max'进行监视,将其设为val_acc。
拟合模型
它包含七个参数:
- X:输入数据
- y:目标数据
- batch_size:每个梯度更新的样本数,batch_size将默认为32。
- epochs:epoch是对所提供的整个x和y数据的迭代。训练模型的epochs数。
- validate_split:将训练数据的一部分用作验证数据。
- verbose:如果verbose = 0,它将不显示任何内容,如果verbose = 1,它将显示进度条和每个epoch一行,如果verbose = 2,它将只显示每个epoch一行。
- callbacks:评估期间要应用的回调列表。
from keras.callbacks import ModelCheckpointimport matplotlib.pyplot as plt#Optimiser adam = k.optimizers.Adam(lr=0.0005, beta_1=0.9, beta_2=0.999)# Compile modelmodel.compile(optimizer=adam, loss=crf.loss_function, metrics=[crf.accuracy, 'accuracy'])model.summary()# Saving the best model onlyfilepath="ner-bi-lstm-td-model-{val_accuracy:.2f}.hdf5"checkpoint = ModelCheckpoint(filepath, monitor='val_accuracy', verbose=1, save_best_only=True, mode='max')callbacks_list = [checkpoint]# Fit the best modelhistory = model.fit(X, np.array(y), batch_size=256, epochs=10, validation_split=0.2, verbose=1, callbacks=callbacks_list)# Plot the graph plt.style.use('ggplot')def plot_history(history): accuracy = history.history['accuracy'] val_accuracy = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] x = range(1, len(accuracy) + 1) plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.plot(x, accuracy, 'b', label='Training acc') plt.plot(x, val_accuracy, 'r', label='Validation acc') plt.title('Training and validation accuracy') plt.legend() plt.subplot(1, 2, 2) plt.plot(x, loss, 'b', label='Training loss') plt.plot(x, val_loss, 'r', label='Validation loss') plt.title('Training and validation loss') plt.legend()plot_history(history)