Python for NLP: Kerasによるマルチラベルテキスト分類

Python for NLPの連載は今回で19回目となります。ここ数回の記事では、深層学習技術に基づくかなり高度な NLP の概念を探求してきました。前回は、様々なデータ型の複数の入力を使って学習させたテキスト分類モデルを作成する方法を見ました。また、テキスト入力とメタ情報を用いて、テキスト感情予測器を開発しました。

今回は、複数の出力を持つテキスト分類モデルを開発する方法を見ます。我々は、テキストのコメントを分析し、そのコメントに関連する複数のラベルを予測するテキスト分類モデルを開発する予定である。複数ラベルの分類問題は、実は複数出力モデルのサブセットである。この記事の終わりには、あなたのデータに対して複数ラベルのテキスト分類を実行できるようになります。

この記事で説明したアプローチは、一般的なマルチラベルの分類を行うために拡張することができます。例えば、入力として画像があり、画像のカテゴリと画像の説明を予測したいような分類問題を解決することができます。

ここで、マルチクラス分類問題とマルチラベル分類問題の違いを説明することが重要です。多クラス分類問題では、1つのインスタンスやレコードは複数の出力クラスのうち1つだけに属することができます。例えば、前回研究したセンチメント分析問題では、テキストレビューは「良い」、「悪い」、「普通」のいずれかになり得る。しかし、「良い」と「普通」の両方を同時に満たすことはできない。一方、マルチラベル分類問題では、1つのインスタンスが同時に複数の出力を持つことが可能である。例えば、今回解くテキスト分類問題では、コメントは複数のタグを持つことができます。これらのタグには、「有害」、「卑猥」、「侮辱」などが同時に含まれる。

データセット

このデータセットはWikipediaのトークページ編集のコメントからなる。各コメントの出力ラベルは、toxic, severe_toxic, obscene, threat, insult, identity_hateの6つである。コメントはこれら全てのカテゴリに属することもあれば、これらのカテゴリのサブセットに属することもあり、複数ラベルの分類問題となります。

この記事のデータセットは、このKaggleのリンクからダウンロードすることができます。ここでは、160,000レコードを含む「train.csv」ファイルのみを使用します。

CSVファイルをローカルディレクトリにダウンロードします。私はこのファイルの名前を「toxic_comments.csv」に変更しました。どのような名前を付けても構いませんが、コード内でその名前を使用するようにしてください。

それでは、必要なライブラリをインポートして、データセットをアプリケーションにロードしてみましょう。以下のスクリプトは、必要なライブラリをインポートしています。

from numpy import array
from keras.preprocessing.text import one_hot
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers.core import Activation, Dropout, Dense
from keras.layers import Flatten, LSTM
from keras.layers import GlobalMaxPooling1D
from keras.models import Model
from keras.layers.embeddings import Embedding
from sklearn.model_selection import train_test_split
from keras.preprocessing.text import Tokenizer
from keras.layers import Input
from keras.layers.merge import Concatenate


import pandas as pd
import numpy as np
import re


import matplotlib.pyplot as plt


では、データセットをメモリにロードしてみましょう。

toxic_comments = pd.read_csv("/content/drive/My Drive/Colab Datasets/toxic_comments.csv")


次のスクリプトはデータセットの形状を表示し、データセットのヘッダを表示する。

print(toxic_comments.shape)


toxic_comments.head()


出力

(159571,8)


このデータセットには、159571件のレコードと8つのカラムがある。データセットのヘッダーは次のようになる。

行に NULL 値または空文字列が含まれるレコードをすべて削除してみましょう。

filter = toxic_comments["comment_text"] != ""
toxic_comments = toxic_comments[filter]
toxic_comments = toxic_comments.dropna()


comment_text` カラムには、テキストコメントが格納されている。ランダムなコメントを出力して、そのラベルを表示してみよう。

print(toxic_comments["comment_text"][168])


出力します。

You should be fired, you're a moronic wimp who is too lazy to do research. It makes me sick that people like you exist in this world.


これは明らかに有害なコメントです。このコメントと関連するラベルを見てみましょう。

print("Toxic:" + str(toxic_comments["toxic"][168]))
print("Severe_toxic:" + str(toxic_comments["severe_toxic"][168]))
print("Obscene:" + str(toxic_comments["obscene"][168]))
print("Threat:" + str(toxic_comments["threat"][168]))
print("Insult:" + str(toxic_comments["insult"][168]))
print("Identity_hate:" + str(toxic_comments["identity_hate"][168]))


出力されます。

Toxic:1
Severe_toxic:0
Obscene:0
Threat:0
Insult:1
Identity_hate:0


それでは、各ラベルのコメント数をプロットしてみましょう。そのために、まず、すべてのラベルまたは出力カラムをフィルタリングします。

toxic_comments_labels = toxic_comments[["toxic", "severe_toxic", "obscene", "threat", "insult", "identity_hate"]]
toxic_comments_labels.head()


出力

toxic_comments_labels` データフレームを使用して、異なるラベルの総コメント数を示す棒グラフをプロットしてみましょう。

fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 10
fig_size[1] = 8
plt.rcParams["figure.figsize"] = fig_size


toxic_comments_labels.sum(axis=0).plot.bar()


出力

toxic” コメントの出現頻度が最も高く、次に “obscene” と “insult” が続いていることがわかります。

次の章では、このデータセットを用いて、マルチラベル分類モデルを作成します。

マルチラベルテキスト分類モデルの作成

マルチラベル分類モデルを作るには、2つの方法がある。単一の密な出力層を用いる方法と、複数の密な出力層を用いる方法である。

最初の方法では、シグモイド活性化関数とバイナリクロスエントロピー損失関数を持つ、6つの出力を持つ単一の密な層を使うことができます。出力密層の各ニューロンは、6つの出力ラベルのうちの1つを表すことになる。シグモイド活性化関数は、各ニューロンに対して0から1の間の値を返す。いずれかのニューロンの出力値が0.5より大きい場合、そのコメントはその特定のニューロンによって表されるクラスに属すると仮定される。

2番目のアプローチでは、各ラベルに対して1つの密な出力層を作成する。出力には合計6つの密な層があることになる。各層はそれ自身のシグモイド関数を持つことになる。

単一出力層による多階層テキスト分類モデル

このセクションでは、単一の出力レイヤーを持つマルチラベルテキスト分類モデルを作成します。いつものように、テキスト分類モデルの最初のステップは、テキストをクリーニングするための関数を作成することです。

def preprocess_text(sen):
    # Remove punctuations and numbers
    sentence = re.sub('[^a-zA-Z]', ' ', sen)


# Single character removal
    sentence = re.sub(r"s+[a-zA-Z]s+", ' ', sentence)


# Removing multiple spaces
    sentence = re.sub(r's+', ' ', sentence)


return sentence


次のステップでは、入力と出力のセットを作成します。入力は comment_text カラムにあるコメントです。すべてのコメントをクリーニングして、変数 X に格納します。ラベルや出力は、既に toxic_comments_labels データフレームに格納されています。このデータフレームの値を使って、出力を y 変数に格納します。次のスクリプトを見てください。

X = []
sentences = list(toxic_comments["comment_text"])
for sen in sentences:
    X.append(preprocess_text(sen))


y = toxic_comments_labels.values


ここでは、出力ラベルはすでに一発符号化されたベクトルの形になっているので、一発符号化を行う必要はありません。

次のステップでは、データを学習セットとテストセットに分割する。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)


テキスト入力は埋め込みベクトルに変換する必要がある。単語埋め込みの詳細については、単語埋め込みの記事を参照してください。

tokenizer = Tokenizer(num_words=5000)
tokenizer.fit_on_texts(X_train)


X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)


vocab_size = len(tokenizer.word_index) + 1


maxlen = 200


X_train = pad_sequences(X_train, padding='post', maxlen=maxlen)
X_test = pad_sequences(X_test, padding='post', maxlen=maxlen)


GloVeの単語埋め込みを利用して、テキスト入力を数値に変換します。

from numpy import array
from numpy import asarray
from numpy import zeros


embeddings_dictionary = dict()


glove_file = open('/content/drive/My Drive/Colab Datasets/glove.6B.100d.txt', encoding="utf8")


for line in glove_file:
    records = line.split()
    word = records[0]
    vector_dimensions = asarray(records[1:], dtype='float32')
    embeddings_dictionary[word] = vector_dimensions
glove_file.close()


embedding_matrix = zeros((vocab_size, 100))
for word, index in tokenizer.word_index.items():
    embedding_vector = embeddings_dictionary.get(word)
    if embedding_vector is not None:
        embedding_matrix[index] = embedding_vector


以下のスクリプトでモデルを作成します。入力層、埋め込み層、LSTM層(ニューロン128個)、出力層(ニューロン6個)で構成されます。

deep_inputs = Input(shape=(maxlen,))
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(deep_inputs)
LSTM_Layer_1 = LSTM(128)(embedding_layer)
dense_layer_1 = Dense(6, activation='sigmoid')(LSTM_Layer_1)
model = Model(inputs=deep_inputs, outputs=dense_layer_1)


model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])


モデルの概要を出力してみましょう。

print(model.summary())


出力

_________________________________________________________________
Layer (type)                 Output Shape              Param #=================================================================
input_1 (InputLayer)         (None, 200)               0
_________________________________________________________________
embedding_1 (Embedding)      (None, 200, 100)          14824300
_________________________________________________________________
lstm_1 (LSTM)                (None, 128)               117248
_________________________________________________________________
dense_1 (Dense)              (None, 6)                 774
=================================================================
Total params: 14,942,322
Trainable params: 118,022
Non-trainable params: 14,824,300


次のスクリプトはニューラルネットワークのアーキテクチャを表示する。

from keras.utils import plot_model
plot_model(model, to_file='model_plot4a.png', show_shapes=True, show_layer_names=True)


出力。

上の図から、出力層には6個のニューロンを持つ密な層が1つだけあることがわかる。それでは、モデルを訓練してみよう。

history = model.fit(X_train, y_train, batch_size=128, epochs=5, verbose=1, validation_split=0.2)


ここでは5つのエポックについてモデルを学習させる。より多くのエポックでモデルを学習させ、結果が良くなるか悪くなるかを見ることができます。

5つのエポックの結果は以下のようになります。

rain on 102124 samples, validate on 25532 samples
Epoch 1/5
102124/102124 [==============================] - 245s 2ms/step - loss: 0.1437 - acc: 0.9634 - val_loss: 0.1361 - val_acc: 0.9631
Epoch 2/5
102124/102124 [==============================] - 245s 2ms/step - loss: 0.0763 - acc: 0.9753 - val_loss: 0.0621 - val_acc: 0.9788
Epoch 3/5
102124/102124 [==============================] - 243s 2ms/step - loss: 0.0588 - acc: 0.9800 - val_loss: 0.0578 - val_acc: 0.9802
Epoch 4/5
102124/102124 [==============================] - 246s 2ms/step - loss: 0.0559 - acc: 0.9807 - val_loss: 0.0571 - val_acc: 0.9801
Epoch 5/5
102124/102124 [==============================] - 245s 2ms/step - loss: 0.0528 - acc: 0.9813 - val_loss: 0.0554 - val_acc: 0.9807


それでは、テストセットでモデルを評価してみましょう。

score = model.evaluate(X_test, y_test, verbose=1)


print("Test Score:", score[0])
print("Test Accuracy:", score[1])


出力

31915/31915 [==============================] - 108s 3ms/step
Test Score: 0.054090796736467786
Test Accuracy: 0.9810642735274182


我々のモデルは約98%の精度を達成し、これは非常に印象的です。

最後に、このモデルがオーバーフィットしているかどうかを見るために、トレーニングセットとテストセットの損失と精度の値をプロットします。

import matplotlib.pyplot as plt


plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])


plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()


plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])


plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()


出力

モデルは検証セットでオーバーフィットしていないことがわかります。

複数の出力レイヤーを持つ多階層テキスト分類モデル

このセクションでは、各出力ラベルが専用の出力密層を持つ、マルチラベルテキスト分類モデルを作成します。まず、前処理関数を定義する。

def preprocess_text(sen):
    # Remove punctuations and numbers
    sentence = re.sub('[^a-zA-Z]', ' ', sen)


# Single character removal
    sentence = re.sub(r"s+[a-zA-Z]s+", ' ', sentence)


# Removing multiple spaces
    sentence = re.sub(r's+', ' ', sentence)


return sentence


第二のステップは、モデルの入力と出力を作成することである。モデルへの入力はテキストコメントで、出力は6つのラベルになります。次のスクリプトは入力層と出力層を作成します。

X = []
sentences = list(toxic_comments["comment_text"])
for sen in sentences:
    X.append(preprocess_text(sen))


y = toxic_comments[["toxic", "severe_toxic", "obscene", "threat", "insult", "identity_hate"]]


データをトレーニングセットとテストセットに分割する。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)


変数 y には6つのラベルを合成した出力が格納される。しかし、我々は各ラベルに対して個別の出力層を作りたい。そこで、トレーニングデータのラベルを格納する変数を6つ、テストデータのラベルの値を格納する変数を6つ作成します。

以下のスクリプトを見てください。

# First output
y1_train = y_train[["toxic"]].values
y1_test =  y_test[["toxic"]].values


# Second output
y2_train = y_train[["severe_toxic"]].values
y2_test =  y_test[["severe_toxic"]].values


# Third output
y3_train = y_train[["obscene"]].values
y3_test =  y_test[["obscene"]].values


# Fourth output
y4_train = y_train[["threat"]].values
y4_test =  y_test[["threat"]].values


# Fifth output
y5_train = y_train[["insult"]].values
y5_test =  y_test[["insult"]].values


# Sixth output
y6_train = y_train[["identity_hate"]].values
y6_test =  y_test[["identity_hate"]].values


次のステップは、テキスト入力を埋め込みベクトルに変換することである。次のスクリプトはそれを行うものである。

tokenizer = Tokenizer(num_words=5000)
tokenizer.fit_on_texts(X_train)


X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)


vocab_size = len(tokenizer.word_index) + 1


maxlen = 200


X_train = pad_sequences(X_train, padding='post', maxlen=maxlen)
X_test = pad_sequences(X_test, padding='post', maxlen=maxlen)


ここでもGloVeの単語埋め込みを利用します。

glove_file = open('/content/drive/My Drive/Colab Datasets/glove.6B.100d.txt', encoding="utf8")


for line in glove_file:
    records = line.split()
    word = records[0]
    vector_dimensions = asarray(records[1:], dtype='float32')
    embeddings_dictionary[word] = vector_dimensions
glove_file.close()


embedding_matrix = zeros((vocab_size, 100))
for word, index in tokenizer.word_index.items():
    embedding_vector = embeddings_dictionary.get(word)
    if embedding_vector is not None:
        embedding_matrix[index] = embedding_vector


さて、いよいよモデルを作成します。このモデルは、1つの入力層、1つの埋め込み層、そして128個のニューロンを持つ1つのLSTM層から構成される予定です。LSTM層からの出力は、6つの密な出力層の入力として使われます。各出力層は、シグモイド活性化関数を持つ1つのニューロンを持つ。各出力は対応するラベルに対して1から0までの整数値を予測する。

以下のスクリプトは、このモデルを作成するものである。

input_1 = Input(shape=(maxlen,))
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(input_1)
LSTM_Layer1 = LSTM(128)(embedding_layer)


output1 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output2 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output3 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output4 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output5 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output6 = Dense(1, activation='sigmoid')(LSTM_Layer1)


model = Model(inputs=input_1, outputs=[output1, output2, output3, output4, output5, output6])
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])


次のスクリプトはモデルの要約を表示します。

print(model.summary())


出力。

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to
==================================================================================================
input_1 (InputLayer)            (None, 200)          0
__________________________________________________________________________________________________
embedding_1 (Embedding)         (None, 200, 100)     14824300    input_1[0][0]
__________________________________________________________________________________________________
lstm_1 (LSTM)                   (None, 128)          117248      embedding_1[0][0]
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_3 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_4 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_5 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_6 (Dense)                 (None, 1)            129         lstm_1[0][0]
==================================================================================================
Total params: 14,942,322
Trainable params: 118,022
Non-trainable params: 14,824,300


そして、次のスクリプトは我々のモデルのアーキテクチャを表示する。

from keras.utils import plot_model
plot_model(model, to_file='model_plot4b.png', show_shapes=True, show_layer_names=True)


出力

6つの出力層があることがわかるだろう。上の図は、前節で作成した入力層が1つのモデルと、複数の出力層を持つモデルの違いを明確に説明しています。

では、モデルを学習させてみましょう。

history = model.fit(x=X_train, y=[y1_train, y2_train, y3_train, y4_train, y5_train, y6_train], batch_size=8192, epochs=5, verbose=1, validation_split=0.2)


5エポック実行してみましたが、検証セットでひどくオーバーフィットしてしまいました。バッチサイズを増やしましたが、それでもテストの精度はあまりよくありませんでした。オーバーフィッティングの原因の1つは、この場合、各ラベルに対して個別の出力層を持っているため、モデルの複雑さが増していることである。モデルの複雑さが増すと、しばしばオーバーフィッティングにつながる。

各エポックの結果は以下の通りである。

出力。

Train on 102124 samples, validate on 25532 samples
Epoch 1/5
102124/102124 [==============================] - 24s 239us/step - loss: 3.5116 - dense_1_loss: 0.6017 - dense_2_loss: 0.5806 - dense_3_loss: 0.6150 - dense_4_loss: 0.5585 - dense_5_loss: 0.5828 - dense_6_loss: 0.5730 - dense_1_acc: 0.9029 - dense_2_acc: 0.9842 - dense_3_acc: 0.9444 - dense_4_acc: 0.9934 - dense_5_acc: 0.9508 - dense_6_acc: 0.9870 - val_loss: 1.0369 - val_dense_1_loss: 0.3290 - val_dense_2_loss: 0.0983 - val_dense_3_loss: 0.2571 - val_dense_4_loss: 0.0595 - val_dense_5_loss: 0.1972 - val_dense_6_loss: 0.0959 - val_dense_1_acc: 0.9037 - val_dense_2_acc: 0.9901 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9966 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9901
Epoch 2/5
102124/102124 [==============================] - 20s 197us/step - loss: 0.9084 - dense_1_loss: 0.3324 - dense_2_loss: 0.0679 - dense_3_loss: 0.2172 - dense_4_loss: 0.0338 - dense_5_loss: 0.1983 - dense_6_loss: 0.0589 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8616 - val_dense_1_loss: 0.3164 - val_dense_2_loss: 0.0555 - val_dense_3_loss: 0.2127 - val_dense_4_loss: 0.0235 - val_dense_5_loss: 0.1981 - val_dense_6_loss: 0.0554 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900
Epoch 3/5
102124/102124 [==============================] - 20s 199us/step - loss: 0.8513 - dense_1_loss: 0.3179 - dense_2_loss: 0.0566 - dense_3_loss: 0.2103 - dense_4_loss: 0.0216 - dense_5_loss: 0.1960 - dense_6_loss: 0.0490 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8552 - val_dense_1_loss: 0.3158 - val_dense_2_loss: 0.0566 - val_dense_3_loss: 0.2074 - val_dense_4_loss: 0.0225 - val_dense_5_loss: 0.1960 - val_dense_6_loss: 0.0568 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900
Epoch 4/5
102124/102124 [==============================] - 20s 198us/step - loss: 0.8442 - dense_1_loss: 0.3153 - dense_2_loss: 0.0570 - dense_3_loss: 0.2061 - dense_4_loss: 0.0213 - dense_5_loss: 0.1952 - dense_6_loss: 0.0493 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8527 - val_dense_1_loss: 0.3156 - val_dense_2_loss: 0.0558 - val_dense_3_loss: 0.2074 - val_dense_4_loss: 0.0226 - val_dense_5_loss: 0.1951 - val_dense_6_loss: 0.0561 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900
Epoch 5/5
102124/102124 [==============================] - 20s 197us/step - loss: 0.8410 - dense_1_loss: 0.3146 - dense_2_loss: 0.0561 - dense_3_loss: 0.2055 - dense_4_loss: 0.0213 - dense_5_loss: 0.1948 - dense_6_loss: 0.0486 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8501 - val_dense_1_loss: 0.3153 - val_dense_2_loss: 0.0553 - val_dense_3_loss: 0.2069 - val_dense_4_loss: 0.0226 - val_dense_5_loss: 0.1948 - val_dense_6_loss: 0.0553 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900


各エポックにおいて、6つの密な層すべてについて、損失、値損失、精度、値精度の値が出力されていることがわかります。

それでは、テストセットで我々のモデルの性能を評価してみましょう。

score = model.evaluate(x=X_test, y=[y1_test, y2_test, y3_test, y4_test, y5_test, y6_test], verbose=1)


print("Test Score:", score[0])
print("Test Accuracy:", score[1])


出力

31915/31915 [==============================] - 111s 3ms/step
Test Score: 0.8471985269747015
Test Accuracy: 0.31425264998511726


テストセットでは、複数の出力レイヤーを経由して、わずか31%の精度しか達成されていません。

次のスクリプトは、最初の密な層の、訓練セットと検証セットの損失と精度の値をプロットします。

import matplotlib.pyplot as plt


plt.plot(history.history['dense_1_acc'])
plt.plot(history.history['val_dense_1_acc'])


plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()


plt.plot(history.history['dense_1_loss'])
plt.plot(history.history['val_dense_1_loss'])


plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()


出力

出力から、テスト(検証)セットの精度が最初のエポック以降に収束していないことがわかります。また、学習と検証の精度の差は非常に小さくなっています。したがって、モデルは最初のエポック以降にオーバーフィットし始め、その結果、未見のテストセットでは低いパフォーマンスを得ることができます。

結論

マルチラベルテキスト分類は、最も一般的なテキスト分類問題の1つである。この記事では、マルチラベルテキスト分類のための2つのディープラーニングアプローチを研究した。最初のアプローチでは、複数のニューロンを持つ単一の密な出力層を用い、各ニューロンが1つのラベルを表すようにした。

2つ目のアプローチでは、1つのニューロンで各ラベルのために別々の密な層を作成した。その結果、我々のケースでは、複数のニューロンを持つ単一の出力層が、複数の出力層よりもうまく機能することがわかりました。

次のステップとして、活性化関数と訓練テストの分割を変更し、この記事で紹介した結果よりも良い結果が得られるかどうかを確認することをお勧めします。

タイトルとURLをコピーしました