NLP(自然言語処理)訓練データと検証データを分ける

前回の投稿,メモリに乗らない大きなデータを分割して訓練するで,ジェネレーター関数を用いる方法を学びました。ここでは,訓練データと検証データを分ける方法を学習します。

訓練データと検証データ

本来,機械学習は訓練されたモデルを用いて,未知のデータに対する適切な予測結果を生成することが目的です。そのため,一般的には用意されたデータの一部を訓練に使用し,残りのデータを検証のために使用します。

検証用データはモデルにとって未知のデータであり,訓練されたモデルがそれをどの程度正確に予測できるかをval_lossで示します。

検証データの設定

テキストの前処理やジェネレーター関数については,前回の記事を参照してください。

train_val_rate = 0.8

ここでは,単純な方法で訓練データと検証データを分けます。読み込んだテキストの最初の80パーセントを訓練データに使用し,残りの20パーセントを検証データに使用します。

train_start = 0
train_end = round(len(texts) * train_val_rate)

テキストは6672行の文で構成され,およそ640KBです。round()は小数点以下を切り捨てて値を整数にします。したがって,round(len(texts) * train_val_rate)=5337です。テキストの0から5337行を訓練データにします。

val_start = train_end + 1
val_end = len(texts)

一方で,検証データは,テキストの5338から6672行までです。

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=1)

訓練を行います。train_generator(train_start, train_end)は0から5337行を訓練データとして呼び出します。

また,validation_data=train_generator(val_start, val_end)は5338から6672行を検証データとして呼び出します。

batch_size=100なので,(train_end - train_start) // batch_size = (5337-0)//100 = 53です。これはfit()がジェネレーター関数を53回呼び出すことを意味しています。

一方で,validation_steps=(val_end - val_start) // batch_size = (6672-5338)//100 = 13 です。ジェネレーター関数は13回呼び出されます。

訓練を行います。

Epoch 100/100
53/53 - 5s - loss: 3.2926 - accuracy: 0.4244 - val_loss: 7.3059 - val_accuracy: 0.2865

新しい値であるval_lossval_accuracyが表示されました。val_lossは検証データの損失を表し,val_accuracyは検証データの精度を表します。

一方で,lossは訓練データの損失を表し,accuracyは訓練データの精度を表します。これは訓練データをそのまま用いて予測を行った場合の精度です。

残念ながら,検証データの精度はおよそ28パーセントであり,未知のデータを予測する性能は低いことが分かります。

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

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 Sequential
from keras.layers import Dense, LSTM
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....')
model = Sequential()
model.add(LSTM(128, input_shape=(seq_length, 3)))
model.add(Dense(len(char_indices)+1, activation='softmax'))
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)
    #callbacks=[early_stopping, reduce_lr])
#save the model
model.save('u_model.h5')