如何使用Keras為自定義NER構建深度神經網絡

不靠譜的貓 發佈 2019-12-29T14:20:03+00:00

在這篇文章中,我們將學習如何使用Keras創建一個簡單的神經網絡來從非結構化文本數據中提取信息(NER)。模型架構在這裡,我們將使用BILSTM + CRF層。LSTM層用於過濾不需要的信息,將僅保留重要的特徵/信息,而CRF層用於處理序列數據。


在這篇文章中,我們將學習如何使用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 pd
df = 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.sentences

print(sentences[1:3])

這是三個句子的樣子:

單詞和標籤詞典

Keras(和大多數其他機器學習模型)期望所有id都是數字,這是節省內存的優化。我們將使用word2idx字典將每個單詞轉換為相應的整數ID,並使用tag2idx將tag轉換為整數ID。

from math import nan

words = 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 iteritems

word2idx = {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_sequences
from keras.utils import to_categorical
from sklearn.model_selection import train_test_split

maxlen = 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 data
X_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, Input
from keras.layers import LSTM, Embedding, Dense, TimeDistributed, Dropout, Bidirectional
import keras as k
from keras_contrib.layers import CRF

input = Input(shape=(140,))
word_embedding_size = 150

# Embedding Layer
model = Embedding(input_dim=n_words, output_dim=word_embedding_size, input_length=140)(input)

# BI-LSTM Layer
model = 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 Layer
model = TimeDistributed(Dense(n_tags, activation="relu"))(model)  

# CRF Layer
crf = CRF(n_tags)

out = crf(model)  # output
model = 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 ModelCheckpoint
import matplotlib.pyplot as plt

#Optimiser 
adam = k.optimizers.Adam(lr=0.0005, beta_1=0.9, beta_2=0.999)

# Compile model
model.compile(optimizer=adam, loss=crf.loss_function, metrics=[crf.accuracy, 'accuracy'])

model.summary()

# Saving the best model only
filepath="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 model
history = 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)
關鍵字: