NLP(自然言語処理) sequential モデルを Functional APIに書き換える

これまでの記事では,sequentialモデルを用いてモデルを構築してきました。

model = Sequential()
model.add(LSTM(128, input_shape=(seq_length, 1)))
model.add(Dense(len(char_indices)+1, activation='softmax'))

sequentialモデルは単純な構造を持つため理解しやすいものですが,一つの入力と一つの出力しか持つことができません。

もし,複数の入力と出力を持つモデルを構築したい場合,Functional APIを用いる必要があります。

Functional API

ここでは,理解のためにsequentialモデルと同じ構造をFunctional APIで記述します。

input = Input(shape=(seq_length,1))

はじめに,入力層を設置します。LSTM層には(バッチサイズ,タイムステップ,入力次数)のテンソルを与えます。しかし,入力層にバッチサイズを指定する必要はなく,(タイムステップ,入力次数)を指定します。inputに入力層が格納されます。

lstm = LSTM(128,input_shape=(seq_length, 1))(input)

LSTM層を設置します。(input)はLSTM層を入力層に接続させることを表しています。

output = Dense(len(char_indices)+1, activation='softmax')(lstm)

出力層を設置します。(lstm)はDense層をLSTM層に接続させることを表しています。

model = Model(inputs=input, outputs=output)

最後にモデルを設置します。ここでは,inputを入力層,outputを出力層として指定しています。

例えば,出力層を2つ設置する場合,outputs=[output1, output2]のように指定します。

sequentialモデルとFunctional API

結果的に,ここに示す2つのモデルは同じものです。

model = Sequential()
model.add(LSTM(128, input_shape=(seq_length, 1)))
model.add(Dense(len(char_indices)+1, activation='softmax'))
input = Input(shape=(seq_length,1))
lstm = LSTM(128,input_shape=(seq_length, 1))(input)
output = Dense(len(char_indices)+1, activation='softmax')(lstm)
model = Model(inputs=input, outputs=output)

全体のコードを示します。

import numpy as np
import sys
import io
import os
import stanza
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
from tensorflow import keras
from keras.models import Model
from keras.layers import Dense, LSTM, Input
from keras.optimizers import Adam
from keras.utils import np_utils
from keras.preprocessing import sequence
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import TimeseriesGenerator
#read the text
with io.open('articles_u.txt', encoding='utf-8') as f:
    text = f.read()
texts = text.replace('eos', 'eos\n').splitlines()
#make the dictionary
tokenizer = Tokenizer()
tokenizer.fit_on_texts(texts)
char_indices = tokenizer.word_index
#make the inverted dictionary
indices_char = dict([(value, key) for (key, value) in char_indices.items()])
np.save('voa_char_indices', char_indices)
np.save('voa_indices_char', indices_char)
#vectorization
texts = tokenizer.texts_to_sequences(texts)
texts = sequence.pad_sequences(texts, maxlen=30, padding="pre", truncating="post")
#make dataset
batch_size = 100
seq_length = 5
def train_generator(start, end):
    while True:
        for step in range((end - start) // batch_size):
            x = []
            y = []
            for line in range(batch_size):
                dataset = TimeseriesGenerator(
                    texts[start+step*batch_size+line],
                    texts[start+step*batch_size+line],
                    length=seq_length,
                    batch_size=1)
                for batch in dataset:
                    X, Y = batch
                    x.extend(X[0])
                    y.extend(Y)
            x = np.reshape(x,(25*batch_size,seq_length,1))
            x = x / float(len(char_indices)+1)
            y = np_utils.to_categorical(y, len(char_indices)+1)
            yield x, y
#build the model
print('build the model....')
input = Input(shape=(seq_length,1))
lstm = LSTM(128,input_shape=(seq_length, 1))(input)
output = Dense(len(char_indices)+1, activation='softmax')(lstm)
model = Model(inputs=input, outputs=output)
optimizer = Adam(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy'])
#training
train_val_rate = 0.8
train_start = 0
train_end = round(len(texts) * train_val_rate)
val_start = train_end + 1
val_end = len(texts)
model.fit(
    train_generator(train_start, train_end),
    steps_per_epoch=(train_end - train_start) // batch_size,
    validation_data=train_generator(val_start, val_end),
    validation_steps=(val_end - val_start) // batch_size,
    epochs=100,
    verbose=2)
#save the model
model.save('u_model.h5')