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