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

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

UnsplashJoao Tzanno 拍攝html

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

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

讓咱們把它付諸實踐!android

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

確保可重複性

爲了保證咱們的實驗可以復現,咱們須要設置一個隨機種子:git

import numpy as np

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

獲取已標註的語料庫

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

首先,咱們下載已標註的語料庫:後端

import nltk

nltk.download('treebank')
複製代碼

而後咱們載入標記好的句子。api

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 個數據集:

  • 訓練集 至關於擬合模型的樣本數據,
  • 驗證集 用於調整分類器的參數,例如選擇網絡中神經元的個數,
  • 測試集 用於評估分類器的性能。

咱們使用大約 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)

咱們想建立一個最基本的神經網絡:多層感知器。這種線性疊層能夠經過序貫(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', [])
)
複製代碼

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

兩次迭代以後,咱們發現模型過擬合。

評估多層感知器

因爲咱們模型已經訓練好了,因此咱們能夠直接評估它:

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 庫提供了一種本地化的方法:

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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索