[譯] 利用 Keras 深度學習庫進行詞性標註教程

luochen1992發表於2018-04-28

在本教程中,你將明白怎樣使用一個簡單的 Keras 模型來訓練和評估用於多分類問題的人工神經網路。

自然語言處理 中,詞性標註是一件眾所周知的任務。它指的是將單詞按詞性分類(也稱為詞類或詞性類別)。這是一種有監督的學習方法。

人工神經網路 已成功應用於詞性標註,並且表現卓越。我們將重點關注多層感知器網路,這是一種非常流行的網路結構,被視為解決詞性標註問題的最新技術。(譯者注:對於詞性標註問題,RNN 有更好的效果)

讓我們把它付諸實踐!

在本文中,你將獲得一個關於如何在 Keras 中實現簡單的多層感知器的快速教程,並在已標註的語料庫上進行訓練。

確保可重複性

為了保證我們的實驗能夠復現,我們需要設定一個隨機種子:

import numpy as np

CUSTOM_SEED = 42
np.random.seed(CUSTOM_SEED)
複製程式碼

獲取已標註的語料庫

Penn Treebank 是一個詞性標註語料庫。Python 庫中有個示例 [NLTK](https://github.com/nltk/nltk) 包含能夠用於訓練和測試某些自然語言處理模型(NLP models)的語料庫。

首先,我們下載已標註的語料庫:

import nltk

nltk.download('treebank')
複製程式碼

然後我們載入標記好的句子。

from nltk.corpus import treebank

sentences = treebank.tagged_sents(tagset='universal')
複製程式碼

然後我們隨便挑個句子看看:

import random

print(random.choice(sentences))
複製程式碼

這是一個元組列表 (term, tag).

[('Mr.', 'NOUN'), ('Otero', 'NOUN'), (',', '.'), ('who', 'PRON'), ('apparently', 'ADV'), ('has', 'VERB'), ('an', 'DET'), ('unpublished', 'ADJ'), ('number', 'NOUN'), (',', '.'), ('also', 'ADV'), ('could', 'VERB'), ("n't", 'ADV'), ('be', 'VERB'), ('reached', 'VERB'), ('.', '.')]
複製程式碼

這是一個包含四十多個不同類別的多分類問題。 Treebank 語料庫上的詞性標註是一個眾所周知的問題,我們期望模型精度能超過 95%。

tags = set([
    tag for sentence in treebank.tagged_sents() 
    for _, tag in sentence
])
print('nb_tags: %sntags: %s' % (len(tags), tags))
複製程式碼

產生了一個:

46
{'IN', 'VBZ', '.', 'RP', 'DT', 'VB', 'RBR', 'CC', '#', ',', 'VBP', 'WP$', 'PRP', 'JJ', 
'RBS', 'LS', 'PRP$', 'WRB', 'JJS', '``', 'EX', 'POS', 'WP', 'VBN', '-LRB-', '-RRB-', 
'FW', 'MD', 'VBG', 'TO', '$', 'NNS', 'NNPS', "''", 'VBD', 'JJR', ':', 'PDT', 'SYM', 
'NNP', 'CD', 'RB', 'WDT', 'UH', 'NN', '-NONE-'}
複製程式碼

### 監督式學習的資料集預處理

我們將標記的句子劃分成 3 個資料集:

  • 訓練集 相當於擬合模型的樣本資料,
  • 驗證集 用於調整分類器的引數,例如選擇網路中神經元的個數,
  • 測試集 用於評估分類器的效能。

[譯] 利用 Keras 深度學習庫進行詞性標註教程

我們使用大約 60% 的標記句子進行訓練,20% 作為驗證集,20% 用於評估我們的模型。

train_test_cutoff = int(.80 * len(sentences)) 
training_sentences = sentences[:train_test_cutoff]
testing_sentences = sentences[train_test_cutoff:]

train_val_cutoff = int(.25 * len(training_sentences))
validation_sentences = training_sentences[:train_val_cutoff]
training_sentences = training_sentences[train_val_cutoff:]
複製程式碼

特徵工程

我們的特徵集非常簡單。 對於每一個單詞而言,我們根據提取單詞的句子建立一個特徵字典。 這些屬性包含該單詞的前後單詞以及它的字首和字尾。

def add_basic_features(sentence_terms, index):
    """ 計算基本的單詞特徵
        :param sentence_terms: [w1, w2, ...] 
        :type sentence_terms: list
        :param index: the index of the word 
        :type index: int
        :return: dict containing features
        :rtype: dict
    """
    term = sentence_terms[index]
    return {
        'nb_terms': len(sentence_terms),
        'term': term,
        'is_first': index == 0,
        'is_last': index == len(sentence_terms) - 1,
        'is_capitalized': term[0].upper() == term[0],
        'is_all_caps': term.upper() == term,
        'is_all_lower': term.lower() == term,
        'prefix-1': term[0],
        'prefix-2': term[:2],
        'prefix-3': term[:3],
        'suffix-1': term[-1],
        'suffix-2': term[-2:],
        'suffix-3': term[-3:],
        'prev_word': '' if index == 0 else sentence_terms[index - 1],
        'next_word': '' if index == len(sentence_terms) - 1 else sentence_terms[index + 1]
    }
複製程式碼

我們將句子列表對映到特徵字典列表。

def untag(tagged_sentence):
    """ 
    刪除每個標記詞語的標籤。

:param tagged_sentence: 已標註的句子
    :type tagged_sentence: list
    :return: a list of tags
    :rtype: list of strings
    """
    return [w for w, _ in tagged_sentence]

def transform_to_dataset(tagged_sentences):
    """
    將標註的句子切分為 X 和 y 以及新增一些基本特徵

:param tagged_sentences: 已標註的句子列表
    :param tagged_sentences: 元組列表之列表 (term_i, tag_i)
    :return: 
    """
    X, y = [], []

for pos_tags in tagged_sentences:
        for index, (term, class_) in enumerate(pos_tags):
            # Add basic NLP features for each sentence term
            X.append(add_basic_features(untag(pos_tags), index))
            y.append(class_)
    return X, y
複製程式碼

對於訓練、驗證和測試句子,我們將屬性分為 X(輸入變數)和 y(輸出變數)。

X_train, y_train = transform_to_dataset(training_sentences)
X_test, y_test = transform_to_dataset(testing_sentences)
X_val, y_val = transform_to_dataset(validation_sentences)
複製程式碼

特徵編碼

我們的神經網路將向量作為輸入,所以我們需要將我們的字典特徵轉換為向量。 sklearn 的內建函式 [DictVectorizer](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.DictVectorizer.html) 提供一種非常直接的方法進行向量轉換。

from sklearn.feature_extraction import DictVectorizer

# 用我們的特徵集擬合字典向量生成器
dict_vectorizer = DictVectorizer(sparse=False)
dict_vectorizer.fit(X_train + X_test + X_val)

# 將字典特徵轉換為向量
X_train = dict_vectorizer.transform(X_train)
X_test = dict_vectorizer.transform(X_test)
X_val = dict_vectorizer.transform(X_val)
複製程式碼

我們的 y 向量必須被編碼。輸出變數包含 49 個不同的字串值,它們被編碼為整數。

from sklearn.preprocessing import LabelEncoder

# 用類別列表訓練標籤編碼器
label_encoder = LabelEncoder()
label_encoder.fit(y_train + y_test + y_val)

# 將類別值編碼成整數
y_train = label_encoder.transform(y_train)
y_test = label_encoder.transform(y_test)
y_val = label_encoder.transform(y_val)
複製程式碼

然後我們需要將這些編碼值轉換為虛擬變數(獨熱編碼)。

# 將整數轉換為虛擬變數(獨熱編碼)
from keras.utils import np_utils

y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
y_val = np_utils.to_categorical(y_val)
複製程式碼

建立 Keras 模型

[Keras](https://github.com/fchollet/keras/) 是一個高階框架,用於設計和執行神經網路,它擁有多個後端像是 [TensorFlow](https://github.com/tensorflow/tensorflow/), [Theano](https://github.com/Theano/Theano) 以及 [CNTK](https://github.com/Microsoft/CNTK)

[譯] 利用 Keras 深度學習庫進行詞性標註教程

我們想建立一個最基本的神經網路:多層感知器。這種線性疊層可以通過序貫(Sequential)模型輕鬆完成。該模型將包含輸入層,隱藏層和輸出層。 為了克服過擬合,我們使用 dropout 正則化。我們設定斷開率為 20%,這意味著在訓練過程中每次更新引數時按 20% 的概率隨機斷開輸入神經元。

我們對隱藏層使用 Rectified Linear Units (ReLU) 啟用函式,因為它們是可用的最簡單的非線性啟用函式。

對於多分類問題,我們想讓神經元輸出轉換為概率,這可以使用 softmax 函式完成。我們決定使用多分類交叉熵(categorical cross-entropy)損失函式。 最後我們選擇 Adam optimizer 因為似乎它非常適合分類任務.

from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation

def build_model(input_dim, hidden_neurons, output_dim):
    """
    構建、編譯以及返回一個用於擬合/預測的 Keras 模型。
    """
    model = Sequential([
        Dense(hidden_neurons, input_dim=input_dim),
        Activation('relu'),
        Dropout(0.2),
        Dense(hidden_neurons),
        Activation('relu'),
        Dropout(0.2),
        Dense(output_dim, activation='softmax')
    ])

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
    return model
複製程式碼

在 Keras API 和 Scikit-Learn 之間建立一個包裝器

[Keras](https://github.com/fchollet/keras/) 提供了一個名為 [KerasClassifier](https://keras.io/scikit-learn-api/) 的包裝器。它實現了 Scikit-Learn 分類器介面。

所有的模型引數定義如下。我們需要提供一個返回神經網路結構的函式 (build_fn)。 隱藏的神經元的數量和批量大小的選擇非常隨意。我們將迭代次數設定為 5,因為隨著迭代次數增多,多層感知器就會開始過擬合(即使用了 Dropout Regularization)。

from keras.wrappers.scikit_learn import KerasClassifier

model_params = {
    'build_fn': build_model,
    'input_dim': X_train.shape[1],
    'hidden_neurons': 512,
    'output_dim': y_train.shape[1],
    'epochs': 5,
    'batch_size': 256,
    'verbose': 1,
    'validation_data': (X_val, y_val),
    'shuffle': True
}

clf = KerasClassifier(**model_params)
複製程式碼

訓練 Keras 模型

最後,我們在訓練集上訓練多層感知器。

hist = clf.fit(X_train, y_train)
複製程式碼

通過回撥歷史(callback history),我們能夠視覺化模型的 log lossaccuracy 隨時間的變化。

import matplotlib.pyplot as plt

def plot_model_performance(train_loss, train_acc, train_val_loss, train_val_acc):
    """  繪製模型損失和準確度隨時間變化的曲線  """

    blue= '#34495E'
    green = '#2ECC71'
    orange = '#E23B13'

    # 繪製模型損失曲線
    fig, (ax1, ax2) = plt.subplots(2, figsize=(10, 8))
    ax1.plot(range(1, len(train_loss) + 1), train_loss, blue, linewidth=5, label='training')
    ax1.plot(range(1, len(train_val_loss) + 1), train_val_loss, green, linewidth=5, label='validation')
    ax1.set_xlabel('# epoch')
    ax1.set_ylabel('loss')
    ax1.tick_params('y')
    ax1.legend(loc='upper right', shadow=False)
    ax1.set_title('Model loss through #epochs', color=orange, fontweight='bold')

    # 繪製模型準確度曲線
    ax2.plot(range(1, len(train_acc) + 1), train_acc, blue, linewidth=5, label='training')
    ax2.plot(range(1, len(train_val_acc) + 1), train_val_acc, green, linewidth=5, label='validation')
    ax2.set_xlabel('# epoch')
    ax2.set_ylabel('accuracy')
    ax2.tick_params('y')
    ax2.legend(loc='lower right', shadow=False)
    ax2.set_title('Model accuracy through #epochs', color=orange, fontweight='bold')
複製程式碼

然後,看看模型的效能:

plot_model_performance(
    train_loss=hist.history.get('loss', []),
    train_acc=hist.history.get('acc', []),
    train_val_loss=hist.history.get('val_loss', []),
    train_val_acc=hist.history.get('val_acc', [])
)
複製程式碼

[譯] 利用 Keras 深度學習庫進行詞性標註教程

模型效能隨迭代次數的變化。

兩次迭代之後,我們發現模型過擬合。

評估多層感知器

由於我們模型已經訓練好了,所以我們可以直接評估它:

score = clf.score(X_test, y_test)
print(score)

[Out] 0.95816
複製程式碼

我們在測試集上的準確率接近 96%,當你檢視我們在模型中輸入的基本特徵時,這一點令人印象非常深刻。 請記住,即使對於人類標註者來說,100% 的準確性也是不可能的。我們估計人類詞性標註的準確度大概在 98%。

模型的視覺化

from keras.utils import plot_model

plot_model(clf.model, to_file='model.png', show_shapes=True)
複製程式碼

[譯] 利用 Keras 深度學習庫進行詞性標註教程

儲存 Keras 模型

儲存 Keras 模型非常簡單,因為 Keras 庫提供了一種本地化的方法:

clf.model.save('/tmp/keras_mlp.h5')
複製程式碼

儲存了模型的結構,權重以及訓練配置(損失函式,優化器)。

資源

  • Keras: Python 深度學習庫:[doc]
  • Adam: 一種隨機優化方法:[paper]
  • Improving neural networks by preventing co-adaptation of feature detectors: [paper]

在本文中,您學習如何使用 Keras 庫定義和評估用於多分類的神經網路的準確性。 程式碼在這裡:[.py|.ipynb].


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章